Merge "Make power counter always positive" into main
diff --git a/Android.bp b/Android.bp
index 273b389..69c599d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -574,6 +574,7 @@
":perfetto_src_android_stats_android_stats",
":perfetto_src_android_stats_perfetto_atoms",
":perfetto_src_base_base",
+ ":perfetto_src_base_clock_snapshots",
":perfetto_src_base_unix_socket",
":perfetto_src_base_version",
":perfetto_src_ipc_client",
@@ -820,6 +821,7 @@
":perfetto_src_android_stats_android_stats",
":perfetto_src_android_stats_perfetto_atoms",
":perfetto_src_base_base",
+ ":perfetto_src_base_clock_snapshots",
":perfetto_src_base_unix_socket",
":perfetto_src_base_version",
":perfetto_src_ipc_client",
@@ -995,6 +997,7 @@
":perfetto_src_android_stats_android_stats",
":perfetto_src_android_stats_perfetto_atoms",
":perfetto_src_base_base",
+ ":perfetto_src_base_clock_snapshots",
":perfetto_src_base_unix_socket",
":perfetto_src_base_version",
":perfetto_src_ipc_client",
@@ -1021,9 +1024,6 @@
shared_libs: [
"liblog",
],
- export_shared_lib_headers: [
- "liblog",
- ],
host_supported: true,
vendor_available: true,
product_available: true,
@@ -1357,6 +1357,7 @@
"protos/perfetto/config/android/windowmanager_config.proto",
"protos/perfetto/config/chrome/chrome_config.proto",
"protos/perfetto/config/chrome/scenario_config.proto",
+ "protos/perfetto/config/chrome/system_metrics.proto",
"protos/perfetto/config/chrome/v8_config.proto",
"protos/perfetto/config/data_source_config.proto",
"protos/perfetto/config/etw/etw_config.proto",
@@ -1418,6 +1419,7 @@
"protos/perfetto/config/android/windowmanager_config.proto",
"protos/perfetto/config/chrome/chrome_config.proto",
"protos/perfetto/config/chrome/scenario_config.proto",
+ "protos/perfetto/config/chrome/system_metrics.proto",
"protos/perfetto/config/chrome/v8_config.proto",
"protos/perfetto/config/data_source_config.proto",
"protos/perfetto/config/etw/etw_config.proto",
@@ -1545,6 +1547,7 @@
":perfetto_src_android_stats_android_stats",
":perfetto_src_android_stats_perfetto_atoms",
":perfetto_src_base_base",
+ ":perfetto_src_base_clock_snapshots",
":perfetto_src_base_test_support",
":perfetto_src_base_unix_socket",
":perfetto_src_base_version",
@@ -1856,6 +1859,7 @@
":perfetto_src_android_stats_android_stats",
":perfetto_src_android_stats_perfetto_atoms",
":perfetto_src_base_base",
+ ":perfetto_src_base_clock_snapshots",
":perfetto_src_base_test_support",
":perfetto_src_base_unix_socket",
":perfetto_src_base_version",
@@ -2423,6 +2427,7 @@
":perfetto_src_android_stats_android_stats",
":perfetto_src_android_stats_perfetto_atoms",
":perfetto_src_base_base",
+ ":perfetto_src_base_clock_snapshots",
":perfetto_src_base_test_support",
":perfetto_src_base_unix_socket",
":perfetto_src_base_version",
@@ -2467,6 +2472,9 @@
":perfetto_src_trace_processor_export_json",
":perfetto_src_trace_processor_importers_android_bugreport_android_bugreport",
":perfetto_src_trace_processor_importers_android_bugreport_android_log_event",
+ ":perfetto_src_trace_processor_importers_archive_archive",
+ ":perfetto_src_trace_processor_importers_art_method_art_method",
+ ":perfetto_src_trace_processor_importers_art_method_art_method_event",
":perfetto_src_trace_processor_importers_common_common",
":perfetto_src_trace_processor_importers_common_parser_types",
":perfetto_src_trace_processor_importers_common_trace_parser_hdr",
@@ -2478,16 +2486,18 @@
":perfetto_src_trace_processor_importers_fuchsia_fuchsia_record",
":perfetto_src_trace_processor_importers_fuchsia_full",
":perfetto_src_trace_processor_importers_fuchsia_minimal",
- ":perfetto_src_trace_processor_importers_gzip_full",
+ ":perfetto_src_trace_processor_importers_gecko_gecko_event",
":perfetto_src_trace_processor_importers_i2c_full",
":perfetto_src_trace_processor_importers_instruments_instruments",
":perfetto_src_trace_processor_importers_instruments_row",
- ":perfetto_src_trace_processor_importers_json_full",
":perfetto_src_trace_processor_importers_json_minimal",
":perfetto_src_trace_processor_importers_memory_tracker_graph_processor",
":perfetto_src_trace_processor_importers_ninja_ninja",
":perfetto_src_trace_processor_importers_perf_perf",
":perfetto_src_trace_processor_importers_perf_record",
+ ":perfetto_src_trace_processor_importers_perf_text_perf_text",
+ ":perfetto_src_trace_processor_importers_perf_text_perf_text_event",
+ ":perfetto_src_trace_processor_importers_perf_text_perf_text_sample_line_parser",
":perfetto_src_trace_processor_importers_perf_tracker",
":perfetto_src_trace_processor_importers_proto_full",
":perfetto_src_trace_processor_importers_proto_minimal",
@@ -2498,7 +2508,6 @@
":perfetto_src_trace_processor_importers_systrace_full",
":perfetto_src_trace_processor_importers_systrace_systrace_line",
":perfetto_src_trace_processor_importers_systrace_systrace_parser",
- ":perfetto_src_trace_processor_importers_zip_full",
":perfetto_src_trace_processor_lib",
":perfetto_src_trace_processor_metatrace",
":perfetto_src_trace_processor_metrics_metrics",
@@ -2540,6 +2549,7 @@
":perfetto_src_trace_processor_util_trace_blob_view_reader",
":perfetto_src_trace_processor_util_trace_type",
":perfetto_src_trace_processor_util_util",
+ ":perfetto_src_trace_processor_util_winscope_proto_mapping",
":perfetto_src_trace_processor_util_zip_reader",
":perfetto_src_traced_probes_android_game_intervention_list_android_game_intervention_list",
":perfetto_src_traced_probes_android_log_android_log",
@@ -2772,7 +2782,6 @@
"protos/perfetto/metrics/android/android_garbage_collection_unagg_metric.proto",
"protos/perfetto/metrics/android/android_oom_adjuster_metric.proto",
"protos/perfetto/metrics/android/android_sysui_notifications_blocking_calls_metric.proto",
- "protos/perfetto/metrics/android/android_trusty_workqueues.proto",
"protos/perfetto/metrics/android/anr_metric.proto",
"protos/perfetto/metrics/android/app_process_starts_metric.proto",
"protos/perfetto/metrics/android/auto_metric.proto",
@@ -2806,7 +2815,6 @@
"protos/perfetto/metrics/android/monitor_contention_metric.proto",
"protos/perfetto/metrics/android/multiuser_metric.proto",
"protos/perfetto/metrics/android/network_metric.proto",
- "protos/perfetto/metrics/android/other_traces.proto",
"protos/perfetto/metrics/android/package_list.proto",
"protos/perfetto/metrics/android/powrails_metric.proto",
"protos/perfetto/metrics/android/process_metadata.proto",
@@ -3355,6 +3363,7 @@
srcs: [
"protos/perfetto/config/chrome/chrome_config.proto",
"protos/perfetto/config/chrome/scenario_config.proto",
+ "protos/perfetto/config/chrome/system_metrics.proto",
"protos/perfetto/config/chrome/v8_config.proto",
"protos/perfetto/config/data_source_config.proto",
"protos/perfetto/config/etw/etw_config.proto",
@@ -3392,6 +3401,7 @@
out: [
"external/perfetto/protos/perfetto/config/chrome/chrome_config.gen.cc",
"external/perfetto/protos/perfetto/config/chrome/scenario_config.gen.cc",
+ "external/perfetto/protos/perfetto/config/chrome/system_metrics.gen.cc",
"external/perfetto/protos/perfetto/config/chrome/v8_config.gen.cc",
"external/perfetto/protos/perfetto/config/data_source_config.gen.cc",
"external/perfetto/protos/perfetto/config/etw/etw_config.gen.cc",
@@ -3429,6 +3439,7 @@
out: [
"external/perfetto/protos/perfetto/config/chrome/chrome_config.gen.h",
"external/perfetto/protos/perfetto/config/chrome/scenario_config.gen.h",
+ "external/perfetto/protos/perfetto/config/chrome/system_metrics.gen.h",
"external/perfetto/protos/perfetto/config/chrome/v8_config.gen.h",
"external/perfetto/protos/perfetto/config/data_source_config.gen.h",
"external/perfetto/protos/perfetto/config/etw/etw_config.gen.h",
@@ -3479,6 +3490,7 @@
"protos/perfetto/config/android/windowmanager_config.proto",
"protos/perfetto/config/chrome/chrome_config.proto",
"protos/perfetto/config/chrome/scenario_config.proto",
+ "protos/perfetto/config/chrome/system_metrics.proto",
"protos/perfetto/config/chrome/v8_config.proto",
"protos/perfetto/config/data_source_config.proto",
"protos/perfetto/config/etw/etw_config.proto",
@@ -4052,6 +4064,7 @@
srcs: [
"protos/perfetto/config/chrome/chrome_config.proto",
"protos/perfetto/config/chrome/scenario_config.proto",
+ "protos/perfetto/config/chrome/system_metrics.proto",
"protos/perfetto/config/chrome/v8_config.proto",
"protos/perfetto/config/data_source_config.proto",
"protos/perfetto/config/etw/etw_config.proto",
@@ -4088,6 +4101,7 @@
out: [
"external/perfetto/protos/perfetto/config/chrome/chrome_config.pb.cc",
"external/perfetto/protos/perfetto/config/chrome/scenario_config.pb.cc",
+ "external/perfetto/protos/perfetto/config/chrome/system_metrics.pb.cc",
"external/perfetto/protos/perfetto/config/chrome/v8_config.pb.cc",
"external/perfetto/protos/perfetto/config/data_source_config.pb.cc",
"external/perfetto/protos/perfetto/config/etw/etw_config.pb.cc",
@@ -4124,6 +4138,7 @@
out: [
"external/perfetto/protos/perfetto/config/chrome/chrome_config.pb.h",
"external/perfetto/protos/perfetto/config/chrome/scenario_config.pb.h",
+ "external/perfetto/protos/perfetto/config/chrome/system_metrics.pb.h",
"external/perfetto/protos/perfetto/config/chrome/v8_config.pb.h",
"external/perfetto/protos/perfetto/config/data_source_config.pb.h",
"external/perfetto/protos/perfetto/config/etw/etw_config.pb.h",
@@ -5099,6 +5114,7 @@
srcs: [
"protos/perfetto/config/chrome/chrome_config.proto",
"protos/perfetto/config/chrome/scenario_config.proto",
+ "protos/perfetto/config/chrome/system_metrics.proto",
"protos/perfetto/config/chrome/v8_config.proto",
"protos/perfetto/config/data_source_config.proto",
"protos/perfetto/config/etw/etw_config.proto",
@@ -5136,6 +5152,7 @@
out: [
"external/perfetto/protos/perfetto/config/chrome/chrome_config.pbzero.cc",
"external/perfetto/protos/perfetto/config/chrome/scenario_config.pbzero.cc",
+ "external/perfetto/protos/perfetto/config/chrome/system_metrics.pbzero.cc",
"external/perfetto/protos/perfetto/config/chrome/v8_config.pbzero.cc",
"external/perfetto/protos/perfetto/config/data_source_config.pbzero.cc",
"external/perfetto/protos/perfetto/config/etw/etw_config.pbzero.cc",
@@ -5173,6 +5190,7 @@
out: [
"external/perfetto/protos/perfetto/config/chrome/chrome_config.pbzero.h",
"external/perfetto/protos/perfetto/config/chrome/scenario_config.pbzero.h",
+ "external/perfetto/protos/perfetto/config/chrome/system_metrics.pbzero.h",
"external/perfetto/protos/perfetto/config/chrome/v8_config.pbzero.h",
"external/perfetto/protos/perfetto/config/data_source_config.pbzero.h",
"external/perfetto/protos/perfetto/config/etw/etw_config.pbzero.h",
@@ -5408,7 +5426,6 @@
"protos/perfetto/metrics/android/android_garbage_collection_unagg_metric.proto",
"protos/perfetto/metrics/android/android_oom_adjuster_metric.proto",
"protos/perfetto/metrics/android/android_sysui_notifications_blocking_calls_metric.proto",
- "protos/perfetto/metrics/android/android_trusty_workqueues.proto",
"protos/perfetto/metrics/android/anr_metric.proto",
"protos/perfetto/metrics/android/app_process_starts_metric.proto",
"protos/perfetto/metrics/android/auto_metric.proto",
@@ -5442,7 +5459,6 @@
"protos/perfetto/metrics/android/monitor_contention_metric.proto",
"protos/perfetto/metrics/android/multiuser_metric.proto",
"protos/perfetto/metrics/android/network_metric.proto",
- "protos/perfetto/metrics/android/other_traces.proto",
"protos/perfetto/metrics/android/package_list.proto",
"protos/perfetto/metrics/android/powrails_metric.proto",
"protos/perfetto/metrics/android/process_metadata.proto",
@@ -5504,7 +5520,6 @@
"protos/perfetto/metrics/android/android_garbage_collection_unagg_metric.proto",
"protos/perfetto/metrics/android/android_oom_adjuster_metric.proto",
"protos/perfetto/metrics/android/android_sysui_notifications_blocking_calls_metric.proto",
- "protos/perfetto/metrics/android/android_trusty_workqueues.proto",
"protos/perfetto/metrics/android/anr_metric.proto",
"protos/perfetto/metrics/android/app_process_starts_metric.proto",
"protos/perfetto/metrics/android/auto_metric.proto",
@@ -5538,7 +5553,6 @@
"protos/perfetto/metrics/android/monitor_contention_metric.proto",
"protos/perfetto/metrics/android/multiuser_metric.proto",
"protos/perfetto/metrics/android/network_metric.proto",
- "protos/perfetto/metrics/android/other_traces.proto",
"protos/perfetto/metrics/android/package_list.proto",
"protos/perfetto/metrics/android/powrails_metric.proto",
"protos/perfetto/metrics/android/process_metadata.proto",
@@ -5584,7 +5598,6 @@
"protos/perfetto/metrics/android/android_garbage_collection_unagg_metric.proto",
"protos/perfetto/metrics/android/android_oom_adjuster_metric.proto",
"protos/perfetto/metrics/android/android_sysui_notifications_blocking_calls_metric.proto",
- "protos/perfetto/metrics/android/android_trusty_workqueues.proto",
"protos/perfetto/metrics/android/anr_metric.proto",
"protos/perfetto/metrics/android/app_process_starts_metric.proto",
"protos/perfetto/metrics/android/auto_metric.proto",
@@ -5618,7 +5631,6 @@
"protos/perfetto/metrics/android/monitor_contention_metric.proto",
"protos/perfetto/metrics/android/multiuser_metric.proto",
"protos/perfetto/metrics/android/network_metric.proto",
- "protos/perfetto/metrics/android/other_traces.proto",
"protos/perfetto/metrics/android/package_list.proto",
"protos/perfetto/metrics/android/powrails_metric.proto",
"protos/perfetto/metrics/android/process_metadata.proto",
@@ -6703,6 +6715,7 @@
"protos/perfetto/config/android/windowmanager_config.proto",
"protos/perfetto/config/chrome/chrome_config.proto",
"protos/perfetto/config/chrome/scenario_config.proto",
+ "protos/perfetto/config/chrome/system_metrics.proto",
"protos/perfetto/config/chrome/v8_config.proto",
"protos/perfetto/config/data_source_config.proto",
"protos/perfetto/config/etw/etw_config.proto",
@@ -6763,9 +6776,11 @@
"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",
+ "protos/perfetto/trace/ftrace/devfreq.proto",
"protos/perfetto/trace/ftrace/dma_fence.proto",
"protos/perfetto/trace/ftrace/dmabuf_heap.proto",
"protos/perfetto/trace/ftrace/dpu.proto",
@@ -7191,9 +7206,11 @@
"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",
+ "protos/perfetto/trace/ftrace/devfreq.proto",
"protos/perfetto/trace/ftrace/dma_fence.proto",
"protos/perfetto/trace/ftrace/dmabuf_heap.proto",
"protos/perfetto/trace/ftrace/dpu.proto",
@@ -7281,9 +7298,11 @@
"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",
+ "external/perfetto/protos/perfetto/trace/ftrace/devfreq.gen.cc",
"external/perfetto/protos/perfetto/trace/ftrace/dma_fence.gen.cc",
"external/perfetto/protos/perfetto/trace/ftrace/dmabuf_heap.gen.cc",
"external/perfetto/protos/perfetto/trace/ftrace/dpu.gen.cc",
@@ -7371,9 +7390,11 @@
"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",
+ "external/perfetto/protos/perfetto/trace/ftrace/devfreq.gen.h",
"external/perfetto/protos/perfetto/trace/ftrace/dma_fence.gen.h",
"external/perfetto/protos/perfetto/trace/ftrace/dmabuf_heap.gen.h",
"external/perfetto/protos/perfetto/trace/ftrace/dpu.gen.h",
@@ -7457,9 +7478,11 @@
"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",
+ "protos/perfetto/trace/ftrace/devfreq.proto",
"protos/perfetto/trace/ftrace/dma_fence.proto",
"protos/perfetto/trace/ftrace/dmabuf_heap.proto",
"protos/perfetto/trace/ftrace/dpu.proto",
@@ -7546,9 +7569,11 @@
"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",
+ "external/perfetto/protos/perfetto/trace/ftrace/devfreq.pb.cc",
"external/perfetto/protos/perfetto/trace/ftrace/dma_fence.pb.cc",
"external/perfetto/protos/perfetto/trace/ftrace/dmabuf_heap.pb.cc",
"external/perfetto/protos/perfetto/trace/ftrace/dpu.pb.cc",
@@ -7635,9 +7660,11 @@
"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",
+ "external/perfetto/protos/perfetto/trace/ftrace/devfreq.pb.h",
"external/perfetto/protos/perfetto/trace/ftrace/dma_fence.pb.h",
"external/perfetto/protos/perfetto/trace/ftrace/dmabuf_heap.pb.h",
"external/perfetto/protos/perfetto/trace/ftrace/dpu.pb.h",
@@ -7721,9 +7748,11 @@
"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",
+ "protos/perfetto/trace/ftrace/devfreq.proto",
"protos/perfetto/trace/ftrace/dma_fence.proto",
"protos/perfetto/trace/ftrace/dmabuf_heap.proto",
"protos/perfetto/trace/ftrace/dpu.proto",
@@ -7811,9 +7840,11 @@
"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",
+ "external/perfetto/protos/perfetto/trace/ftrace/devfreq.pbzero.cc",
"external/perfetto/protos/perfetto/trace/ftrace/dma_fence.pbzero.cc",
"external/perfetto/protos/perfetto/trace/ftrace/dmabuf_heap.pbzero.cc",
"external/perfetto/protos/perfetto/trace/ftrace/dpu.pbzero.cc",
@@ -7901,9 +7932,11 @@
"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",
+ "external/perfetto/protos/perfetto/trace/ftrace/devfreq.pbzero.h",
"external/perfetto/protos/perfetto/trace/ftrace/dma_fence.pbzero.h",
"external/perfetto/protos/perfetto/trace/ftrace/dmabuf_heap.pbzero.h",
"external/perfetto/protos/perfetto/trace/ftrace/dpu.pbzero.h",
@@ -10856,6 +10889,14 @@
],
}
+// GN: //src/base:clock_snapshots
+filegroup {
+ name: "perfetto_src_base_clock_snapshots",
+ srcs: [
+ "src/base/clock_snapshots.cc",
+ ],
+}
+
// GN: //src/base/http:http
filegroup {
name: "perfetto_src_base_http_http",
@@ -11196,7 +11237,6 @@
"src/perfetto_cmd/packet_writer.cc",
"src/perfetto_cmd/perfetto_cmd.cc",
"src/perfetto_cmd/perfetto_cmd_android.cc",
- "src/perfetto_cmd/rate_limiter.cc",
],
}
@@ -11267,7 +11307,6 @@
"src/perfetto_cmd/config_unittest.cc",
"src/perfetto_cmd/packet_writer_unittest.cc",
"src/perfetto_cmd/pbtxt_to_pb_unittest.cc",
- "src/perfetto_cmd/rate_limiter_unittest.cc",
],
}
@@ -11504,6 +11543,7 @@
name: "perfetto_src_profiling_perf_producer_unittests",
srcs: [
"src/profiling/perf/event_config_unittest.cc",
+ "src/profiling/perf/frame_pointer_unwinder_unittest.cc",
"src/profiling/perf/perf_producer_unittest.cc",
"src/profiling/perf/unwind_queue_unittest.cc",
],
@@ -11529,6 +11569,7 @@
filegroup {
name: "perfetto_src_profiling_perf_unwinding",
srcs: [
+ "src/profiling/perf/frame_pointer_unwinder.cc",
"src/profiling/perf/unwinding.cc",
],
}
@@ -11682,27 +11723,6 @@
],
}
-// GN: //src/protozero:test_messages_descriptor
-genrule {
- name: "perfetto_src_protozero_test_messages_descriptor",
- srcs: [
- "src/protozero/test/example_proto/extensions.proto",
- "src/protozero/test/example_proto/library.proto",
- "src/protozero/test/example_proto/library_internals/galaxies.proto",
- "src/protozero/test/example_proto/other_package/test_messages.proto",
- "src/protozero/test/example_proto/subpackage/test_messages.proto",
- "src/protozero/test/example_proto/test_messages.proto",
- "src/protozero/test/example_proto/upper_import.proto",
- ],
- tools: [
- "aprotoc",
- ],
- cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --descriptor_set_out=$(out) $(in)",
- out: [
- "perfetto_src_protozero_test_messages_descriptor.bin",
- ],
-}
-
// GN: //src/protozero:testing_messages_cpp
filegroup {
name: "perfetto_src_protozero_testing_messages_cpp",
@@ -11763,6 +11783,27 @@
],
}
+// GN: //src/protozero:testing_messages_descriptor
+genrule {
+ name: "perfetto_src_protozero_testing_messages_descriptor",
+ srcs: [
+ "src/protozero/test/example_proto/extensions.proto",
+ "src/protozero/test/example_proto/library.proto",
+ "src/protozero/test/example_proto/library_internals/galaxies.proto",
+ "src/protozero/test/example_proto/other_package/test_messages.proto",
+ "src/protozero/test/example_proto/subpackage/test_messages.proto",
+ "src/protozero/test/example_proto/test_messages.proto",
+ "src/protozero/test/example_proto/upper_import.proto",
+ ],
+ tools: [
+ "aprotoc",
+ ],
+ cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --descriptor_set_out=$(out) $(in)",
+ out: [
+ "perfetto_src_protozero_testing_messages_descriptor.bin",
+ ],
+}
+
// GN: //src/protozero:testing_messages_lite
filegroup {
name: "perfetto_src_protozero_testing_messages_lite",
@@ -12344,7 +12385,7 @@
genrule {
name: "perfetto_src_trace_processor_gen_cc_test_messages_descriptor",
srcs: [
- ":perfetto_src_protozero_test_messages_descriptor",
+ ":perfetto_src_protozero_testing_messages_descriptor",
],
cmd: "$(location tools/gen_cc_proto_descriptor.py) --gen_dir=$(genDir) --cpp_out=$(out) $(in)",
out: [
@@ -12383,6 +12424,31 @@
],
}
+// GN: //src/trace_processor/importers/archive:archive
+filegroup {
+ name: "perfetto_src_trace_processor_importers_archive_archive",
+ srcs: [
+ "src/trace_processor/importers/archive/archive_entry.cc",
+ "src/trace_processor/importers/archive/gzip_trace_parser.cc",
+ "src/trace_processor/importers/archive/tar_trace_reader.cc",
+ "src/trace_processor/importers/archive/zip_trace_reader.cc",
+ ],
+}
+
+// GN: //src/trace_processor/importers/art_method:art_method
+filegroup {
+ name: "perfetto_src_trace_processor_importers_art_method_art_method",
+ srcs: [
+ "src/trace_processor/importers/art_method/art_method_parser_impl.cc",
+ "src/trace_processor/importers/art_method/art_method_tokenizer.cc",
+ ],
+}
+
+// GN: //src/trace_processor/importers/art_method:art_method_event
+filegroup {
+ name: "perfetto_src_trace_processor_importers_art_method_art_method_event",
+}
+
// GN: //src/trace_processor/importers/common:common
filegroup {
name: "perfetto_src_trace_processor_importers_common_common",
@@ -12405,7 +12471,6 @@
"src/trace_processor/importers/common/process_track_translation_table.cc",
"src/trace_processor/importers/common/process_tracker.cc",
"src/trace_processor/importers/common/sched_event_tracker.cc",
- "src/trace_processor/importers/common/scoped_active_trace_file.cc",
"src/trace_processor/importers/common/slice_tracker.cc",
"src/trace_processor/importers/common/slice_translation_table.cc",
"src/trace_processor/importers/common/stack_profile_tracker.cc",
@@ -12546,12 +12611,9 @@
],
}
-// GN: //src/trace_processor/importers/gzip:full
+// GN: //src/trace_processor/importers/gecko:gecko_event
filegroup {
- name: "perfetto_src_trace_processor_importers_gzip_full",
- srcs: [
- "src/trace_processor/importers/gzip/gzip_trace_parser.cc",
- ],
+ name: "perfetto_src_trace_processor_importers_gecko_gecko_event",
}
// GN: //src/trace_processor/importers/i2c:full
@@ -12577,15 +12639,6 @@
name: "perfetto_src_trace_processor_importers_instruments_row",
}
-// GN: //src/trace_processor/importers/json:full
-filegroup {
- name: "perfetto_src_trace_processor_importers_json_full",
- srcs: [
- "src/trace_processor/importers/json/json_trace_parser_impl.cc",
- "src/trace_processor/importers/json/json_trace_tokenizer.cc",
- ],
-}
-
// GN: //src/trace_processor/importers/json:minimal
filegroup {
name: "perfetto_src_trace_processor_importers_json_minimal",
@@ -12657,6 +12710,28 @@
],
}
+// GN: //src/trace_processor/importers/perf_text:perf_text
+filegroup {
+ name: "perfetto_src_trace_processor_importers_perf_text_perf_text",
+ srcs: [
+ "src/trace_processor/importers/perf_text/perf_text_trace_parser_impl.cc",
+ "src/trace_processor/importers/perf_text/perf_text_trace_tokenizer.cc",
+ ],
+}
+
+// GN: //src/trace_processor/importers/perf_text:perf_text_event
+filegroup {
+ name: "perfetto_src_trace_processor_importers_perf_text_perf_text_event",
+}
+
+// GN: //src/trace_processor/importers/perf_text:perf_text_sample_line_parser
+filegroup {
+ name: "perfetto_src_trace_processor_importers_perf_text_perf_text_sample_line_parser",
+ srcs: [
+ "src/trace_processor/importers/perf_text/perf_text_sample_line_parser.cc",
+ ],
+}
+
// GN: //src/trace_processor/importers/perf:tracker
filegroup {
name: "perfetto_src_trace_processor_importers_perf_tracker",
@@ -12939,14 +13014,6 @@
],
}
-// GN: //src/trace_processor/importers/zip:full
-filegroup {
- name: "perfetto_src_trace_processor_importers_zip_full",
- srcs: [
- "src/trace_processor/importers/zip/zip_trace_reader.cc",
- ],
-}
-
// GN: //src/trace_processor:lib
filegroup {
name: "perfetto_src_trace_processor_lib",
@@ -13064,7 +13131,6 @@
"src/trace_processor/metrics/sql/android/android_multiuser_populator.sql",
"src/trace_processor/metrics/sql/android/android_netperf.sql",
"src/trace_processor/metrics/sql/android/android_oom_adjuster.sql",
- "src/trace_processor/metrics/sql/android/android_other_traces.sql",
"src/trace_processor/metrics/sql/android/android_package_list.sql",
"src/trace_processor/metrics/sql/android/android_powrails.sql",
"src/trace_processor/metrics/sql/android/android_proxy_power.sql",
@@ -13075,7 +13141,6 @@
"src/trace_processor/metrics/sql/android/android_sysui_notifications_blocking_calls_metric.sql",
"src/trace_processor/metrics/sql/android/android_task_names.sql",
"src/trace_processor/metrics/sql/android/android_trace_quality.sql",
- "src/trace_processor/metrics/sql/android/android_trusty_workqueues.sql",
"src/trace_processor/metrics/sql/android/codec_metrics.sql",
"src/trace_processor/metrics/sql/android/composer_execution.sql",
"src/trace_processor/metrics/sql/android/composition_layers.sql",
@@ -13091,7 +13156,6 @@
"src/trace_processor/metrics/sql/android/jank/cujs_boundaries.sql",
"src/trace_processor/metrics/sql/android/jank/frames.sql",
"src/trace_processor/metrics/sql/android/jank/internal/counters.sql",
- "src/trace_processor/metrics/sql/android/jank/internal/derived_events.sql",
"src/trace_processor/metrics/sql/android/jank/internal/query_base.sql",
"src/trace_processor/metrics/sql/android/jank/internal/query_frame_slice.sql",
"src/trace_processor/metrics/sql/android/jank/params.sql",
@@ -13368,6 +13432,7 @@
"src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_slice_layout.cc",
"src/trace_processor/perfetto_sql/intrinsics/table_functions/flamegraph_construction_algorithms.cc",
"src/trace_processor/perfetto_sql/intrinsics/table_functions/table_info.cc",
+ "src/trace_processor/perfetto_sql/intrinsics/table_functions/winscope_proto_to_args_with_defaults.cc",
],
}
@@ -13485,12 +13550,14 @@
"src/trace_processor/perfetto_sql/stdlib/android/app_process_starts.sql",
"src/trace_processor/perfetto_sql/stdlib/android/auto/multiuser.sql",
"src/trace_processor/perfetto_sql/stdlib/android/battery.sql",
+ "src/trace_processor/perfetto_sql/stdlib/android/battery/charging_states.sql",
"src/trace_processor/perfetto_sql/stdlib/android/battery_stats.sql",
"src/trace_processor/perfetto_sql/stdlib/android/binder.sql",
"src/trace_processor/perfetto_sql/stdlib/android/binder_breakdown.sql",
"src/trace_processor/perfetto_sql/stdlib/android/broadcasts.sql",
"src/trace_processor/perfetto_sql/stdlib/android/cpu/cluster_type.sql",
"src/trace_processor/perfetto_sql/stdlib/android/critical_blocking_calls.sql",
+ "src/trace_processor/perfetto_sql/stdlib/android/desktop_mode.sql",
"src/trace_processor/perfetto_sql/stdlib/android/device.sql",
"src/trace_processor/perfetto_sql/stdlib/android/dvfs.sql",
"src/trace_processor/perfetto_sql/stdlib/android/frames/jank_type.sql",
@@ -13501,10 +13568,13 @@
"src/trace_processor/perfetto_sql/stdlib/android/garbage_collection.sql",
"src/trace_processor/perfetto_sql/stdlib/android/gpu/frequency.sql",
"src/trace_processor/perfetto_sql/stdlib/android/gpu/memory.sql",
+ "src/trace_processor/perfetto_sql/stdlib/android/gpu/work_period.sql",
"src/trace_processor/perfetto_sql/stdlib/android/input.sql",
"src/trace_processor/perfetto_sql/stdlib/android/io.sql",
"src/trace_processor/perfetto_sql/stdlib/android/job_scheduler.sql",
+ "src/trace_processor/perfetto_sql/stdlib/android/job_scheduler_states.sql",
"src/trace_processor/perfetto_sql/stdlib/android/memory/dmabuf.sql",
+ "src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/class_summary_tree.sql",
"src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/class_tree.sql",
"src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/dominator_class_tree.sql",
"src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/dominator_tree.sql",
@@ -13513,6 +13583,7 @@
"src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/helpers.sql",
"src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/raw_dominator_tree.sql",
"src/trace_processor/perfetto_sql/stdlib/android/memory/heap_profile/callstacks.sql",
+ "src/trace_processor/perfetto_sql/stdlib/android/memory/heap_profile/summary_tree.sql",
"src/trace_processor/perfetto_sql/stdlib/android/memory/process.sql",
"src/trace_processor/perfetto_sql/stdlib/android/monitor_contention.sql",
"src/trace_processor/perfetto_sql/stdlib/android/network_packets.sql",
@@ -13522,6 +13593,7 @@
"src/trace_processor/perfetto_sql/stdlib/android/screenshots.sql",
"src/trace_processor/perfetto_sql/stdlib/android/services.sql",
"src/trace_processor/perfetto_sql/stdlib/android/slices.sql",
+ "src/trace_processor/perfetto_sql/stdlib/android/startup/startup_breakdowns.sql",
"src/trace_processor/perfetto_sql/stdlib/android/startup/startup_events.sql",
"src/trace_processor/perfetto_sql/stdlib/android/startup/startups.sql",
"src/trace_processor/perfetto_sql/stdlib/android/startup/startups_maxsdk28.sql",
@@ -13537,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",
@@ -13568,6 +13628,7 @@
"src/trace_processor/perfetto_sql/stdlib/linux/cpu/utilization/slice.sql",
"src/trace_processor/perfetto_sql/stdlib/linux/cpu/utilization/system.sql",
"src/trace_processor/perfetto_sql/stdlib/linux/cpu/utilization/thread.sql",
+ "src/trace_processor/perfetto_sql/stdlib/linux/devfreq.sql",
"src/trace_processor/perfetto_sql/stdlib/linux/memory/general.sql",
"src/trace_processor/perfetto_sql/stdlib/linux/memory/high_watermark.sql",
"src/trace_processor/perfetto_sql/stdlib/linux/memory/process.sql",
@@ -13575,12 +13636,12 @@
"src/trace_processor/perfetto_sql/stdlib/linux/perf/spe.sql",
"src/trace_processor/perfetto_sql/stdlib/linux/threads.sql",
"src/trace_processor/perfetto_sql/stdlib/pkvm/hypervisor.sql",
- "src/trace_processor/perfetto_sql/stdlib/prelude/casts.sql",
- "src/trace_processor/perfetto_sql/stdlib/prelude/slices.sql",
- "src/trace_processor/perfetto_sql/stdlib/prelude/tables.sql",
- "src/trace_processor/perfetto_sql/stdlib/prelude/tables_views.sql",
- "src/trace_processor/perfetto_sql/stdlib/prelude/trace_bounds.sql",
- "src/trace_processor/perfetto_sql/stdlib/prelude/views.sql",
+ "src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/casts.sql",
+ "src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/slices.sql",
+ "src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/tables_views.sql",
+ "src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/views.sql",
+ "src/trace_processor/perfetto_sql/stdlib/prelude/before_eof/tables.sql",
+ "src/trace_processor/perfetto_sql/stdlib/prelude/before_eof/trace_bounds.sql",
"src/trace_processor/perfetto_sql/stdlib/sched/runnable.sql",
"src/trace_processor/perfetto_sql/stdlib/sched/states.sql",
"src/trace_processor/perfetto_sql/stdlib/sched/thread_executing_span.sql",
@@ -13593,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",
@@ -13609,13 +13671,16 @@
"src/trace_processor/perfetto_sql/stdlib/viz/summary/tracks.sql",
"src/trace_processor/perfetto_sql/stdlib/viz/threads.sql",
"src/trace_processor/perfetto_sql/stdlib/wattson/arm_dsu.sql",
+ "src/trace_processor/perfetto_sql/stdlib/wattson/cpu_freq.sql",
+ "src/trace_processor/perfetto_sql/stdlib/wattson/cpu_freq_idle.sql",
"src/trace_processor/perfetto_sql/stdlib/wattson/cpu_idle.sql",
"src/trace_processor/perfetto_sql/stdlib/wattson/cpu_split.sql",
"src/trace_processor/perfetto_sql/stdlib/wattson/curves/device.sql",
- "src/trace_processor/perfetto_sql/stdlib/wattson/curves/grouped.sql",
+ "src/trace_processor/perfetto_sql/stdlib/wattson/curves/estimates.sql",
"src/trace_processor/perfetto_sql/stdlib/wattson/curves/idle_attribution.sql",
- "src/trace_processor/perfetto_sql/stdlib/wattson/curves/ungrouped.sql",
"src/trace_processor/perfetto_sql/stdlib/wattson/curves/utils.sql",
+ "src/trace_processor/perfetto_sql/stdlib/wattson/curves/w_cpu_dependence.sql",
+ "src/trace_processor/perfetto_sql/stdlib/wattson/curves/w_dsu_dependence.sql",
"src/trace_processor/perfetto_sql/stdlib/wattson/device_infos.sql",
"src/trace_processor/perfetto_sql/stdlib/wattson/system_state.sql",
],
@@ -14051,6 +14116,11 @@
name: "perfetto_src_trace_processor_util_util",
}
+// GN: //src/trace_processor/util:winscope_proto_mapping
+filegroup {
+ name: "perfetto_src_trace_processor_util_winscope_proto_mapping",
+}
+
// GN: //src/trace_processor/util:zip_reader
filegroup {
name: "perfetto_src_trace_processor_util_zip_reader",
@@ -14728,7 +14798,6 @@
filegroup {
name: "perfetto_src_tracing_core_core",
srcs: [
- "src/tracing/core/clock_snapshots.cc",
"src/tracing/core/id_allocator.cc",
"src/tracing/core/in_process_shared_memory.cc",
"src/tracing/core/null_trace_writer.cc",
@@ -14852,8 +14921,10 @@
filegroup {
name: "perfetto_src_tracing_service_service",
srcs: [
+ "src/tracing/service/clock.cc",
"src/tracing/service/metatrace_writer.cc",
"src/tracing/service/packet_stream_validator.cc",
+ "src/tracing/service/random.cc",
"src/tracing/service/trace_buffer.cc",
"src/tracing/service/tracing_service_impl.cc",
],
@@ -14914,6 +14985,8 @@
"src/tracing/test/fake_packet.cc",
"src/tracing/test/mock_consumer.cc",
"src/tracing/test/mock_producer.cc",
+ "src/tracing/test/proxy_producer_endpoint.cc",
+ "src/tracing/test/test_shared_memory.cc",
"src/tracing/test/traced_value_test_support.cc",
],
}
@@ -15004,6 +15077,7 @@
"protos/perfetto/config/android/windowmanager_config.proto",
"protos/perfetto/config/chrome/chrome_config.proto",
"protos/perfetto/config/chrome/scenario_config.proto",
+ "protos/perfetto/config/chrome/system_metrics.proto",
"protos/perfetto/config/chrome/v8_config.proto",
"protos/perfetto/config/data_source_config.proto",
"protos/perfetto/config/etw/etw_config.proto",
@@ -15064,9 +15138,11 @@
"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",
+ "protos/perfetto/trace/ftrace/devfreq.proto",
"protos/perfetto/trace/ftrace/dma_fence.proto",
"protos/perfetto/trace/ftrace/dmabuf_heap.proto",
"protos/perfetto/trace/ftrace/dpu.proto",
@@ -15479,6 +15555,7 @@
":perfetto_src_android_stats_android_stats",
":perfetto_src_android_stats_perfetto_atoms",
":perfetto_src_base_base",
+ ":perfetto_src_base_clock_snapshots",
":perfetto_src_base_http_http",
":perfetto_src_base_http_unittests",
":perfetto_src_base_test_support",
@@ -15564,6 +15641,9 @@
":perfetto_src_trace_processor_importers_android_bugreport_android_bugreport",
":perfetto_src_trace_processor_importers_android_bugreport_android_log_event",
":perfetto_src_trace_processor_importers_android_bugreport_unittests",
+ ":perfetto_src_trace_processor_importers_archive_archive",
+ ":perfetto_src_trace_processor_importers_art_method_art_method",
+ ":perfetto_src_trace_processor_importers_art_method_art_method_event",
":perfetto_src_trace_processor_importers_common_common",
":perfetto_src_trace_processor_importers_common_parser_types",
":perfetto_src_trace_processor_importers_common_trace_parser_hdr",
@@ -15578,17 +15658,19 @@
":perfetto_src_trace_processor_importers_fuchsia_full",
":perfetto_src_trace_processor_importers_fuchsia_minimal",
":perfetto_src_trace_processor_importers_fuchsia_unittests",
- ":perfetto_src_trace_processor_importers_gzip_full",
+ ":perfetto_src_trace_processor_importers_gecko_gecko_event",
":perfetto_src_trace_processor_importers_i2c_full",
":perfetto_src_trace_processor_importers_instruments_instruments",
":perfetto_src_trace_processor_importers_instruments_row",
- ":perfetto_src_trace_processor_importers_json_full",
":perfetto_src_trace_processor_importers_json_minimal",
":perfetto_src_trace_processor_importers_memory_tracker_graph_processor",
":perfetto_src_trace_processor_importers_memory_tracker_unittests",
":perfetto_src_trace_processor_importers_ninja_ninja",
":perfetto_src_trace_processor_importers_perf_perf",
":perfetto_src_trace_processor_importers_perf_record",
+ ":perfetto_src_trace_processor_importers_perf_text_perf_text",
+ ":perfetto_src_trace_processor_importers_perf_text_perf_text_event",
+ ":perfetto_src_trace_processor_importers_perf_text_perf_text_sample_line_parser",
":perfetto_src_trace_processor_importers_perf_tracker",
":perfetto_src_trace_processor_importers_perf_unittests",
":perfetto_src_trace_processor_importers_proto_full",
@@ -15603,7 +15685,6 @@
":perfetto_src_trace_processor_importers_systrace_systrace_line",
":perfetto_src_trace_processor_importers_systrace_systrace_parser",
":perfetto_src_trace_processor_importers_systrace_unittests",
- ":perfetto_src_trace_processor_importers_zip_full",
":perfetto_src_trace_processor_lib",
":perfetto_src_trace_processor_metatrace",
":perfetto_src_trace_processor_metrics_metrics",
@@ -15663,6 +15744,7 @@
":perfetto_src_trace_processor_util_trace_type",
":perfetto_src_trace_processor_util_unittests",
":perfetto_src_trace_processor_util_util",
+ ":perfetto_src_trace_processor_util_winscope_proto_mapping",
":perfetto_src_trace_processor_util_zip_reader",
":perfetto_src_trace_redaction_trace_redaction",
":perfetto_src_trace_redaction_unittests",
@@ -16024,6 +16106,7 @@
":perfetto_src_android_stats_android_stats",
":perfetto_src_android_stats_perfetto_atoms",
":perfetto_src_base_base",
+ ":perfetto_src_base_clock_snapshots",
":perfetto_src_base_test_support",
":perfetto_src_base_unix_socket",
":perfetto_src_base_version",
@@ -16295,6 +16378,7 @@
"protos/perfetto/config/android/windowmanager_config.proto",
"protos/perfetto/config/chrome/chrome_config.proto",
"protos/perfetto/config/chrome/scenario_config.proto",
+ "protos/perfetto/config/chrome/system_metrics.proto",
"protos/perfetto/config/chrome/v8_config.proto",
"protos/perfetto/config/data_source_config.proto",
"protos/perfetto/config/etw/etw_config.proto",
@@ -16395,9 +16479,11 @@
"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",
+ "protos/perfetto/trace/ftrace/devfreq.proto",
"protos/perfetto/trace/ftrace/dma_fence.proto",
"protos/perfetto/trace/ftrace/dmabuf_heap.proto",
"protos/perfetto/trace/ftrace/dpu.proto",
@@ -16615,6 +16701,7 @@
":perfetto_protos_third_party_pprof_zero_gen",
":perfetto_protos_third_party_simpleperf_zero_gen",
":perfetto_src_base_base",
+ ":perfetto_src_base_clock_snapshots",
":perfetto_src_base_http_http",
":perfetto_src_base_unix_socket",
":perfetto_src_base_version",
@@ -16632,6 +16719,9 @@
":perfetto_src_trace_processor_export_json",
":perfetto_src_trace_processor_importers_android_bugreport_android_bugreport",
":perfetto_src_trace_processor_importers_android_bugreport_android_log_event",
+ ":perfetto_src_trace_processor_importers_archive_archive",
+ ":perfetto_src_trace_processor_importers_art_method_art_method",
+ ":perfetto_src_trace_processor_importers_art_method_art_method_event",
":perfetto_src_trace_processor_importers_common_common",
":perfetto_src_trace_processor_importers_common_parser_types",
":perfetto_src_trace_processor_importers_common_trace_parser_hdr",
@@ -16643,16 +16733,18 @@
":perfetto_src_trace_processor_importers_fuchsia_fuchsia_record",
":perfetto_src_trace_processor_importers_fuchsia_full",
":perfetto_src_trace_processor_importers_fuchsia_minimal",
- ":perfetto_src_trace_processor_importers_gzip_full",
+ ":perfetto_src_trace_processor_importers_gecko_gecko_event",
":perfetto_src_trace_processor_importers_i2c_full",
":perfetto_src_trace_processor_importers_instruments_instruments",
":perfetto_src_trace_processor_importers_instruments_row",
- ":perfetto_src_trace_processor_importers_json_full",
":perfetto_src_trace_processor_importers_json_minimal",
":perfetto_src_trace_processor_importers_memory_tracker_graph_processor",
":perfetto_src_trace_processor_importers_ninja_ninja",
":perfetto_src_trace_processor_importers_perf_perf",
":perfetto_src_trace_processor_importers_perf_record",
+ ":perfetto_src_trace_processor_importers_perf_text_perf_text",
+ ":perfetto_src_trace_processor_importers_perf_text_perf_text_event",
+ ":perfetto_src_trace_processor_importers_perf_text_perf_text_sample_line_parser",
":perfetto_src_trace_processor_importers_perf_tracker",
":perfetto_src_trace_processor_importers_proto_full",
":perfetto_src_trace_processor_importers_proto_minimal",
@@ -16663,7 +16755,6 @@
":perfetto_src_trace_processor_importers_systrace_full",
":perfetto_src_trace_processor_importers_systrace_systrace_line",
":perfetto_src_trace_processor_importers_systrace_systrace_parser",
- ":perfetto_src_trace_processor_importers_zip_full",
":perfetto_src_trace_processor_lib",
":perfetto_src_trace_processor_metatrace",
":perfetto_src_trace_processor_metrics_metrics",
@@ -16708,6 +16799,7 @@
":perfetto_src_trace_processor_util_trace_blob_view_reader",
":perfetto_src_trace_processor_util_trace_type",
":perfetto_src_trace_processor_util_util",
+ ":perfetto_src_trace_processor_util_winscope_proto_mapping",
":perfetto_src_trace_processor_util_zip_reader",
"src/trace_processor/trace_processor_shell.cc",
],
@@ -16879,16 +16971,20 @@
":perfetto_src_trace_processor_db_compare",
":perfetto_src_trace_processor_db_minimal",
":perfetto_src_trace_processor_importers_android_bugreport_android_log_event",
+ ":perfetto_src_trace_processor_importers_art_method_art_method_event",
":perfetto_src_trace_processor_importers_common_common",
":perfetto_src_trace_processor_importers_common_parser_types",
":perfetto_src_trace_processor_importers_common_trace_parser_hdr",
":perfetto_src_trace_processor_importers_etw_minimal",
":perfetto_src_trace_processor_importers_ftrace_minimal",
":perfetto_src_trace_processor_importers_fuchsia_fuchsia_record",
+ ":perfetto_src_trace_processor_importers_gecko_gecko_event",
":perfetto_src_trace_processor_importers_instruments_row",
":perfetto_src_trace_processor_importers_json_minimal",
":perfetto_src_trace_processor_importers_memory_tracker_graph_processor",
":perfetto_src_trace_processor_importers_perf_record",
+ ":perfetto_src_trace_processor_importers_perf_text_perf_text_event",
+ ":perfetto_src_trace_processor_importers_perf_text_perf_text_sample_line_parser",
":perfetto_src_trace_processor_importers_perf_tracker",
":perfetto_src_trace_processor_importers_proto_minimal",
":perfetto_src_trace_processor_importers_proto_packet_sequence_state_generation_hdr",
@@ -17034,6 +17130,7 @@
":perfetto_protos_third_party_pprof_zero_gen",
":perfetto_protos_third_party_simpleperf_zero_gen",
":perfetto_src_base_base",
+ ":perfetto_src_base_clock_snapshots",
":perfetto_src_base_version",
":perfetto_src_kernel_utils_syscall_table",
":perfetto_src_profiling_deobfuscator",
@@ -17049,6 +17146,9 @@
":perfetto_src_trace_processor_export_json",
":perfetto_src_trace_processor_importers_android_bugreport_android_bugreport",
":perfetto_src_trace_processor_importers_android_bugreport_android_log_event",
+ ":perfetto_src_trace_processor_importers_archive_archive",
+ ":perfetto_src_trace_processor_importers_art_method_art_method",
+ ":perfetto_src_trace_processor_importers_art_method_art_method_event",
":perfetto_src_trace_processor_importers_common_common",
":perfetto_src_trace_processor_importers_common_parser_types",
":perfetto_src_trace_processor_importers_common_trace_parser_hdr",
@@ -17060,16 +17160,18 @@
":perfetto_src_trace_processor_importers_fuchsia_fuchsia_record",
":perfetto_src_trace_processor_importers_fuchsia_full",
":perfetto_src_trace_processor_importers_fuchsia_minimal",
- ":perfetto_src_trace_processor_importers_gzip_full",
+ ":perfetto_src_trace_processor_importers_gecko_gecko_event",
":perfetto_src_trace_processor_importers_i2c_full",
":perfetto_src_trace_processor_importers_instruments_instruments",
":perfetto_src_trace_processor_importers_instruments_row",
- ":perfetto_src_trace_processor_importers_json_full",
":perfetto_src_trace_processor_importers_json_minimal",
":perfetto_src_trace_processor_importers_memory_tracker_graph_processor",
":perfetto_src_trace_processor_importers_ninja_ninja",
":perfetto_src_trace_processor_importers_perf_perf",
":perfetto_src_trace_processor_importers_perf_record",
+ ":perfetto_src_trace_processor_importers_perf_text_perf_text",
+ ":perfetto_src_trace_processor_importers_perf_text_perf_text_event",
+ ":perfetto_src_trace_processor_importers_perf_text_perf_text_sample_line_parser",
":perfetto_src_trace_processor_importers_perf_tracker",
":perfetto_src_trace_processor_importers_proto_full",
":perfetto_src_trace_processor_importers_proto_minimal",
@@ -17080,7 +17182,6 @@
":perfetto_src_trace_processor_importers_systrace_full",
":perfetto_src_trace_processor_importers_systrace_systrace_line",
":perfetto_src_trace_processor_importers_systrace_systrace_parser",
- ":perfetto_src_trace_processor_importers_zip_full",
":perfetto_src_trace_processor_lib",
":perfetto_src_trace_processor_metatrace",
":perfetto_src_trace_processor_metrics_metrics",
@@ -17122,6 +17223,7 @@
":perfetto_src_trace_processor_util_trace_blob_view_reader",
":perfetto_src_trace_processor_util_trace_type",
":perfetto_src_trace_processor_util_util",
+ ":perfetto_src_trace_processor_util_winscope_proto_mapping",
":perfetto_src_trace_processor_util_zip_reader",
":perfetto_src_traceconv_lib",
":perfetto_src_traceconv_main",
@@ -17310,6 +17412,7 @@
":perfetto_src_android_stats_android_stats",
":perfetto_src_android_stats_perfetto_atoms",
":perfetto_src_base_base",
+ ":perfetto_src_base_clock_snapshots",
":perfetto_src_base_unix_socket",
":perfetto_src_base_version",
":perfetto_src_ipc_client",
@@ -17511,6 +17614,7 @@
":perfetto_protos_perfetto_trace_track_event_zero_gen",
":perfetto_protos_perfetto_trace_translation_zero_gen",
":perfetto_src_base_base",
+ ":perfetto_src_base_clock_snapshots",
":perfetto_src_base_unix_socket",
":perfetto_src_base_version",
":perfetto_src_ipc_client",
@@ -17958,6 +18062,7 @@
static_libs: [
"libprotobuf-java-lite",
],
+ sdk_version: "current",
}
java_library {
@@ -17995,3 +18100,10 @@
"trigger_perfetto",
],
}
+
+filegroup {
+ name: "heap_profile",
+ srcs: [
+ "tools/heap_profile",
+ ],
+}
diff --git a/Android.bp.extras b/Android.bp.extras
index 75f8b32..3f6101b 100644
--- a/Android.bp.extras
+++ b/Android.bp.extras
@@ -212,6 +212,7 @@
static_libs: [
"libprotobuf-java-lite",
],
+ sdk_version: "current",
}
java_library {
@@ -249,3 +250,10 @@
"trigger_perfetto",
],
}
+
+filegroup {
+ name: "heap_profile",
+ srcs: [
+ "tools/heap_profile",
+ ],
+}
diff --git a/BUILD b/BUILD
index 787ebb5..d4f1f1b 100644
--- a/BUILD
+++ b/BUILD
@@ -152,6 +152,113 @@
linkstatic = True,
)
+# GN target: //src/shared_lib:libperfetto_c
+perfetto_cc_library(
+ name = "libperfetto_c",
+ srcs = [
+ ":src_android_stats_android_stats",
+ ":src_android_stats_perfetto_atoms",
+ ":src_protozero_filtering_bytecode_common",
+ ":src_protozero_filtering_bytecode_parser",
+ ":src_protozero_filtering_message_filter",
+ ":src_protozero_filtering_string_filter",
+ ":src_shared_lib_intern_map",
+ ":src_shared_lib_shared_lib",
+ ":src_tracing_client_api_without_backends",
+ ":src_tracing_common",
+ ":src_tracing_core_core",
+ ":src_tracing_in_process_backend",
+ ":src_tracing_ipc_common",
+ ":src_tracing_ipc_consumer_consumer",
+ ":src_tracing_ipc_default_socket",
+ ":src_tracing_ipc_producer_producer",
+ ":src_tracing_ipc_service_service",
+ ":src_tracing_platform_impl",
+ ":src_tracing_service_service",
+ ":src_tracing_system_backend",
+ ],
+ hdrs = [
+ ":include_perfetto_base_base",
+ ":include_perfetto_ext_base_base",
+ ":include_perfetto_ext_ipc_ipc",
+ ":include_perfetto_ext_tracing_core_core",
+ ":include_perfetto_ext_tracing_ipc_ipc",
+ ":include_perfetto_protozero_protozero",
+ ":include_perfetto_public_abi_base",
+ ":include_perfetto_public_abi_public",
+ ":include_perfetto_public_base",
+ ":include_perfetto_public_protos_protos",
+ ":include_perfetto_public_protozero",
+ ":include_perfetto_public_public",
+ ":include_perfetto_tracing_core_core",
+ ":include_perfetto_tracing_core_forward_decls",
+ ":include_perfetto_tracing_tracing",
+ ],
+ defines = [
+ "PERFETTO_SHLIB_SDK_IMPLEMENTATION",
+ ],
+ visibility = PERFETTO_CONFIG.public_visibility,
+ deps = [
+ ":perfetto_ipc",
+ ":protos_perfetto_common_cpp",
+ ":protos_perfetto_common_zero",
+ ":protos_perfetto_config_android_cpp",
+ ":protos_perfetto_config_android_zero",
+ ":protos_perfetto_config_cpp",
+ ":protos_perfetto_config_ftrace_cpp",
+ ":protos_perfetto_config_ftrace_zero",
+ ":protos_perfetto_config_gpu_cpp",
+ ":protos_perfetto_config_gpu_zero",
+ ":protos_perfetto_config_inode_file_cpp",
+ ":protos_perfetto_config_inode_file_zero",
+ ":protos_perfetto_config_interceptors_cpp",
+ ":protos_perfetto_config_interceptors_zero",
+ ":protos_perfetto_config_power_cpp",
+ ":protos_perfetto_config_power_zero",
+ ":protos_perfetto_config_process_stats_cpp",
+ ":protos_perfetto_config_process_stats_zero",
+ ":protos_perfetto_config_profiling_cpp",
+ ":protos_perfetto_config_profiling_zero",
+ ":protos_perfetto_config_statsd_cpp",
+ ":protos_perfetto_config_statsd_zero",
+ ":protos_perfetto_config_sys_stats_cpp",
+ ":protos_perfetto_config_sys_stats_zero",
+ ":protos_perfetto_config_system_info_cpp",
+ ":protos_perfetto_config_system_info_zero",
+ ":protos_perfetto_config_track_event_cpp",
+ ":protos_perfetto_config_track_event_zero",
+ ":protos_perfetto_config_zero",
+ ":protos_perfetto_ipc_cpp",
+ ":protos_perfetto_ipc_ipc",
+ ":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_cpp",
+ ":protos_perfetto_trace_track_event_zero",
+ ":protos_perfetto_trace_translation_zero",
+ ":protozero",
+ ":src_base_base",
+ ":src_base_clock_snapshots",
+ ":src_base_version",
+ ],
+ linkstatic = True,
+)
+
# GN target: //src/tools/proto_filter:proto_filter
perfetto_cc_binary(
name = "proto_filter",
@@ -223,6 +330,9 @@
":src_trace_processor_export_json",
":src_trace_processor_importers_android_bugreport_android_bugreport",
":src_trace_processor_importers_android_bugreport_android_log_event",
+ ":src_trace_processor_importers_archive_archive",
+ ":src_trace_processor_importers_art_method_art_method",
+ ":src_trace_processor_importers_art_method_art_method_event",
":src_trace_processor_importers_common_common",
":src_trace_processor_importers_common_parser_types",
":src_trace_processor_importers_common_trace_parser_hdr",
@@ -234,16 +344,20 @@
":src_trace_processor_importers_fuchsia_fuchsia_record",
":src_trace_processor_importers_fuchsia_full",
":src_trace_processor_importers_fuchsia_minimal",
- ":src_trace_processor_importers_gzip_full",
+ ":src_trace_processor_importers_gecko_gecko",
+ ":src_trace_processor_importers_gecko_gecko_event",
":src_trace_processor_importers_i2c_full",
":src_trace_processor_importers_instruments_instruments",
":src_trace_processor_importers_instruments_row",
- ":src_trace_processor_importers_json_full",
+ ":src_trace_processor_importers_json_json",
":src_trace_processor_importers_json_minimal",
":src_trace_processor_importers_memory_tracker_graph_processor",
":src_trace_processor_importers_ninja_ninja",
":src_trace_processor_importers_perf_perf",
":src_trace_processor_importers_perf_record",
+ ":src_trace_processor_importers_perf_text_perf_text",
+ ":src_trace_processor_importers_perf_text_perf_text_event",
+ ":src_trace_processor_importers_perf_text_perf_text_sample_line_parser",
":src_trace_processor_importers_perf_tracker",
":src_trace_processor_importers_proto_full",
":src_trace_processor_importers_proto_minimal",
@@ -254,7 +368,6 @@
":src_trace_processor_importers_systrace_full",
":src_trace_processor_importers_systrace_systrace_line",
":src_trace_processor_importers_systrace_systrace_parser",
- ":src_trace_processor_importers_zip_full",
":src_trace_processor_lib",
":src_trace_processor_metatrace",
":src_trace_processor_metrics_metrics",
@@ -300,6 +413,7 @@
":src_trace_processor_util_trace_blob_view_reader",
":src_trace_processor_util_trace_type",
":src_trace_processor_util_util",
+ ":src_trace_processor_util_winscope_proto_mapping",
":src_trace_processor_util_zip_reader",
],
hdrs = [
@@ -364,6 +478,7 @@
":protos_third_party_simpleperf_zero",
":protozero",
":src_base_base",
+ ":src_base_clock_snapshots",
":src_base_version",
":src_trace_processor_containers_containers",
":src_trace_processor_importers_proto_gen_cc_android_track_event_descriptor",
@@ -387,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",
@@ -577,6 +758,7 @@
":protos_third_party_statsd_config_zero",
":protozero",
":src_base_base",
+ ":src_base_clock_snapshots",
":src_base_version",
] + PERFETTO_CONFIG.deps.zlib,
linkstatic = True,
@@ -596,6 +778,7 @@
"include/perfetto/base/status.h",
"include/perfetto/base/task_runner.h",
"include/perfetto/base/template_util.h",
+ "include/perfetto/base/thread_annotations.h",
"include/perfetto/base/thread_utils.h",
"include/perfetto/base/time.h",
],
@@ -617,6 +800,7 @@
"include/perfetto/ext/base/android_utils.h",
"include/perfetto/ext/base/base64.h",
"include/perfetto/ext/base/circular_queue.h",
+ "include/perfetto/ext/base/clock_snapshots.h",
"include/perfetto/ext/base/container_annotations.h",
"include/perfetto/ext/base/crash_keys.h",
"include/perfetto/ext/base/ctrl_c_handler.h",
@@ -931,7 +1115,6 @@
name = "include_perfetto_tracing_core_core",
srcs = [
"include/perfetto/tracing/core/chrome_config.h",
- "include/perfetto/tracing/core/clock_snapshots.h",
"include/perfetto/tracing/core/data_source_config.h",
"include/perfetto/tracing/core/data_source_descriptor.h",
"include/perfetto/tracing/core/flush_flags.h",
@@ -1107,6 +1290,24 @@
linkstatic = True,
)
+# GN target: //src/base:clock_snapshots
+perfetto_cc_library(
+ name = "src_base_clock_snapshots",
+ srcs = [
+ "src/base/clock_snapshots.cc",
+ ],
+ hdrs = [
+ ":include_perfetto_base_base",
+ ":include_perfetto_ext_base_base",
+ ":include_perfetto_public_abi_base",
+ ":include_perfetto_public_base",
+ ],
+ deps = [
+ ":protos_perfetto_common_zero",
+ ],
+ linkstatic = True,
+)
+
# GN target: //src/base:unix_socket
perfetto_cc_library(
name = "src_base_unix_socket",
@@ -1241,8 +1442,6 @@
"src/perfetto_cmd/packet_writer.h",
"src/perfetto_cmd/perfetto_cmd.cc",
"src/perfetto_cmd/perfetto_cmd.h",
- "src/perfetto_cmd/rate_limiter.cc",
- "src/perfetto_cmd/rate_limiter.h",
],
)
@@ -1513,6 +1712,40 @@
],
)
+# GN target: //src/trace_processor/importers/archive:archive
+perfetto_filegroup(
+ name = "src_trace_processor_importers_archive_archive",
+ srcs = [
+ "src/trace_processor/importers/archive/archive_entry.cc",
+ "src/trace_processor/importers/archive/archive_entry.h",
+ "src/trace_processor/importers/archive/gzip_trace_parser.cc",
+ "src/trace_processor/importers/archive/gzip_trace_parser.h",
+ "src/trace_processor/importers/archive/tar_trace_reader.cc",
+ "src/trace_processor/importers/archive/tar_trace_reader.h",
+ "src/trace_processor/importers/archive/zip_trace_reader.cc",
+ "src/trace_processor/importers/archive/zip_trace_reader.h",
+ ],
+)
+
+# GN target: //src/trace_processor/importers/art_method:art_method
+perfetto_filegroup(
+ name = "src_trace_processor_importers_art_method_art_method",
+ srcs = [
+ "src/trace_processor/importers/art_method/art_method_parser_impl.cc",
+ "src/trace_processor/importers/art_method/art_method_parser_impl.h",
+ "src/trace_processor/importers/art_method/art_method_tokenizer.cc",
+ "src/trace_processor/importers/art_method/art_method_tokenizer.h",
+ ],
+)
+
+# GN target: //src/trace_processor/importers/art_method:art_method_event
+perfetto_filegroup(
+ name = "src_trace_processor_importers_art_method_art_method_event",
+ srcs = [
+ "src/trace_processor/importers/art_method/art_method_event.h",
+ ],
+)
+
# GN target: //src/trace_processor/importers/common:common
perfetto_filegroup(
name = "src_trace_processor_importers_common_common",
@@ -1557,8 +1790,6 @@
"src/trace_processor/importers/common/sched_event_state.h",
"src/trace_processor/importers/common/sched_event_tracker.cc",
"src/trace_processor/importers/common/sched_event_tracker.h",
- "src/trace_processor/importers/common/scoped_active_trace_file.cc",
- "src/trace_processor/importers/common/scoped_active_trace_file.h",
"src/trace_processor/importers/common/slice_tracker.cc",
"src/trace_processor/importers/common/slice_tracker.h",
"src/trace_processor/importers/common/slice_translation_table.cc",
@@ -1574,6 +1805,7 @@
"src/trace_processor/importers/common/trace_parser.cc",
"src/trace_processor/importers/common/track_tracker.cc",
"src/trace_processor/importers/common/track_tracker.h",
+ "src/trace_processor/importers/common/tracks.h",
"src/trace_processor/importers/common/virtual_memory_mapping.cc",
"src/trace_processor/importers/common/virtual_memory_mapping.h",
],
@@ -1703,12 +1935,22 @@
],
)
-# GN target: //src/trace_processor/importers/gzip:full
+# GN target: //src/trace_processor/importers/gecko:gecko
perfetto_filegroup(
- name = "src_trace_processor_importers_gzip_full",
+ name = "src_trace_processor_importers_gecko_gecko",
srcs = [
- "src/trace_processor/importers/gzip/gzip_trace_parser.cc",
- "src/trace_processor/importers/gzip/gzip_trace_parser.h",
+ "src/trace_processor/importers/gecko/gecko_trace_parser_impl.cc",
+ "src/trace_processor/importers/gecko/gecko_trace_parser_impl.h",
+ "src/trace_processor/importers/gecko/gecko_trace_tokenizer.cc",
+ "src/trace_processor/importers/gecko/gecko_trace_tokenizer.h",
+ ],
+)
+
+# GN target: //src/trace_processor/importers/gecko:gecko_event
+perfetto_filegroup(
+ name = "src_trace_processor_importers_gecko_gecko_event",
+ srcs = [
+ "src/trace_processor/importers/gecko/gecko_event.h",
],
)
@@ -1742,9 +1984,9 @@
],
)
-# GN target: //src/trace_processor/importers/json:full
+# GN target: //src/trace_processor/importers/json:json
perfetto_filegroup(
- name = "src_trace_processor_importers_json_full",
+ name = "src_trace_processor_importers_json_json",
srcs = [
"src/trace_processor/importers/json/json_trace_parser_impl.cc",
"src/trace_processor/importers/json/json_trace_parser_impl.h",
@@ -1851,6 +2093,34 @@
],
)
+# GN target: //src/trace_processor/importers/perf_text:perf_text
+perfetto_filegroup(
+ name = "src_trace_processor_importers_perf_text_perf_text",
+ srcs = [
+ "src/trace_processor/importers/perf_text/perf_text_trace_parser_impl.cc",
+ "src/trace_processor/importers/perf_text/perf_text_trace_parser_impl.h",
+ "src/trace_processor/importers/perf_text/perf_text_trace_tokenizer.cc",
+ "src/trace_processor/importers/perf_text/perf_text_trace_tokenizer.h",
+ ],
+)
+
+# GN target: //src/trace_processor/importers/perf_text:perf_text_event
+perfetto_filegroup(
+ name = "src_trace_processor_importers_perf_text_perf_text_event",
+ srcs = [
+ "src/trace_processor/importers/perf_text/perf_text_event.h",
+ ],
+)
+
+# GN target: //src/trace_processor/importers/perf_text:perf_text_sample_line_parser
+perfetto_filegroup(
+ name = "src_trace_processor_importers_perf_text_perf_text_sample_line_parser",
+ srcs = [
+ "src/trace_processor/importers/perf_text/perf_text_sample_line_parser.cc",
+ "src/trace_processor/importers/perf_text/perf_text_sample_line_parser.h",
+ ],
+)
+
# GN target: //src/trace_processor/importers/proto/winscope:full
perfetto_filegroup(
name = "src_trace_processor_importers_proto_winscope_full",
@@ -2127,15 +2397,6 @@
],
)
-# GN target: //src/trace_processor/importers/zip:full
-perfetto_filegroup(
- name = "src_trace_processor_importers_zip_full",
- srcs = [
- "src/trace_processor/importers/zip/zip_trace_reader.cc",
- "src/trace_processor/importers/zip/zip_trace_reader.h",
- ],
-)
-
# GN target: //src/trace_processor/metrics/sql/android:android
perfetto_filegroup(
name = "src_trace_processor_metrics_sql_android_android",
@@ -2180,7 +2441,6 @@
"src/trace_processor/metrics/sql/android/android_multiuser_populator.sql",
"src/trace_processor/metrics/sql/android/android_netperf.sql",
"src/trace_processor/metrics/sql/android/android_oom_adjuster.sql",
- "src/trace_processor/metrics/sql/android/android_other_traces.sql",
"src/trace_processor/metrics/sql/android/android_package_list.sql",
"src/trace_processor/metrics/sql/android/android_powrails.sql",
"src/trace_processor/metrics/sql/android/android_proxy_power.sql",
@@ -2191,7 +2451,6 @@
"src/trace_processor/metrics/sql/android/android_sysui_notifications_blocking_calls_metric.sql",
"src/trace_processor/metrics/sql/android/android_task_names.sql",
"src/trace_processor/metrics/sql/android/android_trace_quality.sql",
- "src/trace_processor/metrics/sql/android/android_trusty_workqueues.sql",
"src/trace_processor/metrics/sql/android/codec_metrics.sql",
"src/trace_processor/metrics/sql/android/composer_execution.sql",
"src/trace_processor/metrics/sql/android/composition_layers.sql",
@@ -2207,7 +2466,6 @@
"src/trace_processor/metrics/sql/android/jank/cujs_boundaries.sql",
"src/trace_processor/metrics/sql/android/jank/frames.sql",
"src/trace_processor/metrics/sql/android/jank/internal/counters.sql",
- "src/trace_processor/metrics/sql/android/jank/internal/derived_events.sql",
"src/trace_processor/metrics/sql/android/jank/internal/query_base.sql",
"src/trace_processor/metrics/sql/android/jank/internal/query_frame_slice.sql",
"src/trace_processor/metrics/sql/android/jank/params.sql",
@@ -2567,6 +2825,8 @@
"src/trace_processor/perfetto_sql/intrinsics/table_functions/flamegraph_construction_algorithms.h",
"src/trace_processor/perfetto_sql/intrinsics/table_functions/table_info.cc",
"src/trace_processor/perfetto_sql/intrinsics/table_functions/table_info.h",
+ "src/trace_processor/perfetto_sql/intrinsics/table_functions/winscope_proto_to_args_with_defaults.cc",
+ "src/trace_processor/perfetto_sql/intrinsics/table_functions/winscope_proto_to_args_with_defaults.h",
],
)
@@ -2636,6 +2896,14 @@
],
)
+# GN target: //src/trace_processor/perfetto_sql/stdlib/android/battery:battery
+perfetto_filegroup(
+ name = "src_trace_processor_perfetto_sql_stdlib_android_battery_battery",
+ srcs = [
+ "src/trace_processor/perfetto_sql/stdlib/android/battery/charging_states.sql",
+ ],
+)
+
# GN target: //src/trace_processor/perfetto_sql/stdlib/android/cpu:cpu
perfetto_filegroup(
name = "src_trace_processor_perfetto_sql_stdlib_android_cpu_cpu",
@@ -2661,6 +2929,7 @@
srcs = [
"src/trace_processor/perfetto_sql/stdlib/android/gpu/frequency.sql",
"src/trace_processor/perfetto_sql/stdlib/android/gpu/memory.sql",
+ "src/trace_processor/perfetto_sql/stdlib/android/gpu/work_period.sql",
],
)
@@ -2668,6 +2937,7 @@
perfetto_filegroup(
name = "src_trace_processor_perfetto_sql_stdlib_android_memory_heap_graph_heap_graph",
srcs = [
+ "src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/class_summary_tree.sql",
"src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/class_tree.sql",
"src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/dominator_class_tree.sql",
"src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/dominator_tree.sql",
@@ -2683,6 +2953,7 @@
name = "src_trace_processor_perfetto_sql_stdlib_android_memory_heap_profile_heap_profile",
srcs = [
"src/trace_processor/perfetto_sql/stdlib/android/memory/heap_profile/callstacks.sql",
+ "src/trace_processor/perfetto_sql/stdlib/android/memory/heap_profile/summary_tree.sql",
],
)
@@ -2699,6 +2970,7 @@
perfetto_filegroup(
name = "src_trace_processor_perfetto_sql_stdlib_android_startup_startup",
srcs = [
+ "src/trace_processor/perfetto_sql/stdlib/android/startup/startup_breakdowns.sql",
"src/trace_processor/perfetto_sql/stdlib/android/startup/startup_events.sql",
"src/trace_processor/perfetto_sql/stdlib/android/startup/startups.sql",
"src/trace_processor/perfetto_sql/stdlib/android/startup/startups_maxsdk28.sql",
@@ -2730,6 +3002,7 @@
"src/trace_processor/perfetto_sql/stdlib/android/binder_breakdown.sql",
"src/trace_processor/perfetto_sql/stdlib/android/broadcasts.sql",
"src/trace_processor/perfetto_sql/stdlib/android/critical_blocking_calls.sql",
+ "src/trace_processor/perfetto_sql/stdlib/android/desktop_mode.sql",
"src/trace_processor/perfetto_sql/stdlib/android/device.sql",
"src/trace_processor/perfetto_sql/stdlib/android/dvfs.sql",
"src/trace_processor/perfetto_sql/stdlib/android/freezer.sql",
@@ -2737,6 +3010,7 @@
"src/trace_processor/perfetto_sql/stdlib/android/input.sql",
"src/trace_processor/perfetto_sql/stdlib/android/io.sql",
"src/trace_processor/perfetto_sql/stdlib/android/job_scheduler.sql",
+ "src/trace_processor/perfetto_sql/stdlib/android/job_scheduler_states.sql",
"src/trace_processor/perfetto_sql/stdlib/android/monitor_contention.sql",
"src/trace_processor/perfetto_sql/stdlib/android/network_packets.sql",
"src/trace_processor/perfetto_sql/stdlib/android/oom_adjuster.sql",
@@ -2766,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",
@@ -2787,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",
@@ -2876,6 +3124,7 @@
perfetto_filegroup(
name = "src_trace_processor_perfetto_sql_stdlib_linux_linux",
srcs = [
+ "src/trace_processor/perfetto_sql/stdlib/linux/devfreq.sql",
"src/trace_processor/perfetto_sql/stdlib/linux/threads.sql",
],
)
@@ -2888,17 +3137,29 @@
],
)
+# GN target: //src/trace_processor/perfetto_sql/stdlib/prelude/after_eof:after_eof
+perfetto_filegroup(
+ name = "src_trace_processor_perfetto_sql_stdlib_prelude_after_eof_after_eof",
+ srcs = [
+ "src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/casts.sql",
+ "src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/slices.sql",
+ "src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/tables_views.sql",
+ "src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/views.sql",
+ ],
+)
+
+# GN target: //src/trace_processor/perfetto_sql/stdlib/prelude/before_eof:before_eof
+perfetto_filegroup(
+ name = "src_trace_processor_perfetto_sql_stdlib_prelude_before_eof_before_eof",
+ srcs = [
+ "src/trace_processor/perfetto_sql/stdlib/prelude/before_eof/tables.sql",
+ "src/trace_processor/perfetto_sql/stdlib/prelude/before_eof/trace_bounds.sql",
+ ],
+)
+
# GN target: //src/trace_processor/perfetto_sql/stdlib/prelude:prelude
perfetto_filegroup(
name = "src_trace_processor_perfetto_sql_stdlib_prelude_prelude",
- srcs = [
- "src/trace_processor/perfetto_sql/stdlib/prelude/casts.sql",
- "src/trace_processor/perfetto_sql/stdlib/prelude/slices.sql",
- "src/trace_processor/perfetto_sql/stdlib/prelude/tables.sql",
- "src/trace_processor/perfetto_sql/stdlib/prelude/tables_views.sql",
- "src/trace_processor/perfetto_sql/stdlib/prelude/trace_bounds.sql",
- "src/trace_processor/perfetto_sql/stdlib/prelude/views.sql",
- ],
)
# GN target: //src/trace_processor/perfetto_sql/stdlib/sched:sched
@@ -2924,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",
],
)
@@ -2989,13 +3251,16 @@
name = "src_trace_processor_perfetto_sql_stdlib_wattson_wattson",
srcs = [
"src/trace_processor/perfetto_sql/stdlib/wattson/arm_dsu.sql",
+ "src/trace_processor/perfetto_sql/stdlib/wattson/cpu_freq.sql",
+ "src/trace_processor/perfetto_sql/stdlib/wattson/cpu_freq_idle.sql",
"src/trace_processor/perfetto_sql/stdlib/wattson/cpu_idle.sql",
"src/trace_processor/perfetto_sql/stdlib/wattson/cpu_split.sql",
"src/trace_processor/perfetto_sql/stdlib/wattson/curves/device.sql",
- "src/trace_processor/perfetto_sql/stdlib/wattson/curves/grouped.sql",
+ "src/trace_processor/perfetto_sql/stdlib/wattson/curves/estimates.sql",
"src/trace_processor/perfetto_sql/stdlib/wattson/curves/idle_attribution.sql",
- "src/trace_processor/perfetto_sql/stdlib/wattson/curves/ungrouped.sql",
"src/trace_processor/perfetto_sql/stdlib/wattson/curves/utils.sql",
+ "src/trace_processor/perfetto_sql/stdlib/wattson/curves/w_cpu_dependence.sql",
+ "src/trace_processor/perfetto_sql/stdlib/wattson/curves/w_dsu_dependence.sql",
"src/trace_processor/perfetto_sql/stdlib/wattson/device_infos.sql",
"src/trace_processor/perfetto_sql/stdlib/wattson/system_state.sql",
],
@@ -3007,6 +3272,7 @@
deps = [
":src_trace_processor_perfetto_sql_stdlib_android_android",
":src_trace_processor_perfetto_sql_stdlib_android_auto_auto",
+ ":src_trace_processor_perfetto_sql_stdlib_android_battery_battery",
":src_trace_processor_perfetto_sql_stdlib_android_cpu_cpu",
":src_trace_processor_perfetto_sql_stdlib_android_frames_frames",
":src_trace_processor_perfetto_sql_stdlib_android_gpu_gpu",
@@ -3017,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",
@@ -3029,6 +3293,8 @@
":src_trace_processor_perfetto_sql_stdlib_linux_memory_memory",
":src_trace_processor_perfetto_sql_stdlib_linux_perf_perf",
":src_trace_processor_perfetto_sql_stdlib_pkvm_pkvm",
+ ":src_trace_processor_perfetto_sql_stdlib_prelude_after_eof_after_eof",
+ ":src_trace_processor_perfetto_sql_stdlib_prelude_before_eof_before_eof",
":src_trace_processor_perfetto_sql_stdlib_prelude_prelude",
":src_trace_processor_perfetto_sql_stdlib_sched_sched",
":src_trace_processor_perfetto_sql_stdlib_slices_slices",
@@ -3379,6 +3645,14 @@
],
)
+# GN target: //src/trace_processor/util:winscope_proto_mapping
+perfetto_filegroup(
+ name = "src_trace_processor_util_winscope_proto_mapping",
+ srcs = [
+ "src/trace_processor/util/winscope_proto_mapping.h",
+ ],
+)
+
# GN target: //src/trace_processor/util:zip_reader
perfetto_filegroup(
name = "src_trace_processor_util_zip_reader",
@@ -3770,7 +4044,6 @@
perfetto_filegroup(
name = "src_tracing_core_core",
srcs = [
- "src/tracing/core/clock_snapshots.cc",
"src/tracing/core/id_allocator.cc",
"src/tracing/core/id_allocator.h",
"src/tracing/core/in_process_shared_memory.cc",
@@ -3846,11 +4119,16 @@
perfetto_filegroup(
name = "src_tracing_service_service",
srcs = [
+ "src/tracing/service/clock.cc",
+ "src/tracing/service/clock.h",
+ "src/tracing/service/dependencies.h",
"src/tracing/service/histogram.h",
"src/tracing/service/metatrace_writer.cc",
"src/tracing/service/metatrace_writer.h",
"src/tracing/service/packet_stream_validator.cc",
"src/tracing/service/packet_stream_validator.h",
+ "src/tracing/service/random.cc",
+ "src/tracing/service/random.h",
"src/tracing/service/trace_buffer.cc",
"src/tracing/service/trace_buffer.h",
"src/tracing/service/tracing_service_impl.cc",
@@ -4598,6 +4876,7 @@
srcs = [
"protos/perfetto/config/chrome/chrome_config.proto",
"protos/perfetto/config/chrome/scenario_config.proto",
+ "protos/perfetto/config/chrome/system_metrics.proto",
"protos/perfetto/config/chrome/v8_config.proto",
"protos/perfetto/config/data_source_config.proto",
"protos/perfetto/config/etw/etw_config.proto",
@@ -4875,7 +5154,6 @@
"protos/perfetto/metrics/android/android_garbage_collection_unagg_metric.proto",
"protos/perfetto/metrics/android/android_oom_adjuster_metric.proto",
"protos/perfetto/metrics/android/android_sysui_notifications_blocking_calls_metric.proto",
- "protos/perfetto/metrics/android/android_trusty_workqueues.proto",
"protos/perfetto/metrics/android/anr_metric.proto",
"protos/perfetto/metrics/android/app_process_starts_metric.proto",
"protos/perfetto/metrics/android/auto_metric.proto",
@@ -4909,7 +5187,6 @@
"protos/perfetto/metrics/android/monitor_contention_metric.proto",
"protos/perfetto/metrics/android/multiuser_metric.proto",
"protos/perfetto/metrics/android/network_metric.proto",
- "protos/perfetto/metrics/android/other_traces.proto",
"protos/perfetto/metrics/android/package_list.proto",
"protos/perfetto/metrics/android/powrails_metric.proto",
"protos/perfetto/metrics/android/process_metadata.proto",
@@ -5037,7 +5314,7 @@
deps = [
":protos_perfetto_metrics_android_protos",
":protos_perfetto_metrics_protos",
- ],
+ ] + PERFETTO_CONFIG.deps.protobuf_descriptor_proto,
)
# GN target: //protos/perfetto/trace/android:android_track_event_descriptor
@@ -5119,8 +5396,7 @@
perfetto_proto_descriptor(
name = "protos_perfetto_trace_android_winscope_descriptor",
deps = [
- ":protos_perfetto_trace_android_winscope_extensions_protos",
- ":protos_perfetto_trace_android_winscope_regular_protos",
+ ":protos_perfetto_trace_android_winscope_protos",
],
outs = [
"protos_perfetto_trace_android_winscope_descriptor.bin",
@@ -5192,6 +5468,23 @@
],
)
+# GN target: //protos/perfetto/trace/android:winscope_source_set
+perfetto_proto_library(
+ name = "protos_perfetto_trace_android_winscope_protos",
+ srcs = [
+ "protos/perfetto/trace/android/winscope.proto",
+ ],
+ visibility = [
+ PERFETTO_CONFIG.proto_library_visibility,
+ ],
+ deps = [
+ ":protos_perfetto_common_protos",
+ ":protos_perfetto_trace_android_winscope_common_protos",
+ ":protos_perfetto_trace_android_winscope_extensions_protos",
+ ":protos_perfetto_trace_android_winscope_regular_protos",
+ ] + PERFETTO_CONFIG.deps.protobuf_descriptor_proto,
+)
+
# GN target: //protos/perfetto/trace/android:winscope_regular_source_set
perfetto_proto_library(
name = "protos_perfetto_trace_android_winscope_regular_protos",
@@ -5259,7 +5552,7 @@
perfetto_proto_descriptor(
name = "protos_perfetto_trace_descriptor",
deps = [
- ":protos_perfetto_trace_non_minimal_protos",
+ ":protos_perfetto_trace_protos",
],
outs = [
"protos_perfetto_trace_descriptor.bin",
@@ -5318,9 +5611,11 @@
"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",
+ "protos/perfetto/trace/ftrace/devfreq.proto",
"protos/perfetto/trace/ftrace/dma_fence.proto",
"protos/perfetto/trace/ftrace/dmabuf_heap.proto",
"protos/perfetto/trace/ftrace/dpu.proto",
@@ -5731,6 +6026,50 @@
],
)
+# GN target: //protos/perfetto/trace:source_set
+perfetto_proto_library(
+ name = "protos_perfetto_trace_protos",
+ visibility = [
+ PERFETTO_CONFIG.proto_library_visibility,
+ ],
+ deps = [
+ ":protos_perfetto_common_protos",
+ ":protos_perfetto_config_android_protos",
+ ":protos_perfetto_config_ftrace_protos",
+ ":protos_perfetto_config_gpu_protos",
+ ":protos_perfetto_config_inode_file_protos",
+ ":protos_perfetto_config_interceptors_protos",
+ ":protos_perfetto_config_power_protos",
+ ":protos_perfetto_config_process_stats_protos",
+ ":protos_perfetto_config_profiling_protos",
+ ":protos_perfetto_config_protos",
+ ":protos_perfetto_config_statsd_protos",
+ ":protos_perfetto_config_sys_stats_protos",
+ ":protos_perfetto_config_system_info_protos",
+ ":protos_perfetto_config_track_event_protos",
+ ":protos_perfetto_trace_android_protos",
+ ":protos_perfetto_trace_android_winscope_common_protos",
+ ":protos_perfetto_trace_android_winscope_regular_protos",
+ ":protos_perfetto_trace_chrome_protos",
+ ":protos_perfetto_trace_etw_protos",
+ ":protos_perfetto_trace_filesystem_protos",
+ ":protos_perfetto_trace_ftrace_protos",
+ ":protos_perfetto_trace_gpu_protos",
+ ":protos_perfetto_trace_interned_data_protos",
+ ":protos_perfetto_trace_minimal_protos",
+ ":protos_perfetto_trace_non_minimal_protos",
+ ":protos_perfetto_trace_perfetto_protos",
+ ":protos_perfetto_trace_power_protos",
+ ":protos_perfetto_trace_profiling_protos",
+ ":protos_perfetto_trace_ps_protos",
+ ":protos_perfetto_trace_statsd_protos",
+ ":protos_perfetto_trace_sys_stats_protos",
+ ":protos_perfetto_trace_system_info_protos",
+ ":protos_perfetto_trace_track_event_protos",
+ ":protos_perfetto_trace_translation_protos",
+ ],
+)
+
# GN target: //protos/perfetto/trace/ps:source_set
perfetto_proto_library(
name = "protos_perfetto_trace_ps_protos",
@@ -6113,6 +6452,7 @@
":protos_perfetto_trace_translation_zero",
":protozero",
":src_base_base",
+ ":src_base_clock_snapshots",
":src_base_version",
],
linkstatic = True,
@@ -6213,114 +6553,6 @@
],
)
-# GN target: //src/shared_lib:libperfetto_c
-perfetto_cc_library(
- name = "libperfetto_c",
- srcs = [
- ":src_android_stats_android_stats",
- ":src_android_stats_perfetto_atoms",
- ":src_protozero_filtering_bytecode_common",
- ":src_protozero_filtering_bytecode_parser",
- ":src_protozero_filtering_message_filter",
- ":src_protozero_filtering_string_filter",
- ":src_shared_lib_intern_map",
- ":src_shared_lib_shared_lib",
- ":src_tracing_client_api_without_backends",
- ":src_tracing_common",
- ":src_tracing_core_core",
- ":src_tracing_in_process_backend",
- ":src_tracing_ipc_common",
- ":src_tracing_ipc_consumer_consumer",
- ":src_tracing_ipc_default_socket",
- ":src_tracing_ipc_producer_producer",
- ":src_tracing_ipc_service_service",
- ":src_tracing_platform_impl",
- ":src_tracing_service_service",
- ":src_tracing_system_backend",
- ],
- hdrs = [
- ":include_perfetto_base_base",
- ":include_perfetto_ext_base_base",
- ":include_perfetto_ext_ipc_ipc",
- ":include_perfetto_ext_tracing_core_core",
- ":include_perfetto_ext_tracing_ipc_ipc",
- ":include_perfetto_protozero_protozero",
- ":include_perfetto_public_abi_base",
- ":include_perfetto_public_abi_public",
- ":include_perfetto_public_base",
- ":include_perfetto_public_protos_protos",
- ":include_perfetto_public_protozero",
- ":include_perfetto_public_public",
- ":include_perfetto_tracing_core_core",
- ":include_perfetto_tracing_core_forward_decls",
- ":include_perfetto_tracing_tracing",
- ],
- defines = [
- "PERFETTO_SHLIB_SDK_IMPLEMENTATION",
- ],
- visibility = [
- "//visibility:public",
- ],
- deps = [
- ":perfetto_ipc",
- ":protos_perfetto_common_cpp",
- ":protos_perfetto_common_zero",
- ":protos_perfetto_config_android_cpp",
- ":protos_perfetto_config_android_zero",
- ":protos_perfetto_config_cpp",
- ":protos_perfetto_config_ftrace_cpp",
- ":protos_perfetto_config_ftrace_zero",
- ":protos_perfetto_config_gpu_cpp",
- ":protos_perfetto_config_gpu_zero",
- ":protos_perfetto_config_inode_file_cpp",
- ":protos_perfetto_config_inode_file_zero",
- ":protos_perfetto_config_interceptors_cpp",
- ":protos_perfetto_config_interceptors_zero",
- ":protos_perfetto_config_power_cpp",
- ":protos_perfetto_config_power_zero",
- ":protos_perfetto_config_process_stats_cpp",
- ":protos_perfetto_config_process_stats_zero",
- ":protos_perfetto_config_profiling_cpp",
- ":protos_perfetto_config_profiling_zero",
- ":protos_perfetto_config_statsd_cpp",
- ":protos_perfetto_config_statsd_zero",
- ":protos_perfetto_config_sys_stats_cpp",
- ":protos_perfetto_config_sys_stats_zero",
- ":protos_perfetto_config_system_info_cpp",
- ":protos_perfetto_config_system_info_zero",
- ":protos_perfetto_config_track_event_cpp",
- ":protos_perfetto_config_track_event_zero",
- ":protos_perfetto_config_zero",
- ":protos_perfetto_ipc_cpp",
- ":protos_perfetto_ipc_ipc",
- ":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_cpp",
- ":protos_perfetto_trace_track_event_zero",
- ":protos_perfetto_trace_translation_zero",
- ":protozero",
- ":src_base_base",
- ":src_base_version",
- ],
- linkstatic = True,
-)
-
# GN target: //src/trace_processor:trace_processor
perfetto_cc_library(
name = "trace_processor",
@@ -6333,6 +6565,9 @@
":src_trace_processor_export_json",
":src_trace_processor_importers_android_bugreport_android_bugreport",
":src_trace_processor_importers_android_bugreport_android_log_event",
+ ":src_trace_processor_importers_archive_archive",
+ ":src_trace_processor_importers_art_method_art_method",
+ ":src_trace_processor_importers_art_method_art_method_event",
":src_trace_processor_importers_common_common",
":src_trace_processor_importers_common_parser_types",
":src_trace_processor_importers_common_trace_parser_hdr",
@@ -6344,16 +6579,20 @@
":src_trace_processor_importers_fuchsia_fuchsia_record",
":src_trace_processor_importers_fuchsia_full",
":src_trace_processor_importers_fuchsia_minimal",
- ":src_trace_processor_importers_gzip_full",
+ ":src_trace_processor_importers_gecko_gecko",
+ ":src_trace_processor_importers_gecko_gecko_event",
":src_trace_processor_importers_i2c_full",
":src_trace_processor_importers_instruments_instruments",
":src_trace_processor_importers_instruments_row",
- ":src_trace_processor_importers_json_full",
+ ":src_trace_processor_importers_json_json",
":src_trace_processor_importers_json_minimal",
":src_trace_processor_importers_memory_tracker_graph_processor",
":src_trace_processor_importers_ninja_ninja",
":src_trace_processor_importers_perf_perf",
":src_trace_processor_importers_perf_record",
+ ":src_trace_processor_importers_perf_text_perf_text",
+ ":src_trace_processor_importers_perf_text_perf_text_event",
+ ":src_trace_processor_importers_perf_text_perf_text_sample_line_parser",
":src_trace_processor_importers_perf_tracker",
":src_trace_processor_importers_proto_full",
":src_trace_processor_importers_proto_minimal",
@@ -6364,7 +6603,6 @@
":src_trace_processor_importers_systrace_full",
":src_trace_processor_importers_systrace_systrace_line",
":src_trace_processor_importers_systrace_systrace_parser",
- ":src_trace_processor_importers_zip_full",
":src_trace_processor_lib",
":src_trace_processor_metatrace",
":src_trace_processor_metrics_metrics",
@@ -6409,6 +6647,7 @@
":src_trace_processor_util_trace_blob_view_reader",
":src_trace_processor_util_trace_type",
":src_trace_processor_util_util",
+ ":src_trace_processor_util_winscope_proto_mapping",
":src_trace_processor_util_zip_reader",
],
hdrs = [
@@ -6474,6 +6713,7 @@
":protos_third_party_simpleperf_zero",
":protozero",
":src_base_base",
+ ":src_base_clock_snapshots",
":src_trace_processor_containers_containers",
":src_trace_processor_importers_proto_gen_cc_android_track_event_descriptor",
":src_trace_processor_importers_proto_gen_cc_chrome_track_event_descriptor",
@@ -6530,6 +6770,9 @@
":src_trace_processor_export_json",
":src_trace_processor_importers_android_bugreport_android_bugreport",
":src_trace_processor_importers_android_bugreport_android_log_event",
+ ":src_trace_processor_importers_archive_archive",
+ ":src_trace_processor_importers_art_method_art_method",
+ ":src_trace_processor_importers_art_method_art_method_event",
":src_trace_processor_importers_common_common",
":src_trace_processor_importers_common_parser_types",
":src_trace_processor_importers_common_trace_parser_hdr",
@@ -6541,16 +6784,20 @@
":src_trace_processor_importers_fuchsia_fuchsia_record",
":src_trace_processor_importers_fuchsia_full",
":src_trace_processor_importers_fuchsia_minimal",
- ":src_trace_processor_importers_gzip_full",
+ ":src_trace_processor_importers_gecko_gecko",
+ ":src_trace_processor_importers_gecko_gecko_event",
":src_trace_processor_importers_i2c_full",
":src_trace_processor_importers_instruments_instruments",
":src_trace_processor_importers_instruments_row",
- ":src_trace_processor_importers_json_full",
+ ":src_trace_processor_importers_json_json",
":src_trace_processor_importers_json_minimal",
":src_trace_processor_importers_memory_tracker_graph_processor",
":src_trace_processor_importers_ninja_ninja",
":src_trace_processor_importers_perf_perf",
":src_trace_processor_importers_perf_record",
+ ":src_trace_processor_importers_perf_text_perf_text",
+ ":src_trace_processor_importers_perf_text_perf_text_event",
+ ":src_trace_processor_importers_perf_text_perf_text_sample_line_parser",
":src_trace_processor_importers_perf_tracker",
":src_trace_processor_importers_proto_full",
":src_trace_processor_importers_proto_minimal",
@@ -6561,7 +6808,6 @@
":src_trace_processor_importers_systrace_full",
":src_trace_processor_importers_systrace_systrace_line",
":src_trace_processor_importers_systrace_systrace_parser",
- ":src_trace_processor_importers_zip_full",
":src_trace_processor_lib",
":src_trace_processor_metatrace",
":src_trace_processor_metrics_metrics",
@@ -6609,6 +6855,7 @@
":src_trace_processor_util_trace_blob_view_reader",
":src_trace_processor_util_trace_type",
":src_trace_processor_util_util",
+ ":src_trace_processor_util_winscope_proto_mapping",
":src_trace_processor_util_zip_reader",
"src/trace_processor/trace_processor_shell.cc",
],
@@ -6657,6 +6904,7 @@
":protos_third_party_simpleperf_zero",
":protozero",
":src_base_base",
+ ":src_base_clock_snapshots",
":src_base_http_http",
":src_base_version",
":src_trace_processor_containers_containers",
@@ -6682,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",
@@ -6784,6 +6964,9 @@
":src_trace_processor_export_json",
":src_trace_processor_importers_android_bugreport_android_bugreport",
":src_trace_processor_importers_android_bugreport_android_log_event",
+ ":src_trace_processor_importers_archive_archive",
+ ":src_trace_processor_importers_art_method_art_method",
+ ":src_trace_processor_importers_art_method_art_method_event",
":src_trace_processor_importers_common_common",
":src_trace_processor_importers_common_parser_types",
":src_trace_processor_importers_common_trace_parser_hdr",
@@ -6795,16 +6978,20 @@
":src_trace_processor_importers_fuchsia_fuchsia_record",
":src_trace_processor_importers_fuchsia_full",
":src_trace_processor_importers_fuchsia_minimal",
- ":src_trace_processor_importers_gzip_full",
+ ":src_trace_processor_importers_gecko_gecko",
+ ":src_trace_processor_importers_gecko_gecko_event",
":src_trace_processor_importers_i2c_full",
":src_trace_processor_importers_instruments_instruments",
":src_trace_processor_importers_instruments_row",
- ":src_trace_processor_importers_json_full",
+ ":src_trace_processor_importers_json_json",
":src_trace_processor_importers_json_minimal",
":src_trace_processor_importers_memory_tracker_graph_processor",
":src_trace_processor_importers_ninja_ninja",
":src_trace_processor_importers_perf_perf",
":src_trace_processor_importers_perf_record",
+ ":src_trace_processor_importers_perf_text_perf_text",
+ ":src_trace_processor_importers_perf_text_perf_text_event",
+ ":src_trace_processor_importers_perf_text_perf_text_sample_line_parser",
":src_trace_processor_importers_perf_tracker",
":src_trace_processor_importers_proto_full",
":src_trace_processor_importers_proto_minimal",
@@ -6815,7 +7002,6 @@
":src_trace_processor_importers_systrace_full",
":src_trace_processor_importers_systrace_systrace_line",
":src_trace_processor_importers_systrace_systrace_parser",
- ":src_trace_processor_importers_zip_full",
":src_trace_processor_lib",
":src_trace_processor_metatrace",
":src_trace_processor_metrics_metrics",
@@ -6860,6 +7046,7 @@
":src_trace_processor_util_trace_blob_view_reader",
":src_trace_processor_util_trace_type",
":src_trace_processor_util_util",
+ ":src_trace_processor_util_winscope_proto_mapping",
":src_trace_processor_util_zip_reader",
":src_traceconv_lib",
":src_traceconv_main",
@@ -6911,6 +7098,7 @@
":protos_third_party_simpleperf_zero",
":protozero",
":src_base_base",
+ ":src_base_clock_snapshots",
":src_base_version",
":src_trace_processor_containers_containers",
":src_trace_processor_importers_proto_gen_cc_android_track_event_descriptor",
diff --git a/CHANGELOG b/CHANGELOG
index 03fa600..2a33ef5 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,14 +1,81 @@
Unreleased:
Tracing service and probes:
+ * Add `--clone-by-name` to the perfetto command line. This allows cloning a
+ tracing session by its unique_session_name.
+ SQL Standard library:
+ * Removed the `linux_device_track` table. Linux RPM (runtime power
+ management) tracks formerly in this table now can be found in the `track`
+ table with `classification` `linux_rpm`.
+ * Removed the `energy_counter_track` table. Android energy estimate
+ breakdown tracks formerly in this table now can be found in the `track`
+ table with `classification` `android_energy_estimation_breakdown`.
+ * Removed the `energy_per_uid_counter_track` table. Android energy estimate
+ breakdown tracks formerly in this table now can be found in the `track`
+ table with `classification` `android_energy_estimation_breakdown_per_uid`.
+ * Moved the `gpu_work_period_track` table into the `android.gpu.work_period`
+ standard library module and renamed it to `android_gpu_work_period_track`.
+ * Removed the `irq_counter_track` table. Irq counter tracks formerly in this
+ table now can be found in the `track` table with `classification`
+ `irq_counter`.
+ * Removed the `softirq_counter_track` table. Softirq counter tracks formerly
+ in this table now can be found in the `track` table with `classification`
+ `softirq_counter`.
+ * Removed the `uid_counter_track` table. It was redundant as no data was
+ 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:
+ *
+ SDK:
+ *
+
+
+v48.1 - 2024-10-14:
+ SDK:
+ * Fix build with MSVC.
+
+
+v48.0 - 2024-10-11:
+ Tracing service and probes:
* Improved accuracy of ftrace event cropping when there are multiple
concurrent tracing sessions. See `previous_bundle_end_timestamp` in
ftrace_event_bundle.proto.
* Increased watchdog timeout to 180s from 30s to make watchdog crashes
much less likely when system is under heavy load.
SQL Standard library:
- *
+ * Improved CPU cycles calculation in `linux.cpu.utilization` modules:
+ `process`, `system` and `thread` by fixing a bug responsible for too high
+ CPU cycles values.
+ * Introduces functions responsible for calculating CPU cycles with
+ breakdown by CPU, thread, process and slice for a given interval.
+ * Added `linux.perf.samples` module for easy querying of perf samples
+ in traces.
+ * Added `stacks.cpu_profiling` module for easy querying of all CPU
+ profiling data in traces.
Trace Processor:
- *
+ * Added (partial) support for the Gecko (Firefox) JSON profiler format.
+ Parsing is optimized for CPU profiling collected with `perf` and converted
+ to the Gecko format. Only parsing of samples is supported; parsing of
+ markers and any other features (e.g. colours) is *not* supported.
+ * Added (partial) suppoort for the perf script text format from both perf
+ and simpleperf. Only parsing files with the default formating or with pids
+ included (i.e. `-F +pid`) is supported. Any other formatting options are
+ *not* supported.
+ * Added support for parsing non-streaming ART method tracing format.
+ * Added support for parsing GZIP files with multiple gzip streams.
+ * Added support for parsing V8 CPU profling samples from proto traces.
+u * Renamed Trace Processor's C++ method `RegisterSqlModule()` to
+ `RegisterSqlPackage()`, which better represents the module/package
+ relationship. Package is the top level grouping of modules, which are
+ objects being included with `INCLUDE PERFETTO MODULE`.
+ `RegisterSqlModule()` is still available and runs `RegisterSqlPackage()`.
+ `RegisterSqlModule()` will be deprecated in v50.0.
UI:
* Scheduling wakeup information now reflects whether the wakeup came
from an interrupt context. The per-cpu scheduling tracks now show only
@@ -16,6 +83,28 @@
link to an exact waker slice or state that the wakeup is from an
interrupt. Older traces that are recorded without interrupt context
information treat all wakeups as non-interrupt.
+ * Nest global/user async tracks according to their parent/child relationship
+ in the trace.
+ * Introduced new workspace API which allows nested tracks & multiple
+ workspace support.
+ * Introduced middle ellipsis in track titles when title text is longer than
+ the available space, while preserving the start and end of title text &
+ add popup of full title text on hover.
+ * Fixed spurious judder issue in popups with tall content.
+ * Fixed bug where marker durations were not rendered on selected markers.
+ * Removed ChromeScrollJank V1 track (V2 should be used from now on).
+ * Major internal changes (affecting pugin developers and core contributors):
+ * Removed legacy selection types, use track event selection types going
+ forward for all single event selections.
+ * Details panels are now attached to the track rather than registered
+ separately.
+ * Introduced `registerSqlSelectionResolver`, which allow plugins to add
+ handlers allowing other parts of the codebase to make selection on
+ tracks from other plugins based on a sql table name and id.
+ * Changed lifetime of track instances; they are now created on trace load
+ by plugins and survive the lifetime of the trace, rather than getting
+ created and destroyed as they appear/disappear on the timeline.
+ * Fix circular dependencies and turn future instances into errors.
SDK:
*
diff --git a/bazel/deps.bzl b/bazel/deps.bzl
index d97179a..4334036 100644
--- a/bazel/deps.bzl
+++ b/bazel/deps.bzl
@@ -95,9 +95,11 @@
_add_repo_if_not_existing(
http_archive,
name = "bazel_skylib",
- sha256 = "bbccf674aa441c266df9894182d80de104cabd19be98be002f6d478aaa31574d",
- strip_prefix = "bazel-skylib-2169ae1c374aab4a09aa90e65efe1a3aad4e279b",
- url = "https://github.com/bazelbuild/bazel-skylib/archive/2169ae1c374aab4a09aa90e65efe1a3aad4e279b.tar.gz",
+ sha256 = "bc283cdfcd526a52c3201279cda4bc298652efa898b10b4db0837dc51652756f",
+ urls = [
+ "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.7.1/bazel-skylib-1.7.1.tar.gz",
+ "https://github.com/bazelbuild/bazel-skylib/releases/download/1.7.1/bazel-skylib-1.7.1.tar.gz",
+ ]
)
def _add_repo_if_not_existing(repo_rule, name, **kwargs):
diff --git a/buildtools/libcxx_config/__config_site b/buildtools/libcxx_config/__config_site
index dd396f0..c5a9f13 100644
--- a/buildtools/libcxx_config/__config_site
+++ b/buildtools/libcxx_config/__config_site
@@ -93,4 +93,10 @@
#define _LIBCPP_CHAR_TRAITS_REMOVE_BASE_SPECIALIZATION
#define _LIBCPP_HARDENING_MODE_DEFAULT _LIBCPP_HARDENING_MODE_NONE
+
+// Enable libcxx part of compile-time thread safety analysis.
+#if defined(__clang__)
+#define _LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS 1
+#endif
+
#endif // _LIBCPP_CONFIG_SITE
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/analysis/trace-processor.md b/docs/analysis/trace-processor.md
index a459566..2de3419 100644
--- a/docs/analysis/trace-processor.md
+++ b/docs/analysis/trace-processor.md
@@ -509,36 +509,6 @@
The metrics subsystem is a significant part of trace processor and thus is
documented on its own [page](/docs/analysis/metrics.md).
-## Creating derived events
-
-TIP: To see how to add to add a new annotation to trace processor, see the
- checklist [here](/docs/contributing/common-tasks.md#new-annotation).
-
-This feature allows creation of new events (slices and counters) from the data
-in the trace. These events can then be displayed in the UI tracks as if they
-were part of the trace itself.
-
-This is useful as often the data in the trace is very low-level. While low
-level information is important for experts to perform deep debugging, often
-users are just looking for a high level overview without needing to consider
-events from multiple locations.
-
-For example, an app startup in Android spans multiple components including
-`ActivityManager`, `system_server`, and the newly created app process derived
-from `zygote`. Most users do not need this level of detail; they are only
-interested in a single slice spanning the entire startup.
-
-Creating derived events is tied very closely to
-[metrics subsystem](/docs/analysis/metrics.md); often SQL-based metrics need to
-create higher-level abstractions from raw events as intermediate artifacts.
-
-From previous example, the
-[startup metric](/src/trace_processor/metrics/sql/android/android_startup.sql)
-creates the exact `launching` slice we want to display in the UI.
-
-The other benefit of aligning the two is that changes in metrics are
-automatically kept in sync with what the user sees in the UI.
-
## Python API
The trace processor's C++ library is also exposed through Python. This
is documented on a [separate page](/docs/analysis/trace-processor-python.md).
diff --git a/docs/contributing/common-tasks.md b/docs/contributing/common-tasks.md
index 11d4f14..43c5421 100644
--- a/docs/contributing/common-tasks.md
+++ b/docs/contributing/common-tasks.md
@@ -138,47 +138,6 @@
3. Run the newly added test with `tools/diff_test_trace_processor.py <path to trace processor shell binary>`.
4. Upload and land your change as normal.
-## Adding new derived events
-
-As derived events depend on metrics, the initial steps are same as that of developing a metric (see above).
-
-NOTE: the metric can be just an empty proto message during prototyping or if no summarization is necessary. However, generally if an event is important enough to display in the UI, it should also be tracked in benchmarks as a metric.
-
-To extend a metric with annotations:
-
-1. Create a new table or view with the name `<metric name>_event`.
- * For example, for the [`android_startup`]() metric, we create a view named `android_startup_event`.
- * Note that the trailing `_event` suffix in the table name is important.
- * The schema required for this table is given below.
-2. List your metric in the `initialiseHelperViews` method of `trace_controller.ts`.
-3. Upload and land your change as normal.
-
-The schema of the `<metric name>_event` table/view is as follows:
-
-| Name | Type | Presence | Meaning |
-| :----------- | -------- | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `track_type` | `string` | Mandatory | 'slice' for slices, 'counter' for counters |
-| `track_name` | `string` | Mandatory | Name of the track to display in the UI. Also the track identifier i.e. all events with same `track_name` appear on the same track. |
-| `ts` | `int64` | Mandatory | The timestamp of the event (slice or counter) |
-| `dur` | `int64` | Mandatory for slice, NULL for counter | The duration of the slice |
-| `slice_name` | `string` | Mandatory for slice, NULL for counter | The name of the slice |
-| `value` | `double` | Mandatory for counter, NULL for slice | The value of the counter |
-| `group_name` | `string` | Optional | Name of the track group under which the track appears. All tracks with the same `group_name` are placed under the same group by that name. Tracks that lack this field or have NULL value in this field are displayed without any grouping. |
-
-#### Known issues:
-
-* Nested slices within the same track are not supported. We plan to support this
- once we have a concrete usecase.
-* Tracks are always created in the global scope. We plan to extend this to
- threads and processes in the near future with additional contexts added as
- necessary.
-* Instant events are currently not supported in the UI but this will be
- implemented in the near future. In trace processor, instants are always `0`
- duration slices with special rendering on the UI side.
-* There is no way to tie newly added events back to the source events in the
- trace which were used to generate them. This is not currently a priority but
- something we may add in the future.
-
## Update `TRACE_PROCESSOR_CURRENT_API_VERSION`
diff --git a/docs/contributing/embedding.md b/docs/contributing/embedding.md
index 0d080c9..58b796f 100644
--- a/docs/contributing/embedding.md
+++ b/docs/contributing/embedding.md
@@ -43,10 +43,3 @@
Metrics can also be registered at run time using the `RegisterMetric` and `ExtendMetricsProto` functions. These can subsequently be executed with `ComputeMetric`.
WARNING: embedders should ensure that the path of any registered metric is consistent with the name used to execute the metric and output view in the SQL.
-
-### Creating derived events
-
-As creating derived events is tied to the metrics subsystem, the `ComputeMetrics` function in the trace processor API should be called with the appropriate metrics. This will create the `<metric_name>_event` table/view which can then be queried using the `ExectueQuery` function.
-
-NOTE: At some point, there are plans to add an API which does not create the metrics proto but just executes the queries in the metric.
-
diff --git a/docs/contributing/ui-plugins.md b/docs/contributing/ui-plugins.md
index aa38d68..cc65f43 100644
--- a/docs/contributing/ui-plugins.md
+++ b/docs/contributing/ui-plugins.md
@@ -1,15 +1,15 @@
# UI plugins
-The Perfetto UI can be extended with plugins. These plugins are shipped
-part of Perfetto.
+The Perfetto UI can be extended with plugins. These plugins are shipped part of
+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 MacOS or Linux machine.
-Follow the steps below or see the
-[Getting Started](./getting-started) guide for more detail.
+First we need to prepare the UI development environment. You will need to use a
+MacOS or Linux machine. Follow the steps below or see the [Getting
+Started](./getting-started) guide for more detail.
```sh
git clone https://android.googlesource.com/platform/external/perfetto/
@@ -21,21 +21,19 @@
```sh
cp -r ui/src/plugins/com.example.Skeleton ui/src/plugins/<your-plugin-name>
```
-Now edit `ui/src/plugins/<your-plugin-name>/index.ts`.
-Search for all instances of `SKELETON: <instruction>` in the file and
-follow the instructions.
+Now edit `ui/src/plugins/<your-plugin-name>/index.ts`. Search for all instances
+of `SKELETON: <instruction>` in the file and follow the instructions.
Notes on naming:
- Don't name the directory `XyzPlugin` just `Xyz`.
- The `pluginId` and directory name must match.
-- Plugins should be prefixed with the reversed components of a domain
- name you control. For example if `example.com` is your domain your
- plugin should be named `com.example.Foo`.
-- Core plugins maintained by the Perfetto team should use
- `dev.perfetto.Foo`.
+- Plugins should be prefixed with the reversed components of a domain name you
+ control. For example if `example.com` is your domain your plugin should be
+ named `com.example.Foo`.
+- Core plugins maintained by the Perfetto team should use `dev.perfetto.Foo`.
- Commands should have ids with the pattern `example.com#DoSomething`
-- Command's ids should be prefixed with the id of the plugin which
- provides them.
+- Command's ids should be prefixed with the id of the plugin which provides
+ them.
- Command names should have the form "Verb something something", and should be
in normal sentence case. I.e. don't capitalize the first letter of each word.
- Good: "Pin janky frame timeline tracks"
@@ -48,246 +46,407 @@
Now navigate to [localhost:10000](http://localhost:10000/)
### Enable your plugin
-- Navigate to the plugins page: [localhost:10000/#!/plugins](http://localhost:10000/#!/plugins).
+- 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.
+Later you can request for your plugin to be enabled by default. Follow the
+[default plugins](#default-plugins) section for this.
### Upload your plugin for review
- Update `ui/src/plugins/<your-plugin-name>/OWNERS` to include your email.
-- Follow the [Contributing](./getting-started#contributing)
- instructions to upload your CL to the codereview tool.
+- Follow the [Contributing](./getting-started#contributing) instructions to
+ 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 types](https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html).
+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 [hotkey.ts](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/ui/src/base/hotkeys.ts)
+See
+[hotkey.ts](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/ui/src/base/hotkeys.ts)
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');
@@ -298,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(),
});
}
@@ -323,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:
@@ -348,9 +507,9 @@
registering the tab.
```ts
-ctx.registerTab({
+trace.registerTab({
isEphemeral: true,
- uri: 'dev.MyPlugin#MyTab',
+ uri: `${trace.pluginId}#MyTab`,
content: new MyEphemeralTab(),
});
```
@@ -363,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) {}
@@ -381,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,
@@ -407,11 +559,6 @@
ctx.tabs.showTab(uri);
}
}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'dev.MyPlugin',
- plugin: MyPlugin,
-};
```
### Details Panels & The Current Selection Tab
@@ -425,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');
@@ -454,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
@@ -463,15 +746,13 @@
### State
NOTE: It is important to consider version skew when using persistent state.
-Plugins can persist information into permalinks. This allows plugins
-to gracefully handle permalinking and is an opt-in - not automatic -
-mechanism.
+Plugins can persist information into permalinks. This allows plugins to
+gracefully handle permalinking and is an opt-in - not automatic - mechanism.
Persistent plugin state works using a `Store<T>` where `T` is some JSON
-serializable object.
-`Store` is implemented [here](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/ui/src/base/store.ts).
-`Store` allows for reading and writing `T`.
-Reading:
+serializable object. `Store` is implemented
+[here](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/ui/src/base/store.ts).
+`Store` allows for reading and writing `T`. Reading:
```typescript
interface Foo {
bar: string;
@@ -509,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);
}
}
@@ -581,20 +863,21 @@
- [dev.perfetto.ExampleState](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/ui/src/plugins/dev.perfetto.ExampleState/index.ts).
## Guide to the plugin API
-The plugin interfaces are defined in [ui/src/public/](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/ui/src/public).
+The plugin interfaces are defined in
+[ui/src/public/](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/ui/src/public).
## Default plugins
-Some plugins are enabled by default.
-These plugins are held to a higher quality than non-default plugins since changes to those plugins effect all users of the UI.
-The list of default plugins is specified at [ui/src/core/default_plugins.ts](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/ui/src/core/default_plugins.ts).
+Some plugins are enabled by default. These plugins are held to a higher quality
+than non-default plugins since changes to those plugins effect all users of the
+UI. The list of default plugins is specified at
+[ui/src/core/default_plugins.ts](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/ui/src/core/default_plugins.ts).
## Misc notes
- Plugins must be licensed under
- [Apache-2.0](https://spdx.org/licenses/Apache-2.0.html)
- the same as all other code in the repository.
-- Plugins are the responsibility of the OWNERS of that plugin to
- maintain, not the responsibility of the Perfetto team. All
- efforts will be made to keep the plugin API stable and existing
- plugins working however plugins that remain unmaintained for long
- periods of time will be disabled and ultimately deleted.
+ [Apache-2.0](https://spdx.org/licenses/Apache-2.0.html) the same as all other
+ code in the repository.
+- Plugins are the responsibility of the OWNERS of that plugin to maintain, not
+ the responsibility of the Perfetto team. All efforts will be made to keep the
+ plugin API stable and existing plugins working however plugins that remain
+ unmaintained for long periods of time will be disabled and ultimately deleted.
diff --git a/docs/images/synthetic-track-event-custom-tree.png b/docs/images/synthetic-track-event-custom-tree.png
new file mode 100644
index 0000000..bad4190
--- /dev/null
+++ b/docs/images/synthetic-track-event-custom-tree.png
Binary files differ
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/instrumentation/tracing-sdk.md b/docs/instrumentation/tracing-sdk.md
index 36c6b39..a77d363 100644
--- a/docs/instrumentation/tracing-sdk.md
+++ b/docs/instrumentation/tracing-sdk.md
@@ -30,7 +30,7 @@
To start using the Client API, first check out the latest SDK release:
```bash
-git clone https://android.googlesource.com/platform/external/perfetto -b v47.0
+git clone https://android.googlesource.com/platform/external/perfetto -b v48.1
```
The SDK consists of two files, `sdk/perfetto.h` and `sdk/perfetto.cc`. These are
diff --git a/docs/reference/synthetic-track-event.md b/docs/reference/synthetic-track-event.md
index ff894e9..03853b0 100644
--- a/docs/reference/synthetic-track-event.md
+++ b/docs/reference/synthetic-track-event.md
@@ -1,4 +1,5 @@
# Writing TrackEvent Protos Synthetically
+
This page acts as a reference guide to synthetically generate TrackEvent,
Perfetto's native protobuf based tracing format. This allows using Perfetto's
analysis and visualzation without using collecting traces using the Perfetto
@@ -6,8 +7,8 @@
TrackEvent protos can be manually written using the
[official protobuf library](https://protobuf.dev/reference/) or any other
-protobuf-compatible library. To be language-agnostic, the rest of this page
-will show examples using the
+protobuf-compatible library. To be language-agnostic, the rest of this page will
+show examples using the
[text format](https://protobuf.dev/reference/protobuf/textformat-spec/)
representation of protobufs.
@@ -18,6 +19,7 @@
messages.
## Thread-scoped (sync) slices
+
NOTE: in the legacy JSON tracing format, this section correspond to B/E/I/X
events with the associated M (metadata) events.
@@ -29,6 +31,7 @@
![Thread track event in UI](/docs/images/synthetic-track-event-thread.png)
This is corresponds to the following protos:
+
```
# Emit this packet once *before* you emit the first event for this process.
packet {
@@ -100,27 +103,29 @@
```
## Process-scoped (async) slices
+
NOTE: in the legacy JSON tracing format, this section corresponds to b/e/n
events with the associated M (metadata) events.
Process-scoped slices are useful to trace execution of a "piece of work" across
-multiple threads of a process. A process-scoped slice can start on a thread
-A and end on a thread B. Examples include work submitted to thread pools
-and coroutines.
+multiple threads of a process. A process-scoped slice can start on a thread A
+and end on a thread B. Examples include work submitted to thread pools and
+coroutines.
Process tracks can be named corresponding to the executor and can also have
child slices in an identical way to thread-scoped slices. Importantly, this
-means slices on a single track must **strictly nest** inside each other
-without overlapping.
+means slices on a single track must **strictly nest** inside each other without
+overlapping.
-As separating each track in the UI can cause a lot of clutter, the UI
-visually merges process tracks with the same name in each process. Note that
-this **does not** change the data model (e.g. in trace processor
-tracks remain separated) as this is simply a visual grouping.
+As separating each track in the UI can cause a lot of clutter, the UI visually
+merges process tracks with the same name in each process. Note that this **does
+not** change the data model (e.g. in trace processor tracks remain separated) as
+this is simply a visual grouping.
![Process track event in UI](/docs/images/synthetic-track-event-process.png)
This is corresponds to the following protos:
+
```
# The first track associated with this process.
packet {
@@ -219,17 +224,304 @@
}
```
+## Custom-scoped slices
+
+NOTE: there is no equivalent in the JSON tracing format.
+
+As well as thread-scoped and process-scoped slices, Perfetto supports creating
+tracks which are not scoped to any OS-level concept. Moreover, these tracks can
+be recursively nested in a tree structure. This is useful to model the timeline
+of execution of GPUs, network traffic, IRQs etc.
+
+Note: in the past, modelling such slices may have been done by abusing
+processes/threads slices, due to limitations with the data model and the
+Perfetto UI. This is no longer necessary and we _strongly_ discourage continued
+use of this hack.
+
+![Process track event in UI](/docs/images/synthetic-track-event-custom-tree.png)
+
+This is corresponds to the following protos:
+
+```
+packet {
+ track_descriptor {
+ uuid: 48948 # 64-bit random number.
+ name: "Root"
+ }
+}
+packet {
+ track_descriptor {
+ uuid: 50001 # 64-bit random number.
+ parent_uuid: 48948 # UUID of root track.
+ name: "Parent B"
+ }
+}
+packet {
+ track_descriptor {
+ uuid: 50000 # 64-bit random number.
+ parent_uuid: 48948 # UUID of root track.
+ name: "Parent A"
+ }
+}
+packet {
+ track_descriptor {
+ uuid: 60000 # 64-bit random number.
+ parent_uuid: 50000 # UUID of Parent A track.
+ name: "Child A1"
+ }
+}
+packet {
+ track_descriptor {
+ uuid: 60001 # 64-bit random number.
+ parent_uuid: 50000 # UUID of Parent A track.
+ name: "Child A2"
+ }
+}
+packet {
+ track_descriptor {
+ uuid: 70000 # 64-bit random number.
+ parent_uuid: 50001 # UUID of Parent B track.
+ name: "Child B1"
+ }
+}
+
+# The events for the Child A1 track.
+packet {
+ timestamp: 200
+ track_event {
+ type: TYPE_SLICE_BEGIN
+ track_uuid: 60000 # Same random number from above.
+ name: "A1"
+ }
+ trusted_packet_sequence_id: 3903809 # Generate *once*, use throughout.
+}
+packet {
+ timestamp: 250
+ track_event {
+ type: TYPE_SLICE_END
+ track_uuid: 60000
+ }
+ trusted_packet_sequence_id: 3903809
+}
+
+# The events for the Child A2 track.
+packet {
+ timestamp: 220
+ track_event {
+ type: TYPE_SLICE_BEGIN
+ track_uuid: 60001 # Same random number from above.
+ name: "A2"
+ }
+ trusted_packet_sequence_id: 3903809 # Generate *once*, use throughout.
+}
+packet {
+ timestamp: 240
+ track_event {
+ type: TYPE_SLICE_END
+ track_uuid: 60001
+ }
+ trusted_packet_sequence_id: 3903809
+}
+
+# The events for the Child B1 track.
+packet {
+ timestamp: 210
+ track_event {
+ type: TYPE_SLICE_BEGIN
+ track_uuid: 70000 # Same random number from above.
+ name: "B1"
+ }
+ trusted_packet_sequence_id: 3903809 # Generate *once*, use throughout.
+}
+packet {
+ timestamp: 230
+ track_event {
+ type: TYPE_SLICE_END
+ track_uuid: 70000
+ }
+ trusted_packet_sequence_id: 3903809
+}
+```
+
+## Track sorting order
+
+NOTE: the closest equivalent to this in the JSON format is `process_sort_index`
+but the Perfetto approach is significantly more flexible.
+
+Perfetto also supports specifying of how the tracks should be visualized in the
+UI by default. This is done via the use of the `child_ordering` field which can
+be set on `TrackDescriptor`.
+
+For example, to sort the tracks lexicographically (i.e. in alphabetical order):
+
+```
+packet {
+ track_descriptor {
+ uuid: 10
+ name: "Root"
+ # Any children of the `Root` track will appear in alphabetical order. This
+ # does *not* propogate to any indirect descendants, just the direct
+ # children.
+ child_ordering: LEXICOGRAPHIC
+ }
+}
+# B will appear nested under `Root` but *after* `A` in the UI, even though it
+# appears first in the trace and has a smaller UUID.
+packet {
+ track_descriptor {
+ uuid: 11
+ parent_uuid: 10
+ name: "B"
+ }
+}
+packet {
+ track_descriptor {
+ uuid: 12
+ parent_uuid: 10
+ name: "A"
+ }
+}
+```
+
+Chronological order is also supported, this sorts the tracks with the earliest
+event first:
+
+```
+packet {
+ track_descriptor {
+ uuid: 10
+ name: "Root"
+ # Any children of the `Root` track will appear in the order based on the
+ # timestamp of the first event on the trace: earlier timestamps will appear
+ # higher in the trace. This does *not* propogate to any indirect
+ # descendants, just the direct children.
+ child_ordering: CHRONOLOGICAL
+ }
+}
+
+# B will appear before A because B's first slice starts earlier than A's first
+# slice.
+packet {
+ track_descriptor {
+ uuid: 11
+ parent_uuid: 10
+ name: "A"
+ }
+}
+packet {
+ timestamp: 220
+ track_event {
+ type: TYPE_SLICE_BEGIN
+ track_uuid: 11
+ name: "A1"
+ }
+ trusted_packet_sequence_id: 3903809
+}
+packet {
+ timestamp: 230
+ track_event {
+ type: TYPE_SLICE_END
+ track_uuid: 60000
+ }
+ trusted_packet_sequence_id: 3903809
+}
+
+packet {
+ track_descriptor {
+ uuid: 12
+ parent_uuid: 10
+ name: "B"
+ }
+}
+packet {
+ timestamp: 210
+ track_event {
+ type: TYPE_SLICE_BEGIN
+ track_uuid: 12
+ name: "B1"
+ }
+ trusted_packet_sequence_id: 3903809
+}
+packet {
+ timestamp: 240
+ track_event {
+ type: TYPE_SLICE_END
+ track_uuid: 12
+ }
+ trusted_packet_sequence_id: 3903809
+}
+```
+
+Finally, for exact control, you can use the `EXPLICIT` ordering and specify
+`sibling_order_rank` on each child track:
+
+```
+packet {
+ track_descriptor {
+ uuid: 10
+ name: "Root"
+ # Any children of the `Root` track will appear in order specified by
+ # `sibling_order_rank` exactly: any unspecified rank is treated as 0
+ # implicitly.
+ child_ordering: EXPLICIT
+ }
+}
+# C will appear first, then B then A following the order specified by
+# `sibling_order_rank`.
+packet {
+ track_descriptor {
+ uuid: 11
+ parent_uuid: 10
+ name: "B"
+ sibling_order_rank: 1
+ }
+}
+packet {
+ track_descriptor {
+ uuid: 12
+ parent_uuid: 10
+ name: "A"
+ sibling_order_rank: 100
+ }
+}
+packet {
+ track_descriptor {
+ uuid: 13
+ parent_uuid: 10
+ name: "C"
+ sibling_order_rank: -100
+ }
+}
+```
+
+NOTE: using `EXPLICIT` is strongly discouraged where there is another option.
+Other orders are significantly more efficient and also allows for trace
+processor and the UI to better understand what you want to do with those tracks.
+Moreover, it gives the flexibility for having custom visualization (e.g. Gannt
+charts for CHRONOLOGICAL view) based on the type specified.
+
+Further documentation about the sorting order is available on the protos for
+[TrackDescriptor](/docs/reference/trace-packet-proto.autogen#TrackDescriptor)
+and
+[ChildTracksOrdering](/docs/reference/trace-packet-proto.autogen#TrackDescriptor.ChildTracksOrdering).
+
+NOTE: the order specified in the trace is a treated as a hint in the UI not a
+gurantee. The UI reserves the right to change the ordering as it sees fit.
+
## Flows
+
NOTE: in the legacy JSON tracing format, this section correspond to s/t/f
events.
-Flows allow connecting any number of slices with arrows. The semantic meaning
-of the arrow varies across different applications but most commonly it is used
-to track work passing between threads or processes: e.g. the UI thread asks a
+Flows allow connecting any number of slices with arrows. The semantic meaning of
+the arrow varies across different applications but most commonly it is used to
+track work passing between threads or processes: e.g. the UI thread asks a
background thread to do some work and notify when the result is available.
-NOTE: a single flow *cannot* fork ands imply represents a single stream of
-arrows from one slice to the next. See [this](https://source.chromium.org/chromium/chromium/src/+/main:third_party/perfetto/protos/perfetto/trace/perfetto_trace.proto;drc=ba05b783d9c29fe334a02913cf157ea1d415d37c;l=9604) comment for information.
+NOTE: a single flow _cannot_ fork ands imply represents a single stream of
+arrows from one slice to the next. See
+[this](https://source.chromium.org/chromium/chromium/src/+/main:third_party/perfetto/protos/perfetto/trace/perfetto_trace.proto;drc=ba05b783d9c29fe334a02913cf157ea1d415d37c;l=9604)
+comment for information.
![TrackEvent flows in UI](/docs/images/synthetic-track-event-flow.png)
@@ -315,6 +607,7 @@
```
## Counters
+
NOTE: in the legacy JSON tracing format, this section correspond to C events.
Counters are useful to represent continuous values which change with time.
@@ -323,6 +616,7 @@
![TrackEvent counter in UI](/docs/images/synthetic-track-event-counter.png)
This corresponds to the following protos:
+
```
# Counter track scoped to a process.
packet {
@@ -381,22 +675,23 @@
```
## Interning
+
NOTE: there is no equivalent to interning in the JSON tracing format.
Interning is an advanced but powerful feature of the protobuf tracing format
-which allows allows for reducing the number of times long strings are emitted
-in the trace.
+which allows allows for reducing the number of times long strings are emitted in
+the trace.
Specifically, certain fields in the protobuf format allow associating an "iid"
(interned id) to a string and using the iid to reference the string in all
-future packets. The most commonly used cases are slice names and category
-names
+future packets. The most commonly used cases are slice names and category names
-Here is an example of a trace which makes use of interning to reduce the
-number of times a very long slice name is emitted:
+Here is an example of a trace which makes use of interning to reduce the number
+of times a very long slice name is emitted:
![TrackEvent interning](/docs/images/synthetic-track-event-interned.png)
This corresponds to the following protos:
+
```
packet {
track_descriptor {
@@ -431,7 +726,7 @@
first_packet_on_sequence: true # Indicates to trace processor that
# this is the first packet on the
- # sequence.
+ # sequence.
previous_packet_dropped: true # Same as |first_packet_on_sequence|.
# Indicates to trace processor that this sequence resets the incremental state but
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/examples/sdk/README.md b/examples/sdk/README.md
index 404647d..042b7b5 100644
--- a/examples/sdk/README.md
+++ b/examples/sdk/README.md
@@ -15,7 +15,7 @@
First, check out the latest Perfetto release:
```bash
-git clone https://android.googlesource.com/platform/external/perfetto -b v47.0
+git clone https://android.googlesource.com/platform/external/perfetto -b v48.1
```
Then, build using CMake:
diff --git a/examples/shared_lib/example_shlib_track_event.c b/examples/shared_lib/example_shlib_track_event.c
index 143180c..a63666a 100644
--- a/examples/shared_lib/example_shlib_track_event.c
+++ b/examples/shared_lib/example_shlib_track_event.c
@@ -93,6 +93,10 @@
perfetto_protos_TrackEvent_source_location_field_number,
PERFETTO_TE_PROTO_FIELD_CSTR(2, __FILE__),
PERFETTO_TE_PROTO_FIELD_VARINT(4, __LINE__))));
+ PERFETTO_TE(
+ physics, PERFETTO_TE_COUNTER(),
+ PERFETTO_TE_COUNTER_TRACK("mycounter", PerfettoTeProcessTrackUuid()),
+ PERFETTO_TE_INT_COUNTER(89));
PERFETTO_TE(PERFETTO_TE_DYNAMIC_CATEGORY, PERFETTO_TE_COUNTER(),
PERFETTO_TE_DOUBLE_COUNTER(3.14),
PERFETTO_TE_REGISTERED_TRACK(&mycounter),
diff --git a/gn/BUILD.gn b/gn/BUILD.gn
index 1bd21b4..f19df9b 100644
--- a/gn/BUILD.gn
+++ b/gn/BUILD.gn
@@ -96,6 +96,7 @@
"PERFETTO_X64_CPU_OPT=$enable_perfetto_x64_cpu_opt",
"PERFETTO_LLVM_DEMANGLE=$enable_perfetto_llvm_demangle",
"PERFETTO_SYSTEM_CONSUMER=$enable_perfetto_system_consumer",
+ "PERFETTO_THREAD_SAFETY_ANNOTATIONS=$perfetto_thread_safety_annotations",
]
rel_out_path = rebase_path(gen_header_path, "$root_build_dir")
diff --git a/gn/perfetto.gni b/gn/perfetto.gni
index 6e632ec..987d347 100644
--- a/gn/perfetto.gni
+++ b/gn/perfetto.gni
@@ -108,6 +108,7 @@
if (perfetto_build_standalone || is_perfetto_build_generator) {
perfetto_root_path = "//"
import("//gn/standalone/android.gni") # For android_api_level
+ import("//gn/standalone/libc++/libc++.gni") # For use_custom_libcxx
import("//gn/standalone/sanitizers/vars.gni") # For is_fuzzer
} else if (!defined(perfetto_root_path)) {
perfetto_root_path = "//third_party/perfetto/"
@@ -246,6 +247,11 @@
enable_perfetto_x64_cpu_opt =
current_cpu == "x64" && is_linux && !is_wasm &&
perfetto_build_standalone && !is_perfetto_build_generator
+
+ # Enables complie-time thread safety analysis.
+ perfetto_thread_safety_annotations =
+ perfetto_build_standalone && !is_perfetto_build_generator &&
+ defined(use_custom_libcxx) && use_custom_libcxx
}
declare_args() {
diff --git a/gn/proto_library.gni b/gn/proto_library.gni
index a37b4ac..af540c2 100644
--- a/gn/proto_library.gni
+++ b/gn/proto_library.gni
@@ -79,10 +79,11 @@
component_build_force_source_set = true
}
+ # Convert deps to link_deps: the proto_library rule requires that C++ files
+ # are passed in as link_deps wheras in Perfetto, we always works with deps.
+ link_deps = []
if (defined(invoker.deps)) {
- deps = invoker.deps
- } else {
- deps = []
+ link_deps += invoker.deps
}
# omit_protozero_dep is intended to be used when protozero_library
@@ -93,7 +94,11 @@
#
# TODO(b/173041866): use fine-grained components instead when available
if (!(defined(invoker.omit_protozero_dep) && invoker.omit_protozero_dep)) {
- deps += [ perfetto_root_path + "src/protozero" ]
+ link_deps += [ perfetto_root_path + "src/protozero" ]
+ }
+
+ if (defined(invoker.link_deps)) {
+ link_deps += invoker.link_deps
}
forward_variables_from(invoker,
@@ -102,6 +107,7 @@
"generator_plugin_options",
"include_dirs",
"proto_data_sources",
+ "proto_deps",
"proto_in_dir",
"proto_out_dir",
"sources",
@@ -132,21 +138,23 @@
component_build_force_source_set = true
}
- deps = [
+ link_deps = [
"$perfetto_root_path/gn:default_deps",
"$perfetto_root_path/include/perfetto/base",
"$perfetto_root_path/src/protozero",
]
+ # Convert deps to link_deps: the proto_library rule requires that C++ files
+ # are passed in as link_deps wheras in Perfetto, we always works with deps.
if (defined(invoker.deps)) {
- deps += invoker.deps
+ link_deps += invoker.deps
}
-
forward_variables_from(invoker,
[
"defines",
"generator_plugin_options",
"include_dirs",
+ "proto_deps",
"proto_in_dir",
"proto_out_dir",
"sources",
@@ -168,11 +176,11 @@
generator_plugin_label =
"$perfetto_root_path/src/ipc/protoc_plugin:ipc_plugin"
generator_plugin_suffix = ".ipc"
- deps = [ "$perfetto_root_path/gn:default_deps" ]
+ link_deps = [ "$perfetto_root_path/gn:default_deps" ]
if (perfetto_component_type == "static_library") {
- deps += [ "$perfetto_root_path/src/ipc:perfetto_ipc" ]
+ link_deps += [ "$perfetto_root_path/src/ipc:perfetto_ipc" ]
} else {
- deps += [ "$perfetto_root_path/src/ipc:common" ]
+ link_deps += [ "$perfetto_root_path/src/ipc:common" ]
}
if (is_win) {
# TODO(primiano): investigate this. In Windows standalone builds, some
@@ -183,20 +191,23 @@
# client-side IPC library. Perhaps we just should do this unconditionally
# on all platforms?
if (perfetto_component_type == "static_library") {
- deps += [ "$perfetto_root_path/src/ipc:perfetto_ipc" ]
+ link_deps += [ "$perfetto_root_path/src/ipc:perfetto_ipc" ]
} else {
- deps += [ "$perfetto_root_path/src/ipc:client" ]
+ link_deps += [ "$perfetto_root_path/src/ipc:client" ]
}
}
+ # Convert deps to link_deps: the proto_library rule requires that C++ files
+ # are passed in as link_deps wheras in Perfetto, we always works with deps.
if (defined(invoker.deps)) {
- deps += invoker.deps
+ link_deps += invoker.deps
}
forward_variables_from(invoker,
[
"defines",
"extra_configs",
"include_dirs",
+ "proto_deps",
"proto_in_dir",
"proto_out_dir",
"generator_plugin_options",
@@ -225,10 +236,14 @@
generator_plugin_label =
"$perfetto_root_path/buildtools/grpc:grpc_cpp_plugin"
generator_plugin_suffix = ".grpc.pb"
- deps = [ "$perfetto_root_path/buildtools/grpc:grpc++" ]
+ link_deps = [ "$perfetto_root_path/buildtools/grpc:grpc++" ]
public_configs = [ "$perfetto_root_path/buildtools:grpc_gen_config" ]
+
+ # Convert deps to link_deps: the proto_library rule requires that C++
+ # files are passed in as link_deps wheras in Perfetto, we always works
+ # with deps.
if (defined(invoker.deps)) {
- deps += invoker.deps
+ link_deps += invoker.deps
}
forward_variables_from(invoker,
[
@@ -252,7 +267,6 @@
"zero",
"lite",
"cpp",
- "source_set",
]
}
@@ -269,46 +283,114 @@
import_dirs_ = []
}
- vars_to_forward = [
- "sources",
- "visibility",
- "testonly",
- "exclude_imports",
- ]
expansion_token = "@TYPE@"
- # gn:public_config propagates the gen dir as include directory. We
- # disable the proto_library's public_config to avoid duplicate include
- # directory command line flags (crbug.com/1043279, crbug.com/gn/142).
- propagate_imports_configs_ = false
+ # The source set target should always be generated as it is used by the
+ # build generators and for generating descriptors.
+ source_set_target_name =
+ string_replace(target_name, expansion_token, "source_set")
+
+ # This config is necessary for Chrome proto_library build rule to work
+ # correctly.
+ source_set_input_config_name = "${source_set_target_name}_input_config"
+ config(source_set_input_config_name) {
+ 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")
+ proto_library(target_name_) {
+ proto_in_dir = proto_path
+ proto_out_dir = proto_path
+ generate_python = false
+ generate_cc = false
+ generate_descriptor = rebase_path(invoker.generate_descriptor, proto_path)
+ sources = [ invoker.descriptor_root_source ]
+ import_dirs = import_dirs_
+ deps = [ ":${source_set_target_name}" ]
+ forward_variables_from(invoker,
+ [
+ "visibility",
+ "testonly",
+ "exclude_imports",
+ ])
+ }
+ }
foreach(gen_type, proto_generators) {
target_name_ = string_replace(target_name, expansion_token, gen_type)
# Translate deps from xxx:@TYPE@ to xxx:lite/zero.
- deps_ = []
+ all_deps_ = []
if (defined(invoker.deps)) {
foreach(dep, invoker.deps) {
- deps_ += [ string_replace(dep, expansion_token, gen_type) ]
+ all_deps_ += [ string_replace(dep, expansion_token, gen_type) ]
}
}
# The distinction between deps and public_deps does not matter for GN
# but Bazel cares about the difference so we distinguish between the two.
- public_deps_ = []
if (defined(invoker.public_deps)) {
foreach(dep, invoker.public_deps) {
- public_deps_ = [ string_replace(dep, expansion_token, gen_type) ]
+ all_deps_ += [ string_replace(dep, expansion_token, gen_type) ]
}
}
- deps_ += public_deps_
+
+ # gn:public_config propagates the gen dir as include directory. We
+ # disable the proto_library's public_config to avoid duplicate include
+ # directory command line flags (crbug.com/1043279, crbug.com/gn/142).
+ propagate_imports_configs_ = false
+ vars_to_forward = []
+ vars_to_forward += [
+ "sources",
+ "visibility",
+ "testonly",
+ ]
if (gen_type == "zero") {
protozero_library(target_name_) {
proto_in_dir = proto_path
proto_out_dir = proto_path
generator_plugin_options = "wrapper_namespace=pbzero"
- deps = deps_
+ 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)
@@ -318,7 +400,8 @@
proto_in_dir = proto_path
proto_out_dir = proto_path
generator_plugin_options = "wrapper_namespace=gen"
- deps = deps_
+ 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)
@@ -329,7 +412,8 @@
proto_in_dir = proto_path
proto_out_dir = proto_path
generator_plugin_options = "wrapper_namespace=gen"
- deps = deps_ + [ ":$cpp_target_name_" ]
+ proto_deps = [ ":$source_set_target_name" ]
+ deps = all_deps_ + [ ":$cpp_target_name_" ]
propagate_imports_configs = propagate_imports_configs_
import_dirs = import_dirs_
forward_variables_from(invoker, vars_to_forward)
@@ -339,59 +423,13 @@
proto_in_dir = proto_path
proto_out_dir = proto_path
generate_python = false
- deps = deps_
+ link_deps = all_deps_
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 if (gen_type == "descriptor") {
- proto_library(target_name_) {
- proto_in_dir = proto_path
- proto_out_dir = proto_path
- generate_python = false
- generate_cc = false
- generate_descriptor =
- rebase_path(invoker.generate_descriptor, proto_path)
- deps = deps_
- import_dirs = import_dirs_
- forward_variables_from(invoker, vars_to_forward)
- }
-
- # Not needed for descriptor proto_library target.
- not_needed([ "propagate_imports_configs_" ])
- } else if (gen_type == "source_set") {
- action(target_name_) {
- out_path = "$target_gen_dir/" + target_name_
- rebased_out_path =
- rebase_path(target_gen_dir, root_build_dir) + "/" + target_name_
-
- script = "$perfetto_root_path/tools/touch_file.py"
- args = [
- "--output",
- rebased_out_path,
- ]
- outputs = [ out_path ]
- deps = deps_
-
- metadata = {
- proto_library_sources = invoker.sources
- proto_import_dirs = import_dirs_
- exports = []
- foreach(i, public_deps_) {
- # Get the absolute target path
- exports +=
- [ get_label_info(i, "dir") + ":" + get_label_info(i, "name") ]
- }
- }
- forward_variables_from(invoker, vars_to_forward)
- }
-
- # Not needed for source_set proto_library target.
- not_needed([
- "propagate_imports_configs_",
- "proto_path",
- ])
} else {
assert(false, "Invalid 'proto_generators' value.")
}
diff --git a/gn/standalone/BUILD.gn b/gn/standalone/BUILD.gn
index 36fb637..a6455ef 100644
--- a/gn/standalone/BUILD.gn
+++ b/gn/standalone/BUILD.gn
@@ -95,6 +95,13 @@
# codebase cleanup.
"-Wno-switch-default",
]
+
+ if (perfetto_thread_safety_annotations) {
+ cflags += [
+ "-Wthread-safety",
+ "-Wno-thread-safety-negative",
+ ]
+ }
} else if (is_gcc) {
# Use return std::move(...) for compatibility with old GCC compilers.
cflags_cc = [ "-Wno-redundant-move" ]
diff --git a/gn/standalone/proto_library.gni b/gn/standalone/proto_library.gni
index 1a23a97..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.
@@ -222,13 +226,6 @@
deps = []
}
- # TODO(hjd): Avoid adding to deps here this.
- # When we generate BUILD files we need find the transitive proto,
- # dependencies, so also add link_deps to actual deps so they show up
- # in gn desc.
- if (defined(invoker.link_deps)) {
- deps += invoker.link_deps
- }
if (generate_with_plugin) {
inputs += [ plugin_path ]
if (defined(plugin_host_label)) {
@@ -237,9 +234,12 @@
}
}
- if (defined(invoker.deps)) {
+ if (generate_descriptor != "") {
deps += invoker.deps
}
+ if (defined(invoker.link_deps)) {
+ deps += invoker.link_deps
+ }
} # action(action_name)
# The source_set that builds the generated .pb.cc files.
@@ -299,6 +299,9 @@
if (defined(invoker.deps)) {
deps += invoker.deps
}
+ if (defined(invoker.link_deps)) {
+ deps += invoker.link_deps
+ }
} # source_set(source_set_name)
}
} # template
diff --git a/include/perfetto/base/BUILD.gn b/include/perfetto/base/BUILD.gn
index 357f89a..1fa5c9d 100644
--- a/include/perfetto/base/BUILD.gn
+++ b/include/perfetto/base/BUILD.gn
@@ -26,6 +26,7 @@
"status.h",
"task_runner.h",
"template_util.h",
+ "thread_annotations.h",
"thread_utils.h",
"time.h",
]
diff --git a/include/perfetto/base/build_configs/android_tree/perfetto_build_flags.h b/include/perfetto/base/build_configs/android_tree/perfetto_build_flags.h
index 52deaa0..6cb3e3d 100644
--- a/include/perfetto/base/build_configs/android_tree/perfetto_build_flags.h
+++ b/include/perfetto/base/build_configs/android_tree/perfetto_build_flags.h
@@ -47,6 +47,7 @@
#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_X64_CPU_OPT() (0)
#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_LLVM_DEMANGLE() (0)
#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_SYSTEM_CONSUMER() (1)
+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_THREAD_SAFETY_ANNOTATIONS() (0)
// clang-format on
#endif // GEN_BUILD_CONFIG_PERFETTO_BUILD_FLAGS_H_
diff --git a/include/perfetto/base/build_configs/bazel/perfetto_build_flags.h b/include/perfetto/base/build_configs/bazel/perfetto_build_flags.h
index c982321..c27eaf8 100644
--- a/include/perfetto/base/build_configs/bazel/perfetto_build_flags.h
+++ b/include/perfetto/base/build_configs/bazel/perfetto_build_flags.h
@@ -47,6 +47,7 @@
#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_X64_CPU_OPT() (0)
#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_LLVM_DEMANGLE() (1)
#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_SYSTEM_CONSUMER() (1)
+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_THREAD_SAFETY_ANNOTATIONS() (0)
// clang-format on
#endif // GEN_BUILD_CONFIG_PERFETTO_BUILD_FLAGS_H_
diff --git a/include/perfetto/base/compiler.h b/include/perfetto/base/compiler.h
index ad78786..2bdc158 100644
--- a/include/perfetto/base/compiler.h
+++ b/include/perfetto/base/compiler.h
@@ -108,15 +108,6 @@
#define PERFETTO_EXPORT_ENTRYPOINT
#endif
-// Disables thread safety analysis for functions where the compiler can't
-// accurate figure out which locks are being held.
-#if defined(__clang__)
-#define PERFETTO_NO_THREAD_SAFETY_ANALYSIS \
- __attribute__((no_thread_safety_analysis))
-#else
-#define PERFETTO_NO_THREAD_SAFETY_ANALYSIS
-#endif
-
// Disables undefined behavior analysis for a function.
#if defined(__clang__)
#define PERFETTO_NO_SANITIZE_UNDEFINED __attribute__((no_sanitize("undefined")))
diff --git a/include/perfetto/base/thread_annotations.h b/include/perfetto/base/thread_annotations.h
new file mode 100644
index 0000000..3d91269
--- /dev/null
+++ b/include/perfetto/base/thread_annotations.h
@@ -0,0 +1,280 @@
+/*
+ * 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.
+ */
+
+#ifndef INCLUDE_PERFETTO_BASE_THREAD_ANNOTATIONS_H_
+#define INCLUDE_PERFETTO_BASE_THREAD_ANNOTATIONS_H_
+
+// This header file contains macro definitions for thread safety annotations
+// that allow developers to document the locking policies of multi-threaded
+// code. The annotations can also help program analysis tools to identify
+// potential thread safety issues.
+//
+// These macro definitions are copied from the Chromium code base:
+// https://source.chromium.org/chromium/chromium/src/+/main:base/thread_annotations.h;drc=10d865767e72f494da1e4e868eb6ae9befe87422
+// with the 'PERFETTO_' prefix added.
+//
+// Note that no analysis is done inside constructors and destructors,
+// regardless of what attributes are used. See
+// https://clang.llvm.org/docs/ThreadSafetyAnalysis.html#no-checking-inside-constructors-and-destructors
+// for details.
+//
+// Note that the annotations we use are described as deprecated in the Clang
+// documentation, linked below. E.g. we use PERFETTO_EXCLUSIVE_LOCKS_REQUIRED
+// where the Clang docs use REQUIRES.
+//
+// http://clang.llvm.org/docs/ThreadSafetyAnalysis.html
+//
+// We use the deprecated Clang annotations to match Abseil (relevant header
+// linked below) and its ecosystem of libraries. We will follow Abseil with
+// respect to upgrading to more modern annotations.
+//
+// https://github.com/abseil/abseil-cpp/blob/master/absl/base/thread_annotations.h
+//
+// These annotations are implemented using compiler attributes. Using the macros
+// defined here instead of raw attributes allow for portability and future
+// compatibility.
+//
+// When referring to mutexes in the arguments of the attributes, you should
+// use variable names or more complex expressions (e.g. my_object->mutex_)
+// that evaluate to a concrete mutex object whenever possible. If the mutex
+// you want to refer to is not in scope, you may use a member pointer
+// (e.g. &MyClass::mutex_) to refer to a mutex in some (unknown) object.
+
+#include "perfetto/base/build_config.h"
+
+#if defined(__clang__) && PERFETTO_BUILDFLAG(PERFETTO_THREAD_SAFETY_ANNOTATIONS)
+#define PERFETTO_THREAD_ANNOTATION_ATTRIBUTE__(x) __attribute__((x))
+#else
+#define PERFETTO_THREAD_ANNOTATION_ATTRIBUTE__(x) // no-op
+#endif
+
+// PERFETTO_GUARDED_BY()
+//
+// Documents if a shared field or global variable needs to be protected by a
+// mutex. PERFETTO_GUARDED_BY() allows the user to specify a particular mutex
+// that should be held when accessing the annotated variable.
+//
+// Example:
+//
+// Mutex mu;
+// int p1 PERFETTO_GUARDED_BY(mu);
+#define PERFETTO_GUARDED_BY(x) \
+ PERFETTO_THREAD_ANNOTATION_ATTRIBUTE__(guarded_by(x))
+
+// PERFETTO_PT_GUARDED_BY()
+//
+// Documents if the memory location pointed to by a pointer should be guarded
+// by a mutex when dereferencing the pointer.
+//
+// Example:
+// Mutex mu;
+// int *p1 PERFETTO_PT_GUARDED_BY(mu);
+//
+// Note that a pointer variable to a shared memory location could itself be a
+// shared variable.
+//
+// Example:
+//
+// // `q`, guarded by `mu1`, points to a shared memory location that is
+// // guarded by `mu2`:
+// int *q PERFETTO_GUARDED_BY(mu1) PERFETTO_PT_GUARDED_BY(mu2);
+#define PERFETTO_PT_GUARDED_BY(x) \
+ PERFETTO_THREAD_ANNOTATION_ATTRIBUTE__(pt_guarded_by(x))
+
+// PERFETTO_ACQUIRED_AFTER() / PERFETTO_ACQUIRED_BEFORE()
+//
+// Documents the acquisition order between locks that can be held
+// simultaneously by a thread. For any two locks that need to be annotated
+// to establish an acquisition order, only one of them needs the annotation.
+// (i.e. You don't have to annotate both locks with both PERFETTO_ACQUIRED_AFTER
+// and PERFETTO_ACQUIRED_BEFORE.)
+//
+// Example:
+//
+// Mutex m1;
+// Mutex m2 PERFETTO_ACQUIRED_AFTER(m1);
+#define PERFETTO_ACQUIRED_AFTER(...) \
+ PERFETTO_THREAD_ANNOTATION_ATTRIBUTE__(acquired_after(__VA_ARGS__))
+
+#define PERFETTO_ACQUIRED_BEFORE(...) \
+ PERFETTO_THREAD_ANNOTATION_ATTRIBUTE__(acquired_before(__VA_ARGS__))
+
+// PERFETTO_EXCLUSIVE_LOCKS_REQUIRED() / PERFETTO_SHARED_LOCKS_REQUIRED()
+//
+// Documents a function that expects a mutex to be held prior to entry.
+// The mutex is expected to be held both on entry to, and exit from, the
+// function.
+//
+// Example:
+//
+// Mutex mu1, mu2;
+// int a PERFETTO_GUARDED_BY(mu1);
+// int b PERFETTO_GUARDED_BY(mu2);
+//
+// void foo() PERFETTO_EXCLUSIVE_LOCKS_REQUIRED(mu1, mu2) { ... };
+#define PERFETTO_EXCLUSIVE_LOCKS_REQUIRED(...) \
+ PERFETTO_THREAD_ANNOTATION_ATTRIBUTE__(exclusive_locks_required(__VA_ARGS__))
+
+#define PERFETTO_SHARED_LOCKS_REQUIRED(...) \
+ PERFETTO_THREAD_ANNOTATION_ATTRIBUTE__(shared_locks_required(__VA_ARGS__))
+
+// PERFETTO_LOCKS_EXCLUDED()
+//
+// Documents the locks acquired in the body of the function. These locks
+// cannot be held when calling this function (as Abseil's `Mutex` locks are
+// non-reentrant).
+#define PERFETTO_LOCKS_EXCLUDED(...) \
+ PERFETTO_THREAD_ANNOTATION_ATTRIBUTE__(locks_excluded(__VA_ARGS__))
+
+// PERFETTO_LOCK_RETURNED()
+//
+// Documents a function that returns a mutex without acquiring it. For example,
+// a public getter method that returns a pointer to a private mutex should
+// be annotated with PERFETTO_LOCK_RETURNED.
+#define PERFETTO_LOCK_RETURNED(x) \
+ PERFETTO_THREAD_ANNOTATION_ATTRIBUTE__(lock_returned(x))
+
+// PERFETTO_LOCKABLE
+//
+// Documents if a class/type is a lockable type (such as the `Mutex` class).
+#define PERFETTO_LOCKABLE PERFETTO_THREAD_ANNOTATION_ATTRIBUTE__(lockable)
+
+// PERFETTO_SCOPED_LOCKABLE
+//
+// Documents if a class does RAII locking (such as the `MutexLock` class).
+// The constructor should use `LOCK_FUNCTION()` to specify the mutex that is
+// acquired, and the destructor should use `PERFETTO_UNLOCK_FUNCTION()` with no
+// arguments; the analysis will assume that the destructor unlocks whatever the
+// constructor locked.
+#define PERFETTO_SCOPED_LOCKABLE \
+ PERFETTO_THREAD_ANNOTATION_ATTRIBUTE__(scoped_lockable)
+
+// PERFETTO_EXCLUSIVE_LOCK_FUNCTION()
+//
+// Documents functions that acquire a lock in the body of a function, and do
+// not release it.
+#define PERFETTO_EXCLUSIVE_LOCK_FUNCTION(...) \
+ PERFETTO_THREAD_ANNOTATION_ATTRIBUTE__(exclusive_lock_function(__VA_ARGS__))
+
+// PERFETTO_SHARED_LOCK_FUNCTION()
+//
+// Documents functions that acquire a shared (reader) lock in the body of a
+// function, and do not release it.
+#define PERFETTO_SHARED_LOCK_FUNCTION(...) \
+ PERFETTO_THREAD_ANNOTATION_ATTRIBUTE__(shared_lock_function(__VA_ARGS__))
+
+// PERFETTO_UNLOCK_FUNCTION()
+//
+// Documents functions that expect a lock to be held on entry to the function,
+// and release it in the body of the function.
+#define PERFETTO_UNLOCK_FUNCTION(...) \
+ PERFETTO_THREAD_ANNOTATION_ATTRIBUTE__(unlock_function(__VA_ARGS__))
+
+// PERFETTO_EXCLUSIVE_TRYLOCK_FUNCTION() / PERFETTO_SHARED_TRYLOCK_FUNCTION()
+//
+// Documents functions that try to acquire a lock, and return success or failure
+// (or a non-boolean value that can be interpreted as a boolean).
+// The first argument should be `true` for functions that return `true` on
+// success, or `false` for functions that return `false` on success. The second
+// argument specifies the mutex that is locked on success. If unspecified, this
+// mutex is assumed to be `this`.
+#define PERFETTO_EXCLUSIVE_TRYLOCK_FUNCTION(...) \
+ PERFETTO_THREAD_ANNOTATION_ATTRIBUTE__( \
+ exclusive_trylock_function(__VA_ARGS__))
+
+#define PERFETTO_SHARED_TRYLOCK_FUNCTION(...) \
+ PERFETTO_THREAD_ANNOTATION_ATTRIBUTE__(shared_trylock_function(__VA_ARGS__))
+
+// PERFETTO_ASSERT_EXCLUSIVE_LOCK() / PERFETTO_ASSERT_SHARED_LOCK()
+//
+// Documents functions that dynamically check to see if a lock is held, and fail
+// if it is not held.
+#define PERFETTO_ASSERT_EXCLUSIVE_LOCK(...) \
+ PERFETTO_THREAD_ANNOTATION_ATTRIBUTE__(assert_exclusive_lock(__VA_ARGS__))
+
+#define PERFETTO_ASSERT_SHARED_LOCK(...) \
+ PERFETTO_THREAD_ANNOTATION_ATTRIBUTE__(assert_shared_lock(__VA_ARGS__))
+
+// PERFETTO_NO_THREAD_SAFETY_ANALYSIS is special and differs from other
+// macros defined in this file, it was defined in `compiler.h` and used before
+// we introduce Thread Safety Analysis. Therefore, we define it here even if
+// 'PERFETTO_ENABLE_THREAD_SAFETY_ANNOTATIONS' macro is not defined.
+
+#if defined(__clang__)
+// PERFETTO_NO_THREAD_SAFETY_ANALYSIS
+//
+// Turns off thread safety checking within the body of a particular function.
+// This annotation is used to mark functions that are known to be correct, but
+// the locking behavior is more complicated than the analyzer can handle.
+#define PERFETTO_NO_THREAD_SAFETY_ANALYSIS \
+ __attribute__((no_thread_safety_analysis))
+#else
+#define PERFETTO_NO_THREAD_SAFETY_ANALYSIS
+#endif
+
+//------------------------------------------------------------------------------
+// Tool-Supplied Annotations
+//------------------------------------------------------------------------------
+
+// PERFETTO_TS_UNCHECKED should be placed around lock expressions that are not
+// valid C++ syntax, but which are present for documentation purposes. These
+// annotations will be ignored by the analysis.
+#define PERFETTO_TS_UNCHECKED(x) ""
+
+// TS_FIXME is used to mark lock expressions that are not valid C++ syntax.
+// It is used by automated tools to mark and disable invalid expressions.
+// The annotation should either be fixed, or changed to PERFETTO_TS_UNCHECKED.
+#define PERFETTO_TS_FIXME(x) ""
+
+// Like NO_THREAD_SAFETY_ANALYSIS, this turns off checking within the body of
+// a particular function. However, this attribute is used to mark functions
+// that are incorrect and need to be fixed. It is used by automated tools to
+// avoid breaking the build when the analysis is updated.
+// Code owners are expected to eventually fix the routine.
+#define PERFETTO_NO_THREAD_SAFETY_ANALYSIS_FIXME \
+ PERFETTO_NO_THREAD_SAFETY_ANALYSIS
+
+// Similar to NO_THREAD_SAFETY_ANALYSIS_FIXME, this macro marks a
+// PERFETTO_GUARDED_BY annotation that needs to be fixed, because it is
+// producing thread safety warning. It disables the PERFETTO_GUARDED_BY.
+#define PERFETTO_PERFETTO_GUARDED_BY_FIXME(x)
+
+// Disables warnings for a single read operation. This can be used to avoid
+// warnings when it is known that the read is not actually involved in a race,
+// but the compiler cannot confirm that.
+#define PERFETTO_TS_UNCHECKED_READ(x) \
+ perfetto::thread_safety_analysis::ts_unchecked_read(x)
+
+namespace perfetto {
+namespace thread_safety_analysis {
+
+// Takes a reference to a guarded data member, and returns an unguarded
+// reference.
+template <typename T>
+inline const T& ts_unchecked_read(const T& v)
+ PERFETTO_NO_THREAD_SAFETY_ANALYSIS {
+ return v;
+}
+
+template <typename T>
+inline T& ts_unchecked_read(T& v) PERFETTO_NO_THREAD_SAFETY_ANALYSIS {
+ return v;
+}
+
+} // namespace thread_safety_analysis
+} // namespace perfetto
+
+#endif // INCLUDE_PERFETTO_BASE_THREAD_ANNOTATIONS_H_
diff --git a/include/perfetto/ext/base/BUILD.gn b/include/perfetto/ext/base/BUILD.gn
index 2418346..b485000 100644
--- a/include/perfetto/ext/base/BUILD.gn
+++ b/include/perfetto/ext/base/BUILD.gn
@@ -19,6 +19,7 @@
"android_utils.h",
"base64.h",
"circular_queue.h",
+ "clock_snapshots.h",
"container_annotations.h",
"crash_keys.h",
"ctrl_c_handler.h",
diff --git a/include/perfetto/tracing/core/clock_snapshots.h b/include/perfetto/ext/base/clock_snapshots.h
similarity index 83%
rename from include/perfetto/tracing/core/clock_snapshots.h
rename to include/perfetto/ext/base/clock_snapshots.h
index 49d4c67..704d0c8 100644
--- a/include/perfetto/tracing/core/clock_snapshots.h
+++ b/include/perfetto/ext/base/clock_snapshots.h
@@ -14,13 +14,14 @@
* limitations under the License.
*/
-#ifndef INCLUDE_PERFETTO_TRACING_CORE_CLOCK_SNAPSHOTS_H_
-#define INCLUDE_PERFETTO_TRACING_CORE_CLOCK_SNAPSHOTS_H_
+#ifndef INCLUDE_PERFETTO_EXT_BASE_CLOCK_SNAPSHOTS_H_
+#define INCLUDE_PERFETTO_EXT_BASE_CLOCK_SNAPSHOTS_H_
#include <cstdint>
#include <vector>
-namespace perfetto {
+namespace perfetto::base {
+
struct ClockReading {
ClockReading(uint32_t _clock_id, uint64_t _timestamp)
: clock_id(_clock_id), timestamp(_timestamp) {}
@@ -37,6 +38,6 @@
// Takes snapshots of clock readings of all supported built-in clocks.
ClockSnapshotVector CaptureClockSnapshots();
-} // namespace perfetto
+} // namespace perfetto::base
-#endif // INCLUDE_PERFETTO_TRACING_CORE_CLOCK_SNAPSHOTS_H_
+#endif // INCLUDE_PERFETTO_EXT_BASE_CLOCK_SNAPSHOTS_H_
diff --git a/include/perfetto/ext/base/threading/channel.h b/include/perfetto/ext/base/threading/channel.h
index 9bf42f5..3659d28 100644
--- a/include/perfetto/ext/base/threading/channel.h
+++ b/include/perfetto/ext/base/threading/channel.h
@@ -22,6 +22,7 @@
#include "perfetto/base/compiler.h"
#include "perfetto/base/platform_handle.h"
+#include "perfetto/base/thread_annotations.h"
#include "perfetto/ext/base/circular_queue.h"
#include "perfetto/ext/base/event_fd.h"
@@ -167,8 +168,8 @@
private:
std::mutex mutex_;
- base::CircularQueue<T> elements_;
- bool is_closed_ = false;
+ base::CircularQueue<T> elements_ PERFETTO_GUARDED_BY(mutex_);
+ bool is_closed_ PERFETTO_GUARDED_BY(mutex_) = false;
base::EventFd read_fd_;
base::EventFd write_fd_;
diff --git a/include/perfetto/ext/base/threading/thread_pool.h b/include/perfetto/ext/base/threading/thread_pool.h
index e08baaa..44402af 100644
--- a/include/perfetto/ext/base/threading/thread_pool.h
+++ b/include/perfetto/ext/base/threading/thread_pool.h
@@ -26,6 +26,7 @@
#include <vector>
#include "perfetto/base/task_runner.h"
+#include "perfetto/base/thread_annotations.h"
namespace perfetto {
namespace base {
@@ -63,13 +64,11 @@
ThreadPool(ThreadPool&&) = delete;
ThreadPool& operator=(ThreadPool&&) = delete;
- // Start of mutex protected members.
std::mutex mutex_;
- std::list<std::function<void()>> pending_tasks_;
- std::condition_variable thread_waiter_;
- uint32_t thread_waiting_count_ = 0;
- bool quit_ = false;
- // End of mutex protected members.
+ std::list<std::function<void()>> pending_tasks_ PERFETTO_GUARDED_BY(mutex_);
+ std::condition_variable thread_waiter_ PERFETTO_GUARDED_BY(mutex_);
+ uint32_t thread_waiting_count_ PERFETTO_GUARDED_BY(mutex_) = 0;
+ bool quit_ PERFETTO_GUARDED_BY(mutex_) = false;
std::vector<std::thread> threads_;
};
diff --git a/include/perfetto/ext/base/unix_task_runner.h b/include/perfetto/ext/base/unix_task_runner.h
index ecde733..5f42161 100644
--- a/include/perfetto/ext/base/unix_task_runner.h
+++ b/include/perfetto/ext/base/unix_task_runner.h
@@ -19,6 +19,7 @@
#include "perfetto/base/build_config.h"
#include "perfetto/base/task_runner.h"
+#include "perfetto/base/thread_annotations.h"
#include "perfetto/base/thread_utils.h"
#include "perfetto/base/time.h"
#include "perfetto/ext/base/event_fd.h"
@@ -69,6 +70,10 @@
// delayed tasks don't count even if they are due to run.
bool IsIdleForTesting();
+ // Pretends (for the purposes of running delayed tasks) that time advanced by
+ // `ms`.
+ void AdvanceTimeForTesting(uint32_t ms);
+
// TaskRunner implementation:
void PostTask(std::function<void()>) override;
void PostDelayedTask(std::function<void()>, uint32_t delay_ms) override;
@@ -83,8 +88,9 @@
private:
void WakeUp();
- void UpdateWatchTasksLocked();
- int GetDelayMsToNextTaskLocked() const;
+ void UpdateWatchTasksLocked() PERFETTO_EXCLUSIVE_LOCKS_REQUIRED(lock_);
+ int GetDelayMsToNextTaskLocked() const
+ PERFETTO_EXCLUSIVE_LOCKS_REQUIRED(lock_);
void RunImmediateAndDelayedTask();
void PostFileDescriptorWatches(uint64_t windows_wait_result);
void RunFileDescriptorWatch(PlatformHandle);
@@ -101,13 +107,14 @@
std::vector<struct pollfd> poll_fds_;
#endif
- // --- Begin lock-protected members ---
-
std::mutex lock_;
- std::deque<std::function<void()>> immediate_tasks_;
- std::multimap<TimeMillis, std::function<void()>> delayed_tasks_;
- bool quit_ = false;
+ std::deque<std::function<void()>> immediate_tasks_ PERFETTO_GUARDED_BY(lock_);
+ std::multimap<TimeMillis, std::function<void()>> delayed_tasks_
+ PERFETTO_GUARDED_BY(lock_);
+ bool quit_ PERFETTO_GUARDED_BY(lock_) = false;
+ TimeMillis advanced_time_for_testing_ PERFETTO_GUARDED_BY(lock_) =
+ TimeMillis(0);
struct WatchTask {
std::function<void()> callback;
@@ -121,10 +128,8 @@
#endif
};
- std::map<PlatformHandle, WatchTask> watch_tasks_;
- bool watch_tasks_changed_ = false;
-
- // --- End lock-protected members ---
+ std::map<PlatformHandle, WatchTask> watch_tasks_ PERFETTO_GUARDED_BY(lock_);
+ bool watch_tasks_changed_ PERFETTO_GUARDED_BY(lock_) = false;
};
} // namespace base
diff --git a/include/perfetto/ext/base/uuid.h b/include/perfetto/ext/base/uuid.h
index 2bf5f5b..55a2c32 100644
--- a/include/perfetto/ext/base/uuid.h
+++ b/include/perfetto/ext/base/uuid.h
@@ -23,10 +23,12 @@
#include <optional>
#include <string>
+#include "perfetto/base/export.h"
+
namespace perfetto {
namespace base {
-class Uuid {
+class PERFETTO_EXPORT_COMPONENT Uuid {
public:
explicit Uuid(const std::string& s);
explicit Uuid(int64_t lsb, int64_t msb);
diff --git a/include/perfetto/ext/base/waitable_event.h b/include/perfetto/ext/base/waitable_event.h
index 0e78619..a55b34b 100644
--- a/include/perfetto/ext/base/waitable_event.h
+++ b/include/perfetto/ext/base/waitable_event.h
@@ -17,6 +17,8 @@
#ifndef INCLUDE_PERFETTO_EXT_BASE_WAITABLE_EVENT_H_
#define INCLUDE_PERFETTO_EXT_BASE_WAITABLE_EVENT_H_
+#include "perfetto/base/thread_annotations.h"
+
#include <condition_variable>
#include <mutex>
@@ -40,8 +42,8 @@
private:
std::mutex mutex_;
- std::condition_variable event_;
- uint64_t notifications_ = 0;
+ std::condition_variable event_ PERFETTO_GUARDED_BY(mutex_);
+ uint64_t notifications_ PERFETTO_GUARDED_BY(mutex_) = 0;
};
} // namespace base
diff --git a/include/perfetto/ext/base/watchdog_posix.h b/include/perfetto/ext/base/watchdog_posix.h
index fef4074..3210c14 100644
--- a/include/perfetto/ext/base/watchdog_posix.h
+++ b/include/perfetto/ext/base/watchdog_posix.h
@@ -17,6 +17,7 @@
#ifndef INCLUDE_PERFETTO_EXT_BASE_WATCHDOG_POSIX_H_
#define INCLUDE_PERFETTO_EXT_BASE_WATCHDOG_POSIX_H_
+#include "perfetto/base/thread_annotations.h"
#include "perfetto/base/time.h"
#include "perfetto/ext/base/scoped_file.h"
@@ -154,12 +155,14 @@
// Check each type of resource every |polling_interval_ms_| miillis.
// Returns true if the threshold is exceeded and the process should be killed.
- bool CheckMemory_Locked(uint64_t rss_bytes);
- bool CheckCpu_Locked(uint64_t cpu_time);
+ bool CheckMemory_Locked(uint64_t rss_bytes)
+ PERFETTO_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
+ bool CheckCpu_Locked(uint64_t cpu_time)
+ PERFETTO_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
void AddFatalTimer(TimerData);
void RemoveFatalTimer(TimerData);
- void RearmTimerFd_Locked();
+ void RearmTimerFd_Locked() PERFETTO_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
void SerializeLogsAndKillThread(int tid, WatchdogCrashReason);
// Computes the time interval spanned by a given ring buffer with respect
@@ -171,24 +174,20 @@
std::thread thread_;
ScopedPlatformHandle timer_fd_;
- // --- Begin lock-protected members ---
-
std::mutex mutex_;
- uint64_t memory_limit_bytes_ = 0;
- WindowedInterval memory_window_bytes_;
+ uint64_t memory_limit_bytes_ PERFETTO_GUARDED_BY(mutex_) = 0;
+ WindowedInterval memory_window_bytes_ PERFETTO_GUARDED_BY(mutex_);
- uint32_t cpu_limit_percentage_ = 0;
- WindowedInterval cpu_window_time_ticks_;
+ uint32_t cpu_limit_percentage_ PERFETTO_GUARDED_BY(mutex_) = 0;
+ WindowedInterval cpu_window_time_ticks_ PERFETTO_GUARDED_BY(mutex_);
// Outstanding timers created via CreateFatalTimer() and not yet destroyed.
// The vector is not sorted. In most cases there are only 1-2 timers, we can
// afford O(N) operations.
// All the timers in the list share the same |timer_fd_|, which is keeped
// armed on the min(timers_) through RearmTimerFd_Locked().
- std::vector<TimerData> timers_;
-
- // --- End lock-protected members ---
+ std::vector<TimerData> timers_ PERFETTO_GUARDED_BY(mutex_);
protected:
// Protected for testing.
diff --git a/include/perfetto/ext/tracing/core/null_consumer_endpoint_for_testing.h b/include/perfetto/ext/tracing/core/null_consumer_endpoint_for_testing.h
index 2e85c92..795f8b8 100644
--- a/include/perfetto/ext/tracing/core/null_consumer_endpoint_for_testing.h
+++ b/include/perfetto/ext/tracing/core/null_consumer_endpoint_for_testing.h
@@ -34,7 +34,7 @@
void ChangeTraceConfig(const perfetto::TraceConfig&) override {}
void StartTracing() override {}
void DisableTracing() override {}
- void CloneSession(TracingSessionID, CloneSessionArgs) override {}
+ void CloneSession(CloneSessionArgs) override {}
void Flush(uint32_t, FlushCallback, FlushFlags) override {}
void ReadBuffers() override {}
void FreeBuffers() override {}
diff --git a/include/perfetto/ext/tracing/core/tracing_service.h b/include/perfetto/ext/tracing/core/tracing_service.h
index 2b33fee..e3beaff 100644
--- a/include/perfetto/ext/tracing/core/tracing_service.h
+++ b/include/perfetto/ext/tracing/core/tracing_service.h
@@ -24,13 +24,13 @@
#include <vector>
#include "perfetto/base/export.h"
+#include "perfetto/ext/base/clock_snapshots.h"
#include "perfetto/ext/base/scoped_file.h"
#include "perfetto/ext/base/sys_types.h"
#include "perfetto/ext/tracing/core/basic_types.h"
#include "perfetto/ext/tracing/core/shared_memory.h"
#include "perfetto/ext/tracing/core/trace_packet.h"
#include "perfetto/tracing/buffer_exhausted_policy.h"
-#include "perfetto/tracing/core/clock_snapshots.h"
#include "perfetto/tracing/core/flush_flags.h"
#include "perfetto/tracing/core/forward_decls.h"
@@ -193,9 +193,17 @@
// Clones an existing tracing session and attaches to it. The session is
// cloned in read-only mode and can only be used to read a snapshot of an
// existing tracing session. Will invoke Consumer::OnSessionCloned().
- // If TracingSessionID == kBugreportSessionId (0xff...ff) the session with the
- // highest bugreport score is cloned (if any exists).
struct CloneSessionArgs {
+ // Exactly one between tsid and unique_session_name should be set.
+
+ // The id of the tracing session that should be cloned. If
+ // kBugreportSessionId (0xff...ff) the session with the highest bugreport
+ // score is cloned (if any exists).
+ TracingSessionID tsid = 0;
+
+ // The unique_session_name of the session that should be cloned.
+ std::string unique_session_name;
+
// If set, the trace filter will not have effect on the cloned session.
// Used for bugreports.
bool skip_trace_filter = false;
@@ -204,7 +212,7 @@
// to kBugreport when requesting the flush to the producers.
bool for_bugreport = false;
};
- virtual void CloneSession(TracingSessionID, CloneSessionArgs) = 0;
+ virtual void CloneSession(CloneSessionArgs) = 0;
// Requests all data sources to flush their data immediately and invokes the
// passed callback once all of them have acked the flush (in which case
@@ -298,14 +306,14 @@
// A snapshot of client and host clocks.
struct SyncClockSnapshot {
- ClockSnapshotVector client_clock_snapshots;
- ClockSnapshotVector host_clock_snapshots;
+ base::ClockSnapshotVector client_clock_snapshots;
+ base::ClockSnapshotVector host_clock_snapshots;
};
enum class SyncMode : uint32_t { PING = 1, UPDATE = 2 };
virtual void SyncClocks(SyncMode sync_mode,
- ClockSnapshotVector client_clocks,
- ClockSnapshotVector host_clocks) = 0;
+ base::ClockSnapshotVector client_clocks,
+ base::ClockSnapshotVector host_clocks) = 0;
virtual void Disconnect() = 0;
};
diff --git a/include/perfetto/public/abi/track_event_hl_abi.h b/include/perfetto/public/abi/track_event_hl_abi.h
index 3d8720c..f214596 100644
--- a/include/perfetto/public/abi/track_event_hl_abi.h
+++ b/include/perfetto/public/abi/track_event_hl_abi.h
@@ -125,6 +125,7 @@
PERFETTO_TE_HL_EXTRA_TYPE_FLUSH = 15,
PERFETTO_TE_HL_EXTRA_TYPE_NO_INTERN = 16,
PERFETTO_TE_HL_EXTRA_TYPE_PROTO_FIELDS = 17,
+ PERFETTO_TE_HL_EXTRA_TYPE_PROTO_TRACK = 18,
};
// An extra event parameter. Each type of parameter should embed this as its
@@ -250,6 +251,14 @@
struct PerfettoTeHlProtoField* const* fields;
};
+// PERFETTO_TE_HL_EXTRA_TYPE_PROTO_TRACK
+struct PerfettoTeHlExtraProtoTrack {
+ struct PerfettoTeHlExtra header;
+ uint64_t uuid;
+ // Array of pointers to the fields. The last pointer should be NULL.
+ struct PerfettoTeHlProtoField* const* fields;
+};
+
// Emits an event on all active instances of the track event data source.
// * `cat`: The registered category of the event, it knows on which data source
// instances the event should be emitted. Use
diff --git a/include/perfetto/public/protos/trace/track_event/track_descriptor.pzc.h b/include/perfetto/public/protos/trace/track_event/track_descriptor.pzc.h
index 7f0a5ad..7c3b0f3 100644
--- a/include/perfetto/public/protos/trace/track_event/track_descriptor.pzc.h
+++ b/include/perfetto/public/protos/trace/track_event/track_descriptor.pzc.h
@@ -49,6 +49,11 @@
static_name,
10);
PERFETTO_PB_FIELD(perfetto_protos_TrackDescriptor,
+ STRING,
+ const char*,
+ atrace_name,
+ 13);
+PERFETTO_PB_FIELD(perfetto_protos_TrackDescriptor,
MSG,
perfetto_protos_ProcessDescriptor,
process,
diff --git a/include/perfetto/public/te_macros.h b/include/perfetto/public/te_macros.h
index 9ab741d..e8f4a03 100644
--- a/include/perfetto/public/te_macros.h
+++ b/include/perfetto/public/te_macros.h
@@ -320,6 +320,38 @@
PERFETTO_I_TE_COMPOUND_LITERAL_ARRAY(struct PerfettoTeHlProtoField*, \
{__VA_ARGS__, PERFETTO_NULL})})
+// Specifies (manually) the track for this event
+// * `UUID` can be computed with e.g.:
+// * PerfettoTeCounterTrackUuid()
+// * PerfettoTeNamedTrackUuid()
+// * `...` the rest of the params should be PERFETTO_TE_PROTO_FIELD_* macros
+// and should be fields of the perfetto.protos.TrackDescriptor protobuf
+// message.
+#define PERFETTO_TE_PROTO_TRACK(UUID, ...) \
+ PERFETTO_I_TE_EXTRA( \
+ PerfettoTeHlExtraProtoTrack, \
+ {{PERFETTO_TE_HL_EXTRA_TYPE_PROTO_TRACK}, \
+ UUID, \
+ PERFETTO_I_TE_COMPOUND_LITERAL_ARRAY(struct PerfettoTeHlProtoField*, \
+ {__VA_ARGS__, PERFETTO_NULL})})
+
+// Specifies that the current track for this event is a counter track named
+// `const char *NAME`, child of a track whose uuid is `PARENT_UUID`. `NAME`
+// and `PARENT_UUID` uniquely identify a track. Common values for `PARENT_UUID`
+// include PerfettoTeProcessTrackUuid(), PerfettoTeThreadTrackUuid() or
+// PerfettoTeGlobalTrackUuid().
+#define PERFETTO_TE_COUNTER_TRACK(NAME, PARENT_UUID) \
+ PERFETTO_TE_PROTO_TRACK( \
+ PerfettoTeCounterTrackUuid(NAME, PARENT_UUID), \
+ PERFETTO_TE_PROTO_FIELD_VARINT( \
+ perfetto_protos_TrackDescriptor_parent_uuid_field_number, \
+ PARENT_UUID), \
+ PERFETTO_TE_PROTO_FIELD_CSTR( \
+ perfetto_protos_TrackDescriptor_name_field_number, NAME), \
+ PERFETTO_TE_PROTO_FIELD_BYTES( \
+ perfetto_protos_TrackDescriptor_counter_field_number, PERFETTO_NULL, \
+ 0))
+
// ----------------------------------
// The main PERFETTO_TE tracing macro
// ----------------------------------
diff --git a/include/perfetto/trace_processor/basic_types.h b/include/perfetto/trace_processor/basic_types.h
index ace17de..716e73a 100644
--- a/include/perfetto/trace_processor/basic_types.h
+++ b/include/perfetto/trace_processor/basic_types.h
@@ -17,9 +17,9 @@
#ifndef INCLUDE_PERFETTO_TRACE_PROCESSOR_BASIC_TYPES_H_
#define INCLUDE_PERFETTO_TRACE_PROCESSOR_BASIC_TYPES_H_
-#include <assert.h>
-#include <math.h>
-#include <stdarg.h>
+#include <cassert>
+#include <cstdarg>
+#include <cstddef>
#include <cstdint>
#include <string>
@@ -30,16 +30,41 @@
#include "perfetto/base/export.h"
#include "perfetto/base/logging.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
// All metrics protos are in this directory. When loading metric extensions, the
// protos are mounted onto a virtual path inside this directory.
constexpr char kMetricProtoRoot[] = "protos/perfetto/metrics/";
+// Enum which encodes how trace processor should parse the ingested data.
+enum class ParsingMode {
+ // This option causes trace processor to tokenize the raw trace bytes, sort
+ // the events into timestamp order and parse the events into tables.
+ //
+ // This is the default mode.
+ kDefault = 0,
+
+ // This option causes trace processor to skip the sorting and parsing
+ // steps of ingesting a trace, only retaining any information which could be
+ // gathered during tokenization of the trace files.
+ //
+ // Note the exact information available with this option is left intentionally
+ // undefined as it relies heavily on implementation details of trace
+ // processor. It is mainly intended for use by the Perfetto UI which
+ // integrates very closely with trace processor. General users should use
+ // `kDefault` unless they know what they are doing.
+ kTokenizeOnly = 1,
+
+ // This option causes trace processor to skip the parsing step of ingesting
+ // a trace.
+ //
+ // Note this option does not offer any visible benefits over `kTokenizeOnly`
+ // but has the downside of being slower. It mainly exists for use by
+ // developers debugging performance of trace processor.
+ kTokenizeAndSort = 2,
+};
+
// Enum which encodes how trace processor should try to sort the ingested data.
-// Note that these options are only applicable to proto traces; other trace
-// types (e.g. JSON, Fuchsia) use full sorts.
enum class SortingMode {
// This option allows trace processor to use built-in heuristics about how to
// sort the data. Generally, this option is correct for most embedders as
@@ -51,26 +76,11 @@
// This is the default mode.
kDefaultHeuristics = 0,
- // This option forces trace processor to wait for all trace packets to be
- // passed to it before doing a full sort of all the packets. This causes any
+ // This option forces trace processor to wait for all events to be passed to
+ // it before doing a full sort of all the events. This causes any
// heuristics trace processor would normally use to ingest partially sorted
// data to be skipped.
kForceFullSort = 1,
-
- // This option is deprecated in v18; trace processor will ignore it and
- // use |kDefaultHeuristics|.
- //
- // Rationale for deprecation:
- // The new windowed sorting logic in trace processor uses a combination of
- // flush and buffer-read lifecycle events inside the trace instead of
- // using time-periods from the config.
- //
- // Recommended migration:
- // Users of this option should switch to using |kDefaultHeuristics| which
- // will act very similarly to the pre-v20 behaviour of this option.
- //
- // This option is scheduled to be removed in v21.
- kForceFlushPeriodWindowedSort = 2
};
// Enum which encodes which event (if any) should be used to drop ftrace data
@@ -128,8 +138,13 @@
// Struct for configuring a TraceProcessor instance (see trace_processor.h).
struct PERFETTO_EXPORT_COMPONENT Config {
- // Indicates the sortinng mode that trace processor should use on the passed
- // trace packets. See the enum documentation for more details.
+ // Indicates the parsing mode trace processor should use to extract
+ // information from the raw trace bytes. See the enum documentation for more
+ // details.
+ ParsingMode parsing_mode = ParsingMode::kDefault;
+
+ // Indicates the sortinng mode that trace processor should use on the
+ // passed trace packets. See the enum documentation for more details.
SortingMode sorting_mode = SortingMode::kDefaultHeuristics;
// When set to false, this option makes the trace processor not include ftrace
@@ -262,28 +277,38 @@
Type type = kNull;
};
-// Data used to register a new SQL module.
-struct SqlModule {
- // Must be unique among modules, or can be used to override existing module if
- // |allow_module_override| is set.
+// Data used to register a new SQL package.
+struct SqlPackage {
+ // Must be unique among package, or can be used to override existing package
+ // if |allow_override| is set.
std::string name;
- // Pairs of strings used for |IMPORT| with the contents of SQL files being
- // run. Strings should only contain alphanumeric characters and '.', where
- // string before the first dot has to be module name.
+ // Pairs of strings mapping from the name of the module used by `INCLUDE
+ // PERFETTO MODULE` statements to the contents of SQL files being executed.
+ // Module names should only contain alphanumeric characters and '.', where
+ // string before the first dot must be the package name.
//
- // It is encouraged that import key should be the path to the SQL file being
+ // It is encouraged that include key should be the path to the SQL file being
// run, with slashes replaced by dots and without the SQL extension. For
- // example, 'android/camera/jank.sql' would be imported by
- // 'android.camera.jank'.
- std::vector<std::pair<std::string, std::string>> files;
+ // example, 'android/camera/jank.sql' would be included by
+ // 'android.camera.jank'. This conforms to user expectations of how modules
+ // behave in other languages (e.g. Java, Python etc).
+ std::vector<std::pair<std::string, std::string>> modules;
- // If true, SqlModule will override registered module with the same name. Can
- // only be set if enable_dev_features is true, otherwise will throw an error.
+ // If true, will allow overriding a package which already exists with `name.
+ // Can only be set if enable_dev_features (in the TraceProcessorConfig object
+ // when creating TraceProcessor) is true. Otherwise, this option will throw an
+ // error.
+ bool allow_override = false;
+};
+
+// Deprecated. Please use `RegisterSqlPackage` and `SqlPackage` instead.
+struct SqlModule {
+ std::string name;
+ std::vector<std::pair<std::string, std::string>> files;
bool allow_module_override = false;
};
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
#endif // INCLUDE_PERFETTO_TRACE_PROCESSOR_BASIC_TYPES_H_
diff --git a/include/perfetto/trace_processor/trace_processor.h b/include/perfetto/trace_processor/trace_processor.h
index e46d1a7..7f70653 100644
--- a/include/perfetto/trace_processor/trace_processor.h
+++ b/include/perfetto/trace_processor/trace_processor.h
@@ -55,15 +55,15 @@
// the returned iterator.
virtual Iterator ExecuteQuery(const std::string& sql) = 0;
- // Registers SQL files with the associated path under the module named
- // |sql_module.name|. These modules can be run by using the |IMPORT| SQL
- // function.
+ // Registers SQL files with the associated path under the package named
+ // |sql_package.name|.
//
- // For example, if you registered a module called "camera" with a file path
- // "camera/cpu/metrics.sql" you can import it (run the file) using "SELECT
- // IMPORT('camera.cpu.metrics');". The first word of the string has to be a
- // module name and there can be only one module registered with a given name.
- virtual base::Status RegisterSqlModule(SqlModule sql_module) = 0;
+ // For example, if you registered a package called "camera" with a file path
+ // "camera/cpu/metrics.sql" you can include it (run the file) using "INCLUDE
+ // PERFETTO MODULE camera.cpu.metrics". The first word of the string has to be
+ // a package name and there can be only one package registered with a given
+ // name.
+ virtual base::Status RegisterSqlPackage(SqlPackage) = 0;
// Registers a metric at the given path which will run the specified SQL.
virtual base::Status RegisterMetric(const std::string& path,
@@ -138,6 +138,11 @@
// loaded by trace processor shell at runtime. The message is encoded as
// DescriptorSet, defined in perfetto/trace_processor/trace_processor.proto.
virtual std::vector<uint8_t> GetMetricDescriptors() = 0;
+
+ // Deprecated. Use |RegisterSqlPackage()| instead, which is identical in
+ // functionality to |RegisterSqlModule()| and the only difference is in
+ // the argument, which is directly translatable to |SqlPackage|.
+ virtual base::Status RegisterSqlModule(SqlModule) = 0;
};
} // namespace trace_processor
diff --git a/include/perfetto/tracing/core/BUILD.gn b/include/perfetto/tracing/core/BUILD.gn
index 63c77ca..9ea0e2c 100644
--- a/include/perfetto/tracing/core/BUILD.gn
+++ b/include/perfetto/tracing/core/BUILD.gn
@@ -20,7 +20,6 @@
]
sources = [
"chrome_config.h",
- "clock_snapshots.h",
"data_source_config.h",
"data_source_descriptor.h",
"flush_flags.h",
diff --git a/include/perfetto/tracing/internal/track_event_data_source.h b/include/perfetto/tracing/internal/track_event_data_source.h
index 70b0dee..204f798 100644
--- a/include/perfetto/tracing/internal/track_event_data_source.h
+++ b/include/perfetto/tracing/internal/track_event_data_source.h
@@ -17,8 +17,8 @@
#ifndef INCLUDE_PERFETTO_TRACING_INTERNAL_TRACK_EVENT_DATA_SOURCE_H_
#define INCLUDE_PERFETTO_TRACING_INTERNAL_TRACK_EVENT_DATA_SOURCE_H_
-#include "perfetto/base/compiler.h"
#include "perfetto/base/template_util.h"
+#include "perfetto/base/thread_annotations.h"
#include "perfetto/protozero/message_handle.h"
#include "perfetto/tracing/core/data_source_config.h"
#include "perfetto/tracing/data_source.h"
diff --git a/include/perfetto/tracing/internal/track_event_macros.h b/include/perfetto/tracing/internal/track_event_macros.h
index 68da366..01db0b1 100644
--- a/include/perfetto/tracing/internal/track_event_macros.h
+++ b/include/perfetto/tracing/internal/track_event_macros.h
@@ -21,7 +21,7 @@
// implementation. Perfetto API users typically don't need to use anything here
// directly.
-#include "perfetto/base/compiler.h"
+#include "perfetto/base/thread_annotations.h"
#include "perfetto/tracing/internal/track_event_data_source.h"
#include "perfetto/tracing/string_helpers.h"
#include "perfetto/tracing/track_event_category_registry.h"
@@ -144,12 +144,6 @@
} \
} while (false)
-// This internal macro is unused from the repo now, but some improper usage
-// remain outside of the repo.
-// TODO(b/294800182): Remove this.
-#define PERFETTO_INTERNAL_TRACK_EVENT(...) \
- PERFETTO_INTERNAL_TRACK_EVENT_WITH_METHOD(TraceForCategory, ##__VA_ARGS__)
-
// C++17 doesn't like a move constructor being defined for the EventFinalizer
// class but C++11 and MSVC doesn't compile without it being defined so support
// both.
diff --git a/include/perfetto/tracing/tracing.h b/include/perfetto/tracing/tracing.h
index 6f28e8d..03b9fec 100644
--- a/include/perfetto/tracing/tracing.h
+++ b/include/perfetto/tracing/tracing.h
@@ -344,6 +344,30 @@
// started.
virtual void StartBlocking() = 0;
+ // Struct passed as argument to the callback passed to CloneTrace().
+ struct CloneTraceCallbackArgs {
+ bool success;
+ std::string error;
+ // UUID of the cloned session.
+ int64_t uuid_msb;
+ int64_t uuid_lsb;
+ };
+
+ // Struct passed as argument to CloneTrace().
+ struct CloneTraceArgs {
+ // The unique_session_name of the session that should be cloned.
+ std::string unique_session_name;
+ };
+
+ // Clones an existing initialized tracing session from the same `BackendType`
+ // as this tracing session, and attaches to it. The session is cloned in
+ // read-only mode and can only be used to read a snapshot of an existing
+ // tracing session. For each session, only one CloneTrace call can be pending
+ // at the same time; subsequent calls after the callback is executed are
+ // supported.
+ using CloneTraceCallback = std::function<void(CloneTraceCallbackArgs)>;
+ virtual void CloneTrace(CloneTraceArgs args, CloneTraceCallback);
+
// This callback will be invoked when all data sources have acknowledged that
// tracing has started. This callback will be invoked on an internal perfetto
// thread.
diff --git a/include/perfetto/tracing/track.h b/include/perfetto/tracing/track.h
index 831c290..ffd92c1 100644
--- a/include/perfetto/tracing/track.h
+++ b/include/perfetto/tracing/track.h
@@ -26,11 +26,15 @@
#include "perfetto/tracing/internal/tracing_muxer.h"
#include "perfetto/tracing/platform.h"
#include "perfetto/tracing/string_helpers.h"
-#include "protos/perfetto/trace/trace_packet.pbzero.h"
-#include "protos/perfetto/trace/track_event/counter_descriptor.gen.h"
-#include "protos/perfetto/trace/track_event/counter_descriptor.pbzero.h"
-#include "protos/perfetto/trace/track_event/track_descriptor.gen.h"
-#include "protos/perfetto/trace/track_event/track_descriptor.pbzero.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h" // IWYU pragma: export
+#include "protos/perfetto/trace/track_event/counter_descriptor.gen.h" // IWYU pragma: export
+#include "protos/perfetto/trace/track_event/counter_descriptor.pbzero.h" // IWYU pragma: export
+#include "protos/perfetto/trace/track_event/process_descriptor.gen.h" // IWYU pragma: export
+#include "protos/perfetto/trace/track_event/process_descriptor.pbzero.h" // IWYU pragma: export
+#include "protos/perfetto/trace/track_event/thread_descriptor.gen.h" // IWYU pragma: export
+#include "protos/perfetto/trace/track_event/thread_descriptor.pbzero.h" // IWYU pragma: export
+#include "protos/perfetto/trace/track_event/track_descriptor.gen.h" // IWYU pragma: export
+#include "protos/perfetto/trace/track_event/track_descriptor.pbzero.h" // IWYU pragma: export
#include <stdint.h>
#include <map>
@@ -188,6 +192,52 @@
disallow_merging_with_system_tracks_) {}
};
+// A track that's identified by an explcit name, id and its parent.
+class PERFETTO_EXPORT_COMPONENT NamedTrack : public Track {
+ // A random value mixed into named track uuids to avoid collisions with
+ // other types of tracks.
+ static constexpr uint64_t kNamedTrackMagic = 0xCD571EC5EAD37024ul;
+
+ public:
+ // `name` is hashed to get a uuid identifying the track. Optionally specify
+ // `id` to differentiate between multiple tracks with the same `name` and
+ // `parent`.
+ NamedTrack(DynamicString name,
+ uint64_t id = 0,
+ Track parent = MakeProcessTrack())
+ : Track(id ^ internal::Fnv1a(name.value, name.length) ^ kNamedTrackMagic,
+ parent),
+ static_name_(nullptr),
+ dynamic_name_(name) {}
+
+ constexpr NamedTrack(StaticString name,
+ uint64_t id = 0,
+ Track parent = MakeProcessTrack())
+ : Track(id ^ internal::Fnv1a(name.value) ^ kNamedTrackMagic, parent),
+ static_name_(name) {}
+
+ // Construct a track using `name` and `id` as identifier within thread-scope.
+ // Shorthand for `Track::NamedTrack("name", id, ThreadTrack::Current())`
+ // Usage: TRACE_EVENT_BEGIN("...", "...",
+ // perfetto::NamedTrack::ThreadScoped("rendering"))
+ template <class TrackEventName>
+ static Track ThreadScoped(TrackEventName name,
+ uint64_t id = 0,
+ Track parent = Track()) {
+ if (parent.uuid == 0)
+ return NamedTrack(std::forward<TrackEventName>(name), id,
+ ThreadTrack::Current());
+ return NamedTrack(std::forward<TrackEventName>(name), id, parent);
+ }
+
+ void Serialize(protos::pbzero::TrackDescriptor*) const;
+ protos::gen::TrackDescriptor Serialize() const;
+
+ private:
+ StaticString static_name_;
+ DynamicString dynamic_name_;
+};
+
// A track for recording counter values with the TRACE_COUNTER macro. Counter
// tracks can optionally be given units and other metadata. See
// /protos/perfetto/trace/track_event/counter_descriptor.proto for details.
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/infra/perfetto.dev/src/gen_stdlib_docs_md.py b/infra/perfetto.dev/src/gen_stdlib_docs_md.py
index e1e60bd..13f51db 100644
--- a/infra/perfetto.dev/src/gen_stdlib_docs_md.py
+++ b/infra/perfetto.dev/src/gen_stdlib_docs_md.py
@@ -52,7 +52,7 @@
FROM android_startups;
```
-Prelude is a special module is automatically imported. It contains key helper
+Prelude is a special module is automatically included. It contains key helper
tables, views and functions which are universally useful.
More information on importing modules is available in the
@@ -60,211 +60,189 @@
for the `INCLUDE PERFETTO MODULE` statement.
<!-- TODO(b/290185551): talk about experimental module and contributions. -->
-
-## Summary
'''
-def _escape_in_table(desc: str):
+def _escape(desc: str) -> str:
"""Escapes special characters in a markdown table."""
return desc.replace('|', '\\|')
-def _md_table(cols: List[str]):
+def _md_table_header(cols: List[str]) -> str:
col_str = ' | '.join(cols) + '\n'
lines = ['-' * len(col) for col in cols]
underlines = ' | '.join(lines)
return col_str + underlines
-def _write_summary(sql_type: str, table_cols: List[str],
- summary_objs: List[str]) -> str:
- table_data = '\n'.join(s.strip() for s in summary_objs if s)
- return f"""
-### {sql_type}
+def _md_rolldown(summary: str, content: str) -> str:
+ return f"""<details>
+ <summary style="cursor: pointer;">{summary}</summary>
-{_md_table(table_cols)}
-{table_data}
+ {content}
-"""
+ </details>
+ """
-class FileMd:
- """Responsible for file level markdown generation."""
-
- def __init__(self, module_name, file_dict):
- self.import_key = file_dict['import_key']
- import_key_name = self.import_key if module_name != 'prelude' else 'N/A'
- self.objs, self.funs, self.view_funs, self.macros = [], [], [], []
- summary_objs_list, summary_funs_list, summary_view_funs_list, summary_macros_list = [], [], [], []
-
- # Add imports if in file.
- for data in file_dict['imports']:
- # Anchor
- anchor = f'''obj/{module_name}/{data['name']}'''
-
- # Add summary of imported view/table
- summary_objs_list.append(f'''[{data['name']}](#{anchor})|'''
- f'''{import_key_name}|'''
- f'''{_escape_in_table(data['summary_desc'])}''')
-
- self.objs.append(f'''\n\n<a name="{anchor}"></a>'''
- f'''**{data['name']}**, {data['type']}\n\n'''
- f'''{_escape_in_table(data['desc'])}\n''')
-
- self.objs.append(_md_table(['Column', 'Type', 'Description']))
- for name, info in data['cols'].items():
- self.objs.append(
- f'{name} | {info["type"]} | {_escape_in_table(info["desc"])}')
-
- self.objs.append('\n\n')
-
- # Add functions if in file
- for data in file_dict['functions']:
- # Anchor
- anchor = f'''fun/{module_name}/{data['name']}'''
-
- # Add summary of imported function
- summary_funs_list.append(f'''[{data['name']}](#{anchor})|'''
- f'''{import_key_name}|'''
- f'''{data['return_type']}|'''
- f'''{_escape_in_table(data['summary_desc'])}''')
- self.funs.append(
- f'''\n\n<a name="{anchor}"></a>'''
- f'''**{data['name']}**\n\n'''
- f'''{data['desc']}\n\n'''
- f'''Returns: {data['return_type']}, {data['return_desc']}\n\n''')
- if data['args']:
- self.funs.append(_md_table(['Argument', 'Type', 'Description']))
- for name, arg_dict in data['args'].items():
- self.funs.append(
- f'''{name} | {arg_dict['type']} | {_escape_in_table(arg_dict['desc'])}'''
- )
-
- self.funs.append('\n\n')
-
- # Add table functions if in file
- for data in file_dict['table_functions']:
- # Anchor
- anchor = rf'''view_fun/{module_name}/{data['name']}'''
- # Add summary of imported view function
- summary_view_funs_list.append(
- f'''[{data['name']}](#{anchor})|'''
- f'''{import_key_name}|'''
- f'''{_escape_in_table(data['summary_desc'])}''')
-
- self.view_funs.append(f'''\n\n<a name="{anchor}"></a>'''
- f'''**{data['name']}**\n'''
- f'''{data['desc']}\n\n''')
- if data['args']:
- self.funs.append(_md_table(['Argument', 'Type', 'Description']))
- for name, arg_dict in data['args'].items():
- self.view_funs.append(
- f'''{name} | {arg_dict['type']} | {_escape_in_table(arg_dict['desc'])}'''
- )
- self.view_funs.append('\n')
- self.view_funs.append(_md_table(['Column', 'Type', 'Description']))
- for name, column in data['cols'].items():
- self.view_funs.append(f'{name} | {column["type"]} | {column["desc"]}')
-
- self.view_funs.append('\n\n')
-
- # Add macros if in file
- for data in file_dict['macros']:
- # Anchor
- anchor = rf'''macro/{module_name}/{data['name']}'''
- # Add summary of imported view function
- summary_macros_list.append(
- f'''[{data['name']}](#{anchor})|'''
- f'''{import_key_name}|'''
- f'''{_escape_in_table(data['summary_desc'])}''')
-
- self.macros.append(
- f'''\n\n<a name="{anchor}"></a>'''
- f'''**{data['name']}**\n'''
- f'''{data['desc']}\n\n'''
- f'''Returns: {data['return_type']}, {data['return_desc']}\n\n''')
- if data['args']:
- self.macros.append(_md_table(['Argument', 'Type', 'Description']))
- for name, arg_dict in data['args'].items():
- self.macros.append(
- f'''{name} | {arg_dict['type']} | {_escape_in_table(arg_dict['desc'])}'''
- )
- self.macros.append('\n')
- self.macros.append('\n\n')
-
- self.summary_objs = '\n'.join(summary_objs_list)
- self.summary_funs = '\n'.join(summary_funs_list)
- self.summary_view_funs = '\n'.join(summary_view_funs_list)
- self.summary_macros = '\n'.join(summary_macros_list)
+def _bold(s: str) -> str:
+ return f"<strong>{s}</strong>"
class ModuleMd:
"""Responsible for module level markdown generation."""
- def __init__(self, module_name: str, module_files: List[Dict[str,
- Any]]) -> None:
- self.module_name = module_name
- self.files_md = sorted(
- [FileMd(module_name, file_dict) for file_dict in module_files],
- key=lambda x: x.import_key)
- self.summary_objs = '\n'.join(
- file.summary_objs for file in self.files_md if file.summary_objs)
- self.summary_funs = '\n'.join(
- file.summary_funs for file in self.files_md if file.summary_funs)
- self.summary_view_funs = '\n'.join(file.summary_view_funs
- for file in self.files_md
- if file.summary_view_funs)
- self.summary_macros = '\n'.join(
- file.summary_macros for file in self.files_md if file.summary_macros)
+ def __init__(self, package_name: str, module_dict: Dict):
+ self.module_name = module_dict['module_name']
+ self.include_str = self.module_name if package_name != 'prelude' else 'N/A'
+ self.objs, self.funs, self.view_funs, self.macros = [], [], [], []
+
+ # Views/tables
+ for data in module_dict['data_objects']:
+ if not data['cols']:
+ continue
+
+ obj_summary = (
+ f'''{_bold(data['name'])}. {data['summary_desc']}\n'''
+ )
+ content = [f"{data['type']}"]
+ if (data['summary_desc'] != data['desc']):
+ content.append(data['desc'])
+
+ table = [_md_table_header(['Column', 'Type', 'Description'])]
+ for info in data['cols']:
+ name = info["name"]
+ table.append(
+ f'{name} | {info["type"]} | {_escape(info["desc"])}')
+ content.append('\n\n')
+ content.append('\n'.join(table))
+
+ self.objs.append(_md_rolldown(obj_summary, '\n'.join(content)))
+
+ self.objs.append('\n\n')
+
+ # Functions
+ for d in module_dict['functions']:
+ summary = f'''{_bold(d['name'])} -> {d['return_type']}. {d['summary_desc']}\n\n'''
+ content = []
+ if (d['summary_desc'] != d['desc']):
+ content.append(d['desc'])
+
+ content.append(
+ f"Returns {d['return_type']}: {d['return_desc']}\n\n")
+ if d['args']:
+ content.append(_md_table_header(['Argument', 'Type', 'Description']))
+ for arg_dict in d['args']:
+ content.append(
+ f'''{arg_dict['name']} | {arg_dict['type']} | {_escape(arg_dict['desc'])}'''
+ )
+
+ self.funs.append(_md_rolldown(summary, '\n'.join(content)))
+ self.funs.append('\n\n')
+
+ # Table functions
+ for data in module_dict['table_functions']:
+ obj_summary = f'''{_bold(data['name'])}. {data['summary_desc']}\n\n'''
+ content = []
+ if (data['summary_desc'] != data['desc']):
+ content.append(data['desc'])
+
+ if data['args']:
+ args_table = [_md_table_header(['Argument', 'Type', 'Description'])]
+ for arg_dict in data['args']:
+ args_table.append(
+ f'''{arg_dict['name']} | {arg_dict['type']} | {_escape(arg_dict['desc'])}'''
+ )
+ content.append('\n'.join(args_table))
+ content.append('\n\n')
+
+ content.append(_md_table_header(['Column', 'Type', 'Description']))
+ for column in data['cols']:
+ content.append(
+ f'{column["name"]} | {column["type"]} | {column["desc"]}')
+
+ self.view_funs.append(_md_rolldown(obj_summary, '\n'.join(content)))
+ self.view_funs.append('\n\n')
+
+ # Macros
+ for data in module_dict['macros']:
+ obj_summary = f'''{_bold(data['name'])}. {data['summary_desc']}\n\n'''
+ content = []
+ if (data['summary_desc'] != data['desc']):
+ content.append(data['desc'])
+
+ content.append(
+ f'''Returns: {data['return_type']}, {data['return_desc']}\n\n''')
+ if data['args']:
+ table = [_md_table_header(['Argument', 'Type', 'Description'])]
+ for arg_dict in data['args']:
+ table.append(
+ f'''{arg_dict['name']} | {arg_dict['type']} | {_escape(arg_dict['desc'])}'''
+ )
+ content.append('\n'.join(table))
+
+ self.macros.append(_md_rolldown(obj_summary, '\n'.join(content)))
+ self.macros.append('\n\n')
+
+
+class PackageMd:
+ """Responsible for package level markdown generation."""
+
+ def __init__(self, package_name: str, module_files: List[Dict[str,
+ Any]]) -> None:
+ self.package_name = package_name
+ self.modules_md = sorted(
+ [ModuleMd(package_name, file_dict) for file_dict in module_files],
+ key=lambda x: x.module_name)
def get_prelude_description(self) -> str:
- if not self.module_name == 'prelude':
+ if not self.package_name == 'prelude':
raise ValueError("Only callable on prelude module")
lines = []
- lines.append(f'## Module: {self.module_name}')
+ lines.append(f'## Package: {self.package_name}')
# Prelude is a special module which is automatically imported and doesn't
# have any include keys.
- objs = '\n'.join(obj for file in self.files_md for obj in file.objs)
+ objs = '\n'.join(obj for module in self.modules_md for obj in module.objs)
if objs:
lines.append('#### Views/Tables')
lines.append(objs)
- funs = '\n'.join(fun for file in self.files_md for fun in file.funs)
+ funs = '\n'.join(fun for module in self.modules_md for fun in module.funs)
if funs:
lines.append('#### Functions')
lines.append(funs)
table_funs = '\n'.join(
- view_fun for file in self.files_md for view_fun in file.view_funs)
+ view_fun for module in self.modules_md for view_fun in module.view_funs)
if table_funs:
lines.append('#### Table Functions')
lines.append(table_funs)
- macros = '\n'.join(macro for file in self.files_md for macro in file.macros)
+ macros = '\n'.join(
+ macro for module in self.modules_md for macro in module.macros)
if macros:
lines.append('#### Macros')
lines.append(macros)
return '\n'.join(lines)
- def get_description(self) -> str:
- if not self.files_md:
+ def get_md(self) -> str:
+ if not self.modules_md:
return ''
- if self.module_name == 'prelude':
+ if self.package_name == 'prelude':
raise ValueError("Can't be called with prelude module")
lines = []
- lines.append(f'## Module: {self.module_name}')
+ lines.append(f'## Package: {self.package_name}')
- for file in self.files_md:
+ for file in self.modules_md:
if not any((file.objs, file.funs, file.view_funs, file.macros)):
continue
- lines.append(f'### {file.import_key}')
+ lines.append(f'### {file.module_name}')
if file.objs:
lines.append('#### Views/Tables')
lines.append('\n'.join(file.objs))
@@ -280,6 +258,12 @@
return '\n'.join(lines)
+ def is_empty(self) -> bool:
+ for file in self.modules_md:
+ if any((file.objs, file.funs, file.view_funs, file.macros)):
+ return False
+ return True
+
def main():
parser = argparse.ArgumentParser()
@@ -288,68 +272,26 @@
args = parser.parse_args()
with open(args.input) as f:
- modules_json_dict = json.load(f)
+ stdlib_json = json.load(f)
# Fetch the modules from json documentation.
- modules_dict: Dict[str, ModuleMd] = {}
- for module_name, module_files in modules_json_dict.items():
+ packages: Dict[str, PackageMd] = {}
+ for package in stdlib_json:
+ package_name = package["name"]
+ modules = package["modules"]
# Remove 'common' when it has been removed from the code.
- if module_name not in ['deprecated', 'common']:
- modules_dict[module_name] = ModuleMd(module_name, module_files)
+ if package_name not in ['deprecated', 'common']:
+ package = PackageMd(package_name, modules)
+ if (not package.is_empty()):
+ packages[package_name] = package
- prelude_module = modules_dict.pop('prelude')
+ prelude = packages.pop('prelude')
with open(args.output, 'w') as f:
f.write(INTRODUCTION)
-
- summary_objs = [prelude_module.summary_objs
- ] if prelude_module.summary_objs else []
- summary_objs += [
- module.summary_objs
- for module in modules_dict.values()
- if (module.summary_objs)
- ]
-
- summary_funs = [prelude_module.summary_funs
- ] if prelude_module.summary_funs else []
- summary_funs += [module.summary_funs for module in modules_dict.values()]
- summary_view_funs = [prelude_module.summary_view_funs
- ] if prelude_module.summary_view_funs else []
- summary_view_funs += [
- module.summary_view_funs for module in modules_dict.values()
- ]
- summary_macros = [prelude_module.summary_macros
- ] if prelude_module.summary_macros else []
- summary_macros += [
- module.summary_macros for module in modules_dict.values()
- ]
-
- if summary_objs:
- f.write(
- _write_summary('Views/tables', ['Name', 'Import', 'Description'],
- summary_objs))
-
- if summary_funs:
- f.write(
- _write_summary('Functions',
- ['Name', 'Import', 'Return type', 'Description'],
- summary_funs))
-
- if summary_view_funs:
- f.write(
- _write_summary('Table functions', ['Name', 'Import', 'Description'],
- summary_view_funs))
-
- if summary_macros:
- f.write(
- _write_summary('Macros', ['Name', 'Import', 'Description'],
- summary_macros))
-
- f.write('\n\n')
- f.write(prelude_module.get_prelude_description())
+ f.write(prelude.get_prelude_description())
f.write('\n')
- f.write('\n'.join(
- module.get_description() for module in modules_dict.values()))
+ f.write('\n'.join(module.get_md() for module in packages.values()))
return 0
diff --git a/protos/perfetto/bigtrace/BUILD.gn b/protos/perfetto/bigtrace/BUILD.gn
index 1ba673b..c1b9fba 100644
--- a/protos/perfetto/bigtrace/BUILD.gn
+++ b/protos/perfetto/bigtrace/BUILD.gn
@@ -18,7 +18,6 @@
proto_generators = [
"lite",
"zero",
- "source_set",
]
deps = [ "../trace_processor:@TYPE@" ]
sources = [ "worker.proto" ]
@@ -28,7 +27,6 @@
proto_generators = [
"lite",
"zero",
- "source_set",
]
deps = [ "../trace_processor:@TYPE@" ]
sources = [ "orchestrator.proto" ]
diff --git a/protos/perfetto/common/BUILD.gn b/protos/perfetto/common/BUILD.gn
index 1a250b2..e92b631 100644
--- a/protos/perfetto/common/BUILD.gn
+++ b/protos/perfetto/common/BUILD.gn
@@ -41,6 +41,5 @@
"lite",
"zero",
"cpp",
- "source_set",
]
}
diff --git a/protos/perfetto/config/BUILD.gn b/protos/perfetto/config/BUILD.gn
index 22d052a..2013e3b 100644
--- a/protos/perfetto/config/BUILD.gn
+++ b/protos/perfetto/config/BUILD.gn
@@ -38,6 +38,7 @@
sources = [
"chrome/chrome_config.proto",
"chrome/scenario_config.proto",
+ "chrome/system_metrics.proto",
"chrome/v8_config.proto",
"data_source_config.proto",
"etw/etw_config.proto",
@@ -46,11 +47,7 @@
"test_config.proto",
"trace_config.proto",
]
-}
-perfetto_proto_library("descriptor") {
- proto_generators = [ "descriptor" ]
generate_descriptor = "config.descriptor"
- deps = [ ":source_set" ]
- sources = [ "trace_config.proto" ]
+ descriptor_root_source = "trace_config.proto"
}
diff --git a/protos/perfetto/config/chrome/BUILD.gn b/protos/perfetto/config/chrome/BUILD.gn
index 4397cfc..f367848 100644
--- a/protos/perfetto/config/chrome/BUILD.gn
+++ b/protos/perfetto/config/chrome/BUILD.gn
@@ -15,8 +15,10 @@
import("../../../../gn/perfetto.gni")
import("../../../../gn/proto_library.gni")
-perfetto_proto_library("scenario_descriptor") {
- proto_generators = [ "descriptor" ]
- generate_descriptor = "scenario_config.descriptor"
+perfetto_proto_library("scenario_@TYPE@") {
+ proto_generators = []
+ deps = [ "..:@TYPE@" ]
sources = [ "scenario_config.proto" ]
+ generate_descriptor = "scenario_config.descriptor"
+ descriptor_root_source = "scenario_config.proto"
}
diff --git a/protos/perfetto/metrics/android/android_trusty_workqueues.proto b/protos/perfetto/config/chrome/system_metrics.proto
similarity index 77%
rename from protos/perfetto/metrics/android/android_trusty_workqueues.proto
rename to protos/perfetto/config/chrome/system_metrics.proto
index b7a1d5a..daca421 100644
--- a/protos/perfetto/metrics/android/android_trusty_workqueues.proto
+++ b/protos/perfetto/config/chrome/system_metrics.proto
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
@@ -18,5 +18,7 @@
package perfetto.protos;
-// Metric used to generate a simplified view of the Trusty kworker events.
-message AndroidTrustyWorkqueues {}
+message ChromiumSystemMetricsConfig {
+ // Samples counters every X ms.
+ optional uint32 sampling_interval_ms = 1;
+}
diff --git a/protos/perfetto/config/data_source_config.proto b/protos/perfetto/config/data_source_config.proto
index 5249100..75947d7 100644
--- a/protos/perfetto/config/data_source_config.proto
+++ b/protos/perfetto/config/data_source_config.proto
@@ -34,6 +34,7 @@
import "protos/perfetto/config/chrome/chrome_config.proto";
import "protos/perfetto/config/chrome/v8_config.proto";
import "protos/perfetto/config/etw/etw_config.proto";
+import "protos/perfetto/config/chrome/system_metrics.proto";
import "protos/perfetto/config/ftrace/ftrace_config.proto";
import "protos/perfetto/config/gpu/gpu_counter_config.proto";
import "protos/perfetto/config/gpu/vulkan_memory_config.proto";
@@ -51,7 +52,7 @@
import "protos/perfetto/config/system_info/system_info.proto";
// The configuration that is passed to each data source when starting tracing.
-// Next id: 131
+// Next id: 132
message DataSourceConfig {
enum SessionInitiator {
SESSION_INITIATOR_UNSPECIFIED = 0;
@@ -206,6 +207,9 @@
// Data source name: android.windowmanager
optional WindowManagerConfig windowmanager_config = 130 [lazy = true];
+ // Data source name: org.chromium.system_metrics
+ optional ChromiumSystemMetricsConfig chromium_system_metrics = 131 [lazy = true];
+
// This is a fallback mechanism to send a free-form text config to the
// producer. In theory this should never be needed. All the code that
// is part of the platform (i.e. traced service) is supposed to *not* truncate
diff --git a/protos/perfetto/config/ftrace/ftrace_config.proto b/protos/perfetto/config/ftrace/ftrace_config.proto
index 0d07f06..b720b21 100644
--- a/protos/perfetto/config/ftrace/ftrace_config.proto
+++ b/protos/perfetto/config/ftrace/ftrace_config.proto
@@ -18,10 +18,26 @@
package perfetto.protos;
-// Next id: 30
+// Next id: 31
message FtraceConfig {
// Ftrace events to record, example: "sched/sched_switch".
repeated string ftrace_events = 1;
+
+ message KprobeEvent {
+ enum KprobeType {
+ KPROBE_TYPE_UNKNOWN = 0;
+ KPROBE_TYPE_KPROBE = 1;
+ KPROBE_TYPE_KRETPROBE = 2;
+ KPROBE_TYPE_BOTH = 3;
+ }
+ // Kernel function name to attach to, for example "fuse_file_write_iter"
+ optional string probe = 1;
+ optional KprobeType type = 2;
+ }
+
+ // Ftrace events to record, specific for kprobes and kretprobes
+ repeated KprobeEvent kprobe_events = 30;
+
// Android-specific event categories:
repeated string atrace_categories = 2;
repeated string atrace_apps = 3;
diff --git a/protos/perfetto/config/perfetto_config.proto b/protos/perfetto/config/perfetto_config.proto
index a87e2a7..40259f2 100644
--- a/protos/perfetto/config/perfetto_config.proto
+++ b/protos/perfetto/config/perfetto_config.proto
@@ -888,6 +888,15 @@
// End of protos/perfetto/config/chrome/chrome_config.proto
+// Begin of protos/perfetto/config/chrome/system_metrics.proto
+
+message ChromiumSystemMetricsConfig {
+ // Samples counters every X ms.
+ optional uint32 sampling_interval_ms = 1;
+}
+
+// End of protos/perfetto/config/chrome/system_metrics.proto
+
// Begin of protos/perfetto/config/chrome/v8_config.proto
message V8Config {
@@ -927,10 +936,26 @@
// Begin of protos/perfetto/config/ftrace/ftrace_config.proto
-// Next id: 30
+// Next id: 31
message FtraceConfig {
// Ftrace events to record, example: "sched/sched_switch".
repeated string ftrace_events = 1;
+
+ message KprobeEvent {
+ enum KprobeType {
+ KPROBE_TYPE_UNKNOWN = 0;
+ KPROBE_TYPE_KPROBE = 1;
+ KPROBE_TYPE_KRETPROBE = 2;
+ KPROBE_TYPE_BOTH = 3;
+ }
+ // Kernel function name to attach to, for example "fuse_file_write_iter"
+ optional string probe = 1;
+ optional KprobeType type = 2;
+ }
+
+ // Ftrace events to record, specific for kprobes and kretprobes
+ repeated KprobeEvent kprobe_events = 30;
+
// Android-specific event categories:
repeated string atrace_categories = 2;
repeated string atrace_apps = 3;
@@ -1957,6 +1982,8 @@
UNWIND_SKIP = 1;
// Use libunwindstack (default):
UNWIND_DWARF = 2;
+ // Use userspace frame pointer unwinder:
+ UNWIND_FRAME_POINTER = 3;
}
}
@@ -2022,11 +2049,9 @@
ATOM_LMK_KILL_OCCURRED = 51;
ATOM_PICTURE_IN_PICTURE_STATE_CHANGED = 52;
ATOM_WIFI_MULTICAST_LOCK_STATE_CHANGED = 53;
- ATOM_LMK_STATE_CHANGED = 54;
ATOM_APP_START_MEMORY_STATE_CAPTURED = 55;
ATOM_SHUTDOWN_SEQUENCE_REPORTED = 56;
ATOM_BOOT_SEQUENCE_REPORTED = 57;
- ATOM_DAVEY_OCCURRED = 58;
ATOM_OVERLAY_STATE_CHANGED = 59;
ATOM_FOREGROUND_SERVICE_STATE_CHANGED = 60;
ATOM_CALL_STATE_CHANGED = 61;
@@ -2343,7 +2368,6 @@
ATOM_PRIVACY_TOGGLE_DIALOG_INTERACTION = 382;
ATOM_APP_SEARCH_OPTIMIZE_STATS_REPORTED = 383;
ATOM_NON_A11Y_TOOL_SERVICE_WARNING_REPORT = 384;
- ATOM_APP_SEARCH_SET_SCHEMA_STATS_REPORTED = 385;
ATOM_APP_COMPAT_STATE_CHANGED = 386;
ATOM_SIZE_COMPAT_RESTART_BUTTON_EVENT_REPORTED = 387;
ATOM_SPLITSCREEN_UI_CHANGED = 388;
@@ -2392,8 +2416,6 @@
ATOM_HOTWORD_DETECTION_SERVICE_RESTARTED = 432;
ATOM_HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED = 433;
ATOM_HOTWORD_DETECTOR_EVENTS = 434;
- ATOM_AD_SERVICES_API_CALLED = 435;
- ATOM_AD_SERVICES_MESUREMENT_REPORTS_UPLOADED = 436;
ATOM_BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED = 437;
ATOM_CONTACTS_INDEXER_UPDATE_STATS_REPORTED = 440;
ATOM_APP_BACKGROUND_RESTRICTIONS_INFO = 441;
@@ -2437,25 +2459,14 @@
ATOM_CB_MODULE_ERROR_REPORTED = 480;
ATOM_CB_SERVICE_FEATURE_CHANGED = 481;
ATOM_CB_RECEIVER_FEATURE_CHANGED = 482;
- ATOM_JSSCRIPTENGINE_LATENCY_REPORTED = 483;
ATOM_PRIVACY_SIGNAL_NOTIFICATION_INTERACTION = 484;
ATOM_PRIVACY_SIGNAL_ISSUE_CARD_INTERACTION = 485;
ATOM_PRIVACY_SIGNALS_JOB_FAILURE = 486;
ATOM_VIBRATION_REPORTED = 487;
ATOM_UWB_RANGING_START = 489;
- ATOM_MOBILE_DATA_DOWNLOAD_FILE_GROUP_STATUS_REPORTED = 490;
ATOM_APP_COMPACTED_V2 = 491;
- ATOM_AD_SERVICES_SETTINGS_USAGE_REPORTED = 493;
ATOM_DISPLAY_BRIGHTNESS_CHANGED = 494;
ATOM_ACTIVITY_ACTION_BLOCKED = 495;
- ATOM_BACKGROUND_FETCH_PROCESS_REPORTED = 496;
- ATOM_UPDATE_CUSTOM_AUDIENCE_PROCESS_REPORTED = 497;
- ATOM_RUN_AD_BIDDING_PROCESS_REPORTED = 498;
- ATOM_RUN_AD_SCORING_PROCESS_REPORTED = 499;
- ATOM_RUN_AD_SELECTION_PROCESS_REPORTED = 500;
- ATOM_RUN_AD_BIDDING_PER_CA_PROCESS_REPORTED = 501;
- ATOM_MOBILE_DATA_DOWNLOAD_DOWNLOAD_RESULT_REPORTED = 502;
- ATOM_MOBILE_DATA_DOWNLOAD_FILE_GROUP_STORAGE_STATS_REPORTED = 503;
ATOM_NETWORK_DNS_SERVER_SUPPORT_REPORTED = 504;
ATOM_VM_BOOTED = 505;
ATOM_VM_EXITED = 506;
@@ -2464,7 +2475,6 @@
ATOM_MEDIAMETRICS_SPATIALIZERDEVICEENABLED_REPORTED = 509;
ATOM_MEDIAMETRICS_HEADTRACKERDEVICEENABLED_REPORTED = 510;
ATOM_MEDIAMETRICS_HEADTRACKERDEVICESUPPORTED_REPORTED = 511;
- ATOM_AD_SERVICES_MEASUREMENT_REGISTRATIONS = 512;
ATOM_HEARING_AID_INFO_REPORTED = 513;
ATOM_DEVICE_WIDE_JOB_CONSTRAINT_CHANGED = 514;
ATOM_AMBIENT_MODE_CHANGED = 515;
@@ -2486,9 +2496,6 @@
ATOM_BLUETOOTH_LOCAL_SUPPORTED_FEATURES_REPORTED = 532;
ATOM_BLUETOOTH_GATT_APP_INFO = 533;
ATOM_BRIGHTNESS_CONFIGURATION_UPDATED = 534;
- ATOM_AD_SERVICES_GET_TOPICS_REPORTED = 535;
- ATOM_AD_SERVICES_EPOCH_COMPUTATION_GET_TOP_TOPICS_REPORTED = 536;
- ATOM_AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED = 537;
ATOM_WEAR_MEDIA_OUTPUT_SWITCHER_LAUNCHED = 538;
ATOM_WEAR_MEDIA_OUTPUT_SWITCHER_FINISHED = 539;
ATOM_WEAR_MEDIA_OUTPUT_SWITCHER_CONNECTION_REPORTED = 540;
@@ -2526,12 +2533,10 @@
ATOM_MEDIAMETRICS_MIDI_DEVICE_CLOSE_REPORTED = 576;
ATOM_BIOMETRIC_TOUCH_REPORTED = 577;
ATOM_HOTWORD_AUDIO_EGRESS_EVENT_REPORTED = 578;
- ATOM_APP_SEARCH_SCHEMA_MIGRATION_STATS_REPORTED = 579;
ATOM_LOCATION_ENABLED_STATE_CHANGED = 580;
ATOM_IME_REQUEST_FINISHED = 581;
ATOM_USB_COMPLIANCE_WARNINGS_REPORTED = 582;
ATOM_APP_SUPPORTED_LOCALES_CHANGED = 583;
- ATOM_GRAMMATICAL_INFLECTION_CHANGED = 584;
ATOM_MEDIA_PROVIDER_VOLUME_RECOVERY_REPORTED = 586;
ATOM_BIOMETRIC_PROPERTIES_COLLECTED = 587;
ATOM_KERNEL_WAKEUP_ATTRIBUTED = 588;
@@ -2544,7 +2549,11 @@
ATOM_WS_NOTIFICATION_UPDATED = 596;
ATOM_NETWORK_VALIDATION_FAILURE_STATS_DAILY_REPORTED = 601;
ATOM_WS_COMPLICATION_TAPPED = 602;
- ATOM_WS_WEAR_TIME_SESSION = 610;
+ ATOM_WS_NOTIFICATION_BLOCKING = 780;
+ ATOM_WS_NOTIFICATION_BRIDGEMODE_UPDATED = 822;
+ ATOM_WS_NOTIFICATION_DISMISSAL_ACTIONED = 823;
+ ATOM_WS_NOTIFICATION_ACTIONED = 824;
+ ATOM_WS_NOTIFICATION_LATENCY = 880;
ATOM_WIFI_BYTES_TRANSFER = 10000;
ATOM_WIFI_BYTES_TRANSFER_BY_FG_BG = 10001;
ATOM_MOBILE_BYTES_TRANSFER = 10002;
@@ -2717,6 +2726,186 @@
ATOM_NOTIFICATION_MEMORY_USE = 10174;
ATOM_HDR_CAPABILITIES = 10175;
ATOM_WS_FAVOURITE_WATCH_FACE_LIST_SNAPSHOT = 10176;
+ ATOM_WS_WEAR_TIME_SESSION = 610;
+ ATOM_WS_INCOMING_CALL_ACTION_REPORTED = 626;
+ ATOM_WS_CALL_DISCONNECTION_REPORTED = 627;
+ ATOM_WS_CALL_DURATION_REPORTED = 628;
+ ATOM_WS_CALL_USER_EXPERIENCE_LATENCY_REPORTED = 629;
+ ATOM_WS_CALL_INTERACTION_REPORTED = 630;
+ ATOM_WS_ON_BODY_STATE_CHANGED = 787;
+ ATOM_WS_WATCH_FACE_RESTRICTED_COMPLICATIONS_IMPACTED = 802;
+ ATOM_WS_WATCH_FACE_DEFAULT_RESTRICTED_COMPLICATIONS_REMOVED = 803;
+ ATOM_WS_COMPLICATIONS_IMPACTED_NOTIFICATION_EVENT_REPORTED = 804;
+ ATOM_WS_STANDALONE_MODE_SNAPSHOT = 10197;
+ ATOM_WS_FAVORITE_WATCH_FACE_SNAPSHOT = 10206;
+ ATOM_SETTINGS_SPA_REPORTED = 622;
+ ATOM_PDF_LOAD_REPORTED = 859;
+ ATOM_PDF_API_USAGE_REPORTED = 860;
+ ATOM_PDF_SEARCH_REPORTED = 861;
+ ATOM_HDMI_EARC_STATUS_REPORTED = 701;
+ ATOM_HDMI_SOUNDBAR_MODE_STATUS_REPORTED = 724;
+ ATOM_MEDIA_PROVIDER_DATABASE_ROLLBACK_REPORTED = 784;
+ ATOM_BACKUP_SETUP_STATUS_REPORTED = 785;
+ ATOM_PHOTOPICKER_SESSION_INFO_REPORTED = 886;
+ ATOM_PHOTOPICKER_API_INFO_REPORTED = 887;
+ ATOM_PHOTOPICKER_UI_EVENT_LOGGED = 888;
+ ATOM_PHOTOPICKER_MEDIA_ITEM_STATUS_REPORTED = 889;
+ ATOM_PHOTOPICKER_PREVIEW_INFO_LOGGED = 890;
+ ATOM_PHOTOPICKER_MENU_INTERACTION_LOGGED = 891;
+ ATOM_PHOTOPICKER_BANNER_INTERACTION_LOGGED = 892;
+ ATOM_PHOTOPICKER_MEDIA_LIBRARY_INFO_LOGGED = 893;
+ ATOM_PHOTOPICKER_PAGE_INFO_LOGGED = 894;
+ ATOM_PHOTOPICKER_MEDIA_GRID_SYNC_INFO_REPORTED = 895;
+ ATOM_PHOTOPICKER_ALBUM_SYNC_INFO_REPORTED = 896;
+ ATOM_PHOTOPICKER_SEARCH_INFO_REPORTED = 897;
+ ATOM_SEARCH_DATA_EXTRACTION_DETAILS_REPORTED = 898;
+ ATOM_EMBEDDED_PHOTOPICKER_INFO_REPORTED = 899;
+ ATOM_WEAR_POWER_MENU_OPENED = 731;
+ ATOM_WEAR_ASSISTANT_OPENED = 755;
+ ATOM_KERNEL_OOM_KILL_OCCURRED = 754;
+ ATOM_AUTOFILL_UI_EVENT_REPORTED = 603;
+ ATOM_AUTOFILL_FILL_REQUEST_REPORTED = 604;
+ ATOM_AUTOFILL_FILL_RESPONSE_REPORTED = 605;
+ ATOM_AUTOFILL_SAVE_EVENT_REPORTED = 606;
+ ATOM_AUTOFILL_SESSION_COMMITTED = 607;
+ ATOM_AUTOFILL_FIELD_CLASSIFICATION_EVENT_REPORTED = 659;
+ ATOM_TV_LOW_POWER_STANDBY_POLICY = 679;
+ ATOM_EXTERNAL_TV_INPUT_EVENT = 717;
+ ATOM_COMPONENT_STATE_CHANGED_REPORTED = 863;
+ ATOM_AI_WALLPAPERS_BUTTON_PRESSED = 706;
+ ATOM_AI_WALLPAPERS_TEMPLATE_SELECTED = 707;
+ ATOM_AI_WALLPAPERS_TERM_SELECTED = 708;
+ ATOM_AI_WALLPAPERS_WALLPAPER_SET = 709;
+ ATOM_AI_WALLPAPERS_SESSION_SUMMARY = 710;
+ ATOM_APF_SESSION_INFO_REPORTED = 777;
+ ATOM_IP_CLIENT_RA_INFO_REPORTED = 778;
+ ATOM_VPN_CONNECTION_STATE_CHANGED = 850;
+ ATOM_VPN_CONNECTION_REPORTED = 851;
+ ATOM_NETWORK_STATS_RECORDER_FILE_OPERATED = 783;
+ ATOM_DAILY_KEEPALIVE_INFO_REPORTED = 650;
+ ATOM_NETWORK_REQUEST_STATE_CHANGED = 779;
+ ATOM_TETHERING_ACTIVE_SESSIONS_REPORTED = 925;
+ ATOM_ART_DATUM_REPORTED = 332;
+ ATOM_ART_DEVICE_DATUM_REPORTED = 550;
+ ATOM_ART_DATUM_DELTA_REPORTED = 565;
+ ATOM_ART_DEX2OAT_REPORTED = 929;
+ ATOM_ART_DEVICE_STATUS = 10205;
+ ATOM_ODREFRESH_REPORTED = 366;
+ ATOM_ODSIGN_REPORTED = 548;
+ ATOM_BACKGROUND_DEXOPT_JOB_ENDED = 467;
+ ATOM_PREREBOOT_DEXOPT_JOB_ENDED = 883;
+ ATOM_PERMISSION_RATIONALE_DIALOG_VIEWED = 645;
+ ATOM_PERMISSION_RATIONALE_DIALOG_ACTION_REPORTED = 646;
+ ATOM_APP_DATA_SHARING_UPDATES_NOTIFICATION_INTERACTION = 647;
+ ATOM_APP_DATA_SHARING_UPDATES_FRAGMENT_VIEWED = 648;
+ ATOM_APP_DATA_SHARING_UPDATES_FRAGMENT_ACTION_REPORTED = 649;
+ ATOM_ENHANCED_CONFIRMATION_DIALOG_RESULT_REPORTED = 827;
+ ATOM_ENHANCED_CONFIRMATION_RESTRICTION_CLEARED = 828;
+ ATOM_EMERGENCY_STATE_CHANGED = 633;
+ ATOM_CHRE_SIGNIFICANT_MOTION_STATE_CHANGED = 868;
+ ATOM_HEALTH_CONNECT_UI_IMPRESSION = 623;
+ ATOM_HEALTH_CONNECT_UI_INTERACTION = 624;
+ ATOM_HEALTH_CONNECT_APP_OPENED_REPORTED = 625;
+ ATOM_HEALTH_CONNECT_API_CALLED = 616;
+ ATOM_HEALTH_CONNECT_USAGE_STATS = 617;
+ ATOM_HEALTH_CONNECT_STORAGE_STATS = 618;
+ ATOM_HEALTH_CONNECT_API_INVOKED = 643;
+ ATOM_EXERCISE_ROUTE_API_CALLED = 654;
+ ATOM_SELINUX_AUDIT_LOG = 799;
+ ATOM_ONDEVICEPERSONALIZATION_API_CALLED = 711;
+ ATOM_CELLULAR_RADIO_POWER_STATE_CHANGED = 713;
+ ATOM_EMERGENCY_NUMBERS_INFO = 10180;
+ ATOM_DATA_NETWORK_VALIDATION = 10207;
+ ATOM_DATA_RAT_STATE_CHANGED = 854;
+ ATOM_CONNECTED_CHANNEL_CHANGED = 882;
+ ATOM_QUALIFIED_RAT_LIST_CHANGED = 634;
+ ATOM_QNS_IMS_CALL_DROP_STATS = 635;
+ ATOM_QNS_FALLBACK_RESTRICTION_CHANGED = 636;
+ ATOM_QNS_RAT_PREFERENCE_MISMATCH_INFO = 10177;
+ ATOM_QNS_HANDOVER_TIME_MILLIS = 10178;
+ ATOM_QNS_HANDOVER_PINGPONG = 10179;
+ ATOM_SATELLITE_CONTROLLER = 10182;
+ ATOM_SATELLITE_SESSION = 10183;
+ ATOM_SATELLITE_INCOMING_DATAGRAM = 10184;
+ ATOM_SATELLITE_OUTGOING_DATAGRAM = 10185;
+ ATOM_SATELLITE_PROVISION = 10186;
+ ATOM_SATELLITE_SOS_MESSAGE_RECOMMENDER = 10187;
+ ATOM_CARRIER_ROAMING_SATELLITE_SESSION = 10211;
+ ATOM_CARRIER_ROAMING_SATELLITE_CONTROLLER_STATS = 10212;
+ ATOM_CONTROLLER_STATS_PER_PACKAGE = 10213;
+ ATOM_SATELLITE_ENTITLEMENT = 10214;
+ ATOM_SATELLITE_CONFIG_UPDATER = 10215;
+ ATOM_SATELLITE_ACCESS_CONTROLLER = 10219;
+ ATOM_CELLULAR_IDENTIFIER_DISCLOSED = 800;
+ ATOM_KEYBOARD_CONFIGURED = 682;
+ ATOM_KEYBOARD_SYSTEMS_EVENT_REPORTED = 683;
+ ATOM_INPUTDEVICE_USAGE_REPORTED = 686;
+ ATOM_TOUCHPAD_USAGE = 10191;
+ ATOM_THREADNETWORK_TELEMETRY_DATA_REPORTED = 738;
+ ATOM_THREADNETWORK_TOPO_ENTRY_REPEATED = 739;
+ ATOM_THREADNETWORK_DEVICE_INFO_REPORTED = 740;
+ ATOM_CRONET_ENGINE_CREATED = 703;
+ ATOM_CRONET_TRAFFIC_REPORTED = 704;
+ ATOM_CRONET_ENGINE_BUILDER_INITIALIZED = 762;
+ ATOM_CRONET_HTTP_FLAGS_INITIALIZED = 763;
+ ATOM_CRONET_INITIALIZED = 764;
+ ATOM_WEAR_MODE_STATE_CHANGED = 715;
+ ATOM_RENDERER_INITIALIZED = 736;
+ ATOM_SCHEMA_VERSION_RECEIVED = 737;
+ ATOM_LAYOUT_INSPECTED = 741;
+ ATOM_LAYOUT_EXPRESSION_INSPECTED = 742;
+ ATOM_LAYOUT_ANIMATIONS_INSPECTED = 743;
+ ATOM_MATERIAL_COMPONENTS_INSPECTED = 744;
+ ATOM_TILE_REQUESTED = 745;
+ ATOM_STATE_RESPONSE_RECEIVED = 746;
+ ATOM_TILE_RESPONSE_RECEIVED = 747;
+ ATOM_INFLATION_FINISHED = 748;
+ ATOM_INFLATION_FAILED = 749;
+ ATOM_IGNORED_INFLATION_FAILURES_REPORTED = 750;
+ ATOM_DRAWABLE_RENDERED = 751;
+ ATOM_MEDIA_ACTION_REPORTED = 608;
+ ATOM_MEDIA_CONTROLS_LAUNCHED = 609;
+ ATOM_MEDIA_SESSION_STATE_CHANGED = 677;
+ ATOM_WEAR_MEDIA_OUTPUT_SWITCHER_DEVICE_SCAN_API_LATENCY = 757;
+ ATOM_WEAR_MEDIA_OUTPUT_SWITCHER_SASS_DEVICE_UNAVAILABLE = 758;
+ ATOM_WEAR_MEDIA_OUTPUT_SWITCHER_FASTPAIR_API_TIMEOUT = 759;
+ ATOM_MEDIATOR_UPDATED = 721;
+ ATOM_SYSPROXY_BLUETOOTH_BYTES_TRANSFER = 10196;
+ ATOM_SYSPROXY_CONNECTION_UPDATED = 786;
+ ATOM_ADAPTIVE_AUTH_UNLOCK_AFTER_LOCK_REPORTED = 820;
+ ATOM_FEDERATED_COMPUTE_API_CALLED = 712;
+ ATOM_FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED = 771;
+ ATOM_EXAMPLE_ITERATOR_NEXT_LATENCY_REPORTED = 838;
+ ATOM_RKPD_POOL_STATS = 664;
+ ATOM_RKPD_CLIENT_OPERATION = 665;
+ ATOM_CPU_POLICY = 10199;
+ ATOM_ATOM_9999 = 9999;
+ ATOM_ATOM_99999 = 99999;
+ ATOM_SCREEN_OFF_REPORTED = 776;
+ ATOM_SCREEN_TIMEOUT_OVERRIDE_REPORTED = 836;
+ ATOM_SCREEN_INTERACTIVE_SESSION_REPORTED = 837;
+ ATOM_SCREEN_DIM_REPORTED = 867;
+ ATOM_FULL_SCREEN_INTENT_LAUNCHED = 631;
+ ATOM_BAL_ALLOWED = 632;
+ ATOM_IN_TASK_ACTIVITY_STARTED = 685;
+ ATOM_CACHED_APPS_HIGH_WATERMARK = 10189;
+ ATOM_STYLUS_PREDICTION_METRICS_REPORTED = 718;
+ ATOM_USER_RISK_EVENT_REPORTED = 725;
+ ATOM_MEDIA_PROJECTION_STATE_CHANGED = 729;
+ ATOM_MEDIA_PROJECTION_TARGET_CHANGED = 730;
+ ATOM_EXCESSIVE_BINDER_PROXY_COUNT_REPORTED = 853;
+ ATOM_PROXY_BYTES_TRANSFER_BY_FG_BG = 10200;
+ ATOM_MOBILE_BYTES_TRANSFER_BY_PROC_STATE = 10204;
+ ATOM_BIOMETRIC_FRR_NOTIFICATION = 817;
+ ATOM_SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION = 830;
+ ATOM_SENSITIVE_NOTIFICATION_APP_PROTECTION_SESSION = 831;
+ ATOM_SENSITIVE_NOTIFICATION_APP_PROTECTION_APPLIED = 832;
+ ATOM_SENSITIVE_NOTIFICATION_REDACTION = 833;
+ ATOM_SENSITIVE_CONTENT_APP_PROTECTION = 835;
+ ATOM_APP_RESTRICTION_STATE_CHANGED = 866;
+ ATOM_DREAM_SETTING_CHANGED = 705;
+ ATOM_DREAM_SETTING_SNAPSHOT = 10192;
+ ATOM_BOOT_INTEGRITY_INFO_REPORTED = 775;
ATOM_WIFI_AWARE_NDP_REPORTED = 638;
ATOM_WIFI_AWARE_ATTACH_REPORTED = 639;
ATOM_WIFI_SELF_RECOVERY_TRIGGERED = 661;
@@ -2731,39 +2920,99 @@
ATOM_WIFI_LOCAL_ONLY_REQUEST_SCAN_TRIGGERED = 693;
ATOM_WIFI_THREAD_TASK_EXECUTED = 694;
ATOM_WIFI_STATE_CHANGED = 700;
+ ATOM_PNO_SCAN_STARTED = 719;
+ ATOM_PNO_SCAN_STOPPED = 720;
+ ATOM_WIFI_IS_UNUSABLE_REPORTED = 722;
+ ATOM_WIFI_AP_CAPABILITIES_REPORTED = 723;
+ ATOM_SOFT_AP_STATE_CHANGED = 805;
+ ATOM_SCORER_PREDICTION_RESULT_REPORTED = 884;
ATOM_WIFI_AWARE_CAPABILITIES = 10190;
ATOM_WIFI_MODULE_INFO = 10193;
- ATOM_SETTINGS_SPA_REPORTED = 622;
+ ATOM_WIFI_SETTING_INFO = 10194;
+ ATOM_WIFI_COMPLEX_SETTING_INFO = 10195;
+ ATOM_WIFI_CONFIGURED_NETWORK_INFO = 10198;
+ ATOM_MTE_STATE = 10181;
+ ATOM_HOTWORD_EGRESS_SIZE_ATOM_REPORTED = 761;
+ ATOM_SANDBOX_API_CALLED = 488;
+ ATOM_SANDBOX_ACTIVITY_EVENT_OCCURRED = 735;
+ ATOM_SDK_SANDBOX_RESTRICTED_ACCESS_IN_SESSION = 796;
+ ATOM_SANDBOX_SDK_STORAGE = 10159;
ATOM_EXPRESS_EVENT_REPORTED = 528;
ATOM_EXPRESS_HISTOGRAM_SAMPLE_REPORTED = 593;
ATOM_EXPRESS_UID_EVENT_REPORTED = 644;
ATOM_EXPRESS_UID_HISTOGRAM_SAMPLE_REPORTED = 658;
- ATOM_PERMISSION_RATIONALE_DIALOG_VIEWED = 645;
- ATOM_PERMISSION_RATIONALE_DIALOG_ACTION_REPORTED = 646;
- ATOM_APP_DATA_SHARING_UPDATES_NOTIFICATION_INTERACTION = 647;
- ATOM_APP_DATA_SHARING_UPDATES_FRAGMENT_VIEWED = 648;
- ATOM_APP_DATA_SHARING_UPDATES_FRAGMENT_ACTION_REPORTED = 649;
- ATOM_WS_INCOMING_CALL_ACTION_REPORTED = 626;
- ATOM_WS_CALL_DISCONNECTION_REPORTED = 627;
- ATOM_WS_CALL_DURATION_REPORTED = 628;
- ATOM_WS_CALL_USER_EXPERIENCE_LATENCY_REPORTED = 629;
- ATOM_WS_CALL_INTERACTION_REPORTED = 630;
- ATOM_FULL_SCREEN_INTENT_LAUNCHED = 631;
- ATOM_BAL_ALLOWED = 632;
- ATOM_IN_TASK_ACTIVITY_STARTED = 685;
- ATOM_CACHED_APPS_HIGH_WATERMARK = 10189;
- ATOM_ODREFRESH_REPORTED = 366;
- ATOM_ODSIGN_REPORTED = 548;
- ATOM_ART_DATUM_REPORTED = 332;
- ATOM_ART_DEVICE_DATUM_REPORTED = 550;
- ATOM_ART_DATUM_DELTA_REPORTED = 565;
- ATOM_BACKGROUND_DEXOPT_JOB_ENDED = 467;
- ATOM_WEAR_ADAPTIVE_SUSPEND_STATS_REPORTED = 619;
- ATOM_WEAR_POWER_ANOMALY_SERVICE_OPERATIONAL_STATS_REPORTED = 620;
- ATOM_WEAR_POWER_ANOMALY_SERVICE_EVENT_STATS_REPORTED = 621;
- ATOM_EMERGENCY_STATE_CHANGED = 633;
- ATOM_DND_STATE_CHANGED = 657;
- ATOM_MTE_STATE = 10181;
+ ATOM_IKE_SESSION_TERMINATED = 678;
+ ATOM_IKE_LIVENESS_CHECK_SESSION_VALIDATED = 760;
+ ATOM_NEGOTIATED_SECURITY_ASSOCIATION = 821;
+ ATOM_APP_SEARCH_SET_SCHEMA_STATS_REPORTED = 385;
+ ATOM_APP_SEARCH_SCHEMA_MIGRATION_STATS_REPORTED = 579;
+ ATOM_APP_SEARCH_USAGE_SEARCH_INTENT_STATS_REPORTED = 825;
+ ATOM_APP_SEARCH_USAGE_SEARCH_INTENT_RAW_QUERY_STATS_REPORTED = 826;
+ ATOM_DEVICE_POLICY_MANAGEMENT_MODE = 10216;
+ ATOM_DEVICE_POLICY_STATE = 10217;
+ ATOM_DESKTOP_MODE_UI_CHANGED = 818;
+ ATOM_DESKTOP_MODE_SESSION_TASK_UPDATE = 819;
+ ATOM_MEDIA_CODEC_RECLAIM_REQUEST_COMPLETED = 600;
+ ATOM_MEDIA_CODEC_STARTED = 641;
+ ATOM_MEDIA_CODEC_STOPPED = 642;
+ ATOM_MEDIA_CODEC_RENDERED = 684;
+ ATOM_MEDIA_EDITING_ENDED_REPORTED = 798;
+ ATOM_CAR_WAKEUP_FROM_SUSPEND_REPORTED = 852;
+ ATOM_PLUGIN_INITIALIZED = 655;
+ ATOM_CAR_RECENTS_EVENT_REPORTED = 770;
+ ATOM_CAR_CALM_MODE_EVENT_REPORTED = 797;
+ ATOM_CAMERA_FEATURE_COMBINATION_QUERY_EVENT = 900;
+ ATOM_THERMAL_STATUS_CALLED = 772;
+ ATOM_THERMAL_HEADROOM_CALLED = 773;
+ ATOM_THERMAL_HEADROOM_THRESHOLDS_CALLED = 774;
+ ATOM_ADPF_HINT_SESSION_TID_CLEANUP = 839;
+ ATOM_THERMAL_HEADROOM_THRESHOLDS = 10201;
+ ATOM_ADPF_SESSION_SNAPSHOT = 10218;
+ ATOM_BLUETOOTH_HASHED_DEVICE_NAME_REPORTED = 613;
+ ATOM_BLUETOOTH_L2CAP_COC_CLIENT_CONNECTION = 614;
+ ATOM_BLUETOOTH_L2CAP_COC_SERVER_CONNECTION = 615;
+ ATOM_BLUETOOTH_LE_SESSION_CONNECTED = 656;
+ ATOM_RESTRICTED_BLUETOOTH_DEVICE_NAME_REPORTED = 666;
+ ATOM_BLUETOOTH_PROFILE_CONNECTION_ATTEMPTED = 696;
+ ATOM_BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED = 781;
+ ATOM_BLUETOOTH_RFCOMM_CONNECTION_ATTEMPTED = 782;
+ ATOM_REMOTE_DEVICE_INFORMATION_WITH_METRIC_ID = 862;
+ ATOM_LE_APP_SCAN_STATE_CHANGED = 870;
+ ATOM_LE_RADIO_SCAN_STOPPED = 871;
+ ATOM_LE_SCAN_RESULT_RECEIVED = 872;
+ ATOM_LE_SCAN_ABUSED = 873;
+ ATOM_LE_ADV_STATE_CHANGED = 874;
+ ATOM_LE_ADV_ERROR_REPORTED = 875;
+ ATOM_A2DP_SESSION_REPORTED = 904;
+ ATOM_BLUETOOTH_CROSS_LAYER_EVENT_REPORTED = 916;
+ ATOM_BROADCAST_AUDIO_SESSION_REPORTED = 927;
+ ATOM_BROADCAST_AUDIO_SYNC_REPORTED = 928;
+ ATOM_DEVICE_LOCK_CHECK_IN_REQUEST_REPORTED = 726;
+ ATOM_DEVICE_LOCK_PROVISIONING_COMPLETE_REPORTED = 727;
+ ATOM_DEVICE_LOCK_KIOSK_APP_REQUEST_REPORTED = 728;
+ ATOM_DEVICE_LOCK_CHECK_IN_RETRY_REPORTED = 789;
+ ATOM_DEVICE_LOCK_PROVISION_FAILURE_REPORTED = 790;
+ ATOM_DEVICE_LOCK_LOCK_UNLOCK_DEVICE_FAILURE_REPORTED = 791;
+ ATOM_APPLICATION_GRAMMATICAL_INFLECTION_CHANGED = 584;
+ ATOM_SYSTEM_GRAMMATICAL_INFLECTION_CHANGED = 816;
+ ATOM_EMERGENCY_NUMBER_DIALED = 637;
+ ATOM_JSSCRIPTENGINE_LATENCY_REPORTED = 483;
+ ATOM_AD_SERVICES_API_CALLED = 435;
+ ATOM_AD_SERVICES_MESUREMENT_REPORTS_UPLOADED = 436;
+ ATOM_MOBILE_DATA_DOWNLOAD_FILE_GROUP_STATUS_REPORTED = 490;
+ ATOM_MOBILE_DATA_DOWNLOAD_DOWNLOAD_RESULT_REPORTED = 502;
+ ATOM_AD_SERVICES_SETTINGS_USAGE_REPORTED = 493;
+ ATOM_BACKGROUND_FETCH_PROCESS_REPORTED = 496;
+ ATOM_UPDATE_CUSTOM_AUDIENCE_PROCESS_REPORTED = 497;
+ ATOM_RUN_AD_BIDDING_PROCESS_REPORTED = 498;
+ ATOM_RUN_AD_SCORING_PROCESS_REPORTED = 499;
+ ATOM_RUN_AD_SELECTION_PROCESS_REPORTED = 500;
+ ATOM_RUN_AD_BIDDING_PER_CA_PROCESS_REPORTED = 501;
+ ATOM_MOBILE_DATA_DOWNLOAD_FILE_GROUP_STORAGE_STATS_REPORTED = 503;
+ ATOM_AD_SERVICES_MEASUREMENT_REGISTRATIONS = 512;
+ ATOM_AD_SERVICES_GET_TOPICS_REPORTED = 535;
+ ATOM_AD_SERVICES_EPOCH_COMPUTATION_GET_TOP_TOPICS_REPORTED = 536;
+ ATOM_AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED = 537;
ATOM_AD_SERVICES_BACK_COMPAT_GET_TOPICS_REPORTED = 598;
ATOM_AD_SERVICES_BACK_COMPAT_EPOCH_COMPUTATION_CLASSIFIER_REPORTED = 599;
ATOM_AD_SERVICES_MEASUREMENT_DEBUG_KEYS = 640;
@@ -2773,68 +3022,68 @@
ATOM_AD_SERVICES_MEASUREMENT_ATTRIBUTION = 674;
ATOM_AD_SERVICES_MEASUREMENT_JOBS = 675;
ATOM_AD_SERVICES_MEASUREMENT_WIPEOUT = 676;
+ ATOM_AD_SERVICES_MEASUREMENT_AD_ID_MATCH_FOR_DEBUG_KEYS = 695;
+ ATOM_AD_SERVICES_ENROLLMENT_DATA_STORED = 697;
+ ATOM_AD_SERVICES_ENROLLMENT_FILE_DOWNLOADED = 698;
+ ATOM_AD_SERVICES_ENROLLMENT_MATCHED = 699;
ATOM_AD_SERVICES_CONSENT_MIGRATED = 702;
- ATOM_RKPD_POOL_STATS = 664;
- ATOM_RKPD_CLIENT_OPERATION = 665;
- ATOM_AUTOFILL_UI_EVENT_REPORTED = 603;
- ATOM_AUTOFILL_FILL_REQUEST_REPORTED = 604;
- ATOM_AUTOFILL_FILL_RESPONSE_REPORTED = 605;
- ATOM_AUTOFILL_SAVE_EVENT_REPORTED = 606;
- ATOM_AUTOFILL_SESSION_COMMITTED = 607;
- ATOM_AUTOFILL_FIELD_CLASSIFICATION_EVENT_REPORTED = 659;
+ ATOM_AD_SERVICES_ENROLLMENT_FAILED = 714;
+ ATOM_AD_SERVICES_MEASUREMENT_CLICK_VERIFICATION = 756;
+ ATOM_AD_SERVICES_ENCRYPTION_KEY_FETCHED = 765;
+ ATOM_AD_SERVICES_ENCRYPTION_KEY_DB_TRANSACTION_ENDED = 766;
+ ATOM_DESTINATION_REGISTERED_BEACONS = 767;
+ ATOM_REPORT_INTERACTION_API_CALLED = 768;
+ ATOM_INTERACTION_REPORTING_TABLE_CLEARED = 769;
+ ATOM_APP_MANIFEST_CONFIG_HELPER_CALLED = 788;
+ ATOM_AD_FILTERING_PROCESS_JOIN_CA_REPORTED = 793;
+ ATOM_AD_FILTERING_PROCESS_AD_SELECTION_REPORTED = 794;
+ ATOM_AD_COUNTER_HISTOGRAM_UPDATER_REPORTED = 795;
+ ATOM_SIGNATURE_VERIFICATION = 807;
+ ATOM_K_ANON_IMMEDIATE_SIGN_JOIN_STATUS_REPORTED = 808;
+ ATOM_K_ANON_BACKGROUND_JOB_STATUS_REPORTED = 809;
+ ATOM_K_ANON_INITIALIZE_STATUS_REPORTED = 810;
+ ATOM_K_ANON_SIGN_STATUS_REPORTED = 811;
+ ATOM_K_ANON_JOIN_STATUS_REPORTED = 812;
+ ATOM_K_ANON_KEY_ATTESTATION_STATUS_REPORTED = 813;
+ ATOM_GET_AD_SELECTION_DATA_API_CALLED = 814;
+ ATOM_GET_AD_SELECTION_DATA_BUYER_INPUT_GENERATED = 815;
+ ATOM_BACKGROUND_JOB_SCHEDULING_REPORTED = 834;
+ ATOM_TOPICS_ENCRYPTION_EPOCH_COMPUTATION_REPORTED = 840;
+ ATOM_TOPICS_ENCRYPTION_GET_TOPICS_REPORTED = 841;
+ ATOM_ADSERVICES_SHELL_COMMAND_CALLED = 842;
+ ATOM_UPDATE_SIGNALS_API_CALLED = 843;
+ ATOM_ENCODING_JOB_RUN = 844;
+ ATOM_ENCODING_JS_FETCH = 845;
+ ATOM_ENCODING_JS_EXECUTION = 846;
+ ATOM_PERSIST_AD_SELECTION_RESULT_CALLED = 847;
+ ATOM_SERVER_AUCTION_KEY_FETCH_CALLED = 848;
+ ATOM_SERVER_AUCTION_BACKGROUND_KEY_FETCH_ENABLED = 849;
+ ATOM_AD_SERVICES_MEASUREMENT_PROCESS_ODP_REGISTRATION = 864;
+ ATOM_AD_SERVICES_MEASUREMENT_NOTIFY_REGISTRATION_TO_ODP = 865;
+ ATOM_SELECT_ADS_FROM_OUTCOMES_API_CALLED = 876;
+ ATOM_REPORT_IMPRESSION_API_CALLED = 877;
+ ATOM_AD_SERVICES_ENROLLMENT_TRANSACTION_STATS = 885;
+ ATOM_EXTERNAL_DISPLAY_STATE_CHANGED = 806;
+ ATOM_DISPLAY_MODE_DIRECTOR_VOTE_CHANGED = 792;
ATOM_TEST_EXTENSION_ATOM_REPORTED = 660;
ATOM_TEST_RESTRICTED_ATOM_REPORTED = 672;
ATOM_STATS_SOCKET_LOSS_REPORTED = 752;
- ATOM_PLUGIN_INITIALIZED = 655;
- ATOM_TV_LOW_POWER_STANDBY_POLICY = 679;
+ ATOM_NFC_OBSERVE_MODE_STATE_CHANGED = 855;
+ ATOM_NFC_FIELD_CHANGED = 856;
+ ATOM_NFC_POLLING_LOOP_NOTIFICATION_REPORTED = 857;
+ ATOM_NFC_PROPRIETARY_CAPABILITIES_REPORTED = 858;
ATOM_LOCKSCREEN_SHORTCUT_SELECTED = 611;
ATOM_LOCKSCREEN_SHORTCUT_TRIGGERED = 612;
- ATOM_EMERGENCY_NUMBERS_INFO = 10180;
- ATOM_QUALIFIED_RAT_LIST_CHANGED = 634;
- ATOM_QNS_IMS_CALL_DROP_STATS = 635;
- ATOM_QNS_FALLBACK_RESTRICTION_CHANGED = 636;
- ATOM_QNS_RAT_PREFERENCE_MISMATCH_INFO = 10177;
- ATOM_QNS_HANDOVER_TIME_MILLIS = 10178;
- ATOM_QNS_HANDOVER_PINGPONG = 10179;
- ATOM_SATELLITE_CONTROLLER = 10182;
- ATOM_SATELLITE_SESSION = 10183;
- ATOM_SATELLITE_INCOMING_DATAGRAM = 10184;
- ATOM_SATELLITE_OUTGOING_DATAGRAM = 10185;
- ATOM_SATELLITE_PROVISION = 10186;
- ATOM_SATELLITE_SOS_MESSAGE_RECOMMENDER = 10187;
- ATOM_IKE_SESSION_TERMINATED = 678;
- ATOM_IKE_LIVENESS_CHECK_SESSION_VALIDATED = 760;
- ATOM_BLUETOOTH_HASHED_DEVICE_NAME_REPORTED = 613;
- ATOM_BLUETOOTH_L2CAP_COC_CLIENT_CONNECTION = 614;
- ATOM_BLUETOOTH_L2CAP_COC_SERVER_CONNECTION = 615;
- ATOM_BLUETOOTH_LE_SESSION_CONNECTED = 656;
- ATOM_RESTRICTED_BLUETOOTH_DEVICE_NAME_REPORTED = 666;
- ATOM_BLUETOOTH_PROFILE_CONNECTION_ATTEMPTED = 696;
- ATOM_HEALTH_CONNECT_UI_IMPRESSION = 623;
- ATOM_HEALTH_CONNECT_UI_INTERACTION = 624;
- ATOM_HEALTH_CONNECT_APP_OPENED_REPORTED = 625;
- ATOM_HEALTH_CONNECT_API_CALLED = 616;
- ATOM_HEALTH_CONNECT_USAGE_STATS = 617;
- ATOM_HEALTH_CONNECT_STORAGE_STATS = 618;
- ATOM_HEALTH_CONNECT_API_INVOKED = 643;
- ATOM_EXERCISE_ROUTE_API_CALLED = 654;
- ATOM_ATOM_9999 = 9999;
- ATOM_ATOM_99999 = 99999;
- ATOM_THREADNETWORK_TELEMETRY_DATA_REPORTED = 738;
- ATOM_THREADNETWORK_TOPO_ENTRY_REPEATED = 739;
- ATOM_THREADNETWORK_DEVICE_INFO_REPORTED = 740;
- ATOM_EMERGENCY_NUMBER_DIALED = 637;
- ATOM_SANDBOX_API_CALLED = 488;
- ATOM_SANDBOX_ACTIVITY_EVENT_OCCURRED = 735;
- ATOM_SANDBOX_SDK_STORAGE = 10159;
- ATOM_CRONET_ENGINE_CREATED = 703;
- ATOM_CRONET_TRAFFIC_REPORTED = 704;
- ATOM_CRONET_ENGINE_BUILDER_INITIALIZED = 762;
- ATOM_CRONET_HTTP_FLAGS_INITIALIZED = 763;
- ATOM_CRONET_INITIALIZED = 764;
- ATOM_DAILY_KEEPALIVE_INFO_REPORTED = 650;
- ATOM_IP_CLIENT_RA_INFO_REPORTED = 778;
- ATOM_APF_SESSION_INFO_REPORTED = 777;
+ ATOM_LAUNCHER_IMPRESSION_EVENT_V2 = 716;
+ ATOM_DISPLAY_SWITCH_LATENCY_TRACKED = 753;
+ ATOM_NOTIFICATION_LISTENER_SERVICE = 829;
+ ATOM_NAV_HANDLE_TOUCH_POINTS = 869;
+ ATOM_WEAR_ADAPTIVE_SUSPEND_STATS_REPORTED = 619;
+ ATOM_WEAR_POWER_ANOMALY_SERVICE_OPERATIONAL_STATS_REPORTED = 620;
+ ATOM_WEAR_POWER_ANOMALY_SERVICE_EVENT_STATS_REPORTED = 621;
+ ATOM_APEX_INSTALLATION_REQUESTED = 732;
+ ATOM_APEX_INSTALLATION_STAGED = 733;
+ ATOM_APEX_INSTALLATION_ENDED = 734;
ATOM_CREDENTIAL_MANAGER_API_CALLED = 585;
ATOM_CREDENTIAL_MANAGER_INIT_PHASE_REPORTED = 651;
ATOM_CREDENTIAL_MANAGER_CANDIDATE_PHASE_REPORTED = 652;
@@ -2845,12 +3094,7 @@
ATOM_CREDENTIAL_MANAGER_AUTH_CLICK_REPORTED = 670;
ATOM_CREDENTIAL_MANAGER_APIV2_CALLED = 671;
ATOM_UWB_ACTIVITY_INFO = 10188;
- ATOM_MEDIA_ACTION_REPORTED = 608;
- ATOM_MEDIA_CONTROLS_LAUNCHED = 609;
- ATOM_MEDIA_CODEC_RECLAIM_REQUEST_COMPLETED = 600;
- ATOM_MEDIA_CODEC_STARTED = 641;
- ATOM_MEDIA_CODEC_STOPPED = 642;
- ATOM_MEDIA_CODEC_RENDERED = 684;
+ ATOM_DND_STATE_CHANGED = 657;
}
// End of protos/perfetto/config/statsd/atom_ids.proto
@@ -3351,7 +3595,7 @@
// Begin of protos/perfetto/config/data_source_config.proto
// The configuration that is passed to each data source when starting tracing.
-// Next id: 131
+// Next id: 132
message DataSourceConfig {
enum SessionInitiator {
SESSION_INITIATOR_UNSPECIFIED = 0;
@@ -3506,6 +3750,9 @@
// Data source name: android.windowmanager
optional WindowManagerConfig windowmanager_config = 130 [lazy = true];
+ // Data source name: org.chromium.system_metrics
+ optional ChromiumSystemMetricsConfig chromium_system_metrics = 131 [lazy = true];
+
// This is a fallback mechanism to send a free-form text config to the
// producer. In theory this should never be needed. All the code that
// is part of the platform (i.e. traced service) is supposed to *not* truncate
@@ -3943,11 +4190,8 @@
}
optional IncrementalStateConfig incremental_state_config = 21;
- // Additional guardrail used by the Perfetto command line client.
- // On user builds when --dropbox is set perfetto will refuse to trace unless
- // this is also set.
- // Added in Q.
- optional bool allow_user_build_tracing = 19;
+ // No longer needed as we unconditionally allow tracing on user builds.
+ optional bool allow_user_build_tracing = 19 [deprecated = true];
// If set the tracing service will ensure there is at most one tracing session
// with this key.
diff --git a/protos/perfetto/config/profiling/perf_event_config.proto b/protos/perfetto/config/profiling/perf_event_config.proto
index b02aa07..d3cc51c 100644
--- a/protos/perfetto/config/profiling/perf_event_config.proto
+++ b/protos/perfetto/config/profiling/perf_event_config.proto
@@ -217,5 +217,7 @@
UNWIND_SKIP = 1;
// Use libunwindstack (default):
UNWIND_DWARF = 2;
+ // Use userspace frame pointer unwinder:
+ UNWIND_FRAME_POINTER = 3;
}
}
diff --git a/protos/perfetto/config/statsd/atom_ids.proto b/protos/perfetto/config/statsd/atom_ids.proto
index c436571..0d0af24 100644
--- a/protos/perfetto/config/statsd/atom_ids.proto
+++ b/protos/perfetto/config/statsd/atom_ids.proto
@@ -75,11 +75,9 @@
ATOM_LMK_KILL_OCCURRED = 51;
ATOM_PICTURE_IN_PICTURE_STATE_CHANGED = 52;
ATOM_WIFI_MULTICAST_LOCK_STATE_CHANGED = 53;
- ATOM_LMK_STATE_CHANGED = 54;
ATOM_APP_START_MEMORY_STATE_CAPTURED = 55;
ATOM_SHUTDOWN_SEQUENCE_REPORTED = 56;
ATOM_BOOT_SEQUENCE_REPORTED = 57;
- ATOM_DAVEY_OCCURRED = 58;
ATOM_OVERLAY_STATE_CHANGED = 59;
ATOM_FOREGROUND_SERVICE_STATE_CHANGED = 60;
ATOM_CALL_STATE_CHANGED = 61;
@@ -396,7 +394,6 @@
ATOM_PRIVACY_TOGGLE_DIALOG_INTERACTION = 382;
ATOM_APP_SEARCH_OPTIMIZE_STATS_REPORTED = 383;
ATOM_NON_A11Y_TOOL_SERVICE_WARNING_REPORT = 384;
- ATOM_APP_SEARCH_SET_SCHEMA_STATS_REPORTED = 385;
ATOM_APP_COMPAT_STATE_CHANGED = 386;
ATOM_SIZE_COMPAT_RESTART_BUTTON_EVENT_REPORTED = 387;
ATOM_SPLITSCREEN_UI_CHANGED = 388;
@@ -445,8 +442,6 @@
ATOM_HOTWORD_DETECTION_SERVICE_RESTARTED = 432;
ATOM_HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED = 433;
ATOM_HOTWORD_DETECTOR_EVENTS = 434;
- ATOM_AD_SERVICES_API_CALLED = 435;
- ATOM_AD_SERVICES_MESUREMENT_REPORTS_UPLOADED = 436;
ATOM_BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED = 437;
ATOM_CONTACTS_INDEXER_UPDATE_STATS_REPORTED = 440;
ATOM_APP_BACKGROUND_RESTRICTIONS_INFO = 441;
@@ -490,25 +485,14 @@
ATOM_CB_MODULE_ERROR_REPORTED = 480;
ATOM_CB_SERVICE_FEATURE_CHANGED = 481;
ATOM_CB_RECEIVER_FEATURE_CHANGED = 482;
- ATOM_JSSCRIPTENGINE_LATENCY_REPORTED = 483;
ATOM_PRIVACY_SIGNAL_NOTIFICATION_INTERACTION = 484;
ATOM_PRIVACY_SIGNAL_ISSUE_CARD_INTERACTION = 485;
ATOM_PRIVACY_SIGNALS_JOB_FAILURE = 486;
ATOM_VIBRATION_REPORTED = 487;
ATOM_UWB_RANGING_START = 489;
- ATOM_MOBILE_DATA_DOWNLOAD_FILE_GROUP_STATUS_REPORTED = 490;
ATOM_APP_COMPACTED_V2 = 491;
- ATOM_AD_SERVICES_SETTINGS_USAGE_REPORTED = 493;
ATOM_DISPLAY_BRIGHTNESS_CHANGED = 494;
ATOM_ACTIVITY_ACTION_BLOCKED = 495;
- ATOM_BACKGROUND_FETCH_PROCESS_REPORTED = 496;
- ATOM_UPDATE_CUSTOM_AUDIENCE_PROCESS_REPORTED = 497;
- ATOM_RUN_AD_BIDDING_PROCESS_REPORTED = 498;
- ATOM_RUN_AD_SCORING_PROCESS_REPORTED = 499;
- ATOM_RUN_AD_SELECTION_PROCESS_REPORTED = 500;
- ATOM_RUN_AD_BIDDING_PER_CA_PROCESS_REPORTED = 501;
- ATOM_MOBILE_DATA_DOWNLOAD_DOWNLOAD_RESULT_REPORTED = 502;
- ATOM_MOBILE_DATA_DOWNLOAD_FILE_GROUP_STORAGE_STATS_REPORTED = 503;
ATOM_NETWORK_DNS_SERVER_SUPPORT_REPORTED = 504;
ATOM_VM_BOOTED = 505;
ATOM_VM_EXITED = 506;
@@ -517,7 +501,6 @@
ATOM_MEDIAMETRICS_SPATIALIZERDEVICEENABLED_REPORTED = 509;
ATOM_MEDIAMETRICS_HEADTRACKERDEVICEENABLED_REPORTED = 510;
ATOM_MEDIAMETRICS_HEADTRACKERDEVICESUPPORTED_REPORTED = 511;
- ATOM_AD_SERVICES_MEASUREMENT_REGISTRATIONS = 512;
ATOM_HEARING_AID_INFO_REPORTED = 513;
ATOM_DEVICE_WIDE_JOB_CONSTRAINT_CHANGED = 514;
ATOM_AMBIENT_MODE_CHANGED = 515;
@@ -539,9 +522,6 @@
ATOM_BLUETOOTH_LOCAL_SUPPORTED_FEATURES_REPORTED = 532;
ATOM_BLUETOOTH_GATT_APP_INFO = 533;
ATOM_BRIGHTNESS_CONFIGURATION_UPDATED = 534;
- ATOM_AD_SERVICES_GET_TOPICS_REPORTED = 535;
- ATOM_AD_SERVICES_EPOCH_COMPUTATION_GET_TOP_TOPICS_REPORTED = 536;
- ATOM_AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED = 537;
ATOM_WEAR_MEDIA_OUTPUT_SWITCHER_LAUNCHED = 538;
ATOM_WEAR_MEDIA_OUTPUT_SWITCHER_FINISHED = 539;
ATOM_WEAR_MEDIA_OUTPUT_SWITCHER_CONNECTION_REPORTED = 540;
@@ -579,12 +559,10 @@
ATOM_MEDIAMETRICS_MIDI_DEVICE_CLOSE_REPORTED = 576;
ATOM_BIOMETRIC_TOUCH_REPORTED = 577;
ATOM_HOTWORD_AUDIO_EGRESS_EVENT_REPORTED = 578;
- ATOM_APP_SEARCH_SCHEMA_MIGRATION_STATS_REPORTED = 579;
ATOM_LOCATION_ENABLED_STATE_CHANGED = 580;
ATOM_IME_REQUEST_FINISHED = 581;
ATOM_USB_COMPLIANCE_WARNINGS_REPORTED = 582;
ATOM_APP_SUPPORTED_LOCALES_CHANGED = 583;
- ATOM_GRAMMATICAL_INFLECTION_CHANGED = 584;
ATOM_MEDIA_PROVIDER_VOLUME_RECOVERY_REPORTED = 586;
ATOM_BIOMETRIC_PROPERTIES_COLLECTED = 587;
ATOM_KERNEL_WAKEUP_ATTRIBUTED = 588;
@@ -597,7 +575,11 @@
ATOM_WS_NOTIFICATION_UPDATED = 596;
ATOM_NETWORK_VALIDATION_FAILURE_STATS_DAILY_REPORTED = 601;
ATOM_WS_COMPLICATION_TAPPED = 602;
- ATOM_WS_WEAR_TIME_SESSION = 610;
+ ATOM_WS_NOTIFICATION_BLOCKING = 780;
+ ATOM_WS_NOTIFICATION_BRIDGEMODE_UPDATED = 822;
+ ATOM_WS_NOTIFICATION_DISMISSAL_ACTIONED = 823;
+ ATOM_WS_NOTIFICATION_ACTIONED = 824;
+ ATOM_WS_NOTIFICATION_LATENCY = 880;
ATOM_WIFI_BYTES_TRANSFER = 10000;
ATOM_WIFI_BYTES_TRANSFER_BY_FG_BG = 10001;
ATOM_MOBILE_BYTES_TRANSFER = 10002;
@@ -770,6 +752,186 @@
ATOM_NOTIFICATION_MEMORY_USE = 10174;
ATOM_HDR_CAPABILITIES = 10175;
ATOM_WS_FAVOURITE_WATCH_FACE_LIST_SNAPSHOT = 10176;
+ ATOM_WS_WEAR_TIME_SESSION = 610;
+ ATOM_WS_INCOMING_CALL_ACTION_REPORTED = 626;
+ ATOM_WS_CALL_DISCONNECTION_REPORTED = 627;
+ ATOM_WS_CALL_DURATION_REPORTED = 628;
+ ATOM_WS_CALL_USER_EXPERIENCE_LATENCY_REPORTED = 629;
+ ATOM_WS_CALL_INTERACTION_REPORTED = 630;
+ ATOM_WS_ON_BODY_STATE_CHANGED = 787;
+ ATOM_WS_WATCH_FACE_RESTRICTED_COMPLICATIONS_IMPACTED = 802;
+ ATOM_WS_WATCH_FACE_DEFAULT_RESTRICTED_COMPLICATIONS_REMOVED = 803;
+ ATOM_WS_COMPLICATIONS_IMPACTED_NOTIFICATION_EVENT_REPORTED = 804;
+ ATOM_WS_STANDALONE_MODE_SNAPSHOT = 10197;
+ ATOM_WS_FAVORITE_WATCH_FACE_SNAPSHOT = 10206;
+ ATOM_SETTINGS_SPA_REPORTED = 622;
+ ATOM_PDF_LOAD_REPORTED = 859;
+ ATOM_PDF_API_USAGE_REPORTED = 860;
+ ATOM_PDF_SEARCH_REPORTED = 861;
+ ATOM_HDMI_EARC_STATUS_REPORTED = 701;
+ ATOM_HDMI_SOUNDBAR_MODE_STATUS_REPORTED = 724;
+ ATOM_MEDIA_PROVIDER_DATABASE_ROLLBACK_REPORTED = 784;
+ ATOM_BACKUP_SETUP_STATUS_REPORTED = 785;
+ ATOM_PHOTOPICKER_SESSION_INFO_REPORTED = 886;
+ ATOM_PHOTOPICKER_API_INFO_REPORTED = 887;
+ ATOM_PHOTOPICKER_UI_EVENT_LOGGED = 888;
+ ATOM_PHOTOPICKER_MEDIA_ITEM_STATUS_REPORTED = 889;
+ ATOM_PHOTOPICKER_PREVIEW_INFO_LOGGED = 890;
+ ATOM_PHOTOPICKER_MENU_INTERACTION_LOGGED = 891;
+ ATOM_PHOTOPICKER_BANNER_INTERACTION_LOGGED = 892;
+ ATOM_PHOTOPICKER_MEDIA_LIBRARY_INFO_LOGGED = 893;
+ ATOM_PHOTOPICKER_PAGE_INFO_LOGGED = 894;
+ ATOM_PHOTOPICKER_MEDIA_GRID_SYNC_INFO_REPORTED = 895;
+ ATOM_PHOTOPICKER_ALBUM_SYNC_INFO_REPORTED = 896;
+ ATOM_PHOTOPICKER_SEARCH_INFO_REPORTED = 897;
+ ATOM_SEARCH_DATA_EXTRACTION_DETAILS_REPORTED = 898;
+ ATOM_EMBEDDED_PHOTOPICKER_INFO_REPORTED = 899;
+ ATOM_WEAR_POWER_MENU_OPENED = 731;
+ ATOM_WEAR_ASSISTANT_OPENED = 755;
+ ATOM_KERNEL_OOM_KILL_OCCURRED = 754;
+ ATOM_AUTOFILL_UI_EVENT_REPORTED = 603;
+ ATOM_AUTOFILL_FILL_REQUEST_REPORTED = 604;
+ ATOM_AUTOFILL_FILL_RESPONSE_REPORTED = 605;
+ ATOM_AUTOFILL_SAVE_EVENT_REPORTED = 606;
+ ATOM_AUTOFILL_SESSION_COMMITTED = 607;
+ ATOM_AUTOFILL_FIELD_CLASSIFICATION_EVENT_REPORTED = 659;
+ ATOM_TV_LOW_POWER_STANDBY_POLICY = 679;
+ ATOM_EXTERNAL_TV_INPUT_EVENT = 717;
+ ATOM_COMPONENT_STATE_CHANGED_REPORTED = 863;
+ ATOM_AI_WALLPAPERS_BUTTON_PRESSED = 706;
+ ATOM_AI_WALLPAPERS_TEMPLATE_SELECTED = 707;
+ ATOM_AI_WALLPAPERS_TERM_SELECTED = 708;
+ ATOM_AI_WALLPAPERS_WALLPAPER_SET = 709;
+ ATOM_AI_WALLPAPERS_SESSION_SUMMARY = 710;
+ ATOM_APF_SESSION_INFO_REPORTED = 777;
+ ATOM_IP_CLIENT_RA_INFO_REPORTED = 778;
+ ATOM_VPN_CONNECTION_STATE_CHANGED = 850;
+ ATOM_VPN_CONNECTION_REPORTED = 851;
+ ATOM_NETWORK_STATS_RECORDER_FILE_OPERATED = 783;
+ ATOM_DAILY_KEEPALIVE_INFO_REPORTED = 650;
+ ATOM_NETWORK_REQUEST_STATE_CHANGED = 779;
+ ATOM_TETHERING_ACTIVE_SESSIONS_REPORTED = 925;
+ ATOM_ART_DATUM_REPORTED = 332;
+ ATOM_ART_DEVICE_DATUM_REPORTED = 550;
+ ATOM_ART_DATUM_DELTA_REPORTED = 565;
+ ATOM_ART_DEX2OAT_REPORTED = 929;
+ ATOM_ART_DEVICE_STATUS = 10205;
+ ATOM_ODREFRESH_REPORTED = 366;
+ ATOM_ODSIGN_REPORTED = 548;
+ ATOM_BACKGROUND_DEXOPT_JOB_ENDED = 467;
+ ATOM_PREREBOOT_DEXOPT_JOB_ENDED = 883;
+ ATOM_PERMISSION_RATIONALE_DIALOG_VIEWED = 645;
+ ATOM_PERMISSION_RATIONALE_DIALOG_ACTION_REPORTED = 646;
+ ATOM_APP_DATA_SHARING_UPDATES_NOTIFICATION_INTERACTION = 647;
+ ATOM_APP_DATA_SHARING_UPDATES_FRAGMENT_VIEWED = 648;
+ ATOM_APP_DATA_SHARING_UPDATES_FRAGMENT_ACTION_REPORTED = 649;
+ ATOM_ENHANCED_CONFIRMATION_DIALOG_RESULT_REPORTED = 827;
+ ATOM_ENHANCED_CONFIRMATION_RESTRICTION_CLEARED = 828;
+ ATOM_EMERGENCY_STATE_CHANGED = 633;
+ ATOM_CHRE_SIGNIFICANT_MOTION_STATE_CHANGED = 868;
+ ATOM_HEALTH_CONNECT_UI_IMPRESSION = 623;
+ ATOM_HEALTH_CONNECT_UI_INTERACTION = 624;
+ ATOM_HEALTH_CONNECT_APP_OPENED_REPORTED = 625;
+ ATOM_HEALTH_CONNECT_API_CALLED = 616;
+ ATOM_HEALTH_CONNECT_USAGE_STATS = 617;
+ ATOM_HEALTH_CONNECT_STORAGE_STATS = 618;
+ ATOM_HEALTH_CONNECT_API_INVOKED = 643;
+ ATOM_EXERCISE_ROUTE_API_CALLED = 654;
+ ATOM_SELINUX_AUDIT_LOG = 799;
+ ATOM_ONDEVICEPERSONALIZATION_API_CALLED = 711;
+ ATOM_CELLULAR_RADIO_POWER_STATE_CHANGED = 713;
+ ATOM_EMERGENCY_NUMBERS_INFO = 10180;
+ ATOM_DATA_NETWORK_VALIDATION = 10207;
+ ATOM_DATA_RAT_STATE_CHANGED = 854;
+ ATOM_CONNECTED_CHANNEL_CHANGED = 882;
+ ATOM_QUALIFIED_RAT_LIST_CHANGED = 634;
+ ATOM_QNS_IMS_CALL_DROP_STATS = 635;
+ ATOM_QNS_FALLBACK_RESTRICTION_CHANGED = 636;
+ ATOM_QNS_RAT_PREFERENCE_MISMATCH_INFO = 10177;
+ ATOM_QNS_HANDOVER_TIME_MILLIS = 10178;
+ ATOM_QNS_HANDOVER_PINGPONG = 10179;
+ ATOM_SATELLITE_CONTROLLER = 10182;
+ ATOM_SATELLITE_SESSION = 10183;
+ ATOM_SATELLITE_INCOMING_DATAGRAM = 10184;
+ ATOM_SATELLITE_OUTGOING_DATAGRAM = 10185;
+ ATOM_SATELLITE_PROVISION = 10186;
+ ATOM_SATELLITE_SOS_MESSAGE_RECOMMENDER = 10187;
+ ATOM_CARRIER_ROAMING_SATELLITE_SESSION = 10211;
+ ATOM_CARRIER_ROAMING_SATELLITE_CONTROLLER_STATS = 10212;
+ ATOM_CONTROLLER_STATS_PER_PACKAGE = 10213;
+ ATOM_SATELLITE_ENTITLEMENT = 10214;
+ ATOM_SATELLITE_CONFIG_UPDATER = 10215;
+ ATOM_SATELLITE_ACCESS_CONTROLLER = 10219;
+ ATOM_CELLULAR_IDENTIFIER_DISCLOSED = 800;
+ ATOM_KEYBOARD_CONFIGURED = 682;
+ ATOM_KEYBOARD_SYSTEMS_EVENT_REPORTED = 683;
+ ATOM_INPUTDEVICE_USAGE_REPORTED = 686;
+ ATOM_TOUCHPAD_USAGE = 10191;
+ ATOM_THREADNETWORK_TELEMETRY_DATA_REPORTED = 738;
+ ATOM_THREADNETWORK_TOPO_ENTRY_REPEATED = 739;
+ ATOM_THREADNETWORK_DEVICE_INFO_REPORTED = 740;
+ ATOM_CRONET_ENGINE_CREATED = 703;
+ ATOM_CRONET_TRAFFIC_REPORTED = 704;
+ ATOM_CRONET_ENGINE_BUILDER_INITIALIZED = 762;
+ ATOM_CRONET_HTTP_FLAGS_INITIALIZED = 763;
+ ATOM_CRONET_INITIALIZED = 764;
+ ATOM_WEAR_MODE_STATE_CHANGED = 715;
+ ATOM_RENDERER_INITIALIZED = 736;
+ ATOM_SCHEMA_VERSION_RECEIVED = 737;
+ ATOM_LAYOUT_INSPECTED = 741;
+ ATOM_LAYOUT_EXPRESSION_INSPECTED = 742;
+ ATOM_LAYOUT_ANIMATIONS_INSPECTED = 743;
+ ATOM_MATERIAL_COMPONENTS_INSPECTED = 744;
+ ATOM_TILE_REQUESTED = 745;
+ ATOM_STATE_RESPONSE_RECEIVED = 746;
+ ATOM_TILE_RESPONSE_RECEIVED = 747;
+ ATOM_INFLATION_FINISHED = 748;
+ ATOM_INFLATION_FAILED = 749;
+ ATOM_IGNORED_INFLATION_FAILURES_REPORTED = 750;
+ ATOM_DRAWABLE_RENDERED = 751;
+ ATOM_MEDIA_ACTION_REPORTED = 608;
+ ATOM_MEDIA_CONTROLS_LAUNCHED = 609;
+ ATOM_MEDIA_SESSION_STATE_CHANGED = 677;
+ ATOM_WEAR_MEDIA_OUTPUT_SWITCHER_DEVICE_SCAN_API_LATENCY = 757;
+ ATOM_WEAR_MEDIA_OUTPUT_SWITCHER_SASS_DEVICE_UNAVAILABLE = 758;
+ ATOM_WEAR_MEDIA_OUTPUT_SWITCHER_FASTPAIR_API_TIMEOUT = 759;
+ ATOM_MEDIATOR_UPDATED = 721;
+ ATOM_SYSPROXY_BLUETOOTH_BYTES_TRANSFER = 10196;
+ ATOM_SYSPROXY_CONNECTION_UPDATED = 786;
+ ATOM_ADAPTIVE_AUTH_UNLOCK_AFTER_LOCK_REPORTED = 820;
+ ATOM_FEDERATED_COMPUTE_API_CALLED = 712;
+ ATOM_FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED = 771;
+ ATOM_EXAMPLE_ITERATOR_NEXT_LATENCY_REPORTED = 838;
+ ATOM_RKPD_POOL_STATS = 664;
+ ATOM_RKPD_CLIENT_OPERATION = 665;
+ ATOM_CPU_POLICY = 10199;
+ ATOM_ATOM_9999 = 9999;
+ ATOM_ATOM_99999 = 99999;
+ ATOM_SCREEN_OFF_REPORTED = 776;
+ ATOM_SCREEN_TIMEOUT_OVERRIDE_REPORTED = 836;
+ ATOM_SCREEN_INTERACTIVE_SESSION_REPORTED = 837;
+ ATOM_SCREEN_DIM_REPORTED = 867;
+ ATOM_FULL_SCREEN_INTENT_LAUNCHED = 631;
+ ATOM_BAL_ALLOWED = 632;
+ ATOM_IN_TASK_ACTIVITY_STARTED = 685;
+ ATOM_CACHED_APPS_HIGH_WATERMARK = 10189;
+ ATOM_STYLUS_PREDICTION_METRICS_REPORTED = 718;
+ ATOM_USER_RISK_EVENT_REPORTED = 725;
+ ATOM_MEDIA_PROJECTION_STATE_CHANGED = 729;
+ ATOM_MEDIA_PROJECTION_TARGET_CHANGED = 730;
+ ATOM_EXCESSIVE_BINDER_PROXY_COUNT_REPORTED = 853;
+ ATOM_PROXY_BYTES_TRANSFER_BY_FG_BG = 10200;
+ ATOM_MOBILE_BYTES_TRANSFER_BY_PROC_STATE = 10204;
+ ATOM_BIOMETRIC_FRR_NOTIFICATION = 817;
+ ATOM_SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION = 830;
+ ATOM_SENSITIVE_NOTIFICATION_APP_PROTECTION_SESSION = 831;
+ ATOM_SENSITIVE_NOTIFICATION_APP_PROTECTION_APPLIED = 832;
+ ATOM_SENSITIVE_NOTIFICATION_REDACTION = 833;
+ ATOM_SENSITIVE_CONTENT_APP_PROTECTION = 835;
+ ATOM_APP_RESTRICTION_STATE_CHANGED = 866;
+ ATOM_DREAM_SETTING_CHANGED = 705;
+ ATOM_DREAM_SETTING_SNAPSHOT = 10192;
+ ATOM_BOOT_INTEGRITY_INFO_REPORTED = 775;
ATOM_WIFI_AWARE_NDP_REPORTED = 638;
ATOM_WIFI_AWARE_ATTACH_REPORTED = 639;
ATOM_WIFI_SELF_RECOVERY_TRIGGERED = 661;
@@ -784,39 +946,99 @@
ATOM_WIFI_LOCAL_ONLY_REQUEST_SCAN_TRIGGERED = 693;
ATOM_WIFI_THREAD_TASK_EXECUTED = 694;
ATOM_WIFI_STATE_CHANGED = 700;
+ ATOM_PNO_SCAN_STARTED = 719;
+ ATOM_PNO_SCAN_STOPPED = 720;
+ ATOM_WIFI_IS_UNUSABLE_REPORTED = 722;
+ ATOM_WIFI_AP_CAPABILITIES_REPORTED = 723;
+ ATOM_SOFT_AP_STATE_CHANGED = 805;
+ ATOM_SCORER_PREDICTION_RESULT_REPORTED = 884;
ATOM_WIFI_AWARE_CAPABILITIES = 10190;
ATOM_WIFI_MODULE_INFO = 10193;
- ATOM_SETTINGS_SPA_REPORTED = 622;
+ ATOM_WIFI_SETTING_INFO = 10194;
+ ATOM_WIFI_COMPLEX_SETTING_INFO = 10195;
+ ATOM_WIFI_CONFIGURED_NETWORK_INFO = 10198;
+ ATOM_MTE_STATE = 10181;
+ ATOM_HOTWORD_EGRESS_SIZE_ATOM_REPORTED = 761;
+ ATOM_SANDBOX_API_CALLED = 488;
+ ATOM_SANDBOX_ACTIVITY_EVENT_OCCURRED = 735;
+ ATOM_SDK_SANDBOX_RESTRICTED_ACCESS_IN_SESSION = 796;
+ ATOM_SANDBOX_SDK_STORAGE = 10159;
ATOM_EXPRESS_EVENT_REPORTED = 528;
ATOM_EXPRESS_HISTOGRAM_SAMPLE_REPORTED = 593;
ATOM_EXPRESS_UID_EVENT_REPORTED = 644;
ATOM_EXPRESS_UID_HISTOGRAM_SAMPLE_REPORTED = 658;
- ATOM_PERMISSION_RATIONALE_DIALOG_VIEWED = 645;
- ATOM_PERMISSION_RATIONALE_DIALOG_ACTION_REPORTED = 646;
- ATOM_APP_DATA_SHARING_UPDATES_NOTIFICATION_INTERACTION = 647;
- ATOM_APP_DATA_SHARING_UPDATES_FRAGMENT_VIEWED = 648;
- ATOM_APP_DATA_SHARING_UPDATES_FRAGMENT_ACTION_REPORTED = 649;
- ATOM_WS_INCOMING_CALL_ACTION_REPORTED = 626;
- ATOM_WS_CALL_DISCONNECTION_REPORTED = 627;
- ATOM_WS_CALL_DURATION_REPORTED = 628;
- ATOM_WS_CALL_USER_EXPERIENCE_LATENCY_REPORTED = 629;
- ATOM_WS_CALL_INTERACTION_REPORTED = 630;
- ATOM_FULL_SCREEN_INTENT_LAUNCHED = 631;
- ATOM_BAL_ALLOWED = 632;
- ATOM_IN_TASK_ACTIVITY_STARTED = 685;
- ATOM_CACHED_APPS_HIGH_WATERMARK = 10189;
- ATOM_ODREFRESH_REPORTED = 366;
- ATOM_ODSIGN_REPORTED = 548;
- ATOM_ART_DATUM_REPORTED = 332;
- ATOM_ART_DEVICE_DATUM_REPORTED = 550;
- ATOM_ART_DATUM_DELTA_REPORTED = 565;
- ATOM_BACKGROUND_DEXOPT_JOB_ENDED = 467;
- ATOM_WEAR_ADAPTIVE_SUSPEND_STATS_REPORTED = 619;
- ATOM_WEAR_POWER_ANOMALY_SERVICE_OPERATIONAL_STATS_REPORTED = 620;
- ATOM_WEAR_POWER_ANOMALY_SERVICE_EVENT_STATS_REPORTED = 621;
- ATOM_EMERGENCY_STATE_CHANGED = 633;
- ATOM_DND_STATE_CHANGED = 657;
- ATOM_MTE_STATE = 10181;
+ ATOM_IKE_SESSION_TERMINATED = 678;
+ ATOM_IKE_LIVENESS_CHECK_SESSION_VALIDATED = 760;
+ ATOM_NEGOTIATED_SECURITY_ASSOCIATION = 821;
+ ATOM_APP_SEARCH_SET_SCHEMA_STATS_REPORTED = 385;
+ ATOM_APP_SEARCH_SCHEMA_MIGRATION_STATS_REPORTED = 579;
+ ATOM_APP_SEARCH_USAGE_SEARCH_INTENT_STATS_REPORTED = 825;
+ ATOM_APP_SEARCH_USAGE_SEARCH_INTENT_RAW_QUERY_STATS_REPORTED = 826;
+ ATOM_DEVICE_POLICY_MANAGEMENT_MODE = 10216;
+ ATOM_DEVICE_POLICY_STATE = 10217;
+ ATOM_DESKTOP_MODE_UI_CHANGED = 818;
+ ATOM_DESKTOP_MODE_SESSION_TASK_UPDATE = 819;
+ ATOM_MEDIA_CODEC_RECLAIM_REQUEST_COMPLETED = 600;
+ ATOM_MEDIA_CODEC_STARTED = 641;
+ ATOM_MEDIA_CODEC_STOPPED = 642;
+ ATOM_MEDIA_CODEC_RENDERED = 684;
+ ATOM_MEDIA_EDITING_ENDED_REPORTED = 798;
+ ATOM_CAR_WAKEUP_FROM_SUSPEND_REPORTED = 852;
+ ATOM_PLUGIN_INITIALIZED = 655;
+ ATOM_CAR_RECENTS_EVENT_REPORTED = 770;
+ ATOM_CAR_CALM_MODE_EVENT_REPORTED = 797;
+ ATOM_CAMERA_FEATURE_COMBINATION_QUERY_EVENT = 900;
+ ATOM_THERMAL_STATUS_CALLED = 772;
+ ATOM_THERMAL_HEADROOM_CALLED = 773;
+ ATOM_THERMAL_HEADROOM_THRESHOLDS_CALLED = 774;
+ ATOM_ADPF_HINT_SESSION_TID_CLEANUP = 839;
+ ATOM_THERMAL_HEADROOM_THRESHOLDS = 10201;
+ ATOM_ADPF_SESSION_SNAPSHOT = 10218;
+ ATOM_BLUETOOTH_HASHED_DEVICE_NAME_REPORTED = 613;
+ ATOM_BLUETOOTH_L2CAP_COC_CLIENT_CONNECTION = 614;
+ ATOM_BLUETOOTH_L2CAP_COC_SERVER_CONNECTION = 615;
+ ATOM_BLUETOOTH_LE_SESSION_CONNECTED = 656;
+ ATOM_RESTRICTED_BLUETOOTH_DEVICE_NAME_REPORTED = 666;
+ ATOM_BLUETOOTH_PROFILE_CONNECTION_ATTEMPTED = 696;
+ ATOM_BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED = 781;
+ ATOM_BLUETOOTH_RFCOMM_CONNECTION_ATTEMPTED = 782;
+ ATOM_REMOTE_DEVICE_INFORMATION_WITH_METRIC_ID = 862;
+ ATOM_LE_APP_SCAN_STATE_CHANGED = 870;
+ ATOM_LE_RADIO_SCAN_STOPPED = 871;
+ ATOM_LE_SCAN_RESULT_RECEIVED = 872;
+ ATOM_LE_SCAN_ABUSED = 873;
+ ATOM_LE_ADV_STATE_CHANGED = 874;
+ ATOM_LE_ADV_ERROR_REPORTED = 875;
+ ATOM_A2DP_SESSION_REPORTED = 904;
+ ATOM_BLUETOOTH_CROSS_LAYER_EVENT_REPORTED = 916;
+ ATOM_BROADCAST_AUDIO_SESSION_REPORTED = 927;
+ ATOM_BROADCAST_AUDIO_SYNC_REPORTED = 928;
+ ATOM_DEVICE_LOCK_CHECK_IN_REQUEST_REPORTED = 726;
+ ATOM_DEVICE_LOCK_PROVISIONING_COMPLETE_REPORTED = 727;
+ ATOM_DEVICE_LOCK_KIOSK_APP_REQUEST_REPORTED = 728;
+ ATOM_DEVICE_LOCK_CHECK_IN_RETRY_REPORTED = 789;
+ ATOM_DEVICE_LOCK_PROVISION_FAILURE_REPORTED = 790;
+ ATOM_DEVICE_LOCK_LOCK_UNLOCK_DEVICE_FAILURE_REPORTED = 791;
+ ATOM_APPLICATION_GRAMMATICAL_INFLECTION_CHANGED = 584;
+ ATOM_SYSTEM_GRAMMATICAL_INFLECTION_CHANGED = 816;
+ ATOM_EMERGENCY_NUMBER_DIALED = 637;
+ ATOM_JSSCRIPTENGINE_LATENCY_REPORTED = 483;
+ ATOM_AD_SERVICES_API_CALLED = 435;
+ ATOM_AD_SERVICES_MESUREMENT_REPORTS_UPLOADED = 436;
+ ATOM_MOBILE_DATA_DOWNLOAD_FILE_GROUP_STATUS_REPORTED = 490;
+ ATOM_MOBILE_DATA_DOWNLOAD_DOWNLOAD_RESULT_REPORTED = 502;
+ ATOM_AD_SERVICES_SETTINGS_USAGE_REPORTED = 493;
+ ATOM_BACKGROUND_FETCH_PROCESS_REPORTED = 496;
+ ATOM_UPDATE_CUSTOM_AUDIENCE_PROCESS_REPORTED = 497;
+ ATOM_RUN_AD_BIDDING_PROCESS_REPORTED = 498;
+ ATOM_RUN_AD_SCORING_PROCESS_REPORTED = 499;
+ ATOM_RUN_AD_SELECTION_PROCESS_REPORTED = 500;
+ ATOM_RUN_AD_BIDDING_PER_CA_PROCESS_REPORTED = 501;
+ ATOM_MOBILE_DATA_DOWNLOAD_FILE_GROUP_STORAGE_STATS_REPORTED = 503;
+ ATOM_AD_SERVICES_MEASUREMENT_REGISTRATIONS = 512;
+ ATOM_AD_SERVICES_GET_TOPICS_REPORTED = 535;
+ ATOM_AD_SERVICES_EPOCH_COMPUTATION_GET_TOP_TOPICS_REPORTED = 536;
+ ATOM_AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED = 537;
ATOM_AD_SERVICES_BACK_COMPAT_GET_TOPICS_REPORTED = 598;
ATOM_AD_SERVICES_BACK_COMPAT_EPOCH_COMPUTATION_CLASSIFIER_REPORTED = 599;
ATOM_AD_SERVICES_MEASUREMENT_DEBUG_KEYS = 640;
@@ -826,68 +1048,68 @@
ATOM_AD_SERVICES_MEASUREMENT_ATTRIBUTION = 674;
ATOM_AD_SERVICES_MEASUREMENT_JOBS = 675;
ATOM_AD_SERVICES_MEASUREMENT_WIPEOUT = 676;
+ ATOM_AD_SERVICES_MEASUREMENT_AD_ID_MATCH_FOR_DEBUG_KEYS = 695;
+ ATOM_AD_SERVICES_ENROLLMENT_DATA_STORED = 697;
+ ATOM_AD_SERVICES_ENROLLMENT_FILE_DOWNLOADED = 698;
+ ATOM_AD_SERVICES_ENROLLMENT_MATCHED = 699;
ATOM_AD_SERVICES_CONSENT_MIGRATED = 702;
- ATOM_RKPD_POOL_STATS = 664;
- ATOM_RKPD_CLIENT_OPERATION = 665;
- ATOM_AUTOFILL_UI_EVENT_REPORTED = 603;
- ATOM_AUTOFILL_FILL_REQUEST_REPORTED = 604;
- ATOM_AUTOFILL_FILL_RESPONSE_REPORTED = 605;
- ATOM_AUTOFILL_SAVE_EVENT_REPORTED = 606;
- ATOM_AUTOFILL_SESSION_COMMITTED = 607;
- ATOM_AUTOFILL_FIELD_CLASSIFICATION_EVENT_REPORTED = 659;
+ ATOM_AD_SERVICES_ENROLLMENT_FAILED = 714;
+ ATOM_AD_SERVICES_MEASUREMENT_CLICK_VERIFICATION = 756;
+ ATOM_AD_SERVICES_ENCRYPTION_KEY_FETCHED = 765;
+ ATOM_AD_SERVICES_ENCRYPTION_KEY_DB_TRANSACTION_ENDED = 766;
+ ATOM_DESTINATION_REGISTERED_BEACONS = 767;
+ ATOM_REPORT_INTERACTION_API_CALLED = 768;
+ ATOM_INTERACTION_REPORTING_TABLE_CLEARED = 769;
+ ATOM_APP_MANIFEST_CONFIG_HELPER_CALLED = 788;
+ ATOM_AD_FILTERING_PROCESS_JOIN_CA_REPORTED = 793;
+ ATOM_AD_FILTERING_PROCESS_AD_SELECTION_REPORTED = 794;
+ ATOM_AD_COUNTER_HISTOGRAM_UPDATER_REPORTED = 795;
+ ATOM_SIGNATURE_VERIFICATION = 807;
+ ATOM_K_ANON_IMMEDIATE_SIGN_JOIN_STATUS_REPORTED = 808;
+ ATOM_K_ANON_BACKGROUND_JOB_STATUS_REPORTED = 809;
+ ATOM_K_ANON_INITIALIZE_STATUS_REPORTED = 810;
+ ATOM_K_ANON_SIGN_STATUS_REPORTED = 811;
+ ATOM_K_ANON_JOIN_STATUS_REPORTED = 812;
+ ATOM_K_ANON_KEY_ATTESTATION_STATUS_REPORTED = 813;
+ ATOM_GET_AD_SELECTION_DATA_API_CALLED = 814;
+ ATOM_GET_AD_SELECTION_DATA_BUYER_INPUT_GENERATED = 815;
+ ATOM_BACKGROUND_JOB_SCHEDULING_REPORTED = 834;
+ ATOM_TOPICS_ENCRYPTION_EPOCH_COMPUTATION_REPORTED = 840;
+ ATOM_TOPICS_ENCRYPTION_GET_TOPICS_REPORTED = 841;
+ ATOM_ADSERVICES_SHELL_COMMAND_CALLED = 842;
+ ATOM_UPDATE_SIGNALS_API_CALLED = 843;
+ ATOM_ENCODING_JOB_RUN = 844;
+ ATOM_ENCODING_JS_FETCH = 845;
+ ATOM_ENCODING_JS_EXECUTION = 846;
+ ATOM_PERSIST_AD_SELECTION_RESULT_CALLED = 847;
+ ATOM_SERVER_AUCTION_KEY_FETCH_CALLED = 848;
+ ATOM_SERVER_AUCTION_BACKGROUND_KEY_FETCH_ENABLED = 849;
+ ATOM_AD_SERVICES_MEASUREMENT_PROCESS_ODP_REGISTRATION = 864;
+ ATOM_AD_SERVICES_MEASUREMENT_NOTIFY_REGISTRATION_TO_ODP = 865;
+ ATOM_SELECT_ADS_FROM_OUTCOMES_API_CALLED = 876;
+ ATOM_REPORT_IMPRESSION_API_CALLED = 877;
+ ATOM_AD_SERVICES_ENROLLMENT_TRANSACTION_STATS = 885;
+ ATOM_EXTERNAL_DISPLAY_STATE_CHANGED = 806;
+ ATOM_DISPLAY_MODE_DIRECTOR_VOTE_CHANGED = 792;
ATOM_TEST_EXTENSION_ATOM_REPORTED = 660;
ATOM_TEST_RESTRICTED_ATOM_REPORTED = 672;
ATOM_STATS_SOCKET_LOSS_REPORTED = 752;
- ATOM_PLUGIN_INITIALIZED = 655;
- ATOM_TV_LOW_POWER_STANDBY_POLICY = 679;
+ ATOM_NFC_OBSERVE_MODE_STATE_CHANGED = 855;
+ ATOM_NFC_FIELD_CHANGED = 856;
+ ATOM_NFC_POLLING_LOOP_NOTIFICATION_REPORTED = 857;
+ ATOM_NFC_PROPRIETARY_CAPABILITIES_REPORTED = 858;
ATOM_LOCKSCREEN_SHORTCUT_SELECTED = 611;
ATOM_LOCKSCREEN_SHORTCUT_TRIGGERED = 612;
- ATOM_EMERGENCY_NUMBERS_INFO = 10180;
- ATOM_QUALIFIED_RAT_LIST_CHANGED = 634;
- ATOM_QNS_IMS_CALL_DROP_STATS = 635;
- ATOM_QNS_FALLBACK_RESTRICTION_CHANGED = 636;
- ATOM_QNS_RAT_PREFERENCE_MISMATCH_INFO = 10177;
- ATOM_QNS_HANDOVER_TIME_MILLIS = 10178;
- ATOM_QNS_HANDOVER_PINGPONG = 10179;
- ATOM_SATELLITE_CONTROLLER = 10182;
- ATOM_SATELLITE_SESSION = 10183;
- ATOM_SATELLITE_INCOMING_DATAGRAM = 10184;
- ATOM_SATELLITE_OUTGOING_DATAGRAM = 10185;
- ATOM_SATELLITE_PROVISION = 10186;
- ATOM_SATELLITE_SOS_MESSAGE_RECOMMENDER = 10187;
- ATOM_IKE_SESSION_TERMINATED = 678;
- ATOM_IKE_LIVENESS_CHECK_SESSION_VALIDATED = 760;
- ATOM_BLUETOOTH_HASHED_DEVICE_NAME_REPORTED = 613;
- ATOM_BLUETOOTH_L2CAP_COC_CLIENT_CONNECTION = 614;
- ATOM_BLUETOOTH_L2CAP_COC_SERVER_CONNECTION = 615;
- ATOM_BLUETOOTH_LE_SESSION_CONNECTED = 656;
- ATOM_RESTRICTED_BLUETOOTH_DEVICE_NAME_REPORTED = 666;
- ATOM_BLUETOOTH_PROFILE_CONNECTION_ATTEMPTED = 696;
- ATOM_HEALTH_CONNECT_UI_IMPRESSION = 623;
- ATOM_HEALTH_CONNECT_UI_INTERACTION = 624;
- ATOM_HEALTH_CONNECT_APP_OPENED_REPORTED = 625;
- ATOM_HEALTH_CONNECT_API_CALLED = 616;
- ATOM_HEALTH_CONNECT_USAGE_STATS = 617;
- ATOM_HEALTH_CONNECT_STORAGE_STATS = 618;
- ATOM_HEALTH_CONNECT_API_INVOKED = 643;
- ATOM_EXERCISE_ROUTE_API_CALLED = 654;
- ATOM_ATOM_9999 = 9999;
- ATOM_ATOM_99999 = 99999;
- ATOM_THREADNETWORK_TELEMETRY_DATA_REPORTED = 738;
- ATOM_THREADNETWORK_TOPO_ENTRY_REPEATED = 739;
- ATOM_THREADNETWORK_DEVICE_INFO_REPORTED = 740;
- ATOM_EMERGENCY_NUMBER_DIALED = 637;
- ATOM_SANDBOX_API_CALLED = 488;
- ATOM_SANDBOX_ACTIVITY_EVENT_OCCURRED = 735;
- ATOM_SANDBOX_SDK_STORAGE = 10159;
- ATOM_CRONET_ENGINE_CREATED = 703;
- ATOM_CRONET_TRAFFIC_REPORTED = 704;
- ATOM_CRONET_ENGINE_BUILDER_INITIALIZED = 762;
- ATOM_CRONET_HTTP_FLAGS_INITIALIZED = 763;
- ATOM_CRONET_INITIALIZED = 764;
- ATOM_DAILY_KEEPALIVE_INFO_REPORTED = 650;
- ATOM_IP_CLIENT_RA_INFO_REPORTED = 778;
- ATOM_APF_SESSION_INFO_REPORTED = 777;
+ ATOM_LAUNCHER_IMPRESSION_EVENT_V2 = 716;
+ ATOM_DISPLAY_SWITCH_LATENCY_TRACKED = 753;
+ ATOM_NOTIFICATION_LISTENER_SERVICE = 829;
+ ATOM_NAV_HANDLE_TOUCH_POINTS = 869;
+ ATOM_WEAR_ADAPTIVE_SUSPEND_STATS_REPORTED = 619;
+ ATOM_WEAR_POWER_ANOMALY_SERVICE_OPERATIONAL_STATS_REPORTED = 620;
+ ATOM_WEAR_POWER_ANOMALY_SERVICE_EVENT_STATS_REPORTED = 621;
+ ATOM_APEX_INSTALLATION_REQUESTED = 732;
+ ATOM_APEX_INSTALLATION_STAGED = 733;
+ ATOM_APEX_INSTALLATION_ENDED = 734;
ATOM_CREDENTIAL_MANAGER_API_CALLED = 585;
ATOM_CREDENTIAL_MANAGER_INIT_PHASE_REPORTED = 651;
ATOM_CREDENTIAL_MANAGER_CANDIDATE_PHASE_REPORTED = 652;
@@ -898,10 +1120,5 @@
ATOM_CREDENTIAL_MANAGER_AUTH_CLICK_REPORTED = 670;
ATOM_CREDENTIAL_MANAGER_APIV2_CALLED = 671;
ATOM_UWB_ACTIVITY_INFO = 10188;
- ATOM_MEDIA_ACTION_REPORTED = 608;
- ATOM_MEDIA_CONTROLS_LAUNCHED = 609;
- ATOM_MEDIA_CODEC_RECLAIM_REQUEST_COMPLETED = 600;
- ATOM_MEDIA_CODEC_STARTED = 641;
- ATOM_MEDIA_CODEC_STOPPED = 642;
- ATOM_MEDIA_CODEC_RENDERED = 684;
+ ATOM_DND_STATE_CHANGED = 657;
}
\ No newline at end of file
diff --git a/protos/perfetto/config/trace_config.proto b/protos/perfetto/config/trace_config.proto
index 8fe4424..255ae7e 100644
--- a/protos/perfetto/config/trace_config.proto
+++ b/protos/perfetto/config/trace_config.proto
@@ -438,11 +438,8 @@
}
optional IncrementalStateConfig incremental_state_config = 21;
- // Additional guardrail used by the Perfetto command line client.
- // On user builds when --dropbox is set perfetto will refuse to trace unless
- // this is also set.
- // Added in Q.
- optional bool allow_user_build_tracing = 19;
+ // No longer needed as we unconditionally allow tracing on user builds.
+ optional bool allow_user_build_tracing = 19 [deprecated = true];
// If set the tracing service will ensure there is at most one tracing session
// with this key.
diff --git a/protos/perfetto/ipc/BUILD.gn b/protos/perfetto/ipc/BUILD.gn
index 6d7eccb..a2d3fac 100644
--- a/protos/perfetto/ipc/BUILD.gn
+++ b/protos/perfetto/ipc/BUILD.gn
@@ -21,7 +21,6 @@
proto_generators = [
"ipc",
"cpp",
- "source_set",
]
sources = [
"consumer_port.proto",
@@ -41,7 +40,6 @@
proto_generators = [
"zero",
"cpp",
- "source_set",
]
sources = [ "wire_protocol.proto" ]
}
diff --git a/protos/perfetto/ipc/consumer_port.proto b/protos/perfetto/ipc/consumer_port.proto
index d7a654c..a38e0eb 100644
--- a/protos/perfetto/ipc/consumer_port.proto
+++ b/protos/perfetto/ipc/consumer_port.proto
@@ -287,9 +287,15 @@
// Arguments for rpc CloneSession.
message CloneSessionRequest {
- // The session ID to clone. If session_id == kBugreportSessionId (0xff...ff)
- // the session with the highest bugreport score is cloned (if any exists).
- optional uint64 session_id = 1;
+ oneof selector {
+ // The session ID to clone. If session_id == kBugreportSessionId (0xff...ff)
+ // the session with the highest bugreport score is cloned (if any exists).
+ uint64 session_id = 1;
+
+ // The unique_session_name of the tracing session to clone. Tracing sessions
+ // that are clones of other tracing sessions are ignored.
+ string unique_session_name = 4;
+ }
// If set, the trace filter will not have effect on the cloned session.
// Used for bugreports.
diff --git a/protos/perfetto/metrics/BUILD.gn b/protos/perfetto/metrics/BUILD.gn
index e09200c..d90ef8c 100644
--- a/protos/perfetto/metrics/BUILD.gn
+++ b/protos/perfetto/metrics/BUILD.gn
@@ -16,26 +16,15 @@
import("../../../gn/proto_library.gni")
perfetto_proto_library("@TYPE@") {
- proto_generators = [
- "lite",
- "source_set",
- ]
+ proto_generators = [ "lite" ]
deps = [ "android:@TYPE@" ]
sources = [ "metrics.proto" ]
+ generate_descriptor = "metrics.descriptor"
+ descriptor_root_source = "metrics.proto"
}
perfetto_proto_library("custom_options_@TYPE@") {
- proto_generators = [
- "lite",
- "source_set",
- ]
+ proto_generators = [ "lite" ]
sources = [ "custom_options.proto" ]
import_dirs = [ "${perfetto_protobuf_src_dir}" ]
}
-
-perfetto_proto_library("descriptor") {
- proto_generators = [ "descriptor" ]
- generate_descriptor = "metrics.descriptor"
- deps = [ ":source_set" ]
- sources = [ "metrics.proto" ]
-}
diff --git a/protos/perfetto/metrics/android/BUILD.gn b/protos/perfetto/metrics/android/BUILD.gn
index fc3cfb4..ba0f60f 100644
--- a/protos/perfetto/metrics/android/BUILD.gn
+++ b/protos/perfetto/metrics/android/BUILD.gn
@@ -15,10 +15,7 @@
import("../../../../gn/proto_library.gni")
perfetto_proto_library("@TYPE@") {
- proto_generators = [
- "lite",
- "source_set",
- ]
+ proto_generators = [ "lite" ]
sources = [
"ad_services_metric.proto",
"android_anomaly_metric.proto",
@@ -32,7 +29,6 @@
"android_garbage_collection_unagg_metric.proto",
"android_oom_adjuster_metric.proto",
"android_sysui_notifications_blocking_calls_metric.proto",
- "android_trusty_workqueues.proto",
"anr_metric.proto",
"app_process_starts_metric.proto",
"auto_metric.proto",
@@ -66,7 +62,6 @@
"monitor_contention_metric.proto",
"multiuser_metric.proto",
"network_metric.proto",
- "other_traces.proto",
"package_list.proto",
"powrails_metric.proto",
"process_metadata.proto",
diff --git a/protos/perfetto/metrics/android/batt_metric.proto b/protos/perfetto/metrics/android/batt_metric.proto
index a06a2d4..792dc0d 100644
--- a/protos/perfetto/metrics/android/batt_metric.proto
+++ b/protos/perfetto/metrics/android/batt_metric.proto
@@ -44,6 +44,8 @@
optional int64 sleep_screen_doze_ns = 8;
// Average power over the duration of the trace.
optional double avg_power_mw = 9;
+ // Energy usage estimate in joules.
+ optional double energy_usage_estimate = 10;
}
// Period of time during the trace that the device went to sleep completely.
diff --git a/protos/perfetto/metrics/android/codec_metrics.proto b/protos/perfetto/metrics/android/codec_metrics.proto
index 2f8b94d..b95dbe6 100644
--- a/protos/perfetto/metrics/android/codec_metrics.proto
+++ b/protos/perfetto/metrics/android/codec_metrics.proto
@@ -34,6 +34,8 @@
optional int64 total_cpu_ns = 2;
// CPU time ( time 'Running' on cpu)
optional int64 running_cpu_ns = 3;
+ // CPU cycles
+ optional int64 cpu_cycles = 4;
}
// These are traces and could indicate framework queue latency
@@ -67,7 +69,34 @@
repeated AndroidCpuMetric.CoreTypeData core_data = 5;
}
+ // Rail details
+ message Rail {
+ // name of rail
+ optional string name = 1;
+ // energy and power details of this rail
+ message Info {
+ // energy from this rail for codec use
+ optional double energy = 1;
+ // power consumption in this rail for codec use
+ optional double power_mw = 2;
+ }
+ optional Info info = 2;
+ }
+
+ // have the energy usage for the codec running time
+ message Energy {
+ // total energy taken by the system during this time
+ optional double total_energy = 1;
+ // total time for this energy is calculated
+ optional int64 duration = 2;
+ // for this session
+ optional double power_mw = 3;
+ // enery breakdown by subsystem
+ repeated Rail rail = 4;
+ }
+
repeated CpuUsage cpu_usage = 1;
repeated CodecFunction codec_function = 2;
+ optional Energy energy = 3;
}
diff --git a/protos/perfetto/metrics/android/startup_metric.proto b/protos/perfetto/metrics/android/startup_metric.proto
index 2c8fcd8..86c206c 100644
--- a/protos/perfetto/metrics/android/startup_metric.proto
+++ b/protos/perfetto/metrics/android/startup_metric.proto
@@ -47,7 +47,7 @@
// Timing information spanning the intent received by the
// activity manager to the first frame drawn.
- // Next id: 36.
+ // Next id: 38
message ToFirstFrame {
// The duration between the intent received and first frame.
optional int64 dur_ns = 1;
@@ -96,6 +96,8 @@
optional Slice time_post_fork = 16;
+ // Total time on class initialization during app startup.
+ optional Slice time_class_initialization = 36;
// The total time spent on opening dex files.
optional Slice time_dex_open = 24;
// Total time spent verifying classes during app startup.
@@ -104,6 +106,9 @@
// Number of methods that were compiled by JIT during app startup.
optional uint32 jit_compiled_methods = 27;
+ // Number of class initializations during app startup.
+ optional uint32 class_initialization_count = 37;
+
// Time spent running CPU on jit thread pool.
optional Slice time_jit_thread_pool_on_cpu = 28;
@@ -298,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;
@@ -344,6 +349,17 @@
optional uint32 slice_id = 3;
optional string slice_name = 4;
+
+ optional uint32 process_pid = 5;
+
+ 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.
@@ -356,6 +372,17 @@
optional uint32 thread_utid = 3;
optional string thread_name = 4;
+
+ optional uint32 process_pid = 5;
+
+ 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
diff --git a/protos/perfetto/metrics/android/wattson_in_time_period.proto b/protos/perfetto/metrics/android/wattson_in_time_period.proto
index 9691b66..495de8c 100644
--- a/protos/perfetto/metrics/android/wattson_in_time_period.proto
+++ b/protos/perfetto/metrics/android/wattson_in_time_period.proto
@@ -19,8 +19,13 @@
package perfetto.protos;
message AndroidWattsonTimePeriodMetric {
+ // Each version increment means updated structure format or field
optional int32 metric_version = 1;
- repeated AndroidWattsonEstimateInfo period_info = 2;
+ // Each version increment means power model has been updated and estimates
+ // might change for the exact same input. Don't compare estimates across
+ // different power model versions.
+ optional int32 power_model_version = 2;
+ repeated AndroidWattsonEstimateInfo period_info = 3;
}
message AndroidWattsonEstimateInfo {
@@ -32,33 +37,37 @@
message AndroidWattsonCpuSubsystemEstimate {
// estimates and estimates of subrails
optional float estimated_mw = 1;
- optional AndroidWattsonPolicyEstimate policy0 = 2;
- optional AndroidWattsonPolicyEstimate policy1 = 3;
- optional AndroidWattsonPolicyEstimate policy2 = 4;
- optional AndroidWattsonPolicyEstimate policy3 = 5;
- optional AndroidWattsonPolicyEstimate policy4 = 6;
- optional AndroidWattsonPolicyEstimate policy5 = 7;
- optional AndroidWattsonPolicyEstimate policy6 = 8;
- optional AndroidWattsonPolicyEstimate policy7 = 9;
- optional AndroidWattsonDsuScuEstimate dsu_scu = 10;
+ optional float estimated_mws = 2;
+ optional AndroidWattsonPolicyEstimate policy0 = 3;
+ optional AndroidWattsonPolicyEstimate policy1 = 4;
+ optional AndroidWattsonPolicyEstimate policy2 = 5;
+ optional AndroidWattsonPolicyEstimate policy3 = 6;
+ optional AndroidWattsonPolicyEstimate policy4 = 7;
+ optional AndroidWattsonPolicyEstimate policy5 = 8;
+ optional AndroidWattsonPolicyEstimate policy6 = 9;
+ optional AndroidWattsonPolicyEstimate policy7 = 10;
+ optional AndroidWattsonDsuScuEstimate dsu_scu = 11;
}
message AndroidWattsonPolicyEstimate {
optional float estimated_mw = 1;
- optional AndroidWattsonCpuEstimate cpu0 = 2;
- optional AndroidWattsonCpuEstimate cpu1 = 3;
- optional AndroidWattsonCpuEstimate cpu2 = 4;
- optional AndroidWattsonCpuEstimate cpu3 = 5;
- optional AndroidWattsonCpuEstimate cpu4 = 6;
- optional AndroidWattsonCpuEstimate cpu5 = 7;
- optional AndroidWattsonCpuEstimate cpu6 = 8;
- optional AndroidWattsonCpuEstimate cpu7 = 9;
+ optional float estimated_mws = 2;
+ optional AndroidWattsonCpuEstimate cpu0 = 3;
+ optional AndroidWattsonCpuEstimate cpu1 = 4;
+ optional AndroidWattsonCpuEstimate cpu2 = 5;
+ optional AndroidWattsonCpuEstimate cpu3 = 6;
+ optional AndroidWattsonCpuEstimate cpu4 = 7;
+ optional AndroidWattsonCpuEstimate cpu5 = 8;
+ optional AndroidWattsonCpuEstimate cpu6 = 9;
+ optional AndroidWattsonCpuEstimate cpu7 = 10;
}
message AndroidWattsonCpuEstimate {
optional float estimated_mw = 1;
+ optional float estimated_mws = 2;
}
message AndroidWattsonDsuScuEstimate {
optional float estimated_mw = 1;
+ optional float estimated_mws = 2;
}
diff --git a/protos/perfetto/metrics/android/wattson_tasks_attribution.proto b/protos/perfetto/metrics/android/wattson_tasks_attribution.proto
index 43b79df..d3b91f4 100644
--- a/protos/perfetto/metrics/android/wattson_tasks_attribution.proto
+++ b/protos/perfetto/metrics/android/wattson_tasks_attribution.proto
@@ -19,8 +19,19 @@
package perfetto.protos;
message AndroidWattsonTasksAttributionMetric {
+ // Each version increment means updated structure format or field
optional int32 metric_version = 1;
+ // Each version increment means power model has been updated and estimates
+ // might change for the exact same input. Don't compare estimates across
+ // different power model versions.
+ optional int32 power_model_version = 2;
// Lists tasks (e.g. threads, process, package) and associated estimates
+ repeated AndroidWattsonTaskPeriodInfo period_info = 3;
+}
+
+// Groups of power per task for each period
+message AndroidWattsonTaskPeriodInfo {
+ optional int32 period_id = 1;
repeated AndroidWattsonTaskInfo task_info = 2;
}
@@ -29,9 +40,11 @@
optional float estimated_mw = 1;
// Total energy over wall duration across CPUs in mWs
optional float estimated_mws = 2;
- optional string thread_name = 3;
- optional string process_name = 4;
- optional string package_name = 5;
- optional int32 thread_id = 6;
- optional int32 process_id = 7;
+ // Energy attributed to a thread for causing CPU idle exit
+ optional float idle_transitions_mws = 3;
+ optional string thread_name = 4;
+ optional string process_name = 5;
+ optional string package_name = 6;
+ optional int32 thread_id = 7;
+ optional int32 process_id = 8;
}
diff --git a/protos/perfetto/metrics/chrome/BUILD.gn b/protos/perfetto/metrics/chrome/BUILD.gn
index a8b6bc2..bf47885 100644
--- a/protos/perfetto/metrics/chrome/BUILD.gn
+++ b/protos/perfetto/metrics/chrome/BUILD.gn
@@ -15,10 +15,7 @@
import("../../../../gn/proto_library.gni")
perfetto_proto_library("@TYPE@") {
- proto_generators = [
- "lite",
- "source_set",
- ]
+ proto_generators = [ "lite" ]
import_dirs = [ "${perfetto_protobuf_src_dir}" ]
deps = [
"..:@TYPE@",
@@ -42,12 +39,6 @@
"unsymbolized_args.proto",
"user_event_hashes.proto",
]
-}
-
-perfetto_proto_library("descriptor") {
- proto_generators = [ "descriptor" ]
- import_dirs = [ "${perfetto_protobuf_src_dir}" ]
generate_descriptor = "all_chrome_metrics.descriptor"
- deps = [ ":source_set" ]
- sources = [ "all_chrome_metrics.proto" ]
+ descriptor_root_source = "all_chrome_metrics.proto"
}
diff --git a/protos/perfetto/metrics/metrics.proto b/protos/perfetto/metrics/metrics.proto
index 622d973..9ab7eda 100644
--- a/protos/perfetto/metrics/metrics.proto
+++ b/protos/perfetto/metrics/metrics.proto
@@ -57,7 +57,6 @@
import "protos/perfetto/metrics/android/mem_unagg_metric.proto";
import "protos/perfetto/metrics/android/multiuser_metric.proto";
import "protos/perfetto/metrics/android/network_metric.proto";
-import "protos/perfetto/metrics/android/other_traces.proto";
import "protos/perfetto/metrics/android/package_list.proto";
import "protos/perfetto/metrics/android/powrails_metric.proto";
import "protos/perfetto/metrics/android/profiler_smaps.proto";
@@ -67,7 +66,6 @@
import "protos/perfetto/metrics/android/surfaceflinger.proto";
import "protos/perfetto/metrics/android/task_names.proto";
import "protos/perfetto/metrics/android/trace_quality.proto";
-import "protos/perfetto/metrics/android/android_trusty_workqueues.proto";
import "protos/perfetto/metrics/android/unsymbolized_frames.proto";
import "protos/perfetto/metrics/android/binder_metric.proto";
import "protos/perfetto/metrics/android/monitor_contention_metric.proto";
@@ -80,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;
@@ -251,11 +251,13 @@
// Metrics for IRQ runtime.
optional AndroidIrqRuntimeMetric android_irq_runtime = 43;
- // Metrics for the Trusty team.
- optional AndroidTrustyWorkqueues android_trusty_workqueues = 44;
+ // Was metrics for the Trusty team.
+ reserved 44;
+ reserved 'android_trusty_workqueues';
- // Summary of other concurrent trace recording.
- optional AndroidOtherTracesMetric android_other_traces = 45;
+ // Was summary of concurrent trace recording.
+ reserved 45;
+ reserved 'android_other_traces';
// Per-process Binder transaction metrics.
optional AndroidBinderMetric android_binder = 46;
diff --git a/protos/perfetto/metrics/perfetto_merged_metrics.proto b/protos/perfetto/metrics/perfetto_merged_metrics.proto
index 2d1c263..e2fc3a3 100644
--- a/protos/perfetto/metrics/perfetto_merged_metrics.proto
+++ b/protos/perfetto/metrics/perfetto_merged_metrics.proto
@@ -527,13 +527,6 @@
// End of protos/perfetto/metrics/android/android_sysui_notifications_blocking_calls_metric.proto
-// Begin of protos/perfetto/metrics/android/android_trusty_workqueues.proto
-
-// Metric used to generate a simplified view of the Trusty kworker events.
-message AndroidTrustyWorkqueues {}
-
-// End of protos/perfetto/metrics/android/android_trusty_workqueues.proto
-
// Begin of protos/perfetto/metrics/android/anr_metric.proto
message AndroidAnrMetric {
@@ -623,6 +616,8 @@
optional int64 sleep_screen_doze_ns = 8;
// Average power over the duration of the trace.
optional double avg_power_mw = 9;
+ // Energy usage estimate in joules.
+ optional double energy_usage_estimate = 10;
}
// Period of time during the trace that the device went to sleep completely.
@@ -863,6 +858,8 @@
optional int64 total_cpu_ns = 2;
// CPU time ( time 'Running' on cpu)
optional int64 running_cpu_ns = 3;
+ // CPU cycles
+ optional int64 cpu_cycles = 4;
}
// These are traces and could indicate framework queue latency
@@ -896,8 +893,35 @@
repeated AndroidCpuMetric.CoreTypeData core_data = 5;
}
+ // Rail details
+ message Rail {
+ // name of rail
+ optional string name = 1;
+ // energy and power details of this rail
+ message Info {
+ // energy from this rail for codec use
+ optional double energy = 1;
+ // power consumption in this rail for codec use
+ optional double power_mw = 2;
+ }
+ optional Info info = 2;
+ }
+
+ // have the energy usage for the codec running time
+ message Energy {
+ // total energy taken by the system during this time
+ optional double total_energy = 1;
+ // total time for this energy is calculated
+ optional int64 duration = 2;
+ // for this session
+ optional double power_mw = 3;
+ // enery breakdown by subsystem
+ repeated Rail rail = 4;
+ }
+
repeated CpuUsage cpu_usage = 1;
repeated CodecFunction codec_function = 2;
+ optional Energy energy = 3;
}
@@ -2099,16 +2123,6 @@
// End of protos/perfetto/metrics/android/network_metric.proto
-// Begin of protos/perfetto/metrics/android/other_traces.proto
-
-message AndroidOtherTracesMetric {
- // Uuids of other traces being finalized while the current trace was being
- // recorded.
- repeated string finalized_traces_uuid = 1;
-}
-
-// End of protos/perfetto/metrics/android/other_traces.proto
-
// Begin of protos/perfetto/metrics/android/package_list.proto
message AndroidPackageList {
@@ -2271,7 +2285,7 @@
// Timing information spanning the intent received by the
// activity manager to the first frame drawn.
- // Next id: 36.
+ // Next id: 38
message ToFirstFrame {
// The duration between the intent received and first frame.
optional int64 dur_ns = 1;
@@ -2320,6 +2334,8 @@
optional Slice time_post_fork = 16;
+ // Total time on class initialization during app startup.
+ optional Slice time_class_initialization = 36;
// The total time spent on opening dex files.
optional Slice time_dex_open = 24;
// Total time spent verifying classes during app startup.
@@ -2328,6 +2344,9 @@
// Number of methods that were compiled by JIT during app startup.
optional uint32 jit_compiled_methods = 27;
+ // Number of class initializations during app startup.
+ optional uint32 class_initialization_count = 37;
+
// Time spent running CPU on jit thread pool.
optional Slice time_jit_thread_pool_on_cpu = 28;
@@ -2522,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;
@@ -2568,6 +2587,17 @@
optional uint32 slice_id = 3;
optional string slice_name = 4;
+
+ optional uint32 process_pid = 5;
+
+ 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.
@@ -2580,6 +2610,17 @@
optional uint32 thread_utid = 3;
optional string thread_name = 4;
+
+ optional uint32 process_pid = 5;
+
+ 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
@@ -2889,8 +2930,13 @@
// Begin of protos/perfetto/metrics/android/wattson_in_time_period.proto
message AndroidWattsonTimePeriodMetric {
+ // Each version increment means updated structure format or field
optional int32 metric_version = 1;
- repeated AndroidWattsonEstimateInfo period_info = 2;
+ // Each version increment means power model has been updated and estimates
+ // might change for the exact same input. Don't compare estimates across
+ // different power model versions.
+ optional int32 power_model_version = 2;
+ repeated AndroidWattsonEstimateInfo period_info = 3;
}
message AndroidWattsonEstimateInfo {
@@ -2902,35 +2948,39 @@
message AndroidWattsonCpuSubsystemEstimate {
// estimates and estimates of subrails
optional float estimated_mw = 1;
- optional AndroidWattsonPolicyEstimate policy0 = 2;
- optional AndroidWattsonPolicyEstimate policy1 = 3;
- optional AndroidWattsonPolicyEstimate policy2 = 4;
- optional AndroidWattsonPolicyEstimate policy3 = 5;
- optional AndroidWattsonPolicyEstimate policy4 = 6;
- optional AndroidWattsonPolicyEstimate policy5 = 7;
- optional AndroidWattsonPolicyEstimate policy6 = 8;
- optional AndroidWattsonPolicyEstimate policy7 = 9;
- optional AndroidWattsonDsuScuEstimate dsu_scu = 10;
+ optional float estimated_mws = 2;
+ optional AndroidWattsonPolicyEstimate policy0 = 3;
+ optional AndroidWattsonPolicyEstimate policy1 = 4;
+ optional AndroidWattsonPolicyEstimate policy2 = 5;
+ optional AndroidWattsonPolicyEstimate policy3 = 6;
+ optional AndroidWattsonPolicyEstimate policy4 = 7;
+ optional AndroidWattsonPolicyEstimate policy5 = 8;
+ optional AndroidWattsonPolicyEstimate policy6 = 9;
+ optional AndroidWattsonPolicyEstimate policy7 = 10;
+ optional AndroidWattsonDsuScuEstimate dsu_scu = 11;
}
message AndroidWattsonPolicyEstimate {
optional float estimated_mw = 1;
- optional AndroidWattsonCpuEstimate cpu0 = 2;
- optional AndroidWattsonCpuEstimate cpu1 = 3;
- optional AndroidWattsonCpuEstimate cpu2 = 4;
- optional AndroidWattsonCpuEstimate cpu3 = 5;
- optional AndroidWattsonCpuEstimate cpu4 = 6;
- optional AndroidWattsonCpuEstimate cpu5 = 7;
- optional AndroidWattsonCpuEstimate cpu6 = 8;
- optional AndroidWattsonCpuEstimate cpu7 = 9;
+ optional float estimated_mws = 2;
+ optional AndroidWattsonCpuEstimate cpu0 = 3;
+ optional AndroidWattsonCpuEstimate cpu1 = 4;
+ optional AndroidWattsonCpuEstimate cpu2 = 5;
+ optional AndroidWattsonCpuEstimate cpu3 = 6;
+ optional AndroidWattsonCpuEstimate cpu4 = 7;
+ optional AndroidWattsonCpuEstimate cpu5 = 8;
+ optional AndroidWattsonCpuEstimate cpu6 = 9;
+ optional AndroidWattsonCpuEstimate cpu7 = 10;
}
message AndroidWattsonCpuEstimate {
optional float estimated_mw = 1;
+ optional float estimated_mws = 2;
}
message AndroidWattsonDsuScuEstimate {
optional float estimated_mw = 1;
+ optional float estimated_mws = 2;
}
// End of protos/perfetto/metrics/android/wattson_in_time_period.proto
@@ -2938,8 +2988,19 @@
// Begin of protos/perfetto/metrics/android/wattson_tasks_attribution.proto
message AndroidWattsonTasksAttributionMetric {
+ // Each version increment means updated structure format or field
optional int32 metric_version = 1;
+ // Each version increment means power model has been updated and estimates
+ // might change for the exact same input. Don't compare estimates across
+ // different power model versions.
+ optional int32 power_model_version = 2;
// Lists tasks (e.g. threads, process, package) and associated estimates
+ repeated AndroidWattsonTaskPeriodInfo period_info = 3;
+}
+
+// Groups of power per task for each period
+message AndroidWattsonTaskPeriodInfo {
+ optional int32 period_id = 1;
repeated AndroidWattsonTaskInfo task_info = 2;
}
@@ -2948,11 +3009,13 @@
optional float estimated_mw = 1;
// Total energy over wall duration across CPUs in mWs
optional float estimated_mws = 2;
- optional string thread_name = 3;
- optional string process_name = 4;
- optional string package_name = 5;
- optional int32 thread_id = 6;
- optional int32 process_id = 7;
+ // Energy attributed to a thread for causing CPU idle exit
+ optional float idle_transitions_mws = 3;
+ optional string thread_name = 4;
+ optional string process_name = 5;
+ optional string package_name = 6;
+ optional int32 thread_id = 7;
+ optional int32 process_id = 8;
}
// End of protos/perfetto/metrics/android/wattson_tasks_attribution.proto
@@ -2960,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;
@@ -3131,11 +3196,13 @@
// Metrics for IRQ runtime.
optional AndroidIrqRuntimeMetric android_irq_runtime = 43;
- // Metrics for the Trusty team.
- optional AndroidTrustyWorkqueues android_trusty_workqueues = 44;
+ // Was metrics for the Trusty team.
+ reserved 44;
+ reserved 'android_trusty_workqueues';
- // Summary of other concurrent trace recording.
- optional AndroidOtherTracesMetric android_other_traces = 45;
+ // Was summary of concurrent trace recording.
+ reserved 45;
+ reserved 'android_other_traces';
// Per-process Binder transaction metrics.
optional AndroidBinderMetric android_binder = 46;
diff --git a/protos/perfetto/metrics/webview/BUILD.gn b/protos/perfetto/metrics/webview/BUILD.gn
index 744794c..ec2c9c4 100644
--- a/protos/perfetto/metrics/webview/BUILD.gn
+++ b/protos/perfetto/metrics/webview/BUILD.gn
@@ -15,18 +15,13 @@
import("../../../../gn/proto_library.gni")
perfetto_proto_library("@TYPE@") {
- proto_generators = [ "source_set" ]
- deps = [ "..:@TYPE@" ]
+ proto_generators = []
+ import_dirs = [ "${perfetto_protobuf_src_dir}" ]
sources = [
"all_webview_metrics.proto",
"webview_jank_approximation.proto",
]
-}
-
-perfetto_proto_library("descriptor") {
- proto_generators = [ "descriptor" ]
- import_dirs = [ "${perfetto_protobuf_src_dir}" ]
+ deps = [ "..:@TYPE@" ]
generate_descriptor = "all_webview_metrics.descriptor"
- deps = [ ":source_set" ]
- sources = [ "all_webview_metrics.proto" ]
+ descriptor_root_source = "all_webview_metrics.proto"
}
diff --git a/protos/perfetto/trace/BUILD.gn b/protos/perfetto/trace/BUILD.gn
index 0eb14c0..4ecdb18 100644
--- a/protos/perfetto/trace/BUILD.gn
+++ b/protos/perfetto/trace/BUILD.gn
@@ -73,21 +73,11 @@
]
}
-# This is only for Bazel build generation.
-group("source_set") {
- testonly = true
- public_deps = [
- ":minimal_source_set",
- ":non_minimal_source_set",
- ]
-}
-
perfetto_proto_library("non_minimal_@TYPE@") {
proto_generators = [
"cpp",
"lite",
"zero",
- "source_set",
]
deps = [
":minimal_@TYPE@",
@@ -117,11 +107,12 @@
sources = proto_sources_minimal
}
-perfetto_proto_library("descriptor") {
- proto_generators = [ "descriptor" ]
+perfetto_proto_library("@TYPE@") {
+ proto_generators = []
+ deps = [ ":non_minimal_@TYPE@" ]
+ sources = []
generate_descriptor = "trace.descriptor"
- sources = [ "trace.proto" ]
- deps = [ ":non_minimal_source_set" ]
+ descriptor_root_source = "trace.proto"
}
# This target exports perfetto trace protos allowing both host and device
@@ -131,11 +122,11 @@
deps = [ ":lite" ]
}
-perfetto_proto_library("test_extensions_descriptor") {
- proto_generators = [ "descriptor" ]
- generate_descriptor = "test_extensions.descriptor"
+perfetto_proto_library("test_extensions_@TYPE@") {
+ proto_generators = []
sources = [ "test_extensions.proto" ]
- deps = [ "track_event:source_set" ]
+ generate_descriptor = "test_extensions.descriptor"
+ descriptor_root_source = "test_extensions.proto"
}
if (enable_perfetto_merged_protos_check) {
diff --git a/protos/perfetto/trace/android/BUILD.gn b/protos/perfetto/trace/android/BUILD.gn
index b7c746a..429bbeb 100644
--- a/protos/perfetto/trace/android/BUILD.gn
+++ b/protos/perfetto/trace/android/BUILD.gn
@@ -60,29 +60,26 @@
# Winscope messages added to TracePacket as extensions
perfetto_proto_library("winscope_extensions_@TYPE@") {
- proto_generators = [
- "zero",
- "source_set",
- ]
+ proto_generators = [ "zero" ]
public_deps = [ ":winscope_common_@TYPE@" ]
sources = [
"android_input_event.proto",
- "graphics/pixelformat.proto",
- "inputmethodeditor.proto",
- "inputmethodservice/inputmethodservice.proto",
- "inputmethodservice/softinputwindow.proto",
- "server/inputmethod/inputmethodmanagerservice.proto",
- "typedef.proto",
"app/statusbarmanager.proto",
"app/window_configuration.proto",
"content/activityinfo.proto",
"content/configuration.proto",
"content/locale.proto",
+ "graphics/pixelformat.proto",
+ "inputmethodeditor.proto",
+ "inputmethodservice/inputmethodservice.proto",
+ "inputmethodservice/softinputwindow.proto",
"privacy.proto",
"server/animationadapter.proto",
+ "server/inputmethod/inputmethodmanagerservice.proto",
"server/surfaceanimator.proto",
"server/windowcontainerthumbnail.proto",
"server/windowmanagerservice.proto",
+ "typedef.proto",
"view/display.proto",
"view/displaycutout.proto",
"view/displayinfo.proto",
@@ -115,29 +112,25 @@
deps = [ ":winscope_extensions_zero" ]
}
-perfetto_proto_library("winscope_descriptor") {
- proto_generators = [ "descriptor" ]
- generate_descriptor = "winscope.descriptor"
- deps = [
- ":winscope_extensions_source_set",
- ":winscope_regular_source_set",
- ]
+perfetto_proto_library("winscope_@TYPE@") {
+ proto_generators = []
sources = [ "winscope.proto" ]
+ deps = [
+ ":winscope_extensions_@TYPE@",
+ ":winscope_regular_@TYPE@",
+ ]
import_dirs = [ "${perfetto_protobuf_src_dir}" ]
+ generate_descriptor = "winscope.descriptor"
+ descriptor_root_source = "winscope.proto"
}
# Android track_event extensions
perfetto_proto_library("android_track_event_@TYPE@") {
- proto_generators = [ "source_set" ]
+ sources = [ "android_track_event.proto" ]
public_deps = [ "../track_event:@TYPE@" ]
- sources = [ "android_track_event.proto" ]
-}
-perfetto_proto_library("android_track_event_@TYPE@") {
- proto_generators = [ "descriptor" ]
generate_descriptor = "android_track_event.descriptor"
- sources = [ "android_track_event.proto" ]
- deps = [ ":android_track_event_source_set" ]
+ descriptor_root_source = "android_track_event.proto"
# This is the descriptor for an extension. It shouldn't include the descriptor
# for the base message as well.
diff --git a/protos/perfetto/trace/android/android_input_event.proto b/protos/perfetto/trace/android/android_input_event.proto
index 85a89c0..901b497 100644
--- a/protos/perfetto/trace/android/android_input_event.proto
+++ b/protos/perfetto/trace/android/android_input_event.proto
@@ -90,7 +90,7 @@
optional int32 device_id = 6;
// Use a signed int for display_id, because -1 (DISPLAY_IS_NONE) is a common value.
optional sint32 display_id = 7;
- optional int32 key_code = 8;
+ optional int32 key_code = 8 [(.perfetto.protos.typedef) = "android.view.KeyEvent.KeyCode"];
optional uint32 scan_code = 9;
optional uint32 meta_state = 10 [(.perfetto.protos.typedef) ="android.view.KeyEvent.MetaState"];
optional int32 repeat_count = 11;
diff --git a/protos/perfetto/trace/android/server/windowmanagerservice.proto b/protos/perfetto/trace/android/server/windowmanagerservice.proto
index dcb4583..2c22522 100644
--- a/protos/perfetto/trace/android/server/windowmanagerservice.proto
+++ b/protos/perfetto/trace/android/server/windowmanagerservice.proto
@@ -454,6 +454,7 @@
repeated RectProto unrestricted_keep_clear_areas = 46;
repeated InsetsSourceProto mergedLocalInsetsSources = 47;
optional int32 requested_visible_types = 48;
+ optional RectProto dim_bounds = 49;
}
message IdentifierProto {
diff --git a/protos/perfetto/trace/etw/BUILD.gn b/protos/perfetto/trace/etw/BUILD.gn
index f22514c..fb4bf93 100644
--- a/protos/perfetto/trace/etw/BUILD.gn
+++ b/protos/perfetto/trace/etw/BUILD.gn
@@ -18,12 +18,8 @@
perfetto_proto_library("@TYPE@") {
sources = etw_proto_names
-}
-
-if (perfetto_build_standalone) {
- perfetto_proto_library("descriptor") {
- proto_generators = [ "descriptor" ]
+ if (perfetto_build_standalone) {
generate_descriptor = "etw.descriptor"
- sources = [ "etw_event_bundle.proto" ]
+ descriptor_root_source = "etw_event_bundle.proto"
}
}
diff --git a/protos/perfetto/trace/ftrace/BUILD.gn b/protos/perfetto/trace/ftrace/BUILD.gn
index 2b6182d..6418223 100644
--- a/protos/perfetto/trace/ftrace/BUILD.gn
+++ b/protos/perfetto/trace/ftrace/BUILD.gn
@@ -18,12 +18,8 @@
perfetto_proto_library("@TYPE@") {
sources = ftrace_proto_names
-}
-
-if (perfetto_build_standalone) {
- perfetto_proto_library("descriptor") {
- proto_generators = [ "descriptor" ]
+ if (perfetto_build_standalone) {
generate_descriptor = "ftrace.descriptor"
- sources = [ "ftrace_event_bundle.proto" ]
+ descriptor_root_source = "ftrace_event_bundle.proto"
}
}
diff --git a/protos/perfetto/trace/ftrace/all_protos.gni b/protos/perfetto/trace/ftrace/all_protos.gni
index 5aae5d9..7319dd4 100644
--- a/protos/perfetto/trace/ftrace/all_protos.gni
+++ b/protos/perfetto/trace/ftrace/all_protos.gni
@@ -28,9 +28,11 @@
"clk.proto",
"cma.proto",
"compaction.proto",
+ "cpm_trace.proto",
"cpuhp.proto",
"cros_ec.proto",
"dcvsh.proto",
+ "devfreq.proto",
"dma_fence.proto",
"dmabuf_heap.proto",
"dpu.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/devfreq.proto b/protos/perfetto/trace/ftrace/devfreq.proto
new file mode 100644
index 0000000..1533fc4
--- /dev/null
+++ b/protos/perfetto/trace/ftrace/devfreq.proto
@@ -0,0 +1,14 @@
+// Autogenerated by:
+// ../../src/tools/ftrace_proto_gen/ftrace_proto_gen.cc
+// Do not edit.
+
+syntax = "proto2";
+package perfetto.protos;
+
+message DevfreqFrequencyFtraceEvent {
+ optional string dev_name = 1;
+ optional uint64 freq = 2;
+ optional uint64 prev_freq = 3;
+ optional uint64 busy_time = 4;
+ optional uint64 total_time = 5;
+}
diff --git a/protos/perfetto/trace/ftrace/ftrace_event.proto b/protos/perfetto/trace/ftrace/ftrace_event.proto
index fc4e30b..b363eb0 100644
--- a/protos/perfetto/trace/ftrace/ftrace_event.proto
+++ b/protos/perfetto/trace/ftrace/ftrace_event.proto
@@ -28,9 +28,11 @@
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";
+import "protos/perfetto/trace/ftrace/devfreq.proto";
import "protos/perfetto/trace/ftrace/dma_fence.proto";
import "protos/perfetto/trace/ftrace/dmabuf_heap.proto";
import "protos/perfetto/trace/ftrace/dpu.proto";
@@ -678,5 +680,8 @@
PixelMmKswapdWakeFtraceEvent pixel_mm_kswapd_wake = 538;
PixelMmKswapdDoneFtraceEvent pixel_mm_kswapd_done = 539;
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/ftrace/ftrace_stats.proto b/protos/perfetto/trace/ftrace/ftrace_stats.proto
index f921779..dd531d4 100644
--- a/protos/perfetto/trace/ftrace/ftrace_stats.proto
+++ b/protos/perfetto/trace/ftrace/ftrace_stats.proto
@@ -61,6 +61,17 @@
optional uint64 read_events = 9;
}
+// Kprobe statistical data, gathered from /sys/kernel/tracing/kprobe_profile.
+message FtraceKprobeStats {
+ // Cumulative number of kprobe events generated for this function
+ optional int64 hits = 1;
+ // Cumulative number of kprobe events that could not be generated for this
+ // function and were missed. This happens when too much nesting
+ // happens between a kprobe and its kretprobe, overflowing the
+ // maxactives buffer.
+ optional int64 misses = 2;
+}
+
// Errors and kernel buffer stats for the ftrace data source.
message FtraceStats {
enum Phase {
@@ -112,6 +123,9 @@
// Any traces with entries in this field should be investigated, as they
// indicate a bug in perfetto or the kernel.
repeated FtraceParseStatus ftrace_parse_errors = 9;
+
+ // Kprobe profile stats for functions hits and misses
+ optional FtraceKprobeStats kprobe_stats = 10;
}
enum FtraceParseStatus {
diff --git a/protos/perfetto/trace/ftrace/generic.proto b/protos/perfetto/trace/ftrace/generic.proto
index 65d5c95..d7d4e36 100644
--- a/protos/perfetto/trace/ftrace/generic.proto
+++ b/protos/perfetto/trace/ftrace/generic.proto
@@ -33,3 +33,14 @@
optional string event_name = 1;
repeated Field field = 2;
}
+
+message KprobeEvent {
+ enum KprobeType {
+ KPROBE_TYPE_UNKNOWN = 0;
+ KPROBE_TYPE_BEGIN = 1;
+ KPROBE_TYPE_END = 2;
+ KPROBE_TYPE_INSTANT = 3;
+ }
+ optional string name = 1;
+ optional KprobeType type = 2;
+}
diff --git a/protos/perfetto/trace/perfetto/tracing_service_event.proto b/protos/perfetto/trace/perfetto/tracing_service_event.proto
index c7e3698..dbc4df1 100644
--- a/protos/perfetto/trace/perfetto/tracing_service_event.proto
+++ b/protos/perfetto/trace/perfetto/tracing_service_event.proto
@@ -46,6 +46,9 @@
// sources have been recording events.
bool all_data_sources_started = 1;
+ // Emitted when a flush is started.
+ bool flush_started = 9;
+
// Emitted when all data sources have been flushed successfully or with an
// error (including timeouts). This can generally happen many times over the
// course of the trace.
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index 6612e40..5e07a0c 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -888,6 +888,15 @@
// End of protos/perfetto/config/chrome/chrome_config.proto
+// Begin of protos/perfetto/config/chrome/system_metrics.proto
+
+message ChromiumSystemMetricsConfig {
+ // Samples counters every X ms.
+ optional uint32 sampling_interval_ms = 1;
+}
+
+// End of protos/perfetto/config/chrome/system_metrics.proto
+
// Begin of protos/perfetto/config/chrome/v8_config.proto
message V8Config {
@@ -927,10 +936,26 @@
// Begin of protos/perfetto/config/ftrace/ftrace_config.proto
-// Next id: 30
+// Next id: 31
message FtraceConfig {
// Ftrace events to record, example: "sched/sched_switch".
repeated string ftrace_events = 1;
+
+ message KprobeEvent {
+ enum KprobeType {
+ KPROBE_TYPE_UNKNOWN = 0;
+ KPROBE_TYPE_KPROBE = 1;
+ KPROBE_TYPE_KRETPROBE = 2;
+ KPROBE_TYPE_BOTH = 3;
+ }
+ // Kernel function name to attach to, for example "fuse_file_write_iter"
+ optional string probe = 1;
+ optional KprobeType type = 2;
+ }
+
+ // Ftrace events to record, specific for kprobes and kretprobes
+ repeated KprobeEvent kprobe_events = 30;
+
// Android-specific event categories:
repeated string atrace_categories = 2;
repeated string atrace_apps = 3;
@@ -1957,6 +1982,8 @@
UNWIND_SKIP = 1;
// Use libunwindstack (default):
UNWIND_DWARF = 2;
+ // Use userspace frame pointer unwinder:
+ UNWIND_FRAME_POINTER = 3;
}
}
@@ -2022,11 +2049,9 @@
ATOM_LMK_KILL_OCCURRED = 51;
ATOM_PICTURE_IN_PICTURE_STATE_CHANGED = 52;
ATOM_WIFI_MULTICAST_LOCK_STATE_CHANGED = 53;
- ATOM_LMK_STATE_CHANGED = 54;
ATOM_APP_START_MEMORY_STATE_CAPTURED = 55;
ATOM_SHUTDOWN_SEQUENCE_REPORTED = 56;
ATOM_BOOT_SEQUENCE_REPORTED = 57;
- ATOM_DAVEY_OCCURRED = 58;
ATOM_OVERLAY_STATE_CHANGED = 59;
ATOM_FOREGROUND_SERVICE_STATE_CHANGED = 60;
ATOM_CALL_STATE_CHANGED = 61;
@@ -2343,7 +2368,6 @@
ATOM_PRIVACY_TOGGLE_DIALOG_INTERACTION = 382;
ATOM_APP_SEARCH_OPTIMIZE_STATS_REPORTED = 383;
ATOM_NON_A11Y_TOOL_SERVICE_WARNING_REPORT = 384;
- ATOM_APP_SEARCH_SET_SCHEMA_STATS_REPORTED = 385;
ATOM_APP_COMPAT_STATE_CHANGED = 386;
ATOM_SIZE_COMPAT_RESTART_BUTTON_EVENT_REPORTED = 387;
ATOM_SPLITSCREEN_UI_CHANGED = 388;
@@ -2392,8 +2416,6 @@
ATOM_HOTWORD_DETECTION_SERVICE_RESTARTED = 432;
ATOM_HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED = 433;
ATOM_HOTWORD_DETECTOR_EVENTS = 434;
- ATOM_AD_SERVICES_API_CALLED = 435;
- ATOM_AD_SERVICES_MESUREMENT_REPORTS_UPLOADED = 436;
ATOM_BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED = 437;
ATOM_CONTACTS_INDEXER_UPDATE_STATS_REPORTED = 440;
ATOM_APP_BACKGROUND_RESTRICTIONS_INFO = 441;
@@ -2437,25 +2459,14 @@
ATOM_CB_MODULE_ERROR_REPORTED = 480;
ATOM_CB_SERVICE_FEATURE_CHANGED = 481;
ATOM_CB_RECEIVER_FEATURE_CHANGED = 482;
- ATOM_JSSCRIPTENGINE_LATENCY_REPORTED = 483;
ATOM_PRIVACY_SIGNAL_NOTIFICATION_INTERACTION = 484;
ATOM_PRIVACY_SIGNAL_ISSUE_CARD_INTERACTION = 485;
ATOM_PRIVACY_SIGNALS_JOB_FAILURE = 486;
ATOM_VIBRATION_REPORTED = 487;
ATOM_UWB_RANGING_START = 489;
- ATOM_MOBILE_DATA_DOWNLOAD_FILE_GROUP_STATUS_REPORTED = 490;
ATOM_APP_COMPACTED_V2 = 491;
- ATOM_AD_SERVICES_SETTINGS_USAGE_REPORTED = 493;
ATOM_DISPLAY_BRIGHTNESS_CHANGED = 494;
ATOM_ACTIVITY_ACTION_BLOCKED = 495;
- ATOM_BACKGROUND_FETCH_PROCESS_REPORTED = 496;
- ATOM_UPDATE_CUSTOM_AUDIENCE_PROCESS_REPORTED = 497;
- ATOM_RUN_AD_BIDDING_PROCESS_REPORTED = 498;
- ATOM_RUN_AD_SCORING_PROCESS_REPORTED = 499;
- ATOM_RUN_AD_SELECTION_PROCESS_REPORTED = 500;
- ATOM_RUN_AD_BIDDING_PER_CA_PROCESS_REPORTED = 501;
- ATOM_MOBILE_DATA_DOWNLOAD_DOWNLOAD_RESULT_REPORTED = 502;
- ATOM_MOBILE_DATA_DOWNLOAD_FILE_GROUP_STORAGE_STATS_REPORTED = 503;
ATOM_NETWORK_DNS_SERVER_SUPPORT_REPORTED = 504;
ATOM_VM_BOOTED = 505;
ATOM_VM_EXITED = 506;
@@ -2464,7 +2475,6 @@
ATOM_MEDIAMETRICS_SPATIALIZERDEVICEENABLED_REPORTED = 509;
ATOM_MEDIAMETRICS_HEADTRACKERDEVICEENABLED_REPORTED = 510;
ATOM_MEDIAMETRICS_HEADTRACKERDEVICESUPPORTED_REPORTED = 511;
- ATOM_AD_SERVICES_MEASUREMENT_REGISTRATIONS = 512;
ATOM_HEARING_AID_INFO_REPORTED = 513;
ATOM_DEVICE_WIDE_JOB_CONSTRAINT_CHANGED = 514;
ATOM_AMBIENT_MODE_CHANGED = 515;
@@ -2486,9 +2496,6 @@
ATOM_BLUETOOTH_LOCAL_SUPPORTED_FEATURES_REPORTED = 532;
ATOM_BLUETOOTH_GATT_APP_INFO = 533;
ATOM_BRIGHTNESS_CONFIGURATION_UPDATED = 534;
- ATOM_AD_SERVICES_GET_TOPICS_REPORTED = 535;
- ATOM_AD_SERVICES_EPOCH_COMPUTATION_GET_TOP_TOPICS_REPORTED = 536;
- ATOM_AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED = 537;
ATOM_WEAR_MEDIA_OUTPUT_SWITCHER_LAUNCHED = 538;
ATOM_WEAR_MEDIA_OUTPUT_SWITCHER_FINISHED = 539;
ATOM_WEAR_MEDIA_OUTPUT_SWITCHER_CONNECTION_REPORTED = 540;
@@ -2526,12 +2533,10 @@
ATOM_MEDIAMETRICS_MIDI_DEVICE_CLOSE_REPORTED = 576;
ATOM_BIOMETRIC_TOUCH_REPORTED = 577;
ATOM_HOTWORD_AUDIO_EGRESS_EVENT_REPORTED = 578;
- ATOM_APP_SEARCH_SCHEMA_MIGRATION_STATS_REPORTED = 579;
ATOM_LOCATION_ENABLED_STATE_CHANGED = 580;
ATOM_IME_REQUEST_FINISHED = 581;
ATOM_USB_COMPLIANCE_WARNINGS_REPORTED = 582;
ATOM_APP_SUPPORTED_LOCALES_CHANGED = 583;
- ATOM_GRAMMATICAL_INFLECTION_CHANGED = 584;
ATOM_MEDIA_PROVIDER_VOLUME_RECOVERY_REPORTED = 586;
ATOM_BIOMETRIC_PROPERTIES_COLLECTED = 587;
ATOM_KERNEL_WAKEUP_ATTRIBUTED = 588;
@@ -2544,7 +2549,11 @@
ATOM_WS_NOTIFICATION_UPDATED = 596;
ATOM_NETWORK_VALIDATION_FAILURE_STATS_DAILY_REPORTED = 601;
ATOM_WS_COMPLICATION_TAPPED = 602;
- ATOM_WS_WEAR_TIME_SESSION = 610;
+ ATOM_WS_NOTIFICATION_BLOCKING = 780;
+ ATOM_WS_NOTIFICATION_BRIDGEMODE_UPDATED = 822;
+ ATOM_WS_NOTIFICATION_DISMISSAL_ACTIONED = 823;
+ ATOM_WS_NOTIFICATION_ACTIONED = 824;
+ ATOM_WS_NOTIFICATION_LATENCY = 880;
ATOM_WIFI_BYTES_TRANSFER = 10000;
ATOM_WIFI_BYTES_TRANSFER_BY_FG_BG = 10001;
ATOM_MOBILE_BYTES_TRANSFER = 10002;
@@ -2717,6 +2726,186 @@
ATOM_NOTIFICATION_MEMORY_USE = 10174;
ATOM_HDR_CAPABILITIES = 10175;
ATOM_WS_FAVOURITE_WATCH_FACE_LIST_SNAPSHOT = 10176;
+ ATOM_WS_WEAR_TIME_SESSION = 610;
+ ATOM_WS_INCOMING_CALL_ACTION_REPORTED = 626;
+ ATOM_WS_CALL_DISCONNECTION_REPORTED = 627;
+ ATOM_WS_CALL_DURATION_REPORTED = 628;
+ ATOM_WS_CALL_USER_EXPERIENCE_LATENCY_REPORTED = 629;
+ ATOM_WS_CALL_INTERACTION_REPORTED = 630;
+ ATOM_WS_ON_BODY_STATE_CHANGED = 787;
+ ATOM_WS_WATCH_FACE_RESTRICTED_COMPLICATIONS_IMPACTED = 802;
+ ATOM_WS_WATCH_FACE_DEFAULT_RESTRICTED_COMPLICATIONS_REMOVED = 803;
+ ATOM_WS_COMPLICATIONS_IMPACTED_NOTIFICATION_EVENT_REPORTED = 804;
+ ATOM_WS_STANDALONE_MODE_SNAPSHOT = 10197;
+ ATOM_WS_FAVORITE_WATCH_FACE_SNAPSHOT = 10206;
+ ATOM_SETTINGS_SPA_REPORTED = 622;
+ ATOM_PDF_LOAD_REPORTED = 859;
+ ATOM_PDF_API_USAGE_REPORTED = 860;
+ ATOM_PDF_SEARCH_REPORTED = 861;
+ ATOM_HDMI_EARC_STATUS_REPORTED = 701;
+ ATOM_HDMI_SOUNDBAR_MODE_STATUS_REPORTED = 724;
+ ATOM_MEDIA_PROVIDER_DATABASE_ROLLBACK_REPORTED = 784;
+ ATOM_BACKUP_SETUP_STATUS_REPORTED = 785;
+ ATOM_PHOTOPICKER_SESSION_INFO_REPORTED = 886;
+ ATOM_PHOTOPICKER_API_INFO_REPORTED = 887;
+ ATOM_PHOTOPICKER_UI_EVENT_LOGGED = 888;
+ ATOM_PHOTOPICKER_MEDIA_ITEM_STATUS_REPORTED = 889;
+ ATOM_PHOTOPICKER_PREVIEW_INFO_LOGGED = 890;
+ ATOM_PHOTOPICKER_MENU_INTERACTION_LOGGED = 891;
+ ATOM_PHOTOPICKER_BANNER_INTERACTION_LOGGED = 892;
+ ATOM_PHOTOPICKER_MEDIA_LIBRARY_INFO_LOGGED = 893;
+ ATOM_PHOTOPICKER_PAGE_INFO_LOGGED = 894;
+ ATOM_PHOTOPICKER_MEDIA_GRID_SYNC_INFO_REPORTED = 895;
+ ATOM_PHOTOPICKER_ALBUM_SYNC_INFO_REPORTED = 896;
+ ATOM_PHOTOPICKER_SEARCH_INFO_REPORTED = 897;
+ ATOM_SEARCH_DATA_EXTRACTION_DETAILS_REPORTED = 898;
+ ATOM_EMBEDDED_PHOTOPICKER_INFO_REPORTED = 899;
+ ATOM_WEAR_POWER_MENU_OPENED = 731;
+ ATOM_WEAR_ASSISTANT_OPENED = 755;
+ ATOM_KERNEL_OOM_KILL_OCCURRED = 754;
+ ATOM_AUTOFILL_UI_EVENT_REPORTED = 603;
+ ATOM_AUTOFILL_FILL_REQUEST_REPORTED = 604;
+ ATOM_AUTOFILL_FILL_RESPONSE_REPORTED = 605;
+ ATOM_AUTOFILL_SAVE_EVENT_REPORTED = 606;
+ ATOM_AUTOFILL_SESSION_COMMITTED = 607;
+ ATOM_AUTOFILL_FIELD_CLASSIFICATION_EVENT_REPORTED = 659;
+ ATOM_TV_LOW_POWER_STANDBY_POLICY = 679;
+ ATOM_EXTERNAL_TV_INPUT_EVENT = 717;
+ ATOM_COMPONENT_STATE_CHANGED_REPORTED = 863;
+ ATOM_AI_WALLPAPERS_BUTTON_PRESSED = 706;
+ ATOM_AI_WALLPAPERS_TEMPLATE_SELECTED = 707;
+ ATOM_AI_WALLPAPERS_TERM_SELECTED = 708;
+ ATOM_AI_WALLPAPERS_WALLPAPER_SET = 709;
+ ATOM_AI_WALLPAPERS_SESSION_SUMMARY = 710;
+ ATOM_APF_SESSION_INFO_REPORTED = 777;
+ ATOM_IP_CLIENT_RA_INFO_REPORTED = 778;
+ ATOM_VPN_CONNECTION_STATE_CHANGED = 850;
+ ATOM_VPN_CONNECTION_REPORTED = 851;
+ ATOM_NETWORK_STATS_RECORDER_FILE_OPERATED = 783;
+ ATOM_DAILY_KEEPALIVE_INFO_REPORTED = 650;
+ ATOM_NETWORK_REQUEST_STATE_CHANGED = 779;
+ ATOM_TETHERING_ACTIVE_SESSIONS_REPORTED = 925;
+ ATOM_ART_DATUM_REPORTED = 332;
+ ATOM_ART_DEVICE_DATUM_REPORTED = 550;
+ ATOM_ART_DATUM_DELTA_REPORTED = 565;
+ ATOM_ART_DEX2OAT_REPORTED = 929;
+ ATOM_ART_DEVICE_STATUS = 10205;
+ ATOM_ODREFRESH_REPORTED = 366;
+ ATOM_ODSIGN_REPORTED = 548;
+ ATOM_BACKGROUND_DEXOPT_JOB_ENDED = 467;
+ ATOM_PREREBOOT_DEXOPT_JOB_ENDED = 883;
+ ATOM_PERMISSION_RATIONALE_DIALOG_VIEWED = 645;
+ ATOM_PERMISSION_RATIONALE_DIALOG_ACTION_REPORTED = 646;
+ ATOM_APP_DATA_SHARING_UPDATES_NOTIFICATION_INTERACTION = 647;
+ ATOM_APP_DATA_SHARING_UPDATES_FRAGMENT_VIEWED = 648;
+ ATOM_APP_DATA_SHARING_UPDATES_FRAGMENT_ACTION_REPORTED = 649;
+ ATOM_ENHANCED_CONFIRMATION_DIALOG_RESULT_REPORTED = 827;
+ ATOM_ENHANCED_CONFIRMATION_RESTRICTION_CLEARED = 828;
+ ATOM_EMERGENCY_STATE_CHANGED = 633;
+ ATOM_CHRE_SIGNIFICANT_MOTION_STATE_CHANGED = 868;
+ ATOM_HEALTH_CONNECT_UI_IMPRESSION = 623;
+ ATOM_HEALTH_CONNECT_UI_INTERACTION = 624;
+ ATOM_HEALTH_CONNECT_APP_OPENED_REPORTED = 625;
+ ATOM_HEALTH_CONNECT_API_CALLED = 616;
+ ATOM_HEALTH_CONNECT_USAGE_STATS = 617;
+ ATOM_HEALTH_CONNECT_STORAGE_STATS = 618;
+ ATOM_HEALTH_CONNECT_API_INVOKED = 643;
+ ATOM_EXERCISE_ROUTE_API_CALLED = 654;
+ ATOM_SELINUX_AUDIT_LOG = 799;
+ ATOM_ONDEVICEPERSONALIZATION_API_CALLED = 711;
+ ATOM_CELLULAR_RADIO_POWER_STATE_CHANGED = 713;
+ ATOM_EMERGENCY_NUMBERS_INFO = 10180;
+ ATOM_DATA_NETWORK_VALIDATION = 10207;
+ ATOM_DATA_RAT_STATE_CHANGED = 854;
+ ATOM_CONNECTED_CHANNEL_CHANGED = 882;
+ ATOM_QUALIFIED_RAT_LIST_CHANGED = 634;
+ ATOM_QNS_IMS_CALL_DROP_STATS = 635;
+ ATOM_QNS_FALLBACK_RESTRICTION_CHANGED = 636;
+ ATOM_QNS_RAT_PREFERENCE_MISMATCH_INFO = 10177;
+ ATOM_QNS_HANDOVER_TIME_MILLIS = 10178;
+ ATOM_QNS_HANDOVER_PINGPONG = 10179;
+ ATOM_SATELLITE_CONTROLLER = 10182;
+ ATOM_SATELLITE_SESSION = 10183;
+ ATOM_SATELLITE_INCOMING_DATAGRAM = 10184;
+ ATOM_SATELLITE_OUTGOING_DATAGRAM = 10185;
+ ATOM_SATELLITE_PROVISION = 10186;
+ ATOM_SATELLITE_SOS_MESSAGE_RECOMMENDER = 10187;
+ ATOM_CARRIER_ROAMING_SATELLITE_SESSION = 10211;
+ ATOM_CARRIER_ROAMING_SATELLITE_CONTROLLER_STATS = 10212;
+ ATOM_CONTROLLER_STATS_PER_PACKAGE = 10213;
+ ATOM_SATELLITE_ENTITLEMENT = 10214;
+ ATOM_SATELLITE_CONFIG_UPDATER = 10215;
+ ATOM_SATELLITE_ACCESS_CONTROLLER = 10219;
+ ATOM_CELLULAR_IDENTIFIER_DISCLOSED = 800;
+ ATOM_KEYBOARD_CONFIGURED = 682;
+ ATOM_KEYBOARD_SYSTEMS_EVENT_REPORTED = 683;
+ ATOM_INPUTDEVICE_USAGE_REPORTED = 686;
+ ATOM_TOUCHPAD_USAGE = 10191;
+ ATOM_THREADNETWORK_TELEMETRY_DATA_REPORTED = 738;
+ ATOM_THREADNETWORK_TOPO_ENTRY_REPEATED = 739;
+ ATOM_THREADNETWORK_DEVICE_INFO_REPORTED = 740;
+ ATOM_CRONET_ENGINE_CREATED = 703;
+ ATOM_CRONET_TRAFFIC_REPORTED = 704;
+ ATOM_CRONET_ENGINE_BUILDER_INITIALIZED = 762;
+ ATOM_CRONET_HTTP_FLAGS_INITIALIZED = 763;
+ ATOM_CRONET_INITIALIZED = 764;
+ ATOM_WEAR_MODE_STATE_CHANGED = 715;
+ ATOM_RENDERER_INITIALIZED = 736;
+ ATOM_SCHEMA_VERSION_RECEIVED = 737;
+ ATOM_LAYOUT_INSPECTED = 741;
+ ATOM_LAYOUT_EXPRESSION_INSPECTED = 742;
+ ATOM_LAYOUT_ANIMATIONS_INSPECTED = 743;
+ ATOM_MATERIAL_COMPONENTS_INSPECTED = 744;
+ ATOM_TILE_REQUESTED = 745;
+ ATOM_STATE_RESPONSE_RECEIVED = 746;
+ ATOM_TILE_RESPONSE_RECEIVED = 747;
+ ATOM_INFLATION_FINISHED = 748;
+ ATOM_INFLATION_FAILED = 749;
+ ATOM_IGNORED_INFLATION_FAILURES_REPORTED = 750;
+ ATOM_DRAWABLE_RENDERED = 751;
+ ATOM_MEDIA_ACTION_REPORTED = 608;
+ ATOM_MEDIA_CONTROLS_LAUNCHED = 609;
+ ATOM_MEDIA_SESSION_STATE_CHANGED = 677;
+ ATOM_WEAR_MEDIA_OUTPUT_SWITCHER_DEVICE_SCAN_API_LATENCY = 757;
+ ATOM_WEAR_MEDIA_OUTPUT_SWITCHER_SASS_DEVICE_UNAVAILABLE = 758;
+ ATOM_WEAR_MEDIA_OUTPUT_SWITCHER_FASTPAIR_API_TIMEOUT = 759;
+ ATOM_MEDIATOR_UPDATED = 721;
+ ATOM_SYSPROXY_BLUETOOTH_BYTES_TRANSFER = 10196;
+ ATOM_SYSPROXY_CONNECTION_UPDATED = 786;
+ ATOM_ADAPTIVE_AUTH_UNLOCK_AFTER_LOCK_REPORTED = 820;
+ ATOM_FEDERATED_COMPUTE_API_CALLED = 712;
+ ATOM_FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED = 771;
+ ATOM_EXAMPLE_ITERATOR_NEXT_LATENCY_REPORTED = 838;
+ ATOM_RKPD_POOL_STATS = 664;
+ ATOM_RKPD_CLIENT_OPERATION = 665;
+ ATOM_CPU_POLICY = 10199;
+ ATOM_ATOM_9999 = 9999;
+ ATOM_ATOM_99999 = 99999;
+ ATOM_SCREEN_OFF_REPORTED = 776;
+ ATOM_SCREEN_TIMEOUT_OVERRIDE_REPORTED = 836;
+ ATOM_SCREEN_INTERACTIVE_SESSION_REPORTED = 837;
+ ATOM_SCREEN_DIM_REPORTED = 867;
+ ATOM_FULL_SCREEN_INTENT_LAUNCHED = 631;
+ ATOM_BAL_ALLOWED = 632;
+ ATOM_IN_TASK_ACTIVITY_STARTED = 685;
+ ATOM_CACHED_APPS_HIGH_WATERMARK = 10189;
+ ATOM_STYLUS_PREDICTION_METRICS_REPORTED = 718;
+ ATOM_USER_RISK_EVENT_REPORTED = 725;
+ ATOM_MEDIA_PROJECTION_STATE_CHANGED = 729;
+ ATOM_MEDIA_PROJECTION_TARGET_CHANGED = 730;
+ ATOM_EXCESSIVE_BINDER_PROXY_COUNT_REPORTED = 853;
+ ATOM_PROXY_BYTES_TRANSFER_BY_FG_BG = 10200;
+ ATOM_MOBILE_BYTES_TRANSFER_BY_PROC_STATE = 10204;
+ ATOM_BIOMETRIC_FRR_NOTIFICATION = 817;
+ ATOM_SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION = 830;
+ ATOM_SENSITIVE_NOTIFICATION_APP_PROTECTION_SESSION = 831;
+ ATOM_SENSITIVE_NOTIFICATION_APP_PROTECTION_APPLIED = 832;
+ ATOM_SENSITIVE_NOTIFICATION_REDACTION = 833;
+ ATOM_SENSITIVE_CONTENT_APP_PROTECTION = 835;
+ ATOM_APP_RESTRICTION_STATE_CHANGED = 866;
+ ATOM_DREAM_SETTING_CHANGED = 705;
+ ATOM_DREAM_SETTING_SNAPSHOT = 10192;
+ ATOM_BOOT_INTEGRITY_INFO_REPORTED = 775;
ATOM_WIFI_AWARE_NDP_REPORTED = 638;
ATOM_WIFI_AWARE_ATTACH_REPORTED = 639;
ATOM_WIFI_SELF_RECOVERY_TRIGGERED = 661;
@@ -2731,39 +2920,99 @@
ATOM_WIFI_LOCAL_ONLY_REQUEST_SCAN_TRIGGERED = 693;
ATOM_WIFI_THREAD_TASK_EXECUTED = 694;
ATOM_WIFI_STATE_CHANGED = 700;
+ ATOM_PNO_SCAN_STARTED = 719;
+ ATOM_PNO_SCAN_STOPPED = 720;
+ ATOM_WIFI_IS_UNUSABLE_REPORTED = 722;
+ ATOM_WIFI_AP_CAPABILITIES_REPORTED = 723;
+ ATOM_SOFT_AP_STATE_CHANGED = 805;
+ ATOM_SCORER_PREDICTION_RESULT_REPORTED = 884;
ATOM_WIFI_AWARE_CAPABILITIES = 10190;
ATOM_WIFI_MODULE_INFO = 10193;
- ATOM_SETTINGS_SPA_REPORTED = 622;
+ ATOM_WIFI_SETTING_INFO = 10194;
+ ATOM_WIFI_COMPLEX_SETTING_INFO = 10195;
+ ATOM_WIFI_CONFIGURED_NETWORK_INFO = 10198;
+ ATOM_MTE_STATE = 10181;
+ ATOM_HOTWORD_EGRESS_SIZE_ATOM_REPORTED = 761;
+ ATOM_SANDBOX_API_CALLED = 488;
+ ATOM_SANDBOX_ACTIVITY_EVENT_OCCURRED = 735;
+ ATOM_SDK_SANDBOX_RESTRICTED_ACCESS_IN_SESSION = 796;
+ ATOM_SANDBOX_SDK_STORAGE = 10159;
ATOM_EXPRESS_EVENT_REPORTED = 528;
ATOM_EXPRESS_HISTOGRAM_SAMPLE_REPORTED = 593;
ATOM_EXPRESS_UID_EVENT_REPORTED = 644;
ATOM_EXPRESS_UID_HISTOGRAM_SAMPLE_REPORTED = 658;
- ATOM_PERMISSION_RATIONALE_DIALOG_VIEWED = 645;
- ATOM_PERMISSION_RATIONALE_DIALOG_ACTION_REPORTED = 646;
- ATOM_APP_DATA_SHARING_UPDATES_NOTIFICATION_INTERACTION = 647;
- ATOM_APP_DATA_SHARING_UPDATES_FRAGMENT_VIEWED = 648;
- ATOM_APP_DATA_SHARING_UPDATES_FRAGMENT_ACTION_REPORTED = 649;
- ATOM_WS_INCOMING_CALL_ACTION_REPORTED = 626;
- ATOM_WS_CALL_DISCONNECTION_REPORTED = 627;
- ATOM_WS_CALL_DURATION_REPORTED = 628;
- ATOM_WS_CALL_USER_EXPERIENCE_LATENCY_REPORTED = 629;
- ATOM_WS_CALL_INTERACTION_REPORTED = 630;
- ATOM_FULL_SCREEN_INTENT_LAUNCHED = 631;
- ATOM_BAL_ALLOWED = 632;
- ATOM_IN_TASK_ACTIVITY_STARTED = 685;
- ATOM_CACHED_APPS_HIGH_WATERMARK = 10189;
- ATOM_ODREFRESH_REPORTED = 366;
- ATOM_ODSIGN_REPORTED = 548;
- ATOM_ART_DATUM_REPORTED = 332;
- ATOM_ART_DEVICE_DATUM_REPORTED = 550;
- ATOM_ART_DATUM_DELTA_REPORTED = 565;
- ATOM_BACKGROUND_DEXOPT_JOB_ENDED = 467;
- ATOM_WEAR_ADAPTIVE_SUSPEND_STATS_REPORTED = 619;
- ATOM_WEAR_POWER_ANOMALY_SERVICE_OPERATIONAL_STATS_REPORTED = 620;
- ATOM_WEAR_POWER_ANOMALY_SERVICE_EVENT_STATS_REPORTED = 621;
- ATOM_EMERGENCY_STATE_CHANGED = 633;
- ATOM_DND_STATE_CHANGED = 657;
- ATOM_MTE_STATE = 10181;
+ ATOM_IKE_SESSION_TERMINATED = 678;
+ ATOM_IKE_LIVENESS_CHECK_SESSION_VALIDATED = 760;
+ ATOM_NEGOTIATED_SECURITY_ASSOCIATION = 821;
+ ATOM_APP_SEARCH_SET_SCHEMA_STATS_REPORTED = 385;
+ ATOM_APP_SEARCH_SCHEMA_MIGRATION_STATS_REPORTED = 579;
+ ATOM_APP_SEARCH_USAGE_SEARCH_INTENT_STATS_REPORTED = 825;
+ ATOM_APP_SEARCH_USAGE_SEARCH_INTENT_RAW_QUERY_STATS_REPORTED = 826;
+ ATOM_DEVICE_POLICY_MANAGEMENT_MODE = 10216;
+ ATOM_DEVICE_POLICY_STATE = 10217;
+ ATOM_DESKTOP_MODE_UI_CHANGED = 818;
+ ATOM_DESKTOP_MODE_SESSION_TASK_UPDATE = 819;
+ ATOM_MEDIA_CODEC_RECLAIM_REQUEST_COMPLETED = 600;
+ ATOM_MEDIA_CODEC_STARTED = 641;
+ ATOM_MEDIA_CODEC_STOPPED = 642;
+ ATOM_MEDIA_CODEC_RENDERED = 684;
+ ATOM_MEDIA_EDITING_ENDED_REPORTED = 798;
+ ATOM_CAR_WAKEUP_FROM_SUSPEND_REPORTED = 852;
+ ATOM_PLUGIN_INITIALIZED = 655;
+ ATOM_CAR_RECENTS_EVENT_REPORTED = 770;
+ ATOM_CAR_CALM_MODE_EVENT_REPORTED = 797;
+ ATOM_CAMERA_FEATURE_COMBINATION_QUERY_EVENT = 900;
+ ATOM_THERMAL_STATUS_CALLED = 772;
+ ATOM_THERMAL_HEADROOM_CALLED = 773;
+ ATOM_THERMAL_HEADROOM_THRESHOLDS_CALLED = 774;
+ ATOM_ADPF_HINT_SESSION_TID_CLEANUP = 839;
+ ATOM_THERMAL_HEADROOM_THRESHOLDS = 10201;
+ ATOM_ADPF_SESSION_SNAPSHOT = 10218;
+ ATOM_BLUETOOTH_HASHED_DEVICE_NAME_REPORTED = 613;
+ ATOM_BLUETOOTH_L2CAP_COC_CLIENT_CONNECTION = 614;
+ ATOM_BLUETOOTH_L2CAP_COC_SERVER_CONNECTION = 615;
+ ATOM_BLUETOOTH_LE_SESSION_CONNECTED = 656;
+ ATOM_RESTRICTED_BLUETOOTH_DEVICE_NAME_REPORTED = 666;
+ ATOM_BLUETOOTH_PROFILE_CONNECTION_ATTEMPTED = 696;
+ ATOM_BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED = 781;
+ ATOM_BLUETOOTH_RFCOMM_CONNECTION_ATTEMPTED = 782;
+ ATOM_REMOTE_DEVICE_INFORMATION_WITH_METRIC_ID = 862;
+ ATOM_LE_APP_SCAN_STATE_CHANGED = 870;
+ ATOM_LE_RADIO_SCAN_STOPPED = 871;
+ ATOM_LE_SCAN_RESULT_RECEIVED = 872;
+ ATOM_LE_SCAN_ABUSED = 873;
+ ATOM_LE_ADV_STATE_CHANGED = 874;
+ ATOM_LE_ADV_ERROR_REPORTED = 875;
+ ATOM_A2DP_SESSION_REPORTED = 904;
+ ATOM_BLUETOOTH_CROSS_LAYER_EVENT_REPORTED = 916;
+ ATOM_BROADCAST_AUDIO_SESSION_REPORTED = 927;
+ ATOM_BROADCAST_AUDIO_SYNC_REPORTED = 928;
+ ATOM_DEVICE_LOCK_CHECK_IN_REQUEST_REPORTED = 726;
+ ATOM_DEVICE_LOCK_PROVISIONING_COMPLETE_REPORTED = 727;
+ ATOM_DEVICE_LOCK_KIOSK_APP_REQUEST_REPORTED = 728;
+ ATOM_DEVICE_LOCK_CHECK_IN_RETRY_REPORTED = 789;
+ ATOM_DEVICE_LOCK_PROVISION_FAILURE_REPORTED = 790;
+ ATOM_DEVICE_LOCK_LOCK_UNLOCK_DEVICE_FAILURE_REPORTED = 791;
+ ATOM_APPLICATION_GRAMMATICAL_INFLECTION_CHANGED = 584;
+ ATOM_SYSTEM_GRAMMATICAL_INFLECTION_CHANGED = 816;
+ ATOM_EMERGENCY_NUMBER_DIALED = 637;
+ ATOM_JSSCRIPTENGINE_LATENCY_REPORTED = 483;
+ ATOM_AD_SERVICES_API_CALLED = 435;
+ ATOM_AD_SERVICES_MESUREMENT_REPORTS_UPLOADED = 436;
+ ATOM_MOBILE_DATA_DOWNLOAD_FILE_GROUP_STATUS_REPORTED = 490;
+ ATOM_MOBILE_DATA_DOWNLOAD_DOWNLOAD_RESULT_REPORTED = 502;
+ ATOM_AD_SERVICES_SETTINGS_USAGE_REPORTED = 493;
+ ATOM_BACKGROUND_FETCH_PROCESS_REPORTED = 496;
+ ATOM_UPDATE_CUSTOM_AUDIENCE_PROCESS_REPORTED = 497;
+ ATOM_RUN_AD_BIDDING_PROCESS_REPORTED = 498;
+ ATOM_RUN_AD_SCORING_PROCESS_REPORTED = 499;
+ ATOM_RUN_AD_SELECTION_PROCESS_REPORTED = 500;
+ ATOM_RUN_AD_BIDDING_PER_CA_PROCESS_REPORTED = 501;
+ ATOM_MOBILE_DATA_DOWNLOAD_FILE_GROUP_STORAGE_STATS_REPORTED = 503;
+ ATOM_AD_SERVICES_MEASUREMENT_REGISTRATIONS = 512;
+ ATOM_AD_SERVICES_GET_TOPICS_REPORTED = 535;
+ ATOM_AD_SERVICES_EPOCH_COMPUTATION_GET_TOP_TOPICS_REPORTED = 536;
+ ATOM_AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED = 537;
ATOM_AD_SERVICES_BACK_COMPAT_GET_TOPICS_REPORTED = 598;
ATOM_AD_SERVICES_BACK_COMPAT_EPOCH_COMPUTATION_CLASSIFIER_REPORTED = 599;
ATOM_AD_SERVICES_MEASUREMENT_DEBUG_KEYS = 640;
@@ -2773,68 +3022,68 @@
ATOM_AD_SERVICES_MEASUREMENT_ATTRIBUTION = 674;
ATOM_AD_SERVICES_MEASUREMENT_JOBS = 675;
ATOM_AD_SERVICES_MEASUREMENT_WIPEOUT = 676;
+ ATOM_AD_SERVICES_MEASUREMENT_AD_ID_MATCH_FOR_DEBUG_KEYS = 695;
+ ATOM_AD_SERVICES_ENROLLMENT_DATA_STORED = 697;
+ ATOM_AD_SERVICES_ENROLLMENT_FILE_DOWNLOADED = 698;
+ ATOM_AD_SERVICES_ENROLLMENT_MATCHED = 699;
ATOM_AD_SERVICES_CONSENT_MIGRATED = 702;
- ATOM_RKPD_POOL_STATS = 664;
- ATOM_RKPD_CLIENT_OPERATION = 665;
- ATOM_AUTOFILL_UI_EVENT_REPORTED = 603;
- ATOM_AUTOFILL_FILL_REQUEST_REPORTED = 604;
- ATOM_AUTOFILL_FILL_RESPONSE_REPORTED = 605;
- ATOM_AUTOFILL_SAVE_EVENT_REPORTED = 606;
- ATOM_AUTOFILL_SESSION_COMMITTED = 607;
- ATOM_AUTOFILL_FIELD_CLASSIFICATION_EVENT_REPORTED = 659;
+ ATOM_AD_SERVICES_ENROLLMENT_FAILED = 714;
+ ATOM_AD_SERVICES_MEASUREMENT_CLICK_VERIFICATION = 756;
+ ATOM_AD_SERVICES_ENCRYPTION_KEY_FETCHED = 765;
+ ATOM_AD_SERVICES_ENCRYPTION_KEY_DB_TRANSACTION_ENDED = 766;
+ ATOM_DESTINATION_REGISTERED_BEACONS = 767;
+ ATOM_REPORT_INTERACTION_API_CALLED = 768;
+ ATOM_INTERACTION_REPORTING_TABLE_CLEARED = 769;
+ ATOM_APP_MANIFEST_CONFIG_HELPER_CALLED = 788;
+ ATOM_AD_FILTERING_PROCESS_JOIN_CA_REPORTED = 793;
+ ATOM_AD_FILTERING_PROCESS_AD_SELECTION_REPORTED = 794;
+ ATOM_AD_COUNTER_HISTOGRAM_UPDATER_REPORTED = 795;
+ ATOM_SIGNATURE_VERIFICATION = 807;
+ ATOM_K_ANON_IMMEDIATE_SIGN_JOIN_STATUS_REPORTED = 808;
+ ATOM_K_ANON_BACKGROUND_JOB_STATUS_REPORTED = 809;
+ ATOM_K_ANON_INITIALIZE_STATUS_REPORTED = 810;
+ ATOM_K_ANON_SIGN_STATUS_REPORTED = 811;
+ ATOM_K_ANON_JOIN_STATUS_REPORTED = 812;
+ ATOM_K_ANON_KEY_ATTESTATION_STATUS_REPORTED = 813;
+ ATOM_GET_AD_SELECTION_DATA_API_CALLED = 814;
+ ATOM_GET_AD_SELECTION_DATA_BUYER_INPUT_GENERATED = 815;
+ ATOM_BACKGROUND_JOB_SCHEDULING_REPORTED = 834;
+ ATOM_TOPICS_ENCRYPTION_EPOCH_COMPUTATION_REPORTED = 840;
+ ATOM_TOPICS_ENCRYPTION_GET_TOPICS_REPORTED = 841;
+ ATOM_ADSERVICES_SHELL_COMMAND_CALLED = 842;
+ ATOM_UPDATE_SIGNALS_API_CALLED = 843;
+ ATOM_ENCODING_JOB_RUN = 844;
+ ATOM_ENCODING_JS_FETCH = 845;
+ ATOM_ENCODING_JS_EXECUTION = 846;
+ ATOM_PERSIST_AD_SELECTION_RESULT_CALLED = 847;
+ ATOM_SERVER_AUCTION_KEY_FETCH_CALLED = 848;
+ ATOM_SERVER_AUCTION_BACKGROUND_KEY_FETCH_ENABLED = 849;
+ ATOM_AD_SERVICES_MEASUREMENT_PROCESS_ODP_REGISTRATION = 864;
+ ATOM_AD_SERVICES_MEASUREMENT_NOTIFY_REGISTRATION_TO_ODP = 865;
+ ATOM_SELECT_ADS_FROM_OUTCOMES_API_CALLED = 876;
+ ATOM_REPORT_IMPRESSION_API_CALLED = 877;
+ ATOM_AD_SERVICES_ENROLLMENT_TRANSACTION_STATS = 885;
+ ATOM_EXTERNAL_DISPLAY_STATE_CHANGED = 806;
+ ATOM_DISPLAY_MODE_DIRECTOR_VOTE_CHANGED = 792;
ATOM_TEST_EXTENSION_ATOM_REPORTED = 660;
ATOM_TEST_RESTRICTED_ATOM_REPORTED = 672;
ATOM_STATS_SOCKET_LOSS_REPORTED = 752;
- ATOM_PLUGIN_INITIALIZED = 655;
- ATOM_TV_LOW_POWER_STANDBY_POLICY = 679;
+ ATOM_NFC_OBSERVE_MODE_STATE_CHANGED = 855;
+ ATOM_NFC_FIELD_CHANGED = 856;
+ ATOM_NFC_POLLING_LOOP_NOTIFICATION_REPORTED = 857;
+ ATOM_NFC_PROPRIETARY_CAPABILITIES_REPORTED = 858;
ATOM_LOCKSCREEN_SHORTCUT_SELECTED = 611;
ATOM_LOCKSCREEN_SHORTCUT_TRIGGERED = 612;
- ATOM_EMERGENCY_NUMBERS_INFO = 10180;
- ATOM_QUALIFIED_RAT_LIST_CHANGED = 634;
- ATOM_QNS_IMS_CALL_DROP_STATS = 635;
- ATOM_QNS_FALLBACK_RESTRICTION_CHANGED = 636;
- ATOM_QNS_RAT_PREFERENCE_MISMATCH_INFO = 10177;
- ATOM_QNS_HANDOVER_TIME_MILLIS = 10178;
- ATOM_QNS_HANDOVER_PINGPONG = 10179;
- ATOM_SATELLITE_CONTROLLER = 10182;
- ATOM_SATELLITE_SESSION = 10183;
- ATOM_SATELLITE_INCOMING_DATAGRAM = 10184;
- ATOM_SATELLITE_OUTGOING_DATAGRAM = 10185;
- ATOM_SATELLITE_PROVISION = 10186;
- ATOM_SATELLITE_SOS_MESSAGE_RECOMMENDER = 10187;
- ATOM_IKE_SESSION_TERMINATED = 678;
- ATOM_IKE_LIVENESS_CHECK_SESSION_VALIDATED = 760;
- ATOM_BLUETOOTH_HASHED_DEVICE_NAME_REPORTED = 613;
- ATOM_BLUETOOTH_L2CAP_COC_CLIENT_CONNECTION = 614;
- ATOM_BLUETOOTH_L2CAP_COC_SERVER_CONNECTION = 615;
- ATOM_BLUETOOTH_LE_SESSION_CONNECTED = 656;
- ATOM_RESTRICTED_BLUETOOTH_DEVICE_NAME_REPORTED = 666;
- ATOM_BLUETOOTH_PROFILE_CONNECTION_ATTEMPTED = 696;
- ATOM_HEALTH_CONNECT_UI_IMPRESSION = 623;
- ATOM_HEALTH_CONNECT_UI_INTERACTION = 624;
- ATOM_HEALTH_CONNECT_APP_OPENED_REPORTED = 625;
- ATOM_HEALTH_CONNECT_API_CALLED = 616;
- ATOM_HEALTH_CONNECT_USAGE_STATS = 617;
- ATOM_HEALTH_CONNECT_STORAGE_STATS = 618;
- ATOM_HEALTH_CONNECT_API_INVOKED = 643;
- ATOM_EXERCISE_ROUTE_API_CALLED = 654;
- ATOM_ATOM_9999 = 9999;
- ATOM_ATOM_99999 = 99999;
- ATOM_THREADNETWORK_TELEMETRY_DATA_REPORTED = 738;
- ATOM_THREADNETWORK_TOPO_ENTRY_REPEATED = 739;
- ATOM_THREADNETWORK_DEVICE_INFO_REPORTED = 740;
- ATOM_EMERGENCY_NUMBER_DIALED = 637;
- ATOM_SANDBOX_API_CALLED = 488;
- ATOM_SANDBOX_ACTIVITY_EVENT_OCCURRED = 735;
- ATOM_SANDBOX_SDK_STORAGE = 10159;
- ATOM_CRONET_ENGINE_CREATED = 703;
- ATOM_CRONET_TRAFFIC_REPORTED = 704;
- ATOM_CRONET_ENGINE_BUILDER_INITIALIZED = 762;
- ATOM_CRONET_HTTP_FLAGS_INITIALIZED = 763;
- ATOM_CRONET_INITIALIZED = 764;
- ATOM_DAILY_KEEPALIVE_INFO_REPORTED = 650;
- ATOM_IP_CLIENT_RA_INFO_REPORTED = 778;
- ATOM_APF_SESSION_INFO_REPORTED = 777;
+ ATOM_LAUNCHER_IMPRESSION_EVENT_V2 = 716;
+ ATOM_DISPLAY_SWITCH_LATENCY_TRACKED = 753;
+ ATOM_NOTIFICATION_LISTENER_SERVICE = 829;
+ ATOM_NAV_HANDLE_TOUCH_POINTS = 869;
+ ATOM_WEAR_ADAPTIVE_SUSPEND_STATS_REPORTED = 619;
+ ATOM_WEAR_POWER_ANOMALY_SERVICE_OPERATIONAL_STATS_REPORTED = 620;
+ ATOM_WEAR_POWER_ANOMALY_SERVICE_EVENT_STATS_REPORTED = 621;
+ ATOM_APEX_INSTALLATION_REQUESTED = 732;
+ ATOM_APEX_INSTALLATION_STAGED = 733;
+ ATOM_APEX_INSTALLATION_ENDED = 734;
ATOM_CREDENTIAL_MANAGER_API_CALLED = 585;
ATOM_CREDENTIAL_MANAGER_INIT_PHASE_REPORTED = 651;
ATOM_CREDENTIAL_MANAGER_CANDIDATE_PHASE_REPORTED = 652;
@@ -2845,12 +3094,7 @@
ATOM_CREDENTIAL_MANAGER_AUTH_CLICK_REPORTED = 670;
ATOM_CREDENTIAL_MANAGER_APIV2_CALLED = 671;
ATOM_UWB_ACTIVITY_INFO = 10188;
- ATOM_MEDIA_ACTION_REPORTED = 608;
- ATOM_MEDIA_CONTROLS_LAUNCHED = 609;
- ATOM_MEDIA_CODEC_RECLAIM_REQUEST_COMPLETED = 600;
- ATOM_MEDIA_CODEC_STARTED = 641;
- ATOM_MEDIA_CODEC_STOPPED = 642;
- ATOM_MEDIA_CODEC_RENDERED = 684;
+ ATOM_DND_STATE_CHANGED = 657;
}
// End of protos/perfetto/config/statsd/atom_ids.proto
@@ -3351,7 +3595,7 @@
// Begin of protos/perfetto/config/data_source_config.proto
// The configuration that is passed to each data source when starting tracing.
-// Next id: 131
+// Next id: 132
message DataSourceConfig {
enum SessionInitiator {
SESSION_INITIATOR_UNSPECIFIED = 0;
@@ -3506,6 +3750,9 @@
// Data source name: android.windowmanager
optional WindowManagerConfig windowmanager_config = 130 [lazy = true];
+ // Data source name: org.chromium.system_metrics
+ optional ChromiumSystemMetricsConfig chromium_system_metrics = 131 [lazy = true];
+
// This is a fallback mechanism to send a free-form text config to the
// producer. In theory this should never be needed. All the code that
// is part of the platform (i.e. traced service) is supposed to *not* truncate
@@ -3943,11 +4190,8 @@
}
optional IncrementalStateConfig incremental_state_config = 21;
- // Additional guardrail used by the Perfetto command line client.
- // On user builds when --dropbox is set perfetto will refuse to trace unless
- // this is also set.
- // Added in Q.
- optional bool allow_user_build_tracing = 19;
+ // No longer needed as we unconditionally allow tracing on user builds.
+ optional bool allow_user_build_tracing = 19 [deprecated = true];
// If set the tracing service will ensure there is at most one tracing session
// with this key.
@@ -7294,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 {
@@ -7351,6 +7605,18 @@
// End of protos/perfetto/trace/ftrace/dcvsh.proto
+// Begin of protos/perfetto/trace/ftrace/devfreq.proto
+
+message DevfreqFrequencyFtraceEvent {
+ optional string dev_name = 1;
+ optional uint64 freq = 2;
+ optional uint64 prev_freq = 3;
+ optional uint64 busy_time = 4;
+ optional uint64 total_time = 5;
+}
+
+// End of protos/perfetto/trace/ftrace/devfreq.proto
+
// Begin of protos/perfetto/trace/ftrace/dma_fence.proto
message DmaFenceInitFtraceEvent {
@@ -8588,6 +8854,17 @@
repeated Field field = 2;
}
+message KprobeEvent {
+ enum KprobeType {
+ KPROBE_TYPE_UNKNOWN = 0;
+ KPROBE_TYPE_BEGIN = 1;
+ KPROBE_TYPE_END = 2;
+ KPROBE_TYPE_INSTANT = 3;
+ }
+ optional string name = 1;
+ optional KprobeType type = 2;
+}
+
// End of protos/perfetto/trace/ftrace/generic.proto
// Begin of protos/perfetto/trace/ftrace/google_icc_trace.proto
@@ -11144,6 +11421,9 @@
PixelMmKswapdWakeFtraceEvent pixel_mm_kswapd_wake = 538;
PixelMmKswapdDoneFtraceEvent pixel_mm_kswapd_done = 539;
SchedWakeupTaskAttrFtraceEvent sched_wakeup_task_attr = 540;
+ DevfreqFrequencyFtraceEvent devfreq_frequency = 541;
+ KprobeEvent kprobe_event = 542;
+ ParamSetValueCpmFtraceEvent param_set_value_cpm = 543;
}
}
@@ -11194,6 +11474,17 @@
optional uint64 read_events = 9;
}
+// Kprobe statistical data, gathered from /sys/kernel/tracing/kprobe_profile.
+message FtraceKprobeStats {
+ // Cumulative number of kprobe events generated for this function
+ optional int64 hits = 1;
+ // Cumulative number of kprobe events that could not be generated for this
+ // function and were missed. This happens when too much nesting
+ // happens between a kprobe and its kretprobe, overflowing the
+ // maxactives buffer.
+ optional int64 misses = 2;
+}
+
// Errors and kernel buffer stats for the ftrace data source.
message FtraceStats {
enum Phase {
@@ -11245,6 +11536,9 @@
// Any traces with entries in this field should be investigated, as they
// indicate a bug in perfetto or the kernel.
repeated FtraceParseStatus ftrace_parse_errors = 9;
+
+ // Kprobe profile stats for functions hits and misses
+ optional FtraceKprobeStats kprobe_stats = 10;
}
enum FtraceParseStatus {
@@ -13429,6 +13723,9 @@
// sources have been recording events.
bool all_data_sources_started = 1;
+ // Emitted when a flush is started.
+ bool flush_started = 9;
+
// Emitted when all data sources have been flushed successfully or with an
// error (including timeouts). This can generally happen many times over the
// course of the trace.
@@ -13762,6 +14059,16 @@
//
// N.B. This is not the native size of this object.
optional int64 native_allocation_registry_size_field = 8;
+
+ enum HeapType {
+ HEAP_TYPE_UNKNOWN = 0;
+ HEAP_TYPE_APP = 1;
+ HEAP_TYPE_ZYGOTE = 2;
+ HEAP_TYPE_BOOT_IMAGE = 3;
+ }
+ // To reduce the space required we only emit the heap type if it has changed
+ // from the previous object we recorded.
+ optional HeapType heap_type_delta = 9;
}
message HeapGraph {
@@ -14562,13 +14869,23 @@
optional string machine = 4;
}
+// Next id: 15;
message SystemInfo {
optional Utsname utsname = 1;
optional string android_build_fingerprint = 2;
+ // The manufacturer of the product/hardware.
+ // Source : "ro.product.manufacturer"
+ // Introduced after Android W in Nov 2024 and is not supported on older
+ // versions.
+ optional string android_device_manufacturer = 14;
+
// The SoC model from which trace is collected
optional string android_soc_model = 9;
+ // The guest SoC model from which trace is collected in case of VMs
+ optional string android_guest_soc_model = 13;
+
// The hardware reversion from android device
optional string android_hardware_revision = 10;
@@ -15093,7 +15410,7 @@
// |TrackEvent::track_uuid|. It is possible but not necessary to emit a
// TrackDescriptor for this implicit track.
//
-// Next id: 11.
+// Next id: 14.
message TrackDescriptor {
// Unique ID that identifies this track. This ID is global to the whole trace.
// Producers should ensure that it is unlikely to clash with IDs emitted by
@@ -15118,6 +15435,9 @@
// This field is only set by the SDK when perfetto::StaticString is
// provided.
string static_name = 10;
+ // Equivalent to name, used just to mark that the data is coming from
+ // android.os.Trace.
+ string atrace_name = 13;
}
// Associate the track with a process, making it the process-global track.
@@ -15152,6 +15472,35 @@
// system events use nanoseconds. It results in broken event nesting when
// track events and system events share a track.
optional bool disallow_merging_with_system_tracks = 9;
+
+ // Specifies how the UI should display child tracks of this track (i.e. tracks
+ // where `parent_uuid` is specified to this track `uuid`). Note that this
+ // value is simply a *hint* to the UI: the UI is not guarnateed to respect
+ // this if it has a good reason not to do so.
+ enum ChildTracksOrdering {
+ // The default ordering, with no bearing on how the UI will visualise the
+ // tracks.
+ UNKNOWN = 0;
+
+ // Order tracks by `name` or `static_name` depending on which one has been
+ // specified.
+ LEXICOGRAPHIC = 1;
+
+ // Order tracks by the first `ts` event in a track.
+ CHRONOLOGICAL = 2;
+
+ // Order tracks by `sibling_order_rank` of child tracks. Child tracks with
+ // the lower values will be shown before tracks with higher values. Tracks
+ // with no value will be treated as having 0 rank.
+ EXPLICIT = 3;
+ }
+ optional ChildTracksOrdering child_ordering = 11;
+
+ // An opaque value which allows specifying how two sibling tracks should be
+ // ordered relative to each other: tracks with lower ranks will appear before
+ // tracks with higher ranks. An unspecified rank will be treated as a rank of
+ // 0.
+ optional int32 sibling_order_rank = 12;
}
// End of protos/perfetto/trace/track_event/track_descriptor.proto
diff --git a/protos/perfetto/trace/profiling/heap_graph.proto b/protos/perfetto/trace/profiling/heap_graph.proto
index d57568f..0713a9f 100644
--- a/protos/perfetto/trace/profiling/heap_graph.proto
+++ b/protos/perfetto/trace/profiling/heap_graph.proto
@@ -116,6 +116,16 @@
//
// N.B. This is not the native size of this object.
optional int64 native_allocation_registry_size_field = 8;
+
+ enum HeapType {
+ HEAP_TYPE_UNKNOWN = 0;
+ HEAP_TYPE_APP = 1;
+ HEAP_TYPE_ZYGOTE = 2;
+ HEAP_TYPE_BOOT_IMAGE = 3;
+ }
+ // To reduce the space required we only emit the heap type if it has changed
+ // from the previous object we recorded.
+ optional HeapType heap_type_delta = 9;
}
message HeapGraph {
diff --git a/protos/perfetto/trace/system_info.proto b/protos/perfetto/trace/system_info.proto
index 32cf887..0048a2c 100644
--- a/protos/perfetto/trace/system_info.proto
+++ b/protos/perfetto/trace/system_info.proto
@@ -25,13 +25,23 @@
optional string machine = 4;
}
+// Next id: 15;
message SystemInfo {
optional Utsname utsname = 1;
optional string android_build_fingerprint = 2;
+ // The manufacturer of the product/hardware.
+ // Source : "ro.product.manufacturer"
+ // Introduced after Android W in Nov 2024 and is not supported on older
+ // versions.
+ optional string android_device_manufacturer = 14;
+
// The SoC model from which trace is collected
optional string android_soc_model = 9;
+ // The guest SoC model from which trace is collected in case of VMs
+ optional string android_guest_soc_model = 13;
+
// The hardware reversion from android device
optional string android_hardware_revision = 10;
diff --git a/protos/perfetto/trace/track_event/BUILD.gn b/protos/perfetto/trace/track_event/BUILD.gn
index c101c46..a7467d7 100644
--- a/protos/perfetto/trace/track_event/BUILD.gn
+++ b/protos/perfetto/trace/track_event/BUILD.gn
@@ -44,11 +44,6 @@
"track_descriptor.proto",
"track_event.proto",
]
-}
-
-perfetto_proto_library("@TYPE@") {
- proto_generators = [ "descriptor" ]
generate_descriptor = "track_event.descriptor"
- sources = [ "track_event.proto" ]
- deps = [ ":source_set" ]
+ descriptor_root_source = "track_event.proto"
}
diff --git a/protos/perfetto/trace/track_event/track_descriptor.proto b/protos/perfetto/trace/track_event/track_descriptor.proto
index 76890f2..2f76c87 100644
--- a/protos/perfetto/trace/track_event/track_descriptor.proto
+++ b/protos/perfetto/trace/track_event/track_descriptor.proto
@@ -37,7 +37,7 @@
// |TrackEvent::track_uuid|. It is possible but not necessary to emit a
// TrackDescriptor for this implicit track.
//
-// Next id: 11.
+// Next id: 14.
message TrackDescriptor {
// Unique ID that identifies this track. This ID is global to the whole trace.
// Producers should ensure that it is unlikely to clash with IDs emitted by
@@ -62,6 +62,9 @@
// This field is only set by the SDK when perfetto::StaticString is
// provided.
string static_name = 10;
+ // Equivalent to name, used just to mark that the data is coming from
+ // android.os.Trace.
+ string atrace_name = 13;
}
// Associate the track with a process, making it the process-global track.
@@ -96,4 +99,33 @@
// system events use nanoseconds. It results in broken event nesting when
// track events and system events share a track.
optional bool disallow_merging_with_system_tracks = 9;
+
+ // Specifies how the UI should display child tracks of this track (i.e. tracks
+ // where `parent_uuid` is specified to this track `uuid`). Note that this
+ // value is simply a *hint* to the UI: the UI is not guarnateed to respect
+ // this if it has a good reason not to do so.
+ enum ChildTracksOrdering {
+ // The default ordering, with no bearing on how the UI will visualise the
+ // tracks.
+ UNKNOWN = 0;
+
+ // Order tracks by `name` or `static_name` depending on which one has been
+ // specified.
+ LEXICOGRAPHIC = 1;
+
+ // Order tracks by the first `ts` event in a track.
+ CHRONOLOGICAL = 2;
+
+ // Order tracks by `sibling_order_rank` of child tracks. Child tracks with
+ // the lower values will be shown before tracks with higher values. Tracks
+ // with no value will be treated as having 0 rank.
+ EXPLICIT = 3;
+ }
+ optional ChildTracksOrdering child_ordering = 11;
+
+ // An opaque value which allows specifying how two sibling tracks should be
+ // ordered relative to each other: tracks with lower ranks will appear before
+ // tracks with higher ranks. An unspecified rank will be treated as a rank of
+ // 0.
+ optional int32 sibling_order_rank = 12;
}
diff --git a/protos/perfetto/trace_processor/BUILD.gn b/protos/perfetto/trace_processor/BUILD.gn
index f514f42..1d25669 100644
--- a/protos/perfetto/trace_processor/BUILD.gn
+++ b/protos/perfetto/trace_processor/BUILD.gn
@@ -20,7 +20,6 @@
"cpp",
"lite",
"zero",
- "source_set",
]
deps = [ "../common:@TYPE@" ] # needed for descriptor.proto.
sources = []
@@ -29,16 +28,14 @@
}
}
-perfetto_proto_library("stack_descriptor") {
- proto_generators = [ "descriptor" ]
- generate_descriptor = "stack.descriptor"
+perfetto_proto_library("stack_@TYPE@") {
+ proto_generators = []
sources = [ "stack.proto" ]
+ generate_descriptor = "stack.descriptor"
+ descriptor_root_source = "stack.proto"
}
perfetto_proto_library("metrics_impl_@TYPE@") {
- proto_generators = [
- "zero",
- "source_set",
- ]
+ proto_generators = [ "zero" ]
sources = [ "metrics_impl.proto" ]
}
diff --git a/protos/perfetto/trace_processor/trace_processor.proto b/protos/perfetto/trace_processor/trace_processor.proto
index c597206..bfb2a1f 100644
--- a/protos/perfetto/trace_processor/trace_processor.proto
+++ b/protos/perfetto/trace_processor/trace_processor.proto
@@ -60,7 +60,8 @@
// 12. Changed UI to be more aggresive about version matching.
// Added version_code.
// 13. Added TPM_REGISTER_SQL_MODULE method.
- TRACE_PROCESSOR_CURRENT_API_VERSION = 13;
+ // 14. Added parsing mode option to RESET method.
+ TRACE_PROCESSOR_CURRENT_API_VERSION = 14;
}
// At lowest level, the wire-format of the RPC protocol is a linear sequence of
@@ -88,13 +89,15 @@
optional string fatal_error = 5;
enum TraceProcessorMethod {
+ reserved 4, 12;
+ reserved "TPM_QUERY_RAW_DEPRECATED";
+ reserved "TPM_REGISTER_SQL_MODULE";
+
TPM_UNSPECIFIED = 0;
TPM_APPEND_TRACE_DATA = 1;
TPM_FINALIZE_TRACE_DATA = 2;
TPM_QUERY_STREAMING = 3;
// Previously: TPM_QUERY_RAW_DEPRECATED
- reserved 4;
- reserved "TPM_QUERY_RAW_DEPRECATED";
TPM_COMPUTE_METRIC = 5;
TPM_GET_METRIC_DESCRIPTORS = 6;
TPM_RESTORE_INITIAL_TABLES = 7;
@@ -102,7 +105,7 @@
TPM_DISABLE_AND_READ_METATRACE = 9;
TPM_GET_STATUS = 10;
TPM_RESET_TRACE_PROCESSOR = 11;
- TPM_REGISTER_SQL_MODULE = 12;
+ TPM_REGISTER_SQL_PACKAGE = 13;
}
oneof type {
@@ -134,8 +137,8 @@
EnableMetatraceArgs enable_metatrace_args = 106;
// For TPM_RESET_TRACE_PROCESSOR.
ResetTraceProcessorArgs reset_trace_processor_args = 107;
- // For TPM_REGISTER_SQL_MODULE.
- RegisterSqlModuleArgs register_sql_module_args = 108;
+ // For TPM_REGISTER_SQL_PACKAGE.
+ RegisterSqlPackageArgs register_sql_package_args = 108;
// TraceProcessorMethod response args.
// For TPM_APPEND_TRACE_DATA.
@@ -150,8 +153,8 @@
DisableAndReadMetatraceResult metatrace = 209;
// For TPM_GET_STATUS.
StatusResult status = 210;
- // For TPM_REGISTER_SQL_MODULE.
- RegisterSqlModuleResult register_sql_module_result = 211;
+ // For TPM_REGISTER_SQL_PACKAGE.
+ RegisterSqlPackageResult register_sql_package_result = 211;
}
// Previously: RawQueryArgs for TPM_QUERY_RAW_DEPRECATED
@@ -328,22 +331,29 @@
NO_DROP = 0;
TRACK_EVENT_RANGE_OF_INTEREST = 1;
}
+ enum ParsingMode {
+ DEFAULT = 0;
+ TOKENIZE_ONLY = 1;
+ TOKENIZE_AND_SORT = 2;
+ }
// Mirror of the corresponding perfetto::trace_processor::Config fields.
optional DropTrackEventDataBefore drop_track_event_data_before = 1;
optional bool ingest_ftrace_in_raw_table = 2;
optional bool analyze_trace_proto_content = 3;
optional bool ftrace_drop_until_all_cpus_valid = 4;
+ optional ParsingMode parsing_mode = 5;
}
-message RegisterSqlModuleArgs {
+message RegisterSqlPackageArgs {
message Module {
optional string name = 1;
optional string sql = 2;
}
- optional string top_level_package_name = 1;
+ optional string package_name = 1;
repeated Module modules = 2;
- optional bool allow_module_override = 3;
+ optional bool allow_override = 3;
}
-message RegisterSqlModuleResult {
+
+message RegisterSqlPackageResult {
optional string error = 1;
}
\ No newline at end of file
diff --git a/protos/third_party/CHROMIUM_OWNERS b/protos/third_party/CHROMIUM_OWNERS
index 0fa67ac..09fc76f 100644
--- a/protos/third_party/CHROMIUM_OWNERS
+++ b/protos/third_party/CHROMIUM_OWNERS
@@ -2,7 +2,6 @@
# The current sheriffs are listed at:
# https://oncall.corp.google.com/chrometto-sheriff
-agarwaltushar@google.com
altimin@google.com
carlscab@google.com
ddrone@google.com
diff --git a/protos/third_party/chromium/BUILD.gn b/protos/third_party/chromium/BUILD.gn
index b0f4f77..c6e47ad 100644
--- a/protos/third_party/chromium/BUILD.gn
+++ b/protos/third_party/chromium/BUILD.gn
@@ -5,13 +5,9 @@
perfetto_proto_library("@TYPE@") {
sources = chrome_track_event_sources
public_deps = [ "../../perfetto/trace/track_event:@TYPE@" ]
-}
-perfetto_proto_library("@TYPE@") {
- proto_generators = [ "descriptor" ]
- sources = chrome_track_event_sources
generate_descriptor = "chrome_track_event.descriptor"
- deps = [ ":source_set" ]
+ descriptor_root_source = "chrome_track_event.proto"
# When rolled into Chrome, extension descriptor is going to be linked into
# binary, therefore increasing its size. Including imports means that the
diff --git a/protos/third_party/chromium/chrome_track_event.proto b/protos/third_party/chromium/chrome_track_event.proto
index badb31d..7cf29f6 100644
--- a/protos/third_party/chromium/chrome_track_event.proto
+++ b/protos/third_party/chromium/chrome_track_event.proto
@@ -1393,6 +1393,7 @@
enum StepName {
STEP_UNKNOWN = 0;
STEP_DID_NOT_PRODUCE_FRAME = 1;
+ STEP_DID_NOT_PRODUCE_COMPOSITOR_FRAME = 22;
STEP_GENERATE_COMPOSITOR_FRAME = 2;
STEP_GENERATE_RENDER_PASS = 3;
STEP_ISSUE_BEGIN_FRAME = 4;
@@ -1452,10 +1453,36 @@
optional StepName step = 1;
optional FrameSinkId frame_sink_id = 2;
+ // Id used to link `ChromeGraphicsPipeline`s corresponding to work done by a
+ // Viz client to produce a frame. This corresponds to
+ // `viz::BeginFrameAck.trace_id`. Covers steps:
+ // * STEP_ISSUE_BEGIN_FRAME
+ // * STEP_RECEIVE_BEGIN_FRAME
+ // * STEP_GENERATE_RENDER_PASS
+ // * STEP_GENERATE_COMPOSITOR_FRAME
+ // * STEP_SUBMIT_COMPOSITOR_FRAME
+ // * STEP_RECEIVE_COMPOSITOR_FRAME
+ // * STEP_RECEIVE_BEGIN_FRAME_DISCARD
+ // * STEP_DID_NOT_PRODUCE_FRAME
+ // * STEP_DID_NOT_PRODUCE_COMPOSITOR_FRAME
+ optional int64 surface_frame_trace_id = 10;
+
// Id used to link `ChromeGraphicsPipeline`s corresponding to work done
- // on creating and presenting one frame.
+ // on creating and presenting one frame *after* surface aggregation. Covers
+ // steps:
+ // * STEP_DRAW_AND_SWAP
+ // * STEP_SURFACE_AGGREGATION
+ // * STEP_SEND_BUFFER_SWAP
+ // * STEP_BUFFER_SWAP_POST_SUBMIT
+ // * STEP_FINISH_BUFFER_SWAP
+ // * STEP_SWAP_BUFFERS_ACK
optional int64 display_trace_id = 3;
+ // `viz::BeginFrameAck.trace_id`s for frames aggregated at this step.
+ // Used with `STEP_SURFACE_AGGREGATION` only. This can be "joined" with
+ // `surface_frame_trace_id`.
+ repeated int64 aggregated_surface_frame_trace_ids = 8;
+
optional LocalSurfaceId local_surface_id = 4;
optional int64 frame_sequence = 5;
optional FrameSkippedReason frame_skipped_reason = 6;
@@ -1463,9 +1490,9 @@
// Optional variable that can be set together with STEP_BACKEND*.
optional int64 backend_frame_id = 7;
- // `viz::BeginFrameAck.trace_id`s for frames aggregated at this step.
- // Used with `STEP_SURFACE_AGGREGATION` only.
- repeated int64 aggregated_frames_ids = 8;
+ // List of LatencyInfo.trace_ids associated with this frame, which
+ // should allow us to match input to frame production.
+ repeated int64 latency_ids = 9;
};
message LibunwindstackUnwinder {
@@ -1532,6 +1559,7 @@
message StartUp {
// This enum must be kept up to date with LaunchCauseMetrics.LaunchCause.
enum LaunchCauseType {
+ UNINITIALIZED = -1;
OTHER = 0;
CUSTOM_TAB = 1;
TWA = 2;
@@ -1985,6 +2013,15 @@
optional BeginFrameId last_begin_frame_id_during_first_draw = 5;
}
+message CurrentTask {
+ // t1 - t0, where t1 is the start timestamp of this slice and t0 is the start
+ // timestamp of the task containing this slice.
+ optional uint64 event_offset_from_task_start_time_us = 1;
+
+ // Timestamp in microseconds of the start of the task containing this slice.
+ optional uint64 task_start_time_us = 2;
+}
+
message ChromeLatencyInfo2 {
optional int64 trace_id = 1;
@@ -2014,7 +2051,7 @@
// Occurs on Browser Main.
STEP_GESTURE_EVENT_HANDLED = 13;
// Renderer's compositor.
- STEP_SWAP_BUFFERS = 6;
+ STEP_SWAP_BUFFERS = 6 [deprecated = true];
// Happens on the VizCompositor in the GPU process.
STEP_DRAW_AND_SWAP = 7 [deprecated = true];
// Happens on the GPU main thread after the swap has completed.
@@ -2188,9 +2225,52 @@
optional EventType type = 9;
}
+// Frame information provided by Android's Choreographer.
+// See
+// https://developer.android.com/ndk/reference/group/choreographer#achoreographerframecallbackdata.
+// Next ID: 4
+message AndroidChoreographerFrameCallbackData {
+ // The time in microseconds at which the frame started being rendered.
+ // See
+ // https://developer.android.com/ndk/reference/group/choreographer#achoreographerframecallbackdata_getframetimenanos.
+ optional int64 frame_time_us = 1;
+
+ message FrameTimeline {
+ // The token used by the platform to identify the frame timeline.
+ // See
+ // https://developer.android.com/ndk/reference/group/choreographer#achoreographerframecallbackdata_getframetimelinevsyncid.
+ optional int64 vsync_id = 1;
+
+ // The difference in microseconds between:
+ // (A) the time at which the frame needs to be ready by in order to be
+ // presented on time and
+ // (B) the time at which the frame started being rendered (frame_time_us).
+ // See
+ // https://developer.android.com/ndk/reference/group/choreographer#achoreographerframecallbackdata_getframetimelinedeadlinenanos.
+ optional int64 latch_delta_us = 2;
+
+ // The difference in microseconds between:
+ // (A) the time at which the frame is expected to be presented and
+ // (B) the time at which the frame started being rendered (frame_time_us).
+ // See
+ // https://developer.android.com/ndk/reference/group/choreographer#achoreographerframecallbackdata_getframetimelineexpectedpresentationtimenanos.
+ optional int64 present_delta_us = 3;
+ }
+
+ // Possible frame timelines.
+ // See
+ // https://developer.android.com/ndk/reference/group/choreographer#achoreographerframecallbackdata_getframetimelineslength.
+ repeated FrameTimeline frame_timeline = 2;
+
+ // The index (into frame_timeline) of the platform-preferred frame timeline.
+ // See
+ // https://developer.android.com/ndk/reference/group/choreographer#achoreographerframecallbackdata_getpreferredframetimelineindex.
+ optional int64 preferred_frame_timeline_index = 3;
+}
+
message ChromeTrackEvent {
// Extension range for Chrome: 1000-1999
- // Next ID: 1070
+ // Next ID: 1072
extend TrackEvent {
optional ChromeAppState chrome_app_state = 1000;
@@ -2338,5 +2418,10 @@
optional ChromeLatencyInfo2 chrome_latency_info = 1068;
optional EventTiming event_timing = 1069;
+
+ optional AndroidChoreographerFrameCallbackData
+ android_choreographer_frame_callback_data = 1070;
+
+ optional CurrentTask current_task = 1071;
}
}
diff --git a/protos/third_party/pprof/BUILD.gn b/protos/third_party/pprof/BUILD.gn
index f38894c..9b8daa2 100644
--- a/protos/third_party/pprof/BUILD.gn
+++ b/protos/third_party/pprof/BUILD.gn
@@ -18,13 +18,8 @@
proto_generators = [
"zero",
"cpp",
- "source_set",
]
sources = [ "profile.proto" ]
-}
-
-perfetto_proto_library("profile_descriptor") {
- proto_generators = [ "descriptor" ]
generate_descriptor = "profile.descriptor"
- sources = [ "profile.proto" ]
+ descriptor_root_source = "profile.proto"
}
diff --git a/protos/third_party/statsd/BUILD.gn b/protos/third_party/statsd/BUILD.gn
index 5558d7b..5a1889e 100644
--- a/protos/third_party/statsd/BUILD.gn
+++ b/protos/third_party/statsd/BUILD.gn
@@ -15,10 +15,7 @@
import("../../../gn/proto_library.gni")
perfetto_proto_library("config_@TYPE@") {
- proto_generators = [
- "zero",
- "source_set",
- ]
+ proto_generators = [ "zero" ]
sources = [
"shell_config.proto",
"shell_data.proto",
diff --git a/python/generators/diff_tests/runner.py b/python/generators/diff_tests/runner.py
index a6c49f0..49bd278 100644
--- a/python/generators/diff_tests/runner.py
+++ b/python/generators/diff_tests/runner.py
@@ -417,15 +417,12 @@
os.path.join(metrics_protos_path, 'webview',
'all_webview_metrics.descriptor')
]
- result_str = ""
-
result, run_str = self.__run(metrics_descriptor_paths,
extension_descriptor_paths, keep_input, rebase)
- result_str += run_str
if not result:
- return self.test.name, result_str, None
+ return self.test.name, run_str, None
- return self.test.name, result_str, result
+ return self.test.name, run_str, result
# Fetches and executes all diff viable tests.
@@ -435,12 +432,15 @@
trace_processor_path: str
trace_descriptor_path: str
test_runners: List[TestCaseRunner]
+ quiet: bool
def __init__(self, name_filter: str, trace_processor_path: str,
trace_descriptor: str, no_colors: bool,
- override_sql_module_paths: List[str], test_dir: str):
+ override_sql_module_paths: List[str], test_dir: str,
+ quiet: bool):
self.tests = read_all_tests(name_filter, test_dir)
self.trace_processor_path = trace_processor_path
+ self.quiet = quiet
out_path = os.path.dirname(self.trace_processor_path)
self.trace_descriptor_path = get_trace_descriptor_path(
@@ -461,6 +461,7 @@
failures = []
rebased = []
test_run_start = datetime.datetime.now()
+ completed_tests = 0
with concurrent.futures.ProcessPoolExecutor() as e:
fut = [
@@ -471,7 +472,16 @@
]
for res in concurrent.futures.as_completed(fut):
test_name, res_str, result = res.result()
- sys.stderr.write(res_str)
+
+ if self.quiet:
+ completed_tests += 1
+ sys.stderr.write(f"\rRan {completed_tests} tests")
+ if not result.passed:
+ sys.stderr.write(f"\r")
+ sys.stderr.write(res_str)
+ else:
+ sys.stderr.write(res_str)
+
if not result or not result.passed:
if rebase:
rebased.append(test_name)
@@ -480,4 +490,6 @@
perf_results.append(result.perf_result)
test_time_ms = int(
(datetime.datetime.now() - test_run_start).total_seconds() * 1000)
+ if self.quiet:
+ sys.stderr.write(f"\r")
return TestResults(failures, perf_results, rebased, test_time_ms)
diff --git a/python/generators/sql_processing/docs_extractor.py b/python/generators/sql_processing/docs_extractor.py
index d8cca0a..6da238c 100644
--- a/python/generators/sql_processing/docs_extractor.py
+++ b/python/generators/sql_processing/docs_extractor.py
@@ -30,18 +30,12 @@
sql: str
@dataclass
- class Annotation:
- key: str
- value: str
-
- @dataclass
class Extract:
"""Extracted documentation for a single view/table/function."""
obj_kind: ObjKind
obj_match: Match
description: str
- annotations: List['DocsExtractor.Annotation']
def __init__(self, path: str, module_name: str, sql: str):
self.path = path
@@ -72,27 +66,13 @@
def _extract_from_comment(self, kind: ObjKind, match: Match,
comment_lines: List[str]) -> Optional[Extract]:
- extract = DocsExtractor.Extract(kind, match, '', [])
+ extract = DocsExtractor.Extract(kind, match, '')
for line in comment_lines:
assert line.startswith('--')
# Remove the comment.
comment_stripped = line.lstrip('--')
stripped = comment_stripped.lstrip()
+ extract.description += comment_stripped + "\n"
- # Check if the line is an annotation.
- if not stripped.startswith('@'):
- # We are not in annotation: if we haven't seen an annotation yet, we
- # must be still be parsing the description. Just add to that
- if not extract.annotations:
- extract.description += comment_stripped + "\n"
- continue
-
- # Otherwise, add to the latest annotation.
- extract.annotations[-1].value += " " + stripped
- continue
-
- # This line is an annotation: find its name and add a new entry
- annotation, rest = stripped.split(' ', 1)
- extract.annotations.append(DocsExtractor.Annotation(annotation, rest))
return extract
diff --git a/python/generators/sql_processing/docs_parse.py b/python/generators/sql_processing/docs_parse.py
index 77d5938..b01933e 100644
--- a/python/generators/sql_processing/docs_parse.py
+++ b/python/generators/sql_processing/docs_parse.py
@@ -25,10 +25,8 @@
from python.generators.sql_processing.utils import ALLOWED_PREFIXES
from python.generators.sql_processing.utils import OBJECT_NAME_ALLOWLIST
-from python.generators.sql_processing.utils import COLUMN_ANNOTATION_PATTERN
from python.generators.sql_processing.utils import ANY_PATTERN
from python.generators.sql_processing.utils import ARG_DEFINITION_PATTERN
-from python.generators.sql_processing.utils import ARG_ANNOTATION_PATTERN
def _is_internal(name: str) -> bool:
@@ -102,88 +100,23 @@
self._error('Description of the table/view/function/macro is missing')
return desc.strip()
- def _validate_only_contains_annotations(self,
- ans: List[DocsExtractor.Annotation],
- ans_types: Set[str]):
- used_ans_types = set(a.key for a in ans)
- for type in used_ans_types.difference(ans_types):
- self._error(f'Unknown documentation annotation {type}')
-
- def _parse_columns(self, ans: List[DocsExtractor.Annotation],
- schema: Optional[str]) -> Dict[str, Arg]:
- column_annotations = {}
- for t in ans:
- if t.key != '@column':
- continue
- m = re.match(COLUMN_ANNOTATION_PATTERN, t.value)
- if not m:
- self._error(f'@column annotation value {t.value} does not match '
- f'pattern {COLUMN_ANNOTATION_PATTERN}')
- continue
- column_annotations[m.group(1)] = Arg(None, m.group(2).strip())
-
- if not schema:
- # If we don't have schema, we have to accept annotations as the source of
- # truth.
- return column_annotations
-
- columns = self._parse_args_definition(schema)
-
+ def _parse_columns(self, schema: str) -> Dict[str, Arg]:
+ columns = self._parse_args_definition(schema) if schema else {}
for column in columns:
- inline_comment = columns[column].description
- if not inline_comment and column not in column_annotations:
+ if not columns[column].description:
self._error(f'Column "{column}" is missing a description. Please add a '
'comment in front of the column definition')
continue
- if column not in column_annotations:
- continue
- annotation = column_annotations[column].description
- if inline_comment and annotation:
- self._error(f'Column "{column}" is documented twice. Please remove the '
- '@column annotation')
- if not inline_comment and annotation:
- # Absorb old-style annotations.
- columns[column] = Arg(columns[column].type, annotation)
-
- # Check that the annotations match existing columns.
- for annotation in column_annotations:
- if annotation not in columns:
- self._error(f'Column "{annotation}" is documented but does not exist '
- 'in table definition')
return columns
- def _parse_args(self, ans: List[DocsExtractor.Annotation],
- sql_args_str: str) -> Dict[str, Arg]:
+ def _parse_args(self, sql_args_str: str) -> Dict[str, Arg]:
args = self._parse_args_definition(sql_args_str)
- arg_annotations = {}
- for an in ans:
- if an.key != '@arg':
- continue
- m = re.match(ARG_ANNOTATION_PATTERN, an.value)
- if m is None:
- self._error(f'Expected arg documentation "{an.value}" to match pattern '
- f'{ARG_ANNOTATION_PATTERN}')
- continue
- arg_annotations[m.group(1)] = Arg(m.group(2), m.group(3).strip())
-
for arg in args:
- if not args[arg].description and arg not in arg_annotations:
+ if not args[arg].description:
self._error(f'Arg "{arg}" is missing a description. '
'Please add a comment in front of the arg definition.')
- if args[arg].description and arg in arg_annotations:
- self._error(f'Arg "{arg}" is documented twice. '
- 'Please remove the @arg annotation')
- if not args[arg].description and arg in arg_annotations:
- # Absorb old-style annotations.
- # TODO(b/307926059): Remove it once stdlib is migrated.
- args[arg] = Arg(args[arg].type, arg_annotations[arg].description)
-
- for arg in arg_annotations:
- if arg not in args:
- self._error(
- f'Arg "{arg}" is documented but not found in function definition.')
return args
# Parse function argument definition list or a table schema, e.g.
@@ -243,22 +176,33 @@
f'{type} "{self.name}": CREATE OR REPLACE is not allowed in stdlib '
f'as standard library modules can only included once. Please just '
f'use CREATE instead.')
+ return
+
if _is_internal(self.name):
return None
- is_perfetto_table_or_view = (
- perfetto_or_virtual and perfetto_or_virtual.lower() == 'perfetto')
- if not schema and is_perfetto_table_or_view:
+ if not schema and self.name.lower() != "window":
self._error(
f'{type} "{self.name}": schema is missing for a non-internal stdlib'
f' perfetto table or view')
+ return
- self._validate_only_contains_annotations(doc.annotations, {'@column'})
+ if type.lower() == "table" and not perfetto_or_virtual:
+ self._error(
+ f'{type} "{self.name}": Can only expose CREATE PERFETTO tables')
+ return
+
+ is_virtual_table = type.lower() == "table" and perfetto_or_virtual.lower(
+ ) == "virtual"
+ if is_virtual_table and self.name.lower() != "window":
+ self._error(f'{type} "{self.name}": Virtual tables cannot be exposed.')
+ return
+
return TableOrView(
name=self._parse_name(),
type=type,
desc=self._parse_desc_not_empty(doc.description),
- cols=self._parse_columns(doc.annotations, schema),
+ cols=self._parse_columns(schema),
)
@@ -309,7 +253,7 @@
return Function(
name=name,
desc=self._parse_desc_not_empty(doc.description),
- args=self._parse_args(doc.annotations, args),
+ args=self._parse_args(args),
return_type=ret_type,
return_desc=ret_desc,
)
@@ -342,13 +286,12 @@
f'Function "{self.name}": CREATE OR REPLACE is not allowed in stdlib '
f'as standard library modules can only included once. Please just '
f'use CREATE instead.')
+ return
# Ignore internal functions.
if _is_internal(self.name):
return None
- self._validate_only_contains_annotations(doc.annotations,
- {'@arg', '@column'})
name = self._parse_name()
if not _is_snake_case(name):
@@ -358,8 +301,8 @@
return TableFunction(
name=name,
desc=self._parse_desc_not_empty(doc.description),
- cols=self._parse_columns(doc.annotations, columns),
- args=self._parse_args(doc.annotations, args),
+ cols=self._parse_columns(columns),
+ args=self._parse_args(args),
)
@@ -398,7 +341,6 @@
if _is_internal(self.name):
return None
- self._validate_only_contains_annotations(doc.annotations, set())
name = self._parse_name()
if not _is_snake_case(name):
@@ -410,7 +352,7 @@
desc=self._parse_desc_not_empty(doc.description),
return_desc=parse_comment(return_desc),
return_type=return_type,
- args=self._parse_args(doc.annotations, args),
+ args=self._parse_args(args),
)
diff --git a/python/generators/sql_processing/utils.py b/python/generators/sql_processing/utils.py
index 9768619..edb6b95 100644
--- a/python/generators/sql_processing/utils.py
+++ b/python/generators/sql_processing/utils.py
@@ -82,12 +82,8 @@
INCLUDE_PATTERN = update_pattern(fr'^INCLUDE PERFETTO MODULE ([A-Za-z_.*]*);$')
-COLUMN_ANNOTATION_PATTERN = update_pattern(fr'^ ({NAME}) ({ANY_WORDS})')
-
NAME_AND_TYPE_PATTERN = update_pattern(fr' ({NAME})\s+({TYPE}) ')
-ARG_ANNOTATION_PATTERN = fr'\s*{NAME_AND_TYPE_PATTERN}\s+({ANY_WORDS})'
-
ARG_DEFINITION_PATTERN = update_pattern(ARG_PATTERN)
FUNCTION_RETURN_PATTERN = update_pattern(fr'^ ({TYPE})\s+({ANY_WORDS})')
@@ -117,7 +113,7 @@
'chrome/util': ['cr'],
'intervals': ['interval'],
'graphs': ['graph'],
- 'slices': ['slice'],
+ 'slices': ['slice', 'thread_slice', 'process_slice'],
'linux': ['cpu', 'memory'],
'stacks': ['cpu_profiling'],
}
@@ -125,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']
}
@@ -196,7 +190,7 @@
errors = []
for _, matches in match_pattern(CREATE_TABLE_AS_PATTERN, sql).items():
name = matches[0]
- if name != "trace_bounds":
+ if name != "_trace_bounds":
errors.append(
f"Table '{name}' uses CREATE TABLE which is deprecated "
"and this table is not allowlisted. Use CREATE PERFETTO TABLE.")
diff --git a/python/perfetto/prebuilts/manifests/trace_processor_shell.py b/python/perfetto/prebuilts/manifests/trace_processor_shell.py
index 2becf1a..1bebad7 100755
--- a/python/perfetto/prebuilts/manifests/trace_processor_shell.py
+++ b/python/perfetto/prebuilts/manifests/trace_processor_shell.py
@@ -1,15 +1,15 @@
-# This file has been generated by: tools/roll-prebuilts v47.0
+# This file has been generated by: tools/roll-prebuilts v48.1
TRACE_PROCESSOR_SHELL_MANIFEST = [{
'arch':
'mac-amd64',
'file_name':
'trace_processor_shell',
'file_size':
- 10209056,
+ 9949656,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/mac-amd64/trace_processor_shell',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/mac-amd64/trace_processor_shell',
'sha256':
- '203c4c7a3621ee7c60a3d558613216427aa0f7245dc34fbe27e03cbcaf15cbd7',
+ 'e9dcf95aaa02f8c00a724f0ff34ba3a454c717beb9900cf9fd97ab142b362452',
'platform':
'darwin',
'machine': ['x86_64']
@@ -19,11 +19,11 @@
'file_name':
'trace_processor_shell',
'file_size':
- 9518360,
+ 9223224,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/mac-arm64/trace_processor_shell',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/mac-arm64/trace_processor_shell',
'sha256':
- '02130db81f477e795f0fb33e5183eed6d9350057346d730fe30aac5a6443d9c1',
+ '9a0541a0f52f95bfcb8dc88d94bc4494c660d95eefc40fc946ab43d995051ff7',
'platform':
'darwin',
'machine': ['arm64']
@@ -33,11 +33,11 @@
'file_name':
'trace_processor_shell',
'file_size':
- 10363488,
+ 10142800,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/linux-amd64/trace_processor_shell',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/linux-amd64/trace_processor_shell',
'sha256':
- '832425c3c7934904d1e0ec1721beb51423de7dbcf399a899973f2b6b464603fa',
+ '18c8730b52f8ee1d9e202031527435b6b2e3149fbd9b1046b2e77d18f06aa337',
'platform':
'linux',
'machine': ['x86_64']
@@ -47,11 +47,11 @@
'file_name':
'trace_processor_shell',
'file_size':
- 7682608,
+ 7329432,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/linux-arm/trace_processor_shell',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/linux-arm/trace_processor_shell',
'sha256':
- '0d5e41279051326311b178c73289d6027493bdd8627f537e538aa39a6f74af81',
+ '0558040998666576e1063d6d626b8aa9e354f18d73d225240f043b3c9236befa',
'platform':
'linux',
'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -61,11 +61,11 @@
'file_name':
'trace_processor_shell',
'file_size':
- 9949744,
+ 9703384,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/linux-arm64/trace_processor_shell',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/linux-arm64/trace_processor_shell',
'sha256':
- '2a9e5f6ee3d9a0d6007fc5503a9358629d7b3881233ee6fbe157edaa0f5a3b1b',
+ 'eeb95cc54358df08375ffae4862c043a6737902179ce8e0408984004c32cf93c',
'platform':
'linux',
'machine': ['aarch64']
@@ -75,55 +75,55 @@
'file_name':
'trace_processor_shell',
'file_size':
- 7716332,
+ 7367412,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/android-arm/trace_processor_shell',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-arm/trace_processor_shell',
'sha256':
- 'a3a1f49448e72c368748cb6ec0cb1f63ba4fe5598ff08118053dac68916b9433'
+ 'd29b1e6aee52ceff24c072f56c7be7795d0fa29f3596e2633fafa60782384718'
}, {
'arch':
'android-arm64',
'file_name':
'trace_processor_shell',
'file_size':
- 9861544,
+ 9598784,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/android-arm64/trace_processor_shell',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-arm64/trace_processor_shell',
'sha256':
- '49c9f94802986b9cada8ffab9ec911f21a416966a7c0b2acc3e467f03892ec56'
+ '06e80c562c0043cca9225ade3c961a081bcc7435660117d5a6db26b815d0b9ca'
}, {
'arch':
'android-x86',
'file_name':
'trace_processor_shell',
'file_size':
- 10805720,
+ 10625488,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/android-x86/trace_processor_shell',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-x86/trace_processor_shell',
'sha256':
- 'b188a7d95533a26b9eadcc5233d9fcc8552f94c0c7224a7f39f5e6eaebd7e981'
+ '2a576fb397da14d0dabcfa97f5eeec15b4dc55df009308f75a5fdf9de8a9b0dd'
}, {
'arch':
'android-x64',
'file_name':
'trace_processor_shell',
'file_size':
- 10150016,
+ 9915664,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/android-x64/trace_processor_shell',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-x64/trace_processor_shell',
'sha256':
- 'ff83eb7f53fc91d42c8756bb752fd70ed1f03b40a9daba99a6843c391bc8ff66'
+ 'a30be9f09b53110394e87af4d6b41ae24cd74d9a3f97ac1cc4d6ae2057ac6977'
}, {
'arch':
'windows-amd64',
'file_name':
'trace_processor_shell.exe',
'file_size':
- 10187264,
+ 9922560,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/windows-amd64/trace_processor_shell.exe',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/windows-amd64/trace_processor_shell.exe',
'sha256':
- 'f9b39c21a99f412697b4bf59f7046f80482c9f07dc3507c2d448dda02915aa14',
+ 'd41639844a6c36dbaa195d91e9c356f2172d924c70a1bfed5432c407f857f009',
'platform':
'win32',
'machine': ['amd64']
diff --git a/python/perfetto/prebuilts/manifests/tracebox.py b/python/perfetto/prebuilts/manifests/tracebox.py
index ec6a7a2..0745b63 100755
--- a/python/perfetto/prebuilts/manifests/tracebox.py
+++ b/python/perfetto/prebuilts/manifests/tracebox.py
@@ -1,15 +1,15 @@
-# This file has been generated by: tools/roll-prebuilts v47.0
+# This file has been generated by: tools/roll-prebuilts v48.1
TRACEBOX_MANIFEST = [{
'arch':
'mac-amd64',
'file_name':
'tracebox',
'file_size':
- 1597456,
+ 1613864,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/mac-amd64/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/mac-amd64/tracebox',
'sha256':
- '1e4b56533ad59e8131473ae6d4204356288a7b7a92241e303ab9865842d36c1d',
+ 'dfb1a3affe905d2e7d1f82bc4dda46b1fda6db054d60ae87c3215dd529b77fee',
'platform':
'darwin',
'machine': ['x86_64']
@@ -19,11 +19,11 @@
'file_name':
'tracebox',
'file_size':
- 1475640,
+ 1492184,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/mac-arm64/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/mac-arm64/tracebox',
'sha256':
- '8eae02034fa45581bd7262d1e3095616cc4f9a06a1bc0345cb5cae1277d8b4e4',
+ '4a492a629dd1f13f3146c4b8267c0b163afba8cef1d49e0c00c48bb727496066',
'platform':
'darwin',
'machine': ['arm64']
@@ -33,11 +33,11 @@
'file_name':
'tracebox',
'file_size':
- 2351336,
+ 2380040,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/linux-amd64/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/linux-amd64/tracebox',
'sha256':
- '0a533702f1ddf80998aaf3e95ce2ee8b154bfcf010c87bb740be6d04ac2e7380',
+ 'd70b284e8c28858fd539ae61ca59764d7f9fd6232073c304926e892fe75e692a',
'platform':
'linux',
'machine': ['x86_64']
@@ -47,11 +47,11 @@
'file_name':
'tracebox',
'file_size':
- 1433188,
+ 1450708,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/linux-arm/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/linux-arm/tracebox',
'sha256':
- 'd346f0ef77211230dd1f61284badb8edf4736852d446b36bb3d3e52a195934e4',
+ '178fa6a1a9bc80f72d81938d40fe201c25c595ffaff7e030d59c2af09dfcc06c',
'platform':
'linux',
'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -61,11 +61,11 @@
'file_name':
'tracebox',
'file_size':
- 2245088,
+ 2269816,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/linux-arm64/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/linux-arm64/tracebox',
'sha256':
- '7899b352ead70894a0cce25cd47db81229804daa168c9b18760003ae2068d3b0',
+ '42c64f9807756aaa08a2bfa13e9e4828c193a6b90ba1329408873c3ebf5adf3f',
'platform':
'linux',
'machine': ['aarch64']
@@ -75,42 +75,42 @@
'file_name':
'tracebox',
'file_size':
- 1323304,
+ 1333336,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/android-arm/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-arm/tracebox',
'sha256':
- '727bfbab060aeaf8e97bdef45f318d28c9e7452f91a7135311aff81f72a02fe7'
+ '93a78d2c42e3c00f117e2f155326383f69c891281ed693a39d87b8cb54ca4e19'
}, {
'arch':
'android-arm64',
'file_name':
'tracebox',
'file_size':
- 2101880,
+ 2115984,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/android-arm64/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-arm64/tracebox',
'sha256':
- 'ca9f2bbcc6fda0f8b2915e7c6b3d113a0a0ec256da14edcdb3ae4ffe69b4f2cb'
+ '508248a9e47ab605fd742efb700391d7267b68b586199a93e13e6ca14b72fe3d'
}, {
'arch':
'android-x86',
'file_name':
'tracebox',
'file_size':
- 2282928,
+ 2302960,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/android-x86/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-x86/tracebox',
'sha256':
- 'ffddf5dcdbe72419a610e7218908a96352b1a6b4fa27cd333aeab34f80a47fc1'
+ '63d20a69c4e0c291329d7917e640fa0d4f146c344e79988e87393b1431d594b1'
}, {
'arch':
'android-x64',
'file_name':
'tracebox',
'file_size':
- 2131400,
+ 2147880,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/android-x64/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-x64/tracebox',
'sha256':
- 'defba9ba1730c2583da87326096448cd7445271254392cd8f250e2fde0b54456'
+ 'c0ea1d5fd6d020e4c2b45d4d45cdd0c44ae63cd755d69260a3e5d2bacd3cbd6a'
}]
diff --git a/python/perfetto/prebuilts/manifests/traceconv.py b/python/perfetto/prebuilts/manifests/traceconv.py
index cacc549..abe58a0 100755
--- a/python/perfetto/prebuilts/manifests/traceconv.py
+++ b/python/perfetto/prebuilts/manifests/traceconv.py
@@ -1,15 +1,15 @@
-# This file has been generated by: tools/roll-prebuilts v47.0
+# This file has been generated by: tools/roll-prebuilts v48.1
TRACECONV_MANIFEST = [{
'arch':
'mac-amd64',
'file_name':
'traceconv',
'file_size':
- 9481408,
+ 9041560,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/mac-amd64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/mac-amd64/traceconv',
'sha256':
- 'b6819bb922438d585e816646c4d60e43bfa823d5f3f499bd8efcaccd26a9009c',
+ 'cec2da5cb771a4812d0b2d15604d5023954d28e0af12e87313da2ab70d26b970',
'platform':
'darwin',
'machine': ['x86_64']
@@ -19,11 +19,11 @@
'file_name':
'traceconv',
'file_size':
- 8852520,
+ 8375512,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/mac-arm64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/mac-arm64/traceconv',
'sha256':
- '9e9eac795578f6ed76128127d8a81c1ce5620b3d47f2c5e23c1f7f2ebbff9bea',
+ '64e200a58ea9c9f366e1071dd274d0023d1fd14043f75dbba3fe0cc138ff5fc7',
'platform':
'darwin',
'machine': ['arm64']
@@ -33,11 +33,11 @@
'file_name':
'traceconv',
'file_size':
- 9538432,
+ 9134136,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/linux-amd64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/linux-amd64/traceconv',
'sha256':
- '01881a82050f36b8db427c741ce236360bb86548e6a6c9445b2477c6150de05b',
+ '87b87e1778367c1e3b99fc77439a28b4911125d2751f9909fd1b51f6bd60b6f4',
'platform':
'linux',
'machine': ['x86_64']
@@ -47,11 +47,11 @@
'file_name':
'traceconv',
'file_size':
- 7286504,
+ 6753020,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/linux-arm/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/linux-arm/traceconv',
'sha256':
- 'bc700f945c78c4a65a60bdb499c6c59e671c5173f45f42976fd0661396b72c16',
+ '804c4e13aca5798731056952d9cb0c6ee58795c03477c69514ccd39703060812',
'platform':
'linux',
'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -61,11 +61,11 @@
'file_name':
'traceconv',
'file_size':
- 9168408,
+ 8740064,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/linux-arm64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/linux-arm64/traceconv',
'sha256':
- 'd0192785a56c5088811a175ab083bb599aab5fd594a3248057380038f0b2b5c2',
+ '0d781886531d11e1d573a1ec5e06376ef139bb479eec38c16c8735821c35b895',
'platform':
'linux',
'machine': ['aarch64']
@@ -75,55 +75,55 @@
'file_name':
'traceconv',
'file_size':
- 7322024,
+ 6792280,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/android-arm/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-arm/traceconv',
'sha256':
- '77583ed2eefe75796a3fe2a7149d6e234c643d4f83690f732367442bbb259812'
+ '7d91e4133184a3722a25488edd3692c5a195148eba56621014311d3f85d3fc15'
}, {
'arch':
'android-arm64',
'file_name':
'traceconv',
'file_size':
- 9123224,
+ 8677992,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/android-arm64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-arm64/traceconv',
'sha256':
- 'cd93333b3d56f41949066688308a23fdd3fbbf367b03aceff711d8102e696702'
+ 'c03c4a901ed23f1e20a12c98ce4556353a62bddcd260fb4d797cd29ff6c49a05'
}, {
'arch':
'android-x86',
'file_name':
'traceconv',
'file_size':
- 9868752,
+ 9503704,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/android-x86/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-x86/traceconv',
'sha256':
- '9b2921ba776ad99bf2ef0d28ff6919dea5ed726a3c61a81159b9d99b39dd7b6d'
+ '704e58a7249de56aadec64d4c0d83bab0821d2c4fd77114a9b71705ff4224539'
}, {
'arch':
'android-x64',
'file_name':
'traceconv',
'file_size':
- 9382824,
+ 8964488,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/android-x64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-x64/traceconv',
'sha256':
- 'c1fdbcb3c2cd15838468c3cc0ed8131a073f220c133384139aa57c4c05b2d34b'
+ 'e4f07836fc2a5fb7cd997a9acc4183af7a06997d1e73aac71021af5114b921bc'
}, {
'arch':
'windows-amd64',
'file_name':
'traceconv.exe',
'file_size':
- 9209856,
+ 8763904,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/windows-amd64/traceconv.exe',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/windows-amd64/traceconv.exe',
'sha256':
- 'ddef23109550784b6069b57bbac1ee627a1ab09086fdd72950965e866cfba536',
+ '084670ac28ed59a9642782a30e051735c1b7474b8cd569b9bc94c305af68290e',
'platform':
'win32',
'machine': ['amd64']
diff --git a/python/perfetto/trace_processor/metrics.descriptor b/python/perfetto/trace_processor/metrics.descriptor
index 604800f..ed15b47 100644
--- a/python/perfetto/trace_processor/metrics.descriptor
+++ b/python/perfetto/trace_processor/metrics.descriptor
Binary files differ
diff --git a/python/perfetto/trace_processor/protos.py b/python/perfetto/trace_processor/protos.py
index ea13297..b2ac388 100644
--- a/python/perfetto/trace_processor/protos.py
+++ b/python/perfetto/trace_processor/protos.py
@@ -43,9 +43,7 @@
def create_message_factory(message_type):
message_desc = self.descriptor_pool.FindMessageTypeByName(message_type)
- if hasattr(message_factory, 'GetMessageClass'):
- return message_factory.GetMessageClass(message_desc)
- return message_factory.MessageFactory().GetPrototype(message_desc)
+ return message_factory.GetMessageClass(message_desc)
# Create proto messages to correctly communicate with the RPC API by sending
# and receiving data as protos
diff --git a/python/perfetto/trace_processor/shell.py b/python/perfetto/trace_processor/shell.py
index 41f1655..2211912 100644
--- a/python/perfetto/trace_processor/shell.py
+++ b/python/perfetto/trace_processor/shell.py
@@ -16,6 +16,7 @@
import os
import subprocess
import sys
+import tempfile
import time
from typing import List, Optional
from urllib import request, error
@@ -55,11 +56,13 @@
if extra_flags:
args.extend(extra_flags)
+ temp_stdout = tempfile.TemporaryFile()
+ temp_stderr = tempfile.TemporaryFile()
p = subprocess.Popen(
tp_exec + args,
stdin=subprocess.DEVNULL,
- stdout=subprocess.DEVNULL,
- stderr=None if verbose else subprocess.DEVNULL)
+ stdout=temp_stdout,
+ stderr=None if verbose else temp_stderr)
success = False
for _ in range(load_timeout + 1):
@@ -72,10 +75,12 @@
time.sleep(1)
if not success:
- raise PerfettoException(
- "Trace processor failed to start. Try rerunning with "
- "verbose=True in TraceProcessorConfig for more detailed "
- "information and file a bug at https://goto.google.com/perfetto-bug "
- "or https://github.com/google/perfetto/issues if necessary.")
+ p.kill()
+ temp_stdout.seek(0)
+ stdout = temp_stdout.read().decode("utf-8")
+ temp_stderr.seek(0)
+ stderr = temp_stderr.read().decode("utf-8")
+ raise PerfettoException("Trace processor failed to start.\n"
+ f"stdout: {stdout}\nstderr: {stderr}\n")
return url, p
diff --git a/python/perfetto/trace_processor/trace_processor.descriptor b/python/perfetto/trace_processor/trace_processor.descriptor
index 0cc3646..976fb9b 100644
--- a/python/perfetto/trace_processor/trace_processor.descriptor
+++ b/python/perfetto/trace_processor/trace_processor.descriptor
Binary files differ
diff --git a/python/setup.py b/python/setup.py
index a001e46..6bb7364 100644
--- a/python/setup.py
+++ b/python/setup.py
@@ -11,7 +11,7 @@
],
package_data={'perfetto.trace_processor': ['*.descriptor']},
include_package_data=True,
- version='0.10.0',
+ version='0.11.0',
license='apache-2.0',
description='Python API for Perfetto\'s Trace Processor',
author='Perfetto',
diff --git a/python/test/api_integrationtest.py b/python/test/api_integrationtest.py
index 8e01679..cb6e9fe 100644
--- a/python/test/api_integrationtest.py
+++ b/python/test/api_integrationtest.py
@@ -134,6 +134,17 @@
with self.assertRaises(TraceProcessorException):
_ = create_tp(trace=f)
+ def test_runtime_error(self):
+ # We emulate a situation when TP returns an error by passing the --version
+ # flag. This makes TP output version information and exit, instead of
+ # starting an http server.
+ config = TraceProcessorConfig(
+ bin_path=os.environ["SHELL_PATH"], extra_flags=["--version"])
+ with self.assertRaisesRegex(
+ TraceProcessorException,
+ expected_regex='.*Trace Processor RPC API version:.*'):
+ TraceProcessor(trace=io.BytesIO(b''), config=config)
+
def test_trace_path(self):
# Get path to trace_processor_shell and construct TraceProcessor
tp = create_tp(trace=example_android_trace_path())
diff --git a/python/test/stdlib_unittest.py b/python/test/stdlib_unittest.py
index 34725cc..9114e65 100644
--- a/python/test/stdlib_unittest.py
+++ b/python/test/stdlib_unittest.py
@@ -25,93 +25,6 @@
class TestStdlib(unittest.TestCase):
- def test_valid_table(self):
- res = parse_file(
- 'foo/bar.sql', f'''
--- First line.
--- Second line.
--- @column slice_id Id of slice.
--- @column slice_name Name of slice.
-CREATE TABLE foo_table AS
-SELECT 1;
- '''.strip())
- self.assertListEqual(res.errors, [])
-
- table = res.table_views[0]
- self.assertEqual(table.name, 'foo_table')
- self.assertEqual(table.desc, 'First line.\n Second line.')
- self.assertEqual(table.type, 'TABLE')
- self.assertEqual(
- table.cols, {
- 'slice_id': Arg(None, 'Id of slice.'),
- 'slice_name': Arg(None, 'Name of slice.'),
- })
-
- def test_valid_function(self):
- res = parse_file(
- 'foo/bar.sql', f'''
--- First line.
--- Second line.
--- @arg utid INT Utid of thread.
--- @arg name STRING String name.
-CREATE PERFETTO FUNCTION foo_fn(utid INT, name STRING)
--- Exists.
-RETURNS BOOL
-AS
-SELECT 1;
- '''.strip())
- self.assertListEqual(res.errors, [])
-
- fn = res.functions[0]
- self.assertEqual(fn.name, 'foo_fn')
- self.assertEqual(fn.desc, 'First line.\n Second line.')
- self.assertEqual(
- fn.args, {
- 'utid': Arg('INT', 'Utid of thread.'),
- 'name': Arg('STRING', 'String name.'),
- })
- self.assertEqual(fn.return_type, 'BOOL')
- self.assertEqual(fn.return_desc, 'Exists.')
-
- def test_valid_table_function(self):
- res = parse_file(
- 'foo/bar.sql', f'''
--- Table comment.
--- @arg utid INT Utid of thread.
--- @arg name STRING String name.
--- @column slice_id Id of slice.
--- @column slice_name Name of slice.
-CREATE PERFETTO FUNCTION foo_view_fn(utid INT, name STRING)
-RETURNS TABLE(slice_id INT, slice_name STRING)
-AS SELECT 1;
- '''.strip())
- self.assertListEqual(res.errors, [])
-
- fn = res.table_functions[0]
- self.assertEqual(fn.name, 'foo_view_fn')
- self.assertEqual(fn.desc, 'Table comment.')
- self.assertEqual(
- fn.args, {
- 'utid': Arg('INT', 'Utid of thread.'),
- 'name': Arg('STRING', 'String name.'),
- })
- self.assertEqual(
- fn.cols, {
- 'slice_id': Arg('INT', 'Id of slice.'),
- 'slice_name': Arg('STRING', 'Name of slice.'),
- })
-
- def test_missing_module_name(self):
- res = parse_file(
- 'foo/bar.sql', f'''
--- Comment
--- @column slice_id Id of slice.
-CREATE TABLE bar_table AS
-SELECT 1;
- '''.strip())
- # Expecting an error: function prefix (bar) not matching module name (foo).
- self.assertEqual(len(res.errors), 1)
-
# Checks that custom prefixes (cr for chrome/util) are allowed.
def test_custom_module_prefix(self):
res = parse_file(
@@ -185,79 +98,6 @@
# (allowed: foo).
self.assertEqual(len(res.errors), 1)
- def test_common_does_not_include_module_name(self):
- res = parse_file(
- 'common/bar.sql', f'''
--- Comment.
--- @column slice_id Id of slice.
-CREATE TABLE common_table AS
-SELECT 1;
- '''.strip())
- # Expecting an error: functions in common/ should not have a module prefix.
- self.assertEqual(len(res.errors), 1)
-
- def test_cols_typo(self):
- res = parse_file(
- 'foo/bar.sql', f'''
--- Comment.
---
--- @column slice_id2 Foo.
--- @column slice_name Bar.
-CREATE TABLE bar_table AS
-SELECT 1;
- '''.strip())
- # Expecting an error: column slice_id2 not found in the table.
- self.assertEqual(len(res.errors), 1)
-
- def test_cols_no_desc(self):
- res = parse_file(
- 'foo/bar.sql', f'''
--- Comment.
---
--- @column slice_id
--- @column slice_name Bar.
-CREATE TABLE bar_table AS
-SELECT 1;
- '''.strip())
- # Expecting an error: column slice_id is missing a description.
- self.assertEqual(len(res.errors), 1)
-
- def test_args_typo(self):
- res = parse_file(
- 'foo/bar.sql', f'''
--- Comment.
---
--- @arg utid2 INT Uint.
--- @arg name STRING String name.
-CREATE PERFETTO FUNCTION foo_fn(utid INT, name STRING)
--- Exists.
-RETURNS BOOL
-AS
-SELECT 1;
- '''.strip())
- # Expecting 2 errors:
- # - arg utid2 not found in the function (should be utid);
- # - utid not documented.
- self.assertEqual(len(res.errors), 2)
-
- def test_args_no_desc(self):
- res = parse_file(
- 'foo/bar.sql', f'''
--- Comment.
---
--- @arg utid INT
--- @arg name STRING String name.
-CREATE PERFETTO FUNCTION foo_fn(utid INT, name STRING)
--- Exists.
-RETURNS BOOL
-AS
-SELECT 1;
- '''.strip())
- # Expecting 2 errors:
- # - arg utid is missing a description;
- # - arg utid is not documented.
- self.assertEqual(len(res.errors), 2)
-
def test_ret_no_desc(self):
res = parse_file(
'foo/bar.sql', f'''
@@ -295,35 +135,6 @@
self.assertEqual(fn.desc,
'This\n is\n\n a\n very\n\n long\n\n description.')
- def test_multiline_arg_desc(self):
- res = parse_file(
- 'foo/bar.sql', f'''
--- Comment.
---
--- @arg utid INT Uint
--- spread
---
--- across lines.
--- @arg name STRING String name
--- which spans across multiple lines
--- inconsistently.
-CREATE PERFETTO FUNCTION foo_fn(utid INT, name STRING)
--- Exists.
-RETURNS BOOL
-AS
-SELECT 1;
- '''.strip())
-
- fn = res.functions[0]
- self.assertEqual(
- fn.args, {
- 'utid':
- Arg('INT', 'Uint spread across lines.'),
- 'name':
- Arg(
- 'STRING', 'String name which spans across multiple lines '
- 'inconsistently.'),
- })
def test_function_name_style(self):
res = parse_file(
diff --git a/python/tools/check_imports.py b/python/tools/check_imports.py
index c61f3bb..37c1a95 100755
--- a/python/tools/check_imports.py
+++ b/python/tools/check_imports.py
@@ -14,17 +14,6 @@
# limitations under the License.
"""
Enforce import rules for https://ui.perfetto.dev.
-Directory structure encodes ideas about the expected dependency graph
-of the code in those directories. Both in a fuzzy sense: we expect code
-withing a directory to have high cohesion within the directory and low
-coupling (aka fewer imports) outside of the directory - but also
-concrete rules:
-- "base should not depend on the fronted"
-- "plugins should only directly depend on the public API"
-- "we should not have circular dependencies"
-
-Without enforcement exceptions to this rule quickly slip in. This
-script allows such rules to be enforced at presubmit time.
"""
import sys
@@ -32,319 +21,133 @@
import re
import collections
import argparse
+import fnmatch
ROOT_DIR = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
UI_SRC_DIR = os.path.join(ROOT_DIR, 'ui', 'src')
-# Current plan for the dependency tree of the UI code (2023-09-21)
-# black = current
-# red = planning to remove
-# green = planning to add
-PLAN_DOT = """
-digraph g {
- mithril [shape=rectangle, label="mithril"];
- protos [shape=rectangle, label="//protos/perfetto"];
+NODE_MODULES = '%node_modules%' # placeholder to depend on any node module.
- _gen [shape=ellipse, label="/gen"];
- _base [shape=ellipse, label="/base"];
- _core [shape=ellipse, label="/core"];
- _engine [shape=ellipse, label="/engine"];
+# The format of this array is: (src) -> (dst).
+# If src or dst are arrays, the semantic is the cartesian product, e.g.:
+# [a,b] -> [c,d] is equivalent to allowing a>c, a>d, b>c, b>d.
+DEPS_ALLOWLIST = [
+ # Everything can depend on base/, protos and NPM packages.
+ ('*', ['/base/*', '/protos/index', '/gen/perfetto_version', NODE_MODULES]),
- _frontend [shape=ellipse, label="/frontend" color=red];
- _common [shape=ellipse, label="/common" color=red];
- _controller [shape=ellipse, label="/controller" color=red];
- _tracks [shape=ellipse, label="/tracks" color=red];
+ # Integration tests can depend on everything.
+ ('/test/*', '*'),
- _widgets [shape=ellipse, label="/widgets"];
-
- _public [shape=ellipse, label="/public"];
- _plugins [shape=ellipse, label="/plugins"];
- _chrome_extension [shape=ellipse, label="/chrome_extension"];
- _trace_processor [shape=ellipse, label="/trace_processor" color="green"];
- _protos [shape=ellipse, label="/protos"];
- engine_worker_bundle [shape=cds, label="Engine worker bundle"];
- frontend_bundle [shape=cds, label="Frontend bundle"];
-
- engine_worker_bundle -> _engine;
- frontend_bundle -> _core [color=green];
- frontend_bundle -> _frontend [color=red];
-
- _core -> _public;
- _plugins -> _public;
-
- _widgets -> _base;
- _core -> _base;
- _core -> _widgets;
-
-
- _widgets -> mithril;
- _plugins -> mithril;
- _core -> mithril
-
- _plugins -> _widgets;
-
- _core -> _chrome_extension;
-
- _frontend -> _widgets [color=red];
- _common -> _core [color=red];
- _frontend -> _core [color=red];
- _controller -> _core [color=red];
-
- _frontend -> _controller [color=red];
- _frontend -> _common [color=red];
- _controller -> _frontend [color=red];
- _controller -> _common [color=red];
- _common -> _controller [color=red];
- _common -> _frontend [color=red];
- _tracks -> _frontend [color=red];
- _tracks -> _controller [color=red];
- _common -> _chrome_extension [color=red];
-
- _core -> _trace_processor [color=green];
-
- _engine -> _trace_processor [color=green];
- _engine -> _common [color=red];
- _engine -> _base;
-
- _gen -> protos;
- _core -> _gen [color=red];
-
- _core -> _protos;
- _protos -> _gen;
- _trace_processor -> _protos [color=green];
-
- _trace_processor -> _public [color=green];
-
- npm_trace_processor [shape=cds, label="npm trace_processor" color="green"];
- npm_trace_processor -> engine_worker_bundle [color="green"];
- npm_trace_processor -> _trace_processor [color="green"];
-}
-"""
-
-
-class Failure(object):
-
- def __init__(self, path, rule):
- self.path = path
- self.rule = rule
-
- def __str__(self):
- nice_path = ["ui/src" + name + ".ts" for name in self.path]
- return ''.join([
- 'Forbidden dependency path:\n\n ',
- '\n -> '.join(nice_path),
- '\n',
- '\n',
- str(self.rule),
- '\n',
- ])
-
-
-class AllowList(object):
-
- def __init__(self, allowed, dst, reasoning):
- self.allowed = allowed
- self.dst = dst
- self.reasoning = reasoning
-
- def check(self, graph):
- for node, edges in graph.items():
- for edge in edges:
- if re.match(self.dst, edge):
- if not any(re.match(a, node) for a in self.allowed):
- yield Failure([node, edge], self)
-
- def __str__(self):
- return f'Only items in the allowlist ({self.allowed}) may directly depend on "{self.dst}" ' + self.reasoning
-
-
-class NoDirectDep(object):
-
- def __init__(self, src, dst, reasoning):
- self.src = src
- self.dst = dst
- self.reasoning = reasoning
-
- def check(self, graph):
- for node, edges in graph.items():
- if re.match(self.src, node):
- for edge in edges:
- if re.match(self.dst, edge):
- yield Failure([node, edge], self)
-
- def __str__(self):
- return f'"{self.src}" may not directly depend on "{self.dst}" ' + self.reasoning
-
-
-class NoDep(object):
-
- def __init__(self, src, dst, reasoning):
- self.src = src
- self.dst = dst
- self.reasoning = reasoning
-
- def check(self, graph):
- for node in graph:
- if re.match(self.src, node):
- for connected, path in bfs(graph, node):
- if re.match(self.dst, connected):
- yield Failure(path, self)
-
- def __str__(self):
- return f'"{self.src}" may not depend on "{self.dst}" ' + self.reasoning
-
-
-class NoCircularDeps(object):
-
- def __init__(self):
- pass
-
- def check(self, graph):
- for node in graph:
- for child in graph[node]:
- for reached, path in dfs(graph, child):
- if reached == node:
- yield Failure([node] + path, self)
-
- def __str__(self):
- return f'circular dependencies can cause complex issues'
-
-
-# We have three kinds of rules:
-# NoDirectDep(a, b) = files matching regex 'a' cannot *directly* import
-# files matching regex 'b' - but they may indirectly depend on them.
-# NoDep(a, b) = as above but 'a' may not even transitively import 'b'.
-# NoCircularDeps = forbid introduction of circular dependencies
-RULES = [
- AllowList(
- ['/protos/index'],
- r'/gen/protos',
- 'protos should be re-exported from /protos/index without the nesting.',
- ),
- NoDirectDep(
- r'/plugins/.*',
- r'/core/.*',
- 'instead plugins should depend on the API exposed at ui/src/public.',
- ),
- NoDirectDep(
- r"/frontend/.*",
- r"/core_plugins/.*",
- "core code should not depend on plugins.",
- ),
- NoDirectDep(
- r"/core/.*",
- r"/core_plugins/.*",
- "core code should not depend on plugins.",
- ),
- NoDirectDep(
- r"/base/.*",
- r"/core_plugins/.*",
- "core code should not depend on plugins.",
- ),
- NoDep(
- r'/core/.*',
- r'/plugins/.*',
- 'otherwise the plugins are no longer optional.',
- ),
- NoDep(
- r'/core/.*',
- r'/frontend/.*',
- 'trying to reduce the dependency mess as we refactor into core',
- ),
- NoDep(
- r'/core/.*',
- r'/common/.*',
- 'trying to reduce the dependency mess as we refactor into core',
- ),
- NoDep(
- r'/core/.*',
- r'/controller/.*',
- 'trying to reduce the dependency mess as we refactor into core',
- ),
- NoDep(
- r'/base/.*',
- r'/core/.*',
- 'core should depend on base not the other way round',
- ),
- NoDep(
- r'/base/.*',
- r'/common/.*',
- 'common should depend on base not the other way round',
- ),
- NoDep(
- r'/common/.*',
- r'/chrome_extension/.*',
- 'chrome_extension must be a leaf',
+ # Dependencies allowed for internal UI code.
+ (
+ [
+ '/frontend/*',
+ '/core/*',
+ '/common/*',
+ ],
+ [
+ '/frontend/*',
+ '/core/*',
+ '/common/*',
+ '/public/*',
+ '/trace_processor/*',
+ '/widgets/*',
+ '/protos/*',
+ '/gen/perfetto_version',
+ ],
),
- # Widgets
- NoDep(
- r'/widgets/.*',
- r'/frontend/.*',
- 'widgets should only depend on base',
- ),
- NoDep(
- r'/widgets/.*',
- r'/core/.*',
- 'widgets should only depend on base',
- ),
- NoDep(
- r'/widgets/.*',
- r'/plugins/.*',
- 'widgets should only depend on base',
- ),
- NoDep(
- r'/widgets/.*',
- r'/common/.*',
- 'widgets should only depend on base',
+ # /public (interfaces + lib) can depend only on a restricted surface.
+ ('/public/*', ['/base/*', '/trace_processor/*']),
+
+ # /public/lib can also depend on the plublic interface and widgets.
+ ('/public/lib/*', ['/public/*', '/frontend/widgets/*', '/widgets/*']),
+
+ # /plugins (and core_plugins) can depend only on a restricted surface.
+ (
+ '/*plugins/*',
+ [
+ '/base/*',
+ '/public/*',
+ '/trace_processor/*',
+ '/widgets/*',
+ '/frontend/widgets/*',
+ ],
),
- # Bigtrace
- NoDep(
- r'/bigtrace/.*',
- r'/frontend/.*',
- 'bigtrace should not depend on frontend',
- ),
- NoDep(
- r'/bigtrace/.*',
- r'/common/.*',
- 'bigtrace should not depend on common',
- ),
- NoDep(
- r'/bigtrace/.*',
- r'/engine/.*',
- 'bigtrace should not depend on engine',
- ),
- NoDep(
- r'/bigtrace/.*',
- r'/trace_processor/.*',
- 'bigtrace should not depend on trace_processor',
- ),
- NoDep(
- r'/bigtrace/.*',
- r'/traceconv/.*',
- 'bigtrace should not depend on traceconv',
- ),
- NoDep(
- r'/bigtrace/.*',
- r'/tracks/.*',
- 'bigtrace should not depend on tracks',
- ),
- NoDep(
- r'/bigtrace/.*',
- r'/controller/.*',
- 'bigtrace should not depend on controller',
+ # Extra dependencies allowed for core_plugins only.
+ # TODO(priniano): remove this entry to figure out what it takes to move the
+ # remaining /core_plugins to /plugins and get rid of core_plugins.
+ (
+ ['/core_plugins/*'],
+ ['/core/*', '/frontend/*', '/common/actions'],
),
- # Fails at the moment as we have several circular dependencies. One
- # example:
- # ui/src/frontend/cookie_consent.ts
- # -> ui/src/frontend/globals.ts
- # -> ui/src/frontend/router.ts
- # -> ui/src/frontend/pages.ts
- # -> ui/src/frontend/cookie_consent.ts
- #NoCircularDeps(),
+ # Miscl legitimate deps.
+ ('/frontend/index', ['/gen/*']),
+ ('/traceconv/index', '/gen/traceconv'),
+ ('/engine/wasm_bridge', '/gen/trace_processor'),
+ ('/trace_processor/sql_utils/*', '/trace_processor/*'),
+ ('/protos/index', '/gen/protos'),
+
+ # ------ Technical debt that needs cleaning up below this point ------
+
+ # TODO(primiano): this dependency for BaseSliceTrack & co needs to be moved
+ # to /public/lib or something similar.
+ ('/*plugins/*', '/frontend/*track'),
+
+ # TODO(primiano): clean up generic_slice_details_tab.
+ ('/*plugins/*', '/frontend/generic_slice_details_tab'),
+
+ # TODO(primiano): these dependencies require a discussion with stevegolton@.
+ # unclear if they should be moved to public/lib/* or be part of the
+ # {Base/Named/Slice}Track overhaul.
+ ('/*plugins/*', [
+ '/frontend/slice_layout',
+ '/frontend/slice_args',
+ '/frontend/checkerboard',
+ '/common/track_helper',
+ '/common/track_data',
+ ]),
+
+ # TODO(primiano): clean up dependencies on feature flags.
+ (['/public/lib/colorizer'], '/core/feature_flags'),
+
+ # TODO(primiano): Record page-related technical debt.
+ ('/plugins/dev.perfetto.RecordTrace/*', '/frontend/globals'),
+ ('/chrome_extension/chrome_tracing_controller',
+ '/plugins/dev.perfetto.RecordTrace/*'),
+
+ # TODO(primiano): query-table tech debt.
+ (
+ '/public/lib/query_table/query_table',
+ ['/frontend/*', '/core/app_impl', '/core/router'],
+ ),
+
+ # TODO(primiano): tracks tech debt.
+ ('/public/lib/tracks/*', [
+ '/frontend/base_counter_track',
+ '/frontend/slice_args',
+ '/frontend/tracks/custom_sql_table_slice_track',
+ '/frontend/tracks/generic_slice_details_tab',
+ ]),
+
+ # TODO(primiano): controller-related tech debt.
+ ('/frontend/index', '/controller/*'),
+ ('/controller/*', ['/base/*', '/core/*', '/common/*']),
+
+ # TODO(primiano): check this with stevegolton@. Unclear if widgets should
+ # be allowed to depend on trace_processor.
+ ('/widgets/vega_view', '/trace_processor/*'),
+
+ # Bigtrace deps.
+ ('/bigtrace/*', ['/base/*', '/widgets/*', '/trace_processor/*']),
+
+ # TODO(primiano): misc tech debt.
+ ('/public/lib/extensions', '/frontend/*'),
+ ('/bigtrace/index', ['/core/live_reload', '/core/raf_scheduler']),
+ ('/plugins/dev.perfetto.HeapProfile/*', '/frontend/trace_converter'),
]
@@ -371,22 +174,78 @@
return s[:-len(suffix)] if s.endswith(suffix) else s
+def normalize_path(path):
+ return remove_suffix(remove_prefix(path, UI_SRC_DIR), '.ts')
+
+
+def find_plugin_declared_deps(path):
+ """Returns the set of deps declared by the plugin (if any)
+
+ It scans the plugin/index.ts file, and resolves the declared dependencies,
+ working out the path of the plugin we depend on (by looking at the imports).
+ Returns a tuple of the form (src_plugin_path, set{dst_plugin_path})
+ Where:
+ src_plugin_path: is the normalized path of the input (e.g. /plugins/foo)
+ dst_path: is the normalized path of the declared dependency.
+ """
+ src = normalize_path(path)
+ src_plugin = get_plugin_path(src)
+ if src_plugin is None or src != src_plugin + '/index':
+ # If the file is not a plugin, or is not the plugin index.ts, bail out.
+ return
+ # First extract all the default-imports in the file. Usually there is one for
+ # each imported plugin, of the form:
+ # import ThreadPlugin from '../plugins/dev.perfetto.Thread'
+ import_map = {} # 'ThreadPlugin' -> '/plugins/dev.perfetto.Thread'
+ for (src, target, default_import) in find_imports(path):
+ target_plugin = get_plugin_path(target)
+ if default_import is not None or target_plugin is not None:
+ import_map[default_import] = target_plugin
+
+ # Now extract the declared dependencies for the plugin. This looks for the
+ # statement 'static readonly dependencies = [ThreadPlugin]'. It can be broken
+ # down over multiple lines, so we approach this in two steps. First we find
+ # everything within the square brackets; then we remove spaces and \n and
+ # tokenize on commas
+ with open(path) as f:
+ s = f.read()
+ DEP_REGEX = r'^\s*static readonly dependencies\s*=\s*\[([^\]]*)\]'
+ all_deps = re.findall(DEP_REGEX, s, flags=re.MULTILINE)
+ if len(all_deps) == 0:
+ return
+ if len(all_deps) > 1:
+ raise Exception('Ambiguous plugin deps in %s: %s' % (path, all_deps))
+ declared_deps = re.sub('\s*', '', all_deps[0]).split(',')
+ for imported_as in declared_deps:
+ resolved_dep = import_map.get(imported_as)
+ if resolved_dep is None:
+ raise Exception('Could not resolve import %s in %s' % (imported_as, src))
+ yield (src_plugin, resolved_dep)
+
+
def find_imports(path):
- src = path
- src = remove_prefix(src, UI_SRC_DIR)
- src = remove_suffix(src, '.ts')
+ src = normalize_path(path)
directory, _ = os.path.split(src)
with open(path) as f:
s = f.read()
- for m in re.finditer("^import[^']*'([^']*)';", s, flags=re.MULTILINE):
- raw_target = m[1]
- if raw_target.startswith('.'):
- target = os.path.normpath(os.path.join(directory, raw_target))
- if is_dir(UI_SRC_DIR + target):
- target = os.path.join(target, 'index')
- else:
- target = raw_target
- yield (src, target)
+ for m in re.finditer(
+ "^import\s+([^;]+)\s+from\s+'([^']+)';$", s, flags=re.MULTILINE):
+ # Flatten multi-line imports into one line, removing spaces. The resulting
+ # import line can look like:
+ # '{foo,bar,baz}' in most cases
+ # 'DefaultImportName' when doing import DefaultImportName from '...'
+ # 'DefaultImportName,{foo,bar,bar}' when doing a mixture of the above.
+ imports = re.sub('\s', '', m[1])
+ default_import = (re.findall('^\w+', imports) + [None])[0]
+
+ # Normalize the imported file
+ target = m[2]
+ if target.startswith('.'):
+ target = os.path.normpath(os.path.join(directory, target))
+ if is_dir(UI_SRC_DIR + target):
+ target = os.path.join(target, 'index')
+
+ yield (src, target, default_import)
def path_to_id(path):
@@ -401,42 +260,6 @@
return not path.startswith('/')
-def bfs(graph, src):
- seen = set()
- queue = [(src, [])]
-
- while queue:
- node, path = queue.pop(0)
- if node in seen:
- continue
-
- seen.add(node)
-
- path = path[:]
- path.append(node)
-
- yield node, path
- queue.extend([(child, path) for child in graph[node]])
-
-
-def dfs(graph, src):
- seen = set()
- queue = [(src, [])]
-
- while queue:
- node, path = queue.pop()
- if node in seen:
- continue
-
- seen.add(node)
-
- path = path[:]
- path.append(node)
-
- yield node, path
- queue.extend([(child, path) for child in graph[node]])
-
-
def write_dot(graph, f):
print('digraph g {', file=f)
for node, edges in graph.items():
@@ -450,20 +273,95 @@
print('}', file=f)
-def do_check(options, graph):
+def get_plugin_path(path):
+ m = re.match('^(/(?:core_)?plugins/([^/]+))/.*', path)
+ return m.group(1) if m is not None else None
+
+
+def flatten_rules(rules):
+ flat_deps = []
+ for rule_src, rule_dst in rules:
+ src_list = rule_src if isinstance(rule_src, list) else [rule_src]
+ dst_list = rule_dst if isinstance(rule_dst, list) else [rule_dst]
+ for src in src_list:
+ for dst in dst_list:
+ flat_deps.append((src, dst))
+ return flat_deps
+
+
+def get_node_modules(graph):
+ """Infers the dependencies onto NPM packages (node_modules)
+
+ An import is guessed to be a node module if doesn't contain any . or .. in the
+ path, and optionally starts with @.
+ """
+ node_modules = set()
+ for _, imports in graph.items():
+ for dst in imports:
+ if re.match(r'^[@a-z][a-z0-9-_/]+$', dst):
+ node_modules.add(dst)
+ return node_modules
+
+
+def check_one_import(src, dst, allowlist, plugin_declared_deps, node_modules):
+ # Translate node_module deps into the wildcard '%node_modules%' so it can be
+ # treated as a single entity.
+ if dst in node_modules:
+ dst = NODE_MODULES
+
+ # Always allow imports from the same directory or its own subdirectories.
+ src_dir = '/'.join(src.split('/')[:-1])
+ dst_dir = '/'.join(dst.split('/')[:-1])
+ if dst_dir.startswith(src_dir):
+ return True
+
+ # Match against the (flattened) allowlist.
+ for rule_src, rule_dst in allowlist:
+ if fnmatch.fnmatch(src, rule_src) and fnmatch.fnmatch(dst, rule_dst):
+ return True
+
+ # Check inter-plugin deps.
+ src_plugin = get_plugin_path(src)
+ dst_plugin = get_plugin_path(dst)
+ extra_err = ''
+ if src_plugin is not None and dst_plugin is not None:
+ if src_plugin == dst_plugin:
+ # Allow a plugin to depends on arbitrary subdirectories of itself.
+ return True
+ # Check if there is a dependency declared by plugins, via
+ # static readonly dependencies = [DstPlugin]
+ declared_deps = plugin_declared_deps.get(src_plugin, set())
+ extra_err = '(plugin deps: %s)' % ','.join(declared_deps)
+ if dst_plugin in declared_deps:
+ return True
+ print('Import not allowed %s -> %s %s' % (src, dst, extra_err))
+ return False
+
+
+def do_check(_options, graph):
result = 0
- for rule in RULES:
- for failure in rule.check(graph):
- print(failure)
- result = 1
+ rules = flatten_rules(DEPS_ALLOWLIST)
+ node_modules = get_node_modules(graph)
+
+ # Build a map of depencies declared between plugin. The maps looks like:
+ # 'Foo' -> {'Bar', 'Baz'} # Foo declares a dependency on Bar and Baz
+ plugin_declared_deps = collections.defaultdict(set)
+ for path in all_source_files():
+ for src_plugin, dst_plugin in find_plugin_declared_deps(path):
+ plugin_declared_deps[src_plugin].add(dst_plugin)
+
+ for src, imports in graph.items():
+ for dst in imports:
+ if not check_one_import(src, dst, rules, plugin_declared_deps,
+ node_modules):
+ result = 1
return result
def do_desc(options, graph):
print('Rules:')
- for rule in RULES:
- print(" - ", end='')
- print(rule)
+ for rule in flatten_rules(DEPS_ALLOWLIST):
+ print(' - %s' % rule)
def do_print(options, graph):
@@ -479,13 +377,12 @@
return path
return os.path.dirname(path)
- if options.simplify:
- new_graph = collections.defaultdict(set)
- for node, edges in graph.items():
- for edge in edges:
- new_graph[simplify(edge)]
- new_graph[simplify(node)].add(simplify(edge))
- graph = new_graph
+ new_graph = collections.defaultdict(set)
+ for node, edges in graph.items():
+ for edge in edges:
+ new_graph[simplify(edge)]
+ new_graph[simplify(node)].add(simplify(edge))
+ graph = new_graph
if options.ignore_external:
new_graph = collections.defaultdict(set)
@@ -503,11 +400,6 @@
return 0
-def do_plan_dot(options, _):
- print(PLAN_DOT, file=sys.stdout)
- return 0
-
-
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.set_defaults(func=do_check)
@@ -525,29 +417,21 @@
dot_command = subparsers.add_parser(
'dot',
- help='Output dependency graph in dot format suitble for use in graphviz (e.g. ./tools/check_imports dot | dot -Tpng -ograph.png)'
- )
+ help='Output dependency graph in dot format suitble for use in ' +
+ 'graphviz (e.g. ./tools/check_imports dot | dot -Tpng -ograph.png)')
dot_command.set_defaults(func=do_dot)
dot_command.add_argument(
- '--simplify',
- action='store_true',
- help='Show directories rather than files',
- )
- dot_command.add_argument(
'--ignore-external',
action='store_true',
help='Don\'t show external dependencies',
)
- plan_dot_command = subparsers.add_parser(
- 'plan-dot',
- help='Output planned dependency graph in dot format suitble for use in graphviz (e.g. ./tools/check_imports plan-dot | dot -Tpng -ograph.png)'
- )
- plan_dot_command.set_defaults(func=do_plan_dot)
-
+ # This is a general import graph of the form /plugins/foo/index -> /base/hash
graph = collections.defaultdict(set)
+
+ # Build the dep graph
for path in all_source_files():
- for src, target in find_imports(path):
+ for src, target, _ in find_imports(path):
graph[src].add(target)
graph[target]
diff --git a/python/tools/check_ratchet.py b/python/tools/check_ratchet.py
index 573e6a3..d53b0c3 100755
--- a/python/tools/check_ratchet.py
+++ b/python/tools/check_ratchet.py
@@ -36,8 +36,8 @@
from dataclasses import dataclass
-EXPECTED_ANY_COUNT = 59
-EXPECTED_RUN_METRIC_COUNT = 5
+EXPECTED_ANY_COUNT = 52
+EXPECTED_RUN_METRIC_COUNT = 4
ROOT_DIR = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
diff --git a/src/android_stats/perfetto_atoms.h b/src/android_stats/perfetto_atoms.h
index 3a3308e..dbf6c8a 100644
--- a/src/android_stats/perfetto_atoms.h
+++ b/src/android_stats/perfetto_atoms.h
@@ -27,13 +27,14 @@
// Checkpoints inside perfetto_cmd before tracing is finished.
kTraceBegin = 1,
kBackgroundTraceBegin = 2,
- kCloneTraceBegin = 55,
- kCloneTriggerTraceBegin = 56,
+ kCmdCloneTraceBegin = 55,
+ kCmdCloneTriggerTraceBegin = 56,
kOnConnect = 3,
+ kCmdOnSessionClone = 58,
+ kCmdOnTriggerSessionClone = 59,
// Guardrails inside perfetto_cmd before tracing is finished.
kOnTimeout = 16,
- kCmdUserBuildTracingNotAllowed = 43,
// Checkpoints inside traced.
kTracedEnableTracing = 37,
@@ -111,6 +112,10 @@
// Contained status of guardrail state initialization and upload limit in
// perfetto_cmd. Removed as perfetto no longer manages stateful guardrails
// reserved 44, 45, 46;
+
+ // Contained the guardrail for user build tracing. Removed as this guardrail
+ // causes more problem than it solves these days.
+ // reserved 43;
};
// This must match the values of the PerfettoTrigger::TriggerType enum in:
@@ -118,17 +123,15 @@
enum PerfettoTriggerAtom {
kUndefined = 0,
- kCmdTrigger = 1,
- kCmdTriggerFail = 2,
-
- kTriggerPerfettoTrigger = 3,
- kTriggerPerfettoTriggerFail = 4,
-
kTracedLimitProbability = 5,
kTracedLimitMaxPer24h = 6,
- kProbesProducerTrigger = 7,
- kProbesProducerTriggerFail = 8,
+ kTracedTrigger = 9,
+
+ // Contained events of logging triggers through perfetto_cmd, probes and
+ // trigger_perfetto.
+ // Removed in W (Oct 2024) and replaced by |kTracedTrigger|.
+ // reserved 1, 2, 3, 4, 7, 8
};
} // namespace perfetto
diff --git a/src/base/BUILD.gn b/src/base/BUILD.gn
index d8d00a7..ace0ac6 100644
--- a/src/base/BUILD.gn
+++ b/src/base/BUILD.gn
@@ -82,6 +82,15 @@
}
}
+perfetto_component("clock_snapshots") {
+ deps = [ "../../gn:default_deps" ]
+ public_deps = [
+ "../../include/perfetto/ext/base",
+ "../../protos/perfetto/common:zero",
+ ]
+ sources = [ "clock_snapshots.cc" ]
+}
+
# This target needs to be named as such because it's exposed directly in Bazel
# and Android.bp.
perfetto_component("perfetto_base_default_platform") {
diff --git a/src/tracing/core/clock_snapshots.cc b/src/base/clock_snapshots.cc
similarity index 97%
rename from src/tracing/core/clock_snapshots.cc
rename to src/base/clock_snapshots.cc
index a4fe6c0..4959e7a 100644
--- a/src/tracing/core/clock_snapshots.cc
+++ b/src/base/clock_snapshots.cc
@@ -14,13 +14,13 @@
* limitations under the License.
*/
-#include "perfetto/tracing/core/clock_snapshots.h"
+#include "perfetto/ext/base/clock_snapshots.h"
#include "perfetto/base/build_config.h"
#include "perfetto/base/time.h"
#include "protos/perfetto/common/builtin_clock.pbzero.h"
-namespace perfetto {
+namespace perfetto::base {
ClockSnapshotVector CaptureClockSnapshots() {
ClockSnapshotVector snapshot_data;
diff --git a/src/base/string_utils.cc b/src/base/string_utils.cc
index e8845d1..c93a8f1 100644
--- a/src/base/string_utils.cc
+++ b/src/base/string_utils.cc
@@ -257,7 +257,7 @@
#endif // PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
size_t SprintfTrunc(char* dst, size_t dst_size, const char* fmt, ...) {
- if (PERFETTO_UNLIKELY(dst_size) == 0)
+ if (PERFETTO_UNLIKELY(dst_size == 0))
return 0;
va_list args;
@@ -265,7 +265,7 @@
int src_size = vsnprintf(dst, dst_size, fmt, args);
va_end(args);
- if (PERFETTO_UNLIKELY(src_size) <= 0) {
+ if (PERFETTO_UNLIKELY(src_size <= 0)) {
dst[0] = '\0';
return 0;
}
diff --git a/src/base/task_runner_unittest.cc b/src/base/task_runner_unittest.cc
index 810ebc6..060cf43 100644
--- a/src/base/task_runner_unittest.cc
+++ b/src/base/task_runner_unittest.cc
@@ -25,7 +25,6 @@
#include "perfetto/ext/base/pipe.h"
#include "perfetto/ext/base/scoped_file.h"
#include "perfetto/ext/base/utils.h"
-#include "src/base/test/gtest_test_suite.h"
#include "test/gtest_and_gmock.h"
namespace perfetto {
diff --git a/src/base/test/gtest_test_suite.h b/src/base/test/gtest_test_suite.h
deleted file mode 100644
index 4d1d550..0000000
--- a/src/base/test/gtest_test_suite.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2019 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.
- */
-
-#ifndef SRC_BASE_TEST_GTEST_TEST_SUITE_H_
-#define SRC_BASE_TEST_GTEST_TEST_SUITE_H_
-
-#include "test/gtest_and_gmock.h"
-
-// Define newer TEST_SUITE googletest APIs as aliases of the older APIs where
-// necessary. This makes it possible to migrate Perfetto to the newer APIs and
-// still use older googletest versions where necessary.
-//
-// TODO(costan): Remove this header after googletest is rolled in Android.
-
-#if !defined(INSTANTIATE_TEST_SUITE_P)
-#define INSTANTIATE_TEST_SUITE_P(...) INSTANTIATE_TEST_CASE_P(__VA_ARGS__)
-#endif
-
-#if !defined(INSTANTIATE_TYPED_TEST_SUITE_P)
-#define INSTANTIATE_TYPED_TEST_SUITE_P(...) \
- INSTANTIATE_TYPED_TEST_CASE_P(__VA_ARGS__)
-#endif
-
-#if !defined(REGISTER_TEST_SUITE_P)
-#define REGISTER_TEST_SUITE_P(...) REGISTER_TEST_CASE_P(__VA_ARGS__)
-#endif
-
-#if !defined(TYPED_TEST_SUITE)
-#define TYPED_TEST_SUITE(...) TYPED_TEST_CASE(__VA_ARGS__)
-#endif
-
-#if !defined(TYPED_TEST_SUITE_P)
-#define TYPED_TEST_SUITE_P(...) TYPED_TEST_CASE_P(__VA_ARGS__)
-#endif
-
-#endif // SRC_BASE_TEST_GTEST_TEST_SUITE_H_
diff --git a/src/base/test/test_task_runner.cc b/src/base/test/test_task_runner.cc
index 3576996..51d6d52 100644
--- a/src/base/test/test_task_runner.cc
+++ b/src/base/test/test_task_runner.cc
@@ -88,6 +88,14 @@
};
}
+void TestTaskRunner::AdvanceTimeAndRunUntilIdle(uint32_t ms) {
+ PERFETTO_DCHECK_THREAD(thread_checker_);
+ task_runner_.PostDelayedTask(std::bind(&TestTaskRunner::QuitIfIdle, this),
+ ms);
+ task_runner_.AdvanceTimeForTesting(ms);
+ task_runner_.Run();
+}
+
// TaskRunner implementation.
void TestTaskRunner::PostTask(std::function<void()> closure) {
task_runner_.PostTask(std::move(closure));
diff --git a/src/base/test/test_task_runner.h b/src/base/test/test_task_runner.h
index a7c5784..142970d 100644
--- a/src/base/test/test_task_runner.h
+++ b/src/base/test/test_task_runner.h
@@ -42,6 +42,10 @@
void RunUntilCheckpoint(const std::string& checkpoint,
uint32_t timeout_ms = 5000);
+ // Pretends (for the purposes of running delayed tasks) that time advanced by
+ // `ms`. Run until then.
+ void AdvanceTimeAndRunUntilIdle(uint32_t ms);
+
// TaskRunner implementation.
void PostTask(std::function<void()> closure) override;
void PostDelayedTask(std::function<void()>, uint32_t delay_ms) override;
diff --git a/src/base/threading/thread_pool.cc b/src/base/threading/thread_pool.cc
index 30843b3..fe3e69d 100644
--- a/src/base/threading/thread_pool.cc
+++ b/src/base/threading/thread_pool.cc
@@ -47,7 +47,10 @@
thread_waiter_.notify_one();
}
-void ThreadPool::RunThreadLoop() {
+void ThreadPool::RunThreadLoop() PERFETTO_NO_THREAD_SAFETY_ANALYSIS {
+ // 'std::unique_lock' lock doesn't work well with thread annotations
+ // (see https://github.com/llvm/llvm-project/issues/63239),
+ // so we suppress thread safety static analysis for this method.
for (;;) {
std::function<void()> fn;
{
@@ -57,8 +60,10 @@
}
if (pending_tasks_.empty()) {
thread_waiting_count_++;
- thread_waiter_.wait(
- guard, [this]() { return quit_ || !pending_tasks_.empty(); });
+ thread_waiter_.wait(guard,
+ [this]() PERFETTO_EXCLUSIVE_LOCKS_REQUIRED(mutex_) {
+ return quit_ || !pending_tasks_.empty();
+ });
thread_waiting_count_--;
continue;
}
diff --git a/src/base/unix_task_runner.cc b/src/base/unix_task_runner.cc
index ba2828f..3e35c03 100644
--- a/src/base/unix_task_runner.cc
+++ b/src/base/unix_task_runner.cc
@@ -53,7 +53,10 @@
void UnixTaskRunner::Run() {
PERFETTO_DCHECK_THREAD(thread_checker_);
created_thread_id_.store(GetThreadId(), std::memory_order_relaxed);
- quit_ = false;
+ {
+ std::lock_guard<std::mutex> lock(lock_);
+ quit_ = false;
+ }
for (;;) {
int poll_timeout_ms;
{
@@ -108,6 +111,11 @@
return immediate_tasks_.empty();
}
+void UnixTaskRunner::AdvanceTimeForTesting(uint32_t ms) {
+ std::lock_guard<std::mutex> lock(lock_);
+ advanced_time_for_testing_ += TimeMillis(ms);
+}
+
void UnixTaskRunner::UpdateWatchTasksLocked() {
PERFETTO_DCHECK_THREAD(thread_checker_);
#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
@@ -142,7 +150,7 @@
}
if (!delayed_tasks_.empty()) {
auto it = delayed_tasks_.begin();
- if (now >= it->first) {
+ if (now + advanced_time_for_testing_ >= it->first) {
delayed_task = std::move(it->second);
delayed_tasks_.erase(it);
}
@@ -242,7 +250,8 @@
if (!immediate_tasks_.empty())
return 0;
if (!delayed_tasks_.empty()) {
- TimeMillis diff = delayed_tasks_.begin()->first - GetWallTimeMs();
+ TimeMillis diff = delayed_tasks_.begin()->first - GetWallTimeMs() -
+ advanced_time_for_testing_;
return std::max(0, static_cast<int>(diff.count()));
}
return -1;
@@ -264,7 +273,8 @@
TimeMillis runtime = GetWallTimeMs() + TimeMillis(delay_ms);
{
std::lock_guard<std::mutex> lock(lock_);
- delayed_tasks_.insert(std::make_pair(runtime, std::move(task)));
+ delayed_tasks_.insert(
+ std::make_pair(runtime + advanced_time_for_testing_, std::move(task)));
}
WakeUp();
}
diff --git a/src/base/waitable_event.cc b/src/base/waitable_event.cc
index 67fb2e0..ac3d31f 100644
--- a/src/base/waitable_event.cc
+++ b/src/base/waitable_event.cc
@@ -22,14 +22,20 @@
WaitableEvent::WaitableEvent() = default;
WaitableEvent::~WaitableEvent() = default;
-void WaitableEvent::Wait(uint64_t notifications) {
+void WaitableEvent::Wait(uint64_t notifications)
+ PERFETTO_NO_THREAD_SAFETY_ANALYSIS {
+ // 'std::unique_lock' lock doesn't work well with thread annotations
+ // (see https://github.com/llvm/llvm-project/issues/63239),
+ // so we suppress thread safety static analysis for this method.
std::unique_lock<std::mutex> lock(mutex_);
- return event_.wait(lock, [&] { return notifications_ >= notifications; });
+ return event_.wait(lock, [&]() PERFETTO_EXCLUSIVE_LOCKS_REQUIRED(mutex_) {
+ return notifications_ >= notifications;
+ });
}
void WaitableEvent::Notify() {
- std::unique_lock<std::mutex> lock(mutex_);
- notifications_++;
+ std::lock_guard<std::mutex> lock(mutex_);
+ ++notifications_;
event_.notify_all();
}
diff --git a/src/base/watchdog_posix.cc b/src/base/watchdog_posix.cc
index 00934f0..a647086 100644
--- a/src/base/watchdog_posix.cc
+++ b/src/base/watchdog_posix.cc
@@ -259,15 +259,16 @@
// Check if any of the timers expired.
int tid_to_kill = 0;
WatchdogCrashReason crash_reason{};
- std::unique_lock<std::mutex> guard(mutex_);
- for (const auto& timer : timers_) {
- if (now >= timer.deadline) {
- tid_to_kill = timer.thread_id;
- crash_reason = timer.crash_reason;
- break;
+ {
+ std::lock_guard<std::mutex> guard(mutex_);
+ for (const auto& timer : timers_) {
+ if (now >= timer.deadline) {
+ tid_to_kill = timer.thread_id;
+ crash_reason = timer.crash_reason;
+ break;
+ }
}
}
- guard.unlock();
if (tid_to_kill)
SerializeLogsAndKillThread(tid_to_kill, crash_reason);
@@ -282,15 +283,16 @@
static_cast<uint64_t>(stat.rss_pages) * base::GetSysPageSize();
bool threshold_exceeded = false;
- guard.lock();
- if (CheckMemory_Locked(rss_bytes) && !IsSyncMemoryTaggingEnabled()) {
- threshold_exceeded = true;
- crash_reason = WatchdogCrashReason::kMemGuardrail;
- } else if (CheckCpu_Locked(cpu_time)) {
- threshold_exceeded = true;
- crash_reason = WatchdogCrashReason::kCpuGuardrail;
+ {
+ std::lock_guard<std::mutex> guard(mutex_);
+ if (CheckMemory_Locked(rss_bytes) && !IsSyncMemoryTaggingEnabled()) {
+ threshold_exceeded = true;
+ crash_reason = WatchdogCrashReason::kMemGuardrail;
+ } else if (CheckCpu_Locked(cpu_time)) {
+ threshold_exceeded = true;
+ crash_reason = WatchdogCrashReason::kCpuGuardrail;
+ }
}
- guard.unlock();
if (threshold_exceeded)
SerializeLogsAndKillThread(getpid(), crash_reason);
diff --git a/src/perfetto_cmd/BUILD.gn b/src/perfetto_cmd/BUILD.gn
index 3f90a57..4cfe0ea 100644
--- a/src/perfetto_cmd/BUILD.gn
+++ b/src/perfetto_cmd/BUILD.gn
@@ -71,8 +71,6 @@
"packet_writer.h",
"perfetto_cmd.cc",
"perfetto_cmd.h",
- "rate_limiter.cc",
- "rate_limiter.h",
]
if (is_android) {
deps += [ "../android_internal:lazy_library_loader" ]
@@ -136,10 +134,7 @@
}
perfetto_proto_library("protos_@TYPE@") {
- proto_generators = [
- "cpp",
- "source_set",
- ]
+ proto_generators = [ "cpp" ]
sources = [ "perfetto_cmd_state.proto" ]
proto_path = perfetto_root_path
}
@@ -163,6 +158,5 @@
"config_unittest.cc",
"packet_writer_unittest.cc",
"pbtxt_to_pb_unittest.cc",
- "rate_limiter_unittest.cc",
]
}
diff --git a/src/perfetto_cmd/perfetto_cmd.cc b/src/perfetto_cmd/perfetto_cmd.cc
index b418734..d3a12d4 100644
--- a/src/perfetto_cmd/perfetto_cmd.cc
+++ b/src/perfetto_cmd/perfetto_cmd.cc
@@ -78,7 +78,6 @@
#include "src/perfetto_cmd/config.h"
#include "src/perfetto_cmd/packet_writer.h"
#include "src/perfetto_cmd/pbtxt_to_pb.h"
-#include "src/perfetto_cmd/rate_limiter.h"
#include "src/perfetto_cmd/trigger_producer.h"
#include "protos/perfetto/common/ftrace_descriptor.gen.h"
@@ -159,30 +158,6 @@
return true;
}
-bool IsUserBuild() {
-#if PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD)
- std::string build_type = base::GetAndroidProp("ro.build.type");
- if (build_type.empty()) {
- PERFETTO_ELOG("Unable to read ro.build.type: assuming user build");
- return true;
- }
- return build_type == "user";
-#else
- return false;
-#endif // PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD)
-}
-
-std::optional<PerfettoStatsdAtom> ConvertRateLimiterResponseToAtom(
- RateLimiter::ShouldTraceResponse resp) {
- switch (resp) {
- case RateLimiter::kNotAllowedOnUserBuild:
- return PerfettoStatsdAtom::kCmdUserBuildTracingNotAllowed;
- case RateLimiter::kOkToTrace:
- return std::nullopt;
- }
- PERFETTO_FATAL("For GCC");
-}
-
void ArgsAppend(std::string* str, const std::string& arg) {
str->append(arg);
str->append("\0", 1);
@@ -219,6 +194,9 @@
received, non-zero otherwise (error or timeout).
--clone TSID : Creates a read-only clone of an existing tracing
session, identified by its ID (see --query).
+ --clone-by-name NAME : Creates a read-only clone of an existing tracing
+ session, identified by its unique_session_name in
+ the config.
--clone-for-bugreport : Can only be used with --clone. It disables the
trace_filter on the cloned session.
--config -c : /path/to/trace/config/file or - for stdin
@@ -279,6 +257,7 @@
OPT_BUGREPORT,
OPT_BUGREPORT_ALL,
OPT_CLONE,
+ OPT_CLONE_BY_NAME,
OPT_CLONE_SKIP_FILTER,
OPT_CONFIG_ID,
OPT_CONFIG_UID,
@@ -319,6 +298,7 @@
{"detach", required_argument, nullptr, OPT_DETACH},
{"attach", required_argument, nullptr, OPT_ATTACH},
{"clone", required_argument, nullptr, OPT_CLONE},
+ {"clone-by-name", required_argument, nullptr, OPT_CLONE_BY_NAME},
{"clone-for-bugreport", no_argument, nullptr, OPT_CLONE_SKIP_FILTER},
{"is_detached", required_argument, nullptr, OPT_IS_DETACHED},
{"stop", no_argument, nullptr, OPT_STOP},
@@ -334,7 +314,6 @@
std::string trace_config_raw;
bool parse_as_pbtxt = false;
TraceConfig::StatsdMetadata statsd_metadata;
- limiter_.reset(new RateLimiter());
ConfigOptions config_options;
bool has_config_options = false;
@@ -422,6 +401,11 @@
continue;
}
+ if (option == OPT_CLONE_BY_NAME) {
+ clone_name_ = optarg;
+ continue;
+ }
+
if (option == OPT_CLONE_SKIP_FILTER) {
clone_for_bugreport_ = true;
continue;
@@ -606,8 +590,13 @@
return 1;
}
- if (clone_for_bugreport_ && !clone_tsid_) {
- PERFETTO_ELOG("--clone-for-bugreport requires --clone");
+ if (clone_tsid_ && !clone_name_.empty()) {
+ PERFETTO_ELOG("--clone and --clone-by-name are mutually exclusive");
+ return 1;
+ }
+
+ if (clone_for_bugreport_ && !is_clone()) {
+ PERFETTO_ELOG("--clone-for-bugreport requires --clone or --clone-by-name");
return 1;
}
@@ -646,7 +635,7 @@
}
parsed = CreateConfigFromOptions(config_options, trace_config_.get());
} else {
- if (trace_config_raw.empty() && !clone_tsid_) {
+ if (trace_config_raw.empty() && !is_clone()) {
PERFETTO_ELOG("The TraceConfig is empty");
return 1;
}
@@ -670,7 +659,7 @@
if (parsed) {
*trace_config_->mutable_statsd_metadata() = std::move(statsd_metadata);
trace_config_raw.clear();
- } else if (will_trace_or_trigger && !clone_tsid_) {
+ } else if (will_trace_or_trigger && !is_clone()) {
PERFETTO_ELOG("The trace config is invalid, bailing out.");
if (cfg_could_be_txt) {
PERFETTO_ELOG(
@@ -954,8 +943,6 @@
// connect as a consumer or run the trace. So bail out after processing all
// the options.
if (!triggers_to_activate_.empty()) {
- LogTriggerEvents(PerfettoTriggerAtom::kCmdTrigger, triggers_to_activate_);
-
bool finished_with_success = false;
auto weak_this = weak_factory_.GetWeakPtr();
TriggerProducer producer(
@@ -968,10 +955,6 @@
},
&triggers_to_activate_);
task_runner_.Run();
- if (!finished_with_success) {
- LogTriggerEvents(PerfettoTriggerAtom::kCmdTriggerFail,
- triggers_to_activate_);
- }
return finished_with_success ? 0 : 1;
} // if (triggers_to_activate_)
@@ -982,11 +965,6 @@
return 1; // We can legitimately get here if the service disconnects.
}
- RateLimiter::Args args{};
- args.is_user_build = IsUserBuild();
- args.is_uploading = save_to_incidentd_ || report_to_android_framework_;
- args.allow_user_build_tracing = trace_config_->allow_user_build_tracing();
-
if (!trace_config_->unique_session_name().empty())
base::MaybeSetThreadName("p-" + trace_config_->unique_session_name());
@@ -1010,11 +988,11 @@
std::this_thread::sleep_for(std::chrono::milliseconds(dist(minstd)));
}
- if (clone_tsid_) {
+ if (is_clone()) {
if (snapshot_trigger_name_.empty()) {
- LogUploadEvent(PerfettoStatsdAtom::kCloneTraceBegin);
+ LogUploadEvent(PerfettoStatsdAtom::kCmdCloneTraceBegin);
} else {
- LogUploadEvent(PerfettoStatsdAtom::kCloneTriggerTraceBegin,
+ LogUploadEvent(PerfettoStatsdAtom::kCmdCloneTriggerTraceBegin,
snapshot_trigger_name_);
}
} else if (trace_config_->trigger_config().trigger_timeout_ms() == 0) {
@@ -1023,12 +1001,6 @@
LogUploadEvent(PerfettoStatsdAtom::kBackgroundTraceBegin);
}
- auto err_atom = ConvertRateLimiterResponseToAtom(limiter_->ShouldTrace(args));
- if (err_atom) {
- LogUploadEvent(err_atom.value());
- return 1;
- }
-
#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
if (!background_ && !is_detach() && !upload_flag_ &&
triggers_to_activate_.empty() && !isatty(STDIN_FILENO) &&
@@ -1093,13 +1065,18 @@
return;
}
- if (clone_tsid_.has_value()) {
+ if (is_clone()) {
task_runner_.PostDelayedTask(std::bind(&PerfettoCmd::OnTimeout, this),
kCloneTimeoutMs);
ConsumerEndpoint::CloneSessionArgs args;
args.skip_trace_filter = clone_for_bugreport_;
args.for_bugreport = clone_for_bugreport_;
- consumer_endpoint_->CloneSession(*clone_tsid_, std::move(args));
+ if (clone_tsid_.has_value()) {
+ args.tsid = *clone_tsid_;
+ } else if (!clone_name_.empty()) {
+ args.unique_session_name = clone_name_;
+ }
+ consumer_endpoint_->CloneSession(std::move(args));
return;
}
@@ -1131,7 +1108,7 @@
// Failsafe mechanism to avoid waiting indefinitely if the service hangs.
// Note: when using prefer_suspend_clock_for_duration the actual duration
// might be < expected_duration_ms_ measured in in wall time. But this is fine
- // because the resulting timeout will be conservative (it will be accurate
+ // because the resulting timeout will be conservative (it will be accut
// if the device never suspends, and will be more lax if it does).
if (expected_duration_ms_) {
uint32_t trace_timeout = expected_duration_ms_ + 60000 +
@@ -1380,12 +1357,17 @@
}
void PerfettoCmd::OnSessionCloned(const OnSessionClonedArgs& args) {
- PERFETTO_DLOG("Cloned tracing session %" PRIu64 ", success=%d",
- clone_tsid_.value_or(0), args.success);
+ PERFETTO_DLOG("Cloned tracing session %" PRIu64 ", name=\"%s\", success=%d",
+ clone_tsid_.value_or(0), clone_name_.c_str(), args.success);
std::string full_error;
if (!args.success) {
- full_error = "Failed to clone tracing session " +
- std::to_string(clone_tsid_.value_or(0)) + ": " + args.error;
+ std::string name;
+ if (clone_tsid_.has_value()) {
+ name = std::to_string(*clone_tsid_);
+ } else {
+ name = "\"" + clone_name_ + "\"";
+ }
+ full_error = "Failed to clone tracing session " + name + ": " + args.error;
}
// This is used with --save-all-for-bugreport, to pause all cloning threads
@@ -1400,6 +1382,14 @@
// Kick off the readback and file finalization (as if we started tracing and
// reached the duration_ms timeout).
uuid_ = args.uuid.ToString();
+
+ // Log the new UUID with the clone tag.
+ if (snapshot_trigger_name_.empty()) {
+ LogUploadEvent(PerfettoStatsdAtom::kCmdOnSessionClone);
+ } else {
+ LogUploadEvent(PerfettoStatsdAtom::kCmdOnTriggerSessionClone,
+ snapshot_trigger_name_);
+ }
ReadbackTraceDataAndQuit(full_error);
}
diff --git a/src/perfetto_cmd/perfetto_cmd.h b/src/perfetto_cmd/perfetto_cmd.h
index 160a701..17bb751 100644
--- a/src/perfetto_cmd/perfetto_cmd.h
+++ b/src/perfetto_cmd/perfetto_cmd.h
@@ -42,8 +42,6 @@
namespace perfetto {
-class RateLimiter;
-
// Directory for local state and temporary files. This is automatically
// created by the system by setting setprop persist.traced.enable=1.
extern const char* kStateDir;
@@ -93,6 +91,9 @@
void OnTimeout();
bool is_detach() const { return !detach_key_.empty(); }
bool is_attach() const { return !attach_key_.empty(); }
+ bool is_clone() const {
+ return clone_tsid_.has_value() || !clone_name_.empty();
+ }
// Once we call ReadBuffers we expect one or more calls to OnTraceData
// with the last call having |has_more| set to false. However we should
@@ -145,7 +146,6 @@
base::UnixTaskRunner task_runner_;
- std::unique_ptr<RateLimiter> limiter_;
std::unique_ptr<perfetto::TracingService::ConsumerEndpoint>
consumer_endpoint_;
std::unique_ptr<TraceConfig> trace_config_;
@@ -177,6 +177,7 @@
bool connected_ = false;
std::string uuid_;
std::optional<TracingSessionID> clone_tsid_{};
+ std::string clone_name_;
bool clone_for_bugreport_ = false;
std::function<void()> on_session_cloned_;
diff --git a/src/perfetto_cmd/rate_limiter.cc b/src/perfetto_cmd/rate_limiter.cc
deleted file mode 100644
index 8b80718..0000000
--- a/src/perfetto_cmd/rate_limiter.cc
+++ /dev/null
@@ -1,44 +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.
- */
-
-#include "src/perfetto_cmd/rate_limiter.h"
-
-#include "perfetto/base/logging.h"
-#include "src/perfetto_cmd/perfetto_cmd.h"
-
-namespace perfetto {
-
-RateLimiter::RateLimiter() = default;
-RateLimiter::~RateLimiter() = default;
-
-RateLimiter::ShouldTraceResponse RateLimiter::ShouldTrace(const Args& args) {
- // Not uploading?
- // -> We can just trace.
- if (!args.is_uploading)
- return ShouldTraceResponse::kOkToTrace;
-
- // If we're tracing a user build we should only trace if the override in
- // the config is set:
- if (args.is_user_build && !args.allow_user_build_tracing) {
- PERFETTO_ELOG(
- "Guardrail: allow_user_build_tracing must be set to trace on user "
- "builds");
- return ShouldTraceResponse::kNotAllowedOnUserBuild;
- }
- return ShouldTraceResponse::kOkToTrace;
-}
-
-} // namespace perfetto
diff --git a/src/perfetto_cmd/rate_limiter.h b/src/perfetto_cmd/rate_limiter.h
deleted file mode 100644
index f82211e..0000000
--- a/src/perfetto_cmd/rate_limiter.h
+++ /dev/null
@@ -1,42 +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.
- */
-
-#ifndef SRC_PERFETTO_CMD_RATE_LIMITER_H_
-#define SRC_PERFETTO_CMD_RATE_LIMITER_H_
-
-namespace perfetto {
-
-class RateLimiter {
- public:
- struct Args {
- bool is_user_build = false;
- bool is_uploading = false;
- bool allow_user_build_tracing = false;
- };
- enum ShouldTraceResponse {
- kOkToTrace,
- kNotAllowedOnUserBuild,
- };
-
- RateLimiter();
- virtual ~RateLimiter();
-
- ShouldTraceResponse ShouldTrace(const Args& args);
-};
-
-} // namespace perfetto
-
-#endif // SRC_PERFETTO_CMD_RATE_LIMITER_H_
diff --git a/src/perfetto_cmd/rate_limiter_unittest.cc b/src/perfetto_cmd/rate_limiter_unittest.cc
deleted file mode 100644
index 59045b0..0000000
--- a/src/perfetto_cmd/rate_limiter_unittest.cc
+++ /dev/null
@@ -1,55 +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.
- */
-
-#include "src/perfetto_cmd/rate_limiter.h"
-
-#include "test/gtest_and_gmock.h"
-
-using testing::_;
-using testing::Contains;
-using testing::Invoke;
-using testing::NiceMock;
-using testing::Return;
-using testing::StrictMock;
-
-namespace perfetto {
-namespace {
-
-TEST(RateLimiterTest, CantTraceOnUser) {
- RateLimiter limiter;
- RateLimiter::Args args;
-
- args.is_user_build = true;
- args.allow_user_build_tracing = false;
- args.is_uploading = true;
-
- ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kNotAllowedOnUserBuild);
-}
-
-TEST(RateLimiterTest, CanTraceOnUser) {
- RateLimiter limiter;
- RateLimiter::Args args;
-
- args.is_user_build = false;
- args.allow_user_build_tracing = false;
- args.is_uploading = true;
-
- ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kOkToTrace);
-}
-
-} // namespace
-
-} // namespace perfetto
diff --git a/src/perfetto_cmd/trigger_perfetto.cc b/src/perfetto_cmd/trigger_perfetto.cc
index 3b7f891..c1711d4 100644
--- a/src/perfetto_cmd/trigger_perfetto.cc
+++ b/src/perfetto_cmd/trigger_perfetto.cc
@@ -86,9 +86,6 @@
return PrintUsage(argv[0]);
}
- android_stats::MaybeLogTriggerEvents(
- PerfettoTriggerAtom::kTriggerPerfettoTrigger, triggers_to_activate);
-
bool finished_with_success = false;
base::UnixTaskRunner task_runner;
TriggerProducer producer(
@@ -101,8 +98,6 @@
task_runner.Run();
if (!finished_with_success) {
- android_stats::MaybeLogTriggerEvents(
- PerfettoTriggerAtom::kTriggerPerfettoTriggerFail, triggers_to_activate);
return 1;
}
return 0;
diff --git a/src/profiling/perf/BUILD.gn b/src/profiling/perf/BUILD.gn
index d77cb84..6465bff 100644
--- a/src/profiling/perf/BUILD.gn
+++ b/src/profiling/perf/BUILD.gn
@@ -32,6 +32,7 @@
":producer",
"../../../gn:default_deps",
"../../../src/base",
+ "../../../src/base:version",
"../../../src/tracing/ipc/producer",
]
sources = [
@@ -101,6 +102,8 @@
"../common:unwind_support",
]
sources = [
+ "frame_pointer_unwinder.cc",
+ "frame_pointer_unwinder.h",
"unwind_queue.h",
"unwinding.cc",
"unwinding.h",
@@ -147,6 +150,7 @@
]
sources = [
"event_config_unittest.cc",
+ "frame_pointer_unwinder_unittest.cc",
"perf_producer_unittest.cc",
"unwind_queue_unittest.cc",
]
diff --git a/src/profiling/perf/event_config.cc b/src/profiling/perf/event_config.cc
index 7409c18..94d563c 100644
--- a/src/profiling/perf/event_config.cc
+++ b/src/profiling/perf/event_config.cc
@@ -273,6 +273,20 @@
}
}
+bool IsSupportedUnwindMode(
+ protos::gen::PerfEventConfig::UnwindMode unwind_mode) {
+ using protos::gen::PerfEventConfig;
+ switch (static_cast<int>(unwind_mode)) { // cast to pacify -Wswitch-enum
+ case PerfEventConfig::UNWIND_UNKNOWN:
+ case PerfEventConfig::UNWIND_SKIP:
+ case PerfEventConfig::UNWIND_DWARF:
+ case PerfEventConfig::UNWIND_FRAME_POINTER:
+ return true;
+ default:
+ return false;
+ }
+}
+
} // namespace
// static
@@ -371,32 +385,18 @@
}
// Callstack sampling.
- bool user_frames = false;
bool kernel_frames = false;
+ // Disable user_frames by default.
+ auto unwind_mode = protos::gen::PerfEventConfig::UNWIND_SKIP;
+
TargetFilter target_filter;
bool legacy_config = pb_config.all_cpus(); // all_cpus was mandatory before
if (pb_config.has_callstack_sampling() || legacy_config) {
- user_frames = true;
-
// Userspace callstacks.
- using protos::gen::PerfEventConfig;
- switch (static_cast<int>(pb_config.callstack_sampling().user_frames())) {
- case PerfEventConfig::UNWIND_UNKNOWN:
- // default to true, both for backwards compatibility and because it's
- // almost always what the user wants.
- user_frames = true;
- break;
- case PerfEventConfig::UNWIND_SKIP:
- user_frames = false;
- break;
- case PerfEventConfig::UNWIND_DWARF:
- user_frames = true;
- break;
- default:
- // enum value from the future that we don't yet know, refuse the config
- // TODO(rsavitski): double-check that both pbzero and ::gen propagate
- // unknown enum values.
- return std::nullopt;
+ unwind_mode = pb_config.callstack_sampling().user_frames();
+ if (!IsSupportedUnwindMode(unwind_mode)) {
+ // enum value from the future that we don't yet know, refuse the config
+ return std::nullopt;
}
// Process scoping. Sharding parameter is supplied from outside as it is
@@ -482,7 +482,7 @@
pe.clockid = ToClockId(pb_config.timebase().timestamp_clock());
pe.use_clockid = true;
- if (user_frames) {
+ if (IsUserFramesEnabled(unwind_mode)) {
pe.sample_type |= PERF_SAMPLE_STACK_USER | PERF_SAMPLE_REGS_USER;
// PERF_SAMPLE_STACK_USER:
// Needs to be < ((u16)(~0u)), and have bottom 8 bits clear.
@@ -529,19 +529,35 @@
return EventConfig(
raw_ds_config, pe, std::move(pe_followers), timebase_event, followers,
- user_frames, kernel_frames, std::move(target_filter),
+ kernel_frames, unwind_mode, std::move(target_filter),
ring_buffer_pages.value(), read_tick_period_ms, samples_per_tick_limit,
remote_descriptor_timeout_ms, pb_config.unwind_state_clear_period_ms(),
max_enqueued_footprint_bytes, pb_config.target_installed_by());
}
+// static
+bool EventConfig::IsUserFramesEnabled(
+ const protos::gen::PerfEventConfig::UnwindMode unwind_mode) {
+ using protos::gen::PerfEventConfig;
+ switch (unwind_mode) {
+ case PerfEventConfig::UNWIND_UNKNOWN:
+ // default to true, both for backwards compatibility and because it's
+ // almost always what the user wants.
+ case PerfEventConfig::UNWIND_DWARF:
+ case PerfEventConfig::UNWIND_FRAME_POINTER:
+ return true;
+ case PerfEventConfig::UNWIND_SKIP:
+ return false;
+ }
+}
+
EventConfig::EventConfig(const DataSourceConfig& raw_ds_config,
const perf_event_attr& pe_timebase,
std::vector<perf_event_attr> pe_followers,
const PerfCounter& timebase_event,
std::vector<PerfCounter> follower_events,
- bool user_frames,
bool kernel_frames,
+ protos::gen::PerfEventConfig::UnwindMode unwind_mode,
TargetFilter target_filter,
uint32_t ring_buffer_pages,
uint32_t read_tick_period_ms,
@@ -554,8 +570,8 @@
perf_event_followers_(std::move(pe_followers)),
timebase_event_(timebase_event),
follower_events_(std::move(follower_events)),
- user_frames_(user_frames),
kernel_frames_(kernel_frames),
+ unwind_mode_(unwind_mode),
target_filter_(std::move(target_filter)),
ring_buffer_pages_(ring_buffer_pages),
read_tick_period_ms_(read_tick_period_ms),
diff --git a/src/profiling/perf/event_config.h b/src/profiling/perf/event_config.h
index fc498f7..a87429d 100644
--- a/src/profiling/perf/event_config.h
+++ b/src/profiling/perf/event_config.h
@@ -31,6 +31,7 @@
#include "perfetto/tracing/core/data_source_config.h"
#include "protos/perfetto/common/perf_events.gen.h"
+#include "protos/perfetto/config/profiling/perf_event_config.gen.h"
namespace perfetto {
namespace protos {
@@ -136,9 +137,12 @@
uint64_t max_enqueued_footprint_bytes() const {
return max_enqueued_footprint_bytes_;
}
- bool sample_callstacks() const { return user_frames_ || kernel_frames_; }
- bool user_frames() const { return user_frames_; }
+ bool sample_callstacks() const { return user_frames() || kernel_frames_; }
+ bool user_frames() const { return IsUserFramesEnabled(unwind_mode_); }
bool kernel_frames() const { return kernel_frames_; }
+ protos::gen::PerfEventConfig::UnwindMode unwind_mode() const {
+ return unwind_mode_;
+ }
const TargetFilter& filter() const { return target_filter_; }
perf_event_attr* perf_attr() const {
return const_cast<perf_event_attr*>(&perf_event_attr_);
@@ -158,13 +162,16 @@
const DataSourceConfig& raw_ds_config() const { return raw_ds_config_; }
private:
+ static bool IsUserFramesEnabled(
+ const protos::gen::PerfEventConfig::UnwindMode unwind_mode);
+
EventConfig(const DataSourceConfig& raw_ds_config,
const perf_event_attr& pe_timebase,
std::vector<perf_event_attr> pe_followers,
const PerfCounter& timebase_event,
std::vector<PerfCounter> follower_events,
- bool user_frames,
bool kernel_frames,
+ protos::gen::PerfEventConfig::UnwindMode unwind_mode,
TargetFilter target_filter,
uint32_t ring_buffer_pages,
uint32_t read_tick_period_ms,
@@ -187,12 +194,12 @@
// Timebase event, which are already described by |perf_event_followers_|.
std::vector<PerfCounter> follower_events_;
- // If true, include userspace frames in sampled callstacks.
- const bool user_frames_;
-
// If true, include kernel frames in sampled callstacks.
const bool kernel_frames_;
+ // Userspace unwinding mode.
+ const protos::gen::PerfEventConfig::UnwindMode unwind_mode_;
+
// Parsed allow/deny-list for filtering samples.
const TargetFilter target_filter_;
diff --git a/src/profiling/perf/frame_pointer_unwinder.cc b/src/profiling/perf/frame_pointer_unwinder.cc
new file mode 100644
index 0000000..6841dc6
--- /dev/null
+++ b/src/profiling/perf/frame_pointer_unwinder.cc
@@ -0,0 +1,156 @@
+/*
+ * 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.
+ */
+#include "src/profiling/perf/frame_pointer_unwinder.h"
+
+#include <cinttypes>
+
+#include "perfetto/base/logging.h"
+
+namespace perfetto {
+namespace profiling {
+
+void FramePointerUnwinder::Unwind() {
+ if (!IsArchSupported()) {
+ PERFETTO_ELOG("Unsupported architecture: %d", arch_);
+ last_error_.code = unwindstack::ErrorCode::ERROR_UNSUPPORTED;
+ return;
+ }
+
+ if (maps_ == nullptr || maps_->Total() == 0) {
+ PERFETTO_ELOG("No maps provided");
+ last_error_.code = unwindstack::ErrorCode::ERROR_INVALID_MAP;
+ return;
+ }
+
+ PERFETTO_DCHECK(stack_size_ > 0u);
+
+ frames_.reserve(max_frames_);
+ ClearErrors();
+ TryUnwind();
+}
+
+void FramePointerUnwinder::TryUnwind() {
+ uint64_t fp = 0;
+ switch (arch_) {
+ case unwindstack::ARCH_ARM64:
+ fp = reinterpret_cast<uint64_t*>(
+ regs_->RawData())[unwindstack::Arm64Reg::ARM64_REG_R29];
+ break;
+ case unwindstack::ARCH_X86_64:
+ fp = reinterpret_cast<uint64_t*>(
+ regs_->RawData())[unwindstack::X86_64Reg::X86_64_REG_RBP];
+ break;
+ case unwindstack::ARCH_RISCV64:
+ fp = reinterpret_cast<uint64_t*>(
+ regs_->RawData())[unwindstack::Riscv64Reg::RISCV64_REG_S0];
+ break;
+ case unwindstack::ARCH_UNKNOWN:
+ case unwindstack::ARCH_ARM:
+ case unwindstack::ARCH_X86:
+ // not supported
+ ;
+ }
+ uint64_t sp = regs_->sp();
+ uint64_t pc = regs_->pc();
+ for (size_t i = 0; i < max_frames_; i++) {
+ if (!IsFrameValid(fp, sp))
+ return;
+
+ // retrive the map info and elf info
+ std::shared_ptr<unwindstack::MapInfo> map_info = maps_->Find(pc);
+ if (map_info == nullptr) {
+ last_error_.code = unwindstack::ErrorCode::ERROR_INVALID_MAP;
+ return;
+ }
+
+ unwindstack::FrameData frame;
+ frame.num = i;
+ frame.rel_pc = pc;
+ frame.pc = pc;
+ frame.map_info = map_info;
+ unwindstack::Elf* elf = map_info->GetElf(process_memory_, arch_);
+ if (elf != nullptr) {
+ uint64_t relative_pc = elf->GetRelPc(pc, map_info.get());
+ uint64_t pc_adjustment = GetPcAdjustment(relative_pc, elf, arch_);
+ frame.rel_pc = relative_pc - pc_adjustment;
+ frame.pc = pc - pc_adjustment;
+ if (!resolve_names_ ||
+ !elf->GetFunctionName(frame.rel_pc, &frame.function_name,
+ &frame.function_offset)) {
+ frame.function_name = "";
+ frame.function_offset = 0;
+ }
+ }
+ frames_.push_back(frame);
+ // move to the next frame
+ fp = DecodeFrame(fp, &pc, &sp);
+ }
+}
+
+uint64_t FramePointerUnwinder::DecodeFrame(uint64_t fp,
+ uint64_t* next_pc,
+ uint64_t* next_sp) {
+ uint64_t next_fp;
+ if (!process_memory_->ReadFully(static_cast<uint64_t>(fp), &next_fp,
+ sizeof(next_fp)))
+ return 0;
+
+ uint64_t pc;
+ if (!process_memory_->ReadFully(static_cast<uint64_t>(fp + sizeof(uint64_t)),
+ &pc, sizeof(pc)))
+ return 0;
+
+ // Ensure there's not a stack overflow.
+ if (__builtin_add_overflow(fp, sizeof(uint64_t) * 2, next_sp))
+ return 0;
+
+ *next_pc = static_cast<uint64_t>(pc);
+ return next_fp;
+}
+
+bool FramePointerUnwinder::IsFrameValid(uint64_t fp, uint64_t sp) {
+ uint64_t align_mask = 0;
+ switch (arch_) {
+ case unwindstack::ARCH_ARM64:
+ align_mask = 0x1;
+ break;
+ case unwindstack::ARCH_X86_64:
+ align_mask = 0xf;
+ break;
+ case unwindstack::ARCH_RISCV64:
+ align_mask = 0x7;
+ break;
+ case unwindstack::ARCH_UNKNOWN:
+ case unwindstack::ARCH_ARM:
+ case unwindstack::ARCH_X86:
+ // not supported
+ ;
+ }
+
+ if (fp == 0 || fp <= sp)
+ return false;
+
+ // Ensure there's space on the stack to read two values: the caller's
+ // frame pointer and the return address.
+ uint64_t result;
+ if (__builtin_add_overflow(fp, sizeof(uint64_t) * 2, &result))
+ return false;
+
+ return result <= stack_end_ && (fp & align_mask) == 0;
+}
+
+} // namespace profiling
+} // namespace perfetto
diff --git a/src/profiling/perf/frame_pointer_unwinder.h b/src/profiling/perf/frame_pointer_unwinder.h
new file mode 100644
index 0000000..14534d9
--- /dev/null
+++ b/src/profiling/perf/frame_pointer_unwinder.h
@@ -0,0 +1,103 @@
+/*
+ * 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.
+ */
+#ifndef SRC_PROFILING_PERF_FRAME_POINTER_UNWINDER_H_
+#define SRC_PROFILING_PERF_FRAME_POINTER_UNWINDER_H_
+
+#include <stdint.h>
+#include <memory>
+#include <vector>
+
+#include <unwindstack/Error.h>
+#include <unwindstack/MachineArm64.h>
+#include <unwindstack/MachineRiscv64.h>
+#include <unwindstack/MachineX86_64.h>
+#include <unwindstack/Unwinder.h>
+
+namespace perfetto {
+namespace profiling {
+
+class FramePointerUnwinder {
+ public:
+ FramePointerUnwinder(size_t max_frames,
+ unwindstack::Maps* maps,
+ unwindstack::Regs* regs,
+ std::shared_ptr<unwindstack::Memory> process_memory,
+ size_t stack_size)
+ : max_frames_(max_frames),
+ maps_(maps),
+ regs_(regs),
+ process_memory_(process_memory),
+ stack_size_(stack_size),
+ arch_(regs->Arch()) {
+ stack_end_ = regs->sp() + stack_size;
+ }
+
+ FramePointerUnwinder(const FramePointerUnwinder&) = delete;
+ FramePointerUnwinder& operator=(const FramePointerUnwinder&) = delete;
+
+ void Unwind();
+
+ // Disabling the resolving of names results in the function name being
+ // set to an empty string and the function offset being set to zero.
+ void SetResolveNames(bool resolve) { resolve_names_ = resolve; }
+
+ unwindstack::ErrorCode LastErrorCode() const { return last_error_.code; }
+ uint64_t warnings() const { return warnings_; }
+
+ std::vector<unwindstack::FrameData> ConsumeFrames() {
+ std::vector<unwindstack::FrameData> frames = std::move(frames_);
+ frames_.clear();
+ return frames;
+ }
+
+ bool IsArchSupported() const {
+ return arch_ == unwindstack::ARCH_ARM64 ||
+ arch_ == unwindstack::ARCH_X86_64;
+ }
+
+ void ClearErrors() {
+ warnings_ = unwindstack::WARNING_NONE;
+ last_error_.code = unwindstack::ERROR_NONE;
+ last_error_.address = 0;
+ }
+
+ protected:
+ const size_t max_frames_;
+ unwindstack::Maps* maps_;
+ unwindstack::Regs* regs_;
+ std::vector<unwindstack::FrameData> frames_;
+ std::shared_ptr<unwindstack::Memory> process_memory_;
+ const size_t stack_size_;
+ unwindstack::ArchEnum arch_ = unwindstack::ARCH_UNKNOWN;
+ bool resolve_names_ = false;
+ size_t stack_end_;
+
+ unwindstack::ErrorData last_error_;
+ uint64_t warnings_ = 0;
+
+ private:
+ void TryUnwind();
+ // Given a frame pointer, returns the frame pointer of the calling stack
+ // frame, places the return address of the calling stack frame into
+ // `ret_addr` and stack pointer into `sp`.
+ uint64_t DecodeFrame(uint64_t fp, uint64_t* ret_addr, uint64_t* sp);
+ bool IsFrameValid(uint64_t fp, uint64_t sp);
+};
+
+} // namespace profiling
+} // namespace perfetto
+
+#endif // SRC_PROFILING_PERF_FRAME_POINTER_UNWINDER_H_
diff --git a/src/profiling/perf/frame_pointer_unwinder_unittest.cc b/src/profiling/perf/frame_pointer_unwinder_unittest.cc
new file mode 100644
index 0000000..c494fd7
--- /dev/null
+++ b/src/profiling/perf/frame_pointer_unwinder_unittest.cc
@@ -0,0 +1,229 @@
+/*
+ * 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.
+ */
+
+#include "src/profiling/perf/frame_pointer_unwinder.h"
+
+#include <sys/mman.h>
+#include <unwindstack/Unwinder.h>
+
+#include "perfetto/base/logging.h"
+#include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/scoped_file.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace profiling {
+namespace {
+
+class RegsFake : public unwindstack::Regs {
+ public:
+ RegsFake(uint16_t total_regs)
+ : unwindstack::Regs(
+ total_regs,
+ unwindstack::Regs::Location(unwindstack::Regs::LOCATION_UNKNOWN,
+ 0)) {
+ fake_data_ = std::make_unique<uint64_t[]>(total_regs);
+ }
+ ~RegsFake() override = default;
+
+ unwindstack::ArchEnum Arch() override { return fake_arch_; }
+ void* RawData() override { return fake_data_.get(); }
+ uint64_t pc() override { return fake_pc_; }
+ uint64_t sp() override { return fake_sp_; }
+ void set_pc(uint64_t pc) override { fake_pc_ = pc; }
+ void set_sp(uint64_t sp) override { fake_sp_ = sp; }
+
+ void set_fp(uint64_t fp) {
+ switch (fake_arch_) {
+ case unwindstack::ARCH_ARM64:
+ fake_data_[unwindstack::Arm64Reg::ARM64_REG_R29] = fp;
+ break;
+ case unwindstack::ARCH_X86_64:
+ fake_data_[unwindstack::X86_64Reg::X86_64_REG_RBP] = fp;
+ break;
+ case unwindstack::ARCH_RISCV64:
+ fake_data_[unwindstack::Riscv64Reg::RISCV64_REG_S0] = fp;
+ break;
+ case unwindstack::ARCH_UNKNOWN:
+ case unwindstack::ARCH_ARM:
+ case unwindstack::ARCH_X86:
+ // not supported
+ ;
+ }
+ }
+
+ bool SetPcFromReturnAddress(unwindstack::Memory*) override { return false; }
+
+ void IterateRegisters(std::function<void(const char*, uint64_t)>) override {}
+
+ bool StepIfSignalHandler(uint64_t,
+ unwindstack::Elf*,
+ unwindstack::Memory*) override {
+ return false;
+ }
+
+ void FakeSetArch(unwindstack::ArchEnum arch) { fake_arch_ = arch; }
+
+ Regs* Clone() override { return nullptr; }
+
+ private:
+ unwindstack::ArchEnum fake_arch_ = unwindstack::ARCH_UNKNOWN;
+ uint64_t fake_pc_ = 0;
+ uint64_t fake_sp_ = 0;
+ std::unique_ptr<uint64_t[]> fake_data_;
+};
+
+class MemoryFake : public unwindstack::Memory {
+ public:
+ MemoryFake() = default;
+ ~MemoryFake() override = default;
+
+ size_t Read(uint64_t addr, void* memory, size_t size) override {
+ uint8_t* dst = reinterpret_cast<uint8_t*>(memory);
+ for (size_t i = 0; i < size; i++, addr++) {
+ auto value = data_.find(addr);
+ if (value == data_.end()) {
+ return i;
+ }
+ dst[i] = value->second;
+ }
+ return size;
+ }
+
+ void SetMemory(uint64_t addr, const void* memory, size_t length) {
+ const uint8_t* src = reinterpret_cast<const uint8_t*>(memory);
+ for (size_t i = 0; i < length; i++, addr++) {
+ auto value = data_.find(addr);
+ if (value != data_.end()) {
+ value->second = src[i];
+ } else {
+ data_.insert({addr, src[i]});
+ }
+ }
+ }
+
+ void SetData8(uint64_t addr, uint8_t value) {
+ SetMemory(addr, &value, sizeof(value));
+ }
+
+ void SetData16(uint64_t addr, uint16_t value) {
+ SetMemory(addr, &value, sizeof(value));
+ }
+
+ void SetData32(uint64_t addr, uint32_t value) {
+ SetMemory(addr, &value, sizeof(value));
+ }
+
+ void SetData64(uint64_t addr, uint64_t value) {
+ SetMemory(addr, &value, sizeof(value));
+ }
+
+ void SetMemory(uint64_t addr, std::vector<uint8_t> values) {
+ SetMemory(addr, values.data(), values.size());
+ }
+
+ void SetMemory(uint64_t addr, std::string string) {
+ SetMemory(addr, string.c_str(), string.size() + 1);
+ }
+
+ void Clear() override { data_.clear(); }
+
+ private:
+ std::unordered_map<uint64_t, uint8_t> data_;
+};
+
+constexpr static uint64_t kMaxFrames = 64;
+constexpr static uint64_t kStackSize = 0xFFFFFFF;
+
+class FramePointerUnwinderTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ memory_fake_ = new MemoryFake;
+ maps_.reset(new unwindstack::Maps);
+ regs_fake_ = std::make_unique<RegsFake>(64);
+ regs_fake_->FakeSetArch(unwindstack::ARCH_X86_64);
+ process_memory_.reset(memory_fake_);
+
+ unwinder_ = std::make_unique<FramePointerUnwinder>(
+ kMaxFrames, maps_.get(), regs_fake_.get(), process_memory_, kStackSize);
+ }
+
+ MemoryFake* memory_fake_;
+ std::unique_ptr<unwindstack::Maps> maps_;
+ std::unique_ptr<RegsFake> regs_fake_;
+ std::shared_ptr<unwindstack::Memory> process_memory_;
+
+ std::unique_ptr<FramePointerUnwinder> unwinder_;
+};
+
+TEST_F(FramePointerUnwinderTest, UnwindUnsupportedArch) {
+ regs_fake_->FakeSetArch(unwindstack::ARCH_UNKNOWN);
+ unwinder_.reset(new FramePointerUnwinder(
+ kMaxFrames, maps_.get(), regs_fake_.get(), process_memory_, kStackSize));
+ unwinder_->Unwind();
+ EXPECT_EQ(unwinder_->LastErrorCode(),
+ unwindstack::ErrorCode::ERROR_UNSUPPORTED);
+
+ regs_fake_->FakeSetArch(unwindstack::ARCH_X86);
+ unwinder_.reset(new FramePointerUnwinder(
+ kMaxFrames, maps_.get(), regs_fake_.get(), process_memory_, kStackSize));
+ unwinder_->Unwind();
+ EXPECT_EQ(unwinder_->LastErrorCode(),
+ unwindstack::ErrorCode::ERROR_UNSUPPORTED);
+
+ regs_fake_->FakeSetArch(unwindstack::ARCH_ARM);
+ unwinder_.reset(new FramePointerUnwinder(
+ kMaxFrames, maps_.get(), regs_fake_.get(), process_memory_, kStackSize));
+ unwinder_->Unwind();
+ EXPECT_EQ(unwinder_->LastErrorCode(),
+ unwindstack::ErrorCode::ERROR_UNSUPPORTED);
+}
+
+TEST_F(FramePointerUnwinderTest, UnwindInvalidMaps) {
+ // Set up a valid stack frame
+ regs_fake_->set_pc(0x1000);
+ regs_fake_->set_sp(0x2000);
+ memory_fake_->SetData64(0x2000, 0x3000);
+ memory_fake_->SetData64(0x2008, 0x2000);
+ unwinder_->Unwind();
+ EXPECT_EQ(unwinder_->LastErrorCode(),
+ unwindstack::ErrorCode::ERROR_INVALID_MAP);
+ EXPECT_EQ(unwinder_->ConsumeFrames().size(), 0UL);
+}
+
+TEST_F(FramePointerUnwinderTest, UnwindValidStack) {
+ regs_fake_->set_pc(0x1900);
+ regs_fake_->set_sp(0x1800);
+ regs_fake_->set_fp(0x2000);
+
+ memory_fake_->SetData64(0x2000, 0x2200); // mock next_fp
+ memory_fake_->SetData64(0x2000 + sizeof(uint64_t),
+ 0x2100); // mock return_address(next_pc)
+
+ memory_fake_->SetData64(0x2200, 0);
+
+ maps_->Add(0x1000, 0x12000, 0, PROT_READ | PROT_WRITE, "libmock.so");
+
+ unwinder_.reset(new FramePointerUnwinder(
+ kMaxFrames, maps_.get(), regs_fake_.get(), process_memory_, kStackSize));
+ unwinder_->Unwind();
+ EXPECT_EQ(unwinder_->LastErrorCode(), unwindstack::ErrorCode::ERROR_NONE);
+ EXPECT_EQ(unwinder_->ConsumeFrames().size(), 2UL);
+}
+
+} // namespace
+} // namespace profiling
+} // namespace perfetto
diff --git a/src/profiling/perf/perf_producer.cc b/src/profiling/perf/perf_producer.cc
index 492907e..86d7b15 100644
--- a/src/profiling/perf/perf_producer.cc
+++ b/src/profiling/perf/perf_producer.cc
@@ -500,8 +500,12 @@
// Inform unwinder of the new data source instance, and optionally start a
// periodic task to clear its cached state.
- unwinding_worker_->PostStartDataSource(ds_id,
- ds.event_config.kernel_frames());
+ auto unwind_mode = (ds.event_config.unwind_mode() ==
+ protos::gen::PerfEventConfig::UNWIND_FRAME_POINTER)
+ ? Unwinder::UnwindMode::kFramePointer
+ : Unwinder::UnwindMode::kUnwindStack;
+ unwinding_worker_->PostStartDataSource(ds_id, ds.event_config.kernel_frames(),
+ unwind_mode);
if (ds.event_config.unwind_state_clear_period_ms()) {
unwinding_worker_->PostClearCachedStatePeriodic(
ds_id, ds.event_config.unwind_state_clear_period_ms());
diff --git a/src/profiling/perf/traced_perf.cc b/src/profiling/perf/traced_perf.cc
index 7664053..f08d866 100644
--- a/src/profiling/perf/traced_perf.cc
+++ b/src/profiling/perf/traced_perf.cc
@@ -15,8 +15,14 @@
*/
#include "src/profiling/perf/traced_perf.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "perfetto/ext/base/getopt.h"
#include "perfetto/ext/base/file_utils.h"
#include "perfetto/ext/base/unix_task_runner.h"
+#include "perfetto/ext/base/version.h"
#include "perfetto/tracing/default_socket.h"
#include "src/profiling/perf/perf_producer.h"
#include "src/profiling/perf/proc_descriptors.h"
@@ -41,7 +47,40 @@
} // namespace
// TODO(rsavitski): watchdog.
-int TracedPerfMain(int, char**) {
+int TracedPerfMain(int argc, char** argv) {
+ enum LongOption {
+ OPT_BACKGROUND = 1000,
+ OPT_VERSION,
+ };
+
+ bool background = false;
+
+ static const option long_options[] = {
+ {"background", no_argument, nullptr, OPT_BACKGROUND},
+ {"version", no_argument, nullptr, OPT_VERSION},
+ {nullptr, 0, nullptr, 0}};
+
+ for (;;) {
+ int option = getopt_long(argc, argv, "", long_options, nullptr);
+ if (option == -1)
+ break;
+ switch (option) {
+ case OPT_BACKGROUND:
+ background = true;
+ break;
+ case OPT_VERSION:
+ printf("%s\n", base::GetVersionString());
+ return 0;
+ default:
+ fprintf(stderr, "Usage: %s [--background] [--version]\n", argv[0]);
+ return 1;
+ }
+ }
+
+ if (background) {
+ base::Daemonize([] { return 0; });
+ }
+
base::UnixTaskRunner task_runner;
// TODO(rsavitski): support standalone --root or similar on android.
diff --git a/src/profiling/perf/unwinding.cc b/src/profiling/perf/unwinding.cc
index 53023a8..7ead30f 100644
--- a/src/profiling/perf/unwinding.cc
+++ b/src/profiling/perf/unwinding.cc
@@ -25,6 +25,7 @@
#include "perfetto/ext/base/no_destructor.h"
#include "perfetto/ext/base/thread_utils.h"
#include "perfetto/ext/base/utils.h"
+#include "src/profiling/perf/frame_pointer_unwinder.h"
namespace {
constexpr size_t kUnwindingMaxFrames = 1000;
@@ -43,18 +44,23 @@
}
void Unwinder::PostStartDataSource(DataSourceInstanceID ds_id,
- bool kernel_frames) {
+ bool kernel_frames,
+ UnwindMode unwind_mode) {
// No need for a weak pointer as the associated task runner quits (stops
// running tasks) strictly before the Unwinder's destruction.
- task_runner_->PostTask(
- [this, ds_id, kernel_frames] { StartDataSource(ds_id, kernel_frames); });
+ task_runner_->PostTask([this, ds_id, kernel_frames, unwind_mode] {
+ StartDataSource(ds_id, kernel_frames, unwind_mode);
+ });
}
-void Unwinder::StartDataSource(DataSourceInstanceID ds_id, bool kernel_frames) {
+void Unwinder::StartDataSource(DataSourceInstanceID ds_id,
+ bool kernel_frames,
+ UnwindMode unwind_mode) {
PERFETTO_DCHECK_THREAD(thread_checker_);
PERFETTO_DLOG("Unwinder::StartDataSource(%zu)", static_cast<size_t>(ds_id));
- auto it_and_inserted = data_sources_.emplace(ds_id, DataSourceState{});
+ auto it_and_inserted =
+ data_sources_.emplace(ds_id, DataSourceState{unwind_mode});
PERFETTO_DCHECK(it_and_inserted.second);
if (kernel_frames) {
@@ -297,8 +303,9 @@
(proc_state.unwind_state.has_value()
? &proc_state.unwind_state.value()
: nullptr);
- CompletedSample unwound_sample = UnwindSample(
- entry.sample, opt_user_state, proc_state.attempted_unwinding);
+ CompletedSample unwound_sample =
+ UnwindSample(entry.sample, opt_user_state,
+ proc_state.attempted_unwinding, ds.unwind_mode);
proc_state.attempted_unwinding = true;
PERFETTO_METATRACE_COUNTER(TAG_PRODUCER, PROFILER_UNWIND_CURRENT_PID, 0);
@@ -334,7 +341,8 @@
CompletedSample Unwinder::UnwindSample(const ParsedSample& sample,
UnwindingMetadata* opt_user_state,
- bool pid_unwound_before) {
+ bool pid_unwound_before,
+ UnwindMode unwind_mode) {
PERFETTO_DCHECK_THREAD(thread_checker_);
CompletedSample ret;
@@ -375,7 +383,7 @@
UnwindResult& operator=(UnwindResult&&) = default;
};
auto attempt_unwind = [&sample, unwind_state, pid_unwound_before,
- &overlay_memory]() -> UnwindResult {
+ &overlay_memory, unwind_mode]() -> UnwindResult {
metatrace::ScopedEvent m(metatrace::TAG_PRODUCER,
pid_unwound_before
? metatrace::PROFILER_UNWIND_ATTEMPT
@@ -384,16 +392,29 @@
// Unwindstack clobbers registers, so make a copy in case of retries.
auto regs_copy = std::unique_ptr<unwindstack::Regs>{sample.regs->Clone()};
- unwindstack::Unwinder unwinder(kUnwindingMaxFrames, &unwind_state->fd_maps,
- regs_copy.get(), overlay_memory);
+ switch (unwind_mode) {
+ case UnwindMode::kFramePointer: {
+ FramePointerUnwinder unwinder(kUnwindingMaxFrames,
+ &unwind_state->fd_maps, regs_copy.get(),
+ overlay_memory, sample.stack.size());
+ unwinder.Unwind();
+ return {unwinder.LastErrorCode(), unwinder.warnings(),
+ unwinder.ConsumeFrames()};
+ }
+ case UnwindMode::kUnwindStack: {
+ unwindstack::Unwinder unwinder(kUnwindingMaxFrames,
+ &unwind_state->fd_maps, regs_copy.get(),
+ overlay_memory);
#if PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD)
- unwinder.SetJitDebug(unwind_state->GetJitDebug(regs_copy->Arch()));
- unwinder.SetDexFiles(unwind_state->GetDexFiles(regs_copy->Arch()));
+ unwinder.SetJitDebug(unwind_state->GetJitDebug(regs_copy->Arch()));
+ unwinder.SetDexFiles(unwind_state->GetDexFiles(regs_copy->Arch()));
#endif
- unwinder.Unwind(/*initial_map_names_to_skip=*/nullptr,
- /*map_suffixes_to_ignore=*/nullptr);
- return {unwinder.LastErrorCode(), unwinder.warnings(),
- unwinder.ConsumeFrames()};
+ unwinder.Unwind(/*initial_map_names_to_skip=*/nullptr,
+ /*map_suffixes_to_ignore=*/nullptr);
+ return {unwinder.LastErrorCode(), unwinder.warnings(),
+ unwinder.ConsumeFrames()};
+ }
+ }
};
// first unwind attempt
diff --git a/src/profiling/perf/unwinding.h b/src/profiling/perf/unwinding.h
index 8295fb8..13a7d6d 100644
--- a/src/profiling/perf/unwinding.h
+++ b/src/profiling/perf/unwinding.h
@@ -75,6 +75,8 @@
public:
friend class UnwinderHandle;
+ enum class UnwindMode { kUnwindStack, kFramePointer };
+
// Callbacks from the unwinder to the primary producer thread.
class Delegate {
public:
@@ -89,7 +91,9 @@
~Unwinder() { PERFETTO_DCHECK_THREAD(thread_checker_); }
- void PostStartDataSource(DataSourceInstanceID ds_id, bool kernel_frames);
+ void PostStartDataSource(DataSourceInstanceID ds_id,
+ bool kernel_frames,
+ UnwindMode unwind_mode);
void PostAdoptProcDescriptors(DataSourceInstanceID ds_id,
pid_t pid,
base::ScopedFile maps_fd,
@@ -144,8 +148,11 @@
struct DataSourceState {
enum class Status { kActive, kShuttingDown };
+ explicit DataSourceState(UnwindMode _unwind_mode)
+ : unwind_mode(_unwind_mode) {}
Status status = Status::kActive;
+ const UnwindMode unwind_mode;
std::map<pid_t, ProcessState> process_states;
};
@@ -163,7 +170,9 @@
// Marks the data source as valid and active at the unwinding stage.
// Initializes kernel address symbolization if needed.
- void StartDataSource(DataSourceInstanceID ds_id, bool kernel_frames);
+ void StartDataSource(DataSourceInstanceID ds_id,
+ bool kernel_frames,
+ UnwindMode unwind_mode);
void AdoptProcDescriptors(DataSourceInstanceID ds_id,
pid_t pid,
@@ -184,7 +193,8 @@
CompletedSample UnwindSample(const ParsedSample& sample,
UnwindingMetadata* opt_user_state,
- bool pid_unwound_before);
+ bool pid_unwound_before,
+ UnwindMode unwind_mode);
// Returns a list of symbolized kernel frames in the sample (if any).
std::vector<unwindstack::FrameData> SymbolizeKernelCallchain(
diff --git a/src/protozero/BUILD.gn b/src/protozero/BUILD.gn
index d1285bb..ebdcc6f 100644
--- a/src/protozero/BUILD.gn
+++ b/src/protozero/BUILD.gn
@@ -104,6 +104,8 @@
"test/example_proto/test_messages.proto",
"test/example_proto/upper_import.proto",
]
+ generate_descriptor = "test_messages.descriptor"
+ descriptor_root_source = "test/example_proto/test_messages.proto"
proto_path = perfetto_root_path
}
@@ -117,13 +119,6 @@
proto_path = perfetto_root_path
}
-perfetto_proto_library("test_messages_descriptor") {
- proto_generators = [ "descriptor" ]
- generate_descriptor = "test_messages.descriptor"
- sources = [ "test/example_proto/test_messages.proto" ]
- deps = [ ":testing_messages_source_set" ]
-}
-
perfetto_fuzzer_test("protozero_decoder_fuzzer") {
sources = [ "proto_decoder_fuzzer.cc" ]
deps = [
diff --git a/src/shared_lib/test/api_integrationtest.cc b/src/shared_lib/test/api_integrationtest.cc
index 025ef37..d5c7d45 100644
--- a/src/shared_lib/test/api_integrationtest.cc
+++ b/src/shared_lib/test/api_integrationtest.cc
@@ -66,6 +66,7 @@
using ::testing::DoAll;
using ::testing::ElementsAre;
using ::testing::InSequence;
+using ::testing::IsNull;
using ::testing::NiceMock;
using ::testing::ResultOf;
using ::testing::Return;
@@ -966,6 +967,56 @@
PERFETTO_DS_TRACE(data_source_1, ctx) {}
}
+TEST_F(SharedLibDataSourceTest, GetInstanceLockedSuccess) {
+ bool ignored = false;
+ void* const kInstancePtr = &ignored;
+ EXPECT_CALL(ds2_callbacks_, OnSetup(_, _, _, _, kDataSource2UserArg, _))
+ .WillOnce(Return(kInstancePtr));
+ TracingSession tracing_session =
+ TracingSession::Builder().set_data_source_name(kDataSourceName2).Build();
+
+ void* arg = nullptr;
+ PERFETTO_DS_TRACE(data_source_2, ctx) {
+ arg = PerfettoDsImplGetInstanceLocked(data_source_2.impl, ctx.impl.inst_id);
+ if (arg) {
+ PerfettoDsImplReleaseInstanceLocked(data_source_2.impl, ctx.impl.inst_id);
+ }
+ }
+
+ EXPECT_EQ(arg, kInstancePtr);
+}
+
+TEST_F(SharedLibDataSourceTest, GetInstanceLockedFailure) {
+ bool ignored = false;
+ void* const kInstancePtr = &ignored;
+ EXPECT_CALL(ds2_callbacks_, OnSetup(_, _, _, _, kDataSource2UserArg, _))
+ .WillOnce(Return(kInstancePtr));
+ TracingSession tracing_session =
+ TracingSession::Builder().set_data_source_name(kDataSourceName2).Build();
+
+ WaitableEvent inside_tracing;
+ WaitableEvent stopped;
+
+ std::thread t([&] {
+ PERFETTO_DS_TRACE(data_source_2, ctx) {
+ inside_tracing.Notify();
+ stopped.WaitForNotification();
+ void* arg =
+ PerfettoDsImplGetInstanceLocked(data_source_2.impl, ctx.impl.inst_id);
+ if (arg) {
+ PerfettoDsImplReleaseInstanceLocked(data_source_2.impl,
+ ctx.impl.inst_id);
+ }
+ EXPECT_THAT(arg, IsNull());
+ }
+ });
+
+ inside_tracing.WaitForNotification();
+ tracing_session.StopBlocking();
+ stopped.Notify();
+ t.join();
+}
+
// Regression test for a `PerfettoDsImplReleaseInstanceLocked()`. Under very
// specific circumstances, that depends on the implementation details of
// `TracingMuxerImpl`, the following events can happen:
diff --git a/src/shared_lib/track_event.cc b/src/shared_lib/track_event.cc
index e3115ba..6714032 100644
--- a/src/shared_lib/track_event.cc
+++ b/src/shared_lib/track_event.cc
@@ -962,8 +962,10 @@
return;
}
- const PerfettoTeRegisteredTrackImpl* registered_track = nullptr;
- const PerfettoTeHlExtraNamedTrack* named_track = nullptr;
+ std::variant<std::monostate, const PerfettoTeRegisteredTrackImpl*,
+ const PerfettoTeHlExtraNamedTrack*,
+ const PerfettoTeHlExtraProtoTrack*>
+ track;
std::optional<uint64_t> track_uuid;
const struct PerfettoTeHlExtraTimestamp* custom_timestamp = nullptr;
@@ -979,12 +981,13 @@
const auto& cast =
reinterpret_cast<const struct PerfettoTeHlExtraRegisteredTrack&>(
extra);
- registered_track = cast.track;
- named_track = nullptr;
+ track = cast.track;
} else if (extra.type == PERFETTO_TE_HL_EXTRA_TYPE_NAMED_TRACK) {
- registered_track = nullptr;
- named_track =
+ track =
&reinterpret_cast<const struct PerfettoTeHlExtraNamedTrack&>(extra);
+ } else if (extra.type == PERFETTO_TE_HL_EXTRA_TYPE_PROTO_TRACK) {
+ track =
+ &reinterpret_cast<const struct PerfettoTeHlExtraProtoTrack&>(extra);
} else if (extra.type == PERFETTO_TE_HL_EXTRA_TYPE_TIMESTAMP) {
custom_timestamp =
&reinterpret_cast<const struct PerfettoTeHlExtraTimestamp&>(extra);
@@ -1032,7 +1035,9 @@
ResetIncrementalStateIfRequired(ii->instance->trace_writer.get(), incr_state,
track_event_tls, ts);
- if (registered_track) {
+ if (std::holds_alternative<const PerfettoTeRegisteredTrackImpl*>(track)) {
+ auto* registered_track =
+ std::get<const PerfettoTeRegisteredTrackImpl*>(track);
if (incr_state->seen_track_uuids.insert(registered_track->uuid).second) {
auto packet = ii->instance->trace_writer->NewTracePacket();
auto* track_descriptor = packet->set_track_descriptor();
@@ -1040,7 +1045,9 @@
registered_track->descriptor_size);
}
track_uuid = registered_track->uuid;
- } else if (named_track) {
+ } else if (std::holds_alternative<const PerfettoTeHlExtraNamedTrack*>(
+ track)) {
+ auto* named_track = std::get<const PerfettoTeHlExtraNamedTrack*>(track);
uint64_t uuid = named_track->parent_uuid;
uuid ^= PerfettoFnv1a(named_track->name, strlen(named_track->name));
uuid ^= named_track->id;
@@ -1054,6 +1061,17 @@
track_descriptor->set_name(named_track->name);
}
track_uuid = uuid;
+ } else if (std::holds_alternative<const PerfettoTeHlExtraProtoTrack*>(
+ track)) {
+ auto* counter_track = std::get<const PerfettoTeHlExtraProtoTrack*>(track);
+ uint64_t uuid = counter_track->uuid;
+ if (incr_state->seen_track_uuids.insert(uuid).second) {
+ auto packet = ii->instance->trace_writer->NewTracePacket();
+ auto* track_descriptor = packet->set_track_descriptor();
+ track_descriptor->set_uuid(uuid);
+ AppendHlProtoFields(track_descriptor, counter_track->fields);
+ }
+ track_uuid = uuid;
}
perfetto::TraceWriterBase* trace_writer = ii->instance->trace_writer.get();
diff --git a/src/tools/ftrace_proto_gen/event_list b/src/tools/ftrace_proto_gen/event_list
index 62528f0..8c99f49 100644
--- a/src/tools/ftrace_proto_gen/event_list
+++ b/src/tools/ftrace_proto_gen/event_list
@@ -535,3 +535,5 @@
pixel_mm/pixel_mm_kswapd_wake
pixel_mm/pixel_mm_kswapd_done
sched/sched_wakeup_task_attr
+devfreq/devfreq_frequency
+cpm_trace/param_set_value_cpm
diff --git a/src/tools/ftrace_proto_gen/ftrace_proto_gen.cc b/src/tools/ftrace_proto_gen/ftrace_proto_gen.cc
index a036016..3fd774c 100644
--- a/src/tools/ftrace_proto_gen/ftrace_proto_gen.cc
+++ b/src/tools/ftrace_proto_gen/ftrace_proto_gen.cc
@@ -169,6 +169,10 @@
*fout << " GenericFtraceEvent generic = " << i << ";\n";
++i;
}
+ if (i == 542) {
+ *fout << " KprobeEvent kprobe_event = " << i << ";\n";
+ ++i;
+ }
}
*fout << " }\n";
*fout << "}\n";
diff --git a/src/tools/ftrace_proto_gen/proto_gen_utils.cc b/src/tools/ftrace_proto_gen/proto_gen_utils.cc
index d734e88..ee4257b 100644
--- a/src/tools/ftrace_proto_gen/proto_gen_utils.cc
+++ b/src/tools/ftrace_proto_gen/proto_gen_utils.cc
@@ -170,6 +170,9 @@
if (type == google::protobuf::FieldDescriptor::Type::TYPE_STRING)
return String(is_repeated);
+ if (type == google::protobuf::FieldDescriptor::Type::TYPE_ENUM)
+ return Numeric(32, true, is_repeated);
+
return Invalid();
}
diff --git a/src/trace_processor/BUILD.gn b/src/trace_processor/BUILD.gn
index 3834b57..5666a50 100644
--- a/src/trace_processor/BUILD.gn
+++ b/src/trace_processor/BUILD.gn
@@ -156,7 +156,6 @@
"trace_processor_impl.cc",
"trace_processor_impl.h",
]
-
deps = [
":metatrace",
":storage_minimal",
@@ -166,22 +165,23 @@
"../../protos/perfetto/trace/perfetto:zero",
"../../protos/perfetto/trace_processor:zero",
"../base",
+ "../base:clock_snapshots",
"../protozero",
"db",
"importers/android_bugreport",
+ "importers/archive",
+ "importers/art_method",
"importers/common",
"importers/etw:full",
"importers/ftrace:full",
"importers/fuchsia:full",
- "importers/gzip:full",
- "importers/json:full",
"importers/json:minimal",
"importers/ninja",
"importers/perf",
+ "importers/perf_text",
"importers/proto:full",
"importers/proto:minimal",
"importers/systrace:full",
- "importers/zip:full",
"metrics",
"perfetto_sql/engine",
"perfetto_sql/intrinsics/functions",
@@ -207,6 +207,12 @@
if (enable_perfetto_trace_processor_mac_instruments) {
deps += [ "importers/instruments" ]
}
+ if (enable_perfetto_trace_processor_json) {
+ deps += [
+ "importers/gecko",
+ "importers/json",
+ ]
+ }
}
executable("trace_processor_shell") {
@@ -241,7 +247,7 @@
"../../protos/perfetto/trace:descriptor",
"../../protos/perfetto/trace:test_extensions_descriptor",
"../../protos/perfetto/trace_processor:stack_descriptor",
- "../../protos/third_party/pprof:profile_descriptor",
+ "../../protos/third_party/pprof:descriptor",
]
}
}
@@ -337,7 +343,7 @@
perfetto_cc_proto_descriptor("gen_cc_test_messages_descriptor") {
descriptor_name = "test_messages.descriptor"
- descriptor_target = "../protozero:test_messages_descriptor"
+ descriptor_target = "../protozero:testing_messages_descriptor"
}
source_set("integrationtests") {
diff --git a/src/trace_processor/containers/row_map_unittest.cc b/src/trace_processor/containers/row_map_unittest.cc
index 28d5e0e..049a071 100644
--- a/src/trace_processor/containers/row_map_unittest.cc
+++ b/src/trace_processor/containers/row_map_unittest.cc
@@ -18,7 +18,6 @@
#include <memory>
-#include "src/base/test/gtest_test_suite.h"
#include "test/gtest_and_gmock.h"
namespace perfetto {
diff --git a/src/trace_processor/export_json_unittest.cc b/src/trace_processor/export_json_unittest.cc
index c34b1a8..08c4148 100644
--- a/src/trace_processor/export_json_unittest.cc
+++ b/src/trace_processor/export_json_unittest.cc
@@ -44,6 +44,7 @@
#include "src/trace_processor/importers/common/process_track_translation_table.h"
#include "src/trace_processor/importers/common/process_tracker.h"
#include "src/trace_processor/importers/common/track_tracker.h"
+#include "src/trace_processor/importers/common/tracks.h"
#include "src/trace_processor/importers/proto/track_event_tracker.h"
#include "src/trace_processor/storage/metadata.h"
#include "src/trace_processor/storage/stats.h"
@@ -251,8 +252,8 @@
}
TEST_F(ExportJsonTest, SystemEventsIgnored) {
- TrackId track = context_.track_tracker->CreateProcessAsyncTrack(
- /*name=*/kNullStringId, /*upid=*/0, /*source=*/kNullStringId);
+ TrackId track =
+ context_.track_tracker->InternProcessTrack(tracks::unknown, 0);
context_.args_tracker->Flush(); // Flush track args.
// System events have no category.
@@ -769,7 +770,12 @@
// Global legacy track.
TrackId track = context_.track_tracker->InternGlobalTrack(
- TrackTracker::GlobalTrackType::kChromeLegacyGlobalInstant);
+ tracks::legacy_chrome_global_instants, TrackTracker::AutoName(),
+ [this](ArgsTracker::BoundInserter& inserter) {
+ inserter.AddArg(
+ context_.storage->InternString("source"),
+ Variadic::String(context_.storage->InternString("chrome")));
+ });
context_.args_tracker->Flush(); // Flush track args.
StringId cat_id = context_.storage->InternString(base::StringView(kCategory));
StringId name_id = context_.storage->InternString(base::StringView(kName));
@@ -784,7 +790,9 @@
{kTimestamp2, 0, track2, cat_id, name_id, 0, 0, 0});
// Async event track.
- track_event_tracker.ReserveDescriptorChildTrack(1234, 0, kNullStringId);
+ TrackEventTracker::DescriptorTrackReservation reservation;
+ reservation.parent_uuid = 0;
+ track_event_tracker.ReserveDescriptorTrack(1234, reservation);
TrackId track3 = *track_event_tracker.GetDescriptorTrack(1234);
context_.args_tracker->Flush(); // Flush track args.
context_.storage->mutable_slice_table()->Insert(
@@ -982,11 +990,11 @@
StringId name3_id = context_.storage->InternString(base::StringView(kName3));
constexpr int64_t kSourceId = 235;
- TrackId track = context_.track_tracker->InternLegacyChromeAsyncTrack(
+ TrackId track = context_.track_tracker->LegacyInternLegacyChromeAsyncTrack(
name_id, upid, kSourceId, /*trace_id_is_process_scoped=*/true,
/*source_scope=*/kNullStringId);
constexpr int64_t kSourceId2 = 236;
- TrackId track2 = context_.track_tracker->InternLegacyChromeAsyncTrack(
+ TrackId track2 = context_.track_tracker->LegacyInternLegacyChromeAsyncTrack(
name3_id, upid, kSourceId2, /*trace_id_is_process_scoped=*/true,
/*source_scope=*/kNullStringId);
context_.args_tracker->Flush(); // Flush track args.
@@ -1129,11 +1137,11 @@
};
constexpr int64_t kSourceId = 235;
- TrackId track = context_.track_tracker->InternLegacyChromeAsyncTrack(
+ TrackId track = context_.track_tracker->LegacyInternLegacyChromeAsyncTrack(
name_id, upid, kSourceId, /*trace_id_is_process_scoped=*/true,
/*source_scope=*/kNullStringId);
constexpr int64_t kSourceId2 = 236;
- TrackId track2 = context_.track_tracker->InternLegacyChromeAsyncTrack(
+ TrackId track2 = context_.track_tracker->LegacyInternLegacyChromeAsyncTrack(
name3_id, upid, kSourceId2, /*trace_id_is_process_scoped=*/true,
/*source_scope=*/kNullStringId);
context_.args_tracker->Flush(); // Flush track args.
@@ -1252,7 +1260,7 @@
StringId name_id = context_.storage->InternString(base::StringView(kName));
constexpr int64_t kSourceId = 235;
- TrackId track = context_.track_tracker->InternLegacyChromeAsyncTrack(
+ TrackId track = context_.track_tracker->LegacyInternLegacyChromeAsyncTrack(
name_id, upid, kSourceId, /*trace_id_is_process_scoped=*/true,
/*source_scope=*/kNullStringId);
context_.args_tracker->Flush(); // Flush track args.
@@ -1308,7 +1316,7 @@
StringId name_id = context_.storage->InternString(base::StringView(kName));
constexpr int64_t kSourceId = 235;
- TrackId track = context_.track_tracker->InternLegacyChromeAsyncTrack(
+ TrackId track = context_.track_tracker->LegacyInternLegacyChromeAsyncTrack(
name_id, upid, kSourceId, /*trace_id_is_process_scoped=*/true,
/*source_scope=*/kNullStringId);
context_.args_tracker->Flush(); // Flush track args.
@@ -1353,7 +1361,7 @@
StringId name_id = context_.storage->InternString(base::StringView(kName));
constexpr int64_t kSourceId = 235;
- TrackId track = context_.track_tracker->InternLegacyChromeAsyncTrack(
+ TrackId track = context_.track_tracker->LegacyInternLegacyChromeAsyncTrack(
name_id, upid, kSourceId, /*trace_id_is_process_scoped=*/true,
/*source_scope=*/kNullStringId);
context_.args_tracker->Flush(); // Flush track args.
@@ -1806,7 +1814,8 @@
const char* kModuleDebugPath = "debugpath";
UniquePid upid = context_.process_tracker->GetOrCreateProcess(kProcessID);
- TrackId track = context_.track_tracker->InternProcessTrack(upid);
+ TrackId track =
+ context_.track_tracker->InternProcessTrack(tracks::track_event, upid);
StringId level_of_detail_id =
context_.storage->InternString(base::StringView(kLevelOfDetail));
auto snapshot_id = context_.storage->mutable_memory_snapshot_table()
@@ -1816,7 +1825,7 @@
StringId peak_resident_set_size_id =
context_.storage->InternString("chrome.peak_resident_set_kb");
TrackId peak_resident_set_size_counter =
- context_.track_tracker->InternProcessCounterTrack(
+ context_.track_tracker->LegacyInternProcessCounterTrack(
peak_resident_set_size_id, upid);
context_.event_tracker->PushCounter(kTimestamp, kPeakResidentSetSize,
peak_resident_set_size_counter);
@@ -1824,7 +1833,7 @@
StringId private_footprint_bytes_id =
context_.storage->InternString("chrome.private_footprint_kb");
TrackId private_footprint_bytes_counter =
- context_.track_tracker->InternProcessCounterTrack(
+ context_.track_tracker->LegacyInternProcessCounterTrack(
private_footprint_bytes_id, upid);
context_.event_tracker->PushCounter(kTimestamp, kPrivateFootprintBytes,
private_footprint_bytes_counter);
@@ -1927,7 +1936,8 @@
UniquePid os_upid =
context_.process_tracker->GetOrCreateProcess(kOsProcessID);
- TrackId track = context_.track_tracker->InternProcessTrack(os_upid);
+ TrackId track =
+ context_.track_tracker->InternProcessTrack(tracks::track_event, os_upid);
StringId level_of_detail_id =
context_.storage->InternString(base::StringView(kLevelOfDetail));
auto snapshot_id = context_.storage->mutable_memory_snapshot_table()
diff --git a/src/trace_processor/forwarding_trace_parser.cc b/src/trace_processor/forwarding_trace_parser.cc
index b2250fd..2bf7958 100644
--- a/src/trace_processor/forwarding_trace_parser.cc
+++ b/src/trace_processor/forwarding_trace_parser.cc
@@ -18,27 +18,30 @@
#include <memory>
#include <optional>
+#include <utility>
#include "perfetto/base/logging.h"
#include "perfetto/base/status.h"
#include "perfetto/ext/base/status_or.h"
+#include "perfetto/trace_processor/basic_types.h"
#include "src/trace_processor/importers/common/chunked_trace_reader.h"
#include "src/trace_processor/importers/common/process_tracker.h"
+#include "src/trace_processor/importers/common/trace_file_tracker.h"
#include "src/trace_processor/importers/proto/proto_trace_reader.h"
#include "src/trace_processor/sorter/trace_sorter.h"
+#include "src/trace_processor/storage/stats.h"
+#include "src/trace_processor/tables/metadata_tables_py.h"
#include "src/trace_processor/trace_reader_registry.h"
#include "src/trace_processor/types/trace_processor_context.h"
#include "src/trace_processor/util/status_macros.h"
#include "src/trace_processor/util/trace_type.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
namespace {
TraceSorter::SortingMode ConvertSortingMode(SortingMode sorting_mode) {
switch (sorting_mode) {
case SortingMode::kDefaultHeuristics:
- case SortingMode::kForceFlushPeriodWindowedSort:
return TraceSorter::SortingMode::kDefault;
case SortingMode::kForceFullSort:
return TraceSorter::SortingMode::kFullSort;
@@ -64,7 +67,11 @@
case kJsonTraceType:
case kFuchsiaTraceType:
case kZipFile:
+ case kTarTraceType:
case kAndroidLogcatTraceType:
+ case kGeckoTraceType:
+ case kArtMethodTraceType:
+ case kPerfTextTraceType:
return TraceSorter::SortingMode::kFullSort;
case kProtoTraceType:
@@ -81,10 +88,11 @@
} // namespace
-ForwardingTraceParser::ForwardingTraceParser(TraceProcessorContext* context)
- : context_(context) {}
+ForwardingTraceParser::ForwardingTraceParser(TraceProcessorContext* context,
+ tables::TraceFileTable::Id id)
+ : context_(context), file_id_(id) {}
-ForwardingTraceParser::~ForwardingTraceParser() {}
+ForwardingTraceParser::~ForwardingTraceParser() = default;
base::Status ForwardingTraceParser::Init(const TraceBlobView& blob) {
PERFETTO_CHECK(!reader_);
@@ -99,13 +107,9 @@
// The UI's error_dialog.ts uses it to make the dialog more graceful.
return base::ErrStatus("Unknown trace type provided (ERR:fmt)");
}
-
- base::StatusOr<std::unique_ptr<ChunkedTraceReader>> reader_or =
- context_->reader_registry->CreateTraceReader(trace_type_);
- if (!reader_or.ok()) {
- return reader_or.status();
- }
- reader_ = std::move(*reader_or);
+ context_->trace_file_tracker->StartParsing(file_id_, trace_type_);
+ ASSIGN_OR_RETURN(reader_,
+ context_->reader_registry->CreateTraceReader(trace_type_));
PERFETTO_DLOG("%s trace detected", TraceTypeToString(trace_type_));
UpdateSorterForTraceType(trace_type_);
@@ -116,7 +120,6 @@
if (trace_type_ == kProtoTraceType || trace_type_ == kSystraceTraceType) {
context_->process_tracker->SetPidZeroIsUpidZeroIdleProcess();
}
-
return base::OkStatus();
}
@@ -128,7 +131,26 @@
}
if (!context_->sorter) {
- context_->sorter.reset(new TraceSorter(context_, *minimum_sorting_mode));
+ TraceSorter::EventHandling event_handling;
+ switch (context_->config.parsing_mode) {
+ case ParsingMode::kDefault:
+ event_handling = TraceSorter::EventHandling::kSortAndPush;
+ break;
+ case ParsingMode::kTokenizeOnly:
+ event_handling = TraceSorter::EventHandling::kDrop;
+ break;
+ case ParsingMode::kTokenizeAndSort:
+ event_handling = TraceSorter::EventHandling::kSortAndDrop;
+ break;
+ }
+ if (context_->config.enable_dev_features) {
+ auto it = context_->config.dev_flags.find("drop-after-sort");
+ if (it != context_->config.dev_flags.end() && it->second == "true") {
+ event_handling = TraceSorter::EventHandling::kSortAndDrop;
+ }
+ }
+ context_->sorter = std::make_shared<TraceSorter>(
+ context_, *minimum_sorting_mode, event_handling);
}
switch (context_->sorter->sorting_mode()) {
@@ -147,12 +169,18 @@
if (!reader_) {
RETURN_IF_ERROR(Init(blob));
}
+ trace_size_ += blob.size();
return reader_->Parse(std::move(blob));
}
base::Status ForwardingTraceParser::NotifyEndOfFile() {
- return reader_ ? reader_->NotifyEndOfFile() : base::OkStatus();
+ if (reader_) {
+ RETURN_IF_ERROR(reader_->NotifyEndOfFile());
+ }
+ if (trace_type_ != kUnknownTraceType) {
+ context_->trace_file_tracker->DoneParsing(file_id_, trace_size_);
+ }
+ return base::OkStatus();
}
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/forwarding_trace_parser.h b/src/trace_processor/forwarding_trace_parser.h
index fa90279..1f4858a 100644
--- a/src/trace_processor/forwarding_trace_parser.h
+++ b/src/trace_processor/forwarding_trace_parser.h
@@ -22,6 +22,7 @@
#include "perfetto/base/status.h"
#include "perfetto/trace_processor/trace_blob_view.h"
#include "src/trace_processor/importers/common/chunked_trace_reader.h"
+#include "src/trace_processor/tables/metadata_tables_py.h"
#include "src/trace_processor/util/trace_type.h"
namespace perfetto::trace_processor {
@@ -30,7 +31,8 @@
class ForwardingTraceParser : public ChunkedTraceReader {
public:
- explicit ForwardingTraceParser(TraceProcessorContext*);
+ explicit ForwardingTraceParser(TraceProcessorContext*,
+ tables::TraceFileTable::Id);
~ForwardingTraceParser() override;
// ChunkedTraceReader implementation
@@ -43,6 +45,8 @@
base::Status Init(const TraceBlobView&);
void UpdateSorterForTraceType(TraceType trace_type);
TraceProcessorContext* const context_;
+ tables::TraceFileTable::Id file_id_;
+ size_t trace_size_ = 0;
std::unique_ptr<ChunkedTraceReader> reader_;
TraceType trace_type_ = kUnknownTraceType;
};
diff --git a/src/trace_processor/importers/android_bugreport/android_bugreport_reader.cc b/src/trace_processor/importers/android_bugreport/android_bugreport_reader.cc
index abded5f..07c8434 100644
--- a/src/trace_processor/importers/android_bugreport/android_bugreport_reader.cc
+++ b/src/trace_processor/importers/android_bugreport/android_bugreport_reader.cc
@@ -23,9 +23,9 @@
#include <string>
#include <vector>
-#include "perfetto/base/logging.h"
#include "perfetto/base/status.h"
#include "perfetto/ext/base/string_utils.h"
+#include "perfetto/ext/base/string_view.h"
#include "protos/perfetto/common/builtin_clock.pbzero.h"
#include "src/trace_processor/importers/android_bugreport/android_dumpstate_reader.h"
#include "src/trace_processor/importers/android_bugreport/android_log_reader.h"
@@ -38,17 +38,23 @@
namespace perfetto::trace_processor {
namespace {
-const util::ZipFile* FindBugReportFile(
- const std::vector<util::ZipFile>& zip_file_entries) {
- for (const auto& zf : zip_file_entries) {
- if (base::StartsWith(zf.name(), "bugreport-") &&
- base::EndsWith(zf.name(), ".txt")) {
- return &zf;
- }
- }
- return nullptr;
+
+using ZipFileVector = std::vector<util::ZipFile>;
+
+bool IsBugReportFile(const util::ZipFile& zip) {
+ return base::StartsWith(zip.name(), "bugreport-") &&
+ base::EndsWith(zip.name(), ".txt");
}
+bool IsLogFile(const util::ZipFile& file) {
+ return base::StartsWith(file.name(), "FS/data/misc/logd/logcat") &&
+ !base::EndsWith(file.name(), "logcat.id");
+}
+
+// Extracts the year field from the bugreport-xxx.txt file name.
+// This is because logcat events have only the month and day.
+// This is obviously bugged for cases of bugreports collected across new year
+// but we'll live with that.
std::optional<int32_t> ExtractYearFromBugReportFilename(
const std::string& filename) {
// Typical name: "bugreport-product-TP1A.220623.001-2022-06-24-16-24-37.txt".
@@ -57,34 +63,76 @@
return base::StringToInt32(year_str);
}
+struct FindBugReportFileResult {
+ size_t file_index;
+ int32_t year;
+};
+
+std::optional<FindBugReportFileResult> FindBugReportFile(
+ const ZipFileVector& files) {
+ for (size_t i = 0; i < files.size(); ++i) {
+ if (!IsBugReportFile(files[i])) {
+ continue;
+ }
+ std::optional<int32_t> year =
+ ExtractYearFromBugReportFilename(files[i].name());
+ if (!year.has_value()) {
+ continue;
+ }
+
+ return FindBugReportFileResult{i, *year};
+ }
+
+ return std::nullopt;
+}
+
} // namespace
// static
bool AndroidBugreportReader::IsAndroidBugReport(
- const std::vector<util::ZipFile>& zip_file_entries) {
- if (const util::ZipFile* file = FindBugReportFile(zip_file_entries);
- file != nullptr) {
- return ExtractYearFromBugReportFilename(file->name()).has_value();
- }
-
- return false;
+ const std::vector<util::ZipFile>& files) {
+ return FindBugReportFile(files).has_value();
}
// static
-util::Status AndroidBugreportReader::Parse(
- TraceProcessorContext* context,
- std::vector<util::ZipFile> zip_file_entries) {
- if (!IsAndroidBugReport(zip_file_entries)) {
+util::Status AndroidBugreportReader::Parse(TraceProcessorContext* context,
+ std::vector<util::ZipFile> files) {
+ auto res = FindBugReportFile(files);
+ if (!res.has_value()) {
return base::ErrStatus("Not a bug report");
}
- return AndroidBugreportReader(context, std::move(zip_file_entries))
+
+ // Move the file to the end move it out of the list and pop the back.
+ std::swap(files[res->file_index], files.back());
+ auto id = context->trace_file_tracker->AddFile(files.back().name());
+ BugReportFile bug_report{id, res->year, std::move(files.back())};
+ files.pop_back();
+
+ std::set<LogFile> ordered_log_files;
+ for (size_t i = 0; i < files.size(); ++i) {
+ id = context->trace_file_tracker->AddFile(files[i].name());
+ // Set size in case we end up not parsing this file.
+ context->trace_file_tracker->SetSize(id, files[i].compressed_size());
+ if (!IsLogFile(files[i])) {
+ continue;
+ }
+
+ int64_t timestamp = files[i].GetDatetime();
+ ordered_log_files.insert(LogFile{id, timestamp, std::move(files[i])});
+ }
+
+ return AndroidBugreportReader(context, std::move(bug_report),
+ std::move(ordered_log_files))
.ParseImpl();
}
AndroidBugreportReader::AndroidBugreportReader(
TraceProcessorContext* context,
- std::vector<util::ZipFile> zip_file_entries)
- : context_(context), zip_file_entries_(std::move(zip_file_entries)) {}
+ BugReportFile bug_report,
+ std::set<LogFile> ordered_log_files)
+ : context_(context),
+ bug_report_(std::move(bug_report)),
+ ordered_log_files_(std::move(ordered_log_files)) {}
AndroidBugreportReader::~AndroidBugreportReader() = default;
@@ -94,10 +142,6 @@
// 1970), but that is the state of affairs.
context_->clock_tracker->SetTraceTimeClock(
protos::pbzero::BUILTIN_CLOCK_REALTIME);
- if (!DetectYearAndBrFilename()) {
- context_->storage->IncrementStats(stats::android_br_parse_errors);
- return base::ErrStatus("Zip file does not contain bugreport file.");
- }
ASSIGN_OR_RETURN(std::vector<TimestampedAndroidLogEvent> logcat_events,
ParsePersistentLogcat());
@@ -106,74 +150,41 @@
base::Status AndroidBugreportReader::ParseDumpstateTxt(
std::vector<TimestampedAndroidLogEvent> logcat_events) {
- PERFETTO_CHECK(dumpstate_file_);
- ScopedActiveTraceFile trace_file = context_->trace_file_tracker->StartNewFile(
- dumpstate_file_->name(), kAndroidDumpstateTraceType,
- dumpstate_file_->uncompressed_size());
- AndroidDumpstateReader reader(context_, br_year_, std::move(logcat_events));
- return dumpstate_file_->DecompressLines(
+ context_->trace_file_tracker->StartParsing(bug_report_.id,
+ kAndroidDumpstateTraceType);
+ AndroidDumpstateReader reader(context_, bug_report_.year,
+ std::move(logcat_events));
+ base::Status status = bug_report_.file.DecompressLines(
[&](const std::vector<base::StringView>& lines) {
for (const base::StringView& line : lines) {
reader.ParseLine(line);
}
});
+ context_->trace_file_tracker->DoneParsing(
+ bug_report_.id, bug_report_.file.uncompressed_size());
+ return status;
}
base::StatusOr<std::vector<TimestampedAndroidLogEvent>>
AndroidBugreportReader::ParsePersistentLogcat() {
- BufferingAndroidLogReader log_reader(context_, br_year_);
-
- // Sort files to ease the job of the subsequent line-based sort. Unfortunately
- // lines within each file are not 100% timestamp-ordered, due to things like
- // kernel messages where log time != event time.
- std::vector<std::pair<uint64_t, const util::ZipFile*>> log_files;
- for (const util::ZipFile& zf : zip_file_entries_) {
- if (base::StartsWith(zf.name(), "FS/data/misc/logd/logcat") &&
- !base::EndsWith(zf.name(), "logcat.id")) {
- log_files.push_back(std::make_pair(zf.GetDatetime(), &zf));
- }
- }
-
- std::sort(log_files.begin(), log_files.end());
+ BufferingAndroidLogReader log_reader(context_, bug_report_.year);
// Push all events into the AndroidLogParser. It will take care of string
// interning into the pool. Appends entries into `log_events`.
- for (const auto& log_file : log_files) {
- ScopedActiveTraceFile trace_file =
- context_->trace_file_tracker->StartNewFile(
- log_file.second->name(), kAndroidLogcatTraceType,
- log_file.second->uncompressed_size());
- RETURN_IF_ERROR(log_file.second->DecompressLines(
+ for (const auto& log_file : ordered_log_files_) {
+ context_->trace_file_tracker->StartParsing(log_file.id,
+ kAndroidLogcatTraceType);
+ RETURN_IF_ERROR(log_file.file.DecompressLines(
[&](const std::vector<base::StringView>& lines) {
for (const auto& line : lines) {
log_reader.ParseLine(line);
}
}));
+ context_->trace_file_tracker->DoneParsing(
+ log_file.id, log_file.file.uncompressed_size());
}
return std::move(log_reader).ConsumeBufferedEvents();
}
-// Populates the `year_` field from the bugreport-xxx.txt file name.
-// This is because logcat events have only the month and day.
-// This is obviously bugged for cases of bugreports collected across new year
-// but we'll live with that.
-bool AndroidBugreportReader::DetectYearAndBrFilename() {
- const util::ZipFile* br_file = FindBugReportFile(zip_file_entries_);
- if (!br_file) {
- PERFETTO_ELOG("Could not find bugreport-*.txt in the zip file");
- return false;
- }
-
- std::optional<int32_t> year =
- ExtractYearFromBugReportFilename(br_file->name());
- if (!year.has_value()) {
- PERFETTO_ELOG("Could not parse the year from %s", br_file->name().c_str());
- return false;
- }
- br_year_ = *year;
- dumpstate_file_ = br_file;
- return true;
-}
-
} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/android_bugreport/android_bugreport_reader.h b/src/trace_processor/importers/android_bugreport/android_bugreport_reader.h
index ddcec9a..b36c45c 100644
--- a/src/trace_processor/importers/android_bugreport/android_bugreport_reader.h
+++ b/src/trace_processor/importers/android_bugreport/android_bugreport_reader.h
@@ -19,12 +19,14 @@
#include <cstddef>
#include <cstdint>
+#include <set>
#include <vector>
#include "perfetto/base/status.h"
#include "perfetto/ext/base/status_or.h"
#include "perfetto/trace_processor/status.h"
#include "src/trace_processor/importers/android_bugreport/android_log_reader.h"
+#include "src/trace_processor/tables/metadata_tables_py.h"
#include "src/trace_processor/util/zip_reader.h"
namespace perfetto ::trace_processor {
@@ -44,22 +46,41 @@
std::vector<util::ZipFile> zip_file_entries);
private:
+ struct BugReportFile {
+ tables::TraceFileTable::Id id;
+ int32_t year;
+ util::ZipFile file;
+ };
+ struct LogFile {
+ tables::TraceFileTable::Id id;
+ int64_t timestamp;
+ util::ZipFile file;
+ // Sort files to ease the job of the line-based sort. Unfortunately
+ // lines within each file are not 100% timestamp-ordered, due to things like
+ // kernel messages where log time != event time.
+ bool operator<(const LogFile& other) const {
+ return timestamp < other.timestamp;
+ }
+ };
+
+ static std::optional<BugReportFile> ExtractBugReportFile(
+ std::vector<util::ZipFile>& vector);
+
AndroidBugreportReader(TraceProcessorContext* context,
- std::vector<util::ZipFile> zip_file_entries);
+ BugReportFile bug_report,
+ std::set<LogFile> ordered_log_files);
~AndroidBugreportReader();
util::Status ParseImpl();
- bool DetectYearAndBrFilename();
base::StatusOr<std::vector<TimestampedAndroidLogEvent>>
ParsePersistentLogcat();
base::Status ParseDumpstateTxt(std::vector<TimestampedAndroidLogEvent>);
TraceProcessorContext* const context_;
- std::vector<util::ZipFile> zip_file_entries_;
- int32_t br_year_ = 0; // The year when the bugreport has been taken.
- const util::ZipFile* dumpstate_file_ =
- nullptr; // The bugreport-xxx-2022-08-04....txt file
- std::string build_fpr_;
+ BugReportFile bug_report_;
+ // Log files conveniently sorted by their file timestamp (see operator< in
+ // LogFile)
+ std::set<LogFile> ordered_log_files_;
};
} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/android_bugreport/android_log_event.cc b/src/trace_processor/importers/android_bugreport/android_log_event.cc
index fe1ce77..d9c7a5d 100644
--- a/src/trace_processor/importers/android_bugreport/android_log_event.cc
+++ b/src/trace_processor/importers/android_bugreport/android_log_event.cc
@@ -17,6 +17,7 @@
#include "src/trace_processor/importers/android_bugreport/android_log_event.h"
#include <algorithm>
+#include <cstddef>
#include <cstdint>
#include <optional>
#include <vector>
@@ -35,8 +36,7 @@
for (; ptr != end; ++ptr) {
if (*ptr == '\n') {
- lines.push_back(
- base::StringView(line_start, static_cast<size_t>(ptr - line_start)));
+ lines.emplace_back(line_start, static_cast<size_t>(ptr - line_start));
line_start = ptr + 1;
}
}
diff --git a/src/trace_processor/importers/zip/BUILD.gn b/src/trace_processor/importers/archive/BUILD.gn
similarity index 66%
rename from src/trace_processor/importers/zip/BUILD.gn
rename to src/trace_processor/importers/archive/BUILD.gn
index 0262e02..4f092ad 100644
--- a/src/trace_processor/importers/zip/BUILD.gn
+++ b/src/trace_processor/importers/archive/BUILD.gn
@@ -1,4 +1,4 @@
-# Copyright (C) 2022 The Android Open Source Project
+# 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.
@@ -12,18 +12,31 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-source_set("full") {
+source_set("archive") {
sources = [
+ "archive_entry.cc",
+ "archive_entry.h",
+ "gzip_trace_parser.cc",
+ "gzip_trace_parser.h",
+ "tar_trace_reader.cc",
+ "tar_trace_reader.h",
"zip_trace_reader.cc",
"zip_trace_reader.h",
]
deps = [
+ "../..:storage_minimal",
"../../../../gn:default_deps",
+ "../../../../include/perfetto/base:base",
"../../../../include/perfetto/ext/base:base",
+ "../../../base",
"../../../trace_processor:storage_minimal",
+ "../../storage",
+ "../../tables:tables_python",
"../../types",
+ "../../util",
+ "../../util:gzip",
+ "../../util:trace_blob_view_reader",
"../../util:trace_type",
- "../../util:util",
"../../util:zip_reader",
"../android_bugreport",
"../common",
diff --git a/src/trace_processor/importers/archive/archive_entry.cc b/src/trace_processor/importers/archive/archive_entry.cc
new file mode 100644
index 0000000..1d187df
--- /dev/null
+++ b/src/trace_processor/importers/archive/archive_entry.cc
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+
+#include "src/trace_processor/importers/archive/archive_entry.h"
+
+#include <tuple>
+
+namespace perfetto::trace_processor {
+
+bool ArchiveEntry::operator<(const ArchiveEntry& rhs) const {
+ // Traces with symbols should be the last ones to be read.
+ // TODO(carlscab): Proto traces with just ModuleSymbols packets should be an
+ // exception. We actually need those are the very end (once whe have all the
+ // Frames). Alternatively we could build a map address -> symbol during
+ // tokenization and use this during parsing to resolve symbols.
+ if (trace_type == kSymbolsTraceType) {
+ return false;
+ }
+ if (rhs.trace_type == kSymbolsTraceType) {
+ return true;
+ }
+
+ // Proto traces should always parsed first as they might contains clock sync
+ // data needed to correctly parse other traces.
+ if (rhs.trace_type == TraceType::kProtoTraceType) {
+ return false;
+ }
+ if (trace_type == TraceType::kProtoTraceType) {
+ return true;
+ }
+
+ if (rhs.trace_type == TraceType::kGzipTraceType) {
+ return false;
+ }
+ if (trace_type == TraceType::kGzipTraceType) {
+ return true;
+ }
+
+ return std::tie(name, index) < std::tie(rhs.name, rhs.index);
+}
+
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/archive/archive_entry.h b/src/trace_processor/importers/archive/archive_entry.h
new file mode 100644
index 0000000..ae13cf7
--- /dev/null
+++ b/src/trace_processor/importers/archive/archive_entry.h
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_ARCHIVE_ARCHIVE_ENTRY_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_ARCHIVE_ARCHIVE_ENTRY_H_
+
+#include <string>
+
+#include "src/trace_processor/util/trace_type.h"
+
+namespace perfetto::trace_processor {
+
+// Helper class to determine a proper tokenization. This class can be used as
+// a key of a std::map to automatically sort files before sending them in proper
+// order for tokenization.
+struct ArchiveEntry {
+ // File name. Used to break ties.
+ std::string name;
+ // Position. Used to break ties.
+ size_t index;
+ // Trace type. This is the main attribute traces are ordered by. Proto
+ // traces are always parsed first as they might contains clock sync
+ // data needed to correctly parse other traces.
+ TraceType trace_type;
+ // Comparator used to determine the order in which files in the ZIP will be
+ // read.
+ bool operator<(const ArchiveEntry& rhs) const;
+};
+} // namespace perfetto::trace_processor
+
+#endif // SRC_TRACE_PROCESSOR_IMPORTERS_ARCHIVE_ARCHIVE_ENTRY_H_
diff --git a/src/trace_processor/importers/gzip/gzip_trace_parser.cc b/src/trace_processor/importers/archive/gzip_trace_parser.cc
similarity index 93%
rename from src/trace_processor/importers/gzip/gzip_trace_parser.cc
rename to src/trace_processor/importers/archive/gzip_trace_parser.cc
index d133b2c..bc4074a 100644
--- a/src/trace_processor/importers/gzip/gzip_trace_parser.cc
+++ b/src/trace_processor/importers/archive/gzip_trace_parser.cc
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include "src/trace_processor/importers/gzip/gzip_trace_parser.h"
+#include "src/trace_processor/importers/archive/gzip_trace_parser.h"
#include <cstdint>
#include <cstring>
@@ -30,6 +30,8 @@
#include "perfetto/trace_processor/trace_blob_view.h"
#include "src/trace_processor/forwarding_trace_parser.h"
#include "src/trace_processor/importers/common/chunked_trace_reader.h"
+#include "src/trace_processor/importers/common/trace_file_tracker.h"
+#include "src/trace_processor/types/trace_processor_context.h"
#include "src/trace_processor/util/gzip_utils.h"
#include "src/trace_processor/util/status_macros.h"
@@ -59,7 +61,8 @@
if (!inner_) {
PERFETTO_CHECK(context_);
- inner_.reset(new ForwardingTraceParser(context_));
+ inner_.reset(new ForwardingTraceParser(
+ context_, context_->trace_file_tracker->AddFile("")));
}
if (!first_chunk_parsed_) {
diff --git a/src/trace_processor/importers/gzip/gzip_trace_parser.h b/src/trace_processor/importers/archive/gzip_trace_parser.h
similarity index 88%
rename from src/trace_processor/importers/gzip/gzip_trace_parser.h
rename to src/trace_processor/importers/archive/gzip_trace_parser.h
index 4a565e6..17fdc4c 100644
--- a/src/trace_processor/importers/gzip/gzip_trace_parser.h
+++ b/src/trace_processor/importers/archive/gzip_trace_parser.h
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_GZIP_GZIP_TRACE_PARSER_H_
-#define SRC_TRACE_PROCESSOR_IMPORTERS_GZIP_GZIP_TRACE_PARSER_H_
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_ARCHIVE_GZIP_TRACE_PARSER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_ARCHIVE_GZIP_TRACE_PARSER_H_
#include <cstddef>
#include <cstdint>
@@ -55,4 +55,4 @@
} // namespace perfetto::trace_processor
-#endif // SRC_TRACE_PROCESSOR_IMPORTERS_GZIP_GZIP_TRACE_PARSER_H_
+#endif // SRC_TRACE_PROCESSOR_IMPORTERS_ARCHIVE_GZIP_TRACE_PARSER_H_
diff --git a/src/trace_processor/importers/archive/tar_trace_reader.cc b/src/trace_processor/importers/archive/tar_trace_reader.cc
new file mode 100644
index 0000000..d0cf11f
--- /dev/null
+++ b/src/trace_processor/importers/archive/tar_trace_reader.cc
@@ -0,0 +1,285 @@
+/*
+ * 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.
+ */
+
+#include "src/trace_processor/importers/archive/tar_trace_reader.h"
+
+#include <algorithm>
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
+#include <memory>
+#include <optional>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "perfetto/base/logging.h"
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/status_or.h"
+#include "perfetto/ext/base/string_view.h"
+#include "perfetto/trace_processor/trace_blob_view.h"
+#include "src/trace_processor/forwarding_trace_parser.h"
+#include "src/trace_processor/importers/archive/archive_entry.h"
+#include "src/trace_processor/importers/common/trace_file_tracker.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/util/status_macros.h"
+#include "src/trace_processor/util/trace_type.h"
+
+namespace perfetto::trace_processor {
+namespace {
+
+constexpr char kUstarMagic[] = {'u', 's', 't', 'a', 'r', '\0'};
+constexpr char kGnuMagic[] = {'u', 's', 't', 'a', 'r', ' ', ' ', '\0'};
+
+constexpr char TYPE_FLAG_REGULAR = '0';
+constexpr char TYPE_FLAG_AREGULAR = '\0';
+constexpr char TYPE_FLAG_GNU_LONG_NAME = 'L';
+
+template <size_t Size>
+std::optional<uint64_t> ExtractUint64(const char (&ptr)[Size]) {
+ static_assert(Size <= 64 / 3);
+ if (*ptr == 0) {
+ return std::nullopt;
+ }
+ uint64_t value = 0;
+ for (size_t i = 0; i < Size && ptr[i] != 0; ++i) {
+ if (ptr[i] > '7' || ptr[i] < '0') {
+ return std::nullopt;
+ }
+ value <<= 3;
+ value += static_cast<uint64_t>(ptr[i] - '0');
+ }
+ return value;
+}
+
+enum class TarType { kUnknown, kUstar, kGnu };
+
+struct alignas(1) Header {
+ char name[100];
+ char mode[8];
+ char uid[8];
+ char gid[8];
+ char size[12];
+ char mtime[12];
+ char checksum[8];
+ char type_flag[1];
+ char link_name[100];
+ union {
+ struct UstarMagic {
+ char magic[6];
+ char version[2];
+ } ustar;
+ char gnu[8];
+ } magic;
+ char user_name[32];
+ char group_name[32];
+ char dev_major[8];
+ char dev_minor[8];
+ char prefix[155];
+ char padding[12];
+
+ TarType GetTarFileType() const {
+ if (memcmp(magic.gnu, kGnuMagic, sizeof(kGnuMagic)) == 0) {
+ return TarType::kGnu;
+ }
+ if (memcmp(magic.ustar.magic, kUstarMagic, sizeof(kUstarMagic)) == 0) {
+ return TarType::kUstar;
+ }
+ return TarType::kUnknown;
+ }
+};
+
+constexpr size_t kHeaderSize = 512;
+static_assert(sizeof(Header) == kHeaderSize);
+
+bool IsAllZeros(const TraceBlobView& data) {
+ const uint8_t* start = data.data();
+ const uint8_t* end = data.data() + data.size();
+ return std::find_if(start, end, [](uint8_t v) { return v != 0; }) == end;
+}
+
+template <size_t Size>
+std::string ExtractString(const char (&start)[Size]) {
+ const char* end = start + Size;
+ end = std::find(start, end, 0);
+ return std::string(start, end);
+}
+
+} // namespace
+
+TarTraceReader::TarTraceReader(TraceProcessorContext* context)
+ : context_(context) {}
+
+TarTraceReader::~TarTraceReader() = default;
+
+util::Status TarTraceReader::Parse(TraceBlobView blob) {
+ ParseResult result = ParseResult::kOk;
+ buffer_.PushBack(std::move(blob));
+ while (!buffer_.empty() && result == ParseResult::kOk) {
+ switch (state_) {
+ case State::kMetadata:
+ case State::kZeroMetadata: {
+ ASSIGN_OR_RETURN(result, ParseMetadata());
+ break;
+ }
+ case State::kContent: {
+ ASSIGN_OR_RETURN(result, ParseContent());
+ break;
+ }
+ case State::kDone:
+ // We are done, ignore any more data
+ buffer_.PopFrontUntil(buffer_.end_offset());
+ }
+ }
+ return base::OkStatus();
+}
+
+base::Status TarTraceReader::NotifyEndOfFile() {
+ if (state_ != State::kDone) {
+ return base::ErrStatus("Premature end of TAR file");
+ }
+
+ for (auto& file : ordered_files_) {
+ auto chunk_reader =
+ std::make_unique<ForwardingTraceParser>(context_, file.second.id);
+ auto& parser = *chunk_reader;
+ context_->chunk_readers.push_back(std::move(chunk_reader));
+
+ for (auto& data : file.second.data) {
+ RETURN_IF_ERROR(parser.Parse(std::move(data)));
+ }
+ RETURN_IF_ERROR(parser.NotifyEndOfFile());
+ // Make sure the ForwardingTraceParser determined the same trace type as we
+ // did.
+ PERFETTO_CHECK(parser.trace_type() == file.first.trace_type);
+ }
+
+ return base::OkStatus();
+}
+
+base::StatusOr<TarTraceReader::ParseResult> TarTraceReader::ParseMetadata() {
+ PERFETTO_CHECK(!metadata_.has_value());
+ auto blob = buffer_.SliceOff(buffer_.start_offset(), kHeaderSize);
+ if (!blob) {
+ return ParseResult::kNeedsMoreData;
+ }
+ buffer_.PopFrontBytes(kHeaderSize);
+ const Header& header = *reinterpret_cast<const Header*>(blob->data());
+
+ TarType type = header.GetTarFileType();
+
+ if (type == TarType::kUnknown) {
+ if (!IsAllZeros(*blob)) {
+ return base::ErrStatus("Invalid magic value");
+ }
+ // EOF is signaled by two consecutive zero headers.
+ if (state_ == State::kMetadata) {
+ // Fist time we see all zeros. NExt parser loop will enter ParseMetadata
+ // again and decide whether it is the real end or maybe a ral header
+ // comes.
+ state_ = State::kZeroMetadata;
+ } else {
+ // Previous header was zeros, thus we are done.
+ PERFETTO_CHECK(state_ == State::kZeroMetadata);
+ state_ = State::kDone;
+ }
+ return ParseResult::kOk;
+ }
+
+ if (type == TarType::kUstar && (header.magic.ustar.version[0] != '0' ||
+ header.magic.ustar.version[1] != '0')) {
+ return base::ErrStatus("Invalid version: %c%c",
+ header.magic.ustar.version[0],
+ header.magic.ustar.version[1]);
+ }
+
+ std::optional<uint64_t> size = ExtractUint64(header.size);
+ if (!size.has_value()) {
+ return base::ErrStatus("Failed to parse octal size field.");
+ }
+
+ metadata_.emplace();
+ metadata_->size = *size;
+ metadata_->type_flag = *header.type_flag;
+
+ if (long_name_) {
+ metadata_->name = std::move(*long_name_);
+ long_name_.reset();
+ } else {
+ metadata_->name =
+ ExtractString(header.prefix) + "/" + ExtractString(header.name);
+ }
+
+ switch (metadata_->type_flag) {
+ case TYPE_FLAG_REGULAR:
+ case TYPE_FLAG_AREGULAR:
+ case TYPE_FLAG_GNU_LONG_NAME:
+ state_ = State::kContent;
+ break;
+
+ default:
+ if (metadata_->size != 0) {
+ return base::ErrStatus("Unsupported file type: 0x%02x",
+ metadata_->type_flag);
+ }
+ state_ = State::kMetadata;
+ break;
+ }
+
+ return ParseResult::kOk;
+}
+
+base::StatusOr<TarTraceReader::ParseResult> TarTraceReader::ParseContent() {
+ PERFETTO_CHECK(metadata_.has_value());
+
+ size_t data_and_padding_size = base::AlignUp(metadata_->size, kHeaderSize);
+ if (buffer_.avail() < data_and_padding_size) {
+ return ParseResult::kNeedsMoreData;
+ }
+
+ if (metadata_->type_flag == TYPE_FLAG_GNU_LONG_NAME) {
+ TraceBlobView data =
+ *buffer_.SliceOff(buffer_.start_offset(), metadata_->size);
+ long_name_ = std::string(reinterpret_cast<const char*>(data.data()),
+ metadata_->size);
+ } else {
+ AddFile(*metadata_,
+ *buffer_.SliceOff(
+ buffer_.start_offset(),
+ std::min(static_cast<uint64_t>(512), metadata_->size)),
+ buffer_.MultiSliceOff(buffer_.start_offset(), metadata_->size));
+ }
+
+ buffer_.PopFrontBytes(data_and_padding_size);
+
+ metadata_.reset();
+ state_ = State::kMetadata;
+ return ParseResult::kOk;
+}
+
+void TarTraceReader::AddFile(const Metadata& metadata,
+ TraceBlobView header,
+ std::vector<TraceBlobView> data) {
+ auto file_id = context_->trace_file_tracker->AddFile(metadata.name);
+ context_->trace_file_tracker->SetSize(file_id, metadata.size);
+ ordered_files_.emplace(
+ ArchiveEntry{metadata.name, ordered_files_.size(),
+ GuessTraceType(header.data(), header.size())},
+ File{file_id, std::move(data)});
+}
+
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/archive/tar_trace_reader.h b/src/trace_processor/importers/archive/tar_trace_reader.h
new file mode 100644
index 0000000..6760774
--- /dev/null
+++ b/src/trace_processor/importers/archive/tar_trace_reader.h
@@ -0,0 +1,82 @@
+/*
+ * 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.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_ARCHIVE_TAR_TRACE_READER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_ARCHIVE_TAR_TRACE_READER_H_
+
+#include <cstddef>
+#include <cstdint>
+#include <map>
+
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/status_or.h"
+#include "perfetto/trace_processor/trace_blob_view.h"
+#include "src/trace_processor/importers/archive/archive_entry.h"
+#include "src/trace_processor/importers/common/chunked_trace_reader.h"
+#include "src/trace_processor/tables/metadata_tables_py.h"
+#include "src/trace_processor/util/trace_blob_view_reader.h"
+
+namespace perfetto::trace_processor {
+
+class TraceProcessorContext;
+
+class TarTraceReader : public ChunkedTraceReader {
+ public:
+ explicit TarTraceReader(TraceProcessorContext*);
+ ~TarTraceReader() override;
+
+ // ChunkedTraceReader implementation
+ base::Status Parse(TraceBlobView) override;
+ base::Status NotifyEndOfFile() override;
+
+ private:
+ struct Metadata {
+ std::string name;
+ uint64_t size;
+ char type_flag;
+ };
+ enum class ParseResult {
+ kOk,
+ kNeedsMoreData,
+ };
+
+ struct File {
+ tables::TraceFileTable::Id id;
+ std::vector<TraceBlobView> data;
+ };
+
+ enum class State { kMetadata, kContent, kZeroMetadata, kDone };
+
+ base::StatusOr<ParseResult> ParseMetadata();
+ base::StatusOr<ParseResult> ParseContent();
+ base::StatusOr<ParseResult> ParseLongName();
+ base::StatusOr<ParseResult> ParsePadding();
+
+ void AddFile(const Metadata& metadata,
+ TraceBlobView header,
+ std::vector<TraceBlobView> data);
+
+ TraceProcessorContext* const context_;
+ State state_{State::kMetadata};
+ util::TraceBlobViewReader buffer_;
+ std::optional<Metadata> metadata_;
+ std::optional<std::string> long_name_;
+ std::map<ArchiveEntry, File> ordered_files_;
+};
+
+} // namespace perfetto::trace_processor
+
+#endif // SRC_TRACE_PROCESSOR_IMPORTERS_ARCHIVE_TAR_TRACE_READER_H_
diff --git a/src/trace_processor/importers/archive/zip_trace_reader.cc b/src/trace_processor/importers/archive/zip_trace_reader.cc
new file mode 100644
index 0000000..6077d00
--- /dev/null
+++ b/src/trace_processor/importers/archive/zip_trace_reader.cc
@@ -0,0 +1,95 @@
+/*
+ * 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.
+ */
+
+#include "src/trace_processor/importers/archive/zip_trace_reader.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <cstring>
+#include <memory>
+#include <string>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include "perfetto/base/logging.h"
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/status_or.h"
+#include "perfetto/ext/base/string_view.h"
+#include "perfetto/trace_processor/trace_blob.h"
+#include "perfetto/trace_processor/trace_blob_view.h"
+#include "src/trace_processor/forwarding_trace_parser.h"
+#include "src/trace_processor/importers/android_bugreport/android_bugreport_reader.h"
+#include "src/trace_processor/importers/archive/archive_entry.h"
+#include "src/trace_processor/importers/common/trace_file_tracker.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/util/status_macros.h"
+#include "src/trace_processor/util/trace_type.h"
+#include "src/trace_processor/util/zip_reader.h"
+
+namespace perfetto::trace_processor {
+
+ZipTraceReader::ZipTraceReader(TraceProcessorContext* context)
+ : context_(context) {}
+ZipTraceReader::~ZipTraceReader() = default;
+
+base::Status ZipTraceReader::Parse(TraceBlobView blob) {
+ return zip_reader_.Parse(std::move(blob));
+}
+
+base::Status ZipTraceReader::NotifyEndOfFile() {
+ std::vector<util::ZipFile> files = zip_reader_.TakeFiles();
+
+ // Android bug reports are ZIP files and its files do not get handled
+ // separately.
+ if (AndroidBugreportReader::IsAndroidBugReport(files)) {
+ return AndroidBugreportReader::Parse(context_, std::move(files));
+ }
+
+ // TODO(carlscab): There is a lot of unnecessary copying going on here.
+ // ZipTraceReader can directly parse the ZIP file and given that we know the
+ // decompressed size we could directly decompress into TraceBlob chunks and
+ // send them to the tokenizer.
+ std::vector<uint8_t> buffer;
+ std::map<ArchiveEntry, File> ordered_files;
+ for (size_t i = 0; i < files.size(); ++i) {
+ util::ZipFile& zip_file = files[i];
+ auto id = context_->trace_file_tracker->AddFile(zip_file.name());
+ context_->trace_file_tracker->SetSize(id, zip_file.compressed_size());
+ RETURN_IF_ERROR(files[i].Decompress(&buffer));
+ TraceBlobView data(TraceBlob::CopyFrom(buffer.data(), buffer.size()));
+ ArchiveEntry entry{zip_file.name(), i,
+ GuessTraceType(data.data(), data.size())};
+ ordered_files.emplace(entry, File{id, std::move(data)});
+ }
+
+ for (auto& file : ordered_files) {
+ auto chunk_reader =
+ std::make_unique<ForwardingTraceParser>(context_, file.second.id);
+ auto& parser = *chunk_reader;
+ context_->chunk_readers.push_back(std::move(chunk_reader));
+
+ RETURN_IF_ERROR(parser.Parse(std::move(file.second.data)));
+ RETURN_IF_ERROR(parser.NotifyEndOfFile());
+ // Make sure the ForwardingTraceParser determined the same trace type as we
+ // did.
+ PERFETTO_CHECK(parser.trace_type() == file.first.trace_type);
+ }
+
+ return base::OkStatus();
+}
+
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/archive/zip_trace_reader.h b/src/trace_processor/importers/archive/zip_trace_reader.h
new file mode 100644
index 0000000..d36fd5c
--- /dev/null
+++ b/src/trace_processor/importers/archive/zip_trace_reader.h
@@ -0,0 +1,57 @@
+/*
+ * 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.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_ARCHIVE_ZIP_TRACE_READER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_ARCHIVE_ZIP_TRACE_READER_H_
+
+#include <cstddef>
+#include <map>
+
+#include "perfetto/base/status.h"
+#include "perfetto/trace_processor/trace_blob_view.h"
+#include "src/trace_processor/importers/archive/archive_entry.h"
+#include "src/trace_processor/importers/common/chunked_trace_reader.h"
+#include "src/trace_processor/tables/metadata_tables_py.h"
+#include "src/trace_processor/util/zip_reader.h"
+
+namespace perfetto::trace_processor {
+
+class ForwardingTraceParser;
+class TraceProcessorContext;
+
+// Forwards files contained in a ZIP to the appropiate ChunkedTraceReader. It is
+// guaranteed that proto traces will be parsed first.
+class ZipTraceReader : public ChunkedTraceReader {
+ public:
+ explicit ZipTraceReader(TraceProcessorContext* context);
+ ~ZipTraceReader() override;
+
+ // ChunkedTraceReader implementation
+ base::Status Parse(TraceBlobView) override;
+ base::Status NotifyEndOfFile() override;
+
+ private:
+ struct File {
+ tables::TraceFileTable::Id id;
+ TraceBlobView data;
+ };
+ TraceProcessorContext* const context_;
+ util::ZipReader zip_reader_;
+};
+
+} // namespace perfetto::trace_processor
+
+#endif // SRC_TRACE_PROCESSOR_IMPORTERS_ARCHIVE_ZIP_TRACE_READER_H_
diff --git a/src/trace_processor/importers/art_method/BUILD.gn b/src/trace_processor/importers/art_method/BUILD.gn
new file mode 100644
index 0000000..57f6c62
--- /dev/null
+++ b/src/trace_processor/importers/art_method/BUILD.gn
@@ -0,0 +1,45 @@
+# 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("../../../../gn/test.gni")
+
+source_set("art_method_event") {
+ sources = [ "art_method_event.h" ]
+ deps = [
+ "../../../../gn:default_deps",
+ "../../containers",
+ ]
+}
+
+source_set("art_method") {
+ sources = [
+ "art_method_parser_impl.cc",
+ "art_method_parser_impl.h",
+ "art_method_tokenizer.cc",
+ "art_method_tokenizer.h",
+ ]
+ deps = [
+ ":art_method_event",
+ "../../../../gn:default_deps",
+ "../../../../protos/perfetto/common:zero",
+ "../../../base",
+ "../../containers",
+ "../../importers/common",
+ "../../sorter",
+ "../../storage",
+ "../../types",
+ "../../util",
+ "../../util:trace_blob_view_reader",
+ ]
+}
diff --git a/src/trace_processor/importers/art_method/art_method_event.h b/src/trace_processor/importers/art_method/art_method_event.h
new file mode 100644
index 0000000..4ad10a7
--- /dev/null
+++ b/src/trace_processor/importers/art_method/art_method_event.h
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_ART_METHOD_ART_METHOD_EVENT_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_ART_METHOD_ART_METHOD_EVENT_H_
+
+#include <cstdint>
+#include <optional>
+
+#include "src/trace_processor/containers/string_pool.h"
+
+namespace perfetto::trace_processor::art_method {
+
+struct alignas(8) ArtMethodEvent {
+ uint32_t tid;
+ std::optional<StringPool::Id> comm;
+ StringPool::Id method;
+ enum { kEnter, kExit } action;
+ std::optional<StringPool::Id> pathname;
+ std::optional<uint32_t> line_number;
+};
+
+} // namespace perfetto::trace_processor::art_method
+
+#endif // SRC_TRACE_PROCESSOR_IMPORTERS_ART_METHOD_ART_METHOD_EVENT_H_
diff --git a/src/trace_processor/importers/art_method/art_method_parser_impl.cc b/src/trace_processor/importers/art_method/art_method_parser_impl.cc
new file mode 100644
index 0000000..2d55095
--- /dev/null
+++ b/src/trace_processor/importers/art_method/art_method_parser_impl.cc
@@ -0,0 +1,66 @@
+/*
+ * 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.
+ */
+
+#include "src/trace_processor/importers/art_method/art_method_parser_impl.h"
+
+#include <cstdint>
+
+#include "src/trace_processor/importers/art_method/art_method_event.h"
+#include "src/trace_processor/importers/common/args_tracker.h"
+#include "src/trace_processor/importers/common/process_tracker.h"
+#include "src/trace_processor/importers/common/slice_tracker.h"
+#include "src/trace_processor/importers/common/stack_profile_tracker.h"
+#include "src/trace_processor/importers/common/track_tracker.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/types/variadic.h"
+
+namespace perfetto::trace_processor::art_method {
+
+ArtMethodParserImpl::ArtMethodParserImpl(TraceProcessorContext* context)
+ : context_(context),
+ pathname_id_(context->storage->InternString("pathname")),
+ line_number_id_(context->storage->InternString("line_number")) {}
+
+ArtMethodParserImpl::~ArtMethodParserImpl() = default;
+
+void ArtMethodParserImpl::ParseArtMethodEvent(int64_t ts, ArtMethodEvent e) {
+ UniqueTid utid = context_->process_tracker->GetOrCreateThread(e.tid);
+ if (e.comm) {
+ context_->process_tracker->UpdateThreadNameAndMaybeProcessName(
+ e.tid, *e.comm, ThreadNamePriority::kOther);
+ }
+ TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+ switch (e.action) {
+ case ArtMethodEvent::kEnter:
+ context_->slice_tracker->Begin(
+ ts, track_id, kNullStringId, e.method,
+ [this, &e](ArgsTracker::BoundInserter* i) {
+ if (e.pathname) {
+ i->AddArg(pathname_id_, Variadic::String(*e.pathname));
+ }
+ if (e.line_number) {
+ i->AddArg(line_number_id_, Variadic::Integer(*e.line_number));
+ }
+ });
+ break;
+ case ArtMethodEvent::kExit:
+ context_->slice_tracker->End(ts, track_id);
+ break;
+ }
+}
+
+} // namespace perfetto::trace_processor::art_method
diff --git a/src/trace_processor/importers/art_method/art_method_parser_impl.h b/src/trace_processor/importers/art_method/art_method_parser_impl.h
new file mode 100644
index 0000000..09759cd
--- /dev/null
+++ b/src/trace_processor/importers/art_method/art_method_parser_impl.h
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_ART_METHOD_ART_METHOD_PARSER_IMPL_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_ART_METHOD_ART_METHOD_PARSER_IMPL_H_
+
+#include <cstdint>
+
+#include "src/trace_processor/containers/string_pool.h"
+#include "src/trace_processor/importers/art_method/art_method_event.h"
+#include "src/trace_processor/importers/common/trace_parser.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto::trace_processor::art_method {
+
+class ArtMethodParserImpl : public ArtMethodParser {
+ public:
+ explicit ArtMethodParserImpl(TraceProcessorContext*);
+ ~ArtMethodParserImpl() override;
+
+ void ParseArtMethodEvent(int64_t ts, ArtMethodEvent) override;
+
+ private:
+ TraceProcessorContext* const context_;
+
+ StringPool::Id pathname_id_;
+ StringPool::Id line_number_id_;
+};
+
+} // namespace perfetto::trace_processor::art_method
+
+#endif // SRC_TRACE_PROCESSOR_IMPORTERS_ART_METHOD_ART_METHOD_PARSER_IMPL_H_
diff --git a/src/trace_processor/importers/art_method/art_method_tokenizer.cc b/src/trace_processor/importers/art_method/art_method_tokenizer.cc
new file mode 100644
index 0000000..bdcbd1d
--- /dev/null
+++ b/src/trace_processor/importers/art_method/art_method_tokenizer.cc
@@ -0,0 +1,623 @@
+/*
+ * 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.
+ */
+
+#include "src/trace_processor/importers/art_method/art_method_tokenizer.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include "perfetto/base/compiler.h"
+#include "perfetto/base/logging.h"
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/status_or.h"
+#include "perfetto/ext/base/string_splitter.h"
+#include "perfetto/ext/base/string_utils.h"
+#include "perfetto/ext/base/string_view.h"
+#include "perfetto/ext/base/utils.h"
+#include "perfetto/trace_processor/trace_blob_view.h"
+#include "src/trace_processor/importers/art_method/art_method_event.h"
+#include "src/trace_processor/importers/common/stack_profile_tracker.h"
+#include "src/trace_processor/sorter/trace_sorter.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/util/status_macros.h"
+#include "src/trace_processor/util/trace_blob_view_reader.h"
+
+#include "protos/perfetto/common/builtin_clock.pbzero.h"
+
+namespace perfetto::trace_processor::art_method {
+namespace {
+
+constexpr uint32_t kTraceMagic = 0x574f4c53; // 'SLOW'
+constexpr uint32_t kStreamingVersionMask = 0xF0U;
+constexpr uint32_t kTraceHeaderLength = 32;
+
+constexpr uint32_t kMethodsCode = 1;
+constexpr uint32_t kThreadsCode = 2;
+constexpr uint32_t kSummaryCode = 3;
+
+std::string_view ToStringView(const TraceBlobView& tbv) {
+ return {reinterpret_cast<const char*>(tbv.data()), tbv.size()};
+}
+
+std::string ConstructPathname(const std::string& class_name,
+ const std::string& pathname) {
+ size_t index = class_name.rfind('/');
+ if (index != std::string::npos && base::EndsWith(pathname, ".java")) {
+ return class_name.substr(0, index + 1) + pathname;
+ }
+ return pathname;
+}
+
+uint64_t ToLong(const TraceBlobView& tbv) {
+ uint64_t x = 0;
+ memcpy(base::AssumeLittleEndian(&x), tbv.data(), tbv.size());
+ return x;
+}
+
+uint32_t ToInt(const TraceBlobView& tbv) {
+ uint32_t x = 0;
+ memcpy(base::AssumeLittleEndian(&x), tbv.data(), tbv.size());
+ return x;
+}
+
+uint16_t ToShort(const TraceBlobView& tbv) {
+ uint16_t x = 0;
+ memcpy(base::AssumeLittleEndian(&x), tbv.data(), tbv.size());
+ return x;
+}
+
+} // namespace
+
+ArtMethodTokenizer::ArtMethodTokenizer(TraceProcessorContext* ctx)
+ : context_(ctx) {}
+ArtMethodTokenizer::~ArtMethodTokenizer() = default;
+
+base::Status ArtMethodTokenizer::Parse(TraceBlobView blob) {
+ reader_.PushBack(std::move(blob));
+ if (sub_parser_.index() == base::variant_index<SubParser, Detect>()) {
+ auto smagic = reader_.SliceOff(reader_.start_offset(), 4);
+ if (!smagic) {
+ return base::OkStatus();
+ }
+ uint32_t magic = ToInt(*smagic);
+ sub_parser_ = magic == kTraceMagic ? SubParser{Streaming{this}}
+ : SubParser{NonStreaming{this}};
+ context_->clock_tracker->SetTraceTimeClock(
+ protos::pbzero::BUILTIN_CLOCK_MONOTONIC);
+ }
+ if (sub_parser_.index() == base::variant_index<SubParser, Streaming>()) {
+ return std::get<Streaming>(sub_parser_).Parse();
+ }
+ return std::get<NonStreaming>(sub_parser_).Parse();
+}
+
+base::Status ArtMethodTokenizer::ParseMethodLine(std::string_view l) {
+ auto tokens = base::SplitString(base::TrimWhitespace(std::string(l)), "\t");
+ auto id = base::StringToUInt32(tokens[0], 16);
+ if (!id) {
+ return base::ErrStatus(
+ "ART method trace: unable to parse method id as integer: %s",
+ tokens[0].c_str());
+ }
+
+ std::string class_name = tokens[1];
+ std::string method_name;
+ std::string signature;
+ std::optional<StringId> pathname;
+ std::optional<uint32_t> line_number;
+ // Below logic was taken from:
+ // https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:perflib/src/main/java/com/android/tools/perflib/vmtrace/VmTraceParser.java;l=251
+ // It's not clear why this complexity is strictly needed (maybe backcompat
+ // or certain configurations of method tracing) but it's best to stick
+ // closely to the official parser implementation.
+ if (tokens.size() == 6) {
+ method_name = tokens[2];
+ signature = tokens[3];
+ pathname = context_->storage->InternString(
+ base::StringView(ConstructPathname(class_name, tokens[4])));
+ line_number = base::StringToUInt32(tokens[5]);
+ } else if (tokens.size() > 2) {
+ if (base::StartsWith(tokens[3], "(")) {
+ method_name = tokens[2];
+ signature = tokens[3];
+ if (tokens.size() >= 5) {
+ pathname = context_->storage->InternString(base::StringView(tokens[4]));
+ }
+ } else {
+ pathname = context_->storage->InternString(base::StringView(tokens[2]));
+ line_number = base::StringToUInt32(tokens[3]);
+ }
+ }
+ base::StackString<2048> slice_name("%s.%s: %s", class_name.c_str(),
+ method_name.c_str(), signature.c_str());
+ method_map_[*id] = {
+ context_->storage->InternString(slice_name.string_view()),
+ pathname,
+ line_number,
+ };
+ return base::OkStatus();
+}
+
+base::Status ArtMethodTokenizer::ParseOptionLine(std::string_view l) {
+ std::string line(l);
+ auto res = base::SplitString(line, "=");
+ if (res.size() != 2) {
+ return base::ErrStatus(
+ "ART method tracing: unable to parse option (line %s)", line.c_str());
+ }
+ if (res[0] == "clock") {
+ if (res[1] == "dual") {
+ clock_ = kDual;
+ } else if (res[1] == "wall") {
+ clock_ = kWall;
+ } else if (res[1] == "thread-cpu") {
+ return base::ErrStatus(
+ "ART method tracing: thread-cpu clock is *not* supported. Use wall "
+ "or dual clocks");
+ } else {
+ return base::ErrStatus("ART method tracing: unknown clock %s",
+ res[1].c_str());
+ }
+ }
+ return base::OkStatus();
+}
+
+base::Status ArtMethodTokenizer::ParseRecord(uint32_t tid,
+ const TraceBlobView& record) {
+ ArtMethodEvent evt{};
+ evt.tid = tid;
+ if (auto* it = thread_map_.Find(tid); it && !it->comm_used) {
+ evt.comm = it->comm;
+ it->comm_used = true;
+ }
+
+ uint32_t methodid_action = ToInt(record.slice_off(0, 4));
+ uint32_t ts_delta = clock_ == kDual ? ToInt(record.slice_off(8, 4))
+ : ToInt(record.slice_off(4, 4));
+
+ uint32_t action = methodid_action & 0x03;
+ uint32_t method_id = methodid_action & ~0x03u;
+
+ const auto& m = method_map_[method_id];
+ evt.method = m.name;
+ evt.pathname = m.pathname;
+ evt.line_number = m.line_number;
+ switch (action) {
+ case 0:
+ evt.action = ArtMethodEvent::kEnter;
+ break;
+ case 1:
+ case 2:
+ evt.action = ArtMethodEvent::kExit;
+ break;
+ }
+ ASSIGN_OR_RETURN(int64_t ts, context_->clock_tracker->ToTraceTime(
+ protos::pbzero::BUILTIN_CLOCK_MONOTONIC,
+ (ts_ + ts_delta) * 1000));
+ context_->sorter->PushArtMethodEvent(ts, evt);
+ return base::OkStatus();
+}
+
+base::Status ArtMethodTokenizer::ParseThread(uint32_t tid,
+ const std::string& comm) {
+ thread_map_.Insert(
+ tid, {context_->storage->InternString(base::StringView(comm)), false});
+ return base::OkStatus();
+}
+
+base::Status ArtMethodTokenizer::Streaming::Parse() {
+ auto it = tokenizer_->reader_.GetIterator();
+ PERFETTO_CHECK(it.MaybeAdvance(it_offset_));
+ for (bool cnt = true; cnt;) {
+ switch (mode_) {
+ case kHeaderStart: {
+ ASSIGN_OR_RETURN(cnt, ParseHeaderStart(it));
+ break;
+ }
+ case kData: {
+ ASSIGN_OR_RETURN(cnt, ParseData(it));
+ break;
+ }
+ case kSummaryDone: {
+ mode_ = kDone;
+ cnt = false;
+ break;
+ }
+ case kDone: {
+ return base::ErrStatus(
+ "ART method trace: unexpected data after eof marker");
+ }
+ }
+ if (cnt) {
+ it_offset_ = it.file_offset();
+ }
+ }
+ return base::OkStatus();
+}
+
+base::StatusOr<bool> ArtMethodTokenizer::Streaming::ParseHeaderStart(
+ Iterator& it) {
+ auto header = it.MaybeRead(kTraceHeaderLength);
+ if (!header) {
+ return false;
+ }
+ uint32_t magic = ToInt(header->slice_off(0, 4));
+ if (magic != kTraceMagic) {
+ return base::ErrStatus("ART Method trace: expected start-header magic");
+ }
+ tokenizer_->version_ =
+ ToShort(header->slice_off(4, 2)) ^ kStreamingVersionMask;
+ tokenizer_->ts_ = static_cast<int64_t>(ToLong(header->slice_off(8, 8)));
+ switch (tokenizer_->version_) {
+ case 1:
+ tokenizer_->record_size_ = 9;
+ break;
+ case 2:
+ tokenizer_->record_size_ = 10;
+ break;
+ case 3:
+ tokenizer_->record_size_ = ToShort(header->slice_off(16, 2));
+ break;
+ default:
+ PERFETTO_FATAL("Illegal version %u", tokenizer_->version_);
+ }
+ mode_ = kData;
+ return true;
+}
+
+base::StatusOr<bool> ArtMethodTokenizer::Streaming::ParseData(Iterator& it) {
+ std::optional<TraceBlobView> op_tbv = it.MaybeRead(2);
+ if (!op_tbv) {
+ return false;
+ }
+ uint32_t op = ToShort(*op_tbv);
+ if (op != 0) {
+ // Just skip past the record: this will be handled later.
+ // -2 because we already the tid above which forms part of the record.
+ return it.MaybeAdvance(tokenizer_->record_size_ - 2);
+ }
+ std::optional<TraceBlobView> code_tbv = it.MaybeRead(1);
+ if (!code_tbv) {
+ return false;
+ }
+ uint8_t code = *code_tbv->data();
+ switch (code) {
+ case kSummaryCode: {
+ std::optional<TraceBlobView> summary_len_tbv = it.MaybeRead(4);
+ if (!summary_len_tbv) {
+ return false;
+ }
+ uint32_t summary_len = ToInt(*summary_len_tbv);
+ std::optional<TraceBlobView> summary_tbv = it.MaybeRead(summary_len);
+ if (!summary_tbv) {
+ return false;
+ }
+ RETURN_IF_ERROR(ParseSummary(ToStringView(*summary_tbv)));
+ mode_ = kSummaryDone;
+ return true;
+ }
+ case kMethodsCode: {
+ std::optional<TraceBlobView> method_len_tbv = it.MaybeRead(2);
+ if (!method_len_tbv) {
+ return false;
+ }
+ uint32_t method_len = ToShort(*method_len_tbv);
+ std::optional<TraceBlobView> method_tbv = it.MaybeRead(method_len);
+ if (!method_tbv) {
+ return false;
+ }
+ RETURN_IF_ERROR(tokenizer_->ParseMethodLine(ToStringView(*method_tbv)));
+ return true;
+ }
+ case kThreadsCode: {
+ std::optional<TraceBlobView> tid_tbv = it.MaybeRead(2);
+ if (!tid_tbv) {
+ return false;
+ }
+ std::optional<TraceBlobView> comm_len_tbv = it.MaybeRead(2);
+ if (!comm_len_tbv) {
+ return false;
+ }
+ uint32_t comm_len = ToShort(*comm_len_tbv);
+ std::optional<TraceBlobView> comm_tbv = it.MaybeRead(comm_len);
+ if (!comm_tbv) {
+ return false;
+ }
+ RETURN_IF_ERROR(tokenizer_->ParseThread(
+ ToShort(*tid_tbv), std::string(ToStringView(*comm_tbv))));
+ return true;
+ }
+ default:
+ return base::ErrStatus("ART method trace: unknown opcode encountered %d",
+ code);
+ }
+}
+
+base::Status ArtMethodTokenizer::Streaming::ParseSummary(
+ std::string_view summary) const {
+ base::StringSplitter s(std::string(summary), '\n');
+
+ // First two lines should be version and line number respectively.
+ if (!s.Next() || !s.Next() || !s.Next()) {
+ return base::ErrStatus(
+ "ART method trace: unexpected format of summary section");
+ }
+
+ // Parse lines until we hit "*threads" as the line.
+ for (;;) {
+ std::string_view line(s.cur_token(), s.cur_token_size());
+ if (line == "*threads") {
+ return base::OkStatus();
+ }
+ RETURN_IF_ERROR(tokenizer_->ParseOptionLine(line));
+ if (!s.Next()) {
+ return base::ErrStatus(
+ "ART method trace: reached end of file before EOF marker");
+ }
+ }
+}
+
+base::Status ArtMethodTokenizer::Streaming::NotifyEndOfFile() {
+ if (mode_ != kDone) {
+ return base::ErrStatus("ART Method trace: trace is incomplete");
+ }
+
+ auto it = tokenizer_->reader_.GetIterator();
+ PERFETTO_CHECK(it.MaybeAdvance(kTraceHeaderLength));
+ for (;;) {
+ std::optional<TraceBlobView> tid_tbv = it.MaybeRead(2);
+ uint32_t tid = ToShort(*tid_tbv);
+ if (tid == 0) {
+ uint8_t code = *it.MaybeRead(1)->data();
+ switch (code) {
+ case kSummaryCode:
+ return base::OkStatus();
+ case kMethodsCode: {
+ PERFETTO_CHECK(it.MaybeAdvance(ToShort(*it.MaybeRead(2))));
+ break;
+ }
+ case kThreadsCode: {
+ // Advance past the tid.
+ PERFETTO_CHECK(it.MaybeAdvance(2));
+ PERFETTO_CHECK(it.MaybeAdvance(ToShort(*it.MaybeRead(2))));
+ break;
+ }
+ default:
+ PERFETTO_FATAL("Should not be reached");
+ }
+ continue;
+ }
+ RETURN_IF_ERROR(tokenizer_->ParseRecord(
+ tid, *it.MaybeRead(tokenizer_->record_size_ - 2)));
+ }
+}
+
+base::Status ArtMethodTokenizer::NonStreaming::Parse() {
+ auto it = tokenizer_->reader_.GetIterator();
+ for (bool cnt = true; cnt;) {
+ switch (mode_) {
+ case kHeaderStart: {
+ ASSIGN_OR_RETURN(cnt, ParseHeaderStart(it));
+ break;
+ }
+ case kHeaderVersion: {
+ ASSIGN_OR_RETURN(cnt, ParseHeaderVersion(it));
+ break;
+ }
+ case kHeaderOptions: {
+ ASSIGN_OR_RETURN(cnt, ParseHeaderOptions(it));
+ break;
+ }
+ case kHeaderThreads: {
+ ASSIGN_OR_RETURN(cnt, ParseHeaderThreads(it));
+ break;
+ }
+ case kHeaderMethods: {
+ ASSIGN_OR_RETURN(cnt, ParseHeaderMethods(it));
+ break;
+ }
+ case kDataHeader: {
+ ASSIGN_OR_RETURN(cnt, ParseDataHeader(it));
+ break;
+ }
+ case kData: {
+ size_t s = it.file_offset();
+ for (size_t i = s;; i += tokenizer_->record_size_) {
+ auto record =
+ tokenizer_->reader_.SliceOff(i, tokenizer_->record_size_);
+ if (!record) {
+ PERFETTO_CHECK(it.MaybeAdvance(i - s));
+ cnt = false;
+ break;
+ }
+ uint32_t tid = tokenizer_->version_ == 1
+ ? record->data()[0]
+ : ToShort(record->slice_off(0, 2));
+ RETURN_IF_ERROR(tokenizer_->ParseRecord(
+ tid, record->slice_off(2, record->size() - 2)));
+ }
+ break;
+ }
+ }
+ }
+ tokenizer_->reader_.PopFrontUntil(it.file_offset());
+ return base::OkStatus();
+}
+
+base::Status ArtMethodTokenizer::NonStreaming::NotifyEndOfFile() const {
+ if (mode_ == NonStreaming::kData && tokenizer_->reader_.empty()) {
+ return base::OkStatus();
+ }
+ return base::ErrStatus("ART Method trace: trace is incomplete");
+}
+
+base::StatusOr<bool> ArtMethodTokenizer::NonStreaming::ParseHeaderStart(
+ Iterator& it) {
+ auto raw = it.MaybeFindAndRead('\n');
+ if (!raw) {
+ return false;
+ }
+ RETURN_IF_ERROR(ParseHeaderSectionLine(ToStringView(*raw)));
+ return true;
+}
+
+base::StatusOr<bool> ArtMethodTokenizer::NonStreaming::ParseHeaderVersion(
+ Iterator& it) {
+ auto line = it.MaybeFindAndRead('\n');
+ if (!line) {
+ return false;
+ }
+ std::string version_str(ToStringView(*line));
+ auto version = base::StringToInt32(version_str);
+ if (!version || *version < 1 || *version > 3) {
+ return base::ErrStatus("ART Method trace: trace version (%s) not supported",
+ version_str.c_str());
+ }
+ tokenizer_->version_ = static_cast<uint32_t>(*version);
+ mode_ = kHeaderOptions;
+ return true;
+}
+
+base::StatusOr<bool> ArtMethodTokenizer::NonStreaming::ParseHeaderOptions(
+ Iterator& it) {
+ for (auto r = it.MaybeFindAndRead('\n'); r; r = it.MaybeFindAndRead('\n')) {
+ std::string_view l = ToStringView(*r);
+ if (l[0] == '*') {
+ RETURN_IF_ERROR(ParseHeaderSectionLine(l));
+ return true;
+ }
+ RETURN_IF_ERROR(tokenizer_->ParseOptionLine(l));
+ }
+ return false;
+}
+
+base::StatusOr<bool> ArtMethodTokenizer::NonStreaming::ParseHeaderThreads(
+ Iterator& it) {
+ for (auto r = it.MaybeFindAndRead('\n'); r; r = it.MaybeFindAndRead('\n')) {
+ std::string_view l = ToStringView(*r);
+ if (l[0] == '*') {
+ RETURN_IF_ERROR(ParseHeaderSectionLine(l));
+ return true;
+ }
+ std::string line(l);
+ auto tokens = base::SplitString(line, "\t");
+ if (tokens.size() != 2) {
+ return base::ErrStatus(
+ "ART method tracing: expected only one tab in thread line (context: "
+ "%s)",
+ line.c_str());
+ }
+ std::optional<uint32_t> tid = base::StringToUInt32(tokens[0]);
+ if (!tid) {
+ return base::ErrStatus(
+ "ART method tracing: failed parse tid in thread line (context: %s)",
+ tokens[0].c_str());
+ }
+ RETURN_IF_ERROR(tokenizer_->ParseThread(*tid, tokens[1]));
+ }
+ return false;
+}
+
+base::StatusOr<bool> ArtMethodTokenizer::NonStreaming::ParseHeaderMethods(
+ Iterator& it) {
+ for (auto r = it.MaybeFindAndRead('\n'); r; r = it.MaybeFindAndRead('\n')) {
+ std::string_view l = ToStringView(*r);
+ if (l[0] == '*') {
+ RETURN_IF_ERROR(ParseHeaderSectionLine(l));
+ return true;
+ }
+ RETURN_IF_ERROR(tokenizer_->ParseMethodLine(l));
+ }
+ return false;
+}
+
+base::StatusOr<bool> ArtMethodTokenizer::NonStreaming::ParseDataHeader(
+ Iterator& it) {
+ auto header = it.MaybeRead(kTraceHeaderLength);
+ if (!header) {
+ return false;
+ }
+ uint32_t magic = ToInt(header->slice_off(0, 4));
+ if (magic != kTraceMagic) {
+ return base::ErrStatus("ART Method trace: expected start-header magic");
+ }
+ uint16_t version = ToShort(header->slice_off(4, 2));
+ if (version != tokenizer_->version_) {
+ return base::ErrStatus(
+ "ART Method trace: trace version does not match data version");
+ }
+ tokenizer_->ts_ = static_cast<int64_t>(ToLong(header->slice_off(8, 8)));
+ switch (tokenizer_->version_) {
+ case 1:
+ tokenizer_->record_size_ = 9;
+ break;
+ case 2:
+ tokenizer_->record_size_ = 10;
+ break;
+ case 3:
+ tokenizer_->record_size_ = ToShort(header->slice_off(16, 2));
+ break;
+ default:
+ PERFETTO_FATAL("Illegal version %u", tokenizer_->version_);
+ }
+ mode_ = kData;
+ return true;
+}
+
+base::Status ArtMethodTokenizer::NonStreaming::ParseHeaderSectionLine(
+ std::string_view line) {
+ if (line == "*version") {
+ mode_ = kHeaderVersion;
+ return base::OkStatus();
+ }
+ if (line == "*threads") {
+ mode_ = kHeaderThreads;
+ return base::OkStatus();
+ }
+ if (line == "*methods") {
+ mode_ = kHeaderMethods;
+ return base::OkStatus();
+ }
+ if (line == "*end") {
+ mode_ = kDataHeader;
+ return base::OkStatus();
+ }
+ return base::ErrStatus(
+ "ART Method trace: unexpected line (%s) when expecting section header "
+ "(line starting with *)",
+ std::string(line).c_str());
+}
+
+base::Status ArtMethodTokenizer::NotifyEndOfFile() {
+ switch (sub_parser_.index()) {
+ case base::variant_index<SubParser, Detect>():
+ return base::ErrStatus("ART Method trace: trace is incomplete");
+ case base::variant_index<SubParser, Streaming>():
+ return std::get<Streaming>(sub_parser_).NotifyEndOfFile();
+ case base::variant_index<SubParser, NonStreaming>():
+ return std::get<NonStreaming>(sub_parser_).NotifyEndOfFile();
+ }
+ PERFETTO_FATAL("For GCC");
+}
+
+} // namespace perfetto::trace_processor::art_method
diff --git a/src/trace_processor/importers/art_method/art_method_tokenizer.h b/src/trace_processor/importers/art_method/art_method_tokenizer.h
new file mode 100644
index 0000000..d79807b
--- /dev/null
+++ b/src/trace_processor/importers/art_method/art_method_tokenizer.h
@@ -0,0 +1,126 @@
+/*
+ * 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.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_ART_METHOD_ART_METHOD_TOKENIZER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_ART_METHOD_ART_METHOD_TOKENIZER_H_
+
+#include <cstddef>
+#include <cstdint>
+#include <limits>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <variant>
+
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/flat_hash_map.h"
+#include "perfetto/ext/base/status_or.h"
+#include "perfetto/trace_processor/trace_blob_view.h"
+#include "src/trace_processor/importers/common/chunked_trace_reader.h"
+#include "src/trace_processor/importers/common/trace_parser.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/util/trace_blob_view_reader.h"
+
+namespace perfetto::trace_processor::art_method {
+
+class ArtMethodTokenizer : public ChunkedTraceReader {
+ public:
+ explicit ArtMethodTokenizer(TraceProcessorContext*);
+ ~ArtMethodTokenizer() override;
+
+ base::Status Parse(TraceBlobView) override;
+ base::Status NotifyEndOfFile() override;
+
+ private:
+ using Iterator = util::TraceBlobViewReader::Iterator;
+ struct Method {
+ StringId name;
+ std::optional<StringId> pathname;
+ std::optional<uint32_t> line_number;
+ };
+ struct Thread {
+ StringId comm;
+ bool comm_used;
+ };
+ struct Detect {};
+ struct NonStreaming {
+ base::Status Parse();
+ base::Status NotifyEndOfFile() const;
+
+ base::StatusOr<bool> ParseHeaderStart(Iterator&);
+ base::StatusOr<bool> ParseHeaderVersion(Iterator&);
+ base::StatusOr<bool> ParseHeaderOptions(Iterator&);
+ base::StatusOr<bool> ParseHeaderThreads(Iterator&);
+ base::StatusOr<bool> ParseHeaderMethods(Iterator&);
+ base::StatusOr<bool> ParseDataHeader(Iterator&);
+
+ base::Status ParseHeaderSectionLine(std::string_view);
+
+ ArtMethodTokenizer* tokenizer_;
+ enum {
+ kHeaderStart,
+ kHeaderVersion,
+ kHeaderOptions,
+ kHeaderThreads,
+ kHeaderMethods,
+ kDataHeader,
+ kData,
+ } mode_ = kHeaderStart;
+ };
+ struct Streaming {
+ base::Status Parse();
+ base::Status NotifyEndOfFile();
+
+ base::StatusOr<bool> ParseHeaderStart(Iterator&);
+ base::StatusOr<bool> ParseData(Iterator&);
+ base::Status ParseSummary(std::string_view) const;
+
+ ArtMethodTokenizer* tokenizer_;
+ enum {
+ kHeaderStart,
+ kData,
+ kSummaryDone,
+ kDone,
+ } mode_ = kHeaderStart;
+ size_t it_offset_ = 0;
+ };
+ using SubParser = std::variant<Detect, NonStreaming, Streaming>;
+
+ [[nodiscard]] base::Status ParseMethodLine(std::string_view);
+ [[nodiscard]] base::Status ParseOptionLine(std::string_view);
+ [[nodiscard]] base::Status ParseThread(uint32_t tid, const std::string&);
+ [[nodiscard]] base::Status ParseRecord(uint32_t tid, const TraceBlobView&);
+
+ TraceProcessorContext* const context_;
+ util::TraceBlobViewReader reader_;
+
+ SubParser sub_parser_;
+ enum {
+ kWall,
+ kDual,
+ } clock_ = kWall;
+
+ uint32_t version_ = std::numeric_limits<uint32_t>::max();
+ int64_t ts_ = std::numeric_limits<int64_t>::max();
+ uint32_t record_size_ = std::numeric_limits<uint32_t>::max();
+ base::FlatHashMap<uint32_t, Method> method_map_;
+ base::FlatHashMap<uint32_t, Thread> thread_map_;
+};
+
+} // namespace perfetto::trace_processor::art_method
+
+#endif // SRC_TRACE_PROCESSOR_IMPORTERS_ART_METHOD_ART_METHOD_TOKENIZER_H_
diff --git a/src/trace_processor/importers/common/BUILD.gn b/src/trace_processor/importers/common/BUILD.gn
index a65d044..8be9508 100644
--- a/src/trace_processor/importers/common/BUILD.gn
+++ b/src/trace_processor/importers/common/BUILD.gn
@@ -56,8 +56,6 @@
"sched_event_state.h",
"sched_event_tracker.cc",
"sched_event_tracker.h",
- "scoped_active_trace_file.cc",
- "scoped_active_trace_file.h",
"slice_tracker.cc",
"slice_tracker.h",
"slice_translation_table.cc",
@@ -73,6 +71,7 @@
"trace_parser.cc",
"track_tracker.cc",
"track_tracker.h",
+ "tracks.h",
"virtual_memory_mapping.cc",
"virtual_memory_mapping.h",
]
diff --git a/src/trace_processor/importers/common/async_track_set_tracker.cc b/src/trace_processor/importers/common/async_track_set_tracker.cc
index 432bd39..dab0af0 100644
--- a/src/trace_processor/importers/common/async_track_set_tracker.cc
+++ b/src/trace_processor/importers/common/async_track_set_tracker.cc
@@ -16,12 +16,12 @@
#include "src/trace_processor/importers/common/async_track_set_tracker.h"
+#include "src/trace_processor/importers/common/process_track_translation_table.h"
#include "src/trace_processor/importers/common/track_tracker.h"
#include "src/trace_processor/storage/trace_storage.h"
#include "src/trace_processor/types/trace_processor_context.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
AsyncTrackSetTracker::AsyncTrackSetTracker(TraceProcessorContext* context)
: android_source_(context->storage->InternString("android")),
@@ -181,11 +181,15 @@
TrackId AsyncTrackSetTracker::CreateTrackForSet(const TrackSet& set) {
switch (set.scope) {
- case TrackSetScope::kGlobal:
- // TODO(lalitm): propogate source from callers rather than just passing
- // kNullStringId here.
- return context_->track_tracker->CreateGlobalAsyncTrack(
- set.global_track_name, kNullStringId);
+ case TrackSetScope::kGlobal: {
+ // TODO(lalitm): propogate source from callers as a dimension
+ TrackTracker::DimensionsBuilder builder =
+ context_->track_tracker->CreateDimensionsBuilder();
+ builder.AppendName(set.global_track_name);
+ return context_->track_tracker->CreateTrack(
+ tracks::unknown, std::move(builder).Build(),
+ TrackTracker::LegacyStringIdName{set.global_track_name});
+ }
case TrackSetScope::kProcess:
// TODO(lalitm): propogate source from callers rather than just passing
// kNullStringId here.
@@ -193,11 +197,28 @@
set.nesting_behaviour == NestingBehaviour::kLegacySaturatingUnnestable
? android_source_
: kNullStringId;
- return context_->track_tracker->CreateProcessAsyncTrack(
- set.process_tuple.name, set.process_tuple.upid, source);
+
+ const StringId name =
+ context_->process_track_translation_table->TranslateName(
+ set.process_tuple.name);
+ TrackTracker::DimensionsBuilder dims_builder =
+ context_->track_tracker->CreateDimensionsBuilder();
+ dims_builder.AppendName(name);
+ dims_builder.AppendUpid(set.process_tuple.upid);
+ TrackTracker::Dimensions dims_id = std::move(dims_builder).Build();
+
+ TrackId id = context_->track_tracker->CreateProcessTrack(
+ tracks::unknown, set.process_tuple.upid, dims_id,
+ TrackTracker::LegacyStringIdName{name});
+
+ if (!source.is_null()) {
+ context_->args_tracker->AddArgsTo(id).AddArg(source,
+ Variadic::String(source));
+ }
+ return id;
}
+
PERFETTO_FATAL("For GCC");
}
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/common/event_tracker.cc b/src/trace_processor/importers/common/event_tracker.cc
index 4b7e817..2ebc62b 100644
--- a/src/trace_processor/importers/common/event_tracker.cc
+++ b/src/trace_processor/importers/common/event_tracker.cc
@@ -88,13 +88,13 @@
TrackId track_id = kInvalidTrackId;
if (upid.has_value()) {
- track_id = context_->track_tracker->InternProcessCounterTrack(
+ track_id = context_->track_tracker->LegacyInternProcessCounterTrack(
pending_counter.name_id, *upid);
} else {
// If we still don't know which process this thread belongs to, fall back
// onto creating a thread counter track. It's too late to drop data
// because the counter values have already been inserted.
- track_id = context_->track_tracker->InternThreadCounterTrack(
+ track_id = context_->track_tracker->LegacyInternThreadCounterTrack(
pending_counter.name_id, utid);
}
auto& counter = *context_->storage->mutable_counter_table();
diff --git a/src/trace_processor/importers/common/event_tracker_unittest.cc b/src/trace_processor/importers/common/event_tracker_unittest.cc
index f83df9b..3f99c78 100644
--- a/src/trace_processor/importers/common/event_tracker_unittest.cc
+++ b/src/trace_processor/importers/common/event_tracker_unittest.cc
@@ -54,8 +54,8 @@
uint32_t cpu = 3;
int64_t timestamp = 100;
- TrackId track = context.track_tracker->InternCpuCounterTrack(
- TrackTracker::CpuCounterTrackType::kFrequency, cpu);
+ TrackId track =
+ context.track_tracker->InternCpuCounterTrack(tracks::cpu_frequency, cpu);
context.event_tracker->PushCounter(timestamp, 1000, track);
context.event_tracker->PushCounter(timestamp + 1, 4000, track);
context.event_tracker->PushCounter(timestamp + 3, 5000, track);
diff --git a/src/trace_processor/importers/common/global_args_tracker.h b/src/trace_processor/importers/common/global_args_tracker.h
index 78bae43..2fd9f36 100644
--- a/src/trace_processor/importers/common/global_args_tracker.h
+++ b/src/trace_processor/importers/common/global_args_tracker.h
@@ -20,6 +20,7 @@
#include <cstdint>
#include <type_traits>
#include <vector>
+#include "perfetto/base/logging.h"
#include "perfetto/ext/base/flat_hash_map.h"
#include "perfetto/ext/base/hash.h"
#include "perfetto/ext/base/small_vector.h"
@@ -63,7 +64,7 @@
"Args must be trivially destructible");
struct ArgHasher {
- uint64_t operator()(const Arg& arg) const noexcept {
+ uint64_t operator()(const CompactArg& arg) const noexcept {
base::Hasher hash;
hash.Update(arg.key.raw_id());
// We don't hash arg.flat_key because it's a subsequence of arg.key.
@@ -99,33 +100,47 @@
explicit GlobalArgsTracker(TraceStorage* storage);
+ ArgSetId AddArgSet(const std::vector<Arg>& args,
+ uint32_t begin,
+ uint32_t end) {
+ return AddArgSet(args.data() + begin, args.data() + end, sizeof(Arg));
+ }
+ ArgSetId AddArgSet(Arg* args, uint32_t begin, uint32_t end) {
+ return AddArgSet(args + begin, args + end, sizeof(Arg));
+ }
+ ArgSetId AddArgSet(CompactArg* args, uint32_t begin, uint32_t end) {
+ return AddArgSet(args + begin, args + end, sizeof(CompactArg));
+ }
+
+ private:
+ using ArgSetHash = uint64_t;
+
// Assumes that the interval [begin, end) of |args| has args with the same key
// grouped together.
- ArgSetId AddArgSet(const Arg* args, uint32_t begin, uint32_t end) {
- base::SmallVector<uint32_t, 64> valid_indexes;
+ ArgSetId AddArgSet(const void* start, const void* end, uint32_t stride) {
+ base::SmallVector<const CompactArg*, 64> valid;
// TODO(eseckler): Also detect "invalid" key combinations in args sets (e.g.
// "foo" and "foo.bar" in the same arg set)?
- for (uint32_t i = begin; i < end; i++) {
- if (!valid_indexes.empty() &&
- args[valid_indexes.back()].key == args[i].key) {
+ for (const void* ptr = start; ptr != end;
+ ptr = reinterpret_cast<const uint8_t*>(ptr) + stride) {
+ const auto& arg = *reinterpret_cast<const CompactArg*>(ptr);
+ if (!valid.empty() && valid.back()->key == arg.key) {
// Last arg had the same key as this one. In case of kSkipIfExists, skip
// this arg. In case of kAddOrUpdate, remove the last arg and add this
// arg instead.
- if (args[i].update_policy == UpdatePolicy::kSkipIfExists) {
+ if (arg.update_policy == UpdatePolicy::kSkipIfExists) {
continue;
- } else {
- PERFETTO_DCHECK(args[i].update_policy == UpdatePolicy::kAddOrUpdate);
- valid_indexes.pop_back();
}
+ PERFETTO_DCHECK(arg.update_policy == UpdatePolicy::kAddOrUpdate);
+ valid.pop_back();
}
-
- valid_indexes.emplace_back(i);
+ valid.emplace_back(&arg);
}
base::Hasher hash;
- for (uint32_t i : valid_indexes) {
- hash.Update(ArgHasher()(args[i]));
+ for (const auto* it : valid) {
+ hash.Update(ArgHasher()(*it));
}
auto& arg_table = *storage_->mutable_arg_table();
@@ -141,9 +156,8 @@
// Taking size() after the Insert() ensures that nothing has an id == 0
// (0 == kInvalidArgSetId).
auto id = static_cast<uint32_t>(arg_row_for_hash_.size());
- for (uint32_t i : valid_indexes) {
- const auto& arg = args[i];
-
+ for (const CompactArg* ptr : valid) {
+ const auto& arg = *ptr;
tables::ArgTable::Row row;
row.arg_set_id = id;
row.flat_key = arg.flat_key;
@@ -179,16 +193,6 @@
return id;
}
- // Exposed for making tests easier to write.
- ArgSetId AddArgSet(const std::vector<Arg>& args,
- uint32_t begin,
- uint32_t end) {
- return AddArgSet(args.data(), begin, end);
- }
-
- private:
- using ArgSetHash = uint64_t;
-
base::FlatHashMap<ArgSetHash, uint32_t, base::AlreadyHashed<ArgSetHash>>
arg_row_for_hash_;
diff --git a/src/trace_processor/importers/common/scoped_active_trace_file.cc b/src/trace_processor/importers/common/scoped_active_trace_file.cc
deleted file mode 100644
index 7249261..0000000
--- a/src/trace_processor/importers/common/scoped_active_trace_file.cc
+++ /dev/null
@@ -1,52 +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.
- */
-
-#include "src/trace_processor/importers/common/scoped_active_trace_file.h"
-
-#include <cstddef>
-#include <cstdint>
-#include <string>
-
-#include "perfetto/ext/base/string_view.h"
-#include "src/trace_processor/importers/common/trace_file_tracker.h"
-#include "src/trace_processor/storage/trace_storage.h"
-#include "src/trace_processor/tables/metadata_tables_py.h"
-#include "src/trace_processor/types/trace_processor_context.h"
-
-namespace perfetto::trace_processor {
-ScopedActiveTraceFile::~ScopedActiveTraceFile() {
- if (is_valid_) {
- context_->trace_file_tracker->EndFile(row_);
- }
-}
-
-void ScopedActiveTraceFile::SetName(const std::string& name) {
- row_.set_name(context_->storage->InternString(base::StringView(name)));
-}
-
-void ScopedActiveTraceFile::SetTraceType(TraceType type) {
- row_.set_trace_type(context_->storage->InternString(TraceTypeToString(type)));
-}
-
-void ScopedActiveTraceFile::SetSize(size_t size) {
- row_.set_size(static_cast<int64_t>(size));
-}
-
-void ScopedActiveTraceFile::AddSize(size_t size) {
- row_.set_size(static_cast<int64_t>(size) + row_.size());
-}
-
-} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/common/scoped_active_trace_file.h b/src/trace_processor/importers/common/scoped_active_trace_file.h
deleted file mode 100644
index 0006fcf..0000000
--- a/src/trace_processor/importers/common/scoped_active_trace_file.h
+++ /dev/null
@@ -1,77 +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.
- */
-
-#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_SCOPED_ACTIVE_TRACE_FILE_H_
-#define SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_SCOPED_ACTIVE_TRACE_FILE_H_
-
-#include <string>
-
-#include "src/trace_processor/tables/metadata_tables_py.h"
-#include "src/trace_processor/util/trace_type.h"
-
-namespace perfetto::trace_processor {
-
-class TraceProcessorContext;
-
-// RAII like object that represents a file currently being parsed. When
-// instances of this object go out of scope they will notify the
-// TraceFileTracker that we are done parsing the file.
-// This class also acts a handler for setting file related properties.
-class ScopedActiveTraceFile {
- public:
- ~ScopedActiveTraceFile();
-
- ScopedActiveTraceFile(const ScopedActiveTraceFile&) = delete;
- ScopedActiveTraceFile& operator=(const ScopedActiveTraceFile&) = delete;
-
- ScopedActiveTraceFile(ScopedActiveTraceFile&& o)
- : context_(o.context_), row_(o.row_), is_valid_(o.is_valid_) {
- o.is_valid_ = false;
- }
-
- ScopedActiveTraceFile& operator=(ScopedActiveTraceFile&& o) {
- context_ = o.context_;
- row_ = o.row_;
- is_valid_ = o.is_valid_;
- o.is_valid_ = false;
- return *this;
- }
-
- void SetTraceType(TraceType type);
-
- // For streamed files this method can be called for each chunk to update the
- // file size incrementally.
- void AddSize(size_t delta);
-
- private:
- friend class TraceFileTracker;
- ScopedActiveTraceFile(TraceProcessorContext* context,
- tables::TraceFileTable::RowReference row)
- : context_(context), row_(row), is_valid_(true) {}
-
- // Sets the file name. If this method is not called (sometimes we do not know
- // the file name, e.g. streaming data) the name is set to null.
- void SetName(const std::string& name);
- void SetSize(size_t size);
-
- TraceProcessorContext* context_;
- tables::TraceFileTable::RowReference row_;
- bool is_valid_;
-};
-
-} // namespace perfetto::trace_processor
-
-#endif // SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_SCOPED_ACTIVE_TRACE_FILE_H_
diff --git a/src/trace_processor/importers/common/slice_tracker.cc b/src/trace_processor/importers/common/slice_tracker.cc
index 5f3af01..443b8fa 100644
--- a/src/trace_processor/importers/common/slice_tracker.cc
+++ b/src/trace_processor/importers/common/slice_tracker.cc
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#include <cstdint>
#include <limits>
#include <stdint.h>
@@ -28,6 +29,9 @@
namespace perfetto {
namespace trace_processor {
+namespace {
+constexpr uint32_t kMaxDepth = 512;
+}
SliceTracker::SliceTracker(TraceProcessorContext* context)
: legacy_unnestable_begin_count_string_id_(
@@ -177,7 +181,7 @@
SliceId id = inserter();
tables::SliceTable::RowReference ref = *slices->FindById(id);
- if (depth >= std::numeric_limits<uint8_t>::max()) {
+ if (depth >= kMaxDepth) {
auto parent_name = context_->storage->GetString(
parent_ref->name().value_or(kNullStringId));
auto name =
@@ -191,7 +195,7 @@
// Post fill all the relevant columns. All the other columns should have
// been filled by the inserter.
- ref.set_depth(static_cast<uint8_t>(depth));
+ ref.set_depth(static_cast<uint32_t>(depth));
ref.set_parent_stack_id(parent_stack_id);
ref.set_stack_id(GetStackHash(stack));
if (parent_id)
diff --git a/src/trace_processor/importers/common/trace_file_tracker.cc b/src/trace_processor/importers/common/trace_file_tracker.cc
index e51371a..2bf02b7 100644
--- a/src/trace_processor/importers/common/trace_file_tracker.cc
+++ b/src/trace_processor/importers/common/trace_file_tracker.cc
@@ -17,11 +17,14 @@
#include "src/trace_processor/importers/common/trace_file_tracker.h"
#include <cstddef>
+#include <cstdint>
+#include <optional>
#include <string>
#include <vector>
+#include "perfetto/base/logging.h"
+#include "perfetto/ext/base/string_view.h"
#include "src/trace_processor/importers/common/metadata_tracker.h"
-#include "src/trace_processor/storage/metadata.h"
#include "src/trace_processor/storage/trace_storage.h"
#include "src/trace_processor/tables/metadata_tables_py.h"
#include "src/trace_processor/types/trace_processor_context.h"
@@ -29,46 +32,49 @@
namespace perfetto::trace_processor {
-ScopedActiveTraceFile TraceFileTracker::StartNewFile() {
- tables::TraceFileTable::Row row;
- if (!ancestors_.empty()) {
- row.parent_id = ancestors_.back();
- }
-
- row.size = 0;
- row.trace_type =
- context_->storage->InternString(TraceTypeToString(kUnknownTraceType));
-
- auto ref =
- context_->storage->mutable_trace_file_table()->Insert(row).row_reference;
-
- ancestors_.push_back(ref.id());
- return ScopedActiveTraceFile(context_, std::move(ref));
+tables::TraceFileTable::Id TraceFileTracker::AddFile(const std::string& name) {
+ return AddFileImpl(context_->storage->InternString(base::StringView(name)));
}
-ScopedActiveTraceFile TraceFileTracker::StartNewFile(const std::string& name,
- TraceType type,
- size_t size) {
- auto file = StartNewFile();
- file.SetName(name);
- file.SetTraceType(type);
- file.SetSize(size);
- return file;
+tables::TraceFileTable::Id TraceFileTracker::AddFileImpl(StringId name) {
+ std::optional<tables::TraceFileTable::Id> parent =
+ parsing_stack_.empty() ? std::nullopt
+ : std::make_optional(parsing_stack_.back());
+ return context_->storage->mutable_trace_file_table()
+ ->Insert({parent, name, 0,
+ context_->storage->InternString(
+ TraceTypeToString(kUnknownTraceType)),
+ std::nullopt})
+ .id;
}
-void TraceFileTracker::EndFile(
- const tables::TraceFileTable::ConstRowReference& row) {
- PERFETTO_CHECK(!ancestors_.empty());
- PERFETTO_CHECK(ancestors_.back() == row.id());
+void TraceFileTracker::SetSize(tables::TraceFileTable::Id id, uint64_t size) {
+ auto row = *context_->storage->mutable_trace_file_table()->FindById(id);
+ row.set_size(static_cast<int64_t>(size));
+}
+
+void TraceFileTracker::StartParsing(tables::TraceFileTable::Id id,
+ TraceType trace_type) {
+ parsing_stack_.push_back(id);
+ auto row = *context_->storage->mutable_trace_file_table()->FindById(id);
+ row.set_trace_type(
+ context_->storage->InternString(TraceTypeToString(trace_type)));
+ row.set_processing_order(static_cast<int64_t>(processing_order_++));
+}
+
+void TraceFileTracker::DoneParsing(tables::TraceFileTable::Id id, size_t size) {
+ PERFETTO_CHECK(!parsing_stack_.empty() && parsing_stack_.back() == id);
+ parsing_stack_.pop_back();
+ auto row = *context_->storage->mutable_trace_file_table()->FindById(id);
+ row.set_size(static_cast<int64_t>(size));
// First file (root)
- if (row.id().value == 0) {
+ if (id.value == 0) {
context_->metadata_tracker->SetMetadata(metadata::trace_size_bytes,
Variadic::Integer(row.size()));
context_->metadata_tracker->SetMetadata(metadata::trace_type,
Variadic::String(row.trace_type()));
}
- ancestors_.pop_back();
}
} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/common/trace_file_tracker.h b/src/trace_processor/importers/common/trace_file_tracker.h
index 0e2f5f9..4c6be5f 100644
--- a/src/trace_processor/importers/common/trace_file_tracker.h
+++ b/src/trace_processor/importers/common/trace_file_tracker.h
@@ -20,7 +20,7 @@
#include <string>
#include <vector>
-#include "src/trace_processor/importers/common/scoped_active_trace_file.h"
+#include "src/trace_processor/storage/trace_storage.h"
#include "src/trace_processor/tables/metadata_tables_py.h"
#include "src/trace_processor/util/trace_type.h"
@@ -36,24 +36,18 @@
explicit TraceFileTracker(TraceProcessorContext* context)
: context_(context) {}
- // Notifies the start of a new file that we are about to parse. It returns a
- // RAII like object that will notify the end of processing when it goes out of
- // scope.
- // NOTE: Files must be ended in reverse order of being started.
- ScopedActiveTraceFile StartNewFile();
-
- // Convenience version of the above that should be used when all the file
- // properties are known upfront.
- ScopedActiveTraceFile StartNewFile(const std::string& name,
- TraceType type,
- size_t size);
+ tables::TraceFileTable::Id AddFile(const std::string& name);
+ tables::TraceFileTable::Id AddFile() { return AddFileImpl(kNullStringId); }
+ void SetSize(tables::TraceFileTable::Id id, uint64_t size);
+ void StartParsing(tables::TraceFileTable::Id id, TraceType trace_type);
+ void DoneParsing(tables::TraceFileTable::Id id, size_t size);
private:
- void EndFile(const tables::TraceFileTable::ConstRowReference& row);
+ tables::TraceFileTable::Id AddFileImpl(StringId name);
- friend class ScopedActiveTraceFile;
TraceProcessorContext* const context_;
- std::vector<tables::TraceFileTable::Id> ancestors_;
+ size_t processing_order_ = 0;
+ std::vector<tables::TraceFileTable::Id> parsing_stack_;
};
} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/common/trace_parser.cc b/src/trace_processor/importers/common/trace_parser.cc
index f0facad..2754a2e 100644
--- a/src/trace_processor/importers/common/trace_parser.cc
+++ b/src/trace_processor/importers/common/trace_parser.cc
@@ -16,8 +16,7 @@
#include "src/trace_processor/importers/common/trace_parser.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
AndroidLogEventParser::~AndroidLogEventParser() = default;
FuchsiaRecordParser::~FuchsiaRecordParser() = default;
@@ -26,6 +25,8 @@
PerfRecordParser::~PerfRecordParser() = default;
ProtoTraceParser::~ProtoTraceParser() = default;
SpeRecordParser::~SpeRecordParser() = default;
+GeckoTraceParser::~GeckoTraceParser() = default;
+ArtMethodParser::~ArtMethodParser() = default;
+PerfTextTraceParser::~PerfTextTraceParser() = default;
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/common/trace_parser.h b/src/trace_processor/importers/common/trace_parser.h
index c1da872..a672a07 100644
--- a/src/trace_processor/importers/common/trace_parser.h
+++ b/src/trace_processor/importers/common/trace_parser.h
@@ -27,6 +27,15 @@
namespace instruments_importer {
struct Row;
}
+namespace gecko_importer {
+struct GeckoEvent;
+}
+namespace art_method {
+struct ArtMethodEvent;
+}
+namespace perf_text_importer {
+struct PerfTextEvent;
+}
struct AndroidLogEvent;
class PacketSequenceStateGeneration;
@@ -48,7 +57,6 @@
virtual void ParseFtraceEvent(uint32_t, int64_t, TracePacketData) = 0;
virtual void ParseInlineSchedSwitch(uint32_t, int64_t, InlineSchedSwitch) = 0;
virtual void ParseInlineSchedWaking(uint32_t, int64_t, InlineSchedWaking) = 0;
- virtual void ParseLegacyV8ProfileEvent(int64_t, LegacyV8CpuProfileEvent) = 0;
};
class JsonTraceParser {
@@ -56,6 +64,7 @@
virtual ~JsonTraceParser();
virtual void ParseJsonPacket(int64_t, std::string) = 0;
virtual void ParseSystraceLine(int64_t, SystraceLine) = 0;
+ virtual void ParseLegacyV8ProfileEvent(int64_t, LegacyV8CpuProfileEvent) = 0;
};
class FuchsiaRecordParser {
@@ -88,6 +97,25 @@
virtual void ParseAndroidLogEvent(int64_t, AndroidLogEvent) = 0;
};
+class GeckoTraceParser {
+ public:
+ virtual ~GeckoTraceParser();
+ virtual void ParseGeckoEvent(int64_t, gecko_importer::GeckoEvent) = 0;
+};
+
+class ArtMethodParser {
+ public:
+ virtual ~ArtMethodParser();
+ virtual void ParseArtMethodEvent(int64_t, art_method::ArtMethodEvent) = 0;
+};
+
+class PerfTextTraceParser {
+ public:
+ virtual ~PerfTextTraceParser();
+ virtual void ParsePerfTextEvent(int64_t,
+ perf_text_importer::PerfTextEvent) = 0;
+};
+
} // namespace perfetto::trace_processor
#endif // SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_TRACE_PARSER_H_
diff --git a/src/trace_processor/importers/common/track_tracker.cc b/src/trace_processor/importers/common/track_tracker.cc
index 452d7f9..6b02ff8 100644
--- a/src/trace_processor/importers/common/track_tracker.cc
+++ b/src/trace_processor/importers/common/track_tracker.cc
@@ -16,23 +16,25 @@
#include "src/trace_processor/importers/common/track_tracker.h"
+#include <cstddef>
#include <cstdint>
#include <optional>
#include <string>
#include <utility>
+#include "perfetto/base/compiler.h"
+#include "perfetto/base/logging.h"
#include "src/trace_processor/importers/common/args_tracker.h"
#include "src/trace_processor/importers/common/cpu_tracker.h"
#include "src/trace_processor/importers/common/process_track_translation_table.h"
+#include "src/trace_processor/importers/common/tracks.h"
#include "src/trace_processor/storage/trace_storage.h"
#include "src/trace_processor/tables/profiler_tables_py.h"
#include "src/trace_processor/tables/track_tables_py.h"
#include "src/trace_processor/types/trace_processor_context.h"
#include "src/trace_processor/types/variadic.h"
-namespace perfetto {
-namespace trace_processor {
-
+namespace perfetto::trace_processor {
namespace {
const char* GetNameForGroup(TrackTracker::Group group) {
@@ -61,84 +63,37 @@
PERFETTO_FATAL("For GCC");
}
-const char* GetTrackName(TrackTracker::GlobalTrackType type) {
- if (type == TrackTracker::GlobalTrackType::kTrigger)
- return "Trace Triggers";
- if (type == TrackTracker::GlobalTrackType::kInterconnect)
- return "Interconnect Events";
- return "";
+bool IsLegacyStringIdNameAllowed(tracks::TrackClassification classification) {
+ // **DO NOT** add new values here. Use TrackTracker::AutoName instead.
+ return classification == tracks::android_energy_estimation_breakdown ||
+ classification ==
+ tracks::android_energy_estimation_breakdown_per_uid ||
+ classification == tracks::unknown;
}
-std::string GetTrackName(TrackTracker::CpuTrackType type, uint32_t cpu) {
- if (type == TrackTracker::CpuTrackType::kIrqCpu)
- return base::StackString<255>("Irq Cpu %u", cpu).c_str();
- if (type == TrackTracker::CpuTrackType::kSortIrqCpu)
- return base::StackString<255>("SoftIrq Cpu %u", cpu).c_str();
- if (type == TrackTracker::CpuTrackType::kNapiGroCpu)
- return base::StackString<255>("Napi Gro Cpu %u", cpu).c_str();
- if (type == TrackTracker::CpuTrackType::kMaxFreqCpu)
- return base::StackString<255>("Cpu %u Max Freq Limit", cpu).c_str();
- if (type == TrackTracker::CpuTrackType::kMinFreqCpu)
- return base::StackString<255>("Cpu %u Min Freq Limit", cpu).c_str();
- if (type == TrackTracker::CpuTrackType::kFuncgraphCpu)
- return base::StackString<255>("swapper%u -funcgraph", cpu).c_str();
- if (type == TrackTracker::CpuTrackType::kMaliIrqCpu)
- return base::StackString<255>("Mali Irq Cpu %u", cpu).c_str();
- if (type == TrackTracker::CpuTrackType::kPkvmHypervisor)
- return base::StackString<255>("pkVM Hypervisor CPU %u", cpu).c_str();
- return "";
-}
-
-std::string GetTrackName(TrackTracker::CpuCounterTrackType type, uint32_t cpu) {
- if (type == TrackTracker::CpuCounterTrackType::kFrequency)
- return "cpufreq";
- if (type == TrackTracker::CpuCounterTrackType::kFreqThrottle)
- return "cpufreq_throttle";
- if (type == TrackTracker::CpuCounterTrackType::kIdle)
- return "cpuidle";
- if (type == TrackTracker::CpuCounterTrackType::kUtilization)
- return base::StackString<255>("Cpu %u Util", cpu).c_str();
- if (type == TrackTracker::CpuCounterTrackType::kCapacity)
- return base::StackString<255>("Cpu %u Cap", cpu).c_str();
- if (type == TrackTracker::CpuCounterTrackType::kNrRunning)
- return base::StackString<255>("Cpu %u Nr Running", cpu).c_str();
- if (type == TrackTracker::CpuCounterTrackType::kMaxFreqLimit)
- return base::StackString<255>("Cpu %u Max Freq Limit", cpu).c_str();
- if (type == TrackTracker::CpuCounterTrackType::kMinFreqLimit)
- return base::StackString<255>("Cpu %u Min Freq Limit", cpu).c_str();
- if (type == TrackTracker::CpuCounterTrackType::kUserTime)
- return "cpu.times.user_ns";
- if (type == TrackTracker::CpuCounterTrackType::kNiceUserTime)
- return "cpu.times.user_nice_ns";
- if (type == TrackTracker::CpuCounterTrackType::kSystemModeTime)
- return "cpu.times.system_mode_ns";
- if (type == TrackTracker::CpuCounterTrackType::kIdleTime)
- return "cpu.times.idle_ns";
- if (type == TrackTracker::CpuCounterTrackType::kIoWaitTime)
- return "cpu.times.io_wait_ns";
- if (type == TrackTracker::CpuCounterTrackType::kIrqTime)
- return "cpu.times.irq_ns";
- if (type == TrackTracker::CpuCounterTrackType::kSoftIrqTime)
- return "cpu.times.softirq_ns";
- return "";
-}
-
-const char* GetTrackName(TrackTracker::GpuCounterTrackType type) {
- if (type == TrackTracker::GpuCounterTrackType::kFrequency)
- return "gpufreq";
- return "";
-}
-
-const char* GetTrackName(TrackTracker::IrqCounterTrackType type) {
- if (type == TrackTracker::IrqCounterTrackType::kCount)
- return "num_irq";
- return "";
-}
-
-const char* GetTrackName(TrackTracker::SoftIrqCounterTrackType type) {
- if (type == TrackTracker::SoftIrqCounterTrackType::kCount)
- return "num_softirq";
- return "";
+bool IsLegacyCharArrayNameAllowed(tracks::TrackClassification classification) {
+ // **DO NOT** add new values here. Use TrackTracker::AutoName instead.
+ return classification == tracks::cpu_capacity ||
+ classification == tracks::cpu_frequency ||
+ classification == tracks::cpu_frequency_throttle ||
+ classification == tracks::cpu_funcgraph ||
+ classification == tracks::cpu_idle ||
+ classification == tracks::cpu_irq ||
+ classification == tracks::cpu_mali_irq ||
+ classification == tracks::cpu_max_frequency_limit ||
+ classification == tracks::cpu_min_frequency_limit ||
+ classification == tracks::cpu_napi_gro ||
+ classification == tracks::cpu_nr_running ||
+ classification == tracks::cpu_stat ||
+ classification == tracks::cpu_softirq ||
+ classification == tracks::cpu_utilization ||
+ classification == tracks::gpu_frequency ||
+ classification == tracks::interconnect_events ||
+ classification == tracks::irq_counter ||
+ classification == tracks::linux_rpm ||
+ classification == tracks::pkvm_hypervisor ||
+ classification == tracks::softirq_counter ||
+ classification == tracks::triggers;
}
} // namespace
@@ -150,289 +105,188 @@
context->storage->InternString("trace_id_is_process_scoped")),
source_scope_key_(context->storage->InternString("source_scope")),
category_key_(context->storage->InternString("category")),
+ scope_id_(context->storage->InternString("scope")),
+ cookie_id_(context->storage->InternString("cookie")),
fuchsia_source_(context->storage->InternString("fuchsia")),
chrome_source_(context->storage->InternString("chrome")),
+ utid_id_(context->storage->InternString("utid")),
+ upid_id_(context->storage->InternString("upid")),
+ ucpu_id_(context->storage->InternString("ucpu")),
+ uid_id_(context->storage->InternString("uid")),
+ gpu_id_(context->storage->InternString("gpu")),
+ name_id_(context->storage->InternString("name")),
context_(context) {}
-TrackId TrackTracker::InternThreadTrack(UniqueTid utid) {
- auto it = thread_tracks_.find(utid);
- if (it != thread_tracks_.end())
- return it->second;
+TrackId TrackTracker::CreateTrack(tracks::TrackClassification classification,
+ std::optional<Dimensions> dimensions,
+ const TrackName& name) {
+ tables::TrackTable::Row row(StringIdFromTrackName(classification, name));
+ row.classification =
+ context_->storage->InternString(tracks::ToString(classification));
+ if (dimensions) {
+ row.dimension_arg_set_id = dimensions->arg_set_id;
+ }
+ row.machine_id = context_->machine_id();
- tables::ThreadTrackTable::Row row;
+ return context_->storage->mutable_track_table()->Insert(row).id;
+}
+
+TrackId TrackTracker::CreateCounterTrack(
+ tracks::TrackClassification classification,
+ std::optional<Dimensions> dimensions,
+ const TrackName& name) {
+ tables::CounterTrackTable::Row row(
+ StringIdFromTrackName(classification, name));
+ row.classification =
+ context_->storage->InternString(tracks::ToString(classification));
+ if (dimensions) {
+ row.dimension_arg_set_id = dimensions->arg_set_id;
+ }
+ row.machine_id = context_->machine_id();
+
+ return context_->storage->mutable_counter_track_table()->Insert(row).id;
+}
+
+TrackId TrackTracker::CreateProcessTrack(
+ tracks::TrackClassification classification,
+ UniquePid upid,
+ std::optional<Dimensions> dims,
+ const TrackName& name) {
+ Dimensions dims_id =
+ dims ? *dims : SingleDimension(upid_id_, Variadic::Integer(upid));
+
+ tables::ProcessTrackTable::Row row(
+ StringIdFromTrackName(classification, name));
+ row.upid = upid;
+ row.dimension_arg_set_id = dims_id.arg_set_id;
+ row.classification =
+ context_->storage->InternString(tracks::ToString(classification));
+ row.machine_id = context_->machine_id();
+
+ return context_->storage->mutable_process_track_table()->Insert(row).id;
+}
+
+TrackId TrackTracker::CreateProcessCounterTrack(
+ tracks::TrackClassification classification,
+ UniquePid upid,
+ std::optional<Dimensions> dims,
+ const TrackName& name) {
+ Dimensions dims_id =
+ dims ? *dims : SingleDimension(upid_id_, Variadic::Integer(upid));
+
+ tables::ProcessCounterTrackTable::Row row(
+ StringIdFromTrackName(classification, name));
+ row.upid = upid;
+ row.machine_id = context_->machine_id();
+ row.dimension_arg_set_id = dims_id.arg_set_id;
+ row.classification =
+ context_->storage->InternString(tracks::ToString(classification));
+
+ return context_->storage->mutable_process_counter_track_table()
+ ->Insert(row)
+ .id;
+}
+
+TrackId TrackTracker::CreateThreadTrack(
+ tracks::TrackClassification classification,
+ UniqueTid utid,
+ const TrackName& name) {
+ Dimensions dims_id = SingleDimension(utid_id_, Variadic::Integer(utid));
+
+ tables::ThreadTrackTable::Row row(
+ StringIdFromTrackName(classification, name));
+ row.utid = utid;
+ row.classification =
+ context_->storage->InternString(tracks::ToString(classification));
+ row.dimension_arg_set_id = dims_id.arg_set_id;
+ row.machine_id = context_->machine_id();
+
+ return context_->storage->mutable_thread_track_table()->Insert(row).id;
+}
+
+TrackId TrackTracker::CreateThreadCounterTrack(
+ tracks::TrackClassification classification,
+ UniqueTid utid,
+ const TrackName& name) {
+ Dimensions dims_id = SingleDimension(utid_id_, Variadic::Integer(utid));
+
+ tables::ThreadCounterTrackTable::Row row(
+ StringIdFromTrackName(classification, name));
row.utid = utid;
row.machine_id = context_->machine_id();
- auto id = context_->storage->mutable_thread_track_table()->Insert(row).id;
- thread_tracks_[utid] = id;
- return id;
+ row.dimension_arg_set_id = dims_id.arg_set_id;
+ row.classification =
+ context_->storage->InternString(tracks::ToString(classification));
+
+ return context_->storage->mutable_thread_counter_track_table()
+ ->Insert(row)
+ .id;
}
-TrackId TrackTracker::InternProcessTrack(UniquePid upid) {
- auto it = process_tracks_.find(upid);
- if (it != process_tracks_.end())
- return it->second;
+TrackId TrackTracker::InternTrack(tracks::TrackClassification classification,
+ std::optional<Dimensions> dimensions,
+ const TrackName& name,
+ const SetArgsCallback& callback) {
+ auto* it = tracks_.Find({classification, dimensions});
+ if (it)
+ return *it;
- tables::ProcessTrackTable::Row row;
- row.upid = upid;
- row.machine_id = context_->machine_id();
- auto id = context_->storage->mutable_process_track_table()->Insert(row).id;
- process_tracks_[upid] = id;
- return id;
-}
-
-TrackId TrackTracker::InternCpuTrack(CpuTrackType type, uint32_t cpu) {
- std::string track_name = GetTrackName(type, cpu);
- StringId name = context_->storage->InternString(track_name.c_str());
- auto it = cpu_tracks_.find(std::make_pair(name, cpu));
- if (it != cpu_tracks_.end()) {
- return it->second;
- }
-
- tables::CpuTrackTable::Row row(name);
- row.ucpu = context_->cpu_tracker->GetOrCreateCpu(cpu);
- row.machine_id = context_->machine_id();
- row.classification = context_->storage->InternString(
- std::string("cpu:" + GetClassification(type)).c_str());
- auto id = context_->storage->mutable_cpu_track_table()->Insert(row).id;
- cpu_tracks_[std::make_pair(name, cpu)] = id;
- return id;
-}
-
-TrackId TrackTracker::InternGlobalTrack(GlobalTrackType type) {
- auto it = unique_tracks_.find(type);
- if (it != unique_tracks_.end())
- return it->second;
-
- tables::TrackTable::Row row;
- row.name = context_->storage->InternString(GetTrackName(type));
- row.machine_id = context_->machine_id();
- row.classification = context_->storage->InternString(
- std::string("global:" + GetClassification(type)).c_str());
- TrackId id = context_->storage->mutable_track_table()->Insert(row).id;
- unique_tracks_[type] = id;
-
- if (type == GlobalTrackType::kChromeLegacyGlobalInstant) {
- context_->args_tracker->AddArgsTo(id).AddArg(
- source_key_, Variadic::String(chrome_source_));
- }
-
- return id;
-}
-
-TrackId TrackTracker::InternFuchsiaAsyncTrack(StringId name,
- uint32_t upid,
- int64_t correlation_id) {
- return InternLegacyChromeAsyncTrack(name, upid, correlation_id, false,
- StringId());
-}
-
-TrackId TrackTracker::InternGpuTrack(const tables::GpuTrackTable::Row& row) {
- GpuTrackTuple tuple{row.name, row.scope, row.context_id.value_or(0)};
-
- auto it = gpu_tracks_.find(tuple);
- if (it != gpu_tracks_.end())
- return it->second;
-
- auto row_copy = row;
- row_copy.machine_id = context_->machine_id();
- auto id = context_->storage->mutable_gpu_track_table()->Insert(row_copy).id;
- gpu_tracks_[tuple] = id;
- return id;
-}
-
-TrackId TrackTracker::InternGpuWorkPeriodTrack(
- const tables::GpuWorkPeriodTrackTable::Row& row) {
- GpuWorkPeriodTrackTuple tuple{row.name, row.gpu_id, row.uid};
-
- auto it = gpu_work_period_tracks_.find(tuple);
- if (it != gpu_work_period_tracks_.end())
- return it->second;
-
- auto id =
- context_->storage->mutable_gpu_work_period_track_table()->Insert(row).id;
- gpu_work_period_tracks_[tuple] = id;
- return id;
-}
-
-TrackId TrackTracker::InternLegacyChromeAsyncTrack(
- StringId raw_name,
- uint32_t upid,
- int64_t trace_id,
- bool trace_id_is_process_scoped,
- StringId source_scope) {
- ChromeTrackTuple tuple;
- if (trace_id_is_process_scoped)
- tuple.upid = upid;
- tuple.trace_id = trace_id;
- tuple.source_scope = source_scope;
-
- const StringId name =
- context_->process_track_translation_table->TranslateName(raw_name);
- auto it = chrome_tracks_.find(tuple);
- if (it != chrome_tracks_.end()) {
- if (name != kNullStringId) {
- // The track may have been created for an end event without name. In that
- // case, update it with this event's name.
- auto& tracks = *context_->storage->mutable_track_table();
- auto rr = *tracks.FindById(it->second);
- if (rr.name() == kNullStringId) {
- rr.set_name(name);
- }
- }
- return it->second;
- }
-
- // Legacy async tracks are always drawn in the context of a process, even if
- // the ID's scope is global.
- tables::ProcessTrackTable::Row track(name);
- track.upid = upid;
- track.machine_id = context_->machine_id();
- TrackId id =
- context_->storage->mutable_process_track_table()->Insert(track).id;
- chrome_tracks_[tuple] = id;
-
- context_->args_tracker->AddArgsTo(id)
- .AddArg(source_key_, Variadic::String(chrome_source_))
- .AddArg(trace_id_key_, Variadic::Integer(trace_id))
- .AddArg(trace_id_is_process_scoped_key_,
- Variadic::Boolean(trace_id_is_process_scoped))
- .AddArg(source_scope_key_, Variadic::String(source_scope));
-
- return id;
-}
-
-TrackId TrackTracker::CreateGlobalAsyncTrack(StringId name, StringId source) {
- tables::TrackTable::Row row(name);
- row.machine_id = context_->machine_id();
- auto id = context_->storage->mutable_track_table()->Insert(row).id;
- if (!source.is_null()) {
- context_->args_tracker->AddArgsTo(id).AddArg(source_key_,
- Variadic::String(source));
- }
- return id;
-}
-
-TrackId TrackTracker::CreateProcessAsyncTrack(StringId raw_name,
- UniquePid upid,
- StringId source) {
- const StringId name =
- context_->process_track_translation_table->TranslateName(raw_name);
- tables::ProcessTrackTable::Row row(name);
- row.upid = upid;
- row.machine_id = context_->machine_id();
- auto id = context_->storage->mutable_process_track_table()->Insert(row).id;
- if (!source.is_null()) {
- context_->args_tracker->AddArgsTo(id).AddArg(source_key_,
- Variadic::String(source));
- }
- return id;
-}
-
-TrackId TrackTracker::InternLegacyChromeProcessInstantTrack(UniquePid upid) {
- auto it = chrome_process_instant_tracks_.find(upid);
- if (it != chrome_process_instant_tracks_.end())
- return it->second;
-
- tables::ProcessTrackTable::Row row;
- row.upid = upid;
- row.machine_id = context_->machine_id();
- auto id = context_->storage->mutable_process_track_table()->Insert(row).id;
- chrome_process_instant_tracks_[upid] = id;
-
- context_->args_tracker->AddArgsTo(id).AddArg(
- source_key_, Variadic::String(chrome_source_));
-
- return id;
-}
-
-TrackId TrackTracker::InternGlobalCounterTrack(TrackTracker::Group group,
- StringId name,
- SetArgsCallback callback,
- StringId unit,
- StringId description) {
- auto it = global_counter_tracks_by_name_.find(name);
- if (it != global_counter_tracks_by_name_.end()) {
- return it->second;
- }
-
- tables::CounterTrackTable::Row row(name);
- row.parent_id = InternTrackForGroup(group);
- row.unit = unit;
- row.description = description;
- row.machine_id = context_->machine_id();
- TrackId track =
- context_->storage->mutable_counter_track_table()->Insert(row).id;
- global_counter_tracks_by_name_[name] = track;
+ TrackId id = CreateTrack(classification, dimensions, name);
+ tracks_[{classification, dimensions}] = id;
if (callback) {
- auto inserter = context_->args_tracker->AddArgsTo(track);
+ ArgsTracker::BoundInserter inserter = context_->args_tracker->AddArgsTo(id);
callback(inserter);
}
- return track;
+ return id;
}
-TrackId TrackTracker::InternCpuCounterTrack(CpuCounterTrackTuple tuple) {
- StringPool::Id name = tuple.name.is_null()
- ? context_->storage->InternString(
- GetTrackName(tuple.type, tuple.cpu).c_str())
- : tuple.name;
- auto it = cpu_counter_tracks_.find(tuple);
- if (it != cpu_counter_tracks_.end()) {
- return it->second;
- }
+TrackId TrackTracker::InternCounterTrack(
+ tracks::TrackClassification classification,
+ std::optional<Dimensions> dimensions,
+ const TrackName& name) {
+ auto* it = tracks_.Find({classification, dimensions});
+ if (it)
+ return *it;
- tables::CpuCounterTrackTable::Row row(name);
- row.ucpu = context_->cpu_tracker->GetOrCreateCpu(tuple.cpu);
- row.machine_id = context_->machine_id();
- row.classification = context_->storage->InternString(
- std::string("cpu_counter:" + GetClassification(tuple.type)).c_str());
-
- TrackId track =
- context_->storage->mutable_cpu_counter_track_table()->Insert(row).id;
- cpu_counter_tracks_[tuple] = track;
- return track;
+ TrackId id = CreateCounterTrack(classification, dimensions, name);
+ tracks_[{classification, dimensions}] = id;
+ return id;
}
-TrackId TrackTracker::InternCpuCounterTrack(CpuCounterTrackType track_type,
- uint32_t cpu) {
- CpuCounterTrackTuple tuple{track_type, cpu};
- return InternCpuCounterTrack(tuple);
+TrackId TrackTracker::InternProcessTrack(
+ tracks::TrackClassification classification,
+ UniquePid upid,
+ const TrackName& name) {
+ Dimensions dims_id = SingleDimension(upid_id_, Variadic::Integer(upid));
+
+ auto* it = tracks_.Find({classification, dims_id});
+ if (it)
+ return *it;
+
+ TrackId track_id =
+ CreateProcessTrack(classification, upid, std::nullopt, name);
+ tracks_[{classification, dims_id}] = track_id;
+ return track_id;
}
-TrackId TrackTracker::InternCpuIdleStateTrack(uint32_t cpu, StringId state) {
- std::string name =
- "cpuidle." + context_->storage->GetString(state).ToStdString();
-
- CpuCounterTrackTuple tuple{TrackTracker::CpuCounterTrackType::kIdleState, cpu,
- context_->storage->InternString(name.c_str()),
- state.raw_id()};
- return InternCpuCounterTrack(tuple);
-}
-
-TrackId TrackTracker::InternThreadCounterTrack(StringId name, UniqueTid utid) {
- auto it = utid_counter_tracks_.find(std::make_pair(name, utid));
- if (it != utid_counter_tracks_.end()) {
- return it->second;
- }
-
- tables::ThreadCounterTrackTable::Row row(name);
- row.utid = utid;
- row.machine_id = context_->machine_id();
-
- TrackId track =
- context_->storage->mutable_thread_counter_track_table()->Insert(row).id;
- utid_counter_tracks_[std::make_pair(name, utid)] = track;
- return track;
-}
-
-TrackId TrackTracker::InternProcessCounterTrack(StringId raw_name,
- UniquePid upid,
- StringId unit,
- StringId description) {
+TrackId TrackTracker::LegacyInternProcessCounterTrack(StringId raw_name,
+ UniquePid upid,
+ StringId unit,
+ StringId description) {
const StringId name =
context_->process_track_translation_table->TranslateName(raw_name);
- auto it = upid_counter_tracks_.find(std::make_pair(name, upid));
- if (it != upid_counter_tracks_.end()) {
- return it->second;
+
+ TrackMapKey key;
+ key.classification = tracks::unknown;
+
+ DimensionsBuilder dims_builder = CreateDimensionsBuilder();
+ dims_builder.AppendUpid(upid);
+ dims_builder.AppendName(name);
+ key.dimensions = std::move(dims_builder).Build();
+
+ auto* it = tracks_.Find(key);
+ if (it) {
+ return *it;
}
tables::ProcessCounterTrackTable::Row row(name);
@@ -440,147 +294,273 @@
row.unit = unit;
row.description = description;
row.machine_id = context_->machine_id();
-
- TrackId track =
+ row.classification =
+ context_->storage->InternString(tracks::ToString(key.classification));
+ row.dimension_arg_set_id = key.dimensions->arg_set_id;
+ TrackId track_id =
context_->storage->mutable_process_counter_track_table()->Insert(row).id;
- upid_counter_tracks_[std::make_pair(name, upid)] = track;
- return track;
+
+ tracks_[key] = track_id;
+ return track_id;
}
-TrackId TrackTracker::InternIrqCounterTrack(IrqCounterTrackType type,
- int32_t irq) {
- auto it = irq_counter_tracks_.find(std::make_pair(type, irq));
- if (it != irq_counter_tracks_.end()) {
- return it->second;
+TrackId TrackTracker::InternThreadTrack(UniqueTid utid, const TrackName& name) {
+ Dimensions dims = SingleDimension(utid_id_, Variadic::Integer(utid));
+
+ auto* it = tracks_.Find({tracks::thread, dims});
+ if (it)
+ return *it;
+ TrackId track_id = CreateThreadTrack(tracks::thread, utid, name);
+ tracks_[{tracks::thread, dims}] = track_id;
+ return track_id;
+}
+
+TrackId TrackTracker::LegacyInternThreadCounterTrack(StringId name,
+ UniqueTid utid) {
+ TrackMapKey key;
+ key.classification = tracks::unknown;
+
+ DimensionsBuilder dims_builder = CreateDimensionsBuilder();
+ dims_builder.AppendUtid(utid);
+ dims_builder.AppendName(name);
+ key.dimensions = std::move(dims_builder).Build();
+
+ auto* it = tracks_.Find(key);
+ if (it) {
+ return *it;
}
- tables::IrqCounterTrackTable::Row row(
- context_->storage->InternString(GetTrackName(type)));
- row.irq = irq;
+ TrackId track_id =
+ CreateThreadCounterTrack(tracks::unknown, utid, LegacyStringIdName{name});
+ tracks_[key] = track_id;
+ return track_id;
+}
+
+TrackId TrackTracker::InternCpuTrack(tracks::TrackClassification classification,
+ uint32_t cpu,
+ const TrackName& name) {
+ auto ucpu = context_->cpu_tracker->GetOrCreateCpu(cpu);
+ Dimensions dims_id = SingleDimension(ucpu_id_, Variadic::Integer(ucpu.value));
+
+ auto* it = tracks_.Find({classification, dims_id});
+ if (it) {
+ return *it;
+ }
+
+ tables::CpuTrackTable::Row row(StringIdFromTrackName(classification, name));
+ row.ucpu = ucpu;
row.machine_id = context_->machine_id();
- row.classification = context_->storage->InternString(
- std::string("irq_counter:" + GetClassification(type)).c_str());
+ row.classification =
+ context_->storage->InternString(tracks::ToString(classification));
+ row.dimension_arg_set_id = dims_id.arg_set_id;
+
+ TrackId track_id =
+ context_->storage->mutable_cpu_track_table()->Insert(row).id;
+ tracks_[{classification, dims_id}] = track_id;
+ return track_id;
+}
+
+TrackId TrackTracker::InternGlobalTrack(
+ tracks::TrackClassification classification,
+ const TrackName& name,
+ const SetArgsCallback& callback) {
+ return InternTrack(classification, std::nullopt, name, callback);
+}
+
+TrackId TrackTracker::LegacyInternGpuTrack(
+ const tables::GpuTrackTable::Row& row) {
+ DimensionsBuilder dims_builder = CreateDimensionsBuilder();
+ dims_builder.AppendGpu(row.context_id.value_or(0));
+ if (row.scope != kNullStringId) {
+ dims_builder.AppendDimension(scope_id_, Variadic::String(row.scope));
+ }
+ dims_builder.AppendName(row.name);
+ Dimensions dims_id = std::move(dims_builder).Build();
+
+ TrackMapKey key;
+ key.classification = tracks::unknown;
+ key.dimensions = dims_id;
+
+ auto* it = tracks_.Find(key);
+ if (it)
+ return *it;
+
+ auto row_copy = row;
+ row_copy.classification =
+ context_->storage->InternString(tracks::ToString(tracks::unknown));
+ row_copy.dimension_arg_set_id = dims_id.arg_set_id;
+ row_copy.machine_id = context_->machine_id();
+
+ TrackId track_id =
+ context_->storage->mutable_gpu_track_table()->Insert(row_copy).id;
+ tracks_[key] = track_id;
+ return track_id;
+}
+
+TrackId TrackTracker::LegacyInternGlobalCounterTrack(TrackTracker::Group group,
+ StringId name,
+ SetArgsCallback callback,
+ StringId unit,
+ StringId description) {
+ TrackMapKey key;
+ key.classification = tracks::unknown;
+ key.dimensions = SingleDimension(name_id_, Variadic::String(name));
+
+ auto* it = tracks_.Find(key);
+ if (it) {
+ return *it;
+ }
+
+ tables::CounterTrackTable::Row row(name);
+ row.parent_id = InternTrackForGroup(group);
+ row.unit = unit;
+ row.description = description;
+ row.machine_id = context_->machine_id();
+ row.classification =
+ context_->storage->InternString(tracks::ToString(tracks::unknown));
TrackId track =
- context_->storage->mutable_irq_counter_track_table()->Insert(row).id;
- irq_counter_tracks_[std::make_pair(type, irq)] = track;
+ context_->storage->mutable_counter_track_table()->Insert(row).id;
+ tracks_[key] = track;
+
+ if (callback) {
+ auto inserter = context_->args_tracker->AddArgsTo(track);
+ callback(inserter);
+ }
+
return track;
}
-TrackId TrackTracker::InternSoftirqCounterTrack(SoftIrqCounterTrackType type,
- int32_t softirq) {
- auto it = softirq_counter_tracks_.find(std::make_pair(type, softirq));
- if (it != softirq_counter_tracks_.end()) {
- return it->second;
+TrackId TrackTracker::InternCpuCounterTrack(
+ tracks::TrackClassification classification,
+ uint32_t cpu,
+ const TrackName& name) {
+ auto ucpu = context_->cpu_tracker->GetOrCreateCpu(cpu);
+ StringId name_id = StringIdFromTrackName(classification, name);
+
+ TrackMapKey key;
+ key.classification = classification;
+
+ DimensionsBuilder dims_builder = CreateDimensionsBuilder();
+ dims_builder.AppendUcpu(ucpu);
+ dims_builder.AppendName(name_id);
+ key.dimensions = std::move(dims_builder).Build();
+
+ auto* it = tracks_.Find(key);
+ if (it) {
+ return *it;
}
- tables::SoftirqCounterTrackTable::Row row(
- context_->storage->InternString(GetTrackName(type)));
- row.softirq = softirq;
+ tables::CpuCounterTrackTable::Row row(name_id);
+ row.ucpu = ucpu;
row.machine_id = context_->machine_id();
- row.classification = context_->storage->InternString(
- std::string("softirq_counter:" + GetClassification(type)).c_str());
+ row.classification =
+ context_->storage->InternString(tracks::ToString(classification));
+ row.dimension_arg_set_id = key.dimensions->arg_set_id;
- TrackId track =
- context_->storage->mutable_softirq_counter_track_table()->Insert(row).id;
- softirq_counter_tracks_[std::make_pair(type, softirq)] = track;
- return track;
+ TrackId track_id =
+ context_->storage->mutable_cpu_counter_track_table()->Insert(row).id;
+ tracks_[key] = track_id;
+ return track_id;
}
-TrackId TrackTracker::InternGpuCounterTrack(GpuCounterTrackType type,
- uint32_t gpu_id) {
- StringId name = context_->storage->InternString(GetTrackName(type));
- auto it = gpu_counter_tracks_.find(std::make_pair(type, gpu_id));
- if (it != gpu_counter_tracks_.end()) {
- return it->second;
+TrackId TrackTracker::LegacyInternCpuIdleStateTrack(uint32_t cpu,
+ StringId state) {
+ auto ucpu = context_->cpu_tracker->GetOrCreateCpu(cpu);
+ DimensionsBuilder dims_builder = CreateDimensionsBuilder();
+ dims_builder.AppendDimension(
+ context_->storage->InternString("cpu_idle_state"),
+ Variadic::String(state));
+ dims_builder.AppendUcpu(ucpu);
+ Dimensions dims_id = std::move(dims_builder).Build();
+
+ tracks::TrackClassification classification = tracks::cpu_idle_state;
+
+ auto* it = tracks_.Find({classification, dims_id});
+ if (it) {
+ return *it;
}
- tables::GpuCounterTrackTable::Row row;
- row.name = name;
+
+ std::string name =
+ "cpuidle." + context_->storage->GetString(state).ToStdString();
+
+ tables::CpuCounterTrackTable::Row row(
+ context_->storage->InternString(name.c_str()));
+ row.ucpu = ucpu;
+ row.machine_id = context_->machine_id();
+ row.classification =
+ context_->storage->InternString(tracks::ToString(classification));
+ row.dimension_arg_set_id = dims_id.arg_set_id;
+
+ TrackId track_id =
+ context_->storage->mutable_cpu_counter_track_table()->Insert(row).id;
+ tracks_[{classification, dims_id}] = track_id;
+ return track_id;
+}
+
+TrackId TrackTracker::InternGpuCounterTrack(
+ tracks::TrackClassification classification,
+ uint32_t gpu_id,
+ const TrackName& name) {
+ Dimensions dims_id = SingleDimension(gpu_id_, Variadic::Integer(gpu_id));
+
+ auto* it = tracks_.Find({classification, dims_id});
+ if (it) {
+ return *it;
+ }
+
+ tables::GpuCounterTrackTable::Row row(
+ StringIdFromTrackName(classification, name));
row.gpu_id = gpu_id;
row.machine_id = context_->machine_id();
- row.classification = row.classification = context_->storage->InternString(
- std::string("gpu_counter:" + GetClassification(type)).c_str());
+ row.dimension_arg_set_id = dims_id.arg_set_id;
+ row.classification =
+ context_->storage->InternString(tracks::ToString(classification));
- TrackId track =
+ TrackId track_id =
context_->storage->mutable_gpu_counter_track_table()->Insert(row).id;
- gpu_counter_tracks_[std::make_pair(type, gpu_id)] = track;
- return track;
+
+ tracks_[{classification, dims_id}] = track_id;
+ return track_id;
}
-TrackId TrackTracker::InternEnergyCounterTrack(StringId name,
- int32_t consumer_id,
- StringId consumer_type,
- int32_t ordinal) {
- auto it = energy_counter_tracks_.find(std::make_pair(name, consumer_id));
- if (it != energy_counter_tracks_.end()) {
- return it->second;
- }
- tables::EnergyCounterTrackTable::Row row(name);
- row.consumer_id = consumer_id;
- row.consumer_type = consumer_type;
- row.ordinal = ordinal;
- row.machine_id = context_->machine_id();
- TrackId track =
- context_->storage->mutable_energy_counter_track_table()->Insert(row).id;
- energy_counter_tracks_[std::make_pair(name, consumer_id)] = track;
- return track;
-}
-
-TrackId TrackTracker::InternEnergyPerUidCounterTrack(StringId name,
- int32_t consumer_id,
- int32_t uid) {
- auto it = energy_per_uid_counter_tracks_.find(std::make_pair(name, uid));
- if (it != energy_per_uid_counter_tracks_.end()) {
- return it->second;
- }
-
- tables::EnergyPerUidCounterTrackTable::Row row(name);
- row.consumer_id = consumer_id;
- row.uid = uid;
- row.machine_id = context_->machine_id();
- TrackId track =
- context_->storage->mutable_energy_per_uid_counter_track_table()
- ->Insert(row)
- .id;
- energy_per_uid_counter_tracks_[std::make_pair(name, uid)] = track;
- return track;
-}
-
-TrackId TrackTracker::InternLinuxDeviceTrack(StringId name) {
- if (auto it = linux_device_tracks_.find(name);
- it != linux_device_tracks_.end()) {
- return it->second;
- }
-
- tables::LinuxDeviceTrackTable::Row row(name);
- TrackId track =
- context_->storage->mutable_linux_device_track_table()->Insert(row).id;
- linux_device_tracks_[name] = track;
- return track;
-}
-
-TrackId TrackTracker::CreateGpuCounterTrack(StringId name,
- uint32_t gpu_id,
- StringId description,
- StringId unit) {
+TrackId TrackTracker::LegacyCreateGpuCounterTrack(StringId name,
+ uint32_t gpu_id,
+ StringId description,
+ StringId unit) {
tables::GpuCounterTrackTable::Row row(name);
row.gpu_id = gpu_id;
row.description = description;
row.unit = unit;
row.machine_id = context_->machine_id();
+ row.classification =
+ context_->storage->InternString(tracks::ToString(tracks::unknown));
+ row.dimension_arg_set_id =
+ SingleDimension(gpu_id_, Variadic::Integer(gpu_id)).arg_set_id;
return context_->storage->mutable_gpu_counter_track_table()->Insert(row).id;
}
-TrackId TrackTracker::CreatePerfCounterTrack(
+TrackId TrackTracker::LegacyCreatePerfCounterTrack(
StringId name,
tables::PerfSessionTable::Id perf_session_id,
uint32_t cpu,
bool is_timebase) {
+ auto ucpu = context_->cpu_tracker->GetOrCreateCpu(cpu);
+ DimensionsBuilder dims_builder = CreateDimensionsBuilder();
+ dims_builder.AppendUcpu(ucpu);
+ dims_builder.AppendDimension(
+ context_->storage->InternString("perf_session_id"),
+ Variadic::Integer(perf_session_id.value));
+ Dimensions dims_id = std::move(dims_builder).Build();
+
tables::PerfCounterTrackTable::Row row(name);
row.perf_session_id = perf_session_id;
row.cpu = cpu;
row.is_timebase = is_timebase;
+ row.dimension_arg_set_id = dims_id.arg_set_id;
+ row.classification =
+ context_->storage->InternString(tracks::ToString(tracks::unknown));
row.machine_id = context_->machine_id();
return context_->storage->mutable_perf_counter_track_table()->Insert(row).id;
}
@@ -592,13 +572,87 @@
return *group_id;
}
- StringId id = context_->storage->InternString(GetNameForGroup(group));
- tables::TrackTable::Row row{id};
- row.machine_id = context_->machine_id();
- TrackId track_id = context_->storage->mutable_track_table()->Insert(row).id;
+ StringId name_id = context_->storage->InternString(GetNameForGroup(group));
+ TrackId track_id =
+ InternTrack(tracks::unknown, std::nullopt, LegacyStringIdName{name_id});
group_track_ids_[group_idx] = track_id;
return track_id;
}
-} // namespace trace_processor
-} // namespace perfetto
+TrackId TrackTracker::LegacyInternLegacyChromeAsyncTrack(
+ StringId raw_name,
+ uint32_t upid,
+ int64_t trace_id,
+ bool trace_id_is_process_scoped,
+ StringId source_scope) {
+ DimensionsBuilder dims_builder = CreateDimensionsBuilder();
+ dims_builder.AppendDimension(scope_id_, Variadic::String(source_scope));
+ if (trace_id_is_process_scoped) {
+ dims_builder.AppendUpid(upid);
+ }
+ dims_builder.AppendDimension(cookie_id_, Variadic::Integer(trace_id));
+
+ const StringId name =
+ context_->process_track_translation_table->TranslateName(raw_name);
+
+ TrackMapKey key;
+ key.classification = tracks::unknown;
+ key.dimensions = std::move(dims_builder).Build();
+
+ auto* it = tracks_.Find(key);
+ if (it) {
+ if (name != kNullStringId) {
+ // The track may have been created for an end event without name. In
+ // that case, update it with this event's name.
+ auto& tracks = *context_->storage->mutable_track_table();
+ auto rr = *tracks.FindById(*it);
+ if (rr.name() == kNullStringId) {
+ rr.set_name(name);
+ }
+ }
+ return *it;
+ }
+
+ // Legacy async tracks are always drawn in the context of a process, even if
+ // the ID's scope is global.
+ tables::ProcessTrackTable::Row track(name);
+ track.upid = upid;
+ track.classification =
+ context_->storage->InternString(tracks::ToString(tracks::unknown));
+ track.dimension_arg_set_id = key.dimensions->arg_set_id;
+ track.machine_id = context_->machine_id();
+
+ TrackId id =
+ context_->storage->mutable_process_track_table()->Insert(track).id;
+ tracks_[key] = id;
+
+ context_->args_tracker->AddArgsTo(id)
+ .AddArg(source_key_, Variadic::String(chrome_source_))
+ .AddArg(trace_id_key_, Variadic::Integer(trace_id))
+ .AddArg(trace_id_is_process_scoped_key_,
+ Variadic::Boolean(trace_id_is_process_scoped))
+ .AddArg(source_scope_key_, Variadic::String(source_scope));
+
+ return id;
+}
+
+StringId TrackTracker::StringIdFromTrackName(
+ tracks::TrackClassification classification,
+ const TrackTracker::TrackName& name) {
+ switch (name.index()) {
+ case base::variant_index<TrackName, AutoName>():
+ return kNullStringId;
+ case base::variant_index<TrackName, LegacyStringIdName>():
+ PERFETTO_DCHECK(IsLegacyStringIdNameAllowed(classification));
+ return std::get<LegacyStringIdName>(name).id;
+ case base::variant_index<TrackName, LegacyCharArrayName>():
+ PERFETTO_DCHECK(IsLegacyCharArrayNameAllowed(classification));
+ return context_->storage->InternString(
+ std::get<LegacyCharArrayName>(name).name);
+ case base::variant_index<TrackName, FromTraceName>():
+ return std::get<FromTraceName>(name).id;
+ }
+ PERFETTO_FATAL("For GCC");
+}
+
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/common/track_tracker.h b/src/trace_processor/importers/common/track_tracker.h
index 4118b07..d4d14f0 100644
--- a/src/trace_processor/importers/common/track_tracker.h
+++ b/src/trace_processor/importers/common/track_tracker.h
@@ -17,23 +17,105 @@
#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_TRACK_TRACKER_H_
#define SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_TRACK_TRACKER_H_
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <functional>
+#include <limits>
#include <optional>
-#include <string>
+#include <tuple>
+#include <variant>
+#include "perfetto/ext/base/flat_hash_map.h"
+#include "perfetto/ext/base/hash.h"
#include "perfetto/ext/base/string_utils.h"
-#include "perfetto/ext/base/string_view.h"
#include "src/trace_processor/importers/common/args_tracker.h"
-#include "src/trace_processor/importers/common/cpu_tracker.h"
+#include "src/trace_processor/importers/common/global_args_tracker.h"
+#include "src/trace_processor/importers/common/tracks.h"
#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/tables/metadata_tables_py.h"
#include "src/trace_processor/tables/profiler_tables_py.h"
+#include "src/trace_processor/tables/track_tables_py.h"
#include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/types/variadic.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
// Tracks and stores tracks based on track types, ids and scopes.
class TrackTracker {
public:
+ using SetArgsCallback = std::function<void(ArgsTracker::BoundInserter&)>;
+
+ // Dimensions of the data in a track. Used as an argument in
+ // `TrackTracker::InternTrack()`. Use `TrackTracker::DimensionsBuilder` to
+ // create.
+ struct Dimensions {
+ ArgSetId arg_set_id;
+
+ bool operator==(const Dimensions& o) const {
+ return arg_set_id == o.arg_set_id;
+ }
+ };
+
+ // Used to create `Dimensions` required to intern a new track.
+ class DimensionsBuilder {
+ public:
+ explicit DimensionsBuilder(TrackTracker* tt) : tt_(tt) {}
+
+ // Append CPU dimension of a track.
+ void AppendUcpu(tables::CpuTable::Id ucpu) {
+ AppendDimension(tt_->ucpu_id_, Variadic::Integer(ucpu.value));
+ }
+
+ // Append Utid (unique tid) dimension of a track.
+ void AppendUtid(UniqueTid utid) {
+ AppendDimension(tt_->utid_id_, Variadic::Integer(utid));
+ }
+
+ // Append Upid (unique pid) dimension of a track.
+ void AppendUpid(UniquePid upid) {
+ AppendDimension(tt_->upid_id_, Variadic::Integer(upid));
+ }
+
+ // Append gpu id dimension of a track.
+ void AppendGpu(int64_t gpu) {
+ AppendDimension(tt_->gpu_id_, Variadic::Integer(gpu));
+ }
+
+ // Append Uid (user id) dimension of a track.
+ void AppendUid(int64_t uid) {
+ AppendDimension(tt_->uid_id_, Variadic::Integer(uid));
+ }
+
+ // Append name dimension of a track. Only use in cases where name is a
+ // dimension, it is not a way to force the name of the track in a table.
+ void AppendName(StringId name) {
+ AppendDimension(tt_->name_id_, Variadic::String(name));
+ }
+
+ // Append custom dimension. Only use if none of the other Append functions
+ // are suitable.
+ void AppendDimension(StringId key, const Variadic& val) {
+ GlobalArgsTracker::CompactArg& arg = args_[count_args++];
+ arg.flat_key = key;
+ arg.key = key;
+ arg.value = val;
+ }
+
+ // Build to fetch the `Dimensions` value of the Appended dimensions. Pushes
+ // the dimensions into args table. Use the result in
+ // `TrackTracker::InternTrack`.
+ Dimensions Build() && {
+ return Dimensions{tt_->context_->global_args_tracker->AddArgSet(
+ args_.data(), 0, count_args)};
+ }
+
+ private:
+ TrackTracker* tt_;
+ std::array<GlobalArgsTracker::CompactArg, 64> args_;
+ uint32_t count_args = 0;
+ };
+
// Enum which groups global tracks to avoid an explosion of tracks at the top
// level.
// Try and keep members of this enum high level as every entry here
@@ -53,362 +135,233 @@
kSizeSentinel,
};
- // Classifications of global tracks (one per trace).
- enum class GlobalTrackType {
- kTrigger,
- kInterconnect,
- kChromeLegacyGlobalInstant
+ // Indicates that the track name will be automatically generated by the trace
+ // processor.
+ // All tracks *MUST* use this option unless it is explicitly approved by a
+ // trace processor maintainer.
+ struct AutoName {};
+ // Indicates that the track name comes from the trace directly with no
+ // modification.
+ // *MUST NOT* be used without explicit appoval from a trace processor
+ // maintainer.
+ struct FromTraceName {
+ StringId id;
};
-
- // Classifications of CPU tracks.
- enum class CpuTrackType {
- kIrqCpu,
- kSortIrqCpu,
- kNapiGroCpu,
- kMaliIrqCpu,
- kMinFreqCpu,
- kMaxFreqCpu,
- kFuncgraphCpu,
- kPkvmHypervisor,
+ // Indicates that the track name is synthesied in trace processor as a
+ // StringId and works this way due to legacy reasons.
+ //
+ // Tracks *MUST NOT* use this method: this only exists for legacy tracks which
+ // we named before the introduction of classification/dimension system.
+ struct LegacyStringIdName {
+ StringId id;
};
-
- // Classifications of CPU counter tracks.
- enum class CpuCounterTrackType {
- // Frequency CPU counters
- kFrequency,
- kFreqThrottle,
- kMaxFreqLimit,
- kMinFreqLimit,
-
- kIdle,
- kIdleState,
- kUtilization,
- kCapacity,
- kNrRunning,
-
- // Time CPU spent in state.
- kUserTime,
- kNiceUserTime,
- kSystemModeTime,
- kIoWaitTime,
- kIrqTime,
- kSoftIrqTime,
- kIdleTime,
+ // Indicates that the track name is synthesied in trace processor as a
+ // StackString and works this way due to legacy reasons.
+ //
+ // Tracks *MUST NOT* use this method: this only exists for legacy tracks which
+ // we named before the introduction of classification/dimension system.
+ struct LegacyCharArrayName {
+ template <size_t N>
+ explicit LegacyCharArrayName(const char (&_name)[N]) {
+ static_assert(N > 0 && N <= 512);
+ base::StringCopy(name, _name, N);
+ }
+ template <size_t N>
+ explicit LegacyCharArrayName(const base::StackString<N>& _name) {
+ static_assert(N > 0 && N <= 512);
+ base::StringCopy(name, _name.c_str(), N);
+ }
+ char name[512];
};
-
- // Classifications of GPU counter tracks.
- enum class GpuCounterTrackType { kFrequency };
-
- enum class IrqCounterTrackType { kCount };
-
- enum class SoftIrqCounterTrackType { kCount };
-
- using SetArgsCallback = std::function<void(ArgsTracker::BoundInserter&)>;
+ using TrackName = std::
+ variant<AutoName, FromTraceName, LegacyStringIdName, LegacyCharArrayName>;
explicit TrackTracker(TraceProcessorContext*);
- // Interns a unique track into the storage.
- TrackId InternGlobalTrack(GlobalTrackType);
+ DimensionsBuilder CreateDimensionsBuilder() {
+ return DimensionsBuilder(this);
+ }
- // Interns a global track keyed by track type + CPU into the storage.
- TrackId InternCpuTrack(CpuTrackType, uint32_t cpu);
+ // Interns track into TrackTable. If the track created with below arguments
+ // already exists, returns the TrackTable::Id of the track.
+ //
+ // `name` is the display name of the track in trace processor and should
+ // always be `AutoName()` unless approved by a trace processor maintainer.
+ TrackId InternTrack(tracks::TrackClassification,
+ std::optional<Dimensions>,
+ const TrackName& name = AutoName(),
+ const SetArgsCallback& callback = {});
+
+ // Interns a track with the given classification and one dimension into the
+ // `track` table. This is useful when interning global tracks which have a
+ // single uncommon dimension attached to them.
+ TrackId InternSingleDimensionTrack(tracks::TrackClassification classification,
+ StringId key,
+ const Variadic& value,
+ const TrackName& name = AutoName(),
+ const SetArgsCallback& callback = {}) {
+ return InternTrack(classification, SingleDimension(key, value), name,
+ callback);
+ }
+
+ // Interns counter track into TrackTable. If the track created with below
+ // arguments already exists, returns the TrackTable::Id of the track.
+ TrackId InternCounterTrack(tracks::TrackClassification,
+ std::optional<Dimensions>,
+ const TrackName& = AutoName());
+
+ // Interns a unique track into the storage.
+ TrackId InternGlobalTrack(tracks::TrackClassification,
+ const TrackName& = AutoName(),
+ const SetArgsCallback& callback = {});
// Interns a thread track into the storage.
- TrackId InternThreadTrack(UniqueTid utid);
+ TrackId InternThreadTrack(UniqueTid, const TrackName& = AutoName());
// Interns a process track into the storage.
- TrackId InternProcessTrack(UniquePid upid);
+ TrackId InternProcessTrack(tracks::TrackClassification,
+ UniquePid,
+ const TrackName& = AutoName());
- // Interns a given GPU track into the storage.
- TrackId InternGpuTrack(const tables::GpuTrackTable::Row& row);
-
- // Interns a GPU work period track into the storage.
- TrackId InternGpuWorkPeriodTrack(
- const tables::GpuWorkPeriodTrackTable::Row& row);
-
- // Interns a legacy Chrome async event track into the storage.
- TrackId InternLegacyChromeAsyncTrack(StringId name,
- uint32_t upid,
- int64_t trace_id,
- bool trace_id_is_process_scoped,
- StringId source_scope);
-
- // Interns a track for legacy Chrome process-scoped instant events into the
- // storage.
- TrackId InternLegacyChromeProcessInstantTrack(UniquePid upid);
-
- // Interns a global counter track into the storage.
- TrackId InternGlobalCounterTrack(Group group,
- StringId name,
- SetArgsCallback = {},
- StringId unit = kNullStringId,
- StringId description = kNullStringId);
+ // Interns a global track keyed by track type + CPU into the storage.
+ TrackId InternCpuTrack(tracks::TrackClassification,
+ uint32_t cpu,
+ const TrackName& = AutoName());
// Interns a counter track associated with a cpu into the storage.
- TrackId InternCpuCounterTrack(CpuCounterTrackType, uint32_t cpu);
+ TrackId InternCpuCounterTrack(tracks::TrackClassification,
+ uint32_t cpu,
+ const TrackName& = AutoName());
// Interns a counter track associated with a GPU into the storage.
- TrackId InternGpuCounterTrack(GpuCounterTrackType, uint32_t gpu_id);
-
- // Interns a counter track associated with a cpu into the storage.
- TrackId InternCpuIdleStateTrack(uint32_t cpu, StringId state);
-
- // Interns a counter track associated with a thread into the storage.
- TrackId InternThreadCounterTrack(StringId name, UniqueTid utid);
-
- // Interns a counter track associated with a process into the storage.
- TrackId InternProcessCounterTrack(StringId name,
- UniquePid upid,
- StringId unit = kNullStringId,
- StringId description = kNullStringId);
-
- // Interns a counter track associated with an irq into the storage.
- TrackId InternIrqCounterTrack(IrqCounterTrackType, int32_t irq);
-
- // Interns a counter track associated with an softirq into the storage.
- TrackId InternSoftirqCounterTrack(SoftIrqCounterTrackType, int32_t softirq);
-
- // Interns energy counter track associated with a
- // Energy breakdown into the storage.
- TrackId InternEnergyCounterTrack(StringId name,
- int32_t consumer_id,
- StringId consumer_type,
- int32_t ordinal);
-
- // Interns a per process energy consumer counter track associated with a
- // Energy Uid into the storage.
- TrackId InternEnergyPerUidCounterTrack(StringId name,
- int32_t consumer_id,
- int32_t uid);
-
- // Interns a track associated with a Linux device (where a Linux device
- // implies a kernel-level device managed by a Linux driver).
- TrackId InternLinuxDeviceTrack(StringId name);
-
- // Creates a counter track associated with a GPU into the storage.
- TrackId CreateGpuCounterTrack(StringId name,
+ TrackId InternGpuCounterTrack(tracks::TrackClassification,
uint32_t gpu_id,
- StringId description = StringId::Null(),
- StringId unit = StringId::Null());
+ const TrackName& = AutoName());
- // Creates a counter track for values within perf samples.
- // The tracks themselves are managed by PerfSampleTracker.
- TrackId CreatePerfCounterTrack(StringId name,
- tables::PerfSessionTable::Id perf_session_id,
- uint32_t cpu,
- bool is_timebase);
+ // Everything below this point are legacy functions and should no longer be
+ // used.
- // Interns a Fuchsia async track into the storage.
- TrackId InternFuchsiaAsyncTrack(StringId name,
- uint32_t upid,
- int64_t correlation_id);
+ TrackId LegacyInternLegacyChromeAsyncTrack(StringId name,
+ uint32_t upid,
+ int64_t trace_id,
+ bool trace_id_is_process_scoped,
+ StringId source_scope);
- // NOTE:
- // The below method should only be called by AsyncTrackSetTracker
+ TrackId LegacyInternCpuIdleStateTrack(uint32_t cpu, StringId state);
- // Creates and inserts a global async track into the storage.
- TrackId CreateGlobalAsyncTrack(StringId name, StringId source);
+ TrackId LegacyCreateGpuCounterTrack(StringId name,
+ uint32_t gpu_id,
+ StringId description = StringId::Null(),
+ StringId unit = StringId::Null());
- // Creates and inserts a Android async track into the storage.
- TrackId CreateProcessAsyncTrack(StringId name,
- UniquePid upid,
- StringId source);
+ TrackId LegacyCreatePerfCounterTrack(StringId name,
+ tables::PerfSessionTable::Id,
+ uint32_t cpu,
+ bool is_timebase);
+
+ TrackId LegacyInternThreadCounterTrack(StringId name, UniqueTid);
+
+ TrackId LegacyInternGpuTrack(const tables::GpuTrackTable::Row&);
+
+ TrackId LegacyInternProcessCounterTrack(StringId name,
+ UniquePid,
+ StringId unit = kNullStringId,
+ StringId description = kNullStringId);
+
+ TrackId LegacyInternGlobalCounterTrack(Group,
+ StringId name,
+ SetArgsCallback = {},
+ StringId unit = kNullStringId,
+ StringId description = kNullStringId);
private:
- struct GpuTrackTuple {
- StringId track_name;
- StringId scope;
- int64_t context_id;
+ friend class AsyncTrackSetTracker;
+ friend class TrackEventTracker;
- friend bool operator<(const GpuTrackTuple& l, const GpuTrackTuple& r) {
- return std::tie(l.track_name, l.scope, l.context_id) <
- std::tie(r.track_name, r.scope, r.context_id);
+ struct TrackMapKey {
+ tracks::TrackClassification classification;
+ std::optional<Dimensions> dimensions;
+
+ bool operator==(const TrackMapKey& k) const {
+ return std::tie(classification, dimensions) ==
+ std::tie(k.classification, k.dimensions);
}
};
- struct GpuWorkPeriodTrackTuple {
- StringId track_name;
- uint32_t gpu_id;
- int32_t uid;
- friend bool operator<(const GpuWorkPeriodTrackTuple& l,
- const GpuWorkPeriodTrackTuple& r) {
- return std::tie(l.track_name, l.gpu_id, l.uid) <
- std::tie(r.track_name, r.gpu_id, r.uid);
+ struct MapHasher {
+ size_t operator()(const TrackMapKey& l) const {
+ perfetto::base::Hasher hasher;
+ hasher.Update(static_cast<uint32_t>(l.classification));
+ hasher.Update(l.dimensions ? l.dimensions->arg_set_id : -1ll);
+ return hasher.digest();
}
};
- struct ChromeTrackTuple {
- std::optional<int64_t> upid;
- int64_t trace_id = 0;
- StringId source_scope = StringId::Null();
- friend bool operator<(const ChromeTrackTuple& l,
- const ChromeTrackTuple& r) {
- return std::tie(l.trace_id, l.upid, l.source_scope) <
- std::tie(r.trace_id, r.upid, r.source_scope);
- }
- };
- struct CpuCounterTrackTuple {
- // Required fields.
- CpuCounterTrackType type;
- uint32_t cpu;
- StringId name = StringPool::Id::Null();
-
- // Optional properties of the track.
- uint64_t optional_hash = 0;
-
- friend bool operator<(const CpuCounterTrackTuple& l,
- const CpuCounterTrackTuple& r) {
- return std::tie(l.type, l.cpu, l.optional_hash) <
- std::tie(r.type, r.cpu, r.optional_hash);
- }
- };
static constexpr size_t kGroupCount =
static_cast<uint32_t>(Group::kSizeSentinel);
- std::string GetClassification(TrackTracker::GlobalTrackType type) {
- switch (type) {
- case TrackTracker::GlobalTrackType::kTrigger:
- return "triggers";
- case TrackTracker::GlobalTrackType::kInterconnect:
- return "interconnect_events";
- case TrackTracker::GlobalTrackType::kChromeLegacyGlobalInstant:
- return "legacy_chrome_global_instants";
- }
- PERFETTO_FATAL("For GCC");
- }
+ TrackId CreateTrack(tracks::TrackClassification,
+ std::optional<Dimensions>,
+ const TrackName&);
- std::string GetClassification(TrackTracker::CpuTrackType type) {
- switch (type) {
- case CpuTrackType::kIrqCpu:
- return "irq";
- case CpuTrackType::kSortIrqCpu:
- return "soft_irq";
- case CpuTrackType::kNapiGroCpu:
- return "napi_gro";
- case CpuTrackType::kMaliIrqCpu:
- return "mali_irq";
- case CpuTrackType::kMinFreqCpu:
- return "min_freq";
- case CpuTrackType::kMaxFreqCpu:
- return "max_freq";
- case CpuTrackType::kFuncgraphCpu:
- return "funcgraph";
- case CpuTrackType::kPkvmHypervisor:
- return "pkvm_hypervisor";
- }
- PERFETTO_FATAL("For GCC");
- }
+ TrackId CreateCounterTrack(tracks::TrackClassification,
+ std::optional<Dimensions>,
+ const TrackName&);
- std::string GetClassification(TrackTracker::CpuCounterTrackType type) {
- switch (type) {
- case CpuCounterTrackType::kFrequency:
- return "frequency";
- case CpuCounterTrackType::kFreqThrottle:
- return "frequency_throttle";
- case CpuCounterTrackType::kMaxFreqLimit:
- return "max_frequency_limit";
- case CpuCounterTrackType::kMinFreqLimit:
- return "min_frequency_limit";
- case CpuCounterTrackType::kIdle:
- return "idle";
- case CpuCounterTrackType::kIdleState:
- return "idle_state";
- case CpuCounterTrackType::kIdleTime:
- return "idle_time";
- case CpuCounterTrackType::kUtilization:
- return "utilization";
- case CpuCounterTrackType::kCapacity:
- return "capacity";
- case CpuCounterTrackType::kNrRunning:
- return "nr_running";
- case CpuCounterTrackType::kUserTime:
- return "user_time";
- case CpuCounterTrackType::kNiceUserTime:
- return "nice_user_time";
- case CpuCounterTrackType::kSystemModeTime:
- return "system_mode_time";
- case CpuCounterTrackType::kIoWaitTime:
- return "io_wait_time";
- case CpuCounterTrackType::kIrqTime:
- return "irq_time";
- case CpuCounterTrackType::kSoftIrqTime:
- return "soft_irq_time";
- }
- PERFETTO_FATAL("For GCC");
- }
+ TrackId CreateThreadTrack(tracks::TrackClassification,
+ UniqueTid,
+ const TrackName&);
- std::string GetClassification(TrackTracker::GpuCounterTrackType type) {
- switch (type) {
- case GpuCounterTrackType::kFrequency:
- return "frequency";
- }
- PERFETTO_FATAL("For GCC");
- }
+ TrackId CreateThreadCounterTrack(tracks::TrackClassification,
+ UniqueTid,
+ const TrackName&);
- std::string GetClassification(TrackTracker::IrqCounterTrackType type) {
- switch (type) {
- case IrqCounterTrackType::kCount:
- return "count";
- }
- PERFETTO_FATAL("For GCC");
- }
+ TrackId CreateProcessTrack(tracks::TrackClassification,
+ UniquePid,
+ std::optional<Dimensions>,
+ const TrackName&);
- std::string GetClassification(TrackTracker::SoftIrqCounterTrackType type) {
- switch (type) {
- case SoftIrqCounterTrackType::kCount:
- return "count";
- }
- PERFETTO_FATAL("For GCC");
- }
+ TrackId CreateProcessCounterTrack(tracks::TrackClassification,
+ UniquePid,
+ std::optional<Dimensions>,
+ const TrackName&);
TrackId InternTrackForGroup(Group);
- TrackId InternCpuCounterTrack(CpuCounterTrackTuple);
+
+ StringId StringIdFromTrackName(tracks::TrackClassification classification,
+ const TrackTracker::TrackName& name);
+
+ Dimensions SingleDimension(StringId key, const Variadic& val) {
+ std::array args{GlobalArgsTracker::CompactArg{key, key, val}};
+ return Dimensions{
+ context_->global_args_tracker->AddArgSet(args.data(), 0, 1)};
+ }
std::array<std::optional<TrackId>, kGroupCount> group_track_ids_;
- std::map<UniqueTid, TrackId> thread_tracks_;
- std::map<UniquePid, TrackId> process_tracks_;
- std::map<int64_t /* correlation_id */, TrackId> fuchsia_async_tracks_;
+ base::FlatHashMap<TrackMapKey, TrackId, MapHasher> tracks_;
- std::map<std::pair<StringId, uint32_t /* cpu */>, TrackId> cpu_tracks_;
+ const StringId source_key_;
+ const StringId trace_id_key_;
+ const StringId trace_id_is_process_scoped_key_;
+ const StringId source_scope_key_;
+ const StringId category_key_;
+ const StringId scope_id_;
+ const StringId cookie_id_;
- std::map<GpuTrackTuple, TrackId> gpu_tracks_;
- std::map<ChromeTrackTuple, TrackId> chrome_tracks_;
- std::map<UniquePid, TrackId> chrome_process_instant_tracks_;
- std::map<std::pair<StringId, int32_t /*uid*/>, TrackId> uid_tracks_;
- std::map<GpuWorkPeriodTrackTuple, TrackId> gpu_work_period_tracks_;
+ const StringId fuchsia_source_;
+ const StringId chrome_source_;
- std::map<StringId, TrackId> global_counter_tracks_by_name_;
- std::map<CpuCounterTrackTuple, TrackId> cpu_counter_tracks_;
- std::map<std::pair<GpuCounterTrackType, uint32_t>, TrackId>
- gpu_counter_tracks_;
- std::map<std::pair<StringId, UniqueTid>, TrackId> utid_counter_tracks_;
- std::map<std::pair<StringId, UniquePid>, TrackId> upid_counter_tracks_;
- std::map<std::pair<IrqCounterTrackType, int32_t>, TrackId>
- irq_counter_tracks_;
- std::map<std::pair<SoftIrqCounterTrackType, int32_t>, TrackId>
- softirq_counter_tracks_;
- std::map<std::pair<StringId, int32_t>, TrackId> energy_counter_tracks_;
- std::map<std::pair<StringId, int32_t>, TrackId> uid_counter_tracks_;
- std::map<std::pair<StringId, int32_t>, TrackId>
- energy_per_uid_counter_tracks_;
- std::map<StringId, TrackId> linux_device_tracks_;
-
- std::map<GlobalTrackType, TrackId> unique_tracks_;
-
- const StringId source_key_ = kNullStringId;
- const StringId trace_id_key_ = kNullStringId;
- const StringId trace_id_is_process_scoped_key_ = kNullStringId;
- const StringId source_scope_key_ = kNullStringId;
- const StringId category_key_ = kNullStringId;
-
- const StringId fuchsia_source_ = kNullStringId;
- const StringId chrome_source_ = kNullStringId;
+ const StringId utid_id_;
+ const StringId upid_id_;
+ const StringId ucpu_id_;
+ const StringId uid_id_;
+ const StringId gpu_id_;
+ const StringId name_id_;
TraceProcessorContext* const context_;
};
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
#endif // SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_TRACK_TRACKER_H_
diff --git a/src/trace_processor/importers/common/tracks.h b/src/trace_processor/importers/common/tracks.h
new file mode 100644
index 0000000..ce4fbcd
--- /dev/null
+++ b/src/trace_processor/importers/common/tracks.h
@@ -0,0 +1,82 @@
+/*
+ * 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.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_TRACKS_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_TRACKS_H_
+
+#include <array>
+#include <cstddef>
+
+namespace perfetto::trace_processor::tracks {
+
+// The classification of a track indicates the "type of data" the track
+// contains.
+//
+// Every track is uniquely identified by the the combination of the
+// classification and a set of dimensions: classifications allow identifying a
+// set of tracks with the same type of data within the whole universe of tracks
+// while dimensions allow distinguishing between different tracks in that set.
+#define PERFETTO_TP_TRACKS(F) \
+ F(android_energy_estimation_breakdown_per_uid) \
+ F(android_energy_estimation_breakdown) \
+ F(android_gpu_work_period) \
+ F(android_lmk) \
+ F(chrome_process_instant) \
+ F(cpu_capacity) \
+ F(cpu_frequency_throttle) \
+ F(cpu_frequency) \
+ F(cpu_funcgraph) \
+ F(cpu_idle_state) \
+ F(cpu_idle) \
+ F(cpu_irq) \
+ F(cpu_nr_running) \
+ F(cpu_mali_irq) \
+ F(cpu_max_frequency_limit) \
+ F(cpu_min_frequency_limit) \
+ F(cpu_napi_gro) \
+ F(cpu_softirq) \
+ F(cpu_stat) \
+ F(cpu_utilization) \
+ F(gpu_frequency) \
+ F(interconnect_events) \
+ F(irq_counter) \
+ F(legacy_chrome_global_instants) \
+ F(linux_device_frequency) \
+ F(linux_rpm) \
+ F(pixel_cpm_trace) \
+ F(pkvm_hypervisor) \
+ F(softirq_counter) \
+ F(thread) \
+ F(track_event) \
+ F(triggers) \
+ F(unknown)
+
+#define PERFETTO_TP_TRACKS_CLASSIFICATION_ENUM(name) name,
+enum TrackClassification : size_t {
+ PERFETTO_TP_TRACKS(PERFETTO_TP_TRACKS_CLASSIFICATION_ENUM)
+};
+
+#define PERFETTO_TP_TRACKS_CLASSIFICATION_STR(name) #name,
+constexpr std::array kTrackClassificationStr{
+ PERFETTO_TP_TRACKS(PERFETTO_TP_TRACKS_CLASSIFICATION_STR)};
+
+constexpr const char* ToString(TrackClassification c) {
+ return kTrackClassificationStr[c];
+}
+
+} // namespace perfetto::trace_processor::tracks
+
+#endif // SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_TRACKS_H_
diff --git a/src/trace_processor/importers/ftrace/BUILD.gn b/src/trace_processor/importers/ftrace/BUILD.gn
index 1cc2b0d..79544b1 100644
--- a/src/trace_processor/importers/ftrace/BUILD.gn
+++ b/src/trace_processor/importers/ftrace/BUILD.gn
@@ -74,6 +74,7 @@
"../../../protozero",
"../../sorter",
"../../storage",
+ "../../tables",
"../../types",
"../common",
"../common:parser_types",
diff --git a/src/trace_processor/importers/ftrace/drm_tracker.cc b/src/trace_processor/importers/ftrace/drm_tracker.cc
index aeb7de6..f23ca75 100644
--- a/src/trace_processor/importers/ftrace/drm_tracker.cc
+++ b/src/trace_processor/importers/ftrace/drm_tracker.cc
@@ -126,7 +126,7 @@
base::StackString<256> track_name("vblank-%d", crtc);
StringId track_name_id =
context_->storage->InternString(track_name.string_view());
- return context_->track_tracker->InternGpuTrack(
+ return context_->track_tracker->LegacyInternGpuTrack(
tables::GpuTrackTable::Row(track_name_id));
}
@@ -163,7 +163,7 @@
base::StackString<64> track_name("sched-%.*s", int(name.size()), name.data());
StringId track_name_id =
context_->storage->InternString(track_name.string_view());
- TrackId track_id = context_->track_tracker->InternGpuTrack(
+ TrackId track_id = context_->track_tracker->LegacyInternGpuTrack(
tables::GpuTrackTable::Row(track_name_id));
// no std::make_unique until C++14..
@@ -260,7 +260,7 @@
name.data(), context);
StringId track_name_id =
context_->storage->InternString(track_name.string_view());
- TrackId track_id = context_->track_tracker->InternGpuTrack(
+ TrackId track_id = context_->track_tracker->LegacyInternGpuTrack(
tables::GpuTrackTable::Row(track_name_id));
// no std::make_unique until C++14..
diff --git a/src/trace_processor/importers/ftrace/ftrace_descriptors.cc b/src/trace_processor/importers/ftrace/ftrace_descriptors.cc
index 21f6be4..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, 541> descriptors{{
+std::array<FtraceMessageDescriptor, 544> descriptors{{
{nullptr, 0, {}},
{nullptr, 0, {}},
{nullptr, 0, {}},
@@ -5985,6 +5985,37 @@
{"vruntime", ProtoSchemaType::kUint64},
},
},
+ {
+ "devfreq_frequency",
+ 5,
+ {
+ {},
+ {"dev_name", ProtoSchemaType::kString},
+ {"freq", ProtoSchemaType::kUint64},
+ {"prev_freq", ProtoSchemaType::kUint64},
+ {"busy_time", ProtoSchemaType::kUint64},
+ {"total_time", ProtoSchemaType::kUint64},
+ },
+ },
+ {
+ "kprobe_event",
+ 2,
+ {
+ {},
+ {"name", ProtoSchemaType::kString},
+ {"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 de63f54..59b2446 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.cc
@@ -15,15 +15,29 @@
*/
#include "src/trace_processor/importers/ftrace/ftrace_parser.h"
-#include <optional>
+#include <algorithm>
+#include <array>
+#include <cinttypes>
+#include <cstddef>
+#include <cstdint>
+#include <limits>
+#include <optional>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "perfetto/base/compiler.h"
#include "perfetto/base/logging.h"
#include "perfetto/base/status.h"
#include "perfetto/ext/base/string_utils.h"
#include "perfetto/ext/base/string_view.h"
+#include "perfetto/protozero/field.h"
#include "perfetto/protozero/proto_decoder.h"
-
+#include "perfetto/protozero/proto_utils.h"
+#include "perfetto/public/compiler.h"
#include "perfetto/trace_processor/basic_types.h"
+#include "perfetto/trace_processor/trace_blob_view.h"
#include "src/trace_processor/importers/common/args_tracker.h"
#include "src/trace_processor/importers/common/async_track_set_tracker.h"
#include "src/trace_processor/importers/common/cpu_tracker.h"
@@ -33,8 +47,11 @@
#include "src/trace_processor/importers/common/system_info_tracker.h"
#include "src/trace_processor/importers/common/thread_state_tracker.h"
#include "src/trace_processor/importers/common/track_tracker.h"
+#include "src/trace_processor/importers/common/tracks.h"
#include "src/trace_processor/importers/ftrace/binder_tracker.h"
+#include "src/trace_processor/importers/ftrace/ftrace_descriptors.h"
#include "src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.h"
+#include "src/trace_processor/importers/ftrace/pkvm_hyp_cpu_tracker.h"
#include "src/trace_processor/importers/ftrace/v4l2_tracker.h"
#include "src/trace_processor/importers/ftrace/virtio_video_tracker.h"
#include "src/trace_processor/importers/i2c/i2c_tracker.h"
@@ -46,15 +63,19 @@
#include "src/trace_processor/storage/trace_storage.h"
#include "src/trace_processor/types/softirq_action.h"
#include "src/trace_processor/types/tcp_state.h"
+#include "src/trace_processor/types/variadic.h"
+#include "src/trace_processor/types/version_number.h"
#include "protos/perfetto/common/gpu_counter_descriptor.pbzero.h"
#include "protos/perfetto/trace/ftrace/android_fs.pbzero.h"
#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"
+#include "protos/perfetto/trace/ftrace/devfreq.pbzero.h"
#include "protos/perfetto/trace/ftrace/dmabuf_heap.pbzero.h"
#include "protos/perfetto/trace/ftrace/dpu.pbzero.h"
#include "protos/perfetto/trace/ftrace/fastrpc.pbzero.h"
@@ -103,9 +124,12 @@
namespace {
+using protos::pbzero::perfetto_pbzero_enum_KprobeEvent::KprobeType;
using protozero::ConstBytes;
using protozero::ProtoDecoder;
+constexpr char kInternconnectTrackName[] = "Interconnect Events";
+
struct FtraceEventAndFieldId {
uint32_t event_id;
uint32_t field_id;
@@ -319,6 +343,9 @@
sched_wakeup_name_id_(context->storage->InternString("sched_wakeup")),
sched_waking_name_id_(context->storage->InternString("sched_waking")),
cpu_id_(context->storage->InternString("cpu")),
+ ucpu_id_(context->storage->InternString("ucpu")),
+ linux_device_name_id_(
+ context->storage->InternString("linux_device_name")),
suspend_resume_name_id_(
context->storage->InternString("Suspend/Resume Latency")),
suspend_resume_minimal_name_id_(
@@ -440,7 +467,8 @@
suspend_resume_callback_phase_arg_name_(
context->storage->InternString("callback_phase")),
suspend_resume_event_type_arg_name_(
- context->storage->InternString("event_type")) {
+ context->storage->InternString("event_type")),
+ device_name_id_(context->storage->InternString("device_name")) {
// Build the lookup table for the strings inside ftrace events (e.g. the
// name of ftrace event fields and the names of their args).
for (size_t i = 0; i < GetDescriptorsSize(); i++) {
@@ -633,6 +661,28 @@
}
}
+ protos::pbzero::FtraceKprobeStats::Decoder kprobe_stats(evt.kprobe_stats());
+ storage->SetStats(stats::ftrace_kprobe_hits_begin + phase,
+ kprobe_stats.hits());
+ storage->SetStats(stats::ftrace_kprobe_misses_begin + phase,
+ kprobe_stats.misses());
+ if (is_end) {
+ auto kprobe_hits_begin = storage->GetStats(stats::ftrace_kprobe_hits_begin);
+ auto kprobe_hits_end = kprobe_stats.hits();
+ if (kprobe_hits_begin) {
+ int64_t delta_hits = kprobe_hits_end - kprobe_hits_begin;
+ storage->SetStats(stats::ftrace_kprobe_hits_delta, delta_hits);
+ }
+
+ auto kprobe_misses_begin =
+ storage->GetStats(stats::ftrace_kprobe_misses_begin);
+ auto kprobe_misses_end = kprobe_stats.misses();
+ if (kprobe_misses_begin) {
+ int64_t delta_misses = kprobe_misses_end - kprobe_misses_begin;
+ storage->SetStats(stats::ftrace_kprobe_misses_delta, delta_misses);
+ }
+ }
+
// Compute atrace + ftrace setup errors. We do two things here:
// 1. We add up all the errors and put the counter in the stats table (which
// can hold only numerals).
@@ -1079,7 +1129,7 @@
break;
}
case FtraceEvent::kSuspendResumeFieldNumber: {
- ParseSuspendResume(ts, pid, fld_bytes);
+ ParseSuspendResume(ts, cpu, pid, fld_bytes);
break;
}
case FtraceEvent::kSuspendResumeMinimalFieldNumber: {
@@ -1221,6 +1271,10 @@
ParseTrustyEnqueueNop(pid, ts, fld_bytes);
break;
}
+ case FtraceEvent::kDevfreqFrequencyFieldNumber: {
+ ParseDeviceFrequency(ts, fld_bytes);
+ break;
+ }
case FtraceEvent::kMaliMaliKCPUCQSSETFieldNumber:
case FtraceEvent::kMaliMaliKCPUCQSWAITSTARTFieldNumber:
case FtraceEvent::kMaliMaliKCPUCQSWAITENDFieldNumber:
@@ -1295,7 +1349,7 @@
break;
}
case FtraceEvent::kDevicePmCallbackStartFieldNumber: {
- ParseDevicePmCallbackStart(ts, pid, fld_bytes);
+ ParseDevicePmCallbackStart(ts, cpu, pid, fld_bytes);
break;
}
case FtraceEvent::kDevicePmCallbackEndFieldNumber: {
@@ -1311,7 +1365,16 @@
break;
}
case FtraceEvent::kPixelMmKswapdDoneFieldNumber: {
- pixel_mm_kswapd_event_tracker_.ParsePixelMmKswapdDone(ts, pid, fld_bytes);
+ pixel_mm_kswapd_event_tracker_.ParsePixelMmKswapdDone(ts, pid,
+ fld_bytes);
+ break;
+ }
+ case FtraceEvent::kKprobeEventFieldNumber: {
+ ParseKprobe(ts, pid, fld_bytes);
+ break;
+ }
+ case FtraceEvent::kParamSetValueCpmFieldNumber: {
+ ParseParamSetValueCpm(fld_bytes);
break;
}
default:
@@ -1593,6 +1656,33 @@
next_pid, ss.next_comm(), ss.next_prio());
}
+void FtraceParser::ParseKprobe(int64_t timestamp,
+ uint32_t pid,
+ ConstBytes blob) {
+ protos::pbzero::KprobeEvent::Decoder kp(blob.data, blob.size);
+
+ auto kprobe_type = static_cast<KprobeType>(kp.type());
+ StringId name_id = context_->storage->InternString(kp.name());
+ UniqueTid utid = context_->process_tracker->GetOrCreateThread(pid);
+ TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+ switch (kprobe_type) {
+ case KprobeType::KPROBE_TYPE_BEGIN:
+ context_->slice_tracker->Begin(timestamp, track_id,
+ kNullStringId /* cat */, name_id);
+ break;
+ case KprobeType::KPROBE_TYPE_END:
+ context_->slice_tracker->End(timestamp, track_id, kNullStringId /* cat */,
+ name_id);
+ break;
+ case KprobeType::KPROBE_TYPE_INSTANT:
+ context_->slice_tracker->Scoped(timestamp, track_id, kNullStringId,
+ name_id, 0);
+ break;
+ case KprobeType::KPROBE_TYPE_UNKNOWN:
+ break;
+ }
+}
+
void FtraceParser::ParseSchedWaking(int64_t timestamp,
uint32_t pid,
ConstBytes blob) {
@@ -1617,7 +1707,7 @@
uint32_t cpu = freq.cpu_id();
uint32_t new_freq_khz = freq.state();
TrackId track = context_->track_tracker->InternCpuCounterTrack(
- TrackTracker::CpuCounterTrackType::kFrequency, cpu);
+ tracks::cpu_frequency, cpu, TrackTracker::LegacyCharArrayName{"cpufreq"});
context_->event_tracker->PushCounter(timestamp, new_freq_khz, track);
}
@@ -1626,7 +1716,8 @@
uint32_t cpu = static_cast<uint32_t>(freq.cpu());
double new_freq_khz = static_cast<double>(freq.freq());
TrackId track = context_->track_tracker->InternCpuCounterTrack(
- TrackTracker::CpuCounterTrackType::kFreqThrottle, cpu);
+ tracks::cpu_frequency_throttle, cpu,
+ TrackTracker::LegacyCharArrayName{"cpufreq_throttle"});
context_->event_tracker->PushCounter(timestamp, new_freq_khz, track);
}
@@ -1635,7 +1726,7 @@
uint32_t gpu = freq.gpu_id();
uint32_t new_freq = freq.state();
TrackId track = context_->track_tracker->InternGpuCounterTrack(
- TrackTracker::GpuCounterTrackType::kFrequency, gpu);
+ tracks::gpu_frequency, gpu, TrackTracker::LegacyCharArrayName{"gpufreq"});
context_->event_tracker->PushCounter(timestamp, new_freq, track);
}
@@ -1646,7 +1737,7 @@
// Source data is frequency / 1000, so we correct that here:
double new_freq = static_cast<double>(freq.gpu_freq()) * 1000.0;
TrackId track = context_->track_tracker->InternGpuCounterTrack(
- TrackTracker::GpuCounterTrackType::kFrequency, gpu);
+ tracks::gpu_frequency, gpu, TrackTracker::LegacyCharArrayName{"gpufreq"});
context_->event_tracker->PushCounter(timestamp, new_freq, track);
}
@@ -1655,7 +1746,7 @@
uint32_t cpu = idle.cpu_id();
uint32_t new_state = idle.state();
TrackId track = context_->track_tracker->InternCpuCounterTrack(
- TrackTracker::CpuCounterTrackType::kIdle, cpu);
+ tracks::cpu_idle, cpu, TrackTracker::LegacyCharArrayName{"cpuidle"});
context_->event_tracker->PushCounter(timestamp, new_state, track);
}
@@ -1810,7 +1901,8 @@
void FtraceParser::ParseGoogleIccEvent(int64_t timestamp, ConstBytes blob) {
protos::pbzero::GoogleIccEventFtraceEvent::Decoder evt(blob.data, blob.size);
TrackId track_id = context_->track_tracker->InternGlobalTrack(
- TrackTracker::GlobalTrackType::kInterconnect);
+ tracks::interconnect_events,
+ TrackTracker::LegacyCharArrayName(kInternconnectTrackName));
StringId slice_name_id =
context_->storage->InternString(base::StringView(evt.event()));
context_->slice_tracker->Scoped(timestamp, track_id, google_icc_event_id_,
@@ -1820,7 +1912,8 @@
void FtraceParser::ParseGoogleIrmEvent(int64_t timestamp, ConstBytes blob) {
protos::pbzero::GoogleIrmEventFtraceEvent::Decoder evt(blob.data, blob.size);
TrackId track_id = context_->track_tracker->InternGlobalTrack(
- TrackTracker::GlobalTrackType::kInterconnect);
+ tracks::interconnect_events,
+ TrackTracker::LegacyCharArrayName{kInternconnectTrackName});
StringId slice_name_id =
context_->storage->InternString(base::StringView(evt.event()));
context_->slice_tracker->Scoped(timestamp, track_id, google_irm_event_id_,
@@ -1852,7 +1945,7 @@
}
// Push the global counter.
- TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+ TrackId track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kMemory, global_name_id);
context_->event_tracker->PushCounter(timestamp,
static_cast<double>(total_bytes), track);
@@ -1860,8 +1953,8 @@
// Push the change counter.
// TODO(b/121331269): these should really be instant events.
UniqueTid utid = context_->process_tracker->GetOrCreateThread(pid);
- track =
- context_->track_tracker->InternThreadCounterTrack(change_name_id, utid);
+ track = context_->track_tracker->LegacyInternThreadCounterTrack(
+ change_name_id, utid);
context_->event_tracker->PushCounter(
timestamp, static_cast<double>(change_bytes), track);
@@ -1890,7 +1983,7 @@
protozero::ConstBytes data) {
protos::pbzero::IonStatFtraceEvent::Decoder ion(data.data, data.size);
// Push the global counter.
- TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+ TrackId track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kMemory, ion_total_id_);
context_->event_tracker->PushCounter(
timestamp, static_cast<double>(ion.total_allocated()), track);
@@ -1898,8 +1991,8 @@
// Push the change counter.
// TODO(b/121331269): these should really be instant events.
UniqueTid utid = context_->process_tracker->GetOrCreateThread(pid);
- track =
- context_->track_tracker->InternThreadCounterTrack(ion_change_id_, utid);
+ track = context_->track_tracker->LegacyInternThreadCounterTrack(
+ ion_change_id_, utid);
context_->event_tracker->PushCounter(timestamp,
static_cast<double>(ion.len()), track);
@@ -1924,44 +2017,44 @@
protos::pbzero::BclIrqTriggerFtraceEvent::Decoder bcl(data.data, data.size);
int throttle = bcl.throttle();
// id
- TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+ TrackId track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kBatteryMitigation, bcl_irq_id_);
context_->event_tracker->PushCounter(ts, throttle ? bcl.id() : -1, track);
// throttle
- track = context_->track_tracker->InternGlobalCounterTrack(
+ track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kBatteryMitigation, bcl_irq_throttle_);
context_->event_tracker->PushCounter(ts, throttle, track);
// cpu0_limit
- track = context_->track_tracker->InternGlobalCounterTrack(
+ track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kBatteryMitigation, bcl_irq_cpu0_);
context_->event_tracker->PushCounter(ts, throttle ? bcl.cpu0_limit() : 0,
track);
// cpu1_limit
- track = context_->track_tracker->InternGlobalCounterTrack(
+ track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kBatteryMitigation, bcl_irq_cpu1_);
context_->event_tracker->PushCounter(ts, throttle ? bcl.cpu1_limit() : 0,
track);
// cpu2_limit
- track = context_->track_tracker->InternGlobalCounterTrack(
+ track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kBatteryMitigation, bcl_irq_cpu2_);
context_->event_tracker->PushCounter(ts, throttle ? bcl.cpu2_limit() : 0,
track);
// tpu_limit
- track = context_->track_tracker->InternGlobalCounterTrack(
+ track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kBatteryMitigation, bcl_irq_tpu_);
context_->event_tracker->PushCounter(ts, throttle ? bcl.tpu_limit() : 0,
track);
// gpu_limit
- track = context_->track_tracker->InternGlobalCounterTrack(
+ track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kBatteryMitigation, bcl_irq_gpu_);
context_->event_tracker->PushCounter(ts, throttle ? bcl.gpu_limit() : 0,
track);
// voltage
- track = context_->track_tracker->InternGlobalCounterTrack(
+ track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kBatteryMitigation, bcl_irq_voltage_);
context_->event_tracker->PushCounter(ts, bcl.voltage(), track);
// capacity
- track = context_->track_tracker->InternGlobalCounterTrack(
+ track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kBatteryMitigation, bcl_irq_capacity_);
context_->event_tracker->PushCounter(ts, bcl.capacity(), track);
}
@@ -1972,7 +2065,7 @@
protos::pbzero::DmaHeapStatFtraceEvent::Decoder dma_heap(data.data,
data.size);
// Push the global counter.
- TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+ TrackId track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kMemory, dma_heap_total_id_);
context_->event_tracker->PushCounter(
timestamp, static_cast<double>(dma_heap.total_allocated()), track);
@@ -1980,8 +2073,8 @@
// Push the change counter.
// TODO(b/121331269): these should really be instant events.
UniqueTid utid = context_->process_tracker->GetOrCreateThread(pid);
- track = context_->track_tracker->InternThreadCounterTrack(dma_heap_change_id_,
- utid);
+ track = context_->track_tracker->LegacyInternThreadCounterTrack(
+ dma_heap_change_id_, utid);
auto opt_counter_id = context_->event_tracker->PushCounter(
timestamp, static_cast<double>(dma_heap.len()), track);
@@ -2175,6 +2268,22 @@
// family) and thread creation (clone(CLONE_THREAD, ...)).
static const uint32_t kCloneThread = 0x00010000; // From kernel's sched.h.
+ if (PERFETTO_UNLIKELY(new_tid == 0)) {
+ // In the case of boot-time tracing (kernel is started with tracing
+ // enabled), the ftrace buffer will see /bin/init creating swapper/0 tasks:
+ // event {
+ // pid: 1
+ // task_newtask {
+ // pid: 0
+ // comm: "swapper/0"
+ // }
+ // }
+ // Skip these task_newtask events since they are kernel idle tasks.
+ PERFETTO_DCHECK(source_tid == 1);
+ PERFETTO_DCHECK(base::StartsWith(evt.comm().ToStdString(), "swapper"));
+ return;
+ }
+
// If the process is a fork, start a new process.
if ((clone_flags & kCloneThread) == 0) {
// This is a plain-old fork() or equivalent.
@@ -2312,7 +2421,7 @@
clock_name.data(), int(subtitle.size()),
subtitle.data());
StringId name = context_->storage->InternString(counter_name.c_str());
- TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+ TrackId track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kClockFrequency, name);
context_->event_tracker->PushCounter(timestamp, static_cast<double>(rate),
track);
@@ -2521,7 +2630,9 @@
StringId slice_name_id =
context_->storage->InternString(slice_name.string_view());
TrackId track = context_->track_tracker->InternCpuTrack(
- TrackTracker::CpuTrackType::kIrqCpu, cpu);
+ tracks::cpu_irq, cpu,
+ TrackTracker::LegacyCharArrayName{
+ base::StackString<255>("Irq Cpu %u", cpu)});
context_->slice_tracker->Begin(timestamp, track, irq_id_, slice_name_id);
}
@@ -2530,7 +2641,9 @@
protozero::ConstBytes blob) {
protos::pbzero::IrqHandlerExitFtraceEvent::Decoder evt(blob.data, blob.size);
TrackId track = context_->track_tracker->InternCpuTrack(
- TrackTracker::CpuTrackType::kIrqCpu, cpu);
+ tracks::cpu_irq, cpu,
+ TrackTracker::LegacyCharArrayName{
+ base::StackString<255>("Irq Cpu %u", cpu)});
base::StackString<255> status("%s", evt.ret() == 1 ? "handled" : "unhandled");
StringId status_id = context_->storage->InternString(status.string_view());
@@ -2553,7 +2666,9 @@
base::StringView slice_name = kActionNames[evt.vec()];
StringId slice_name_id = context_->storage->InternString(slice_name);
TrackId track = context_->track_tracker->InternCpuTrack(
- TrackTracker::CpuTrackType::kSortIrqCpu, cpu);
+ tracks::cpu_softirq, cpu,
+ TrackTracker::LegacyCharArrayName{
+ base::StackString<255>("SoftIrq Cpu %u", cpu)});
context_->slice_tracker->Begin(timestamp, track, irq_id_, slice_name_id);
}
@@ -2562,7 +2677,9 @@
protozero::ConstBytes blob) {
protos::pbzero::SoftirqExitFtraceEvent::Decoder evt(blob.data, blob.size);
TrackId track = context_->track_tracker->InternCpuTrack(
- TrackTracker::CpuTrackType::kSortIrqCpu, cpu);
+ tracks::cpu_softirq, cpu,
+ TrackTracker::LegacyCharArrayName{
+ base::StackString<255>("SoftIrq Cpu %u", cpu)});
auto vec = evt.vec();
auto args_inserter = [this, vec](ArgsTracker::BoundInserter* inserter) {
inserter->AddArg(vec_arg_id_, Variadic::Integer(vec));
@@ -2579,7 +2696,7 @@
const uint32_t pid = gpu_mem_total.pid();
if (pid == 0) {
// Pid 0 is used to indicate the global total
- track = context_->track_tracker->InternGlobalCounterTrack(
+ track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kMemory, gpu_mem_total_name_id_, {},
gpu_mem_total_unit_id_, gpu_mem_total_global_desc_id_);
} else {
@@ -2603,7 +2720,7 @@
UniquePid upid = *context_->storage->thread_table()[*opt_utid].upid();
PERFETTO_DCHECK(context_->storage->process_table()[upid].pid() == pid);
- track = context_->track_tracker->InternProcessCounterTrack(
+ track = context_->track_tracker->LegacyInternProcessCounterTrack(
gpu_mem_total_name_id_, upid, gpu_mem_total_unit_id_,
gpu_mem_total_proc_desc_id_);
}
@@ -2658,7 +2775,7 @@
}
// Push the global counter.
- TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+ TrackId track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kMemory, total_name);
context_->event_tracker->PushCounter(
timestamp, static_cast<double>(event.total_allocated()), track);
@@ -2666,7 +2783,7 @@
// Push the change counter.
UniqueTid utid = context_->process_tracker->GetOrCreateThread(pid);
TrackId delta_track =
- context_->track_tracker->InternThreadCounterTrack(name, utid);
+ context_->track_tracker->LegacyInternThreadCounterTrack(name, utid);
context_->event_tracker->PushCounter(
timestamp, static_cast<double>(event.len()), delta_track);
}
@@ -2692,7 +2809,7 @@
nic_received_bytes_[name] += event.len();
uint64_t nic_received_kilobytes = nic_received_bytes_[name] / 1024;
- TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+ TrackId track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kNetwork, name);
std::optional<CounterId> id = context_->event_tracker->PushCounter(
timestamp, static_cast<double>(nic_received_kilobytes), track);
@@ -2724,7 +2841,7 @@
nic_transmitted_bytes_[name] += evt.len();
uint64_t nic_transmitted_kilobytes = nic_transmitted_bytes_[name] / 1024;
- TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+ TrackId track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kNetwork, name);
std::optional<CounterId> id = context_->event_tracker->PushCounter(
timestamp, static_cast<double>(nic_transmitted_kilobytes), track);
@@ -2822,7 +2939,9 @@
base::StringView net_device = evt.name();
StringId slice_name_id = context_->storage->InternString(net_device);
TrackId track = context_->track_tracker->InternCpuTrack(
- TrackTracker::CpuTrackType::kNapiGroCpu, cpu);
+ tracks::cpu_napi_gro, cpu,
+ TrackTracker::LegacyCharArrayName{
+ base::StackString<255>("Napi Gro Cpu %u", cpu)});
auto len = evt.len();
auto args_inserter = [this, len](ArgsTracker::BoundInserter* inserter) {
inserter->AddArg(len_arg_id_, Variadic::Integer(len));
@@ -2837,7 +2956,9 @@
protos::pbzero::NapiGroReceiveExitFtraceEvent::Decoder evt(blob.data,
blob.size);
TrackId track = context_->track_tracker->InternCpuTrack(
- TrackTracker::CpuTrackType::kNapiGroCpu, cpu);
+ tracks::cpu_napi_gro, cpu,
+ TrackTracker::LegacyCharArrayName{
+ base::StackString<255>("Napi Gro Cpu %u", cpu)});
auto ret = evt.ret();
auto args_inserter = [this, ret](ArgsTracker::BoundInserter* inserter) {
inserter->AddArg(ret_arg_id_, Variadic::Integer(ret));
@@ -2852,12 +2973,16 @@
blob.size);
TrackId max_track = context_->track_tracker->InternCpuCounterTrack(
- TrackTracker::CpuCounterTrackType::kMaxFreqLimit, evt.cpu_id());
+ tracks::cpu_max_frequency_limit, evt.cpu_id(),
+ TrackTracker::LegacyCharArrayName{
+ base::StackString<255>("Cpu %u Max Freq Limit", evt.cpu_id())});
context_->event_tracker->PushCounter(
timestamp, static_cast<double>(evt.max_freq()), max_track);
TrackId min_track = context_->track_tracker->InternCpuCounterTrack(
- TrackTracker::CpuCounterTrackType::kMinFreqLimit, evt.cpu_id());
+ tracks::cpu_min_frequency_limit, evt.cpu_id(),
+ TrackTracker::LegacyCharArrayName{
+ base::StackString<255>("Cpu %u Min Freq Limit", evt.cpu_id())});
context_->event_tracker->PushCounter(
timestamp, static_cast<double>(evt.min_freq()), min_track);
}
@@ -2872,7 +2997,7 @@
}
num_of_kfree_skb_ip_prot += 1;
- TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+ TrackId track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kNetwork, kfree_skb_name_id_);
std::optional<CounterId> id = context_->event_tracker->PushCounter(
timestamp, static_cast<double>(num_of_kfree_skb_ip_prot), track);
@@ -2892,7 +3017,7 @@
blob.size);
// Push the global counter.
- TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+ TrackId track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kDeviceState,
context_->storage->InternString(
base::StringView("cros_ec.cros_ec_sensorhub_data." +
@@ -2933,7 +3058,7 @@
clk_state = 2;
break;
}
- TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+ TrackId track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kNetwork, ufs_clkgating_id_);
context_->event_tracker->PushCounter(timestamp,
static_cast<double>(clk_state), track);
@@ -3255,7 +3380,7 @@
uint32_t num = evt.doorbell() > 0
? static_cast<uint32_t>(PERFETTO_POPCOUNT(evt.doorbell()))
: (evt.str_t() == 1 ? 0 : 1);
- TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+ TrackId track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kIo, ufs_command_count_id_);
context_->event_tracker->PushCounter(timestamp, static_cast<double>(num),
track);
@@ -3325,6 +3450,7 @@
}
void FtraceParser::ParseSuspendResume(int64_t timestamp,
+ uint32_t cpu,
uint32_t tid,
protozero::ConstBytes blob) {
protos::pbzero::SuspendResumeFtraceEvent::Decoder evt(blob.data, blob.size);
@@ -3380,6 +3506,8 @@
context_->process_tracker->GetOrCreateThread(tid)));
inserter->AddArg(suspend_resume_event_type_arg_name_,
Variadic::String(suspend_resume_main_event_id_));
+ auto ucpu = context_->cpu_tracker->GetOrCreateCpu(cpu);
+ inserter->AddArg(ucpu_id_, Variadic::UnsignedInteger(ucpu.value));
// These fields are set to null as this is not a device PM callback event.
inserter->AddArg(suspend_resume_device_arg_name_,
@@ -3418,17 +3546,23 @@
protozero::ConstBytes blob) {
protos::pbzero::SchedCpuUtilCfsFtraceEvent::Decoder evt(blob.data, blob.size);
TrackId util_track = context_->track_tracker->InternCpuCounterTrack(
- TrackTracker::CpuCounterTrackType::kUtilization, evt.cpu());
+ tracks::cpu_utilization, evt.cpu(),
+ TrackTracker::LegacyCharArrayName{
+ base::StackString<255>("Cpu %u Util", evt.cpu())});
context_->event_tracker->PushCounter(
timestamp, static_cast<double>(evt.cpu_util()), util_track);
TrackId cap_track = context_->track_tracker->InternCpuCounterTrack(
- TrackTracker::CpuCounterTrackType::kCapacity, evt.cpu());
+ tracks::cpu_capacity, evt.cpu(),
+ TrackTracker::LegacyCharArrayName{
+ base::StackString<255>("Cpu %u Cap", evt.cpu())});
context_->event_tracker->PushCounter(
timestamp, static_cast<double>(evt.capacity()), cap_track);
TrackId nrr_track = context_->track_tracker->InternCpuCounterTrack(
- TrackTracker::CpuCounterTrackType::kNrRunning, evt.cpu());
+ tracks::cpu_nr_running, evt.cpu(),
+ TrackTracker::LegacyCharArrayName{
+ base::StackString<255>("Cpu %u Nr Running", evt.cpu())});
context_->event_tracker->PushCounter(
timestamp, static_cast<double>(evt.nr_running()), nrr_track);
}
@@ -3453,7 +3587,9 @@
// of swapper might be running concurrently. Fall back onto global tracks
// (one per cpu).
track = context_->track_tracker->InternCpuTrack(
- TrackTracker::CpuTrackType::kFuncgraphCpu, cpu);
+ tracks::cpu_funcgraph, cpu,
+ TrackTracker::LegacyCharArrayName{
+ base::StackString<255>("swapper%u -funcgraph", cpu)});
}
context_->slice_tracker->Begin(timestamp, track, kNullStringId, name_id);
@@ -3476,7 +3612,9 @@
} else {
// special case: see |ParseFuncgraphEntry|
track = context_->track_tracker->InternCpuTrack(
- TrackTracker::CpuTrackType::kFuncgraphCpu, cpu);
+ tracks::cpu_funcgraph, cpu,
+ TrackTracker::LegacyCharArrayName{
+ base::StackString<255>("swapper%u -funcgraph", cpu)});
}
context_->slice_tracker->End(timestamp, track, kNullStringId, name_id);
@@ -3562,15 +3700,15 @@
// Device here refers to anything managed by a Linux kernel driver.
std::string device_name = rpm_event.name().ToStdString();
- int32_t rpm_status = rpm_event.status();
- StringId device_name_string_id =
- context_->storage->InternString(device_name.c_str());
- TrackId track_id =
- context_->track_tracker->InternLinuxDeviceTrack(device_name_string_id);
+ StringId device_name_string_id = context_->storage->InternString(device_name);
+ TrackId track_id = context_->track_tracker->InternSingleDimensionTrack(
+ tracks::linux_rpm, linux_device_name_id_,
+ Variadic::String(device_name_string_id),
+ TrackTracker::LegacyStringIdName{device_name_string_id});
// A `runtime_status` event implies a potential change in state. Hence, if an
// active slice exists for this device, end that slice.
- if (devices_with_active_rpm_slice_.find(device_name) !=
+ if (devices_with_active_rpm_slice_.find(device_name_string_id) !=
devices_with_active_rpm_slice_.end()) {
context_->slice_tracker->End(ts, track_id);
}
@@ -3578,19 +3716,21 @@
// To reduce visual clutter, the "SUSPENDED" state will be omitted from the
// visualization, as devices typically spend the majority of their time in
// this state.
+ int32_t rpm_status = rpm_event.status();
if (rpm_status == RPM_SUSPENDED) {
- devices_with_active_rpm_slice_.erase(device_name);
+ devices_with_active_rpm_slice_.erase(device_name_string_id);
return;
}
context_->slice_tracker->Begin(ts, track_id, /*category=*/kNullStringId,
/*raw_name=*/GetRpmStatusStringId(rpm_status));
- devices_with_active_rpm_slice_.insert(device_name);
+ devices_with_active_rpm_slice_.insert(device_name_string_id);
}
// Parses `device_pm_callback_start` events and begins corresponding slices in
// the suspend / resume latency UI track.
void FtraceParser::ParseDevicePmCallbackStart(int64_t ts,
+ uint32_t cpu,
uint32_t tid,
protozero::ConstBytes blob) {
protos::pbzero::DevicePmCallbackStartFtraceEvent::Decoder dpm_event(
@@ -3625,6 +3765,8 @@
context_->process_tracker->GetOrCreateThread(tid)));
inserter->AddArg(suspend_resume_event_type_arg_name_,
Variadic::String(suspend_resume_device_pm_event_id_));
+ auto ucpu = context_->cpu_tracker->GetOrCreateCpu(cpu);
+ inserter->AddArg(ucpu_id_, Variadic::UnsignedInteger(ucpu.value));
inserter->AddArg(
suspend_resume_device_arg_name_,
Variadic::String(context_->storage->InternString(device_name.c_str())));
@@ -3691,4 +3833,40 @@
return name_id;
}
+void FtraceParser::ParseDeviceFrequency(int64_t ts,
+ protozero::ConstBytes blob) {
+ protos::pbzero::DevfreqFrequencyFtraceEvent::Decoder event(blob);
+ std::string dev_name = event.dev_name().ToStdString();
+
+ constexpr char kDelimiter[] = "devfreq_";
+ auto position = dev_name.find(kDelimiter);
+ if (position == std::string::npos) {
+ return;
+ }
+
+ // Get device name by getting substring after delimiter and keep existing
+ // naming convention (e.g. cpufreq, gpufreq) consistent by adding a suffix
+ // to the devfreq name (e.g. dsufreq, bcifreq)
+ StringId device_name = context_->storage->InternString(
+ (dev_name.substr(position + sizeof(kDelimiter) - 1) + "freq").c_str());
+
+ TrackId track_id = context_->track_tracker->InternSingleDimensionTrack(
+ tracks::linux_device_frequency, device_name_id_,
+ Variadic::String(device_name));
+ context_->event_tracker->PushCounter(ts, static_cast<double>(event.freq()),
+ 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 9469d24..6a08f65 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.h
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.h
@@ -33,11 +33,12 @@
#include "src/trace_processor/importers/ftrace/gpu_work_period_tracker.h"
#include "src/trace_processor/importers/ftrace/iostat_tracker.h"
#include "src/trace_processor/importers/ftrace/mali_gpu_event_tracker.h"
+#include "src/trace_processor/importers/ftrace/pixel_mm_kswapd_event_tracker.h"
#include "src/trace_processor/importers/ftrace/pkvm_hyp_cpu_tracker.h"
#include "src/trace_processor/importers/ftrace/rss_stat_tracker.h"
#include "src/trace_processor/importers/ftrace/thermal_tracker.h"
#include "src/trace_processor/importers/ftrace/virtio_gpu_tracker.h"
-#include "src/trace_processor/importers/ftrace/pixel_mm_kswapd_event_tracker.h"
+#include "src/trace_processor/storage/trace_storage.h"
#include "src/trace_processor/types/trace_processor_context.h"
namespace perfetto {
@@ -72,6 +73,7 @@
protozero::ConstBytes,
PacketSequenceStateGeneration*);
void ParseSchedSwitch(uint32_t cpu, int64_t timestamp, protozero::ConstBytes);
+ void ParseKprobe(int64_t timestamp, uint32_t pid, protozero::ConstBytes);
void ParseSchedWaking(int64_t timestamp, uint32_t pid, protozero::ConstBytes);
void ParseSchedProcessFree(int64_t timestamp, protozero::ConstBytes);
void ParseCpuFreq(int64_t timestamp, protozero::ConstBytes);
@@ -236,6 +238,7 @@
void ParseWakeSourceActivate(int64_t timestamp, protozero::ConstBytes);
void ParseWakeSourceDeactivate(int64_t timestamp, protozero::ConstBytes);
void ParseSuspendResume(int64_t timestamp,
+ uint32_t cpu,
uint32_t pid,
protozero::ConstBytes);
void ParseSuspendResumeMinimal(int64_t timestamp, protozero::ConstBytes);
@@ -304,6 +307,7 @@
StringId GetRpmStatusStringId(int32_t rpm_status_val);
void ParseRpmStatus(int64_t ts, protozero::ConstBytes);
void ParseDevicePmCallbackStart(int64_t ts,
+ uint32_t cpu,
uint32_t pid,
protozero::ConstBytes);
void ParseDevicePmCallbackEnd(int64_t ts, protozero::ConstBytes);
@@ -312,6 +316,8 @@
protozero::ConstBytes);
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_;
@@ -327,6 +333,8 @@
const StringId sched_wakeup_name_id_;
const StringId sched_waking_name_id_;
const StringId cpu_id_;
+ const StringId ucpu_id_;
+ const StringId linux_device_name_id_;
const StringId suspend_resume_name_id_;
const StringId suspend_resume_minimal_name_id_;
const StringId suspend_resume_minimal_slice_name_id_;
@@ -423,6 +431,7 @@
const StringId suspend_resume_driver_arg_name_;
const StringId suspend_resume_callback_phase_arg_name_;
const StringId suspend_resume_event_type_arg_name_;
+ const StringId device_name_id_;
std::vector<StringId> syscall_arg_name_ids_;
@@ -493,7 +502,7 @@
// Tracks Linux devices with active runtime power management (RPM) status
// slices.
- std::unordered_set<std::string> devices_with_active_rpm_slice_;
+ std::unordered_set<StringId> devices_with_active_rpm_slice_;
// Tracks unique identifiers ("cookies") to create separate async tracks for
// the Suspend/Resume UI track. The separation prevents unnestable slices from
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/importers/ftrace/gpu_work_period_tracker.cc b/src/trace_processor/importers/ftrace/gpu_work_period_tracker.cc
index 86db07b..922b21e 100644
--- a/src/trace_processor/importers/ftrace/gpu_work_period_tracker.cc
+++ b/src/trace_processor/importers/ftrace/gpu_work_period_tracker.cc
@@ -16,36 +16,39 @@
#include "src/trace_processor/importers/ftrace/gpu_work_period_tracker.h"
+#include <cstdint>
+
#include "perfetto/ext/base/string_utils.h"
-#include "protos/perfetto/common/gpu_counter_descriptor.pbzero.h"
+#include "perfetto/protozero/field.h"
#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
#include "protos/perfetto/trace/ftrace/power.pbzero.h"
#include "src/trace_processor/importers/common/async_track_set_tracker.h"
#include "src/trace_processor/importers/common/event_tracker.h"
-#include "src/trace_processor/importers/common/process_tracker.h"
#include "src/trace_processor/importers/common/slice_tracker.h"
#include "src/trace_processor/importers/common/track_tracker.h"
+#include "src/trace_processor/importers/common/tracks.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/tables/slice_tables_py.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
GpuWorkPeriodTracker::GpuWorkPeriodTracker(TraceProcessorContext* context)
- : context_(context),
- gpu_work_period_id_(context->storage->InternString("GPU Work Period")) {}
+ : context_(context) {}
void GpuWorkPeriodTracker::ParseGpuWorkPeriodEvent(int64_t timestamp,
protozero::ConstBytes blob) {
- protos::pbzero::GpuWorkPeriodFtraceEvent::Decoder evt(blob.data, blob.size);
+ protos::pbzero::GpuWorkPeriodFtraceEvent::Decoder evt(blob);
- tables::GpuWorkPeriodTrackTable::Row track(gpu_work_period_id_);
- track.uid = static_cast<int32_t>(evt.uid());
- track.gpu_id = evt.gpu_id();
- track.machine_id = context_->machine_id();
- TrackId track_id = context_->track_tracker->InternGpuWorkPeriodTrack(track);
+ TrackTracker::DimensionsBuilder dims_builder =
+ context_->track_tracker->CreateDimensionsBuilder();
+ dims_builder.AppendGpu(evt.gpu_id());
+ dims_builder.AppendUid(static_cast<int32_t>(evt.uid()));
+ TrackId track_id = context_->track_tracker->InternTrack(
+ tracks::android_gpu_work_period, std::move(dims_builder).Build());
- const int64_t duration =
+ const auto duration =
static_cast<int64_t>(evt.end_time_ns() - evt.start_time_ns());
- const int64_t active_duration =
+ const auto active_duration =
static_cast<int64_t>(evt.total_active_duration_ns());
const double active_percent = 100.0 * (static_cast<double>(active_duration) /
static_cast<double>(duration));
@@ -66,5 +69,4 @@
row);
}
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/ftrace/gpu_work_period_tracker.h b/src/trace_processor/importers/ftrace/gpu_work_period_tracker.h
index a0001e3..861c90b 100644
--- a/src/trace_processor/importers/ftrace/gpu_work_period_tracker.h
+++ b/src/trace_processor/importers/ftrace/gpu_work_period_tracker.h
@@ -17,11 +17,11 @@
#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_FTRACE_GPU_WORK_PERIOD_TRACKER_H_
#define SRC_TRACE_PROCESSOR_IMPORTERS_FTRACE_GPU_WORK_PERIOD_TRACKER_H_
-#include "src/trace_processor/storage/trace_storage.h"
-#include "src/trace_processor/util/descriptors.h"
+#include <cstdint>
-namespace perfetto {
-namespace trace_processor {
+#include "perfetto/protozero/field.h"
+
+namespace perfetto::trace_processor {
class TraceProcessorContext;
@@ -32,10 +32,8 @@
private:
TraceProcessorContext* context_;
- const StringId gpu_work_period_id_;
};
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
#endif // SRC_TRACE_PROCESSOR_IMPORTERS_FTRACE_GPU_WORK_PERIOD_TRACKER_H_
diff --git a/src/trace_processor/importers/ftrace/iostat_tracker.cc b/src/trace_processor/importers/ftrace/iostat_tracker.cc
index d3c40d8..e0ae458 100644
--- a/src/trace_processor/importers/ftrace/iostat_tracker.cc
+++ b/src/trace_processor/importers/ftrace/iostat_tracker.cc
@@ -43,7 +43,7 @@
uint64_t value) {
std::string track_name = tagPrefix + "." + std::string(counter_name);
StringId string_id = context_->storage->InternString(track_name.c_str());
- TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+ TrackId track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kIo, string_id);
context_->event_tracker->PushCounter(timestamp, static_cast<double>(value),
track);
@@ -83,7 +83,7 @@
uint64_t value) {
std::string track_name = tagPrefix + "." + std::string(counter_name);
StringId string_id = context_->storage->InternString(track_name.c_str());
- TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+ TrackId track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kIo, string_id);
context_->event_tracker->PushCounter(timestamp, static_cast<double>(value),
track);
diff --git a/src/trace_processor/importers/ftrace/mali_gpu_event_tracker.cc b/src/trace_processor/importers/ftrace/mali_gpu_event_tracker.cc
index 6f8d6f3..44c8d9f 100644
--- a/src/trace_processor/importers/ftrace/mali_gpu_event_tracker.cc
+++ b/src/trace_processor/importers/ftrace/mali_gpu_event_tracker.cc
@@ -155,7 +155,9 @@
// associated to a single process or thread. Add to a custom Mali Irq track
// instead.
TrackId track_id = context_->track_tracker->InternCpuTrack(
- TrackTracker::CpuTrackType::kMaliIrqCpu, cpu);
+ tracks::cpu_mali_irq, cpu,
+ TrackTracker::LegacyCharArrayName{
+ base::StackString<255>("Mali Irq Cpu %u", cpu)});
switch (field_id) {
case FtraceEvent::kMaliMaliCSFINTERRUPTSTARTFieldNumber: {
@@ -175,7 +177,7 @@
void MaliGpuEventTracker::ParseMaliGpuMcuStateEvent(int64_t timestamp,
uint32_t field_id) {
tables::GpuTrackTable::Row track_info(mcu_state_track_name_);
- TrackId track_id = context_->track_tracker->InternGpuTrack(track_info);
+ TrackId track_id = context_->track_tracker->LegacyInternGpuTrack(track_info);
if (field_id < kFirstMcuStateId || field_id > kLastMcuStateId) {
PERFETTO_FATAL("Mali MCU state ID out of range");
diff --git a/src/trace_processor/importers/ftrace/pixel_mm_kswapd_event_tracker.cc b/src/trace_processor/importers/ftrace/pixel_mm_kswapd_event_tracker.cc
index a43e764..60f85a7 100644
--- a/src/trace_processor/importers/ftrace/pixel_mm_kswapd_event_tracker.cc
+++ b/src/trace_processor/importers/ftrace/pixel_mm_kswapd_event_tracker.cc
@@ -16,15 +16,19 @@
#include "src/trace_processor/importers/ftrace/pixel_mm_kswapd_event_tracker.h"
-#include "perfetto/ext/base/string_utils.h"
+#include <cmath>
+#include <cstdint>
+
+#include "perfetto/protozero/field.h"
#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
#include "protos/perfetto/trace/ftrace/pixel_mm.pbzero.h"
#include "src/trace_processor/importers/common/process_tracker.h"
#include "src/trace_processor/importers/common/slice_tracker.h"
#include "src/trace_processor/importers/common/track_tracker.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/types/variadic.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
PixelMmKswapdEventTracker::PixelMmKswapdEventTracker(
TraceProcessorContext* context)
@@ -82,5 +86,4 @@
});
}
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/ftrace/pkvm_hyp_cpu_tracker.cc b/src/trace_processor/importers/ftrace/pkvm_hyp_cpu_tracker.cc
index 67ef203..cf0d7f2 100644
--- a/src/trace_processor/importers/ftrace/pkvm_hyp_cpu_tracker.cc
+++ b/src/trace_processor/importers/ftrace/pkvm_hyp_cpu_tracker.cc
@@ -20,12 +20,18 @@
#include "perfetto/ext/base/string_utils.h"
#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
#include "protos/perfetto/trace/ftrace/hyp.pbzero.h"
-#include "src/trace_processor/importers/common/event_tracker.h"
#include "src/trace_processor/importers/common/slice_tracker.h"
#include "src/trace_processor/importers/common/track_tracker.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
+namespace {
+
+TrackTracker::LegacyCharArrayName GetTrackName(uint32_t cpu) {
+ return TrackTracker::LegacyCharArrayName{
+ base::StackString<255>("pkVM Hypervisor CPU %u", cpu)};
+}
+
+} // namespace
PkvmHypervisorCpuTracker::PkvmHypervisorCpuTracker(
TraceProcessorContext* context)
@@ -78,16 +84,15 @@
void PkvmHypervisorCpuTracker::ParseHypEnter(uint32_t cpu, int64_t timestamp) {
// TODO(b/249050813): handle bad events (e.g. 2 hyp_enter in a row)
-
TrackId track_id = context_->track_tracker->InternCpuTrack(
- TrackTracker::CpuTrackType::kPkvmHypervisor, cpu);
+ tracks::pkvm_hypervisor, cpu, GetTrackName(cpu));
context_->slice_tracker->Begin(timestamp, track_id, category_, slice_name_);
}
void PkvmHypervisorCpuTracker::ParseHypExit(uint32_t cpu, int64_t timestamp) {
// TODO(b/249050813): handle bad events (e.g. 2 hyp_exit in a row)
TrackId track_id = context_->track_tracker->InternCpuTrack(
- TrackTracker::CpuTrackType::kPkvmHypervisor, cpu);
+ tracks::pkvm_hypervisor, cpu, GetTrackName(cpu));
context_->slice_tracker->End(timestamp, track_id);
}
@@ -95,7 +100,7 @@
protozero::ConstBytes blob) {
protos::pbzero::HostHcallFtraceEvent::Decoder evt(blob.data, blob.size);
TrackId track_id = context_->track_tracker->InternCpuTrack(
- TrackTracker::CpuTrackType::kPkvmHypervisor, cpu);
+ tracks::pkvm_hypervisor, cpu, GetTrackName(cpu));
auto args_inserter = [this, &evt](ArgsTracker::BoundInserter* inserter) {
StringId host_hcall = context_->storage->InternString("host_hcall");
@@ -113,7 +118,7 @@
protozero::ConstBytes blob) {
protos::pbzero::HostSmcFtraceEvent::Decoder evt(blob.data, blob.size);
TrackId track_id = context_->track_tracker->InternCpuTrack(
- TrackTracker::CpuTrackType::kPkvmHypervisor, cpu);
+ tracks::pkvm_hypervisor, cpu, GetTrackName(cpu));
auto args_inserter = [this, &evt](ArgsTracker::BoundInserter* inserter) {
StringId host_smc = context_->storage->InternString("host_smc");
@@ -131,7 +136,7 @@
protozero::ConstBytes blob) {
protos::pbzero::HostMemAbortFtraceEvent::Decoder evt(blob.data, blob.size);
TrackId track_id = context_->track_tracker->InternCpuTrack(
- TrackTracker::CpuTrackType::kPkvmHypervisor, cpu);
+ tracks::pkvm_hypervisor, cpu, GetTrackName(cpu));
auto args_inserter = [this, &evt](ArgsTracker::BoundInserter* inserter) {
StringId host_mem_abort = context_->storage->InternString("host_mem_abort");
@@ -145,5 +150,4 @@
args_inserter);
}
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/ftrace/thermal_tracker.cc b/src/trace_processor/importers/ftrace/thermal_tracker.cc
index f2fe7bb..b97d001 100644
--- a/src/trace_processor/importers/ftrace/thermal_tracker.cc
+++ b/src/trace_processor/importers/ftrace/thermal_tracker.cc
@@ -104,7 +104,7 @@
void ThermalTracker::PushCounter(int64_t timestamp,
StringId counter_id,
double value) {
- TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+ TrackId track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kThermals, counter_id);
context_->event_tracker->PushCounter(timestamp, value, track);
}
diff --git a/src/trace_processor/importers/ftrace/virtio_gpu_tracker.cc b/src/trace_processor/importers/ftrace/virtio_gpu_tracker.cc
index c66ab62..d142d3d 100644
--- a/src/trace_processor/importers/ftrace/virtio_gpu_tracker.cc
+++ b/src/trace_processor/importers/ftrace/virtio_gpu_tracker.cc
@@ -160,7 +160,7 @@
void VirtioGpuTracker::VirtioGpuQueue::HandleNumFree(int64_t timestamp,
uint32_t num_free) {
- TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+ TrackId track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kVirtio, num_free_id_);
context_->event_tracker->PushCounter(timestamp, static_cast<double>(num_free),
track);
@@ -201,7 +201,7 @@
int64_t duration = timestamp - *start_timestamp;
- TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+ TrackId track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kVirtio, latency_id_);
context_->event_tracker->PushCounter(timestamp, static_cast<double>(duration),
track);
diff --git a/src/trace_processor/importers/fuchsia/fuchsia_trace_parser.cc b/src/trace_processor/importers/fuchsia/fuchsia_trace_parser.cc
index c74f2e5..beed31e 100644
--- a/src/trace_processor/importers/fuchsia/fuchsia_trace_parser.cc
+++ b/src/trace_processor/importers/fuchsia/fuchsia_trace_parser.cc
@@ -332,7 +332,7 @@
StringId counter_name_id = context_->storage->InternString(
base::StringView(counter_name_str));
TrackId track =
- context_->track_tracker->InternProcessCounterTrack(
+ context_->track_tracker->LegacyInternProcessCounterTrack(
counter_name_id, upid);
context_->event_tracker->PushCounter(ts, counter_value, track);
}
@@ -386,8 +386,9 @@
}
UniquePid upid =
procs->GetOrCreateProcess(static_cast<uint32_t>(tinfo.pid));
- TrackId track_id = context_->track_tracker->InternFuchsiaAsyncTrack(
- name, upid, correlation_id);
+ TrackId track_id =
+ context_->track_tracker->LegacyInternLegacyChromeAsyncTrack(
+ name, upid, correlation_id, false, kNullStringId);
slices->Begin(ts, track_id, cat, name, std::move(insert_args));
break;
}
@@ -399,8 +400,9 @@
}
UniquePid upid =
procs->GetOrCreateProcess(static_cast<uint32_t>(tinfo.pid));
- TrackId track_id = context_->track_tracker->InternFuchsiaAsyncTrack(
- name, upid, correlation_id);
+ TrackId track_id =
+ context_->track_tracker->LegacyInternLegacyChromeAsyncTrack(
+ name, upid, correlation_id, false, kNullStringId);
slices->Scoped(ts, track_id, cat, name, 0, std::move(insert_args));
break;
}
@@ -412,8 +414,9 @@
}
UniquePid upid =
procs->GetOrCreateProcess(static_cast<uint32_t>(tinfo.pid));
- TrackId track_id = context_->track_tracker->InternFuchsiaAsyncTrack(
- name, upid, correlation_id);
+ TrackId track_id =
+ context_->track_tracker->LegacyInternLegacyChromeAsyncTrack(
+ name, upid, correlation_id, false, kNullStringId);
slices->End(ts, track_id, cat, name, std::move(insert_args));
break;
}
diff --git a/src/trace_processor/importers/gecko/BUILD.gn b/src/trace_processor/importers/gecko/BUILD.gn
new file mode 100644
index 0000000..5ebd2b8
--- /dev/null
+++ b/src/trace_processor/importers/gecko/BUILD.gn
@@ -0,0 +1,49 @@
+# 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("../../../../gn/test.gni")
+
+source_set("gecko_event") {
+ sources = [ "gecko_event.h" ]
+ deps = [
+ "../../../../gn:default_deps",
+ "../../containers",
+ "../../tables",
+ ]
+}
+
+if (enable_perfetto_trace_processor_json) {
+ source_set("gecko") {
+ sources = [
+ "gecko_trace_parser_impl.cc",
+ "gecko_trace_parser_impl.h",
+ "gecko_trace_tokenizer.cc",
+ "gecko_trace_tokenizer.h",
+ ]
+ deps = [
+ ":gecko_event",
+ "../../../../gn:default_deps",
+ "../../../../gn:jsoncpp",
+ "../../../../include/perfetto/trace_processor:storage",
+ "../../../../protos/perfetto/trace:zero",
+ "../../../base",
+ "../../importers/common",
+ "../../sorter",
+ "../../storage",
+ "../../tables",
+ "../../types",
+ "../json:minimal",
+ ]
+ }
+}
diff --git a/src/trace_processor/importers/gecko/gecko_event.h b/src/trace_processor/importers/gecko/gecko_event.h
new file mode 100644
index 0000000..52dacb1
--- /dev/null
+++ b/src/trace_processor/importers/gecko/gecko_event.h
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_GECKO_GECKO_EVENT_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_GECKO_GECKO_EVENT_H_
+
+#include <cstdint>
+#include <variant>
+
+#include "src/trace_processor/containers/string_pool.h"
+#include "src/trace_processor/tables/profiler_tables_py.h"
+
+namespace perfetto::trace_processor::gecko_importer {
+
+struct alignas(8) GeckoEvent {
+ struct ThreadMetadata {
+ uint32_t tid;
+ uint32_t pid;
+ StringPool::Id name;
+ };
+ struct StackSample {
+ uint32_t tid;
+ tables::StackProfileCallsiteTable::Id callsite_id;
+ };
+ using OneOf = std::variant<ThreadMetadata, StackSample>;
+ OneOf oneof;
+};
+
+} // namespace perfetto::trace_processor::gecko_importer
+
+#endif // SRC_TRACE_PROCESSOR_IMPORTERS_GECKO_GECKO_EVENT_H_
diff --git a/src/trace_processor/importers/gecko/gecko_trace_parser_impl.cc b/src/trace_processor/importers/gecko/gecko_trace_parser_impl.cc
new file mode 100644
index 0000000..66eb0fc
--- /dev/null
+++ b/src/trace_processor/importers/gecko/gecko_trace_parser_impl.cc
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+
+#include "src/trace_processor/importers/gecko/gecko_trace_parser_impl.h"
+
+#include <cstdint>
+
+#include "perfetto/base/compiler.h"
+#include "src/trace_processor/importers/common/process_tracker.h"
+#include "src/trace_processor/importers/common/stack_profile_tracker.h"
+#include "src/trace_processor/importers/gecko/gecko_event.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/tables/profiler_tables_py.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto::trace_processor::gecko_importer {
+
+namespace {
+
+template <typename T>
+constexpr uint32_t GeckoOneOf() {
+ return base::variant_index<GeckoEvent::OneOf, T>();
+}
+
+} // namespace
+
+GeckoTraceParserImpl::GeckoTraceParserImpl(TraceProcessorContext* context)
+ : context_(context) {}
+
+GeckoTraceParserImpl::~GeckoTraceParserImpl() = default;
+
+void GeckoTraceParserImpl::ParseGeckoEvent(int64_t ts, GeckoEvent evt) {
+ switch (evt.oneof.index()) {
+ case GeckoOneOf<GeckoEvent::ThreadMetadata>(): {
+ auto thread = std::get<GeckoEvent::ThreadMetadata>(evt.oneof);
+ UniqueTid utid =
+ context_->process_tracker->UpdateThread(thread.tid, thread.pid);
+ context_->process_tracker->UpdateThreadNameByUtid(
+ utid, thread.name, ThreadNamePriority::kOther);
+ break;
+ }
+ case GeckoOneOf<GeckoEvent::StackSample>():
+ auto sample = std::get<GeckoEvent::StackSample>(evt.oneof);
+ auto* ss = context_->storage->mutable_cpu_profile_stack_sample_table();
+ tables::CpuProfileStackSampleTable::Row row;
+ row.ts = ts;
+ row.callsite_id = sample.callsite_id;
+ row.utid = context_->process_tracker->GetOrCreateThread(sample.tid);
+ ss->Insert(row);
+ break;
+ }
+}
+
+} // namespace perfetto::trace_processor::gecko_importer
diff --git a/src/trace_processor/importers/gecko/gecko_trace_parser_impl.h b/src/trace_processor/importers/gecko/gecko_trace_parser_impl.h
new file mode 100644
index 0000000..de5b378
--- /dev/null
+++ b/src/trace_processor/importers/gecko/gecko_trace_parser_impl.h
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_GECKO_GECKO_TRACE_PARSER_IMPL_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_GECKO_GECKO_TRACE_PARSER_IMPL_H_
+
+#include <cstdint>
+
+#include "src/trace_processor/importers/common/trace_parser.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto::trace_processor::gecko_importer {
+
+class GeckoTraceParserImpl : public GeckoTraceParser {
+ public:
+ explicit GeckoTraceParserImpl(TraceProcessorContext*);
+ ~GeckoTraceParserImpl() override;
+
+ void ParseGeckoEvent(int64_t ts, GeckoEvent) override;
+
+ private:
+ TraceProcessorContext* const context_;
+};
+
+} // namespace perfetto::trace_processor::gecko_importer
+
+#endif // SRC_TRACE_PROCESSOR_IMPORTERS_GECKO_GECKO_TRACE_PARSER_IMPL_H_
diff --git a/src/trace_processor/importers/gecko/gecko_trace_tokenizer.cc b/src/trace_processor/importers/gecko/gecko_trace_tokenizer.cc
new file mode 100644
index 0000000..32945f4
--- /dev/null
+++ b/src/trace_processor/importers/gecko/gecko_trace_tokenizer.cc
@@ -0,0 +1,166 @@
+/*
+ * 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.
+ */
+
+#include "src/trace_processor/importers/gecko/gecko_trace_tokenizer.h"
+
+#include <json/value.h>
+#include <cstddef>
+#include <cstdint>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/flat_hash_map.h"
+#include "perfetto/ext/base/string_view.h"
+#include "perfetto/trace_processor/trace_blob_view.h"
+#include "protos/perfetto/trace/clock_snapshot.pbzero.h"
+#include "src/trace_processor/importers/common/mapping_tracker.h"
+#include "src/trace_processor/importers/common/stack_profile_tracker.h"
+#include "src/trace_processor/importers/common/virtual_memory_mapping.h"
+#include "src/trace_processor/importers/gecko/gecko_event.h"
+#include "src/trace_processor/importers/json/json_utils.h"
+#include "src/trace_processor/sorter/trace_sorter.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/util/status_macros.h"
+
+namespace perfetto::trace_processor::gecko_importer {
+namespace {
+
+struct Callsite {
+ CallsiteId id;
+ uint32_t depth;
+};
+
+} // namespace
+
+GeckoTraceTokenizer::GeckoTraceTokenizer(TraceProcessorContext* ctx)
+ : context_(ctx) {}
+GeckoTraceTokenizer::~GeckoTraceTokenizer() = default;
+
+base::Status GeckoTraceTokenizer::Parse(TraceBlobView blob) {
+ pending_json_.append(reinterpret_cast<const char*>(blob.data()), blob.size());
+ return base::OkStatus();
+}
+
+base::Status GeckoTraceTokenizer::NotifyEndOfFile() {
+ std::optional<Json::Value> opt_value =
+ json::ParseJsonString(base::StringView(pending_json_));
+ if (!opt_value) {
+ return base::ErrStatus(
+ "Syntactic error while Gecko trace; please use an external JSON tool "
+ "(e.g. jq) to understand the source of the error.");
+ }
+ context_->clock_tracker->SetTraceTimeClock(
+ protos::pbzero::ClockSnapshot::Clock::MONOTONIC);
+
+ DummyMemoryMapping* dummy_mapping = nullptr;
+ base::FlatHashMap<std::string, DummyMemoryMapping*> mappings;
+
+ const Json::Value& value = *opt_value;
+ std::vector<FrameId> frame_ids;
+ std::vector<Callsite> callsites;
+ for (const auto& t : value["threads"]) {
+ // The trace uses per-thread indices, we reuse the vector for perf reasons
+ // to prevent reallocs on every thread.
+ frame_ids.clear();
+ callsites.clear();
+
+ const auto& strings = t["stringTable"];
+ const auto& frames = t["frameTable"];
+ const auto& frames_schema = frames["schema"];
+ uint32_t location_idx = frames_schema["location"].asUInt();
+ for (const auto& frame : frames["data"]) {
+ base::StringView name = strings[frame[location_idx].asUInt()].asCString();
+
+ constexpr std::string_view kMappingStart = " (in ";
+ size_t mapping_meta_start = name.find(
+ base::StringView(kMappingStart.data(), kMappingStart.size()));
+ if (mapping_meta_start == base::StringView::npos &&
+ name.data()[name.size() - 1] == ')') {
+ if (!dummy_mapping) {
+ dummy_mapping =
+ &context_->mapping_tracker->CreateDummyMapping("gecko");
+ }
+ frame_ids.push_back(
+ dummy_mapping->InternDummyFrame(name, base::StringView()));
+ continue;
+ }
+
+ DummyMemoryMapping* mapping;
+ size_t mapping_start = mapping_meta_start + kMappingStart.size();
+ size_t mapping_end = name.find(')', mapping_start);
+ std::string mapping_name =
+ name.substr(mapping_start, mapping_end - mapping_start).ToStdString();
+ if (auto* mapping_ptr = mappings.Find(mapping_name); mapping_ptr) {
+ mapping = *mapping_ptr;
+ } else {
+ mapping = &context_->mapping_tracker->CreateDummyMapping(mapping_name);
+ mappings.Insert(mapping_name, mapping);
+ }
+ frame_ids.push_back(mapping->InternDummyFrame(
+ name.substr(0, mapping_meta_start), base::StringView()));
+ }
+
+ const auto& stacks = t["stackTable"];
+ const auto& stacks_schema = stacks["schema"];
+ uint32_t prefix_index = stacks_schema["prefix"].asUInt();
+ uint32_t frame_index = stacks_schema["frame"].asUInt();
+ for (const auto& frame : stacks["data"]) {
+ const auto& prefix = frame[prefix_index];
+ std::optional<CallsiteId> prefix_id;
+ uint32_t depth = 0;
+ if (!prefix.isNull()) {
+ const auto& c = callsites[prefix.asUInt()];
+ prefix_id = c.id;
+ depth = c.depth + 1;
+ }
+ CallsiteId cid = context_->stack_profile_tracker->InternCallsite(
+ prefix_id, frame_ids[frame[frame_index].asUInt()], depth);
+ callsites.push_back({cid, depth});
+ }
+
+ const auto& samples = t["samples"];
+ const auto& samples_schema = samples["schema"];
+ uint32_t stack_index = samples_schema["stack"].asUInt();
+ uint32_t time_index = samples_schema["time"].asUInt();
+ bool added_metadata = false;
+ for (const auto& sample : samples["data"]) {
+ uint32_t stack_idx = sample[stack_index].asUInt();
+ auto ts =
+ static_cast<int64_t>(sample[time_index].asDouble() * 1000 * 1000);
+ if (!added_metadata) {
+ context_->sorter->PushGeckoEvent(
+ ts, GeckoEvent{GeckoEvent::ThreadMetadata{
+ t["tid"].asUInt(), t["pid"].asUInt(),
+ context_->storage->InternString(t["name"].asCString())}});
+ added_metadata = true;
+ }
+ ASSIGN_OR_RETURN(
+ int64_t converted,
+ context_->clock_tracker->ToTraceTime(
+ protos::pbzero::ClockSnapshot::Clock::MONOTONIC, ts));
+ context_->sorter->PushGeckoEvent(
+ converted, GeckoEvent{GeckoEvent::StackSample{
+ t["tid"].asUInt(), callsites[stack_idx].id}});
+ }
+ }
+ return base::OkStatus();
+}
+
+} // namespace perfetto::trace_processor::gecko_importer
diff --git a/src/trace_processor/importers/gecko/gecko_trace_tokenizer.h b/src/trace_processor/importers/gecko/gecko_trace_tokenizer.h
new file mode 100644
index 0000000..c6050d5
--- /dev/null
+++ b/src/trace_processor/importers/gecko/gecko_trace_tokenizer.h
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_GECKO_GECKO_TRACE_TOKENIZER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_GECKO_GECKO_TRACE_TOKENIZER_H_
+
+#include <string>
+
+#include "perfetto/base/status.h"
+#include "src/trace_processor/importers/common/chunked_trace_reader.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto::trace_processor::gecko_importer {
+
+class GeckoTraceTokenizer : public ChunkedTraceReader {
+ public:
+ explicit GeckoTraceTokenizer(TraceProcessorContext*);
+ ~GeckoTraceTokenizer() override;
+
+ base::Status Parse(TraceBlobView) override;
+ base::Status NotifyEndOfFile() override;
+
+ private:
+ TraceProcessorContext* const context_;
+ std::string pending_json_;
+};
+
+} // namespace perfetto::trace_processor::gecko_importer
+
+#endif // SRC_TRACE_PROCESSOR_IMPORTERS_GECKO_GECKO_TRACE_TOKENIZER_H_
diff --git a/src/trace_processor/importers/gzip/BUILD.gn b/src/trace_processor/importers/gzip/BUILD.gn
deleted file mode 100644
index d2fad21..0000000
--- a/src/trace_processor/importers/gzip/BUILD.gn
+++ /dev/null
@@ -1,28 +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.
-
-source_set("full") {
- sources = [
- "gzip_trace_parser.cc",
- "gzip_trace_parser.h",
- ]
- deps = [
- "../..:storage_minimal",
- "../../../../gn:default_deps",
- "../../../base",
- "../../util",
- "../../util:gzip",
- "../common",
- ]
-}
diff --git a/src/trace_processor/importers/json/BUILD.gn b/src/trace_processor/importers/json/BUILD.gn
index ec0813d..c7921d2 100644
--- a/src/trace_processor/importers/json/BUILD.gn
+++ b/src/trace_processor/importers/json/BUILD.gn
@@ -22,30 +22,8 @@
]
deps = [
"../../../../gn:default_deps",
- "../common",
- ]
- if (enable_perfetto_trace_processor_json) {
- public_deps = [ "../../../../gn:jsoncpp" ]
- }
-}
-
-source_set("full") {
- sources = [
- "json_trace_parser_impl.cc",
- "json_trace_parser_impl.h",
- "json_trace_tokenizer.cc",
- "json_trace_tokenizer.h",
- ]
- deps = [
- ":minimal",
- "../../../../gn:default_deps",
- "../../sorter",
"../../storage",
- "../../tables",
- "../../types",
"../common",
- "../systrace:full",
- "../systrace:systrace_line",
]
if (enable_perfetto_trace_processor_json) {
public_deps = [ "../../../../gn:jsoncpp" ]
@@ -53,6 +31,30 @@
}
if (enable_perfetto_trace_processor_json) {
+ source_set("json") {
+ sources = [
+ "json_trace_parser_impl.cc",
+ "json_trace_parser_impl.h",
+ "json_trace_tokenizer.cc",
+ "json_trace_tokenizer.h",
+ ]
+ deps = [
+ ":minimal",
+ "../../../../gn:default_deps",
+ "../../sorter",
+ "../../storage",
+ "../../tables",
+ "../../types",
+ "../common",
+ "../common:parser_types",
+ "../systrace:full",
+ "../systrace:systrace_line",
+ ]
+ if (enable_perfetto_trace_processor_json) {
+ public_deps = [ "../../../../gn:jsoncpp" ]
+ }
+ }
+
perfetto_unittest_source_set("unittests") {
testonly = true
sources = [
@@ -60,7 +62,7 @@
"json_utils_unittest.cc",
]
deps = [
- ":full",
+ ":json",
":minimal",
"../../../../gn:default_deps",
"../../../../gn:gtest_and_gmock",
diff --git a/src/trace_processor/importers/json/json_trace_parser_impl.cc b/src/trace_processor/importers/json/json_trace_parser_impl.cc
index 5f62162..1890498 100644
--- a/src/trace_processor/importers/json/json_trace_parser_impl.cc
+++ b/src/trace_processor/importers/json/json_trace_parser_impl.cc
@@ -24,11 +24,14 @@
#include "perfetto/base/build_config.h"
#include "perfetto/base/logging.h"
+#include "perfetto/base/status.h"
#include "perfetto/ext/base/hash.h"
#include "perfetto/ext/base/string_utils.h"
#include "perfetto/ext/base/string_view.h"
#include "src/trace_processor/importers/common/event_tracker.h"
#include "src/trace_processor/importers/common/flow_tracker.h"
+#include "src/trace_processor/importers/common/legacy_v8_cpu_profile_tracker.h"
+#include "src/trace_processor/importers/common/parser_types.h"
#include "src/trace_processor/importers/common/process_tracker.h"
#include "src/trace_processor/importers/common/slice_tracker.h"
#include "src/trace_processor/importers/common/track_tracker.h"
@@ -39,10 +42,8 @@
#include "src/trace_processor/tables/slice_tables_py.h"
#include "src/trace_processor/types/trace_processor_context.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
-#if PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
namespace {
std::optional<uint64_t> MaybeExtractFlowIdentifier(const Json::Value& value,
@@ -60,7 +61,6 @@
}
} // namespace
-#endif // PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
JsonTraceParserImpl::JsonTraceParserImpl(TraceProcessorContext* context)
: context_(context), systrace_line_parser_(context) {}
@@ -75,7 +75,6 @@
std::string string_value) {
PERFETTO_DCHECK(json::IsJsonSupported());
-#if PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
auto opt_value = json::ParseJsonString(base::StringView(string_value));
if (!opt_value) {
context_->storage->IncrementStats(stats::json_parser_failure);
@@ -205,14 +204,14 @@
const std::string& real_id = id.empty() ? global : id;
int64_t cookie = static_cast<int64_t>(
base::Hasher::Combine(cat_id.raw_id(), real_id));
- track_id = context_->track_tracker->InternLegacyChromeAsyncTrack(
+ track_id = context_->track_tracker->LegacyInternLegacyChromeAsyncTrack(
name_id, upid, cookie, false /* source_id_is_process_scoped */,
kNullStringId /* source_scope */);
} else {
PERFETTO_DCHECK(!local.empty());
int64_t cookie =
static_cast<int64_t>(base::Hasher::Combine(cat_id.raw_id(), local));
- track_id = context_->track_tracker->InternLegacyChromeAsyncTrack(
+ track_id = context_->track_tracker->LegacyInternLegacyChromeAsyncTrack(
name_id, upid, cookie, true /* source_id_is_process_scoped */,
kNullStringId /* source_scope */);
}
@@ -290,16 +289,23 @@
TrackId track_id;
if (scope == "g") {
track_id = context_->track_tracker->InternGlobalTrack(
- TrackTracker::GlobalTrackType::kChromeLegacyGlobalInstant);
+ tracks::legacy_chrome_global_instants, TrackTracker::AutoName(),
+ [this](ArgsTracker::BoundInserter& inserter) {
+ inserter.AddArg(
+ context_->storage->InternString("source"),
+ Variadic::String(context_->storage->InternString("chrome")));
+ });
} else if (scope == "p") {
if (!opt_pid) {
context_->storage->IncrementStats(stats::json_parser_failure);
break;
}
UniquePid upid = context_->process_tracker->GetOrCreateProcess(pid);
- track_id =
- context_->track_tracker->InternLegacyChromeProcessInstantTrack(
- upid);
+ track_id = context_->track_tracker->InternProcessTrack(
+ tracks::chrome_process_instant, upid);
+ context_->args_tracker->AddArgsTo(track_id).AddArg(
+ context_->storage->InternString("source"),
+ Variadic::String(context_->storage->InternString("chrome")));
} else if (scope == "t" || scope.data() == nullptr) {
if (!opt_tid) {
context_->storage->IncrementStats(stats::json_parser_failure);
@@ -381,18 +387,11 @@
}
}
}
-#else
- perfetto::base::ignore_result(timestamp);
- perfetto::base::ignore_result(context_);
- perfetto::base::ignore_result(string_value);
- PERFETTO_ELOG("Cannot parse JSON trace due to missing JSON support");
-#endif // PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
}
void JsonTraceParserImpl::MaybeAddFlow(TrackId track_id,
const Json::Value& event) {
PERFETTO_DCHECK(json::IsJsonSupported());
-#if PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
auto opt_bind_id = MaybeExtractFlowIdentifier(event, /* version2 = */ true);
if (opt_bind_id) {
FlowTracker* flow_tracker = context_->flow_tracker.get();
@@ -410,11 +409,18 @@
context_->storage->IncrementStats(stats::flow_without_direction);
}
}
-#else
- perfetto::base::ignore_result(track_id);
- perfetto::base::ignore_result(event);
-#endif // PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
}
-} // namespace trace_processor
-} // namespace perfetto
+void JsonTraceParserImpl::ParseLegacyV8ProfileEvent(
+ int64_t ts,
+ LegacyV8CpuProfileEvent event) {
+ base::Status status = context_->legacy_v8_cpu_profile_tracker->AddSample(
+ ts, event.session_id, event.pid, event.tid, event.callsite_id);
+ if (!status.ok()) {
+ context_->storage->IncrementStats(
+ stats::legacy_v8_cpu_profile_invalid_sample);
+ }
+ context_->args_tracker->Flush();
+}
+
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/json/json_trace_parser_impl.h b/src/trace_processor/importers/json/json_trace_parser_impl.h
index c675f23..8b89897 100644
--- a/src/trace_processor/importers/json/json_trace_parser_impl.h
+++ b/src/trace_processor/importers/json/json_trace_parser_impl.h
@@ -43,6 +43,7 @@
// TraceParser implementation.
void ParseJsonPacket(int64_t, std::string) override;
void ParseSystraceLine(int64_t, SystraceLine) override;
+ void ParseLegacyV8ProfileEvent(int64_t, LegacyV8CpuProfileEvent) override;
private:
TraceProcessorContext* const context_;
diff --git a/src/trace_processor/importers/json/json_trace_tokenizer.cc b/src/trace_processor/importers/json/json_trace_tokenizer.cc
index 2cceb0e..8ec36af 100644
--- a/src/trace_processor/importers/json/json_trace_tokenizer.cc
+++ b/src/trace_processor/importers/json/json_trace_tokenizer.cc
@@ -22,15 +22,15 @@
#include <memory>
#include <optional>
#include <string>
-#include <utility>
#include "perfetto/base/logging.h"
#include "perfetto/base/status.h"
#include "perfetto/ext/base/string_utils.h"
#include "perfetto/ext/base/string_view.h"
+#include "perfetto/public/compiler.h"
#include "perfetto/trace_processor/trace_blob_view.h"
+#include "src/trace_processor/importers/common/legacy_v8_cpu_profile_tracker.h"
#include "src/trace_processor/importers/json/json_utils.h"
-#include "src/trace_processor/importers/systrace/systrace_line.h"
#include "src/trace_processor/sorter/trace_sorter.h" // IWYU pragma: keep
#include "src/trace_processor/storage/stats.h"
#include "src/trace_processor/util/status_macros.h"
@@ -551,6 +551,14 @@
break;
}
+ // Metadata events may omit ts. In all other cases error:
+ std::optional<std::string> opt_raw_ph;
+ RETURN_IF_ERROR(ExtractValueForJsonKey(unparsed, "ph", &opt_raw_ph));
+ if (PERFETTO_UNLIKELY(opt_raw_ph == "P")) {
+ RETURN_IF_ERROR(ParseV8SampleEvent(unparsed));
+ continue;
+ }
+
std::optional<std::string> opt_raw_ts;
RETURN_IF_ERROR(ExtractValueForJsonKey(unparsed, "ts", &opt_raw_ts));
std::optional<int64_t> opt_ts =
@@ -563,9 +571,6 @@
if (opt_ts.has_value()) {
ts = opt_ts.value();
} else {
- // Metadata events may omit ts. In all other cases error:
- std::optional<std::string> opt_raw_ph;
- RETURN_IF_ERROR(ExtractValueForJsonKey(unparsed, "ph", &opt_raw_ph));
if (!opt_raw_ph || *opt_raw_ph != "M") {
context_->storage->IncrementStats(stats::json_tokenizer_failure);
continue;
@@ -576,6 +581,58 @@
return SetOutAndReturn(next, out);
}
+base::Status JsonTraceTokenizer::ParseV8SampleEvent(base::StringView unparsed) {
+ auto opt_evt = json::ParseJsonString(unparsed);
+ if (!opt_evt) {
+ return base::OkStatus();
+ }
+ const auto& evt = *opt_evt;
+ std::optional<uint32_t> id = base::StringToUInt32(evt["id"].asString(), 16);
+ if (!id) {
+ return base::OkStatus();
+ }
+ uint32_t pid = evt["pid"].asUInt();
+ uint32_t tid = evt["tid"].asUInt();
+ const auto& val = evt["args"]["data"];
+ if (val.isMember("startTime")) {
+ context_->legacy_v8_cpu_profile_tracker->SetStartTsForSessionAndPid(
+ *id, pid, val["startTime"].asInt64() * 1000);
+ return base::OkStatus();
+ }
+ const auto& profile = val["cpuProfile"];
+ for (const auto& n : profile["nodes"]) {
+ uint32_t node_id = n["id"].asUInt();
+ std::optional<uint32_t> parent_node_id =
+ n.isMember("parent") ? std::make_optional(n["parent"].asUInt())
+ : std::nullopt;
+ const auto& frame = n["callFrame"];
+ base::StringView url =
+ frame.isMember("url") ? frame["url"].asCString() : base::StringView();
+ base::StringView function_name = frame["functionName"].asCString();
+ base::Status status = context_->legacy_v8_cpu_profile_tracker->AddCallsite(
+ *id, pid, node_id, parent_node_id, url, function_name);
+ if (!status.ok()) {
+ context_->storage->IncrementStats(
+ stats::legacy_v8_cpu_profile_invalid_callsite);
+ continue;
+ }
+ }
+ const auto& samples = profile["samples"];
+ const auto& deltas = val["timeDeltas"];
+ if (samples.size() != deltas.size()) {
+ return base::ErrStatus(
+ "v8 legacy profile: samples and timestamps do not have same size");
+ }
+ for (uint32_t i = 0; i < samples.size(); ++i) {
+ ASSIGN_OR_RETURN(int64_t ts,
+ context_->legacy_v8_cpu_profile_tracker->AddDeltaAndGetTs(
+ *id, pid, deltas[i].asInt64() * 1000));
+ context_->sorter->PushLegacyV8CpuProfileEvent(ts, *id, pid, tid,
+ samples[i].asUInt());
+ }
+ return base::OkStatus();
+}
+
base::Status JsonTraceTokenizer::HandleDictionaryKey(const char* start,
const char* end,
const char** out) {
@@ -691,7 +748,9 @@
}
base::Status JsonTraceTokenizer::NotifyEndOfFile() {
- return position_ == TracePosition::kEof
+ return position_ == TracePosition::kEof ||
+ (position_ == TracePosition::kInsideTraceEventsArray &&
+ format_ == TraceFormat::kOnlyTraceEvents)
? base::OkStatus()
: base::ErrStatus("JSON trace file is incomplete");
}
diff --git a/src/trace_processor/importers/json/json_trace_tokenizer.h b/src/trace_processor/importers/json/json_trace_tokenizer.h
index 00af4ca..398b67d 100644
--- a/src/trace_processor/importers/json/json_trace_tokenizer.h
+++ b/src/trace_processor/importers/json/json_trace_tokenizer.h
@@ -140,6 +140,8 @@
const char* end,
const char** out);
+ base::Status ParseV8SampleEvent(base::StringView unparsed);
+
base::Status HandleTraceEvent(const char* start,
const char* end,
const char** out);
diff --git a/src/trace_processor/importers/json/json_utils.h b/src/trace_processor/importers/json/json_utils.h
index 83b79ed..3c1d8fa 100644
--- a/src/trace_processor/importers/json/json_utils.h
+++ b/src/trace_processor/importers/json/json_utils.h
@@ -17,12 +17,14 @@
#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_JSON_JSON_UTILS_H_
#define SRC_TRACE_PROCESSOR_IMPORTERS_JSON_JSON_UTILS_H_
-#include <stdint.h>
+#include <cstdint>
#include <optional>
+#include <string>
+#include "perfetto/base/build_config.h"
#include "perfetto/ext/base/string_view.h"
-
#include "src/trace_processor/importers/common/args_tracker.h"
+#include "src/trace_processor/storage/trace_storage.h"
#if PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
#include <json/value.h>
@@ -32,9 +34,7 @@
} // namespace Json
#endif
-namespace perfetto {
-namespace trace_processor {
-namespace json {
+namespace perfetto::trace_processor::json {
// Returns whether JSON related functioanlity is supported with the current
// build flags.
@@ -67,8 +67,6 @@
TraceStorage* storage,
ArgsTracker::BoundInserter* inserter);
-} // namespace json
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor::json
#endif // SRC_TRACE_PROCESSOR_IMPORTERS_JSON_JSON_UTILS_H_
diff --git a/src/trace_processor/importers/perf/spe_tokenizer.cc b/src/trace_processor/importers/perf/spe_tokenizer.cc
index 8186bbb..6c637b6 100644
--- a/src/trace_processor/importers/perf/spe_tokenizer.cc
+++ b/src/trace_processor/importers/perf/spe_tokenizer.cc
@@ -20,6 +20,7 @@
#include <cstring>
#include <memory>
#include <optional>
+#include <utility>
#include "perfetto/base/logging.h"
#include "perfetto/base/status.h"
@@ -56,7 +57,7 @@
}
bool SpeTokenizer::ProcessRecord() {
- for (auto it = buffer_.begin(); it;) {
+ for (auto it = buffer_.GetIterator(); it;) {
uint8_t byte_0 = *it;
// Must be true (we passed the for loop condition).
it.MaybeAdvance(1);
diff --git a/src/trace_processor/importers/perf_text/BUILD.gn b/src/trace_processor/importers/perf_text/BUILD.gn
new file mode 100644
index 0000000..0596c03
--- /dev/null
+++ b/src/trace_processor/importers/perf_text/BUILD.gn
@@ -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("../../../../gn/test.gni")
+
+source_set("perf_text") {
+ sources = [
+ "perf_text_trace_parser_impl.cc",
+ "perf_text_trace_parser_impl.h",
+ "perf_text_trace_tokenizer.cc",
+ "perf_text_trace_tokenizer.h",
+ ]
+ deps = [
+ ":perf_text_event",
+ ":perf_text_sample_line_parser",
+ "../../../../gn:default_deps",
+ "../../sorter",
+ "../../storage",
+ "../../tables",
+ "../../types",
+ "../../util:trace_blob_view_reader",
+ "../common",
+ ]
+}
+
+source_set("perf_text_event") {
+ sources = [ "perf_text_event.h" ]
+ deps = [
+ "../../../../gn:default_deps",
+ "../../containers",
+ "../../tables",
+ ]
+}
+
+source_set("perf_text_sample_line_parser") {
+ sources = [
+ "perf_text_sample_line_parser.cc",
+ "perf_text_sample_line_parser.h",
+ ]
+ deps = [
+ "../../../../gn:default_deps",
+ "../../../base",
+ "../../containers",
+ ]
+}
diff --git a/src/trace_processor/importers/perf_text/perf_text_event.h b/src/trace_processor/importers/perf_text/perf_text_event.h
new file mode 100644
index 0000000..ed37bb7
--- /dev/null
+++ b/src/trace_processor/importers/perf_text/perf_text_event.h
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PERF_TEXT_PERF_TEXT_EVENT_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_PERF_TEXT_PERF_TEXT_EVENT_H_
+
+#include <cstddef>
+#include <cstdint>
+#include <optional>
+
+#include "src/trace_processor/containers/string_pool.h"
+#include "src/trace_processor/tables/profiler_tables_py.h"
+
+namespace perfetto::trace_processor::perf_text_importer {
+
+struct alignas(8) PerfTextEvent {
+ std::optional<StringPool::Id> comm;
+ uint32_t tid;
+ std::optional<uint32_t> pid;
+ tables::StackProfileCallsiteTable::Id callsite_id;
+};
+
+} // namespace perfetto::trace_processor::perf_text_importer
+
+#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PERF_TEXT_PERF_TEXT_EVENT_H_
diff --git a/src/trace_processor/importers/perf_text/perf_text_sample_line_parser.cc b/src/trace_processor/importers/perf_text/perf_text_sample_line_parser.cc
new file mode 100644
index 0000000..526060f
--- /dev/null
+++ b/src/trace_processor/importers/perf_text/perf_text_sample_line_parser.cc
@@ -0,0 +1,153 @@
+/*
+ * 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.
+ */
+
+#include "src/trace_processor/importers/perf_text/perf_text_sample_line_parser.h"
+
+#include <cctype>
+#include <cstddef>
+#include <cstdint>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "perfetto/ext/base/string_utils.h"
+
+namespace perfetto::trace_processor::perf_text_importer {
+
+namespace {
+
+std::string_view FindTsAtEnd(std::string_view line) {
+ // We need to have 8 characters to have a valid timestamp with decimal
+ // and 6 trailing digits.
+ if (line.size() < 8) {
+ return {};
+ }
+ // All of the 6 trailing digits should be digits.
+ for (char c : line.substr(line.size() - 6)) {
+ if (!isdigit(c)) {
+ return {};
+ }
+ }
+ // 7 digits from the end should be a '.'.
+ if (line[line.size() - 7] != '.') {
+ return {};
+ }
+
+ // A space before the timestamp dot should exist.
+ std::string_view until_dot = line.substr(0, line.size() - 7);
+ size_t c = until_dot.rfind(' ');
+ if (c == std::string_view::npos) {
+ return {};
+ }
+
+ // All the characters between the last space and the colon should also
+ // be the digits.
+ for (char x : until_dot.substr(c + 1, until_dot.size() - c - 1)) {
+ if (!isdigit(x)) {
+ return {};
+ }
+ }
+ return line.substr(c + 1);
+}
+
+} // namespace
+
+std::optional<SampleLine> ParseSampleLine(std::string_view line) {
+ // Example of what we're parsing here:
+ // trace_processor 3962131 303057.417513: 1 cpu_atom/cycles/Pu:
+ //
+ // Find colons and look backwards to find something which looks like a
+ // timestamp. Anything before that is metadata of the sample we may be able
+ // to parse out.
+ for (size_t s = 0, cln = line.find(':', s); cln != std::string_view::npos;
+ s = cln + 1, cln = line.find(':', s)) {
+ std::string_view raw_ts = FindTsAtEnd(line.substr(0, cln));
+ if (raw_ts.empty()) {
+ continue;
+ }
+ std::optional<double> ts = base::StringToDouble(std::string(raw_ts));
+ if (!ts) {
+ continue;
+ }
+ std::string before_ts(line.data(),
+ static_cast<size_t>(raw_ts.data() - line.data()));
+
+ // simpleperf puts tabs after the comm while perf puts spaces. Make it
+ // consistent and just use spaces.
+ before_ts = base::ReplaceAll(before_ts, "\t", " ");
+
+ std::vector<std::string> pieces = base::SplitString(before_ts, " ");
+ if (pieces.empty()) {
+ continue;
+ }
+
+ size_t pos = pieces.size() - 1;
+
+ // Try to parse out the CPU in the form: '[cpu]' (e.g. '[3]').
+ std::optional<uint32_t> cpu;
+ if (base::StartsWith(pieces[pos], "[") &&
+ base::EndsWith(pieces[pos], "]")) {
+ cpu = base::StringToUInt32(pieces[pos].substr(1, pieces[pos].size() - 2));
+ if (!cpu) {
+ continue;
+ }
+ --pos;
+ }
+
+ // Try to parse out the tid and pid in the form 'pid/tid' (e.g.
+ // '1024/1025'). If there's no '/' then just try to parse it as a tid.
+ std::vector<std::string> pid_and_tid = base::SplitString(pieces[pos], "/");
+ if (pid_and_tid.size() == 0 || pid_and_tid.size() > 2) {
+ continue;
+ }
+
+ uint32_t tid_idx = pid_and_tid.size() == 1 ? 0 : 1;
+ auto opt_tid = base::StringToUInt32(pid_and_tid[tid_idx]);
+ if (!opt_tid) {
+ continue;
+ }
+ uint32_t tid = *opt_tid;
+
+ std::optional<uint32_t> pid;
+ if (pid_and_tid.size() == 2) {
+ pid = base::StringToUInt32(pid_and_tid[0]);
+ if (!pid) {
+ continue;
+ }
+ }
+
+ // All the remaining pieces are the comm which needs to be joined together
+ // with ' '.
+ pieces.resize(pos);
+ std::string comm = base::Join(pieces, " ");
+ return SampleLine{
+ comm, pid, tid, cpu, static_cast<int64_t>(*ts * 1000 * 1000 * 1000),
+ };
+ }
+ return std::nullopt;
+}
+
+bool IsPerfTextFormatTrace(const uint8_t* ptr, size_t size) {
+ std::string_view str(reinterpret_cast<const char*>(ptr), size);
+ size_t nl = str.find('\n');
+ if (nl == std::string_view::npos) {
+ return false;
+ }
+ return ParseSampleLine(str.substr(0, nl)).has_value();
+}
+
+} // namespace perfetto::trace_processor::perf_text_importer
diff --git a/src/trace_processor/importers/perf_text/perf_text_sample_line_parser.h b/src/trace_processor/importers/perf_text/perf_text_sample_line_parser.h
new file mode 100644
index 0000000..f64f6af
--- /dev/null
+++ b/src/trace_processor/importers/perf_text/perf_text_sample_line_parser.h
@@ -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.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PERF_TEXT_PERF_TEXT_SAMPLE_LINE_PARSER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_PERF_TEXT_PERF_TEXT_SAMPLE_LINE_PARSER_H_
+
+#include <cstddef>
+#include <cstdint>
+#include <optional>
+#include <string>
+#include <string_view>
+
+namespace perfetto::trace_processor::perf_text_importer {
+
+struct SampleLine {
+ std::string comm;
+ std::optional<uint32_t> pid;
+ uint32_t tid;
+ std::optional<uint32_t> cpu;
+ int64_t ts;
+};
+
+// Given a single line of a perf text sample, parses it into its components and
+// returns the result. If parsing was not possible, returns std::nullopt.
+std::optional<SampleLine> ParseSampleLine(std::string_view line);
+
+// Given a chunk of a trace file (starting at `ptr` and containing `size`
+// bytes), returns whether the file is a perf text format trace.
+bool IsPerfTextFormatTrace(const uint8_t* ptr, size_t size);
+
+} // namespace perfetto::trace_processor::perf_text_importer
+
+#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PERF_TEXT_PERF_TEXT_SAMPLE_LINE_PARSER_H_
diff --git a/src/trace_processor/importers/perf_text/perf_text_trace_parser_impl.cc b/src/trace_processor/importers/perf_text/perf_text_trace_parser_impl.cc
new file mode 100644
index 0000000..e81c4d3
--- /dev/null
+++ b/src/trace_processor/importers/perf_text/perf_text_trace_parser_impl.cc
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+#include "src/trace_processor/importers/perf_text/perf_text_trace_parser_impl.h"
+
+#include <cstdint>
+
+#include "src/trace_processor/importers/common/process_tracker.h"
+#include "src/trace_processor/importers/common/stack_profile_tracker.h"
+#include "src/trace_processor/importers/perf_text/perf_text_event.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/tables/profiler_tables_py.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto::trace_processor::perf_text_importer {
+
+PerfTextTraceParserImpl::PerfTextTraceParserImpl(TraceProcessorContext* context)
+ : context_(context) {}
+
+PerfTextTraceParserImpl::~PerfTextTraceParserImpl() = default;
+
+void PerfTextTraceParserImpl::ParsePerfTextEvent(int64_t ts,
+ PerfTextEvent evt) {
+ auto* ss = context_->storage->mutable_cpu_profile_stack_sample_table();
+ tables::CpuProfileStackSampleTable::Row row;
+ row.ts = ts;
+ row.callsite_id = evt.callsite_id;
+ row.utid = evt.pid
+ ? context_->process_tracker->UpdateThread(evt.tid, *evt.pid)
+ : context_->process_tracker->GetOrCreateThread(evt.tid);
+ if (evt.comm) {
+ context_->process_tracker->UpdateThreadNameAndMaybeProcessName(
+ evt.tid, *evt.comm, ThreadNamePriority::kOther);
+ }
+ ss->Insert(row);
+}
+
+} // namespace perfetto::trace_processor::perf_text_importer
diff --git a/src/trace_processor/importers/perf_text/perf_text_trace_parser_impl.h b/src/trace_processor/importers/perf_text/perf_text_trace_parser_impl.h
new file mode 100644
index 0000000..d586091
--- /dev/null
+++ b/src/trace_processor/importers/perf_text/perf_text_trace_parser_impl.h
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PERF_TEXT_PERF_TEXT_TRACE_PARSER_IMPL_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_PERF_TEXT_PERF_TEXT_TRACE_PARSER_IMPL_H_
+
+#include <cstdint>
+
+#include "src/trace_processor/importers/common/trace_parser.h"
+#include "src/trace_processor/importers/perf_text/perf_text_event.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto::trace_processor::perf_text_importer {
+
+class PerfTextTraceParserImpl : public PerfTextTraceParser {
+ public:
+ explicit PerfTextTraceParserImpl(TraceProcessorContext*);
+ ~PerfTextTraceParserImpl() override;
+
+ void ParsePerfTextEvent(int64_t ts, PerfTextEvent) override;
+
+ private:
+ TraceProcessorContext* const context_;
+};
+
+} // namespace perfetto::trace_processor::perf_text_importer
+
+#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PERF_TEXT_PERF_TEXT_TRACE_PARSER_IMPL_H_
diff --git a/src/trace_processor/importers/perf_text/perf_text_trace_tokenizer.cc b/src/trace_processor/importers/perf_text/perf_text_trace_tokenizer.cc
new file mode 100644
index 0000000..fb99c35
--- /dev/null
+++ b/src/trace_processor/importers/perf_text/perf_text_trace_tokenizer.cc
@@ -0,0 +1,165 @@
+/*
+ * 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.
+ */
+
+#include "src/trace_processor/importers/perf_text/perf_text_trace_tokenizer.h"
+
+#include <cctype>
+#include <cstddef>
+#include <cstdint>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include "perfetto/base/logging.h"
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/string_utils.h"
+#include "perfetto/ext/base/string_view.h"
+#include "perfetto/trace_processor/trace_blob_view.h"
+#include "src/trace_processor/importers/common/mapping_tracker.h"
+#include "src/trace_processor/importers/common/stack_profile_tracker.h"
+#include "src/trace_processor/importers/common/virtual_memory_mapping.h"
+#include "src/trace_processor/importers/perf_text/perf_text_event.h"
+#include "src/trace_processor/importers/perf_text/perf_text_sample_line_parser.h"
+#include "src/trace_processor/sorter/trace_sorter.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/util/trace_blob_view_reader.h"
+
+namespace perfetto::trace_processor::perf_text_importer {
+
+namespace {
+
+std::string_view ToStringView(const TraceBlobView& tbv) {
+ return {reinterpret_cast<const char*>(tbv.data()), tbv.size()};
+}
+
+std::string Slice(const std::string& str, size_t start, size_t end) {
+ return str.substr(start, end - start);
+}
+
+} // namespace
+
+PerfTextTraceTokenizer::PerfTextTraceTokenizer(TraceProcessorContext* ctx)
+ : context_(ctx) {}
+PerfTextTraceTokenizer::~PerfTextTraceTokenizer() = default;
+
+base::Status PerfTextTraceTokenizer::Parse(TraceBlobView blob) {
+ reader_.PushBack(std::move(blob));
+ std::vector<FrameId> frames;
+ // Loop over each sample.
+ for (;;) {
+ auto it = reader_.GetIterator();
+ auto r = it.MaybeFindAndRead('\n');
+ if (!r) {
+ return base::OkStatus();
+ }
+ // The start line of a sample. An example:
+ // trace_processor 3962131 303057.417513: 1 cpu_atom/cycles/Pu:
+ //
+ // Note that perf script output is fully configurable so we have to be
+ // parse all the optionality carefully.
+ std::string_view first_line = ToStringView(*r);
+ std::optional<SampleLine> sample = ParseSampleLine(first_line);
+ if (!sample) {
+ return base::ErrStatus(
+ "Perf text parser: unable to parse sample line (context: '%s')",
+ std::string(first_line).c_str());
+ }
+
+ // Loop over the frames in the sample.
+ for (;;) {
+ auto raw_frame = it.MaybeFindAndRead('\n');
+ // If we don't manage to parse the full stack, we should bail out.
+ if (!raw_frame) {
+ return base::OkStatus();
+ }
+ // An empty line indicates that we have reached the end of this sample.
+ std::string frame =
+ base::TrimWhitespace(std::string(ToStringView(*raw_frame)));
+ if (frame.size() == 0) {
+ break;
+ }
+
+ size_t symbol_end = frame.find(' ');
+ if (symbol_end == std::string::npos) {
+ return base::ErrStatus(
+ "Perf text parser: unable to find symbol in frame (context: '%s')",
+ frame.c_str());
+ }
+
+ size_t mapping_start = frame.rfind('(');
+ if (mapping_start == std::string::npos || frame.back() != ')') {
+ return base::ErrStatus(
+ "Perf text parser: unable to find mapping in frame (context: '%s')",
+ frame.c_str());
+ }
+
+ std::string mapping_name =
+ Slice(frame, mapping_start + 1, frame.size() - 1);
+ DummyMemoryMapping* mapping;
+ if (DummyMemoryMapping** mapping_ptr = mappings_.Find(mapping_name);
+ mapping_ptr) {
+ mapping = *mapping_ptr;
+ } else {
+ mapping = &context_->mapping_tracker->CreateDummyMapping(mapping_name);
+ PERFETTO_CHECK(mappings_.Insert(mapping_name, mapping).second);
+ }
+
+ std::string symbol_name_with_offset =
+ base::TrimWhitespace(Slice(frame, symbol_end, mapping_start));
+ size_t offset = symbol_name_with_offset.rfind('+');
+ base::StringView symbol_name(symbol_name_with_offset);
+ if (offset != std::string::npos) {
+ symbol_name = symbol_name.substr(0, offset);
+ }
+ frames.emplace_back(
+ mapping->InternDummyFrame(symbol_name, base::StringView()));
+ }
+ if (frames.empty()) {
+ return base::ErrStatus(
+ "Perf text parser: no frames in sample (context: '%s')",
+ std::string(first_line).c_str());
+ }
+
+ std::optional<CallsiteId> parent_callsite;
+ uint32_t depth = 0;
+ for (auto rit = frames.rbegin(); rit != frames.rend(); ++rit) {
+ parent_callsite = context_->stack_profile_tracker->InternCallsite(
+ parent_callsite, *rit, ++depth);
+ }
+ frames.clear();
+
+ PerfTextEvent evt;
+ if (!sample->comm.empty()) {
+ evt.comm = context_->storage->InternString(
+ base::StringView(sample->comm.data(), sample->comm.size()));
+ }
+ evt.tid = sample->tid;
+ evt.pid = sample->pid;
+ evt.callsite_id = *parent_callsite;
+
+ context_->sorter->PushPerfTextEvent(sample->ts, evt);
+ reader_.PopFrontUntil(it.file_offset());
+ }
+}
+
+base::Status PerfTextTraceTokenizer::NotifyEndOfFile() {
+ return base::OkStatus();
+}
+
+} // namespace perfetto::trace_processor::perf_text_importer
diff --git a/src/trace_processor/importers/perf_text/perf_text_trace_tokenizer.h b/src/trace_processor/importers/perf_text/perf_text_trace_tokenizer.h
new file mode 100644
index 0000000..42330db
--- /dev/null
+++ b/src/trace_processor/importers/perf_text/perf_text_trace_tokenizer.h
@@ -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.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PERF_TEXT_PERF_TEXT_TRACE_TOKENIZER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_PERF_TEXT_PERF_TEXT_TRACE_TOKENIZER_H_
+
+#include <string>
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/flat_hash_map.h"
+#include "src/trace_processor/importers/common/chunked_trace_reader.h"
+#include "src/trace_processor/importers/common/virtual_memory_mapping.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/util/trace_blob_view_reader.h"
+
+namespace perfetto::trace_processor::perf_text_importer {
+
+class PerfTextTraceTokenizer : public ChunkedTraceReader {
+ public:
+ explicit PerfTextTraceTokenizer(TraceProcessorContext*);
+ ~PerfTextTraceTokenizer() override;
+
+ base::Status Parse(TraceBlobView) override;
+ base::Status NotifyEndOfFile() override;
+
+ private:
+ TraceProcessorContext* const context_;
+ util::TraceBlobViewReader reader_;
+ base::FlatHashMap<std::string, DummyMemoryMapping*> mappings_;
+};
+
+} // namespace perfetto::trace_processor::perf_text_importer
+
+#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PERF_TEXT_PERF_TEXT_TRACE_TOKENIZER_H_
diff --git a/src/trace_processor/importers/proto/BUILD.gn b/src/trace_processor/importers/proto/BUILD.gn
index f7ebbe7..fe8ebcc 100644
--- a/src/trace_processor/importers/proto/BUILD.gn
+++ b/src/trace_processor/importers/proto/BUILD.gn
@@ -186,7 +186,9 @@
"../../../../protos/perfetto/trace/system_info:zero",
"../../../../protos/perfetto/trace/translation:zero",
"../../../base",
+ "../../../kernel_utils:syscall_table",
"../../../protozero",
+ "../../containers",
"../../sorter",
"../../storage",
"../../tables",
diff --git a/src/trace_processor/importers/proto/android_probes_module.cc b/src/trace_processor/importers/proto/android_probes_module.cc
index 31f51dc..01a65fc 100644
--- a/src/trace_processor/importers/proto/android_probes_module.cc
+++ b/src/trace_processor/importers/proto/android_probes_module.cc
@@ -155,7 +155,7 @@
}
StringId counter_name_id =
context_->storage->InternString(counter_name.string_view());
- TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+ TrackId track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kPower, counter_name_id,
[this, &desc](ArgsTracker::BoundInserter& inserter) {
StringId raw_name = context_->storage->InternString(desc.rail_name());
diff --git a/src/trace_processor/importers/proto/android_probes_parser.cc b/src/trace_processor/importers/proto/android_probes_parser.cc
index dcdae79..7c3e9f9 100644
--- a/src/trace_processor/importers/proto/android_probes_parser.cc
+++ b/src/trace_processor/importers/proto/android_probes_parser.cc
@@ -16,21 +16,34 @@
#include "src/trace_processor/importers/proto/android_probes_parser.h"
+#include <atomic>
+#include <cinttypes>
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
#include <optional>
+#include <string>
+#include "perfetto/base/logging.h"
+#include "perfetto/ext/base/status_or.h"
#include "perfetto/ext/base/string_utils.h"
-#include "perfetto/ext/traced/sys_stats_counters.h"
+#include "perfetto/ext/base/string_view.h"
#include "src/trace_processor/importers/common/args_tracker.h"
#include "src/trace_processor/importers/common/async_track_set_tracker.h"
#include "src/trace_processor/importers/common/clock_tracker.h"
#include "src/trace_processor/importers/common/event_tracker.h"
#include "src/trace_processor/importers/common/metadata_tracker.h"
#include "src/trace_processor/importers/common/process_tracker.h"
-#include "src/trace_processor/importers/syscalls/syscall_tracker.h"
-#include "src/trace_processor/types/tcp_state.h"
+#include "src/trace_processor/importers/common/slice_tracker.h"
+#include "src/trace_processor/importers/common/track_tracker.h"
+#include "src/trace_processor/importers/common/tracks.h"
+#include "src/trace_processor/importers/proto/android_probes_tracker.h"
+#include "src/trace_processor/storage/metadata.h"
+#include "src/trace_processor/storage/stats.h"
+#include "src/trace_processor/storage/trace_storage.h"
#include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/types/variadic.h"
-#include "protos/perfetto/common/android_energy_consumer_descriptor.pbzero.h"
#include "protos/perfetto/common/android_log_constants.pbzero.h"
#include "protos/perfetto/common/builtin_clock.pbzero.h"
#include "protos/perfetto/config/trace_config.pbzero.h"
@@ -42,15 +55,8 @@
#include "protos/perfetto/trace/power/android_entity_state_residency.pbzero.h"
#include "protos/perfetto/trace/power/battery_counters.pbzero.h"
#include "protos/perfetto/trace/power/power_rails.pbzero.h"
-#include "protos/perfetto/trace/ps/process_stats.pbzero.h"
-#include "protos/perfetto/trace/ps/process_tree.pbzero.h"
-#include "protos/perfetto/trace/sys_stats/sys_stats.pbzero.h"
-#include "protos/perfetto/trace/system_info.pbzero.h"
-#include "src/trace_processor/importers/proto/android_probes_tracker.h"
-
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
AndroidProbesParser::AndroidProbesParser(TraceProcessorContext* context)
: context_(context),
@@ -65,7 +71,11 @@
device_state_id_(context->storage->InternString("DeviceStateChanged")),
battery_status_id_(context->storage->InternString("BatteryStatus")),
plug_type_id_(context->storage->InternString("PlugType")),
- rail_packet_timestamp_id_(context->storage->InternString("packet_ts")) {}
+ rail_packet_timestamp_id_(context->storage->InternString("packet_ts")),
+ energy_consumer_id_(
+ context_->storage->InternString("energy_consumer_id")),
+ consumer_type_id_(context_->storage->InternString("consumer_type")),
+ ordinal_id_(context_->storage->InternString("ordinal")) {}
void AndroidProbesParser::ParseBatteryCounters(int64_t ts, ConstBytes blob) {
protos::pbzero::BatteryCounters::Decoder evt(blob.data, blob.size);
@@ -91,13 +101,13 @@
std::string("batt.").append(batt_name).append(".power_mw")));
}
if (evt.has_charge_counter_uah()) {
- TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+ TrackId track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kPower, batt_charge_id);
context_->event_tracker->PushCounter(
ts, static_cast<double>(evt.charge_counter_uah()), track);
} else if (evt.has_energy_counter_uwh() && evt.has_voltage_uv()) {
// Calculate charge counter from energy counter and voltage.
- TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+ TrackId track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kPower, batt_charge_id);
auto energy = evt.energy_counter_uwh();
auto voltage = evt.voltage_uv();
@@ -108,32 +118,32 @@
}
if (evt.has_capacity_percent()) {
- TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+ TrackId track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kPower, batt_capacity_id);
context_->event_tracker->PushCounter(
ts, static_cast<double>(evt.capacity_percent()), track);
}
if (evt.has_current_ua()) {
- TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+ TrackId track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kPower, batt_current_id);
context_->event_tracker->PushCounter(
ts, static_cast<double>(evt.current_ua()), track);
}
if (evt.has_current_avg_ua()) {
- TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+ TrackId track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kPower, batt_current_avg_id);
context_->event_tracker->PushCounter(
ts, static_cast<double>(evt.current_avg_ua()), track);
}
if (evt.has_voltage_uv()) {
- TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+ TrackId track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kPower, batt_voltage_id);
context_->event_tracker->PushCounter(
ts, static_cast<double>(evt.voltage_uv()), track);
}
if (evt.has_current_ua() && evt.has_voltage_uv()) {
// Calculate power from current and voltage.
- TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+ TrackId track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kPower, batt_power_id);
auto current = evt.current_ua();
auto voltage = evt.voltage_uv();
@@ -204,8 +214,14 @@
auto consumer_type = energy_consumer_specs->type;
auto ordinal = energy_consumer_specs->ordinal;
- TrackId energy_track = context_->track_tracker->InternEnergyCounterTrack(
- consumer_name, consumer_id, consumer_type, ordinal);
+ TrackId energy_track = context_->track_tracker->InternSingleDimensionTrack(
+ tracks::android_energy_estimation_breakdown, energy_consumer_id_,
+ Variadic::Integer(consumer_id),
+ TrackTracker::LegacyStringIdName{consumer_name},
+ [&](ArgsTracker::BoundInserter& inserter) {
+ inserter.AddArg(consumer_type_id_, Variadic::String(consumer_type));
+ inserter.AddArg(ordinal_id_, Variadic::Integer(ordinal));
+ });
context_->event_tracker->PushCounter(ts, total_energy, energy_track);
// Consumers providing per-uid energy breakdown
@@ -219,9 +235,14 @@
continue;
}
- TrackId energy_uid_track =
- context_->track_tracker->InternEnergyPerUidCounterTrack(
- consumer_name, consumer_id, breakdown.uid());
+ auto builder = context_->track_tracker->CreateDimensionsBuilder();
+ builder.AppendUid(breakdown.uid());
+ builder.AppendDimension(energy_consumer_id_,
+ Variadic::Integer(consumer_id));
+ TrackId energy_uid_track = context_->track_tracker->InternTrack(
+ tracks::android_energy_estimation_breakdown_per_uid,
+ std::move(builder).Build(),
+ TrackTracker::LegacyStringIdName{consumer_name});
context_->event_tracker->PushCounter(
ts, static_cast<double>(breakdown.energy_uws()), energy_uid_track);
}
@@ -250,7 +271,7 @@
return;
}
- TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+ TrackId track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kPower, entity_state->overall_name);
context_->event_tracker->PushCounter(
ts, double(residency.total_time_in_state_ms()), track);
@@ -428,7 +449,7 @@
ConstBytes blob) {
protos::pbzero::InitialDisplayState::Decoder state(blob.data, blob.size);
- TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+ TrackId track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kDeviceState, screen_state_id_);
context_->event_tracker->PushCounter(ts, state.display_state(), track);
}
@@ -460,7 +481,7 @@
std::optional<int32_t> state =
base::StringToInt32(kv.value().ToStdString());
if (state) {
- TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+ TrackId track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kNetwork, name_id);
context_->event_tracker->PushCounter(ts, *state, track);
}
@@ -475,7 +496,7 @@
std::optional<int32_t> state =
base::StringToInt32(kv.value().ToStdString());
if (state) {
- TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+ TrackId track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kDeviceState, *mapped_name_id);
context_->event_tracker->PushCounter(ts, *state, track);
}
@@ -483,5 +504,4 @@
}
}
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/proto/android_probes_parser.h b/src/trace_processor/importers/proto/android_probes_parser.h
index 12f81f8..04d8a24 100644
--- a/src/trace_processor/importers/proto/android_probes_parser.h
+++ b/src/trace_processor/importers/proto/android_probes_parser.h
@@ -17,13 +17,12 @@
#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_ANDROID_PROBES_PARSER_H_
#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_ANDROID_PROBES_PARSER_H_
-#include <vector>
+#include <cstdint>
#include "perfetto/protozero/field.h"
#include "src/trace_processor/storage/trace_storage.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
class TraceProcessorContext;
@@ -59,8 +58,10 @@
const StringId battery_status_id_;
const StringId plug_type_id_;
const StringId rail_packet_timestamp_id_;
+ const StringId energy_consumer_id_;
+ const StringId consumer_type_id_;
+ const StringId ordinal_id_;
};
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_ANDROID_PROBES_PARSER_H_
diff --git a/src/trace_processor/importers/proto/atoms.descriptor b/src/trace_processor/importers/proto/atoms.descriptor
index 923f583..b318814 100644
--- a/src/trace_processor/importers/proto/atoms.descriptor
+++ b/src/trace_processor/importers/proto/atoms.descriptor
Binary files differ
diff --git a/src/trace_processor/importers/proto/chrome_system_probes_parser.cc b/src/trace_processor/importers/proto/chrome_system_probes_parser.cc
index e3c95bd..aadf3f5 100644
--- a/src/trace_processor/importers/proto/chrome_system_probes_parser.cc
+++ b/src/trace_processor/importers/proto/chrome_system_probes_parser.cc
@@ -74,7 +74,7 @@
continue;
UniquePid upid = context_->process_tracker->GetOrCreateProcess(pid);
TrackId track =
- context_->track_tracker->InternProcessCounterTrack(name, upid);
+ context_->track_tracker->LegacyInternProcessCounterTrack(name, upid);
int64_t value = fld.as_int64() * 1024;
context_->event_tracker->PushCounter(ts, static_cast<double>(value),
track);
diff --git a/src/trace_processor/importers/proto/gpu_event_parser.cc b/src/trace_processor/importers/proto/gpu_event_parser.cc
index 49f7dbd..d5875d8 100644
--- a/src/trace_processor/importers/proto/gpu_event_parser.cc
+++ b/src/trace_processor/importers/proto/gpu_event_parser.cc
@@ -182,7 +182,7 @@
auto name_id = context_->storage->InternString(name);
auto desc_id = context_->storage->InternString(desc);
- auto track_id = context_->track_tracker->CreateGpuCounterTrack(
+ auto track_id = context_->track_tracker->LegacyCreateGpuCounterTrack(
name_id, 0 /* gpu_id */, desc_id, unit_id);
gpu_counter_track_ids_.emplace(counter_id, track_id);
if (spec.has_groups()) {
@@ -265,12 +265,12 @@
track.description = context_->storage->InternString(hw_queue.description());
if (gpu_hw_queue_counter_ >= gpu_hw_queue_ids_.size()) {
gpu_hw_queue_ids_.emplace_back(
- context_->track_tracker->InternGpuTrack(track));
+ context_->track_tracker->LegacyInternGpuTrack(track));
} else {
// If a gpu_render_stage_event is received before the specification, it is
// possible that the slot has already been allocated.
gpu_hw_queue_ids_[gpu_hw_queue_counter_] =
- context_->track_tracker->InternGpuTrack(track);
+ context_->track_tracker->LegacyInternGpuTrack(track);
}
} else {
// If a gpu_render_stage_event is received before the specification, a track
@@ -429,7 +429,7 @@
track.scope = gpu_render_stage_scope_id_;
track.description =
context_->storage->InternString(decoder->description());
- track_id = context_->track_tracker->InternGpuTrack(track);
+ track_id = context_->track_tracker->LegacyInternGpuTrack(track);
} else {
uint32_t id = static_cast<uint32_t>(event.hw_queue_id());
if (id < gpu_hw_queue_ids_.size() && gpu_hw_queue_ids_[id].has_value()) {
@@ -454,7 +454,7 @@
context_->storage->InternString(writer.GetStringView());
tables::GpuTrackTable::Row track(track_name);
track.scope = gpu_render_stage_scope_id_;
- track_id = context_->track_tracker->InternGpuTrack(track);
+ track_id = context_->track_tracker->LegacyInternGpuTrack(track);
gpu_hw_queue_ids_.resize(id + 1);
gpu_hw_queue_ids_[id] = track_id;
}
@@ -534,8 +534,8 @@
}
track_str_id = vulkan_memory_tracker_.FindAllocationScopeCounterString(
allocation_scope);
- track = context_->track_tracker->InternProcessCounterTrack(track_str_id,
- upid);
+ track = context_->track_tracker->LegacyInternProcessCounterTrack(
+ track_str_id, upid);
context_->event_tracker->PushCounter(
event.timestamp(),
static_cast<double>(vulkan_driver_memory_counters_[allocation_scope]),
@@ -562,8 +562,8 @@
track_str_id = vulkan_memory_tracker_.FindMemoryTypeCounterString(
memory_type,
VulkanMemoryTracker::DeviceCounterType::kAllocationCounter);
- track = context_->track_tracker->InternProcessCounterTrack(track_str_id,
- upid);
+ track = context_->track_tracker->LegacyInternProcessCounterTrack(
+ track_str_id, upid);
context_->event_tracker->PushCounter(
event.timestamp(),
static_cast<double>(
@@ -591,8 +591,8 @@
}
track_str_id = vulkan_memory_tracker_.FindMemoryTypeCounterString(
memory_type, VulkanMemoryTracker::DeviceCounterType::kBindCounter);
- track = context_->track_tracker->InternProcessCounterTrack(track_str_id,
- upid);
+ track = context_->track_tracker->LegacyInternProcessCounterTrack(
+ track_str_id, upid);
context_->event_tracker->PushCounter(
event.timestamp(),
static_cast<double>(vulkan_device_memory_counters_bind_[memory_type]),
@@ -690,7 +690,7 @@
tables::GpuTrackTable::Row track(gpu_log_track_name_id_);
track.scope = gpu_log_scope_id_;
- TrackId track_id = context_->track_tracker->InternGpuTrack(track);
+ TrackId track_id = context_->track_tracker->LegacyInternGpuTrack(track);
auto args_callback = [this, &event](ArgsTracker::BoundInserter* inserter) {
if (event.has_tag()) {
@@ -736,7 +736,7 @@
// track so that they can appear close to the render stage slices.
tables::GpuTrackTable::Row track(vk_event_track_id_);
track.scope = vk_event_scope_id_;
- TrackId track_id = context_->track_tracker->InternGpuTrack(track);
+ TrackId track_id = context_->track_tracker->LegacyInternGpuTrack(track);
tables::GpuSliceTable::Row row;
row.ts = ts;
row.dur = static_cast<int64_t>(event.duration_ns());
@@ -764,14 +764,14 @@
const uint32_t pid = gpu_mem_total.pid();
if (pid == 0) {
// Pid 0 is used to indicate the global total
- track = context_->track_tracker->InternGlobalCounterTrack(
+ track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kMemory, gpu_mem_total_name_id_, {},
gpu_mem_total_unit_id_, gpu_mem_total_global_desc_id_);
} else {
// Process emitting the packet can be different from the pid in the event.
UniqueTid utid = context_->process_tracker->UpdateThread(pid, pid);
UniquePid upid = context_->storage->thread_table()[utid].upid().value_or(0);
- track = context_->track_tracker->InternProcessCounterTrack(
+ track = context_->track_tracker->LegacyInternProcessCounterTrack(
gpu_mem_total_name_id_, upid, gpu_mem_total_unit_id_,
gpu_mem_total_proc_desc_id_);
}
diff --git a/src/trace_processor/importers/proto/graphics_frame_event_parser.cc b/src/trace_processor/importers/proto/graphics_frame_event_parser.cc
index d75c617..55075ee 100644
--- a/src/trace_processor/importers/proto/graphics_frame_event_parser.cc
+++ b/src/trace_processor/importers/proto/graphics_frame_event_parser.cc
@@ -132,7 +132,7 @@
tables::GpuTrackTable::Row track(track_name_id);
track.scope = graphics_event_scope_id_;
- TrackId track_id = context_->track_tracker->InternGpuTrack(track);
+ TrackId track_id = context_->track_tracker->LegacyInternGpuTrack(track);
auto* graphics_frame_slice_table =
context_->storage->mutable_graphics_frame_slice_table();
@@ -247,7 +247,7 @@
context_->storage->InternString(track_name.GetStringView());
tables::GpuTrackTable::Row app_track(track_name_id);
app_track.scope = graphics_event_scope_id_;
- track_id = context_->track_tracker->InternGpuTrack(app_track);
+ track_id = context_->track_tracker->LegacyInternGpuTrack(app_track);
// Error handling
auto dequeue_time = dequeue_map_.find(event_key);
@@ -301,7 +301,7 @@
context_->storage->InternString(track_name.GetStringView());
tables::GpuTrackTable::Row gpu_track(track_name_id);
gpu_track.scope = graphics_event_scope_id_;
- track_id = context_->track_tracker->InternGpuTrack(gpu_track);
+ track_id = context_->track_tracker->LegacyInternGpuTrack(gpu_track);
queue_map_[event_key] = track_id;
break;
}
@@ -332,7 +332,7 @@
context_->storage->InternString(track_name.GetStringView());
tables::GpuTrackTable::Row sf_track(track_name_id);
sf_track.scope = graphics_event_scope_id_;
- track_id = context_->track_tracker->InternGpuTrack(sf_track);
+ track_id = context_->track_tracker->LegacyInternGpuTrack(sf_track);
latch_map_[event_key] = track_id;
break;
}
@@ -356,7 +356,7 @@
context_->storage->InternString(track_name.GetStringView());
tables::GpuTrackTable::Row display_track(track_name_id);
display_track.scope = graphics_event_scope_id_;
- track_id = context_->track_tracker->InternGpuTrack(display_track);
+ track_id = context_->track_tracker->LegacyInternGpuTrack(display_track);
display_map_[layer_name_id] = track_id;
break;
}
diff --git a/src/trace_processor/importers/proto/memory_tracker_snapshot_parser.cc b/src/trace_processor/importers/proto/memory_tracker_snapshot_parser.cc
index 2bbe8f3..27296ba 100644
--- a/src/trace_processor/importers/proto/memory_tracker_snapshot_parser.cc
+++ b/src/trace_processor/importers/proto/memory_tracker_snapshot_parser.cc
@@ -20,6 +20,7 @@
#include "protos/perfetto/trace/memory_graph.pbzero.h"
#include "src/trace_processor/containers/string_pool.h"
#include "src/trace_processor/importers/common/args_tracker.h"
+#include "src/trace_processor/importers/common/track_tracker.h"
#include "src/trace_processor/tables/memory_tables_py.h"
namespace perfetto {
@@ -170,7 +171,12 @@
// For now, we use the existing global instant event track for chrome events,
// since memory dumps are global.
TrackId track_id = context_->track_tracker->InternGlobalTrack(
- TrackTracker::GlobalTrackType::kChromeLegacyGlobalInstant);
+ tracks::legacy_chrome_global_instants, TrackTracker::AutoName(),
+ [this](ArgsTracker::BoundInserter& inserter) {
+ inserter.AddArg(
+ context_->storage->InternString("source"),
+ Variadic::String(context_->storage->InternString("chrome")));
+ });
tables::MemorySnapshotTable::Row snapshot_row(
ts, track_id, level_of_detail_ids_[static_cast<size_t>(level_of_detail)]);
diff --git a/src/trace_processor/importers/proto/metadata_module.cc b/src/trace_processor/importers/proto/metadata_module.cc
index 1d43a06..c82b01f 100644
--- a/src/trace_processor/importers/proto/metadata_module.cc
+++ b/src/trace_processor/importers/proto/metadata_module.cc
@@ -105,7 +105,7 @@
protos::pbzero::Trigger::Decoder trigger(blob.data, blob.size);
StringId cat_id = kNullStringId;
TrackId track_id = context_->track_tracker->InternGlobalTrack(
- TrackTracker::GlobalTrackType::kTrigger);
+ tracks::triggers, TrackTracker::LegacyCharArrayName("Trace Triggers"));
StringId name_id = context_->storage->InternString(trigger.trigger_name());
context_->slice_tracker->Scoped(
ts, track_id, cat_id, name_id,
@@ -127,8 +127,8 @@
void MetadataModule::ParseChromeTrigger(int64_t ts, ConstBytes blob) {
protos::pbzero::ChromeTrigger::Decoder trigger(blob.data, blob.size);
StringId cat_id = kNullStringId;
- TrackId track_id = context_->track_tracker->InternGlobalTrack(
- TrackTracker::GlobalTrackType::kTrigger);
+ TrackId track_id =
+ context_->track_tracker->InternGlobalTrack(tracks::triggers);
StringId name_id;
if (trigger.has_trigger_name()) {
name_id = context_->storage->InternString(trigger.trigger_name());
diff --git a/src/trace_processor/importers/proto/metadata_module.h b/src/trace_processor/importers/proto/metadata_module.h
index 0014936..ca9b97e 100644
--- a/src/trace_processor/importers/proto/metadata_module.h
+++ b/src/trace_processor/importers/proto/metadata_module.h
@@ -52,8 +52,9 @@
void ParseTraceUuid(ConstBytes);
TraceProcessorContext* context_;
- StringId producer_name_key_id_ = kNullStringId;
- StringId trusted_producer_uid_key_id_ = kNullStringId;
+
+ const StringId producer_name_key_id_;
+ const StringId trusted_producer_uid_key_id_;
};
} // namespace trace_processor
diff --git a/src/trace_processor/importers/proto/network_trace_module_unittest.cc b/src/trace_processor/importers/proto/network_trace_module_unittest.cc
index 36ede9a..9331c18 100644
--- a/src/trace_processor/importers/proto/network_trace_module_unittest.cc
+++ b/src/trace_processor/importers/proto/network_trace_module_unittest.cc
@@ -136,15 +136,15 @@
ASSERT_EQ(slices.row_count(), 1u);
EXPECT_EQ(slices[0].ts(), 123);
- EXPECT_TRUE(HasArg(1u, "packet_length", Variadic::Integer(72)));
- EXPECT_TRUE(HasArg(1u, "socket_uid", Variadic::Integer(1010)));
- EXPECT_TRUE(HasArg(1u, "local_port", Variadic::Integer(5100)));
- EXPECT_TRUE(HasArg(1u, "remote_port", Variadic::Integer(443)));
- EXPECT_TRUE(HasArg(1u, "packet_transport",
+ EXPECT_TRUE(HasArg(2u, "packet_length", Variadic::Integer(72)));
+ EXPECT_TRUE(HasArg(2u, "socket_uid", Variadic::Integer(1010)));
+ EXPECT_TRUE(HasArg(2u, "local_port", Variadic::Integer(5100)));
+ EXPECT_TRUE(HasArg(2u, "remote_port", Variadic::Integer(443)));
+ EXPECT_TRUE(HasArg(2u, "packet_transport",
Variadic::String(storage_->InternString("IPPROTO_TCP"))));
- EXPECT_TRUE(HasArg(1u, "socket_tag",
+ EXPECT_TRUE(HasArg(2u, "socket_tag",
Variadic::String(storage_->InternString("0x407"))));
- EXPECT_TRUE(HasArg(1u, "packet_tcp_flags",
+ EXPECT_TRUE(HasArg(2u, "packet_tcp_flags",
Variadic::String(storage_->InternString(".s..a..."))));
}
@@ -176,8 +176,8 @@
EXPECT_EQ(slices[0].ts(), 123);
EXPECT_EQ(slices[1].ts(), 133);
- EXPECT_TRUE(HasArg(1u, "packet_length", Variadic::Integer(72)));
- EXPECT_TRUE(HasArg(2u, "packet_length", Variadic::Integer(100)));
+ EXPECT_TRUE(HasArg(2u, "packet_length", Variadic::Integer(72)));
+ EXPECT_TRUE(HasArg(3u, "packet_length", Variadic::Integer(100)));
}
TEST_F(NetworkTraceModuleTest, TokenizeAndParseAggregateBundle) {
@@ -201,8 +201,8 @@
EXPECT_EQ(slices[0].ts(), 123);
EXPECT_EQ(slices[0].dur(), 10);
- EXPECT_TRUE(HasArg(1u, "packet_length", Variadic::Integer(172)));
- EXPECT_TRUE(HasArg(1u, "packet_count", Variadic::Integer(2)));
+ EXPECT_TRUE(HasArg(2u, "packet_length", Variadic::Integer(172)));
+ EXPECT_TRUE(HasArg(2u, "packet_count", Variadic::Integer(2)));
}
} // namespace
diff --git a/src/trace_processor/importers/proto/perf_sample_tracker.cc b/src/trace_processor/importers/proto/perf_sample_tracker.cc
index a6384ec..4db6cc7 100644
--- a/src/trace_processor/importers/proto/perf_sample_tracker.cc
+++ b/src/trace_processor/importers/proto/perf_sample_tracker.cc
@@ -177,8 +177,9 @@
StringifyCounter(protos::pbzero::PerfEvents::SW_CPU_CLOCK));
}
- TrackId timebase_track_id = context_->track_tracker->CreatePerfCounterTrack(
- name_id, session_id, cpu, /*is_timebase=*/true);
+ TrackId timebase_track_id =
+ context_->track_tracker->LegacyCreatePerfCounterTrack(
+ name_id, session_id, cpu, /*is_timebase=*/true);
std::vector<TrackId> follower_track_ids;
if (perf_defaults.has_value()) {
@@ -186,7 +187,7 @@
follower_track_ids.reserve(name_ids.size());
for (const auto& follower_name_id : name_ids) {
follower_track_ids.push_back(
- context_->track_tracker->CreatePerfCounterTrack(
+ context_->track_tracker->LegacyCreatePerfCounterTrack(
follower_name_id, session_id, cpu, /*is_timebase=*/true));
}
}
diff --git a/src/trace_processor/importers/proto/perf_sample_tracker_unittest.cc b/src/trace_processor/importers/proto/perf_sample_tracker_unittest.cc
index d15f7f8..0f2dfbc 100644
--- a/src/trace_processor/importers/proto/perf_sample_tracker_unittest.cc
+++ b/src/trace_processor/importers/proto/perf_sample_tracker_unittest.cc
@@ -16,29 +16,34 @@
#include "src/trace_processor/importers/proto/perf_sample_tracker.h"
#include <cstdint>
+#include <memory>
#include <string>
-#include "perfetto/base/logging.h"
+#include "src/trace_processor/importers/common/cpu_tracker.h"
+#include "src/trace_processor/importers/common/global_args_tracker.h"
+#include "src/trace_processor/importers/common/machine_tracker.h"
#include "src/trace_processor/importers/common/track_tracker.h"
#include "src/trace_processor/storage/trace_storage.h"
#include "test/gtest_and_gmock.h"
#include "protos/perfetto/common/perf_events.gen.h"
#include "protos/perfetto/trace/profiling/profile_packet.gen.h"
-#include "protos/perfetto/trace/profiling/profile_packet.pbzero.h"
#include "protos/perfetto/trace/trace_packet_defaults.gen.h"
#include "protos/perfetto/trace/trace_packet_defaults.pbzero.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
namespace {
class PerfSampleTrackerTest : public ::testing::Test {
public:
PerfSampleTrackerTest() {
- context.storage.reset(new TraceStorage());
- context.track_tracker.reset(new TrackTracker(&context));
- context.perf_sample_tracker.reset(new PerfSampleTracker(&context));
+ context.storage = std::make_shared<TraceStorage>();
+ context.machine_tracker = std::make_unique<MachineTracker>(&context, 0);
+ context.cpu_tracker = std::make_unique<CpuTracker>(&context);
+ context.global_args_tracker =
+ std::make_unique<GlobalArgsTracker>(context.storage.get());
+ context.track_tracker = std::make_unique<TrackTracker>(&context);
+ context.perf_sample_tracker = std::make_unique<PerfSampleTracker>(&context);
}
protected:
@@ -299,5 +304,4 @@
}
} // namespace
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/proto/pigweed_detokenizer.cc b/src/trace_processor/importers/proto/pigweed_detokenizer.cc
index b3a97b8..9e3ed8d 100644
--- a/src/trace_processor/importers/proto/pigweed_detokenizer.cc
+++ b/src/trace_processor/importers/proto/pigweed_detokenizer.cc
@@ -159,26 +159,28 @@
formatted_size =
perfetto::base::SprintfTrunc(buffer, kFormatBufferSize, fmt, value);
} else {
- uint64_t value;
+ uint64_t raw;
auto old_ptr = ptr;
ptr = protozero::proto_utils::ParseVarInt(ptr, bytes.data + bytes.size,
- &value);
+ &raw);
if (old_ptr == ptr) {
return base::ErrStatus("Truncated Pigweed varint");
}
+ // All Pigweed integers (including unsigned) are zigzag encoded.
+ int64_t value = ::protozero::proto_utils::ZigZagDecode(raw);
if (arg.type == kSignedInt) {
- int64_t value_signed;
- memcpy(&value_signed, &value, sizeof(value_signed));
- args.push_back(value_signed);
- formatted_size = perfetto::base::SprintfTrunc(buffer, kFormatBufferSize,
- fmt, value_signed);
- } else {
- if (arg.type == kUnsigned32) {
- value &= 0xFFFFFFFFu;
- }
args.push_back(value);
formatted_size =
perfetto::base::SprintfTrunc(buffer, kFormatBufferSize, fmt, value);
+ } else {
+ uint64_t value_unsigned;
+ memcpy(&value_unsigned, &value, sizeof(value_unsigned));
+ if (arg.type == kUnsigned32) {
+ value_unsigned &= 0xFFFFFFFFu;
+ }
+ args.push_back(value_unsigned);
+ formatted_size = perfetto::base::SprintfTrunc(buffer, kFormatBufferSize,
+ fmt, value_unsigned);
}
}
if (formatted_size == kFormatBufferSize - 1) {
diff --git a/src/trace_processor/importers/proto/pixel_modem_parser.cc b/src/trace_processor/importers/proto/pixel_modem_parser.cc
index 56e08b3..0119f54 100644
--- a/src/trace_processor/importers/proto/pixel_modem_parser.cc
+++ b/src/trace_processor/importers/proto/pixel_modem_parser.cc
@@ -64,6 +64,7 @@
detokenizer_(pigweed::CreateNullDetokenizer()),
template_id_(context->storage->InternString("raw_template")),
token_id_(context->storage->InternString("token_id")),
+ token_id_hex_(context->storage->InternString("token_id_hex")),
packet_timestamp_id_(context->storage->InternString("packet_ts")) {}
PixelModemParser::~PixelModemParser() = default;
@@ -103,13 +104,17 @@
inserter->AddArg(template_id_,
Variadic::String(context_->storage->InternString(
detokenized_str.template_str().c_str())));
- inserter->AddArg(token_id_, Variadic::Integer(detokenized_str.token()));
+ uint32_t token = detokenized_str.token();
+ inserter->AddArg(token_id_, Variadic::Integer(token));
+ inserter->AddArg(token_id_hex_,
+ Variadic::String(context_->storage->InternString(
+ base::IntToHexString(token).c_str())));
inserter->AddArg(packet_timestamp_id_,
Variadic::UnsignedInteger(trace_packet_ts));
auto pw_args = detokenized_str.args();
for (size_t i = 0; i < pw_args.size(); i++) {
StringId arg_name = context_->storage->InternString(
- ("pw_token_" + std::to_string(detokenized_str.token()) + ".arg_" +
+ ("pw_token_" + std::to_string(token) + ".arg_" +
std::to_string(i))
.c_str());
auto arg = pw_args[i];
diff --git a/src/trace_processor/importers/proto/pixel_modem_parser.h b/src/trace_processor/importers/proto/pixel_modem_parser.h
index a505a0a..bdb6d18 100644
--- a/src/trace_processor/importers/proto/pixel_modem_parser.h
+++ b/src/trace_processor/importers/proto/pixel_modem_parser.h
@@ -41,6 +41,7 @@
const StringId template_id_;
const StringId token_id_;
+ const StringId token_id_hex_;
const StringId packet_timestamp_id_;
};
diff --git a/src/trace_processor/importers/proto/profile_packet_sequence_state_unittest.cc b/src/trace_processor/importers/proto/profile_packet_sequence_state_unittest.cc
index 2022952..88d492a 100644
--- a/src/trace_processor/importers/proto/profile_packet_sequence_state_unittest.cc
+++ b/src/trace_processor/importers/proto/profile_packet_sequence_state_unittest.cc
@@ -16,10 +16,15 @@
#include "src/trace_processor/importers/proto/profile_packet_sequence_state.h"
+#include <cstddef>
#include <cstdint>
#include <memory>
#include <optional>
+#include <string>
+#include "perfetto/ext/base/string_view.h"
+#include "perfetto/ext/base/utils.h"
+#include "perfetto/trace_processor/ref_counted.h"
#include "src/trace_processor/importers/common/mapping_tracker.h"
#include "src/trace_processor/importers/common/stack_profile_tracker.h"
#include "src/trace_processor/importers/proto/packet_sequence_state_generation.h"
@@ -131,7 +136,7 @@
profile_packet_sequence_state().FinalizeProfile();
EXPECT_THAT(context.storage->stack_profile_mapping_table()[0].build_id(),
- context.storage->InternString({kBuildIDHexName}));
+ context.storage->InternString(kBuildIDHexName));
EXPECT_THAT(context.storage->stack_profile_mapping_table()[0].exact_offset(),
kMappingExactOffset);
EXPECT_THAT(context.storage->stack_profile_mapping_table()[0].start_offset(),
diff --git a/src/trace_processor/importers/proto/proto_trace_parser_impl.cc b/src/trace_processor/importers/proto/proto_trace_parser_impl.cc
index 393bd79..f4b6fca 100644
--- a/src/trace_processor/importers/proto/proto_trace_parser_impl.cc
+++ b/src/trace_processor/importers/proto/proto_trace_parser_impl.cc
@@ -158,18 +158,6 @@
context_->args_tracker->Flush();
}
-void ProtoTraceParserImpl::ParseLegacyV8ProfileEvent(
- int64_t ts,
- LegacyV8CpuProfileEvent event) {
- base::Status status = context_->legacy_v8_cpu_profile_tracker->AddSample(
- ts, event.session_id, event.pid, event.tid, event.callsite_id);
- if (!status.ok()) {
- context_->storage->IncrementStats(
- stats::legacy_v8_cpu_profile_invalid_sample);
- }
- context_->args_tracker->Flush();
-}
-
void ProtoTraceParserImpl::ParseChromeEvents(int64_t ts, ConstBytes blob) {
TraceStorage* storage = context_->storage.get();
protos::pbzero::ChromeEventBundle::Decoder bundle(blob.data, blob.size);
@@ -367,7 +355,7 @@
name_id = context_->storage->InternString(event.counter_name());
}
TrackId track =
- context_->track_tracker->InternThreadCounterTrack(name_id, utid);
+ context_->track_tracker->LegacyInternThreadCounterTrack(name_id, utid);
auto opt_id =
context_->event_tracker->PushCounter(ts, event.counter_value(), track);
if (opt_id) {
diff --git a/src/trace_processor/importers/proto/proto_trace_parser_impl.h b/src/trace_processor/importers/proto/proto_trace_parser_impl.h
index f6b2304..0c1db93 100644
--- a/src/trace_processor/importers/proto/proto_trace_parser_impl.h
+++ b/src/trace_processor/importers/proto/proto_trace_parser_impl.h
@@ -61,8 +61,6 @@
int64_t /*ts*/,
InlineSchedWaking data) override;
- void ParseLegacyV8ProfileEvent(int64_t ts, LegacyV8CpuProfileEvent) override;
-
private:
StringId GetMetatraceInternedString(uint64_t iid);
diff --git a/src/trace_processor/importers/proto/proto_trace_parser_impl_unittest.cc b/src/trace_processor/importers/proto/proto_trace_parser_impl_unittest.cc
index 53aa4f1..229a658 100644
--- a/src/trace_processor/importers/proto/proto_trace_parser_impl_unittest.cc
+++ b/src/trace_processor/importers/proto/proto_trace_parser_impl_unittest.cc
@@ -125,7 +125,7 @@
// a NAN must return false.
double d_exp = exp;
double d_arg = arg;
- if (isnan(d_exp) || isnan(d_arg))
+ if (std::isnan(d_exp) || std::isnan(d_arg))
return false;
return fabs(d_arg - d_exp) < 1e-128;
}
@@ -2465,31 +2465,29 @@
const auto& cpu_table = storage_->cpu_table();
EXPECT_EQ(cpu_table[ucpu.value].cpu(), 0u);
EXPECT_EQ(raw_table[0].utid(), 1u);
- EXPECT_EQ(raw_table[0].arg_set_id(), 1u);
+ EXPECT_EQ(raw_table[0].arg_set_id(), 3u);
- EXPECT_GE(storage_->arg_table().row_count(), 10u);
-
- EXPECT_TRUE(HasArg(1u, storage_->InternString("legacy_event.category"),
+ EXPECT_TRUE(HasArg(3u, storage_->InternString("legacy_event.category"),
Variadic::String(cat_1)));
- EXPECT_TRUE(HasArg(1u, storage_->InternString("legacy_event.name"),
+ EXPECT_TRUE(HasArg(3u, storage_->InternString("legacy_event.name"),
Variadic::String(ev_1)));
- EXPECT_TRUE(HasArg(1u, storage_->InternString("legacy_event.phase"),
+ EXPECT_TRUE(HasArg(3u, storage_->InternString("legacy_event.phase"),
Variadic::String(question)));
- EXPECT_TRUE(HasArg(1u, storage_->InternString("legacy_event.duration_ns"),
+ EXPECT_TRUE(HasArg(3u, storage_->InternString("legacy_event.duration_ns"),
Variadic::Integer(23000)));
- EXPECT_TRUE(HasArg(1u,
+ EXPECT_TRUE(HasArg(3u,
storage_->InternString("legacy_event.thread_timestamp_ns"),
Variadic::Integer(2005000)));
- EXPECT_TRUE(HasArg(1u,
+ EXPECT_TRUE(HasArg(3u,
storage_->InternString("legacy_event.thread_duration_ns"),
Variadic::Integer(15000)));
- EXPECT_TRUE(HasArg(1u, storage_->InternString("legacy_event.use_async_tts"),
+ EXPECT_TRUE(HasArg(3u, storage_->InternString("legacy_event.use_async_tts"),
Variadic::Boolean(true)));
- EXPECT_TRUE(HasArg(1u, storage_->InternString("legacy_event.global_id"),
+ EXPECT_TRUE(HasArg(3u, storage_->InternString("legacy_event.global_id"),
Variadic::UnsignedInteger(99u)));
- EXPECT_TRUE(HasArg(1u, storage_->InternString("legacy_event.id_scope"),
+ EXPECT_TRUE(HasArg(3u, storage_->InternString("legacy_event.id_scope"),
Variadic::String(scope_1)));
- EXPECT_TRUE(HasArg(1u, debug_an_1, Variadic::UnsignedInteger(10u)));
+ EXPECT_TRUE(HasArg(3u, debug_an_1, Variadic::UnsignedInteger(10u)));
}
TEST_F(ProtoTraceParserTest, TrackEventLegacyTimestampsWithClockSnapshot) {
diff --git a/src/trace_processor/importers/proto/proto_trace_reader.cc b/src/trace_processor/importers/proto/proto_trace_reader.cc
index 1e72da0..6776fbd 100644
--- a/src/trace_processor/importers/proto/proto_trace_reader.cc
+++ b/src/trace_processor/importers/proto/proto_trace_reader.cc
@@ -23,6 +23,7 @@
#include <map>
#include <numeric>
#include <optional>
+#include <string>
#include <tuple>
#include <utility>
#include <vector>
@@ -109,15 +110,19 @@
// Assert that the packet is parsed using the right instance of reader.
PERFETTO_DCHECK(decoder.has_machine_id() == !!context_->machine_id());
- const uint32_t seq_id = decoder.trusted_packet_sequence_id();
- auto* state = GetIncrementalStateForPacketSequence(seq_id);
+ uint32_t seq_id = decoder.trusted_packet_sequence_id();
+ auto [scoped_state, inserted] = sequence_state_.Insert(seq_id, {});
+ if (decoder.has_trusted_packet_sequence_id()) {
+ if (!inserted && decoder.previous_packet_dropped()) {
+ ++scoped_state->previous_packet_dropped_count;
+ }
+ }
if (decoder.first_packet_on_sequence()) {
HandleFirstPacketOnSequence(seq_id);
}
uint32_t sequence_flags = decoder.sequence_flags();
-
if (decoder.incremental_state_cleared() ||
sequence_flags &
protos::pbzero::TracePacket::SEQ_INCREMENTAL_STATE_CLEARED) {
@@ -126,16 +131,6 @@
HandlePreviousPacketDropped(decoder);
}
- uint32_t sequence_id = decoder.trusted_packet_sequence_id();
- if (sequence_id) {
- auto [data_loss, inserted] =
- packet_sequence_data_loss_.Insert(sequence_id, 0);
-
- if (!inserted && decoder.previous_packet_dropped()) {
- *data_loss += 1;
- }
- }
-
// It is important that we parse defaults before parsing other fields such as
// the timestamp, since the defaults could affect them.
if (decoder.has_trace_packet_defaults()) {
@@ -149,7 +144,7 @@
}
if (decoder.has_clock_snapshot()) {
- return ParseClockSnapshot(decoder.clock_snapshot(), sequence_id);
+ return ParseClockSnapshot(decoder.clock_snapshot(), seq_id);
}
if (decoder.has_trace_stats()) {
@@ -171,6 +166,7 @@
return ParseExtensionDescriptor(decoder.extension_descriptor());
}
+ auto* state = GetIncrementalStateForPacketSequence(seq_id);
if (decoder.sequence_flags() &
protos::pbzero::TracePacket::SEQ_NEEDS_INCREMENTAL_STATE) {
if (!seq_id) {
@@ -179,6 +175,7 @@
"TraceWriter's sequence_id is zero (the service is "
"probably too old)");
}
+ scoped_state->needs_incremental_state_total++;
if (!state->IsIncrementalStateValid()) {
if (context_->content_analyzer) {
@@ -189,6 +186,7 @@
invalid_incremental_state_key_id_);
PacketAnalyzer::Get(context_)->ProcessPacket(packet, annotation);
}
+ scoped_state->needs_incremental_state_skipped++;
context_->storage->IncrementStats(stats::tokenizer_skipped_packets);
return base::OkStatus();
}
@@ -286,13 +284,20 @@
}
void ProtoTraceReader::ParseTraceConfig(protozero::ConstBytes blob) {
- protos::pbzero::TraceConfig::Decoder trace_config(blob);
- if (trace_config.write_into_file() && !trace_config.flush_period_ms()) {
- PERFETTO_ELOG(
- "It is strongly recommended to have flush_period_ms set when "
- "write_into_file is turned on. This trace will be loaded fully "
- "into memory before sorting which increases the likelihood of "
- "OOMs.");
+ using Config = protos::pbzero::TraceConfig;
+ Config::Decoder trace_config(blob);
+ if (trace_config.write_into_file()) {
+ if (!trace_config.flush_period_ms()) {
+ context_->storage->IncrementStats(stats::config_write_into_file_no_flush);
+ }
+ int i = 0;
+ for (auto it = trace_config.buffers(); it; ++it, ++i) {
+ Config::BufferConfig::Decoder buf(*it);
+ if (buf.fill_policy() == Config::BufferConfig::FillPolicy::DISCARD) {
+ context_->storage->IncrementIndexedStats(
+ stats::config_write_into_file_discard, i);
+ }
+ }
}
}
@@ -617,6 +622,20 @@
if (tse.read_tracing_buffers_completed()) {
context_->sorter->NotifyReadBufferEvent();
}
+ if (tse.has_slow_starting_data_sources()) {
+ protos::pbzero::TracingServiceEvent::DataSources::Decoder msg(
+ tse.slow_starting_data_sources());
+ for (auto it = msg.data_source(); it; it++) {
+ protos::pbzero::TracingServiceEvent::DataSources::DataSource::Decoder
+ data_source(*it);
+ std::string formatted = data_source.producer_name().ToStdString() + " " +
+ data_source.data_source_name().ToStdString();
+ context_->metadata_tracker->AppendMetadata(
+ metadata::slow_start_data_source,
+ Variadic::String(
+ context_->storage->InternString(base::StringView(formatted))));
+ }
+ }
return base::OkStatus();
}
@@ -721,21 +740,30 @@
static_cast<int64_t>(buf.trace_writer_packet_loss()));
}
- base::FlatHashMap<int32_t, int64_t> data_loss_per_buffer;
-
+ struct BufStats {
+ uint32_t packet_loss = 0;
+ uint32_t incremental_sequences_dropped = 0;
+ };
+ base::FlatHashMap<int32_t, BufStats> stats_per_buffer;
for (auto it = evt.writer_stats(); it; ++it) {
- protos::pbzero::TraceStats::WriterStats::Decoder writer(*it);
- auto* data_loss = packet_sequence_data_loss_.Find(
- static_cast<uint32_t>(writer.sequence_id()));
- if (data_loss) {
- data_loss_per_buffer[static_cast<int32_t>(writer.buffer())] +=
- static_cast<int64_t>(*data_loss);
+ protos::pbzero::TraceStats::WriterStats::Decoder w(*it);
+ auto seq_id = static_cast<uint32_t>(w.sequence_id());
+ if (auto* s = sequence_state_.Find(seq_id)) {
+ auto& stats = stats_per_buffer[static_cast<int32_t>(w.buffer())];
+ stats.packet_loss += s->previous_packet_dropped_count;
+ stats.incremental_sequences_dropped +=
+ s->needs_incremental_state_skipped > 0 &&
+ s->needs_incremental_state_skipped ==
+ s->needs_incremental_state_total;
}
}
- for (auto it = data_loss_per_buffer.GetIterator(); it; ++it) {
+ for (auto it = stats_per_buffer.GetIterator(); it; ++it) {
+ auto& v = it.value();
storage->SetIndexedStats(stats::traced_buf_sequence_packet_loss, it.key(),
- it.value());
+ v.packet_loss);
+ storage->SetIndexedStats(stats::traced_buf_incremental_sequences_dropped,
+ it.key(), v.incremental_sequences_dropped);
}
}
diff --git a/src/trace_processor/importers/proto/proto_trace_reader.h b/src/trace_processor/importers/proto/proto_trace_reader.h
index 9864156..fa9986e 100644
--- a/src/trace_processor/importers/proto/proto_trace_reader.h
+++ b/src/trace_processor/importers/proto/proto_trace_reader.h
@@ -76,6 +76,13 @@
std::optional<StringId> GetBuiltinClockNameOrNull(int64_t clock_id);
private:
+ struct SequenceScopedState {
+ std::optional<PacketSequenceStateBuilder> sequence_state_builder;
+ uint32_t previous_packet_dropped_count = 0;
+ uint32_t needs_incremental_state_total = 0;
+ uint32_t needs_incremental_state_skipped = 0;
+ };
+
using ConstBytes = protozero::ConstBytes;
base::Status ParsePacket(TraceBlobView);
base::Status ParseServiceEvent(int64_t ts, ConstBytes);
@@ -89,7 +96,7 @@
TraceBlobView trace_packet_defaults);
void ParseInternedData(const protos::pbzero::TracePacket_Decoder&,
TraceBlobView interned_data);
- static void ParseTraceConfig(ConstBytes);
+ void ParseTraceConfig(ConstBytes);
void ParseTraceStats(ConstBytes);
static base::FlatHashMap<int64_t /*Clock Id*/, int64_t /*Offset*/>
@@ -97,13 +104,11 @@
PacketSequenceStateBuilder* GetIncrementalStateForPacketSequence(
uint32_t sequence_id) {
- auto* builder = packet_sequence_state_builders_.Find(sequence_id);
- if (builder == nullptr) {
- builder = packet_sequence_state_builders_
- .Insert(sequence_id, PacketSequenceStateBuilder(context_))
- .first;
+ auto& builder = sequence_state_.Find(sequence_id)->sequence_state_builder;
+ if (!builder) {
+ builder = PacketSequenceStateBuilder(context_);
}
- return builder;
+ return &*builder;
}
base::Status ParseExtensionDescriptor(ConstBytes descriptor);
@@ -115,11 +120,7 @@
// timestamp given is latest_timestamp_.
int64_t latest_timestamp_ = 0;
- base::FlatHashMap<uint32_t, PacketSequenceStateBuilder>
- packet_sequence_state_builders_;
-
- base::FlatHashMap<uint32_t, size_t> packet_sequence_data_loss_;
-
+ base::FlatHashMap<uint32_t, SequenceScopedState> sequence_state_;
StringId skipped_packet_key_id_;
StringId invalid_incremental_state_key_id_;
};
diff --git a/src/trace_processor/importers/proto/system_probes_parser.cc b/src/trace_processor/importers/proto/system_probes_parser.cc
index c8eb1d2..08212a8 100644
--- a/src/trace_processor/importers/proto/system_probes_parser.cc
+++ b/src/trace_processor/importers/proto/system_probes_parser.cc
@@ -16,14 +16,27 @@
#include "src/trace_processor/importers/proto/system_probes_parser.h"
+#include <algorithm>
+#include <array>
+#include <cstddef>
#include <cstdint>
#include <optional>
+#include <string>
+#include <utility>
+#include <variant>
+#include <vector>
#include "perfetto/base/logging.h"
+#include "perfetto/ext/base/status_or.h"
#include "perfetto/ext/base/string_utils.h"
#include "perfetto/ext/base/string_view.h"
#include "perfetto/ext/traced/sys_stats_counters.h"
+#include "perfetto/protozero/field.h"
#include "perfetto/protozero/proto_decoder.h"
+#include "perfetto/public/compiler.h"
+#include "protos/perfetto/trace/sys_stats/sys_stats.pbzero.h"
+#include "src/kernel_utils/syscall_table.h"
+#include "src/trace_processor/containers/string_pool.h"
#include "src/trace_processor/importers/common/clock_tracker.h"
#include "src/trace_processor/importers/common/cpu_tracker.h"
#include "src/trace_processor/importers/common/event_tracker.h"
@@ -31,8 +44,12 @@
#include "src/trace_processor/importers/common/process_tracker.h"
#include "src/trace_processor/importers/common/system_info_tracker.h"
#include "src/trace_processor/importers/common/track_tracker.h"
+#include "src/trace_processor/importers/common/tracks.h"
#include "src/trace_processor/importers/syscalls/syscall_tracker.h"
#include "src/trace_processor/storage/metadata.h"
+#include "src/trace_processor/storage/stats.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/tables/metadata_tables_py.h"
#include "src/trace_processor/types/trace_processor_context.h"
#include "protos/perfetto/common/builtin_clock.pbzero.h"
@@ -40,6 +57,7 @@
#include "protos/perfetto/trace/ps/process_tree.pbzero.h"
#include "protos/perfetto/trace/system_info.pbzero.h"
#include "protos/perfetto/trace/system_info/cpu_info.pbzero.h"
+#include "src/trace_processor/types/variadic.h"
namespace {
@@ -49,8 +67,7 @@
} // namespace
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
namespace {
@@ -139,6 +156,7 @@
bytes_unit_id_(context->storage->InternString("bytes")),
available_chunks_unit_id_(
context->storage->InternString("available chunks")),
+ irq_id_(context->storage->InternString("irq")),
num_forks_name_id_(context->storage->InternString("num_forks")),
num_irq_total_name_id_(context->storage->InternString("num_irq_total")),
num_softirq_total_name_id_(
@@ -147,6 +165,7 @@
thermal_unit_id_(context->storage->InternString("C")),
gpufreq_id(context->storage->InternString("gpufreq")),
gpufreq_unit_id(context->storage->InternString("MHz")),
+ cpu_stat_counter_name_id_(context->storage->InternString("counter_name")),
arm_cpu_implementer(
context->storage->InternString("arm_cpu_implementer")),
arm_cpu_architecture(
@@ -231,7 +250,7 @@
base::StackString<512> track_name("%s.%s", tag_prefix.c_str(),
counter_name);
StringId string_id = context_->storage->InternString(track_name.c_str());
- TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+ TrackId track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kIo, string_id);
context_->event_tracker->PushCounter(ts, value, track);
};
@@ -306,7 +325,7 @@
continue;
}
// /proc/meminfo counters are in kB, convert to bytes
- TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+ TrackId track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kMemory, meminfo_strs_id_[key], {},
bytes_unit_id_);
context_->event_tracker->PushCounter(
@@ -323,7 +342,7 @@
"%.*s %.*s", int(key.size()), key.data(), int(devfreq_subtitle.size()),
devfreq_subtitle.data());
StringId name = context_->storage->InternString(counter_name.string_view());
- TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+ TrackId track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kClockFrequency, name);
context_->event_tracker->PushCounter(ts, static_cast<double>(vm.value()),
track);
@@ -332,7 +351,7 @@
uint32_t c = 0;
for (auto it = sys_stats.cpufreq_khz(); it; ++it, ++c) {
TrackId track = context_->track_tracker->InternCpuCounterTrack(
- TrackTracker::CpuCounterTrackType::kFrequency, c);
+ tracks::cpu_frequency, c, TrackTracker::LegacyCharArrayName{"cpufreq"});
context_->event_tracker->PushCounter(ts, static_cast<double>(*it), track);
}
@@ -344,7 +363,7 @@
context_->storage->IncrementStats(stats::vmstat_unknown_keys);
continue;
}
- TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+ TrackId track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kMemory, vmstat_strs_id_[key]);
context_->event_tracker->PushCounter(ts, static_cast<double>(vm.value()),
track);
@@ -358,76 +377,78 @@
continue;
}
- TrackId track = context_->track_tracker->InternCpuCounterTrack(
- TrackTracker::CpuCounterTrackType::kUserTime, ct.cpu_id());
- context_->event_tracker->PushCounter(ts, static_cast<double>(ct.user_ns()),
- track);
-
- track = context_->track_tracker->InternCpuCounterTrack(
- TrackTracker::CpuCounterTrackType::kNiceUserTime, ct.cpu_id());
+ auto ucpu = context_->cpu_tracker->GetOrCreateCpu(ct.cpu_id());
+ auto intern_track =
+ [&, this](TrackTracker::LegacyCharArrayName name) -> TrackId {
+ auto builder = context_->track_tracker->CreateDimensionsBuilder();
+ builder.AppendDimension(
+ cpu_stat_counter_name_id_,
+ Variadic::String(context_->storage->InternString(name.name)));
+ builder.AppendUcpu(ucpu);
+ return context_->track_tracker->InternCounterTrack(
+ tracks::cpu_stat, std::move(builder).Build(), name);
+ };
context_->event_tracker->PushCounter(
- ts, static_cast<double>(ct.user_nice_ns()), track);
-
- track = context_->track_tracker->InternCpuCounterTrack(
- TrackTracker::CpuCounterTrackType::kSystemModeTime, ct.cpu_id());
+ ts, static_cast<double>(ct.user_ns()),
+ intern_track(TrackTracker::LegacyCharArrayName{"cpu.times.user_ns"}));
context_->event_tracker->PushCounter(
- ts, static_cast<double>(ct.system_mode_ns()), track);
-
- track = context_->track_tracker->InternCpuCounterTrack(
- TrackTracker::CpuCounterTrackType::kIdleTime, ct.cpu_id());
- context_->event_tracker->PushCounter(ts, static_cast<double>(ct.idle_ns()),
- track);
-
- track = context_->track_tracker->InternCpuCounterTrack(
- TrackTracker::CpuCounterTrackType::kIoWaitTime, ct.cpu_id());
+ ts, static_cast<double>(ct.user_nice_ns()),
+ intern_track(
+ TrackTracker::LegacyCharArrayName{"cpu.times.user_nice_ns"}));
context_->event_tracker->PushCounter(
- ts, static_cast<double>(ct.io_wait_ns()), track);
-
- track = context_->track_tracker->InternCpuCounterTrack(
- TrackTracker::CpuCounterTrackType::kIrqTime, ct.cpu_id());
- context_->event_tracker->PushCounter(ts, static_cast<double>(ct.irq_ns()),
- track);
-
- track = context_->track_tracker->InternCpuCounterTrack(
- TrackTracker::CpuCounterTrackType::kSoftIrqTime, ct.cpu_id());
+ ts, static_cast<double>(ct.system_mode_ns()),
+ intern_track(
+ TrackTracker::LegacyCharArrayName{"cpu.times.system_mode_ns"}));
context_->event_tracker->PushCounter(
- ts, static_cast<double>(ct.softirq_ns()), track);
+ ts, static_cast<double>(ct.idle_ns()),
+ intern_track(TrackTracker::LegacyCharArrayName{"cpu.times.idle_ns"}));
+ context_->event_tracker->PushCounter(
+ ts, static_cast<double>(ct.io_wait_ns()),
+ intern_track(
+ TrackTracker::LegacyCharArrayName{"cpu.times.io_wait_ns"}));
+ context_->event_tracker->PushCounter(
+ ts, static_cast<double>(ct.irq_ns()),
+ intern_track(TrackTracker::LegacyCharArrayName{"cpu.times.irq_ns"}));
+ context_->event_tracker->PushCounter(
+ ts, static_cast<double>(ct.softirq_ns()),
+ intern_track(
+ TrackTracker::LegacyCharArrayName{"cpu.times.softirq_ns"}));
}
for (auto it = sys_stats.num_irq(); it; ++it) {
protos::pbzero::SysStats::InterruptCount::Decoder ic(*it);
-
- TrackId track = context_->track_tracker->InternIrqCounterTrack(
- TrackTracker::IrqCounterTrackType::kCount, ic.irq());
+ TrackId track = context_->track_tracker->InternSingleDimensionTrack(
+ tracks::irq_counter, irq_id_, Variadic::Integer(ic.irq()),
+ TrackTracker::LegacyCharArrayName{"num_irq"});
context_->event_tracker->PushCounter(ts, static_cast<double>(ic.count()),
track);
}
for (auto it = sys_stats.num_softirq(); it; ++it) {
protos::pbzero::SysStats::InterruptCount::Decoder ic(*it);
-
- TrackId track = context_->track_tracker->InternSoftirqCounterTrack(
- TrackTracker::SoftIrqCounterTrackType::kCount, ic.irq());
+ TrackId track = context_->track_tracker->InternSingleDimensionTrack(
+ tracks::softirq_counter, irq_id_, Variadic::Integer(ic.irq()),
+ TrackTracker::LegacyCharArrayName{"num_softirq"});
context_->event_tracker->PushCounter(ts, static_cast<double>(ic.count()),
track);
}
if (sys_stats.has_num_forks()) {
- TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+ TrackId track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kDeviceState, num_forks_name_id_);
context_->event_tracker->PushCounter(
ts, static_cast<double>(sys_stats.num_forks()), track);
}
if (sys_stats.has_num_irq_total()) {
- TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+ TrackId track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kDeviceState, num_irq_total_name_id_);
context_->event_tracker->PushCounter(
ts, static_cast<double>(sys_stats.num_irq_total()), track);
}
if (sys_stats.has_num_softirq_total()) {
- TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+ TrackId track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kDeviceState, num_softirq_total_name_id_);
context_->event_tracker->PushCounter(
ts, static_cast<double>(sys_stats.num_softirq_total()), track);
@@ -448,7 +469,7 @@
chunk_size_kb);
StringId name =
context_->storage->InternString(counter_name.string_view());
- TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+ TrackId track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kMemory, name, {}, available_chunks_unit_id_);
context_->event_tracker->PushCounter(ts, static_cast<double>(*order_it),
track);
@@ -475,7 +496,7 @@
// Unit = total blocked time on this resource in nanoseconds.
// TODO(b/315152880): Consider moving psi entries for cpu/io/memory into
// groups specific to that resource (e.g., `Group::kMemory`).
- TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+ TrackId track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kDeviceState,
sys_stats_psi_resource_names_[resource], {}, ns_unit_id_);
context_->event_tracker->PushCounter(
@@ -485,7 +506,7 @@
for (auto it = sys_stats.thermal_zone(); it; ++it) {
protos::pbzero::SysStats::ThermalZone::Decoder thermal(*it);
StringId track_name = context_->storage->InternString(thermal.type());
- TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+ TrackId track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kThermals, track_name, {}, thermal_unit_id_);
context_->event_tracker->PushCounter(
ts, static_cast<double>(thermal.temp()), track);
@@ -496,7 +517,7 @@
}
for (auto it = sys_stats.gpufreq_mhz(); it; ++it, ++c) {
- TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+ TrackId track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kPower, gpufreq_id, {}, gpufreq_unit_id);
context_->event_tracker->PushCounter(ts, static_cast<double>(*it), track);
}
@@ -509,7 +530,7 @@
++cpuidle_field) {
protos::pbzero::SysStats::CpuIdleStateEntry::Decoder idle(*cpuidle_field);
- TrackId track = context_->track_tracker->InternCpuIdleStateTrack(
+ TrackId track = context_->track_tracker->LegacyInternCpuIdleStateTrack(
cpu_id, context_->storage->InternString(idle.state()));
context_->event_tracker->PushCounter(
ts, static_cast<double>(idle.duration_us()), track);
@@ -568,7 +589,7 @@
// single cmdline element. This will be wrong for binaries that have spaces
// in their path and are invoked without additional arguments, but those are
// very rare. The full cmdline will still be correct either way.
- if (bool(++proc.cmdline()) == false) {
+ if (!static_cast<bool>(++proc.cmdline())) {
size_t delim_pos = argv0.find(' ');
if (delim_pos != base::StringView::npos) {
argv0 = argv0.substr(0, delim_pos);
@@ -690,7 +711,7 @@
const StringId& name = proc_stats_process_names_[field_id];
UniquePid upid = context_->process_tracker->GetOrCreateProcess(pid);
TrackId track =
- context_->track_tracker->InternProcessCounterTrack(name, upid);
+ context_->track_tracker->LegacyInternProcessCounterTrack(name, upid);
int64_t value = counter_values[field_id];
context_->event_tracker->PushCounter(ts, static_cast<double>(value),
track);
@@ -777,6 +798,13 @@
packet.android_build_fingerprint())));
}
+ if (packet.has_android_device_manufacturer()) {
+ context_->metadata_tracker->SetMetadata(
+ metadata::android_device_manufacturer,
+ Variadic::String(context_->storage->InternString(
+ packet.android_device_manufacturer())));
+ }
+
// If we have the SDK version in the trace directly just use that.
// Otherwise, try and parse it from the fingerprint.
std::optional<int64_t> opt_sdk_version;
@@ -799,6 +827,13 @@
context_->storage->InternString(packet.android_soc_model())));
}
+ if (packet.has_android_guest_soc_model()) {
+ context_->metadata_tracker->SetMetadata(
+ metadata::android_guest_soc_model,
+ Variadic::String(
+ context_->storage->InternString(packet.android_guest_soc_model())));
+ }
+
if (packet.has_android_hardware_revision()) {
context_->metadata_tracker->SetMetadata(
metadata::android_hardware_revision,
@@ -809,15 +844,15 @@
if (packet.has_android_storage_model()) {
context_->metadata_tracker->SetMetadata(
metadata::android_storage_model,
- Variadic::String(context_->storage->InternString(
- packet.android_storage_model())));
+ Variadic::String(
+ context_->storage->InternString(packet.android_storage_model())));
}
if (packet.has_android_ram_model()) {
context_->metadata_tracker->SetMetadata(
metadata::android_ram_model,
- Variadic::String(context_->storage->InternString(
- packet.android_ram_model())));
+ Variadic::String(
+ context_->storage->InternString(packet.android_ram_model())));
}
page_size_ = packet.page_size();
@@ -872,13 +907,13 @@
// Capacities are defined as existing on all CPUs if present and so we set
// them as invalid if any is missing
- bool valid_capacities =
- std::all_of(cpu_infos.begin(), cpu_infos.end(),
- [](CpuInfo info) { return info.capacity.has_value(); });
+ bool valid_capacities = std::all_of(
+ cpu_infos.begin(), cpu_infos.end(),
+ [](const CpuInfo& info) { return info.capacity.has_value(); });
- bool valid_frequencies =
- std::all_of(cpu_infos.begin(), cpu_infos.end(),
- [](CpuInfo info) { return !info.frequencies.empty(); });
+ bool valid_frequencies = std::all_of(
+ cpu_infos.begin(), cpu_infos.end(),
+ [](const CpuInfo& info) { return !info.frequencies.empty(); });
std::vector<uint32_t> cluster_ids(cpu_infos.size());
uint32_t cluster_id = 0;
@@ -945,5 +980,4 @@
}
}
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/proto/system_probes_parser.h b/src/trace_processor/importers/proto/system_probes_parser.h
index 6863c3c..87b5734 100644
--- a/src/trace_processor/importers/proto/system_probes_parser.h
+++ b/src/trace_processor/importers/proto/system_probes_parser.h
@@ -18,14 +18,15 @@
#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_SYSTEM_PROBES_PARSER_H_
#include <array>
+#include <cstddef>
+#include <cstdint>
#include <vector>
#include "perfetto/protozero/field.h"
#include "protos/perfetto/trace/sys_stats/sys_stats.pbzero.h"
#include "src/trace_processor/storage/trace_storage.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
class TraceProcessorContext;
@@ -37,7 +38,7 @@
explicit SystemProbesParser(TraceProcessorContext*);
void ParseProcessTree(ConstBytes);
- void ParseProcessStats(int64_t timestamp, ConstBytes);
+ void ParseProcessStats(int64_t ts, ConstBytes);
void ParseSysStats(int64_t ts, ConstBytes);
void ParseSystemInfo(ConstBytes);
void ParseCpuInfo(ConstBytes);
@@ -54,6 +55,7 @@
const StringId ns_unit_id_;
const StringId bytes_unit_id_;
const StringId available_chunks_unit_id_;
+ const StringId irq_id_;
const StringId num_forks_name_id_;
const StringId num_irq_total_name_id_;
@@ -63,6 +65,8 @@
const StringId gpufreq_id;
const StringId gpufreq_unit_id;
+ const StringId cpu_stat_counter_name_id_;
+
// Arm CPU identifier string IDs
const StringId arm_cpu_implementer;
const StringId arm_cpu_architecture;
@@ -96,7 +100,6 @@
int64_t prev_flush_time = -1;
};
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_SYSTEM_PROBES_PARSER_H_
diff --git a/src/trace_processor/importers/proto/track_event_parser.cc b/src/trace_processor/importers/proto/track_event_parser.cc
index 360126c..8a241df 100644
--- a/src/trace_processor/importers/proto/track_event_parser.cc
+++ b/src/trace_processor/importers/proto/track_event_parser.cc
@@ -38,8 +38,6 @@
#include "src/trace_processor/importers/common/cpu_tracker.h"
#include "src/trace_processor/importers/common/event_tracker.h"
#include "src/trace_processor/importers/common/flow_tracker.h"
-#include "src/trace_processor/importers/common/global_args_tracker.h"
-#include "src/trace_processor/importers/common/legacy_v8_cpu_profile_tracker.h"
#include "src/trace_processor/importers/common/parser_types.h"
#include "src/trace_processor/importers/common/process_track_translation_table.h"
#include "src/trace_processor/importers/common/process_tracker.h"
@@ -218,13 +216,6 @@
RETURN_IF_ERROR(ParseTrackAssociation());
- // Counter-type events don't support arguments (those are on the
- // CounterDescriptor instead). All they have is a |{double_,}counter_value|.
- if (event_.type() == TrackEvent::TYPE_COUNTER) {
- ParseCounterEvent();
- return base::OkStatus();
- }
-
// If we have legacy thread time / instruction count fields, also parse them
// into the counters tables.
ParseLegacyThreadTimeAndInstructionsAsCounters();
@@ -234,6 +225,12 @@
// these counter values and also parse them as slice attributes / arguments.
ParseExtraCounterValues();
+ // Non-legacy counters are treated differently. Legacy counters do not have
+ // a track_id_ and should instead go through the switch below.
+ if (event_.type() == TrackEvent::TYPE_COUNTER) {
+ return ParseCounterEvent();
+ }
+
// TODO(eseckler): Replace phase with type and remove handling of
// legacy_event_.phase() once it is no longer used by producers.
char phase = static_cast<char>(ParsePhaseOrType());
@@ -374,9 +371,10 @@
track_event_tracker_->GetDescriptorTrack(track_uuid_, name_id_,
packet_sequence_id_);
if (!opt_track_id) {
- track_event_tracker_->ReserveDescriptorChildTrack(track_uuid_,
- /*parent_uuid=*/0,
- name_id_);
+ TrackEventTracker::DescriptorTrackReservation r;
+ r.parent_uuid = 0;
+ r.name = name_id_;
+ track_event_tracker_->ReserveDescriptorTrack(track_uuid_, r);
opt_track_id = track_event_tracker_->GetDescriptorTrack(
track_uuid_, name_id_, packet_sequence_id_);
}
@@ -497,7 +495,7 @@
id_scope = storage_->InternString(base::StringView(concat));
}
- track_id_ = context_->track_tracker->InternLegacyChromeAsyncTrack(
+ track_id_ = context_->track_tracker->LegacyInternLegacyChromeAsyncTrack(
name_id_, upid_.value_or(0), source_id, source_id_is_process_scoped,
id_scope);
legacy_passthrough_utid_ = utid_;
@@ -518,7 +516,13 @@
break;
case LegacyEvent::SCOPE_GLOBAL:
track_id_ = context_->track_tracker->InternGlobalTrack(
- TrackTracker::GlobalTrackType::kChromeLegacyGlobalInstant);
+ tracks::legacy_chrome_global_instants, TrackTracker::AutoName(),
+ [this](ArgsTracker::BoundInserter& inserter) {
+ inserter.AddArg(
+ context_->storage->InternString("source"),
+ Variadic::String(
+ context_->storage->InternString("chrome")));
+ });
legacy_passthrough_utid_ = utid_;
utid_ = std::nullopt;
break;
@@ -528,9 +532,11 @@
"Process-scoped instant event without process association");
}
- track_id_ =
- context_->track_tracker->InternLegacyChromeProcessInstantTrack(
- *upid_);
+ track_id_ = context_->track_tracker->InternProcessTrack(
+ tracks::chrome_process_instant, *upid_);
+ context_->args_tracker->AddArgsTo(track_id_).AddArg(
+ context_->storage->InternString("source"),
+ Variadic::String(context_->storage->InternString("chrome")));
legacy_passthrough_utid_ = utid_;
utid_ = std::nullopt;
break;
@@ -561,7 +567,7 @@
}
}
- void ParseCounterEvent() {
+ base::Status ParseCounterEvent() {
// Tokenizer ensures that TYPE_COUNTER events are associated with counter
// tracks and have values.
PERFETTO_DCHECK(storage_->counter_track_table().FindById(track_id_));
@@ -569,7 +575,9 @@
event_.has_double_counter_value());
context_->event_tracker->PushCounter(
- ts_, static_cast<double>(event_data_->counter_value), track_id_);
+ ts_, static_cast<double>(event_data_->counter_value), track_id_,
+ [this](BoundInserter* inserter) { ParseTrackEventArgs(inserter); });
+ return base::OkStatus();
}
void ParseLegacyThreadTimeAndInstructionsAsCounters() {
@@ -582,14 +590,16 @@
// EventTracker expects counters to be pushed in order of their timestamps.
// One more reason to switch to split begin/end events.
if (thread_timestamp_) {
- TrackId track_id = context_->track_tracker->InternThreadCounterTrack(
- parser_->counter_name_thread_time_id_, *utid_);
+ TrackId track_id =
+ context_->track_tracker->LegacyInternThreadCounterTrack(
+ parser_->counter_name_thread_time_id_, *utid_);
context_->event_tracker->PushCounter(
ts_, static_cast<double>(*thread_timestamp_), track_id);
}
if (thread_instruction_count_) {
- TrackId track_id = context_->track_tracker->InternThreadCounterTrack(
- parser_->counter_name_thread_instruction_count_id_, *utid_);
+ TrackId track_id =
+ context_->track_tracker->LegacyInternThreadCounterTrack(
+ parser_->counter_name_thread_instruction_count_id_, *utid_);
context_->event_tracker->PushCounter(
ts_, static_cast<double>(*thread_instruction_count_), track_id);
}
@@ -1536,10 +1546,17 @@
}
// Override the name with the most recent name seen (after sorting by ts).
- if (decoder.has_name() || decoder.has_static_name()) {
+ ::protozero::ConstChars name = {nullptr, 0};
+ if (decoder.has_name()) {
+ name = decoder.name();
+ } else if (decoder.has_static_name()) {
+ name = decoder.static_name();
+ } else if (decoder.has_atrace_name()) {
+ name = decoder.atrace_name();
+ }
+ if (name.data != nullptr) {
auto* tracks = context_->storage->mutable_track_table();
- const StringId raw_name_id = context_->storage->InternString(
- decoder.has_name() ? decoder.name() : decoder.static_name());
+ const StringId raw_name_id = context_->storage->InternString(name);
const StringId name_id =
context_->process_track_translation_table->TranslateName(raw_name_id);
tracks->FindById(track_id)->set_name(name_id);
diff --git a/src/trace_processor/importers/proto/track_event_tokenizer.cc b/src/trace_processor/importers/proto/track_event_tokenizer.cc
index 268f45f..86c402f 100644
--- a/src/trace_processor/importers/proto/track_event_tokenizer.cc
+++ b/src/trace_processor/importers/proto/track_event_tokenizer.cc
@@ -23,7 +23,7 @@
#include <string>
#include <utility>
-#include "perfetto/base/compiler.h"
+#include "perfetto/base/build_config.h"
#include "perfetto/base/logging.h"
#include "perfetto/base/status.h"
#include "perfetto/ext/base/status_or.h"
@@ -96,9 +96,13 @@
RefPtr<PacketSequenceStateGeneration> state,
const protos::pbzero::TracePacket::Decoder& packet,
int64_t packet_timestamp) {
+ using TrackDescriptorProto = protos::pbzero::TrackDescriptor;
+ using Reservation = TrackEventTracker::DescriptorTrackReservation;
auto track_descriptor_field = packet.track_descriptor();
- protos::pbzero::TrackDescriptor::Decoder track(track_descriptor_field.data,
- track_descriptor_field.size);
+ TrackDescriptorProto::Decoder track(track_descriptor_field.data,
+ track_descriptor_field.size);
+
+ Reservation reservation;
if (!track.has_uuid()) {
PERFETTO_ELOG("TrackDescriptor packet without uuid");
@@ -106,11 +110,37 @@
return ModuleResult::Handled();
}
- StringId name_id = kNullStringId;
+ if (track.has_parent_uuid()) {
+ reservation.parent_uuid = track.parent_uuid();
+ }
+
+ if (track.has_child_ordering()) {
+ switch (track.child_ordering()) {
+ case TrackDescriptorProto::ChildTracksOrdering::UNKNOWN:
+ reservation.ordering = Reservation::ChildTracksOrdering::kUnknown;
+ break;
+ case TrackDescriptorProto::ChildTracksOrdering::CHRONOLOGICAL:
+ reservation.ordering = Reservation::ChildTracksOrdering::kChronological;
+ break;
+ case TrackDescriptorProto::ChildTracksOrdering::LEXICOGRAPHIC:
+ reservation.ordering = Reservation::ChildTracksOrdering::kLexicographic;
+ break;
+ case TrackDescriptorProto::ChildTracksOrdering::EXPLICIT:
+ reservation.ordering = Reservation::ChildTracksOrdering::kExplicit;
+ break;
+ default:
+ PERFETTO_FATAL("Unsupported ChildTracksOrdering");
+ }
+ }
+
+ if (track.has_sibling_order_rank()) {
+ reservation.sibling_order_rank = track.sibling_order_rank();
+ }
+
if (track.has_name())
- name_id = context_->storage->InternString(track.name());
+ reservation.name = context_->storage->InternString(track.name());
else if (track.has_static_name())
- name_id = context_->storage->InternString(track.static_name());
+ reservation.name = context_->storage->InternString(track.static_name());
if (packet.has_trusted_pid()) {
context_->process_tracker->UpdateTrustedPid(
@@ -132,12 +162,18 @@
TokenizeThreadDescriptor(*state, thread);
}
- track_event_tracker_->ReserveDescriptorThreadTrack(
- track.uuid(), track.parent_uuid(), name_id,
- static_cast<uint32_t>(thread.pid()),
- static_cast<uint32_t>(thread.tid()), packet_timestamp,
- track.disallow_merging_with_system_tracks());
- } else if (track.has_process()) {
+ reservation.min_timestamp = packet_timestamp;
+ reservation.pid = static_cast<uint32_t>(thread.pid());
+ reservation.tid = static_cast<uint32_t>(thread.tid());
+ reservation.use_separate_track =
+ track.disallow_merging_with_system_tracks();
+
+ track_event_tracker_->ReserveDescriptorTrack(track.uuid(), reservation);
+
+ return ModuleResult::Ignored();
+ }
+
+ if (track.has_process()) {
protos::pbzero::ProcessDescriptor::Decoder process(track.process());
if (!process.has_pid()) {
@@ -147,10 +183,13 @@
return ModuleResult::Handled();
}
- track_event_tracker_->ReserveDescriptorProcessTrack(
- track.uuid(), name_id, static_cast<uint32_t>(process.pid()),
- packet_timestamp);
- } else if (track.has_counter()) {
+ reservation.pid = static_cast<uint32_t>(process.pid());
+ reservation.min_timestamp = packet_timestamp;
+ track_event_tracker_->ReserveDescriptorTrack(track.uuid(), reservation);
+
+ return ModuleResult::Ignored();
+ }
+ if (track.has_counter()) {
protos::pbzero::CounterDescriptor::Decoder counter(track.counter());
StringId category_id = kNullStringId;
@@ -174,31 +213,42 @@
// threads, in which case it has to use absolute values on a different
// track_uuid. Right now these absolute values are imported onto a separate
// counter track than the other thread's regular thread time values.)
- if (name_id.is_null()) {
+ if (reservation.name.is_null()) {
switch (counter.type()) {
case CounterDescriptor::COUNTER_UNSPECIFIED:
break;
case CounterDescriptor::COUNTER_THREAD_TIME_NS:
- name_id = counter_name_thread_time_id_;
+ reservation.name = counter_name_thread_time_id_;
break;
case CounterDescriptor::COUNTER_THREAD_INSTRUCTION_COUNT:
- name_id = counter_name_thread_instruction_count_id_;
+ reservation.name = counter_name_thread_instruction_count_id_;
break;
}
}
- track_event_tracker_->ReserveDescriptorCounterTrack(
- track.uuid(), track.parent_uuid(), name_id, category_id,
- counter.unit_multiplier(), counter.is_incremental(),
- packet.trusted_packet_sequence_id());
- } else {
- track_event_tracker_->ReserveDescriptorChildTrack(
- track.uuid(), track.parent_uuid(), name_id);
+ reservation.is_counter = true;
+
+ TrackEventTracker::DescriptorTrackReservation::CounterDetails
+ counter_details;
+ counter_details.category = category_id;
+ counter_details.is_incremental = counter.is_incremental();
+ counter_details.unit_multiplier = counter.unit_multiplier();
+
+ // Incrementally encoded counters are only valid on a single sequence.
+ if (counter.is_incremental())
+ counter_details.packet_sequence_id = packet.trusted_packet_sequence_id();
+
+ reservation.counter_details = std::move(counter_details);
+ track_event_tracker_->ReserveDescriptorTrack(track.uuid(), reservation);
+
+ return ModuleResult::Ignored();
}
+ track_event_tracker_->ReserveDescriptorTrack(track.uuid(), reservation);
+
// Let ProtoTraceReader forward the packet to the parser.
return ModuleResult::Ignored();
-}
+} // namespace perfetto::trace_processor
ModuleResult TrackEventTokenizer::TokenizeThreadDescriptorPacket(
RefPtr<PacketSequenceStateGeneration> state,
@@ -454,6 +504,9 @@
PacketSequenceStateGeneration& state) {
// We are just trying to parse out the V8 profiling events into the cpu
// sampling tables: if we don't have JSON enabled, just don't do this.
+ if (!context_->json_trace_parser) {
+ return base::OkStatus();
+ }
#if PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
for (auto it = event.debug_annotations(); it; ++it) {
protos::pbzero::DebugAnnotation::Decoder da(*it);
diff --git a/src/trace_processor/importers/proto/track_event_tracker.cc b/src/trace_processor/importers/proto/track_event_tracker.cc
index a336b60..8762006 100644
--- a/src/trace_processor/importers/proto/track_event_tracker.cc
+++ b/src/trace_processor/importers/proto/track_event_tracker.cc
@@ -32,9 +32,9 @@
#include "src/trace_processor/importers/common/process_track_translation_table.h"
#include "src/trace_processor/importers/common/process_tracker.h"
#include "src/trace_processor/importers/common/track_tracker.h"
+#include "src/trace_processor/importers/common/tracks.h"
#include "src/trace_processor/storage/stats.h"
#include "src/trace_processor/storage/trace_storage.h"
-#include "src/trace_processor/tables/track_tables_py.h"
#include "src/trace_processor/types/trace_processor_context.h"
#include "src/trace_processor/types/variadic.h"
@@ -47,20 +47,20 @@
category_key_(context->storage->InternString("category")),
has_first_packet_on_sequence_key_id_(
context->storage->InternString("has_first_packet_on_sequence")),
+ child_ordering_key_(context->storage->InternString("child_ordering")),
+ explicit_id_(context->storage->InternString("explicit")),
+ lexicographic_id_(context->storage->InternString("lexicographic")),
+ chronological_id_(context->storage->InternString("chronological")),
+ sibling_order_rank_key_(
+ context->storage->InternString("sibling_order_rank")),
descriptor_source_(context->storage->InternString("descriptor")),
default_descriptor_track_name_(
context->storage->InternString("Default Track")),
context_(context) {}
-void TrackEventTracker::ReserveDescriptorProcessTrack(uint64_t uuid,
- StringId name,
- uint32_t pid,
- int64_t timestamp) {
- DescriptorTrackReservation reservation;
- reservation.min_timestamp = timestamp;
- reservation.pid = pid;
- reservation.name = name;
-
+void TrackEventTracker::ReserveDescriptorTrack(
+ uint64_t uuid,
+ const DescriptorTrackReservation& reservation) {
std::map<uint64_t, DescriptorTrackReservation>::iterator it;
bool inserted;
std::tie(it, inserted) =
@@ -70,125 +70,14 @@
return;
if (!it->second.IsForSameTrack(reservation)) {
- // Process tracks should not be reassigned to a different pid later (neither
- // should the type of the track change).
PERFETTO_DLOG("New track reservation for process track with uuid %" PRIu64
" doesn't match earlier one",
uuid);
context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
return;
}
- it->second.min_timestamp = std::min(it->second.min_timestamp, timestamp);
-}
-
-void TrackEventTracker::ReserveDescriptorThreadTrack(uint64_t uuid,
- uint64_t parent_uuid,
- StringId name,
- uint32_t pid,
- uint32_t tid,
- int64_t timestamp,
- bool use_separate_track) {
- DescriptorTrackReservation reservation;
- reservation.min_timestamp = timestamp;
- reservation.parent_uuid = parent_uuid;
- reservation.pid = pid;
- reservation.tid = tid;
- reservation.name = name;
- reservation.use_separate_track = use_separate_track;
-
- std::map<uint64_t, DescriptorTrackReservation>::iterator it;
- bool inserted;
- std::tie(it, inserted) =
- reserved_descriptor_tracks_.insert(std::make_pair<>(uuid, reservation));
-
- if (inserted)
- return;
-
- if (!it->second.IsForSameTrack(reservation)) {
- // Thread tracks should not be reassigned to a different pid/tid later
- // (neither should the type of the track change).
- PERFETTO_DLOG("New track reservation for thread track with uuid %" PRIu64
- " doesn't match earlier one",
- uuid);
- context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
- return;
- }
-
- it->second.min_timestamp = std::min(it->second.min_timestamp, timestamp);
-}
-
-void TrackEventTracker::ReserveDescriptorCounterTrack(
- uint64_t uuid,
- uint64_t parent_uuid,
- StringId name,
- StringId category,
- int64_t unit_multiplier,
- bool is_incremental,
- uint32_t packet_sequence_id) {
- DescriptorTrackReservation reservation;
- reservation.parent_uuid = parent_uuid;
- reservation.is_counter = true;
- reservation.name = name;
- reservation.category = category;
- reservation.unit_multiplier = unit_multiplier;
- reservation.is_incremental = is_incremental;
- // Incrementally encoded counters are only valid on a single sequence.
- if (is_incremental)
- reservation.packet_sequence_id = packet_sequence_id;
-
- std::map<uint64_t, DescriptorTrackReservation>::iterator it;
- bool inserted;
- std::tie(it, inserted) =
- reserved_descriptor_tracks_.insert(std::make_pair<>(uuid, reservation));
-
- if (inserted || it->second.IsForSameTrack(reservation))
- return;
-
- // Counter tracks should not be reassigned to a different parent track later
- // (neither should the type of the track change).
- PERFETTO_DLOG("New track reservation for counter track with uuid %" PRIu64
- " doesn't match earlier one",
- uuid);
- context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
-}
-
-void TrackEventTracker::ReserveDescriptorChildTrack(uint64_t uuid,
- uint64_t parent_uuid,
- StringId name) {
- DescriptorTrackReservation reservation;
- reservation.parent_uuid = parent_uuid;
- reservation.name = name;
-
- std::map<uint64_t, DescriptorTrackReservation>::iterator it;
- bool inserted;
- std::tie(it, inserted) =
- reserved_descriptor_tracks_.insert(std::make_pair<>(uuid, reservation));
-
- if (inserted || it->second.IsForSameTrack(reservation))
- return;
-
- // Child tracks should not be reassigned to a different parent track later
- // (neither should the type of the track change).
- PERFETTO_DLOG("New track reservation for child track with uuid %" PRIu64
- " doesn't match earlier one",
- uuid);
- context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
-}
-
-TrackId TrackEventTracker::InsertThreadTrack(UniqueTid utid) {
- tables::ThreadTrackTable::Row row;
- row.utid = utid;
- row.machine_id = context_->machine_id();
- auto* thread_tracks = context_->storage->mutable_thread_track_table();
- return thread_tracks->Insert(row).id;
-}
-
-TrackId TrackEventTracker::InternThreadTrack(UniqueTid utid) {
- auto it = thread_tracks_.find(utid);
- if (it != thread_tracks_.end()) {
- return it->second;
- }
- return thread_tracks_[utid] = InsertThreadTrack(utid);
+ it->second.min_timestamp =
+ std::min(it->second.min_timestamp, reservation.min_timestamp);
}
std::optional<TrackId> TrackEventTracker::GetDescriptorTrack(
@@ -202,8 +91,7 @@
// Update the name of the track if unset and the track is not the primary
// track of a process/thread or a counter track.
- auto* tracks = context_->storage->mutable_track_table();
- auto rr = *tracks->FindById(*track_id);
+ auto rr = *context_->storage->mutable_track_table()->FindById(*track_id);
if (!rr.name().is_null()) {
return track_id;
}
@@ -255,16 +143,36 @@
.AddArg(source_id_key_, Variadic::Integer(static_cast<int64_t>(uuid)))
.AddArg(is_root_in_scope_key_,
Variadic::Boolean(resolved_track->is_root_in_scope()));
- if (!reservation.category.is_null())
- args.AddArg(category_key_, Variadic::String(reservation.category));
+ if (reservation.counter_details &&
+ !reservation.counter_details->category.is_null())
+ args.AddArg(category_key_,
+ Variadic::String(reservation.counter_details->category));
if (packet_sequence_id &&
sequences_with_first_packet_.find(*packet_sequence_id) !=
sequences_with_first_packet_.end()) {
args.AddArg(has_first_packet_on_sequence_key_id_, Variadic::Boolean(true));
}
- auto* tracks = context_->storage->mutable_track_table();
- auto row_ref = *tracks->FindById(track_id);
+ switch (reservation.ordering) {
+ case DescriptorTrackReservation::ChildTracksOrdering::kLexicographic:
+ args.AddArg(child_ordering_key_, Variadic::String(lexicographic_id_));
+ break;
+ case DescriptorTrackReservation::ChildTracksOrdering::kChronological:
+ args.AddArg(child_ordering_key_, Variadic::String(chronological_id_));
+ break;
+ case DescriptorTrackReservation::ChildTracksOrdering::kExplicit:
+ args.AddArg(child_ordering_key_, Variadic::String(explicit_id_));
+ break;
+ case DescriptorTrackReservation::ChildTracksOrdering::kUnknown:
+ break;
+ }
+
+ if (reservation.sibling_order_rank) {
+ args.AddArg(sibling_order_rank_key_,
+ Variadic::Integer(*reservation.sibling_order_rank));
+ }
+
+ auto row_ref = *context_->storage->mutable_track_table()->FindById(track_id);
if (parent_id) {
row_ref.set_parent_id(*parent_id);
}
@@ -284,59 +192,54 @@
switch (track.scope()) {
case ResolvedDescriptorTrack::Scope::kThread: {
if (track.use_separate_track()) {
- return InternThreadTrack(track.utid());
+ auto it = thread_tracks_.find(track.utid());
+ if (it != thread_tracks_.end()) {
+ return it->second;
+ }
+ TrackId id = context_->track_tracker->CreateThreadTrack(
+ tracks::track_event, track.utid(), TrackTracker::AutoName());
+ thread_tracks_[track.utid()] = id;
+ return id;
}
return context_->track_tracker->InternThreadTrack(track.utid());
}
case ResolvedDescriptorTrack::Scope::kProcess:
- return context_->track_tracker->InternProcessTrack(track.upid());
+ return context_->track_tracker->InternProcessTrack(tracks::track_event,
+ track.upid());
case ResolvedDescriptorTrack::Scope::kGlobal:
// Will be handled below.
break;
}
}
+ if (track.is_counter()) {
+ switch (track.scope()) {
+ case ResolvedDescriptorTrack::Scope::kThread:
+ return context_->track_tracker->CreateThreadCounterTrack(
+ tracks::track_event, track.utid(), TrackTracker::AutoName());
+ case ResolvedDescriptorTrack::Scope::kProcess:
+ return context_->track_tracker->CreateProcessCounterTrack(
+ tracks::track_event, track.upid(), std::nullopt,
+ TrackTracker::AutoName());
+ case ResolvedDescriptorTrack::Scope::kGlobal:
+ return context_->track_tracker->CreateCounterTrack(
+ tracks::track_event, std::nullopt, TrackTracker::AutoName());
+ }
+ }
+
switch (track.scope()) {
case ResolvedDescriptorTrack::Scope::kThread: {
- if (track.is_counter()) {
- tables::ThreadCounterTrackTable::Row row;
- row.utid = track.utid();
- row.machine_id = context_->machine_id();
-
- auto* thread_counter_tracks =
- context_->storage->mutable_thread_counter_track_table();
- return thread_counter_tracks->Insert(row).id;
- }
-
- return InsertThreadTrack(track.utid());
+ return context_->track_tracker->CreateThreadTrack(
+ tracks::track_event, track.utid(), TrackTracker::AutoName());
}
case ResolvedDescriptorTrack::Scope::kProcess: {
- if (track.is_counter()) {
- tables::ProcessCounterTrackTable::Row row;
- row.upid = track.upid();
- row.machine_id = context_->machine_id();
-
- auto* process_counter_tracks =
- context_->storage->mutable_process_counter_track_table();
- return process_counter_tracks->Insert(row).id;
- }
-
- tables::ProcessTrackTable::Row row;
- row.upid = track.upid();
- row.machine_id = context_->machine_id();
-
- auto* process_tracks = context_->storage->mutable_process_track_table();
- return process_tracks->Insert(row).id;
+ return context_->track_tracker->CreateProcessTrack(
+ tracks::track_event, track.upid(), std::nullopt,
+ TrackTracker::AutoName());
}
case ResolvedDescriptorTrack::Scope::kGlobal: {
- if (track.is_counter()) {
- tables::CounterTrackTable::Row row;
- row.machine_id = context_->machine_id();
- return context_->storage->mutable_counter_track_table()->Insert(row).id;
- }
- tables::TrackTable::Row row;
- row.machine_id = context_->machine_id();
- return context_->storage->mutable_track_table()->Insert(row).id;
+ return context_->track_tracker->CreateTrack(
+ tracks::track_event, std::nullopt, TrackTracker::AutoName());
}
}
PERFETTO_FATAL("For GCC");
@@ -545,8 +448,10 @@
return *track_id;
// Otherwise reserve a new track and resolve it.
- ReserveDescriptorChildTrack(kDefaultDescriptorTrackUuid, /*parent_uuid=*/0,
- default_descriptor_track_name_);
+ DescriptorTrackReservation r;
+ r.parent_uuid = 0;
+ r.name = default_descriptor_track_name_;
+ ReserveDescriptorTrack(kDefaultDescriptorTrackUuid, r);
return *GetDescriptorTrack(kDefaultDescriptorTrackUuid);
}
@@ -567,23 +472,27 @@
counter_track_uuid);
return std::nullopt;
}
+ if (!reservation.counter_details) {
+ PERFETTO_FATAL("Counter tracks require `counter_details`.");
+ }
+ DescriptorTrackReservation::CounterDetails& c_details =
+ *reservation.counter_details;
- if (reservation.unit_multiplier > 0)
- value *= static_cast<double>(reservation.unit_multiplier);
+ if (c_details.unit_multiplier > 0)
+ value *= static_cast<double>(c_details.unit_multiplier);
- if (reservation.is_incremental) {
- if (reservation.packet_sequence_id != packet_sequence_id) {
+ if (c_details.is_incremental) {
+ if (c_details.packet_sequence_id != packet_sequence_id) {
PERFETTO_DLOG(
"Incremental counter track with uuid %" PRIu64
" was updated from the wrong packet sequence (expected: %" PRIu32
" got:%" PRIu32 ")",
- counter_track_uuid, reservation.packet_sequence_id,
- packet_sequence_id);
+ counter_track_uuid, c_details.packet_sequence_id, packet_sequence_id);
return std::nullopt;
}
- reservation.latest_value += value;
- value = reservation.latest_value;
+ c_details.latest_value += value;
+ value = c_details.latest_value;
}
return value;
@@ -597,12 +506,13 @@
for (auto& entry : reserved_descriptor_tracks_) {
DescriptorTrackReservation& reservation = entry.second;
// Only consider incremental counter tracks for current sequence.
- if (!reservation.is_counter || !reservation.is_incremental ||
- reservation.packet_sequence_id != packet_sequence_id) {
+ if (!reservation.is_counter || !reservation.counter_details ||
+ !reservation.counter_details->is_incremental ||
+ reservation.counter_details->packet_sequence_id != packet_sequence_id) {
continue;
}
// Reset their value to 0, see CounterDescriptor's |is_incremental|.
- reservation.latest_value = 0;
+ reservation.counter_details->latest_value = 0;
}
}
diff --git a/src/trace_processor/importers/proto/track_event_tracker.h b/src/trace_processor/importers/proto/track_event_tracker.h
index e32138d..b801a74 100644
--- a/src/trace_processor/importers/proto/track_event_tracker.h
+++ b/src/trace_processor/importers/proto/track_event_tracker.h
@@ -36,73 +36,67 @@
// Tracks and stores tracks based on track types, ids and scopes.
class TrackEventTracker {
public:
+ // Data from TrackDescriptor proto used to reserve a track before interning it
+ // with |TrackTracker|.
+ struct DescriptorTrackReservation {
+ // Maps to TrackDescriptor::ChildTracksOrdering proto values
+ enum class ChildTracksOrdering {
+ kUnknown = 0,
+ kLexicographic = 1,
+ kChronological = 2,
+ kExplicit = 3,
+ };
+ struct CounterDetails {
+ StringId category;
+ int64_t unit_multiplier = 1;
+ bool is_incremental = false;
+ uint32_t packet_sequence_id = 0;
+ double latest_value = 0;
+
+ bool operator==(const CounterDetails& o) const {
+ return std::tie(category, unit_multiplier, is_incremental,
+ packet_sequence_id, latest_value) ==
+ std::tie(o.category, o.unit_multiplier, o.is_incremental,
+ o.packet_sequence_id, o.latest_value);
+ }
+ };
+
+ uint64_t parent_uuid = 0;
+ std::optional<uint32_t> pid;
+ std::optional<uint32_t> tid;
+ int64_t min_timestamp = 0; // only set if |pid| and/or |tid| is set.
+ StringId name = kNullStringId;
+ bool use_separate_track = false;
+ bool is_counter = false;
+
+ // For counter tracks.
+ std::optional<CounterDetails> counter_details;
+
+ // For UI visualisation
+ ChildTracksOrdering ordering = ChildTracksOrdering::kUnknown;
+ std::optional<int32_t> sibling_order_rank;
+
+ // Whether |other| is a valid descriptor for this track reservation. A track
+ // should always remain nested underneath its original parent.
+ bool IsForSameTrack(const DescriptorTrackReservation& other) {
+ // Note that |min_timestamp|, |latest_value|, and |name| are ignored for
+ // this comparison.
+ return std::tie(parent_uuid, pid, tid, is_counter, counter_details) ==
+ std::tie(other.parent_uuid, other.pid, other.tid, other.is_counter,
+ other.counter_details);
+ }
+ };
explicit TrackEventTracker(TraceProcessorContext*);
// Associate a TrackDescriptor track identified by the given |uuid| with a
- // process's |pid|. This is called during tokenization. If a reservation for
- // the same |uuid| already exists, verifies that the present reservation
- // matches the new one.
- //
- // The track will be resolved to the process track (see InternProcessTrack())
- // upon the first call to GetDescriptorTrack() with the same |uuid|. At this
- // time, |pid| will also be resolved to a |upid|.
- void ReserveDescriptorProcessTrack(uint64_t uuid,
- StringId name,
- uint32_t pid,
- int64_t timestamp);
-
- // Associate a TrackDescriptor track identified by the given |uuid| with a
- // thread's |pid| and |tid|. This is called during tokenization. If a
+ // given track description. This is called during tokenization. If a
// reservation for the same |uuid| already exists, verifies that the present
// reservation matches the new one.
//
- // The track will be resolved to the thread track (see InternThreadTrack())
+ // The track will be resolved to the track (see TrackTracker::InternTrack())
// upon the first call to GetDescriptorTrack() with the same |uuid|. At this
- // time, |pid| will also be resolved to a |upid|.
- void ReserveDescriptorThreadTrack(uint64_t uuid,
- uint64_t parent_uuid,
- StringId name,
- uint32_t pid,
- uint32_t tid,
- int64_t timestamp,
- bool use_separate_track);
-
- // Associate a TrackDescriptor track identified by the given |uuid| with a
- // parent track (usually a process- or thread-associated track). This is
- // called during tokenization. If a reservation for the same |uuid| already
- // exists, will attempt to update it.
- //
- // The track will be created upon the first call to GetDescriptorTrack() with
- // the same |uuid|. If |parent_uuid| is 0, the track will become a global
- // track. Otherwise, it will become a new track of the same type as its parent
- // track.
- void ReserveDescriptorChildTrack(uint64_t uuid,
- uint64_t parent_uuid,
- StringId name);
-
- // Associate a counter-type TrackDescriptor track identified by the given
- // |uuid| with a parent track (usually a process or thread track). This is
- // called during tokenization. If a reservation for the same |uuid| already
- // exists, will attempt to update it. The provided |category| will be stored
- // into the track's args.
- //
- // If |is_incremental| is true, the counter will only be valid on the packet
- // sequence identified by |packet_sequence_id|. |unit_multiplier| is an
- // optional multiplication factor applied to counter values. Values for the
- // counter will be translated during tokenization via
- // ConvertToAbsoluteCounterValue().
- //
- // The track will be created upon the first call to GetDescriptorTrack() with
- // the same |uuid|. If |parent_uuid| is 0, the track will become a global
- // track. Otherwise, it will become a new counter track for the same
- // process/thread as its parent track.
- void ReserveDescriptorCounterTrack(uint64_t uuid,
- uint64_t parent_uuid,
- StringId name,
- StringId category,
- int64_t unit_multiplier,
- bool is_incremental,
- uint32_t packet_sequence_id);
+ // time, |pid| will be resolved to a |upid| and |tid| to |utid|.
+ void ReserveDescriptorTrack(uint64_t uuid, const DescriptorTrackReservation&);
// Returns the ID of the track for the TrackDescriptor with the given |uuid|.
// This is called during parsing. The first call to GetDescriptorTrack() for
@@ -133,13 +127,6 @@
// GetDescriptorTrack is moved back.
TrackId GetOrCreateDefaultDescriptorTrack();
- // Track events timestamps in Chrome have microsecond resolution, while
- // system events use nanoseconds. It results in broken event nesting when
- // track events and system events share a track.
- // So TrackEventTracker needs to support its own tracks, separate from the
- // ones in the TrackTracker.
- TrackId InternThreadTrack(UniqueTid utid);
-
// Called by ProtoTraceReader whenever incremental state is cleared on a
// packet sequence. Resets counter values for any incremental counters of
// the sequence identified by |packet_sequence_id|.
@@ -156,34 +143,6 @@
}
private:
- struct DescriptorTrackReservation {
- uint64_t parent_uuid = 0;
- std::optional<uint32_t> pid;
- std::optional<uint32_t> tid;
- int64_t min_timestamp = 0; // only set if |pid| and/or |tid| is set.
- StringId name = kNullStringId;
- bool use_separate_track = false;
-
- // For counter tracks.
- bool is_counter = false;
- StringId category = kNullStringId;
- int64_t unit_multiplier = 1;
- bool is_incremental = false;
- uint32_t packet_sequence_id = 0;
- double latest_value = 0;
-
- // Whether |other| is a valid descriptor for this track reservation. A track
- // should always remain nested underneath its original parent.
- bool IsForSameTrack(const DescriptorTrackReservation& other) {
- // Note that |min_timestamp|, |latest_value|, and |name| are ignored for
- // this comparison.
- return std::tie(parent_uuid, pid, tid, is_counter, category,
- unit_multiplier, is_incremental, packet_sequence_id) ==
- std::tie(other.parent_uuid, pid, tid, is_counter, category,
- unit_multiplier, is_incremental, packet_sequence_id);
- }
- };
-
class ResolvedDescriptorTrack {
public:
enum class Scope {
@@ -238,7 +197,6 @@
uint64_t uuid,
const DescriptorTrackReservation&,
std::vector<uint64_t>* descendent_uuids);
- TrackId InsertThreadTrack(UniqueTid utid);
static constexpr uint64_t kDefaultDescriptorTrackUuid = 0u;
@@ -258,15 +216,20 @@
std::unordered_set<uint32_t> sequences_with_first_packet_;
- const StringId source_key_ = kNullStringId;
- const StringId source_id_key_ = kNullStringId;
- const StringId is_root_in_scope_key_ = kNullStringId;
- const StringId category_key_ = kNullStringId;
- const StringId has_first_packet_on_sequence_key_id_ = kNullStringId;
+ const StringId source_key_;
+ const StringId source_id_key_;
+ const StringId is_root_in_scope_key_;
+ const StringId category_key_;
+ const StringId has_first_packet_on_sequence_key_id_;
+ const StringId child_ordering_key_;
+ const StringId explicit_id_;
+ const StringId lexicographic_id_;
+ const StringId chronological_id_;
+ const StringId sibling_order_rank_key_;
- const StringId descriptor_source_ = kNullStringId;
+ const StringId descriptor_source_;
- const StringId default_descriptor_track_name_ = kNullStringId;
+ const StringId default_descriptor_track_name_;
std::optional<int64_t> range_of_interest_start_us_;
diff --git a/src/trace_processor/importers/proto/winscope/BUILD.gn b/src/trace_processor/importers/proto/winscope/BUILD.gn
index e04c4f0..ba4452c 100644
--- a/src/trace_processor/importers/proto/winscope/BUILD.gn
+++ b/src/trace_processor/importers/proto/winscope/BUILD.gn
@@ -55,6 +55,7 @@
"../../common:parser_types",
"../../proto:minimal",
"../../proto:packet_sequence_state_generation_hdr",
+ "../../../util:winscope_proto_mapping"
]
}
diff --git a/src/trace_processor/importers/proto/winscope/android_input_event_parser.cc b/src/trace_processor/importers/proto/winscope/android_input_event_parser.cc
index 03bf5ad..e3b2553 100644
--- a/src/trace_processor/importers/proto/winscope/android_input_event_parser.cc
+++ b/src/trace_processor/importers/proto/winscope/android_input_event_parser.cc
@@ -16,13 +16,14 @@
#include "src/trace_processor/importers/proto/winscope/android_input_event_parser.h"
+#include "perfetto/ext/base/base64.h"
#include "protos/perfetto/trace/android/android_input_event.pbzero.h"
#include "src/trace_processor/importers/common/args_tracker.h"
#include "src/trace_processor/importers/proto/args_parser.h"
-#include "src/trace_processor/importers/proto/winscope/winscope.descriptor.h"
#include "src/trace_processor/storage/trace_storage.h"
#include "src/trace_processor/tables/android_tables_py.h"
#include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/util/winscope_proto_mapping.h"
namespace perfetto::trace_processor {
@@ -33,10 +34,7 @@
using perfetto::protos::pbzero::TracePacket;
AndroidInputEventParser::AndroidInputEventParser(TraceProcessorContext* context)
- : context_(*context), args_parser_{pool_} {
- pool_.AddFromFileDescriptorSet(kWinscopeDescriptor.data(),
- kWinscopeDescriptor.size());
-}
+ : context_(*context), args_parser_{*context->descriptor_pool_} {}
void AndroidInputEventParser::ParseAndroidInputEvent(
int64_t packet_ts,
@@ -80,6 +78,10 @@
tables::AndroidMotionEventsTable::Row event_row;
event_row.event_id = event_proto.event_id();
event_row.ts = packet_ts;
+ event_row.base64_proto =
+ context_.storage->mutable_string_pool()->InternString(
+ base::StringView(base::Base64Encode(bytes.data, bytes.size)));
+ event_row.base64_proto_id = event_row.base64_proto.raw_id();
auto event_row_id = context_.storage->mutable_android_motion_events_table()
->Insert(event_row)
@@ -88,7 +90,9 @@
ArgsParser writer{packet_ts, inserter, *context_.storage};
base::Status status =
- args_parser_.ParseMessage(bytes, ".perfetto.protos.AndroidMotionEvent",
+ args_parser_.ParseMessage(bytes,
+ *util::winscope_proto_mapping::GetProtoName(
+ tables::AndroidMotionEventsTable::Name()),
nullptr /*parse all fields*/, writer);
if (!status.ok())
context_.storage->IncrementStats(stats::android_input_event_parse_errors);
@@ -101,6 +105,10 @@
tables::AndroidKeyEventsTable::Row event_row;
event_row.event_id = event_proto.event_id();
event_row.ts = packet_ts;
+ event_row.base64_proto =
+ context_.storage->mutable_string_pool()->InternString(
+ base::StringView(base::Base64Encode(bytes.data, bytes.size)));
+ event_row.base64_proto_id = event_row.base64_proto.raw_id();
auto event_row_id = context_.storage->mutable_android_key_events_table()
->Insert(event_row)
@@ -109,7 +117,9 @@
ArgsParser writer{packet_ts, inserter, *context_.storage};
base::Status status =
- args_parser_.ParseMessage(bytes, ".perfetto.protos.AndroidKeyEvent",
+ args_parser_.ParseMessage(bytes,
+ *util::winscope_proto_mapping::GetProtoName(
+ tables::AndroidKeyEventsTable::Name()),
nullptr /*parse all fields*/, writer);
if (!status.ok())
context_.storage->IncrementStats(stats::android_input_event_parse_errors);
@@ -123,16 +133,23 @@
event_row.event_id = event_proto.event_id();
event_row.vsync_id = event_proto.vsync_id();
event_row.window_id = event_proto.window_id();
+ event_row.base64_proto =
+ context_.storage->mutable_string_pool()->InternString(
+ base::StringView(base::Base64Encode(bytes.data, bytes.size)));
+ event_row.base64_proto_id = event_row.base64_proto.raw_id();
auto event_row_id =
context_.storage->mutable_android_input_event_dispatch_table()
->Insert(event_row)
.id;
+
auto inserter = context_.args_tracker->AddArgsTo(event_row_id);
ArgsParser writer{packet_ts, inserter, *context_.storage};
base::Status status = args_parser_.ParseMessage(
- bytes, ".perfetto.protos.AndroidWindowInputDispatchEvent",
+ bytes,
+ *util::winscope_proto_mapping::GetProtoName(
+ tables::AndroidInputEventDispatchTable::Name()),
nullptr /*parse all fields*/, writer);
if (!status.ok())
context_.storage->IncrementStats(stats::android_input_event_parse_errors);
diff --git a/src/trace_processor/importers/proto/winscope/android_input_event_parser.h b/src/trace_processor/importers/proto/winscope/android_input_event_parser.h
index 96f523b..12ce63b 100644
--- a/src/trace_processor/importers/proto/winscope/android_input_event_parser.h
+++ b/src/trace_processor/importers/proto/winscope/android_input_event_parser.h
@@ -36,7 +36,6 @@
private:
TraceProcessorContext& context_;
- DescriptorPool pool_;
util::ProtoToArgsParser args_parser_;
void ParseMotionEvent(int64_t packet_ts, const protozero::ConstBytes& bytes);
diff --git a/src/trace_processor/importers/proto/winscope/protolog_parser.cc b/src/trace_processor/importers/proto/winscope/protolog_parser.cc
index 8c3b4a4..e9e0c83 100644
--- a/src/trace_processor/importers/proto/winscope/protolog_parser.cc
+++ b/src/trace_processor/importers/proto/winscope/protolog_parser.cc
@@ -35,7 +35,6 @@
#include "src/trace_processor/containers/string_pool.h"
#include "src/trace_processor/importers/proto/packet_sequence_state_generation.h"
#include "src/trace_processor/importers/proto/winscope/protolog_message_decoder.h"
-#include "src/trace_processor/importers/proto/winscope/winscope.descriptor.h"
#include "src/trace_processor/storage/stats.h"
#include "src/trace_processor/storage/trace_storage.h"
#include "src/trace_processor/tables/winscope_tables_py.h"
@@ -45,7 +44,7 @@
ProtoLogParser::ProtoLogParser(TraceProcessorContext* context)
: context_(context),
- args_parser_{pool_},
+ args_parser_{*context_->descriptor_pool_},
log_level_debug_string_id_(context->storage->InternString("DEBUG")),
log_level_verbose_string_id_(context->storage->InternString("VERBOSE")),
log_level_info_string_id_(context->storage->InternString("INFO")),
@@ -53,8 +52,6 @@
log_level_error_string_id_(context->storage->InternString("ERROR")),
log_level_wtf_string_id_(context->storage->InternString("WTF")),
log_level_unknown_string_id_(context_->storage->InternString("UNKNOWN")) {
- pool_.AddFromFileDescriptorSet(kWinscopeDescriptor.data(),
- kWinscopeDescriptor.size());
}
void ProtoLogParser::ParseProtoLogMessage(
diff --git a/src/trace_processor/importers/proto/winscope/protolog_parser.h b/src/trace_processor/importers/proto/winscope/protolog_parser.h
index 13348d6..98b71c8 100644
--- a/src/trace_processor/importers/proto/winscope/protolog_parser.h
+++ b/src/trace_processor/importers/proto/winscope/protolog_parser.h
@@ -48,7 +48,6 @@
std::optional<std::string>& location);
TraceProcessorContext* const context_;
- DescriptorPool pool_;
util::ProtoToArgsParser args_parser_;
const StringId log_level_debug_string_id_;
diff --git a/src/trace_processor/importers/proto/winscope/shell_transitions_parser.cc b/src/trace_processor/importers/proto/winscope/shell_transitions_parser.cc
index 12618ac..07fef5e 100644
--- a/src/trace_processor/importers/proto/winscope/shell_transitions_parser.cc
+++ b/src/trace_processor/importers/proto/winscope/shell_transitions_parser.cc
@@ -17,21 +17,19 @@
#include "src/trace_processor/importers/proto/winscope/shell_transitions_parser.h"
#include "src/trace_processor/importers/proto/winscope/shell_transitions_tracker.h"
+#include "perfetto/ext/base/base64.h"
#include "protos/perfetto/trace/android/shell_transition.pbzero.h"
#include "src/trace_processor/importers/common/args_tracker.h"
#include "src/trace_processor/importers/proto/args_parser.h"
-#include "src/trace_processor/importers/proto/winscope/winscope.descriptor.h"
#include "src/trace_processor/storage/trace_storage.h"
#include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/util/winscope_proto_mapping.h"
namespace perfetto {
namespace trace_processor {
ShellTransitionsParser::ShellTransitionsParser(TraceProcessorContext* context)
- : context_(context), args_parser_{pool_} {
- pool_.AddFromFileDescriptorSet(kWinscopeDescriptor.data(),
- kWinscopeDescriptor.size());
-}
+ : context_(context), args_parser_{*context->descriptor_pool_} {}
void ShellTransitionsParser::ParseTransition(protozero::ConstBytes blob) {
protos::pbzero::ShellTransition::Decoder transition(blob);
@@ -48,10 +46,17 @@
row.set_ts(transition.dispatch_time_ns());
}
+ auto base64_proto = context_->storage->mutable_string_pool()->InternString(
+ base::StringView(base::Base64Encode(blob.data, blob.size)));
+ row.set_base64_proto(base64_proto);
+ row.set_base64_proto_id(base64_proto.raw_id());
auto inserter = context_->args_tracker->AddArgsTo(row_id);
ArgsParser writer(/*timestamp=*/0, inserter, *context_->storage.get());
base::Status status = args_parser_.ParseMessage(
- blob, kShellTransitionsProtoName, nullptr /* parse all fields */, writer);
+ blob,
+ *util::winscope_proto_mapping::GetProtoName(
+ tables::WindowManagerShellTransitionsTable::Name()),
+ nullptr /* parse all fields */, writer);
if (!status.ok()) {
context_->storage->IncrementStats(
@@ -72,6 +77,9 @@
row.handler_id = mapping.id();
row.handler_name = context_->storage->InternString(
base::StringView(mapping.name().ToStdString()));
+ row.base64_proto = context_->storage->mutable_string_pool()->InternString(
+ base::StringView(base::Base64Encode(blob.data, blob.size)));
+ row.base64_proto_id = row.base64_proto.raw_id();
shell_handlers_table->Insert(row);
}
}
diff --git a/src/trace_processor/importers/proto/winscope/shell_transitions_parser.h b/src/trace_processor/importers/proto/winscope/shell_transitions_parser.h
index 44b86d1..3dfe529 100644
--- a/src/trace_processor/importers/proto/winscope/shell_transitions_parser.h
+++ b/src/trace_processor/importers/proto/winscope/shell_transitions_parser.h
@@ -33,11 +33,7 @@
void ParseHandlerMappings(protozero::ConstBytes);
private:
- static constexpr auto* kShellTransitionsProtoName =
- ".perfetto.protos.ShellTransition";
-
TraceProcessorContext* const context_;
- DescriptorPool pool_;
util::ProtoToArgsParser args_parser_;
};
} // namespace trace_processor
diff --git a/src/trace_processor/importers/proto/winscope/shell_transitions_tracker.cc b/src/trace_processor/importers/proto/winscope/shell_transitions_tracker.cc
index 6025d91..3526750 100644
--- a/src/trace_processor/importers/proto/winscope/shell_transitions_tracker.cc
+++ b/src/trace_processor/importers/proto/winscope/shell_transitions_tracker.cc
@@ -19,6 +19,7 @@
#include "src/trace_processor/importers/common/process_tracker.h"
#include "src/trace_processor/storage/metadata.h"
#include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/util/winscope_proto_mapping.h"
namespace perfetto {
namespace trace_processor {
diff --git a/src/trace_processor/importers/proto/winscope/shell_transitions_tracker.h b/src/trace_processor/importers/proto/winscope/shell_transitions_tracker.h
index 07ef736..5218a7b 100644
--- a/src/trace_processor/importers/proto/winscope/shell_transitions_tracker.h
+++ b/src/trace_processor/importers/proto/winscope/shell_transitions_tracker.h
@@ -20,6 +20,7 @@
#include "perfetto/trace_processor/basic_types.h"
#include "src/trace_processor/storage/trace_storage.h"
#include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/util/winscope_proto_mapping.h"
namespace perfetto {
namespace trace_processor {
diff --git a/src/trace_processor/importers/proto/winscope/surfaceflinger_layers_parser.cc b/src/trace_processor/importers/proto/winscope/surfaceflinger_layers_parser.cc
index 20687e4..1975f5d 100644
--- a/src/trace_processor/importers/proto/winscope/surfaceflinger_layers_parser.cc
+++ b/src/trace_processor/importers/proto/winscope/surfaceflinger_layers_parser.cc
@@ -16,21 +16,19 @@
#include "src/trace_processor/importers/proto/winscope/surfaceflinger_layers_parser.h"
+#include "perfetto/ext/base/base64.h"
#include "protos/perfetto/trace/android/surfaceflinger_layers.pbzero.h"
#include "src/trace_processor/importers/common/args_tracker.h"
#include "src/trace_processor/importers/proto/args_parser.h"
-#include "src/trace_processor/importers/proto/winscope/winscope.descriptor.h"
#include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/util/winscope_proto_mapping.h"
namespace perfetto {
namespace trace_processor {
SurfaceFlingerLayersParser::SurfaceFlingerLayersParser(
TraceProcessorContext* context)
- : context_{context}, args_parser_{pool_} {
- pool_.AddFromFileDescriptorSet(kWinscopeDescriptor.data(),
- kWinscopeDescriptor.size());
-}
+ : context_{context}, args_parser_{*context->descriptor_pool_} {}
void SurfaceFlingerLayersParser::Parse(int64_t timestamp,
protozero::ConstBytes blob) {
@@ -38,6 +36,10 @@
blob.size);
tables::SurfaceFlingerLayersSnapshotTable::Row snapshot;
snapshot.ts = timestamp;
+ snapshot.base64_proto =
+ context_->storage->mutable_string_pool()->InternString(
+ base::StringView(base::Base64Encode(blob.data, blob.size)));
+ snapshot.base64_proto_id = snapshot.base64_proto.raw_id();
auto snapshot_id =
context_->storage->mutable_surfaceflinger_layers_snapshot_table()
->Insert(snapshot)
@@ -45,9 +47,12 @@
auto inserter = context_->args_tracker->AddArgsTo(snapshot_id);
ArgsParser writer(timestamp, inserter, *context_->storage);
- base::Status status =
- args_parser_.ParseMessage(blob, kLayersSnapshotProtoName,
- &kLayersSnapshotFieldsToArgsParse, writer);
+ const auto table_name = tables::SurfaceFlingerLayersSnapshotTable::Name();
+ auto allowed_fields =
+ util::winscope_proto_mapping::GetAllowedFields(table_name);
+ base::Status status = args_parser_.ParseMessage(
+ blob, *util::winscope_proto_mapping::GetProtoName(table_name),
+ &allowed_fields.value(), writer);
if (!status.ok()) {
context_->storage->IncrementStats(stats::winscope_sf_layers_parse_errors);
}
@@ -65,14 +70,20 @@
tables::SurfaceFlingerLayersSnapshotTable::Id snapshot_id) {
tables::SurfaceFlingerLayerTable::Row layer;
layer.snapshot_id = snapshot_id;
+ layer.base64_proto = context_->storage->mutable_string_pool()->InternString(
+ base::StringView(base::Base64Encode(blob.data, blob.size)));
+ layer.base64_proto_id = layer.base64_proto.raw_id();
auto layerId =
context_->storage->mutable_surfaceflinger_layer_table()->Insert(layer).id;
ArgsTracker tracker(context_);
auto inserter = tracker.AddArgsTo(layerId);
ArgsParser writer(timestamp, inserter, *context_->storage);
- base::Status status = args_parser_.ParseMessage(
- blob, kLayerProtoName, nullptr /* parse all fields */, writer);
+ base::Status status =
+ args_parser_.ParseMessage(blob,
+ *util::winscope_proto_mapping::GetProtoName(
+ tables::SurfaceFlingerLayerTable::Name()),
+ nullptr /* parse all fields */, writer);
if (!status.ok()) {
context_->storage->IncrementStats(stats::winscope_sf_layers_parse_errors);
}
diff --git a/src/trace_processor/importers/proto/winscope/surfaceflinger_layers_parser.h b/src/trace_processor/importers/proto/winscope/surfaceflinger_layers_parser.h
index f615a8e..987eb81 100644
--- a/src/trace_processor/importers/proto/winscope/surfaceflinger_layers_parser.h
+++ b/src/trace_processor/importers/proto/winscope/surfaceflinger_layers_parser.h
@@ -33,18 +33,11 @@
void Parse(int64_t timestamp, protozero::ConstBytes);
private:
- const std::vector<std::uint32_t> kLayersSnapshotFieldsToArgsParse{1, 2, 4, 5,
- 6, 7, 8};
- static constexpr auto* kLayersSnapshotProtoName =
- ".perfetto.protos.LayersSnapshotProto";
- static constexpr auto* kLayerProtoName = ".perfetto.protos.LayerProto";
-
void ParseLayer(int64_t timestamp,
protozero::ConstBytes blob,
tables::SurfaceFlingerLayersSnapshotTable::Id);
TraceProcessorContext* const context_;
- DescriptorPool pool_;
util::ProtoToArgsParser args_parser_;
};
} // namespace trace_processor
diff --git a/src/trace_processor/importers/proto/winscope/surfaceflinger_transactions_parser.cc b/src/trace_processor/importers/proto/winscope/surfaceflinger_transactions_parser.cc
index c2c5638..2fa14b6 100644
--- a/src/trace_processor/importers/proto/winscope/surfaceflinger_transactions_parser.cc
+++ b/src/trace_processor/importers/proto/winscope/surfaceflinger_transactions_parser.cc
@@ -16,27 +16,28 @@
#include "src/trace_processor/importers/proto/winscope/surfaceflinger_transactions_parser.h"
+#include "perfetto/ext/base/base64.h"
#include "protos/perfetto/trace/android/surfaceflinger_transactions.pbzero.h"
#include "src/trace_processor/importers/common/args_tracker.h"
#include "src/trace_processor/importers/proto/args_parser.h"
-#include "src/trace_processor/importers/proto/winscope/winscope.descriptor.h"
#include "src/trace_processor/storage/trace_storage.h"
#include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/util/winscope_proto_mapping.h"
namespace perfetto {
namespace trace_processor {
SurfaceFlingerTransactionsParser::SurfaceFlingerTransactionsParser(
TraceProcessorContext* context)
- : context_{context}, args_parser_{pool_} {
- pool_.AddFromFileDescriptorSet(kWinscopeDescriptor.data(),
- kWinscopeDescriptor.size());
-}
+ : context_{context}, args_parser_{*context->descriptor_pool_} {}
void SurfaceFlingerTransactionsParser::Parse(int64_t timestamp,
protozero::ConstBytes blob) {
tables::SurfaceFlingerTransactionsTable::Row row;
row.ts = timestamp;
+ row.base64_proto = context_->storage->mutable_string_pool()->InternString(
+ base::StringView(base::Base64Encode(blob.data, blob.size)));
+ row.base64_proto_id = row.base64_proto.raw_id();
auto rowId = context_->storage->mutable_surfaceflinger_transactions_table()
->Insert(row)
.id;
@@ -44,9 +45,11 @@
ArgsTracker tracker(context_);
auto inserter = tracker.AddArgsTo(rowId);
ArgsParser writer(timestamp, inserter, *context_->storage.get());
- base::Status status =
- args_parser_.ParseMessage(blob, kTransactionTraceEntryProtoName,
- nullptr /* parse all fields */, writer);
+ base::Status status = args_parser_.ParseMessage(
+ blob,
+ *util::winscope_proto_mapping::GetProtoName(
+ tables::SurfaceFlingerTransactionsTable::Name()),
+ nullptr /* parse all fields */, writer);
if (!status.ok()) {
context_->storage->IncrementStats(
stats::winscope_sf_transactions_parse_errors);
diff --git a/src/trace_processor/importers/proto/winscope/surfaceflinger_transactions_parser.h b/src/trace_processor/importers/proto/winscope/surfaceflinger_transactions_parser.h
index f9d45cc..b696408 100644
--- a/src/trace_processor/importers/proto/winscope/surfaceflinger_transactions_parser.h
+++ b/src/trace_processor/importers/proto/winscope/surfaceflinger_transactions_parser.h
@@ -32,11 +32,7 @@
void Parse(int64_t timestamp, protozero::ConstBytes);
private:
- static constexpr auto* kTransactionTraceEntryProtoName =
- ".perfetto.protos.TransactionTraceEntry";
-
TraceProcessorContext* const context_;
- DescriptorPool pool_;
util::ProtoToArgsParser args_parser_;
};
} // namespace trace_processor
diff --git a/src/trace_processor/importers/proto/winscope/winscope_module.cc b/src/trace_processor/importers/proto/winscope/winscope_module.cc
index ac81193..a25a05d 100644
--- a/src/trace_processor/importers/proto/winscope/winscope_module.cc
+++ b/src/trace_processor/importers/proto/winscope/winscope_module.cc
@@ -15,11 +15,13 @@
*/
#include "src/trace_processor/importers/proto/winscope/winscope_module.h"
+#include "perfetto/ext/base/base64.h"
#include "protos/perfetto/trace/android/winscope_extensions.pbzero.h"
#include "protos/perfetto/trace/android/winscope_extensions_impl.pbzero.h"
#include "src/trace_processor/importers/proto/args_parser.h"
#include "src/trace_processor/importers/proto/winscope/viewcapture_args_parser.h"
#include "src/trace_processor/importers/proto/winscope/winscope.descriptor.h"
+#include "src/trace_processor/util/winscope_proto_mapping.h"
namespace perfetto {
namespace trace_processor {
@@ -29,12 +31,14 @@
WinscopeModule::WinscopeModule(TraceProcessorContext* context)
: context_{context},
- args_parser_{pool_},
+ args_parser_{*context->descriptor_pool_.get()},
surfaceflinger_layers_parser_(context),
surfaceflinger_transactions_parser_(context),
shell_transitions_parser_(context),
protolog_parser_(context),
android_input_event_parser_(context) {
+ context->descriptor_pool_->AddFromFileDescriptorSet(
+ kWinscopeDescriptor.data(), kWinscopeDescriptor.size());
RegisterForField(TracePacket::kSurfaceflingerLayersSnapshotFieldNumber,
context);
RegisterForField(TracePacket::kSurfaceflingerTransactionsFieldNumber,
@@ -44,9 +48,6 @@
RegisterForField(TracePacket::kProtologMessageFieldNumber, context);
RegisterForField(TracePacket::kProtologViewerConfigFieldNumber, context);
RegisterForField(TracePacket::kWinscopeExtensionsFieldNumber, context);
-
- pool_.AddFromFileDescriptorSet(kWinscopeDescriptor.data(),
- kWinscopeDescriptor.size());
}
ModuleResult WinscopeModule::TokenizePacket(
@@ -135,6 +136,9 @@
protozero::ConstBytes blob) {
tables::InputMethodClientsTable::Row row;
row.ts = timestamp;
+ row.base64_proto = context_->storage->mutable_string_pool()->InternString(
+ base::StringView(base::Base64Encode(blob.data, blob.size)));
+ row.base64_proto_id = row.base64_proto.raw_id();
auto rowId =
context_->storage->mutable_inputmethod_clients_table()->Insert(row).id;
@@ -142,7 +146,9 @@
auto inserter = tracker.AddArgsTo(rowId);
ArgsParser writer(timestamp, inserter, *context_->storage.get());
base::Status status =
- args_parser_.ParseMessage(blob, kInputMethodClientsProtoName,
+ args_parser_.ParseMessage(blob,
+ *util::winscope_proto_mapping::GetProtoName(
+ tables::InputMethodClientsTable::Name()),
nullptr /* parse all fields */, writer);
if (!status.ok()) {
context_->storage->IncrementStats(
@@ -155,6 +161,9 @@
protozero::ConstBytes blob) {
tables::InputMethodManagerServiceTable::Row row;
row.ts = timestamp;
+ row.base64_proto = context_->storage->mutable_string_pool()->InternString(
+ base::StringView(base::Base64Encode(blob.data, blob.size)));
+ row.base64_proto_id = row.base64_proto.raw_id();
auto rowId = context_->storage->mutable_inputmethod_manager_service_table()
->Insert(row)
.id;
@@ -162,9 +171,11 @@
ArgsTracker tracker(context_);
auto inserter = tracker.AddArgsTo(rowId);
ArgsParser writer(timestamp, inserter, *context_->storage.get());
- base::Status status =
- args_parser_.ParseMessage(blob, kInputMethodManagerServiceProtoName,
- nullptr /* parse all fields */, writer);
+ base::Status status = args_parser_.ParseMessage(
+ blob,
+ *util::winscope_proto_mapping::GetProtoName(
+ tables::InputMethodManagerServiceTable::Name()),
+ nullptr /* parse all fields */, writer);
if (!status.ok()) {
context_->storage->IncrementStats(
stats::winscope_inputmethod_manager_service_parse_errors);
@@ -175,6 +186,9 @@
protozero::ConstBytes blob) {
tables::InputMethodServiceTable::Row row;
row.ts = timestamp;
+ row.base64_proto = context_->storage->mutable_string_pool()->InternString(
+ base::StringView(base::Base64Encode(blob.data, blob.size)));
+ row.base64_proto_id = row.base64_proto.raw_id();
auto rowId =
context_->storage->mutable_inputmethod_service_table()->Insert(row).id;
@@ -182,7 +196,9 @@
auto inserter = tracker.AddArgsTo(rowId);
ArgsParser writer(timestamp, inserter, *context_->storage.get());
base::Status status =
- args_parser_.ParseMessage(blob, kInputMethodServiceProtoName,
+ args_parser_.ParseMessage(blob,
+ *util::winscope_proto_mapping::GetProtoName(
+ tables::InputMethodServiceTable::Name()),
nullptr /* parse all fields */, writer);
if (!status.ok()) {
context_->storage->IncrementStats(
@@ -196,14 +212,20 @@
PacketSequenceStateGeneration* sequence_state) {
tables::ViewCaptureTable::Row row;
row.ts = timestamp;
+ row.base64_proto = context_->storage->mutable_string_pool()->InternString(
+ base::StringView(base::Base64Encode(blob.data, blob.size)));
+ row.base64_proto_id = row.base64_proto.raw_id();
auto rowId = context_->storage->mutable_viewcapture_table()->Insert(row).id;
ArgsTracker tracker(context_);
auto inserter = tracker.AddArgsTo(rowId);
ViewCaptureArgsParser writer(timestamp, inserter, *context_->storage.get(),
sequence_state);
- base::Status status = args_parser_.ParseMessage(
- blob, kViewCaptureProtoName, nullptr /* parse all fields */, writer);
+ base::Status status =
+ args_parser_.ParseMessage(blob,
+ *util::winscope_proto_mapping::GetProtoName(
+ tables::ViewCaptureTable::Name()),
+ nullptr /* parse all fields */, writer);
if (!status.ok()) {
context_->storage->IncrementStats(stats::winscope_viewcapture_parse_errors);
}
@@ -213,13 +235,19 @@
protozero::ConstBytes blob) {
tables::WindowManagerTable::Row row;
row.ts = timestamp;
+ row.base64_proto = context_->storage->mutable_string_pool()->InternString(
+ base::StringView(base::Base64Encode(blob.data, blob.size)));
+ row.base64_proto_id = row.base64_proto.raw_id();
auto rowId = context_->storage->mutable_windowmanager_table()->Insert(row).id;
ArgsTracker tracker(context_);
auto inserter = tracker.AddArgsTo(rowId);
ArgsParser writer(timestamp, inserter, *context_->storage.get());
- base::Status status = args_parser_.ParseMessage(
- blob, kWindowManagerProtoName, nullptr /* parse all fields */, writer);
+ base::Status status =
+ args_parser_.ParseMessage(blob,
+ *util::winscope_proto_mapping::GetProtoName(
+ tables::WindowManagerTable::Name()),
+ nullptr /* parse all fields */, writer);
if (!status.ok()) {
context_->storage->IncrementStats(
stats::winscope_windowmanager_parse_errors);
diff --git a/src/trace_processor/importers/proto/winscope/winscope_module.h b/src/trace_processor/importers/proto/winscope/winscope_module.h
index e14be59..b6e876f 100644
--- a/src/trace_processor/importers/proto/winscope/winscope_module.h
+++ b/src/trace_processor/importers/proto/winscope/winscope_module.h
@@ -63,18 +63,7 @@
PacketSequenceStateGeneration* sequence_state);
void ParseWindowManagerData(int64_t timestamp, protozero::ConstBytes blob);
- static constexpr auto* kInputMethodClientsProtoName =
- ".perfetto.protos.InputMethodClientsTraceProto";
- static constexpr auto* kInputMethodManagerServiceProtoName =
- ".perfetto.protos.InputMethodManagerServiceTraceProto";
- static constexpr auto* kInputMethodServiceProtoName =
- ".perfetto.protos.InputMethodServiceTraceProto";
- static constexpr auto* kViewCaptureProtoName = ".perfetto.protos.ViewCapture";
- static constexpr auto* kWindowManagerProtoName =
- ".perfetto.protos.WindowManagerTraceEntry";
-
TraceProcessorContext* const context_;
- DescriptorPool pool_;
util::ProtoToArgsParser args_parser_;
SurfaceFlingerLayersParser surfaceflinger_layers_parser_;
diff --git a/src/trace_processor/importers/syscalls/syscall_tracker_unittest.cc b/src/trace_processor/importers/syscalls/syscall_tracker_unittest.cc
index 260d60d..9a3ac01 100644
--- a/src/trace_processor/importers/syscalls/syscall_tracker_unittest.cc
+++ b/src/trace_processor/importers/syscalls/syscall_tracker_unittest.cc
@@ -16,6 +16,7 @@
#include "src/trace_processor/importers/syscalls/syscall_tracker.h"
+#include "src/trace_processor/importers/common/global_args_tracker.h"
#include "src/trace_processor/importers/common/slice_tracker.h"
#include "test/gtest_and_gmock.h"
@@ -64,6 +65,8 @@
public:
SyscallTrackerTest() {
context.storage.reset(new TraceStorage());
+ context.global_args_tracker.reset(
+ new GlobalArgsTracker(context.storage.get()));
track_tracker = new TrackTracker(&context);
context.track_tracker.reset(track_tracker);
slice_tracker = new MockSliceTracker(&context);
diff --git a/src/trace_processor/importers/systrace/systrace_line_parser.cc b/src/trace_processor/importers/systrace/systrace_line_parser.cc
index 53488a6..2c06136 100644
--- a/src/trace_processor/importers/systrace/systrace_line_parser.cc
+++ b/src/trace_processor/importers/systrace/systrace_line_parser.cc
@@ -137,7 +137,8 @@
}
TrackId track = context_->track_tracker->InternCpuCounterTrack(
- TrackTracker::CpuCounterTrackType::kFrequency, event_cpu.value());
+ tracks::cpu_frequency, event_cpu.value(),
+ TrackTracker::LegacyCharArrayName{"cpufreq"});
context_->event_tracker->PushCounter(line.ts, new_state.value(), track);
} else if (line.event_name == "cpu_idle") {
std::optional<uint32_t> event_cpu = base::StringToUInt32(args["cpu_id"]);
@@ -150,7 +151,7 @@
}
TrackId track = context_->track_tracker->InternCpuCounterTrack(
- TrackTracker::CpuCounterTrackType::kIdle, event_cpu.value());
+ tracks::cpu_idle, event_cpu.value());
context_->event_tracker->PushCounter(line.ts, new_state.value(), track);
} else if (line.event_name == "binder_transaction") {
auto id = base::StringToInt32(args["transaction"]);
@@ -228,7 +229,7 @@
std::string clock_name_str = args["name"] + subtitle;
StringId clock_name =
context_->storage->InternString(base::StringView(clock_name_str));
- TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+ TrackId track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kClockFrequency, clock_name);
context_->event_tracker->PushCounter(line.ts, rate.value(), track);
} else if (line.event_name == "workqueue_execute_start") {
@@ -244,7 +245,7 @@
std::string thermal_zone = args["thermal_zone"] + " Temperature";
StringId track_name =
context_->storage->InternString(base::StringView(thermal_zone));
- TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+ TrackId track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kThermals, track_name);
auto temp = base::StringToInt32(args["temp"]);
if (!temp.has_value()) {
@@ -255,7 +256,7 @@
std::string type = args["type"] + " Cooling Device";
StringId track_name =
context_->storage->InternString(base::StringView(type));
- TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+ TrackId track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kThermals, track_name);
auto target = base::StringToDouble(args["target"]);
if (!target.has_value()) {
diff --git a/src/trace_processor/importers/systrace/systrace_parser.cc b/src/trace_processor/importers/systrace/systrace_parser.cc
index e00322b..c88f4c5 100644
--- a/src/trace_processor/importers/systrace/systrace_parser.cc
+++ b/src/trace_processor/importers/systrace/systrace_parser.cc
@@ -24,6 +24,7 @@
#include "src/trace_processor/importers/common/process_tracker.h"
#include "src/trace_processor/importers/common/slice_tracker.h"
#include "src/trace_processor/importers/common/track_tracker.h"
+#include "src/trace_processor/importers/common/tracks.h"
#include "src/trace_processor/storage/trace_storage.h"
namespace perfetto {
@@ -270,15 +271,15 @@
if (killed_pid != 0) {
UniquePid killed_upid =
context_->process_tracker->GetOrCreateProcess(killed_pid);
- TrackId track =
- context_->track_tracker->InternProcessTrack(killed_upid);
+ TrackId track = context_->track_tracker->InternProcessTrack(
+ tracks::android_lmk, killed_upid);
context_->slice_tracker->Scoped(ts, track, kNullStringId, lmk_id_, 0);
}
// TODO(lalitm): we should not add LMK events to the counters table
// once the UI has support for displaying instants.
} else if (point.name == "ScreenState") {
// Promote ScreenState to its own top level counter.
- TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+ TrackId track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kDeviceState, screen_state_id_);
context_->event_tracker->PushCounter(
ts, static_cast<double>(point.int_value), track);
@@ -286,7 +287,7 @@
} else if (point.name.StartsWith("battery_stats.")) {
// Promote battery_stats conters to global tracks.
StringId name_id = context_->storage->InternString(point.name);
- TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+ TrackId track = context_->track_tracker->LegacyInternGlobalCounterTrack(
TrackTracker::Group::kPower, name_id);
context_->event_tracker->PushCounter(
ts, static_cast<double>(point.int_value), track);
@@ -300,7 +301,8 @@
UniquePid upid =
context_->process_tracker->GetOrCreateProcess(point.tgid);
TrackId track_id =
- context_->track_tracker->InternProcessCounterTrack(name_id, upid);
+ context_->track_tracker->LegacyInternProcessCounterTrack(name_id,
+ upid);
context_->event_tracker->PushCounter(
ts, static_cast<double>(point.int_value), track_id);
}
@@ -326,12 +328,14 @@
UniquePid killed_upid =
context_->process_tracker->GetOrCreateProcess(*killed_pid);
// Add the oom score entry
- TrackId counter_track = context_->track_tracker->InternProcessCounterTrack(
- oom_score_adj_id_, killed_upid);
+ TrackId counter_track =
+ context_->track_tracker->LegacyInternProcessCounterTrack(
+ oom_score_adj_id_, killed_upid);
context_->event_tracker->PushCounter(ts, *oom_score_adj, counter_track);
// Add mem.lmk instant event for consistency with other methods.
- TrackId track = context_->track_tracker->InternProcessTrack(killed_upid);
+ TrackId track = context_->track_tracker->InternProcessTrack(
+ tracks::android_lmk, killed_upid);
context_->slice_tracker->Scoped(ts, track, kNullStringId, lmk_id_, 0);
}
}
diff --git a/src/trace_processor/importers/zip/zip_trace_reader.cc b/src/trace_processor/importers/zip/zip_trace_reader.cc
deleted file mode 100644
index 732559d..0000000
--- a/src/trace_processor/importers/zip/zip_trace_reader.cc
+++ /dev/null
@@ -1,138 +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.
- */
-
-#include "src/trace_processor/importers/zip/zip_trace_reader.h"
-
-#include <algorithm>
-#include <cstdint>
-#include <cstring>
-#include <memory>
-#include <string>
-#include <tuple>
-#include <utility>
-#include <vector>
-
-#include "perfetto/base/logging.h"
-#include "perfetto/base/status.h"
-#include "perfetto/ext/base/status_or.h"
-#include "perfetto/trace_processor/trace_blob.h"
-#include "perfetto/trace_processor/trace_blob_view.h"
-#include "src/trace_processor/forwarding_trace_parser.h"
-#include "src/trace_processor/importers/android_bugreport/android_bugreport_reader.h"
-#include "src/trace_processor/importers/common/trace_file_tracker.h"
-#include "src/trace_processor/types/trace_processor_context.h"
-#include "src/trace_processor/util/status_macros.h"
-#include "src/trace_processor/util/trace_type.h"
-#include "src/trace_processor/util/zip_reader.h"
-
-namespace perfetto::trace_processor {
-
-ZipTraceReader::ZipTraceReader(TraceProcessorContext* context)
- : context_(context) {}
-ZipTraceReader::~ZipTraceReader() = default;
-
-bool ZipTraceReader::Entry::operator<(const Entry& rhs) const {
- // Traces with symbols should be the last ones to be read.
- // TODO(carlscab): Proto traces with just ModuleSymbols packets should be an
- // exception. We actually need those are the very end (once whe have all the
- // Frames). Alternatively we could build a map address -> symbol during
- // tokenization and use this during parsing to resolve symbols.
- if (trace_type == kSymbolsTraceType) {
- return false;
- }
- if (rhs.trace_type == kSymbolsTraceType) {
- return true;
- }
-
- // Proto traces should always parsed first as they might contains clock sync
- // data needed to correctly parse other traces.
- if (rhs.trace_type == TraceType::kProtoTraceType) {
- return false;
- }
- if (trace_type == TraceType::kProtoTraceType) {
- return true;
- }
-
- if (rhs.trace_type == TraceType::kGzipTraceType) {
- return false;
- }
- if (trace_type == TraceType::kGzipTraceType) {
- return true;
- }
-
- return std::tie(name, index) < std::tie(rhs.name, rhs.index);
-}
-
-base::Status ZipTraceReader::Parse(TraceBlobView blob) {
- return zip_reader_.Parse(std::move(blob));
-}
-
-base::Status ZipTraceReader::NotifyEndOfFile() {
- std::vector<util::ZipFile> files = zip_reader_.TakeFiles();
-
- // Android bug reports are ZIP files and its files do not get handled
- // separately.
- if (AndroidBugreportReader::IsAndroidBugReport(files)) {
- return AndroidBugreportReader::Parse(context_, std::move(files));
- }
-
- ASSIGN_OR_RETURN(std::vector<Entry> entries,
- ExtractEntries(std::move(files)));
- std::sort(entries.begin(), entries.end());
-
- for (Entry& e : entries) {
- ScopedActiveTraceFile trace_file =
- context_->trace_file_tracker->StartNewFile(e.name, e.trace_type,
- e.uncompressed_data.size());
-
- auto chunk_reader = std::make_unique<ForwardingTraceParser>(context_);
- auto& parser = *chunk_reader;
- context_->chunk_readers.push_back(std::move(chunk_reader));
-
- RETURN_IF_ERROR(parser.Parse(std::move(e.uncompressed_data)));
- RETURN_IF_ERROR(parser.NotifyEndOfFile());
-
- // Make sure the ForwardingTraceParser determined the same trace type as we
- // did.
- PERFETTO_CHECK(parser.trace_type() == e.trace_type);
- }
- return base::OkStatus();
-}
-
-base::StatusOr<std::vector<ZipTraceReader::Entry>>
-ZipTraceReader::ExtractEntries(std::vector<util::ZipFile> files) {
- // TODO(carlsacab): There is a lot of unnecessary copying going on here.
- // ZipTraceReader can directly parse the ZIP file and given that we know the
- // decompressed size we could directly decompress into TraceBlob chunks and
- // send them to the tokenizer.
- std::vector<Entry> entries;
- std::vector<uint8_t> buffer;
- for (size_t i = 0; i < files.size(); ++i) {
- const util::ZipFile& zip_file = files[i];
- Entry entry;
- entry.name = zip_file.name();
- entry.index = i;
- RETURN_IF_ERROR(files[i].Decompress(&buffer));
- entry.uncompressed_data =
- TraceBlobView(TraceBlob::CopyFrom(buffer.data(), buffer.size()));
- entry.trace_type = GuessTraceType(entry.uncompressed_data.data(),
- entry.uncompressed_data.size());
- entries.push_back(std::move(entry));
- }
- return std::move(entries);
-}
-
-} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/zip/zip_trace_reader.h b/src/trace_processor/importers/zip/zip_trace_reader.h
deleted file mode 100644
index b6620ae..0000000
--- a/src/trace_processor/importers/zip/zip_trace_reader.h
+++ /dev/null
@@ -1,76 +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.
- */
-
-#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_ZIP_ZIP_TRACE_READER_H_
-#define SRC_TRACE_PROCESSOR_IMPORTERS_ZIP_ZIP_TRACE_READER_H_
-
-#include <cstddef>
-#include <memory>
-#include <string>
-#include <vector>
-
-#include "perfetto/base/status.h"
-#include "perfetto/ext/base/status_or.h"
-#include "perfetto/trace_processor/trace_blob_view.h"
-#include "src/trace_processor/importers/common/chunked_trace_reader.h"
-#include "src/trace_processor/util/trace_type.h"
-#include "src/trace_processor/util/zip_reader.h"
-
-namespace perfetto::trace_processor {
-
-class ForwardingTraceParser;
-class TraceProcessorContext;
-
-// Forwards files contained in a ZIP to the appropiate ChunkedTraceReader. It is
-// guaranteed that proto traces will be parsed first.
-class ZipTraceReader : public ChunkedTraceReader {
- public:
- explicit ZipTraceReader(TraceProcessorContext* context);
- ~ZipTraceReader() override;
-
- // ChunkedTraceReader implementation
- base::Status Parse(TraceBlobView) override;
- base::Status NotifyEndOfFile() override;
-
- private:
- // Represents a file in the ZIP file. Used to sort them before sending the
- // files one by one to a `ForwardingTraceParser` instance.
- struct Entry {
- // File name. Used to break ties.
- std::string name;
- // Position in the zip file. Used to break ties.
- size_t index;
- // Trace type. This is the main attribute traces are ordered by. Proto
- // traces are always parsed first as they might contains clock sync
- // data needed to correctly parse other traces.
- TraceType trace_type;
- TraceBlobView uncompressed_data;
- // Comparator used to determine the order in which files in the ZIP will be
- // read.
- bool operator<(const Entry& rhs) const;
- };
-
- static base::StatusOr<std::vector<Entry>> ExtractEntries(
- std::vector<util::ZipFile> files);
- base::Status ParseEntry(Entry entry);
-
- TraceProcessorContext* const context_;
- util::ZipReader zip_reader_;
-};
-
-} // namespace perfetto::trace_processor
-
-#endif // SRC_TRACE_PROCESSOR_IMPORTERS_ZIP_ZIP_TRACE_READER_H_
diff --git a/src/trace_processor/metrics/metrics_unittest.cc b/src/trace_processor/metrics/metrics_unittest.cc
index 4779251..7b5a050 100644
--- a/src/trace_processor/metrics/metrics_unittest.cc
+++ b/src/trace_processor/metrics/metrics_unittest.cc
@@ -88,9 +88,9 @@
ProtoDescriptor descriptor("file.proto", ".perfetto.protos",
".perfetto.protos.TestProto",
ProtoDescriptor::Type::kMessage, std::nullopt);
- descriptor.AddField(FieldDescriptor("int_value", 1,
- FieldDescriptorProto::TYPE_INT64, "",
- std::vector<uint8_t>(), false, false));
+ descriptor.AddField(
+ FieldDescriptor("int_value", 1, FieldDescriptorProto::TYPE_INT64, "",
+ std::vector<uint8_t>(), std::nullopt, false, false));
ProtoBuilder builder(&pool, &descriptor);
ASSERT_OK(builder.AppendSqlValue("int_value", SqlValue::Long(12345)));
@@ -112,9 +112,9 @@
ProtoDescriptor descriptor("file.proto", ".perfetto.protos",
".perfetto.protos.TestProto",
ProtoDescriptor::Type::kMessage, std::nullopt);
- descriptor.AddField(FieldDescriptor("double_value", 1,
- FieldDescriptorProto::TYPE_DOUBLE, "",
- std::vector<uint8_t>(), false, false));
+ descriptor.AddField(
+ FieldDescriptor("double_value", 1, FieldDescriptorProto::TYPE_DOUBLE, "",
+ std::vector<uint8_t>(), std::nullopt, false, false));
ProtoBuilder builder(&pool, &descriptor);
ASSERT_OK(builder.AppendSqlValue("double_value", SqlValue::Double(1.2345)));
@@ -136,9 +136,9 @@
ProtoDescriptor descriptor("file.proto", ".perfetto.protos",
".perfetto.protos.TestProto",
ProtoDescriptor::Type::kMessage, std::nullopt);
- descriptor.AddField(FieldDescriptor("string_value", 1,
- FieldDescriptorProto::TYPE_STRING, "",
- std::vector<uint8_t>(), false, false));
+ descriptor.AddField(
+ FieldDescriptor("string_value", 1, FieldDescriptorProto::TYPE_STRING, "",
+ std::vector<uint8_t>(), std::nullopt, false, false));
ProtoBuilder builder(&pool, &descriptor);
ASSERT_OK(
@@ -164,9 +164,9 @@
ProtoDescriptor nested("file.proto", ".perfetto.protos",
".perfetto.protos.TestProto.NestedProto",
ProtoDescriptor::Type::kMessage, std::nullopt);
- nested.AddField(FieldDescriptor("nested_int_value", 1,
- FieldDescriptorProto::TYPE_INT64, "",
- std::vector<uint8_t>(), false, false));
+ nested.AddField(
+ FieldDescriptor("nested_int_value", 1, FieldDescriptorProto::TYPE_INT64,
+ "", std::vector<uint8_t>(), std::nullopt, false, false));
ProtoDescriptor descriptor("file.proto", ".perfetto.protos",
".perfetto.protos.TestProto",
@@ -174,7 +174,7 @@
auto field =
FieldDescriptor("nested_value", 1, FieldDescriptorProto::TYPE_MESSAGE,
".perfetto.protos.TestProto.NestedProto",
- std::vector<uint8_t>(), false, false);
+ std::vector<uint8_t>(), std::nullopt, false, false);
field.set_resolved_type_name(".perfetto.protos.TestProto.NestedProto");
descriptor.AddField(field);
@@ -214,9 +214,9 @@
ProtoDescriptor descriptor("file.proto", ".perfetto.protos",
".perfetto.protos.TestProto",
ProtoDescriptor::Type::kMessage, std::nullopt);
- descriptor.AddField(FieldDescriptor("rep_int_value", 1,
- FieldDescriptorProto::TYPE_INT64, "",
- std::vector<uint8_t>(), true, false));
+ descriptor.AddField(
+ FieldDescriptor("rep_int_value", 1, FieldDescriptorProto::TYPE_INT64, "",
+ std::vector<uint8_t>(), std::nullopt, true, false));
ASSERT_THAT(RepeatedFieldBuilder().SerializeToProtoBuilderResult(),
IsEmpty());
@@ -241,9 +241,9 @@
ProtoDescriptor descriptor("file.proto", ".perfetto.protos",
".perfetto.protos.TestProto",
ProtoDescriptor::Type::kMessage, std::nullopt);
- descriptor.AddField(FieldDescriptor("rep_int_value", 1,
- FieldDescriptorProto::TYPE_INT64, "",
- std::vector<uint8_t>(), true, false));
+ descriptor.AddField(
+ FieldDescriptor("rep_int_value", 1, FieldDescriptorProto::TYPE_INT64, "",
+ std::vector<uint8_t>(), std::nullopt, true, false));
RepeatedFieldBuilder rep_builder;
rep_builder.AddSqlValue(SqlValue::Long(1234));
@@ -289,7 +289,8 @@
ProtoDescriptor::Type::kMessage, std::nullopt);
FieldDescriptor enum_field("enum_value", 1, FieldDescriptorProto::TYPE_ENUM,
".perfetto.protos.TestEnum",
- std::vector<uint8_t>(), false, false);
+ std::vector<uint8_t>(), std::nullopt, false,
+ false);
enum_field.set_resolved_type_name(".perfetto.protos.TestEnum");
descriptor.AddField(enum_field);
pool.AddProtoDescriptorForTesting(descriptor);
diff --git a/src/trace_processor/metrics/sql/android/BUILD.gn b/src/trace_processor/metrics/sql/android/BUILD.gn
index a10366b..ae6fb83 100644
--- a/src/trace_processor/metrics/sql/android/BUILD.gn
+++ b/src/trace_processor/metrics/sql/android/BUILD.gn
@@ -59,7 +59,6 @@
"android_multiuser_populator.sql",
"android_netperf.sql",
"android_oom_adjuster.sql",
- "android_other_traces.sql",
"android_package_list.sql",
"android_powrails.sql",
"android_proxy_power.sql",
@@ -70,7 +69,6 @@
"android_sysui_notifications_blocking_calls_metric.sql",
"android_task_names.sql",
"android_trace_quality.sql",
- "android_trusty_workqueues.sql",
"codec_metrics.sql",
"composer_execution.sql",
"composition_layers.sql",
@@ -86,7 +84,6 @@
"jank/cujs_boundaries.sql",
"jank/frames.sql",
"jank/internal/counters.sql",
- "jank/internal/derived_events.sql",
"jank/internal/query_base.sql",
"jank/internal/query_frame_slice.sql",
"jank/params.sql",
diff --git a/src/trace_processor/metrics/sql/android/android_batt.sql b/src/trace_processor/metrics/sql/android/android_batt.sql
index 6600841..0e12c1d 100644
--- a/src/trace_processor/metrics/sql/android/android_batt.sql
+++ b/src/trace_processor/metrics/sql/android/android_batt.sql
@@ -81,6 +81,33 @@
)
SELECT * FROM counter_leading_intervals!(power_mw_counter);
+DROP TABLE IF EXISTS energy_usage_estimate;
+CREATE PERFETTO TABLE energy_usage_estimate AS
+with energy_counters as (
+select
+ ts,
+ CASE
+ WHEN energy_counter_uwh IS NOT NULL THEN energy_counter_uwh
+ ELSE charge_uah * voltage_uv / 1e12 END as energy
+ from android_battery_charge
+), start_energy as (
+ select
+ min(ts),
+ energy
+ from energy_counters
+), end_energy as (
+ select
+ max(ts),
+ energy
+ from energy_counters
+)
+select
+ -- If the battery is discharging, the start value will be greater than the end
+ -- and the estimate will report a positive value.
+ -- Battery energy is in watt hours, so multiply by 3600 to convert to joules.
+ (s.energy - e.energy) * 3600 as estimate
+from start_energy s, end_energy e;
+
DROP VIEW IF EXISTS android_batt_output;
CREATE PERFETTO VIEW android_batt_output AS
SELECT AndroidBatteryMetric(
@@ -115,7 +142,9 @@
'total_wakelock_ns',
(SELECT SUM(ts_end - ts) FROM android_batt_wakelocks_merged),
'avg_power_mw',
- (SELECT SUM(value * dur) / SUM(dur) FROM power_mw_intervals)
+ (SELECT SUM(value * dur) / SUM(dur) FROM power_mw_intervals),
+ 'energy_usage_estimate',
+ (select estimate FROM energy_usage_estimate)
))
FROM (
SELECT dur, value AS state, 'total' AS tbl
diff --git a/src/trace_processor/metrics/sql/android/android_camera.sql b/src/trace_processor/metrics/sql/android/android_camera.sql
index a893cf6..1d54753 100644
--- a/src/trace_processor/metrics/sql/android/android_camera.sql
+++ b/src/trace_processor/metrics/sql/android/android_camera.sql
@@ -101,15 +101,6 @@
SUM(rss_and_dma_val * dur / 1e3) / SUM(dur / 1e3) AS avg_value
FROM rss_and_dma_all_camera_span;
-DROP VIEW IF EXISTS android_camera_event;
-CREATE PERFETTO VIEW android_camera_event AS
-SELECT
- 'counter' AS track_type,
- 'Camera Memory' AS track_name,
- ts,
- rss_and_dma_val AS value
-FROM rss_and_dma_all_camera_span;
-
DROP VIEW IF EXISTS android_camera_output;
CREATE PERFETTO VIEW android_camera_output AS
SELECT
diff --git a/src/trace_processor/metrics/sql/android/android_fastrpc.sql b/src/trace_processor/metrics/sql/android/android_fastrpc.sql
index 6d174de..94523ec 100644
--- a/src/trace_processor/metrics/sql/android/android_fastrpc.sql
+++ b/src/trace_processor/metrics/sql/android/android_fastrpc.sql
@@ -43,20 +43,6 @@
FROM fastrpc_raw_allocs
GROUP BY 1;
--- We need to group by ts here as we can have two events from
--- different processes occurring at the same timestamp. We take the
--- max as this will take both allocations into account at that
--- timestamp.
-DROP VIEW IF EXISTS android_fastrpc_event;
-CREATE PERFETTO VIEW android_fastrpc_event AS
-SELECT
- 'counter' AS track_type,
- printf('fastrpc allocations (subsystem: %s)', subsystem_name) AS track_name,
- ts,
- MAX(value) AS value
-FROM fastrpc_raw_allocs
-GROUP BY 1, 2, 3;
-
DROP VIEW IF EXISTS android_fastrpc_output;
CREATE PERFETTO VIEW android_fastrpc_output AS
SELECT AndroidFastrpcMetric(
diff --git a/src/trace_processor/metrics/sql/android/android_ion.sql b/src/trace_processor/metrics/sql/android/android_ion.sql
index 1071eef..37ccbf3 100644
--- a/src/trace_processor/metrics/sql/android/android_ion.sql
+++ b/src/trace_processor/metrics/sql/android/android_ion.sql
@@ -65,20 +65,6 @@
FROM ion_raw_allocs
GROUP BY 1;
--- We need to group by ts here as we can have two ion events from
--- different processes occurring at the same timestamp. We take the
--- max as this will take both allocations into account at that
--- timestamp.
-DROP VIEW IF EXISTS android_ion_event;
-CREATE PERFETTO VIEW android_ion_event AS
-SELECT
- 'counter' AS track_type,
- printf('ION allocations (heap: %s)', heap_name) AS track_name,
- ts,
- MAX(value) AS value
-FROM ion_raw_allocs
-GROUP BY 1, 2, 3;
-
DROP VIEW IF EXISTS android_ion_output;
CREATE PERFETTO VIEW android_ion_output AS
SELECT AndroidIonMetric(
diff --git a/src/trace_processor/metrics/sql/android/android_jank_cuj.sql b/src/trace_processor/metrics/sql/android/android_jank_cuj.sql
index 39baf04..09b84ce 100644
--- a/src/trace_processor/metrics/sql/android/android_jank_cuj.sql
+++ b/src/trace_processor/metrics/sql/android/android_jank_cuj.sql
@@ -75,13 +75,6 @@
-- The same numbers are also reported by FrameTracker to statsd.
SELECT RUN_METRIC('android/jank/internal/counters.sql');
--- Creates derived events to visualize a few of the created tables.
--- Used only for debugging so by default not used and not displayed in the UI.
--- See https://perfetto.dev/docs/contributing/common-tasks#adding-new-derived-events
--- for instructions on how to add these events to the UI.
-SELECT RUN_METRIC('android/jank/internal/derived_events.sql');
-
-
DROP VIEW IF EXISTS android_jank_cuj_output;
CREATE PERFETTO VIEW android_jank_cuj_output AS
SELECT
diff --git a/src/trace_processor/metrics/sql/android/android_lmk.sql b/src/trace_processor/metrics/sql/android/android_lmk.sql
index ae49a0a..3cd9efd 100644
--- a/src/trace_processor/metrics/sql/android/android_lmk.sql
+++ b/src/trace_processor/metrics/sql/android/android_lmk.sql
@@ -37,46 +37,6 @@
AND raw_events.ts < oom_scores.ts + oom_scores.dur)
ORDER BY 1;
-DROP VIEW IF EXISTS android_lmk_event;
-CREATE PERFETTO VIEW android_lmk_event AS
-WITH raw_events AS (
- SELECT
- ts,
- LEAD(ts) OVER (ORDER BY ts) - ts AS dur,
- CAST(value AS INTEGER) AS pid
- FROM counter c
- JOIN counter_track t ON t.id = c.track_id
- WHERE t.name = 'kill_one_process'
- UNION ALL
- SELECT
- slice.ts,
- slice.dur,
- CAST(STR_SPLIT(slice.name, ",", 1) AS INTEGER) AS pid
- FROM slice
- WHERE slice.name GLOB 'lmk,*'
-),
-lmks_with_proc_name AS (
- SELECT
- *,
- process.name AS process_name
- FROM raw_events
- LEFT JOIN process ON
- process.pid = raw_events.pid
- AND (raw_events.ts >= process.start_ts OR process.start_ts IS NULL)
- AND (raw_events.ts < process.end_ts OR process.end_ts IS NULL)
- WHERE raw_events.pid != 0
-)
-SELECT
- 'slice' AS track_type,
- 'Low Memory Kills (LMKs)' AS track_name,
- ts,
- dur,
- CASE
- WHEN process_name IS NULL THEN printf('Process %d', lmk.pid)
- ELSE printf('%s (pid: %d)', process_name, lmk.pid)
- END AS slice_name
-FROM lmks_with_proc_name AS lmk;
-
DROP VIEW IF EXISTS android_lmk_output;
CREATE PERFETTO VIEW android_lmk_output AS
WITH lmk_counts AS (
diff --git a/src/trace_processor/metrics/sql/android/android_other_traces.sql b/src/trace_processor/metrics/sql/android/android_other_traces.sql
deleted file mode 100644
index 367de23..0000000
--- a/src/trace_processor/metrics/sql/android/android_other_traces.sql
+++ /dev/null
@@ -1,47 +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.
---
-
-DROP VIEW IF EXISTS android_other_traces_view;
-CREATE PERFETTO VIEW android_other_traces_view AS
-SELECT
- ts,
- dur,
- SUBSTR(slice.name, 15) AS uuid,
- 'Finalize' AS event_type
-FROM slice
-JOIN track
- ON track.name = 'OtherTraces' AND slice.track_id = track.id
-WHERE
- slice.name GLOB 'finalize-uuid-*';
-
-DROP VIEW IF EXISTS android_other_traces_event;
-CREATE PERFETTO VIEW android_other_traces_event AS
-SELECT
- 'slice' AS track_type,
- 'Other Traces' AS track_name,
- ts,
- dur,
- event_type || ' ' || uuid AS slice_name
-FROM android_other_traces_view;
-
-DROP VIEW IF EXISTS android_other_traces_output;
-CREATE PERFETTO VIEW android_other_traces_output AS
-SELECT AndroidOtherTracesMetric(
- 'finalized_traces_uuid', (
- SELECT RepeatedField(uuid)
- FROM android_other_traces_view
- WHERE event_type = 'Finalize')
- );
diff --git a/src/trace_processor/metrics/sql/android/android_startup.sql b/src/trace_processor/metrics/sql/android/android_startup.sql
index a4c8c75..7deb13e 100644
--- a/src/trace_processor/metrics/sql/android/android_startup.sql
+++ b/src/trace_processor/metrics/sql/android/android_startup.sql
@@ -241,6 +241,8 @@
dur_sum_slice_proto_for_launch(launches.startup_id, 'inflate'),
'time_get_resources',
dur_sum_slice_proto_for_launch(launches.startup_id, 'ResourcesManager#getResources'),
+ 'time_class_initialization',
+ dur_sum_slice_proto_for_launch(launches.startup_id, 'L*/*;'),
'time_dex_open',
dur_sum_slice_proto_for_launch(launches.startup_id, 'OpenDexFilesFromOat*'),
'time_verify_class',
@@ -293,6 +295,10 @@
FROM ANDROID_SLICES_FOR_STARTUP_AND_SLICE_NAME(launches.startup_id, 'JIT compiling*')
WHERE thread_name = 'Jit thread pool'
),
+ 'class_initialization_count', (
+ SELECT IIF(COUNT(1) = 0, NULL, COUNT(1))
+ FROM ANDROID_SLICES_FOR_STARTUP_AND_SLICE_NAME(launches.startup_id, 'L*/*;')
+ ),
'other_processes_spawned_count', (
SELECT COUNT(1)
FROM process
diff --git a/src/trace_processor/metrics/sql/android/android_surfaceflinger.sql b/src/trace_processor/metrics/sql/android/android_surfaceflinger.sql
index a24bc9b..1801d33 100644
--- a/src/trace_processor/metrics/sql/android/android_surfaceflinger.sql
+++ b/src/trace_processor/metrics/sql/android/android_surfaceflinger.sql
@@ -29,17 +29,6 @@
'output', 'gpu_frame_missed'
);
-DROP VIEW IF EXISTS android_surfaceflinger_event;
-CREATE PERFETTO VIEW android_surfaceflinger_event AS
-SELECT
- 'slice' AS track_type,
- 'Android Missed Frames' AS track_name,
- ts,
- dur,
- 'Frame missed' AS slice_name
-FROM frame_missed
-WHERE value = 1 AND ts IS NOT NULL;
-
DROP VIEW IF EXISTS surfaceflinger_track;
CREATE PERFETTO VIEW surfaceflinger_track AS
SELECT tr.id AS track_id, t.utid, t.tid
diff --git a/src/trace_processor/metrics/sql/android/android_trusty_workqueues.sql b/src/trace_processor/metrics/sql/android/android_trusty_workqueues.sql
deleted file mode 100644
index d34b225..0000000
--- a/src/trace_processor/metrics/sql/android/android_trusty_workqueues.sql
+++ /dev/null
@@ -1,21 +0,0 @@
--- Gather the `nop_work_func` slices and the CPU they each ran on and use that
--- information to generate a metric that displays just the Trusty workqueue
--- events grouped by CPU.
-DROP VIEW IF EXISTS android_trusty_workqueues_event;
-CREATE PERFETTO VIEW android_trusty_workqueues_event AS
-SELECT
- 'slice' AS track_type,
- name AS slice_name,
- ts,
- dur,
- 'Cpu ' || EXTRACT_ARG(arg_set_id, 'cpu') AS track_name,
- 'Trusty Workqueues' AS group_name
-FROM slice
-WHERE slice.name GLOB 'nop_work_func*';
-
--- Generate the final metric output. This is empty because we're only using the
--- metric to generate custom tracks, and so don't have any aggregate data to
--- generate.
-DROP VIEW IF EXISTS android_trusty_workqueues_output;
-CREATE PERFETTO VIEW android_trusty_workqueues_output AS
-SELECT AndroidTrustyWorkqueues();
diff --git a/src/trace_processor/metrics/sql/android/codec_metrics.sql b/src/trace_processor/metrics/sql/android/codec_metrics.sql
index 68b4646..21ab859 100644
--- a/src/trace_processor/metrics/sql/android/codec_metrics.sql
+++ b/src/trace_processor/metrics/sql/android/codec_metrics.sql
@@ -14,13 +14,20 @@
-- limitations under the License.
--
+INCLUDE PERFETTO MODULE linux.cpu.utilization.thread;
+INCLUDE PERFETTO MODULE linux.cpu.utilization.slice;
+INCLUDE PERFETTO MODULE slices.with_context;
+INCLUDE PERFETTO MODULE slices.cpu_time;
+
SELECT RUN_METRIC('android/android_cpu.sql');
+SELECT RUN_METRIC('android/android_powrails.sql');
-- Attaching thread proto with media thread name
DROP VIEW IF EXISTS core_type_proto_per_thread_name;
CREATE PERFETTO VIEW core_type_proto_per_thread_name AS
SELECT
-thread.name as thread_name,
+utid,
+thread.name AS thread_name,
core_type_proto_per_thread.proto AS proto
FROM core_type_proto_per_thread
JOIN thread using(utid)
@@ -28,54 +35,32 @@
thread.name = 'CodecLooper'
GROUP BY thread.name;
--- aggregate all cpu the codec threads
-DROP VIEW IF EXISTS codec_per_thread_cpu_use;
-CREATE PERFETTO VIEW codec_per_thread_cpu_use AS
-SELECT
- upid,
- process.name AS process_name,
- thread.name AS thread_name,
- CAST(SUM(sched.dur) as INT64) AS cpu_time_ns,
- COUNT(DISTINCT utid) AS num_threads
-FROM sched
-JOIN thread USING(utid)
-JOIN process USING(upid)
-WHERE thread.name = 'MediaCodec_loop' OR
- thread.name = 'CodecLooper'
-GROUP BY process.name, thread.name;
-
-- All process that has codec thread
-DROP VIEW IF EXISTS android_codec_process;
-CREATE PERFETTO VIEW android_codec_process AS
+DROP TABLE IF EXISTS android_codec_process;
+CREATE PERFETTO TABLE android_codec_process AS
SELECT
+ utid,
upid,
- process.name as process_name
-FROM sched
-JOIN thread using(utid)
+ process.name AS process_name
+FROM thread
JOIN process using(upid)
WHERE thread.name = 'MediaCodec_loop' OR
thread.name = 'CodecLooper'
-GROUP BY process_name;
+GROUP BY process_name, thread.name;
--- Total cpu for a process
-DROP VIEW IF EXISTS codec_total_per_process_cpu_use;
-CREATE PERFETTO VIEW codec_total_per_process_cpu_use AS
+-- Getting cpu cycles for the threads
+DROP VIEW IF EXISTS cpu_cycles_runtime;
+CREATE PERFETTO VIEW cpu_cycles_runtime AS
SELECT
- upid,
+ utid,
+ megacycles,
+ runtime,
+ proto,
process_name,
- CAST(SUM(sched.dur) as INT64) AS media_process_cpu_time_ns
-FROM sched
-JOIN thread using(utid)
-JOIN android_codec_process using(upid)
-GROUP BY process_name;
-
--- Joining total process with media thread table
-DROP VIEW IF EXISTS codec_per_process_thread_cpu_use;
-CREATE PERFETTO VIEW codec_per_process_thread_cpu_use AS
-SELECT
- *
-FROM codec_total_per_process_cpu_use
-JOIN codec_per_thread_cpu_use using(process_name);
+ thread_name
+FROM android_codec_process
+JOIN cpu_cycles_per_thread using(utid)
+JOIN core_type_proto_per_thread_name using(utid);
-- Traces are collected using specific traits in codec framework. These traits
-- are mapped to actual names of slices and then combined with other tables to
@@ -92,68 +77,75 @@
ELSE $slice_name
END;
--- traits strings from codec framework
+-- Traits strings from codec framework
DROP TABLE IF EXISTS trace_trait_table;
-CREATE TABLE trace_trait_table(
- trace_trait varchar(100));
-insert into trace_trait_table (trace_trait) values
- ('MediaCodec'),
- ('CCodec'),
- ('C2PooledBlockPool'),
- ('C2BufferQueueBlockPool'),
- ('Codec2'),
- ('ACodec'),
- ('FrameDecoder');
+CREATE TABLE trace_trait_table(trace_trait TEXT UNIQUE);
+INSERT INTO trace_trait_table VALUES
+ ('MediaCodec::'),
+ ('CCodec::'),
+ ('CCodecBufferChannel::'),
+ ('C2PooledBlockPool::'),
+ ('C2hal::'),
+ ('ACodec::'),
+ ('FrameDecoder::');
-- Maps traits to slice strings. Any string with '@' is considered to indicate
-- the same trace with different information.Hence those strings are delimited
--- using '@' and considered as part of single trace.
-DROP VIEW IF EXISTS codec_slices;
-CREATE PERFETTO VIEW codec_slices AS
+-- using '@' and considered as part of single slice.
+
+-- View to hold slice ids(sid) and the assigned slice ids for codec slices.
+DROP TABLE IF EXISTS codec_slices;
+CREATE PERFETTO TABLE codec_slices AS
+WITH
+ __codec_slices AS (
+ SELECT DISTINCT
+ extract_codec_string(name, '@') AS codec_string,
+ slice.id AS sid,
+ slice.name AS sname
+ FROM slice
+ JOIN trace_trait_table ON slice.name glob trace_trait || '*'
+ ),
+ _codec_slices AS (
+ SELECT DISTINCT codec_string,
+ ROW_NUMBER() OVER() AS codec_slice_idx
+ FROM __codec_slices
+ GROUP BY codec_string
+ )
SELECT
- DISTINCT extract_codec_string(slice.name, '@') as codec_slice_string
-FROM slice
-JOIN trace_trait_table ON slice.name glob '*' || trace_trait || '*';
+ codec_slice_idx,
+ a.codec_string,
+ sid
+FROM __codec_slices a
+JOIN _codec_slices b USING(codec_string);
--- combine slice and thread info
-DROP VIEW IF EXISTS slice_with_utid;
-CREATE PERFETTO VIEW slice_with_utid AS
-SELECT
- extract_codec_string(slice.name, '@') as codec_string,
- ts,
- dur,
- upid,
- 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);
-
--- Combine with thread_state info
-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
-);
-
--- Get cpu_running_time for all the slices of interest
-DROP VIEW IF EXISTS slice_cpu_running;
-CREATE PERFETTO VIEW slice_cpu_running AS
+-- Combine slice and and cpu dur and cycles info
+DROP TABLE IF EXISTS codec_slice_cpu_running;
+CREATE PERFETTO TABLE codec_slice_cpu_running AS
SELECT
codec_string,
- sum(dur) as cpu_time,
- sum(case when state = 'Running' then dur else 0 end) as cpu_run_ns,
- thread_name,
- process.name as process_name,
- slice_id,
- slice_name
-FROM slice_thread_state_breakdown
-LEFT JOIN process using(upid)
-where codec_string in (select codec_slice_string from codec_slices)
-GROUP BY codec_string, thread_name, process_name;
+ MIN(ts) AS ts,
+ MAX(ts + t.dur) AS max_ts,
+ SUM(t.dur) AS dur,
+ SUM(ct.cpu_time) AS cpu_run_ns,
+ SUM(megacycles) AS cpu_cycles,
+ cc.thread_name,
+ cc.process_name
+FROM codec_slices
+JOIN thread_slice t ON(sid = t.id)
+JOIN thread_slice_cpu_cycles cc ON(sid = cc.id)
+JOIN thread_slice_cpu_time ct ON(sid = ct.id)
+GROUP BY codec_slice_idx, cc.thread_name, cc.process_name;
+-- POWER consumed during codec use.
+DROP VIEW IF EXISTS codec_power_mw;
+CREATE PERFETTO VIEW codec_power_mw AS
+SELECT
+ AndroidCodecMetrics_Rail_Info (
+ 'energy', tot_used_power,
+ 'power_mw', tot_used_power / (powrail_end_ts - powrail_start_ts)
+ ) AS proto,
+ name
+FROM avg_used_powers;
-- Generate proto for the trace
DROP VIEW IF EXISTS metrics_per_slice_type;
@@ -163,26 +155,25 @@
codec_string,
AndroidCodecMetrics_Detail(
'thread_name', thread_name,
- 'total_cpu_ns', CAST(cpu_time as INT64),
- 'running_cpu_ns', CAST(cpu_run_ns as INT64)
+ 'total_cpu_ns', CAST(dur AS INT64),
+ 'running_cpu_ns', CAST(cpu_run_ns AS INT64),
+ 'cpu_cycles', CAST(cpu_cycles AS INT64)
) AS proto
-FROM slice_cpu_running;
+FROM codec_slice_cpu_running;
-- Generating codec framework cpu metric
DROP VIEW IF EXISTS codec_metrics_output;
CREATE PERFETTO VIEW codec_metrics_output AS
-SELECT AndroidCodecMetrics(
+SELECT AndroidCodecMetrics (
'cpu_usage', (
SELECT RepeatedField(
AndroidCodecMetrics_CpuUsage(
'process_name', process_name,
'thread_name', thread_name,
- 'thread_cpu_ns', CAST((cpu_time_ns) as INT64),
- 'num_threads', num_threads,
- 'core_data', core_type_proto_per_thread_name.proto
+ 'thread_cpu_ns', CAST((runtime) AS INT64),
+ 'core_data', proto
)
- ) FROM codec_per_process_thread_cpu_use
- JOIN core_type_proto_per_thread_name using(thread_name)
+ ) FROM cpu_cycles_runtime
),
'codec_function', (
SELECT RepeatedField (
@@ -192,5 +183,20 @@
'detail', metrics_per_slice_type.proto
)
) FROM metrics_per_slice_type
+ ),
+ 'energy', (
+ AndroidCodecMetrics_Energy(
+ 'total_energy', (SELECT SUM(tot_used_power) FROM avg_used_powers),
+ 'duration', (SELECT MAX(powrail_end_ts) - MIN(powrail_start_ts) FROM avg_used_powers),
+ 'power_mw', (SELECT SUM(tot_used_power) / (MAX(powrail_end_ts) - MIN(powrail_start_ts)) FROM avg_used_powers),
+ 'rail', (
+ SELECT RepeatedField (
+ AndroidCodecMetrics_Rail (
+ 'name', name,
+ 'info', codec_power_mw.proto
+ )
+ ) FROM codec_power_mw
+ )
+ )
)
);
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/internal/derived_events.sql b/src/trace_processor/metrics/sql/android/jank/internal/derived_events.sql
deleted file mode 100644
index 2211539..0000000
--- a/src/trace_processor/metrics/sql/android/jank/internal/derived_events.sql
+++ /dev/null
@@ -1,83 +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.
-
-
-DROP VIEW IF EXISTS android_jank_cuj_event;
-CREATE PERFETTO VIEW android_jank_cuj_event AS
--- Computed CUJ boundaries.
-SELECT
- 'slice' AS track_type,
- cuj.cuj_name AS track_name,
- boundary.ts,
- boundary.dur,
- cuj.cuj_name || ' (adjusted, id=' || cuj_id || ') ' AS slice_name,
- 'CUJ Boundaries' AS group_name
-FROM android_jank_cuj cuj
-JOIN android_jank_cuj_boundary boundary USING (cuj_id)
-UNION ALL
--- Computed frame boundaries on the Main Thread.
-SELECT
- 'slice' AS track_type,
- cuj.cuj_name || ' MT ' || vsync AS track_name,
- boundary.ts,
- boundary.dur,
- vsync || '' AS slice_name,
- cuj.cuj_name || ' - MT frame boundaries' AS group_name
-FROM android_jank_cuj cuj
-JOIN android_jank_cuj_main_thread_frame_boundary boundary USING (cuj_id)
-UNION ALL
--- Computed frame boundaries on the Render Thread.
-SELECT
- 'slice' AS track_type,
- cuj.cuj_name || ' RT ' || vsync AS track_name,
- boundary.ts,
- boundary.dur,
- vsync || '' AS slice_name,
- cuj.cuj_name || ' - RT frame boundaries' AS group_name
-FROM android_jank_cuj cuj
-JOIN android_jank_cuj_render_thread_frame_boundary boundary USING (cuj_id)
-UNION ALL
--- Computed overall frame boundaries not specific to any thread.
-SELECT
- 'slice' AS track_type,
- cuj.cuj_name || ' ' || vsync AS track_name,
- f.ts,
- f.dur,
- vsync || ' [app_missed=' || f.app_missed || ']' AS slice_name,
- cuj.cuj_name || ' - frames' AS group_name
-FROM android_jank_cuj cuj
-JOIN android_jank_cuj_frame f USING (cuj_id)
-UNION ALL
--- Computed frame boundaries on the SF Main Thread
-SELECT
- 'slice' AS track_type,
- cuj.cuj_name || ' SF MT ' || vsync AS track_name,
- boundary.ts,
- boundary.dur,
- vsync || '' AS slice_name,
- cuj.cuj_name || ' - SF MT frame boundaries' AS group_name
-FROM android_jank_cuj cuj
-JOIN android_jank_cuj_sf_main_thread_frame_boundary boundary USING (cuj_id)
-UNION ALL
--- Computed frame boundaries on the SF RenderEngine Thread.
-SELECT
- 'slice' AS track_type,
- cuj.cuj_name || ' SF RE ' || vsync AS track_name,
- boundary.ts,
- boundary.dur,
- vsync || '' AS slice_name,
- cuj.cuj_name || ' - SF RE frame boundaries' AS group_name
-FROM android_jank_cuj cuj
-JOIN android_jank_cuj_sf_render_engine_frame_boundary boundary USING (cuj_id);
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 5743dfd..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,72 +46,97 @@
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_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 ts, dur, utid, thread_name
- FROM launch_threads_by_thread_state l
+ SELECT p.pid, ts, dur, thread.tid, thread_name
+ FROM launch_threads_by_thread_state l, android_startup_processes p
JOIN thread USING (utid)
WHERE l.startup_id = $startup_id AND (state GLOB "R" OR state GLOB "R+") AND l.is_main_thread
+ AND p.startup_id = $startup_id
ORDER BY dur DESC
LIMIT $num_threads);
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_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 ts, dur, utid, thread_name
- FROM launch_threads_by_thread_state l
+ SELECT p.pid, ts, dur, thread.tid, thread_name
+ FROM launch_threads_by_thread_state l, android_startup_processes p
JOIN thread USING (utid)
WHERE l.startup_id = $startup_id AND state GLOB $state AND l.is_main_thread
+ AND p.startup_id = $startup_id
ORDER BY dur DESC
LIMIT $num_threads);
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_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 ts, dur, utid, thread_name
- FROM launch_threads_by_thread_state l
+ SELECT p.pid, ts, dur, thread.tid, thread_name
+ FROM launch_threads_by_thread_state l, android_startup_processes p
JOIN thread USING (utid)
WHERE l.startup_id = $startup_id AND state GLOB $state
AND l.is_main_thread AND l.io_wait = $io_wait
+ AND p.startup_id = $startup_id
ORDER BY dur DESC
LIMIT $num_threads);
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_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 ts, dur, utid, thread_name
- FROM launch_threads_by_thread_state l
+ SELECT p.pid, ts, dur, thread.tid, thread_name
+ FROM launch_threads_by_thread_state l, android_startup_processes p
JOIN thread USING (utid)
WHERE l.startup_id = $startup_id AND state GLOB $state AND thread_name = $thread_name
+ AND p.startup_id = $startup_id
ORDER BY dur DESC
LIMIT $num_threads);
CREATE OR REPLACE PERFETTO FUNCTION get_missing_baseline_profile_for_launch(
startup_id LONG, pkg_name STRING)
RETURNS PROTO AS
- SELECT RepeatedField(AndroidStartupMetric_TraceSliceSection(
- '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 slice_ts, slice_dur, slice_id, slice_name
+ SELECT p.pid, tid, slice_ts, slice_dur, slice_id, slice_name
FROM ANDROID_SLICES_FOR_STARTUP_AND_SLICE_NAME($startup_id,
- "location=* status=* filter=* reason=*")
+ "location=* status=* filter=* reason=*"), android_startup_processes p
WHERE
-- when location is the package odex file and the reason is "install" or "install-dm",
-- if the compilation filter is not "speed-profile", baseline/cloud profile is missing.
@@ -119,36 +144,49 @@
GLOB ("*" || $pkg_name || "*odex")
AND (STR_SPLIT(slice_name, " reason=", 1) = "install"
OR STR_SPLIT(slice_name, " reason=", 1) = "install-dm")
+ AND p.startup_id = $startup_id
ORDER BY slice_dur DESC
LIMIT 1);
CREATE OR REPLACE PERFETTO FUNCTION get_run_from_apk(startup_id LONG)
RETURNS PROTO AS
- SELECT RepeatedField(AndroidStartupMetric_TraceSliceSection(
- '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 slice_ts, slice_dur, slice_id, slice_name
- FROM android_thread_slices_for_all_startups
+ SELECT p.pid, tid, slice_ts, slice_dur, slice_id, slice_name
+ FROM android_thread_slices_for_all_startups l, android_startup_processes p
WHERE
- startup_id = $startup_id AND is_main_thread AND
+ l.startup_id = $startup_id AND is_main_thread AND
slice_name GLOB "location=* status=* filter=* reason=*" AND
STR_SPLIT(STR_SPLIT(slice_name, " filter=", 1), " reason=", 0)
GLOB ("*" || "run-from-apk" || "*")
+ AND p.startup_id = $startup_id
ORDER BY slice_dur DESC
LIMIT 1);
-CREATE OR REPLACE PERFETTO FUNCTION get_unlock_running_during_launch_slice(startup_id LONG)
+CREATE OR REPLACE PERFETTO FUNCTION get_unlock_running_during_launch_slice(startup_id LONG,
+ pid INT)
RETURNS PROTO AS
- SELECT RepeatedField(AndroidStartupMetric_TraceSliceSection(
- '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 slice.ts as slice_ts, slice.dur as slice_dur,
+ SELECT tid, slice.ts as slice_ts, slice.dur as slice_dur,
slice.id as slice_id, slice.name as slice_name
FROM slice, android_startups launches
JOIN thread_track ON slice.track_id = thread_track.id
@@ -163,15 +201,21 @@
CREATE OR REPLACE PERFETTO FUNCTION get_gc_activity(startup_id LONG, num_slices INT)
RETURNS PROTO AS
- SELECT RepeatedField(AndroidStartupMetric_TraceSliceSection(
- '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 slice_ts, slice_dur, slice_id, slice_name
- FROM android_thread_slices_for_all_startups slice
+ SELECT p.pid, tid, slice_ts, slice_dur, slice_id, slice_name
+ FROM android_thread_slices_for_all_startups slice, android_startup_processes p
WHERE
+ p.startup_id = $startup_id AND
slice.startup_id = $startup_id AND
(
slice_name GLOB "*semispace GC" OR
@@ -184,15 +228,21 @@
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(
- '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 slice_ts, slice_dur, slice_id, slice_name
- FROM android_thread_slices_for_all_startups l
- WHERE startup_id = $startup_id
+ SELECT p.pid, tid, slice_ts, slice_dur, slice_id, slice_name
+ FROM android_thread_slices_for_all_startups l,
+ android_startup_processes p
+ WHERE l.startup_id = $startup_id AND p.startup_id == $startup_id
AND slice_name GLOB $slice_name
ORDER BY slice_dur DESC
LIMIT $num_slices);
@@ -200,22 +250,28 @@
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(
- '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 request.slice_ts as slice_ts, request.slice_dur as slice_dur,
+ 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
FROM (
- SELECT slice_id as id, slice_dur, thread_name, process.name as process,
+ SELECT p.pid, tid, slice_id as id, slice_dur, thread_name, process.name as process,
s.arg_set_id, is_main_thread,
slice_ts, s.utid, slice_name
- FROM android_thread_slices_for_all_startups s
+ FROM android_thread_slices_for_all_startups s,
+ android_startup_processes p
JOIN process ON (
EXTRACT_ARG(s.arg_set_id, "destination process") = process.pid
)
- WHERE startup_id = $startup_id AND slice_name GLOB "binder transaction"
- AND slice_dur > $threshold
+ WHERE s.startup_id = $startup_id AND slice_name GLOB "binder transaction"
+ AND slice_dur > $threshold AND p.startup_id = $startup_id
) request
JOIN following_flow(request.id) arrow
JOIN slice reply ON reply.id = arrow.slice_in
@@ -225,13 +281,20 @@
LIMIT $num_slices);
CREATE OR REPLACE PERFETTO FUNCTION get_slices_concurrent_to_launch(
- startup_id INT, slice_glob STRING, num_slices INT)
+ startup_id INT, slice_glob STRING, num_slices INT, pid INT)
RETURNS PROTO AS
- SELECT RepeatedField(AndroidStartupMetric_TraceSliceSection(
- '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 s.ts as ts, dur, id, name FROM slice s
+ 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
+ JOIN thread USING(utid)
JOIN (
SELECT ts, ts_end
FROM android_startups
@@ -243,13 +306,18 @@
ORDER BY dur DESC LIMIT $num_slices);
CREATE OR REPLACE PERFETTO FUNCTION get_slices_for_startup_and_slice_name(
- startup_id INT, slice_name STRING, num_slices INT)
+ startup_id INT, slice_name STRING, num_slices INT, pid int)
RETURNS PROTO AS
- SELECT RepeatedField(AndroidStartupMetric_TraceSliceSection(
- '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 slice_ts, slice_dur, slice_id, slice_name
+ SELECT tid, slice_ts, slice_dur, slice_id, slice_name
FROM android_thread_slices_for_all_startups
WHERE startup_id = $startup_id AND slice_name GLOB $slice_name
ORDER BY slice_dur DESC
@@ -313,7 +381,8 @@
'unit', 'TRUE_OR_FALSE') as expected_val,
AndroidStartupMetric_ActualValue(
'value', TRUE) as actual_val,
- get_run_from_apk(launch.startup_id) as trace_slices,
+ get_run_from_apk(launch.startup_id)
+ as trace_slices,
NULL as trace_threads,
NULL as extra
FROM android_startups launch
@@ -330,7 +399,9 @@
'unit', 'TRUE_OR_FALSE') as expected_val,
AndroidStartupMetric_ActualValue(
'value', TRUE) as actual_val,
- get_unlock_running_during_launch_slice(launch.startup_id) as trace_slices,
+ get_unlock_running_during_launch_slice(launch.startup_id,
+ (SELECT pid FROM android_startup_processes WHERE launch.startup_id = startup_id))
+ as trace_slices,
NULL as trace_threads,
NULL as extra
FROM android_startups launch
@@ -364,7 +435,8 @@
'unit', 'TRUE_OR_FALSE') as expected_val,
AndroidStartupMetric_ActualValue(
'value', TRUE) as actual_val,
- get_gc_activity(launch.startup_id, 1) as trace_slices,
+ get_gc_activity(launch.startup_id, 1)
+ as trace_slices,
NULL as trace_threads,
NULL as extra
FROM android_startups launch
@@ -421,7 +493,8 @@
main_thread_time_for_launch_in_runnable_state(launch.startup_id) * 100 / launch.dur,
'dur', main_thread_time_for_launch_in_runnable_state(launch.startup_id)) as actual_val,
NULL as trace_slices,
- get_main_thread_time_for_launch_in_runnable_state(launch.startup_id, 3) as trace_threads,
+ get_main_thread_time_for_launch_in_runnable_state(launch.startup_id, 3)
+ as trace_threads,
NULL as extra
FROM android_startups launch
WHERE launch.startup_id = $startup_id
@@ -440,7 +513,8 @@
AndroidStartupMetric_ActualValue(
'value', main_thread_time_for_launch_and_state(launch.startup_id, 'S')) as actual_val,
NULL as trace_slices,
- get_main_thread_time_for_launch_and_state(launch.startup_id, 'S', 3) as trace_threads,
+ get_main_thread_time_for_launch_and_state(launch.startup_id, 'S', 3)
+ as trace_threads,
NULL as extra
FROM android_startups launch
WHERE launch.startup_id = $startup_id
@@ -461,7 +535,8 @@
launch.startup_id, 'D*', TRUE)) as actual_val,
NULL as trace_slices,
get_main_thread_time_for_launch_state_and_io_wait(
- launch.startup_id, 'D*', TRUE, 3) as trace_threads,
+ launch.startup_id, 'D*', TRUE, 3)
+ as trace_threads,
NULL as extra
FROM android_startups launch
WHERE launch.startup_id = $startup_id
@@ -547,7 +622,8 @@
'value', android_sum_dur_for_startup_and_slice(
launch.startup_id, 'ResourcesManager#getResources')) as actual_val,
get_dur_on_main_thread_for_startup_and_slice(
- launch.startup_id, 'ResourcesManager#getResources', 3) as trace_slices,
+ launch.startup_id, 'ResourcesManager#getResources', 3)
+ as trace_slices,
NULL as trace_threads,
NULL as extra
FROM android_startups launch
@@ -592,7 +668,8 @@
'value',
main_thread_time_for_launch_in_runnable_state(launch.startup_id)) as actual_val,
NULL as trace_slices,
- get_main_thread_time_for_launch_in_runnable_state(launch.startup_id, 3) as trace_threads,
+ get_main_thread_time_for_launch_in_runnable_state(launch.startup_id, 3)
+ as trace_threads,
NULL as extra
FROM android_startups launch
WHERE launch.startup_id = $startup_id AND
@@ -614,7 +691,8 @@
launch.startup_id, 'Running', 'Jit thread pool')) as actual_val,
NULL as trace_slices,
get_thread_time_for_launch_state_and_thread(
- launch.startup_id, 'Running', 'Jit thread pool', 3) as trace_threads,
+ launch.startup_id, 'Running', 'Jit thread pool', 3)
+ as trace_threads,
NULL as extra
FROM android_startups launch
WHERE launch.startup_id = $startup_id
@@ -638,7 +716,7 @@
launch.startup_id, 'Lock contention on*') * 100 / launch.dur,
'dur', android_sum_dur_on_main_thread_for_startup_and_slice(
launch.startup_id, 'Lock contention on*')) as actual_val,
- get_dur_on_main_thread_for_startup_and_slice(launch.startup_id, 'lock contention on*', 3)
+ get_dur_on_main_thread_for_startup_and_slice(launch.startup_id, 'Lock contention on*', 3)
as trace_slices,
NULL as trace_threads,
NULL as extra
@@ -664,7 +742,8 @@
'dur', android_sum_dur_on_main_thread_for_startup_and_slice(
launch.startup_id, 'Lock contention on a monitor*')) as actual_val,
get_dur_on_main_thread_for_startup_and_slice(
- launch.startup_id, 'lock contention on a monitor*', 3) as trace_slices,
+ launch.startup_id, 'Lock contention on a monitor*', 3)
+ as trace_slices,
NULL as trace_threads,
NULL as extra
FROM android_startups launch
@@ -687,7 +766,8 @@
'value', (SELECT COUNT(1)
FROM ANDROID_SLICES_FOR_STARTUP_AND_SLICE_NAME(launch.startup_id, 'JIT compiling*')
WHERE thread_name = 'Jit thread pool')) as actual_val,
- get_slices_for_startup_and_slice_name(launch.startup_id, 'JIT compiling*', 3)
+ get_slices_for_startup_and_slice_name(launch.startup_id, 'JIT compiling*', 3,
+ (SELECT pid FROM android_startup_processes WHERE launch.startup_id = startup_id))
as trace_slices,
NULL as traced_threads,
NULL as extra
@@ -710,7 +790,8 @@
AndroidStartupMetric_ActualValue(
'value', count_slices_concurrent_to_launch(launch.startup_id,
'Broadcast dispatched*')) as actual_val,
- get_slices_concurrent_to_launch(launch.startup_id, 'Broadcast dispatched*', 3)
+ get_slices_concurrent_to_launch(launch.startup_id, 'Broadcast dispatched*', 3,
+ (SELECT pid FROM android_startup_processes WHERE launch.startup_id = startup_id))
as trace_slices,
NULL as trace_threads,
NULL as extra
@@ -732,7 +813,8 @@
AndroidStartupMetric_ActualValue(
'value', count_slices_concurrent_to_launch(launch.startup_id,
'broadcastReceiveReg*')) as actual_val,
- get_slices_concurrent_to_launch(launch.startup_id, 'broadcastReceiveReg*', 3)
+ get_slices_concurrent_to_launch(launch.startup_id, 'broadcastReceiveReg*', 3,
+ (SELECT pid FROM android_startup_processes WHERE launch.startup_id = startup_id))
as trace_slices,
NULL as trace_threads,
NULL as extra
@@ -778,7 +860,8 @@
'unit', 'TRUE_OR_FALSE') as expected_val,
AndroidStartupMetric_ActualValue(
'value', TRUE) as actual_val,
- get_main_thread_binder_transactions_blocked(launch.startup_id, 2e7, 3) as trace_slices,
+ get_main_thread_binder_transactions_blocked(launch.startup_id, 2e7, 3)
+ as trace_slices,
NULL as trace_threads,
NULL as extra
FROM android_startups launch
diff --git a/src/trace_processor/metrics/sql/android/wattson_app_startup_rails.sql b/src/trace_processor/metrics/sql/android/wattson_app_startup_rails.sql
index 1ed0a3d..4490d5f 100644
--- a/src/trace_processor/metrics/sql/android/wattson_app_startup_rails.sql
+++ b/src/trace_processor/metrics/sql/android/wattson_app_startup_rails.sql
@@ -31,7 +31,8 @@
DROP VIEW IF EXISTS wattson_app_startup_rails_output;
CREATE PERFETTO VIEW wattson_app_startup_rails_output AS
SELECT AndroidWattsonTimePeriodMetric(
- 'metric_version', 3,
+ 'metric_version', 4,
+ 'power_model_version', 1,
'period_info', (
SELECT RepeatedField(
AndroidWattsonEstimateInfo(
diff --git a/src/trace_processor/metrics/sql/android/wattson_markers_rails.sql b/src/trace_processor/metrics/sql/android/wattson_markers_rails.sql
index 73e8e0d..f18a6fa 100644
--- a/src/trace_processor/metrics/sql/android/wattson_markers_rails.sql
+++ b/src/trace_processor/metrics/sql/android/wattson_markers_rails.sql
@@ -13,7 +13,7 @@
-- See the License for the specific language governing permissions and
-- limitations under the License.
-INCLUDE PERFETTO MODULE wattson.curves.ungrouped;
+INCLUDE PERFETTO MODULE wattson.curves.estimates;
DROP VIEW IF EXISTS _wattson_period_windows;
CREATE PERFETTO VIEW _wattson_period_windows AS
@@ -33,7 +33,8 @@
DROP VIEW IF EXISTS wattson_markers_rails_output;
CREATE PERFETTO VIEW wattson_markers_rails_output AS
SELECT AndroidWattsonTimePeriodMetric(
- 'metric_version', 3,
+ 'metric_version', 4,
+ 'power_model_version', 1,
'period_info', (
SELECT RepeatedField(
AndroidWattsonEstimateInfo(
diff --git a/src/trace_processor/metrics/sql/android/wattson_markers_threads.sql b/src/trace_processor/metrics/sql/android/wattson_markers_threads.sql
index ce8666f..4074a20 100644
--- a/src/trace_processor/metrics/sql/android/wattson_markers_threads.sql
+++ b/src/trace_processor/metrics/sql/android/wattson_markers_threads.sql
@@ -13,11 +13,11 @@
-- See the License for the specific language governing permissions and
-- limitations under the License.
-INCLUDE PERFETTO MODULE wattson.curves.grouped;
+INCLUDE PERFETTO MODULE wattson.curves.estimates;
INCLUDE PERFETTO MODULE viz.summary.threads_w_processes;
-DROP VIEW IF EXISTS _wattson_period_windows;
-CREATE PERFETTO VIEW _wattson_period_windows AS
+DROP VIEW IF EXISTS _wattson_period_window;
+CREATE PERFETTO VIEW _wattson_period_window AS
SELECT
-- Requirement is there is exactly one pair of start/stop
(SELECT ts FROM slice WHERE name == 'wattson_start') as ts,
@@ -28,42 +28,21 @@
SELECT RUN_METRIC(
'android/wattson_tasks_attribution.sql',
'window_table',
- '_wattson_period_windows'
+ '_wattson_period_window'
);
--- Group by unique thread ID and disregard CPUs, summing of power over all CPUs
--- and all instances of the thread
-DROP VIEW IF EXISTS _wattson_thread_attribution;
-CREATE PERFETTO VIEW _wattson_thread_attribution AS
-SELECT
- -- active time of thread divided by total time where Wattson is defined
- SUM(estimated_mw * dur) / 1000000000 as estimated_mws,
- (
- SUM(estimated_mw * dur) / (SELECT SUM(dur) from _windowed_wattson)
- ) as estimated_mw,
- thread_name,
- process_name,
- tid,
- pid
-FROM _windowed_threads_system_state
-GROUP BY utid
-ORDER BY estimated_mw DESC;
-
DROP VIEW IF EXISTS wattson_markers_threads_output;
CREATE PERFETTO VIEW wattson_markers_threads_output AS
SELECT AndroidWattsonTasksAttributionMetric(
- 'metric_version', 2,
- 'task_info', (
+ 'metric_version', 4,
+ 'power_model_version', 1,
+ 'period_info', (
SELECT RepeatedField(
- AndroidWattsonTaskInfo(
- 'estimated_mws', ROUND(estimated_mws, 6),
- 'estimated_mw', ROUND(estimated_mw, 6),
- 'thread_name', thread_name,
- 'process_name', process_name,
- 'thread_id', tid,
- 'process_id', pid
+ AndroidWattsonTaskPeriodInfo(
+ 'period_id', period_id,
+ 'task_info', proto
)
)
- FROM _wattson_thread_attribution
+ FROM _wattson_per_task
)
);
diff --git a/src/trace_processor/metrics/sql/android/wattson_rail_relations.sql b/src/trace_processor/metrics/sql/android/wattson_rail_relations.sql
index 4a2c1b0..4c80ebb 100644
--- a/src/trace_processor/metrics/sql/android/wattson_rail_relations.sql
+++ b/src/trace_processor/metrics/sql/android/wattson_rail_relations.sql
@@ -16,13 +16,7 @@
-- This file established the tables that define the relationships between rails
-- and subrails as well as the hierarchical power estimates of each rail
-INCLUDE PERFETTO MODULE wattson.curves.ungrouped;
-
--- Take only the Wattson estimations that are in the window of interest
-DROP TABLE IF EXISTS _windowed_wattson;
-CREATE VIRTUAL TABLE _windowed_wattson
-USING
- SPAN_JOIN({{window_table}}, _system_state_mw);
+INCLUDE PERFETTO MODULE wattson.curves.estimates;
-- The most basic rail components that form the "building blocks" from which all
-- other rails and components are derived. Average power over the entire trace
@@ -39,19 +33,27 @@
(SELECT m.policy FROM _dev_cpu_policy_map AS m WHERE m.cpu = 6) as cpu6_poli,
(SELECT m.policy FROM _dev_cpu_policy_map AS m WHERE m.cpu = 7) as cpu7_poli,
-- Converts all mW of all slices into average mW of total trace
- SUM(dur * cpu0_mw) / SUM(dur) as cpu0_mw,
- SUM(dur * cpu1_mw) / SUM(dur) as cpu1_mw,
- SUM(dur * cpu2_mw) / SUM(dur) as cpu2_mw,
- SUM(dur * cpu3_mw) / SUM(dur) as cpu3_mw,
- SUM(dur * cpu4_mw) / SUM(dur) as cpu4_mw,
- SUM(dur * cpu5_mw) / SUM(dur) as cpu5_mw,
- SUM(dur * cpu6_mw) / SUM(dur) as cpu6_mw,
- SUM(dur * cpu7_mw) / SUM(dur) as cpu7_mw,
- SUM(dur * dsu_scu_mw) / SUM(dur) as dsu_scu_mw,
- SUM(dur) as period_dur,
- period_id
-FROM _windowed_wattson
-GROUP BY period_id;
+ SUM(ii.dur * ss.cpu0_mw) / SUM(ii.dur) as cpu0_mw,
+ SUM(ii.dur * ss.cpu1_mw) / SUM(ii.dur) as cpu1_mw,
+ SUM(ii.dur * ss.cpu2_mw) / SUM(ii.dur) as cpu2_mw,
+ SUM(ii.dur * ss.cpu3_mw) / SUM(ii.dur) as cpu3_mw,
+ SUM(ii.dur * ss.cpu4_mw) / SUM(ii.dur) as cpu4_mw,
+ SUM(ii.dur * ss.cpu5_mw) / SUM(ii.dur) as cpu5_mw,
+ SUM(ii.dur * ss.cpu6_mw) / SUM(ii.dur) as cpu6_mw,
+ SUM(ii.dur * ss.cpu7_mw) / SUM(ii.dur) as cpu7_mw,
+ SUM(ii.dur * ss.dsu_scu_mw) / SUM(ii.dur) as dsu_scu_mw,
+ SUM(ii.dur) as period_dur,
+ w.period_id
+FROM _interval_intersect!(
+ (
+ (SELECT period_id AS id, * FROM {{window_table}}),
+ _ii_subquery!(_system_state_mw)
+ ),
+ ()
+) ii
+JOIN {{window_table}} AS w ON w.period_id = id_0
+JOIN _system_state_mw AS ss ON ss._auto_id = id_1
+GROUP BY w.period_id;
-- Macro that filters out CPUs that are unrelated to the policy of the table
-- passed in, and does some bookkeeping to put data in expected format
@@ -77,46 +79,76 @@
period_id,
period_dur,
cast_double!(IIF(is_defined, sum_mw, NULL)) as estimated_mw,
+ cast_double!(
+ IIF(is_defined, sum_mw * period_dur / 1e9, NULL)
+ ) as estimated_mws,
AndroidWattsonPolicyEstimate(
'estimated_mw', cast_double!(IIF(is_defined, sum_mw, NULL)),
+ 'estimated_mws', cast_double!(
+ IIF(is_defined, sum_mw * period_dur / 1e9, NULL)
+ ),
'cpu0', IIF(
cpu0_mw,
- AndroidWattsonCpuEstimate('estimated_mw', cpu0_mw),
+ AndroidWattsonCpuEstimate(
+ 'estimated_mw', cpu0_mw,
+ 'estimated_mws', cpu0_mw * period_dur / 1e9
+ ),
NULL
),
'cpu1', IIF(
cpu1_mw,
- AndroidWattsonCpuEstimate('estimated_mw', cpu1_mw),
+ AndroidWattsonCpuEstimate(
+ 'estimated_mw', cpu1_mw,
+ 'estimated_mws', cpu1_mw * period_dur / 1e9
+ ),
NULL
),
'cpu2', IIF(
cpu2_mw,
- AndroidWattsonCpuEstimate('estimated_mw', cpu2_mw),
+ AndroidWattsonCpuEstimate(
+ 'estimated_mw', cpu2_mw,
+ 'estimated_mws', cpu2_mw * period_dur / 1e9
+ ),
NULL
),
'cpu3', IIF(
cpu3_mw,
- AndroidWattsonCpuEstimate('estimated_mw', cpu3_mw),
+ AndroidWattsonCpuEstimate(
+ 'estimated_mw', cpu3_mw,
+ 'estimated_mws', cpu3_mw * period_dur / 1e9
+ ),
NULL
),
'cpu4', IIF(
cpu4_mw,
- AndroidWattsonCpuEstimate('estimated_mw', cpu4_mw),
+ AndroidWattsonCpuEstimate(
+ 'estimated_mw', cpu4_mw,
+ 'estimated_mws', cpu4_mw * period_dur / 1e9
+ ),
NULL
),
'cpu5', IIF(
cpu5_mw,
- AndroidWattsonCpuEstimate('estimated_mw', cpu5_mw),
+ AndroidWattsonCpuEstimate(
+ 'estimated_mw', cpu5_mw,
+ 'estimated_mws', cpu5_mw * period_dur / 1e9
+ ),
NULL
),
'cpu6', IIF(
cpu6_mw,
- AndroidWattsonCpuEstimate('estimated_mw', cpu6_mw),
+ AndroidWattsonCpuEstimate(
+ 'estimated_mw', cpu6_mw,
+ 'estimated_mws', cpu6_mw * period_dur / 1e9
+ ),
NULL
),
'cpu7', IIF(
cpu7_mw,
- AndroidWattsonCpuEstimate('estimated_mw', cpu7_mw),
+ AndroidWattsonCpuEstimate(
+ 'estimated_mw', cpu7_mw,
+ 'estimated_mws', cpu7_mw * period_dur / 1e9
+ ),
NULL
)
) AS proto
@@ -345,6 +377,7 @@
period_dur,
AndroidWattsonCpuSubsystemEstimate(
'estimated_mw', sum_mw,
+ 'estimated_mws', sum_mw * period_dur / 1e9,
'policy0', p0_proto,
'policy1', p1_proto,
'policy2', p2_proto,
@@ -353,7 +386,10 @@
'policy5', p5_proto,
'policy6', p6_proto,
'policy7', p7_proto,
- 'dsu_scu', AndroidWattsonDsuScuEstimate('estimated_mw', dsu_scu_mw)
+ 'dsu_scu', AndroidWattsonDsuScuEstimate(
+ 'estimated_mw', dsu_scu_mw,
+ 'estimated_mws', dsu_scu_mw * period_dur / 1e9
+ )
) as proto
FROM components_w_sum;
diff --git a/src/trace_processor/metrics/sql/android/wattson_tasks_attribution.sql b/src/trace_processor/metrics/sql/android/wattson_tasks_attribution.sql
index 61353c0..d023354 100644
--- a/src/trace_processor/metrics/sql/android/wattson_tasks_attribution.sql
+++ b/src/trace_processor/metrics/sql/android/wattson_tasks_attribution.sql
@@ -13,58 +13,152 @@
-- See the License for the specific language governing permissions and
-- limitations under the License.
-INCLUDE PERFETTO MODULE wattson.curves.grouped;
+INCLUDE PERFETTO MODULE wattson.curves.estimates;
+INCLUDE PERFETTO MODULE wattson.curves.idle_attribution;
INCLUDE PERFETTO MODULE viz.summary.threads_w_processes;
-- Take only the Wattson estimations that are in the window of interest
-DROP TABLE IF EXISTS _windowed_wattson;
-CREATE VIRTUAL TABLE _windowed_wattson
-USING
- SPAN_JOIN({{window_table}}, _system_state_mw);
+DROP VIEW IF EXISTS _windowed_wattson;
+CREATE PERFETTO VIEW _windowed_wattson AS
+SELECT
+ ii.ts,
+ ii.dur,
+ ii.id_1 as period_id,
+ ss.cpu0_mw,
+ ss.cpu1_mw,
+ ss.cpu2_mw,
+ ss.cpu3_mw,
+ ss.cpu4_mw,
+ ss.cpu5_mw,
+ ss.cpu6_mw,
+ ss.cpu7_mw,
+ ss.dsu_scu_mw
+FROM _interval_intersect!(
+ (
+ _ii_subquery!(_system_state_mw),
+ (SELECT ts, dur, period_id as id FROM {{window_table}})
+ ),
+ ()
+) ii
+JOIN _system_state_mw AS ss ON ss._auto_id = id_0;
-- "Unpivot" the table so that table can by PARTITIONED BY cpu
DROP TABLE IF EXISTS _unioned_windowed_wattson;
CREATE PERFETTO TABLE _unioned_windowed_wattson AS
- SELECT ts, dur, 0 as cpu, cpu0_mw as estimated_mw
+ SELECT ts, dur, 0 as cpu, cpu0_mw as estimated_mw, period_id
FROM _windowed_wattson
WHERE EXISTS (SELECT cpu FROM _dev_cpu_policy_map WHERE 0 = cpu)
UNION ALL
- SELECT ts, dur, 1 as cpu, cpu1_mw as estimated_mw
+ SELECT ts, dur, 1 as cpu, cpu1_mw as estimated_mw, period_id
FROM _windowed_wattson
WHERE EXISTS (SELECT cpu FROM _dev_cpu_policy_map WHERE 1 = cpu)
UNION ALL
- SELECT ts, dur, 2 as cpu, cpu2_mw as estimated_mw
+ SELECT ts, dur, 2 as cpu, cpu2_mw as estimated_mw, period_id
FROM _windowed_wattson
WHERE EXISTS (SELECT cpu FROM _dev_cpu_policy_map WHERE 2 = cpu)
UNION ALL
- SELECT ts, dur, 3 as cpu, cpu3_mw as estimated_mw
+ SELECT ts, dur, 3 as cpu, cpu3_mw as estimated_mw, period_id
FROM _windowed_wattson
WHERE EXISTS (SELECT cpu FROM _dev_cpu_policy_map WHERE 3 = cpu)
UNION ALL
- SELECT ts, dur, 4 as cpu, cpu4_mw as estimated_mw
+ SELECT ts, dur, 4 as cpu, cpu4_mw as estimated_mw, period_id
FROM _windowed_wattson
WHERE EXISTS (SELECT cpu FROM _dev_cpu_policy_map WHERE 4 = cpu)
UNION ALL
- SELECT ts, dur, 5 as cpu, cpu5_mw as estimated_mw
+ SELECT ts, dur, 5 as cpu, cpu5_mw as estimated_mw, period_id
FROM _windowed_wattson
WHERE EXISTS (SELECT cpu FROM _dev_cpu_policy_map WHERE 5 = cpu)
UNION ALL
- SELECT ts, dur, 6 as cpu, cpu6_mw as estimated_mw
+ SELECT ts, dur, 6 as cpu, cpu6_mw as estimated_mw, period_id
FROM _windowed_wattson
WHERE EXISTS (SELECT cpu FROM _dev_cpu_policy_map WHERE 6 = cpu)
UNION ALL
- SELECT ts, dur, 7 as cpu, cpu7_mw as estimated_mw
+ SELECT ts, dur, 7 as cpu, cpu7_mw as estimated_mw, period_id
FROM _windowed_wattson
WHERE EXISTS (SELECT cpu FROM _dev_cpu_policy_map WHERE 7 = cpu)
UNION ALL
- SELECT ts, dur, -1 as cpu, dsu_scu_mw as estimated_mw
+ SELECT ts, dur, -1 as cpu, dsu_scu_mw as estimated_mw, period_id
FROM _windowed_wattson;
DROP TABLE IF EXISTS _windowed_threads_system_state;
-CREATE VIRTUAL TABLE _windowed_threads_system_state
-USING
- SPAN_JOIN(
- _unioned_windowed_wattson partitioned cpu,
- _sched_w_thread_process_package_summary partitioned cpu
- );
+CREATE PERFETTO TABLE _windowed_threads_system_state AS
+SELECT
+ ii.ts,
+ ii.dur,
+ ii.cpu,
+ uw.estimated_mw,
+ s.thread_name,
+ s.process_name,
+ s.tid,
+ s.pid,
+ s.utid,
+ uw.period_id
+FROM _interval_intersect!(
+ (
+ _ii_subquery!(_unioned_windowed_wattson),
+ _ii_subquery!(_sched_w_thread_process_package_summary)
+ ),
+ (cpu)
+) ii
+JOIN _unioned_windowed_wattson AS uw ON uw._auto_id = id_0
+JOIN _sched_w_thread_process_package_summary AS s ON s._auto_id = id_1;
+
+-- Get idle overhead attribution per thread
+DROP VIEW IF EXISTS _per_thread_idle_attribution;
+CREATE PERFETTO VIEW _per_thread_idle_attribution AS
+SELECT
+ SUM(cost.estimated_mw * cost.dur) / 1e9 as idle_cost_mws,
+ cost.utid,
+ ii.id_1 as period_id
+FROM _interval_intersect!(
+ (
+ _ii_subquery!(_idle_transition_cost),
+ (SELECT ts, dur, period_id as id FROM {{window_table}})
+ ),
+ ()
+) ii
+JOIN _idle_transition_cost as cost ON cost._auto_id = id_0
+GROUP BY utid, period_id;
+
+-- Group by unique thread ID and disregard CPUs, summing of power over all CPUs
+-- and all instances of the thread
+DROP VIEW IF EXISTS _wattson_thread_attribution;
+CREATE PERFETTO VIEW _wattson_thread_attribution AS
+SELECT
+ -- active time of thread divided by total time where Wattson is defined
+ SUM(estimated_mw * dur) / 1000000000 as estimated_mws,
+ (
+ SUM(estimated_mw * dur) / (SELECT SUM(dur) from _windowed_wattson)
+ ) as estimated_mw,
+ idle_cost_mws,
+ thread_name,
+ process_name,
+ tid,
+ pid,
+ period_id
+FROM _windowed_threads_system_state
+LEFT JOIN _per_thread_idle_attribution USING (utid, period_id)
+GROUP BY utid, period_id
+ORDER BY estimated_mw DESC;
+
+-- Create proto format task attribution for each period
+DROP VIEW IF EXISTS _wattson_per_task;
+CREATE PERFETTO VIEW _wattson_per_task AS
+SELECT
+ period_id,
+ (
+ SELECT RepeatedField(
+ AndroidWattsonTaskInfo(
+ 'estimated_mws', ROUND(estimated_mws, 6),
+ 'estimated_mw', ROUND(estimated_mw, 6),
+ 'idle_transitions_mws', ROUND(idle_cost_mws, 6),
+ 'thread_name', thread_name,
+ 'process_name', process_name,
+ 'thread_id', tid,
+ 'process_id', pid
+ )
+ )
+ ) as proto
+FROM _wattson_thread_attribution
+GROUP BY period_id;
diff --git a/src/trace_processor/metrics/sql/android/wattson_trace_rails.sql b/src/trace_processor/metrics/sql/android/wattson_trace_rails.sql
index e5489c4..3c4af17 100644
--- a/src/trace_processor/metrics/sql/android/wattson_trace_rails.sql
+++ b/src/trace_processor/metrics/sql/android/wattson_trace_rails.sql
@@ -13,17 +13,15 @@
-- See the License for the specific language governing permissions and
-- limitations under the License.
-INCLUDE PERFETTO MODULE wattson.curves.ungrouped;
+INCLUDE PERFETTO MODULE wattson.curves.estimates;
--- The power calculations need to use the same time period in which energy
--- calculations were made for consistency
+-- This metric is defined to be for entire trace duration
DROP VIEW IF EXISTS _wattson_period_windows;
CREATE PERFETTO VIEW _wattson_period_windows AS
SELECT
- MIN(ts) as ts,
- MAX(ts) - MIN(ts) as dur,
- 1 as period_id
-FROM _system_state_mw;
+ trace_start() as ts,
+ trace_dur() as dur,
+ 1 as period_id;
SELECT RUN_METRIC(
'android/wattson_rail_relations.sql',
@@ -33,7 +31,8 @@
DROP VIEW IF EXISTS wattson_trace_rails_output;
CREATE PERFETTO VIEW wattson_trace_rails_output AS
SELECT AndroidWattsonTimePeriodMetric(
- 'metric_version', 3,
+ 'metric_version', 4,
+ 'power_model_version', 1,
'period_info', (
SELECT RepeatedField(
AndroidWattsonEstimateInfo(
diff --git a/src/trace_processor/metrics/sql/android/wattson_trace_threads.sql b/src/trace_processor/metrics/sql/android/wattson_trace_threads.sql
index bb83cab..fb8071f 100644
--- a/src/trace_processor/metrics/sql/android/wattson_trace_threads.sql
+++ b/src/trace_processor/metrics/sql/android/wattson_trace_threads.sql
@@ -13,56 +13,35 @@
-- See the License for the specific language governing permissions and
-- limitations under the License.
-INCLUDE PERFETTO MODULE wattson.curves.grouped;
+INCLUDE PERFETTO MODULE wattson.curves.estimates;
INCLUDE PERFETTO MODULE viz.summary.threads_w_processes;
-DROP VIEW IF EXISTS _wattson_period_windows;
-CREATE PERFETTO VIEW _wattson_period_windows AS
+-- This metric is defined to be for entire trace duration
+DROP VIEW IF EXISTS _wattson_period_window;
+CREATE PERFETTO VIEW _wattson_period_window AS
SELECT
- MIN(ts) as ts,
- MAX(ts) - MIN(ts) as dur,
- 1 as period_id
-FROM _system_state_mw;
+ trace_start() as ts,
+ trace_dur() as dur,
+ 1 as period_id;
SELECT RUN_METRIC(
'android/wattson_tasks_attribution.sql',
'window_table',
- '_wattson_period_windows'
+ '_wattson_period_window'
);
--- Group by unique thread ID and disregard CPUs, summing of power over all CPUs
--- and all instances of the thread
-DROP VIEW IF EXISTS _wattson_thread_attribution;
-CREATE PERFETTO VIEW _wattson_thread_attribution AS
-SELECT
- -- active time of thread divided by total time of trace
- SUM(estimated_mw * dur) / 1000000000 as estimated_mws,
- (
- SUM(estimated_mw * dur) / (SELECT SUM(dur) from _windowed_wattson)
- ) as estimated_mw,
- thread_name,
- process_name,
- tid,
- pid
-FROM _windowed_threads_system_state
-GROUP BY utid
-ORDER BY estimated_mw DESC;
-
DROP VIEW IF EXISTS wattson_trace_threads_output;
CREATE PERFETTO VIEW wattson_trace_threads_output AS
SELECT AndroidWattsonTasksAttributionMetric(
- 'metric_version', 2,
- 'task_info', (
+ 'metric_version', 4,
+ 'power_model_version', 1,
+ 'period_info', (
SELECT RepeatedField(
- AndroidWattsonTaskInfo(
- 'estimated_mws', ROUND(estimated_mws, 6),
- 'estimated_mw', ROUND(estimated_mw, 6),
- 'thread_name', thread_name,
- 'process_name', process_name,
- 'thread_id', tid,
- 'process_id', pid
+ AndroidWattsonTaskPeriodInfo(
+ 'period_id', period_id,
+ 'task_info', proto
)
)
- FROM _wattson_thread_attribution
+ FROM _wattson_per_task
)
);
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/experimental/chrome_dropped_frames.sql b/src/trace_processor/metrics/sql/experimental/chrome_dropped_frames.sql
index 2c33bc0..1fef9b1 100644
--- a/src/trace_processor/metrics/sql/experimental/chrome_dropped_frames.sql
+++ b/src/trace_processor/metrics/sql/experimental/chrome_dropped_frames.sql
@@ -53,36 +53,6 @@
JOIN process
ON dropped_frames_with_upid.upid = process.upid;
--- Create the derived event track for dropped frames.
--- All tracks generated from chrome_dropped_frames_event are
--- placed under a track group named 'Dropped Frames', whose summary
--- track is the first track ('All Processes') in chrome_dropped_frames_event.
--- Note that the 'All Processes' track is generated only when dropped frames
--- come from more than one origin process.
-DROP VIEW IF EXISTS chrome_dropped_frames_event;
-CREATE PERFETTO VIEW chrome_dropped_frames_event AS
-SELECT
- 'slice' AS track_type,
- 'All Processes' AS track_name,
- ts,
- 0 AS dur,
- 'Dropped Frame' AS slice_name,
- 'Dropped Frames' AS group_name
-FROM dropped_frames_with_process_info
-WHERE (SELECT COUNT(DISTINCT process_id)
- FROM dropped_frames_with_process_info) > 1
-GROUP BY ts
-UNION ALL
-SELECT
- 'slice' AS track_type,
- COALESCE(process_name, 'Process') || ' ' || process_id AS track_name,
- ts,
- 0 AS dur,
- 'Dropped Frame' AS slice_name,
- 'Dropped Frames' AS group_name
-FROM dropped_frames_with_process_info
-GROUP BY process_id, ts;
-
-- Create the dropped frames metric output.
DROP VIEW IF EXISTS chrome_dropped_frames_output;
CREATE PERFETTO VIEW chrome_dropped_frames_output AS
diff --git a/src/trace_processor/metrics/sql/experimental/chrome_long_latency.sql b/src/trace_processor/metrics/sql/experimental/chrome_long_latency.sql
index d8b49c6..d8e1eb2 100644
--- a/src/trace_processor/metrics/sql/experimental/chrome_long_latency.sql
+++ b/src/trace_processor/metrics/sql/experimental/chrome_long_latency.sql
@@ -63,36 +63,6 @@
ON long_latency_with_upid.upid = process.upid
GROUP BY ts, process.pid;
--- Create the derived event track for long latency.
--- All tracks generated from chrome_long_latency_event are
--- placed under a track group named 'Long Latency', whose summary
--- track is the first track ('All Processes') in chrome_long_latency_event.
--- Note that the 'All Processes' track is generated only when there are more
--- than one source of long latency events.
-DROP VIEW IF EXISTS chrome_long_latency_event;
-CREATE PERFETTO VIEW chrome_long_latency_event AS
-SELECT
- 'slice' AS track_type,
- 'All Processes' AS track_name,
- ts,
- 0 AS dur,
- event_type AS slice_name,
- 'Long Latency' AS group_name
-FROM long_latency_with_process_info
-WHERE (SELECT COUNT(DISTINCT process_id)
- FROM long_latency_with_process_info) > 1
-GROUP BY ts
-UNION ALL
-SELECT
- 'slice' AS track_type,
- process_name || ' ' || process_id AS track_name,
- ts,
- 0 AS dur,
- event_type AS slice_name,
- 'Long Latency' AS group_name
-FROM long_latency_with_process_info
-GROUP BY ts;
-
-- Create the long latency metric output.
DROP VIEW IF EXISTS chrome_long_latency_output;
CREATE PERFETTO VIEW chrome_long_latency_output AS
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 7442b14..36bd391 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc
@@ -682,7 +682,8 @@
sqlite_engine()->GetFunctionContext(name, kSupportedArgCount));
if (!ctx) {
return base::ErrStatus(
- "EXPERIMENTAL_MEMOIZE: Function %s(INT) does not exist", name.c_str());
+ "EXPERIMENTAL_MEMOIZE: Function '%s'(INT) does not exist",
+ name.c_str());
}
return CreatedFunction::EnableMemoization(ctx);
}
@@ -696,19 +697,28 @@
std::string key = include.key;
if (key == "*") {
- for (auto moduleIt = modules_.GetIterator(); moduleIt; ++moduleIt) {
- RETURN_IF_ERROR(IncludeModuleImpl(moduleIt.value(), key, parser));
+ for (auto package = packages_.GetIterator(); package; ++package) {
+ RETURN_IF_ERROR(IncludePackageImpl(package.value(), key, parser));
}
return base::OkStatus();
}
- std::string module_name = sql_modules::GetModuleName(key);
- auto* module = FindModule(module_name);
- if (!module) {
- return base::ErrStatus("INCLUDE: Unknown module name provided - %s",
- key.c_str());
+ 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 IncludeModuleImpl(*module, key, parser);
+ return IncludePackageImpl(*package, key, parser);
}
base::Status PerfettoSqlEngine::ExecuteCreateIndex(
@@ -758,35 +768,34 @@
return base::OkStatus();
}
-base::Status PerfettoSqlEngine::IncludeModuleImpl(
- sql_modules::RegisteredModule& module,
- const std::string& key,
+base::Status PerfettoSqlEngine::IncludePackageImpl(
+ sql_modules::RegisteredPackage& package,
+ const std::string& include_key,
const PerfettoSqlParser& parser) {
- if (!key.empty() && key.back() == '*') {
+ if (!include_key.empty() && include_key.back() == '*') {
// If the key ends with a wildcard, iterate through all the keys in the
// module and include matching ones.
- std::string prefix = key.substr(0, key.size() - 1);
- for (auto fileIt = module.include_key_to_file.GetIterator(); fileIt;
- ++fileIt) {
- if (!base::StartsWith(fileIt.key(), prefix))
+ std::string prefix = include_key.substr(0, include_key.size() - 1);
+ for (auto module = package.modules.GetIterator(); module; ++module) {
+ if (!base::StartsWith(module.key(), prefix))
continue;
PERFETTO_TP_TRACE(
metatrace::Category::QUERY_TIMELINE,
"Include (expanded from wildcard)",
- [&](metatrace::Record* r) { r->AddArg("Module", fileIt.key()); });
- RETURN_IF_ERROR(IncludeFileImpl(fileIt.value(), fileIt.key(), parser));
+ [&](metatrace::Record* r) { r->AddArg("Module", module.key()); });
+ RETURN_IF_ERROR(IncludeModuleImpl(module.value(), module.key(), parser));
}
return base::OkStatus();
}
- auto* module_file = module.include_key_to_file.Find(key);
+ auto* module_file = package.modules.Find(include_key);
if (!module_file) {
- return base::ErrStatus("INCLUDE: unknown module '%s'", key.c_str());
+ return base::ErrStatus("INCLUDE: unknown module '%s'", include_key.c_str());
}
- return IncludeFileImpl(*module_file, key, parser);
+ return IncludeModuleImpl(*module_file, include_key, parser);
}
-base::Status PerfettoSqlEngine::IncludeFileImpl(
- sql_modules::RegisteredModule::ModuleFile& file,
+base::Status PerfettoSqlEngine::IncludeModuleImpl(
+ sql_modules::RegisteredPackage::ModuleFile& file,
const std::string& key,
const PerfettoSqlParser& parser) {
// INCLUDE is noop for already included files.
diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h
index 54fc18e..f09d8c8 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h
@@ -194,16 +194,16 @@
SqliteEngine* sqlite_engine() { return engine_.get(); }
- // Makes new SQL module available to import.
- void RegisterModule(const std::string& name,
- sql_modules::RegisteredModule module) {
- modules_.Erase(name);
- modules_.Insert(name, std::move(module));
+ // Makes new SQL package available to include.
+ void RegisterPackage(const std::string& name,
+ sql_modules::RegisteredPackage package) {
+ packages_.Erase(name);
+ packages_.Insert(name, std::move(package));
}
- // Fetches registered SQL module.
- sql_modules::RegisteredModule* FindModule(const std::string& name) {
- return modules_.Find(name);
+ // Fetches registered SQL package.
+ sql_modules::RegisteredPackage* FindPackage(const std::string& name) {
+ return packages_.Find(name);
}
// Returns the number of objects (tables, views, functions etc) registered
@@ -317,18 +317,17 @@
const std::vector<sql_argument::ArgumentDefinition>& schema,
const char* tag) const;
- // Given a module and a key, include the correct file(s) from the module.
+ // Given a package and a key, include the correct file(s) from the package.
// The key can contain a wildcard to include all files in the module with the
// matching prefix.
- base::Status IncludeModuleImpl(sql_modules::RegisteredModule& module,
- const std::string& key,
- const PerfettoSqlParser& parser);
+ base::Status IncludePackageImpl(sql_modules::RegisteredPackage&,
+ const std::string& key,
+ const PerfettoSqlParser&);
- // Import a given file.
- base::Status IncludeFileImpl(
- sql_modules::RegisteredModule::ModuleFile& module,
- const std::string& key,
- const PerfettoSqlParser& parser);
+ // Include a given module.
+ base::Status IncludeModuleImpl(sql_modules::RegisteredPackage::ModuleFile&,
+ const std::string& key,
+ const PerfettoSqlParser&);
StringPool* pool_ = nullptr;
// If true, engine will perform additional consistency checks when e.g.
@@ -344,7 +343,7 @@
DbSqliteModule::Context* runtime_table_context_ = nullptr;
DbSqliteModule::Context* static_table_context_ = nullptr;
DbSqliteModule::Context* static_table_fn_context_ = nullptr;
- base::FlatHashMap<std::string, sql_modules::RegisteredModule> modules_;
+ base::FlatHashMap<std::string, sql_modules::RegisteredPackage> packages_;
base::FlatHashMap<std::string, PerfettoSqlPreprocessor::Macro> macros_;
std::unique_ptr<SqliteEngine> engine_;
};
diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine_unittest.cc b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine_unittest.cc
index 90cd2df..a1e8f54 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine_unittest.cc
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine_unittest.cc
@@ -30,12 +30,12 @@
PerfettoSqlEngine engine_{&pool_, true};
};
-sql_modules::RegisteredModule CreateTestModule(
+sql_modules::RegisteredPackage CreateTestPackage(
std::vector<std::pair<std::string, std::string>> files) {
- sql_modules::RegisteredModule result;
+ sql_modules::RegisteredPackage result;
for (auto& file : files) {
- result.include_key_to_file[file.first] =
- sql_modules::RegisteredModule::ModuleFile{file.second, false};
+ result.modules[file.first] =
+ sql_modules::RegisteredPackage::ModuleFile{file.second, false};
}
return result;
}
@@ -283,43 +283,38 @@
}
TEST_F(PerfettoSqlEngineTest, Include_All) {
- engine_.RegisterModule(
- "foo", CreateTestModule(
+ engine_.RegisterPackage(
+ "foo", CreateTestPackage(
{{"foo.foo", "CREATE PERFETTO TABLE foo AS SELECT 42 AS x"}}));
- engine_.RegisterModule(
+ engine_.RegisterPackage(
"bar",
- CreateTestModule(
+ CreateTestPackage(
{{"bar.bar", "CREATE PERFETTO TABLE bar AS SELECT 42 AS x "}}));
auto res_create =
engine_.Execute(SqlSource::FromExecuteQuery("INCLUDE PERFETTO MODULE *"));
ASSERT_TRUE(res_create.ok()) << res_create.status().c_message();
- ASSERT_TRUE(
- engine_.FindModule("foo")->include_key_to_file["foo.foo"].included);
- ASSERT_TRUE(
- engine_.FindModule("bar")->include_key_to_file["bar.bar"].included);
+ ASSERT_TRUE(engine_.FindPackage("foo")->modules["foo.foo"].included);
+ ASSERT_TRUE(engine_.FindPackage("bar")->modules["bar.bar"].included);
}
TEST_F(PerfettoSqlEngineTest, Include_Module) {
- engine_.RegisterModule(
- "foo", CreateTestModule({
+ engine_.RegisterPackage(
+ "foo", CreateTestPackage({
{"foo.foo1", "CREATE PERFETTO TABLE foo1 AS SELECT 42 AS x"},
{"foo.foo2", "CREATE PERFETTO TABLE foo2 AS SELECT 42 AS x"},
}));
- engine_.RegisterModule(
+ engine_.RegisterPackage(
"bar",
- CreateTestModule(
+ CreateTestPackage(
{{"bar.bar", "CREATE PERFETTO TABLE bar AS SELECT 42 AS x "}}));
auto res_create = engine_.Execute(
SqlSource::FromExecuteQuery("INCLUDE PERFETTO MODULE foo.*"));
ASSERT_TRUE(res_create.ok()) << res_create.status().c_message();
- ASSERT_TRUE(
- engine_.FindModule("foo")->include_key_to_file["foo.foo1"].included);
- ASSERT_TRUE(
- engine_.FindModule("foo")->include_key_to_file["foo.foo2"].included);
- ASSERT_FALSE(
- engine_.FindModule("bar")->include_key_to_file["bar.bar"].included);
+ ASSERT_TRUE(engine_.FindPackage("foo")->modules["foo.foo1"].included);
+ ASSERT_TRUE(engine_.FindPackage("foo")->modules["foo.foo2"].included);
+ ASSERT_FALSE(engine_.FindPackage("bar")->modules["bar.bar"].included);
}
TEST_F(PerfettoSqlEngineTest, MismatchedRange) {
diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/layout_functions.cc b/src/trace_processor/perfetto_sql/intrinsics/functions/layout_functions.cc
index 4029384..45df5ca 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/functions/layout_functions.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/functions/layout_functions.cc
@@ -147,15 +147,19 @@
GetOrCreateAggregationContext(ctx);
RETURN_IF_ERROR(slice_packer.status());
- base::StatusOr<SqlValue> ts =
- sqlite::utils::ExtractArgument(argc, argv, "ts", 0, SqlValue::kLong);
- RETURN_IF_ERROR(ts.status());
+ ASSIGN_OR_RETURN(SqlValue ts, sqlite::utils::ExtractArgument(
+ argc, argv, "ts", 0, SqlValue::kLong));
+ if (ts.AsLong() < 0) {
+ return base::ErrStatus("ts cannot be negative.");
+ }
- base::StatusOr<SqlValue> dur =
- sqlite::utils::ExtractArgument(argc, argv, "dur", 1, SqlValue::kLong);
- RETURN_IF_ERROR(dur.status());
+ ASSIGN_OR_RETURN(SqlValue dur, sqlite::utils::ExtractArgument(
+ argc, argv, "dur", 1, SqlValue::kLong));
+ if (dur.AsLong() < -1) {
+ return base::ErrStatus("dur cannot be < -1.");
+ }
- return slice_packer.value()->AddSlice(ts->AsLong(), dur.value().AsLong());
+ return slice_packer.value()->AddSlice(ts.AsLong(), dur.AsLong());
}
struct InternalLayout : public SqliteWindowFunction {
diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/type_builders.cc b/src/trace_processor/perfetto_sql/intrinsics/functions/type_builders.cc
index a9d9287..93ce52d 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/functions/type_builders.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/functions/type_builders.cc
@@ -308,25 +308,29 @@
struct AggCtx : SqliteAggregateContext<AggCtx> {
perfetto_sql::PartitionedTable partitions;
std::vector<SqlValue> tmp_vals;
- uint64_t max_ts = std::numeric_limits<uint32_t>::min();
+ uint64_t last_interval_start = 0;
};
static void Step(sqlite3_context* ctx, int rargc, sqlite3_value** argv) {
auto argc = static_cast<uint32_t>(rargc);
PERFETTO_DCHECK(argc >= kMinArgCount);
auto& agg_ctx = AggCtx::GetOrCreateContextForStep(ctx);
- auto& parts = AggCtx::GetOrCreateContextForStep(ctx).partitions;
// Fetch and validate the interval.
Interval interval;
interval.id = static_cast<uint32_t>(sqlite::value::Int64(argv[0]));
interval.start = static_cast<uint64_t>(sqlite::value::Int64(argv[1]));
- if (interval.start < agg_ctx.max_ts) {
+ if (interval.start < agg_ctx.last_interval_start) {
+ if (sqlite::value::Int64(argv[1]) < 0) {
+ sqlite::result::Error(
+ ctx, "Interval intersect only accepts positive `ts` values.");
+ return;
+ }
sqlite::result::Error(
ctx, "Interval intersect requires intervals to be sorted by ts.");
return;
}
- agg_ctx.max_ts = interval.start;
+ agg_ctx.last_interval_start = interval.start;
int64_t dur = sqlite::value::Int64(argv[2]);
if (dur < 1) {
sqlite::result::Error(
@@ -336,6 +340,7 @@
interval.end = interval.start + static_cast<uint64_t>(dur);
// Fast path for no partitions.
+ auto& parts = agg_ctx.partitions;
if (argc == kMinArgCount) {
auto& part = parts.partitions_map[0];
part.intervals.push_back(interval);
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/BUILD.gn b/src/trace_processor/perfetto_sql/intrinsics/table_functions/BUILD.gn
index 7be5b26..a73c7d9 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/BUILD.gn
@@ -41,6 +41,8 @@
"experimental_slice_layout.h",
"flamegraph_construction_algorithms.cc",
"flamegraph_construction_algorithms.h",
+ "winscope_proto_to_args_with_defaults.cc",
+ "winscope_proto_to_args_with_defaults.h",
"table_info.cc",
"table_info.h",
]
@@ -62,6 +64,9 @@
"../../../tables",
"../../../types",
"../../../util",
+ "../../../util:descriptors",
+ "../../../util:proto_to_args_parser",
+ "../../../util:winscope_proto_mapping",
"../../engine",
]
public_deps = [ ":interface" ]
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/tables.py b/src/trace_processor/perfetto_sql/intrinsics/table_functions/tables.py
index aa882dd..22999ee 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/tables.py
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/tables.py
@@ -80,6 +80,21 @@
],
parent=FLOW_TABLE)
+ARGS_WITH_DEFAULTS_TABLE = Table(
+ python_module=__file__,
+ class_name='WinscopeArgsWithDefaultsTable',
+ sql_name='__intrinsic_winscope_proto_to_args_with_defaults',
+ columns=[
+ C("table_name", CppString(), flags=ColumnFlag.HIDDEN),
+ C('base64_proto_id', CppUint32()),
+ C('flat_key', CppString()),
+ C('key', CppString()),
+ C('int_value', CppOptional(CppInt64())),
+ C('string_value', CppOptional(CppString())),
+ C('real_value', CppOptional(CppDouble())),
+ C('value_type', CppString()),
+ ])
+
DESCENDANT_SLICE_TABLE = Table(
python_module=__file__,
class_name="DescendantSliceTable",
@@ -169,6 +184,7 @@
ANCESTOR_SLICE_TABLE,
ANCESTOR_STACK_PROFILE_CALLSITE_TABLE,
CONNECTED_FLOW_TABLE,
+ ARGS_WITH_DEFAULTS_TABLE,
DESCENDANT_SLICE_BY_STACK_TABLE,
DESCENDANT_SLICE_TABLE,
DFS_WEIGHT_BOUNDED_TABLE,
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/winscope_proto_to_args_with_defaults.cc b/src/trace_processor/perfetto_sql/intrinsics/table_functions/winscope_proto_to_args_with_defaults.cc
new file mode 100644
index 0000000..ee8e600
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/winscope_proto_to_args_with_defaults.cc
@@ -0,0 +1,201 @@
+/*
+ * 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.
+ */
+
+#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/winscope_proto_to_args_with_defaults.h"
+
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/base64.h"
+#include "perfetto/ext/base/status_or.h"
+#include "src/trace_processor/containers/string_pool.h"
+#include "src/trace_processor/db/table.h"
+#include "src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h"
+#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/tables_py.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/util/descriptors.h"
+#include "src/trace_processor/util/proto_to_args_parser.h"
+#include "src/trace_processor/util/status_macros.h"
+#include "src/trace_processor/util/winscope_proto_mapping.h"
+
+namespace perfetto::trace_processor {
+namespace tables {
+WinscopeArgsWithDefaultsTable::~WinscopeArgsWithDefaultsTable() = default;
+} // namespace tables
+
+namespace {
+using Row = tables::WinscopeArgsWithDefaultsTable::Row;
+
+class Delegate : public util::ProtoToArgsParser::Delegate {
+ public:
+ using Key = util::ProtoToArgsParser::Key;
+ explicit Delegate(StringPool* pool,
+ const uint32_t base64_proto_id,
+ tables::WinscopeArgsWithDefaultsTable* table)
+ : pool_(pool), base64_proto_id_(base64_proto_id), table_(table) {}
+
+ void AddInteger(const Key& key, int64_t res) override {
+ Row r;
+ r.int_value = res;
+ SetColumnsAndInsertRow(key, r);
+ }
+ void AddUnsignedInteger(const Key& key, uint64_t res) override {
+ Row r;
+ r.int_value = res;
+ SetColumnsAndInsertRow(key, r);
+ }
+ void AddString(const Key& key, const protozero::ConstChars& res) override {
+ Row r;
+ r.string_value = pool_->InternString(base::StringView((res.ToStdString())));
+ SetColumnsAndInsertRow(key, r);
+ }
+ void AddString(const Key& key, const std::string& res) override {
+ Row r;
+ r.string_value = pool_->InternString(base::StringView(res));
+ SetColumnsAndInsertRow(key, r);
+ }
+ void AddDouble(const Key& key, double res) override {
+ Row r;
+ r.real_value = res;
+ SetColumnsAndInsertRow(key, r);
+ }
+ void AddBoolean(const Key& key, bool res) override {
+ Row r;
+ r.int_value = res;
+ SetColumnsAndInsertRow(key, r);
+ }
+ void AddBytes(const Key& key, const protozero::ConstBytes& res) override {
+ Row r;
+ r.string_value = pool_->InternString(base::StringView((res.ToStdString())));
+ SetColumnsAndInsertRow(key, r);
+ }
+ void AddNull(const Key& key) override {
+ Row r;
+ SetColumnsAndInsertRow(key, r);
+ }
+ void AddPointer(const Key&, const void*) override {
+ PERFETTO_FATAL("Unsupported");
+ }
+ bool AddJson(const Key&, const protozero::ConstChars&) override {
+ PERFETTO_FATAL("Unsupported");
+ }
+ size_t GetArrayEntryIndex(const std::string&) override {
+ PERFETTO_FATAL("Unsupported");
+ }
+ size_t IncrementArrayEntryIndex(const std::string&) override {
+ PERFETTO_FATAL("Unsupported");
+ }
+ PacketSequenceStateGeneration* seq_state() override { return nullptr; }
+
+ private:
+ InternedMessageView* GetInternedMessageView(uint32_t, uint64_t) override {
+ return nullptr;
+ }
+
+ void SetColumnsAndInsertRow(const Key& key, Row& row) {
+ row.key = pool_->InternString(base::StringView(key.key));
+ row.flat_key = pool_->InternString(base::StringView(key.flat_key));
+ row.base64_proto_id = base64_proto_id_;
+ table_->Insert(row);
+ }
+
+ StringPool* pool_;
+ const uint32_t base64_proto_id_;
+ tables::WinscopeArgsWithDefaultsTable* table_;
+};
+
+base::Status InsertRows(
+ const Table& static_table,
+ tables::WinscopeArgsWithDefaultsTable* inflated_args_table,
+ const std::string& proto_name,
+ const std::vector<uint32_t>* allowed_fields,
+ DescriptorPool& descriptor_pool,
+ StringPool* string_pool) {
+ util::ProtoToArgsParser args_parser{descriptor_pool};
+ const auto base64_proto_id_col_idx =
+ static_table.ColumnIdxFromName("base64_proto_id").value();
+ const auto base_64_proto_col_idx =
+ static_table.ColumnIdxFromName("base64_proto").value();
+
+ std::unordered_set<uint32_t> inflated_protos;
+ for (auto it = static_table.IterateRows(); it; ++it) {
+ const auto base64_proto_id =
+ static_cast<uint32_t>(it.Get(base64_proto_id_col_idx).AsLong());
+ if (inflated_protos.count(base64_proto_id) > 0) {
+ continue;
+ }
+ inflated_protos.insert(base64_proto_id);
+ const auto* raw_proto = it.Get(base_64_proto_col_idx).AsString();
+ const auto blob = *base::Base64Decode(raw_proto);
+ const auto cb = protozero::ConstBytes{
+ reinterpret_cast<const uint8_t*>(blob.data()), blob.size()};
+ Delegate delegate(string_pool, base64_proto_id, inflated_args_table);
+ RETURN_IF_ERROR(args_parser.ParseMessage(cb, proto_name, allowed_fields,
+ delegate, nullptr, true));
+ }
+ return base::OkStatus();
+}
+} // namespace
+
+WinscopeProtoToArgsWithDefaults::WinscopeProtoToArgsWithDefaults(
+ StringPool* string_pool,
+ PerfettoSqlEngine* engine,
+ TraceProcessorContext* context)
+ : string_pool_(string_pool), engine_(engine), context_(context) {}
+
+base::StatusOr<std::unique_ptr<Table>>
+WinscopeProtoToArgsWithDefaults::ComputeTable(
+ const std::vector<SqlValue>& arguments) {
+ PERFETTO_CHECK(arguments.size() == 1);
+ if (arguments[0].type != SqlValue::kString) {
+ return base::ErrStatus(
+ "__intrinsic_winscope_proto_to_args_with_defaults takes table name as "
+ "a string.");
+ }
+ std::string table_name = arguments[0].AsString();
+
+ const Table* static_table = engine_->GetStaticTableOrNull(table_name);
+ if (!static_table) {
+ return base::ErrStatus("Failed to find %s table.", table_name.c_str());
+ }
+
+ std::string proto_name;
+ ASSIGN_OR_RETURN(proto_name,
+ util::winscope_proto_mapping::GetProtoName(table_name));
+
+ auto table =
+ std::make_unique<tables::WinscopeArgsWithDefaultsTable>(string_pool_);
+
+ auto allowed_fields =
+ util::winscope_proto_mapping::GetAllowedFields(table_name);
+ RETURN_IF_ERROR(InsertRows(*static_table, table.get(), proto_name,
+ allowed_fields ? &allowed_fields.value() : nullptr,
+ *context_->descriptor_pool_, string_pool_));
+
+ return std::unique_ptr<Table>(std::move(table));
+}
+
+Table::Schema WinscopeProtoToArgsWithDefaults::CreateSchema() {
+ return tables::WinscopeArgsWithDefaultsTable::ComputeStaticSchema();
+}
+
+std::string WinscopeProtoToArgsWithDefaults::TableName() {
+ return tables::WinscopeArgsWithDefaultsTable::Name();
+}
+
+uint32_t WinscopeProtoToArgsWithDefaults::EstimateRowCount() {
+ // 100 inflated args per 100 elements per 100 entries
+ return 1000000;
+}
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/winscope_proto_to_args_with_defaults.h b/src/trace_processor/perfetto_sql/intrinsics/table_functions/winscope_proto_to_args_with_defaults.h
new file mode 100644
index 0000000..91ab8c8
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/winscope_proto_to_args_with_defaults.h
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_TABLE_FUNCTIONS_WINSCOPE_PROTO_TO_ARGS_WITH_DEFAULTS_H_
+#define SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_TABLE_FUNCTIONS_WINSCOPE_PROTO_TO_ARGS_WITH_DEFAULTS_H_
+
+#include "perfetto/ext/base/status_or.h"
+#include "src/trace_processor/containers/string_pool.h"
+#include "src/trace_processor/db/table.h"
+#include "src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h"
+#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/static_table_function.h"
+
+namespace perfetto::trace_processor {
+
+class TraceProcessorContext;
+
+class WinscopeProtoToArgsWithDefaults : public StaticTableFunction {
+ public:
+ explicit WinscopeProtoToArgsWithDefaults(StringPool*,
+ PerfettoSqlEngine*,
+ TraceProcessorContext* context);
+
+ Table::Schema CreateSchema() override;
+ std::string TableName() override;
+ uint32_t EstimateRowCount() override;
+ base::StatusOr<std::unique_ptr<Table>> ComputeTable(
+ const std::vector<SqlValue>& arguments) override;
+
+ private:
+ StringPool* string_pool_ = nullptr;
+ PerfettoSqlEngine* engine_ = nullptr;
+ TraceProcessorContext* context_ = nullptr;
+};
+
+} // namespace perfetto::trace_processor
+
+#endif // SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_TABLE_FUNCTIONS_WINSCOPE_PROTO_TO_ARGS_WITH_DEFAULTS_H_
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/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/android/BUILD.gn
index 40320fa..4f0208c 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/android/BUILD.gn
@@ -1,4 +1,4 @@
-# Copyright (C) 2022 The Android Open Source Project
+# 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.
@@ -17,6 +17,7 @@
perfetto_sql_source_set("android") {
deps = [
"auto",
+ "battery",
"cpu",
"frames",
"gpu",
@@ -33,6 +34,7 @@
"binder_breakdown.sql",
"broadcasts.sql",
"critical_blocking_calls.sql",
+ "desktop_mode.sql",
"device.sql",
"dvfs.sql",
"freezer.sql",
@@ -40,6 +42,7 @@
"input.sql",
"io.sql",
"job_scheduler.sql",
+ "job_scheduler_states.sql",
"monitor_contention.sql",
"network_packets.sql",
"oom_adjuster.sql",
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/battery.sql b/src/trace_processor/perfetto_sql/stdlib/android/battery.sql
index 8d21767..c3723d8 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/battery.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/battery.sql
@@ -24,14 +24,20 @@
-- Current charge in micro ampers.
charge_uah DOUBLE,
-- Current micro ampers.
- current_ua DOUBLE
+ current_ua DOUBLE,
+ -- Current voltage in micro volts.
+ voltage_uv DOUBLE,
+ -- Current energy counter in microwatt-hours(µWh).
+ energy_counter_uwh DOUBLE
) AS
SELECT
all_ts.ts,
current_avg_ua,
capacity_percent,
charge_uah,
- current_ua
+ current_ua,
+ voltage_uv,
+ energy_counter_uwh
FROM (
SELECT DISTINCT(ts) AS ts
FROM counter c
@@ -62,4 +68,16 @@
JOIN counter_track t ON c.track_id = t.id
WHERE name = 'batt.current_ua'
) USING(ts)
+LEFT JOIN (
+ SELECT ts, value AS voltage_uv
+ FROM counter c
+ JOIN counter_track t ON c.track_id = t.id
+ WHERE name = 'batt.voltage_uv'
+) USING(ts)
+LEFT JOIN (
+ SELECT ts, value AS energy_counter_uwh
+ FROM counter c
+ JOIN counter_track t ON c.track_id = t.id
+ WHERE name = 'batt.energy_counter_uwh'
+) USING(ts)
ORDER BY ts;
diff --git a/src/trace_processor/perfetto_sql/stdlib/common/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/android/battery/BUILD.gn
similarity index 67%
copy from src/trace_processor/perfetto_sql/stdlib/common/BUILD.gn
copy to src/trace_processor/perfetto_sql/stdlib/android/battery/BUILD.gn
index 73db0a7..828c10b 100644
--- a/src/trace_processor/perfetto_sql/stdlib/common/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/android/battery/BUILD.gn
@@ -1,4 +1,4 @@
-# Copyright (C) 2022 The Android Open Source Project
+# 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.
@@ -12,15 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import("../../../../../gn/perfetto_sql.gni")
+import("../../../../../../gn/perfetto_sql.gni")
-perfetto_sql_source_set("common") {
- sources = [
- "args.sql",
- "counters.sql",
- "metadata.sql",
- "percentiles.sql",
- "slices.sql",
- "timestamps.sql",
- ]
+perfetto_sql_source_set("battery") {
+ sources = [ "charging_states.sql" ]
}
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/battery/charging_states.sql b/src/trace_processor/perfetto_sql/stdlib/android/battery/charging_states.sql
new file mode 100644
index 0000000..6629fe4
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/android/battery/charging_states.sql
@@ -0,0 +1,71 @@
+--
+-- 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 counters.intervals;
+
+-- Device charging states.
+CREATE PERFETTO TABLE android_charging_states(
+ -- Alias of counter.id if a slice with charging state exists otherwise
+ -- there will be a single row where id = 1.
+ id INT,
+ -- Timestamp at which the device charging state began.
+ ts INT,
+ -- Duration of the device charging state.
+ dur INT,
+ -- Device charging state, one of: Charging, Discharging, Not charging
+ -- (when the charger is present but battery is not charging),
+ -- Full, Unknown
+ charging_state STRING
+) AS SELECT
+ id,
+ ts,
+ dur,
+ charging_state
+ FROM (
+ WITH
+ _counter AS (
+ SELECT counter.id, ts, 0 AS track_id, value
+ FROM counter
+ JOIN counter_track
+ ON counter_track.id = counter.track_id
+ WHERE counter_track.name = 'BatteryStatus'
+ )
+ SELECT
+ id,
+ ts,
+ dur,
+ CASE
+ value
+ -- 0 and 1 are both 'Unknown'
+ WHEN 2 THEN 'Charging'
+ WHEN 3 THEN 'Discharging'
+ -- special case when charger is present but battery isn't charging
+ WHEN 4 THEN 'Not charging'
+ WHEN 5 THEN 'Full'
+ ELSE 'Unknown'
+ END AS charging_state
+ FROM counter_leading_intervals !(_counter)
+ WHERE dur > 0
+ -- Either the above select statement is populated or the
+ -- select statement after the union is populated but not both.
+ UNION
+ -- When the trace does not have a slice in the charging state track then
+ -- we will assume that the charging state for the entire trace is Unknown.
+ -- This ensures that we still have job data even if the charging state is
+ -- not known. The following statement will only ever return a single row.
+ SELECT 1, TRACE_START(), TRACE_DUR(), 'Unknown'
+ WHERE NOT EXISTS (
+ SELECT * FROM _counter
+ ) AND TRACE_DUR() > 0
+ );
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/binder.sql b/src/trace_processor/perfetto_sql/stdlib/android/binder.sql
index ec15d90..6994122 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/binder.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/binder.sql
@@ -122,8 +122,9 @@
JOIN thread reply_thread ON reply_thread.utid = reply_thread_track.utid
JOIN process reply_process ON reply_process.upid = reply_thread.upid
LEFT JOIN slice aidl ON aidl.parent_id = binder_reply.id
- AND (aidl.name GLOB 'AIDL::cpp*Server'
- OR aidl.name GLOB 'AIDL::java*server'
+ -- Filter for only server side AIDL slices as there are some client side ones for cpp
+ AND (aidl.name GLOB 'AIDL::*Server'
+ OR aidl.name GLOB 'AIDL::*server'
OR aidl.name GLOB 'HIDL::*server')
)
SELECT
@@ -323,8 +324,9 @@
SELECT id, ts, dur, track_id, name
FROM slice
WHERE
- name GLOB 'AIDL::cpp*Server'
- OR name GLOB 'AIDL::java*server'
+ -- Filter for only server side AIDL slices as there are some client side ones for cpp
+ name GLOB 'AIDL::*Server'
+ OR name GLOB 'AIDL::*server'
OR name GLOB 'HIDL::*server'
OR name = 'binder async rcv'
) SELECT *, LEAD(name) OVER (PARTITION BY track_id ORDER BY ts) AS next_name,
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/android/desktop_mode.sql b/src/trace_processor/perfetto_sql/stdlib/android/desktop_mode.sql
new file mode 100644
index 0000000..496e4ee
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/android/desktop_mode.sql
@@ -0,0 +1,73 @@
+--
+-- 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 android.statsd;
+
+-- Desktop Windows with durations they were open.
+CREATE PERFETTO TABLE android_desktop_mode_windows (
+-- Window add timestamp; NULL if no add event in the trace.
+raw_add_ts INT,
+-- Window remove timestamp; NULL if no remove event in the trace.
+raw_remove_ts INT,
+-- timestamp that the window was added; or trace_start() if no add event in the trace.
+ts INT,
+-- duration the window was open; or until trace_end() if no remove event in the trace.
+dur INT,
+-- Desktop Window instance ID - unique per window.
+instance_id INT,
+-- UID of the app running in the window.
+uid INT
+) AS
+WITH
+ atoms AS (
+ SELECT
+ ts,
+ extract_arg(arg_set_id, 'desktop_mode_session_task_update.task_event') AS type,
+ extract_arg(arg_set_id, 'desktop_mode_session_task_update.instance_id') AS instance_id,
+ extract_arg(arg_set_id, 'desktop_mode_session_task_update.uid') AS uid,
+ extract_arg(arg_set_id, 'desktop_mode_session_task_update.session_id') AS session_id
+ FROM android_statsd_atoms
+ WHERE name = 'desktop_mode_session_task_update'),
+ dw_statsd_events_add AS (
+ SELECT *
+ FROM atoms
+ WHERE type = 'TASK_ADDED'),
+ dw_statsd_events_remove AS (
+ SELECT * FROM atoms
+ WHERE type = 'TASK_REMOVED'),
+ dw_statsd_events_update_by_instance AS (
+ SELECT instance_id, session_id, min(uid) AS uid FROM atoms
+ WHERE type = 'TASK_INFO_CHANGED' GROUP BY instance_id, session_id),
+ dw_windows AS (
+ SELECT
+ a.ts AS raw_add_ts,
+ r.ts AS raw_remove_ts,
+ ifnull(a.ts, trace_start()) AS ts, -- Assume trace_start() if no add event found.
+ ifnull(r.ts, trace_end()) - ifnull(a.ts, trace_start()) AS dur, -- Assume trace_end() if no remove event found.
+ ifnull(a.instance_id, r.instance_id) AS instance_id,
+ ifnull(a.uid, r.uid) AS uid
+ FROM dw_statsd_events_add a
+ FULL JOIN dw_statsd_events_remove r USING(instance_id, session_id)),
+ -- Assume window was open for the entire trace if we only see change events for the instance ID.
+ dw_windows_with_update_events AS (
+ SELECT * FROM dw_windows
+ UNION
+ SELECT NULL, NULL, trace_start(), trace_end() - trace_start(), instance_id, uid
+ FROM dw_statsd_events_update_by_instance
+ WHERE
+ instance_id NOT IN (SELECT instance_id FROM dw_windows))
+SELECT * FROM dw_windows_with_update_events;
+
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/frames/timeline.sql b/src/trace_processor/perfetto_sql/stdlib/android/frames/timeline.sql
index f9637d5..5eb7c52 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/frames/timeline.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/frames/timeline.sql
@@ -30,14 +30,17 @@
-- Utid.
utid INT,
-- Upid.
- upid INT
+ upid INT,
+ -- Timestamp of the frame slice.
+ ts INT
) AS
WITH all_found AS (
SELECT
id,
cast_int!(STR_SPLIT(name, ' ', 1)) AS frame_id,
utid,
- upid
+ upid,
+ ts
FROM thread_slice
-- Mostly the frame slice is at depth 0. Though it could be pushed to depth 1 while users
-- enable low layer trace e.g. atrace_app.
@@ -57,13 +60,16 @@
-- Utid of the UI thread
ui_thread_utid INT,
-- Upid of application process
- upid INT
+ upid INT,
+ -- Timestamp of the slice.
+ ts INT
) AS
SELECT
id,
frame_id,
utid AS ui_thread_utid,
- upid
+ upid,
+ ts
-- Some OEMs have customized `doFrame` to add more information, but we've only
-- observed it added after the frame ID (b/303823815).
FROM _get_frame_table_with_id('Choreographer#doFrame*');
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/gpu/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/android/gpu/BUILD.gn
index 09a5e85..e481ac5 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/gpu/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/android/gpu/BUILD.gn
@@ -18,5 +18,6 @@
sources = [
"frequency.sql",
"memory.sql",
+ "work_period.sql",
]
}
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/gpu/work_period.sql b/src/trace_processor/perfetto_sql/stdlib/android/gpu/work_period.sql
new file mode 100644
index 0000000..4f498d0
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/android/gpu/work_period.sql
@@ -0,0 +1,36 @@
+--
+-- 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.
+
+-- Tracks for GPU work period events originating from the
+-- `power/gpu_work_period` Linux ftrace tracepoint.
+--
+-- This tracepoint is usually only available on selected Android devices.
+CREATE PERFETTO TABLE android_gpu_work_period_track (
+ -- Unique identifier for this track. Joinable with track.id.
+ id UINT,
+ -- Machine identifier, non-null for tracks on a remote machine.
+ machine_id UINT,
+ -- The UID of the package for which the GPU work period events were emitted.
+ uid INT,
+ -- The GPU identifier for which the GPU work period events were emitted.
+ gpu_id INT
+) AS
+SELECT
+ id,
+ machine_id,
+ extract_arg(dimension_arg_set_id, 'uid') as uid,
+ extract_arg(dimension_arg_set_id, 'gpu') as gpu_id
+FROM track
+WHERE classification = 'android_gpu_work_period';
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/input.sql b/src/trace_processor/perfetto_sql/stdlib/android/input.sql
index 687357c..217132d 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/input.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/input.sql
@@ -13,6 +13,9 @@
-- See the License for the specific language governing permissions and
-- limitations under the License.
+INCLUDE PERFETTO MODULE android.frames.timeline;
+INCLUDE PERFETTO MODULE slices.with_context;
+
CREATE PERFETTO TABLE _input_message_sent
AS
SELECT
@@ -59,6 +62,115 @@
WHERE slice.name GLOB 'receiveMessage(*'
ORDER BY event_seq;
+CREATE PERFETTO TABLE _input_read_time
+AS
+SELECT
+ name,
+ STR_SPLIT(STR_SPLIT(name, '=', 1), ')', 0) AS input_event_id,
+ ts as read_time
+FROM slice
+WHERE name GLOB 'UnwantedInteractionBlocker::notifyMotion*';
+
+CREATE PERFETTO TABLE _event_seq_to_input_event_id
+AS
+SELECT
+ STR_SPLIT(STR_SPLIT(send_message_slice.name, '=', 2), ',', 0) AS event_seq,
+ STR_SPLIT(STR_SPLIT(send_message_slice.name, '=', 1), ',', 0) AS event_channel,
+ STR_SPLIT(STR_SPLIT(enqeue_slice.name, '=', 2), ')', 0) AS input_event_id,
+ thread_slice.thread_name
+FROM slice send_message_slice
+JOIN slice publish_slice
+ ON send_message_slice.parent_id = publish_slice.id
+JOIN slice start_dispatch_slice
+ ON publish_slice.parent_id = start_dispatch_slice.id
+JOIN slice enqeue_slice
+ ON start_dispatch_slice.parent_id = enqeue_slice.id
+JOIN thread_slice
+ ON send_message_slice.id = thread_slice.id
+WHERE send_message_slice.name GLOB 'sendMessage(*' AND thread_slice.thread_name = 'InputDispatcher';
+
+CREATE PERFETTO TABLE _input_event_id_to_android_frame
+AS
+SELECT
+ STR_SPLIT(deliver_input_slice.name, '=', 3) AS input_event_id,
+ STR_SPLIT(STR_SPLIT(dispatch_input_slice.name, '_', 1), ' ', 0) AS event_action,
+ dispatch_input_slice.ts AS consume_time,
+ dispatch_input_slice.ts + dispatch_input_slice.dur AS finish_time,
+ thread_slice.utid,
+ thread_slice.process_name AS process_name,
+ (
+ SELECT
+ android_frames.frame_id
+ FROM android_frames
+ WHERE android_frames.ts > dispatch_input_slice.ts
+ LIMIT 1
+ ) as frame_id,
+ (
+ SELECT
+ android_frames.ts
+ FROM android_frames
+ WHERE android_frames.ts > dispatch_input_slice.ts
+ LIMIT 1
+ ) as ts,
+ (
+ SELECT
+ _input_message_received.event_channel
+ FROM _input_message_received
+ WHERE _input_message_received.ts < deliver_input_slice.ts
+ AND _input_message_received.track_id = deliver_input_slice.track_id
+ ORDER BY _input_message_received.ts DESC
+ LIMIT 1
+ ) as event_channel
+FROM slice deliver_input_slice
+JOIN slice dispatch_input_slice
+ ON deliver_input_slice.parent_id = dispatch_input_slice.id
+JOIN thread_slice
+ ON deliver_input_slice.id = thread_slice.id
+WHERE deliver_input_slice.name GLOB 'deliverInputEvent src=*';
+
+CREATE PERFETTO TABLE _app_frame_to_surface_flinger_frame
+AS
+SELECT
+ app.surface_frame_token as app_surface_frame_token,
+ surface_flinger.ts as surface_flinger_ts,
+ surface_flinger.dur as surface_flinger_dur,
+ app.ts as app_ts,
+ app.present_type,
+ app.upid
+FROM actual_frame_timeline_slice surface_flinger
+JOIN actual_frame_timeline_slice app
+ ON surface_flinger.display_frame_token = app.display_frame_token
+ AND surface_flinger.id != app.id
+WHERE surface_flinger.surface_frame_token = 0 AND app.present_type != 'Dropped Frame';
+
+CREATE PERFETTO TABLE _first_non_dropped_frame_after_input
+AS
+SELECT
+ _input_read_time.input_event_id,
+ _input_read_time.read_time,
+ (
+ SELECT
+ surface_flinger_ts + surface_flinger_dur
+ FROM _app_frame_to_surface_flinger_frame sf_frames
+ WHERE sf_frames.app_ts >= _input_event_id_to_android_frame.ts
+ LIMIT 1
+ ) AS present_time,
+ (
+ SELECT
+ app_surface_frame_token
+ FROM _app_frame_to_surface_flinger_frame sf_frames
+ WHERE sf_frames.app_ts >= _input_event_id_to_android_frame.ts
+ LIMIT 1
+ ) as frame_id,
+ event_seq,
+ event_action
+FROM _input_event_id_to_android_frame
+RIGHT JOIN _event_seq_to_input_event_id
+ ON _input_event_id_to_android_frame.input_event_id = _event_seq_to_input_event_id.input_event_id
+ AND _input_event_id_to_android_frame.event_channel = _event_seq_to_input_event_id.event_channel
+JOIN _input_read_time
+ ON _input_read_time.input_event_id = _event_seq_to_input_event_id.input_event_id;
+
-- All input events with round trip latency breakdown. Input delivery is socket based and every
-- input event sent from the OS needs to be ACK'ed by the app. This gives us 4 subevents to measure
-- latencies between:
@@ -75,6 +187,8 @@
ack_latency_dur INT,
-- Duration from input dispatch to input event ACK received.
total_latency_dur INT,
+ -- Duration from input read to frame present time. Null if an input event has no associated frame event.
+ end_to_end_latency_dur INT,
-- Tid of thread receiving the input event.
tid INT,
-- Name of thread receiving the input event.
@@ -85,10 +199,16 @@
process_name STRING,
-- Input event type. See InputTransport.h: InputMessage#Type
event_type STRING,
+ -- Input event action.
+ event_action STRING,
-- Input event sequence number, monotonically increasing for an event channel and pid.
event_seq STRING,
-- Input event channel name.
event_channel STRING,
+ -- Unique identifier for the input event.
+ input_event_id STRING,
+ -- Timestamp input event was read by InputReader.
+ read_time INT,
-- Thread track id of input event dispatching thread.
dispatch_track_id INT,
-- Timestamp input event was dispatched.
@@ -100,7 +220,9 @@
-- Timestamp input event was received.
receive_ts INT,
-- Duration of input event receipt.
- receive_dur INT
+ receive_dur INT,
+ -- Vsync Id associated with the input. Null if an input event has no associated frame event.
+ frame_id INT
)
AS
WITH dispatch AS MATERIALIZED (
@@ -134,19 +256,24 @@
finish.ts - receive.ts AS handling_latency_dur,
finish_ack.ts - finish.ts AS ack_latency_dur,
finish_ack.ts - dispatch.ts AS total_latency_dur,
+ frame.present_time - frame.read_time AS end_to_end_latency_dur,
finish.tid AS tid,
finish.thread_name AS thread_name,
finish.pid AS pid,
finish.process_name AS process_name,
dispatch.event_type,
+ frame.event_action,
dispatch.event_seq,
dispatch.event_channel,
+ frame.input_event_id,
+ frame.read_time,
dispatch.track_id AS dispatch_track_id,
dispatch.ts AS dispatch_ts,
dispatch.dur AS dispatch_dur,
receive.ts AS receive_ts,
receive.dur AS receive_dur,
- receive.track_id AS receive_track_id
+ receive.track_id AS receive_track_id,
+ frame.frame_id
FROM dispatch
JOIN receive
ON
@@ -159,7 +286,9 @@
JOIN finish_ack
ON
finish_ack.event_channel = dispatch.event_channel
- AND dispatch.event_seq = finish_ack.event_seq;
+ AND dispatch.event_seq = finish_ack.event_seq
+LEFT JOIN _first_non_dropped_frame_after_input frame
+ ON frame.event_seq = dispatch.event_seq;
-- Key events processed by the Android framework (from android.input.inputevent data source).
CREATE PERFETTO VIEW android_key_events(
@@ -171,13 +300,19 @@
-- The timestamp of when the input event was processed by the system
ts INT,
-- Details of the input event parsed from the proto message
- arg_set_id INT
+ arg_set_id INT,
+ -- Raw proto message encoded in base64
+ base64_proto STRING,
+ -- String id for raw proto message
+ base64_proto_id INT
) AS
SELECT
id,
event_id,
ts,
- arg_set_id
+ arg_set_id,
+ base64_proto,
+ base64_proto_id
FROM __intrinsic_android_key_events;
-- Motion events processed by the Android framework (from android.input.inputevent data source).
@@ -190,13 +325,19 @@
-- The timestamp of when the input event was processed by the system
ts INT,
-- Details of the input event parsed from the proto message
- arg_set_id INT
+ arg_set_id INT,
+ -- Raw proto message encoded in base64
+ base64_proto STRING,
+ -- String id for raw proto message
+ base64_proto_id INT
) AS
SELECT
id,
event_id,
ts,
- arg_set_id
+ arg_set_id,
+ base64_proto,
+ base64_proto_id
FROM __intrinsic_android_motion_events;
-- Input event dispatching information in Android (from android.input.inputevent data source).
@@ -205,8 +346,12 @@
id INT,
-- Event ID of the input event that was dispatched
event_id INT,
- -- Extra args parsed from the proto message
+ -- Details of the input event parsed from the proto message
arg_set_id INT,
+ -- Raw proto message encoded in base64
+ base64_proto STRING,
+ -- String id for raw proto message
+ base64_proto_id INT,
-- Vsync ID that identifies the state of the windows during which the dispatch decision was made
vsync_id INT,
-- Window ID of the window receiving the event
@@ -216,6 +361,8 @@
id,
event_id,
arg_set_id,
+ base64_proto,
+ base64_proto_id,
vsync_id,
window_id
FROM __intrinsic_android_input_event_dispatch;
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/job_scheduler.sql b/src/trace_processor/perfetto_sql/stdlib/android/job_scheduler.sql
index dc222bf..98f65e0 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/job_scheduler.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/job_scheduler.sql
@@ -15,6 +15,18 @@
--
-- All scheduled jobs and their latencies.
+--
+-- The table is populated by ATrace using the system server ATrace category
+-- (`atrace_categories: "ss"`). You can also set the `atrace_apps` of interest.
+--
+-- This differs from the `android_job_scheduler_states` table
+-- in the `android.job_scheduler_states` module which is populated
+-- by the `ScheduledJobStateChanged` atom.
+--
+-- Using `android_job_scheduler_states` is preferred when the
+-- `ATOM_SCHEDULED_JOB_STATE_CHANGED` is available in the trace since
+-- it includes the constraint, screen, or charging state changes for
+-- each job in a trace.
CREATE PERFETTO TABLE android_job_scheduler_events (
-- Id of the scheduled job assigned by the app developer.
job_id INT,
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/job_scheduler_states.sql b/src/trace_processor/perfetto_sql/stdlib/android/job_scheduler_states.sql
new file mode 100644
index 0000000..4407660
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/android/job_scheduler_states.sql
@@ -0,0 +1,458 @@
+--
+-- 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 counters.intervals;
+INCLUDE PERFETTO MODULE android.battery.charging_states;
+INCLUDE PERFETTO MODULE intervals.intersect;
+
+CREATE PERFETTO TABLE _screen_states AS
+SELECT
+ id,
+ ts,
+ dur,
+ screen_state
+FROM (
+ WITH _screen_state_span AS (
+ SELECT *
+ FROM counter_leading_intervals!((
+ SELECT counter.id, ts, 0 AS track_id, value
+ FROM counter
+ JOIN counter_track ON counter_track.id = counter.track_id
+ WHERE name = 'ScreenState'
+ ))) SELECT
+ id,
+ ts,
+ dur,
+ CASE value
+ WHEN 1 THEN 'Screen off'
+ WHEN 2 THEN 'Screen on'
+ WHEN 3 THEN 'Always-on display (doze)'
+ ELSE 'Unknown'
+ END AS screen_state
+ FROM _screen_state_span
+ WHERE dur > 0
+ -- Either the above select statement is populated or the
+ -- select statement after the union is populated but not both.
+ UNION
+ -- When the trace does not have a slice in the screen state track then
+ -- we will assume that the screen state for the entire trace is Unknown.
+ -- This ensures that we still have job data even if the screen state is
+ -- not known. The following statement will only ever return a single row.
+ SELECT 1, TRACE_START() as ts, TRACE_DUR() as dur, 'Unknown'
+ WHERE NOT EXISTS (
+ SELECT * FROM _screen_state_span
+ ) AND TRACE_DUR() > 0
+);
+
+CREATE PERFETTO TABLE _job_states AS
+SELECT
+ t.id as track_id,
+ s.ts,
+ s.id AS slice_id,
+ extract_arg(arg_set_id, 'scheduled_job_state_changed.job_name') AS job_name,
+ extract_arg(arg_set_id, 'scheduled_job_state_changed.attribution_node[0].uid') AS uid,
+ extract_arg(arg_set_id, 'scheduled_job_state_changed.state') AS state,
+ extract_arg(arg_set_id, 'scheduled_job_state_changed.internal_stop_reason')
+ AS internal_stop_reason,
+ extract_arg(arg_set_id, 'scheduled_job_state_changed.public_stop_reason')
+ AS public_stop_reason,
+ extract_arg(arg_set_id, 'scheduled_job_state_changed.effective_priority')
+ AS effective_priority,
+ extract_arg(arg_set_id, 'scheduled_job_state_changed.has_battery_not_low_constraint')
+ AS has_battery_not_low_constraint,
+ extract_arg(arg_set_id, 'scheduled_job_state_changed.has_charging_constraint')
+ AS has_charging_constraint,
+ extract_arg(arg_set_id, 'scheduled_job_state_changed.has_connectivity_constraint')
+ AS has_connectivity_constraint,
+ extract_arg(arg_set_id, 'scheduled_job_state_changed.has_content_trigger_constraint')
+ AS has_content_trigger_constraint,
+ extract_arg(arg_set_id, 'scheduled_job_state_changed.has_deadline_constraint')
+ AS has_deadline_constraint,
+ extract_arg(arg_set_id, 'scheduled_job_state_changed.has_idle_constraint')
+ AS has_idle_constraint,
+ extract_arg(arg_set_id, 'scheduled_job_state_changed.has_storage_not_low_constraint')
+ AS has_storage_not_low_constraint,
+ extract_arg(arg_set_id, 'scheduled_job_state_changed.has_timing_delay_constraint')
+ AS has_timing_delay_constraint,
+ extract_arg(arg_set_id, 'scheduled_job_state_changed.is_prefetch') == 1
+ AS is_prefetch,
+ extract_arg(arg_set_id, 'scheduled_job_state_changed.is_requested_expedited_job')
+ AS is_requested_expedited_job,
+ extract_arg(arg_set_id, 'scheduled_job_state_changed.is_running_as_expedited_job')
+ AS is_running_as_expedited_job,
+ extract_arg(arg_set_id, 'scheduled_job_state_changed.job_id') AS job_id,
+ extract_arg(arg_set_id, 'scheduled_job_state_changed.num_previous_attempts')
+ AS num_previous_attempts,
+ extract_arg(arg_set_id, 'scheduled_job_state_changed.requested_priority')
+ AS requested_priority,
+ extract_arg(arg_set_id, 'scheduled_job_state_changed.standby_bucket')
+ AS standby_bucket,
+ extract_arg(arg_set_id, 'scheduled_job_state_changed.is_periodic')
+ AS is_periodic,
+ extract_arg(arg_set_id, 'scheduled_job_state_changed.is_periodic')
+ AS has_flex_constraint,
+ extract_arg(arg_set_id, 'scheduled_job_state_changed.is_requested_as_user_initiated_job')
+ AS is_requested_as_user_initiated_job,
+ extract_arg(arg_set_id, 'scheduled_job_state_changed.is_running_as_user_initiated_job')
+ AS is_running_as_user_initiated_job,
+ extract_arg(arg_set_id, 'scheduled_job_state_changed.deadline_ms')
+ AS deadline_ms,
+ extract_arg(arg_set_id, 'scheduled_job_state_changed.job_start_latency_ms')
+ AS job_start_latency_ms,
+ extract_arg(arg_set_id, 'scheduled_job_state_changed.num_uncompleted_work_items')
+ AS num_uncompleted_work_items,
+ extract_arg(arg_set_id, 'scheduled_job_state_changed.proc_state')
+ AS proc_state
+FROM
+ track t
+JOIN slice s
+ ON (s.track_id = t.id)
+WHERE
+ t.name = 'Statsd Atoms' AND s.name = 'scheduled_job_state_changed';
+
+CREATE PERFETTO TABLE _job_started AS
+WITH cte AS (
+ SELECT
+ *,
+ LEAD(state, 1)
+ OVER (PARTITION BY uid, job_name, job_id ORDER BY uid, job_name, job_id, ts) AS lead_state,
+ LEAD(ts, 1, TRACE_END())
+ OVER (PARTITION BY uid, job_name, job_id ORDER BY uid, job_name, job_id, ts) AS ts_lead,
+ --- Filter out statsd lossy issue.
+ LEAD(ts, 1)
+ OVER (PARTITION BY uid, job_name, job_id ORDER BY uid, job_name, job_id, ts) IS NULL AS is_end_slice,
+ LEAD(internal_stop_reason, 1, 'INTERNAL_STOP_REASON_UNKNOWN')
+ OVER (
+ PARTITION BY uid, job_name, job_id
+ ORDER BY uid, job_name, job_id, ts
+ ) AS lead_internal_stop_reason,
+ LEAD(public_stop_reason, 1, 'PUBLIC_STOP_REASON_UNKNOWN')
+ OVER (
+ PARTITION BY uid, job_name, job_id
+ ORDER BY uid, job_name, job_id, ts
+ ) AS lead_public_stop_reason
+ FROM _job_states
+ WHERE state != 'CANCELLED'
+)
+SELECT
+ -- Job name is based on whether the tag and/or namespace are present:
+ -- 1. Both tag and namespace are present: @<namespace>@<tag>:<package name>
+ -- 2. Only tag is present: <tag>:<package name>
+ -- 3. Only namespace is present: @<namespace>@<package name>/<class name>
+ CASE
+ WHEN substr(job_name, 1, 1) = '@'
+ THEN
+ CASE
+ WHEN substr(STR_SPLIT(job_name, '/', 1), 1, 3) = 'com' THEN STR_SPLIT(job_name, '/', 1)
+ ELSE STR_SPLIT(STR_SPLIT(job_name, '/', 0), '@', 2)
+ END
+ ELSE STR_SPLIT(job_name, '/', 0)
+ END AS package_name,
+ CASE
+ WHEN substr(job_name, 1, 1) = '@' THEN STR_SPLIT(job_name, '@', 1)
+ ELSE STR_SPLIT(job_name, '/', 1)
+ END AS job_namespace,
+ ts_lead - ts AS dur,
+ IIF(lead_state = 'SCHEDULED', TRUE, FALSE) AS is_rescheduled,
+ *
+FROM cte
+WHERE
+ is_end_slice = FALSE
+ AND (ts_lead - ts) > 0
+ AND state = 'STARTED'
+ AND lead_state IN ('FINISHED', 'SCHEDULED');
+
+CREATE PERFETTO TABLE _charging_screen_states AS
+SELECT
+ ROW_NUMBER() OVER () AS id,
+ ii.ts,
+ ii.dur,
+ c.charging_state,
+ s.screen_state
+FROM _interval_intersect!(
+ (android_charging_states, _screen_states),
+ ()
+) ii
+JOIN android_charging_states c ON c.id = ii.id_0
+JOIN _screen_states s ON s.id = ii.id_1;
+
+-- This table returns constraint changes that a
+-- job will go through in a single trace.
+--
+-- Values in this table are derived from the the `ScheduledJobStateChanged`
+-- atom. This table differs from the
+-- `android_job_scheduler_with_screen_charging_states` in this module
+-- (`android.job_scheduler_states`) by only having job constraint information.
+--
+-- See documentation for the `android_job_scheduler_with_screen_charging_states`
+-- for how tables in this module differ from `android_job_scheduler_events`
+-- table in the `android.job_scheduler` module and how to populate this table.
+CREATE PERFETTO TABLE android_job_scheduler_states(
+ -- Unique identifier for row.
+ id INT,
+ -- Timestamp of job state slice.
+ ts INT,
+ -- Duration of job state slice.
+ dur INT,
+ -- Id of the slice.
+ slice_id INT,
+ -- Name of the job (as named by the app).
+ job_name STRING,
+ -- Uid associated with job.
+ uid INT,
+ -- Id of job (assigned by app for T- builds and system generated in U+
+ -- builds).
+ job_id INT,
+ -- Package that the job belongs (ex: associated app).
+ package_name STRING,
+ -- Namespace of job.
+ job_namespace STRING,
+ -- Priority at which JobScheduler ran the job.
+ effective_priority INT,
+ -- True if app requested job should run when the device battery is not low.
+ has_battery_not_low_constraint BOOL,
+ -- True if app requested job should run when the device is charging.
+ has_charging_constraint BOOL,
+ -- True if app requested job should run when device has connectivity.
+ has_connectivity_constraint BOOL,
+ -- True if app requested job should run when there is a content trigger.
+ has_content_trigger_constraint BOOL,
+ -- True if app requested there is a deadline by which the job should run.
+ has_deadline_constraint BOOL,
+ -- True if app requested job should run when device is idle.
+ has_idle_constraint BOOL,
+ -- True if app requested job should run when device storage is not low.
+ has_storage_not_low_constraint BOOL,
+ -- True if app requested job has a timing delay.
+ has_timing_delay_constraint BOOL,
+ -- True if app requested job should run within hours of app launch.
+ is_prefetch BOOL,
+ -- True if app requested that the job is run as an expedited job.
+ is_requested_expedited_job BOOL,
+ -- The job is run as an expedited job.
+ is_running_as_expedited_job BOOL,
+ -- Number of previous attempts at running job.
+ num_previous_attempts INT,
+ -- The requested priority at which the job should run.
+ requested_priority INT,
+ -- The job's standby bucket (one of: Active, Working Set, Frequent, Rare,
+ -- Never, Restricted, Exempt).
+ standby_bucket STRING,
+ -- Job should run in intervals.
+ is_periodic BOOL,
+ -- True if the job should run as a flex job.
+ has_flex_constraint BOOL,
+ -- True is app has requested that a job be run as a user initiated job.
+ is_requested_as_user_initiated_job BOOL,
+ -- True if job is running as a user initiated job.
+ is_running_as_user_initiated_job BOOL,
+ -- Deadline that job has requested and valid if has_deadline_constraint is
+ -- true.
+ deadline_ms INT,
+ -- The latency in ms between when a job is scheduled and when it actually
+ -- starts.
+ job_start_latency_ms INT,
+ -- Number of uncompleted job work items.
+ num_uncompleted_work_items INT,
+ -- Process state of the process responsible for running the job.
+ proc_state STRING,
+ -- Internal stop reason for a job.
+ internal_stop_reason STRING,
+ -- Public stop reason for a job.
+ public_stop_reason STRING
+
+) AS
+SELECT
+ ROW_NUMBER() OVER (ORDER BY ts) AS id,
+ ts,
+ dur,
+ slice_id,
+ job_name,
+ uid,
+ job_id,
+ package_name,
+ job_namespace,
+ effective_priority,
+ has_battery_not_low_constraint,
+ has_charging_constraint,
+ has_connectivity_constraint,
+ has_content_trigger_constraint,
+ has_deadline_constraint,
+ has_idle_constraint,
+ has_storage_not_low_constraint,
+ has_timing_delay_constraint,
+ is_prefetch,
+ is_requested_expedited_job,
+ is_running_as_expedited_job,
+ num_previous_attempts,
+ requested_priority,
+ standby_bucket,
+ is_periodic,
+ has_flex_constraint,
+ is_requested_as_user_initiated_job,
+ is_running_as_user_initiated_job,
+ deadline_ms,
+ job_start_latency_ms,
+ num_uncompleted_work_items,
+ proc_state,
+ lead_internal_stop_reason AS internal_stop_reason,
+ lead_public_stop_reason AS public_stop_reason
+FROM _job_started;
+
+-- This table returns the constraint, charging,
+-- and screen state changes that a job will go through
+-- in a single trace.
+--
+-- Values from this table are derived from
+-- the `ScheduledJobStateChanged` atom. This differs from the
+-- `android_job_scheduler_events` table in the `android.job_scheduler` module
+-- which is derived from ATrace the system server category
+-- (`atrace_categories: "ss"`).
+--
+-- This also differs from the `android_job_scheduler_states` in this module
+-- (`android.job_scheduler_states`) by providing charging and screen state
+-- changes.
+--
+-- To populate this table, enable the Statsd Tracing Config with the
+-- ATOM_SCHEDULED_JOB_STATE_CHANGED push atom id.
+-- https://perfetto.dev/docs/reference/trace-config-proto#StatsdTracingConfig
+--
+-- This table is preferred over `android_job_scheduler_events`
+-- since it contains more information and should be used whenever
+-- `ATOM_SCHEDULED_JOB_STATE_CHANGED` is available in a trace.
+CREATE PERFETTO TABLE android_job_scheduler_with_screen_charging_states(
+ -- Timestamp of job.
+ ts INT,
+ -- Duration of slice in ns.
+ dur INT,
+ -- Id of the slice.
+ slice_id INT,
+ -- Name of the job (as named by the app).
+ job_name STRING,
+ -- Id of job (assigned by app for T- builds and system generated in U+
+ -- builds).
+ job_id INT,
+ -- Uid associated with job.
+ uid INT,
+ -- Duration of entire job in ns.
+ job_dur INT,
+ -- Package that the job belongs (ex: associated app).
+ package_name STRING,
+ -- Namespace of job.
+ job_namespace STRING,
+ -- Device charging state during job (one of: Charging, Discharging, Not charging,
+ -- Full, Unknown).
+ charging_state STRING,
+ -- Device screen state during job (one of: Screen off, Screen on, Always-on display
+ -- (doze), Unknown).
+ screen_state STRING,
+ -- Priority at which JobScheduler ran the job.
+ effective_priority INT,
+ -- True if app requested job should run when the device battery is not low.
+ has_battery_not_low_constraint BOOL,
+ -- True if app requested job should run when the device is charging.
+ has_charging_constraint BOOL,
+ -- True if app requested job should run when device has connectivity.
+ has_connectivity_constraint BOOL,
+ -- True if app requested job should run when there is a content trigger.
+ has_content_trigger_constraint BOOL,
+ -- True if app requested there is a deadline by which the job should run.
+ has_deadline_constraint BOOL,
+ -- True if app requested job should run when device is idle.
+ has_idle_constraint BOOL,
+ -- True if app requested job should run when device storage is not low.
+ has_storage_not_low_constraint BOOL,
+ -- True if app requested job has a timing delay.
+ has_timing_delay_constraint BOOL,
+ -- True if app requested job should run within hours of app launch.
+ is_prefetch BOOL,
+ -- True if app requested that the job is run as an expedited job.
+ is_requested_expedited_job BOOL,
+ -- The job is run as an expedited job.
+ is_running_as_expedited_job BOOL,
+ -- Number of previous attempts at running job.
+ num_previous_attempts INT,
+ -- The requested priority at which the job should run.
+ requested_priority INT,
+ -- The job's standby bucket (one of: Active, Working Set, Frequent, Rare,
+ -- Never, Restricted, Exempt).
+ standby_bucket STRING,
+ -- Job should run in intervals.
+ is_periodic BOOL,
+ -- True if the job should run as a flex job.
+ has_flex_constraint BOOL,
+ -- True is app has requested that a job be run as a user initiated job.
+ is_requested_as_user_initiated_job BOOL,
+ -- True if job is running as a user initiated job.
+ is_running_as_user_initiated_job BOOL,
+ -- Deadline that job has requested and valid if has_deadline_constraint is
+ -- true.
+ deadline_ms INT,
+ -- The latency in ms between when a job is scheduled and when it actually
+ -- starts.
+ job_start_latency_ms INT,
+ -- Number of uncompleted job work items.
+ num_uncompleted_work_items INT,
+ -- Process state of the process responsible for running the job.
+ proc_state STRING,
+ -- Internal stop reason for a job.
+ internal_stop_reason STRING,
+ -- Public stop reason for a job.
+ public_stop_reason STRING
+) AS
+SELECT
+ ii.ts,
+ ii.dur,
+ js.slice_id,
+ js.job_name || '_' || js.job_id AS job_name,
+ js.uid,
+ js.job_id,
+ js.dur AS job_dur,
+ js.package_name,
+ js.job_namespace,
+ c.charging_state,
+ c.screen_state,
+ js.effective_priority,
+ js.has_battery_not_low_constraint,
+ js.has_charging_constraint,
+ js.has_connectivity_constraint,
+ js.has_content_trigger_constraint,
+ js.has_deadline_constraint,
+ js.has_idle_constraint,
+ js.has_storage_not_low_constraint,
+ js.has_timing_delay_constraint,
+ js.is_prefetch,
+ js.is_requested_expedited_job,
+ js.is_running_as_expedited_job,
+ js.num_previous_attempts,
+ js.requested_priority,
+ js.standby_bucket,
+ js.is_periodic,
+ js.has_flex_constraint,
+ js.is_requested_as_user_initiated_job,
+ js.is_running_as_user_initiated_job,
+ js.deadline_ms,
+ js.job_start_latency_ms,
+ js.num_uncompleted_work_items,
+ js.proc_state,
+ js.internal_stop_reason,
+ js.public_stop_reason
+ FROM _interval_intersect!(
+ (_charging_screen_states,
+ android_job_scheduler_states),
+ ()
+ ) ii
+ JOIN _charging_screen_states c ON c.id = ii.id_0
+ JOIN android_job_scheduler_states js ON js.id = ii.id_1;
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/memory/dmabuf.sql b/src/trace_processor/perfetto_sql/stdlib/android/memory/dmabuf.sql
index 1576e9c..afc19b4 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/memory/dmabuf.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/memory/dmabuf.sql
@@ -13,9 +13,6 @@
-- See the License for the specific language governing permissions and
-- limitations under the License.
-INCLUDE PERFETTO MODULE android.binder;
-INCLUDE PERFETTO MODULE slices.with_context;
-
-- Raw ftrace events
CREATE PERFETTO TABLE _raw_dmabuf_events AS
SELECT
@@ -29,14 +26,22 @@
-- gralloc binder reply slices
CREATE PERFETTO TABLE _gralloc_binders AS
+WITH gralloc_threads AS (
+ SELECT utid
+ FROM process JOIN thread USING (upid)
+ WHERE process.name GLOB '/vendor/bin/hw/android.hardware.graphics.allocator*'
+)
SELECT
- id AS gralloc_binder_reply_id,
- utid,
- ts,
- dur
-FROM thread_slice
-WHERE process_name GLOB '/vendor/bin/hw/android.hardware.graphics.allocator*'
-AND name = 'binder reply';
+ flow.slice_out AS client_slice_id,
+ gralloc_slice.ts,
+ gralloc_slice.dur,
+ thread_track.utid
+FROM slice gralloc_slice
+JOIN thread_track ON gralloc_slice.track_id = thread_track.id
+JOIN gralloc_threads USING (utid)
+JOIN flow ON gralloc_slice.id = flow.slice_in
+WHERE gralloc_slice.name = 'binder reply'
+;
-- Match gralloc thread allocations to inbound binders
CREATE PERFETTO TABLE _attributed_dmabufs AS
@@ -44,10 +49,10 @@
r.inode,
r.ts,
r.buf_size,
- IFNULL(b.client_utid, r.utid) AS attr_utid
+ IFNULL(client_thread.utid, r.utid) AS attr_utid
FROM _raw_dmabuf_events r
LEFT JOIN _gralloc_binders gb ON r.utid = gb.utid AND r.ts BETWEEN gb.ts AND gb.ts + gb.dur
-LEFT JOIN android_binder_txns b ON gb.gralloc_binder_reply_id = b.binder_reply_id
+LEFT JOIN thread_track client_thread ON gb.client_slice_id = client_thread.id
ORDER BY r.inode, r.ts;
CREATE PERFETTO FUNCTION _alloc_source(is_alloc BOOL, inode INT, ts INT)
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/BUILD.gn
index 2c86bfe..4ecaeb0 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/BUILD.gn
@@ -16,6 +16,7 @@
perfetto_sql_source_set("heap_graph") {
sources = [
+ "class_summary_tree.sql",
"class_tree.sql",
"dominator_class_tree.sql",
"dominator_tree.sql",
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/class_summary_tree.sql b/src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/class_summary_tree.sql
new file mode 100644
index 0000000..9c9d5bf
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/class_summary_tree.sql
@@ -0,0 +1,98 @@
+--
+-- 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 android.memory.heap_graph.class_tree;
+INCLUDE PERFETTO MODULE graphs.scan;
+
+CREATE PERFETTO TABLE _heap_graph_class_tree_cumulatives AS
+SELECT *
+FROM _graph_aggregating_scan!(
+ (
+ SELECT id AS source_node_id, parent_id AS dest_node_id
+ FROM _heap_graph_class_tree
+ WHERE parent_id IS NOT NULL
+ ),
+ (
+ SELECT
+ p.id,
+ p.self_count AS cumulative_count,
+ p.self_size AS cumulative_size
+ FROM _heap_graph_class_tree p
+ LEFT JOIN _heap_graph_class_tree c ON c.parent_id = p.id
+ WHERE c.id IS NULL
+ ),
+ (cumulative_count, cumulative_size),
+ (
+ WITH agg AS (
+ SELECT
+ t.id,
+ SUM(t.cumulative_count) AS child_count,
+ SUM(t.cumulative_size) AS child_size
+ FROM $table t
+ GROUP BY t.id
+ )
+ SELECT
+ a.id,
+ a.child_count + r.self_count as cumulative_count,
+ a.child_size + r.self_size as cumulative_size
+ FROM agg a
+ JOIN _heap_graph_class_tree r USING (id)
+ )
+) a
+ORDER BY id;
+
+-- Table containing all the Android heap graphs in the trace converted to a
+-- shortest-path tree and then aggregated by class name.
+--
+-- This table contains a "flamegraph-like" representation of the contents of the
+-- heap graph.
+CREATE PERFETTO TABLE android_heap_graph_class_summary_tree(
+ -- The timestamp the heap graph was dumped at.
+ graph_sample_ts INT,
+ -- The upid of the process.
+ upid INT,
+ -- The id of the node in the class tree.
+ id INT,
+ -- The parent id of the node in the class tree or NULL if this is the root.
+ parent_id INT,
+ -- The name of the class.
+ name STRING,
+ -- A string describing the type of Java root if this node is a root or NULL
+ -- if this node is not a root.
+ root_type STRING,
+ -- The count of objects with the same class name and the same path to the
+ -- root.
+ self_count INT,
+ -- The size of objects with the same class name and the same path to the
+ -- root.
+ self_size INT,
+ -- The sum of `self_count` of this node and all descendants of this node.
+ cumulative_count INT,
+ -- The sum of `self_size` of this node and all descendants of this node.
+ cumulative_size INT
+) AS
+SELECT
+ t.graph_sample_ts,
+ t.upid,
+ t.id,
+ t.parent_id,
+ t.name,
+ t.root_type,
+ t.self_count,
+ t.self_size,
+ c.cumulative_count,
+ c.cumulative_size
+FROM _heap_graph_class_tree t
+JOIN _heap_graph_class_tree_cumulatives c USING (id);
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/memory/heap_profile/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/android/memory/heap_profile/BUILD.gn
index daaf9e0..aa317e4 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/memory/heap_profile/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/android/memory/heap_profile/BUILD.gn
@@ -15,5 +15,8 @@
import("../../../../../../../gn/perfetto_sql.gni")
perfetto_sql_source_set("heap_profile") {
- sources = [ "callstacks.sql" ]
+ sources = [
+ "callstacks.sql",
+ "summary_tree.sql",
+ ]
}
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/memory/heap_profile/summary_tree.sql b/src/trace_processor/perfetto_sql/stdlib/android/memory/heap_profile/summary_tree.sql
new file mode 100644
index 0000000..c9e89c3
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/android/memory/heap_profile/summary_tree.sql
@@ -0,0 +1,123 @@
+
+--
+-- 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 callstacks.stack_profile;
+
+CREATE PERFETTO TABLE _android_heap_profile_raw_callstacks AS
+WITH metrics AS MATERIALIZED (
+ SELECT
+ callsite_id,
+ SUM(size) AS self_size,
+ SUM(MAX(size, 0)) AS self_alloc_size
+ FROM heap_profile_allocation
+ GROUP BY callsite_id
+)
+SELECT
+ c.id,
+ c.parent_id,
+ c.name,
+ c.mapping_name,
+ c.source_file,
+ c.line_number,
+ IFNULL(m.self_size, 0) AS self_size,
+ IFNULL(m.self_alloc_size, 0) AS self_alloc_size
+FROM _callstacks_for_stack_profile_samples!(metrics) c
+LEFT JOIN metrics m USING (callsite_id);
+
+CREATE PERFETTO TABLE _android_heap_profile_cumulatives AS
+SELECT a.*
+FROM _graph_aggregating_scan!(
+ (
+ SELECT id AS source_node_id, parent_id AS dest_node_id
+ FROM _android_heap_profile_raw_callstacks
+ WHERE parent_id IS NOT NULL
+ ),
+ (
+ SELECT
+ p.id,
+ p.self_size AS cumulative_size,
+ p.self_alloc_size AS cumulative_alloc_size
+ FROM _android_heap_profile_raw_callstacks p
+ LEFT JOIN _android_heap_profile_raw_callstacks c ON c.parent_id = p.id
+ WHERE c.id IS NULL
+ ),
+ (cumulative_size, cumulative_alloc_size),
+ (
+ WITH agg AS (
+ SELECT
+ t.id,
+ SUM(t.cumulative_size) AS child_size,
+ SUM(t.cumulative_alloc_size) AS child_alloc_size
+ FROM $table t
+ GROUP BY t.id
+ )
+ SELECT
+ a.id,
+ a.child_size + r.self_size as cumulative_size,
+ a.child_alloc_size + r.self_alloc_size AS cumulative_alloc_size
+ FROM agg a
+ JOIN _android_heap_profile_raw_callstacks r USING (id)
+ )
+) a;
+
+-- Table summarising the amount of memory allocated by each
+-- callstack as seen by Android native heap profiling (i.e.
+-- profiling information collected by heapprofd).
+--
+-- Note: this table collapses data from all processes together
+-- into a single table.
+CREATE PERFETTO TABLE android_heap_profile_summary_tree(
+ -- The id of the callstack. A callstack in this context
+ -- is a unique set of frames up to the root.
+ id INT,
+ -- The id of the parent callstack for this callstack.
+ parent_id INT,
+ -- The function name of the frame for this callstack.
+ name STRING,
+ -- The name of the mapping containing the frame. This
+ -- can be a native binary, library, JAR or APK.
+ mapping_name STRING,
+ -- The name of the file containing the function.
+ source_file STRING,
+ -- The line number in the file the function is located at.
+ line_number INT,
+ -- The amount of memory allocated and *not freed* with this
+ -- function as the leaf frame.
+ self_size INT,
+ -- The amount of memory allocated and *not freed* with this
+ -- function appearing anywhere on the callstack.
+ cumulative_size INT,
+ -- The amount of memory allocated with this function as the leaf
+ -- frame. This may include memory which was later freed.
+ self_alloc_size INT,
+ -- The amount of memory allocated with this function appearing
+ -- anywhere on the callstack. This may include memory which was
+ -- later freed.
+ cumulative_alloc_size INT
+) AS
+SELECT
+ id,
+ parent_id,
+ name,
+ mapping_name,
+ source_file,
+ line_number,
+ self_size,
+ cumulative_size,
+ self_alloc_size,
+ cumulative_alloc_size
+FROM _android_heap_profile_raw_callstacks r
+JOIN _android_heap_profile_cumulatives a USING (id);
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/monitor_contention.sql b/src/trace_processor/perfetto_sql/stdlib/android/monitor_contention.sql
index c7ad3bf..d1d5b02 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/monitor_contention.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/monitor_contention.sql
@@ -353,44 +353,40 @@
SELECT utid AS blocking_utid, ts, dur, state, blocked_function
FROM thread_state;
--- Contains the span join of the first waiters in the |android_monitor_contention_chain| with their
--- blocking_thread thread state.
-
--- Note that we only span join the duration where the lock was actually held and contended.
--- This can be less than the duration the lock was 'waited on' when a different waiter acquired the
--- lock earlier than the first waiter.
---
--- @column parent_id Id of slice blocking the blocking_thread.
--- @column blocking_method Name of the method holding the lock.
--- @column blocked_methhod Name of the method trying to acquire the lock.
--- @column short_blocking_method Blocking_method without arguments and return types.
--- @column short_blocked_method Blocked_method without arguments and return types.
--- @column blocking_src File location of blocking_method in form <filename:linenumber>.
--- @column blocked_src File location of blocked_method in form <filename:linenumber>.
--- @column waiter_count Zero indexed number of threads trying to acquire the lock.
--- @column blocking_utid Utid of thread holding the lock.
--- @column blocking_thread_name Thread name of thread holding the lock.
--- @column upid Upid of process experiencing lock contention.
--- @column process_name Process name of process experiencing lock contention.
--- @column id Slice id of lock contention.
--- @column ts Timestamp of lock contention start.
--- @column dur Wall clock duration of lock contention.
--- @column monotonic_dur Monotonic clock duration of lock contention.
--- @column track_id Thread track id of blocked thread.
--- @column is_blocked_main_thread Whether the blocked thread is the main thread.
--- @column is_blocking_main_thread Whether the blocking thread is the main thread.
--- @column binder_reply_id Slice id of binder reply slice if lock contention was part of a binder txn.
--- @column binder_reply_ts Timestamp of binder reply slice if lock contention was part of a binder txn.
--- @column binder_reply_tid Tid of binder reply slice if lock contention was part of a binder txn.
--- @column blocking_utid Utid of the blocking |thread_state|.
--- @column ts Timestamp of the blocking |thread_state|.
--- @column state Thread state of the blocking thread.
--- @column blocked_function Blocked kernel function of the blocking thread.
-CREATE VIRTUAL TABLE android_monitor_contention_chain_thread_state
+CREATE VIRTUAL TABLE _android_monitor_contention_chain_thread_state
USING
SPAN_JOIN(_first_blocked_contention PARTITIONED blocking_utid,
_blocking_thread_state PARTITIONED blocking_utid);
+-- Contains the span join of the first waiters in the |android_monitor_contention_chain| with their
+-- blocking_thread thread state.
+--
+-- Note that we only span join the duration where the lock was actually held and contended.
+-- This can be less than the duration the lock was 'waited on' when a different waiter acquired the
+-- lock earlier than the first waiter.
+CREATE PERFETTO TABLE android_monitor_contention_chain_thread_state(
+-- Slice id of lock contention.
+id INT,
+-- Timestamp of lock contention start.
+ts INT,
+-- Wall clock duration of lock contention.
+dur INT,
+-- Utid of the blocking |thread_state|.
+blocking_utid INT,
+-- Blocked kernel function of the blocking thread.
+blocked_function STRING,
+-- Thread state of the blocking thread.
+state STRING
+) AS
+SELECT
+ id,
+ ts,
+ dur,
+ blocking_utid,
+ blocked_function,
+ state
+FROM _android_monitor_contention_chain_thread_state;
+
-- Aggregated thread_states on the 'blocking thread', the thread holding the lock.
-- This builds on the data from |android_monitor_contention_chain| and
-- for each contention slice, it returns the aggregated sum of all the thread states on the
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/startup/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/android/startup/BUILD.gn
index c495f08..bf7a41a 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/startup/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/android/startup/BUILD.gn
@@ -16,6 +16,7 @@
perfetto_sql_source_set("startup") {
sources = [
+ "startup_breakdowns.sql",
"startup_events.sql",
"startups.sql",
"startups_maxsdk28.sql",
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/startup/startup_breakdowns.sql b/src/trace_processor/perfetto_sql/stdlib/android/startup/startup_breakdowns.sql
new file mode 100644
index 0000000..c7af07b
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/android/startup/startup_breakdowns.sql
@@ -0,0 +1,188 @@
+--
+-- 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 android.startup.startups;
+INCLUDE PERFETTO MODULE intervals.overlap;
+INCLUDE PERFETTO MODULE slices.hierarchy;
+INCLUDE PERFETTO MODULE slices.with_context;
+
+-- Maps slice names with common prefixes to a static string key.
+-- Returns NULL if there's no mapping.
+CREATE PERFETTO FUNCTION _normalize_android_string(name STRING)
+RETURNS STRING
+AS
+SELECT
+ CASE
+ WHEN $name = 'mm_vmscan_direct_reclaim' THEN 'kernel_memory_reclaim'
+ WHEN $name GLOB 'GC: Wait For*' THEN 'userspace_memory_reclaim'
+ WHEN ($name GLOB 'monitor contention*' OR $name GLOB 'Lock contention on a monitor lock*')
+ THEN 'monitor_contention'
+ WHEN $name GLOB 'Lock contention*' THEN 'art_lock_contention'
+ WHEN ($name = 'binder transaction' OR $name = 'binder reply') THEN 'binder'
+ WHEN $name = 'Contending for pthread mutex' THEN 'mutex_contention'
+ WHEN $name GLOB 'dlopen*' THEN 'dlopen'
+ WHEN $name GLOB 'VerifyClass*' THEN 'verify_class'
+ WHEN $name = 'inflate' THEN 'inflate'
+ WHEN $name GLOB 'Choreographer#doFrame*' THEN 'choreographer_do_frame'
+ WHEN $name GLOB 'OpenDexFilesFromOat*' THEN 'open_dex_files_from_oat'
+ WHEN $name = 'ResourcesManager#getResources' THEN 'resources_manager_get_resources'
+ WHEN $name = 'bindApplication' THEN 'bind_application'
+ WHEN $name = 'activityStart' THEN 'activity_start'
+ WHEN $name = 'activityResume' THEN 'activity_resume'
+ WHEN $name = 'activityRestart' THEN 'activity_restart'
+ WHEN $name = 'clientTransactionExecuted' THEN 'client_transaction_executed'
+ ELSE NULL
+ END name;
+
+-- Derives a startup reason from a slice name and some thread_state columns.
+CREATE PERFETTO FUNCTION _startup_breakdown_reason(
+ name STRING,
+ state STRING,
+ io_wait INT,
+ irq_context INT)
+RETURNS STRING
+AS
+SELECT
+ CASE
+ WHEN $io_wait = 1 THEN 'io'
+ WHEN $name IS NOT NULL THEN $name
+ WHEN $irq_context = 1 THEN 'irq'
+ ELSE $state
+ END name;
+
+-- List of startups with unique ids for each possible upid. The existing
+-- startup_ids are not necessarily unique (because of multiuser).
+CREATE PERFETTO TABLE _startup_root_slices
+AS
+SELECT
+ (SELECT MAX(id) FROM slice) + row_number() OVER () AS id,
+ android_startups.dur AS dur,
+ android_startups.ts AS ts,
+ android_startups.startup_id,
+ android_startups.startup_type,
+ process.name AS process_name,
+ thread.utid AS utid
+FROM android_startup_processes startup
+JOIN android_startups
+ USING (startup_id)
+JOIN thread
+ ON thread.upid = process.upid AND thread.is_main_thread
+JOIN process
+ ON process.upid = startup.upid
+WHERE android_startups.dur > 0
+ORDER BY ts;
+
+-- All relevant startup slices normalized with _normalize_android_string.
+CREATE PERFETTO TABLE _startup_normalized_slices
+AS
+WITH
+ relevant_startup_slices AS (
+ SELECT slice.*
+ FROM thread_slice slice
+ JOIN _startup_root_slices startup
+ ON
+ slice.utid = startup.utid
+ -- Inline the logic to check whether startup intervals overlap
+ -- with main thread slices. This is to improve performance until
+ -- interval_intersect doesn't require a JOIN.
+ -- TODO(zezeozue): Replace with interval intersect when JOINs are
+ -- not required.
+ AND MAX(slice.ts, startup.ts) < MIN(slice.ts + slice.dur, startup.ts + startup.dur)
+ )
+SELECT p.id, p.parent_id, p.depth, p.name, thread_slice.ts, thread_slice.dur, thread_slice.utid
+FROM
+ _slice_remove_nulls_and_reparent
+ !(
+ (
+ SELECT id, parent_id, depth, _normalize_android_string(name) AS name
+ FROM relevant_startup_slices
+ WHERE dur > 0
+ ),
+ name)
+ p
+JOIN thread_slice
+ USING (id);
+
+-- Subset of _startup_normalized_slices that occurred during any app startups on the main thread.
+-- Their timestamps and durations are chopped to fit within the respective app startup duration.
+CREATE PERFETTO TABLE _startup_slices_breakdown
+AS
+SELECT *
+FROM _intervals_merge_root_and_children_by_intersection !(_startup_root_slices, _startup_normalized_slices, utid);
+
+-- Flattened slice version of _startup_slices_breakdown. This selects the leaf slice at every region
+-- of the slice stack.
+CREATE PERFETTO TABLE _startup_flat_slices_breakdown
+AS
+SELECT i.ts, i.dur, i.root_id, s.id AS slice_id, s.name FROM _intervals_flatten !(_startup_slices_breakdown) i
+JOIN _startup_normalized_slices s USING (id);
+
+-- Subset of thread_states that occurred during any app startups on the main thread.
+CREATE PERFETTO TABLE _startup_thread_states_breakdown
+AS
+SELECT i.ts, i.dur, i.root_id, t.id AS thread_state_id, t.state, t.io_wait, t.irq_context
+ FROM _intervals_merge_root_and_children_by_intersection!(_startup_root_slices,
+ (SELECT *, NULL AS parent_id FROM thread_state),
+ utid) i
+JOIN thread_state t USING(id);
+
+-- Intersection of _startup_flat_slices_breakdown and _startup_thread_states_breakdown.
+-- A left intersection is used since some parts of the slice stack may not have any slices
+-- but will have thread states.
+CREATE VIRTUAL TABLE _startup_thread_states_and_slices_breakdown_sp
+USING
+ SPAN_LEFT_JOIN(
+ _startup_thread_states_breakdown PARTITIONED root_id,
+ _startup_flat_slices_breakdown PARTITIONED root_id);
+
+-- Blended thread state and slice breakdown blocking app startups.
+--
+-- Each row blames a unique period during an app startup with a reason
+-- derived from the slices and thread states on the main thread.
+--
+-- Some helpful events to enables are binder transactions, ART, am and view.
+CREATE PERFETTO TABLE android_startup_opinionated_breakdown(
+ -- Startup id. Alias of `slice.id`
+ startup_id INT,
+ -- Id of relevant slice blocking startup. Alias of `slice.id`.
+ slice_id INT,
+ -- Id of thread_state blocking startup. Alias of `thread_state.id`.
+ thread_state_id INT,
+ -- Timestamp of an exclusive interval during the app startup with a single latency reason.
+ ts INT,
+ -- Duration of an exclusive interval during the app startup with a single latency reason.
+ dur INT,
+ -- Cause of delay during an exclusive interval of the app startup.
+ reason STRING
+)
+AS
+SELECT b.ts, b.dur, startup.startup_id, b.slice_id, b.thread_state_id, _startup_breakdown_reason(name, state, io_wait, irq_context) AS reason
+FROM _startup_thread_states_and_slices_breakdown_sp b
+JOIN _startup_root_slices startup ON startup.id = b.root_id
+UNION ALL
+-- Augment the existing startup breakdown with an artificial slice accounting for
+-- any launch delays before the app starts handling startup on its main thread
+SELECT
+ _startup_root_slices.ts,
+ MIN(_startup_thread_states_breakdown.ts) - _startup_root_slices.ts AS dur,
+ startup_id,
+ NULL AS slice_id,
+ NULL AS thread_state_id,
+ 'launch_delay' AS reason
+FROM _startup_thread_states_breakdown
+JOIN _startup_root_slices
+ ON _startup_root_slices.id = root_id
+GROUP BY root_id
+HAVING MIN(_startup_thread_states_breakdown.ts) - _startup_root_slices.ts > 0;
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/startup/startups.sql b/src/trace_processor/perfetto_sql/stdlib/android/startup/startups.sql
index a715f52..2bba728 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/startup/startups.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/startup/startups.sql
@@ -96,6 +96,8 @@
startup_id INT,
-- Upid of process on which activity started.
upid INT,
+ -- Pid of process on which activity started.
+ pid INT,
-- Type of the startup.
startup_type STRING
) AS
@@ -105,6 +107,7 @@
SELECT
startup_id,
upid,
+ pid,
CASE
-- type parsed from platform event takes precedence if available
WHEN startup_type IS NOT NULL THEN startup_type
@@ -118,6 +121,7 @@
l.startup_id,
l.startup_type,
p.upid,
+ p.pid,
_startup_indicator_slice_count(l.ts, l.ts_end, t.utid, 'bindApplication') AS bind_app,
_startup_indicator_slice_count(l.ts, l.ts_end, t.utid, 'activityStart') AS a_start,
_startup_indicator_slice_count(l.ts, l.ts_end, t.utid, 'activityResume') AS a_resume
@@ -153,8 +157,12 @@
dur INT,
-- Upid of process involved in startup.
upid INT,
+ -- Pid if process involved in startup.
+ pid INT,
-- Utid of the thread.
utid INT,
+ -- Tid of the thread.
+ tid INT,
-- Name of the thread.
thread_name STRING,
-- Thread is a main thread.
@@ -165,7 +173,9 @@
startups.ts,
startups.dur,
android_startup_processes.upid,
+ android_startup_processes.pid,
thread.utid,
+ thread.tid,
thread.name AS thread_name,
thread.is_main_thread AS is_main_thread
FROM android_startups startups
@@ -189,6 +199,8 @@
startup_id INT,
-- UTID of thread with slice.
utid INT,
+ --Tid of thread.
+ tid INT,
-- Name of thread.
thread_name STRING,
-- Whether it is main thread.
@@ -209,6 +221,7 @@
st.ts + st.dur AS startup_ts_end,
st.startup_id,
st.utid,
+ st.tid,
st.thread_name,
st.is_main_thread,
slice.arg_set_id,
@@ -238,10 +251,12 @@
slice_dur INT,
-- Name of the thread with the slice.
thread_name STRING,
+ -- Tid of the thread with the slice.
+ tid INT,
-- Arg set id.
arg_set_id INT
) AS
-SELECT slice_id, slice_name, slice_ts, slice_dur, thread_name, arg_set_id
+SELECT slice_id, slice_name, slice_ts, slice_dur, thread_name, tid, arg_set_id
FROM android_thread_slices_for_all_startups
WHERE startup_id = $startup_id AND slice_name GLOB $slice_name;
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/startup/startups_minsdk33.sql b/src/trace_processor/perfetto_sql/stdlib/android/startup/startups_minsdk33.sql
index 2378473..b96b53a 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/startup/startups_minsdk33.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/startup/startups_minsdk33.sql
@@ -45,6 +45,7 @@
-- Originally completed was unqualified, but at some point we introduced
-- the startup type as well
AND name GLOB 'launchingActivity#*:completed*:*'
+ AND NOT name GLOB '*:completed-same-process:*'
)
GROUP BY 1, 2, 3;
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/suspend.sql b/src/trace_processor/perfetto_sql/stdlib/android/suspend.sql
index 1551ea6..8d2c88c 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/suspend.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/suspend.sql
@@ -49,11 +49,11 @@
AND NOT EXISTS(SELECT * FROM suspend_slice_from_minimal)
),
awake_slice AS (
- -- If we don't have any rows, use the trace bounds.
+ -- If we don't have any rows, use the trace bounds if bounds are defined.
SELECT
trace_start() AS ts,
trace_dur() AS dur
- WHERE (SELECT COUNT(*) FROM suspend_slice) = 0
+ WHERE (SELECT COUNT(*) FROM suspend_slice) = 0 AND dur > 0
UNION ALL
-- If we do have rows, create one slice from the trace start to the first suspend.
SELECT
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/winscope/inputmethod.sql b/src/trace_processor/perfetto_sql/stdlib/android/winscope/inputmethod.sql
index 15a4a8e..afd234b 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/winscope/inputmethod.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/winscope/inputmethod.sql
@@ -20,12 +20,18 @@
-- Timestamp when the dump was triggered
ts INT,
-- Extra args parsed from the proto message
- arg_set_id INT
+ arg_set_id INT,
+ -- Raw proto message encoded in base64
+ base64_proto STRING,
+ -- String id for raw proto message
+ base64_proto_id INT
) AS
SELECT
id,
ts,
- arg_set_id
+ arg_set_id,
+ base64_proto,
+ base64_proto_id
FROM __intrinsic_inputmethod_clients;
-- Android inputmethod manager service state dumps (from android.inputmethod data source).
@@ -35,12 +41,18 @@
-- Timestamp when the dump was triggered
ts INT,
-- Extra args parsed from the proto message
- arg_set_id INT
+ arg_set_id INT,
+ -- Raw proto message encoded in base64
+ base64_proto STRING,
+ -- String id for raw proto message
+ base64_proto_id INT
) AS
SELECT
id,
ts,
- arg_set_id
+ arg_set_id,
+ base64_proto,
+ base64_proto_id
FROM __intrinsic_inputmethod_manager_service;
-- Android inputmethod service state dumps (from android.inputmethod data source).
@@ -50,10 +62,16 @@
-- Timestamp when the dump was triggered
ts INT,
-- Extra args parsed from the proto message
- arg_set_id INT
+ arg_set_id INT,
+ -- Raw proto message encoded in base64
+ base64_proto STRING,
+ -- String id for raw proto message
+ base64_proto_id INT
) AS
SELECT
id,
ts,
- arg_set_id
+ arg_set_id,
+ base64_proto,
+ base64_proto_id
FROM __intrinsic_inputmethod_service;
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/winscope/viewcapture.sql b/src/trace_processor/perfetto_sql/stdlib/android/winscope/viewcapture.sql
index 77e68b5..11fb341 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/winscope/viewcapture.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/winscope/viewcapture.sql
@@ -20,10 +20,16 @@
-- Timestamp when the snapshot was triggered
ts INT,
-- Extra args parsed from the proto message
- arg_set_id INT
+ arg_set_id INT,
+ -- Raw proto message encoded in base64
+ base64_proto STRING,
+ -- String id for raw proto message
+ base64_proto_id INT
) AS
SELECT
id,
ts,
- arg_set_id
+ arg_set_id,
+ base64_proto,
+ base64_proto_id
FROM __intrinsic_viewcapture;
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/winscope/windowmanager.sql b/src/trace_processor/perfetto_sql/stdlib/android/winscope/windowmanager.sql
index e602125..f8d42da 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/winscope/windowmanager.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/winscope/windowmanager.sql
@@ -20,10 +20,16 @@
-- Timestamp when the snapshot was triggered
ts INT,
-- Extra args parsed from the proto message
- arg_set_id INT
+ arg_set_id INT,
+ -- Raw proto message encoded in base64
+ base64_proto STRING,
+ -- String id for raw proto message
+ base64_proto_id INT
) AS
SELECT
id,
ts,
- arg_set_id
+ arg_set_id,
+ base64_proto,
+ base64_proto_id
FROM __intrinsic_windowmanager;
diff --git a/src/trace_processor/perfetto_sql/stdlib/callstacks/stack_profile.sql b/src/trace_processor/perfetto_sql/stdlib/callstacks/stack_profile.sql
index 6ffa588..d8bc406 100644
--- a/src/trace_processor/perfetto_sql/stdlib/callstacks/stack_profile.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/callstacks/stack_profile.sql
@@ -114,7 +114,7 @@
JOIN stack_profile_mapping m ON f.mapping_id = m.id
);
-CREATE PERFETTO MACRO _callstacks_for_cpu_profile_stack_samples(
+CREATE PERFETTO MACRO _callstacks_for_callsites(
samples TableOrSubquery
)
RETURNS TableOrSubquery
diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/cpu_powerups.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/cpu_powerups.sql
index 8657ca5..a653903 100644
--- a/src/trace_processor/perfetto_sql/stdlib/chrome/cpu_powerups.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/chrome/cpu_powerups.sql
@@ -149,23 +149,33 @@
WHERE s.depth = 0 -- Top-level slices only.
ORDER BY ts ASC;
--- A table holding the slices that executed within the scheduler
--- slice that ran on a CPU immediately after power-up.
---
--- @column ts Timestamp of the resulting slice
--- @column dur Duration of the slice.
--- @column cpu The CPU the sched slice ran on.
--- @column utid Unique thread id for the slice.
--- @column sched_id 'id' field from the sched_slice table.
--- @column type From the sched_slice table, always 'sched_slice'.
--- @column end_state The ending state for the sched_slice
--- @column priority The kernel thread priority
--- @column slice_id Id of the top-level slice for this (sched) slice.
-CREATE VIRTUAL TABLE chrome_cpu_power_post_powerup_slice
+CREATE VIRTUAL TABLE _chrome_cpu_power_post_powerup_slice_sj
USING
SPAN_JOIN(chrome_cpu_power_first_sched_slice_after_powerup PARTITIONED utid,
_cpu_power_thread_and_toplevel_slice PARTITIONED utid);
+-- A table holding the slices that executed within the scheduler
+-- slice that ran on a CPU immediately after power-up.
+CREATE PERFETTO TABLE chrome_cpu_power_post_powerup_slice(
+-- Timestamp of the resulting slice
+ts INT,
+-- Duration of the slice.
+dur INT,
+-- The CPU the sched slice ran on.
+cpu INT,
+-- Unique thread id for the slice.
+utid INT,
+-- 'id' field from the sched_slice table.
+sched_id INT,
+-- Id of the top-level slice for this (sched) slice.
+slice_id INT,
+-- Previous power state.
+previous_power_state LONG,
+-- Id of the powerup.
+powerup_id INT
+) AS
+SELECT * FROM _chrome_cpu_power_post_powerup_slice_sj;
+
-- The first top-level slice that ran after a CPU power-up.
CREATE PERFETTO VIEW chrome_cpu_power_first_toplevel_slice_after_powerup(
-- ID of the slice in the slice table.
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 8e58912..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,7 +2,22 @@
-- 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
+-- into phases, where each subphase can happen only once.
+CREATE PERFETTO FUNCTION _descendant_slice_begin(
+ -- Id of the parent slice.
+ parent_id INT,
+ -- Name of the child with the desired start TS.
+ child_name STRING
+)
+-- Start timestamp of the child or NULL if it doesn't exist.
+RETURNS INT AS
+SELECT s.ts
+FROM descendant_slice($parent_id) s
+WHERE s.name GLOB $child_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
@@ -25,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),
@@ -59,7 +90,24 @@
-- EventLatency event type.
event_type STRING,
-- Perfetto track this slice is found on.
- track_id INT
+ track_id INT,
+ -- Vsync interval (in milliseconds).
+ vsync_interval_ms DOUBLE,
+ -- Whether the corresponding frame is janky.
+ is_janky_scrolled_frame BOOL,
+ -- Timestamp of the BufferAvailableToBufferReady substage.
+ buffer_available_timestamp INT,
+ -- Timestamp of the BufferReadyToLatch substage.
+ buffer_ready_timestamp INT,
+ -- Timestamp of the LatchToSwapEnd substage.
+ latch_timestamp INT,
+ -- Timestamp of the SwapEndToPresentationCompositorFrame substage.
+ swap_end_timestamp INT,
+ -- Frame presentation timestamp aka the timestamp of the
+ -- SwapEndToPresentationCompositorFrame substage.
+ -- TODO(b/341047059): temporarily use LatchToSwapEnd as a workaround if
+ -- SwapEndToPresentationCompositorFrame is missing due to b/247542163.
+ presentation_timestamp INT
) AS
SELECT
slice.id,
@@ -67,12 +115,24 @@
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,
+ AS is_presented,
EXTRACT_ARG(arg_set_id, 'event_latency.event_type') AS event_type,
- slice.track_id
+ slice.track_id,
+ EXTRACT_ARG(arg_set_id, 'event_latency.vsync_interval_ms')
+ AS vsync_interval_ms,
+ COALESCE(EXTRACT_ARG(arg_set_id, 'event_latency.is_janky_scrolled_frame'), 0)
+ AS is_janky_scrolled_frame,
+ _descendant_slice_begin(slice.id, 'BufferAvailableToBufferReady')
+ AS buffer_available_timestamp,
+ _descendant_slice_begin(slice.id, 'BufferReadyToLatch')
+ AS buffer_ready_timestamp,
+ _descendant_slice_begin(slice.id, 'LatchToSwapEnd') AS latch_timestamp,
+ _descendant_slice_begin(slice.id, 'SwapEndToPresentationCompositorFrame')
+ AS swap_end_timestamp,
+ _get_presentation_timestamp(slice.id) AS presentation_timestamp
FROM slice
WHERE name = 'EventLatency';
@@ -96,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
new file mode 100644
index 0000000..eae5bd5
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/chrome/graphics_pipeline.sql
@@ -0,0 +1,119 @@
+-- Copyright 2024 The Chromium Authors
+-- Use of this source code is governed by a BSD-style license that can be
+-- found in the LICENSE file.
+
+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:
+-- * STEP_ISSUE_BEGIN_FRAME
+-- * STEP_RECEIVE_BEGIN_FRAME
+-- * STEP_GENERATE_RENDER_PASS
+-- * STEP_GENERATE_COMPOSITOR_FRAME
+-- * STEP_SUBMIT_COMPOSITOR_FRAME
+-- * STEP_RECEIVE_COMPOSITOR_FRAME
+-- * STEP_RECEIVE_BEGIN_FRAME_DISCARD
+-- * STEP_DID_NOT_PRODUCE_FRAME
+-- * STEP_DID_NOT_PRODUCE_COMPOSITOR_FRAME
+CREATE PERFETTO TABLE chrome_graphics_pipeline_surface_frame_steps(
+ -- Slice Id of the `Graphics.Pipeline` slice.
+ id INT,
+ -- The start timestamp of the slice/step.
+ ts INT,
+ -- The duration of the slice/step.
+ dur INT,
+ -- Step name of the `Graphics.Pipeline` slice.
+ step STRING,
+ -- Id of the graphics pipeline, pre-surface aggregation.
+ surface_frame_trace_id INT,
+ -- Utid of the thread where this slice exists.
+ utid INT)
+AS
+SELECT
+ id,
+ ts,
+ dur,
+ extract_arg(arg_set_id, 'chrome_graphics_pipeline.step') AS step,
+ extract_arg(arg_set_id, 'chrome_graphics_pipeline.surface_frame_trace_id')
+ AS surface_frame_trace_id,
+ utid
+FROM thread_slice
+WHERE name = 'Graphics.Pipeline' AND surface_frame_trace_id IS NOT NULL;
+
+-- `Graphics.Pipeline` steps corresponding to work done on creating and
+-- presenting one frame during/after surface aggregation. Covers steps:
+-- * STEP_DRAW_AND_SWAP
+-- * STEP_SURFACE_AGGREGATION
+-- * STEP_SEND_BUFFER_SWAP
+-- * STEP_BUFFER_SWAP_POST_SUBMIT
+-- * STEP_FINISH_BUFFER_SWAP
+-- * STEP_SWAP_BUFFERS_ACK
+CREATE PERFETTO TABLE chrome_graphics_pipeline_display_frame_steps(
+ -- Slice Id of the `Graphics.Pipeline` slice.
+ id INT,
+ -- The start timestamp of the slice/step.
+ ts INT,
+ -- The duration of the slice/step.
+ dur INT,
+ -- Step name of the `Graphics.Pipeline` slice.
+ step STRING,
+ -- Id of the graphics pipeline, post-surface aggregation.
+ display_trace_id INT,
+ -- Utid of the thread where this slice exists.
+ utid INT)
+AS
+SELECT
+ id,
+ ts,
+ dur,
+ extract_arg(arg_set_id, 'chrome_graphics_pipeline.step') AS step,
+ extract_arg(arg_set_id, 'chrome_graphics_pipeline.display_trace_id')
+ AS display_trace_id,
+ utid
+FROM thread_slice
+WHERE name = 'Graphics.Pipeline' AND display_trace_id IS NOT NULL;
+
+-- Links surface frames (`chrome_graphics_pipeline_surface_frame_steps`) to the
+-- display frame (`chrome_graphics_pipeline_display_frame_steps`) into which
+-- they are merged. In other words, in general, multiple
+-- `surface_frame_trace_id`s will correspond to one `display_trace_id`.
+CREATE PERFETTO TABLE chrome_graphics_pipeline_aggregated_frames(
+ -- Id of the graphics pipeline, pre-surface aggregation.
+ surface_frame_trace_id INT,
+ -- Id of the graphics pipeline, post-surface aggregation.
+ display_trace_id INT)
+AS
+SELECT
+ args.int_value AS surface_frame_trace_id,
+ display_trace_id
+FROM chrome_graphics_pipeline_display_frame_steps step
+JOIN slice
+ USING (id)
+JOIN args
+ USING (arg_set_id)
+WHERE
+ step.step = 'STEP_SURFACE_AGGREGATION'
+ AND args.flat_key
+ = 'chrome_graphics_pipeline.aggregated_surface_frame_trace_ids';
+
+-- Links inputs (`chrome_input_pipeline_steps.latency_id`) to the surface frame
+-- (`chrome_graphics_pipeline_surface_frame_steps`) to which they correspond.
+-- In other words, in general, multiple `latency_id`s will correspond to one
+-- `surface_frame_trace_id`.
+CREATE PERFETTO TABLE chrome_graphics_pipeline_inputs_to_surface_frames(
+ -- Id corresponding to the input pipeline.
+ latency_id INT,
+ -- Id of the graphics pipeline, post-surface aggregation.
+ surface_frame_trace_id INT)
+AS
+SELECT
+ args.int_value AS latency_id,
+ surface_frame_trace_id
+FROM chrome_graphics_pipeline_surface_frame_steps step
+JOIN slice
+ USING (id)
+JOIN args
+ USING (arg_set_id)
+WHERE
+ step.step = 'STEP_SUBMIT_COMPOSITOR_FRAME'
+ AND args.flat_key = 'chrome_graphics_pipeline.latency_ids';
diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/input.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/input.sql
new file mode 100644
index 0000000..ebc8f8b
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/chrome/input.sql
@@ -0,0 +1,128 @@
+-- Copyright 2024 The Chromium Authors
+-- Use of this source code is governed by a BSD-style license that can be
+-- found in the LICENSE file.
+
+INCLUDE PERFETTO MODULE slices.with_context;
+
+-- Processing steps of the Chrome input pipeline.
+CREATE PERFETTO TABLE _chrome_input_pipeline_steps_no_input_type(
+ -- Id of this Chrome input pipeline (LatencyInfo).
+ latency_id INT,
+ -- Slice id
+ slice_id INT,
+ -- The step timestamp.
+ ts INT,
+ -- Step duration.
+ dur INT,
+ -- Utid of the thread.
+ utid INT,
+ -- Step name (ChromeLatencyInfo.step).
+ step STRING,
+ -- Input type.
+ 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,
+ id AS slice_id,
+ ts,
+ 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,
+ 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
+ step IS NOT NULL
+ AND latency_id != -1
+ORDER BY slice_id, ts;
+
+-- Each row represents one input pipeline.
+CREATE PERFETTO TABLE chrome_inputs(
+ -- Id of this Chrome input pipeline (LatencyInfo).
+ latency_id INT,
+ -- Input type.
+ input_type STRING
+) AS
+SELECT
+ -- Id of this Chrome input pipeline (LatencyInfo).
+ latency_id,
+ -- MIN selects the first non-null value.
+ MIN(input_type) as input_type
+FROM _chrome_input_pipeline_steps_no_input_type
+WHERE latency_id != -1
+GROUP BY latency_id;
+
+-- Since not all steps have associated input type (but all steps
+-- for a given latency id should have the same input type),
+-- populate input type for steps where it would be NULL.
+CREATE PERFETTO TABLE chrome_input_pipeline_steps(
+ -- Id of this Chrome input pipeline (LatencyInfo).
+ latency_id INT,
+ -- Slice id
+ slice_id INT,
+ -- The step timestamp.
+ ts INT,
+ -- Step duration.
+ dur INT,
+ -- Utid of the thread.
+ utid INT,
+ -- Step name (ChromeLatencyInfo.step).
+ step STRING,
+ -- Input type.
+ 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,
+ slice_id,
+ ts,
+ dur,
+ utid,
+ step,
+ chrome_inputs.input_type AS input_type,
+ task_start_time_ts
+FROM
+ chrome_inputs
+LEFT JOIN
+ _chrome_input_pipeline_steps_no_input_type
+ USING (latency_id)
+WHERE chrome_inputs.input_type IS NOT NULL;
+
+-- For each input, get the latency id of the input that it was coalesced into.
+CREATE PERFETTO TABLE chrome_coalesced_inputs(
+ -- The `latency_id` of the coalesced input.
+ coalesced_latency_id INT,
+ -- The `latency_id` of the input that the current input was coalesced into.
+ presented_latency_id INT
+) AS
+SELECT
+ args.int_value AS coalesced_latency_id,
+ latency_id AS presented_latency_id
+FROM chrome_input_pipeline_steps step
+JOIN slice USING (slice_id)
+JOIN args USING (arg_set_id)
+WHERE step.step = 'STEP_RESAMPLE_SCROLL_EVENTS'
+ AND args.flat_key = 'chrome_latency_info.coalesced_trace_ids';
+
+-- Slices with information about non-blocking touch move inputs
+-- that were converted into gesture scroll updates.
+CREATE PERFETTO TABLE chrome_touch_move_to_scroll_update(
+ -- Latency id of the touch move input (LatencyInfo).
+ touch_move_latency_id INT,
+ -- Latency id of the corresponding scroll update input (LatencyInfo).
+ scroll_update_latency_id INT
+) AS
+SELECT
+ scroll_update_step.latency_id AS scroll_update_latency_id,
+ touch_move_step.latency_id AS touch_move_latency_id
+FROM chrome_input_pipeline_steps scroll_update_step
+JOIN ancestor_slice(scroll_update_step.slice_id) AS ancestor
+JOIN chrome_input_pipeline_steps touch_move_step
+ ON ancestor.id = touch_move_step.slice_id
+WHERE scroll_update_step.step = 'STEP_SEND_INPUT_EVENT_UI'
+AND scroll_update_step.input_type = 'GESTURE_SCROLL_UPDATE_EVENT'
+AND touch_move_step.step = 'STEP_TOUCH_EVENT_HANDLED';
diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/perfetto_sql_files.gni b/src/trace_processor/perfetto_sql/stdlib/chrome/perfetto_sql_files.gni
index 77eeb14..cd0f974 100644
--- a/src/trace_processor/perfetto_sql/stdlib/chrome/perfetto_sql_files.gni
+++ b/src/trace_processor/perfetto_sql/stdlib/chrome/perfetto_sql_files.gni
@@ -8,9 +8,11 @@
"cpu_powerups.sql",
"event_latency.sql",
"event_latency_description.sql",
+ "graphics_pipeline.sql",
"histograms.sql",
"interactions.sql",
"metadata.sql",
+ "input.sql",
"page_loads.sql",
"scroll_interactions.sql",
"speedometer.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/intervals/overlap.sql b/src/trace_processor/perfetto_sql/stdlib/intervals/overlap.sql
index cd84164..356ec31 100644
--- a/src/trace_processor/perfetto_sql/stdlib/intervals/overlap.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/intervals/overlap.sql
@@ -13,6 +13,8 @@
-- See the License for the specific language governing permissions and
-- limitations under the License.
+INCLUDE PERFETTO MODULE intervals.intersect;
+
-- Compute the distribution of the overlap of the given intervals over time.
--
-- Each interval is a (ts, dur) pair and the overlap represented as a (ts, value)
@@ -94,6 +96,8 @@
-- Merges a |roots_table| and |children_table| into one table. See _intervals_flatten
-- that accepts the output of this macro to flatten intervals.
+
+-- See: _intervals_merge_root_and_children_by_intersection.
CREATE PERFETTO MACRO _intervals_merge_root_and_children(
-- Table or subquery containing all the root intervals: (id, ts, dur).
-- Note that parent_id is not necessary in this table as it will be NULL anyways.
@@ -141,6 +145,46 @@
JOIN _roots USING(root_id)
);
+-- Merges a |roots_table| and |children_table| into one table. See _intervals_flatten
+-- that accepts the output of this macro to flatten intervals.
+
+-- This is very similar to _intervals_merge_root_and_children but there is no explicit
+-- root_id shared between the root and the children. Instead an _interval_intersect is
+-- used to derive the root and child relationships.
+CREATE PERFETTO MACRO _intervals_merge_root_and_children_by_intersection(
+ -- Table or subquery containing all the root intervals: (id, ts, dur).
+ -- Note that parent_id is not necessary in this table as it will be NULL anyways.
+ roots_table TableOrSubquery,
+ -- Table or subquery containing all the child intervals:
+ -- (root_id, id, parent_id, ts, dur)
+ children_table TableOrSubquery,
+ -- intersection key used in deriving the root child relationships.
+ key ColumnName)
+RETURNS TableOrSubQuery
+AS (
+ WITH
+ _roots AS (
+ SELECT * FROM $roots_table WHERE dur > 0 ORDER BY ts
+ ),
+ _children AS (
+ SELECT * FROM $children_table WHERE dur > 0 ORDER BY ts
+ )
+ SELECT
+ ii.ts,
+ ii.dur,
+ _children.id,
+ IIF(_children.parent_id IS NULL, id_1, _children.parent_id) AS parent_id,
+ _roots.id AS root_id,
+ _roots.ts AS root_ts,
+ _roots.dur AS root_dur,
+ ii.$key
+ FROM _interval_intersect!((_children, _roots), ($key)) ii
+ JOIN _children
+ ON _children.id = id_0
+ JOIN _roots
+ ON _roots.id = id_1
+);
+
-- Partition and flatten a hierarchy of intervals into non-overlapping intervals where
-- each resulting interval is the leaf in the hierarchy at any given time. The result also
-- denotes the 'self-time' of each interval.
diff --git a/src/trace_processor/perfetto_sql/stdlib/linux/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/linux/BUILD.gn
index 23371df..95f49d8 100644
--- a/src/trace_processor/perfetto_sql/stdlib/linux/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/linux/BUILD.gn
@@ -15,7 +15,10 @@
import("../../../../../gn/perfetto_sql.gni")
perfetto_sql_source_set("linux") {
- sources = [ "threads.sql" ]
+ sources = [
+ "devfreq.sql",
+ "threads.sql",
+ ]
deps = [
"cpu",
"memory",
diff --git a/src/trace_processor/perfetto_sql/stdlib/linux/devfreq.sql b/src/trace_processor/perfetto_sql/stdlib/linux/devfreq.sql
new file mode 100644
index 0000000..7fa09bc
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/linux/devfreq.sql
@@ -0,0 +1,62 @@
+--
+-- 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 counters.intervals;
+
+-- Gets devfreq frequency counter based on device queried. These counters will
+-- only be available if the "devfreq/devfreq_frequency" ftrace event is enabled.
+CREATE PERFETTO FUNCTION _get_devfreq_counters(
+ -- Devfreq name to query for.
+ device_name STRING
+)
+RETURNS TABLE(
+ -- Unique identifier for this counter.
+ id INT,
+ -- Starting timestamp of the counter.
+ ts LONG,
+ -- Duration in which counter is constant and frequency doesn't chamge.
+ dur INT,
+ -- Frequency in kHz of the device that corresponds to the counter.
+ freq INT
+) AS
+SELECT
+ count_w_dur.id,
+ count_w_dur.ts,
+ count_w_dur.dur,
+ cast_int!(count_w_dur.value) as freq
+FROM counter_leading_intervals!((
+ SELECT c.*
+ FROM counter c
+ JOIN track t ON t.id = c.track_id
+ WHERE t.classification = 'linux_device_frequency'
+ AND EXTRACT_ARG(t.dimension_arg_set_id, 'device_name') = $device_name
+)) AS count_w_dur;
+
+-- ARM DSU device frequency counters. This table will only be populated on
+-- traces collected with "devfreq/devfreq_frequency" ftrace event enabled,
+-- and from ARM devices with the DSU (DynamIQ Shared Unit) hardware.
+CREATE PERFETTO TABLE linux_devfreq_dsu_counter(
+ -- Unique identifier for this counter.
+ id INT,
+ -- Starting timestamp of the counter.
+ ts LONG,
+ -- Duration in which counter is constant and frequency doesn't chamge.
+ dur INT,
+ -- Frequency in kHz of the device that corresponds to the counter.
+ dsu_freq INT
+) AS
+SELECT
+ id, ts, dur, freq as dsu_freq
+FROM _get_devfreq_counters("dsufreq");
diff --git a/src/trace_processor/perfetto_sql/stdlib/linux/perf/samples.sql b/src/trace_processor/perfetto_sql/stdlib/linux/perf/samples.sql
index 0d58ef1..2584316 100644
--- a/src/trace_processor/perfetto_sql/stdlib/linux/perf/samples.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/linux/perf/samples.sql
@@ -15,36 +15,12 @@
INCLUDE PERFETTO MODULE callstacks.stack_profile;
-CREATE PERFETTO MACRO _linux_perf_callstacks_for_samples(
- samples TableOrSubquery
-)
-RETURNS TableOrSubquery
-AS
-(
- WITH metrics AS MATERIALIZED (
- SELECT
- callsite_id,
- COUNT() AS self_count
- FROM $samples
- GROUP BY callsite_id
- )
- SELECT
- c.id,
- c.parent_id,
- c.name,
- c.mapping_name,
- c.source_file,
- c.line_number,
- IFNULL(m.self_count, 0) AS self_count
- FROM _callstacks_for_stack_profile_samples!(metrics) c
- LEFT JOIN metrics m USING (callsite_id)
-);
-
CREATE PERFETTO TABLE _linux_perf_raw_callstacks AS
SELECT *
-FROM _linux_perf_callstacks_for_samples!(
- (SELECT p.callsite_id FROM perf_sample p)
-) c
+FROM _callstacks_for_callsites!((
+ SELECT p.callsite_id
+ FROM perf_sample p
+)) c
ORDER BY c.id;
-- Table summarising the callstacks captured during all
diff --git a/src/trace_processor/perfetto_sql/stdlib/prelude/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/prelude/BUILD.gn
index 446a002..32aba6f 100644
--- a/src/trace_processor/perfetto_sql/stdlib/prelude/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/prelude/BUILD.gn
@@ -1,4 +1,4 @@
-# Copyright (C) 2023 The Android Open Source Project
+# 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.
@@ -15,12 +15,9 @@
import("../../../../../gn/perfetto_sql.gni")
perfetto_sql_source_set("prelude") {
- sources = [
- "casts.sql",
- "slices.sql",
- "tables.sql",
- "tables_views.sql",
- "trace_bounds.sql",
- "views.sql",
+ sources = []
+ deps = [
+ "after_eof",
+ "before_eof",
]
}
diff --git a/src/trace_processor/perfetto_sql/stdlib/common/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/BUILD.gn
similarity index 71%
rename from src/trace_processor/perfetto_sql/stdlib/common/BUILD.gn
rename to src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/BUILD.gn
index 73db0a7..c564a57 100644
--- a/src/trace_processor/perfetto_sql/stdlib/common/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/BUILD.gn
@@ -1,4 +1,4 @@
-# Copyright (C) 2022 The Android Open Source Project
+# 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.
@@ -12,15 +12,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import("../../../../../gn/perfetto_sql.gni")
+import("../../../../../../gn/perfetto_sql.gni")
-perfetto_sql_source_set("common") {
+perfetto_sql_source_set("after_eof") {
sources = [
- "args.sql",
- "counters.sql",
- "metadata.sql",
- "percentiles.sql",
+ "casts.sql",
"slices.sql",
- "timestamps.sql",
+ "tables_views.sql",
+ "views.sql",
]
}
diff --git a/src/trace_processor/perfetto_sql/stdlib/prelude/casts.sql b/src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/casts.sql
similarity index 100%
rename from src/trace_processor/perfetto_sql/stdlib/prelude/casts.sql
rename to src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/casts.sql
diff --git a/src/trace_processor/perfetto_sql/stdlib/prelude/slices.sql b/src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/slices.sql
similarity index 95%
rename from src/trace_processor/perfetto_sql/stdlib/prelude/slices.sql
rename to src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/slices.sql
index 48f7132..f68f872 100644
--- a/src/trace_processor/perfetto_sql/stdlib/prelude/slices.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/slices.sql
@@ -13,7 +13,7 @@
-- See the License for the specific language governing permissions and
-- limitations under the License.
-INCLUDE PERFETTO MODULE prelude.views;
+INCLUDE PERFETTO MODULE prelude.after_eof.views;
-- Given two slice ids, returns whether the first is an ancestor of the second.
CREATE PERFETTO FUNCTION slice_is_ancestor(
diff --git a/src/trace_processor/perfetto_sql/stdlib/prelude/tables_views.sql b/src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/tables_views.sql
similarity index 89%
rename from src/trace_processor/perfetto_sql/stdlib/prelude/tables_views.sql
rename to src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/tables_views.sql
index 57a1e5c..e074220 100644
--- a/src/trace_processor/perfetto_sql/stdlib/prelude/tables_views.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/tables_views.sql
@@ -13,7 +13,26 @@
-- See the License for the specific language governing permissions and
-- limitations under the License.
-INCLUDE PERFETTO MODULE prelude.views;
+INCLUDE PERFETTO MODULE prelude.after_eof.views;
+
+-- Lists all metrics built-into trace processor.
+CREATE PERFETTO VIEW trace_metrics(
+ -- The name of the metric.
+ name STRING
+) AS
+SELECT name FROM _trace_metrics;
+
+-- Definition of `trace_bounds` table. The values are being filled by Trace
+-- Processor when parsing the trace.
+-- It is recommended to depend on the `trace_start()` and `trace_end()`
+-- functions rather than directly on `trace_bounds`.
+CREATE PERFETTO VIEW trace_bounds(
+ -- First ts in the trace.
+ start_ts INT,
+ -- End of the trace.
+ end_ts INT
+) AS
+SELECT start_ts, end_ts FROM _trace_bounds;
-- Tracks are a fundamental concept in trace processor and represent a
-- "timeline" for events of the same type and with the same context. See
@@ -28,25 +47,42 @@
-- Name of the track; can be null for some types of tracks (e.g. thread
-- tracks).
name STRING,
+ -- The classification of a track indicates the "type of data" the track
+ -- contains.
+ --
+ -- Every track is uniquely identified by the the combination of the
+ -- classification and a set of dimensions: classifications allow identifying
+ -- a set of tracks with the same type of data within the whole universe of
+ -- tracks while dimensions allow distinguishing between different tracks in
+ -- that set.
+ classification STRING,
+ -- The dimensions of the track which uniquely identify the track within a
+ -- given classification.
+ --
+ -- Join with the `args` table or use the `EXTRACT_ARG` helper function to
+ -- expand the args.
+ dimension_arg_set_id UINT,
-- The track which is the "parent" of this track. Only non-null for tracks
-- created using Perfetto's track_event API.
parent_id UINT,
- -- Args for this track which store information about "source" of this track
- -- in the trace. For example: whether this track orginated from atrace,
- -- Chrome tracepoints etc. Alias of `args.arg_set_id`.
+ -- Generic key-value pairs containing extra information about the track.
+ --
+ -- Join with the `args` table or use the `EXTRACT_ARG` helper function to
+ -- expand the args.
source_arg_set_id UINT,
-- Machine identifier, non-null for tracks on a remote machine.
machine_id UINT
) AS
SELECT
id,
- type AS type,
+ type,
name,
+ classification,
+ dimension_arg_set_id,
parent_id,
source_arg_set_id,
machine_id
-FROM
- __intrinsic_track;
+FROM __intrinsic_track;
-- Contains information about the CPUs on the device this trace was taken on.
CREATE PERFETTO VIEW cpu (
diff --git a/src/trace_processor/perfetto_sql/stdlib/prelude/views.sql b/src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/views.sql
similarity index 99%
rename from src/trace_processor/perfetto_sql/stdlib/prelude/views.sql
rename to src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/views.sql
index fa1eece..8f3a90f 100644
--- a/src/trace_processor/perfetto_sql/stdlib/prelude/views.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/views.sql
@@ -13,7 +13,7 @@
-- See the License for the specific language governing permissions and
-- limitations under the License.
-INCLUDE PERFETTO MODULE prelude.casts;
+INCLUDE PERFETTO MODULE prelude.after_eof.casts;
-- Alias of the `counter` table.
CREATE PERFETTO VIEW counters(
@@ -146,7 +146,7 @@
-- Alias of `slice.thread_instruction_delta`.
thread_instruction_delta LONG,
-- Alias of `slice.cat`.
- cat LONG,
+ cat STRING,
-- Alias of `slice.slice_id`.
slice_id LONG
) AS
diff --git a/src/trace_processor/perfetto_sql/stdlib/common/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/prelude/before_eof/BUILD.gn
similarity index 69%
copy from src/trace_processor/perfetto_sql/stdlib/common/BUILD.gn
copy to src/trace_processor/perfetto_sql/stdlib/prelude/before_eof/BUILD.gn
index 73db0a7..ebe56f9 100644
--- a/src/trace_processor/perfetto_sql/stdlib/common/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/prelude/before_eof/BUILD.gn
@@ -1,4 +1,4 @@
-# Copyright (C) 2022 The Android Open Source Project
+# 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.
@@ -12,15 +12,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import("../../../../../gn/perfetto_sql.gni")
+import("../../../../../../gn/perfetto_sql.gni")
-perfetto_sql_source_set("common") {
+perfetto_sql_source_set("before_eof") {
sources = [
- "args.sql",
- "counters.sql",
- "metadata.sql",
- "percentiles.sql",
- "slices.sql",
- "timestamps.sql",
+ "tables.sql",
+ "trace_bounds.sql",
]
}
diff --git a/src/trace_processor/perfetto_sql/stdlib/prelude/tables.sql b/src/trace_processor/perfetto_sql/stdlib/prelude/before_eof/tables.sql
similarity index 96%
rename from src/trace_processor/perfetto_sql/stdlib/prelude/tables.sql
rename to src/trace_processor/perfetto_sql/stdlib/prelude/before_eof/tables.sql
index e8b5db0..2689491 100644
--- a/src/trace_processor/perfetto_sql/stdlib/prelude/tables.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/prelude/before_eof/tables.sql
@@ -14,7 +14,7 @@
-- limitations under the License.
-- Lists all metrics built-into trace processor.
-CREATE TABLE trace_metrics(
+CREATE TABLE _trace_metrics(
-- The name of the metric.
name STRING
);
diff --git a/src/trace_processor/perfetto_sql/stdlib/prelude/trace_bounds.sql b/src/trace_processor/perfetto_sql/stdlib/prelude/before_eof/trace_bounds.sql
similarity index 72%
rename from src/trace_processor/perfetto_sql/stdlib/prelude/trace_bounds.sql
rename to src/trace_processor/perfetto_sql/stdlib/prelude/before_eof/trace_bounds.sql
index 51db48d..ece2ecd 100644
--- a/src/trace_processor/perfetto_sql/stdlib/prelude/trace_bounds.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/prelude/before_eof/trace_bounds.sql
@@ -13,25 +13,22 @@
-- See the License for the specific language governing permissions and
-- limitations under the License.
--- Definition of `trace_bounds` table. The values are being filled by Trace
--- Processor when parsing the trace. Can't be a Perfetto table because it has
--- to be mutable.
--- It is recommended to depend on the `trace_start()` and `trace_end()`
--- functions rather than directly on `trace_bounds`.
-CREATE TABLE trace_bounds AS
+-- The values are being filled by Trace Processor when parsing the trace.
+-- Exposed with `trace_bounds`.
+CREATE TABLE _trace_bounds AS
SELECT 0 AS start_ts, 0 AS end_ts;
-- Fetch start of the trace.
CREATE PERFETTO FUNCTION trace_start()
-- Start of the trace in nanoseconds.
RETURNS LONG AS
-SELECT start_ts FROM trace_bounds;
+SELECT start_ts FROM _trace_bounds;
-- Fetch end of the trace.
CREATE PERFETTO FUNCTION trace_end()
-- End of the trace in nanoseconds.
RETURNS LONG AS
-SELECT end_ts FROM trace_bounds;
+SELECT end_ts FROM _trace_bounds;
-- Fetch duration of the trace.
CREATE PERFETTO FUNCTION trace_dur()
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/hierarchy.sql b/src/trace_processor/perfetto_sql/stdlib/slices/hierarchy.sql
index dff6475..137a123 100644
--- a/src/trace_processor/perfetto_sql/stdlib/slices/hierarchy.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/slices/hierarchy.sql
@@ -91,4 +91,41 @@
UNION ALL
SELECT
id, type, ts, dur, track_id, category, name, depth, parent_id, arg_set_id, thread_ts, thread_dur
-FROM descendant_slice($slice_id);
\ No newline at end of file
+FROM descendant_slice($slice_id);
+
+-- Delete rows from |slice_table| where the |column_name| value is NULL.
+--
+-- The |parent_id| of the remaining rows are adjusted to point to the closest
+-- ancestor remaining. This keeps the trees as connected as possible,
+-- allowing further graph analysis.
+CREATE PERFETTO MACRO _slice_remove_nulls_and_reparent(
+ -- Table or subquery containing a subset of the slice table. Required columns are
+ -- (id INT64, parent_id INT64, depth UINT32, <column_name>).
+ slice_table TableOrSubQuery,
+ -- Column name for which a NULL value indicates the row will be deleted.
+ column_name ColumnName)
+ -- The returned table has the schema (id INT64, parent_id INT64, depth UINT32, <column_name>).
+RETURNS TableOrSubQuery
+AS (
+ WITH _slice AS (
+ SELECT * FROM $slice_table WHERE $column_name IS NOT NULL
+ )
+ SELECT
+ id,
+ parent_id,
+ depth,
+ $column_name
+ FROM _slice
+ WHERE depth = 0
+ UNION ALL
+ SELECT
+ child.id,
+ anc.id AS parent_id,
+ MAX(IIF(parent.$column_name IS NULL, 0, anc.depth)) AS depth,
+ child.$column_name
+ FROM _slice child
+ JOIN ancestor_slice(child.id) anc
+ LEFT JOIN _slice parent
+ ON parent.id = anc.id
+ GROUP BY child.id
+);
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/perfetto_sql/stdlib/stacks/cpu_profiling.sql b/src/trace_processor/perfetto_sql/stdlib/stacks/cpu_profiling.sql
index a5200e8..d117f05 100644
--- a/src/trace_processor/perfetto_sql/stdlib/stacks/cpu_profiling.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/stacks/cpu_profiling.sql
@@ -14,49 +14,95 @@
-- limitations under the License.
INCLUDE PERFETTO MODULE callstacks.stack_profile;
-INCLUDE PERFETTO MODULE graphs.scan;
-INCLUDE PERFETTO MODULE linux.perf.samples;
-CREATE PERFETTO TABLE _cpu_profiling_raw_callstacks AS
-SELECT *
-FROM _callstacks_for_cpu_profile_stack_samples!(
- (SELECT s.callsite_id FROM cpu_profile_stack_sample s)
-) c
-ORDER BY c.id;
-
--- Table summarising the callstacks captured during any CPU
--- profiling which occurred during the trace.
+-- Table containing all the timestamped samples of CPU profiling which occurred
+-- during the trace.
--
--- Specifically, this table returns a tree containing all
--- the callstacks seen during the trace with `self_count`
--- equal to the number of samples with that frame as the
--- leaf and `cumulative_count` equal to the number of
--- samples with the frame anywhere in the tree.
---
--- Currently, this table is backed by the following data
--- sources:
--- * any perf sampling
--- * generic CPU profiling (e.g. Chrome, ad-hoc traces)
-CREATE PERFETTO TABLE cpu_profiling_summary_tree(
- -- The id of the callstack. A callstack in this context
- -- is a unique set of frames up to the root.
+-- Currently, this table is backed by the following data sources:
+-- * Linux perf
+-- * macOS instruments
+-- * Chrome CPU profiling
+-- * Legacy V8 CPU profiling
+-- * Profiling data in Gecko traces
+CREATE PERFETTO TABLE cpu_profiling_samples(
+ -- The id of the sample.
id INT,
- -- The id of the parent callstack for this callstack.
+ -- The timestamp of the sample.
+ ts INT,
+ -- The utid of the thread of the sample, if available.
+ utid INT,
+ -- The tid of the sample, if available.
+ tid INT,
+ -- The thread name of thread of the sample, if available.
+ thread_name STRING,
+ -- The ucpu of the sample, if available.
+ ucpu INT,
+ -- The cpu of the sample, if available.
+ cpu INT,
+ -- The callsite id of the sample.
+ callsite_id INT
+)
+AS
+WITH raw_samples AS (
+ -- Linux perf samples.
+ SELECT p.ts, p.utid, p.cpu AS ucpu, p.callsite_id
+ FROM perf_sample p
+ UNION ALL
+ -- Instruments samples.
+ SELECT p.ts, p.utid, p.cpu AS ucpu, p.callsite_id
+ FROM instruments_sample p
+ UNION ALL
+ -- All other CPU profiling.
+ SELECT s.ts, s.utid, NULL AS ucpu, s.callsite_id
+ FROM cpu_profile_stack_sample s
+)
+SELECT
+ ROW_NUMBER() OVER (ORDER BY ts) AS id,
+ r.*,
+ t.tid,
+ t.name AS thread_name,
+ c.cpu
+FROM raw_samples r
+LEFT JOIN thread t USING (utid)
+LEFT JOIN cpu c USING (ucpu)
+ORDER BY ts;
+
+CREATE PERFETTO TABLE _cpu_profiling_self_callsites AS
+SELECT *
+FROM _callstacks_for_callsites!((
+ SELECT callsite_id
+ FROM cpu_profiling_samples
+))
+ORDER BY id;
+
+-- Table summarising the callstacks captured during any CPU profiling which
+-- occurred during the trace.
+--
+-- Specifically, this table returns a tree containing all the callstacks seen
+-- during the trace with `self_count` equal to the number of samples with that
+-- frame as the leaf and `cumulative_count` equal to the number of samples with
+-- the frame anywhere in the tree.
+--
+-- The data sources supported are the same as the `cpu_profiling_samples` table.
+CREATE PERFETTO TABLE cpu_profiling_summary_tree(
+ -- The id of the callstack; by callstack we mean a unique set of frames up to
+ -- the root frame.
+ id INT,
+ -- The id of the parent callstack for this callstack. NULL if this is root.
parent_id INT,
-- The function name of the frame for this callstack.
name STRING,
- -- The name of the mapping containing the frame. This
- -- can be a native binary, library, JAR or APK.
+ -- The name of the mapping containing the frame. This can be a native binary,
+ -- library, JAR or APK.
mapping_name STRING,
-- The name of the file containing the function.
source_file STRING,
-- The line number in the file the function is located at.
line_number INT,
- -- The number of samples with this function as the leaf
- -- frame.
+ -- The number of samples with this function as the leaf frame.
self_count INT,
- -- The number of samples with this function appearing
- -- anywhere on the callstack.
+ -- The number of samples with this function appearing anywhere on the
+ -- callstack.
cumulative_count INT
) AS
SELECT
@@ -69,17 +115,11 @@
SUM(self_count) AS self_count,
SUM(cumulative_count) AS cumulative_count
FROM (
- -- Generic CPU profiling.
SELECT r.*, a.cumulative_count
- FROM _callstacks_self_to_cumulative!((
+ FROM _cpu_profiling_self_callsites r
+ JOIN _callstacks_self_to_cumulative!((
SELECT id, parent_id, self_count
- FROM _cpu_profiling_raw_callstacks
- )) a
- JOIN _cpu_profiling_raw_callstacks r USING (id)
- UNION ALL
- -- Linux perf sampling.
- SELECT *
- FROM linux_perf_samples_summary_tree
+ FROM _cpu_profiling_self_callsites
+ )) a USING (id)
)
-GROUP BY id
-ORDER BY id;
+GROUP BY id;
diff --git a/src/trace_processor/perfetto_sql/stdlib/viz/flamegraph.sql b/src/trace_processor/perfetto_sql/stdlib/viz/flamegraph.sql
index 3dcdc3a..77c2b10 100644
--- a/src/trace_processor/perfetto_sql/stdlib/viz/flamegraph.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/viz/flamegraph.sql
@@ -294,12 +294,6 @@
ORDER BY hash
);
-CREATE PERFETTO MACRO _viz_flamegraph_merge_grouped(
- col ColumnName
-)
-RETURNS _SqlFragment
-AS IIF(COUNT() = 1, $col, NULL) AS $col;
-
CREATE PERFETTO MACRO _col_list_id(a ColumnName)
RETURNS _SqlFragment AS $a;
@@ -308,7 +302,7 @@
CREATE PERFETTO MACRO _viz_flamegraph_merge_hashes(
hashed TableOrSubquery,
grouping _ColumnNameList,
- grouped _ColumnNameList
+ grouped_agged_exprs _ColumnNameList
)
RETURNS TableOrSubquery
AS (
@@ -326,7 +320,7 @@
-- hash took them into account: we would not merged any nodes where
-- the grouping columns were different.
__intrinsic_token_apply!(_col_list_id, $grouping),
- __intrinsic_token_apply!(_viz_flamegraph_merge_grouped, $grouped),
+ __intrinsic_token_apply!(_col_list_id, $grouped_agged_exprs),
SUM(value) AS value,
SUM(cumulativeValue) AS cumulativeValue
FROM $hashed c
diff --git a/src/trace_processor/perfetto_sql/stdlib/viz/summary/tracks.sql b/src/trace_processor/perfetto_sql/stdlib/viz/summary/tracks.sql
index dd2da12..49fb3f0 100644
--- a/src/trace_processor/perfetto_sql/stdlib/viz/summary/tracks.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/viz/summary/tracks.sql
@@ -15,6 +15,89 @@
INCLUDE PERFETTO MODULE viz.summary.slices;
+CREATE PERFETTO VIEW _track_event_tracks_unordered AS
+WITH extracted AS (
+ SELECT
+ t.id,
+ t.parent_id,
+ t.name,
+ EXTRACT_ARG(t.source_arg_set_id, 'child_ordering') AS ordering,
+ EXTRACT_ARG(t.source_arg_set_id, 'sibling_order_rank') AS rank
+ FROM track t
+)
+SELECT
+ t.id,
+ t.parent_id,
+ t.name,
+ t.ordering,
+ p.ordering AS parent_ordering,
+ IFNULL(t.rank, 0) AS rank
+FROM extracted t
+LEFT JOIN extracted p ON t.parent_id = p.id
+WHERE p.ordering IS NOT NULL;
+
+CREATE PERFETTO TABLE _track_event_tracks_ordered AS
+WITH lexicographic_and_none AS (
+ SELECT
+ id, parent_id, name,
+ ROW_NUMBER() OVER (ORDER BY parent_id, name) AS order_id
+ FROM _track_event_tracks_unordered
+ WHERE parent_ordering = 'lexicographic'
+),
+explicit AS (
+SELECT
+ id, parent_id, name,
+ ROW_NUMBER() OVER (ORDER BY parent_id, rank) AS order_id
+FROM _track_event_tracks_unordered
+WHERE parent_ordering = 'explicit'
+),
+slice_chronological AS (
+ SELECT
+ t.*,
+ min(ts) AS min_ts
+ FROM _track_event_tracks_unordered t
+ JOIN slice s on t.id = s.track_id
+ WHERE parent_ordering = 'chronological'
+ GROUP BY track_id
+),
+counter_chronological AS (
+ SELECT
+ t.*,
+ min(ts) AS min_ts
+ FROM _track_event_tracks_unordered t
+ JOIN counter s on t.id = s.track_id
+ WHERE parent_ordering = 'chronological'
+ GROUP BY track_id
+),
+slice_and_counter_chronological AS (
+ SELECT t.*, u.min_ts
+ FROM _track_event_tracks_unordered t
+ LEFT JOIN (
+ SELECT * FROM slice_chronological
+ UNION ALL
+ SELECT * FROM counter_chronological) u USING (id)
+ WHERE t.parent_ordering = 'chronological'
+),
+chronological AS (
+ SELECT
+ id, parent_id, name,
+ ROW_NUMBER() OVER (ORDER BY parent_id, min_ts) AS order_id
+ FROM slice_and_counter_chronological
+),
+all_tracks AS (
+ SELECT id, parent_id, name, order_id
+ FROM lexicographic_and_none
+ UNION
+ SELECT id, parent_id, name, order_id
+ FROM explicit
+ UNION
+ SELECT id, parent_id, name, order_id
+ FROM chronological
+)
+SELECT id, order_id
+FROM all_tracks all_t
+ORDER BY parent_id, order_id;
+
CREATE PERFETTO TABLE _thread_track_summary_by_utid_and_name AS
SELECT
utid,
@@ -28,7 +111,8 @@
COUNT() AS track_count
FROM thread_track
JOIN _slice_track_summary USING (id)
-GROUP BY utid, parent_id, name;
+LEFT JOIN _track_event_tracks_ordered USING (id)
+GROUP BY utid, parent_id, order_id, name;
CREATE PERFETTO TABLE _process_track_summary_by_upid_and_parent_id_and_name AS
SELECT
@@ -40,15 +124,5 @@
COUNT() AS track_count
FROM process_track
JOIN _slice_track_summary USING (id)
-GROUP BY upid, parent_id, name;
-
-CREATE PERFETTO TABLE _uid_track_track_summary_by_uid_and_name AS
-SELECT
- uid,
- parent_id,
- name,
- GROUP_CONCAT(id) AS track_ids,
- COUNT() AS track_count
-FROM uid_track
-JOIN _slice_track_summary USING (id)
-GROUP BY uid, parent_id, name;
+LEFT JOIN _track_event_tracks_ordered USING (id)
+GROUP BY upid, parent_id, order_id, name;
\ No newline at end of file
diff --git a/src/trace_processor/perfetto_sql/stdlib/wattson/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/wattson/BUILD.gn
index 852a839..185d6ef 100644
--- a/src/trace_processor/perfetto_sql/stdlib/wattson/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/wattson/BUILD.gn
@@ -17,13 +17,16 @@
perfetto_sql_source_set("wattson") {
sources = [
"arm_dsu.sql",
+ "cpu_freq.sql",
+ "cpu_freq_idle.sql",
"cpu_idle.sql",
"cpu_split.sql",
"curves/device.sql",
- "curves/grouped.sql",
+ "curves/estimates.sql",
"curves/idle_attribution.sql",
- "curves/ungrouped.sql",
"curves/utils.sql",
+ "curves/w_cpu_dependence.sql",
+ "curves/w_dsu_dependence.sql",
"device_infos.sql",
"system_state.sql",
]
diff --git a/src/trace_processor/perfetto_sql/stdlib/wattson/cpu_freq.sql b/src/trace_processor/perfetto_sql/stdlib/wattson/cpu_freq.sql
new file mode 100644
index 0000000..94f8fd7
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/wattson/cpu_freq.sql
@@ -0,0 +1,54 @@
+--
+-- 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 linux.cpu.frequency;
+INCLUDE PERFETTO MODULE wattson.device_infos;
+
+CREATE PERFETTO TABLE _adjusted_cpu_freq AS
+ WITH _cpu_freq AS (
+ SELECT
+ ts,
+ dur,
+ freq,
+ cf.ucpu as cpu,
+ d_map.policy
+ FROM cpu_frequency_counters as cf
+ JOIN _dev_cpu_policy_map as d_map
+ ON cf.ucpu = d_map.cpu
+ ),
+ -- Get first freq transition per CPU
+ first_cpu_freq_slices AS (
+ SELECT ts, cpu FROM _cpu_freq
+ GROUP BY cpu
+ ORDER by ts ASC
+ )
+-- Prepend NULL slices up to first freq events on a per CPU basis
+SELECT
+ -- Construct slices from first cpu ts up to first freq event for each cpu
+ trace_start() as ts,
+ first_slices.ts - trace_start() as dur,
+ NULL as freq,
+ first_slices.cpu,
+ d_map.policy
+FROM first_cpu_freq_slices as first_slices
+JOIN _dev_cpu_policy_map as d_map ON first_slices.cpu = d_map.cpu
+UNION ALL
+SELECT
+ ts,
+ dur,
+ freq,
+ cpu,
+ policy
+FROM _cpu_freq;
diff --git a/src/trace_processor/perfetto_sql/stdlib/wattson/cpu_freq_idle.sql b/src/trace_processor/perfetto_sql/stdlib/wattson/cpu_freq_idle.sql
new file mode 100644
index 0000000..756ebed
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/wattson/cpu_freq_idle.sql
@@ -0,0 +1,76 @@
+--
+-- 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 wattson.cpu_freq;
+INCLUDE PERFETTO MODULE wattson.cpu_idle;
+INCLUDE PERFETTO MODULE wattson.curves.utils;
+INCLUDE PERFETTO MODULE wattson.device_infos;
+
+-- Helper macro for using Perfetto table with interval intersect
+CREATE PERFETTO MACRO _ii_subquery(tab TableOrSubquery)
+RETURNS TableOrSubquery AS (SELECT _auto_id AS id, * FROM $tab);
+
+-- Wattson estimation is valid from when first CPU0 frequency appears
+CREATE PERFETTO TABLE _valid_window
+AS
+WITH window_start AS (
+ SELECT ts as start_ts
+ FROM _adjusted_cpu_freq
+ WHERE cpu = 0 AND freq IS NOT NULL
+ ORDER BY ts ASC
+ LIMIT 1
+),
+window AS (
+ SELECT start_ts as ts, trace_end() - start_ts as dur
+ FROM window_start
+)
+SELECT *, 0 as cpu FROM window
+UNION ALL
+SELECT *, 1 as cpu FROM window
+UNION ALL
+SELECT *, 2 as cpu FROM window
+UNION ALL
+SELECT *, 3 as cpu FROM window
+UNION ALL
+SELECT *, 4 as cpu FROM window
+UNION ALL
+SELECT *, 5 as cpu FROM window
+UNION ALL
+SELECT *, 6 as cpu FROM window
+UNION ALL
+SELECT *, 7 as cpu FROM window;
+
+-- Start matching CPUs with 1D curves based on combination of freq and idle
+CREATE PERFETTO TABLE _idle_freq_materialized
+AS
+SELECT
+ ii.ts, ii.dur, ii.cpu, freq.policy, freq.freq, idle.idle, lut.curve_value
+FROM _interval_intersect!(
+ (
+ _ii_subquery!(_valid_window),
+ _ii_subquery!(_adjusted_cpu_freq),
+ _ii_subquery!(_adjusted_deep_idle)
+ ),
+ (cpu)
+) ii
+JOIN _adjusted_cpu_freq AS freq ON freq._auto_id = id_1
+JOIN _adjusted_deep_idle AS idle ON idle._auto_id = id_2
+-- Left join since some CPUs may only match the 2D LUT
+LEFT JOIN _filtered_curves_1d lut ON
+ freq.policy = lut.policy AND
+ freq.freq = lut.freq_khz AND
+ idle.idle = lut.idle;
+
diff --git a/src/trace_processor/perfetto_sql/stdlib/wattson/cpu_idle.sql b/src/trace_processor/perfetto_sql/stdlib/wattson/cpu_idle.sql
index 98c8d17..5c8c6b5 100644
--- a/src/trace_processor/perfetto_sql/stdlib/wattson/cpu_idle.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/wattson/cpu_idle.sql
@@ -17,8 +17,7 @@
INCLUDE PERFETTO MODULE wattson.device_infos;
-- Get the corresponding deep idle time offset based on device and CPU.
-CREATE PERFETTO TABLE _filtered_deep_idle_offsets
-AS
+CREATE PERFETTO VIEW _filtered_deep_idle_offsets AS
SELECT cpu, offset_ns
FROM _device_cpu_deep_idle_offsets as offsets
JOIN _wattson_device as device
@@ -27,8 +26,7 @@
-- Adjust duration of active portion to be slightly longer to account for
-- overhead cost of transitioning out of deep idle. This is done because the
-- device is active and consumes power for longer than the logs actually report.
-CREATE PERFETTO TABLE _adjusted_deep_idle
-AS
+CREATE PERFETTO TABLE _adjusted_deep_idle AS
WITH
idle_prev AS (
SELECT
@@ -61,11 +59,36 @@
idle
FROM idle_prev
JOIN _filtered_deep_idle_offsets USING (cpu)
+ ),
+ _cpu_idle AS (
+ SELECT
+ ts,
+ LEAD(ts, 1, trace_end()) OVER (PARTITION BY cpu ORDER by ts) - ts as dur,
+ cpu,
+ cast_int!(IIF(idle = 4294967295, -1, idle)) AS idle
+ FROM idle_mod
+ ),
+ -- Get first idle transition per CPU
+ first_cpu_idle_slices AS (
+ SELECT ts, cpu FROM _cpu_idle
+ GROUP BY cpu
+ ORDER by ts ASC
)
+-- Prepend NULL slices up to first idle events on a per CPU basis
+SELECT
+ -- Construct slices from first cpu ts up to first freq event for each cpu
+ trace_start() as ts,
+ first_slices.ts - trace_start() as dur,
+ first_slices.cpu,
+ NULL as idle
+FROM first_cpu_idle_slices as first_slices
+WHERE dur > 0
+UNION ALL
SELECT
ts,
- LEAD(ts, 1, trace_end()) OVER (PARTITION BY cpu ORDER by ts) - ts as dur,
+ dur,
cpu,
- cast_int!(IIF(idle = 4294967295, -1, idle)) AS idle
-FROM idle_mod;
-
+ idle
+FROM _cpu_idle
+-- Some durations are 0 post-adjustment and won't work with interval intersect
+WHERE dur > 0;
diff --git a/src/trace_processor/perfetto_sql/stdlib/wattson/cpu_split.sql b/src/trace_processor/perfetto_sql/stdlib/wattson/cpu_split.sql
index 8c496d9..3d35a94 100644
--- a/src/trace_processor/perfetto_sql/stdlib/wattson/cpu_split.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/wattson/cpu_split.sql
@@ -13,208 +13,206 @@
-- See the License for the specific language governing permissions and
-- limitations under the License.
-INCLUDE PERFETTO MODULE linux.cpu.frequency;
+INCLUDE PERFETTO MODULE android.suspend;
+INCLUDE PERFETTO MODULE intervals.intersect;
INCLUDE PERFETTO MODULE time.conversion;
INCLUDE PERFETTO MODULE wattson.arm_dsu;
-INCLUDE PERFETTO MODULE wattson.cpu_idle;
+INCLUDE PERFETTO MODULE wattson.cpu_freq_idle;
INCLUDE PERFETTO MODULE wattson.curves.utils;
INCLUDE PERFETTO MODULE wattson.device_infos;
-CREATE PERFETTO TABLE _cpu_freq
-AS
-SELECT
- ts,
- dur,
- freq,
- cf.cpu,
- d_map.policy
-FROM cpu_frequency_counters as cf
-JOIN _dev_cpu_policy_map as d_map
-ON cf.cpu = d_map.cpu;
-
--- Combines idle and freq tables of all CPUs to create system state.
-CREATE VIRTUAL TABLE _idle_freq
-USING
- SPAN_OUTER_JOIN(
- _cpu_freq partitioned cpu, _adjusted_deep_idle partitioned cpu
- );
-
--- Add extra column indicating that frequency info are present
-CREATE PERFETTO TABLE _valid_window
-AS
-WITH window_start AS (
- SELECT ts as start_ts
- FROM _idle_freq
- WHERE cpu = 0 and freq GLOB '*[0-9]*'
- ORDER BY ts ASC
- LIMIT 1
-),
-window_end AS (
- SELECT ts + dur as end_ts
- FROM cpu_frequency_counters
- ORDER by ts DESC
- LIMIT 1
+-- Helper macro to do pivot function without policy information
+CREATE PERFETTO MACRO _stats_wo_policy_subquery(
+ cpu Expr, curve_col ColumnName, freq_col ColumnName, idle_col ColumnName
)
-SELECT
- start_ts as ts,
- end_ts - start_ts as dur
-FROM window_start, window_end;
+RETURNS TableOrSubquery AS (
+ SELECT
+ ts,
+ dur,
+ curve_value as $curve_col,
+ freq as $freq_col,
+ idle as $idle_col
+ FROM _idle_freq_materialized
+ WHERE cpu = $cpu
+);
-CREATE VIRTUAL TABLE _idle_freq_filtered
+-- Helper macro to do pivot function with policy information
+CREATE PERFETTO MACRO _stats_w_policy_subquery(
+ cpu Expr,
+ policy_col ColumnName,
+ curve_col ColumnName,
+ freq_col ColumnName,
+ idle_col ColumnName
+)
+RETURNS TableOrSubquery AS (
+ SELECT
+ ts,
+ dur,
+ policy AS $policy_col,
+ curve_value as $curve_col,
+ freq as $freq_col,
+ idle as $idle_col
+ FROM _idle_freq_materialized
+ WHERE cpu = $cpu
+);
+
+CREATE PERFETTO TABLE _stats_cpu0 AS
+SELECT * FROM _stats_wo_policy_subquery!(0, cpu0_curve, freq_0, idle_0);
+
+CREATE PERFETTO TABLE _stats_cpu1 AS
+SELECT * FROM _stats_wo_policy_subquery!(1, cpu1_curve, freq_1, idle_1);
+
+CREATE PERFETTO TABLE _stats_cpu2 AS
+SELECT * FROM _stats_wo_policy_subquery!(2, cpu2_curve, freq_2, idle_2);
+
+CREATE PERFETTO TABLE _stats_cpu3 AS
+SELECT * FROM _stats_wo_policy_subquery!(3, cpu3_curve, freq_3, idle_3);
+
+CREATE PERFETTO TABLE _stats_cpu4 AS
+SELECT * FROM _stats_w_policy_subquery!(4, policy_4, cpu4_curve, freq_4, idle_4);
+
+CREATE PERFETTO TABLE _stats_cpu5 AS
+SELECT * FROM _stats_w_policy_subquery!(5, policy_5, cpu5_curve, freq_5, idle_5);
+
+CREATE PERFETTO TABLE _stats_cpu6 AS
+SELECT * FROM _stats_w_policy_subquery!(6, policy_6, cpu6_curve, freq_6, idle_6);
+
+CREATE PERFETTO TABLE _stats_cpu7 AS
+SELECT * FROM _stats_w_policy_subquery!(7, policy_7, cpu7_curve, freq_7, idle_7);
+
+CREATE PERFETTO TABLE _stats_cpu0123_suspend AS
+SELECT
+ ii.ts,
+ ii.dur,
+ id_0 as cpu0_id, id_1 as cpu1_id, id_2 as cpu2_id, id_3 as cpu3_id,
+ ss.power_state = 'suspended' as suspended
+FROM _interval_intersect!(
+ (
+ _ii_subquery!(_stats_cpu0),
+ _ii_subquery!(_stats_cpu1),
+ _ii_subquery!(_stats_cpu2),
+ _ii_subquery!(_stats_cpu3),
+ -- Includes suspend AND awake portions, which will cover entire trace and
+ -- allows us to use _interval_intersect instead of SPAN_OUTER_JOIN()
+ _ii_subquery!(android_suspend_state)
+ ),
+ ()
+) as ii
+JOIN android_suspend_state AS ss ON ss._auto_id = id_4;
+
+CREATE PERFETTO TABLE _stats_cpu4567 AS
+SELECT
+ ii.ts,
+ ii.dur,
+ id_0 as cpu4_id, id_1 as cpu5_id, id_2 as cpu6_id, id_3 as cpu7_id
+FROM _interval_intersect!(
+ (
+ _ii_subquery!(_stats_cpu4),
+ _ii_subquery!(_stats_cpu5),
+ _ii_subquery!(_stats_cpu6),
+ _ii_subquery!(_stats_cpu7)
+ ),
+ ()
+) as ii;
+
+-- SPAN OUTER JOIN because sometimes CPU4/5/6/7 are empty tables
+CREATE VIRTUAL TABLE _stats_cpu01234567_suspend
USING
- SPAN_JOIN(_valid_window, _idle_freq);
-
--- Start matching split CPUs with curves
-CREATE PERFETTO TABLE _idle_freq_materialized
-AS
-SELECT
- iff.ts, iff.dur, iff.cpu, iff.policy, iff.freq, iff.idle, lut.curve_value
-FROM _idle_freq_filtered iff
--- Left join since some CPUs may only match the 2D LUT
-LEFT JOIN _filtered_curves_1d lut ON
- iff.policy = lut.policy AND
- iff.freq = lut.freq_khz AND
- iff.idle = lut.idle;
-
-CREATE PERFETTO TABLE _stats_cpu0
-AS
-SELECT
- ts,
- dur,
- curve_value as cpu0_curve,
- freq as freq_0,
- idle as idle_0
-FROM _idle_freq_materialized
-WHERE cpu = 0;
-
-CREATE PERFETTO TABLE _stats_cpu1
-AS
-SELECT
- ts,
- dur,
- curve_value as cpu1_curve,
- freq as freq_1,
- idle as idle_1
-FROM _idle_freq_materialized
-WHERE cpu = 1;
-
-CREATE PERFETTO TABLE _stats_cpu2
-AS
-SELECT
- ts,
- dur,
- curve_value as cpu2_curve,
- freq as freq_2,
- idle as idle_2
-FROM _idle_freq_materialized
-WHERE cpu = 2;
-
-CREATE PERFETTO TABLE _stats_cpu3
-AS
-SELECT
- ts,
- dur,
- curve_value as cpu3_curve,
- freq as freq_3,
- idle as idle_3
-FROM _idle_freq_materialized
-WHERE cpu = 3;
-
-CREATE PERFETTO TABLE _stats_cpu4
-AS
-SELECT
- ts,
- dur,
- policy as policy_4,
- curve_value as cpu4_curve,
- freq as freq_4,
- idle as idle_4
-FROM _idle_freq_materialized
-WHERE cpu = 4;
-
-CREATE PERFETTO TABLE _stats_cpu5
-AS
-SELECT
- ts,
- dur,
- policy as policy_5,
- curve_value as cpu5_curve,
- freq as freq_5,
- idle as idle_5
-FROM _idle_freq_materialized
-WHERE cpu = 5;
-
-CREATE PERFETTO TABLE _stats_cpu6
-AS
-SELECT
- ts,
- dur,
- policy as policy_6,
- curve_value as cpu6_curve,
- freq as freq_6,
- idle as idle_6
-FROM _idle_freq_materialized
-WHERE cpu = 6;
-
-CREATE PERFETTO TABLE _stats_cpu7
-AS
-SELECT
- ts,
- dur,
- policy as policy_7,
- curve_value as cpu7_curve,
- freq as freq_7,
- idle as idle_7
-FROM _idle_freq_materialized
-WHERE cpu = 7;
-
-CREATE VIRTUAL TABLE _stats_cpu01
-USING
- SPAN_OUTER_JOIN(_stats_cpu1, _stats_cpu0);
-
-CREATE VIRTUAL TABLE _stats_cpu012
-USING
- SPAN_OUTER_JOIN(_stats_cpu2, _stats_cpu01);
-
-CREATE VIRTUAL TABLE _stats_cpu0123
-USING
- SPAN_OUTER_JOIN(_stats_cpu3, _stats_cpu012);
-
-CREATE VIRTUAL TABLE _stats_cpu01234
-USING
- SPAN_OUTER_JOIN(_stats_cpu4, _stats_cpu0123);
-
-CREATE VIRTUAL TABLE _stats_cpu012345
-USING
- SPAN_OUTER_JOIN(_stats_cpu5, _stats_cpu01234);
-
-CREATE VIRTUAL TABLE _stats_cpu0123456
-USING
- SPAN_OUTER_JOIN(_stats_cpu6, _stats_cpu012345);
-
-CREATE VIRTUAL TABLE _stats_cpu01234567
-USING
- SPAN_OUTER_JOIN(_stats_cpu7, _stats_cpu0123456);
-
--- get suspend resume state as logged by ftrace.
-CREATE PERFETTO TABLE _suspend_slice
-AS
-SELECT
- ts, dur, TRUE AS suspended
-FROM slice
-WHERE name GLOB "timekeeping_freeze(0)";
-
--- Combine suspend information with CPU idle and frequency system states.
-CREATE VIRTUAL TABLE _idle_freq_suspend_slice
-USING
- SPAN_OUTER_JOIN(_stats_cpu01234567, _suspend_slice);
+ SPAN_OUTER_JOIN(_stats_cpu0123_suspend, _stats_cpu4567);
-- Combine system state so that it has idle, freq, and L3 hit info.
CREATE VIRTUAL TABLE _idle_freq_l3_hit_slice
USING
- SPAN_OUTER_JOIN(_idle_freq_suspend_slice, _arm_l3_hit_rate);
+ SPAN_OUTER_JOIN(_stats_cpu01234567_suspend, _arm_l3_hit_rate);
-- Combine system state so that it has idle, freq, L3 hit, and L3 miss info.
CREATE VIRTUAL TABLE _idle_freq_l3_hit_l3_miss_slice
USING
SPAN_OUTER_JOIN(_idle_freq_l3_hit_slice, _arm_l3_miss_rate);
+
+-- Does calculations for CPUs that are independent of other CPUs or frequencies
+-- This is the last generic table before going to device specific table calcs
+CREATE PERFETTO TABLE _w_independent_cpus_calc
+AS
+SELECT
+ base.ts,
+ base.dur,
+ cast_int!(l3_hit_rate * base.dur) as l3_hit_count,
+ cast_int!(l3_miss_rate * base.dur) as l3_miss_count,
+ freq_0,
+ idle_0,
+ freq_1,
+ idle_1,
+ freq_2,
+ idle_2,
+ freq_3,
+ idle_3,
+ freq_4,
+ idle_4,
+ freq_5,
+ idle_5,
+ freq_6,
+ idle_6,
+ freq_7,
+ idle_7,
+ policy_4,
+ policy_5,
+ policy_6,
+ policy_7,
+ IIF(
+ suspended,
+ 1,
+ MIN(
+ IFNULL(idle_0, 1),
+ IFNULL(idle_1, 1),
+ IFNULL(idle_2, 1),
+ IFNULL(idle_3, 1)
+ )
+ ) as no_static,
+ IIF(suspended, 0.0, cpu0_curve) as cpu0_curve,
+ IIF(suspended, 0.0, cpu1_curve) as cpu1_curve,
+ IIF(suspended, 0.0, cpu2_curve) as cpu2_curve,
+ IIF(suspended, 0.0, cpu3_curve) as cpu3_curve,
+ IIF(suspended, 0.0, cpu4_curve) as cpu4_curve,
+ IIF(suspended, 0.0, cpu5_curve) as cpu5_curve,
+ IIF(suspended, 0.0, cpu6_curve) as cpu6_curve,
+ IIF(suspended, 0.0, cpu7_curve) as cpu7_curve,
+ -- If dependency CPUs are active, then that CPU could contribute static power
+ IIF(idle_4 = -1, lut4.curve_value, -1) as static_4,
+ IIF(idle_5 = -1, lut5.curve_value, -1) as static_5,
+ IIF(idle_6 = -1, lut6.curve_value, -1) as static_6,
+ IIF(idle_7 = -1, lut7.curve_value, -1) as static_7
+FROM _idle_freq_l3_hit_l3_miss_slice as base
+-- Get CPU power curves for CPUs guaranteed on device
+JOIN _stats_cpu0 ON _stats_cpu0._auto_id = base.cpu0_id
+JOIN _stats_cpu1 ON _stats_cpu1._auto_id = base.cpu1_id
+JOIN _stats_cpu2 ON _stats_cpu2._auto_id = base.cpu2_id
+JOIN _stats_cpu3 ON _stats_cpu3._auto_id = base.cpu3_id
+-- Get CPU power curves for CPUs that aren't always present
+LEFT JOIN _stats_cpu4 ON _stats_cpu4._auto_id = base.cpu4_id
+LEFT JOIN _stats_cpu5 ON _stats_cpu5._auto_id = base.cpu5_id
+LEFT JOIN _stats_cpu6 ON _stats_cpu6._auto_id = base.cpu6_id
+LEFT JOIN _stats_cpu7 ON _stats_cpu7._auto_id = base.cpu7_id
+-- Match power curves if possible on CPUs that decide 2D dependence
+LEFT JOIN _filtered_curves_2d lut4 ON
+ _stats_cpu0.freq_0 = lut4.freq_khz AND
+ _stats_cpu4.policy_4 = lut4.other_policy AND
+ _stats_cpu4.freq_4 = lut4.other_freq_khz AND
+ lut4.idle = 255
+LEFT JOIN _filtered_curves_2d lut5 ON
+ _stats_cpu0.freq_0 = lut5.freq_khz AND
+ _stats_cpu5.policy_5 = lut5.other_policy AND
+ _stats_cpu5.freq_5 = lut5.other_freq_khz AND
+ lut5.idle = 255
+LEFT JOIN _filtered_curves_2d lut6 ON
+ _stats_cpu0.freq_0 = lut6.freq_khz AND
+ _stats_cpu6.policy_6 = lut6.other_policy AND
+ _stats_cpu6.freq_6 = lut6.other_freq_khz AND
+ lut6.idle = 255
+LEFT JOIN _filtered_curves_2d lut7 ON
+ _stats_cpu0.freq_0 = lut7.freq_khz AND
+ _stats_cpu7.policy_7 = lut7.other_policy AND
+ _stats_cpu7.freq_7 = lut7.other_freq_khz AND
+ lut7.idle = 255
+-- Needs to be at least 1us to reduce inconsequential rows.
+WHERE base.dur > time_from_us(1);
diff --git a/src/trace_processor/perfetto_sql/stdlib/wattson/curves/device.sql b/src/trace_processor/perfetto_sql/stdlib/wattson/curves/device.sql
index de8e173..e65cf1c 100644
--- a/src/trace_processor/perfetto_sql/stdlib/wattson/curves/device.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/wattson/curves/device.sql
@@ -54,7 +54,42 @@
("Tensor", 6, 2507000, 0, 1267.19, 66.14, 0),
("Tensor", 6, 2630000, 0, 1500.6, 82.36, 0),
("Tensor", 6, 2704000, 0, 1660.81, 95.11, 0),
- ("Tensor", 6, 2802000, 0, 1942.89, 121.43, 0)
+ ("Tensor", 6, 2802000, 0, 1942.89, 121.43, 0),
+ ("Tensor G4", 4, 357000, 0, 39.49, 6.1, 0),
+ ("Tensor G4", 4, 578000, 0, 62.09, 6.5, 0),
+ ("Tensor G4", 4, 648000, 0, 70.05, 6.93, 0),
+ ("Tensor G4", 4, 787000, 0, 83.26, 7.31, 0),
+ ("Tensor G4", 4, 910000, 0, 97.12, 7.55, 0),
+ ("Tensor G4", 4, 1065000, 0, 116.15, 7.9, 0),
+ ("Tensor G4", 4, 1221000, 0, 138.37, 8.47, 0),
+ ("Tensor G4", 4, 1328000, 0, 155.59, 8.94, 0),
+ ("Tensor G4", 4, 1418000, 0, 172.52, 9.37, 0),
+ ("Tensor G4", 4, 1549000, 0, 200.69, 10.21, 0),
+ ("Tensor G4", 4, 1795000, 0, 267.18, 11.89, 0),
+ ("Tensor G4", 4, 1945000, 0, 317.06, 13.58, 0),
+ ("Tensor G4", 4, 2130000, 0, 388.15, 16.02, 0),
+ ("Tensor G4", 4, 2245000, 0, 430.4, 17.54, 0),
+ ("Tensor G4", 4, 2367000, 0, 504.35, 20.92, 0),
+ ("Tensor G4", 4, 2450000, 0, 579.03, 23.39, 0),
+ ("Tensor G4", 4, 2600000, 0, 674.24, 31.07, 0),
+ ("Tensor G4", 7, 700000, 0, 211.41, 17.97, 0),
+ ("Tensor G4", 7, 1164000, 0, 375.49, 20.24, 0),
+ ("Tensor G4", 7, 1396000, 0, 491.17, 22.35, 0),
+ ("Tensor G4", 7, 1557000, 0, 589.06, 24.29, 0),
+ ("Tensor G4", 7, 1745000, 0, 742.95, 26.79, 0),
+ ("Tensor G4", 7, 1885000, 0, 862.73, 28.61, 0),
+ ("Tensor G4", 7, 1999000, 0, 965.94, 30.04, 0),
+ ("Tensor G4", 7, 2147000, 0, 1136.58, 32.65, 0),
+ ("Tensor G4", 7, 2294000, 0, 1309.39, 35.62, 0),
+ ("Tensor G4", 7, 2363000, 0, 1415.82, 37.93, 0),
+ ("Tensor G4", 7, 2499000, 0, 1669.61, 42.96, 0),
+ ("Tensor G4", 7, 2687000, 0, 2052.32, 52.16, 0),
+ ("Tensor G4", 7, 2802000, 0, 2354.18, 60.2, 0),
+ ("Tensor G4", 7, 2914000, 0, 2789.17, 77.16, 0),
+ ("Tensor G4", 7, 2943000, 0, 2840.06, 79.64, 0),
+ ("Tensor G4", 7, 2970000, 0, 2949.03, 84.78, 0),
+ ("Tensor G4", 7, 3015000, 0, 3029.38, 87.22, 0),
+ ("Tensor G4", 7, 3105000, 0, 3327.56, 99.47, 0)
)
select * from data;
@@ -315,7 +350,157 @@
("Tensor", 1803000, 6, 2507000, 34.89, 240.7, 3.58, 0),
("Tensor", 1803000, 6, 2630000, 35.21, 150.76, 3.42, 0),
("Tensor", 1803000, 6, 2704000, 35.2, 277.28, 3.44, 0),
- ("Tensor", 1803000, 6, 2802000, 35.12, 269.2, 3.62, 0)
+ ("Tensor", 1803000, 6, 2802000, 35.12, 269.2, 3.62, 0),
+ ("Tensor G4", 820000, 255, 610000, 3.3, 47.6, 1.04, 0),
+ ("Tensor G4", 820000, 255, 820000, 6.77, 65.48, 1.17, 0),
+ ("Tensor G4", 820000, 255, 970000, 8.61, 78.56, 1.28, 0),
+ ("Tensor G4", 820000, 255, 1098000, 12.5, 92.7, 1.28, 0),
+ ("Tensor G4", 820000, 255, 1197000, 15.24, 110.72, 1.46, 0),
+ ("Tensor G4", 820000, 255, 1328000, 21.73, 134.04, 1.58, 0),
+ ("Tensor G4", 820000, 255, 1444000, 26.89, 151.02, 1.75, 0),
+ ("Tensor G4", 820000, 255, 1548000, 31.53, 164.93, 1.8, 0),
+ ("Tensor G4", 820000, 255, 1704000, 43.86, 157.18, 2.24, 0),
+ ("Tensor G4", 820000, 255, 1800000, 52.1, 137.62, 2.64, 0),
+ ("Tensor G4", 820000, 255, 1880000, 59.74, 145.28, 2.44, 0),
+ ("Tensor G4", 820000, 255, 1950000, 71.34, 156.19, 3.12, 0),
+ ("Tensor G4", 820000, 255, 2024000, 86.3, 155.05, 3.53, 0),
+ ("Tensor G4", 820000, 255, 2120000, 112.45, 176.15, 4.74, 0),
+ ("Tensor G4", 820000, 255, 2150000, 112.1, 155.11, 4.69, 0),
+ ("Tensor G4", 955000, 255, 610000, 6.18, 56.03, 0.68, 0),
+ ("Tensor G4", 955000, 255, 820000, 6.39, 74.22, 1.21, 0),
+ ("Tensor G4", 955000, 255, 970000, 9.18, 82.2, 1.26, 0),
+ ("Tensor G4", 955000, 255, 1098000, 12.62, 98.11, 1.34, 0),
+ ("Tensor G4", 955000, 255, 1197000, 15.72, 117.95, 1.41, 0),
+ ("Tensor G4", 955000, 255, 1328000, 21.94, 141.91, 1.57, 0),
+ ("Tensor G4", 955000, 255, 1444000, 27.38, 162.99, 1.85, 0),
+ ("Tensor G4", 955000, 255, 1548000, 31.94, 175.19, 1.72, 0),
+ ("Tensor G4", 955000, 255, 1704000, 43.84, 130.38, 2.44, 0),
+ ("Tensor G4", 955000, 255, 1800000, 52.67, 117.99, 2.5, 0),
+ ("Tensor G4", 955000, 255, 1880000, 59.69, 145.08, 2.91, 0),
+ ("Tensor G4", 955000, 255, 1950000, 73.33, 141.24, 3.23, 0),
+ ("Tensor G4", 955000, 255, 2024000, 86.96, 171.19, 3.7, 0),
+ ("Tensor G4", 955000, 255, 2120000, 112.7, 188.38, 5.0, 0),
+ ("Tensor G4", 955000, 255, 2150000, 111.86, 179.34, 5.53, 0),
+ ("Tensor G4", 1098000, 255, 610000, 7.23, 66.6, 1.26, 0),
+ ("Tensor G4", 1098000, 255, 820000, 8.04, 80.19, 1.21, 0),
+ ("Tensor G4", 1098000, 255, 970000, 9.56, 90.34, 1.22, 0),
+ ("Tensor G4", 1098000, 255, 1098000, 12.86, 109.5, 1.33, 0),
+ ("Tensor G4", 1098000, 255, 1197000, 16.57, 120.41, 1.07, 0),
+ ("Tensor G4", 1098000, 255, 1328000, 22.15, 145.31, 1.54, 0),
+ ("Tensor G4", 1098000, 255, 1444000, 27.91, 163.9, 1.68, 0),
+ ("Tensor G4", 1098000, 255, 1548000, 32.01, 174.89, 1.87, 0),
+ ("Tensor G4", 1098000, 255, 1704000, 44.5, 139.63, 2.24, 0),
+ ("Tensor G4", 1098000, 255, 1800000, 53.21, 140.32, 2.52, 0),
+ ("Tensor G4", 1098000, 255, 1880000, 60.44, 157.97, 2.83, 0),
+ ("Tensor G4", 1098000, 255, 1950000, 73.65, 169.76, 3.28, 0),
+ ("Tensor G4", 1098000, 255, 2024000, 87.15, 182.83, 3.98, 0),
+ ("Tensor G4", 1098000, 255, 2120000, 114.08, 187.49, 4.17, 0),
+ ("Tensor G4", 1098000, 255, 2150000, 113.79, 189.6, 4.65, 0),
+ ("Tensor G4", 1197000, 255, 610000, 8.34, 75.11, 1.27, 0),
+ ("Tensor G4", 1197000, 255, 820000, 9.54, 84.82, 1.14, 0),
+ ("Tensor G4", 1197000, 255, 970000, 10.37, 89.93, 1.18, 0),
+ ("Tensor G4", 1197000, 255, 1098000, 12.81, 104.44, 1.37, 0),
+ ("Tensor G4", 1197000, 255, 1197000, 16.36, 129.81, 1.39, 0),
+ ("Tensor G4", 1197000, 255, 1328000, 22.4, 145.01, 1.64, 0),
+ ("Tensor G4", 1197000, 255, 1444000, 28.1, 170.53, 1.61, 0),
+ ("Tensor G4", 1197000, 255, 1548000, 32.23, 186.28, 1.91, 0),
+ ("Tensor G4", 1197000, 255, 1704000, 44.93, 156.69, 2.32, 0),
+ ("Tensor G4", 1197000, 255, 1800000, 53.17, 151.91, 2.43, 0),
+ ("Tensor G4", 1197000, 255, 1880000, 60.94, 141.69, 2.72, 0),
+ ("Tensor G4", 1197000, 255, 1950000, 73.72, 189.86, 3.42, 0),
+ ("Tensor G4", 1197000, 255, 2024000, 87.87, 158.58, 3.7, 0),
+ ("Tensor G4", 1197000, 255, 2120000, 114.16, 193.12, 4.81, 0),
+ ("Tensor G4", 1197000, 255, 2150000, 113.59, 191.22, 4.8, 0),
+ ("Tensor G4", 1328000, 255, 610000, 10.73, 90.03, 1.33, 0),
+ ("Tensor G4", 1328000, 255, 820000, 11.88, 99.06, 1.31, 0),
+ ("Tensor G4", 1328000, 255, 970000, 12.77, 106.72, 1.33, 0),
+ ("Tensor G4", 1328000, 255, 1098000, 13.12, 110.06, 1.39, 0),
+ ("Tensor G4", 1328000, 255, 1197000, 16.68, 127.98, 1.33, 0),
+ ("Tensor G4", 1328000, 255, 1328000, 22.66, 154.27, 1.68, 0),
+ ("Tensor G4", 1328000, 255, 1444000, 28.49, 174.25, 1.72, 0),
+ ("Tensor G4", 1328000, 255, 1548000, 32.16, 191.25, 1.73, 0),
+ ("Tensor G4", 1328000, 255, 1704000, 44.27, 129.41, 2.25, 0),
+ ("Tensor G4", 1328000, 255, 1800000, 53.79, 154.61, 2.51, 0),
+ ("Tensor G4", 1328000, 255, 1880000, 61.04, 163.47, 2.68, 0),
+ ("Tensor G4", 1328000, 255, 1950000, 75.05, 189.16, 3.08, 0),
+ ("Tensor G4", 1328000, 255, 2024000, 89.05, 204.54, 3.43, 0),
+ ("Tensor G4", 1328000, 255, 2120000, 115.33, 210.24, 4.36, 0),
+ ("Tensor G4", 1328000, 255, 2150000, 114.98, 206.93, 4.34, 0),
+ ("Tensor G4", 1425000, 255, 610000, 13.32, 101.33, 1.43, 0),
+ ("Tensor G4", 1425000, 255, 820000, 14.56, 111.02, 1.46, 0),
+ ("Tensor G4", 1425000, 255, 970000, 15.11, 121.09, 1.47, 0),
+ ("Tensor G4", 1425000, 255, 1098000, 16.25, 128.03, 1.41, 0),
+ ("Tensor G4", 1425000, 255, 1197000, 16.68, 127.43, 1.45, 0),
+ ("Tensor G4", 1425000, 255, 1328000, 22.57, 156.98, 1.68, 0),
+ ("Tensor G4", 1425000, 255, 1444000, 28.81, 182.29, 1.72, 0),
+ ("Tensor G4", 1425000, 255, 1548000, 33.08, 198.0, 1.83, 0),
+ ("Tensor G4", 1425000, 255, 1704000, 45.21, 162.21, 2.12, 0),
+ ("Tensor G4", 1425000, 255, 1800000, 54.37, 167.27, 2.5, 0),
+ ("Tensor G4", 1425000, 255, 1880000, 61.48, 116.14, 2.89, 0),
+ ("Tensor G4", 1425000, 255, 1950000, 74.85, 180.6, 3.49, 0),
+ ("Tensor G4", 1425000, 255, 2024000, 89.32, 187.51, 3.6, 0),
+ ("Tensor G4", 1425000, 255, 2120000, 115.2, 203.97, 4.57, 0),
+ ("Tensor G4", 1425000, 255, 2150000, 115.53, 210.01, 4.25, 0),
+ ("Tensor G4", 1548000, 255, 610000, 16.36, 123.83, 1.45, 0),
+ ("Tensor G4", 1548000, 255, 820000, 17.5, 128.9, 1.62, 0),
+ ("Tensor G4", 1548000, 255, 970000, 18.34, 139.52, 1.58, 0),
+ ("Tensor G4", 1548000, 255, 1098000, 19.32, 149.77, 1.53, 0),
+ ("Tensor G4", 1548000, 255, 1197000, 19.8, 152.01, 1.43, 0),
+ ("Tensor G4", 1548000, 255, 1328000, 22.59, 159.55, 1.61, 0),
+ ("Tensor G4", 1548000, 255, 1444000, 28.75, 198.79, 1.86, 0),
+ ("Tensor G4", 1548000, 255, 1548000, 33.46, 211.95, 1.77, 0),
+ ("Tensor G4", 1548000, 255, 1704000, 46.36, 169.26, 2.11, 0),
+ ("Tensor G4", 1548000, 255, 1800000, 54.71, 177.24, 2.42, 0),
+ ("Tensor G4", 1548000, 255, 1880000, 62.25, 145.44, 2.76, 0),
+ ("Tensor G4", 1548000, 255, 1950000, 75.84, 191.27, 3.09, 0),
+ ("Tensor G4", 1548000, 255, 2024000, 88.97, 198.32, 3.86, 0),
+ ("Tensor G4", 1548000, 255, 2120000, 115.79, 232.48, 4.72, 0),
+ ("Tensor G4", 1548000, 255, 2150000, 115.31, 222.76, 4.71, 0),
+ ("Tensor G4", 1696000, 255, 610000, 19.61, 132.84, 1.68, 0),
+ ("Tensor G4", 1696000, 255, 820000, 21.09, 151.29, 1.59, 0),
+ ("Tensor G4", 1696000, 255, 970000, 21.92, 157.59, 1.75, 0),
+ ("Tensor G4", 1696000, 255, 1098000, 22.76, 163.33, 1.59, 0),
+ ("Tensor G4", 1696000, 255, 1197000, 23.53, 173.96, 1.67, 0),
+ ("Tensor G4", 1696000, 255, 1328000, 24.28, 184.05, 1.58, 0),
+ ("Tensor G4", 1696000, 255, 1444000, 29.47, 203.99, 1.77, 0),
+ ("Tensor G4", 1696000, 255, 1548000, 33.94, 225.78, 1.7, 0),
+ ("Tensor G4", 1696000, 255, 1704000, 46.92, 171.8, 2.16, 0),
+ ("Tensor G4", 1696000, 255, 1800000, 55.32, 217.17, 2.38, 0),
+ ("Tensor G4", 1696000, 255, 1880000, 62.55, 224.61, 2.77, 0),
+ ("Tensor G4", 1696000, 255, 1950000, 76.98, 204.48, 2.82, 0),
+ ("Tensor G4", 1696000, 255, 2024000, 90.13, 226.98, 3.76, 0),
+ ("Tensor G4", 1696000, 255, 2120000, 116.77, 245.48, 4.52, 0),
+ ("Tensor G4", 1696000, 255, 2150000, 112.69, 222.79, 6.43, 0),
+ ("Tensor G4", 1849000, 255, 610000, 29.35, 176.28, 1.8, 0),
+ ("Tensor G4", 1849000, 255, 820000, 30.31, 187.61, 1.94, 0),
+ ("Tensor G4", 1849000, 255, 970000, 31.7, 202.99, 2.05, 0),
+ ("Tensor G4", 1849000, 255, 1098000, 32.48, 207.22, 2.01, 0),
+ ("Tensor G4", 1849000, 255, 1197000, 33.7, 222.81, 1.9, 0),
+ ("Tensor G4", 1849000, 255, 1328000, 34.79, 229.5, 1.9, 0),
+ ("Tensor G4", 1849000, 255, 1444000, 35.97, 228.13, 1.91, 0),
+ ("Tensor G4", 1849000, 255, 1548000, 36.59, 235.62, 2.01, 0),
+ ("Tensor G4", 1849000, 255, 1704000, 47.47, 233.89, 2.16, 0),
+ ("Tensor G4", 1849000, 255, 1800000, 55.69, 211.69, 2.53, 0),
+ ("Tensor G4", 1849000, 255, 1880000, 63.47, 225.85, 2.39, 0),
+ ("Tensor G4", 1849000, 255, 1950000, 77.22, 209.34, 3.0, 0),
+ ("Tensor G4", 1849000, 255, 2024000, 90.92, 230.3, 3.48, 0),
+ ("Tensor G4", 1849000, 255, 2120000, 117.19, 247.78, 4.49, 0),
+ ("Tensor G4", 1849000, 255, 2150000, 117.53, 239.55, 4.32, 0),
+ ("Tensor G4", 1950000, 255, 610000, 40.27, 197.26, 2.54, 0),
+ ("Tensor G4", 1950000, 255, 820000, 41.93, 221.2, 2.67, 0),
+ ("Tensor G4", 1950000, 255, 970000, 43.45, 239.45, 2.56, 0),
+ ("Tensor G4", 1950000, 255, 1098000, 44.27, 240.43, 2.64, 0),
+ ("Tensor G4", 1950000, 255, 1197000, 45.84, 259.94, 2.42, 0),
+ ("Tensor G4", 1950000, 255, 1328000, 47.03, 273.66, 2.55, 0),
+ ("Tensor G4", 1950000, 255, 1444000, 48.53, 267.32, 2.32, 0),
+ ("Tensor G4", 1950000, 255, 1548000, 49.59, 232.85, 2.35, 0),
+ ("Tensor G4", 1950000, 255, 1704000, 51.2, 234.87, 2.23, 0),
+ ("Tensor G4", 1950000, 255, 1800000, 55.47, 205.6, 2.67, 0),
+ ("Tensor G4", 1950000, 255, 1880000, 63.68, 201.13, 2.59, 0),
+ ("Tensor G4", 1950000, 255, 1950000, 77.22, 201.28, 3.13, 0),
+ ("Tensor G4", 1950000, 255, 2024000, 90.93, 230.61, 3.81, 0),
+ ("Tensor G4", 1950000, 255, 2120000, 118.19, 233.8, 4.28, 0),
+ ("Tensor G4", 1950000, 255, 2150000, 118.61, 240.57, 4.6, 0)
)
select * from data;
@@ -572,7 +757,157 @@
("Tensor", 1803000, 6, 2507000, 2.6630, 1.2641),
("Tensor", 1803000, 6, 2630000, 2.7385, 2.3263),
("Tensor", 1803000, 6, 2704000, 2.6901, 1.0629),
- ("Tensor", 1803000, 6, 2802000, 2.7476, 1.0673)
+ ("Tensor", 1803000, 6, 2802000, 2.7476, 1.0673),
+ ("Tensor G4", 820000, 255, 610000, 0.4824, 0.8357),
+ ("Tensor G4", 955000, 255, 610000, 0.4801, 0.852),
+ ("Tensor G4", 1098000, 255, 610000, 0.4988, 0.8219),
+ ("Tensor G4", 1197000, 255, 610000, 0.5025, 0.8369),
+ ("Tensor G4", 1328000, 255, 610000, 0.516, 0.8928),
+ ("Tensor G4", 1425000, 255, 610000, 0.5266, 0.8895),
+ ("Tensor G4", 1548000, 255, 610000, 0.5286, 0.915),
+ ("Tensor G4", 1696000, 255, 610000, 0.5288, 1.0169),
+ ("Tensor G4", 1849000, 255, 610000, 0.5326, 1.1313),
+ ("Tensor G4", 1950000, 255, 610000, 0.5495, 1.1839),
+ ("Tensor G4", 820000, 255, 820000, 0.4815, 0.8114),
+ ("Tensor G4", 955000, 255, 820000, 0.5055, 0.9356),
+ ("Tensor G4", 1098000, 255, 820000, 0.5168, 0.9567),
+ ("Tensor G4", 1197000, 255, 820000, 0.5228, 0.8653),
+ ("Tensor G4", 1328000, 255, 820000, 0.5228, 0.8895),
+ ("Tensor G4", 1425000, 255, 820000, 0.5309, 0.9692),
+ ("Tensor G4", 1548000, 255, 820000, 0.5461, 0.9987),
+ ("Tensor G4", 1696000, 255, 820000, 0.564, 1.053),
+ ("Tensor G4", 1849000, 255, 820000, 0.5649, 0.9087),
+ ("Tensor G4", 1950000, 255, 820000, 0.5737, 1.0893),
+ ("Tensor G4", 820000, 255, 970000, 0.4923, 0.852),
+ ("Tensor G4", 955000, 255, 970000, 0.5014, 0.8357),
+ ("Tensor G4", 1098000, 255, 970000, 0.4952, 1.0169),
+ ("Tensor G4", 1197000, 255, 970000, 0.5284, 0.8594),
+ ("Tensor G4", 1328000, 255, 970000, 0.5332, 0.8521),
+ ("Tensor G4", 1425000, 255, 970000, 0.5424, 0.8612),
+ ("Tensor G4", 1548000, 255, 970000, 0.5641, 0.883),
+ ("Tensor G4", 1696000, 255, 970000, 0.5524, 0.915),
+ ("Tensor G4", 1849000, 255, 970000, 0.5685, 0.8765),
+ ("Tensor G4", 1950000, 255, 970000, 0.5527, 0.8164),
+ ("Tensor G4", 820000, 255, 1098000, 0.5084, 0.947),
+ ("Tensor G4", 955000, 255, 1098000, 0.5177, 0.8058),
+ ("Tensor G4", 1098000, 255, 1098000, 0.5284, 0.763),
+ ("Tensor G4", 1197000, 255, 1098000, 0.596, 0.8633),
+ ("Tensor G4", 1328000, 255, 1098000, 0.596, 0.8633),
+ ("Tensor G4", 1425000, 255, 1098000, 0.5373, 0.7504),
+ ("Tensor G4", 1548000, 255, 1098000, 0.5186, 0.8604),
+ ("Tensor G4", 1696000, 255, 1098000, 0.5772, 89.73),
+ ("Tensor G4", 1849000, 255, 1098000, 0.5758, 0.9588),
+ ("Tensor G4", 1950000, 255, 1098000, 0.5904, 1.1074),
+ ("Tensor G4", 820000, 255, 1197000, 0.5105, 0.866),
+ ("Tensor G4", 955000, 255, 1197000, 0.5241, 0.8533),
+ ("Tensor G4", 1098000, 255, 1197000, 0.5347, 0.8569),
+ ("Tensor G4", 1197000, 255, 1197000, 0.524, 0.8318),
+ ("Tensor G4", 1328000, 255, 1197000, 0.5298, 0.8374),
+ ("Tensor G4", 1425000, 255, 1197000, 0.5201, 0.9479),
+ ("Tensor G4", 1548000, 255, 1197000, 0.497, 0.9093),
+ ("Tensor G4", 1696000, 255, 1197000, 0.5007, 0.8765),
+ ("Tensor G4", 1849000, 255, 1197000, 0.6107, 0.8682),
+ ("Tensor G4", 1950000, 255, 1197000, 0.5888, 0.9738),
+ ("Tensor G4", 820000, 255, 1328000, 0.534, 0.9162),
+ ("Tensor G4", 955000, 255, 1328000, 0.5551, 0.9499),
+ ("Tensor G4", 1098000, 255, 1328000, 0.556, 0.954),
+ ("Tensor G4", 1197000, 255, 1328000, 0.5711, 0.9117),
+ ("Tensor G4", 1328000, 255, 1328000, 0.5372, 0.956),
+ ("Tensor G4", 1425000, 255, 1328000, 0.5482, 0.934),
+ ("Tensor G4", 1548000, 255, 1328000, 0.5615, 0.7495),
+ ("Tensor G4", 1696000, 255, 1328000, 0.53, 0.9145),
+ ("Tensor G4", 1849000, 255, 1328000, 0.5504, 0.9478),
+ ("Tensor G4", 1950000, 255, 1328000, 0.6243, 0.9335),
+ ("Tensor G4", 820000, 255, 1444000, 0.5437, 0.9403),
+ ("Tensor G4", 955000, 255, 1444000, 0.5635, 0.9997),
+ ("Tensor G4", 1098000, 255, 1444000, 0.5711, 0.919),
+ ("Tensor G4", 1197000, 255, 1444000, 0.5595, 0.9455),
+ ("Tensor G4", 1328000, 255, 1444000, 0.5772, 0.9415),
+ ("Tensor G4", 1425000, 255, 1444000, 0.5507, 0.9582),
+ ("Tensor G4", 1548000, 255, 1444000, 0.5535, 0.8986),
+ ("Tensor G4", 1696000, 255, 1444000, 0.507, 0.9369),
+ ("Tensor G4", 1849000, 255, 1444000, 0.628, 0.8878),
+ ("Tensor G4", 1950000, 255, 1444000, 0.658, 1.0974),
+ ("Tensor G4", 820000, 255, 1548000, 0.578, 0.8322),
+ ("Tensor G4", 955000, 255, 1548000, 0.603, 0.9371),
+ ("Tensor G4", 1098000, 255, 1548000, 0.6163, 0.962),
+ ("Tensor G4", 1197000, 255, 1548000, 0.6105, 0.9323),
+ ("Tensor G4", 1328000, 255, 1548000, 0.589, 0.8355),
+ ("Tensor G4", 1425000, 255, 1548000, 0.5924, 0.9416),
+ ("Tensor G4", 1548000, 255, 1548000, 0.5611, 0.8911),
+ ("Tensor G4", 1696000, 255, 1548000, 0.5792, 0.905),
+ ("Tensor G4", 1849000, 255, 1548000, 0.6021, 0.9761),
+ ("Tensor G4", 1950000, 255, 1548000, 0.6579, 1.0438),
+ ("Tensor G4", 820000, 255, 1704000, 1.5584, 1.2388),
+ ("Tensor G4", 955000, 255, 1704000, 1.5287, 1.104),
+ ("Tensor G4", 1098000, 255, 1704000, 1.6412, 1.2419),
+ ("Tensor G4", 1197000, 255, 1704000, 1.9947, 1.318),
+ ("Tensor G4", 1328000, 255, 1704000, 1.6701, 1.178),
+ ("Tensor G4", 1425000, 255, 1704000, 1.5944, 1.3017),
+ ("Tensor G4", 1548000, 255, 1704000, 1.6769, 1.6058),
+ ("Tensor G4", 1696000, 255, 1704000, 1.7215, 1.8),
+ ("Tensor G4", 1849000, 255, 1704000, 1.9143, 2.0842),
+ ("Tensor G4", 1950000, 255, 1704000, 1.932, 2.8572),
+ ("Tensor G4", 820000, 255, 1800000, 1.575, 1.5401),
+ ("Tensor G4", 955000, 255, 1800000, 1.6833, 1.5087),
+ ("Tensor G4", 1098000, 255, 1800000, 1.5571, 2.0737),
+ ("Tensor G4", 1197000, 255, 1800000, 1.6815, 1.8473),
+ ("Tensor G4", 1328000, 255, 1800000, 1.7828, 1.666),
+ ("Tensor G4", 1425000, 255, 1800000, 1.8061, 1.8018),
+ ("Tensor G4", 1548000, 255, 1800000, 1.9956, 1.64),
+ ("Tensor G4", 1696000, 255, 1800000, 1.8592, 1.701),
+ ("Tensor G4", 1849000, 255, 1800000, 1.5758, 1.5364),
+ ("Tensor G4", 1950000, 255, 1800000, 1.9823, 1.7952),
+ ("Tensor G4", 820000, 255, 1880000, 1.7249, 1.7787),
+ ("Tensor G4", 955000, 255, 1880000, 1.7635, 2.0677),
+ ("Tensor G4", 1098000, 255, 1880000, 1.805, 1.5907),
+ ("Tensor G4", 1197000, 255, 1880000, 1.7293, 2.0645),
+ ("Tensor G4", 1328000, 255, 1880000, 1.918, 1.9844),
+ ("Tensor G4", 1425000, 255, 1880000, 2.0013, 1.5541),
+ ("Tensor G4", 1548000, 255, 1880000, 2.0504, 1.9877),
+ ("Tensor G4", 1696000, 255, 1880000, 1.9603, 1.8955),
+ ("Tensor G4", 1849000, 255, 1880000, 2.1168, 1.9674),
+ ("Tensor G4", 1950000, 255, 1880000, 2.2047, 2.4697),
+ ("Tensor G4", 820000, 255, 1950000, 1.9756, 1.4938),
+ ("Tensor G4", 955000, 255, 1950000, 1.90147, 1.8144),
+ ("Tensor G4", 1098000, 255, 1950000, 2.1539, 2.0811),
+ ("Tensor G4", 1197000, 255, 1950000, 2.2192, 1.8968),
+ ("Tensor G4", 1328000, 255, 1950000, 2.2974, 2.1572),
+ ("Tensor G4", 1425000, 255, 1950000, 2.3865, 2.2972),
+ ("Tensor G4", 1548000, 255, 1950000, 2.4361, 2.3942),
+ ("Tensor G4", 1696000, 255, 1950000, 2.8677, 2.2737),
+ ("Tensor G4", 1849000, 255, 1950000, 2.6123, 2.5181),
+ ("Tensor G4", 1950000, 255, 1950000, 2.7293, 2.5798),
+ ("Tensor G4", 820000, 255, 2024000, 2.0136, 1.7557),
+ ("Tensor G4", 955000, 255, 2024000, 1.9834, 1.6844),
+ ("Tensor G4", 1098000, 255, 2024000, 2.1129, 1.8735),
+ ("Tensor G4", 1197000, 255, 2024000, 2.5894, 2.2185),
+ ("Tensor G4", 1328000, 255, 2024000, 2.6949, 2.1905),
+ ("Tensor G4", 1425000, 255, 2024000, 2.8143, 2.7641),
+ ("Tensor G4", 1548000, 255, 2024000, 2.8513, 2.6064),
+ ("Tensor G4", 1696000, 255, 2024000, 2.663, 2.3265),
+ ("Tensor G4", 1849000, 255, 2024000, 2.6618, 2.595),
+ ("Tensor G4", 1950000, 255, 2024000, 2.7227, 2.7533),
+ ("Tensor G4", 820000, 255, 2120000, 2.5414, 2.6693),
+ ("Tensor G4", 955000, 255, 2120000, 2.7682, 2.7252),
+ ("Tensor G4", 1098000, 255, 2120000, 3.1524, 2.8636),
+ ("Tensor G4", 1197000, 255, 2120000, 3.0192, 2.6325),
+ ("Tensor G4", 1328000, 255, 2120000, 2.9797, 3.1899),
+ ("Tensor G4", 1425000, 255, 2120000, 3.176, 3.069),
+ ("Tensor G4", 1548000, 255, 2120000, 3.1105, 2.2982),
+ ("Tensor G4", 1696000, 255, 2120000, 2.9221, 3.2752),
+ ("Tensor G4", 1849000, 255, 2120000, 3.2659, 3.0112),
+ ("Tensor G4", 1950000, 255, 2120000, 3.1351, 3.5576),
+ ("Tensor G4", 820000, 255, 2150000, 2.9947, 2.6827),
+ ("Tensor G4", 955000, 255, 2150000, 2.9357, 2.5455),
+ ("Tensor G4", 1098000, 255, 2150000, 3.1562, 2.8923),
+ ("Tensor G4", 1197000, 255, 2150000, 3.0068, 3.2811),
+ ("Tensor G4", 1328000, 255, 2150000, 3.1187, 3.0526),
+ ("Tensor G4", 1425000, 255, 2150000, 3.2907, 2.9659),
+ ("Tensor G4", 1548000, 255, 2150000, 2.9841, 3.347),
+ ("Tensor G4", 1696000, 255, 2150000, 3.2313, 3.1632),
+ ("Tensor G4", 1849000, 255, 2150000, 3.091, 3.1305),
+ ("Tensor G4", 1950000, 255, 2150000, 3.326, 3.0211)
)
select * from data;
diff --git a/src/trace_processor/perfetto_sql/stdlib/wattson/curves/estimates.sql b/src/trace_processor/perfetto_sql/stdlib/wattson/curves/estimates.sql
new file mode 100644
index 0000000..1c81f49
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/wattson/curves/estimates.sql
@@ -0,0 +1,183 @@
+--
+-- 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 wattson.cpu_split;
+INCLUDE PERFETTO MODULE wattson.curves.utils;
+INCLUDE PERFETTO MODULE wattson.curves.w_cpu_dependence;
+INCLUDE PERFETTO MODULE wattson.curves.w_dsu_dependence;
+INCLUDE PERFETTO MODULE wattson.device_infos;
+
+-- One of the two tables will be empty, depending on whether the device is
+-- dependent on devfreq or a different CPU's frequency
+CREATE PERFETTO VIEW _curves_w_dependencies(
+ ts LONG,
+ dur LONG,
+ freq_0 INT,
+ idle_0 INT,
+ freq_1 INT,
+ idle_1 INT,
+ freq_2 INT,
+ idle_2 INT,
+ freq_3 INT,
+ idle_3 INT,
+ cpu0_curve FLOAT,
+ cpu1_curve FLOAT,
+ cpu2_curve FLOAT,
+ cpu3_curve FLOAT,
+ cpu4_curve FLOAT,
+ cpu5_curve FLOAT,
+ cpu6_curve FLOAT,
+ cpu7_curve FLOAT,
+ l3_hit_count INT,
+ l3_miss_count INT,
+ no_static INT,
+ all_cpu_deep_idle INT,
+ dependent_freq INT,
+ dependent_policy INT
+) AS
+-- Table that is dependent on differet CPU's frequency
+SELECT * FROM _w_cpu_dependence
+UNION ALL
+-- Table that is dependent of devfreq frequency
+SELECT * FROM _w_dsu_dependence;
+
+-- Final table showing the curves per CPU per slice
+CREATE PERFETTO TABLE _system_state_curves
+AS
+SELECT
+ base.ts,
+ base.dur,
+ -- base.cpu[0-3]_curve will be non-zero if CPU has 1D dependency
+ -- base.cpu[0-3]_curve will be zero if device is suspended or deep idle
+ -- base.cpu[0-3]_curve will be NULL if 2D dependency required
+ COALESCE(base.cpu0_curve, lut0.curve_value) as cpu0_curve,
+ COALESCE(base.cpu1_curve, lut1.curve_value) as cpu1_curve,
+ COALESCE(base.cpu2_curve, lut2.curve_value) as cpu2_curve,
+ COALESCE(base.cpu3_curve, lut3.curve_value) as cpu3_curve,
+ -- base.cpu[4-7]_curve will be non-zero if CPU has 1D dependency
+ -- base.cpu[4-7]_curve will be zero if device is suspended or deep idle
+ -- base.cpu[4-7]_curve will be NULL if CPU doesn't exist on device
+ COALESCE(base.cpu4_curve, 0.0) as cpu4_curve,
+ COALESCE(base.cpu5_curve, 0.0) as cpu5_curve,
+ COALESCE(base.cpu6_curve, 0.0) as cpu6_curve,
+ COALESCE(base.cpu7_curve, 0.0) as cpu7_curve,
+ IIF(
+ no_static = 1,
+ 0.0,
+ COALESCE(static_1d.curve_value, static_2d.curve_value)
+ ) as static_curve,
+ IIF(
+ all_cpu_deep_idle = 1,
+ 0,
+ base.l3_hit_count * l3_hit_lut.curve_value
+ ) as l3_hit_value,
+ IIF(
+ all_cpu_deep_idle = 1,
+ 0,
+ base.l3_miss_count * l3_miss_lut.curve_value
+ ) as l3_miss_value
+FROM _curves_w_dependencies as base
+-- LUT for 2D dependencies
+LEFT JOIN _filtered_curves_2d lut0 ON
+ lut0.freq_khz = base.freq_0 AND
+ lut0.other_policy = base.dependent_policy AND
+ lut0.other_freq_khz = base.dependent_freq AND
+ lut0.idle = base.idle_0
+LEFT JOIN _filtered_curves_2d lut1 ON
+ lut1.freq_khz = base.freq_1 AND
+ lut1.other_policy = base.dependent_policy AND
+ lut1.other_freq_khz = base.dependent_freq AND
+ lut1.idle = base.idle_1
+LEFT JOIN _filtered_curves_2d lut2 ON
+ lut2.freq_khz = base.freq_2 AND
+ lut2.other_policy = base.dependent_policy AND
+ lut2.other_freq_khz = base.dependent_freq AND
+ lut2.idle = base.idle_2
+LEFT JOIN _filtered_curves_2d lut3 ON
+ lut3.freq_khz = base.freq_3 AND
+ lut3.other_policy = base.dependent_policy AND
+ lut3.other_freq_khz = base.dependent_freq AND
+ lut3.idle = base.idle_3
+-- LUT for static curve lookup
+LEFT JOIN _filtered_curves_2d static_2d ON
+ static_2d.freq_khz = base.freq_0 AND
+ static_2d.other_policy = base.dependent_policy AND
+ static_2d.other_freq_khz = base.dependent_freq AND
+ static_2d.idle = 255
+LEFT JOIN _filtered_curves_1d static_1d ON
+ static_1d.policy = 0 AND
+ static_1d.freq_khz = base.freq_0 AND
+ static_1d.idle = 255
+-- LUT joins for L3 cache
+LEFT JOIN _filtered_curves_l3 l3_hit_lut ON
+ l3_hit_lut.freq_khz = base.freq_0 AND
+ l3_hit_lut.other_policy = base.dependent_policy AND
+ l3_hit_lut.other_freq_khz = base.dependent_freq AND
+ l3_hit_lut.action = 'hit'
+LEFT JOIN _filtered_curves_l3 l3_miss_lut ON
+ l3_miss_lut.freq_khz = base.freq_0 AND
+ l3_miss_lut.other_policy = base.dependent_policy AND
+ l3_miss_lut.other_freq_khz = base.dependent_freq AND
+ l3_miss_lut.action = 'miss';
+
+-- The most basic components of Wattson, all normalized to be in mW on a per
+-- system state basis
+CREATE PERFETTO TABLE _system_state_mw
+AS
+SELECT
+ ts,
+ dur,
+ cpu0_curve as cpu0_mw,
+ cpu1_curve as cpu1_mw,
+ cpu2_curve as cpu2_mw,
+ cpu3_curve as cpu3_mw,
+ cpu4_curve as cpu4_mw,
+ cpu5_curve as cpu5_mw,
+ cpu6_curve as cpu6_mw,
+ cpu7_curve as cpu7_mw,
+ -- LUT for l3 is scaled by 10^6 to save resolution and in units of kWs. Scale
+ -- this by 10^3 so when divided by ns, result is in units of mW
+ (
+ (
+ IFNULL(l3_hit_value, 0) + IFNULL(l3_miss_value, 0)
+ ) * 1000 / dur
+ ) + static_curve as dsu_scu_mw
+FROM _system_state_curves;
+
+-- API to get power from each system state in an arbitrary time window
+CREATE PERFETTO FUNCTION _windowed_system_state_mw(ts LONG, dur LONG)
+RETURNS TABLE(
+ cpu0_mw FLOAT,
+ cpu1_mw FLOAT,
+ cpu2_mw FLOAT,
+ cpu3_mw FLOAT,
+ cpu4_mw FLOAT,
+ cpu5_mw FLOAT,
+ cpu6_mw FLOAT,
+ cpu7_mw FLOAT,
+ dsu_scu_mw FLOAT
+) AS
+SELECT
+ SUM(ss.cpu0_mw * ss.dur) / SUM(ss.dur) AS cpu0_mw,
+ SUM(ss.cpu1_mw * ss.dur) / SUM(ss.dur) AS cpu1_mw,
+ SUM(ss.cpu2_mw * ss.dur) / SUM(ss.dur) AS cpu2_mw,
+ SUM(ss.cpu3_mw * ss.dur) / SUM(ss.dur) AS cpu3_mw,
+ SUM(ss.cpu4_mw * ss.dur) / SUM(ss.dur) AS cpu4_mw,
+ SUM(ss.cpu5_mw * ss.dur) / SUM(ss.dur) AS cpu5_mw,
+ SUM(ss.cpu6_mw * ss.dur) / SUM(ss.dur) AS cpu6_mw,
+ SUM(ss.cpu7_mw * ss.dur) / SUM(ss.dur) AS cpu7_mw,
+ SUM(ss.dsu_scu_mw * ss.dur) / SUM(ss.dur) AS dsu_scu_mw
+FROM _interval_intersect_single!($ts, $dur, _ii_subquery!(_system_state_mw)) ii
+JOIN _system_state_mw AS ss ON ss._auto_id = id;
diff --git a/src/trace_processor/perfetto_sql/stdlib/wattson/curves/grouped.sql b/src/trace_processor/perfetto_sql/stdlib/wattson/curves/grouped.sql
deleted file mode 100644
index 18d1770..0000000
--- a/src/trace_processor/perfetto_sql/stdlib/wattson/curves/grouped.sql
+++ /dev/null
@@ -1,66 +0,0 @@
---
--- 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 wattson.curves.ungrouped;
-
--- Wattson's estimated usage of the system, split out into cpu cluster based on
--- the natural grouping of the hardware.
-CREATE PERFETTO TABLE wattson_estimate_per_component(
- -- Starting timestamp of the slice
- ts LONG,
- -- Duration of the slice
- dur INT,
- -- Total L3 estimated usage in mW during this slice
- l3 FLOAT,
- -- Total little CPU estimated usage in mW during this slice
- little_cpus FLOAT,
- -- Total mid CPU cluster estimated usage in mW during this slice
- mid_cpus FLOAT,
- -- Total big CPU cluster estimated usage in mW during this slice
- big_cpus FLOAT
-)
-AS
-SELECT
- ts,
- dur,
- IFNULL(l3_hit_value, 0.0) + IFNULL(l3_miss_value, 0.0) as l3,
- IFNULL(cpu0_curve, 0.0) + IFNULL(cpu1_curve, 0.0) + IFNULL(cpu2_curve, 0.0) +
- IFNULL(cpu3_curve, 0.0) + static_curve as little_cpus,
- cpu4_curve + cpu5_curve as mid_cpus,
- cpu6_curve + cpu7_curve as big_cpus
-FROM _system_state_curves;
-
--- Gives total contribution of each HW component for the entire trace, bringing
--- the output of the table to parity with the Python version of Wattson
-CREATE PERFETTO TABLE _wattson_entire_trace
-AS
-WITH _individual_totals AS (
- SELECT
- -- LUT for l3 is scaled by 10^6 to save resolution, so do the inversion
- -- scaling by 10^6 after the summation to minimize losing resolution
- SUM(l3) / 1000000 as total_l3,
- SUM(dur * little_cpus) / 1000000000 as total_little_cpus,
- SUM(dur * mid_cpus) / 1000000000 as total_mid_cpus,
- SUM(dur * big_cpus) / 1000000000 as total_big_cpus
- FROM wattson_estimate_per_component
- )
-SELECT
- ROUND(total_l3, 2) as total_l3,
- ROUND(total_little_cpus, 2) as total_little_cpus,
- ROUND(total_mid_cpus, 2) as total_mid_cpus,
- ROUND(total_big_cpus, 2) as total_big_cpus,
- ROUND(total_l3 + total_little_cpus + total_mid_cpus + total_big_cpus, 2) as total
-FROM _individual_totals;
-
diff --git a/src/trace_processor/perfetto_sql/stdlib/wattson/curves/idle_attribution.sql b/src/trace_processor/perfetto_sql/stdlib/wattson/curves/idle_attribution.sql
index abb146f..77f045d 100644
--- a/src/trace_processor/perfetto_sql/stdlib/wattson/curves/idle_attribution.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/wattson/curves/idle_attribution.sql
@@ -14,7 +14,7 @@
-- limitations under the License.
INCLUDE PERFETTO MODULE intervals.intersect;
-INCLUDE PERFETTO MODULE wattson.curves.grouped;
+INCLUDE PERFETTO MODULE wattson.curves.estimates;
-- Get slice info of threads/processes
CREATE PERFETTO TABLE _thread_process_slices AS
diff --git a/src/trace_processor/perfetto_sql/stdlib/wattson/curves/ungrouped.sql b/src/trace_processor/perfetto_sql/stdlib/wattson/curves/ungrouped.sql
deleted file mode 100644
index 37da258..0000000
--- a/src/trace_processor/perfetto_sql/stdlib/wattson/curves/ungrouped.sql
+++ /dev/null
@@ -1,236 +0,0 @@
---
--- 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 time.conversion;
-INCLUDE PERFETTO MODULE wattson.arm_dsu;
-INCLUDE PERFETTO MODULE wattson.cpu_split;
-INCLUDE PERFETTO MODULE wattson.curves.utils;
-INCLUDE PERFETTO MODULE wattson.device_infos;
-
--- System state table with LUT for CPUs and intermediate values for calculations
-CREATE PERFETTO TABLE _w_independent_cpus_calc
-AS
-SELECT
- ts,
- dur,
- cast_int!(l3_hit_rate * dur) as l3_hit_count,
- cast_int!(l3_miss_rate * dur) as l3_miss_count,
- freq_0,
- idle_0,
- freq_1,
- idle_1,
- freq_2,
- idle_2,
- freq_3,
- idle_3,
- freq_4,
- idle_4,
- freq_5,
- idle_5,
- freq_6,
- idle_6,
- freq_7,
- idle_7,
- policy_4,
- policy_5,
- policy_6,
- policy_7,
- IIF(
- suspended = 1,
- 1,
- MIN(
- IFNULL(idle_0, 1),
- IFNULL(idle_1, 1),
- IFNULL(idle_2, 1),
- IFNULL(idle_3, 1)
- )
- ) as no_static,
- IIF(suspended = 1, 0, cpu0_curve) as cpu0_curve,
- IIF(suspended = 1, 0, cpu1_curve) as cpu1_curve,
- IIF(suspended = 1, 0, cpu2_curve) as cpu2_curve,
- IIF(suspended = 1, 0, cpu3_curve) as cpu3_curve,
- IIF(suspended = 1, 0, cpu4_curve) as cpu4_curve,
- IIF(suspended = 1, 0, cpu5_curve) as cpu5_curve,
- IIF(suspended = 1, 0, cpu6_curve) as cpu6_curve,
- IIF(suspended = 1, 0, cpu7_curve) as cpu7_curve,
- -- If dependency CPUs are active, then that CPU could contribute static power
- IIF(idle_4 = -1, lut4.curve_value, -1) as static_4,
- IIF(idle_5 = -1, lut5.curve_value, -1) as static_5,
- IIF(idle_6 = -1, lut6.curve_value, -1) as static_6,
- IIF(idle_7 = -1, lut7.curve_value, -1) as static_7
-FROM _idle_freq_l3_hit_l3_miss_slice as base
-LEFT JOIN _filtered_curves_2d lut4 ON
- base.freq_0 = lut4.freq_khz AND
- base.policy_4 = lut4.other_policy AND
- base.freq_4 = lut4.other_freq_khz AND
- lut4.idle = 255
-LEFT JOIN _filtered_curves_2d lut5 ON
- base.freq_0 = lut5.freq_khz AND
- base.policy_5 = lut5.other_policy AND
- base.freq_5 = lut5.other_freq_khz AND
- lut5.idle = 255
-LEFT JOIN _filtered_curves_2d lut6 ON
- base.freq_0 = lut6.freq_khz AND
- base.policy_6 = lut6.other_policy AND
- base.freq_6 = lut6.other_freq_khz AND
- lut6.idle = 255
-LEFT JOIN _filtered_curves_2d lut7 ON
- base.freq_0 = lut7.freq_khz AND
- base.policy_7 = lut7.other_policy AND
- base.freq_7 = lut7.other_freq_khz AND
- lut7.idle = 255
--- Needs to be at least 1us to reduce inconsequential rows.
-WHERE dur > time_from_us(1);
-
--- Find the CPU states creating the max vote
-CREATE PERFETTO TABLE _get_max_vote
-AS
-WITH max_power_tbl AS (
- SELECT
- *,
- -- Indicates if all CPUs are in deep idle
- MIN(
- no_static,
- IFNULL(idle_4, 1),
- IFNULL(idle_5, 1),
- IFNULL(idle_6, 1),
- IFNULL(idle_7, 1)
- ) as all_cpu_deep_idle,
- -- Determines which CPU has highest vote
- MAX(
- static_4,
- static_5,
- static_6,
- static_7
- ) as max_static_vote
- FROM _w_independent_cpus_calc
-)
-SELECT
- *,
- CASE max_static_vote
- WHEN -1 THEN _get_min_freq_vote()
- WHEN static_4 THEN freq_4
- WHEN static_5 THEN freq_5
- WHEN static_6 THEN freq_6
- WHEN static_7 THEN freq_7
- ELSE 400000
- END max_freq_vote,
- CASE max_static_vote
- WHEN -1 THEN _get_min_policy_vote()
- WHEN static_4 THEN policy_4
- WHEN static_5 THEN policy_5
- WHEN static_6 THEN policy_6
- WHEN static_7 THEN policy_7
- ELSE 4
- END max_policy_vote
-FROM max_power_tbl;
-
--- Final table showing the curves per CPU per slice
-CREATE PERFETTO TABLE _system_state_curves
-AS
-SELECT
- base.ts,
- base.dur,
- COALESCE(lut0.curve_value, cpu0_curve) as cpu0_curve,
- COALESCE(lut1.curve_value, cpu1_curve) as cpu1_curve,
- COALESCE(lut2.curve_value, cpu2_curve) as cpu2_curve,
- COALESCE(lut3.curve_value, cpu3_curve) as cpu3_curve,
- COALESCE(base.cpu4_curve, 0.0) as cpu4_curve,
- COALESCE(base.cpu5_curve, 0.0) as cpu5_curve,
- COALESCE(base.cpu6_curve, 0.0) as cpu6_curve,
- COALESCE(base.cpu7_curve, 0.0) as cpu7_curve,
- IIF(
- no_static = 1,
- 0.0,
- COALESCE(static_1d.curve_value, static_2d.curve_value)
- ) as static_curve,
- IIF(
- all_cpu_deep_idle = 1,
- 0,
- base.l3_hit_count * l3_hit_lut.curve_value
- ) as l3_hit_value,
- IIF(
- all_cpu_deep_idle = 1,
- 0,
- base.l3_miss_count * l3_miss_lut.curve_value
- ) as l3_miss_value
-FROM _get_max_vote as base
--- LUT for 2D dependencies
-LEFT JOIN _filtered_curves_2d lut0 ON
- lut0.freq_khz = base.freq_0 AND
- lut0.other_policy = base.max_policy_vote AND
- lut0.other_freq_khz = base.max_freq_vote AND
- lut0.idle = base.idle_0
-LEFT JOIN _filtered_curves_2d lut1 ON
- lut1.freq_khz = base.freq_1 AND
- lut1.other_policy = base.max_policy_vote AND
- lut1.other_freq_khz = base.max_freq_vote AND
- lut1.idle = base.idle_1
-LEFT JOIN _filtered_curves_2d lut2 ON
- lut2.freq_khz = base.freq_2 AND
- lut2.other_policy = base.max_policy_vote AND
- lut2.other_freq_khz = base.max_freq_vote AND
- lut2.idle = base.idle_2
-LEFT JOIN _filtered_curves_2d lut3 ON
- lut3.freq_khz = base.freq_3 AND
- lut3.other_policy = base.max_policy_vote AND
- lut3.other_freq_khz = base.max_freq_vote AND
- lut3.idle = base.idle_3
--- LUT for static curve lookup
-LEFT JOIN _filtered_curves_2d static_2d ON
- static_2d.freq_khz = base.freq_0 AND
- static_2d.other_policy = base.max_policy_vote AND
- static_2d.other_freq_khz = base.max_freq_vote AND
- static_2d.idle = 255
-LEFT JOIN _filtered_curves_1d static_1d ON
- static_1d.policy = 0 AND
- static_1d.freq_khz = base.freq_0 AND
- static_1d.idle = 255
--- LUT joins for L3 cache
-LEFT JOIN _filtered_curves_l3 l3_hit_lut ON
- l3_hit_lut.freq_khz = base.freq_0 AND
- l3_hit_lut.other_policy = base.max_policy_vote AND
- l3_hit_lut.other_freq_khz = base.max_freq_vote AND
- l3_hit_lut.action = 'hit'
-LEFT JOIN _filtered_curves_l3 l3_miss_lut ON
- l3_miss_lut.freq_khz = base.freq_0 AND
- l3_miss_lut.other_policy = base.max_policy_vote AND
- l3_miss_lut.other_freq_khz = base.max_freq_vote AND
- l3_miss_lut.action = 'miss';
-
--- The most basic components of Wattson, all normalized to be in mW on a per
--- system state basis
-CREATE PERFETTO TABLE _system_state_mw
-AS
-SELECT
- ts,
- dur,
- cpu0_curve as cpu0_mw,
- cpu1_curve as cpu1_mw,
- cpu2_curve as cpu2_mw,
- cpu3_curve as cpu3_mw,
- cpu4_curve as cpu4_mw,
- cpu5_curve as cpu5_mw,
- cpu6_curve as cpu6_mw,
- cpu7_curve as cpu7_mw,
- -- LUT for l3 is scaled by 10^6 to save resolution and in units of kWs. Scale
- -- this by 10^3 so when divided by ns, result is in units of mW
- (
- (
- IFNULL(l3_hit_value, 0) + IFNULL(l3_miss_value, 0)
- ) * 1000 / dur
- ) + static_curve as dsu_scu_mw
-FROM _system_state_curves;
-
diff --git a/src/trace_processor/perfetto_sql/stdlib/wattson/curves/w_cpu_dependence.sql b/src/trace_processor/perfetto_sql/stdlib/wattson/curves/w_cpu_dependence.sql
new file mode 100644
index 0000000..70b6ea2
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/wattson/curves/w_cpu_dependence.sql
@@ -0,0 +1,78 @@
+--
+-- 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 wattson.cpu_split;
+
+-- Find the CPU states creating the max vote
+CREATE PERFETTO TABLE _w_cpu_dependence
+AS
+WITH max_power_tbl AS (
+ SELECT
+ *,
+ -- Indicates if all CPUs are in deep idle
+ MIN(
+ no_static,
+ IFNULL(idle_4, 1),
+ IFNULL(idle_5, 1),
+ IFNULL(idle_6, 1),
+ IFNULL(idle_7, 1)
+ ) as all_cpu_deep_idle,
+ -- Determines which CPU has highest vote
+ MAX(
+ static_4,
+ static_5,
+ static_6,
+ static_7
+ ) as max_static_vote
+ FROM _w_independent_cpus_calc
+ -- _skip_devfreq_for_calc short circuits this table if devfreq is needed
+ JOIN _skip_devfreq_for_calc
+)
+SELECT
+ ts,
+ dur,
+ freq_0, idle_0,
+ freq_1, idle_1,
+ freq_2, idle_2,
+ freq_3, idle_3,
+ cpu0_curve,
+ cpu1_curve,
+ cpu2_curve,
+ cpu3_curve,
+ cpu4_curve,
+ cpu5_curve,
+ cpu6_curve,
+ cpu7_curve,
+ l3_hit_count,
+ l3_miss_count,
+ no_static,
+ all_cpu_deep_idle,
+ CASE max_static_vote
+ WHEN -1 THEN _get_min_freq_vote()
+ WHEN static_4 THEN freq_4
+ WHEN static_5 THEN freq_5
+ WHEN static_6 THEN freq_6
+ WHEN static_7 THEN freq_7
+ ELSE 400000
+ END dependent_freq,
+ CASE max_static_vote
+ WHEN -1 THEN _get_min_policy_vote()
+ WHEN static_4 THEN policy_4
+ WHEN static_5 THEN policy_5
+ WHEN static_6 THEN policy_6
+ WHEN static_7 THEN policy_7
+ ELSE 4
+ END dependent_policy
+FROM max_power_tbl;
diff --git a/src/trace_processor/perfetto_sql/stdlib/wattson/curves/w_dsu_dependence.sql b/src/trace_processor/perfetto_sql/stdlib/wattson/curves/w_dsu_dependence.sql
new file mode 100644
index 0000000..dbaf4ac
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/wattson/curves/w_dsu_dependence.sql
@@ -0,0 +1,89 @@
+--
+-- 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 linux.devfreq;
+INCLUDE PERFETTO MODULE wattson.cpu_split;
+INCLUDE PERFETTO MODULE wattson.curves.utils;
+INCLUDE PERFETTO MODULE wattson.device_infos;
+
+CREATE PERFETTO TABLE _cpu_curves AS
+SELECT
+ ts, dur,
+ freq_0, idle_0,
+ freq_1, idle_1,
+ freq_2, idle_2,
+ freq_3, idle_3,
+ lut4.curve_value as cpu4_curve,
+ lut5.curve_value as cpu5_curve,
+ lut6.curve_value as cpu6_curve,
+ lut7.curve_value as cpu7_curve,
+ l3_hit_count, l3_miss_count,
+ no_static,
+ MIN(
+ no_static,
+ IFNULL(idle_4, 1),
+ IFNULL(idle_5, 1),
+ IFNULL(idle_6, 1),
+ IFNULL(idle_7, 1)
+ ) as all_cpu_deep_idle
+FROM _w_independent_cpus_calc as base
+-- _use_devfreq_for_calc short circuits this table if devfreq isn't needed
+JOIN _use_devfreq_for_calc
+LEFT JOIN _filtered_curves_1d lut4 ON
+ base.freq_4 = lut4.freq_khz AND
+ base.idle_4 = lut4.idle
+LEFT JOIN _filtered_curves_1d lut5 ON
+ base.freq_5 = lut5.freq_khz AND
+ base.idle_5 = lut5.idle
+LEFT JOIN _filtered_curves_1d lut6 ON
+ base.freq_6 = lut6.freq_khz AND
+ base.idle_6 = lut6.idle
+LEFT JOIN _filtered_curves_1d lut7 ON
+ base.freq_7 = lut7.freq_khz AND
+ base.idle_7 = lut7.idle;
+
+CREATE PERFETTO TABLE _w_dsu_dependence AS
+SELECT
+ c.ts, c.dur,
+ c.freq_0, c.idle_0,
+ c.freq_1, c.idle_1,
+ c.freq_2, c.idle_2,
+ c.freq_3, c.idle_3,
+ -- NULL columns needed to match columns of _get_max_vote before UNION
+ NULL as cpu0_curve,
+ NULL as cpu1_curve,
+ NULL as cpu2_curve,
+ NULL as cpu3_curve,
+ c.cpu4_curve,
+ c.cpu5_curve,
+ c.cpu6_curve,
+ c.cpu7_curve,
+ c.l3_hit_count,
+ c.l3_miss_count,
+ c.no_static,
+ c.all_cpu_deep_idle,
+ d.dsu_freq as dependent_freq,
+ 255 as dependent_policy
+FROM _interval_intersect!(
+ (
+ _ii_subquery!(_cpu_curves),
+ _ii_subquery!(linux_devfreq_dsu_counter)
+ ),
+ ()
+) ii
+JOIN _cpu_curves AS c ON c._auto_id = id_0
+JOIN linux_devfreq_dsu_counter AS d on d._auto_id = id_1;
+
diff --git a/src/trace_processor/perfetto_sql/stdlib/wattson/device_infos.sql b/src/trace_processor/perfetto_sql/stdlib/wattson/device_infos.sql
index 89d128b..6b8cda5 100644
--- a/src/trace_processor/perfetto_sql/stdlib/wattson/device_infos.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/wattson/device_infos.sql
@@ -31,7 +31,15 @@
("monaco", 0, 450000),
("monaco", 1, 450000),
("monaco", 2, 450000),
- ("monaco", 3, 450000)
+ ("monaco", 3, 450000),
+ ("Tensor G4", 0, 0),
+ ("Tensor G4", 1, 0),
+ ("Tensor G4", 2, 0),
+ ("Tensor G4", 3, 0),
+ ("Tensor G4", 4, 110000),
+ ("Tensor G4", 5, 110000),
+ ("Tensor G4", 6, 110000),
+ ("Tensor G4", 7, 400000)
)
select * from data;
@@ -50,6 +58,8 @@
CREATE PERFETTO TABLE _wattson_device AS
WITH soc_model AS (
SELECT COALESCE(
+ -- Get guest model from metadata, which takes precedence if set
+ (SELECT str_value FROM metadata WHERE name = 'android_guest_soc_model'),
-- Get model from metadata
(SELECT str_value FROM metadata WHERE name = 'android_soc_model'),
-- Get device name from metadata and map it to model
@@ -82,7 +92,17 @@
("Tensor", 4, 4),
("Tensor", 5, 4),
("Tensor", 6, 6),
- ("Tensor", 7, 6)
+ ("Tensor", 7, 6),
+ ("Tensor G4", 0, 0),
+ ("Tensor G4", 1, 0),
+ ("Tensor G4", 2, 0),
+ ("Tensor G4", 3, 0),
+ ("Tensor G4", 4, 4),
+ ("Tensor G4", 5, 4),
+ ("Tensor G4", 6, 4),
+ ("Tensor G4", 7, 7),
+ -- need 255 policy to match devfreq
+ ("Tensor G4", 255, 255)
)
select * from data;
@@ -102,7 +122,8 @@
WITH data(device, policy, freq) AS (
VALUES
("monaco", 0, 614400),
- ("Tensor", 4, 400000)
+ ("Tensor", 4, 400000),
+ ("Tensor G4", 0, 700000)
)
select * from data;
@@ -123,3 +144,26 @@
FROM _device_min_volt_vote as vote_tbl
JOIN _wattson_device as device
WHERE vote_tbl.device = device.name;
+
+-- Devices that require using devfreq
+CREATE PERFETTO TABLE _use_devfreq
+AS
+WITH data(device) AS (
+ VALUES
+ ("Tensor G4")
+)
+select * from data;
+
+-- Creates non-empty table if device needs devfreq
+CREATE PERFETTO TABLE _use_devfreq_for_calc AS
+SELECT TRUE AS devfreq_necessary
+FROM _use_devfreq as d
+JOIN _wattson_device as device
+ON d.device = device.name;
+
+-- Creates empty table if device needs devfreq; inverse of _use_devfreq_for_calc
+CREATE PERFETTO TABLE _skip_devfreq_for_calc AS
+SELECT FALSE AS devfreq_necessary
+FROM _use_devfreq as d
+JOIN _wattson_device as device
+ON d.device != device.name;
diff --git a/src/trace_processor/perfetto_sql/stdlib/wattson/system_state.sql b/src/trace_processor/perfetto_sql/stdlib/wattson/system_state.sql
index bfa12ca..61907a5 100644
--- a/src/trace_processor/perfetto_sql/stdlib/wattson/system_state.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/wattson/system_state.sql
@@ -63,10 +63,10 @@
)
AS
SELECT
- ts,
- dur,
- cast_int!(round(l3_hit_rate * dur, 0)) as l3_hit_count,
- cast_int!(round(l3_miss_rate * dur, 0)) as l3_miss_count,
+ s.ts,
+ s.dur,
+ cast_int!(round(l3_hit_rate * s.dur, 0)) as l3_hit_count,
+ cast_int!(round(l3_miss_rate * s.dur, 0)) as l3_miss_count,
freq_0,
idle_0,
freq_1,
@@ -84,7 +84,14 @@
freq_7,
idle_7,
IFNULL(suspended, FALSE) as suspended
-FROM _idle_freq_l3_hit_l3_miss_slice
+FROM _idle_freq_l3_hit_l3_miss_slice s
+JOIN _stats_cpu0 ON _stats_cpu0._auto_id = s.cpu0_id
+JOIN _stats_cpu1 ON _stats_cpu1._auto_id = s.cpu1_id
+JOIN _stats_cpu2 ON _stats_cpu2._auto_id = s.cpu2_id
+JOIN _stats_cpu3 ON _stats_cpu3._auto_id = s.cpu3_id
+LEFT JOIN _stats_cpu4 ON _stats_cpu4._auto_id = s.cpu4_id
+LEFT JOIN _stats_cpu5 ON _stats_cpu5._auto_id = s.cpu5_id
+LEFT JOIN _stats_cpu6 ON _stats_cpu6._auto_id = s.cpu6_id
+LEFT JOIN _stats_cpu7 ON _stats_cpu7._auto_id = s.cpu7_id
-- Needs to be at least 1us to reduce inconsequential rows.
-WHERE dur > time_from_us(1);
-
+WHERE s.dur > time_from_us(1);
diff --git a/src/trace_processor/read_trace.cc b/src/trace_processor/read_trace.cc
index 91e784a..e9f8f8c 100644
--- a/src/trace_processor/read_trace.cc
+++ b/src/trace_processor/read_trace.cc
@@ -26,8 +26,8 @@
#include "perfetto/protozero/proto_utils.h"
#include "perfetto/trace_processor/trace_blob_view.h"
#include "perfetto/trace_processor/trace_processor.h"
+#include "src/trace_processor/importers/archive/gzip_trace_parser.h"
#include "src/trace_processor/importers/common/chunked_trace_reader.h"
-#include "src/trace_processor/importers/gzip/gzip_trace_parser.h"
#include "src/trace_processor/importers/proto/proto_trace_tokenizer.h"
#include "src/trace_processor/read_trace_internal.h"
#include "src/trace_processor/util/gzip_utils.h"
diff --git a/src/trace_processor/read_trace_internal.cc b/src/trace_processor/read_trace_internal.cc
index 3b537cd..f2b1a67 100644
--- a/src/trace_processor/read_trace_internal.cc
+++ b/src/trace_processor/read_trace_internal.cc
@@ -27,7 +27,6 @@
#include "perfetto/trace_processor/trace_blob.h"
#include "perfetto/trace_processor/trace_blob_view.h"
#include "src/trace_processor/forwarding_trace_parser.h"
-#include "src/trace_processor/importers/gzip/gzip_trace_parser.h"
#include "src/trace_processor/importers/proto/proto_trace_tokenizer.h"
#include "src/trace_processor/util/gzip_utils.h"
#include "src/trace_processor/util/status_macros.h"
diff --git a/src/trace_processor/rpc/rpc.cc b/src/trace_processor/rpc/rpc.cc
index e52130d..49feef3 100644
--- a/src/trace_processor/rpc/rpc.cc
+++ b/src/trace_processor/rpc/rpc.cc
@@ -324,10 +324,10 @@
resp.Send(rpc_response_fn_);
break;
}
- case RpcProto::TPM_REGISTER_SQL_MODULE: {
+ case RpcProto::TPM_REGISTER_SQL_PACKAGE: {
Response resp(tx_seq_id_++, req_type);
- base::Status status = RegisterSqlModule(req.register_sql_module_args());
- auto* res = resp->set_register_sql_module_result();
+ base::Status status = RegisterSqlPackage(req.register_sql_package_args());
+ auto* res = resp->set_register_sql_package_result();
if (!status.ok()) {
res->set_error(status.message());
}
@@ -407,19 +407,31 @@
? SoftDropFtraceDataBefore::kAllPerCpuBuffersValid
: SoftDropFtraceDataBefore::kNoDrop;
}
+ using Args = protos::pbzero::ResetTraceProcessorArgs;
+ switch (reset_trace_processor_args.parsing_mode()) {
+ case Args::ParsingMode::DEFAULT:
+ config.parsing_mode = ParsingMode::kDefault;
+ break;
+ case Args::ParsingMode::TOKENIZE_ONLY:
+ config.parsing_mode = ParsingMode::kTokenizeOnly;
+ break;
+ case Args::ParsingMode::TOKENIZE_AND_SORT:
+ config.parsing_mode = ParsingMode::kTokenizeAndSort;
+ break;
+ }
ResetTraceProcessorInternal(config);
}
-base::Status Rpc::RegisterSqlModule(protozero::ConstBytes bytes) {
- protos::pbzero::RegisterSqlModuleArgs::Decoder args(bytes);
- SqlModule module;
- module.name = args.top_level_package_name().ToStdString();
- module.allow_module_override = args.allow_module_override();
+base::Status Rpc::RegisterSqlPackage(protozero::ConstBytes bytes) {
+ protos::pbzero::RegisterSqlPackageArgs::Decoder args(bytes);
+ SqlPackage package;
+ package.name = args.package_name().ToStdString();
+ package.allow_override = args.allow_override();
for (auto it = args.modules(); it; ++it) {
- protos::pbzero::RegisterSqlModuleArgs::Module::Decoder m(*it);
- module.files.emplace_back(m.name().ToStdString(), m.sql().ToStdString());
+ protos::pbzero::RegisterSqlPackageArgs::Module::Decoder m(*it);
+ package.modules.emplace_back(m.name().ToStdString(), m.sql().ToStdString());
}
- return trace_processor_->RegisterSqlModule(module);
+ return trace_processor_->RegisterSqlPackage(package);
}
void Rpc::MaybePrintProgress() {
diff --git a/src/trace_processor/rpc/rpc.h b/src/trace_processor/rpc/rpc.h
index 96bbfbc..fe28748 100644
--- a/src/trace_processor/rpc/rpc.h
+++ b/src/trace_processor/rpc/rpc.h
@@ -131,7 +131,7 @@
private:
void ParseRpcRequest(const uint8_t*, size_t);
void ResetTraceProcessor(const uint8_t*, size_t);
- base::Status RegisterSqlModule(protozero::ConstBytes);
+ base::Status RegisterSqlPackage(protozero::ConstBytes);
void ResetTraceProcessorInternal(const Config&);
void MaybePrintProgress();
Iterator QueryInternal(const uint8_t*, size_t);
diff --git a/src/trace_processor/sorter/BUILD.gn b/src/trace_processor/sorter/BUILD.gn
index 22400e4..2653f44 100644
--- a/src/trace_processor/sorter/BUILD.gn
+++ b/src/trace_processor/sorter/BUILD.gn
@@ -30,11 +30,14 @@
"../../../include/perfetto/trace_processor:storage",
"../../base",
"../importers/android_bugreport:android_log_event",
+ "../importers/art_method:art_method_event",
"../importers/common:parser_types",
"../importers/common:trace_parser_hdr",
"../importers/fuchsia:fuchsia_record",
+ "../importers/gecko:gecko_event",
"../importers/instruments:row",
"../importers/perf:record",
+ "../importers/perf_text:perf_text_event",
"../importers/proto:packet_sequence_state_generation_hdr",
"../importers/systrace:systrace_line",
"../storage",
diff --git a/src/trace_processor/sorter/trace_sorter.cc b/src/trace_processor/sorter/trace_sorter.cc
index d5e27f0..4fff091 100644
--- a/src/trace_processor/sorter/trace_sorter.cc
+++ b/src/trace_processor/sorter/trace_sorter.cc
@@ -28,11 +28,14 @@
#include "perfetto/public/compiler.h"
#include "perfetto/trace_processor/trace_blob_view.h"
#include "src/trace_processor/importers/android_bugreport/android_log_event.h"
+#include "src/trace_processor/importers/art_method/art_method_event.h"
#include "src/trace_processor/importers/common/parser_types.h"
#include "src/trace_processor/importers/common/trace_parser.h"
#include "src/trace_processor/importers/fuchsia/fuchsia_record.h"
+#include "src/trace_processor/importers/gecko/gecko_event.h"
#include "src/trace_processor/importers/instruments/row.h"
#include "src/trace_processor/importers/perf/record.h"
+#include "src/trace_processor/importers/perf_text/perf_text_event.h"
#include "src/trace_processor/sorter/trace_sorter.h"
#include "src/trace_processor/sorter/trace_token_buffer.h"
#include "src/trace_processor/storage/stats.h"
@@ -42,13 +45,12 @@
namespace perfetto::trace_processor {
TraceSorter::TraceSorter(TraceProcessorContext* context,
- SortingMode sorting_mode)
- : sorting_mode_(sorting_mode), storage_(context->storage) {
+ SortingMode sorting_mode,
+ EventHandling event_handling)
+ : sorting_mode_(sorting_mode),
+ storage_(context->storage),
+ event_handling_(event_handling) {
AddMachineContext(context);
- const char* env = getenv("TRACE_PROCESSOR_SORT_ONLY");
- bypass_next_stage_for_testing_ = env && !strcmp(env, "1");
- if (bypass_next_stage_for_testing_)
- PERFETTO_ELOG("TEST MODE: bypassing protobuf parsing stage");
}
TraceSorter::~TraceSorter() {
@@ -259,9 +261,22 @@
event.ts, token_buffer_.Extract<AndroidLogEvent>(id));
return;
case TimestampedEvent::Type::kLegacyV8CpuProfileEvent:
- context.proto_trace_parser->ParseLegacyV8ProfileEvent(
+ context.json_trace_parser->ParseLegacyV8ProfileEvent(
event.ts, token_buffer_.Extract<LegacyV8CpuProfileEvent>(id));
return;
+ case TimestampedEvent::Type::kGeckoEvent:
+ context.gecko_trace_parser->ParseGeckoEvent(
+ event.ts, token_buffer_.Extract<gecko_importer::GeckoEvent>(id));
+ return;
+ case TimestampedEvent::Type::kArtMethodEvent:
+ context.art_method_parser->ParseArtMethodEvent(
+ event.ts, token_buffer_.Extract<art_method::ArtMethodEvent>(id));
+ return;
+ case TimestampedEvent::Type::kPerfTextEvent:
+ context.perf_text_parser->ParsePerfTextEvent(
+ event.ts,
+ token_buffer_.Extract<perf_text_importer::PerfTextEvent>(id));
+ return;
case TimestampedEvent::Type::kInlineSchedSwitch:
case TimestampedEvent::Type::kInlineSchedWaking:
case TimestampedEvent::Type::kEtwEvent:
@@ -294,6 +309,9 @@
case TimestampedEvent::Type::kFuchsiaRecord:
case TimestampedEvent::Type::kAndroidLogEvent:
case TimestampedEvent::Type::kLegacyV8CpuProfileEvent:
+ case TimestampedEvent::Type::kGeckoEvent:
+ case TimestampedEvent::Type::kArtMethodEvent:
+ case TimestampedEvent::Type::kPerfTextEvent:
PERFETTO_FATAL("Invalid event type");
}
PERFETTO_FATAL("For GCC");
@@ -328,6 +346,9 @@
case TimestampedEvent::Type::kFuchsiaRecord:
case TimestampedEvent::Type::kAndroidLogEvent:
case TimestampedEvent::Type::kLegacyV8CpuProfileEvent:
+ case TimestampedEvent::Type::kGeckoEvent:
+ case TimestampedEvent::Type::kArtMethodEvent:
+ case TimestampedEvent::Type::kPerfTextEvent:
PERFETTO_FATAL("Invalid event type");
}
PERFETTO_FATAL("For GCC");
@@ -378,6 +399,18 @@
case TimestampedEvent::Type::kLegacyV8CpuProfileEvent:
base::ignore_result(token_buffer_.Extract<LegacyV8CpuProfileEvent>(id));
return;
+ case TimestampedEvent::Type::kGeckoEvent:
+ base::ignore_result(
+ token_buffer_.Extract<gecko_importer::GeckoEvent>(id));
+ return;
+ case TimestampedEvent::Type::kArtMethodEvent:
+ base::ignore_result(
+ token_buffer_.Extract<art_method::ArtMethodEvent>(id));
+ return;
+ case TimestampedEvent::Type::kPerfTextEvent:
+ base::ignore_result(
+ token_buffer_.Extract<perf_text_importer::PerfTextEvent>(id));
+ return;
}
PERFETTO_FATAL("For GCC");
}
@@ -393,12 +426,13 @@
latest_pushed_event_ts_ = std::max(latest_pushed_event_ts_, timestamp);
- if (PERFETTO_UNLIKELY(bypass_next_stage_for_testing_)) {
+ if (PERFETTO_UNLIKELY(event_handling_ == EventHandling::kSortAndDrop)) {
// Parse* would extract this event and push it to the next stage. Since we
// are skipping that, just extract and discard it.
ExtractAndDiscardTokenizedObject(event);
return;
}
+ PERFETTO_DCHECK(event_handling_ == EventHandling::kSortAndPush);
if (queue_idx == 0) {
ParseTracePacket(*machine_context, event);
diff --git a/src/trace_processor/sorter/trace_sorter.h b/src/trace_processor/sorter/trace_sorter.h
index 588c8a3..cb6763c 100644
--- a/src/trace_processor/sorter/trace_sorter.h
+++ b/src/trace_processor/sorter/trace_sorter.h
@@ -35,11 +35,14 @@
#include "perfetto/trace_processor/ref_counted.h"
#include "perfetto/trace_processor/trace_blob_view.h"
#include "src/trace_processor/importers/android_bugreport/android_log_event.h"
+#include "src/trace_processor/importers/art_method/art_method_event.h"
#include "src/trace_processor/importers/common/parser_types.h"
#include "src/trace_processor/importers/common/trace_parser.h"
#include "src/trace_processor/importers/fuchsia/fuchsia_record.h"
+#include "src/trace_processor/importers/gecko/gecko_event.h"
#include "src/trace_processor/importers/instruments/row.h"
#include "src/trace_processor/importers/perf/record.h"
+#include "src/trace_processor/importers/perf_text/perf_text_event.h"
#include "src/trace_processor/importers/systrace/systrace_line.h"
#include "src/trace_processor/sorter/trace_token_buffer.h"
#include "src/trace_processor/storage/trace_storage.h"
@@ -100,118 +103,148 @@
kDefault,
kFullSort,
};
+ enum class EventHandling {
+ // Indicates that events should be sorted and pushed to the parsing stage.
+ kSortAndPush,
- TraceSorter(TraceProcessorContext* context, SortingMode sorting_mode);
+ // Indicates that events should be sorted but then dropped before pushing
+ // to the parsing stage.
+ // Used for performance analysis of the sorter.
+ kSortAndDrop,
+
+ // Indicates that the events should be dropped as soon as they enter the
+ // sorter.
+ // Used in cases where we only want to perform tokenization: dropping data
+ // when it hits the sorter is much cleaner than trying to handle this
+ // at every different tokenizer.
+ kDrop,
+ };
+
+ TraceSorter(TraceProcessorContext*,
+ SortingMode,
+ EventHandling = EventHandling::kSortAndPush);
~TraceSorter();
SortingMode sorting_mode() const { return sorting_mode_; }
- inline void AddMachineContext(TraceProcessorContext* context) {
+ void AddMachineContext(TraceProcessorContext* context) {
sorter_data_by_machine_.emplace_back(context);
}
- inline void PushAndroidLogEvent(
- int64_t timestamp,
- AndroidLogEvent event,
- std::optional<MachineId> machine_id = std::nullopt) {
- TraceTokenBuffer::Id id = token_buffer_.Append(std::move(event));
+ void PushAndroidLogEvent(int64_t timestamp,
+ const AndroidLogEvent& event,
+ std::optional<MachineId> machine_id = std::nullopt) {
AppendNonFtraceEvent(timestamp, TimestampedEvent::Type::kAndroidLogEvent,
- id, machine_id);
+ event, machine_id);
}
- inline void PushPerfRecord(
- int64_t timestamp,
- perf_importer::Record record,
- std::optional<MachineId> machine_id = std::nullopt) {
- TraceTokenBuffer::Id id = token_buffer_.Append(std::move(record));
- AppendNonFtraceEvent(timestamp, TimestampedEvent::Type::kPerfRecord, id,
- machine_id);
+ void PushPerfRecord(int64_t timestamp,
+ perf_importer::Record record,
+ std::optional<MachineId> machine_id = std::nullopt) {
+ AppendNonFtraceEvent(timestamp, TimestampedEvent::Type::kPerfRecord,
+ std::move(record), machine_id);
}
- inline void PushSpeRecord(
- int64_t timestamp,
- TraceBlobView record,
- std::optional<MachineId> machine_id = std::nullopt) {
- TraceTokenBuffer::Id id = token_buffer_.Append(std::move(record));
- AppendNonFtraceEvent(timestamp, TimestampedEvent::Type::kSpeRecord, id,
- machine_id);
+ void PushSpeRecord(int64_t timestamp,
+ TraceBlobView record,
+ std::optional<MachineId> machine_id = std::nullopt) {
+ AppendNonFtraceEvent(timestamp, TimestampedEvent::Type::kSpeRecord,
+ std::move(record), machine_id);
}
- inline void PushInstrumentsRow(
- int64_t timestamp,
- instruments_importer::Row row,
- std::optional<MachineId> machine_id = std::nullopt) {
- TraceTokenBuffer::Id id = token_buffer_.Append(std::move(row));
- AppendNonFtraceEvent(timestamp, TimestampedEvent::Type::kInstrumentsRow, id,
- machine_id);
+ void PushInstrumentsRow(int64_t timestamp,
+ const instruments_importer::Row& row,
+ std::optional<MachineId> machine_id = std::nullopt) {
+ AppendNonFtraceEvent(timestamp, TimestampedEvent::Type::kInstrumentsRow,
+ row, machine_id);
}
- inline void PushTracePacket(
- int64_t timestamp,
- TracePacketData data,
- std::optional<MachineId> machine_id = std::nullopt) {
- TraceTokenBuffer::Id id = token_buffer_.Append(std::move(data));
- AppendNonFtraceEvent(timestamp, TimestampedEvent::Type::kTracePacket, id,
- machine_id);
+ void PushTracePacket(int64_t timestamp,
+ TracePacketData data,
+ std::optional<MachineId> machine_id = std::nullopt) {
+ AppendNonFtraceEvent(timestamp, TimestampedEvent::Type::kTracePacket,
+ std::move(data), machine_id);
}
- inline void PushTracePacket(
- int64_t timestamp,
- RefPtr<PacketSequenceStateGeneration> state,
- TraceBlobView tbv,
- std::optional<MachineId> machine_id = std::nullopt) {
+ void PushTracePacket(int64_t timestamp,
+ RefPtr<PacketSequenceStateGeneration> state,
+ TraceBlobView tbv,
+ std::optional<MachineId> machine_id = std::nullopt) {
PushTracePacket(timestamp,
TracePacketData{std::move(tbv), std::move(state)},
machine_id);
}
- inline void PushJsonValue(int64_t timestamp,
- std::string json_value,
- std::optional<int64_t> dur = std::nullopt) {
+ void PushJsonValue(int64_t timestamp,
+ std::string json_value,
+ std::optional<int64_t> dur = std::nullopt) {
if (dur.has_value()) {
- // We need to account for slices with duration by sorting them first: this requires us to
- // use the slower comparator which takes this into account.
+ // We need to account for slices with duration by sorting them first: this
+ // requires us to use the slower comparator which takes this into account.
use_slow_sorting_ = true;
-
- TraceTokenBuffer::Id id =
- token_buffer_.Append(JsonWithDurEvent{*dur, std::move(json_value)});
AppendNonFtraceEvent(timestamp, TimestampedEvent::Type::kJsonValueWithDur,
- id);
+ JsonWithDurEvent{*dur, std::move(json_value)});
return;
}
-
- TraceTokenBuffer::Id id =
- token_buffer_.Append(JsonEvent{std::move(json_value)});
- AppendNonFtraceEvent(timestamp, TimestampedEvent::Type::kJsonValue, id);
+ AppendNonFtraceEvent(timestamp, TimestampedEvent::Type::kJsonValue,
+ JsonEvent{std::move(json_value)});
}
- inline void PushFuchsiaRecord(int64_t timestamp,
- FuchsiaRecord fuchsia_record) {
- TraceTokenBuffer::Id id = token_buffer_.Append(std::move(fuchsia_record));
- AppendNonFtraceEvent(timestamp, TimestampedEvent::Type::kFuchsiaRecord, id);
+ void PushFuchsiaRecord(int64_t timestamp, FuchsiaRecord fuchsia_record) {
+ AppendNonFtraceEvent(timestamp, TimestampedEvent::Type::kFuchsiaRecord,
+ std::move(fuchsia_record));
}
- inline void PushSystraceLine(SystraceLine systrace_line) {
- TraceTokenBuffer::Id id = token_buffer_.Append(std::move(systrace_line));
+ void PushSystraceLine(SystraceLine systrace_line) {
AppendNonFtraceEvent(systrace_line.ts,
- TimestampedEvent::Type::kSystraceLine, id);
+ TimestampedEvent::Type::kSystraceLine,
+ std::move(systrace_line));
}
- inline void PushTrackEventPacket(
+ void PushTrackEventPacket(
int64_t timestamp,
TrackEventData track_event,
std::optional<MachineId> machine_id = std::nullopt) {
- TraceTokenBuffer::Id id = token_buffer_.Append(std::move(track_event));
- AppendNonFtraceEvent(timestamp, TimestampedEvent::Type::kTrackEvent, id,
- machine_id);
+ AppendNonFtraceEvent(timestamp, TimestampedEvent::Type::kTrackEvent,
+ std::move(track_event), machine_id);
}
- inline void PushEtwEvent(uint32_t cpu,
- int64_t timestamp,
- TraceBlobView tbv,
- RefPtr<PacketSequenceStateGeneration> state,
- std::optional<MachineId> machine_id = std::nullopt) {
+ void PushLegacyV8CpuProfileEvent(int64_t timestamp,
+ uint64_t session_id,
+ uint32_t pid,
+ uint32_t tid,
+ uint32_t callsite_id) {
+ AppendNonFtraceEvent(
+ timestamp, TimestampedEvent::Type::kLegacyV8CpuProfileEvent,
+ LegacyV8CpuProfileEvent{session_id, pid, tid, callsite_id});
+ }
+
+ void PushGeckoEvent(int64_t timestamp,
+ const gecko_importer::GeckoEvent& event) {
+ AppendNonFtraceEvent(timestamp, TimestampedEvent::Type::kGeckoEvent, event);
+ }
+
+ void PushArtMethodEvent(int64_t timestamp,
+ const art_method::ArtMethodEvent& event) {
+ AppendNonFtraceEvent(timestamp, TimestampedEvent::Type::kArtMethodEvent,
+ event);
+ }
+
+ void PushPerfTextEvent(int64_t timestamp,
+ const perf_text_importer::PerfTextEvent& event) {
+ AppendNonFtraceEvent(timestamp, TimestampedEvent::Type::kPerfTextEvent,
+ event);
+ }
+
+ void PushEtwEvent(uint32_t cpu,
+ int64_t timestamp,
+ TraceBlobView tbv,
+ RefPtr<PacketSequenceStateGeneration> state,
+ std::optional<MachineId> machine_id = std::nullopt) {
+ if (PERFETTO_UNLIKELY(event_handling_ == EventHandling::kDrop)) {
+ return;
+ }
TraceTokenBuffer::Id id =
token_buffer_.Append(TracePacketData{std::move(tbv), std::move(state)});
auto* queue = GetQueue(cpu + 1, machine_id);
@@ -220,12 +253,14 @@
UpdateAppendMaxTs(queue);
}
- inline void PushFtraceEvent(
- uint32_t cpu,
- int64_t timestamp,
- TraceBlobView tbv,
- RefPtr<PacketSequenceStateGeneration> state,
- std::optional<MachineId> machine_id = std::nullopt) {
+ void PushFtraceEvent(uint32_t cpu,
+ int64_t timestamp,
+ TraceBlobView tbv,
+ RefPtr<PacketSequenceStateGeneration> state,
+ std::optional<MachineId> machine_id = std::nullopt) {
+ if (PERFETTO_UNLIKELY(event_handling_ == EventHandling::kDrop)) {
+ return;
+ }
TraceTokenBuffer::Id id =
token_buffer_.Append(TracePacketData{std::move(tbv), std::move(state)});
auto* queue = GetQueue(cpu + 1, machine_id);
@@ -234,26 +269,14 @@
UpdateAppendMaxTs(queue);
}
- inline void PushLegacyV8CpuProfileEvent(
- int64_t timestamp,
- uint64_t session_id,
- uint32_t pid,
- uint32_t tid,
- uint32_t callsite_id,
- std::optional<MachineId> machine_id = std::nullopt) {
- TraceTokenBuffer::Id id = token_buffer_.Append(
- LegacyV8CpuProfileEvent{session_id, pid, tid, callsite_id});
- auto* queue = GetQueue(0, machine_id);
- queue->Append(timestamp, TimestampedEvent::Type::kLegacyV8CpuProfileEvent,
- id, use_slow_sorting_);
- UpdateAppendMaxTs(queue);
- }
-
- inline void PushInlineFtraceEvent(
+ void PushInlineFtraceEvent(
uint32_t cpu,
int64_t timestamp,
- InlineSchedSwitch inline_sched_switch,
+ const InlineSchedSwitch& inline_sched_switch,
std::optional<MachineId> machine_id = std::nullopt) {
+ if (PERFETTO_UNLIKELY(event_handling_ == EventHandling::kDrop)) {
+ return;
+ }
// TODO(rsavitski): if a trace has a mix of normal & "compact" events
// (being pushed through this function), the ftrace batches will no longer
// be fully sorted by timestamp. In such situations, we will have to sort
@@ -261,21 +284,22 @@
// sorted however. Consider adding extra queues, or pushing them in a
// merge-sort fashion
// // instead.
- TraceTokenBuffer::Id id =
- token_buffer_.Append(std::move(inline_sched_switch));
+ TraceTokenBuffer::Id id = token_buffer_.Append(inline_sched_switch);
auto* queue = GetQueue(cpu + 1, machine_id);
queue->Append(timestamp, TimestampedEvent::Type::kInlineSchedSwitch, id,
use_slow_sorting_);
UpdateAppendMaxTs(queue);
}
- inline void PushInlineFtraceEvent(
+ void PushInlineFtraceEvent(
uint32_t cpu,
int64_t timestamp,
- InlineSchedWaking inline_sched_waking,
+ const InlineSchedWaking& inline_sched_waking,
std::optional<MachineId> machine_id = std::nullopt) {
- TraceTokenBuffer::Id id =
- token_buffer_.Append(std::move(inline_sched_waking));
+ if (PERFETTO_UNLIKELY(event_handling_ == EventHandling::kDrop)) {
+ return;
+ }
+ TraceTokenBuffer::Id id = token_buffer_.Append(inline_sched_waking);
auto* queue = GetQueue(cpu + 1, machine_id);
queue->Append(timestamp, TimestampedEvent::Type::kInlineSchedWaking, id,
use_slow_sorting_);
@@ -329,11 +353,14 @@
kSystraceLine,
kTracePacket,
kTrackEvent,
- kMax = kTrackEvent,
+ kGeckoEvent,
+ kArtMethodEvent,
+ kPerfTextEvent,
+ kMax = kPerfTextEvent,
};
// Number of bits required to store the max element in |Type|.
- static constexpr uint32_t kMaxTypeBits = 4;
+ static constexpr uint32_t kMaxTypeBits = 6;
static_assert(static_cast<uint8_t>(Type::kMax) <= (1 << kMaxTypeBits),
"Max type does not fit inside storage");
@@ -357,20 +384,20 @@
Type type() const { return static_cast<Type>(event_type); }
// For std::lower_bound().
- static inline bool Compare(const TimestampedEvent& x, int64_t ts) {
+ static bool Compare(const TimestampedEvent& x, int64_t ts) {
return x.ts < ts;
}
// For std::sort().
- inline bool operator<(const TimestampedEvent& evt) const {
+ bool operator<(const TimestampedEvent& evt) const {
return std::tie(ts, chunk_index, chunk_offset) <
std::tie(evt.ts, evt.chunk_index, evt.chunk_offset);
}
struct SlowOperatorLess {
// For std::sort() in slow mode.
- inline bool operator()(const TimestampedEvent& a,
- const TimestampedEvent& b) const {
+ bool operator()(const TimestampedEvent& a,
+ const TimestampedEvent& b) const {
int64_t a_key =
a.type() == Type::kJsonValueWithDur
? std::numeric_limits<int64_t>::max() -
@@ -448,8 +475,8 @@
void SortAndExtractEventsUntilAllocId(BumpAllocator::AllocId alloc_id);
- inline Queue* GetQueue(size_t index,
- std::optional<MachineId> machine_id = std::nullopt) {
+ Queue* GetQueue(size_t index,
+ std::optional<MachineId> machine_id = std::nullopt) {
// sorter_data_by_machine_[0] corresponds to the default machine.
PERFETTO_DCHECK(sorter_data_by_machine_[0].machine_id == std::nullopt);
auto* queues = &sorter_data_by_machine_[0].queues;
@@ -470,17 +497,29 @@
return &queues->at(index);
}
- inline void AppendNonFtraceEvent(
+ template <typename E>
+ void AppendNonFtraceEvent(
int64_t ts,
TimestampedEvent::Type event_type,
- TraceTokenBuffer::Id id,
+ E&& evt,
std::optional<MachineId> machine_id = std::nullopt) {
+ if (PERFETTO_UNLIKELY(event_handling_ == EventHandling::kDrop)) {
+ return;
+ }
+ TraceTokenBuffer::Id id = token_buffer_.Append(std::forward<E>(evt));
+ AppendNonFtraceEventWithId(ts, event_type, id, machine_id);
+ }
+
+ void AppendNonFtraceEventWithId(int64_t ts,
+ TimestampedEvent::Type event_type,
+ TraceTokenBuffer::Id id,
+ std::optional<MachineId> machine_id) {
Queue* queue = GetQueue(0, machine_id);
queue->Append(ts, event_type, id, use_slow_sorting_);
UpdateAppendMaxTs(queue);
}
- inline void UpdateAppendMaxTs(Queue* queue) {
+ void UpdateAppendMaxTs(Queue* queue) {
append_max_ts_ = std::max(append_max_ts_, queue->max_ts_);
}
@@ -537,9 +576,8 @@
// max(e.ts for e appended to the sorter)
int64_t append_max_ts_ = 0;
- // Used for performance tests. True when setting
- // TRACE_PROCESSOR_SORT_ONLY=1.
- bool bypass_next_stage_for_testing_ = false;
+ // How events pushed into the sorter should be handled.
+ EventHandling event_handling_ = EventHandling::kSortAndPush;
// max(e.ts for e pushed to next stage)
int64_t latest_pushed_event_ts_ = std::numeric_limits<int64_t>::min();
diff --git a/src/trace_processor/storage/metadata.h b/src/trace_processor/storage/metadata.h
index ba5d9ee..a690f84 100644
--- a/src/trace_processor/storage/metadata.h
+++ b/src/trace_processor/storage/metadata.h
@@ -31,8 +31,10 @@
F(all_data_source_flushed_ns, KeyType::kMulti, Variadic::kInt), \
F(all_data_source_started_ns, KeyType::kSingle, Variadic::kInt), \
F(android_build_fingerprint, KeyType::kSingle, Variadic::kString), \
+ F(android_device_manufacturer, KeyType::kSingle, Variadic::kString), \
F(android_sdk_version, KeyType::kSingle, Variadic::kInt), \
F(android_soc_model, KeyType::kSingle, Variadic::kString), \
+ F(android_guest_soc_model, KeyType::kSingle, Variadic::kString), \
F(android_hardware_revision, KeyType::kSingle, Variadic::kString), \
F(android_storage_model, KeyType::kSingle, Variadic::kString), \
F(android_ram_model, KeyType::kSingle, Variadic::kString), \
@@ -48,6 +50,7 @@
F(ftrace_setup_errors, KeyType::kMulti, Variadic::kString), \
F(ftrace_latest_data_start_ns, KeyType::kSingle, Variadic::kInt), \
F(range_of_interest_start_us, KeyType::kSingle, Variadic::kInt), \
+ F(slow_start_data_source, KeyType::kMulti, Variadic::kString), \
F(statsd_triggering_subscription_id, KeyType::kSingle, Variadic::kInt), \
F(system_machine, KeyType::kSingle, Variadic::kString), \
F(system_name, KeyType::kSingle, Variadic::kString), \
diff --git a/src/trace_processor/storage/stats.h b/src/trace_processor/storage/stats.h
index c8f45a3..cb24633 100644
--- a/src/trace_processor/storage/stats.h
+++ b/src/trace_processor/storage/stats.h
@@ -67,6 +67,21 @@
"unreliable. The kernel buffer overwrote events between our reads " \
"in userspace. Try re-recording the trace with a bigger buffer " \
"(ftrace_config.buffer_size_kb), or with fewer enabled ftrace events."),\
+ F(ftrace_kprobe_hits_begin, kSingle, kInfo, kTrace, \
+ "The number of kretprobe hits at the beginning of the trace."), \
+ F(ftrace_kprobe_hits_end, kSingle, kInfo, kTrace, \
+ "The number of kretprobe hits at the end of the trace."), \
+ F(ftrace_kprobe_hits_delta, kSingle, kInfo, kTrace, \
+ "The number of kprobe hits encountered during the collection of the" \
+ "trace."), \
+ F(ftrace_kprobe_misses_begin, kSingle, kInfo, kTrace, \
+ "The number of kretprobe missed events at the beginning of the trace."),\
+ F(ftrace_kprobe_misses_end, kSingle, kInfo, kTrace, \
+ "The number of kretprobe missed events at the end of the trace."), \
+ F(ftrace_kprobe_misses_delta, kSingle, kDataLoss, kTrace, \
+ "The number of kretprobe missed events encountered during the " \
+ "collection of the trace. A value greater than zero is due to the " \
+ "maxactive parameter for the kretprobe being too small"), \
F(ftrace_setup_errors, kSingle, kInfo, kTrace, \
"One or more atrace/ftrace categories were not found or failed to " \
"enable. See ftrace_setup_errors in the metadata table for details."), \
@@ -159,7 +174,17 @@
"the RING_BUFFER start or after the DISCARD buffer end."), \
F(traced_buf_sequence_packet_loss, kIndexed, kDataLoss, kAnalysis, \
"The number of groups of consecutive packets lost in each sequence for " \
- "this buffer"), \
+ "this buffer"), \
+ F(traced_buf_incremental_sequences_dropped, kIndexed, kDataLoss, kAnalysis, \
+ "For a given buffer, indicates the number of sequences where all the " \
+ "packets on that sequence were dropped due to lack of a valid " \
+ "incremental state (i.e. interned data). This is usually a strong sign " \
+ "that either: " \
+ "1) incremental state invalidation is disabled. " \
+ "2) the incremental state invalidation interval is too low. " \
+ "In either case, see " \
+ "https://perfetto.dev/docs/concepts/buffers" \
+ "#incremental-state-in-trace-packets"), \
F(traced_buf_write_wrap_count, kIndexed, kInfo, kTrace, ""), \
F(traced_chunks_discarded, kSingle, kInfo, kTrace, ""), \
F(traced_data_sources_registered, kSingle, kInfo, kTrace, ""), \
@@ -428,7 +453,17 @@
"Indicates a callsite in legacy v8 CPU profiling is invalid."), \
F(legacy_v8_cpu_profile_invalid_sample, kSingle, kError, kAnalysis, \
"Indicates a sample in legacy v8 CPU profile is invalid. This will " \
- "cause CPU samples to be missing in the UI.")
+ "cause CPU samples to be missing in the UI."), \
+ F(config_write_into_file_no_flush, kSingle, kError, kTrace, \
+ "The trace was collected with the `write_into_file` option set but " \
+ "*without* `flush_period_ms` being set. This will cause the trace to " \
+ "be fully loaded into memory and use significantly more memory than " \
+ "necessary."), \
+ F(config_write_into_file_discard, kIndexed, kDataLoss, kTrace, \
+ "The trace was collected with the `write_into_file` option set but " \
+ "uses a `DISCARD` buffer. This configuration is strongly discouraged " \
+ "and can cause mysterious data loss in the trace. Please use " \
+ "`RING_BUFFER` buffers instead.")
// clang-format on
enum Type {
diff --git a/src/trace_processor/storage/trace_storage.h b/src/trace_processor/storage/trace_storage.h
index 2e2a3ec..f590be0 100644
--- a/src/trace_processor/storage/trace_storage.h
+++ b/src/trace_processor/storage/trace_storage.h
@@ -36,7 +36,6 @@
#include "perfetto/base/time.h"
#include "perfetto/ext/base/string_view.h"
#include "perfetto/trace_processor/basic_types.h"
-#include "perfetto/trace_processor/status.h"
#include "src/trace_processor/containers/null_term_string_view.h"
#include "src/trace_processor/containers/string_pool.h"
#include "src/trace_processor/db/column/types.h"
@@ -58,8 +57,7 @@
#include "src/trace_processor/tables/winscope_tables_py.h"
#include "src/trace_processor/types/variadic.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
// UniquePid is an offset into |unique_processes_|. This is necessary because
// Unix pids are reused and thus not guaranteed to be unique over a long
@@ -231,6 +229,12 @@
virtual StringId InternString(base::StringView str) {
return string_pool_.InternString(str);
}
+ virtual StringId InternString(const char* str) {
+ return InternString(base::StringView(str));
+ }
+ virtual StringId InternString(const std::string& str) {
+ return InternString(base::StringView(str));
+ }
// Example usage: SetStats(stats::android_log_num_failed, 42);
void SetStats(size_t key, int64_t value) {
@@ -271,6 +275,12 @@
return std::nullopt;
}
+ int64_t GetStats(size_t key) {
+ PERFETTO_DCHECK(key < stats::kNumKeys);
+ PERFETTO_DCHECK(stats::kTypes[key] == stats::kSingle);
+ return stats_[key].value;
+ }
+
class ScopedStatsTracer {
public:
ScopedStatsTracer(TraceStorage* storage, size_t key)
@@ -380,43 +390,6 @@
return &gpu_counter_track_table_;
}
- const tables::EnergyCounterTrackTable& energy_counter_track_table() const {
- return energy_counter_track_table_;
- }
- tables::EnergyCounterTrackTable* mutable_energy_counter_track_table() {
- return &energy_counter_track_table_;
- }
-
- const tables::LinuxDeviceTrackTable& linux_device_track_table() const {
- return linux_device_track_table_;
- }
- tables::LinuxDeviceTrackTable* mutable_linux_device_track_table() {
- return &linux_device_track_table_;
- }
-
- const tables::UidCounterTrackTable& uid_counter_track_table() const {
- return uid_counter_track_table_;
- }
- tables::UidCounterTrackTable* mutable_uid_counter_track_table() {
- return &uid_counter_track_table_;
- }
-
- const tables::EnergyPerUidCounterTrackTable&
- energy_per_uid_counter_track_table() const {
- return energy_per_uid_counter_track_table_;
- }
- tables::EnergyPerUidCounterTrackTable*
- mutable_energy_per_uid_counter_track_table() {
- return &energy_per_uid_counter_track_table_;
- }
-
- const tables::IrqCounterTrackTable& irq_counter_track_table() const {
- return irq_counter_track_table_;
- }
- tables::IrqCounterTrackTable* mutable_irq_counter_track_table() {
- return &irq_counter_track_table_;
- }
-
const tables::PerfCounterTrackTable& perf_counter_track_table() const {
return perf_counter_track_table_;
}
@@ -459,13 +432,6 @@
return &thread_counter_track_table_;
}
- const tables::SoftirqCounterTrackTable& softirq_counter_track_table() const {
- return softirq_counter_track_table_;
- }
- tables::SoftirqCounterTrackTable* mutable_softirq_counter_track_table() {
- return &softirq_counter_track_table_;
- }
-
const tables::SchedSliceTable& sched_slice_table() const {
return sched_slice_table_;
}
@@ -704,18 +670,6 @@
}
tables::GpuTrackTable* mutable_gpu_track_table() { return &gpu_track_table_; }
- const tables::UidTrackTable& uid_track_table() const {
- return uid_track_table_;
- }
- tables::UidTrackTable* mutable_uid_track_table() { return &uid_track_table_; }
-
- const tables::GpuWorkPeriodTrackTable& gpu_work_period_track_table() const {
- return gpu_work_period_track_table_;
- }
- tables::GpuWorkPeriodTrackTable* mutable_gpu_work_period_track_table() {
- return &gpu_work_period_track_table_;
- }
-
const tables::VulkanMemoryAllocationsTable& vulkan_memory_allocations_table()
const {
return vulkan_memory_allocations_table_;
@@ -1073,13 +1027,8 @@
tables::ThreadStateTable thread_state_table_{&string_pool_};
tables::CpuTrackTable cpu_track_table_{&string_pool_, &track_table_};
tables::GpuTrackTable gpu_track_table_{&string_pool_, &track_table_};
- tables::UidTrackTable uid_track_table_{&string_pool_, &track_table_};
- tables::GpuWorkPeriodTrackTable gpu_work_period_track_table_{
- &string_pool_, &uid_track_table_};
tables::ProcessTrackTable process_track_table_{&string_pool_, &track_table_};
tables::ThreadTrackTable thread_track_table_{&string_pool_, &track_table_};
- tables::LinuxDeviceTrackTable linux_device_track_table_{&string_pool_,
- &track_table_};
// Track tables for counter events.
tables::CounterTrackTable counter_track_table_{&string_pool_, &track_table_};
@@ -1089,18 +1038,8 @@
&string_pool_, &counter_track_table_};
tables::CpuCounterTrackTable cpu_counter_track_table_{&string_pool_,
&counter_track_table_};
- tables::IrqCounterTrackTable irq_counter_track_table_{&string_pool_,
- &counter_track_table_};
- tables::SoftirqCounterTrackTable softirq_counter_track_table_{
- &string_pool_, &counter_track_table_};
tables::GpuCounterTrackTable gpu_counter_track_table_{&string_pool_,
&counter_track_table_};
- tables::EnergyCounterTrackTable energy_counter_track_table_{
- &string_pool_, &counter_track_table_};
- tables::UidCounterTrackTable uid_counter_track_table_{&string_pool_,
- &counter_track_table_};
- tables::EnergyPerUidCounterTrackTable energy_per_uid_counter_track_table_{
- &string_pool_, &uid_counter_track_table_};
tables::GpuCounterGroupTable gpu_counter_group_table_{&string_pool_};
tables::PerfCounterTrackTable perf_counter_track_table_{
&string_pool_, &counter_track_table_};
@@ -1251,8 +1190,7 @@
std::array<StringId, Variadic::kMaxType + 1> variadic_type_ids_;
};
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
template <>
struct std::hash<::perfetto::trace_processor::BaseId> {
diff --git a/src/trace_processor/tables/android_tables.py b/src/trace_processor/tables/android_tables.py
index e086d3d..47fe3ea 100644
--- a/src/trace_processor/tables/android_tables.py
+++ b/src/trace_processor/tables/android_tables.py
@@ -24,6 +24,7 @@
from python.generators.trace_processor_table.public import TableDoc
from python.generators.trace_processor_table.public import CppTableId
from python.generators.trace_processor_table.public import CppUint32
+from python.generators.trace_processor_table.public import WrappingSqlView
from src.trace_processor.tables.metadata_tables import THREAD_TABLE
@@ -164,6 +165,8 @@
C('event_id', CppUint32()),
C('ts', CppInt64()),
C('arg_set_id', CppUint32()),
+ C('base64_proto', CppString()),
+ C('base64_proto_id', CppOptional(CppUint32())),
],
tabledoc=TableDoc(
doc='Contains Android MotionEvents processed by the system',
@@ -180,6 +183,8 @@
ColumnDoc(
doc='Details of the motion event parsed from the proto message.',
joinable='args.arg_set_id'),
+ 'base64_proto': 'Raw proto message encoded in base64',
+ 'base64_proto_id': 'String id for raw proto message',
}))
ANDROID_KEY_EVENTS_TABLE = Table(
@@ -190,6 +195,8 @@
C('event_id', CppUint32()),
C('ts', CppInt64()),
C('arg_set_id', CppUint32()),
+ C('base64_proto', CppString()),
+ C('base64_proto_id', CppOptional(CppUint32())),
],
tabledoc=TableDoc(
doc='Contains Android KeyEvents processed by the system',
@@ -206,6 +213,8 @@
ColumnDoc(
doc='Details of the key event parsed from the proto message.',
joinable='args.arg_set_id'),
+ 'base64_proto': 'Raw proto message encoded in base64',
+ 'base64_proto_id': 'String id for raw proto message',
}))
ANDROID_INPUT_EVENT_DISPATCH_TABLE = Table(
@@ -217,10 +226,11 @@
C('arg_set_id', CppUint32()),
C('vsync_id', CppInt64()),
C('window_id', CppInt32()),
+ C('base64_proto', CppString()),
+ C('base64_proto_id', CppOptional(CppUint32())),
],
tabledoc=TableDoc(
- doc=
- '''
+ doc='''
Contains records of Android input events being dispatched to input windows
by the Android Framework.
''',
@@ -242,6 +252,8 @@
''',
'window_id':
'The id of the window to which the event was dispatched.',
+ 'base64_proto': 'Raw proto message encoded in base64',
+ 'base64_proto_id': 'String id for raw proto message',
}))
# Keep this list sorted.
diff --git a/src/trace_processor/tables/jit_tables.py b/src/trace_processor/tables/jit_tables.py
index 44707a6..6f0637f 100644
--- a/src/trace_processor/tables/jit_tables.py
+++ b/src/trace_processor/tables/jit_tables.py
@@ -27,6 +27,7 @@
from python.generators.trace_processor_table.public import ColumnFlag
from python.generators.trace_processor_table.public import Table
from python.generators.trace_processor_table.public import TableDoc
+from python.generators.trace_processor_table.public import WrappingSqlView
from .profiler_tables import STACK_PROFILE_FRAME_TABLE
JIT_CODE_TABLE = Table(
diff --git a/src/trace_processor/tables/metadata_tables.py b/src/trace_processor/tables/metadata_tables.py
index 99cca68..10da09b 100644
--- a/src/trace_processor/tables/metadata_tables.py
+++ b/src/trace_processor/tables/metadata_tables.py
@@ -203,6 +203,7 @@
C('capacity', CppOptional(CppUint32())),
C('arg_set_id', CppOptional(CppUint32())),
],
+ wrapping_sql_view=WrappingSqlView('cpu'),
tabledoc=TableDoc(
doc='''
Contains information of processes seen during the trace
@@ -212,8 +213,7 @@
'cpu':
'''the index (0-based) of the CPU core on the device''',
'cluster_id':
- '''the cluster id is shared by CPUs in
-the same cluster''',
+ '''the cluster id is shared by CPUs in the same cluster''',
'processor':
'''a string describing this core''',
'machine_id':
@@ -243,11 +243,14 @@
C('common_flags', CppUint32()),
C('ucpu', CppTableId(CPU_TABLE))
],
+ wrapping_sql_view=WrappingSqlView('track'),
tabledoc=TableDoc(
doc='''
Contains 'raw' events from the trace for some types of events. This
table only exists for debugging purposes and should not be relied on
in production usecases (i.e. metrics, standard library etc).
+
+ If you are looking for ftrace_events: please use the ftrace_event table.
''',
group='Events',
columns={
@@ -281,6 +284,7 @@
sql_name='__intrinsic_ftrace_event',
parent=RAW_TABLE,
columns=[],
+ wrapping_sql_view=WrappingSqlView('ftrace_event'),
tabledoc=TableDoc(
doc='''
Contains all the ftrace events in the trace. This table exists only
@@ -401,6 +405,7 @@
C('ucpu', CppTableId(CPU_TABLE)),
C('freq', CppUint32()),
],
+ wrapping_sql_view=WrappingSqlView('cpu_freq'),
tabledoc=TableDoc(
doc='''''', group='Misc', columns={
'ucpu': '''''',
@@ -455,7 +460,9 @@
C('name', CppOptional(CppString())),
C('size', CppInt64()),
C('trace_type', CppString()),
+ C('processing_order', CppOptional(CppInt64())),
],
+ wrapping_sql_view=WrappingSqlView('trace_file'),
tabledoc=TableDoc(
doc='''
Metadata related to the trace file parsed. Note the order in which
@@ -475,6 +482,8 @@
'''Size in bytes''',
'trace_type':
'''Trace type''',
+ 'processing_order':
+ '''In which order where the files were processed.''',
}))
# Keep this list sorted.
diff --git a/src/trace_processor/tables/profiler_tables.py b/src/trace_processor/tables/profiler_tables.py
index 60cb1fc..eafd5a9 100644
--- a/src/trace_processor/tables/profiler_tables.py
+++ b/src/trace_processor/tables/profiler_tables.py
@@ -24,6 +24,7 @@
from python.generators.trace_processor_table.public import TableDoc
from python.generators.trace_processor_table.public import CppTableId
from python.generators.trace_processor_table.public import CppUint32
+from python.generators.trace_processor_table.public import WrappingSqlView
from src.trace_processor.tables.track_tables import TRACK_TABLE, COUNTER_TRACK_TABLE
@@ -236,6 +237,7 @@
columns=[
C('cmdline', CppOptional(CppString())),
],
+ wrapping_sql_view=WrappingSqlView('perf_session'),
tabledoc=TableDoc(
doc='''Perf sessions.''',
group='Callstack profilers',
diff --git a/src/trace_processor/tables/sched_tables.py b/src/trace_processor/tables/sched_tables.py
index 3539543..b16d014 100644
--- a/src/trace_processor/tables/sched_tables.py
+++ b/src/trace_processor/tables/sched_tables.py
@@ -41,6 +41,7 @@
C('priority', CppInt32()),
C('ucpu', CppTableId(CPU_TABLE)),
],
+ wrapping_sql_view=WrappingSqlView('sched'),
tabledoc=TableDoc(
doc='''
This table holds slices with kernel thread scheduling information.
@@ -133,6 +134,7 @@
C('irq_context', CppOptional(CppUint32())),
C('ucpu', CppOptional(CppTableId(CPU_TABLE))),
],
+ wrapping_sql_view=WrappingSqlView('thread_state'),
tabledoc=TableDoc(
doc='''
This table contains the scheduling state of every thread on the
diff --git a/src/trace_processor/tables/slice_tables.py b/src/trace_processor/tables/slice_tables.py
index dd394a3..99f64c8 100644
--- a/src/trace_processor/tables/slice_tables.py
+++ b/src/trace_processor/tables/slice_tables.py
@@ -352,25 +352,37 @@
C('packet_tcp_flags_str', CppOptional(CppString())),
],
parent=SLICE_TABLE,
+ wrapping_sql_view=WrappingSqlView('android_network_packets'),
tabledoc=TableDoc(
doc="""
This table contains details on Android Network activity.
""",
group='Slice',
columns={
- 'iface': 'The name of the network interface used',
- 'direction': 'The direction of traffic (Received or Transmitted)',
+ 'iface':
+ 'The name of the network interface used',
+ 'direction':
+ 'The direction of traffic (Received or Transmitted)',
'packet_transport':
'The transport protocol of packets in this event',
- 'packet_length': 'The length (in bytes) of packets in this event',
- 'packet_count': 'The number of packets contained in this event',
- 'socket_tag': 'The Android network tag of the socket',
- 'socket_tag_str': 'The socket tag formatted as a hex string',
- 'socket_uid': 'The Linux user id of the socket',
- 'local_port': 'The local udp/tcp port',
- 'remote_port': 'The remote udp/tcp port',
- 'packet_icmp_type': 'The 1-byte ICMP type identifier',
- 'packet_icmp_code': 'The 1-byte ICMP code identifier',
+ 'packet_length':
+ 'The length (in bytes) of packets in this event',
+ 'packet_count':
+ 'The number of packets contained in this event',
+ 'socket_tag':
+ 'The Android network tag of the socket',
+ 'socket_tag_str':
+ 'The socket tag formatted as a hex string',
+ 'socket_uid':
+ 'The Linux user id of the socket',
+ 'local_port':
+ 'The local udp/tcp port',
+ 'remote_port':
+ 'The remote udp/tcp port',
+ 'packet_icmp_type':
+ 'The 1-byte ICMP type identifier',
+ 'packet_icmp_code':
+ 'The 1-byte ICMP code identifier',
'packet_tcp_flags':
'The TCP flags as an integer bitmask (FIN=0x1, SYN=0x2, etc)',
'packet_tcp_flags_str':
diff --git a/src/trace_processor/tables/table_destructors.cc b/src/trace_processor/tables/table_destructors.cc
index e49761c..b01364ae 100644
--- a/src/trace_processor/tables/table_destructors.cc
+++ b/src/trace_processor/tables/table_destructors.cc
@@ -110,20 +110,12 @@
ThreadTrackTable::~ThreadTrackTable() = default;
CpuTrackTable::~CpuTrackTable() = default;
GpuTrackTable::~GpuTrackTable() = default;
-UidTrackTable::~UidTrackTable() = default;
-GpuWorkPeriodTrackTable::~GpuWorkPeriodTrackTable() = default;
CounterTrackTable::~CounterTrackTable() = default;
ThreadCounterTrackTable::~ThreadCounterTrackTable() = default;
ProcessCounterTrackTable::~ProcessCounterTrackTable() = default;
CpuCounterTrackTable::~CpuCounterTrackTable() = default;
-IrqCounterTrackTable::~IrqCounterTrackTable() = default;
-SoftirqCounterTrackTable::~SoftirqCounterTrackTable() = default;
GpuCounterTrackTable::~GpuCounterTrackTable() = default;
PerfCounterTrackTable::~PerfCounterTrackTable() = default;
-EnergyCounterTrackTable::~EnergyCounterTrackTable() = default;
-UidCounterTrackTable::~UidCounterTrackTable() = default;
-EnergyPerUidCounterTrackTable::~EnergyPerUidCounterTrackTable() = default;
-LinuxDeviceTrackTable::~LinuxDeviceTrackTable() = default;
// trace_proto_tables_py.h
ExperimentalProtoPathTable::~ExperimentalProtoPathTable() = default;
diff --git a/src/trace_processor/tables/track_tables.py b/src/trace_processor/tables/track_tables.py
index 411f801..1ed6df8 100644
--- a/src/trace_processor/tables/track_tables.py
+++ b/src/trace_processor/tables/track_tables.py
@@ -14,17 +14,18 @@
"""Contains tables for tracks."""
from python.generators.trace_processor_table.public import Column as C
+from python.generators.trace_processor_table.public import ColumnDoc
+from python.generators.trace_processor_table.public import ColumnFlag
from python.generators.trace_processor_table.public import CppInt32
from python.generators.trace_processor_table.public import CppInt64
from python.generators.trace_processor_table.public import CppOptional
-from python.generators.trace_processor_table.public import CppString
-from python.generators.trace_processor_table.public import Table
-from python.generators.trace_processor_table.public import TableDoc
-from python.generators.trace_processor_table.public import ColumnDoc
-from python.generators.trace_processor_table.public import ColumnFlag
from python.generators.trace_processor_table.public import CppSelfTableId
+from python.generators.trace_processor_table.public import CppString
from python.generators.trace_processor_table.public import CppTableId
from python.generators.trace_processor_table.public import CppUint32
+from python.generators.trace_processor_table.public import Table
+from python.generators.trace_processor_table.public import TableDoc
+from python.generators.trace_processor_table.public import WrappingSqlView
from src.trace_processor.tables.metadata_tables import CPU_TABLE, MACHINE_TABLE
@@ -37,9 +38,10 @@
C("parent_id", CppOptional(CppSelfTableId())),
C("source_arg_set_id", CppOptional(CppUint32())),
C('machine_id', CppOptional(CppTableId(MACHINE_TABLE))),
- C("classification", CppOptional(CppString()), flags=ColumnFlag.HIDDEN),
- C("tags", CppOptional(CppUint32()), flags=ColumnFlag.HIDDEN),
+ C("classification", CppString()),
+ C("dimension_arg_set_id", CppOptional(CppUint32())),
],
+ wrapping_sql_view=WrappingSqlView('track'),
tabledoc=TableDoc(
doc='''
Tracks are a fundamental concept in trace processor and represent a
@@ -59,14 +61,6 @@
The track which is the "parent" of this track. Only non-null
for tracks created using Perfetto's track_event API.
''',
- 'source_arg_set_id':
- ColumnDoc(
- doc='''
- Args for this track which store information about "source"
- of this track in the trace. For example: whether this
- track orginated from atrace, Chrome tracepoints etc.
- ''',
- joinable='args.arg_set_id'),
'machine_id':
'''
Machine identifier, non-null for tracks on a remote machine.
@@ -76,9 +70,25 @@
Classification of this track. Responsible for grouping
similar tracks together.
''',
- 'tags':
+ 'dimension_arg_set_id':
ColumnDoc(
- doc='Additional details about the track.',
+ doc='''
+ The dimensions of the track which uniquely identify the
+ track within a given classification.
+
+ Join with the `args` table or use the `EXTRACT_ARG` helper
+ function to expand the args.
+ ''',
+ joinable='args.arg_set_id'),
+ 'source_arg_set_id':
+ ColumnDoc(
+ doc='''
+ Generic key-value pairs containing extra information about
+ the track.
+
+ Join with the `args` table or use the `EXTRACT_ARG` helper
+ function to expand the args.
+ ''',
joinable='args.arg_set_id'),
}))
@@ -130,6 +140,7 @@
columns=[
C('ucpu', CppTableId(CPU_TABLE)),
],
+ wrapping_sql_view=WrappingSqlView('cpu_track'),
parent=TRACK_TABLE,
tabledoc=TableDoc(
doc='Tracks which are associated to a single CPU',
@@ -160,36 +171,6 @@
'The context id for the GPU this track is associated to.'
}))
-UID_TRACK_TABLE = Table(
- python_module=__file__,
- class_name='UidTrackTable',
- sql_name='uid_track',
- columns=[
- C('uid', CppInt32()),
- ],
- parent=TRACK_TABLE,
- tabledoc=TableDoc(
- doc='Tracks associated to a UID.',
- group='Tracks',
- columns={
- 'uid': 'The uid associated with this track.',
- }))
-
-GPU_WORK_PERIOD_TRACK_TABLE = Table(
- python_module=__file__,
- class_name='GpuWorkPeriodTrackTable',
- sql_name='gpu_work_period_track',
- columns=[
- C('gpu_id', CppUint32()),
- ],
- parent=UID_TRACK_TABLE,
- tabledoc=TableDoc(
- doc='Tracks containing gpu_work_period events.',
- group='Tracks',
- columns={
- 'gpu_id': 'The identifier for the GPU.',
- }))
-
COUNTER_TRACK_TABLE = Table(
python_module=__file__,
class_name='CounterTrackTable',
@@ -259,6 +240,7 @@
columns=[
C('ucpu', CppTableId(CPU_TABLE)),
],
+ wrapping_sql_view=WrappingSqlView('cpu_counter_track'),
parent=COUNTER_TRACK_TABLE,
tabledoc=TableDoc(
doc='Tracks containing counter-like events associated to a CPU.',
@@ -267,32 +249,6 @@
'ucpu': 'The unique CPU identifier associated with this track.'
}))
-IRQ_COUNTER_TRACK_TABLE = Table(
- python_module=__file__,
- class_name='IrqCounterTrackTable',
- sql_name='irq_counter_track',
- columns=[
- C('irq', CppInt32()),
- ],
- parent=COUNTER_TRACK_TABLE,
- tabledoc=TableDoc(
- doc='Tracks containing counter-like events associated to an hardirq',
- group='Counter Tracks',
- columns={'irq': 'The identifier for the hardirq.'}))
-
-SOFTIRQ_COUNTER_TRACK_TABLE = Table(
- python_module=__file__,
- class_name='SoftirqCounterTrackTable',
- sql_name='softirq_counter_track',
- columns=[
- C('softirq', CppInt32()),
- ],
- parent=COUNTER_TRACK_TABLE,
- tabledoc=TableDoc(
- doc='Tracks containing counter-like events associated to a softirq',
- group='Counter Tracks',
- columns={'softirq': 'The identifier for the softirq.'}))
-
GPU_COUNTER_TRACK_TABLE = Table(
python_module=__file__,
class_name='GpuCounterTrackTable',
@@ -307,90 +263,16 @@
columns={'gpu_id': 'The identifier for the GPU.'}))
-ENERGY_COUNTER_TRACK_TABLE = Table(
- python_module=__file__,
- class_name='EnergyCounterTrackTable',
- sql_name='energy_counter_track',
- columns=[
- C('consumer_id', CppInt32()),
- C('consumer_type', CppString()),
- C('ordinal', CppInt32()),
- ],
- parent=COUNTER_TRACK_TABLE,
- tabledoc=TableDoc(
- doc='''
- Energy consumers' values for energy descriptors in
- energy_estimation_breakdown packet
- ''',
- group='Counter Tracks',
- columns={
- 'consumer_id': 'id of a distinct energy consumer',
- 'consumer_type': 'type of energy consumer',
- 'ordinal': 'ordinal of energy consumer'
- }))
-
-LINUX_DEVICE_TRACK_TABLE = Table(
- python_module=__file__,
- class_name='LinuxDeviceTrackTable',
- sql_name='linux_device_track',
- columns=[],
- parent=TRACK_TABLE,
- tabledoc=TableDoc(
- doc='''
- Slice data corresponding to runtime power state transitions
- associated with Linux devices (where a Linux device is anything
- managed by a Linux driver). The name of each track corresponds to the
- device name as recognized by the linux kernel running on the system.
- ''',
- group='Tracks',
- # No additional columns are needed because the track name implicitly
- # serves as the device name, providing all required information.
- columns={}))
-
-UID_COUNTER_TRACK_TABLE = Table(
- python_module=__file__,
- class_name='UidCounterTrackTable',
- sql_name='uid_counter_track',
- columns=[
- C('uid', CppInt32()),
- ],
- parent=COUNTER_TRACK_TABLE,
- tabledoc=TableDoc(
- doc='The uid associated with this track',
- group='Counter Tracks',
- columns={'uid': 'uid of process for which breakdowns are emitted'}))
-
-ENERGY_PER_UID_COUNTER_TRACK_TABLE = Table(
- python_module=__file__,
- class_name='EnergyPerUidCounterTrackTable',
- sql_name='energy_per_uid_counter_track',
- columns=[
- C('consumer_id', CppInt32()),
- ],
- parent=UID_COUNTER_TRACK_TABLE,
- tabledoc=TableDoc(
- doc='Energy consumer values for per uid in uid_counter_track',
- group='Counter Tracks',
- columns={'consumer_id': 'id of the consumer process'}))
-
# Keep this list sorted.
ALL_TABLES = [
COUNTER_TRACK_TABLE,
CPU_COUNTER_TRACK_TABLE,
CPU_TRACK_TABLE,
- ENERGY_COUNTER_TRACK_TABLE,
- ENERGY_PER_UID_COUNTER_TRACK_TABLE,
GPU_COUNTER_TRACK_TABLE,
GPU_TRACK_TABLE,
- GPU_WORK_PERIOD_TRACK_TABLE,
- IRQ_COUNTER_TRACK_TABLE,
- LINUX_DEVICE_TRACK_TABLE,
PROCESS_COUNTER_TRACK_TABLE,
PROCESS_TRACK_TABLE,
- SOFTIRQ_COUNTER_TRACK_TABLE,
THREAD_COUNTER_TRACK_TABLE,
THREAD_TRACK_TABLE,
TRACK_TABLE,
- UID_COUNTER_TRACK_TABLE,
- UID_TRACK_TABLE,
]
diff --git a/src/trace_processor/tables/v8_tables.py b/src/trace_processor/tables/v8_tables.py
index 28ed330..25592f0 100644
--- a/src/trace_processor/tables/v8_tables.py
+++ b/src/trace_processor/tables/v8_tables.py
@@ -29,6 +29,8 @@
from python.generators.trace_processor_table.public import CppUint32 as CppBool
from python.generators.trace_processor_table.public import Table
from python.generators.trace_processor_table.public import TableDoc
+from python.generators.trace_processor_table.public import WrappingSqlView
+
from .jit_tables import JIT_CODE_TABLE
V8_ISOLATE = Table(
diff --git a/src/trace_processor/tables/winscope_tables.py b/src/trace_processor/tables/winscope_tables.py
index 8887eb5..a218e6b 100644
--- a/src/trace_processor/tables/winscope_tables.py
+++ b/src/trace_processor/tables/winscope_tables.py
@@ -17,9 +17,11 @@
from python.generators.trace_processor_table.public import ColumnFlag
from python.generators.trace_processor_table.public import Table
from python.generators.trace_processor_table.public import CppTableId
+from python.generators.trace_processor_table.public import CppOptional
from python.generators.trace_processor_table.public import TableDoc
from python.generators.trace_processor_table.public import CppUint32
from python.generators.trace_processor_table.public import CppString
+from python.generators.trace_processor_table.public import WrappingSqlView
INPUTMETHOD_CLIENTS_TABLE = Table(
python_module=__file__,
@@ -28,6 +30,8 @@
columns=[
C('ts', CppInt64(), ColumnFlag.SORTED),
C('arg_set_id', CppUint32()),
+ C('base64_proto', CppString()),
+ C('base64_proto_id', CppOptional(CppUint32())),
],
tabledoc=TableDoc(
doc='InputMethod clients',
@@ -35,6 +39,8 @@
columns={
'ts': 'The timestamp the dump was triggered',
'arg_set_id': 'Extra args parsed from the proto message',
+ 'base64_proto': 'Raw proto message encoded in base64',
+ 'base64_proto_id': 'String id for raw proto message',
}))
INPUTMETHOD_MANAGER_SERVICE_TABLE = Table(
@@ -44,6 +50,8 @@
columns=[
C('ts', CppInt64(), ColumnFlag.SORTED),
C('arg_set_id', CppUint32()),
+ C('base64_proto', CppString()),
+ C('base64_proto_id', CppOptional(CppUint32())),
],
tabledoc=TableDoc(
doc='InputMethod manager service',
@@ -51,6 +59,8 @@
columns={
'ts': 'The timestamp the dump was triggered',
'arg_set_id': 'Extra args parsed from the proto message',
+ 'base64_proto': 'Raw proto message encoded in base64',
+ 'base64_proto_id': 'String id for raw proto message',
}))
INPUTMETHOD_SERVICE_TABLE = Table(
@@ -60,6 +70,8 @@
columns=[
C('ts', CppInt64(), ColumnFlag.SORTED),
C('arg_set_id', CppUint32()),
+ C('base64_proto', CppString()),
+ C('base64_proto_id', CppOptional(CppUint32())),
],
tabledoc=TableDoc(
doc='InputMethod service',
@@ -67,6 +79,8 @@
columns={
'ts': 'The timestamp the dump was triggered',
'arg_set_id': 'Extra args parsed from the proto message',
+ 'base64_proto': 'Raw proto message encoded in base64',
+ 'base64_proto_id': 'String id for raw proto message',
}))
SURFACE_FLINGER_LAYERS_SNAPSHOT_TABLE = Table(
@@ -76,6 +90,8 @@
columns=[
C('ts', CppInt64(), ColumnFlag.SORTED),
C('arg_set_id', CppUint32()),
+ C('base64_proto', CppString()),
+ C('base64_proto_id', CppOptional(CppUint32())),
],
tabledoc=TableDoc(
doc='SurfaceFlinger layers snapshot',
@@ -83,6 +99,8 @@
columns={
'ts': 'Timestamp of the snapshot',
'arg_set_id': 'Extra args parsed from the proto message',
+ 'base64_proto': 'Raw proto message encoded in base64',
+ 'base64_proto_id': 'String id for raw proto message',
}))
SURFACE_FLINGER_LAYER_TABLE = Table(
@@ -92,6 +110,8 @@
columns=[
C('snapshot_id', CppTableId(SURFACE_FLINGER_LAYERS_SNAPSHOT_TABLE)),
C('arg_set_id', CppUint32()),
+ C('base64_proto', CppString()),
+ C('base64_proto_id', CppOptional(CppUint32())),
],
tabledoc=TableDoc(
doc='SurfaceFlinger layer',
@@ -99,6 +119,8 @@
columns={
'snapshot_id': 'The snapshot that generated this layer',
'arg_set_id': 'Extra args parsed from the proto message',
+ 'base64_proto': 'Raw proto message encoded in base64',
+ 'base64_proto_id': 'String id for raw proto message',
}))
SURFACE_FLINGER_TRANSACTIONS_TABLE = Table(
@@ -108,6 +130,8 @@
columns=[
C('ts', CppInt64(), ColumnFlag.SORTED),
C('arg_set_id', CppUint32()),
+ C('base64_proto', CppString()),
+ C('base64_proto_id', CppOptional(CppUint32())),
],
tabledoc=TableDoc(
doc='SurfaceFlinger transactions. Each row contains a set of ' +
@@ -116,6 +140,8 @@
columns={
'ts': 'Timestamp of the transactions commit',
'arg_set_id': 'Extra args parsed from the proto message',
+ 'base64_proto': 'Raw proto message encoded in base64',
+ 'base64_proto_id': 'String id for raw proto message',
}))
VIEWCAPTURE_TABLE = Table(
@@ -125,6 +151,8 @@
columns=[
C('ts', CppInt64(), ColumnFlag.SORTED),
C('arg_set_id', CppUint32()),
+ C('base64_proto', CppString()),
+ C('base64_proto_id', CppOptional(CppUint32())),
],
tabledoc=TableDoc(
doc='ViewCapture',
@@ -132,6 +160,8 @@
columns={
'ts': 'The timestamp the views were captured',
'arg_set_id': 'Extra args parsed from the proto message',
+ 'base64_proto': 'Raw proto message encoded in base64',
+ 'base64_proto_id': 'String id for raw proto message',
}))
WINDOW_MANAGER_SHELL_TRANSITIONS_TABLE = Table(
@@ -142,6 +172,8 @@
C('ts', CppInt64()),
C('transition_id', CppInt64(), ColumnFlag.SORTED),
C('arg_set_id', CppUint32()),
+ C('base64_proto', CppString()),
+ C('base64_proto_id', CppOptional(CppUint32())),
],
tabledoc=TableDoc(
doc='Window Manager Shell Transitions',
@@ -150,6 +182,8 @@
'ts': 'The timestamp the transition started playing',
'transition_id': 'The id of the transition',
'arg_set_id': 'Extra args parsed from the proto message',
+ 'base64_proto': 'Raw proto message encoded in base64',
+ 'base64_proto_id': 'String id for raw proto message',
}))
WINDOW_MANAGER_SHELL_TRANSITION_HANDLERS_TABLE = Table(
@@ -159,6 +193,8 @@
columns=[
C('handler_id', CppInt64()),
C('handler_name', CppString()),
+ C('base64_proto', CppString()),
+ C('base64_proto_id', CppOptional(CppUint32())),
],
tabledoc=TableDoc(
doc='Window Manager Shell Transition Handlers',
@@ -166,6 +202,8 @@
columns={
'handler_id': 'The id of the handler',
'handler_name': 'The name of the handler',
+ 'base64_proto': 'Raw proto message encoded in base64',
+ 'base64_proto_id': 'String id for raw proto message',
}))
WINDOW_MANAGER_TABLE = Table(
@@ -175,13 +213,18 @@
columns=[
C('ts', CppInt64(), ColumnFlag.SORTED),
C('arg_set_id', CppUint32()),
+ C('base64_proto', CppString()),
+ C('base64_proto_id', CppOptional(CppUint32())),
],
+ wrapping_sql_view=WrappingSqlView('windowmanager'),
tabledoc=TableDoc(
doc='WindowManager',
group='Winscope',
columns={
'ts': 'The timestamp the state snapshot was captured',
'arg_set_id': 'Extra args parsed from the proto message',
+ 'base64_proto': 'Raw proto message encoded in base64',
+ 'base64_proto_id': 'String id for raw proto message',
}))
PROTOLOG_TABLE = Table(
diff --git a/src/trace_processor/trace_database_integrationtest.cc b/src/trace_processor/trace_database_integrationtest.cc
index 52852cd..b9d4c47 100644
--- a/src/trace_processor/trace_database_integrationtest.cc
+++ b/src/trace_processor/trace_database_integrationtest.cc
@@ -124,8 +124,9 @@
if (!status.ok())
return status;
}
- return processor_->NotifyEndOfFile();
+ return NotifyEndOfFile();
}
+ base::Status NotifyEndOfFile() { return processor_->NotifyEndOfFile(); }
Iterator Query(const std::string& query) {
return processor_->ExecuteQuery(query);
@@ -289,6 +290,8 @@
}
TEST_F(TraceProcessorIntegrationTest, ComputeMetricsFormattedExtension) {
+ ASSERT_OK(NotifyEndOfFile());
+
std::string metric_output;
base::Status status = Processor()->ComputeMetricText(
std::vector<std::string>{"test_chrome_metric"},
@@ -302,11 +305,12 @@
}
TEST_F(TraceProcessorIntegrationTest, ComputeMetricsFormattedNoExtension) {
+ ASSERT_OK(NotifyEndOfFile());
+
std::string metric_output;
- base::Status status = Processor()->ComputeMetricText(
+ ASSERT_OK(Processor()->ComputeMetricText(
std::vector<std::string>{"trace_metadata"},
- TraceProcessor::MetricResultFormat::kProtoText, &metric_output);
- ASSERT_TRUE(status.ok());
+ TraceProcessor::MetricResultFormat::kProtoText, &metric_output));
// Check that metric result starts with trace_metadata field. Since this is
// not an extension field, the field name is not fully qualified.
ASSERT_TRUE(metric_output.rfind("trace_metadata {") == 0);
@@ -410,13 +414,13 @@
}
TEST_F(TraceProcessorIntegrationTest, RestoreInitialTablesInvariant) {
- ASSERT_OK(Processor()->NotifyEndOfFile());
+ ASSERT_OK(NotifyEndOfFile());
uint64_t first_restore = RestoreInitialTables();
ASSERT_EQ(RestoreInitialTables(), first_restore);
}
TEST_F(TraceProcessorIntegrationTest, RestoreInitialTablesPerfettoSql) {
- ASSERT_OK(Processor()->NotifyEndOfFile());
+ ASSERT_OK(NotifyEndOfFile());
RestoreInitialTables();
for (int repeat = 0; repeat < 3; repeat++) {
@@ -465,7 +469,7 @@
}
TEST_F(TraceProcessorIntegrationTest, RestoreInitialTablesStandardSqlite) {
- ASSERT_OK(Processor()->NotifyEndOfFile());
+ ASSERT_OK(NotifyEndOfFile());
RestoreInitialTables();
for (int repeat = 0; repeat < 3; repeat++) {
@@ -491,13 +495,13 @@
}
TEST_F(TraceProcessorIntegrationTest, RestoreInitialTablesModules) {
- ASSERT_OK(Processor()->NotifyEndOfFile());
+ ASSERT_OK(NotifyEndOfFile());
RestoreInitialTables();
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());
}
@@ -511,7 +515,7 @@
}
TEST_F(TraceProcessorIntegrationTest, RestoreInitialTablesSpanJoin) {
- ASSERT_OK(Processor()->NotifyEndOfFile());
+ ASSERT_OK(NotifyEndOfFile());
RestoreInitialTables();
for (int repeat = 0; repeat < 3; repeat++) {
@@ -550,7 +554,7 @@
}
TEST_F(TraceProcessorIntegrationTest, RestoreInitialTablesWithClause) {
- ASSERT_OK(Processor()->NotifyEndOfFile());
+ ASSERT_OK(NotifyEndOfFile());
RestoreInitialTables();
for (int repeat = 0; repeat < 3; repeat++) {
@@ -567,7 +571,7 @@
}
TEST_F(TraceProcessorIntegrationTest, RestoreInitialTablesIndex) {
- ASSERT_OK(Processor()->NotifyEndOfFile());
+ ASSERT_OK(NotifyEndOfFile());
RestoreInitialTables();
for (int repeat = 0; repeat < 3; repeat++) {
@@ -605,7 +609,7 @@
}
TEST_F(TraceProcessorIntegrationTest, RestoreInitialTablesDependents) {
- ASSERT_OK(Processor()->NotifyEndOfFile());
+ ASSERT_OK(NotifyEndOfFile());
{
auto it = Query("create perfetto table foo as select 1 as x");
ASSERT_FALSE(it.Next());
@@ -625,7 +629,7 @@
}
TEST_F(TraceProcessorIntegrationTest, RestoreDependentFunction) {
- ASSERT_OK(Processor()->NotifyEndOfFile());
+ ASSERT_OK(NotifyEndOfFile());
{
auto it =
Query("create perfetto function foo0() returns INT as select 1 as x");
@@ -645,7 +649,7 @@
}
TEST_F(TraceProcessorIntegrationTest, RestoreDependentTableFunction) {
- ASSERT_OK(Processor()->NotifyEndOfFile());
+ ASSERT_OK(NotifyEndOfFile());
{
auto it = Query(
"create perfetto function foo0() returns TABLE(x INT) "
@@ -735,6 +739,7 @@
}
TEST_F(TraceProcessorIntegrationTest, ErrorMessageExecuteQuery) {
+ ASSERT_OK(NotifyEndOfFile());
auto it = Query("select t from slice");
ASSERT_FALSE(it.Next());
ASSERT_FALSE(it.Status().ok());
@@ -748,6 +753,7 @@
}
TEST_F(TraceProcessorIntegrationTest, ErrorMessageMetricFile) {
+ ASSERT_OK(NotifyEndOfFile());
ASSERT_TRUE(
Processor()->RegisterMetric("foo/bar.sql", "select t from slice").ok());
@@ -767,11 +773,12 @@
}
TEST_F(TraceProcessorIntegrationTest, ErrorMessageModule) {
- SqlModule module;
+ ASSERT_OK(NotifyEndOfFile());
+ SqlPackage module;
module.name = "foo";
- module.files.push_back(std::make_pair("foo.bar", "select t from slice"));
+ module.modules.push_back(std::make_pair("foo.bar", "select t from slice"));
- ASSERT_TRUE(Processor()->RegisterSqlModule(module).ok());
+ ASSERT_TRUE(Processor()->RegisterSqlPackage(module).ok());
auto it = Query("include perfetto module foo.bar;");
ASSERT_FALSE(it.Next());
@@ -819,7 +826,7 @@
->Parse(TraceBlobView(
TraceBlob::CopyFrom(kBadData, sizeof(kBadData))))
.ok());
- Processor()->NotifyEndOfFile();
+ NotifyEndOfFile();
}
TEST_F(TraceProcessorIntegrationTest, NoNotifyEndOfFileCalled) {
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index ba89252..cd04da5 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -29,9 +29,12 @@
#include <utility>
#include <vector>
+#include "perfetto/base/build_config.h"
#include "perfetto/base/logging.h"
#include "perfetto/base/status.h"
+#include "perfetto/base/thread_utils.h"
#include "perfetto/base/time.h"
+#include "perfetto/ext/base/clock_snapshots.h"
#include "perfetto/ext/base/flat_hash_map.h"
#include "perfetto/ext/base/small_vector.h"
#include "perfetto/ext/base/status_or.h"
@@ -45,12 +48,18 @@
#include "perfetto/trace_processor/trace_processor.h"
#include "src/trace_processor/importers/android_bugreport/android_log_event_parser_impl.h"
#include "src/trace_processor/importers/android_bugreport/android_log_reader.h"
+#include "src/trace_processor/importers/archive/gzip_trace_parser.h"
+#include "src/trace_processor/importers/archive/tar_trace_reader.h"
+#include "src/trace_processor/importers/archive/zip_trace_reader.h"
+#include "src/trace_processor/importers/art_method/art_method_parser_impl.h"
+#include "src/trace_processor/importers/art_method/art_method_tokenizer.h"
#include "src/trace_processor/importers/common/clock_tracker.h"
#include "src/trace_processor/importers/common/trace_file_tracker.h"
#include "src/trace_processor/importers/common/trace_parser.h"
#include "src/trace_processor/importers/fuchsia/fuchsia_trace_parser.h"
#include "src/trace_processor/importers/fuchsia/fuchsia_trace_tokenizer.h"
-#include "src/trace_processor/importers/gzip/gzip_trace_parser.h"
+#include "src/trace_processor/importers/gecko/gecko_trace_parser_impl.h"
+#include "src/trace_processor/importers/gecko/gecko_trace_tokenizer.h"
#include "src/trace_processor/importers/json/json_trace_parser_impl.h"
#include "src/trace_processor/importers/json/json_trace_tokenizer.h"
#include "src/trace_processor/importers/json/json_utils.h"
@@ -58,10 +67,11 @@
#include "src/trace_processor/importers/perf/perf_data_tokenizer.h"
#include "src/trace_processor/importers/perf/record_parser.h"
#include "src/trace_processor/importers/perf/spe_record_parser.h"
+#include "src/trace_processor/importers/perf_text/perf_text_trace_parser_impl.h"
+#include "src/trace_processor/importers/perf_text/perf_text_trace_tokenizer.h"
#include "src/trace_processor/importers/proto/additional_modules.h"
#include "src/trace_processor/importers/proto/content_analyzer.h"
#include "src/trace_processor/importers/systrace/systrace_trace_parser.h"
-#include "src/trace_processor/importers/zip/zip_trace_reader.h"
#include "src/trace_processor/iterator_impl.h"
#include "src/trace_processor/metrics/all_chrome_metrics.descriptor.h"
#include "src/trace_processor/metrics/all_webview_metrics.descriptor.h"
@@ -105,6 +115,7 @@
#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_sched_upid.h"
#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_slice_layout.h"
#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/table_info.h"
+#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/winscope_proto_to_args_with_defaults.h"
#include "src/trace_processor/perfetto_sql/stdlib/stdlib.h"
#include "src/trace_processor/sqlite/bindings/sqlite_aggregate_function.h"
#include "src/trace_processor/sqlite/bindings/sqlite_result.h"
@@ -182,14 +193,14 @@
void BuildBoundsTable(sqlite3* db, std::pair<int64_t, int64_t> bounds) {
char* error = nullptr;
- sqlite3_exec(db, "DELETE FROM trace_bounds", nullptr, nullptr, &error);
+ sqlite3_exec(db, "DELETE FROM _trace_bounds", nullptr, nullptr, &error);
if (error) {
PERFETTO_ELOG("Error deleting from bounds table: %s", error);
sqlite3_free(error);
return;
}
- base::StackString<1024> sql("INSERT INTO trace_bounds VALUES(%" PRId64
+ base::StackString<1024> sql("INSERT INTO _trace_bounds VALUES(%" PRId64
", %" PRId64 ")",
bounds.first, bounds.second);
sqlite3_exec(db, sql.c_str(), nullptr, nullptr, &error);
@@ -301,7 +312,7 @@
void InsertIntoTraceMetricsTable(sqlite3* db, const std::string& metric_name) {
char* insert_sql = sqlite3_mprintf(
- "INSERT INTO trace_metrics(name) VALUES('%q')", metric_name.c_str());
+ "INSERT INTO _trace_metrics(name) VALUES('%q')", metric_name.c_str());
char* insert_error = nullptr;
sqlite3_exec(db, insert_sql, nullptr, nullptr, &insert_error);
sqlite3_free(insert_sql);
@@ -311,14 +322,15 @@
}
}
-sql_modules::NameToModule GetStdlibModules() {
- sql_modules::NameToModule modules;
+sql_modules::NameToPackage GetStdlibPackages() {
+ sql_modules::NameToPackage packages;
for (const auto& file_to_sql : stdlib::kFileToSql) {
- std::string import_key = sql_modules::GetIncludeKey(file_to_sql.path);
- std::string module = sql_modules::GetModuleName(import_key);
- modules.Insert(module, {}).first->push_back({import_key, file_to_sql.sql});
+ std::string module_name = sql_modules::GetIncludeKey(file_to_sql.path);
+ std::string package_name = sql_modules::GetPackageName(module_name);
+ packages.Insert(package_name, {})
+ .first->push_back({module_name, file_to_sql.sql});
}
- return modules;
+ return packages;
}
std::pair<int64_t, int64_t> GetTraceTimestampBoundsNs(
@@ -415,7 +427,7 @@
std::make_unique<instruments_importer::RowParser>(&context_);
#endif
- if (util::IsGzipSupported()) {
+ if constexpr (util::IsGzipSupported()) {
context_.reader_registry->RegisterTraceReader<GzipTraceParser>(
kGzipTraceType);
context_.reader_registry->RegisterTraceReader<GzipTraceParser>(
@@ -423,13 +435,32 @@
context_.reader_registry->RegisterTraceReader<ZipTraceReader>(kZipFile);
}
- if (json::IsJsonSupported()) {
+ if constexpr (json::IsJsonSupported()) {
context_.reader_registry->RegisterTraceReader<JsonTraceTokenizer>(
kJsonTraceType);
context_.json_trace_parser =
std::make_unique<JsonTraceParserImpl>(&context_);
+
+ context_.reader_registry
+ ->RegisterTraceReader<gecko_importer::GeckoTraceTokenizer>(
+ kGeckoTraceType);
+ context_.gecko_trace_parser =
+ std::make_unique<gecko_importer::GeckoTraceParserImpl>(&context_);
}
+ context_.reader_registry->RegisterTraceReader<art_method::ArtMethodTokenizer>(
+ kArtMethodTraceType);
+ context_.art_method_parser =
+ std::make_unique<art_method::ArtMethodParserImpl>(&context_);
+
+ context_.reader_registry
+ ->RegisterTraceReader<perf_text_importer::PerfTextTraceTokenizer>(
+ kPerfTextTraceType);
+ context_.perf_text_parser =
+ std::make_unique<perf_text_importer::PerfTextTraceParserImpl>(&context_);
+
+ context_.reader_registry->RegisterTraceReader<TarTraceReader>(kTarTraceType);
+
if (context_.config.analyze_trace_proto_content) {
context_.content_analyzer =
std::make_unique<ProtoContentAnalyzer>(&context_);
@@ -455,8 +486,7 @@
RegisterAdditionalModules(&context_);
InitPerfettoSqlEngine();
- sqlite_objects_post_constructor_initialization_ =
- engine_->SqliteRegisteredObjectCount();
+ sqlite_objects_post_prelude_ = engine_->SqliteRegisteredObjectCount();
bool skip_all_sql = std::find(config_.skip_builtin_metric_paths.begin(),
config_.skip_builtin_metric_paths.end(),
@@ -522,6 +552,9 @@
GetTraceTimestampBoundsNs(*context_.storage));
TraceProcessorStorageImpl::DestroyContext();
+
+ IncludeAfterEofPrelude();
+ sqlite_objects_post_prelude_ = engine_->SqliteRegisteredObjectCount();
return base::OkStatus();
}
@@ -529,15 +562,13 @@
// We should always have at least as many objects now as we did in the
// constructor.
uint64_t registered_count_before = engine_->SqliteRegisteredObjectCount();
- PERFETTO_CHECK(registered_count_before >=
- sqlite_objects_post_constructor_initialization_);
+ PERFETTO_CHECK(registered_count_before >= sqlite_objects_post_prelude_);
InitPerfettoSqlEngine();
// The registered count should now be the same as it was in the constructor.
uint64_t registered_count_after = engine_->SqliteRegisteredObjectCount();
- PERFETTO_CHECK(registered_count_after ==
- sqlite_objects_post_constructor_initialization_);
+ PERFETTO_CHECK(registered_count_after == sqlite_objects_post_prelude_);
return static_cast<size_t>(registered_count_before - registered_count_after);
}
@@ -574,30 +605,30 @@
return field_idx != nullptr;
}
-base::Status TraceProcessorImpl::RegisterSqlModule(SqlModule sql_module) {
- sql_modules::RegisteredModule new_module;
- std::string name = sql_module.name;
- if (engine_->FindModule(name) && !sql_module.allow_module_override) {
+base::Status TraceProcessorImpl::RegisterSqlPackage(SqlPackage sql_package) {
+ sql_modules::RegisteredPackage new_package;
+ std::string name = sql_package.name;
+ if (engine_->FindPackage(name) && !sql_package.allow_override) {
return base::ErrStatus(
- "Module '%s' is already registered. Choose a different name.\n"
- "If you want to replace the existing module using trace processor "
+ "Package '%s' is already registered. Choose a different name.\n"
+ "If you want to replace the existing package using trace processor "
"shell, you need to pass the --dev flag and use "
"--override-sql-module "
"to pass the module path.",
name.c_str());
}
- for (auto const& name_and_sql : sql_module.files) {
- if (sql_modules::GetModuleName(name_and_sql.first) != name) {
+ for (auto const& module_name_and_sql : sql_package.modules) {
+ if (sql_modules::GetPackageName(module_name_and_sql.first) != name) {
return base::ErrStatus(
- "File import key doesn't match the module name. First part of "
- "import "
- "key should be module name. Import key: %s, module name: %s.",
- name_and_sql.first.c_str(), name.c_str());
+ "Module name doesn't match the package name. First part of module "
+ "name should be package name. Import key: '%s', package name: '%s'.",
+ module_name_and_sql.first.c_str(), name.c_str());
}
- new_module.include_key_to_file.Insert(name_and_sql.first,
- {name_and_sql.second, false});
+ new_package.modules.Insert(module_name_and_sql.first,
+ {module_name_and_sql.second, false});
}
- engine_->RegisterModule(name, std::move(new_module));
+ manually_registered_sql_packages_.push_back(SqlPackage(sql_package));
+ engine_->RegisterPackage(name, std::move(new_package));
return base::OkStatus();
}
@@ -845,11 +876,12 @@
"__intrinsic_slice_mipmap",
std::make_unique<SliceMipmapOperator::Context>(engine_.get()));
- // Register stdlib modules.
- auto stdlib_modules = GetStdlibModules();
- for (auto module_it = stdlib_modules.GetIterator(); module_it; ++module_it) {
+ // Register stdlib packages.
+ auto packages = GetStdlibPackages();
+ for (auto package = packages.GetIterator(); package; ++package) {
base::Status status =
- RegisterSqlModule({module_it.key(), module_it.value(), false});
+ RegisterSqlPackage({/*name=*/package.key(), /*modules=*/package.value(),
+ /*allow_override=*/false});
if (!status.ok())
PERFETTO_ELOG("%s", status.c_message());
}
@@ -904,8 +936,6 @@
RegisterStaticTable(storage->mutable_process_track_table());
RegisterStaticTable(storage->mutable_cpu_track_table());
RegisterStaticTable(storage->mutable_gpu_track_table());
- RegisterStaticTable(storage->mutable_uid_track_table());
- RegisterStaticTable(storage->mutable_gpu_work_period_track_table());
RegisterStaticTable(storage->mutable_counter_table());
@@ -913,15 +943,9 @@
RegisterStaticTable(storage->mutable_process_counter_track_table());
RegisterStaticTable(storage->mutable_thread_counter_track_table());
RegisterStaticTable(storage->mutable_cpu_counter_track_table());
- RegisterStaticTable(storage->mutable_irq_counter_track_table());
- RegisterStaticTable(storage->mutable_softirq_counter_track_table());
RegisterStaticTable(storage->mutable_gpu_counter_track_table());
RegisterStaticTable(storage->mutable_gpu_counter_group_table());
RegisterStaticTable(storage->mutable_perf_counter_track_table());
- RegisterStaticTable(storage->mutable_energy_counter_track_table());
- RegisterStaticTable(storage->mutable_linux_device_track_table());
- RegisterStaticTable(storage->mutable_uid_counter_track_table());
- RegisterStaticTable(storage->mutable_energy_per_uid_counter_track_table());
RegisterStaticTable(storage->mutable_heap_graph_object_table());
RegisterStaticTable(storage->mutable_heap_graph_reference_table());
@@ -1038,6 +1062,9 @@
std::make_unique<ExperimentalFlatSlice>(&context_));
engine_->RegisterStaticTableFunction(std::make_unique<DfsWeightBounded>(
context_.storage->mutable_string_pool()));
+ engine_->RegisterStaticTableFunction(
+ std::make_unique<WinscopeProtoToArgsWithDefaults>(
+ context_.storage->mutable_string_pool(), engine_.get(), &context_));
// Value table aggregate functions.
engine_->RegisterSqliteAggregateFunction<DominatorTree>(
@@ -1054,14 +1081,10 @@
}
}
- // Import prelude module.
- {
- auto result = engine_->Execute(SqlSource::FromTraceProcessorImplementation(
- "INCLUDE PERFETTO MODULE prelude.*"));
- if (!result.status().ok()) {
- PERFETTO_FATAL("Failed to import prelude: %s",
- result.status().c_message());
- }
+ // Import prelude package.
+ IncludeBeforeEofPrelude();
+ if (notify_eof_called_) {
+ IncludeAfterEofPrelude();
}
for (const auto& metric : sql_metrics_) {
@@ -1072,6 +1095,27 @@
// Fill trace bounds table.
BuildBoundsTable(db, GetTraceTimestampBoundsNs(*context_.storage));
+
+ // Reregister manually added stdlib packages.
+ for (const auto& package : manually_registered_sql_packages_) {
+ RegisterSqlPackage(package);
+ }
+}
+
+void TraceProcessorImpl::IncludeBeforeEofPrelude() {
+ auto result = engine_->Execute(SqlSource::FromTraceProcessorImplementation(
+ "INCLUDE PERFETTO MODULE prelude.before_eof.*"));
+ if (!result.status().ok()) {
+ PERFETTO_FATAL("Failed to import prelude: %s", result.status().c_message());
+ }
+}
+
+void TraceProcessorImpl::IncludeAfterEofPrelude() {
+ auto result = engine_->Execute(SqlSource::FromTraceProcessorImplementation(
+ "INCLUDE PERFETTO MODULE prelude.after_eof.*"));
+ if (!result.status().ok()) {
+ PERFETTO_FATAL("Failed to import prelude: %s", result.status().c_message());
+ }
}
namespace {
@@ -1112,54 +1156,42 @@
std::vector<uint8_t>* trace_proto) {
protozero::HeapBuffered<protos::pbzero::Trace> trace;
- {
- uint64_t realtime_timestamp = static_cast<uint64_t>(
- std::chrono::system_clock::now().time_since_epoch() /
- std::chrono::nanoseconds(1));
- uint64_t boottime_timestamp = metatrace::TraceTimeNowNs();
- auto* clock_snapshot = trace->add_packet()->set_clock_snapshot();
- {
- auto* realtime_clock = clock_snapshot->add_clocks();
- realtime_clock->set_clock_id(
- protos::pbzero::BuiltinClock::BUILTIN_CLOCK_REALTIME);
- realtime_clock->set_timestamp(realtime_timestamp);
- }
- {
- auto* boottime_clock = clock_snapshot->add_clocks();
- boottime_clock->set_clock_id(
- protos::pbzero::BuiltinClock::BUILTIN_CLOCK_BOOTTIME);
- boottime_clock->set_timestamp(boottime_timestamp);
- }
+ auto* clock_snapshot = trace->add_packet()->set_clock_snapshot();
+ for (const auto& [clock_id, ts] : base::CaptureClockSnapshots()) {
+ auto* clock = clock_snapshot->add_clocks();
+ clock->set_clock_id(clock_id);
+ clock->set_timestamp(ts);
}
+ auto tid = static_cast<uint32_t>(base::GetThreadId());
base::FlatHashMap<std::string, uint64_t> interned_strings;
- metatrace::DisableAndReadBuffer([&trace, &interned_strings](
- metatrace::Record* record) {
- auto* packet = trace->add_packet();
- packet->set_timestamp(record->timestamp_ns);
- auto* evt = packet->set_perfetto_metatrace();
+ metatrace::DisableAndReadBuffer(
+ [&trace, &interned_strings, tid](metatrace::Record* record) {
+ auto* packet = trace->add_packet();
+ packet->set_timestamp(record->timestamp_ns);
+ auto* evt = packet->set_perfetto_metatrace();
- StringInterner interner(*evt, interned_strings);
+ StringInterner interner(*evt, interned_strings);
- evt->set_event_name_iid(interner.InternString(record->event_name));
- evt->set_event_duration_ns(record->duration_ns);
- evt->set_thread_id(1); // Not really important, just required for the ui.
+ evt->set_event_name_iid(interner.InternString(record->event_name));
+ evt->set_event_duration_ns(record->duration_ns);
+ evt->set_thread_id(tid);
- if (record->args_buffer_size == 0)
- return;
+ if (record->args_buffer_size == 0)
+ return;
- base::StringSplitter s(
- record->args_buffer, record->args_buffer_size, '\0',
- base::StringSplitter::EmptyTokenMode::ALLOW_EMPTY_TOKENS);
- for (; s.Next();) {
- auto* arg_proto = evt->add_args();
- arg_proto->set_key_iid(interner.InternString(s.cur_token()));
+ base::StringSplitter s(
+ record->args_buffer, record->args_buffer_size, '\0',
+ base::StringSplitter::EmptyTokenMode::ALLOW_EMPTY_TOKENS);
+ for (; s.Next();) {
+ auto* arg_proto = evt->add_args();
+ arg_proto->set_key_iid(interner.InternString(s.cur_token()));
- bool has_next = s.Next();
- PERFETTO_CHECK(has_next);
- arg_proto->set_value_iid(interner.InternString(s.cur_token()));
- }
- });
+ bool has_next = s.Next();
+ PERFETTO_CHECK(has_next);
+ arg_proto->set_value_iid(interner.InternString(s.cur_token()));
+ }
+ });
*trace_proto = trace.SerializeAsArray();
return base::OkStatus();
}
diff --git a/src/trace_processor/trace_processor_impl.h b/src/trace_processor/trace_processor_impl.h
index 68fcafa..3c86da0 100644
--- a/src/trace_processor/trace_processor_impl.h
+++ b/src/trace_processor/trace_processor_impl.h
@@ -67,7 +67,7 @@
base::Status RegisterMetric(const std::string& path,
const std::string& sql) override;
- base::Status RegisterSqlModule(SqlModule sql_module) override;
+ base::Status RegisterSqlPackage(SqlPackage) override;
base::Status ExtendMetricsProto(const uint8_t* data, size_t size) override;
@@ -97,6 +97,15 @@
base::Status DisableAndReadMetatrace(
std::vector<uint8_t>* trace_proto) override;
+ base::Status RegisterSqlModule(SqlModule module) override {
+ SqlPackage package;
+ package.name = std::move(module.name);
+ package.modules = std::move(module.files);
+ package.allow_override = module.allow_module_override;
+
+ return RegisterSqlPackage(package);
+ }
+
private:
// Needed for iterators to be able to access the context.
friend class IteratorImpl;
@@ -110,6 +119,8 @@
bool IsRootMetricField(const std::string& metric_name);
void InitPerfettoSqlEngine();
+ void IncludeBeforeEofPrelude();
+ void IncludeAfterEofPrelude();
const Config config_;
std::unique_ptr<PerfettoSqlEngine> engine_;
@@ -117,6 +128,11 @@
DescriptorPool pool_;
std::vector<metrics::SqlMetricFile> sql_metrics_;
+
+ // Manually registeres SQL packages are stored here, to be able to restore
+ // them when running |RestoreInitialTables()|.
+ std::vector<SqlPackage> manually_registered_sql_packages_;
+
std::unordered_map<std::string, std::string> proto_field_to_sql_metric_path_;
std::unordered_map<std::string, std::string> proto_fn_name_to_path_;
@@ -124,8 +140,8 @@
// to prevent single-flow compiler optimizations in ExecuteQuery().
std::atomic<bool> query_interrupted_{false};
- // Track the number of objects registered with SQLite after the constructor.
- uint64_t sqlite_objects_post_constructor_initialization_ = 0;
+ // Track the number of objects registered with SQLite post prelude.
+ uint64_t sqlite_objects_post_prelude_ = 0;
std::string current_trace_name_;
uint64_t bytes_parsed_ = 0;
diff --git a/src/trace_processor/trace_processor_shell.cc b/src/trace_processor/trace_processor_shell.cc
index ea60770..dac39a9 100644
--- a/src/trace_processor/trace_processor_shell.cc
+++ b/src/trace_processor/trace_processor_shell.cc
@@ -14,22 +14,29 @@
* limitations under the License.
*/
-#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/stat.h>
+#include <algorithm>
#include <cctype>
+#include <cerrno>
+#include <chrono>
#include <cinttypes>
-#include <functional>
-#include <iostream>
+#include <cstdint>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <memory>
#include <optional>
#include <string>
-#include <unordered_map>
#include <unordered_set>
+#include <utility>
#include <vector>
#include <google/protobuf/compiler/parser.h>
+#include <google/protobuf/descriptor.pb.h>
#include <google/protobuf/dynamic_message.h>
+#include <google/protobuf/io/tokenizer.h>
#include <google/protobuf/io/zero_copy_stream_impl.h>
#include <google/protobuf/text_format.h>
@@ -38,23 +45,25 @@
#include "perfetto/base/status.h"
#include "perfetto/base/time.h"
#include "perfetto/ext/base/file_utils.h"
-#include "perfetto/ext/base/flat_hash_map.h"
-#include "perfetto/ext/base/getopt.h"
+#include "perfetto/ext/base/getopt.h" // IWYU pragma: keep
#include "perfetto/ext/base/scoped_file.h"
#include "perfetto/ext/base/status_or.h"
#include "perfetto/ext/base/string_splitter.h"
#include "perfetto/ext/base/string_utils.h"
#include "perfetto/ext/base/version.h"
-
+#include "perfetto/trace_processor/basic_types.h"
+#include "perfetto/trace_processor/iterator.h"
#include "perfetto/trace_processor/metatrace_config.h"
#include "perfetto/trace_processor/read_trace.h"
#include "perfetto/trace_processor/trace_processor.h"
+#include "src/profiling/deobfuscator.h"
+#include "src/profiling/symbolizer/local_symbolizer.h"
+#include "src/profiling/symbolizer/symbolize_database.h"
#include "src/trace_processor/metrics/all_chrome_metrics.descriptor.h"
#include "src/trace_processor/metrics/all_webview_metrics.descriptor.h"
#include "src/trace_processor/metrics/metrics.descriptor.h"
#include "src/trace_processor/read_trace_internal.h"
#include "src/trace_processor/rpc/stdiod.h"
-#include "src/trace_processor/sqlite/sqlite_utils.h"
#include "src/trace_processor/util/sql_modules.h"
#include "src/trace_processor/util/status_macros.h"
@@ -63,10 +72,6 @@
#if PERFETTO_BUILDFLAG(PERFETTO_TP_HTTPD)
#include "src/trace_processor/rpc/httpd.h"
#endif
-#include "src/profiling/deobfuscator.h"
-#include "src/profiling/symbolizer/local_symbolizer.h"
-#include "src/profiling/symbolizer/symbolize_database.h"
-#include "src/profiling/symbolizer/symbolizer.h"
#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
@@ -98,8 +103,7 @@
#include <unistd.h> // For getuid() in GetConfigPath().
#endif
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
namespace {
TraceProcessor* g_tp;
@@ -1151,10 +1155,9 @@
base::Status RunQueriesFromFile(const std::string& query_file_path,
bool expect_output) {
std::string queries;
- if (!base::ReadFile(query_file_path.c_str(), &queries)) {
+ if (!base::ReadFile(query_file_path, &queries)) {
return base::ErrStatus("Unable to read file %s", query_file_path.c_str());
}
-
return RunQueries(queries, expect_output);
}
@@ -1232,7 +1235,7 @@
// Get module name
size_t last_slash = root.rfind('/');
if ((last_slash == std::string::npos) ||
- (root.find(".") != std::string::npos))
+ (root.find('.') != std::string::npos))
return base::ErrStatus("Module path must point to the directory: %s",
root.c_str());
@@ -1240,7 +1243,7 @@
std::vector<std::string> paths;
RETURN_IF_ERROR(base::ListFilesRecursive(root, paths));
- sql_modules::NameToModule modules;
+ sql_modules::NameToPackage modules;
for (const auto& path : paths) {
if (base::GetFileExtension(path) != ".sql")
continue;
@@ -1256,8 +1259,9 @@
.first->push_back({import_key, file_contents});
}
for (auto module_it = modules.GetIterator(); module_it; ++module_it) {
- auto status = g_tp->RegisterSqlModule(
- {module_it.key(), module_it.value(), allow_override});
+ auto status = g_tp->RegisterSqlPackage({/*name=*/module_it.key(),
+ /*files=*/module_it.value(),
+ /*allow_override=*/allow_override});
if (!status.ok())
return status;
}
@@ -1272,27 +1276,30 @@
}
if (!base::FileExists(root)) {
- return base::ErrStatus("Directory %s does not exist.", root.c_str());
+ return base::ErrStatus("Directory '%s' does not exist.", root.c_str());
}
std::vector<std::string> paths;
RETURN_IF_ERROR(base::ListFilesRecursive(root, paths));
- sql_modules::NameToModule modules;
+ sql_modules::NameToPackage packages;
for (const auto& path : paths) {
if (base::GetFileExtension(path) != ".sql") {
continue;
}
std::string filename = root + "/" + path;
- std::string file_contents;
- if (!base::ReadFile(filename, &file_contents)) {
- return base::ErrStatus("Cannot read file %s", filename.c_str());
+ std::string module_file;
+ if (!base::ReadFile(filename, &module_file)) {
+ return base::ErrStatus("Cannot read file '%s'", filename.c_str());
}
- std::string import_key = sql_modules::GetIncludeKey(path);
- std::string module = sql_modules::GetModuleName(import_key);
- modules.Insert(module, {}).first->push_back({import_key, file_contents});
+ std::string module_name = sql_modules::GetIncludeKey(path);
+ std::string package_name = sql_modules::GetPackageName(module_name);
+ packages.Insert(package_name, {})
+ .first->push_back({module_name, module_file});
}
- for (auto module_it = modules.GetIterator(); module_it; ++module_it) {
- g_tp->RegisterSqlModule({module_it.key(), module_it.value(), true});
+ for (auto package = packages.GetIterator(); package; ++package) {
+ g_tp->RegisterSqlPackage({/*name=*/package.key(),
+ /*files=*/package.value(),
+ /*allow_override=*/true});
}
return base::OkStatus();
@@ -1513,7 +1520,8 @@
sscanf(line.get() + 1, "%31s %1023s", command, arg);
if (strcmp(command, "quit") == 0 || strcmp(command, "q") == 0) {
break;
- } else if (strcmp(command, "help") == 0) {
+ }
+ if (strcmp(command, "help") == 0) {
PrintShellUsage();
} else if (strcmp(command, "dump") == 0 && strlen(arg)) {
if (!ExportTraceToDatabase(arg).ok())
@@ -1784,8 +1792,7 @@
} // namespace
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
int main(int argc, char** argv) {
auto status = perfetto::trace_processor::TraceProcessorMain(argc, argv);
diff --git a/src/trace_processor/trace_processor_storage_impl.cc b/src/trace_processor/trace_processor_storage_impl.cc
index 8325d87..d409320 100644
--- a/src/trace_processor/trace_processor_storage_impl.cc
+++ b/src/trace_processor/trace_processor_storage_impl.cc
@@ -20,6 +20,7 @@
#include <cstddef>
#include <cstdint>
#include <memory>
+#include <optional>
#include <utility>
#include "perfetto/base/logging.h"
@@ -45,11 +46,13 @@
#include "src/trace_processor/importers/proto/proto_importer_module.h"
#include "src/trace_processor/importers/proto/proto_trace_parser_impl.h"
#include "src/trace_processor/importers/proto/proto_trace_reader.h"
+#include "src/trace_processor/sorter/trace_sorter.h"
#include "src/trace_processor/storage/metadata.h"
#include "src/trace_processor/storage/stats.h"
#include "src/trace_processor/storage/trace_storage.h"
#include "src/trace_processor/trace_reader_registry.h"
#include "src/trace_processor/types/variadic.h"
+#include "src/trace_processor/util/descriptors.h"
#include "src/trace_processor/util/status_macros.h"
#include "src/trace_processor/util/trace_type.h"
@@ -75,8 +78,8 @@
return base::ErrStatus(
"Failed unrecoverably while parsing in a previous Parse call");
if (!parser_) {
- active_file_ = context_.trace_file_tracker->StartNewFile();
- auto parser = std::make_unique<ForwardingTraceParser>(&context_);
+ auto parser = std::make_unique<ForwardingTraceParser>(
+ &context_, context_.trace_file_tracker->AddFile());
parser_ = parser.get();
context_.chunk_readers.push_back(std::move(parser));
}
@@ -96,7 +99,6 @@
Variadic::String(id_for_uuid));
}
- active_file_->AddSize(blob.size());
base::Status status = parser_->Parse(std::move(blob));
unrecoverable_parse_error_ |= !status.ok();
return status;
@@ -120,9 +122,6 @@
}
Flush();
RETURN_IF_ERROR(parser_->NotifyEndOfFile());
- PERFETTO_CHECK(active_file_.has_value());
- active_file_->SetTraceType(parser_->trace_type());
- active_file_.reset();
// NotifyEndOfFile might have pushed packets to the sorter.
Flush();
for (std::unique_ptr<ProtoImporterModule>& module : context_.modules) {
@@ -143,8 +142,6 @@
}
void TraceProcessorStorageImpl::DestroyContext() {
- // End any active files. Eg. when NotifyEndOfFile is not called.
- active_file_.reset();
TraceProcessorContext context;
context.storage = std::move(context_.storage);
@@ -156,6 +153,9 @@
// kernel version (inside system_info_tracker) to know how to textualise
// sched_switch.prev_state bitflags.
context.system_info_tracker = std::move(context_.system_info_tracker);
+ // "__intrinsic_winscope_proto_to_args_with_defaults" requires proto
+ // descriptors.
+ context.descriptor_pool_ = std::move(context_.descriptor_pool_);
context_ = std::move(context);
diff --git a/src/trace_processor/trace_processor_storage_impl.h b/src/trace_processor/trace_processor_storage_impl.h
index bab8961..9198d2f 100644
--- a/src/trace_processor/trace_processor_storage_impl.h
+++ b/src/trace_processor/trace_processor_storage_impl.h
@@ -17,9 +17,6 @@
#ifndef SRC_TRACE_PROCESSOR_TRACE_PROCESSOR_STORAGE_IMPL_H_
#define SRC_TRACE_PROCESSOR_TRACE_PROCESSOR_STORAGE_IMPL_H_
-#include <memory>
-#include <optional>
-
#include "perfetto/ext/base/hash.h"
#include "perfetto/trace_processor/basic_types.h"
#include "perfetto/trace_processor/status.h"
@@ -51,7 +48,6 @@
bool unrecoverable_parse_error_ = false;
size_t hash_input_size_remaining_ = 4096;
ForwardingTraceParser* parser_ = nullptr;
- std::optional<ScopedActiveTraceFile> active_file_;
};
} // namespace trace_processor
diff --git a/src/trace_processor/trace_reader_registry.cc b/src/trace_processor/trace_reader_registry.cc
index dcecaf9..fe108c8 100644
--- a/src/trace_processor/trace_reader_registry.cc
+++ b/src/trace_processor/trace_reader_registry.cc
@@ -15,7 +15,13 @@
*/
#include "src/trace_processor/trace_reader_registry.h"
+
+#include <memory>
+#include <utility>
+
#include "perfetto/base/logging.h"
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/status_or.h"
#include "src/trace_processor/importers/common/chunked_trace_reader.h"
#include "src/trace_processor/types/trace_processor_context.h"
#include "src/trace_processor/util/gzip_utils.h"
@@ -45,6 +51,10 @@
case kSymbolsTraceType:
case kAndroidLogcatTraceType:
case kAndroidDumpstateTraceType:
+ case kGeckoTraceType:
+ case kArtMethodTraceType:
+ case kPerfTextTraceType:
+ case kTarTraceType:
return false;
}
PERFETTO_FATAL("For GCC");
@@ -58,7 +68,7 @@
base::StatusOr<std::unique_ptr<ChunkedTraceReader>>
TraceReaderRegistry::CreateTraceReader(TraceType type) {
- if (auto it = factories_.Find(type); it) {
+ if (auto* it = factories_.Find(type); it) {
return (*it)(context_);
}
diff --git a/src/trace_processor/trace_reader_registry.h b/src/trace_processor/trace_reader_registry.h
index 8e9b039..3d140bf 100644
--- a/src/trace_processor/trace_reader_registry.h
+++ b/src/trace_processor/trace_reader_registry.h
@@ -19,13 +19,9 @@
#include <functional>
#include <memory>
-#include <optional>
#include "perfetto/ext/base/flat_hash_map.h"
-
#include "perfetto/ext/base/status_or.h"
-#include "src/trace_processor/importers/common/trace_parser.h"
-#include "src/trace_processor/sorter/trace_sorter.h"
#include "src/trace_processor/util/trace_type.h"
namespace perfetto {
diff --git a/src/trace_processor/types/trace_processor_context.h b/src/trace_processor/types/trace_processor_context.h
index 9069ef6..3d8ef29 100644
--- a/src/trace_processor/types/trace_processor_context.h
+++ b/src/trace_processor/types/trace_processor_context.h
@@ -31,6 +31,7 @@
class AndroidLogEventParser;
class ArgsTracker;
class ArgsTranslationTable;
+class ArtMethodParser;
class AsyncTrackSetTracker;
class ChunkedTraceReader;
class ClockConverter;
@@ -44,6 +45,7 @@
class ForwardingTraceParser;
class FtraceModule;
class FuchsiaRecordParser;
+class GeckoTraceParser;
class GlobalArgsTracker;
class HeapGraphTracker;
class InstrumentsRowParser;
@@ -56,6 +58,7 @@
class PacketAnalyzer;
class PerfRecordParser;
class PerfSampleTracker;
+class PerfTextTraceParser;
class ProcessTracker;
class ProcessTrackTranslationTable;
class ProtoImporterModule;
@@ -173,6 +176,9 @@
std::unique_ptr<SpeRecordParser> spe_record_parser;
std::unique_ptr<InstrumentsRowParser> instruments_row_parser;
std::unique_ptr<AndroidLogEventParser> android_log_event_parser;
+ std::unique_ptr<GeckoTraceParser> gecko_trace_parser;
+ std::unique_ptr<ArtMethodParser> art_method_parser;
+ std::unique_ptr<PerfTextTraceParser> perf_text_parser;
// This field contains the list of proto descriptors that can be used by
// reflection-based parsers.
diff --git a/src/trace_processor/util/BUILD.gn b/src/trace_processor/util/BUILD.gn
index b857bd0..73bdbcb 100644
--- a/src/trace_processor/util/BUILD.gn
+++ b/src/trace_processor/util/BUILD.gn
@@ -278,7 +278,18 @@
"../../../gn:default_deps",
"../../../include/perfetto/ext/base",
"../../../protos/perfetto/trace:non_minimal_zero",
+ "../../protozero",
"../importers/android_bugreport:android_log_event",
+ "../importers/perf_text:perf_text_sample_line_parser",
+ ]
+}
+
+source_set("winscope_proto_mapping") {
+ sources = ["winscope_proto_mapping.h"]
+ deps = [
+ "../../../gn:default_deps",
+ "../../../include/perfetto/ext/base:base",
+ "../tables",
]
}
diff --git a/src/trace_processor/util/bump_allocator.h b/src/trace_processor/util/bump_allocator.h
index 985b5bc..9e92471 100644
--- a/src/trace_processor/util/bump_allocator.h
+++ b/src/trace_processor/util/bump_allocator.h
@@ -54,7 +54,7 @@
public:
// The limit on the total number of bits which can be used to represent
// the chunk id.
- static constexpr uint64_t kMaxIdBits = 60;
+ static constexpr uint64_t kMaxIdBits = 58;
// The limit on the total amount of memory which can be allocated.
static constexpr uint64_t kAllocLimit = 1ull << kMaxIdBits;
diff --git a/src/trace_processor/util/descriptors.cc b/src/trace_processor/util/descriptors.cc
index 96833ed..8a1d35f 100644
--- a/src/trace_processor/util/descriptors.cc
+++ b/src/trace_processor/util/descriptors.cc
@@ -48,13 +48,17 @@
? static_cast<uint32_t>(f_decoder.type())
: static_cast<uint32_t>(FieldDescriptorProto::TYPE_MESSAGE);
protos::pbzero::FieldOptions::Decoder opt(f_decoder.options());
+ std::optional<std::string> default_value;
+ if (f_decoder.has_default_value()) {
+ default_value = f_decoder.default_value().ToStdString();
+ }
return FieldDescriptor(
base::StringView(f_decoder.name()).ToStdString(),
static_cast<uint32_t>(f_decoder.number()), type, std::move(type_name),
std::vector<uint8_t>(f_decoder.options().data,
f_decoder.options().data + f_decoder.options().size),
- f_decoder.label() == FieldDescriptorProto::LABEL_REPEATED, opt.packed(),
- is_extension);
+ default_value, f_decoder.label() == FieldDescriptorProto::LABEL_REPEATED,
+ opt.packed(), is_extension);
}
base::Status CheckExtensionField(const ProtoDescriptor& proto_descriptor,
@@ -443,6 +447,7 @@
uint32_t type,
std::string raw_type_name,
std::vector<uint8_t> options,
+ std::optional<std::string> default_value,
bool is_repeated,
bool is_packed,
bool is_extension)
@@ -451,6 +456,7 @@
type_(type),
raw_type_name_(std::move(raw_type_name)),
options_(std::move(options)),
+ default_value_(std::move(default_value)),
is_repeated_(is_repeated),
is_packed_(is_packed),
is_extension_(is_extension) {}
diff --git a/src/trace_processor/util/descriptors.h b/src/trace_processor/util/descriptors.h
index c99a72b..075455c 100644
--- a/src/trace_processor/util/descriptors.h
+++ b/src/trace_processor/util/descriptors.h
@@ -41,7 +41,8 @@
uint32_t number,
uint32_t type,
std::string raw_type_name,
- std::vector<uint8_t>,
+ std::vector<uint8_t> options,
+ std::optional<std::string> default_value,
bool is_repeated,
bool is_packed,
bool is_extension = false);
@@ -57,6 +58,9 @@
const std::vector<uint8_t>& options() const { return options_; }
std::vector<uint8_t>* mutable_options() { return &options_; }
+ const std::optional<std::string>& default_value() const {
+ return default_value_;
+ }
void set_resolved_type_name(const std::string& resolved_type_name) {
resolved_type_name_ = resolved_type_name;
@@ -69,6 +73,7 @@
std::string raw_type_name_;
std::string resolved_type_name_;
std::vector<uint8_t> options_;
+ std::optional<std::string> default_value_;
bool is_repeated_;
bool is_packed_;
bool is_extension_;
diff --git a/src/trace_processor/util/gzip_utils.cc b/src/trace_processor/util/gzip_utils.cc
index 3d76c33..50ea9db 100644
--- a/src/trace_processor/util/gzip_utils.cc
+++ b/src/trace_processor/util/gzip_utils.cc
@@ -32,14 +32,6 @@
namespace perfetto::trace_processor::util {
-bool IsGzipSupported() {
-#if PERFETTO_BUILDFLAG(PERFETTO_ZLIB)
- return true;
-#else
- return false;
-#endif
-}
-
#if PERFETTO_BUILDFLAG(PERFETTO_ZLIB) // Real Implementation
GzipDecompressor::GzipDecompressor(InputMode mode)
diff --git a/src/trace_processor/util/gzip_utils.h b/src/trace_processor/util/gzip_utils.h
index c9c9c32..8719bd8 100644
--- a/src/trace_processor/util/gzip_utils.h
+++ b/src/trace_processor/util/gzip_utils.h
@@ -22,15 +22,21 @@
#include <memory>
#include <vector>
+#include "perfetto/base/build_config.h"
+
struct z_stream_s;
-namespace perfetto {
-namespace trace_processor {
-namespace util {
+namespace perfetto::trace_processor::util {
// Returns whether gzip related functioanlity is supported with the current
// build flags.
-bool IsGzipSupported();
+constexpr bool IsGzipSupported() {
+#if PERFETTO_BUILDFLAG(PERFETTO_ZLIB)
+ return true;
+#else
+ return false;
+#endif
+}
// Usage: To decompress in a streaming way, there are two ways of using it:
// 1. [Commonly used] - Feed the sequence of mem-blocks in 'FeedAndExtract' one
@@ -130,8 +136,6 @@
std::unique_ptr<z_stream_s, Deleter> z_stream_;
};
-} // namespace util
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor::util
#endif // SRC_TRACE_PROCESSOR_UTIL_GZIP_UTILS_H_
diff --git a/src/trace_processor/util/proto_to_args_parser.cc b/src/trace_processor/util/proto_to_args_parser.cc
index e09359c..da2f15c 100644
--- a/src/trace_processor/util/proto_to_args_parser.cc
+++ b/src/trace_processor/util/proto_to_args_parser.cc
@@ -16,9 +16,11 @@
#include "src/trace_processor/util/proto_to_args_parser.h"
-#include <stdint.h>
+#include <unordered_set>
#include "perfetto/base/status.h"
+#include "perfetto/ext/base/string_utils.h"
+#include "perfetto/protozero/field.h"
#include "perfetto/protozero/proto_decoder.h"
#include "perfetto/protozero/proto_utils.h"
#include "protos/perfetto/common/descriptor.pbzero.h"
@@ -40,6 +42,15 @@
target += value;
}
+bool IsFieldAllowed(const FieldDescriptor& field,
+ const std::vector<uint32_t>* allowed_fields) {
+ // If allowlist is not provided, reflect all fields. Otherwise, check if the
+ // current field either an extension or is in allowlist.
+ return field.is_extension() || !allowed_fields ||
+ std::find(allowed_fields->begin(), allowed_fields->end(),
+ field.number()) != allowed_fields->end();
+}
+
} // namespace
ProtoToArgsParser::Key::Key() = default;
@@ -88,10 +99,11 @@
const std::string& type,
const std::vector<uint32_t>* allowed_fields,
Delegate& delegate,
- int* unknown_extensions) {
+ int* unknown_extensions,
+ bool add_defaults) {
ScopedNestedKeyContext key_context(key_prefix_);
return ParseMessageInternal(key_context, cb, type, allowed_fields, delegate,
- unknown_extensions);
+ unknown_extensions, add_defaults);
}
base::Status ProtoToArgsParser::ParseMessageInternal(
@@ -100,7 +112,8 @@
const std::string& type,
const std::vector<uint32_t>* allowed_fields,
Delegate& delegate,
- int* unknown_extensions) {
+ int* unknown_extensions,
+ bool add_defaults) {
if (auto override_result =
MaybeApplyOverrideForType(type, key_context, cb, delegate)) {
return override_result.value();
@@ -116,6 +129,7 @@
std::unordered_map<size_t, int> repeated_field_index;
bool empty_message = true;
protozero::ProtoDecoder decoder(cb);
+ std::unordered_set<std::string_view> existing_fields;
for (protozero::Field f = decoder.ReadField(); f.valid();
f = decoder.ReadField()) {
empty_message = false;
@@ -128,13 +142,11 @@
continue;
}
- // If allowlist is not provided, reflect all fields. Otherwise, check if the
- // current field either an extension or is in allowlist.
- bool is_allowed = field->is_extension() || !allowed_fields ||
- std::find(allowed_fields->begin(), allowed_fields->end(),
- f.id()) != allowed_fields->end();
+ if (add_defaults) {
+ existing_fields.insert(field->name());
+ }
- if (!is_allowed) {
+ if (!IsFieldAllowed(*field, allowed_fields)) {
// Field is neither an extension, nor is allowed to be
// reflected.
continue;
@@ -143,12 +155,13 @@
// Packed fields need to be handled specially because
if (field->is_packed()) {
RETURN_IF_ERROR(ParsePackedField(*field, repeated_field_index, f,
- delegate, unknown_extensions));
+ delegate, unknown_extensions,
+ add_defaults));
continue;
}
RETURN_IF_ERROR(ParseField(*field, repeated_field_index[f.id()], f,
- delegate, unknown_extensions));
+ delegate, unknown_extensions, add_defaults));
if (field->is_repeated()) {
repeated_field_index[f.id()]++;
}
@@ -156,6 +169,22 @@
if (empty_message) {
delegate.AddNull(key_prefix_);
+ } else if (add_defaults) {
+ for (const auto& [id, field] : descriptor.fields()) {
+ if (!IsFieldAllowed(field, allowed_fields)) {
+ continue;
+ }
+ const std::string& field_name = field.name();
+ bool field_exists =
+ existing_fields.find(field_name) != existing_fields.cend();
+ if (field_exists) {
+ continue;
+ }
+ ScopedNestedKeyContext key_context_default(key_prefix_);
+ AppendProtoType(key_prefix_.flat_key, field_name);
+ AppendProtoType(key_prefix_.key, field_name);
+ RETURN_IF_ERROR(AddDefault(field, delegate));
+ }
}
return base::OkStatus();
@@ -166,7 +195,8 @@
int repeated_field_number,
protozero::Field field,
Delegate& delegate,
- int* unknown_extensions) {
+ int* unknown_extensions,
+ bool add_defaults) {
std::string prefix_part = field_descriptor.name();
if (field_descriptor.is_repeated()) {
std::string number = std::to_string(repeated_field_number);
@@ -197,7 +227,7 @@
protos::pbzero::FieldDescriptorProto::TYPE_MESSAGE) {
return ParseMessageInternal(key_context, field.as_bytes(),
field_descriptor.resolved_type_name(), nullptr,
- delegate, unknown_extensions);
+ delegate, unknown_extensions, add_defaults);
}
return ParseSimpleField(field_descriptor, field, delegate);
}
@@ -207,7 +237,8 @@
std::unordered_map<size_t, int>& repeated_field_index,
protozero::Field field,
Delegate& delegate,
- int* unknown_extensions) {
+ int* unknown_extensions,
+ bool add_defaults) {
using FieldDescriptorProto = protos::pbzero::FieldDescriptorProto;
using PWT = protozero::proto_utils::ProtoWireType;
@@ -225,7 +256,7 @@
protozero::Field f;
f.initialize(field.id(), static_cast<uint8_t>(wire_type), new_value, 0);
return ParseField(field_descriptor, repeated_field_index[field.id()]++, f,
- delegate, unknown_extensions);
+ delegate, unknown_extensions, add_defaults);
};
const uint8_t* data = field.as_bytes().data;
@@ -335,30 +366,12 @@
case FieldDescriptorProto::TYPE_STRING:
delegate.AddString(key_prefix_, field.as_string());
return base::OkStatus();
- case FieldDescriptorProto::TYPE_ENUM: {
- auto opt_enum_descriptor_idx =
- pool_.FindDescriptorIdx(descriptor.resolved_type_name());
- if (!opt_enum_descriptor_idx) {
- delegate.AddInteger(key_prefix_, field.as_int32());
- return base::OkStatus();
- }
- auto opt_enum_string =
- pool_.descriptors()[*opt_enum_descriptor_idx].FindEnumString(
- field.as_int32());
- if (!opt_enum_string) {
- // Fall back to the integer representation of the field.
- delegate.AddInteger(key_prefix_, field.as_int32());
- return base::OkStatus();
- }
- delegate.AddString(key_prefix_,
- protozero::ConstChars{opt_enum_string->data(),
- opt_enum_string->size()});
- return base::OkStatus();
- }
+ case FieldDescriptorProto::TYPE_ENUM:
+ return AddEnum(descriptor, field.as_int32(), delegate);
default:
return base::ErrStatus(
"Tried to write value of type field %s (in proto type "
- "%s) which has type enum %d",
+ "%s) which has type enum %u",
descriptor.name().c_str(), descriptor.resolved_type_name().c_str(),
descriptor.type());
}
@@ -379,6 +392,108 @@
return context;
}
+base::Status ProtoToArgsParser::AddDefault(const FieldDescriptor& descriptor,
+ Delegate& delegate) {
+ using FieldDescriptorProto = protos::pbzero::FieldDescriptorProto;
+ if (descriptor.is_repeated()) {
+ delegate.AddNull(key_prefix_);
+ return base::OkStatus();
+ }
+ const auto& default_value = descriptor.default_value();
+ const auto& default_value_if_number =
+ default_value ? default_value.value() : "0";
+ switch (descriptor.type()) {
+ case FieldDescriptorProto::TYPE_INT32:
+ case FieldDescriptorProto::TYPE_SFIXED32:
+ delegate.AddInteger(key_prefix_,
+ base::StringToInt32(default_value_if_number).value());
+ return base::OkStatus();
+ case FieldDescriptorProto::TYPE_SINT32:
+ delegate.AddInteger(
+ key_prefix_,
+ protozero::proto_utils::ZigZagDecode(
+ base::StringToInt64(default_value_if_number).value()));
+ return base::OkStatus();
+ case FieldDescriptorProto::TYPE_INT64:
+ case FieldDescriptorProto::TYPE_SFIXED64:
+ delegate.AddInteger(key_prefix_,
+ base::StringToInt64(default_value_if_number).value());
+ return base::OkStatus();
+ case FieldDescriptorProto::TYPE_SINT64:
+ delegate.AddInteger(
+ key_prefix_,
+ protozero::proto_utils::ZigZagDecode(
+ base::StringToInt64(default_value_if_number).value()));
+ return base::OkStatus();
+ case FieldDescriptorProto::TYPE_UINT32:
+ case FieldDescriptorProto::TYPE_FIXED32:
+ delegate.AddUnsignedInteger(
+ key_prefix_, base::StringToUInt32(default_value_if_number).value());
+ return base::OkStatus();
+ case FieldDescriptorProto::TYPE_UINT64:
+ case FieldDescriptorProto::TYPE_FIXED64:
+ delegate.AddUnsignedInteger(
+ key_prefix_, base::StringToUInt64(default_value_if_number).value());
+ return base::OkStatus();
+ case FieldDescriptorProto::TYPE_BOOL:
+ delegate.AddBoolean(key_prefix_, default_value == "true");
+ return base::OkStatus();
+ case FieldDescriptorProto::TYPE_DOUBLE:
+ case FieldDescriptorProto::TYPE_FLOAT:
+ delegate.AddDouble(key_prefix_,
+ base::StringToDouble(default_value_if_number).value());
+ return base::OkStatus();
+ case FieldDescriptorProto::TYPE_BYTES:
+ delegate.AddBytes(key_prefix_, protozero::ConstBytes{});
+ return base::OkStatus();
+ case FieldDescriptorProto::TYPE_STRING:
+ if (default_value) {
+ delegate.AddString(key_prefix_, default_value.value());
+ } else {
+ delegate.AddNull(key_prefix_);
+ }
+ return base::OkStatus();
+ case FieldDescriptorProto::TYPE_MESSAGE:
+ delegate.AddNull(key_prefix_);
+ return base::OkStatus();
+ case FieldDescriptorProto::TYPE_ENUM:
+ return AddEnum(descriptor,
+ base::StringToInt32(default_value_if_number).value(),
+ delegate);
+ default:
+ return base::ErrStatus(
+ "Tried to write default value of type field %s (in proto type "
+ "%s) which has type enum %u",
+ descriptor.name().c_str(), descriptor.resolved_type_name().c_str(),
+ descriptor.type());
+ }
+}
+
+base::Status ProtoToArgsParser::AddEnum(const FieldDescriptor& descriptor,
+ int32_t value,
+ Delegate& delegate) {
+ auto opt_enum_descriptor_idx =
+ pool_.FindDescriptorIdx(descriptor.resolved_type_name());
+ if (!opt_enum_descriptor_idx) {
+ delegate.AddInteger(key_prefix_, value);
+ return base::OkStatus();
+ }
+ auto opt_enum_string =
+ pool_.descriptors()[*opt_enum_descriptor_idx].FindEnumString(value);
+ if (!opt_enum_string) {
+ // Fall back to the integer representation of the field.
+ // 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(
+ key_prefix_,
+ protozero::ConstChars{opt_enum_string->data(), opt_enum_string->size()});
+ return base::OkStatus();
+}
} // namespace util
} // namespace trace_processor
} // namespace perfetto
diff --git a/src/trace_processor/util/proto_to_args_parser.h b/src/trace_processor/util/proto_to_args_parser.h
index b709a40..427ed41 100644
--- a/src/trace_processor/util/proto_to_args_parser.h
+++ b/src/trace_processor/util/proto_to_args_parser.h
@@ -147,7 +147,8 @@
const std::string& type,
const std::vector<uint32_t>* allowed_fields,
Delegate& delegate,
- int* unknown_extensions = nullptr);
+ int* unknown_extensions = nullptr,
+ bool add_defaults = false);
// This class is responsible for resetting the current key prefix to the old
// value when deleted or reset.
@@ -249,14 +250,16 @@
int repeated_field_number,
protozero::Field field,
Delegate& delegate,
- int* unknown_extensions);
+ int* unknown_extensions,
+ bool add_defaults);
base::Status ParsePackedField(
const FieldDescriptor& field_descriptor,
std::unordered_map<size_t, int>& repeated_field_index,
protozero::Field field,
Delegate& delegate,
- int* unknown_extensions);
+ int* unknown_extensions,
+ bool add_defaults);
std::optional<base::Status> MaybeApplyOverrideForField(
const protozero::Field&,
@@ -275,12 +278,19 @@
const std::string& type,
const std::vector<uint32_t>* fields,
Delegate& delegate,
- int* unknown_extensions);
+ int* unknown_extensions,
+ bool add_defaults = false);
base::Status ParseSimpleField(const FieldDescriptor& desciptor,
const protozero::Field& field,
Delegate& delegate);
+ base::Status AddDefault(const FieldDescriptor& desciptor, Delegate& delegate);
+
+ base::Status AddEnum(const FieldDescriptor& descriptor,
+ int32_t value,
+ Delegate& delegate);
+
std::unordered_map<std::string, ParsingOverrideForField> field_overrides_;
std::unordered_map<std::string, ParsingOverrideForType> type_overrides_;
const DescriptorPool& pool_;
diff --git a/src/trace_processor/util/proto_to_args_parser_unittest.cc b/src/trace_processor/util/proto_to_args_parser_unittest.cc
index 5baf8f0..3dae057 100644
--- a/src/trace_processor/util/proto_to_args_parser_unittest.cc
+++ b/src/trace_processor/util/proto_to_args_parser_unittest.cc
@@ -662,6 +662,54 @@
"field_double field_double[3] 1.79769e+308"));
}
+TEST_F(ProtoToArgsParserTest, AddsDefaults) {
+ using namespace protozero::test::protos::pbzero;
+ protozero::HeapBuffered<EveryField> msg{kChunkSize, kChunkSize};
+ msg->set_field_int32(-1);
+ msg->add_repeated_string("test");
+ msg->add_repeated_sfixed32(1);
+ msg->add_repeated_fixed64(1);
+ msg->set_nested_enum(EveryField::PONG);
+
+ auto binary_proto = msg.SerializeAsArray();
+
+ DescriptorPool pool;
+ auto status = pool.AddFromFileDescriptorSet(kTestMessagesDescriptor.data(),
+ kTestMessagesDescriptor.size());
+ ProtoToArgsParser parser(pool);
+ ASSERT_TRUE(status.ok()) << "Failed to parse kTestMessagesDescriptor: "
+ << status.message();
+
+ status = parser.ParseMessage(
+ protozero::ConstBytes{binary_proto.data(), binary_proto.size()},
+ ".protozero.test.protos.EveryField", nullptr, *this, nullptr, true);
+
+ EXPECT_TRUE(status.ok()) << "AddsDefaults failed with error: "
+ << status.message();
+
+ EXPECT_THAT(
+ args(),
+ testing::UnorderedElementsAre(
+ "field_int32 field_int32 -1", // exists in message
+ "repeated_string repeated_string[0] test",
+ "repeated_sfixed32 repeated_sfixed32[0] 1",
+ "repeated_fixed64 repeated_fixed64[0] 1",
+ "nested_enum nested_enum PONG",
+ "field_bytes field_bytes <bytes size=0>",
+ "field_string field_string [NULL]", // null if no string default
+ "field_nested field_nested [NULL]", // no defaults for inner fields
+ "field_bool field_bool false",
+ "repeated_int32 repeated_int32 [NULL]", // null for repeated fields
+ "field_double field_double 0", "field_float field_float 0",
+ "field_sfixed64 field_sfixed64 0", "field_sfixed32 field_sfixed32 0",
+ "field_fixed64 field_fixed64 0", "field_sint64 field_sint64 0",
+ "big_enum big_enum 0", "field_fixed32 field_fixed32 0",
+ "field_sint32 field_sint32 0",
+ "signed_enum signed_enum NEUTRAL", // translates default enum
+ "small_enum small_enum NOT_TO_BE", "field_uint64 field_uint64 0",
+ "field_uint32 field_uint32 0", "field_int64 field_int64 0"));
+}
+
} // namespace
} // namespace util
} // namespace trace_processor
diff --git a/src/trace_processor/util/sql_modules.h b/src/trace_processor/util/sql_modules.h
index 997acb0..e060fb0 100644
--- a/src/trace_processor/util/sql_modules.h
+++ b/src/trace_processor/util/sql_modules.h
@@ -20,26 +20,22 @@
#include <string>
#include "perfetto/ext/base/flat_hash_map.h"
-#include "perfetto/ext/base/string_splitter.h"
#include "perfetto/ext/base/string_view.h"
-#include "perfetto/trace_processor/basic_types.h"
-namespace perfetto {
-namespace trace_processor {
-namespace sql_modules {
+namespace perfetto ::trace_processor::sql_modules {
-using NameToModule =
+using NameToPackage =
base::FlatHashMap<std::string,
std::vector<std::pair<std::string, std::string>>>;
// Map from include key to sql file. Include key is the string used in INCLUDE
// function.
-struct RegisteredModule {
+struct RegisteredPackage {
struct ModuleFile {
std::string sql;
bool included;
};
- base::FlatHashMap<std::string, ModuleFile> include_key_to_file;
+ base::FlatHashMap<std::string, ModuleFile> modules;
};
inline std::string ReplaceSlashWithDot(std::string str) {
@@ -51,13 +47,13 @@
return str;
}
-inline std::string GetIncludeKey(std::string path) {
+inline std::string GetIncludeKey(const std::string& path) {
base::StringView path_view(path);
auto path_no_extension = path_view.substr(0, path_view.rfind('.'));
return ReplaceSlashWithDot(path_no_extension.ToStdString());
}
-inline std::string GetModuleName(std::string str) {
+inline std::string GetPackageName(const std::string& str) {
size_t found = str.find('.');
if (found == std::string::npos) {
return str;
@@ -65,7 +61,5 @@
return str.substr(0, found);
}
-} // namespace sql_modules
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor::sql_modules
#endif // SRC_TRACE_PROCESSOR_UTIL_SQL_MODULES_H_
diff --git a/src/trace_processor/util/trace_blob_view_reader.cc b/src/trace_processor/util/trace_blob_view_reader.cc
index 78a1e1f..acdc898 100644
--- a/src/trace_processor/util/trace_blob_view_reader.cc
+++ b/src/trace_processor/util/trace_blob_view_reader.cc
@@ -21,8 +21,10 @@
#include <cstddef>
#include <cstdint>
#include <cstring>
+#include <iterator>
#include <optional>
#include <utility>
+#include <vector>
#include "perfetto/base/logging.h"
#include "perfetto/public/compiler.h"
@@ -59,12 +61,13 @@
return target_offset == end_offset_;
}
-std::optional<TraceBlobView> TraceBlobViewReader::SliceOff(
- size_t offset,
- size_t length) const {
- // If the length is zero, then a zero-sized blob view is always approrpriate.
+template <typename Visitor>
+auto TraceBlobViewReader::SliceOffImpl(const size_t offset,
+ const size_t length,
+ Visitor& visitor) const {
+ // If the length is zero, then a zero-sized blob view is always appropiate.
if (PERFETTO_UNLIKELY(length == 0)) {
- return TraceBlobView();
+ return visitor.OneSlice(TraceBlobView());
}
PERFETTO_DCHECK(offset >= start_offset());
@@ -76,47 +79,105 @@
!data_.empty() &&
offset + length <= data_.front().start_offset + data_.front().data.size();
if (PERFETTO_LIKELY(is_fast_path)) {
- return data_.front().data.slice_off(offset - data_.front().start_offset,
- length);
+ return visitor.OneSlice(data_.front().data.slice_off(
+ offset - data_.front().start_offset, length));
}
// If we don't have any TBVs or the end of the slice does not fit, then we
// cannot possibly return a full slice.
if (PERFETTO_UNLIKELY(data_.empty() || offset + length > end_offset_)) {
- return std::nullopt;
+ return visitor.NoData();
}
// Find the first block finishes *after* start_offset i.e. there is at least
// one byte in that block which will end up in the slice. We know this *must*
// exist because of the above check.
- auto rit = std::upper_bound(
+ auto it = std::upper_bound(
data_.begin(), data_.end(), offset, [](size_t offset, const Entry& rhs) {
return offset < rhs.start_offset + rhs.data.size();
});
- PERFETTO_CHECK(rit != data_.end());
+ PERFETTO_CHECK(it != data_.end());
// If the slice fits entirely in the block we found, then just slice that
// block avoiding any copies.
- size_t rel_off = offset - rit->start_offset;
- if (rel_off + length <= rit->data.size()) {
- return rit->data.slice_off(rel_off, length);
+ size_t rel_off = offset - it->start_offset;
+ if (rel_off + length <= it->data.size()) {
+ return visitor.OneSlice(it->data.slice_off(rel_off, length));
}
- // Otherwise, allocate some memory and make a copy.
- auto buffer = TraceBlob::Allocate(length);
- uint8_t* ptr = buffer.data();
- uint8_t* end = buffer.data() + buffer.size();
+ auto res = visitor.StartMultiSlice(length);
- // Copy all bytes in this block which overlap with the slice.
- memcpy(ptr, rit->data.data() + rel_off, rit->data.length() - rel_off);
- ptr += rit->data.length() - rel_off;
+ size_t res_offset = 0;
+ size_t left = length;
- for (auto it = rit + 1; ptr != end; ++it) {
- auto len = std::min(static_cast<size_t>(end - ptr), it->data.size());
- memcpy(ptr, it->data.data(), len);
- ptr += len;
+ size_t size = it->data.length() - rel_off;
+ visitor.AddSlice(res, res_offset, it->data.slice_off(rel_off, size));
+ left -= size;
+ res_offset += size;
+
+ for (++it; left != 0; ++it) {
+ size = std::min(left, it->data.size());
+ visitor.AddSlice(res, res_offset, it->data.slice_off(0, size));
+ left -= size;
+ res_offset += size;
}
- return TraceBlobView(std::move(buffer));
+
+ return visitor.Finalize(std::move(res));
+}
+
+std::optional<TraceBlobView> TraceBlobViewReader::SliceOff(
+ size_t offset,
+ size_t length) const {
+ struct Visitor {
+ std::optional<TraceBlobView> NoData() { return std::nullopt; }
+
+ std::optional<TraceBlobView> OneSlice(TraceBlobView tbv) {
+ return std::move(tbv);
+ }
+
+ TraceBlob StartMultiSlice(size_t length) {
+ return TraceBlob::Allocate(length);
+ }
+
+ void AddSlice(TraceBlob& blob, size_t offset, TraceBlobView tbv) {
+ memcpy(blob.data() + offset, tbv.data(), tbv.size());
+ }
+
+ std::optional<TraceBlobView> Finalize(TraceBlob blob) {
+ return TraceBlobView(std::move(blob));
+ }
+
+ } visitor;
+
+ return SliceOffImpl(offset, length, visitor);
+}
+
+std::vector<TraceBlobView> TraceBlobViewReader::MultiSliceOff(
+ size_t offset,
+ size_t length) const {
+ struct Visitor {
+ std::vector<TraceBlobView> NoData() { return {}; }
+
+ std::vector<TraceBlobView> OneSlice(TraceBlobView tbv) {
+ std::vector<TraceBlobView> res;
+ res.reserve(1);
+ res.push_back(std::move(tbv));
+ return res;
+ }
+
+ std::vector<TraceBlobView> StartMultiSlice(size_t) { return {}; }
+
+ void AddSlice(std::vector<TraceBlobView>& vec, size_t, TraceBlobView tbv) {
+ vec.push_back(std::move(tbv));
+ }
+
+ std::vector<TraceBlobView> Finalize(std::vector<TraceBlobView> vec) {
+ return vec;
+ }
+
+ } visitor;
+
+ return SliceOffImpl(offset, length, visitor);
}
} // namespace perfetto::trace_processor::util
diff --git a/src/trace_processor/util/trace_blob_view_reader.h b/src/trace_processor/util/trace_blob_view_reader.h
index 69e5aa3..76fac2d 100644
--- a/src/trace_processor/util/trace_blob_view_reader.h
+++ b/src/trace_processor/util/trace_blob_view_reader.h
@@ -20,7 +20,9 @@
#include <cstddef>
#include <cstdint>
#include <cstdio>
+#include <cstring>
#include <optional>
+#include <string_view>
#include "perfetto/base/logging.h"
#include "perfetto/ext/base/circular_queue.h"
@@ -46,12 +48,64 @@
public:
class Iterator {
public:
+ ~Iterator() = default;
+
Iterator(const Iterator&) = default;
- Iterator(Iterator&&) = default;
Iterator& operator=(const Iterator&) = default;
+
+ Iterator(Iterator&&) = default;
Iterator& operator=(Iterator&&) = default;
- ~Iterator() = default;
+ // Tries to advance the iterator `size` bytes forward. Returns true if
+ // the advance was successful and false if it would overflow the iterator.
+ // If false is returned, the state of the iterator is not changed.
+ bool MaybeAdvance(size_t delta) {
+ file_offset_ += delta;
+ if (PERFETTO_LIKELY(file_offset_ < iter_->end_offset())) {
+ return true;
+ }
+ if (file_offset_ == end_offset_) {
+ return true;
+ }
+ if (file_offset_ > end_offset_) {
+ file_offset_ -= delta;
+ return false;
+ }
+ do {
+ ++iter_;
+ } while (file_offset_ >= iter_->end_offset());
+ return true;
+ }
+
+ // Tries to read `size` bytes from the iterator. Returns a TraceBlobView
+ // containing the data if `size` bytes were available and std::nullopt
+ // otherwise. If std::nullopt is returned, the state of the iterator is not
+ // changed.
+ std::optional<TraceBlobView> MaybeRead(size_t delta) {
+ std::optional<TraceBlobView> tbv =
+ reader_->SliceOff(file_offset(), delta);
+ if (PERFETTO_LIKELY(tbv)) {
+ PERFETTO_CHECK(MaybeAdvance(delta));
+ }
+ return tbv;
+ }
+
+ // Tries to find a byte equal to |chr| in the iterator and, if found,
+ // advance to it. Returns a TraceBlobView containing the data if the byte
+ // was found and could be advanced to and std::nullopt if no such byte was
+ // found before the end of the iterator. If std::nullopt is returned, the
+ // state of the iterator is not changed.
+ std::optional<TraceBlobView> MaybeFindAndRead(uint8_t chr) {
+ size_t begin = file_offset();
+ if (!MaybeFindAndAdvanceInner(chr)) {
+ return std::nullopt;
+ }
+ std::optional<TraceBlobView> tbv =
+ reader_->SliceOff(begin, file_offset() - begin);
+ PERFETTO_CHECK(tbv);
+ PERFETTO_CHECK(MaybeAdvance(1));
+ return tbv;
+ }
uint8_t operator*() const {
PERFETTO_DCHECK(file_offset_ < iter_->end_offset());
@@ -62,45 +116,48 @@
size_t file_offset() const { return file_offset_; }
- bool MaybeAdvance(size_t delta) {
- if (delta == 0) {
- return true;
- }
- if (delta > end_offset_ - file_offset_) {
- return false;
- }
- file_offset_ += delta;
- if (PERFETTO_LIKELY(file_offset_ < iter_->end_offset())) {
- return true;
- }
- while (file_offset_ > iter_->end_offset()) {
- ++iter_;
- }
- if (file_offset_ == iter_->end_offset()) {
- ++iter_;
- }
-
- return true;
- }
-
private:
friend TraceBlobViewReader;
- Iterator(base::CircularQueue<Entry>::Iterator iter,
+
+ Iterator(const TraceBlobViewReader* reader,
+ base::CircularQueue<Entry>::Iterator iter,
size_t file_offset,
size_t end_offset)
- : iter_(std::move(iter)),
+ : reader_(reader),
+ iter_(iter),
file_offset_(file_offset),
end_offset_(end_offset) {}
+
+ // Tries to find a byte equal to |chr| in the iterator and, if found,
+ // advance to it. Returns true if the byte was found and could be advanced
+ // to and false if no such byte was found before the end of the iterator. If
+ // false is returned, the state of the iterator is not changed.
+ bool MaybeFindAndAdvanceInner(uint8_t chr) {
+ size_t off = file_offset_;
+ while (off < end_offset_) {
+ size_t iter_off = off - iter_->start_offset;
+ size_t iter_rem = iter_->data.size() - iter_off;
+ const auto* p = reinterpret_cast<const uint8_t*>(
+ memchr(iter_->data.data() + iter_off, chr, iter_rem));
+ if (p) {
+ file_offset_ =
+ iter_->start_offset + static_cast<size_t>(p - iter_->data.data());
+ return true;
+ }
+ off = iter_->end_offset();
+ ++iter_;
+ }
+ return false;
+ }
+
+ const TraceBlobViewReader* reader_;
base::CircularQueue<Entry>::Iterator iter_;
size_t file_offset_;
size_t end_offset_;
};
- Iterator begin() const {
- return Iterator(data_.begin(), start_offset(), end_offset());
- }
- Iterator end() const {
- return Iterator(data_.end(), end_offset(), end_offset());
+ Iterator GetIterator() const {
+ return {this, data_.begin(), start_offset(), end_offset()};
}
// Adds a `TraceBlobView` at the back.
@@ -110,12 +167,18 @@
// given offset is reached. If not enough data is present as much data as
// possible will be dropped and `false` will be returned.
//
- // NOTE: If `offset` < 'file_offset()' this method will CHECK fail.
+ // Note:
+ // * if `offset` < 'file_offset()' this method will CHECK fail.
+ // * calling this function invalidates all iterators created from this
+ // reader.
bool PopFrontUntil(size_t offset);
// Shrinks the buffer by dropping `bytes` from the front of the buffer. If not
// enough data is present as much data as possible will be dropped and `false`
// will be returned.
+ //
+ // Note: calling this function invalidates all iterators created from this
+ // reader.
bool PopFrontBytes(size_t bytes) {
return PopFrontUntil(start_offset() + bytes);
}
@@ -129,6 +192,12 @@
//
// NOTE: If `offset` < 'file_offset()' this method will CHECK fail.
std::optional<TraceBlobView> SliceOff(size_t offset, size_t length) const;
+
+ // Similar to SliceOff but this method will not combine slices but instead
+ // potentially return multiple chunks. Useful if we are extracting slices to
+ // forward them to a `ChunkedTraceReader`.
+ std::vector<TraceBlobView> MultiSliceOff(size_t offset, size_t length) const;
+
// Returns the offset to the start of the available data.
size_t start_offset() const {
return data_.empty() ? end_offset_ : data_.front().start_offset;
@@ -143,6 +212,9 @@
bool empty() const { return data_.empty(); }
private:
+ template <typename Visitor>
+ auto SliceOffImpl(size_t offset, size_t length, Visitor& visitor) const;
+
// CircularQueue has no const_iterator, so mutable is needed to access it from
// const methods.
mutable base::CircularQueue<Entry> data_;
diff --git a/src/trace_processor/util/trace_type.cc b/src/trace_processor/util/trace_type.cc
index 3c64c3f..bec7279 100644
--- a/src/trace_processor/util/trace_type.cc
+++ b/src/trace_processor/util/trace_type.cc
@@ -17,14 +17,16 @@
#include "src/trace_processor/util/trace_type.h"
#include <algorithm>
+#include <cctype>
#include <cstddef>
#include <cstdint>
#include <string>
#include "perfetto/base/logging.h"
#include "perfetto/ext/base/string_utils.h"
-#include "perfetto/ext/base/string_view.h"
+#include "perfetto/protozero/proto_utils.h"
#include "src/trace_processor/importers/android_bugreport/android_log_event.h"
+#include "src/trace_processor/importers/perf_text/perf_text_sample_line_parser.h"
#include "protos/perfetto/trace/trace.pbzero.h"
#include "protos/perfetto/trace/trace_packet.pbzero.h"
@@ -36,10 +38,12 @@
constexpr char kFuchsiaMagic[] = {'\x10', '\x00', '\x04', '\x46',
'\x78', '\x54', '\x16', '\x00'};
constexpr char kPerfMagic[] = {'P', 'E', 'R', 'F', 'I', 'L', 'E', '2'};
-
constexpr char kZipMagic[] = {'P', 'K', '\x03', '\x04'};
-
constexpr char kGzipMagic[] = {'\x1f', '\x8b'};
+constexpr char kArtMethodStreamingMagic[] = {'S', 'L', 'O', 'W'};
+constexpr char kTarPosixMagic[] = {'u', 's', 't', 'a', 'r', '\0'};
+constexpr char kTarGnuMagic[] = {'u', 's', 't', 'a', 'r', ' ', ' ', '\0'};
+constexpr size_t kTarMagicOffset = 257;
constexpr uint8_t kTracePacketTag =
protozero::proto_utils::MakeTagLengthDelimited(
@@ -58,11 +62,15 @@
}
template <size_t N>
-bool MatchesMagic(const uint8_t* data, size_t size, const char (&magic)[N]) {
- if (size < N) {
+bool MatchesMagic(const uint8_t* data,
+ size_t size,
+ const char (&magic)[N],
+ size_t offset = 0) {
+ if (size < N + offset) {
return false;
}
- return memcmp(data, magic, N) == 0;
+
+ return memcmp(data + offset, magic, N) == 0;
}
bool IsProtoTraceWithSymbols(const uint8_t* ptr, size_t size) {
@@ -127,8 +135,16 @@
return "android_dumpstate";
case kAndroidBugreportTraceType:
return "android_bugreport";
+ case kGeckoTraceType:
+ return "gecko";
+ case kArtMethodTraceType:
+ return "art_method";
+ case kPerfTextTraceType:
+ return "perf_text";
case kUnknownTraceType:
return "unknown";
+ case kTarTraceType:
+ return "tar";
}
PERFETTO_FATAL("For GCC");
}
@@ -138,6 +154,14 @@
return kUnknownTraceType;
}
+ if (MatchesMagic(data, size, kTarPosixMagic, kTarMagicOffset)) {
+ return kTarTraceType;
+ }
+
+ if (MatchesMagic(data, size, kTarGnuMagic, kTarMagicOffset)) {
+ return kTarTraceType;
+ }
+
if (MatchesMagic(data, size, kFuchsiaMagic)) {
return kFuchsiaTraceType;
}
@@ -154,15 +178,29 @@
return kGzipTraceType;
}
+ if (MatchesMagic(data, size, kArtMethodStreamingMagic)) {
+ return kArtMethodTraceType;
+ }
+
std::string start(reinterpret_cast<const char*>(data),
std::min<size_t>(size, kGuessTraceMaxLookahead));
std::string start_minus_white_space = RemoveWhitespace(start);
+ // Generated by the Gecko conversion script built into perf.
+ if (base::StartsWith(start_minus_white_space, "{\"meta\""))
+ return kGeckoTraceType;
+ // Generated by the simpleperf conversion script.
+ if (base::StartsWith(start_minus_white_space, "{\"libs\""))
+ return kGeckoTraceType;
if (base::StartsWith(start_minus_white_space, "{\""))
return kJsonTraceType;
if (base::StartsWith(start_minus_white_space, "[{\""))
return kJsonTraceType;
+ // ART method traces (non-streaming).
+ if (base::StartsWith(start, "*version\n"))
+ return kArtMethodTraceType;
+
// Systrace with header but no leading HTML.
if (base::Contains(start, "# tracer"))
return kSystraceTraceType;
@@ -196,6 +234,10 @@
return kAndroidLogcatTraceType;
}
+ // Perf text format.
+ if (perf_text_importer::IsPerfTextFormatTrace(data, size))
+ return kPerfTextTraceType;
+
// Systrace with no header or leading HTML.
if (base::StartsWith(start, " "))
return kSystraceTraceType;
diff --git a/src/trace_processor/util/trace_type.h b/src/trace_processor/util/trace_type.h
index dbf72ea..80d8ff4 100644
--- a/src/trace_processor/util/trace_type.h
+++ b/src/trace_processor/util/trace_type.h
@@ -38,6 +38,10 @@
kUnknownTraceType,
kZipFile,
kInstrumentsXmlTraceType,
+ kGeckoTraceType,
+ kArtMethodTraceType,
+ kPerfTextTraceType,
+ kTarTraceType,
};
constexpr size_t kGuessTraceMaxLookahead = 64;
diff --git a/src/trace_processor/util/winscope_proto_mapping.h b/src/trace_processor/util/winscope_proto_mapping.h
new file mode 100644
index 0000000..b9bd320
--- /dev/null
+++ b/src/trace_processor/util/winscope_proto_mapping.h
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_UTIL_WINSCOPE_PROTO_MAPPING_H_
+#define SRC_TRACE_PROCESSOR_UTIL_WINSCOPE_PROTO_MAPPING_H_
+
+#include "perfetto/ext/base/status_or.h"
+#include "src/trace_processor/tables/android_tables_py.h"
+#include "src/trace_processor/tables/winscope_tables_py.h"
+
+namespace perfetto::trace_processor {
+namespace util {
+namespace winscope_proto_mapping {
+inline base::StatusOr<const char* const> GetProtoName(
+ const std::string& table_name) {
+ if (table_name == tables::SurfaceFlingerLayerTable::Name()) {
+ return ".perfetto.protos.LayerProto";
+ }
+ if (table_name == tables::SurfaceFlingerLayersSnapshotTable::Name()) {
+ return ".perfetto.protos.LayersSnapshotProto";
+ }
+ if (table_name == tables::SurfaceFlingerTransactionsTable::Name()) {
+ return ".perfetto.protos.TransactionTraceEntry";
+ }
+ if (table_name == tables::WindowManagerShellTransitionsTable::Name()) {
+ return ".perfetto.protos.ShellTransition";
+ }
+ if (table_name == tables::InputMethodClientsTable::Name()) {
+ return ".perfetto.protos.InputMethodClientsTraceProto";
+ }
+ if (table_name == tables::InputMethodManagerServiceTable::Name()) {
+ return ".perfetto.protos.InputMethodManagerServiceTraceProto";
+ }
+ if (table_name == tables::InputMethodServiceTable::Name()) {
+ return ".perfetto.protos.InputMethodServiceTraceProto";
+ }
+ if (table_name == tables::ViewCaptureTable::Name()) {
+ return ".perfetto.protos.ViewCapture";
+ }
+ if (table_name == tables::WindowManagerTable::Name()) {
+ return ".perfetto.protos.WindowManagerTraceEntry";
+ }
+ if (table_name == tables::AndroidKeyEventsTable::Name()) {
+ return ".perfetto.protos.AndroidKeyEvent";
+ }
+ if (table_name == tables::AndroidMotionEventsTable::Name()) {
+ return ".perfetto.protos.AndroidMotionEvent";
+ }
+ if (table_name == tables::AndroidInputEventDispatchTable::Name()) {
+ return ".perfetto.protos.AndroidWindowInputDispatchEvent";
+ }
+ return base::ErrStatus("%s table does not have proto descriptor.",
+ table_name.c_str());
+}
+
+inline std::optional<const std::vector<uint32_t>> GetAllowedFields(
+ const std::string& table_name) {
+ if (table_name == tables::SurfaceFlingerLayersSnapshotTable::Name()) {
+ return std::vector<uint32_t>({1, 2, 4, 5, 6, 7, 8});
+ }
+ return std::nullopt;
+}
+} // namespace winscope_proto_mapping
+} // namespace util
+} // namespace perfetto::trace_processor
+
+#endif // SRC_TRACE_PROCESSOR_UTIL_WINSCOPE_PROTO_MAPPING_H_
diff --git a/src/trace_processor/util/zip_reader.cc b/src/trace_processor/util/zip_reader.cc
index 1ec0cf5..f65f0b7 100644
--- a/src/trace_processor/util/zip_reader.cc
+++ b/src/trace_processor/util/zip_reader.cc
@@ -60,7 +60,9 @@
k8kSlidingDictionary = 1u << 1,
kShannonFaro = 1u << 2,
kDataDescriptor = 1u << 3,
- kUnknown = ~((1u << 4) - 1),
+ kLangageEncoding = 1u << 11,
+ kUnknown = ~(kEncrypted | k8kSlidingDictionary | kShannonFaro |
+ kDataDescriptor | kLangageEncoding),
};
// Compression flags.
diff --git a/src/traceconv/trace_to_pprof_integrationtest.cc b/src/traceconv/trace_to_pprof_integrationtest.cc
index b852854..bff3e26 100644
--- a/src/traceconv/trace_to_pprof_integrationtest.cc
+++ b/src/traceconv/trace_to_pprof_integrationtest.cc
@@ -14,9 +14,14 @@
* limitations under the License.
*/
+#include <unistd.h>
#include "test/gtest_and_gmock.h"
#include <fstream>
+#include <ostream>
+#include <sstream>
+#include <string>
+#include <vector>
#include "perfetto/base/logging.h"
#include "perfetto/ext/base/file_utils.h"
@@ -30,7 +35,7 @@
using testing::Contains;
-pprof::PprofProfileReader convert_trace_to_pprof(
+pprof::PprofProfileReader ConvertTraceToPprof(
const std::string& input_file_name) {
const std::string trace_file = base::GetTestDataPath(input_file_name);
std::ifstream file_istream;
@@ -84,8 +89,7 @@
};
TEST_F(TraceToPprofTest, SummaryValues) {
- const auto pprof =
- convert_trace_to_pprof("test/data/heap_graph/heap_graph.pb");
+ const auto pprof = ConvertTraceToPprof("test/data/heap_graph/heap_graph.pb");
EXPECT_EQ(pprof.get_samples_value_sum("Foo", "Total allocation count"), 1);
EXPECT_EQ(pprof.get_samples_value_sum("Foo", "Total allocation size"), 32);
@@ -100,7 +104,7 @@
TEST_F(TraceToPprofTest, TreeLocationFunctionNames) {
const auto pprof =
- convert_trace_to_pprof("test/data/heap_graph/heap_graph_branching.pb");
+ ConvertTraceToPprof("test/data/heap_graph/heap_graph_branching.pb");
EXPECT_THAT(get_samples_function_names(pprof, "LeftChild0"),
Contains(std::vector<std::string>{"LeftChild0",
@@ -118,7 +122,7 @@
TEST_F(TraceToPprofTest, HugeSizes) {
const auto pprof =
- convert_trace_to_pprof("test/data/heap_graph/heap_graph_huge_size.pb");
+ ConvertTraceToPprof("test/data/heap_graph/heap_graph_huge_size.pb");
EXPECT_EQ(pprof.get_samples_value_sum("dev.perfetto.BigStuff",
"Total allocation size"),
3000000000);
@@ -138,7 +142,7 @@
TEST_F(TraceToPprofRealTraceTest, AllocationCountForClass) {
const auto pprof =
- convert_trace_to_pprof("test/data/system-server-heap-graph-new.pftrace");
+ ConvertTraceToPprof("test/data/system-server-heap-graph-new.pftrace");
EXPECT_EQ(pprof.get_samples_value_sum(
"android.content.pm.parsing.component.ParsedActivity",
diff --git a/src/traceconv/trace_to_profile.cc b/src/traceconv/trace_to_profile.cc
index b7676be..96b97fd 100644
--- a/src/traceconv/trace_to_profile.cc
+++ b/src/traceconv/trace_to_profile.cc
@@ -110,12 +110,11 @@
tp->Flush();
MaybeSymbolize(tp.get());
MaybeDeobfuscate(tp.get());
-
- TraceToPprof(tp.get(), &profiles, conversion_mode, conversion_flags, pid,
- timestamps);
if (auto status = tp->NotifyEndOfFile(); !status.ok()) {
return -1;
}
+ TraceToPprof(tp.get(), &profiles, conversion_mode, conversion_flags, pid,
+ timestamps);
if (profiles.empty()) {
return 0;
}
diff --git a/src/traced/probes/ftrace/cpu_reader.cc b/src/traced/probes/ftrace/cpu_reader.cc
index 2df0f42..850ff0e 100644
--- a/src/traced/probes/ftrace/cpu_reader.cc
+++ b/src/traced/probes/ftrace/cpu_reader.cc
@@ -47,6 +47,7 @@
namespace {
using FtraceParseStatus = protos::pbzero::FtraceParseStatus;
+using protos::pbzero::KprobeEvent;
// If the compact_sched buffer accumulates more unique strings, the reader will
// flush it to reset the interning state (and make it cheap again).
@@ -746,6 +747,14 @@
info.proto_field_id ==
protos::pbzero::FtraceEvent::kSysExitFieldNumber)) {
success &= ParseSysExit(info, start, end, ds_config, nested, metadata);
+ } else if (PERFETTO_UNLIKELY(
+ info.proto_field_id ==
+ protos::pbzero::FtraceEvent::kKprobeEventFieldNumber)) {
+ KprobeEvent::KprobeType* elem = ds_config->kprobes.Find(ftrace_event_id);
+ nested->AppendString(KprobeEvent::kNameFieldNumber, info.name);
+ if (elem) {
+ nested->AppendVarInt(KprobeEvent::kTypeFieldNumber, *elem);
+ }
} else { // Parse all other events.
for (const Field& field : info.fields) {
success &= ParseField(field, start, end, table, nested, metadata);
diff --git a/src/traced/probes/ftrace/cpu_reader_unittest.cc b/src/traced/probes/ftrace/cpu_reader_unittest.cc
index 8d7e39f..daa90e5 100644
--- a/src/traced/probes/ftrace/cpu_reader_unittest.cc
+++ b/src/traced/probes/ftrace/cpu_reader_unittest.cc
@@ -25,6 +25,8 @@
#include "perfetto/protozero/proto_utils.h"
#include "perfetto/protozero/scattered_heap_buffer.h"
#include "perfetto/protozero/scattered_stream_writer.h"
+#include "protos/perfetto/trace/ftrace/generic.pbzero.h"
+#include "src/base/test/tmp_dir_tree.h"
#include "src/traced/probes/ftrace/event_info.h"
#include "src/traced/probes/ftrace/ftrace_config_muxer.h"
#include "src/traced/probes/ftrace/ftrace_procfs.h"
@@ -42,6 +44,7 @@
#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
#include "protos/perfetto/trace/ftrace/ftrace_stats.gen.h"
#include "protos/perfetto/trace/ftrace/ftrace_stats.pbzero.h"
+#include "protos/perfetto/trace/ftrace/generic.gen.h"
#include "protos/perfetto/trace/ftrace/power.gen.h"
#include "protos/perfetto/trace/ftrace/raw_syscalls.gen.h"
#include "protos/perfetto/trace/ftrace/sched.gen.h"
@@ -1225,6 +1228,82 @@
EXPECT_EQ("kworker/u16:17", comm);
}
+TEST_F(CpuReaderParsePagePayloadTest, ParseKprobeAndKretprobe) {
+ char kprobe_fuse_file_write_iter_page[] =
+ R"(
+ 00000000: b31b bfe2 a513 0000 1400 0000 0000 0000 ................
+ 00000010: 0400 0000 ff05 48ff 8a33 0000 443d 0e91 ......H..3..D=..
+ 00000020: ffff ffff 0000 0000 0000 0000 0000 0000 ................
+ )";
+
+ std::unique_ptr<uint8_t[]> page =
+ PageFromXxd(kprobe_fuse_file_write_iter_page);
+
+ base::TmpDirTree ftrace;
+ ftrace.AddFile("available_events", "perfetto_kprobes:fuse_file_write_iter\n");
+ ftrace.AddDir("events");
+ ftrace.AddFile(
+ "events/header_page",
+ R"( field: u64 timestamp; offset:0; size:8; signed:0;
+ field: local_t commit; offset:8; size:8; signed:1;
+ field: int overwrite; offset:8; size:1; signed:1;
+ field: char data; offset:16; size:4080; signed:1;
+)");
+ ftrace.AddDir("events/perfetto_kprobes");
+ ftrace.AddDir("events/perfetto_kprobes/fuse_file_write_iter");
+ ftrace.AddFile("events/perfetto_kprobes/fuse_file_write_iter/format",
+ R"format(name: fuse_file_write_iter
+ID: 1535
+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:unsigned long __probe_ip; offset:8; size:8; signed:0;
+
+print fmt: "(%lx)", REC->__probe_ip
+)format");
+ ftrace.AddFile("trace", "");
+
+ std::unique_ptr<FtraceProcfs> ftrace_procfs =
+ FtraceProcfs::Create(ftrace.path() + "/");
+ ASSERT_NE(ftrace_procfs.get(), nullptr);
+ std::unique_ptr<ProtoTranslationTable> table = ProtoTranslationTable::Create(
+ ftrace_procfs.get(), GetStaticEventInfo(), GetStaticCommonFieldsInfo());
+ table->GetOrCreateKprobeEvent(
+ GroupAndName("perfetto_kprobes", "fuse_file_write_iter"));
+
+ auto ftrace_evt_id = static_cast<uint32_t>(table->EventToFtraceId(
+ GroupAndName("perfetto_kprobes", "fuse_file_write_iter")));
+ FtraceDataSourceConfig ds_config = EmptyConfig();
+ ds_config.event_filter.AddEnabledEvent(ftrace_evt_id);
+ ds_config.kprobes[ftrace_evt_id] =
+ protos::pbzero::KprobeEvent::KprobeType::KPROBE_TYPE_INSTANT;
+
+ const uint8_t* parse_pos = page.get();
+ std::optional<CpuReader::PageHeader> page_header =
+ CpuReader::ParsePageHeader(&parse_pos, table->page_header_size_len());
+
+ const uint8_t* page_end = page.get() + base::GetSysPageSize();
+ ASSERT_TRUE(page_header.has_value());
+ EXPECT_FALSE(page_header->lost_events);
+ EXPECT_LE(parse_pos + page_header->size, page_end);
+
+ FtraceParseStatus status = CpuReader::ParsePagePayload(
+ parse_pos, &page_header.value(), table.get(), &ds_config,
+ CreateBundler(ds_config), &metadata_, &last_read_event_ts_);
+
+ EXPECT_EQ(status, FtraceParseStatus::FTRACE_STATUS_OK);
+
+ // Write the buffer out & check the serialized format:
+ auto bundle = GetBundle();
+ ASSERT_EQ(bundle.event_size(), 1);
+ EXPECT_EQ(bundle.event()[0].kprobe_event().name(), "fuse_file_write_iter");
+ EXPECT_EQ(bundle.event()[0].kprobe_event().type(),
+ protos::gen::KprobeEvent::KPROBE_TYPE_INSTANT);
+}
+
TEST_F(CpuReaderTableTest, ParseAllFields) {
using FakeEventProvider =
ProtoProvider<pbzero::FakeFtraceEvent, gen::FakeFtraceEvent>;
diff --git a/src/traced/probes/ftrace/event_info.cc b/src/traced/probes/ftrace/event_info.cc
index 3670416..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",
{
@@ -1427,6 +1443,28 @@
kUnsetFtraceId,
508,
kUnsetSize},
+ {"devfreq_frequency",
+ "devfreq",
+ {
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "dev_name", 1, ProtoSchemaType::kString,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "freq", 2, ProtoSchemaType::kUint64,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "prev_freq", 3, ProtoSchemaType::kUint64,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "busy_time", 4, ProtoSchemaType::kUint64,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "total_time", 5, ProtoSchemaType::kUint64,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ },
+ kUnsetFtraceId,
+ 541,
+ kUnsetSize},
{"dma_fence_init",
"dma_fence",
{
diff --git a/src/traced/probes/ftrace/ftrace_config_muxer.cc b/src/traced/probes/ftrace/ftrace_config_muxer.cc
index ee9a3a2..e05d6f2 100644
--- a/src/traced/probes/ftrace/ftrace_config_muxer.cc
+++ b/src/traced/probes/ftrace/ftrace_config_muxer.cc
@@ -27,6 +27,7 @@
#include "perfetto/base/compiler.h"
#include "perfetto/ext/base/utils.h"
+#include "protos/perfetto/trace/ftrace/generic.pbzero.h"
#include "src/traced/probes/ftrace/atrace_wrapper.h"
#include "src/traced/probes/ftrace/compact_sched.h"
#include "src/traced/probes/ftrace/ftrace_config_utils.h"
@@ -37,6 +38,8 @@
namespace perfetto {
namespace {
+using protos::pbzero::KprobeEvent;
+
constexpr uint64_t kDefaultLowRamPerCpuBufferSizeKb = 2 * (1ULL << 10); // 2mb
constexpr uint64_t kDefaultHighRamPerCpuBufferSizeKb = 8 * (1ULL << 10); // 8mb
@@ -132,6 +135,43 @@
dst->insert(GroupAndName(group, name));
}
+std::map<GroupAndName, KprobeEvent::KprobeType> GetFtraceKprobeEvents(
+ const FtraceConfig& request) {
+ std::map<GroupAndName, KprobeEvent::KprobeType> events;
+ for (const auto& config_value : request.kprobe_events()) {
+ switch (config_value.type()) {
+ case protos::gen::FtraceConfig::KprobeEvent::KPROBE_TYPE_KPROBE:
+ events[GroupAndName(kKprobeGroup, config_value.probe().c_str())] =
+ KprobeEvent::KprobeType::KPROBE_TYPE_INSTANT;
+ break;
+ case protos::gen::FtraceConfig::KprobeEvent::KPROBE_TYPE_KRETPROBE:
+ events[GroupAndName(kKretprobeGroup, config_value.probe().c_str())] =
+ KprobeEvent::KprobeType::KPROBE_TYPE_INSTANT;
+ break;
+ case protos::gen::FtraceConfig::KprobeEvent::KPROBE_TYPE_BOTH:
+ events[GroupAndName(kKprobeGroup, config_value.probe().c_str())] =
+ KprobeEvent::KprobeType::KPROBE_TYPE_BEGIN;
+ events[GroupAndName(kKretprobeGroup, config_value.probe().c_str())] =
+ KprobeEvent::KprobeType::KPROBE_TYPE_END;
+ break;
+ case protos::gen::FtraceConfig::KprobeEvent::KPROBE_TYPE_UNKNOWN:
+ PERFETTO_DLOG("Unknown kprobe event");
+ break;
+ }
+ PERFETTO_DLOG("Added kprobe event: %s", config_value.probe().c_str());
+ }
+ return events;
+}
+
+bool ValidateKprobeName(const std::string& name) {
+ for (const char& c : name) {
+ if (!std::isalnum(c) && c != '_') {
+ return false;
+ }
+ }
+ return true;
+}
+
} // namespace
std::set<GroupAndName> FtraceConfigMuxer::GetFtraceEvents(
@@ -588,6 +628,29 @@
return true;
}
+void FtraceConfigMuxer::EnableFtraceEvent(const Event* event,
+ const GroupAndName& group_and_name,
+ EventFilter* filter,
+ FtraceSetupErrors* errors) {
+ // Note: ftrace events are always implicitly enabled (and don't have an
+ // "enable" file). So they aren't tracked by the central event filter (but
+ // still need to be added to the per data source event filter to retain
+ // the events during parsing).
+ if (current_state_.ftrace_events.IsEventEnabled(event->ftrace_event_id) ||
+ std::string("ftrace") == event->group) {
+ filter->AddEnabledEvent(event->ftrace_event_id);
+ return;
+ }
+ if (ftrace_->EnableEvent(event->group, event->name)) {
+ current_state_.ftrace_events.AddEnabledEvent(event->ftrace_event_id);
+ filter->AddEnabledEvent(event->ftrace_event_id);
+ } else {
+ PERFETTO_DPLOG("Failed to enable %s.", group_and_name.ToString().c_str());
+ if (errors)
+ errors->failed_ftrace_events.push_back(group_and_name.ToString());
+ }
+}
+
FtraceConfigMuxer::FtraceConfigMuxer(
FtraceProcfs* ftrace,
AtraceWrapper* atrace_wrapper,
@@ -646,6 +709,8 @@
}
std::set<GroupAndName> events = GetFtraceEvents(request, table_);
+ std::map<GroupAndName, KprobeEvent::KprobeType> events_kprobes =
+ GetFtraceKprobeEvents(request);
// Vendors can provide a set of extra ftrace categories to be enabled when a
// specific atrace category is used (e.g. "gfx" -> ["my_hw/my_custom_event",
@@ -675,7 +740,47 @@
UpdateAtrace(request, errors ? &errors->atrace_errors : nullptr);
}
+ base::FlatHashMap<uint32_t, KprobeEvent::KprobeType> kprobes;
+ for (const auto& [group_and_name, type] : events_kprobes) {
+ if (!ValidateKprobeName(group_and_name.name())) {
+ PERFETTO_ELOG("Invalid kprobes event %s", group_and_name.name().c_str());
+ if (errors)
+ errors->failed_ftrace_events.push_back(group_and_name.ToString());
+ continue;
+ }
+ // Kprobes events are created after their definition is written in the
+ // kprobe_events file
+ if (!ftrace_->CreateKprobeEvent(
+ group_and_name.group(), group_and_name.name(),
+ group_and_name.group() == kKretprobeGroup)) {
+ PERFETTO_ELOG("Failed creation of kprobes event %s",
+ group_and_name.name().c_str());
+ if (errors)
+ errors->failed_ftrace_events.push_back(group_and_name.ToString());
+ continue;
+ }
+
+ const Event* event = table_->GetOrCreateKprobeEvent(group_and_name);
+ if (!event) {
+ PERFETTO_ELOG("Can't enable kprobe %s",
+ group_and_name.ToString().c_str());
+ if (errors)
+ errors->unknown_ftrace_events.push_back(group_and_name.ToString());
+ continue;
+ }
+ EnableFtraceEvent(event, group_and_name, &filter, errors);
+ kprobes[event->ftrace_event_id] = type;
+ }
+
for (const auto& group_and_name : events) {
+ if (group_and_name.group() == kKprobeGroup ||
+ group_and_name.group() == kKretprobeGroup) {
+ PERFETTO_DLOG("Can't enable %s, group reserved for kprobes",
+ group_and_name.ToString().c_str());
+ if (errors)
+ errors->failed_ftrace_events.push_back(group_and_name.ToString());
+ continue;
+ }
const Event* event = table_->GetOrCreateEvent(group_and_name);
if (!event) {
PERFETTO_DLOG("Can't enable %s, event not known",
@@ -684,6 +789,7 @@
errors->unknown_ftrace_events.push_back(group_and_name.ToString());
continue;
}
+
// Niche option to skip events that are in the config, but don't have a
// dedicated proto for the event in perfetto. Otherwise such events will be
// encoded as GenericFtraceEvent.
@@ -694,23 +800,8 @@
errors->failed_ftrace_events.push_back(group_and_name.ToString());
continue;
}
- // Note: ftrace events are always implicitly enabled (and don't have an
- // "enable" file). So they aren't tracked by the central event filter (but
- // still need to be added to the per data source event filter to retain
- // the events during parsing).
- if (current_state_.ftrace_events.IsEventEnabled(event->ftrace_event_id) ||
- std::string("ftrace") == event->group) {
- filter.AddEnabledEvent(event->ftrace_event_id);
- continue;
- }
- if (ftrace_->EnableEvent(event->group, event->name)) {
- current_state_.ftrace_events.AddEnabledEvent(event->ftrace_event_id);
- filter.AddEnabledEvent(event->ftrace_event_id);
- } else {
- PERFETTO_DPLOG("Failed to enable %s.", group_and_name.ToString().c_str());
- if (errors)
- errors->failed_ftrace_events.push_back(group_and_name.ToString());
- }
+
+ EnableFtraceEvent(event, group_and_name, &filter, errors);
}
EventFilter syscall_filter = BuildSyscallFilter(filter, request);
@@ -773,7 +864,7 @@
std::vector<std::string> categories(request.atrace_categories());
std::vector<std::string> categories_sdk_optout = Subtract(
request.atrace_categories(), request.atrace_categories_prefer_sdk());
- ds_configs_.emplace(
+ auto [it, inserted] = ds_configs_.emplace(
std::piecewise_construct, std::forward_as_tuple(id),
std::forward_as_tuple(
std::move(filter), std::move(syscall_filter), compact_sched,
@@ -781,6 +872,9 @@
std::move(categories), std::move(categories_sdk_optout),
request.symbolize_ksyms(), request.drain_buffer_percent(),
GetSyscallsReturningFds(syscalls_)));
+ if (inserted) {
+ it->second.kprobes = std::move(kprobes);
+ }
return true;
}
@@ -866,6 +960,11 @@
PERFETTO_DCHECK(event);
if (ftrace_->DisableEvent(event->group, event->name))
current_state_.ftrace_events.DisableEvent(event->ftrace_event_id);
+
+ if (event->group == kKprobeGroup || event->group == kKretprobeGroup) {
+ ftrace_->RemoveKprobeEvent(event->group, event->name);
+ table_->RemoveEvent({event->group, event->name});
+ }
}
auto active_it = active_configs_.find(config_id);
diff --git a/src/traced/probes/ftrace/ftrace_config_muxer.h b/src/traced/probes/ftrace/ftrace_config_muxer.h
index e654eed..ea3ef24 100644
--- a/src/traced/probes/ftrace/ftrace_config_muxer.h
+++ b/src/traced/probes/ftrace/ftrace_config_muxer.h
@@ -21,6 +21,8 @@
#include <optional>
#include <set>
+#include "perfetto/ext/base/flat_hash_map.h"
+#include "protos/perfetto/trace/ftrace/generic.pbzero.h"
#include "src/kernel_utils/syscall_table.h"
#include "src/traced/probes/ftrace/atrace_wrapper.h"
#include "src/traced/probes/ftrace/compact_sched.h"
@@ -31,6 +33,9 @@
namespace perfetto {
+constexpr std::string_view kKprobeGroup = "perfetto_kprobes";
+constexpr std::string_view kKretprobeGroup = "perfetto_kretprobes";
+
namespace protos {
namespace pbzero {
enum FtraceClock : int32_t;
@@ -92,6 +97,9 @@
// List of syscalls monitored to return a new filedescriptor upon success
base::FlatSet<int64_t> syscalls_returning_fd;
+
+ // Keep track of the kprobe type for the given tracefs event id
+ base::FlatHashMap<uint32_t, protos::pbzero::KprobeEvent::KprobeType> kprobes;
};
// Ftrace is a bunch of globally modifiable persistent state.
@@ -221,6 +229,11 @@
std::set<GroupAndName> GetFtraceEvents(const FtraceConfig& request,
const ProtoTranslationTable*);
+ void EnableFtraceEvent(const Event*,
+ const GroupAndName& group_and_name,
+ EventFilter* filter,
+ FtraceSetupErrors* errors);
+
// Returns true if the event filter has at least one event from group.
bool FilterHasGroup(const EventFilter& filter, const std::string& group);
diff --git a/src/traced/probes/ftrace/ftrace_config_muxer_unittest.cc b/src/traced/probes/ftrace/ftrace_config_muxer_unittest.cc
index 53fa696..39c3ea7 100644
--- a/src/traced/probes/ftrace/ftrace_config_muxer_unittest.cc
+++ b/src/traced/probes/ftrace/ftrace_config_muxer_unittest.cc
@@ -20,6 +20,7 @@
#include "ftrace_config_muxer.h"
#include "perfetto/ext/base/utils.h"
+#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
#include "src/traced/probes/ftrace/atrace_wrapper.h"
#include "src/traced/probes/ftrace/compact_sched.h"
#include "src/traced/probes/ftrace/ftrace_procfs.h"
@@ -30,10 +31,12 @@
using testing::_;
using testing::AnyNumber;
using testing::Contains;
+using testing::ElementsAre;
using testing::ElementsAreArray;
using testing::Eq;
using testing::Invoke;
using testing::IsEmpty;
+using testing::IsSupersetOf;
using testing::MatchesRegex;
using testing::NiceMock;
using testing::Not;
@@ -64,6 +67,7 @@
MockFtraceProcfs() : FtraceProcfs("/root/") {
ON_CALL(*this, NumberOfCpus()).WillByDefault(Return(1));
ON_CALL(*this, WriteToFile(_, _)).WillByDefault(Return(true));
+ ON_CALL(*this, AppendToFile(_, _)).WillByDefault(Return(true));
ON_CALL(*this, ClearFile(_)).WillByDefault(Return(true));
EXPECT_CALL(*this, NumberOfCpus()).Times(AnyNumber());
}
@@ -117,6 +121,10 @@
GetOrCreateEvent,
(const GroupAndName& group_and_name),
(override));
+ MOCK_METHOD(Event*,
+ GetOrCreateKprobeEvent,
+ (const GroupAndName& group_and_name),
+ (override));
MOCK_METHOD(const Event*,
GetEvent,
(const GroupAndName& group_and_name),
@@ -1204,6 +1212,28 @@
ASSERT_TRUE(model_.SetupConfig(id, config));
}
+TEST_F(FtraceConfigMuxerFakeTableTest, KprobeNamesReserved) {
+ FtraceConfig config = CreateFtraceConfig(
+ {"perfetto_kprobes/fuse_file_write_iter",
+ "perfetto_kretprobes/fuse_file_write_iter", "unknown/unknown"});
+
+ ON_CALL(ftrace_, ReadFileIntoString("/root/current_tracer"))
+ .WillByDefault(Return("nop"));
+ ON_CALL(ftrace_, ReadFileIntoString("/root/events/enable"))
+ .WillByDefault(Return("0"));
+
+ FtraceSetupErrors errors{};
+ FtraceConfigId id_a = 23;
+ ASSERT_TRUE(model_.SetupConfig(id_a, config, &errors));
+ // These event fail because the names "perfetto_kprobes" and
+ // "perfetto_kretprobes" are used internally by perfetto.
+ EXPECT_THAT(errors.failed_ftrace_events,
+ IsSupersetOf({"perfetto_kprobes/fuse_file_write_iter",
+ "perfetto_kretprobes/fuse_file_write_iter"}));
+ // This event is just unknown
+ EXPECT_THAT(errors.unknown_ftrace_events, ElementsAre("unknown/unknown"));
+}
+
// Fixture that constructs a FtraceConfigMuxer with a mock
// ProtoTranslationTable.
class FtraceConfigMuxerMockTableTest : public FtraceConfigMuxerTest {
@@ -1265,6 +1295,158 @@
ElementsAreArray({kExpectedEventId}));
}
+class FtraceConfigMuxerMockTableParamTest
+ : public FtraceConfigMuxerMockTableTest,
+ public testing::WithParamInterface<
+ std::pair<perfetto::protos::gen::FtraceConfig_KprobeEvent_KprobeType,
+ std::string>> {};
+
+TEST_P(FtraceConfigMuxerMockTableParamTest, AddKprobeEvent) {
+ auto kprobe_type = std::get<0>(GetParam());
+ std::string group_name(std::get<1>(GetParam()));
+
+ FtraceConfig config;
+ FtraceConfig::KprobeEvent kprobe_event;
+
+ kprobe_event.set_probe("fuse_file_write_iter");
+ kprobe_event.set_type(kprobe_type);
+ *config.add_kprobe_events() = kprobe_event;
+
+ EXPECT_CALL(ftrace_, ReadFileIntoString("/root/current_tracer"))
+ .WillOnce(Return("nop"));
+ EXPECT_CALL(ftrace_, ReadOneCharFromFile("/root/tracing_on"))
+ .WillOnce(Return('1'));
+ EXPECT_CALL(ftrace_, WriteToFile("/root/tracing_on", "0"));
+ EXPECT_CALL(ftrace_, WriteToFile("/root/events/enable", "0"));
+ EXPECT_CALL(ftrace_, ClearFile("/root/trace"));
+ EXPECT_CALL(ftrace_, ClearFile(MatchesRegex("/root/per_cpu/cpu[0-9]/trace")));
+ ON_CALL(ftrace_, ReadFileIntoString("/root/trace_clock"))
+ .WillByDefault(Return("[local] global boot"));
+ EXPECT_CALL(ftrace_, ReadFileIntoString("/root/trace_clock"))
+ .Times(AnyNumber());
+ EXPECT_CALL(ftrace_, WriteToFile("/root/buffer_size_kb", _));
+ EXPECT_CALL(ftrace_, WriteToFile("/root/trace_clock", "boot"));
+ EXPECT_CALL(ftrace_, WriteToFile("/root/events/" + group_name +
+ "/fuse_file_write_iter/enable",
+ "1"));
+ EXPECT_CALL(*mock_table_, GetEvent(GroupAndName("power", "cpu_frequency")))
+ .Times(AnyNumber());
+
+ static constexpr int kExpectedEventId = 77;
+ Event event_to_return_kprobe;
+ event_to_return_kprobe.name = "fuse_file_write_iter";
+ event_to_return_kprobe.group = group_name.c_str();
+ event_to_return_kprobe.ftrace_event_id = kExpectedEventId;
+ EXPECT_CALL(*mock_table_, GetOrCreateKprobeEvent(GroupAndName(
+ group_name, "fuse_file_write_iter")))
+ .WillOnce(Return(&event_to_return_kprobe));
+
+ FtraceConfigId id = 7;
+ ASSERT_TRUE(model_.SetupConfig(id, config));
+
+ EXPECT_CALL(ftrace_, WriteToFile("/root/tracing_on", "1"));
+ ASSERT_TRUE(model_.ActivateConfig(id));
+
+ const FtraceDataSourceConfig* ds_config = model_.GetDataSourceConfig(id);
+ ASSERT_TRUE(ds_config);
+ ASSERT_THAT(ds_config->event_filter.GetEnabledEvents(),
+ ElementsAre(kExpectedEventId));
+
+ const EventFilter* central_filter = model_.GetCentralEventFilterForTesting();
+ ASSERT_THAT(central_filter->GetEnabledEvents(),
+ ElementsAre(kExpectedEventId));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ KprobeTypes,
+ FtraceConfigMuxerMockTableParamTest,
+ testing::Values(
+ std::make_pair(
+ protos::gen::FtraceConfig::KprobeEvent::KPROBE_TYPE_KPROBE,
+ kKprobeGroup),
+ std::make_pair(
+ protos::gen::FtraceConfig::KprobeEvent::KPROBE_TYPE_KRETPROBE,
+ kKretprobeGroup)));
+
+TEST_F(FtraceConfigMuxerMockTableTest, AddKprobeBothEvent) {
+ FtraceConfig config;
+ FtraceConfig::KprobeEvent kprobe_event;
+
+ kprobe_event.set_probe("fuse_file_write_iter");
+ kprobe_event.set_type(
+ protos::gen::FtraceConfig::KprobeEvent::KPROBE_TYPE_BOTH);
+ *config.add_kprobe_events() = kprobe_event;
+
+ EXPECT_CALL(ftrace_, ReadFileIntoString("/root/current_tracer"))
+ .WillOnce(Return("nop"));
+ EXPECT_CALL(ftrace_, ReadOneCharFromFile("/root/tracing_on"))
+ .WillOnce(Return('1'));
+ EXPECT_CALL(ftrace_, WriteToFile("/root/tracing_on", "0"));
+ EXPECT_CALL(ftrace_, WriteToFile("/root/events/enable", "0"));
+ EXPECT_CALL(ftrace_, ClearFile("/root/trace"));
+ EXPECT_CALL(ftrace_, ClearFile(MatchesRegex("/root/per_cpu/cpu[0-9]/trace")));
+ ON_CALL(ftrace_, ReadFileIntoString("/root/trace_clock"))
+ .WillByDefault(Return("[local] global boot"));
+ EXPECT_CALL(ftrace_, ReadFileIntoString("/root/trace_clock"))
+ .Times(AnyNumber());
+ EXPECT_CALL(ftrace_, WriteToFile("/root/buffer_size_kb", _));
+ EXPECT_CALL(ftrace_, WriteToFile("/root/trace_clock", "boot"));
+ EXPECT_CALL(
+ ftrace_,
+ WriteToFile("/root/events/perfetto_kprobes/fuse_file_write_iter/enable",
+ "1"));
+ EXPECT_CALL(
+ ftrace_,
+ WriteToFile(
+ "/root/events/perfetto_kretprobes/fuse_file_write_iter/enable", "1"));
+ EXPECT_CALL(
+ ftrace_,
+ AppendToFile(
+ "/root/kprobe_events",
+ "p:perfetto_kprobes/fuse_file_write_iter fuse_file_write_iter"));
+ EXPECT_CALL(
+ ftrace_,
+ AppendToFile("/root/kprobe_events",
+ std::string("r") + std::string(kKretprobeDefaultMaxactives) +
+ ":perfetto_kretprobes/fuse_file_write_iter "
+ "fuse_file_write_iter"));
+
+ std::string g1(kKprobeGroup);
+ static constexpr int kExpectedEventId = 77;
+ Event event_to_return_kprobe;
+ event_to_return_kprobe.name = "fuse_file_write_iter";
+ event_to_return_kprobe.group = g1.c_str();
+ event_to_return_kprobe.ftrace_event_id = kExpectedEventId;
+ EXPECT_CALL(*mock_table_, GetOrCreateKprobeEvent(GroupAndName(
+ "perfetto_kprobes", "fuse_file_write_iter")))
+ .WillOnce(Return(&event_to_return_kprobe));
+
+ std::string g2(kKretprobeGroup);
+ static constexpr int kExpectedEventId2 = 78;
+ Event event_to_return_kretprobe;
+ event_to_return_kretprobe.name = "fuse_file_write_iter";
+ event_to_return_kretprobe.group = g2.c_str();
+ event_to_return_kretprobe.ftrace_event_id = kExpectedEventId2;
+ EXPECT_CALL(*mock_table_, GetOrCreateKprobeEvent(GroupAndName(
+ "perfetto_kretprobes", "fuse_file_write_iter")))
+ .WillOnce(Return(&event_to_return_kretprobe));
+
+ FtraceConfigId id = 7;
+ ASSERT_TRUE(model_.SetupConfig(id, config));
+
+ EXPECT_CALL(ftrace_, WriteToFile("/root/tracing_on", "1"));
+ ASSERT_TRUE(model_.ActivateConfig(id));
+
+ const FtraceDataSourceConfig* ds_config = model_.GetDataSourceConfig(id);
+ ASSERT_TRUE(ds_config);
+ ASSERT_THAT(ds_config->event_filter.GetEnabledEvents(),
+ UnorderedElementsAre(kExpectedEventId, kExpectedEventId2));
+
+ const EventFilter* central_filter = model_.GetCentralEventFilterForTesting();
+ ASSERT_THAT(central_filter->GetEnabledEvents(),
+ UnorderedElementsAre(kExpectedEventId, kExpectedEventId2));
+}
+
TEST_F(FtraceConfigMuxerMockTableTest, AddAllEvents) {
FtraceConfig config = CreateFtraceConfig({"sched/*"});
diff --git a/src/traced/probes/ftrace/ftrace_controller.cc b/src/traced/probes/ftrace/ftrace_controller.cc
index a09f13d..837a5e1 100644
--- a/src/traced/probes/ftrace/ftrace_controller.cc
+++ b/src/traced/probes/ftrace/ftrace_controller.cc
@@ -39,6 +39,7 @@
#include "perfetto/ext/base/file_utils.h"
#include "perfetto/ext/base/metatrace.h"
#include "perfetto/ext/base/scoped_file.h"
+#include "perfetto/ext/base/string_splitter.h"
#include "perfetto/ext/base/string_utils.h"
#include "perfetto/ext/tracing/core/trace_writer.h"
#include "src/kallsyms/kernel_symbol_map.h"
@@ -630,6 +631,33 @@
StopIfNeeded(instance);
}
+bool DumpKprobeStats(const std::string& text, FtraceStats* ftrace_stats) {
+ int64_t hits = 0;
+ int64_t misses = 0;
+
+ base::StringSplitter line(std::move(text), '\n');
+ while (line.Next()) {
+ base::StringSplitter tok(line.cur_token(), line.cur_token_size() + 1, ' ');
+
+ if (!tok.Next())
+ return false;
+ // Skip the event name field
+
+ if (!tok.Next())
+ return false;
+ hits += static_cast<int64_t>(std::strtoll(tok.cur_token(), nullptr, 10));
+
+ if (!tok.Next())
+ return false;
+ misses += static_cast<int64_t>(std::strtoll(tok.cur_token(), nullptr, 10));
+ }
+
+ ftrace_stats->kprobe_stats.hits = hits;
+ ftrace_stats->kprobe_stats.misses = misses;
+
+ return true;
+}
+
void FtraceController::DumpFtraceStats(FtraceDataSource* data_source,
FtraceStats* stats_out) {
FtraceInstanceState* instance =
@@ -646,6 +674,11 @@
stats_out->kernel_symbols_mem_kb =
static_cast<uint32_t>(symbol_map->size_bytes() / 1024);
}
+
+ if (data_source->parsing_config()->kprobes.size() > 0) {
+ DumpKprobeStats(instance->ftrace_procfs.get()->ReadKprobeStats(),
+ stats_out);
+ }
}
void FtraceController::MaybeSnapshotFtraceClock() {
diff --git a/src/traced/probes/ftrace/ftrace_controller.h b/src/traced/probes/ftrace/ftrace_controller.h
index a858dfe..41bb59c 100644
--- a/src/traced/probes/ftrace/ftrace_controller.h
+++ b/src/traced/probes/ftrace/ftrace_controller.h
@@ -189,6 +189,8 @@
base::WeakPtrFactory<FtraceController> weak_factory_; // Keep last.
};
+bool DumpKprobeStats(const std::string& text, FtraceStats* ftrace_stats);
+
} // namespace perfetto
#endif // SRC_TRACED_PROBES_FTRACE_FTRACE_CONTROLLER_H_
diff --git a/src/traced/probes/ftrace/ftrace_controller_unittest.cc b/src/traced/probes/ftrace/ftrace_controller_unittest.cc
index 723df01..549f81a 100644
--- a/src/traced/probes/ftrace/ftrace_controller_unittest.cc
+++ b/src/traced/probes/ftrace/ftrace_controller_unittest.cc
@@ -656,6 +656,72 @@
EXPECT_EQ(result.cpu(), 0u);
EXPECT_EQ(result.entries(), 1u);
EXPECT_EQ(result.overrun(), 2u);
+ auto kprobe_stats = result_packet.ftrace_stats().kprobe_stats();
+ EXPECT_EQ(kprobe_stats.hits(), 0u);
+ EXPECT_EQ(kprobe_stats.misses(), 0u);
+}
+
+TEST(FtraceStatsTest, WriteKprobeStats) {
+ FtraceStats stats{};
+ FtraceKprobeStats kprobe_stats{};
+ kprobe_stats.hits = 1;
+ kprobe_stats.misses = 2;
+ stats.kprobe_stats = kprobe_stats;
+
+ std::unique_ptr<TraceWriterForTesting> writer =
+ std::unique_ptr<TraceWriterForTesting>(new TraceWriterForTesting());
+ {
+ auto packet = writer->NewTracePacket();
+ auto* out = packet->set_ftrace_stats();
+ stats.Write(out);
+ }
+
+ protos::gen::TracePacket result_packet = writer->GetOnlyTracePacket();
+ auto result = result_packet.ftrace_stats();
+ EXPECT_EQ(result.kprobe_stats().hits(), 1u);
+ EXPECT_EQ(result.kprobe_stats().misses(), 2u);
+}
+
+TEST(FtraceStatsTest, KprobeProfileParseEmpty) {
+ std::string text = "";
+
+ FtraceStats stats{};
+ EXPECT_TRUE(DumpKprobeStats(text, &stats));
+}
+
+TEST(FtraceStatsTest, KprobeProfileParseEmptyLines) {
+ std::string text = R"(
+
+)";
+
+ FtraceStats stats{};
+ EXPECT_TRUE(DumpKprobeStats(text, &stats));
+}
+
+TEST(FtraceStatsTest, KprobeProfileParseValid) {
+ std::string text = R"( _binder_inner_proc_lock 1 8
+ _binder_inner_proc_unlock 2 9
+ _binder_node_inner_unlock 3 10
+ _binder_node_unlock 4 11
+)";
+
+ FtraceStats stats{};
+ EXPECT_TRUE(DumpKprobeStats(text, &stats));
+
+ EXPECT_EQ(stats.kprobe_stats.hits, 10u);
+ EXPECT_EQ(stats.kprobe_stats.misses, 38u);
+}
+
+TEST(FtraceStatsTest, KprobeProfileMissingValuesParseInvalid) {
+ std::string text = R"( _binder_inner_proc_lock 1 8
+ _binder_inner_proc_unlock 2
+)";
+
+ FtraceStats stats{};
+ EXPECT_FALSE(DumpKprobeStats(text, &stats));
+
+ EXPECT_EQ(stats.kprobe_stats.hits, 0u);
+ EXPECT_EQ(stats.kprobe_stats.misses, 0u);
}
TEST(FtraceControllerTest, OnlySecondaryInstance) {
diff --git a/src/traced/probes/ftrace/ftrace_procfs.cc b/src/traced/probes/ftrace/ftrace_procfs.cc
index b7adf48..fd2c948 100644
--- a/src/traced/probes/ftrace/ftrace_procfs.cc
+++ b/src/traced/probes/ftrace/ftrace_procfs.cc
@@ -144,6 +144,46 @@
return AppendToFile(path, group + ":" + name);
}
+bool FtraceProcfs::CreateKprobeEvent(const std::string& group,
+ const std::string& name,
+ bool is_retprobe) {
+ std::string path = root_ + "kprobe_events";
+ std::string probe =
+ (is_retprobe ? std::string("r") + std::string(kKretprobeDefaultMaxactives)
+ : "p") +
+ std::string(":") + group + "/" + name + " " + name;
+
+ PERFETTO_DLOG("Writing \"%s >> %s\"", probe.c_str(), path.c_str());
+
+ bool ret = AppendToFile(path, probe);
+ if (!ret) {
+ if (errno == EEXIST) {
+ // The kprobe event defined by group/name already exists.
+ // TODO maybe because the /sys/kernel/tracing/kprobe_events file has not
+ // been properly cleaned up after tracing
+ PERFETTO_DLOG("Kprobe event %s::%s already exists", group.c_str(),
+ name.c_str());
+ return true;
+ }
+ PERFETTO_PLOG("Failed writing '%s' to '%s'", probe.c_str(), path.c_str());
+ }
+
+ return ret;
+}
+
+// Utility function to remove kprobe event from the system
+bool FtraceProcfs::RemoveKprobeEvent(const std::string& group,
+ const std::string& name) {
+ PERFETTO_DLOG("RemoveKprobeEvent %s::%s", group.c_str(), name.c_str());
+ std::string path = root_ + "kprobe_events";
+ return AppendToFile(path, "-:" + group + "/" + name);
+}
+
+std::string FtraceProcfs::ReadKprobeStats() const {
+ std::string path = root_ + "/kprobe_profile";
+ return ReadFileIntoString(path);
+}
+
bool FtraceProcfs::DisableEvent(const std::string& group,
const std::string& name) {
std::string path = root_ + "events/" + group + "/" + name + "/enable";
diff --git a/src/traced/probes/ftrace/ftrace_procfs.h b/src/traced/probes/ftrace/ftrace_procfs.h
index 42b1f83..2a0593f 100644
--- a/src/traced/probes/ftrace/ftrace_procfs.h
+++ b/src/traced/probes/ftrace/ftrace_procfs.h
@@ -26,6 +26,8 @@
namespace perfetto {
+constexpr std::string_view kKretprobeDefaultMaxactives = "1024";
+
class FtraceProcfs {
public:
static const char* const kTracingPaths[];
@@ -49,6 +51,19 @@
// Enable the event under with the given |group| and |name|.
bool EnableEvent(const std::string& group, const std::string& name);
+ // Create the kprobe event for the function |name|. The event will be in
+ // |group|/|name|. Depending on the value of |is_retprobe|, installs a kprobe
+ // or a kretprobe.
+ bool CreateKprobeEvent(const std::string& group,
+ const std::string& name,
+ bool is_retprobe);
+
+ // Remove kprobe event from the system
+ bool RemoveKprobeEvent(const std::string& group, const std::string& name);
+
+ // Read the "kprobe_profile" file.
+ std::string ReadKprobeStats() const;
+
// Disable the event under with the given |group| and |name|.
bool DisableEvent(const std::string& group, const std::string& name);
diff --git a/src/traced/probes/ftrace/ftrace_stats.cc b/src/traced/probes/ftrace/ftrace_stats.cc
index 01c5890..1f7b053 100644
--- a/src/traced/probes/ftrace/ftrace_stats.cc
+++ b/src/traced/probes/ftrace/ftrace_stats.cc
@@ -32,6 +32,12 @@
writer->add_unknown_ftrace_events(err);
for (const std::string& err : setup_errors.failed_ftrace_events)
writer->add_failed_ftrace_events(err);
+
+ if (kprobe_stats.hits || kprobe_stats.misses) {
+ auto* kprobe_stats_pb = writer->set_kprobe_stats();
+ kprobe_stats_pb->set_hits(kprobe_stats.hits);
+ kprobe_stats_pb->set_misses(kprobe_stats.misses);
+ }
}
void FtraceCpuStats::Write(protos::pbzero::FtraceCpuStats* writer) const {
diff --git a/src/traced/probes/ftrace/ftrace_stats.h b/src/traced/probes/ftrace/ftrace_stats.h
index 127b4f4..ccabe4a 100644
--- a/src/traced/probes/ftrace/ftrace_stats.h
+++ b/src/traced/probes/ftrace/ftrace_stats.h
@@ -27,6 +27,7 @@
namespace pbzero {
class FtraceStats;
class FtraceCpuStats;
+class FtraceKprobeStats;
} // namespace pbzero
} // namespace protos
@@ -44,6 +45,11 @@
void Write(protos::pbzero::FtraceCpuStats*) const;
};
+struct FtraceKprobeStats {
+ int64_t hits;
+ int64_t misses;
+};
+
struct FtraceSetupErrors {
std::string atrace_errors;
std::vector<std::string> unknown_ftrace_events;
@@ -55,6 +61,7 @@
FtraceSetupErrors setup_errors;
uint32_t kernel_symbols_parsed = 0;
uint32_t kernel_symbols_mem_kb = 0;
+ FtraceKprobeStats kprobe_stats = {};
void Write(protos::pbzero::FtraceStats*) const;
};
diff --git a/src/traced/probes/ftrace/proto_translation_table.cc b/src/traced/probes/ftrace/proto_translation_table.cc
index 7e9092e..ef3b557 100644
--- a/src/traced/probes/ftrace/proto_translation_table.cc
+++ b/src/traced/probes/ftrace/proto_translation_table.cc
@@ -540,11 +540,9 @@
}
}
-const Event* ProtoTranslationTable::GetOrCreateEvent(
- const GroupAndName& group_and_name) {
- const Event* event = GetEvent(group_and_name);
- if (event)
- return event;
+const Event* ProtoTranslationTable::CreateEventWithProtoId(
+ const GroupAndName& group_and_name,
+ uint32_t proto_field_id) {
// The ftrace event does not already exist so a new one will be created
// by parsing the format file.
std::string contents = ftrace_procfs_->ReadEventFormat(group_and_name.group(),
@@ -563,7 +561,7 @@
// Set known event variables
Event* e = &events_.at(ftrace_event.id);
e->ftrace_event_id = ftrace_event.id;
- e->proto_field_id = protos::pbzero::FtraceEvent::kGenericFieldNumber;
+ e->proto_field_id = proto_field_id;
e->name = InternString(group_and_name.name());
e->group = InternString(group_and_name.group());
@@ -584,6 +582,57 @@
return e;
}
+const Event* ProtoTranslationTable::GetOrCreateEvent(
+ const GroupAndName& group_and_name) {
+ const Event* event = GetEvent(group_and_name);
+ if (event)
+ return event;
+ return CreateEventWithProtoId(
+ group_and_name, protos::pbzero::FtraceEvent::kGenericFieldNumber);
+}
+
+const Event* ProtoTranslationTable::GetOrCreateKprobeEvent(
+ const GroupAndName& group_and_name) {
+ const Event* event = GetEvent(group_and_name);
+ const uint32_t proto_field_id =
+ protos::pbzero::FtraceEvent::kKprobeEventFieldNumber;
+ if (event) {
+ if (event->proto_field_id != proto_field_id) {
+ return nullptr;
+ }
+ return event;
+ }
+ return CreateEventWithProtoId(group_and_name, proto_field_id);
+}
+
+void ProtoTranslationTable::RemoveEvent(const GroupAndName& group_and_name) {
+ const std::string& group = group_and_name.group();
+ const std::string& name = group_and_name.name();
+ auto it = group_and_name_to_event_.find(group_and_name);
+ if (it == group_and_name_to_event_.end()) {
+ return;
+ }
+ Event* event = &events_[it->second->ftrace_event_id];
+ event->ftrace_event_id = 0;
+ if (auto it2 = name_to_events_.find(name); it2 != name_to_events_.end()) {
+ std::vector<const Event*>& events = it2->second;
+ events.erase(std::remove(events.begin(), events.end(), event),
+ events.end());
+ if (events.empty()) {
+ name_to_events_.erase(it2);
+ }
+ }
+ if (auto it2 = group_to_events_.find(group); it2 != group_to_events_.end()) {
+ std::vector<const Event*>& events = it2->second;
+ events.erase(std::remove(events.begin(), events.end(), event),
+ events.end());
+ if (events.empty()) {
+ group_to_events_.erase(it2);
+ }
+ }
+ group_and_name_to_event_.erase(it);
+}
+
const char* ProtoTranslationTable::InternString(const std::string& str) {
auto it_and_inserted = interned_strings_.insert(str);
return it_and_inserted.first->c_str();
diff --git a/src/traced/probes/ftrace/proto_translation_table.h b/src/traced/probes/ftrace/proto_translation_table.h
index 7b71380..482525d 100644
--- a/src/traced/probes/ftrace/proto_translation_table.h
+++ b/src/traced/probes/ftrace/proto_translation_table.h
@@ -48,7 +48,7 @@
// ftrace event.
class GroupAndName {
public:
- GroupAndName(const std::string& group, const std::string& name)
+ GroupAndName(std::string_view group, std::string_view name)
: group_(group), name_(name) {}
bool operator==(const GroupAndName& other) const {
@@ -162,6 +162,14 @@
// new event with the proto id set to generic. Virtual for testing.
virtual const Event* GetOrCreateEvent(const GroupAndName&);
+ // Retrieves the ftrace event, that's going to be translated to a kprobe, from
+ // the proto translation table. If the event is already known and used for
+ // something other than a kprobe, returns nullptr.
+ virtual const Event* GetOrCreateKprobeEvent(const GroupAndName&);
+
+ // Removes the ftrace event from the proto translation table.
+ virtual void RemoveEvent(const GroupAndName&);
+
// This is for backwards compatibility. If a group is not specified in the
// config then the first event with that name will be returned.
const Event* GetEventByName(const std::string& name) const {
@@ -185,6 +193,10 @@
// Store strings so they can be read when writing the trace output.
const char* InternString(const std::string& str);
+ // The event must not already exist.
+ const Event* CreateEventWithProtoId(const GroupAndName& group_and_name,
+ uint32_t proto_field_id);
+
uint16_t CreateGenericEventField(const FtraceEvent::Field& ftrace_field,
Event& event);
diff --git a/src/traced/probes/ftrace/proto_translation_table_unittest.cc b/src/traced/probes/ftrace/proto_translation_table_unittest.cc
index 40df967..1ea829c 100644
--- a/src/traced/probes/ftrace/proto_translation_table_unittest.cc
+++ b/src/traced/probes/ftrace/proto_translation_table_unittest.cc
@@ -16,7 +16,6 @@
#include "src/traced/probes/ftrace/proto_translation_table.h"
-#include "src/base/test/gtest_test_suite.h"
#include "src/base/test/utils.h"
#include "src/traced/probes/ftrace/compact_sched.h"
#include "src/traced/probes/ftrace/event_info.h"
@@ -30,8 +29,10 @@
using testing::AllOf;
using testing::AnyNumber;
using testing::Contains;
+using testing::ElementsAre;
using testing::Eq;
using testing::IsNull;
+using testing::NiceMock;
using testing::Pointee;
using testing::Return;
using testing::StrEq;
@@ -601,5 +602,70 @@
}
}
+TEST(TranslationTableTest, CreateRemoveKprobeEvent) {
+ NiceMock<MockFtraceProcfs> ftrace;
+ ON_CALL(ftrace, ReadEventFormat(_, _)).WillByDefault(Return(""));
+ ON_CALL(ftrace, ReadPageHeaderFormat())
+ .WillByDefault(Return(
+ R"( field: u64 timestamp; offset:0; size:8; signed:0;
+ field: local_t commit; offset:8; size:4; signed:1;
+ field: int overwrite; offset:8; size:1; signed:1;
+ field: char data; offset:16; size:4080; signed:0;)"));
+ auto table = ProtoTranslationTable::Create(&ftrace, GetStaticEventInfo(),
+ GetStaticCommonFieldsInfo());
+ PERFETTO_CHECK(table);
+
+ EXPECT_CALL(ftrace,
+ ReadEventFormat("perfetto_kprobe", "fuse_file_write_iter"))
+ .WillOnce(Return(R"format(name: fuse_file_write_iter
+ID: 1535
+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:unsigned long __probe_ip; offset:8; size:8; signed:0;
+
+print fmt: "(%lx)", REC->__probe_ip
+)format"));
+ const Event* event = table->GetOrCreateKprobeEvent(
+ {"perfetto_kprobe", "fuse_file_write_iter"});
+ ASSERT_NE(event, nullptr);
+ EXPECT_EQ(event->ftrace_event_id, 1535u);
+ EXPECT_EQ(table->GetEventByName("fuse_file_write_iter"), event);
+ EXPECT_THAT(table->GetEventsByGroup("perfetto_kprobe"),
+ Pointee(ElementsAre(event)));
+ EXPECT_EQ(table->GetEventById(1535), event);
+
+ table->RemoveEvent({"perfetto_kprobe", "fuse_file_write_iter"});
+ EXPECT_EQ(table->GetEventByName("fuse_file_write_iter"), nullptr);
+ EXPECT_EQ(table->GetEventsByGroup("perfetto_kprobe"), nullptr);
+ EXPECT_EQ(table->GetEventById(1535), nullptr);
+
+ EXPECT_CALL(ftrace,
+ ReadEventFormat("perfetto_kprobe", "fuse_file_write_iter"))
+ .WillOnce(Return(R"format(name: fuse_file_write_iter
+ID: 1536
+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:unsigned long __probe_ip; offset:8; size:8; signed:0;
+
+print fmt: "(%lx)", REC->__probe_ip
+)format"));
+ event = table->GetOrCreateKprobeEvent(
+ {"perfetto_kprobe", "fuse_file_write_iter"});
+ ASSERT_NE(event, nullptr);
+ EXPECT_EQ(event->ftrace_event_id, 1536u);
+ EXPECT_EQ(table->GetEventByName("fuse_file_write_iter"), event);
+ EXPECT_THAT(table->GetEventsByGroup("perfetto_kprobe"),
+ Pointee(ElementsAre(event)));
+ EXPECT_EQ(table->GetEventById(1536), event);
+}
+
} // namespace
} // namespace perfetto
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/src/traced/probes/ftrace/test/data/synthetic/events/devfreq/devfreq_frequency/format b/src/traced/probes/ftrace/test/data/synthetic/events/devfreq/devfreq_frequency/format
new file mode 100644
index 0000000..1b021f2
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/synthetic/events/devfreq/devfreq_frequency/format
@@ -0,0 +1,15 @@
+name: devfreq_frequency
+ID: 898
+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[] dev_name; offset:8; size:4; signed:0;
+ field:unsigned long freq; offset:16; size:8; signed:0;
+ field:unsigned long prev_freq; offset:24; size:8; signed:0;
+ field:unsigned long busy_time; offset:32; size:8; signed:0;
+ field:unsigned long total_time; offset:40; size:8; signed:0;
+
+print fmt: "dev_name=%-30s freq=%-12lu prev_freq=%-12lu load=%-2lu", __get_str(dev_name), REC->freq, REC->prev_freq, REC->total_time == 0 ? 0 : (100 * REC->busy_time) / REC->total_time
diff --git a/src/traced/probes/probes_producer.cc b/src/traced/probes/probes_producer.cc
index ab75b35..7b95f09 100644
--- a/src/traced/probes/probes_producer.cc
+++ b/src/traced/probes/probes_producer.cc
@@ -661,17 +661,8 @@
}
void ProbesProducer::ActivateTrigger(std::string trigger) {
- android_stats::MaybeLogTriggerEvent(
- PerfettoTriggerAtom::kProbesProducerTrigger, trigger);
-
- task_runner_->PostTask([this, trigger]() {
- if (!endpoint_) {
- android_stats::MaybeLogTriggerEvent(
- PerfettoTriggerAtom::kProbesProducerTriggerFail, trigger);
- return;
- }
- endpoint_->ActivateTriggers({trigger});
- });
+ task_runner_->PostTask(
+ [this, trigger]() { endpoint_->ActivateTriggers({trigger}); });
}
} // namespace perfetto
diff --git a/src/traced_relay/BUILD.gn b/src/traced_relay/BUILD.gn
index 6799e34..346c17f 100644
--- a/src/traced_relay/BUILD.gn
+++ b/src/traced_relay/BUILD.gn
@@ -22,6 +22,7 @@
"../../gn:default_deps",
"../../include/perfetto/ext/traced",
"../base",
+ "../base:clock_snapshots",
"../base:unix_socket",
"../base:version",
"../ipc:perfetto_ipc",
diff --git a/src/traced_relay/relay_service.cc b/src/traced_relay/relay_service.cc
index 3eb4214..2954cfd 100644
--- a/src/traced_relay/relay_service.cc
+++ b/src/traced_relay/relay_service.cc
@@ -22,13 +22,13 @@
#include "perfetto/base/build_config.h"
#include "perfetto/base/logging.h"
#include "perfetto/base/task_runner.h"
+#include "perfetto/ext/base/clock_snapshots.h"
#include "perfetto/ext/base/file_utils.h"
#include "perfetto/ext/base/hash.h"
#include "perfetto/ext/base/string_utils.h"
#include "perfetto/ext/base/unix_socket.h"
#include "perfetto/ext/base/utils.h"
#include "perfetto/ext/ipc/client.h"
-#include "perfetto/tracing/core/clock_snapshots.h"
#include "perfetto/tracing/core/forward_decls.h"
#include "protos/perfetto/ipc/wire_protocol.gen.h"
#include "src/ipc/buffered_frame_deserializer.h"
@@ -143,7 +143,7 @@
break;
}
- ClockSnapshotVector snapshot_data = CaptureClockSnapshots();
+ base::ClockSnapshotVector snapshot_data = base::CaptureClockSnapshots();
for (auto& clock : snapshot_data) {
auto* clock_proto = request.add_clocks();
clock_proto->set_clock_id(clock.clock_id);
diff --git a/src/tracing/core/BUILD.gn b/src/tracing/core/BUILD.gn
index 1441c97..53deb50 100644
--- a/src/tracing/core/BUILD.gn
+++ b/src/tracing/core/BUILD.gn
@@ -29,7 +29,6 @@
"../../base",
]
sources = [
- "clock_snapshots.cc",
"id_allocator.cc",
"id_allocator.h",
"in_process_shared_memory.cc",
diff --git a/src/tracing/core/shared_memory_abi_unittest.cc b/src/tracing/core/shared_memory_abi_unittest.cc
index d3e1754..5e39a93 100644
--- a/src/tracing/core/shared_memory_abi_unittest.cc
+++ b/src/tracing/core/shared_memory_abi_unittest.cc
@@ -17,7 +17,6 @@
#include "perfetto/ext/tracing/core/shared_memory_abi.h"
#include "perfetto/ext/tracing/core/basic_types.h"
-#include "src/base/test/gtest_test_suite.h"
#include "src/tracing/test/aligned_buffer_test.h"
#include "test/gtest_and_gmock.h"
diff --git a/src/tracing/core/shared_memory_arbiter_impl.cc b/src/tracing/core/shared_memory_arbiter_impl.cc
index d9d774e..d611dfd 100644
--- a/src/tracing/core/shared_memory_arbiter_impl.cc
+++ b/src/tracing/core/shared_memory_arbiter_impl.cc
@@ -861,6 +861,7 @@
void SharedMemoryArbiterImpl::ReleaseWriterID(WriterID id) {
base::TaskRunner* task_runner = nullptr;
+ base::WeakPtr<SharedMemoryArbiterImpl> weak_this;
{
std::lock_guard<std::mutex> scoped_lock(lock_);
active_writer_ids_.Free(id);
@@ -879,12 +880,15 @@
if (!task_runner_)
return;
+ // If `active_writer_ids_` is empty, `TryShutdown()` can return true
+ // and `*this` can be deleted. Let's grab everything we need from `*this`
+ // before releasing the lock.
+ weak_this = weak_ptr_factory_.GetWeakPtr();
task_runner = task_runner_;
} // scoped_lock
// We shouldn't post tasks while locked. |task_runner| remains valid after
// unlocking, because |task_runner_| is never reset.
- auto weak_this = weak_ptr_factory_.GetWeakPtr();
task_runner->PostTask([weak_this, id] {
if (weak_this)
weak_this->producer_endpoint_->UnregisterTraceWriter(id);
diff --git a/src/tracing/core/shared_memory_arbiter_impl_unittest.cc b/src/tracing/core/shared_memory_arbiter_impl_unittest.cc
index 8f7184c..c269897 100644
--- a/src/tracing/core/shared_memory_arbiter_impl_unittest.cc
+++ b/src/tracing/core/shared_memory_arbiter_impl_unittest.cc
@@ -24,7 +24,6 @@
#include "perfetto/ext/tracing/core/trace_packet.h"
#include "perfetto/ext/tracing/core/trace_writer.h"
#include "perfetto/ext/tracing/core/tracing_service.h"
-#include "src/base/test/gtest_test_suite.h"
#include "src/base/test/test_task_runner.h"
#include "src/tracing/core/in_process_shared_memory.h"
#include "src/tracing/core/patch_list.h"
diff --git a/src/tracing/core/trace_writer_impl.h b/src/tracing/core/trace_writer_impl.h
index d72f15b..3ee4d01 100644
--- a/src/tracing/core/trace_writer_impl.h
+++ b/src/tracing/core/trace_writer_impl.h
@@ -67,10 +67,6 @@
return protobuf_stream_writer_.written();
}
- void ResetChunkForTesting() {
- cur_chunk_ = SharedMemoryABI::Chunk();
- cur_chunk_packet_count_inflated_ = false;
- }
bool drop_packets_for_testing() const { return drop_packets_; }
private:
diff --git a/src/tracing/core/trace_writer_impl_unittest.cc b/src/tracing/core/trace_writer_impl_unittest.cc
index b345c55..f0a14e6 100644
--- a/src/tracing/core/trace_writer_impl_unittest.cc
+++ b/src/tracing/core/trace_writer_impl_unittest.cc
@@ -26,7 +26,6 @@
#include "perfetto/protozero/message.h"
#include "perfetto/protozero/proto_utils.h"
#include "perfetto/protozero/scattered_stream_writer.h"
-#include "src/base/test/gtest_test_suite.h"
#include "src/base/test/test_task_runner.h"
#include "src/tracing/core/shared_memory_arbiter_impl.h"
#include "src/tracing/test/aligned_buffer_test.h"
diff --git a/src/tracing/internal/tracing_backend_fake.cc b/src/tracing/internal/tracing_backend_fake.cc
index ff896a2..226a558 100644
--- a/src/tracing/internal/tracing_backend_fake.cc
+++ b/src/tracing/internal/tracing_backend_fake.cc
@@ -134,7 +134,7 @@
void QueryCapabilities(QueryCapabilitiesCallback) override {}
void SaveTraceForBugreport(SaveTraceForBugreportCallback) override {}
- void CloneSession(TracingSessionID, CloneSessionArgs) override {}
+ void CloneSession(CloneSessionArgs) override {}
private:
Consumer* const consumer_;
diff --git a/src/tracing/internal/tracing_muxer_impl.cc b/src/tracing/internal/tracing_muxer_impl.cc
index 04832f1..03771b0 100644
--- a/src/tracing/internal/tracing_muxer_impl.cc
+++ b/src/tracing/internal/tracing_muxer_impl.cc
@@ -442,6 +442,11 @@
query_service_state_callback_ = nullptr;
muxer_->QueryServiceState(session_id_, std::move(callback));
}
+ if (session_to_clone_) {
+ service_->CloneSession(*session_to_clone_);
+ session_to_clone_ = std::nullopt;
+ }
+
if (stop_pending_)
muxer_->StopTracingSession(session_id_);
}
@@ -613,9 +618,17 @@
}
void TracingMuxerImpl::ConsumerImpl::OnSessionCloned(
- const OnSessionClonedArgs&) {
- // CloneSession is not exposed in the SDK. This should never happen.
- PERFETTO_DCHECK(false);
+ const OnSessionClonedArgs& args) {
+ if (!clone_trace_callback_)
+ return;
+ TracingSession::CloneTraceCallbackArgs callback_arg{};
+ callback_arg.success = args.success;
+ callback_arg.error = std::move(args.error);
+ callback_arg.uuid_msb = args.uuid.msb();
+ callback_arg.uuid_lsb = args.uuid.lsb();
+ muxer_->task_runner_->PostTask(
+ std::bind(std::move(clone_trace_callback_), std::move(callback_arg)));
+ clone_trace_callback_ = nullptr;
}
void TracingMuxerImpl::ConsumerImpl::OnTraceStats(
@@ -688,6 +701,15 @@
[muxer, session_id] { muxer->StartTracingSession(session_id); });
}
+void TracingMuxerImpl::TracingSessionImpl::CloneTrace(CloneTraceArgs args,
+ CloneTraceCallback cb) {
+ auto* muxer = muxer_;
+ auto session_id = session_id_;
+ muxer->task_runner_->PostTask([muxer, session_id, args, cb] {
+ muxer->CloneTracingSession(session_id, args, std::move(cb));
+ });
+}
+
// Can be called from any thread.
void TracingMuxerImpl::TracingSessionImpl::ChangeTraceConfig(
const TraceConfig& cfg) {
@@ -1927,6 +1949,32 @@
// TODO implement support for the deferred-start + fast-triggering case.
}
+void TracingMuxerImpl::CloneTracingSession(
+ TracingSessionGlobalID session_id,
+ TracingSession::CloneTraceArgs args,
+ TracingSession::CloneTraceCallback callback) {
+ PERFETTO_DCHECK_THREAD(thread_checker_);
+ auto* consumer = FindConsumer(session_id);
+ if (!consumer) {
+ TracingSession::CloneTraceCallbackArgs callback_arg{};
+ callback_arg.success = false;
+ callback_arg.error = "Tracing session not found";
+ callback(callback_arg);
+ return;
+ }
+ // Multiple concurrent cloning isn't supported.
+ PERFETTO_DCHECK(!consumer->clone_trace_callback_);
+ consumer->clone_trace_callback_ = std::move(callback);
+ ConsumerEndpoint::CloneSessionArgs consumer_args{};
+ consumer_args.unique_session_name = args.unique_session_name;
+ if (!consumer->connected_) {
+ consumer->session_to_clone_ = std::move(consumer_args);
+ return;
+ }
+ consumer->session_to_clone_ = std::nullopt;
+ consumer->service_->CloneSession(consumer_args);
+}
+
void TracingMuxerImpl::ChangeTracingSessionConfig(
TracingSessionGlobalID session_id,
const TraceConfig& trace_config) {
diff --git a/src/tracing/internal/tracing_muxer_impl.h b/src/tracing/internal/tracing_muxer_impl.h
index 2ab59c4..82e23a6 100644
--- a/src/tracing/internal/tracing_muxer_impl.h
+++ b/src/tracing/internal/tracing_muxer_impl.h
@@ -37,6 +37,7 @@
#include "perfetto/ext/tracing/core/basic_types.h"
#include "perfetto/ext/tracing/core/consumer.h"
#include "perfetto/ext/tracing/core/producer.h"
+#include "perfetto/ext/tracing/core/tracing_service.h"
#include "perfetto/tracing/backend_type.h"
#include "perfetto/tracing/core/data_source_descriptor.h"
#include "perfetto/tracing/core/forward_decls.h"
@@ -169,6 +170,9 @@
const std::shared_ptr<TraceConfig>&,
base::ScopedFile trace_fd = base::ScopedFile());
void StartTracingSession(TracingSessionGlobalID);
+ void CloneTracingSession(TracingSessionGlobalID,
+ TracingSession::CloneTraceArgs,
+ TracingSession::CloneTraceCallback);
void ChangeTracingSessionConfig(TracingSessionGlobalID, const TraceConfig&);
void StopTracingSession(TracingSessionGlobalID);
void DestroyTracingSession(TracingSessionGlobalID);
@@ -334,6 +338,10 @@
// consumer wasn't connected yet.
bool get_trace_stats_pending_ = false;
+ // Similarly we need to buffer a session cloning args if the session is
+ // cloning another sesison before the consumer was connected.
+ std::optional<ConsumerEndpoint::CloneSessionArgs> session_to_clone_;
+
// Whether this session was already stopped. This will happen in response to
// Stop{,Blocking}, but also if the service stops the session for us
// automatically (e.g., when there are no data sources).
@@ -362,6 +370,9 @@
// An internal callback used to implement StopBlocking().
std::function<void()> blocking_stop_complete_callback_;
+ // Callback for a pending call to CloneTrace().
+ TracingSession::CloneTraceCallback clone_trace_callback_;
+
// Callback passed to ReadTrace().
std::function<void(TracingSession::ReadTraceCallbackArgs)>
read_trace_callback_;
@@ -390,6 +401,7 @@
void Setup(const TraceConfig&, int fd) override;
void Start() override;
void StartBlocking() override;
+ void CloneTrace(CloneTraceArgs args, CloneTraceCallback) override;
void SetOnStartCallback(std::function<void()>) override;
void SetOnErrorCallback(std::function<void(TracingError)>) override;
void Stop() override;
diff --git a/src/tracing/ipc/consumer/consumer_ipc_client_impl.cc b/src/tracing/ipc/consumer/consumer_ipc_client_impl.cc
index 9aa7cd6..5ab5aed 100644
--- a/src/tracing/ipc/consumer/consumer_ipc_client_impl.cc
+++ b/src/tracing/ipc/consumer/consumer_ipc_client_impl.cc
@@ -464,15 +464,19 @@
consumer_port_.SaveTraceForBugreport(req, std::move(async_response));
}
-void ConsumerIPCClientImpl::CloneSession(TracingSessionID tsid,
- CloneSessionArgs args) {
+void ConsumerIPCClientImpl::CloneSession(CloneSessionArgs args) {
if (!connected_) {
PERFETTO_DLOG("Cannot CloneSession(), not connected to tracing service");
return;
}
protos::gen::CloneSessionRequest req;
- req.set_session_id(tsid);
+ if (args.tsid) {
+ req.set_session_id(args.tsid);
+ }
+ if (!args.unique_session_name.empty()) {
+ req.set_unique_session_name(args.unique_session_name);
+ }
req.set_skip_trace_filter(args.skip_trace_filter);
req.set_for_bugreport(args.for_bugreport);
ipc::Deferred<protos::gen::CloneSessionResponse> async_response;
diff --git a/src/tracing/ipc/consumer/consumer_ipc_client_impl.h b/src/tracing/ipc/consumer/consumer_ipc_client_impl.h
index 985de64..fd2d6e5 100644
--- a/src/tracing/ipc/consumer/consumer_ipc_client_impl.h
+++ b/src/tracing/ipc/consumer/consumer_ipc_client_impl.h
@@ -75,7 +75,7 @@
QueryServiceStateCallback) override;
void QueryCapabilities(QueryCapabilitiesCallback) override;
void SaveTraceForBugreport(SaveTraceForBugreportCallback) override;
- void CloneSession(TracingSessionID, CloneSessionArgs) override;
+ void CloneSession(CloneSessionArgs) override;
// ipc::ServiceProxy::EventListener implementation.
// These methods are invoked by the IPC layer, which knows nothing about
diff --git a/src/tracing/ipc/service/consumer_ipc_service.cc b/src/tracing/ipc/service/consumer_ipc_service.cc
index 8837dde..4773d90 100644
--- a/src/tracing/ipc/service/consumer_ipc_service.cc
+++ b/src/tracing/ipc/service/consumer_ipc_service.cc
@@ -333,8 +333,13 @@
ConsumerEndpoint::CloneSessionArgs args;
args.skip_trace_filter = req.skip_trace_filter();
args.for_bugreport = req.for_bugreport();
- remote_consumer->service_endpoint->CloneSession(req.session_id(),
- std::move(args));
+ if (req.has_session_id()) {
+ args.tsid = req.session_id();
+ }
+ if (req.has_unique_session_name()) {
+ args.unique_session_name = req.unique_session_name();
+ }
+ remote_consumer->service_endpoint->CloneSession(std::move(args));
}
// Called by the service in response to
diff --git a/src/tracing/ipc/service/relay_ipc_service.cc b/src/tracing/ipc/service/relay_ipc_service.cc
index c305f3b..9c2b328 100644
--- a/src/tracing/ipc/service/relay_ipc_service.cc
+++ b/src/tracing/ipc/service/relay_ipc_service.cc
@@ -20,9 +20,9 @@
#include <utility>
#include "perfetto/base/logging.h"
+#include "perfetto/ext/base/clock_snapshots.h"
#include "perfetto/ext/ipc/service.h"
#include "perfetto/ext/tracing/core/tracing_service.h"
-#include "perfetto/tracing/core/clock_snapshots.h"
#include "perfetto/tracing/core/forward_decls.h"
namespace perfetto {
@@ -52,13 +52,13 @@
void RelayIPCService::SyncClock(const protos::gen::SyncClockRequest& req,
DeferredSyncClockResponse resp) {
- auto host_clock_snapshots = CaptureClockSnapshots();
+ auto host_clock_snapshots = base::CaptureClockSnapshots();
// Send the response to client to reduce RTT.
auto async_resp = ipc::AsyncResult<protos::gen::SyncClockResponse>::Create();
resp.Resolve(std::move(async_resp));
- ClockSnapshotVector client_clock_snapshots;
+ base::ClockSnapshotVector client_clock_snapshots;
for (size_t i = 0; i < req.clocks().size(); i++) {
auto& client_clock = req.clocks()[i];
client_clock_snapshots.emplace_back(client_clock.clock_id(),
diff --git a/src/tracing/ipc/shared_memory_windows.h b/src/tracing/ipc/shared_memory_windows.h
index ca6443b..9a4aeee 100644
--- a/src/tracing/ipc/shared_memory_windows.h
+++ b/src/tracing/ipc/shared_memory_windows.h
@@ -52,7 +52,8 @@
const base::ScopedPlatformHandle& handle() const { return handle_; }
// SharedMemory implementation.
- void* start() const override { return start_; }
+ using SharedMemory::start; // Equal priority to const and non-const versions
+ const void* start() const override { return start_; }
size_t size() const override { return size_; }
private:
diff --git a/src/tracing/service/BUILD.gn b/src/tracing/service/BUILD.gn
index 984c692..24d894d 100644
--- a/src/tracing/service/BUILD.gn
+++ b/src/tracing/service/BUILD.gn
@@ -31,17 +31,23 @@
"../../../protos/perfetto/trace/perfetto:zero", # For MetatraceWriter.
"../../android_stats",
"../../base",
+ "../../base:clock_snapshots",
"../../base:version",
"../../protozero/filtering:message_filter",
"../../protozero/filtering:string_filter",
"../core",
]
sources = [
+ "clock.cc",
+ "clock.h",
+ "dependencies.h",
"histogram.h",
"metatrace_writer.cc",
"metatrace_writer.h",
"packet_stream_validator.cc",
"packet_stream_validator.h",
+ "random.cc",
+ "random.h",
"trace_buffer.cc",
"trace_buffer.h",
"tracing_service_impl.cc",
diff --git a/src/tracing/service/clock.cc b/src/tracing/service/clock.cc
new file mode 100644
index 0000000..350115a
--- /dev/null
+++ b/src/tracing/service/clock.cc
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+#include "src/tracing/service/clock.h"
+
+namespace perfetto::tracing_service {
+
+Clock::~Clock() = default;
+
+ClockImpl::~ClockImpl() = default;
+
+base::TimeNanos ClockImpl::GetBootTimeNs() {
+ return base::GetBootTimeNs();
+}
+
+base::TimeNanos ClockImpl::GetWallTimeNs() {
+ return base::GetWallTimeNs();
+}
+
+} // namespace perfetto::tracing_service
diff --git a/src/tracing/service/clock.h b/src/tracing/service/clock.h
new file mode 100644
index 0000000..9fc9ef4
--- /dev/null
+++ b/src/tracing/service/clock.h
@@ -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.
+ */
+
+#ifndef SRC_TRACING_SERVICE_CLOCK_H_
+#define SRC_TRACING_SERVICE_CLOCK_H_
+
+#include "perfetto/base/time.h"
+
+namespace perfetto::tracing_service {
+
+class Clock {
+ public:
+ virtual ~Clock();
+ virtual base::TimeNanos GetBootTimeNs() = 0;
+ virtual base::TimeNanos GetWallTimeNs() = 0;
+
+ base::TimeMillis GetBootTimeMs() {
+ return std::chrono::duration_cast<base::TimeMillis>(GetBootTimeNs());
+ }
+ base::TimeMillis GetWallTimeMs() {
+ return std::chrono::duration_cast<base::TimeMillis>(GetWallTimeNs());
+ }
+
+ base::TimeSeconds GetBootTimeS() {
+ return std::chrono::duration_cast<base::TimeSeconds>(GetBootTimeNs());
+ }
+ base::TimeSeconds GetWallTimeS() {
+ return std::chrono::duration_cast<base::TimeSeconds>(GetWallTimeNs());
+ }
+};
+
+class ClockImpl : public Clock {
+ public:
+ ~ClockImpl() override;
+ base::TimeNanos GetBootTimeNs() override;
+ base::TimeNanos GetWallTimeNs() override;
+};
+
+} // namespace perfetto::tracing_service
+
+#endif // SRC_TRACING_SERVICE_CLOCK_H_
diff --git a/src/tracing/service/dependencies.h b/src/tracing/service/dependencies.h
new file mode 100644
index 0000000..e4da51b
--- /dev/null
+++ b/src/tracing/service/dependencies.h
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+#ifndef SRC_TRACING_SERVICE_DEPENDENCIES_H_
+#define SRC_TRACING_SERVICE_DEPENDENCIES_H_
+
+#include <memory>
+
+#include "src/tracing/service/clock.h"
+#include "src/tracing/service/random.h"
+
+namespace perfetto::tracing_service {
+
+// Dependencies of TracingServiceImpl. Can point to real implementations or to
+// mocks in tests.
+struct Dependencies {
+ std::unique_ptr<Clock> clock;
+ std::unique_ptr<Random> random;
+};
+
+} // namespace perfetto::tracing_service
+
+#endif // SRC_TRACING_SERVICE_DEPENDENCIES_H_
diff --git a/protos/perfetto/metrics/android/other_traces.proto b/src/tracing/service/random.cc
similarity index 61%
rename from protos/perfetto/metrics/android/other_traces.proto
rename to src/tracing/service/random.cc
index 1795ee2..d1c4cb0 100644
--- a/protos/perfetto/metrics/android/other_traces.proto
+++ b/src/tracing/service/random.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
@@ -14,12 +14,17 @@
* limitations under the License.
*/
-syntax = "proto2";
+#include "src/tracing/service/random.h"
-package perfetto.protos;
+namespace perfetto::tracing_service {
-message AndroidOtherTracesMetric {
- // Uuids of other traces being finalized while the current trace was being
- // recorded.
- repeated string finalized_traces_uuid = 1;
+Random::~Random() = default;
+
+RandomImpl::RandomImpl(uint32_t seed) : prng_(seed) {}
+RandomImpl::~RandomImpl() = default;
+
+double RandomImpl::GetValue() {
+ return dist_(prng_);
}
+
+} // namespace perfetto::tracing_service
diff --git a/src/tracing/service/random.h b/src/tracing/service/random.h
new file mode 100644
index 0000000..26a4391
--- /dev/null
+++ b/src/tracing/service/random.h
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+#ifndef SRC_TRACING_SERVICE_RANDOM_H_
+#define SRC_TRACING_SERVICE_RANDOM_H_
+
+#include <random>
+
+namespace perfetto::tracing_service {
+
+class Random {
+ public:
+ virtual ~Random();
+ virtual double GetValue() = 0;
+};
+
+class RandomImpl : public Random {
+ public:
+ explicit RandomImpl(uint32_t seed);
+ ~RandomImpl() override;
+ double GetValue() override;
+
+ private:
+ std::minstd_rand prng_;
+ std::uniform_real_distribution<double> dist_;
+};
+
+} // namespace perfetto::tracing_service
+
+#endif // SRC_TRACING_SERVICE_RANDOM_H_
diff --git a/src/tracing/service/tracing_service_impl.cc b/src/tracing/service/tracing_service_impl.cc
index cd819e8..68b5ba6 100644
--- a/src/tracing/service/tracing_service_impl.cc
+++ b/src/tracing/service/tracing_service_impl.cc
@@ -19,16 +19,13 @@
#include <limits.h>
#include <string.h>
+#include <algorithm>
#include <cinttypes>
#include <cstdint>
#include <limits>
#include <optional>
-#include <regex>
#include <string>
#include <unordered_set>
-#include "perfetto/base/time.h"
-#include "perfetto/ext/tracing/core/client_identity.h"
-#include "perfetto/tracing/core/clock_snapshots.h"
#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) && \
!PERFETTO_BUILDFLAG(PERFETTO_OS_NACL)
@@ -50,12 +47,11 @@
#include <sys/stat.h>
#endif
-#include <algorithm>
-
#include "perfetto/base/build_config.h"
#include "perfetto/base/status.h"
#include "perfetto/base/task_runner.h"
#include "perfetto/ext/base/android_utils.h"
+#include "perfetto/ext/base/clock_snapshots.h"
#include "perfetto/ext/base/file_utils.h"
#include "perfetto/ext/base/metatrace.h"
#include "perfetto/ext/base/string_utils.h"
@@ -66,6 +62,7 @@
#include "perfetto/ext/base/version.h"
#include "perfetto/ext/base/watchdog.h"
#include "perfetto/ext/tracing/core/basic_types.h"
+#include "perfetto/ext/tracing/core/client_identity.h"
#include "perfetto/ext/tracing/core/consumer.h"
#include "perfetto/ext/tracing/core/observable_events.h"
#include "perfetto/ext/tracing/core/producer.h"
@@ -112,6 +109,7 @@
constexpr int kMaxBuffersPerConsumer = 128;
constexpr uint32_t kDefaultSnapshotsIntervalMs = 10 * 1000;
constexpr int kDefaultWriteIntoFilePeriodMs = 5000;
+constexpr int kMinWriteIntoFilePeriodMs = 100;
constexpr uint32_t kAllDataSourceStartedTimeout = 20000;
constexpr int kMaxConcurrentTracingSessions = 15;
constexpr int kMaxConcurrentTracingSessionsPerUid = 5;
@@ -335,21 +333,26 @@
std::unique_ptr<SharedMemory::Factory> shm_factory,
base::TaskRunner* task_runner,
InitOpts init_opts) {
- return std::unique_ptr<TracingService>(
- new TracingServiceImpl(std::move(shm_factory), task_runner, init_opts));
+ tracing_service::Dependencies deps;
+ deps.clock = std::make_unique<tracing_service::ClockImpl>();
+ uint32_t seed = static_cast<uint32_t>(deps.clock->GetWallTimeMs().count());
+ deps.random = std::make_unique<tracing_service::RandomImpl>(seed);
+ return std::unique_ptr<TracingService>(new TracingServiceImpl(
+ std::move(shm_factory), task_runner, std::move(deps), init_opts));
}
TracingServiceImpl::TracingServiceImpl(
std::unique_ptr<SharedMemory::Factory> shm_factory,
base::TaskRunner* task_runner,
+ tracing_service::Dependencies deps,
InitOpts init_opts)
: task_runner_(task_runner),
+ clock_(std::move(deps.clock)),
+ random_(std::move(deps.random)),
init_opts_(init_opts),
shm_factory_(std::move(shm_factory)),
uid_(base::GetCurrentUserId()),
buffer_ids_(kMaxTraceBufferID),
- trigger_probability_rand_(
- static_cast<uint32_t>(base::GetWallTimeNs().count())),
weak_ptr_factory_(this) {
PERFETTO_DCHECK(task_runner_);
}
@@ -834,7 +837,7 @@
if (cfg.enable_extra_guardrails()) {
// unique_session_name can be empty
const std::string& name = cfg.unique_session_name();
- int64_t now_s = base::GetBootTimeS().count();
+ int64_t now_s = clock_->GetBootTimeS().count();
// Remove any entries where the time limit has passed so this map doesn't
// grow indefinitely:
@@ -975,8 +978,8 @@
uint32_t write_period_ms = cfg.file_write_period_ms();
if (write_period_ms == 0)
write_period_ms = kDefaultWriteIntoFilePeriodMs;
- if (write_period_ms < min_write_period_ms_)
- write_period_ms = min_write_period_ms_;
+ if (write_period_ms < kMinWriteIntoFilePeriodMs)
+ write_period_ms = kMinWriteIntoFilePeriodMs;
tracing_session->write_period_ms = write_period_ms;
tracing_session->max_file_size_bytes = cfg.max_file_size_bytes();
tracing_session->bytes_written_into_file = 0;
@@ -1253,6 +1256,14 @@
}
}
+uint32_t TracingServiceImpl::DelayToNextWritePeriodMs(
+ const TracingSession& session) {
+ PERFETTO_DCHECK(session.write_period_ms > 0);
+ return session.write_period_ms -
+ static_cast<uint32_t>(clock_->GetWallTimeMs().count() %
+ session.write_period_ms);
+}
+
void TracingServiceImpl::StartTracing(TracingSessionID tsid) {
PERFETTO_DCHECK_THREAD(thread_checker_);
@@ -1342,7 +1353,7 @@
if (weak_this)
weak_this->ReadBuffersIntoFile(tsid);
},
- tracing_session->delay_to_next_write_period_ms());
+ DelayToNextWritePeriodMs(*tracing_session));
}
// Start the periodic flush tasks if the config specified a flush period.
@@ -1560,7 +1571,7 @@
return;
}
- int64_t timestamp = base::GetBootTimeNs().count();
+ int64_t timestamp = clock_->GetBootTimeNs().count();
protozero::HeapBuffered<protos::pbzero::TracePacket> packet;
packet->set_timestamp(static_cast<uint64_t>(timestamp));
@@ -1670,10 +1681,13 @@
auto* producer = GetProducer(producer_id);
PERFETTO_DCHECK(producer);
- int64_t now_ns = base::GetBootTimeNs().count();
+ int64_t now_ns = clock_->GetBootTimeNs().count();
for (const auto& trigger_name : triggers) {
PERFETTO_DLOG("Received ActivateTriggers request for \"%s\"",
trigger_name.c_str());
+ android_stats::MaybeLogTriggerEvent(PerfettoTriggerAtom::kTracedTrigger,
+ trigger_name);
+
base::Hasher hash;
hash.Update(trigger_name.c_str(), trigger_name.size());
std::string triggered_session_name;
@@ -1713,10 +1727,7 @@
// Use a random number between 0 and 1 to check if we should allow this
// trigger through or not.
- double trigger_rnd =
- trigger_rnd_override_for_testing_ > 0
- ? trigger_rnd_override_for_testing_
- : trigger_probability_dist_(trigger_probability_rand_);
+ double trigger_rnd = random_->GetValue();
PERFETTO_DCHECK(trigger_rnd >= 0 && trigger_rnd < 1);
if (trigger_rnd < iter->skip_probability()) {
MaybeLogTriggerEvent(tracing_session.config,
@@ -1900,6 +1911,11 @@
return;
}
+ SnapshotLifecyleEvent(
+ tracing_session,
+ protos::pbzero::TracingServiceEvent::kFlushStartedFieldNumber,
+ false /* snapshot_clocks */);
+
std::map<ProducerID, std::vector<DataSourceInstanceID>> data_source_instances;
for (const auto& [producer_id, ds_inst] :
tracing_session->data_source_instances) {
@@ -2017,7 +2033,7 @@
if ((flush_flags.reason() == FlushFlags::Reason::kTraceClone ||
flush_flags.reason() == FlushFlags::Reason::kTraceStop) &&
!success) {
- int64_t timestamp = base::GetBootTimeNs().count();
+ int64_t timestamp = clock_->GetBootTimeNs().count();
protozero::HeapBuffered<protos::pbzero::TracePacket> packet;
packet->set_timestamp(static_cast<uint64_t>(timestamp));
@@ -2246,7 +2262,7 @@
if (weak_this)
weak_this->PeriodicFlushTask(tsid, /*post_next_only=*/false);
},
- flush_period_ms - static_cast<uint32_t>(base::GetWallTimeMs().count() %
+ flush_period_ms - static_cast<uint32_t>(clock_->GetWallTimeMs().count() %
flush_period_ms));
if (post_next_only)
@@ -2280,7 +2296,7 @@
weak_this->PeriodicClearIncrementalStateTask(
tsid, /*post_next_only=*/false);
},
- clear_period_ms - static_cast<uint32_t>(base::GetWallTimeMs().count() %
+ clear_period_ms - static_cast<uint32_t>(clock_->GetWallTimeMs().count() %
clear_period_ms));
if (post_next_only)
@@ -2416,7 +2432,7 @@
if (weak_this)
weak_this->ReadBuffersIntoFile(tsid);
},
- tracing_session->delay_to_next_write_period_ms());
+ DelayToNextWritePeriodMs(*tracing_session));
return true;
}
@@ -2644,7 +2660,7 @@
// by the earlier call to SetFilterRoot() in EnableTracing().
PERFETTO_DCHECK(trace_filter.config().root_msg_index() != 0);
std::vector<protozero::MessageFilter::InputSlice> filter_input;
- auto start = base::GetWallTimeNs();
+ auto start = clock_->GetWallTimeNs();
for (TracePacket& packet : *packets) {
const auto& packet_slices = packet.slices();
const size_t input_packet_size = packet.size();
@@ -2684,7 +2700,7 @@
filtered_packet.size, kMaxTracePacketSliceSize,
&packet);
}
- auto end = base::GetWallTimeNs();
+ auto end = clock_->GetWallTimeNs();
tracing_session->filter_time_taken_ns +=
static_cast<uint64_t>((end - start).count());
}
@@ -3310,6 +3326,25 @@
}
TracingServiceImpl::TracingSession*
+TracingServiceImpl::GetTracingSessionByUniqueName(
+ const std::string& unique_session_name) {
+ PERFETTO_DCHECK_THREAD(thread_checker_);
+ if (unique_session_name.empty()) {
+ return nullptr;
+ }
+ for (auto& session_id_and_session : tracing_sessions_) {
+ TracingSession& session = session_id_and_session.second;
+ if (session.state == TracingSession::CLONED_READ_ONLY) {
+ continue;
+ }
+ if (session.config.unique_session_name() == unique_session_name) {
+ return &session;
+ }
+ }
+ return nullptr;
+}
+
+TracingServiceImpl::TracingSession*
TracingServiceImpl::FindTracingSessionWithMaxBugreportScore() {
TracingSession* max_session = nullptr;
for (auto& session_id_and_session : tracing_sessions_) {
@@ -3443,7 +3478,7 @@
event->timestamps.erase_front(1 + event->timestamps.size() -
event->max_size);
}
- event->timestamps.emplace_back(base::GetBootTimeNs().count());
+ event->timestamps.emplace_back(clock_->GetBootTimeNs().count());
}
void TracingServiceImpl::MaybeSnapshotClocksIntoRingBuffer(
@@ -3486,7 +3521,8 @@
// been emitted into the trace yet (see comment below).
static constexpr int64_t kSignificantDriftNs = 10 * 1000 * 1000; // 10 ms
- TracingSession::ClockSnapshotData new_snapshot_data = CaptureClockSnapshots();
+ TracingSession::ClockSnapshotData new_snapshot_data =
+ base::CaptureClockSnapshots();
// If we're about to update a session's latest clock snapshot that hasn't been
// emitted into the trace yet, check whether the clocks have drifted enough to
// warrant overriding the current snapshot values. The older snapshot would be
@@ -3719,6 +3755,14 @@
PERFETTO_ELOG("Unable to read ro.build.fingerprint");
}
+ std::string device_manufacturer_value =
+ base::GetAndroidProp("ro.product.manufacturer");
+ if (!device_manufacturer_value.empty()) {
+ info->set_android_device_manufacturer(device_manufacturer_value);
+ } else {
+ PERFETTO_ELOG("Unable to read ro.product.manufacturer");
+ }
+
std::string sdk_str_value = base::GetAndroidProp("ro.build.version.sdk");
std::optional<uint64_t> sdk_value = base::StringToUInt64(sdk_str_value);
if (sdk_value.has_value()) {
@@ -3734,6 +3778,13 @@
PERFETTO_ELOG("Unable to read ro.soc.model");
}
+ // guest_soc model is not always present
+ std::string guest_soc_model_value =
+ base::GetAndroidProp("ro.boot.guest_soc.model");
+ if (!guest_soc_model_value.empty()) {
+ info->set_android_guest_soc_model(guest_soc_model_value);
+ }
+
std::string hw_rev_value = base::GetAndroidProp("ro.boot.hardware.revision");
if (!hw_rev_value.empty()) {
info->set_android_hardware_revision(hw_rev_value);
@@ -3903,12 +3954,13 @@
size_t TracingServiceImpl::PurgeExpiredAndCountTriggerInWindow(
int64_t now_ns,
uint64_t trigger_name_hash) {
+ constexpr int64_t kOneDayInNs = 24ll * 60 * 60 * 1000 * 1000 * 1000;
PERFETTO_DCHECK(
std::is_sorted(trigger_history_.begin(), trigger_history_.end()));
size_t remove_count = 0;
size_t trigger_count = 0;
for (const TriggerHistory& h : trigger_history_) {
- if (h.timestamp_ns < now_ns - trigger_window_ns_) {
+ if (h.timestamp_ns < now_ns - kOneDayInNs) {
remove_count++;
} else if (h.name_hash == trigger_name_hash) {
trigger_count++;
@@ -3920,33 +3972,37 @@
base::Status TracingServiceImpl::FlushAndCloneSession(
ConsumerEndpointImpl* consumer,
- TracingSessionID tsid,
- bool skip_trace_filter,
- bool for_bugreport) {
+ ConsumerEndpoint::CloneSessionArgs args) {
PERFETTO_DCHECK_THREAD(thread_checker_);
auto clone_target = FlushFlags::CloneTarget::kUnknown;
- if (tsid == kBugreportSessionId) {
- // This branch is only here to support the legacy protocol where we could
- // clone only a single session using the magic ID kBugreportSessionId.
- // The newer perfetto --clone-all-for-bugreport first queries the existing
- // sessions and then issues individual clone requests specifying real
- // session IDs, setting args.{for_bugreport,skip_trace_filter}=true.
- PERFETTO_LOG("Looking for sessions for bugreport");
- TracingSession* session = FindTracingSessionWithMaxBugreportScore();
- if (!session) {
- return base::ErrStatus(
- "No tracing sessions eligible for bugreport found");
- }
- tsid = session->id;
- clone_target = FlushFlags::CloneTarget::kBugreport;
- skip_trace_filter = true;
- for_bugreport = true;
- } else if (for_bugreport) {
+ TracingSession* session = nullptr;
+ if (args.for_bugreport) {
clone_target = FlushFlags::CloneTarget::kBugreport;
}
+ if (args.tsid != 0) {
+ if (args.tsid == kBugreportSessionId) {
+ // This branch is only here to support the legacy protocol where we could
+ // clone only a single session using the magic ID kBugreportSessionId.
+ // The newer perfetto --clone-all-for-bugreport first queries the existing
+ // sessions and then issues individual clone requests specifying real
+ // session IDs, setting args.{for_bugreport,skip_trace_filter}=true.
+ PERFETTO_LOG("Looking for sessions for bugreport");
+ session = FindTracingSessionWithMaxBugreportScore();
+ if (!session) {
+ return base::ErrStatus(
+ "No tracing sessions eligible for bugreport found");
+ }
+ args.tsid = session->id;
+ clone_target = FlushFlags::CloneTarget::kBugreport;
+ args.skip_trace_filter = true;
+ } else {
+ session = GetTracingSession(args.tsid);
+ }
+ } else if (!args.unique_session_name.empty()) {
+ session = GetTracingSessionByUniqueName(args.unique_session_name);
+ }
- TracingSession* session = GetTracingSession(tsid);
if (!session) {
return base::ErrStatus("Tracing session not found");
}
@@ -4002,7 +4058,7 @@
clone_op.buffers =
std::vector<std::unique_ptr<TraceBuffer>>(session->buffers_index.size());
clone_op.weak_consumer = weak_consumer;
- clone_op.skip_trace_filter = skip_trace_filter;
+ clone_op.skip_trace_filter = args.skip_trace_filter;
// Issue separate flush requests for separate buffer groups. The buffer marked
// as transfer_on_clone will be flushed and cloned separately: even if they're
@@ -4020,12 +4076,15 @@
}
}
+ SnapshotLifecyleEvent(
+ session, protos::pbzero::TracingServiceEvent::kFlushStartedFieldNumber,
+ false /* snapshot_clocks */);
clone_op.pending_flush_cnt = bufs_groups.size();
for (const std::set<BufferID>& buf_group : bufs_groups) {
FlushDataSourceInstances(
session, 0,
GetFlushableDataSourceInstancesForBuffers(session, buf_group),
- [tsid, clone_id, buf_group, weak_this](bool final_flush) {
+ [tsid = session->id, clone_id, buf_group, weak_this](bool final_flush) {
if (!weak_this)
return;
weak_this->OnFlushDoneForClone(tsid, clone_id, buf_group,
@@ -4611,12 +4670,10 @@
}
void TracingServiceImpl::ConsumerEndpointImpl::CloneSession(
- TracingSessionID tsid,
CloneSessionArgs args) {
PERFETTO_DCHECK_THREAD(thread_checker_);
// FlushAndCloneSession will call OnSessionCloned after the async flush.
- base::Status result = service_->FlushAndCloneSession(
- this, tsid, args.skip_trace_filter, args.for_bugreport);
+ base::Status result = service_->FlushAndCloneSession(this, std::move(args));
if (!result.ok()) {
consumer_->OnSessionCloned({false, result.message(), {}});
@@ -4962,12 +5019,15 @@
config(new_config),
snapshot_periodic_task(task_runner),
timed_stop_task(task_runner) {
- // all_data_sources_flushed is special because we store up to 64 events of
- // this type. Other events will go through the default case in
+ // all_data_sources_flushed (and flush_started) is special because we store up
+ // to 64 events of this type. Other events will go through the default case in
// SnapshotLifecycleEvent() where they will be given a max history of 1.
lifecycle_events.emplace_back(
protos::pbzero::TracingServiceEvent::kAllDataSourcesFlushedFieldNumber,
64 /* max_size */);
+ lifecycle_events.emplace_back(
+ protos::pbzero::TracingServiceEvent::kFlushStartedFieldNumber,
+ 64 /* max_size */);
}
////////////////////////////////////////////////////////////////////////////////
@@ -4981,8 +5041,8 @@
void TracingServiceImpl::RelayEndpointImpl::SyncClocks(
SyncMode sync_mode,
- ClockSnapshotVector client_clocks,
- ClockSnapshotVector host_clocks) {
+ base::ClockSnapshotVector client_clocks,
+ base::ClockSnapshotVector host_clocks) {
// We keep only the most recent 5 clock sync snapshots.
static constexpr size_t kNumSyncClocks = 5;
if (synced_clocks_.size() >= kNumSyncClocks)
diff --git a/src/tracing/service/tracing_service_impl.h b/src/tracing/service/tracing_service_impl.h
index 6e14d6d..22f527b 100644
--- a/src/tracing/service/tracing_service_impl.h
+++ b/src/tracing/service/tracing_service_impl.h
@@ -22,7 +22,6 @@
#include <map>
#include <memory>
#include <optional>
-#include <random>
#include <set>
#include <utility>
#include <vector>
@@ -31,8 +30,8 @@
#include "perfetto/base/status.h"
#include "perfetto/base/time.h"
#include "perfetto/ext/base/circular_queue.h"
+#include "perfetto/ext/base/clock_snapshots.h"
#include "perfetto/ext/base/periodic_task.h"
-#include "perfetto/ext/base/string_view.h"
#include "perfetto/ext/base/uuid.h"
#include "perfetto/ext/base/weak_ptr.h"
#include "perfetto/ext/tracing/core/basic_types.h"
@@ -48,6 +47,9 @@
#include "perfetto/tracing/core/trace_config.h"
#include "src/android_stats/perfetto_atoms.h"
#include "src/tracing/core/id_allocator.h"
+#include "src/tracing/service/clock.h"
+#include "src/tracing/service/dependencies.h"
+#include "src/tracing/service/random.h"
namespace protozero {
class MessageFilter;
@@ -159,8 +161,6 @@
private:
friend class TracingServiceImpl;
- friend class TracingServiceImplTest;
- friend class TracingIntegrationTest;
ProducerEndpointImpl(const ProducerEndpointImpl&) = delete;
ProducerEndpointImpl& operator=(const ProducerEndpointImpl&) = delete;
@@ -230,7 +230,7 @@
QueryServiceStateCallback) override;
void QueryCapabilities(QueryCapabilitiesCallback) override;
void SaveTraceForBugreport(SaveTraceForBugreportCallback) override;
- void CloneSession(TracingSessionID, CloneSessionArgs) override;
+ void CloneSession(CloneSessionArgs) override;
// Will queue a task to notify the consumer about the state change.
void OnDataSourceInstanceStateChange(const ProducerEndpointImpl&,
@@ -274,22 +274,22 @@
struct SyncedClockSnapshots {
SyncedClockSnapshots(SyncMode _sync_mode,
- ClockSnapshotVector _client_clocks,
- ClockSnapshotVector _host_clocks)
+ base::ClockSnapshotVector _client_clocks,
+ base::ClockSnapshotVector _host_clocks)
: sync_mode(_sync_mode),
client_clocks(std::move(_client_clocks)),
host_clocks(std::move(_host_clocks)) {}
SyncMode sync_mode;
- ClockSnapshotVector client_clocks;
- ClockSnapshotVector host_clocks;
+ base::ClockSnapshotVector client_clocks;
+ base::ClockSnapshotVector host_clocks;
};
explicit RelayEndpointImpl(RelayClientID relay_client_id,
TracingServiceImpl* service);
~RelayEndpointImpl() override;
void SyncClocks(SyncMode sync_mode,
- ClockSnapshotVector client_clocks,
- ClockSnapshotVector host_clocks) override;
+ base::ClockSnapshotVector client_clocks,
+ base::ClockSnapshotVector host_clocks) override;
void Disconnect() override;
MachineID machine_id() const { return relay_client_id_.first; }
@@ -311,6 +311,7 @@
explicit TracingServiceImpl(std::unique_ptr<SharedMemory::Factory>,
base::TaskRunner*,
+ tracing_service::Dependencies,
InitOpts = {});
~TracingServiceImpl() override;
@@ -353,9 +354,7 @@
FlushFlags);
void FlushAndDisableTracing(TracingSessionID);
base::Status FlushAndCloneSession(ConsumerEndpointImpl*,
- TracingSessionID,
- bool skip_filter,
- bool for_bugreport);
+ ConsumerEndpoint::CloneSessionArgs);
// Starts reading the internal tracing buffers from the tracing session `tsid`
// and sends them to `*consumer` (which must be != nullptr).
@@ -415,11 +414,6 @@
ProducerEndpointImpl* GetProducer(ProducerID) const;
private:
- friend class TracingServiceImplTest;
- friend class TracingIntegrationTest;
-
- static constexpr int64_t kOneDayInNs = 24ll * 60 * 60 * 1000 * 1000 * 1000;
-
struct TriggerHistory {
int64_t timestamp_ns;
uint64_t name_hash;
@@ -510,13 +504,6 @@
size_t num_buffers() const { return buffers_index.size(); }
- uint32_t delay_to_next_write_period_ms() const {
- PERFETTO_DCHECK(write_period_ms > 0);
- return write_period_ms -
- static_cast<uint32_t>(base::GetWallTimeMs().count() %
- write_period_ms);
- }
-
uint32_t flush_timeout_ms() {
uint32_t timeout_ms = config.flush_timeout_ms();
return timeout_ms ? timeout_ms : kDefaultFlushTimeoutMs;
@@ -695,7 +682,7 @@
std::vector<ArbitraryLifecycleEvent> last_flush_events;
- using ClockSnapshotData = ClockSnapshotVector;
+ using ClockSnapshotData = base::ClockSnapshotVector;
// Initial clock snapshot, captured at trace start time (when state goes to
// TracingSession::STARTED). Emitted into the trace when the consumer first
@@ -770,6 +757,12 @@
// session doesn't exists.
TracingSession* GetTracingSession(TracingSessionID);
+ // Returns a pointer to the |tracing_sessions_| entry with
+ // |unique_session_name| in the config (or nullptr if the
+ // session doesn't exists). CLONED_READ_ONLY sessions are ignored.
+ TracingSession* GetTracingSessionByUniqueName(
+ const std::string& unique_session_name);
+
// Returns a pointer to the tracing session that has the highest
// TraceConfig.bugreport_score, if any, or nullptr.
TracingSession* FindTracingSessionWithMaxBugreportScore();
@@ -782,6 +775,7 @@
// shared memory and trace buffers.
void UpdateMemoryGuardrail();
+ uint32_t DelayToNextWritePeriodMs(const TracingSession&);
void StartDataSourceInstance(ProducerEndpointImpl*,
TracingSession*,
DataSourceInstance*);
@@ -886,6 +880,8 @@
TracingSessionID);
base::TaskRunner* const task_runner_;
+ std::unique_ptr<tracing_service::Clock> clock_;
+ std::unique_ptr<tracing_service::Random> random_;
const InitOpts init_opts_;
std::unique_ptr<SharedMemory::Factory> shm_factory_;
ProducerID last_producer_id_ = 0;
@@ -907,18 +903,12 @@
std::map<std::string, int64_t> session_to_last_trace_s_;
// Contains timestamps of triggers.
- // The queue is sorted by timestamp and invocations older than
- // |trigger_window_ns_| are purged when a trigger happens.
+ // The queue is sorted by timestamp and invocations older than 24 hours are
+ // purged when a trigger happens.
base::CircularQueue<TriggerHistory> trigger_history_;
bool smb_scraping_enabled_ = false;
bool lockdown_mode_ = false;
- uint32_t min_write_period_ms_ = 100; // Overridable for testing.
- int64_t trigger_window_ns_ = kOneDayInNs; // Overridable for testing.
-
- std::minstd_rand trigger_probability_rand_;
- std::uniform_real_distribution<> trigger_probability_dist_;
- double trigger_rnd_override_for_testing_ = 0; // Overridable for testing.
uint8_t sync_marker_packet_[32]; // Lazily initialized.
size_t sync_marker_packet_size_ = 0;
diff --git a/src/tracing/service/tracing_service_impl_unittest.cc b/src/tracing/service/tracing_service_impl_unittest.cc
index 8c59442..0a6438a 100644
--- a/src/tracing/service/tracing_service_impl_unittest.cc
+++ b/src/tracing/service/tracing_service_impl_unittest.cc
@@ -64,6 +64,7 @@
#include "src/tracing/core/trace_writer_impl.h"
#include "src/tracing/test/mock_consumer.h"
#include "src/tracing/test/mock_producer.h"
+#include "src/tracing/test/proxy_producer_endpoint.h"
#include "src/tracing/test/test_shared_memory.h"
#include "test/gtest_and_gmock.h"
@@ -101,6 +102,7 @@
using ::testing::IsEmpty;
using ::testing::Mock;
using ::testing::Ne;
+using ::testing::NiceMock;
using ::testing::Not;
using ::testing::Pointee;
using ::testing::Property;
@@ -194,23 +196,61 @@
}
#endif // PERFETTO_BUILDFLAG(PERFETTO_ZLIB)
-} // namespace
+std::vector<std::string> GetReceivedTriggers(
+ const std::vector<protos::gen::TracePacket>& trace) {
+ std::vector<std::string> triggers;
+ for (const protos::gen::TracePacket& packet : trace) {
+ if (packet.has_trigger()) {
+ triggers.push_back(packet.trigger().trigger_name());
+ }
+ }
+ return triggers;
+}
+
+class MockClock : public tracing_service::Clock {
+ public:
+ ~MockClock() override = default;
+ MOCK_METHOD(base::TimeNanos, GetBootTimeNs, (), (override));
+ MOCK_METHOD(base::TimeNanos, GetWallTimeNs, (), (override));
+};
+
+class MockRandom : public tracing_service::Random {
+ public:
+ ~MockRandom() override = default;
+ MOCK_METHOD(double, GetValue, (), (override));
+};
class TracingServiceImplTest : public testing::Test {
public:
- using DataSourceInstanceState =
- TracingServiceImpl::DataSourceInstance::DataSourceInstanceState;
-
TracingServiceImplTest() { InitializeSvcWithOpts({}); }
void InitializeSvcWithOpts(TracingService::InitOpts init_opts) {
auto shm_factory =
std::unique_ptr<SharedMemory::Factory>(new TestSharedMemory::Factory());
- svc.reset(static_cast<TracingServiceImpl*>(
- TracingService::CreateInstance(std::move(shm_factory), &task_runner,
- init_opts)
- .release()));
- svc->min_write_period_ms_ = 1;
+
+ tracing_service::Dependencies deps;
+
+ auto mock_clock = std::make_unique<NiceMock<MockClock>>();
+ mock_clock_ = mock_clock.get();
+ deps.clock = std::move(mock_clock);
+ ON_CALL(*mock_clock_, GetBootTimeNs).WillByDefault(Invoke([&] {
+ return real_clock_.GetBootTimeNs() + mock_clock_displacement_;
+ }));
+ ON_CALL(*mock_clock_, GetWallTimeNs).WillByDefault(Invoke([&] {
+ return real_clock_.GetWallTimeNs() + mock_clock_displacement_;
+ }));
+
+ auto mock_random = std::make_unique<NiceMock<MockRandom>>();
+ mock_random_ = mock_random.get();
+ deps.random = std::move(mock_random);
+ real_random_ = std::make_unique<tracing_service::RandomImpl>(
+ real_clock_.GetWallTimeMs().count());
+ ON_CALL(*mock_random_, GetValue).WillByDefault(Invoke([&] {
+ return real_random_->GetValue();
+ }));
+
+ svc = std::make_unique<TracingServiceImpl>(
+ std::move(shm_factory), &task_runner, std::move(deps), init_opts);
}
std::unique_ptr<MockProducer> CreateMockProducer() {
@@ -223,95 +263,31 @@
new StrictMock<MockConsumer>(&task_runner));
}
- ProducerID* last_producer_id() { return &svc->last_producer_id_; }
-
- uid_t GetProducerUid(ProducerID producer_id) {
- return svc->GetProducer(producer_id)->uid();
- }
-
- TracingServiceImpl::TracingSession* GetTracingSession(TracingSessionID tsid) {
- auto* session = svc->GetTracingSession(tsid);
- EXPECT_NE(nullptr, session);
- return session;
- }
-
- TracingServiceImpl::TracingSession* tracing_session() {
- return GetTracingSession(GetTracingSessionID());
- }
-
- TracingSessionID GetTracingSessionID() {
- return svc->last_tracing_session_id_;
- }
-
- const std::set<BufferID>& GetAllowedTargetBuffers(ProducerID producer_id) {
- return svc->GetProducer(producer_id)->allowed_target_buffers_;
- }
-
- const std::map<WriterID, BufferID>& GetWriters(ProducerID producer_id) {
- return svc->GetProducer(producer_id)->writers_;
- }
-
- SharedMemoryArbiterImpl* GetShmemArbiterForProducer(ProducerID producer_id) {
- return svc->GetProducer(producer_id)->inproc_shmem_arbiter_.get();
- }
-
- std::unique_ptr<SharedMemoryArbiterImpl> StealShmemArbiterForProducer(
- ProducerID producer_id) {
- return std::move(svc->GetProducer(producer_id)->inproc_shmem_arbiter_);
- }
-
- size_t GetNumPendingFlushes() {
- return tracing_session()->pending_flushes.size();
- }
-
- void WaitForNextSyncMarker() {
- tracing_session()->should_emit_sync_marker = true;
- static int attempt = 0;
- while (tracing_session()->should_emit_sync_marker) {
- auto checkpoint_name = "wait_snapshot_" + std::to_string(attempt++);
- auto timer_expired = task_runner.CreateCheckpoint(checkpoint_name);
- task_runner.PostDelayedTask([timer_expired] { timer_expired(); }, 1);
- task_runner.RunUntilCheckpoint(checkpoint_name);
- }
- }
-
- void WaitForTraceWritersChanged(ProducerID producer_id) {
- static int i = 0;
- auto checkpoint_name = "writers_changed_" + std::to_string(producer_id) +
- "_" + std::to_string(i++);
- auto writers_changed = task_runner.CreateCheckpoint(checkpoint_name);
- auto writers = GetWriters(producer_id);
- std::function<void()> task;
- task = [&task, writers, writers_changed, producer_id, this]() {
- if (writers != GetWriters(producer_id)) {
- writers_changed();
- return;
+ TracingSessionID GetLastTracingSessionId(MockConsumer* consumer) {
+ TracingSessionID ret = 0;
+ TracingServiceState svc_state = consumer->QueryServiceState();
+ for (const auto& session : svc_state.tracing_sessions()) {
+ TracingSessionID id = session.id();
+ if (id > ret) {
+ ret = id;
}
- task_runner.PostDelayedTask(task, 1);
- };
- task_runner.PostDelayedTask(task, 1);
- task_runner.RunUntilCheckpoint(checkpoint_name);
- }
-
- DataSourceInstanceState GetDataSourceInstanceState(const std::string& name) {
- for (const auto& kv : tracing_session()->data_source_instances) {
- if (kv.second.data_source_name == name)
- return kv.second.state;
}
- PERFETTO_FATAL("Can't find data source instance with name %s",
- name.c_str());
+ return ret;
}
- void SetTriggerWindowNs(int64_t window_ns) {
- svc->trigger_window_ns_ = window_ns;
+ void AdvanceTimeAndRunUntilIdle(uint32_t ms) {
+ mock_clock_displacement_ += base::TimeMillis(ms);
+ task_runner.AdvanceTimeAndRunUntilIdle(ms);
}
- void OverrideNextTriggerRandomNumber(double number) {
- svc->trigger_rnd_override_for_testing_ = number;
- }
+ base::TimeNanos mock_clock_displacement_{0};
+ tracing_service::ClockImpl real_clock_;
+ MockClock* mock_clock_; // Owned by svc;
+ std::unique_ptr<tracing_service::RandomImpl> real_random_;
+ MockRandom* mock_random_; // Owned by svc;
base::TestTaskRunner task_runner;
- std::unique_ptr<TracingServiceImpl> svc;
+ std::unique_ptr<TracingService> svc;
};
TEST_F(TracingServiceImplTest, AtMostOneConfig) {
@@ -383,11 +359,15 @@
mock_producer_1->Connect(svc.get(), "mock_producer_1", 123u /* uid */);
mock_producer_2->Connect(svc.get(), "mock_producer_2", 456u /* uid */);
- ASSERT_EQ(2u, svc->num_producers());
- ASSERT_EQ(mock_producer_1->endpoint(), svc->GetProducer(1));
- ASSERT_EQ(mock_producer_2->endpoint(), svc->GetProducer(2));
- ASSERT_EQ(123u, GetProducerUid(1));
- ASSERT_EQ(456u, GetProducerUid(2));
+ std::unique_ptr<MockConsumer> consumer = CreateMockConsumer();
+ consumer->Connect(svc.get());
+
+ TracingServiceState svc_state = consumer->QueryServiceState();
+ ASSERT_EQ(svc_state.producers_size(), 2);
+ EXPECT_EQ(svc_state.producers().at(0).id(), 1);
+ EXPECT_EQ(svc_state.producers().at(0).uid(), 123);
+ EXPECT_EQ(svc_state.producers().at(1).id(), 2);
+ EXPECT_EQ(svc_state.producers().at(1).uid(), 456);
mock_producer_1->RegisterDataSource("foo");
mock_producer_2->RegisterDataSource("bar");
@@ -396,13 +376,15 @@
mock_producer_2->UnregisterDataSource("bar");
mock_producer_1.reset();
- ASSERT_EQ(1u, svc->num_producers());
- ASSERT_EQ(nullptr, svc->GetProducer(1));
+
+ svc_state = consumer->QueryServiceState();
+ ASSERT_EQ(svc_state.producers_size(), 1);
+ EXPECT_EQ(svc_state.producers().at(0).id(), 2);
mock_producer_2.reset();
- ASSERT_EQ(nullptr, svc->GetProducer(2));
- ASSERT_EQ(0u, svc->num_producers());
+ svc_state = consumer->QueryServiceState();
+ ASSERT_EQ(svc_state.producers_size(), 0);
}
TEST_F(TracingServiceImplTest, EnableAndDisableTracing) {
@@ -480,13 +462,11 @@
producer->WaitForDataSourceStop("ds_1");
consumer->WaitForTracingDisabled();
- ASSERT_EQ(1u, tracing_session()->received_triggers.size());
- EXPECT_EQ("trigger_name",
- tracing_session()->received_triggers[0].trigger_name);
-
+ std::vector<protos::gen::TracePacket> trace = consumer->ReadBuffers();
EXPECT_THAT(
- consumer->ReadBuffers(),
+ trace,
HasTriggerMode(protos::gen::TraceConfig::TriggerConfig::START_TRACING));
+ EXPECT_THAT(GetReceivedTriggers(trace), ElementsAre("trigger_name"));
}
// Creates a tracing session with a START_TRACING trigger and checks that the
@@ -773,8 +753,6 @@
producer->WaitForDataSourceSetup("ds_1");
- auto tracing_session_1_id = GetTracingSessionID();
-
(*trace_config.mutable_data_sources())[0].mutable_config()->set_name("ds_2");
trigger = trace_config.mutable_trigger_config()->add_triggers();
trigger->set_name("trigger_name_2");
@@ -784,9 +762,6 @@
producer->WaitForDataSourceSetup("ds_2");
- auto tracing_session_2_id = GetTracingSessionID();
- EXPECT_NE(tracing_session_1_id, tracing_session_2_id);
-
const DataSourceInstanceID id1 = producer->GetDataSourceInstanceId("ds_1");
const DataSourceInstanceID id2 = producer->GetDataSourceInstanceId("ds_2");
@@ -801,24 +776,6 @@
producer->WaitForDataSourceStart("ds_1");
producer->WaitForDataSourceStart("ds_2");
- // Now that they've started we can check the triggers they've seen.
- auto* tracing_session_1 = GetTracingSession(tracing_session_1_id);
- ASSERT_EQ(1u, tracing_session_1->received_triggers.size());
- EXPECT_EQ("trigger_name",
- tracing_session_1->received_triggers[0].trigger_name);
-
- // This is actually dependent on the order in which the triggers were received
- // but there isn't really a better way than iteration order so probably not to
- // brittle of a test. And this caught a real bug in implementation.
- auto* tracing_session_2 = GetTracingSession(tracing_session_2_id);
- ASSERT_EQ(2u, tracing_session_2->received_triggers.size());
-
- EXPECT_EQ("trigger_name",
- tracing_session_2->received_triggers[0].trigger_name);
-
- EXPECT_EQ("trigger_name_2",
- tracing_session_2->received_triggers[1].trigger_name);
-
auto writer1 = producer->CreateTraceWriter("ds_1");
auto writer2 = producer->CreateTraceWriter("ds_2");
@@ -870,12 +827,18 @@
EXPECT_TRUE(flushed_writer_1);
EXPECT_TRUE(flushed_writer_2);
+
+ std::vector<protos::gen::TracePacket> trace1 = consumer_1->ReadBuffers();
EXPECT_THAT(
- consumer_1->ReadBuffers(),
+ trace1,
HasTriggerMode(protos::gen::TraceConfig::TriggerConfig::START_TRACING));
+ EXPECT_THAT(GetReceivedTriggers(trace1), ElementsAre("trigger_name"));
+ std::vector<protos::gen::TracePacket> trace2 = consumer_2->ReadBuffers();
EXPECT_THAT(
- consumer_2->ReadBuffers(),
+ trace2,
HasTriggerMode(protos::gen::TraceConfig::TriggerConfig::START_TRACING));
+ EXPECT_THAT(GetReceivedTriggers(trace2),
+ UnorderedElementsAre("trigger_name", "trigger_name_2"));
}
// Creates a tracing session with a START_TRACING trigger and checks that the
@@ -919,37 +882,14 @@
producer->WaitForDataSourceStop("ds_1");
consumer->WaitForTracingDisabled();
- ASSERT_EQ(1u, tracing_session()->received_triggers.size());
- EXPECT_EQ("trigger_name",
- tracing_session()->received_triggers[0].trigger_name);
-
auto packets = consumer->ReadBuffers();
EXPECT_THAT(
packets,
- Contains(Property(
- &protos::gen::TracePacket::trace_config,
- Property(
- &protos::gen::TraceConfig::trigger_config,
- Property(&protos::gen::TraceConfig::TriggerConfig::trigger_mode,
- Eq(protos::gen::TraceConfig::TriggerConfig::
- START_TRACING))))));
- auto expect_received_trigger = [&](const std::string& name) {
- return Contains(AllOf(
- Property(&protos::gen::TracePacket::trigger,
- AllOf(Property(&protos::gen::Trigger::trigger_name, Eq(name)),
- Property(&protos::gen::Trigger::trusted_producer_uid,
- Eq(123)),
- Property(&protos::gen::Trigger::producer_name,
- Eq("mock_producer")))),
- Property(&protos::gen::TracePacket::trusted_packet_sequence_id,
- Eq(kServicePacketSequenceID))));
- };
- EXPECT_THAT(packets, expect_received_trigger("trigger_name"));
- EXPECT_THAT(packets, Not(expect_received_trigger("trigger_name_2")));
- EXPECT_THAT(packets, Not(expect_received_trigger("trigger_name_3")));
+ HasTriggerMode(protos::gen::TraceConfig::TriggerConfig::START_TRACING));
+ EXPECT_THAT(GetReceivedTriggers(packets), ElementsAre("trigger_name"));
}
-// Creates a tracing session with a START_TRACING trigger and checks that the
+// Creates a tracing session with a STOP_TRACING trigger and checks that the
// received_triggers are emitted as packets.
TEST_F(TracingServiceImplTest, EmitTriggersWithStopTracingTrigger) {
std::unique_ptr<MockConsumer> consumer = CreateMockConsumer();
@@ -992,40 +932,15 @@
producer->WaitForDataSourceStop("ds_1");
consumer->WaitForTracingDisabled();
- ASSERT_EQ(2u, tracing_session()->received_triggers.size());
- EXPECT_EQ("trigger_name",
- tracing_session()->received_triggers[0].trigger_name);
- EXPECT_EQ("trigger_name_3",
- tracing_session()->received_triggers[1].trigger_name);
-
auto packets = consumer->ReadBuffers();
EXPECT_THAT(
packets,
- Contains(Property(
- &protos::gen::TracePacket::trace_config,
- Property(
- &protos::gen::TraceConfig::trigger_config,
- Property(&protos::gen::TraceConfig::TriggerConfig::trigger_mode,
- Eq(protos::gen::TraceConfig::TriggerConfig::
- STOP_TRACING))))));
-
- auto expect_received_trigger = [&](const std::string& name) {
- return Contains(AllOf(
- Property(&protos::gen::TracePacket::trigger,
- AllOf(Property(&protos::gen::Trigger::trigger_name, Eq(name)),
- Property(&protos::gen::Trigger::trusted_producer_uid,
- Eq(321)),
- Property(&protos::gen::Trigger::producer_name,
- Eq("mock_producer")))),
- Property(&protos::gen::TracePacket::trusted_packet_sequence_id,
- Eq(kServicePacketSequenceID))));
- };
- EXPECT_THAT(packets, expect_received_trigger("trigger_name"));
- EXPECT_THAT(packets, Not(expect_received_trigger("trigger_name_2")));
- EXPECT_THAT(packets, expect_received_trigger("trigger_name_3"));
+ HasTriggerMode(protos::gen::TraceConfig::TriggerConfig::STOP_TRACING));
+ EXPECT_THAT(GetReceivedTriggers(packets),
+ UnorderedElementsAre("trigger_name", "trigger_name_3"));
}
-// Creates a tracing session with a START_TRACING trigger and checks that the
+// Creates a tracing session with a STOP_TRACING trigger and checks that the
// received_triggers are emitted as packets even ones after the initial
// ReadBuffers() call.
TEST_F(TracingServiceImplTest, EmitTriggersRepeatedly) {
@@ -1053,16 +968,6 @@
trigger_config->set_trigger_timeout_ms(30000);
- auto expect_received_trigger = [&](const std::string& name) {
- return Contains(AllOf(
- Property(&protos::gen::TracePacket::trigger,
- AllOf(Property(&protos::gen::Trigger::trigger_name, Eq(name)),
- Property(&protos::gen::Trigger::producer_name,
- Eq("mock_producer")))),
- Property(&protos::gen::TracePacket::trusted_packet_sequence_id,
- Eq(kServicePacketSequenceID))));
- };
-
consumer->EnableTracing(trace_config);
producer->WaitForTracingSetup();
producer->WaitForDataSourceSetup("ds_1");
@@ -1075,15 +980,8 @@
auto packets = consumer->ReadBuffers();
EXPECT_THAT(
packets,
- Contains(Property(
- &protos::gen::TracePacket::trace_config,
- Property(
- &protos::gen::TraceConfig::trigger_config,
- Property(&protos::gen::TraceConfig::TriggerConfig::trigger_mode,
- Eq(protos::gen::TraceConfig::TriggerConfig::
- STOP_TRACING))))));
- EXPECT_THAT(packets, expect_received_trigger("trigger_name"));
- EXPECT_THAT(packets, Not(expect_received_trigger("trigger_name_2")));
+ HasTriggerMode(protos::gen::TraceConfig::TriggerConfig::STOP_TRACING));
+ EXPECT_THAT(GetReceivedTriggers(packets), ElementsAre("trigger_name"));
// Send a new trigger.
producer->endpoint()->ActivateTriggers({"trigger_name_2"});
@@ -1093,16 +991,9 @@
producer->WaitForDataSourceStop("ds_1");
consumer->WaitForTracingDisabled();
- ASSERT_EQ(2u, tracing_session()->received_triggers.size());
- EXPECT_EQ("trigger_name",
- tracing_session()->received_triggers[0].trigger_name);
- EXPECT_EQ("trigger_name_2",
- tracing_session()->received_triggers[1].trigger_name);
-
packets = consumer->ReadBuffers();
// We don't rewrite the old trigger.
- EXPECT_THAT(packets, Not(expect_received_trigger("trigger_name")));
- EXPECT_THAT(packets, expect_received_trigger("trigger_name_2"));
+ EXPECT_THAT(GetReceivedTriggers(packets), ElementsAre("trigger_name_2"));
}
// Creates a tracing session with a STOP_TRACING trigger and checks that the
@@ -1126,8 +1017,6 @@
// The trace won't return data because there has been no trigger
EXPECT_THAT(consumer->ReadBuffers(), IsEmpty());
- ASSERT_EQ(0u, tracing_session()->received_triggers.size());
-
consumer->WaitForTracingDisabled();
// The trace won't return data because there has been no trigger
@@ -1198,14 +1087,11 @@
}
producer->ExpectFlush(writer.get());
- ASSERT_EQ(1u, tracing_session()->received_triggers.size());
- EXPECT_EQ("trigger_name",
- tracing_session()->received_triggers[0].trigger_name);
-
producer->WaitForDataSourceStop("ds_1");
consumer->WaitForTracingDisabled();
auto packets = consumer->ReadBuffers();
+ EXPECT_THAT(GetReceivedTriggers(packets), ElementsAre("trigger_name"));
EXPECT_LT(kNumTestPackets, packets.size());
// We expect for the TraceConfig preamble packet to be there correctly and
// then we expect each payload to be there, but not the |large_payload|
@@ -1275,17 +1161,14 @@
auto writer = producer->CreateTraceWriter("ds_1");
producer->ExpectFlush(writer.get());
- ASSERT_EQ(2u, tracing_session()->received_triggers.size());
- EXPECT_EQ("trigger_name",
- tracing_session()->received_triggers[0].trigger_name);
- EXPECT_EQ("trigger_name_2",
- tracing_session()->received_triggers[1].trigger_name);
-
producer->WaitForDataSourceStop("ds_1");
consumer->WaitForTracingDisabled();
+ std::vector<protos::gen::TracePacket> packets = consumer->ReadBuffers();
EXPECT_THAT(
- consumer->ReadBuffers(),
+ packets,
HasTriggerMode(protos::gen::TraceConfig::TriggerConfig::STOP_TRACING));
+ EXPECT_THAT(GetReceivedTriggers(packets),
+ UnorderedElementsAre("trigger_name", "trigger_name_2"));
}
TEST_F(TracingServiceImplTest, SecondTriggerHitsLimit) {
@@ -1323,20 +1206,20 @@
req.push_back("trigger_name");
producer->endpoint()->ActivateTriggers(req);
- ASSERT_EQ(1u, tracing_session()->received_triggers.size());
- EXPECT_EQ("trigger_name",
- tracing_session()->received_triggers[0].trigger_name);
-
auto writer = producer->CreateTraceWriter("data_source_a");
producer->ExpectFlush(writer.get());
producer->WaitForDataSourceStop("data_source_a");
consumer->WaitForTracingDisabled();
+ std::vector<protos::gen::TracePacket> packets = consumer->ReadBuffers();
EXPECT_THAT(
- consumer->ReadBuffers(),
+ packets,
HasTriggerMode(protos::gen::TraceConfig::TriggerConfig::STOP_TRACING));
+ EXPECT_THAT(GetReceivedTriggers(packets), ElementsAre("trigger_name"));
}
+ AdvanceTimeAndRunUntilIdle(23 * 60 * 60 * 1000); // 23h
+
// Second session.
{
std::unique_ptr<MockProducer> producer = CreateMockProducer();
@@ -1357,13 +1240,14 @@
req.push_back("trigger_name");
producer->endpoint()->ActivateTriggers(req);
- ASSERT_EQ(0u, tracing_session()->received_triggers.size());
-
consumer->DisableTracing();
- consumer->FreeBuffers();
producer->WaitForDataSourceStop("data_source_b");
consumer->WaitForTracingDisabled();
+ // When triggers are not hit, the tracing session doesn't return any data.
+ EXPECT_THAT(consumer->ReadBuffers(), IsEmpty());
+
+ consumer->FreeBuffers();
}
}
@@ -1382,10 +1266,6 @@
auto* ds = trace_config.add_data_sources()->mutable_config();
- // Set the trigger window size to something really small so the second
- // session is still allowed through.
- SetTriggerWindowNs(1);
-
// First session.
{
std::unique_ptr<MockProducer> producer = CreateMockProducer();
@@ -1406,22 +1286,19 @@
req.push_back("trigger_name");
producer->endpoint()->ActivateTriggers(req);
- ASSERT_EQ(1u, tracing_session()->received_triggers.size());
- EXPECT_EQ("trigger_name",
- tracing_session()->received_triggers[0].trigger_name);
-
auto writer = producer->CreateTraceWriter("data_source_a");
producer->ExpectFlush(writer.get());
producer->WaitForDataSourceStop("data_source_a");
consumer->WaitForTracingDisabled();
+ std::vector<protos::gen::TracePacket> packets = consumer->ReadBuffers();
EXPECT_THAT(
- consumer->ReadBuffers(),
+ packets,
HasTriggerMode(protos::gen::TraceConfig::TriggerConfig::STOP_TRACING));
+ EXPECT_THAT(GetReceivedTriggers(packets), ElementsAre("trigger_name"));
}
- // Sleep 1 micro so that we're sure that the window time would have elapsed.
- base::SleepMicroseconds(1);
+ AdvanceTimeAndRunUntilIdle(24 * 60 * 60 * 1000); // 24h
// Second session.
{
@@ -1443,18 +1320,16 @@
req.push_back("trigger_name");
producer->endpoint()->ActivateTriggers(req);
- ASSERT_EQ(1u, tracing_session()->received_triggers.size());
- EXPECT_EQ("trigger_name",
- tracing_session()->received_triggers[0].trigger_name);
-
auto writer = producer->CreateTraceWriter("data_source_b");
producer->ExpectFlush(writer.get());
producer->WaitForDataSourceStop("data_source_b");
consumer->WaitForTracingDisabled();
+ std::vector<protos::gen::TracePacket> packets = consumer->ReadBuffers();
EXPECT_THAT(
- consumer->ReadBuffers(),
+ packets,
HasTriggerMode(protos::gen::TraceConfig::TriggerConfig::STOP_TRACING));
+ EXPECT_THAT(GetReceivedTriggers(packets), ElementsAre("trigger_name"));
}
}
@@ -1489,27 +1364,26 @@
req.push_back("trigger_name");
// This is below the probability of 0.15 so should be skipped.
- OverrideNextTriggerRandomNumber(0.14);
+ EXPECT_CALL(*mock_random_, GetValue).WillOnce(Return(0.14));
producer->endpoint()->ActivateTriggers(req);
- ASSERT_EQ(0u, tracing_session()->received_triggers.size());
+ // When triggers are not hit, the tracing session doesn't return any data.
+ EXPECT_THAT(consumer->ReadBuffers(), IsEmpty());
- // This is above the probaility of 0.15 so should be allowed.
- OverrideNextTriggerRandomNumber(0.16);
+ // This is above the probability of 0.15 so should be allowed.
+ EXPECT_CALL(*mock_random_, GetValue).WillOnce(Return(0.16));
producer->endpoint()->ActivateTriggers(req);
auto writer = producer->CreateTraceWriter("data_source");
producer->ExpectFlush(writer.get());
- ASSERT_EQ(1u, tracing_session()->received_triggers.size());
- EXPECT_EQ("trigger_name",
- tracing_session()->received_triggers[0].trigger_name);
-
producer->WaitForDataSourceStop("data_source");
consumer->WaitForTracingDisabled();
+ std::vector<protos::gen::TracePacket> packets = consumer->ReadBuffers();
EXPECT_THAT(
- consumer->ReadBuffers(),
+ packets,
HasTriggerMode(protos::gen::TraceConfig::TriggerConfig::STOP_TRACING));
+ EXPECT_THAT(GetReceivedTriggers(packets), ElementsAre("trigger_name"));
}
// Creates a tracing session with a CLONE_SNAPSHOT trigger and checks that
@@ -1546,7 +1420,7 @@
auto writer = producer->CreateTraceWriter("ds_1");
- TracingSessionID orig_tsid = GetTracingSessionID();
+ std::optional<TracingSessionID> orig_tsid;
// Iterate over a sequence of trigger + CloneSession, to emulate a long trace
// receiving different triggers and being cloned several times.
@@ -1554,10 +1428,6 @@
std::string trigger_name = "trigger_" + std::to_string(iter);
producer->endpoint()->ActivateTriggers({trigger_name});
- auto* orig_session = GetTracingSession(orig_tsid);
- ASSERT_EQ(orig_session->received_triggers.size(), 1u);
- EXPECT_EQ(trigger_name, orig_session->received_triggers[0].trigger_name);
-
// Reading the original trace session should always return nothing. Only the
// cloned sessions should return data.
EXPECT_THAT(consumer->ReadBuffers(), IsEmpty());
@@ -1565,12 +1435,15 @@
// Now clone the session and check that the cloned session has the triggers.
std::unique_ptr<MockConsumer> clone_cons = CreateMockConsumer();
clone_cons->Connect(svc.get());
+ if (!orig_tsid) {
+ orig_tsid = GetLastTracingSessionId(clone_cons.get());
+ }
std::string checkpoint_name = "clone_done_" + std::to_string(iter);
auto clone_done = task_runner.CreateCheckpoint(checkpoint_name);
EXPECT_CALL(*clone_cons, OnSessionCloned(_))
.WillOnce(InvokeWithoutArgs(clone_done));
- clone_cons->CloneSession(orig_tsid);
+ clone_cons->CloneSession(*orig_tsid);
// CloneSession() will implicitly issue a flush. Linearize with that.
producer->ExpectFlush(writer.get());
task_runner.RunUntilCheckpoint(checkpoint_name);
@@ -1828,33 +1701,6 @@
producer->WaitForDataSourceStart("data_source");
}
-TEST_F(TracingServiceImplTest, ProducerIDWrapping) {
- std::vector<std::unique_ptr<MockProducer>> producers;
- producers.push_back(nullptr);
-
- auto connect_producer_and_get_id = [&producers,
- this](const std::string& name) {
- producers.emplace_back(CreateMockProducer());
- producers.back()->Connect(svc.get(), "mock_producer_" + name);
- return *last_producer_id();
- };
-
- // Connect producers 1-4.
- for (ProducerID i = 1; i <= 4; i++)
- ASSERT_EQ(i, connect_producer_and_get_id(std::to_string(i)));
-
- // Disconnect producers 1,3.
- producers[1].reset();
- producers[3].reset();
-
- *last_producer_id() = kMaxProducerID - 1;
- ASSERT_EQ(kMaxProducerID, connect_producer_and_get_id("maxid"));
- ASSERT_EQ(1u, connect_producer_and_get_id("1_again"));
- ASSERT_EQ(3u, connect_producer_and_get_id("3_again"));
- ASSERT_EQ(5u, connect_producer_and_get_id("5"));
- ASSERT_EQ(6u, connect_producer_and_get_id("6"));
-}
-
TEST_F(TracingServiceImplTest, CompressionConfiguredButUnsupported) {
// Initialize the service without support for compression.
TracingService::InitOpts init_opts;
@@ -2531,7 +2377,6 @@
auto flush_req_4 = consumer->Flush(/*timeout_ms=*/10);
task_runner.RunUntilCheckpoint("all_flushes_received");
- ASSERT_EQ(4u, GetNumPendingFlushes());
writer->Flush();
// Reply only to flush 3. Do not reply to 1,2 and 4.
@@ -2757,13 +2602,6 @@
producer->WaitForTracingSetup();
- EXPECT_EQ(GetDataSourceInstanceState("ds_will_ack_1"),
- DataSourceInstanceState::CONFIGURED);
- EXPECT_EQ(GetDataSourceInstanceState("ds_wont_ack"),
- DataSourceInstanceState::CONFIGURED);
- EXPECT_EQ(GetDataSourceInstanceState("ds_will_ack_2"),
- DataSourceInstanceState::CONFIGURED);
-
producer->WaitForDataSourceSetup("ds_will_ack_1");
producer->WaitForDataSourceSetup("ds_wont_ack");
producer->WaitForDataSourceSetup("ds_will_ack_2");
@@ -2777,18 +2615,8 @@
producer->WaitForDataSourceStart("ds_wont_ack");
producer->WaitForDataSourceStart("ds_will_ack_2");
- EXPECT_EQ(GetDataSourceInstanceState("ds_will_ack_1"),
- DataSourceInstanceState::STARTING);
- EXPECT_EQ(GetDataSourceInstanceState("ds_wont_ack"),
- DataSourceInstanceState::STARTED);
- EXPECT_EQ(GetDataSourceInstanceState("ds_will_ack_2"),
- DataSourceInstanceState::STARTED);
-
producer->endpoint()->NotifyDataSourceStarted(id1);
- EXPECT_EQ(GetDataSourceInstanceState("ds_will_ack_1"),
- DataSourceInstanceState::STARTED);
-
std::unique_ptr<TraceWriter> writer =
producer->CreateTraceWriter("ds_wont_ack");
producer->ExpectFlush(writer.get());
@@ -2797,21 +2625,9 @@
producer->WaitForDataSourceStop("ds_wont_ack");
producer->WaitForDataSourceStop("ds_will_ack_2");
- EXPECT_EQ(GetDataSourceInstanceState("ds_will_ack_1"),
- DataSourceInstanceState::STOPPING);
- EXPECT_EQ(GetDataSourceInstanceState("ds_wont_ack"),
- DataSourceInstanceState::STOPPED);
- EXPECT_EQ(GetDataSourceInstanceState("ds_will_ack_2"),
- DataSourceInstanceState::STOPPING);
-
producer->endpoint()->NotifyDataSourceStopped(id1);
producer->endpoint()->NotifyDataSourceStopped(id2);
- EXPECT_EQ(GetDataSourceInstanceState("ds_will_ack_1"),
- DataSourceInstanceState::STOPPED);
- EXPECT_EQ(GetDataSourceInstanceState("ds_will_ack_2"),
- DataSourceInstanceState::STOPPED);
-
// Wait for at most half of the service timeout, so that this test fails if
// the service falls back on calling the OnTracingDisabled() because some of
// the expected acks weren't received.
@@ -2954,7 +2770,8 @@
auto* ds_config = trace_config.add_data_sources()->mutable_config();
ds_config->set_name("data_source");
trace_config.set_write_into_file(true);
- trace_config.set_file_write_period_ms(1);
+ trace_config.set_file_write_period_ms(100);
+ trace_config.mutable_builtin_data_sources()->set_snapshot_interval_ms(100);
base::TempFile tmp_file = base::TempFile::Create();
consumer->EnableTracing(trace_config, base::ScopedFile(dup(tmp_file.fd())));
producer->WaitForTracingSetup();
@@ -2971,7 +2788,8 @@
writer->NewTracePacket()->set_for_testing()->set_str(payload.c_str());
if (i % (100 / kNumMarkers) == 0) {
writer->Flush();
- WaitForNextSyncMarker();
+ // The snapshot will happen every 100ms
+ AdvanceTimeAndRunUntilIdle(100);
}
}
writer->Flush();
@@ -3159,82 +2977,6 @@
Eq(4u)))));
}
-TEST_F(TracingServiceImplTest, AllowedBuffers) {
- std::unique_ptr<MockConsumer> consumer = CreateMockConsumer();
- consumer->Connect(svc.get());
-
- std::unique_ptr<MockProducer> producer1 = CreateMockProducer();
- producer1->Connect(svc.get(), "mock_producer1");
- ProducerID producer1_id = *last_producer_id();
- producer1->RegisterDataSource("data_source1");
- std::unique_ptr<MockProducer> producer2 = CreateMockProducer();
- producer2->Connect(svc.get(), "mock_producer2");
- ProducerID producer2_id = *last_producer_id();
- producer2->RegisterDataSource("data_source2.1");
- producer2->RegisterDataSource("data_source2.2");
- producer2->RegisterDataSource("data_source2.3");
-
- EXPECT_EQ(std::set<BufferID>(), GetAllowedTargetBuffers(producer1_id));
- EXPECT_EQ(std::set<BufferID>(), GetAllowedTargetBuffers(producer2_id));
-
- TraceConfig trace_config;
- trace_config.add_buffers()->set_size_kb(128);
- trace_config.add_buffers()->set_size_kb(128);
- trace_config.add_buffers()->set_size_kb(128);
- auto* ds_config1 = trace_config.add_data_sources()->mutable_config();
- ds_config1->set_name("data_source1");
- ds_config1->set_target_buffer(0);
- auto* ds_config21 = trace_config.add_data_sources()->mutable_config();
- ds_config21->set_name("data_source2.1");
- ds_config21->set_target_buffer(1);
- auto* ds_config22 = trace_config.add_data_sources()->mutable_config();
- ds_config22->set_name("data_source2.2");
- ds_config22->set_target_buffer(2);
- auto* ds_config23 = trace_config.add_data_sources()->mutable_config();
- ds_config23->set_name("data_source2.3");
- ds_config23->set_target_buffer(2); // same buffer as data_source2.2.
- consumer->EnableTracing(trace_config);
-
- producer1->WaitForTracingSetup();
- producer1->WaitForDataSourceSetup("data_source1");
-
- producer2->WaitForTracingSetup();
- producer2->WaitForDataSourceSetup("data_source2.1");
- producer2->WaitForDataSourceSetup("data_source2.2");
- producer2->WaitForDataSourceSetup("data_source2.3");
-
- ASSERT_EQ(3u, tracing_session()->num_buffers());
- std::set<BufferID> expected_buffers_producer1 = {
- tracing_session()->buffers_index[0]};
- std::set<BufferID> expected_buffers_producer2 = {
- tracing_session()->buffers_index[1], tracing_session()->buffers_index[2]};
- EXPECT_EQ(expected_buffers_producer1, GetAllowedTargetBuffers(producer1_id));
- EXPECT_EQ(expected_buffers_producer2, GetAllowedTargetBuffers(producer2_id));
-
- producer1->WaitForDataSourceStart("data_source1");
- producer2->WaitForDataSourceStart("data_source2.1");
- producer2->WaitForDataSourceStart("data_source2.2");
- producer2->WaitForDataSourceStart("data_source2.3");
-
- producer2->UnregisterDataSource("data_source2.3");
- producer2->WaitForDataSourceStop("data_source2.3");
-
- // Should still be allowed to write to buffers 1 (data_source2.1) and 2
- // (data_source2.2).
- EXPECT_EQ(expected_buffers_producer2, GetAllowedTargetBuffers(producer2_id));
-
- consumer->DisableTracing();
- producer1->WaitForDataSourceStop("data_source1");
- producer2->WaitForDataSourceStop("data_source2.1");
- producer2->WaitForDataSourceStop("data_source2.2");
- consumer->WaitForTracingDisabled();
-
- consumer->FreeBuffers();
- task_runner.RunUntilIdle();
- EXPECT_EQ(std::set<BufferID>(), GetAllowedTargetBuffers(producer1_id));
- EXPECT_EQ(std::set<BufferID>(), GetAllowedTargetBuffers(producer2_id));
-}
-
#if !PERFETTO_DCHECK_IS_ON()
TEST_F(TracingServiceImplTest, CommitToForbiddenBufferIsDiscarded) {
std::unique_ptr<MockConsumer> consumer = CreateMockConsumer();
@@ -3242,10 +2984,11 @@
std::unique_ptr<MockProducer> producer = CreateMockProducer();
producer->Connect(svc.get(), "mock_producer");
- ProducerID producer_id = *last_producer_id();
producer->RegisterDataSource("data_source");
- EXPECT_EQ(std::set<BufferID>(), GetAllowedTargetBuffers(producer_id));
+ std::unique_ptr<MockProducer> producer2 = CreateMockProducer();
+ producer2->Connect(svc.get(), "mock_producer_2");
+ producer2->RegisterDataSource("data_source_2");
TraceConfig trace_config;
trace_config.add_buffers()->set_size_kb(128);
@@ -3253,42 +2996,74 @@
auto* ds_config = trace_config.add_data_sources()->mutable_config();
ds_config->set_name("data_source");
ds_config->set_target_buffer(0);
+ ds_config = trace_config.add_data_sources()->mutable_config();
+ ds_config->set_name("data_source_2");
+ ds_config->set_target_buffer(1);
consumer->EnableTracing(trace_config);
- ASSERT_EQ(2u, tracing_session()->num_buffers());
- std::set<BufferID> expected_buffers = {tracing_session()->buffers_index[0]};
- EXPECT_EQ(expected_buffers, GetAllowedTargetBuffers(producer_id));
-
producer->WaitForTracingSetup();
producer->WaitForDataSourceSetup("data_source");
+
+ producer2->WaitForTracingSetup();
+ producer2->WaitForDataSourceSetup("data_source_2");
+
producer->WaitForDataSourceStart("data_source");
+ producer2->WaitForDataSourceStart("data_source_2");
+
+ const auto* ds1 = producer->GetDataSourceInstance("data_source");
+ ASSERT_NE(ds1, nullptr);
+ const auto* ds2 = producer2->GetDataSourceInstance("data_source_2");
+ ASSERT_NE(ds2, nullptr);
+ BufferID buf0 = ds1->target_buffer;
+ BufferID buf1 = ds2->target_buffer;
// Try to write to the correct buffer.
- std::unique_ptr<TraceWriter> writer = producer->endpoint()->CreateTraceWriter(
- tracing_session()->buffers_index[0]);
+ std::unique_ptr<TraceWriter> writer =
+ producer->endpoint()->CreateTraceWriter(buf0);
{
auto tp = writer->NewTracePacket();
tp->set_for_testing()->set_str("good_payload");
}
auto flush_request = consumer->Flush();
- producer->ExpectFlush(writer.get());
+ EXPECT_CALL(*producer, Flush)
+ .WillOnce(Invoke([&](FlushRequestID flush_req_id,
+ const DataSourceInstanceID*, size_t, FlushFlags) {
+ writer->Flush();
+ producer->endpoint()->NotifyFlushComplete(flush_req_id);
+ }));
+ EXPECT_CALL(*producer2, Flush)
+ .WillOnce(Invoke([&](FlushRequestID flush_req_id,
+ const DataSourceInstanceID*, size_t, FlushFlags) {
+ producer2->endpoint()->NotifyFlushComplete(flush_req_id);
+ }));
ASSERT_TRUE(flush_request.WaitForReply());
// Try to write to the wrong buffer.
- writer = producer->endpoint()->CreateTraceWriter(
- tracing_session()->buffers_index[1]);
+ writer = producer->endpoint()->CreateTraceWriter(buf1);
{
auto tp = writer->NewTracePacket();
tp->set_for_testing()->set_str("bad_payload");
}
flush_request = consumer->Flush();
- producer->ExpectFlush(writer.get());
+ EXPECT_CALL(*producer, Flush)
+ .WillOnce(Invoke([&](FlushRequestID flush_req_id,
+ const DataSourceInstanceID*, size_t, FlushFlags) {
+ writer->Flush();
+ producer->endpoint()->NotifyFlushComplete(flush_req_id);
+ }));
+ EXPECT_CALL(*producer2, Flush)
+ .WillOnce(Invoke([&](FlushRequestID flush_req_id,
+ const DataSourceInstanceID*, size_t, FlushFlags) {
+ producer2->endpoint()->NotifyFlushComplete(flush_req_id);
+ }));
+
ASSERT_TRUE(flush_request.WaitForReply());
consumer->DisableTracing();
producer->WaitForDataSourceStop("data_source");
+ producer2->WaitForDataSourceStop("data_source_2");
consumer->WaitForTracingDisabled();
auto packets = consumer->ReadBuffers();
@@ -3301,67 +3076,9 @@
Property(&protos::gen::TestEvent::str, Eq("bad_payload"))))));
consumer->FreeBuffers();
- EXPECT_EQ(std::set<BufferID>(), GetAllowedTargetBuffers(producer_id));
}
#endif // !PERFETTO_DCHECK_IS_ON()
-TEST_F(TracingServiceImplTest, RegisterAndUnregisterTraceWriter) {
- std::unique_ptr<MockConsumer> consumer = CreateMockConsumer();
- consumer->Connect(svc.get());
-
- std::unique_ptr<MockProducer> producer = CreateMockProducer();
- producer->Connect(svc.get(), "mock_producer");
- ProducerID producer_id = *last_producer_id();
- producer->RegisterDataSource("data_source");
-
- EXPECT_TRUE(GetWriters(producer_id).empty());
-
- TraceConfig trace_config;
- trace_config.add_buffers()->set_size_kb(128);
- auto* ds_config = trace_config.add_data_sources()->mutable_config();
- ds_config->set_name("data_source");
- ds_config->set_target_buffer(0);
- consumer->EnableTracing(trace_config);
-
- producer->WaitForTracingSetup();
- producer->WaitForDataSourceSetup("data_source");
- producer->WaitForDataSourceStart("data_source");
-
- // Creating the trace writer should register it with the service.
- std::unique_ptr<TraceWriter> writer = producer->endpoint()->CreateTraceWriter(
- tracing_session()->buffers_index[0]);
-
- WaitForTraceWritersChanged(producer_id);
-
- std::map<WriterID, BufferID> expected_writers;
- expected_writers[writer->writer_id()] = tracing_session()->buffers_index[0];
- EXPECT_EQ(expected_writers, GetWriters(producer_id));
-
- // Verify writing works.
- {
- auto tp = writer->NewTracePacket();
- tp->set_for_testing()->set_str("payload");
- }
-
- auto flush_request = consumer->Flush();
- producer->ExpectFlush(writer.get());
- ASSERT_TRUE(flush_request.WaitForReply());
-
- // Destroying the writer should unregister it.
- writer.reset();
- WaitForTraceWritersChanged(producer_id);
- EXPECT_TRUE(GetWriters(producer_id).empty());
-
- consumer->DisableTracing();
- producer->WaitForDataSourceStop("data_source");
- consumer->WaitForTracingDisabled();
-
- auto packets = consumer->ReadBuffers();
- EXPECT_THAT(packets, Contains(Property(&protos::gen::TracePacket::for_testing,
- Property(&protos::gen::TestEvent::str,
- Eq("payload")))));
-}
-
TEST_F(TracingServiceImplTest, ScrapeBuffersOnFlush) {
svc->SetSMBScrapingEnabled(true);
@@ -3370,7 +3087,6 @@
std::unique_ptr<MockProducer> producer = CreateMockProducer();
producer->Connect(svc.get(), "mock_producer");
- ProducerID producer_id = *last_producer_id();
producer->RegisterDataSource("data_source");
TraceConfig trace_config;
@@ -3384,9 +3100,10 @@
producer->WaitForDataSourceSetup("data_source");
producer->WaitForDataSourceStart("data_source");
- std::unique_ptr<TraceWriter> writer = producer->endpoint()->CreateTraceWriter(
- tracing_session()->buffers_index[0]);
- WaitForTraceWritersChanged(producer_id);
+ std::unique_ptr<TraceWriter> writer =
+ producer->CreateTraceWriter("data_source");
+ // Wait for the writer to be registered.
+ task_runner.RunUntilIdle();
// Write a few trace packets.
writer->NewTracePacket()->set_for_testing()->set_str("payload1");
@@ -3457,7 +3174,6 @@
std::unique_ptr<MockProducer> producer = CreateMockProducer();
producer->Connect(svc.get(), "mock_producer");
- ProducerID producer_id = *last_producer_id();
producer->RegisterDataSource("data_source");
TraceConfig trace_config;
@@ -3471,9 +3187,10 @@
producer->WaitForDataSourceSetup("data_source");
producer->WaitForDataSourceStart("data_source");
- std::unique_ptr<TraceWriter> writer = producer->endpoint()->CreateTraceWriter(
- tracing_session()->buffers_index[0], BufferExhaustedPolicy::kDrop);
- WaitForTraceWritersChanged(producer_id);
+ std::unique_ptr<TraceWriter> writer =
+ producer->CreateTraceWriter("data_source", BufferExhaustedPolicy::kDrop);
+ // Wait for the writer to be registered.
+ task_runner.RunUntilIdle();
std::atomic<bool> packets_written = false;
std::atomic<bool> quit = false;
@@ -3514,8 +3231,19 @@
consumer->Connect(svc.get());
std::unique_ptr<MockProducer> producer = CreateMockProducer();
- producer->Connect(svc.get(), "mock_producer");
- ProducerID producer_id = *last_producer_id();
+
+ static constexpr size_t kShmSizeBytes = 1024 * 1024;
+ static constexpr size_t kShmPageSizeBytes = 4 * 1024;
+
+ TestSharedMemory::Factory factory;
+ auto shm = factory.CreateSharedMemory(kShmSizeBytes);
+
+ // Service should adopt the SMB provided by the producer.
+ producer->Connect(svc.get(), "mock_producer", /*uid=*/42, /*pid=*/1025,
+ /*shared_memory_size_hint_bytes=*/0, kShmPageSizeBytes,
+ TestRefSharedMemory::Create(shm.get()),
+ /*in_process=*/false);
+
producer->RegisterDataSource("data_source");
TraceConfig trace_config;
@@ -3529,9 +3257,20 @@
producer->WaitForDataSourceSetup("data_source");
producer->WaitForDataSourceStart("data_source");
- std::unique_ptr<TraceWriter> writer = producer->endpoint()->CreateTraceWriter(
- tracing_session()->buffers_index[0]);
- WaitForTraceWritersChanged(producer_id);
+ auto client_producer_endpoint = std::make_unique<ProxyProducerEndpoint>();
+ client_producer_endpoint->set_backend(producer->endpoint());
+
+ auto shmem_arbiter = std::make_unique<SharedMemoryArbiterImpl>(
+ shm->start(), shm->size(), SharedMemoryABI::ShmemMode::kDefault,
+ kShmPageSizeBytes, client_producer_endpoint.get(), &task_runner);
+ shmem_arbiter->SetDirectSMBPatchingSupportedByService();
+
+ const auto* ds_inst = producer->GetDataSourceInstance("data_source");
+ ASSERT_NE(nullptr, ds_inst);
+ std::unique_ptr<TraceWriter> writer =
+ shmem_arbiter->CreateTraceWriter(ds_inst->target_buffer);
+ // Wait for the TraceWriter to be registered.
+ task_runner.RunUntilIdle();
// Write a few trace packets.
writer->NewTracePacket()->set_for_testing()->set_str("payload1");
@@ -3539,9 +3278,8 @@
writer->NewTracePacket()->set_for_testing()->set_str("payload3");
// Disconnect the producer without committing the chunk. This should cause a
- // scrape of the SMB. Avoid destroying the ShmemArbiter until writer is
- // destroyed.
- auto shmem_arbiter = StealShmemArbiterForProducer(producer_id);
+ // scrape of the SMB.
+ client_producer_endpoint->set_backend(nullptr);
producer.reset();
// Chunk with the packets should have been scraped.
@@ -3556,9 +3294,6 @@
Property(&protos::gen::TestEvent::str,
Eq("payload3")))));
- // Cleanup writer without causing a crash because the producer already went
- // away.
- static_cast<TraceWriterImpl*>(writer.get())->ResetChunkForTesting();
writer.reset();
shmem_arbiter.reset();
@@ -3574,7 +3309,6 @@
std::unique_ptr<MockProducer> producer = CreateMockProducer();
producer->Connect(svc.get(), "mock_producer");
- ProducerID producer_id = *last_producer_id();
producer->RegisterDataSource("data_source");
TraceConfig trace_config;
@@ -3588,9 +3322,10 @@
producer->WaitForDataSourceSetup("data_source");
producer->WaitForDataSourceStart("data_source");
- std::unique_ptr<TraceWriter> writer = producer->endpoint()->CreateTraceWriter(
- tracing_session()->buffers_index[0]);
- WaitForTraceWritersChanged(producer_id);
+ std::unique_ptr<TraceWriter> writer =
+ producer->CreateTraceWriter("data_source");
+ // Wait for the TraceWriter to be registered.
+ task_runner.RunUntilIdle();
// Write a few trace packets.
writer->NewTracePacket()->set_for_testing()->set_str("payload1");
@@ -3625,8 +3360,19 @@
consumer_ = CreateMockConsumer();
consumer_->Connect(svc.get());
producer_ = CreateMockProducer();
- producer_->Connect(svc.get(), "mock_producer");
- ProducerID producer_id = *last_producer_id();
+
+ static constexpr size_t kShmSizeBytes = 1024 * 1024;
+ static constexpr size_t kShmPageSizeBytes = 4 * 1024;
+
+ TestSharedMemory::Factory factory;
+ shm_ = factory.CreateSharedMemory(kShmSizeBytes);
+
+ // Service should adopt the SMB provided by the producer.
+ producer_->Connect(svc.get(), "mock_producer", /*uid=*/42, /*pid=*/1025,
+ /*shared_memory_size_hint_bytes=*/0, kShmPageSizeBytes,
+ TestRefSharedMemory::Create(shm_.get()),
+ /*in_process=*/false);
+
producer_->RegisterDataSource("data_source");
TraceConfig trace_config;
@@ -3640,11 +3386,19 @@
producer_->WaitForDataSourceSetup("data_source");
producer_->WaitForDataSourceStart("data_source");
- writer_ = producer_->endpoint()->CreateTraceWriter(
- tracing_session()->buffers_index[0]);
- WaitForTraceWritersChanged(producer_id);
+ arbiter_ = std::make_unique<SharedMemoryArbiterImpl>(
+ shm_->start(), shm_->size(), SharedMemoryABI::ShmemMode::kDefault,
+ kShmPageSizeBytes, producer_->endpoint(), &task_runner);
+ arbiter_->SetDirectSMBPatchingSupportedByService();
- arbiter_ = GetShmemArbiterForProducer(producer_id);
+ const auto* ds = producer_->GetDataSourceInstance("data_source");
+ ASSERT_NE(ds, nullptr);
+
+ target_buffer_ = ds->target_buffer;
+
+ writer_ = arbiter_->CreateTraceWriter(target_buffer_);
+ // Wait for the writer to be registered.
+ task_runner.RunUntilIdle();
}
void TearDown() override {
@@ -3659,17 +3413,23 @@
std::optional<std::vector<protos::gen::TracePacket>> FlushAndRead() {
// Scrape: ask the service to flush but don't flush the chunk.
auto flush_request = consumer_->Flush();
- producer_->ExpectFlush(nullptr, /*reply=*/true);
+
+ EXPECT_CALL(*producer_, Flush)
+ .WillOnce(Invoke([&](FlushRequestID flush_req_id,
+ const DataSourceInstanceID*, size_t, FlushFlags) {
+ arbiter_->NotifyFlushComplete(flush_req_id);
+ }));
if (flush_request.WaitForReply()) {
return consumer_->ReadBuffers();
}
return std::nullopt;
}
std::unique_ptr<MockConsumer> consumer_;
+ std::unique_ptr<SharedMemory> shm_;
+ std::unique_ptr<SharedMemoryArbiterImpl> arbiter_;
std::unique_ptr<MockProducer> producer_;
std::unique_ptr<TraceWriter> writer_;
- // Owned by `svc`.
- SharedMemoryArbiterImpl* arbiter_;
+ BufferID target_buffer_{};
struct : public protozero::ScatteredStreamWriter::Delegate {
protozero::ContiguousMemoryRange GetNewBuffer() override {
@@ -3740,8 +3500,7 @@
&protos::gen::TracePacket::for_testing,
Property(&protos::gen::TestEvent::str, Eq("payload1"))))));
- arbiter_->ReturnCompletedChunk(std::move(chunk),
- tracing_session()->buffers_index[0],
+ arbiter_->ReturnCompletedChunk(std::move(chunk), target_buffer_,
&empty_patch_list_);
packets = FlushAndRead();
@@ -3796,8 +3555,7 @@
uint8_t zero_size = 0;
stream_writer.WriteBytesUnsafe(&zero_size, sizeof zero_size);
- arbiter_->ReturnCompletedChunk(std::move(chunk),
- tracing_session()->buffers_index[0],
+ arbiter_->ReturnCompletedChunk(std::move(chunk), target_buffer_,
&empty_patch_list_);
packets = FlushAndRead();
@@ -4441,11 +4199,14 @@
ASSERT_TRUE(flush_request.WaitForReply());
auto packets = consumer->ReadBuffers();
- uint32_t count = 0;
+ uint32_t flush_started_count = 0;
+ uint32_t flush_done_count = 0;
for (const auto& packet : packets) {
- count += packet.service_event().all_data_sources_flushed();
+ flush_started_count += packet.service_event().flush_started();
+ flush_done_count += packet.service_event().all_data_sources_flushed();
}
- ASSERT_EQ(count, 2u);
+ EXPECT_EQ(flush_started_count, 2u);
+ EXPECT_EQ(flush_done_count, 2u);
consumer->DisableTracing();
producer->WaitForDataSourceStop("data_source");
@@ -5483,6 +5244,117 @@
consumer->WaitForTracingDisabled();
}
+TEST_F(TracingServiceImplTest, CloneSessionByName) {
+ // The consumer the creates the initial tracing session.
+ std::unique_ptr<MockConsumer> consumer = CreateMockConsumer();
+ consumer->Connect(svc.get());
+
+ // The consumer that clones it and reads back the data.
+ std::unique_ptr<MockConsumer> consumer2 = CreateMockConsumer();
+ consumer2->Connect(svc.get());
+
+ std::unique_ptr<MockProducer> producer = CreateMockProducer();
+ producer->Connect(svc.get(), "mock_producer");
+
+ producer->RegisterDataSource("ds_1");
+
+ TraceConfig trace_config;
+ trace_config.add_buffers()->set_size_kb(32);
+ trace_config.set_unique_session_name("my_unique_session_name");
+ auto* ds_cfg = trace_config.add_data_sources()->mutable_config();
+ ds_cfg->set_name("ds_1");
+ ds_cfg->set_target_buffer(0);
+
+ consumer->EnableTracing(trace_config);
+ producer->WaitForTracingSetup();
+ producer->WaitForDataSourceSetup("ds_1");
+ producer->WaitForDataSourceStart("ds_1");
+
+ std::unique_ptr<TraceWriter> writer = producer->CreateTraceWriter("ds_1");
+
+ static constexpr size_t kNumTestPackets = 20;
+ for (size_t i = 0; i < kNumTestPackets; i++) {
+ auto tp = writer->NewTracePacket();
+ std::string payload("payload" + std::to_string(i));
+ tp->set_for_testing()->set_str(payload.c_str(), payload.size());
+ tp->set_timestamp(static_cast<uint64_t>(i));
+ }
+
+ {
+ auto clone_done = task_runner.CreateCheckpoint("clone_done");
+ EXPECT_CALL(*consumer2, OnSessionCloned(_))
+ .WillOnce(
+ Invoke([clone_done](const Consumer::OnSessionClonedArgs& args) {
+ ASSERT_TRUE(args.success);
+ ASSERT_TRUE(args.error.empty());
+ clone_done();
+ }));
+ ConsumerEndpoint::CloneSessionArgs args;
+ args.unique_session_name = "my_unique_session_name";
+ consumer2->endpoint()->CloneSession(args);
+ // CloneSession() will implicitly issue a flush. Linearize with that.
+ producer->ExpectFlush(writer.get());
+ task_runner.RunUntilCheckpoint("clone_done");
+ }
+
+ // Disable the initial tracing session.
+ consumer->DisableTracing();
+ producer->WaitForDataSourceStop("ds_1");
+ consumer->WaitForTracingDisabled();
+
+ // Read back the cloned trace and the original trace.
+ auto packets = consumer->ReadBuffers();
+ auto cloned_packets = consumer2->ReadBuffers();
+ for (size_t i = 0; i < kNumTestPackets; i++) {
+ std::string payload = "payload" + std::to_string(i);
+ EXPECT_THAT(packets,
+ Contains(Property(
+ &protos::gen::TracePacket::for_testing,
+ Property(&protos::gen::TestEvent::str, Eq(payload)))));
+ EXPECT_THAT(cloned_packets,
+ Contains(Property(
+ &protos::gen::TracePacket::for_testing,
+ Property(&protos::gen::TestEvent::str, Eq(payload)))));
+ }
+
+ // Delete the original tracing session.
+ consumer->FreeBuffers();
+
+ {
+ std::unique_ptr<MockConsumer> consumer3 = CreateMockConsumer();
+ consumer3->Connect(svc.get());
+
+ // The original session is gone. The cloned session is still there. It
+ // should not be possible to clone that by name.
+
+ auto clone_failed = task_runner.CreateCheckpoint("clone_failed");
+ EXPECT_CALL(*consumer3, OnSessionCloned(_))
+ .WillOnce(
+ Invoke([clone_failed](const Consumer::OnSessionClonedArgs& args) {
+ EXPECT_FALSE(args.success);
+ EXPECT_THAT(args.error, HasSubstr("Tracing session not found"));
+ clone_failed();
+ }));
+ ConsumerEndpoint::CloneSessionArgs args_f;
+ args_f.unique_session_name = "my_unique_session_name";
+ consumer3->endpoint()->CloneSession(args_f);
+ task_runner.RunUntilCheckpoint("clone_failed");
+
+ // But it should be possible to clone that by id.
+ auto clone_success = task_runner.CreateCheckpoint("clone_success");
+ EXPECT_CALL(*consumer3, OnSessionCloned(_))
+ .WillOnce(
+ Invoke([clone_success](const Consumer::OnSessionClonedArgs& args) {
+ EXPECT_TRUE(args.success);
+ clone_success();
+ }));
+ ConsumerEndpoint::CloneSessionArgs args_s;
+ args_s.tsid = GetLastTracingSessionId(consumer3.get());
+ consumer3->endpoint()->CloneSession(args_s);
+ task_runner.RunUntilCheckpoint("clone_success");
+ }
+}
+
TEST_F(TracingServiceImplTest, InvalidBufferSizes) {
std::unique_ptr<MockConsumer> consumer = CreateMockConsumer();
consumer->Connect(svc.get());
@@ -6079,4 +5951,147 @@
task_runner.RunUntilCheckpoint(on_attach_name);
}
+TEST_F(TracingServiceImplTest, SlowStartingDataSources) {
+ std::unique_ptr<MockConsumer> consumer = CreateMockConsumer();
+ consumer->Connect(svc.get());
+
+ std::unique_ptr<MockProducer> producer = CreateMockProducer();
+ producer->Connect(svc.get(), "mock_producer");
+ producer->RegisterDataSource("data_source1", /*ack_stop=*/false,
+ /*ack_start=*/true);
+ producer->RegisterDataSource("data_source2", /*ack_stop=*/false,
+ /*ack_start=*/true);
+ producer->RegisterDataSource("data_source3", /*ack_stop=*/false,
+ /*ack_start=*/true);
+
+ TraceConfig trace_config;
+ trace_config.add_buffers()->set_size_kb(128);
+ trace_config.add_data_sources()->mutable_config()->set_name("data_source1");
+ trace_config.add_data_sources()->mutable_config()->set_name("data_source2");
+ trace_config.add_data_sources()->mutable_config()->set_name("data_source3");
+ consumer->EnableTracing(trace_config);
+
+ producer->WaitForTracingSetup();
+ producer->WaitForDataSourceSetup("data_source1");
+ producer->WaitForDataSourceSetup("data_source2");
+ producer->WaitForDataSourceSetup("data_source3");
+
+ producer->WaitForDataSourceStart("data_source1");
+ producer->WaitForDataSourceStart("data_source2");
+ producer->WaitForDataSourceStart("data_source3");
+
+ DataSourceInstanceID id1 = producer->GetDataSourceInstanceId("data_source1");
+ DataSourceInstanceID id3 = producer->GetDataSourceInstanceId("data_source3");
+
+ producer->endpoint()->NotifyDataSourceStarted(id1);
+ producer->endpoint()->NotifyDataSourceStarted(id3);
+
+ // This matches kAllDataSourceStartedTimeout.
+ AdvanceTimeAndRunUntilIdle(20000);
+
+ consumer->DisableTracing();
+ producer->WaitForDataSourceStop("data_source1");
+ producer->WaitForDataSourceStop("data_source2");
+ producer->WaitForDataSourceStop("data_source3");
+ consumer->WaitForTracingDisabled();
+
+ std::vector<protos::gen::TracePacket> packets = consumer->ReadBuffers();
+ EXPECT_THAT(
+ packets,
+ Contains(Property(
+ &protos::gen::TracePacket::service_event,
+ Property(
+ &protos::gen::TracingServiceEvent::slow_starting_data_sources,
+ Property(
+ &protos::gen::TracingServiceEvent::DataSources::data_source,
+ ElementsAre(
+ Property(&protos::gen::TracingServiceEvent::DataSources::
+ DataSource::data_source_name,
+ "data_source2")))))));
+}
+
+TEST_F(TracingServiceImplTest, FlushTimeoutEventsEmitted) {
+ std::unique_ptr<MockConsumer> consumer = CreateMockConsumer();
+ consumer->Connect(svc.get());
+
+ std::unique_ptr<MockProducer> producer = CreateMockProducer();
+ producer->Connect(svc.get(), "mock_producer1");
+ producer->RegisterDataSource("ds_1");
+
+ TraceConfig trace_config;
+ trace_config.add_buffers()->set_size_kb(1024); // Buf 0.
+ auto* ds_cfg = trace_config.add_data_sources()->mutable_config();
+ ds_cfg->set_name("ds_1");
+ ds_cfg->set_target_buffer(0);
+
+ consumer->EnableTracing(trace_config);
+ producer->WaitForTracingSetup();
+ producer->WaitForDataSourceSetup("ds_1");
+ producer->WaitForDataSourceStart("ds_1");
+
+ std::unique_ptr<TraceWriter> writer1 = producer->CreateTraceWriter("ds_1");
+
+ // Do not reply to Flush.
+ std::string producer_flush1_checkpoint_name = "producer_flush1_requested";
+ auto flush1_requested =
+ task_runner.CreateCheckpoint(producer_flush1_checkpoint_name);
+ EXPECT_CALL(*producer, Flush).WillOnce(Invoke(flush1_requested));
+ consumer->Flush(5000, FlushFlags(FlushFlags::Initiator::kTraced,
+ FlushFlags::Reason::kTraceStop));
+
+ task_runner.RunUntilCheckpoint(producer_flush1_checkpoint_name);
+
+ AdvanceTimeAndRunUntilIdle(5000);
+
+ // ReadBuffers returns a last_flush_slow_data_source event.
+ std::vector<protos::gen::TracePacket> packets = consumer->ReadBuffers();
+ EXPECT_THAT(
+ packets,
+ Contains(Property(
+ &protos::gen::TracePacket::service_event,
+ Property(
+ &protos::gen::TracingServiceEvent::last_flush_slow_data_sources,
+ Property(
+ &protos::gen::TracingServiceEvent::DataSources::data_source,
+ ElementsAre(
+ Property(&protos::gen::TracingServiceEvent::DataSources::
+ DataSource::data_source_name,
+ "ds_1")))))));
+
+ // Reply to Flush.
+ std::string producer_flush2_checkpoint_name = "producer_flush2_requested";
+ auto flush2_requested =
+ task_runner.CreateCheckpoint(producer_flush2_checkpoint_name);
+ FlushRequestID flush2_req_id;
+ EXPECT_CALL(*producer, Flush(_, _, _, _))
+ .WillOnce([&](FlushRequestID req_id, const DataSourceInstanceID*, size_t,
+ FlushFlags) {
+ flush2_req_id = req_id;
+ flush2_requested();
+ });
+ consumer->Flush(5000, FlushFlags(FlushFlags::Initiator::kTraced,
+ FlushFlags::Reason::kTraceStop));
+
+ task_runner.RunUntilCheckpoint(producer_flush2_checkpoint_name);
+
+ producer->endpoint()->NotifyFlushComplete(flush2_req_id);
+
+ AdvanceTimeAndRunUntilIdle(5000);
+
+ // ReadBuffers returns a last_flush_slow_data_source event.
+ packets = consumer->ReadBuffers();
+ EXPECT_THAT(
+ packets,
+ Not(Contains(Property(&protos::gen::TracePacket::service_event,
+ Property(&protos::gen::TracingServiceEvent::
+ has_last_flush_slow_data_sources,
+ Eq(true))))));
+
+ consumer->DisableTracing();
+ producer->WaitForDataSourceStop("ds_1");
+ consumer->WaitForTracingDisabled();
+}
+
+} // namespace
+
} // namespace perfetto
diff --git a/src/tracing/test/BUILD.gn b/src/tracing/test/BUILD.gn
index 47038c2..3acedef 100644
--- a/src/tracing/test/BUILD.gn
+++ b/src/tracing/test/BUILD.gn
@@ -41,6 +41,7 @@
"aligned_buffer_test.h",
"fake_packet.cc",
"fake_packet.h",
+ "test_shared_memory.cc",
"test_shared_memory.h",
"traced_value_test_support.cc",
]
@@ -54,6 +55,8 @@
"mock_producer.cc",
"mock_producer.h",
"mock_producer_endpoint.h",
+ "proxy_producer_endpoint.cc",
+ "proxy_producer_endpoint.h",
]
}
}
diff --git a/src/tracing/test/api_integrationtest.cc b/src/tracing/test/api_integrationtest.cc
index a900c8a..7fc2f70 100644
--- a/src/tracing/test/api_integrationtest.cc
+++ b/src/tracing/test/api_integrationtest.cc
@@ -2045,6 +2045,72 @@
perfetto::TrackEvent::EraseTrackDescriptor(track);
}
+TEST_P(PerfettoApiTest, TrackEventCustomNamedTrack) {
+ // Create a new trace session.
+ auto* tracing_session = NewTraceWithCategories({"bar"});
+ tracing_session->get()->StartBlocking();
+
+ // Declare a custom track and give it a name.
+ uint64_t async_id = 123;
+
+ // Start events on one thread and end them on another.
+ TRACE_EVENT_BEGIN("bar", "AsyncEvent",
+ perfetto::NamedTrack("MyCustomTrack", async_id),
+ "debug_arg", 123);
+
+ TRACE_EVENT_BEGIN("bar", "SubEvent",
+ perfetto::NamedTrack("MyCustomTrack", async_id),
+ [](perfetto::EventContext) {});
+ const auto main_thread_track = perfetto::NamedTrack(
+ "MyCustomTrack", async_id, perfetto::ThreadTrack::Current());
+ std::thread thread([&] {
+ TRACE_EVENT_END("bar", perfetto::NamedTrack("MyCustomTrack", async_id));
+ TRACE_EVENT_END("bar", perfetto::NamedTrack("MyCustomTrack", async_id),
+ "arg1", false, "arg2", true);
+ const auto thread_track = perfetto::NamedTrack(
+ "MyCustomTrack", async_id, perfetto::ThreadTrack::Current());
+ // Thread-scoped tracks will have different uuids on different thread even
+ // if the id matches.
+ ASSERT_NE(main_thread_track.uuid, thread_track.uuid);
+ });
+ thread.join();
+
+ auto trace = StopSessionAndReturnParsedTrace(tracing_session);
+
+ // Check that the track uuids match on the begin and end events.
+ const auto track = perfetto::NamedTrack("MyCustomTrack", async_id);
+ uint32_t main_thread_sequence = GetMainThreadPacketSequenceId(trace);
+ int event_count = 0;
+ bool found_descriptor = false;
+ for (const auto& packet : trace.packet()) {
+ if (packet.has_track_descriptor() &&
+ !packet.track_descriptor().has_process() &&
+ !packet.track_descriptor().has_thread()) {
+ auto td = packet.track_descriptor();
+ EXPECT_EQ("MyCustomTrack", td.static_name());
+ EXPECT_EQ(track.uuid, td.uuid());
+ EXPECT_EQ(perfetto::ProcessTrack::Current().uuid, td.parent_uuid());
+ found_descriptor = true;
+ continue;
+ }
+
+ if (!packet.has_track_event())
+ continue;
+ auto track_event = packet.track_event();
+ if (track_event.type() ==
+ perfetto::protos::gen::TrackEvent::TYPE_SLICE_BEGIN) {
+ EXPECT_EQ(main_thread_sequence, packet.trusted_packet_sequence_id());
+ EXPECT_EQ(track.uuid, track_event.track_uuid());
+ } else {
+ EXPECT_NE(main_thread_sequence, packet.trusted_packet_sequence_id());
+ EXPECT_EQ(track.uuid, track_event.track_uuid());
+ }
+ event_count++;
+ }
+ EXPECT_TRUE(found_descriptor);
+ EXPECT_EQ(4, event_count);
+}
+
TEST_P(PerfettoApiTest, TrackEventCustomTimestampClock) {
// Create a new trace session.
auto* tracing_session = NewTraceWithCategories({"foo"});
@@ -6306,6 +6372,41 @@
data_source->handle_stop_asynchronously = false;
}
+TEST_P(PerfettoApiTest, CloneSession) {
+ perfetto::TraceConfig cfg;
+ cfg.set_unique_session_name("test_session");
+ auto* tracing_session = NewTraceWithCategories({"test"}, {}, cfg);
+ tracing_session->get()->StartBlocking();
+
+ TRACE_EVENT_BEGIN("test", "TestEvent");
+ TRACE_EVENT_END("test");
+
+ sessions_.emplace_back();
+ TestTracingSessionHandle* other_tracing_session = &sessions_.back();
+ other_tracing_session->session =
+ perfetto::Tracing::NewTrace(/*backend_type=*/GetParam());
+
+ WaitableTestEvent session_cloned;
+ other_tracing_session->get()->CloneTrace(
+ {"test_session"}, [&](perfetto::TracingSession::CloneTraceCallbackArgs) {
+ session_cloned.Notify();
+ });
+ session_cloned.Wait();
+
+ {
+ std::vector<char> raw_trace =
+ other_tracing_session->get()->ReadTraceBlocking();
+ std::string trace(raw_trace.data(), raw_trace.size());
+ EXPECT_THAT(trace, HasSubstr("TestEvent"));
+ }
+
+ {
+ std::vector<char> raw_trace = StopSessionAndReturnBytes(tracing_session);
+ std::string trace(raw_trace.data(), raw_trace.size());
+ EXPECT_THAT(trace, HasSubstr("TestEvent"));
+ }
+}
+
class PerfettoStartupTracingApiTest : public PerfettoApiTest {
public:
using SetupStartupTracingOpts = perfetto::Tracing::SetupStartupTracingOpts;
diff --git a/src/tracing/test/mock_consumer.cc b/src/tracing/test/mock_consumer.cc
index 0171bea..09c1f24 100644
--- a/src/tracing/test/mock_consumer.cc
+++ b/src/tracing/test/mock_consumer.cc
@@ -79,7 +79,9 @@
}
void MockConsumer::CloneSession(TracingSessionID tsid) {
- service_endpoint_->CloneSession(tsid, {});
+ ConsumerEndpoint::CloneSessionArgs args;
+ args.tsid = tsid;
+ service_endpoint_->CloneSession(args);
}
void MockConsumer::WaitForTracingDisabledWithError(
diff --git a/src/tracing/test/mock_producer.cc b/src/tracing/test/mock_producer.cc
index 501afc3..ca36075 100644
--- a/src/tracing/test/mock_producer.cc
+++ b/src/tracing/test/mock_producer.cc
@@ -73,13 +73,15 @@
pid_t pid,
size_t shared_memory_size_hint_bytes,
size_t shared_memory_page_size_hint_bytes,
- std::unique_ptr<SharedMemory> shm) {
+ std::unique_ptr<SharedMemory> shm,
+ bool in_process) {
producer_name_ = producer_name;
- service_endpoint_ = svc->ConnectProducer(
- this, ClientIdentity(uid, pid), producer_name,
- shared_memory_size_hint_bytes,
- /*in_process=*/true, TracingService::ProducerSMBScrapingMode::kDefault,
- shared_memory_page_size_hint_bytes, std::move(shm));
+ service_endpoint_ =
+ svc->ConnectProducer(this, ClientIdentity(uid, pid), producer_name,
+ shared_memory_size_hint_bytes,
+ /*in_process=*/in_process,
+ TracingService::ProducerSMBScrapingMode::kDefault,
+ shared_memory_page_size_hint_bytes, std::move(shm));
auto checkpoint_name = "on_producer_connect_" + producer_name;
auto on_connect = task_runner_->CreateCheckpoint(checkpoint_name);
EXPECT_CALL(*this, OnConnect()).WillOnce(Invoke(on_connect));
@@ -190,10 +192,11 @@
}
std::unique_ptr<TraceWriter> MockProducer::CreateTraceWriter(
- const std::string& data_source_name) {
+ const std::string& data_source_name,
+ BufferExhaustedPolicy buffer_exhausted_policy) {
PERFETTO_DCHECK(data_source_instances_.count(data_source_name));
BufferID buf_id = data_source_instances_[data_source_name].target_buffer;
- return service_endpoint_->CreateTraceWriter(buf_id);
+ return service_endpoint_->CreateTraceWriter(buf_id, buffer_exhausted_policy);
}
void MockProducer::ExpectFlush(TraceWriter* writer_to_flush,
diff --git a/src/tracing/test/mock_producer.h b/src/tracing/test/mock_producer.h
index f3fab40..fcd678a 100644
--- a/src/tracing/test/mock_producer.h
+++ b/src/tracing/test/mock_producer.h
@@ -51,7 +51,8 @@
pid_t pid = 1025,
size_t shared_memory_size_hint_bytes = 0,
size_t shared_memory_page_size_hint_bytes = 0,
- std::unique_ptr<SharedMemory> shm = nullptr);
+ std::unique_ptr<SharedMemory> shm = nullptr,
+ bool in_process = true);
void RegisterDataSource(const std::string& name,
bool ack_stop = false,
bool ack_start = false,
@@ -73,7 +74,9 @@
DataSourceInstanceID GetDataSourceInstanceId(const std::string& name);
const EnabledDataSource* GetDataSourceInstance(const std::string& name);
std::unique_ptr<TraceWriter> CreateTraceWriter(
- const std::string& data_source_name);
+ const std::string& data_source_name,
+ BufferExhaustedPolicy buffer_exhausted_policy =
+ BufferExhaustedPolicy::kDefault);
// Expect a flush. Flushes |writer_to_flush| if non-null. If |reply| is true,
// replies to the flush request, otherwise ignores it and doesn't reply.
diff --git a/src/tracing/test/proxy_producer_endpoint.cc b/src/tracing/test/proxy_producer_endpoint.cc
new file mode 100644
index 0000000..31dca1e
--- /dev/null
+++ b/src/tracing/test/proxy_producer_endpoint.cc
@@ -0,0 +1,134 @@
+/*
+ * 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.
+ */
+
+#include "src/tracing/test/proxy_producer_endpoint.h"
+
+#include "perfetto/ext/tracing/core/trace_writer.h"
+
+namespace perfetto {
+
+ProxyProducerEndpoint::~ProxyProducerEndpoint() = default;
+
+void ProxyProducerEndpoint::Disconnect() {
+ if (!backend_) {
+ return;
+ }
+ backend_->Disconnect();
+}
+void ProxyProducerEndpoint::RegisterDataSource(
+ const DataSourceDescriptor& dsd) {
+ if (!backend_) {
+ return;
+ }
+ backend_->RegisterDataSource(dsd);
+}
+void ProxyProducerEndpoint::UpdateDataSource(const DataSourceDescriptor& dsd) {
+ if (!backend_) {
+ return;
+ }
+ backend_->UpdateDataSource(dsd);
+}
+void ProxyProducerEndpoint::UnregisterDataSource(const std::string& name) {
+ if (!backend_) {
+ return;
+ }
+ backend_->UnregisterDataSource(name);
+}
+void ProxyProducerEndpoint::RegisterTraceWriter(uint32_t writer_id,
+ uint32_t target_buffer) {
+ if (!backend_) {
+ return;
+ }
+ backend_->RegisterTraceWriter(writer_id, target_buffer);
+}
+void ProxyProducerEndpoint::UnregisterTraceWriter(uint32_t writer_id) {
+ if (!backend_) {
+ return;
+ }
+ backend_->UnregisterTraceWriter(writer_id);
+}
+void ProxyProducerEndpoint::CommitData(const CommitDataRequest& req,
+ CommitDataCallback callback) {
+ if (!backend_) {
+ return;
+ }
+ backend_->CommitData(req, callback);
+}
+SharedMemory* ProxyProducerEndpoint::shared_memory() const {
+ if (!backend_) {
+ return nullptr;
+ }
+ return backend_->shared_memory();
+}
+size_t ProxyProducerEndpoint::shared_buffer_page_size_kb() const {
+ if (!backend_) {
+ return 0;
+ }
+ return backend_->shared_buffer_page_size_kb();
+}
+std::unique_ptr<TraceWriter> ProxyProducerEndpoint::CreateTraceWriter(
+ BufferID target_buffer,
+ BufferExhaustedPolicy buffer_exhausted_policy) {
+ if (!backend_) {
+ return nullptr;
+ }
+ return backend_->CreateTraceWriter(target_buffer, buffer_exhausted_policy);
+}
+SharedMemoryArbiter* ProxyProducerEndpoint::MaybeSharedMemoryArbiter() {
+ if (!backend_) {
+ return nullptr;
+ }
+ return backend_->MaybeSharedMemoryArbiter();
+}
+bool ProxyProducerEndpoint::IsShmemProvidedByProducer() const {
+ if (!backend_) {
+ return false;
+ }
+ return backend_->IsShmemProvidedByProducer();
+}
+void ProxyProducerEndpoint::NotifyFlushComplete(FlushRequestID id) {
+ if (!backend_) {
+ return;
+ }
+ backend_->NotifyFlushComplete(id);
+}
+void ProxyProducerEndpoint::NotifyDataSourceStarted(DataSourceInstanceID id) {
+ if (!backend_) {
+ return;
+ }
+ backend_->NotifyDataSourceStarted(id);
+}
+void ProxyProducerEndpoint::NotifyDataSourceStopped(DataSourceInstanceID id) {
+ if (!backend_) {
+ return;
+ }
+ backend_->NotifyDataSourceStopped(id);
+}
+void ProxyProducerEndpoint::ActivateTriggers(
+ const std::vector<std::string>& triggers) {
+ if (!backend_) {
+ return;
+ }
+ backend_->ActivateTriggers(triggers);
+}
+void ProxyProducerEndpoint::Sync(std::function<void()> callback) {
+ if (!backend_) {
+ return;
+ }
+ backend_->Sync(callback);
+}
+
+} // namespace perfetto
diff --git a/src/tracing/test/proxy_producer_endpoint.h b/src/tracing/test/proxy_producer_endpoint.h
new file mode 100644
index 0000000..708151f
--- /dev/null
+++ b/src/tracing/test/proxy_producer_endpoint.h
@@ -0,0 +1,66 @@
+/*
+ * 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.
+ */
+
+#ifndef SRC_TRACING_TEST_PROXY_PRODUCER_ENDPOINT_H_
+#define SRC_TRACING_TEST_PROXY_PRODUCER_ENDPOINT_H_
+
+#include "perfetto/ext/tracing/core/tracing_service.h"
+
+namespace perfetto {
+
+// A "proxy" ProducerEndpoint that forwards all the requests to a real
+// (`backend_`) ProducerEndpoint endpoint or drops them if (`backend_`) is
+// nullptr.
+class ProxyProducerEndpoint : public ProducerEndpoint {
+ public:
+ ~ProxyProducerEndpoint() override;
+
+ // `backend` is not owned.
+ void set_backend(ProducerEndpoint* backend) { backend_ = backend; }
+
+ ProducerEndpoint* backend() const { return backend_; }
+
+ // Begin ProducerEndpoint implementation
+ void Disconnect() override;
+ void RegisterDataSource(const DataSourceDescriptor&) override;
+ void UpdateDataSource(const DataSourceDescriptor&) override;
+ void UnregisterDataSource(const std::string& name) override;
+ void RegisterTraceWriter(uint32_t writer_id, uint32_t target_buffer) override;
+ void UnregisterTraceWriter(uint32_t writer_id) override;
+ void CommitData(const CommitDataRequest&,
+ CommitDataCallback callback = {}) override;
+ SharedMemory* shared_memory() const override;
+ size_t shared_buffer_page_size_kb() const override;
+ std::unique_ptr<TraceWriter> CreateTraceWriter(
+ BufferID target_buffer,
+ BufferExhaustedPolicy buffer_exhausted_policy =
+ BufferExhaustedPolicy::kDefault) override;
+ SharedMemoryArbiter* MaybeSharedMemoryArbiter() override;
+ bool IsShmemProvidedByProducer() const override;
+ void NotifyFlushComplete(FlushRequestID) override;
+ void NotifyDataSourceStarted(DataSourceInstanceID) override;
+ void NotifyDataSourceStopped(DataSourceInstanceID) override;
+ void ActivateTriggers(const std::vector<std::string>&) override;
+ void Sync(std::function<void()> callback) override;
+ // End ProducerEndpoint implementation
+
+ private:
+ ProducerEndpoint* backend_ = nullptr;
+};
+
+} // namespace perfetto
+
+#endif // SRC_TRACING_TEST_PROXY_PRODUCER_ENDPOINT_H_
diff --git a/protos/perfetto/metrics/android/android_trusty_workqueues.proto b/src/tracing/test/test_shared_memory.cc
similarity index 62%
copy from protos/perfetto/metrics/android/android_trusty_workqueues.proto
copy to src/tracing/test/test_shared_memory.cc
index b7a1d5a..509c8e0 100644
--- a/protos/perfetto/metrics/android/android_trusty_workqueues.proto
+++ b/src/tracing/test/test_shared_memory.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
@@ -14,9 +14,17 @@
* limitations under the License.
*/
-syntax = "proto2";
+#include "src/tracing/test/test_shared_memory.h"
-package perfetto.protos;
+namespace perfetto {
-// Metric used to generate a simplified view of the Trusty kworker events.
-message AndroidTrustyWorkqueues {}
+TestRefSharedMemory::~TestRefSharedMemory() = default;
+
+const void* TestRefSharedMemory::start() const {
+ return start_;
+}
+size_t TestRefSharedMemory::size() const {
+ return size_;
+}
+
+} // namespace perfetto
diff --git a/src/tracing/test/test_shared_memory.h b/src/tracing/test/test_shared_memory.h
index e932ad6..2c62b9b 100644
--- a/src/tracing/test/test_shared_memory.h
+++ b/src/tracing/test/test_shared_memory.h
@@ -21,7 +21,6 @@
#include <memory>
-#include "perfetto/ext/base/paged_memory.h"
#include "perfetto/ext/tracing/core/shared_memory.h"
#include "src/tracing/core/in_process_shared_memory.h"
@@ -31,6 +30,33 @@
// (just a wrapper around malloc() that fits the SharedMemory API).
using TestSharedMemory = InProcessSharedMemory;
+// An implementation of the SharedMemory that doesn't own any memory, but just
+// points to memory owned by another SharedMemory.
+//
+// This is useful to test two components that own separate SharedMemory that
+// really point to the same memory underneath without setting up real posix
+// shared memory.
+class TestRefSharedMemory : public SharedMemory {
+ public:
+ // N.B. `*mem` must outlive `*this`.
+ explicit TestRefSharedMemory(SharedMemory* mem)
+ : start_(mem->start()), size_(mem->size()) {}
+ ~TestRefSharedMemory() override;
+
+ static std::unique_ptr<TestRefSharedMemory> Create(SharedMemory* mem) {
+ return std::make_unique<TestRefSharedMemory>(mem);
+ }
+
+ // SharedMemory implementation.
+ using SharedMemory::start; // Equal priority to const and non-const versions
+ const void* start() const override;
+ size_t size() const override;
+
+ private:
+ void* start_;
+ size_t size_;
+};
+
} // namespace perfetto
#endif // SRC_TRACING_TEST_TEST_SHARED_MEMORY_H_
diff --git a/src/tracing/test/tracing_integration_test.cc b/src/tracing/test/tracing_integration_test.cc
index 45def13..b9c2bac 100644
--- a/src/tracing/test/tracing_integration_test.cc
+++ b/src/tracing/test/tracing_integration_test.cc
@@ -200,35 +200,6 @@
return TracingService::ProducerSMBScrapingMode::kDefault;
}
- void WaitForTraceWritersChanged(ProducerID producer_id) {
- static int i = 0;
- auto checkpoint_name = "writers_changed_" + std::to_string(producer_id) +
- "_" + std::to_string(i++);
- auto writers_changed = task_runner_->CreateCheckpoint(checkpoint_name);
- auto writers = GetWriters(producer_id);
- std::function<void()> task;
- task = [&task, writers, writers_changed, producer_id, this]() {
- if (writers != GetWriters(producer_id)) {
- writers_changed();
- return;
- }
- task_runner_->PostDelayedTask(task, 1);
- };
- task_runner_->PostDelayedTask(task, 1);
- task_runner_->RunUntilCheckpoint(checkpoint_name);
- }
-
- const std::map<WriterID, BufferID>& GetWriters(ProducerID producer_id) {
- return reinterpret_cast<TracingServiceImpl*>(svc_->service())
- ->GetProducer(producer_id)
- ->writers_;
- }
-
- ProducerID* last_producer_id() {
- return &reinterpret_cast<TracingServiceImpl*>(svc_->service())
- ->last_producer_id_;
- }
-
std::unique_ptr<base::TestTaskRunner> task_runner_;
std::unique_ptr<ServiceIPCHost> svc_;
std::unique_ptr<TracingService::ProducerEndpoint> producer_endpoint_;
@@ -515,7 +486,7 @@
ASSERT_TRUE(writer);
// Wait for the writer to be registered.
- WaitForTraceWritersChanged(*last_producer_id());
+ task_runner_->RunUntilIdle();
// Write a few trace packets.
writer->NewTracePacket()->set_for_testing()->set_str("payload1");
diff --git a/src/tracing/tracing.cc b/src/tracing/tracing.cc
index a0ab7b2..10e0992 100644
--- a/src/tracing/tracing.cc
+++ b/src/tracing/tracing.cc
@@ -126,6 +126,8 @@
TracingSession::~TracingSession() = default;
+void TracingSession::CloneTrace(CloneTraceArgs, CloneTraceCallback) {}
+
// Can be called from any thread.
bool TracingSession::FlushBlocking(uint32_t timeout_ms) {
std::atomic<bool> flush_result;
diff --git a/src/tracing/track.cc b/src/tracing/track.cc
index dc02609..152c39e 100644
--- a/src/tracing/track.cc
+++ b/src/tracing/track.cc
@@ -116,6 +116,21 @@
desc->AppendRawProtoBytes(bytes.data(), bytes.size());
}
+protos::gen::TrackDescriptor NamedTrack::Serialize() const {
+ auto desc = Track::Serialize();
+ if (static_name_) {
+ desc.set_static_name(static_name_.value);
+ } else {
+ desc.set_name(dynamic_name_.value);
+ }
+ return desc;
+}
+
+void NamedTrack::Serialize(protos::pbzero::TrackDescriptor* desc) const {
+ auto bytes = Serialize().SerializeAsString();
+ desc->AppendRawProtoBytes(bytes.data(), bytes.size());
+}
+
protos::gen::TrackDescriptor CounterTrack::Serialize() const {
auto desc = Track::Serialize();
auto* counter = desc.mutable_counter();
diff --git a/test/ci/common.sh b/test/ci/common.sh
index c28dff6..bdabb51 100644
--- a/test/ci/common.sh
+++ b/test/ci/common.sh
@@ -37,7 +37,7 @@
tools/check_sql_modules.py
tools/check_sql_metrics.py
-if !(git diff --name-only HEAD^1 HEAD | egrep -qv '^(ui|docs|infra)/'); then
+if !(git diff --name-only HEAD^1 HEAD | egrep -qv '^(ui|docs|infra|test/data/ui-screenshots)/'); then
export UI_DOCS_INFRA_ONLY_CHANGE=1
else
export UI_DOCS_INFRA_ONLY_CHANGE=0
diff --git a/test/cmdline_integrationtest.cc b/test/cmdline_integrationtest.cc
index 5094455..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,57 +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);
- trace_config.set_allow_user_build_tracing(true);
+ 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);
@@ -602,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);
@@ -667,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");
@@ -677,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);
@@ -773,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);
@@ -832,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");
@@ -842,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,
@@ -854,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);
@@ -911,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");
@@ -932,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,6 +918,81 @@
/*use_explicit_clone=*/true);
}
+TEST_F(PerfettoCmdlineTest, CloneByName) {
+ protos::gen::TraceConfig trace_config =
+ CreateTraceConfigForTest(kTestMessageCount, kTestMessageSize);
+ trace_config.set_unique_session_name("my_unique_session_name");
+
+ // We have to construct all the processes we want to fork before we start the
+ // service with |StartServiceIfRequired()|. this is because it is unsafe
+ // (could deadlock) to fork after we've spawned some threads which might
+ // printf (and thus hold locks).
+ const std::string path = RandomTraceFileName();
+ ScopedFileRemove remove_on_test_exit(path);
+ auto perfetto_proc = ExecPerfetto(
+ {
+ "-o",
+ path,
+ "-c",
+ "-",
+ },
+ trace_config.SerializeAsString());
+
+ const std::string path_cloned = RandomTraceFileName();
+ ScopedFileRemove path_cloned_remove(path_cloned);
+ auto perfetto_proc_clone = ExecPerfetto({
+ "-o",
+ path_cloned,
+ "--clone-by-name",
+ "my_unique_session_name",
+ });
+
+ const std::string path_cloned_2 = RandomTraceFileName();
+ ScopedFileRemove path_cloned_2_remove(path_cloned_2);
+ auto perfetto_proc_clone_2 = ExecPerfetto({
+ "-o",
+ path_cloned_2,
+ "--clone-by-name",
+ "non_existing_session_name",
+ });
+
+ // Start the service and connect a simple fake producer.
+ StartServiceIfRequiredNoNewExecsAfterThis();
+ auto* fake_producer = test_helper().ConnectFakeProducer();
+ EXPECT_TRUE(fake_producer);
+
+ std::thread background_trace([&perfetto_proc]() {
+ std::string stderr_str;
+ EXPECT_EQ(0, perfetto_proc.Run(&stderr_str)) << stderr_str;
+ });
+
+ test_helper().WaitForProducerEnabled();
+
+ 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");
+
+ EXPECT_EQ(0, perfetto_proc_clone.Run(&stderr_)) << "stderr: " << stderr_;
+ EXPECT_TRUE(base::FileExists(path_cloned));
+
+ // The command still returns 0, but doesn't create a file.
+ EXPECT_EQ(0, perfetto_proc_clone_2.Run(&stderr_)) << "stderr: " << stderr_;
+ EXPECT_FALSE(base::FileExists(path_cloned_2));
+
+ protos::gen::Trace cloned_trace;
+ ASSERT_TRUE(ParseNotEmptyTraceFromFile(path_cloned, cloned_trace));
+ ExpectTraceContainsTestMessages(cloned_trace, kTestMessageCount);
+ ExpectTraceContainsTestMessagesWithSize(cloned_trace, kTestMessageSize);
+
+ perfetto_proc.SendSigterm();
+ background_trace.join();
+
+ protos::gen::Trace trace;
+ ASSERT_TRUE(ParseNotEmptyTraceFromFile(path, trace));
+ ExpectTraceContainsTestMessages(trace, kTestMessageCount);
+ ExpectTraceContainsTestMessagesWithSize(trace, kTestMessageSize);
+}
+
// Regression test for b/279753347: --save-for-bugreport would create an empty
// file if no session with bugreport_score was active.
TEST_F(PerfettoCmdlineTest, UnavailableBugreportLeavesNoEmptyFiles) {
@@ -1098,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,
@@ -1124,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);
@@ -1176,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/cts/heapprofd_java_test_cts.cc b/test/cts/heapprofd_java_test_cts.cc
index ff0e759..1a492b7 100644
--- a/test/cts/heapprofd_java_test_cts.cc
+++ b/test/cts/heapprofd_java_test_cts.cc
@@ -72,7 +72,7 @@
StopApp(app_name, "old.app.stopped", &task_runner);
task_runner.RunUntilCheckpoint("old.app.stopped", 10000 /*ms*/);
}
- StartAppActivity(app_name, "MainActivity", "target.app.running", &task_runner,
+ StartAppActivity(app_name, "NoopActivity", "target.app.running", &task_runner,
/*delay_ms=*/100);
task_runner.RunUntilCheckpoint("target.app.running", 10000 /*ms*/);
// If we try to dump too early in app initialization, we sometimes deadlock.
@@ -219,7 +219,7 @@
StopApp(app_name, "old.app.stopped", &task_runner);
task_runner.RunUntilCheckpoint("old.app.stopped", 10000 /*ms*/);
}
- StartAppActivity(app_name, "MainActivity", "target.app.running", &task_runner,
+ StartAppActivity(app_name, "NoopActivity", "target.app.running", &task_runner,
/*delay_ms=*/100);
task_runner.RunUntilCheckpoint("target.app.running", 10000 /*ms*/);
// If we try to dump too early in app initialization, we sometimes deadlock.
diff --git a/test/cts/reporter/reporter_test_cts.cc b/test/cts/reporter/reporter_test_cts.cc
index 8745dcf..c2a2e7b 100644
--- a/test/cts/reporter/reporter_test_cts.cc
+++ b/test/cts/reporter/reporter_test_cts.cc
@@ -41,7 +41,6 @@
TraceConfig trace_config;
trace_config.add_buffers()->set_size_kb(1024);
trace_config.set_duration_ms(200);
- trace_config.set_allow_user_build_tracing(true);
trace_config.set_unique_session_name("TestEndToEndReport");
// Make the trace as small as possible (see b/282508742).
diff --git a/test/cts/test_apps/AndroidManifest_debuggable.xml b/test/cts/test_apps/AndroidManifest_debuggable.xml
index 79993f8..e2fe258 100755
--- a/test/cts/test_apps/AndroidManifest_debuggable.xml
+++ b/test/cts/test_apps/AndroidManifest_debuggable.xml
@@ -18,7 +18,20 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.perfetto.cts.app.debuggable">
- <application android:debuggable="true">
+ <!-- vmSafeMode="true" disables the JIT.
+
+ HeapprofdJavaCtsTest cover Java heap dumps.
+
+ Java heap dumps are not 100% reliable because they fork the app process,
+ which is multithreaded. If another thread is holding a lock, the forked
+ process can get stuck. This is a known limitation of java heap dumps.
+
+ debuggable apps are not AOT-compiled, so there's a high chance that the
+ JIT is in use. The JIT runs on another thread and can hold locks. To
+ reduce the chance of running into the fork deadlock described earlier,
+ we simply disable the JIT for debuggable apps in tests.
+ -->
+ <application android:debuggable="true" android:vmSafeMode="true">
<activity
android:name="android.perfetto.cts.app.MainActivity"
android:exported="true">
@@ -71,6 +84,19 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
+ <activity
+ android:name="android.perfetto.cts.app.NoopActivity"
+ android:exported="true">
+ </activity>
+ <activity-alias
+ android:name="android.perfetto.cts.app.debuggable.NoopActivity"
+ android:targetActivity="android.perfetto.cts.app.NoopActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
<provider
android:name="android.perfetto.cts.app.FileContentProvider"
android:authorities="android.perfetto.cts.app.debuggable"
diff --git a/test/cts/test_apps/AndroidManifest_nonprofileable.xml b/test/cts/test_apps/AndroidManifest_nonprofileable.xml
index 8322daf..da3210d 100755
--- a/test/cts/test_apps/AndroidManifest_nonprofileable.xml
+++ b/test/cts/test_apps/AndroidManifest_nonprofileable.xml
@@ -59,6 +59,19 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
+ <activity
+ android:name="android.perfetto.cts.app.NoopActivity"
+ android:exported="true">
+ </activity>
+ <activity-alias
+ android:name="android.perfetto.cts.app.nonprofileable.NoopActivity"
+ android:targetActivity="android.perfetto.cts.app.NoopActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
<provider
android:name="android.perfetto.cts.app.FileContentProvider"
android:authorities="android.perfetto.cts.app.nonprofileable"
diff --git a/test/cts/test_apps/AndroidManifest_profileable.xml b/test/cts/test_apps/AndroidManifest_profileable.xml
index cd434d4..aebb810 100755
--- a/test/cts/test_apps/AndroidManifest_profileable.xml
+++ b/test/cts/test_apps/AndroidManifest_profileable.xml
@@ -72,6 +72,19 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
+ <activity
+ android:name="android.perfetto.cts.app.NoopActivity"
+ android:exported="true">
+ </activity>
+ <activity-alias
+ android:name="android.perfetto.cts.app.profileable.NoopActivity"
+ android:targetActivity="android.perfetto.cts.app.NoopActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
<provider
android:name="android.perfetto.cts.app.FileContentProvider"
android:authorities="android.perfetto.cts.app.profileable"
diff --git a/test/cts/test_apps/AndroidManifest_release.xml b/test/cts/test_apps/AndroidManifest_release.xml
index 1795a59..8a87e23 100755
--- a/test/cts/test_apps/AndroidManifest_release.xml
+++ b/test/cts/test_apps/AndroidManifest_release.xml
@@ -71,6 +71,19 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
+ <activity
+ android:name="android.perfetto.cts.app.NoopActivity"
+ android:exported="true">
+ </activity>
+ <activity-alias
+ android:name="android.perfetto.cts.app.release.NoopActivity"
+ android:targetActivity="android.perfetto.cts.app.NoopActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
<provider
android:name="android.perfetto.cts.app.FileContentProvider"
android:authorities="android.perfetto.cts.app.release"
diff --git a/protos/perfetto/metrics/android/android_trusty_workqueues.proto b/test/cts/test_apps/src/android/perfetto/cts/app/NoopActivity.java
similarity index 66%
copy from protos/perfetto/metrics/android/android_trusty_workqueues.proto
copy to test/cts/test_apps/src/android/perfetto/cts/app/NoopActivity.java
index b7a1d5a..296e692 100644
--- a/protos/perfetto/metrics/android/android_trusty_workqueues.proto
+++ b/test/cts/test_apps/src/android/perfetto/cts/app/NoopActivity.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2019 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.
@@ -14,9 +14,14 @@
* limitations under the License.
*/
-syntax = "proto2";
+package android.perfetto.cts.app;
-package perfetto.protos;
+import android.app.Activity;
+import android.os.Bundle;
-// Metric used to generate a simplified view of the Trusty kworker events.
-message AndroidTrustyWorkqueues {}
+public class NoopActivity extends Activity {
+ @Override
+ public void onCreate(Bundle state) {
+ super.onCreate(state);
+ }
+}
diff --git a/test/data/android_desktop_mode/multiple_window_only_update.pb.sha256 b/test/data/android_desktop_mode/multiple_window_only_update.pb.sha256
new file mode 100644
index 0000000..939c986
--- /dev/null
+++ b/test/data/android_desktop_mode/multiple_window_only_update.pb.sha256
@@ -0,0 +1 @@
+de2c99d57ebec6ab843bc56047eb12dbedc3d08a8a1d989d9b3302ed30057286
\ No newline at end of file
diff --git a/test/data/android_desktop_mode/multiple_windows_add_update_remove.pb.sha256 b/test/data/android_desktop_mode/multiple_windows_add_update_remove.pb.sha256
new file mode 100644
index 0000000..5866d07
--- /dev/null
+++ b/test/data/android_desktop_mode/multiple_windows_add_update_remove.pb.sha256
@@ -0,0 +1 @@
+0967fdf494167fe47e5ddf4c540fcc7cc0b22e36ddafa4b4c1172fe3c171b3d6
\ No newline at end of file
diff --git a/test/data/android_desktop_mode/session_with_same_instance_id.pb.sha256 b/test/data/android_desktop_mode/session_with_same_instance_id.pb.sha256
new file mode 100644
index 0000000..a24473b
--- /dev/null
+++ b/test/data/android_desktop_mode/session_with_same_instance_id.pb.sha256
@@ -0,0 +1 @@
+bc81316b9dbfefa7a998f94750594c1eb7b07e070006f335b80815349f09ad96
\ No newline at end of file
diff --git a/test/data/android_desktop_mode/single_window_add_update_no_remove.pb.sha256 b/test/data/android_desktop_mode/single_window_add_update_no_remove.pb.sha256
new file mode 100644
index 0000000..f4656e4
--- /dev/null
+++ b/test/data/android_desktop_mode/single_window_add_update_no_remove.pb.sha256
@@ -0,0 +1 @@
+0bb09b4c68a125a09b6856879af92cd47dd980c2dcaa1513c370b9b6a7b575d1
\ No newline at end of file
diff --git a/test/data/android_desktop_mode/single_window_add_update_remove.pb.sha256 b/test/data/android_desktop_mode/single_window_add_update_remove.pb.sha256
new file mode 100644
index 0000000..3496183
--- /dev/null
+++ b/test/data/android_desktop_mode/single_window_add_update_remove.pb.sha256
@@ -0,0 +1 @@
+bab0c50523ac903872cd11f3fdd4a9ecda98e937573d17011eb877636e82c3c3
\ No newline at end of file
diff --git a/test/data/android_desktop_mode/single_window_no_add_update_remove.pb.sha256 b/test/data/android_desktop_mode/single_window_no_add_update_remove.pb.sha256
new file mode 100644
index 0000000..3c01fec
--- /dev/null
+++ b/test/data/android_desktop_mode/single_window_no_add_update_remove.pb.sha256
@@ -0,0 +1 @@
+7f63f17b65a0fca4bc5ccd75c9a7eb854779585b20cb91fcf371e7892642327e
\ No newline at end of file
diff --git a/test/data/android_desktop_mode/single_window_only_update.pb.sha256 b/test/data/android_desktop_mode/single_window_only_update.pb.sha256
new file mode 100644
index 0000000..20f7be3
--- /dev/null
+++ b/test/data/android_desktop_mode/single_window_only_update.pb.sha256
@@ -0,0 +1 @@
+ee1172b8ad0eaf856e2776575b861f44663bb98f1d40d7d4e66ee0f532220568
\ No newline at end of file
diff --git a/test/data/android_job_scheduler.perfetto-trace.sha256 b/test/data/android_job_scheduler.perfetto-trace.sha256
new file mode 100644
index 0000000..f3779ec
--- /dev/null
+++ b/test/data/android_job_scheduler.perfetto-trace.sha256
@@ -0,0 +1 @@
+9941708f26c9dddfb250f8fdbef94a160302e3020c677f86287a3c061a430738
\ No newline at end of file
diff --git a/test/data/art-method-tracing-streaming.trace.sha256 b/test/data/art-method-tracing-streaming.trace.sha256
new file mode 100644
index 0000000..11425d6
--- /dev/null
+++ b/test/data/art-method-tracing-streaming.trace.sha256
@@ -0,0 +1 @@
+8444001ac344e3f2da2def058e4c6622db424b377a456237d94e47dc85321d7f
\ No newline at end of file
diff --git a/test/data/art-method-tracing.trace.sha256 b/test/data/art-method-tracing.trace.sha256
new file mode 100644
index 0000000..38910a6
--- /dev/null
+++ b/test/data/art-method-tracing.trace.sha256
@@ -0,0 +1 @@
+ebd46d41eaa4656ad06535dacc1d3c6f6018a180f89c546515fed4f7e1df2337
\ No newline at end of file
diff --git a/test/data/chrome/scroll_m131.pftrace.sha256 b/test/data/chrome/scroll_m131.pftrace.sha256
new file mode 100644
index 0000000..38b6159
--- /dev/null
+++ b/test/data/chrome/scroll_m131.pftrace.sha256
@@ -0,0 +1 @@
+14171c9e502a65a454f39fe14fce8b313c7012a2c14394bed496fc93b1644b0d
\ No newline at end of file
diff --git a/test/data/perf_track_sym.tar.gz.sha256 b/test/data/perf_track_sym.tar.gz.sha256
new file mode 100644
index 0000000..edccf35
--- /dev/null
+++ b/test/data/perf_track_sym.tar.gz.sha256
@@ -0,0 +1 @@
+358db7f9628a9bb79d2dffa274c73c2894c3c7108b67bfb9f513bf4bac34acd6
\ No newline at end of file
diff --git a/test/data/simpleperf_as_gecko.json.sha256 b/test/data/simpleperf_as_gecko.json.sha256
new file mode 100644
index 0000000..5162d27
--- /dev/null
+++ b/test/data/simpleperf_as_gecko.json.sha256
@@ -0,0 +1 @@
+225e86a03ada87e586c4cf053a7369516f8f124318495e2109022e72389d0b68
\ No newline at end of file
diff --git a/test/data/simpleperf_as_text.txt.sha256 b/test/data/simpleperf_as_text.txt.sha256
new file mode 100644
index 0000000..65c8bc3
--- /dev/null
+++ b/test/data/simpleperf_as_text.txt.sha256
@@ -0,0 +1 @@
+40d817e31b5dac6bd745cf2eddb1bc7dc4c78aa3a8c398e718dcb3b650975388
\ No newline at end of file
diff --git a/test/data/trace_processor_perf_as_gecko.json.sha256 b/test/data/trace_processor_perf_as_gecko.json.sha256
new file mode 100644
index 0000000..0341371
--- /dev/null
+++ b/test/data/trace_processor_perf_as_gecko.json.sha256
@@ -0,0 +1 @@
+8e2b7d825d190f12bd87fee90220ec2b91110c056ca9f55c5e00dd0d1f3455c5
\ No newline at end of file
diff --git a/test/data/trace_processor_perf_as_text.txt.sha256 b/test/data/trace_processor_perf_as_text.txt.sha256
new file mode 100644
index 0000000..2d5a681
--- /dev/null
+++ b/test/data/trace_processor_perf_as_text.txt.sha256
@@ -0,0 +1 @@
+a39b72bf2a6c9355c4dec142814874c7977b071fc4cc1f37bd78167efee12e14
\ No newline at end of file
diff --git a/test/data/track_event_ordered.pb.sha256 b/test/data/track_event_ordered.pb.sha256
new file mode 100644
index 0000000..645416e
--- /dev/null
+++ b/test/data/track_event_ordered.pb.sha256
@@ -0,0 +1 @@
+1bab9bdb32c88b22a0e8f09ddad84338c5c320e416429f92e6d7ff5a671eb06e
\ No newline at end of file
diff --git a/test/data/ui-screenshots/aggregation.test.ts/frametimeline/frame-timeline-aggregation.png.sha256 b/test/data/ui-screenshots/aggregation.test.ts/frametimeline/frame-timeline-aggregation.png.sha256
index 481608e..85d36de 100644
--- a/test/data/ui-screenshots/aggregation.test.ts/frametimeline/frame-timeline-aggregation.png.sha256
+++ b/test/data/ui-screenshots/aggregation.test.ts/frametimeline/frame-timeline-aggregation.png.sha256
@@ -1 +1 @@
-ce511035c02bfeb1ed8f5f67eaa544fdccb5339513deeb187ce79699ab05feec
\ No newline at end of file
+9cf722b3752271f0e82ac12afb3bf682a9bdb665110df684a0b9879076415c6a
\ No newline at end of file
diff --git a/test/data/ui-screenshots/aggregation.test.ts/gpu-counter/gpu-counter-aggregation.png.sha256 b/test/data/ui-screenshots/aggregation.test.ts/gpu-counter/gpu-counter-aggregation.png.sha256
index 535cf34..b7aa6eb 100644
--- a/test/data/ui-screenshots/aggregation.test.ts/gpu-counter/gpu-counter-aggregation.png.sha256
+++ b/test/data/ui-screenshots/aggregation.test.ts/gpu-counter/gpu-counter-aggregation.png.sha256
@@ -1 +1 @@
-7f5db38af5a02754d89e894f0bdd99fb583221d210e55535a2b2c048912efe40
\ No newline at end of file
+2a1be50f914dd081829704878691425954b63a60e3c33ca10c23b24c6e62af53
\ No newline at end of file
diff --git a/test/data/ui-screenshots/aggregation.test.ts/sched/cpu-by-process.png.sha256 b/test/data/ui-screenshots/aggregation.test.ts/sched/cpu-by-process.png.sha256
index 68c1f7e..b1c1f3e 100644
--- a/test/data/ui-screenshots/aggregation.test.ts/sched/cpu-by-process.png.sha256
+++ b/test/data/ui-screenshots/aggregation.test.ts/sched/cpu-by-process.png.sha256
@@ -1 +1 @@
-83feca34209ac40f2b9a7e8cd109ba16cf08a02231a5f1fc8946ba09b1eb449c
\ No newline at end of file
+cdcb5c93e489619244b12f52003cfb57f705054180bcbd3729945b11010090ab
\ No newline at end of file
diff --git a/test/data/ui-screenshots/aggregation.test.ts/sched/cpu-by-thread.png.sha256 b/test/data/ui-screenshots/aggregation.test.ts/sched/cpu-by-thread.png.sha256
index 7682630..287e4cc 100644
--- a/test/data/ui-screenshots/aggregation.test.ts/sched/cpu-by-thread.png.sha256
+++ b/test/data/ui-screenshots/aggregation.test.ts/sched/cpu-by-thread.png.sha256
@@ -1 +1 @@
-5d6af88ed2130f0ace3af676dc40a02561c8f0794e1c022091f5a55f2660aceb
\ No newline at end of file
+312cf370818fc7d5b9359d0bebb9cde36f3897331f31933c09986f1f6f8d139b
\ No newline at end of file
diff --git a/test/data/ui-screenshots/aggregation.test.ts/sched/sort-by-occurrences.png.sha256 b/test/data/ui-screenshots/aggregation.test.ts/sched/sort-by-occurrences.png.sha256
index c6877a1..6bcd9f2 100644
--- a/test/data/ui-screenshots/aggregation.test.ts/sched/sort-by-occurrences.png.sha256
+++ b/test/data/ui-screenshots/aggregation.test.ts/sched/sort-by-occurrences.png.sha256
@@ -1 +1 @@
-d93e4f4e96004fc112891cb6997cd3146f9ea92aeb10856dc59cceefd8a2518c
\ No newline at end of file
+5c88453331ee4fd6af586ca1d170ea2ba2124a7b3a12662395d6360e94f064d8
\ No newline at end of file
diff --git a/test/data/ui-screenshots/aggregation.test.ts/sched/sort-by-wall-duration-desc.png.sha256 b/test/data/ui-screenshots/aggregation.test.ts/sched/sort-by-wall-duration-desc.png.sha256
index 8700bb5..45ce9a3 100644
--- a/test/data/ui-screenshots/aggregation.test.ts/sched/sort-by-wall-duration-desc.png.sha256
+++ b/test/data/ui-screenshots/aggregation.test.ts/sched/sort-by-wall-duration-desc.png.sha256
@@ -1 +1 @@
-508df930d68bcf99045d20d26eb7059e546632c4c6ca4de3e53291b93db7af9c
\ No newline at end of file
+b462dfa84705f813ea4bee02097fd6444420c2dac7624d6f60c211fff251c85b
\ No newline at end of file
diff --git a/test/data/ui-screenshots/aggregation.test.ts/sched/sort-by-wall-duration.png.sha256 b/test/data/ui-screenshots/aggregation.test.ts/sched/sort-by-wall-duration.png.sha256
index ed0bb6a..fda23e2 100644
--- a/test/data/ui-screenshots/aggregation.test.ts/sched/sort-by-wall-duration.png.sha256
+++ b/test/data/ui-screenshots/aggregation.test.ts/sched/sort-by-wall-duration.png.sha256
@@ -1 +1 @@
-0f4bd88c6a0d445272aff0d6f9596fe55249532317a3347b58424bf675a7695d
\ No newline at end of file
+cee7ff84c65e1363a61e7ecb38ff882392ee64e07965913ca8cf4cca779d876e
\ No newline at end of file
diff --git a/test/data/ui-screenshots/aggregation.test.ts/slices/slice-aggregation.png.sha256 b/test/data/ui-screenshots/aggregation.test.ts/slices/slice-aggregation.png.sha256
index 0abe6e7..8b08f2a 100644
--- a/test/data/ui-screenshots/aggregation.test.ts/slices/slice-aggregation.png.sha256
+++ b/test/data/ui-screenshots/aggregation.test.ts/slices/slice-aggregation.png.sha256
@@ -1 +1 @@
-a7949f3163af0185e297227e05b6530d3cf4a5d4f0f73fcf7cf135848070951f
\ No newline at end of file
+d7ff2899d9756375f5b3c5d3c1a58f943742d613be96bef181be07bd10f61333
\ No newline at end of file
diff --git a/test/data/ui-screenshots/chrome_missing_track_names.test.ts/expand-all-tracks/all-tracks-expanded.png.sha256 b/test/data/ui-screenshots/chrome_missing_track_names.test.ts/expand-all-tracks/all-tracks-expanded.png.sha256
index 97e615c..b2f1c8a 100644
--- a/test/data/ui-screenshots/chrome_missing_track_names.test.ts/expand-all-tracks/all-tracks-expanded.png.sha256
+++ b/test/data/ui-screenshots/chrome_missing_track_names.test.ts/expand-all-tracks/all-tracks-expanded.png.sha256
@@ -1 +1 @@
-aedf5fc53c5cf02299bf1b91753dca1cdf4d117f54c394bae8efef9da345fcee
\ No newline at end of file
+664dd536449ade2ba5667c670d43c13a0807394221503f1e19ec375f49c9c02e
\ No newline at end of file
diff --git a/test/data/ui-screenshots/chrome_missing_track_names.test.ts/trace-loaded/trace-loaded.png.sha256 b/test/data/ui-screenshots/chrome_missing_track_names.test.ts/trace-loaded/trace-loaded.png.sha256
index 0655353..f33f150 100644
--- a/test/data/ui-screenshots/chrome_missing_track_names.test.ts/trace-loaded/trace-loaded.png.sha256
+++ b/test/data/ui-screenshots/chrome_missing_track_names.test.ts/trace-loaded/trace-loaded.png.sha256
@@ -1 +1 @@
-cc68b6a2933576cccad4eae68bc0c8823fa5e7ee155befd112d650ada74fd0b4
\ No newline at end of file
+b584a88e648c34d8a154d84aa7e9c7a829eb93df98d006435493d47b35520c0d
\ No newline at end of file
diff --git a/test/data/ui-screenshots/chrome_rendering_desktop.test.ts/expand-browser/browser-expanded.png.sha256 b/test/data/ui-screenshots/chrome_rendering_desktop.test.ts/expand-browser/browser-expanded.png.sha256
index c63bd92..5462947 100644
--- a/test/data/ui-screenshots/chrome_rendering_desktop.test.ts/expand-browser/browser-expanded.png.sha256
+++ b/test/data/ui-screenshots/chrome_rendering_desktop.test.ts/expand-browser/browser-expanded.png.sha256
@@ -1 +1 @@
-7a70ac4a6166da36663ef7e79366e512fe84d9ec516e0d7f2b1b934661139767
\ No newline at end of file
+fdbe33426328cca5b441bd252e35b7364418bcf40f68f1f653f706fd7b23a547
\ No newline at end of file
diff --git a/test/data/ui-screenshots/chrome_rendering_desktop.test.ts/load-trace/loaded.png.sha256 b/test/data/ui-screenshots/chrome_rendering_desktop.test.ts/load-trace/loaded.png.sha256
index 8c0b04e..5e2a9ef 100644
--- a/test/data/ui-screenshots/chrome_rendering_desktop.test.ts/load-trace/loaded.png.sha256
+++ b/test/data/ui-screenshots/chrome_rendering_desktop.test.ts/load-trace/loaded.png.sha256
@@ -1 +1 @@
-3dde3619c184fb32018642cb0e4b3150301a31971b197709eaeb8d22da55f462
\ No newline at end of file
+2bda98865dbd14472a07d68093d75efcb2c1d240d96ecf809614315f18b91767
\ No newline at end of file
diff --git a/test/data/ui-screenshots/chrome_rendering_desktop.test.ts/slice-with-flows/slice-with-flows.png.sha256 b/test/data/ui-screenshots/chrome_rendering_desktop.test.ts/slice-with-flows/slice-with-flows.png.sha256
index 6c551c6..02b96b1 100644
--- a/test/data/ui-screenshots/chrome_rendering_desktop.test.ts/slice-with-flows/slice-with-flows.png.sha256
+++ b/test/data/ui-screenshots/chrome_rendering_desktop.test.ts/slice-with-flows/slice-with-flows.png.sha256
@@ -1 +1 @@
-098ee0afd6595fa48d2bd70fa0cca88f094f10991dd799b935f4ae123697e5c7
\ No newline at end of file
+fb7aea9a2f5fa70103eb9bbad432dd7bff64cb6604c19b9ab199ca507f8bb336
\ No newline at end of file
diff --git a/test/data/ui-screenshots/debug_tracks.test.ts/debug-tracks-pivot/debug-track-pivot.png.sha256 b/test/data/ui-screenshots/debug_tracks.test.ts/debug-tracks-pivot/debug-track-pivot.png.sha256
index 63b5a9f..8eb007d 100644
--- a/test/data/ui-screenshots/debug_tracks.test.ts/debug-tracks-pivot/debug-track-pivot.png.sha256
+++ b/test/data/ui-screenshots/debug_tracks.test.ts/debug-tracks-pivot/debug-track-pivot.png.sha256
@@ -1 +1 @@
-ccb67744056ddf8e5f16a0656b85b3101f568da3768ecb7346d753f7d76b9d97
\ No newline at end of file
+d06cd9a16a7aefdd0c9254f56444b80056920d0e6ea0b14b1919deab6103eb3f
\ No newline at end of file
diff --git a/test/data/ui-screenshots/debug_tracks.test.ts/debug-tracks/debug-slice-clicked.png.sha256 b/test/data/ui-screenshots/debug_tracks.test.ts/debug-tracks/debug-slice-clicked.png.sha256
index 4d665c7..f18a143 100644
--- a/test/data/ui-screenshots/debug_tracks.test.ts/debug-tracks/debug-slice-clicked.png.sha256
+++ b/test/data/ui-screenshots/debug_tracks.test.ts/debug-tracks/debug-slice-clicked.png.sha256
@@ -1 +1 @@
-d8755c491238e5d9b020b7dbc6e90f5081b53b7faf85d17ac499a8c75bc436fb
\ No newline at end of file
+f89e12d7bc8ba6c45d35be4c157bff221e9659465e2106dc22c9960cfd6b6de8
\ No newline at end of file
diff --git a/test/data/ui-screenshots/debug_tracks.test.ts/debug-tracks/debug-track-added.png.sha256 b/test/data/ui-screenshots/debug_tracks.test.ts/debug-tracks/debug-track-added.png.sha256
index 3db5096..86980b4 100644
--- a/test/data/ui-screenshots/debug_tracks.test.ts/debug-tracks/debug-track-added.png.sha256
+++ b/test/data/ui-screenshots/debug_tracks.test.ts/debug-tracks/debug-track-added.png.sha256
@@ -1 +1 @@
-e1c39f16371dc67fecf01f889327204fb0a248c8e37a8ed5c646b6132106ca31
\ No newline at end of file
+882aff2a3750f56e47cda9adacb2ca71be837f23ccd54ae2cf32eefdd65b2618
\ No newline at end of file
diff --git a/test/data/ui-screenshots/debug_tracks.test.ts/debug-tracks/debug-track-removed.png.sha256 b/test/data/ui-screenshots/debug_tracks.test.ts/debug-tracks/debug-track-removed.png.sha256
index ffd4a6b..e5bab38 100644
--- a/test/data/ui-screenshots/debug_tracks.test.ts/debug-tracks/debug-track-removed.png.sha256
+++ b/test/data/ui-screenshots/debug_tracks.test.ts/debug-tracks/debug-track-removed.png.sha256
@@ -1 +1 @@
-7b45f5e9428485b1685928bad8be997665e3a5556d8b4eabe3d53b8ca05b4551
\ No newline at end of file
+fda4f777d5a613c3c7622f5825031610441bf924af8c7920bf63840526a87819
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ftrace_tracks_and_tab.test.ts/ftrace-tab/ftrace-tab.png.sha256 b/test/data/ui-screenshots/ftrace_tracks_and_tab.test.ts/ftrace-tab/ftrace-tab.png.sha256
index b7f43de..83fd6cc 100644
--- a/test/data/ui-screenshots/ftrace_tracks_and_tab.test.ts/ftrace-tab/ftrace-tab.png.sha256
+++ b/test/data/ui-screenshots/ftrace_tracks_and_tab.test.ts/ftrace-tab/ftrace-tab.png.sha256
@@ -1 +1 @@
-8a18bedfcc448596e198d82e9e4742bf6eb7ea6b2ea4cebfa8aed9f19d58f6cb
\ No newline at end of file
+bb7fb0c07b33c4c08039436d33519f9dee5c4baf98f1067e6926a9f9875edeaf
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ftrace_tracks_and_tab.test.ts/ftrace-tracks/ftrace-events.png.sha256 b/test/data/ui-screenshots/ftrace_tracks_and_tab.test.ts/ftrace-tracks/ftrace-events.png.sha256
index 76b9bb7..b97e598 100644
--- a/test/data/ui-screenshots/ftrace_tracks_and_tab.test.ts/ftrace-tracks/ftrace-events.png.sha256
+++ b/test/data/ui-screenshots/ftrace_tracks_and_tab.test.ts/ftrace-tracks/ftrace-events.png.sha256
@@ -1 +1 @@
-936d29a5e1de414ef1c6bac44d2041d702832cd134620572adadc2ac8e2579bc
\ No newline at end of file
+ef239fe1e10e3b830f7adf9b5b8f96a52c69a50e6d879cfb5a873cd26caab769
\ No newline at end of file
diff --git a/test/data/ui-screenshots/independent_features.test.ts/debuggable-chip/track-with-debuggable-chip-expanded.png.sha256 b/test/data/ui-screenshots/independent_features.test.ts/debuggable-chip/track-with-debuggable-chip-expanded.png.sha256
index 6e2eb2e..98255ae 100644
--- a/test/data/ui-screenshots/independent_features.test.ts/debuggable-chip/track-with-debuggable-chip-expanded.png.sha256
+++ b/test/data/ui-screenshots/independent_features.test.ts/debuggable-chip/track-with-debuggable-chip-expanded.png.sha256
@@ -1 +1 @@
-7b6bfb6624aba6574ae47ac41ec0614045580be199ed5fbc6ddb89064f9060d3
\ No newline at end of file
+526c3bce9a8622bc5ac983a88bec6fb41194c7f84dd7b740468bd275be13ee8e
\ No newline at end of file
diff --git a/test/data/ui-screenshots/independent_features.test.ts/debuggable-chip/track-with-debuggable-chip.png.sha256 b/test/data/ui-screenshots/independent_features.test.ts/debuggable-chip/track-with-debuggable-chip.png.sha256
index fc33143..eff6f6b 100644
--- a/test/data/ui-screenshots/independent_features.test.ts/debuggable-chip/track-with-debuggable-chip.png.sha256
+++ b/test/data/ui-screenshots/independent_features.test.ts/debuggable-chip/track-with-debuggable-chip.png.sha256
@@ -1 +1 @@
-30cab257f1b44ceeab1b223fcbe0fa9cb7cce7778df3228b38ba4f1cc248ba50
\ No newline at end of file
+67240fa5e871e2e8cb9369c06a27dad3a3754bc4628d8d6060ffd6711ab3a012
\ No newline at end of file
diff --git a/test/data/ui-screenshots/load_and_tracks.test.ts/info-and-stats/back-to-timeline.png.sha256 b/test/data/ui-screenshots/load_and_tracks.test.ts/info-and-stats/back-to-timeline.png.sha256
index 148ab59..732451a 100644
--- a/test/data/ui-screenshots/load_and_tracks.test.ts/info-and-stats/back-to-timeline.png.sha256
+++ b/test/data/ui-screenshots/load_and_tracks.test.ts/info-and-stats/back-to-timeline.png.sha256
@@ -1 +1 @@
-253a35f660e68deab9916a3202852ec3f3510fdec730e51c0e65aac8a1a8703b
\ No newline at end of file
+6114bce1d944eb9308105215a845a6b0e01830fc3bf1b0cd0029eb0b6bf7a586
\ No newline at end of file
diff --git a/test/data/ui-screenshots/load_and_tracks.test.ts/info-and-stats/into-and-stats.png.sha256 b/test/data/ui-screenshots/load_and_tracks.test.ts/info-and-stats/into-and-stats.png.sha256
index 0422b8d..928eb48 100644
--- a/test/data/ui-screenshots/load_and_tracks.test.ts/info-and-stats/into-and-stats.png.sha256
+++ b/test/data/ui-screenshots/load_and_tracks.test.ts/info-and-stats/into-and-stats.png.sha256
@@ -1 +1 @@
-5d85decb756d76dcb16f7211996f6f64da4d14cbacf10a0380763c9a04a50688
\ No newline at end of file
+821c18e11f1a721dc5a72c1cf8060526dd4aeff95fe3ec6b5becb601beec00cd
\ No newline at end of file
diff --git a/test/data/ui-screenshots/load_and_tracks.test.ts/load-trace/loaded.png.sha256 b/test/data/ui-screenshots/load_and_tracks.test.ts/load-trace/loaded.png.sha256
index 148ab59..732451a 100644
--- a/test/data/ui-screenshots/load_and_tracks.test.ts/load-trace/loaded.png.sha256
+++ b/test/data/ui-screenshots/load_and_tracks.test.ts/load-trace/loaded.png.sha256
@@ -1 +1 @@
-253a35f660e68deab9916a3202852ec3f3510fdec730e51c0e65aac8a1a8703b
\ No newline at end of file
+6114bce1d944eb9308105215a845a6b0e01830fc3bf1b0cd0029eb0b6bf7a586
\ No newline at end of file
diff --git a/test/data/ui-screenshots/load_and_tracks.test.ts/mark/mark-0.png.sha256 b/test/data/ui-screenshots/load_and_tracks.test.ts/mark/mark-0.png.sha256
index 46bd74a..91ad70c 100644
--- a/test/data/ui-screenshots/load_and_tracks.test.ts/mark/mark-0.png.sha256
+++ b/test/data/ui-screenshots/load_and_tracks.test.ts/mark/mark-0.png.sha256
@@ -1 +1 @@
-e334b59ddfbb12ae351fc3aa2bf514bb2e2f0d574c936c97c04b230294473318
\ No newline at end of file
+45ae6941d53babfaf49915bc0cf9f46900385fd4fd7466e0fb48be8848fb9332
\ No newline at end of file
diff --git a/test/data/ui-screenshots/load_and_tracks.test.ts/mark/mark-1.png.sha256 b/test/data/ui-screenshots/load_and_tracks.test.ts/mark/mark-1.png.sha256
index 7b0c883..7264b55 100644
--- a/test/data/ui-screenshots/load_and_tracks.test.ts/mark/mark-1.png.sha256
+++ b/test/data/ui-screenshots/load_and_tracks.test.ts/mark/mark-1.png.sha256
@@ -1 +1 @@
-fa851f275f2cb252e576bf05b3e04adac7b4e4a6c3f396542ae84be6d602246e
\ No newline at end of file
+ed546d5013d0f9d536248df2ac7df9d4a02c318265a3f120d7d0de0960615bf9
\ No newline at end of file
diff --git a/test/data/ui-screenshots/load_and_tracks.test.ts/mark/mark-2.png.sha256 b/test/data/ui-screenshots/load_and_tracks.test.ts/mark/mark-2.png.sha256
index 9aa430b..47f9a48 100644
--- a/test/data/ui-screenshots/load_and_tracks.test.ts/mark/mark-2.png.sha256
+++ b/test/data/ui-screenshots/load_and_tracks.test.ts/mark/mark-2.png.sha256
@@ -1 +1 @@
-dad3a33cf20deba74d22f10bfe7ebedd283c9911417f0d90c8453e8b1329eda0
\ No newline at end of file
+f0a2d7936b06f27ed78c7c3cbd0323de50e3d666d52f11f161c60cfefb397b1a
\ No newline at end of file
diff --git a/test/data/ui-screenshots/load_and_tracks.test.ts/mark/mark-3.png.sha256 b/test/data/ui-screenshots/load_and_tracks.test.ts/mark/mark-3.png.sha256
index 605e20d..058e1df 100644
--- a/test/data/ui-screenshots/load_and_tracks.test.ts/mark/mark-3.png.sha256
+++ b/test/data/ui-screenshots/load_and_tracks.test.ts/mark/mark-3.png.sha256
@@ -1 +1 @@
-08d737f09abfb1879088ac8767c5842d01f73942b3659645f988db680120a52a
\ No newline at end of file
+2a38de005da72784917a8a1fbeb60d7237fc71cd62615afc57642234a63f6165
\ No newline at end of file
diff --git a/test/data/ui-screenshots/load_and_tracks.test.ts/omnibox-search/process-details.png.sha256 b/test/data/ui-screenshots/load_and_tracks.test.ts/omnibox-search/process-details.png.sha256
index c810881..7990d70 100644
--- a/test/data/ui-screenshots/load_and_tracks.test.ts/omnibox-search/process-details.png.sha256
+++ b/test/data/ui-screenshots/load_and_tracks.test.ts/omnibox-search/process-details.png.sha256
@@ -1 +1 @@
-76060bc501093af9df39fb3f16db7411035a83e593ce0d80807e3dc297e487c3
\ No newline at end of file
+f974f09cd7010de66ea75aae2b0deb6e69fcf5ff1f6a6747572aaac2898e8fb9
\ No newline at end of file
diff --git a/test/data/ui-screenshots/load_and_tracks.test.ts/omnibox-search/search-slice.png.sha256 b/test/data/ui-screenshots/load_and_tracks.test.ts/omnibox-search/search-slice.png.sha256
index c58db7c..0012208 100644
--- a/test/data/ui-screenshots/load_and_tracks.test.ts/omnibox-search/search-slice.png.sha256
+++ b/test/data/ui-screenshots/load_and_tracks.test.ts/omnibox-search/search-slice.png.sha256
@@ -1 +1 @@
-412d4954730f17ab14e763654129bfc0d4a7b45e6a7f59e1b9f1062f3829352f
\ No newline at end of file
+3ba015fed84e47ef9ba11967d3745036a5e3a80f76d47d2e3a8a064b136e31ef
\ No newline at end of file
diff --git a/test/data/ui-screenshots/load_and_tracks.test.ts/pin-tracks/one-track-pinned.png.sha256 b/test/data/ui-screenshots/load_and_tracks.test.ts/pin-tracks/one-track-pinned.png.sha256
index 2ce1b7b..b683281 100644
--- a/test/data/ui-screenshots/load_and_tracks.test.ts/pin-tracks/one-track-pinned.png.sha256
+++ b/test/data/ui-screenshots/load_and_tracks.test.ts/pin-tracks/one-track-pinned.png.sha256
@@ -1 +1 @@
-b38f3fd22157b98c4d0f0e82ccac29acffd21ca42ae51bd49c7aa1cfa2d1cd53
\ No newline at end of file
+df9c6cea86b96bed3d607a1c1253628fbb999913e962443ac04aaed0e004eb14
\ No newline at end of file
diff --git a/test/data/ui-screenshots/load_and_tracks.test.ts/pin-tracks/two-tracks-pinned.png.sha256 b/test/data/ui-screenshots/load_and_tracks.test.ts/pin-tracks/two-tracks-pinned.png.sha256
index ba5adfd..958fc22 100644
--- a/test/data/ui-screenshots/load_and_tracks.test.ts/pin-tracks/two-tracks-pinned.png.sha256
+++ b/test/data/ui-screenshots/load_and_tracks.test.ts/pin-tracks/two-tracks-pinned.png.sha256
@@ -1 +1 @@
-799d74d9046bddf2830fd28600f8338359d923c69e430da412365235f6619d6e
\ No newline at end of file
+1f8e548dbb67ab743c2d7a0812e2b9019096043f596c83d6c0e936072c98c9a0
\ No newline at end of file
diff --git a/test/data/ui-screenshots/load_and_tracks.test.ts/track-expand-and-collapse/traced-probes-compressed.png.sha256 b/test/data/ui-screenshots/load_and_tracks.test.ts/track-expand-and-collapse/traced-probes-compressed.png.sha256
index b7bdab2..d2c55f0 100644
--- a/test/data/ui-screenshots/load_and_tracks.test.ts/track-expand-and-collapse/traced-probes-compressed.png.sha256
+++ b/test/data/ui-screenshots/load_and_tracks.test.ts/track-expand-and-collapse/traced-probes-compressed.png.sha256
@@ -1 +1 @@
-9fb445372aa8c9b754de4193d91ea593bd130cab8082f3ea5561ca183a7bc374
\ No newline at end of file
+007dfdd647fb12be40704f8aef1cc2e6188fdd16912dd6b2a8bc5c23f2cd9c8f
\ No newline at end of file
diff --git a/test/data/ui-screenshots/load_and_tracks.test.ts/track-expand-and-collapse/traced-probes-expanded.png.sha256 b/test/data/ui-screenshots/load_and_tracks.test.ts/track-expand-and-collapse/traced-probes-expanded.png.sha256
index b7bdab2..d2c55f0 100644
--- a/test/data/ui-screenshots/load_and_tracks.test.ts/track-expand-and-collapse/traced-probes-expanded.png.sha256
+++ b/test/data/ui-screenshots/load_and_tracks.test.ts/track-expand-and-collapse/traced-probes-expanded.png.sha256
@@ -1 +1 @@
-9fb445372aa8c9b754de4193d91ea593bd130cab8082f3ea5561ca183a7bc374
\ No newline at end of file
+007dfdd647fb12be40704f8aef1cc2e6188fdd16912dd6b2a8bc5c23f2cd9c8f
\ No newline at end of file
diff --git a/test/data/ui-screenshots/local_cache_key.test.ts/multiple-traces-via-url-and-local-cache-key/back-to-trace-1.png.sha256 b/test/data/ui-screenshots/local_cache_key.test.ts/multiple-traces-via-url-and-local-cache-key/back-to-trace-1.png.sha256
index abaf8ec..5ae135f 100644
--- a/test/data/ui-screenshots/local_cache_key.test.ts/multiple-traces-via-url-and-local-cache-key/back-to-trace-1.png.sha256
+++ b/test/data/ui-screenshots/local_cache_key.test.ts/multiple-traces-via-url-and-local-cache-key/back-to-trace-1.png.sha256
@@ -1 +1 @@
-6571fb80446711a999eae1a061729756e980261a5a9eef60bc14120e2e628e2e
\ No newline at end of file
+6e04f747447d8c57a54c7317d2028141a8d4f5651c75d348e4e943ca6a3263c8
\ No newline at end of file
diff --git a/test/data/ui-screenshots/local_cache_key.test.ts/multiple-traces-via-url-and-local-cache-key/trace-1.png.sha256 b/test/data/ui-screenshots/local_cache_key.test.ts/multiple-traces-via-url-and-local-cache-key/trace-1.png.sha256
index 898b52e..79e1c86 100644
--- a/test/data/ui-screenshots/local_cache_key.test.ts/multiple-traces-via-url-and-local-cache-key/trace-1.png.sha256
+++ b/test/data/ui-screenshots/local_cache_key.test.ts/multiple-traces-via-url-and-local-cache-key/trace-1.png.sha256
@@ -1 +1 @@
-b140a146387d730a4ec8ba60d07f52c5b33ad60284434a6fa4d238f3428133c4
\ No newline at end of file
+e9c26966c58a1bc50e5f6fd4ab8b906499b6b1e799a3ccf66d27afae5805ee75
\ No newline at end of file
diff --git a/test/data/ui-screenshots/local_cache_key.test.ts/multiple-traces-via-url-and-local-cache-key/trace-2.png.sha256 b/test/data/ui-screenshots/local_cache_key.test.ts/multiple-traces-via-url-and-local-cache-key/trace-2.png.sha256
index d48fcd3..9ae5378 100644
--- a/test/data/ui-screenshots/local_cache_key.test.ts/multiple-traces-via-url-and-local-cache-key/trace-2.png.sha256
+++ b/test/data/ui-screenshots/local_cache_key.test.ts/multiple-traces-via-url-and-local-cache-key/trace-2.png.sha256
@@ -1 +1 @@
-914de1d755db3b8e861024f233c690533eee7ccb4e654278ab689be5e5de8e16
\ No newline at end of file
+9b37991687fb75c5aa52896fde047b45298c2ae2c5e179906d1647f1091772c2
\ No newline at end of file
diff --git a/test/data/ui-screenshots/queries.test.ts/omnibox-query/query-mode.png.sha256 b/test/data/ui-screenshots/queries.test.ts/omnibox-query/query-mode.png.sha256
index add1b4a..dec1614 100644
--- a/test/data/ui-screenshots/queries.test.ts/omnibox-query/query-mode.png.sha256
+++ b/test/data/ui-screenshots/queries.test.ts/omnibox-query/query-mode.png.sha256
@@ -1 +1 @@
-98e5ddab112c3263280ffdd0dd975d350e6ca349691169fa93670b03e32d2a03
\ No newline at end of file
+5cde26dc18d32e38af60db7b82608bf97b64a8772f74d8e2d6a8bad374f58630
\ No newline at end of file
diff --git a/test/data/ui-screenshots/queries.test.ts/omnibox-query/row-1-clicked.png.sha256 b/test/data/ui-screenshots/queries.test.ts/omnibox-query/row-1-clicked.png.sha256
index b2ab67f..59bc15d 100644
--- a/test/data/ui-screenshots/queries.test.ts/omnibox-query/row-1-clicked.png.sha256
+++ b/test/data/ui-screenshots/queries.test.ts/omnibox-query/row-1-clicked.png.sha256
@@ -1 +1 @@
-04aef63cd329c407627dd2be58c91b2157ec378d81e9afa0f3dc4a3a81e3ea3b
\ No newline at end of file
+cc3eda4a58d455908eaa865ebe64128f5fe61d7c4f88cfb803ec92db24dcc55a
\ No newline at end of file
diff --git a/test/data/ui-screenshots/queries.test.ts/omnibox-query/row-2-clicked.png.sha256 b/test/data/ui-screenshots/queries.test.ts/omnibox-query/row-2-clicked.png.sha256
index 3e35b36..3dbf2fa 100644
--- a/test/data/ui-screenshots/queries.test.ts/omnibox-query/row-2-clicked.png.sha256
+++ b/test/data/ui-screenshots/queries.test.ts/omnibox-query/row-2-clicked.png.sha256
@@ -1 +1 @@
-74384938265787c67a0027f3caab53cf5fe5e284f4a0d34e5b379de9d8ea2400
\ No newline at end of file
+e4bb9566ee1ff007ce17bdbe29f4d3ca946fcecf65eb60fddf206664a1d48bb6
\ No newline at end of file
diff --git a/test/data/ui-screenshots/queries.test.ts/query-page/query-limit-1.png.sha256 b/test/data/ui-screenshots/queries.test.ts/query-page/query-limit-1.png.sha256
index 27dee49..76a97ba 100644
--- a/test/data/ui-screenshots/queries.test.ts/query-page/query-limit-1.png.sha256
+++ b/test/data/ui-screenshots/queries.test.ts/query-page/query-limit-1.png.sha256
@@ -1 +1 @@
-ead0523b274bd10035b8e7830bc627560597446caf2f0134104be6c33f3b53fa
\ No newline at end of file
+aaf361593ca634a7dc9beaa578844ca8118482cb2553ae4308a36a045add60b0
\ No newline at end of file
diff --git a/test/data/ui-screenshots/queries.test.ts/query-page/query-limit-2.png.sha256 b/test/data/ui-screenshots/queries.test.ts/query-page/query-limit-2.png.sha256
index 7d0ac22..5090443 100644
--- a/test/data/ui-screenshots/queries.test.ts/query-page/query-limit-2.png.sha256
+++ b/test/data/ui-screenshots/queries.test.ts/query-page/query-limit-2.png.sha256
@@ -1 +1 @@
-8e46b254e3a4604ebf13d6758ead6f348b76b8046a8d3134582cbde9cb66c70b
\ No newline at end of file
+8344e56ced8e7d23ec5f17fa0ee17a429fd29377ae953c374eb5afdeb02ba704
\ No newline at end of file
diff --git a/test/data/ui-screenshots/queries.test.ts/query-page/query-limit-3.png.sha256 b/test/data/ui-screenshots/queries.test.ts/query-page/query-limit-3.png.sha256
index 6384615..184c548 100644
--- a/test/data/ui-screenshots/queries.test.ts/query-page/query-limit-3.png.sha256
+++ b/test/data/ui-screenshots/queries.test.ts/query-page/query-limit-3.png.sha256
@@ -1 +1 @@
-ed1f0942fc6ada989bdec04c6c6c1e6ea5a58ef706d694ccddc197f5e09c8fe0
\ No newline at end of file
+9667eb3714b75569d5055237e302f6053468e52242ac89d9fd01dbc2157b7bf8
\ No newline at end of file
diff --git a/test/data/ui-screenshots/sql_table_tab.test.ts/ShowTable-command/slices-table.png.sha256 b/test/data/ui-screenshots/sql_table_tab.test.ts/ShowTable-command/slices-table.png.sha256
index 0b21fad..5326122 100644
--- a/test/data/ui-screenshots/sql_table_tab.test.ts/ShowTable-command/slices-table.png.sha256
+++ b/test/data/ui-screenshots/sql_table_tab.test.ts/ShowTable-command/slices-table.png.sha256
@@ -1 +1 @@
-b3ca41f98356f086d1edfeb67696e272c1f91354103236620cea6432583a6bb2
\ No newline at end of file
+2e8ea1113b2bfa09408c06c34cabec429ee7ca7d83fa2db8d2d913ef34292077
\ No newline at end of file
diff --git a/test/data/ui-screenshots/sql_table_tab.test.ts/slices-with-same-name/slices-with-same-name.png.sha256 b/test/data/ui-screenshots/sql_table_tab.test.ts/slices-with-same-name/slices-with-same-name.png.sha256
index ed3df9a..e3b1a1a 100644
--- a/test/data/ui-screenshots/sql_table_tab.test.ts/slices-with-same-name/slices-with-same-name.png.sha256
+++ b/test/data/ui-screenshots/sql_table_tab.test.ts/slices-with-same-name/slices-with-same-name.png.sha256
@@ -1 +1 @@
-50e835c984c3716426f6e910f0870c98bc21099d376e8400e60539ef797fcbaa
\ No newline at end of file
+ef48a99b241e3bf2c56764c43d345cbf3e5a95e16cdc2bed89ee8605ed97b769
\ No newline at end of file
diff --git a/test/data/ui-screenshots/track_event_ordered_tracks.test.ts/chronological-order/chronological.png.sha256 b/test/data/ui-screenshots/track_event_ordered_tracks.test.ts/chronological-order/chronological.png.sha256
new file mode 100644
index 0000000..aaa8d26
--- /dev/null
+++ b/test/data/ui-screenshots/track_event_ordered_tracks.test.ts/chronological-order/chronological.png.sha256
@@ -0,0 +1 @@
+11a59e122d629a1e21852d46bba18fd97ae5753d4ae4249c3559ff164fcd355c
\ No newline at end of file
diff --git a/test/data/ui-screenshots/track_event_ordered_tracks.test.ts/explicit-order/explicit.png.sha256 b/test/data/ui-screenshots/track_event_ordered_tracks.test.ts/explicit-order/explicit.png.sha256
new file mode 100644
index 0000000..6b03833
--- /dev/null
+++ b/test/data/ui-screenshots/track_event_ordered_tracks.test.ts/explicit-order/explicit.png.sha256
@@ -0,0 +1 @@
+2eed4857125ffea15828a71637c42f279f80b662f357c92c353d98e1a668fbe7
\ No newline at end of file
diff --git a/test/data/ui-screenshots/track_event_ordered_tracks.test.ts/lexicographic-tracks/lexicographic.png.sha256 b/test/data/ui-screenshots/track_event_ordered_tracks.test.ts/lexicographic-tracks/lexicographic.png.sha256
new file mode 100644
index 0000000..5f58f66
--- /dev/null
+++ b/test/data/ui-screenshots/track_event_ordered_tracks.test.ts/lexicographic-tracks/lexicographic.png.sha256
@@ -0,0 +1 @@
+788e540b171775317df23c1def6d7adac15adff8acbc1072d29b4e649e25af97
\ No newline at end of file
diff --git a/test/data/ui-screenshots/track_event_ordered_tracks.test.ts/load-trace/loaded.png.sha256 b/test/data/ui-screenshots/track_event_ordered_tracks.test.ts/load-trace/loaded.png.sha256
new file mode 100644
index 0000000..03c2394
--- /dev/null
+++ b/test/data/ui-screenshots/track_event_ordered_tracks.test.ts/load-trace/loaded.png.sha256
@@ -0,0 +1 @@
+e8b5caa9b53e81ec5c5c9ab892c2326172ba56b91d4c1421a17e971341704255
\ No newline at end of file
diff --git a/test/data/ui-screenshots/wattson.test.ts/sched-aggregations/sched-aggr-process.png.sha256 b/test/data/ui-screenshots/wattson.test.ts/sched-aggregations/sched-aggr-process.png.sha256
index 258563d..86f06ee 100644
--- a/test/data/ui-screenshots/wattson.test.ts/sched-aggregations/sched-aggr-process.png.sha256
+++ b/test/data/ui-screenshots/wattson.test.ts/sched-aggregations/sched-aggr-process.png.sha256
@@ -1 +1 @@
-5751a9dc11fba6f164676fec125374fd183ede3f8c7fa545d5b19b421f0a5bce
\ No newline at end of file
+aef36afce71de9eaa3fb92d36c2dc37dbfdfc82d9b25717f0b80c11db35d7caa
\ No newline at end of file
diff --git a/test/data/ui-screenshots/wattson.test.ts/sched-aggregations/sched-aggr-thread.png.sha256 b/test/data/ui-screenshots/wattson.test.ts/sched-aggregations/sched-aggr-thread.png.sha256
index 9c2ca10..d51ed7f 100644
--- a/test/data/ui-screenshots/wattson.test.ts/sched-aggregations/sched-aggr-thread.png.sha256
+++ b/test/data/ui-screenshots/wattson.test.ts/sched-aggregations/sched-aggr-thread.png.sha256
@@ -1 +1 @@
-2f4c6320397ee6deb6b19006bc54a310a4cbc96b82d2ecc6d861339527b2d283
\ No newline at end of file
+e0ec62b98f6c2a4d1781ab4b0913f9d11c24b1c5d44b03010fcd178aeda06c3d
\ No newline at end of file
diff --git a/test/data/ui-screenshots/wattson.test.ts/wattson-aggregations/wattson-estimate-aggr.png.sha256 b/test/data/ui-screenshots/wattson.test.ts/wattson-aggregations/wattson-estimate-aggr.png.sha256
index 789c695..8121185 100644
--- a/test/data/ui-screenshots/wattson.test.ts/wattson-aggregations/wattson-estimate-aggr.png.sha256
+++ b/test/data/ui-screenshots/wattson.test.ts/wattson-aggregations/wattson-estimate-aggr.png.sha256
@@ -1 +1 @@
-4032e7e8ba999917fb12d474171e14df34da308b1294454d1b6c12d965c2f834
\ No newline at end of file
+1cacd75a7bc38896b5609ef77666345242ecd1efb692760f891061bd8ae0d86b
\ No newline at end of file
diff --git a/test/data/v8-samples.json.sha256 b/test/data/v8-samples.json.sha256
new file mode 100644
index 0000000..58baadc
--- /dev/null
+++ b/test/data/v8-samples.json.sha256
@@ -0,0 +1 @@
+b0714096a2112ed161bda0aa4bbd6ab80f0ac195c72ff2be1a1a57272fd14735
\ No newline at end of file
diff --git a/test/data/wattson_syscore_suspend.pb.sha256 b/test/data/wattson_syscore_suspend.pb.sha256
new file mode 100644
index 0000000..6a6d34e
--- /dev/null
+++ b/test/data/wattson_syscore_suspend.pb.sha256
@@ -0,0 +1 @@
+611b1824b4c5a26c7cf9e0bf06c4af32a5e30149ecd07ca2cd10160a0872108d
\ No newline at end of file
diff --git a/test/data/wattson_tk4_pcmark.pb.sha256 b/test/data/wattson_tk4_pcmark.pb.sha256
new file mode 100644
index 0000000..c39092d
--- /dev/null
+++ b/test/data/wattson_tk4_pcmark.pb.sha256
@@ -0,0 +1 @@
+9f807b520842034eda96b81ef6e6be6d2b395e83d08a3162ad0df2956ca9f947
\ No newline at end of file
diff --git a/test/trace_processor/diff_tests/include_index.py b/test/trace_processor/diff_tests/include_index.py
index e522806..69c1bc7 100644
--- a/test/trace_processor/diff_tests/include_index.py
+++ b/test/trace_processor/diff_tests/include_index.py
@@ -60,6 +60,7 @@
from diff_tests.parser.android.tests_surfaceflinger_transactions import SurfaceFlingerTransactions
from diff_tests.parser.android.tests_viewcapture import ViewCapture
from diff_tests.parser.android.tests_windowmanager import WindowManager
+from diff_tests.parser.art_method.tests import ArtMethodParser
from diff_tests.parser.atrace.tests import Atrace
from diff_tests.parser.atrace.tests_error_handling import AtraceErrorHandling
from diff_tests.parser.chrome.tests import ChromeParser
@@ -68,7 +69,9 @@
from diff_tests.parser.cros.tests import Cros
from diff_tests.parser.fs.tests import Fs
from diff_tests.parser.ftrace.ftrace_crop_tests import FtraceCrop
+from diff_tests.parser.ftrace.kprobes_tests import Kprobes
from diff_tests.parser.fuchsia.tests import Fuchsia
+from diff_tests.parser.gecko.tests import GeckoParser
from diff_tests.parser.graphics.tests import GraphicsParser
from diff_tests.parser.graphics.tests_drm_related_ftrace_events import GraphicsDrmRelatedFtraceEvents
from diff_tests.parser.graphics.tests_gpu_trace import GraphicsGpuTrace
@@ -83,6 +86,7 @@
from diff_tests.parser.parsing.tests_rss_stats import ParsingRssStats
from diff_tests.parser.parsing.tests_sys_stats import ParsingSysStats
from diff_tests.parser.parsing.tests_traced_stats import ParsingTracedStats
+from diff_tests.parser.perf_text.tests import PerfTextParser
from diff_tests.parser.power.tests_energy_breakdown import PowerEnergyBreakdown
from diff_tests.parser.power.tests_entity_state_residency import EntityStateResidency
from diff_tests.parser.power.tests_linux_sysfs_power import LinuxSysfsPower
@@ -104,15 +108,15 @@
from diff_tests.parser.ufs.tests import Ufs
from diff_tests.parser.zip.tests import Zip
from diff_tests.stdlib.android.cpu_cluster_tests import CpuClusters
+from diff_tests.stdlib.android.desktop_mode_tests import DesktopMode
from diff_tests.stdlib.android.frames_tests import Frames
from diff_tests.stdlib.android.gpu import AndroidGpu
from diff_tests.stdlib.android.heap_graph_tests import HeapGraph
+from diff_tests.stdlib.android.heap_profile_tests import HeapProfile
from diff_tests.stdlib.android.memory import AndroidMemory
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
@@ -139,6 +143,7 @@
from diff_tests.stdlib.span_join.tests_smoke import SpanJoinSmoke
from diff_tests.stdlib.tests import StdlibSmoke
from diff_tests.stdlib.timestamps.tests import Timestamps
+from diff_tests.stdlib.viz.tests import Viz
from diff_tests.stdlib.wattson.tests import WattsonStdlib
from diff_tests.syntax.filtering_tests import PerfettoFiltering
from diff_tests.syntax.function_tests import PerfettoFunction
@@ -233,6 +238,7 @@
*ParsingMemoryCounters(index_path, 'parser/parsing',
'ParsingMemoryCounters').fetch(),
*FtraceCrop(index_path, 'parser/ftrace', 'FtraceCrop').fetch(),
+ *Kprobes(index_path, 'parser/ftrace', 'Kprobes').fetch(),
*ParsingTracedStats(index_path, 'parser/parsing',
'ParsingTracedStats').fetch(),
*Zip(index_path, 'parser/zip', 'Zip').fetch(),
@@ -240,6 +246,10 @@
'AndroidInputEvent').fetch(),
*Instruments(index_path, 'parser/instruments', 'Instruments').fetch(),
*Gzip(index_path, 'parser/gzip', 'Gzip').fetch(),
+ *GeckoParser(index_path, 'parser/gecko', 'GeckoParser').fetch(),
+ *ArtMethodParser(index_path, 'parser/art_method',
+ 'ArtMethodParser').fetch(),
+ *PerfTextParser(index_path, 'parser/perf_text', 'PerfTextParser').fetch(),
]
metrics_tests = [
@@ -286,6 +296,7 @@
*AndroidGpu(index_path, 'stdlib/android', 'AndroidGpu').fetch(),
*AndroidStdlib(index_path, 'stdlib/android', 'AndroidStdlib').fetch(),
*CpuClusters(index_path, 'stdlib/android', 'CpuClusters').fetch(),
+ *DesktopMode(index_path, 'stdlib/android', 'DesktopMode').fetch(),
*LinuxCpu(index_path, 'stdlib/linux/cpu', 'LinuxCpu').fetch(),
*LinuxTests(index_path, 'stdlib/linux', 'LinuxTests').fetch(),
*DominatorTree(index_path, 'stdlib/graphs', 'DominatorTree').fetch(),
@@ -313,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(),
@@ -322,14 +332,15 @@
*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',
'StdlibIntervalsIntersect').fetch(),
*Startups(index_path, 'stdlib/android', 'Startups').fetch(),
*Timestamps(index_path, 'stdlib/timestamps', 'Timestamps').fetch(),
+ *Viz(index_path, 'stdlib/viz', 'Viz').fetch(),
*WattsonStdlib(index_path, 'stdlib/wattson', 'WattsonStdlib').fetch(),
+ *HeapProfile(index_path, 'stdlib/android', 'HeapProfile').fetch(),
] + chrome_stdlib_tests
syntax_tests = [
diff --git a/test/trace_processor/diff_tests/metrics/android/tests.py b/test/trace_processor/diff_tests/metrics/android/tests.py
index 347f95f..5f24e2e 100644
--- a/test/trace_processor/diff_tests/metrics/android/tests.py
+++ b/test/trace_processor/diff_tests/metrics/android/tests.py
@@ -375,47 +375,61 @@
query=Metric("wattson_app_startup_rails"),
out=Csv("""
wattson_app_startup_rails {
- metric_version: 3
+ metric_version: 4
+ power_model_version: 1
period_info {
period_id: 1
period_dur: 384847255
cpu_subsystem {
estimated_mw: 4568.1772
+ estimated_mws: 1758.050415
policy0 {
estimated_mw: 578.31256
+ estimated_mws: 222.561996
cpu0 {
estimated_mw: 148.99423
+ estimated_mws: 57.340019
}
cpu1 {
estimated_mw: 130.13142
+ estimated_mws: 50.080723
}
cpu2 {
estimated_mw: 127.60357
+ estimated_mws: 49.107883
}
cpu3 {
estimated_mw: 171.58333
+ estimated_mws: 66.033371
}
}
policy4 {
estimated_mw: 684.18835
+ estimated_mws: 263.308014
cpu4 {
estimated_mw: 344.39563
+ estimated_mws: 132.539703
}
cpu5 {
estimated_mw: 339.7927
+ estimated_mws: 130.768295
}
}
policy6 {
estimated_mw: 2163.158
+ estimated_mws: 832.48541
cpu6 {
estimated_mw: 1080.6881
+ estimated_mws: 415.89984
}
cpu7 {
estimated_mw: 1082.47
+ estimated_mws: 416.585602
}
}
dsu_scu {
estimated_mw: 1142.5181
+ estimated_mws: 439.694946
}
}
}
@@ -428,29 +442,37 @@
query=Metric("wattson_trace_rails"),
out=Csv("""
wattson_trace_rails {
- metric_version: 3
+ metric_version: 4
+ power_model_version: 1
period_info {
period_id: 1
- period_dur: 61792616758
+ period_dur: 61792677852
cpu_subsystem {
- estimated_mw: 42.12355
+ estimated_mw: 42.123608
+ estimated_mws: 2602.930420
policy0 {
- estimated_mw: 34.71888
+ estimated_mw: 34.71892
+ estimated_mws: 2145.375244
cpu0 {
- estimated_mw: 10.7050705
+ estimated_mw: 10.705099
+ estimated_mws: 661.496704
}
cpu1 {
- estimated_mw: 8.315672
+ estimated_mw: 8.315703
+ estimated_mws: 513.849548
}
cpu2 {
- estimated_mw: 7.7776303
+ estimated_mw: 7.7776227
+ estimated_mws: 480.600128
}
cpu3 {
- estimated_mw: 7.920505
+ estimated_mw: 7.9204974
+ estimated_mws: 489.428741
}
}
dsu_scu {
- estimated_mw: 7.404673
+ estimated_mw: 7.404684
+ estimated_mws: 457.555267
}
}
}
@@ -481,29 +503,37 @@
query=Metric("wattson_markers_rails"),
out=Csv("""
wattson_markers_rails {
- metric_version: 3
+ metric_version: 4
+ power_model_version: 1
period_info {
period_id: 1
period_dur: 2031871358
cpu_subsystem {
estimated_mw: 46.540943
+ estimated_mws: 94.565208
policy0 {
estimated_mw: 34.037483
+ estimated_mws: 69.159790
cpu0 {
estimated_mw: 14.416655
+ estimated_mws: 29.292788
}
cpu1 {
estimated_mw: 6.641429
+ estimated_mws: 13.494529
}
cpu2 {
estimated_mw: 8.134797
+ estimated_mws: 16.528862
}
cpu3 {
estimated_mw: 4.8446035
+ estimated_mws: 9.843612
}
}
dsu_scu {
estimated_mw: 12.503458
+ estimated_mws: 25.405418
}
}
}
diff --git a/test/trace_processor/diff_tests/metrics/android/wattson_markers_threads.out b/test/trace_processor/diff_tests/metrics/android/wattson_markers_threads.out
index 28464f2..4168034 100644
--- a/test/trace_processor/diff_tests/metrics/android/wattson_markers_threads.out
+++ b/test/trace_processor/diff_tests/metrics/android/wattson_markers_threads.out
@@ -1,658 +1,722 @@
wattson_markers_threads {
- metric_version: 2
- task_info {
- estimated_mws: 15.333553
- estimated_mw: 7.546518
- thread_name: "swapper"
- thread_id: 0
- process_id: 0
- }
- task_info {
- estimated_mws: 11.805121
- estimated_mw: 5.809974
- thread_name: "RenderThread"
- process_name: "com.google.android.wearable.sysui"
- thread_id: 3099
- process_id: 2710
- }
- task_info {
- estimated_mws: 9.112684
- estimated_mw: 4.484872
- thread_name: "binder:683_3"
- process_name: "/vendor/bin/hw/vendor.qti.hardware.display.composer-service"
- thread_id: 816
- process_id: 683
- }
- task_info {
- estimated_mws: 8.802570
- estimated_mw: 4.332248
- thread_name: "surfaceflinger"
- process_name: "/system/bin/surfaceflinger"
- thread_id: 742
- process_id: 742
- }
- task_info {
- estimated_mws: 4.007993
- estimated_mw: 1.972562
- thread_name: ".wearable.sysui"
- process_name: "com.google.android.wearable.sysui"
- thread_id: 2710
- process_id: 2710
- }
- task_info {
- estimated_mws: 1.779128
- estimated_mw: 0.875610
- thread_name: "crtc_commit:80"
- process_name: "crtc_commit:80"
- thread_id: 300
- process_id: 300
- }
- task_info {
- estimated_mws: 1.436499
- estimated_mw: 0.706983
- thread_name: "binder:2710_E"
- process_name: "com.google.android.wearable.sysui"
- thread_id: 6515
- process_id: 2710
- }
- task_info {
- estimated_mws: 1.262685
- estimated_mw: 0.621440
- thread_name: "TimerDispatch"
- process_name: "/system/bin/surfaceflinger"
- thread_id: 819
- process_id: 742
- }
- task_info {
- estimated_mws: 1.242906
- estimated_mw: 0.611705
- thread_name: "kworker/u8:4"
- process_name: "kworker/u8:4"
- thread_id: 11407
- process_id: 11407
- }
- task_info {
- estimated_mws: 1.231494
- estimated_mw: 0.606089
- thread_name: "BckgrndExec HP"
- process_name: "/system/bin/surfaceflinger"
- thread_id: 837
- process_id: 742
- }
- task_info {
- estimated_mws: 1.194067
- estimated_mw: 0.587669
- thread_name: "kworker/u8:5"
- process_name: "kworker/u8:5"
- thread_id: 10610
- process_id: 10610
- }
- task_info {
- estimated_mws: 1.132809
- estimated_mw: 0.557520
- thread_name: "binder:742_2"
- process_name: "/system/bin/surfaceflinger"
- thread_id: 791
- process_id: 742
- }
- task_info {
- estimated_mws: 1.065350
- estimated_mw: 0.524320
- thread_name: "rcu_preempt"
- process_name: "rcu_preempt"
- thread_id: 14
- process_id: 14
- }
- task_info {
- estimated_mws: 0.872391
- estimated_mw: 0.429353
- thread_name: "binder:2710_7"
- process_name: "com.google.android.wearable.sysui"
- thread_id: 5691
- process_id: 2710
- }
- task_info {
- estimated_mws: 0.865098
- estimated_mw: 0.425764
- thread_name: "traced_probes"
- process_name: "/system/bin/traced_probes"
- thread_id: 916
- process_id: 916
- }
- task_info {
- estimated_mws: 0.844506
- estimated_mw: 0.415630
- thread_name: "sleep"
- process_name: "sleep"
- thread_id: 11474
- process_id: 11474
- }
- task_info {
- estimated_mws: 0.795564
- estimated_mw: 0.391542
- thread_name: "kgsl_dispatcher"
- process_name: "kgsl_dispatcher"
- thread_id: 122
- process_id: 122
- }
- task_info {
- estimated_mws: 0.715993
- estimated_mw: 0.352381
- thread_name: "irq/33-4520300."
- process_name: "irq/33-4520300."
- thread_id: 307
- process_id: 307
- }
- task_info {
- estimated_mws: 0.715212
- estimated_mw: 0.351997
- thread_name: "binder:742_1"
- process_name: "/system/bin/surfaceflinger"
- thread_id: 786
- process_id: 742
- }
- task_info {
- estimated_mws: 0.659174
- estimated_mw: 0.324417
- thread_name: "surfaceflinger"
- process_name: "/system/bin/surfaceflinger"
- thread_id: 788
- process_id: 742
- }
- task_info {
- estimated_mws: 0.653970
- estimated_mw: 0.321856
- thread_name: "app"
- process_name: "/system/bin/surfaceflinger"
- thread_id: 820
- process_id: 742
- }
- task_info {
- estimated_mws: 0.463974
- estimated_mw: 0.228348
- thread_name: "rcuog/0"
- process_name: "rcuog/0"
- thread_id: 15
- process_id: 15
- }
- task_info {
- estimated_mws: 0.434532
- estimated_mw: 0.213858
- thread_name: "Primes-Jank"
- process_name: "com.google.android.wearable.sysui"
- thread_id: 5094
- process_id: 2710
- }
- task_info {
- estimated_mws: 0.356684
- estimated_mw: 0.175544
- thread_name: "crtc_event:80"
- process_name: "crtc_event:80"
- thread_id: 301
- process_id: 301
- }
- task_info {
- estimated_mws: 0.271999
- estimated_mw: 0.133866
- thread_name: "rcuog/2"
- process_name: "rcuog/2"
- thread_id: 40
- process_id: 40
- }
- task_info {
- estimated_mws: 0.204649
- estimated_mw: 0.100719
- thread_name: "binder:2710_2"
- process_name: "com.google.android.wearable.sysui"
- thread_id: 3100
- process_id: 2710
- }
- task_info {
- estimated_mws: 0.197450
- estimated_mw: 0.097176
- thread_name: "FileWatcherThre"
- process_name: "/vendor/bin/hw/android.hardware.thermal-service.pixel"
- thread_id: 1544
- process_id: 1529
- }
- task_info {
- estimated_mws: 0.165350
- estimated_mw: 0.081378
- thread_name: "rcuop/0"
- process_name: "rcuop/0"
- thread_id: 16
- process_id: 16
- }
- task_info {
- estimated_mws: 0.123135
- estimated_mw: 0.060602
- thread_name: "msm_irqbalance"
- process_name: "/vendor/bin/msm_irqbalance"
- thread_id: 3230
- process_id: 3230
- }
- task_info {
- estimated_mws: 0.122703
- estimated_mw: 0.060389
- thread_name: "kgsl-events"
- process_name: "kgsl-events"
- thread_id: 120
- process_id: 120
- }
- task_info {
- estimated_mws: 0.106642
- estimated_mw: 0.052485
- thread_name: "traced"
- process_name: "/system/bin/traced"
- thread_id: 919
- process_id: 919
- }
- task_info {
- estimated_mws: 0.104195
- estimated_mw: 0.051280
- thread_name: "kworker/2:0"
- process_name: "kworker/2:0"
- thread_id: 11444
- process_id: 11444
- }
- task_info {
- estimated_mws: 0.095284
- estimated_mw: 0.046894
- thread_name: "rcuop/2"
- process_name: "rcuop/2"
- thread_id: 41
- process_id: 41
- }
- task_info {
- estimated_mws: 0.084534
- estimated_mw: 0.041604
- thread_name: "RegSampIdle"
- process_name: "/system/bin/surfaceflinger"
- thread_id: 826
- process_id: 742
- }
- task_info {
- estimated_mws: 0.076505
- estimated_mw: 0.037652
- thread_name: "rcuop/1"
- process_name: "rcuop/1"
- thread_id: 32
- process_id: 32
- }
- task_info {
- estimated_mws: 0.067736
- estimated_mw: 0.033337
- thread_name: "sh"
- process_name: "/system/bin/sh"
- thread_id: 11472
- process_id: 11472
- }
- task_info {
- estimated_mws: 0.065940
- estimated_mw: 0.032453
- thread_name: "BG"
- process_name: "com.google.android.wearable.sysui"
- thread_id: 3524
- process_id: 2710
- }
- task_info {
- estimated_mws: 0.053141
- estimated_mw: 0.026154
- thread_name: "StateService"
- process_name: "com.google.android.apps.scone"
- thread_id: 3621
- process_id: 3505
- }
- task_info {
- estimated_mws: 0.052200
- estimated_mw: 0.025691
- thread_name: "Blocking Thread"
- process_name: "com.fitbit.FitbitMobile"
- thread_id: 11310
- process_id: 11279
- }
- task_info {
- estimated_mws: 0.040767
- estimated_mw: 0.020064
- thread_name: "kworker/0:1"
- process_name: "kworker/0:1"
- thread_id: 11436
- process_id: 11436
- }
- task_info {
- estimated_mws: 0.040587
- estimated_mw: 0.019975
- thread_name: "binder:1629_7"
- process_name: "system_server"
- thread_id: 2635
- process_id: 1629
- }
- task_info {
- estimated_mws: 0.040484
- estimated_mw: 0.019924
- thread_name: "rcuop/3"
- process_name: "rcuop/3"
- thread_id: 49
- process_id: 49
- }
- task_info {
- estimated_mws: 0.038016
- estimated_mw: 0.018710
- thread_name: "atchdog.monitor"
- process_name: "system_server"
- thread_id: 1669
- process_id: 1629
- }
- task_info {
- estimated_mws: 0.036888
- estimated_mw: 0.018155
- thread_name: "logd.writer"
- process_name: "/system/bin/logd"
- thread_id: 228
- process_id: 213
- }
- task_info {
- estimated_mws: 0.032972
- estimated_mw: 0.016227
- thread_name: "surfaceflinger"
- process_name: "/system/bin/surfaceflinger"
- thread_id: 828
- process_id: 742
- }
- task_info {
- estimated_mws: 0.032239
- estimated_mw: 0.015867
- thread_name: "it.FitbitMobile"
- process_name: "com.fitbit.FitbitMobile"
- thread_id: 11279
- process_id: 11279
- }
- task_info {
- estimated_mws: 0.031160
- estimated_mw: 0.015336
- thread_name: "binder:11279_4"
- process_name: "com.fitbit.FitbitMobile"
- thread_id: 11426
- process_id: 11279
- }
- task_info {
- estimated_mws: 0.028389
- estimated_mw: 0.013972
- thread_name: "irq/207-dwc3"
- process_name: "irq/207-dwc3"
- thread_id: 9733
- process_id: 9733
- }
- task_info {
- estimated_mws: 0.027208
- estimated_mw: 0.013391
- thread_name: "UsbFfs-worker"
- process_name: "/apex/com.android.adbd/bin/adbd"
- thread_id: 9734
- process_id: 5154
- }
- task_info {
- estimated_mws: 0.024832
- estimated_mw: 0.012221
- thread_name: "logcat"
- process_name: "logcat"
- thread_id: 1199
- process_id: 1199
- }
- task_info {
- estimated_mws: 0.023707
- estimated_mw: 0.011668
- thread_name: "logd.reader.per"
- process_name: "/system/bin/logd"
- thread_id: 1227
- process_id: 213
- }
- task_info {
- estimated_mws: 0.022160
- estimated_mw: 0.010906
- thread_name: "kworker/u8:2"
- process_name: "kworker/u8:2"
- thread_id: 11458
- process_id: 11458
- }
- task_info {
- estimated_mws: 0.019052
- estimated_mw: 0.009376
- thread_name: "Scheduled BG"
- process_name: "com.google.android.wearable.sysui"
- thread_id: 3575
- process_id: 2710
- }
- task_info {
- estimated_mws: 0.018414
- estimated_mw: 0.009063
- thread_name: "RegionSampling"
- process_name: "/system/bin/surfaceflinger"
- thread_id: 825
- process_id: 742
- }
- task_info {
- estimated_mws: 0.016701
- estimated_mw: 0.008220
- thread_name: "halt_drain_rqs"
- process_name: "halt_drain_rqs"
- thread_id: 108
- process_id: 108
- }
- task_info {
- estimated_mws: 0.011023
- estimated_mw: 0.005425
- thread_name: "irq/26-4744000."
- process_name: "irq/26-4744000."
- thread_id: 112
- process_id: 112
- }
- task_info {
- estimated_mws: 0.010004
- estimated_mw: 0.004924
- thread_name: "migration/2"
- process_name: "migration/2"
- thread_id: 35
- process_id: 35
- }
- task_info {
- estimated_mws: 0.008819
- estimated_mw: 0.004341
- thread_name: "ksoftirqd/0"
- process_name: "ksoftirqd/0"
- thread_id: 13
- process_id: 13
- }
- task_info {
- estimated_mws: 0.007911
- estimated_mw: 0.003894
- thread_name: "watchdog"
- process_name: "system_server"
- thread_id: 1676
- process_id: 1629
- }
- task_info {
- estimated_mws: 0.007796
- estimated_mw: 0.003837
- thread_name: "pool-283-thread"
- process_name: "system_server"
- thread_id: 4427
- process_id: 1629
- }
- task_info {
- estimated_mws: 0.007628
- estimated_mw: 0.003754
- thread_name: "adbd"
- process_name: "/apex/com.android.adbd/bin/adbd"
- thread_id: 5154
- process_id: 5154
- }
- task_info {
- estimated_mws: 0.006796
- estimated_mw: 0.003344
- thread_name: "pool-1-thread-1"
- process_name: "system_server"
- thread_id: 2655
- process_id: 1629
- }
- task_info {
- estimated_mws: 0.005691
- estimated_mw: 0.002801
- thread_name: "pool-1-thread-1"
- process_name: "com.google.android.apps.scone"
- thread_id: 3625
- process_id: 3505
- }
- task_info {
- estimated_mws: 0.005476
- estimated_mw: 0.002695
- thread_name: "binder:237_2"
- process_name: "/system/bin/vold"
- thread_id: 237
- process_id: 237
- }
- task_info {
- estimated_mws: 0.004537
- estimated_mw: 0.002233
- thread_name: "shell svc 11472"
- process_name: "/apex/com.android.adbd/bin/adbd"
- thread_id: 11473
- process_id: 5154
- }
- task_info {
- estimated_mws: 0.003924
- estimated_mw: 0.001931
- thread_name: "ksoftirqd/1"
- process_name: "ksoftirqd/1"
- thread_id: 29
- process_id: 29
- }
- task_info {
- estimated_mws: 0.002908
- estimated_mw: 0.001431
- thread_name: "BG"
- process_name: "com.google.android.wearable.sysui"
- thread_id: 5230
- process_id: 2710
- }
- task_info {
- estimated_mws: 0.002492
- estimated_mw: 0.001226
- thread_name: "kworker/3:2"
- process_name: "kworker/3:2"
- thread_id: 9832
- process_id: 9832
- }
- task_info {
- estimated_mws: 0.002333
- estimated_mw: 0.001148
- thread_name: "Scheduled BG"
- process_name: "com.google.android.wearable.sysui"
- thread_id: 3577
- process_id: 2710
- }
- task_info {
- estimated_mws: 0.002293
- estimated_mw: 0.001128
- thread_name: "InputReader"
- process_name: "system_server"
- thread_id: 2560
- process_id: 1629
- }
- task_info {
- estimated_mws: 0.002261
- estimated_mw: 0.001113
- thread_name: "DefaultDispatch"
- process_name: "com.google.android.wearable.media.sessions"
- thread_id: 3618
- process_id: 3553
- }
- task_info {
- estimated_mws: 0.002226
- estimated_mw: 0.001095
- thread_name: "migration/3"
- process_name: "migration/3"
- thread_id: 44
- process_id: 44
- }
- task_info {
- estimated_mws: 0.002100
- estimated_mw: 0.001034
- thread_name: "InputDispatcher"
- process_name: "system_server"
- thread_id: 2559
- process_id: 1629
- }
- task_info {
- estimated_mws: 0.001973
- estimated_mw: 0.000971
- thread_name: "kworker/1:0"
- process_name: "kworker/1:0"
- thread_id: 10984
- process_id: 10984
- }
- task_info {
- estimated_mws: 0.001966
- estimated_mw: 0.000967
- thread_name: "irq/193-wdog-ba"
- process_name: "irq/193-wdog-ba"
- thread_id: 344
- process_id: 344
- }
- task_info {
- estimated_mws: 0.001867
- estimated_mw: 0.000919
- thread_name: "DefaultDispatch"
- process_name: "com.google.android.wearable.media.sessions"
- thread_id: 3615
- process_id: 3553
- }
- task_info {
- estimated_mws: 0.001867
- estimated_mw: 0.000919
- thread_name: "irq/25-mmc0"
- process_name: "irq/25-mmc0"
- thread_id: 115
- process_id: 115
- }
- task_info {
- estimated_mws: 0.001728
- estimated_mw: 0.000851
- thread_name: "iou-wrk-214"
- process_name: "/system/bin/lmkd"
- thread_id: 11440
- process_id: 214
- }
- task_info {
- estimated_mws: 0.001600
- estimated_mw: 0.000787
- thread_name: "DefaultDispatch"
- process_name: "com.google.android.wearable.media.sessions"
- thread_id: 3616
- process_id: 3553
- }
- task_info {
- estimated_mws: 0.001393
- estimated_mw: 0.000686
- thread_name: "kworker/u8:1"
- process_name: "kworker/u8:1"
- thread_id: 11185
- process_id: 11185
- }
- task_info {
- estimated_mws: 0.001373
- estimated_mw: 0.000676
- thread_name: "Scheduled BG"
- process_name: "com.google.android.wearable.sysui"
- thread_id: 3576
- process_id: 2710
- }
- task_info {
- estimated_mws: 0.000811
- estimated_mw: 0.000399
- thread_name: "Scheduled BG"
- process_name: "com.google.android.wearable.sysui"
- thread_id: 3622
- process_id: 2710
+ metric_version: 4
+ power_model_version: 1
+ period_info {
+ period_id: 1
+ task_info {
+ estimated_mws: 15.333553
+ estimated_mw: 7.546518
+ thread_name: "swapper"
+ thread_id: 0
+ process_id: 0
+ }
+ task_info {
+ estimated_mws: 11.805121
+ estimated_mw: 5.809974
+ idle_transitions_mws: 0.300579
+ thread_name: "RenderThread"
+ process_name: "com.google.android.wearable.sysui"
+ thread_id: 3099
+ process_id: 2710
+ }
+ task_info {
+ estimated_mws: 9.112684
+ estimated_mw: 4.484872
+ idle_transitions_mws: 0.013483
+ thread_name: "binder:683_3"
+ process_name: "/vendor/bin/hw/vendor.qti.hardware.display.composer-service"
+ thread_id: 816
+ process_id: 683
+ }
+ task_info {
+ estimated_mws: 8.802570
+ estimated_mw: 4.332248
+ idle_transitions_mws: 0.223591
+ thread_name: "surfaceflinger"
+ process_name: "/system/bin/surfaceflinger"
+ thread_id: 742
+ process_id: 742
+ }
+ task_info {
+ estimated_mws: 4.007993
+ estimated_mw: 1.972562
+ idle_transitions_mws: 0.449704
+ thread_name: ".wearable.sysui"
+ process_name: "com.google.android.wearable.sysui"
+ thread_id: 2710
+ process_id: 2710
+ }
+ task_info {
+ estimated_mws: 1.779128
+ estimated_mw: 0.875610
+ idle_transitions_mws: 0.686579
+ thread_name: "crtc_commit:80"
+ process_name: "crtc_commit:80"
+ thread_id: 300
+ process_id: 300
+ }
+ task_info {
+ estimated_mws: 1.436499
+ estimated_mw: 0.706983
+ idle_transitions_mws: 0.113163
+ thread_name: "binder:2710_E"
+ process_name: "com.google.android.wearable.sysui"
+ thread_id: 6515
+ process_id: 2710
+ }
+ task_info {
+ estimated_mws: 1.262685
+ estimated_mw: 0.621440
+ idle_transitions_mws: 0.337691
+ thread_name: "TimerDispatch"
+ process_name: "/system/bin/surfaceflinger"
+ thread_id: 819
+ process_id: 742
+ }
+ task_info {
+ estimated_mws: 1.242906
+ estimated_mw: 0.611705
+ idle_transitions_mws: 0.318923
+ thread_name: "kworker/u8:4"
+ process_name: "kworker/u8:4"
+ thread_id: 11407
+ process_id: 11407
+ }
+ task_info {
+ estimated_mws: 1.231494
+ estimated_mw: 0.606089
+ idle_transitions_mws: 0.311600
+ thread_name: "BckgrndExec HP"
+ process_name: "/system/bin/surfaceflinger"
+ thread_id: 837
+ process_id: 742
+ }
+ task_info {
+ estimated_mws: 1.194067
+ estimated_mw: 0.587669
+ idle_transitions_mws: 0.427458
+ thread_name: "kworker/u8:5"
+ process_name: "kworker/u8:5"
+ thread_id: 10610
+ process_id: 10610
+ }
+ task_info {
+ estimated_mws: 1.132809
+ estimated_mw: 0.557520
+ idle_transitions_mws: 0.095612
+ thread_name: "binder:742_2"
+ process_name: "/system/bin/surfaceflinger"
+ thread_id: 791
+ process_id: 742
+ }
+ task_info {
+ estimated_mws: 1.065350
+ estimated_mw: 0.524320
+ idle_transitions_mws: 1.154817
+ thread_name: "rcu_preempt"
+ process_name: "rcu_preempt"
+ thread_id: 14
+ process_id: 14
+ }
+ task_info {
+ estimated_mws: 0.872391
+ estimated_mw: 0.429353
+ idle_transitions_mws: 0.072071
+ thread_name: "binder:2710_7"
+ process_name: "com.google.android.wearable.sysui"
+ thread_id: 5691
+ process_id: 2710
+ }
+ task_info {
+ estimated_mws: 0.865098
+ estimated_mw: 0.425764
+ idle_transitions_mws: 0.010689
+ thread_name: "traced_probes"
+ process_name: "/system/bin/traced_probes"
+ thread_id: 916
+ process_id: 916
+ }
+ task_info {
+ estimated_mws: 0.844506
+ estimated_mw: 0.415630
+ idle_transitions_mws: 0.000684
+ thread_name: "sleep"
+ process_name: "sleep"
+ thread_id: 11474
+ process_id: 11474
+ }
+ task_info {
+ estimated_mws: 0.795564
+ estimated_mw: 0.391542
+ idle_transitions_mws: 0.252256
+ thread_name: "kgsl_dispatcher"
+ process_name: "kgsl_dispatcher"
+ thread_id: 122
+ process_id: 122
+ }
+ task_info {
+ estimated_mws: 0.715993
+ estimated_mw: 0.352381
+ idle_transitions_mws: 0.426966
+ thread_name: "irq/33-4520300."
+ process_name: "irq/33-4520300."
+ thread_id: 307
+ process_id: 307
+ }
+ task_info {
+ estimated_mws: 0.715212
+ estimated_mw: 0.351997
+ idle_transitions_mws: 0.062089
+ thread_name: "binder:742_1"
+ process_name: "/system/bin/surfaceflinger"
+ thread_id: 786
+ process_id: 742
+ }
+ task_info {
+ estimated_mws: 0.659174
+ estimated_mw: 0.324417
+ idle_transitions_mws: 0.153809
+ thread_name: "surfaceflinger"
+ process_name: "/system/bin/surfaceflinger"
+ thread_id: 788
+ process_id: 742
+ }
+ task_info {
+ estimated_mws: 0.653970
+ estimated_mw: 0.321856
+ idle_transitions_mws: 0.111764
+ thread_name: "app"
+ process_name: "/system/bin/surfaceflinger"
+ thread_id: 820
+ process_id: 742
+ }
+ task_info {
+ estimated_mws: 0.463974
+ estimated_mw: 0.228348
+ idle_transitions_mws: 0.175385
+ thread_name: "rcuog/0"
+ process_name: "rcuog/0"
+ thread_id: 15
+ process_id: 15
+ }
+ task_info {
+ estimated_mws: 0.434532
+ estimated_mw: 0.213858
+ idle_transitions_mws: 0.095329
+ thread_name: "Primes-Jank"
+ process_name: "com.google.android.wearable.sysui"
+ thread_id: 5094
+ process_id: 2710
+ }
+ task_info {
+ estimated_mws: 0.356684
+ estimated_mw: 0.175544
+ idle_transitions_mws: 0.276803
+ thread_name: "crtc_event:80"
+ process_name: "crtc_event:80"
+ thread_id: 301
+ process_id: 301
+ }
+ task_info {
+ estimated_mws: 0.271999
+ estimated_mw: 0.133866
+ idle_transitions_mws: 0.151016
+ thread_name: "rcuog/2"
+ process_name: "rcuog/2"
+ thread_id: 40
+ process_id: 40
+ }
+ task_info {
+ estimated_mws: 0.204649
+ estimated_mw: 0.100719
+ idle_transitions_mws: 0.067710
+ thread_name: "binder:2710_2"
+ process_name: "com.google.android.wearable.sysui"
+ thread_id: 3100
+ process_id: 2710
+ }
+ task_info {
+ estimated_mws: 0.197450
+ estimated_mw: 0.097176
+ thread_name: "FileWatcherThre"
+ process_name: "/vendor/bin/hw/android.hardware.thermal-service.pixel"
+ thread_id: 1544
+ process_id: 1529
+ }
+ task_info {
+ estimated_mws: 0.165350
+ estimated_mw: 0.081378
+ idle_transitions_mws: 0.030527
+ thread_name: "rcuop/0"
+ process_name: "rcuop/0"
+ thread_id: 16
+ process_id: 16
+ }
+ task_info {
+ estimated_mws: 0.123135
+ estimated_mw: 0.060602
+ thread_name: "msm_irqbalance"
+ process_name: "/vendor/bin/msm_irqbalance"
+ thread_id: 3230
+ process_id: 3230
+ }
+ task_info {
+ estimated_mws: 0.122703
+ estimated_mw: 0.060389
+ idle_transitions_mws: 0.026041
+ thread_name: "kgsl-events"
+ process_name: "kgsl-events"
+ thread_id: 120
+ process_id: 120
+ }
+ task_info {
+ estimated_mws: 0.106642
+ estimated_mw: 0.052485
+ thread_name: "traced"
+ process_name: "/system/bin/traced"
+ thread_id: 919
+ process_id: 919
+ }
+ task_info {
+ estimated_mws: 0.104195
+ estimated_mw: 0.051280
+ idle_transitions_mws: 0.301336
+ thread_name: "kworker/2:0"
+ process_name: "kworker/2:0"
+ thread_id: 11444
+ process_id: 11444
+ }
+ task_info {
+ estimated_mws: 0.095284
+ estimated_mw: 0.046894
+ idle_transitions_mws: 0.039497
+ thread_name: "rcuop/2"
+ process_name: "rcuop/2"
+ thread_id: 41
+ process_id: 41
+ }
+ task_info {
+ estimated_mws: 0.084534
+ estimated_mw: 0.041604
+ idle_transitions_mws: 0.025241
+ thread_name: "RegSampIdle"
+ process_name: "/system/bin/surfaceflinger"
+ thread_id: 826
+ process_id: 742
+ }
+ task_info {
+ estimated_mws: 0.076505
+ estimated_mw: 0.037652
+ idle_transitions_mws: 0.075963
+ thread_name: "rcuop/1"
+ process_name: "rcuop/1"
+ thread_id: 32
+ process_id: 32
+ }
+ task_info {
+ estimated_mws: 0.067736
+ estimated_mw: 0.033337
+ thread_name: "sh"
+ process_name: "/system/bin/sh"
+ thread_id: 11472
+ process_id: 11472
+ }
+ task_info {
+ estimated_mws: 0.065940
+ estimated_mw: 0.032453
+ idle_transitions_mws: 0.002859
+ thread_name: "BG"
+ process_name: "com.google.android.wearable.sysui"
+ thread_id: 3524
+ process_id: 2710
+ }
+ task_info {
+ estimated_mws: 0.053141
+ estimated_mw: 0.026154
+ idle_transitions_mws: 0.000744
+ thread_name: "StateService"
+ process_name: "com.google.android.apps.scone"
+ thread_id: 3621
+ process_id: 3505
+ }
+ task_info {
+ estimated_mws: 0.052200
+ estimated_mw: 0.025691
+ idle_transitions_mws: 0.000544
+ thread_name: "Blocking Thread"
+ process_name: "com.fitbit.FitbitMobile"
+ thread_id: 11310
+ process_id: 11279
+ }
+ task_info {
+ estimated_mws: 0.040767
+ estimated_mw: 0.020064
+ idle_transitions_mws: 0.003269
+ thread_name: "kworker/0:1"
+ process_name: "kworker/0:1"
+ thread_id: 11436
+ process_id: 11436
+ }
+ task_info {
+ estimated_mws: 0.040587
+ estimated_mw: 0.019975
+ thread_name: "binder:1629_7"
+ process_name: "system_server"
+ thread_id: 2635
+ process_id: 1629
+ }
+ task_info {
+ estimated_mws: 0.040484
+ estimated_mw: 0.019924
+ idle_transitions_mws: 0.020651
+ thread_name: "rcuop/3"
+ process_name: "rcuop/3"
+ thread_id: 49
+ process_id: 49
+ }
+ task_info {
+ estimated_mws: 0.038016
+ estimated_mw: 0.018710
+ idle_transitions_mws: 0.002906
+ thread_name: "atchdog.monitor"
+ process_name: "system_server"
+ thread_id: 1669
+ process_id: 1629
+ }
+ task_info {
+ estimated_mws: 0.036888
+ estimated_mw: 0.018155
+ thread_name: "logd.writer"
+ process_name: "/system/bin/logd"
+ thread_id: 228
+ process_id: 213
+ }
+ task_info {
+ estimated_mws: 0.032972
+ estimated_mw: 0.016227
+ idle_transitions_mws: 0.025051
+ thread_name: "surfaceflinger"
+ process_name: "/system/bin/surfaceflinger"
+ thread_id: 828
+ process_id: 742
+ }
+ task_info {
+ estimated_mws: 0.032239
+ estimated_mw: 0.015867
+ idle_transitions_mws: 0.001840
+ thread_name: "it.FitbitMobile"
+ process_name: "com.fitbit.FitbitMobile"
+ thread_id: 11279
+ process_id: 11279
+ }
+ task_info {
+ estimated_mws: 0.031160
+ estimated_mw: 0.015336
+ idle_transitions_mws: 0.000559
+ thread_name: "binder:11279_4"
+ process_name: "com.fitbit.FitbitMobile"
+ thread_id: 11426
+ process_id: 11279
+ }
+ task_info {
+ estimated_mws: 0.028389
+ estimated_mw: 0.013972
+ thread_name: "irq/207-dwc3"
+ process_name: "irq/207-dwc3"
+ thread_id: 9733
+ process_id: 9733
+ }
+ task_info {
+ estimated_mws: 0.027208
+ estimated_mw: 0.013391
+ idle_transitions_mws: 0.000951
+ thread_name: "UsbFfs-worker"
+ process_name: "/apex/com.android.adbd/bin/adbd"
+ thread_id: 9734
+ process_id: 5154
+ }
+ task_info {
+ estimated_mws: 0.024832
+ estimated_mw: 0.012221
+ thread_name: "logcat"
+ process_name: "logcat"
+ thread_id: 1199
+ process_id: 1199
+ }
+ task_info {
+ estimated_mws: 0.023707
+ estimated_mw: 0.011668
+ idle_transitions_mws: 0.000854
+ thread_name: "logd.reader.per"
+ process_name: "/system/bin/logd"
+ thread_id: 1227
+ process_id: 213
+ }
+ task_info {
+ estimated_mws: 0.022160
+ estimated_mw: 0.010906
+ idle_transitions_mws: 0.006160
+ thread_name: "kworker/u8:2"
+ process_name: "kworker/u8:2"
+ thread_id: 11458
+ process_id: 11458
+ }
+ task_info {
+ estimated_mws: 0.019052
+ estimated_mw: 0.009376
+ idle_transitions_mws: 0.008121
+ thread_name: "Scheduled BG"
+ process_name: "com.google.android.wearable.sysui"
+ thread_id: 3575
+ process_id: 2710
+ }
+ task_info {
+ estimated_mws: 0.018414
+ estimated_mw: 0.009063
+ idle_transitions_mws: 0.040061
+ thread_name: "RegionSampling"
+ process_name: "/system/bin/surfaceflinger"
+ thread_id: 825
+ process_id: 742
+ }
+ task_info {
+ estimated_mws: 0.016701
+ estimated_mw: 0.008220
+ idle_transitions_mws: 0.006448
+ thread_name: "halt_drain_rqs"
+ process_name: "halt_drain_rqs"
+ thread_id: 108
+ process_id: 108
+ }
+ task_info {
+ estimated_mws: 0.011023
+ estimated_mw: 0.005425
+ idle_transitions_mws: 0.001553
+ thread_name: "irq/26-4744000."
+ process_name: "irq/26-4744000."
+ thread_id: 112
+ process_id: 112
+ }
+ task_info {
+ estimated_mws: 0.010004
+ estimated_mw: 0.004924
+ thread_name: "migration/2"
+ process_name: "migration/2"
+ thread_id: 35
+ process_id: 35
+ }
+ task_info {
+ estimated_mws: 0.008819
+ estimated_mw: 0.004341
+ thread_name: "ksoftirqd/0"
+ process_name: "ksoftirqd/0"
+ thread_id: 13
+ process_id: 13
+ }
+ task_info {
+ estimated_mws: 0.007911
+ estimated_mw: 0.003894
+ idle_transitions_mws: 0.001205
+ thread_name: "watchdog"
+ process_name: "system_server"
+ thread_id: 1676
+ process_id: 1629
+ }
+ task_info {
+ estimated_mws: 0.007796
+ estimated_mw: 0.003837
+ idle_transitions_mws: 0.000627
+ thread_name: "pool-283-thread"
+ process_name: "system_server"
+ thread_id: 4427
+ process_id: 1629
+ }
+ task_info {
+ estimated_mws: 0.007628
+ estimated_mw: 0.003754
+ idle_transitions_mws: 0.001280
+ thread_name: "adbd"
+ process_name: "/apex/com.android.adbd/bin/adbd"
+ thread_id: 5154
+ process_id: 5154
+ }
+ task_info {
+ estimated_mws: 0.006796
+ estimated_mw: 0.003344
+ idle_transitions_mws: 0.001484
+ thread_name: "pool-1-thread-1"
+ process_name: "system_server"
+ thread_id: 2655
+ process_id: 1629
+ }
+ task_info {
+ estimated_mws: 0.005691
+ estimated_mw: 0.002801
+ idle_transitions_mws: 0.001832
+ thread_name: "pool-1-thread-1"
+ process_name: "com.google.android.apps.scone"
+ thread_id: 3625
+ process_id: 3505
+ }
+ task_info {
+ estimated_mws: 0.005476
+ estimated_mw: 0.002695
+ thread_name: "binder:237_2"
+ process_name: "/system/bin/vold"
+ thread_id: 237
+ process_id: 237
+ }
+ task_info {
+ estimated_mws: 0.004537
+ estimated_mw: 0.002233
+ thread_name: "shell svc 11472"
+ process_name: "/apex/com.android.adbd/bin/adbd"
+ thread_id: 11473
+ process_id: 5154
+ }
+ task_info {
+ estimated_mws: 0.003924
+ estimated_mw: 0.001931
+ thread_name: "ksoftirqd/1"
+ process_name: "ksoftirqd/1"
+ thread_id: 29
+ process_id: 29
+ }
+ task_info {
+ estimated_mws: 0.002908
+ estimated_mw: 0.001431
+ thread_name: "BG"
+ process_name: "com.google.android.wearable.sysui"
+ thread_id: 5230
+ process_id: 2710
+ }
+ task_info {
+ estimated_mws: 0.002492
+ estimated_mw: 0.001226
+ idle_transitions_mws: 0.028896
+ thread_name: "kworker/3:2"
+ process_name: "kworker/3:2"
+ thread_id: 9832
+ process_id: 9832
+ }
+ task_info {
+ estimated_mws: 0.002333
+ estimated_mw: 0.001148
+ idle_transitions_mws: 0.000780
+ thread_name: "Scheduled BG"
+ process_name: "com.google.android.wearable.sysui"
+ thread_id: 3577
+ process_id: 2710
+ }
+ task_info {
+ estimated_mws: 0.002293
+ estimated_mw: 0.001128
+ idle_transitions_mws: 0.000781
+ thread_name: "InputReader"
+ process_name: "system_server"
+ thread_id: 2560
+ process_id: 1629
+ }
+ task_info {
+ estimated_mws: 0.002261
+ estimated_mw: 0.001113
+ thread_name: "DefaultDispatch"
+ process_name: "com.google.android.wearable.media.sessions"
+ thread_id: 3618
+ process_id: 3553
+ }
+ task_info {
+ estimated_mws: 0.002226
+ estimated_mw: 0.001095
+ thread_name: "migration/3"
+ process_name: "migration/3"
+ thread_id: 44
+ process_id: 44
+ }
+ task_info {
+ estimated_mws: 0.002100
+ estimated_mw: 0.001034
+ idle_transitions_mws: 0.000657
+ thread_name: "InputDispatcher"
+ process_name: "system_server"
+ thread_id: 2559
+ process_id: 1629
+ }
+ task_info {
+ estimated_mws: 0.001973
+ estimated_mw: 0.000971
+ idle_transitions_mws: 0.000764
+ thread_name: "kworker/1:0"
+ process_name: "kworker/1:0"
+ thread_id: 10984
+ process_id: 10984
+ }
+ task_info {
+ estimated_mws: 0.001966
+ estimated_mw: 0.000967
+ thread_name: "irq/193-wdog-ba"
+ process_name: "irq/193-wdog-ba"
+ thread_id: 344
+ process_id: 344
+ }
+ task_info {
+ estimated_mws: 0.001867
+ estimated_mw: 0.000919
+ thread_name: "DefaultDispatch"
+ process_name: "com.google.android.wearable.media.sessions"
+ thread_id: 3615
+ process_id: 3553
+ }
+ task_info {
+ estimated_mws: 0.001867
+ estimated_mw: 0.000919
+ idle_transitions_mws: 0.000705
+ thread_name: "irq/25-mmc0"
+ process_name: "irq/25-mmc0"
+ thread_id: 115
+ process_id: 115
+ }
+ task_info {
+ estimated_mws: 0.001728
+ estimated_mw: 0.000851
+ thread_name: "iou-wrk-214"
+ process_name: "/system/bin/lmkd"
+ thread_id: 11440
+ process_id: 214
+ }
+ task_info {
+ estimated_mws: 0.001600
+ estimated_mw: 0.000787
+ idle_transitions_mws: 0.001911
+ thread_name: "DefaultDispatch"
+ process_name: "com.google.android.wearable.media.sessions"
+ thread_id: 3616
+ process_id: 3553
+ }
+ task_info {
+ estimated_mws: 0.001393
+ estimated_mw: 0.000686
+ thread_name: "kworker/u8:1"
+ process_name: "kworker/u8:1"
+ thread_id: 11185
+ process_id: 11185
+ }
+ task_info {
+ estimated_mws: 0.001373
+ estimated_mw: 0.000676
+ idle_transitions_mws: 0.007386
+ thread_name: "Scheduled BG"
+ process_name: "com.google.android.wearable.sysui"
+ thread_id: 3576
+ process_id: 2710
+ }
+ task_info {
+ estimated_mws: 0.000811
+ estimated_mw: 0.000399
+ thread_name: "Scheduled BG"
+ process_name: "com.google.android.wearable.sysui"
+ thread_id: 3622
+ process_id: 2710
+ }
}
}
diff --git a/test/trace_processor/diff_tests/metrics/android/wattson_trace_threads.out b/test/trace_processor/diff_tests/metrics/android/wattson_trace_threads.out
index 76b3c98..bcc7daf 100644
--- a/test/trace_processor/diff_tests/metrics/android/wattson_trace_threads.out
+++ b/test/trace_processor/diff_tests/metrics/android/wattson_trace_threads.out
@@ -1,3838 +1,4050 @@
wattson_trace_threads {
- metric_version: 2
- task_info {
- estimated_mws: 34.415016
- estimated_mw: 3.979049
- thread_name: "swapper"
- thread_id: 0
- process_id: 0
- }
- task_info {
- estimated_mws: 19.853703
- estimated_mw: 2.295476
- thread_name: "RenderThread"
- process_name: "com.google.android.wearable.sysui"
- thread_id: 1986
- process_id: 1926
- }
- task_info {
- estimated_mws: 17.530441
- estimated_mw: 2.026862
- thread_name: "Jit thread pool"
- process_name: "system_server"
- thread_id: 1344
- process_id: 1302
- }
- task_info {
- estimated_mws: 16.980274
- estimated_mw: 1.963252
- thread_name: "surfaceflinger"
- process_name: "/system/bin/surfaceflinger"
- thread_id: 755
- process_id: 755
- }
- task_info {
- estimated_mws: 14.908094
- estimated_mw: 1.723667
- thread_name: ".wearable.sysui"
- process_name: "com.google.android.wearable.sysui"
- thread_id: 1926
- process_id: 1926
- }
- task_info {
- estimated_mws: 13.373355
- estimated_mw: 1.546221
- thread_name: "binder:685_3"
- process_name: "/vendor/bin/hw/vendor.qti.hardware.display.composer-service"
- thread_id: 804
- process_id: 685
- }
- task_info {
- estimated_mws: 6.747261
- estimated_mw: 0.780115
- thread_name: "binder:1302_7"
- process_name: "system_server"
- thread_id: 1671
- process_id: 1302
- }
- task_info {
- estimated_mws: 6.504173
- estimated_mw: 0.752010
- thread_name: "binder:1302_A"
- process_name: "system_server"
- thread_id: 2015
- process_id: 1302
- }
- task_info {
- estimated_mws: 4.858775
- estimated_mw: 0.561769
- thread_name: "android.anim"
- process_name: "system_server"
- thread_id: 1419
- process_id: 1302
- }
- task_info {
- estimated_mws: 4.769800
- estimated_mw: 0.551482
- thread_name: "RenderEngine"
- process_name: "/system/bin/surfaceflinger"
- thread_id: 788
- process_id: 755
- }
- task_info {
- estimated_mws: 4.672233
- estimated_mw: 0.540201
- thread_name: "kswapd0"
- process_name: "kswapd0"
- thread_id: 63
- process_id: 63
- }
- task_info {
- estimated_mws: 4.314495
- estimated_mw: 0.498840
- thread_name: "lowpool[2]"
- process_name: "com.google.android.gms"
- thread_id: 3525
- process_id: 2856
- }
- task_info {
- estimated_mws: 4.117818
- estimated_mw: 0.476100
- thread_name: "logd.writer"
- process_name: "/system/bin/logd"
- thread_id: 221
- process_id: 211
- }
- task_info {
- estimated_mws: 4.108276
- estimated_mw: 0.474997
- thread_name: "binder:1302_17"
- process_name: "system_server"
- thread_id: 5202
- process_id: 1302
- }
- task_info {
- estimated_mws: 3.723955
- estimated_mw: 0.430562
- thread_name: "binder:1302_6"
- process_name: "system_server"
- thread_id: 1662
- process_id: 1302
- }
- task_info {
- estimated_mws: 3.666289
- estimated_mw: 0.423895
- thread_name: "e.watchface.rwf"
- process_name: "com.google.android.wearable.watchface.rwf"
- thread_id: 1999
- process_id: 1999
- }
- task_info {
- estimated_mws: 3.524869
- estimated_mw: 0.407544
- thread_name: "killall"
- process_name: "/system/bin/sh"
- thread_id: 5620
- process_id: 5620
- }
- task_info {
- estimated_mws: 3.495762
- estimated_mw: 0.404178
- thread_name: "CachedAppOptimi"
- process_name: "system_server"
- thread_id: 1773
- process_id: 1302
- }
- task_info {
- estimated_mws: 3.459922
- estimated_mw: 0.400035
- thread_name: "logcat"
- process_name: "logcat"
- thread_id: 1230
- process_id: 1230
- }
- task_info {
- estimated_mws: 3.429554
- estimated_mw: 0.396524
- thread_name: "system_server"
- process_name: "system_server"
- thread_id: 1302
- process_id: 1302
- }
- task_info {
- estimated_mws: 3.300661
- estimated_mw: 0.381621
- thread_name: "crtc_commit:80"
- process_name: "crtc_commit:80"
- thread_id: 244
- process_id: 244
- }
- task_info {
- estimated_mws: 3.194881
- estimated_mw: 0.369391
- thread_name: "InputDispatcher"
- process_name: "system_server"
- thread_id: 1783
- process_id: 1302
- }
- task_info {
- estimated_mws: 3.011913
- estimated_mw: 0.348236
- thread_name: "binder:755_1"
- process_name: "/system/bin/surfaceflinger"
- thread_id: 782
- process_id: 755
- }
- task_info {
- estimated_mws: 3.006022
- estimated_mw: 0.347555
- thread_name: "android.display"
- process_name: "system_server"
- thread_id: 1418
- process_id: 1302
- }
- task_info {
- estimated_mws: 2.856301
- estimated_mw: 0.330244
- thread_name: "binder:524_2"
- process_name: "/vendor/bin/mcu_mgmtd"
- thread_id: 524
- process_id: 524
- }
- task_info {
- estimated_mws: 2.712443
- estimated_mw: 0.313611
- thread_name: "traced_probes"
- process_name: "/system/bin/traced_probes"
- thread_id: 904
- process_id: 904
- }
- task_info {
- estimated_mws: 2.550916
- estimated_mw: 0.294936
- thread_name: "kworker/u8:0"
- process_name: "kworker/u8:0"
- thread_id: 8
- process_id: 8
- }
- task_info {
- estimated_mws: 2.487099
- estimated_mw: 0.287557
- thread_name: "surfaceflinger"
- process_name: "/system/bin/surfaceflinger"
- thread_id: 883
- process_id: 755
- }
- task_info {
- estimated_mws: 2.386123
- estimated_mw: 0.275883
- thread_name: "binder:1302_15"
- process_name: "system_server"
- thread_id: 3754
- process_id: 1302
- }
- task_info {
- estimated_mws: 2.258779
- estimated_mw: 0.261159
- thread_name: "logd.reader.per"
- process_name: "/system/bin/logd"
- thread_id: 1274
- process_id: 211
- }
- task_info {
- estimated_mws: 2.171289
- estimated_mw: 0.251044
- thread_name: "RenderThread"
- process_name: "com.google.android.wearable.watchface.rwf"
- thread_id: 2301
- process_id: 1999
- }
- task_info {
- estimated_mws: 2.143151
- estimated_mw: 0.247790
- thread_name: "InputReader"
- process_name: "system_server"
- thread_id: 1784
- process_id: 1302
- }
- task_info {
- estimated_mws: 2.091430
- estimated_mw: 0.241810
- thread_name: "rcu_preempt"
- process_name: "rcu_preempt"
- thread_id: 14
- process_id: 14
- }
- task_info {
- estimated_mws: 2.048920
- estimated_mw: 0.236895
- thread_name: "binder:1926_4"
- process_name: "com.google.android.wearable.sysui"
- thread_id: 2262
- process_id: 1926
- }
- task_info {
- estimated_mws: 1.914560
- estimated_mw: 0.221361
- thread_name: "arable.systemui"
- process_name: "com.google.android.apps.wearable.systemui"
- thread_id: 2171
- process_id: 2171
- }
- task_info {
- estimated_mws: 1.854433
- estimated_mw: 0.214409
- thread_name: "android.ui"
- process_name: "system_server"
- thread_id: 1416
- process_id: 1302
- }
- task_info {
- estimated_mws: 1.777087
- estimated_mw: 0.205466
- thread_name: "kworker/u8:4"
- process_name: "kworker/u8:4"
- thread_id: 431
- process_id: 431
- }
- task_info {
- estimated_mws: 1.773777
- estimated_mw: 0.205083
- thread_name: "TimerDispatch"
- process_name: "/system/bin/surfaceflinger"
- thread_id: 865
- process_id: 755
- }
- task_info {
- estimated_mws: 1.760400
- estimated_mw: 0.203537
- thread_name: "ActivityManager"
- process_name: "system_server"
- thread_id: 1431
- process_id: 1302
- }
- task_info {
- estimated_mws: 1.733169
- estimated_mw: 0.200388
- thread_name: "PowerManagerSer"
- process_name: "system_server"
- thread_id: 1506
- process_id: 1302
- }
- task_info {
- estimated_mws: 1.639501
- estimated_mw: 0.189558
- thread_name: "WifiHandlerThre"
- process_name: "system_server"
- thread_id: 1818
- process_id: 1302
- }
- task_info {
- estimated_mws: 1.631037
- estimated_mw: 0.188580
- thread_name: "binder:755_5"
- process_name: "/system/bin/surfaceflinger"
- thread_id: 1987
- process_id: 755
- }
- task_info {
- estimated_mws: 1.605931
- estimated_mw: 0.185677
- thread_name: "kgsl_dispatcher"
- process_name: "kgsl_dispatcher"
- thread_id: 111
- process_id: 111
- }
- task_info {
- estimated_mws: 1.564964
- estimated_mw: 0.180940
- thread_name: "binder:1302_8"
- process_name: "system_server"
- thread_id: 1679
- process_id: 1302
- }
- task_info {
- estimated_mws: 1.476619
- estimated_mw: 0.170726
- thread_name: "lowpool[5]"
- process_name: "com.google.android.gms.persistent"
- thread_id: 3489
- process_id: 1949
- }
- task_info {
- estimated_mws: 1.470155
- estimated_mw: 0.169979
- thread_name: "-Executor] idle"
- process_name: "com.google.android.gms"
- thread_id: 5591
- process_id: 2856
- }
- task_info {
- estimated_mws: 1.469958
- estimated_mw: 0.169956
- thread_name: "binder:1302_B"
- process_name: "system_server"
- thread_id: 2033
- process_id: 1302
- }
- task_info {
- estimated_mws: 1.390635
- estimated_mw: 0.160785
- thread_name: "binder:755_4"
- process_name: "/system/bin/surfaceflinger"
- thread_id: 1125
- process_id: 755
- }
- task_info {
- estimated_mws: 1.327049
- estimated_mw: 0.153433
- thread_name: "batterystats-ha"
- process_name: "system_server"
- thread_id: 1484
- process_id: 1302
- }
- task_info {
- estimated_mws: 1.312721
- estimated_mw: 0.151776
- thread_name: "statsd.writer"
- process_name: "/apex/com.android.os.statsd/bin/statsd"
- thread_id: 980
- process_id: 545
- }
- task_info {
- estimated_mws: 1.252738
- estimated_mw: 0.144841
- thread_name: "kworker/u8:2"
- process_name: "kworker/u8:2"
- thread_id: 62
- process_id: 62
- }
- task_info {
- estimated_mws: 1.251944
- estimated_mw: 0.144749
- thread_name: "app"
- process_name: "/system/bin/surfaceflinger"
- thread_id: 867
- process_id: 755
- }
- task_info {
- estimated_mws: 1.233674
- estimated_mw: 0.142637
- thread_name: "system_server"
- process_name: "system_server"
- thread_id: 1343
- process_id: 1302
- }
- task_info {
- estimated_mws: 1.228813
- estimated_mw: 0.142075
- thread_name: "irq/33-4520300."
- process_name: "irq/33-4520300.qcom,bwmon-ddr"
- thread_id: 95
- process_id: 95
- }
- task_info {
- estimated_mws: 1.068197
- estimated_mw: 0.123504
- thread_name: "logd.klogd"
- process_name: "/system/bin/logd"
- thread_id: 234
- process_id: 211
- }
- task_info {
- estimated_mws: 1.007070
- estimated_mw: 0.116437
- thread_name: "android.fg"
- process_name: "system_server"
- thread_id: 1415
- process_id: 1302
- }
- task_info {
- estimated_mws: 0.969980
- estimated_mw: 0.112149
- thread_name: "rcuog/0"
- process_name: "rcuog/0"
- thread_id: 15
- process_id: 15
- }
- task_info {
- estimated_mws: 0.952077
- estimated_mw: 0.110079
- thread_name: "binder:1926_3"
- process_name: "com.google.android.wearable.sysui"
- thread_id: 1940
- process_id: 1926
- }
- task_info {
- estimated_mws: 0.946746
- estimated_mw: 0.109462
- thread_name: "gle.android.gms"
- process_name: "com.google.android.gms"
- thread_id: 2856
- process_id: 2856
- }
- task_info {
- estimated_mws: 0.930774
- estimated_mw: 0.107616
- thread_name: "crtc_event:80"
- process_name: "crtc_event:80"
- thread_id: 245
- process_id: 245
- }
- task_info {
- estimated_mws: 0.907425
- estimated_mw: 0.104916
- thread_name: "binder:755_3"
- process_name: "/system/bin/surfaceflinger"
- thread_id: 1124
- process_id: 755
- }
- task_info {
- estimated_mws: 0.897620
- estimated_mw: 0.103782
- thread_name: "init"
- process_name: "/system/bin/init"
- thread_id: 143
- process_id: 1
- }
- task_info {
- estimated_mws: 0.880853
- estimated_mw: 0.101844
- thread_name: "wmshell.main"
- process_name: "com.google.android.apps.wearable.systemui"
- thread_id: 2260
- process_id: 2171
- }
- task_info {
- estimated_mws: 0.870598
- estimated_mw: 0.100658
- thread_name: "Primes-1"
- process_name: "com.google.android.wearable.sysui"
- thread_id: 1944
- process_id: 1926
- }
- task_info {
- estimated_mws: 0.847641
- estimated_mw: 0.098004
- thread_name: "init"
- process_name: "/system/bin/init"
- thread_id: 1
- process_id: 1
- }
- task_info {
- estimated_mws: 0.846054
- estimated_mw: 0.097820
- thread_name: "binder:1302_D"
- process_name: "system_server"
- thread_id: 2043
- process_id: 1302
- }
- task_info {
- estimated_mws: 0.844958
- estimated_mw: 0.097694
- thread_name: "surfaceflinger"
- process_name: "/system/bin/surfaceflinger"
- thread_id: 786
- process_id: 755
- }
- task_info {
- estimated_mws: 0.833920
- estimated_mw: 0.096417
- thread_name: "kworker/u8:5"
- process_name: "kworker/u8:5"
- thread_id: 5304
- process_id: 5304
- }
- task_info {
- estimated_mws: 0.780835
- estimated_mw: 0.090280
- thread_name: "kworker/2:4"
- process_name: "kworker/2:4"
- thread_id: 4995
- process_id: 4995
- }
- task_info {
- estimated_mws: 0.747755
- estimated_mw: 0.086455
- thread_name: "binder:2171_4"
- process_name: "com.google.android.apps.wearable.systemui"
- thread_id: 2374
- process_id: 2171
- }
- task_info {
- estimated_mws: 0.746488
- estimated_mw: 0.086309
- thread_name: "binder:1999_5"
- process_name: "com.google.android.wearable.watchface.rwf"
- thread_id: 3678
- process_id: 1999
- }
- task_info {
- estimated_mws: 0.744159
- estimated_mw: 0.086039
- thread_name: "servicemanager"
- process_name: "/system/bin/servicemanager"
- thread_id: 213
- process_id: 213
- }
- task_info {
- estimated_mws: 0.717520
- estimated_mw: 0.082959
- thread_name: "wmshell.anim"
- process_name: "com.google.android.apps.wearable.systemui"
- thread_id: 2269
- process_id: 2171
- }
- task_info {
- estimated_mws: 0.699681
- estimated_mw: 0.080897
- thread_name: "GoogleApiHandle"
- process_name: "com.google.android.gms"
- thread_id: 3208
- process_id: 2856
- }
- task_info {
- estimated_mws: 0.675997
- estimated_mw: 0.078159
- thread_name: "binder:1302_4"
- process_name: "system_server"
- thread_id: 1592
- process_id: 1302
- }
- task_info {
- estimated_mws: 0.647999
- estimated_mw: 0.074921
- thread_name: "batterystats-wo"
- process_name: "system_server"
- thread_id: 1487
- process_id: 1302
- }
- task_info {
- estimated_mws: 0.640576
- estimated_mw: 0.074063
- thread_name: ".gms.persistent"
- process_name: "com.google.android.gms.persistent"
- thread_id: 1949
- process_id: 1949
- }
- task_info {
- estimated_mws: 0.631830
- estimated_mw: 0.073052
- thread_name: "binder:1926_6"
- process_name: "com.google.android.wearable.sysui"
- thread_id: 5211
- process_id: 1926
- }
- task_info {
- estimated_mws: 0.627672
- estimated_mw: 0.072571
- thread_name: "DisplayOffloadB"
- process_name: "system_server"
- thread_id: 1512
- process_id: 1302
- }
- task_info {
- estimated_mws: 0.627487
- estimated_mw: 0.072550
- thread_name: "binder:682_2"
- process_name: "/vendor/bin/hw/vendor.qti.hardware.display.allocator-service"
- thread_id: 682
- process_id: 682
- }
- task_info {
- estimated_mws: 0.624294
- estimated_mw: 0.072181
- thread_name: "rcuog/2"
- process_name: "rcuog/2"
- thread_id: 37
- process_id: 37
- }
- task_info {
- estimated_mws: 0.623909
- estimated_mw: 0.072136
- thread_name: "kworker/0:6"
- process_name: "kworker/0:6"
- thread_id: 586
- process_id: 586
- }
- task_info {
- estimated_mws: 0.597177
- estimated_mw: 0.069045
- thread_name: "diag-router"
- process_name: "/vendor/bin/diag-router"
- thread_id: 634
- process_id: 634
- }
- task_info {
- estimated_mws: 0.582498
- estimated_mw: 0.067348
- thread_name: "HeapTaskDaemon"
- process_name: "com.google.android.gms"
- thread_id: 2882
- process_id: 2856
- }
- task_info {
- estimated_mws: 0.579675
- estimated_mw: 0.067022
- thread_name: "FileWatcherThre"
- process_name: "/vendor/bin/hw/android.hardware.thermal-service.pixel"
- thread_id: 1411
- process_id: 1404
- }
- task_info {
- estimated_mws: 0.568415
- estimated_mw: 0.065720
- thread_name: "TaskSnapshotPer"
- process_name: "system_server"
- thread_id: 1913
- process_id: 1302
- }
- task_info {
- estimated_mws: 0.565566
- estimated_mw: 0.065390
- thread_name: "lmkd"
- process_name: "/system/bin/lmkd"
- thread_id: 212
- process_id: 212
- }
- task_info {
- estimated_mws: 0.554734
- estimated_mw: 0.064138
- thread_name: "binder:1949_8"
- process_name: "com.google.android.gms.persistent"
- thread_id: 3269
- process_id: 1949
- }
- task_info {
- estimated_mws: 0.517529
- estimated_mw: 0.059836
- thread_name: "appSf"
- process_name: "/system/bin/surfaceflinger"
- thread_id: 868
- process_id: 755
- }
- task_info {
- estimated_mws: 0.514221
- estimated_mw: 0.059454
- thread_name: "kworker/1:1"
- process_name: "kworker/1:1"
- thread_id: 47
- process_id: 47
- }
- task_info {
- estimated_mws: 0.507581
- estimated_mw: 0.058686
- thread_name: "android.hardwar"
- process_name: "/vendor/bin/hw/android.hardware.usb-service.qti"
- thread_id: 1861
- process_id: 665
- }
- task_info {
- estimated_mws: 0.504068
- estimated_mw: 0.058280
- thread_name: "Primes-Jank"
- process_name: "com.google.android.wearable.sysui"
- thread_id: 2389
- process_id: 1926
- }
- task_info {
- estimated_mws: 0.493578
- estimated_mw: 0.057067
- thread_name: "binder:2171_3"
- process_name: "com.google.android.apps.wearable.systemui"
- thread_id: 2235
- process_id: 2171
- }
- task_info {
- estimated_mws: 0.490345
- estimated_mw: 0.056694
- thread_name: "traced"
- process_name: "/system/bin/traced"
- thread_id: 905
- process_id: 905
- }
- task_info {
- estimated_mws: 0.468415
- estimated_mw: 0.054158
- thread_name: "eduling.default"
- process_name: "system_server"
- thread_id: 1761
- process_id: 1302
- }
- task_info {
- estimated_mws: 0.462913
- estimated_mw: 0.053522
- thread_name: "binder:545_2"
- process_name: "/apex/com.android.os.statsd/bin/statsd"
- thread_id: 553
- process_id: 545
- }
- task_info {
- estimated_mws: 0.462537
- estimated_mw: 0.053478
- thread_name: "User"
- process_name: "com.google.android.wearable.sysui"
- thread_id: 2234
- process_id: 1926
- }
- task_info {
- estimated_mws: 0.454063
- estimated_mw: 0.052499
- thread_name: "putmethod.latin"
- process_name: "com.google.android.inputmethod.latin"
- thread_id: 4997
- process_id: 4997
- }
- task_info {
- estimated_mws: 0.450612
- estimated_mw: 0.052100
- thread_name: "ueventd"
- process_name: "/system/bin/ueventd"
- thread_id: 145
- process_id: 145
- }
- task_info {
- estimated_mws: 0.448044
- estimated_mw: 0.051803
- thread_name: "wpa_supplicant"
- process_name: "/vendor/bin/hw/wpa_supplicant"
- thread_id: 5214
- process_id: 5214
- }
- task_info {
- estimated_mws: 0.431304
- estimated_mw: 0.049867
- thread_name: "rcuop/0"
- process_name: "rcuop/0"
- thread_id: 16
- process_id: 16
- }
- task_info {
- estimated_mws: 0.416635
- estimated_mw: 0.048171
- thread_name: "Jit thread pool"
- process_name: "com.google.android.wearable.sysui"
- thread_id: 1933
- process_id: 1926
- }
- task_info {
- estimated_mws: 0.404592
- estimated_mw: 0.046779
- thread_name: "pixelstats-vend"
- process_name: "/vendor/bin/pixelstats-vendor"
- thread_id: 267
- process_id: 255
- }
- task_info {
- estimated_mws: 0.396838
- estimated_mw: 0.045882
- thread_name: "irq/236-NVT-ts"
- process_name: "irq/236-NVT-ts"
- thread_id: 505
- process_id: 505
- }
- task_info {
- estimated_mws: 0.393249
- estimated_mw: 0.045467
- thread_name: "nanohub"
- process_name: "nanohub"
- thread_id: 297
- process_id: 297
- }
- task_info {
- estimated_mws: 0.376686
- estimated_mw: 0.043552
- thread_name: "android.bg"
- process_name: "system_server"
- thread_id: 1430
- process_id: 1302
- }
- task_info {
- estimated_mws: 0.375869
- estimated_mw: 0.043458
- thread_name: "chre"
- process_name: "/vendor/bin/chre"
- thread_id: 1041
- process_id: 1041
- }
- task_info {
- estimated_mws: 0.373520
- estimated_mw: 0.043186
- thread_name: "lowpool[1]"
- process_name: "com.google.android.gms.persistent"
- thread_id: 2279
- process_id: 1949
- }
- task_info {
- estimated_mws: 0.366001
- estimated_mw: 0.042317
- thread_name: "TracingMuxer"
- process_name: "/system/bin/surfaceflinger"
- thread_id: 783
- process_id: 755
- }
- task_info {
- estimated_mws: 0.359494
- estimated_mw: 0.041565
- thread_name: "kgsl-events"
- process_name: "kgsl-events"
- thread_id: 109
- process_id: 109
- }
- task_info {
- estimated_mws: 0.359130
- estimated_mw: 0.041522
- thread_name: "IpClient.wlan0"
- process_name: "com.android.networkstack.process"
- thread_id: 5216
- process_id: 2049
- }
- task_info {
- estimated_mws: 0.346517
- estimated_mw: 0.040064
- thread_name: "binder:257_5"
- process_name: "/system/bin/hw/android.system.suspend-service"
- thread_id: 1491
- process_id: 257
- }
- task_info {
- estimated_mws: 0.341200
- estimated_mw: 0.039449
- thread_name: "binder:1901_3"
- process_name: "/vendor/bin/hw/android.hardware.wifi-service-lazy"
- thread_id: 1905
- process_id: 1901
- }
- task_info {
- estimated_mws: 0.335534
- estimated_mw: 0.038794
- thread_name: "binder:740_1"
- process_name: "/system/bin/audioserver"
- thread_id: 821
- process_id: 740
- }
- task_info {
- estimated_mws: 0.331405
- estimated_mw: 0.038317
- thread_name: "BG"
- process_name: "com.google.wear.services"
- thread_id: 2023
- process_id: 1948
- }
- task_info {
- estimated_mws: 0.326344
- estimated_mw: 0.037732
- thread_name: "kworker/0:5H"
- process_name: "kworker/0:5H"
- thread_id: 1337
- process_id: 1337
- }
- task_info {
- estimated_mws: 0.322384
- estimated_mw: 0.037274
- thread_name: "binder:755_2"
- process_name: "/system/bin/surfaceflinger"
- thread_id: 784
- process_id: 755
- }
- task_info {
- estimated_mws: 0.319511
- estimated_mw: 0.036942
- thread_name: "audioserver"
- process_name: "/system/bin/audioserver"
- thread_id: 740
- process_id: 740
- }
- task_info {
- estimated_mws: 0.310996
- estimated_mw: 0.035957
- thread_name: "binder:1949_2"
- process_name: "com.google.android.gms.persistent"
- thread_id: 1978
- process_id: 1949
- }
- task_info {
- estimated_mws: 0.302115
- estimated_mw: 0.034930
- thread_name: "-Executor] idle"
- process_name: "com.google.android.gms.persistent"
- thread_id: 5602
- process_id: 1949
- }
- task_info {
- estimated_mws: 0.301578
- estimated_mw: 0.034868
- thread_name: "pool-11-thread-"
- process_name: "com.google.android.wearable.healthservices"
- thread_id: 3329
- process_id: 3028
- }
- task_info {
- estimated_mws: 0.299169
- estimated_mw: 0.034590
- thread_name: "android.io"
- process_name: "system_server"
- thread_id: 1417
- process_id: 1302
- }
- task_info {
- estimated_mws: 0.296825
- estimated_mw: 0.034319
- thread_name: "binder:1901_3"
- process_name: "/vendor/bin/hw/android.hardware.wifi-service-lazy"
- thread_id: 5205
- process_id: 1901
- }
- task_info {
- estimated_mws: 0.294242
- estimated_mw: 0.034020
- thread_name: "rcuop/1"
- process_name: "rcuop/1"
- thread_id: 30
- process_id: 30
- }
- task_info {
- estimated_mws: 0.286642
- estimated_mw: 0.033141
- thread_name: "binder:1948_6"
- process_name: "com.google.wear.services"
- thread_id: 5315
- process_id: 1948
- }
- task_info {
- estimated_mws: 0.285983
- estimated_mw: 0.033065
- thread_name: "AssistantHandle"
- process_name: "com.google.android.wearable.assistant"
- thread_id: 4081
- process_id: 4038
- }
- task_info {
- estimated_mws: 0.283378
- estimated_mw: 0.032764
- thread_name: "binder:1999_1"
- process_name: "com.google.android.wearable.watchface.rwf"
- thread_id: 2016
- process_id: 1999
- }
- task_info {
- estimated_mws: 0.279959
- estimated_mw: 0.032369
- thread_name: "binder:2182_7"
- process_name: "com.android.phone"
- thread_id: 2694
- process_id: 2182
- }
- task_info {
- estimated_mws: 0.279816
- estimated_mw: 0.032352
- thread_name: "kworker/3:2H"
- process_name: "kworker/3:2H"
- thread_id: 226
- process_id: 226
- }
- task_info {
- estimated_mws: 0.277230
- estimated_mw: 0.032053
- thread_name: "BG"
- process_name: "com.google.android.wearable.sysui"
- thread_id: 3005
- process_id: 1926
- }
- task_info {
- estimated_mws: 0.274735
- estimated_mw: 0.031765
- thread_name: "lowpool[3]"
- process_name: "com.google.android.gms"
- thread_id: 3527
- process_id: 2856
- }
- task_info {
- estimated_mws: 0.267749
- estimated_mw: 0.030957
- thread_name: "hvdcp_opti"
- process_name: "/vendor/bin/hvdcp_opti"
- thread_id: 1276
- process_id: 1270
- }
- task_info {
- estimated_mws: 0.262081
- estimated_mw: 0.030302
- thread_name: "binder:1926_3"
- process_name: "com.google.android.wearable.sysui"
- thread_id: 2022
- process_id: 1926
- }
- task_info {
- estimated_mws: 0.259248
- estimated_mw: 0.029974
- thread_name: "kworker/3:5"
- process_name: "kworker/3:5"
- thread_id: 104
- process_id: 104
- }
- task_info {
- estimated_mws: 0.256714
- estimated_mw: 0.029681
- thread_name: "binder:257_2"
- process_name: "/system/bin/hw/android.system.suspend-service"
- thread_id: 264
- process_id: 257
- }
- task_info {
- estimated_mws: 0.247037
- estimated_mw: 0.028562
- thread_name: "SDM_EventThread"
- process_name: "/vendor/bin/hw/vendor.qti.hardware.display.composer-service"
- thread_id: 727
- process_id: 685
- }
- task_info {
- estimated_mws: 0.244112
- estimated_mw: 0.028224
- thread_name: "POSIX timer 2"
- process_name: "/vendor/bin/hw/android.hardware.sensors-service.multihal"
- thread_id: 1600
- process_id: 664
- }
- task_info {
- estimated_mws: 0.242754
- estimated_mw: 0.028067
- thread_name: "binder:2856_4"
- process_name: "com.google.android.gms"
- thread_id: 3679
- process_id: 2856
- }
- task_info {
- estimated_mws: 0.241348
- estimated_mw: 0.027905
- thread_name: "pool-2-thread-1"
- process_name: "com.android.networkstack.process"
- thread_id: 2416
- process_id: 2049
- }
- task_info {
- estimated_mws: 0.231145
- estimated_mw: 0.026725
- thread_name: "rcuop/3"
- process_name: "rcuop/3"
- thread_id: 45
- process_id: 45
- }
- task_info {
- estimated_mws: 0.230341
- estimated_mw: 0.026632
- thread_name: "f2fs_ckpt-254:4"
- process_name: "f2fs_ckpt-254:43"
- thread_id: 347
- process_id: 347
- }
- task_info {
- estimated_mws: 0.229722
- estimated_mw: 0.026560
- thread_name: "OomAdjuster"
- process_name: "system_server"
- thread_id: 1482
- process_id: 1302
- }
- task_info {
- estimated_mws: 0.226417
- estimated_mw: 0.026178
- thread_name: "binder:740_6"
- process_name: "/system/bin/audioserver"
- thread_id: 2639
- process_id: 740
- }
- task_info {
- estimated_mws: 0.226007
- estimated_mw: 0.026131
- thread_name: "rcuop/2"
- process_name: "rcuop/2"
- thread_id: 38
- process_id: 38
- }
- task_info {
- estimated_mws: 0.225133
- estimated_mw: 0.026030
- thread_name: "kworker/0:7"
- process_name: "kworker/0:7"
- thread_id: 598
- process_id: 598
- }
- task_info {
- estimated_mws: 0.212788
- estimated_mw: 0.024602
- thread_name: "queued-work-loo"
- process_name: "system_server"
- thread_id: 1886
- process_id: 1302
- }
- task_info {
- estimated_mws: 0.202562
- estimated_mw: 0.023420
- thread_name: "pool-13-thread-"
- process_name: "com.google.android.wearable.healthservices"
- thread_id: 3327
- process_id: 3028
- }
- task_info {
- estimated_mws: 0.201687
- estimated_mw: 0.023319
- thread_name: "WearSdkThread"
- process_name: "com.google.android.wearable.sysui"
- thread_id: 2207
- process_id: 1926
- }
- task_info {
- estimated_mws: 0.201119
- estimated_mw: 0.023253
- thread_name: "qrtr_ns"
- process_name: "qrtr_ns"
- thread_id: 88
- process_id: 88
- }
- task_info {
- estimated_mws: 0.200639
- estimated_mw: 0.023198
- thread_name: "binder:740_7"
- process_name: "/system/bin/audioserver"
- thread_id: 5206
- process_id: 740
- }
- task_info {
- estimated_mws: 0.196587
- estimated_mw: 0.022729
- thread_name: "binder:4997_4"
- process_name: "com.google.android.inputmethod.latin"
- thread_id: 5122
- process_id: 4997
- }
- task_info {
- estimated_mws: 0.194754
- estimated_mw: 0.022517
- thread_name: "m.android.phone"
- process_name: "com.android.phone"
- thread_id: 2182
- process_id: 2182
- }
- task_info {
- estimated_mws: 0.192336
- estimated_mw: 0.022238
- thread_name: "HwcAsyncWorker"
- process_name: "/system/bin/surfaceflinger"
- thread_id: 835
- process_id: 755
- }
- task_info {
- estimated_mws: 0.190522
- estimated_mw: 0.022028
- thread_name: "binder:636_2"
- process_name: "/vendor/bin/hw/android.hardware.audio.service"
- thread_id: 636
- process_id: 636
- }
- task_info {
- estimated_mws: 0.188908
- estimated_mw: 0.021841
- thread_name: "SettingsProvide"
- process_name: "system_server"
- thread_id: 1771
- process_id: 1302
- }
- task_info {
- estimated_mws: 0.181172
- estimated_mw: 0.020947
- thread_name: "binder:1302_2"
- process_name: "system_server"
- thread_id: 1350
- process_id: 1302
- }
- task_info {
- estimated_mws: 0.177579
- estimated_mw: 0.020532
- thread_name: "RegSampIdle"
- process_name: "/system/bin/surfaceflinger"
- thread_id: 872
- process_id: 755
- }
- task_info {
- estimated_mws: 0.168738
- estimated_mw: 0.019509
- thread_name: "ice] processing"
- process_name: "com.google.android.gms"
- thread_id: 3238
- process_id: 2856
- }
- task_info {
- estimated_mws: 0.162808
- estimated_mw: 0.018824
- thread_name: "binder:682_3"
- process_name: "/vendor/bin/hw/vendor.qti.hardware.display.allocator-service"
- thread_id: 2308
- process_id: 682
- }
- task_info {
- estimated_mws: 0.158857
- estimated_mw: 0.018367
- thread_name: "binder:678_3"
- process_name: "/apex/com.google.wearable.wac.whshal/bin/hw/vendor.google.wearable.wac.whshal@2.0-service"
- thread_id: 1884
- process_id: 678
- }
- task_info {
- estimated_mws: 0.158692
- estimated_mw: 0.018348
- thread_name: "binder:650_4"
- process_name: "/vendor/bin/hw/android.hardware.gnss-aidl-service-qti"
- thread_id: 5498
- process_id: 650
- }
- task_info {
- estimated_mws: 0.157956
- estimated_mw: 0.018263
- thread_name: "vndservicemanag"
- process_name: "/vendor/bin/vndservicemanager"
- thread_id: 215
- process_id: 215
- }
- task_info {
- estimated_mws: 0.153600
- estimated_mw: 0.017759
- thread_name: "GoogleLocationS"
- process_name: "com.google.android.gms.persistent"
- thread_id: 3355
- process_id: 1949
- }
- task_info {
- estimated_mws: 0.153038
- estimated_mw: 0.017694
- thread_name: "TransportThread"
- process_name: "/vendor/bin/chre"
- thread_id: 1078
- process_id: 1041
- }
- task_info {
- estimated_mws: 0.152058
- estimated_mw: 0.017581
- thread_name: "kworker/2:1H"
- process_name: "kworker/2:1H"
- thread_id: 123
- process_id: 123
- }
- task_info {
- estimated_mws: 0.148559
- estimated_mw: 0.017176
- thread_name: "BG"
- process_name: "com.google.android.wearable.sysui"
- thread_id: 2120
- process_id: 1926
- }
- task_info {
- estimated_mws: 0.148352
- estimated_mw: 0.017152
- thread_name: "vendor.google.w"
- process_name: "/apex/com.google.wearable.wac.whshal/bin/hw/vendor.google.wearable.wac.whshal@2.0-service"
- thread_id: 1881
- process_id: 678
- }
- task_info {
- estimated_mws: 0.140864
- estimated_mw: 0.016287
- thread_name: "NetworkStats"
- process_name: "system_server"
- thread_id: 1814
- process_id: 1302
- }
- task_info {
- estimated_mws: 0.138579
- estimated_mw: 0.016022
- thread_name: "binder:969_2"
- process_name: "/system/vendor/bin/cnd"
- thread_id: 1011
- process_id: 969
- }
- task_info {
- estimated_mws: 0.134607
- estimated_mw: 0.015563
- thread_name: "dmabuf-deferred"
- process_name: "dmabuf-deferred-free-worker"
- thread_id: 69
- process_id: 69
- }
- task_info {
- estimated_mws: 0.129449
- estimated_mw: 0.014967
- thread_name: "highpool[5]"
- process_name: "com.google.android.gms.persistent"
- thread_id: 3354
- process_id: 1949
- }
- task_info {
- estimated_mws: 0.126973
- estimated_mw: 0.014681
- thread_name: "ice] processing"
- process_name: "com.google.android.gms.persistent"
- thread_id: 2363
- process_id: 1949
- }
- task_info {
- estimated_mws: 0.126830
- estimated_mw: 0.014664
- thread_name: "ediator.Toggler"
- process_name: "system_server"
- thread_id: 1910
- process_id: 1302
- }
- task_info {
- estimated_mws: 0.125742
- estimated_mw: 0.014538
- thread_name: "surfaceflinger"
- process_name: "/system/bin/surfaceflinger"
- thread_id: 875
- process_id: 755
- }
- task_info {
- estimated_mws: 0.123833
- estimated_mw: 0.014317
- thread_name: "wificond"
- process_name: "/system/bin/wificond"
- thread_id: 964
- process_id: 964
- }
- task_info {
- estimated_mws: 0.123248
- estimated_mw: 0.014250
- thread_name: "MobileDataStats"
- process_name: "system_server"
- thread_id: 1912
- process_id: 1302
- }
- task_info {
- estimated_mws: 0.119885
- estimated_mw: 0.013861
- thread_name: "GlobalScheduler"
- process_name: "com.google.android.gms"
- thread_id: 3156
- process_id: 2856
- }
- task_info {
- estimated_mws: 0.119479
- estimated_mw: 0.013814
- thread_name: "RenderThread"
- thread_id: 5599
- }
- task_info {
- estimated_mws: 0.119243
- estimated_mw: 0.013787
- thread_name: "TouchTimer"
- process_name: "/system/bin/surfaceflinger"
- thread_id: 866
- process_id: 755
- }
- task_info {
- estimated_mws: 0.114249
- estimated_mw: 0.013209
- thread_name: "binder:4038_1"
- process_name: "com.google.android.wearable.assistant"
- thread_id: 4050
- process_id: 4038
- }
- task_info {
- estimated_mws: 0.112705
- estimated_mw: 0.013031
- thread_name: "displayoffload@"
- process_name: "/vendor/bin/hw/vendor.google_clockwork.displayoffload@2.0-service.1p"
- thread_id: 937
- process_id: 937
- }
- task_info {
- estimated_mws: 0.111279
- estimated_mw: 0.012866
- thread_name: "adbd"
- process_name: "/apex/com.android.adbd/bin/adbd"
- thread_id: 5544
- process_id: 5544
- }
- task_info {
- estimated_mws: 0.108114
- estimated_mw: 0.012500
- thread_name: "kworker/1:2H"
- process_name: "kworker/1:2H"
- thread_id: 300
- process_id: 300
- }
- task_info {
- estimated_mws: 0.107442
- estimated_mw: 0.012422
- thread_name: "RenderThread"
- thread_id: 5584
- }
- task_info {
- estimated_mws: 0.105863
- estimated_mw: 0.012240
- thread_name: "Primes-2"
- process_name: "com.google.android.wearable.sysui"
- thread_id: 1946
- process_id: 1926
- }
- task_info {
- estimated_mws: 0.104306
- estimated_mw: 0.012060
- thread_name: "iptables-restor"
- process_name: "/system/bin/iptables-restore"
- thread_id: 558
- process_id: 558
- }
- task_info {
- estimated_mws: 0.104093
- estimated_mw: 0.012035
- thread_name: "RenderThread"
- process_name: "system_server"
- thread_id: 5223
- process_id: 1302
- }
- task_info {
- estimated_mws: 0.102912
- estimated_mw: 0.011899
- thread_name: "irq/168-nanohub"
- process_name: "irq/168-nanohub-irq1"
- thread_id: 296
- process_id: 296
- }
- task_info {
- estimated_mws: 0.102167
- estimated_mw: 0.011812
- thread_name: "RenderThread"
- thread_id: 5604
- }
- task_info {
- estimated_mws: 0.101945
- estimated_mw: 0.011787
- thread_name: "ksoftirqd/2"
- process_name: "ksoftirqd/2"
- thread_id: 34
- process_id: 34
- }
- task_info {
- estimated_mws: 0.101282
- estimated_mw: 0.011710
- thread_name: "PhotonicModulat"
- process_name: "system_server"
- thread_id: 1899
- process_id: 1302
- }
- task_info {
- estimated_mws: 0.100285
- estimated_mw: 0.011595
- thread_name: "ip6tables-resto"
- process_name: "/system/bin/ip6tables-restore"
- thread_id: 559
- process_id: 559
- }
- task_info {
- estimated_mws: 0.099432
- estimated_mw: 0.011496
- thread_name: "init"
- process_name: "/system/bin/init"
- thread_id: 144
- process_id: 144
- }
- task_info {
- estimated_mws: 0.096314
- estimated_mw: 0.011136
- thread_name: "FrameworkReceiv"
- process_name: ".qtidataservices"
- thread_id: 2793
- process_id: 2118
- }
- task_info {
- estimated_mws: 0.095442
- estimated_mw: 0.011035
- thread_name: "Jit thread pool"
- process_name: "com.google.android.gms.persistent"
- thread_id: 1969
- process_id: 1949
- }
- task_info {
- estimated_mws: 0.094448
- estimated_mw: 0.010920
- thread_name: "pool-14-thread-"
- process_name: "com.google.android.wearable.healthservices"
- thread_id: 3314
- process_id: 3028
- }
- task_info {
- estimated_mws: 0.093937
- estimated_mw: 0.010861
- thread_name: "binder:1926_2"
- process_name: "com.google.android.wearable.sysui"
- thread_id: 1939
- process_id: 1926
- }
- task_info {
- estimated_mws: 0.093621
- estimated_mw: 0.010824
- thread_name: "ChreMsgHandler"
- process_name: "/vendor/bin/chre"
- thread_id: 1080
- process_id: 1041
- }
- task_info {
- estimated_mws: 0.091738
- estimated_mw: 0.010607
- thread_name: "DispatcherModul"
- process_name: "/vendor/bin/hw/qcrilNrd"
- thread_id: 1673
- process_id: 1062
- }
- task_info {
- estimated_mws: 0.091698
- estimated_mw: 0.010602
- thread_name: "irq/234-pixart_"
- process_name: "irq/234-pixart_pat9126_irq"
- thread_id: 500
- process_id: 500
- }
- task_info {
- estimated_mws: 0.090883
- estimated_mw: 0.010508
- thread_name: "scheduler_threa"
- process_name: "scheduler_thread"
- thread_id: 5198
- process_id: 5198
- }
- task_info {
- estimated_mws: 0.089001
- estimated_mw: 0.010290
- thread_name: "binder:2085_4"
- process_name: "com.google.android.bluetooth"
- thread_id: 2713
- process_id: 2085
- }
- task_info {
- estimated_mws: 0.086934
- estimated_mw: 0.010051
- thread_name: "binder:3028_5"
- process_name: "com.google.android.wearable.healthservices"
- thread_id: 5434
- process_id: 3028
- }
- task_info {
- estimated_mws: 0.085941
- estimated_mw: 0.009936
- thread_name: "binder:2670_6"
- process_name: "com.android.nfc"
- thread_id: 3159
- process_id: 2670
- }
- task_info {
- estimated_mws: 0.083859
- estimated_mw: 0.009696
- thread_name: "psimon"
- process_name: "psimon"
- thread_id: 1480
- process_id: 1480
- }
- task_info {
- estimated_mws: 0.083773
- estimated_mw: 0.009686
- thread_name: "binder:233_2"
- process_name: "/system/bin/vold"
- thread_id: 252
- process_id: 233
- }
- task_info {
- estimated_mws: 0.080959
- estimated_mw: 0.009360
- thread_name: "binder:2049_2"
- process_name: "com.android.networkstack.process"
- thread_id: 2068
- process_id: 2049
- }
- task_info {
- estimated_mws: 0.080298
- estimated_mw: 0.009284
- thread_name: "netd"
- process_name: "/system/bin/netd"
- thread_id: 568
- process_id: 546
- }
- task_info {
- estimated_mws: 0.080265
- estimated_mw: 0.009280
- thread_name: "UEventObserver"
- process_name: "system_server"
- thread_id: 1857
- process_id: 1302
- }
- task_info {
- estimated_mws: 0.079337
- estimated_mw: 0.009173
- thread_name: "RenderThread"
- thread_id: 5619
- }
- task_info {
- estimated_mws: 0.078696
- estimated_mw: 0.009099
- thread_name: "pool-8-thread-1"
- process_name: "com.google.android.gms"
- thread_id: 3102
- process_id: 2856
- }
- task_info {
- estimated_mws: 0.077362
- estimated_mw: 0.008945
- thread_name: "mcu_mgmtd"
- process_name: "/vendor/bin/mcu_mgmtd"
- thread_id: 594
- process_id: 524
- }
- task_info {
- estimated_mws: 0.074501
- estimated_mw: 0.008614
- thread_name: "spi0"
- process_name: "spi0"
- thread_id: 295
- process_id: 295
- }
- task_info {
- estimated_mws: 0.073914
- estimated_mw: 0.008546
- thread_name: "com.android.nfc"
- process_name: "com.android.nfc"
- thread_id: 2670
- process_id: 2670
- }
- task_info {
- estimated_mws: 0.072671
- estimated_mw: 0.008402
- thread_name: "rcu_exp_gp_kthr"
- process_name: "rcu_exp_gp_kthread_worker"
- thread_id: 19
- process_id: 19
- }
- task_info {
- estimated_mws: 0.070587
- estimated_mw: 0.008161
- thread_name: "adbd"
- process_name: "/apex/com.android.adbd/bin/adbd"
- thread_id: 5546
- process_id: 5544
- }
- task_info {
- estimated_mws: 0.070105
- estimated_mw: 0.008105
- thread_name: "servicemanager"
- thread_id: 5598
- }
- task_info {
- estimated_mws: 0.069214
- estimated_mw: 0.008002
- thread_name: "android.imms"
- process_name: "system_server"
- thread_id: 1791
- process_id: 1302
- }
- task_info {
- estimated_mws: 0.068600
- estimated_mw: 0.007931
- thread_name: "lowpool[2]"
- process_name: "com.google.android.gms.persistent"
- thread_id: 2321
- process_id: 1949
- }
- task_info {
- estimated_mws: 0.068467
- estimated_mw: 0.007916
- thread_name: "FileObserver"
- process_name: "com.google.android.gms"
- thread_id: 3035
- process_id: 2856
- }
- task_info {
- estimated_mws: 0.068316
- estimated_mw: 0.007899
- thread_name: "binder:546_3"
- process_name: "/system/bin/netd"
- thread_id: 546
- process_id: 546
- }
- task_info {
- estimated_mws: 0.066410
- estimated_mw: 0.007678
- thread_name: "pool-51-thread-"
- process_name: "com.google.android.gms"
- thread_id: 4215
- process_id: 2856
- }
- task_info {
- estimated_mws: 0.064761
- estimated_mw: 0.007488
- thread_name: "AudioService"
- process_name: "system_server"
- thread_id: 1844
- process_id: 1302
- }
- task_info {
- estimated_mws: 0.064339
- estimated_mw: 0.007439
- thread_name: "adbd"
- process_name: "/apex/com.android.adbd/bin/adbd"
- thread_id: 5545
- process_id: 5544
- }
- task_info {
- estimated_mws: 0.063188
- estimated_mw: 0.007306
- thread_name: "droid.bluetooth"
- process_name: "com.google.android.bluetooth"
- thread_id: 2085
- process_id: 2085
- }
- task_info {
- estimated_mws: 0.061069
- estimated_mw: 0.007061
- thread_name: "msm_irqbalance"
- process_name: "/vendor/bin/msm_irqbalance"
- thread_id: 2466
- process_id: 2466
- }
- task_info {
- estimated_mws: 0.060920
- estimated_mw: 0.007044
- thread_name: "BackgroundInsta"
- process_name: "system_server"
- thread_id: 1875
- process_id: 1302
- }
- task_info {
- estimated_mws: 0.059901
- estimated_mw: 0.006926
- thread_name: "ConnectivitySer"
- process_name: "system_server"
- thread_id: 1827
- process_id: 1302
- }
- task_info {
- estimated_mws: 0.059295
- estimated_mw: 0.006856
- thread_name: "pool-1-thread-1"
- process_name: "system_server"
- thread_id: 1873
- process_id: 1302
- }
- task_info {
- estimated_mws: 0.059196
- estimated_mw: 0.006844
- thread_name: "binder:3028_1"
- process_name: "com.google.android.wearable.healthservices"
- thread_id: 3049
- process_id: 3028
- }
- task_info {
- estimated_mws: 0.058807
- estimated_mw: 0.006799
- thread_name: "roid.apps.scone"
- process_name: "com.google.android.apps.scone"
- thread_id: 5245
- process_id: 5245
- }
- task_info {
- estimated_mws: 0.057400
- estimated_mw: 0.006637
- thread_name: "android.hardwar"
- process_name: "/vendor/bin/hw/android.hardware.health-service.eos"
- thread_id: 1271
- process_id: 1271
- }
- task_info {
- estimated_mws: 0.056947
- estimated_mw: 0.006584
- thread_name: "bgres-controlle"
- process_name: "system_server"
- thread_id: 1495
- process_id: 1302
- }
- task_info {
- estimated_mws: 0.056879
- estimated_mw: 0.006576
- thread_name: "netd"
- process_name: "/system/bin/netd"
- thread_id: 569
- process_id: 546
- }
- task_info {
- estimated_mws: 0.055771
- estimated_mw: 0.006448
- thread_name: "UsbFfs-worker"
- process_name: "/apex/com.android.adbd/bin/adbd"
- thread_id: 5560
- process_id: 5544
- }
- task_info {
- estimated_mws: 0.055642
- estimated_mw: 0.006433
- thread_name: "system_server"
- thread_id: 5590
- }
- task_info {
- estimated_mws: 0.054764
- estimated_mw: 0.006332
- thread_name: "binder:2049_4"
- process_name: "com.android.networkstack.process"
- thread_id: 2083
- process_id: 2049
- }
- task_info {
- estimated_mws: 0.053127
- estimated_mw: 0.006142
- thread_name: "binder:1949_4"
- process_name: "com.google.android.gms.persistent"
- thread_id: 2302
- process_id: 1949
- }
- task_info {
- estimated_mws: 0.052984
- estimated_mw: 0.006126
- thread_name: "oid.grilservice"
- process_name: "com.google.android.grilservice"
- thread_id: 2129
- process_id: 2129
- }
- task_info {
- estimated_mws: 0.052980
- estimated_mw: 0.006126
- thread_name: "binder:2856_9"
- process_name: "com.google.android.gms"
- thread_id: 5585
- process_id: 2856
- }
- task_info {
- estimated_mws: 0.052715
- estimated_mw: 0.006095
- thread_name: "StateService"
- process_name: "com.google.android.apps.scone"
- thread_id: 5269
- process_id: 5245
- }
- task_info {
- estimated_mws: 0.052232
- estimated_mw: 0.006039
- thread_name: "vndservicemanag"
- thread_id: 5597
- }
- task_info {
- estimated_mws: 0.052027
- estimated_mw: 0.006015
- thread_name: "binder:1949_9"
- process_name: "com.google.android.gms.persistent"
- thread_id: 3359
- process_id: 1949
- }
- task_info {
- estimated_mws: 0.051717
- estimated_mw: 0.005979
- thread_name: "Ipc-5004:1"
- process_name: "/vendor/bin/hw/android.hardware.gnss-aidl-service-qti"
- thread_id: 5483
- process_id: 650
- }
- task_info {
- estimated_mws: 0.049546
- estimated_mw: 0.005729
- thread_name: "PackageManager"
- process_name: "system_server"
- thread_id: 1530
- process_id: 1302
- }
- task_info {
- estimated_mws: 0.048227
- estimated_mw: 0.005576
- thread_name: "android.hardwar"
- process_name: "/vendor/bin/hw/android.hardware.power-service"
- thread_id: 660
- process_id: 660
- }
- task_info {
- estimated_mws: 0.047454
- estimated_mw: 0.005487
- thread_name: "BluetoothScanMa"
- process_name: "com.google.android.bluetooth"
- thread_id: 2609
- process_id: 2085
- }
- task_info {
- estimated_mws: 0.046472
- estimated_mw: 0.005373
- thread_name: "subsystem_ramdu"
- process_name: "/system/vendor/bin/subsystem_ramdump"
- thread_id: 816
- process_id: 799
- }
- task_info {
- estimated_mws: 0.046295
- estimated_mw: 0.005353
- thread_name: "Ipc-5004:2"
- process_name: "/vendor/bin/hw/android.hardware.gnss-aidl-service-qti"
- thread_id: 5484
- process_id: 650
- }
- task_info {
- estimated_mws: 0.045805
- estimated_mw: 0.005296
- thread_name: "binder:975_2"
- process_name: "/vendor/bin/imsdaemon"
- thread_id: 1047
- process_id: 975
- }
- task_info {
- estimated_mws: 0.045282
- estimated_mw: 0.005235
- thread_name: ".healthservices"
- process_name: "com.google.android.wearable.healthservices"
- thread_id: 3028
- process_id: 3028
- }
- task_info {
- estimated_mws: 0.045087
- estimated_mw: 0.005213
- thread_name: "queued-work-loo"
- process_name: "com.google.android.gms"
- thread_id: 3236
- process_id: 2856
- }
- task_info {
- estimated_mws: 0.043921
- estimated_mw: 0.005078
- thread_name: "powerstateservi"
- process_name: "/vendor/bin/hw/vendor.qti.hardware.powerstateservice@1.0-service"
- thread_id: 276
- process_id: 269
- }
- task_info {
- estimated_mws: 0.043653
- estimated_mw: 0.005047
- thread_name: "wlan_logging_th"
- process_name: "wlan_logging_thread"
- thread_id: 368
- process_id: 368
- }
- task_info {
- estimated_mws: 0.043574
- estimated_mw: 0.005038
- thread_name: "FlpThread"
- process_name: "com.google.android.gms.persistent"
- thread_id: 3279
- process_id: 1949
- }
- task_info {
- estimated_mws: 0.043436
- estimated_mw: 0.005022
- thread_name: "Light-P0-2"
- process_name: "com.google.android.inputmethod.latin"
- thread_id: 5071
- process_id: 4997
- }
- task_info {
- estimated_mws: 0.042985
- estimated_mw: 0.004970
- thread_name: "binder:5245_4"
- process_name: "com.google.android.apps.scone"
- thread_id: 5270
- process_id: 5245
- }
- task_info {
- estimated_mws: 0.042946
- estimated_mw: 0.004965
- thread_name: "HWC_UeventThrea"
- process_name: "/vendor/bin/hw/vendor.qti.hardware.display.composer-service"
- thread_id: 717
- process_id: 685
- }
- task_info {
- estimated_mws: 0.042762
- estimated_mw: 0.004944
- thread_name: "pool-12-thread-"
- process_name: "com.google.android.wearable.sysui"
- thread_id: 2371
- process_id: 1926
- }
- task_info {
- estimated_mws: 0.042752
- estimated_mw: 0.004943
- thread_name: "android.hardwar"
- process_name: "/vendor/bin/hw/android.hardware.contexthub-service.wac"
- thread_id: 668
- process_id: 644
- }
- task_info {
- estimated_mws: 0.042468
- estimated_mw: 0.004910
- thread_name: "binder:1948_3"
- process_name: "com.google.wear.services"
- thread_id: 1976
- process_id: 1948
- }
- task_info {
- estimated_mws: 0.042220
- estimated_mw: 0.004881
- thread_name: "netlink socket"
- process_name: "/system/vendor/bin/ipacm"
- thread_id: 538
- process_id: 523
- }
- task_info {
- estimated_mws: 0.040507
- estimated_mw: 0.004683
- thread_name: "RegionSampling"
- process_name: "/system/bin/surfaceflinger"
- thread_id: 871
- process_id: 755
- }
- task_info {
- estimated_mws: 0.040385
- estimated_mw: 0.004669
- thread_name: "TransportThread"
- process_name: "/vendor/bin/hw/android.hardware.sensors-service.multihal"
- thread_id: 794
- process_id: 664
- }
- task_info {
- estimated_mws: 0.040349
- estimated_mw: 0.004665
- thread_name: "binder:2856_1"
- process_name: "com.google.android.gms"
- thread_id: 2898
- process_id: 2856
- }
- task_info {
- estimated_mws: 0.040180
- estimated_mw: 0.004646
- thread_name: "servicemanager"
- thread_id: 5595
- }
- task_info {
- estimated_mws: 0.039525
- estimated_mw: 0.004570
- thread_name: "pd-mapper"
- process_name: "/vendor/bin/pd-mapper"
- thread_id: 752
- process_id: 725
- }
- task_info {
- estimated_mws: 0.039196
- estimated_mw: 0.004532
- thread_name: "vndservicemanag"
- thread_id: 5618
- }
- task_info {
- estimated_mws: 0.039101
- estimated_mw: 0.004521
- thread_name: "vndservicemanag"
- thread_id: 5605
- }
- task_info {
- estimated_mws: 0.038960
- estimated_mw: 0.004505
- thread_name: "WifiScanningSer"
- process_name: "system_server"
- thread_id: 1823
- process_id: 1302
- }
- task_info {
- estimated_mws: 0.038580
- estimated_mw: 0.004461
- thread_name: "cnss-daemon"
- process_name: "/system/vendor/bin/cnss-daemon"
- thread_id: 5204
- process_id: 1009
- }
- task_info {
- estimated_mws: 0.038053
- estimated_mw: 0.004400
- thread_name: "shell svc 5620"
- process_name: "/apex/com.android.adbd/bin/adbd"
- thread_id: 5622
- process_id: 5544
- }
- task_info {
- estimated_mws: 0.037116
- estimated_mw: 0.004291
- thread_name: "rmt_storage"
- process_name: "/vendor/bin/rmt_storage"
- thread_id: 758
- process_id: 758
- }
- task_info {
- estimated_mws: 0.036357
- estimated_mw: 0.004204
- thread_name: "halt_drain_rqs"
- process_name: "halt_drain_rqs"
- thread_id: 105
- process_id: 105
- }
- task_info {
- estimated_mws: 0.035907
- estimated_mw: 0.004152
- thread_name: "BG Thread #2"
- process_name: "com.google.android.wearable.assistant"
- thread_id: 4106
- process_id: 4038
- }
- task_info {
- estimated_mws: 0.035876
- estimated_mw: 0.004148
- thread_name: "-Executor] idle"
- process_name: "com.google.android.gms"
- thread_id: 5592
- process_id: 2856
- }
- task_info {
- estimated_mws: 0.035444
- estimated_mw: 0.004098
- thread_name: "tftp_server"
- process_name: "/vendor/bin/tftp_server"
- thread_id: 759
- process_id: 759
- }
- task_info {
- estimated_mws: 0.035386
- estimated_mw: 0.004091
- thread_name: "FinalizerWatchd"
- process_name: "com.google.android.gms"
- thread_id: 2887
- process_id: 2856
- }
- task_info {
- estimated_mws: 0.035171
- estimated_mw: 0.004066
- thread_name: "servicemanager"
- thread_id: 5606
- }
- task_info {
- estimated_mws: 0.035157
- estimated_mw: 0.004065
- thread_name: "Blocking Thread"
- process_name: "com.google.android.wearable.assistant"
- thread_id: 5587
- process_id: 4038
- }
- task_info {
- estimated_mws: 0.035034
- estimated_mw: 0.004051
- thread_name: "vndservicemanag"
- thread_id: 5611
- }
- task_info {
- estimated_mws: 0.034307
- estimated_mw: 0.003967
- thread_name: "vndservicemanag"
- thread_id: 5593
- }
- task_info {
- estimated_mws: 0.034030
- estimated_mw: 0.003935
- thread_name: "servicemanager"
- thread_id: 5621
- }
- task_info {
- estimated_mws: 0.032631
- estimated_mw: 0.003773
- thread_name: "binder:685_3"
- thread_id: 5586
- }
- task_info {
- estimated_mws: 0.031847
- estimated_mw: 0.003682
- thread_name: "radioext@1.0-se"
- process_name: "/vendor/bin/hw/vendor.google.radioext@1.0-service"
- thread_id: 676
- process_id: 676
- }
- task_info {
- estimated_mws: 0.031818
- estimated_mw: 0.003679
- thread_name: "BgBroadcastRegi"
- process_name: "com.google.wear.services"
- thread_id: 2017
- process_id: 1948
- }
- task_info {
- estimated_mws: 0.031204
- estimated_mw: 0.003608
- thread_name: "DefaultExecutor"
- process_name: "com.google.android.wearable.watchface.rwf"
- thread_id: 5600
- process_id: 1999
- }
- task_info {
- estimated_mws: 0.030138
- estimated_mw: 0.003484
- thread_name: "Light-P0-1"
- process_name: "com.google.android.inputmethod.latin"
- thread_id: 5064
- process_id: 4997
- }
- task_info {
- estimated_mws: 0.029778
- estimated_mw: 0.003443
- thread_name: "binder:978_2"
- process_name: "/system/vendor/bin/nicmd"
- thread_id: 1368
- process_id: 978
- }
- task_info {
- estimated_mws: 0.029627
- estimated_mw: 0.003425
- thread_name: "atchdog.monitor"
- process_name: "system_server"
- thread_id: 1414
- process_id: 1302
- }
- task_info {
- estimated_mws: 0.029590
- estimated_mw: 0.003421
- thread_name: "UsfHalWorker"
- process_name: "/vendor/bin/hw/android.hardware.sensors-service.multihal"
- thread_id: 792
- process_id: 664
- }
- task_info {
- estimated_mws: 0.028316
- estimated_mw: 0.003274
- thread_name: "binder:1999_5"
- process_name: "com.google.android.wearable.watchface.rwf"
- thread_id: 4985
- process_id: 1999
- }
- task_info {
- estimated_mws: 0.028097
- estimated_mw: 0.003249
- thread_name: "SatelliteContro"
- process_name: "com.android.phone"
- thread_id: 2382
- process_id: 2182
- }
- task_info {
- estimated_mws: 0.027797
- estimated_mw: 0.003214
- thread_name: "pm-service"
- process_name: "/vendor/bin/pm-service"
- thread_id: 745
- process_id: 730
- }
- task_info {
- estimated_mws: 0.027301
- estimated_mw: 0.003157
- thread_name: "-Executor] idle"
- process_name: "com.google.android.gms.persistent"
- thread_id: 5603
- process_id: 1949
- }
- task_info {
- estimated_mws: 0.027287
- estimated_mw: 0.003155
- thread_name: "perfetto"
- process_name: "perfetto"
- thread_id: 5581
- process_id: 5581
- }
- task_info {
- estimated_mws: 0.026942
- estimated_mw: 0.003115
- thread_name: "ExeSeq-P10-1"
- process_name: "com.google.android.inputmethod.latin"
- thread_id: 5074
- process_id: 4997
- }
- task_info {
- estimated_mws: 0.026776
- estimated_mw: 0.003096
- thread_name: "binder:978_2"
- process_name: "/system/vendor/bin/nicmd"
- thread_id: 1364
- process_id: 978
- }
- task_info {
- estimated_mws: 0.026328
- estimated_mw: 0.003044
- thread_name: "HwBinder:2129_1"
- process_name: "com.google.android.grilservice"
- thread_id: 3649
- process_id: 2129
- }
- task_info {
- estimated_mws: 0.026013
- estimated_mw: 0.003008
- thread_name: "DefaultExecutor"
- process_name: "com.google.android.wearable.watchface.rwf"
- thread_id: 5588
- process_id: 1999
- }
- task_info {
- estimated_mws: 0.025701
- estimated_mw: 0.002972
- thread_name: "rkstack.process"
- process_name: "com.android.networkstack.process"
- thread_id: 2049
- process_id: 2049
- }
- task_info {
- estimated_mws: 0.024843
- estimated_mw: 0.002872
- thread_name: "hwuiTask1"
- process_name: "com.google.android.wearable.sysui"
- thread_id: 1997
- process_id: 1926
- }
- task_info {
- estimated_mws: 0.024811
- estimated_mw: 0.002869
- thread_name: "pool-1-thread-1"
- process_name: "com.google.android.apps.scone"
- thread_id: 5271
- process_id: 5245
- }
- task_info {
- estimated_mws: 0.024633
- estimated_mw: 0.002848
- thread_name: "binder:978_2"
- process_name: "/system/vendor/bin/nicmd"
- thread_id: 1360
- process_id: 978
- }
- task_info {
- estimated_mws: 0.023564
- estimated_mw: 0.002724
- thread_name: "binder:978_2"
- process_name: "/system/vendor/bin/nicmd"
- thread_id: 1366
- process_id: 978
- }
- task_info {
- estimated_mws: 0.023405
- estimated_mw: 0.002706
- thread_name: "cnss-daemon"
- process_name: "/system/vendor/bin/cnss-daemon"
- thread_id: 5613
- process_id: 1009
- }
- task_info {
- estimated_mws: 0.023393
- estimated_mw: 0.002705
- thread_name: "servicemanager"
- thread_id: 5608
- }
- task_info {
- estimated_mws: 0.022813
- estimated_mw: 0.002638
- thread_name: "android.hardwar"
- process_name: "/vendor/bin/hw/android.hardware.contexthub-service.wac"
- thread_id: 644
- process_id: 644
- }
- task_info {
- estimated_mws: 0.022774
- estimated_mw: 0.002633
- thread_name: "binder:978_2"
- process_name: "/system/vendor/bin/nicmd"
- thread_id: 1362
- process_id: 978
- }
- task_info {
- estimated_mws: 0.022714
- estimated_mw: 0.002626
- thread_name: "binder:685_3"
- thread_id: 5594
- }
- task_info {
- estimated_mws: 0.022642
- estimated_mw: 0.002618
- thread_name: "binder:978_2"
- process_name: "/system/vendor/bin/nicmd"
- thread_id: 1358
- process_id: 978
- }
- task_info {
- estimated_mws: 0.022617
- estimated_mw: 0.002615
- thread_name: "vndservicemanag"
- thread_id: 5607
- }
- task_info {
- estimated_mws: 0.021814
- estimated_mw: 0.002522
- thread_name: "it.FitbitMobile"
- process_name: "com.fitbit.FitbitMobile"
- thread_id: 5377
- process_id: 5377
- }
- task_info {
- estimated_mws: 0.021313
- estimated_mw: 0.002464
- thread_name: "irq/199-dwc3"
- process_name: "irq/199-dwc3"
- thread_id: 5559
- process_id: 5559
- }
- task_info {
- estimated_mws: 0.021307
- estimated_mw: 0.002463
- thread_name: "SysUiBg"
- process_name: "com.google.android.apps.wearable.systemui"
- thread_id: 2294
- process_id: 2171
- }
- task_info {
- estimated_mws: 0.020941
- estimated_mw: 0.002421
- thread_name: "-Executor] idle"
- process_name: "com.google.android.gms.persistent"
- thread_id: 5623
- process_id: 1949
- }
- task_info {
- estimated_mws: 0.020393
- estimated_mw: 0.002358
- thread_name: "vndservicemanag"
- thread_id: 5582
- }
- task_info {
- estimated_mws: 0.019946
- estimated_mw: 0.002306
- thread_name: "qcom,system-poo"
- process_name: "qcom,system-pool-refill-thread"
- thread_id: 81
- process_id: 81
- }
- task_info {
- estimated_mws: 0.019901
- estimated_mw: 0.002301
- thread_name: "binder:2129_9"
- process_name: "com.google.android.grilservice"
- thread_id: 5203
- process_id: 2129
- }
- task_info {
- estimated_mws: 0.019723
- estimated_mw: 0.002280
- thread_name: "servicemanager"
- thread_id: 5610
- }
- task_info {
- estimated_mws: 0.019537
- estimated_mw: 0.002259
- thread_name: "cnss-daemon"
- process_name: "/system/vendor/bin/cnss-daemon"
- thread_id: 1009
- process_id: 1009
- }
- task_info {
- estimated_mws: 0.019390
- estimated_mw: 0.002242
- thread_name: "pool-9-thread-1"
- process_name: "com.google.android.wearable.watchface.rwf"
- thread_id: 2073
- process_id: 1999
- }
- task_info {
- estimated_mws: 0.019219
- estimated_mw: 0.002222
- thread_name: "binder:1302_3"
- process_name: "system_server"
- thread_id: 1433
- process_id: 1302
- }
- task_info {
- estimated_mws: 0.019059
- estimated_mw: 0.002204
- thread_name: "mcu_mgmtd"
- process_name: "/vendor/bin/mcu_mgmtd"
- thread_id: 587
- process_id: 524
- }
- task_info {
- estimated_mws: 0.018619
- estimated_mw: 0.002153
- thread_name: "WCMTelemetryLog"
- process_name: "system_server"
- thread_id: 1906
- process_id: 1302
- }
- task_info {
- estimated_mws: 0.018508
- estimated_mw: 0.002140
- thread_name: "droid.tethering"
- process_name: "com.android.networkstack.process"
- thread_id: 2158
- process_id: 2049
- }
- task_info {
- estimated_mws: 0.018055
- estimated_mw: 0.002087
- thread_name: "DefaultWallpape"
- process_name: "com.google.android.wearable.watchface.rwf"
- thread_id: 2082
- process_id: 1999
- }
- task_info {
- estimated_mws: 0.017725
- estimated_mw: 0.002049
- thread_name: "HeapTaskDaemon"
- process_name: "com.fitbit.FitbitMobile"
- thread_id: 5386
- process_id: 5377
- }
- task_info {
- estimated_mws: 0.017125
- estimated_mw: 0.001980
- thread_name: "WearConnectionT"
- process_name: "com.google.android.wearable.sysui"
- thread_id: 2172
- process_id: 1926
- }
- task_info {
- estimated_mws: 0.016887
- estimated_mw: 0.001952
- thread_name: "wear-services-w"
- process_name: "com.google.wear.services"
- thread_id: 2029
- process_id: 1948
- }
- task_info {
- estimated_mws: 0.016252
- estimated_mw: 0.001879
- thread_name: "BG Thread #1"
- process_name: "com.google.android.wearable.assistant"
- thread_id: 4080
- process_id: 4038
- }
- task_info {
- estimated_mws: 0.016215
- estimated_mw: 0.001875
- thread_name: "GlobalScheduler"
- process_name: "com.google.android.gms.persistent"
- thread_id: 2276
- process_id: 1949
- }
- task_info {
- estimated_mws: 0.016141
- estimated_mw: 0.001866
- thread_name: "pool-31-thread-"
- process_name: "com.google.android.wearable.sysui"
- thread_id: 5617
- process_id: 1926
- }
- task_info {
- estimated_mws: 0.016069
- estimated_mw: 0.001858
- thread_name: "servicemanager"
- thread_id: 5583
- }
- task_info {
- estimated_mws: 0.015376
- estimated_mw: 0.001778
- thread_name: "RenderEngine"
- process_name: "/system/bin/surfaceflinger"
- thread_id: 5601
- process_id: 755
- }
- task_info {
- estimated_mws: 0.015374
- estimated_mw: 0.001777
- thread_name: "pool-11-thread-"
- process_name: "com.google.android.wearable.healthservices"
- thread_id: 5596
- process_id: 3028
- }
- task_info {
- estimated_mws: 0.015097
- estimated_mw: 0.001745
- thread_name: "dsi_err_workq"
- process_name: "dsi_err_workq"
- thread_id: 5589
- process_id: 5589
- }
- task_info {
- estimated_mws: 0.015062
- estimated_mw: 0.001741
- thread_name: "InteractionJank"
- process_name: "com.google.android.apps.wearable.systemui"
- thread_id: 2300
- process_id: 2171
- }
- task_info {
- estimated_mws: 0.015034
- estimated_mw: 0.001738
- thread_name: "vndservicemanag"
- thread_id: 5609
- }
- task_info {
- estimated_mws: 0.014592
- estimated_mw: 0.001687
- thread_name: "hwuiTask0"
- process_name: "com.google.android.wearable.sysui"
- thread_id: 1996
- process_id: 1926
- }
- task_info {
- estimated_mws: 0.014520
- estimated_mw: 0.001679
- thread_name: "tworkPolicy.uid"
- process_name: "system_server"
- thread_id: 1817
- process_id: 1302
- }
- task_info {
- estimated_mws: 0.014278
- estimated_mw: 0.001651
- thread_name: "highpool[10]"
- process_name: "com.google.android.gms.persistent"
- thread_id: 3417
- process_id: 1949
- }
- task_info {
- estimated_mws: 0.014123
- estimated_mw: 0.001633
- thread_name: "LowMemThread"
- process_name: "system_server"
- thread_id: 1481
- process_id: 1302
- }
- task_info {
- estimated_mws: 0.014026
- estimated_mw: 0.001622
- thread_name: "pixelstats-vend"
- process_name: "/vendor/bin/pixelstats-vendor"
- thread_id: 266
- process_id: 255
- }
- task_info {
- estimated_mws: 0.013579
- estimated_mw: 0.001570
- thread_name: "kworker/u9:0"
- process_name: "kworker/u9:0"
- thread_id: 64
- process_id: 64
- }
- task_info {
- estimated_mws: 0.013322
- estimated_mw: 0.001540
- thread_name: "migration/1"
- process_name: "migration/1"
- thread_id: 25
- process_id: 25
- }
- task_info {
- estimated_mws: 0.013321
- estimated_mw: 0.001540
- thread_name: "GlobalDispatchi"
- process_name: "com.google.android.gms.persistent"
- thread_id: 2290
- process_id: 1949
- }
- task_info {
- estimated_mws: 0.013127
- estimated_mw: 0.001518
- thread_name: "main"
- process_name: "/vendor/bin/hw/qcrilNrd"
- thread_id: 1568
- process_id: 1062
- }
- task_info {
- estimated_mws: 0.012863
- estimated_mw: 0.001487
- thread_name: "servicemanager"
- thread_id: 5612
- }
- task_info {
- estimated_mws: 0.012835
- estimated_mw: 0.001484
- thread_name: "qtidataservices"
- process_name: ".qtidataservices"
- thread_id: 2846
- process_id: 2118
- }
- task_info {
- estimated_mws: 0.012734
- estimated_mw: 0.001472
- thread_name: "shortcut"
- process_name: "system_server"
- thread_id: 1874
- process_id: 1302
- }
- task_info {
- estimated_mws: 0.012433
- estimated_mw: 0.001438
- thread_name: "irq/25-mmc0"
- process_name: "irq/25-mmc0"
- thread_id: 120
- process_id: 120
- }
- task_info {
- estimated_mws: 0.012244
- estimated_mw: 0.001416
- thread_name: "GlobalDispatchi"
- process_name: "com.google.android.gms"
- thread_id: 3155
- process_id: 2856
- }
- task_info {
- estimated_mws: 0.012130
- estimated_mw: 0.001402
- thread_name: "ConnectivityThr"
- process_name: "com.google.android.gms"
- thread_id: 4172
- process_id: 2856
- }
- task_info {
- estimated_mws: 0.011932
- estimated_mw: 0.001380
- thread_name: "RenderThread"
- thread_id: 5616
- }
- task_info {
- estimated_mws: 0.011783
- estimated_mw: 0.001362
- thread_name: "AGMIPC@1.0-serv"
- process_name: "/vendor/bin/hw/vendor.qti.hardware.AGMIPC@1.0-service"
- thread_id: 1454
- process_id: 1446
- }
- task_info {
- estimated_mws: 0.011706
- estimated_mw: 0.001353
- thread_name: "binder:2171_2"
- process_name: "com.google.android.apps.wearable.systemui"
- thread_id: 2209
- process_id: 2171
- }
- task_info {
- estimated_mws: 0.011675
- estimated_mw: 0.001350
- thread_name: "radioext@1.0-se"
- process_name: "/vendor/bin/hw/vendor.google.radioext@1.0-service"
- thread_id: 714
- process_id: 676
- }
- task_info {
- estimated_mws: 0.011615
- estimated_mw: 0.001343
- thread_name: "servicemanager"
- thread_id: 5615
- }
- task_info {
- estimated_mws: 0.011467
- estimated_mw: 0.001326
- thread_name: "LocApiMsgTask"
- process_name: "/vendor/bin/hw/android.hardware.gnss-aidl-service-qti"
- thread_id: 694
- process_id: 650
- }
- task_info {
- estimated_mws: 0.011318
- estimated_mw: 0.001309
- thread_name: "LocApiMsgTask"
- process_name: "xtra-daemon"
- thread_id: 1090
- process_id: 1031
- }
- task_info {
- estimated_mws: 0.011273
- estimated_mw: 0.001303
- thread_name: "vndservicemanag"
- thread_id: 5614
- }
- task_info {
- estimated_mws: 0.011024
- estimated_mw: 0.001275
- thread_name: "TimerThread"
- process_name: "/system/bin/audioserver"
- thread_id: 1486
- process_id: 740
- }
- task_info {
- estimated_mws: 0.010869
- estimated_mw: 0.001257
- thread_name: "irq/26-4744000."
- process_name: "irq/26-4744000.sdhci"
- thread_id: 117
- process_id: 117
- }
- task_info {
- estimated_mws: 0.010764
- estimated_mw: 0.001245
- thread_name: "SurfaceSyncGrou"
- process_name: "com.google.android.wearable.sysui"
- thread_id: 1994
- process_id: 1926
- }
- task_info {
- estimated_mws: 0.010731
- estimated_mw: 0.001241
- thread_name: "migration/3"
- process_name: "migration/3"
- thread_id: 40
- process_id: 40
- }
- task_info {
- estimated_mws: 0.010156
- estimated_mw: 0.001174
- thread_name: "id.wearable.app"
- process_name: "com.google.android.wearable.app"
- thread_id: 3857
- process_id: 3857
- }
- task_info {
- estimated_mws: 0.009691
- estimated_mw: 0.001121
- thread_name: "ksoftirqd/0"
- process_name: "ksoftirqd/0"
- thread_id: 13
- process_id: 13
- }
- task_info {
- estimated_mws: 0.009668
- estimated_mw: 0.001118
- thread_name: "cnss-daemon"
- process_name: "/system/vendor/bin/cnss-daemon"
- thread_id: 1052
- process_id: 1009
- }
- task_info {
- estimated_mws: 0.009639
- estimated_mw: 0.001114
- thread_name: "kworker/u9:2"
- process_name: "kworker/u9:2"
- thread_id: 338
- process_id: 338
- }
- task_info {
- estimated_mws: 0.009404
- estimated_mw: 0.001087
- thread_name: "binder:2856_3"
- process_name: "com.google.android.gms"
- thread_id: 3157
- process_id: 2856
- }
- task_info {
- estimated_mws: 0.009364
- estimated_mw: 0.001083
- thread_name: "binder:969_3"
- process_name: "/system/vendor/bin/cnd"
- thread_id: 1013
- process_id: 969
- }
- task_info {
- estimated_mws: 0.008837
- estimated_mw: 0.001022
- thread_name: "binder:2118_2"
- process_name: ".qtidataservices"
- thread_id: 2142
- process_id: 2118
- }
- task_info {
- estimated_mws: 0.008775
- estimated_mw: 0.001015
- thread_name: "thermal-engine-"
- process_name: "/vendor/bin/thermal-engine-v2"
- thread_id: 2520
- process_id: 2493
- }
- task_info {
- estimated_mws: 0.008724
- estimated_mw: 0.001009
- thread_name: "binder:2856_7"
- process_name: "com.google.android.gms"
- thread_id: 4825
- process_id: 2856
- }
- task_info {
- estimated_mws: 0.008275
- estimated_mw: 0.000957
- thread_name: "binder:2856_6"
- process_name: "com.google.android.gms"
- thread_id: 4824
- process_id: 2856
- }
- task_info {
- estimated_mws: 0.008136
- estimated_mw: 0.000941
- thread_name: "binder:3857_3"
- process_name: "com.google.android.wearable.app"
- thread_id: 3872
- process_id: 3857
- }
- task_info {
- estimated_mws: 0.007913
- estimated_mw: 0.000915
- thread_name: "binder:975_3"
- process_name: "/vendor/bin/imsdaemon"
- thread_id: 1630
- process_id: 975
- }
- task_info {
- estimated_mws: 0.007892
- estimated_mw: 0.000913
- thread_name: "time_daemon"
- process_name: "/vendor/bin/time_daemon"
- thread_id: 525
- process_id: 522
- }
- task_info {
- estimated_mws: 0.007750
- estimated_mw: 0.000896
- thread_name: "binder:978_2"
- process_name: "/system/vendor/bin/nicmd"
- thread_id: 1044
- process_id: 978
- }
- task_info {
- estimated_mws: 0.007591
- estimated_mw: 0.000878
- thread_name: "binder:2856_5"
- process_name: "com.google.android.gms"
- thread_id: 3681
- process_id: 2856
- }
- task_info {
- estimated_mws: 0.007392
- estimated_mw: 0.000855
- thread_name: "binder:2182_1"
- process_name: "com.android.phone"
- thread_id: 2231
- process_id: 2182
- }
- task_info {
- estimated_mws: 0.007384
- estimated_mw: 0.000854
- thread_name: "binder:2171_5"
- process_name: "com.google.android.apps.wearable.systemui"
- thread_id: 2638
- process_id: 2171
- }
- task_info {
- estimated_mws: 0.007245
- estimated_mw: 0.000838
- thread_name: "qrtr_rx"
- process_name: "qrtr_rx"
- thread_id: 1556
- process_id: 1556
- }
- task_info {
- estimated_mws: 0.007086
- estimated_mw: 0.000819
- thread_name: "radioext@1.0-se"
- process_name: "/vendor/bin/hw/vendor.google.radioext@1.0-service"
- thread_id: 1605
- process_id: 676
- }
- task_info {
- estimated_mws: 0.006850
- estimated_mw: 0.000792
- thread_name: "hwservicemanage"
- process_name: "/system/system_ext/bin/hwservicemanager"
- thread_id: 214
- process_id: 214
- }
- task_info {
- estimated_mws: 0.006731
- estimated_mw: 0.000778
- thread_name: "rcub/0"
- process_name: "rcub/0"
- thread_id: 17
- process_id: 17
- }
- task_info {
- estimated_mws: 0.006663
- estimated_mw: 0.000770
- thread_name: "binder:2856_8"
- process_name: "com.google.android.gms"
- thread_id: 4826
- process_id: 2856
- }
- task_info {
- estimated_mws: 0.006650
- estimated_mw: 0.000769
- thread_name: "kthreadd"
- process_name: "kthreadd"
- thread_id: 2
- process_id: 2
- }
- task_info {
- estimated_mws: 0.006574
- estimated_mw: 0.000760
- thread_name: "binder:233_2"
- process_name: "/system/bin/vold"
- thread_id: 233
- process_id: 233
- }
- task_info {
- estimated_mws: 0.006115
- estimated_mw: 0.000707
- thread_name: "cds_ol_rx_threa"
- process_name: "cds_ol_rx_thread"
- thread_id: 5199
- process_id: 5199
- }
- task_info {
- estimated_mws: 0.005829
- estimated_mw: 0.000674
- thread_name: "NsdService"
- process_name: "system_server"
- thread_id: 1831
- process_id: 1302
- }
- task_info {
- estimated_mws: 0.005734
- estimated_mw: 0.000663
- thread_name: "binder:978_2"
- process_name: "/system/vendor/bin/nicmd"
- thread_id: 1367
- process_id: 978
- }
- task_info {
- estimated_mws: 0.005699
- estimated_mw: 0.000659
- thread_name: "Scheduled BG"
- process_name: "com.google.android.wearable.sysui"
- thread_id: 2895
- process_id: 1926
- }
- task_info {
- estimated_mws: 0.005530
- estimated_mw: 0.000639
- thread_name: "binder:2856_2"
- process_name: "com.google.android.gms"
- thread_id: 2903
- process_id: 2856
- }
- task_info {
- estimated_mws: 0.005484
- estimated_mw: 0.000634
- thread_name: "binder:978_2"
- process_name: "/system/vendor/bin/nicmd"
- thread_id: 1361
- process_id: 978
- }
- task_info {
- estimated_mws: 0.005366
- estimated_mw: 0.000620
- thread_name: "binder:978_2"
- process_name: "/system/vendor/bin/nicmd"
- thread_id: 1357
- process_id: 978
- }
- task_info {
- estimated_mws: 0.005364
- estimated_mw: 0.000620
- thread_name: "FileObserver"
- process_name: "system_server"
- thread_id: 1498
- process_id: 1302
- }
- task_info {
- estimated_mws: 0.005278
- estimated_mw: 0.000610
- thread_name: "binder:978_2"
- process_name: "/system/vendor/bin/nicmd"
- thread_id: 1359
- process_id: 978
- }
- task_info {
- estimated_mws: 0.005261
- estimated_mw: 0.000608
- thread_name: "Lite Thread #0"
- process_name: "com.google.android.wearable.assistant"
- thread_id: 4109
- process_id: 4038
- }
- task_info {
- estimated_mws: 0.005011
- estimated_mw: 0.000579
- thread_name: "kworker/3:1H"
- process_name: "kworker/3:1H"
- thread_id: 122
- process_id: 122
- }
- task_info {
- estimated_mws: 0.004996
- estimated_mw: 0.000578
- thread_name: "binder:978_2"
- process_name: "/system/vendor/bin/nicmd"
- thread_id: 1365
- process_id: 978
- }
- task_info {
- estimated_mws: 0.004986
- estimated_mw: 0.000576
- thread_name: "Scheduled BG"
- process_name: "com.google.android.wearable.sysui"
- thread_id: 2890
- process_id: 1926
- }
- task_info {
- estimated_mws: 0.004877
- estimated_mw: 0.000564
- thread_name: "perfetto_hprof_"
- process_name: "com.google.android.gms"
- thread_id: 2880
- process_id: 2856
- }
- task_info {
- estimated_mws: 0.004761
- estimated_mw: 0.000550
- thread_name: "backup-0"
- process_name: "system_server"
- thread_id: 2660
- process_id: 1302
- }
- task_info {
- estimated_mws: 0.004757
- estimated_mw: 0.000550
- thread_name: "tts-player-0"
- process_name: "com.google.android.wearable.assistant"
- thread_id: 4209
- process_id: 4038
- }
- task_info {
- estimated_mws: 0.004444
- estimated_mw: 0.000514
- thread_name: "Signal Catcher"
- process_name: "com.google.android.gms"
- thread_id: 2878
- process_id: 2856
- }
- task_info {
- estimated_mws: 0.004331
- estimated_mw: 0.000501
- thread_name: "binder:978_2"
- process_name: "/system/vendor/bin/nicmd"
- thread_id: 1363
- process_id: 978
- }
- task_info {
- estimated_mws: 0.004292
- estimated_mw: 0.000496
- thread_name: "f2fs_discard-25"
- process_name: "f2fs_discard-254:43"
- thread_id: 349
- process_id: 349
- }
- task_info {
- estimated_mws: 0.004283
- estimated_mw: 0.000495
- thread_name: "irq/24-glink-na"
- process_name: "irq/24-glink-native-rpm-glink"
- thread_id: 86
- process_id: 86
- }
- task_info {
- estimated_mws: 0.004252
- estimated_mw: 0.000492
- thread_name: "pool-4-thread-1"
- process_name: "system_server"
- thread_id: 1774
- process_id: 1302
- }
- task_info {
- estimated_mws: 0.004010
- estimated_mw: 0.000464
- thread_name: "PasspointProvis"
- process_name: "system_server"
- thread_id: 1821
- process_id: 1302
- }
- task_info {
- estimated_mws: 0.003934
- estimated_mw: 0.000455
- thread_name: "binder:5377_5"
- process_name: "com.fitbit.FitbitMobile"
- thread_id: 5573
- process_id: 5377
- }
- task_info {
- estimated_mws: 0.003880
- estimated_mw: 0.000449
- thread_name: "BG Thread #3"
- process_name: "com.google.android.wearable.assistant"
- thread_id: 4107
- process_id: 4038
- }
- task_info {
- estimated_mws: 0.003872
- estimated_mw: 0.000448
- thread_name: "TransportThread"
- process_name: "/vendor/bin/mcu_mgmtd"
- thread_id: 3540
- process_id: 524
- }
- task_info {
- estimated_mws: 0.003835
- estimated_mw: 0.000443
- thread_name: "kworker/1:1H"
- process_name: "kworker/1:1H"
- thread_id: 127
- process_id: 127
- }
- task_info {
- estimated_mws: 0.003754
- estimated_mw: 0.000434
- thread_name: "watchdog"
- process_name: "system_server"
- thread_id: 1421
- process_id: 1302
- }
- task_info {
- estimated_mws: 0.003628
- estimated_mw: 0.000419
- thread_name: "PackageInstalle"
- process_name: "system_server"
- thread_id: 1744
- process_id: 1302
- }
- task_info {
- estimated_mws: 0.003469
- estimated_mw: 0.000401
- thread_name: "Lite Thread #1"
- process_name: "com.google.android.wearable.assistant"
- thread_id: 4118
- process_id: 4038
- }
- task_info {
- estimated_mws: 0.003393
- estimated_mw: 0.000392
- thread_name: "FinalizerWatchd"
- process_name: "com.fitbit.FitbitMobile"
- thread_id: 5389
- process_id: 5377
- }
- task_info {
- estimated_mws: 0.003365
- estimated_mw: 0.000389
- thread_name: "DFacilitator-1"
- process_name: "com.google.android.inputmethod.latin"
- thread_id: 5128
- process_id: 4997
- }
- task_info {
- estimated_mws: 0.003243
- estimated_mw: 0.000375
- thread_name: "pool-8-thread-1"
- process_name: "com.google.android.wearable.healthservices"
- thread_id: 3308
- process_id: 3028
- }
- task_info {
- estimated_mws: 0.002873
- estimated_mw: 0.000332
- thread_name: "lowpool[1]"
- process_name: "com.google.android.gms"
- thread_id: 3503
- process_id: 2856
- }
- task_info {
- estimated_mws: 0.002842
- estimated_mw: 0.000329
- thread_name: "ReferenceQueueD"
- process_name: "com.google.android.gms"
- thread_id: 2883
- process_id: 2856
- }
- task_info {
- estimated_mws: 0.002778
- estimated_mw: 0.000321
- thread_name: "ipacm-diag"
- process_name: "/system/vendor/bin/ipacm-diag"
- thread_id: 976
- process_id: 976
- }
- task_info {
- estimated_mws: 0.002739
- estimated_mw: 0.000317
- thread_name: "migration/2"
- process_name: "migration/2"
- thread_id: 32
- process_id: 32
- }
- task_info {
- estimated_mws: 0.002654
- estimated_mw: 0.000307
- thread_name: "qrtr_rx"
- process_name: "qrtr_rx"
- thread_id: 564
- process_id: 564
- }
- task_info {
- estimated_mws: 0.002601
- estimated_mw: 0.000301
- thread_name: "card0-crtc0"
- process_name: "card0-crtc0"
- thread_id: 247
- process_id: 247
- }
- task_info {
- estimated_mws: 0.002574
- estimated_mw: 0.000298
- thread_name: "pool-7-thread-3"
- process_name: "com.google.android.wearable.healthservices"
- thread_id: 5435
- process_id: 3028
- }
- task_info {
- estimated_mws: 0.002458
- estimated_mw: 0.000284
- thread_name: "RenderThread"
- process_name: "com.google.android.apps.wearable.systemui"
- thread_id: 2319
- process_id: 2171
- }
- task_info {
- estimated_mws: 0.002443
- estimated_mw: 0.000283
- thread_name: "highpool[0]"
- process_name: "com.google.android.gms"
- thread_id: 3154
- process_id: 2856
- }
- task_info {
- estimated_mws: 0.002437
- estimated_mw: 0.000282
- thread_name: "lowpool[0]"
- process_name: "com.google.android.gms"
- thread_id: 3478
- process_id: 2856
- }
- task_info {
- estimated_mws: 0.002241
- estimated_mw: 0.000259
- thread_name: "queued-work-loo"
- process_name: "com.google.android.gms.persistent"
- thread_id: 3533
- process_id: 1949
- }
- task_info {
- estimated_mws: 0.002222
- estimated_mw: 0.000257
- thread_name: "arch_disk_io_2"
- process_name: "com.google.android.gms"
- thread_id: 4174
- process_id: 2856
- }
- task_info {
- estimated_mws: 0.002160
- estimated_mw: 0.000250
- thread_name: "binder:523_2"
- process_name: "/system/vendor/bin/ipacm"
- thread_id: 537
- process_id: 523
- }
- task_info {
- estimated_mws: 0.002129
- estimated_mw: 0.000246
- thread_name: "Jit thread pool"
- process_name: "com.google.android.gms"
- thread_id: 2881
- process_id: 2856
- }
- task_info {
- estimated_mws: 0.002082
- estimated_mw: 0.000241
- thread_name: "pool-48-thread-"
- process_name: "com.google.android.gms"
- thread_id: 4110
- process_id: 2856
- }
- task_info {
- estimated_mws: 0.002071
- estimated_mw: 0.000239
- thread_name: "RenderThread"
- process_name: "com.google.android.inputmethod.latin"
- thread_id: 5120
- process_id: 4997
- }
- task_info {
- estimated_mws: 0.002020
- estimated_mw: 0.000234
- thread_name: "arch_disk_io_0"
- process_name: "com.google.android.gms"
- thread_id: 4031
- process_id: 2856
- }
- task_info {
- estimated_mws: 0.001985
- estimated_mw: 0.000229
- thread_name: "arch_disk_io_1"
- process_name: "com.google.android.gms"
- thread_id: 4034
- process_id: 2856
- }
- task_info {
- estimated_mws: 0.001856
- estimated_mw: 0.000215
- thread_name: "BG Thread #0"
- process_name: "com.google.android.wearable.assistant"
- thread_id: 4061
- process_id: 4038
- }
- task_info {
- estimated_mws: 0.001782
- estimated_mw: 0.000206
- thread_name: "binder:1926_1"
- process_name: "com.google.android.wearable.sysui"
- thread_id: 1938
- process_id: 1926
- }
- task_info {
- estimated_mws: 0.001776
- estimated_mw: 0.000205
- thread_name: "highpool[3]"
- process_name: "com.google.android.gms"
- thread_id: 3470
- process_id: 2856
- }
- task_info {
- estimated_mws: 0.001776
- estimated_mw: 0.000205
- thread_name: "migration/0"
- process_name: "migration/0"
- thread_id: 21
- process_id: 21
- }
- task_info {
- estimated_mws: 0.001772
- estimated_mw: 0.000205
- thread_name: "AsyncTask #2"
- process_name: "com.google.android.gms"
- thread_id: 4164
- process_id: 2856
- }
- task_info {
- estimated_mws: 0.001720
- estimated_mw: 0.000199
- thread_name: "arch_disk_io_3"
- process_name: "com.google.android.gms"
- thread_id: 4175
- process_id: 2856
- }
- task_info {
- estimated_mws: 0.001562
- estimated_mw: 0.000181
- thread_name: "POSIX timer 0"
- process_name: "/vendor/bin/hw/android.hardware.sensors-service.multihal"
- thread_id: 850
- process_id: 664
- }
- task_info {
- estimated_mws: 0.001520
- estimated_mw: 0.000176
- thread_name: "ksoftirqd/3"
- process_name: "ksoftirqd/3"
- thread_id: 42
- process_id: 42
- }
- task_info {
- estimated_mws: 0.001401
- estimated_mw: 0.000162
- thread_name: "Primes-1"
- process_name: "com.fitbit.FitbitMobile"
- thread_id: 5394
- process_id: 5377
- }
- task_info {
- estimated_mws: 0.001320
- estimated_mw: 0.000153
- thread_name: "binder:5377_3"
- process_name: "com.fitbit.FitbitMobile"
- thread_id: 5392
- process_id: 5377
- }
- task_info {
- estimated_mws: 0.001316
- estimated_mw: 0.000152
- thread_name: "msm-watchdog"
- process_name: "msm-watchdog"
- thread_id: 76
- process_id: 76
- }
- task_info {
- estimated_mws: 0.001222
- estimated_mw: 0.000141
- thread_name: "Lite Thread #1"
- process_name: "com.fitbit.FitbitMobile"
- thread_id: 5421
- process_id: 5377
- }
- task_info {
- estimated_mws: 0.001220
- estimated_mw: 0.000141
- thread_name: "Signal Catcher"
- process_name: "com.fitbit.FitbitMobile"
- thread_id: 5382
- process_id: 5377
- }
- task_info {
- estimated_mws: 0.001179
- estimated_mw: 0.000136
- thread_name: "GoogleApiHandle"
- process_name: "com.fitbit.FitbitMobile"
- thread_id: 5398
- process_id: 5377
- }
- task_info {
- estimated_mws: 0.001127
- estimated_mw: 0.000130
- thread_name: "binder:2171_6"
- process_name: "com.google.android.apps.wearable.systemui"
- thread_id: 2678
- process_id: 2171
- }
- task_info {
- estimated_mws: 0.001103
- estimated_mw: 0.000128
- thread_name: "Blocking Thread"
- process_name: "com.fitbit.FitbitMobile"
- thread_id: 5574
- process_id: 5377
- }
- task_info {
- estimated_mws: 0.001055
- estimated_mw: 0.000122
- thread_name: "WM.task-3"
- process_name: "com.fitbit.FitbitMobile"
- thread_id: 5430
- process_id: 5377
- }
- task_info {
- estimated_mws: 0.000990
- estimated_mw: 0.000114
- thread_name: "highpool[1]"
- process_name: "com.google.android.gms"
- thread_id: 3373
- process_id: 2856
- }
- task_info {
- estimated_mws: 0.000984
- estimated_mw: 0.000114
- thread_name: "Primes-nativecr"
- process_name: "com.fitbit.FitbitMobile"
- thread_id: 5397
- process_id: 5377
- }
- task_info {
- estimated_mws: 0.000961
- estimated_mw: 0.000111
- thread_name: "binder:740_4"
- process_name: "/system/bin/audioserver"
- thread_id: 2183
- process_id: 740
- }
- task_info {
- estimated_mws: 0.000954
- estimated_mw: 0.000110
- thread_name: "Lite Thread #0"
- process_name: "com.fitbit.FitbitMobile"
- thread_id: 5404
- process_id: 5377
- }
- task_info {
- estimated_mws: 0.000927
- estimated_mw: 0.000107
- thread_name: "BG Thread #0"
- process_name: "com.fitbit.FitbitMobile"
- thread_id: 5395
- process_id: 5377
- }
- task_info {
- estimated_mws: 0.000926
- estimated_mw: 0.000107
- thread_name: "FinalizerDaemon"
- process_name: "com.google.android.gms"
- thread_id: 2885
- process_id: 2856
- }
- task_info {
- estimated_mws: 0.000887
- estimated_mw: 0.000103
- thread_name: "BG Thread #1"
- process_name: "com.fitbit.FitbitMobile"
- thread_id: 5396
- process_id: 5377
- }
- task_info {
- estimated_mws: 0.000865
- estimated_mw: 0.000100
- thread_name: "Jit thread pool"
- process_name: "com.fitbit.FitbitMobile"
- thread_id: 5385
- process_id: 5377
- }
- task_info {
- estimated_mws: 0.000858
- estimated_mw: 0.000099
- thread_name: "highpool[2]"
- process_name: "com.google.android.gms"
- thread_id: 3375
- process_id: 2856
- }
- task_info {
- estimated_mws: 0.000855
- estimated_mw: 0.000099
- thread_name: "ConnectivityThr"
- process_name: "com.fitbit.FitbitMobile"
- thread_id: 5423
- process_id: 5377
- }
- task_info {
- estimated_mws: 0.000828
- estimated_mw: 0.000096
- thread_name: "Profile Saver"
- process_name: "com.fitbit.FitbitMobile"
- thread_id: 5393
- process_id: 5377
- }
- task_info {
- estimated_mws: 0.000808
- estimated_mw: 0.000093
- thread_name: "ReferenceQueueD"
- process_name: "com.fitbit.FitbitMobile"
- thread_id: 5387
- process_id: 5377
- }
- task_info {
- estimated_mws: 0.000803
- estimated_mw: 0.000093
- thread_name: "binder:5377_4"
- process_name: "com.fitbit.FitbitMobile"
- thread_id: 5433
- process_id: 5377
- }
- task_info {
- estimated_mws: 0.000783
- estimated_mw: 0.000091
- thread_name: "ksoftirqd/1"
- process_name: "ksoftirqd/1"
- thread_id: 27
- process_id: 27
- }
- task_info {
- estimated_mws: 0.000782
- estimated_mw: 0.000090
- thread_name: "HsConnectionMan"
- process_name: "com.fitbit.FitbitMobile"
- thread_id: 5422
- process_id: 5377
- }
- task_info {
- estimated_mws: 0.000769
- estimated_mw: 0.000089
- thread_name: "ADB-JDWP Connec"
- process_name: "com.fitbit.FitbitMobile"
- thread_id: 5384
- process_id: 5377
- }
- task_info {
- estimated_mws: 0.000742
- estimated_mw: 0.000086
- thread_name: "Scheduler Threa"
- process_name: "com.fitbit.FitbitMobile"
- thread_id: 5428
- process_id: 5377
- }
- task_info {
- estimated_mws: 0.000733
- estimated_mw: 0.000085
- thread_name: "WM.task-2"
- process_name: "com.fitbit.FitbitMobile"
- thread_id: 5429
- process_id: 5377
- }
- task_info {
- estimated_mws: 0.000730
- estimated_mw: 0.000084
- thread_name: "Scheduled BG"
- process_name: "com.google.android.wearable.sysui"
- thread_id: 2896
- process_id: 1926
- }
- task_info {
- estimated_mws: 0.000727
- estimated_mw: 0.000084
- thread_name: "DefaultDispatch"
- process_name: "com.fitbit.FitbitMobile"
- thread_id: 5431
- process_id: 5377
- }
- task_info {
- estimated_mws: 0.000726
- estimated_mw: 0.000084
- thread_name: "BG Thread #3"
- process_name: "com.fitbit.FitbitMobile"
- thread_id: 5400
- process_id: 5377
- }
- task_info {
- estimated_mws: 0.000724
- estimated_mw: 0.000084
- thread_name: "WM.task-1"
- process_name: "com.fitbit.FitbitMobile"
- thread_id: 5427
- process_id: 5377
- }
- task_info {
- estimated_mws: 0.000718
- estimated_mw: 0.000083
- thread_name: "binder:5377_1"
- process_name: "com.fitbit.FitbitMobile"
- thread_id: 5390
- process_id: 5377
- }
- task_info {
- estimated_mws: 0.000689
- estimated_mw: 0.000080
- thread_name: "DefaultDispatch"
- process_name: "com.fitbit.FitbitMobile"
- thread_id: 5432
- process_id: 5377
- }
- task_info {
- estimated_mws: 0.000669
- estimated_mw: 0.000077
- thread_name: "BG Thread #2"
- process_name: "com.fitbit.FitbitMobile"
- thread_id: 5399
- process_id: 5377
- }
- task_info {
- estimated_mws: 0.000630
- estimated_mw: 0.000073
- thread_name: "binder:5377_2"
- process_name: "com.fitbit.FitbitMobile"
- thread_id: 5391
- process_id: 5377
- }
- task_info {
- estimated_mws: 0.000583
- estimated_mw: 0.000067
- thread_name: "perfetto_hprof_"
- process_name: "com.fitbit.FitbitMobile"
- thread_id: 5383
- process_id: 5377
- }
- task_info {
- estimated_mws: 0.000507
- estimated_mw: 0.000059
- thread_name: "Primes-2"
- process_name: "com.fitbit.FitbitMobile"
- thread_id: 5444
- process_id: 5377
- }
- task_info {
- estimated_mws: 0.000403
- estimated_mw: 0.000047
- thread_name: "FinalizerDaemon"
- process_name: "com.fitbit.FitbitMobile"
- thread_id: 5388
- process_id: 5377
+ metric_version: 4
+ power_model_version: 1
+ period_info {
+ period_id: 1
+ task_info {
+ estimated_mws: 34.416729
+ estimated_mw: 3.979098
+ thread_name: "swapper"
+ thread_id: 0
+ process_id: 0
+ }
+ task_info {
+ estimated_mws: 19.853703
+ estimated_mw: 2.295390
+ idle_transitions_mws: 0.220895
+ thread_name: "RenderThread"
+ process_name: "com.google.android.wearable.sysui"
+ thread_id: 1986
+ process_id: 1926
+ }
+ task_info {
+ estimated_mws: 17.530441
+ estimated_mw: 2.026786
+ idle_transitions_mws: 0.028812
+ thread_name: "Jit thread pool"
+ process_name: "system_server"
+ thread_id: 1344
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 16.980274
+ estimated_mw: 1.963178
+ idle_transitions_mws: 0.387957
+ thread_name: "surfaceflinger"
+ process_name: "/system/bin/surfaceflinger"
+ thread_id: 755
+ process_id: 755
+ }
+ task_info {
+ estimated_mws: 14.908094
+ estimated_mw: 1.723603
+ idle_transitions_mws: 0.455047
+ thread_name: ".wearable.sysui"
+ process_name: "com.google.android.wearable.sysui"
+ thread_id: 1926
+ process_id: 1926
+ }
+ task_info {
+ estimated_mws: 13.373355
+ estimated_mw: 1.546164
+ idle_transitions_mws: 0.011711
+ thread_name: "binder:685_3"
+ process_name: "/vendor/bin/hw/vendor.qti.hardware.display.composer-service"
+ thread_id: 804
+ process_id: 685
+ }
+ task_info {
+ estimated_mws: 6.747261
+ estimated_mw: 0.780086
+ idle_transitions_mws: 0.021185
+ thread_name: "binder:1302_7"
+ process_name: "system_server"
+ thread_id: 1671
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 6.504173
+ estimated_mw: 0.751981
+ idle_transitions_mws: 0.055166
+ thread_name: "binder:1302_A"
+ process_name: "system_server"
+ thread_id: 2015
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 4.858775
+ estimated_mw: 0.561748
+ idle_transitions_mws: 0.082958
+ thread_name: "android.anim"
+ process_name: "system_server"
+ thread_id: 1419
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 4.769800
+ estimated_mw: 0.551462
+ idle_transitions_mws: 0.094492
+ thread_name: "RenderEngine"
+ process_name: "/system/bin/surfaceflinger"
+ thread_id: 788
+ process_id: 755
+ }
+ task_info {
+ estimated_mws: 4.672233
+ estimated_mw: 0.540181
+ idle_transitions_mws: 0.012303
+ thread_name: "kswapd0"
+ process_name: "kswapd0"
+ thread_id: 63
+ process_id: 63
+ }
+ task_info {
+ estimated_mws: 4.314495
+ estimated_mw: 0.498821
+ thread_name: "lowpool[2]"
+ process_name: "com.google.android.gms"
+ thread_id: 3525
+ process_id: 2856
+ }
+ task_info {
+ estimated_mws: 4.117818
+ estimated_mw: 0.476083
+ thread_name: "logd.writer"
+ process_name: "/system/bin/logd"
+ thread_id: 221
+ process_id: 211
+ }
+ task_info {
+ estimated_mws: 4.108276
+ estimated_mw: 0.474979
+ idle_transitions_mws: 0.001470
+ thread_name: "binder:1302_17"
+ process_name: "system_server"
+ thread_id: 5202
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 3.723955
+ estimated_mw: 0.430546
+ idle_transitions_mws: 0.046603
+ thread_name: "binder:1302_6"
+ process_name: "system_server"
+ thread_id: 1662
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 3.666289
+ estimated_mw: 0.423879
+ idle_transitions_mws: 0.147155
+ thread_name: "e.watchface.rwf"
+ process_name: "com.google.android.wearable.watchface.rwf"
+ thread_id: 1999
+ process_id: 1999
+ }
+ task_info {
+ estimated_mws: 3.524869
+ estimated_mw: 0.407529
+ idle_transitions_mws: 0.003585
+ thread_name: "killall"
+ process_name: "/system/bin/sh"
+ thread_id: 5620
+ process_id: 5620
+ }
+ task_info {
+ estimated_mws: 3.495762
+ estimated_mw: 0.404163
+ idle_transitions_mws: 0.012035
+ thread_name: "CachedAppOptimi"
+ process_name: "system_server"
+ thread_id: 1773
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 3.459922
+ estimated_mw: 0.400020
+ idle_transitions_mws: 0.022780
+ thread_name: "logcat"
+ process_name: "logcat"
+ thread_id: 1230
+ process_id: 1230
+ }
+ task_info {
+ estimated_mws: 3.429554
+ estimated_mw: 0.396509
+ idle_transitions_mws: 0.020454
+ thread_name: "system_server"
+ process_name: "system_server"
+ thread_id: 1302
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 3.300661
+ estimated_mw: 0.381607
+ idle_transitions_mws: 1.010862
+ thread_name: "crtc_commit:80"
+ process_name: "crtc_commit:80"
+ thread_id: 244
+ process_id: 244
+ }
+ task_info {
+ estimated_mws: 3.194881
+ estimated_mw: 0.369377
+ idle_transitions_mws: 0.163208
+ thread_name: "InputDispatcher"
+ process_name: "system_server"
+ thread_id: 1783
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 3.011913
+ estimated_mw: 0.348223
+ idle_transitions_mws: 0.261953
+ thread_name: "binder:755_1"
+ process_name: "/system/bin/surfaceflinger"
+ thread_id: 782
+ process_id: 755
+ }
+ task_info {
+ estimated_mws: 3.006022
+ estimated_mw: 0.347542
+ idle_transitions_mws: 0.064213
+ thread_name: "android.display"
+ process_name: "system_server"
+ thread_id: 1418
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 2.856301
+ estimated_mw: 0.330232
+ idle_transitions_mws: 0.000982
+ thread_name: "binder:524_2"
+ process_name: "/vendor/bin/mcu_mgmtd"
+ thread_id: 524
+ process_id: 524
+ }
+ task_info {
+ estimated_mws: 2.712443
+ estimated_mw: 0.313600
+ idle_transitions_mws: 0.314323
+ thread_name: "traced_probes"
+ process_name: "/system/bin/traced_probes"
+ thread_id: 904
+ process_id: 904
+ }
+ task_info {
+ estimated_mws: 2.553161
+ estimated_mw: 0.295184
+ idle_transitions_mws: 0.751582
+ thread_name: "kworker/u8:0"
+ process_name: "kworker/u8:0"
+ thread_id: 8
+ process_id: 8
+ }
+ task_info {
+ estimated_mws: 2.487099
+ estimated_mw: 0.287547
+ idle_transitions_mws: 0.509790
+ thread_name: "surfaceflinger"
+ process_name: "/system/bin/surfaceflinger"
+ thread_id: 883
+ process_id: 755
+ }
+ task_info {
+ estimated_mws: 2.386123
+ estimated_mw: 0.275872
+ idle_transitions_mws: 0.002251
+ thread_name: "binder:1302_15"
+ process_name: "system_server"
+ thread_id: 3754
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 2.258779
+ estimated_mw: 0.261149
+ idle_transitions_mws: 0.219690
+ thread_name: "logd.reader.per"
+ process_name: "/system/bin/logd"
+ thread_id: 1274
+ process_id: 211
+ }
+ task_info {
+ estimated_mws: 2.171289
+ estimated_mw: 0.251034
+ idle_transitions_mws: 0.014154
+ thread_name: "RenderThread"
+ process_name: "com.google.android.wearable.watchface.rwf"
+ thread_id: 2301
+ process_id: 1999
+ }
+ task_info {
+ estimated_mws: 2.143151
+ estimated_mw: 0.247781
+ idle_transitions_mws: 0.052887
+ thread_name: "InputReader"
+ process_name: "system_server"
+ thread_id: 1784
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 2.091430
+ estimated_mw: 0.241801
+ idle_transitions_mws: 2.681956
+ thread_name: "rcu_preempt"
+ process_name: "rcu_preempt"
+ thread_id: 14
+ process_id: 14
+ }
+ task_info {
+ estimated_mws: 2.048920
+ estimated_mw: 0.236886
+ idle_transitions_mws: 0.122795
+ thread_name: "binder:1926_4"
+ process_name: "com.google.android.wearable.sysui"
+ thread_id: 2262
+ process_id: 1926
+ }
+ task_info {
+ estimated_mws: 1.914560
+ estimated_mw: 0.221352
+ idle_transitions_mws: 0.033359
+ thread_name: "arable.systemui"
+ process_name: "com.google.android.apps.wearable.systemui"
+ thread_id: 2171
+ process_id: 2171
+ }
+ task_info {
+ estimated_mws: 1.854433
+ estimated_mw: 0.214401
+ idle_transitions_mws: 0.046845
+ thread_name: "android.ui"
+ process_name: "system_server"
+ thread_id: 1416
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 1.777087
+ estimated_mw: 0.205458
+ idle_transitions_mws: 0.428240
+ thread_name: "kworker/u8:4"
+ process_name: "kworker/u8:4"
+ thread_id: 431
+ process_id: 431
+ }
+ task_info {
+ estimated_mws: 1.773777
+ estimated_mw: 0.205076
+ idle_transitions_mws: 0.584214
+ thread_name: "TimerDispatch"
+ process_name: "/system/bin/surfaceflinger"
+ thread_id: 865
+ process_id: 755
+ }
+ task_info {
+ estimated_mws: 1.760400
+ estimated_mw: 0.203529
+ idle_transitions_mws: 0.078772
+ thread_name: "ActivityManager"
+ process_name: "system_server"
+ thread_id: 1431
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 1.733169
+ estimated_mw: 0.200381
+ idle_transitions_mws: 0.039163
+ thread_name: "PowerManagerSer"
+ process_name: "system_server"
+ thread_id: 1506
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 1.639501
+ estimated_mw: 0.189551
+ thread_name: "WifiHandlerThre"
+ process_name: "system_server"
+ thread_id: 1818
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 1.631037
+ estimated_mw: 0.188573
+ idle_transitions_mws: 0.170842
+ thread_name: "binder:755_5"
+ process_name: "/system/bin/surfaceflinger"
+ thread_id: 1987
+ process_id: 755
+ }
+ task_info {
+ estimated_mws: 1.605931
+ estimated_mw: 0.185670
+ idle_transitions_mws: 0.400486
+ thread_name: "kgsl_dispatcher"
+ process_name: "kgsl_dispatcher"
+ thread_id: 111
+ process_id: 111
+ }
+ task_info {
+ estimated_mws: 1.564964
+ estimated_mw: 0.180934
+ idle_transitions_mws: 0.000901
+ thread_name: "binder:1302_8"
+ process_name: "system_server"
+ thread_id: 1679
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 1.476619
+ estimated_mw: 0.170720
+ idle_transitions_mws: 0.013637
+ thread_name: "lowpool[5]"
+ process_name: "com.google.android.gms.persistent"
+ thread_id: 3489
+ process_id: 1949
+ }
+ task_info {
+ estimated_mws: 1.470155
+ estimated_mw: 0.169972
+ thread_name: "-Executor] idle"
+ process_name: "com.google.android.gms"
+ thread_id: 5591
+ process_id: 2856
+ }
+ task_info {
+ estimated_mws: 1.469958
+ estimated_mw: 0.169950
+ idle_transitions_mws: 0.002534
+ thread_name: "binder:1302_B"
+ process_name: "system_server"
+ thread_id: 2033
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 1.390635
+ estimated_mw: 0.160779
+ idle_transitions_mws: 0.083044
+ thread_name: "binder:755_4"
+ process_name: "/system/bin/surfaceflinger"
+ thread_id: 1125
+ process_id: 755
+ }
+ task_info {
+ estimated_mws: 1.327049
+ estimated_mw: 0.153427
+ idle_transitions_mws: 0.029246
+ thread_name: "batterystats-ha"
+ process_name: "system_server"
+ thread_id: 1484
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 1.312721
+ estimated_mw: 0.151771
+ thread_name: "statsd.writer"
+ process_name: "/apex/com.android.os.statsd/bin/statsd"
+ thread_id: 980
+ process_id: 545
+ }
+ task_info {
+ estimated_mws: 1.252738
+ estimated_mw: 0.144836
+ idle_transitions_mws: 0.705073
+ thread_name: "kworker/u8:2"
+ process_name: "kworker/u8:2"
+ thread_id: 62
+ process_id: 62
+ }
+ task_info {
+ estimated_mws: 1.251944
+ estimated_mw: 0.144744
+ idle_transitions_mws: 0.206733
+ thread_name: "app"
+ process_name: "/system/bin/surfaceflinger"
+ thread_id: 867
+ process_id: 755
+ }
+ task_info {
+ estimated_mws: 1.233674
+ estimated_mw: 0.142632
+ idle_transitions_mws: 0.066972
+ thread_name: "system_server"
+ process_name: "system_server"
+ thread_id: 1343
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 1.228813
+ estimated_mw: 0.142070
+ idle_transitions_mws: 0.785543
+ thread_name: "irq/33-4520300."
+ process_name: "irq/33-4520300.qcom,bwmon-ddr"
+ thread_id: 95
+ process_id: 95
+ }
+ task_info {
+ estimated_mws: 1.068197
+ estimated_mw: 0.123500
+ idle_transitions_mws: 0.111670
+ thread_name: "logd.klogd"
+ process_name: "/system/bin/logd"
+ thread_id: 234
+ process_id: 211
+ }
+ task_info {
+ estimated_mws: 1.007070
+ estimated_mw: 0.116433
+ idle_transitions_mws: 0.029416
+ thread_name: "android.fg"
+ process_name: "system_server"
+ thread_id: 1415
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 0.969980
+ estimated_mw: 0.112144
+ idle_transitions_mws: 0.455999
+ thread_name: "rcuog/0"
+ process_name: "rcuog/0"
+ thread_id: 15
+ process_id: 15
+ }
+ task_info {
+ estimated_mws: 0.952077
+ estimated_mw: 0.110075
+ idle_transitions_mws: 0.102993
+ thread_name: "binder:1926_3"
+ process_name: "com.google.android.wearable.sysui"
+ thread_id: 1940
+ process_id: 1926
+ }
+ task_info {
+ estimated_mws: 0.946746
+ estimated_mw: 0.109458
+ idle_transitions_mws: 0.007196
+ thread_name: "gle.android.gms"
+ process_name: "com.google.android.gms"
+ thread_id: 2856
+ process_id: 2856
+ }
+ task_info {
+ estimated_mws: 0.930774
+ estimated_mw: 0.107612
+ idle_transitions_mws: 0.738786
+ thread_name: "crtc_event:80"
+ process_name: "crtc_event:80"
+ thread_id: 245
+ process_id: 245
+ }
+ task_info {
+ estimated_mws: 0.907425
+ estimated_mw: 0.104912
+ idle_transitions_mws: 0.009862
+ thread_name: "binder:755_3"
+ process_name: "/system/bin/surfaceflinger"
+ thread_id: 1124
+ process_id: 755
+ }
+ task_info {
+ estimated_mws: 0.897620
+ estimated_mw: 0.103779
+ idle_transitions_mws: 0.007379
+ thread_name: "init"
+ process_name: "/system/bin/init"
+ thread_id: 143
+ process_id: 1
+ }
+ task_info {
+ estimated_mws: 0.880853
+ estimated_mw: 0.101840
+ idle_transitions_mws: 0.013499
+ thread_name: "wmshell.main"
+ process_name: "com.google.android.apps.wearable.systemui"
+ thread_id: 2260
+ process_id: 2171
+ }
+ task_info {
+ estimated_mws: 0.870598
+ estimated_mw: 0.100654
+ idle_transitions_mws: 0.005937
+ thread_name: "Primes-1"
+ process_name: "com.google.android.wearable.sysui"
+ thread_id: 1944
+ process_id: 1926
+ }
+ task_info {
+ estimated_mws: 0.847641
+ estimated_mw: 0.098000
+ idle_transitions_mws: 0.013271
+ thread_name: "init"
+ process_name: "/system/bin/init"
+ thread_id: 1
+ process_id: 1
+ }
+ task_info {
+ estimated_mws: 0.846054
+ estimated_mw: 0.097817
+ thread_name: "binder:1302_D"
+ process_name: "system_server"
+ thread_id: 2043
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 0.844958
+ estimated_mw: 0.097690
+ idle_transitions_mws: 0.202229
+ thread_name: "surfaceflinger"
+ process_name: "/system/bin/surfaceflinger"
+ thread_id: 786
+ process_id: 755
+ }
+ task_info {
+ estimated_mws: 0.833920
+ estimated_mw: 0.096414
+ idle_transitions_mws: 0.342121
+ thread_name: "kworker/u8:5"
+ process_name: "kworker/u8:5"
+ thread_id: 5304
+ process_id: 5304
+ }
+ task_info {
+ estimated_mws: 0.780835
+ estimated_mw: 0.090276
+ idle_transitions_mws: 0.182034
+ thread_name: "kworker/2:4"
+ process_name: "kworker/2:4"
+ thread_id: 4995
+ process_id: 4995
+ }
+ task_info {
+ estimated_mws: 0.747755
+ estimated_mw: 0.086452
+ idle_transitions_mws: 0.004701
+ thread_name: "binder:2171_4"
+ process_name: "com.google.android.apps.wearable.systemui"
+ thread_id: 2374
+ process_id: 2171
+ }
+ task_info {
+ estimated_mws: 0.746488
+ estimated_mw: 0.086305
+ idle_transitions_mws: 0.043573
+ thread_name: "binder:1999_5"
+ process_name: "com.google.android.wearable.watchface.rwf"
+ thread_id: 3678
+ process_id: 1999
+ }
+ task_info {
+ estimated_mws: 0.744159
+ estimated_mw: 0.086036
+ idle_transitions_mws: 0.120816
+ thread_name: "servicemanager"
+ process_name: "/system/bin/servicemanager"
+ thread_id: 213
+ process_id: 213
+ }
+ task_info {
+ estimated_mws: 0.717520
+ estimated_mw: 0.082956
+ idle_transitions_mws: 0.004484
+ thread_name: "wmshell.anim"
+ process_name: "com.google.android.apps.wearable.systemui"
+ thread_id: 2269
+ process_id: 2171
+ }
+ task_info {
+ estimated_mws: 0.699681
+ estimated_mw: 0.080894
+ thread_name: "GoogleApiHandle"
+ process_name: "com.google.android.gms"
+ thread_id: 3208
+ process_id: 2856
+ }
+ task_info {
+ estimated_mws: 0.675997
+ estimated_mw: 0.078156
+ idle_transitions_mws: 0.003826
+ thread_name: "binder:1302_4"
+ process_name: "system_server"
+ thread_id: 1592
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 0.647999
+ estimated_mw: 0.074919
+ thread_name: "batterystats-wo"
+ process_name: "system_server"
+ thread_id: 1487
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 0.640576
+ estimated_mw: 0.074060
+ idle_transitions_mws: 0.005443
+ thread_name: ".gms.persistent"
+ process_name: "com.google.android.gms.persistent"
+ thread_id: 1949
+ process_id: 1949
+ }
+ task_info {
+ estimated_mws: 0.631830
+ estimated_mw: 0.073049
+ idle_transitions_mws: 0.013282
+ thread_name: "binder:1926_6"
+ process_name: "com.google.android.wearable.sysui"
+ thread_id: 5211
+ process_id: 1926
+ }
+ task_info {
+ estimated_mws: 0.627672
+ estimated_mw: 0.072568
+ idle_transitions_mws: 0.000795
+ thread_name: "DisplayOffloadB"
+ process_name: "system_server"
+ thread_id: 1512
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 0.627487
+ estimated_mw: 0.072547
+ thread_name: "binder:682_2"
+ process_name: "/vendor/bin/hw/vendor.qti.hardware.display.allocator-service"
+ thread_id: 682
+ process_id: 682
+ }
+ task_info {
+ estimated_mws: 0.624294
+ estimated_mw: 0.072178
+ idle_transitions_mws: 0.431093
+ thread_name: "rcuog/2"
+ process_name: "rcuog/2"
+ thread_id: 37
+ process_id: 37
+ }
+ task_info {
+ estimated_mws: 0.623909
+ estimated_mw: 0.072133
+ idle_transitions_mws: 0.274820
+ thread_name: "kworker/0:6"
+ process_name: "kworker/0:6"
+ thread_id: 586
+ process_id: 586
+ }
+ task_info {
+ estimated_mws: 0.597177
+ estimated_mw: 0.069043
+ thread_name: "diag-router"
+ process_name: "/vendor/bin/diag-router"
+ thread_id: 634
+ process_id: 634
+ }
+ task_info {
+ estimated_mws: 0.582498
+ estimated_mw: 0.067346
+ thread_name: "HeapTaskDaemon"
+ process_name: "com.google.android.gms"
+ thread_id: 2882
+ process_id: 2856
+ }
+ task_info {
+ estimated_mws: 0.579675
+ estimated_mw: 0.067019
+ idle_transitions_mws: 0.102756
+ thread_name: "FileWatcherThre"
+ process_name: "/vendor/bin/hw/android.hardware.thermal-service.pixel"
+ thread_id: 1411
+ process_id: 1404
+ }
+ task_info {
+ estimated_mws: 0.568415
+ estimated_mw: 0.065717
+ idle_transitions_mws: 0.004498
+ thread_name: "TaskSnapshotPer"
+ process_name: "system_server"
+ thread_id: 1913
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 0.565566
+ estimated_mw: 0.065388
+ idle_transitions_mws: 0.016823
+ thread_name: "lmkd"
+ process_name: "/system/bin/lmkd"
+ thread_id: 212
+ process_id: 212
+ }
+ task_info {
+ estimated_mws: 0.554734
+ estimated_mw: 0.064136
+ idle_transitions_mws: 0.001437
+ thread_name: "binder:1949_8"
+ process_name: "com.google.android.gms.persistent"
+ thread_id: 3269
+ process_id: 1949
+ }
+ task_info {
+ estimated_mws: 0.517529
+ estimated_mw: 0.059834
+ idle_transitions_mws: 0.084239
+ thread_name: "appSf"
+ process_name: "/system/bin/surfaceflinger"
+ thread_id: 868
+ process_id: 755
+ }
+ task_info {
+ estimated_mws: 0.514221
+ estimated_mw: 0.059452
+ idle_transitions_mws: 0.542479
+ thread_name: "kworker/1:1"
+ process_name: "kworker/1:1"
+ thread_id: 47
+ process_id: 47
+ }
+ task_info {
+ estimated_mws: 0.507581
+ estimated_mw: 0.058684
+ idle_transitions_mws: 0.039847
+ thread_name: "android.hardwar"
+ process_name: "/vendor/bin/hw/android.hardware.usb-service.qti"
+ thread_id: 1861
+ process_id: 665
+ }
+ task_info {
+ estimated_mws: 0.504068
+ estimated_mw: 0.058278
+ idle_transitions_mws: 0.058645
+ thread_name: "Primes-Jank"
+ process_name: "com.google.android.wearable.sysui"
+ thread_id: 2389
+ process_id: 1926
+ }
+ task_info {
+ estimated_mws: 0.493578
+ estimated_mw: 0.057065
+ idle_transitions_mws: 0.006902
+ thread_name: "binder:2171_3"
+ process_name: "com.google.android.apps.wearable.systemui"
+ thread_id: 2235
+ process_id: 2171
+ }
+ task_info {
+ estimated_mws: 0.490345
+ estimated_mw: 0.056691
+ idle_transitions_mws: 0.005860
+ thread_name: "traced"
+ process_name: "/system/bin/traced"
+ thread_id: 905
+ process_id: 905
+ }
+ task_info {
+ estimated_mws: 0.468415
+ estimated_mw: 0.054156
+ thread_name: "eduling.default"
+ process_name: "system_server"
+ thread_id: 1761
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 0.462913
+ estimated_mw: 0.053520
+ idle_transitions_mws: 0.021445
+ thread_name: "binder:545_2"
+ process_name: "/apex/com.android.os.statsd/bin/statsd"
+ thread_id: 553
+ process_id: 545
+ }
+ task_info {
+ estimated_mws: 0.462537
+ estimated_mw: 0.053476
+ thread_name: "User"
+ process_name: "com.google.android.wearable.sysui"
+ thread_id: 2234
+ process_id: 1926
+ }
+ task_info {
+ estimated_mws: 0.454063
+ estimated_mw: 0.052497
+ idle_transitions_mws: 0.032182
+ thread_name: "putmethod.latin"
+ process_name: "com.google.android.inputmethod.latin"
+ thread_id: 4997
+ process_id: 4997
+ }
+ task_info {
+ estimated_mws: 0.450612
+ estimated_mw: 0.052098
+ thread_name: "ueventd"
+ process_name: "/system/bin/ueventd"
+ thread_id: 145
+ process_id: 145
+ }
+ task_info {
+ estimated_mws: 0.448044
+ estimated_mw: 0.051801
+ thread_name: "wpa_supplicant"
+ process_name: "/vendor/bin/hw/wpa_supplicant"
+ thread_id: 5214
+ process_id: 5214
+ }
+ task_info {
+ estimated_mws: 0.431304
+ estimated_mw: 0.049865
+ idle_transitions_mws: 0.223655
+ thread_name: "rcuop/0"
+ process_name: "rcuop/0"
+ thread_id: 16
+ process_id: 16
+ }
+ task_info {
+ estimated_mws: 0.416635
+ estimated_mw: 0.048169
+ idle_transitions_mws: 0.003314
+ thread_name: "Jit thread pool"
+ process_name: "com.google.android.wearable.sysui"
+ thread_id: 1933
+ process_id: 1926
+ }
+ task_info {
+ estimated_mws: 0.404592
+ estimated_mw: 0.046777
+ thread_name: "pixelstats-vend"
+ process_name: "/vendor/bin/pixelstats-vendor"
+ thread_id: 267
+ process_id: 255
+ }
+ task_info {
+ estimated_mws: 0.396838
+ estimated_mw: 0.045880
+ idle_transitions_mws: 0.096821
+ thread_name: "irq/236-NVT-ts"
+ process_name: "irq/236-NVT-ts"
+ thread_id: 505
+ process_id: 505
+ }
+ task_info {
+ estimated_mws: 0.393249
+ estimated_mw: 0.045466
+ idle_transitions_mws: 0.004124
+ thread_name: "nanohub"
+ process_name: "nanohub"
+ thread_id: 297
+ process_id: 297
+ }
+ task_info {
+ estimated_mws: 0.376686
+ estimated_mw: 0.043551
+ idle_transitions_mws: 0.007074
+ thread_name: "android.bg"
+ process_name: "system_server"
+ thread_id: 1430
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 0.375869
+ estimated_mw: 0.043456
+ idle_transitions_mws: 0.002342
+ thread_name: "chre"
+ process_name: "/vendor/bin/chre"
+ thread_id: 1041
+ process_id: 1041
+ }
+ task_info {
+ estimated_mws: 0.373520
+ estimated_mw: 0.043185
+ idle_transitions_mws: 0.003289
+ thread_name: "lowpool[1]"
+ process_name: "com.google.android.gms.persistent"
+ thread_id: 2279
+ process_id: 1949
+ }
+ task_info {
+ estimated_mws: 0.366001
+ estimated_mw: 0.042315
+ idle_transitions_mws: 0.018638
+ thread_name: "TracingMuxer"
+ process_name: "/system/bin/surfaceflinger"
+ thread_id: 783
+ process_id: 755
+ }
+ task_info {
+ estimated_mws: 0.359494
+ estimated_mw: 0.041563
+ idle_transitions_mws: 0.136611
+ thread_name: "kgsl-events"
+ process_name: "kgsl-events"
+ thread_id: 109
+ process_id: 109
+ }
+ task_info {
+ estimated_mws: 0.359130
+ estimated_mw: 0.041521
+ thread_name: "IpClient.wlan0"
+ process_name: "com.android.networkstack.process"
+ thread_id: 5216
+ process_id: 2049
+ }
+ task_info {
+ estimated_mws: 0.346517
+ estimated_mw: 0.040063
+ idle_transitions_mws: 0.001877
+ thread_name: "binder:257_5"
+ process_name: "/system/bin/hw/android.system.suspend-service"
+ thread_id: 1491
+ process_id: 257
+ }
+ task_info {
+ estimated_mws: 0.341200
+ estimated_mw: 0.039448
+ idle_transitions_mws: 0.001228
+ thread_name: "binder:1901_3"
+ process_name: "/vendor/bin/hw/android.hardware.wifi-service-lazy"
+ thread_id: 1905
+ process_id: 1901
+ }
+ task_info {
+ estimated_mws: 0.335534
+ estimated_mw: 0.038793
+ thread_name: "binder:740_1"
+ process_name: "/system/bin/audioserver"
+ thread_id: 821
+ process_id: 740
+ }
+ task_info {
+ estimated_mws: 0.331405
+ estimated_mw: 0.038316
+ idle_transitions_mws: 0.001044
+ thread_name: "BG"
+ process_name: "com.google.wear.services"
+ thread_id: 2023
+ process_id: 1948
+ }
+ task_info {
+ estimated_mws: 0.326344
+ estimated_mw: 0.037730
+ idle_transitions_mws: 0.045342
+ thread_name: "kworker/0:5H"
+ process_name: "kworker/0:5H"
+ thread_id: 1337
+ process_id: 1337
+ }
+ task_info {
+ estimated_mws: 0.322384
+ estimated_mw: 0.037273
+ idle_transitions_mws: 0.002751
+ thread_name: "binder:755_2"
+ process_name: "/system/bin/surfaceflinger"
+ thread_id: 784
+ process_id: 755
+ }
+ task_info {
+ estimated_mws: 0.319511
+ estimated_mw: 0.036940
+ thread_name: "audioserver"
+ process_name: "/system/bin/audioserver"
+ thread_id: 740
+ process_id: 740
+ }
+ task_info {
+ estimated_mws: 0.310996
+ estimated_mw: 0.035956
+ idle_transitions_mws: 0.007033
+ thread_name: "binder:1949_2"
+ process_name: "com.google.android.gms.persistent"
+ thread_id: 1978
+ process_id: 1949
+ }
+ task_info {
+ estimated_mws: 0.302115
+ estimated_mw: 0.034929
+ idle_transitions_mws: 0.000843
+ thread_name: "-Executor] idle"
+ process_name: "com.google.android.gms.persistent"
+ thread_id: 5602
+ process_id: 1949
+ }
+ task_info {
+ estimated_mws: 0.301578
+ estimated_mw: 0.034867
+ thread_name: "pool-11-thread-"
+ process_name: "com.google.android.wearable.healthservices"
+ thread_id: 3329
+ process_id: 3028
+ }
+ task_info {
+ estimated_mws: 0.299169
+ estimated_mw: 0.034589
+ thread_name: "android.io"
+ process_name: "system_server"
+ thread_id: 1417
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 0.296825
+ estimated_mw: 0.034318
+ thread_name: "binder:1901_3"
+ process_name: "/vendor/bin/hw/android.hardware.wifi-service-lazy"
+ thread_id: 5205
+ process_id: 1901
+ }
+ task_info {
+ estimated_mws: 0.294242
+ estimated_mw: 0.034019
+ idle_transitions_mws: 0.142357
+ thread_name: "rcuop/1"
+ process_name: "rcuop/1"
+ thread_id: 30
+ process_id: 30
+ }
+ task_info {
+ estimated_mws: 0.286642
+ estimated_mw: 0.033140
+ thread_name: "binder:1948_6"
+ process_name: "com.google.wear.services"
+ thread_id: 5315
+ process_id: 1948
+ }
+ task_info {
+ estimated_mws: 0.285983
+ estimated_mw: 0.033064
+ thread_name: "AssistantHandle"
+ process_name: "com.google.android.wearable.assistant"
+ thread_id: 4081
+ process_id: 4038
+ }
+ task_info {
+ estimated_mws: 0.283378
+ estimated_mw: 0.032763
+ idle_transitions_mws: 0.023300
+ thread_name: "binder:1999_1"
+ process_name: "com.google.android.wearable.watchface.rwf"
+ thread_id: 2016
+ process_id: 1999
+ }
+ task_info {
+ estimated_mws: 0.279959
+ estimated_mw: 0.032367
+ idle_transitions_mws: 0.001981
+ thread_name: "binder:2182_7"
+ process_name: "com.android.phone"
+ thread_id: 2694
+ process_id: 2182
+ }
+ task_info {
+ estimated_mws: 0.279816
+ estimated_mw: 0.032351
+ idle_transitions_mws: 0.055476
+ thread_name: "kworker/3:2H"
+ process_name: "kworker/3:2H"
+ thread_id: 226
+ process_id: 226
+ }
+ task_info {
+ estimated_mws: 0.277230
+ estimated_mw: 0.032052
+ idle_transitions_mws: 0.002913
+ thread_name: "BG"
+ process_name: "com.google.android.wearable.sysui"
+ thread_id: 3005
+ process_id: 1926
+ }
+ task_info {
+ estimated_mws: 0.274735
+ estimated_mw: 0.031764
+ thread_name: "lowpool[3]"
+ process_name: "com.google.android.gms"
+ thread_id: 3527
+ process_id: 2856
+ }
+ task_info {
+ estimated_mws: 0.267749
+ estimated_mw: 0.030956
+ idle_transitions_mws: 0.038805
+ thread_name: "hvdcp_opti"
+ process_name: "/vendor/bin/hvdcp_opti"
+ thread_id: 1276
+ process_id: 1270
+ }
+ task_info {
+ estimated_mws: 0.262081
+ estimated_mw: 0.030301
+ idle_transitions_mws: 0.190243
+ thread_name: "binder:1926_3"
+ process_name: "com.google.android.wearable.sysui"
+ thread_id: 2022
+ process_id: 1926
+ }
+ task_info {
+ estimated_mws: 0.259248
+ estimated_mw: 0.029973
+ idle_transitions_mws: 0.172554
+ thread_name: "kworker/3:5"
+ process_name: "kworker/3:5"
+ thread_id: 104
+ process_id: 104
+ }
+ task_info {
+ estimated_mws: 0.256714
+ estimated_mw: 0.029680
+ idle_transitions_mws: 0.002836
+ thread_name: "binder:257_2"
+ process_name: "/system/bin/hw/android.system.suspend-service"
+ thread_id: 264
+ process_id: 257
+ }
+ task_info {
+ estimated_mws: 0.247037
+ estimated_mw: 0.028561
+ idle_transitions_mws: 0.088455
+ thread_name: "SDM_EventThread"
+ process_name: "/vendor/bin/hw/vendor.qti.hardware.display.composer-service"
+ thread_id: 727
+ process_id: 685
+ }
+ task_info {
+ estimated_mws: 0.244112
+ estimated_mw: 0.028223
+ thread_name: "POSIX timer 2"
+ process_name: "/vendor/bin/hw/android.hardware.sensors-service.multihal"
+ thread_id: 1600
+ process_id: 664
+ }
+ task_info {
+ estimated_mws: 0.242754
+ estimated_mw: 0.028066
+ idle_transitions_mws: 0.005424
+ thread_name: "binder:2856_4"
+ process_name: "com.google.android.gms"
+ thread_id: 3679
+ process_id: 2856
+ }
+ task_info {
+ estimated_mws: 0.241348
+ estimated_mw: 0.027904
+ thread_name: "pool-2-thread-1"
+ process_name: "com.android.networkstack.process"
+ thread_id: 2416
+ process_id: 2049
+ }
+ task_info {
+ estimated_mws: 0.231145
+ estimated_mw: 0.026724
+ idle_transitions_mws: 0.081316
+ thread_name: "rcuop/3"
+ process_name: "rcuop/3"
+ thread_id: 45
+ process_id: 45
+ }
+ task_info {
+ estimated_mws: 0.230341
+ estimated_mw: 0.026631
+ idle_transitions_mws: 0.003605
+ thread_name: "f2fs_ckpt-254:4"
+ process_name: "f2fs_ckpt-254:43"
+ thread_id: 347
+ process_id: 347
+ }
+ task_info {
+ estimated_mws: 0.229722
+ estimated_mw: 0.026559
+ thread_name: "OomAdjuster"
+ process_name: "system_server"
+ thread_id: 1482
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 0.226417
+ estimated_mw: 0.026177
+ idle_transitions_mws: 0.001358
+ thread_name: "binder:740_6"
+ process_name: "/system/bin/audioserver"
+ thread_id: 2639
+ process_id: 740
+ }
+ task_info {
+ estimated_mws: 0.226007
+ estimated_mw: 0.026130
+ idle_transitions_mws: 0.054115
+ thread_name: "rcuop/2"
+ process_name: "rcuop/2"
+ thread_id: 38
+ process_id: 38
+ }
+ task_info {
+ estimated_mws: 0.225133
+ estimated_mw: 0.026029
+ idle_transitions_mws: 0.065811
+ thread_name: "kworker/0:7"
+ process_name: "kworker/0:7"
+ thread_id: 598
+ process_id: 598
+ }
+ task_info {
+ estimated_mws: 0.212788
+ estimated_mw: 0.024602
+ idle_transitions_mws: 0.006439
+ thread_name: "queued-work-loo"
+ process_name: "system_server"
+ thread_id: 1886
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 0.202562
+ estimated_mw: 0.023419
+ thread_name: "pool-13-thread-"
+ process_name: "com.google.android.wearable.healthservices"
+ thread_id: 3327
+ process_id: 3028
+ }
+ task_info {
+ estimated_mws: 0.201687
+ estimated_mw: 0.023318
+ idle_transitions_mws: 0.001195
+ thread_name: "WearSdkThread"
+ process_name: "com.google.android.wearable.sysui"
+ thread_id: 2207
+ process_id: 1926
+ }
+ task_info {
+ estimated_mws: 0.201119
+ estimated_mw: 0.023252
+ thread_name: "qrtr_ns"
+ process_name: "qrtr_ns"
+ thread_id: 88
+ process_id: 88
+ }
+ task_info {
+ estimated_mws: 0.200639
+ estimated_mw: 0.023197
+ thread_name: "binder:740_7"
+ process_name: "/system/bin/audioserver"
+ thread_id: 5206
+ process_id: 740
+ }
+ task_info {
+ estimated_mws: 0.196587
+ estimated_mw: 0.022729
+ idle_transitions_mws: 0.002857
+ thread_name: "binder:4997_4"
+ process_name: "com.google.android.inputmethod.latin"
+ thread_id: 5122
+ process_id: 4997
+ }
+ task_info {
+ estimated_mws: 0.194754
+ estimated_mw: 0.022517
+ thread_name: "m.android.phone"
+ process_name: "com.android.phone"
+ thread_id: 2182
+ process_id: 2182
+ }
+ task_info {
+ estimated_mws: 0.192336
+ estimated_mw: 0.022237
+ idle_transitions_mws: 0.006386
+ thread_name: "HwcAsyncWorker"
+ process_name: "/system/bin/surfaceflinger"
+ thread_id: 835
+ process_id: 755
+ }
+ task_info {
+ estimated_mws: 0.190522
+ estimated_mw: 0.022027
+ thread_name: "binder:636_2"
+ process_name: "/vendor/bin/hw/android.hardware.audio.service"
+ thread_id: 636
+ process_id: 636
+ }
+ task_info {
+ estimated_mws: 0.188908
+ estimated_mw: 0.021841
+ thread_name: "SettingsProvide"
+ process_name: "system_server"
+ thread_id: 1771
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 0.181172
+ estimated_mw: 0.020946
+ thread_name: "binder:1302_2"
+ process_name: "system_server"
+ thread_id: 1350
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 0.177579
+ estimated_mw: 0.020531
+ idle_transitions_mws: 0.111479
+ thread_name: "RegSampIdle"
+ process_name: "/system/bin/surfaceflinger"
+ thread_id: 872
+ process_id: 755
+ }
+ task_info {
+ estimated_mws: 0.168738
+ estimated_mw: 0.019509
+ thread_name: "ice] processing"
+ process_name: "com.google.android.gms"
+ thread_id: 3238
+ process_id: 2856
+ }
+ task_info {
+ estimated_mws: 0.162808
+ estimated_mw: 0.018823
+ thread_name: "binder:682_3"
+ process_name: "/vendor/bin/hw/vendor.qti.hardware.display.allocator-service"
+ thread_id: 2308
+ process_id: 682
+ }
+ task_info {
+ estimated_mws: 0.158857
+ estimated_mw: 0.018366
+ thread_name: "binder:678_3"
+ process_name: "/apex/com.google.wearable.wac.whshal/bin/hw/vendor.google.wearable.wac.whshal@2.0-service"
+ thread_id: 1884
+ process_id: 678
+ }
+ task_info {
+ estimated_mws: 0.158692
+ estimated_mw: 0.018347
+ idle_transitions_mws: 0.016263
+ thread_name: "binder:650_4"
+ process_name: "/vendor/bin/hw/android.hardware.gnss-aidl-service-qti"
+ thread_id: 5498
+ process_id: 650
+ }
+ task_info {
+ estimated_mws: 0.157956
+ estimated_mw: 0.018262
+ idle_transitions_mws: 0.002306
+ thread_name: "vndservicemanag"
+ process_name: "/vendor/bin/vndservicemanager"
+ thread_id: 215
+ process_id: 215
+ }
+ task_info {
+ estimated_mws: 0.153600
+ estimated_mw: 0.017759
+ thread_name: "GoogleLocationS"
+ process_name: "com.google.android.gms.persistent"
+ thread_id: 3355
+ process_id: 1949
+ }
+ task_info {
+ estimated_mws: 0.153038
+ estimated_mw: 0.017694
+ thread_name: "TransportThread"
+ process_name: "/vendor/bin/chre"
+ thread_id: 1078
+ process_id: 1041
+ }
+ task_info {
+ estimated_mws: 0.152058
+ estimated_mw: 0.017580
+ idle_transitions_mws: 0.068137
+ thread_name: "kworker/2:1H"
+ process_name: "kworker/2:1H"
+ thread_id: 123
+ process_id: 123
+ }
+ task_info {
+ estimated_mws: 0.148559
+ estimated_mw: 0.017176
+ idle_transitions_mws: 0.002115
+ thread_name: "BG"
+ process_name: "com.google.android.wearable.sysui"
+ thread_id: 2120
+ process_id: 1926
+ }
+ task_info {
+ estimated_mws: 0.148352
+ estimated_mw: 0.017152
+ thread_name: "vendor.google.w"
+ process_name: "/apex/com.google.wearable.wac.whshal/bin/hw/vendor.google.wearable.wac.whshal@2.0-service"
+ thread_id: 1881
+ process_id: 678
+ }
+ task_info {
+ estimated_mws: 0.140864
+ estimated_mw: 0.016286
+ thread_name: "NetworkStats"
+ process_name: "system_server"
+ thread_id: 1814
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 0.138579
+ estimated_mw: 0.016022
+ idle_transitions_mws: 0.021968
+ thread_name: "binder:969_2"
+ process_name: "/system/vendor/bin/cnd"
+ thread_id: 1011
+ process_id: 969
+ }
+ task_info {
+ estimated_mws: 0.134607
+ estimated_mw: 0.015563
+ idle_transitions_mws: 0.010586
+ thread_name: "dmabuf-deferred"
+ process_name: "dmabuf-deferred-free-worker"
+ thread_id: 69
+ process_id: 69
+ }
+ task_info {
+ estimated_mws: 0.129449
+ estimated_mw: 0.014966
+ thread_name: "highpool[5]"
+ process_name: "com.google.android.gms.persistent"
+ thread_id: 3354
+ process_id: 1949
+ }
+ task_info {
+ estimated_mws: 0.126973
+ estimated_mw: 0.014680
+ thread_name: "ice] processing"
+ process_name: "com.google.android.gms.persistent"
+ thread_id: 2363
+ process_id: 1949
+ }
+ task_info {
+ estimated_mws: 0.126830
+ estimated_mw: 0.014663
+ thread_name: "ediator.Toggler"
+ process_name: "system_server"
+ thread_id: 1910
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 0.125742
+ estimated_mw: 0.014538
+ idle_transitions_mws: 0.064827
+ thread_name: "surfaceflinger"
+ process_name: "/system/bin/surfaceflinger"
+ thread_id: 875
+ process_id: 755
+ }
+ task_info {
+ estimated_mws: 0.123833
+ estimated_mw: 0.014317
+ thread_name: "wificond"
+ process_name: "/system/bin/wificond"
+ thread_id: 964
+ process_id: 964
+ }
+ task_info {
+ estimated_mws: 0.123248
+ estimated_mw: 0.014249
+ thread_name: "MobileDataStats"
+ process_name: "system_server"
+ thread_id: 1912
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 0.119885
+ estimated_mw: 0.013860
+ thread_name: "GlobalScheduler"
+ process_name: "com.google.android.gms"
+ thread_id: 3156
+ process_id: 2856
+ }
+ task_info {
+ estimated_mws: 0.119479
+ estimated_mw: 0.013814
+ idle_transitions_mws: 0.000930
+ thread_name: "RenderThread"
+ thread_id: 5599
+ }
+ task_info {
+ estimated_mws: 0.119243
+ estimated_mw: 0.013786
+ idle_transitions_mws: 0.008398
+ thread_name: "TouchTimer"
+ process_name: "/system/bin/surfaceflinger"
+ thread_id: 866
+ process_id: 755
+ }
+ task_info {
+ estimated_mws: 0.114249
+ estimated_mw: 0.013209
+ thread_name: "binder:4038_1"
+ process_name: "com.google.android.wearable.assistant"
+ thread_id: 4050
+ process_id: 4038
+ }
+ task_info {
+ estimated_mws: 0.112705
+ estimated_mw: 0.013030
+ idle_transitions_mws: 0.011129
+ thread_name: "displayoffload@"
+ process_name: "/vendor/bin/hw/vendor.google_clockwork.displayoffload@2.0-service.1p"
+ thread_id: 937
+ process_id: 937
+ }
+ task_info {
+ estimated_mws: 0.111279
+ estimated_mw: 0.012866
+ idle_transitions_mws: 0.003661
+ thread_name: "adbd"
+ process_name: "/apex/com.android.adbd/bin/adbd"
+ thread_id: 5544
+ process_id: 5544
+ }
+ task_info {
+ estimated_mws: 0.108114
+ estimated_mw: 0.012500
+ thread_name: "kworker/1:2H"
+ process_name: "kworker/1:2H"
+ thread_id: 300
+ process_id: 300
+ }
+ task_info {
+ estimated_mws: 0.107442
+ estimated_mw: 0.012422
+ idle_transitions_mws: 0.003376
+ thread_name: "RenderThread"
+ thread_id: 5584
+ }
+ task_info {
+ estimated_mws: 0.105863
+ estimated_mw: 0.012239
+ idle_transitions_mws: 0.006458
+ thread_name: "Primes-2"
+ process_name: "com.google.android.wearable.sysui"
+ thread_id: 1946
+ process_id: 1926
+ }
+ task_info {
+ estimated_mws: 0.104306
+ estimated_mw: 0.012059
+ thread_name: "iptables-restor"
+ process_name: "/system/bin/iptables-restore"
+ thread_id: 558
+ process_id: 558
+ }
+ task_info {
+ estimated_mws: 0.104093
+ estimated_mw: 0.012035
+ thread_name: "RenderThread"
+ process_name: "system_server"
+ thread_id: 5223
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 0.102912
+ estimated_mw: 0.011898
+ idle_transitions_mws: 0.006649
+ thread_name: "irq/168-nanohub"
+ process_name: "irq/168-nanohub-irq1"
+ thread_id: 296
+ process_id: 296
+ }
+ task_info {
+ estimated_mws: 0.102167
+ estimated_mw: 0.011812
+ idle_transitions_mws: 0.003890
+ thread_name: "RenderThread"
+ thread_id: 5604
+ }
+ task_info {
+ estimated_mws: 0.101945
+ estimated_mw: 0.011786
+ thread_name: "ksoftirqd/2"
+ process_name: "ksoftirqd/2"
+ thread_id: 34
+ process_id: 34
+ }
+ task_info {
+ estimated_mws: 0.101282
+ estimated_mw: 0.011710
+ thread_name: "PhotonicModulat"
+ process_name: "system_server"
+ thread_id: 1899
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 0.100285
+ estimated_mw: 0.011595
+ thread_name: "ip6tables-resto"
+ process_name: "/system/bin/ip6tables-restore"
+ thread_id: 559
+ process_id: 559
+ }
+ task_info {
+ estimated_mws: 0.099432
+ estimated_mw: 0.011496
+ thread_name: "init"
+ process_name: "/system/bin/init"
+ thread_id: 144
+ process_id: 144
+ }
+ task_info {
+ estimated_mws: 0.096314
+ estimated_mw: 0.011135
+ thread_name: "FrameworkReceiv"
+ process_name: ".qtidataservices"
+ thread_id: 2793
+ process_id: 2118
+ }
+ task_info {
+ estimated_mws: 0.095442
+ estimated_mw: 0.011035
+ thread_name: "Jit thread pool"
+ process_name: "com.google.android.gms.persistent"
+ thread_id: 1969
+ process_id: 1949
+ }
+ task_info {
+ estimated_mws: 0.094448
+ estimated_mw: 0.010920
+ thread_name: "pool-14-thread-"
+ process_name: "com.google.android.wearable.healthservices"
+ thread_id: 3314
+ process_id: 3028
+ }
+ task_info {
+ estimated_mws: 0.093937
+ estimated_mw: 0.010861
+ thread_name: "binder:1926_2"
+ process_name: "com.google.android.wearable.sysui"
+ thread_id: 1939
+ process_id: 1926
+ }
+ task_info {
+ estimated_mws: 0.093621
+ estimated_mw: 0.010824
+ thread_name: "ChreMsgHandler"
+ process_name: "/vendor/bin/chre"
+ thread_id: 1080
+ process_id: 1041
+ }
+ task_info {
+ estimated_mws: 0.091738
+ estimated_mw: 0.010606
+ idle_transitions_mws: 0.001106
+ thread_name: "DispatcherModul"
+ process_name: "/vendor/bin/hw/qcrilNrd"
+ thread_id: 1673
+ process_id: 1062
+ }
+ task_info {
+ estimated_mws: 0.091698
+ estimated_mw: 0.010602
+ idle_transitions_mws: 0.047196
+ thread_name: "irq/234-pixart_"
+ process_name: "irq/234-pixart_pat9126_irq"
+ thread_id: 500
+ process_id: 500
+ }
+ task_info {
+ estimated_mws: 0.090883
+ estimated_mw: 0.010507
+ thread_name: "scheduler_threa"
+ process_name: "scheduler_thread"
+ thread_id: 5198
+ process_id: 5198
+ }
+ task_info {
+ estimated_mws: 0.089001
+ estimated_mw: 0.010290
+ thread_name: "binder:2085_4"
+ process_name: "com.google.android.bluetooth"
+ thread_id: 2713
+ process_id: 2085
+ }
+ task_info {
+ estimated_mws: 0.086934
+ estimated_mw: 0.010051
+ idle_transitions_mws: 0.001387
+ thread_name: "binder:3028_5"
+ process_name: "com.google.android.wearable.healthservices"
+ thread_id: 5434
+ process_id: 3028
+ }
+ task_info {
+ estimated_mws: 0.085941
+ estimated_mw: 0.009936
+ thread_name: "binder:2670_6"
+ process_name: "com.android.nfc"
+ thread_id: 3159
+ process_id: 2670
+ }
+ task_info {
+ estimated_mws: 0.083859
+ estimated_mw: 0.009695
+ idle_transitions_mws: 0.289424
+ thread_name: "psimon"
+ process_name: "psimon"
+ thread_id: 1480
+ process_id: 1480
+ }
+ task_info {
+ estimated_mws: 0.083773
+ estimated_mw: 0.009685
+ thread_name: "binder:233_2"
+ process_name: "/system/bin/vold"
+ thread_id: 252
+ process_id: 233
+ }
+ task_info {
+ estimated_mws: 0.080959
+ estimated_mw: 0.009360
+ thread_name: "binder:2049_2"
+ process_name: "com.android.networkstack.process"
+ thread_id: 2068
+ process_id: 2049
+ }
+ task_info {
+ estimated_mws: 0.080298
+ estimated_mw: 0.009284
+ thread_name: "netd"
+ process_name: "/system/bin/netd"
+ thread_id: 568
+ process_id: 546
+ }
+ task_info {
+ estimated_mws: 0.080265
+ estimated_mw: 0.009280
+ thread_name: "UEventObserver"
+ process_name: "system_server"
+ thread_id: 1857
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 0.079337
+ estimated_mw: 0.009173
+ thread_name: "RenderThread"
+ thread_id: 5619
+ }
+ task_info {
+ estimated_mws: 0.078696
+ estimated_mw: 0.009098
+ thread_name: "pool-8-thread-1"
+ process_name: "com.google.android.gms"
+ thread_id: 3102
+ process_id: 2856
+ }
+ task_info {
+ estimated_mws: 0.077362
+ estimated_mw: 0.008944
+ idle_transitions_mws: 0.019002
+ thread_name: "mcu_mgmtd"
+ process_name: "/vendor/bin/mcu_mgmtd"
+ thread_id: 594
+ process_id: 524
+ }
+ task_info {
+ estimated_mws: 0.074501
+ estimated_mw: 0.008613
+ thread_name: "spi0"
+ process_name: "spi0"
+ thread_id: 295
+ process_id: 295
+ }
+ task_info {
+ estimated_mws: 0.073914
+ estimated_mw: 0.008546
+ thread_name: "com.android.nfc"
+ process_name: "com.android.nfc"
+ thread_id: 2670
+ process_id: 2670
+ }
+ task_info {
+ estimated_mws: 0.072671
+ estimated_mw: 0.008402
+ idle_transitions_mws: 0.063131
+ thread_name: "rcu_exp_gp_kthr"
+ process_name: "rcu_exp_gp_kthread_worker"
+ thread_id: 19
+ process_id: 19
+ }
+ task_info {
+ estimated_mws: 0.070587
+ estimated_mw: 0.008161
+ idle_transitions_mws: 0.005993
+ thread_name: "adbd"
+ process_name: "/apex/com.android.adbd/bin/adbd"
+ thread_id: 5546
+ process_id: 5544
+ }
+ task_info {
+ estimated_mws: 0.070105
+ estimated_mw: 0.008105
+ thread_name: "servicemanager"
+ thread_id: 5598
+ }
+ task_info {
+ estimated_mws: 0.069214
+ estimated_mw: 0.008002
+ idle_transitions_mws: 0.001447
+ thread_name: "android.imms"
+ process_name: "system_server"
+ thread_id: 1791
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 0.068600
+ estimated_mw: 0.007931
+ thread_name: "lowpool[2]"
+ process_name: "com.google.android.gms.persistent"
+ thread_id: 2321
+ process_id: 1949
+ }
+ task_info {
+ estimated_mws: 0.068467
+ estimated_mw: 0.007916
+ thread_name: "FileObserver"
+ process_name: "com.google.android.gms"
+ thread_id: 3035
+ process_id: 2856
+ }
+ task_info {
+ estimated_mws: 0.068316
+ estimated_mw: 0.007898
+ thread_name: "binder:546_3"
+ process_name: "/system/bin/netd"
+ thread_id: 546
+ process_id: 546
+ }
+ task_info {
+ estimated_mws: 0.066410
+ estimated_mw: 0.007678
+ thread_name: "pool-51-thread-"
+ process_name: "com.google.android.gms"
+ thread_id: 4215
+ process_id: 2856
+ }
+ task_info {
+ estimated_mws: 0.064761
+ estimated_mw: 0.007487
+ thread_name: "AudioService"
+ process_name: "system_server"
+ thread_id: 1844
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 0.064339
+ estimated_mw: 0.007439
+ idle_transitions_mws: 0.003289
+ thread_name: "adbd"
+ process_name: "/apex/com.android.adbd/bin/adbd"
+ thread_id: 5545
+ process_id: 5544
+ }
+ task_info {
+ estimated_mws: 0.063188
+ estimated_mw: 0.007305
+ thread_name: "droid.bluetooth"
+ process_name: "com.google.android.bluetooth"
+ thread_id: 2085
+ process_id: 2085
+ }
+ task_info {
+ estimated_mws: 0.061069
+ estimated_mw: 0.007061
+ thread_name: "msm_irqbalance"
+ process_name: "/vendor/bin/msm_irqbalance"
+ thread_id: 2466
+ process_id: 2466
+ }
+ task_info {
+ estimated_mws: 0.060920
+ estimated_mw: 0.007043
+ idle_transitions_mws: 0.003380
+ thread_name: "BackgroundInsta"
+ process_name: "system_server"
+ thread_id: 1875
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 0.059901
+ estimated_mw: 0.006925
+ idle_transitions_mws: 0.008904
+ thread_name: "ConnectivitySer"
+ process_name: "system_server"
+ thread_id: 1827
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 0.059295
+ estimated_mw: 0.006855
+ idle_transitions_mws: 0.018518
+ thread_name: "pool-1-thread-1"
+ process_name: "system_server"
+ thread_id: 1873
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 0.059196
+ estimated_mw: 0.006844
+ thread_name: "binder:3028_1"
+ process_name: "com.google.android.wearable.healthservices"
+ thread_id: 3049
+ process_id: 3028
+ }
+ task_info {
+ estimated_mws: 0.058807
+ estimated_mw: 0.006799
+ idle_transitions_mws: 0.001108
+ thread_name: "roid.apps.scone"
+ process_name: "com.google.android.apps.scone"
+ thread_id: 5245
+ process_id: 5245
+ }
+ task_info {
+ estimated_mws: 0.058053
+ estimated_mw: 0.006712
+ thread_name: "UsbFfs-worker"
+ process_name: "/apex/com.android.adbd/bin/adbd"
+ thread_id: 5560
+ process_id: 5544
+ }
+ task_info {
+ estimated_mws: 0.057400
+ estimated_mw: 0.006636
+ idle_transitions_mws: 0.034371
+ thread_name: "android.hardwar"
+ process_name: "/vendor/bin/hw/android.hardware.health-service.eos"
+ thread_id: 1271
+ process_id: 1271
+ }
+ task_info {
+ estimated_mws: 0.056947
+ estimated_mw: 0.006584
+ idle_transitions_mws: 0.002094
+ thread_name: "bgres-controlle"
+ process_name: "system_server"
+ thread_id: 1495
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 0.056879
+ estimated_mw: 0.006576
+ thread_name: "netd"
+ process_name: "/system/bin/netd"
+ thread_id: 569
+ process_id: 546
+ }
+ task_info {
+ estimated_mws: 0.055642
+ estimated_mw: 0.006433
+ thread_name: "system_server"
+ thread_id: 5590
+ }
+ task_info {
+ estimated_mws: 0.054764
+ estimated_mw: 0.006332
+ thread_name: "binder:2049_4"
+ process_name: "com.android.networkstack.process"
+ thread_id: 2083
+ process_id: 2049
+ }
+ task_info {
+ estimated_mws: 0.053127
+ estimated_mw: 0.006142
+ thread_name: "binder:1949_4"
+ process_name: "com.google.android.gms.persistent"
+ thread_id: 2302
+ process_id: 1949
+ }
+ task_info {
+ estimated_mws: 0.052984
+ estimated_mw: 0.006126
+ idle_transitions_mws: 0.001354
+ thread_name: "oid.grilservice"
+ process_name: "com.google.android.grilservice"
+ thread_id: 2129
+ process_id: 2129
+ }
+ task_info {
+ estimated_mws: 0.052980
+ estimated_mw: 0.006125
+ thread_name: "binder:2856_9"
+ process_name: "com.google.android.gms"
+ thread_id: 5585
+ process_id: 2856
+ }
+ task_info {
+ estimated_mws: 0.052715
+ estimated_mw: 0.006095
+ thread_name: "StateService"
+ process_name: "com.google.android.apps.scone"
+ thread_id: 5269
+ process_id: 5245
+ }
+ task_info {
+ estimated_mws: 0.052232
+ estimated_mw: 0.006039
+ thread_name: "vndservicemanag"
+ thread_id: 5597
+ }
+ task_info {
+ estimated_mws: 0.052027
+ estimated_mw: 0.006015
+ thread_name: "binder:1949_9"
+ process_name: "com.google.android.gms.persistent"
+ thread_id: 3359
+ process_id: 1949
+ }
+ task_info {
+ estimated_mws: 0.051717
+ estimated_mw: 0.005979
+ idle_transitions_mws: 0.019339
+ thread_name: "Ipc-5004:1"
+ process_name: "/vendor/bin/hw/android.hardware.gnss-aidl-service-qti"
+ thread_id: 5483
+ process_id: 650
+ }
+ task_info {
+ estimated_mws: 0.049546
+ estimated_mw: 0.005728
+ thread_name: "PackageManager"
+ process_name: "system_server"
+ thread_id: 1530
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 0.048227
+ estimated_mw: 0.005576
+ thread_name: "android.hardwar"
+ process_name: "/vendor/bin/hw/android.hardware.power-service"
+ thread_id: 660
+ process_id: 660
+ }
+ task_info {
+ estimated_mws: 0.047454
+ estimated_mw: 0.005486
+ thread_name: "BluetoothScanMa"
+ process_name: "com.google.android.bluetooth"
+ thread_id: 2609
+ process_id: 2085
+ }
+ task_info {
+ estimated_mws: 0.046472
+ estimated_mw: 0.005373
+ thread_name: "subsystem_ramdu"
+ process_name: "/system/vendor/bin/subsystem_ramdump"
+ thread_id: 816
+ process_id: 799
+ }
+ task_info {
+ estimated_mws: 0.046295
+ estimated_mw: 0.005352
+ thread_name: "Ipc-5004:2"
+ process_name: "/vendor/bin/hw/android.hardware.gnss-aidl-service-qti"
+ thread_id: 5484
+ process_id: 650
+ }
+ task_info {
+ estimated_mws: 0.045805
+ estimated_mw: 0.005296
+ thread_name: "binder:975_2"
+ process_name: "/vendor/bin/imsdaemon"
+ thread_id: 1047
+ process_id: 975
+ }
+ task_info {
+ estimated_mws: 0.045282
+ estimated_mw: 0.005235
+ idle_transitions_mws: 0.000972
+ thread_name: ".healthservices"
+ process_name: "com.google.android.wearable.healthservices"
+ thread_id: 3028
+ process_id: 3028
+ }
+ task_info {
+ estimated_mws: 0.045087
+ estimated_mw: 0.005213
+ idle_transitions_mws: 0.001817
+ thread_name: "queued-work-loo"
+ process_name: "com.google.android.gms"
+ thread_id: 3236
+ process_id: 2856
+ }
+ task_info {
+ estimated_mws: 0.043921
+ estimated_mw: 0.005078
+ thread_name: "powerstateservi"
+ process_name: "/vendor/bin/hw/vendor.qti.hardware.powerstateservice@1.0-service"
+ thread_id: 276
+ process_id: 269
+ }
+ task_info {
+ estimated_mws: 0.043653
+ estimated_mw: 0.005047
+ idle_transitions_mws: 0.045128
+ thread_name: "wlan_logging_th"
+ process_name: "wlan_logging_thread"
+ thread_id: 368
+ process_id: 368
+ }
+ task_info {
+ estimated_mws: 0.043574
+ estimated_mw: 0.005038
+ thread_name: "FlpThread"
+ process_name: "com.google.android.gms.persistent"
+ thread_id: 3279
+ process_id: 1949
+ }
+ task_info {
+ estimated_mws: 0.043436
+ estimated_mw: 0.005022
+ thread_name: "Light-P0-2"
+ process_name: "com.google.android.inputmethod.latin"
+ thread_id: 5071
+ process_id: 4997
+ }
+ task_info {
+ estimated_mws: 0.042985
+ estimated_mw: 0.004970
+ idle_transitions_mws: 0.004250
+ thread_name: "binder:5245_4"
+ process_name: "com.google.android.apps.scone"
+ thread_id: 5270
+ process_id: 5245
+ }
+ task_info {
+ estimated_mws: 0.042946
+ estimated_mw: 0.004965
+ thread_name: "HWC_UeventThrea"
+ process_name: "/vendor/bin/hw/vendor.qti.hardware.display.composer-service"
+ thread_id: 717
+ process_id: 685
+ }
+ task_info {
+ estimated_mws: 0.042762
+ estimated_mw: 0.004944
+ thread_name: "pool-12-thread-"
+ process_name: "com.google.android.wearable.sysui"
+ thread_id: 2371
+ process_id: 1926
+ }
+ task_info {
+ estimated_mws: 0.042752
+ estimated_mw: 0.004943
+ thread_name: "android.hardwar"
+ process_name: "/vendor/bin/hw/android.hardware.contexthub-service.wac"
+ thread_id: 668
+ process_id: 644
+ }
+ task_info {
+ estimated_mws: 0.042468
+ estimated_mw: 0.004910
+ thread_name: "binder:1948_3"
+ process_name: "com.google.wear.services"
+ thread_id: 1976
+ process_id: 1948
+ }
+ task_info {
+ estimated_mws: 0.042220
+ estimated_mw: 0.004881
+ thread_name: "netlink socket"
+ process_name: "/system/vendor/bin/ipacm"
+ thread_id: 538
+ process_id: 523
+ }
+ task_info {
+ estimated_mws: 0.040507
+ estimated_mw: 0.004683
+ idle_transitions_mws: 0.020051
+ thread_name: "RegionSampling"
+ process_name: "/system/bin/surfaceflinger"
+ thread_id: 871
+ process_id: 755
+ }
+ task_info {
+ estimated_mws: 0.040385
+ estimated_mw: 0.004669
+ thread_name: "TransportThread"
+ process_name: "/vendor/bin/hw/android.hardware.sensors-service.multihal"
+ thread_id: 794
+ process_id: 664
+ }
+ task_info {
+ estimated_mws: 0.040349
+ estimated_mw: 0.004665
+ thread_name: "binder:2856_1"
+ process_name: "com.google.android.gms"
+ thread_id: 2898
+ process_id: 2856
+ }
+ task_info {
+ estimated_mws: 0.040180
+ estimated_mw: 0.004645
+ thread_name: "servicemanager"
+ thread_id: 5595
+ }
+ task_info {
+ estimated_mws: 0.039525
+ estimated_mw: 0.004570
+ thread_name: "pd-mapper"
+ process_name: "/vendor/bin/pd-mapper"
+ thread_id: 752
+ process_id: 725
+ }
+ task_info {
+ estimated_mws: 0.039196
+ estimated_mw: 0.004532
+ thread_name: "vndservicemanag"
+ thread_id: 5618
+ }
+ task_info {
+ estimated_mws: 0.039101
+ estimated_mw: 0.004521
+ idle_transitions_mws: 0.001158
+ thread_name: "vndservicemanag"
+ thread_id: 5605
+ }
+ task_info {
+ estimated_mws: 0.038960
+ estimated_mw: 0.004504
+ thread_name: "WifiScanningSer"
+ process_name: "system_server"
+ thread_id: 1823
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 0.038580
+ estimated_mw: 0.004460
+ thread_name: "cnss-daemon"
+ process_name: "/system/vendor/bin/cnss-daemon"
+ thread_id: 5204
+ process_id: 1009
+ }
+ task_info {
+ estimated_mws: 0.038053
+ estimated_mw: 0.004399
+ idle_transitions_mws: 0.002046
+ thread_name: "shell svc 5620"
+ process_name: "/apex/com.android.adbd/bin/adbd"
+ thread_id: 5622
+ process_id: 5544
+ }
+ task_info {
+ estimated_mws: 0.037116
+ estimated_mw: 0.004291
+ thread_name: "rmt_storage"
+ process_name: "/vendor/bin/rmt_storage"
+ thread_id: 758
+ process_id: 758
+ }
+ task_info {
+ estimated_mws: 0.036357
+ estimated_mw: 0.004203
+ idle_transitions_mws: 0.164265
+ thread_name: "halt_drain_rqs"
+ process_name: "halt_drain_rqs"
+ thread_id: 105
+ process_id: 105
+ }
+ task_info {
+ estimated_mws: 0.035907
+ estimated_mw: 0.004151
+ thread_name: "BG Thread #2"
+ process_name: "com.google.android.wearable.assistant"
+ thread_id: 4106
+ process_id: 4038
+ }
+ task_info {
+ estimated_mws: 0.035876
+ estimated_mw: 0.004148
+ idle_transitions_mws: 0.007692
+ thread_name: "-Executor] idle"
+ process_name: "com.google.android.gms"
+ thread_id: 5592
+ process_id: 2856
+ }
+ task_info {
+ estimated_mws: 0.035444
+ estimated_mw: 0.004098
+ thread_name: "tftp_server"
+ process_name: "/vendor/bin/tftp_server"
+ thread_id: 759
+ process_id: 759
+ }
+ task_info {
+ estimated_mws: 0.035386
+ estimated_mw: 0.004091
+ thread_name: "FinalizerWatchd"
+ process_name: "com.google.android.gms"
+ thread_id: 2887
+ process_id: 2856
+ }
+ task_info {
+ estimated_mws: 0.035171
+ estimated_mw: 0.004066
+ idle_transitions_mws: 0.012569
+ thread_name: "servicemanager"
+ thread_id: 5606
+ }
+ task_info {
+ estimated_mws: 0.035157
+ estimated_mw: 0.004065
+ thread_name: "Blocking Thread"
+ process_name: "com.google.android.wearable.assistant"
+ thread_id: 5587
+ process_id: 4038
+ }
+ task_info {
+ estimated_mws: 0.035034
+ estimated_mw: 0.004050
+ thread_name: "vndservicemanag"
+ thread_id: 5611
+ }
+ task_info {
+ estimated_mws: 0.034307
+ estimated_mw: 0.003966
+ thread_name: "vndservicemanag"
+ thread_id: 5593
+ }
+ task_info {
+ estimated_mws: 0.034030
+ estimated_mw: 0.003934
+ thread_name: "servicemanager"
+ thread_id: 5621
+ }
+ task_info {
+ estimated_mws: 0.032631
+ estimated_mw: 0.003773
+ thread_name: "binder:685_3"
+ thread_id: 5586
+ }
+ task_info {
+ estimated_mws: 0.031847
+ estimated_mw: 0.003682
+ idle_transitions_mws: 0.000891
+ thread_name: "radioext@1.0-se"
+ process_name: "/vendor/bin/hw/vendor.google.radioext@1.0-service"
+ thread_id: 676
+ process_id: 676
+ }
+ task_info {
+ estimated_mws: 0.031818
+ estimated_mw: 0.003679
+ idle_transitions_mws: 0.001158
+ thread_name: "BgBroadcastRegi"
+ process_name: "com.google.wear.services"
+ thread_id: 2017
+ process_id: 1948
+ }
+ task_info {
+ estimated_mws: 0.031204
+ estimated_mw: 0.003608
+ idle_transitions_mws: 0.002208
+ thread_name: "DefaultExecutor"
+ process_name: "com.google.android.wearable.watchface.rwf"
+ thread_id: 5600
+ process_id: 1999
+ }
+ task_info {
+ estimated_mws: 0.030138
+ estimated_mw: 0.003484
+ thread_name: "Light-P0-1"
+ process_name: "com.google.android.inputmethod.latin"
+ thread_id: 5064
+ process_id: 4997
+ }
+ task_info {
+ estimated_mws: 0.029778
+ estimated_mw: 0.003443
+ thread_name: "binder:978_2"
+ process_name: "/system/vendor/bin/nicmd"
+ thread_id: 1368
+ process_id: 978
+ }
+ task_info {
+ estimated_mws: 0.029627
+ estimated_mw: 0.003425
+ thread_name: "atchdog.monitor"
+ process_name: "system_server"
+ thread_id: 1414
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 0.029590
+ estimated_mw: 0.003421
+ idle_transitions_mws: 0.012793
+ thread_name: "UsfHalWorker"
+ process_name: "/vendor/bin/hw/android.hardware.sensors-service.multihal"
+ thread_id: 792
+ process_id: 664
+ }
+ task_info {
+ estimated_mws: 0.028316
+ estimated_mw: 0.003274
+ idle_transitions_mws: 0.003170
+ thread_name: "binder:1999_5"
+ process_name: "com.google.android.wearable.watchface.rwf"
+ thread_id: 4985
+ process_id: 1999
+ }
+ task_info {
+ estimated_mws: 0.028097
+ estimated_mw: 0.003248
+ thread_name: "SatelliteContro"
+ process_name: "com.android.phone"
+ thread_id: 2382
+ process_id: 2182
+ }
+ task_info {
+ estimated_mws: 0.027888
+ estimated_mw: 0.003224
+ idle_transitions_mws: 0.009315
+ thread_name: "irq/199-dwc3"
+ process_name: "irq/199-dwc3"
+ thread_id: 5559
+ process_id: 5559
+ }
+ task_info {
+ estimated_mws: 0.027797
+ estimated_mw: 0.003214
+ thread_name: "pm-service"
+ process_name: "/vendor/bin/pm-service"
+ thread_id: 745
+ process_id: 730
+ }
+ task_info {
+ estimated_mws: 0.027301
+ estimated_mw: 0.003156
+ idle_transitions_mws: 0.001040
+ thread_name: "-Executor] idle"
+ process_name: "com.google.android.gms.persistent"
+ thread_id: 5603
+ process_id: 1949
+ }
+ task_info {
+ estimated_mws: 0.027287
+ estimated_mw: 0.003155
+ idle_transitions_mws: 0.002419
+ thread_name: "perfetto"
+ process_name: "perfetto"
+ thread_id: 5581
+ process_id: 5581
+ }
+ task_info {
+ estimated_mws: 0.026942
+ estimated_mw: 0.003115
+ thread_name: "ExeSeq-P10-1"
+ process_name: "com.google.android.inputmethod.latin"
+ thread_id: 5074
+ process_id: 4997
+ }
+ task_info {
+ estimated_mws: 0.026776
+ estimated_mw: 0.003096
+ thread_name: "binder:978_2"
+ process_name: "/system/vendor/bin/nicmd"
+ thread_id: 1364
+ process_id: 978
+ }
+ task_info {
+ estimated_mws: 0.026328
+ estimated_mw: 0.003044
+ thread_name: "HwBinder:2129_1"
+ process_name: "com.google.android.grilservice"
+ thread_id: 3649
+ process_id: 2129
+ }
+ task_info {
+ estimated_mws: 0.026013
+ estimated_mw: 0.003008
+ thread_name: "DefaultExecutor"
+ process_name: "com.google.android.wearable.watchface.rwf"
+ thread_id: 5588
+ process_id: 1999
+ }
+ task_info {
+ estimated_mws: 0.025701
+ estimated_mw: 0.002971
+ thread_name: "rkstack.process"
+ process_name: "com.android.networkstack.process"
+ thread_id: 2049
+ process_id: 2049
+ }
+ task_info {
+ estimated_mws: 0.024843
+ estimated_mw: 0.002872
+ idle_transitions_mws: 0.001271
+ thread_name: "hwuiTask1"
+ process_name: "com.google.android.wearable.sysui"
+ thread_id: 1997
+ process_id: 1926
+ }
+ task_info {
+ estimated_mws: 0.024811
+ estimated_mw: 0.002868
+ idle_transitions_mws: 0.000632
+ thread_name: "pool-1-thread-1"
+ process_name: "com.google.android.apps.scone"
+ thread_id: 5271
+ process_id: 5245
+ }
+ task_info {
+ estimated_mws: 0.024633
+ estimated_mw: 0.002848
+ thread_name: "binder:978_2"
+ process_name: "/system/vendor/bin/nicmd"
+ thread_id: 1360
+ process_id: 978
+ }
+ task_info {
+ estimated_mws: 0.023564
+ estimated_mw: 0.002724
+ thread_name: "binder:978_2"
+ process_name: "/system/vendor/bin/nicmd"
+ thread_id: 1366
+ process_id: 978
+ }
+ task_info {
+ estimated_mws: 0.023405
+ estimated_mw: 0.002706
+ thread_name: "cnss-daemon"
+ process_name: "/system/vendor/bin/cnss-daemon"
+ thread_id: 5613
+ process_id: 1009
+ }
+ task_info {
+ estimated_mws: 0.023393
+ estimated_mw: 0.002705
+ thread_name: "servicemanager"
+ thread_id: 5608
+ }
+ task_info {
+ estimated_mws: 0.022813
+ estimated_mw: 0.002638
+ thread_name: "android.hardwar"
+ process_name: "/vendor/bin/hw/android.hardware.contexthub-service.wac"
+ thread_id: 644
+ process_id: 644
+ }
+ task_info {
+ estimated_mws: 0.022774
+ estimated_mw: 0.002633
+ thread_name: "binder:978_2"
+ process_name: "/system/vendor/bin/nicmd"
+ thread_id: 1362
+ process_id: 978
+ }
+ task_info {
+ estimated_mws: 0.022714
+ estimated_mw: 0.002626
+ thread_name: "binder:685_3"
+ thread_id: 5594
+ }
+ task_info {
+ estimated_mws: 0.022642
+ estimated_mw: 0.002618
+ thread_name: "binder:978_2"
+ process_name: "/system/vendor/bin/nicmd"
+ thread_id: 1358
+ process_id: 978
+ }
+ task_info {
+ estimated_mws: 0.022617
+ estimated_mw: 0.002615
+ thread_name: "vndservicemanag"
+ thread_id: 5607
+ }
+ task_info {
+ estimated_mws: 0.021814
+ estimated_mw: 0.002522
+ idle_transitions_mws: 0.002423
+ thread_name: "it.FitbitMobile"
+ process_name: "com.fitbit.FitbitMobile"
+ thread_id: 5377
+ process_id: 5377
+ }
+ task_info {
+ estimated_mws: 0.021307
+ estimated_mw: 0.002463
+ thread_name: "SysUiBg"
+ process_name: "com.google.android.apps.wearable.systemui"
+ thread_id: 2294
+ process_id: 2171
+ }
+ task_info {
+ estimated_mws: 0.020941
+ estimated_mw: 0.002421
+ thread_name: "-Executor] idle"
+ process_name: "com.google.android.gms.persistent"
+ thread_id: 5623
+ process_id: 1949
+ }
+ task_info {
+ estimated_mws: 0.020393
+ estimated_mw: 0.002358
+ thread_name: "vndservicemanag"
+ thread_id: 5582
+ }
+ task_info {
+ estimated_mws: 0.019946
+ estimated_mw: 0.002306
+ idle_transitions_mws: 0.009476
+ thread_name: "qcom,system-poo"
+ process_name: "qcom,system-pool-refill-thread"
+ thread_id: 81
+ process_id: 81
+ }
+ task_info {
+ estimated_mws: 0.019901
+ estimated_mw: 0.002301
+ idle_transitions_mws: 0.000916
+ thread_name: "binder:2129_9"
+ process_name: "com.google.android.grilservice"
+ thread_id: 5203
+ process_id: 2129
+ }
+ task_info {
+ estimated_mws: 0.019723
+ estimated_mw: 0.002280
+ thread_name: "servicemanager"
+ thread_id: 5610
+ }
+ task_info {
+ estimated_mws: 0.019537
+ estimated_mw: 0.002259
+ thread_name: "cnss-daemon"
+ process_name: "/system/vendor/bin/cnss-daemon"
+ thread_id: 1009
+ process_id: 1009
+ }
+ task_info {
+ estimated_mws: 0.019390
+ estimated_mw: 0.002242
+ thread_name: "pool-9-thread-1"
+ process_name: "com.google.android.wearable.watchface.rwf"
+ thread_id: 2073
+ process_id: 1999
+ }
+ task_info {
+ estimated_mws: 0.019219
+ estimated_mw: 0.002222
+ thread_name: "binder:1302_3"
+ process_name: "system_server"
+ thread_id: 1433
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 0.019059
+ estimated_mw: 0.002204
+ thread_name: "mcu_mgmtd"
+ process_name: "/vendor/bin/mcu_mgmtd"
+ thread_id: 587
+ process_id: 524
+ }
+ task_info {
+ estimated_mws: 0.018619
+ estimated_mw: 0.002153
+ thread_name: "WCMTelemetryLog"
+ process_name: "system_server"
+ thread_id: 1906
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 0.018508
+ estimated_mw: 0.002140
+ thread_name: "droid.tethering"
+ process_name: "com.android.networkstack.process"
+ thread_id: 2158
+ process_id: 2049
+ }
+ task_info {
+ estimated_mws: 0.018055
+ estimated_mw: 0.002087
+ thread_name: "DefaultWallpape"
+ process_name: "com.google.android.wearable.watchface.rwf"
+ thread_id: 2082
+ process_id: 1999
+ }
+ task_info {
+ estimated_mws: 0.017725
+ estimated_mw: 0.002049
+ idle_transitions_mws: 0.001379
+ thread_name: "HeapTaskDaemon"
+ process_name: "com.fitbit.FitbitMobile"
+ thread_id: 5386
+ process_id: 5377
+ }
+ task_info {
+ estimated_mws: 0.017125
+ estimated_mw: 0.001980
+ idle_transitions_mws: 0.001201
+ thread_name: "WearConnectionT"
+ process_name: "com.google.android.wearable.sysui"
+ thread_id: 2172
+ process_id: 1926
+ }
+ task_info {
+ estimated_mws: 0.016887
+ estimated_mw: 0.001952
+ idle_transitions_mws: 0.001112
+ thread_name: "wear-services-w"
+ process_name: "com.google.wear.services"
+ thread_id: 2029
+ process_id: 1948
+ }
+ task_info {
+ estimated_mws: 0.016252
+ estimated_mw: 0.001879
+ thread_name: "BG Thread #1"
+ process_name: "com.google.android.wearable.assistant"
+ thread_id: 4080
+ process_id: 4038
+ }
+ task_info {
+ estimated_mws: 0.016215
+ estimated_mw: 0.001875
+ idle_transitions_mws: 0.001249
+ thread_name: "GlobalScheduler"
+ process_name: "com.google.android.gms.persistent"
+ thread_id: 2276
+ process_id: 1949
+ }
+ task_info {
+ estimated_mws: 0.016141
+ estimated_mw: 0.001866
+ thread_name: "pool-31-thread-"
+ process_name: "com.google.android.wearable.sysui"
+ thread_id: 5617
+ process_id: 1926
+ }
+ task_info {
+ estimated_mws: 0.016069
+ estimated_mw: 0.001858
+ idle_transitions_mws: 0.005041
+ thread_name: "servicemanager"
+ thread_id: 5583
+ }
+ task_info {
+ estimated_mws: 0.015376
+ estimated_mw: 0.001778
+ idle_transitions_mws: 0.007814
+ thread_name: "RenderEngine"
+ process_name: "/system/bin/surfaceflinger"
+ thread_id: 5601
+ process_id: 755
+ }
+ task_info {
+ estimated_mws: 0.015374
+ estimated_mw: 0.001777
+ thread_name: "pool-11-thread-"
+ process_name: "com.google.android.wearable.healthservices"
+ thread_id: 5596
+ process_id: 3028
+ }
+ task_info {
+ estimated_mws: 0.015097
+ estimated_mw: 0.001745
+ idle_transitions_mws: 0.000833
+ thread_name: "dsi_err_workq"
+ process_name: "dsi_err_workq"
+ thread_id: 5589
+ process_id: 5589
+ }
+ task_info {
+ estimated_mws: 0.015062
+ estimated_mw: 0.001741
+ thread_name: "InteractionJank"
+ process_name: "com.google.android.apps.wearable.systemui"
+ thread_id: 2300
+ process_id: 2171
+ }
+ task_info {
+ estimated_mws: 0.015034
+ estimated_mw: 0.001738
+ thread_name: "vndservicemanag"
+ thread_id: 5609
+ }
+ task_info {
+ estimated_mws: 0.014592
+ estimated_mw: 0.001687
+ idle_transitions_mws: 0.000734
+ thread_name: "hwuiTask0"
+ process_name: "com.google.android.wearable.sysui"
+ thread_id: 1996
+ process_id: 1926
+ }
+ task_info {
+ estimated_mws: 0.014520
+ estimated_mw: 0.001679
+ thread_name: "tworkPolicy.uid"
+ process_name: "system_server"
+ thread_id: 1817
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 0.014278
+ estimated_mw: 0.001651
+ thread_name: "highpool[10]"
+ process_name: "com.google.android.gms.persistent"
+ thread_id: 3417
+ process_id: 1949
+ }
+ task_info {
+ estimated_mws: 0.014123
+ estimated_mw: 0.001633
+ idle_transitions_mws: 0.010036
+ thread_name: "LowMemThread"
+ process_name: "system_server"
+ thread_id: 1481
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 0.014026
+ estimated_mw: 0.001622
+ thread_name: "pixelstats-vend"
+ process_name: "/vendor/bin/pixelstats-vendor"
+ thread_id: 266
+ process_id: 255
+ }
+ task_info {
+ estimated_mws: 0.013579
+ estimated_mw: 0.001570
+ idle_transitions_mws: 0.001160
+ thread_name: "kworker/u9:0"
+ process_name: "kworker/u9:0"
+ thread_id: 64
+ process_id: 64
+ }
+ task_info {
+ estimated_mws: 0.013322
+ estimated_mw: 0.001540
+ idle_transitions_mws: 0.011775
+ thread_name: "migration/1"
+ process_name: "migration/1"
+ thread_id: 25
+ process_id: 25
+ }
+ task_info {
+ estimated_mws: 0.013321
+ estimated_mw: 0.001540
+ thread_name: "GlobalDispatchi"
+ process_name: "com.google.android.gms.persistent"
+ thread_id: 2290
+ process_id: 1949
+ }
+ task_info {
+ estimated_mws: 0.013127
+ estimated_mw: 0.001518
+ thread_name: "main"
+ process_name: "/vendor/bin/hw/qcrilNrd"
+ thread_id: 1568
+ process_id: 1062
+ }
+ task_info {
+ estimated_mws: 0.012863
+ estimated_mw: 0.001487
+ thread_name: "servicemanager"
+ thread_id: 5612
+ }
+ task_info {
+ estimated_mws: 0.012835
+ estimated_mw: 0.001484
+ thread_name: "qtidataservices"
+ process_name: ".qtidataservices"
+ thread_id: 2846
+ process_id: 2118
+ }
+ task_info {
+ estimated_mws: 0.012734
+ estimated_mw: 0.001472
+ thread_name: "shortcut"
+ process_name: "system_server"
+ thread_id: 1874
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 0.012433
+ estimated_mw: 0.001437
+ idle_transitions_mws: 0.001905
+ thread_name: "irq/25-mmc0"
+ process_name: "irq/25-mmc0"
+ thread_id: 120
+ process_id: 120
+ }
+ task_info {
+ estimated_mws: 0.012244
+ estimated_mw: 0.001416
+ thread_name: "GlobalDispatchi"
+ process_name: "com.google.android.gms"
+ thread_id: 3155
+ process_id: 2856
+ }
+ task_info {
+ estimated_mws: 0.012130
+ estimated_mw: 0.001402
+ thread_name: "ConnectivityThr"
+ process_name: "com.google.android.gms"
+ thread_id: 4172
+ process_id: 2856
+ }
+ task_info {
+ estimated_mws: 0.011932
+ estimated_mw: 0.001379
+ thread_name: "RenderThread"
+ thread_id: 5616
+ }
+ task_info {
+ estimated_mws: 0.011783
+ estimated_mw: 0.001362
+ thread_name: "AGMIPC@1.0-serv"
+ process_name: "/vendor/bin/hw/vendor.qti.hardware.AGMIPC@1.0-service"
+ thread_id: 1454
+ process_id: 1446
+ }
+ task_info {
+ estimated_mws: 0.011706
+ estimated_mw: 0.001353
+ thread_name: "binder:2171_2"
+ process_name: "com.google.android.apps.wearable.systemui"
+ thread_id: 2209
+ process_id: 2171
+ }
+ task_info {
+ estimated_mws: 0.011675
+ estimated_mw: 0.001350
+ thread_name: "radioext@1.0-se"
+ process_name: "/vendor/bin/hw/vendor.google.radioext@1.0-service"
+ thread_id: 714
+ process_id: 676
+ }
+ task_info {
+ estimated_mws: 0.011615
+ estimated_mw: 0.001343
+ thread_name: "servicemanager"
+ thread_id: 5615
+ }
+ task_info {
+ estimated_mws: 0.011467
+ estimated_mw: 0.001326
+ thread_name: "LocApiMsgTask"
+ process_name: "/vendor/bin/hw/android.hardware.gnss-aidl-service-qti"
+ thread_id: 694
+ process_id: 650
+ }
+ task_info {
+ estimated_mws: 0.011318
+ estimated_mw: 0.001309
+ thread_name: "LocApiMsgTask"
+ process_name: "xtra-daemon"
+ thread_id: 1090
+ process_id: 1031
+ }
+ task_info {
+ estimated_mws: 0.011273
+ estimated_mw: 0.001303
+ thread_name: "vndservicemanag"
+ thread_id: 5614
+ }
+ task_info {
+ estimated_mws: 0.011024
+ estimated_mw: 0.001275
+ idle_transitions_mws: 0.004820
+ thread_name: "TimerThread"
+ process_name: "/system/bin/audioserver"
+ thread_id: 1486
+ process_id: 740
+ }
+ task_info {
+ estimated_mws: 0.010869
+ estimated_mw: 0.001257
+ idle_transitions_mws: 0.001558
+ thread_name: "irq/26-4744000."
+ process_name: "irq/26-4744000.sdhci"
+ thread_id: 117
+ process_id: 117
+ }
+ task_info {
+ estimated_mws: 0.010764
+ estimated_mw: 0.001245
+ idle_transitions_mws: 0.003066
+ thread_name: "SurfaceSyncGrou"
+ process_name: "com.google.android.wearable.sysui"
+ thread_id: 1994
+ process_id: 1926
+ }
+ task_info {
+ estimated_mws: 0.010731
+ estimated_mw: 0.001241
+ idle_transitions_mws: 0.009927
+ thread_name: "migration/3"
+ process_name: "migration/3"
+ thread_id: 40
+ process_id: 40
+ }
+ task_info {
+ estimated_mws: 0.010156
+ estimated_mw: 0.001174
+ thread_name: "id.wearable.app"
+ process_name: "com.google.android.wearable.app"
+ thread_id: 3857
+ process_id: 3857
+ }
+ task_info {
+ estimated_mws: 0.009691
+ estimated_mw: 0.001120
+ thread_name: "ksoftirqd/0"
+ process_name: "ksoftirqd/0"
+ thread_id: 13
+ process_id: 13
+ }
+ task_info {
+ estimated_mws: 0.009668
+ estimated_mw: 0.001118
+ thread_name: "cnss-daemon"
+ process_name: "/system/vendor/bin/cnss-daemon"
+ thread_id: 1052
+ process_id: 1009
+ }
+ task_info {
+ estimated_mws: 0.009639
+ estimated_mw: 0.001114
+ idle_transitions_mws: 0.002373
+ thread_name: "kworker/u9:2"
+ process_name: "kworker/u9:2"
+ thread_id: 338
+ process_id: 338
+ }
+ task_info {
+ estimated_mws: 0.009404
+ estimated_mw: 0.001087
+ thread_name: "binder:2856_3"
+ process_name: "com.google.android.gms"
+ thread_id: 3157
+ process_id: 2856
+ }
+ task_info {
+ estimated_mws: 0.009364
+ estimated_mw: 0.001083
+ thread_name: "binder:969_3"
+ process_name: "/system/vendor/bin/cnd"
+ thread_id: 1013
+ process_id: 969
+ }
+ task_info {
+ estimated_mws: 0.008837
+ estimated_mw: 0.001022
+ thread_name: "binder:2118_2"
+ process_name: ".qtidataservices"
+ thread_id: 2142
+ process_id: 2118
+ }
+ task_info {
+ estimated_mws: 0.008775
+ estimated_mw: 0.001015
+ thread_name: "thermal-engine-"
+ process_name: "/vendor/bin/thermal-engine-v2"
+ thread_id: 2520
+ process_id: 2493
+ }
+ task_info {
+ estimated_mws: 0.008724
+ estimated_mw: 0.001009
+ thread_name: "binder:2856_7"
+ process_name: "com.google.android.gms"
+ thread_id: 4825
+ process_id: 2856
+ }
+ task_info {
+ estimated_mws: 0.008275
+ estimated_mw: 0.000957
+ thread_name: "binder:2856_6"
+ process_name: "com.google.android.gms"
+ thread_id: 4824
+ process_id: 2856
+ }
+ task_info {
+ estimated_mws: 0.008136
+ estimated_mw: 0.000941
+ thread_name: "binder:3857_3"
+ process_name: "com.google.android.wearable.app"
+ thread_id: 3872
+ process_id: 3857
+ }
+ task_info {
+ estimated_mws: 0.007913
+ estimated_mw: 0.000915
+ thread_name: "binder:975_3"
+ process_name: "/vendor/bin/imsdaemon"
+ thread_id: 1630
+ process_id: 975
+ }
+ task_info {
+ estimated_mws: 0.007892
+ estimated_mw: 0.000912
+ thread_name: "time_daemon"
+ process_name: "/vendor/bin/time_daemon"
+ thread_id: 525
+ process_id: 522
+ }
+ task_info {
+ estimated_mws: 0.007750
+ estimated_mw: 0.000896
+ thread_name: "binder:978_2"
+ process_name: "/system/vendor/bin/nicmd"
+ thread_id: 1044
+ process_id: 978
+ }
+ task_info {
+ estimated_mws: 0.007591
+ estimated_mw: 0.000878
+ thread_name: "binder:2856_5"
+ process_name: "com.google.android.gms"
+ thread_id: 3681
+ process_id: 2856
+ }
+ task_info {
+ estimated_mws: 0.007392
+ estimated_mw: 0.000855
+ thread_name: "binder:2182_1"
+ process_name: "com.android.phone"
+ thread_id: 2231
+ process_id: 2182
+ }
+ task_info {
+ estimated_mws: 0.007384
+ estimated_mw: 0.000854
+ thread_name: "binder:2171_5"
+ process_name: "com.google.android.apps.wearable.systemui"
+ thread_id: 2638
+ process_id: 2171
+ }
+ task_info {
+ estimated_mws: 0.007245
+ estimated_mw: 0.000838
+ idle_transitions_mws: 0.000922
+ thread_name: "qrtr_rx"
+ process_name: "qrtr_rx"
+ thread_id: 1556
+ process_id: 1556
+ }
+ task_info {
+ estimated_mws: 0.007086
+ estimated_mw: 0.000819
+ thread_name: "radioext@1.0-se"
+ process_name: "/vendor/bin/hw/vendor.google.radioext@1.0-service"
+ thread_id: 1605
+ process_id: 676
+ }
+ task_info {
+ estimated_mws: 0.006850
+ estimated_mw: 0.000792
+ idle_transitions_mws: 0.005140
+ thread_name: "hwservicemanage"
+ process_name: "/system/system_ext/bin/hwservicemanager"
+ thread_id: 214
+ process_id: 214
+ }
+ task_info {
+ estimated_mws: 0.006731
+ estimated_mw: 0.000778
+ thread_name: "rcub/0"
+ process_name: "rcub/0"
+ thread_id: 17
+ process_id: 17
+ }
+ task_info {
+ estimated_mws: 0.006663
+ estimated_mw: 0.000770
+ thread_name: "binder:2856_8"
+ process_name: "com.google.android.gms"
+ thread_id: 4826
+ process_id: 2856
+ }
+ task_info {
+ estimated_mws: 0.006650
+ estimated_mw: 0.000769
+ thread_name: "kthreadd"
+ process_name: "kthreadd"
+ thread_id: 2
+ process_id: 2
+ }
+ task_info {
+ estimated_mws: 0.006574
+ estimated_mw: 0.000760
+ thread_name: "binder:233_2"
+ process_name: "/system/bin/vold"
+ thread_id: 233
+ process_id: 233
+ }
+ task_info {
+ estimated_mws: 0.006115
+ estimated_mw: 0.000707
+ thread_name: "cds_ol_rx_threa"
+ process_name: "cds_ol_rx_thread"
+ thread_id: 5199
+ process_id: 5199
+ }
+ task_info {
+ estimated_mws: 0.005829
+ estimated_mw: 0.000674
+ thread_name: "NsdService"
+ process_name: "system_server"
+ thread_id: 1831
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 0.005734
+ estimated_mw: 0.000663
+ thread_name: "binder:978_2"
+ process_name: "/system/vendor/bin/nicmd"
+ thread_id: 1367
+ process_id: 978
+ }
+ task_info {
+ estimated_mws: 0.005699
+ estimated_mw: 0.000659
+ thread_name: "Scheduled BG"
+ process_name: "com.google.android.wearable.sysui"
+ thread_id: 2895
+ process_id: 1926
+ }
+ task_info {
+ estimated_mws: 0.005530
+ estimated_mw: 0.000639
+ thread_name: "binder:2856_2"
+ process_name: "com.google.android.gms"
+ thread_id: 2903
+ process_id: 2856
+ }
+ task_info {
+ estimated_mws: 0.005484
+ estimated_mw: 0.000634
+ thread_name: "binder:978_2"
+ process_name: "/system/vendor/bin/nicmd"
+ thread_id: 1361
+ process_id: 978
+ }
+ task_info {
+ estimated_mws: 0.005366
+ estimated_mw: 0.000620
+ thread_name: "binder:978_2"
+ process_name: "/system/vendor/bin/nicmd"
+ thread_id: 1357
+ process_id: 978
+ }
+ task_info {
+ estimated_mws: 0.005364
+ estimated_mw: 0.000620
+ thread_name: "FileObserver"
+ process_name: "system_server"
+ thread_id: 1498
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 0.005278
+ estimated_mw: 0.000610
+ thread_name: "binder:978_2"
+ process_name: "/system/vendor/bin/nicmd"
+ thread_id: 1359
+ process_id: 978
+ }
+ task_info {
+ estimated_mws: 0.005261
+ estimated_mw: 0.000608
+ thread_name: "Lite Thread #0"
+ process_name: "com.google.android.wearable.assistant"
+ thread_id: 4109
+ process_id: 4038
+ }
+ task_info {
+ estimated_mws: 0.005011
+ estimated_mw: 0.000579
+ thread_name: "kworker/3:1H"
+ process_name: "kworker/3:1H"
+ thread_id: 122
+ process_id: 122
+ }
+ task_info {
+ estimated_mws: 0.004996
+ estimated_mw: 0.000578
+ thread_name: "binder:978_2"
+ process_name: "/system/vendor/bin/nicmd"
+ thread_id: 1365
+ process_id: 978
+ }
+ task_info {
+ estimated_mws: 0.004986
+ estimated_mw: 0.000576
+ idle_transitions_mws: 0.031459
+ thread_name: "Scheduled BG"
+ process_name: "com.google.android.wearable.sysui"
+ thread_id: 2890
+ process_id: 1926
+ }
+ task_info {
+ estimated_mws: 0.004877
+ estimated_mw: 0.000564
+ thread_name: "perfetto_hprof_"
+ process_name: "com.google.android.gms"
+ thread_id: 2880
+ process_id: 2856
+ }
+ task_info {
+ estimated_mws: 0.004761
+ estimated_mw: 0.000550
+ thread_name: "backup-0"
+ process_name: "system_server"
+ thread_id: 2660
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 0.004757
+ estimated_mw: 0.000550
+ thread_name: "tts-player-0"
+ process_name: "com.google.android.wearable.assistant"
+ thread_id: 4209
+ process_id: 4038
+ }
+ task_info {
+ estimated_mws: 0.004444
+ estimated_mw: 0.000514
+ thread_name: "Signal Catcher"
+ process_name: "com.google.android.gms"
+ thread_id: 2878
+ process_id: 2856
+ }
+ task_info {
+ estimated_mws: 0.004331
+ estimated_mw: 0.000501
+ thread_name: "binder:978_2"
+ process_name: "/system/vendor/bin/nicmd"
+ thread_id: 1363
+ process_id: 978
+ }
+ task_info {
+ estimated_mws: 0.004292
+ estimated_mw: 0.000496
+ idle_transitions_mws: 0.005500
+ thread_name: "f2fs_discard-25"
+ process_name: "f2fs_discard-254:43"
+ thread_id: 349
+ process_id: 349
+ }
+ task_info {
+ estimated_mws: 0.004283
+ estimated_mw: 0.000495
+ idle_transitions_mws: 0.009490
+ thread_name: "irq/24-glink-na"
+ process_name: "irq/24-glink-native-rpm-glink"
+ thread_id: 86
+ process_id: 86
+ }
+ task_info {
+ estimated_mws: 0.004252
+ estimated_mw: 0.000492
+ idle_transitions_mws: 0.001945
+ thread_name: "pool-4-thread-1"
+ process_name: "system_server"
+ thread_id: 1774
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 0.004010
+ estimated_mw: 0.000464
+ thread_name: "PasspointProvis"
+ process_name: "system_server"
+ thread_id: 1821
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 0.003934
+ estimated_mw: 0.000455
+ idle_transitions_mws: 0.001222
+ thread_name: "binder:5377_5"
+ process_name: "com.fitbit.FitbitMobile"
+ thread_id: 5573
+ process_id: 5377
+ }
+ task_info {
+ estimated_mws: 0.003880
+ estimated_mw: 0.000449
+ thread_name: "BG Thread #3"
+ process_name: "com.google.android.wearable.assistant"
+ thread_id: 4107
+ process_id: 4038
+ }
+ task_info {
+ estimated_mws: 0.003872
+ estimated_mw: 0.000448
+ thread_name: "TransportThread"
+ process_name: "/vendor/bin/mcu_mgmtd"
+ thread_id: 3540
+ process_id: 524
+ }
+ task_info {
+ estimated_mws: 0.003835
+ estimated_mw: 0.000443
+ thread_name: "kworker/1:1H"
+ process_name: "kworker/1:1H"
+ thread_id: 127
+ process_id: 127
+ }
+ task_info {
+ estimated_mws: 0.003754
+ estimated_mw: 0.000434
+ thread_name: "watchdog"
+ process_name: "system_server"
+ thread_id: 1421
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 0.003628
+ estimated_mw: 0.000419
+ thread_name: "PackageInstalle"
+ process_name: "system_server"
+ thread_id: 1744
+ process_id: 1302
+ }
+ task_info {
+ estimated_mws: 0.003469
+ estimated_mw: 0.000401
+ thread_name: "Lite Thread #1"
+ process_name: "com.google.android.wearable.assistant"
+ thread_id: 4118
+ process_id: 4038
+ }
+ task_info {
+ estimated_mws: 0.003393
+ estimated_mw: 0.000392
+ idle_transitions_mws: 0.009600
+ thread_name: "FinalizerWatchd"
+ process_name: "com.fitbit.FitbitMobile"
+ thread_id: 5389
+ process_id: 5377
+ }
+ task_info {
+ estimated_mws: 0.003365
+ estimated_mw: 0.000389
+ thread_name: "DFacilitator-1"
+ process_name: "com.google.android.inputmethod.latin"
+ thread_id: 5128
+ process_id: 4997
+ }
+ task_info {
+ estimated_mws: 0.003243
+ estimated_mw: 0.000375
+ thread_name: "pool-8-thread-1"
+ process_name: "com.google.android.wearable.healthservices"
+ thread_id: 3308
+ process_id: 3028
+ }
+ task_info {
+ estimated_mws: 0.002873
+ estimated_mw: 0.000332
+ thread_name: "lowpool[1]"
+ process_name: "com.google.android.gms"
+ thread_id: 3503
+ process_id: 2856
+ }
+ task_info {
+ estimated_mws: 0.002842
+ estimated_mw: 0.000329
+ thread_name: "ReferenceQueueD"
+ process_name: "com.google.android.gms"
+ thread_id: 2883
+ process_id: 2856
+ }
+ task_info {
+ estimated_mws: 0.002778
+ estimated_mw: 0.000321
+ thread_name: "ipacm-diag"
+ process_name: "/system/vendor/bin/ipacm-diag"
+ thread_id: 976
+ process_id: 976
+ }
+ task_info {
+ estimated_mws: 0.002739
+ estimated_mw: 0.000317
+ idle_transitions_mws: 0.005358
+ thread_name: "migration/2"
+ process_name: "migration/2"
+ thread_id: 32
+ process_id: 32
+ }
+ task_info {
+ estimated_mws: 0.002654
+ estimated_mw: 0.000307
+ thread_name: "qrtr_rx"
+ process_name: "qrtr_rx"
+ thread_id: 564
+ process_id: 564
+ }
+ task_info {
+ estimated_mws: 0.002601
+ estimated_mw: 0.000301
+ thread_name: "card0-crtc0"
+ process_name: "card0-crtc0"
+ thread_id: 247
+ process_id: 247
+ }
+ task_info {
+ estimated_mws: 0.002574
+ estimated_mw: 0.000298
+ thread_name: "pool-7-thread-3"
+ process_name: "com.google.android.wearable.healthservices"
+ thread_id: 5435
+ process_id: 3028
+ }
+ task_info {
+ estimated_mws: 0.002458
+ estimated_mw: 0.000284
+ thread_name: "RenderThread"
+ process_name: "com.google.android.apps.wearable.systemui"
+ thread_id: 2319
+ process_id: 2171
+ }
+ task_info {
+ estimated_mws: 0.002443
+ estimated_mw: 0.000282
+ thread_name: "highpool[0]"
+ process_name: "com.google.android.gms"
+ thread_id: 3154
+ process_id: 2856
+ }
+ task_info {
+ estimated_mws: 0.002437
+ estimated_mw: 0.000282
+ thread_name: "lowpool[0]"
+ process_name: "com.google.android.gms"
+ thread_id: 3478
+ process_id: 2856
+ }
+ task_info {
+ estimated_mws: 0.002241
+ estimated_mw: 0.000259
+ thread_name: "queued-work-loo"
+ process_name: "com.google.android.gms.persistent"
+ thread_id: 3533
+ process_id: 1949
+ }
+ task_info {
+ estimated_mws: 0.002222
+ estimated_mw: 0.000257
+ thread_name: "arch_disk_io_2"
+ process_name: "com.google.android.gms"
+ thread_id: 4174
+ process_id: 2856
+ }
+ task_info {
+ estimated_mws: 0.002160
+ estimated_mw: 0.000250
+ thread_name: "binder:523_2"
+ process_name: "/system/vendor/bin/ipacm"
+ thread_id: 537
+ process_id: 523
+ }
+ task_info {
+ estimated_mws: 0.002129
+ estimated_mw: 0.000246
+ thread_name: "Jit thread pool"
+ process_name: "com.google.android.gms"
+ thread_id: 2881
+ process_id: 2856
+ }
+ task_info {
+ estimated_mws: 0.002082
+ estimated_mw: 0.000241
+ thread_name: "pool-48-thread-"
+ process_name: "com.google.android.gms"
+ thread_id: 4110
+ process_id: 2856
+ }
+ task_info {
+ estimated_mws: 0.002071
+ estimated_mw: 0.000239
+ thread_name: "RenderThread"
+ process_name: "com.google.android.inputmethod.latin"
+ thread_id: 5120
+ process_id: 4997
+ }
+ task_info {
+ estimated_mws: 0.002020
+ estimated_mw: 0.000233
+ thread_name: "arch_disk_io_0"
+ process_name: "com.google.android.gms"
+ thread_id: 4031
+ process_id: 2856
+ }
+ task_info {
+ estimated_mws: 0.001985
+ estimated_mw: 0.000229
+ thread_name: "arch_disk_io_1"
+ process_name: "com.google.android.gms"
+ thread_id: 4034
+ process_id: 2856
+ }
+ task_info {
+ estimated_mws: 0.001856
+ estimated_mw: 0.000215
+ thread_name: "BG Thread #0"
+ process_name: "com.google.android.wearable.assistant"
+ thread_id: 4061
+ process_id: 4038
+ }
+ task_info {
+ estimated_mws: 0.001782
+ estimated_mw: 0.000206
+ thread_name: "binder:1926_1"
+ process_name: "com.google.android.wearable.sysui"
+ thread_id: 1938
+ process_id: 1926
+ }
+ task_info {
+ estimated_mws: 0.001776
+ estimated_mw: 0.000205
+ thread_name: "highpool[3]"
+ process_name: "com.google.android.gms"
+ thread_id: 3470
+ process_id: 2856
+ }
+ task_info {
+ estimated_mws: 0.001776
+ estimated_mw: 0.000205
+ idle_transitions_mws: 0.005770
+ thread_name: "migration/0"
+ process_name: "migration/0"
+ thread_id: 21
+ process_id: 21
+ }
+ task_info {
+ estimated_mws: 0.001772
+ estimated_mw: 0.000205
+ thread_name: "AsyncTask #2"
+ process_name: "com.google.android.gms"
+ thread_id: 4164
+ process_id: 2856
+ }
+ task_info {
+ estimated_mws: 0.001720
+ estimated_mw: 0.000199
+ thread_name: "arch_disk_io_3"
+ process_name: "com.google.android.gms"
+ thread_id: 4175
+ process_id: 2856
+ }
+ task_info {
+ estimated_mws: 0.001562
+ estimated_mw: 0.000181
+ idle_transitions_mws: 0.001329
+ thread_name: "POSIX timer 0"
+ process_name: "/vendor/bin/hw/android.hardware.sensors-service.multihal"
+ thread_id: 850
+ process_id: 664
+ }
+ task_info {
+ estimated_mws: 0.001520
+ estimated_mw: 0.000176
+ thread_name: "ksoftirqd/3"
+ process_name: "ksoftirqd/3"
+ thread_id: 42
+ process_id: 42
+ }
+ task_info {
+ estimated_mws: 0.001401
+ estimated_mw: 0.000162
+ thread_name: "Primes-1"
+ process_name: "com.fitbit.FitbitMobile"
+ thread_id: 5394
+ process_id: 5377
+ }
+ task_info {
+ estimated_mws: 0.001320
+ estimated_mw: 0.000153
+ thread_name: "binder:5377_3"
+ process_name: "com.fitbit.FitbitMobile"
+ thread_id: 5392
+ process_id: 5377
+ }
+ task_info {
+ estimated_mws: 0.001316
+ estimated_mw: 0.000152
+ idle_transitions_mws: 0.008908
+ thread_name: "msm-watchdog"
+ process_name: "msm-watchdog"
+ thread_id: 76
+ process_id: 76
+ }
+ task_info {
+ estimated_mws: 0.001222
+ estimated_mw: 0.000141
+ thread_name: "Lite Thread #1"
+ process_name: "com.fitbit.FitbitMobile"
+ thread_id: 5421
+ process_id: 5377
+ }
+ task_info {
+ estimated_mws: 0.001220
+ estimated_mw: 0.000141
+ thread_name: "Signal Catcher"
+ process_name: "com.fitbit.FitbitMobile"
+ thread_id: 5382
+ process_id: 5377
+ }
+ task_info {
+ estimated_mws: 0.001179
+ estimated_mw: 0.000136
+ thread_name: "GoogleApiHandle"
+ process_name: "com.fitbit.FitbitMobile"
+ thread_id: 5398
+ process_id: 5377
+ }
+ task_info {
+ estimated_mws: 0.001127
+ estimated_mw: 0.000130
+ thread_name: "binder:2171_6"
+ process_name: "com.google.android.apps.wearable.systemui"
+ thread_id: 2678
+ process_id: 2171
+ }
+ task_info {
+ estimated_mws: 0.001103
+ estimated_mw: 0.000128
+ thread_name: "Blocking Thread"
+ process_name: "com.fitbit.FitbitMobile"
+ thread_id: 5574
+ process_id: 5377
+ }
+ task_info {
+ estimated_mws: 0.001055
+ estimated_mw: 0.000122
+ thread_name: "WM.task-3"
+ process_name: "com.fitbit.FitbitMobile"
+ thread_id: 5430
+ process_id: 5377
+ }
+ task_info {
+ estimated_mws: 0.000990
+ estimated_mw: 0.000114
+ thread_name: "highpool[1]"
+ process_name: "com.google.android.gms"
+ thread_id: 3373
+ process_id: 2856
+ }
+ task_info {
+ estimated_mws: 0.000984
+ estimated_mw: 0.000114
+ thread_name: "Primes-nativecr"
+ process_name: "com.fitbit.FitbitMobile"
+ thread_id: 5397
+ process_id: 5377
+ }
+ task_info {
+ estimated_mws: 0.000961
+ estimated_mw: 0.000111
+ thread_name: "binder:740_4"
+ process_name: "/system/bin/audioserver"
+ thread_id: 2183
+ process_id: 740
+ }
+ task_info {
+ estimated_mws: 0.000954
+ estimated_mw: 0.000110
+ thread_name: "Lite Thread #0"
+ process_name: "com.fitbit.FitbitMobile"
+ thread_id: 5404
+ process_id: 5377
+ }
+ task_info {
+ estimated_mws: 0.000927
+ estimated_mw: 0.000107
+ thread_name: "BG Thread #0"
+ process_name: "com.fitbit.FitbitMobile"
+ thread_id: 5395
+ process_id: 5377
+ }
+ task_info {
+ estimated_mws: 0.000926
+ estimated_mw: 0.000107
+ thread_name: "FinalizerDaemon"
+ process_name: "com.google.android.gms"
+ thread_id: 2885
+ process_id: 2856
+ }
+ task_info {
+ estimated_mws: 0.000887
+ estimated_mw: 0.000103
+ thread_name: "BG Thread #1"
+ process_name: "com.fitbit.FitbitMobile"
+ thread_id: 5396
+ process_id: 5377
+ }
+ task_info {
+ estimated_mws: 0.000865
+ estimated_mw: 0.000100
+ thread_name: "Jit thread pool"
+ process_name: "com.fitbit.FitbitMobile"
+ thread_id: 5385
+ process_id: 5377
+ }
+ task_info {
+ estimated_mws: 0.000858
+ estimated_mw: 0.000099
+ thread_name: "highpool[2]"
+ process_name: "com.google.android.gms"
+ thread_id: 3375
+ process_id: 2856
+ }
+ task_info {
+ estimated_mws: 0.000855
+ estimated_mw: 0.000099
+ thread_name: "ConnectivityThr"
+ process_name: "com.fitbit.FitbitMobile"
+ thread_id: 5423
+ process_id: 5377
+ }
+ task_info {
+ estimated_mws: 0.000828
+ estimated_mw: 0.000096
+ thread_name: "Profile Saver"
+ process_name: "com.fitbit.FitbitMobile"
+ thread_id: 5393
+ process_id: 5377
+ }
+ task_info {
+ estimated_mws: 0.000808
+ estimated_mw: 0.000093
+ idle_transitions_mws: 0.000732
+ thread_name: "ReferenceQueueD"
+ process_name: "com.fitbit.FitbitMobile"
+ thread_id: 5387
+ process_id: 5377
+ }
+ task_info {
+ estimated_mws: 0.000803
+ estimated_mw: 0.000093
+ idle_transitions_mws: 0.000679
+ thread_name: "binder:5377_4"
+ process_name: "com.fitbit.FitbitMobile"
+ thread_id: 5433
+ process_id: 5377
+ }
+ task_info {
+ estimated_mws: 0.000783
+ estimated_mw: 0.000091
+ thread_name: "ksoftirqd/1"
+ process_name: "ksoftirqd/1"
+ thread_id: 27
+ process_id: 27
+ }
+ task_info {
+ estimated_mws: 0.000782
+ estimated_mw: 0.000090
+ thread_name: "HsConnectionMan"
+ process_name: "com.fitbit.FitbitMobile"
+ thread_id: 5422
+ process_id: 5377
+ }
+ task_info {
+ estimated_mws: 0.000769
+ estimated_mw: 0.000089
+ thread_name: "ADB-JDWP Connec"
+ process_name: "com.fitbit.FitbitMobile"
+ thread_id: 5384
+ process_id: 5377
+ }
+ task_info {
+ estimated_mws: 0.000742
+ estimated_mw: 0.000086
+ thread_name: "Scheduler Threa"
+ process_name: "com.fitbit.FitbitMobile"
+ thread_id: 5428
+ process_id: 5377
+ }
+ task_info {
+ estimated_mws: 0.000733
+ estimated_mw: 0.000085
+ thread_name: "WM.task-2"
+ process_name: "com.fitbit.FitbitMobile"
+ thread_id: 5429
+ process_id: 5377
+ }
+ task_info {
+ estimated_mws: 0.000730
+ estimated_mw: 0.000084
+ thread_name: "Scheduled BG"
+ process_name: "com.google.android.wearable.sysui"
+ thread_id: 2896
+ process_id: 1926
+ }
+ task_info {
+ estimated_mws: 0.000727
+ estimated_mw: 0.000084
+ thread_name: "DefaultDispatch"
+ process_name: "com.fitbit.FitbitMobile"
+ thread_id: 5431
+ process_id: 5377
+ }
+ task_info {
+ estimated_mws: 0.000726
+ estimated_mw: 0.000084
+ thread_name: "BG Thread #3"
+ process_name: "com.fitbit.FitbitMobile"
+ thread_id: 5400
+ process_id: 5377
+ }
+ task_info {
+ estimated_mws: 0.000724
+ estimated_mw: 0.000084
+ thread_name: "WM.task-1"
+ process_name: "com.fitbit.FitbitMobile"
+ thread_id: 5427
+ process_id: 5377
+ }
+ task_info {
+ estimated_mws: 0.000718
+ estimated_mw: 0.000083
+ thread_name: "binder:5377_1"
+ process_name: "com.fitbit.FitbitMobile"
+ thread_id: 5390
+ process_id: 5377
+ }
+ task_info {
+ estimated_mws: 0.000689
+ estimated_mw: 0.000080
+ thread_name: "DefaultDispatch"
+ process_name: "com.fitbit.FitbitMobile"
+ thread_id: 5432
+ process_id: 5377
+ }
+ task_info {
+ estimated_mws: 0.000669
+ estimated_mw: 0.000077
+ thread_name: "BG Thread #2"
+ process_name: "com.fitbit.FitbitMobile"
+ thread_id: 5399
+ process_id: 5377
+ }
+ task_info {
+ estimated_mws: 0.000630
+ estimated_mw: 0.000073
+ idle_transitions_mws: 0.008723
+ thread_name: "binder:5377_2"
+ process_name: "com.fitbit.FitbitMobile"
+ thread_id: 5391
+ process_id: 5377
+ }
+ task_info {
+ estimated_mws: 0.000583
+ estimated_mw: 0.000067
+ thread_name: "perfetto_hprof_"
+ process_name: "com.fitbit.FitbitMobile"
+ thread_id: 5383
+ process_id: 5377
+ }
+ task_info {
+ estimated_mws: 0.000507
+ estimated_mw: 0.000059
+ thread_name: "Primes-2"
+ process_name: "com.fitbit.FitbitMobile"
+ thread_id: 5444
+ process_id: 5377
+ }
+ task_info {
+ estimated_mws: 0.000403
+ estimated_mw: 0.000047
+ thread_name: "FinalizerDaemon"
+ process_name: "com.fitbit.FitbitMobile"
+ thread_id: 5388
+ process_id: 5377
+ }
}
}
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/codecs/codec-framedecoder-trace.out b/test/trace_processor/diff_tests/metrics/codecs/codec-framedecoder-trace.out
index 1ba2a49..2873b57 100644
--- a/test/trace_processor/diff_tests/metrics/codecs/codec-framedecoder-trace.out
+++ b/test/trace_processor/diff_tests/metrics/codecs/codec-framedecoder-trace.out
@@ -3,7 +3,6 @@
process_name: "/system/bin/mediaserver"
thread_name: "CodecLooper"
thread_cpu_ns: 40782760
- num_threads: 1
core_data {
type: "bigger"
metrics {
@@ -32,6 +31,7 @@
thread_name: "CodecLooper"
total_cpu_ns: 6505730
running_cpu_ns: 6224689
+ cpu_cycles: 4
}
}
codec_function {
@@ -41,6 +41,7 @@
thread_name: "HwBinder:1465_4"
total_cpu_ns: 805417
running_cpu_ns: 805417
+ cpu_cycles: 1
}
}
codec_function {
@@ -50,6 +51,7 @@
thread_name: "CodecLooper"
total_cpu_ns: 34476253
running_cpu_ns: 19314740
+ cpu_cycles: 0
}
}
codec_function {
@@ -79,13 +81,6 @@
running_cpu_ns: 17917404
}
}
- codec_function {
- codec_string: "FrameDecoderBenchmark#frameDecoderBenchmarkTest"
- process_name: "android.platform.test.scenario"
- detail {
- thread_name: "roidJUnitRunner"
- total_cpu_ns: -1
- running_cpu_ns: -1
- }
+ energy {
}
}
diff --git a/test/trace_processor/diff_tests/metrics/graphics/tests.py b/test/trace_processor/diff_tests/metrics/graphics/tests.py
index 28a1f66..cc514a2 100644
--- a/test/trace_processor/diff_tests/metrics/graphics/tests.py
+++ b/test/trace_processor/diff_tests/metrics/graphics/tests.py
@@ -20,23 +20,6 @@
class GraphicsMetrics(TestSuite):
- # Android SurfaceFlinger metrics
- def test_frame_missed_event_frame_missed(self):
- return DiffTestBlueprint(
- trace=Path('frame_missed.py'),
- query="""
- SELECT RUN_METRIC('android/android_surfaceflinger.sql');
-
- SELECT ts, dur
- FROM android_surfaceflinger_event;
- """,
- out=Csv("""
- "ts","dur"
- 100,1
- 102,1
- 103,1
- """))
-
def test_frame_missed_metrics(self):
return DiffTestBlueprint(
trace=Path('frame_missed.py'),
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 be32033..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,9 +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"
}
}
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 1806323..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
@@ -57,6 +57,11 @@
dur_ns: 55
dur_ms: 5.5e-05
}
+ time_class_initialization {
+ dur_ns: 2
+ dur_ms: 2e-06
+ }
+ class_initialization_count: 2
}
activity_hosting_process_count: 1
process {
@@ -143,10 +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: 18
- slice_name: "CollectorTransition mark sweep GC"
}
}
slow_start_reason_with_details {
@@ -164,16 +175,24 @@
}
launch_dur: 999999900
trace_slice_sections {
- start_timestamp: 170
- end_timestamp: 500000000
- slice_id: 9
- slice_name: "OpenDexFilesFromOat(something else)"
- }
- 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)"
+ end_timestamp: 500000000
}
}
slow_start_reason_with_details {
@@ -189,10 +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: 19
- slice_name: "binder transaction"
}
}
}
diff --git a/test/trace_processor/diff_tests/metrics/startup/android_startup_attribution.py b/test/trace_processor/diff_tests/metrics/startup/android_startup_attribution.py
index dc3e0ab..2b98863 100644
--- a/test/trace_processor/diff_tests/metrics/startup/android_startup_attribution.py
+++ b/test/trace_processor/diff_tests/metrics/startup/android_startup_attribution.py
@@ -107,6 +107,15 @@
trace.add_atrace_begin(ts=270, pid=APP_PID, tid=APP_TID, buf='VerifyClass dl')
trace.add_atrace_end(ts=275, pid=APP_PID, tid=APP_TID)
+# class init slices within the startup.
+# class init slices will start with an L and end with a semicolon,
+# e.g. Lkotlin/text/MatchResult;
+trace.add_atrace_begin(ts=276, pid=APP_PID, tid=APP_TID, buf='Landroid/dummy;')
+trace.add_atrace_end(ts=277, pid=APP_PID, tid=APP_TID)
+
+trace.add_atrace_begin(ts=278, pid=APP_PID, tid=APP_TID, buf='Lcom/dummy;')
+trace.add_atrace_end(ts=279, pid=APP_PID, tid=APP_TID)
+
# VerifyClass slice outside the startup.
trace.add_atrace_begin(ts=55, pid=APP_PID, tid=APP_TID, buf='VerifyClass xf')
trace.add_atrace_end(ts=65, pid=APP_PID, tid=APP_TID)
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 dced654..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,10 +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"
}
}
slow_start_reason_with_details {
@@ -127,19 +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"
- }
- trace_thread_sections {
- start_timestamp: 170000000000
- end_timestamp: 175000000000
- thread_name: "Jit thread pool"
- }
- trace_thread_sections {
- start_timestamp: 185000000000
end_timestamp: 190000000000
- thread_name: "Jit thread pool"
}
}
slow_start_reason_with_details {
@@ -156,22 +172,32 @@
}
launch_dur: 999999900000000000
trace_slice_sections {
- start_timestamp: 200000000000
- end_timestamp: 210000000000
- slice_id: 84
- slice_name: "JIT compiling nothing"
- }
- 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"
- }
- trace_slice_sections {
- start_timestamp: 101000000000
- end_timestamp: 102000000000
- slice_id: 10
- slice_name: "JIT compiling something"
+ 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 d113186..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,10 +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"
}
}
slow_start_reason_with_details {
@@ -147,10 +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"
}
}
slow_start_reason_with_details {
@@ -167,10 +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"
}
}
slow_start_reason_with_details {
@@ -187,16 +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"
- }
- trace_slice_sections {
- start_timestamp: 191000000000
end_timestamp: 192000000000
- slice_id: 8
- slice_name: "inflate"
}
}
slow_start_reason_with_details {
@@ -213,10 +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"
}
}
slow_start_reason_with_details {
@@ -233,9 +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"
}
}
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 3044b4a..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,10 +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"
}
}
slow_start_reason_with_details {
@@ -147,10 +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"
}
}
slow_start_reason_with_details {
@@ -167,16 +179,24 @@
}
launch_dur: 108000000000
trace_slice_sections {
- start_timestamp: 190000000000
- end_timestamp: 192000000000
- slice_id: 8
- slice_name: "inflate"
- }
- 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"
+ end_timestamp: 192000000000
}
}
slow_start_reason_with_details {
@@ -193,10 +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"
}
}
slow_start_reason_with_details {
@@ -213,9 +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"
}
}
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 c8be143..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,22 +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"
- }
- trace_slice_sections {
- start_timestamp: 106
- end_timestamp: 107
- slice_id: 8
- slice_name: "Broadcast dispatched from android (2005:system/1000) x"
- }
- trace_slice_sections {
- start_timestamp: 107
end_timestamp: 108
- slice_id: 10
- slice_name: "Broadcast dispatched from android (2005:system/1000) x"
}
}
slow_start_reason_with_details {
@@ -78,22 +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"
- }
- trace_slice_sections {
- start_timestamp: 101
- end_timestamp: 102
- slice_id: 2
- slice_name: "broadcastReceiveReg: x"
- }
- trace_slice_sections {
- start_timestamp: 102
end_timestamp: 103
- slice_id: 3
- slice_name: "broadcastReceiveReg: x"
}
}
}
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 efe04c3..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,10 +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"
}
}
slow_start_reason_with_details {
@@ -102,6 +108,34 @@
dur: 27000000000
}
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: 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: 160000000000
+ }
}
slow_start_reason_with_details {
reason_id: MAIN_THREAD_MONITOR_CONTENTION
@@ -117,6 +151,26 @@
dur: 17000000000
}
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: 160000000000
+ }
}
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 9ca72c1..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,9 +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"
}
}
}
@@ -162,9 +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"
}
}
}
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 bb704dd..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,9 +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"
}
}
slow_start_reason_with_details {
@@ -100,9 +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"
}
}
slow_start_reason_with_details {
@@ -119,9 +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"
}
}
slow_start_reason_with_details {
@@ -138,9 +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"
}
}
}
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 ea4a98c..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,10 +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"
}
}
}
diff --git a/test/trace_processor/diff_tests/metrics/startup/tests_metrics.py b/test/trace_processor/diff_tests/metrics/startup/tests_metrics.py
index ab1e44a..88fae90 100644
--- a/test/trace_processor/diff_tests/metrics/startup/tests_metrics.py
+++ b/test/trace_processor/diff_tests/metrics/startup/tests_metrics.py
@@ -105,6 +105,7 @@
}
battery_aggregates {
avg_power_mw: 9497.722222222223
+ energy_usage_estimate: 1.3017599999999998
}
}
"""))
diff --git a/test/trace_processor/diff_tests/parser/android/surfaceflinger_layers.textproto b/test/trace_processor/diff_tests/parser/android/surfaceflinger_layers.textproto
index 7afbb6a..c8da434 100644
--- a/test/trace_processor/diff_tests/parser/android/surfaceflinger_layers.textproto
+++ b/test/trace_processor/diff_tests/parser/android/surfaceflinger_layers.textproto
@@ -48,12 +48,9 @@
children: 44
children: 77
children: 87
- type: "Layer"
layer_stack: 0
z: 0
crop {
- left: 0
- top: 0
right: -1
bottom: -1
}
@@ -393,6 +390,18 @@
}
is_virtual: false
}
+ displays {
+ id: 4619827677550801153
+ name: "Common Panel"
+ size {
+ w: 1080
+ h: 2400
+ }
+ layer_stack_space_rect {
+ right: 1080
+ bottom: 2400
+ }
+ }
vsync_id: 24767
}
trusted_uid: 1000
diff --git a/test/trace_processor/diff_tests/parser/android/tests_android_input_event.py b/test/trace_processor/diff_tests/parser/android/tests_android_input_event.py
index c7c8e96..4f91501 100644
--- a/test/trace_processor/diff_tests/parser/android/tests_android_input_event.py
+++ b/test/trace_processor/diff_tests/parser/android/tests_android_input_event.py
@@ -282,3 +282,24 @@
27,"vsync_id","0"
27,"window_id","0"
"""))
+
+ def test_tables_have_raw_protos(self):
+ return DiffTestBlueprint(
+ trace=Path('input_event_trace.textproto'),
+ query="""
+ INCLUDE PERFETTO MODULE android.input;
+ SELECT COUNT(*) FROM android_key_events
+ WHERE base64_proto IS NOT NULL AND base64_proto_id IS NOT NULL
+ UNION ALL
+ SELECT COUNT(*) FROM android_motion_events
+ WHERE base64_proto IS NOT NULL AND base64_proto_id IS NOT NULL
+ UNION ALL
+ SELECT COUNT(*) FROM android_input_event_dispatch
+ WHERE base64_proto IS NOT NULL AND base64_proto_id IS NOT NULL
+ """,
+ out=Csv("""
+ "COUNT(*)"
+ 2
+ 6
+ 30
+ """))
diff --git a/test/trace_processor/diff_tests/parser/android/tests_bugreport.py b/test/trace_processor/diff_tests/parser/android/tests_bugreport.py
index d67867d..b52f5f8 100644
--- a/test/trace_processor/diff_tests/parser/android/tests_bugreport.py
+++ b/test/trace_processor/diff_tests/parser/android/tests_bugreport.py
@@ -60,18 +60,36 @@
""",
out=Path('android_bugreport_dumpsys_test.out'))
- def test_android_bugreport_trace_types(self):
+ def test_android_bugreport_parse_order(self):
return DiffTestBlueprint(
trace=DataPath('bugreport-crosshatch-SPB5.zip'),
query="""
SELECT *
FROM __intrinsic_trace_file
- ORDER BY id
+ WHERE trace_type <> "unknown"
+ ORDER BY processing_order
""",
out=Csv("""
- "id","type","parent_id","name","size","trace_type"
- 0,"__intrinsic_trace_file","[NULL]","[NULL]",6220586,"zip"
- 1,"__intrinsic_trace_file",0,"FS/data/misc/logd/logcat.01",2169697,"android_logcat"
- 2,"__intrinsic_trace_file",0,"FS/data/misc/logd/logcat",2152073,"android_logcat"
- 3,"__intrinsic_trace_file",0,"bugreport-crosshatch-SPB5.210812.002-2021-08-24-23-35-40.txt",43132864,"android_dumpstate"
- """))
\ No newline at end of file
+ "id","type","parent_id","name","size","trace_type","processing_order"
+ 0,"__intrinsic_trace_file","[NULL]","[NULL]",6220586,"zip",0
+ 16,"__intrinsic_trace_file",0,"FS/data/misc/logd/logcat.01",2169697,"android_logcat",1
+ 15,"__intrinsic_trace_file",0,"FS/data/misc/logd/logcat",2152073,"android_logcat",2
+ 1,"__intrinsic_trace_file",0,"bugreport-crosshatch-SPB5.210812.002-2021-08-24-23-35-40.txt",43132864,"android_dumpstate",3
+ """))
+
+ def test_android_bugreport_trace_types(self):
+ return DiffTestBlueprint(
+ trace=DataPath('bugreport-crosshatch-SPB5.zip'),
+ query="""
+ SELECT trace_type, count(*) AS cnt, sum(size) AS total_size
+ FROM __intrinsic_trace_file
+ GROUP BY trace_type
+ ORDER BY trace_type
+ """,
+ out=Csv("""
+ "trace_type","cnt","total_size"
+ "android_dumpstate",1,43132864
+ "android_logcat",2,4321770
+ "unknown",2452,626115
+ "zip",1,6220586
+ """))
diff --git a/test/trace_processor/diff_tests/parser/android/tests_inputmethod_clients.py b/test/trace_processor/diff_tests/parser/android/tests_inputmethod_clients.py
index 74b2760..282a5b3 100644
--- a/test/trace_processor/diff_tests/parser/android/tests_inputmethod_clients.py
+++ b/test/trace_processor/diff_tests/parser/android/tests_inputmethod_clients.py
@@ -63,3 +63,16 @@
"client.ime_insets_source_consumer.insets_source_consumer.source_control.leash.hash_code","135479902"
"client.ime_insets_source_consumer.insets_source_consumer.source_control.leash.layerId","105"
"""))
+
+ def test_table_has_raw_protos(self):
+ return DiffTestBlueprint(
+ trace=Path('inputmethod_clients.textproto'),
+ query="""
+ INCLUDE PERFETTO MODULE android.winscope.inputmethod;
+ SELECT COUNT(*) FROM android_inputmethod_clients
+ WHERE base64_proto IS NOT NULL AND base64_proto_id IS NOT NULL
+ """,
+ out=Csv("""
+ "COUNT(*)"
+ 2
+ """))
diff --git a/test/trace_processor/diff_tests/parser/android/tests_inputmethod_manager_service.py b/test/trace_processor/diff_tests/parser/android/tests_inputmethod_manager_service.py
index 612f5b8..1295cfe 100644
--- a/test/trace_processor/diff_tests/parser/android/tests_inputmethod_manager_service.py
+++ b/test/trace_processor/diff_tests/parser/android/tests_inputmethod_manager_service.py
@@ -66,3 +66,16 @@
"input_method_manager_service.system_ready","true"
"where","InputMethodManagerService#startInputOrWindowGainedFocus"
"""))
+
+ def test_table_has_raw_protos(self):
+ return DiffTestBlueprint(
+ trace=Path('inputmethod_manager_service.textproto'),
+ query="""
+ INCLUDE PERFETTO MODULE android.winscope.inputmethod;
+ SELECT COUNT(*) FROM android_inputmethod_manager_service
+ WHERE base64_proto IS NOT NULL AND base64_proto_id IS NOT NULL
+ """,
+ out=Csv("""
+ "COUNT(*)"
+ 2
+ """))
diff --git a/test/trace_processor/diff_tests/parser/android/tests_inputmethod_service.py b/test/trace_processor/diff_tests/parser/android/tests_inputmethod_service.py
index 4ec909c..164fa8c 100644
--- a/test/trace_processor/diff_tests/parser/android/tests_inputmethod_service.py
+++ b/test/trace_processor/diff_tests/parser/android/tests_inputmethod_service.py
@@ -64,3 +64,16 @@
"input_method_service.token","android.os.BinderProxy@50043d1"
"where","InputMethodService#doFinishInput"
"""))
+
+ def test_table_has_raw_protos(self):
+ return DiffTestBlueprint(
+ trace=Path('inputmethod_service.textproto'),
+ query="""
+ INCLUDE PERFETTO MODULE android.winscope.inputmethod;
+ SELECT COUNT(*) FROM android_inputmethod_service
+ WHERE base64_proto IS NOT NULL AND base64_proto_id IS NOT NULL
+ """,
+ out=Csv("""
+ "COUNT(*)"
+ 2
+ """))
diff --git a/test/trace_processor/diff_tests/parser/android/tests_shell_transitions.py b/test/trace_processor/diff_tests/parser/android/tests_shell_transitions.py
index a8e0328..b691bfb 100644
--- a/test/trace_processor/diff_tests/parser/android/tests_shell_transitions.py
+++ b/test/trace_processor/diff_tests/parser/android/tests_shell_transitions.py
@@ -74,6 +74,18 @@
"type","1"
"""))
+ def test_shell_transitions_table_has_raw_protos(self):
+ return DiffTestBlueprint(
+ trace=Path('shell_transitions.textproto'),
+ query="""
+ SELECT COUNT(*) FROM window_manager_shell_transitions
+ WHERE base64_proto IS NOT NULL AND base64_proto_id IS NOT NULL
+ """,
+ out=Csv("""
+ "COUNT(*)"
+ 6
+ """))
+
def test_has_shell_handlers(self):
return DiffTestBlueprint(
trace=Path('shell_handlers.textproto'),
@@ -89,3 +101,15 @@
2,"RecentsTransitionHandler"
3,"FreeformTaskTransitionHandler"
"""))
+
+ def test_shell_handlers_table_has_raw_protos(self):
+ return DiffTestBlueprint(
+ trace=Path('shell_handlers.textproto'),
+ query="""
+ SELECT COUNT(*) FROM window_manager_shell_transition_handlers
+ WHERE base64_proto IS NOT NULL AND base64_proto_id IS NOT NULL
+ """,
+ out=Csv("""
+ "COUNT(*)"
+ 3
+ """))
diff --git a/test/trace_processor/diff_tests/parser/android/tests_surfaceflinger_layers.py b/test/trace_processor/diff_tests/parser/android/tests_surfaceflinger_layers.py
index ac1fcab..6ea3ff3 100644
--- a/test/trace_processor/diff_tests/parser/android/tests_surfaceflinger_layers.py
+++ b/test/trace_processor/diff_tests/parser/android/tests_surfaceflinger_layers.py
@@ -81,3 +81,19 @@
2,1,"surfaceflinger_layer"
3,1,"surfaceflinger_layer"
"""))
+
+ def test_tables_have_raw_protos(self):
+ return DiffTestBlueprint(
+ trace=Path('surfaceflinger_layers.textproto'),
+ query="""
+ SELECT COUNT(*) FROM surfaceflinger_layers_snapshot
+ WHERE base64_proto IS NOT NULL AND base64_proto_id IS NOT NULL
+ UNION ALL
+ SELECT COUNT(*) FROM surfaceflinger_layer
+ WHERE base64_proto IS NOT NULL AND base64_proto_id IS NOT NULL
+ """,
+ out=Csv("""
+ "COUNT(*)"
+ 2
+ 4
+ """))
diff --git a/test/trace_processor/diff_tests/parser/android/tests_surfaceflinger_transactions.py b/test/trace_processor/diff_tests/parser/android/tests_surfaceflinger_transactions.py
index cf83ce4..ff84af8 100644
--- a/test/trace_processor/diff_tests/parser/android/tests_surfaceflinger_transactions.py
+++ b/test/trace_processor/diff_tests/parser/android/tests_surfaceflinger_transactions.py
@@ -80,3 +80,15 @@
"transactions[0].vsync_id","24769"
"vsync_id","24776"
"""))
+
+ def test_table_has_raw_protos(self):
+ return DiffTestBlueprint(
+ trace=Path('surfaceflinger_transactions.textproto'),
+ query="""
+ SELECT COUNT(*) FROM surfaceflinger_transactions
+ WHERE base64_proto IS NOT NULL AND base64_proto_id IS NOT NULL
+ """,
+ out=Csv("""
+ "COUNT(*)"
+ 2
+ """))
diff --git a/test/trace_processor/diff_tests/parser/android/tests_viewcapture.py b/test/trace_processor/diff_tests/parser/android/tests_viewcapture.py
index e089ecb..a99946e 100644
--- a/test/trace_processor/diff_tests/parser/android/tests_viewcapture.py
+++ b/test/trace_processor/diff_tests/parser/android/tests_viewcapture.py
@@ -79,3 +79,16 @@
"key","display_value"
"views[1].class_name","STRING DE-INTERNING ERROR"
"""))
+
+ def test_table_has_raw_protos(self):
+ return DiffTestBlueprint(
+ trace=Path('viewcapture.textproto'),
+ query="""
+ INCLUDE PERFETTO MODULE android.winscope.viewcapture;
+ SELECT COUNT(*) FROM android_viewcapture
+ WHERE base64_proto IS NOT NULL AND base64_proto_id IS NOT NULL
+ """,
+ out=Csv("""
+ "COUNT(*)"
+ 2
+ """))
diff --git a/test/trace_processor/diff_tests/parser/android/tests_windowmanager.py b/test/trace_processor/diff_tests/parser/android/tests_windowmanager.py
index 9349b8d..3946458 100644
--- a/test/trace_processor/diff_tests/parser/android/tests_windowmanager.py
+++ b/test/trace_processor/diff_tests/parser/android/tests_windowmanager.py
@@ -63,3 +63,16 @@
"window_manager_service.policy.keyguard_delegate.screen_state","SCREEN_STATE_ON"
"window_manager_service.policy.keyguard_draw_complete","true"
"""))
+
+ def test_table_has_raw_protos(self):
+ return DiffTestBlueprint(
+ trace=Path('windowmanager.textproto'),
+ query="""
+ INCLUDE PERFETTO MODULE android.winscope.windowmanager;
+ SELECT COUNT(*) FROM android_windowmanager
+ WHERE base64_proto IS NOT NULL AND base64_proto_id IS NOT NULL
+ """,
+ out=Csv("""
+ "COUNT(*)"
+ 2
+ """))
diff --git a/test/trace_processor/diff_tests/parser/art_method/tests.py b/test/trace_processor/diff_tests/parser/art_method/tests.py
new file mode 100644
index 0000000..1a62b42
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/art_method/tests.py
@@ -0,0 +1,72 @@
+#!/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 DataPath
+from python.generators.diff_tests.testing import Csv
+from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import TestSuite
+
+
+class ArtMethodParser(TestSuite):
+
+ def test_art_method_smoke(self):
+ return DiffTestBlueprint(
+ trace=DataPath('art-method-tracing.trace'),
+ query="""
+ INCLUDE PERFETTO MODULE slices.with_context;
+
+ SELECT ts, dur, name, thread_name, extract_arg(arg_set_id, 'pathname') AS pathname
+ FROM thread_slice
+ ORDER BY dur desc
+ LIMIT 10
+ """,
+ out=Csv('''
+ "ts","dur","name","thread_name","pathname"
+ 430421819633000,182000,"androidx.benchmark.MethodTracing.start: (Ljava/lang/String;)Landroidx/benchmark/Profiler$ResultFile;","Instr: androidx.test.runner.AndroidJUnitRunner","Profiler.kt"
+ 430421819633000,175000,"androidx.benchmark.ProfilerKt.startRuntimeMethodTracing: (Ljava/lang/String;Z)Landroidx/benchmark/Profiler$ResultFile;","Instr: androidx.test.runner.AndroidJUnitRunner","Profiler.kt"
+ 430421819634000,67000,"android.os.Debug.startMethodTracing: (Ljava/lang/String;II)V","Instr: androidx.test.runner.AndroidJUnitRunner","Debug.java"
+ 430421819635000,62000,"dalvik.system.VMDebug.startMethodTracing: (Ljava/lang/String;IIZI)V","Instr: androidx.test.runner.AndroidJUnitRunner","VMDebug.java"
+ 430421819636000,57000,"dalvik.system.VMDebug.startMethodTracingFilename: (Ljava/lang/String;IIZI)V","Instr: androidx.test.runner.AndroidJUnitRunner","VMDebug.java"
+ 430421819788000,19000,"androidx.benchmark.Profiler$ResultFile.<init>: (Ljava/lang/String;Ljava/lang/String;)V","Instr: androidx.test.runner.AndroidJUnitRunner","Profiler.kt"
+ 430421819795000,2000,"kotlin.jvm.internal.Intrinsics.checkNotNullParameter: (Ljava/lang/Object;Ljava/lang/String;)V","Instr: androidx.test.runner.AndroidJUnitRunner","Intrinsics.java"
+ 430421819817000,2000,"androidx.benchmark.vmtrace.ArtTraceTest.myTracedMethod: ()V","Instr: androidx.test.runner.AndroidJUnitRunner","ArtTraceTest.kt"
+ 430421819801000,1000,"kotlin.jvm.internal.Intrinsics.checkNotNullParameter: (Ljava/lang/Object;Ljava/lang/String;)V","Instr: androidx.test.runner.AndroidJUnitRunner","Intrinsics.java"
+ 430421819804000,1000,"java.lang.Object.<init>: ()V","Instr: androidx.test.runner.AndroidJUnitRunner","Object.java"
+ '''))
+
+ def test_art_method_streaming_smoke(self):
+ return DiffTestBlueprint(
+ trace=DataPath('art-method-tracing-streaming.trace'),
+ query="""
+ INCLUDE PERFETTO MODULE slices.with_context;
+
+ SELECT ts, dur, name, thread_name, extract_arg(arg_set_id, 'pathname') AS pathname
+ FROM thread_slice
+ ORDER BY dur desc
+ LIMIT 10
+ """,
+ out=Csv('''
+ "ts","dur","name","thread_name","pathname"
+ 793682939000,26513017000,"java.util.concurrent.ThreadPoolExecutor.getTask: ()Ljava/lang/Runnable;","AsyncTask #1","ThreadPoolExecutor.java"
+ 793682939000,26513017000,"java.util.concurrent.LinkedBlockingQueue.take: ()Ljava/lang/Object;","AsyncTask #1","LinkedBlockingQueue.java"
+ 793682939000,26513017000,"java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await: ()V","AsyncTask #1","AbstractQueuedSynchronizer.java"
+ 793682939000,26513017000,"java.util.concurrent.locks.LockSupport.park: (Ljava/lang/Object;)V","AsyncTask #1","LockSupport.java"
+ 793682939000,26513017000,"sun.misc.Unsafe.park: (ZJ)V","AsyncTask #1","Unsafe.java"
+ 793682939000,26513017000,"java.lang.Thread.parkFor$: (J)V","AsyncTask #1","Thread.java"
+ 793682939000,26513017000,"java.lang.Object.wait: (JI)V","AsyncTask #1","Object.java"
+ 810910588000,13004716000,"java.lang.Object.wait: ()V","ReferenceQueueDaemon","Object.java"
+ 808685761000,12599705000,"java.lang.Object.wait: (JI)V","OkHttp ConnectionPool","Object.java"
+ 800148789000,10759203000,"java.lang.Object.wait: ()V","ReferenceQueueDaemon","Object.java"
+ '''))
diff --git a/test/trace_processor/diff_tests/parser/chrome/tests_v8.py b/test/trace_processor/diff_tests/parser/chrome/tests_v8.py
index f77a47a..87c8584 100644
--- a/test/trace_processor/diff_tests/parser/chrome/tests_v8.py
+++ b/test/trace_processor/diff_tests/parser/chrome/tests_v8.py
@@ -105,15 +105,12 @@
return DiffTestBlueprint(
trace=DataPath('v8-samples.pftrace'),
query='''
- include perfetto module callstacks.stack_profile;
+ include perfetto module stacks.cpu_profiling;
select name, source_file, self_count
- from _callstacks_for_cpu_profile_stack_samples!(
- cpu_profile_stack_sample
- )
- where self_count > 0
- order by self_count desc
- limit 20
+ from cpu_profiling_summary_tree
+ where self_count >= 15
+ order by self_count desc, source_file
''',
out=Csv('''
"name","source_file","self_count"
@@ -130,11 +127,32 @@
"","https://www.google.com/xjs/_/js/k=xjs.hd.en.nSJdbfIGUiE.O/am=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAACAEKAAAABR4AAAAgAAAAAAAAAAQIAQDEAQAAAgA4AAAEAQAEABQQAAAKEATgUTYAgAAwAQAIAAAQAAACQAAACAAAAAMAACAIAAAAAKAAAAAAAAAAAAAAAAAAYAABBAAAAAAAAAAAAIACAAAAoAMAAAAAgAAAgIAAANghAwgAAAQAAACgDwCCB8AghQcAAAAAAAAAAAAAAAKQIJgLCSgIQAAAAAAAAAAAAAAAAACkpIkLCw/d=1/ed=1/dg=3/br=1/rs=ACT90oH8sSQRHJq5R0DO9ABVW-vZJa5Baw/ee=ALeJib:B8gLwd;AfeaP:TkrAjf;BMxAGc:E5bFse;BgS6mb:fidj5d;BjwMce:cXX2Wb;CxXAWb:YyRLvc;DULqB:RKfG5c;Dkk6ge:wJqrrd;DpcR3d:zL72xf;EABSZ:MXZt9d;ESrPQc:mNTJvc;EVNhjf:pw70Gc;EmZ2Bf:zr1jrb;EnlcNd:WeHg4;Erl4fe:FloWmf,FloWmf;F9mqte:UoRcbe;Fmv9Nc:O1Tzwc;G0KhTb:LIaoZ;G6wU6e:hezEbd;GleZL:J1A7Od;HMDDWe:G8QUdb;HoYVKb:PkDN7e;HqeXPd:cmbnH;IBADCc:RYquRb;IZrNqe:P8ha2c;IoGlCf:b5lhvb;IsdWVc:qzxzOb;JXS8fb:Qj0suc;JbMT3:M25sS;JsbNhc:Xd8iUd;KOxcK:OZqGte;KQzWid:ZMKkN;KcokUb:KiuZBf;KpRAue:Tia57b;LBgRLc:SdcwHb,XVMNvd;LEikZe:byfTOb,lsjVmc;LXA8b:q7OdKd;LsNahb:ucGLNb;Me32dd:MEeYgc;NPKaK:SdcwHb;NSEoX:lazG7b;Np8Qkd:Dpx6qc;Nyt6ic:jn2sGd;OgagBe:",33
"_.m.Ddb","https://www.google.com/xjs/_/js/k=xjs.hd.en.nSJdbfIGUiE.O/am=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAACAEKAAAABR4AAAAgAAAAAAAAAAQIAQDEAQAAAgA4AAAEAQAEABQQAAAKEATgUTYAgAAwAQAIAAAQAAACQAAACAAAAAMAACAIAAAAAKAAAAAAAAAAAAAAAAAAYAABBAAAAAAAAAAAAIACAAAAoAMAAAAAgAAAgIAAANghAwgAAAQAAACgDwCCB8AghQcAAAAAAAAAAAAAAAKQIJgLCSgIQAAAAAAAAAAAAAAAAACkpIkLCw/d=1/ed=1/dg=3/br=1/rs=ACT90oH8sSQRHJq5R0DO9ABVW-vZJa5Baw/ee=ALeJib:B8gLwd;AfeaP:TkrAjf;BMxAGc:E5bFse;BgS6mb:fidj5d;BjwMce:cXX2Wb;CxXAWb:YyRLvc;DULqB:RKfG5c;Dkk6ge:wJqrrd;DpcR3d:zL72xf;EABSZ:MXZt9d;ESrPQc:mNTJvc;EVNhjf:pw70Gc;EmZ2Bf:zr1jrb;EnlcNd:WeHg4;Erl4fe:FloWmf,FloWmf;F9mqte:UoRcbe;Fmv9Nc:O1Tzwc;G0KhTb:LIaoZ;G6wU6e:hezEbd;GleZL:J1A7Od;HMDDWe:G8QUdb;HoYVKb:PkDN7e;HqeXPd:cmbnH;IBADCc:RYquRb;IZrNqe:P8ha2c;IoGlCf:b5lhvb;IsdWVc:qzxzOb;JXS8fb:Qj0suc;JbMT3:M25sS;JsbNhc:Xd8iUd;KOxcK:OZqGte;KQzWid:ZMKkN;KcokUb:KiuZBf;KpRAue:Tia57b;LBgRLc:SdcwHb,XVMNvd;LEikZe:byfTOb,lsjVmc;LXA8b:q7OdKd;LsNahb:ucGLNb;Me32dd:MEeYgc;NPKaK:SdcwHb;NSEoX:lazG7b;Np8Qkd:Dpx6qc;Nyt6ic:jn2sGd;OgagBe:",18
"da","https://www.google.com/",15
- "","https://www.gstatic.com/_/mss/boq-one-google/_/js/k=boq-one-google.OneGoogleWidgetUi.en.Dv_TT86KXl4.es5.O/ck=boq-one-google.OneGoogleWidgetUi.xexmpZqkioA.L.B1.O/am=QKBgwGw/d=1/exm=_b,_tp/excm=_b,_tp,calloutview/ed=1/wt=2/ujg=1/rs=AM-SdHu61g-i-YBZiLcGm3tURf4VJO5hyA/ee=EVNhjf:pw70Gc;EmZ2Bf:zr1jrb;Erl4fe:FloWmf;JsbNhc:Xd8iUd;LBgRLc:SdcwHb;Me32dd:MEeYgc;NPKaK:SdcwHb;NSEoX:lazG7b;Oj465e:KG2eXe;Pjplud:EEDORb;QGR0gd:Mlhmy;SNUn3:ZwDk9d;a56pNe:JEfCwb;cEt90b:ws9Tlc;dIoSBb:SpsfSb;eBAeSb:zbML3c;iFQyKf:QIhFr;io8t5d:yDVVkb;kMFpHd:OTA3Ae;nAFL3:s39S4;oGtAuc:sOXFj;pXdRYb:MdUzUe;qddgKe:xQtZb;sP4Vbe:VwDzFe;uY49fb:COQbmf;ul9GGd:VDovNc;wR5FRb:O1Gjze;xqZiqf:wmnU7d;yxTchf:KUM7Z;zxnPse:GkRiKb/m=ws9Tlc,n73qwf,GkRiKb,e5qFLc,IZT63,UUJqVe,O1Gjze,byfTOb,lsjVmc,xUdipf,OTA3Ae,COQbmf,fKUV3e,aurFic,U0aPgd,ZwDk9d,V3dDOb,mI3LFb,yYB61,O6y8ed,PrPYRd,MpJwZc,LEikZe,NwH0H,OmgaI,lazG7b,XVMNvd,L1AAkb,KUM7Z,Mlhmy,s39S4,lwddkf,gychg,w9hDv,EEDORb,RMhBfe,SdcwHb,aW3pY,pw70Gc,EFQ78c,Ulmmrd,ZfAoz,mdR7q,wmnU7d,xQtZb,JNoxi,kWgXee,MI6k7c,kjKdXe,BVgquf,QIhFr,ov",13
- "updateAttrs","https://ui.perfetto.dev/v46.0-0a53e685b/frontend_bundle.js",12
- "","https://www.gstatic.com/_/mss/boq-one-google/_/js/k=boq-one-google.OneGoogleWidgetUi.en.Dv_TT86KXl4.es5.O/am=QKBgwGw/d=1/excm=_b,_tp,calloutview/ed=1/dg=0/wt=2/ujg=1/rs=AM-SdHsuxqEW2z6uUf-9MJvUVpOyFk0ecQ/m=_b,_tp",11
- "a._isVisible","https://ogs.google.com/widget/callout?prid=19037050&pgid=19037049&puid=6a851fbb7ce797ac&eom=1&cce=1&dc=1&origin=https%3A%2F%2Fwww.google.com&cn=callout&pid=1&spid=538&hl=en&dm=",11
- "","chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js",11
- "","https://www.google.com/xjs/_/js/k=xjs.hd.en.nSJdbfIGUiE.O/ck=xjs.hd.F00K1IyvS9A.L.B1.O/am=IFEAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAIAAAEAAAAAAAASAEakAAABZ5sAMBiAAAABAAIAAQIAQDEAQAAAwQ4AAAEAQAUABQREAEKEgTgUTYAhIAwAQQoQAgUQAICQBCFCAAAAAMAACEIDDAMQKgAYBQgAAAAAEBABAAAYAA3BhAgAMAPAAAYAKICAAAhoAMQAAABgAJAgIACAtghAwgAAAQAAACgDwCCB8AghQcAAAAAAAAAAAAAAAKQIJgLCSgIQAAAAAAAAAAAAAAAAACkpIkLCw/d=0/dg=0/br=1/ujg=1/rs=ACT90oFYV6TnvY5P3NcVPbMRvVPRlxmm8A/m=sb_wiz,aa,abd,sytt,syts,sytn,syfx,sytr,sytd,sy101,syz7,syti,syz6,syto,sytq,sytm,syu7,sytb,syu8,syu9,syu0,syu4,sytj,syty,syu1,syu2,sytv,sytw,syte,sytf,sys4,syru,syrs,syrr,syth,syz5,syug,syuh,syuf,async,syvk,ifl,pHXghd,sf,sy1c2,sy1c5,sy4e0,sonic,TxCJfd,sy4e4,qzxzOb,IsdWVc,sy4e6,sy1gs,sy1d4,sy1d0,syrq,syro,syrp,syrn,syrm,sy4cl,sy4co,sy2ib,sy18p,sy18r,sy13l,sy13m,syrj,syrh,syfb,sybv,syby,sybt,sybx,sybw,sycp,spch,sys7,sys6,rtH1bd,sy1ea,sy19r,sy18g,syg9,sy1e9,sy13t,sy1e8,sy18h,sygb,sy1eb,SMquOb,sy8f,sygh,sygf,sygg,sygi,syge,sygp,sygn,sygl,sygd,sycm,sych,syck,syak,syac,syb6,syaj,syai,sya",10
- "maybeUpdateMoreOptions","chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js",10
+ '''))
+
+ def test_v8_cpu_samples_json(self):
+ return DiffTestBlueprint(
+ trace=DataPath('v8-samples.json'),
+ query='''
+ include perfetto module stacks.cpu_profiling;
+
+ select name, source_file, self_count
+ from cpu_profiling_summary_tree
+ where self_count >= 15
+ order by self_count desc, name
+ ''',
+ out=Csv('''
+ "name","source_file","self_count"
+ "(program)","[NULL]",17083
+ "(program)","[NULL]",15399
+ "(program)","[NULL]",9853
+ "(program)","[NULL]",9391
+ "(program)","[NULL]",7299
+ "(program)","[NULL]",5245
+ "(program)","[NULL]",2443
+ "(garbage collector)","[NULL]",107
+ "_.mg","chrome-untrusted://new-tab-page/one-google-bar?paramsencoded=",38
+ "(garbage collector)","[NULL]",34
+ "","https://www.google.com/xjs/_/js/k=xjs.hd.en.nSJdbfIGUiE.O/am=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAACAEKAAAABR4AAAAgAAAAAAAAAAQIAQDEAQAAAgA4AAAEAQAEABQQAAAKEATgUTYAgAAwAQAIAAAQAAACQAAACAAAAAMAACAIAAAAAKAAAAAAAAAAAAAAAAAAYAABBAAAAAAAAAAAAIACAAAAoAMAAAAAgAAAgIAAANghAwgAAAQAAACgDwCCB8AghQcAAAAAAAAAAAAAAAKQIJgLCSgIQAAAAAAAAAAAAAAAAACkpIkLCw/d=1/ed=1/dg=3/br=1/rs=ACT90oH8sSQRHJq5R0DO9ABVW-vZJa5Baw/ee=ALeJib:B8gLwd;AfeaP:TkrAjf;BMxAGc:E5bFse;BgS6mb:fidj5d;BjwMce:cXX2Wb;CxXAWb:YyRLvc;DULqB:RKfG5c;Dkk6ge:wJqrrd;DpcR3d:zL72xf;EABSZ:MXZt9d;ESrPQc:mNTJvc;EVNhjf:pw70Gc;EmZ2Bf:zr1jrb;EnlcNd:WeHg4;Erl4fe:FloWmf,FloWmf;F9mqte:UoRcbe;Fmv9Nc:O1Tzwc;G0KhTb:LIaoZ;G6wU6e:hezEbd;GleZL:J1A7Od;HMDDWe:G8QUdb;HoYVKb:PkDN7e;HqeXPd:cmbnH;IBADCc:RYquRb;IZrNqe:P8ha2c;IoGlCf:b5lhvb;IsdWVc:qzxzOb;JXS8fb:Qj0suc;JbMT3:M25sS;JsbNhc:Xd8iUd;KOxcK:OZqGte;KQzWid:ZMKkN;KcokUb:KiuZBf;KpRAue:Tia57b;LBgRLc:SdcwHb,XVMNvd;LEikZe:byfTOb,lsjVmc;LXA8b:q7OdKd;LsNahb:ucGLNb;Me32dd:MEeYgc;NPKaK:SdcwHb;NSEoX:lazG7b;Np8Qkd:Dpx6qc;Nyt6ic:jn2sGd;OgagBe:",33
+ "_.m.Ddb","https://www.google.com/xjs/_/js/k=xjs.hd.en.nSJdbfIGUiE.O/am=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAACAEKAAAABR4AAAAgAAAAAAAAAAQIAQDEAQAAAgA4AAAEAQAEABQQAAAKEATgUTYAgAAwAQAIAAAQAAACQAAACAAAAAMAACAIAAAAAKAAAAAAAAAAAAAAAAAAYAABBAAAAAAAAAAAAIACAAAAoAMAAAAAgAAAgIAAANghAwgAAAQAAACgDwCCB8AghQcAAAAAAAAAAAAAAAKQIJgLCSgIQAAAAAAAAAAAAAAAAACkpIkLCw/d=1/ed=1/dg=3/br=1/rs=ACT90oH8sSQRHJq5R0DO9ABVW-vZJa5Baw/ee=ALeJib:B8gLwd;AfeaP:TkrAjf;BMxAGc:E5bFse;BgS6mb:fidj5d;BjwMce:cXX2Wb;CxXAWb:YyRLvc;DULqB:RKfG5c;Dkk6ge:wJqrrd;DpcR3d:zL72xf;EABSZ:MXZt9d;ESrPQc:mNTJvc;EVNhjf:pw70Gc;EmZ2Bf:zr1jrb;EnlcNd:WeHg4;Erl4fe:FloWmf,FloWmf;F9mqte:UoRcbe;Fmv9Nc:O1Tzwc;G0KhTb:LIaoZ;G6wU6e:hezEbd;GleZL:J1A7Od;HMDDWe:G8QUdb;HoYVKb:PkDN7e;HqeXPd:cmbnH;IBADCc:RYquRb;IZrNqe:P8ha2c;IoGlCf:b5lhvb;IsdWVc:qzxzOb;JXS8fb:Qj0suc;JbMT3:M25sS;JsbNhc:Xd8iUd;KOxcK:OZqGte;KQzWid:ZMKkN;KcokUb:KiuZBf;KpRAue:Tia57b;LBgRLc:SdcwHb,XVMNvd;LEikZe:byfTOb,lsjVmc;LXA8b:q7OdKd;LsNahb:ucGLNb;Me32dd:MEeYgc;NPKaK:SdcwHb;NSEoX:lazG7b;Np8Qkd:Dpx6qc;Nyt6ic:jn2sGd;OgagBe:",18
+ "da","https://www.google.com/",15
'''))
diff --git a/test/trace_processor/diff_tests/parser/ftrace/kprobes_tests.py b/test/trace_processor/diff_tests/parser/ftrace/kprobes_tests.py
new file mode 100644
index 0000000..9d9c16c
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/ftrace/kprobes_tests.py
@@ -0,0 +1,83 @@
+#!/usr/bin/env python3
+# 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 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 Csv, TextProto
+from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import TestSuite
+
+
+class Kprobes(TestSuite):
+
+ def test_kprobes_slice(self):
+ return DiffTestBlueprint(
+ trace=TextProto(r"""
+ packet { ftrace_events {
+ cpu: 1
+ event {
+ timestamp: 1500
+ pid: 42
+ kprobe_event {
+ name: "fuse_file_write_iter"
+ type: KPROBE_TYPE_BEGIN
+ }
+ }
+ event {
+ timestamp: 2700
+ pid: 42
+ kprobe_event {
+ name: "fuse_file_write_iter"
+ type: KPROBE_TYPE_END
+ }
+ }
+ }}
+ """),
+ query="""
+ select
+ ts,
+ dur as slice_dur,
+ slice.name as slice_name
+ from slice
+ """,
+ out=Csv("""
+ "ts","slice_dur","slice_name"
+ 1500,1200,"fuse_file_write_iter"
+ """))
+
+ def test_kprobes_instant(self):
+ return DiffTestBlueprint(
+ trace=TextProto(r"""
+ packet { ftrace_events {
+ cpu: 1
+ event {
+ timestamp: 1500
+ pid: 42
+ kprobe_event {
+ name: "fuse_file_write_iter"
+ type: KPROBE_TYPE_INSTANT
+ }
+ }
+ }}
+ """),
+ query="""
+ select
+ ts,
+ dur as slice_dur,
+ slice.name as slice_name
+ from slice
+ """,
+ out=Csv("""
+ "ts","slice_dur","slice_name"
+ 1500,0,"fuse_file_write_iter"
+ """))
diff --git a/test/trace_processor/diff_tests/parser/fuchsia/tests.py b/test/trace_processor/diff_tests/parser/fuchsia/tests.py
index d7b8b4d..59f2673 100644
--- a/test/trace_processor/diff_tests/parser/fuchsia/tests.py
+++ b/test/trace_processor/diff_tests/parser/fuchsia/tests.py
@@ -238,20 +238,30 @@
query="""
SELECT key,int_value,string_value,real_value,value_type,display_value
FROM args
- LIMIT 12;
+ GROUP BY key
+ ORDER BY key
""",
out=Csv("""
"key","int_value","string_value","real_value","value_type","display_value"
"SomeNullArg","[NULL]","null","[NULL]","string","null"
- "Someuint32",2145,"[NULL]","[NULL]","int","2145"
- "Someuint64",423621626134123415,"[NULL]","[NULL]","int","423621626134123415"
+ "Somedouble","[NULL]","[NULL]",3.141500,"real","3.1415"
"Someint32",-7,"[NULL]","[NULL]","int","-7"
"Someint64",-234516543631231,"[NULL]","[NULL]","int","-234516543631231"
- "Somedouble","[NULL]","[NULL]",3.141500,"real","3.1415"
+ "Someuint32",2145,"[NULL]","[NULL]","int","2145"
+ "Someuint64",423621626134123415,"[NULL]","[NULL]","int","423621626134123415"
+ "cookie",658,"[NULL]","[NULL]","int","658"
+ "name","[NULL]","example_counter:somedataseries:0","[NULL]","string","example_counter:somedataseries:0"
"ping","[NULL]","pong","[NULL]","string","pong"
- "somepointer",3285933758964,"[NULL]","[NULL]","pointer","0x2fd10ea19f4"
- "someotherpointer",43981,"[NULL]","[NULL]","pointer","0xabcd"
- "somekoid",18,"[NULL]","[NULL]","int","18"
+ "scope","[NULL]","[NULL]","[NULL]","string","[NULL]"
"somebool",1,"[NULL]","[NULL]","bool","true"
+ "somekoid",18,"[NULL]","[NULL]","int","18"
"someotherbool",0,"[NULL]","[NULL]","bool","false"
+ "someotherpointer",43981,"[NULL]","[NULL]","pointer","0xabcd"
+ "somepointer",3285933758964,"[NULL]","[NULL]","pointer","0x2fd10ea19f4"
+ "source","[NULL]","chrome","[NULL]","string","chrome"
+ "source_scope","[NULL]","[NULL]","[NULL]","string","[NULL]"
+ "trace_id",658,"[NULL]","[NULL]","int","658"
+ "trace_id_is_process_scoped",0,"[NULL]","[NULL]","bool","false"
+ "upid",1,"[NULL]","[NULL]","int","1"
+ "utid",1,"[NULL]","[NULL]","int","1"
"""))
diff --git a/test/trace_processor/diff_tests/parser/gecko/tests.py b/test/trace_processor/diff_tests/parser/gecko/tests.py
new file mode 100644
index 0000000..3ef6697
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/gecko/tests.py
@@ -0,0 +1,71 @@
+#!/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 DataPath
+from python.generators.diff_tests.testing import Csv
+from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import TestSuite
+
+
+class GeckoParser(TestSuite):
+
+ def test_gecko_samples_smoke(self):
+ return DiffTestBlueprint(
+ trace=DataPath('trace_processor_perf_as_gecko.json'),
+ query="""
+ INCLUDE PERFETTO MODULE stacks.cpu_profiling;
+
+ SELECT id, parent_id, name, mapping_name, self_count, cumulative_count
+ FROM cpu_profiling_summary_tree
+ LIMIT 10
+ """,
+ out=Csv('''
+ "id","parent_id","name","mapping_name","self_count","cumulative_count"
+ 0,"[NULL]","__libc_start_call_main","/usr/lib/x86_64-linux-gnu/libc.so.6",0,37030
+ 1,0,"main","/usr/local/google/home/lalitm/perfetto/out/linux_clang_release/trace_processor_shell",0,37030
+ 2,1,"perfetto::trace_processor::(anonymous namespace)::TraceProcessorMain(int, char**)","/usr/local/google/home/lalitm/perfetto/out/linux_clang_release/trace_processor_shell",0,37030
+ 3,2,"perfetto::trace_processor::(anonymous namespace)::StartInteractiveShell(perfetto::trace_processor::(anonymous namespace)::InteractiveOptions const&)","/usr/local/google/home/lalitm/perfetto/out/linux_clang_release/trace_processor_shell",0,37029
+ 4,3,"read","/usr/lib/x86_64-linux-gnu/libc.so.6",8,8
+ 5,3,"cfree@GLIBC_2.2.5","/usr/lib/x86_64-linux-gnu/libc.so.6",1,1
+ 6,2,"clock_gettime@@GLIBC_2.17","/usr/lib/x86_64-linux-gnu/libc.so.6",1,1
+ 7,3,"perfetto::trace_processor::TraceProcessorImpl::ExecuteQuery(std::__Cr::basic_string<char, std::__Cr::char_traits<char>, std::__Cr::allocator<char> > const&)","/usr/local/google/home/lalitm/perfetto/out/linux_clang_release/trace_processor_shell",0,37020
+ 8,7,"perfetto::trace_processor::PerfettoSqlEngine::ExecuteUntilLastStatement(perfetto::trace_processor::SqlSource)","/usr/local/google/home/lalitm/perfetto/out/linux_clang_release/trace_processor_shell",0,37020
+ 9,8,"perfetto::trace_processor::PerfettoSqlEngine::ExecuteInclude(perfetto::trace_processor::PerfettoSqlParser::Include const&, perfetto::trace_processor::PerfettoSqlParser const&)","/usr/local/google/home/lalitm/perfetto/out/linux_clang_release/trace_processor_shell",0,37020
+ '''))
+
+ def test_gecko_samples_simpleperf_smoke(self):
+ return DiffTestBlueprint(
+ trace=DataPath('simpleperf_as_gecko.json'),
+ query="""
+ INCLUDE PERFETTO MODULE stacks.cpu_profiling;
+
+ SELECT id, parent_id, name, mapping_name, self_count, cumulative_count
+ FROM cpu_profiling_summary_tree
+ ORDER BY cumulative_count desc
+ LIMIT 10
+ """,
+ out=Csv('''
+ "id","parent_id","name","mapping_name","self_count","cumulative_count"
+ 13260,"[NULL]","__start_thread","/apex/com.android.runtime/lib64/bionic/libc.so",0,5551
+ 13261,13260,"__pthread_start(void*)","/apex/com.android.runtime/lib64/bionic/libc.so",0,5551
+ 13262,13261,"art::Thread::CreateCallbackWithUffdGc(void*)","/apex/com.android.art/lib64/libart.so",0,3043
+ 13263,13262,"art::Thread::CreateCallback(void*)","/apex/com.android.art/lib64/libart.so",2,3043
+ 13266,13263,"art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*)","/apex/com.android.art/lib64/libart.so",0,3036
+ 13267,13266,"art_quick_invoke_stub","/apex/com.android.art/lib64/libart.so",0,3036
+ 13268,13267,"java.lang.Thread.run","/system/framework/arm64/boot.oat",0,2159
+ 0,"[NULL]","__libc_init","/apex/com.android.runtime/lib64/bionic/libc.so",0,1714
+ 1,0,"main","/system/bin/app_process64",0,1714
+ 2,1,"android::AndroidRuntime::start(char const*, android::Vector<android::String8> const&, bool)","/system/lib64/libandroid_runtime.so",0,1714
+ '''))
diff --git a/test/trace_processor/diff_tests/parser/json/tests.py b/test/trace_processor/diff_tests/parser/json/tests.py
index f5e1d8b..5b981ac 100644
--- a/test/trace_processor/diff_tests/parser/json/tests.py
+++ b/test/trace_processor/diff_tests/parser/json/tests.py
@@ -135,3 +135,18 @@
1000,10000,"Parent",0
1000,5000,"Child",1
"""))
+
+ def test_json_incomplete(self):
+ return DiffTestBlueprint(
+ trace=Json('''
+ [
+ {"name":"typecheck","ph":"X","ts":4619295550.000,"dur":8000.000,"pid":306339,"tid":3},
+ '''),
+ query='''
+ select ts from slice
+ ''',
+ out=Csv('''
+ "ts"
+ 4619295550000
+ ''')
+ )
diff --git a/test/trace_processor/diff_tests/parser/parsing/otheruuids.textproto b/test/trace_processor/diff_tests/parser/parsing/otheruuids.textproto
deleted file mode 100644
index e5281e8..0000000
--- a/test/trace_processor/diff_tests/parser/parsing/otheruuids.textproto
+++ /dev/null
@@ -1,46 +0,0 @@
-packet {
- ftrace_events {
- cpu: 4
- event {
- timestamp: 171311335293
- pid: 7663
- print {
- buf: "N|7663|OtherTraces|finalize-uuid-75e4c6d0-d8f6-4f82-fa4b-9e09c5512288\n"
- }
- }
- }
- trusted_uid: 9999
- trusted_packet_sequence_id: 2
- trusted_pid: 1277
- previous_packet_dropped: true
-}
-packet {
- ftrace_events {
- cpu: 4
- event {
- timestamp: 187198579688
- pid: 7752
- print {
- buf: "N|7752|OtherTraces|finalize-uuid-ad836701-3113-3fb1-be4f-f7731e23fbbf\n"
- }
- }
- }
- trusted_uid: 9999
- trusted_packet_sequence_id: 2
- trusted_pid: 1277
-}
-packet {
- ftrace_events {
- cpu: 6
- event {
- timestamp: 200857707872
- pid: 7824
- print {
- buf: "N|7824|OtherTraces|finalize-uuid-0de1a010-efa1-a081-2345-969b1186a6ab\n"
- }
- }
- }
- trusted_uid: 9999
- trusted_packet_sequence_id: 2
- trusted_pid: 1277
-}
diff --git a/test/trace_processor/diff_tests/parser/parsing/tests.py b/test/trace_processor/diff_tests/parser/parsing/tests.py
index b9d05e4..8af739d 100644
--- a/test/trace_processor/diff_tests/parser/parsing/tests.py
+++ b/test/trace_processor/diff_tests/parser/parsing/tests.py
@@ -1141,18 +1141,6 @@
9
"""))
- def test_otheruuids_android_other_traces(self):
- return DiffTestBlueprint(
- trace=Path('otheruuids.textproto'),
- query=Metric('android_other_traces'),
- out=TextProto(r"""
- android_other_traces {
- finalized_traces_uuid: "75e4c6d0-d8f6-4f82-fa4b-9e09c5512288"
- finalized_traces_uuid: "ad836701-3113-3fb1-be4f-f7731e23fbbf"
- finalized_traces_uuid: "0de1a010-efa1-a081-2345-969b1186a6ab"
- }
- """))
-
# Per-process Binder transaction metrics
def test_android_binder(self):
return DiffTestBlueprint(
@@ -1326,6 +1314,35 @@
"all_data_source_flushed_ns",12345
"""))
+ def test_slow_starting_data_sources(self):
+ return DiffTestBlueprint(
+ trace=TextProto(r"""
+ packet {
+ timestamp: 108227060089867
+ trusted_uid: 679634
+ trusted_packet_sequence_id: 1
+ service_event {
+ slow_starting_data_sources {
+ data_source {
+ producer_name: "producer2"
+ data_source_name: "track_event"
+ }
+ data_source {
+ producer_name: "producer3"
+ data_source_name: "track_event"
+ }
+ }
+ }
+ }
+ """),
+ query="""
+ SELECT str_value FROM metadata WHERE name = 'slow_start_data_source'""",
+ out=Csv("""
+ "str_value"
+ "producer2 track_event"
+ "producer3 track_event"
+ """))
+
def test_ftrace_abi_errors_skipped_zero_data_length(self):
return DiffTestBlueprint(
trace=TextProto(r"""
@@ -1553,3 +1570,76 @@
5230422153284,0,1306,"[NULL]"
5230425693562,0,10,1
"""))
+
+ # Kernel idle tasks created by /sbin/init should be filtered.
+ def test_task_newtask_swapper_by_init(self):
+ return DiffTestBlueprint(
+ trace=TextProto(r"""
+ packet {
+ first_packet_on_sequence: true
+ ftrace_events {
+ cpu: 1
+ event {
+ timestamp: 1000000
+ pid: 0
+ task_newtask {
+ pid: 1
+ comm: "swapper/0"
+ clone_flags: 8389376
+ oom_score_adj: 0
+ }
+ }
+ event {
+ timestamp: 1000000
+ pid: 0
+ task_newtask {
+ pid: 2
+ comm: "swapper/0"
+ clone_flags: 8390400
+ oom_score_adj: 0
+ }
+ }
+ event {
+ timestamp: 17000000
+ pid: 1
+ task_newtask {
+ pid: 0
+ comm: "swapper/0"
+ clone_flags: 256
+ oom_score_adj: 0
+ }
+ }
+ event {
+ timestamp: 17000000
+ pid: 1
+ task_newtask {
+ pid: 0
+ comm: "swapper/0"
+ clone_flags: 256
+ oom_score_adj: 0
+ }
+ }
+ event {
+ timestamp: 17000000
+ pid: 1
+ task_newtask {
+ pid: 0
+ comm: "swapper/0"
+ clone_flags: 256
+ oom_score_adj: 0
+ }
+ }
+ }
+ trusted_uid: 9999
+ trusted_packet_sequence_id: 2
+ trusted_pid: 521
+ previous_packet_dropped: true
+ }
+ """),
+ query="""
+ SELECT utid, tid, name from thread where tid = 0
+ """,
+ out=Csv("""
+ "utid","tid","name"
+ 0,0,"swapper"
+ """))
diff --git a/test/trace_processor/diff_tests/parser/parsing/tests_traced_stats.py b/test/trace_processor/diff_tests/parser/parsing/tests_traced_stats.py
index af2f362..275c3f4 100644
--- a/test/trace_processor/diff_tests/parser/parsing/tests_traced_stats.py
+++ b/test/trace_processor/diff_tests/parser/parsing/tests_traced_stats.py
@@ -102,3 +102,65 @@
1,1
2,2
"""))
+
+ # Check that dropping all packets leads to
+ # `traced_buf_incremental_sequences_dropped` being set.
+ def test_sequence_all_incremental_dropped(self):
+ return DiffTestBlueprint(
+ trace=TextProto('''
+ packet {
+ trusted_packet_sequence_id: 2
+ previous_packet_dropped: true
+ first_packet_on_sequence: true
+ sequence_flags: 1 # SEQ_INCREMENTAL_STATE_CLEARED
+ }
+ packet {
+ trusted_packet_sequence_id: 2
+ sequence_flags: 2 # SEQ_NEEDS_INCREMENTAL_STATE
+ }
+ packet {
+ trusted_packet_sequence_id: 2
+ sequence_flags: 2 # SEQ_NEEDS_INCREMENTAL_STATE
+ }
+ packet {
+ trusted_packet_sequence_id: 3
+ sequence_flags: 2 # SEQ_NEEDS_INCREMENTAL_STATE
+ }
+ packet {
+ trusted_packet_sequence_id: 3
+ sequence_flags: 2 # SEQ_NEEDS_INCREMENTAL_STATE
+ }
+ packet {
+ trusted_packet_sequence_id: 4
+ sequence_flags: 2 # SEQ_NEEDS_INCREMENTAL_STATE
+ }
+ packet {
+ trusted_uid: 9999
+ trusted_packet_sequence_id: 1
+ trace_stats {
+ writer_stats {
+ sequence_id: 2
+ buffer: 0
+ }
+ writer_stats {
+ sequence_id: 3
+ buffer: 1
+ }
+ writer_stats {
+ sequence_id: 4
+ buffer: 1
+ }
+ }
+ }
+ '''),
+ query='''
+ SELECT idx, value
+ FROM stats
+ WHERE name = 'traced_buf_incremental_sequences_dropped'
+ ORDER BY idx;
+ ''',
+ out=Csv('''
+ "idx","value"
+ 0,0
+ 1,2
+ '''))
diff --git a/test/trace_processor/diff_tests/parser/perf_text/tests.py b/test/trace_processor/diff_tests/parser/perf_text/tests.py
new file mode 100644
index 0000000..a574d3f
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/perf_text/tests.py
@@ -0,0 +1,70 @@
+#!/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 DataPath
+from python.generators.diff_tests.testing import Csv
+from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import TestSuite
+
+
+class PerfTextParser(TestSuite):
+
+ def test_perf_text_smoke(self):
+ return DiffTestBlueprint(
+ trace=DataPath('trace_processor_perf_as_text.txt'),
+ query="""
+ INCLUDE PERFETTO MODULE stacks.cpu_profiling;
+
+ SELECT id, parent_id, name, mapping_name, self_count, cumulative_count
+ FROM cpu_profiling_summary_tree
+ LIMIT 10
+ """,
+ out=Csv('''
+ "id","parent_id","name","mapping_name","self_count","cumulative_count"
+ 0,"[NULL]","_start","/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2",1,2
+ 1,0,"[unknown]","[unknown]",1,1
+ 2,"[NULL]","_dl_start","/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2",1,1
+ 3,"[NULL]","_dl_start_user","/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2",0,16
+ 4,3,"_dl_start","/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2",2,5
+ 5,4,"[unknown]","[unknown]",3,3
+ 6,"[NULL]","[unknown]","[unknown]",0,27
+ 7,6,"[unknown]","[unknown]",0,3
+ 8,7,"__GI___tunables_init","/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2",1,2
+ 9,8,"[unknown]","[unknown]",1,1
+ '''))
+
+ def test_perf_text_simpleperf_smoke(self):
+ return DiffTestBlueprint(
+ trace=DataPath('simpleperf_as_text.txt'),
+ query="""
+ INCLUDE PERFETTO MODULE stacks.cpu_profiling;
+
+ SELECT id, parent_id, name, mapping_name, self_count, cumulative_count
+ FROM cpu_profiling_summary_tree
+ LIMIT 10
+ """,
+ out=Csv('''
+ "id","parent_id","name","mapping_name","self_count","cumulative_count"
+ 0,"[NULL]","__libc_init","/apex/com.android.runtime/lib64/bionic/libc.so",0,1714
+ 1,0,"main","/system/bin/app_process64",0,1714
+ 2,1,"android::AndroidRuntime::start(char const*, android::Vector<android::String8> const&, bool)","/system/lib64/libandroid_runtime.so",0,1714
+ 3,2,"_JNIEnv::CallStaticVoidMethod(_jclass*, _jmethodID*, ...)","/system/lib64/libandroid_runtime.so",0,1714
+ 4,3,"art::JNI<true>::CallStaticVoidMethodV(_JNIEnv*, _jclass*, _jmethodID*, std::__va_list)","/apex/com.android.art/lib64/libart.so",0,1714
+ 5,4,"art::JValue art::InvokeWithVarArgs<_jmethodID*>(art::ScopedObjectAccessAlreadyRunnable const&, _jobject*, _jmethodID*, std::__va_list)","/apex/com.android.art/lib64/libart.so",0,1714
+ 6,5,"art_quick_invoke_static_stub","/apex/com.android.art/lib64/libart.so",0,1714
+ 7,6,"com.android.internal.os.ZygoteInit.main","/system/framework/arm64/boot-framework.oat",0,1714
+ 8,7,"com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run","/system/framework/arm64/boot-framework.oat",0,1714
+ 9,8,"art_jni_trampoline","/system/framework/arm64/boot.oat",0,1714
+ '''))
diff --git a/test/trace_processor/diff_tests/parser/power/tests_energy_breakdown.py b/test/trace_processor/diff_tests/parser/power/tests_energy_breakdown.py
index d678400..655792c 100644
--- a/test/trace_processor/diff_tests/parser/power/tests_energy_breakdown.py
+++ b/test/trace_processor/diff_tests/parser/power/tests_energy_breakdown.py
@@ -25,8 +25,16 @@
return DiffTestBlueprint(
trace=Path('energy_breakdown.textproto'),
query="""
- SELECT consumer_id, name, consumer_type, ordinal
- FROM energy_counter_track;
+ SELECT
+ EXTRACT_ARG(
+ dimension_arg_set_id,
+ 'energy_consumer_id'
+ ) AS consumer_id,
+ name,
+ EXTRACT_ARG(source_arg_set_id, 'consumer_type') AS consumer_type,
+ EXTRACT_ARG(source_arg_set_id, 'ordinal') AS ordinal
+ FROM track
+ WHERE classification = 'android_energy_estimation_breakdown';
""",
out=Csv("""
"consumer_id","name","consumer_type","ordinal"
@@ -39,7 +47,7 @@
query="""
SELECT ts, value
FROM counter
- JOIN energy_counter_track ON counter.track_id = energy_counter_track.id
+ JOIN track ON counter.track_id = track.id
ORDER BY ts;
""",
out=Csv("""
@@ -47,42 +55,18 @@
1030255882785,98567522.000000
"""))
- def test_energy_breakdown_uid_table(self):
- return DiffTestBlueprint(
- trace=Path('energy_breakdown_uid.textproto'),
- query="""
- SELECT uid, name
- FROM uid_counter_track;
- """,
- out=Csv("""
- "uid","name"
- 10234,"GPU"
- 10190,"GPU"
- 10235,"GPU"
- """))
-
- def test_energy_breakdown_uid_event(self):
- return DiffTestBlueprint(
- trace=Path('energy_breakdown_uid.textproto'),
- query="""
- SELECT ts, value
- FROM counter
- JOIN uid_counter_track ON counter.track_id = uid_counter_track.id
- ORDER BY ts;
- """,
- out=Csv("""
- "ts","value"
- 1026753926322,3004536.000000
- 1026753926322,0.000000
- 1026753926322,4002274.000000
- """))
-
def test_energy_per_uid_table(self):
return DiffTestBlueprint(
trace=Path('energy_breakdown_uid.textproto'),
query="""
- SELECT consumer_id, uid
- FROM energy_per_uid_counter_track;
+ SELECT
+ EXTRACT_ARG(
+ dimension_arg_set_id,
+ 'energy_consumer_id'
+ ) AS consumer_id,
+ EXTRACT_ARG(dimension_arg_set_id, 'uid') AS uid
+ FROM track
+ WHERE classification = 'android_energy_estimation_breakdown_per_uid';
""",
out=Csv("""
"consumer_id","uid"
@@ -97,9 +81,12 @@
trace_modifier=TraceInjector(['android_energy_estimation_breakdown'],
{'machine_id': 1001}),
query="""
- SELECT uid, name
- FROM uid_counter_track
- WHERE machine_id IS NOT NULL;
+ SELECT
+ EXTRACT_ARG(dimension_arg_set_id, 'uid') AS uid,
+ name
+ FROM track
+ WHERE classification = 'android_energy_estimation_breakdown_per_uid'
+ AND machine_id IS NOT NULL;
""",
out=Csv("""
"uid","name"
@@ -114,9 +101,17 @@
trace_modifier=TraceInjector(['android_energy_estimation_breakdown'],
{'machine_id': 1001}),
query="""
- SELECT consumer_id, name, consumer_type, ordinal
- FROM energy_counter_track
- WHERE machine_id IS NOT NULL;
+ SELECT
+ EXTRACT_ARG(
+ dimension_arg_set_id,
+ 'energy_consumer_id'
+ ) AS consumer_id,
+ name,
+ EXTRACT_ARG(source_arg_set_id, 'consumer_type') AS consumer_type,
+ EXTRACT_ARG(source_arg_set_id, 'ordinal') AS ordinal
+ FROM track
+ WHERE classification = 'android_energy_estimation_breakdown'
+ AND machine_id IS NOT NULL;
""",
out=Csv("""
"consumer_id","name","consumer_type","ordinal"
diff --git a/test/trace_processor/diff_tests/parser/track_event/legacy_async_event.out b/test/trace_processor/diff_tests/parser/track_event/legacy_async_event.out
deleted file mode 100644
index 56a8f2e..0000000
--- a/test/trace_processor/diff_tests/parser/track_event/legacy_async_event.out
+++ /dev/null
@@ -1,15 +0,0 @@
-"track","process","thread","thread_process","ts","dur","category","name","key","string_value","int_value"
-"name1","[NULL]","[NULL]","[NULL]",1000,7000,"cat","name1","debug.arg1","value1","[NULL]"
-"name1","[NULL]","[NULL]","[NULL]",1000,7000,"cat","name1","legacy_event.passthrough_utid","[NULL]",1
-"name1","[NULL]","[NULL]","[NULL]",1000,7000,"cat","name1","legacy_event.phase","S","[NULL]"
-"name1","[NULL]","[NULL]","[NULL]",1000,7000,"cat","name1","debug.arg2","value2","[NULL]"
-"name1","[NULL]","[NULL]","[NULL]",2000,1000,"cat","name1","legacy_event.passthrough_utid","[NULL]",2
-"name1","[NULL]","[NULL]","[NULL]",2000,1000,"cat","name1","legacy_event.phase","S","[NULL]"
-"name1","[NULL]","[NULL]","[NULL]",3000,0,"cat","name1","debug.arg3","value3","[NULL]"
-"name1","[NULL]","[NULL]","[NULL]",3000,0,"cat","name1","debug.step","Step1","[NULL]"
-"name1","[NULL]","[NULL]","[NULL]",3000,0,"cat","name1","legacy_event.passthrough_utid","[NULL]",1
-"name1","[NULL]","[NULL]","[NULL]",3000,0,"cat","name1","legacy_event.phase","T","[NULL]"
-"name1","[NULL]","[NULL]","[NULL]",5000,0,"cat","name1","debug.arg4","value4","[NULL]"
-"name1","[NULL]","[NULL]","[NULL]",5000,0,"cat","name1","debug.step","Step2","[NULL]"
-"name1","[NULL]","[NULL]","[NULL]",5000,0,"cat","name1","legacy_event.passthrough_utid","[NULL]",1
-"name1","[NULL]","[NULL]","[NULL]",5000,0,"cat","name1","legacy_event.phase","p","[NULL]"
diff --git a/test/trace_processor/diff_tests/parser/track_event/tests.py b/test/trace_processor/diff_tests/parser/track_event/tests.py
index 76bedbd..97f0804 100644
--- a/test/trace_processor/diff_tests/parser/track_event/tests.py
+++ b/test/trace_processor/diff_tests/parser/track_event/tests.py
@@ -196,7 +196,15 @@
def test_track_event_typed_args_args(self):
return DiffTestBlueprint(
trace=Path('track_event_typed_args.textproto'),
- query=Path('track_event_args_test.sql'),
+ query="""
+ SELECT
+ flat_key,
+ key,
+ int_value,
+ string_value
+ FROM args
+ ORDER BY key, display_value, arg_set_id, key ASC;
+ """,
out=Path('track_event_typed_args_args.out'))
# Track handling
@@ -222,7 +230,23 @@
LEFT JOIN process thread_process ON thread.upid = thread_process.upid
ORDER BY ts ASC;
""",
- out=Path('track_event_tracks_slices.out'))
+ out=Csv("""
+ "track","process","thread","thread_process","ts","dur","category","name"
+ "[NULL]","[NULL]","t1","p1",1000,0,"cat","event1_on_t1"
+ "[NULL]","[NULL]","t2","p1",2000,0,"cat","event1_on_t2"
+ "[NULL]","[NULL]","t2","p1",3000,0,"cat","event2_on_t2"
+ "[NULL]","p1","[NULL]","[NULL]",4000,0,"cat","event1_on_p1"
+ "async","p1","[NULL]","[NULL]",5000,0,"cat","event1_on_async"
+ "async2","p1","[NULL]","[NULL]",5100,100,"cat","event1_on_async2"
+ "[NULL]","[NULL]","t1","p1",6000,0,"cat","event3_on_t1"
+ "[NULL]","[NULL]","t3","p1",11000,0,"cat","event1_on_t3"
+ "[NULL]","p2","[NULL]","[NULL]",21000,0,"cat","event1_on_p2"
+ "[NULL]","[NULL]","t4","p2",22000,0,"cat","event1_on_t4"
+ "Default Track","[NULL]","[NULL]","[NULL]",30000,0,"cat","event1_on_t1"
+ "[NULL]","p2","[NULL]","[NULL]",31000,0,"cat","event2_on_p2"
+ "[NULL]","[NULL]","t4","p2",32000,0,"cat","event2_on_t4"
+ "event_and_track_async3","p1","[NULL]","[NULL]",40000,0,"cat","event_and_track_async3"
+ """))
def test_track_event_tracks_processes(self):
return DiffTestBlueprint(
@@ -393,7 +417,23 @@
LEFT JOIN args ON slice.arg_set_id = args.arg_set_id
ORDER BY slice.ts, args.id;
""",
- out=Path('legacy_async_event.out'))
+ out=Csv("""
+ "track","process","thread","thread_process","ts","dur","category","name","key","string_value","int_value"
+ "name1","[NULL]","[NULL]","[NULL]",1000,7000,"cat","name1","debug.arg1","value1","[NULL]"
+ "name1","[NULL]","[NULL]","[NULL]",1000,7000,"cat","name1","legacy_event.passthrough_utid","[NULL]",1
+ "name1","[NULL]","[NULL]","[NULL]",1000,7000,"cat","name1","legacy_event.phase","S","[NULL]"
+ "name1","[NULL]","[NULL]","[NULL]",1000,7000,"cat","name1","debug.arg2","value2","[NULL]"
+ "name1","[NULL]","[NULL]","[NULL]",2000,1000,"cat","name1","legacy_event.passthrough_utid","[NULL]",2
+ "name1","[NULL]","[NULL]","[NULL]",2000,1000,"cat","name1","legacy_event.phase","S","[NULL]"
+ "name1","[NULL]","[NULL]","[NULL]",3000,0,"cat","name1","debug.arg3","value3","[NULL]"
+ "name1","[NULL]","[NULL]","[NULL]",3000,0,"cat","name1","debug.step","Step1","[NULL]"
+ "name1","[NULL]","[NULL]","[NULL]",3000,0,"cat","name1","legacy_event.passthrough_utid","[NULL]",1
+ "name1","[NULL]","[NULL]","[NULL]",3000,0,"cat","name1","legacy_event.phase","T","[NULL]"
+ "name1","[NULL]","[NULL]","[NULL]",5000,0,"cat","name1","debug.arg4","value4","[NULL]"
+ "name1","[NULL]","[NULL]","[NULL]",5000,0,"cat","name1","debug.step","Step2","[NULL]"
+ "name1","[NULL]","[NULL]","[NULL]",5000,0,"cat","name1","legacy_event.passthrough_utid","[NULL]",1
+ "name1","[NULL]","[NULL]","[NULL]",5000,0,"cat","name1","legacy_event.phase","p","[NULL]"
+ """))
# Legacy atrace
def test_track_event_with_atrace(self):
@@ -458,8 +498,52 @@
def test_track_event_merged_debug_annotations_args(self):
return DiffTestBlueprint(
trace=Path('track_event_merged_debug_annotations.textproto'),
- query=Path('track_event_args_test.sql'),
- out=Path('track_event_merged_debug_annotations_args.out'))
+ query="""
+ SELECT
+ flat_key,
+ key,
+ int_value,
+ string_value
+ FROM args
+ ORDER BY key, display_value, arg_set_id, key ASC;
+ """,
+ out=Csv('''
+ "flat_key","key","int_value","string_value"
+ "cookie","cookie",1234,"[NULL]"
+ "debug.debug1.key1","debug.debug1.key1",10,"[NULL]"
+ "debug.debug1.key2","debug.debug1.key2[0]",20,"[NULL]"
+ "debug.debug1.key2","debug.debug1.key2[1]",21,"[NULL]"
+ "debug.debug1.key2","debug.debug1.key2[2]",22,"[NULL]"
+ "debug.debug1.key2","debug.debug1.key2[3]",23,"[NULL]"
+ "debug.debug1.key3","debug.debug1.key3",30,"[NULL]"
+ "debug.debug2.key1","debug.debug2.key1",10,"[NULL]"
+ "debug.debug2.key2","debug.debug2.key2[0]",20,"[NULL]"
+ "debug.debug2.key2","debug.debug2.key2[1]",21,"[NULL]"
+ "debug.debug2.key2","debug.debug2.key2[2]",22,"[NULL]"
+ "debug.debug2.key2","debug.debug2.key2[3]",23,"[NULL]"
+ "debug.debug2.key3.key31","debug.debug2.key3.key31",31,"[NULL]"
+ "debug.debug2.key3.key32","debug.debug2.key3.key32",32,"[NULL]"
+ "debug.debug2.key4","debug.debug2.key4",40,"[NULL]"
+ "debug.debug3","debug.debug3",32,"[NULL]"
+ "debug.debug4.key1","debug.debug4.key1",10,"[NULL]"
+ "debug.debug4.key2","debug.debug4.key2[0]",20,"[NULL]"
+ "debug.debug4.key2","debug.debug4.key2[1]",21,"[NULL]"
+ "event.category","event.category","[NULL]","cat"
+ "event.category","event.category","[NULL]","cat"
+ "event.name","event.name","[NULL]","[NULL]"
+ "event.name","event.name","[NULL]","name1"
+ "is_root_in_scope","is_root_in_scope",1,"[NULL]"
+ "legacy_event.passthrough_utid","legacy_event.passthrough_utid",1,"[NULL]"
+ "scope","scope","[NULL]","cat"
+ "source","source","[NULL]","chrome"
+ "source","source","[NULL]","descriptor"
+ "source_scope","source_scope","[NULL]","cat"
+ "trace_id","trace_id",1,"[NULL]"
+ "trace_id","trace_id",1234,"[NULL]"
+ "trace_id_is_process_scoped","trace_id_is_process_scoped",0,"[NULL]"
+ "utid","utid",1,"[NULL]"
+ "utid","utid",2,"[NULL]"
+ '''))
# Counters
def test_track_event_counters_slices(self):
@@ -516,7 +600,27 @@
LEFT JOIN process thread_process ON thread.upid = thread_process.upid
ORDER BY ts ASC;
""",
- out=Path('track_event_counters_counters.out'))
+ out=Csv("""
+ "counter_name","process","thread","thread_process","unit","ts","value"
+ "thread_time","[NULL]","t1","Browser","ns",1000,1000000.000000
+ "thread_time","[NULL]","t1","Browser","ns",1100,1010000.000000
+ "thread_time","[NULL]","t1","Browser","ns",2000,2000000.000000
+ "thread_time","[NULL]","t1","Browser","ns",2000,2010000.000000
+ "thread_time","[NULL]","t1","Browser","ns",2200,2020000.000000
+ "thread_time","[NULL]","t1","Browser","ns",2200,2030000.000000
+ "MySizeCounter","[NULL]","[NULL]","[NULL]","bytes",3000,1024.000000
+ "MySizeCounter","[NULL]","[NULL]","[NULL]","bytes",3100,2048.000000
+ "thread_time","[NULL]","t1","Browser","ns",4000,2040000.000000
+ "MySizeCounter","[NULL]","[NULL]","[NULL]","bytes",4000,1024.000000
+ "thread_time","[NULL]","t4","Browser","[NULL]",4000,10000.000000
+ "thread_instruction_count","[NULL]","t4","Browser","[NULL]",4000,20.000000
+ "thread_time","[NULL]","t4","Browser","[NULL]",4100,15000.000000
+ "thread_instruction_count","[NULL]","t4","Browser","[NULL]",4100,25.000000
+ "MyDoubleCounter","[NULL]","[NULL]","[NULL]","[NULL]",4200,3.141593
+ "MyDoubleCounter","[NULL]","[NULL]","[NULL]","[NULL]",4300,0.500000
+ "MySizeCounter","[NULL]","[NULL]","[NULL]","bytes",4500,4096.000000
+ "MyDoubleCounter","[NULL]","[NULL]","[NULL]","[NULL]",4500,2.718280
+ """))
# Clock handling
def test_track_event_monotonic_trace_clock_slices(self):
@@ -589,7 +693,15 @@
def test_track_event_chrome_histogram_sample_args(self):
return DiffTestBlueprint(
trace=Path('track_event_chrome_histogram_sample.textproto'),
- query=Path('track_event_args_test.sql'),
+ query="""
+ SELECT
+ flat_key,
+ key,
+ int_value,
+ string_value
+ FROM args
+ ORDER BY key, display_value, arg_set_id, key ASC;
+ """,
out=Path('track_event_chrome_histogram_sample_args.out'))
# Flow events importing from proto
@@ -693,6 +805,34 @@
13000,"slice4"
"""))
+ def test_track_event_tracks_ordering(self):
+ return DiffTestBlueprint(
+ trace=Path('track_event_tracks_ordering.textproto'),
+ query="""
+ SELECT
+ id,
+ parent_id,
+ EXTRACT_ARG(source_arg_set_id, 'child_ordering') AS ordering,
+ EXTRACT_ARG(source_arg_set_id, 'sibling_order_rank') AS rank
+ FROM track
+ """,
+ out=Csv("""
+ "id","parent_id","ordering","rank"
+ 0,"[NULL]","explicit","[NULL]"
+ 1,0,"[NULL]",-10
+ 2,0,"[NULL]",-2
+ 3,0,"[NULL]",1
+ 4,0,"[NULL]",2
+ 5,2,"[NULL]","[NULL]"
+ 6,0,"[NULL]","[NULL]"
+ 7,"[NULL]","[NULL]","[NULL]"
+ 8,7,"[NULL]","[NULL]"
+ 9,"[NULL]","[NULL]","[NULL]"
+ 10,"[NULL]","[NULL]","[NULL]"
+ 11,"[NULL]","[NULL]","[NULL]"
+ 12,0,"[NULL]","[NULL]"
+ """))
+
def test_track_event_tracks_machine_id(self):
return DiffTestBlueprint(
trace=Path('track_event_tracks.textproto'),
diff --git a/test/trace_processor/diff_tests/parser/track_event/track_event_args_test.sql b/test/trace_processor/diff_tests/parser/track_event/track_event_args_test.sql
deleted file mode 100644
index 7d8ced6..0000000
--- a/test/trace_processor/diff_tests/parser/track_event/track_event_args_test.sql
+++ /dev/null
@@ -1,16 +0,0 @@
---
--- Copyright 2019 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.
---
-SELECT flat_key, key, int_value, string_value FROM args ORDER BY key, display_value, arg_set_id, key ASC;
diff --git a/test/trace_processor/diff_tests/parser/track_event/track_event_counters_counters.out b/test/trace_processor/diff_tests/parser/track_event/track_event_counters_counters.out
deleted file mode 100644
index 840ffe1..0000000
--- a/test/trace_processor/diff_tests/parser/track_event/track_event_counters_counters.out
+++ /dev/null
@@ -1,19 +0,0 @@
-"counter_name","process","thread","thread_process","unit","ts","value"
-"thread_time","[NULL]","t1","Browser","ns",1000,1000000.000000
-"thread_time","[NULL]","t1","Browser","ns",1100,1010000.000000
-"thread_time","[NULL]","t1","Browser","ns",2000,2000000.000000
-"thread_time","[NULL]","t1","Browser","ns",2000,2010000.000000
-"thread_time","[NULL]","t1","Browser","ns",2200,2020000.000000
-"thread_time","[NULL]","t1","Browser","ns",2200,2030000.000000
-"MySizeCounter","[NULL]","[NULL]","[NULL]","bytes",3000,1024.000000
-"MySizeCounter","[NULL]","[NULL]","[NULL]","bytes",3100,2048.000000
-"thread_time","[NULL]","t1","Browser","ns",4000,2040000.000000
-"MySizeCounter","[NULL]","[NULL]","[NULL]","bytes",4000,1024.000000
-"thread_time","[NULL]","t4","Browser","[NULL]",4000,10000.000000
-"thread_instruction_count","[NULL]","t4","Browser","[NULL]",4000,20.000000
-"thread_time","[NULL]","t4","Browser","[NULL]",4100,15000.000000
-"thread_instruction_count","[NULL]","t4","Browser","[NULL]",4100,25.000000
-"MyDoubleCounter","[NULL]","[NULL]","[NULL]","[NULL]",4200,3.141593
-"MyDoubleCounter","[NULL]","[NULL]","[NULL]","[NULL]",4300,0.500000
-"MySizeCounter","[NULL]","[NULL]","[NULL]","bytes",4500,4096.000000
-"MyDoubleCounter","[NULL]","[NULL]","[NULL]","[NULL]",4500,2.718280
diff --git a/test/trace_processor/diff_tests/parser/track_event/track_event_merged_debug_annotations_args.out b/test/trace_processor/diff_tests/parser/track_event/track_event_merged_debug_annotations_args.out
deleted file mode 100644
index 5536952..0000000
--- a/test/trace_processor/diff_tests/parser/track_event/track_event_merged_debug_annotations_args.out
+++ /dev/null
@@ -1,31 +0,0 @@
-"flat_key","key","int_value","string_value"
-"debug.debug1.key1","debug.debug1.key1",10,"[NULL]"
-"debug.debug1.key2","debug.debug1.key2[0]",20,"[NULL]"
-"debug.debug1.key2","debug.debug1.key2[1]",21,"[NULL]"
-"debug.debug1.key2","debug.debug1.key2[2]",22,"[NULL]"
-"debug.debug1.key2","debug.debug1.key2[3]",23,"[NULL]"
-"debug.debug1.key3","debug.debug1.key3",30,"[NULL]"
-"debug.debug2.key1","debug.debug2.key1",10,"[NULL]"
-"debug.debug2.key2","debug.debug2.key2[0]",20,"[NULL]"
-"debug.debug2.key2","debug.debug2.key2[1]",21,"[NULL]"
-"debug.debug2.key2","debug.debug2.key2[2]",22,"[NULL]"
-"debug.debug2.key2","debug.debug2.key2[3]",23,"[NULL]"
-"debug.debug2.key3.key31","debug.debug2.key3.key31",31,"[NULL]"
-"debug.debug2.key3.key32","debug.debug2.key3.key32",32,"[NULL]"
-"debug.debug2.key4","debug.debug2.key4",40,"[NULL]"
-"debug.debug3","debug.debug3",32,"[NULL]"
-"debug.debug4.key1","debug.debug4.key1",10,"[NULL]"
-"debug.debug4.key2","debug.debug4.key2[0]",20,"[NULL]"
-"debug.debug4.key2","debug.debug4.key2[1]",21,"[NULL]"
-"event.category","event.category","[NULL]","cat"
-"event.category","event.category","[NULL]","cat"
-"event.name","event.name","[NULL]","[NULL]"
-"event.name","event.name","[NULL]","name1"
-"is_root_in_scope","is_root_in_scope",1,"[NULL]"
-"legacy_event.passthrough_utid","legacy_event.passthrough_utid",1,"[NULL]"
-"source","source","[NULL]","chrome"
-"source","source","[NULL]","descriptor"
-"source_scope","source_scope","[NULL]","cat"
-"trace_id","trace_id",1,"[NULL]"
-"trace_id","trace_id",1234,"[NULL]"
-"trace_id_is_process_scoped","trace_id_is_process_scoped",0,"[NULL]"
diff --git a/test/trace_processor/diff_tests/parser/track_event/track_event_tracks_ordering.textproto b/test/trace_processor/diff_tests/parser/track_event/track_event_tracks_ordering.textproto
new file mode 100644
index 0000000..892266b
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/track_event/track_event_tracks_ordering.textproto
@@ -0,0 +1,338 @@
+# Sequence 1 defaults to track for "t1".
+packet {
+ trusted_packet_sequence_id: 1
+ timestamp: 0
+ incremental_state_cleared: true
+ first_packet_on_sequence: true
+ track_descriptor {
+ uuid: 1
+ parent_uuid: 10
+ thread {
+ pid: 5
+ tid: 1
+ thread_name: "t1"
+ }
+ sibling_order_rank: -10
+ }
+ trace_packet_defaults {
+ track_event_defaults {
+ track_uuid: 1
+ }
+ }
+}
+# Sequence 2 defaults to track for "t2".
+packet {
+ trusted_packet_sequence_id: 2
+ timestamp: 0
+ incremental_state_cleared: true
+ first_packet_on_sequence: true
+ track_descriptor {
+ uuid: 2
+ parent_uuid: 10
+ thread {
+ pid: 5
+ tid: 2
+ thread_name: "t2"
+ }
+ sibling_order_rank: -2
+ }
+ trace_packet_defaults {
+ track_event_defaults {
+ track_uuid: 2
+ }
+ }
+}
+# Both thread tracks are nested underneath this process track.
+packet {
+ trusted_packet_sequence_id: 1
+ timestamp: 0
+ track_descriptor {
+ uuid: 10
+ process {
+ pid: 5
+ process_name: "p1"
+ }
+ child_ordering: 3
+ chrome_process {
+ host_app_package_name: "host_app"
+ }
+ }
+}
+# And we have an async track underneath the process too.
+packet {
+ trusted_packet_sequence_id: 1
+ timestamp: 0
+ track_descriptor {
+ uuid: 11
+ parent_uuid: 10
+ name: "async"
+ sibling_order_rank: 1
+ }
+}
+packet {
+ trusted_packet_sequence_id: 1
+ timestamp: 100
+ track_descriptor {
+ uuid: 12
+ parent_uuid: 10
+ name: "async2"
+ sibling_order_rank: 2
+ }
+}
+packet {
+ trusted_packet_sequence_id: 2
+ timestamp: 200
+ track_descriptor {
+ uuid: 12
+ parent_uuid: 10
+ name: "async2"
+ }
+}
+
+# Threads also can have child async tracks.
+packet {
+ trusted_packet_sequence_id: 1
+ timestamp: 200
+ track_descriptor {
+ uuid: 14
+ parent_uuid: 2
+ name: "async3"
+ }
+}
+
+# Should appear on default track "t1".
+packet {
+ trusted_packet_sequence_id: 1
+ timestamp: 1000
+ track_event {
+ categories: "cat"
+ name: "event1_on_t1"
+ type: 3
+ }
+}
+# Should appear on default track "t2".
+packet {
+ trusted_packet_sequence_id: 2
+ timestamp: 2000
+ track_event {
+ categories: "cat"
+ name: "event1_on_t2"
+ type: 3
+ }
+}
+# Should appear on overridden track "t2".
+packet {
+ trusted_packet_sequence_id: 1
+ timestamp: 3000
+ track_event {
+ track_uuid: 2
+ categories: "cat"
+ name: "event2_on_t2"
+ type: 3
+ }
+}
+# Should appear on process track.
+packet {
+ trusted_packet_sequence_id: 1
+ timestamp: 4000
+ track_event {
+ track_uuid: 10
+ categories: "cat"
+ name: "event1_on_p1"
+ type: 3
+ }
+}
+# Should appear on async track.
+packet {
+ trusted_packet_sequence_id: 1
+ timestamp: 5000
+ track_event {
+ track_uuid: 11
+ categories: "cat"
+ name: "event1_on_async"
+ type: 3
+ }
+}
+# Event for the "async2" track starting on one thread and ending on another.
+packet {
+ trusted_packet_sequence_id: 1
+ timestamp: 5100
+ track_event {
+ track_uuid: 12
+ categories: "cat"
+ name: "event1_on_async2"
+ type: 1
+ }
+}
+packet {
+ trusted_packet_sequence_id: 2
+ timestamp: 5200
+ track_event {
+ track_uuid: 12
+ categories: "cat"
+ name: "event1_on_async2"
+ type: 2
+ }
+}
+
+# If we later see another track descriptor for tid 1, but with a different uuid,
+# we should detect tid reuse and start a new thread.
+packet {
+ trusted_packet_sequence_id: 3
+ timestamp: 10000
+ incremental_state_cleared: true
+ first_packet_on_sequence: true
+ track_descriptor {
+ uuid: 3
+ parent_uuid: 10
+ thread {
+ pid: 5
+ tid: 1
+ thread_name: "t3"
+ }
+ }
+}
+# Should appear on t3.
+packet {
+ trusted_packet_sequence_id: 3
+ timestamp: 11000
+ track_event {
+ track_uuid: 3
+ categories: "cat"
+ name: "event1_on_t3"
+ type: 3
+ }
+}
+
+# If we later see another track descriptor for pid 5, but with a different uuid,
+# we should detect pid reuse and start a new process.
+packet {
+ trusted_packet_sequence_id: 4
+ timestamp: 20000
+ incremental_state_cleared: true
+ track_descriptor {
+ uuid: 20
+ process {
+ pid: 5
+ process_name: "p2"
+ }
+ }
+}
+# Should appear on p2.
+packet {
+ trusted_packet_sequence_id: 4
+ timestamp: 21000
+ track_event {
+ track_uuid: 20
+ categories: "cat"
+ name: "event1_on_p2"
+ type: 3
+ }
+}
+# Another thread t4 in the new process.
+packet {
+ trusted_packet_sequence_id: 4
+ timestamp: 22000
+ incremental_state_cleared: true
+ track_descriptor {
+ uuid: 21
+ parent_uuid: 20
+ thread {
+ pid: 5
+ tid: 4
+ thread_name: "t4"
+ }
+ }
+}
+# Should appear on t4.
+packet {
+ trusted_packet_sequence_id: 4
+ timestamp: 22000
+ track_event {
+ track_uuid: 21
+ categories: "cat"
+ name: "event1_on_t4"
+ type: 3
+ }
+}
+
+# Another packet for a thread track in the old process, badly sorted.
+packet {
+ trusted_packet_sequence_id: 2
+ timestamp: 6000
+ track_event {
+ track_uuid: 1
+ categories: "cat"
+ name: "event3_on_t1"
+ type: 3
+ }
+}
+
+# Override the track to the default descriptor track for an event with a
+# TrackEvent type. Should appear on the default descriptor track instead of
+# "t1".
+packet {
+ trusted_packet_sequence_id: 1
+ timestamp: 30000
+ track_event {
+ track_uuid: 0
+ categories: "cat"
+ name: "event1_on_t1"
+ type: 3
+ }
+}
+
+# But a legacy event without TrackEvent type falls back to legacy tracks (based
+# on ThreadDescriptor / async IDs / legacy instant scopes). This instant event
+# should appear on the process track "p2".
+packet {
+ trusted_packet_sequence_id: 1
+ timestamp: 31000
+ track_event {
+ track_uuid: 0
+ categories: "cat"
+ name: "event2_on_p2"
+ legacy_event {
+ phase: 73 # 'I'
+ instant_event_scope: 2 # Process scope
+ }
+ }
+}
+
+# And pid/tid overrides take effect even for TrackEvent type events.
+packet {
+ trusted_packet_sequence_id: 1
+ timestamp: 32000
+ track_event {
+ track_uuid: 0
+ categories: "cat"
+ name: "event2_on_t4"
+ type: 3
+ legacy_event {
+ pid_override: 5
+ tid_override: 4
+ }
+ }
+}
+
+# Track descriptor without name and process/thread association derives its
+# name from the first event on the track.
+packet {
+ trusted_packet_sequence_id: 1
+ timestamp: 40000
+ track_descriptor {
+ uuid: 13
+ parent_uuid: 10
+ }
+}
+
+packet {
+ trusted_packet_sequence_id: 1
+ timestamp: 40000
+ track_event {
+ track_uuid: 13
+ categories: "cat"
+ name: "event_and_track_async3"
+ type: 3
+ }
+}
diff --git a/test/trace_processor/diff_tests/parser/track_event/track_event_tracks_slices.out b/test/trace_processor/diff_tests/parser/track_event/track_event_tracks_slices.out
deleted file mode 100644
index d0186cc..0000000
--- a/test/trace_processor/diff_tests/parser/track_event/track_event_tracks_slices.out
+++ /dev/null
@@ -1,15 +0,0 @@
-"track","process","thread","thread_process","ts","dur","category","name"
-"[NULL]","[NULL]","t1","p1",1000,0,"cat","event1_on_t1"
-"[NULL]","[NULL]","t2","p1",2000,0,"cat","event1_on_t2"
-"[NULL]","[NULL]","t2","p1",3000,0,"cat","event2_on_t2"
-"[NULL]","p1","[NULL]","[NULL]",4000,0,"cat","event1_on_p1"
-"async","p1","[NULL]","[NULL]",5000,0,"cat","event1_on_async"
-"async2","p1","[NULL]","[NULL]",5100,100,"cat","event1_on_async2"
-"[NULL]","[NULL]","t1","p1",6000,0,"cat","event3_on_t1"
-"[NULL]","[NULL]","t3","p1",11000,0,"cat","event1_on_t3"
-"[NULL]","p2","[NULL]","[NULL]",21000,0,"cat","event1_on_p2"
-"[NULL]","[NULL]","t4","p2",22000,0,"cat","event1_on_t4"
-"Default Track","[NULL]","[NULL]","[NULL]",30000,0,"cat","event1_on_t1"
-"[NULL]","p2","[NULL]","[NULL]",31000,0,"cat","event2_on_p2"
-"[NULL]","[NULL]","t4","p2",32000,0,"cat","event2_on_t4"
-"event_and_track_async3","p1","[NULL]","[NULL]",40000,0,"cat","event_and_track_async3"
diff --git a/test/trace_processor/diff_tests/parser/track_event/track_event_typed_args_args.out b/test/trace_processor/diff_tests/parser/track_event/track_event_typed_args_args.out
index 3083a2b..2e88593 100644
--- a/test/trace_processor/diff_tests/parser/track_event/track_event_typed_args_args.out
+++ b/test/trace_processor/diff_tests/parser/track_event/track_event_typed_args_args.out
@@ -38,3 +38,4 @@
"string_extension_for_testing","string_extension_for_testing","[NULL]","an extension string!"
"string_extension_for_testing2","string_extension_for_testing2","[NULL]","a second extension string!"
"trace_id","trace_id",1,"[NULL]"
+"utid","utid",1,"[NULL]"
diff --git a/test/trace_processor/diff_tests/parser/translated_args/chrome_args_test.sql b/test/trace_processor/diff_tests/parser/translated_args/chrome_args_test.sql
index 542df14..031604b 100644
--- a/test/trace_processor/diff_tests/parser/translated_args/chrome_args_test.sql
+++ b/test/trace_processor/diff_tests/parser/translated_args/chrome_args_test.sql
@@ -13,4 +13,6 @@
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
-SELECT flat_key, key, int_value, string_value FROM args ORDER BY key, display_value, arg_set_id ASC;
+SELECT flat_key, key, int_value, string_value
+FROM args
+ORDER BY key, display_value, arg_set_id ASC;
diff --git a/test/trace_processor/diff_tests/parser/translated_args/chrome_histogram.out b/test/trace_processor/diff_tests/parser/translated_args/chrome_histogram.out
index 7718306..efbb466 100644
--- a/test/trace_processor/diff_tests/parser/translated_args/chrome_histogram.out
+++ b/test/trace_processor/diff_tests/parser/translated_args/chrome_histogram.out
@@ -14,3 +14,4 @@
"is_root_in_scope","is_root_in_scope",1,"[NULL]"
"source","source","[NULL]","descriptor"
"trace_id","trace_id",12345,"[NULL]"
+"utid","utid",1,"[NULL]"
diff --git a/test/trace_processor/diff_tests/parser/translated_args/chrome_performance_mark.out b/test/trace_processor/diff_tests/parser/translated_args/chrome_performance_mark.out
index d61b9d3..fd17777 100644
--- a/test/trace_processor/diff_tests/parser/translated_args/chrome_performance_mark.out
+++ b/test/trace_processor/diff_tests/parser/translated_args/chrome_performance_mark.out
@@ -8,3 +8,4 @@
"is_root_in_scope","is_root_in_scope",1,"[NULL]"
"source","source","[NULL]","descriptor"
"trace_id","trace_id",12345,"[NULL]"
+"utid","utid",1,"[NULL]"
diff --git a/test/trace_processor/diff_tests/parser/translated_args/chrome_user_event.out b/test/trace_processor/diff_tests/parser/translated_args/chrome_user_event.out
index ece5310..ace9de6 100644
--- a/test/trace_processor/diff_tests/parser/translated_args/chrome_user_event.out
+++ b/test/trace_processor/diff_tests/parser/translated_args/chrome_user_event.out
@@ -13,3 +13,4 @@
"is_root_in_scope","is_root_in_scope",1,"[NULL]"
"source","source","[NULL]","descriptor"
"trace_id","trace_id",12345,"[NULL]"
+"utid","utid",1,"[NULL]"
diff --git a/test/trace_processor/diff_tests/parser/translated_args/native_symbol_arg.out b/test/trace_processor/diff_tests/parser/translated_args/native_symbol_arg.out
index 3471f41..ea05157 100644
--- a/test/trace_processor/diff_tests/parser/translated_args/native_symbol_arg.out
+++ b/test/trace_processor/diff_tests/parser/translated_args/native_symbol_arg.out
@@ -18,3 +18,4 @@
"is_root_in_scope","is_root_in_scope",1,"[NULL]"
"source","source","[NULL]","descriptor"
"trace_id","trace_id",12345,"[NULL]"
+"utid","utid",1,"[NULL]"
diff --git a/test/trace_processor/diff_tests/parser/zip/tests.py b/test/trace_processor/diff_tests/parser/zip/tests.py
index 660bdbe..4e46286 100644
--- a/test/trace_processor/diff_tests/parser/zip/tests.py
+++ b/test/trace_processor/diff_tests/parser/zip/tests.py
@@ -59,20 +59,37 @@
"main,E"
'''))
- def test_tokenization_order(self):
+ def test_zip_tokenization_order(self):
return DiffTestBlueprint(
trace=DataPath('zip/perf_track_sym.zip'),
query='''
SELECT *
FROM __intrinsic_trace_file
- ORDER BY id
+ ORDER BY processing_order
''',
out=Csv('''
- "id","type","parent_id","name","size","trace_type"
- 0,"__intrinsic_trace_file","[NULL]","[NULL]",94651,"zip"
- 1,"__intrinsic_trace_file",0,"c.trace.pb",379760,"proto"
- 2,"__intrinsic_trace_file",0,"b.simpleperf.data",554911,"perf"
- 3,"__intrinsic_trace_file",0,"a.symbols.pb",186149,"symbols"
+ "id","type","parent_id","name","size","trace_type","processing_order"
+ 0,"__intrinsic_trace_file","[NULL]","[NULL]",94651,"zip",0
+ 3,"__intrinsic_trace_file",0,"c.trace.pb",379760,"proto",1
+ 1,"__intrinsic_trace_file",0,"b.simpleperf.data",554911,"perf",2
+ 2,"__intrinsic_trace_file",0,"a.symbols.pb",186149,"symbols",3
+ '''))
+
+ def test_tar_gz_tokenization_order(self):
+ return DiffTestBlueprint(
+ trace=DataPath('perf_track_sym.tar.gz'),
+ query='''
+ SELECT *
+ FROM __intrinsic_trace_file
+ ORDER BY processing_order
+ ''',
+ out=Csv('''
+ "id","type","parent_id","name","size","trace_type","processing_order"
+ 0,"__intrinsic_trace_file","[NULL]","[NULL]",94091,"gzip",0
+ 1,"__intrinsic_trace_file",0,"",1126400,"tar",1
+ 4,"__intrinsic_trace_file",1,"/c.trace.pb",379760,"proto",2
+ 3,"__intrinsic_trace_file",1,"/b.simpleperf.data",554911,"perf",3
+ 2,"__intrinsic_trace_file",1,"/a.symbols.pb",186149,"symbols",4
'''))
# Make sure the logcat timestamps are correctly converted to trace ts. All
diff --git a/test/trace_processor/diff_tests/stdlib/android/desktop_mode_tests.py b/test/trace_processor/diff_tests/stdlib/android/desktop_mode_tests.py
new file mode 100644
index 0000000..6c16720
--- /dev/null
+++ b/test/trace_processor/diff_tests/stdlib/android/desktop_mode_tests.py
@@ -0,0 +1,110 @@
+#!/usr/bin/env python3
+# 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 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 DataPath
+from python.generators.diff_tests.testing import Csv
+from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import TestSuite
+
+class DesktopMode(TestSuite):
+
+ def test_android_desktop_mode_windows_statsd_events(self):
+ return DiffTestBlueprint(
+ trace=DataPath('android_desktop_mode/single_window_add_update_remove.pb'),
+ query="""
+ INCLUDE PERFETTO MODULE android.desktop_mode;
+ SELECT * FROM android_desktop_mode_windows;
+ """,
+ out=Csv("""
+ "raw_add_ts","raw_remove_ts","ts","dur","instance_id","uid"
+ 1112172132337,1115098491388,1112172132337,2926359051,22,10211
+ """))
+
+ def test_android_desktop_mode_windows_statsd_events_multiple_windows(self):
+ return DiffTestBlueprint(
+ trace=DataPath('android_desktop_mode/multiple_windows_add_update_remove.pb'),
+ query="""
+ INCLUDE PERFETTO MODULE android.desktop_mode;
+ SELECT * FROM android_desktop_mode_windows;
+ """,
+ out=Csv("""
+ "raw_add_ts","raw_remove_ts","ts","dur","instance_id","uid"
+ 1340951146935,1347096280320,1340951146935,6145133385,24,10211
+ 1342507511641,1345461733688,1342507511641,2954222047,26,10183
+ """))
+
+ def test_android_desktop_mode_windows_statsd_events_add_no_remove(self):
+ return DiffTestBlueprint(
+ trace=DataPath('android_desktop_mode/single_window_add_update_no_remove.pb'),
+ query="""
+ INCLUDE PERFETTO MODULE android.desktop_mode;
+ SELECT * FROM android_desktop_mode_windows;
+ """,
+ out=Csv("""
+ "raw_add_ts","raw_remove_ts","ts","dur","instance_id","uid"
+ 1552558346094,"[NULL]",1552558346094,1620521485,27,10211
+ """))
+
+ def test_android_desktop_mode_windows_statsd_events_no_add_update_remove(self):
+ return DiffTestBlueprint(
+ trace=DataPath('android_desktop_mode/single_window_no_add_update_remove.pb'),
+ query="""
+ INCLUDE PERFETTO MODULE android.desktop_mode;
+ SELECT * FROM android_desktop_mode_windows;
+ """,
+ out=Csv("""
+ "raw_add_ts","raw_remove_ts","ts","dur","instance_id","uid"
+ "[NULL]",1696520389866,1695387563286,1132826580,29,10211
+ """))
+
+ def test_android_desktop_mode_windows_statsd_events_only_update(self):
+ return DiffTestBlueprint(
+ trace=DataPath('android_desktop_mode/single_window_only_update.pb'),
+ query="""
+ INCLUDE PERFETTO MODULE android.desktop_mode;
+ SELECT * FROM android_desktop_mode_windows;
+ """,
+ out=Csv("""
+ "raw_add_ts","raw_remove_ts","ts","dur","instance_id","uid"
+ "[NULL]","[NULL]",1852548597746,3663403770,31,10211
+ """))
+
+ def test_android_desktop_mode_windows_statsd_events_multiple_windows_update_only(self):
+ return DiffTestBlueprint(
+ trace=DataPath('android_desktop_mode/multiple_window_only_update.pb'),
+ query="""
+ INCLUDE PERFETTO MODULE android.desktop_mode;
+ SELECT * FROM android_desktop_mode_windows;
+ """,
+ out=Csv("""
+ "raw_add_ts","raw_remove_ts","ts","dur","instance_id","uid"
+ "[NULL]","[NULL]",2137135290268,4737314089,33,10211
+ "[NULL]","[NULL]",2137135290268,4737314089,35,10183
+ """))
+
+ def test_android_desktop_mode_windows_statsd_events_multiple_windows_same_instance_new_session(self):
+ return DiffTestBlueprint(
+ trace=DataPath('android_desktop_mode/session_with_same_instance_id.pb'),
+ query="""
+ INCLUDE PERFETTO MODULE android.desktop_mode;
+ SELECT * FROM android_desktop_mode_windows;
+ """,
+ out=Csv("""
+ "raw_add_ts","raw_remove_ts","ts","dur","instance_id","uid"
+ 8936818061228,8963638163943,8936818061228,26820102715,1000025,1110217
+ 8966480744267,"[NULL]",8966480744267,3596089886,1000025,1110217
+ 8966481546961,"[NULL]",8966481546961,3595287192,1000028,1110329
+ """))
+
diff --git a/test/trace_processor/diff_tests/stdlib/android/frames_tests.py b/test/trace_processor/diff_tests/stdlib/android/frames_tests.py
index 2155b95..fdf171a 100644
--- a/test/trace_processor/diff_tests/stdlib/android/frames_tests.py
+++ b/test/trace_processor/diff_tests/stdlib/android/frames_tests.py
@@ -45,22 +45,22 @@
SELECT * FROM android_frames_choreographer_do_frame;
""",
out=Csv("""
- "id","frame_id","ui_thread_utid","upid"
- 2,10,2,2
- 15,20,2,2
- 22,30,2,2
- 35,40,2,2
- 46,60,2,2
- 55,90,2,2
- 63,100,2,2
- 73,110,2,2
- 79,120,2,2
- 87,130,2,2
- 93,140,2,2
- 99,145,2,2
- 102,150,2,2
- 108,160,2,2
- 140,1000,2,2
+ "id","frame_id","ui_thread_utid","upid","ts"
+ 2,10,2,2,0
+ 15,20,2,2,20000000
+ 22,30,2,2,30000000
+ 35,40,2,2,40000000
+ 46,60,2,2,70000000
+ 55,90,2,2,100000000
+ 63,100,2,2,200000000
+ 73,110,2,2,300000000
+ 79,120,2,2,400000000
+ 87,130,2,2,550000000
+ 93,140,2,2,608500000
+ 99,145,2,2,655000000
+ 102,150,2,2,700000000
+ 108,160,2,2,800000000
+ 140,1000,2,2,1100000000
"""))
def test_android_frames_draw_frame(self):
diff --git a/test/trace_processor/diff_tests/stdlib/android/heap_graph_tests.py b/test/trace_processor/diff_tests/stdlib/android/heap_graph_tests.py
index fe3ba2d..dfe5b2d 100644
--- a/test/trace_processor/diff_tests/stdlib/android/heap_graph_tests.py
+++ b/test/trace_processor/diff_tests/stdlib/android/heap_graph_tests.py
@@ -84,3 +84,20 @@
10,2,"B",0,1,1000,1,1000
10,2,"java.lang.String",1,2,10666,2,10666
"""))
+
+ def test_heap_graph_class_summary_tree(self):
+ return DiffTestBlueprint(
+ trace=Path('heap_graph_for_aggregation.textproto'),
+ query="""
+ INCLUDE PERFETTO MODULE android.memory.heap_graph.class_summary_tree;
+
+ SELECT name, self_count, self_size, cumulative_count, cumulative_size
+ FROM android_heap_graph_class_summary_tree
+ ORDER BY cumulative_size DESC;
+ """,
+ out=Csv("""
+ "name","self_count","self_size","cumulative_count","cumulative_size"
+ "A",2,200,4,11200
+ "java.lang.String",1,10000,1,10000
+ "B",1,1000,1,1000
+ """))
diff --git a/test/trace_processor/diff_tests/stdlib/android/heap_profile_tests.py b/test/trace_processor/diff_tests/stdlib/android/heap_profile_tests.py
new file mode 100644
index 0000000..bbdd809
--- /dev/null
+++ b/test/trace_processor/diff_tests/stdlib/android/heap_profile_tests.py
@@ -0,0 +1,52 @@
+#!/usr/bin/env python3
+# 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 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 DataPath
+from python.generators.diff_tests.testing import Csv
+from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import TestSuite
+
+
+class HeapProfile(TestSuite):
+
+ def test_heap_profile_summary_tree(self):
+ return DiffTestBlueprint(
+ trace=DataPath('system-server-native-profile'),
+ query="""
+ INCLUDE PERFETTO MODULE android.memory.heap_profile.summary_tree;
+
+ SELECT
+ name,
+ self_size,
+ cumulative_size,
+ self_alloc_size,
+ cumulative_alloc_size
+ FROM android_heap_profile_summary_tree
+ ORDER BY cumulative_size DESC, name
+ LIMIT 10;
+ """,
+ out=Csv("""
+ "name","self_size","cumulative_size","self_alloc_size","cumulative_alloc_size"
+ "__pthread_start(void*)",0,84848,0,1084996
+ "__start_thread",0,84848,0,1084996
+ "art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*)",0,57144,0,736946
+ "art::JValue art::InvokeVirtualOrInterfaceWithJValues<art::ArtMethod*>(art::ScopedObjectAccessAlreadyRunnable const&, _jobject*, art::ArtMethod*, jvalue const*)",0,57144,0,736946
+ "art::Thread::CreateCallback(void*)",0,57144,0,736946
+ "art_quick_invoke_stub",0,57144,0,736946
+ "android.os.HandlerThread.run",0,53048,0,197068
+ "com.android.server.UiThread.run",0,53048,0,197068
+ "android::AndroidRuntime::javaThreadShell(void*)",0,27704,0,348050
+ "(anonymous namespace)::nativeInitSensorEventQueue(_JNIEnv*, _jclass*, long, _jobject*, _jobject*, _jstring*, int)",0,26624,0,26624
+ """))
diff --git a/test/trace_processor/diff_tests/stdlib/android/startups_tests.py b/test/trace_processor/diff_tests/stdlib/android/startups_tests.py
index c4f5a2e..c44bca7 100644
--- a/test/trace_processor/diff_tests/stdlib/android/startups_tests.py
+++ b/test/trace_processor/diff_tests/stdlib/android/startups_tests.py
@@ -168,3 +168,35 @@
"startup_id","time_to_initial_display","time_to_full_display","ttid_frame_id","ttfd_frame_id","upid"
0,143980066,620815843,5873276,5873353,229
"""))
+
+ def test_android_startup_breakdown(self):
+ return DiffTestBlueprint(
+ trace=DataPath('api31_startup_cold.perfetto-trace'),
+ query="""
+ INCLUDE PERFETTO MODULE android.startup.startup_breakdowns;
+ SELECT
+ SUM(dur) AS dur,
+ reason
+ FROM android_startup_opinionated_breakdown
+ GROUP BY reason ORDER BY dur DESC;
+ """,
+ out=Csv("""
+ "dur","reason"
+ 28663023,"choreographer_do_frame"
+ 22564487,"binder"
+ 22011252,"launch_delay"
+ 16351925,"Running"
+ 13212137,"activity_start"
+ 10264635,"io"
+ 6779947,"inflate"
+ 6240207,"bind_application"
+ 5214375,"R+"
+ 3072397,"resources_manager_get_resources"
+ 2722869,"D"
+ 2574273,"open_dex_files_from_oat"
+ 2392761,"S"
+ 2353124,"activity_resume"
+ 1325727,"R"
+ 43698,"art_lock_contention"
+ 5573,"verify_class"
+ """))
diff --git a/test/trace_processor/diff_tests/stdlib/android/tests.py b/test/trace_processor/diff_tests/stdlib/android/tests.py
index 7a649e1..1b204fc 100644
--- a/test/trace_processor/diff_tests/stdlib/android/tests.py
+++ b/test/trace_processor/diff_tests/stdlib/android/tests.py
@@ -306,6 +306,28 @@
13934,"D",11950576,1
"""))
+ def test_android_monitor_contention_chain_thread_state(self):
+ return DiffTestBlueprint(
+ trace=DataPath('android_monitor_contention_trace.atr'),
+ query="""
+ INCLUDE PERFETTO MODULE android.monitor_contention;
+ SELECT
+ *
+ FROM android_monitor_contention_chain_thread_state
+ WHERE id = 13934;
+ """,
+ out=Csv("""
+ "id","ts","dur","blocking_utid","blocked_function","state"
+ 13934,1739927671503,141874,557,"[NULL]","R"
+ 13934,1739927813377,69101,557,"[NULL]","Running"
+ 13934,1739927882478,7649,557,"[NULL]","R+"
+ 13934,1739927890127,3306,557,"[NULL]","Running"
+ 13934,1739927893433,11950576,557,"blkdev_issue_flush","D"
+ 13934,1739939844009,76306,557,"[NULL]","R"
+ 13934,1739939920315,577554,557,"[NULL]","Running"
+ 13934,1739940497869,82426,557,"[NULL]","R"
+ """))
+
def test_monitor_contention_chain_extraction(self):
return DiffTestBlueprint(
trace=DataPath('android_monitor_contention_trace.atr'),
@@ -1302,3 +1324,117 @@
"binder",4174605447
"S",5144384456
"""))
+
+ def test_android_charging_states_output(self):
+ return DiffTestBlueprint(
+ trace=DataPath('android_job_scheduler.perfetto-trace'),
+ query="""
+ INCLUDE PERFETTO MODULE android.battery.charging_states;
+ SELECT ts, dur, charging_state FROM android_charging_states;
+ """,
+ out=Csv("""
+ "ts","dur","charging_state"
+ 368604749651,59806073237,"Charging"
+ """))
+
+ def test_android_job_scheduler_states_output(self):
+ return DiffTestBlueprint(
+ trace=DataPath('android_job_scheduler.perfetto-trace'),
+ query="""
+ INCLUDE PERFETTO MODULE android.job_scheduler_states;
+ SELECT
+ id,
+ ts,
+ dur,
+ slice_id,
+ job_name || '_' || job_id AS job_name,
+ uid,
+ job_id,
+ package_name,
+ job_namespace,
+ effective_priority,
+ has_battery_not_low_constraint,
+ has_charging_constraint,
+ has_connectivity_constraint,
+ has_content_trigger_constraint,
+ has_deadline_constraint,
+ has_idle_constraint,
+ has_storage_not_low_constraint,
+ has_timing_delay_constraint,
+ is_prefetch,
+ is_requested_expedited_job,
+ is_running_as_expedited_job,
+ num_previous_attempts,
+ requested_priority,
+ standby_bucket,
+ is_periodic,
+ has_flex_constraint,
+ is_requested_as_user_initiated_job,
+ is_running_as_user_initiated_job,
+ deadline_ms,
+ job_start_latency_ms,
+ num_uncompleted_work_items,
+ proc_state,
+ internal_stop_reason,
+ public_stop_reason
+ FROM android_job_scheduler_states;
+ """,
+ out=Csv("""
+"id","ts","dur","slice_id","job_name","uid","job_id","package_name","job_namespace","effective_priority","has_battery_not_low_constraint","has_charging_constraint","has_connectivity_constraint","has_content_trigger_constraint","has_deadline_constraint","has_idle_constraint","has_storage_not_low_constraint","has_timing_delay_constraint","is_prefetch","is_requested_expedited_job","is_running_as_expedited_job","num_previous_attempts","requested_priority","standby_bucket","is_periodic","has_flex_constraint","is_requested_as_user_initiated_job","is_running_as_user_initiated_job","deadline_ms","job_start_latency_ms","num_uncompleted_work_items","proc_state","internal_stop_reason","public_stop_reason"
+1,377089754138,83200835,10,"@androidx.work.systemjobscheduler@com.android.providers.media.module/androidx.work.impl.background.systemjob.SystemJobService_-2746960329031286783",10090,-2746960329031286783,"com.android.providers.media.module","androidx.work.systemjobscheduler",400,1,0,0,0,0,0,0,0,0,0,0,0,400,"EXEMPTED",0,0,0,0,0,3,0,"PROCESS_STATE_PERSISTENT","INTERNAL_STOP_REASON_CANCELLED","STOP_REASON_CANCELLED_BY_APP"
+2,385507499374,111746552,17,"@androidx.work.systemjobscheduler@com.android.providers.media.module/androidx.work.impl.background.systemjob.SystemJobService_-2746960329031286782",10090,-2746960329031286782,"com.android.providers.media.module","androidx.work.systemjobscheduler",400,1,0,0,0,0,0,0,0,0,0,0,0,400,"EXEMPTED",0,0,0,0,0,6,0,"PROCESS_STATE_PERSISTENT","INTERNAL_STOP_REASON_SUCCESSFUL_FINISH","STOP_REASON_UNDEFINED"
+3,416753734715,129444346,53,"@androidx.work.systemjobscheduler@com.android.providers.media.module/androidx.work.impl.background.systemjob.SystemJobService_-2746960329031286781",10090,-2746960329031286781,"com.android.providers.media.module","androidx.work.systemjobscheduler",400,1,0,0,0,0,0,0,0,0,0,0,0,400,"EXEMPTED",0,0,0,0,0,5,0,"PROCESS_STATE_PERSISTENT","INTERNAL_STOP_REASON_SUCCESSFUL_FINISH","STOP_REASON_UNDEFINED"
+4,422530232411,86735906,59,"@androidx.work.systemjobscheduler@com.android.providers.media.module/androidx.work.impl.background.systemjob.SystemJobService_-2746960329031286780",10090,-2746960329031286780,"com.android.providers.media.module","androidx.work.systemjobscheduler",400,1,0,0,0,0,0,0,0,0,0,0,0,400,"EXEMPTED",0,0,0,0,0,3,0,"PROCESS_STATE_PERSISTENT","INTERNAL_STOP_REASON_SUCCESSFUL_FINISH","STOP_REASON_UNDEFINED"
+ """))
+
+ def test_android_job_scheduler_with_screen_charging_output(self):
+ return DiffTestBlueprint(
+ trace=DataPath('android_job_scheduler.perfetto-trace'),
+ query="""
+ INCLUDE PERFETTO MODULE android.job_scheduler_states;
+ SELECT
+ ts,
+ dur,
+ slice_id,
+ job_name,
+ uid,
+ job_id,
+ job_dur,
+ package_name,
+ job_namespace,
+ charging_state,
+ screen_state,
+ effective_priority,
+ has_battery_not_low_constraint,
+ has_charging_constraint,
+ has_connectivity_constraint,
+ has_content_trigger_constraint,
+ has_deadline_constraint,
+ has_idle_constraint,
+ has_storage_not_low_constraint,
+ has_timing_delay_constraint,
+ is_prefetch,
+ is_requested_expedited_job,
+ is_running_as_expedited_job,
+ num_previous_attempts,
+ requested_priority,
+ standby_bucket,
+ is_periodic,
+ has_flex_constraint,
+ is_requested_as_user_initiated_job,
+ is_running_as_user_initiated_job,
+ deadline_ms,
+ job_start_latency_ms,
+ num_uncompleted_work_items,
+ proc_state,
+ internal_stop_reason,
+ public_stop_reason
+ from android_job_scheduler_with_screen_charging_states;
+ """,
+ out=Csv("""
+ "ts","dur","slice_id","job_name","uid","job_id","job_dur","package_name","job_namespace","charging_state","screen_state","effective_priority","has_battery_not_low_constraint","has_charging_constraint","has_connectivity_constraint","has_content_trigger_constraint","has_deadline_constraint","has_idle_constraint","has_storage_not_low_constraint","has_timing_delay_constraint","is_prefetch","is_requested_expedited_job","is_running_as_expedited_job","num_previous_attempts","requested_priority","standby_bucket","is_periodic","has_flex_constraint","is_requested_as_user_initiated_job","is_running_as_user_initiated_job","deadline_ms","job_start_latency_ms","num_uncompleted_work_items","proc_state","internal_stop_reason","public_stop_reason"
+377089754138,83200835,10,"@androidx.work.systemjobscheduler@com.android.providers.media.module/androidx.work.impl.background.systemjob.SystemJobService_-2746960329031286783",10090,-2746960329031286783,83200835,"com.android.providers.media.module","androidx.work.systemjobscheduler","Charging","Unknown",400,1,0,0,0,0,0,0,0,0,0,0,0,400,"EXEMPTED",0,0,0,0,0,3,0,"PROCESS_STATE_PERSISTENT","INTERNAL_STOP_REASON_CANCELLED","STOP_REASON_CANCELLED_BY_APP"
+385507499374,111746552,17,"@androidx.work.systemjobscheduler@com.android.providers.media.module/androidx.work.impl.background.systemjob.SystemJobService_-2746960329031286782",10090,-2746960329031286782,111746552,"com.android.providers.media.module","androidx.work.systemjobscheduler","Charging","Unknown",400,1,0,0,0,0,0,0,0,0,0,0,0,400,"EXEMPTED",0,0,0,0,0,6,0,"PROCESS_STATE_PERSISTENT","INTERNAL_STOP_REASON_SUCCESSFUL_FINISH","STOP_REASON_UNDEFINED"
+416753734715,129444346,53,"@androidx.work.systemjobscheduler@com.android.providers.media.module/androidx.work.impl.background.systemjob.SystemJobService_-2746960329031286781",10090,-2746960329031286781,129444346,"com.android.providers.media.module","androidx.work.systemjobscheduler","Charging","Unknown",400,1,0,0,0,0,0,0,0,0,0,0,0,400,"EXEMPTED",0,0,0,0,0,5,0,"PROCESS_STATE_PERSISTENT","INTERNAL_STOP_REASON_SUCCESSFUL_FINISH","STOP_REASON_UNDEFINED"
+422530232411,86735906,59,"@androidx.work.systemjobscheduler@com.android.providers.media.module/androidx.work.impl.background.systemjob.SystemJobService_-2746960329031286780",10090,-2746960329031286780,86735906,"com.android.providers.media.module","androidx.work.systemjobscheduler","Charging","Unknown",400,1,0,0,0,0,0,0,0,0,0,0,0,400,"EXEMPTED",0,0,0,0,0,3,0,"PROCESS_STATE_PERSISTENT","INTERNAL_STOP_REASON_SUCCESSFUL_FINISH","STOP_REASON_UNDEFINED"
+ """))
diff --git a/test/trace_processor/diff_tests/stdlib/chrome/tests.py b/test/trace_processor/diff_tests/stdlib/chrome/tests.py
index 6e5fd6c..c7ce840 100755
--- a/test/trace_processor/diff_tests/stdlib/chrome/tests.py
+++ b/test/trace_processor/diff_tests/stdlib/chrome/tests.py
@@ -229,3 +229,119 @@
703,2
708,2
"""))
+
+ def test_chrome_graphics_pipeline_surface_frame_steps(self):
+ return DiffTestBlueprint(
+ trace=DataPath('scroll_m131.pftrace'),
+ query="""
+ INCLUDE PERFETTO MODULE chrome.graphics_pipeline;
+
+ SELECT
+ id,
+ ts,
+ dur,
+ step,
+ surface_frame_trace_id,
+ utid
+ FROM chrome_graphics_pipeline_surface_frame_steps
+ ORDER BY ts
+ LIMIT 10;
+ """,
+ out=Csv("""
+ "id","ts","dur","step","surface_frame_trace_id","utid"
+ 209,1292552020392633,142000,"STEP_ISSUE_BEGIN_FRAME",1407387768455321,6
+ 210,1292552020907210,1264000,"STEP_RECEIVE_BEGIN_FRAME",1407387768455321,4
+ 259,1292552026179210,1550000,"STEP_GENERATE_COMPOSITOR_FRAME",1407387768455321,4
+ 264,1292552026586210,924000,"STEP_SUBMIT_COMPOSITOR_FRAME",1407387768455321,4
+ 265,1292552027255633,791000,"STEP_RECEIVE_COMPOSITOR_FRAME",1407387768455321,6
+ 268,1292552028200633,122000,"STEP_ISSUE_BEGIN_FRAME",4294967439,6
+ 269,1292552028581257,1772000,"STEP_GENERATE_COMPOSITOR_FRAME",4294967439,1
+ 276,1292552030185257,164000,"STEP_SUBMIT_COMPOSITOR_FRAME",4294967439,1
+ 277,1292552030600633,195000,"STEP_RECEIVE_COMPOSITOR_FRAME",4294967439,6
+ 302,1292552032277633,178000,"STEP_ISSUE_BEGIN_FRAME",1407387768455322,6
+ """))
+
+ def test_chrome_graphics_pipeline_display_frame_steps(self):
+ return DiffTestBlueprint(
+ trace=DataPath('scroll_m131.pftrace'),
+ query="""
+ INCLUDE PERFETTO MODULE chrome.graphics_pipeline;
+
+ SELECT
+ id,
+ ts,
+ dur,
+ step,
+ display_trace_id,
+ utid
+ FROM chrome_graphics_pipeline_display_frame_steps
+ ORDER BY ts
+ LIMIT 10;
+ """,
+ out=Csv("""
+ "id","ts","dur","step","display_trace_id","utid"
+ 279,1292552030930633,1263000,"STEP_DRAW_AND_SWAP",65565,6
+ 285,1292552031240633,143000,"STEP_SURFACE_AGGREGATION",65565,6
+ 299,1292552032042633,68000,"STEP_SEND_BUFFER_SWAP",65565,6
+ 319,1292552033751131,667000,"STEP_BUFFER_SWAP_POST_SUBMIT",65565,7
+ 337,1292552036240633,2033000,"STEP_DRAW_AND_SWAP",65566,6
+ 341,1292552036520633,873000,"STEP_SURFACE_AGGREGATION",65566,6
+ 359,1292552038113633,75000,"STEP_SEND_BUFFER_SWAP",65566,6
+ 376,1292552039773131,458000,"STEP_BUFFER_SWAP_POST_SUBMIT",65566,7
+ 394,1292552043191131,48000,"STEP_FINISH_BUFFER_SWAP",65565,7
+ 397,1292552043253633,75000,"STEP_SWAP_BUFFERS_ACK",65565,6
+ """))
+
+ def test_chrome_graphics_pipeline_aggregated_frames(self):
+ return DiffTestBlueprint(
+ trace=DataPath('scroll_m131.pftrace'),
+ query="""
+ INCLUDE PERFETTO MODULE chrome.graphics_pipeline;
+
+ SELECT
+ display_trace_id,
+ surface_frame_trace_id
+ FROM chrome_graphics_pipeline_aggregated_frames
+ ORDER BY display_trace_id, surface_frame_trace_id
+ LIMIT 10;
+ """,
+ out=Csv("""
+ "display_trace_id","surface_frame_trace_id"
+ 65565,4294967439
+ 65565,1407387768455321
+ 65566,4294967440
+ 65566,1407387768455322
+ 65567,4294967441
+ 65567,1407387768455323
+ 65568,4294967442
+ 65568,1407387768455324
+ 65569,4294967443
+ 65569,1407387768455325
+ """))
+
+ def test_chrome_graphics_pipeline_inputs_to_surface_frames(self):
+ return DiffTestBlueprint(
+ trace=DataPath('scroll_m131.pftrace'),
+ query="""
+ INCLUDE PERFETTO MODULE chrome.graphics_pipeline;
+
+ SELECT
+ surface_frame_trace_id,
+ latency_id
+ FROM chrome_graphics_pipeline_inputs_to_surface_frames
+ ORDER BY surface_frame_trace_id, latency_id
+ LIMIT 10;
+ """,
+ out=Csv("""
+ "surface_frame_trace_id","latency_id"
+ 1407387768455321,-2143831735395279174
+ 1407387768455321,-2143831735395279169
+ 1407387768455322,-2143831735395279191
+ 1407387768455323,-2143831735395279278
+ 1407387768455324,-2143831735395279270
+ 1407387768455325,-2143831735395279284
+ 1407387768455326,-2143831735395279244
+ 1407387768455327,-2143831735395279233
+ 1407387768455328,-2143831735395279258
+ 1407387768455329,-2143831735395279255
+ """))
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 fcb98a7..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
@@ -307,3 +307,172 @@
"BrowserMainToRendererCompositor","[NULL]",22.250000,50230,"GESTURE_SCROLL_UPDATE"
"SubmitCompositorFrameToPresentationCompositorFrame","BufferReadyToLatch",22.267000,50517,"GESTURE_SCROLL_UPDATE"
"""))
+
+ def test_chrome_event_latencies(self):
+ return DiffTestBlueprint(
+ trace=DataPath('chrome_input_with_frame_view_new.pftrace'),
+ query="""
+ INCLUDE PERFETTO MODULE chrome.event_latency;
+
+ SELECT
+ id,
+ name,
+ ts,
+ dur,
+ scroll_update_id,
+ is_presented,
+ event_type,
+ track_id,
+ vsync_interval_ms,
+ is_janky_scrolled_frame,
+ buffer_available_timestamp,
+ buffer_ready_timestamp,
+ latch_timestamp,
+ swap_end_timestamp,
+ presentation_timestamp
+ FROM chrome_event_latencies
+ WHERE
+ (
+ event_type = 'GESTURE_SCROLL_UPDATE'
+ OR event_type = 'INERTIAL_GESTURE_SCROLL_UPDATE')
+ AND is_presented
+ ORDER BY id
+ LIMIT 10;
+ """,
+ out=Csv("""
+ "id","name","ts","dur","scroll_update_id","is_presented","event_type","track_id","vsync_interval_ms","is_janky_scrolled_frame","buffer_available_timestamp","buffer_ready_timestamp","latch_timestamp","swap_end_timestamp","presentation_timestamp"
+ 69,"EventLatency",4488833086777189,49497000,10,1,"GESTURE_SCROLL_UPDATE",14,11.111000,0,4488833114547189,4488833114874189,4488833119872189,4488833126765189,4488833136274189
+ 431,"EventLatency",4488833114107189,33292000,14,1,"INERTIAL_GESTURE_SCROLL_UPDATE",27,11.111000,0,"[NULL]",4488833122361189,4488833137159189,4488833138924189,4488833147399189
+ 480,"EventLatency",4488833125213189,33267000,15,1,"INERTIAL_GESTURE_SCROLL_UPDATE",29,11.111000,0,4488833131905189,4488833132275189,4488833148524189,4488833150809189,4488833158480189
+ 531,"EventLatency",4488833142387189,27234000,16,1,"INERTIAL_GESTURE_SCROLL_UPDATE",32,11.111000,0,4488833147322189,4488833147657189,4488833159447189,4488833161654189,4488833169621189
+ 581,"EventLatency",4488833153584189,27225000,17,1,"INERTIAL_GESTURE_SCROLL_UPDATE",34,11.111000,0,4488833158111189,4488833158333189,4488833170433189,4488833171562189,4488833180809189
+ 630,"EventLatency",4488833164707189,27227000,18,1,"INERTIAL_GESTURE_SCROLL_UPDATE",36,11.111000,0,4488833169215189,4488833169529189,4488833181700189,4488833183880189,4488833191934189
+ 679,"EventLatency",4488833175814189,27113000,19,1,"INERTIAL_GESTURE_SCROLL_UPDATE",38,11.111000,0,4488833180589189,4488833180876189,4488833192722189,4488833194160189,4488833202927189
+ 728,"EventLatency",4488833186929189,38217000,20,1,"INERTIAL_GESTURE_SCROLL_UPDATE",40,11.111000,1,4488833201398189,4488833201459189,4488833215357189,4488833217727189,4488833225146189
+ 772,"EventLatency",4488833198068189,38185000,21,1,"INERTIAL_GESTURE_SCROLL_UPDATE",42,11.111000,0,4488833211744189,4488833212097189,4488833226028189,4488833227246189,4488833236253189
+ 819,"EventLatency",4488833209202189,38170000,22,1,"INERTIAL_GESTURE_SCROLL_UPDATE",43,11.111000,0,4488833223115189,4488833223308189,4488833237115189,4488833238196189,4488833247372189
+ """))
+
+ # A trace from M131 (ToT as of adding this test) has the necessary
+ # events/arguments.
+ def test_chrome_input_pipeline_steps(self):
+ return DiffTestBlueprint(
+ trace=DataPath('scroll_m131.pftrace'),
+ query="""
+ INCLUDE PERFETTO MODULE chrome.input;
+
+ SELECT latency_id,
+ input_type,
+ GROUP_CONCAT(step) AS steps
+ FROM chrome_input_pipeline_steps
+ GROUP BY latency_id
+ ORDER by input_type
+ LIMIT 20
+ """,
+ out=Csv("""
+ "latency_id","input_type","steps"
+ -2143831735395279846,"GESTURE_FLING_CANCEL_EVENT","STEP_SEND_INPUT_EVENT_UI"
+ -2143831735395279570,"GESTURE_FLING_CANCEL_EVENT","STEP_SEND_INPUT_EVENT_UI"
+ -2143831735395279037,"GESTURE_FLING_CANCEL_EVENT","STEP_SEND_INPUT_EVENT_UI"
+ -2143831735395280234,"GESTURE_FLING_START_EVENT","STEP_SEND_INPUT_EVENT_UI"
+ -2143831735395279756,"GESTURE_FLING_START_EVENT","STEP_SEND_INPUT_EVENT_UI"
+ -2143831735395279516,"GESTURE_FLING_START_EVENT","STEP_SEND_INPUT_EVENT_UI"
+ -2143831735395278975,"GESTURE_FLING_START_EVENT","STEP_SEND_INPUT_EVENT_UI"
+ -2143831735395280167,"GESTURE_SCROLL_BEGIN_EVENT","STEP_SEND_INPUT_EVENT_UI,STEP_HANDLE_INPUT_EVENT_IMPL,STEP_DID_HANDLE_INPUT_AND_OVERSCROLL,STEP_GESTURE_EVENT_HANDLED"
+ -2143831735395279816,"GESTURE_SCROLL_BEGIN_EVENT","STEP_SEND_INPUT_EVENT_UI,STEP_HANDLE_INPUT_EVENT_IMPL,STEP_DID_HANDLE_INPUT_AND_OVERSCROLL,STEP_GESTURE_EVENT_HANDLED"
+ -2143831735395279175,"GESTURE_SCROLL_BEGIN_EVENT","STEP_SEND_INPUT_EVENT_UI,STEP_HANDLE_INPUT_EVENT_IMPL,STEP_DID_HANDLE_INPUT_AND_OVERSCROLL,STEP_GESTURE_EVENT_HANDLED"
+ -2143831735395279004,"GESTURE_SCROLL_BEGIN_EVENT","STEP_SEND_INPUT_EVENT_UI,STEP_HANDLE_INPUT_EVENT_IMPL,STEP_DID_HANDLE_INPUT_AND_OVERSCROLL,STEP_GESTURE_EVENT_HANDLED"
+ -2143831735395280198,"GESTURE_SCROLL_END_EVENT","STEP_SEND_INPUT_EVENT_UI,STEP_GESTURE_EVENT_HANDLED,STEP_HANDLE_INPUT_EVENT_IMPL,STEP_DID_HANDLE_INPUT_AND_OVERSCROLL"
+ -2143831735395279762,"GESTURE_SCROLL_END_EVENT","STEP_SEND_INPUT_EVENT_UI,STEP_GESTURE_EVENT_HANDLED,STEP_HANDLE_INPUT_EVENT_IMPL,STEP_DID_HANDLE_INPUT_AND_OVERSCROLL"
+ -2143831735395279584,"GESTURE_SCROLL_END_EVENT","STEP_SEND_INPUT_EVENT_UI,STEP_GESTURE_EVENT_HANDLED,STEP_HANDLE_INPUT_EVENT_IMPL,STEP_DID_HANDLE_INPUT_AND_OVERSCROLL"
+ -2143831735395279038,"GESTURE_SCROLL_END_EVENT","STEP_SEND_INPUT_EVENT_UI,STEP_GESTURE_EVENT_HANDLED,STEP_HANDLE_INPUT_EVENT_IMPL,STEP_DID_HANDLE_INPUT_AND_OVERSCROLL"
+ -2143831735395280256,"GESTURE_SCROLL_UPDATE_EVENT","STEP_SEND_INPUT_EVENT_UI,STEP_HANDLE_INPUT_EVENT_IMPL,STEP_DID_HANDLE_INPUT_AND_OVERSCROLL,STEP_GESTURE_EVENT_HANDLED"
+ -2143831735395280254,"GESTURE_SCROLL_UPDATE_EVENT","STEP_SEND_INPUT_EVENT_UI,STEP_HANDLE_INPUT_EVENT_IMPL,STEP_DID_HANDLE_INPUT_AND_OVERSCROLL,STEP_GESTURE_EVENT_HANDLED"
+ -2143831735395280250,"GESTURE_SCROLL_UPDATE_EVENT","STEP_SEND_INPUT_EVENT_UI,STEP_HANDLE_INPUT_EVENT_IMPL,STEP_DID_HANDLE_INPUT_AND_OVERSCROLL,STEP_GESTURE_EVENT_HANDLED"
+ -2143831735395280248,"GESTURE_SCROLL_UPDATE_EVENT","STEP_SEND_INPUT_EVENT_UI,STEP_HANDLE_INPUT_EVENT_IMPL,STEP_DID_HANDLE_INPUT_AND_OVERSCROLL,STEP_GESTURE_EVENT_HANDLED"
+ -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'),
+ query="""
+ INCLUDE PERFETTO MODULE chrome.input;
+
+ SELECT
+ coalesced_latency_id,
+ presented_latency_id
+ FROM chrome_coalesced_inputs
+ ORDER BY coalesced_latency_id
+ LIMIT 10
+ """,
+ out=Csv("""
+ "coalesced_latency_id","presented_latency_id"
+ -2143831735395280239,-2143831735395280239
+ -2143831735395280183,-2143831735395280179
+ -2143831735395280179,-2143831735395280179
+ -2143831735395280166,-2143831735395280166
+ -2143831735395280158,-2143831735395280153
+ -2143831735395280153,-2143831735395280153
+ -2143831735395280150,-2143831735395280146
+ -2143831735395280146,-2143831735395280146
+ -2143831735395280144,-2143831735395280139
+ -2143831735395280139,-2143831735395280139
+ """))
+
+ def test_chrome_touch_move_to_scroll_update(self):
+ return DiffTestBlueprint(
+ trace=DataPath('scroll_m131.pftrace'),
+ query="""
+ INCLUDE PERFETTO MODULE chrome.input;
+
+ SELECT
+ touch_move_latency_id,
+ scroll_update_latency_id
+ FROM chrome_touch_move_to_scroll_update
+ ORDER BY touch_move_latency_id
+ LIMIT 10
+ """,
+ out=Csv("""
+ "touch_move_latency_id","scroll_update_latency_id"
+ -2143831735395280236,-2143831735395280239
+ -2143831735395280189,-2143831735395280179
+ -2143831735395280181,-2143831735395280139
+ -2143831735395280177,-2143831735395280183
+ -2143831735395280163,-2143831735395280166
+ -2143831735395280160,-2143831735395280158
+ -2143831735395280155,-2143831735395280153
+ -2143831735395280152,-2143831735395280150
+ -2143831735395280148,-2143831735395280146
+ -2143831735395280142,-2143831735395280132
+ """))
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/intervals/tests.py b/test/trace_processor/diff_tests/stdlib/intervals/tests.py
index cf48cdc..2712599 100644
--- a/test/trace_processor/diff_tests/stdlib/intervals/tests.py
+++ b/test/trace_processor/diff_tests/stdlib/intervals/tests.py
@@ -118,3 +118,72 @@
8,1,0,0
9,1,1,1
"""))
+
+ def test_intervals_flatten_by_intersection(self):
+ return DiffTestBlueprint(
+ trace=TextProto(""),
+ query="""
+ INCLUDE PERFETTO MODULE intervals.overlap;
+
+ CREATE PERFETTO TABLE foo AS
+ WITH roots_data (id, ts, dur, utid) AS (
+ VALUES
+ (0, 0, 9, 0),
+ (0, 0, 9, 1),
+ (1, 9, 1, 2)
+ ), children_data (id, parent_id, ts, dur, utid) AS (
+ VALUES
+ (2, 0, 1, 3, 0),
+ (3, 0, 5, 1, 0),
+ (4, 0, 6, 1, 0),
+ (5, 0, 7, 0, 0),
+ (6, 0, 7, 1, 0),
+ (7, 2, 2, 1, 0)
+ )
+ SELECT *
+ FROM _intervals_merge_root_and_children_by_intersection!(roots_data, children_data, utid);
+
+ SELECT ts, dur, id, root_id FROM _intervals_flatten!(foo) ORDER BY ts;
+ """,
+ out=Csv("""
+ "ts","dur","id","root_id"
+ 0,1,0,0
+ 1,1,2,0
+ 2,1,7,0
+ 3,1,2,0
+ 4,1,0,0
+ 5,1,3,0
+ 6,1,4,0
+ 7,1,6,0
+ 8,1,0,0
+ """))
+
+ def test_intervals_flatten_by_intersection_no_matching_key(self):
+ return DiffTestBlueprint(
+ trace=TextProto(""),
+ query="""
+ INCLUDE PERFETTO MODULE intervals.overlap;
+
+ CREATE PERFETTO TABLE foo AS
+ WITH roots_data (id, ts, dur, utid) AS (
+ VALUES
+ (0, 0, 9, 1),
+ (0, 0, 9, 2),
+ (1, 9, 1, 3)
+ ), children_data (id, parent_id, ts, dur, utid) AS (
+ VALUES
+ (2, 0, 1, 3, 0),
+ (3, 0, 5, 1, 0),
+ (4, 0, 6, 1, 0),
+ (5, 0, 7, 0, 0),
+ (6, 0, 7, 1, 0),
+ (7, 2, 2, 1, 0)
+ )
+ SELECT *
+ FROM _intervals_merge_root_and_children_by_intersection!(roots_data, children_data, utid);
+
+ SELECT ts, dur, id, root_id FROM _intervals_flatten!(foo) ORDER BY ts;
+ """,
+ out=Csv("""
+ "ts","dur","id","root_id"
+ """))
diff --git a/test/trace_processor/diff_tests/stdlib/linux/tests.py b/test/trace_processor/diff_tests/stdlib/linux/tests.py
index 5be22eb..20fc769 100644
--- a/test/trace_processor/diff_tests/stdlib/linux/tests.py
+++ b/test/trace_processor/diff_tests/stdlib/linux/tests.py
@@ -44,4 +44,37 @@
18,45,2379,2379,"csf_kcpu_0","csf_kcpu_0"
12,47,247,247,"decon0_kthread","decon0_kthread"
65,48,159,159,"spi0","spi0"
- """))
\ No newline at end of file
+ """))
+
+ # Tests that DSU devfreq counters are working properly
+ def test_dsu_devfreq(self):
+ return DiffTestBlueprint(
+ trace=DataPath('wattson_tk4_pcmark.pb'),
+ query=("""
+ INCLUDE PERFETTO MODULE linux.devfreq;
+ SELECT id, ts, dur, dsu_freq FROM linux_devfreq_dsu_counter
+ LIMIT 20
+ """),
+ out=Csv("""
+ "id","ts","dur","dsu_freq"
+ 61,4106584783742,11482788,610000
+ 166,4106596266530,8108602,1197000
+ 212,4106604375132,21453410,610000
+ 487,4106625828542,39427368,820000
+ 1130,4106665255910,3264242,610000
+ 1173,4106668520152,16966105,820000
+ 1391,4106685486257,10596883,970000
+ 1584,4106696083140,10051636,610000
+ 1868,4106706134776,14058960,820000
+ 2136,4106720193736,116719238,610000
+ 4388,4106836912974,8285848,1197000
+ 4583,4106845198822,16518433,820000
+ 5006,4106861717255,9357503,1328000
+ 5238,4106871074758,27228760,1197000
+ 5963,4106898303518,16581706,820000
+ 6498,4106914885224,9954142,1197000
+ 6763,4106924839366,9024780,970000
+ 7061,4106933864146,26264160,820000
+ 7637,4106960128306,11008505,970000
+ 7880,4106971136811,9282511,1197000
+ """))
diff --git a/test/trace_processor/diff_tests/stdlib/slices/tests.py b/test/trace_processor/diff_tests/stdlib/slices/tests.py
index fa326d0..88b430f 100644
--- a/test/trace_processor/diff_tests/stdlib/slices/tests.py
+++ b/test/trace_processor/diff_tests/stdlib/slices/tests.py
@@ -104,6 +104,26 @@
"NestedThreadSlice",6,1,1
"""))
+ def test_slice_remove_nulls_and_reparent(self):
+ return DiffTestBlueprint(
+ trace=Path('trace.py'),
+ query="""
+ INCLUDE PERFETTO MODULE slices.hierarchy;
+
+ SELECT id, parent_id, name, depth
+ FROM _slice_remove_nulls_and_reparent!(
+ (SELECT id, parent_id, depth, IIF(name = 'ProcessSlice', NULL, name) AS name
+ FROM slice),
+ name
+ ) LIMIT 10;
+ """,
+ out=Csv("""
+ "id","parent_id","name","depth"
+ 0,"[NULL]","AsyncSlice",0
+ 2,"[NULL]","ThreadSlice",0
+ 3,2,"NestedThreadSlice",0
+ """))
+
# Common functions
def test_slice_flattened(self):
@@ -156,4 +176,28 @@
7,33333
8,46926
9,17865
- """))
\ No newline at end of file
+ """))
+
+ 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/stdlib/viz/tests.py b/test/trace_processor/diff_tests/stdlib/viz/tests.py
new file mode 100644
index 0000000..0b986bb
--- /dev/null
+++ b/test/trace_processor/diff_tests/stdlib/viz/tests.py
@@ -0,0 +1,459 @@
+#!/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, TraceInjector
+from python.generators.diff_tests.testing import TestSuite
+
+
+class Viz(TestSuite):
+ chronological_trace = TextProto(r"""
+ packet {
+ track_descriptor {
+ uuid: 1
+ name: "Root Chronological"
+ child_ordering: 2
+ }
+ }
+ packet {
+ track_descriptor {
+ uuid: 11
+ name: "A"
+ parent_uuid: 1
+ }
+ }
+ packet {
+ timestamp: 220
+ trusted_packet_sequence_id: 3903809
+ track_event {
+ type: TYPE_SLICE_BEGIN
+ track_uuid: 11
+ name: "A1"
+ }
+ }
+ packet {
+ timestamp: 230
+ trusted_packet_sequence_id: 3903809
+ track_event {
+ type: TYPE_SLICE_END
+ track_uuid: 11
+ }
+ }
+ packet {
+ track_descriptor {
+ uuid: 12
+ name: "B"
+ parent_uuid: 1
+ }
+ }
+ packet {
+ timestamp: 210
+ trusted_packet_sequence_id: 3903809
+ track_event {
+ type: TYPE_SLICE_BEGIN
+ track_uuid: 12
+ name: "B"
+ }
+ }
+ packet {
+ timestamp: 240
+ trusted_packet_sequence_id: 3903809
+ track_event {
+ type: TYPE_SLICE_END
+ track_uuid: 12
+ }
+ }
+ """)
+
+ explicit_trace = TextProto(r"""
+ packet {
+ track_descriptor {
+ uuid: 2
+ name: "Root Explicit"
+ child_ordering: 3
+ }
+ }
+ packet {
+ track_descriptor {
+ uuid: 110
+ name: "B"
+ parent_uuid: 2
+ sibling_order_rank: 1
+ }
+ }
+ packet {
+ track_descriptor {
+ uuid: 120
+ name: "A"
+ parent_uuid: 2
+ sibling_order_rank: 100
+ }
+ }
+ packet {
+ track_descriptor {
+ uuid: 130
+ name: "C"
+ parent_uuid: 2
+ sibling_order_rank: -100
+ }
+ }
+ packet {
+ timestamp: 220
+ trusted_packet_sequence_id: 3903809
+ track_event {
+ type: TYPE_SLICE_BEGIN
+ track_uuid: 110
+ name: "1"
+ }
+ }
+ packet {
+ timestamp: 230
+ trusted_packet_sequence_id: 3903809
+ track_event {
+ type: TYPE_SLICE_END
+ track_uuid: 110
+ }
+ }
+ packet {
+ timestamp: 230
+ trusted_packet_sequence_id: 3903809
+ track_event {
+ type: TYPE_SLICE_BEGIN
+ track_uuid: 120
+ name: "2"
+ }
+ }
+ packet {
+ timestamp: 240
+ trusted_packet_sequence_id: 3903809
+ track_event {
+ type: TYPE_SLICE_END
+ track_uuid: 120
+ }
+ }
+ packet {
+ timestamp: 225
+ trusted_packet_sequence_id: 3903809
+ track_event {
+ type: TYPE_SLICE_BEGIN
+ track_uuid: 130
+ name: "3"
+ }
+ }
+ packet {
+ timestamp: 235
+ trusted_packet_sequence_id: 3903809
+ track_event {
+ type: TYPE_SLICE_END
+ track_uuid: 130
+ }
+ }
+ """)
+
+ lexicographic_trace = TextProto(r"""
+ packet {
+ track_descriptor {
+ uuid: 3
+ name: "Root Lexicographic"
+ child_ordering: 1
+ }
+ }
+ packet {
+ track_descriptor {
+ uuid: 1100
+ name: "B"
+ parent_uuid: 3
+ }
+ }
+ packet {
+ track_descriptor {
+ uuid: 1200
+ name: "A"
+ parent_uuid: 3
+ }
+ }
+ packet {
+ track_descriptor {
+ uuid: 1300
+ name: "C"
+ parent_uuid: 3
+ }
+ }
+ packet {
+ timestamp: 220
+ trusted_packet_sequence_id: 3903809
+ track_event {
+ type: TYPE_SLICE_BEGIN
+ track_uuid: 1100
+ name: "A1"
+ }
+ }
+ packet {
+ timestamp: 230
+ trusted_packet_sequence_id: 3903809
+ track_event {
+ type: TYPE_SLICE_END
+ track_uuid: 1100
+ }
+ }
+ packet {
+ timestamp: 210
+ trusted_packet_sequence_id: 3903809
+ track_event {
+ type: TYPE_SLICE_BEGIN
+ track_uuid: 1200
+ name: "B1"
+ }
+ }
+ packet {
+ timestamp: 300
+ trusted_packet_sequence_id: 3903809
+ track_event {
+ type: TYPE_SLICE_END
+ track_uuid: 1200
+ }
+ }
+ packet {
+ timestamp: 350
+ trusted_packet_sequence_id: 3903809
+ track_event {
+ type: TYPE_SLICE_BEGIN
+ track_uuid: 1300
+ name: "C1"
+ }
+ }
+ packet {
+ timestamp: 400
+ trusted_packet_sequence_id: 3903809
+ track_event {
+ type: TYPE_SLICE_END
+ track_uuid: 1300
+ }
+ }
+ """)
+
+ all_ordering_trace = TextProto(f"""{chronological_trace.contents}
+ {explicit_trace.contents}
+ {lexicographic_trace.contents}""")
+
+ def test_track_event_tracks_chronological(self):
+ return DiffTestBlueprint(
+ trace=self.chronological_trace,
+ query="""
+ SELECT
+ id,
+ parent_id,
+ EXTRACT_ARG(source_arg_set_id, 'child_ordering') AS ordering,
+ EXTRACT_ARG(source_arg_set_id, 'sibling_order_rank') AS rank
+ FROM track;
+ """,
+ out=Csv("""
+ "id","parent_id","ordering","rank"
+ 0,"[NULL]","chronological","[NULL]"
+ 1,0,"[NULL]","[NULL]"
+ 2,0,"[NULL]","[NULL]"
+ """))
+
+ def test_all_tracks_ordered_chronological(self):
+ return DiffTestBlueprint(
+ trace=self.chronological_trace,
+ query="""
+ INCLUDE PERFETTO MODULE viz.summary.tracks;
+ SELECT id, order_id
+ FROM _track_event_tracks_ordered
+ ORDER BY id;
+ """,
+ out=Csv("""
+ "id","order_id"
+ 1,2
+ 2,1
+ """))
+
+ def test_track_event_tracks_explicit(self):
+ return DiffTestBlueprint(
+ trace=self.explicit_trace,
+ query="""
+ SELECT
+ id,
+ parent_id,
+ EXTRACT_ARG(source_arg_set_id, 'child_ordering') AS ordering,
+ EXTRACT_ARG(source_arg_set_id, 'sibling_order_rank') AS rank
+ FROM track;
+ """,
+ out=Csv("""
+ "id","parent_id","ordering","rank"
+ 0,"[NULL]","explicit","[NULL]"
+ 1,0,"[NULL]",1
+ 2,0,"[NULL]",100
+ 3,0,"[NULL]",-100
+ """))
+
+ def test_all_tracks_ordered_explicit(self):
+ return DiffTestBlueprint(
+ trace=self.explicit_trace,
+ query="""
+ INCLUDE PERFETTO MODULE viz.summary.tracks;
+ SELECT id, order_id
+ FROM _track_event_tracks_ordered
+ ORDER BY id;
+ """,
+ out=Csv("""
+ "id","order_id"
+ 1,2
+ 2,3
+ 3,1
+ """))
+
+ def test_track_event_tracks_lexicographic(self):
+ return DiffTestBlueprint(
+ trace=self.lexicographic_trace,
+ query="""
+ SELECT
+ id,
+ parent_id,
+ name,
+ EXTRACT_ARG(source_arg_set_id, 'child_ordering') AS ordering,
+ EXTRACT_ARG(source_arg_set_id, 'sibling_order_rank') AS rank
+ FROM track;
+ """,
+ out=Csv("""
+ "id","parent_id","name","ordering","rank"
+ 0,"[NULL]","Root Lexicographic","lexicographic","[NULL]"
+ 1,0,"B","[NULL]","[NULL]"
+ 2,0,"A","[NULL]","[NULL]"
+ 3,0,"C","[NULL]","[NULL]"
+ """))
+
+ def test_all_tracks_ordered_lexicographic(self):
+ return DiffTestBlueprint(
+ trace=self.lexicographic_trace,
+ query="""
+ INCLUDE PERFETTO MODULE viz.summary.tracks;
+ SELECT id, order_id
+ FROM _track_event_tracks_ordered
+ ORDER BY id;
+ """,
+ out=Csv("""
+ "id","order_id"
+ 1,2
+ 2,1
+ 3,3
+ """))
+
+ def test_track_event_tracks_all_orderings(self):
+ return DiffTestBlueprint(
+ trace=self.all_ordering_trace,
+ query="""
+ SELECT
+ id,
+ parent_id,
+ name,
+ EXTRACT_ARG(source_arg_set_id, 'child_ordering') AS ordering,
+ EXTRACT_ARG(source_arg_set_id, 'sibling_order_rank') AS rank
+ FROM track
+ ORDER BY parent_id, id;
+ """,
+ out=Csv("""
+ "id","parent_id","name","ordering","rank"
+ 0,"[NULL]","Root Chronological","chronological","[NULL]"
+ 3,"[NULL]","Root Lexicographic","lexicographic","[NULL]"
+ 5,"[NULL]","Root Explicit","explicit","[NULL]"
+ 1,0,"A","[NULL]","[NULL]"
+ 2,0,"B","[NULL]","[NULL]"
+ 4,3,"A","[NULL]","[NULL]"
+ 7,3,"B","[NULL]","[NULL]"
+ 10,3,"C","[NULL]","[NULL]"
+ 6,5,"B","[NULL]",1
+ 8,5,"C","[NULL]",-100
+ 9,5,"A","[NULL]",100
+ """))
+
+ def test_all_tracks_ordered_all_ordering(self):
+ return DiffTestBlueprint(
+ trace=self.all_ordering_trace,
+ query="""
+ INCLUDE PERFETTO MODULE viz.summary.tracks;
+ SELECT id, parent_id, order_id
+ FROM _track_event_tracks_ordered
+ JOIN track USING (id)
+ ORDER BY parent_id, id
+ """,
+ out=Csv("""
+ "id","parent_id","order_id"
+ 1,0,2
+ 2,0,1
+ 4,3,1
+ 7,3,2
+ 10,3,3
+ 6,5,2
+ 8,5,1
+ 9,5,3
+ """))
+
+ def test_sanity_ordering_tracks(self):
+ return DiffTestBlueprint(
+ trace=Path('track_event_tracks_ordering.textproto'),
+ query="""
+ SELECT
+ id,
+ parent_id,
+ name,
+ EXTRACT_ARG(source_arg_set_id, 'child_ordering') AS ordering,
+ EXTRACT_ARG(source_arg_set_id, 'sibling_order_rank') AS rank
+ FROM track
+ ORDER BY parent_id, id;
+ """,
+ out=Csv("""
+ "id","parent_id","name","ordering","rank"
+ 0,"[NULL]","explicit_parent","explicit",-10
+ 4,"[NULL]","chronological_parent","chronological","[NULL]"
+ 9,"[NULL]","lexicographic_parent","lexicographic","[NULL]"
+ 1,0,"explicit_child:no z-index","[NULL]","[NULL]"
+ 2,0,"explicit_child:5 z-index","[NULL]",5
+ 3,0,"explicit_child:-5 z-index","[NULL]",-5
+ 8,0,"explicit_child:-5 z-index","[NULL]",-5
+ 5,4,"chrono","[NULL]","[NULL]"
+ 6,4,"chrono2","[NULL]","[NULL]"
+ 7,4,"chrono1","[NULL]","[NULL]"
+ 10,9,"[NULL]","[NULL]","[NULL]"
+ 11,9,"a","[NULL]","[NULL]"
+ 12,9,"b","[NULL]","[NULL]"
+ 13,9,"ab","[NULL]","[NULL]"
+ """))
+
+ def test_sanity_ordering(self):
+ return DiffTestBlueprint(
+ trace=Path('track_event_tracks_ordering.textproto'),
+ query="""
+ INCLUDE PERFETTO MODULE viz.summary.tracks;
+ SELECT id, order_id
+ FROM _track_event_tracks_ordered
+ ORDER BY id;
+ """,
+ out=Csv("""
+ "id","order_id"
+ 1,3
+ 2,4
+ 3,1
+ 5,1
+ 6,2
+ 7,3
+ 8,2
+ 10,1
+ 11,2
+ 12,4
+ 13,3
+ """))
diff --git a/test/trace_processor/diff_tests/stdlib/viz/track_event_tracks_ordering.textproto b/test/trace_processor/diff_tests/stdlib/viz/track_event_tracks_ordering.textproto
new file mode 100644
index 0000000..90f3b05
--- /dev/null
+++ b/test/trace_processor/diff_tests/stdlib/viz/track_event_tracks_ordering.textproto
@@ -0,0 +1,163 @@
+# Explicit tracks.
+
+## Parent
+packet {
+ trusted_packet_sequence_id: 1
+ timestamp: 0
+ incremental_state_cleared: true
+ first_packet_on_sequence: true
+ track_descriptor {
+ uuid: 100
+ child_ordering: 3
+ name: "explicit_parent"
+ sibling_order_rank: -10
+ }
+ trace_packet_defaults {
+ track_event_defaults {
+ track_uuid: 1
+ }
+ }
+}
+
+## Children
+packet {
+ trusted_packet_sequence_id: 2
+ timestamp: 0
+ incremental_state_cleared: true
+ first_packet_on_sequence: true
+ track_descriptor {
+ uuid: 2
+ parent_uuid: 100
+ name: "explicit_child:no z-index"
+ }
+ trace_packet_defaults {
+ track_event_defaults {
+ track_uuid: 2
+ }
+ }
+}
+packet {
+ trusted_packet_sequence_id: 1
+ timestamp: 0
+ track_descriptor {
+ uuid: 3
+ parent_uuid: 100
+ name: "explicit_child:5 z-index"
+ sibling_order_rank: 5
+ }
+}
+packet {
+ trusted_packet_sequence_id: 1
+ timestamp: 0
+ track_descriptor {
+ uuid: 4
+ parent_uuid: 100
+ name: "explicit_child:-5 z-index"
+ sibling_order_rank: -5
+ }
+}
+packet {
+ trusted_packet_sequence_id: 1
+ timestamp: 100
+ track_descriptor {
+ uuid: 5
+ parent_uuid: 100
+ name: "explicit_child:-5 z-index"
+ sibling_order_rank: -5
+ }
+}
+
+# Lexicographic tracks.
+
+## Parent
+packet {
+ trusted_packet_sequence_id: 2
+ timestamp: 200
+ track_descriptor {
+ uuid: 200
+ child_ordering: 1
+ name: "lexicographic_parent"
+ }
+}
+
+## Children
+
+packet {
+ trusted_packet_sequence_id: 1
+ timestamp: 200
+ track_descriptor {
+ uuid: 6
+ parent_uuid: 200
+ }
+}
+packet {
+ trusted_packet_sequence_id: 1
+ timestamp: 1000
+ track_descriptor {
+ uuid: 7
+ parent_uuid: 200
+ name: "a"
+ }
+}
+packet {
+ trusted_packet_sequence_id: 2
+ timestamp: 2000
+ track_descriptor {
+ uuid: 8
+ parent_uuid: 200
+ name: "b"
+ }
+}
+# Should appear on overridden track "t2".
+packet {
+ trusted_packet_sequence_id: 2
+ timestamp: 2000
+ track_descriptor {
+ uuid: 9
+ parent_uuid: 200
+ name: "ab"
+ }
+}
+
+# Chronological tracks.
+
+## Parent
+packet {
+ trusted_packet_sequence_id: 2
+ timestamp: 1000
+ track_descriptor {
+ uuid: 300
+ child_ordering: 2
+ name: "chronological_parent"
+ }
+}
+
+## Children
+
+packet {
+ trusted_packet_sequence_id: 1
+ timestamp: 0
+ track_descriptor {
+ uuid: 10
+ parent_uuid: 300
+ name: "chrono"
+ }
+}
+packet {
+ trusted_packet_sequence_id: 2
+ timestamp: 10
+ track_descriptor {
+ uuid: 11
+ parent_uuid: 300
+ name: "chrono1"
+ }
+}
+packet {
+ trusted_packet_sequence_id: 2
+ timestamp: 5
+ track_descriptor {
+ uuid: 12
+ parent_uuid: 300
+ name: "chrono2"
+ }
+}
diff --git a/test/trace_processor/diff_tests/stdlib/wattson/tests.py b/test/trace_processor/diff_tests/stdlib/wattson/tests.py
index 1ddead7..b5c9cae 100644
--- a/test/trace_processor/diff_tests/stdlib/wattson/tests.py
+++ b/test/trace_processor/diff_tests/stdlib/wattson/tests.py
@@ -200,7 +200,7 @@
return DiffTestBlueprint(
trace=DataPath('wattson_dsu_pmu.pb'),
query=("""
- INCLUDE PERFETTO MODULE wattson.curves.ungrouped;
+ INCLUDE PERFETTO MODULE wattson.curves.estimates;
select * from _w_independent_cpus_calc
WHERE ts > 359661672577
ORDER by ts ASC
@@ -225,7 +225,7 @@
return DiffTestBlueprint(
trace=DataPath('wattson_dsu_pmu.pb'),
query=("""
- INCLUDE PERFETTO MODULE wattson.curves.ungrouped;
+ INCLUDE PERFETTO MODULE wattson.curves.estimates;
select * from _system_state_curves
ORDER by ts ASC
LIMIT 5
@@ -244,7 +244,7 @@
return DiffTestBlueprint(
trace=DataPath('wattson_dsu_pmu.pb'),
query=("""
- INCLUDE PERFETTO MODULE wattson.curves.ungrouped;
+ INCLUDE PERFETTO MODULE wattson.curves.estimates;
select * from _system_state_curves
WHERE ts > 359661672577
ORDER by ts ASC
@@ -264,7 +264,7 @@
return DiffTestBlueprint(
trace=DataPath('wattson_dsu_pmu.pb'),
query=("""
- INCLUDE PERFETTO MODULE wattson.curves.ungrouped;
+ INCLUDE PERFETTO MODULE wattson.curves.estimates;
select * from _system_state_mw
WHERE ts > 359661672577
ORDER by ts ASC
@@ -289,7 +289,7 @@
return DiffTestBlueprint(
trace=DataPath('wattson_eos_suspend.pb'),
query=("""
- INCLUDE PERFETTO MODULE wattson.curves.ungrouped;
+ INCLUDE PERFETTO MODULE wattson.curves.estimates;
select * from _system_state_curves
WHERE ts > 24790009884888
ORDER by ts ASC
@@ -304,57 +304,6 @@
24792795173845,50781,39.690000,39.690000,0.000000,39.690000,0.000000,0.000000,0.000000,0.000000,18.390000,"[NULL]","[NULL]"
"""))
- # Tests that device curve table is being looked up correctly
- def test_wattson_device_curve_per_policy(self):
- return DiffTestBlueprint(
- trace=DataPath('wattson_dsu_pmu.pb'),
- query=("""
- INCLUDE PERFETTO MODULE wattson.curves.grouped;
- select * from wattson_estimate_per_component
- WHERE ts > 359661672577
- ORDER by ts ASC
- LIMIT 10
- """),
- out=Csv("""
- "ts","dur","l3","little_cpus","mid_cpus","big_cpus"
- 359661672578,75521,18051.005200,49.300000,550.550000,2064.320000
- 359661748099,2254517,840849.917600,49.440000,47.000000,2064.320000
- 359664003674,11596,2770.713600,1031.260000,1054.100000,3885.780000
- 359664015270,4720,1127.359000,1031.260000,1054.100000,2064.320000
- 359664019990,18921,4522.446400,1031.260000,550.550000,2064.320000
- 359664038911,8871,2120.319000,785.770000,550.550000,2064.320000
- 359664047782,1343,320.839600,540.280000,550.550000,2064.320000
- 359664049491,1383,514.276100,254.130000,47.000000,2064.320000
- 359664050874,2409912,898807.333300,49.440000,47.000000,2064.320000
- 359666460786,13754,3286.709200,49.300000,550.550000,2064.320000
- """))
-
- # Tests that total calculations are correct
- def test_wattson_total_raven_calc(self):
- return DiffTestBlueprint(
- trace=DataPath('wattson_dsu_pmu.pb'),
- query=("""
- INCLUDE PERFETTO MODULE wattson.curves.grouped;
- select * from _wattson_entire_trace
- """),
- out=Csv("""
- "total_l3","total_little_cpus","total_mid_cpus","total_big_cpus","total"
- 500.010000,662.000000,370.730000,1490.770000,3023.520000
- """))
-
- # Tests that total calculations are correct
- def test_wattson_total_eos_calc(self):
- return DiffTestBlueprint(
- trace=DataPath('wattson_eos_suspend.pb'),
- query=("""
- INCLUDE PERFETTO MODULE wattson.curves.grouped;
- select * from _wattson_entire_trace
- """),
- out=Csv("""
- "total_l3","total_little_cpus","total_mid_cpus","total_big_cpus","total"
- 0.000000,2602.930000,0.000000,0.000000,2602.930000
- """))
-
# Tests that total calculations are correct
def test_wattson_idle_attribution(self):
return DiffTestBlueprint(
@@ -393,3 +342,113 @@
2.434878,172,172
2.256320,414,414
"""))
+
+ # Tests that DSU devfreq calculations are merged correctly
+ def test_wattson_dsu_devfreq(self):
+ return DiffTestBlueprint(
+ trace=DataPath('wattson_tk4_pcmark.pb'),
+ query=("""
+ INCLUDE PERFETTO MODULE wattson.curves.w_dsu_dependence;
+ SELECT * FROM _cpu_curves
+ WHERE ts > 4108586775197
+ LIMIT 20
+ """),
+ out=Csv("""
+ "ts","dur","freq_0","idle_0","freq_1","idle_1","freq_2","idle_2","freq_3","idle_3","cpu4_curve","cpu5_curve","cpu6_curve","cpu7_curve","l3_hit_count","l3_miss_count","no_static","all_cpu_deep_idle"
+ 4108586789603,35685,1950000,0,1950000,-1,1950000,-1,1950000,-1,674.240000,674.240000,674.240000,3327.560000,14718,5837,-1,-1
+ 4108586825288,30843,1950000,-1,1950000,-1,1950000,-1,1950000,-1,674.240000,674.240000,674.240000,3327.560000,12721,5045,-1,-1
+ 4108586856131,13387,1950000,-1,1950000,-1,1950000,-1,1950000,-1,674.240000,674.240000,674.240000,99.470000,5521,2189,-1,-1
+ 4108586869518,22542,1950000,-1,1950000,-1,1950000,-1,1950000,-1,674.240000,674.240000,674.240000,3327.560000,9297,3687,-1,-1
+ 4108586892060,2482,1950000,-1,1950000,-1,1950000,-1,1950000,0,674.240000,674.240000,674.240000,3327.560000,1023,406,-1,-1
+ 4108586894542,68563,1950000,-1,1950000,-1,1950000,-1,1950000,-1,674.240000,674.240000,674.240000,3327.560000,28279,11216,-1,-1
+ 4108586963105,59652,1950000,-1,1950000,-1,1950000,-1,1950000,0,674.240000,674.240000,674.240000,3327.560000,24603,9758,-1,-1
+ 4108587022757,3743,1950000,0,1950000,-1,1950000,-1,1950000,0,674.240000,674.240000,674.240000,3327.560000,1543,612,-1,-1
+ 4108587026500,15992,1950000,-1,1950000,-1,1950000,-1,1950000,0,674.240000,674.240000,674.240000,3327.560000,6595,2616,-1,-1
+ 4108587042492,15625,1950000,-1,1950000,-1,1950000,-1,1950000,0,674.240000,674.240000,674.240000,99.470000,6444,2556,-1,-1
+ 4108587058117,8138,1950000,-1,1950000,-1,1950000,-1,1950000,0,674.240000,674.240000,674.240000,3327.560000,3356,1331,-1,-1
+ 4108587066255,80566,1950000,-1,1950000,-1,1950000,-1,1950000,-1,674.240000,674.240000,674.240000,3327.560000,33229,13179,-1,-1
+ 4108587146821,19572,1950000,-1,1950000,-1,1950000,-1,1950000,-1,674.240000,674.240000,674.240000,99.470000,8072,3201,-1,-1
+ 4108587166393,219116,1950000,-1,1950000,-1,1950000,-1,1950000,-1,674.240000,674.240000,674.240000,3327.560000,90375,35845,-1,-1
+ 4108587385509,81991,1950000,-1,1950000,0,1950000,-1,1950000,-1,674.240000,674.240000,674.240000,3327.560000,33817,13413,-1,-1
+ 4108587467500,90413,1950000,-1,1950000,0,1950000,0,1950000,-1,674.240000,674.240000,674.240000,3327.560000,37291,14790,-1,-1
+ 4108587557913,92896,1950000,0,1950000,0,1950000,0,1950000,-1,674.240000,674.240000,674.240000,3327.560000,38315,15196,-1,-1
+ 4108587650809,95296,1950000,-1,1950000,0,1950000,0,1950000,-1,674.240000,674.240000,674.240000,3327.560000,39305,15589,-1,-1
+ 4108587746105,12451,1950000,0,1950000,0,1950000,0,1950000,-1,674.240000,674.240000,674.240000,3327.560000,5135,2036,-1,-1
+ 4108587758556,28524,1950000,0,1950000,0,1950000,-1,1950000,-1,674.240000,674.240000,674.240000,3327.560000,11764,4666,-1,-1
+ """))
+
+ # Tests that DSU devfreq calculations are merged correctly
+ def test_wattson_dsu_devfreq_system_state(self):
+ return DiffTestBlueprint(
+ trace=DataPath('wattson_tk4_pcmark.pb'),
+ query=("""
+ INCLUDE PERFETTO MODULE wattson.curves.estimates;
+ SELECT * FROM _system_state_mw
+ WHERE ts > 4108586775197
+ LIMIT 20
+ """),
+ out=Csv("""
+ "ts","dur","cpu0_mw","cpu1_mw","cpu2_mw","cpu3_mw","cpu4_mw","cpu5_mw","cpu6_mw","cpu7_mw","dsu_scu_mw"
+ 4108586789603,35685,2.670000,205.600000,205.600000,205.600000,674.240000,674.240000,674.240000,3327.560000,1166.695271
+ 4108586825288,30843,205.600000,205.600000,205.600000,205.600000,674.240000,674.240000,674.240000,3327.560000,1166.698554
+ 4108586856131,13387,205.600000,205.600000,205.600000,205.600000,674.240000,674.240000,674.240000,99.470000,1166.545753
+ 4108586869518,22542,205.600000,205.600000,205.600000,205.600000,674.240000,674.240000,674.240000,3327.560000,1166.655587
+ 4108586892060,2482,205.600000,205.600000,205.600000,2.670000,674.240000,674.240000,674.240000,3327.560000,1166.164641
+ 4108586894542,68563,205.600000,205.600000,205.600000,205.600000,674.240000,674.240000,674.240000,3327.560000,1166.746124
+ 4108586963105,59652,205.600000,205.600000,205.600000,2.670000,674.240000,674.240000,674.240000,3327.560000,1166.716706
+ 4108587022757,3743,2.670000,205.600000,205.600000,2.670000,674.240000,674.240000,674.240000,3327.560000,1166.170321
+ 4108587026500,15992,205.600000,205.600000,205.600000,2.670000,674.240000,674.240000,674.240000,3327.560000,1166.620056
+ 4108587042492,15625,205.600000,205.600000,205.600000,2.670000,674.240000,674.240000,674.240000,99.470000,1166.668234
+ 4108587058117,8138,205.600000,205.600000,205.600000,2.670000,674.240000,674.240000,674.240000,3327.560000,1166.555033
+ 4108587066255,80566,205.600000,205.600000,205.600000,205.600000,674.240000,674.240000,674.240000,3327.560000,1166.717766
+ 4108587146821,19572,205.600000,205.600000,205.600000,205.600000,674.240000,674.240000,674.240000,99.470000,1166.626795
+ 4108587166393,219116,205.600000,205.600000,205.600000,205.600000,674.240000,674.240000,674.240000,3327.560000,1166.750356
+ 4108587385509,81991,205.600000,2.670000,205.600000,205.600000,674.240000,674.240000,674.240000,3327.560000,1166.743880
+ 4108587467500,90413,205.600000,2.670000,2.670000,205.600000,674.240000,674.240000,674.240000,3327.560000,1166.736713
+ 4108587557913,92896,2.670000,2.670000,2.670000,205.600000,674.240000,674.240000,674.240000,3327.560000,1166.730805
+ 4108587650809,95296,205.600000,2.670000,2.670000,205.600000,674.240000,674.240000,674.240000,3327.560000,1166.740927
+ 4108587746105,12451,2.670000,2.670000,2.670000,205.600000,674.240000,674.240000,674.240000,3327.560000,1166.556475
+ 4108587758556,28524,2.670000,2.670000,205.600000,205.600000,674.240000,674.240000,674.240000,3327.560000,1166.680924
+ """))
+
+ def test_wattson_time_window_api(self):
+ return DiffTestBlueprint(
+ trace=DataPath('wattson_dsu_pmu.pb'),
+ query="""
+ INCLUDE PERFETTO MODULE wattson.curves.estimates;
+
+ SELECT
+ cpu0_mw,
+ cpu1_mw,
+ cpu2_mw,
+ cpu3_mw,
+ cpu4_mw,
+ cpu5_mw,
+ cpu6_mw,
+ cpu7_mw,
+ dsu_scu_mw
+ FROM _windowed_system_state_mw(362426061658, 5067704349)
+ """,
+ out=Csv("""
+ "cpu0_mw","cpu1_mw","cpu2_mw","cpu3_mw","cpu4_mw","cpu5_mw","cpu6_mw","cpu7_mw","dsu_scu_mw"
+ 13.025673,6.270190,5.448549,8.796540,8.937174,10.717942,29.482823,30.239208,26.121213
+ """))
+
+ # Tests that suspend calculations are correct on 8 CPU device where suspend
+ # indication comes from "syscore" command
+ def test_wattson_syscore_suspend(self):
+ return DiffTestBlueprint(
+ trace=DataPath('wattson_syscore_suspend.pb'),
+ query=("""
+ INCLUDE PERFETTO MODULE wattson.curves.estimates;
+ SELECT ts, dur, cpu0_id, cpu1_id, cpu2_id, cpu3_id, suspended
+ FROM _stats_cpu0123_suspend
+ WHERE suspended
+ """),
+ out=Csv("""
+ "ts","dur","cpu0_id","cpu1_id","cpu2_id","cpu3_id","suspended"
+ 385019771468,61975407053,12041,12218,10488,8910,1
+ 448320364476,3674872885,13005,12954,11166,9272,1
+ 452415394221,69579176303,13654,13361,11651,9609,1
+ 564873995228,135118729231,45223,37594,22798,20132,1
+ """))
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/test/trace_processor/diff_tests/syntax/table_tests.py b/test/trace_processor/diff_tests/syntax/table_tests.py
index a90e71c..fb2cbc2 100644
--- a/test/trace_processor/diff_tests/syntax/table_tests.py
+++ b/test/trace_processor/diff_tests/syntax/table_tests.py
@@ -412,3 +412,159 @@
"MAX(id)"
20745
"""))
+
+ def test_winscope_proto_to_args_with_defaults_with_nested_fields(self):
+ return DiffTestBlueprint(
+ trace=Path('../parser/android/surfaceflinger_layers.textproto'),
+ query="""
+ SELECT flat_key, key, int_value, string_value, real_value FROM __intrinsic_winscope_proto_to_args_with_defaults('surfaceflinger_layer') AS sfl
+ ORDER BY sfl.base64_proto_id, key
+ LIMIT 95
+ """,
+ out=Csv("""
+ "flat_key","key","int_value","string_value","real_value"
+ "active_buffer","active_buffer","[NULL]","[NULL]","[NULL]"
+ "app_id","app_id",0,"[NULL]","[NULL]"
+ "background_blur_radius","background_blur_radius",0,"[NULL]","[NULL]"
+ "barrier_layer","barrier_layer","[NULL]","[NULL]","[NULL]"
+ "blur_regions","blur_regions","[NULL]","[NULL]","[NULL]"
+ "bounds.bottom","bounds.bottom","[NULL]","[NULL]",24000.000000
+ "bounds.left","bounds.left","[NULL]","[NULL]",-10800.000000
+ "bounds.right","bounds.right","[NULL]","[NULL]",10800.000000
+ "bounds.top","bounds.top","[NULL]","[NULL]",-24000.000000
+ "buffer_transform","buffer_transform","[NULL]","[NULL]","[NULL]"
+ "children","children[0]",4,"[NULL]","[NULL]"
+ "children","children[1]",35,"[NULL]","[NULL]"
+ "children","children[2]",43,"[NULL]","[NULL]"
+ "children","children[3]",45,"[NULL]","[NULL]"
+ "children","children[4]",44,"[NULL]","[NULL]"
+ "children","children[5]",77,"[NULL]","[NULL]"
+ "children","children[6]",87,"[NULL]","[NULL]"
+ "color.a","color.a","[NULL]","[NULL]",1.000000
+ "color.b","color.b","[NULL]","[NULL]",-1.000000
+ "color.g","color.g","[NULL]","[NULL]",-1.000000
+ "color.r","color.r","[NULL]","[NULL]",-1.000000
+ "color_transform","color_transform","[NULL]","[NULL]","[NULL]"
+ "corner_radius","corner_radius","[NULL]","[NULL]",0.000000
+ "corner_radius_crop","corner_radius_crop","[NULL]","[NULL]","[NULL]"
+ "crop.bottom","crop.bottom",-1,"[NULL]","[NULL]"
+ "crop.left","crop.left",0,"[NULL]","[NULL]"
+ "crop.right","crop.right",-1,"[NULL]","[NULL]"
+ "crop.top","crop.top",0,"[NULL]","[NULL]"
+ "curr_frame","curr_frame",0,"[NULL]","[NULL]"
+ "damage_region","damage_region","[NULL]","[NULL]","[NULL]"
+ "dataspace","dataspace","[NULL]","BT709 sRGB Full range","[NULL]"
+ "destination_frame.bottom","destination_frame.bottom",-1,"[NULL]","[NULL]"
+ "destination_frame.left","destination_frame.left",0,"[NULL]","[NULL]"
+ "destination_frame.right","destination_frame.right",-1,"[NULL]","[NULL]"
+ "destination_frame.top","destination_frame.top",0,"[NULL]","[NULL]"
+ "effective_scaling_mode","effective_scaling_mode",0,"[NULL]","[NULL]"
+ "effective_transform","effective_transform","[NULL]","[NULL]","[NULL]"
+ "final_crop","final_crop","[NULL]","[NULL]","[NULL]"
+ "flags","flags",2,"[NULL]","[NULL]"
+ "hwc_composition_type","hwc_composition_type","[NULL]","HWC_TYPE_UNSPECIFIED","[NULL]"
+ "hwc_crop","hwc_crop","[NULL]","[NULL]","[NULL]"
+ "hwc_frame","hwc_frame","[NULL]","[NULL]","[NULL]"
+ "hwc_transform","hwc_transform",0,"[NULL]","[NULL]"
+ "id","id",3,"[NULL]","[NULL]"
+ "input_window_info","input_window_info","[NULL]","[NULL]","[NULL]"
+ "invalidate","invalidate",1,"[NULL]","[NULL]"
+ "is_opaque","is_opaque",0,"[NULL]","[NULL]"
+ "is_protected","is_protected",0,"[NULL]","[NULL]"
+ "is_relative_of","is_relative_of",0,"[NULL]","[NULL]"
+ "is_trusted_overlay","is_trusted_overlay",0,"[NULL]","[NULL]"
+ "layer_stack","layer_stack",0,"[NULL]","[NULL]"
+ "metadata","metadata","[NULL]","[NULL]","[NULL]"
+ "name","name","[NULL]","Display 0 name=\"Built-in Screen\"#3","[NULL]"
+ "original_id","original_id",0,"[NULL]","[NULL]"
+ "owner_uid","owner_uid",1000,"[NULL]","[NULL]"
+ "parent","parent",0,"[NULL]","[NULL]"
+ "pixel_format","pixel_format","[NULL]","Unknown/None","[NULL]"
+ "position","position","[NULL]","[NULL]","[NULL]"
+ "queued_frames","queued_frames",0,"[NULL]","[NULL]"
+ "refresh_pending","refresh_pending",0,"[NULL]","[NULL]"
+ "relatives","relatives","[NULL]","[NULL]","[NULL]"
+ "requested_color.a","requested_color.a","[NULL]","[NULL]",1.000000
+ "requested_color.b","requested_color.b","[NULL]","[NULL]",-1.000000
+ "requested_color.g","requested_color.g","[NULL]","[NULL]",-1.000000
+ "requested_color.r","requested_color.r","[NULL]","[NULL]",-1.000000
+ "requested_corner_radius","requested_corner_radius","[NULL]","[NULL]",0.000000
+ "requested_position","requested_position","[NULL]","[NULL]","[NULL]"
+ "requested_transform.dsdx","requested_transform.dsdx","[NULL]","[NULL]",0.000000
+ "requested_transform.dsdy","requested_transform.dsdy","[NULL]","[NULL]",0.000000
+ "requested_transform.dtdx","requested_transform.dtdx","[NULL]","[NULL]",0.000000
+ "requested_transform.dtdy","requested_transform.dtdy","[NULL]","[NULL]",0.000000
+ "requested_transform.type","requested_transform.type",0,"[NULL]","[NULL]"
+ "screen_bounds.bottom","screen_bounds.bottom","[NULL]","[NULL]",24000.000000
+ "screen_bounds.left","screen_bounds.left","[NULL]","[NULL]",-10800.000000
+ "screen_bounds.right","screen_bounds.right","[NULL]","[NULL]",10800.000000
+ "screen_bounds.top","screen_bounds.top","[NULL]","[NULL]",-24000.000000
+ "shadow_radius","shadow_radius","[NULL]","[NULL]",0.000000
+ "size","size","[NULL]","[NULL]","[NULL]"
+ "source_bounds.bottom","source_bounds.bottom","[NULL]","[NULL]",24000.000000
+ "source_bounds.left","source_bounds.left","[NULL]","[NULL]",-10800.000000
+ "source_bounds.right","source_bounds.right","[NULL]","[NULL]",10800.000000
+ "source_bounds.top","source_bounds.top","[NULL]","[NULL]",-24000.000000
+ "transform.dsdx","transform.dsdx","[NULL]","[NULL]",0.000000
+ "transform.dsdy","transform.dsdy","[NULL]","[NULL]",0.000000
+ "transform.dtdx","transform.dtdx","[NULL]","[NULL]",0.000000
+ "transform.dtdy","transform.dtdy","[NULL]","[NULL]",0.000000
+ "transform.type","transform.type",0,"[NULL]","[NULL]"
+ "transparent_region","transparent_region","[NULL]","[NULL]","[NULL]"
+ "trusted_overlay","trusted_overlay","[NULL]","UNSET","[NULL]"
+ "type","type","[NULL]","[NULL]","[NULL]"
+ "visible_region","visible_region","[NULL]","[NULL]","[NULL]"
+ "window_type","window_type",0,"[NULL]","[NULL]"
+ "z","z",0,"[NULL]","[NULL]"
+ "z_order_relative_of","z_order_relative_of",0,"[NULL]","[NULL]"
+ "active_buffer","active_buffer","[NULL]","[NULL]","[NULL]"
+ """))
+
+ def test_winscope_proto_to_args_with_defaults_with_repeated_fields(self):
+ return DiffTestBlueprint(
+ trace=Path('../parser/android/surfaceflinger_layers.textproto'),
+ query="""
+ SELECT flat_key, key, int_value, string_value, real_value FROM __intrinsic_winscope_proto_to_args_with_defaults('surfaceflinger_layers_snapshot') AS sfs
+ WHERE key != "hwc_blob"
+ ORDER BY sfs.base64_proto_id DESC, key ASC
+ LIMIT 36
+ """,
+ out=Csv("""
+ "flat_key","key","int_value","string_value","real_value"
+ "displays.dpi_x","displays[0].dpi_x","[NULL]","[NULL]",0.000000
+ "displays.dpi_y","displays[0].dpi_y","[NULL]","[NULL]",0.000000
+ "displays.id","displays[0].id",4619827677550801152,"[NULL]","[NULL]"
+ "displays.is_virtual","displays[0].is_virtual",0,"[NULL]","[NULL]"
+ "displays.layer_stack","displays[0].layer_stack",0,"[NULL]","[NULL]"
+ "displays.layer_stack_space_rect.bottom","displays[0].layer_stack_space_rect.bottom",2400,"[NULL]","[NULL]"
+ "displays.layer_stack_space_rect.left","displays[0].layer_stack_space_rect.left",0,"[NULL]","[NULL]"
+ "displays.layer_stack_space_rect.right","displays[0].layer_stack_space_rect.right",1080,"[NULL]","[NULL]"
+ "displays.layer_stack_space_rect.top","displays[0].layer_stack_space_rect.top",0,"[NULL]","[NULL]"
+ "displays.name","displays[0].name","[NULL]","Common Panel","[NULL]"
+ "displays.size.h","displays[0].size.h",2400,"[NULL]","[NULL]"
+ "displays.size.w","displays[0].size.w",1080,"[NULL]","[NULL]"
+ "displays.transform.dsdx","displays[0].transform.dsdx","[NULL]","[NULL]",0.000000
+ "displays.transform.dsdy","displays[0].transform.dsdy","[NULL]","[NULL]",0.000000
+ "displays.transform.dtdx","displays[0].transform.dtdx","[NULL]","[NULL]",0.000000
+ "displays.transform.dtdy","displays[0].transform.dtdy","[NULL]","[NULL]",0.000000
+ "displays.transform.type","displays[0].transform.type",0,"[NULL]","[NULL]"
+ "displays.dpi_x","displays[1].dpi_x","[NULL]","[NULL]",0.000000
+ "displays.dpi_y","displays[1].dpi_y","[NULL]","[NULL]",0.000000
+ "displays.id","displays[1].id",4619827677550801153,"[NULL]","[NULL]"
+ "displays.is_virtual","displays[1].is_virtual",0,"[NULL]","[NULL]"
+ "displays.layer_stack","displays[1].layer_stack",0,"[NULL]","[NULL]"
+ "displays.layer_stack_space_rect.bottom","displays[1].layer_stack_space_rect.bottom",2400,"[NULL]","[NULL]"
+ "displays.layer_stack_space_rect.left","displays[1].layer_stack_space_rect.left",0,"[NULL]","[NULL]"
+ "displays.layer_stack_space_rect.right","displays[1].layer_stack_space_rect.right",1080,"[NULL]","[NULL]"
+ "displays.layer_stack_space_rect.top","displays[1].layer_stack_space_rect.top",0,"[NULL]","[NULL]"
+ "displays.name","displays[1].name","[NULL]","Common Panel","[NULL]"
+ "displays.size.h","displays[1].size.h",2400,"[NULL]","[NULL]"
+ "displays.size.w","displays[1].size.w",1080,"[NULL]","[NULL]"
+ "displays.transform","displays[1].transform","[NULL]","[NULL]","[NULL]"
+ "elapsed_realtime_nanos","elapsed_realtime_nanos",2749500341063,"[NULL]","[NULL]"
+ "excludes_composition_state","excludes_composition_state",0,"[NULL]","[NULL]"
+ "missed_entries","missed_entries",0,"[NULL]","[NULL]"
+ "vsync_id","vsync_id",24767,"[NULL]","[NULL]"
+ "where","where","[NULL]","bufferLatched","[NULL]"
+ "displays.dpi_x","displays[0].dpi_x","[NULL]","[NULL]",0.000000
+ """))
diff --git a/test/vts/Android.bp b/test/vts/Android.bp
index f65011e..8a726e5 100644
--- a/test/vts/Android.bp
+++ b/test/vts/Android.bp
@@ -1,4 +1,5 @@
package {
+ default_team: "trendy_team_perfetto",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "external_perfetto_license"
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/cpu_profile b/tools/cpu_profile
index 0f09f49..5339b16 100755
--- a/tools/cpu_profile
+++ b/tools/cpu_profile
@@ -37,18 +37,18 @@
# ----- Amalgamator: begin of python/perfetto/prebuilts/manifests/traceconv.py
-# This file has been generated by: tools/roll-prebuilts v47.0
+# This file has been generated by: tools/roll-prebuilts v48.1
TRACECONV_MANIFEST = [{
'arch':
'mac-amd64',
'file_name':
'traceconv',
'file_size':
- 9481408,
+ 9041560,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/mac-amd64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/mac-amd64/traceconv',
'sha256':
- 'b6819bb922438d585e816646c4d60e43bfa823d5f3f499bd8efcaccd26a9009c',
+ 'cec2da5cb771a4812d0b2d15604d5023954d28e0af12e87313da2ab70d26b970',
'platform':
'darwin',
'machine': ['x86_64']
@@ -58,11 +58,11 @@
'file_name':
'traceconv',
'file_size':
- 8852520,
+ 8375512,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/mac-arm64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/mac-arm64/traceconv',
'sha256':
- '9e9eac795578f6ed76128127d8a81c1ce5620b3d47f2c5e23c1f7f2ebbff9bea',
+ '64e200a58ea9c9f366e1071dd274d0023d1fd14043f75dbba3fe0cc138ff5fc7',
'platform':
'darwin',
'machine': ['arm64']
@@ -72,11 +72,11 @@
'file_name':
'traceconv',
'file_size':
- 9538432,
+ 9134136,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/linux-amd64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/linux-amd64/traceconv',
'sha256':
- '01881a82050f36b8db427c741ce236360bb86548e6a6c9445b2477c6150de05b',
+ '87b87e1778367c1e3b99fc77439a28b4911125d2751f9909fd1b51f6bd60b6f4',
'platform':
'linux',
'machine': ['x86_64']
@@ -86,11 +86,11 @@
'file_name':
'traceconv',
'file_size':
- 7286504,
+ 6753020,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/linux-arm/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/linux-arm/traceconv',
'sha256':
- 'bc700f945c78c4a65a60bdb499c6c59e671c5173f45f42976fd0661396b72c16',
+ '804c4e13aca5798731056952d9cb0c6ee58795c03477c69514ccd39703060812',
'platform':
'linux',
'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -100,11 +100,11 @@
'file_name':
'traceconv',
'file_size':
- 9168408,
+ 8740064,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/linux-arm64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/linux-arm64/traceconv',
'sha256':
- 'd0192785a56c5088811a175ab083bb599aab5fd594a3248057380038f0b2b5c2',
+ '0d781886531d11e1d573a1ec5e06376ef139bb479eec38c16c8735821c35b895',
'platform':
'linux',
'machine': ['aarch64']
@@ -114,55 +114,55 @@
'file_name':
'traceconv',
'file_size':
- 7322024,
+ 6792280,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/android-arm/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-arm/traceconv',
'sha256':
- '77583ed2eefe75796a3fe2a7149d6e234c643d4f83690f732367442bbb259812'
+ '7d91e4133184a3722a25488edd3692c5a195148eba56621014311d3f85d3fc15'
}, {
'arch':
'android-arm64',
'file_name':
'traceconv',
'file_size':
- 9123224,
+ 8677992,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/android-arm64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-arm64/traceconv',
'sha256':
- 'cd93333b3d56f41949066688308a23fdd3fbbf367b03aceff711d8102e696702'
+ 'c03c4a901ed23f1e20a12c98ce4556353a62bddcd260fb4d797cd29ff6c49a05'
}, {
'arch':
'android-x86',
'file_name':
'traceconv',
'file_size':
- 9868752,
+ 9503704,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/android-x86/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-x86/traceconv',
'sha256':
- '9b2921ba776ad99bf2ef0d28ff6919dea5ed726a3c61a81159b9d99b39dd7b6d'
+ '704e58a7249de56aadec64d4c0d83bab0821d2c4fd77114a9b71705ff4224539'
}, {
'arch':
'android-x64',
'file_name':
'traceconv',
'file_size':
- 9382824,
+ 8964488,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/android-x64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-x64/traceconv',
'sha256':
- 'c1fdbcb3c2cd15838468c3cc0ed8131a073f220c133384139aa57c4c05b2d34b'
+ 'e4f07836fc2a5fb7cd997a9acc4183af7a06997d1e73aac71021af5114b921bc'
}, {
'arch':
'windows-amd64',
'file_name':
'traceconv.exe',
'file_size':
- 9209856,
+ 8763904,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/windows-amd64/traceconv.exe',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/windows-amd64/traceconv.exe',
'sha256':
- 'ddef23109550784b6069b57bbac1ee627a1ab09086fdd72950965e866cfba536',
+ '084670ac28ed59a9642782a30e051735c1b7474b8cd569b9bc94c305af68290e',
'platform':
'win32',
'machine': ['amd64']
diff --git a/tools/diff_test_trace_processor.py b/tools/diff_test_trace_processor.py
index a4d3261..181039f 100755
--- a/tools/diff_test_trace_processor.py
+++ b/tools/diff_test_trace_processor.py
@@ -60,6 +60,8 @@
action='store_true',
help='Update the expected output file with the actual result')
parser.add_argument(
+ '--quiet', action='store_true', help='Only print if the test failed.')
+ parser.add_argument(
'--no-colors', action='store_true', help='Print without coloring')
parser.add_argument(
'trace_processor', type=str, help='location of trace processor binary')
@@ -80,7 +82,8 @@
test_runner = DiffTestsRunner(args.name_filter, args.trace_processor,
args.trace_descriptor, args.no_colors,
- args.override_sql_module, args.test_dir)
+ args.override_sql_module, args.test_dir,
+ args.quiet)
sys.stderr.write(f"[==========] Running {len(test_runner.tests)} tests.\n")
results = test_runner.run_all_tests(args.metrics_descriptor,
diff --git a/tools/gen_android_bp b/tools/gen_android_bp
index 8041e29..f5386dc 100755
--- a/tools/gen_android_bp
+++ b/tools/gen_android_bp
@@ -286,7 +286,6 @@
}),
('min_sdk_version', '30'),
('shared_libs', {'liblog'}),
- ('export_shared_lib_headers', {'liblog'}),
('export_include_dirs', {'include', buildflags_dir}),
],
'libperfetto_c': [
@@ -542,7 +541,6 @@
self.main: Optional[str] = None
self.comment = 'GN: ' + gn_utils.label_without_toolchain(gn_target)
self.shared_libs = set()
- self.export_shared_lib_headers = set()
self.static_libs = set()
self.whole_static_libs = set()
self.runtime_libs = set()
@@ -592,7 +590,6 @@
self._output_field(output, 'name')
self._output_field(output, 'srcs')
self._output_field(output, 'shared_libs')
- self._output_field(output, 'export_shared_lib_headers')
self._output_field(output, 'static_libs')
self._output_field(output, 'whole_static_libs')
self._output_field(output, 'runtime_libs')
diff --git a/tools/gen_bazel b/tools/gen_bazel
index 1b619f5..a5776ba 100755
--- a/tools/gen_bazel
+++ b/tools/gen_bazel
@@ -78,12 +78,16 @@
public_targets = [
'//:libperfetto_client_experimental',
'//src/perfetto_cmd:perfetto',
- '//src/shared_lib:libperfetto_c',
'//src/traced/probes:traced_probes',
'//src/traced/service:traced',
'//src/trace_processor:trace_processor_shell',
'//src/trace_processor:trace_processor',
'//src/traceconv:traceconv',
+]
+
+# These targets will be exported with visibility only to our allowlist.
+allowlist_public_targets = [
+ '//src/shared_lib:libperfetto_c',
'//src/traceconv:libpprofbuilder',
]
@@ -100,7 +104,7 @@
'//src/tools/proto_merger:proto_merger',
'//src/trace_processor/rpc:trace_processor_rpc',
'//test:client_api_example',
-] + public_targets
+] + public_targets + allowlist_public_targets
# Proto target groups which will be made public.
proto_groups = {
@@ -713,8 +717,14 @@
else:
label.srcs = raw_srcs
- if gn_target.name in public_targets:
+ is_public = gn_target.name in public_targets
+ is_public_for_allowlist = gn_target.name in allowlist_public_targets
+ if is_public and is_public_for_allowlist:
+ raise Error('Target %s in both public_targets and allowlist_public_targets', gn.target.name)
+ elif is_public:
label.visibility = ['//visibility:public']
+ elif is_public_for_allowlist:
+ label.visibility = ALLOWLIST_PUBLIC_VISIBILITY
if win_target:
label.win_srcs = list(set(label.srcs) & {s[2:] for s in win_target.sources | win_target.inputs})
diff --git a/tools/gen_stdlib_docs_json.py b/tools/gen_stdlib_docs_json.py
index 8ae6044..5edfd8f 100755
--- a/tools/gen_stdlib_docs_json.py
+++ b/tools/gen_stdlib_docs_json.py
@@ -26,10 +26,15 @@
from python.generators.sql_processing.docs_parse import parse_file
+def _summary_desc(s: str) -> str:
+ return s.split('. ')[0].replace('\n', ' ')
+
+
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--json-out', required=True)
parser.add_argument('--input-list-file')
+ parser.add_argument('--minify')
parser.add_argument('sql_files', nargs='*')
args = parser.parse_args()
@@ -64,11 +69,11 @@
sql_outputs[relpath] = f.read()
- modules = defaultdict(list)
+ packages = defaultdict(list)
# Add documentation from each file
for path, sql in sql_outputs.items():
- module_name = path.split("/")[0]
- import_key = path.split(".sql")[0].replace("/", ".")
+ package_name = path.split("/")[0]
+ module_name = path.split(".sql")[0].replace("/", ".")
docs = parse_file(path, sql)
@@ -81,69 +86,81 @@
print(e)
return 1
- file_dict = {
- 'import_key':
- import_key,
- 'imports': [{
- 'name': table.name,
- 'desc': table.desc,
- 'summary_desc': table.desc.split('\n\n')[0].replace('\n', ' '),
- 'type': table.type,
- 'cols': {
- col_name: {
- 'type': col.type,
- 'desc': col.description,
- } for (col_name, col) in table.cols.items()
- },
+ module_dict = {
+ 'module_name':
+ module_name,
+ 'data_objects': [{
+ 'name':
+ table.name,
+ 'desc':
+ table.desc,
+ 'summary_desc':
+ _summary_desc(table.desc),
+ 'type':
+ table.type,
+ 'cols': [{
+ 'name': col_name,
+ 'type': col.type,
+ 'desc': col.description
+ } for (col_name, col) in table.cols.items()]
} for table in docs.table_views],
'functions': [{
'name': function.name,
'desc': function.desc,
- 'summary_desc': function.desc.split('\n\n')[0].replace('\n', ' '),
- 'args': {
- arg_name: {
- 'type': arg.type,
- 'desc': arg.description,
- } for (arg_name, arg) in function.args.items()
- },
+ 'summary_desc': _summary_desc(function.desc),
+ 'args': [{
+ 'name': arg_name,
+ 'type': arg.type,
+ 'desc': arg.description,
+ } for (arg_name, arg) in function.args.items()],
'return_type': function.return_type,
'return_desc': function.return_desc,
} for function in docs.functions],
'table_functions': [{
- 'name': function.name,
- 'desc': function.desc,
- 'summary_desc': function.desc.split('\n\n')[0].replace('\n', ' '),
- 'args': {
- arg_name: {
- 'type': arg.type,
- 'desc': arg.description,
- } for (arg_name, arg) in function.args.items()
- },
- 'cols': {
- col_name: {
- 'type': col.type,
- 'desc': col.description,
- } for (col_name, col) in function.cols.items()
- },
+ 'name':
+ function.name,
+ 'desc':
+ function.desc,
+ 'summary_desc':
+ _summary_desc(function.desc),
+ 'args': [{
+ 'name': arg_name,
+ 'type': arg.type,
+ 'desc': arg.description,
+ } for (arg_name, arg) in function.args.items()],
+ 'cols': [{
+ 'name': col_name,
+ 'type': col.type,
+ 'desc': col.description
+ } for (col_name, col) in function.cols.items()]
} for function in docs.table_functions],
'macros': [{
- 'name': macro.name,
- 'desc': macro.desc,
- 'summary_desc': macro.desc.split('\n\n')[0].replace('\n', ' '),
- 'return_desc': macro.return_desc,
- 'return_type': macro.return_type,
- 'args': {
- arg_name: {
- 'type': arg.type,
- 'desc': arg.description,
- } for (arg_name, arg) in macro.args.items()
- },
+ 'name':
+ macro.name,
+ 'desc':
+ macro.desc,
+ 'summary_desc':
+ _summary_desc(macro.desc),
+ 'return_desc':
+ macro.return_desc,
+ 'return_type':
+ macro.return_type,
+ 'args': [{
+ 'name': arg_name,
+ 'type': arg.type,
+ 'desc': arg.description,
+ } for (arg_name, arg) in macro.args.items()],
} for macro in docs.macros],
}
- modules[module_name].append(file_dict)
+ packages[package_name].append(module_dict)
+
+ packages_list = [{
+ "name": name,
+ "modules": modules
+ } for name, modules in packages.items()]
with open(args.json_out, 'w+') as f:
- json.dump(modules, f, indent=4)
+ json.dump(packages_list, f, indent=None if args.minify else 4)
return 0
diff --git a/tools/gen_tp_table_docs.py b/tools/gen_tp_table_docs.py
index 04339bf..c69ab58 100755
--- a/tools/gen_tp_table_docs.py
+++ b/tools/gen_tp_table_docs.py
@@ -110,6 +110,13 @@
table_docs = []
for parsed in util.parse_tables_from_modules(modules):
table = parsed.table
+
+ # If there is no non-intrinsic alias for the table, don't
+ # include the table in the docs.
+ name = util.public_sql_name(table)
+ if name.startswith('__intrinsic_') or name.startswith('experimental_'):
+ continue
+
doc = table.tabledoc
assert doc
cols = (
@@ -117,7 +124,7 @@
for c in parsed.columns
if not c.is_ancestor)
table_docs.append({
- 'name': util.public_sql_name(table),
+ 'name': name,
'cppClassName': table.class_name,
'defMacro': table.class_name,
'comment': '\n'.join(l.strip() for l in doc.doc.splitlines()),
diff --git a/tools/gen_ui_imports b/tools/gen_ui_imports
index 36b3825..4b3947f 100755
--- a/tools/gen_ui_imports
+++ b/tools/gen_ui_imports
@@ -23,13 +23,13 @@
This generates code like:
-import {pluginRegistry} from '../common/plugins';
-
import {plugin as fooPlugin} from '../plugins/foo_plugin';
import {plugin as barPlugin} from '../plugins/bar_plugin';
-pluginRegistry.register(fooPlugin);
-pluginRegistry.register(barPlugin);
+export default [
+ fooPlugin,
+ barPlugin,
+];
"""
from __future__ import print_function
@@ -49,9 +49,15 @@
return first + ''.join(x.title() for x in rest)
+def is_plugin_dir(dir):
+ # Ensure plugins contain a file called index.ts. This avoids the issue empty
+ # dirs are detected as plugins.
+ return os.path.isdir(dir) and os.path.exists(os.path.join(dir, 'index.ts'))
+
+
def gen_imports(input_dir, output_path):
paths = [os.path.join(input_dir, p) for p in os.listdir(input_dir)]
- paths = [p for p in paths if os.path.isdir(p)]
+ paths = [p for p in paths if is_plugin_dir(p)]
paths.sort()
output_dir = os.path.dirname(output_path)
@@ -60,17 +66,20 @@
imports = []
registrations = []
for path in paths:
+ # Get out if
rel_path = os.path.relpath(path, output_dir)
snake_name = os.path.basename(path)
camel_name = to_camel_case(snake_name)
- imports.append(f"import {{plugin as {camel_name}}} from '{rel_path}';")
- registrations.append(f"pluginRegistry.register({camel_name});")
+ imports.append(f"import {camel_name} from '{rel_path}';")
+ registrations.append(camel_name)
- header = f"import {{pluginRegistry}} from '{rel_plugins_path}';"
import_text = '\n'.join(imports)
- registration_text = '\n'.join(registrations)
+ registration_text = 'export default [\n'
+ for camel_name in registrations:
+ registration_text += f" {camel_name},\n"
+ registration_text += '];\n'
- expected = f"{header}\n\n{import_text}\n\n{registration_text}\n"
+ expected = f"{import_text}\n\n{registration_text}"
with open(output_path, 'w') as f:
f.write(expected)
diff --git a/tools/gn_utils.py b/tools/gn_utils.py
index 9e32c91..eb1a5c7 100644
--- a/tools/gn_utils.py
+++ b/tools/gn_utils.py
@@ -444,7 +444,8 @@
target.proto_plugin = proto_target_type
target.proto_paths.update(self.get_proto_paths(proto_desc))
target.proto_exports.update(self.get_proto_exports(proto_desc))
- target.sources.update(proto_desc.get('sources', []))
+ target.sources.update(
+ self.get_proto_sources(proto_target_type, proto_desc))
assert (all(x.endswith('.proto') for x in target.sources))
elif target.type == 'source_set':
self.source_sets[gn_target_name] = target
@@ -536,6 +537,12 @@
metadata = proto_desc.get('metadata', {})
return metadata.get('proto_import_dirs', [])
+ def get_proto_sources(self, proto_target_type, proto_desc):
+ if proto_target_type == 'source_set':
+ metadata = proto_desc.get('metadata', {})
+ return metadata.get('proto_library_sources', [])
+ return proto_desc.get('sources', [])
+
def get_proto_target_type(
self, target: Target) -> Tuple[Optional[str], Optional[Dict]]:
""" Checks if the target is a proto library and return the plugin.
diff --git a/tools/heap_profile b/tools/heap_profile
index e94d216..df14a90 100755
--- a/tools/heap_profile
+++ b/tools/heap_profile
@@ -34,18 +34,18 @@
# ----- Amalgamator: begin of python/perfetto/prebuilts/manifests/traceconv.py
-# This file has been generated by: tools/roll-prebuilts v47.0
+# This file has been generated by: tools/roll-prebuilts v48.1
TRACECONV_MANIFEST = [{
'arch':
'mac-amd64',
'file_name':
'traceconv',
'file_size':
- 9481408,
+ 9041560,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/mac-amd64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/mac-amd64/traceconv',
'sha256':
- 'b6819bb922438d585e816646c4d60e43bfa823d5f3f499bd8efcaccd26a9009c',
+ 'cec2da5cb771a4812d0b2d15604d5023954d28e0af12e87313da2ab70d26b970',
'platform':
'darwin',
'machine': ['x86_64']
@@ -55,11 +55,11 @@
'file_name':
'traceconv',
'file_size':
- 8852520,
+ 8375512,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/mac-arm64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/mac-arm64/traceconv',
'sha256':
- '9e9eac795578f6ed76128127d8a81c1ce5620b3d47f2c5e23c1f7f2ebbff9bea',
+ '64e200a58ea9c9f366e1071dd274d0023d1fd14043f75dbba3fe0cc138ff5fc7',
'platform':
'darwin',
'machine': ['arm64']
@@ -69,11 +69,11 @@
'file_name':
'traceconv',
'file_size':
- 9538432,
+ 9134136,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/linux-amd64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/linux-amd64/traceconv',
'sha256':
- '01881a82050f36b8db427c741ce236360bb86548e6a6c9445b2477c6150de05b',
+ '87b87e1778367c1e3b99fc77439a28b4911125d2751f9909fd1b51f6bd60b6f4',
'platform':
'linux',
'machine': ['x86_64']
@@ -83,11 +83,11 @@
'file_name':
'traceconv',
'file_size':
- 7286504,
+ 6753020,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/linux-arm/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/linux-arm/traceconv',
'sha256':
- 'bc700f945c78c4a65a60bdb499c6c59e671c5173f45f42976fd0661396b72c16',
+ '804c4e13aca5798731056952d9cb0c6ee58795c03477c69514ccd39703060812',
'platform':
'linux',
'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -97,11 +97,11 @@
'file_name':
'traceconv',
'file_size':
- 9168408,
+ 8740064,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/linux-arm64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/linux-arm64/traceconv',
'sha256':
- 'd0192785a56c5088811a175ab083bb599aab5fd594a3248057380038f0b2b5c2',
+ '0d781886531d11e1d573a1ec5e06376ef139bb479eec38c16c8735821c35b895',
'platform':
'linux',
'machine': ['aarch64']
@@ -111,55 +111,55 @@
'file_name':
'traceconv',
'file_size':
- 7322024,
+ 6792280,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/android-arm/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-arm/traceconv',
'sha256':
- '77583ed2eefe75796a3fe2a7149d6e234c643d4f83690f732367442bbb259812'
+ '7d91e4133184a3722a25488edd3692c5a195148eba56621014311d3f85d3fc15'
}, {
'arch':
'android-arm64',
'file_name':
'traceconv',
'file_size':
- 9123224,
+ 8677992,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/android-arm64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-arm64/traceconv',
'sha256':
- 'cd93333b3d56f41949066688308a23fdd3fbbf367b03aceff711d8102e696702'
+ 'c03c4a901ed23f1e20a12c98ce4556353a62bddcd260fb4d797cd29ff6c49a05'
}, {
'arch':
'android-x86',
'file_name':
'traceconv',
'file_size':
- 9868752,
+ 9503704,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/android-x86/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-x86/traceconv',
'sha256':
- '9b2921ba776ad99bf2ef0d28ff6919dea5ed726a3c61a81159b9d99b39dd7b6d'
+ '704e58a7249de56aadec64d4c0d83bab0821d2c4fd77114a9b71705ff4224539'
}, {
'arch':
'android-x64',
'file_name':
'traceconv',
'file_size':
- 9382824,
+ 8964488,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/android-x64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-x64/traceconv',
'sha256':
- 'c1fdbcb3c2cd15838468c3cc0ed8131a073f220c133384139aa57c4c05b2d34b'
+ 'e4f07836fc2a5fb7cd997a9acc4183af7a06997d1e73aac71021af5114b921bc'
}, {
'arch':
'windows-amd64',
'file_name':
'traceconv.exe',
'file_size':
- 9209856,
+ 8763904,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/windows-amd64/traceconv.exe',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/windows-amd64/traceconv.exe',
'sha256':
- 'ddef23109550784b6069b57bbac1ee627a1ab09086fdd72950965e866cfba536',
+ '084670ac28ed59a9642782a30e051735c1b7474b8cd569b9bc94c305af68290e',
'platform':
'win32',
'machine': ['amd64']
diff --git a/tools/java_heap_dump b/tools/java_heap_dump
index 36e6428..379a771 100755
--- a/tools/java_heap_dump
+++ b/tools/java_heap_dump
@@ -237,7 +237,7 @@
"--buffer-size",
help="Buffer size in memory that store the whole java heap graph. N(kb|mb|gb)",
type=str,
- default="100024kb")
+ default="256mb")
parser.add_argument(
"-c",
"--continuous-dump",
diff --git a/tools/measure_tp_performance.py b/tools/measure_tp_performance.py
index 8014c39..4e51d6a 100755
--- a/tools/measure_tp_performance.py
+++ b/tools/measure_tp_performance.py
@@ -32,6 +32,8 @@
tp_args = [os.path.join(args.out, 'trace_processor_shell'), args.trace_file]
if not args.ftrace_raw:
tp_args.append('--no-ftrace-raw')
+ tp_args.append('--dev')
+ tp_args.append('--dev-flag drop-after-sort=true')
tp = subprocess.Popen(
tp_args,
stdin=subprocess.PIPE,
@@ -123,7 +125,6 @@
def only_sort_run(args):
env = {
'TRACE_PROCESSOR_NO_MMAP': '1',
- 'TRACE_PROCESSOR_SORT_ONLY': '1',
}
(tp, fail, time) = run_tp_until_ingestion(args, env)
diff --git a/tools/record_android_trace b/tools/record_android_trace
index 8a1ed29..203b636 100755
--- a/tools/record_android_trace
+++ b/tools/record_android_trace
@@ -34,18 +34,18 @@
# ----- Amalgamator: begin of python/perfetto/prebuilts/manifests/tracebox.py
-# This file has been generated by: tools/roll-prebuilts v47.0
+# This file has been generated by: tools/roll-prebuilts v48.1
TRACEBOX_MANIFEST = [{
'arch':
'mac-amd64',
'file_name':
'tracebox',
'file_size':
- 1597456,
+ 1613864,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/mac-amd64/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/mac-amd64/tracebox',
'sha256':
- '1e4b56533ad59e8131473ae6d4204356288a7b7a92241e303ab9865842d36c1d',
+ 'dfb1a3affe905d2e7d1f82bc4dda46b1fda6db054d60ae87c3215dd529b77fee',
'platform':
'darwin',
'machine': ['x86_64']
@@ -55,11 +55,11 @@
'file_name':
'tracebox',
'file_size':
- 1475640,
+ 1492184,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/mac-arm64/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/mac-arm64/tracebox',
'sha256':
- '8eae02034fa45581bd7262d1e3095616cc4f9a06a1bc0345cb5cae1277d8b4e4',
+ '4a492a629dd1f13f3146c4b8267c0b163afba8cef1d49e0c00c48bb727496066',
'platform':
'darwin',
'machine': ['arm64']
@@ -69,11 +69,11 @@
'file_name':
'tracebox',
'file_size':
- 2351336,
+ 2380040,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/linux-amd64/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/linux-amd64/tracebox',
'sha256':
- '0a533702f1ddf80998aaf3e95ce2ee8b154bfcf010c87bb740be6d04ac2e7380',
+ 'd70b284e8c28858fd539ae61ca59764d7f9fd6232073c304926e892fe75e692a',
'platform':
'linux',
'machine': ['x86_64']
@@ -83,11 +83,11 @@
'file_name':
'tracebox',
'file_size':
- 1433188,
+ 1450708,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/linux-arm/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/linux-arm/tracebox',
'sha256':
- 'd346f0ef77211230dd1f61284badb8edf4736852d446b36bb3d3e52a195934e4',
+ '178fa6a1a9bc80f72d81938d40fe201c25c595ffaff7e030d59c2af09dfcc06c',
'platform':
'linux',
'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -97,11 +97,11 @@
'file_name':
'tracebox',
'file_size':
- 2245088,
+ 2269816,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/linux-arm64/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/linux-arm64/tracebox',
'sha256':
- '7899b352ead70894a0cce25cd47db81229804daa168c9b18760003ae2068d3b0',
+ '42c64f9807756aaa08a2bfa13e9e4828c193a6b90ba1329408873c3ebf5adf3f',
'platform':
'linux',
'machine': ['aarch64']
@@ -111,44 +111,44 @@
'file_name':
'tracebox',
'file_size':
- 1323304,
+ 1333336,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/android-arm/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-arm/tracebox',
'sha256':
- '727bfbab060aeaf8e97bdef45f318d28c9e7452f91a7135311aff81f72a02fe7'
+ '93a78d2c42e3c00f117e2f155326383f69c891281ed693a39d87b8cb54ca4e19'
}, {
'arch':
'android-arm64',
'file_name':
'tracebox',
'file_size':
- 2101880,
+ 2115984,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/android-arm64/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-arm64/tracebox',
'sha256':
- 'ca9f2bbcc6fda0f8b2915e7c6b3d113a0a0ec256da14edcdb3ae4ffe69b4f2cb'
+ '508248a9e47ab605fd742efb700391d7267b68b586199a93e13e6ca14b72fe3d'
}, {
'arch':
'android-x86',
'file_name':
'tracebox',
'file_size':
- 2282928,
+ 2302960,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/android-x86/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-x86/tracebox',
'sha256':
- 'ffddf5dcdbe72419a610e7218908a96352b1a6b4fa27cd333aeab34f80a47fc1'
+ '63d20a69c4e0c291329d7917e640fa0d4f146c344e79988e87393b1431d594b1'
}, {
'arch':
'android-x64',
'file_name':
'tracebox',
'file_size':
- 2131400,
+ 2147880,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/android-x64/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-x64/tracebox',
'sha256':
- 'defba9ba1730c2583da87326096448cd7445271254392cd8f250e2fde0b54456'
+ 'c0ea1d5fd6d020e4c2b45d4d45cdd0c44ae63cd755d69260a3e5d2bacd3cbd6a'
}]
# ----- Amalgamator: end of python/perfetto/prebuilts/manifests/tracebox.py
diff --git a/tools/trace_processor b/tools/trace_processor
index d651a13..29e6186 100755
--- a/tools/trace_processor
+++ b/tools/trace_processor
@@ -30,18 +30,18 @@
# ----- Amalgamator: begin of python/perfetto/prebuilts/manifests/trace_processor_shell.py
-# This file has been generated by: tools/roll-prebuilts v47.0
+# This file has been generated by: tools/roll-prebuilts v48.1
TRACE_PROCESSOR_SHELL_MANIFEST = [{
'arch':
'mac-amd64',
'file_name':
'trace_processor_shell',
'file_size':
- 10209056,
+ 9949656,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/mac-amd64/trace_processor_shell',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/mac-amd64/trace_processor_shell',
'sha256':
- '203c4c7a3621ee7c60a3d558613216427aa0f7245dc34fbe27e03cbcaf15cbd7',
+ 'e9dcf95aaa02f8c00a724f0ff34ba3a454c717beb9900cf9fd97ab142b362452',
'platform':
'darwin',
'machine': ['x86_64']
@@ -51,11 +51,11 @@
'file_name':
'trace_processor_shell',
'file_size':
- 9518360,
+ 9223224,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/mac-arm64/trace_processor_shell',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/mac-arm64/trace_processor_shell',
'sha256':
- '02130db81f477e795f0fb33e5183eed6d9350057346d730fe30aac5a6443d9c1',
+ '9a0541a0f52f95bfcb8dc88d94bc4494c660d95eefc40fc946ab43d995051ff7',
'platform':
'darwin',
'machine': ['arm64']
@@ -65,11 +65,11 @@
'file_name':
'trace_processor_shell',
'file_size':
- 10363488,
+ 10142800,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/linux-amd64/trace_processor_shell',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/linux-amd64/trace_processor_shell',
'sha256':
- '832425c3c7934904d1e0ec1721beb51423de7dbcf399a899973f2b6b464603fa',
+ '18c8730b52f8ee1d9e202031527435b6b2e3149fbd9b1046b2e77d18f06aa337',
'platform':
'linux',
'machine': ['x86_64']
@@ -79,11 +79,11 @@
'file_name':
'trace_processor_shell',
'file_size':
- 7682608,
+ 7329432,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/linux-arm/trace_processor_shell',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/linux-arm/trace_processor_shell',
'sha256':
- '0d5e41279051326311b178c73289d6027493bdd8627f537e538aa39a6f74af81',
+ '0558040998666576e1063d6d626b8aa9e354f18d73d225240f043b3c9236befa',
'platform':
'linux',
'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -93,11 +93,11 @@
'file_name':
'trace_processor_shell',
'file_size':
- 9949744,
+ 9703384,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/linux-arm64/trace_processor_shell',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/linux-arm64/trace_processor_shell',
'sha256':
- '2a9e5f6ee3d9a0d6007fc5503a9358629d7b3881233ee6fbe157edaa0f5a3b1b',
+ 'eeb95cc54358df08375ffae4862c043a6737902179ce8e0408984004c32cf93c',
'platform':
'linux',
'machine': ['aarch64']
@@ -107,55 +107,55 @@
'file_name':
'trace_processor_shell',
'file_size':
- 7716332,
+ 7367412,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/android-arm/trace_processor_shell',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-arm/trace_processor_shell',
'sha256':
- 'a3a1f49448e72c368748cb6ec0cb1f63ba4fe5598ff08118053dac68916b9433'
+ 'd29b1e6aee52ceff24c072f56c7be7795d0fa29f3596e2633fafa60782384718'
}, {
'arch':
'android-arm64',
'file_name':
'trace_processor_shell',
'file_size':
- 9861544,
+ 9598784,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/android-arm64/trace_processor_shell',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-arm64/trace_processor_shell',
'sha256':
- '49c9f94802986b9cada8ffab9ec911f21a416966a7c0b2acc3e467f03892ec56'
+ '06e80c562c0043cca9225ade3c961a081bcc7435660117d5a6db26b815d0b9ca'
}, {
'arch':
'android-x86',
'file_name':
'trace_processor_shell',
'file_size':
- 10805720,
+ 10625488,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/android-x86/trace_processor_shell',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-x86/trace_processor_shell',
'sha256':
- 'b188a7d95533a26b9eadcc5233d9fcc8552f94c0c7224a7f39f5e6eaebd7e981'
+ '2a576fb397da14d0dabcfa97f5eeec15b4dc55df009308f75a5fdf9de8a9b0dd'
}, {
'arch':
'android-x64',
'file_name':
'trace_processor_shell',
'file_size':
- 10150016,
+ 9915664,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/android-x64/trace_processor_shell',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-x64/trace_processor_shell',
'sha256':
- 'ff83eb7f53fc91d42c8756bb752fd70ed1f03b40a9daba99a6843c391bc8ff66'
+ 'a30be9f09b53110394e87af4d6b41ae24cd74d9a3f97ac1cc4d6ae2057ac6977'
}, {
'arch':
'windows-amd64',
'file_name':
'trace_processor_shell.exe',
'file_size':
- 10187264,
+ 9922560,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/windows-amd64/trace_processor_shell.exe',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/windows-amd64/trace_processor_shell.exe',
'sha256':
- 'f9b39c21a99f412697b4bf59f7046f80482c9f07dc3507c2d448dda02915aa14',
+ 'd41639844a6c36dbaa195d91e9c356f2172d924c70a1bfed5432c407f857f009',
'platform':
'win32',
'machine': ['amd64']
diff --git a/tools/tracebox b/tools/tracebox
index b3ed784..9389051 100755
--- a/tools/tracebox
+++ b/tools/tracebox
@@ -30,18 +30,18 @@
# ----- Amalgamator: begin of python/perfetto/prebuilts/manifests/tracebox.py
-# This file has been generated by: tools/roll-prebuilts v47.0
+# This file has been generated by: tools/roll-prebuilts v48.1
TRACEBOX_MANIFEST = [{
'arch':
'mac-amd64',
'file_name':
'tracebox',
'file_size':
- 1597456,
+ 1613864,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/mac-amd64/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/mac-amd64/tracebox',
'sha256':
- '1e4b56533ad59e8131473ae6d4204356288a7b7a92241e303ab9865842d36c1d',
+ 'dfb1a3affe905d2e7d1f82bc4dda46b1fda6db054d60ae87c3215dd529b77fee',
'platform':
'darwin',
'machine': ['x86_64']
@@ -51,11 +51,11 @@
'file_name':
'tracebox',
'file_size':
- 1475640,
+ 1492184,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/mac-arm64/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/mac-arm64/tracebox',
'sha256':
- '8eae02034fa45581bd7262d1e3095616cc4f9a06a1bc0345cb5cae1277d8b4e4',
+ '4a492a629dd1f13f3146c4b8267c0b163afba8cef1d49e0c00c48bb727496066',
'platform':
'darwin',
'machine': ['arm64']
@@ -65,11 +65,11 @@
'file_name':
'tracebox',
'file_size':
- 2351336,
+ 2380040,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/linux-amd64/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/linux-amd64/tracebox',
'sha256':
- '0a533702f1ddf80998aaf3e95ce2ee8b154bfcf010c87bb740be6d04ac2e7380',
+ 'd70b284e8c28858fd539ae61ca59764d7f9fd6232073c304926e892fe75e692a',
'platform':
'linux',
'machine': ['x86_64']
@@ -79,11 +79,11 @@
'file_name':
'tracebox',
'file_size':
- 1433188,
+ 1450708,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/linux-arm/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/linux-arm/tracebox',
'sha256':
- 'd346f0ef77211230dd1f61284badb8edf4736852d446b36bb3d3e52a195934e4',
+ '178fa6a1a9bc80f72d81938d40fe201c25c595ffaff7e030d59c2af09dfcc06c',
'platform':
'linux',
'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -93,11 +93,11 @@
'file_name':
'tracebox',
'file_size':
- 2245088,
+ 2269816,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/linux-arm64/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/linux-arm64/tracebox',
'sha256':
- '7899b352ead70894a0cce25cd47db81229804daa168c9b18760003ae2068d3b0',
+ '42c64f9807756aaa08a2bfa13e9e4828c193a6b90ba1329408873c3ebf5adf3f',
'platform':
'linux',
'machine': ['aarch64']
@@ -107,44 +107,44 @@
'file_name':
'tracebox',
'file_size':
- 1323304,
+ 1333336,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/android-arm/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-arm/tracebox',
'sha256':
- '727bfbab060aeaf8e97bdef45f318d28c9e7452f91a7135311aff81f72a02fe7'
+ '93a78d2c42e3c00f117e2f155326383f69c891281ed693a39d87b8cb54ca4e19'
}, {
'arch':
'android-arm64',
'file_name':
'tracebox',
'file_size':
- 2101880,
+ 2115984,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/android-arm64/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-arm64/tracebox',
'sha256':
- 'ca9f2bbcc6fda0f8b2915e7c6b3d113a0a0ec256da14edcdb3ae4ffe69b4f2cb'
+ '508248a9e47ab605fd742efb700391d7267b68b586199a93e13e6ca14b72fe3d'
}, {
'arch':
'android-x86',
'file_name':
'tracebox',
'file_size':
- 2282928,
+ 2302960,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/android-x86/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-x86/tracebox',
'sha256':
- 'ffddf5dcdbe72419a610e7218908a96352b1a6b4fa27cd333aeab34f80a47fc1'
+ '63d20a69c4e0c291329d7917e640fa0d4f146c344e79988e87393b1431d594b1'
}, {
'arch':
'android-x64',
'file_name':
'tracebox',
'file_size':
- 2131400,
+ 2147880,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/android-x64/tracebox',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-x64/tracebox',
'sha256':
- 'defba9ba1730c2583da87326096448cd7445271254392cd8f250e2fde0b54456'
+ 'c0ea1d5fd6d020e4c2b45d4d45cdd0c44ae63cd755d69260a3e5d2bacd3cbd6a'
}]
# ----- Amalgamator: end of python/perfetto/prebuilts/manifests/tracebox.py
diff --git a/tools/traceconv b/tools/traceconv
index dca907c..55805b0 100755
--- a/tools/traceconv
+++ b/tools/traceconv
@@ -30,18 +30,18 @@
# ----- Amalgamator: begin of python/perfetto/prebuilts/manifests/traceconv.py
-# This file has been generated by: tools/roll-prebuilts v47.0
+# This file has been generated by: tools/roll-prebuilts v48.1
TRACECONV_MANIFEST = [{
'arch':
'mac-amd64',
'file_name':
'traceconv',
'file_size':
- 9481408,
+ 9041560,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/mac-amd64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/mac-amd64/traceconv',
'sha256':
- 'b6819bb922438d585e816646c4d60e43bfa823d5f3f499bd8efcaccd26a9009c',
+ 'cec2da5cb771a4812d0b2d15604d5023954d28e0af12e87313da2ab70d26b970',
'platform':
'darwin',
'machine': ['x86_64']
@@ -51,11 +51,11 @@
'file_name':
'traceconv',
'file_size':
- 8852520,
+ 8375512,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/mac-arm64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/mac-arm64/traceconv',
'sha256':
- '9e9eac795578f6ed76128127d8a81c1ce5620b3d47f2c5e23c1f7f2ebbff9bea',
+ '64e200a58ea9c9f366e1071dd274d0023d1fd14043f75dbba3fe0cc138ff5fc7',
'platform':
'darwin',
'machine': ['arm64']
@@ -65,11 +65,11 @@
'file_name':
'traceconv',
'file_size':
- 9538432,
+ 9134136,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/linux-amd64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/linux-amd64/traceconv',
'sha256':
- '01881a82050f36b8db427c741ce236360bb86548e6a6c9445b2477c6150de05b',
+ '87b87e1778367c1e3b99fc77439a28b4911125d2751f9909fd1b51f6bd60b6f4',
'platform':
'linux',
'machine': ['x86_64']
@@ -79,11 +79,11 @@
'file_name':
'traceconv',
'file_size':
- 7286504,
+ 6753020,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/linux-arm/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/linux-arm/traceconv',
'sha256':
- 'bc700f945c78c4a65a60bdb499c6c59e671c5173f45f42976fd0661396b72c16',
+ '804c4e13aca5798731056952d9cb0c6ee58795c03477c69514ccd39703060812',
'platform':
'linux',
'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -93,11 +93,11 @@
'file_name':
'traceconv',
'file_size':
- 9168408,
+ 8740064,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/linux-arm64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/linux-arm64/traceconv',
'sha256':
- 'd0192785a56c5088811a175ab083bb599aab5fd594a3248057380038f0b2b5c2',
+ '0d781886531d11e1d573a1ec5e06376ef139bb479eec38c16c8735821c35b895',
'platform':
'linux',
'machine': ['aarch64']
@@ -107,55 +107,55 @@
'file_name':
'traceconv',
'file_size':
- 7322024,
+ 6792280,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/android-arm/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-arm/traceconv',
'sha256':
- '77583ed2eefe75796a3fe2a7149d6e234c643d4f83690f732367442bbb259812'
+ '7d91e4133184a3722a25488edd3692c5a195148eba56621014311d3f85d3fc15'
}, {
'arch':
'android-arm64',
'file_name':
'traceconv',
'file_size':
- 9123224,
+ 8677992,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/android-arm64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-arm64/traceconv',
'sha256':
- 'cd93333b3d56f41949066688308a23fdd3fbbf367b03aceff711d8102e696702'
+ 'c03c4a901ed23f1e20a12c98ce4556353a62bddcd260fb4d797cd29ff6c49a05'
}, {
'arch':
'android-x86',
'file_name':
'traceconv',
'file_size':
- 9868752,
+ 9503704,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/android-x86/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-x86/traceconv',
'sha256':
- '9b2921ba776ad99bf2ef0d28ff6919dea5ed726a3c61a81159b9d99b39dd7b6d'
+ '704e58a7249de56aadec64d4c0d83bab0821d2c4fd77114a9b71705ff4224539'
}, {
'arch':
'android-x64',
'file_name':
'traceconv',
'file_size':
- 9382824,
+ 8964488,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/android-x64/traceconv',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-x64/traceconv',
'sha256':
- 'c1fdbcb3c2cd15838468c3cc0ed8131a073f220c133384139aa57c4c05b2d34b'
+ 'e4f07836fc2a5fb7cd997a9acc4183af7a06997d1e73aac71021af5114b921bc'
}, {
'arch':
'windows-amd64',
'file_name':
'traceconv.exe',
'file_size':
- 9209856,
+ 8763904,
'url':
- 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/windows-amd64/traceconv.exe',
+ 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/windows-amd64/traceconv.exe',
'sha256':
- 'ddef23109550784b6069b57bbac1ee627a1ab09086fdd72950965e866cfba536',
+ '084670ac28ed59a9642782a30e051735c1b7474b8cd569b9bc94c305af68290e',
'platform':
'win32',
'machine': ['amd64']
diff --git a/ui/build.js b/ui/build.js
index 96ac8a6..1079dff 100644
--- a/ui/build.js
+++ b/ui/build.js
@@ -256,6 +256,7 @@
generateImports('ui/src/plugins', 'all_plugins.ts');
compileProtos();
genVersion();
+ generateStdlibDocs();
const tsProjects = [
'ui',
@@ -453,6 +454,25 @@
addTask(exec, [cmd, args]);
}
+function generateStdlibDocs() {
+ const cmd = pjoin(ROOT_DIR, 'tools/gen_stdlib_docs_json.py');
+ const stdlibDir = pjoin(ROOT_DIR, 'src/trace_processor/perfetto_sql/stdlib');
+
+ const stdlibFiles =
+ listFilesRecursive(stdlibDir)
+ .filter((filePath) => path.extname(filePath) === '.sql');
+
+ addTask(exec, [
+ cmd,
+ [
+ '--json-out',
+ pjoin(cfg.outDistDir, 'stdlib_docs.json'),
+ '--minify',
+ ...stdlibFiles,
+ ],
+ ]);
+}
+
function updateSymlinks() {
// /ui/out -> /out/ui.
mklink(cfg.outUiDir, pjoin(ROOT_DIR, 'ui/out'));
@@ -823,6 +843,18 @@
}
}
+// Recursively build a list of files in a given directory and return a list of
+// file paths, similar to `find -type f`.
+function listFilesRecursive(dir) {
+ const fileList = [];
+
+ walk(dir, (filePath) => {
+ fileList.push(filePath);
+ });
+
+ return fileList;
+}
+
function ensureDir(dirPath, clean) {
const exists = fs.existsSync(dirPath);
if (exists && clean) {
diff --git a/ui/package.json b/ui/package.json
index 7f8d483..600775f 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -31,6 +31,7 @@
"devtools-protocol": "0.0.1319565",
"esbuild": "^0.21.5",
"events": "^3.3.0",
+ "fzf": "^0.5.2",
"hsluv": "^0.1.0",
"immer": "^10.1.1",
"jsbn-rsa": "^1.0.4",
diff --git a/ui/pnpm-lock.yaml b/ui/pnpm-lock.yaml
index f06a93d..636e138 100644
--- a/ui/pnpm-lock.yaml
+++ b/ui/pnpm-lock.yaml
@@ -77,6 +77,9 @@
events:
specifier: ^3.3.0
version: 3.3.0
+ fzf:
+ specifier: ^0.5.2
+ version: 0.5.2
hsluv:
specifier: ^0.1.0
version: 0.1.0
@@ -3258,6 +3261,10 @@
/function-bind@1.1.1:
resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
+ /fzf@0.5.2:
+ resolution: {integrity: sha512-Tt4kuxLXFKHy8KT40zwsUPUkg1CrsgY25FxA2U/j/0WgEDCk3ddc/zLTCCcbSHX9FcKtLuVaDGtGE/STWC+j3Q==}
+ dev: false
+
/gensync@1.0.0-beta.2:
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
engines: {node: '>=6.9.0'}
diff --git a/ui/release/channels.json b/ui/release/channels.json
index 992488a..5f8615b 100644
--- a/ui/release/channels.json
+++ b/ui/release/channels.json
@@ -6,7 +6,7 @@
},
{
"name": "canary",
- "rev": "8ce69f6b35d277f96eac6ec184de0a7320c7c09c"
+ "rev": "2db61efa59d1e2eecb6975854c14b2a122fbfa8a"
},
{
"name": "autopush",
diff --git a/ui/src/assets/common.scss b/ui/src/assets/common.scss
index d88c8f1..14f3fd7 100644
--- a/ui/src/assets/common.scss
+++ b/ui/src/assets/common.scss
@@ -27,8 +27,6 @@
--selection-fill-color: #8398e64d;
--overview-timeline-non-visible-color: #c8c8c8cc;
--details-content-height: 280px;
- --collapsed-background: hsla(190, 49%, 97%, 1);
- --expanded-background: hsl(215, 22%, 19%);
}
@mixin transition($time: 0.1s) {
@@ -157,24 +155,6 @@
grid-area: page;
}
-.alerts {
- grid-area: alerts;
- background-color: #f2f2f2;
- > div {
- font-family: "Roboto", sans-serif;
- font-weight: 400;
- letter-spacing: 0.25px;
- padding: 1rem;
- display: flex;
- justify-content: space-between;
- align-items: center;
- button {
- width: 24px;
- height: 24px;
- }
- }
-}
-
@mixin table-font-size {
font-size: 14px;
line-height: 18px;
diff --git a/ui/src/assets/topbar.scss b/ui/src/assets/topbar.scss
index d3ae7ef..2cce56f 100644
--- a/ui/src/assets/topbar.scss
+++ b/ui/src/assets/topbar.scss
@@ -236,21 +236,6 @@
font-family: "Roboto Condensed", sans-serif;
}
-.helpful-hint {
- position: absolute;
- z-index: 10;
- right: 5px;
- top: 5px;
- width: 300px;
- background-color: white;
- font-size: 12px;
- color: #3f4040;
- display: grid;
- border-radius: 5px;
- padding: 8px;
- box-shadow: 1px 3px 15px rgba(23, 32, 44, 0.3);
-}
-
.hint-text {
padding-bottom: 5px;
}
diff --git a/ui/src/assets/viewer_page.scss b/ui/src/assets/viewer_page.scss
index 73d82a6..56a8e5b 100644
--- a/ui/src/assets/viewer_page.scss
+++ b/ui/src/assets/viewer_page.scss
@@ -94,4 +94,27 @@
.time-selection-panel {
height: 10px;
}
+
+ .helpful-hint {
+ position: absolute;
+ z-index: 10;
+ right: 5px;
+ top: 5px;
+ width: 300px;
+ background-color: white;
+ font-size: 12px;
+ color: #3f4040;
+ display: grid;
+ border-radius: 5px;
+ padding: 8px;
+ box-shadow: 1px 3px 15px rgba(23, 32, 44, 0.3);
+ }
+}
+
+.pf-track-crash-popup {
+ font-family: $pf-font;
+ max-width: 300px;
+ display: flex;
+ flex-direction: column;
+ row-gap: 6px;
}
diff --git a/ui/src/assets/widgets/track_widget.scss b/ui/src/assets/widgets/track_widget.scss
index c2191fd..6b45ef9 100644
--- a/ui/src/assets/widgets/track_widget.scss
+++ b/ui/src/assets/widgets/track_widget.scss
@@ -13,11 +13,16 @@
// limitations under the License.
@use "sass:math";
+@import "theme";
.pf-track {
--text-color-dark: hsl(213, 22%, 30%);
--text-color-light: white;
--indent-size: 8px;
+ --drag-highlight-color: rgb(29, 85, 189);
+ --default-background: white;
+ --collapsed-background: hsla(190, 49%, 97%, 1);
+ --expanded-background: hsl(215, 22%, 19%);
// Layout
display: grid;
@@ -33,22 +38,7 @@
font-weight: 300;
font-size: 14px;
color: var(--text-color-dark);
-
- &.pf-is-summary {
- background-color: var(--collapsed-background);
-
- &.pf-expanded {
- background-color: var(--expanded-background);
- color: var(--text-color-light);
- }
- }
-
- &.pf-highlight {
- .pf-track-shell {
- background-color: #ffe263;
- color: var(--text-color-dark);
- }
- }
+ background-color: var(--default-background);
&::before {
content: "";
@@ -60,8 +50,17 @@
.pf-track-shell {
grid-area: shell;
+ background-color: inherit;
box-shadow: inset 0px -1px 0px 0px var(--track-border-color);
+ &.pf-drag-after {
+ box-shadow: inset 0 -6px 3px -3px var(--drag-highlight-color);
+ }
+
+ &.pf-drag-before {
+ box-shadow: inset 0 6px 3px -3px var(--drag-highlight-color);
+ }
+
&.pf-clickable {
cursor: pointer;
}
@@ -129,6 +128,22 @@
}
}
+ &.pf-is-summary {
+ background-color: var(--collapsed-background);
+
+ &.pf-expanded {
+ background-color: var(--expanded-background);
+ color: var(--text-color-light);
+ }
+ }
+
+ &.pf-highlight {
+ .pf-track-shell {
+ background-color: #ffe263;
+ color: var(--text-color-dark);
+ }
+ }
+
.pf-track-content {
grid-area: content;
box-shadow: inset 1px -1px 0px 0px var(--track-border-color);
diff --git a/ui/src/base/assets.ts b/ui/src/base/assets.ts
new file mode 100644
index 0000000..7eee18e
--- /dev/null
+++ b/ui/src/base/assets.ts
@@ -0,0 +1,33 @@
+// 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 {getServingRoot} from './http_utils';
+
+let rootUrl = '';
+
+/**
+ * This function must be called once while bootstrapping in a direct script
+ * context (i.e. not a promise or callback). Typically frontend/index.ts.
+ */
+export function initAssets() {
+ rootUrl = getServingRoot();
+}
+
+/**
+ * Returns the absolute url of an asset.
+ * assetSrc('assets/image.jpg') -> '/v123-deadbef/assets/image.png';
+ */
+export function assetSrc(relPath: string) {
+ return rootUrl + relPath;
+}
diff --git a/ui/src/base/fuzzy.ts b/ui/src/base/fuzzy.ts
index bd15658..7a67a6c 100644
--- a/ui/src/base/fuzzy.ts
+++ b/ui/src/base/fuzzy.ts
@@ -12,6 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+import {Fzf} from 'fzf';
+import {SyncOptionsTuple} from 'fzf/dist/types/finders';
+
export interface FuzzySegment {
matching: boolean;
value: string;
@@ -26,36 +29,32 @@
// Finds approx matching in arbitrary lists of items.
export class FuzzyFinder<T> {
- private readonly items: ReadonlyArray<T>;
- private readonly keyLookup: KeyLookup<T>;
-
+ private readonly fzf: Fzf<ReadonlyArray<T>>;
// Because we operate on arbitrary lists, a key lookup function is required to
// so we know which part of the list is to be be searched. It should return
// the relevant search string for each item.
- constructor(items: ReadonlyArray<T>, keyLookup: KeyLookup<T>) {
- this.items = items;
- this.keyLookup = keyLookup;
+ constructor(
+ items: ReadonlyArray<T>,
+ private readonly keyLookup: KeyLookup<T>,
+ ) {
+ // NOTE(stevegolton): This type assertion because FZF appears to be very
+ // fussy about its input types.
+ const options = [{selector: keyLookup}] as SyncOptionsTuple<T>;
+ this.fzf = new Fzf<ReadonlyArray<T>>(items, ...options);
}
// Return a list of items that match any of the search terms.
- find(...searchTerms: string[]): FuzzyResult<T>[] {
- const result: FuzzyResult<T>[] = [];
-
- for (const item of this.items) {
- const key = this.keyLookup(item);
- for (const searchTerm of searchTerms) {
- const indicies: number[] = new Array(searchTerm.length);
- if (match(searchTerm, key, indicies)) {
- const segments = indiciesToSegments(indicies, key);
- result.push({item, segments});
-
- // Don't try to match any more...
- break;
- }
- }
- }
-
- return result;
+ find(searchTerm: string): FuzzyResult<T>[] {
+ return this.fzf.find(searchTerm).map((m) => {
+ const normalisedTerm = this.keyLookup(m.item);
+ return {
+ item: m.item,
+ segments: indiciesToSegments(
+ Array.from(m.positions).sort((a, b) => a - b),
+ normalisedTerm,
+ ),
+ };
+ });
}
}
diff --git a/ui/src/base/fuzzy_unittest.ts b/ui/src/base/fuzzy_unittest.ts
index aa3fdd3..5d195bc 100644
--- a/ui/src/base/fuzzy_unittest.ts
+++ b/ui/src/base/fuzzy_unittest.ts
@@ -15,7 +15,7 @@
import {FuzzyFinder, fuzzyMatch} from './fuzzy';
describe('FuzzyFinder', () => {
- const items = ['aaa', 'aba', 'zzz', 'c z d z e', 'Foo', 'ababc'];
+ const items = ['aaa', 'aba', 'zzz', 'c z d z e', 'CAPS', 'ababc'];
const finder = new FuzzyFinder(items, (x) => x);
it('finds all for empty search term', () => {
@@ -26,7 +26,7 @@
{item: 'aba', segments: [{matching: false, value: 'aba'}]},
{item: 'zzz', segments: [{matching: false, value: 'zzz'}]},
{item: 'c z d z e', segments: [{matching: false, value: 'c z d z e'}]},
- {item: 'Foo', segments: [{matching: false, value: 'Foo'}]},
+ {item: 'CAPS', segments: [{matching: false, value: 'CAPS'}]},
{item: 'ababc', segments: [{matching: false, value: 'ababc'}]},
]);
});
@@ -91,11 +91,11 @@
);
});
- it('finds case insensitive match', () => {
- const result = finder.find('foO');
+ it('finds caps match when search term is in lower case', () => {
+ const result = finder.find('caps');
expect(result).toEqual(
expect.arrayContaining([
- {item: 'Foo', segments: [{matching: true, value: 'Foo'}]},
+ {item: 'CAPS', segments: [{matching: true, value: 'CAPS'}]},
]),
);
});
@@ -115,29 +115,6 @@
]),
);
});
-
- it('match multiple', () => {
- const result = finder.find('abc', 'c z d');
- expect(result).toEqual(
- expect.arrayContaining([
- {
- item: 'ababc',
- segments: [
- {matching: true, value: 'ab'},
- {matching: false, value: 'ab'},
- {matching: true, value: 'c'},
- ],
- },
- {
- item: 'c z d z e',
- segments: [
- {matching: true, value: 'c z d'},
- {matching: false, value: ' z e'},
- ],
- },
- ]),
- );
- });
});
test('fuzzyMatch', () => {
diff --git a/ui/src/common/gcs_uploader.ts b/ui/src/base/gcs_uploader.ts
similarity index 93%
rename from ui/src/common/gcs_uploader.ts
rename to ui/src/base/gcs_uploader.ts
index d2012ee..b2f2bd5 100644
--- a/ui/src/common/gcs_uploader.ts
+++ b/ui/src/base/gcs_uploader.ts
@@ -12,9 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {defer} from '../base/deferred';
-import {Time} from '../base/time';
-import {TraceFileStream} from '../core/trace_stream';
+import {defer} from './deferred';
+import {Time} from './time';
export const BUCKET_NAME = 'perfetto-ui-data';
export const MIME_JSON = 'application/json; charset=utf-8';
@@ -184,13 +183,16 @@
* @returns A hex-encoded string containing the hash of the file.
*/
async function hashFileStreaming(file: Blob): Promise<string> {
- const fileStream = new TraceFileStream(file);
+ const CHUNK_SIZE = 32 * 1024 * 1024; // 32MB
+ const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
let chunkDigests = '';
- for (;;) {
- const chunk = await fileStream.readChunk();
- const digest = await crypto.subtle.digest('SHA-1', chunk.data);
+
+ for (let i = 0; i < totalChunks; i++) {
+ const start = i * CHUNK_SIZE;
+ const end = Math.min(start + CHUNK_SIZE, file.size);
+ const chunk = await file.slice(start, end).arrayBuffer();
+ const digest = await crypto.subtle.digest('SHA-1', chunk);
chunkDigests += digestToHex(digest);
- if (chunk.eof) break;
}
return sha1(chunkDigests);
}
diff --git a/ui/src/core/hash.ts b/ui/src/base/hash.ts
similarity index 100%
rename from ui/src/core/hash.ts
rename to ui/src/base/hash.ts
diff --git a/ui/src/base/hotkeys.ts b/ui/src/base/hotkeys.ts
index aad5bd6..17e3a00 100644
--- a/ui/src/base/hotkeys.ts
+++ b/ui/src/base/hotkeys.ts
@@ -46,7 +46,6 @@
// these keys.
import {elementIsEditable} from './dom_utils';
-import {Optional} from './utils';
type Alphabet =
| 'A'
@@ -170,7 +169,7 @@
// Deconstruct a hotkey from its string representation into its constituent
// parts.
-export function parseHotkey(hotkey: Hotkey): Optional<HotkeyParts> {
+export function parseHotkey(hotkey: Hotkey): HotkeyParts | undefined {
const regex = /^(!?)((?:Mod\+|Shift\+|Alt\+|Ctrl\+)*)(.*)$/;
const result = hotkey.match(regex);
@@ -189,7 +188,7 @@
export function formatHotkey(
hotkey: Hotkey,
spoof?: Platform,
-): Optional<string> {
+): string | undefined {
const parsed = parseHotkey(hotkey);
return parsed && formatHotkeyParts(parsed, spoof);
}
diff --git a/ui/src/base/http_utils.ts b/ui/src/base/http_utils.ts
index 357fa5d..869dfb6 100644
--- a/ui/src/base/http_utils.ts
+++ b/ui/src/base/http_utils.ts
@@ -32,10 +32,45 @@
});
}
+export function fetchWithProgress(
+ url: string,
+ onProgress?: (percentage: number) => void,
+): Promise<Blob> {
+ return new Promise((resolve, reject) => {
+ const xhr = new XMLHttpRequest();
+
+ xhr.open('GET', url, /* async= */ true);
+ xhr.responseType = 'blob';
+
+ xhr.onprogress = (event) => {
+ if (event.lengthComputable) {
+ const percentComplete = Math.round((event.loaded / event.total) * 100);
+ onProgress?.(percentComplete);
+ }
+ };
+
+ xhr.onload = () => {
+ if (xhr.status >= 200 && xhr.status < 300) {
+ resolve(xhr.response); // Resolve with the Blob response
+ } else {
+ reject(
+ new Error(`Failed to download: ${xhr.status} ${xhr.statusText}`),
+ );
+ }
+ };
+
+ xhr.onerror = () => {
+ reject(new Error(`Network error in fetchWithProgress(${url})`));
+ };
+
+ xhr.send();
+ });
+}
+
/**
* NOTE: this function can only be called from synchronous contexts. It will
* fail if called in timer handlers or async continuations (e.g. after an await)
- * Use globals.root which caches it on startup.
+ * Use assetSrc(relPath) which caches it on startup.
* @returns the directory where the app is served from, e.g. 'v46.0-a2082649b'
*/
export function getServingRoot() {
diff --git a/ui/src/base/logging.ts b/ui/src/base/logging.ts
index bfe7940..6a0dc5c 100644
--- a/ui/src/base/logging.ts
+++ b/ui/src/base/logging.ts
@@ -36,6 +36,11 @@
return value;
}
+export function assertIsInstance<T>(value: unknown, clazz: Function): T {
+ assertTrue(value instanceof clazz);
+ return value as T;
+}
+
export function assertTrue(value: boolean, optMsg?: string) {
if (!value) {
throw new Error(optMsg ?? 'Failed assertion');
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/base/string_utils.ts b/ui/src/base/string_utils.ts
index 389551c..df0e3da 100644
--- a/ui/src/base/string_utils.ts
+++ b/ui/src/base/string_utils.ts
@@ -93,6 +93,11 @@
return `'${str.replaceAll("'", "''")}'`;
}
+// Makes a string safe to be used as a SQL table/view/function name.
+export function sqlNameSafe(str: string): string {
+ return str.replace(/[^a-zA-Z0-9_]+/g, '_');
+}
+
// Chat apps (including G Chat) sometimes replace ASCII characters with similar
// looking unicode characters that break code snippets.
// This function attempts to undo these replacements.
diff --git a/ui/src/base/utils.ts b/ui/src/base/utils.ts
index f2e90a5..a82062c 100644
--- a/ui/src/base/utils.ts
+++ b/ui/src/base/utils.ts
@@ -25,8 +25,16 @@
| {success: true; result: T}
| {success: false; error: E};
-// Generic "optional" type
-export type Optional<T> = T | undefined;
+// Type util to make sure that exactly one of the passed keys is defined.
+// Example usage:
+// type FooOrBar = ExactlyOne<{foo: number; bar: number}>;
+// const x : FooOrBar = {foo: 42}; // OK
+// const x : FooOrBar = {bar: 42}; // OK
+// const x : FooOrBar = {}; // Compiler error
+// const x : FooOrBar = {foo:1, bar:2}; // Compiler error
+export type ExactlyOne<T, K extends keyof T = keyof T> = K extends keyof T
+ ? {[P in K]: T[P]} & {[P in Exclude<keyof T, K>]?: undefined}
+ : never;
// Escape characters that are not allowed inside a css selector
export function escapeCSSSelector(selector: string): string {
@@ -36,3 +44,47 @@
// Make field K required in T
export type RequiredField<T, K extends keyof T> = Omit<T, K> &
Required<Pick<T, K>>;
+
+// The lowest common denoninator between Map<> and WeakMap<>.
+// This is just to avoid duplication of the getOrCreate below.
+interface MapLike<K, V> {
+ get(key: K): V | undefined;
+ set(key: K, value: V): this;
+}
+
+export function getOrCreate<K, V>(
+ map: MapLike<K, V>,
+ key: K,
+ factory: () => V,
+): V {
+ let value = map.get(key);
+ if (value !== undefined) return value;
+ value = factory();
+ map.set(key, value);
+ return value;
+}
+
+// Allows to take an existing class instance (`target`) and override some of its
+// methods via `overrides`. We use this for cases where we want to expose a
+// "manager" (e.g. TrackManager, SidebarManager) to the plugins, but we want to
+// override few of its methods (e.g. to inject the pluginId in the args).
+export function createProxy<T extends object>(
+ target: T,
+ overrides: Partial<T>,
+): T {
+ return new Proxy(target, {
+ get: (target: T, prop: string | symbol, receiver) => {
+ // If the property is overriden, use that; otherwise, use target
+ const overrideValue = (overrides as {[key: symbol | string]: {}})[prop];
+ if (overrideValue !== undefined) {
+ return typeof overrideValue === 'function'
+ ? overrideValue.bind(overrides)
+ : overrideValue;
+ }
+ const baseValue = Reflect.get(target, prop, receiver);
+ return typeof baseValue === 'function'
+ ? baseValue.bind(target)
+ : baseValue;
+ },
+ }) as T;
+}
diff --git a/ui/src/base/uuid.ts b/ui/src/base/uuid.ts
index 1c595ba..1ffdd37 100644
--- a/ui/src/base/uuid.ts
+++ b/ui/src/base/uuid.ts
@@ -13,6 +13,7 @@
// limitations under the License.
import {v4} from 'uuid';
+import {sqlNameSafe} from './string_utils';
export const uuidv4 = v4;
@@ -23,5 +24,5 @@
*/
export function uuidv4Sql(uuid?: string): string {
const str = uuid ?? uuidv4();
- return str.replace(/[^a-zA-Z0-9_]+/g, '_');
+ return sqlNameSafe(str);
}
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/actions.ts b/ui/src/common/actions.ts
deleted file mode 100644
index cd9ec83..0000000
--- a/ui/src/common/actions.ts
+++ /dev/null
@@ -1,348 +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 {Draft} from 'immer';
-import {time} from '../base/time';
-import {RecordConfig} from '../controller/record_config_types';
-import {createEmptyState} from './empty_state';
-import {
- MetatraceTrackId,
- traceEventBegin,
- traceEventEnd,
- TraceEventScope,
-} from './metatracing';
-import {
- AdbRecordingTarget,
- EngineMode,
- LoadedConfig,
- NewEngineMode,
- PendingDeeplinkState,
- RecordingTarget,
- State,
- Status,
-} from './state';
-
-type StateDraft = Draft<State>;
-
-export interface PostedTrace {
- buffer: ArrayBuffer;
- title: string;
- fileName?: string;
- url?: string;
- uuid?: string;
- localOnly?: boolean;
- keepApiOpen?: boolean;
-
- // Allows to pass extra arguments to plugins. This can be read by plugins
- // onTraceLoad() and can be used to trigger plugin-specific-behaviours (e.g.
- // allow dashboards like APC to pass extra data to materialize onto tracks).
- // The format is the following:
- // pluginArgs: {
- // 'dev.perfetto.PluginFoo': { 'key1': 'value1', 'key2': 1234 }
- // 'dev.perfetto.PluginBar': { 'key3': '...', 'key4': ... }
- // }
- pluginArgs?: {[pluginId: string]: {[key: string]: unknown}};
-}
-
-export interface PostedScrollToRange {
- timeStart: number;
- timeEnd: number;
- viewPercentage?: number;
-}
-
-function clearTraceState(state: StateDraft) {
- const nextId = state.nextId;
- const recordConfig = state.recordConfig;
- const recordingTarget = state.recordingTarget;
- const fetchChromeCategories = state.fetchChromeCategories;
- const extensionInstalled = state.extensionInstalled;
- const availableAdbDevices = state.availableAdbDevices;
- const chromeCategories = state.chromeCategories;
- const newEngineMode = state.newEngineMode;
-
- Object.assign(state, createEmptyState());
- state.nextId = nextId;
- state.recordConfig = recordConfig;
- state.recordingTarget = recordingTarget;
- state.fetchChromeCategories = fetchChromeCategories;
- state.extensionInstalled = extensionInstalled;
- state.availableAdbDevices = availableAdbDevices;
- state.chromeCategories = chromeCategories;
- state.newEngineMode = newEngineMode;
-}
-
-function generateNextId(draft: StateDraft): string {
- const nextId = String(Number(draft.nextId) + 1);
- draft.nextId = nextId;
- return nextId;
-}
-
-let statusTraceEvent: TraceEventScope | undefined;
-
-export const StateActions = {
- openTraceFromFile(state: StateDraft, args: {file: File}): void {
- clearTraceState(state);
- const id = generateNextId(state);
- state.engine = {
- id,
- ready: false,
- source: {type: 'FILE', file: args.file},
- };
- },
-
- openTraceFromBuffer(state: StateDraft, args: PostedTrace): void {
- clearTraceState(state);
- const id = generateNextId(state);
- state.engine = {
- id,
- ready: false,
- source: {type: 'ARRAY_BUFFER', ...args},
- };
- },
-
- openTraceFromUrl(state: StateDraft, args: {url: string}): void {
- clearTraceState(state);
- const id = generateNextId(state);
- state.engine = {
- id,
- ready: false,
- source: {type: 'URL', url: args.url},
- };
- },
-
- openTraceFromHttpRpc(state: StateDraft, _args: {}): void {
- clearTraceState(state);
- const id = generateNextId(state);
- state.engine = {
- id,
- ready: false,
- source: {type: 'HTTP_RPC'},
- };
- },
-
- setTraceUuid(state: StateDraft, args: {traceUuid: string}) {
- state.traceUuid = args.traceUuid;
- },
-
- requestTrackReload(state: StateDraft, _: {}) {
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
- if (state.lastTrackReloadRequest) {
- state.lastTrackReloadRequest++;
- } else {
- state.lastTrackReloadRequest = 1;
- }
- },
-
- maybeSetPendingDeeplink(state: StateDraft, args: PendingDeeplinkState) {
- state.pendingDeeplink = args;
- },
-
- clearPendingDeeplink(state: StateDraft, _: {}) {
- state.pendingDeeplink = undefined;
- },
-
- // TODO(hjd): engine.ready should be a published thing. If it's part
- // of the state it interacts badly with permalinks.
- setEngineReady(
- state: StateDraft,
- args: {engineId: string; ready: boolean; mode: EngineMode},
- ): void {
- const engine = state.engine;
- if (engine === undefined || engine.id !== args.engineId) {
- return;
- }
- engine.ready = args.ready;
- engine.mode = args.mode;
- },
-
- setNewEngineMode(state: StateDraft, args: {mode: NewEngineMode}): void {
- state.newEngineMode = args.mode;
- },
-
- // Marks all engines matching the given |mode| as failed.
- setEngineFailed(
- state: StateDraft,
- args: {mode: EngineMode; failure: string},
- ): void {
- if (state.engine !== undefined && state.engine.mode === args.mode) {
- state.engine.failed = args.failure;
- }
- },
-
- updateStatus(state: StateDraft, args: Status): void {
- if (statusTraceEvent) {
- traceEventEnd(statusTraceEvent);
- }
- statusTraceEvent = traceEventBegin(args.msg, {
- track: MetatraceTrackId.kOmniboxStatus,
- });
- state.status = args;
- },
-
- // TODO(hjd): Remove setState - it causes problems due to reuse of ids.
- setState(state: StateDraft, args: {newState: State}): void {
- for (const key of Object.keys(state)) {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- delete (state as any)[key];
- }
- for (const key of Object.keys(args.newState)) {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- (state as any)[key] = (args.newState as any)[key];
- }
-
- // If we're loading from a permalink then none of the engines can
- // possibly be ready:
- if (state.engine !== undefined) {
- state.engine.ready = false;
- }
- },
-
- setRecordConfig(
- state: StateDraft,
- args: {config: RecordConfig; configType?: LoadedConfig},
- ): void {
- state.recordConfig = args.config;
- state.lastLoadedConfig = args.configType || {type: 'NONE'};
- },
-
- startRecording(state: StateDraft, _: {}): void {
- state.recordingInProgress = true;
- state.lastRecordingError = undefined;
- state.recordingCancelled = false;
- },
-
- stopRecording(state: StateDraft, _: {}): void {
- state.recordingInProgress = false;
- },
-
- cancelRecording(state: StateDraft, _: {}): void {
- state.recordingInProgress = false;
- state.recordingCancelled = true;
- },
-
- setExtensionAvailable(state: StateDraft, args: {available: boolean}): void {
- state.extensionInstalled = args.available;
- },
-
- setRecordingTarget(state: StateDraft, args: {target: RecordingTarget}): void {
- state.recordingTarget = args.target;
- },
-
- setFetchChromeCategories(state: StateDraft, args: {fetch: boolean}): void {
- state.fetchChromeCategories = args.fetch;
- },
-
- setAvailableAdbDevices(
- state: StateDraft,
- args: {devices: AdbRecordingTarget[]},
- ): void {
- state.availableAdbDevices = args.devices;
- },
-
- setChromeCategories(state: StateDraft, args: {categories: string[]}): void {
- state.chromeCategories = args.categories;
- },
-
- setLastRecordingError(state: StateDraft, args: {error?: string}): void {
- state.lastRecordingError = args.error;
- state.recordingStatus = undefined;
- },
-
- setRecordingStatus(state: StateDraft, args: {status?: string}): void {
- state.recordingStatus = args.status;
- state.lastRecordingError = undefined;
- },
-
- togglePerfDebug(state: StateDraft, _: {}): void {
- state.perfDebug = !state.perfDebug;
- },
-
- setSidebar(state: StateDraft, args: {visible: boolean}): void {
- state.sidebarVisible = args.visible;
- },
-
- setHoveredUtidAndPid(state: StateDraft, args: {utid: number; pid: number}) {
- state.hoveredPid = args.pid;
- state.hoveredUtid = args.utid;
- },
-
- setHighlightedSliceId(state: StateDraft, args: {sliceId: number}) {
- state.highlightedSliceId = args.sliceId;
- },
-
- setHoveredNoteTimestamp(state: StateDraft, args: {ts: time}) {
- state.hoveredNoteTimestamp = args.ts;
- },
-
- dismissFlamegraphModal(state: StateDraft, _: {}) {
- state.flamegraphModalDismissed = true;
- },
-
- setTrackFilterTerm(
- state: StateDraft,
- args: {filterTerm: string | undefined},
- ) {
- state.trackFilterTerm = args.filterTerm;
- },
-
- runControllers(state: StateDraft, _args: {}) {
- state.forceRunControllers++;
- },
-};
-
-// When we are on the frontend side, we don't really want to execute the
-// actions above, we just want to serialize them and marshal their
-// arguments, send them over to the controller side and have them being
-// executed there. The magic below takes care of turning each action into a
-// function that returns the marshaled args.
-
-// A DeferredAction is a bundle of Args and a method name. This is the marshaled
-// version of a StateActions method call.
-export interface DeferredAction<Args = {}> {
- type: string;
- args: Args;
-}
-
-// This type magic creates a type function DeferredActions<T> which takes a type
-// T and 'maps' its attributes. For each attribute on T matching the signature:
-// (state: StateDraft, args: Args) => void
-// DeferredActions<T> has an attribute:
-// (args: Args) => DeferredAction<Args>
-type ActionFunction<Args> = (state: StateDraft, args: Args) => void;
-type DeferredActionFunc<T> =
- T extends ActionFunction<infer Args>
- ? (args: Args) => DeferredAction<Args>
- : never;
-type DeferredActions<C> = {
- [P in keyof C]: DeferredActionFunc<C[P]>;
-};
-
-// Actions is an implementation of DeferredActions<typeof StateActions>.
-// (since StateActions is a variable not a type we have to do
-// 'typeof StateActions' to access the (unnamed) type of StateActions).
-// It's a Proxy such that any attribute access returns a function:
-// (args) => {return {type: ATTRIBUTE_NAME, args};}
-export const Actions =
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- new Proxy<DeferredActions<typeof StateActions>>({} as any, {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- get(_: any, prop: string, _2: any) {
- return (args: {}): DeferredAction<{}> => {
- return {
- type: prop,
- args,
- };
- };
- },
- });
diff --git a/ui/src/common/actions_unittest.ts b/ui/src/common/actions_unittest.ts
deleted file mode 100644
index 50fd8bd..0000000
--- a/ui/src/common/actions_unittest.ts
+++ /dev/null
@@ -1,81 +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 {produce} from 'immer';
-import {assertExists} from '../base/logging';
-import {StateActions} from './actions';
-import {createEmptyState} from './empty_state';
-import {TraceUrlSource} from '../public/trace_source';
-
-test('open trace', () => {
- const state = createEmptyState();
- const recordConfig = state.recordConfig;
- const after = produce(state, (draft) => {
- StateActions.openTraceFromUrl(draft, {
- url: 'https://example.com/bar',
- });
- });
-
- expect(after.engine).not.toBeUndefined();
- expect((after.engine!!.source as TraceUrlSource).url).toBe(
- 'https://example.com/bar',
- );
- expect(after.recordConfig).toBe(recordConfig);
-});
-
-test('open second trace from file', () => {
- const once = produce(createEmptyState(), (draft) => {
- StateActions.openTraceFromUrl(draft, {
- url: 'https://example.com/bar',
- });
- });
-
- const thrice = produce(once, (draft) => {
- StateActions.openTraceFromUrl(draft, {
- url: 'https://example.com/foo',
- });
- });
-
- expect(thrice.engine).not.toBeUndefined();
- expect((thrice.engine!!.source as TraceUrlSource).url).toBe(
- 'https://example.com/foo',
- );
-});
-
-test('setEngineReady with missing engine is ignored', () => {
- const state = createEmptyState();
- produce(state, (draft) => {
- StateActions.setEngineReady(draft, {
- engineId: '1',
- ready: true,
- mode: 'WASM',
- });
- });
-});
-
-test('setEngineReady', () => {
- const state = createEmptyState();
- const after = produce(state, (draft) => {
- StateActions.openTraceFromUrl(draft, {
- url: 'https://example.com/bar',
- });
- const latestEngineId = assertExists(draft.engine).id;
- StateActions.setEngineReady(draft, {
- engineId: latestEngineId,
- ready: true,
- mode: 'WASM',
- });
- });
- expect(after.engine!!.ready).toBe(true);
-});
diff --git a/ui/src/common/add_ephemeral_tab.ts b/ui/src/common/add_ephemeral_tab.ts
index 11798aa..9ef6aca 100644
--- a/ui/src/common/add_ephemeral_tab.ts
+++ b/ui/src/common/add_ephemeral_tab.ts
@@ -12,29 +12,20 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import {uuidv4} from '../base/uuid';
-import {BottomTab} from '../public/lib/bottom_tab';
-import {globals} from '../frontend/globals';
+import {AppImpl} from '../core/app_impl';
import {Tab} from '../public/tab';
-import {BottomTabToTabAdapter} from '../public/utils';
+// TODO(primiano): this method should take a Trace parameter (or probably
+// shouldn't exist at all in favour of some helper in the Trace object).
export function addEphemeralTab(uriPrefix: string, tab: Tab): void {
const uri = `${uriPrefix}#${uuidv4()}`;
- globals.tabManager.registerTab({
+ const tabManager = AppImpl.instance.trace?.tabs;
+ if (tabManager === undefined) return;
+ tabManager.registerTab({
uri,
content: tab,
isEphemeral: true,
});
- globals.tabManager.showTab(uri);
-}
-
-export function addBottomTab(tab: BottomTab, uriPrefix: string): void {
- const uri = `${uriPrefix}#${tab.uuid}`;
-
- globals.tabManager.registerTab({
- uri,
- content: new BottomTabToTabAdapter(tab),
- isEphemeral: true,
- });
- globals.tabManager.showTab(uri);
+ tabManager.showTab(uri);
}
diff --git a/ui/src/common/cache_utils.ts b/ui/src/common/cache_utils.ts
deleted file mode 100644
index a04eb2a..0000000
--- a/ui/src/common/cache_utils.ts
+++ /dev/null
@@ -1,93 +0,0 @@
-// 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 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 {BigintMath} from '../base/bigint_math';
-import {Duration, duration} from '../base/time';
-import {globals} from '../frontend/globals';
-
-// We choose 100000 as the table size to cache as this is roughly the point
-// where SQLite sorts start to become expensive.
-const MIN_TABLE_SIZE_TO_CACHE = 100000;
-
-// Decides, based on the length of the trace and the number of rows
-// provided whether a TrackController subclass should cache its quantized
-// data. Returns the bucket size (in ns) if caching should happen and
-// undefined otherwise.
-export function calcCachedBucketSize(numRows: number): duration | undefined {
- // Ensure that we're not caching when the table size isn't even that big.
- if (numRows < MIN_TABLE_SIZE_TO_CACHE) {
- return undefined;
- }
-
- const traceContext = globals.traceContext;
- const traceDuration = traceContext.end - traceContext.start;
-
- // For large traces, going through the raw table in the most zoomed-out
- // states can be very expensive as this can involve going through O(millions
- // of rows). The cost of this becomes high even for just iteration but is
- // especially slow as quantization involves a SQLite sort on the quantized
- // timestamp (for the group by).
- //
- // To get around this, we can cache a pre-quantized table which we can then
- // in zoomed-out situations and fall back to the real table when zoomed in
- // (which naturally constrains the amount of data by virtue of the window
- // covering a smaller timespan)
- //
- // This method computes that cached table by computing an approximation for
- // the bucket size we would use when totally zoomed out and then going a few
- // resolution levels down which ensures that our cached table works for more
- // than the literally most zoomed out state. Moving down a resolution level
- // is defined as moving down a power of 2; this matches the logic in
- // |globals.getCurResolution|.
- //
- // TODO(lalitm): in the future, we should consider having a whole set of
- // quantized tables each of which cover some portion of resolution lvel
- // range. As each table covers a large number of resolution levels, even 3-4
- // tables should really cover the all concievable trace sizes. This set
- // could be computed by looking at the number of events being processed one
- // level below the cached table and computing another layer of caching if
- // that count is too high (with respect to MIN_TABLE_SIZE_TO_CACHE).
-
- // 4k monitors have 3840 horizontal pixels so use that for a worst case
- // approximation of the window width.
- const approxWidthPx = 3840n;
-
- // Compute the outermost bucket size. This acts as a starting point for
- // computing the cached size.
- const outermostBucketSize = BigintMath.bitCeil(traceDuration / approxWidthPx);
- const outermostResolutionLevel = BigintMath.log2(outermostBucketSize);
-
- // This constant decides how many resolution levels down from our outermost
- // bucket computation we want to be able to use the cached table.
- // We've chosen 7 as it empirically seems to be a good fit for trace data.
- const resolutionLevelsCovered = 7n;
-
- // If we've got less resolution levels in the trace than the number of
- // resolution levels we want to go down, bail out because this cached
- // table is really not going to be used enough.
- if (outermostResolutionLevel < resolutionLevelsCovered) {
- return Duration.MAX;
- }
-
- // Another way to look at moving down resolution levels is to consider how
- // many sub-intervals we are splitting the bucket into.
- const bucketSubIntervals = 1n << resolutionLevelsCovered;
-
- // Calculate the smallest bucket we want our table to be able to handle by
- // dividing the outermsot bucket by the number of subintervals we should
- // divide by.
- const cachedBucketSize = outermostBucketSize / bucketSubIntervals;
-
- return cachedBucketSize;
-}
diff --git a/ui/src/common/conversion_jobs.ts b/ui/src/common/conversion_jobs.ts
deleted file mode 100644
index 6805bc7..0000000
--- a/ui/src/common/conversion_jobs.ts
+++ /dev/null
@@ -1,30 +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 enum ConversionJobStatus {
- InProgress = 'InProgress',
- NotRunning = 'NotRunning',
-}
-
-export type ConversionJobName =
- | 'convert_systrace'
- | 'convert_json'
- | 'open_in_legacy'
- | 'convert_pprof'
- | 'create_permalink';
-
-export interface ConversionJobStatusUpdate {
- jobName: ConversionJobName;
- jobStatus: ConversionJobStatus;
-}
diff --git a/ui/src/common/empty_state.ts b/ui/src/common/empty_state.ts
deleted file mode 100644
index d31d94e..0000000
--- a/ui/src/common/empty_state.ts
+++ /dev/null
@@ -1,82 +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.
-
-import {Time} from '../base/time';
-import {createEmptyRecordConfig} from '../controller/record_config_types';
-import {featureFlags} from '../core/feature_flags';
-import {
- autosaveConfigStore,
- recordTargetStore,
-} from '../frontend/record_config';
-import {State, STATE_VERSION} from './state';
-
-const AUTOLOAD_STARTED_CONFIG_FLAG = featureFlags.register({
- id: 'autoloadStartedConfig',
- name: 'Auto-load last used recording config',
- description:
- 'Starting a recording automatically saves its configuration. ' +
- 'This flag controls whether this config is automatically loaded.',
- defaultValue: true,
-});
-
-export function keyedMap<T>(
- keyFn: (key: T) => string,
- ...values: T[]
-): Map<string, T> {
- const result = new Map<string, T>();
-
- for (const value of values) {
- result.set(keyFn(value), value);
- }
-
- return result;
-}
-
-export function createEmptyState(): State {
- return {
- version: STATE_VERSION,
- nextId: '-1',
- newEngineMode: 'USE_HTTP_RPC_IF_AVAILABLE',
- queries: {},
-
- recordConfig: AUTOLOAD_STARTED_CONFIG_FLAG.get()
- ? autosaveConfigStore.get()
- : createEmptyRecordConfig(),
- displayConfigAsPbtxt: false,
- lastLoadedConfig: {type: 'NONE'},
-
- status: {msg: '', timestamp: 0},
- traceConversionInProgress: false,
-
- perfDebug: false,
- sidebarVisible: true,
- hoveredUtid: -1,
- hoveredPid: -1,
- hoveredNoteTimestamp: Time.INVALID,
- highlightedSliceId: -1,
-
- recordingInProgress: false,
- recordingCancelled: false,
- extensionInstalled: false,
- flamegraphModalDismissed: false,
- recordingTarget: recordTargetStore.getValidTarget(),
- availableAdbDevices: [],
-
- fetchChromeCategories: false,
- chromeCategories: undefined,
-
- trackFilterTerm: undefined,
- forceRunControllers: 0,
- };
-}
diff --git a/ui/src/common/plugins.ts b/ui/src/common/plugins.ts
deleted file mode 100644
index 47e4588..0000000
--- a/ui/src/common/plugins.ts
+++ /dev/null
@@ -1,267 +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 {Registry} from '../base/registry';
-import {Trace} from '../public/trace';
-import {App} from '../public/app';
-import {MetricVisualisation} from '../public/plugin';
-import {PerfettoPlugin, PluginDescriptor} from '../public/plugin';
-import {Flag, featureFlags} from '../core/feature_flags';
-import {assertExists, assertTrue} from '../base/logging';
-import {raf} from '../core/raf_scheduler';
-import {defaultPlugins} from '../core/default_plugins';
-import {TraceImpl} from '../core/trace_impl';
-import {AppImpl, CORE_PLUGIN_ID} from '../core/app_impl';
-
-// 'Static' registry of all known plugins.
-export class PluginRegistry extends Registry<PluginDescriptor> {
- constructor() {
- super((info) => info.pluginId);
- }
-}
-
-export interface PluginDetails {
- plugin: PerfettoPlugin;
- app: App;
- trace?: Trace;
- previousOnTraceLoadTimeMillis?: number;
-}
-
-function makePlugin(info: PluginDescriptor): PerfettoPlugin {
- const {plugin} = info;
-
- // Class refs are functions, concrete plugins are not
- if (typeof plugin === 'function') {
- const PluginClass = plugin;
- return new PluginClass();
- } else {
- return plugin;
- }
-}
-
-export class PluginManager {
- private registry: PluginRegistry;
- private _plugins: Map<string, PluginDetails>;
- private flags = new Map<string, Flag>();
- private _needsRestart = false;
-
- constructor(registry: PluginRegistry) {
- this.registry = registry;
- this._plugins = new Map();
- }
-
- get plugins(): Map<string, PluginDetails> {
- return this._plugins;
- }
-
- // Must only be called once on startup
- async initialize(): Promise<void> {
- for (const {pluginId} of pluginRegistry.values()) {
- const flagId = `plugin_${pluginId}`;
- const name = `Plugin: ${pluginId}`;
- const flag = featureFlags.register({
- id: flagId,
- name,
- description: `Overrides '${pluginId}' plugin.`,
- defaultValue: defaultPlugins.includes(pluginId),
- });
- this.flags.set(pluginId, flag);
- if (flag.get()) {
- await this.activatePlugin(pluginId);
- }
- }
- }
-
- /**
- * Enable plugin flag - i.e. configure a plugin to start on boot.
- * @param id The ID of the plugin.
- */
- async enablePlugin(id: string): Promise<void> {
- const flag = this.flags.get(id);
- if (flag) {
- flag.set(true);
- }
- await this.activatePlugin(id);
- }
-
- /**
- * Disable plugin flag - i.e. configure a plugin not to start on boot.
- * @param id The ID of the plugin.
- */
- async disablePlugin(id: string): Promise<void> {
- const flag = this.flags.get(id);
- if (flag) {
- flag.set(false);
- }
- this._needsRestart = true;
- }
-
- /**
- * Start a plugin just for this session. This setting is not persisted.
- * @param id The ID of the plugin to start.
- */
- async activatePlugin(id: string): Promise<void> {
- if (this.isActive(id)) {
- return;
- }
-
- const pluginInfo = this.registry.get(id);
- const plugin = makePlugin(pluginInfo);
-
- const app = AppImpl.instance.forkForPlugin(id);
-
- plugin.onActivate?.(app);
-
- const pluginDetails: PluginDetails = {plugin, app};
-
- // If a trace is already loaded when plugin is activated, make sure to
- // call onTraceLoad().
- const maybeTrace = AppImpl.instance.trace;
- if (maybeTrace !== undefined) {
- await doPluginTraceLoad(pluginDetails, maybeTrace);
- await doPluginTraceReady(pluginDetails);
- }
-
- this._plugins.set(id, pluginDetails);
-
- raf.scheduleFullRedraw();
- }
-
- /**
- * Restore all plugins enable/disabled flags to their default values.
- * Also activates new plugins to match flag settings.
- */
- async restoreDefaults(): Promise<void> {
- for (const plugin of pluginRegistry.values()) {
- const pluginId = plugin.pluginId;
- const flag = assertExists(this.flags.get(pluginId));
- flag.reset();
- if (flag.get()) {
- await this.activatePlugin(plugin.pluginId);
- } else {
- this._needsRestart = true;
- }
- }
- }
-
- hasPlugin(pluginId: string): boolean {
- return pluginRegistry.has(pluginId);
- }
-
- isActive(pluginId: string): boolean {
- return this.getPluginContext(pluginId) !== undefined;
- }
-
- isEnabled(pluginId: string): boolean {
- return Boolean(this.flags.get(pluginId)?.get());
- }
-
- getPluginContext(pluginId: string): PluginDetails | undefined {
- return this._plugins.get(pluginId);
- }
-
- // NOTE: here we take as argument the TraceImpl for the core. This is because
- // we pass it to doPluginTraceLoad() which uses to call forkForPlugin(id) and
- // derive a per-plugin instance.
- async onTraceLoad(
- traceCore: TraceImpl,
- beforeEach?: (id: string) => void,
- ): Promise<void> {
- assertTrue(traceCore.pluginId === CORE_PLUGIN_ID);
-
- // Awaiting all plugins in parallel will skew timing data as later plugins
- // will spend most of their time waiting for earlier plugins to load.
- // Running in parallel will have very little performance benefit assuming
- // most plugins use the same engine, which can only process one query at a
- // time.
- for (const [id, plugin] of this._plugins.entries()) {
- beforeEach?.(id);
- await doPluginTraceLoad(plugin, traceCore);
- }
- }
-
- async onTraceReady(): Promise<void> {
- const pluginsShuffled = Array.from(this._plugins.values())
- .map((plugin) => ({plugin, sort: Math.random()}))
- .sort((a, b) => a.sort - b.sort);
-
- for (const {plugin} of pluginsShuffled) {
- await doPluginTraceReady(plugin);
- }
- }
-
- onTraceClose() {
- for (const pluginDetails of this._plugins.values()) {
- doPluginTraceUnload(pluginDetails);
- }
- }
-
- metricVisualisations(): MetricVisualisation[] {
- return Array.from(this._plugins.values()).flatMap((ctx) => {
- const tracePlugin = ctx.plugin;
- if (tracePlugin.metricVisualisations) {
- return tracePlugin.metricVisualisations(ctx.app);
- } else {
- return [];
- }
- });
- }
-
- get needsRestart() {
- return this._needsRestart;
- }
-}
-
-async function doPluginTraceReady(pluginDetails: PluginDetails): Promise<void> {
- const {plugin, trace: traceContext} = pluginDetails;
- await Promise.resolve(plugin.onTraceReady?.(assertExists(traceContext)));
- raf.scheduleFullRedraw();
-}
-
-async function doPluginTraceLoad(
- pluginDetails: PluginDetails,
- traceCore: TraceImpl,
-): Promise<void> {
- assertTrue(traceCore.pluginId === CORE_PLUGIN_ID);
- assertTrue(pluginDetails.app.pluginId !== CORE_PLUGIN_ID);
- const {plugin} = pluginDetails;
- const trace = traceCore.forkForPlugin(pluginDetails.app.pluginId);
-
- pluginDetails.trace = trace;
-
- const startTime = performance.now();
- await Promise.resolve(plugin.onTraceLoad?.(trace));
- const loadTime = performance.now() - startTime;
- pluginDetails.previousOnTraceLoadTimeMillis = loadTime;
-
- raf.scheduleFullRedraw();
-}
-
-async function doPluginTraceUnload(
- pluginDetails: PluginDetails,
-): Promise<void> {
- const {trace, plugin} = pluginDetails;
-
- if (trace) {
- plugin.onTraceUnload && (await plugin.onTraceUnload(trace));
- pluginDetails.trace = undefined;
- // All the disposable resources created by plugins are appeneded to the
- // per-trace (not per-plugin) trash. There is no need of per-plugin dispose
- // call.
- }
-}
-
-// TODO(hjd): Sort out the story for global singletons like these:
-export const pluginRegistry = new PluginRegistry();
-export const pluginManager = new PluginManager(pluginRegistry);
diff --git a/ui/src/common/plugins_unittest.ts b/ui/src/common/plugins_unittest.ts
deleted file mode 100644
index 9be40f1..0000000
--- a/ui/src/common/plugins_unittest.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-// 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 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 {PerfettoPlugin} from '../public/plugin';
-import {createFakeTraceImpl} from './fake_trace_impl';
-import {PluginManager, PluginRegistry} from './plugins';
-
-function makeMockPlugin(): PerfettoPlugin {
- return {
- onActivate: jest.fn(),
- onTraceLoad: jest.fn(),
- onTraceUnload: jest.fn(),
- };
-}
-
-let mockPlugin: PerfettoPlugin;
-let manager: PluginManager;
-
-describe('PluginManger', () => {
- beforeEach(() => {
- mockPlugin = makeMockPlugin();
- const registry = new PluginRegistry();
- registry.register({
- pluginId: 'foo',
- plugin: mockPlugin,
- });
- manager = new PluginManager(registry);
- });
-
- it('can activate plugin', async () => {
- await manager.activatePlugin('foo');
-
- expect(manager.isActive('foo')).toBe(true);
- expect(mockPlugin.onActivate).toHaveBeenCalledTimes(1);
- });
-
- it('invokes onTraceLoad when trace is loaded', async () => {
- await manager.activatePlugin('foo');
- await manager.onTraceLoad(createFakeTraceImpl());
-
- expect(mockPlugin.onTraceLoad).toHaveBeenCalledTimes(1);
- });
-
- it('invokes onTraceLoad when plugin activated while trace loaded', async () => {
- await manager.onTraceLoad(createFakeTraceImpl());
- await manager.activatePlugin('foo');
-
- expect(mockPlugin.onTraceLoad).toHaveBeenCalledTimes(1);
- });
-});
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/common/schema.ts b/ui/src/common/schema.ts
deleted file mode 100644
index c3f33e4..0000000
--- a/ui/src/common/schema.ts
+++ /dev/null
@@ -1,119 +0,0 @@
-// 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 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 {Engine} from '../trace_processor/engine';
-import {STR} from '../trace_processor/query_result';
-
-const CACHED_SCHEMAS = new WeakMap<Engine, DatabaseSchema>();
-
-export class SchemaError extends Error {
- constructor(message: string) {
- super(message);
- }
-}
-
-// POJO representing the table structure of trace_processor.
-// Exposed for testing.
-export interface DatabaseInfo {
- tables: TableInfo[];
-}
-
-interface TableInfo {
- name: string;
- parent?: TableInfo;
- columns: ColumnInfo[];
-}
-
-interface ColumnInfo {
- name: string;
-}
-
-async function getColumns(
- engine: Engine,
- table: string,
-): Promise<ColumnInfo[]> {
- const result = await engine.query(`PRAGMA table_info(${table});`);
- const it = result.iter({
- name: STR,
- });
- const columns = [];
- for (; it.valid(); it.next()) {
- columns.push({name: it['name']});
- }
- return columns;
-}
-
-// Opinionated view on the schema of the given trace_processor instance
-// suitable for EventSets to use for query generation.
-export class DatabaseSchema {
- private tableToKeys: Map<string, Set<string>>;
-
- constructor(info: DatabaseInfo) {
- this.tableToKeys = new Map();
- for (const tableInfo of info.tables) {
- const columns = new Set(tableInfo.columns.map((c) => c.name));
- this.tableToKeys.set(tableInfo.name, columns);
- }
- }
-
- // Return all the EventSet keys available for a given table. This
- // includes the direct columns on the table (and all parent tables)
- // as well as all direct and indirect joinable tables where the join
- // is N:1 or 1:1. e.g. for the table thread_slice we also include
- // the columns from thread, process, thread_track etc.
- getKeys(tableName: string): Set<string> {
- const columns = this.tableToKeys.get(tableName);
- if (columns === undefined) {
- throw new SchemaError(`Unknown table '${tableName}'`);
- }
- return columns;
- }
-}
-
-// Deliberately not exported. Users should call getSchema below and
-// participate in cacheing.
-async function createSchema(engine: Engine): Promise<DatabaseSchema> {
- const tables: TableInfo[] = [];
- const result = await engine.query(`SELECT name from perfetto_tables;`);
- const it = result.iter({
- name: STR,
- });
- for (; it.valid(); it.next()) {
- const name = it['name'];
- tables.push({
- name,
- columns: await getColumns(engine, name),
- });
- }
-
- const database: DatabaseInfo = {
- tables,
- };
-
- return new DatabaseSchema(database);
-}
-
-// Get the schema for the given engine (from the cache if possible).
-// The schemas are per-engine (i.e. they can't be statically determined
-// at build time) since we might be in httpd mode and not-running
-// against the version of trace_processor we build with.
-export async function getSchema(engine: Engine): Promise<DatabaseSchema> {
- const schema = CACHED_SCHEMAS.get(engine);
- if (schema === undefined) {
- const newSchema = await createSchema(engine);
- CACHED_SCHEMAS.set(engine, newSchema);
- return newSchema;
- }
- return schema;
-}
diff --git a/ui/src/common/schema_unittest.ts b/ui/src/common/schema_unittest.ts
deleted file mode 100644
index f0a6a58..0000000
--- a/ui/src/common/schema_unittest.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-// 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 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 {DatabaseInfo, DatabaseSchema, SchemaError} from './schema';
-
-test('DatabaseSchema > getKeys', () => {
- const info: DatabaseInfo = {
- tables: [
- {
- name: 'slice',
- columns: [{name: 'id'}, {name: 'ts'}, {name: 'dur'}],
- },
- ],
- };
- const schema = new DatabaseSchema(info);
- expect(schema.getKeys('slice')).toEqual(new Set(['id', 'ts', 'dur']));
-});
-
-test('DatabaseSchema > getKeys > Sad path', () => {
- const info: DatabaseInfo = {
- tables: [],
- };
- const schema = new DatabaseSchema(info);
- expect(() => schema.getKeys('foo')).toThrow(SchemaError);
-});
diff --git a/ui/src/common/track_helper.ts b/ui/src/common/track_helper.ts
index 3087228..e9ef6fb 100644
--- a/ui/src/common/track_helper.ts
+++ b/ui/src/common/track_helper.ts
@@ -95,6 +95,6 @@
const {start, end} = this.latestTimespan;
const resolution = this.latestResolution;
this.data_ = await this.doFetch(start, end, resolution);
- raf.scheduleRedraw();
+ raf.scheduleCanvasRedraw();
}
}
diff --git a/ui/src/controller/app_controller.ts b/ui/src/controller/app_controller.ts
deleted file mode 100644
index fa2e910..0000000
--- a/ui/src/controller/app_controller.ts
+++ /dev/null
@@ -1,53 +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 {RECORDING_V2_FLAG} from '../core/feature_flags';
-import {globals} from '../frontend/globals';
-import {Child, Controller, ControllerInitializerAny} from './controller';
-import {RecordController} from './record_controller';
-import {TraceController} from './trace_controller';
-
-// The root controller for the entire app. It handles the lifetime of all
-// the other controllers (e.g., track and query controllers) according to the
-// global state.
-export class AppController extends Controller<'main'> {
- // extensionPort is needed for the RecordController to communicate with the
- // extension through the frontend. This is because the controller is running
- // on a worker, and isn't able to directly send messages to the extension.
- private extensionPort: MessagePort;
-
- constructor(extensionPort: MessagePort) {
- super('main');
- this.extensionPort = extensionPort;
- }
-
- // This is the root method that is called every time the controller tree is
- // re-triggered. This can happen due to:
- // - An action received from the frontend.
- // - An internal promise of a nested controller being resolved and manually
- // re-triggering the controllers.
- run() {
- const childControllers: ControllerInitializerAny[] = [];
- if (!RECORDING_V2_FLAG.get()) {
- childControllers.push(
- Child('record', RecordController, {extensionPort: this.extensionPort}),
- );
- }
- if (globals.state.engine !== undefined) {
- const engineCfg = globals.state.engine;
- childControllers.push(Child(engineCfg.id, TraceController, engineCfg.id));
- }
- return childControllers;
- }
-}
diff --git a/ui/src/controller/controller.ts b/ui/src/controller/controller.ts
deleted file mode 100644
index b70ed4f..0000000
--- a/ui/src/controller/controller.ts
+++ /dev/null
@@ -1,116 +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.
-
-export type ControllerAny = Controller</* StateType=*/ unknown>;
-
-export interface ControllerFactory<ConstructorArgs> {
- new (args: ConstructorArgs): ControllerAny;
-}
-
-interface ControllerInitializer<ConstructorArgs> {
- id: string;
- factory: ControllerFactory<ConstructorArgs>;
- args: ConstructorArgs;
-}
-
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-export type ControllerInitializerAny = ControllerInitializer<any>;
-
-export function Child<ConstructorArgs>(
- id: string,
- factory: ControllerFactory<ConstructorArgs>,
- args: ConstructorArgs,
-): ControllerInitializer<ConstructorArgs> {
- return {id, factory, args};
-}
-
-export type Children = ControllerInitializerAny[];
-
-export abstract class Controller<StateType> {
- // This is about the local FSM state, has nothing to do with the global
- // app state.
- private _stateChanged = false;
- private _inRunner = false;
- private _state: StateType;
- private _children = new Map<string, ControllerAny>();
-
- constructor(initialState: StateType) {
- this._state = initialState;
- }
-
- abstract run(): Children | void;
- onDestroy(): void {}
-
- // Invokes the current controller subtree, recursing into children.
- // While doing so handles lifecycle of child controllers.
- // This method should be called only by the runControllers() method in
- // globals.ts. Exposed publicly for testing.
- invoke(): boolean {
- if (this._inRunner) throw new Error('Reentrancy in Controller');
- this._stateChanged = false;
- this._inRunner = true;
- const resArray = this.run();
- let triggerAnotherRun = this._stateChanged;
- this._stateChanged = false;
-
- const nextChildren = new Map<string, ControllerInitializerAny>();
- if (resArray !== undefined) {
- for (const childConfig of resArray) {
- if (nextChildren.has(childConfig.id)) {
- throw new Error(`Duplicate children controller ${childConfig.id}`);
- }
- nextChildren.set(childConfig.id, childConfig);
- }
- }
- const dtors = new Array<() => void>();
- const runners = new Array<() => boolean>();
- for (const key of this._children.keys()) {
- if (nextChildren.has(key)) continue;
- const instance = this._children.get(key)!;
- this._children.delete(key);
- dtors.push(() => instance.onDestroy());
- }
- for (const nextChild of nextChildren.values()) {
- if (!this._children.has(nextChild.id)) {
- const instance = new nextChild.factory(nextChild.args);
- this._children.set(nextChild.id, instance);
- }
- const instance = this._children.get(nextChild.id)!;
- runners.push(() => instance.invoke());
- }
-
- for (const dtor of dtors) dtor(); // Invoke all onDestroy()s.
-
- // Invoke all runner()s.
- for (const runner of runners) {
- const recursiveRes = runner();
- triggerAnotherRun = triggerAnotherRun || recursiveRes;
- }
-
- this._inRunner = false;
- return triggerAnotherRun;
- }
-
- setState(state: StateType) {
- if (!this._inRunner) {
- throw new Error('Cannot setState() outside of the run() method');
- }
- this._stateChanged = state !== this._state;
- this._state = state;
- }
-
- get state(): StateType {
- return this._state;
- }
-}
diff --git a/ui/src/controller/controller_unittest.ts b/ui/src/controller/controller_unittest.ts
deleted file mode 100644
index ff5a558..0000000
--- a/ui/src/controller/controller_unittest.ts
+++ /dev/null
@@ -1,154 +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 {Child, Controller} from './controller';
-
-const _onCreate = jest.fn();
-const _onDestroy = jest.fn();
-const _run = jest.fn();
-
-type MockStates = 'idle' | 'state1' | 'state2' | 'state3';
-class MockController extends Controller<MockStates> {
- constructor(public type: string) {
- super('idle');
- _onCreate(this.type);
- }
-
- run() {
- return _run(this.type);
- }
-
- onDestroy() {
- return _onDestroy(this.type);
- }
-}
-
-function runControllerTree(rootController: MockController): void {
- for (let runAgain = true, i = 0; runAgain; i++) {
- if (i >= 100) throw new Error('Controller livelock');
- runAgain = rootController.invoke();
- }
-}
-
-beforeEach(() => {
- _onCreate.mockClear();
- _onCreate.mockReset();
- _onDestroy.mockClear();
- _onDestroy.mockReset();
- _run.mockClear();
- _run.mockReset();
-});
-
-test('singleControllerNoTransition', () => {
- const rootCtl = new MockController('root');
- runControllerTree(rootCtl);
- expect(_run).toHaveBeenCalledTimes(1);
- expect(_run).toHaveBeenCalledWith('root');
-});
-
-test('singleControllerThreeTransitions', () => {
- const rootCtl = new MockController('root');
- _run.mockImplementation(() => {
- if (rootCtl.state === 'idle') {
- rootCtl.setState('state1');
- } else if (rootCtl.state === 'state1') {
- rootCtl.setState('state2');
- }
- });
- runControllerTree(rootCtl);
- expect(_run).toHaveBeenCalledTimes(3);
- expect(_run).toHaveBeenCalledWith('root');
-});
-
-test('nestedControllers', () => {
- const rootCtl = new MockController('root');
- let nextState: MockStates = 'idle';
- _run.mockImplementation((type: string) => {
- if (type !== 'root') return;
- rootCtl.setState(nextState);
- if (rootCtl.state === 'idle') return;
-
- if (rootCtl.state === 'state1') {
- return [Child('child1', MockController, 'child1')];
- }
- if (rootCtl.state === 'state2') {
- return [
- Child('child1', MockController, 'child1'),
- Child('child2', MockController, 'child2'),
- ];
- }
- if (rootCtl.state === 'state3') {
- return [
- Child('child1', MockController, 'child1'),
- Child('child3', MockController, 'child3'),
- ];
- }
- throw new Error('Not reached');
- });
- runControllerTree(rootCtl);
- expect(_run).toHaveBeenCalledWith('root');
- expect(_run).toHaveBeenCalledTimes(1);
-
- // Transition the root controller to state1. This will create the first child
- // and re-run both (because of the idle -> state1 transition).
- _run.mockClear();
- _onCreate.mockClear();
- nextState = 'state1';
- runControllerTree(rootCtl);
- expect(_onCreate).toHaveBeenCalledWith('child1');
- expect(_onCreate).toHaveBeenCalledTimes(1);
- expect(_run).toHaveBeenCalledWith('root');
- expect(_run).toHaveBeenCalledWith('child1');
- expect(_run).toHaveBeenCalledTimes(4);
-
- // Transition the root controller to state2. This will create the 2nd child
- // and run the three of them (root + 2 chilren) two times.
- _run.mockClear();
- _onCreate.mockClear();
- nextState = 'state2';
- runControllerTree(rootCtl);
- expect(_onCreate).toHaveBeenCalledWith('child2');
- expect(_onCreate).toHaveBeenCalledTimes(1);
- expect(_run).toHaveBeenCalledWith('root');
- expect(_run).toHaveBeenCalledWith('child1');
- expect(_run).toHaveBeenCalledWith('child2');
- expect(_run).toHaveBeenCalledTimes(6);
-
- // Transition the root controller to state3. This will create the 3rd child
- // and remove the 2nd one.
- _run.mockClear();
- _onCreate.mockClear();
- nextState = 'state3';
- runControllerTree(rootCtl);
- expect(_onCreate).toHaveBeenCalledWith('child3');
- expect(_onDestroy).toHaveBeenCalledWith('child2');
- expect(_onCreate).toHaveBeenCalledTimes(1);
- expect(_run).toHaveBeenCalledWith('root');
- expect(_run).toHaveBeenCalledWith('child1');
- expect(_run).toHaveBeenCalledWith('child3');
- expect(_run).toHaveBeenCalledTimes(6);
-
- // Finally transition back to the idle state. All children should be removed.
- _run.mockClear();
- _onCreate.mockClear();
- _onDestroy.mockClear();
- nextState = 'idle';
- runControllerTree(rootCtl);
- expect(_onDestroy).toHaveBeenCalledWith('child1');
- expect(_onDestroy).toHaveBeenCalledWith('child3');
- expect(_onCreate).toHaveBeenCalledTimes(0);
- expect(_onDestroy).toHaveBeenCalledTimes(2);
- expect(_run).toHaveBeenCalledWith('root');
- expect(_run).toHaveBeenCalledTimes(2);
-});
diff --git a/ui/src/controller/index.ts b/ui/src/controller/index.ts
deleted file mode 100644
index e451a36..0000000
--- a/ui/src/controller/index.ts
+++ /dev/null
@@ -1,42 +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 '../common/recordingV2/target_factories';
-import {assertExists, assertTrue} from '../base/logging';
-import {AppController} from './app_controller';
-import {ControllerAny} from './controller';
-
-let rootController: ControllerAny;
-let runningControllers = false;
-
-export function initController(extensionPort: MessagePort) {
- assertTrue(rootController === undefined);
- rootController = new AppController(extensionPort);
-}
-
-export function runControllers() {
- if (runningControllers) throw new Error('Re-entrant call detected');
-
- // Run controllers locally until all state machines reach quiescence.
- let runAgain = true;
- for (let iter = 0; runAgain; iter++) {
- if (iter > 100) throw new Error('Controllers are stuck in a livelock');
- runningControllers = true;
- try {
- runAgain = assertExists(rootController).invoke();
- } finally {
- runningControllers = false;
- }
- }
-}
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
deleted file mode 100644
index be5e139..0000000
--- a/ui/src/controller/trace_controller.ts
+++ /dev/null
@@ -1,1073 +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 {assertExists, assertTrue} from '../base/logging';
-import {Duration, time, Time, TimeSpan} from '../base/time';
-import {Actions, DeferredAction} from '../common/actions';
-import {cacheTrace} from '../common/cache_manager';
-import {
- getEnabledMetatracingCategories,
- isMetatracingEnabled,
-} from '../common/metatracing';
-import {pluginManager} from '../common/plugins';
-import {EngineConfig, EngineMode, PendingDeeplinkState} from '../common/state';
-import {featureFlags, Flag} from '../core/feature_flags';
-import {globals, QuantizedLoad, ThreadDesc} from '../frontend/globals';
-import {
- clearOverviewData,
- publishHasFtrace,
- publishMetricError,
- publishOverviewData,
- publishThreads,
-} from '../frontend/publish';
-import {addQueryResultsTab} from '../public/lib/query_table/query_result_tab';
-import {Router} from '../frontend/router';
-import {Engine, EngineBase} from '../trace_processor/engine';
-import {HttpRpcEngine} from '../trace_processor/http_rpc_engine';
-import {
- LONG,
- LONG_NULL,
- NUM,
- NUM_NULL,
- QueryError,
- STR,
- STR_NULL,
-} from '../trace_processor/query_result';
-import {
- resetEngineWorker,
- WasmEngineProxy,
-} from '../trace_processor/wasm_engine_proxy';
-import {Controller} from './controller';
-import {
- TraceBufferStream,
- TraceFileStream,
- TraceHttpStream,
- TraceStream,
-} from '../core/trace_stream';
-import {decideTracks} from './track_decider';
-import {
- deserializeAppStatePhase1,
- deserializeAppStatePhase2,
-} from '../common/state_serialization';
-import {TraceInfo} from '../public/trace_info';
-import {AppImpl} from '../core/app_impl';
-import {raf} from '../core/raf_scheduler';
-import {TraceImpl} from '../core/trace_impl';
-
-type States = 'init' | 'loading_trace' | 'ready';
-
-const METRICS = [
- 'android_ion',
- 'android_lmk',
- 'android_surfaceflinger',
- 'android_batt',
- 'android_other_traces',
- 'chrome_dropped_frames',
- // TODO(289365196): Reenable:
- // 'chrome_long_latency',
- 'android_trusty_workqueues',
-];
-const FLAGGED_METRICS: Array<[Flag, string]> = METRICS.map((m) => {
- const id = `forceMetric${m}`;
- let name = m.split('_').join(' ');
- name = name[0].toUpperCase() + name.slice(1);
- name = 'Metric: ' + name;
- const flag = featureFlags.register({
- id,
- name,
- description: `Overrides running the '${m}' metric at import time.`,
- defaultValue: true,
- });
- return [flag, m];
-});
-
-const ENABLE_CHROME_RELIABLE_RANGE_ZOOM_FLAG = featureFlags.register({
- id: 'enableChromeReliableRangeZoom',
- name: 'Enable Chrome reliable range zoom',
- description: 'Automatically zoom into the reliable range for Chrome traces',
- defaultValue: false,
-});
-
-const ENABLE_CHROME_RELIABLE_RANGE_ANNOTATION_FLAG = featureFlags.register({
- id: 'enableChromeReliableRangeAnnotation',
- name: 'Enable Chrome reliable range annotation',
- description: 'Automatically adds an annotation for the reliable range start',
- defaultValue: false,
-});
-
-// The following flags control TraceProcessor Config.
-const CROP_TRACK_EVENTS_FLAG = featureFlags.register({
- id: 'cropTrackEvents',
- name: 'Crop track events',
- description: 'Ignores track events outside of the range of interest',
- defaultValue: false,
-});
-const INGEST_FTRACE_IN_RAW_TABLE_FLAG = featureFlags.register({
- id: 'ingestFtraceInRawTable',
- name: 'Ingest ftrace in raw table',
- description: 'Enables ingestion of typed ftrace events into the raw table',
- defaultValue: true,
-});
-const ANALYZE_TRACE_PROTO_CONTENT_FLAG = featureFlags.register({
- id: 'analyzeTraceProtoContent',
- name: 'Analyze trace proto content',
- description:
- 'Enables trace proto content analysis (experimental_proto_content table)',
- defaultValue: false,
-});
-const FTRACE_DROP_UNTIL_FLAG = featureFlags.register({
- id: 'ftraceDropUntilAllCpusValid',
- name: 'Crop ftrace events',
- description:
- 'Drop ftrace events until all per-cpu data streams are known to be valid',
- defaultValue: true,
-});
-
-// TODO(stevegolton): Move this into some global "SQL extensions" file and
-// ensure it's only run once.
-async function defineMaxLayoutDepthSqlFunction(engine: Engine): Promise<void> {
- await engine.query(`
- create perfetto function __max_layout_depth(track_count INT, track_ids STRING)
- returns INT AS
- select iif(
- $track_count = 1,
- (
- select max_depth
- from _slice_track_summary
- where id = cast($track_ids AS int)
- ),
- (
- select max(layout_depth)
- from experimental_slice_layout($track_ids)
- )
- );
- `);
-}
-
-// TraceController handles handshakes with the frontend for everything that
-// concerns a single trace. It owns the WASM trace processor engine, handles
-// tracks data and SQL queries. There is one TraceController instance for each
-// trace opened in the UI (for now only one trace is supported).
-export class TraceController extends Controller<States> {
- private readonly engineId: string;
- private engine?: EngineBase;
-
- constructor(engineId: string) {
- super('init');
- this.engineId = engineId;
- }
-
- run() {
- const engineCfg = assertExists(globals.state.engine);
- switch (this.state) {
- case 'init':
- this.loadTrace()
- .then((mode) => {
- globals.dispatch(
- Actions.setEngineReady({
- engineId: this.engineId,
- ready: true,
- mode,
- }),
- );
- })
- .catch((err) => {
- this.updateStatus(`${err}`);
- throw err;
- });
- this.updateStatus('Opening trace');
- this.setState('loading_trace');
- break;
-
- case 'loading_trace':
- // Stay in this state until loadTrace() returns and marks the engine as
- // ready.
- if (this.engine === undefined || !engineCfg.ready) return;
- this.setState('ready');
- break;
-
- case 'ready':
- return [];
-
- default:
- throw new Error(`unknown state ${this.state}`);
- }
- return;
- }
-
- onDestroy() {
- pluginManager.onTraceClose();
- AppImpl.instance.closeCurrentTrace();
- globals.engines.delete(this.engineId);
- }
-
- private async loadTrace(): Promise<EngineMode> {
- this.updateStatus('Creating trace processor');
- // Check if there is any instance of the trace_processor_shell running in
- // HTTP RPC mode (i.e. trace_processor_shell -D).
- let engineMode: EngineMode;
- let useRpc = false;
- if (globals.state.newEngineMode === 'USE_HTTP_RPC_IF_AVAILABLE') {
- useRpc = (await HttpRpcEngine.checkConnection()).connected;
- }
- let engine;
- if (useRpc) {
- console.log('Opening trace using native accelerator over HTTP+RPC');
- engineMode = 'HTTP_RPC';
- engine = new HttpRpcEngine(this.engineId);
- engine.errorHandler = (err) => {
- globals.dispatch(
- Actions.setEngineFailed({mode: 'HTTP_RPC', failure: `${err}`}),
- );
- throw err;
- };
- } else {
- console.log('Opening trace using built-in WASM engine');
- engineMode = 'WASM';
- const enginePort = resetEngineWorker();
- engine = new WasmEngineProxy(this.engineId, enginePort);
- engine.resetTraceProcessor({
- cropTrackEvents: CROP_TRACK_EVENTS_FLAG.get(),
- ingestFtraceInRawTable: INGEST_FTRACE_IN_RAW_TABLE_FLAG.get(),
- analyzeTraceProtoContent: ANALYZE_TRACE_PROTO_CONTENT_FLAG.get(),
- ftraceDropUntilAllCpusValid: FTRACE_DROP_UNTIL_FLAG.get(),
- });
- }
- engine.onResponseReceived = () => raf.scheduleFullRedraw();
- this.engine = engine;
-
- if (isMetatracingEnabled()) {
- this.engine.enableMetatrace(
- assertExists(getEnabledMetatracingCategories()),
- );
- }
-
- globals.engines.set(this.engineId, engine);
- globals.dispatch(
- Actions.setEngineReady({
- engineId: this.engineId,
- ready: false,
- mode: engineMode,
- }),
- );
- const engineCfg = assertExists(globals.state.engine);
- assertTrue(engineCfg.id === this.engineId);
- let traceStream: TraceStream | undefined;
- if (engineCfg.source.type === 'FILE') {
- traceStream = new TraceFileStream(engineCfg.source.file);
- } else if (engineCfg.source.type === 'ARRAY_BUFFER') {
- traceStream = new TraceBufferStream(engineCfg.source.buffer);
- } else if (engineCfg.source.type === 'URL') {
- traceStream = new TraceHttpStream(engineCfg.source.url);
- } else if (engineCfg.source.type === 'HTTP_RPC') {
- traceStream = undefined;
- } else {
- throw new Error(`Unknown source: ${JSON.stringify(engineCfg.source)}`);
- }
-
- // |traceStream| can be undefined in the case when we are using the external
- // HTTP+RPC endpoint and the trace processor instance has already loaded
- // a trace (because it was passed as a cmdline argument to
- // trace_processor_shell). In this case we don't want the UI to load any
- // file/stream and we just want to jump to the loading phase.
- if (traceStream !== undefined) {
- const tStart = performance.now();
- for (;;) {
- const res = await traceStream.readChunk();
- await this.engine.parse(res.data);
- const elapsed = (performance.now() - tStart) / 1000;
- let status = 'Loading trace ';
- if (res.bytesTotal > 0) {
- const progress = Math.round((res.bytesRead / res.bytesTotal) * 100);
- status += `${progress}%`;
- } else {
- status += `${Math.round(res.bytesRead / 1e6)} MB`;
- }
- status += ` - ${Math.ceil(res.bytesRead / elapsed / 1e6)} MB/s`;
- this.updateStatus(status);
- if (res.eof) break;
- }
- await this.engine.notifyEof();
- } else {
- assertTrue(this.engine instanceof HttpRpcEngine);
- await this.engine.restoreInitialTables();
- }
- for (const p of globals.extraSqlPackages) {
- await this.engine.registerSqlModules(p);
- }
-
- // traceUuid will be '' if the trace is not cacheable (URL or RPC).
- const traceUuid = await this.cacheCurrentTrace();
-
- const traceDetails = await getTraceInfo(this.engine, engineCfg);
- if (traceDetails.traceTitle) {
- document.title = `${traceDetails.traceTitle} - Perfetto UI`;
- }
- const trace = TraceImpl.newInstance(this.engine, traceDetails);
- await globals.onTraceLoad(trace);
-
- AppImpl.instance.omnibox.reset();
- const actions: DeferredAction[] = [Actions.setTraceUuid({traceUuid})];
-
- const visibleTimeSpan = await computeVisibleTime(
- traceDetails.start,
- traceDetails.end,
- trace.traceInfo.traceType === 'json',
- this.engine,
- );
-
- globals.timeline.updateVisibleTime(visibleTimeSpan);
-
- globals.dispatchMultiple(actions);
- Router.navigate(`#!/viewer?local_cache_key=${traceUuid}`);
-
- // Make sure the helper views are available before we start adding tracks.
- await this.initialiseHelperViews();
- await this.includeSummaryTables();
-
- await defineMaxLayoutDepthSqlFunction(engine);
-
- if (globals.restoreAppStateAfterTraceLoad) {
- deserializeAppStatePhase1(globals.restoreAppStateAfterTraceLoad, trace);
- }
-
- await pluginManager.onTraceLoad(trace, (id) => {
- this.updateStatus(`Running plugin: ${id}`);
- });
-
- await this.listTracks();
-
- this.decideTabs();
-
- await this.listThreads();
- await this.loadTimelineOverview(
- new TimeSpan(traceDetails.start, traceDetails.end),
- );
-
- {
- // Check if we have any ftrace events at all
- const query = `
- select
- *
- from ftrace_event
- limit 1`;
-
- const res = await engine.query(query);
- publishHasFtrace(res.numRows() > 0);
- }
-
- const pendingDeeplink = globals.state.pendingDeeplink;
- if (pendingDeeplink !== undefined) {
- globals.dispatch(Actions.clearPendingDeeplink({}));
- await this.selectPendingDeeplink(pendingDeeplink);
- if (
- pendingDeeplink.visStart !== undefined &&
- pendingDeeplink.visEnd !== undefined
- ) {
- this.zoomPendingDeeplink(
- pendingDeeplink.visStart,
- pendingDeeplink.visEnd,
- );
- }
- if (pendingDeeplink.query !== undefined) {
- addQueryResultsTab(trace, {
- query: pendingDeeplink.query,
- title: 'Deeplink Query',
- });
- }
- }
-
- // Trace Processor doesn't support the reliable range feature for JSON
- // traces.
- if (
- trace.traceInfo.traceType !== 'json' &&
- ENABLE_CHROME_RELIABLE_RANGE_ANNOTATION_FLAG.get()
- ) {
- const reliableRangeStart = await computeTraceReliableRangeStart(engine);
- if (reliableRangeStart > 0) {
- globals.noteManager.addNote({
- timestamp: reliableRangeStart,
- color: '#ff0000',
- text: 'Reliable Range Start',
- });
- }
- }
-
- if (globals.restoreAppStateAfterTraceLoad) {
- // Wait that plugins have completed their actions and then proceed with
- // the final phase of app state restore.
- // TODO(primiano): this can probably be removed once we refactor tracks
- // to be URI based and can deal with non-existing URIs.
- deserializeAppStatePhase2(globals.restoreAppStateAfterTraceLoad);
- globals.restoreAppStateAfterTraceLoad = undefined;
- }
-
- await pluginManager.onTraceReady();
-
- return engineMode;
- }
-
- private async selectPendingDeeplink(link: PendingDeeplinkState) {
- const conditions = [];
- const {ts, dur} = link;
-
- if (ts !== undefined) {
- conditions.push(`ts = ${ts}`);
- }
- if (dur !== undefined) {
- conditions.push(`dur = ${dur}`);
- }
-
- if (conditions.length === 0) {
- return;
- }
-
- const query = `
- select
- id,
- track_id as traceProcessorTrackId,
- type
- from slice
- where ${conditions.join(' and ')}
- ;`;
-
- const result = await assertExists(this.engine).query(query);
- if (result.numRows() > 0) {
- const row = result.firstRow({
- id: NUM,
- traceProcessorTrackId: NUM,
- type: STR,
- });
-
- const id = row.traceProcessorTrackId;
- const track = globals.workspace.flatTracks.find(
- (t) =>
- t.uri &&
- globals.trackManager.getTrack(t.uri)?.tags?.trackIds?.includes(id),
- );
- if (track === undefined) {
- return;
- }
- globals.selectionManager.selectSqlEvent('slice', row.id, {
- pendingScrollId: row.id,
- switchToCurrentSelectionTab: false,
- });
- }
- }
-
- private async listTracks() {
- this.updateStatus('Loading tracks');
- await decideTracks();
- }
-
- // Show the list of default tabs, but don't make them active!
- private decideTabs() {
- for (const tabUri of globals.tabManager.defaultTabs) {
- globals.tabManager.showTab(tabUri);
- }
- }
-
- private async listThreads() {
- this.updateStatus('Reading thread list');
- const query = `select
- utid,
- tid,
- pid,
- ifnull(thread.name, '') as threadName,
- ifnull(
- case when length(process.name) > 0 then process.name else null end,
- thread.name) as procName,
- process.cmdline as cmdline
- from (select * from thread order by upid) as thread
- left join (select * from process order by upid) as process
- using(upid)`;
- const result = await assertExists(this.engine).query(query);
- const threads: ThreadDesc[] = [];
- const it = result.iter({
- utid: NUM,
- tid: NUM,
- pid: NUM_NULL,
- threadName: STR,
- procName: STR_NULL,
- cmdline: STR_NULL,
- });
- for (; it.valid(); it.next()) {
- const utid = it.utid;
- const tid = it.tid;
- const pid = it.pid === null ? undefined : it.pid;
- const threadName = it.threadName;
- const procName = it.procName === null ? undefined : it.procName;
- const cmdline = it.cmdline === null ? undefined : it.cmdline;
- threads.push({utid, tid, threadName, pid, procName, cmdline});
- }
- publishThreads(threads);
- }
-
- private async loadTimelineOverview(trace: TimeSpan) {
- clearOverviewData();
- const engine = assertExists<Engine>(this.engine);
- const stepSize = Duration.max(1n, trace.duration / 100n);
- const hasSchedSql = 'select ts from sched limit 1';
- const hasSchedOverview = (await engine.query(hasSchedSql)).numRows() > 0;
- if (hasSchedOverview) {
- const stepPromises = [];
- for (
- let start = trace.start;
- start < trace.end;
- start = Time.add(start, stepSize)
- ) {
- const progress = start - trace.start;
- const ratio = Number(progress) / Number(trace.duration);
- this.updateStatus('Loading overview ' + `${Math.round(ratio * 100)}%`);
- const end = Time.add(start, stepSize);
- // The (async() => {})() queues all the 100 async promises in one batch.
- // Without that, we would wait for each step to be rendered before
- // kicking off the next one. That would interleave an animation frame
- // between each step, slowing down significantly the overall process.
- stepPromises.push(
- (async () => {
- const schedResult = await engine.query(
- `select cast(sum(dur) as float)/${stepSize} as load, cpu from sched ` +
- `where ts >= ${start} and ts < ${end} and utid != 0 ` +
- 'group by cpu order by cpu',
- );
- const schedData: {[key: string]: QuantizedLoad} = {};
- const it = schedResult.iter({load: NUM, cpu: NUM});
- for (; it.valid(); it.next()) {
- const load = it.load;
- const cpu = it.cpu;
- schedData[cpu] = {start, end, load};
- }
- publishOverviewData(schedData);
- })(),
- );
- } // for(start = ...)
- await Promise.all(stepPromises);
- return;
- } // if (hasSchedOverview)
-
- // Slices overview.
- const sliceResult = await engine.query(`select
- bucket,
- upid,
- ifnull(sum(utid_sum) / cast(${stepSize} as float), 0) as load
- from thread
- inner join (
- select
- ifnull(cast((ts - ${trace.start})/${stepSize} as int), 0) as bucket,
- sum(dur) as utid_sum,
- utid
- from slice
- inner join thread_track on slice.track_id = thread_track.id
- group by bucket, utid
- ) using(utid)
- where upid is not null
- group by bucket, upid`);
-
- const slicesData: {[key: string]: QuantizedLoad[]} = {};
- const it = sliceResult.iter({bucket: LONG, upid: NUM, load: NUM});
- for (; it.valid(); it.next()) {
- const bucket = it.bucket;
- const upid = it.upid;
- const load = it.load;
-
- const start = Time.add(trace.start, stepSize * bucket);
- const end = Time.add(start, stepSize);
-
- const upidStr = upid.toString();
- let loadArray = slicesData[upidStr];
- if (loadArray === undefined) {
- loadArray = slicesData[upidStr] = [];
- }
- loadArray.push({start, end, load});
- }
- publishOverviewData(slicesData);
- }
-
- private async cacheCurrentTrace(): Promise<string> {
- const engine = assertExists(this.engine);
- const result = await engine.query(`select str_value as uuid from metadata
- where name = 'trace_uuid'`);
- if (result.numRows() === 0) {
- // One of the cases covered is an empty trace.
- return '';
- }
- const traceUuid = result.firstRow({uuid: STR}).uuid;
- const engineConfig = assertExists(globals.state.engine);
- assertTrue(engineConfig.id === this.engineId);
- if (!(await cacheTrace(engineConfig.source, traceUuid))) {
- // If the trace is not cacheable (cacheable means it has been opened from
- // URL or RPC) only append '?local_cache_key' to the URL, without the
- // local_cache_key value. Doing otherwise would cause an error if the tab
- // is discarded or the user hits the reload button because the trace is
- // not in the cache.
- return '';
- }
- return traceUuid;
- }
-
- async initialiseHelperViews() {
- const engine = assertExists(this.engine);
-
- this.updateStatus('Creating annotation counter track table');
- // Create the helper tables for all the annotations related data.
- // NULL in min/max means "figure it out per track in the usual way".
- await engine.query(`
- CREATE TABLE annotation_counter_track(
- id INTEGER PRIMARY KEY,
- name STRING,
- __metric_name STRING,
- upid INTEGER,
- min_value DOUBLE,
- max_value DOUBLE
- );
- `);
- this.updateStatus('Creating annotation slice track table');
- await engine.query(`
- CREATE TABLE annotation_slice_track(
- id INTEGER PRIMARY KEY,
- name STRING,
- __metric_name STRING,
- upid INTEGER,
- group_name STRING
- );
- `);
-
- this.updateStatus('Creating annotation counter table');
- await engine.query(`
- CREATE TABLE annotation_counter(
- id BIGINT,
- track_id INT,
- ts BIGINT,
- value DOUBLE,
- PRIMARY KEY (track_id, ts)
- ) WITHOUT ROWID;
- `);
- this.updateStatus('Creating annotation slice table');
- await engine.query(`
- CREATE TABLE annotation_slice(
- id INTEGER PRIMARY KEY,
- track_id INT,
- ts BIGINT,
- dur BIGINT,
- thread_dur BIGINT,
- depth INT,
- cat STRING,
- name STRING,
- UNIQUE(track_id, ts)
- );
- `);
-
- const availableMetrics = [];
- const metricsResult = await engine.query('select name from trace_metrics');
- for (const it = metricsResult.iter({name: STR}); it.valid(); it.next()) {
- availableMetrics.push(it.name);
- }
-
- const availableMetricsSet = new Set<string>(availableMetrics);
- for (const [flag, metric] of FLAGGED_METRICS) {
- if (!flag.get() || !availableMetricsSet.has(metric)) {
- continue;
- }
-
- this.updateStatus(`Computing ${metric} metric`);
- try {
- // We don't care about the actual result of metric here as we are just
- // interested in the annotation tracks.
- await engine.computeMetric([metric], 'proto');
- } catch (e) {
- if (e instanceof QueryError) {
- publishMetricError('MetricError: ' + e.message);
- continue;
- } else {
- throw e;
- }
- }
-
- this.updateStatus(`Inserting data for ${metric} metric`);
- try {
- const result = await engine.query(`pragma table_info(${metric}_event)`);
- let hasSliceName = false;
- let hasDur = false;
- let hasUpid = false;
- let hasValue = false;
- let hasGroupName = false;
- const it = result.iter({name: STR});
- for (; it.valid(); it.next()) {
- const name = it.name;
- hasSliceName = hasSliceName || name === 'slice_name';
- hasDur = hasDur || name === 'dur';
- hasUpid = hasUpid || name === 'upid';
- hasValue = hasValue || name === 'value';
- hasGroupName = hasGroupName || name === 'group_name';
- }
-
- const upidColumnSelect = hasUpid ? 'upid' : '0 AS upid';
- const upidColumnWhere = hasUpid ? 'upid' : '0';
- const groupNameColumn = hasGroupName
- ? 'group_name'
- : 'NULL AS group_name';
- if (hasSliceName && hasDur) {
- await engine.query(`
- INSERT INTO annotation_slice_track(
- name, __metric_name, upid, group_name)
- SELECT DISTINCT
- track_name,
- '${metric}' as metric_name,
- ${upidColumnSelect},
- ${groupNameColumn}
- FROM ${metric}_event
- WHERE track_type = 'slice'
- `);
- await engine.query(`
- INSERT INTO annotation_slice(
- track_id, ts, dur, thread_dur, depth, cat, name
- )
- SELECT
- t.id AS track_id,
- ts,
- dur,
- NULL as thread_dur,
- 0 AS depth,
- a.track_name as cat,
- slice_name AS name
- FROM ${metric}_event a
- JOIN annotation_slice_track t
- ON a.track_name = t.name AND t.__metric_name = '${metric}'
- ORDER BY t.id, ts
- `);
- }
-
- if (hasValue) {
- const minMax = await engine.query(`
- SELECT
- IFNULL(MIN(value), 0) as minValue,
- IFNULL(MAX(value), 0) as maxValue
- FROM ${metric}_event
- WHERE ${upidColumnWhere} != 0`);
- const row = minMax.firstRow({minValue: NUM, maxValue: NUM});
- await engine.query(`
- INSERT INTO annotation_counter_track(
- name, __metric_name, min_value, max_value, upid)
- SELECT DISTINCT
- track_name,
- '${metric}' as metric_name,
- CASE ${upidColumnWhere} WHEN 0 THEN NULL ELSE ${row.minValue} END,
- CASE ${upidColumnWhere} WHEN 0 THEN NULL ELSE ${row.maxValue} END,
- ${upidColumnSelect}
- FROM ${metric}_event
- WHERE track_type = 'counter'
- `);
- await engine.query(`
- INSERT INTO annotation_counter(id, track_id, ts, value)
- SELECT
- -1 as id,
- t.id AS track_id,
- ts,
- value
- FROM ${metric}_event a
- JOIN annotation_counter_track t
- ON a.track_name = t.name AND t.__metric_name = '${metric}'
- ORDER BY t.id, ts
- `);
- }
- } catch (e) {
- if (e instanceof QueryError) {
- publishMetricError('MetricError: ' + e.message);
- } else {
- throw e;
- }
- }
- }
- }
-
- async includeSummaryTables() {
- const engine = assertExists<Engine>(this.engine);
-
- this.updateStatus('Creating slice summaries');
- await engine.query(`include perfetto module viz.summary.slices;`);
-
- this.updateStatus('Creating counter summaries');
- await engine.query(`include perfetto module viz.summary.counters;`);
-
- this.updateStatus('Creating thread summaries');
- await engine.query(`include perfetto module viz.summary.threads;`);
-
- this.updateStatus('Creating processes summaries');
- await engine.query(`include perfetto module viz.summary.processes;`);
-
- this.updateStatus('Creating track summaries');
- await engine.query(`include perfetto module viz.summary.tracks;`);
- }
-
- private updateStatus(msg: string): void {
- globals.dispatch(
- Actions.updateStatus({
- msg,
- timestamp: Date.now() / 1000,
- }),
- );
- }
-
- private zoomPendingDeeplink(visStart: string, visEnd: string) {
- const visualStart = Time.fromRaw(BigInt(visStart));
- const visualEnd = Time.fromRaw(BigInt(visEnd));
- const traceContext = globals.traceContext;
-
- if (
- !(
- visualStart < visualEnd &&
- traceContext.start <= visualStart &&
- visualEnd <= traceContext.end
- )
- ) {
- return;
- }
-
- globals.timeline.updateVisibleTime(new TimeSpan(visualStart, visualEnd));
- }
-}
-
-async function computeFtraceBounds(engine: Engine): Promise<TimeSpan | null> {
- const result = await engine.query(`
- SELECT min(ts) as start, max(ts) as end FROM ftrace_event;
- `);
- const {start, end} = result.firstRow({start: LONG_NULL, end: LONG_NULL});
- if (start !== null && end !== null) {
- return new TimeSpan(Time.fromRaw(start), Time.fromRaw(end));
- }
- return null;
-}
-
-async function computeTraceReliableRangeStart(engine: Engine): Promise<time> {
- const result =
- await engine.query(`SELECT RUN_METRIC('chrome/chrome_reliable_range.sql');
- SELECT start FROM chrome_reliable_range`);
- const bounds = result.firstRow({start: LONG});
- return Time.fromRaw(bounds.start);
-}
-
-async function computeVisibleTime(
- traceStart: time,
- traceEnd: time,
- isJsonTrace: boolean,
- engine: Engine,
-): Promise<TimeSpan> {
- // initialise visible time to the trace time bounds
- let visibleStart = traceStart;
- let visibleEnd = traceEnd;
-
- // compare start and end with metadata computed by the trace processor
- const mdTime = await getTracingMetadataTimeBounds(engine);
- // make sure the bounds hold
- if (Time.max(visibleStart, mdTime.start) < Time.min(visibleEnd, mdTime.end)) {
- visibleStart = Time.max(visibleStart, mdTime.start);
- visibleEnd = Time.min(visibleEnd, mdTime.end);
- }
-
- // Trace Processor doesn't support the reliable range feature for JSON
- // traces.
- if (!isJsonTrace && ENABLE_CHROME_RELIABLE_RANGE_ZOOM_FLAG.get()) {
- const reliableRangeStart = await computeTraceReliableRangeStart(engine);
- visibleStart = Time.max(visibleStart, reliableRangeStart);
- }
-
- // Move start of visible window to the first ftrace event
- const ftraceBounds = await computeFtraceBounds(engine);
- if (ftraceBounds !== null) {
- // Avoid moving start of visible window past its end!
- visibleStart = Time.min(ftraceBounds.start, visibleEnd);
- }
- return new TimeSpan(visibleStart, visibleEnd);
-}
-
-async function getTraceInfo(
- engine: Engine,
- engineCfg: EngineConfig,
-): Promise<TraceInfo> {
- const traceTime = await getTraceTimeBounds(engine);
-
- // Find the first REALTIME or REALTIME_COARSE clock snapshot.
- // Prioritize REALTIME over REALTIME_COARSE.
- const query = `select
- ts,
- clock_value as clockValue,
- clock_name as clockName
- from clock_snapshot
- where
- snapshot_id = 0 AND
- clock_name in ('REALTIME', 'REALTIME_COARSE')
- `;
- const result = await engine.query(query);
- const it = result.iter({
- ts: LONG,
- clockValue: LONG,
- clockName: STR,
- });
-
- let snapshot = {
- clockName: '',
- ts: Time.ZERO,
- clockValue: Time.ZERO,
- };
-
- // Find the most suitable snapshot
- for (let row = 0; it.valid(); it.next(), row++) {
- if (it.clockName === 'REALTIME') {
- snapshot = {
- clockName: it.clockName,
- ts: Time.fromRaw(it.ts),
- clockValue: Time.fromRaw(it.clockValue),
- };
- break;
- } else if (it.clockName === 'REALTIME_COARSE') {
- if (snapshot.clockName !== 'REALTIME') {
- snapshot = {
- clockName: it.clockName,
- ts: Time.fromRaw(it.ts),
- clockValue: Time.fromRaw(it.clockValue),
- };
- }
- }
- }
-
- // The max() is so the query returns NULL if the tz info doesn't exist.
- const queryTz = `select max(int_value) as tzOffMin from metadata
- where name = 'timezone_off_mins'`;
- const resTz = await assertExists(engine).query(queryTz);
- const tzOffMin = resTz.firstRow({tzOffMin: NUM_NULL}).tzOffMin ?? 0;
-
- // This is the offset between the unix epoch and ts in the ts domain.
- // I.e. the value of ts at the time of the unix epoch - usually some large
- // negative value.
- const realtimeOffset = Time.sub(snapshot.ts, snapshot.clockValue);
-
- // Find the previous closest midnight from the trace start time.
- const utcOffset = Time.getLatestMidnight(traceTime.start, realtimeOffset);
-
- const traceTzOffset = Time.getLatestMidnight(
- traceTime.start,
- Time.sub(realtimeOffset, Time.fromSeconds(tzOffMin * 60)),
- );
-
- let traceTitle = '';
- let traceUrl = '';
- switch (engineCfg.source.type) {
- case 'FILE':
- // Split on both \ and / (because C:\Windows\paths\are\like\this).
- traceTitle = engineCfg.source.file.name.split(/[/\\]/).pop()!;
- const fileSizeMB = Math.ceil(engineCfg.source.file.size / 1e6);
- traceTitle += ` (${fileSizeMB} MB)`;
- break;
- case 'URL':
- traceUrl = engineCfg.source.url;
- traceTitle = traceUrl.split('/').pop()!;
- break;
- case 'ARRAY_BUFFER':
- traceTitle = engineCfg.source.title;
- traceUrl = engineCfg.source.url ?? '';
- const arrayBufferSizeMB = Math.ceil(
- engineCfg.source.buffer.byteLength / 1e6,
- );
- traceTitle += ` (${arrayBufferSizeMB} MB)`;
- break;
- case 'HTTP_RPC':
- traceTitle = `RPC @ ${HttpRpcEngine.hostAndPort}`;
- break;
- default:
- break;
- }
-
- const traceType = (
- await engine.query(
- `select str_value from metadata where name = 'trace_type'`,
- )
- ).firstRow({str_value: STR}).str_value;
-
- return {
- ...traceTime,
- traceTitle,
- traceUrl,
- realtimeOffset,
- utcOffset,
- traceTzOffset,
- cpus: await getCpus(engine),
- gpuCount: await getNumberOfGpus(engine),
- importErrors: await getTraceErrors(engine),
- source: engineCfg.source,
- traceType,
- };
-}
-
-async function getTraceTimeBounds(engine: Engine): Promise<TimeSpan> {
- const result = await engine.query(
- `select start_ts as startTs, end_ts as endTs from trace_bounds`,
- );
- const bounds = result.firstRow({
- startTs: LONG,
- endTs: LONG,
- });
- return new TimeSpan(Time.fromRaw(bounds.startTs), Time.fromRaw(bounds.endTs));
-}
-
-// TODO(hjd): When streaming must invalidate this somehow.
-async function getCpus(engine: Engine): Promise<number[]> {
- const cpus = [];
- const queryRes = await engine.query(
- 'select distinct(cpu) as cpu from sched order by cpu;',
- );
- for (const it = queryRes.iter({cpu: NUM}); it.valid(); it.next()) {
- cpus.push(it.cpu);
- }
- return cpus;
-}
-
-async function getNumberOfGpus(engine: Engine): Promise<number> {
- const result = await engine.query(`
- select count(distinct(gpu_id)) as gpuCount
- from gpu_counter_track
- where name = 'gpufreq';
- `);
- return result.firstRow({gpuCount: NUM}).gpuCount;
-}
-
-async function getTraceErrors(engine: Engine): Promise<number> {
- const sql = `SELECT sum(value) as errs FROM stats WHERE severity != 'info'`;
- const result = await engine.query(sql);
- return result.firstRow({errs: NUM}).errs;
-}
-
-async function getTracingMetadataTimeBounds(engine: Engine): Promise<TimeSpan> {
- const queryRes = await engine.query(`select
- name,
- int_value as intValue
- from metadata
- where name = 'tracing_started_ns' or name = 'tracing_disabled_ns'
- or name = 'all_data_source_started_ns'`);
- let startBound = Time.MIN;
- let endBound = Time.MAX;
- const it = queryRes.iter({name: STR, intValue: LONG_NULL});
- for (; it.valid(); it.next()) {
- const columnName = it.name;
- const timestamp = it.intValue;
- if (timestamp === null) continue;
- if (columnName === 'tracing_disabled_ns') {
- endBound = Time.min(endBound, Time.fromRaw(timestamp));
- } else {
- startBound = Time.max(startBound, Time.fromRaw(timestamp));
- }
- }
-
- return new TimeSpan(startBound, endBound);
-}
diff --git a/ui/src/controller/track_decider.ts b/ui/src/controller/track_decider.ts
deleted file mode 100644
index b538917..0000000
--- a/ui/src/controller/track_decider.ts
+++ /dev/null
@@ -1,233 +0,0 @@
-// Copyright (C) 2020 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 {globals} from '../frontend/globals';
-import {TrackNode} from '../public/workspace';
-
-const MEM_DMA_COUNTER_NAME = 'mem.dma_heap';
-const MEM_DMA = 'mem.dma_buffer';
-const MEM_ION = 'mem.ion';
-const F2FS_IOSTAT_TAG = 'f2fs_iostat.';
-const F2FS_IOSTAT_GROUP_NAME = 'f2fs_iostat';
-const F2FS_IOSTAT_LAT_TAG = 'f2fs_iostat_latency.';
-const F2FS_IOSTAT_LAT_GROUP_NAME = 'f2fs_iostat_latency';
-const DISK_IOSTAT_TAG = 'diskstat.';
-const DISK_IOSTAT_GROUP_NAME = 'diskstat';
-const BUDDY_INFO_TAG = 'mem.buddyinfo';
-const UFS_CMD_TAG_REGEX = new RegExp('^io.ufs.command.tag.*$');
-const UFS_CMD_TAG_GROUP = 'io.ufs.command.tags';
-// NB: Userspace wakelocks start with "WakeLock" not "Wakelock".
-const KERNEL_WAKELOCK_REGEX = new RegExp('^Wakelock.*$');
-const KERNEL_WAKELOCK_GROUP = 'Kernel wakelocks';
-const NETWORK_TRACK_REGEX = new RegExp('^.* (Received|Transmitted)( KB)?$');
-const NETWORK_TRACK_GROUP = 'Networking';
-const ENTITY_RESIDENCY_REGEX = new RegExp('^Entity residency:');
-const ENTITY_RESIDENCY_GROUP = 'Entity residency';
-const UCLAMP_REGEX = new RegExp('^UCLAMP_');
-const UCLAMP_GROUP = 'Scheduler Utilization Clamping';
-const POWER_RAILS_GROUP = 'Power Rails';
-const POWER_RAILS_REGEX = new RegExp('^power.');
-const FREQUENCY_GROUP = 'Frequency Scaling';
-const TEMPERATURE_REGEX = new RegExp('^.* Temperature$');
-const TEMPERATURE_GROUP = 'Temperature';
-const IRQ_GROUP = 'IRQs';
-const IRQ_REGEX = new RegExp('^(Irq|SoftIrq) Cpu.*');
-const CHROME_TRACK_REGEX = new RegExp('^Chrome.*|^InputLatency::.*');
-const CHROME_TRACK_GROUP = 'Chrome Global Tracks';
-const MISC_GROUP = 'Misc Global Tracks';
-
-function groupGlobalIonTracks(): void {
- const ionTracks: TrackNode[] = [];
- let hasSummary = false;
-
- for (const track of globals.workspace.children) {
- if (track.hasChildren) continue;
-
- const isIon = track.title.startsWith(MEM_ION);
- const isIonCounter = track.title === MEM_ION;
- const isDmaHeapCounter = track.title === MEM_DMA_COUNTER_NAME;
- const isDmaBuffferSlices = track.title === MEM_DMA;
- if (isIon || isIonCounter || isDmaHeapCounter || isDmaBuffferSlices) {
- ionTracks.push(track);
- }
- hasSummary = hasSummary || isIonCounter;
- hasSummary = hasSummary || isDmaHeapCounter;
- }
-
- if (ionTracks.length === 0 || !hasSummary) {
- return;
- }
-
- let group: TrackNode | undefined;
- for (const track of ionTracks) {
- if (!group && [MEM_DMA_COUNTER_NAME, MEM_ION].includes(track.title)) {
- globals.workspace.removeChild(track);
- group = new TrackNode({
- title: track.title,
- uri: track.uri,
- isSummary: true,
- });
- globals.workspace.addChildInOrder(group);
- } else {
- group?.addChildInOrder(track);
- }
- }
-}
-
-function groupGlobalIostatTracks(tag: string, groupName: string): void {
- const devMap = new Map<string, TrackNode>();
-
- for (const track of globals.workspace.children) {
- if (track.hasChildren) continue;
- if (track.title.startsWith(tag)) {
- const name = track.title.split('.', 3);
- const key = name[1];
-
- let parentGroup = devMap.get(key);
- if (!parentGroup) {
- const group = new TrackNode({title: groupName, isSummary: true});
- globals.workspace.addChildInOrder(group);
- devMap.set(key, group);
- parentGroup = group;
- }
-
- track.title = name[2];
- parentGroup.addChildInOrder(track);
- }
- }
-}
-
-function groupGlobalBuddyInfoTracks(): void {
- const devMap = new Map<string, TrackNode>();
-
- for (const track of globals.workspace.children) {
- if (track.hasChildren) continue;
- if (track.title.startsWith(BUDDY_INFO_TAG)) {
- const tokens = track.title.split('[');
- const node = tokens[1].slice(0, -1);
- const zone = tokens[2].slice(0, -1);
- const size = tokens[3].slice(0, -1);
-
- const groupName = 'Buddyinfo: Node: ' + node + ' Zone: ' + zone;
- if (!devMap.has(groupName)) {
- const group = new TrackNode({title: groupName, isSummary: true});
- devMap.set(groupName, group);
- globals.workspace.addChildInOrder(group);
- }
- track.title = 'Chunk size: ' + size;
- const group = devMap.get(groupName)!;
- group.addChildInOrder(track);
- }
- }
-}
-
-function groupFrequencyTracks(groupName: string): void {
- const group = new TrackNode({title: groupName, isSummary: true});
-
- for (const track of globals.workspace.children) {
- if (track.hasChildren) continue;
- // Group all the frequency tracks together (except the CPU and GPU
- // frequency ones).
- if (
- track.title.endsWith('Frequency') &&
- !track.title.startsWith('Cpu') &&
- !track.title.startsWith('Gpu')
- ) {
- group.addChildInOrder(track);
- }
- }
-
- if (group.children.length > 0) {
- globals.workspace.addChildInOrder(group);
- }
-}
-
-function groupMiscNonAllowlistedTracks(groupName: string): void {
- // List of allowlisted track names.
- const ALLOWLIST_REGEXES = [
- new RegExp('^Cpu .*$', 'i'),
- new RegExp('^Gpu .*$', 'i'),
- new RegExp('^Trace Triggers$'),
- new RegExp('^Android App Startups$'),
- new RegExp('^Device State.*$'),
- new RegExp('^Android logs$'),
- ];
-
- const group = new TrackNode({title: groupName, isSummary: true});
- for (const track of globals.workspace.children) {
- if (track.hasChildren) continue;
- let allowlisted = false;
- for (const regex of ALLOWLIST_REGEXES) {
- allowlisted = allowlisted || regex.test(track.title);
- }
- if (allowlisted) {
- continue;
- }
- group.addChildInOrder(track);
- }
-
- if (group.children.length > 0) {
- globals.workspace.addChildInOrder(group);
- }
-}
-
-function groupTracksByRegex(regex: RegExp, groupName: string): void {
- const group = new TrackNode({title: groupName, isSummary: true});
-
- for (const track of globals.workspace.children) {
- if (track.hasChildren) continue;
- if (regex.test(track.title)) {
- group.addChildInOrder(track);
- }
- }
-
- if (group.children.length > 0) {
- globals.workspace.addChildInOrder(group);
- }
-}
-
-export async function decideTracks(): Promise<void> {
- groupGlobalIonTracks();
- groupGlobalIostatTracks(F2FS_IOSTAT_TAG, F2FS_IOSTAT_GROUP_NAME);
- groupGlobalIostatTracks(F2FS_IOSTAT_LAT_TAG, F2FS_IOSTAT_LAT_GROUP_NAME);
- groupGlobalIostatTracks(DISK_IOSTAT_TAG, DISK_IOSTAT_GROUP_NAME);
- groupTracksByRegex(UFS_CMD_TAG_REGEX, UFS_CMD_TAG_GROUP);
- groupGlobalBuddyInfoTracks();
- groupTracksByRegex(KERNEL_WAKELOCK_REGEX, KERNEL_WAKELOCK_GROUP);
- groupTracksByRegex(NETWORK_TRACK_REGEX, NETWORK_TRACK_GROUP);
- groupTracksByRegex(ENTITY_RESIDENCY_REGEX, ENTITY_RESIDENCY_GROUP);
- groupTracksByRegex(UCLAMP_REGEX, UCLAMP_GROUP);
- groupFrequencyTracks(FREQUENCY_GROUP);
- groupTracksByRegex(POWER_RAILS_REGEX, POWER_RAILS_GROUP);
- groupTracksByRegex(TEMPERATURE_REGEX, TEMPERATURE_GROUP);
- groupTracksByRegex(IRQ_REGEX, IRQ_GROUP);
- groupTracksByRegex(CHROME_TRACK_REGEX, CHROME_TRACK_GROUP);
- groupMiscNonAllowlistedTracks(MISC_GROUP);
-
- // Move groups underneath tracks
- Array.from(globals.workspace.children)
- .sort((a, b) => {
- // Get the index in the order array
- const indexA = a.hasChildren ? 1 : 0;
- const indexB = b.hasChildren ? 1 : 0;
- return indexA - indexB;
- })
- .forEach((n) => globals.workspace.addChildLast(n));
-
- // If there is only one group, expand it
- const rootLevelChildren = globals.workspace.children;
- if (rootLevelChildren.length === 1 && rootLevelChildren[0].hasChildren) {
- rootLevelChildren[0].expand();
- }
-}
diff --git a/ui/src/frontend/analytics.ts b/ui/src/core/analytics_impl.ts
similarity index 87%
rename from ui/src/frontend/analytics.ts
rename to ui/src/core/analytics_impl.ts
index 7973ee1..be785ca 100644
--- a/ui/src/frontend/analytics.ts
+++ b/ui/src/core/analytics_impl.ts
@@ -13,12 +13,11 @@
// limitations under the License.
import {ErrorDetails} from '../base/logging';
-import {getCurrentChannel} from '../common/channels';
+import {getCurrentChannel} from './channels';
import {VERSION} from '../gen/perfetto_version';
-import {globals} from './globals';
import {Router} from './router';
+import {Analytics, TraceCategories} from '../public/analytics';
-type TraceCategories = 'Trace Actions' | 'Record Trace' | 'User Actions';
const ANALYTICS_ID = 'G-BD89KT2P3C';
const PAGE_TITLE = 'no-page-title';
@@ -61,7 +60,15 @@
}
}
-export function initAnalytics() {
+// Interface exposed only to core (for the initialize method).
+export interface AnalyticsInternal extends Analytics {
+ initialize(isInternalUser: boolean): void;
+}
+
+export function initAnalytics(
+ testingMode: boolean,
+ embeddedMode: boolean,
+): AnalyticsInternal {
// Only initialize logging on the official site and on localhost (to catch
// analytics bugs when testing locally).
// Skip analytics is the fragment has "testing=1", this is used by UI tests.
@@ -70,8 +77,8 @@
if (
(window.location.origin.startsWith('http://localhost:') ||
window.location.origin.endsWith('.perfetto.dev')) &&
- !globals.testing &&
- !globals.embeddedMode
+ !testingMode &&
+ !embeddedMode
) {
return new AnalyticsImpl();
}
@@ -84,17 +91,8 @@
gtag: (command: string, event: string | Date, args?: {}) => void;
};
-export interface Analytics {
- initialize(): void;
- updatePath(_: string): void;
- logEvent(category: TraceCategories | null, event: string): void;
- logError(err: ErrorDetails): void;
- isEnabled(): boolean;
-}
-
-class NullAnalytics implements Analytics {
- initialize() {}
- updatePath(_: string) {}
+class NullAnalytics implements AnalyticsInternal {
+ initialize(_: boolean) {}
logEvent(_category: TraceCategories | null, _event: string) {}
logError(_err: ErrorDetails) {}
isEnabled(): boolean {
@@ -102,7 +100,7 @@
}
}
-class AnalyticsImpl implements Analytics {
+class AnalyticsImpl implements AnalyticsInternal {
private initialized_ = false;
constructor() {
@@ -128,7 +126,7 @@
// This is callled only after the script that sets isInternalUser loads.
// It is fine to call updatePath() and log*() functions before initialize().
// The gtag() function internally enqueues all requests into |dataLayer|.
- initialize() {
+ initialize(isInternalUser: boolean) {
if (this.initialized_) return;
this.initialized_ = true;
const script = document.createElement('script');
@@ -138,7 +136,7 @@
const route = window.location.href;
console.log(
`GA initialized. route=${route}`,
- `isInternalUser=${globals.isInternalUser}`,
+ `isInternalUser=${isInternalUser}`,
);
// GA's recommendation for SPAs is to disable automatic page views and
// manually send page_view events. See:
@@ -151,19 +149,16 @@
page_referrer: getReferrer(),
send_page_view: false,
page_title: PAGE_TITLE,
- perfetto_is_internal_user: globals.isInternalUser ? '1' : '0',
+ perfetto_is_internal_user: isInternalUser ? '1' : '0',
perfetto_version: VERSION,
// Release channel (canary, stable, autopush)
perfetto_channel: getCurrentChannel(),
// Referrer *if overridden* via the query string else empty string.
perfetto_referrer_override: getReferrerOverride() ?? '',
});
- this.updatePath(route);
- }
- updatePath(path: string) {
gtagGlobals.gtag('event', 'page_view', {
- page_path: path,
+ page_path: route,
page_title: PAGE_TITLE,
});
}
diff --git a/ui/src/core/app_impl.ts b/ui/src/core/app_impl.ts
index a2be0f4..7662755 100644
--- a/ui/src/core/app_impl.ts
+++ b/ui/src/core/app_impl.ts
@@ -12,57 +12,137 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {assertTrue} from '../base/logging';
+import {assertExists, assertTrue} from '../base/logging';
import {App} from '../public/app';
import {TraceContext, TraceImpl} from './trace_impl';
import {CommandManagerImpl} from './command_manager';
import {OmniboxManagerImpl} from './omnibox_manager';
import {raf} from './raf_scheduler';
import {SidebarManagerImpl} from './sidebar_manager';
+import {PluginManagerImpl} from './plugin_manager';
+import {NewEngineMode} from '../trace_processor/engine';
+import {RouteArgs} from '../public/route_schema';
+import {SqlPackage} from '../public/extra_sql_packages';
+import {SerializedAppState} from './state_serialization_schema';
+import {PostedTrace, TraceSource} from './trace_source';
+import {loadTrace} from './load_trace';
+import {CORE_PLUGIN_ID} from './plugin_manager';
+import {Router} from './router';
+import {AnalyticsInternal, initAnalytics} from './analytics_impl';
+import {createProxy, getOrCreate} from '../base/utils';
+import {PageManagerImpl} from './page_manager';
+import {PageHandler} from '../public/page';
+import {PerfManager} from './perf_manager';
+import {ServiceWorkerController} from '../frontend/service_worker_controller';
+import {FeatureFlagManager, FlagSettings} from '../public/feature_flag';
+import {featureFlags} from './feature_flags';
-// The pseudo plugin id used for the core instance of AppImpl.
-
-export const CORE_PLUGIN_ID = '__core__';
+// The args that frontend/index.ts passes when calling AppImpl.initialize().
+// This is to deal with injections that would otherwise cause circular deps.
+export interface AppInitArgs {
+ initialRouteArgs: RouteArgs;
+}
/**
* Handles the global state of the ui, for anything that is not related to a
* specific trace. This is always available even before a trace is loaded (in
* contrast to TraceContext, which is bound to the lifetime of a trace).
* There is only one instance in total of this class (see instance()).
- * This class is not exposed to anybody. Both core and plugins should access
- * this via AppImpl.
+ * This class is only exposed to TraceImpl, nobody else should refer to this
+ * and should use AppImpl instead.
*/
export class AppContext {
+ // The per-plugin instances of AppImpl (including the CORE_PLUGIN one).
+ private readonly pluginInstances = new Map<string, AppImpl>();
readonly commandMgr = new CommandManagerImpl();
readonly omniboxMgr = new OmniboxManagerImpl();
- readonly sidebarMgr = new SidebarManagerImpl();
+ readonly pageMgr = new PageManagerImpl();
+ readonly sidebarMgr: SidebarManagerImpl;
+ readonly pluginMgr: PluginManagerImpl;
+ readonly perfMgr = new PerfManager();
+ readonly analytics: AnalyticsInternal;
+ readonly serviceWorkerController: ServiceWorkerController;
+ httpRpc = {
+ newEngineMode: 'USE_HTTP_RPC_IF_AVAILABLE' as NewEngineMode,
+ httpRpcAvailable: false,
+ };
+ initialRouteArgs: RouteArgs;
+ isLoadingTrace = false; // Set when calling openTrace().
+ readonly initArgs: AppInitArgs;
+ readonly embeddedMode: boolean;
+ readonly testingMode: boolean;
- // The most recently created trace context. Can be undefined before any trace
- // is loaded.
- private traceCtx?: TraceContext;
+ // This is normally empty and is injected with extra google-internal packages
+ // via is_internal_user.js
+ extraSqlPackages: SqlPackage[] = [];
- // There is only one global instance, lazily initialized on the first call.
+ // The currently open trace.
+ currentTrace?: TraceContext;
+
private static _instance: AppContext;
- static get instance() {
- return (AppContext._instance = AppContext._instance ?? new AppContext());
+
+ static initialize(initArgs: AppInitArgs): AppContext {
+ assertTrue(AppContext._instance === undefined);
+ return (AppContext._instance = new AppContext(initArgs));
}
- private constructor() {}
-
- get currentTraceCtx(): TraceContext | undefined {
- return this.traceCtx;
+ static get instance(): AppContext {
+ return assertExists(AppContext._instance);
}
- // Called by AppImpl.newTraceInstance().
- setActiveTrace(traceCtx: TraceContext | undefined) {
- if (this.traceCtx !== undefined) {
+ // This constructor is invoked only once, when frontend/index.ts invokes
+ // AppMainImpl.initialize().
+ private constructor(initArgs: AppInitArgs) {
+ this.initArgs = initArgs;
+ this.initialRouteArgs = initArgs.initialRouteArgs;
+ 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),
+ get trace() {
+ return AppImpl.instance.trace;
+ },
+ });
+ }
+
+ // Gets or creates an instance of AppImpl backed by the current AppContext
+ // for the given plugin.
+ forPlugin(pluginId: string) {
+ return getOrCreate(this.pluginInstances, pluginId, () => {
+ return new AppImpl(this, pluginId);
+ });
+ }
+
+ closeCurrentTrace() {
+ this.omniboxMgr.reset(/* focus= */ false);
+
+ if (this.currentTrace !== undefined) {
// This will trigger the unregistration of trace-scoped commands and
// sidebar menuitems (and few similar things).
- this.traceCtx[Symbol.dispose]();
+ this.currentTrace[Symbol.dispose]();
+ this.currentTrace = undefined;
}
- this.traceCtx = traceCtx;
+ }
+
+ // Called by trace_loader.ts soon after it has created a new TraceImpl.
+ setActiveTrace(traceCtx: TraceContext) {
+ // In 99% this closeCurrentTrace() call is not needed because the real one
+ // is performed by openTrace() in this file. However in some rare cases we
+ // might end up loading a trace while another one is still loading, and this
+ // covers races in that case.
+ this.closeCurrentTrace();
+ this.currentTrace = traceCtx;
}
}
+
/*
* Every plugin gets its own instance. This is how we keep track
* what each plugin is doing and how we can blame issues on particular
@@ -71,23 +151,39 @@
*/
export class AppImpl implements App {
- private appCtx: AppContext;
readonly pluginId: string;
- private currentTrace?: TraceImpl;
+ private readonly appCtx: AppContext;
+ private readonly pageMgrProxy: PageManagerImpl;
- private constructor(appCtx: AppContext, pluginId: string) {
- this.appCtx = appCtx;
- this.pluginId = pluginId;
+ // 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;
static get instance(): AppImpl {
- AppImpl._instance =
- AppImpl._instance ?? new AppImpl(AppContext.instance, CORE_PLUGIN_ID);
- return AppImpl._instance;
+ return AppContext.instance.forPlugin(CORE_PLUGIN_ID);
+ }
+
+ // Only called by AppContext.forPlugin().
+ constructor(appCtx: AppContext, pluginId: string) {
+ this.appCtx = appCtx;
+ this.pluginId = pluginId;
+
+ this.pageMgrProxy = createProxy(this.appCtx.pageMgr, {
+ registerPage(pageHandler: PageHandler): Disposable {
+ return appCtx.pageMgr.registerPage({
+ ...pageHandler,
+ pluginId,
+ });
+ },
+ });
+ }
+
+ forPlugin(pluginId: string): AppImpl {
+ return this.appCtx.forPlugin(pluginId);
}
get commands(): CommandManagerImpl {
@@ -102,26 +198,120 @@
return this.appCtx.omniboxMgr;
}
+ get plugins(): PluginManagerImpl {
+ return this.appCtx.pluginMgr;
+ }
+
+ get analytics(): AnalyticsInternal {
+ return this.appCtx.analytics;
+ }
+
+ get pages(): PageManagerImpl {
+ return this.pageMgrProxy;
+ }
+
get trace(): TraceImpl | undefined {
- return this.currentTrace;
+ return this.appCtx.currentTrace?.forPlugin(this.pluginId);
}
- closeCurrentTrace() {
- this.currentTrace = undefined;
- this.appCtx.setActiveTrace(undefined);
- }
-
- scheduleRedraw(): void {
+ scheduleFullRedraw(): void {
raf.scheduleFullRedraw();
}
- setActiveTrace(traceImpl: TraceImpl, traceCtx: TraceContext) {
- this.appCtx.setActiveTrace(traceCtx);
- this.currentTrace = traceImpl;
+ get httpRpc() {
+ return this.appCtx.httpRpc;
}
- forkForPlugin(pluginId: string): AppImpl {
- assertTrue(pluginId != CORE_PLUGIN_ID);
- return new AppImpl(this.appCtx, pluginId);
+ get initialRouteArgs(): RouteArgs {
+ return this.appCtx.initialRouteArgs;
+ }
+
+ get featureFlags(): FeatureFlagManager {
+ return {
+ register: (settings: FlagSettings) => featureFlags.register(settings),
+ };
+ }
+
+ openTraceFromFile(file: File): void {
+ this.openTrace({type: 'FILE', file});
+ }
+
+ openTraceFromUrl(url: string, serializedAppState?: SerializedAppState) {
+ this.openTrace({type: 'URL', url, serializedAppState});
+ }
+
+ openTraceFromBuffer(postMessageArgs: PostedTrace): void {
+ this.openTrace({type: 'ARRAY_BUFFER', ...postMessageArgs});
+ }
+
+ openTraceFromHttpRpc(): void {
+ this.openTrace({type: 'HTTP_RPC'});
+ }
+
+ private async openTrace(src: TraceSource) {
+ this.appCtx.closeCurrentTrace();
+ this.appCtx.isLoadingTrace = true;
+ try {
+ // loadTrace() in trace_loader.ts will do the following:
+ // - Create a new engine.
+ // - Pump the data from the TraceSource into the engine.
+ // - Do the initial queries to build the TraceImpl object
+ // - Call AppImpl.setActiveTrace(TraceImpl)
+ // - Continue with the trace loading logic (track decider, plugins, etc)
+ // - Resolve the promise when everything is done.
+ await loadTrace(this, src);
+ this.omnibox.reset(/* focus= */ false);
+ // loadTrace() internally will call setActiveTrace() and change our
+ // _currentTrace in the middle of its ececution. We cannot wait for
+ // loadTrace to be finished before setting it because some internal
+ // implementation details of loadTrace() rely on that trace to be current
+ // to work properly (mainly the router hash uuid).
+ } catch (err) {
+ this.omnibox.showStatusMessage(`${err}`);
+ throw err;
+ } finally {
+ this.appCtx.isLoadingTrace = false;
+ raf.scheduleFullRedraw();
+ }
+ }
+
+ // Called by trace_loader.ts soon after it has created a new TraceImpl.
+ setActiveTrace(traceImpl: TraceImpl) {
+ this.appCtx.setActiveTrace(traceImpl.__traceCtxForApp);
+ }
+
+ get embeddedMode(): boolean {
+ return this.appCtx.embeddedMode;
+ }
+
+ get testingMode(): boolean {
+ return this.appCtx.testingMode;
+ }
+
+ get isLoadingTrace() {
+ return this.appCtx.isLoadingTrace;
+ }
+
+ get extraSqlPackages(): SqlPackage[] {
+ return this.appCtx.extraSqlPackages;
+ }
+
+ get perfDebugging(): PerfManager {
+ return this.appCtx.perfMgr;
+ }
+
+ get serviceWorkerController(): ServiceWorkerController {
+ return this.appCtx.serviceWorkerController;
+ }
+
+ // Nothing other than TraceImpl's constructor should ever refer to this.
+ // This is necessary to avoid circular dependencies between trace_impl.ts
+ // and app_impl.ts.
+ get __appCtxForTrace() {
+ return this.appCtx;
+ }
+
+ navigate(newHash: string): void {
+ Router.navigate(newHash);
}
}
diff --git a/ui/src/common/cache_manager.ts b/ui/src/core/cache_manager.ts
similarity index 98%
rename from ui/src/common/cache_manager.ts
rename to ui/src/core/cache_manager.ts
index 78c7567..7dd9cd0 100644
--- a/ui/src/common/cache_manager.ts
+++ b/ui/src/core/cache_manager.ts
@@ -18,7 +18,7 @@
* containing it is discarded by Chrome (e.g. because the tab was not used for
* a long time) or when the user accidentally hits reload.
*/
-import {TraceArrayBufferSource, TraceSource} from '../public/trace_source';
+import {TraceArrayBufferSource, TraceSource} from './trace_source';
const TRACE_CACHE_NAME = 'cached_traces';
const TRACE_CACHE_SIZE = 10;
diff --git a/ui/src/common/channels.ts b/ui/src/core/channels.ts
similarity index 97%
rename from ui/src/common/channels.ts
rename to ui/src/core/channels.ts
index d071628..22cf8e0 100644
--- a/ui/src/common/channels.ts
+++ b/ui/src/core/channels.ts
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {raf} from '../core/raf_scheduler';
+import {raf} from './raf_scheduler';
export const DEFAULT_CHANNEL = 'stable';
const CHANNEL_KEY = 'perfettoUiChannel';
diff --git a/ui/src/core/default_plugins.ts b/ui/src/core/default_plugins.ts
index 922ae74..4791e8e 100644
--- a/ui/src/core/default_plugins.ts
+++ b/ui/src/core/default_plugins.ts
@@ -21,51 +21,56 @@
// - Not directly rely on any other plugins.
// - 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',
'dev.perfetto.AndroidCujs',
+ 'dev.perfetto.AndroidDmabuf',
+ 'dev.perfetto.AndroidLog',
'dev.perfetto.AndroidLongBatteryTracing',
'dev.perfetto.AndroidNetwork',
'dev.perfetto.AndroidPerf',
'dev.perfetto.AndroidPerfTraceCounters',
'dev.perfetto.AndroidStartup',
+ 'dev.perfetto.AsyncSlices',
'dev.perfetto.BookmarkletApi',
+ 'dev.perfetto.Counter',
+ 'dev.perfetto.CpuFreq',
+ 'dev.perfetto.CpuProfile',
+ 'dev.perfetto.CpuSlices',
+ 'dev.perfetto.CriticalPath',
+ 'dev.perfetto.DebugTracks',
+ 'dev.perfetto.DeeplinkQuerystring',
+ 'dev.perfetto.FlagsPage',
+ 'dev.perfetto.Frames',
+ 'dev.perfetto.Ftrace',
+ 'dev.perfetto.HeapProfile',
'dev.perfetto.LargeScreensPerf',
+ 'dev.perfetto.MetricsPage',
+ 'dev.perfetto.PerfSamplesProfile',
'dev.perfetto.PinAndroidPerfMetrics',
'dev.perfetto.PinSysUITracks',
+ 'dev.perfetto.Process',
+ 'dev.perfetto.ProcessSummary',
+ 'dev.perfetto.ProcessThreadGroups',
+ 'dev.perfetto.QueryPage',
+ 'dev.perfetto.RecordTrace',
'dev.perfetto.RestorePinnedTrack',
+ 'dev.perfetto.Sched',
+ 'dev.perfetto.Screenshots',
+ 'dev.perfetto.Thread',
+ 'dev.perfetto.ThreadState',
'dev.perfetto.TimelineSync',
+ 'dev.perfetto.TraceInfoPage',
'dev.perfetto.TraceMetadata',
- 'org.kernel.LinuxKernelDevices',
+ 'dev.perfetto.VizPage',
+ 'org.chromium.CriticalUserInteraction',
+ 'org.kernel.LinuxKernelSubsystems',
'org.kernel.SuspendResumeLatency',
- 'perfetto.AndroidLog',
- 'perfetto.Annotation',
- 'perfetto.AsyncSlices',
- 'perfetto.ChromeScrollJank',
'perfetto.CoreCommands',
- 'perfetto.Counter',
- 'perfetto.CpuFreq',
- 'perfetto.CpuProfile',
- 'perfetto.CpuSlices',
- 'perfetto.CriticalPath',
- 'perfetto.CriticalUserInteraction',
- 'perfetto.DebugTracks',
'perfetto.ExampleTraces',
- 'perfetto.Flows',
- 'perfetto.Frames',
- 'perfetto.FtraceRaw',
- 'perfetto.HeapProfile',
- 'perfetto.PerfSamplesProfile',
- 'perfetto.PivotTable',
- 'perfetto.Process',
- 'perfetto.ProcessSummary',
- 'perfetto.ProcessThreadGroups',
- 'perfetto.Sched',
- 'perfetto.Screenshots',
- 'perfetto.Slice',
- 'perfetto.Thread',
- 'perfetto.ThreadSlices',
- 'perfetto.ThreadState',
+ 'perfetto.GlobalGroups',
'perfetto.TrackUtils',
];
diff --git a/ui/src/common/fake_trace_impl.ts b/ui/src/core/fake_trace_impl.ts
similarity index 73%
rename from ui/src/common/fake_trace_impl.ts
rename to ui/src/core/fake_trace_impl.ts
index 793df3e..fc944db 100644
--- a/ui/src/common/fake_trace_impl.ts
+++ b/ui/src/core/fake_trace_impl.ts
@@ -13,9 +13,10 @@
// limitations under the License.
import {Time} from '../base/time';
-import {TraceInfo} from '../public/trace_info';
import {EngineBase} from '../trace_processor/engine';
-import {TraceImpl} from '../core/trace_impl';
+import {AppImpl} from './app_impl';
+import {TraceImpl} from './trace_impl';
+import {TraceInfoImpl} from './trace_info_impl';
export interface FakeTraceImplArgs {
// If true suppresses exceptions when trying to issue a query. This is to
@@ -24,12 +25,20 @@
allowQueries?: boolean;
}
-// This is used:
-// - For testing.
-// - By globals.ts before we have an actual trace loaded, to avoid causing
-// if (!= undefined) checks everywhere.
+let appImplInitialized = false;
+
+export function initializeAppImplForTesting(): AppImpl {
+ if (!appImplInitialized) {
+ appImplInitialized = true;
+ AppImpl.initialize({initialRouteArgs: {}});
+ }
+ return AppImpl.instance;
+}
+
+// For testing purposes only.
export function createFakeTraceImpl(args: FakeTraceImplArgs = {}) {
- const fakeTraceInfo: TraceInfo = {
+ initializeAppImplForTesting();
+ const fakeTraceInfo: TraceInfoImpl = {
source: {type: 'URL', url: ''},
traceTitle: '',
traceUrl: '',
@@ -39,17 +48,22 @@
utcOffset: Time.ZERO,
traceTzOffset: Time.ZERO,
cpus: [],
- gpuCount: 0,
importErrors: 0,
traceType: 'proto',
+ hasFtrace: false,
+ uuid: '',
+ cached: false,
+ downloadable: false,
};
- return TraceImpl.newInstance(
+ return TraceImpl.createInstanceForCore(
+ AppImpl.instance,
new FakeEngine(args.allowQueries ?? false),
fakeTraceInfo,
);
}
class FakeEngine extends EngineBase {
+ readonly mode = 'WASM';
id: string = 'TestEngine';
constructor(private allowQueries: boolean) {
@@ -65,4 +79,6 @@
);
}
}
+
+ [Symbol.dispose]() {}
}
diff --git a/ui/src/core/feature_flags.ts b/ui/src/core/feature_flags.ts
index 0a45c9a..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;
@@ -118,39 +105,21 @@
this.store.save(this.overrides);
}
-}
-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;
+ /**
+ * Modify an override at runtime and save it back to local storage.
+ *
+ * This method operates on the raw JSON in local storage and doesn't require
+ * the presence of a flag to work. Thus, it may be called at any point in the
+ * lifecycle of the flags object.
+ *
+ * @param key - The key of the flag to modify.
+ * @param state - The desired state of the flag override.
+ */
+ patchOverride(key: string, state: OverrideState): void {
+ this.overrides[key] = state;
+ this.save();
+ }
}
class FlagImpl implements Flag {
@@ -233,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/flow_manager.ts b/ui/src/core/flow_manager.ts
index 2707407..efa35bd 100644
--- a/ui/src/core/flow_manager.ts
+++ b/ui/src/core/flow_manager.ts
@@ -19,18 +19,12 @@
import {LONG, NUM, STR_NULL} from '../trace_processor/query_result';
import {
ACTUAL_FRAMES_SLICE_TRACK_KIND,
- THREAD_SLICE_TRACK_KIND,
+ SLICE_TRACK_KIND,
} from '../public/track_kinds';
import {TrackDescriptor, TrackManager} from '../public/track';
-import {
- AreaSelection,
- LegacySelection,
- Selection,
- SelectionManager,
-} from '../public/selection';
+import {AreaSelection, Selection, SelectionManager} from '../public/selection';
import {raf} from './raf_scheduler';
import {Engine} from '../trace_processor/engine';
-import {Workspace} from '../public/workspace';
const SHOW_INDIRECT_PRECEDING_FLOWS_FLAG = featureFlags.register({
id: 'showIndirectPrecedingFlows',
@@ -44,7 +38,7 @@
export class FlowManager {
private _connectedFlows: Flow[] = [];
private _selectedFlows: Flow[] = [];
- private _curSelection?: LegacySelection;
+ private _curSelection?: Selection;
private _focusedFlowIdLeft = -1;
private _focusedFlowIdRight = -1;
private _visibleCategories = new Map<string, boolean>();
@@ -54,7 +48,6 @@
private engine: Engine,
private trackMgr: TrackManager,
private selectionMgr: SelectionManager,
- private getCurWorkspace: () => Workspace,
) {}
// TODO(primiano): the only reason why this is not done in the constructor is
@@ -378,7 +371,7 @@
for (const trackInfo of area.tracks) {
const kind = trackInfo?.tags?.kind;
if (
- kind === THREAD_SLICE_TRACK_KIND ||
+ kind === SLICE_TRACK_KIND ||
kind === ACTUAL_FRAMES_SLICE_TRACK_KIND
) {
if (trackInfo?.tags?.trackIds) {
@@ -442,8 +435,8 @@
// focus. In all other cases the focusedFlowId(Left|Right) will be set to -1.
this._focusedFlowIdLeft = -1;
this._focusedFlowIdRight = -1;
- if (this._curSelection?.kind === 'SLICE') {
- const sliceId = this._curSelection.id;
+ if (this._curSelection?.kind === 'track_event') {
+ const sliceId = this._curSelection.eventId;
for (const flow of connectedFlows) {
if (flow.begin.sliceId === sliceId) {
this._focusedFlowIdRight = flow.id;
@@ -463,9 +456,7 @@
updateFlows(selection: Selection) {
this.initialize();
- const legacySelection =
- selection.kind === 'legacy' ? selection.legacySelection : undefined;
- this._curSelection = legacySelection;
+ this._curSelection = selection;
if (selection.kind === 'empty') {
this.setConnectedFlows([]);
@@ -475,12 +466,8 @@
// TODO(b/155483804): This is a hack as annotation slices don't contain
// flows. We should tidy this up when fixing this bug.
- if (
- legacySelection &&
- legacySelection.kind === 'SLICE' &&
- legacySelection.table !== 'annotation'
- ) {
- this.sliceSelected(legacySelection.id);
+ if (selection.kind === 'track_event' && selection.tableName === 'slice') {
+ this.sliceSelected(selection.eventId);
} else {
this.setConnectedFlows([]);
}
@@ -495,10 +482,10 @@
// Change focus to the next flow event (matching the direction)
focusOtherFlow(direction: FlowDirection) {
const currentSelection = this._curSelection;
- if (!currentSelection || currentSelection.kind !== 'SLICE') {
+ if (!currentSelection || currentSelection.kind !== 'track_event') {
return;
}
- const sliceId = currentSelection.id;
+ const sliceId = currentSelection.eventId;
if (sliceId === -1) {
return;
}
@@ -528,11 +515,11 @@
// Select the slice connected to the flow in focus
moveByFocusedFlow(direction: FlowDirection): void {
const currentSelection = this._curSelection;
- if (!currentSelection || currentSelection.kind !== 'SLICE') {
+ if (!currentSelection || currentSelection.kind !== 'track_event') {
return;
}
- const sliceId = currentSelection.id;
+ const sliceId = currentSelection.eventId;
const flowId =
direction === 'Backward'
? this._focusedFlowIdLeft
@@ -546,17 +533,9 @@
for (const flow of this._connectedFlows) {
if (flow.id === flowId) {
const flowPoint = direction === 'Backward' ? flow.begin : flow.end;
- const track = this.getCurWorkspace().flatTracks.find((t) => {
- if (t.uri === undefined) return false;
- return this.trackMgr
- .getTrack(t.uri)
- ?.tags?.trackIds?.includes(flowPoint.trackId);
+ this.selectionMgr.selectSqlEvent('slice', flowPoint.sliceId, {
+ scrollToSelection: true,
});
- if (track) {
- this.selectionMgr.selectSqlEvent('slice', flowPoint.sliceId, {
- pendingScrollId: flowPoint.sliceId,
- });
- }
}
}
}
diff --git a/ui/src/core/load_trace.ts b/ui/src/core/load_trace.ts
new file mode 100644
index 0000000..c39245f
--- /dev/null
+++ b/ui/src/core/load_trace.ts
@@ -0,0 +1,544 @@
+// 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 {assertExists, assertTrue} from '../base/logging';
+import {time, Time, TimeSpan} from '../base/time';
+import {cacheTrace} from './cache_manager';
+import {
+ getEnabledMetatracingCategories,
+ isMetatracingEnabled,
+} from './metatracing';
+import {featureFlags} from './feature_flags';
+import {Engine, EngineBase} from '../trace_processor/engine';
+import {HttpRpcEngine} from '../trace_processor/http_rpc_engine';
+import {
+ LONG,
+ LONG_NULL,
+ NUM,
+ NUM_NULL,
+ STR,
+} from '../trace_processor/query_result';
+import {WasmEngineProxy} from '../trace_processor/wasm_engine_proxy';
+import {
+ TraceBufferStream,
+ TraceFileStream,
+ TraceHttpStream,
+ TraceStream,
+} from '../core/trace_stream';
+import {
+ deserializeAppStatePhase1,
+ deserializeAppStatePhase2,
+} from './state_serialization';
+import {AppImpl} from './app_impl';
+import {raf} from './raf_scheduler';
+import {TraceImpl} from './trace_impl';
+import {SerializedAppState} from './state_serialization_schema';
+import {TraceSource} from './trace_source';
+import {Router} from '../core/router';
+import {TraceInfoImpl} from './trace_info_impl';
+
+const ENABLE_CHROME_RELIABLE_RANGE_ZOOM_FLAG = featureFlags.register({
+ id: 'enableChromeReliableRangeZoom',
+ name: 'Enable Chrome reliable range zoom',
+ description: 'Automatically zoom into the reliable range for Chrome traces',
+ defaultValue: false,
+});
+
+const ENABLE_CHROME_RELIABLE_RANGE_ANNOTATION_FLAG = featureFlags.register({
+ id: 'enableChromeReliableRangeAnnotation',
+ name: 'Enable Chrome reliable range annotation',
+ description: 'Automatically adds an annotation for the reliable range start',
+ defaultValue: false,
+});
+
+// The following flags control TraceProcessor Config.
+const CROP_TRACK_EVENTS_FLAG = featureFlags.register({
+ id: 'cropTrackEvents',
+ name: 'Crop track events',
+ description: 'Ignores track events outside of the range of interest',
+ defaultValue: false,
+});
+const INGEST_FTRACE_IN_RAW_TABLE_FLAG = featureFlags.register({
+ id: 'ingestFtraceInRawTable',
+ name: 'Ingest ftrace in raw table',
+ description: 'Enables ingestion of typed ftrace events into the raw table',
+ defaultValue: true,
+});
+const ANALYZE_TRACE_PROTO_CONTENT_FLAG = featureFlags.register({
+ id: 'analyzeTraceProtoContent',
+ name: 'Analyze trace proto content',
+ description:
+ 'Enables trace proto content analysis (experimental_proto_content table)',
+ defaultValue: false,
+});
+const FTRACE_DROP_UNTIL_FLAG = featureFlags.register({
+ id: 'ftraceDropUntilAllCpusValid',
+ name: 'Crop ftrace events',
+ description:
+ 'Drop ftrace events until all per-cpu data streams are known to be valid',
+ defaultValue: true,
+});
+
+// TODO(stevegolton): Move this into some global "SQL extensions" file and
+// ensure it's only run once.
+async function defineMaxLayoutDepthSqlFunction(engine: Engine): Promise<void> {
+ await engine.query(`
+ create perfetto function __max_layout_depth(track_count INT, track_ids STRING)
+ returns INT AS
+ select iif(
+ $track_count = 1,
+ (
+ select max_depth
+ from _slice_track_summary
+ where id = cast($track_ids AS int)
+ ),
+ (
+ select max(layout_depth)
+ from experimental_slice_layout($track_ids)
+ )
+ );
+ `);
+}
+
+let lastEngineId = 0;
+
+export async function loadTrace(
+ app: AppImpl,
+ traceSource: TraceSource,
+): Promise<TraceImpl> {
+ updateStatus(app, 'Opening trace');
+ const engineId = `${++lastEngineId}`;
+ const engine = await createEngine(app, engineId);
+ return await loadTraceIntoEngine(app, traceSource, engine);
+}
+
+async function createEngine(
+ app: AppImpl,
+ engineId: string,
+): Promise<EngineBase> {
+ // Check if there is any instance of the trace_processor_shell running in
+ // HTTP RPC mode (i.e. trace_processor_shell -D).
+ let useRpc = false;
+ if (app.httpRpc.newEngineMode === 'USE_HTTP_RPC_IF_AVAILABLE') {
+ useRpc = (await HttpRpcEngine.checkConnection()).connected;
+ }
+ let engine;
+ if (useRpc) {
+ console.log('Opening trace using native accelerator over HTTP+RPC');
+ engine = new HttpRpcEngine(engineId);
+ } else {
+ console.log('Opening trace using built-in WASM engine');
+ engine = new WasmEngineProxy(engineId);
+ engine.resetTraceProcessor({
+ cropTrackEvents: CROP_TRACK_EVENTS_FLAG.get(),
+ ingestFtraceInRawTable: INGEST_FTRACE_IN_RAW_TABLE_FLAG.get(),
+ analyzeTraceProtoContent: ANALYZE_TRACE_PROTO_CONTENT_FLAG.get(),
+ ftraceDropUntilAllCpusValid: FTRACE_DROP_UNTIL_FLAG.get(),
+ });
+ }
+ engine.onResponseReceived = () => raf.scheduleFullRedraw();
+
+ if (isMetatracingEnabled()) {
+ engine.enableMetatrace(assertExists(getEnabledMetatracingCategories()));
+ }
+ return engine;
+}
+
+async function loadTraceIntoEngine(
+ app: AppImpl,
+ traceSource: TraceSource,
+ engine: EngineBase,
+): Promise<TraceImpl> {
+ let traceStream: TraceStream | undefined;
+ let serializedAppState: SerializedAppState | undefined;
+ if (traceSource.type === 'FILE') {
+ traceStream = new TraceFileStream(traceSource.file);
+ } else if (traceSource.type === 'ARRAY_BUFFER') {
+ traceStream = new TraceBufferStream(traceSource.buffer);
+ } else if (traceSource.type === 'URL') {
+ traceStream = new TraceHttpStream(traceSource.url);
+ serializedAppState = traceSource.serializedAppState;
+ } else if (traceSource.type === 'HTTP_RPC') {
+ traceStream = undefined;
+ } else {
+ throw new Error(`Unknown source: ${JSON.stringify(traceSource)}`);
+ }
+
+ // |traceStream| can be undefined in the case when we are using the external
+ // HTTP+RPC endpoint and the trace processor instance has already loaded
+ // a trace (because it was passed as a cmdline argument to
+ // trace_processor_shell). In this case we don't want the UI to load any
+ // file/stream and we just want to jump to the loading phase.
+ if (traceStream !== undefined) {
+ const tStart = performance.now();
+ for (;;) {
+ const res = await traceStream.readChunk();
+ await engine.parse(res.data);
+ const elapsed = (performance.now() - tStart) / 1000;
+ let status = 'Loading trace ';
+ if (res.bytesTotal > 0) {
+ const progress = Math.round((res.bytesRead / res.bytesTotal) * 100);
+ status += `${progress}%`;
+ } else {
+ status += `${Math.round(res.bytesRead / 1e6)} MB`;
+ }
+ status += ` - ${Math.ceil(res.bytesRead / elapsed / 1e6)} MB/s`;
+ updateStatus(app, status);
+ if (res.eof) break;
+ }
+ await engine.notifyEof();
+ } else {
+ assertTrue(engine instanceof HttpRpcEngine);
+ await engine.restoreInitialTables();
+ }
+ for (const p of app.extraSqlPackages) {
+ await engine.registerSqlPackages(p);
+ }
+
+ const traceDetails = await getTraceInfo(engine, traceSource);
+ const trace = TraceImpl.createInstanceForCore(app, engine, traceDetails);
+ app.setActiveTrace(trace);
+
+ const visibleTimeSpan = await computeVisibleTime(
+ traceDetails.start,
+ traceDetails.end,
+ trace.traceInfo.traceType === 'json',
+ engine,
+ );
+
+ trace.timeline.updateVisibleTime(visibleTimeSpan);
+
+ const cacheUuid = traceDetails.cached ? traceDetails.uuid : '';
+ Router.navigate(`#!/viewer?local_cache_key=${cacheUuid}`);
+
+ // Make sure the helper views are available before we start adding tracks.
+ await includeSummaryTables(trace);
+
+ await defineMaxLayoutDepthSqlFunction(engine);
+
+ if (serializedAppState !== undefined) {
+ deserializeAppStatePhase1(serializedAppState, trace);
+ }
+
+ await app.plugins.onTraceLoad(trace, (id) => {
+ updateStatus(app, `Running plugin: ${id}`);
+ });
+
+ decideTabs(trace);
+
+ // Trace Processor doesn't support the reliable range feature for JSON
+ // traces.
+ if (
+ trace.traceInfo.traceType !== 'json' &&
+ ENABLE_CHROME_RELIABLE_RANGE_ANNOTATION_FLAG.get()
+ ) {
+ const reliableRangeStart = await computeTraceReliableRangeStart(engine);
+ if (reliableRangeStart > 0) {
+ trace.notes.addNote({
+ timestamp: reliableRangeStart,
+ color: '#ff0000',
+ text: 'Reliable Range Start',
+ });
+ }
+ }
+
+ for (const callback of trace.getEventListeners('traceready')) {
+ await callback();
+ }
+
+ if (serializedAppState !== undefined) {
+ // Wait that plugins have completed their actions and then proceed with
+ // the final phase of app state restore.
+ // TODO(primiano): this can probably be removed once we refactor tracks
+ // to be URI based and can deal with non-existing URIs.
+ deserializeAppStatePhase2(serializedAppState, trace);
+ }
+
+ return trace;
+}
+
+function decideTabs(trace: TraceImpl) {
+ // Show the list of default tabs, but don't make them active!
+ for (const tabUri of trace.tabs.defaultTabs) {
+ trace.tabs.showTab(tabUri);
+ }
+}
+
+async function includeSummaryTables(trace: TraceImpl) {
+ const engine = trace.engine;
+ updateStatus(trace, 'Creating slice summaries');
+ await engine.query(`include perfetto module viz.summary.slices;`);
+
+ updateStatus(trace, 'Creating counter summaries');
+ await engine.query(`include perfetto module viz.summary.counters;`);
+
+ updateStatus(trace, 'Creating thread summaries');
+ await engine.query(`include perfetto module viz.summary.threads;`);
+
+ updateStatus(trace, 'Creating processes summaries');
+ await engine.query(`include perfetto module viz.summary.processes;`);
+
+ updateStatus(trace, 'Creating track summaries');
+ await engine.query(`include perfetto module viz.summary.tracks;`);
+}
+
+function updateStatus(traceOrApp: TraceImpl | AppImpl, msg: string): void {
+ const showUntilDismissed = 0;
+ traceOrApp.omnibox.showStatusMessage(msg, showUntilDismissed);
+}
+
+async function computeFtraceBounds(engine: Engine): Promise<TimeSpan | null> {
+ const result = await engine.query(`
+ SELECT min(ts) as start, max(ts) as end FROM ftrace_event;
+ `);
+ const {start, end} = result.firstRow({start: LONG_NULL, end: LONG_NULL});
+ if (start !== null && end !== null) {
+ return new TimeSpan(Time.fromRaw(start), Time.fromRaw(end));
+ }
+ return null;
+}
+
+async function computeTraceReliableRangeStart(engine: Engine): Promise<time> {
+ const result =
+ await engine.query(`SELECT RUN_METRIC('chrome/chrome_reliable_range.sql');
+ SELECT start FROM chrome_reliable_range`);
+ const bounds = result.firstRow({start: LONG});
+ return Time.fromRaw(bounds.start);
+}
+
+async function computeVisibleTime(
+ traceStart: time,
+ traceEnd: time,
+ isJsonTrace: boolean,
+ engine: Engine,
+): Promise<TimeSpan> {
+ // initialise visible time to the trace time bounds
+ let visibleStart = traceStart;
+ let visibleEnd = traceEnd;
+
+ // compare start and end with metadata computed by the trace processor
+ const mdTime = await getTracingMetadataTimeBounds(engine);
+ // make sure the bounds hold
+ if (Time.max(visibleStart, mdTime.start) < Time.min(visibleEnd, mdTime.end)) {
+ visibleStart = Time.max(visibleStart, mdTime.start);
+ visibleEnd = Time.min(visibleEnd, mdTime.end);
+ }
+
+ // Trace Processor doesn't support the reliable range feature for JSON
+ // traces.
+ if (!isJsonTrace && ENABLE_CHROME_RELIABLE_RANGE_ZOOM_FLAG.get()) {
+ const reliableRangeStart = await computeTraceReliableRangeStart(engine);
+ visibleStart = Time.max(visibleStart, reliableRangeStart);
+ }
+
+ // Move start of visible window to the first ftrace event
+ const ftraceBounds = await computeFtraceBounds(engine);
+ if (ftraceBounds !== null) {
+ // Avoid moving start of visible window past its end!
+ visibleStart = Time.min(ftraceBounds.start, visibleEnd);
+ }
+ return new TimeSpan(visibleStart, visibleEnd);
+}
+
+async function getTraceInfo(
+ engine: Engine,
+ traceSource: TraceSource,
+): Promise<TraceInfoImpl> {
+ const traceTime = await getTraceTimeBounds(engine);
+
+ // Find the first REALTIME or REALTIME_COARSE clock snapshot.
+ // Prioritize REALTIME over REALTIME_COARSE.
+ const query = `select
+ ts,
+ clock_value as clockValue,
+ clock_name as clockName
+ from clock_snapshot
+ where
+ snapshot_id = 0 AND
+ clock_name in ('REALTIME', 'REALTIME_COARSE')
+ `;
+ const result = await engine.query(query);
+ const it = result.iter({
+ ts: LONG,
+ clockValue: LONG,
+ clockName: STR,
+ });
+
+ let snapshot = {
+ clockName: '',
+ ts: Time.ZERO,
+ clockValue: Time.ZERO,
+ };
+
+ // Find the most suitable snapshot
+ for (let row = 0; it.valid(); it.next(), row++) {
+ if (it.clockName === 'REALTIME') {
+ snapshot = {
+ clockName: it.clockName,
+ ts: Time.fromRaw(it.ts),
+ clockValue: Time.fromRaw(it.clockValue),
+ };
+ break;
+ } else if (it.clockName === 'REALTIME_COARSE') {
+ if (snapshot.clockName !== 'REALTIME') {
+ snapshot = {
+ clockName: it.clockName,
+ ts: Time.fromRaw(it.ts),
+ clockValue: Time.fromRaw(it.clockValue),
+ };
+ }
+ }
+ }
+
+ // The max() is so the query returns NULL if the tz info doesn't exist.
+ const queryTz = `select max(int_value) as tzOffMin from metadata
+ where name = 'timezone_off_mins'`;
+ const resTz = await assertExists(engine).query(queryTz);
+ const tzOffMin = resTz.firstRow({tzOffMin: NUM_NULL}).tzOffMin ?? 0;
+
+ // This is the offset between the unix epoch and ts in the ts domain.
+ // I.e. the value of ts at the time of the unix epoch - usually some large
+ // negative value.
+ const realtimeOffset = Time.sub(snapshot.ts, snapshot.clockValue);
+
+ // Find the previous closest midnight from the trace start time.
+ const utcOffset = Time.getLatestMidnight(traceTime.start, realtimeOffset);
+
+ const traceTzOffset = Time.getLatestMidnight(
+ traceTime.start,
+ Time.sub(realtimeOffset, Time.fromSeconds(tzOffMin * 60)),
+ );
+
+ let traceTitle = '';
+ let traceUrl = '';
+ switch (traceSource.type) {
+ case 'FILE':
+ // Split on both \ and / (because C:\Windows\paths\are\like\this).
+ traceTitle = traceSource.file.name.split(/[/\\]/).pop()!;
+ const fileSizeMB = Math.ceil(traceSource.file.size / 1e6);
+ traceTitle += ` (${fileSizeMB} MB)`;
+ break;
+ case 'URL':
+ traceUrl = traceSource.url;
+ traceTitle = traceUrl.split('/').pop()!;
+ break;
+ case 'ARRAY_BUFFER':
+ traceTitle = traceSource.title;
+ traceUrl = traceSource.url ?? '';
+ const arrayBufferSizeMB = Math.ceil(traceSource.buffer.byteLength / 1e6);
+ traceTitle += ` (${arrayBufferSizeMB} MB)`;
+ break;
+ case 'HTTP_RPC':
+ traceTitle = `RPC @ ${HttpRpcEngine.hostAndPort}`;
+ break;
+ default:
+ break;
+ }
+
+ const traceType = await getTraceType(engine);
+
+ const hasFtrace =
+ (await engine.query(`select * from ftrace_event limit 1`)).numRows() > 0;
+
+ const uuidRes = await engine.query(`select str_value as uuid from metadata
+ where name = 'trace_uuid'`);
+ // trace_uuid can be missing from the TP tables if the trace is empty or in
+ // other similar edge cases.
+ const uuid = uuidRes.numRows() > 0 ? uuidRes.firstRow({uuid: STR}).uuid : '';
+ const cached = await cacheTrace(traceSource, uuid);
+
+ const downloadable =
+ (traceSource.type === 'ARRAY_BUFFER' && !traceSource.localOnly) ||
+ traceSource.type === 'FILE' ||
+ traceSource.type === 'URL';
+
+ return {
+ ...traceTime,
+ traceTitle,
+ traceUrl,
+ realtimeOffset,
+ utcOffset,
+ traceTzOffset,
+ cpus: await getCpus(engine),
+ importErrors: await getTraceErrors(engine),
+ source: traceSource,
+ traceType,
+ hasFtrace,
+ uuid,
+ cached,
+ downloadable,
+ };
+}
+
+async function getTraceType(engine: Engine) {
+ const result = await engine.query(
+ `select str_value from metadata where name = 'trace_type'`,
+ );
+
+ if (result.numRows() === 0) return undefined;
+ return result.firstRow({str_value: STR}).str_value;
+}
+
+async function getTraceTimeBounds(engine: Engine): Promise<TimeSpan> {
+ const result = await engine.query(
+ `select start_ts as startTs, end_ts as endTs from trace_bounds`,
+ );
+ const bounds = result.firstRow({
+ startTs: LONG,
+ endTs: LONG,
+ });
+ return new TimeSpan(Time.fromRaw(bounds.startTs), Time.fromRaw(bounds.endTs));
+}
+
+// TODO(hjd): When streaming must invalidate this somehow.
+async function getCpus(engine: Engine): Promise<number[]> {
+ const cpus = [];
+ const queryRes = await engine.query(
+ 'select distinct(cpu) as cpu from sched order by cpu;',
+ );
+ for (const it = queryRes.iter({cpu: NUM}); it.valid(); it.next()) {
+ cpus.push(it.cpu);
+ }
+ return cpus;
+}
+
+async function getTraceErrors(engine: Engine): Promise<number> {
+ const sql = `SELECT sum(value) as errs FROM stats WHERE severity != 'info'`;
+ const result = await engine.query(sql);
+ return result.firstRow({errs: NUM}).errs;
+}
+
+async function getTracingMetadataTimeBounds(engine: Engine): Promise<TimeSpan> {
+ const queryRes = await engine.query(`select
+ name,
+ int_value as intValue
+ from metadata
+ where name = 'tracing_started_ns' or name = 'tracing_disabled_ns'
+ or name = 'all_data_source_started_ns'`);
+ let startBound = Time.MIN;
+ let endBound = Time.MAX;
+ const it = queryRes.iter({name: STR, intValue: LONG_NULL});
+ for (; it.valid(); it.next()) {
+ const columnName = it.name;
+ const timestamp = it.intValue;
+ if (timestamp === null) continue;
+ if (columnName === 'tracing_disabled_ns') {
+ endBound = Time.min(endBound, Time.fromRaw(timestamp));
+ } else {
+ startBound = Time.max(startBound, Time.fromRaw(timestamp));
+ }
+ }
+
+ return new TimeSpan(startBound, endBound);
+}
diff --git a/ui/src/common/metatracing.ts b/ui/src/core/metatracing.ts
similarity index 98%
rename from ui/src/common/metatracing.ts
rename to ui/src/core/metatracing.ts
index d68db64..3567468 100644
--- a/ui/src/common/metatracing.ts
+++ b/ui/src/core/metatracing.ts
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {featureFlags} from '../core/feature_flags';
+import {featureFlags} from './feature_flags';
import {MetatraceCategories, PerfettoMetatrace} from '../protos';
import protobuf from 'protobufjs/minimal';
diff --git a/ui/src/common/metatracing_unittest.ts b/ui/src/core/metatracing_unittest.ts
similarity index 100%
rename from ui/src/common/metatracing_unittest.ts
rename to ui/src/core/metatracing_unittest.ts
diff --git a/ui/src/core/note_manager.ts b/ui/src/core/note_manager.ts
index b98d872..ab2249a 100644
--- a/ui/src/core/note_manager.ts
+++ b/ui/src/core/note_manager.ts
@@ -19,7 +19,7 @@
NoteManager,
SpanNote,
} from '../public/note';
-import {randomColor} from './colorizer';
+import {randomColor} from '../public/lib/colorizer';
import {raf} from './raf_scheduler';
export class NoteManagerImpl implements NoteManager {
diff --git a/ui/src/core/omnibox_manager.ts b/ui/src/core/omnibox_manager.ts
index df918b7..8a9dfe0 100644
--- a/ui/src/core/omnibox_manager.ts
+++ b/ui/src/core/omnibox_manager.ts
@@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {Optional} from '../base/utils';
import {OmniboxManager, PromptOption} from '../public/omnibox';
import {raf} from './raf_scheduler';
@@ -26,7 +25,7 @@
interface Prompt {
text: string;
options?: PromptOption[];
- resolve(result: Optional<string>): void;
+ resolve(result: string | undefined): void;
}
const defaultMode = OmniboxMode.Search;
@@ -39,6 +38,7 @@
private _omniboxSelectionIndex = 0;
private _forceShortTextSearch = false;
private _textForMode = new Map<OmniboxMode, string>();
+ private _statusMessageContainer: {msg?: string} = {};
get mode(): OmniboxMode {
return this._mode;
@@ -87,22 +87,38 @@
this._pendingCursorPlacement = undefined;
}
- setMode(mode: OmniboxMode): void {
+ setMode(mode: OmniboxMode, focus = true): void {
this._mode = mode;
- this._focusOmniboxNextRender = true;
+ this._focusOmniboxNextRender = focus;
this._omniboxSelectionIndex = 0;
this.rejectPendingPrompt();
raf.scheduleFullRedraw();
}
+ showStatusMessage(msg: string, durationMs = 2000) {
+ const statusMessageContainer: {msg?: string} = {msg};
+ if (durationMs > 0) {
+ setTimeout(() => {
+ statusMessageContainer.msg = undefined;
+ raf.scheduleFullRedraw();
+ }, durationMs);
+ }
+ this._statusMessageContainer = statusMessageContainer;
+ raf.scheduleFullRedraw();
+ }
+
+ get statusMessage(): string | undefined {
+ return this._statusMessageContainer.msg;
+ }
+
// Start a prompt. If options are supplied, the user must pick one from the
// list, otherwise the input is free-form text.
- prompt(text: string, options?: PromptOption[]): Promise<Optional<string>> {
+ prompt(text: string, options?: PromptOption[]): Promise<string | undefined> {
this._mode = OmniboxMode.Prompt;
this._omniboxSelectionIndex = 0;
this.rejectPendingPrompt();
- const promise = new Promise<Optional<string>>((resolve) => {
+ const promise = new Promise<string | undefined>((resolve) => {
this._pendingPrompt = {
text,
options,
@@ -133,9 +149,10 @@
this.setMode(OmniboxMode.Search);
}
- reset(): void {
- this.setMode(defaultMode);
+ reset(focus = true): void {
+ this.setMode(defaultMode, focus);
this._omniboxSelectionIndex = 0;
+ this._statusMessageContainer = {};
raf.scheduleFullRedraw();
}
diff --git a/ui/src/core/page_manager.ts b/ui/src/core/page_manager.ts
new file mode 100644
index 0000000..18e6858
--- /dev/null
+++ b/ui/src/core/page_manager.ts
@@ -0,0 +1,83 @@
+// 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 {assertExists, assertTrue} from '../base/logging';
+import {Registry} from '../base/registry';
+import {PageAttrs, PageHandler, PageWithTraceAttrs} from '../public/page';
+import {Router} from './router';
+import {TraceImpl} from './trace_impl';
+
+export interface PageWithTraceImplAttrs extends PageAttrs {
+ trace: TraceImpl;
+}
+
+// This is to allow internal core classes to get a TraceImpl injected rather
+// than just a Trace.
+type PageHandlerInternal = PageHandler<
+ | m.ComponentTypes<PageWithTraceAttrs>
+ | m.ComponentTypes<PageWithTraceImplAttrs>
+>;
+
+export class PageManagerImpl {
+ private readonly registry = new Registry<PageHandlerInternal>((x) => x.route);
+
+ registerPage(pageHandler: PageHandlerInternal): Disposable {
+ assertTrue(/^\/\w*$/.exec(pageHandler.route) !== null);
+ // The pluginId is injected by the proxy in AppImpl / TraceImpl. If this is
+ // undefined somebody (tests) managed to call this method without proxy.
+ assertExists(pageHandler.pluginId);
+ return this.registry.register(pageHandler);
+ }
+
+ // Called by index.ts upon the main frame redraw callback.
+ renderPageForCurrentRoute(
+ trace: TraceImpl | undefined,
+ ): m.Vnode<PageAttrs> | m.Vnode<PageWithTraceImplAttrs> {
+ const route = Router.parseFragment(location.hash);
+ const res = this.renderPageForRoute(trace, route.page, route.subpage);
+ if (res !== undefined) {
+ return res;
+ }
+ // If either the route doesn't exist or requires a trace but the trace is
+ // not loaded, fall back on the default route /.
+ return assertExists(this.renderPageForRoute(trace, '/', ''));
+ }
+
+ // Will return undefined if either: (1) the route does not exist; (2) the
+ // route exists, it requires a trace, but there is no trace loaded.
+ private renderPageForRoute(
+ coreTrace: TraceImpl | undefined,
+ page: string,
+ subpage: string,
+ ) {
+ const handler = this.registry.tryGet(page);
+ if (handler === undefined) {
+ return undefined;
+ }
+ const pluginId = assertExists(handler?.pluginId);
+ const trace = coreTrace?.forkForPlugin(pluginId);
+ const traceRequired = !handler?.traceless;
+ if (traceRequired && trace === undefined) {
+ return undefined;
+ }
+ if (traceRequired) {
+ return m(handler.page as m.ComponentTypes<PageWithTraceImplAttrs>, {
+ subpage,
+ trace: assertExists(trace),
+ });
+ }
+ return m(handler.page, {subpage, trace});
+ }
+}
diff --git a/ui/src/core/perf.ts b/ui/src/core/perf.ts
deleted file mode 100644
index 6e9afaf..0000000
--- a/ui/src/core/perf.ts
+++ /dev/null
@@ -1,135 +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 m from 'mithril';
-
-const hooks = {
- isDebug: () => false,
- toggleDebug: () => {},
-};
-
-export function setPerfHooks(isDebug: () => boolean, toggleDebug: () => void) {
- hooks.isDebug = isDebug;
- hooks.toggleDebug = toggleDebug;
-}
-
-// Shorthand for if globals perf debug mode is on.
-export const perfDebug = () => hooks.isDebug();
-
-// Returns performance.now() if perfDebug is enabled, otherwise 0.
-// This is needed because calling performance.now is generally expensive
-// and should not be done for every frame.
-export const debugNow = () => (perfDebug() ? performance.now() : 0);
-
-// Returns execution time of |fn| if perf debug mode is on. Returns 0 otherwise.
-export function measure(fn: () => void): number {
- const start = debugNow();
- fn();
- return debugNow() - start;
-}
-
-// Stores statistics about samples, and keeps a fixed size buffer of most recent
-// samples.
-export class RunningStatistics {
- private _count = 0;
- private _mean = 0;
- private _lastValue = 0;
- private _ptr = 0;
-
- private buffer: number[] = [];
-
- constructor(private _maxBufferSize = 10) {}
-
- addValue(value: number) {
- this._lastValue = value;
- if (this.buffer.length >= this._maxBufferSize) {
- this.buffer[this._ptr++] = value;
- if (this._ptr >= this.buffer.length) {
- this._ptr -= this.buffer.length;
- }
- } else {
- this.buffer.push(value);
- }
-
- this._mean = (this._mean * this._count + value) / (this._count + 1);
- this._count++;
- }
-
- get mean() {
- return this._mean;
- }
- get count() {
- return this._count;
- }
- get bufferMean() {
- return this.buffer.reduce((sum, v) => sum + v, 0) / this.buffer.length;
- }
- get bufferSize() {
- return this.buffer.length;
- }
- get maxBufferSize() {
- return this._maxBufferSize;
- }
- get last() {
- return this._lastValue;
- }
-}
-
-// Returns a summary string representation of a RunningStatistics object.
-export function runningStatStr(stat: RunningStatistics) {
- return (
- `Last: ${stat.last.toFixed(2)}ms | ` +
- `Avg: ${stat.mean.toFixed(2)}ms | ` +
- `Avg${stat.maxBufferSize}: ${stat.bufferMean.toFixed(2)}ms`
- );
-}
-
-export interface PerfStatsSource {
- renderPerfStats(): m.Children;
-}
-
-// Globals singleton class that renders performance stats for the whole app.
-class PerfDisplay {
- private containers: PerfStatsSource[] = [];
-
- addContainer(container: PerfStatsSource) {
- this.containers.push(container);
- }
-
- removeContainer(container: PerfStatsSource) {
- const i = this.containers.indexOf(container);
- this.containers.splice(i, 1);
- }
-
- renderPerfStats(src: PerfStatsSource) {
- if (!perfDebug()) return;
- const perfDisplayEl = document.querySelector('.perf-stats');
- if (!perfDisplayEl) return;
- m.render(perfDisplayEl, [
- m('section', src.renderPerfStats()),
- m(
- 'button.close-button',
- {
- onclick: hooks.toggleDebug,
- },
- m('i.material-icons', 'close'),
- ),
- this.containers.map((c, i) =>
- m('section', m('div', `Panel Container ${i + 1}`), c.renderPerfStats()),
- ),
- ]);
- }
-}
-
-export const perfDisplay = new PerfDisplay();
diff --git a/ui/src/core/perf_manager.ts b/ui/src/core/perf_manager.ts
new file mode 100644
index 0000000..e63e7e8
--- /dev/null
+++ b/ui/src/core/perf_manager.ts
@@ -0,0 +1,145 @@
+// 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 m from 'mithril';
+import {raf} from './raf_scheduler';
+import {PerfStats, PerfStatsContainer, runningStatStr} from './perf_stats';
+
+export class PerfManager {
+ private _enabled = false;
+ readonly containers: PerfStatsContainer[] = [];
+
+ get enabled(): boolean {
+ return this._enabled;
+ }
+
+ set enabled(enabled: boolean) {
+ this._enabled = enabled;
+ raf.setPerfStatsEnabled(true);
+ this.containers.forEach((c) => c.setPerfStatsEnabled(enabled));
+ }
+
+ addContainer(container: PerfStatsContainer): Disposable {
+ this.containers.push(container);
+ return {
+ [Symbol.dispose]: () => {
+ const i = this.containers.indexOf(container);
+ this.containers.splice(i, 1);
+ },
+ };
+ }
+
+ renderPerfStats(): m.Children {
+ if (!this._enabled) return;
+ // The rendering of the perf stats UI is atypical. The main issue is that we
+ // want to redraw the mithril component even if there is no full DOM redraw
+ // happening (and we don't want to force redraws as a side effect). So we
+ // return here just a container and handle its rendering ourselves.
+ const perfMgr = this;
+ let removed = false;
+ return m('.perf-stats', {
+ oncreate(vnode: m.VnodeDOM) {
+ const animationFrame = (dom: Element) => {
+ if (removed) return;
+ m.render(dom, m(PerfStatsUi, {perfMgr}));
+ requestAnimationFrame(() => animationFrame(dom));
+ };
+ animationFrame(vnode.dom);
+ },
+ onremove() {
+ removed = true;
+ },
+ });
+ }
+}
+
+// The mithril component that draws the contents of the perf stats box.
+
+interface PerfStatsUiAttrs {
+ perfMgr: PerfManager;
+}
+
+class PerfStatsUi implements m.ClassComponent<PerfStatsUiAttrs> {
+ view({attrs}: m.Vnode<PerfStatsUiAttrs>) {
+ return m(
+ '.perf-stats',
+ {},
+ m('section', this.renderRafSchedulerStats()),
+ m(
+ 'button.close-button',
+ {
+ onclick: () => (attrs.perfMgr.enabled = false),
+ },
+ m('i.material-icons', 'close'),
+ ),
+ attrs.perfMgr.containers.map((c, i) =>
+ m('section', m('div', `Panel Container ${i + 1}`), c.renderPerfStats()),
+ ),
+ );
+ }
+
+ renderRafSchedulerStats() {
+ return m(
+ 'div',
+ m('div', [
+ m(
+ 'button',
+ {onclick: () => raf.scheduleCanvasRedraw()},
+ 'Do Canvas Redraw',
+ ),
+ ' | ',
+ m(
+ 'button',
+ {onclick: () => raf.scheduleFullRedraw()},
+ 'Do Full Redraw',
+ ),
+ ]),
+ m('div', 'Raf Timing ' + '(Total may not add up due to imprecision)'),
+ m(
+ 'table',
+ this.statTableHeader(),
+ this.statTableRow('Actions', raf.perfStats.rafActions),
+ this.statTableRow('Dom', raf.perfStats.rafDom),
+ this.statTableRow('Canvas', raf.perfStats.rafCanvas),
+ this.statTableRow('Total', raf.perfStats.rafTotal),
+ ),
+ m(
+ 'div',
+ 'Dom redraw: ' +
+ `Count: ${raf.perfStats.domRedraw.count} | ` +
+ runningStatStr(raf.perfStats.domRedraw),
+ ),
+ );
+ }
+
+ statTableHeader() {
+ return m(
+ 'tr',
+ m('th', ''),
+ m('th', 'Last (ms)'),
+ m('th', 'Avg (ms)'),
+ m('th', 'Avg-10 (ms)'),
+ );
+ }
+
+ statTableRow(title: string, stat: PerfStats) {
+ return m(
+ 'tr',
+ m('td', title),
+ m('td', stat.last.toFixed(2)),
+ m('td', stat.mean.toFixed(2)),
+ m('td', stat.bufferMean.toFixed(2)),
+ );
+ }
+}
diff --git a/ui/src/core/perf_stats.ts b/ui/src/core/perf_stats.ts
new file mode 100644
index 0000000..3f1eda0
--- /dev/null
+++ b/ui/src/core/perf_stats.ts
@@ -0,0 +1,78 @@
+// 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';
+
+// The interface that every container (e.g. Track Panels) that exposes granular
+// per-container masurements implements to be perf-stats-aware.
+export interface PerfStatsContainer {
+ setPerfStatsEnabled(enable: boolean): void;
+ renderPerfStats(): m.Children;
+}
+
+// Stores statistics about samples, and keeps a fixed size buffer of most recent
+// samples.
+export class PerfStats {
+ private _count = 0;
+ private _mean = 0;
+ private _lastValue = 0;
+ private _ptr = 0;
+
+ private buffer: number[] = [];
+
+ constructor(private _maxBufferSize = 10) {}
+
+ addValue(value: number) {
+ this._lastValue = value;
+ if (this.buffer.length >= this._maxBufferSize) {
+ this.buffer[this._ptr++] = value;
+ if (this._ptr >= this.buffer.length) {
+ this._ptr -= this.buffer.length;
+ }
+ } else {
+ this.buffer.push(value);
+ }
+
+ this._mean = (this._mean * this._count + value) / (this._count + 1);
+ this._count++;
+ }
+
+ get mean() {
+ return this._mean;
+ }
+ get count() {
+ return this._count;
+ }
+ get bufferMean() {
+ return this.buffer.reduce((sum, v) => sum + v, 0) / this.buffer.length;
+ }
+ get bufferSize() {
+ return this.buffer.length;
+ }
+ get maxBufferSize() {
+ return this._maxBufferSize;
+ }
+ get last() {
+ return this._lastValue;
+ }
+}
+
+// Returns a summary string representation of a RunningStatistics object.
+export function runningStatStr(stat: PerfStats) {
+ return (
+ `Last: ${stat.last.toFixed(2)}ms | ` +
+ `Avg: ${stat.mean.toFixed(2)}ms | ` +
+ `Avg${stat.maxBufferSize}: ${stat.bufferMean.toFixed(2)}ms`
+ );
+}
diff --git a/ui/src/core/perf_unittest.ts b/ui/src/core/perf_stats_unittest.ts
similarity index 86%
rename from ui/src/core/perf_unittest.ts
rename to ui/src/core/perf_stats_unittest.ts
index 5ba357c..1b24bf5 100644
--- a/ui/src/core/perf_unittest.ts
+++ b/ui/src/core/perf_stats_unittest.ts
@@ -12,10 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {RunningStatistics} from './perf';
+import {PerfStats} from './perf_stats';
test('buffer size is accurate before reaching max capacity', () => {
- const buf = new RunningStatistics(10);
+ const buf = new PerfStats(10);
for (let i = 0; i < 10; i++) {
buf.addValue(i);
@@ -24,7 +24,7 @@
});
test('buffer size is accurate after reaching max capacity', () => {
- const buf = new RunningStatistics(10);
+ const buf = new PerfStats(10);
for (let i = 0; i < 10; i++) {
buf.addValue(i);
@@ -37,7 +37,7 @@
});
test('buffer mean is accurate before reaching max capacity', () => {
- const buf = new RunningStatistics(10);
+ const buf = new PerfStats(10);
buf.addValue(1);
buf.addValue(2);
@@ -47,7 +47,7 @@
});
test('buffer mean is accurate after reaching max capacity', () => {
- const buf = new RunningStatistics(10);
+ const buf = new PerfStats(10);
for (let i = 0; i < 20; i++) {
buf.addValue(2);
diff --git a/ui/src/core/pivot_table_query_generator.ts b/ui/src/core/pivot_table_query_generator.ts
index 6c94f29..dfaa919 100644
--- a/ui/src/core/pivot_table_query_generator.ts
+++ b/ui/src/core/pivot_table_query_generator.ts
@@ -20,10 +20,7 @@
TableColumn,
} from './pivot_table_types';
import {AreaSelection} from '../public/selection';
-import {
- ASYNC_SLICE_TRACK_KIND,
- THREAD_SLICE_TRACK_KIND,
-} from '../public/track_kinds';
+import {SLICE_TRACK_KIND} from '../public/track_kinds';
interface Table {
name: string;
@@ -177,11 +174,7 @@
function getSelectedTrackSqlIds(area: AreaSelection): number[] {
const selectedTrackKeys: number[] = [];
for (const trackInfo of area.tracks) {
- if (trackInfo?.tags?.kind === THREAD_SLICE_TRACK_KIND) {
- trackInfo.tags.trackIds &&
- selectedTrackKeys.push(...trackInfo.tags.trackIds);
- }
- if (trackInfo?.tags?.kind === ASYNC_SLICE_TRACK_KIND) {
+ if (trackInfo?.tags?.kind === SLICE_TRACK_KIND) {
trackInfo.tags.trackIds &&
selectedTrackKeys.push(...trackInfo.tags.trackIds);
}
diff --git a/ui/src/core/plugin_manager.ts b/ui/src/core/plugin_manager.ts
new file mode 100644
index 0000000..f444618
--- /dev/null
+++ b/ui/src/core/plugin_manager.ts
@@ -0,0 +1,206 @@
+// 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 {assertExists} from '../base/logging';
+import {Registry} from '../base/registry';
+import {App} from '../public/app';
+import {
+ MetricVisualisation,
+ PerfettoPlugin,
+ PerfettoPluginStatic,
+} from '../public/plugin';
+import {Trace} from '../public/trace';
+import {defaultPlugins} from './default_plugins';
+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.
+export const CORE_PLUGIN_ID = '__core__';
+
+function makePlugin(
+ desc: PerfettoPluginStatic<PerfettoPlugin>,
+ trace: Trace,
+): PerfettoPlugin {
+ const PluginClass = desc;
+ return new PluginClass(trace);
+}
+
+// This interface injects AppImpl's methods into PluginManager to avoid
+// circular dependencies between PluginManager and AppImpl.
+export interface PluginAppInterface {
+ forkForPlugin(pluginId: string): App;
+ get trace(): TraceImpl | undefined;
+}
+
+// Contains information about a plugin.
+export interface PluginWrapper {
+ // A reference to the plugin descriptor
+ readonly desc: PerfettoPluginStatic<PerfettoPlugin>;
+
+ // The feature flag used to allow users to change whether this plugin should
+ // be enabled or not.
+ readonly enableFlag: Flag;
+
+ // Keeps track of whether the plugin has been activated or not.
+ active?: boolean;
+
+ // If a trace has been loaded, this object stores the relevant trace-scoped
+ // plugin data
+ traceContext?: {
+ // The concrete plugin instance, created on trace load.
+ readonly instance: PerfettoPlugin;
+
+ // How long it took for the plugin's onTraceLoad() function to run.
+ readonly loadTimeMs: number;
+ };
+}
+
+export class PluginManagerImpl {
+ private readonly registry = new Registry<PluginWrapper>((x) => x.desc.id);
+ private orderedPlugins: Array<PluginWrapper> = [];
+
+ constructor(private readonly app: PluginAppInterface) {}
+
+ registerPlugin(desc: PerfettoPluginStatic<PerfettoPlugin>) {
+ const flagId = `plugin_${desc.id}`;
+ const name = `Plugin: ${desc.id}`;
+ const flag = featureFlags.register({
+ id: flagId,
+ name,
+ description: `Overrides '${desc.id}' plugin.`,
+ defaultValue: defaultPlugins.includes(desc.id),
+ });
+ this.registry.register({
+ desc,
+ enableFlag: flag,
+ });
+ }
+
+ /**
+ * Activates all registered plugins that have not already been registered.
+ *
+ * @param enableOverrides - The list of plugins that are enabled regardless of
+ * the current flag setting.
+ */
+ activatePlugins(enableOverrides: ReadonlyArray<string> = []) {
+ const enabledPlugins = this.registry
+ .valuesAsArray()
+ .filter((p) => p.enableFlag.get() || enableOverrides.includes(p.desc.id));
+
+ this.orderedPlugins = this.sortPluginsTopologically(enabledPlugins);
+
+ this.orderedPlugins.forEach((p) => {
+ if (p.active) return;
+ const app = this.app.forkForPlugin(p.desc.id);
+ p.desc.onActivate?.(app);
+ p.active = true;
+ });
+ }
+
+ async onTraceLoad(
+ traceCore: TraceImpl,
+ beforeEach?: (id: string) => void,
+ ): Promise<void> {
+ // Awaiting all plugins in parallel will skew timing data as later plugins
+ // will spend most of their time waiting for earlier plugins to load.
+ // Running in parallel will have very little performance benefit assuming
+ // most plugins use the same engine, which can only process one query at a
+ // time.
+ for (const p of this.orderedPlugins) {
+ if (p.active) {
+ beforeEach?.(p.desc.id);
+ const trace = traceCore.forkForPlugin(p.desc.id);
+ const before = performance.now();
+ const instance = makePlugin(p.desc, trace);
+ await instance.onTraceLoad?.(trace);
+ const loadTimeMs = performance.now() - before;
+ p.traceContext = {
+ instance,
+ loadTimeMs,
+ };
+ traceCore.trash.defer(() => {
+ p.traceContext = undefined;
+ });
+ }
+ }
+ }
+
+ metricVisualisations(): MetricVisualisation[] {
+ return this.registry.valuesAsArray().flatMap((plugin) => {
+ if (!plugin.active) return [];
+ return plugin.desc.metricVisualisations?.() ?? [];
+ });
+ }
+
+ getAllPlugins() {
+ return this.registry.valuesAsArray();
+ }
+
+ getPluginContainer(id: string): PluginWrapper | undefined {
+ return this.registry.tryGet(id);
+ }
+
+ getPlugin<T extends PerfettoPlugin>(
+ pluginDescriptor: PerfettoPluginStatic<T>,
+ ): T {
+ const plugin = this.registry.get(pluginDescriptor.id);
+ return assertExists(plugin.traceContext).instance as T;
+ }
+
+ /**
+ * Sort plugins in dependency order, ensuring that if a plugin depends on
+ * other plugins, those plugins will appear fist in the list.
+ */
+ private sortPluginsTopologically(
+ plugins: ReadonlyArray<PluginWrapper>,
+ ): Array<PluginWrapper> {
+ const orderedPlugins = new Array<PluginWrapper>();
+ const visiting = new Set<string>();
+
+ const visit = (p: PluginWrapper) => {
+ // Continue if we've already added this plugin, there's no need to add it
+ // again
+ if (orderedPlugins.includes(p)) {
+ return;
+ }
+
+ // Detect circular dependencies
+ if (visiting.has(p.desc.id)) {
+ const cycle = Array.from(visiting).concat(p.desc.id);
+ throw new Error(
+ `Cyclic plugin dependency detected: ${cycle.join(' -> ')}`,
+ );
+ }
+
+ // Temporarily push this plugin onto the visiting stack while visiting
+ // dependencies, to allow circular dependencies to be detected
+ visiting.add(p.desc.id);
+
+ // Recursively visit dependencies
+ p.desc.dependencies?.forEach((d) => {
+ visit(this.registry.get(d.id));
+ });
+
+ visiting.delete(p.desc.id);
+
+ // Finally add this plugin to the ordered list
+ orderedPlugins.push(p);
+ };
+
+ plugins.forEach((p) => visit(p));
+
+ return orderedPlugins;
+ }
+}
diff --git a/ui/src/core/raf_scheduler.ts b/ui/src/core/raf_scheduler.ts
index c6ca0fc..b23379f 100644
--- a/ui/src/core/raf_scheduler.ts
+++ b/ui/src/core/raf_scheduler.ts
@@ -12,39 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import m from 'mithril';
-import {
- debugNow,
- measure,
- perfDebug,
- perfDisplay,
- PerfStatsSource,
- RunningStatistics,
- runningStatStr,
-} from './perf';
+import {PerfStats} from './perf_stats';
-function statTableHeader() {
- return m(
- 'tr',
- m('th', ''),
- m('th', 'Last (ms)'),
- m('th', 'Avg (ms)'),
- m('th', 'Avg-10 (ms)'),
- );
-}
-
-function statTableRow(title: string, stat: RunningStatistics) {
- return m(
- 'tr',
- m('td', title),
- m('td', stat.last.toFixed(2)),
- m('td', stat.mean.toFixed(2)),
- m('td', stat.bufferMean.toFixed(2)),
- );
-}
-
-export type ActionCallback = (nowMs: number) => void;
-export type RedrawCallback = (nowMs: number) => void;
+export type AnimationCallback = (lastFrameMs: number) => void;
+export type RedrawCallback = () => void;
// This class orchestrates all RAFs in the UI. It ensures that there is only
// one animation frame handler overall and that callbacks are called in
@@ -54,146 +25,134 @@
// - redraw callbacks that will repaint canvases.
// This class guarantees that, on each frame, redraw callbacks are called after
// all action callbacks.
-export class RafScheduler implements PerfStatsSource {
- private actionCallbacks = new Set<ActionCallback>();
+export class RafScheduler {
+ // These happen at the beginning of any animation frame. Used by Animation.
+ private animationCallbacks = new Set<AnimationCallback>();
+
+ // These happen during any animaton frame, after the (optional) DOM redraw.
private canvasRedrawCallbacks = new Set<RedrawCallback>();
- private _syncDomRedraw: RedrawCallback = (_) => {};
+
+ // These happen at the end of full (DOM) animation frames.
+ private postRedrawCallbacks = new Array<RedrawCallback>();
+ private syncDomRedrawFn: () => void = () => {};
private hasScheduledNextFrame = false;
private requestedFullRedraw = false;
private isRedrawing = false;
private _shutdown = false;
- private _beforeRedraw: () => void = () => {};
- private _afterRedraw: () => void = () => {};
- private _pendingCallbacks: RedrawCallback[] = [];
+ private recordPerfStats = false;
- private perfStats = {
- rafActions: new RunningStatistics(),
- rafCanvas: new RunningStatistics(),
- rafDom: new RunningStatistics(),
- rafTotal: new RunningStatistics(),
- domRedraw: new RunningStatistics(),
+ readonly perfStats = {
+ rafActions: new PerfStats(),
+ rafCanvas: new PerfStats(),
+ rafDom: new PerfStats(),
+ rafTotal: new PerfStats(),
+ domRedraw: new PerfStats(),
};
- start(cb: ActionCallback) {
- this.actionCallbacks.add(cb);
- this.maybeScheduleAnimationFrame();
+ // Called by frontend/index.ts. syncDomRedrawFn is a function that invokes
+ // m.render() of the root UiMain component.
+ initialize(syncDomRedrawFn: () => void) {
+ this.syncDomRedrawFn = syncDomRedrawFn;
}
- stop(cb: ActionCallback) {
- this.actionCallbacks.delete(cb);
- }
-
- addRedrawCallback(cb: RedrawCallback) {
- this.canvasRedrawCallbacks.add(cb);
- }
-
- removeRedrawCallback(cb: RedrawCallback) {
- this.canvasRedrawCallbacks.delete(cb);
- }
-
- addPendingCallback(cb: RedrawCallback) {
- this._pendingCallbacks.push(cb);
+ // Schedule re-rendering of virtual DOM and canvas.
+ // If a callback is passed it will be executed after the DOM redraw has
+ // completed.
+ scheduleFullRedraw(cb?: RedrawCallback) {
+ this.requestedFullRedraw = true;
+ cb && this.postRedrawCallbacks.push(cb);
+ this.maybeScheduleAnimationFrame(true);
}
// Schedule re-rendering of canvas only.
- scheduleRedraw() {
+ scheduleCanvasRedraw() {
this.maybeScheduleAnimationFrame(true);
}
+ startAnimation(cb: AnimationCallback) {
+ this.animationCallbacks.add(cb);
+ this.maybeScheduleAnimationFrame();
+ }
+
+ stopAnimation(cb: AnimationCallback) {
+ this.animationCallbacks.delete(cb);
+ }
+
+ addCanvasRedrawCallback(cb: RedrawCallback): Disposable {
+ this.canvasRedrawCallbacks.add(cb);
+ const canvasRedrawCallbacks = this.canvasRedrawCallbacks;
+ return {
+ [Symbol.dispose]() {
+ canvasRedrawCallbacks.delete(cb);
+ },
+ };
+ }
+
shutdown() {
this._shutdown = true;
}
- set domRedraw(cb: RedrawCallback) {
- this._syncDomRedraw = cb;
- }
-
- set beforeRedraw(cb: () => void) {
- this._beforeRedraw = cb;
- }
-
- set afterRedraw(cb: () => void) {
- this._afterRedraw = cb;
- }
-
- // Schedule re-rendering of virtual DOM and canvas.
- scheduleFullRedraw() {
- this.requestedFullRedraw = true;
- this.maybeScheduleAnimationFrame(true);
- }
-
- // Schedule a full redraw to happen after a short delay (50 ms).
- // This is done to prevent flickering / visual noise and allow the UI to fetch
- // the initial data from the Trace Processor.
- // There is a chance that someone else schedules a full redraw in the
- // meantime, forcing the flicker, but in practice it works quite well and
- // avoids a lot of complexity for the callers.
- scheduleDelayedFullRedraw() {
- // 50ms is half of the responsiveness threshold (100ms):
- // https://web.dev/rail/#response-process-events-in-under-50ms
- const delayMs = 50;
- setTimeout(() => this.scheduleFullRedraw(), delayMs);
- }
-
- syncDomRedraw(nowMs: number) {
- const redrawStart = debugNow();
- this._syncDomRedraw(nowMs);
- if (perfDebug()) {
- this.perfStats.domRedraw.addValue(debugNow() - redrawStart);
- }
+ setPerfStatsEnabled(enabled: boolean) {
+ this.recordPerfStats = enabled;
+ this.scheduleFullRedraw();
}
get hasPendingRedraws(): boolean {
return this.isRedrawing || this.hasScheduledNextFrame;
}
- private syncCanvasRedraw(nowMs: number) {
- const redrawStart = debugNow();
- if (this.isRedrawing) return;
- this._beforeRedraw();
- this.isRedrawing = true;
- for (const redraw of this.canvasRedrawCallbacks) redraw(nowMs);
- this.isRedrawing = false;
- this._afterRedraw();
- for (const cb of this._pendingCallbacks) {
- cb(nowMs);
+ private syncDomRedraw() {
+ const redrawStart = performance.now();
+ this.syncDomRedrawFn();
+ if (this.recordPerfStats) {
+ this.perfStats.domRedraw.addValue(performance.now() - redrawStart);
}
- this._pendingCallbacks.splice(0, this._pendingCallbacks.length);
- if (perfDebug()) {
- this.perfStats.rafCanvas.addValue(debugNow() - redrawStart);
+ }
+
+ private syncCanvasRedraw() {
+ const redrawStart = performance.now();
+ if (this.isRedrawing) return;
+ this.isRedrawing = true;
+ this.canvasRedrawCallbacks.forEach((cb) => cb());
+ this.isRedrawing = false;
+ if (this.recordPerfStats) {
+ this.perfStats.rafCanvas.addValue(performance.now() - redrawStart);
}
}
private maybeScheduleAnimationFrame(force = false) {
if (this.hasScheduledNextFrame) return;
- if (this.actionCallbacks.size !== 0 || force) {
+ if (this.animationCallbacks.size !== 0 || force) {
this.hasScheduledNextFrame = true;
window.requestAnimationFrame(this.onAnimationFrame.bind(this));
}
}
- private onAnimationFrame(nowMs: number) {
+ private onAnimationFrame(lastFrameMs: number) {
if (this._shutdown) return;
- const rafStart = debugNow();
this.hasScheduledNextFrame = false;
-
const doFullRedraw = this.requestedFullRedraw;
this.requestedFullRedraw = false;
- const actionTime = measure(() => {
- for (const action of this.actionCallbacks) action(nowMs);
- });
+ const tStart = performance.now();
+ this.animationCallbacks.forEach((cb) => cb(lastFrameMs));
+ const tAnim = performance.now();
+ doFullRedraw && this.syncDomRedraw();
+ const tDom = performance.now();
+ this.syncCanvasRedraw();
+ const tCanvas = performance.now();
- const domTime = measure(() => {
- if (doFullRedraw) this.syncDomRedraw(nowMs);
- });
- const canvasTime = measure(() => this.syncCanvasRedraw(nowMs));
-
- const totalRafTime = debugNow() - rafStart;
- this.updatePerfStats(actionTime, domTime, canvasTime, totalRafTime);
- perfDisplay.renderPerfStats(this);
-
+ const animTime = tAnim - tStart;
+ const domTime = tDom - tAnim;
+ const canvasTime = tCanvas - tDom;
+ const totalTime = tCanvas - tStart;
+ this.updatePerfStats(animTime, domTime, canvasTime, totalTime);
this.maybeScheduleAnimationFrame();
+
+ if (doFullRedraw && this.postRedrawCallbacks.length > 0) {
+ const pendingCbs = this.postRedrawCallbacks.splice(0); // splice = clear.
+ pendingCbs.forEach((cb) => cb());
+ }
}
private updatePerfStats(
@@ -202,42 +161,12 @@
canvasTime: number,
totalRafTime: number,
) {
- if (!perfDebug()) return;
+ if (!this.recordPerfStats) return;
this.perfStats.rafActions.addValue(actionsTime);
this.perfStats.rafDom.addValue(domTime);
this.perfStats.rafCanvas.addValue(canvasTime);
this.perfStats.rafTotal.addValue(totalRafTime);
}
-
- renderPerfStats() {
- return m(
- 'div',
- m('div', [
- m('button', {onclick: () => this.scheduleRedraw()}, 'Do Canvas Redraw'),
- ' | ',
- m(
- 'button',
- {onclick: () => this.scheduleFullRedraw()},
- 'Do Full Redraw',
- ),
- ]),
- m('div', 'Raf Timing ' + '(Total may not add up due to imprecision)'),
- m(
- 'table',
- statTableHeader(),
- statTableRow('Actions', this.perfStats.rafActions),
- statTableRow('Dom', this.perfStats.rafDom),
- statTableRow('Canvas', this.perfStats.rafCanvas),
- statTableRow('Total', this.perfStats.rafTotal),
- ),
- m(
- 'div',
- 'Dom redraw: ' +
- `Count: ${this.perfStats.domRedraw.count} | ` +
- runningStatStr(this.perfStats.domRedraw),
- ),
- );
- }
}
export const raf = new RafScheduler();
diff --git a/ui/src/frontend/router.ts b/ui/src/core/router.ts
similarity index 65%
rename from ui/src/frontend/router.ts
rename to ui/src/core/router.ts
index e865b07..259ed6d 100644
--- a/ui/src/frontend/router.ts
+++ b/ui/src/core/router.ts
@@ -13,12 +13,11 @@
// limitations under the License.
import m from 'mithril';
-import {assertExists, assertTrue} from '../base/logging';
-import {PageAttrs} from './pages';
-import {z} from 'zod';
+import {assertTrue} from '../base/logging';
+import {RouteArgs, ROUTE_SCHEMA} from '../public/route_schema';
+import {PageAttrs} from '../public/page';
export const ROUTE_PREFIX = '#!';
-const DEFAULT_ROUTE = '/';
// The set of args that can be set on the route via #!/page?a=1&b2.
// Route args are orthogonal to pages (i.e. should NOT make sense only in a
@@ -36,64 +35,6 @@
// This is client-only. All the routing logic in the Perfetto UI uses only
// this.
-// We use .catch(undefined) on every field below to make sure that passing an
-// invalid value doesn't invalidate the other keys which might be valid.
-// Zod default behaviour is atomic: either everything validates correctly or
-// the whole parsing fails.
-const ROUTE_SCHEMA = z
- .object({
- // The local_cache_key is special and is persisted across navigations.
- local_cache_key: z.string().optional().catch(undefined),
-
- // These are transient and are really set only on startup.
-
- // Are we loading a trace via ABT.
- openFromAndroidBugTool: z.boolean().optional().catch(undefined),
-
- // For permalink hash.
- s: z.string().optional().catch(undefined),
-
- // DEPRECATED: for #!/record?p=cpu subpages (b/191255021).
- p: z.string().optional().catch(undefined),
-
- // For fetching traces from Cloud Storage or local servers
- // as with record_android_trace.
- url: z.string().optional().catch(undefined),
-
- // For connecting to a trace_processor_shell --httpd instance running on a
- // non-standard port. This requires the CSP_WS_PERMISSIVE_PORT flag to relax
- // the Content Security Policy.
- rpc_port: z.string().regex(/\d+/).optional().catch(undefined),
-
- // Override the referrer. Useful for scripts such as
- // record_android_trace to record where the trace is coming from.
- referrer: z.string().optional().catch(undefined),
-
- // For the 'mode' of the UI. For example when the mode is 'embedded'
- // some features are disabled.
- mode: z.enum(['embedded']).optional().catch(undefined),
-
- // Should we hide the sidebar?
- hideSidebar: z.boolean().optional().catch(undefined),
-
- // A comma-separated list of plugins to enable for the current session.
- enablePlugins: z.string().optional().catch(undefined),
-
- // Deep link support
- ts: z.string().optional().catch(undefined),
- dur: z.string().optional().catch(undefined),
- tid: z.string().optional().catch(undefined),
- pid: z.string().optional().catch(undefined),
- query: z.string().optional().catch(undefined),
- visStart: z.string().optional().catch(undefined),
- visEnd: z.string().optional().catch(undefined),
- })
- // default({}) ensures at compile-time that every entry is either optional or
- // has a default value.
- .default({});
-
-type RouteArgs = z.infer<typeof ROUTE_SCHEMA>;
-
function safeParseRoute(rawRoute: unknown): RouteArgs {
const res = ROUTE_SCHEMA.safeParse(rawRoute);
return res.success ? res.data : {};
@@ -135,15 +76,12 @@
// triggered by Router.onRouteChanged().
export class Router {
private readonly recentChanges: number[] = [];
- private routes: RoutesMap;
// frontend/index.ts calls maybeOpenTraceFromRoute() + redraw here.
// This event is decoupled for testing and to avoid circular deps.
onRouteChanged: (route: Route) => void = () => {};
- constructor(routes: RoutesMap) {
- assertExists(routes[DEFAULT_ROUTE]);
- this.routes = routes;
+ constructor() {
window.onhashchange = (e: HashChangeEvent) => this.onHashChange(e);
const route = Router.parseUrl(window.location.href);
this.onRouteChanged(route);
@@ -191,18 +129,6 @@
this.onRouteChanged(newRoute);
}
- // Returns the component for the current route in the URL.
- // If no route matches the URL, returns a component corresponding to
- // |this.defaultRoute|.
- resolve(): m.Vnode<PageAttrs> {
- const route = Router.parseFragment(location.hash);
- let component = this.routes[route.page];
- if (component === undefined) {
- component = assertExists(this.routes[DEFAULT_ROUTE]);
- }
- return m(component, {subpage: route.subpage});
- }
-
static navigate(newHash: string) {
assertTrue(newHash.startsWith(ROUTE_PREFIX));
window.location.hash = newHash;
@@ -305,43 +231,4 @@
throw new Error('History rewriting livelock');
}
}
-
- static getUrlForVersion(versionCode: string): string {
- const url = `${window.location.origin}/${versionCode}/`;
- return url;
- }
-
- static async isVersionAvailable(
- versionCode: string,
- ): Promise<string | undefined> {
- if (versionCode === '') {
- return undefined;
- }
- const controller = new AbortController();
- const timeoutId = setTimeout(() => controller.abort(), 1000);
- const url = Router.getUrlForVersion(versionCode);
- let r;
- try {
- r = await fetch(url, {signal: controller.signal});
- } catch (e) {
- console.error(
- `No UI version for ${versionCode} at ${url}. This is an error if ${versionCode} is a released Perfetto version`,
- );
- return undefined;
- } finally {
- clearTimeout(timeoutId);
- }
- if (!r.ok) {
- return undefined;
- }
- return url;
- }
-
- static navigateToVersion(versionCode: string): void {
- const url = Router.getUrlForVersion(versionCode);
- if (url === undefined) {
- throw new Error(`No URL known for UI version ${versionCode}.`);
- }
- window.location.replace(url);
- }
}
diff --git a/ui/src/frontend/router_unittest.ts b/ui/src/core/router_unittest.ts
similarity index 81%
rename from ui/src/frontend/router_unittest.ts
rename to ui/src/core/router_unittest.ts
index ec65425..89887d0 100644
--- a/ui/src/frontend/router_unittest.ts
+++ b/ui/src/core/router_unittest.ts
@@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+import {PageManagerImpl} from './page_manager';
+import {CORE_PLUGIN_ID} from './plugin_manager';
import {Router} from './router';
const mockComponent = {
@@ -23,36 +25,50 @@
window.location.hash = '';
});
- test('Default route must be defined', () => {
- expect(() => new Router({'/a': mockComponent})).toThrow();
- });
+ const pluginId = CORE_PLUGIN_ID;
+ const traceless = true;
test('Resolves empty route to default component', () => {
- const router = new Router({'/': mockComponent});
+ const pages = new PageManagerImpl();
+ pages.registerPage({route: '/', page: mockComponent, traceless, pluginId});
window.location.hash = '';
- expect(router.resolve().tag).toBe(mockComponent);
+ expect(pages.renderPageForCurrentRoute(undefined).tag).toBe(mockComponent);
});
test('Resolves subpage route to component of main page', () => {
const nonDefaultComponent = {view() {}};
- const router = new Router({
- '/': mockComponent,
- '/a': nonDefaultComponent,
+ const pages = new PageManagerImpl();
+ pages.registerPage({route: '/', page: mockComponent, traceless, pluginId});
+ pages.registerPage({
+ route: '/a',
+ page: nonDefaultComponent,
+ traceless,
+ pluginId,
});
window.location.hash = '#!/a/subpage';
- expect(router.resolve().tag).toBe(nonDefaultComponent);
- expect(router.resolve().attrs.subpage).toBe('/subpage');
+ expect(pages.renderPageForCurrentRoute(undefined).tag).toBe(
+ nonDefaultComponent,
+ );
+ expect(pages.renderPageForCurrentRoute(undefined).attrs.subpage).toBe(
+ '/subpage',
+ );
});
test('Pass empty subpage if not found in URL', () => {
const nonDefaultComponent = {view() {}};
- const router = new Router({
- '/': mockComponent,
- '/a': nonDefaultComponent,
+ const pages = new PageManagerImpl();
+ pages.registerPage({route: '/', page: mockComponent, traceless, pluginId});
+ pages.registerPage({
+ route: '/a',
+ page: nonDefaultComponent,
+ traceless,
+ pluginId,
});
window.location.hash = '#!/a';
- expect(router.resolve().tag).toBe(nonDefaultComponent);
- expect(router.resolve().attrs.subpage).toBe('');
+ expect(pages.renderPageForCurrentRoute(undefined).tag).toBe(
+ nonDefaultComponent,
+ );
+ expect(pages.renderPageForCurrentRoute(undefined).attrs.subpage).toBe('');
});
});
diff --git a/ui/src/core/scroll_helper.ts b/ui/src/core/scroll_helper.ts
index 59b7b11..c732b91 100644
--- a/ui/src/core/scroll_helper.ts
+++ b/ui/src/core/scroll_helper.ts
@@ -35,7 +35,7 @@
// See comments in ScrollToArgs for the intended semantics.
scrollTo(args: ScrollToArgs) {
const {time, track} = args;
- raf.scheduleRedraw();
+ raf.scheduleCanvasRedraw();
if (time !== undefined) {
if (time.end === undefined) {
diff --git a/ui/src/core/search_manager.ts b/ui/src/core/search_manager.ts
index 9f82742..bc56498 100644
--- a/ui/src/core/search_manager.ts
+++ b/ui/src/core/search_manager.ts
@@ -16,8 +16,8 @@
import {searchSegment} from '../base/binary_search';
import {assertExists, assertTrue} from '../base/logging';
import {sqliteString} from '../base/string_utils';
-import {Time} from '../base/time';
-import {exists, Optional} from '../base/utils';
+import {Time, TimeSpan} from '../base/time';
+import {exists} from '../base/utils';
import {ResultStepEventHandler} from '../public/search';
import {
ANDROID_LOGS_TRACK_KIND,
@@ -44,6 +44,7 @@
export class SearchManagerImpl {
private _searchGeneration = 0;
private _searchText = '';
+ private _searchWindow?: TimeSpan;
private _results?: SearchResults;
private _resultIndex = -1;
private _searchInProgress = false;
@@ -82,6 +83,7 @@
this._searchInProgress = false;
if (text !== '') {
this._searchInProgress = true;
+ this._searchWindow = this._timeline?.visibleWindow.toTimeSpan();
this._limiter.schedule(async () => {
await this.executeSearch();
this._searchInProgress = false;
@@ -104,12 +106,11 @@
}
private stepInternal(reverse = false) {
- if (this._timeline === undefined) return;
+ if (this._searchWindow === undefined) return;
if (this._results === undefined) return;
+
const index = this._resultIndex;
- const vizWindow = this._timeline.visibleWindow.toTimeSpan();
- const startNs = vizWindow.start;
- const endNs = vizWindow.end;
+ const {start: startNs, end: endNs} = this._searchWindow;
const currentTs = this._results.tses[index];
// If the value of |this._results.totalResults| is 0,
@@ -193,13 +194,14 @@
private async executeSearch() {
const search = this._searchText;
+ const window = this._searchWindow;
const searchLiteral = escapeSearchQuery(this._searchText);
const generation = this._searchGeneration;
const engine = this._engine;
const trackManager = this._trackManager;
const workspace = this._workspace;
- if (!engine || !trackManager || !workspace) {
+ if (!engine || !trackManager || !workspace || !window) {
return;
}
@@ -316,7 +318,7 @@
utid: NUM,
});
for (; it.valid(); it.next()) {
- let track: Optional<string> = undefined;
+ let track: string | undefined = undefined;
if (it.source === 'cpu') {
track = trackUrisByCpu.get(it.sourceId);
diff --git a/ui/src/core/selection_aggregation_manager.ts b/ui/src/core/selection_aggregation_manager.ts
index 01f93a8..cba366f 100644
--- a/ui/src/core/selection_aggregation_manager.ts
+++ b/ui/src/core/selection_aggregation_manager.ts
@@ -14,7 +14,6 @@
import {AsyncLimiter} from '../base/async_limiter';
import {isString} from '../base/object_utils';
-import {Optional} from '../base/utils';
import {AggregateData, Column, ColumnDef, Sorting} from '../public/aggregation';
import {AreaSelection, AreaSelectionAggregator} from '../public/selection';
import {Engine} from '../trace_processor/engine';
@@ -27,7 +26,7 @@
private _aggregators = new Array<AreaSelectionAggregator>();
private _aggregatedData = new Map<string, AggregateData>();
private _sorting = new Map<string, Sorting>();
- private _currentArea: Optional<AreaSelection> = undefined;
+ private _currentArea: AreaSelection | undefined = undefined;
constructor(engine: Engine) {
this.engine = engine;
@@ -63,7 +62,7 @@
});
}
- getSortingPrefs(aggregatorId: string): Optional<Sorting> {
+ getSortingPrefs(aggregatorId: string): Sorting | undefined {
return this._sorting.get(aggregatorId);
}
@@ -97,7 +96,7 @@
return this._aggregators;
}
- getAggregatedData(aggregatorId: string): Optional<AggregateData> {
+ getAggregatedData(aggregatorId: string): AggregateData | undefined {
return this._aggregatedData.get(aggregatorId);
}
diff --git a/ui/src/core/selection_manager.ts b/ui/src/core/selection_manager.ts
index 6b0651e..8644301 100644
--- a/ui/src/core/selection_manager.ts
+++ b/ui/src/core/selection_manager.ts
@@ -15,32 +15,35 @@
import {assertTrue, assertUnreachable} from '../base/logging';
import {
Selection,
- LegacySelection,
Area,
SelectionOpts,
SelectionManager,
AreaSelectionAggregator,
SqlSelectionResolver,
+ TrackEventSelection,
} from '../public/selection';
-import {duration, Time, time, TimeSpan} from '../base/time';
-import {
- GenericSliceDetailsTabConfig,
- GenericSliceDetailsTabConfigBase,
-} from '../public/details_panel';
+import {TimeSpan} from '../base/time';
import {raf} from './raf_scheduler';
-import {exists, Optional} from '../base/utils';
+import {exists} from '../base/utils';
import {TrackManagerImpl} from './track_manager';
-import {SelectionResolver} from './selection_resolver';
import {Engine} from '../trace_processor/engine';
import {ScrollHelper} from './scroll_helper';
import {NoteManagerImpl} from './note_manager';
-import {AsyncLimiter} from '../base/async_limiter';
import {SearchResult} from '../public/search';
import {SelectionAggregationManager} from './selection_aggregation_manager';
+import {AsyncLimiter} from '../base/async_limiter';
+import m from 'mithril';
+import {SerializedSelection} from './state_serialization_schema';
const INSTANT_FOCUS_DURATION = 1n;
const INCOMPLETE_SLICE_DURATION = 30_000n;
+interface SelectionDetailsPanel {
+ isLoading: boolean;
+ render(): m.Children;
+ serializatonState(): unknown;
+}
+
// There are two selection-related states in this class.
// 1. _selection: This is the "input" / locator of the selection, what other
// parts of the codebase specify (e.g., a tuple of trackUri + eventId) to say
@@ -50,15 +53,15 @@
// `_selection` is valid, this is filled in the near future. Doing so
// requires querying the SQL engine, which is an async operation.
export class SelectionManagerImpl implements SelectionManager {
+ private readonly detailsPanelLimiter = new AsyncLimiter();
private _selection: Selection = {kind: 'empty'};
- private _selectedDetails?: LegacySelectionDetails;
- private _selectionResolver: SelectionResolver;
- private _pendingScrollId?: number;
private _aggregationManager: SelectionAggregationManager;
// Incremented every time _selection changes.
- private _selectionGeneration = 0;
- private _limiter = new AsyncLimiter();
private readonly selectionResolvers = new Array<SqlSelectionResolver>();
+ private readonly detailsPanels = new WeakMap<
+ Selection,
+ SelectionDetailsPanel
+ >();
constructor(
engine: Engine,
@@ -67,7 +70,6 @@
private scrollHelper: ScrollHelper,
private onSelectionChange: (s: Selection, opts: SelectionOpts) => void,
) {
- this._selectionResolver = new SelectionResolver(engine);
this._aggregationManager = new SelectionAggregationManager(
engine.getProxy('SelectionAggregationManager'),
);
@@ -81,15 +83,16 @@
this.setSelection({kind: 'empty'});
}
- selectTrackEvent(trackUri: string, eventId: number, opts?: SelectionOpts) {
- this.setSelection(
- {
- kind: 'single',
- trackUri,
- eventId,
- },
- opts,
- );
+ async selectTrackEvent(
+ trackUri: string,
+ eventId: number,
+ opts?: SelectionOpts,
+ ) {
+ this.selectTrackEventInternal(trackUri, eventId, opts);
+ }
+
+ selectTrack(trackUri: string, opts?: SelectionOpts) {
+ this.setSelection({kind: 'track', trackUri}, opts);
}
selectNote(args: {id: string}, opts?: SelectionOpts) {
@@ -102,19 +105,53 @@
);
}
- selectArea(args: Area, opts?: SelectionOpts): void {
- const {start, end} = args;
+ selectArea(area: Area, opts?: SelectionOpts): void {
+ const {start, end} = area;
assertTrue(start <= end);
+
+ // In the case of area selection, the caller provides a list of trackUris.
+ // However, all the consumer want to access the resolved TrackDescriptor.
+ // Rather than delegating this to the various consumers, we resolve them
+ // now once and for all and place them in the selection object.
+ const tracks = [];
+ for (const uri of area.trackUris) {
+ const trackDescr = this.trackManager.getTrack(uri);
+ if (trackDescr === undefined) continue;
+ tracks.push(trackDescr);
+ }
+
this.setSelection(
{
+ ...area,
kind: 'area',
- tracks: [],
- ...args,
+ tracks,
},
opts,
);
}
+ deserialize(serialized: SerializedSelection | undefined) {
+ if (serialized === undefined) {
+ return;
+ }
+ switch (serialized.kind) {
+ case 'TRACK_EVENT':
+ this.selectTrackEventInternal(
+ serialized.trackKey,
+ parseInt(serialized.eventId),
+ undefined,
+ serialized.detailsPanel,
+ );
+ break;
+ case 'AREA':
+ this.selectArea({
+ start: serialized.start,
+ end: serialized.end,
+ trackUris: serialized.trackUris,
+ });
+ }
+ }
+
toggleTrackAreaSelection(trackUri: string) {
const curSelection = this._selection;
if (curSelection.kind !== 'area') return;
@@ -125,7 +162,7 @@
} else {
trackUris = trackUris.filter((t) => t !== trackUri);
}
- this.setSelection({
+ this.selectArea({
...curSelection,
trackUris,
});
@@ -153,66 +190,18 @@
}
});
}
- this.setSelection({
+ this.selectArea({
...curSelection,
trackUris: newTrackUris,
});
}
- // There is no matching addLegacy as we did not support multi-single
- // selection with the legacy selection system.
- selectLegacy(legacySelection: LegacySelection, opts?: SelectionOpts): void {
- this.setSelection(
- {
- kind: 'legacy',
- legacySelection,
- },
- opts,
- );
- }
-
- selectGenericSlice(args: {
- id: number;
- sqlTableName: string;
- start: time;
- duration: duration;
- trackUri: string;
- detailsPanelConfig: {
- kind: string;
- config: GenericSliceDetailsTabConfigBase;
- };
- }): void {
- const detailsPanelConfig: GenericSliceDetailsTabConfig = {
- id: args.id,
- ...args.detailsPanelConfig.config,
- };
- this.setSelection({
- kind: 'legacy',
- legacySelection: {
- kind: 'GENERIC_SLICE',
- id: args.id,
- sqlTableName: args.sqlTableName,
- start: args.start,
- duration: args.duration,
- trackUri: args.trackUri,
- detailsPanelConfig: {
- kind: args.detailsPanelConfig.kind,
- config: detailsPanelConfig,
- },
- },
- });
- }
-
get selection(): Selection {
return this._selection;
}
- get legacySelection(): LegacySelection | null {
- return toLegacySelection(this._selection);
- }
-
- get legacySelectionDetails(): LegacySelectionDetails | undefined {
- return this._selectedDetails;
+ getDetailsPanelForSelection(): SelectionDetailsPanel | undefined {
+ return this.detailsPanels.get(this._selection);
}
registerSqlSelectionResolver(resolver: SqlSelectionResolver): void {
@@ -222,7 +211,7 @@
async resolveSqlEvent(
sqlTableName: string,
id: number,
- ): Promise<Selection | undefined> {
+ ): Promise<{eventId: number; trackUri: string} | undefined> {
const matchingResolvers = this.selectionResolvers.filter(
(r) => r.sqlTableName === sqlTableName,
);
@@ -240,70 +229,25 @@
selectSqlEvent(sqlTableName: string, id: number, opts?: SelectionOpts): void {
this.resolveSqlEvent(sqlTableName, id).then((selection) => {
- selection && this.setSelection(selection, opts);
+ selection &&
+ this.selectTrackEvent(selection.trackUri, selection.eventId, opts);
});
}
private setSelection(selection: Selection, opts?: SelectionOpts) {
- if (selection.kind === 'area') {
- // In the case of area selection, the caller provides a list of trackUris.
- // However, all the consumer want to access the resolved TrackDescriptor.
- // Rather than delegating this to the various consumers, we resolve them
- // now once and for all and place them in the selection object.
- const tracks = [];
- for (const uri of selection.trackUris) {
- const trackDescr = this.trackManager.getTrack(uri);
- if (trackDescr === undefined) continue;
- tracks.push(trackDescr);
- }
- selection = {...selection, tracks};
- }
-
this._selection = selection;
- this._pendingScrollId = opts?.pendingScrollId;
this.onSelectionChange(selection, opts ?? {});
- const generation = ++this._selectionGeneration;
raf.scheduleFullRedraw();
+ if (opts?.scrollToSelection) {
+ this.scrollToCurrentSelection();
+ }
+
if (this._selection.kind === 'area') {
this._aggregationManager.aggregateArea(this._selection);
} else {
this._aggregationManager.clear();
}
-
- // The code below is to avoid flickering while switching selection. There
- // are three cases here:
- // 1. The async code resolves the selection quickly. In this case we
- // "atomically" switch the _selectedSlice in one animation frame, without
- // flashing white. The continuation below will clear the timeout.
- // 2. The async code resolves the selection but takes time. The timeout
- // below will kick in and clear the selection; later the async
- // continuation will set it to the current slice.
- // 3. The async code below fails to resolve the seleciton. We just clear
- // the selection.
- const clearOnTimeout = setTimeout(() => {
- if (this._selectionGeneration !== generation) return;
- this._selectedDetails = undefined;
- raf.scheduleFullRedraw();
- }, 50);
-
- const legacySel = this.legacySelection;
- if (!exists(legacySel)) return;
-
- this._limiter.schedule(async () => {
- const details =
- await this._selectionResolver?.resolveSelection(legacySel);
- raf.scheduleFullRedraw();
- clearTimeout(clearOnTimeout);
- this._selectedDetails = undefined;
- if (details == undefined) return;
- if (this._selectionGeneration !== generation) return;
- this._selectedDetails = details;
- if (exists(legacySel.id) && legacySel.id === this._pendingScrollId) {
- this._pendingScrollId = undefined;
- this.scrollToCurrentSelection();
- }
- });
}
selectSearchResult(searchResult: SearchResult) {
@@ -313,36 +257,27 @@
}
switch (source) {
case 'track':
- this.scrollHelper.scrollTo({
- track: {uri: trackUri, expandGroup: true},
+ this.selectTrack(trackUri, {
+ clearSearch: false,
+ scrollToSelection: true,
});
break;
case 'cpu':
this.selectSqlEvent('sched_slice', eventId, {
clearSearch: false,
- pendingScrollId: eventId,
+ scrollToSelection: true,
switchToCurrentSelectionTab: true,
});
break;
case 'log':
- this.selectLegacy(
- {
- kind: 'LOG',
- id: eventId,
- trackUri,
- },
- {
- clearSearch: false,
- switchToCurrentSelectionTab: true,
- },
- );
+ // TODO(stevegolton): Get log selection working.
break;
case 'slice':
// Search results only include slices from the slice table for now.
// When we include annotations we need to pass the correct table.
this.selectSqlEvent('slice', eventId, {
clearSearch: false,
- pendingScrollId: eventId,
+ scrollToSelection: true,
switchToCurrentSelectionTab: true,
});
break;
@@ -352,20 +287,102 @@
}
scrollToCurrentSelection() {
- const selection = this.legacySelection;
- if (!exists(selection)) return;
- const uri = selection.trackUri;
- this.findTimeRangeOfSelection().then((range) => {
- // The selection changed meanwhile.
- if (this.legacySelection !== selection) return;
- this.scrollHelper.scrollTo({
- time: range ? {...range} : undefined,
- track: uri ? {uri: uri, expandGroup: true} : undefined,
- });
+ const uri = (() => {
+ switch (this.selection.kind) {
+ case 'track_event':
+ case 'track':
+ return this.selection.trackUri;
+ // TODO(stevegolton): Handle scrolling to area and note selections.
+ default:
+ return undefined;
+ }
+ })();
+ const range = this.findFocusRangeOfSelection();
+ this.scrollHelper.scrollTo({
+ time: range ? {...range} : undefined,
+ track: uri ? {uri: uri, expandGroup: true} : undefined,
});
}
- async findTimeRangeOfSelection(): Promise<Optional<TimeSpan>> {
+ // Finds the time range range that we should actually focus on - using dummy
+ // values for instant and incomplete slices, so we don't end up super zoomed
+ // in.
+ private findFocusRangeOfSelection(): TimeSpan | undefined {
+ const sel = this.selection;
+ if (sel.kind === 'track_event') {
+ // The focus range of slices is different to that of the actual span
+ if (sel.dur === -1n) {
+ return TimeSpan.fromTimeAndDuration(sel.ts, INCOMPLETE_SLICE_DURATION);
+ } else if (sel.dur === 0n) {
+ return TimeSpan.fromTimeAndDuration(sel.ts, INSTANT_FOCUS_DURATION);
+ } else {
+ return TimeSpan.fromTimeAndDuration(sel.ts, sel.dur);
+ }
+ } else {
+ return this.findTimeRangeOfSelection();
+ }
+ }
+
+ private async selectTrackEventInternal(
+ trackUri: string,
+ eventId: number,
+ opts?: SelectionOpts,
+ serializedDetailsPanel?: unknown,
+ ) {
+ const details = await this.trackManager
+ .getTrack(trackUri)
+ ?.track.getSelectionDetails?.(eventId);
+
+ if (!exists(details)) {
+ throw new Error('Unable to resolve selection details');
+ }
+
+ const selection: TrackEventSelection = {
+ ...details,
+ kind: 'track_event',
+ trackUri,
+ eventId,
+ };
+ this.createTrackEventDetailsPanel(selection, serializedDetailsPanel);
+ this.setSelection(selection, opts);
+ }
+
+ private createTrackEventDetailsPanel(
+ selection: TrackEventSelection,
+ serializedState: unknown,
+ ) {
+ const td = this.trackManager.getTrack(selection.trackUri);
+ if (!td) {
+ return;
+ }
+ const panel = td.track.detailsPanel?.(selection);
+ if (!panel) {
+ return;
+ }
+
+ if (panel.serialization && serializedState !== undefined) {
+ const res = panel.serialization.schema.safeParse(serializedState);
+ if (res.success) {
+ panel.serialization.state = res.data;
+ }
+ }
+
+ const detailsPanel: SelectionDetailsPanel = {
+ render: () => panel.render(),
+ serializatonState: () => panel.serialization?.state,
+ isLoading: true,
+ };
+ // Associate this details panel with this selection object
+ this.detailsPanels.set(selection, detailsPanel);
+
+ this.detailsPanelLimiter.schedule(async () => {
+ await panel?.load?.(selection);
+ detailsPanel.isLoading = false;
+ raf.scheduleFullRedraw();
+ });
+ }
+
+ findTimeRangeOfSelection(): TimeSpan | undefined {
const sel = this.selection;
if (sel.kind === 'area') {
return new TimeSpan(sel.start, sel.end);
@@ -385,35 +402,8 @@
assertUnreachable(kind);
}
}
- } else if (sel.kind === 'single') {
- const uri = sel.trackUri;
- const bounds = await this.trackManager
- .getTrack(uri)
- ?.getEventBounds?.(sel.eventId);
- if (bounds) {
- return TimeSpan.fromTimeAndDuration(bounds.ts, bounds.dur);
- }
- return undefined;
- }
-
- const legacySel = this.legacySelection;
- if (!exists(legacySel)) {
- return undefined;
- }
-
- if (
- legacySel.kind === 'SCHED_SLICE' ||
- legacySel.kind === 'SLICE' ||
- legacySel.kind === 'THREAD_STATE'
- ) {
- return findTimeRangeOfSlice(this._selectedDetails ?? {});
- } else if (legacySel.kind === 'LOG') {
- // TODO(hjd): Make focus selection work for logs.
- } else if (legacySel.kind === 'GENERIC_SLICE') {
- return findTimeRangeOfSlice({
- ts: legacySel.start,
- dur: legacySel.duration,
- });
+ } else if (sel.kind === 'track_event') {
+ return TimeSpan.fromTimeAndDuration(sel.ts, sel.dur);
}
return undefined;
@@ -423,51 +413,3 @@
return this._aggregationManager;
}
}
-
-function toLegacySelection(selection: Selection): LegacySelection | null {
- switch (selection.kind) {
- case 'area':
- case 'single':
- case 'empty':
- case 'note':
- return null;
- case 'union':
- for (const child of selection.selections) {
- const result = toLegacySelection(child);
- if (result !== null) {
- return result;
- }
- }
- return null;
- case 'legacy':
- return selection.legacySelection;
- default:
- assertUnreachable(selection);
- return null;
- }
-}
-
-// Returns the start and end points of a slice-like object If slice is instant
-// or incomplete, dummy time will be returned which instead.
-function findTimeRangeOfSlice(slice: {ts?: time; dur?: duration}): TimeSpan {
- if (exists(slice.ts) && exists(slice.dur)) {
- if (slice.dur === -1n) {
- return TimeSpan.fromTimeAndDuration(slice.ts, INCOMPLETE_SLICE_DURATION);
- } else if (slice.dur === 0n) {
- return TimeSpan.fromTimeAndDuration(slice.ts, INSTANT_FOCUS_DURATION);
- } else {
- return TimeSpan.fromTimeAndDuration(slice.ts, slice.dur);
- }
- } else {
- // TODO(primiano): unclear why we dont return undefined here.
- return new TimeSpan(Time.INVALID, Time.INVALID);
- }
-}
-
-export interface LegacySelectionDetails {
- ts?: time;
- dur?: duration;
- // Additional information for sched selection, used to draw the wakeup arrow.
- wakeupTs?: time;
- wakerCpu?: number;
-}
diff --git a/ui/src/core/selection_resolver.ts b/ui/src/core/selection_resolver.ts
deleted file mode 100644
index d5cf174..0000000
--- a/ui/src/core/selection_resolver.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright (C) 2019 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 {Optional} from '../base/utils';
-import {Engine} from '../trace_processor/engine';
-import {LegacySelection} from '../public/selection';
-import {LegacySelectionDetails} from './selection_manager';
-import {getSched, getSchedWakeupInfo} from '../trace_processor/sql_utils/sched';
-import {
- asSchedSqlId,
- asSliceSqlId,
-} from '../trace_processor/sql_utils/core_types';
-import {getThreadState} from '../trace_processor/sql_utils/thread_state';
-import {getSlice} from '../trace_processor/sql_utils/slice';
-
-// This class queries the TP for the details on a specific slice that has
-// been clicked.
-export class SelectionResolver {
- constructor(private engine: Engine) {}
-
- async resolveSelection(
- selection: LegacySelection,
- ): Promise<Optional<LegacySelectionDetails>> {
- if (selection.kind === 'SCHED_SLICE') {
- const sched = await getSched(this.engine, asSchedSqlId(selection.id));
- if (sched === undefined) {
- return undefined;
- }
- const wakeup = await getSchedWakeupInfo(this.engine, sched);
- return {
- ts: sched.ts,
- dur: sched.dur,
- wakeupTs: wakeup?.wakeupTs,
- wakerCpu: wakeup?.wakerCpu,
- };
- } else if (selection.kind === 'THREAD_STATE') {
- const threadState = await getThreadState(this.engine, selection.id);
- if (threadState === undefined) {
- return undefined;
- }
- return {
- ts: threadState.ts,
- dur: threadState.dur,
- };
- } else if (selection.kind === 'SLICE') {
- const slice = await getSlice(this.engine, asSliceSqlId(selection.id));
- if (slice === undefined) {
- return undefined;
- }
- return {
- ts: slice.ts,
- dur: slice.dur,
- };
- }
- return undefined;
- }
-}
diff --git a/ui/src/core/sidebar_manager.ts b/ui/src/core/sidebar_manager.ts
index 96d492b..9de9b90 100644
--- a/ui/src/core/sidebar_manager.ts
+++ b/ui/src/core/sidebar_manager.ts
@@ -14,11 +14,39 @@
import {Registry} from '../base/registry';
import {SidebarManager, SidebarMenuItem} from '../public/sidebar';
+import {raf} from './raf_scheduler';
+
+export type SidebarMenuItemInternal = SidebarMenuItem & {
+ id: string; // A unique id generated by this class at registration time.
+};
export class SidebarManagerImpl implements SidebarManager {
- readonly menuItems = new Registry<SidebarMenuItem>((m) => m.commandId);
+ readonly enabled: boolean;
+ private _visible: boolean;
+ private lastId = 0;
- addMenuItem(menuItem: SidebarMenuItem): Disposable {
- return this.menuItems.register(menuItem);
+ readonly menuItems = new Registry<SidebarMenuItemInternal>((m) => m.id);
+
+ constructor(args: {disabled?: boolean; hidden?: boolean}) {
+ this.enabled = !args.disabled;
+ this._visible = !args.hidden;
+ }
+
+ addMenuItem(item: SidebarMenuItem): Disposable {
+ // Assign a unique id to every item. This simplifies the job of the mithril
+ // component that renders the sidebar.
+ const id = `sidebar_${++this.lastId}`;
+ const itemInt: SidebarMenuItemInternal = {...item, id};
+ return this.menuItems.register(itemInt);
+ }
+
+ public get visible() {
+ return this._visible;
+ }
+
+ public toggleVisibility() {
+ if (!this.enabled) return;
+ this._visible = !this._visible;
+ raf.scheduleFullRedraw();
}
}
diff --git a/ui/src/common/state_serialization.ts b/ui/src/core/state_serialization.ts
similarity index 70%
rename from ui/src/common/state_serialization.ts
rename to ui/src/core/state_serialization.ts
index ce7e2a1..0ad4872 100644
--- a/ui/src/common/state_serialization.ts
+++ b/ui/src/core/state_serialization.ts
@@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {globals} from '../frontend/globals';
import {
SERIALIZED_STATE_VERSION,
APP_STATE_SCHEMA,
@@ -22,9 +21,7 @@
SerializedAppState,
} from './state_serialization_schema';
import {TimeSpan} from '../base/time';
-import {ProfileType} from '../public/selection';
-import {TraceImpl} from '../core/trace_impl';
-import {AppImpl} from '../core/app_impl';
+import {TraceImpl} from './trace_impl';
// When it comes to serialization & permalinks there are two different use cases
// 1. Uploading the current trace in a Cloud Storage (GCS) file AND serializing
@@ -53,11 +50,11 @@
* in a permalink (@see permalink.ts).
* @returns A @type {SerializedAppState} object, @see state_serialization_schema.ts
*/
-export function serializeAppState(): SerializedAppState {
- const vizWindow = globals.timeline.visibleWindow.toTimeSpan();
+export function serializeAppState(trace: TraceImpl): SerializedAppState {
+ const vizWindow = trace.timeline.visibleWindow.toTimeSpan();
const notes = new Array<SerializedNote>();
- for (const [id, note] of globals.noteManager.notes.entries()) {
+ for (const [id, note] of trace.notes.notes.entries()) {
if (note.noteType === 'DEFAULT') {
notes.push({
noteType: 'DEFAULT',
@@ -79,48 +76,27 @@
}
const selection = new Array<SerializedSelection>();
- const stateSel = globals.selectionManager.selection;
- if (stateSel.kind === 'single') {
+ const stateSel = trace.selection.selection;
+ if (stateSel.kind === 'track_event') {
selection.push({
kind: 'TRACK_EVENT',
trackKey: stateSel.trackUri,
eventId: stateSel.eventId.toString(),
+ detailsPanel: trace.selection
+ .getDetailsPanelForSelection()
+ ?.serializatonState(),
});
- } else if (stateSel.kind === 'legacy') {
- // TODO(primiano): get rid of these once we unify selection.
- switch (stateSel.legacySelection.kind) {
- case 'SLICE':
- selection.push({
- kind: 'LEGACY_SLICE',
- id: stateSel.legacySelection.id,
- });
- break;
- case 'SCHED_SLICE':
- selection.push({
- kind: 'LEGACY_SCHED_SLICE',
- id: stateSel.legacySelection.id,
- });
- break;
- case 'THREAD_STATE':
- selection.push({
- kind: 'LEGACY_THREAD_STATE',
- id: stateSel.legacySelection.id,
- });
- break;
- case 'HEAP_PROFILE':
- selection.push({
- kind: 'LEGACY_HEAP_PROFILE',
- id: stateSel.legacySelection.id,
- upid: stateSel.legacySelection.upid,
- ts: stateSel.legacySelection.ts,
- type: stateSel.legacySelection.type,
- });
- }
+ } else if (stateSel.kind === 'area') {
+ selection.push({
+ kind: 'AREA',
+ trackUris: stateSel.trackUris,
+ start: stateSel.start,
+ end: stateSel.end,
+ });
}
const plugins = new Array<SerializedPluginState>();
- const pluginsStore =
- AppImpl.instance.trace?.getPluginStoreForSerialization() ?? {};
+ const pluginsStore = trace.getPluginStoreForSerialization();
for (const [id, pluginState] of Object.entries(pluginsStore)) {
plugins.push({id, state: pluginState});
@@ -128,7 +104,7 @@
return {
version: SERIALIZED_STATE_VERSION,
- pinnedTracks: globals.workspace.pinnedTracks
+ pinnedTracks: trace.workspace.pinnedTracks
.map((t) => t.uri)
.filter((uri) => uri !== undefined),
viewport: {
@@ -189,17 +165,21 @@
* This function gets invoked after the trace controller has run and all plugins
* have executed.
* @param appState the .data object returned by parseAppState() when successful.
+ * @param trace the target trace object to manipulate.
*/
-export function deserializeAppStatePhase2(appState: SerializedAppState): void {
+export function deserializeAppStatePhase2(
+ appState: SerializedAppState,
+ trace: TraceImpl,
+): void {
if (appState.viewport !== undefined) {
- globals.timeline.updateVisibleTime(
+ trace.timeline.updateVisibleTime(
new TimeSpan(appState.viewport.start, appState.viewport.end),
);
}
// Restore the pinned tracks, if they exist.
for (const uri of appState.pinnedTracks) {
- const track = globals.workspace.findTrackByUri(uri);
+ const track = trace.workspace.findTrackByUri(uri);
if (track) {
track.pin();
}
@@ -214,9 +194,9 @@
text: note.text,
};
if (note.noteType === 'DEFAULT') {
- globals.noteManager.addNote({...commonArgs});
+ trace.notes.addNote({...commonArgs});
} else if (note.noteType === 'SPAN') {
- globals.noteManager.addSpanNote({
+ trace.notes.addSpanNote({
...commonArgs,
start: commonArgs.timestamp,
end: note.end,
@@ -225,33 +205,7 @@
}
// Restore the selection
- const sel = appState.selection[0];
- if (sel !== undefined) {
- const selMgr = globals.selectionManager;
- switch (sel.kind) {
- case 'TRACK_EVENT':
- selMgr.selectTrackEvent(sel.trackKey, parseInt(sel.eventId));
- break;
- case 'LEGACY_SCHED_SLICE':
- selMgr.selectSqlEvent('sched_slice', sel.id);
- break;
- case 'LEGACY_SLICE':
- selMgr.selectSqlEvent('slice', sel.id);
- break;
- case 'LEGACY_THREAD_STATE':
- selMgr.selectSqlEvent('thread_slice', sel.id);
- break;
- case 'LEGACY_HEAP_PROFILE':
- selMgr.selectLegacy({
- kind: 'HEAP_PROFILE',
- id: sel.id,
- upid: sel.upid,
- ts: sel.ts,
- type: sel.type as ProfileType,
- });
- break;
- }
- }
+ trace.selection.deserialize(appState.selection[0]);
}
/**
diff --git a/ui/src/common/state_serialization_schema.ts b/ui/src/core/state_serialization_schema.ts
similarity index 88%
rename from ui/src/common/state_serialization_schema.ts
rename to ui/src/core/state_serialization_schema.ts
index 27737a9..c42772a 100644
--- a/ui/src/common/state_serialization_schema.ts
+++ b/ui/src/core/state_serialization_schema.ts
@@ -34,16 +34,13 @@
// This is actually the track URI but let's not rename for backwards compat
trackKey: z.string(),
eventId: z.string(),
+ detailsPanel: z.unknown(),
}),
- z.object({kind: z.literal('LEGACY_SLICE'), id: z.number()}),
- z.object({kind: z.literal('LEGACY_SCHED_SLICE'), id: z.number()}),
- z.object({kind: z.literal('LEGACY_THREAD_STATE'), id: z.number()}),
z.object({
- kind: z.literal('LEGACY_HEAP_PROFILE'),
- id: z.number(),
- upid: z.number(),
- ts: zTime,
- type: z.string(),
+ kind: z.literal('AREA'),
+ start: zTime,
+ end: zTime,
+ trackUris: z.array(z.string()),
}),
]);
diff --git a/ui/src/core/tab_manager.ts b/ui/src/core/tab_manager.ts
index 8911f19..3179bd1 100644
--- a/ui/src/core/tab_manager.ts
+++ b/ui/src/core/tab_manager.ts
@@ -21,6 +21,8 @@
tab?: TabDescriptor;
}
+export type TabPanelVisibility = 'COLLAPSED' | 'VISIBLE' | 'FULLSCREEN';
+
/**
* Stores tab & current selection section registries.
* Keeps track of tab lifecycles.
@@ -32,6 +34,8 @@
private _instantiatedTabs = new Map<string, TabDescriptor>();
private _openTabs: string[] = []; // URIs of the tabs open.
private _currentTab: string = 'current_selection';
+ private _tabPanelVisibility: TabPanelVisibility = 'COLLAPSED';
+ private _tabPanelVisibilityChanged = false;
[Symbol.dispose]() {
// Dispose of all tabs that are currently alive
@@ -79,6 +83,18 @@
}
}
this._currentTab = uri;
+
+ // The first time that we show a tab, auto-expand the tab bottom panel.
+ // However, if the user has later collapsed the panel (hence if
+ // _tabPanelVisibilityChanged == true), don't insist and leave things as
+ // they are.
+ if (
+ !this._tabPanelVisibilityChanged &&
+ this._tabPanelVisibility === 'COLLAPSED'
+ ) {
+ this.setTabPanelVisibility('VISIBLE');
+ }
+
raf.scheduleFullRedraw();
}
@@ -182,6 +198,27 @@
return tabs;
}
+ setTabPanelVisibility(visibility: TabPanelVisibility): void {
+ this._tabPanelVisibility = visibility;
+ this._tabPanelVisibilityChanged = true;
+ raf.scheduleFullRedraw();
+ }
+
+ toggleTabPanelVisibility(): void {
+ switch (this._tabPanelVisibility) {
+ case 'COLLAPSED':
+ case 'FULLSCREEN':
+ return this.setTabPanelVisibility('VISIBLE');
+ case 'VISIBLE':
+ this.setTabPanelVisibility('COLLAPSED');
+ break;
+ }
+ }
+
+ get tabPanelVisibility() {
+ return this._tabPanelVisibility;
+ }
+
/**
* Call onShow() on this tab.
* @param tab The tab to initialize.
diff --git a/ui/src/core/timeline.ts b/ui/src/core/timeline.ts
index a71cace..bc8a613 100644
--- a/ui/src/core/timeline.ts
+++ b/ui/src/core/timeline.ts
@@ -22,11 +22,6 @@
import {timestampFormat, TimestampFormat} from './timestamp_format';
import {TraceInfo} from '../public/trace_info';
-interface Range {
- start?: number;
- end?: number;
-}
-
const MIN_DURATION = 10;
/**
@@ -36,18 +31,52 @@
export class TimelineImpl implements Timeline {
private _visibleWindow: HighPrecisionTimeSpan;
private _hoverCursorTimestamp?: time;
+ private _highlightedSliceId?: number;
+ private _hoveredNoteTimestamp?: time;
- // This is a giant hack. Basically, removing visible window from the state
- // means that we no longer update the state periodically while navigating
- // the timeline, which means that controllers are not running. This keeps
- // making null edits to the store which triggers the controller to run.
- // This function is injected from Globals.initialize() to avoid a dependency
- // on the State.
- // TODO(stevegolton): When we remove controllers, we can remove this!
- retriggerControllersOnChange: () => void = () => {};
+ // TODO(stevegolton): These are currently only referenced by the cpu slice
+ // tracks and the process summary tracks. We should just make this a local
+ // property of the cpu slice tracks and ignore them in the process tracks.
+ private _hoveredUtid?: number;
+ private _hoveredPid?: number;
+
+ get highlightedSliceId() {
+ return this._highlightedSliceId;
+ }
+
+ set highlightedSliceId(x) {
+ this._highlightedSliceId = x;
+ raf.scheduleCanvasRedraw();
+ }
+
+ get hoveredNoteTimestamp() {
+ return this._hoveredNoteTimestamp;
+ }
+
+ set hoveredNoteTimestamp(x) {
+ this._hoveredNoteTimestamp = x;
+ raf.scheduleCanvasRedraw();
+ }
+
+ get hoveredUtid() {
+ return this._hoveredUtid;
+ }
+
+ set hoveredUtid(x) {
+ this._hoveredUtid = x;
+ raf.scheduleCanvasRedraw();
+ }
+
+ get hoveredPid() {
+ return this._hoveredPid;
+ }
+
+ set hoveredPid(x) {
+ this._hoveredPid = x;
+ raf.scheduleCanvasRedraw();
+ }
// This is used to calculate the tracks within a Y range for area selection.
- areaY: Range = {};
private _selectedArea?: Area;
constructor(private readonly traceInfo: TraceInfo) {
@@ -66,8 +95,7 @@
.scale(ratio, centerPoint, MIN_DURATION)
.fitWithin(this.traceInfo.start, this.traceInfo.end);
- raf.scheduleRedraw();
- this.retriggerControllersOnChange();
+ raf.scheduleCanvasRedraw();
}
panVisibleWindow(delta: number) {
@@ -75,8 +103,7 @@
.translate(delta)
.fitWithin(this.traceInfo.start, this.traceInfo.end);
- raf.scheduleRedraw();
- this.retriggerControllersOnChange();
+ raf.scheduleCanvasRedraw();
}
// Given a timestamp, if |ts| is not currently in view move the view to
@@ -109,7 +136,7 @@
deselectArea() {
this._selectedArea = undefined;
- raf.scheduleRedraw();
+ raf.scheduleCanvasRedraw();
}
get selectedArea(): Area | undefined {
@@ -133,8 +160,7 @@
.clampDuration(MIN_DURATION)
.fitWithin(this.traceInfo.start, this.traceInfo.end);
- raf.scheduleRedraw();
- this.retriggerControllersOnChange();
+ raf.scheduleCanvasRedraw();
}
// Get the bounds of the visible window as a high-precision time span
@@ -148,7 +174,7 @@
set hoverCursorTimestamp(t: time | undefined) {
this._hoverCursorTimestamp = t;
- raf.scheduleRedraw();
+ raf.scheduleCanvasRedraw();
}
// Offset between t=0 and the configured time domain.
diff --git a/ui/src/core/trace_impl.ts b/ui/src/core/trace_impl.ts
index 641c9d4..abed7f5 100644
--- a/ui/src/core/trace_impl.ts
+++ b/ui/src/core/trace_impl.ts
@@ -13,13 +13,11 @@
// limitations under the License.
import {DisposableStack} from '../base/disposable_stack';
-import {assertTrue} from '../base/logging';
import {createStore, Migrate, Store} from '../base/store';
import {TimelineImpl} from './timeline';
import {Command} from '../public/command';
-import {Trace} from '../public/trace';
+import {EventListeners, Trace} from '../public/trace';
import {ScrollToArgs, setScrollToFunction} from '../public/scroll_helper';
-import {TraceInfo} from '../public/trace_info';
import {TrackDescriptor} from '../public/track';
import {EngineBase, EngineProxy} from '../trace_processor/engine';
import {CommandManagerImpl} from './command_manager';
@@ -37,7 +35,22 @@
import {SearchResult} from '../public/search';
import {PivotTableManager} from './pivot_table_manager';
import {FlowManager} from './flow_manager';
-import {AppContext, AppImpl, CORE_PLUGIN_ID} from './app_impl';
+import {AppContext, AppImpl} from './app_impl';
+import {PluginManagerImpl} from './plugin_manager';
+import {RouteArgs} from '../public/route_schema';
+import {CORE_PLUGIN_ID} from './plugin_manager';
+import {Analytics} from '../public/analytics';
+import {getOrCreate} from '../base/utils';
+import {fetchWithProgress} from '../base/http_utils';
+import {TraceInfoImpl} from './trace_info_impl';
+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';
+import {PerfManager} from './perf_manager';
/**
* Handles the per-trace state of the UI
@@ -48,6 +61,7 @@
* per trace per plugin.
*/
export class TraceContext implements Disposable {
+ private readonly pluginInstances = new Map<string, TraceImpl>();
readonly appCtx: AppContext;
readonly engine: EngineBase;
readonly omniboxMgr = new OmniboxManagerImpl();
@@ -55,7 +69,7 @@
readonly selectionMgr: SelectionManagerImpl;
readonly tabMgr = new TabManagerImpl();
readonly timeline: TimelineImpl;
- readonly traceInfo: TraceInfo;
+ readonly traceInfo: TraceInfoImpl;
readonly trackMgr = new TrackManagerImpl();
readonly workspaceMgr = new WorkspaceManagerImpl();
readonly noteMgr = new NoteManagerImpl();
@@ -64,10 +78,17 @@
readonly scrollHelper: ScrollHelper;
readonly pivotTableMgr;
readonly trash = new DisposableStack();
+ readonly eventListeners = new Map<keyof EventListeners, Array<unknown>>();
- constructor(gctx: AppContext, engine: EngineBase, traceInfo: TraceInfo) {
+ // List of errors that were encountered while loading the trace by the TS
+ // code. These are on top of traceInfo.importErrors, which is a summary of
+ // what TraceProcessor reports on the stats table at import time.
+ readonly loadingErrors: string[] = [];
+
+ constructor(gctx: AppContext, engine: EngineBase, traceInfo: TraceInfoImpl) {
this.appCtx = gctx;
this.engine = engine;
+ this.trash.use(engine);
this.traceInfo = traceInfo;
this.timeline = new TimelineImpl(traceInfo);
@@ -103,7 +124,6 @@
engine.getProxy('FlowManager'),
this.trackMgr,
this.selectionMgr,
- () => this.workspaceMgr.currentWorkspace,
);
this.searchMgr = new SearchManagerImpl({
@@ -122,27 +142,31 @@
if (clearSearch) {
this.searchMgr.reset();
}
- if (switchToCurrentSelectionTab) {
+ if (switchToCurrentSelectionTab && selection.kind !== 'empty') {
this.tabMgr.showCurrentSelectionTab();
}
- // pendingScrollId is handled by SelectionManager internally.
if (selection.kind === 'area') {
this.pivotTableMgr.setSelectionArea(selection);
}
this.flowMgr.updateFlows(selection);
-
- // TODO(primiano): this is temporarily necessary until we kill
- // controllers. The flow controller needs to be re-kicked when we change
- // the selection.
- rerunControllersFunction?.();
}
private onResultStep(searchResult: SearchResult) {
this.selectionMgr.selectSearchResult(searchResult);
}
+ // Gets or creates an instance of TraceImpl backed by the current TraceContext
+ // for the given plugin.
+ forPlugin(pluginId: string) {
+ return getOrCreate(this.pluginInstances, pluginId, () => {
+ const appForPlugin = this.appCtx.forPlugin(pluginId);
+ return new TraceImpl(appForPlugin, this);
+ });
+ }
+
+ // Called by AppContext.closeCurrentTrace().
[Symbol.dispose]() {
this.trash.dispose();
}
@@ -156,32 +180,35 @@
* for the core.
*/
export class TraceImpl implements Trace {
- private appImpl: AppImpl;
- private traceCtx: TraceContext;
+ private readonly appImpl: AppImpl;
+ private readonly traceCtx: TraceContext;
// This is not the original Engine base, rather an EngineProxy based on the
// same engineBase.
- private engineProxy: EngineProxy;
- private trackMgrProxy: TrackManagerImpl;
- private commandMgrProxy: CommandManagerImpl;
- private sidebarProxy: SidebarManagerImpl;
+ private readonly engineProxy: EngineProxy;
+ private readonly trackMgrProxy: TrackManagerImpl;
+ private readonly commandMgrProxy: CommandManagerImpl;
+ private readonly sidebarProxy: SidebarManagerImpl;
+ private readonly pageMgrProxy: PageManagerImpl;
// This is called by TraceController when loading a new trace, soon after the
// engine has been set up. It obtains a new TraceImpl for the core. From that
// we can fork sibling instances (i.e. bound to the same TraceContext) for
// the various plugins.
- static newInstance(engine: EngineBase, traceInfo: TraceInfo): TraceImpl {
- const appCtx = AppContext.instance;
- const appImpl = AppImpl.instance;
- const traceCtx = new TraceContext(appCtx, engine, traceInfo);
- const traceImpl = new TraceImpl(appImpl, traceCtx);
- appImpl.setActiveTrace(traceImpl, traceCtx);
-
- // TODO(primiano): remove this injection once we plumb Trace everywhere.
- setScrollToFunction((x: ScrollToArgs) => traceCtx.scrollHelper.scrollTo(x));
- return traceImpl;
+ static createInstanceForCore(
+ appImpl: AppImpl,
+ engine: EngineBase,
+ traceInfo: TraceInfoImpl,
+ ): TraceImpl {
+ const traceCtx = new TraceContext(
+ appImpl.__appCtxForTrace,
+ engine,
+ traceInfo,
+ );
+ return traceCtx.forPlugin(CORE_PLUGIN_ID);
}
+ // Only called by TraceContext.forPlugin().
constructor(appImpl: AppImpl, ctx: TraceContext) {
const pluginId = appImpl.pluginId;
this.appImpl = appImpl;
@@ -219,6 +246,20 @@
return disposable;
},
});
+
+ this.pageMgrProxy = createProxy(ctx.appCtx.pageMgr, {
+ registerPage(pageHandler: PageHandler): Disposable {
+ const disposable = appImpl.pages.registerPage({
+ ...pageHandler,
+ pluginId: appImpl.pluginId,
+ });
+ traceUnloadTrash.use(disposable);
+ return disposable;
+ },
+ });
+
+ // TODO(primiano): remove this injection once we plumb Trace everywhere.
+ setScrollToFunction((x: ScrollToArgs) => ctx.scrollHelper.scrollTo(x));
}
scrollTo(where: ScrollToArgs): void {
@@ -229,8 +270,7 @@
// another plugin. This is effectively a way to "fork" the core instance and
// create the N instances for plugins.
forkForPlugin(pluginId: string) {
- assertTrue(pluginId != CORE_PLUGIN_ID);
- return new TraceImpl(this.appImpl.forkForPlugin(pluginId), this.traceCtx);
+ return this.traceCtx.forPlugin(pluginId);
}
mountStore<T>(migrate: Migrate<T>): Store<T> {
@@ -244,6 +284,31 @@
return this.traceCtx.pluginSerializableState;
}
+ async getTraceFile(): Promise<Blob> {
+ const src = this.traceInfo.source;
+ if (this.traceInfo.downloadable) {
+ if (src.type === 'ARRAY_BUFFER') {
+ return new Blob([src.buffer]);
+ } else if (src.type === 'FILE') {
+ return src.file;
+ } else if (src.type === 'URL') {
+ return await fetchWithProgress(src.url, (progressPercent: number) =>
+ this.omnibox.showStatusMessage(
+ `Downloading trace ${progressPercent}%`,
+ ),
+ );
+ }
+ }
+ // Not available in HTTP+RPC mode. Rather than propagating an undefined,
+ // show a graceful error (the ERR:trace_src will be intercepted by
+ // error_dialog.ts). We expect all users of this feature to not be able to
+ // do anything useful if we returned undefined (other than showing the same
+ // dialog).
+ // The caller was supposed to check that traceInfo.downloadable === true
+ // before calling this. Throwing while downloadable is true is a bug.
+ throw new Error(`Cannot getTraceFile(${src.type})`);
+ }
+
get openerPluginArgs(): {[key: string]: unknown} | undefined {
const traceSource = this.traceCtx.traceInfo.source;
if (traceSource.type !== 'ARRAY_BUFFER') {
@@ -253,6 +318,10 @@
return (pluginArgs ?? {})[this.pluginId];
}
+ get trace() {
+ return this;
+ }
+
get engine() {
return this.engineProxy;
}
@@ -285,7 +354,7 @@
return this.traceCtx.selectionMgr;
}
- get traceInfo(): TraceInfo {
+ get traceInfo(): TraceInfoImpl {
return this.traceCtx.traceInfo;
}
@@ -301,6 +370,14 @@
return this.traceCtx.flowMgr;
}
+ get loadingErrors(): ReadonlyArray<string> {
+ return this.traceCtx.loadingErrors;
+ }
+
+ addLoadingError(err: string) {
+ this.traceCtx.loadingErrors.push(err);
+ }
+
// App interface implementation.
get pluginId(): string {
@@ -315,41 +392,94 @@
return this.sidebarProxy;
}
+ get pages(): PageManager {
+ return this.pageMgrProxy;
+ }
+
get omnibox(): OmniboxManagerImpl {
return this.appImpl.omnibox;
}
- scheduleRedraw(): void {
- this.appImpl.scheduleRedraw();
+ get plugins(): PluginManagerImpl {
+ return this.appImpl.plugins;
+ }
+
+ get analytics(): Analytics {
+ return this.appImpl.analytics;
+ }
+
+ get initialRouteArgs(): RouteArgs {
+ return this.appImpl.initialRouteArgs;
+ }
+
+ get featureFlags(): FeatureFlagManager {
+ return {
+ register: (settings: FlagSettings) => featureFlags.register(settings),
+ };
+ }
+
+ scheduleFullRedraw(): void {
+ this.appImpl.scheduleFullRedraw();
+ }
+
+ navigate(newHash: string): void {
+ 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],
+ ): void {
+ const listeners = getOrCreate(
+ this.traceCtx.eventListeners,
+ event,
+ () => [],
+ );
+ listeners.push(callback);
+ }
+
+ getEventListeners<T extends keyof EventListeners>(
+ event: T,
+ ): ReadonlyArray<EventListeners[T]> {
+ const listeners = this.traceCtx.eventListeners.get(event);
+ if (listeners) {
+ return listeners as ReadonlyArray<EventListeners[T]>;
+ } else {
+ return [];
+ }
+ }
+
+ get perfDebugging(): PerfManager {
+ return this.appImpl.perfDebugging;
+ }
+
+ get trash(): DisposableStack {
+ return this.traceCtx.trash;
+ }
+
+ // Nothing other than AppImpl should ever refer to this, hence the __ name.
+ get __traceCtxForApp() {
+ return this.traceCtx;
}
}
-// Allows to take an existing class instance (`target`) and override some of its
-// methods via `overrides`. We use this for cases where we want to expose a
-// "manager" (e.g. TrackManager, SidebarManager) to the plugins, but we want to
-// override few of its methods (e.g. to inject the pluginId in the args).
-function createProxy<T extends object>(target: T, overrides: Partial<T>): T {
- return new Proxy(target, {
- get: (target: T, prop: string | symbol, receiver) => {
- // If the property is overriden, use that; otherwise, use target
- const overrideValue = (overrides as {[key: symbol | string]: {}})[prop];
- if (overrideValue !== undefined) {
- return typeof overrideValue === 'function'
- ? overrideValue.bind(overrides)
- : overrideValue;
- }
- const baseValue = Reflect.get(target, prop, receiver);
- return typeof baseValue === 'function'
- ? baseValue.bind(target)
- : baseValue;
- },
- }) as T;
+// A convenience interface to inject the App in Mithril components.
+export interface TraceImplAttrs {
+ trace: TraceImpl;
}
-// TODO(primiano): remove this once we get rid of controllers. This function
-// gets bound to `globals.dispatch(Actions.runControllers({}));` and exists
-// only to avoid a circular dependency between globals.ts and this file.
-let rerunControllersFunction: () => void;
-export function setRerunControllersFunction(f: () => void) {
- rerunControllersFunction = f;
+export interface OptionalTraceImplAttrs {
+ trace?: TraceImpl;
}
diff --git a/ui/src/common/constants.ts b/ui/src/core/trace_info_impl.ts
similarity index 69%
rename from ui/src/common/constants.ts
rename to ui/src/core/trace_info_impl.ts
index cc10366..1584143 100644
--- a/ui/src/common/constants.ts
+++ b/ui/src/core/trace_info_impl.ts
@@ -1,4 +1,4 @@
-// Copyright (C) 2021 The Android Open Source Project
+// 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.
@@ -12,4 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-export const TRACE_SUFFIX = '.perfetto-trace';
+import {TraceSource} from '../core/trace_source';
+import {TraceInfo} from '../public/trace_info';
+
+export interface TraceInfoImpl extends TraceInfo {
+ readonly source: TraceSource;
+}
diff --git a/ui/src/public/trace_source.ts b/ui/src/core/trace_source.ts
similarity index 66%
rename from ui/src/public/trace_source.ts
rename to ui/src/core/trace_source.ts
index 152942d..cfc702d 100644
--- a/ui/src/public/trace_source.ts
+++ b/ui/src/core/trace_source.ts
@@ -12,17 +12,42 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+import {SerializedAppState} from './state_serialization_schema';
+
+export type TraceSource =
+ | TraceFileSource
+ | TraceArrayBufferSource
+ | TraceUrlSource
+ | TraceHttpRpcSource;
+
export interface TraceFileSource {
type: 'FILE';
file: File;
}
-export interface TraceArrayBufferSource {
+export interface TraceUrlSource {
+ type: 'URL';
+ url: string;
+
+ // When loading from a permalink, the permalink might supply also the app
+ // state alongside the URL of the trace.
+ serializedAppState?: SerializedAppState;
+}
+
+export interface TraceHttpRpcSource {
+ type: 'HTTP_RPC';
+}
+
+export interface TraceArrayBufferSource extends PostedTrace {
type: 'ARRAY_BUFFER';
+ // See PostedTrace (which this interface extends).
+}
+
+export interface PostedTrace {
buffer: ArrayBuffer;
title: string;
- url?: string;
fileName?: string;
+ url?: string;
// |uuid| is set only when loading via ?local_cache_key=1234. When set,
// this matches global.state.traceUuid, with the exception of the following
@@ -31,25 +56,18 @@
// temporarily == T1 until T2 has been loaded (consistently to what happens
// with all other state fields).
uuid?: string;
+
// if |localOnly| is true then the trace should not be shared or downloaded.
localOnly?: boolean;
+ keepApiOpen?: boolean;
- // The set of extra args, keyed by plugin, that can be passed when opening the
- // trace via postMessge deep-linking. See post_message_handler.ts for details.
+ // Allows to pass extra arguments to plugins. This can be read by plugins
+ // onTraceLoad() and can be used to trigger plugin-specific-behaviours (e.g.
+ // allow dashboards like APC to pass extra data to materialize onto tracks).
+ // The format is the following:
+ // pluginArgs: {
+ // 'dev.perfetto.PluginFoo': { 'key1': 'value1', 'key2': 1234 }
+ // 'dev.perfetto.PluginBar': { 'key3': '...', 'key4': ... }
+ // }
pluginArgs?: {[pluginId: string]: {[key: string]: unknown}};
}
-
-export interface TraceUrlSource {
- type: 'URL';
- url: string;
-}
-
-export interface TraceHttpRpcSource {
- type: 'HTTP_RPC';
-}
-
-export type TraceSource =
- | TraceFileSource
- | TraceArrayBufferSource
- | TraceUrlSource
- | TraceHttpRpcSource;
diff --git a/ui/src/core/track_manager.ts b/ui/src/core/track_manager.ts
index c492a55..8270564 100644
--- a/ui/src/core/track_manager.ts
+++ b/ui/src/core/track_manager.ts
@@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {Optional} from '../base/utils';
import {Registry} from '../base/registry';
import {Track, TrackDescriptor, TrackManager} from '../public/track';
import {AsyncLimiter} from '../base/async_limiter';
@@ -22,7 +21,7 @@
readonly track: Track;
desc: TrackDescriptor;
render(ctx: TrackRenderContext): void;
- getError(): Optional<Error>;
+ getError(): Error | undefined;
}
/**
@@ -138,41 +137,46 @@
tick(): void {
if (this.tickSinceLastUsed++ === DESTROY_IF_NOT_SEEN_FOR_TICK_COUNT) {
// Schedule an onDestroy
- this.limiter
- .schedule(async () => {
+ this.limiter.schedule(async () => {
+ // Don't enter the track again once an error is has occurred
+ if (this.error !== undefined) {
+ return;
+ }
+
+ try {
if (this.created) {
await Promise.resolve(this.track.onDestroy?.());
this.created = false;
}
- })
- .catch((e) => {
- // Errors thrown inside lifecycle hooks will bubble up through the
- // AsyncLimiter to here, where we can swallow and capture the error.
+ } catch (e) {
this.error = e;
- });
+ }
+ });
}
}
render(ctx: TrackRenderContext): void {
- this.limiter
- .schedule(async () => {
- // Call onCreate() if we have been destroyed or were never created in
- // the first place.
+ this.limiter.schedule(async () => {
+ // Don't enter the track again once an error has occurred
+ if (this.error !== undefined) {
+ return;
+ }
+
+ try {
+ // Call onCreate() if this is our first call
if (!this.created) {
- await Promise.resolve(this.track.onCreate?.(ctx));
+ await this.track.onCreate?.(ctx);
this.created = true;
}
await Promise.resolve(this.track.onUpdate?.(ctx));
- })
- .catch((e) => {
- // Errors thrown inside lifecycle hooks will bubble up through the
- // AsyncLimiter to here, where we can swallow and capture the error.
+ } catch (e) {
this.error = e;
- });
+ }
+ });
this.track.render(ctx);
}
- getError(): Optional<Error> {
+ getError(): Error | undefined {
return this.error;
}
diff --git a/ui/src/core/workspace_manager.ts b/ui/src/core/workspace_manager.ts
index 11a7f59..77803b7 100644
--- a/ui/src/core/workspace_manager.ts
+++ b/ui/src/core/workspace_manager.ts
@@ -14,6 +14,7 @@
import {assertTrue} from '../base/logging';
import {Workspace, WorkspaceManager} from '../public/workspace';
+import {raf} from './raf_scheduler';
const DEFAULT_WORKSPACE_NAME = 'Default Workspace';
@@ -30,6 +31,7 @@
createEmptyWorkspace(title: string): Workspace {
const workspace = new Workspace();
workspace.title = title;
+ workspace.onchange = () => raf.scheduleFullRedraw();
this._workspaces.push(workspace);
return workspace;
}
diff --git a/ui/src/core_plugins/annotation/index.ts b/ui/src/core_plugins/annotation/index.ts
deleted file mode 100644
index 10b3fab..0000000
--- a/ui/src/core_plugins/annotation/index.ts
+++ /dev/null
@@ -1,156 +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.
-
-import {COUNTER_TRACK_KIND} from '../../public/track_kinds';
-import {Trace} from '../../public/trace';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
-import {ThreadSliceTrack} from '../../frontend/thread_slice_track';
-import {NUM, NUM_NULL, STR, STR_NULL} from '../../trace_processor/query_result';
-import {TraceProcessorCounterTrack} from '../counter/trace_processor_counter_track';
-import {THREAD_SLICE_TRACK_KIND} from '../../public/track_kinds';
-import {TrackNode, type TrackNodeContainer} from '../../public/workspace';
-import {getOrCreateGroupForProcess} from '../../public/standard_groups';
-
-class AnnotationPlugin implements PerfettoPlugin {
- async onTraceLoad(ctx: Trace): Promise<void> {
- await this.addAnnotationTracks(ctx);
- await this.addAnnotationCounterTracks(ctx);
- }
-
- private async addAnnotationTracks(ctx: Trace) {
- const {engine} = ctx;
-
- const result = await engine.query(`
- select
- id,
- name,
- upid,
- group_name as groupName
- from annotation_slice_track
- order by name
- `);
-
- const it = result.iter({
- id: NUM,
- name: STR,
- upid: NUM,
- groupName: STR_NULL,
- });
-
- const groups = new Map<string, TrackNode>();
-
- for (; it.valid(); it.next()) {
- const {id, name, upid, groupName} = it;
-
- const uri = `/annotation_${id}`;
- ctx.tracks.registerTrack({
- uri,
- title: name,
- tags: {
- kind: THREAD_SLICE_TRACK_KIND,
- scope: 'annotation',
- upid,
- },
- chips: ['metric'],
- track: new ThreadSliceTrack(
- {
- trace: ctx,
- uri,
- },
- id,
- 0,
- 'annotation_slice',
- ),
- });
-
- // We want to try and find a group to put this track in. If groupName is
- // defined, create a new group or place in existing one if it already
- // exists Otherwise, try upid to see if we can put this in a process
- // group
-
- let container: TrackNodeContainer;
- if (groupName) {
- const existingGroup = groups.get(groupName);
- if (!existingGroup) {
- const group = new TrackNode({title: groupName, uri, isSummary: true});
- container = group;
- groups.set(groupName, group);
- ctx.workspace.addChildInOrder(group);
- } else {
- container = existingGroup;
- }
- } else {
- if (upid !== 0) {
- container = getOrCreateGroupForProcess(ctx.workspace, upid);
- } else {
- container = ctx.workspace;
- }
- }
-
- const track = new TrackNode({uri, title: name});
- container.addChildInOrder(track);
- }
- }
-
- private async addAnnotationCounterTracks(ctx: Trace) {
- const {engine} = ctx;
- const counterResult = await engine.query(`
- SELECT
- id,
- name,
- min_value as minValue,
- max_value as maxValue,
- upid
- FROM annotation_counter_track`);
-
- const counterIt = counterResult.iter({
- id: NUM,
- name: STR,
- minValue: NUM_NULL,
- maxValue: NUM_NULL,
- upid: NUM,
- });
-
- for (; counterIt.valid(); counterIt.next()) {
- const {id: trackId, name, upid} = counterIt;
-
- const uri = `/annotation_counter_${trackId}`;
- ctx.tracks.registerTrack({
- uri,
- title: name,
- tags: {
- kind: COUNTER_TRACK_KIND,
- scope: 'annotation',
- upid,
- },
- chips: ['metric'],
- track: new TraceProcessorCounterTrack({
- trace: ctx,
- uri,
- trackId,
- rootTable: 'annotation_counter',
- }),
- });
-
- const group = getOrCreateGroupForProcess(ctx.workspace, upid);
- const track = new TrackNode({uri, title: name});
- group.addChildInOrder(track);
- }
- }
-}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'perfetto.Annotation',
- plugin: AnnotationPlugin,
-};
diff --git a/ui/src/core_plugins/async_slices/async_slice_track.ts b/ui/src/core_plugins/async_slices/async_slice_track.ts
deleted file mode 100644
index ce1e4c9..0000000
--- a/ui/src/core_plugins/async_slices/async_slice_track.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-// 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 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 {
- NAMED_ROW,
- NamedRow,
- NamedSliceTrack,
-} from '../../frontend/named_slice_track';
-import {SLICE_LAYOUT_FIT_CONTENT_DEFAULTS} from '../../frontend/slice_layout';
-import {NewTrackArgs} from '../../frontend/track';
-import {Slice} from '../../public/track';
-
-export class AsyncSliceTrack extends NamedSliceTrack {
- constructor(
- args: NewTrackArgs,
- maxDepth: number,
- private trackIds: number[],
- ) {
- super(args);
- this.sliceLayout = {
- ...SLICE_LAYOUT_FIT_CONTENT_DEFAULTS,
- depthGuess: maxDepth,
- };
- }
-
- getRowSpec(): NamedRow {
- return NAMED_ROW;
- }
-
- rowToSlice(row: NamedRow): Slice {
- return this.rowToSliceBase(row);
- }
-
- getSqlSource(): string {
- return `
- select
- ts,
- dur,
- layout_depth as depth,
- ifnull(name, '[null]') as name,
- id,
- thread_dur as threadDur
- from experimental_slice_layout
- where filter_track_ids = '${this.trackIds.join(',')}'
- `;
- }
-
- onUpdatedSlices(slices: Slice[]) {
- for (const slice of slices) {
- slice.isHighlighted = slice === this.hoveredSlice;
- }
- }
-}
diff --git a/ui/src/core_plugins/async_slices/index.ts b/ui/src/core_plugins/async_slices/index.ts
deleted file mode 100644
index 5f3b199..0000000
--- a/ui/src/core_plugins/async_slices/index.ts
+++ /dev/null
@@ -1,368 +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.
-
-import {removeFalsyValues} from '../../base/array_utils';
-import {TrackNode} from '../../public/workspace';
-import {ASYNC_SLICE_TRACK_KIND} from '../../public/track_kinds';
-import {Trace} from '../../public/trace';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
-import {getThreadUriPrefix, getTrackName} from '../../public/utils';
-import {NUM, NUM_NULL, STR, STR_NULL} from '../../trace_processor/query_result';
-import {AsyncSliceTrack} from './async_slice_track';
-import {
- getOrCreateGroupForProcess,
- getOrCreateGroupForThread,
-} from '../../public/standard_groups';
-import {exists} from '../../base/utils';
-
-class AsyncSlicePlugin implements PerfettoPlugin {
- async onTraceLoad(ctx: Trace): Promise<void> {
- await this.addGlobalAsyncTracks(ctx);
- await this.addProcessAsyncSliceTracks(ctx);
- await this.addThreadAsyncSliceTracks(ctx);
- await this.addUserAsyncSliceTracks(ctx);
- }
-
- async addGlobalAsyncTracks(ctx: Trace): Promise<void> {
- const {engine} = ctx;
- // TODO(stevegolton): The track exclusion logic is currently a hack. This will be replaced
- // by a mechanism for more specific plugins to override tracks from more generic plugins.
- const suspendResumeLatencyTrackName = 'Suspend/Resume Latency';
- const rawGlobalAsyncTracks = await engine.query(`
- with global_tracks_grouped as (
- select
- parent_id,
- name,
- group_concat(id) as trackIds,
- count() as trackCount
- from track t
- join _slice_track_summary using (id)
- where t.type in ('__intrinsic_track', 'gpu_track', '__intrinsic_cpu_track')
- and (name != '${suspendResumeLatencyTrackName}' or name is null)
- group by parent_id, name
- )
- select
- t.name as name,
- t.parent_id as parentId,
- t.trackIds as trackIds,
- __max_layout_depth(t.trackCount, t.trackIds) as maxDepth
- from global_tracks_grouped t
- `);
- const it = rawGlobalAsyncTracks.iter({
- name: STR_NULL,
- parentId: NUM_NULL,
- trackIds: STR,
- maxDepth: NUM,
- });
-
- // Create a map of track nodes by id
- const trackMap = new Map<
- number,
- {parentId: number | null; trackNode: TrackNode}
- >();
-
- for (; it.valid(); it.next()) {
- const rawName = it.name === null ? undefined : it.name;
- const title = getTrackName({
- name: rawName,
- kind: ASYNC_SLICE_TRACK_KIND,
- });
- const rawTrackIds = it.trackIds;
- const trackIds = rawTrackIds.split(',').map((v) => Number(v));
- const maxDepth = it.maxDepth;
-
- const uri = `/async_slices_${rawName}_${it.parentId}`;
- ctx.tracks.registerTrack({
- uri,
- title,
- tags: {
- trackIds,
- kind: ASYNC_SLICE_TRACK_KIND,
- scope: 'global',
- },
- track: new AsyncSliceTrack({trace: ctx, uri}, maxDepth, trackIds),
- });
- const trackNode = new TrackNode({uri, title, sortOrder: -25});
- trackIds.forEach((id) =>
- trackMap.set(id, {parentId: it.parentId, trackNode}),
- );
- }
-
- // Attach track nodes to parents / or the workspace if they have no parent
- trackMap.forEach((t) => {
- const parent = exists(t.parentId) && trackMap.get(t.parentId);
- if (parent !== false && parent !== undefined) {
- parent.trackNode.addChildInOrder(t.trackNode);
- } else {
- ctx.workspace.addChildInOrder(t.trackNode);
- }
- });
- }
-
- async addProcessAsyncSliceTracks(ctx: Trace): Promise<void> {
- const result = await ctx.engine.query(`
- select
- upid,
- t.name as trackName,
- t.track_ids as trackIds,
- process.name as processName,
- process.pid as pid,
- t.parent_id as parentId,
- __max_layout_depth(t.track_count, t.track_ids) as maxDepth
- from _process_track_summary_by_upid_and_parent_id_and_name t
- join process using(upid)
- where t.name is null or t.name not glob "* Timeline"
- `);
-
- const it = result.iter({
- upid: NUM,
- parentId: NUM_NULL,
- trackName: STR_NULL,
- trackIds: STR,
- processName: STR_NULL,
- pid: NUM_NULL,
- maxDepth: NUM,
- });
-
- const trackMap = new Map<
- number,
- {parentId: number | null; upid: number; trackNode: TrackNode}
- >();
-
- for (; it.valid(); it.next()) {
- const upid = it.upid;
- const trackName = it.trackName;
- const rawTrackIds = it.trackIds;
- const trackIds = rawTrackIds.split(',').map((v) => Number(v));
- const processName = it.processName;
- const pid = it.pid;
- const maxDepth = it.maxDepth;
-
- const kind = ASYNC_SLICE_TRACK_KIND;
- const title = getTrackName({
- name: trackName,
- upid,
- pid,
- processName,
- kind,
- });
-
- const uri = `/process_${upid}/async_slices_${rawTrackIds}`;
- ctx.tracks.registerTrack({
- uri,
- title,
- tags: {
- trackIds,
- kind: ASYNC_SLICE_TRACK_KIND,
- scope: 'process',
- upid,
- },
- track: new AsyncSliceTrack({trace: ctx, uri}, maxDepth, trackIds),
- });
- const track = new TrackNode({uri, title, sortOrder: 30});
- trackIds.forEach((id) =>
- trackMap.set(id, {trackNode: track, parentId: it.parentId, upid}),
- );
- }
-
- // Attach track nodes to parents / or the workspace if they have no parent
- trackMap.forEach((t) => {
- const parent = exists(t.parentId) && trackMap.get(t.parentId);
- if (parent !== false && parent !== undefined) {
- parent.trackNode.addChildInOrder(t.trackNode);
- } else {
- const processGroup = getOrCreateGroupForProcess(ctx.workspace, t.upid);
- processGroup.addChildInOrder(t.trackNode);
- }
- });
- }
-
- async addThreadAsyncSliceTracks(ctx: Trace): Promise<void> {
- const result = await ctx.engine.query(`
- include perfetto module viz.summary.slices;
- include perfetto module viz.summary.threads;
- include perfetto module viz.threads;
-
- select
- t.utid,
- t.parent_id as parentId,
- thread.upid,
- t.name as trackName,
- thread.name as threadName,
- thread.tid as tid,
- t.track_ids as trackIds,
- __max_layout_depth(t.track_count, t.track_ids) as maxDepth,
- k.is_main_thread as isMainThread,
- k.is_kernel_thread AS isKernelThread
- from _thread_track_summary_by_utid_and_name t
- join _threads_with_kernel_flag k using(utid)
- join thread using (utid)
- where t.track_count > 1
- `);
-
- const it = result.iter({
- utid: NUM,
- parentId: NUM_NULL,
- upid: NUM_NULL,
- trackName: STR_NULL,
- trackIds: STR,
- maxDepth: NUM,
- isMainThread: NUM_NULL,
- isKernelThread: NUM,
- threadName: STR_NULL,
- tid: NUM_NULL,
- });
-
- const trackMap = new Map<
- number,
- {parentId: number | null; utid: number; trackNode: TrackNode}
- >();
-
- for (; it.valid(); it.next()) {
- const {
- utid,
- parentId,
- upid,
- trackName,
- isMainThread,
- isKernelThread,
- maxDepth,
- threadName,
- tid,
- } = it;
- const rawTrackIds = it.trackIds;
- const trackIds = rawTrackIds.split(',').map((v) => Number(v));
- const title = getTrackName({
- name: trackName,
- utid,
- tid,
- threadName,
- kind: 'Slices',
- });
-
- const uri = `/${getThreadUriPrefix(upid, utid)}_slice_${rawTrackIds}`;
- ctx.tracks.registerTrack({
- uri,
- title,
- tags: {
- trackIds,
- kind: ASYNC_SLICE_TRACK_KIND,
- scope: 'thread',
- utid,
- upid: upid ?? undefined,
- },
- chips: removeFalsyValues([
- isKernelThread === 0 && isMainThread === 1 && 'main thread',
- ]),
- track: new AsyncSliceTrack({trace: ctx, uri}, maxDepth, trackIds),
- });
- const track = new TrackNode({uri, title, sortOrder: 20});
- trackIds.forEach((id) =>
- trackMap.set(id, {trackNode: track, parentId, utid}),
- );
- }
-
- // Attach track nodes to parents / or the workspace if they have no parent
- trackMap.forEach((t) => {
- const parent = exists(t.parentId) && trackMap.get(t.parentId);
- if (parent !== false && parent !== undefined) {
- parent.trackNode.addChildInOrder(t.trackNode);
- } else {
- const group = getOrCreateGroupForThread(ctx.workspace, t.utid);
- group.addChildInOrder(t.trackNode);
- }
- });
- }
-
- async addUserAsyncSliceTracks(ctx: Trace): Promise<void> {
- const {engine} = ctx;
- const result = await engine.query(`
- with grouped_packages as materialized (
- select
- uid,
- group_concat(package_name, ',') as package_name,
- count() as cnt
- from package_list
- group by uid
- )
- select
- t.name as name,
- t.uid as uid,
- t.parent_id as parentId,
- t.track_ids as trackIds,
- __max_layout_depth(t.track_count, t.track_ids) as maxDepth,
- iif(g.cnt = 1, g.package_name, 'UID ' || g.uid) as packageName
- from _uid_track_track_summary_by_uid_and_name t
- left join grouped_packages g using (uid)
- `);
-
- const it = result.iter({
- name: STR_NULL,
- uid: NUM_NULL,
- packageName: STR_NULL,
- trackIds: STR,
- maxDepth: NUM,
- parentId: NUM_NULL,
- });
-
- const trackMap = new Map<
- number,
- {parentId: number | null; trackNode: TrackNode}
- >();
-
- for (; it.valid(); it.next()) {
- const {name, uid, maxDepth, parentId} = it;
- const kind = ASYNC_SLICE_TRACK_KIND;
- const userName = it.packageName === null ? `UID ${uid}` : it.packageName;
- const trackIds = it.trackIds.split(',').map((v) => Number(v));
-
- const title = getTrackName({
- name,
- uid,
- userName,
- kind,
- uidTrack: true,
- });
-
- const uri = `/async_slices_${name}_${uid}`;
- ctx.tracks.registerTrack({
- uri,
- title,
- tags: {
- trackIds: trackIds,
- kind: ASYNC_SLICE_TRACK_KIND,
- },
- track: new AsyncSliceTrack({trace: ctx, uri}, maxDepth, trackIds),
- });
-
- const track = new TrackNode({uri, title});
- trackIds.forEach((id) => trackMap.set(id, {trackNode: track, parentId}));
- }
-
- // Attach track nodes to parents / or the workspace if they have no parent
- trackMap.forEach((t) => {
- const parent = exists(t.parentId) && trackMap.get(t.parentId);
- if (parent !== false && parent !== undefined) {
- parent.trackNode.addChildInOrder(t.trackNode);
- } else {
- ctx.workspace.addChildInOrder(t.trackNode);
- }
- });
- }
-}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'perfetto.AsyncSlices',
- plugin: AsyncSlicePlugin,
-};
diff --git a/ui/src/core_plugins/chrome_critical_user_interactions/critical_user_interaction_track.ts b/ui/src/core_plugins/chrome_critical_user_interactions/critical_user_interaction_track.ts
deleted file mode 100644
index 3f5888a..0000000
--- a/ui/src/core_plugins/chrome_critical_user_interactions/critical_user_interaction_track.ts
+++ /dev/null
@@ -1,163 +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 {OnSliceClickArgs} from '../../frontend/base_slice_track';
-import {GenericSliceDetailsTab} from '../../frontend/generic_slice_details_tab';
-import {NAMED_ROW} from '../../frontend/named_slice_track';
-import {NUM, STR} from '../../trace_processor/query_result';
-import {Slice} from '../../public/track';
-import {
- CustomSqlDetailsPanelConfig,
- CustomSqlImportConfig,
- CustomSqlTableDefConfig,
- CustomSqlTableSliceTrack,
-} from '../../frontend/tracks/custom_sql_table_slice_track';
-import {PageLoadDetailsPanel} from './page_load_details_panel';
-import {StartupDetailsPanel} from './startup_details_panel';
-import {WebContentInteractionPanel} from './web_content_interaction_details_panel';
-
-export const CRITICAL_USER_INTERACTIONS_KIND =
- 'org.chromium.CriticalUserInteraction.track';
-
-export const CRITICAL_USER_INTERACTIONS_ROW = {
- ...NAMED_ROW,
- scopedId: NUM,
- type: STR,
-};
-export type CriticalUserInteractionRow = typeof CRITICAL_USER_INTERACTIONS_ROW;
-
-export interface CriticalUserInteractionSlice extends Slice {
- scopedId: number;
- type: string;
-}
-
-enum CriticalUserInteractionType {
- UNKNOWN = 'Unknown',
- PAGE_LOAD = 'chrome_page_loads',
- STARTUP = 'chrome_startups',
- WEB_CONTENT_INTERACTION = 'chrome_web_content_interactions',
-}
-
-function convertToCriticalUserInteractionType(
- cujType: string,
-): CriticalUserInteractionType {
- switch (cujType) {
- case CriticalUserInteractionType.PAGE_LOAD:
- return CriticalUserInteractionType.PAGE_LOAD;
- case CriticalUserInteractionType.STARTUP:
- return CriticalUserInteractionType.STARTUP;
- case CriticalUserInteractionType.WEB_CONTENT_INTERACTION:
- return CriticalUserInteractionType.WEB_CONTENT_INTERACTION;
- default:
- return CriticalUserInteractionType.UNKNOWN;
- }
-}
-
-export class CriticalUserInteractionTrack extends CustomSqlTableSliceTrack {
- static readonly kind = `/critical_user_interactions`;
-
- getSqlDataSource(): CustomSqlTableDefConfig {
- return {
- columns: [
- // The scoped_id is not a unique identifier within the table; generate
- // a unique id from type and scoped_id on the fly to use for slice
- // selection.
- 'hash(type, scoped_id) AS id',
- 'scoped_id AS scopedId',
- 'name',
- 'ts',
- 'dur',
- 'type',
- ],
- sqlTableName: 'chrome_interactions',
- };
- }
-
- getDetailsPanel(
- args: OnSliceClickArgs<CriticalUserInteractionSlice>,
- ): CustomSqlDetailsPanelConfig {
- let detailsPanel = {
- kind: GenericSliceDetailsTab.kind,
- config: {
- sqlTableName: this.tableName,
- title: 'Chrome Interaction',
- },
- };
-
- switch (convertToCriticalUserInteractionType(args.slice.type)) {
- case CriticalUserInteractionType.PAGE_LOAD:
- detailsPanel = {
- kind: PageLoadDetailsPanel.kind,
- config: {
- sqlTableName: this.tableName,
- title: 'Chrome Page Load',
- },
- };
- break;
- case CriticalUserInteractionType.STARTUP:
- detailsPanel = {
- kind: StartupDetailsPanel.kind,
- config: {
- sqlTableName: this.tableName,
- title: 'Chrome Startup',
- },
- };
- break;
- case CriticalUserInteractionType.WEB_CONTENT_INTERACTION:
- detailsPanel = {
- kind: WebContentInteractionPanel.kind,
- config: {
- sqlTableName: this.tableName,
- title: 'Chrome Web Content Interaction',
- },
- };
- break;
- default:
- break;
- }
- return detailsPanel;
- }
-
- onSliceClick(args: OnSliceClickArgs<CriticalUserInteractionSlice>) {
- const detailsPanelConfig = this.getDetailsPanel(args);
- this.trace.selection.selectGenericSlice({
- id: args.slice.scopedId,
- sqlTableName: this.tableName,
- start: args.slice.ts,
- duration: args.slice.dur,
- trackUri: this.uri,
- detailsPanelConfig: {
- kind: detailsPanelConfig.kind,
- config: detailsPanelConfig.config,
- },
- });
- }
-
- getSqlImports(): CustomSqlImportConfig {
- return {
- modules: ['chrome.interactions'],
- };
- }
-
- getRowSpec(): CriticalUserInteractionRow {
- return CRITICAL_USER_INTERACTIONS_ROW;
- }
-
- rowToSlice(row: CriticalUserInteractionRow): CriticalUserInteractionSlice {
- const baseSlice = super.rowToSlice(row);
- const scopedId = row.scopedId;
- const type = row.type;
- return {...baseSlice, scopedId, type};
- }
-}
diff --git a/ui/src/core_plugins/chrome_critical_user_interactions/index.ts b/ui/src/core_plugins/chrome_critical_user_interactions/index.ts
deleted file mode 100644
index 23560c9..0000000
--- a/ui/src/core_plugins/chrome_critical_user_interactions/index.ts
+++ /dev/null
@@ -1,116 +0,0 @@
-// 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 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 {v4 as uuidv4} from 'uuid';
-import {GenericSliceDetailsTabConfig} from '../../frontend/generic_slice_details_tab';
-import {BottomTabToSCSAdapter} from '../../public/utils';
-import {Trace} from '../../public/trace';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
-import {PageLoadDetailsPanel} from './page_load_details_panel';
-import {StartupDetailsPanel} from './startup_details_panel';
-import {WebContentInteractionPanel} from './web_content_interaction_details_panel';
-import {CriticalUserInteractionTrack} from './critical_user_interaction_track';
-import {TrackNode} from '../../public/workspace';
-
-class CriticalUserInteractionPlugin implements PerfettoPlugin {
- async onTraceLoad(ctx: Trace): Promise<void> {
- ctx.commands.registerCommand({
- id: 'perfetto.CriticalUserInteraction.AddInteractionTrack',
- name: 'Add track: Chrome interactions',
- callback: () => {
- const track = new TrackNode({
- uri: CriticalUserInteractionTrack.kind,
- title: 'Chrome Interactions',
- });
- ctx.workspace.addChildInOrder(track);
- track.pin();
- },
- });
-
- ctx.tracks.registerTrack({
- uri: CriticalUserInteractionTrack.kind,
- tags: {
- kind: CriticalUserInteractionTrack.kind,
- },
- title: 'Chrome Interactions',
- track: new CriticalUserInteractionTrack({
- trace: ctx,
- uri: CriticalUserInteractionTrack.kind,
- }),
- });
-
- ctx.tabs.registerDetailsPanel(
- new BottomTabToSCSAdapter({
- tabFactory: (selection) => {
- if (
- selection.kind === 'GENERIC_SLICE' &&
- selection.detailsPanelConfig.kind === PageLoadDetailsPanel.kind
- ) {
- const config = selection.detailsPanelConfig.config;
- return new PageLoadDetailsPanel({
- config: config as GenericSliceDetailsTabConfig,
- trace: ctx,
- uuid: uuidv4(),
- });
- }
- return undefined;
- },
- }),
- );
-
- ctx.tabs.registerDetailsPanel(
- new BottomTabToSCSAdapter({
- tabFactory: (selection) => {
- if (
- selection.kind === 'GENERIC_SLICE' &&
- selection.detailsPanelConfig.kind === StartupDetailsPanel.kind
- ) {
- const config = selection.detailsPanelConfig.config;
- return new StartupDetailsPanel({
- config: config as GenericSliceDetailsTabConfig,
- trace: ctx,
- uuid: uuidv4(),
- });
- }
- return undefined;
- },
- }),
- );
-
- ctx.tabs.registerDetailsPanel(
- new BottomTabToSCSAdapter({
- tabFactory: (selection) => {
- if (
- selection.kind === 'GENERIC_SLICE' &&
- selection.detailsPanelConfig.kind ===
- WebContentInteractionPanel.kind
- ) {
- const config = selection.detailsPanelConfig.config;
- return new WebContentInteractionPanel({
- config: config as GenericSliceDetailsTabConfig,
- trace: ctx,
- uuid: uuidv4(),
- });
- }
- return undefined;
- },
- }),
- );
- }
-}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'perfetto.CriticalUserInteraction',
- plugin: CriticalUserInteractionPlugin,
-};
diff --git a/ui/src/core_plugins/chrome_scroll_jank/chrome_tasks_scroll_jank_track.ts b/ui/src/core_plugins/chrome_scroll_jank/chrome_tasks_scroll_jank_track.ts
deleted file mode 100644
index 1df20a2..0000000
--- a/ui/src/core_plugins/chrome_scroll_jank/chrome_tasks_scroll_jank_track.ts
+++ /dev/null
@@ -1,48 +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 {
- NAMED_ROW,
- NamedRow,
- NamedSliceTrack,
-} from '../../frontend/named_slice_track';
-import {NewTrackArgs} from '../../frontend/track';
-import {Slice} from '../../public/track';
-
-export class ChromeTasksScrollJankTrack extends NamedSliceTrack {
- constructor(args: NewTrackArgs) {
- super(args);
- }
-
- getRowSpec(): NamedRow {
- return NAMED_ROW;
- }
-
- rowToSlice(row: NamedRow): Slice {
- return this.rowToSliceBase(row);
- }
-
- getSqlSource(): string {
- return `
- select
- s2.ts as ts,
- s2.dur as dur,
- s2.id as id,
- 0 as depth,
- s1.full_name as name
- from chrome_tasks_delaying_input_processing s1
- join slice s2 on s2.id=s1.slice_id
- `;
- }
-}
diff --git a/ui/src/core_plugins/chrome_scroll_jank/common.ts b/ui/src/core_plugins/chrome_scroll_jank/common.ts
deleted file mode 100644
index c6232a2..0000000
--- a/ui/src/core_plugins/chrome_scroll_jank/common.ts
+++ /dev/null
@@ -1,69 +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 {ObjectByKey} from '../../common/state';
-import {featureFlags} from '../../core/feature_flags';
-import {CustomSqlDetailsPanelConfig} from '../../frontend/tracks/custom_sql_table_slice_track';
-
-export const ENABLE_CHROME_SCROLL_JANK_PLUGIN = featureFlags.register({
- id: 'enableChromeScrollJankPlugin',
- name: 'Enable Chrome Scroll Jank plugin',
- description: 'Adds new tracks for scroll jank in Chrome',
- defaultValue: false,
-});
-
-export interface ScrollJankTrackSpec {
- key: string;
- sqlTableName: string;
- detailsPanelConfig: CustomSqlDetailsPanelConfig;
-}
-
-// Global state for the scroll jank plugin.
-export class ScrollJankPluginState {
- private static instance?: ScrollJankPluginState;
- private tracks: ObjectByKey<ScrollJankTrackSpec>;
-
- private constructor() {
- this.tracks = {};
- }
-
- public static getInstance(): ScrollJankPluginState {
- if (!ScrollJankPluginState.instance) {
- ScrollJankPluginState.instance = new ScrollJankPluginState();
- }
-
- return ScrollJankPluginState.instance;
- }
-
- public registerTrack(args: {
- kind: string;
- trackUri: string;
- tableName: string;
- detailsPanelConfig: CustomSqlDetailsPanelConfig;
- }): void {
- this.tracks[args.kind] = {
- key: args.trackUri,
- sqlTableName: args.tableName,
- detailsPanelConfig: args.detailsPanelConfig,
- };
- }
-
- public unregisterTrack(kind: string): void {
- delete this.tracks[kind];
- }
-
- public getTrack(kind: string): ScrollJankTrackSpec | undefined {
- return this.tracks[kind];
- }
-}
diff --git a/ui/src/core_plugins/chrome_scroll_jank/event_latency_details_panel.ts b/ui/src/core_plugins/chrome_scroll_jank/event_latency_details_panel.ts
index 2d629ea..cfcdf87 100644
--- a/ui/src/core_plugins/chrome_scroll_jank/event_latency_details_panel.ts
+++ b/ui/src/core_plugins/chrome_scroll_jank/event_latency_details_panel.ts
@@ -13,16 +13,12 @@
// limitations under the License.
import m from 'mithril';
-import {Duration, duration, time} from '../../base/time';
-import {raf} from '../../core/raf_scheduler';
-import {BottomTab, NewBottomTabArgs} from '../../public/lib/bottom_tab';
-import {GenericSliceDetailsTabConfig} from '../../frontend/generic_slice_details_tab';
+import {Duration, duration, Time, time} from '../../base/time';
import {hasArgs, renderArguments} from '../../frontend/slice_args';
import {renderDetails} from '../../frontend/slice_details';
import {
getDescendantSliceTree,
getSlice,
- getSliceFromConstraints,
SliceDetails,
SliceTreeNode,
} from '../../trace_processor/sql_utils/slice';
@@ -35,9 +31,9 @@
Table,
TableData,
widgetColumn,
-} from '../../frontend/tables/table';
+} from '../../widgets/table';
import {TreeTable, TreeTableAttrs} from '../../frontend/widgets/treetable';
-import {NUM, STR} from '../../trace_processor/query_result';
+import {LONG, NUM, STR} from '../../trace_processor/query_result';
import {DetailsShell} from '../../widgets/details_shell';
import {GridLayout, GridLayoutColumn} from '../../widgets/grid_layout';
import {Section} from '../../widgets/section';
@@ -51,13 +47,10 @@
getScrollJankCauseStage,
} from './scroll_jank_cause_link_utils';
import {ScrollJankCauseMap} from './scroll_jank_cause_map';
-import {
- getScrollJankSlices,
- getSliceForTrack,
- ScrollJankSlice,
-} from './scroll_jank_slice';
import {sliceRef} from '../../frontend/widgets/slice';
-import {SCROLL_JANK_V3_TRACK_KIND} from '../../public/track_kinds';
+import {JANKS_TRACK_URI, renderSliceRef} from './selection_utils';
+import {TrackEventDetailsPanel} from '../../public/details_panel';
+import {Trace} from '../../public/trace';
// Given a node in the slice tree, return a path from root to it.
function getPath(slice: SliceTreeNode): string[] {
@@ -104,15 +97,17 @@
return `${delta > 0 ? '+' : ''}${Duration.humanise(delta)}`;
}
-export class EventLatencySliceDetailsPanel extends BottomTab<GenericSliceDetailsTabConfig> {
- static readonly kind = 'dev.perfetto.EventLatencySliceDetailsPanel';
-
- private loaded = false;
+export class EventLatencySliceDetailsPanel implements TrackEventDetailsPanel {
private name = '';
private topEventLatencyId: SliceSqlId | undefined = undefined;
private sliceDetails?: SliceDetails;
- private jankySlice?: ScrollJankSlice;
+ private jankySlice?: {
+ ts: time;
+ dur: duration;
+ id: number;
+ causeOfJank: string;
+ };
// Whether this stage has caused jank. This is also true for top level
// EventLatency slices where a descendant is a cause of jank.
@@ -132,31 +127,24 @@
private tracksByTrackId: Map<number, string>;
- static create(
- args: NewBottomTabArgs<GenericSliceDetailsTabConfig>,
- ): EventLatencySliceDetailsPanel {
- return new EventLatencySliceDetailsPanel(args);
- }
-
- constructor(args: NewBottomTabArgs<GenericSliceDetailsTabConfig>) {
- super(args);
-
+ constructor(
+ private readonly trace: Trace,
+ private readonly id: number,
+ ) {
this.tracksByTrackId = new Map<number, string>();
this.trace.tracks.getAllTracks().forEach((td) => {
td.tags?.trackIds?.forEach((trackId) => {
this.tracksByTrackId.set(trackId, td.uri);
});
});
-
- this.loadData();
}
- async loadData() {
- const queryResult = await this.engine.query(`
+ async load() {
+ const queryResult = await this.trace.engine.query(`
SELECT
name
- FROM ${this.config.sqlTableName}
- WHERE id = ${this.config.id}
+ FROM slice
+ WHERE id = ${this.id}
`);
const iter = queryResult.firstRow({
@@ -169,16 +157,14 @@
await this.loadJankSlice();
await this.loadRelevantThreads();
await this.loadEventLatencyBreakdown();
-
- this.loaded = true;
}
async loadSlice() {
this.sliceDetails = await getSlice(
- this.engine,
- asSliceSqlId(this.config.id),
+ this.trace.engine,
+ asSliceSqlId(this.id),
);
- raf.scheduleRedraw();
+ this.trace.scheduleFullRedraw();
}
async loadJankSlice() {
@@ -194,14 +180,25 @@
);
}
- const possibleSlices = await getScrollJankSlices(
- this.engine,
- this.topEventLatencyId,
- );
- // We may not get any slices if the EventLatency doesn't indicate any
- // jank occurred.
- if (possibleSlices.length > 0) {
- this.jankySlice = possibleSlices[0];
+ const it = (
+ await this.trace.engine.query(`
+ SELECT ts, dur, id, cause_of_jank as causeOfJank
+ FROM chrome_janky_frame_presentation_intervals
+ WHERE event_latency_id = ${this.topEventLatencyId}`)
+ ).iter({
+ id: NUM,
+ ts: LONG,
+ dur: LONG,
+ causeOfJank: STR,
+ });
+
+ if (it.valid()) {
+ this.jankySlice = {
+ id: it.id,
+ ts: Time.fromRaw(it.ts),
+ dur: Duration.fromRaw(it.dur),
+ causeOfJank: it.causeOfJank,
+ };
}
}
@@ -214,7 +211,7 @@
if (this.sliceDetails.name === 'EventLatency' && !this.jankySlice) return;
const possibleScrollJankStage = await getScrollJankCauseStage(
- this.engine,
+ this.trace.engine,
this.topEventLatencyId,
);
if (this.sliceDetails.name === 'EventLatency') {
@@ -237,7 +234,7 @@
if (this.relevantThreadStage) {
this.relevantThreadTracks = await getEventLatencyCauseTracks(
- this.engine,
+ this.trace.engine,
this.relevantThreadStage,
);
}
@@ -248,51 +245,55 @@
return;
}
this.eventLatencyBreakdown = await getDescendantSliceTree(
- this.engine,
+ this.trace.engine,
this.topEventLatencyId,
);
- // TODO(altimin): this should be based on an stdlib table and consider only
- // EventLatencies within the same scroll.
- // This is a copy of the statement in event_latency_track. It should move to
- // stdlib instead of living in the UI code.
- const whereClause = `
- EXTRACT_ARG(arg_set_id, 'event_latency.event_type') IN (
- 'FIRST_GESTURE_SCROLL_UPDATE',
- 'GESTURE_SCROLL_UPDATE',
- 'INERTIAL_GESTURE_SCROLL_UPDATE')
- AND HAS_DESCENDANT_SLICE_WITH_NAME(
- id,
- 'SubmitCompositorFrameToPresentationCompositorFrame')`;
- const prevEventLatency = await getSliceFromConstraints(this.engine, {
- filters: [
- `name = 'EventLatency'`,
- `id < ${this.topEventLatencyId}`,
- whereClause,
- ],
- orderBy: [{fieldName: 'id', direction: 'DESC'}],
- limit: 1,
- });
- if (prevEventLatency.length > 0) {
+ // TODO(altimin): this should only consider EventLatencies within the same scroll.
+ const prevEventLatency = (
+ await this.trace.engine.query(`
+ INCLUDE PERFETTO MODULE chrome.event_latency;
+ SELECT
+ id
+ FROM chrome_event_latencies
+ WHERE event_type IN (
+ 'FIRST_GESTURE_SCROLL_UPDATE',
+ 'GESTURE_SCROLL_UPDATE',
+ 'INERTIAL_GESTURE_SCROLL_UPDATE')
+ AND is_presented
+ AND id < ${this.topEventLatencyId}
+ ORDER BY id DESC
+ LIMIT 1
+ ;
+ `)
+ ).maybeFirstRow({id: NUM});
+ if (prevEventLatency !== undefined) {
this.prevEventLatencyBreakdown = await getDescendantSliceTree(
- this.engine,
- prevEventLatency[0].id,
+ this.trace.engine,
+ asSliceSqlId(prevEventLatency.id),
);
}
- const nextEventLatency = await getSliceFromConstraints(this.engine, {
- filters: [
- `name = 'EventLatency'`,
- `id > ${this.topEventLatencyId}`,
- whereClause,
- ],
- orderBy: ['id'],
- limit: 1,
- });
- if (nextEventLatency.length > 0) {
+ const nextEventLatency = (
+ await this.trace.engine.query(`
+ INCLUDE PERFETTO MODULE chrome.event_latency;
+ SELECT
+ id
+ FROM chrome_event_latencies
+ WHERE event_type IN (
+ 'FIRST_GESTURE_SCROLL_UPDATE',
+ 'GESTURE_SCROLL_UPDATE',
+ 'INERTIAL_GESTURE_SCROLL_UPDATE')
+ AND is_presented
+ AND id > ${this.topEventLatencyId}
+ ORDER BY id DESC
+ LIMIT 1;
+ `)
+ ).maybeFirstRow({id: NUM});
+ if (nextEventLatency !== undefined) {
this.nextEventLatencyBreakdown = await getDescendantSliceTree(
- this.engine,
- nextEventLatency[0].id,
+ this.trace.engine,
+ asSliceSqlId(nextEventLatency.id),
);
}
}
@@ -381,7 +382,7 @@
private async getOldestAncestorSliceId(): Promise<number> {
let eventLatencyId = -1;
if (!this.sliceDetails) return eventLatencyId;
- const queryResult = await this.engine.query(`
+ const queryResult = await this.trace.engine.query(`
SELECT
id
FROM ancestor_slice(${this.sliceDetails.id})
@@ -415,16 +416,15 @@
: 'EventLatency in context of other Input events',
right: this.sliceDetails ? '' : 'N/A',
}),
- m(TreeNode, {
- left: this.jankySlice
- ? getSliceForTrack(
- this.jankySlice,
- SCROLL_JANK_V3_TRACK_KIND,
- 'Jank Interval',
- )
- : 'Jank Interval',
- right: this.jankySlice ? '' : 'N/A',
- }),
+ this.jankySlice &&
+ m(TreeNode, {
+ left: renderSliceRef({
+ trace: this.trace,
+ id: this.jankySlice.id,
+ trackUri: JANKS_TRACK_URI,
+ title: this.jankySlice.causeOfJank,
+ }),
+ }),
),
);
}
@@ -498,7 +498,7 @@
);
}
- viewTab() {
+ render() {
if (this.sliceDetails) {
const slice = this.sliceDetails;
@@ -544,12 +544,4 @@
return m(DetailsShell, {title: 'Slice', description: 'Loading...'});
}
}
-
- isLoading() {
- return !this.loaded;
- }
-
- getTitle(): string {
- return `Current Selection`;
- }
}
diff --git a/ui/src/core_plugins/chrome_scroll_jank/event_latency_track.ts b/ui/src/core_plugins/chrome_scroll_jank/event_latency_track.ts
index 8581258..33b42b4 100644
--- a/ui/src/core_plugins/chrome_scroll_jank/event_latency_track.ts
+++ b/ui/src/core_plugins/chrome_scroll_jank/event_latency_track.ts
@@ -12,20 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {globals} from '../../frontend/globals';
import {NamedRow} from '../../frontend/named_slice_track';
import {NewTrackArgs} from '../../frontend/track';
-import {CHROME_EVENT_LATENCY_TRACK_KIND} from '../../public/track_kinds';
import {Slice} from '../../public/track';
import {
- CustomSqlDetailsPanelConfig,
CustomSqlTableDefConfig,
CustomSqlTableSliceTrack,
} from '../../frontend/tracks/custom_sql_table_slice_track';
-import {EventLatencySliceDetailsPanel} from './event_latency_details_panel';
import {JANK_COLOR} from './jank_colors';
-import {ScrollJankPluginState} from './common';
-import {exists} from '../../base/utils';
+import {TrackEventSelection} from '../../public/selection';
+import {EventLatencySliceDetailsPanel} from './event_latency_details_panel';
export const JANKY_LATENCY_NAME = 'Janky EventLatency';
@@ -35,32 +31,12 @@
private baseTable: string,
) {
super(args);
- ScrollJankPluginState.getInstance().registerTrack({
- kind: CHROME_EVENT_LATENCY_TRACK_KIND,
- trackUri: this.uri,
- tableName: this.tableName,
- detailsPanelConfig: this.getDetailsPanel(),
- });
- }
-
- async onDestroy(): Promise<void> {
- await super.onDestroy();
- ScrollJankPluginState.getInstance().unregisterTrack(
- CHROME_EVENT_LATENCY_TRACK_KIND,
- );
}
getSqlSource(): string {
return `SELECT * FROM ${this.baseTable}`;
}
- getDetailsPanel(): CustomSqlDetailsPanelConfig {
- return {
- kind: EventLatencySliceDetailsPanel.kind,
- config: {title: '', sqlTableName: this.tableName},
- };
- }
-
getSqlDataSource(): CustomSqlTableDefConfig {
return {
sqlTableName: this.baseTable,
@@ -76,22 +52,7 @@
}
}
- onUpdatedSlices(slices: Slice[]) {
- for (const slice of slices) {
- const currentSelection = this.trace.selection.legacySelection;
- const isSelected =
- exists(currentSelection) &&
- currentSelection.kind === 'GENERIC_SLICE' &&
- currentSelection.id !== undefined &&
- currentSelection.id === slice.id;
-
- const highlighted = globals.state.highlightedSliceId === slice.id;
- const hasFocus = highlighted || isSelected;
- slice.isHighlighted = !!hasFocus;
- }
- super.onUpdatedSlices(slices);
+ override detailsPanel(sel: TrackEventSelection) {
+ return new EventLatencySliceDetailsPanel(this.trace, sel.eventId);
}
-
- // At the moment we will just display the slice details. However, on select,
- // this behavior should be customized to show jank-related data.
}
diff --git a/ui/src/core_plugins/chrome_scroll_jank/index.ts b/ui/src/core_plugins/chrome_scroll_jank/index.ts
index a585f6b..19a0c70 100644
--- a/ui/src/core_plugins/chrome_scroll_jank/index.ts
+++ b/ui/src/core_plugins/chrome_scroll_jank/index.ts
@@ -12,128 +12,71 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {v4 as uuidv4} from 'uuid';
import {uuidv4Sql} from '../../base/uuid';
-import {generateSqlWithInternalLayout} from '../../common/internal_layout_utils';
-import {featureFlags} from '../../core/feature_flags';
-import {GenericSliceDetailsTabConfig} from '../../frontend/generic_slice_details_tab';
-import {BottomTabToSCSAdapter} from '../../public/utils';
-import {
- CHROME_EVENT_LATENCY_TRACK_KIND,
- CHROME_TOPLEVEL_SCROLLS_KIND,
- CHROME_SCROLL_JANK_TRACK_KIND,
- SCROLL_JANK_V3_TRACK_KIND,
-} from '../../public/track_kinds';
-import {NUM} from '../../trace_processor/query_result';
+import {generateSqlWithInternalLayout} from '../../trace_processor/sql_utils/layout';
import {Trace} from '../../public/trace';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
-import {Engine} from '../../trace_processor/engine';
-import {ChromeTasksScrollJankTrack} from './chrome_tasks_scroll_jank_track';
-import {ENABLE_CHROME_SCROLL_JANK_PLUGIN} from './common';
-import {EventLatencySliceDetailsPanel} from './event_latency_details_panel';
+import {PerfettoPlugin} from '../../public/plugin';
import {EventLatencyTrack, JANKY_LATENCY_NAME} from './event_latency_track';
-import {ScrollDetailsPanel} from './scroll_details_panel';
-import {ScrollJankV3DetailsPanel} from './scroll_jank_v3_details_panel';
import {ScrollJankV3Track} from './scroll_jank_v3_track';
import {TopLevelScrollTrack} from './scroll_track';
import {ScrollJankCauseMap} from './scroll_jank_cause_map';
import {TrackNode} from '../../public/workspace';
-import {getOrCreateGroupForThread} from '../../public/standard_groups';
-import {addQueryResultsTab} from '../../public/lib/query_table/query_result_tab';
+import {featureFlags} from '../../core/feature_flags';
+import {OverrideState} from '../../public/feature_flag';
-const ENABLE_SCROLL_JANK_PLUGIN_V2 = featureFlags.register({
- id: 'enableScrollJankPluginV2',
- name: 'Enable Chrome Scroll Jank plugin V2',
- description: 'Adds new tracks and visualizations for scroll jank.',
- defaultValue: false,
-});
+// Before plugins were a thing, this plugin was enabled using a feature flag.
+// However, nowadays, plugins themselves can be selectively enabled and
+// disabled. This function inspects local storage to see whether the old feature
+// flag is enabled, and patches the flags settings to enable the chrome scroll
+// jank plugin, before deleting the old flag. This provides a seamless
+// experience for anyone who currently uses the chrome scroll jank plugin.
+//
+// TODO(stevegolton): Remove this code after 2025-01-01. This should give it
+// enough time on stable for most relevant users to have run it at least once.
+function patchChromeScrollJankFlag() {
+ try {
+ const flagsKey = 'perfettoFeatureFlags';
+ const enableScrollJankPluginV2FlagKey = 'enableScrollJankPluginV2';
+ const chromeScrollJankPuginFlagKey = 'plugin_perfetto.ChromeScrollJank';
-class ChromeScrollJankPlugin implements PerfettoPlugin {
- async onTraceLoad(ctx: Trace): Promise<void> {
- if (ENABLE_CHROME_SCROLL_JANK_PLUGIN.get()) {
- await this.addChromeScrollJankTrack(ctx);
-
- if (!(await isChromeTrace(ctx.engine))) {
- return;
+ const flagsRaw = localStorage.getItem(flagsKey);
+ if (flagsRaw) {
+ const flags = JSON.parse(flagsRaw);
+ if (flags[enableScrollJankPluginV2FlagKey] === 'OVERRIDE_TRUE') {
+ featureFlags.patchOverride(
+ chromeScrollJankPuginFlagKey,
+ OverrideState.TRUE,
+ );
+ console.log(
+ `Cleared deprecated 'enableScrollJankPluginV2' flag & enabled 'ChromeScrollJank' plugin.`,
+ );
}
- // Initialise the chrome_tasks_delaying_input_processing table. It will be
- // used in the tracks above.
- await ctx.engine.query(`
- INCLUDE PERFETTO MODULE deprecated.v42.common.slices;
- SELECT RUN_METRIC(
- 'chrome/chrome_tasks_delaying_input_processing.sql',
- 'duration_causing_jank_ms',
- /* duration_causing_jank_ms = */ '8');`);
-
- const query = `
- select
- s1.full_name,
- s1.duration_ms,
- s1.slice_id,
- s1.thread_dur_ms,
- s2.id,
- s2.ts,
- s2.dur,
- s2.track_id
- from chrome_tasks_delaying_input_processing s1
- join slice s2 on s1.slice_id=s2.id
- `;
- addQueryResultsTab(ctx, {query, title: 'Scroll Jank: long tasks'});
+ // Just remove the original flag
+ delete flags[enableScrollJankPluginV2FlagKey];
+ localStorage.setItem(flagsKey, JSON.stringify(flags));
}
-
- if (ENABLE_SCROLL_JANK_PLUGIN_V2.get()) {
- const group = new TrackNode({
- title: 'Chrome Scroll Jank',
- sortOrder: -30,
- isSummary: true,
- });
- await this.addTopLevelScrollTrack(ctx, group);
- await this.addEventLatencyTrack(ctx, group);
- await this.addScrollJankV3ScrollTrack(ctx, group);
- await ScrollJankCauseMap.initialize(ctx.engine);
- ctx.workspace.addChildInOrder(group);
- group.expand();
- }
+ } catch {
+ // Ignore - this was very much best-effort.
}
+}
- private async addChromeScrollJankTrack(ctx: Trace): Promise<void> {
- const queryResult = await ctx.engine.query(`
- select
- utid,
- upid
- from thread
- where name='CrBrowserMain'
- `);
+patchChromeScrollJankFlag();
- if (queryResult.numRows() === 0) {
- return;
- }
-
- const it = queryResult.firstRow({
- utid: NUM,
- upid: NUM,
+export default class implements PerfettoPlugin {
+ static readonly id = 'perfetto.ChromeScrollJank';
+ async onTraceLoad(ctx: Trace): Promise<void> {
+ const group = new TrackNode({
+ title: 'Chrome Scroll Jank',
+ sortOrder: -30,
+ isSummary: true,
});
-
- const {upid, utid} = it;
- const uri = 'perfetto.ChromeScrollJank';
- const title = 'Scroll Jank causes - long tasks';
- ctx.tracks.registerTrack({
- uri,
- title,
- tags: {
- kind: CHROME_SCROLL_JANK_TRACK_KIND,
- upid,
- utid,
- },
- track: new ChromeTasksScrollJankTrack({
- trace: ctx,
- uri,
- }),
- });
- const group = getOrCreateGroupForThread(ctx.workspace, utid);
- const track = new TrackNode({uri, title});
- group.addChildInOrder(track);
+ await this.addTopLevelScrollTrack(ctx, group);
+ await this.addEventLatencyTrack(ctx, group);
+ await this.addScrollJankV3ScrollTrack(ctx, group);
+ await ScrollJankCauseMap.initialize(ctx.engine);
+ ctx.workspace.addChildInOrder(group);
+ group.expand();
}
private async addTopLevelScrollTrack(
@@ -143,6 +86,7 @@
await ctx.engine.query(`
INCLUDE PERFETTO MODULE chrome.chrome_scrolls;
INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_offsets;
+ INCLUDE PERFETTO MODULE chrome.event_latency;
`);
const uri = 'perfetto.ChromeScrollJank#toplevelScrolls';
@@ -151,9 +95,6 @@
ctx.tracks.registerTrack({
uri,
title,
- tags: {
- kind: CHROME_TOPLEVEL_SCROLLS_KIND,
- },
track: new TopLevelScrollTrack({
trace: ctx,
uri,
@@ -162,25 +103,6 @@
const track = new TrackNode({uri, title});
group.addChildInOrder(track);
-
- ctx.tabs.registerDetailsPanel(
- new BottomTabToSCSAdapter({
- tabFactory: (selection) => {
- if (
- selection.kind === 'GENERIC_SLICE' &&
- selection.detailsPanelConfig.kind === ScrollDetailsPanel.kind
- ) {
- const config = selection.detailsPanelConfig.config;
- return new ScrollDetailsPanel({
- config: config as GenericSliceDetailsTabConfig,
- trace: ctx,
- uuid: uuidv4(),
- });
- }
- return undefined;
- },
- }),
- );
}
private async addEventLatencyTrack(
@@ -189,19 +111,15 @@
): Promise<void> {
const subTableSql = generateSqlWithInternalLayout({
columns: ['id', 'ts', 'dur', 'track_id', 'name'],
- sourceTable: 'slice',
+ sourceTable: 'chrome_event_latencies',
ts: 'ts',
dur: 'dur',
whereClause: `
- EXTRACT_ARG(arg_set_id, 'event_latency.event_type') IN (
+ event_type IN (
'FIRST_GESTURE_SCROLL_UPDATE',
'GESTURE_SCROLL_UPDATE',
'INERTIAL_GESTURE_SCROLL_UPDATE')
- AND has_descendant_slice_with_name(
- id,
- 'SubmitCompositorFrameToPresentationCompositorFrame')
- AND name = 'EventLatency'
- AND depth = 0`,
+ AND is_presented`,
});
// Table name must be unique - it cannot include '-' characters or begin
@@ -289,34 +207,11 @@
ctx.tracks.registerTrack({
uri,
title,
- tags: {
- kind: CHROME_EVENT_LATENCY_TRACK_KIND,
- },
track: new EventLatencyTrack({trace: ctx, uri}, baseTable),
});
const track = new TrackNode({uri, title});
group.addChildInOrder(track);
-
- ctx.tabs.registerDetailsPanel(
- new BottomTabToSCSAdapter({
- tabFactory: (selection) => {
- if (
- selection.kind === 'GENERIC_SLICE' &&
- selection.detailsPanelConfig.kind ===
- EventLatencySliceDetailsPanel.kind
- ) {
- const config = selection.detailsPanelConfig.config;
- return new EventLatencySliceDetailsPanel({
- config: config as GenericSliceDetailsTabConfig,
- trace: ctx,
- uuid: uuidv4(),
- });
- }
- return undefined;
- },
- }),
- );
}
private async addScrollJankV3ScrollTrack(
@@ -333,9 +228,6 @@
ctx.tracks.registerTrack({
uri,
title,
- tags: {
- kind: SCROLL_JANK_V3_TRACK_KIND,
- },
track: new ScrollJankV3Track({
trace: ctx,
uri,
@@ -344,44 +236,5 @@
const track = new TrackNode({uri, title});
group.addChildInOrder(track);
-
- ctx.tabs.registerDetailsPanel(
- new BottomTabToSCSAdapter({
- tabFactory: (selection) => {
- if (
- selection.kind === 'GENERIC_SLICE' &&
- selection.detailsPanelConfig.kind === ScrollJankV3DetailsPanel.kind
- ) {
- const config = selection.detailsPanelConfig.config;
- return new ScrollJankV3DetailsPanel({
- config: config as GenericSliceDetailsTabConfig,
- trace: ctx,
- uuid: uuidv4(),
- });
- }
- return undefined;
- },
- }),
- );
}
}
-
-async function isChromeTrace(engine: Engine) {
- const queryResult = await engine.query(`
- select utid, upid
- from thread
- where name='CrBrowserMain'
- `);
-
- const it = queryResult.iter({
- utid: NUM,
- upid: NUM,
- });
-
- return it.valid();
-}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'perfetto.ChromeScrollJank',
- plugin: ChromeScrollJankPlugin,
-};
diff --git a/ui/src/core_plugins/chrome_scroll_jank/jank_colors.ts b/ui/src/core_plugins/chrome_scroll_jank/jank_colors.ts
index c2e4686..f764ebf 100644
--- a/ui/src/core_plugins/chrome_scroll_jank/jank_colors.ts
+++ b/ui/src/core_plugins/chrome_scroll_jank/jank_colors.ts
@@ -13,6 +13,6 @@
// limitations under the License.
import {HSLColor} from '../../public/color';
-import {makeColorScheme} from '../../core/colorizer';
+import {makeColorScheme} from '../../public/lib/colorizer';
export const JANK_COLOR = makeColorScheme(new HSLColor([343, 100, 43]));
diff --git a/ui/src/core_plugins/chrome_scroll_jank/scroll_details_panel.ts b/ui/src/core_plugins/chrome_scroll_jank/scroll_details_panel.ts
index c184ba7..eed111f 100644
--- a/ui/src/core_plugins/chrome_scroll_jank/scroll_details_panel.ts
+++ b/ui/src/core_plugins/chrome_scroll_jank/scroll_details_panel.ts
@@ -15,19 +15,21 @@
import m from 'mithril';
import {duration, Time, time} from '../../base/time';
import {exists} from '../../base/utils';
-import {raf} from '../../core/raf_scheduler';
-import {BottomTab, NewBottomTabArgs} from '../../public/lib/bottom_tab';
-import {GenericSliceDetailsTabConfig} from '../../frontend/generic_slice_details_tab';
import {
ColumnDescriptor,
- numberColumn,
Table,
TableData,
widgetColumn,
-} from '../../frontend/tables/table';
+} from '../../widgets/table';
import {DurationWidget} from '../../frontend/widgets/duration';
import {Timestamp} from '../../frontend/widgets/timestamp';
-import {LONG, NUM, STR} from '../../trace_processor/query_result';
+import {
+ LONG,
+ LONG_NULL,
+ NUM,
+ NUM_NULL,
+ STR,
+} from '../../trace_processor/query_result';
import {DetailsShell} from '../../widgets/details_shell';
import {GridLayout, GridLayoutColumn} from '../../widgets/grid_layout';
import {Section} from '../../widgets/section';
@@ -41,12 +43,9 @@
getPredictorJankDeltas,
getPresentedScrollDeltas,
} from './scroll_delta_graph';
-import {
- getScrollJankSlices,
- getSliceForTrack,
- ScrollJankSlice,
-} from './scroll_jank_slice';
-import {SCROLL_JANK_V3_TRACK_KIND} from '../../public/track_kinds';
+import {JANKS_TRACK_URI, renderSliceRef} from './selection_utils';
+import {TrackEventDetailsPanel} from '../../public/details_panel';
+import {Trace} from '../../public/trace';
interface Data {
// Scroll ID.
@@ -71,32 +70,27 @@
interface JankSliceDetails {
cause: string;
- jankSlice: ScrollJankSlice;
- delayDur: duration;
- delayVsync: number;
+ id: number;
+ ts: time;
+ dur?: duration;
+ delayVsync?: number;
}
-export class ScrollDetailsPanel extends BottomTab<GenericSliceDetailsTabConfig> {
- static readonly kind = 'org.perfetto.ScrollDetailsPanel';
- loaded = false;
- data: Data | undefined;
- metrics: Metrics = {};
- orderedJankSlices: JankSliceDetails[] = [];
- scrollDeltas: m.Child;
+export class ScrollDetailsPanel implements TrackEventDetailsPanel {
+ private data?: Data;
+ private metrics: Metrics = {};
+ private orderedJankSlices: JankSliceDetails[] = [];
- static create(
- args: NewBottomTabArgs<GenericSliceDetailsTabConfig>,
- ): ScrollDetailsPanel {
- return new ScrollDetailsPanel(args);
- }
+ // TODO(altimin): Don't store Mithril vnodes between render cycles.
+ private scrollDeltas: m.Child;
- constructor(args: NewBottomTabArgs<GenericSliceDetailsTabConfig>) {
- super(args);
- this.loadData();
- }
+ constructor(
+ private readonly trace: Trace,
+ private readonly id: number,
+ ) {}
- private async loadData() {
- const queryResult = await this.engine.query(`
+ async load() {
+ const queryResult = await this.trace.engine.query(`
WITH scrolls AS (
SELECT
id,
@@ -107,7 +101,7 @@
THEN gesture_scroll_begin_ts + dur
ELSE ts + dur
END AS end_ts
- FROM chrome_scrolls WHERE id = ${this.config.id})
+ FROM chrome_scrolls WHERE id = ${this.id})
SELECT
id,
start_ts AS ts,
@@ -126,8 +120,6 @@
};
await this.loadMetrics();
- this.loaded = true;
- raf.scheduleFullRedraw();
}
private async loadMetrics() {
@@ -139,7 +131,7 @@
private async loadInputEventCount() {
if (exists(this.data)) {
- const queryResult = await this.engine.query(`
+ const queryResult = await this.trace.engine.query(`
SELECT
COUNT(*) AS inputEventCount
FROM slice s
@@ -159,7 +151,7 @@
private async loadFrameStats() {
if (exists(this.data)) {
- const queryResult = await this.engine.query(`
+ const queryResult = await this.trace.engine.query(`
SELECT
IFNULL(frame_count, 0) AS frameCount,
IFNULL(missed_vsyncs, 0) AS missedVsyncs,
@@ -190,39 +182,36 @@
private async loadDelayData() {
if (exists(this.data)) {
- const queryResult = await this.engine.query(`
+ const queryResult = await this.trace.engine.query(`
SELECT
+ id,
+ ts,
+ dur,
IFNULL(sub_cause_of_jank, IFNULL(cause_of_jank, 'Unknown')) AS cause,
- IFNULL(event_latency_id, 0) AS eventLatencyId,
- IFNULL(dur, 0) AS delayDur,
- IFNULL(delayed_frame_count, 0) AS delayVsync
+ event_latency_id AS eventLatencyId,
+ delayed_frame_count AS delayVsync
FROM chrome_janky_frame_presentation_intervals s
WHERE s.ts >= ${this.data.ts}
AND s.ts + s.dur <= ${this.data.ts + this.data.dur}
- ORDER by delayDur DESC;
+ ORDER by dur DESC;
`);
- const iter = queryResult.iter({
+ const it = queryResult.iter({
+ id: NUM,
+ ts: LONG,
+ dur: LONG_NULL,
cause: STR,
- eventLatencyId: NUM,
- delayDur: LONG,
- delayVsync: NUM,
+ eventLatencyId: NUM_NULL,
+ delayVsync: NUM_NULL,
});
- for (; iter.valid(); iter.next()) {
- if (iter.delayDur <= 0) {
- break;
- }
- const jankSlices = await getScrollJankSlices(
- this.engine,
- iter.eventLatencyId,
- );
-
+ for (; it.valid(); it.next()) {
this.orderedJankSlices.push({
- cause: iter.cause,
- jankSlice: jankSlices[0],
- delayDur: iter.delayDur,
- delayVsync: iter.delayVsync,
+ id: it.id,
+ ts: Time.fromRaw(it.ts),
+ dur: it.dur ?? undefined,
+ cause: it.cause,
+ delayVsync: it.delayVsync ?? undefined,
});
}
}
@@ -230,17 +219,20 @@
private async loadScrollOffsets() {
if (exists(this.data)) {
- const inputDeltas = await getInputScrollDeltas(this.engine, this.data.id);
+ const inputDeltas = await getInputScrollDeltas(
+ this.trace.engine,
+ this.data.id,
+ );
const presentedDeltas = await getPresentedScrollDeltas(
- this.engine,
+ this.trace.engine,
this.data.id,
);
const predictorDeltas = await getPredictorJankDeltas(
- this.engine,
+ this.trace.engine,
this.data.id,
);
const jankIntervals = await getJankIntervals(
- this.engine,
+ this.trace.engine,
this.data.ts,
this.data.dur,
);
@@ -310,31 +302,27 @@
private getDelayTable(): m.Child {
if (this.orderedJankSlices.length > 0) {
- interface DelayData {
- jankLink: m.Child;
- dur: m.Child;
- delayedVSyncs: number;
- }
-
- const columns: ColumnDescriptor<DelayData>[] = [
- widgetColumn<DelayData>('Cause', (x) => x.jankLink),
- widgetColumn<DelayData>('Duration', (x) => x.dur),
- numberColumn<DelayData>('Delayed Vsyncs', (x) => x.delayedVSyncs),
+ const columns: ColumnDescriptor<JankSliceDetails>[] = [
+ widgetColumn<JankSliceDetails>('Cause', (jankSlice) =>
+ renderSliceRef({
+ trace: this.trace,
+ id: jankSlice.id,
+ trackUri: JANKS_TRACK_URI,
+ title: jankSlice.cause,
+ }),
+ ),
+ widgetColumn<JankSliceDetails>('Duration', (jankSlice) =>
+ jankSlice.dur !== undefined
+ ? m(DurationWidget, {dur: jankSlice.dur})
+ : 'NULL',
+ ),
+ widgetColumn<JankSliceDetails>(
+ 'Delayed Vsyncs',
+ (jankSlice) => jankSlice.delayVsync,
+ ),
];
- const data: DelayData[] = [];
- for (const jankSlice of this.orderedJankSlices) {
- data.push({
- jankLink: getSliceForTrack(
- jankSlice.jankSlice,
- SCROLL_JANK_V3_TRACK_KIND,
- jankSlice.cause,
- ),
- dur: m(DurationWidget, {dur: jankSlice.delayDur}),
- delayedVSyncs: jankSlice.delayVsync,
- });
- }
- const tableData = new TableData(data);
+ const tableData = new TableData(this.orderedJankSlices);
return m(Table, {
data: tableData,
@@ -391,8 +379,8 @@
);
}
- viewTab() {
- if (this.isLoading() || this.data == undefined) {
+ render() {
+ if (this.data == undefined) {
return m('h2', 'Loading');
}
@@ -400,13 +388,13 @@
'Scroll ID': this.data.id,
'Start time': m(Timestamp, {ts: this.data.ts}),
'Duration': m(DurationWidget, {dur: this.data.dur}),
- 'SQL ID': m(SqlRef, {table: 'chrome_scrolls', id: this.config.id}),
+ 'SQL ID': m(SqlRef, {table: 'chrome_scrolls', id: this.id}),
});
return m(
DetailsShell,
{
- title: this.getTitle(),
+ title: 'Scroll',
},
m(
GridLayout,
@@ -437,12 +425,4 @@
),
);
}
-
- getTitle(): string {
- return this.config.title;
- }
-
- isLoading() {
- return !this.loaded;
- }
}
diff --git a/ui/src/core_plugins/chrome_scroll_jank/scroll_jank_slice.ts b/ui/src/core_plugins/chrome_scroll_jank/scroll_jank_slice.ts
deleted file mode 100644
index 2b3e33c..0000000
--- a/ui/src/core_plugins/chrome_scroll_jank/scroll_jank_slice.ts
+++ /dev/null
@@ -1,214 +0,0 @@
-// 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 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 {Icons} from '../../base/semantic_icons';
-import {duration, time, Time} from '../../base/time';
-import {globals} from '../../frontend/globals';
-import {SliceSqlId} from '../../trace_processor/sql_utils/core_types';
-import {Engine} from '../../trace_processor/engine';
-import {LONG, NUM} from '../../trace_processor/query_result';
-import {
- constraintsToQuerySuffix,
- SQLConstraints,
-} from '../../trace_processor/sql_utils';
-import {Anchor} from '../../widgets/anchor';
-import {ScrollJankPluginState, ScrollJankTrackSpec} from './common';
-import {
- CHROME_EVENT_LATENCY_TRACK_KIND,
- SCROLL_JANK_V3_TRACK_KIND,
-} from '../../public/track_kinds';
-import {scrollTo} from '../../public/scroll_helper';
-
-interface BasicSlice {
- // ID of slice.
- sliceId: number;
- // Timestamp of the beginning of this slice in nanoseconds.
- ts: time;
- // Duration of this slice in nanoseconds.
- dur: duration;
-}
-
-async function getSlicesFromTrack(
- engine: Engine,
- track: ScrollJankTrackSpec,
- constraints: SQLConstraints,
-): Promise<BasicSlice[]> {
- const query = await engine.query(`
- SELECT
- id AS sliceId,
- ts,
- dur AS dur
- FROM ${track.sqlTableName}
- ${constraintsToQuerySuffix(constraints)}`);
- const it = query.iter({
- sliceId: NUM,
- ts: LONG,
- dur: LONG,
- });
-
- const result: BasicSlice[] = [];
- for (; it.valid(); it.next()) {
- result.push({
- sliceId: it.sliceId as number,
- ts: Time.fromRaw(it.ts),
- dur: it.dur,
- });
- }
- return result;
-}
-
-export type ScrollJankSlice = BasicSlice;
-export async function getScrollJankSlices(
- engine: Engine,
- id: number,
-): Promise<ScrollJankSlice[]> {
- const track = ScrollJankPluginState.getInstance().getTrack(
- SCROLL_JANK_V3_TRACK_KIND,
- );
- if (track == undefined) {
- throw new Error(`${SCROLL_JANK_V3_TRACK_KIND} track is not registered.`);
- }
-
- const slices = await getSlicesFromTrack(engine, track, {
- filters: [`event_latency_id=${id}`],
- });
- return slices;
-}
-
-export type EventLatencySlice = BasicSlice;
-export async function getEventLatencySlice(
- engine: Engine,
- id: number,
-): Promise<EventLatencySlice | undefined> {
- const track = ScrollJankPluginState.getInstance().getTrack(
- CHROME_EVENT_LATENCY_TRACK_KIND,
- );
- if (track == undefined) {
- throw new Error(
- `${CHROME_EVENT_LATENCY_TRACK_KIND} track is not registered.`,
- );
- }
-
- const slices = await getSlicesFromTrack(engine, track, {
- filters: [`id=${id}`],
- });
- return slices[0];
-}
-
-export async function getEventLatencyDescendantSlice(
- engine: Engine,
- id: number,
- descendant: string | undefined,
-): Promise<EventLatencySlice | undefined> {
- const query = await engine.query(`
- SELECT
- id as sliceId,
- ts,
- dur as dur
- FROM descendant_slice(${id})
- WHERE name='${descendant}'`);
- const it = query.iter({
- sliceId: NUM,
- ts: LONG,
- dur: LONG,
- });
-
- const result: EventLatencySlice[] = [];
-
- for (; it.valid(); it.next()) {
- result.push({
- sliceId: it.sliceId as SliceSqlId,
- ts: Time.fromRaw(it.ts),
- dur: it.dur,
- });
- }
-
- const eventLatencyTrack = ScrollJankPluginState.getInstance().getTrack(
- CHROME_EVENT_LATENCY_TRACK_KIND,
- );
- if (eventLatencyTrack == undefined) {
- throw new Error(
- `${CHROME_EVENT_LATENCY_TRACK_KIND} track is not registered.`,
- );
- }
-
- if (result.length > 1) {
- throw new Error(`
- Slice table and track view ${eventLatencyTrack.sqlTableName} has more than one descendant of slice id ${id} with name ${descendant}`);
- }
- if (result.length === 0) {
- return undefined;
- }
- return result[0];
-}
-
-interface BasicScrollJankSliceRefAttrs {
- id: number;
- ts: time;
- dur: duration;
- name: string;
- kind: string;
-}
-
-export class ScrollJankSliceRef
- implements m.ClassComponent<BasicScrollJankSliceRefAttrs>
-{
- view(vnode: m.Vnode<BasicScrollJankSliceRefAttrs>) {
- return m(
- Anchor,
- {
- icon: Icons.UpdateSelection,
- onclick: () => {
- const track = ScrollJankPluginState.getInstance().getTrack(
- vnode.attrs.kind,
- );
- if (track == undefined) {
- throw new Error(`${vnode.attrs.kind} track is not registered.`);
- }
-
- const trackUri = track.key;
- globals.selectionManager.selectGenericSlice({
- id: vnode.attrs.id,
- sqlTableName: track.sqlTableName,
- start: vnode.attrs.ts,
- duration: vnode.attrs.dur,
- trackUri,
- detailsPanelConfig: track.detailsPanelConfig,
- });
-
- scrollTo({
- track: {uri: trackUri, expandGroup: true},
- time: {start: vnode.attrs.ts},
- });
- },
- },
- vnode.attrs.name,
- );
- }
-}
-
-export function getSliceForTrack(
- state: BasicSlice,
- trackKind: string,
- name: string,
-): m.Child {
- return m(ScrollJankSliceRef, {
- id: state.sliceId,
- ts: state.ts,
- dur: state.dur,
- name: name,
- kind: trackKind,
- });
-}
diff --git a/ui/src/core_plugins/chrome_scroll_jank/scroll_jank_v3_details_panel.ts b/ui/src/core_plugins/chrome_scroll_jank/scroll_jank_v3_details_panel.ts
index 48fd988..fc51eb4 100644
--- a/ui/src/core_plugins/chrome_scroll_jank/scroll_jank_v3_details_panel.ts
+++ b/ui/src/core_plugins/chrome_scroll_jank/scroll_jank_v3_details_panel.ts
@@ -15,9 +15,6 @@
import m from 'mithril';
import {duration, Time, time} from '../../base/time';
import {exists} from '../../base/utils';
-import {raf} from '../../core/raf_scheduler';
-import {BottomTab, NewBottomTabArgs} from '../../public/lib/bottom_tab';
-import {GenericSliceDetailsTabConfig} from '../../frontend/generic_slice_details_tab';
import {getSlice, SliceDetails} from '../../trace_processor/sql_utils/slice';
import {asSliceSqlId} from '../../trace_processor/sql_utils/core_types';
import {DurationWidget} from '../../frontend/widgets/duration';
@@ -30,13 +27,9 @@
import {SqlRef} from '../../widgets/sql_ref';
import {MultiParagraphText, TextParagraph} from '../../widgets/text_paragraph';
import {dictToTreeNodes, Tree, TreeNode} from '../../widgets/tree';
-import {
- EventLatencySlice,
- getEventLatencyDescendantSlice,
- getEventLatencySlice,
- getSliceForTrack,
-} from './scroll_jank_slice';
-import {CHROME_EVENT_LATENCY_TRACK_KIND} from '../../public/track_kinds';
+import {EVENT_LATENCY_TRACK_URI, renderSliceRef} from './selection_utils';
+import {TrackEventDetailsPanel} from '../../public/details_panel';
+import {Trace} from '../../public/trace';
interface Data {
name: string;
@@ -64,10 +57,8 @@
return getSlice(engine, asSliceSqlId(id));
}
-export class ScrollJankV3DetailsPanel extends BottomTab<GenericSliceDetailsTabConfig> {
- static readonly kind = 'org.perfetto.ScrollJankV3DetailsPanel';
- data: Data | undefined;
- loaded = false;
+export class ScrollJankV3DetailsPanel implements TrackEventDetailsPanel {
+ private data?: Data;
//
// Linking to associated slices
@@ -80,29 +71,34 @@
// Link to the Event Latency in the EventLatencyTrack (subset of event
// latencies associated with input events).
- private eventLatencySliceDetails?: EventLatencySlice;
+ private eventLatencySliceDetails?: {
+ ts: time;
+ dur: duration;
+ };
// Link to the scroll jank cause stage of the associated EventLatencyTrack
// slice. May be unknown.
- private causeSliceDetails?: EventLatencySlice;
+ private causeSliceDetails?: {
+ id: number;
+ ts: time;
+ dur: duration;
+ };
// Link to the scroll jank sub-cause stage of the associated EventLatencyTrack
// slice. Does not apply to all causes.
- private subcauseSliceDetails?: EventLatencySlice;
+ private subcauseSliceDetails?: {
+ id: number;
+ ts: time;
+ dur: duration;
+ };
- static create(
- args: NewBottomTabArgs<GenericSliceDetailsTabConfig>,
- ): ScrollJankV3DetailsPanel {
- return new ScrollJankV3DetailsPanel(args);
- }
+ constructor(
+ private readonly trace: Trace,
+ private readonly id: number,
+ ) {}
- constructor(args: NewBottomTabArgs<GenericSliceDetailsTabConfig>) {
- super(args);
- this.loadData();
- }
-
- private async loadData() {
- const queryResult = await this.engine.query(`
+ async load() {
+ const queryResult = await this.trace.engine.query(`
SELECT
IIF(
cause_of_jank IS NOT NULL,
@@ -117,7 +113,7 @@
IFNULL(cause_of_jank, "UNKNOWN") AS causeOfJank,
IFNULL(sub_cause_of_jank, "UNKNOWN") AS subcauseOfJank
FROM chrome_janky_frame_presentation_intervals
- WHERE id = ${this.config.id}`);
+ WHERE id = ${this.id}`);
const iter = queryResult.firstRow({
name: STR,
@@ -143,8 +139,7 @@
await this.loadJankyFrames();
await this.loadSlices();
- this.loaded = true;
- raf.scheduleFullRedraw();
+ this.trace.scheduleFullRedraw();
}
private hasCause(): boolean {
@@ -164,35 +159,62 @@
private async loadSlices() {
if (exists(this.data)) {
this.sliceDetails = await getSliceDetails(
- this.engine,
+ this.trace.engine,
this.data.eventLatencyId,
);
- this.eventLatencySliceDetails = await getEventLatencySlice(
- this.engine,
- this.data.eventLatencyId,
- );
+ const it = (
+ await this.trace.engine.query(`
+ SELECT ts, dur
+ FROM slice
+ WHERE id = ${this.data.eventLatencyId}
+ `)
+ ).iter({ts: LONG, dur: LONG});
+ this.eventLatencySliceDetails = {
+ ts: Time.fromRaw(it.ts),
+ dur: it.dur,
+ };
if (this.hasCause()) {
- this.causeSliceDetails = await getEventLatencyDescendantSlice(
- this.engine,
- this.data.eventLatencyId,
- this.data.jankCause,
- );
+ const it = (
+ await this.trace.engine.query(`
+ SELECT id, ts, dur
+ FROM descendant_slice(${this.data.eventLatencyId})
+ WHERE name = "${this.data.jankCause}"
+ `)
+ ).iter({id: NUM, ts: LONG, dur: LONG});
+
+ if (it.valid()) {
+ this.causeSliceDetails = {
+ id: it.id,
+ ts: Time.fromRaw(it.ts),
+ dur: it.dur,
+ };
+ }
}
if (this.hasSubcause()) {
- this.subcauseSliceDetails = await getEventLatencyDescendantSlice(
- this.engine,
- this.data.eventLatencyId,
- this.data.jankSubcause,
- );
+ const it = (
+ await this.trace.engine.query(`
+ SELECT id, ts, dur
+ FROM descendant_slice(${this.data.eventLatencyId})
+ WHERE name = "${this.data.jankSubcause}"
+ `)
+ ).iter({id: NUM, ts: LONG, dur: LONG});
+
+ if (it.valid()) {
+ this.subcauseSliceDetails = {
+ id: it.id,
+ ts: Time.fromRaw(it.ts),
+ dur: it.dur,
+ };
+ }
}
}
}
private async loadJankyFrames() {
if (exists(this.data)) {
- const queryResult = await this.engine.query(`
+ const queryResult = await this.trace.engine.query(`
SELECT
COUNT(*) AS jankyFrames
FROM chrome_frame_info_with_delay
@@ -263,33 +285,36 @@
const result: {[key: string]: m.Child} = {};
if (exists(this.sliceDetails) && exists(this.data)) {
- result['Janked Event Latency stage'] = exists(this.causeSliceDetails)
- ? getSliceForTrack(
- this.causeSliceDetails,
- CHROME_EVENT_LATENCY_TRACK_KIND,
- this.data.jankCause,
- )
- : this.data.jankCause;
+ result['Janked Event Latency stage'] =
+ exists(this.causeSliceDetails) &&
+ renderSliceRef({
+ trace: this.trace,
+ id: this.causeSliceDetails.id,
+ trackUri: EVENT_LATENCY_TRACK_URI,
+ title: this.data.jankCause,
+ });
if (this.hasSubcause()) {
- result['Sub-cause of Jank'] = exists(this.subcauseSliceDetails)
- ? getSliceForTrack(
- this.subcauseSliceDetails,
- CHROME_EVENT_LATENCY_TRACK_KIND,
- this.data.jankSubcause,
- )
- : this.data.jankSubcause;
+ result['Sub-cause of Jank'] =
+ exists(this.subcauseSliceDetails) &&
+ renderSliceRef({
+ trace: this.trace,
+ id: this.subcauseSliceDetails.id,
+ trackUri: EVENT_LATENCY_TRACK_URI,
+ title: this.data.jankCause,
+ });
}
const children = dictToTreeNodes(result);
if (exists(this.eventLatencySliceDetails)) {
children.unshift(
m(TreeNode, {
- left: getSliceForTrack(
- this.eventLatencySliceDetails,
- CHROME_EVENT_LATENCY_TRACK_KIND,
- 'Input EventLatency in context of ScrollUpdates',
- ),
+ left: renderSliceRef({
+ trace: this.trace,
+ id: this.data.eventLatencyId,
+ trackUri: EVENT_LATENCY_TRACK_URI,
+ title: this.data.jankCause,
+ }),
right: '',
}),
);
@@ -303,7 +328,7 @@
return dictToTreeNodes(result);
}
- viewTab() {
+ render() {
if (this.data === undefined) {
return m('h2', 'Loading');
}
@@ -313,7 +338,7 @@
return m(
DetailsShell,
{
- title: this.getTitle(),
+ title: 'EventLatency',
},
m(
GridLayout,
@@ -326,12 +351,4 @@
),
);
}
-
- getTitle(): string {
- return this.config.title;
- }
-
- isLoading() {
- return !this.loaded;
- }
}
diff --git a/ui/src/core_plugins/chrome_scroll_jank/scroll_jank_v3_track.ts b/ui/src/core_plugins/chrome_scroll_jank/scroll_jank_v3_track.ts
index 81175e4..4683dfa 100644
--- a/ui/src/core_plugins/chrome_scroll_jank/scroll_jank_v3_track.ts
+++ b/ui/src/core_plugins/chrome_scroll_jank/scroll_jank_v3_track.ts
@@ -12,35 +12,21 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {globals} from '../../frontend/globals';
import {NamedRow} from '../../frontend/named_slice_track';
-import {NewTrackArgs} from '../../frontend/track';
-import {SCROLL_JANK_V3_TRACK_KIND} from '../../public/track_kinds';
import {Slice} from '../../public/track';
import {
- CustomSqlDetailsPanelConfig,
CustomSqlTableDefConfig,
CustomSqlTableSliceTrack,
} from '../../frontend/tracks/custom_sql_table_slice_track';
import {JANK_COLOR} from './jank_colors';
+import {getColorForSlice} from '../../public/lib/colorizer';
+import {TrackEventSelection} from '../../public/selection';
import {ScrollJankV3DetailsPanel} from './scroll_jank_v3_details_panel';
-import {getColorForSlice} from '../../core/colorizer';
-import {ScrollJankPluginState} from './common';
const UNKNOWN_SLICE_NAME = 'Unknown';
const JANK_SLICE_NAME = ' Jank';
export class ScrollJankV3Track extends CustomSqlTableSliceTrack {
- constructor(args: NewTrackArgs) {
- super(args);
- ScrollJankPluginState.getInstance().registerTrack({
- kind: SCROLL_JANK_V3_TRACK_KIND,
- trackUri: this.uri,
- tableName: this.tableName,
- detailsPanelConfig: this.getDetailsPanel(),
- });
- }
-
getSqlDataSource(): CustomSqlTableDefConfig {
return {
columns: [
@@ -58,23 +44,6 @@
};
}
- getDetailsPanel(): CustomSqlDetailsPanelConfig {
- return {
- kind: ScrollJankV3DetailsPanel.kind,
- config: {
- sqlTableName: 'chrome_janky_frame_presentation_intervals',
- title: 'Chrome Scroll Janks',
- },
- };
- }
-
- async onDestroy(): Promise<void> {
- await super.onDestroy();
- ScrollJankPluginState.getInstance().unregisterTrack(
- SCROLL_JANK_V3_TRACK_KIND,
- );
- }
-
rowToSlice(row: NamedRow): Slice {
const slice = super.rowToSlice(row);
@@ -93,19 +62,7 @@
}
}
- onUpdatedSlices(slices: Slice[]) {
- for (const slice of slices) {
- const currentSelection = globals.selectionManager.legacySelection;
- const isSelected =
- currentSelection &&
- currentSelection.kind === 'GENERIC_SLICE' &&
- currentSelection.id !== undefined &&
- currentSelection.id === slice.id;
-
- const highlighted = globals.state.highlightedSliceId === slice.id;
- const hasFocus = highlighted || isSelected;
- slice.isHighlighted = !!hasFocus;
- }
- super.onUpdatedSlices(slices);
+ override detailsPanel(sel: TrackEventSelection) {
+ return new ScrollJankV3DetailsPanel(this.trace, sel.eventId);
}
}
diff --git a/ui/src/core_plugins/chrome_scroll_jank/scroll_track.ts b/ui/src/core_plugins/chrome_scroll_jank/scroll_track.ts
index 1ee7d2c..3721123 100644
--- a/ui/src/core_plugins/chrome_scroll_jank/scroll_track.ts
+++ b/ui/src/core_plugins/chrome_scroll_jank/scroll_track.ts
@@ -12,19 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {NewTrackArgs} from '../../frontend/track';
-import {CHROME_TOPLEVEL_SCROLLS_KIND} from '../../public/track_kinds';
import {
- CustomSqlDetailsPanelConfig,
CustomSqlTableDefConfig,
CustomSqlTableSliceTrack,
} from '../../frontend/tracks/custom_sql_table_slice_track';
-import {ScrollJankPluginState} from './common';
+import {TrackEventSelection} from '../../public/selection';
import {ScrollDetailsPanel} from './scroll_details_panel';
export class TopLevelScrollTrack extends CustomSqlTableSliceTrack {
- public static kind = CHROME_TOPLEVEL_SCROLLS_KIND;
-
getSqlDataSource(): CustomSqlTableDefConfig {
return {
columns: [`printf("Scroll %s", CAST(id AS STRING)) AS name`, '*'],
@@ -32,31 +27,7 @@
};
}
- getDetailsPanel(): CustomSqlDetailsPanelConfig {
- return {
- kind: ScrollDetailsPanel.kind,
- config: {
- sqlTableName: this.tableName,
- title: 'Chrome Top Level Scrolls',
- },
- };
- }
-
- constructor(args: NewTrackArgs) {
- super(args);
-
- ScrollJankPluginState.getInstance().registerTrack({
- kind: TopLevelScrollTrack.kind,
- trackUri: this.uri,
- tableName: this.tableName,
- detailsPanelConfig: this.getDetailsPanel(),
- });
- }
-
- async onDestroy(): Promise<void> {
- await super.onDestroy();
- ScrollJankPluginState.getInstance().unregisterTrack(
- TopLevelScrollTrack.kind,
- );
+ override detailsPanel(sel: TrackEventSelection) {
+ return new ScrollDetailsPanel(this.trace, sel.eventId);
}
}
diff --git a/ui/src/core_plugins/chrome_scroll_jank/selection_utils.ts b/ui/src/core_plugins/chrome_scroll_jank/selection_utils.ts
new file mode 100644
index 0000000..4b79e05
--- /dev/null
+++ b/ui/src/core_plugins/chrome_scroll_jank/selection_utils.ts
@@ -0,0 +1,42 @@
+// 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 {Anchor} from '../../widgets/anchor';
+import {Icons} from '../../base/semantic_icons';
+import {Trace} from '../../public/trace';
+
+export const SCROLLS_TRACK_URI = 'perfetto.ChromeScrollJank#toplevelScrolls';
+export const EVENT_LATENCY_TRACK_URI = 'perfetto.ChromeScrollJank#eventLatency';
+export const JANKS_TRACK_URI = 'perfetto.ChromeScrollJank#scrollJankV3';
+
+export function renderSliceRef(args: {
+ trace: Trace;
+ id: number;
+ trackUri: string;
+ title: m.Children;
+}) {
+ return m(
+ Anchor,
+ {
+ icon: Icons.UpdateSelection,
+ onclick: () => {
+ args.trace.selection.selectTrackEvent(args.trackUri, args.id, {
+ scrollToSelection: true,
+ });
+ },
+ },
+ args.title,
+ );
+}
diff --git a/ui/src/core_plugins/commands/index.ts b/ui/src/core_plugins/commands/index.ts
index 9c87de4..b937daa 100644
--- a/ui/src/core_plugins/commands/index.ts
+++ b/ui/src/core_plugins/commands/index.ts
@@ -14,12 +14,10 @@
import {Time, time} from '../../base/time';
import {exists} from '../../base/utils';
-import {Actions} from '../../common/actions';
-import {globals} from '../../frontend/globals';
import {openInOldUIWithSizeCheck} from '../../frontend/legacy_trace_viewer';
import {Trace} from '../../public/trace';
import {App} from '../../public/app';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
+import {PerfettoPlugin} from '../../public/plugin';
import {
isLegacyTrace,
openFileWithLegacyTraceViewer,
@@ -93,28 +91,19 @@
order by total_self_size desc
limit 100;`;
-class CoreCommandsPlugin implements PerfettoPlugin {
- onActivate(ctx: App) {
- ctx.commands.registerCommand({
- id: 'perfetto.CoreCommands#ToggleLeftSidebar',
- name: 'Toggle left sidebar',
- callback: () => {
- if (globals.state.sidebarVisible) {
- globals.dispatch(
- Actions.setSidebar({
- visible: false,
- }),
- );
- } else {
- globals.dispatch(
- Actions.setSidebar({
- visible: true,
- }),
- );
- }
- },
- defaultHotkey: '!Mod+B',
- });
+export default class implements PerfettoPlugin {
+ static readonly id = 'perfetto.CoreCommands';
+ static onActivate(ctx: App) {
+ if (ctx.sidebar.enabled) {
+ ctx.commands.registerCommand({
+ id: 'perfetto.CoreCommands#ToggleLeftSidebar',
+ name: 'Toggle left sidebar',
+ callback: () => {
+ ctx.sidebar.toggleVisibility();
+ },
+ defaultHotkey: '!Mod+B',
+ });
+ }
const input = document.createElement('input');
input.classList.add('trace_file');
@@ -135,14 +124,13 @@
});
ctx.sidebar.addMenuItem({
commandId: OPEN_TRACE_COMMAND_ID,
- group: 'navigation',
+ section: 'navigation',
icon: 'folder_open',
});
- const OPEN_LEGACY_TRACE_COMMAND_ID =
- 'perfetto.CoreCommands#openTraceInLegacyUi';
+ const OPEN_LEGACY_COMMAND_ID = 'perfetto.CoreCommands#openTraceInLegacyUi';
ctx.commands.registerCommand({
- id: OPEN_LEGACY_TRACE_COMMAND_ID,
+ id: OPEN_LEGACY_COMMAND_ID,
name: 'Open with legacy UI',
callback: () => {
input.dataset['useCatapultLegacyUi'] = '1';
@@ -150,8 +138,8 @@
},
});
ctx.sidebar.addMenuItem({
- commandId: OPEN_LEGACY_TRACE_COMMAND_ID,
- group: 'navigation',
+ commandId: OPEN_LEGACY_COMMAND_ID,
+ section: 'navigation',
icon: 'filter_none',
});
}
@@ -336,21 +324,18 @@
return;
}
- globals.logging.logEvent('Trace Actions', 'Open trace from file');
- globals.dispatch(Actions.openTraceFromFile({file}));
+ AppImpl.instance.analytics.logEvent('Trace Actions', 'Open trace from file');
+ AppImpl.instance.openTraceFromFile(file);
}
async function openWithLegacyUi(file: File) {
// Switch back to the old catapult UI.
- globals.logging.logEvent('Trace Actions', 'Open trace in Legacy UI');
+ AppImpl.instance.analytics.logEvent(
+ 'Trace Actions',
+ 'Open trace in Legacy UI',
+ );
if (await isLegacyTrace(file)) {
- openFileWithLegacyTraceViewer(file);
- return;
+ return await openFileWithLegacyTraceViewer(file);
}
- openInOldUIWithSizeCheck(file);
+ return await openInOldUIWithSizeCheck(file);
}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'perfetto.CoreCommands',
- plugin: CoreCommandsPlugin,
-};
diff --git a/ui/src/core_plugins/counter/trace_processor_counter_track.ts b/ui/src/core_plugins/counter/trace_processor_counter_track.ts
deleted file mode 100644
index 2566470..0000000
--- a/ui/src/core_plugins/counter/trace_processor_counter_track.ts
+++ /dev/null
@@ -1,86 +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 {LONG, LONG_NULL, NUM} from '../../trace_processor/query_result';
-import {
- BaseCounterTrack,
- BaseCounterTrackArgs,
-} from '../../frontend/base_counter_track';
-import {TrackMouseEvent} from '../../public/track';
-
-interface TraceProcessorCounterTrackArgs extends BaseCounterTrackArgs {
- trackId: number;
- rootTable?: string;
-}
-
-export class TraceProcessorCounterTrack extends BaseCounterTrack {
- private trackId: number;
- private rootTable: string;
-
- constructor(args: TraceProcessorCounterTrackArgs) {
- super(args);
- this.trackId = args.trackId;
- this.rootTable = args.rootTable ?? 'counter';
- }
-
- getSqlSource() {
- return `
- select
- ts,
- value
- from ${this.rootTable}
- where track_id = ${this.trackId}
- `;
- }
-
- onMouseClick({x, timescale}: TrackMouseEvent): boolean {
- const time = timescale.pxToHpTime(x).toTime('floor');
-
- const query = `
- select
- id,
- ts as leftTs,
- (
- select ts
- from ${this.rootTable}
- where
- track_id = ${this.trackId}
- and ts >= ${time}
- order by ts
- limit 1
- ) as rightTs
- from ${this.rootTable}
- where
- track_id = ${this.trackId}
- and ts < ${time}
- order by ts DESC
- limit 1
- `;
-
- this.engine.query(query).then((result) => {
- const it = result.iter({
- id: NUM,
- leftTs: LONG,
- rightTs: LONG_NULL,
- });
- if (!it.valid()) {
- return;
- }
- const id = it.id;
- this.trace.selection.selectTrackEvent(this.uri, id);
- });
-
- return true;
- }
-}
diff --git a/ui/src/core_plugins/cpu_profile/index.ts b/ui/src/core_plugins/cpu_profile/index.ts
deleted file mode 100644
index c43150f..0000000
--- a/ui/src/core_plugins/cpu_profile/index.ts
+++ /dev/null
@@ -1,173 +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.
-
-import m from 'mithril';
-import {CPU_PROFILE_TRACK_KIND} from '../../public/track_kinds';
-import {Engine} from '../../trace_processor/engine';
-import {DetailsPanel} from '../../public/details_panel';
-import {Trace} from '../../public/trace';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
-import {NUM, NUM_NULL, STR_NULL} from '../../trace_processor/query_result';
-import {CpuProfileTrack} from './cpu_profile_track';
-import {getThreadUriPrefix} from '../../public/utils';
-import {exists} from '../../base/utils';
-import {Monitor} from '../../base/monitor';
-import {
- metricsFromTableOrSubquery,
- QueryFlamegraph,
- QueryFlamegraphAttrs,
-} from '../../core/query_flamegraph';
-import {Timestamp} from '../../frontend/widgets/timestamp';
-import {assertExists} from '../../base/logging';
-import {DetailsShell} from '../../widgets/details_shell';
-import {CpuProfileSampleSelection, Selection} from '../../public/selection';
-import {getOrCreateGroupForThread} from '../../public/standard_groups';
-import {TrackNode} from '../../public/workspace';
-
-class CpuProfile implements PerfettoPlugin {
- async onTraceLoad(ctx: Trace): Promise<void> {
- const result = await ctx.engine.query(`
- with thread_cpu_sample as (
- select distinct utid
- from cpu_profile_stack_sample
- where utid != 0
- )
- select
- utid,
- tid,
- upid,
- thread.name as threadName
- from thread_cpu_sample
- join thread using(utid)
- `);
-
- const it = result.iter({
- utid: NUM,
- upid: NUM_NULL,
- tid: NUM_NULL,
- threadName: STR_NULL,
- });
- for (; it.valid(); it.next()) {
- const utid = it.utid;
- const upid = it.upid;
- const threadName = it.threadName;
- const uri = `${getThreadUriPrefix(upid, utid)}_cpu_samples`;
- const title = `${threadName} (CPU Stack Samples)`;
- ctx.tracks.registerTrack({
- uri,
- title,
- tags: {
- kind: CPU_PROFILE_TRACK_KIND,
- utid,
- ...(exists(upid) && {upid}),
- },
- track: new CpuProfileTrack(
- {
- trace: ctx,
- uri,
- },
- utid,
- ),
- });
- const group = getOrCreateGroupForThread(ctx.workspace, utid);
- const track = new TrackNode({uri, title, sortOrder: -40});
- group.addChildInOrder(track);
- }
- ctx.tabs.registerDetailsPanel(
- new CpuProfileSampleFlamegraphDetailsPanel(ctx.engine),
- );
- }
-}
-
-class CpuProfileSampleFlamegraphDetailsPanel implements DetailsPanel {
- private sel?: CpuProfileSampleSelection;
- private selMonitor = new Monitor([() => this.sel?.ts, () => this.sel?.utid]);
- private flamegraphAttrs?: QueryFlamegraphAttrs;
-
- constructor(private engine: Engine) {}
-
- render(sel: Selection) {
- if (sel.kind !== 'legacy') {
- this.sel = undefined;
- return;
- }
-
- const legacySel = sel.legacySelection;
- if (legacySel.kind !== 'CPU_PROFILE_SAMPLE') {
- this.sel = undefined;
- return undefined;
- }
- const {ts, utid} = legacySel;
- this.sel = legacySel;
- if (this.selMonitor.ifStateChanged()) {
- this.flamegraphAttrs = {
- engine: this.engine,
- metrics: [
- ...metricsFromTableOrSubquery(
- `
- (
- select
- id,
- parent_id as parentId,
- name,
- mapping_name,
- source_file,
- cast(line_number AS text) as line_number,
- self_count
- from _callstacks_for_cpu_profile_stack_samples!((
- select p.callsite_id
- from cpu_profile_stack_sample p
- where p.ts = ${ts} and p.utid = ${utid}
- ))
- )
- `,
- [
- {
- name: 'CPU Profile Samples',
- unit: '',
- columnName: 'self_count',
- },
- ],
- 'include perfetto module callstacks.stack_profile',
- [{name: 'mapping_name', displayName: 'Mapping'}],
- [
- {name: 'source_file', displayName: 'Source File'},
- {name: 'line_number', displayName: 'Line Number'},
- ],
- ),
- ],
- };
- }
- return m(
- '.flamegraph-profile',
- m(
- DetailsShell,
- {
- fillParent: true,
- title: m('.title', 'CPU Profile Samples'),
- description: [],
- buttons: [
- m('div.time', `Timestamp: `, m(Timestamp, {ts: this.sel.ts})),
- ],
- },
- m(QueryFlamegraph, assertExists(this.flamegraphAttrs)),
- ),
- );
- }
-}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'perfetto.CpuProfile',
- plugin: CpuProfile,
-};
diff --git a/ui/src/core_plugins/debug/index.ts b/ui/src/core_plugins/debug/index.ts
deleted file mode 100644
index 3a43977..0000000
--- a/ui/src/core_plugins/debug/index.ts
+++ /dev/null
@@ -1,110 +0,0 @@
-// 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 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 {uuidv4} from '../../base/uuid';
-import {
- addDebugCounterTrack,
- addDebugSliceTrack,
-} from '../../public/lib/debug_tracks/debug_tracks';
-import {BottomTabToSCSAdapter} from '../../public/utils';
-import {Trace} from '../../public/trace';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
-import {DebugSliceDetailsTab} from '../../public/lib/debug_tracks/details_tab';
-import {GenericSliceDetailsTabConfig} from '../../frontend/generic_slice_details_tab';
-import {Optional, exists} from '../../base/utils';
-
-class DebugTracksPlugin implements PerfettoPlugin {
- async onTraceLoad(ctx: Trace): Promise<void> {
- ctx.commands.registerCommand({
- id: 'perfetto.DebugTracks#addDebugSliceTrack',
- name: 'Add debug slice track',
- callback: async (arg: unknown) => {
- // This command takes a query and creates a debug track out of it The
- // query can be passed in using the first arg, or if this is not defined
- // or is the wrong type, we prompt the user for it.
- const query = await getStringFromArgOrPrompt(ctx, arg);
- if (exists(query)) {
- await addDebugSliceTrack(
- ctx,
- {
- sqlSource: query,
- },
- 'Debug slice track',
- {ts: 'ts', dur: 'dur', name: 'name'},
- [],
- );
- }
- },
- });
-
- ctx.commands.registerCommand({
- id: 'perfetto.DebugTracks#addDebugCounterTrack',
- name: 'Add debug counter track',
- callback: async (arg: unknown) => {
- const query = await getStringFromArgOrPrompt(ctx, arg);
- if (exists(query)) {
- await addDebugCounterTrack(
- ctx,
- {
- sqlSource: query,
- },
- 'Debug slice track',
- {ts: 'ts', value: 'value'},
- );
- }
- },
- });
-
- // TODO(stevegolton): While debug tracks are in their current state, we rely
- // on this plugin to provide the details panel for them. In the future, this
- // details panel will become part of the debug track's definition.
- ctx.tabs.registerDetailsPanel(
- new BottomTabToSCSAdapter({
- tabFactory: (selection) => {
- if (
- selection.kind === 'GENERIC_SLICE' &&
- selection.detailsPanelConfig.kind === DebugSliceDetailsTab.kind
- ) {
- const config = selection.detailsPanelConfig.config;
- return new DebugSliceDetailsTab({
- config: config as GenericSliceDetailsTabConfig,
- trace: ctx,
- uuid: uuidv4(),
- });
- }
- return undefined;
- },
- }),
- );
- }
-}
-
-// If arg is a string, return it, otherwise prompt the user for a string. An
-// exception is thrown if the prompt is cancelled, so this function handles this
-// and returns undefined in this case.
-async function getStringFromArgOrPrompt(
- ctx: Trace,
- arg: unknown,
-): Promise<Optional<string>> {
- if (typeof arg === 'string') {
- return arg;
- } else {
- return await ctx.omnibox.prompt('Enter a query...');
- }
-}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'perfetto.DebugTracks',
- plugin: DebugTracksPlugin,
-};
diff --git a/ui/src/core_plugins/example_traces/index.ts b/ui/src/core_plugins/example_traces/index.ts
index 8a2ed7d..4b69ec9 100644
--- a/ui/src/core_plugins/example_traces/index.ts
+++ b/ui/src/core_plugins/example_traces/index.ts
@@ -12,10 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {Actions} from '../../common/actions';
-import {globals} from '../../frontend/globals';
+import {AppImpl} from '../../core/app_impl';
import {App} from '../../public/app';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
+import {PerfettoPlugin} from '../../public/plugin';
const EXAMPLE_ANDROID_TRACE_URL =
'https://storage.googleapis.com/perfetto-misc/example_android_trace_15s';
@@ -23,25 +22,26 @@
const EXAMPLE_CHROME_TRACE_URL =
'https://storage.googleapis.com/perfetto-misc/chrome_example_wikipedia.perfetto_trace.gz';
-function openTraceUrl(url: string): void {
- globals.logging.logEvent('Trace Actions', 'Open example trace');
- globals.dispatch(Actions.openTraceFromUrl({url}));
+function openTraceUrl(app: App, url: string): void {
+ app.analytics.logEvent('Trace Actions', 'Open example trace');
+ AppImpl.instance.openTraceFromUrl(url);
}
-class ExampleTracesPlugin implements PerfettoPlugin {
- onActivate(ctx: App) {
+export default class implements PerfettoPlugin {
+ static readonly id = 'perfetto.ExampleTraces';
+ static onActivate(ctx: App) {
const OPEN_EXAMPLE_ANDROID_TRACE_COMMAND_ID =
'perfetto.CoreCommands#openExampleAndroidTrace';
ctx.commands.registerCommand({
id: OPEN_EXAMPLE_ANDROID_TRACE_COMMAND_ID,
name: 'Open Android example',
callback: () => {
- openTraceUrl(EXAMPLE_ANDROID_TRACE_URL);
+ openTraceUrl(ctx, EXAMPLE_ANDROID_TRACE_URL);
},
});
ctx.sidebar.addMenuItem({
+ section: 'example_traces',
commandId: OPEN_EXAMPLE_ANDROID_TRACE_COMMAND_ID,
- group: 'example_traces',
icon: 'description',
});
@@ -51,18 +51,13 @@
id: OPEN_EXAMPLE_CHROME_TRACE_COMMAND_ID,
name: 'Open Chrome example',
callback: () => {
- openTraceUrl(EXAMPLE_CHROME_TRACE_URL);
+ openTraceUrl(ctx, EXAMPLE_CHROME_TRACE_URL);
},
});
ctx.sidebar.addMenuItem({
+ section: 'example_traces',
commandId: OPEN_EXAMPLE_CHROME_TRACE_COMMAND_ID,
- group: 'example_traces',
icon: 'description',
});
}
}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'perfetto.ExampleTraces',
- plugin: ExampleTracesPlugin,
-};
diff --git a/ui/src/frontend/flags_page.ts b/ui/src/core_plugins/flags_page/flags_page.ts
similarity index 92%
rename from ui/src/frontend/flags_page.ts
rename to ui/src/core_plugins/flags_page/flags_page.ts
index 3a77789..d8cfdf6 100644
--- a/ui/src/frontend/flags_page.ts
+++ b/ui/src/core_plugins/flags_page/flags_page.ts
@@ -13,11 +13,12 @@
// limitations under the License.
import m from 'mithril';
-import {channelChanged, getNextChannel, setChannel} from '../common/channels';
-import {featureFlags, Flag, OverrideState} from '../core/feature_flags';
-import {raf} from '../core/raf_scheduler';
-import {PageAttrs} from './pages';
-import {Router} from './router';
+import {channelChanged, getNextChannel, setChannel} from '../../core/channels';
+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';
const RELEASE_PROCESS_URL =
'https://perfetto.dev/docs/visualization/perfetto-ui-release-process';
diff --git a/ui/src/core_plugins/flags_page/index.ts b/ui/src/core_plugins/flags_page/index.ts
new file mode 100644
index 0000000..85039c4
--- /dev/null
+++ b/ui/src/core_plugins/flags_page/index.ts
@@ -0,0 +1,62 @@
+// 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 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 {featureFlags} from '../../core/feature_flags';
+import {App} from '../../public/app';
+import {PerfettoPlugin} from '../../public/plugin';
+import {FlagsPage} from './flags_page';
+import {PluginsPage} from './plugins_page';
+
+export default class implements PerfettoPlugin {
+ static readonly id = 'dev.perfetto.FlagsPage';
+
+ static onActivate(app: App) {
+ // Flags page
+ app.pages.registerPage({
+ route: '/flags',
+ page: FlagsPage,
+ traceless: true,
+ });
+ app.sidebar.addMenuItem({
+ section: 'support',
+ sortOrder: 3,
+ text: 'Flags',
+ href: '#!/flags',
+ icon: 'emoji_flags',
+ });
+
+ // Plugins page.
+ app.pages.registerPage({
+ route: '/plugins',
+ page: PluginsPage,
+ traceless: true,
+ });
+
+ const PLUGINS_PAGE_IN_NAV_FLAG = featureFlags.register({
+ id: 'showPluginsPageInNav',
+ name: 'Show plugins page',
+ description: 'Show a link to the plugins page in the side bar.',
+ defaultValue: false,
+ });
+ if (PLUGINS_PAGE_IN_NAV_FLAG.get()) {
+ app.sidebar.addMenuItem({
+ section: 'support',
+ text: 'Plugins',
+ href: '#!/plugins',
+ icon: 'extension',
+ sortOrder: 9,
+ });
+ }
+ }
+}
diff --git a/ui/src/core_plugins/flags_page/plugins_page.ts b/ui/src/core_plugins/flags_page/plugins_page.ts
new file mode 100644
index 0000000..ed5bd0b
--- /dev/null
+++ b/ui/src/core_plugins/flags_page/plugins_page.ts
@@ -0,0 +1,125 @@
+// 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 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 {Button} from '../../widgets/button';
+import {exists} from '../../base/utils';
+import {defaultPlugins} from '../../core/default_plugins';
+import {Intent} from '../../widgets/common';
+import {PageAttrs} from '../../public/page';
+import {AppImpl} from '../../core/app_impl';
+import {PluginWrapper} from '../../core/plugin_manager';
+import {raf} from '../../core/raf_scheduler';
+
+// This flag indicated whether we need to restart the UI to apply plugin
+// changes. It is purposely a global as we want it to outlive the Mithril
+// component, and it'll be reset we restart anyway.
+let needsRestart = false;
+
+export class PluginsPage implements m.ClassComponent<PageAttrs> {
+ view() {
+ const pluginManager = AppImpl.instance.plugins;
+ const registeredPlugins = pluginManager.getAllPlugins();
+ return m(
+ '.pf-plugins-page',
+ m('h1', 'Plugins'),
+ needsRestart &&
+ m(
+ 'h3.restart_needed',
+ 'Some plugins have been disabled. ' +
+ 'Please reload your page to apply the changes.',
+ ),
+ m(
+ '.pf-plugins-topbar',
+ m(Button, {
+ intent: Intent.Primary,
+ label: 'Disable All',
+ onclick: async () => {
+ for (const plugin of registeredPlugins) {
+ plugin.enableFlag.set(false);
+ }
+ needsRestart = true;
+ raf.scheduleFullRedraw();
+ },
+ }),
+ m(Button, {
+ intent: Intent.Primary,
+ label: 'Enable All',
+ onclick: async () => {
+ for (const plugin of registeredPlugins) {
+ plugin.enableFlag.set(true);
+ }
+ needsRestart = true;
+ raf.scheduleFullRedraw();
+ },
+ }),
+ m(Button, {
+ intent: Intent.Primary,
+ label: 'Restore Defaults',
+ onclick: async () => {
+ for (const plugin of registeredPlugins) {
+ plugin.enableFlag.reset();
+ }
+ needsRestart = true;
+ raf.scheduleFullRedraw();
+ },
+ }),
+ ),
+ m(
+ '.pf-plugins-grid',
+ m('span', 'Plugin'),
+ m('span', 'Default?'),
+ m('span', 'Enabled?'),
+ m('span', 'Active?'),
+ m('span', 'Control'),
+ m('span', 'Load Time'),
+ registeredPlugins.map((plugin) => this.renderPluginRow(plugin)),
+ ),
+ );
+ }
+
+ private renderPluginRow(plugin: PluginWrapper): m.Children {
+ const pluginId = plugin.desc.id;
+ const isDefault = defaultPlugins.includes(pluginId);
+ const isActive = plugin.active;
+ const isEnabled = plugin.enableFlag.get();
+ const loadTime = plugin.traceContext?.loadTimeMs;
+ return [
+ m('span', pluginId),
+ m('span', isDefault ? 'Yes' : 'No'),
+ isEnabled
+ ? m('.pf-tag.pf-active', 'Enabled')
+ : m('.pf-tag.pf-inactive', 'Disabled'),
+ isActive
+ ? m('.pf-tag.pf-active', 'Active')
+ : m('.pf-tag.pf-inactive', 'Inactive'),
+ m(Button, {
+ label: isEnabled ? 'Disable' : 'Enable',
+ intent: Intent.Primary,
+ onclick: () => {
+ if (isEnabled) {
+ plugin.enableFlag.set(false);
+ } else {
+ plugin.enableFlag.set(true);
+ }
+ needsRestart = true;
+ raf.scheduleFullRedraw();
+ },
+ }),
+ exists(loadTime)
+ ? m('span', `${loadTime.toFixed(1)} ms`)
+ : m('span', `-`),
+ ];
+ }
+}
diff --git a/ui/src/core_plugins/global_groups/index.ts b/ui/src/core_plugins/global_groups/index.ts
new file mode 100644
index 0000000..6c9243d
--- /dev/null
+++ b/ui/src/core_plugins/global_groups/index.ts
@@ -0,0 +1,251 @@
+// 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 {PerfettoPlugin} from '../../public/plugin';
+import {Trace} from '../../public/trace';
+import {TrackNode} from '../../public/workspace';
+
+const MEM_DMA_COUNTER_NAME = 'mem.dma_heap';
+const MEM_DMA = 'mem.dma_buffer';
+const MEM_ION = 'mem.ion';
+const F2FS_IOSTAT_TAG = 'f2fs_iostat.';
+const F2FS_IOSTAT_GROUP_NAME = 'f2fs_iostat';
+const F2FS_IOSTAT_LAT_TAG = 'f2fs_iostat_latency.';
+const F2FS_IOSTAT_LAT_GROUP_NAME = 'f2fs_iostat_latency';
+const DISK_IOSTAT_TAG = 'diskstat.';
+const DISK_IOSTAT_GROUP_NAME = 'diskstat';
+const BUDDY_INFO_TAG = 'mem.buddyinfo';
+const UFS_CMD_TAG_REGEX = new RegExp('^io.ufs.command.tag.*$');
+const UFS_CMD_TAG_GROUP = 'io.ufs.command.tags';
+// NB: Userspace wakelocks start with "WakeLock" not "Wakelock".
+const KERNEL_WAKELOCK_REGEX = new RegExp('^Wakelock.*$');
+const KERNEL_WAKELOCK_GROUP = 'Kernel wakelocks';
+const NETWORK_TRACK_REGEX = new RegExp('^.* (Received|Transmitted)( KB)?$');
+const NETWORK_TRACK_GROUP = 'Networking';
+const ENTITY_RESIDENCY_REGEX = new RegExp('^Entity residency:');
+const ENTITY_RESIDENCY_GROUP = 'Entity residency';
+const UCLAMP_REGEX = new RegExp('^UCLAMP_');
+const UCLAMP_GROUP = 'Scheduler Utilization Clamping';
+const POWER_RAILS_GROUP = 'Power Rails';
+const POWER_RAILS_REGEX = new RegExp('^power.');
+const FREQUENCY_GROUP = 'Frequency Scaling';
+const TEMPERATURE_REGEX = new RegExp('^.* Temperature$');
+const TEMPERATURE_GROUP = 'Temperature';
+const IRQ_GROUP = 'IRQs';
+const IRQ_REGEX = new RegExp('^(Irq|SoftIrq) Cpu.*');
+const CHROME_TRACK_REGEX = new RegExp('^Chrome.*|^InputLatency::.*');
+const CHROME_TRACK_GROUP = 'Chrome Global Tracks';
+const MISC_GROUP = 'Misc Global Tracks';
+
+// This plugin is responsible for organizing all the global tracks.
+export default class implements PerfettoPlugin {
+ static readonly id = 'perfetto.GlobalGroups';
+ async onTraceLoad(trace: Trace): Promise<void> {
+ trace.addEventListener('traceready', () => {
+ groupGlobalIonTracks(trace);
+ groupGlobalIostatTracks(trace, F2FS_IOSTAT_TAG, F2FS_IOSTAT_GROUP_NAME);
+ groupGlobalIostatTracks(
+ trace,
+ F2FS_IOSTAT_LAT_TAG,
+ F2FS_IOSTAT_LAT_GROUP_NAME,
+ );
+ groupGlobalIostatTracks(trace, DISK_IOSTAT_TAG, DISK_IOSTAT_GROUP_NAME);
+ groupTracksByRegex(trace, UFS_CMD_TAG_REGEX, UFS_CMD_TAG_GROUP);
+ groupGlobalBuddyInfoTracks(trace);
+ groupTracksByRegex(trace, KERNEL_WAKELOCK_REGEX, KERNEL_WAKELOCK_GROUP);
+ groupTracksByRegex(trace, NETWORK_TRACK_REGEX, NETWORK_TRACK_GROUP);
+ groupTracksByRegex(trace, ENTITY_RESIDENCY_REGEX, ENTITY_RESIDENCY_GROUP);
+ groupTracksByRegex(trace, UCLAMP_REGEX, UCLAMP_GROUP);
+ groupFrequencyTracks(trace, FREQUENCY_GROUP);
+ groupTracksByRegex(trace, POWER_RAILS_REGEX, POWER_RAILS_GROUP);
+ groupTracksByRegex(trace, TEMPERATURE_REGEX, TEMPERATURE_GROUP);
+ groupTracksByRegex(trace, IRQ_REGEX, IRQ_GROUP);
+ groupTracksByRegex(trace, CHROME_TRACK_REGEX, CHROME_TRACK_GROUP);
+ groupMiscNonAllowlistedTracks(trace, MISC_GROUP);
+
+ // Move groups underneath tracks
+ Array.from(trace.workspace.children)
+ .sort((a, b) => {
+ // Get the index in the order array
+ const indexA = a.hasChildren ? 1 : 0;
+ const indexB = b.hasChildren ? 1 : 0;
+ return indexA - indexB;
+ })
+ .forEach((n) => trace.workspace.addChildLast(n));
+
+ // If there is only one group, expand it
+ const rootLevelChildren = trace.workspace.children;
+ if (rootLevelChildren.length === 1 && rootLevelChildren[0].hasChildren) {
+ rootLevelChildren[0].expand();
+ }
+ });
+ }
+}
+
+function groupGlobalIonTracks(trace: Trace): void {
+ const ionTracks: TrackNode[] = [];
+ let hasSummary = false;
+
+ for (const track of trace.workspace.children) {
+ if (track.hasChildren) continue;
+
+ const isIon = track.title.startsWith(MEM_ION);
+ const isIonCounter = track.title === MEM_ION;
+ const isDmaHeapCounter = track.title === MEM_DMA_COUNTER_NAME;
+ const isDmaBuffferSlices = track.title === MEM_DMA;
+ if (isIon || isIonCounter || isDmaHeapCounter || isDmaBuffferSlices) {
+ ionTracks.push(track);
+ }
+ hasSummary = hasSummary || isIonCounter;
+ hasSummary = hasSummary || isDmaHeapCounter;
+ }
+
+ if (ionTracks.length === 0 || !hasSummary) {
+ return;
+ }
+
+ const group = new TrackNode({title: 'Ion Tracks'});
+ group.isSummary = true;
+ trace.workspace.addChildInOrder(group);
+
+ for (const track of ionTracks) {
+ if ([MEM_DMA_COUNTER_NAME, MEM_ION].includes(track.title)) {
+ trace.workspace.removeChild(track);
+ group.uri = track.uri;
+ group.title = track.title;
+ } else {
+ group.addChildInOrder(track);
+ }
+ }
+}
+
+function groupGlobalIostatTracks(
+ trace: Trace,
+ tag: string,
+ groupName: string,
+): void {
+ const devMap = new Map<string, TrackNode>();
+
+ for (const track of trace.workspace.children) {
+ if (track.hasChildren) continue;
+ if (track.title.startsWith(tag)) {
+ const name = track.title.split('.', 3);
+ const key = name[1];
+
+ let parentGroup = devMap.get(key);
+ if (!parentGroup) {
+ const group = new TrackNode({title: groupName, isSummary: true});
+ trace.workspace.addChildInOrder(group);
+ devMap.set(key, group);
+ parentGroup = group;
+ }
+
+ track.title = name[2];
+ parentGroup.addChildInOrder(track);
+ }
+ }
+}
+
+function groupGlobalBuddyInfoTracks(trace: Trace): void {
+ const devMap = new Map<string, TrackNode>();
+
+ for (const track of trace.workspace.children) {
+ if (track.hasChildren) continue;
+ if (track.title.startsWith(BUDDY_INFO_TAG)) {
+ const tokens = track.title.split('[');
+ const node = tokens[1].slice(0, -1);
+ const zone = tokens[2].slice(0, -1);
+ const size = tokens[3].slice(0, -1);
+
+ const groupName = 'Buddyinfo: Node: ' + node + ' Zone: ' + zone;
+ if (!devMap.has(groupName)) {
+ const group = new TrackNode({title: groupName, isSummary: true});
+ devMap.set(groupName, group);
+ trace.workspace.addChildInOrder(group);
+ }
+ track.title = 'Chunk size: ' + size;
+ const group = devMap.get(groupName)!;
+ group.addChildInOrder(track);
+ }
+ }
+}
+
+function groupFrequencyTracks(trace: Trace, groupName: string): void {
+ const group = new TrackNode({title: groupName, isSummary: true});
+
+ for (const track of trace.workspace.children) {
+ if (track.hasChildren) continue;
+ // Group all the frequency tracks together (except the CPU and GPU
+ // frequency ones).
+ if (
+ track.title.endsWith('Frequency') &&
+ !track.title.startsWith('Cpu') &&
+ !track.title.startsWith('Gpu')
+ ) {
+ group.addChildInOrder(track);
+ }
+ }
+
+ if (group.children.length > 0) {
+ trace.workspace.addChildInOrder(group);
+ }
+}
+
+function groupMiscNonAllowlistedTracks(trace: Trace, groupName: string): void {
+ // List of allowlisted track names.
+ const ALLOWLIST_REGEXES = [
+ new RegExp('^Cpu .*$', 'i'),
+ new RegExp('^Gpu .*$', 'i'),
+ new RegExp('^Trace Triggers$'),
+ new RegExp('^Android App Startups$'),
+ new RegExp('^Device State.*$'),
+ new RegExp('^Android logs$'),
+ ];
+
+ const group = new TrackNode({title: groupName, isSummary: true});
+ for (const track of trace.workspace.children) {
+ if (track.hasChildren) continue;
+ let allowlisted = false;
+ for (const regex of ALLOWLIST_REGEXES) {
+ allowlisted = allowlisted || regex.test(track.title);
+ }
+ if (allowlisted) {
+ continue;
+ }
+ group.addChildInOrder(track);
+ }
+
+ if (group.children.length > 0) {
+ trace.workspace.addChildInOrder(group);
+ }
+}
+
+function groupTracksByRegex(
+ trace: Trace,
+ regex: RegExp,
+ groupName: string,
+): void {
+ const group = new TrackNode({title: groupName, isSummary: true});
+
+ for (const track of trace.workspace.children) {
+ if (track.hasChildren) continue;
+ if (regex.test(track.title)) {
+ group.addChildInOrder(track);
+ }
+ }
+
+ if (group.children.length > 0) {
+ trace.workspace.addChildInOrder(group);
+ }
+}
diff --git a/ui/src/core_plugins/heap_profile/heap_profile_track.ts b/ui/src/core_plugins/heap_profile/heap_profile_track.ts
deleted file mode 100644
index c2d83a3..0000000
--- a/ui/src/core_plugins/heap_profile/heap_profile_track.ts
+++ /dev/null
@@ -1,127 +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 {
- BASE_ROW,
- BaseSliceTrack,
- OnSliceClickArgs,
- OnSliceOverArgs,
-} from '../../frontend/base_slice_track';
-import {globals} from '../../frontend/globals';
-import {NewTrackArgs} from '../../frontend/track';
-import {
- LegacySelection,
- ProfileType,
- profileType,
-} from '../../public/selection';
-import {Slice} from '../../public/track';
-import {STR} from '../../trace_processor/query_result';
-import {createPerfettoTable} from '../../trace_processor/sql_utils';
-
-const HEAP_PROFILE_ROW = {
- ...BASE_ROW,
- type: STR,
-};
-type HeapProfileRow = typeof HEAP_PROFILE_ROW;
-interface HeapProfileSlice extends Slice {
- type: ProfileType;
-}
-
-export class HeapProfileTrack extends BaseSliceTrack<
- HeapProfileSlice,
- HeapProfileRow
-> {
- private upid: number;
-
- constructor(args: NewTrackArgs, upid: number) {
- super(args);
- this.upid = upid;
- }
-
- async onInit() {
- return createPerfettoTable(
- this.engine,
- `_heap_profile_track_${this.trackUuid}`,
- `
- with
- heaps as (select group_concat(distinct heap_name) h from heap_profile_allocation where upid = ${this.upid}),
- allocation_tses as (select distinct ts from heap_profile_allocation where upid = ${this.upid}),
- graph_tses as (select distinct graph_sample_ts from heap_graph_object where upid = ${this.upid})
- select
- *,
- 0 AS dur,
- 0 AS depth
- from (
- select
- (
- select a.id
- from heap_profile_allocation a
- where a.ts = t.ts
- order by a.id
- limit 1
- ) as id,
- ts,
- 'heap_profile:' || (select h from heaps) AS type
- from allocation_tses t
- union all
- select
- (
- select o.id
- from heap_graph_object o
- where o.graph_sample_ts = g.graph_sample_ts
- order by o.id
- limit 1
- ) as id,
- graph_sample_ts AS ts,
- 'graph' AS type
- from graph_tses g
- )
- `,
- );
- }
-
- getSqlSource(): string {
- return `_heap_profile_track_${this.trackUuid}`;
- }
-
- getRowSpec(): HeapProfileRow {
- return HEAP_PROFILE_ROW;
- }
-
- rowToSlice(row: HeapProfileRow): HeapProfileSlice {
- const slice = this.rowToSliceBase(row);
- return {
- ...slice,
- type: profileType(row.type),
- };
- }
-
- onSliceOver(args: OnSliceOverArgs<HeapProfileSlice>) {
- args.tooltip = [args.slice.type];
- }
-
- onSliceClick(args: OnSliceClickArgs<HeapProfileSlice>) {
- globals.selectionManager.selectLegacy({
- kind: 'HEAP_PROFILE',
- id: args.slice.id,
- upid: this.upid,
- ts: args.slice.ts,
- type: args.slice.type,
- });
- }
-
- protected isSelectionHandled(selection: LegacySelection): boolean {
- return selection.kind === 'HEAP_PROFILE';
- }
-}
diff --git a/ui/src/core_plugins/heap_profile/index.ts b/ui/src/core_plugins/heap_profile/index.ts
deleted file mode 100644
index bf55a11..0000000
--- a/ui/src/core_plugins/heap_profile/index.ts
+++ /dev/null
@@ -1,468 +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.
-
-import m from 'mithril';
-import {assertExists, assertFalse} from '../../base/logging';
-import {Monitor} from '../../base/monitor';
-import {profileType, ProfileType, Selection} from '../../public/selection';
-import {HeapProfileSelection} from '../../public/selection';
-import {Timestamp} from '../../frontend/widgets/timestamp';
-import {Engine} from '../../trace_processor/engine';
-import {HEAP_PROFILE_TRACK_KIND} from '../../public/track_kinds';
-import {DetailsPanel} from '../../public/details_panel';
-import {Trace} from '../../public/trace';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
-import {LONG, NUM, STR} from '../../trace_processor/query_result';
-import {DetailsShell} from '../../widgets/details_shell';
-import {HeapProfileTrack} from './heap_profile_track';
-import {
- QueryFlamegraph,
- QueryFlamegraphAttrs,
- metricsFromTableOrSubquery,
-} from '../../core/query_flamegraph';
-import {Time, time} from '../../base/time';
-import {Popup} from '../../widgets/popup';
-import {Icon} from '../../widgets/icon';
-import {Button} from '../../widgets/button';
-import {Intent} from '../../widgets/common';
-import {getCurrentTrace} from '../../frontend/sidebar';
-import {convertTraceToPprofAndDownload} from '../../frontend/trace_converter';
-import {raf} from '../../core/raf_scheduler';
-import {globals} from '../../frontend/globals';
-import {Modal} from '../../widgets/modal';
-import {Router} from '../../frontend/router';
-import {Actions} from '../../common/actions';
-import {getOrCreateGroupForProcess} from '../../public/standard_groups';
-import {TrackNode} from '../../public/workspace';
-
-class HeapProfilePlugin implements PerfettoPlugin {
- async onTraceLoad(ctx: Trace): Promise<void> {
- const result = await ctx.engine.query(`
- select distinct upid from heap_profile_allocation
- union
- select distinct upid from heap_graph_object
- `);
- for (const it = result.iter({upid: NUM}); it.valid(); it.next()) {
- const upid = it.upid;
- const uri = `/process_${upid}/heap_profile`;
- const title = 'Heap Profile';
- ctx.tracks.registerTrack({
- uri,
- title,
- tags: {
- kind: HEAP_PROFILE_TRACK_KIND,
- upid,
- },
- track: new HeapProfileTrack(
- {
- trace: ctx,
- uri,
- },
- upid,
- ),
- });
- const group = getOrCreateGroupForProcess(ctx.workspace, upid);
- const track = new TrackNode({uri, title, sortOrder: -30});
- group.addChildInOrder(track);
- }
- const it = await ctx.engine.query(`
- select value from stats
- where name = 'heap_graph_non_finalized_graph'
- `);
- const incomplete = it.firstRow({value: NUM}).value > 0;
- ctx.tabs.registerDetailsPanel(
- new HeapProfileFlamegraphDetailsPanel(ctx.engine, incomplete),
- );
-
- await selectFirstHeapProfile(ctx);
- }
-}
-
-async function selectFirstHeapProfile(ctx: Trace) {
- const query = `
- select * from (
- select
- min(ts) AS ts,
- 'heap_profile:' || group_concat(distinct heap_name) AS type,
- upid
- from heap_profile_allocation
- group by upid
- union
- select distinct graph_sample_ts as ts, 'graph' as type, upid
- from heap_graph_object
- )
- order by ts
- limit 1
- `;
- const profile = await assertExists(ctx.engine).query(query);
- if (profile.numRows() !== 1) return;
- const row = profile.firstRow({ts: LONG, type: STR, upid: NUM});
- const ts = Time.fromRaw(row.ts);
- const upid = row.upid;
- ctx.selection.selectLegacy({
- kind: 'HEAP_PROFILE',
- id: 0,
- upid,
- ts,
- type: profileType(row.type),
- });
-}
-
-class HeapProfileFlamegraphDetailsPanel implements DetailsPanel {
- private sel?: HeapProfileSelection;
- private selMonitor = new Monitor([
- () => this.sel?.ts,
- () => this.sel?.upid,
- () => this.sel?.type,
- ]);
- private flamegraphAttrs?: QueryFlamegraphAttrs;
-
- constructor(
- private engine: Engine,
- private heapGraphIncomplete: boolean,
- ) {}
-
- render(sel: Selection) {
- if (sel.kind !== 'legacy') {
- this.sel = undefined;
- return;
- }
-
- const legacySel = sel.legacySelection;
- if (legacySel.kind !== 'HEAP_PROFILE') {
- this.sel = undefined;
- return undefined;
- }
-
- const {ts, upid, type} = legacySel;
- this.sel = legacySel;
- if (this.selMonitor.ifStateChanged()) {
- this.flamegraphAttrs = flamegraphAttrs(this.engine, ts, upid, type);
- }
- return m(
- '.flamegraph-profile',
- maybeShowModal(type, this.heapGraphIncomplete),
- m(
- DetailsShell,
- {
- fillParent: true,
- title: m(
- '.title',
- getFlamegraphTitle(type),
- legacySel.type === ProfileType.MIXED_HEAP_PROFILE &&
- m(
- Popup,
- {
- trigger: m(Icon, {icon: 'warning'}),
- },
- m(
- '',
- {style: {width: '300px'}},
- 'This is a mixed java/native heap profile, free()s are not visualized. To visualize free()s, remove "all_heaps: true" from the config.',
- ),
- ),
- ),
- description: [],
- buttons: [
- m('.time', `Snapshot time: `, m(Timestamp, {ts})),
- (legacySel.type === ProfileType.NATIVE_HEAP_PROFILE ||
- legacySel.type === ProfileType.JAVA_HEAP_SAMPLES) &&
- m(Button, {
- icon: 'file_download',
- intent: Intent.Primary,
- onclick: () => {
- downloadPprof(this.engine, upid, ts);
- raf.scheduleFullRedraw();
- },
- }),
- ],
- },
- m(QueryFlamegraph, assertExists(this.flamegraphAttrs)),
- ),
- );
- }
-}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'perfetto.HeapProfile',
- plugin: HeapProfilePlugin,
-};
-
-function flamegraphAttrs(
- engine: Engine,
- ts: time,
- upid: number,
- type: ProfileType,
-): QueryFlamegraphAttrs {
- switch (type) {
- case ProfileType.NATIVE_HEAP_PROFILE:
- return flamegraphAttrsForHeapProfile(engine, ts, upid, [
- {
- name: 'Unreleased Malloc Size',
- unit: 'B',
- columnName: 'self_size',
- },
- {
- name: 'Unreleased Malloc Count',
- unit: '',
- columnName: 'self_count',
- },
- {
- name: 'Total Malloc Size',
- unit: 'B',
- columnName: 'self_alloc_size',
- },
- {
- name: 'Total Malloc Count',
- unit: '',
- columnName: 'self_alloc_count',
- },
- ]);
- case ProfileType.HEAP_PROFILE:
- return flamegraphAttrsForHeapProfile(engine, ts, upid, [
- {
- name: 'Unreleased Size',
- unit: 'B',
- columnName: 'self_size',
- },
- {
- name: 'Unreleased Count',
- unit: '',
- columnName: 'self_count',
- },
- {
- name: 'Total Size',
- unit: 'B',
- columnName: 'self_alloc_size',
- },
- {
- name: 'Total Count',
- unit: '',
- columnName: 'self_alloc_count',
- },
- ]);
- case ProfileType.JAVA_HEAP_SAMPLES:
- return flamegraphAttrsForHeapProfile(engine, ts, upid, [
- {
- name: 'Unreleased Allocation Size',
- unit: 'B',
- columnName: 'self_size',
- },
- {
- name: 'Unreleased Allocation Count',
- unit: '',
- columnName: 'self_count',
- },
- ]);
- case ProfileType.MIXED_HEAP_PROFILE:
- return flamegraphAttrsForHeapProfile(engine, ts, upid, [
- {
- name: 'Unreleased Allocation Size (malloc + java)',
- unit: 'B',
- columnName: 'self_size',
- },
- {
- name: 'Unreleased Allocation Count (malloc + java)',
- unit: '',
- columnName: 'self_count',
- },
- ]);
- case ProfileType.JAVA_HEAP_GRAPH:
- return flamegraphAttrsForHeapGraph(engine, ts, upid);
- case ProfileType.PERF_SAMPLE:
- assertFalse(false, 'Perf sample not supported');
- return {engine, metrics: []};
- }
-}
-
-function flamegraphAttrsForHeapProfile(
- engine: Engine,
- ts: time,
- upid: number,
- metrics: {name: string; unit: string; columnName: string}[],
-) {
- return {
- engine,
- metrics: [
- ...metricsFromTableOrSubquery(
- `
- (
- select
- id,
- parent_id as parentId,
- name,
- mapping_name,
- source_file,
- cast(line_number AS text) as line_number,
- self_size,
- self_count,
- self_alloc_size,
- self_alloc_count
- from _android_heap_profile_callstacks_for_allocations!((
- select
- callsite_id,
- size,
- count,
- max(size, 0) as alloc_size,
- max(count, 0) as alloc_count
- from heap_profile_allocation a
- where a.ts <= ${ts} and a.upid = ${upid}
- ))
- )
- `,
- metrics,
- 'include perfetto module android.memory.heap_profile.callstacks',
- [{name: 'mapping_name', displayName: 'Mapping'}],
- [
- {name: 'source_file', displayName: 'Source File'},
- {name: 'line_number', displayName: 'Line Number'},
- ],
- ),
- ],
- };
-}
-
-function flamegraphAttrsForHeapGraph(engine: Engine, ts: time, upid: number) {
- return {
- engine,
- metrics: [
- ...metricsFromTableOrSubquery(
- `
- (
- select
- id,
- parent_id as parentId,
- ifnull(name, '[Unknown]') as name,
- root_type,
- self_size,
- self_count
- from _heap_graph_class_tree
- where graph_sample_ts = ${ts} and upid = ${upid}
- )
- `,
- [
- {
- name: 'Object Size',
- unit: 'B',
- columnName: 'self_size',
- },
- {
- name: 'Object Count',
- unit: '',
- columnName: 'self_count',
- },
- ],
- 'include perfetto module android.memory.heap_graph.class_tree;',
- [{name: 'root_type', displayName: 'Root Type'}],
- ),
- ...metricsFromTableOrSubquery(
- `
- (
- select
- id,
- parent_id as parentId,
- ifnull(name, '[Unknown]') as name,
- root_type,
- self_size,
- self_count
- from _heap_graph_dominator_class_tree
- where graph_sample_ts = ${ts} and upid = ${upid}
- )
- `,
- [
- {
- name: 'Dominated Object Size',
- unit: 'B',
- columnName: 'self_size',
- },
- {
- name: 'Dominated Object Count',
- unit: '',
- columnName: 'self_count',
- },
- ],
- 'include perfetto module android.memory.heap_graph.dominator_class_tree;',
- [{name: 'root_type', displayName: 'Root Type'}],
- ),
- ],
- };
-}
-
-function getFlamegraphTitle(type: ProfileType) {
- switch (type) {
- case ProfileType.HEAP_PROFILE:
- return 'Heap profile';
- case ProfileType.JAVA_HEAP_GRAPH:
- return 'Java heap graph';
- case ProfileType.JAVA_HEAP_SAMPLES:
- return 'Java heap samples';
- case ProfileType.MIXED_HEAP_PROFILE:
- return 'Mixed heap profile';
- case ProfileType.NATIVE_HEAP_PROFILE:
- return 'Native heap profile';
- case ProfileType.PERF_SAMPLE:
- assertFalse(false, 'Perf sample not supported');
- return 'Impossible';
- }
-}
-
-async function downloadPprof(
- engine: Engine | undefined,
- upid: number,
- ts: time,
-) {
- if (engine === undefined) {
- return;
- }
- try {
- const pid = await engine.query(
- `select pid from process where upid = ${upid}`,
- );
- const trace = await getCurrentTrace();
- convertTraceToPprofAndDownload(trace, pid.firstRow({pid: NUM}).pid, ts);
- } catch (error) {
- throw new Error(`Failed to get current trace ${error}`);
- }
-}
-
-function maybeShowModal(type: ProfileType, heapGraphIncomplete: boolean) {
- if (type !== ProfileType.JAVA_HEAP_GRAPH || !heapGraphIncomplete) {
- return undefined;
- }
- if (globals.state.flamegraphModalDismissed) {
- return undefined;
- }
- return m(Modal, {
- title: 'The flamegraph is incomplete',
- vAlign: 'TOP',
- content: m(
- 'div',
- 'The current trace does not have a fully formed flamegraph',
- ),
- buttons: [
- {
- text: 'Show the errors',
- primary: true,
- action: () => Router.navigate('#!/info'),
- },
- {
- text: 'Skip',
- action: () => {
- globals.dispatch(Actions.dismissFlamegraphModal({}));
- raf.scheduleFullRedraw();
- },
- },
- ],
- });
-}
diff --git a/ui/src/core_plugins/perf_samples_profile/index.ts b/ui/src/core_plugins/perf_samples_profile/index.ts
deleted file mode 100644
index b47e426..0000000
--- a/ui/src/core_plugins/perf_samples_profile/index.ts
+++ /dev/null
@@ -1,284 +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.
-
-import m from 'mithril';
-import {TrackData} from '../../common/track_data';
-import {Engine} from '../../trace_processor/engine';
-import {DetailsPanel} from '../../public/details_panel';
-import {PERF_SAMPLES_PROFILE_TRACK_KIND} from '../../public/track_kinds';
-import {Trace} from '../../public/trace';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
-import {NUM, NUM_NULL, STR_NULL} from '../../trace_processor/query_result';
-import {
- PerfSamplesSelection,
- ProfileType,
- Selection,
-} from '../../public/selection';
-import {
- QueryFlamegraph,
- QueryFlamegraphAttrs,
- metricsFromTableOrSubquery,
-} from '../../core/query_flamegraph';
-import {Monitor} from '../../base/monitor';
-import {DetailsShell} from '../../widgets/details_shell';
-import {assertExists} from '../../base/logging';
-import {Timestamp} from '../../frontend/widgets/timestamp';
-import {
- ProcessPerfSamplesProfileTrack,
- ThreadPerfSamplesProfileTrack,
-} from './perf_samples_profile_track';
-import {getThreadUriPrefix} from '../../public/utils';
-import {
- getOrCreateGroupForProcess,
- getOrCreateGroupForThread,
-} from '../../public/standard_groups';
-import {TrackNode} from '../../public/workspace';
-
-export interface Data extends TrackData {
- tsStarts: BigInt64Array;
-}
-
-class PerfSamplesProfilePlugin implements PerfettoPlugin {
- async onTraceLoad(ctx: Trace): Promise<void> {
- const pResult = await ctx.engine.query(`
- select distinct upid
- from perf_sample
- join thread using (utid)
- where callsite_id is not null and upid is not null
- `);
- for (const it = pResult.iter({upid: NUM}); it.valid(); it.next()) {
- const upid = it.upid;
- const uri = `/process_${upid}/perf_samples_profile`;
- const title = `Process Callstacks`;
- ctx.tracks.registerTrack({
- uri,
- title,
- tags: {
- kind: PERF_SAMPLES_PROFILE_TRACK_KIND,
- upid,
- },
- track: new ProcessPerfSamplesProfileTrack(
- {
- trace: ctx,
- uri,
- },
- upid,
- ),
- });
- const group = getOrCreateGroupForProcess(ctx.workspace, upid);
- const track = new TrackNode({uri, title, sortOrder: -40});
- group.addChildInOrder(track);
- }
- const tResult = await ctx.engine.query(`
- select distinct
- utid,
- tid,
- thread.name as threadName,
- upid
- from perf_sample
- join thread using (utid)
- where callsite_id is not null
- `);
- for (
- const it = tResult.iter({
- utid: NUM,
- tid: NUM,
- threadName: STR_NULL,
- upid: NUM_NULL,
- });
- it.valid();
- it.next()
- ) {
- const {threadName, utid, tid, upid} = it;
- const title =
- threadName === null
- ? `Thread Callstacks ${tid}`
- : `${threadName} Callstacks ${tid}`;
- const uri = `${getThreadUriPrefix(upid, utid)}_perf_samples_profile`;
- ctx.tracks.registerTrack({
- uri,
- title,
- tags: {
- kind: PERF_SAMPLES_PROFILE_TRACK_KIND,
- utid,
- upid: upid ?? undefined,
- },
- track: new ThreadPerfSamplesProfileTrack(
- {
- trace: ctx,
- uri,
- },
- utid,
- ),
- });
- const group = getOrCreateGroupForThread(ctx.workspace, utid);
- const track = new TrackNode({uri, title, sortOrder: -50});
- group.addChildInOrder(track);
- }
- ctx.tabs.registerDetailsPanel(
- new PerfSamplesFlamegraphDetailsPanel(ctx.engine),
- );
-
- await selectPerfSample(ctx);
- }
-}
-
-async function selectPerfSample(ctx: Trace) {
- const profile = await assertExists(ctx.engine).query(`
- select upid
- from perf_sample
- join thread using (utid)
- where callsite_id is not null
- order by ts desc
- limit 1
- `);
- if (profile.numRows() !== 1) return;
- const row = profile.firstRow({upid: NUM});
- const upid = row.upid;
- const leftTs = ctx.traceInfo.start;
- const rightTs = ctx.traceInfo.end;
- ctx.selection.selectLegacy({
- kind: 'PERF_SAMPLES',
- id: 0,
- upid,
- leftTs,
- rightTs,
- type: ProfileType.PERF_SAMPLE,
- });
-}
-
-class PerfSamplesFlamegraphDetailsPanel implements DetailsPanel {
- private sel?: PerfSamplesSelection;
- private selMonitor = new Monitor([
- () => this.sel?.leftTs,
- () => this.sel?.rightTs,
- () => this.sel?.upid,
- () => this.sel?.utid,
- () => this.sel?.type,
- ]);
- private flamegraphAttrs?: QueryFlamegraphAttrs;
-
- constructor(private engine: Engine) {}
-
- render(sel: Selection) {
- if (sel.kind !== 'legacy') {
- this.sel = undefined;
- return;
- }
-
- const legacySel = sel.legacySelection;
- if (legacySel.kind !== 'PERF_SAMPLES') {
- this.sel = undefined;
- return undefined;
- }
-
- const {leftTs, rightTs, upid, utid} = legacySel;
- this.sel = legacySel;
- if (this.selMonitor.ifStateChanged()) {
- this.flamegraphAttrs = {
- engine: this.engine,
- metrics: [
- ...metricsFromTableOrSubquery(
- utid !== undefined
- ? `
- (
- select
- id,
- parent_id as parentId,
- name,
- mapping_name,
- source_file,
- cast(line_number AS text) as line_number,
- self_count
- from _linux_perf_callstacks_for_samples!((
- select p.callsite_id
- from perf_sample p
- where p.ts >= ${leftTs}
- and p.ts <= ${rightTs}
- and p.utid = ${utid}
- ))
- )
- `
- : `
- (
- select
- id,
- parent_id as parentId,
- name,
- mapping_name,
- source_file,
- cast(line_number AS text) as line_number,
- self_count
- from _linux_perf_callstacks_for_samples!((
- select p.callsite_id
- from perf_sample p
- join thread t using (utid)
- where p.ts >= ${leftTs}
- and p.ts <= ${rightTs}
- and t.upid = ${assertExists(upid)}
- ))
- )
- `,
- [
- {
- name: 'Perf Samples',
- unit: '',
- columnName: 'self_count',
- },
- ],
- 'include perfetto module linux.perf.samples',
- [{name: 'mapping_name', displayName: 'Mapping'}],
- [
- {name: 'source_file', displayName: 'Source File'},
- {name: 'line_number', displayName: 'Line Number'},
- ],
- ),
- ],
- };
- }
- return m(
- '.flamegraph-profile',
- m(
- DetailsShell,
- {
- fillParent: true,
- title: m('.title', 'Perf Samples'),
- description: [],
- buttons: [
- m(
- 'div.time',
- `First timestamp: `,
- m(Timestamp, {
- ts: this.sel.leftTs,
- }),
- ),
- m(
- 'div.time',
- `Last timestamp: `,
- m(Timestamp, {
- ts: this.sel.rightTs,
- }),
- ),
- ],
- },
- m(QueryFlamegraph, assertExists(this.flamegraphAttrs)),
- ),
- );
- }
-}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'perfetto.PerfSamplesProfile',
- plugin: PerfSamplesProfilePlugin,
-};
diff --git a/ui/src/core_plugins/perf_samples_profile/perf_samples_profile_track.ts b/ui/src/core_plugins/perf_samples_profile/perf_samples_profile_track.ts
deleted file mode 100644
index d06e776..0000000
--- a/ui/src/core_plugins/perf_samples_profile/perf_samples_profile_track.ts
+++ /dev/null
@@ -1,133 +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 {NUM} from '../../trace_processor/query_result';
-import {Slice} from '../../public/track';
-import {
- BaseSliceTrack,
- OnSliceClickArgs,
-} from '../../frontend/base_slice_track';
-import {NewTrackArgs} from '../../frontend/track';
-import {NAMED_ROW, NamedRow} from '../../frontend/named_slice_track';
-import {getColorForSample} from '../../core/colorizer';
-import {Time} from '../../base/time';
-import {globals} from '../../frontend/globals';
-import {ProfileType} from '../../public/selection';
-import {LegacySelection} from '../../public/selection';
-import {assertExists} from '../../base/logging';
-
-interface PerfSampleRow extends NamedRow {
- callsiteId: number;
-}
-
-abstract class BasePerfSamplesProfileTrack extends BaseSliceTrack<
- Slice,
- PerfSampleRow
-> {
- constructor(args: NewTrackArgs) {
- super(args);
- }
-
- protected getRowSpec(): PerfSampleRow {
- return {...NAMED_ROW, callsiteId: NUM};
- }
-
- protected rowToSlice(row: PerfSampleRow): Slice {
- const baseSlice = super.rowToSliceBase(row);
- const name = assertExists(row.name);
- const colorScheme = getColorForSample(row.callsiteId);
- return {...baseSlice, title: name, colorScheme};
- }
-
- isSelectionHandled(selection: LegacySelection): boolean {
- return selection.kind === 'PERF_SAMPLES';
- }
-
- onUpdatedSlices(slices: Slice[]) {
- for (const slice of slices) {
- slice.isHighlighted = slice === this.hoveredSlice;
- }
- }
-}
-
-export class ProcessPerfSamplesProfileTrack extends BasePerfSamplesProfileTrack {
- constructor(
- args: NewTrackArgs,
- private upid: number,
- ) {
- super(args);
- }
-
- getSqlSource(): string {
- return `
- select
- p.id,
- ts,
- 0 as dur,
- 0 as depth,
- 'Perf Sample' as name,
- callsite_id as callsiteId
- from perf_sample p
- join thread using (utid)
- where upid = ${this.upid} and callsite_id is not null
- order by ts
- `;
- }
-
- onSliceClick({slice}: OnSliceClickArgs<Slice>) {
- globals.selectionManager.selectLegacy({
- kind: 'PERF_SAMPLES',
- id: slice.id,
- upid: this.upid,
- leftTs: Time.fromRaw(slice.ts),
- rightTs: Time.fromRaw(slice.ts),
- type: ProfileType.PERF_SAMPLE,
- });
- }
-}
-
-export class ThreadPerfSamplesProfileTrack extends BasePerfSamplesProfileTrack {
- constructor(
- args: NewTrackArgs,
- private utid: number,
- ) {
- super(args);
- }
-
- getSqlSource(): string {
- return `
- select
- p.id,
- ts,
- 0 as dur,
- 0 as depth,
- 'Perf Sample' as name,
- callsite_id as callsiteId
- from perf_sample p
- where utid = ${this.utid} and callsite_id is not null
- order by ts
- `;
- }
-
- onSliceClick({slice}: OnSliceClickArgs<Slice>) {
- globals.selectionManager.selectLegacy({
- kind: 'PERF_SAMPLES',
- id: slice.id,
- utid: this.utid,
- leftTs: Time.fromRaw(slice.ts),
- rightTs: Time.fromRaw(slice.ts),
- type: ProfileType.PERF_SAMPLE,
- });
- }
-}
diff --git a/ui/src/core_plugins/screenshots/index.ts b/ui/src/core_plugins/screenshots/index.ts
deleted file mode 100644
index b7e41e2..0000000
--- a/ui/src/core_plugins/screenshots/index.ts
+++ /dev/null
@@ -1,77 +0,0 @@
-// 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 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 {uuidv4} from '../../base/uuid';
-import {GenericSliceDetailsTabConfig} from '../../frontend/generic_slice_details_tab';
-import {TrackNode} from '../../public/workspace';
-import {BottomTabToSCSAdapter} from '../../public/utils';
-import {NUM} from '../../trace_processor/query_result';
-import {Trace} from '../../public/trace';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
-import {ScreenshotTab} from './screenshot_panel';
-import {ScreenshotsTrack} from './screenshots_track';
-
-class ScreenshotsPlugin implements PerfettoPlugin {
- async onTraceLoad(ctx: Trace): Promise<void> {
- const res = await ctx.engine.query(`
- INCLUDE PERFETTO MODULE android.screenshots;
- select
- count() as count
- from android_screenshots
- `);
- const {count} = res.firstRow({count: NUM});
-
- if (count > 0) {
- const title = 'Screenshots';
- const uri = '/screenshots';
- ctx.tracks.registerTrack({
- uri,
- title,
- track: new ScreenshotsTrack({
- trace: ctx,
- uri,
- }),
- tags: {
- kind: ScreenshotsTrack.kind,
- },
- });
- const trackNode = new TrackNode({uri, title, sortOrder: -60});
- ctx.workspace.addChildInOrder(trackNode);
-
- ctx.tabs.registerDetailsPanel(
- new BottomTabToSCSAdapter({
- tabFactory: (selection) => {
- if (
- selection.kind === 'GENERIC_SLICE' &&
- selection.detailsPanelConfig.kind === ScreenshotTab.kind
- ) {
- const config = selection.detailsPanelConfig.config;
- return new ScreenshotTab({
- config: config as GenericSliceDetailsTabConfig,
- trace: ctx,
- uuid: uuidv4(),
- });
- }
- return undefined;
- },
- }),
- );
- }
- }
-}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'perfetto.Screenshots',
- plugin: ScreenshotsPlugin,
-};
diff --git a/ui/src/core_plugins/slice/index.ts b/ui/src/core_plugins/slice/index.ts
deleted file mode 100644
index 34a1ebc..0000000
--- a/ui/src/core_plugins/slice/index.ts
+++ /dev/null
@@ -1,44 +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 {addSqlTableTab} from '../../frontend/sql_table_tab_interface';
-import {sqlTableRegistry} from '../../frontend/widgets/sql/table/sql_table_registry';
-import {Trace} from '../../public/trace';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
-import {getSliceTable} from './table';
-import {AsyncAndThreadSliceSelectionAggregator} from './async_and_thread_slice_selection_aggregator';
-
-class SlicePlugin implements PerfettoPlugin {
- async onTraceLoad(ctx: Trace) {
- ctx.selection.registerAreaSelectionAggreagtor(
- new AsyncAndThreadSliceSelectionAggregator(),
- );
-
- sqlTableRegistry['slice'] = getSliceTable();
- ctx.commands.registerCommand({
- id: 'perfetto.ShowTable.slice',
- name: 'Open table: slice',
- callback: () => {
- addSqlTableTab(ctx, {
- table: getSliceTable(),
- });
- },
- });
- }
-}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'perfetto.Slice',
- plugin: SlicePlugin,
-};
diff --git a/ui/src/core_plugins/thread/index.ts b/ui/src/core_plugins/thread/index.ts
deleted file mode 100644
index 892bd95..0000000
--- a/ui/src/core_plugins/thread/index.ts
+++ /dev/null
@@ -1,39 +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 {addSqlTableTab} from '../../frontend/sql_table_tab_interface';
-import {sqlTableRegistry} from '../../frontend/widgets/sql/table/sql_table_registry';
-import {Trace} from '../../public/trace';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
-import {getThreadTable} from './table';
-
-class ThreadPlugin implements PerfettoPlugin {
- async onTraceLoad(ctx: Trace) {
- sqlTableRegistry['thread'] = getThreadTable();
- ctx.commands.registerCommand({
- id: 'perfetto.ShowTable.thread',
- name: 'Open table: thread',
- callback: () => {
- addSqlTableTab(ctx, {
- table: getThreadTable(),
- });
- },
- });
- }
-}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'perfetto.Thread',
- plugin: ThreadPlugin,
-};
diff --git a/ui/src/core_plugins/thread_slice/index.ts b/ui/src/core_plugins/thread_slice/index.ts
deleted file mode 100644
index d57d41d..0000000
--- a/ui/src/core_plugins/thread_slice/index.ts
+++ /dev/null
@@ -1,182 +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.
-
-import {uuidv4} from '../../base/uuid';
-import {THREAD_SLICE_TRACK_KIND} from '../../public/track_kinds';
-import {ThreadSliceDetailsTab} from '../../frontend/thread_slice_details_tab';
-import {BottomTabToSCSAdapter} from '../../public/utils';
-import {Trace} from '../../public/trace';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
-import {getThreadUriPrefix, getTrackName} from '../../public/utils';
-import {NUM, NUM_NULL, STR_NULL} from '../../trace_processor/query_result';
-import {ThreadSliceTrack} from '../../frontend/thread_slice_track';
-import {removeFalsyValues} from '../../base/array_utils';
-import {getOrCreateGroupForThread} from '../../public/standard_groups';
-import {TrackNode} from '../../public/workspace';
-
-function uriForSliceTrack(
- upid: number | null,
- utid: number,
- trackId: number,
-): string {
- return `${getThreadUriPrefix(upid, utid)}_slice_${trackId}`;
-}
-
-class ThreadSlicesPlugin implements PerfettoPlugin {
- async onTraceLoad(ctx: Trace): Promise<void> {
- const {engine} = ctx;
-
- const result = await engine.query(`
- include perfetto module viz.summary.slices;
- include perfetto module viz.summary.threads;
- include perfetto module viz.threads;
-
- select
- tt.utid as utid,
- tt.id as trackId,
- tt.name as trackName,
- extract_arg(
- tt.source_arg_set_id,
- 'is_root_in_scope'
- ) as isDefaultTrackForScope,
- t.tid,
- t.name as threadName,
- s.max_depth as maxDepth,
- t.upid as upid,
- t.is_main_thread as isMainThread,
- t.is_kernel_thread AS isKernelThread
- from _thread_track_summary_by_utid_and_name s
- join _threads_with_kernel_flag t using(utid)
- join thread_track tt on s.track_id = tt.id
- where s.track_count = 1
- `);
- const it = result.iter({
- utid: NUM,
- trackId: NUM,
- trackName: STR_NULL,
- isDefaultTrackForScope: NUM_NULL,
- tid: NUM_NULL,
- threadName: STR_NULL,
- maxDepth: NUM,
- upid: NUM_NULL,
- isMainThread: NUM_NULL,
- isKernelThread: NUM,
- });
- for (; it.valid(); it.next()) {
- const {
- upid,
- utid,
- trackId,
- trackName,
- tid,
- threadName,
- maxDepth,
- isMainThread,
- isKernelThread,
- isDefaultTrackForScope,
- } = it;
- const title = getTrackName({
- name: trackName,
- utid,
- tid,
- threadName,
- kind: 'Slices',
- });
-
- const uri = uriForSliceTrack(upid, utid, trackId);
- ctx.tracks.registerTrack({
- uri,
- title,
- tags: {
- trackIds: [trackId],
- kind: THREAD_SLICE_TRACK_KIND,
- utid,
- upid: upid ?? undefined,
- ...(isDefaultTrackForScope === 1 && {isDefaultTrackForScope: true}),
- ...(isKernelThread === 1 && {kernelThread: true}),
- },
- chips: removeFalsyValues([
- isKernelThread === 0 && isMainThread === 1 && 'main thread',
- ]),
- track: new ThreadSliceTrack(
- {
- trace: ctx,
- uri,
- },
- trackId,
- maxDepth,
- ),
- });
- const group = getOrCreateGroupForThread(ctx.workspace, utid);
- const track = new TrackNode({uri, title, sortOrder: 20});
- group.addChildInOrder(track);
- }
-
- ctx.tabs.registerDetailsPanel(
- new BottomTabToSCSAdapter({
- tabFactory: (sel) => {
- if (sel.kind !== 'SLICE') {
- return undefined;
- }
- return new ThreadSliceDetailsTab({
- config: {
- table: sel.table ?? 'slice',
- id: sel.id,
- },
- trace: ctx,
- uuid: uuidv4(),
- });
- },
- }),
- );
-
- ctx.selection.registerSqlSelectionResolver({
- sqlTableName: 'slice',
- callback: async (id: number) => {
- const result = await ctx.engine.query(`
- select
- tt.utid as utid,
- t.upid as upid,
- track_id as trackId
- from
- slice
- join thread_track tt on slice.track_id = tt.id
- join _threads_with_kernel_flag t using(utid)
- where slice.id = ${id}
- `);
-
- const {upid, utid, trackId} = result.firstRow({
- upid: NUM_NULL,
- utid: NUM,
- trackId: NUM,
- });
-
- return {
- kind: 'legacy',
- legacySelection: {
- kind: 'SLICE',
- id,
- trackUri: uriForSliceTrack(upid, utid, trackId),
- table: 'slice',
- },
- };
- },
- });
- }
-}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'perfetto.ThreadSlices',
- plugin: ThreadSlicesPlugin,
-};
diff --git a/ui/src/core_plugins/track_utils/index.ts b/ui/src/core_plugins/track_utils/index.ts
index e671907..369d2fe 100644
--- a/ui/src/core_plugins/track_utils/index.ts
+++ b/ui/src/core_plugins/track_utils/index.ts
@@ -14,12 +14,13 @@
import {OmniboxMode} from '../../core/omnibox_manager';
import {Trace} from '../../public/trace';
-import {PromptOption} from '../../public/omnibox';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
+import {PerfettoPlugin} from '../../public/plugin';
import {AppImpl} from '../../core/app_impl';
import {getTimeSpanOfSelectionOrVisibleWindow} from '../../public/utils';
+import {exists} from '../../base/utils';
-class TrackUtilsPlugin implements PerfettoPlugin {
+export default class implements PerfettoPlugin {
+ static readonly id = 'perfetto.TrackUtils';
async onTraceLoad(ctx: Trace): Promise<void> {
ctx.commands.registerCommand({
id: 'perfetto.RunQueryInSelectedTimeWindow',
@@ -36,41 +37,51 @@
});
ctx.commands.registerCommand({
- // Selects & reveals the first track on the timeline with a given URI.
- id: 'perfetto.FindTrack',
+ id: 'perfetto.FindTrackByName',
+ name: 'Find track by name',
+ callback: async () => {
+ const options = ctx.workspace.flatTracks
+ .map((node) => {
+ return exists(node.uri)
+ ? {key: node.uri, displayName: node.fullPath.join(' \u2023 ')}
+ : undefined;
+ })
+ .filter((pair) => pair !== undefined);
+ const uri = await ctx.omnibox.prompt('Choose a track...', options);
+ uri && ctx.selection.selectTrack(uri, {scrollToSelection: true});
+ },
+ });
+
+ ctx.commands.registerCommand({
+ id: 'perfetto.FindTrackByUri',
name: 'Find track by URI',
callback: async () => {
- const tracks = ctx.tracks.getAllTracks();
- const options = tracks.map(({uri}): PromptOption => {
- return {key: uri, displayName: uri};
- });
+ const options = ctx.workspace.flatTracks
+ .map((track) => track.uri)
+ .filter((uri) => uri !== undefined)
+ .map((uri) => {
+ return {key: uri, displayName: uri};
+ });
- // Sort tracks in a natural sort order
- const collator = new Intl.Collator('en', {
- numeric: true,
- sensitivity: 'base',
- });
- const sortedOptions = options.sort((a, b) => {
- return collator.compare(a.displayName, b.displayName);
- });
+ const uri = await ctx.omnibox.prompt('Choose a track...', options);
+ uri && ctx.selection.selectTrack(uri, {scrollToSelection: true});
+ },
+ });
- const selectedUri = await ctx.omnibox.prompt(
- 'Choose a track...',
- sortedOptions,
- );
- if (selectedUri === undefined) return; // Prompt cancelled.
- ctx.scrollTo({track: {uri: selectedUri, expandGroup: true}});
- ctx.selection.selectArea({
- start: ctx.traceInfo.start,
- end: ctx.traceInfo.end,
- trackUris: [selectedUri],
- });
+ ctx.commands.registerCommand({
+ id: 'perfetto.PinTrackByName',
+ name: 'Pin track by name',
+ callback: async () => {
+ const options = ctx.workspace.flatTracks
+ .map((node) => {
+ return exists(node.uri)
+ ? {key: node.id, displayName: node.fullPath.join(' \u2023 ')}
+ : undefined;
+ })
+ .filter((option) => option !== undefined);
+ const id = await ctx.omnibox.prompt('Choose a track...', options);
+ id && ctx.workspace.getTrackById(id)?.pin();
},
});
}
}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'perfetto.TrackUtils',
- plugin: TrackUtilsPlugin,
-};
diff --git a/ui/src/frontend/aggregation_panel.ts b/ui/src/frontend/aggregation_panel.ts
index 01f90b3..3d441ba 100644
--- a/ui/src/frontend/aggregation_panel.ts
+++ b/ui/src/frontend/aggregation_panel.ts
@@ -19,7 +19,7 @@
ThreadStateExtra,
isEmptyData,
} from '../public/aggregation';
-import {colorForState} from '../core/colorizer';
+import {colorForState} from '../public/lib/colorizer';
import {DurationWidget} from './widgets/duration';
import {EmptyState} from '../widgets/empty_state';
import {Anchor} from '../widgets/anchor';
diff --git a/ui/src/frontend/aggregation_tab.ts b/ui/src/frontend/aggregation_tab.ts
index 3a223e6..e410a60 100644
--- a/ui/src/frontend/aggregation_tab.ts
+++ b/ui/src/frontend/aggregation_tab.ts
@@ -26,17 +26,17 @@
import {
CPU_PROFILE_TRACK_KIND,
PERF_SAMPLES_PROFILE_TRACK_KIND,
- THREAD_SLICE_TRACK_KIND,
+ SLICE_TRACK_KIND,
} from '../public/track_kinds';
import {
QueryFlamegraph,
- QueryFlamegraphAttrs,
metricsFromTableOrSubquery,
-} from '../core/query_flamegraph';
+} from '../public/lib/query_flamegraph';
import {DisposableStack} from '../base/disposable_stack';
import {assertExists} from '../base/logging';
import {TraceImpl} from '../core/trace_impl';
import {Trace} from '../public/trace';
+import {Flamegraph} from '../widgets/flamegraph';
interface View {
key: string;
@@ -50,9 +50,9 @@
private trace: TraceImpl;
private monitor: Monitor;
private currentTab: string | undefined = undefined;
- private cpuProfileFlamegraphAttrs?: QueryFlamegraphAttrs;
- private perfSampleFlamegraphAttrs?: QueryFlamegraphAttrs;
- private sliceFlamegraphAttrs?: QueryFlamegraphAttrs;
+ private cpuProfileFlamegraph?: QueryFlamegraph;
+ private perfSampleFlamegraph?: QueryFlamegraph;
+ private sliceFlamegraph?: QueryFlamegraph;
constructor({attrs}: m.CVnode<AreaDetailsPanelAttrs>) {
this.trace = attrs.trace;
@@ -174,42 +174,39 @@
}
private addFlamegraphView(trace: Trace, isChanged: boolean, views: View[]) {
- this.cpuProfileFlamegraphAttrs = this.computeCpuProfileFlamegraphAttrs(
+ this.cpuProfileFlamegraph = this.computeCpuProfileFlamegraph(
trace,
isChanged,
);
- if (this.cpuProfileFlamegraphAttrs !== undefined) {
+ if (this.cpuProfileFlamegraph !== undefined) {
views.push({
key: 'cpu_profile_flamegraph_selection',
name: 'CPU Profile Sample Flamegraph',
- content: m(QueryFlamegraph, this.cpuProfileFlamegraphAttrs),
+ content: this.cpuProfileFlamegraph.render(),
});
}
- this.perfSampleFlamegraphAttrs = this.computePerfSampleFlamegraphAttrs(
+ this.perfSampleFlamegraph = this.computePerfSampleFlamegraph(
trace,
isChanged,
);
- if (this.perfSampleFlamegraphAttrs !== undefined) {
+ if (this.perfSampleFlamegraph !== undefined) {
views.push({
key: 'perf_sample_flamegraph_selection',
name: 'Perf Sample Flamegraph',
- content: m(QueryFlamegraph, this.perfSampleFlamegraphAttrs),
+ content: this.perfSampleFlamegraph.render(),
});
}
- this.sliceFlamegraphAttrs = this.computeSliceFlamegraphAttrs(
- trace,
- isChanged,
- );
- if (this.sliceFlamegraphAttrs !== undefined) {
+ this.sliceFlamegraph = this.computeSliceFlamegraph(trace, isChanged);
+ if (this.sliceFlamegraph !== undefined) {
views.push({
key: 'slice_flamegraph_selection',
name: 'Slice Flamegraph',
- content: m(QueryFlamegraph, this.sliceFlamegraphAttrs),
+ content: this.sliceFlamegraph.render(),
});
}
}
- private computeCpuProfileFlamegraphAttrs(trace: Trace, isChanged: boolean) {
+ private computeCpuProfileFlamegraph(trace: Trace, isChanged: boolean) {
const currentSelection = trace.selection.selection;
if (currentSelection.kind !== 'area') {
return undefined;
@@ -217,7 +214,7 @@
if (!isChanged) {
// If the selection has not changed, just return a copy of the last seen
// attrs.
- return this.cpuProfileFlamegraphAttrs;
+ return this.cpuProfileFlamegraph;
}
const utids = [];
for (const trackInfo of currentSelection.tracks) {
@@ -228,48 +225,54 @@
if (utids.length === 0) {
return undefined;
}
- return {
- engine: trace.engine,
- metrics: [
- ...metricsFromTableOrSubquery(
- `
- (
- select
- id,
- parent_id as parentId,
- name,
- mapping_name,
- source_file,
- cast(line_number AS text) as line_number,
- self_count
- from _callstacks_for_cpu_profile_stack_samples!((
- select p.callsite_id
- from cpu_profile_stack_sample p
- where p.ts >= ${currentSelection.start}
- and p.ts <= ${currentSelection.end}
- and p.utid in (${utids.join(',')})
- ))
- )
- `,
- [
- {
- name: 'CPU Profile Samples',
- unit: '',
- columnName: 'self_count',
- },
- ],
- 'include perfetto module callstacks.stack_profile',
- [{name: 'mapping_name', displayName: 'Mapping'}],
- [
- {name: 'source_file', displayName: 'Source File'},
- {name: 'line_number', displayName: 'Line Number'},
- ],
- ),
+ const metrics = metricsFromTableOrSubquery(
+ `
+ (
+ select
+ id,
+ parent_id as parentId,
+ name,
+ mapping_name,
+ source_file,
+ cast(line_number AS text) as line_number,
+ self_count
+ from _callstacks_for_callsites!((
+ select p.callsite_id
+ from cpu_profile_stack_sample p
+ where p.ts >= ${currentSelection.start}
+ and p.ts <= ${currentSelection.end}
+ and p.utid in (${utids.join(',')})
+ ))
+ )
+ `,
+ [
+ {
+ name: 'CPU Profile Samples',
+ unit: '',
+ columnName: 'self_count',
+ },
],
- };
+ 'include perfetto module callstacks.stack_profile',
+ [{name: 'mapping_name', displayName: 'Mapping'}],
+ [
+ {
+ name: 'source_file',
+ displayName: 'Source File',
+ mergeAggregation: 'ONE_OR_NULL',
+ },
+ {
+ name: 'line_number',
+ displayName: 'Line Number',
+ mergeAggregation: 'ONE_OR_NULL',
+ },
+ ],
+ );
+ return new QueryFlamegraph(trace, metrics, {
+ state: Flamegraph.createDefaultState(metrics),
+ });
}
- private computePerfSampleFlamegraphAttrs(trace: Trace, isChanged: boolean) {
+ private computePerfSampleFlamegraph(trace: Trace, isChanged: boolean) {
const currentSelection = trace.selection.selection;
if (currentSelection.kind !== 'area') {
return undefined;
@@ -277,47 +280,45 @@
if (!isChanged) {
// If the selection has not changed, just return a copy of the last seen
// attrs.
- return this.perfSampleFlamegraphAttrs;
+ return this.perfSampleFlamegraph;
}
const upids = getUpidsFromPerfSampleAreaSelection(currentSelection);
const utids = getUtidsFromPerfSampleAreaSelection(currentSelection);
if (utids.length === 0 && upids.length === 0) {
return undefined;
}
- return {
- engine: trace.engine,
- metrics: [
- ...metricsFromTableOrSubquery(
- `
- (
- select id, parent_id as parentId, name, self_count
- from _linux_perf_callstacks_for_samples!((
- select p.callsite_id
- from perf_sample p
- join thread t using (utid)
- where p.ts >= ${currentSelection.start}
- and p.ts <= ${currentSelection.end}
- and (
- p.utid in (${utids.join(',')})
- or t.upid in (${upids.join(',')})
- )
- ))
- )
- `,
- [
- {
- name: 'Perf Samples',
- unit: '',
- columnName: 'self_count',
- },
- ],
- 'include perfetto module linux.perf.samples',
- ),
+ const metrics = metricsFromTableOrSubquery(
+ `
+ (
+ select id, parent_id as parentId, name, self_count
+ from _callstacks_for_callsites!((
+ select p.callsite_id
+ from perf_sample p
+ join thread t using (utid)
+ where p.ts >= ${currentSelection.start}
+ and p.ts <= ${currentSelection.end}
+ and (
+ p.utid in (${utids.join(',')})
+ or t.upid in (${upids.join(',')})
+ )
+ ))
+ )
+ `,
+ [
+ {
+ name: 'Perf Samples',
+ unit: '',
+ columnName: 'self_count',
+ },
],
- };
+ 'include perfetto module linux.perf.samples',
+ );
+ return new QueryFlamegraph(trace, metrics, {
+ state: Flamegraph.createDefaultState(metrics),
+ });
}
- private computeSliceFlamegraphAttrs(trace: Trace, isChanged: boolean) {
+ private computeSliceFlamegraph(trace: Trace, isChanged: boolean) {
const currentSelection = trace.selection.selection;
if (currentSelection.kind !== 'area') {
return undefined;
@@ -325,11 +326,11 @@
if (!isChanged) {
// If the selection has not changed, just return a copy of the last seen
// attrs.
- return this.sliceFlamegraphAttrs;
+ return this.sliceFlamegraph;
}
const trackIds = [];
for (const trackInfo of currentSelection.tracks) {
- if (trackInfo?.tags?.kind !== THREAD_SLICE_TRACK_KIND) {
+ if (trackInfo?.tags?.kind !== SLICE_TRACK_KIND) {
continue;
}
if (trackInfo.tags?.trackIds === undefined) {
@@ -340,38 +341,38 @@
if (trackIds.length === 0) {
return undefined;
}
- return {
- engine: trace.engine,
- metrics: [
- ...metricsFromTableOrSubquery(
- `(
- select *
- from _viz_slice_ancestor_agg!((
- select s.id, s.dur
- from slice s
- left join slice t on t.parent_id = s.id
- where s.ts >= ${currentSelection.start}
- and s.ts <= ${currentSelection.end}
- and s.track_id in (${trackIds.join(',')})
- and t.id is null
- ))
- )`,
- [
- {
- name: 'Duration',
- unit: 'ns',
- columnName: 'self_dur',
- },
- {
- name: 'Samples',
- unit: '',
- columnName: 'self_count',
- },
- ],
- 'include perfetto module viz.slices;',
- ),
+ const metrics = metricsFromTableOrSubquery(
+ `
+ (
+ select *
+ from _viz_slice_ancestor_agg!((
+ select s.id, s.dur
+ from slice s
+ left join slice t on t.parent_id = s.id
+ where s.ts >= ${currentSelection.start}
+ and s.ts <= ${currentSelection.end}
+ and s.track_id in (${trackIds.join(',')})
+ and t.id is null
+ ))
+ )
+ `,
+ [
+ {
+ name: 'Duration',
+ unit: 'ns',
+ columnName: 'self_dur',
+ },
+ {
+ name: 'Samples',
+ unit: '',
+ columnName: 'self_count',
+ },
],
- };
+ 'include perfetto module viz.slices;',
+ );
+ return new QueryFlamegraph(trace, metrics, {
+ state: Flamegraph.createDefaultState(metrics),
+ });
}
}
diff --git a/ui/src/frontend/animation.ts b/ui/src/frontend/animation.ts
index c8428c4..74cf065 100644
--- a/ui/src/frontend/animation.ts
+++ b/ui/src/frontend/animation.ts
@@ -31,12 +31,12 @@
}
this.startMs = nowMs;
this.endMs = nowMs + durationMs;
- raf.start(this.boundOnAnimationFrame);
+ raf.startAnimation(this.boundOnAnimationFrame);
}
stop() {
this.endMs = 0;
- raf.stop(this.boundOnAnimationFrame);
+ raf.stopAnimation(this.boundOnAnimationFrame);
}
get startTimeMs(): number {
@@ -45,7 +45,7 @@
private onAnimationFrame(nowMs: number) {
if (nowMs >= this.endMs) {
- raf.stop(this.boundOnAnimationFrame);
+ raf.stopAnimation(this.boundOnAnimationFrame);
return;
}
this.onAnimationStep(Math.max(Math.round(nowMs - this.startMs), 0));
diff --git a/ui/src/frontend/base_counter_track.ts b/ui/src/frontend/base_counter_track.ts
index c09ccbc..b5d57fa 100644
--- a/ui/src/frontend/base_counter_track.ts
+++ b/ui/src/frontend/base_counter_track.ts
@@ -867,7 +867,7 @@
this.countersKey = countersKey;
this.counters = data;
- raf.scheduleRedraw();
+ raf.scheduleCanvasRedraw();
}
private async createTableAndFetchLimits(
diff --git a/ui/src/frontend/base_slice_track.ts b/ui/src/frontend/base_slice_track.ts
index ec1a821..0ca6c01 100644
--- a/ui/src/frontend/base_slice_track.ts
+++ b/ui/src/frontend/base_slice_track.ts
@@ -14,21 +14,19 @@
import {assertExists} from '../base/logging';
import {clamp, floatEqual} from '../base/math_utils';
-import {Time, time} from '../base/time';
-import {exists, Optional} from '../base/utils';
-import {Actions} from '../common/actions';
+import {Duration, Time, time} from '../base/time';
+import {exists} from '../base/utils';
import {drawIncompleteSlice, drawTrackHoverTooltip} from '../base/canvas_utils';
import {cropText} from '../base/string_utils';
import {colorCompare} from '../public/color';
-import {UNEXPECTED_PINK} from '../core/colorizer';
-import {LegacySelection, SelectionKind} from '../public/selection';
+import {UNEXPECTED_PINK} from '../public/lib/colorizer';
+import {TrackEventDetails} from '../public/selection';
import {featureFlags} from '../core/feature_flags';
import {raf} from '../core/raf_scheduler';
import {Track} from '../public/track';
import {Slice} from '../public/track';
import {LONG, NUM} from '../trace_processor/query_result';
import {checkerboardExcept} from './checkerboard';
-import {globals} from './globals';
import {DEFAULT_SLICE_LAYOUT, SliceLayout} from './slice_layout';
import {NewTrackArgs} from './track';
import {BUCKETS_PER_PIXEL, CacheKey} from '../core/timeline_cache';
@@ -232,7 +230,7 @@
// - This is NOT guaranteed to be called on every frame. For instance you
// cannot use this to do some colour-based animation.
onUpdatedSlices(slices: Array<SliceT>): void {
- this.highlightHovererdAndSameTitle(slices);
+ this.highlightHoveredAndSameTitle(slices);
}
// TODO(hjd): Remove.
@@ -277,16 +275,6 @@
}
}
- protected isSelectionHandled(selection: LegacySelection): boolean {
- // TODO(hjd): Remove when updating selection.
- // We shouldn't know here about THREAD_SLICE. Maybe should be set by
- // whatever deals with that. Dunno the namespace of selection is weird. For
- // most cases in non-ambiguous (because most things are a 'slice'). But some
- // others (e.g. THREAD_SLICE) have their own ID namespace so we need this.
- const supportedSelectionKinds: SelectionKind[] = ['SCHED_SLICE', 'SLICE'];
- return supportedSelectionKinds.includes(selection.kind);
- }
-
private getTitleFont(): string {
const size = this.sliceLayout.titleSizePx ?? 12;
return `${size}px Roboto Condensed`;
@@ -403,11 +391,12 @@
visibleWindow.end.toTime('ceil'),
);
- let selection = globals.selectionManager.legacySelection;
- if (!selection || !this.isSelectionHandled(selection)) {
- selection = null;
- }
- const selectedId = selection ? (selection as {id: number}).id : undefined;
+ const selection = this.trace.selection.selection;
+ const selectedId =
+ selection.kind === 'track_event' && selection.trackUri === this.uri
+ ? selection.eventId
+ : undefined;
+
if (selectedId === undefined) {
this.selectedSlice = undefined;
}
@@ -663,7 +652,7 @@
);
}
- const resolution = rawSlicesKey.bucketSize;
+ const resolution = slicesKey.bucketSize;
const extraCols = this.extraSqlColumns.join(',');
const queryRes = await this.engine.query(`
SELECT
@@ -677,7 +666,7 @@
FROM ${this.getTableName()}(
${slicesKey.start},
${slicesKey.end},
- ${slicesKey.bucketSize}
+ ${resolution}
) z
CROSS JOIN (${this.getSqlSource()}) s using (id)
`);
@@ -705,7 +694,7 @@
this.onUpdatedSlices(slices);
this.slices = slices;
- raf.scheduleRedraw();
+ raf.scheduleCanvasRedraw();
}
private rowToSliceInternal(row: RowT): CastInternal<SliceT> {
@@ -822,15 +811,13 @@
if (slice === lastHoveredSlice) return;
if (this.hoveredSlice === undefined) {
- globals.dispatch(Actions.setHighlightedSliceId({sliceId: -1}));
+ this.trace.timeline.highlightedSliceId = undefined;
this.onSliceOut({slice: assertExists(lastHoveredSlice)});
this.hoverTooltip = [];
this.hoverPos = undefined;
} else {
const args: OnSliceOverArgs<SliceT> = {slice: this.hoveredSlice};
- globals.dispatch(
- Actions.setHighlightedSliceId({sliceId: this.hoveredSlice.id}),
- );
+ this.trace.timeline.highlightedSliceId = this.hoveredSlice.id;
this.onSliceOver(args);
this.hoverTooltip = args.tooltip || [];
}
@@ -934,10 +921,10 @@
// onUpdatedSlices() calls this. However, if the XxxSliceTrack impl overrides
// onUpdatedSlices() this gives them a chance to call the highlighting without
// having to reimplement it.
- protected highlightHovererdAndSameTitle(slices: Slice[]) {
+ protected highlightHoveredAndSameTitle(slices: Slice[]) {
for (const slice of slices) {
const isHovering =
- globals.state.highlightedSliceId === slice.id ||
+ this.trace.timeline.highlightedSliceId === slice.id ||
(this.hoveredSlice && this.hoveredSlice.title === slice.title);
slice.isHighlighted = !!isHovering;
}
@@ -948,7 +935,7 @@
return this.computedTrackHeight;
}
- getSliceVerticalBounds(depth: number): Optional<VerticalBounds> {
+ getSliceVerticalBounds(depth: number): VerticalBounds | undefined {
this.updateSliceAndTrackHeight();
const totalSliceHeight = this.computedRowSpacing + this.computedSliceHeight;
@@ -963,6 +950,28 @@
protected get engine() {
return this.trace.engine;
}
+
+ async getSelectionDetails(
+ id: number,
+ ): Promise<TrackEventDetails | undefined> {
+ const query = `
+ SELECT
+ ts,
+ dur
+ FROM (${this.getSqlSource()})
+ WHERE id = ${id}
+ `;
+
+ const result = await this.engine.query(query);
+ if (result.numRows() === 0) {
+ return undefined;
+ }
+ const row = result.iter({
+ ts: LONG,
+ dur: LONG,
+ });
+ return {ts: Time.fromRaw(row.ts), dur: Duration.fromRaw(row.dur)};
+ }
}
// This is the argument passed to onSliceOver(args).
diff --git a/ui/src/frontend/base_slice_track_unittest.ts b/ui/src/frontend/base_slice_track_unittest.ts
index dbc257c..9d07e49 100644
--- a/ui/src/frontend/base_slice_track_unittest.ts
+++ b/ui/src/frontend/base_slice_track_unittest.ts
@@ -13,7 +13,7 @@
// limitations under the License.
import {Time} from '../base/time';
-import {UNEXPECTED_PINK} from '../core/colorizer';
+import {UNEXPECTED_PINK} from '../public/lib/colorizer';
import {Slice} from '../public/track';
import {filterVisibleSlicesForTesting as filterVisibleSlices} from './base_slice_track';
diff --git a/ui/src/frontend/charts/histogram/state.ts b/ui/src/frontend/charts/histogram/state.ts
deleted file mode 100644
index f947149..0000000
--- a/ui/src/frontend/charts/histogram/state.ts
+++ /dev/null
@@ -1,110 +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 {raf} from '../../../core/raf_scheduler';
-import {Engine} from '../../../trace_processor/engine';
-import {Row} from '../../../trace_processor/query_result';
-
-interface ChartConfig {
- binAxisType: 'nominal' | 'quantitative';
- binAxis: 'x' | 'y';
- countAxis: 'x' | 'y';
- sort: string;
- isBinned: boolean;
- labelLimit?: number;
-}
-
-interface HistogramData {
- readonly rows: Row[];
- readonly error?: string;
- readonly chartConfig: ChartConfig;
-}
-
-function getHistogramConfig(
- aggregationType: 'nominal' | 'quantitative',
-): ChartConfig {
- const labelLimit = 500;
- if (aggregationType === 'nominal') {
- return {
- binAxisType: aggregationType,
- binAxis: 'y',
- countAxis: 'x',
- sort: `{
- "op": "count",
- "order": "descending"
- }`,
- isBinned: false,
- labelLimit,
- };
- } else {
- return {
- binAxisType: aggregationType,
- binAxis: 'x',
- countAxis: 'y',
- sort: 'false',
- isBinned: true,
- labelLimit,
- };
- }
-}
-
-export class HistogramState {
- data?: HistogramData;
-
- constructor(
- private readonly engine: Engine,
- private readonly query: string,
- private readonly sqlColumn: string,
- private readonly aggregationType?: 'nominal' | 'quantitative',
- ) {
- this.loadData();
- }
-
- private async loadData() {
- const res = await this.engine.query(`
- SELECT ${this.sqlColumn}
- FROM (
- ${this.query}
- )
- `);
-
- const rows: Row[] = [];
-
- let hasQuantitativeData = false;
-
- for (const it = res.iter({}); it.valid(); it.next()) {
- const rowVal = it.get(this.sqlColumn);
- if (typeof rowVal === 'bigint') {
- hasQuantitativeData = true;
- }
-
- rows.push({
- [this.sqlColumn]: rowVal,
- });
- }
-
- const aggregationType =
- this.aggregationType !== undefined
- ? this.aggregationType
- : hasQuantitativeData
- ? 'quantitative'
- : 'nominal';
-
- this.data = {
- rows,
- chartConfig: getHistogramConfig(aggregationType),
- };
-
- raf.scheduleFullRedraw();
- }
-}
diff --git a/ui/src/frontend/charts/histogram/tab.ts b/ui/src/frontend/charts/histogram/tab.ts
deleted file mode 100644
index 470249c..0000000
--- a/ui/src/frontend/charts/histogram/tab.ts
+++ /dev/null
@@ -1,146 +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 {stringifyJsonWithBigints} from '../../../base/json_utils';
-import {uuidv4} from '../../../base/uuid';
-import {addBottomTab} from '../../../common/add_ephemeral_tab';
-import {DetailsShell} from '../../../widgets/details_shell';
-import {Spinner} from '../../../widgets/spinner';
-import {VegaView} from '../../../widgets/vega_view';
-import {BottomTab, NewBottomTabArgs} from '../../../public/lib/bottom_tab';
-import {Filter, filterTitle} from '../../widgets/sql/table/column';
-import {HistogramState} from './state';
-import {Trace} from '../../../public/trace';
-
-interface HistogramTabConfig {
- 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 function addHistogramTab(
- config: HistogramTabConfig,
- trace: Trace,
-): void {
- const histogramTab = new HistogramTab({
- config,
- trace,
- uuid: uuidv4(),
- });
-
- addBottomTab(histogramTab, 'histogramTab');
-}
-
-export class HistogramTab extends BottomTab<HistogramTabConfig> {
- static readonly kind = 'dev.perfetto.HistogramTab';
-
- private state: HistogramState;
-
- constructor(args: NewBottomTabArgs<HistogramTabConfig>) {
- super(args);
-
- this.state = new HistogramState(
- this.engine,
- this.config.query,
- this.config.sqlColumn,
- this.config.aggregationType,
- );
- }
-
- static create(args: NewBottomTabArgs<HistogramTabConfig>): HistogramTab {
- return new HistogramTab(args);
- }
-
- viewTab() {
- const data = this.state.data;
- if (data === undefined) {
- return m(Spinner);
- }
- return m(
- DetailsShell,
- {
- title: this.getTitle(),
- description: this.getDescription(),
- },
- m(
- '.histogram',
- m(VegaView, {
- spec: `
- {
- "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
- "mark": "bar",
- "data": {
- "values": ${stringifyJsonWithBigints(data.rows)}
- },
- "encoding": {
- "${data.chartConfig.binAxis}": {
- "bin": ${data.chartConfig.isBinned},
- "field": "${this.config.sqlColumn}",
- "type": "${data.chartConfig.binAxisType}",
- "title": "${this.config.columnTitle}",
- "sort": ${data.chartConfig.sort},
- "axis": {
- "labelLimit": ${data.chartConfig.labelLimit}
- }
- },
- "${data.chartConfig.countAxis}": {
- "aggregate": "count",
- "title": "Count"
- }
- }
- }
- `,
- data: {},
- }),
- ),
- );
- }
-
- getTitle(): string {
- return `${this.toTitleCase(this.config.columnTitle)} ${
- this.state.data?.chartConfig.binAxisType === 'quantitative'
- ? 'Histogram'
- : 'Counts'
- }`;
- }
-
- getDescription(): string {
- let desc = `Count distribution for ${this.config.tableDisplay ?? ''} table`;
-
- if (this.config.filters) {
- desc += ' where ';
- desc += this.config.filters.map((f) => filterTitle(f)).join(', ');
- }
-
- return desc;
- }
-
- toTitleCase(s: string): string {
- const words = s.split(/\s/);
-
- for (let i = 0; i < words.length; ++i) {
- words[i] = words[i][0].toUpperCase() + words[i].substring(1);
- }
-
- return words.join(' ');
- }
-
- isLoading(): boolean {
- return this.state.data === undefined;
- }
-}
diff --git a/ui/src/frontend/clipboard.ts b/ui/src/frontend/clipboard.ts
deleted file mode 100644
index 1ddcea6..0000000
--- a/ui/src/frontend/clipboard.ts
+++ /dev/null
@@ -1,30 +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 {Actions} from '../common/actions';
-import {globals} from './globals';
-
-export function onClickCopy(url: string) {
- return (e: Event) => {
- e.preventDefault();
- copyToClipboard(url);
- globals.dispatch(
- Actions.updateStatus({
- msg: 'Link copied into the clipboard',
- timestamp: Date.now() / 1000,
- }),
- );
- };
-}
diff --git a/ui/src/frontend/cookie_consent.ts b/ui/src/frontend/cookie_consent.ts
index a869466..d7cfd84 100644
--- a/ui/src/frontend/cookie_consent.ts
+++ b/ui/src/frontend/cookie_consent.ts
@@ -14,7 +14,7 @@
import m from 'mithril';
import {raf} from '../core/raf_scheduler';
-import {globals} from './globals';
+import {AppImpl} from '../core/app_impl';
const COOKIE_ACK_KEY = 'cookieAck';
@@ -24,7 +24,7 @@
oninit() {
this.showCookieConsent = true;
if (
- !globals.logging.isEnabled() ||
+ !AppImpl.instance.analytics.isEnabled() ||
localStorage.getItem(COOKIE_ACK_KEY) === 'true'
) {
this.showCookieConsent = false;
diff --git a/ui/src/frontend/debug.ts b/ui/src/frontend/debug.ts
index 57dcfa5..9899380 100644
--- a/ui/src/frontend/debug.ts
+++ b/ui/src/frontend/debug.ts
@@ -14,30 +14,25 @@
import {produce} from 'immer';
import m from 'mithril';
-import {Actions} from '../common/actions';
-import {pluginManager} from '../common/plugins';
-import {getSchema} from '../common/schema';
import {raf} from '../core/raf_scheduler';
import {globals} from './globals';
+import {App} from '../public/app';
+import {AppImpl} from '../core/app_impl';
declare global {
interface Window {
m: typeof m;
- getSchema: typeof getSchema;
+ app: App;
globals: typeof globals;
- Actions: typeof Actions;
produce: typeof produce;
- pluginManager: typeof pluginManager;
raf: typeof raf;
}
}
export function registerDebugGlobals() {
- window.getSchema = getSchema;
window.m = m;
+ window.app = AppImpl.instance;
window.globals = globals;
- window.Actions = Actions;
window.produce = produce;
- window.pluginManager = pluginManager;
window.raf = raf;
}
diff --git a/ui/src/frontend/drag/border_drag_strategy.ts b/ui/src/frontend/drag/border_drag_strategy.ts
index 05a5228..c98b02d 100644
--- a/ui/src/frontend/drag/border_drag_strategy.ts
+++ b/ui/src/frontend/drag/border_drag_strategy.ts
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import {TimeScale} from '../../base/time_scale';
-import {DragStrategy} from './drag_strategy';
+import {DragStrategy, DragStrategyUpdateTimeFn} from './drag_strategy';
export class BorderDragStrategy extends DragStrategy {
private moveStart = false;
@@ -20,8 +20,9 @@
constructor(
map: TimeScale,
private pixelBounds: [number, number],
+ updateVizTime: DragStrategyUpdateTimeFn,
) {
- super(map);
+ super(map, updateVizTime);
}
onDrag(x: number) {
diff --git a/ui/src/frontend/drag/drag_strategy.ts b/ui/src/frontend/drag/drag_strategy.ts
index 4824335..0a0f3d4 100644
--- a/ui/src/frontend/drag/drag_strategy.ts
+++ b/ui/src/frontend/drag/drag_strategy.ts
@@ -13,12 +13,15 @@
// limitations under the License.
import {HighPrecisionTime} from '../../base/high_precision_time';
import {HighPrecisionTimeSpan} from '../../base/high_precision_time_span';
-import {raf} from '../../core/raf_scheduler';
-import {globals} from '../globals';
import {TimeScale} from '../../base/time_scale';
+export type DragStrategyUpdateTimeFn = (ts: HighPrecisionTimeSpan) => void;
+
export abstract class DragStrategy {
- constructor(protected map: TimeScale) {}
+ constructor(
+ protected map: TimeScale,
+ private updateVizTime: DragStrategyUpdateTimeFn,
+ ) {}
abstract onDrag(x: number): void;
@@ -29,7 +32,6 @@
tStart,
tEnd.sub(tStart).toNumber(),
);
- globals.timeline.updateVisibleTimeHP(vizTime);
- raf.scheduleRedraw();
+ this.updateVizTime(vizTime);
}
}
diff --git a/ui/src/frontend/drag/inner_drag_strategy.ts b/ui/src/frontend/drag/inner_drag_strategy.ts
index e423d12..61ad694 100644
--- a/ui/src/frontend/drag/inner_drag_strategy.ts
+++ b/ui/src/frontend/drag/inner_drag_strategy.ts
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import {TimeScale} from '../../base/time_scale';
-import {DragStrategy} from './drag_strategy';
+import {DragStrategy, DragStrategyUpdateTimeFn} from './drag_strategy';
export class InnerDragStrategy extends DragStrategy {
private dragStartPx = 0;
@@ -20,8 +20,9 @@
constructor(
timeScale: TimeScale,
private pixelBounds: [number, number],
+ updateVizTime: DragStrategyUpdateTimeFn,
) {
- super(timeScale);
+ super(timeScale, updateVizTime);
}
onDrag(x: number) {
diff --git a/ui/src/frontend/drag_handle.ts b/ui/src/frontend/drag_handle.ts
deleted file mode 100644
index 81fae9b..0000000
--- a/ui/src/frontend/drag_handle.ts
+++ /dev/null
@@ -1,272 +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 {raf} from '../core/raf_scheduler';
-import {Button} from '../widgets/button';
-import {MenuItem, PopupMenu2} from '../widgets/menu';
-import {DEFAULT_DETAILS_CONTENT_HEIGHT} from './css_constants';
-import {DragGestureHandler} from '../base/drag_gesture_handler';
-import {globals} from './globals';
-import {DisposableStack} from '../base/disposable_stack';
-
-const DRAG_HANDLE_HEIGHT_PX = 28;
-const UP_ICON = 'keyboard_arrow_up';
-const DOWN_ICON = 'keyboard_arrow_down';
-
-export interface Tab {
- // Unique key for this tab, passed to callbacks.
- key: string;
-
- // Tab title to show on the tab handle.
- title: m.Children;
-
- // Whether to show a close button on the tab handle or not.
- // Default = false.
- hasCloseButton?: boolean;
-}
-
-export interface TabDropdownEntry {
- // Unique key for this tab dropdown entry.
- key: string;
-
- // Title to show on this entry.
- title: string;
-
- // Called when tab dropdown entry is clicked.
- onClick: () => void;
-
- // Whether this tab is checked or not
- checked: boolean;
-}
-
-export interface DragHandleAttrs {
- // The current height of the panel.
- height: number;
-
- // Called when the panel is dragged.
- resize: (height: number) => void;
-
- // A list of tabs to show in the tab bar.
- tabs: Tab[];
-
- // The key of the "current" tab.
- currentTabKey?: string;
-
- // A list of entries to show in the tab dropdown.
- // If undefined, the tab dropdown button will not be displayed.
- tabDropdownEntries?: TabDropdownEntry[];
-
- // Called when a tab is clicked.
- onTabClick: (key: string) => void;
-
- // Called when a tab is closed using its close button.
- onTabClose?: (key: string) => void;
-}
-
-export function getDefaultDetailsHeight() {
- // This needs to be a function instead of a const to ensure the CSS constants
- // have been initialized by the time we perform this calculation;
- return DRAG_HANDLE_HEIGHT_PX + DEFAULT_DETAILS_CONTENT_HEIGHT;
-}
-
-function getFullScreenHeight() {
- const page = document.querySelector('.page') as HTMLElement;
- if (page === null) {
- // Fall back to at least partially open.
- return getDefaultDetailsHeight();
- } else {
- return page.clientHeight;
- }
-}
-
-export class DragHandle implements m.ClassComponent<DragHandleAttrs> {
- private dragStartHeight = 0;
- private height = 0;
- private previousHeight = this.height;
- private resize: (height: number) => void = () => {};
- private isClosed = this.height <= 0;
- private isFullscreen = false;
- // We can't get real fullscreen height until the pan_and_zoom_handler
- // exists.
- private fullscreenHeight = 0;
- private trash = new DisposableStack();
-
- oncreate({dom, attrs}: m.CVnodeDOM<DragHandleAttrs>) {
- this.resize = attrs.resize;
- this.height = attrs.height;
- this.isClosed = this.height <= 0;
- this.fullscreenHeight = getFullScreenHeight();
- const elem = dom as HTMLElement;
- this.trash.use(
- new DragGestureHandler(
- elem,
- this.onDrag.bind(this),
- this.onDragStart.bind(this),
- this.onDragEnd.bind(this),
- ),
- );
- const cmd = globals.commandManager.registerCommand({
- id: 'perfetto.ToggleDrawer',
- name: 'Toggle drawer',
- defaultHotkey: 'Q',
- callback: () => {
- this.toggleVisibility();
- },
- });
- this.trash.use(cmd);
- }
-
- private toggleVisibility() {
- if (this.height === 0) {
- this.isClosed = false;
- if (this.previousHeight === 0) {
- this.previousHeight = getDefaultDetailsHeight();
- }
- this.resize(this.previousHeight);
- } else {
- this.isFullscreen = false;
- this.isClosed = true;
- this.previousHeight = this.height;
- this.resize(0);
- }
- raf.scheduleFullRedraw();
- }
-
- onupdate({attrs}: m.CVnodeDOM<DragHandleAttrs>) {
- this.resize = attrs.resize;
- this.height = attrs.height;
- this.isClosed = this.height <= 0;
- }
-
- onremove(_: m.CVnodeDOM<DragHandleAttrs>) {
- this.trash.dispose();
- }
-
- onDrag(_x: number, y: number) {
- const newHeight = Math.floor(
- this.dragStartHeight + DRAG_HANDLE_HEIGHT_PX / 2 - y,
- );
- this.isClosed = newHeight <= 0;
- this.isFullscreen = newHeight >= this.fullscreenHeight;
- this.resize(newHeight);
- raf.scheduleFullRedraw();
- }
-
- onDragStart(_x: number, _y: number) {
- this.dragStartHeight = this.height;
- }
-
- onDragEnd() {}
-
- view({attrs}: m.CVnode<DragHandleAttrs>) {
- const {
- tabDropdownEntries,
- currentTabKey,
- tabs,
- onTabClick,
- onTabClose = () => {},
- } = attrs;
-
- const icon = this.isClosed ? UP_ICON : DOWN_ICON;
- const title = this.isClosed ? 'Show panel' : 'Hide panel';
- const renderTab = (tab: Tab) => {
- const {key, hasCloseButton = false} = tab;
- const tag = currentTabKey === key ? '.tab[active]' : '.tab';
- return m(
- tag,
- {
- key,
- onclick: (event: Event) => {
- if (!event.defaultPrevented) {
- onTabClick(key);
- }
- },
- // Middle click to close
- onauxclick: (event: MouseEvent) => {
- if (!event.defaultPrevented) {
- onTabClose(key);
- }
- },
- },
- m('span.pf-tab-title', tab.title),
- hasCloseButton &&
- m(Button, {
- onclick: (event: Event) => {
- onTabClose(key);
- event.preventDefault();
- },
- compact: true,
- icon: 'close',
- }),
- );
- };
-
- return m(
- '.handle',
- m(
- '.buttons',
- tabDropdownEntries && this.renderTabDropdown(tabDropdownEntries),
- ),
- m('.tabs', tabs.map(renderTab)),
- m(
- '.buttons',
- m(Button, {
- onclick: () => {
- this.isClosed = false;
- this.isFullscreen = true;
- // Ensure fullscreenHeight is up to date.
- this.fullscreenHeight = getFullScreenHeight();
- this.resize(this.fullscreenHeight);
- raf.scheduleFullRedraw();
- },
- title: 'Open fullscreen',
- disabled: this.isFullscreen,
- icon: 'vertical_align_top',
- compact: true,
- }),
- m(Button, {
- onclick: () => {
- this.toggleVisibility();
- },
- title,
- icon,
- compact: true,
- }),
- ),
- );
- }
-
- private renderTabDropdown(entries: TabDropdownEntry[]) {
- return m(
- PopupMenu2,
- {
- trigger: m(Button, {
- compact: true,
- icon: 'more_vert',
- disabled: entries.length === 0,
- title: 'More Tabs',
- }),
- },
- entries.map((entry) => {
- return m(MenuItem, {
- key: entry.key,
- label: entry.title,
- onclick: () => entry.onClick(),
- icon: entry.checked ? 'check_box' : 'check_box_outline_blank',
- });
- }),
- );
- }
-}
diff --git a/ui/src/frontend/error_dialog.ts b/ui/src/frontend/error_dialog.ts
index b733463..99d4157 100644
--- a/ui/src/frontend/error_dialog.ts
+++ b/ui/src/frontend/error_dialog.ts
@@ -14,15 +14,13 @@
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';
import {globals} from './globals';
-import {Router} from './router';
import {AppImpl} from '../core/app_impl';
+import {Router} from '../core/router';
const MODAL_KEY = 'crash_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/file_drop_handler.ts b/ui/src/frontend/file_drop_handler.ts
index 1068af8..6d096be 100644
--- a/ui/src/frontend/file_drop_handler.ts
+++ b/ui/src/frontend/file_drop_handler.ts
@@ -12,8 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {Actions} from '../common/actions';
-import {globals} from './globals';
+import {AppImpl} from '../core/app_impl';
let lastDragTarget: EventTarget | null = null;
@@ -43,7 +42,7 @@
const file = evt.dataTransfer.files[0];
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
if (file) {
- globals.dispatch(Actions.openTraceFromFile({file}));
+ AppImpl.instance.openTraceFromFile(file);
}
}
evt.preventDefault();
diff --git a/ui/src/frontend/flow_events_renderer.ts b/ui/src/frontend/flow_events_renderer.ts
index 8a184c1..28ee34b 100644
--- a/ui/src/frontend/flow_events_renderer.ts
+++ b/ui/src/frontend/flow_events_renderer.ts
@@ -14,10 +14,8 @@
import {ArrowHeadStyle, drawBezierArrow} from '../base/canvas/bezier_arrow';
import {Size2D, Point2D, HorizontalBounds} from '../base/geom';
-import {Optional} from '../base/utils';
import {ALL_CATEGORIES, getFlowCategories} from './flow_events_panel';
-import {globals} from './globals';
-import {Flow} from 'src/core/flow_types';
+import {Flow} from '../core/flow_types';
import {RenderedPanelInfo} from './panel_container';
import {TimeScale} from '../base/time_scale';
import {TrackNode} from '../public/workspace';
@@ -107,11 +105,11 @@
}
const highlighted =
- flow.end.sliceId === globals.state.highlightedSliceId ||
- flow.begin.sliceId === globals.state.highlightedSliceId;
+ flow.end.sliceId === trace.timeline.highlightedSliceId ||
+ flow.begin.sliceId === trace.timeline.highlightedSliceId;
const focused =
- flow.id === globals.trace.flows.focusedFlowIdLeft ||
- flow.id === globals.trace.flows.focusedFlowIdRight;
+ flow.id === trace.flows.focusedFlowIdLeft ||
+ flow.id === trace.flows.focusedFlowIdRight;
let intensity = DEFAULT_FLOW_INTENSITY;
let width = DEFAULT_FLOW_WIDTH;
@@ -139,7 +137,7 @@
trackId: number,
depth: number,
x: number,
- ): Optional<VerticalEdgeOrPoint> => {
+ ): VerticalEdgeOrPoint | undefined => {
const track = sqlTrackIdToTrack.get(trackId);
if (!track) {
return undefined;
@@ -185,17 +183,17 @@
};
// Render the connected flows
- globals.trace.flows.connectedFlows.forEach((flow) => {
+ trace.flows.connectedFlows.forEach((flow) => {
drawFlow(flow, CONNECTED_FLOW_HUE);
});
// Render the selected flows
- globals.trace.flows.selectedFlows.forEach((flow) => {
+ trace.flows.selectedFlows.forEach((flow) => {
const categories = getFlowCategories(flow);
for (const cat of categories) {
if (
- globals.trace.flows.visibleCategories.get(cat) ||
- globals.trace.flows.visibleCategories.get(ALL_CATEGORIES)
+ trace.flows.visibleCategories.get(cat) ||
+ trace.flows.visibleCategories.get(ALL_CATEGORIES)
) {
drawFlow(flow, SELECTED_FLOW_HUE);
break;
diff --git a/ui/src/frontend/generic_slice_details_tab.ts b/ui/src/frontend/generic_slice_details_tab.ts
index 289e217..5595981 100644
--- a/ui/src/frontend/generic_slice_details_tab.ts
+++ b/ui/src/frontend/generic_slice_details_tab.ts
@@ -13,8 +13,7 @@
// limitations under the License.
import m from 'mithril';
-import {GenericSliceDetailsTabConfig} from '../public/details_panel';
-import {raf} from '../core/raf_scheduler';
+import {TrackEventDetailsPanel} from '../public/details_panel';
import {ColumnType} from '../trace_processor/query_result';
import {sqlValueToReadableString} from '../trace_processor/sql_utils';
import {DetailsShell} from '../widgets/details_shell';
@@ -22,53 +21,49 @@
import {Section} from '../widgets/section';
import {SqlRef} from '../widgets/sql_ref';
import {dictToTree, Tree, TreeNode} from '../widgets/tree';
-import {BottomTab, NewBottomTabArgs} from '../public/lib/bottom_tab';
+import {Trace} from '../public/trace';
-export {
- ColumnConfig,
- Columns,
- GenericSliceDetailsTabConfig,
- GenericSliceDetailsTabConfigBase,
-} from '../public/details_panel';
+export interface ColumnConfig {
+ readonly displayName?: string;
+}
+
+export type Columns = {
+ readonly [columnName: string]: ColumnConfig;
+};
// A details tab, which fetches slice-like object from a given SQL table by id
// and renders it according to the provided config, specifying which columns
// need to be rendered and how.
-export class GenericSliceDetailsTab extends BottomTab<GenericSliceDetailsTabConfig> {
- static readonly kind = 'dev.perfetto.GenericSliceDetailsTab';
+export class GenericSliceDetailsTab implements TrackEventDetailsPanel {
+ private data?: {[key: string]: ColumnType};
- data: {[key: string]: ColumnType} | undefined;
+ constructor(
+ private readonly trace: Trace,
+ private readonly sqlTableName: string,
+ private readonly id: number,
+ private readonly title: string,
+ private readonly columns?: Columns,
+ ) {}
- static create(
- args: NewBottomTabArgs<GenericSliceDetailsTabConfig>,
- ): GenericSliceDetailsTab {
- return new GenericSliceDetailsTab(args);
+ async load() {
+ const result = await this.trace.engine.query(
+ `select * from ${this.sqlTableName} where id = ${this.id}`,
+ );
+
+ this.data = result.firstRow({});
}
- constructor(args: NewBottomTabArgs<GenericSliceDetailsTabConfig>) {
- super(args);
-
- this.engine
- .query(
- `select * from ${this.config.sqlTableName} where id = ${this.config.id}`,
- )
- .then((queryResult) => {
- this.data = queryResult.firstRow({});
- raf.scheduleFullRedraw();
- });
- }
-
- viewTab() {
- if (this.data === undefined) {
+ render() {
+ if (!this.data) {
return m('h2', 'Loading');
}
const args: {[key: string]: m.Child} = {};
- if (this.config.columns !== undefined) {
- for (const key of Object.keys(this.config.columns)) {
+ if (this.columns !== undefined) {
+ for (const key of Object.keys(this.columns)) {
let argKey = key;
- if (this.config.columns[key].displayName !== undefined) {
- argKey = this.config.columns[key].displayName!;
+ if (this.columns[key].displayName !== undefined) {
+ argKey = this.columns[key].displayName!;
}
args[argKey] = sqlValueToReadableString(this.data[key]);
}
@@ -83,7 +78,7 @@
return m(
DetailsShell,
{
- title: this.config.title,
+ title: this.title,
},
m(
GridLayout,
@@ -95,8 +90,8 @@
m(TreeNode, {
left: 'SQL ID',
right: m(SqlRef, {
- table: this.config.sqlTableName,
- id: this.config.id,
+ table: this.sqlTableName,
+ id: this.id,
}),
}),
]),
@@ -104,12 +99,4 @@
),
);
}
-
- getTitle(): string {
- return this.config.title;
- }
-
- isLoading() {
- return this.data === undefined;
- }
}
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index ecd716f..f5be8b4 100644
--- a/ui/src/frontend/globals.ts
+++ b/ui/src/frontend/globals.ts
@@ -12,313 +12,22 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {assertExists} from '../base/logging';
-import {createStore, Store} from '../base/store';
-import {time} from '../base/time';
-import {Actions, DeferredAction} from '../common/actions';
-import {CommandManagerImpl} from '../core/command_manager';
-import {
- ConversionJobName,
- ConversionJobStatus,
-} from '../common/conversion_jobs';
-import {createEmptyState} from '../common/empty_state';
-import {EngineConfig, State} from '../common/state';
-import {setPerfHooks} from '../core/perf';
import {raf} from '../core/raf_scheduler';
-import {ServiceWorkerController} from './service_worker_controller';
-import {EngineBase} from '../trace_processor/engine';
-import {HttpRpcState} from '../trace_processor/http_rpc_engine';
-import type {Analytics} from './analytics';
-import {SerializedAppState} from '../common/state_serialization_schema';
-import {getServingRoot} from '../base/http_utils';
-import {Workspace} from '../public/workspace';
-import {ratelimit} from './rate_limiters';
-import {setRerunControllersFunction, TraceImpl} from '../core/trace_impl';
import {AppImpl} from '../core/app_impl';
-import {createFakeTraceImpl} from '../common/fake_trace_impl';
-
-type DispatchMultiple = (actions: DeferredAction[]) => void;
-type TrackDataStore = Map<string, {}>;
-
-export interface QuantizedLoad {
- start: time;
- end: time;
- load: number;
-}
-type OverviewStore = Map<string, QuantizedLoad[]>;
-
-export interface ThreadDesc {
- utid: number;
- tid: number;
- threadName: string;
- pid?: number;
- procName?: string;
- cmdline?: string;
-}
-type ThreadMap = Map<number, ThreadDesc>;
-
-interface SqlModule {
- readonly name: string;
- readonly sql: string;
-}
-
-interface SqlPackage {
- readonly name: string;
- readonly modules: SqlModule[];
-}
/**
* Global accessors for state/dispatch in the frontend.
*/
class Globals {
- readonly root = getServingRoot();
-
- private _trace: TraceImpl;
- private _testing = false;
- private _dispatchMultiple?: DispatchMultiple = undefined;
- private _store = createStore<State>(createEmptyState());
- private _serviceWorkerController?: ServiceWorkerController = undefined;
- private _logging?: Analytics = undefined;
+ // This is normally undefined is injected in via is_internal_user.js.
+ // WARNING: do not change/rename/move without considering impact on the
+ // internal_user script.
private _isInternalUser: boolean | undefined = undefined;
- // TODO(hjd): Unify trackDataStore, queryResults, overviewStore, threads.
- private _trackDataStore?: TrackDataStore = undefined;
- private _overviewStore?: OverviewStore = undefined;
- private _threadMap?: ThreadMap = undefined;
- private _bufferUsage?: number = undefined;
- private _recordingLog?: string = undefined;
- private _metricError?: string = undefined;
- private _jobStatus?: Map<ConversionJobName, ConversionJobStatus> = undefined;
- private _embeddedMode?: boolean = undefined;
- private _hideSidebar?: boolean = undefined;
- private _hasFtrace: boolean = false;
- private _currentTraceId = '';
- httpRpcState: HttpRpcState = {connected: false};
- showPanningHint = false;
- permalinkHash?: string;
- extraSqlPackages: SqlPackage[] = [];
-
- get workspace(): Workspace {
- return this._trace?.workspace;
- }
-
- // This is the app's equivalent of a plugin's onTraceLoad() function.
- // TODO(primiano): right now this is used to inject the TracImpl class into
- // globals, so it can hop consistently all its accessors to it. Once globals
- // is gone, figure out what to do with createSearchOverviewTrack().
- async onTraceLoad(trace: TraceImpl): Promise<void> {
- this._trace = trace;
-
- this._trace.timeline.retriggerControllersOnChange = () =>
- ratelimit(() => this.store.edit(() => {}), 50);
-
- this._currentTraceId = trace.engine.engineId;
- }
-
- // Used for permalink load by trace_controller.ts.
- restoreAppStateAfterTraceLoad?: SerializedAppState;
-
- // TODO(hjd): Remove once we no longer need to update UUID on redraw.
- private _publishRedraw?: () => void = undefined;
-
- engines = new Map<string, EngineBase>();
-
- constructor() {
- // TODO(primiano): we do this to avoid making all our members possibly
- // undefined, which would cause a drama of if (!=undefined) all over the
- // code. This is not pretty, but this entire file is going to be nuked from
- // orbit soon.
- this._trace = createFakeTraceImpl();
-
- // We just want an empty instance of TraceImpl but don't want to mark it
- // as the current trace, otherwise this will trigger the plugins' OnLoad().
- AppImpl.instance.closeCurrentTrace();
-
- setRerunControllersFunction(() =>
- this.dispatch(Actions.runControllers({})),
- );
- }
-
- initialize(
- dispatchMultiple: DispatchMultiple,
- initAnalytics: () => Analytics,
- ) {
- this._dispatchMultiple = dispatchMultiple;
-
- setPerfHooks(
- () => this.state.perfDebug,
- () => this.dispatch(Actions.togglePerfDebug({})),
- );
-
- this._serviceWorkerController = new ServiceWorkerController(
- getServingRoot(),
- );
- this._testing =
- /* eslint-disable @typescript-eslint/strict-boolean-expressions */
- self.location && self.location.search.indexOf('testing=1') >= 0;
- /* eslint-enable */
-
- // TODO(stevegolton): This is a mess. We should just inject this object in,
- // instead of passing in a function. The only reason this is done like this
- // is because the current implementation of initAnalytics depends on the
- // state of globals.testing, so this needs to be set before we run the
- // function.
- this._logging = initAnalytics();
-
- // TODO(hjd): Unify trackDataStore, queryResults, overviewStore, threads.
- // TODO(primiano): for posterity: these assignments below are completely
- // pointless and could be done as member variable initializers, as
- // initialize() is only called ever once. (But then i'm going to kill this
- // entire file soon).
- this._trackDataStore = new Map<string, {}>();
- this._overviewStore = new Map<string, QuantizedLoad[]>();
- this._threadMap = new Map<number, ThreadDesc>();
- this.engines.clear();
- }
-
- get publishRedraw(): () => void {
- return this._publishRedraw || (() => {});
- }
-
- set publishRedraw(f: () => void) {
- this._publishRedraw = f;
- }
-
- get state(): State {
- return this._store.state;
- }
-
- get store(): Store<State> {
- return this._store;
- }
-
- dispatch(action: DeferredAction) {
- this.dispatchMultiple([action]);
- }
-
- dispatchMultiple(actions: DeferredAction[]) {
- assertExists(this._dispatchMultiple)(actions);
- }
-
- get trace() {
- return this._trace;
- }
-
- get timeline() {
- return this._trace.timeline;
- }
-
- get searchManager() {
- return this._trace.search;
- }
-
- get logging() {
- return assertExists(this._logging);
- }
-
- get serviceWorkerController() {
- return assertExists(this._serviceWorkerController);
- }
-
- // TODO(hjd): Unify trackDataStore, queryResults, overviewStore, threads.
-
- // TODO(primiano): this should be really renamed to traceInfo, but doing so
- // creates extra churn. Not worth it as we are going to get rid of this file
- // soon.
- get traceContext() {
- return this._trace.traceInfo;
- }
-
- get overviewStore(): OverviewStore {
- return assertExists(this._overviewStore);
- }
-
- get trackDataStore(): TrackDataStore {
- return assertExists(this._trackDataStore);
- }
-
- get threads() {
- return assertExists(this._threadMap);
- }
-
- get metricError() {
- return this._metricError;
- }
-
- setMetricError(arg: string) {
- this._metricError = arg;
- }
-
- get bufferUsage() {
- return this._bufferUsage;
- }
-
- get recordingLog() {
- return this._recordingLog;
- }
-
- set hasFtrace(value: boolean) {
- this._hasFtrace = value;
- }
-
- get hasFtrace(): boolean {
- return this._hasFtrace;
- }
-
- get currentTraceId() {
- return this._currentTraceId;
- }
-
- getConversionJobStatus(name: ConversionJobName): ConversionJobStatus {
- return this.getJobStatusMap().get(name) ?? ConversionJobStatus.NotRunning;
- }
-
- setConversionJobStatus(name: ConversionJobName, status: ConversionJobStatus) {
- const map = this.getJobStatusMap();
- if (status === ConversionJobStatus.NotRunning) {
- map.delete(name);
- } else {
- map.set(name, status);
- }
- }
-
- private getJobStatusMap(): Map<ConversionJobName, ConversionJobStatus> {
- if (!this._jobStatus) {
- this._jobStatus = new Map();
- }
- return this._jobStatus;
- }
-
- get embeddedMode(): boolean {
- return !!this._embeddedMode;
- }
-
- set embeddedMode(value: boolean) {
- this._embeddedMode = value;
- }
-
- get hideSidebar(): boolean {
- return !!this._hideSidebar;
- }
-
- set hideSidebar(value: boolean) {
- this._hideSidebar = value;
- }
-
- setBufferUsage(bufferUsage: number) {
- this._bufferUsage = bufferUsage;
- }
-
- setTrackData(id: string, data: {}) {
- this.trackDataStore.set(id, data);
- }
-
- setRecordingLog(recordingLog: string) {
- this._recordingLog = recordingLog;
- }
-
- getCurrentEngine(): EngineConfig | undefined {
- return this.state.engine;
+ // WARNING: do not change/rename/move without considering impact on the
+ // internal_user script.
+ get extraSqlPackages() {
+ return AppImpl.instance.extraSqlPackages;
}
// This variable is set by the is_internal_user.js script if the user is a
@@ -340,10 +49,6 @@
raf.scheduleFullRedraw();
}
- get testing() {
- return this._testing;
- }
-
// Used when switching to the legacy TraceViewer UI.
// Most resources are cleaned up by replacing the current |window| object,
// however pending RAFs and workers seem to outlive the |window| and need to
@@ -351,26 +56,6 @@
shutdown() {
raf.shutdown();
}
-
- get commandManager(): CommandManagerImpl {
- return AppImpl.instance.commands;
- }
-
- get tabManager() {
- return this._trace.tabs;
- }
-
- get trackManager() {
- return this._trace.tracks;
- }
-
- get selectionManager() {
- return this._trace.selection;
- }
-
- get noteManager() {
- return this._trace.notes;
- }
}
export const globals = new Globals();
diff --git a/ui/src/frontend/help_modal.ts b/ui/src/frontend/help_modal.ts
index 603d6df..819f271 100644
--- a/ui/src/frontend/help_modal.ts
+++ b/ui/src/frontend/help_modal.ts
@@ -13,10 +13,8 @@
// limitations under the License.
import m from 'mithril';
-import {raf} from '../core/raf_scheduler';
import {showModal} from '../widgets/modal';
import {Spinner} from '../widgets/spinner';
-import {globals} from './globals';
import {
KeyboardLayoutMap,
nativeKeyboardLayoutMap,
@@ -25,10 +23,15 @@
import {KeyMapping} from './pan_and_zoom_handler';
import {HotkeyGlyphs} from '../widgets/hotkey_glyphs';
import {assertExists} from '../base/logging';
+import {AppImpl} from '../core/app_impl';
export function toggleHelp() {
- globals.logging.logEvent('User Actions', 'Show help');
- showHelp();
+ AppImpl.instance.analytics.logEvent('User Actions', 'Show help');
+ showModal({
+ title: 'Perfetto Help',
+ content: () => m(KeyMappingsHelp),
+ buttons: [],
+ });
}
function keycap(glyph: m.Children): m.Children {
@@ -51,7 +54,7 @@
nativeKeyboardLayoutMap()
.then((keyMap: KeyboardLayoutMap) => {
this.keyMap = keyMap;
- raf.scheduleFullRedraw();
+ AppImpl.instance.scheduleFullRedraw();
})
.catch((e) => {
if (
@@ -66,7 +69,7 @@
// The alternative would be to show key mappings for all keyboard
// layouts which is not feasible.
this.keyMap = new EnglishQwertyKeyboardLayoutMap();
- raf.scheduleFullRedraw();
+ AppImpl.instance.scheduleFullRedraw();
} else {
// Something unexpected happened. Either the browser doesn't conform
// to the keyboard API spec, or the keyboard API spec has changed!
@@ -75,32 +78,7 @@
});
}
- view(_: m.Vnode): m.Children {
- const queryPageInstructions = globals.hideSidebar
- ? []
- : [
- m('h2', 'Making SQL queries from the query page'),
- m(
- 'table',
- m(
- 'tr',
- m('td', keycap('Ctrl'), ' + ', keycap('Enter')),
- m('td', 'Execute query'),
- ),
- m(
- 'tr',
- m(
- 'td',
- keycap('Ctrl'),
- ' + ',
- keycap('Enter'),
- ' (with selection)',
- ),
- m('td', 'Execute selection'),
- ),
- ),
- ];
-
+ view(): m.Children {
return m(
'.help',
m('h2', 'Navigation'),
@@ -163,11 +141,24 @@
),
),
),
- ...queryPageInstructions,
+ m('h2', 'Making SQL queries from the query page'),
+ m(
+ 'table',
+ m(
+ 'tr',
+ m('td', keycap('Ctrl'), ' + ', keycap('Enter')),
+ m('td', 'Execute query'),
+ ),
+ m(
+ 'tr',
+ m('td', keycap('Ctrl'), ' + ', keycap('Enter'), ' (with selection)'),
+ m('td', 'Execute selection'),
+ ),
+ ),
m('h2', 'Command Hotkeys'),
m(
'table',
- globals.commandManager.commands
+ AppImpl.instance.commands.commands
.filter(({defaultHotkey}) => defaultHotkey)
.sort((a, b) => a.name.localeCompare(b.name))
.map(({defaultHotkey, name}) => {
@@ -189,11 +180,3 @@
}
}
}
-
-function showHelp() {
- showModal({
- title: 'Perfetto Help',
- content: () => m(KeyMappingsHelp),
- buttons: [],
- });
-}
diff --git a/ui/src/frontend/home_page.ts b/ui/src/frontend/home_page.ts
index 776f3c7..e38f74c 100644
--- a/ui/src/frontend/home_page.ts
+++ b/ui/src/frontend/home_page.ts
@@ -13,11 +13,11 @@
// limitations under the License.
import m from 'mithril';
-import {channelChanged, getNextChannel, setChannel} from '../common/channels';
+import {channelChanged, getNextChannel, setChannel} from '../core/channels';
import {Anchor} from '../widgets/anchor';
import {HotkeyGlyphs} from '../widgets/hotkey_glyphs';
-import {globals} from './globals';
-import {PageAttrs} from './pages';
+import {PageAttrs} from '../public/page';
+import {assetSrc} from '../base/assets';
export class Hints implements m.ClassComponent {
view() {
@@ -74,7 +74,7 @@
'.home-page-center',
m(
'.home-page-title',
- m(`img.logo[src=${globals.root}assets/logo-3d.png]`),
+ m(`img.logo[src=${assetSrc('assets/logo-3d.png')}]`),
'Perfetto',
),
m(Hints),
diff --git a/ui/src/frontend/index.ts b/ui/src/frontend/index.ts
index 8cb0d25..4c87e4d 100644
--- a/ui/src/frontend/index.ts
+++ b/ui/src/frontend/index.ts
@@ -15,20 +15,12 @@
// Keep this import first.
import '../base/disposable_polyfill';
import '../base/static_initializers';
-import '../gen/all_plugins';
-import '../gen/all_core_plugins';
-import {Draft} from 'immer';
+import NON_CORE_PLUGINS from '../gen/all_plugins';
+import CORE_PLUGINS from '../gen/all_core_plugins';
import m from 'mithril';
import {defer} from '../base/deferred';
import {addErrorHandler, reportError} from '../base/logging';
-import {Store} from '../base/store';
-import {Actions, DeferredAction, StateActions} from '../common/actions';
-import {traceEvent} from '../common/metatracing';
-import {pluginManager} from '../common/plugins';
-import {State} from '../common/state';
-import {initController, runControllers} from '../controller';
-import {isGetCategoriesResponse} from '../controller/chrome_proxy_record_controller';
-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';
@@ -38,34 +30,27 @@
import {registerDebugGlobals} from './debug';
import {maybeShowErrorDialog} from './error_dialog';
import {installFileDropHandler} from './file_drop_handler';
-import {FlagsPage} from './flags_page';
import {globals} from './globals';
import {HomePage} from './home_page';
-import {InsightsPage} from './insights_page';
-import {MetricsPage} from './metrics_page';
-import {PluginsPage} from './plugins_page';
import {postMessageHandler} from './post_message_handler';
-import {QueryPage} from './query_page';
-import {RecordPage, updateAvailableAdbDevices} from './record_page';
-import {RecordPageV2} from './record_page_v2';
-import {Route, Router} from './router';
+import {Route, Router} from '../core/router';
import {CheckHttpRpcConnection} from './rpc_http_dialog';
-import {TraceInfoPage} from './trace_info_page';
import {maybeOpenTraceFromRoute} from './trace_url_handler';
import {ViewerPage} from './viewer_page';
-import {VizPage} from './viz_page';
-import {WidgetsPage} from './widgets_page';
import {HttpRpcEngine} from '../trace_processor/http_rpc_engine';
import {showModal} from '../widgets/modal';
-import {initAnalytics} from './analytics';
import {IdleDetector} from './idle_detector';
import {IdleDetectorWindow} from './idle_detector_interface';
-import {pageWithTrace} from './pages';
import {AppImpl} from '../core/app_impl';
-import {setAddSqlTableTabImplFunction} from './sql_table_tab_interface';
-import {addSqlTableTabImpl} from './sql_table_tab';
-
-const EXTENSION_ID = 'lfmkphfpdbjijhpomgecfikhfohaoine';
+import {addSqlTableTab} from './sql_table_tab';
+import {configureExtensions} from '../public/lib/extensions';
+import {
+ addDebugCounterTrack,
+ addDebugSliceTrack,
+} from '../public/lib/tracks/debug_tracks';
+import {addVisualizedArgTracks} from './visualized_args_tracks';
+import {addQueryResultsTab} from '../public/lib/query_table/query_result_tab';
+import {assetSrc, initAssets} from '../base/assets';
const CSP_WS_PERMISSIVE_PORT = featureFlags.register({
id: 'cspAllowAnyWebsocketPort',
@@ -77,28 +62,19 @@
defaultValue: false,
});
-function setExtensionAvailability(available: boolean) {
- globals.dispatch(
- Actions.setExtensionAvailable({
- available,
- }),
- );
-}
-
function routeChange(route: Route) {
- raf.scheduleFullRedraw();
- maybeOpenTraceFromRoute(route);
- if (route.fragment) {
- // This needs to happen after the next redraw call. It's not enough
- // to use setTimeout(..., 0); since that may occur before the
- // redraw scheduled above.
- raf.addPendingCallback(() => {
+ raf.scheduleFullRedraw(() => {
+ if (route.fragment) {
+ // This needs to happen after the next redraw call. It's not enough
+ // to use setTimeout(..., 0); since that may occur before the
+ // redraw scheduled above.
const e = document.getElementById(route.fragment);
if (e) {
e.scrollIntoView();
}
- });
- }
+ }
+ });
+ maybeOpenTraceFromRoute(route);
}
function setupContentSecurityPolicy() {
@@ -163,104 +139,55 @@
document.head.appendChild(meta);
}
-function setupExtentionPort(extensionLocalChannel: MessageChannel) {
- // We proxy messages between the extension and the controller because the
- // controller's worker can't access chrome.runtime.
- const extensionPort =
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
- window.chrome && chrome.runtime
- ? chrome.runtime.connect(EXTENSION_ID)
- : undefined;
-
- setExtensionAvailability(extensionPort !== undefined);
-
- if (extensionPort) {
- // Send messages to keep-alive the extension port.
- const interval = setInterval(() => {
- extensionPort.postMessage({
- method: 'ExtensionVersion',
- });
- }, 25000);
- extensionPort.onDisconnect.addListener((_) => {
- setExtensionAvailability(false);
- clearInterval(interval);
- void chrome.runtime.lastError; // Needed to not receive an error log.
- });
- // This forwards the messages from the extension to the controller.
- extensionPort.onMessage.addListener(
- (message: object, _port: chrome.runtime.Port) => {
- if (isGetCategoriesResponse(message)) {
- globals.dispatch(Actions.setChromeCategories(message));
- return;
- }
- extensionLocalChannel.port2.postMessage(message);
- },
- );
- }
-
- // This forwards the messages from the controller to the extension
- extensionLocalChannel.port2.onmessage = ({data}) => {
- if (extensionPort) extensionPort.postMessage(data);
- };
-}
-
function main() {
+ // Setup content security policy before anything else.
+ setupContentSecurityPolicy();
+ initAssets();
+ AppImpl.initialize({
+ initialRouteArgs: Router.parseUrl(window.location.href).args,
+ });
+
// Wire up raf for widgets.
setScheduleFullRedraw(() => raf.scheduleFullRedraw());
- setupContentSecurityPolicy();
-
// Load the css. The load is asynchronous and the CSS is not ready by the time
// appendChild returns.
const cssLoadPromise = defer<void>();
const css = document.createElement('link');
css.rel = 'stylesheet';
- css.href = globals.root + 'perfetto.css';
+ css.href = assetSrc('perfetto.css');
css.onload = () => cssLoadPromise.resolve();
css.onerror = (err) => cssLoadPromise.reject(err);
const favicon = document.head.querySelector('#favicon');
if (favicon instanceof HTMLLinkElement) {
- favicon.href = globals.root + 'assets/favicon.png';
+ favicon.href = assetSrc('assets/favicon.png');
}
// Load the script to detect if this is a Googler (see comments on globals.ts)
// and initialize GA after that (or after a timeout if something goes wrong).
+ function initAnalyticsOnScriptLoad() {
+ AppImpl.instance.analytics.initialize(globals.isInternalUser);
+ }
const script = document.createElement('script');
script.src =
'https://storage.cloud.google.com/perfetto-ui-internal/is_internal_user.js';
script.async = true;
- script.onerror = () => globals.logging.initialize();
- script.onload = () => globals.logging.initialize();
- setTimeout(() => globals.logging.initialize(), 5000);
+ script.onerror = () => initAnalyticsOnScriptLoad();
+ script.onload = () => initAnalyticsOnScriptLoad();
+ setTimeout(() => initAnalyticsOnScriptLoad(), 5000);
document.head.append(script, css);
// Route errors to both the UI bugreport dialog and Analytics (if enabled).
addErrorHandler(maybeShowErrorDialog);
- addErrorHandler((e) => globals.logging.logError(e));
+ addErrorHandler((e) => AppImpl.instance.analytics.logError(e));
// Add Error handlers for JS error and for uncaught exceptions in promises.
window.addEventListener('error', (e) => reportError(e));
window.addEventListener('unhandledrejection', (e) => reportError(e));
- const extensionLocalChannel = new MessageChannel();
-
- initWasm(globals.root);
- initController(extensionLocalChannel.port1);
-
- // These need to be set before globals.initialize.
- const route = Router.parseUrl(window.location.href);
- globals.embeddedMode = route.args.mode === 'embedded';
- globals.hideSidebar = route.args.hideSidebar === true;
-
- globals.initialize(stateActionDispatcher, initAnalytics);
-
- globals.serviceWorkerController.install();
-
- globals.store.subscribe(scheduleRafAndRunControllersOnStateChange);
- globals.publishRedraw = () => raf.scheduleFullRedraw();
-
- setupExtentionPort(extensionLocalChannel);
+ initWasm();
+ AppImpl.instance.serviceWorkerController.install();
// Put debug variables in the global scope for better debugging.
registerDebugGlobals();
@@ -276,7 +203,7 @@
cssLoadPromise.then(() => onCssLoaded());
- if (globals.testing) {
+ if (AppImpl.instance.testingMode) {
document.body.classList.add('testing');
}
@@ -291,48 +218,29 @@
// And replace it with the root <main> element which will be used by mithril.
document.body.innerHTML = '';
- const router = new Router({
- '/': HomePage,
- '/flags': FlagsPage,
- '/info': pageWithTrace(TraceInfoPage),
- '/insights': pageWithTrace(InsightsPage),
- '/metrics': pageWithTrace(MetricsPage),
- '/plugins': PluginsPage,
- '/query': pageWithTrace(QueryPage),
- '/record': RECORDING_V2_FLAG.get() ? RecordPageV2 : RecordPage,
- '/viewer': pageWithTrace(ViewerPage),
- '/viz': pageWithTrace(VizPage),
- '/widgets': WidgetsPage,
- });
+ const pages = AppImpl.instance.pages;
+ const traceless = true;
+ pages.registerPage({route: '/', traceless, page: HomePage});
+ pages.registerPage({route: '/viewer', page: ViewerPage});
+ const router = new Router();
router.onRouteChanged = routeChange;
- raf.domRedraw = () => {
- m.render(document.body, m(UiMain, router.resolve()));
- };
+ raf.initialize(() =>
+ m.render(
+ document.body,
+ m(UiMain, pages.renderPageForCurrentRoute(AppImpl.instance.trace)),
+ ),
+ );
if (
(location.origin.startsWith('http://localhost:') ||
location.origin.startsWith('http://127.0.0.1:')) &&
- !globals.embeddedMode &&
- !globals.testing
+ !AppImpl.instance.embeddedMode &&
+ !AppImpl.instance.testingMode
) {
initLiveReload();
}
- if (!RECORDING_V2_FLAG.get()) {
- updateAvailableAdbDevices();
- try {
- navigator.usb.addEventListener('connect', () =>
- updateAvailableAdbDevices(),
- );
- navigator.usb.addEventListener('disconnect', () =>
- updateAvailableAdbDevices(),
- );
- } catch (e) {
- console.error('WebUSB API not supported');
- }
- }
-
// Will update the chip on the sidebar footer that notifies that the RPC is
// connected. Has no effect on the controller (which will repeat this check
// before creating a new engine).
@@ -342,19 +250,7 @@
maybeChangeRpcPortFromFragment();
CheckHttpRpcConnection().then(() => {
const route = Router.parseUrl(window.location.href);
- globals.dispatch(
- Actions.maybeSetPendingDeeplink({
- ts: route.args.ts,
- tid: route.args.tid,
- dur: route.args.dur,
- pid: route.args.pid,
- query: route.args.query,
- visStart: route.args.visStart,
- visEnd: route.args.visEnd,
- }),
- );
-
- if (!globals.embeddedMode) {
+ if (!AppImpl.instance.embeddedMode) {
installFileDropHandler();
}
@@ -374,22 +270,18 @@
});
// Force one initial render to get everything in place
- m.render(document.body, m(UiMain, router.resolve()));
+ m.render(
+ document.body,
+ m(UiMain, AppImpl.instance.pages.renderPageForCurrentRoute(undefined)),
+ );
- // TODO(primiano): this injection is to break a cirular dependency. See
- // comment in sql_table_tab_interface.ts. Remove once we add an extension
- // point for context menus.
- setAddSqlTableTabImplFunction(addSqlTableTabImpl);
-
- // Initialize plugins, now that we are ready to go
- pluginManager.initialize();
-
+ // Initialize plugins, now that we are ready to go.
+ const pluginManager = AppImpl.instance.plugins;
+ CORE_PLUGINS.forEach((p) => pluginManager.registerPlugin(p));
+ NON_CORE_PLUGINS.forEach((p) => pluginManager.registerPlugin(p));
const route = Router.parseUrl(window.location.href);
- for (const pluginId of (route.args.enablePlugins ?? '').split(',')) {
- if (pluginManager.hasPlugin(pluginId)) {
- pluginManager.activatePlugin(pluginId);
- }
- }
+ const overrides = (route.args.enablePlugins ?? '').split(',');
+ pluginManager.activatePlugins(overrides);
}
// If the URL is /#!?rpc_port=1234, change the default RPC port.
@@ -424,28 +316,15 @@
}
}
-function stateActionDispatcher(actions: DeferredAction[]) {
- const edits = actions.map((action) => {
- return traceEvent(`action.${action.type}`, () => {
- return (draft: Draft<State>) => {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- (StateActions as any)[action.type](draft, action.args);
- };
- });
- });
- globals.store.edit(edits);
-}
-
-function scheduleRafAndRunControllersOnStateChange(
- store: Store<State>,
- oldState: State,
-) {
- // Only redraw if something actually changed
- if (oldState !== store.state) {
- raf.scheduleFullRedraw();
- }
- // Run in a separate task to avoid avoid reentry.
- setTimeout(runControllers, 0);
-}
+// TODO(primiano): this injection is to break a cirular dependency. See
+// comment in sql_table_tab_interface.ts. Remove once we add an extension
+// point for context menus.
+configureExtensions({
+ addDebugCounterTrack,
+ addDebugSliceTrack,
+ addVisualizedArgTracks,
+ addSqlTableTab,
+ addQueryResultsTab,
+});
main();
diff --git a/ui/src/frontend/legacy_trace_viewer.ts b/ui/src/frontend/legacy_trace_viewer.ts
index 3246713..25af38b 100644
--- a/ui/src/frontend/legacy_trace_viewer.ts
+++ b/ui/src/frontend/legacy_trace_viewer.ts
@@ -17,9 +17,9 @@
import {assertTrue} from '../base/logging';
import {isString} from '../base/object_utils';
import {showModal} from '../widgets/modal';
-import {globals} from './globals';
import {utf8Decode} from '../base/string_utils';
import {convertToJson} from './trace_converter';
+import {assetSrc} from '../base/assets';
const CTRACE_HEADER = 'TRACE:\n';
@@ -142,7 +142,7 @@
// The location.pathname mangling is to make this code work also when hosted
// in a non-root sub-directory, for the case of CI artifacts.
- const catapultUrl = globals.root + 'assets/catapult_trace_viewer.html';
+ const catapultUrl = assetSrc('assets/catapult_trace_viewer.html');
const newWin = window.open(catapultUrl);
if (newWin) {
// Popup succeedeed.
@@ -172,16 +172,21 @@
});
}
-export function openInOldUIWithSizeCheck(trace: Blob) {
+export async function openInOldUIWithSizeCheck(trace: Blob): Promise<void> {
// Perfetto traces smaller than 50mb can be safely opened in the legacy UI.
if (trace.size < 1024 * 1024 * 50) {
- convertToJson(trace, openBufferWithLegacyTraceViewer);
- return;
+ return await convertToJson(trace, openBufferWithLegacyTraceViewer);
}
// Give the user the option to truncate larger perfetto traces.
const size = Math.round(trace.size / (1024 * 1024));
- showModal({
+
+ // If the user presses one of the buttons below, remember the promise that
+ // they trigger, so we await for it before returning.
+ let nextPromise: Promise<void> | undefined;
+ const setNextPromise = (p: Promise<void>) => (nextPromise = p);
+
+ await showModal({
title: 'Legacy UI may fail to open this trace',
content: m(
'div',
@@ -206,30 +211,38 @@
buttons: [
{
text: 'Open full trace (not recommended)',
- action: () => convertToJson(trace, openBufferWithLegacyTraceViewer),
+ action: () =>
+ setNextPromise(convertToJson(trace, openBufferWithLegacyTraceViewer)),
},
{
text: 'Open beginning of trace',
action: () =>
- convertToJson(
- trace,
- openBufferWithLegacyTraceViewer,
- /* truncate*/ 'start',
+ setNextPromise(
+ convertToJson(
+ trace,
+ openBufferWithLegacyTraceViewer,
+ /* truncate*/ 'start',
+ ),
),
},
{
text: 'Open end of trace',
primary: true,
action: () =>
- convertToJson(
- trace,
- openBufferWithLegacyTraceViewer,
- /* truncate*/ 'end',
+ setNextPromise(
+ convertToJson(
+ trace,
+ openBufferWithLegacyTraceViewer,
+ /* truncate*/ 'end',
+ ),
),
},
],
});
- return;
+ // nextPromise is undefined if the user just dimisses the dialog with ESC.
+ if (nextPromise !== undefined) {
+ await nextPromise;
+ }
}
// TraceViewer method that we wire up to trigger the file load.
diff --git a/ui/src/frontend/named_slice_track.ts b/ui/src/frontend/named_slice_track.ts
index bee03e6..ed9b5f0 100644
--- a/ui/src/frontend/named_slice_track.ts
+++ b/ui/src/frontend/named_slice_track.ts
@@ -12,7 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {getColorForSlice} from '../core/colorizer';
+import {getColorForSlice} from '../public/lib/colorizer';
+import {TrackEventDetailsPanel} from '../public/details_panel';
+import {TrackEventSelection} from '../public/selection';
import {Slice} from '../public/track';
import {STR_NULL} from '../trace_processor/query_result';
import {
@@ -23,9 +25,11 @@
SLICE_FLAGS_INCOMPLETE,
SLICE_FLAGS_INSTANT,
} from './base_slice_track';
-import {globals} from './globals';
+import {ThreadSliceDetailsPanel} from './thread_slice_details_tab';
import {NewTrackArgs} from './track';
import {renderDuration} from './widgets/duration';
+import {TraceImpl} from '../core/trace_impl';
+import {assertIsInstance} from '../base/logging';
export const NAMED_ROW = {
// Base columns (tsq, ts, dur, id, depth).
@@ -67,11 +71,13 @@
}
onSliceClick(args: OnSliceClickArgs<SliceType>) {
- globals.selectionManager.selectLegacy({
- kind: 'SLICE',
- id: args.slice.id,
- trackUri: this.uri,
- table: 'slice',
- });
+ this.trace.selection.selectTrackEvent(this.uri, args.slice.id);
+ }
+
+ detailsPanel(_sel: TrackEventSelection): TrackEventDetailsPanel {
+ // Rationale for the assertIsInstance: ThreadSliceDetailsPanel requires a
+ // TraceImpl (because of flows) but here we must take a Trace interface,
+ // because this class is exposed to plugins (which see only Trace).
+ return new ThreadSliceDetailsPanel(assertIsInstance(this.trace, TraceImpl));
}
}
diff --git a/ui/src/frontend/notes_list_editor.ts b/ui/src/frontend/notes_list_editor.ts
index f4443d6..e747ac9 100644
--- a/ui/src/frontend/notes_list_editor.ts
+++ b/ui/src/frontend/notes_list_editor.ts
@@ -13,13 +13,13 @@
// limitations under the License.
import m from 'mithril';
-import {globals} from './globals';
import {Button} from '../widgets/button';
import {Icons} from '../base/semantic_icons';
+import {TraceImplAttrs} from '../core/trace_impl';
-export class NotesListEditor implements m.ClassComponent {
- view(_: m.CVnode) {
- const notes = globals.noteManager.notes;
+export class NotesListEditor implements m.ClassComponent<TraceImplAttrs> {
+ view({attrs}: m.CVnode<TraceImplAttrs>) {
+ const notes = attrs.trace.notes.notes;
if (notes.size === 0) {
return 'No notes found';
}
@@ -51,7 +51,7 @@
m(Button, {
icon: Icons.Delete,
onclick: () => {
- globals.noteManager.removeNote(id);
+ attrs.trace.notes.removeNote(id);
},
}),
),
diff --git a/ui/src/frontend/notes_panel.ts b/ui/src/frontend/notes_panel.ts
index c15476c..ac5b015 100644
--- a/ui/src/frontend/notes_panel.ts
+++ b/ui/src/frontend/notes_panel.ts
@@ -15,14 +15,11 @@
import m from 'mithril';
import {currentTargetOffset} from '../base/dom_utils';
import {Icons} from '../base/semantic_icons';
-import {Time} from '../base/time';
-import {Actions} from '../common/actions';
-import {randomColor} from '../core/colorizer';
+import {randomColor} from '../public/lib/colorizer';
import {SpanNote, Note} from '../public/note';
import {raf} from '../core/raf_scheduler';
import {Button, ButtonBar} from '../widgets/button';
import {TRACK_SHELL_WIDTH} from './css_constants';
-import {globals} from './globals';
import {getMaxMajorTicks, generateTicks, TickType} from './gridline_helper';
import {Size2D} from '../base/geom';
import {Panel} from './panel_container';
@@ -31,9 +28,8 @@
import {DetailsPanel} from '../public/details_panel';
import {TimeScale} from '../base/time_scale';
import {canvasClip} from '../base/canvas_utils';
-import {isTraceLoaded} from './trace_attrs';
import {Selection} from '../public/selection';
-import {Trace} from '../public/trace';
+import {TraceImpl} from '../core/trace_impl';
const FLAG_WIDTH = 16;
const AREA_TRIANGLE_WIDTH = 10;
@@ -59,12 +55,19 @@
export class NotesPanel implements Panel {
readonly kind = 'panel';
readonly selectable = false;
+ private readonly trace: TraceImpl;
private timescale?: TimeScale; // The timescale from the last render()
private hoveredX: null | number = null;
private mouseDragging = false;
+ constructor(trace: TraceImpl) {
+ this.trace = trace;
+ }
+
render(): m.Children {
- const allCollapsed = globals.workspace.flatTracks.every((n) => n.collapsed);
+ const allCollapsed = this.trace.workspace.flatTracks.every(
+ (n) => n.collapsed,
+ );
return m(
'.notes-panel',
@@ -86,72 +89,71 @@
onmousemove: (e: MouseEvent) => {
this.mouseDragging = true;
this.hoveredX = currentTargetOffset(e).x - TRACK_SHELL_WIDTH;
- raf.scheduleRedraw();
+ raf.scheduleCanvasRedraw();
},
onmouseenter: (e: MouseEvent) => {
this.hoveredX = currentTargetOffset(e).x - TRACK_SHELL_WIDTH;
- raf.scheduleRedraw();
+ raf.scheduleCanvasRedraw();
},
onmouseout: () => {
this.hoveredX = null;
- globals.dispatch(Actions.setHoveredNoteTimestamp({ts: Time.INVALID}));
+ this.trace.timeline.hoveredNoteTimestamp = undefined;
},
},
- isTraceLoaded() &&
- m(
- ButtonBar,
- {className: 'pf-toolbar'},
- m(Button, {
- onclick: (e: Event) => {
- e.preventDefault();
- if (allCollapsed) {
- globals.commandManager.runCommand(
- 'perfetto.CoreCommands#ExpandAllGroups',
- );
- } else {
- globals.commandManager.runCommand(
- 'perfetto.CoreCommands#CollapseAllGroups',
- );
- }
- },
- title: allCollapsed ? 'Expand all' : 'Collapse all',
- icon: allCollapsed ? 'unfold_more' : 'unfold_less',
- compact: true,
- }),
- m(Button, {
- onclick: (e: Event) => {
- e.preventDefault();
- globals.workspace.pinnedTracks.forEach((t) =>
- globals.workspace.unpinTrack(t),
+ m(
+ ButtonBar,
+ {className: 'pf-toolbar'},
+ m(Button, {
+ onclick: (e: Event) => {
+ e.preventDefault();
+ if (allCollapsed) {
+ this.trace.commands.runCommand(
+ 'perfetto.CoreCommands#ExpandAllGroups',
);
- raf.scheduleFullRedraw();
- },
- title: 'Clear all pinned tracks',
- icon: 'clear_all',
- compact: true,
- }),
- // TODO(stevegolton): Re-introduce this when we fix track filtering
- // m(TextInput, {
- // placeholder: 'Filter tracks...',
- // title:
- // 'Track filter - enter one or more comma-separated search terms',
- // value: globals.state.trackFilterTerm,
- // oninput: (e: Event) => {
- // const filterTerm = (e.target as HTMLInputElement).value;
- // globals.dispatch(Actions.setTrackFilterTerm({filterTerm}));
- // },
- // }),
- // m(Button, {
- // type: 'reset',
- // icon: 'backspace',
- // onclick: () => {
- // globals.dispatch(
- // Actions.setTrackFilterTerm({filterTerm: undefined}),
- // );
- // },
- // title: 'Clear track filter',
- // }),
- ),
+ } else {
+ this.trace.commands.runCommand(
+ 'perfetto.CoreCommands#CollapseAllGroups',
+ );
+ }
+ },
+ title: allCollapsed ? 'Expand all' : 'Collapse all',
+ icon: allCollapsed ? 'unfold_more' : 'unfold_less',
+ compact: true,
+ }),
+ m(Button, {
+ onclick: (e: Event) => {
+ e.preventDefault();
+ this.trace.workspace.pinnedTracks.forEach((t) =>
+ this.trace.workspace.unpinTrack(t),
+ );
+ raf.scheduleFullRedraw();
+ },
+ title: 'Clear all pinned tracks',
+ icon: 'clear_all',
+ compact: true,
+ }),
+ // TODO(stevegolton): Re-introduce this when we fix track filtering
+ // m(TextInput, {
+ // placeholder: 'Filter tracks...',
+ // title:
+ // 'Track filter - enter one or more comma-separated search terms',
+ // value: this.trace.state.trackFilterTerm,
+ // oninput: (e: Event) => {
+ // const filterTerm = (e.target as HTMLInputElement).value;
+ // this.trace.dispatch(Actions.setTrackFilterTerm({filterTerm}));
+ // },
+ // }),
+ // m(Button, {
+ // type: 'reset',
+ // icon: 'backspace',
+ // onclick: () => {
+ // this.trace.dispatch(
+ // Actions.setTrackFilterTerm({filterTerm: undefined}),
+ // );
+ // },
+ // title: 'Clear track filter',
+ // }),
+ ),
);
}
@@ -171,7 +173,7 @@
private renderPanel(ctx: CanvasRenderingContext2D, size: Size2D): void {
let aNoteIsHovered = false;
- const visibleWindow = globals.timeline.visibleWindow;
+ const visibleWindow = this.trace.timeline.visibleWindow;
const timescale = new TimeScale(visibleWindow, {
left: 0,
right: size.width,
@@ -182,7 +184,7 @@
if (size.width > 0 && timespan.duration > 0n) {
const maxMajorTicks = getMaxMajorTicks(size.width);
- const offset = globals.trace.timeline.timestampOffset();
+ const offset = this.trace.timeline.timestampOffset();
const tickGen = generateTicks(timespan, maxMajorTicks, offset);
for (const {type, time} of tickGen) {
const px = Math.floor(timescale.timeToPx(time));
@@ -195,7 +197,7 @@
ctx.textBaseline = 'bottom';
ctx.font = '10px Helvetica';
- for (const note of globals.noteManager.notes.values()) {
+ for (const note of this.trace.notes.notes.values()) {
const timestamp = getStartTimestamp(note);
// TODO(hjd): We should still render area selection marks in viewport is
// *within* the area (e.g. both lhs and rhs are out of bounds).
@@ -211,7 +213,7 @@
this.hoveredX !== null && this.hitTestNote(this.hoveredX, note);
if (currentIsHovered) aNoteIsHovered = true;
- const selection = globals.selectionManager.selection;
+ const selection = this.trace.selection.selection;
const isSelected = selection.kind === 'note' && selection.id === note.id;
const x = timescale.timeToPx(timestamp);
const left = Math.floor(x);
@@ -248,14 +250,14 @@
// A real note is hovered so we don't need to see the preview line.
// TODO(hjd): Change cursor to pointer here.
if (aNoteIsHovered) {
- globals.dispatch(Actions.setHoveredNoteTimestamp({ts: Time.INVALID}));
+ this.trace.timeline.hoveredNoteTimestamp = undefined;
}
// View preview note flag when hovering on notes panel.
if (!aNoteIsHovered && this.hoveredX !== null) {
const timestamp = timescale.pxToHpTime(this.hoveredX).toTime();
if (visibleWindow.contains(timestamp)) {
- globals.dispatch(Actions.setHoveredNoteTimestamp({ts: timestamp}));
+ this.trace.timeline.hoveredNoteTimestamp = timestamp;
const x = timescale.timeToPx(timestamp);
const left = Math.floor(x);
this.drawFlag(ctx, left, size.height, '#aaa', /* fill */ true);
@@ -336,16 +338,16 @@
// Select the hovered note, or create a new single note & select it
if (x < 0) return;
- for (const note of globals.noteManager.notes.values()) {
+ for (const note of this.trace.notes.notes.values()) {
if (this.hoveredX !== null && this.hitTestNote(this.hoveredX, note)) {
- globals.selectionManager.selectNote({id: note.id});
+ this.trace.selection.selectNote({id: note.id});
return;
}
}
const timestamp = this.timescale.pxToHpTime(x).toTime();
const color = randomColor();
- const noteId = globals.noteManager.addNote({timestamp, color});
- globals.selectionManager.selectNote({id: noteId});
+ const noteId = this.trace.notes.addNote({timestamp, color});
+ this.trace.selection.selectNote({id: noteId});
}
private hitTestNote(x: number, note: SpanNote | Note): boolean {
@@ -369,7 +371,7 @@
}
export class NotesEditorTab implements DetailsPanel {
- constructor(private trace: Trace) {}
+ constructor(private trace: TraceImpl) {}
render(selection: Selection) {
if (selection.kind !== 'note') {
@@ -407,7 +409,7 @@
},
onchange: (e: InputEvent) => {
const newText = (e.target as HTMLInputElement).value;
- globals.noteManager.changeNote(id, {text: newText});
+ this.trace.notes.changeNote(id, {text: newText});
},
}),
m(
@@ -417,14 +419,14 @@
value: note.color,
onchange: (e: Event) => {
const newColor = (e.target as HTMLInputElement).value;
- globals.noteManager.changeNote(id, {color: newColor});
+ this.trace.notes.changeNote(id, {color: newColor});
},
}),
),
m(Button, {
label: 'Remove',
icon: Icons.Delete,
- onclick: () => globals.noteManager.removeNote(id),
+ onclick: () => this.trace.notes.removeNote(id),
}),
),
);
diff --git a/ui/src/frontend/overview_timeline_panel.ts b/ui/src/frontend/overview_timeline_panel.ts
index 3545ba1..8eb41ec 100644
--- a/ui/src/frontend/overview_timeline_panel.ts
+++ b/ui/src/frontend/overview_timeline_panel.ts
@@ -13,8 +13,8 @@
// limitations under the License.
import m from 'mithril';
-import {Time, TimeSpan, time} from '../base/time';
-import {colorForCpu} from '../core/colorizer';
+import {Duration, Time, TimeSpan, duration, time} from '../base/time';
+import {colorForCpu} from '../public/lib/colorizer';
import {timestampFormat, TimestampFormat} from '../core/timestamp_format';
import {
OVERVIEW_TIMELINE_NON_VISIBLE_COLOR,
@@ -25,7 +25,6 @@
import {InnerDragStrategy} from './drag/inner_drag_strategy';
import {OuterDragStrategy} from './drag/outer_drag_strategy';
import {DragGestureHandler} from '../base/drag_gesture_handler';
-import {globals} from './globals';
import {
getMaxMajorTicks,
MIN_PX_PER_STEP,
@@ -36,23 +35,37 @@
import {Panel} from './panel_container';
import {TimeScale} from '../base/time_scale';
import {HighPrecisionTimeSpan} from '../base/high_precision_time_span';
+import {TraceImpl} from '../core/trace_impl';
+import {LONG, NUM} from '../trace_processor/query_result';
+import {raf} from '../core/raf_scheduler';
+import {getOrCreate} from '../base/utils';
+
+const tracesData = new WeakMap<TraceImpl, OverviewDataLoader>();
export class OverviewTimelinePanel implements Panel {
private static HANDLE_SIZE_PX = 5;
readonly kind = 'panel';
readonly selectable = false;
-
private width = 0;
private gesture?: DragGestureHandler;
private timeScale?: TimeScale;
private dragStrategy?: DragStrategy;
private readonly boundOnMouseMove = this.onMouseMove.bind(this);
+ private readonly overviewData: OverviewDataLoader;
+
+ constructor(private trace: TraceImpl) {
+ this.overviewData = getOrCreate(
+ tracesData,
+ trace,
+ () => new OverviewDataLoader(trace),
+ );
+ }
// Must explicitly type now; arguments types are no longer auto-inferred.
// https://github.com/Microsoft/TypeScript/issues/1373
onupdate({dom}: m.CVnodeDOM) {
this.width = dom.getBoundingClientRect().width;
- const traceTime = globals.traceContext;
+ const traceTime = this.trace.traceInfo;
if (this.width > TRACK_SHELL_WIDTH) {
const pxBounds = {left: TRACK_SHELL_WIDTH, right: this.width};
const hpTraceTime = HighPrecisionTimeSpan.fromTime(
@@ -103,16 +116,17 @@
renderCanvas(ctx: CanvasRenderingContext2D, size: Size2D) {
if (this.width === undefined) return;
if (this.timeScale === undefined) return;
+
const headerHeight = 20;
const tracksHeight = size.height - headerHeight;
const traceContext = new TimeSpan(
- globals.traceContext.start,
- globals.traceContext.end,
+ this.trace.traceInfo.start,
+ this.trace.traceInfo.end,
);
if (size.width > TRACK_SHELL_WIDTH && traceContext.duration > 0n) {
const maxMajorTicks = getMaxMajorTicks(this.width - TRACK_SHELL_WIDTH);
- const offset = globals.trace.timeline.timestampOffset();
+ const offset = this.trace.timeline.timestampOffset();
const tickGen = generateTicks(traceContext, maxMajorTicks, offset);
// Draw time labels
@@ -124,7 +138,7 @@
if (xPos > this.width) break;
if (type === TickType.MAJOR) {
ctx.fillRect(xPos - 1, 0, 1, headerHeight - 5);
- const domainTime = globals.trace.timeline.toDomainTime(time);
+ const domainTime = this.trace.timeline.toDomainTime(time);
renderTimestamp(ctx, domainTime, xPos + 5, 18, MIN_PX_PER_STEP);
} else if (type == TickType.MEDIUM) {
ctx.fillRect(xPos - 1, 0, 1, 8);
@@ -135,12 +149,13 @@
}
// Draw mini-tracks with quanitzed density for each process.
- if (globals.overviewStore.size > 0) {
- const numTracks = globals.overviewStore.size;
+ const overviewData = this.overviewData.overviewData;
+ if (overviewData.size > 0) {
+ const numTracks = overviewData.size;
let y = 0;
const trackHeight = (tracksHeight - 1) / numTracks;
- for (const key of globals.overviewStore.keys()) {
- const loads = globals.overviewStore.get(key)!;
+ for (const key of overviewData.keys()) {
+ const loads = overviewData.get(key)!;
for (let i = 0; i < loads.length; i++) {
const xStart = Math.floor(this.timeScale.timeToPx(loads[i].start));
const xEnd = Math.ceil(this.timeScale.timeToPx(loads[i].end));
@@ -159,9 +174,7 @@
ctx.fillRect(0, size.height - 1, this.width, 1);
// Draw semi-opaque rects that occlude the non-visible time range.
- const [vizStartPx, vizEndPx] = OverviewTimelinePanel.extractBounds(
- this.timeScale,
- );
+ const [vizStartPx, vizEndPx] = this.extractBounds(this.timeScale);
ctx.fillStyle = OVERVIEW_TIMELINE_NON_VISIBLE_COLOR;
ctx.fillRect(
@@ -203,9 +216,7 @@
private chooseCursor(x: number) {
if (this.timeScale === undefined) return 'default';
- const [startBound, endBound] = OverviewTimelinePanel.extractBounds(
- this.timeScale,
- );
+ const [startBound, endBound] = this.extractBounds(this.timeScale);
if (
OverviewTimelinePanel.inBorderRange(x, startBound) ||
OverviewTimelinePanel.inBorderRange(x, endBound)
@@ -227,16 +238,22 @@
onDragStart(x: number) {
if (this.timeScale === undefined) return;
- const pixelBounds = OverviewTimelinePanel.extractBounds(this.timeScale);
+
+ const cb = (vizTime: HighPrecisionTimeSpan) => {
+ this.trace.timeline.updateVisibleTimeHP(vizTime);
+ raf.scheduleCanvasRedraw();
+ };
+ const pixelBounds = this.extractBounds(this.timeScale);
+ const timeScale = this.timeScale;
if (
OverviewTimelinePanel.inBorderRange(x, pixelBounds[0]) ||
OverviewTimelinePanel.inBorderRange(x, pixelBounds[1])
) {
- this.dragStrategy = new BorderDragStrategy(this.timeScale, pixelBounds);
+ this.dragStrategy = new BorderDragStrategy(timeScale, pixelBounds, cb);
} else if (x < pixelBounds[0] || pixelBounds[1] < x) {
- this.dragStrategy = new OuterDragStrategy(this.timeScale);
+ this.dragStrategy = new OuterDragStrategy(timeScale, cb);
} else {
- this.dragStrategy = new InnerDragStrategy(this.timeScale, pixelBounds);
+ this.dragStrategy = new InnerDragStrategy(timeScale, pixelBounds, cb);
}
this.dragStrategy.onDragStart(x);
}
@@ -245,8 +262,8 @@
this.dragStrategy = undefined;
}
- private static extractBounds(timeScale: TimeScale): [number, number] {
- const vizTime = globals.timeline.visibleWindow;
+ private extractBounds(timeScale: TimeScale): [number, number] {
+ const vizTime = this.trace.timeline.visibleWindow;
return [
Math.floor(timeScale.hpTimeToPx(vizTime.start)),
Math.ceil(timeScale.hpTimeToPx(vizTime.end)),
@@ -308,3 +325,126 @@
const {dhhmmss} = timecode;
ctx.fillText(dhhmmss, x, y, minWidth);
}
+
+interface QuantizedLoad {
+ start: time;
+ end: time;
+ load: number;
+}
+
+// Kicks of a sequence of promises that load the overiew data in steps.
+// Each step schedules an animation frame.
+class OverviewDataLoader {
+ overviewData = new Map<string, QuantizedLoad[]>();
+
+ constructor(private trace: TraceImpl) {
+ this.beginLoad();
+ }
+
+ async beginLoad() {
+ const traceSpan = new TimeSpan(
+ this.trace.traceInfo.start,
+ this.trace.traceInfo.end,
+ );
+ const engine = this.trace.engine;
+ const stepSize = Duration.max(1n, traceSpan.duration / 100n);
+ const hasSchedSql = 'select ts from sched limit 1';
+ const hasSchedOverview = (await engine.query(hasSchedSql)).numRows() > 0;
+ if (hasSchedOverview) {
+ await this.loadSchedOverview(traceSpan, stepSize);
+ } else {
+ await this.loadSliceOverview(traceSpan, stepSize);
+ }
+ }
+
+ async loadSchedOverview(traceSpan: TimeSpan, stepSize: duration) {
+ const stepPromises = [];
+ for (
+ let start = traceSpan.start;
+ start < traceSpan.end;
+ start = Time.add(start, stepSize)
+ ) {
+ const progress = start - traceSpan.start;
+ const ratio = Number(progress) / Number(traceSpan.duration);
+ this.trace.omnibox.showStatusMessage(
+ 'Loading overview ' + `${Math.round(ratio * 100)}%`,
+ );
+ const end = Time.add(start, stepSize);
+ // The (async() => {})() queues all the 100 async promises in one batch.
+ // Without that, we would wait for each step to be rendered before
+ // kicking off the next one. That would interleave an animation frame
+ // between each step, slowing down significantly the overall process.
+ stepPromises.push(
+ (async () => {
+ const schedResult = await this.trace.engine.query(
+ `select cast(sum(dur) as float)/${stepSize} as load, cpu from sched ` +
+ `where ts >= ${start} and ts < ${end} and utid != 0 ` +
+ 'group by cpu order by cpu',
+ );
+ const schedData: {[key: string]: QuantizedLoad} = {};
+ const it = schedResult.iter({load: NUM, cpu: NUM});
+ for (; it.valid(); it.next()) {
+ const load = it.load;
+ const cpu = it.cpu;
+ schedData[cpu] = {start, end, load};
+ }
+ this.appendData(schedData);
+ })(),
+ );
+ } // for(start = ...)
+ await Promise.all(stepPromises);
+ }
+
+ async loadSliceOverview(traceSpan: TimeSpan, stepSize: duration) {
+ // Slices overview.
+ const sliceResult = await this.trace.engine.query(`select
+ bucket,
+ upid,
+ ifnull(sum(utid_sum) / cast(${stepSize} as float), 0) as load
+ from thread
+ inner join (
+ select
+ ifnull(cast((ts - ${traceSpan.start})/${stepSize} as int), 0) as bucket,
+ sum(dur) as utid_sum,
+ utid
+ from slice
+ inner join thread_track on slice.track_id = thread_track.id
+ group by bucket, utid
+ ) using(utid)
+ where upid is not null
+ group by bucket, upid`);
+
+ const slicesData: {[key: string]: QuantizedLoad[]} = {};
+ const it = sliceResult.iter({bucket: LONG, upid: NUM, load: NUM});
+ for (; it.valid(); it.next()) {
+ const bucket = it.bucket;
+ const upid = it.upid;
+ const load = it.load;
+
+ const start = Time.add(traceSpan.start, stepSize * bucket);
+ const end = Time.add(start, stepSize);
+
+ const upidStr = upid.toString();
+ let loadArray = slicesData[upidStr];
+ if (loadArray === undefined) {
+ loadArray = slicesData[upidStr] = [];
+ }
+ loadArray.push({start, end, load});
+ }
+ this.appendData(slicesData);
+ }
+
+ appendData(data: {[key: string]: QuantizedLoad | QuantizedLoad[]}) {
+ for (const [key, value] of Object.entries(data)) {
+ if (!this.overviewData.has(key)) {
+ this.overviewData.set(key, []);
+ }
+ if (value instanceof Array) {
+ this.overviewData.get(key)!.push(...value);
+ } else {
+ this.overviewData.get(key)!.push(value);
+ }
+ }
+ raf.scheduleCanvasRedraw();
+ }
+}
diff --git a/ui/src/frontend/pages.ts b/ui/src/frontend/pages.ts
deleted file mode 100644
index 5907dec..0000000
--- a/ui/src/frontend/pages.ts
+++ /dev/null
@@ -1,42 +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 m from 'mithril';
-import {TraceImpl} from '../core/trace_impl';
-import {AppImpl} from '../core/app_impl';
-import {HomePage} from './home_page';
-
-export interface PageAttrs {
- subpage?: string;
-}
-
-export interface PageWithTraceAttrs extends PageAttrs {
- trace: TraceImpl;
-}
-
-export function pageWithTrace(
- component: m.ComponentTypes<PageWithTraceAttrs>,
-): m.Component<PageAttrs> {
- return {
- view(vnode: m.Vnode<PageAttrs>) {
- const trace = AppImpl.instance.trace;
- if (trace !== undefined) {
- return m(component, {...vnode.attrs, trace});
- }
- // Fallback on homepage if trying to open a page that requires a trace
- // while no trace is loaded.
- return m(HomePage);
- },
- };
-}
diff --git a/ui/src/frontend/pan_and_zoom_handler.ts b/ui/src/frontend/pan_and_zoom_handler.ts
index 4536b9e..0009335 100644
--- a/ui/src/frontend/pan_and_zoom_handler.ts
+++ b/ui/src/frontend/pan_and_zoom_handler.ts
@@ -259,12 +259,12 @@
private onWheel(e: WheelEvent) {
if (Math.abs(e.deltaX) > Math.abs(e.deltaY)) {
this.onPanned(e.deltaX * HORIZONTAL_WHEEL_PAN_SPEED);
- raf.scheduleRedraw();
+ raf.scheduleCanvasRedraw();
} else if (e.ctrlKey && this.mousePositionX !== null) {
const sign = e.deltaY < 0 ? -1 : 1;
const deltaY = sign * Math.log2(1 + Math.abs(e.deltaY));
this.onZoomed(this.mousePositionX, deltaY * WHEEL_ZOOM_SPEED);
- raf.scheduleRedraw();
+ raf.scheduleCanvasRedraw();
}
}
diff --git a/ui/src/frontend/panel_container.ts b/ui/src/frontend/panel_container.ts
index 90adcca..760e098 100644
--- a/ui/src/frontend/panel_container.ts
+++ b/ui/src/frontend/panel_container.ts
@@ -16,29 +16,21 @@
import {findRef, toHTMLElement} from '../base/dom_utils';
import {assertExists, assertFalse} from '../base/logging';
import {
- PerfStatsSource,
- RunningStatistics,
- debugNow,
- perfDebug,
- perfDisplay,
+ PerfStats,
+ PerfStatsContainer,
runningStatStr,
-} from '../core/perf';
+} from '../core/perf_stats';
import {raf} from '../core/raf_scheduler';
import {SimpleResizeObserver} from '../base/resize_observer';
import {canvasClip} from '../base/canvas_utils';
-import {
- SELECTION_STROKE_COLOR,
- TOPBAR_HEIGHT,
- TRACK_SHELL_WIDTH,
-} from './css_constants';
-import {globals} from './globals';
+import {SELECTION_STROKE_COLOR, TRACK_SHELL_WIDTH} from './css_constants';
import {Bounds2D, Size2D, VerticalBounds} from '../base/geom';
import {VirtualCanvas} from './virtual_canvas';
import {DisposableStack} from '../base/disposable_stack';
import {TimeScale} from '../base/time_scale';
-import {Optional} from '../base/utils';
import {TrackNode} from '../public/workspace';
import {HTMLAttrs} from '../widgets/common';
+import {TraceImpl, TraceImplAttrs} from '../core/trace_impl';
const CANVAS_OVERDRAW_PX = 100;
@@ -50,7 +42,7 @@
// tracks!
readonly trackNode?: TrackNode;
renderCanvas(ctx: CanvasRenderingContext2D, size: Size2D): void;
- getSliceVerticalBounds?(depth: number): Optional<VerticalBounds>;
+ getSliceVerticalBounds?(depth: number): VerticalBounds | undefined;
}
export interface PanelGroup {
@@ -64,9 +56,11 @@
export type PanelOrGroup = Panel | PanelGroup;
-export interface PanelContainerAttrs {
+export interface PanelContainerAttrs extends TraceImplAttrs {
panels: PanelOrGroup[];
className?: string;
+ selectedYRange: VerticalBounds | undefined;
+
onPanelStackResize?: (width: number, height: number) => void;
// Called after all panels have been rendered to the canvas, to give the
@@ -88,6 +82,7 @@
width: number;
clientX: number;
clientY: number;
+ absY: number;
}
export interface RenderedPanelInfo {
@@ -96,12 +91,10 @@
}
export class PanelContainer
- implements m.ClassComponent<PanelContainerAttrs>, PerfStatsSource
+ implements m.ClassComponent<PanelContainerAttrs>, PerfStatsContainer
{
- // These values are updated with proper values in oncreate.
- // Y position of the panel container w.r.t. the client
- private panelContainerTop = 0;
- private panelContainerHeight = 0;
+ private readonly trace: TraceImpl;
+ private attrs: PanelContainerAttrs;
// Updated every render cycle in the view() hook
private panelById = new Map<string, Panel>();
@@ -109,11 +102,12 @@
// Updated every render cycle in the oncreate/onupdate hook
private panelInfos: PanelInfo[] = [];
- private panelPerfStats = new WeakMap<Panel, RunningStatistics>();
+ private perfStatsEnabled = false;
+ private panelPerfStats = new WeakMap<Panel, PerfStats>();
private perfStats = {
totalPanels: 0,
panelsOnCanvas: 0,
- renderStats: new RunningStatistics(10),
+ renderStats: new PerfStats(10),
};
private ctx?: CanvasRenderingContext2D;
@@ -123,6 +117,13 @@
private readonly OVERLAY_REF = 'overlay';
private readonly PANEL_STACK_REF = 'panel-stack';
+ constructor({attrs}: m.CVnode<PanelContainerAttrs>) {
+ this.attrs = attrs;
+ this.trace = attrs.trace;
+ this.trash.use(raf.addCanvasRedrawCallback(() => this.renderCanvas()));
+ this.trash.use(attrs.trace.perfDebugging.addContainer(this));
+ }
+
getPanelsInRegion(
startX: number,
endX: number,
@@ -140,8 +141,8 @@
if (
realPosX + pos.width >= minX &&
realPosX <= maxX &&
- pos.clientY + pos.height >= minY &&
- pos.clientY <= maxY &&
+ pos.absY + pos.height >= minY &&
+ pos.absY <= maxY &&
pos.panel.selectable
) {
panels.push(pos.panel);
@@ -153,33 +154,21 @@
// This finds the tracks covered by the in-progress area selection. When
// editing areaY is not set, so this will not be used.
handleAreaSelection() {
- const area = globals.timeline.selectedArea;
+ const {selectedYRange} = this.attrs;
+ const area = this.trace.timeline.selectedArea;
if (
area === undefined ||
- globals.timeline.areaY.start === undefined ||
- globals.timeline.areaY.end === undefined ||
+ selectedYRange === undefined ||
this.panelInfos.length === 0
) {
return;
}
- // Only get panels from the current panel container if the selection began
- // in this container.
- const panelContainerTop = this.panelInfos[0].clientY;
- const panelContainerBottom =
- this.panelInfos[this.panelInfos.length - 1].clientY +
- this.panelInfos[this.panelInfos.length - 1].height;
- if (
- globals.timeline.areaY.start + TOPBAR_HEIGHT < panelContainerTop ||
- globals.timeline.areaY.start + TOPBAR_HEIGHT > panelContainerBottom
- ) {
- return;
- }
// TODO(stevegolton): We shouldn't know anything about visible time scale
// right now, that's a job for our parent, but we can put one together so we
// don't have to refactor this entire bit right now...
- const visibleTimeScale = new TimeScale(globals.timeline.visibleWindow, {
+ const visibleTimeScale = new TimeScale(this.trace.timeline.visibleWindow, {
left: 0,
right: this.virtualCanvas!.size.width - TRACK_SHELL_WIDTH,
});
@@ -189,14 +178,15 @@
const panels = this.getPanelsInRegion(
visibleTimeScale.timeToPx(area.start),
visibleTimeScale.timeToPx(area.end),
- globals.timeline.areaY.start + TOPBAR_HEIGHT,
- globals.timeline.areaY.end + TOPBAR_HEIGHT,
+ selectedYRange.top,
+ selectedYRange.bottom,
);
+
// Get the track ids from the panels.
const trackUris: string[] = [];
for (const panel of panels) {
if (panel.trackNode) {
- if (panel.trackNode.hasChildren) {
+ if (panel.trackNode.isSummary) {
const groupNode = panel.trackNode;
// Select a track group and all child tracks if it is collapsed
if (groupNode.collapsed) {
@@ -209,20 +199,7 @@
}
}
}
- globals.timeline.selectArea(area.start, area.end, trackUris);
- }
-
- constructor({attrs}: m.CVnode<PanelContainerAttrs>) {
- const onRedraw = () => this.renderCanvas(attrs);
- raf.addRedrawCallback(onRedraw);
- this.trash.defer(() => {
- raf.removeRedrawCallback(onRedraw);
- });
-
- perfDisplay.addContainer(this);
- this.trash.defer(() => {
- perfDisplay.removeContainer(this);
- });
+ this.trace.timeline.selectArea(area.start, area.end, trackUris);
}
private virtualCanvas?: VirtualCanvas;
@@ -253,7 +230,7 @@
});
virtualCanvas.setLayoutShiftListener(() => {
- this.renderCanvas(vnode.attrs);
+ this.renderCanvas();
});
this.onupdate(vnode);
@@ -312,6 +289,7 @@
}
view({attrs}: m.CVnode<PanelContainerAttrs>) {
+ this.attrs = attrs;
this.panelById.clear();
const children = attrs.panels.map((panel, index) =>
this.renderTree(panel, `${index}`),
@@ -336,12 +314,10 @@
private readPanelRectsFromDom(dom: Element): void {
this.panelInfos = [];
+ const panel = dom.querySelectorAll('.pf-panel');
const panels = assertExists(findRef(dom, this.PANEL_STACK_REF));
- const domRect = panels.getBoundingClientRect();
- this.panelContainerTop = domRect.y;
- this.panelContainerHeight = domRect.height;
-
- dom.querySelectorAll('.pf-panel').forEach((panelElement) => {
+ const {top} = panels.getBoundingClientRect();
+ panel.forEach((panelElement) => {
const panelHTMLElement = toHTMLElement(panelElement);
const panelId = assertExists(panelHTMLElement.dataset.panelId);
const panel = assertExists(this.panelById.get(panelId));
@@ -354,18 +330,19 @@
width: rect.width,
clientX: rect.x,
clientY: rect.y,
+ absY: rect.y - top,
panel,
});
});
}
- private renderCanvas(attrs: PanelContainerAttrs) {
+ private renderCanvas() {
if (!this.ctx) return;
if (!this.virtualCanvas) return;
const ctx = this.ctx;
const vc = this.virtualCanvas;
- const redrawStart = debugNow();
+ const redrawStart = performance.now();
ctx.resetTransform();
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
@@ -376,12 +353,11 @@
this.handleAreaSelection();
- const totalRenderedPanels = this.renderPanels(ctx, vc, attrs);
-
+ const totalRenderedPanels = this.renderPanels(ctx, vc);
this.drawTopLayerOnCanvas(ctx, vc);
// Collect performance as the last thing we do.
- const redrawDur = debugNow() - redrawStart;
+ const redrawDur = performance.now() - redrawStart;
this.updatePerfStats(
redrawDur,
this.panelInfos.length,
@@ -392,9 +368,8 @@
private renderPanels(
ctx: CanvasRenderingContext2D,
vc: VirtualCanvas,
- attrs: PanelContainerAttrs,
): number {
- attrs.renderUnderlay?.(ctx, vc.size);
+ this.attrs.renderUnderlay?.(ctx, vc.size);
let panelTop = 0;
let totalOnCanvas = 0;
@@ -422,12 +397,12 @@
ctx.save();
ctx.translate(0, panelTop);
canvasClip(ctx, 0, 0, panelWidth, panelHeight);
- const beforeRender = debugNow();
+ const beforeRender = performance.now();
panel.renderCanvas(ctx, panelSize);
this.updatePanelStats(
i,
panel,
- debugNow() - beforeRender,
+ performance.now() - beforeRender,
ctx,
panelSize,
);
@@ -447,7 +422,7 @@
panelTop += panelHeight;
}
- attrs.renderOverlay?.(ctx, vc.size, renderedPanels);
+ this.attrs.renderOverlay?.(ctx, vc.size, renderedPanels);
return totalOnCanvas;
}
@@ -458,55 +433,43 @@
ctx: CanvasRenderingContext2D,
vc: VirtualCanvas,
): void {
- const area = globals.timeline.selectedArea;
- if (
- area === undefined ||
- globals.timeline.areaY.start === undefined ||
- globals.timeline.areaY.end === undefined
- ) {
+ const {selectedYRange} = this.attrs;
+ const area = this.trace.timeline.selectedArea;
+ if (area === undefined || selectedYRange === undefined) {
return;
}
- if (this.panelInfos.length === 0 || area.trackUris.length === 0) return;
+ if (this.panelInfos.length === 0 || area.trackUris.length === 0) {
+ return;
+ }
// Find the minY and maxY of the selected tracks in this panel container.
- let selectedTracksMinY = this.panelContainerHeight + this.panelContainerTop;
- let selectedTracksMaxY = this.panelContainerTop;
- let trackFromCurrentContainerSelected = false;
+ let selectedTracksMinY = selectedYRange.top;
+ let selectedTracksMaxY = selectedYRange.bottom;
for (let i = 0; i < this.panelInfos.length; i++) {
const trackUri = this.panelInfos[i].trackNode?.uri;
if (trackUri && area.trackUris.includes(trackUri)) {
- trackFromCurrentContainerSelected = true;
selectedTracksMinY = Math.min(
selectedTracksMinY,
- this.panelInfos[i].clientY,
+ this.panelInfos[i].absY,
);
selectedTracksMaxY = Math.max(
selectedTracksMaxY,
- this.panelInfos[i].clientY + this.panelInfos[i].height,
+ this.panelInfos[i].absY + this.panelInfos[i].height,
);
}
}
- // No box should be drawn if there are no selected tracks in the current
- // container.
- if (!trackFromCurrentContainerSelected) {
- return;
- }
-
// TODO(stevegolton): We shouldn't know anything about visible time scale
// right now, that's a job for our parent, but we can put one together so we
// don't have to refactor this entire bit right now...
- const visibleTimeScale = new TimeScale(globals.timeline.visibleWindow, {
+ const visibleTimeScale = new TimeScale(this.trace.timeline.visibleWindow, {
left: 0,
right: vc.size.width - TRACK_SHELL_WIDTH,
});
const startX = visibleTimeScale.timeToPx(area.start);
const endX = visibleTimeScale.timeToPx(area.end);
- // To align with where to draw on the canvas subtract the first panel Y.
- selectedTracksMinY -= this.panelContainerTop;
- selectedTracksMaxY -= this.panelContainerTop;
ctx.save();
ctx.strokeStyle = SELECTION_STROKE_COLOR;
ctx.lineWidth = 1;
@@ -532,10 +495,10 @@
ctx: CanvasRenderingContext2D,
size: Size2D,
) {
- if (!perfDebug()) return;
+ if (!this.perfStatsEnabled) return;
let renderStats = this.panelPerfStats.get(panel);
if (renderStats === undefined) {
- renderStats = new RunningStatistics();
+ renderStats = new PerfStats();
this.panelPerfStats.set(panel, renderStats);
}
renderStats.addValue(renderTime);
@@ -564,12 +527,16 @@
totalPanels: number,
panelsOnCanvas: number,
) {
- if (!perfDebug()) return;
+ if (!this.perfStatsEnabled) return;
this.perfStats.renderStats.addValue(renderTime);
this.perfStats.totalPanels = totalPanels;
this.perfStats.panelsOnCanvas = panelsOnCanvas;
}
+ setPerfStatsEnabled(enable: boolean): void {
+ this.perfStatsEnabled = enable;
+ }
+
renderPerfStats() {
return [
m(
diff --git a/ui/src/frontend/permalink.ts b/ui/src/frontend/permalink.ts
index a02b455..b69916f 100644
--- a/ui/src/frontend/permalink.ts
+++ b/ui/src/frontend/permalink.ts
@@ -14,33 +14,25 @@
import m from 'mithril';
import {assertExists} from '../base/logging';
-import {Actions} from '../common/actions';
-import {ConversionJobStatus} from '../common/conversion_jobs';
import {
JsonSerialize,
parseAppState,
serializeAppState,
-} from '../common/state_serialization';
+} from '../core/state_serialization';
import {
BUCKET_NAME,
MIME_BINARY,
MIME_JSON,
GcsUploader,
-} from '../common/gcs_uploader';
-import {globals} from './globals';
-import {
- publishConversionJobStatusUpdate,
- publishPermalinkHash,
-} from './publish';
-import {Router} from './router';
-import {Optional} from '../base/utils';
+} from '../base/gcs_uploader';
import {
SERIALIZED_STATE_VERSION,
SerializedAppState,
-} from '../common/state_serialization_schema';
+} from '../core/state_serialization_schema';
import {z} from 'zod';
import {showModal} from '../widgets/modal';
import {AppImpl} from '../core/app_impl';
+import {CopyableLink} from '../widgets/copyable_link';
// Permalink serialization has two layers:
// 1. Serialization of the app state (state_serialization.ts):
@@ -63,82 +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 jobName = 'create_permalink';
- publishConversionJobStatusUpdate({
- jobName,
- jobStatus: ConversionJobStatus.InProgress,
- });
-
- try {
- const hash = await createPermalinkInternal(opts);
- publishPermalinkHash(hash);
- } finally {
- publishConversionJobStatusUpdate({
- jobName,
- jobStatus: ConversionJobStatus.NotRunning,
- });
- }
+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 = globals.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 traceInfo = assertExists(AppImpl.instance.trace?.traceInfo);
- const traceSource = traceInfo.source;
- let dataToUpload: File | ArrayBuffer | undefined = undefined;
- let traceName = 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();
+ // 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);
@@ -185,28 +149,19 @@
}
}
- if (permalink.recordingOpts !== undefined) {
- // This permalink state only contains a RecordConfig. Show the
- // recording page with the config, but keep other state as-is.
- globals.dispatch(
- Actions.setRecordConfig({config: 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
- // (and optionally a traceUrl, below). globals.restoreAppStateAfterTraceLoad
- // will be processed by trace_controller.ts after the trace has loaded.
+ // (and optionally a traceUrl, below).
const parseRes = parseAppState(permalink.appState);
if (parseRes.success) {
- globals.restoreAppStateAfterTraceLoad = parseRes.data;
+ serializedAppState = parseRes.data;
} else {
error = parseRes.error;
}
}
if (permalink.traceUrl) {
- globals.dispatch(Actions.openTraceFromUrl({url: permalink.traceUrl}));
+ AppImpl.instance.openTraceFromUrl(permalink.traceUrl, serializedAppState);
}
if (error) {
@@ -245,7 +200,7 @@
// the trace URL.
// If we suceed, convert it to a new-style JSON object preserving some minimal
// information (really just vieport and pinned tracks).
-function tryLoadLegacyPermalink(data: unknown): Optional<PermalinkState> {
+function tryLoadLegacyPermalink(data: unknown): PermalinkState | undefined {
const legacyData = data as {
version?: number;
engine?: {source?: {url?: string}};
@@ -283,11 +238,12 @@
}
function updateStatus(msg: string): void {
- // TODO(hjd): Unify loading updates.
- globals.dispatch(
- Actions.updateStatus({
- msg,
- timestamp: Date.now() / 1000,
- }),
- );
+ AppImpl.instance.omnibox.showStatusMessage(msg);
+}
+
+function showPermalinkDialog(hash: string) {
+ showModal({
+ title: 'Permalink',
+ content: m(CopyableLink, {url: `${self.location.origin}/#!/?s=${hash}`}),
+ });
}
diff --git a/ui/src/frontend/pivot_table.ts b/ui/src/frontend/pivot_table.ts
index e07614c..925cfed 100644
--- a/ui/src/frontend/pivot_table.ts
+++ b/ui/src/frontend/pivot_table.ts
@@ -34,17 +34,19 @@
sliceAggregationColumns,
tables,
} from '../core/pivot_table_query_generator';
-import {PopupMenuButton, popupMenuIcon, PopupMenuItem} from './popup_menu';
import {ReorderableCell, ReorderableCellGroup} from './reorderable_cells';
import {AttributeModalHolder} from './tables/attribute_modal_holder';
import {DurationWidget} from './widgets/duration';
-import {addSqlTableTab} from './sql_table_tab_interface';
import {getSqlTableDescription} from './widgets/sql/table/sql_table_registry';
import {assertExists, assertFalse} from '../base/logging';
import {Filter, SqlColumn} from './widgets/sql/table/column';
import {argSqlColumn} from './widgets/sql/table/well_known_columns';
import {TraceImpl} from '../core/trace_impl';
import {PivotTableManager} from '../core/pivot_table_manager';
+import {extensions} from '../public/lib/extensions';
+import {MenuItem, PopupMenu2} from '../widgets/menu';
+import {Button} from '../widgets/button';
+import {popupMenuIcon} from '../widgets/table';
interface PathItem {
tree: PivotTree;
@@ -132,7 +134,7 @@
if (this.pivotState.constrainToArea) {
queryFilters.push(...areaFilters(attrs.selectionArea));
}
- addSqlTableTab(attrs.trace, {
+ extensions.addSqlTableTab(attrs.trace, {
table: assertExists(getSqlTableDescription('slice')),
// TODO(altimin): this should properly reference the required columns, but it works for now (until the pivot table is going to be rewritten to be more flexible).
filters: queryFilters,
@@ -289,15 +291,14 @@
return m('tr', overallValuesRow);
}
- sortingItem(aggregationIndex: number, order: SortDirection): PopupMenuItem {
+ sortingItem(aggregationIndex: number, order: SortDirection): m.Child {
const pivotMgr = this.pivotMgr;
- return {
- itemType: 'regular',
- text: order === 'DESC' ? 'Highest first' : 'Lowest first',
- callback() {
+ return m(MenuItem, {
+ label: order === 'DESC' ? 'Highest first' : 'Lowest first',
+ onclick: () => {
pivotMgr.setSortColumn(aggregationIndex, order);
},
- };
+ });
}
readableAggregationName(aggregation: Aggregation) {
@@ -313,20 +314,21 @@
aggregation: Aggregation,
index: number,
nameOverride?: string,
- ): PopupMenuItem {
- return {
- itemType: 'regular',
- text: nameOverride ?? readableColumnName(aggregation.column),
- callback: () => this.pivotMgr.addAggregation(aggregation, index),
- };
+ ): m.Child {
+ return m(MenuItem, {
+ label: nameOverride ?? readableColumnName(aggregation.column),
+ onclick: () => {
+ this.pivotMgr.addAggregation(aggregation, index);
+ },
+ });
}
aggregationPopupTableGroup(
table: string,
columns: string[],
index: number,
- ): PopupMenuItem | undefined {
- const items = [];
+ ): m.Child | undefined {
+ const items: m.Child[] = [];
for (const column of columns) {
const tableColumn: TableColumn = {kind: 'regular', table, column};
items.push(
@@ -341,12 +343,7 @@
return undefined;
}
- return {
- itemType: 'group',
- itemId: `aggregations-${table}`,
- text: `Add ${table} aggregation`,
- children: items,
- };
+ return m(MenuItem, {label: `Add ${table} aggregation`}, items);
}
renderAggregationHeaderCell(
@@ -354,7 +351,7 @@
index: number,
removeItem: boolean,
): ReorderableCell {
- const popupItems: PopupMenuItem[] = [];
+ const popupItems: m.Child[] = [];
if (aggregation.sortDirection === undefined) {
popupItems.push(
this.sortingItem(index, 'DESC'),
@@ -377,22 +374,26 @@
continue;
}
const pivotMgr = this.pivotMgr;
- popupItems.push({
- itemType: 'regular',
- text: otherAgg,
- callback() {
- pivotMgr.setAggregationFunction(index, otherAgg);
- },
- });
+ popupItems.push(
+ m(MenuItem, {
+ label: otherAgg,
+ onclick: () => {
+ pivotMgr.setAggregationFunction(index, otherAgg);
+ },
+ }),
+ );
}
}
if (removeItem) {
- popupItems.push({
- itemType: 'regular',
- text: 'Remove',
- callback: () => this.pivotMgr.removeAggregation(index),
- });
+ popupItems.push(
+ m(MenuItem, {
+ label: 'Remove',
+ onclick: () => {
+ this.pivotMgr.removeAggregation(index);
+ },
+ }),
+ );
}
let hasCount = false;
@@ -425,10 +426,15 @@
extraClass: '.aggregation' + markFirst(index),
content: [
this.readableAggregationName(aggregation),
- m(PopupMenuButton, {
- icon: popupMenuIcon(aggregation.sortDirection),
- items: popupItems,
- }),
+ m(
+ PopupMenu2,
+ {
+ trigger: m(Button, {
+ icon: popupMenuIcon(aggregation.sortDirection),
+ }),
+ },
+ popupItems,
+ ),
],
};
}
@@ -441,27 +447,27 @@
selectedPivots: Set<string>,
): ReorderableCell {
const pivotMgr = this.pivotMgr;
- const items: PopupMenuItem[] = [
- {
- itemType: 'regular',
- text: 'Add argument pivot',
- callback: () => {
+ const items: m.Child[] = [
+ m(MenuItem, {
+ label: 'Add argument pivot',
+ onclick: () => {
this.attributeModalHolder.start();
},
- },
+ }),
];
if (queryResult.metadata.pivotColumns.length > 1) {
- items.push({
- itemType: 'regular',
- text: 'Remove',
- callback() {
- pivotMgr.setPivotSelected({column: pivot, selected: false});
- },
- });
+ items.push(
+ m(MenuItem, {
+ label: 'Remove',
+ onclick: () => {
+ pivotMgr.setPivotSelected({column: pivot, selected: false});
+ },
+ }),
+ );
}
for (const table of tables) {
- const group: PopupMenuItem[] = [];
+ const group: m.Child[] = [];
for (const columnName of table.columns) {
const column: TableColumn = {
kind: 'regular',
@@ -471,26 +477,30 @@
if (selectedPivots.has(columnKey(column))) {
continue;
}
- group.push({
- itemType: 'regular',
- text: columnName,
- callback() {
- pivotMgr.setPivotSelected({column, selected: true});
- },
- });
+ group.push(
+ m(MenuItem, {
+ label: columnName,
+ onclick: () => {
+ pivotMgr.setPivotSelected({column, selected: true});
+ },
+ }),
+ );
}
- items.push({
- itemType: 'group',
- itemId: `pivot-${table.name}`,
- text: `Add ${table.displayName} pivot`,
- children: group,
- });
+ items.push(
+ m(
+ MenuItem,
+ {
+ label: `Add ${table.displayName} pivot`,
+ },
+ group,
+ ),
+ );
}
return {
content: [
readableColumnName(pivot),
- m(PopupMenuButton, {icon: 'more_horiz', items}),
+ m(PopupMenu2, {trigger: m(Button, {icon: 'more_horiz'})}, items),
],
};
}
@@ -547,20 +557,20 @@
}),
m(
'td.menu',
- m(PopupMenuButton, {
- icon: 'menu',
- items: [
- {
- itemType: 'regular',
- text: state.constrainToArea
- ? 'Query data for the whole timeline'
- : 'Constrain to selected area',
- callback: () => {
- this.pivotMgr.setConstrainedToArea(!state.constrainToArea);
- },
+ m(
+ PopupMenu2,
+ {
+ trigger: m(Button, {icon: 'menu'}),
+ },
+ m(MenuItem, {
+ label: state.constrainToArea
+ ? 'Query data for the whole timeline'
+ : 'Constrain to selected area',
+ onclick: () => {
+ this.pivotMgr.setConstrainedToArea(!state.constrainToArea);
},
- ],
- }),
+ }),
+ ),
),
),
),
diff --git a/ui/src/frontend/plugins_page.ts b/ui/src/frontend/plugins_page.ts
deleted file mode 100644
index 5e0c915..0000000
--- a/ui/src/frontend/plugins_page.ts
+++ /dev/null
@@ -1,115 +0,0 @@
-// 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 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 {pluginManager, pluginRegistry} from '../common/plugins';
-import {raf} from '../core/raf_scheduler';
-import {Button} from '../widgets/button';
-import {exists} from '../base/utils';
-import {PluginDescriptor} from '../public/plugin';
-import {defaultPlugins} from '../core/default_plugins';
-import {Intent} from '../widgets/common';
-import {PageAttrs} from './pages';
-
-export class PluginsPage implements m.ClassComponent<PageAttrs> {
- view() {
- return m(
- '.pf-plugins-page',
- m('h1', 'Plugins'),
- pluginManager.needsRestart &&
- m(
- 'h3.restart_needed',
- 'Some plugins have been disabled. ' +
- 'Please reload your page to apply the changes.',
- ),
- m(
- '.pf-plugins-topbar',
- m(Button, {
- intent: Intent.Primary,
- label: 'Disable All',
- onclick: async () => {
- for (const plugin of pluginRegistry.values()) {
- await pluginManager.disablePlugin(plugin.pluginId);
- raf.scheduleFullRedraw();
- }
- },
- }),
- m(Button, {
- intent: Intent.Primary,
- label: 'Enable All',
- onclick: async () => {
- for (const plugin of pluginRegistry.values()) {
- await pluginManager.enablePlugin(plugin.pluginId);
- raf.scheduleFullRedraw();
- }
- },
- }),
- m(Button, {
- intent: Intent.Primary,
- label: 'Restore Defaults',
- onclick: async () => {
- await pluginManager.restoreDefaults();
- raf.scheduleFullRedraw();
- },
- }),
- ),
- m(
- '.pf-plugins-grid',
- [
- m('span', 'Plugin'),
- m('span', 'Default?'),
- m('span', 'Enabled?'),
- m('span', 'Active?'),
- m('span', 'Control'),
- m('span', 'Load Time'),
- ],
- Array.from(pluginRegistry.values()).map((plugin) => {
- return renderPluginRow(plugin);
- }),
- ),
- );
- }
-}
-
-function renderPluginRow(plugin: PluginDescriptor): m.Children {
- const pluginId = plugin.pluginId;
- const isDefault = defaultPlugins.includes(pluginId);
- const pluginDetails = pluginManager.plugins.get(pluginId);
- const isActive = pluginManager.isActive(pluginId);
- const isEnabled = pluginManager.isEnabled(pluginId);
- const loadTime = pluginDetails?.previousOnTraceLoadTimeMillis;
- return [
- m('span', pluginId),
- m('span', isDefault ? 'Yes' : 'No'),
- isEnabled
- ? m('.pf-tag.pf-active', 'Enabled')
- : m('.pf-tag.pf-inactive', 'Disabled'),
- isActive
- ? m('.pf-tag.pf-active', 'Active')
- : m('.pf-tag.pf-inactive', 'Inactive'),
- m(Button, {
- label: isEnabled ? 'Disable' : 'Enable',
- intent: Intent.Primary,
- onclick: async () => {
- if (isEnabled) {
- await pluginManager.disablePlugin(pluginId);
- } else {
- await pluginManager.enablePlugin(pluginId);
- }
- raf.scheduleFullRedraw();
- },
- }),
- exists(loadTime) ? m('span', `${loadTime.toFixed(1)} ms`) : m('span', `-`),
- ];
-}
diff --git a/ui/src/frontend/popup_menu.ts b/ui/src/frontend/popup_menu.ts
deleted file mode 100644
index a59a1b5..0000000
--- a/ui/src/frontend/popup_menu.ts
+++ /dev/null
@@ -1,198 +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 m from 'mithril';
-import {SortDirection} from '../base/comparison_utils';
-import {raf} from '../core/raf_scheduler';
-
-export interface RegularPopupMenuItem {
- itemType: 'regular';
- // Display text
- text: string;
- // Action on menu item click
- callback: () => void;
-}
-
-// Helper function for simplifying defining menus.
-export function menuItem(
- text: string,
- action: () => void,
-): RegularPopupMenuItem {
- return {
- itemType: 'regular',
- text,
- callback: action,
- };
-}
-
-export interface GroupPopupMenuItem {
- itemType: 'group';
- text: string;
- itemId: string;
- children: PopupMenuItem[];
-}
-
-export type PopupMenuItem = RegularPopupMenuItem | GroupPopupMenuItem;
-
-export interface PopupMenuButtonAttrs {
- // Icon for button opening a menu
- icon: string;
- // List of popup menu items
- items: PopupMenuItem[];
-}
-
-// To ensure having at most one popup menu on the screen at a time, we need to
-// listen to click events on the whole page and close currently opened popup, if
-// there's any. This class, used as a singleton, does exactly that.
-class PopupHolder {
- // Invariant: global listener should be register if and only if this.popup is
- // not undefined.
- popup: PopupMenuButton | undefined = undefined;
- initialized = false;
- listener: (e: MouseEvent) => void;
-
- constructor() {
- this.listener = (e: MouseEvent) => {
- // Only handle those events that are not part of dropdown menu themselves.
- const hasDropdown =
- e.composedPath().find(PopupHolder.isDropdownElement) !== undefined;
- if (!hasDropdown) {
- this.ensureHidden();
- }
- };
- }
-
- static isDropdownElement(target: EventTarget) {
- if (target instanceof HTMLElement) {
- return target.tagName === 'DIV' && target.classList.contains('dropdown');
- }
- return false;
- }
-
- ensureHidden() {
- if (this.popup !== undefined) {
- this.popup.setVisible(false);
- }
- }
-
- clear() {
- if (this.popup !== undefined) {
- this.popup = undefined;
- window.removeEventListener('click', this.listener);
- }
- }
-
- showPopup(popup: PopupMenuButton) {
- this.ensureHidden();
- this.popup = popup;
- window.addEventListener('click', this.listener);
- }
-}
-
-// Singleton instance of PopupHolder
-const popupHolder = new PopupHolder();
-
-// For a table column that can be sorted; the standard popup icon should
-// reflect the current sorting direction. This function returns an icon
-// corresponding to optional SortDirection according to which the column is
-// sorted. (Optional because column might be unsorted)
-export function popupMenuIcon(sortDirection?: SortDirection) {
- switch (sortDirection) {
- case undefined:
- return 'more_horiz';
- case 'DESC':
- return 'arrow_drop_down';
- case 'ASC':
- return 'arrow_drop_up';
- }
-}
-
-// Component that displays a button that shows a popup menu on click.
-export class PopupMenuButton implements m.ClassComponent<PopupMenuButtonAttrs> {
- popupShown = false;
- expandedGroups: Set<string> = new Set();
-
- setVisible(visible: boolean) {
- this.popupShown = visible;
- if (this.popupShown) {
- popupHolder.showPopup(this);
- } else {
- popupHolder.clear();
- }
- raf.scheduleFullRedraw();
- }
-
- renderItem(item: PopupMenuItem): m.Child {
- switch (item.itemType) {
- case 'regular':
- return m(
- 'button.open-menu',
- {
- onclick: () => {
- item.callback();
- // Hide the menu item after the action has been invoked
- this.setVisible(false);
- },
- },
- item.text,
- );
- case 'group':
- const isExpanded = this.expandedGroups.has(item.itemId);
- return m(
- 'div',
- m(
- 'button.open-menu.disallow-selection',
- {
- onclick: () => {
- if (this.expandedGroups.has(item.itemId)) {
- this.expandedGroups.delete(item.itemId);
- } else {
- this.expandedGroups.add(item.itemId);
- }
- raf.scheduleFullRedraw();
- },
- },
- // Show text with up/down arrow, depending on expanded state.
- item.text + (isExpanded ? ' \u25B2' : ' \u25BC'),
- ),
- isExpanded
- ? m(
- 'div.nested-menu',
- item.children.map((item) => this.renderItem(item)),
- )
- : null,
- );
- }
- }
-
- view(vnode: m.Vnode<PopupMenuButtonAttrs, this>) {
- return m(
- '.dropdown',
- m(
- '.dropdown-button',
- {
- onclick: () => {
- this.setVisible(!this.popupShown);
- },
- },
- vnode.children,
- m('i.material-icons', vnode.attrs.icon),
- ),
- m(
- this.popupShown ? '.popup-menu.opened' : '.popup-menu.closed',
- vnode.attrs.items.map((item) => this.renderItem(item)),
- ),
- );
- }
-}
diff --git a/ui/src/frontend/post_message_handler.ts b/ui/src/frontend/post_message_handler.ts
index d56926f..f04bea1 100644
--- a/ui/src/frontend/post_message_handler.ts
+++ b/ui/src/frontend/post_message_handler.ts
@@ -14,12 +14,12 @@
import m from 'mithril';
import {Time} from '../base/time';
-import {Actions, PostedScrollToRange, PostedTrace} from '../common/actions';
+import {PostedTrace} from '../core/trace_source';
import {showModal} from '../widgets/modal';
import {initCssConstants} from './css_constants';
-import {globals} from './globals';
import {toggleHelp} from './help_modal';
import {scrollTo} from '../public/scroll_helper';
+import {AppImpl} from '../core/app_impl';
const TRUSTED_ORIGINS_KEY = 'trustedOrigins';
@@ -31,6 +31,12 @@
perfetto: PostedScrollToRange;
}
+interface PostedScrollToRange {
+ timeStart: number;
+ timeEnd: number;
+ viewPercentage?: number;
+}
+
// Returns whether incoming traces should be opened automatically or should
// instead require a user interaction.
export function isTrustedOrigin(origin: string): boolean {
@@ -204,7 +210,7 @@
// For external traces, we need to disable other features such as
// downloading and sharing a trace.
postedTrace.localOnly = true;
- globals.dispatch(Actions.openTraceFromBuffer(postedTrace));
+ AppImpl.instance.openTraceFromBuffer(postedTrace);
};
const trustAndOpenTrace = () => {
@@ -260,16 +266,12 @@
return str.replace(/[^A-Za-z0-9.\-_#:/?=&;%+$ ]/g, ' ');
}
-function isTraceViewerReady(): boolean {
- return !!globals.getCurrentEngine()?.ready;
-}
-
const _maxScrollToRangeAttempts = 20;
async function scrollToTimeRange(
postedScrollToRange: PostedScrollToRange,
maxAttempts?: number,
) {
- const ready = isTraceViewerReady();
+ const ready = AppImpl.instance.trace && !AppImpl.instance.isLoadingTrace;
if (!ready) {
if (maxAttempts === undefined) {
maxAttempts = 0;
diff --git a/ui/src/frontend/publish.ts b/ui/src/frontend/publish.ts
deleted file mode 100644
index ab30a19..0000000
--- a/ui/src/frontend/publish.ts
+++ /dev/null
@@ -1,94 +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.
-
-import {ConversionJobStatusUpdate} from '../common/conversion_jobs';
-import {raf} from '../core/raf_scheduler';
-import {HttpRpcState} from '../trace_processor/http_rpc_engine';
-import {globals, QuantizedLoad, ThreadDesc} from './globals';
-
-export function publishOverviewData(data: {
- [key: string]: QuantizedLoad | QuantizedLoad[];
-}) {
- for (const [key, value] of Object.entries(data)) {
- if (!globals.overviewStore.has(key)) {
- globals.overviewStore.set(key, []);
- }
- if (value instanceof Array) {
- globals.overviewStore.get(key)!.push(...value);
- } else {
- globals.overviewStore.get(key)!.push(value);
- }
- }
- raf.scheduleRedraw();
-}
-
-export function clearOverviewData() {
- globals.overviewStore.clear();
- raf.scheduleRedraw();
-}
-
-export function publishTrackData(args: {id: string; data: {}}) {
- globals.setTrackData(args.id, args.data);
- raf.scheduleRedraw();
-}
-
-export function publishHttpRpcState(httpRpcState: HttpRpcState) {
- globals.httpRpcState = httpRpcState;
- raf.scheduleFullRedraw();
-}
-
-export function publishHasFtrace(value: boolean): void {
- globals.hasFtrace = value;
- globals.publishRedraw();
-}
-
-export function publishConversionJobStatusUpdate(
- job: ConversionJobStatusUpdate,
-) {
- globals.setConversionJobStatus(job.jobName, job.jobStatus);
- globals.publishRedraw();
-}
-
-export function publishBufferUsage(args: {percentage: number}) {
- globals.setBufferUsage(args.percentage);
- globals.publishRedraw();
-}
-
-export function publishRecordingLog(args: {logs: string}) {
- globals.setRecordingLog(args.logs);
- globals.publishRedraw();
-}
-
-export function publishMetricError(error: string) {
- globals.setMetricError(error);
- globals.publishRedraw();
-}
-
-export function publishThreads(data: ThreadDesc[]) {
- globals.threads.clear();
- data.forEach((thread) => {
- globals.threads.set(thread.utid, thread);
- });
- globals.publishRedraw();
-}
-
-export function publishShowPanningHint() {
- globals.showPanningHint = true;
- globals.publishRedraw();
-}
-
-export function publishPermalinkHash(hash: string | undefined): void {
- globals.permalinkHash = hash;
- globals.publishRedraw();
-}
diff --git a/ui/src/frontend/rpc_http_dialog.ts b/ui/src/frontend/rpc_http_dialog.ts
index a2aca27..e031b09 100644
--- a/ui/src/frontend/rpc_http_dialog.ts
+++ b/ui/src/frontend/rpc_http_dialog.ts
@@ -14,14 +14,11 @@
import m from 'mithril';
import {assertExists} from '../base/logging';
-import {Actions} from '../common/actions';
import {VERSION} from '../gen/perfetto_version';
import {StatusResult, TraceProcessorApiVersion} from '../protos';
import {HttpRpcEngine} from '../trace_processor/http_rpc_engine';
import {showModal} from '../widgets/modal';
-import {Router} from './router';
-import {globals} from './globals';
-import {publishHttpRpcState} from './publish';
+import {AppImpl} from '../core/app_impl';
const CURRENT_API_VERSION =
TraceProcessorApiVersion.TRACE_PROCESSOR_CURRENT_API_VERSION;
@@ -153,7 +150,7 @@
// having to open a trace).
export async function CheckHttpRpcConnection(): Promise<void> {
const state = await HttpRpcEngine.checkConnection();
- publishHttpRpcState(state);
+ AppImpl.instance.httpRpc.httpRpcAvailable = state.connected;
if (!state.connected) {
// No RPC = exit immediately to the WASM UI.
return;
@@ -161,12 +158,12 @@
const tpStatus = assertExists(state.status);
function forceWasm() {
- globals.dispatch(Actions.setNewEngineMode({mode: 'FORCE_BUILTIN_WASM'}));
+ AppImpl.instance.httpRpc.newEngineMode = 'FORCE_BUILTIN_WASM';
}
// Check short version:
if (tpStatus.versionCode !== '' && tpStatus.versionCode !== VERSION) {
- const url = await Router.isVersionAvailable(tpStatus.versionCode);
+ const url = await isVersionAvailable(tpStatus.versionCode);
if (url !== undefined) {
// If matched UI available show a dialog asking the user to
// switch.
@@ -174,7 +171,7 @@
switch (result) {
case MismatchedVersionDialog.Dismissed:
case MismatchedVersionDialog.UseMatchingUi:
- Router.navigateToVersion(tpStatus.versionCode);
+ navigateToVersion(tpStatus.versionCode);
return;
case MismatchedVersionDialog.UseMismatchedRpc:
break;
@@ -213,7 +210,7 @@
switch (result) {
case PreloadedDialogResult.Dismissed:
case PreloadedDialogResult.UseRpcWithPreloadedTrace:
- globals.dispatch(Actions.openTraceFromHttpRpc({}));
+ AppImpl.instance.openTraceFromHttpRpc();
return;
case PreloadedDialogResult.UseRpc:
// Resetting state is the default.
@@ -338,3 +335,42 @@
});
return result;
}
+
+function getUrlForVersion(versionCode: string): string {
+ const url = `${window.location.origin}/${versionCode}/`;
+ return url;
+}
+
+async function isVersionAvailable(
+ versionCode: string,
+): Promise<string | undefined> {
+ if (versionCode === '') {
+ return undefined;
+ }
+ const controller = new AbortController();
+ const timeoutId = setTimeout(() => controller.abort(), 1000);
+ const url = getUrlForVersion(versionCode);
+ let r;
+ try {
+ r = await fetch(url, {signal: controller.signal});
+ } catch (e) {
+ console.error(
+ `No UI version for ${versionCode} at ${url}. This is an error if ${versionCode} is a released Perfetto version`,
+ );
+ return undefined;
+ } finally {
+ clearTimeout(timeoutId);
+ }
+ if (!r.ok) {
+ return undefined;
+ }
+ return url;
+}
+
+function navigateToVersion(versionCode: string): void {
+ const url = getUrlForVersion(versionCode);
+ if (url === undefined) {
+ throw new Error(`No URL known for UI version ${versionCode}.`);
+ }
+ window.location.replace(url);
+}
diff --git a/ui/src/frontend/search_overview_track.ts b/ui/src/frontend/search_overview_track.ts
index 6329d9c..2e6bc8a 100644
--- a/ui/src/frontend/search_overview_track.ts
+++ b/ui/src/frontend/search_overview_track.ts
@@ -17,17 +17,12 @@
import {Size2D} from '../base/geom';
import {Duration, Time, TimeSpan, duration, time} from '../base/time';
import {TimeScale} from '../base/time_scale';
-import {Optional} from '../base/utils';
import {calculateResolution} from '../common/resolution';
import {TraceImpl} from '../core/trace_impl';
import {LONG, NUM} from '../trace_processor/query_result';
import {escapeSearchQuery} from '../trace_processor/query_utils';
import {createVirtualTable} from '../trace_processor/sql_utils';
-export interface SearchOverviewTrack extends AsyncDisposable {
- render(ctx: CanvasRenderingContext2D, size: Size2D): void;
-}
-
interface SearchSummary {
tsStarts: BigInt64Array;
tsEnds: BigInt64Array;
@@ -35,48 +30,62 @@
}
/**
- * This function describes a pseudo-track that renders the search overview
- * blobs.
- *
- * @returns A new search overview renderer.
+ * This component is drawn on top of the timeline and creates small yellow
+ * rectangles that highlight the time span of search results (similarly to what
+ * Chrome does on the scrollbar when you Ctrl+F and type a search term).
+ * It reacts to changes in SearchManager and queries the quantized ranges of the
+ * search results.
*/
-export async function createSearchOverviewTrack(
- trace: TraceImpl,
-): Promise<SearchOverviewTrack> {
- const trash = new AsyncDisposableStack();
- const engine = trace.engine;
- const searchManager = trace.search;
- const timeline = trace.timeline;
- trash.use(
- await createVirtualTable(engine, 'search_summary_window', 'window'),
- );
- trash.use(
- await createVirtualTable(
- engine,
- 'search_summary_sched_span',
- 'span_join(sched PARTITIONED cpu, search_summary_window)',
- ),
- );
- trash.use(
- await createVirtualTable(
- engine,
- 'search_summary_slice_span',
- 'span_join(slice PARTITIONED track_id, search_summary_window)',
- ),
- );
+export class SearchOverviewTrack implements AsyncDisposable {
+ private readonly trash = new AsyncDisposableStack();
+ private readonly trace: TraceImpl;
+ private readonly limiter = new AsyncLimiter();
+ private initialized = false;
+ private previousResolution: duration | undefined;
+ private previousSpan: TimeSpan | undefined;
+ private previousSearchGeneration = 0;
+ private searchSummary: SearchSummary | undefined;
- let previousResolution: duration;
- let previousSpan: TimeSpan;
- let previousSearchGeneration = 0;
- let searchSummary: Optional<SearchSummary>;
- const limiter = new AsyncLimiter();
+ constructor(trace: TraceImpl) {
+ this.trace = trace;
+ }
- async function update(
+ render(ctx: CanvasRenderingContext2D, size: Size2D) {
+ this.maybeUpdate(size);
+ this.renderSearchOverview(ctx, size);
+ }
+
+ private async initialize() {
+ const engine = this.trace.engine;
+ this.trash.use(
+ await createVirtualTable(engine, 'search_summary_window', 'window'),
+ );
+ this.trash.use(
+ await createVirtualTable(
+ engine,
+ 'search_summary_sched_span',
+ 'span_join(sched PARTITIONED cpu, search_summary_window)',
+ ),
+ );
+ this.trash.use(
+ await createVirtualTable(
+ engine,
+ 'search_summary_slice_span',
+ 'span_join(slice PARTITIONED track_id, search_summary_window)',
+ ),
+ );
+ }
+
+ private async update(
search: string,
start: time,
end: time,
resolution: duration,
): Promise<SearchSummary> {
+ if (!this.initialized) {
+ this.initialized = true;
+ await this.initialize();
+ }
const searchLiteral = escapeSearchQuery(search);
const resolutionScalingFactor = 10n;
@@ -84,6 +93,7 @@
start = Time.quantFloor(start, quantum);
const windowDur = Duration.max(Time.diff(end, start), 1n);
+ const engine = this.trace.engine;
await engine.query(`update search_summary_window set
window_start=${start},
window_dur=${windowDur},
@@ -134,7 +144,9 @@
return summary;
}
- function maybeUpdate(size: Size2D) {
+ private maybeUpdate(size: Size2D) {
+ const searchManager = this.trace.search;
+ const timeline = this.trace.timeline;
if (!searchManager.hasResults) {
return;
}
@@ -143,9 +155,9 @@
const newResolution = calculateResolution(newSpan, size.width);
const newTimeSpan = newSpan.toTimeSpan();
if (
- previousSpan?.containsSpan(newTimeSpan.start, newTimeSpan.end) &&
- previousResolution === newResolution &&
- previousSearchGeneration === newSearchGeneration
+ this.previousSpan?.containsSpan(newTimeSpan.start, newTimeSpan.end) &&
+ this.previousResolution === newResolution &&
+ this.previousSearchGeneration === newSearchGeneration
) {
return;
}
@@ -154,12 +166,12 @@
// that is not easily available here.
// N.B. Timestamps can be negative.
const {start, end} = newTimeSpan.pad(newTimeSpan.duration);
- previousSpan = new TimeSpan(start, end);
- previousResolution = newResolution;
- previousSearchGeneration = newSearchGeneration;
+ this.previousSpan = new TimeSpan(start, end);
+ this.previousResolution = newResolution;
+ this.previousSearchGeneration = newSearchGeneration;
const search = searchManager.searchText;
if (search === '') {
- searchSummary = {
+ this.searchSummary = {
tsStarts: new BigInt64Array(0),
tsEnds: new BigInt64Array(0),
count: new Uint8Array(0),
@@ -167,32 +179,32 @@
return;
}
- limiter.schedule(async () => {
- const summary = await update(
+ this.limiter.schedule(async () => {
+ const summary = await this.update(
searchManager.searchText,
start,
end,
newResolution,
);
- searchSummary = summary;
+ this.searchSummary = summary;
});
}
- function renderSearchOverview(
+ private renderSearchOverview(
ctx: CanvasRenderingContext2D,
size: Size2D,
): void {
- const visibleWindow = timeline.visibleWindow;
+ const visibleWindow = this.trace.timeline.visibleWindow;
const timescale = new TimeScale(visibleWindow, {
left: 0,
right: size.width,
});
- if (!searchSummary) return;
+ if (!this.searchSummary) return;
- for (let i = 0; i < searchSummary.tsStarts.length; i++) {
- const tStart = Time.fromRaw(searchSummary.tsStarts[i]);
- const tEnd = Time.fromRaw(searchSummary.tsEnds[i]);
+ for (let i = 0; i < this.searchSummary.tsStarts.length; i++) {
+ const tStart = Time.fromRaw(this.searchSummary.tsStarts[i]);
+ const tEnd = Time.fromRaw(this.searchSummary.tsEnds[i]);
if (!visibleWindow.overlaps(tStart, tEnd)) {
continue;
}
@@ -206,11 +218,11 @@
size.height,
);
}
- const results = searchManager.searchResults;
+ const results = this.trace.search.searchResults;
if (results === undefined) {
return;
}
- const index = searchManager.resultIndex;
+ const index = this.trace.search.resultIndex;
if (index !== -1 && index < results.tses.length) {
const start = results.tses[index];
if (start !== -1n) {
@@ -232,13 +244,7 @@
ctx.restore();
}
- return {
- render(ctx: CanvasRenderingContext2D, size: Size2D) {
- maybeUpdate(size);
- renderSearchOverview(ctx, size);
- },
- async [Symbol.asyncDispose](): Promise<void> {
- return await trash.asyncDispose();
- },
- };
+ async [Symbol.asyncDispose](): Promise<void> {
+ return await this.trash.asyncDispose();
+ }
}
diff --git a/ui/src/frontend/service_worker_controller.ts b/ui/src/frontend/service_worker_controller.ts
index 6bd3b81..a246a61 100644
--- a/ui/src/frontend/service_worker_controller.ts
+++ b/ui/src/frontend/service_worker_controller.ts
@@ -18,6 +18,7 @@
// The actual service worker code is in src/service_worker.
// Design doc: http://go/perfetto-offline.
+import {getServingRoot} from '../base/http_utils';
import {reportError} from '../base/logging';
import {raf} from '../core/raf_scheduler';
@@ -52,11 +53,10 @@
}
export class ServiceWorkerController {
+ private readonly servingRoot = getServingRoot();
private _bypassed = false;
private _installing = false;
- constructor(private servingRoot: string) {}
-
// Caller should reload().
async setBypass(bypass: boolean) {
if (!('serviceWorker' in navigator)) return; // Not supported.
diff --git a/ui/src/frontend/sidebar.ts b/ui/src/frontend/sidebar.ts
index 24dd203..8ece2d3 100644
--- a/ui/src/frontend/sidebar.ts
+++ b/ui/src/frontend/sidebar.ts
@@ -13,43 +13,39 @@
// limitations under the License.
import m from 'mithril';
-import {assertExists, assertTrue} from '../base/logging';
-import {isString} from '../base/object_utils';
-import {Actions} from '../common/actions';
-import {getCurrentChannel} from '../common/channels';
-import {TRACE_SUFFIX} from '../common/constants';
-import {ConversionJobStatus} from '../common/conversion_jobs';
+import {getCurrentChannel} from '../core/channels';
+import {TRACE_SUFFIX} from '../public/trace';
import {
disableMetatracingAndGetTrace,
enableMetatracing,
isMetatracingEnabled,
-} from '../common/metatracing';
-import {EngineMode} from '../common/state';
+} from '../core/metatracing';
+import {Engine, EngineMode} from '../trace_processor/engine';
import {featureFlags} from '../core/feature_flags';
import {raf} from '../core/raf_scheduler';
import {SCM_REVISION, VERSION} from '../gen/perfetto_version';
-import {EngineBase} from '../trace_processor/engine';
import {showModal} from '../widgets/modal';
import {Animation} from './animation';
import {downloadData, downloadUrl} from './download_utils';
import {globals} from './globals';
import {toggleHelp} from './help_modal';
-import {Router} from './router';
-import {
- createTraceLink,
- isDownloadable,
- isTraceLoaded,
- shareTrace,
-} from './trace_attrs';
+import {shareTrace} from './trace_share_utils';
import {
convertTraceToJsonAndDownload,
convertTraceToSystraceAndDownload,
} from './trace_converter';
import {openInOldUIWithSizeCheck} from './legacy_trace_viewer';
-import {formatHotkey} from '../base/hotkeys';
-import {SidebarMenuItem} from '../public/sidebar';
+import {SIDEBAR_SECTIONS, SidebarSections} from '../public/sidebar';
import {AppImpl} from '../core/app_impl';
import {Trace} from '../public/trace';
+import {OptionalTraceImplAttrs, TraceImpl} from '../core/trace_impl';
+import {Command} from '../public/command';
+import {SidebarMenuItemInternal} from '../core/sidebar_manager';
+import {exists, getOrCreate} from '../base/utils';
+import {copyToClipboard} from '../base/clipboard';
+import {classNames} from '../base/classnames';
+import {formatHotkey} from '../base/hotkeys';
+import {assetSrc} from '../base/assets';
const GITILES_URL =
'https://android.googlesource.com/platform/external/perfetto';
@@ -69,381 +65,38 @@
defaultValue: false,
});
-const WIDGETS_PAGE_IN_NAV_FLAG = featureFlags.register({
- id: 'showWidgetsPageInNav',
- name: 'Show widgets page',
- description: 'Show a link to the widgets page in the side bar.',
- defaultValue: false,
-});
-
-const PLUGINS_PAGE_IN_NAV_FLAG = featureFlags.register({
- id: 'showPluginsPageInNav',
- name: 'Show plugins page',
- description: 'Show a link to the plugins page in the side bar.',
- defaultValue: false,
-});
-
-const INSIGHTS_PAGE_IN_NAV_FLAG = featureFlags.register({
- id: 'showInsightsPageInNav',
- name: 'Show insights page',
- description: 'Show a link to the insights page in the side bar.',
- defaultValue: false,
-});
-
-const VIZ_PAGE_IN_NAV_FLAG = featureFlags.register({
- id: 'showVizPageInNav',
- name: 'Show viz page',
- description: 'Show a link to the viz page in the side bar.',
- defaultValue: true,
-});
-
-export interface OptionalTraceAttrs {
- trace?: Trace;
-}
-
function shouldShowHiringBanner(): boolean {
return globals.isInternalUser && HIRING_BANNER_FLAG.get();
}
-interface SectionItem {
- t: string;
- a: string | ((e: Event) => void);
- i: string;
- title?: string;
- isPending?: () => boolean;
- isVisible?: () => boolean;
- internalUserOnly?: boolean;
- checkDownloadDisabled?: boolean;
- checkMetatracingEnabled?: boolean;
- checkMetatracingDisabled?: boolean;
+async function openCurrentTraceWithOldUI(trace: Trace): Promise<void> {
+ AppImpl.instance.analytics.logEvent(
+ 'Trace Actions',
+ 'Open current trace in legacy UI',
+ );
+ const file = await trace.getTraceFile();
+ await openInOldUIWithSizeCheck(file);
}
-interface Section {
- title: string;
- summary: string;
- items: SectionItem[];
- expanded?: boolean;
- hideIfNoTraceLoaded?: boolean;
- appendOpenedTraceTitle?: boolean;
+async function convertTraceToSystrace(trace: Trace): Promise<void> {
+ AppImpl.instance.analytics.logEvent('Trace Actions', 'Convert to .systrace');
+ const file = await trace.getTraceFile();
+ await convertTraceToSystraceAndDownload(file);
}
-function insertSidebarMenuitems(
- groupSelector: SidebarMenuItem['group'],
-): ReadonlyArray<SectionItem> {
- return AppImpl.instance.sidebar.menuItems
- .valuesAsArray()
- .filter(({group}) => group === groupSelector)
- .sort((a, b) => {
- const prioA = a.priority ?? 0;
- const prioB = b.priority ?? 0;
- return prioA - prioB;
- })
- .map((item) => {
- const cmd = globals.commandManager.getCommand(item.commandId);
- const title = cmd.defaultHotkey
- ? `${cmd.name} [${formatHotkey(cmd.defaultHotkey)}]`
- : cmd.name;
- return {
- t: cmd.name,
- a: (e: Event) => {
- e.preventDefault();
- cmd.callback();
- },
- i: item.icon,
- title,
- };
- });
+async function convertTraceToJson(trace: Trace): Promise<void> {
+ AppImpl.instance.analytics.logEvent('Trace Actions', 'Convert to .json');
+ const file = await trace.getTraceFile();
+ await convertTraceToJsonAndDownload(file);
}
-function getSections(): Section[] {
- return [
- {
- title: 'Navigation',
- summary: 'Open or record a new trace',
- expanded: true,
- items: [
- ...insertSidebarMenuitems('navigation'),
- {t: 'Record new trace', a: navigateRecord, i: 'fiber_smart_record'},
- {
- t: 'Widgets',
- a: navigateWidgets,
- i: 'widgets',
- isVisible: () => WIDGETS_PAGE_IN_NAV_FLAG.get(),
- },
- {
- t: 'Plugins',
- a: navigatePlugins,
- i: 'extension',
- isVisible: () => PLUGINS_PAGE_IN_NAV_FLAG.get(),
- },
- ],
- },
+function downloadTrace(trace: TraceImpl) {
+ if (!trace.traceInfo.downloadable) return;
+ AppImpl.instance.analytics.logEvent('Trace Actions', 'Download trace');
- {
- title: 'Current Trace',
- summary: 'Actions on the current trace',
- expanded: true,
- hideIfNoTraceLoaded: true,
- appendOpenedTraceTitle: true,
- items: [
- {t: 'Show timeline', a: navigateViewer, i: 'line_style'},
- {
- t: 'Share',
- a: handleShareTrace,
- i: 'share',
- internalUserOnly: true,
- isPending: () =>
- globals.getConversionJobStatus('create_permalink') ===
- ConversionJobStatus.InProgress,
- },
- {
- t: 'Download',
- a: downloadTrace,
- i: 'file_download',
- checkDownloadDisabled: true,
- },
- {t: 'Query (SQL)', a: navigateQuery, i: 'database'},
- {
- t: 'Insights',
- a: navigateInsights,
- i: 'insights',
- isVisible: () => INSIGHTS_PAGE_IN_NAV_FLAG.get(),
- },
- {
- t: 'Viz',
- a: navigateViz,
- i: 'area_chart',
- isVisible: () => VIZ_PAGE_IN_NAV_FLAG.get(),
- },
- {t: 'Metrics', a: navigateMetrics, i: 'speed'},
- {t: 'Info and stats', a: navigateInfo, i: 'info'},
- ],
- },
-
- {
- title: 'Convert trace',
- summary: 'Convert to other formats',
- expanded: true,
- hideIfNoTraceLoaded: true,
- items: [
- {
- t: 'Switch to legacy UI',
- a: openCurrentTraceWithOldUI,
- i: 'filter_none',
- isPending: () =>
- globals.getConversionJobStatus('open_in_legacy') ===
- ConversionJobStatus.InProgress,
- },
- {
- t: 'Convert to .json',
- a: convertTraceToJson,
- i: 'file_download',
- isPending: () =>
- globals.getConversionJobStatus('convert_json') ===
- ConversionJobStatus.InProgress,
- checkDownloadDisabled: true,
- },
-
- {
- t: 'Convert to .systrace',
- a: convertTraceToSystrace,
- i: 'file_download',
- isVisible: () => globals.hasFtrace,
- isPending: () =>
- globals.getConversionJobStatus('convert_systrace') ===
- ConversionJobStatus.InProgress,
- checkDownloadDisabled: true,
- },
- ],
- },
-
- {
- title: 'Example Traces',
- expanded: true,
- summary: 'Open an example trace',
- items: [...insertSidebarMenuitems('example_traces')],
- },
-
- {
- title: 'Support',
- expanded: true,
- summary: 'Documentation & Bugs',
- items: [
- {t: 'Keyboard shortcuts', a: openHelp, i: 'help'},
- {t: 'Documentation', a: 'https://perfetto.dev/docs', i: 'find_in_page'},
- {t: 'Flags', a: navigateFlags, i: 'emoji_flags'},
- {
- t: 'Report a bug',
- a: getBugReportUrl(),
- i: 'bug_report',
- },
- {
- t: 'Record metatrace',
- a: recordMetatrace,
- i: 'fiber_smart_record',
- checkMetatracingDisabled: true,
- },
- {
- t: 'Finalise metatrace',
- a: finaliseMetatrace,
- i: 'file_download',
- checkMetatracingEnabled: true,
- },
- ],
- },
- ];
-}
-
-function openHelp(e: Event) {
- e.preventDefault();
- toggleHelp();
-}
-
-function downloadTraceFromUrl(url: string): Promise<File> {
- return m.request({
- method: 'GET',
- url,
- // TODO(hjd): Once mithril is updated we can use responseType here rather
- // than using config and remove the extract below.
- config: (xhr) => {
- xhr.responseType = 'blob';
- xhr.onprogress = (progress) => {
- const percent = ((100 * progress.loaded) / progress.total).toFixed(1);
- globals.dispatch(
- Actions.updateStatus({
- msg: `Downloading trace ${percent}%`,
- timestamp: Date.now() / 1000,
- }),
- );
- };
- },
- extract: (xhr) => {
- return xhr.response;
- },
- });
-}
-
-export async function getCurrentTrace(): Promise<Blob> {
- // Caller must check engine exists.
- const src = assertExists(AppImpl.instance.trace?.traceInfo.source);
- if (src.type === 'ARRAY_BUFFER') {
- return new Blob([src.buffer]);
- } else if (src.type === 'FILE') {
- return src.file;
- } else if (src.type === 'URL') {
- return downloadTraceFromUrl(src.url);
- } else {
- throw new Error(`Loading to catapult from source with type ${src.type}`);
- }
-}
-
-function openCurrentTraceWithOldUI(e: Event) {
- e.preventDefault();
- assertTrue(isTraceLoaded());
- globals.logging.logEvent('Trace Actions', 'Open current trace in legacy UI');
- if (!isTraceLoaded()) return;
- getCurrentTrace()
- .then((file) => {
- openInOldUIWithSizeCheck(file);
- })
- .catch((error) => {
- throw new Error(`Failed to get current trace ${error}`);
- });
-}
-
-function convertTraceToSystrace(e: Event) {
- e.preventDefault();
- assertTrue(isTraceLoaded());
- globals.logging.logEvent('Trace Actions', 'Convert to .systrace');
- if (!isTraceLoaded()) return;
- getCurrentTrace()
- .then((file) => {
- convertTraceToSystraceAndDownload(file);
- })
- .catch((error) => {
- throw new Error(`Failed to get current trace ${error}`);
- });
-}
-
-function convertTraceToJson(e: Event) {
- e.preventDefault();
- assertTrue(isTraceLoaded());
- globals.logging.logEvent('Trace Actions', 'Convert to .json');
- if (!isTraceLoaded()) return;
- getCurrentTrace()
- .then((file) => {
- convertTraceToJsonAndDownload(file);
- })
- .catch((error) => {
- throw new Error(`Failed to get current trace ${error}`);
- });
-}
-
-function navigateRecord(e: Event) {
- e.preventDefault();
- Router.navigate('#!/record');
-}
-
-function navigateWidgets(e: Event) {
- e.preventDefault();
- Router.navigate('#!/widgets');
-}
-
-function navigatePlugins(e: Event) {
- e.preventDefault();
- Router.navigate('#!/plugins');
-}
-
-function navigateQuery(e: Event) {
- e.preventDefault();
- Router.navigate('#!/query');
-}
-
-function navigateInsights(e: Event) {
- e.preventDefault();
- Router.navigate('#!/insights');
-}
-
-function navigateViz(e: Event) {
- e.preventDefault();
- Router.navigate('#!/viz');
-}
-
-function navigateFlags(e: Event) {
- e.preventDefault();
- Router.navigate('#!/flags');
-}
-
-function navigateMetrics(e: Event) {
- e.preventDefault();
- Router.navigate('#!/metrics');
-}
-
-function navigateInfo(e: Event) {
- e.preventDefault();
- Router.navigate('#!/info');
-}
-
-function navigateViewer(e: Event) {
- e.preventDefault();
- Router.navigate('#!/viewer');
-}
-
-function handleShareTrace(e: Event) {
- e.preventDefault();
- shareTrace();
-}
-
-function downloadTrace(e: Event) {
- e.preventDefault();
- if (!isDownloadable() || !isTraceLoaded()) return;
- globals.logging.logEvent('Trace Actions', 'Download trace');
-
- const engine = globals.getCurrentEngine();
- if (!engine) return;
let url = '';
let fileName = `trace${TRACE_SUFFIX}`;
- const src = engine.source;
+ const src = trace.traceInfo.source;
if (src.type === 'URL') {
url = src.url;
fileName = url.split('/').slice(-1)[0];
@@ -468,27 +121,17 @@
downloadUrl(fileName, url);
}
-function getCurrentEngine(): EngineBase | undefined {
- const engineId = globals.getCurrentEngine()?.id;
- if (engineId === undefined) return undefined;
- return globals.engines.get(engineId);
-}
-
function highPrecisionTimersAvailable(): boolean {
// High precision timers are available either when the page is cross-origin
// isolated or when the trace processor is a standalone binary.
return (
window.crossOriginIsolated ||
- globals.getCurrentEngine()?.mode === 'HTTP_RPC'
+ AppImpl.instance.trace?.engine.mode === 'HTTP_RPC'
);
}
-function recordMetatrace(e: Event) {
- e.preventDefault();
- globals.logging.logEvent('Trace Actions', 'Record metatrace');
-
- const engine = getCurrentEngine();
- if (!engine) return;
+function recordMetatrace(engine: Engine) {
+ AppImpl.instance.analytics.logEvent('Trace Actions', 'Record metatrace');
if (!highPrecisionTimersAvailable()) {
const PROMPT = `High-precision timers are not available to WASM trace processor yet.
@@ -524,15 +167,15 @@
}
}
-async function finaliseMetatrace(e: Event) {
- e.preventDefault();
- globals.logging.logEvent('Trace Actions', 'Finalise metatrace');
+async function toggleMetatrace(e: Engine) {
+ return isMetatracingEnabled() ? finaliseMetatrace(e) : recordMetatrace(e);
+}
+
+async function finaliseMetatrace(engine: Engine) {
+ AppImpl.instance.analytics.logEvent('Trace Actions', 'Finalise metatrace');
const jsEvents = disableMetatracingAndGetTrace();
- const engine = getCurrentEngine();
- if (!engine) return;
-
const result = await engine.stopAndGetMetatrace();
if (result.error.length !== 0) {
throw new Error(`Failed to read metatrace: ${result.error}`);
@@ -541,20 +184,20 @@
downloadData('metatrace', result.metatrace, jsEvents);
}
-class EngineRPCWidget implements m.ClassComponent<OptionalTraceAttrs> {
- view({attrs}: m.CVnode<OptionalTraceAttrs>) {
+class EngineRPCWidget implements m.ClassComponent<OptionalTraceImplAttrs> {
+ view({attrs}: m.CVnode<OptionalTraceImplAttrs>) {
let cssClass = '';
let title = 'Number of pending SQL queries';
let label: string;
let failed = false;
let mode: EngineMode | undefined;
- const engineCfg = globals.state.engine;
- if (engineCfg !== undefined) {
- mode = engineCfg.mode;
- if (engineCfg.failed !== undefined) {
+ const engine = attrs.trace?.engine;
+ if (engine !== undefined) {
+ mode = engine.mode;
+ if (engine.failed !== undefined) {
cssClass += '.red';
- title = 'Query engine crashed\n' + engineCfg.failed;
+ title = 'Query engine crashed\n' + engine.failed;
failed = true;
}
}
@@ -566,8 +209,8 @@
// this will eventually become consistent once the engine is created.
if (mode === undefined) {
if (
- globals.httpRpcState.connected &&
- globals.state.newEngineMode === 'USE_HTTP_RPC_IF_AVAILABLE'
+ AppImpl.instance.httpRpc.httpRpcAvailable &&
+ AppImpl.instance.httpRpc.newEngineMode === 'USE_HTTP_RPC_IF_AVAILABLE'
) {
mode = 'HTTP_RPC';
} else {
@@ -599,7 +242,7 @@
let cssClass = '';
let title = 'Service Worker: ';
let label = 'N/A';
- const ctl = globals.serviceWorkerController;
+ const ctl = AppImpl.instance.serviceWorkerController;
if (!('serviceWorker' in navigator)) {
label = 'N/A';
title += 'not supported by the browser (requires HTTPS)';
@@ -621,8 +264,8 @@
}
const toggle = async () => {
- if (globals.serviceWorkerController.bypassed) {
- globals.serviceWorkerController.setBypass(false);
+ if (ctl.bypassed) {
+ ctl.setBypass(false);
return;
}
showModal({
@@ -654,11 +297,7 @@
{
text: 'Disable and reload',
primary: true,
- action: () => {
- globals.serviceWorkerController
- .setBypass(true)
- .then(() => location.reload());
- },
+ action: () => ctl.setBypass(true).then(() => location.reload()),
},
{text: 'Cancel'},
],
@@ -674,8 +313,8 @@
},
};
-class SidebarFooter implements m.ClassComponent<OptionalTraceAttrs> {
- view({attrs}: m.CVnode<OptionalTraceAttrs>) {
+class SidebarFooter implements m.ClassComponent<OptionalTraceImplAttrs> {
+ view({attrs}: m.CVnode<OptionalTraceImplAttrs>) {
return m(
'.sidebar-footer',
m(EngineRPCWidget, attrs),
@@ -712,105 +351,22 @@
}
}
-export class Sidebar implements m.ClassComponent<OptionalTraceAttrs> {
+export class Sidebar implements m.ClassComponent<OptionalTraceImplAttrs> {
private _redrawWhileAnimating = new Animation(() => raf.scheduleFullRedraw());
- view({attrs}: m.CVnode<OptionalTraceAttrs>) {
- if (globals.hideSidebar) return null;
- const vdomSections = [];
- for (const section of getSections()) {
- if (section.hideIfNoTraceLoaded && !isTraceLoaded()) continue;
- const vdomItems = [];
- for (const item of section.items) {
- if (item.isVisible !== undefined && !item.isVisible()) {
- continue;
- }
- let css = '';
- let attrs = {
- onclick: typeof item.a === 'function' ? item.a : null,
- href: isString(item.a) ? item.a : '#',
- target: isString(item.a) ? '_blank' : null,
- disabled: false,
- id: item.t.toLowerCase().replace(/[^\w]/g, '_'),
- };
- if (item.isPending && item.isPending()) {
- attrs.onclick = (e) => e.preventDefault();
- css = '.pending';
- }
- if (item.internalUserOnly && !globals.isInternalUser) {
- continue;
- }
- if (item.checkMetatracingEnabled || item.checkMetatracingDisabled) {
- if (
- item.checkMetatracingEnabled === true &&
- !isMetatracingEnabled()
- ) {
- continue;
- }
- if (
- item.checkMetatracingDisabled === true &&
- isMetatracingEnabled()
- ) {
- continue;
- }
- if (
- item.checkMetatracingDisabled &&
- !highPrecisionTimersAvailable()
- ) {
- attrs.disabled = true;
- }
- }
- if (item.checkDownloadDisabled && !isDownloadable()) {
- attrs = {
- onclick: (e) => {
- e.preventDefault();
- alert('Can not download external trace.');
- },
- href: '#',
- target: null,
- disabled: true,
- id: '',
- };
- }
- vdomItems.push(
- m(
- 'li',
- m(
- `a${css}`,
- {...attrs, title: item.title},
- m('i.material-icons', item.i),
- item.t,
- ),
- ),
- );
- }
- if (section.appendOpenedTraceTitle) {
- if (globals.traceContext.traceTitle) {
- const {traceTitle, traceUrl} = globals.traceContext;
- vdomItems.unshift(m('li', createTraceLink(traceTitle, traceUrl)));
- }
- }
- vdomSections.push(
- m(
- `section${section.expanded ? '.expanded' : ''}`,
- m(
- '.section-header',
- {
- onclick: () => {
- section.expanded = !section.expanded;
- raf.scheduleFullRedraw();
- },
- },
- m('h1', {title: section.summary}, section.title),
- m('h2', section.summary),
- ),
- m('.section-content', m('ul', vdomItems)),
- ),
- );
- }
+ private _asyncJobPending = new Set<string>();
+ private _sectionExpanded = new Map<string, boolean>();
+
+ constructor() {
+ registerMenuItems();
+ }
+
+ view({attrs}: m.CVnode<OptionalTraceImplAttrs>) {
+ const sidebar = AppImpl.instance.sidebar;
+ if (!sidebar.enabled) return null;
return m(
'nav.sidebar',
{
- class: globals.state.sidebarVisible ? 'show-sidebar' : 'hide-sidebar',
+ class: sidebar.visible ? 'show-sidebar' : 'hide-sidebar',
// 150 here matches --sidebar-timing in the css.
// TODO(hjd): Should link to the CSS variable.
ontransitionstart: (e: TransitionEvent) => {
@@ -825,20 +381,16 @@
shouldShowHiringBanner() ? m(HiringBanner) : null,
m(
`header.${getCurrentChannel()}`,
- m(`img[src=${globals.root}assets/brand.png].brand`),
+ m(`img[src=${assetSrc('assets/brand.png')}].brand`),
m(
'button.sidebar-button',
{
- onclick: () => {
- globals.commandManager.runCommand(
- 'perfetto.CoreCommands#ToggleLeftSidebar',
- );
- },
+ onclick: () => sidebar.toggleVisibility(),
},
m(
'i.material-icons',
{
- title: globals.state.sidebarVisible ? 'Hide menu' : 'Show menu',
+ title: sidebar.visible ? 'Hide menu' : 'Show menu',
},
'menu',
),
@@ -848,10 +400,252 @@
'.sidebar-scroll',
m(
'.sidebar-scroll-container',
- ...vdomSections,
+ ...(Object.keys(SIDEBAR_SECTIONS) as SidebarSections[]).map((s) =>
+ this.renderSection(s),
+ ),
m(SidebarFooter, attrs),
),
),
);
}
+
+ private renderSection(sectionId: SidebarSections) {
+ const section = SIDEBAR_SECTIONS[sectionId];
+ const menuItems = AppImpl.instance.sidebar.menuItems
+ .valuesAsArray()
+ .filter((item) => item.section === sectionId)
+ .sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0))
+ .map((item) => this.renderItem(item));
+
+ // Don't render empty sections.
+ if (menuItems.length === 0) return undefined;
+
+ const expanded = getOrCreate(this._sectionExpanded, sectionId, () => true);
+ return m(
+ `section${expanded ? '.expanded' : ''}`,
+ m(
+ '.section-header',
+ {
+ onclick: () => {
+ this._sectionExpanded.set(sectionId, !expanded);
+ raf.scheduleFullRedraw();
+ },
+ },
+ m('h1', {title: section.title}, section.title),
+ m('h2', section.summary),
+ ),
+ m('.section-content', m('ul', menuItems)),
+ );
+ }
+
+ private renderItem(item: SidebarMenuItemInternal): m.Child {
+ let href = '#';
+ let disabled = false;
+ let target = null;
+ let command: Command | undefined = undefined;
+ let tooltip = valueOrCallback(item.tooltip);
+ let onclick: (() => unknown | Promise<unknown>) | undefined = undefined;
+ const commandId = 'commandId' in item ? item.commandId : undefined;
+ const action = 'action' in item ? item.action : undefined;
+ let text = valueOrCallback(item.text);
+ const disabReason: boolean | string | undefined = valueOrCallback(
+ item.disabled,
+ );
+
+ if (disabReason === true || typeof disabReason === 'string') {
+ disabled = true;
+ onclick = () => typeof disabReason === 'string' && alert(disabReason);
+ } else if (action !== undefined) {
+ onclick = action;
+ } else if (commandId !== undefined) {
+ const cmdMgr = AppImpl.instance.commands;
+ command = cmdMgr.hasCommand(commandId ?? '')
+ ? cmdMgr.getCommand(commandId)
+ : undefined;
+ if (command === undefined) {
+ disabled = true;
+ } else {
+ text = text !== undefined ? text : command.name;
+ if (command.defaultHotkey !== undefined) {
+ tooltip =
+ `${tooltip ?? command.name}` +
+ ` [${formatHotkey(command.defaultHotkey)}]`;
+ }
+ onclick = () => cmdMgr.runCommand(commandId);
+ }
+ }
+
+ // This is not an else if because in some rare cases the user might want
+ // to have both an href and onclick, with different behaviors. The only case
+ // today is the trace name / URL, where we want the URL in the href to
+ // support right-click -> copy URL, but the onclick does copyToClipboard().
+ if ('href' in item && item.href !== undefined) {
+ href = item.href;
+ target = href.startsWith('#') ? null : '_blank';
+ }
+ return m(
+ 'li',
+ m(
+ 'a',
+ {
+ className: classNames(
+ valueOrCallback(item.cssClass),
+ this._asyncJobPending.has(item.id) && 'pending',
+ ),
+ onclick: onclick && this.wrapClickHandler(item.id, onclick),
+ href,
+ target,
+ disabled,
+ title: tooltip,
+ },
+ exists(item.icon) && m('i.material-icons', valueOrCallback(item.icon)),
+ text,
+ ),
+ );
+ }
+
+ // Creates the onClick handlers for the items which provided a function in the
+ // `action` member. The function can be either sync or async.
+ // What we want to achieve here is the following:
+ // - If the action is async (returns a Promise), we want to render a spinner,
+ // next to the menu item, until the promise is resolved.
+ // - [Minor] we want to call e.preventDefault() to override the behaviour of
+ // the <a href='#'> which gets rendered for accessibility reasons.
+ private wrapClickHandler(itemId: string, itemAction: Function) {
+ return (e: Event) => {
+ e.preventDefault(); // Make the <a href="#"> a no-op.
+ const res = itemAction();
+ if (!(res instanceof Promise)) return;
+ if (this._asyncJobPending.has(itemId)) {
+ return; // Don't queue up another action if not yet finished.
+ }
+ this._asyncJobPending.add(itemId);
+ raf.scheduleFullRedraw();
+ res.finally(() => {
+ this._asyncJobPending.delete(itemId);
+ raf.scheduleFullRedraw();
+ });
+ };
+ }
+}
+
+// TODO(primiano): The registrations below should be moved to dedicated
+// plugins (most of this really belongs to core_plugins/commads/index.ts).
+// For now i'm keeping everything here as splitting these require moving some
+// functions like share_trace() out of core, splitting out permalink, etc.
+
+let globalItemsRegistered = false;
+const traceItemsRegistered = new WeakSet<TraceImpl>();
+
+function registerMenuItems() {
+ if (!globalItemsRegistered) {
+ globalItemsRegistered = true;
+ registerGlobalSidebarEntries();
+ }
+ const trace = AppImpl.instance.trace;
+ if (trace !== undefined && !traceItemsRegistered.has(trace)) {
+ traceItemsRegistered.add(trace);
+ registerTraceMenuItems(trace);
+ }
+}
+
+function registerGlobalSidebarEntries() {
+ const app = AppImpl.instance;
+ // TODO(primiano): The Open file / Open with legacy entries are registered by
+ // the 'perfetto.CoreCommands' plugins. Make things consistent.
+ app.sidebar.addMenuItem({
+ section: 'support',
+ text: 'Keyboard shortcuts',
+ action: toggleHelp,
+ icon: 'help',
+ });
+ app.sidebar.addMenuItem({
+ section: 'support',
+ text: 'Documentation',
+ href: 'https://perfetto.dev/docs',
+ icon: 'find_in_page',
+ });
+ app.sidebar.addMenuItem({
+ section: 'support',
+ sortOrder: 4,
+ text: 'Report a bug',
+ href: getBugReportUrl(),
+ icon: 'bug_report',
+ });
+}
+
+function registerTraceMenuItems(trace: TraceImpl) {
+ const downloadDisabled = trace.traceInfo.downloadable
+ ? false
+ : 'Cannot download external trace';
+
+ const traceTitle = trace?.traceInfo.traceTitle;
+ traceTitle &&
+ trace.sidebar.addMenuItem({
+ section: 'current_trace',
+ text: traceTitle,
+ href: trace.traceInfo.traceUrl,
+ action: () => copyToClipboard(trace.traceInfo.traceUrl),
+ tooltip: 'Click to copy the URL',
+ cssClass: 'trace-file-name',
+ });
+ trace.sidebar.addMenuItem({
+ section: 'current_trace',
+ text: 'Show timeline',
+ href: '#!/viewer',
+ icon: 'line_style',
+ });
+ globals.isInternalUser &&
+ trace.sidebar.addMenuItem({
+ section: 'current_trace',
+ text: 'Share',
+ action: async () => await shareTrace(trace),
+ icon: 'share',
+ });
+ trace.sidebar.addMenuItem({
+ section: 'current_trace',
+ text: 'Download',
+ action: () => downloadTrace(trace),
+ icon: 'file_download',
+ disabled: downloadDisabled,
+ });
+ trace.sidebar.addMenuItem({
+ section: 'convert_trace',
+ text: 'Switch to legacy UI',
+ action: async () => await openCurrentTraceWithOldUI(trace),
+ icon: 'filter_none',
+ disabled: downloadDisabled,
+ });
+ trace.sidebar.addMenuItem({
+ section: 'convert_trace',
+ text: 'Convert to .json',
+ action: async () => await convertTraceToJson(trace),
+ icon: 'file_download',
+ disabled: downloadDisabled,
+ });
+ trace.traceInfo.hasFtrace &&
+ trace.sidebar.addMenuItem({
+ section: 'convert_trace',
+ text: 'Convert to .systrace',
+ action: async () => await convertTraceToSystrace(trace),
+ icon: 'file_download',
+ disabled: downloadDisabled,
+ });
+ trace.sidebar.addMenuItem({
+ section: 'support',
+ sortOrder: 5,
+ text: () =>
+ isMetatracingEnabled() ? 'Finalize metatrace' : 'Record metatrace',
+ action: () => toggleMetatrace(trace.engine),
+ icon: () => (isMetatracingEnabled() ? 'download' : 'fiber_smart_record'),
+ });
+}
+
+// Used to deal with fields like the entry name, which can be either a direct
+// string or a callback that returns the string.
+function valueOrCallback<T>(value: T | (() => T)): T;
+function valueOrCallback<T>(value: T | (() => T) | undefined): T | undefined;
+function valueOrCallback<T>(value: T | (() => T) | undefined): T | undefined {
+ if (value === undefined) return undefined;
+ return value instanceof Function ? value() : value;
}
diff --git a/ui/src/frontend/simple_counter_track.ts b/ui/src/frontend/simple_counter_track.ts
deleted file mode 100644
index aca8f7e..0000000
--- a/ui/src/frontend/simple_counter_track.ts
+++ /dev/null
@@ -1,69 +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 {TrackContext} from '../public/track';
-import {BaseCounterTrack, CounterOptions} from './base_counter_track';
-import {
- CounterColumns,
- SqlDataSource,
-} from '../public/lib/debug_tracks/debug_tracks';
-import {uuidv4Sql} from '../base/uuid';
-import {createPerfettoTable} from '../trace_processor/sql_utils';
-import {Trace} from '../public/trace';
-
-export type SimpleCounterTrackConfig = {
- data: SqlDataSource;
- columns: CounterColumns;
- options?: Partial<CounterOptions>;
-};
-
-export class SimpleCounterTrack extends BaseCounterTrack {
- private config: SimpleCounterTrackConfig;
- private sqlTableName: string;
-
- constructor(
- trace: Trace,
- ctx: TrackContext,
- config: SimpleCounterTrackConfig,
- ) {
- super({
- trace,
- uri: ctx.trackUri,
- options: config.options,
- });
- this.config = config;
- this.sqlTableName = `__simple_counter_${uuidv4Sql()}`;
- }
-
- async onInit() {
- return await createPerfettoTable(
- this.engine,
- this.sqlTableName,
- `
- with data as (
- ${this.config.data.sqlSource}
- )
- select
- ${this.config.columns.ts} as ts,
- ${this.config.columns.value} as value
- from data
- order by ts
- `,
- );
- }
-
- getSqlSource(): string {
- return `select * from ${this.sqlTableName}`;
- }
-}
diff --git a/ui/src/frontend/simple_slice_track.ts b/ui/src/frontend/simple_slice_track.ts
deleted file mode 100644
index 484b834..0000000
--- a/ui/src/frontend/simple_slice_track.ts
+++ /dev/null
@@ -1,115 +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 {TrackContext} from '../public/track';
-import {
- CustomSqlDetailsPanelConfig,
- CustomSqlTableDefConfig,
- CustomSqlTableSliceTrack,
-} from './tracks/custom_sql_table_slice_track';
-import {
- SliceColumns,
- SqlDataSource,
-} from '../public/lib/debug_tracks/debug_tracks';
-import {uuidv4Sql} from '../base/uuid';
-import {
- ARG_PREFIX,
- DebugSliceDetailsTab,
-} from '../public/lib/debug_tracks/details_tab';
-import {createPerfettoTable} from '../trace_processor/sql_utils';
-import {Trace} from '../public/trace';
-
-export interface SimpleSliceTrackConfig {
- data: SqlDataSource;
- columns: SliceColumns;
- argColumns: string[];
-}
-
-export class SimpleSliceTrack extends CustomSqlTableSliceTrack {
- private config: SimpleSliceTrackConfig;
- private sqlTableName: string;
-
- constructor(trace: Trace, ctx: TrackContext, config: SimpleSliceTrackConfig) {
- super({
- trace,
- uri: ctx.trackUri,
- });
-
- this.config = config;
- this.sqlTableName = `__simple_slice_${uuidv4Sql(ctx.trackUri)}`;
- }
-
- async getSqlDataSource(): Promise<CustomSqlTableDefConfig> {
- const table = await createPerfettoTable(
- this.engine,
- this.sqlTableName,
- this.createTableQuery(
- this.config.data,
- this.config.columns,
- this.config.argColumns,
- ),
- );
- return {
- sqlTableName: this.sqlTableName,
- disposable: table,
- };
- }
-
- getDetailsPanel(): CustomSqlDetailsPanelConfig {
- // We currently borrow the debug slice details tab.
- // TODO: Don't do this!
- return {
- kind: DebugSliceDetailsTab.kind,
- config: {
- sqlTableName: this.sqlTableName,
- title: 'Debug Slice',
- },
- };
- }
-
- private createTableQuery(
- data: SqlDataSource,
- sliceColumns: SliceColumns,
- argColumns: string[],
- ): string {
- // If the view has clashing names (e.g. "name" coming from joining two
- // different tables, we will see names like "name_1", "name_2", but they
- // won't be addressable from the SQL. So we explicitly name them through a
- // list of columns passed to CTE.
- const dataColumns =
- data.columns !== undefined ? `(${data.columns.join(', ')})` : '';
-
- // TODO(altimin): Support removing this table when the track is closed.
- const dur = sliceColumns.dur === '0' ? 0 : sliceColumns.dur;
- return `
- with data${dataColumns} as (
- ${data.sqlSource}
- ),
- prepared_data as (
- select
- ${sliceColumns.ts} as ts,
- ifnull(cast(${dur} as int), -1) as dur,
- printf('%s', ${sliceColumns.name}) as name
- ${argColumns.length > 0 ? ',' : ''}
- ${argColumns.map((c) => `${c} as ${ARG_PREFIX}${c}`).join(',\n')}
- from data
- )
- select
- row_number() over (order by ts) as id,
- *
- from prepared_data
- order by ts
- `;
- }
-}
diff --git a/ui/src/frontend/slice_args.ts b/ui/src/frontend/slice_args.ts
index dff2495..88ecc65 100644
--- a/ui/src/frontend/slice_args.ts
+++ b/ui/src/frontend/slice_args.ts
@@ -17,16 +17,15 @@
import {Icons} from '../base/semantic_icons';
import {sqliteString} from '../base/string_utils';
import {exists} from '../base/utils';
-import {ArgNode, convertArgsToTree, Key} from '../controller/args_parser';
-import {addVisualisedArgTracks} from './visualized_args_tracks';
+import {ArgNode, convertArgsToTree, Key} from './slice_args_parser';
import {Anchor} from '../widgets/anchor';
import {MenuItem, PopupMenu2} from '../widgets/menu';
import {TreeNode} from '../widgets/tree';
import {Arg} from '../trace_processor/sql_utils/args';
-import {addSqlTableTab} from './sql_table_tab_interface';
import {assertExists} from '../base/logging';
import {getSqlTableDescription} from './widgets/sql/table/sql_table_registry';
import {Trace} from '../public/trace';
+import {extensions} from '../public/lib/extensions';
// Renders slice arguments (key/value pairs) as a subtree.
export function renderArguments(trace: Trace, args: Arg[]): m.Children {
@@ -84,7 +83,7 @@
label: 'Find slices with same arg value',
icon: 'search',
onclick: () => {
- addSqlTableTab(trace, {
+ extensions.addSqlTableTab(trace, {
table: assertExists(getSqlTableDescription('slice')),
filters: [
{
@@ -107,10 +106,10 @@
},
}),
m(MenuItem, {
- label: 'Visualise argument values',
+ label: 'Visualize argument values',
icon: 'query_stats',
onclick: () => {
- addVisualisedArgTracks(trace, fullKey);
+ extensions.addVisualizedArgTracks(trace, fullKey);
},
}),
);
diff --git a/ui/src/controller/args_parser.ts b/ui/src/frontend/slice_args_parser.ts
similarity index 100%
rename from ui/src/controller/args_parser.ts
rename to ui/src/frontend/slice_args_parser.ts
diff --git a/ui/src/controller/args_parser_unittest.ts b/ui/src/frontend/slice_args_parser_unittest.ts
similarity index 98%
rename from ui/src/controller/args_parser_unittest.ts
rename to ui/src/frontend/slice_args_parser_unittest.ts
index ca0cb31..ba6a6f0 100644
--- a/ui/src/controller/args_parser_unittest.ts
+++ b/ui/src/frontend/slice_args_parser_unittest.ts
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {convertArgsToObject, convertArgsToTree} from './args_parser';
+import {convertArgsToObject, convertArgsToTree} from './slice_args_parser';
const args = [
{key: 'simple_key', value: 'simple_value'},
diff --git a/ui/src/frontend/slice_details.ts b/ui/src/frontend/slice_details.ts
index 652d36d..784e5b1 100644
--- a/ui/src/frontend/slice_details.ts
+++ b/ui/src/frontend/slice_details.ts
@@ -26,7 +26,6 @@
BreakdownByThreadState,
BreakdownByThreadStateTreeNode,
} from './sql/thread_state';
-import {addSqlTableTab} from './sql_table_tab_interface';
import {DurationWidget} from './widgets/duration';
import {renderProcessRef} from './widgets/process';
import {renderThreadRef} from './widgets/thread';
@@ -34,6 +33,7 @@
import {getSqlTableDescription} from './widgets/sql/table/sql_table_registry';
import {assertExists} from '../base/logging';
import {Trace} from '../public/trace';
+import {extensions} from '../public/lib/extensions';
// Renders a widget storing all of the generic details for a slice from the
// slice table.
@@ -57,7 +57,7 @@
m(MenuItem, {
label: 'Slices with the same name',
onclick: () => {
- addSqlTableTab(trace, {
+ extensions.addSqlTableTab(trace, {
table: assertExists(getSqlTableDescription('slice')),
filters: [
{
diff --git a/ui/src/frontend/sql_table_tab.ts b/ui/src/frontend/sql_table_tab.ts
index 495b4d4..b803148 100644
--- a/ui/src/frontend/sql_table_tab.ts
+++ b/ui/src/frontend/sql_table_tab.ts
@@ -16,19 +16,24 @@
import {copyToClipboard} from '../base/clipboard';
import {Icons} from '../base/semantic_icons';
import {exists} from '../base/utils';
-import {uuidv4} from '../base/uuid';
-import {addBottomTab} from '../common/add_ephemeral_tab';
import {Button} from '../widgets/button';
import {DetailsShell} from '../widgets/details_shell';
import {Popup, PopupPosition} from '../widgets/popup';
-import {BottomTab, NewBottomTabArgs} from '../public/lib/bottom_tab';
-import {AddDebugTrackMenu} from '../public/lib/debug_tracks/add_debug_track_menu';
+import {AddDebugTrackMenu} from '../public/lib/tracks/add_debug_track_menu';
import {Filter} from './widgets/sql/table/column';
import {SqlTableState} from './widgets/sql/table/state';
import {SqlTable} from './widgets/sql/table/table';
import {SqlTableDescription} from './widgets/sql/table/table_description';
import {Trace} from '../public/trace';
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;
@@ -36,7 +41,7 @@
imports?: string[];
}
-export function addSqlTableTabImpl(
+export function addSqlTableTab(
trace: Trace,
config: AddSqlTableTabParams,
): void {
@@ -49,31 +54,13 @@
}
function addSqlTableTabWithState(state: SqlTableState) {
- const queryResultsTab = new SqlTableTab({
- config: {state},
- trace: state.trace,
- uuid: uuidv4(),
- });
-
- addBottomTab(queryResultsTab, 'sqlTable');
+ addEphemeralTab('sqlTable', new SqlTableTab(state));
}
-interface SqlTableTabConfig {
- state: SqlTableState;
-}
+class SqlTableTab implements Tab {
+ constructor(private readonly state: SqlTableState) {}
-class SqlTableTab extends BottomTab<SqlTableTabConfig> {
- static readonly kind = 'dev.perfetto.SqlTableTab';
-
- private state: SqlTableState;
-
- constructor(args: NewBottomTabArgs<SqlTableTabConfig>) {
- super(args);
-
- this.state = args.config.state;
- }
-
- viewTab() {
+ render() {
const range = this.state.getDisplayedRange();
const rowCount = this.state.getTotalRowCount();
const navigation = [
@@ -141,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/sql_table_tab_interface.ts b/ui/src/frontend/sql_table_tab_interface.ts
deleted file mode 100644
index 3525a9e..0000000
--- a/ui/src/frontend/sql_table_tab_interface.ts
+++ /dev/null
@@ -1,41 +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 {Trace} from '../public/trace';
-import {type AddSqlTableTabParams} from './sql_table_tab';
-
-type AddSqlTableTabFunction = (
- trace: Trace,
- config: AddSqlTableTabParams,
-) => void;
-
-// TODO(primiano): this injection is to break the circular dependency cycle that
-// there is between DebugSliceTrack and SqlTableTab. The problem is:
-// DebugSliceTrack has a DebugSliceDetailsTab which shows details about slices,
-// which have a context menu, which allows to create a debug track from it.
-// We should probably break this cycle "more properly" by having a registry for
-// context menu items for slices.
-
-let addSqlTableTabFunction: AddSqlTableTabFunction;
-
-export function addSqlTableTab(
- trace: Trace,
- config: AddSqlTableTabParams,
-): void {
- addSqlTableTabFunction(trace, config);
-}
-
-export function setAddSqlTableTabImplFunction(f: AddSqlTableTabFunction) {
- addSqlTableTabFunction = f;
-}
diff --git a/ui/src/frontend/tab_panel.ts b/ui/src/frontend/tab_panel.ts
index 7353771..0662ef8 100644
--- a/ui/src/frontend/tab_panel.ts
+++ b/ui/src/frontend/tab_panel.ts
@@ -15,33 +15,97 @@
import m from 'mithril';
import {Gate} from '../base/mithril_utils';
import {EmptyState} from '../widgets/empty_state';
-import {
- DragHandle,
- Tab,
- TabDropdownEntry,
- getDefaultDetailsHeight,
-} from './drag_handle';
-import {globals} from './globals';
import {raf} from '../core/raf_scheduler';
-import {TraceAttrs} from '../public/trace';
+import {DetailsShell} from '../widgets/details_shell';
+import {GridLayout, GridLayoutColumn} from '../widgets/grid_layout';
+import {Section} from '../widgets/section';
+import {Tree, TreeNode} from '../widgets/tree';
+import {TraceImpl, TraceImplAttrs} from '../core/trace_impl';
+import {MenuItem, PopupMenu2} from '../widgets/menu';
+import {Button} from '../widgets/button';
+import {DEFAULT_DETAILS_CONTENT_HEIGHT} from './css_constants';
+import {DisposableStack} from '../base/disposable_stack';
+import {DragGestureHandler} from '../base/drag_gesture_handler';
+import {assertExists} from '../base/logging';
+
+export type TabPanelAttrs = TraceImplAttrs;
+
+export interface Tab {
+ // Unique key for this tab, passed to callbacks.
+ key: string;
+
+ // Tab title to show on the tab handle.
+ title: m.Children;
+
+ // Whether to show a close button on the tab handle or not.
+ // Default = false.
+ hasCloseButton?: boolean;
+}
interface TabWithContent extends Tab {
content: m.Children;
}
-export type TabPanelAttrs = TraceAttrs;
+export interface TabDropdownEntry {
+ // Unique key for this tab dropdown entry.
+ key: string;
+
+ // Title to show on this entry.
+ title: string;
+
+ // Called when tab dropdown entry is clicked.
+ onClick: () => void;
+
+ // Whether this tab is checked or not
+ checked: boolean;
+}
export class TabPanel implements m.ClassComponent<TabPanelAttrs> {
+ private readonly trace: TraceImpl;
// Tabs panel starts collapsed.
- private detailsHeight = 0;
+
+ // NOTE: the visibility state of the tab panel (COLLAPSED, VISIBLE,
+ // FULLSCREEN) is stored in TabManagerImpl because it can be toggled via
+ // commands. Here we store only the heights for the various states, because
+ // nobody else needs to know about them and are an impl. detail of the VDOM.
+
+ // The actual height of the vdom node. It matches resizableHeight if VISIBLE,
+ // 0 if COLLAPSED, fullscreenHeight if FULLSCREEN.
+ private height = 0;
+
+ // The height when the panel is 'VISIBLE'.
+ private resizableHeight = getDefaultDetailsHeight();
+
+ // The height when the panel is 'FULLSCREEN'.
+ private fullscreenHeight = 0;
+
private fadeContext = new FadeContext();
- private hasBeenDragged = false;
+ private trash = new DisposableStack();
+
+ constructor({attrs}: m.CVnode<TabPanelAttrs>) {
+ this.trace = attrs.trace;
+ }
view() {
- const tabMan = globals.tabManager;
- const tabList = globals.tabManager.openTabsUri;
-
+ const tabMan = this.trace.tabs;
+ const tabList = this.trace.tabs.openTabsUri;
const resolvedTabs = tabMan.resolveTabs(tabList);
+
+ switch (this.trace.tabs.tabPanelVisibility) {
+ case 'VISIBLE':
+ this.height = Math.min(
+ Math.max(this.resizableHeight, 0),
+ this.fullscreenHeight,
+ );
+ break;
+ case 'FULLSCREEN':
+ this.height = this.fullscreenHeight;
+ break;
+ case 'COLLAPSED':
+ this.height = 0;
+ break;
+ }
+
const tabs = resolvedTabs.map(({uri, tab: tabDesc}): TabWithContent => {
if (tabDesc) {
return {
@@ -60,13 +124,6 @@
}
});
- if (
- !this.hasBeenDragged &&
- (tabs.length > 0 || globals.selectionManager.selection.kind !== 'empty')
- ) {
- this.detailsHeight = getDefaultDetailsHeight();
- }
-
// Add the permanent current selection tab to the front of the list of tabs
tabs.unshift({
key: 'current_selection',
@@ -74,41 +131,156 @@
content: this.renderCSTabContentWithFading(),
});
- const tabDropdownEntries = globals.tabManager.tabs
+ return [
+ // Render the header with the ... menu, tab strip and resize buttons.
+ m(
+ '.handle',
+ this.renderTripleDotDropdownMenu(),
+ this.renderTabStrip(tabs),
+ this.renderTabResizeButtons(),
+ ),
+ // Render the tab contents.
+ m(
+ '.details-panel-container',
+ {
+ style: {height: `${this.height}px`},
+ },
+ tabs.map(({key, content}) => {
+ const active = key === this.trace.tabs.currentTabUri;
+ return m(Gate, {open: active}, content);
+ }),
+ ),
+ ];
+ }
+
+ oncreate(vnode: m.VnodeDOM<TraceImplAttrs, this>) {
+ let dragStartY = 0;
+ let heightWhenDragStarted = 0;
+
+ this.trash.use(
+ new DragGestureHandler(
+ vnode.dom as HTMLElement,
+ /* onDrag */ (_x, y) => {
+ const deltaYSinceDragStart = dragStartY - y;
+ this.resizableHeight = heightWhenDragStarted + deltaYSinceDragStart;
+ raf.scheduleFullRedraw();
+ },
+ /* onDragStarted */ (_x, y) => {
+ this.resizableHeight = this.height;
+ heightWhenDragStarted = this.height;
+ dragStartY = y;
+ this.trace.tabs.setTabPanelVisibility('VISIBLE');
+ },
+ /* onDragFinished */ () => {},
+ ),
+ );
+
+ const page = assertExists(vnode.dom.parentElement);
+ this.fullscreenHeight = page.clientHeight;
+ const resizeObs = new ResizeObserver(() => {
+ this.fullscreenHeight = page.clientHeight;
+ raf.scheduleFullRedraw();
+ });
+ resizeObs.observe(page);
+ this.trash.defer(() => resizeObs.disconnect());
+ }
+
+ onremove() {
+ this.trash.dispose();
+ }
+
+ private renderTripleDotDropdownMenu(): m.Child {
+ const entries = this.trace.tabs.tabs
.filter((tab) => tab.isEphemeral === false)
.map(({content, uri}): TabDropdownEntry => {
return {
key: uri,
title: content.getTitle(),
- onClick: () => globals.tabManager.toggleTab(uri),
- checked: globals.tabManager.isOpen(uri),
+ onClick: () => this.trace.tabs.toggleTab(uri),
+ checked: this.trace.tabs.isOpen(uri),
};
});
- return [
- m(DragHandle, {
- resize: (height: number) => {
- this.detailsHeight = Math.max(height, 0);
- this.hasBeenDragged = true;
- },
- height: this.detailsHeight,
- tabs,
- currentTabKey: globals.tabManager.currentTabUri,
- tabDropdownEntries,
- onTabClick: (uri) => globals.tabManager.showTab(uri),
- onTabClose: (uri) => globals.tabManager.hideTab(uri),
- }),
+ return m(
+ '.buttons',
m(
- '.details-panel-container',
+ PopupMenu2,
{
- style: {height: `${this.detailsHeight}px`},
+ trigger: m(Button, {
+ compact: true,
+ icon: 'more_vert',
+ disabled: entries.length === 0,
+ title: 'More Tabs',
+ }),
},
- tabs.map(({key, content}) => {
- const active = key === globals.tabManager.currentTabUri;
- return m(Gate, {open: active}, content);
+ entries.map((entry) => {
+ return m(MenuItem, {
+ key: entry.key,
+ label: entry.title,
+ onclick: () => entry.onClick(),
+ icon: entry.checked ? 'check_box' : 'check_box_outline_blank',
+ });
}),
),
- ];
+ );
+ }
+
+ private renderTabStrip(tabs: Tab[]): m.Child {
+ const currentTabKey = this.trace.tabs.currentTabUri;
+ return m(
+ '.tabs',
+ tabs.map((tab) => {
+ const {key, hasCloseButton = false} = tab;
+ const tag = currentTabKey === key ? '.tab[active]' : '.tab';
+ return m(
+ tag,
+ {
+ key,
+ onclick: (event: Event) => {
+ if (!event.defaultPrevented) {
+ this.trace.tabs.showTab(key);
+ }
+ },
+ // Middle click to close
+ onauxclick: (event: MouseEvent) => {
+ if (!event.defaultPrevented) {
+ this.trace.tabs.hideTab(key);
+ }
+ },
+ },
+ m('span.pf-tab-title', tab.title),
+ hasCloseButton &&
+ m(Button, {
+ onclick: (event: Event) => {
+ this.trace.tabs.hideTab(key);
+ event.preventDefault();
+ },
+ compact: true,
+ icon: 'close',
+ }),
+ );
+ }),
+ );
+ }
+
+ private renderTabResizeButtons(): m.Child {
+ const isClosed = this.trace.tabs.tabPanelVisibility === 'COLLAPSED';
+ return m(
+ '.buttons',
+ m(Button, {
+ title: 'Open fullscreen',
+ disabled: this.trace.tabs.tabPanelVisibility === 'FULLSCREEN',
+ icon: 'vertical_align_top',
+ compact: true,
+ onclick: () => this.trace.tabs.setTabPanelVisibility('FULLSCREEN'),
+ }),
+ m(Button, {
+ onclick: () => this.trace.tabs.toggleTabPanelVisibility(),
+ title: isClosed ? 'Show panel' : 'Hide panel',
+ icon: isClosed ? 'keyboard_arrow_up' : 'keyboard_arrow_down',
+ compact: true,
+ }),
+ );
}
private renderCSTabContentWithFading(): m.Children {
@@ -121,7 +293,7 @@
}
private renderCSTabContent(): {isLoading: boolean; content: m.Children} {
- const currentSelection = globals.selectionManager.selection;
+ const currentSelection = this.trace.selection.selection;
if (currentSelection.kind === 'empty') {
return {
isLoading: false,
@@ -136,24 +308,23 @@
};
}
- // Show single selection panels if they are registered
- if (currentSelection.kind === 'single') {
- const uri = currentSelection.trackUri;
+ if (currentSelection.kind === 'track') {
+ return {
+ isLoading: false,
+ content: this.renderTrackDetailsPanel(currentSelection.trackUri),
+ };
+ }
- if (uri) {
- const trackDesc = globals.trackManager.getTrack(uri);
- const panel = trackDesc?.detailsPanel;
- if (panel) {
- return {
- content: panel.render(currentSelection.eventId),
- isLoading: panel.isLoading?.() ?? false,
- };
- }
- }
+ const detailsPanel = this.trace.selection.getDetailsPanelForSelection();
+ if (currentSelection.kind === 'track_event' && detailsPanel !== undefined) {
+ return {
+ isLoading: detailsPanel.isLoading,
+ content: detailsPanel.render(),
+ };
}
// Get the first "truthy" details panel
- const detailsPanels = globals.tabManager.detailsPanels.map((dp) => {
+ const detailsPanels = this.trace.tabs.detailsPanels.map((dp) => {
return {
content: dp.render(currentSelection),
isLoading: dp.isLoading?.() ?? false,
@@ -179,6 +350,42 @@
};
}
}
+
+ private renderTrackDetailsPanel(trackUri: string) {
+ const track = this.trace.tracks.getTrack(trackUri);
+ if (track) {
+ return m(
+ DetailsShell,
+ {title: 'Track', description: track.title},
+ m(
+ GridLayout,
+ m(
+ GridLayoutColumn,
+ m(
+ Section,
+ {title: 'Details'},
+ m(
+ Tree,
+ m(TreeNode, {left: 'Name', right: track.title}),
+ m(TreeNode, {left: 'URI', right: track.uri}),
+ m(TreeNode, {left: 'Plugin ID', right: track.pluginId}),
+ m(
+ TreeNode,
+ {left: 'Tags'},
+ track.tags &&
+ Object.entries(track.tags).map(([key, value]) => {
+ return m(TreeNode, {left: key, right: value?.toString()});
+ }),
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+ } else {
+ return undefined; // TODO show something sensible here
+ }
+ }
}
const FADE_TIME_MS = 50;
@@ -231,3 +438,10 @@
return this.show ? vnode.children : undefined;
}
}
+
+function getDefaultDetailsHeight() {
+ const DRAG_HANDLE_HEIGHT_PX = 28;
+ // This needs to be a function instead of a const to ensure the CSS constants
+ // have been initialized by the time we perform this calculation;
+ return DRAG_HANDLE_HEIGHT_PX + DEFAULT_DETAILS_CONTENT_HEIGHT;
+}
diff --git a/ui/src/frontend/thread_slice_details_tab.ts b/ui/src/frontend/thread_slice_details_tab.ts
index 02fe65a..b549971 100644
--- a/ui/src/frontend/thread_slice_details_tab.ts
+++ b/ui/src/frontend/thread_slice_details_tab.ts
@@ -14,22 +14,16 @@
import m from 'mithril';
import {Icons} from '../base/semantic_icons';
-import {Time, TimeSpan} from '../base/time';
+import {TimeSpan} from '../base/time';
import {exists} from '../base/utils';
-import {raf} from '../core/raf_scheduler';
import {Engine} from '../trace_processor/engine';
-import {LONG, LONG_NULL, NUM, STR_NULL} from '../trace_processor/query_result';
import {Button} from '../widgets/button';
import {DetailsShell} from '../widgets/details_shell';
import {GridLayout, GridLayoutColumn} from '../widgets/grid_layout';
import {MenuItem, PopupMenu2} from '../widgets/menu';
import {Section} from '../widgets/section';
import {Tree} from '../widgets/tree';
-import {BottomTab, NewBottomTabArgs} from '../public/lib/bottom_tab';
-import {addDebugSliceTrack} from '../public/lib/debug_tracks/debug_tracks';
-import {globals} from './globals';
-import {Flow, FlowPoint} from 'src/core/flow_types';
-import {addQueryResultsTab} from '../public/lib/query_table/query_result_tab';
+import {Flow, FlowPoint} from '../core/flow_types';
import {hasArgs, renderArguments} from './slice_args';
import {renderDetails} from './slice_details';
import {getSlice, SliceDetails} from '../trace_processor/sql_utils/slice';
@@ -39,12 +33,15 @@
} from './sql/thread_state';
import {asSliceSqlId} from '../trace_processor/sql_utils/core_types';
import {DurationWidget} from './widgets/duration';
-import {addSqlTableTab} from './sql_table_tab_interface';
import {SliceRef} from './widgets/slice';
import {BasicTable} from '../widgets/basic_table';
import {getSqlTableDescription} from './widgets/sql/table/sql_table_registry';
import {assertExists} from '../base/logging';
import {Trace} from '../public/trace';
+import {TrackEventDetailsPanel} from '../public/details_panel';
+import {TrackEventSelection} from '../public/selection';
+import {extensions} from '../public/lib/extensions';
+import {TraceImpl} from '../core/trace_impl';
interface ContextMenuItem {
name: string;
@@ -93,7 +90,7 @@
name: 'Ancestor slices',
shouldDisplay: (slice: SliceDetails) => slice.parentId !== undefined,
run: (slice: SliceDetails, trace: Trace) =>
- addSqlTableTab(trace, {
+ extensions.addSqlTableTab(trace, {
table: assertExists(getSqlTableDescription('slice')),
filters: [
{
@@ -109,7 +106,7 @@
name: 'Descendant slices',
shouldDisplay: () => true,
run: (slice: SliceDetails, trace: Trace) =>
- addSqlTableTab(trace, {
+ extensions.addSqlTableTab(trace, {
table: assertExists(getSqlTableDescription('slice')),
filters: [
{
@@ -125,7 +122,7 @@
name: 'Average duration of slice name',
shouldDisplay: (slice: SliceDetails) => hasName(slice),
run: (slice: SliceDetails, trace: Trace) =>
- addQueryResultsTab(trace, {
+ extensions.addQueryResultsTab(trace, {
query: `SELECT AVG(dur) / 1e9 FROM slice WHERE name = '${slice.name!}'`,
title: `${slice.name} average dur`,
}),
@@ -144,9 +141,9 @@
INCLUDE PERFETTO MODULE android.monitor_contention;`,
)
.then(() =>
- addDebugSliceTrack(
+ extensions.addDebugSliceTrack({
trace,
- {
+ data: {
sqlSource: `
WITH merged AS (
SELECT s.ts, s.dur, tx.aidl_name AS name, 0 AS depth
@@ -185,12 +182,10 @@
ORDER BY depth
) SELECT ts, dur, name FROM merged`,
},
- `Binder names (${getProcessNameFromSlice(
+ title: `Binder names (${getProcessNameFromSlice(
slice,
)}:${getThreadNameFromSlice(slice)})`,
- {ts: 'ts', dur: 'dur', name: 'name'},
- [],
- ),
+ }),
);
},
},
@@ -200,83 +195,22 @@
return ITEMS.filter((item) => item.shouldDisplay(slice));
}
-async function getAnnotationSlice(
- engine: Engine,
- id: number,
-): Promise<SliceDetails | undefined> {
- const query = await engine.query(`
- SELECT
- id,
- name,
- ts,
- dur,
- track_id as trackId,
- thread_dur as threadDur,
- cat,
- ABS_TIME_STR(ts) as absTime
- FROM annotation_slice
- where id = ${id}`);
-
- const it = query.firstRow({
- id: NUM,
- name: STR_NULL,
- ts: LONG,
- dur: LONG,
- trackId: NUM,
- threadDur: LONG_NULL,
- cat: STR_NULL,
- absTime: STR_NULL,
- });
-
- return {
- id: asSliceSqlId(it.id),
- name: it.name ?? 'null',
- ts: Time.fromRaw(it.ts),
- dur: it.dur,
- depth: 0,
- trackId: it.trackId,
- threadDur: it.threadDur ?? undefined,
- category: it.cat ?? undefined,
- absTime: it.absTime ?? undefined,
- };
-}
-
async function getSliceDetails(
engine: Engine,
id: number,
- table: string,
): Promise<SliceDetails | undefined> {
- if (table === 'annotation_slice') {
- return getAnnotationSlice(engine, id);
- } else {
- return getSlice(engine, asSliceSqlId(id));
- }
+ return getSlice(engine, asSliceSqlId(id));
}
-interface ThreadSliceDetailsTabConfig {
- id: number;
- table: string;
-}
-
-export class ThreadSliceDetailsTab extends BottomTab<ThreadSliceDetailsTabConfig> {
+export class ThreadSliceDetailsPanel implements TrackEventDetailsPanel {
private sliceDetails?: SliceDetails;
private breakdownByThreadState?: BreakdownByThreadState;
- static create(
- args: NewBottomTabArgs<ThreadSliceDetailsTabConfig>,
- ): ThreadSliceDetailsTab {
- return new ThreadSliceDetailsTab(args);
- }
+ constructor(private readonly trace: TraceImpl) {}
- constructor(args: NewBottomTabArgs<ThreadSliceDetailsTabConfig>) {
- super(args);
- this.load();
- }
-
- async load() {
- // Start loading the slice details
- const {id, table} = this.config;
- const details = await getSliceDetails(this.engine, id, table);
+ async load({eventId}: TrackEventSelection) {
+ const {trace} = this;
+ const details = await getSliceDetails(trace.engine, eventId);
if (
details !== undefined &&
@@ -284,21 +218,16 @@
details.dur > 0
) {
this.breakdownByThreadState = await breakDownIntervalByThreadState(
- this.engine,
+ trace.engine,
TimeSpan.fromTimeAndDuration(details.ts, details.dur),
details.thread.utid,
);
}
this.sliceDetails = details;
- raf.scheduleFullRedraw();
}
- getTitle(): string {
- return `Current Selection`;
- }
-
- viewTab() {
+ render() {
if (!exists(this.sliceDetails)) {
return m(DetailsShell, {title: 'Slice', description: 'Loading...'});
}
@@ -318,10 +247,6 @@
);
}
- isLoading() {
- return !exists(this.sliceDetails);
- }
-
private renderRhs(trace: Trace, slice: SliceDetails): m.Children {
const precFlows = this.renderPrecedingFlows(slice);
const followingFlows = this.renderFollowingFlows(slice);
@@ -341,7 +266,7 @@
}
private renderPrecedingFlows(slice: SliceDetails): m.Children {
- const flows = globals.trace.flows.connectedFlows;
+ const flows = this.trace.flows.connectedFlows;
const inFlows = flows.filter(({end}) => end.sliceId === slice.id);
if (inFlows.length > 0) {
@@ -361,9 +286,6 @@
id: asSliceSqlId(flow.begin.sliceId),
name:
flow.begin.sliceChromeCustomName ?? flow.begin.sliceName,
- ts: flow.begin.sliceStartTs,
- dur: flow.begin.sliceEndTs - flow.begin.sliceStartTs,
- sqlTrackId: flow.begin.trackId,
}),
},
{
@@ -388,7 +310,7 @@
}
private renderFollowingFlows(slice: SliceDetails): m.Children {
- const flows = globals.trace.flows.connectedFlows;
+ const flows = this.trace.flows.connectedFlows;
const outFlows = flows.filter(({begin}) => begin.sliceId === slice.id);
if (outFlows.length > 0) {
@@ -407,9 +329,6 @@
m(SliceRef, {
id: asSliceSqlId(flow.end.sliceId),
name: flow.end.sliceChromeCustomName ?? flow.end.sliceName,
- ts: flow.end.sliceStartTs,
- dur: flow.end.sliceEndTs - flow.end.sliceStartTs,
- sqlTrackId: flow.end.trackId,
}),
},
{
diff --git a/ui/src/frontend/thread_slice_track.ts b/ui/src/frontend/thread_slice_track.ts
index b0fa1b7..6e47c8a 100644
--- a/ui/src/frontend/thread_slice_track.ts
+++ b/ui/src/frontend/thread_slice_track.ts
@@ -14,13 +14,15 @@
import {BigintMath as BIMath} from '../base/bigint_math';
import {clamp} from '../base/math_utils';
-import {OnSliceClickArgs} from './base_slice_track';
-import {globals} from './globals';
import {NAMED_ROW, NamedSliceTrack} from './named_slice_track';
import {SLICE_LAYOUT_FIT_CONTENT_DEFAULTS} from './slice_layout';
import {NewTrackArgs} from './track';
import {LONG_NULL} from '../trace_processor/query_result';
import {Slice} from '../public/track';
+import {TrackEventDetails} from '../public/selection';
+import {ThreadSliceDetailsPanel} from './thread_slice_details_tab';
+import {TraceImpl} from '../core/trace_impl';
+import {assertIsInstance} from '../base/logging';
export const THREAD_SLICE_ROW = {
// Base columns (tsq, ts, dur, id, depth).
@@ -82,12 +84,21 @@
}
}
- onSliceClick(args: OnSliceClickArgs<Slice>) {
- globals.selectionManager.selectLegacy({
- kind: 'SLICE',
- id: args.slice.id,
- trackUri: this.uri,
- table: this.tableName,
- });
+ async getSelectionDetails(
+ id: number,
+ ): Promise<TrackEventDetails | undefined> {
+ const baseDetails = await super.getSelectionDetails(id);
+ if (!baseDetails) return undefined;
+ return {
+ ...baseDetails,
+ tableName: this.tableName,
+ };
+ }
+
+ override detailsPanel() {
+ // Rationale for the assertIsInstance: ThreadSliceDetailsPanel requires a
+ // TraceImpl (because of flows) but here we must take a Trace interface,
+ // because this class is exposed to plugins (which see only Trace).
+ return new ThreadSliceDetailsPanel(assertIsInstance(this.trace, TraceImpl));
}
}
diff --git a/ui/src/frontend/thread_state_tab.ts b/ui/src/frontend/thread_state_tab.ts
deleted file mode 100644
index bab2361..0000000
--- a/ui/src/frontend/thread_state_tab.ts
+++ /dev/null
@@ -1,354 +0,0 @@
-// Copyright (C) 2023 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use size 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 {time} from '../base/time';
-import {raf} from '../core/raf_scheduler';
-import {Anchor} from '../widgets/anchor';
-import {Button} from '../widgets/button';
-import {DetailsShell} from '../widgets/details_shell';
-import {GridLayout} from '../widgets/grid_layout';
-import {Section} from '../widgets/section';
-import {SqlRef} from '../widgets/sql_ref';
-import {Tree, TreeNode} from '../widgets/tree';
-import {Intent} from '../widgets/common';
-import {BottomTab, NewBottomTabArgs} from '../public/lib/bottom_tab';
-import {
- SchedSqlId,
- ThreadStateSqlId,
-} from '../trace_processor/sql_utils/core_types';
-import {
- getThreadState,
- getThreadStateFromConstraints,
- ThreadState,
-} from '../trace_processor/sql_utils/thread_state';
-import {DurationWidget, renderDuration} from './widgets/duration';
-import {Timestamp} from './widgets/timestamp';
-import {globals} from './globals';
-import {getProcessName} from '../trace_processor/sql_utils/process';
-import {
- getFullThreadName,
- getThreadName,
-} from '../trace_processor/sql_utils/thread';
-import {ThreadStateRef} from './widgets/thread_state';
-import {
- CRITICAL_PATH_CMD,
- CRITICAL_PATH_LITE_CMD,
-} from '../public/exposed_commands';
-import {goToSchedSlice} from './widgets/sched';
-
-interface ThreadStateTabConfig {
- // Id into |thread_state| sql table.
- readonly id: ThreadStateSqlId;
-}
-
-interface RelatedThreadStates {
- prev?: ThreadState;
- next?: ThreadState;
- waker?: ThreadState;
- wakerInterruptCtx?: boolean;
- wakee?: ThreadState[];
-}
-
-export class ThreadStateTab extends BottomTab<ThreadStateTabConfig> {
- static readonly kind = 'dev.perfetto.ThreadStateTab';
-
- state?: ThreadState;
- relatedStates?: RelatedThreadStates;
- loaded: boolean = false;
-
- static create(args: NewBottomTabArgs<ThreadStateTabConfig>): ThreadStateTab {
- return new ThreadStateTab(args);
- }
-
- constructor(args: NewBottomTabArgs<ThreadStateTabConfig>) {
- super(args);
-
- this.load().then(() => {
- this.loaded = true;
- raf.scheduleFullRedraw();
- });
- }
-
- async load() {
- this.state = await getThreadState(this.engine, this.config.id);
-
- if (!this.state) {
- return;
- }
-
- const relatedStates: RelatedThreadStates = {};
- relatedStates.prev = (
- await getThreadStateFromConstraints(this.engine, {
- filters: [
- `ts + dur = ${this.state.ts}`,
- `utid = ${this.state.thread?.utid}`,
- ],
- limit: 1,
- })
- )[0];
- relatedStates.next = (
- await getThreadStateFromConstraints(this.engine, {
- filters: [
- `ts = ${this.state.ts + this.state.dur}`,
- `utid = ${this.state.thread?.utid}`,
- ],
- limit: 1,
- })
- )[0];
- if (this.state.wakerId !== undefined) {
- relatedStates.waker = await getThreadState(
- this.engine,
- this.state.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.wakee = await getThreadStateFromConstraints(this.engine, {
- filters: [
- `waker_id = ${this.config.id}`,
- `(irq_context is null or irq_context = 0)`,
- ],
- });
-
- this.relatedStates = relatedStates;
- }
-
- getTitle() {
- // TODO(altimin): Support dynamic titles here.
- return 'Current Selection';
- }
-
- viewTab() {
- // TODO(altimin/stevegolton): Differentiate between "Current Selection" and
- // "Pinned" views in DetailsShell.
- return m(
- DetailsShell,
- {title: 'Thread State', description: this.renderLoadingText()},
- m(
- GridLayout,
- m(
- Section,
- {title: 'Details'},
- this.state && this.renderTree(this.state),
- ),
- m(
- Section,
- {title: 'Related thread states'},
- this.renderRelatedThreadStates(),
- ),
- ),
- );
- }
-
- private renderLoadingText() {
- if (!this.loaded) {
- return 'Loading';
- }
- if (!this.state) {
- return `Thread state ${this.config.id} does not exist`;
- }
- // TODO(stevegolton): Return something intelligent here.
- return this.config.id;
- }
-
- private renderTree(state: ThreadState) {
- const thread = state.thread;
- const process = state.thread?.process;
- return m(
- Tree,
- m(TreeNode, {
- left: 'Start time',
- right: m(Timestamp, {ts: state.ts}),
- }),
- m(TreeNode, {
- left: 'Duration',
- right: m(DurationWidget, {dur: state.dur}),
- }),
- m(TreeNode, {
- left: 'State',
- right: this.renderState(
- state.state,
- state.cpu,
- state.schedSqlId,
- state.ts,
- ),
- }),
- state.blockedFunction &&
- m(TreeNode, {
- left: 'Blocked function',
- right: state.blockedFunction,
- }),
- process &&
- m(TreeNode, {
- left: 'Process',
- right: getProcessName(process),
- }),
- thread && m(TreeNode, {left: 'Thread', right: getThreadName(thread)}),
- m(TreeNode, {
- left: 'SQL ID',
- right: m(SqlRef, {table: 'thread_state', id: state.threadStateSqlId}),
- }),
- );
- }
-
- private renderState(
- state: string,
- cpu: number | undefined,
- id: SchedSqlId | undefined,
- ts: time,
- ): m.Children {
- if (!state) {
- return null;
- }
- if (id === undefined || cpu === undefined) {
- return state;
- }
- return m(
- Anchor,
- {
- title: 'Go to CPU slice',
- icon: 'call_made',
- onclick: () => goToSchedSlice(cpu, id, ts),
- },
- `${state} on CPU ${cpu}`,
- );
- }
-
- private renderRelatedThreadStates(): m.Children {
- if (this.state === undefined || this.relatedStates === undefined) {
- return 'Loading';
- }
- const startTs = this.state.ts;
- const renderRef = (state: ThreadState, name?: string) =>
- m(ThreadStateRef, {
- id: state.threadStateSqlId,
- ts: state.ts,
- dur: state.dur,
- utid: state.thread!.utid,
- name,
- });
-
- const nameForNextOrPrev = (state: ThreadState) =>
- `${state.state} for ${renderDuration(state.dur)}`;
-
- const renderWaker = (related: RelatedThreadStates) => {
- // Could be absent if:
- // * this thread state wasn't woken up (e.g. it is a running slice).
- // * the wakeup is from an interrupt during the idle process (which
- // isn't populated in thread_state).
- // * at the start of the trace, before all per-cpu scheduling is known.
- const hasWakerId = related.waker !== undefined;
- // Interrupt context for the wakeups is absent from older traces.
- const hasInterruptCtx = related.wakerInterruptCtx !== undefined;
-
- if (!hasWakerId && !hasInterruptCtx) {
- return null;
- }
- if (related.wakerInterruptCtx) {
- return m(TreeNode, {
- left: 'Woken by',
- right: `Interrupt`,
- });
- }
- return (
- related.waker &&
- m(TreeNode, {
- left: hasInterruptCtx ? 'Woken by' : 'Woken by (maybe interrupt)',
- right: renderRef(
- related.waker,
- getFullThreadName(related.waker.thread),
- ),
- })
- );
- };
-
- const renderWakees = (related: RelatedThreadStates) => {
- if (related.wakee === undefined || related.wakee.length == 0) {
- return null;
- }
- const hasInterruptCtx = related.wakee[0].wakerInterruptCtx !== undefined;
- return m(
- TreeNode,
- {
- left: hasInterruptCtx
- ? 'Woken threads'
- : 'Woken threads (maybe interrupt)',
- },
- related.wakee.map((state) =>
- m(TreeNode, {
- left: m(Timestamp, {
- ts: state.ts,
- display: `+${renderDuration(state.ts - startTs)}`,
- }),
- right: renderRef(state, getFullThreadName(state.thread)),
- }),
- ),
- );
- };
-
- return [
- m(
- Tree,
- this.relatedStates.prev &&
- m(TreeNode, {
- left: 'Previous state',
- right: renderRef(
- this.relatedStates.prev,
- nameForNextOrPrev(this.relatedStates.prev),
- ),
- }),
- this.relatedStates.next &&
- m(TreeNode, {
- left: 'Next state',
- right: renderRef(
- this.relatedStates.next,
- nameForNextOrPrev(this.relatedStates.next),
- ),
- }),
- renderWaker(this.relatedStates),
- renderWakees(this.relatedStates),
- ),
- globals.commandManager.hasCommand(CRITICAL_PATH_LITE_CMD) &&
- m(Button, {
- label: 'Critical path lite',
- intent: Intent.Primary,
- onclick: () => {
- globals.commandManager.runCommand(
- CRITICAL_PATH_LITE_CMD,
- this.state?.thread?.utid,
- );
- },
- }),
- globals.commandManager.hasCommand(CRITICAL_PATH_CMD) &&
- m(Button, {
- label: 'Critical path',
- intent: Intent.Primary,
- onclick: () => {
- globals.commandManager.runCommand(
- CRITICAL_PATH_CMD,
- this.state?.thread?.utid,
- );
- },
- }),
- ];
- }
-
- isLoading() {
- return this.state === undefined || this.relatedStates === undefined;
- }
-}
diff --git a/ui/src/frontend/tickmark_panel.ts b/ui/src/frontend/tickmark_panel.ts
index c330d1c..8ce8336 100644
--- a/ui/src/frontend/tickmark_panel.ts
+++ b/ui/src/frontend/tickmark_panel.ts
@@ -14,17 +14,14 @@
import m from 'mithril';
import {TRACK_SHELL_WIDTH} from './css_constants';
-import {globals} from './globals';
import {getMaxMajorTicks, generateTicks, TickType} from './gridline_helper';
import {Size2D} from '../base/geom';
import {Panel} from './panel_container';
import {TimeScale} from '../base/time_scale';
import {canvasClip} from '../base/canvas_utils';
-import {
- createSearchOverviewTrack,
- SearchOverviewTrack,
-} from './search_overview_track';
+import {SearchOverviewTrack} from './search_overview_track';
import {TraceImpl} from '../core/trace_impl';
+import {getOrCreate} from '../base/utils';
// We want to create the overview track only once per trace, but this
// class can be delete and re-instantiated when switching between pages via
@@ -36,16 +33,14 @@
export class TickmarkPanel implements Panel {
readonly kind = 'panel';
readonly selectable = false;
- private searchOverviewTrack?: SearchOverviewTrack;
+ private searchOverviewTrack: SearchOverviewTrack;
- constructor(trace: TraceImpl) {
- this.searchOverviewTrack = trackTraceMap.get(trace);
- if (this.searchOverviewTrack === undefined) {
- createSearchOverviewTrack(trace).then((track) => {
- trackTraceMap.set(trace, track);
- this.searchOverviewTrack = track;
- });
- }
+ constructor(private readonly trace: TraceImpl) {
+ this.searchOverviewTrack = getOrCreate(
+ trackTraceMap,
+ trace,
+ () => new SearchOverviewTrack(trace),
+ );
}
render(): m.Children {
@@ -65,7 +60,7 @@
}
private renderTrack(ctx: CanvasRenderingContext2D, size: Size2D): void {
- const visibleWindow = globals.timeline.visibleWindow;
+ const visibleWindow = this.trace.timeline.visibleWindow;
const timescale = new TimeScale(visibleWindow, {
left: 0,
right: size.width,
@@ -75,7 +70,7 @@
if (size.width > 0 && timespan.duration > 0n) {
const maxMajorTicks = getMaxMajorTicks(size.width);
- const offset = globals.trace.timeline.timestampOffset();
+ const offset = this.trace.timeline.timestampOffset();
const tickGen = generateTicks(timespan, maxMajorTicks, offset);
for (const {type, time} of tickGen) {
const px = Math.floor(timescale.timeToPx(time));
@@ -85,8 +80,6 @@
}
}
- if (this.searchOverviewTrack) {
- this.searchOverviewTrack.render(ctx, size);
- }
+ this.searchOverviewTrack.render(ctx, size);
}
}
diff --git a/ui/src/frontend/time_axis_panel.ts b/ui/src/frontend/time_axis_panel.ts
index 9253488..143e402 100644
--- a/ui/src/frontend/time_axis_panel.ts
+++ b/ui/src/frontend/time_axis_panel.ts
@@ -16,7 +16,6 @@
import {Time, time, toISODateOnly} from '../base/time';
import {TimestampFormat, timestampFormat} from '../core/timestamp_format';
import {TRACK_SHELL_WIDTH} from './css_constants';
-import {globals} from './globals';
import {
getMaxMajorTicks,
MIN_PX_PER_STEP,
@@ -27,12 +26,15 @@
import {Panel} from './panel_container';
import {TimeScale} from '../base/time_scale';
import {canvasClip} from '../base/canvas_utils';
+import {Trace} from '../public/trace';
export class TimeAxisPanel implements Panel {
readonly kind = 'panel';
readonly selectable = false;
readonly id = 'time-axis-panel';
+ constructor(private readonly trace: Trace) {}
+
render(): m.Children {
return m('.time-axis-panel');
}
@@ -55,7 +57,7 @@
}
private renderOffsetTimestamp(ctx: CanvasRenderingContext2D): void {
- const offset = globals.trace.timeline.timestampOffset();
+ const offset = this.trace.timeline.timestampOffset();
switch (timestampFormat()) {
case TimestampFormat.TraceNs:
case TimestampFormat.TraceNsLocale:
@@ -67,16 +69,16 @@
break;
case TimestampFormat.UTC:
const offsetDate = Time.toDate(
- globals.traceContext.utcOffset,
- globals.traceContext.realtimeOffset,
+ this.trace.traceInfo.utcOffset,
+ this.trace.traceInfo.realtimeOffset,
);
const dateStr = toISODateOnly(offsetDate);
ctx.fillText(`UTC ${dateStr}`, 6, 10);
break;
case TimestampFormat.TraceTz:
const offsetTzDate = Time.toDate(
- globals.traceContext.traceTzOffset,
- globals.traceContext.realtimeOffset,
+ this.trace.traceInfo.traceTzOffset,
+ this.trace.traceInfo.realtimeOffset,
);
const dateTzStr = toISODateOnly(offsetTzDate);
ctx.fillText(dateTzStr, 6, 10);
@@ -85,13 +87,13 @@
}
private renderPanel(ctx: CanvasRenderingContext2D, size: Size2D): void {
- const visibleWindow = globals.timeline.visibleWindow;
+ const visibleWindow = this.trace.timeline.visibleWindow;
const timescale = new TimeScale(visibleWindow, {
left: 0,
right: size.width,
});
const timespan = visibleWindow.toTimeSpan();
- const offset = globals.trace.timeline.timestampOffset();
+ const offset = this.trace.timeline.timestampOffset();
// Draw time axis.
if (size.width > 0 && timespan.duration > 0n) {
@@ -101,7 +103,7 @@
if (type === TickType.MAJOR) {
const position = Math.floor(timescale.timeToPx(time));
ctx.fillRect(position, 0, 1, size.height);
- const domainTime = globals.trace.timeline.toDomainTime(time);
+ const domainTime = this.trace.timeline.toDomainTime(time);
renderTimestamp(ctx, domainTime, position + 5, 10, MIN_PX_PER_STEP);
}
}
diff --git a/ui/src/frontend/time_selection_panel.ts b/ui/src/frontend/time_selection_panel.ts
index db808aa..5621ff4 100644
--- a/ui/src/frontend/time_selection_panel.ts
+++ b/ui/src/frontend/time_selection_panel.ts
@@ -20,13 +20,13 @@
FOREGROUND_COLOR,
TRACK_SHELL_WIDTH,
} from './css_constants';
-import {globals} from './globals';
import {getMaxMajorTicks, generateTicks, TickType} from './gridline_helper';
import {Size2D} from '../base/geom';
import {Panel} from './panel_container';
import {renderDuration} from './widgets/duration';
import {canvasClip} from '../base/canvas_utils';
import {TimeScale} from '../base/time_scale';
+import {TraceImpl} from '../core/trace_impl';
export interface BBox {
x: number;
@@ -135,6 +135,8 @@
readonly kind = 'panel';
readonly selectable = false;
+ constructor(private readonly trace: TraceImpl) {}
+
render(): m.Children {
return m('.time-selection-panel');
}
@@ -153,7 +155,7 @@
}
private renderPanel(ctx: CanvasRenderingContext2D, size: Size2D): void {
- const visibleWindow = globals.timeline.visibleWindow;
+ const visibleWindow = this.trace.timeline.visibleWindow;
const timescale = new TimeScale(visibleWindow, {
left: 0,
right: size.width,
@@ -162,7 +164,7 @@
if (size.width > 0 && timespan.duration > 0n) {
const maxMajorTicks = getMaxMajorTicks(size.width);
- const offset = globals.trace.timeline.timestampOffset();
+ const offset = this.trace.timeline.timestampOffset();
const tickGen = generateTicks(timespan, maxMajorTicks, offset);
for (const {type, time} of tickGen) {
const px = Math.floor(timescale.timeToPx(time));
@@ -172,28 +174,36 @@
}
}
- const localArea = globals.timeline.selectedArea;
- const selection = globals.selectionManager.selection;
+ const localArea = this.trace.timeline.selectedArea;
+ const selection = this.trace.selection.selection;
if (localArea !== undefined) {
const start = Time.min(localArea.start, localArea.end);
const end = Time.max(localArea.start, localArea.end);
this.renderSpan(ctx, timescale, size, start, end);
- } else if (selection.kind === 'area') {
- const start = Time.min(selection.start, selection.end);
- const end = Time.max(selection.start, selection.end);
- this.renderSpan(ctx, timescale, size, start, end);
+ } else {
+ if (selection.kind === 'area') {
+ const start = Time.min(selection.start, selection.end);
+ const end = Time.max(selection.start, selection.end);
+ this.renderSpan(ctx, timescale, size, start, end);
+ } else if (selection.kind === 'track_event') {
+ const start = selection.ts;
+ const end = Time.add(selection.ts, selection.dur);
+ if (end > start) {
+ this.renderSpan(ctx, timescale, size, start, end);
+ }
+ }
}
- if (globals.trace.timeline.hoverCursorTimestamp !== undefined) {
+ if (this.trace.timeline.hoverCursorTimestamp !== undefined) {
this.renderHover(
ctx,
timescale,
size,
- globals.trace.timeline.hoverCursorTimestamp,
+ this.trace.timeline.hoverCursorTimestamp,
);
}
- for (const note of globals.noteManager.notes.values()) {
+ for (const note of this.trace.notes.notes.values()) {
const noteIsSelected =
selection.kind === 'note' && selection.id === note.id;
if (note.noteType === 'SPAN' && noteIsSelected) {
@@ -211,7 +221,7 @@
ts: time,
) {
const xPos = Math.floor(timescale.timeToPx(ts));
- const domainTime = globals.trace.timeline.toDomainTime(ts);
+ const domainTime = this.trace.timeline.toDomainTime(ts);
const label = stringifyTimestamp(domainTime);
drawIBar(ctx, xPos, this.getBBoxFromSize(size), label);
}
diff --git a/ui/src/frontend/topbar.ts b/ui/src/frontend/topbar.ts
index 76f385e..da440d3 100644
--- a/ui/src/frontend/topbar.ts
+++ b/ui/src/frontend/topbar.ts
@@ -14,23 +14,18 @@
import m from 'mithril';
import {classNames} from '../base/classnames';
-import {raf} from '../core/raf_scheduler';
-import {globals} from './globals';
import {taskTracker} from './task_tracker';
import {Popup, PopupPosition} from '../widgets/popup';
import {assertFalse} from '../base/logging';
import {OmniboxMode} from '../core/omnibox_manager';
import {AppImpl} from '../core/app_impl';
-import {Trace, TraceAttrs} from '../public/trace';
+import {TraceImpl, TraceImplAttrs} from '../core/trace_impl';
-export const DISMISSED_PANNING_HINT_KEY = 'dismissedPanningHint';
-
-class Progress implements m.ClassComponent<TraceAttrs> {
- view({attrs}: m.CVnode<TraceAttrs>): m.Children {
+class Progress implements m.ClassComponent<TraceImplAttrs> {
+ view({attrs}: m.CVnode<TraceImplAttrs>): m.Children {
const engine = attrs.trace.engine;
- const engineCfg = globals.getCurrentEngine();
const isLoading =
- (engineCfg && !engineCfg.ready) ||
+ AppImpl.instance.isLoadingTrace ||
engine.numRequestsPending > 0 ||
taskTracker.hasPendingTasks();
const classes = classNames(isLoading && 'progress-anim');
@@ -38,58 +33,20 @@
}
}
-class HelpPanningNotification implements m.ClassComponent {
- view() {
- const dismissed = localStorage.getItem(DISMISSED_PANNING_HINT_KEY);
- // Do not show the help notification in embedded mode because local storage
- // does not persist for iFrames. The host is responsible for communicating
- // to users that they can press '?' for help.
- if (
- globals.embeddedMode ||
- dismissed === 'true' ||
- !globals.showPanningHint
- ) {
- return;
- }
- return m(
- '.helpful-hint',
- m(
- '.hint-text',
- 'Are you trying to pan? Use the WASD keys or hold shift to click ' +
- "and drag. Press '?' for more help.",
- ),
- m(
- 'button.hint-dismiss-button',
- {
- onclick: () => {
- globals.showPanningHint = false;
- localStorage.setItem(DISMISSED_PANNING_HINT_KEY, 'true');
- raf.scheduleFullRedraw();
- },
- },
- 'Dismiss',
- ),
- );
- }
-}
-
-class TraceErrorIcon implements m.ClassComponent<TraceAttrs> {
+class TraceErrorIcon implements m.ClassComponent<TraceImplAttrs> {
private tracePopupErrorDismissed = false;
- view({attrs}: m.CVnode<TraceAttrs>) {
+ view({attrs}: m.CVnode<TraceImplAttrs>) {
const trace = attrs.trace;
- if (globals.embeddedMode) return;
+ if (AppImpl.instance.embeddedMode) return;
const mode = AppImpl.instance.omnibox.mode;
- const errors = trace.traceInfo.importErrors;
- if (
- (!Boolean(errors) && !globals.metricError) ||
- mode === OmniboxMode.Command
- ) {
+ const totErrors = trace.traceInfo.importErrors + trace.loadingErrors.length;
+ if (totErrors === 0 || mode === OmniboxMode.Command) {
return;
}
- const message = Boolean(errors)
- ? `${errors} import or data loss errors detected.`
+ const message = Boolean(totErrors)
+ ? `${totErrors} import or data loss errors detected.`
: `Metric error detected.`;
return m(
'.error-box',
@@ -123,7 +80,7 @@
export interface TopbarAttrs {
omnibox: m.Children;
- trace?: Trace;
+ trace?: TraceImpl;
}
export class Topbar implements m.ClassComponent<TopbarAttrs> {
@@ -131,10 +88,11 @@
const {omnibox} = attrs;
return m(
'.topbar',
- {class: globals.state.sidebarVisible ? '' : 'hide-sidebar'},
+ {
+ class: AppImpl.instance.sidebar.visible ? '' : 'hide-sidebar',
+ },
omnibox,
attrs.trace && m(Progress, {trace: attrs.trace}),
- m(HelpPanningNotification),
attrs.trace && m(TraceErrorIcon, {trace: attrs.trace}),
);
}
diff --git a/ui/src/frontend/trace_attrs.ts b/ui/src/frontend/trace_attrs.ts
deleted file mode 100644
index 785b327..0000000
--- a/ui/src/frontend/trace_attrs.ts
+++ /dev/null
@@ -1,104 +0,0 @@
-// Copyright (C) 2020 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 {assertExists} from '../base/logging';
-import {TraceUrlSource} from '../public/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';
-
-export function isShareable() {
- return globals.isInternalUser && isDownloadable();
-}
-
-export function isDownloadable() {
- const traceSource = AppImpl.instance.trace?.traceInfo.source;
- if (traceSource === undefined) {
- return false;
- }
- if (traceSource.type === 'ARRAY_BUFFER' && traceSource.localOnly) {
- return false;
- }
- if (traceSource.type === 'HTTP_RPC') {
- return false;
- }
- return true;
-}
-
-export function shareTrace() {
- const traceSource = assertExists(AppImpl.instance.trace?.traceInfo.source);
- const traceUrl = (traceSource as TraceUrlSource).url ?? '';
-
- // If the trace is not shareable (has been pushed via postMessage()) but has
- // a url, create a pseudo-permalink by echoing back the URL.
- if (!isShareable()) {
- const msg = [
- m(
- 'p',
- 'This trace was opened by an external site and as such cannot ' +
- 'be re-shared preserving the UI state.',
- ),
- ];
- 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));
- }
-
- showModal({
- title: 'Cannot create permalink from external trace',
- content: m('div', msg),
- });
- return;
- }
-
- if (!isShareable() || !isTraceLoaded()) return;
-
- const result = confirm(
- `Upload UI state and generate a permalink. ` +
- `The trace will be accessible by anybody with the permalink.`,
- );
- if (result) {
- globals.logging.logEvent('Trace Actions', 'Create permalink');
- createPermalink({mode: 'APP_STATE'});
- }
-}
-
-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);
-}
-
-export function isTraceLoaded(): boolean {
- return AppImpl.instance.trace !== undefined;
-}
-
-export function getCurrentTraceUrl(): undefined | string {
- const source = AppImpl.instance.trace?.traceInfo.source;
- if (source && source.type === 'URL') {
- return source.url;
- }
- return undefined;
-}
diff --git a/ui/src/frontend/trace_converter.ts b/ui/src/frontend/trace_converter.ts
index 14373e4..7960cc5 100644
--- a/ui/src/frontend/trace_converter.ts
+++ b/ui/src/frontend/trace_converter.ts
@@ -12,21 +12,18 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+import {assetSrc} from '../base/assets';
import {download} from '../base/clipboard';
+import {defer} from '../base/deferred';
import {ErrorDetails} from '../base/logging';
import {utf8Decode} from '../base/string_utils';
import {time} from '../base/time';
-import {Actions} from '../common/actions';
-import {
- ConversionJobName,
- ConversionJobStatus,
-} from '../common/conversion_jobs';
+import {AppImpl} from '../core/app_impl';
import {maybeShowErrorDialog} from './error_dialog';
-import {globals} from './globals';
type Args =
| UpdateStatusArgs
- | UpdateJobStatusArgs
+ | JobCompletedArgs
| DownloadFileArgs
| OpenTraceInLegacyArgs
| ErrorArgs;
@@ -36,10 +33,8 @@
status: string;
}
-interface UpdateJobStatusArgs {
- kind: 'updateJobStatus';
- name: ConversionJobName;
- status: ConversionJobStatus;
+interface JobCompletedArgs {
+ kind: 'jobCompleted';
}
interface DownloadFileArgs {
@@ -64,21 +59,18 @@
size: number,
) => void;
-function makeWorkerAndPost(
+async function makeWorkerAndPost(
msg: unknown,
openTraceInLegacy?: OpenTraceInLegacyCallback,
) {
+ const promise = defer<void>();
+
function handleOnMessage(msg: MessageEvent): void {
const args: Args = msg.data;
if (args.kind === 'updateStatus') {
- globals.dispatch(
- Actions.updateStatus({
- msg: args.status,
- timestamp: Date.now() / 1000,
- }),
- );
- } else if (args.kind === 'updateJobStatus') {
- globals.setConversionJobStatus(args.name, args.status);
+ AppImpl.instance.omnibox.showStatusMessage(args.status);
+ } else if (args.kind === 'jobCompleted') {
+ promise.resolve();
} else if (args.kind === 'downloadFile') {
download(new File([new Blob([args.buffer])], args.name));
} else if (args.kind === 'openTraceInLegacy') {
@@ -91,21 +83,22 @@
}
}
- const worker = new Worker(globals.root + 'traceconv_bundle.js');
+ const worker = new Worker(assetSrc('traceconv_bundle.js'));
worker.onmessage = handleOnMessage;
worker.postMessage(msg);
+ return promise;
}
-export function convertTraceToJsonAndDownload(trace: Blob) {
- makeWorkerAndPost({
+export function convertTraceToJsonAndDownload(trace: Blob): Promise<void> {
+ return makeWorkerAndPost({
kind: 'ConvertTraceAndDownload',
trace,
format: 'json',
});
}
-export function convertTraceToSystraceAndDownload(trace: Blob) {
- makeWorkerAndPost({
+export function convertTraceToSystraceAndDownload(trace: Blob): Promise<void> {
+ return makeWorkerAndPost({
kind: 'ConvertTraceAndDownload',
trace,
format: 'systrace',
@@ -116,8 +109,8 @@
trace: Blob,
openTraceInLegacy: OpenTraceInLegacyCallback,
truncate?: 'start' | 'end',
-) {
- makeWorkerAndPost(
+): Promise<void> {
+ return makeWorkerAndPost(
{
kind: 'ConvertTraceAndOpenInLegacy',
trace,
@@ -131,8 +124,8 @@
trace: Blob,
pid: number,
ts: time,
-) {
- makeWorkerAndPost({
+): Promise<void> {
+ return makeWorkerAndPost({
kind: 'ConvertTraceToPprof',
trace,
pid,
diff --git a/ui/src/frontend/trace_share_utils.ts b/ui/src/frontend/trace_share_utils.ts
new file mode 100644
index 0000000..cf8f185
--- /dev/null
+++ b/ui/src/frontend/trace_share_utils.ts
@@ -0,0 +1,66 @@
+// Copyright (C) 2020 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 {TraceUrlSource} from '../core/trace_source';
+import {createPermalink} from './permalink';
+import {showModal} from '../widgets/modal';
+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;
+}
+
+export async function shareTrace(trace: TraceImpl) {
+ const traceSource = trace.traceInfo.source;
+ const traceUrl = (traceSource as TraceUrlSource).url ?? '';
+
+ // If the trace is not shareable (has been pushed via postMessage()) but has
+ // a url, create a pseudo-permalink by echoing back the URL.
+ if (!isShareable(trace)) {
+ const msg = [
+ m(
+ 'p',
+ 'This trace was opened by an external site and as such cannot ' +
+ 'be re-shared preserving the UI state.',
+ ),
+ ];
+ 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(m(CopyableLink, {url: traceUrl}));
+ }
+
+ showModal({
+ title: 'Cannot create permalink from external trace',
+ content: m('div', msg),
+ });
+ return;
+ }
+
+ if (!isShareable(trace)) return;
+
+ const result = confirm(
+ `Upload UI state and generate a permalink. ` +
+ `The trace will be accessible by anybody with the permalink.`,
+ );
+ if (result) {
+ AppImpl.instance.analytics.logEvent('Trace Actions', 'Create permalink');
+ return await createPermalink();
+ }
+}
diff --git a/ui/src/frontend/trace_url_handler.ts b/ui/src/frontend/trace_url_handler.ts
index 3dc2d80..8808818 100644
--- a/ui/src/frontend/trace_url_handler.ts
+++ b/ui/src/frontend/trace_url_handler.ts
@@ -13,13 +13,11 @@
// limitations under the License.
import m from 'mithril';
-import {Actions} from '../common/actions';
-import {tryGetTrace} from '../common/cache_manager';
+import {tryGetTrace} from '../core/cache_manager';
import {showModal} from '../widgets/modal';
import {loadPermalink} from './permalink';
import {loadAndroidBugToolInfo} from './android_bug_tool';
-import {globals} from './globals';
-import {Route, Router} from './router';
+import {Route, Router} from '../core/router';
import {taskTracker} from './task_tracker';
import {AppImpl} from '../core/app_impl';
@@ -83,13 +81,13 @@
* 3. '' -> URL with a ?local_cache_key=xxx arg:
* - Same as case 2.
* 4. URL with local_cache_key=1 -> URL with local_cache_key=2:
- * a) If 2 != uuid of the trace currently loaded (globals.state.traceUuid):
+ * a) If 2 != uuid of the trace currently loaded (TraceImpl.traceInfo.uuid):
* - Ask the user if they intend to switch trace and load 2.
* b) If 2 == uuid of current trace (e.g., after a new trace has loaded):
* - no effect (except redrawing).
* 5. URL with local_cache_key -> URL without local_cache_key:
* - Redirect to ?local_cache_key=1234 where 1234 is the UUID of the previous
- * URL (this might or might not match globals.state.traceUuid).
+ * URL (this might or might not match traceInfo.uuid).
*
* Backward navigation cases:
* 6. URL without local_cache_key <- URL with local_cache_key:
@@ -100,7 +98,10 @@
* - Same as case 5: re-append the local_cache_key.
*/
async function maybeOpenCachedTrace(traceUuid: string) {
- if (traceUuid === globals.state.traceUuid) {
+ const curTrace = AppImpl.instance.trace?.traceInfo;
+ const curCacheUuid = curTrace?.cached ? curTrace.uuid : '';
+
+ if (traceUuid === curCacheUuid) {
// Do nothing, matches the currently loaded trace.
return;
}
@@ -118,22 +119,20 @@
// This early out prevents to re-trigger the openTraceFromXXX() action if the
// URL changes (e.g. if the user navigates back/fwd) while the new trace is
// being loaded.
- if (globals.state.engine !== undefined) {
- const eng = globals.state.engine;
- if (eng.source.type === 'ARRAY_BUFFER' && eng.source.uuid === traceUuid) {
- return;
- }
+ if (
+ curTrace !== undefined &&
+ curTrace.source.type === 'ARRAY_BUFFER' &&
+ curTrace.source.uuid === traceUuid
+ ) {
+ return;
}
// Fetch the trace from the cache storage. If available load it. If not, show
// a dialog informing the user about the cache miss.
const maybeTrace = await tryGetTrace(traceUuid);
- const navigateToOldTraceUuid = () => {
- Router.navigate(
- `#!/viewer?local_cache_key=${globals.state.traceUuid ?? ''}`,
- );
- };
+ const navigateToOldTraceUuid = () =>
+ Router.navigate(`#!/viewer?local_cache_key=${curCacheUuid}`);
if (!maybeTrace) {
showModal({
@@ -162,8 +161,8 @@
// the trace without showing any further dialog. This is the case of tab
// discarding, reloading or pasting a url with a local_cache_key in an empty
// instance.
- if (globals.state.traceUuid === undefined) {
- globals.dispatch(Actions.openTraceFromBuffer(maybeTrace));
+ if (curTrace === undefined) {
+ AppImpl.instance.openTraceFromBuffer(maybeTrace);
return;
}
@@ -189,7 +188,7 @@
),
m(
'pre',
- `Old trace: ${globals.state.traceUuid || '<no trace>'}\n` +
+ `Old trace: ${curTrace !== undefined ? curCacheUuid : '<no trace>'}\n` +
`New trace: ${traceUuid}`,
),
),
@@ -200,7 +199,7 @@
primary: true,
action: () => {
hasOpenedNewTrace = true;
- globals.dispatch(Actions.openTraceFromBuffer(maybeTrace));
+ AppImpl.instance.openTraceFromBuffer(maybeTrace);
},
},
{text: 'Cancel'},
@@ -228,39 +227,20 @@
const fileName = url.split('/').pop() ?? 'local_trace.pftrace';
const request = fetch(url)
.then((response) => response.blob())
- .then((blob) => {
- globals.dispatch(
- Actions.openTraceFromFile({
- file: new File([blob], fileName),
- }),
- );
- })
+ .then((b) => AppImpl.instance.openTraceFromFile(new File([b], fileName)))
.catch((e) => alert(`Could not load local trace ${e}`));
taskTracker.trackPromise(request, 'Downloading local trace');
} else {
- globals.dispatch(Actions.openTraceFromUrl({url}));
+ AppImpl.instance.openTraceFromUrl(url);
}
}
function openTraceFromAndroidBugTool() {
- // TODO(hjd): Unify updateStatus and TaskTracker
- globals.dispatch(
- Actions.updateStatus({
- msg: 'Loading trace from ABT extension',
- timestamp: Date.now() / 1000,
- }),
- );
+ const msg = 'Loading trace from ABT extension';
+ AppImpl.instance.omnibox.showStatusMessage(msg);
const loadInfo = loadAndroidBugToolInfo();
- taskTracker.trackPromise(loadInfo, 'Loading trace from ABT extension');
+ taskTracker.trackPromise(loadInfo, msg);
loadInfo
- .then((info) => {
- globals.dispatch(
- Actions.openTraceFromFile({
- file: info.file,
- }),
- );
- })
- .catch((e) => {
- console.error(e);
- });
+ .then((info) => AppImpl.instance.openTraceFromFile(info.file))
+ .catch((e) => console.error(e));
}
diff --git a/ui/src/frontend/track_panel.ts b/ui/src/frontend/track_panel.ts
index 831242b..fcae30c 100644
--- a/ui/src/frontend/track_panel.ts
+++ b/ui/src/frontend/track_panel.ts
@@ -18,7 +18,7 @@
import {Bounds2D, Size2D, VerticalBounds} from '../base/geom';
import {Icons} from '../base/semantic_icons';
import {TimeScale} from '../base/time_scale';
-import {Optional, RequiredField} from '../base/utils';
+import {RequiredField} from '../base/utils';
import {calculateResolution} from '../common/resolution';
import {featureFlags} from '../core/feature_flags';
import {TrackRenderer} from '../core/track_manager';
@@ -28,10 +28,11 @@
import {Popup, PopupPosition} from '../widgets/popup';
import {Tree, TreeNode} from '../widgets/tree';
import {SELECTION_FILL_COLOR, TRACK_SHELL_WIDTH} from './css_constants';
-import {globals} from './globals';
import {Panel} from './panel_container';
import {TrackWidget} from '../widgets/track_widget';
import {raf} from '../core/raf_scheduler';
+import {Intent} from '../widgets/common';
+import {TraceImpl} from '../core/trace_impl';
const SHOW_TRACK_DETAILS_BUTTON = featureFlags.register({
id: 'showTrackDetailsButton',
@@ -45,11 +46,13 @@
export const DEFAULT_TRACK_HEIGHT_PX = 30;
interface TrackPanelAttrs {
+ readonly trace: TraceImpl;
readonly node: TrackNode;
readonly indentationLevel: number;
readonly trackRenderer?: TrackRenderer;
readonly revealOnCreate?: boolean;
readonly topOffsetPx: number;
+ readonly reorderable?: boolean;
}
export class TrackPanel implements Panel {
@@ -76,21 +79,32 @@
}
render(): m.Children {
- const {node, indentationLevel, trackRenderer, revealOnCreate, topOffsetPx} =
- this.attrs;
+ const {
+ node,
+ indentationLevel,
+ trackRenderer,
+ revealOnCreate,
+ topOffsetPx,
+ reorderable = false,
+ } = this.attrs;
+
+ const error = trackRenderer?.getError();
const buttons = [
SHOW_TRACK_DETAILS_BUTTON.get() &&
renderTrackDetailsButton(node, trackRenderer?.desc),
trackRenderer?.track.getTrackShellButtons?.(),
+ node.removable && renderCloseButton(node),
// Can't pin groups.. yet!
!node.hasChildren && renderPinButton(node),
- renderAreaSelectionCheckbox(node),
+ this.renderAreaSelectionCheckbox(node),
+ error && renderCrashButton(error, trackRenderer?.desc.pluginId),
];
let scrollIntoView = false;
- if (globals.trackManager.scrollToTrackNodeId === node.id) {
- globals.trackManager.scrollToTrackNodeId = undefined;
+ const tracks = this.attrs.trace.tracks;
+ if (tracks.scrollToTrackNodeId === node.id) {
+ tracks.scrollToTrackNodeId = undefined;
scrollIntoView = true;
}
@@ -99,7 +113,7 @@
title: node.title,
path: node.fullPath.join('/'),
heightPx: this.heightPx,
- error: trackRenderer?.getError(),
+ error: Boolean(trackRenderer?.getError()),
chips: trackRenderer?.desc.chips,
indentationLevel,
topOffsetPx,
@@ -107,26 +121,27 @@
revealOnCreate: revealOnCreate || scrollIntoView,
collapsible: node.hasChildren,
collapsed: node.collapsed,
- highlight: isHighlighted(node),
+ highlight: this.isHighlighted(node),
isSummary: node.isSummary,
+ reorderable,
onToggleCollapsed: () => {
node.hasChildren && node.toggleCollapsed();
},
onTrackContentMouseMove: (pos, bounds) => {
- const timescale = getTimescaleForBounds(bounds);
+ const timescale = this.getTimescaleForBounds(bounds);
trackRenderer?.track.onMouseMove?.({
...pos,
timescale,
});
- raf.scheduleRedraw();
+ raf.scheduleCanvasRedraw();
},
onTrackContentMouseOut: () => {
trackRenderer?.track.onMouseOut?.();
- raf.scheduleRedraw();
+ raf.scheduleCanvasRedraw();
},
onTrackContentClick: (pos, bounds) => {
- const timescale = getTimescaleForBounds(bounds);
- raf.scheduleRedraw();
+ const timescale = this.getTimescaleForBounds(bounds);
+ raf.scheduleCanvasRedraw();
return (
trackRenderer?.track.onMouseClick?.({
...pos,
@@ -137,6 +152,20 @@
onupdate: () => {
trackRenderer?.track.onFullRedraw?.();
},
+ onMoveBefore: (nodeId: string) => {
+ const targetNode = node.workspace?.getTrackById(nodeId);
+ if (targetNode !== undefined) {
+ // Insert the target node before this one
+ targetNode.parent?.addChildBefore(targetNode, node);
+ }
+ },
+ onMoveAfter: (nodeId: string) => {
+ const targetNode = node.workspace?.getTrackById(nodeId);
+ if (targetNode !== undefined) {
+ // Insert the target node after this one
+ targetNode.parent?.addChildAfter(targetNode, node);
+ }
+ },
});
}
@@ -157,7 +186,7 @@
ctx.translate(TRACK_SHELL_WIDTH, 0);
canvasClip(ctx, 0, 0, trackSize.width, trackSize.height);
- const visibleWindow = globals.timeline.visibleWindow;
+ const visibleWindow = this.attrs.trace.timeline.visibleWindow;
const timescale = new TimeScale(visibleWindow, {
left: 0,
right: trackSize.width,
@@ -177,39 +206,194 @@
}
}
- highlightIfTrackInAreaSelection(ctx, timescale, node, trackSize);
+ this.highlightIfTrackInAreaSelection(ctx, timescale, node, trackSize);
}
- getSliceVerticalBounds(depth: number): Optional<VerticalBounds> {
+ getSliceVerticalBounds(depth: number): VerticalBounds | undefined {
if (this.attrs.trackRenderer === undefined) {
return undefined;
}
return this.attrs.trackRenderer.track.getSliceVerticalBounds?.(depth);
}
-}
-function getTimescaleForBounds(bounds: Bounds2D) {
- const timeWindow = globals.timeline.visibleWindow;
- return new TimeScale(timeWindow, {
- left: 0,
- right: bounds.right - bounds.left,
- });
-}
+ private getTimescaleForBounds(bounds: Bounds2D) {
+ const timeWindow = this.attrs.trace.timeline.visibleWindow;
+ return new TimeScale(timeWindow, {
+ left: 0,
+ right: bounds.right - bounds.left,
+ });
+ }
-function isHighlighted(node: TrackNode) {
- // The track should be highlighted if the current search result matches this
- // track or one of its children.
- const searchIndex = globals.searchManager.resultIndex;
- const searchResults = globals.searchManager.searchResults;
+ private isHighlighted(node: TrackNode) {
+ // The track should be highlighted if the current search result matches this
+ // track or one of its children.
+ const searchIndex = this.attrs.trace.search.resultIndex;
+ const searchResults = this.attrs.trace.search.searchResults;
- if (searchIndex !== -1 && searchResults !== undefined) {
- const uri = searchResults.trackUris[searchIndex];
- // Highlight if this or any children match the search results
- if (uri === node.uri || node.flatTracks.find((t) => t.uri === uri)) {
+ if (searchIndex !== -1 && searchResults !== undefined) {
+ const uri = searchResults.trackUris[searchIndex];
+ // Highlight if this or any children match the search results
+ if (uri === node.uri || node.flatTracks.find((t) => t.uri === uri)) {
+ return true;
+ }
+ }
+
+ const curSelection = this.attrs.trace.selection;
+ if (
+ curSelection.selection.kind === 'track' &&
+ curSelection.selection.trackUri === node.uri
+ ) {
return true;
}
+
+ return false;
}
- return false;
+
+ private highlightIfTrackInAreaSelection(
+ ctx: CanvasRenderingContext2D,
+ timescale: TimeScale,
+ node: TrackNode,
+ size: Size2D,
+ ) {
+ const selection = this.attrs.trace.selection.selection;
+ if (selection.kind !== 'area') {
+ return;
+ }
+
+ const tracksWithUris = node.flatTracks.filter(
+ (t) => t.uri !== undefined,
+ ) as ReadonlyArray<RequiredField<TrackNode, 'uri'>>;
+
+ let selected = false;
+ if (node.isSummary) {
+ selected = tracksWithUris.some((track) =>
+ selection.trackUris.includes(track.uri),
+ );
+ } else {
+ if (node.uri) {
+ selected = selection.trackUris.includes(node.uri);
+ }
+ }
+
+ if (selected) {
+ const selectedAreaDuration = selection.end - selection.start;
+ ctx.fillStyle = SELECTION_FILL_COLOR;
+ ctx.fillRect(
+ timescale.timeToPx(selection.start),
+ 0,
+ timescale.durationToPx(selectedAreaDuration),
+ size.height,
+ );
+ }
+ }
+
+ private renderAreaSelectionCheckbox(node: TrackNode): m.Children {
+ const selectionManager = this.attrs.trace.selection;
+ const selection = selectionManager.selection;
+ if (selection.kind === 'area') {
+ if (node.isSummary) {
+ const tracksWithUris = node.flatTracks.filter(
+ (t) => t.uri !== undefined,
+ ) as ReadonlyArray<RequiredField<TrackNode, 'uri'>>;
+ // Check if any nodes within are selected
+ const childTracksInSelection = tracksWithUris.map((t) =>
+ selection.trackUris.includes(t.uri),
+ );
+ if (childTracksInSelection.every((b) => b)) {
+ return m(Button, {
+ onclick: (e: MouseEvent) => {
+ const uris = tracksWithUris.map((t) => t.uri);
+ selectionManager.toggleGroupAreaSelection(uris);
+ e.stopPropagation();
+ },
+ compact: true,
+ icon: Icons.Checkbox,
+ title: 'Remove child tracks from selection',
+ });
+ } else if (childTracksInSelection.some((b) => b)) {
+ return m(Button, {
+ onclick: (e: MouseEvent) => {
+ const uris = tracksWithUris.map((t) => t.uri);
+ selectionManager.toggleGroupAreaSelection(uris);
+ e.stopPropagation();
+ },
+ compact: true,
+ icon: Icons.IndeterminateCheckbox,
+ title: 'Add remaining child tracks to selection',
+ });
+ } else {
+ return m(Button, {
+ onclick: (e: MouseEvent) => {
+ const uris = tracksWithUris.map((t) => t.uri);
+ selectionManager.toggleGroupAreaSelection(uris);
+ e.stopPropagation();
+ },
+ compact: true,
+ icon: Icons.BlankCheckbox,
+ title: 'Add child tracks to selection',
+ });
+ }
+ } else {
+ const nodeUri = node.uri;
+ if (nodeUri) {
+ return (
+ selection.kind === 'area' &&
+ m(Button, {
+ onclick: (e: MouseEvent) => {
+ selectionManager.toggleTrackAreaSelection(nodeUri);
+ e.stopPropagation();
+ },
+ compact: true,
+ ...(selection.trackUris.includes(nodeUri)
+ ? {icon: Icons.Checkbox, title: 'Remove track'}
+ : {icon: Icons.BlankCheckbox, title: 'Add track to selection'}),
+ })
+ );
+ }
+ }
+ }
+ return undefined;
+ }
+}
+
+function renderCrashButton(error: Error, pluginId?: string) {
+ return m(
+ Popup,
+ {
+ trigger: m(Button, {
+ icon: Icons.Crashed,
+ compact: true,
+ }),
+ },
+ m(
+ '.pf-track-crash-popup',
+ m('span', 'This track has crashed.'),
+ pluginId && m('span', `Owning plugin: ${pluginId}`),
+ m(Button, {
+ label: 'View & Report Crash',
+ intent: Intent.Primary,
+ className: Popup.DISMISS_POPUP_GROUP_CLASS,
+ onclick: () => {
+ throw error;
+ },
+ }),
+ // TODO(stevegolton): In the future we should provide a quick way to
+ // disable the plugin, or provide a link to the plugin page, but this
+ // relies on the plugin page being fully functional.
+ ),
+ );
+}
+
+function renderCloseButton(node: TrackNode) {
+ return m(Button, {
+ onclick: (e) => {
+ node.remove();
+ e.stopPropagation();
+ },
+ icon: Icons.Close,
+ title: 'Close track',
+ compact: true,
+ });
}
function renderPinButton(node: TrackNode): m.Children {
@@ -227,111 +411,6 @@
});
}
-function highlightIfTrackInAreaSelection(
- ctx: CanvasRenderingContext2D,
- timescale: TimeScale,
- node: TrackNode,
- size: Size2D,
-) {
- const selection = globals.selectionManager.selection;
- if (selection.kind !== 'area') {
- return;
- }
-
- const tracksWithUris = node.flatTracks.filter(
- (t) => t.uri !== undefined,
- ) as ReadonlyArray<RequiredField<TrackNode, 'uri'>>;
-
- let selected = false;
- if (node.hasChildren) {
- selected = tracksWithUris.some((track) =>
- selection.trackUris.includes(track.uri),
- );
- } else {
- if (node.uri) {
- selected = selection.trackUris.includes(node.uri);
- }
- }
-
- if (selected) {
- const selectedAreaDuration = selection.end - selection.start;
- ctx.fillStyle = SELECTION_FILL_COLOR;
- ctx.fillRect(
- timescale.timeToPx(selection.start),
- 0,
- timescale.durationToPx(selectedAreaDuration),
- size.height,
- );
- }
-}
-
-function renderAreaSelectionCheckbox(node: TrackNode): m.Children {
- const selection = globals.selectionManager.selection;
- if (selection.kind === 'area') {
- if (node.hasChildren) {
- const tracksWithUris = node.flatTracks.filter(
- (t) => t.uri !== undefined,
- ) as ReadonlyArray<RequiredField<TrackNode, 'uri'>>;
- // Check if any nodes within are selected
- const childTracksInSelection = tracksWithUris.map((t) =>
- selection.trackUris.includes(t.uri),
- );
- if (childTracksInSelection.every((b) => b)) {
- return m(Button, {
- onclick: (e: MouseEvent) => {
- const uris = tracksWithUris.map((t) => t.uri);
- globals.selectionManager.toggleGroupAreaSelection(uris);
- e.stopPropagation();
- },
- compact: true,
- icon: Icons.Checkbox,
- title: 'Remove child tracks from selection',
- });
- } else if (childTracksInSelection.some((b) => b)) {
- return m(Button, {
- onclick: (e: MouseEvent) => {
- const uris = tracksWithUris.map((t) => t.uri);
- globals.selectionManager.toggleGroupAreaSelection(uris);
- e.stopPropagation();
- },
- compact: true,
- icon: Icons.IndeterminateCheckbox,
- title: 'Add remaining child tracks to selection',
- });
- } else {
- return m(Button, {
- onclick: (e: MouseEvent) => {
- const uris = tracksWithUris.map((t) => t.uri);
- globals.selectionManager.toggleGroupAreaSelection(uris);
- e.stopPropagation();
- },
- compact: true,
- icon: Icons.BlankCheckbox,
- title: 'Add child tracks to selection',
- });
- }
- } else {
- const nodeUri = node.uri;
- if (nodeUri) {
- return (
- selection.kind === 'area' &&
- m(Button, {
- onclick: (e: MouseEvent) => {
- globals.selectionManager.toggleTrackAreaSelection(nodeUri);
- e.stopPropagation();
- },
- compact: true,
- ...(selection.trackUris.includes(nodeUri)
- ? {icon: Icons.Checkbox, title: 'Remove track'}
- : {icon: Icons.BlankCheckbox, title: 'Add track to selection'}),
- })
- );
- }
- }
- }
- return undefined;
-}
-
function renderTrackDetailsButton(
node: TrackNode,
td?: TrackDescriptor,
@@ -367,6 +446,10 @@
}),
m(TreeNode, {left: 'Path', right: fullPath}),
m(TreeNode, {left: 'Title', right: node.title}),
+ m(TreeNode, {
+ left: 'Workspace',
+ right: node.workspace?.title ?? '[no workspace]',
+ }),
td && m(TreeNode, {left: 'Plugin ID', right: td.pluginId}),
td &&
m(
diff --git a/ui/src/frontend/tracks/custom_sql_table_slice_track.ts b/ui/src/frontend/tracks/custom_sql_table_slice_track.ts
index f97142b..7d3caa3 100644
--- a/ui/src/frontend/tracks/custom_sql_table_slice_track.ts
+++ b/ui/src/frontend/tracks/custom_sql_table_slice_track.ts
@@ -12,17 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {generateSqlWithInternalLayout} from '../../common/internal_layout_utils';
-import {LegacySelection} from '../../public/selection';
-import {OnSliceClickArgs} from '../base_slice_track';
-import {GenericSliceDetailsTabConfigBase} from '../generic_slice_details_tab';
-import {globals} from '../globals';
+import {generateSqlWithInternalLayout} from '../../trace_processor/sql_utils/layout';
import {NAMED_ROW, NamedRow, NamedSliceTrack} from '../named_slice_track';
import {NewTrackArgs} from '../track';
import {createView} from '../../trace_processor/sql_utils';
import {Slice} from '../../public/track';
-import {uuidv4} from '../../base/uuid';
import {AsyncDisposableStack} from '../../base/disposable_stack';
+import {sqlNameSafe} from '../../base/string_utils';
export interface CustomSqlImportConfig {
modules: string[];
@@ -37,19 +33,17 @@
disposable?: AsyncDisposable;
}
-export interface CustomSqlDetailsPanelConfig {
- // Type of details panel to create
- kind: string;
- // Config for the details panel
- config: GenericSliceDetailsTabConfigBase;
-}
-
export abstract class CustomSqlTableSliceTrack extends NamedSliceTrack<
Slice,
NamedRow
> {
protected readonly tableName;
+ constructor(args: NewTrackArgs) {
+ super(args);
+ this.tableName = `customsqltableslicetrack_${sqlNameSafe(args.uri)}`;
+ }
+
getRowSpec(): NamedRow {
return NAMED_ROW;
}
@@ -58,22 +52,10 @@
return this.rowToSliceBase(row);
}
- constructor(args: NewTrackArgs) {
- super(args);
- this.tableName = `customsqltableslicetrack_${uuidv4()
- .split('-')
- .join('_')}`;
- }
-
abstract getSqlDataSource():
| CustomSqlTableDefConfig
| Promise<CustomSqlTableDefConfig>;
- // Override by subclasses.
- abstract getDetailsPanel(
- args: OnSliceClickArgs<Slice>,
- ): CustomSqlDetailsPanelConfig;
-
getSqlImports(): CustomSqlImportConfig {
return {
modules: [] as string[],
@@ -109,32 +91,6 @@
return `SELECT * FROM ${this.tableName}`;
}
- isSelectionHandled(selection: LegacySelection) {
- if (selection.kind !== 'GENERIC_SLICE') {
- return false;
- }
- return selection.trackUri === this.uri;
- }
-
- onSliceClick(args: OnSliceClickArgs<Slice>) {
- if (this.getDetailsPanel(args) === undefined) {
- return;
- }
-
- const detailsPanelConfig = this.getDetailsPanel(args);
- globals.selectionManager.selectGenericSlice({
- id: args.slice.id,
- sqlTableName: this.tableName,
- start: args.slice.ts,
- duration: args.slice.dur,
- trackUri: this.uri,
- detailsPanelConfig: {
- kind: detailsPanelConfig.kind,
- config: detailsPanelConfig.config,
- },
- });
- }
-
async loadImports() {
for (const importModule of this.getSqlImports().modules) {
await this.engine.query(`INCLUDE PERFETTO MODULE ${importModule};`);
diff --git a/ui/src/frontend/ui_main.ts b/ui/src/frontend/ui_main.ts
index 5912189..c67f5b7 100644
--- a/ui/src/frontend/ui_main.ts
+++ b/ui/src/frontend/ui_main.ts
@@ -16,9 +16,8 @@
import {copyToClipboard} from '../base/clipboard';
import {findRef} from '../base/dom_utils';
import {FuzzyFinder} from '../base/fuzzy';
-import {assertExists, assertTrue, assertUnreachable} from '../base/logging';
+import {assertExists, assertUnreachable} from '../base/logging';
import {undoCommonChatAppReplacements} from '../base/string_utils';
-import {Actions} from '../common/actions';
import {
DurationPrecision,
setDurationPrecision,
@@ -30,17 +29,14 @@
import {HotkeyConfig, HotkeyContext} from '../widgets/hotkey_context';
import {HotkeyGlyphs} from '../widgets/hotkey_glyphs';
import {maybeRenderFullscreenModalDialog, showModal} from '../widgets/modal';
-import {onClickCopy} from './clipboard';
import {CookieConsent} from './cookie_consent';
-import {globals} from './globals';
import {toggleHelp} from './help_modal';
import {Omnibox, OmniboxOption} from './omnibox';
import {addQueryResultsTab} from '../public/lib/query_table/query_result_tab';
import {Sidebar} from './sidebar';
import {Topbar} from './topbar';
-import {shareTrace} from './trace_attrs';
+import {shareTrace} from './trace_share_utils';
import {AggregationsTabs} from './aggregation_tab';
-import {publishPermalinkHash} from './publish';
import {OmniboxMode} from '../core/omnibox_manager';
import {PromptOption} from '../public/omnibox';
import {DisposableStack} from '../base/disposable_stack';
@@ -50,39 +46,15 @@
import {NotesEditorTab} from './notes_panel';
import {NotesListEditor} from './notes_list_editor';
import {getTimeSpanOfSelectionOrVisibleWindow} from '../public/utils';
-import {scrollTo} from '../public/scroll_helper';
const OMNIBOX_INPUT_REF = 'omnibox';
-function renderPermalink(): m.Children {
- const hash = globals.permalinkHash;
- if (!hash) return null;
- const url = `${self.location.origin}/#!/?s=${hash}`;
- const linkProps = {title: 'Click to copy the URL', onclick: onClickCopy(url)};
-
- return m('.alert-permalink', [
- m('div', 'Permalink: ', m(`a[href=${url}]`, linkProps, url)),
- m(
- 'button',
- {
- onclick: () => publishPermalinkHash(undefined),
- },
- m('i.material-icons.disallow-selection', 'close'),
- ),
- ]);
-}
-
-class Alerts implements m.ClassComponent {
- view() {
- return m('.alerts', renderPermalink());
- }
-}
-
// This wrapper creates a new instance of UiMainPerTrace for each new trace
// loaded (including the case of no trace at the beginning).
export class UiMain implements m.ClassComponent {
view({children}: m.CVnode) {
- return [m(UiMainPerTrace, {key: globals.currentTraceId}, children)];
+ const currentTraceId = AppImpl.instance.trace?.engine.engineId ?? '';
+ return [m(UiMainPerTrace, {key: currentTraceId}, children)];
}
}
@@ -98,9 +70,10 @@
private trace?: TraceImpl;
// This function is invoked once per trace.
- async oncreate(vnode: m.VnodeDOM) {
- this.updateOmniboxInputRef(vnode.dom);
- this.maybeFocusOmnibar();
+ constructor() {
+ const app = AppImpl.instance;
+ const trace = app.trace;
+ this.trace = trace;
// Register global commands (commands that are useful even without a trace
// loaded).
@@ -108,7 +81,7 @@
{
id: 'perfetto.OpenCommandPalette',
name: 'Open command palette',
- callback: () => AppImpl.instance.omnibox.setMode(OmniboxMode.Command),
+ callback: () => app.omnibox.setMode(OmniboxMode.Command),
defaultHotkey: '!Mod+Shift+P',
},
@@ -120,15 +93,12 @@
},
];
globalCmds.forEach((cmd) => {
- this.trash.use(AppImpl.instance.commands.registerCommand(cmd));
+ this.trash.use(app.commands.registerCommand(cmd));
});
// When the UI loads there is no trace. There is no point registering
// commands or anything in this state as they will be useless.
- const trace = AppImpl.instance.trace as TraceImpl;
if (trace === undefined) return;
- assertTrue(trace instanceof TraceImpl);
- this.trace = trace;
document.title = `${trace.traceInfo.traceTitle || 'Trace'} - Perfetto UI`;
this.maybeShowJsonWarning();
@@ -144,7 +114,7 @@
isEphemeral: false,
content: {
getTitle: () => 'Notes & markers',
- render: () => m(NotesListEditor),
+ render: () => m(NotesListEditor, {trace}),
},
}),
);
@@ -173,10 +143,7 @@
];
const promptText = 'Select format...';
- const result = await AppImpl.instance.omnibox.prompt(
- promptText,
- options,
- );
+ const result = await app.omnibox.prompt(promptText, options);
if (result === undefined) return;
setTimestampFormat(result as TimestampFormat);
raf.scheduleFullRedraw();
@@ -195,10 +162,7 @@
];
const promptText = 'Select duration precision mode...';
- const result = await AppImpl.instance.omnibox.prompt(
- promptText,
- options,
- );
+ const result = await app.omnibox.prompt(promptText, options);
if (result === undefined) return;
setDurationPrecision(result as DurationPrecision);
raf.scheduleFullRedraw();
@@ -207,9 +171,8 @@
{
id: 'perfetto.TogglePerformanceMetrics',
name: 'Toggle performance metrics',
- callback: () => {
- globals.dispatch(Actions.togglePerfDebug({}));
- },
+ callback: () =>
+ (app.perfDebugging.enabled = !app.perfDebugging.enabled),
},
{
id: 'perfetto.ShareTrace',
@@ -269,8 +232,8 @@
{
id: 'perfetto.SetTemporarySpanNote',
name: 'Set the temporary span note based on the current selection',
- callback: async () => {
- const range = await trace.selection.findTimeRangeOfSelection();
+ callback: () => {
+ const range = trace.selection.findTimeRangeOfSelection();
if (range) {
trace.notes.addSpanNote({
start: range.start,
@@ -284,8 +247,8 @@
{
id: 'perfetto.AddSpanNote',
name: 'Add a new span note based on the current selection',
- callback: async () => {
- const range = await trace.selection.findTimeRangeOfSelection();
+ callback: () => {
+ const range = trace.selection.findTimeRangeOfSelection();
if (range) {
trace.notes.addSpanNote({
start: range.start,
@@ -341,7 +304,7 @@
// - If nothing is selected, or all selected tracks are entirely
// selected, then select the entire trace. This allows double tapping
// Ctrl+A to select the entire track, then select the entire trace.
- let tracksToSelect: string[] = [];
+ let tracksToSelect: string[];
const selection = trace.selection.selection;
if (selection.kind === 'area') {
// Something is already selected, let's see if it covers the entire
@@ -377,17 +340,27 @@
defaultHotkey: 'Mod+A',
},
{
- id: 'perfetto.ScrollToTrack',
- name: 'Scroll to track',
- callback: async () => {
- const opts = trace.tracks
- .getAllTracks()
- .map((td) => ({key: td.uri, displayName: td.uri}));
- const result = await trace.omnibox.prompt('Choose a track', opts);
- if (result) {
- scrollTo({track: {uri: result, expandGroup: true}});
+ id: 'perfetto.ConvertSelectionToArea',
+ name: 'Convert the current selection to an area selection',
+ callback: () => {
+ const selection = trace.selection.selection;
+ const range = trace.selection.findTimeRangeOfSelection();
+ if (selection.kind === 'track_event' && range) {
+ trace.selection.selectArea({
+ start: range.start,
+ end: range.end,
+ trackUris: [selection.trackUri],
+ });
}
},
+ // TODO(stevegolton): Decide on a sensible hotkey.
+ // defaultHotkey: 'L',
+ },
+ {
+ id: 'perfetto.ToggleDrawer',
+ name: 'Toggle drawer',
+ defaultHotkey: 'Q',
+ callback: () => trace.tabs.toggleTabPanelVisibility(),
},
];
@@ -398,24 +371,18 @@
}
private renderOmnibox(): m.Children {
- const msgTTL = globals.state.status.timestamp + 1 - Date.now() / 1e3;
- const engineIsBusy =
- globals.state.engine !== undefined && !globals.state.engine.ready;
-
- if (msgTTL > 0 || engineIsBusy) {
- setTimeout(() => raf.scheduleFullRedraw(), msgTTL * 1000);
+ const omnibox = AppImpl.instance.omnibox;
+ const omniboxMode = omnibox.mode;
+ const statusMessage = omnibox.statusMessage;
+ if (statusMessage !== undefined) {
return m(
`.omnibox.message-mode`,
m(`input[readonly][disabled][ref=omnibox]`, {
value: '',
- placeholder: globals.state.status.msg,
+ placeholder: statusMessage,
}),
);
- }
-
- const omniboxMode = AppImpl.instance.omnibox.mode;
-
- if (omniboxMode === OmniboxMode.Command) {
+ } else if (omniboxMode === OmniboxMode.Command) {
return this.renderCommandOmnibox();
} else if (omniboxMode === OmniboxMode.Prompt) {
return this.renderPromptOmnibox();
@@ -429,7 +396,8 @@
}
renderPromptOmnibox(): m.Children {
- const prompt = assertExists(AppImpl.instance.omnibox.pendingPrompt);
+ const omnibox = AppImpl.instance.omnibox;
+ const prompt = assertExists(omnibox.pendingPrompt);
let options: OmniboxOption[] | undefined = undefined;
@@ -438,7 +406,7 @@
prompt.options,
({displayName}) => displayName,
);
- const result = fuzzy.find(AppImpl.instance.omnibox.text);
+ const result = fuzzy.find(omnibox.text);
options = result.map((result) => {
return {
key: result.item.key,
@@ -448,38 +416,35 @@
}
return m(Omnibox, {
- value: AppImpl.instance.omnibox.text,
+ value: omnibox.text,
placeholder: prompt.text,
inputRef: OMNIBOX_INPUT_REF,
extraClasses: 'prompt-mode',
closeOnOutsideClick: true,
options,
- selectedOptionIndex: AppImpl.instance.omnibox.selectionIndex,
+ selectedOptionIndex: omnibox.selectionIndex,
onSelectedOptionChanged: (index) => {
- AppImpl.instance.omnibox.setSelectionIndex(index);
+ omnibox.setSelectionIndex(index);
raf.scheduleFullRedraw();
},
onInput: (value) => {
- AppImpl.instance.omnibox.setText(value);
- AppImpl.instance.omnibox.setSelectionIndex(0);
+ omnibox.setText(value);
+ omnibox.setSelectionIndex(0);
raf.scheduleFullRedraw();
},
onSubmit: (value, _alt) => {
- AppImpl.instance.omnibox.resolvePrompt(value);
+ omnibox.resolvePrompt(value);
},
onClose: () => {
- AppImpl.instance.omnibox.rejectPrompt();
+ omnibox.rejectPrompt();
},
});
}
renderCommandOmnibox(): m.Children {
- const cmdMgr = AppImpl.instance.commands;
-
// Fuzzy-filter commands by the filter string.
- const filteredCmds = cmdMgr.fuzzyFilterCommands(
- AppImpl.instance.omnibox.text,
- );
+ const {commands, omnibox} = AppImpl.instance;
+ const filteredCmds = commands.fuzzyFilterCommands(omnibox.text);
// Create an array of commands with attached heuristics from the recent
// command register.
@@ -510,35 +475,35 @@
});
return m(Omnibox, {
- value: AppImpl.instance.omnibox.text,
+ value: omnibox.text,
placeholder: 'Filter commands...',
inputRef: OMNIBOX_INPUT_REF,
extraClasses: 'command-mode',
options,
closeOnSubmit: true,
closeOnOutsideClick: true,
- selectedOptionIndex: AppImpl.instance.omnibox.selectionIndex,
+ selectedOptionIndex: omnibox.selectionIndex,
onSelectedOptionChanged: (index) => {
- AppImpl.instance.omnibox.setSelectionIndex(index);
+ omnibox.setSelectionIndex(index);
raf.scheduleFullRedraw();
},
onInput: (value) => {
- AppImpl.instance.omnibox.setText(value);
- AppImpl.instance.omnibox.setSelectionIndex(0);
+ omnibox.setText(value);
+ omnibox.setSelectionIndex(0);
raf.scheduleFullRedraw();
},
onClose: () => {
if (this.omniboxInputEl) {
this.omniboxInputEl.blur();
}
- AppImpl.instance.omnibox.reset();
+ omnibox.reset();
},
onSubmit: (key: string) => {
this.addRecentCommand(key);
- cmdMgr.runCommand(key);
+ commands.runCommand(key);
},
onGoBack: () => {
- AppImpl.instance.omnibox.reset();
+ omnibox.reset();
},
});
}
@@ -659,14 +624,17 @@
return m('.stepthrough', children);
}
+ oncreate(vnode: m.VnodeDOM) {
+ this.updateOmniboxInputRef(vnode.dom);
+ this.maybeFocusOmnibar();
+ }
+
view({children}: m.Vnode): m.Children {
- const cmdMgr = AppImpl.instance.commands;
const hotkeys: HotkeyConfig[] = [];
- const commands = cmdMgr.commands;
- for (const {id, defaultHotkey} of commands) {
+ for (const {id, defaultHotkey} of AppImpl.instance.commands.commands) {
if (defaultHotkey) {
hotkeys.push({
- callback: () => cmdMgr.runCommand(id),
+ callback: () => AppImpl.instance.commands.runCommand(id),
hotkey: defaultHotkey,
});
}
@@ -682,11 +650,10 @@
omnibox: this.renderOmnibox(),
trace: this.trace,
}),
- m(Alerts),
children,
m(CookieConsent),
maybeRenderFullscreenModalDialog(),
- globals.state.perfDebug && m('.perf-stats'),
+ AppImpl.instance.perfDebugging.renderPerfStats(),
),
);
}
@@ -746,7 +713,7 @@
if (
!isJsonTrace ||
window.localStorage.getItem(SHOWN_JSON_WARNING_KEY) === 'true' ||
- globals.embeddedMode
+ AppImpl.instance.embeddedMode
) {
// When in embedded mode, the host app will control which trace format
// it passes to Perfetto, so we don't need to show this warning.
diff --git a/ui/src/frontend/value.ts b/ui/src/frontend/value.ts
index 8be8994..a57f2ea 100644
--- a/ui/src/frontend/value.ts
+++ b/ui/src/frontend/value.ts
@@ -14,7 +14,8 @@
import m from 'mithril';
import {Tree, TreeNode} from '../widgets/tree';
-import {PopupMenuButton, PopupMenuItem} from './popup_menu';
+import {PopupMenu2} from '../widgets/menu';
+import {Button} from '../widgets/button';
// This file implements a component for rendering JSON-like values (with
// customisation options like context menu and action buttons).
@@ -109,7 +110,7 @@
// Customisation parameters which apply to any Value (e.g. context menu).
interface ValueParams {
- contextMenu?: PopupMenuItem[];
+ contextMenu?: m.Child[];
}
// Customisation parameters which apply for a primitive value (e.g. showing
@@ -137,10 +138,15 @@
const left = [
name,
value.contextMenu
- ? m(PopupMenuButton, {
- icon: 'arrow_drop_down',
- items: value.contextMenu,
- })
+ ? m(
+ PopupMenu2,
+ {
+ trigger: m(Button, {
+ icon: 'arrow_drop_down',
+ }),
+ },
+ value.contextMenu,
+ )
: null,
];
if (isArray(value)) {
diff --git a/ui/src/frontend/viewer_page.ts b/ui/src/frontend/viewer_page.ts
index 8a9af07..9509c1d 100644
--- a/ui/src/frontend/viewer_page.ts
+++ b/ui/src/frontend/viewer_page.ts
@@ -17,19 +17,16 @@
import {removeFalsyValues} from '../base/array_utils';
import {canvasClip, canvasSave} from '../base/canvas_utils';
import {findRef, toHTMLElement} from '../base/dom_utils';
-import {Size2D} from '../base/geom';
+import {Size2D, VerticalBounds} from '../base/geom';
import {assertExists} from '../base/logging';
import {clamp} from '../base/math_utils';
import {Time, TimeSpan} from '../base/time';
import {TimeScale} from '../base/time_scale';
-import {exists} from '../base/utils';
import {featureFlags} from '../core/feature_flags';
import {raf} from '../core/raf_scheduler';
import {TrackNode} from '../public/workspace';
-import {EmptyState} from '../widgets/empty_state';
import {TRACK_BORDER_COLOR, TRACK_SHELL_WIDTH} from './css_constants';
import {renderFlows} from './flow_events_renderer';
-import {globals} from './globals';
import {generateTicks, getMaxMajorTicks, TickType} from './gridline_helper';
import {NotesPanel} from './notes_panel';
import {OverviewTimelinePanel} from './overview_timeline_panel';
@@ -39,15 +36,15 @@
PanelOrGroup,
RenderedPanelInfo,
} from './panel_container';
-import {publishShowPanningHint} from './publish';
import {TabPanel} from './tab_panel';
import {TickmarkPanel} from './tickmark_panel';
import {TimeAxisPanel} from './time_axis_panel';
import {TimeSelectionPanel} from './time_selection_panel';
-import {DISMISSED_PANNING_HINT_KEY} from './topbar';
import {TrackPanel} from './track_panel';
import {drawVerticalLineAtTime} from './vertical_line_helper';
-import {PageWithTraceAttrs} from './pages';
+import {TraceImpl} from '../core/trace_impl';
+import {PageWithTraceImplAttrs} from '../core/page_manager';
+import {AppImpl} from '../core/app_impl';
const OVERVIEW_PANEL_FLAG = featureFlags.register({
id: 'overviewVisible',
@@ -59,15 +56,16 @@
// Checks if the mousePos is within 3px of the start or end of the
// current selected time range.
function onTimeRangeBoundary(
+ trace: TraceImpl,
timescale: TimeScale,
mousePos: number,
): 'START' | 'END' | null {
- const selection = globals.selectionManager.selection;
+ const selection = trace.selection.selection;
if (selection.kind === 'area') {
// If frontend selectedArea exists then we are in the process of editing the
// time range and need to use that value instead.
- const area = globals.timeline.selectedArea
- ? globals.timeline.selectedArea
+ const area = trace.timeline.selectedArea
+ ? trace.timeline.selectedArea
: selection;
const start = timescale.timeToPx(area.start);
const end = timescale.timeToPx(area.end);
@@ -83,36 +81,51 @@
return null;
}
+interface SelectedContainer {
+ readonly containerClass: string;
+ readonly dragStartAbsY: number;
+ readonly dragEndAbsY: number;
+}
+
/**
* Top-most level component for the viewer page. Holds tracks, brush timeline,
* panels, and everything else that's part of the main trace viewer page.
*/
-export class ViewerPage implements m.ClassComponent<PageWithTraceAttrs> {
+export class ViewerPage implements m.ClassComponent<PageWithTraceImplAttrs> {
private zoomContent?: PanAndZoomHandler;
// Used to prevent global deselection if a pan/drag select occurred.
private keepCurrentSelection = false;
- private overviewTimelinePanel = new OverviewTimelinePanel();
- private timeAxisPanel = new TimeAxisPanel();
- private timeSelectionPanel = new TimeSelectionPanel();
- private notesPanel = new NotesPanel();
+ private overviewTimelinePanel: OverviewTimelinePanel;
+ private timeAxisPanel: TimeAxisPanel;
+ private timeSelectionPanel: TimeSelectionPanel;
+ private notesPanel: NotesPanel;
private tickmarkPanel: TickmarkPanel;
private timelineWidthPx?: number;
+ private selectedContainer?: SelectedContainer;
+ private showPanningHint = false;
private readonly PAN_ZOOM_CONTENT_REF = 'pan-and-zoom-content';
- constructor(vnode: m.CVnode<PageWithTraceAttrs>) {
+ constructor(vnode: m.CVnode<PageWithTraceImplAttrs>) {
+ this.notesPanel = new NotesPanel(vnode.attrs.trace);
+ this.timeAxisPanel = new TimeAxisPanel(vnode.attrs.trace);
+ this.timeSelectionPanel = new TimeSelectionPanel(vnode.attrs.trace);
this.tickmarkPanel = new TickmarkPanel(vnode.attrs.trace);
+ this.overviewTimelinePanel = new OverviewTimelinePanel(vnode.attrs.trace);
+ this.notesPanel = new NotesPanel(vnode.attrs.trace);
+ this.timeSelectionPanel = new TimeSelectionPanel(vnode.attrs.trace);
}
- oncreate(vnode: m.CVnodeDOM<PageWithTraceAttrs>) {
- const panZoomElRaw = findRef(vnode.dom, this.PAN_ZOOM_CONTENT_REF);
+ oncreate({dom, attrs}: m.CVnodeDOM<PageWithTraceImplAttrs>) {
+ const panZoomElRaw = findRef(dom, this.PAN_ZOOM_CONTENT_REF);
const panZoomEl = toHTMLElement(assertExists(panZoomElRaw));
+ const {top: panTop} = panZoomEl.getBoundingClientRect();
this.zoomContent = new PanAndZoomHandler({
element: panZoomEl,
onPanned: (pannedPx: number) => {
- const timeline = globals.timeline;
+ const timeline = attrs.trace.timeline;
if (this.timelineWidthPx === undefined) return;
@@ -123,28 +136,24 @@
});
const tDelta = timescale.pxToDuration(pannedPx);
timeline.panVisibleWindow(tDelta);
-
- // If the user has panned they no longer need the hint.
- localStorage.setItem(DISMISSED_PANNING_HINT_KEY, 'true');
- raf.scheduleRedraw();
},
onZoomed: (zoomedPositionPx: number, zoomRatio: number) => {
- const timeline = globals.timeline;
+ const timeline = attrs.trace.timeline;
// TODO(hjd): Avoid hardcoding TRACK_SHELL_WIDTH.
// TODO(hjd): Improve support for zooming in overview timeline.
const zoomPx = zoomedPositionPx - TRACK_SHELL_WIDTH;
- const rect = vnode.dom.getBoundingClientRect();
+ const rect = dom.getBoundingClientRect();
const centerPoint = zoomPx / (rect.width - TRACK_SHELL_WIDTH);
timeline.zoomVisibleWindow(1 - zoomRatio, centerPoint);
- raf.scheduleRedraw();
+ raf.scheduleCanvasRedraw();
},
editSelection: (currentPx: number) => {
if (this.timelineWidthPx === undefined) return false;
- const timescale = new TimeScale(globals.timeline.visibleWindow, {
+ const timescale = new TimeScale(attrs.trace.timeline.visibleWindow, {
left: 0,
right: this.timelineWidthPx,
});
- return onTimeRangeBoundary(timescale, currentPx) !== null;
+ return onTimeRangeBoundary(attrs.trace, timescale, currentPx) !== null;
},
onSelection: (
dragStartX: number,
@@ -154,8 +163,8 @@
currentY: number,
editing: boolean,
) => {
- const traceTime = globals.traceContext;
- const timeline = globals.timeline;
+ const traceTime = attrs.trace.traceInfo;
+ const timeline = attrs.trace.timeline;
if (this.timelineWidthPx === undefined) return;
@@ -171,16 +180,20 @@
});
if (editing) {
- const selection = globals.selectionManager.selection;
+ const selection = attrs.trace.selection.selection;
if (selection.kind === 'area') {
- const area = globals.timeline.selectedArea
- ? globals.timeline.selectedArea
+ const area = attrs.trace.timeline.selectedArea
+ ? attrs.trace.timeline.selectedArea
: selection;
let newTime = timescale
.pxToHpTime(currentX - TRACK_SHELL_WIDTH)
.toTime();
// Have to check again for when one boundary crosses over the other.
- const curBoundary = onTimeRangeBoundary(timescale, prevX);
+ const curBoundary = onTimeRangeBoundary(
+ attrs.trace,
+ timescale,
+ prevX,
+ );
if (curBoundary == null) return;
const keepTime = curBoundary === 'START' ? area.end : area.start;
// Don't drag selection outside of current screen.
@@ -207,30 +220,62 @@
timescale.pxToHpTime(startPx).toTime('floor'),
timescale.pxToHpTime(endPx).toTime('ceil'),
);
- timeline.areaY.start = dragStartY;
- timeline.areaY.end = currentY;
- publishShowPanningHint();
+
+ const absStartY = dragStartY + panTop;
+ const absCurrentY = currentY + panTop;
+ if (this.selectedContainer === undefined) {
+ for (const c of dom.querySelectorAll('.pf-panel-container')) {
+ const {top, bottom} = c.getBoundingClientRect();
+ if (top <= absStartY && absCurrentY <= bottom) {
+ const stack = assertExists(c.querySelector('.pf-panel-stack'));
+ const stackTop = stack.getBoundingClientRect().top;
+ this.selectedContainer = {
+ containerClass: Array.from(c.classList).filter(
+ (x) => x !== 'pf-panel-container',
+ )[0],
+ dragStartAbsY: -stackTop + absStartY,
+ dragEndAbsY: -stackTop + absCurrentY,
+ };
+ break;
+ }
+ }
+ } else {
+ const c = assertExists(
+ dom.querySelector(`.${this.selectedContainer.containerClass}`),
+ );
+ const {top, bottom} = c.getBoundingClientRect();
+ const boundedCurrentY = Math.min(
+ Math.max(top, absCurrentY),
+ bottom,
+ );
+ const stack = assertExists(c.querySelector('.pf-panel-stack'));
+ const stackTop = stack.getBoundingClientRect().top;
+ this.selectedContainer = {
+ ...this.selectedContainer,
+ dragEndAbsY: -stackTop + boundedCurrentY,
+ };
+ }
+ this.showPanningHint = true;
}
- raf.scheduleRedraw();
+ raf.scheduleCanvasRedraw();
},
endSelection: (edit: boolean) => {
- globals.timeline.areaY.start = undefined;
- globals.timeline.areaY.end = undefined;
- const area = globals.timeline.selectedArea;
+ this.selectedContainer = undefined;
+ const area = attrs.trace.timeline.selectedArea;
// If we are editing we need to pass the current id through to ensure
// the marked area with that id is also updated.
if (edit) {
- const selection = globals.selectionManager.selection;
+ const selection = attrs.trace.selection.selection;
if (selection.kind === 'area' && area) {
- globals.selectionManager.selectArea({...area});
+ attrs.trace.selection.selectArea({...area});
}
} else if (area) {
- globals.selectionManager.selectArea({...area});
+ attrs.trace.selection.selectArea({...area});
}
// Now the selection has ended we stored the final selected area in the
// global state and can remove the in progress selection from the
// timeline.
- globals.timeline.deselectArea();
+ attrs.trace.timeline.deselectArea();
// Full redraw to color track shell.
raf.scheduleFullRedraw();
},
@@ -241,8 +286,8 @@
if (this.zoomContent) this.zoomContent[Symbol.dispose]();
}
- view() {
- const scrollingPanels = renderToplevelPanels();
+ view({attrs}: m.CVnode<PageWithTraceImplAttrs>) {
+ const scrollingPanels = renderToplevelPanels(attrs.trace);
const result = m(
'.page.viewer-page',
@@ -256,12 +301,13 @@
this.keepCurrentSelection = false;
return;
}
- globals.selectionManager.clear();
+ attrs.trace.selection.clear();
},
},
m(
'.pf-timeline-header',
m(PanelContainer, {
+ trace: attrs.trace,
className: 'header-panel-container',
panels: removeFalsyValues([
OVERVIEW_PANEL_FLAG.get() && this.overviewTimelinePanel,
@@ -270,15 +316,19 @@
this.notesPanel,
this.tickmarkPanel,
]),
+ selectedYRange: this.getYRange('header-panel-container'),
}),
m('.scrollbar-spacer-vertical'),
),
m(PanelContainer, {
+ trace: attrs.trace,
className: 'pinned-panel-container',
- panels: globals.workspace.pinnedTracks.map((trackNode) => {
+ panels: attrs.trace.workspace.pinnedTracks.map((trackNode) => {
if (trackNode.uri) {
- const tr = globals.trackManager.getTrackRenderer(trackNode.uri);
+ const tr = attrs.trace.tracks.getTrackRenderer(trackNode.uri);
return new TrackPanel({
+ trace: attrs.trace,
+ reorderable: true,
node: trackNode,
trackRenderer: tr,
revealOnCreate: true,
@@ -287,6 +337,7 @@
});
} else {
return new TrackPanel({
+ trace: attrs.trace,
node: trackNode,
revealOnCreate: true,
indentationLevel: 0,
@@ -294,36 +345,49 @@
});
}
}),
- renderUnderlay,
- renderOverlay,
+ renderUnderlay: (ctx, size) => renderUnderlay(attrs.trace, ctx, size),
+ renderOverlay: (ctx, size, panels) =>
+ renderOverlay(attrs.trace, ctx, size, panels),
+ selectedYRange: this.getYRange('pinned-panel-container'),
}),
- scrollingPanels.length === 0 &&
- filterTermIsValid(globals.state.trackFilterTerm)
- ? m(
- EmptyState,
- {title: 'No matching tracks'},
- `No tracks match filter term "${globals.state.trackFilterTerm}"`,
- )
- : m(PanelContainer, {
- className: 'scrolling-panel-container',
- panels: scrollingPanels,
- onPanelStackResize: (width) => {
- const timelineWidth = width - TRACK_SHELL_WIDTH;
- this.timelineWidthPx = timelineWidth;
- },
- renderUnderlay,
- renderOverlay,
- }),
+ m(PanelContainer, {
+ trace: attrs.trace,
+ className: 'scrolling-panel-container',
+ panels: scrollingPanels,
+ onPanelStackResize: (width) => {
+ const timelineWidth = width - TRACK_SHELL_WIDTH;
+ this.timelineWidthPx = timelineWidth;
+ },
+ renderUnderlay: (ctx, size) => renderUnderlay(attrs.trace, ctx, size),
+ renderOverlay: (ctx, size, panels) =>
+ renderOverlay(attrs.trace, ctx, size, panels),
+ selectedYRange: this.getYRange('scrolling-panel-container'),
+ }),
),
- m(TabPanel),
+ m(TabPanel, {
+ trace: attrs.trace,
+ }),
+ this.showPanningHint && m(HelpPanningNotification),
);
- globals.trackManager.flushOldTracks();
+ attrs.trace.tracks.flushOldTracks();
return result;
}
+
+ private getYRange(cls: string): VerticalBounds | undefined {
+ if (this.selectedContainer?.containerClass !== cls) {
+ return undefined;
+ }
+ const {dragStartAbsY, dragEndAbsY} = this.selectedContainer;
+ return {
+ top: Math.min(dragStartAbsY, dragEndAbsY),
+ bottom: Math.max(dragStartAbsY, dragEndAbsY),
+ };
+ }
}
function renderUnderlay(
+ trace: TraceImpl,
ctx: CanvasRenderingContext2D,
canvasSize: Size2D,
): void {
@@ -335,14 +399,15 @@
using _ = canvasSave(ctx);
ctx.translate(TRACK_SHELL_WIDTH, 0);
- const timewindow = globals.timeline.visibleWindow;
+ const timewindow = trace.timeline.visibleWindow;
const timescale = new TimeScale(timewindow, {left: 0, right: size.width});
// Just render the gridlines - these should appear underneath all tracks
- drawGridLines(ctx, timewindow.toTimeSpan(), timescale, size);
+ drawGridLines(trace, ctx, timewindow.toTimeSpan(), timescale, size);
}
function renderOverlay(
+ trace: TraceImpl,
ctx: CanvasRenderingContext2D,
canvasSize: Size2D,
panels: ReadonlyArray<RenderedPanelInfo>,
@@ -357,32 +422,26 @@
canvasClip(ctx, 0, 0, size.width, size.height);
// TODO(primiano): plumb the TraceImpl obj throughout the viwer page.
- renderFlows(globals.trace, ctx, size, panels);
+ renderFlows(trace, ctx, size, panels);
- const timewindow = globals.timeline.visibleWindow;
+ const timewindow = trace.timeline.visibleWindow;
const timescale = new TimeScale(timewindow, {left: 0, right: size.width});
- renderHoveredNoteVertical(ctx, timescale, size);
- renderHoveredCursorVertical(ctx, timescale, size);
- renderWakeupVertical(ctx, timescale, size);
- renderNoteVerticals(ctx, timescale, size);
-}
-
-function filterTermIsValid(
- filterTerm: undefined | string,
-): filterTerm is string {
- // Note: Boolean(filterTerm) returns the same result, but this is clearer
- return filterTerm !== undefined && filterTerm !== '';
+ renderHoveredNoteVertical(trace, ctx, timescale, size);
+ renderHoveredCursorVertical(trace, ctx, timescale, size);
+ renderWakeupVertical(trace, ctx, timescale, size);
+ renderNoteVerticals(trace, ctx, timescale, size);
}
// Render the toplevel "scrolling" tracks and track groups
-function renderToplevelPanels(): PanelOrGroup[] {
- return renderNodes(globals.workspace.children, 0, 0);
+function renderToplevelPanels(trace: TraceImpl): PanelOrGroup[] {
+ return renderNodes(trace, trace.workspace.children, 0, 0);
}
// Given a list of tracks and a filter term, return a list pf panels filtered by
// the filter term
function renderNodes(
+ trace: TraceImpl,
nodes: ReadonlyArray<TrackNode>,
indent: number,
topOffsetPx: number,
@@ -390,14 +449,14 @@
return nodes.flatMap((node) => {
if (node.headless) {
// Render children as if this node doesn't exist
- return renderNodes(node.children, indent, topOffsetPx);
+ return renderNodes(trace, node.children, indent, topOffsetPx);
} else if (node.children.length === 0) {
- return renderTrackPanel(node, indent, topOffsetPx);
+ return renderTrackPanel(trace, node, indent, topOffsetPx);
} else {
- const headerPanel = renderTrackPanel(node, indent, topOffsetPx);
+ const headerPanel = renderTrackPanel(trace, node, indent, topOffsetPx);
const isSticky = node.isSummary;
const nextTopOffsetPx = isSticky
- ? topOffsetPx + headerPanel.heightPx ?? 0
+ ? topOffsetPx + headerPanel.heightPx
: topOffsetPx;
return {
kind: 'group',
@@ -407,22 +466,24 @@
topOffsetPx,
childPanels: node.collapsed
? []
- : renderNodes(node.children, indent + 1, nextTopOffsetPx),
+ : renderNodes(trace, node.children, indent + 1, nextTopOffsetPx),
};
}
});
}
function renderTrackPanel(
+ trace: TraceImpl,
trackNode: TrackNode,
indent: number,
topOffsetPx: number,
) {
let tr = undefined;
if (trackNode.uri) {
- tr = globals.trackManager.getTrackRenderer(trackNode.uri);
+ tr = trace.tracks.getTrackRenderer(trackNode.uri);
}
return new TrackPanel({
+ trace,
node: trackNode,
trackRenderer: tr,
indentationLevel: indent,
@@ -431,6 +492,7 @@
}
export function drawGridLines(
+ trace: TraceImpl,
ctx: CanvasRenderingContext2D,
timespan: TimeSpan,
timescale: TimeScale,
@@ -441,7 +503,7 @@
if (size.width > 0 && timespan.duration > 0n) {
const maxMajorTicks = getMaxMajorTicks(size.width);
- const offset = globals.trace.timeline.timestampOffset();
+ const offset = trace.timeline.timestampOffset();
for (const {type, time} of generateTicks(timespan, maxMajorTicks, offset)) {
const px = Math.floor(timescale.timeToPx(time));
if (type === TickType.MAJOR) {
@@ -455,15 +517,16 @@
}
export function renderHoveredCursorVertical(
+ trace: TraceImpl,
ctx: CanvasRenderingContext2D,
timescale: TimeScale,
size: Size2D,
) {
- if (globals.trace.timeline.hoverCursorTimestamp !== undefined) {
+ if (trace.timeline.hoverCursorTimestamp !== undefined) {
drawVerticalLineAtTime(
ctx,
timescale,
- globals.trace.timeline.hoverCursorTimestamp,
+ trace.timeline.hoverCursorTimestamp,
size.height,
`#344596`,
);
@@ -471,15 +534,16 @@
}
export function renderHoveredNoteVertical(
+ trace: TraceImpl,
ctx: CanvasRenderingContext2D,
timescale: TimeScale,
size: Size2D,
) {
- if (globals.state.hoveredNoteTimestamp !== -1n) {
+ if (trace.timeline.hoveredNoteTimestamp !== undefined) {
drawVerticalLineAtTime(
ctx,
timescale,
- globals.state.hoveredNoteTimestamp,
+ trace.timeline.hoveredNoteTimestamp,
size.height,
`#aaa`,
);
@@ -487,37 +551,32 @@
}
export function renderWakeupVertical(
+ trace: TraceImpl,
ctx: CanvasRenderingContext2D,
timescale: TimeScale,
size: Size2D,
) {
- const currentSelection = globals.selectionManager.legacySelection;
- const sliceDetails = globals.selectionManager.legacySelectionDetails;
- if (currentSelection !== null) {
- if (
- currentSelection.kind === 'SCHED_SLICE' &&
- exists(sliceDetails) &&
- sliceDetails.wakeupTs !== undefined
- ) {
- drawVerticalLineAtTime(
- ctx,
- timescale,
- sliceDetails.wakeupTs,
- size.height,
- `black`,
- );
- }
+ const selection = trace.selection.selection;
+ if (selection.kind === 'track_event' && selection.wakeupTs) {
+ drawVerticalLineAtTime(
+ ctx,
+ timescale,
+ selection.wakeupTs,
+ size.height,
+ `black`,
+ );
}
}
export function renderNoteVerticals(
+ trace: TraceImpl,
ctx: CanvasRenderingContext2D,
timescale: TimeScale,
size: Size2D,
) {
// All marked areas should have semi-transparent vertical lines
// marking the start and end.
- for (const note of globals.noteManager.notes.values()) {
+ for (const note of trace.notes.notes.values()) {
if (note.noteType === 'SPAN') {
const transparentNoteColor =
'rgba(' + hex.rgb(note.color.substr(1)).toString() + ', 0.65)';
@@ -548,3 +607,36 @@
}
}
}
+
+class HelpPanningNotification implements m.ClassComponent {
+ private readonly PANNING_HINT_KEY = 'dismissedPanningHint';
+ private dismissed = localStorage.getItem(this.PANNING_HINT_KEY) === 'true';
+
+ view() {
+ // Do not show the help notification in embedded mode because local storage
+ // does not persist for iFrames. The host is responsible for communicating
+ // to users that they can press '?' for help.
+ if (AppImpl.instance.embeddedMode || this.dismissed) {
+ return;
+ }
+ return m(
+ '.helpful-hint',
+ m(
+ '.hint-text',
+ 'Are you trying to pan? Use the WASD keys or hold shift to click ' +
+ "and drag. Press '?' for more help.",
+ ),
+ m(
+ 'button.hint-dismiss-button',
+ {
+ onclick: () => {
+ this.dismissed = true;
+ localStorage.setItem(this.PANNING_HINT_KEY, 'true');
+ raf.scheduleFullRedraw();
+ },
+ },
+ 'Dismiss',
+ ),
+ );
+ }
+}
diff --git a/ui/src/frontend/visualized_args_track.ts b/ui/src/frontend/visualized_args_track.ts
index 8e423bc..fdea5b3 100644
--- a/ui/src/frontend/visualized_args_track.ts
+++ b/ui/src/frontend/visualized_args_track.ts
@@ -18,7 +18,6 @@
import {ThreadSliceTrack} from './thread_slice_track';
import {uuidv4Sql} from '../base/uuid';
import {createView} from '../trace_processor/sql_utils';
-import {globals} from './globals';
import {Trace} from '../public/trace';
export interface VisualizedArgsTrackAttrs {
@@ -27,11 +26,13 @@
readonly trackId: number;
readonly maxDepth: number;
readonly argName: string;
+ readonly onClose: () => void;
}
-export class VisualisedArgsTrack extends ThreadSliceTrack {
+export class VisualizedArgsTrack extends ThreadSliceTrack {
private readonly viewName: string;
private readonly argName: string;
+ private readonly onClose: () => void;
constructor({
uri,
@@ -39,6 +40,7 @@
trackId,
maxDepth,
argName,
+ onClose,
}: VisualizedArgsTrackAttrs) {
const uuid = uuidv4Sql();
const escapedArgName = argName.replace(/[^a-zA-Z]/g, '_');
@@ -47,6 +49,7 @@
super({trace, uri}, trackId, maxDepth, viewName);
this.viewName = viewName;
this.argName = argName;
+ this.onClose = onClose;
}
async onInit() {
@@ -81,11 +84,9 @@
getTrackShellButtons(): m.Children {
return m(Button, {
- onclick: () => {
- globals.workspace.findTrackByUri(this.uri)?.remove();
- },
+ onclick: () => this.onClose(),
icon: Icons.Close,
- title: 'Close',
+ title: 'Close all visualised args tracks for this arg',
compact: true,
});
}
diff --git a/ui/src/frontend/visualized_args_tracks.ts b/ui/src/frontend/visualized_args_tracks.ts
index 32826fc..985d090 100644
--- a/ui/src/frontend/visualized_args_tracks.ts
+++ b/ui/src/frontend/visualized_args_tracks.ts
@@ -13,16 +13,15 @@
// limitations under the License.
import {uuidv4} from '../base/uuid';
-// import {THREAD_SLICE_TRACK_KIND} from '../public';
import {NUM} from '../trace_processor/query_result';
-import {globals} from './globals';
-import {VisualisedArgsTrack} from './visualized_args_track';
+import {VisualizedArgsTrack} from './visualized_args_track';
import {TrackNode} from '../public/workspace';
import {Trace} from '../public/trace';
+import {SLICE_TRACK_KIND} from '../public/track_kinds';
-const VISUALISED_ARGS_SLICE_TRACK_URI_PREFIX = 'perfetto.VisualisedArgs';
+const VISUALIZED_ARGS_SLICE_TRACK_URI_PREFIX = 'perfetto.VisualizedArgs';
-export async function addVisualisedArgTracks(trace: Trace, argName: string) {
+export async function addVisualizedArgTracks(trace: Trace, argName: string) {
const escapedArgName = argName.replace(/[^a-zA-Z]/g, '_');
const tableName = `__arg_visualisation_helper_${escapedArgName}_slice`;
@@ -59,33 +58,38 @@
group by track_id;
`);
+ const addedTracks: TrackNode[] = [];
const it = result.iter({trackId: NUM, maxDepth: NUM});
for (; it.valid(); it.next()) {
const trackId = it.trackId;
const maxDepth = it.maxDepth;
- const uri = `${VISUALISED_ARGS_SLICE_TRACK_URI_PREFIX}#${uuidv4()}`;
+ const uri = `${VISUALIZED_ARGS_SLICE_TRACK_URI_PREFIX}#${uuidv4()}`;
trace.tracks.registerTrack({
uri,
title: argName,
- chips: ['metric'],
- track: new VisualisedArgsTrack({
+ chips: ['arg'],
+ track: new VisualizedArgsTrack({
trace,
uri,
trackId,
maxDepth,
argName,
+ onClose: () => {
+ // Remove all added for this argument
+ addedTracks.forEach((t) => t.parent?.removeChild(t));
+ },
}),
});
// Find the thread slice track that corresponds with this trackID and insert
// this track before it.
- const threadSliceTrack = globals.workspace.flatTracks.find((trackNode) => {
+ const threadSliceTrack = trace.workspace.flatTracks.find((trackNode) => {
if (!trackNode.uri) return false;
- const trackDescriptor = globals.trackManager.getTrack(trackNode.uri);
+ const trackDescriptor = trace.tracks.getTrack(trackNode.uri);
return (
trackDescriptor &&
- trackDescriptor.tags?.kind === 'ThreadSliceTrack' &&
+ trackDescriptor.tags?.kind === SLICE_TRACK_KIND &&
trackDescriptor.tags?.trackIds?.includes(trackId)
);
});
@@ -94,6 +98,7 @@
if (parentGroup) {
const newTrack = new TrackNode({uri, title: argName});
parentGroup.addChildBefore(newTrack, threadSliceTrack);
+ addedTracks.push(newTrack);
}
}
}
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
new file mode 100644
index 0000000..124a52e
--- /dev/null
+++ b/ui/src/frontend/widgets/charts/chart.ts
@@ -0,0 +1,120 @@
+// 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 {Row} from '../../../trace_processor/query_result';
+import {Engine} from '../../../trace_processor/engine';
+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;
+ width: string | number;
+ mark:
+ | 'area'
+ | 'bar'
+ | 'circle'
+ | 'line'
+ | 'point'
+ | 'rect'
+ | 'rule'
+ | 'square'
+ | 'text'
+ | 'tick'
+ | 'geoshape'
+ | 'boxplot'
+ | 'errorband'
+ | 'errorbar';
+ data: {values?: string | Row[]};
+
+ encoding: {
+ x: {[key: string]: unknown};
+ y: {[key: string]: unknown};
+ };
+}
+
+// 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;
+}
+
+export interface ChartState {
+ readonly engine: Engine;
+ readonly query: string;
+ readonly columns: TableColumn[] | TableColumnSet[] | string[];
+ data?: ChartData;
+ spec?: VegaLiteChartSpec;
+ loadData(): Promise<void>;
+ isLoading(): boolean;
+}
+
+export function toTitleCase(s: string): string {
+ const words = s.split(/\s/);
+
+ for (let i = 0; i < words.length; ++i) {
+ words[i] = words[i][0].toUpperCase() + words[i].substring(1);
+ }
+
+ 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
new file mode 100644
index 0000000..38f4a65
--- /dev/null
+++ b/ui/src/frontend/widgets/charts/histogram/histogram.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 {stringifyJsonWithBigints} from '../../../../base/json_utils';
+import {VegaView} from '../../../../widgets/vega_view';
+import {HistogramState} from './state';
+import {Spinner} from '../../../../widgets/spinner';
+import {ChartConfig} from '../chart';
+
+export class Histogram implements m.ClassComponent<ChartConfig> {
+ private readonly state: HistogramState;
+
+ constructor({attrs}: m.Vnode<ChartConfig>) {
+ this.state = new HistogramState(
+ attrs.engine,
+ attrs.query,
+ attrs.sqlColumn,
+ attrs.aggregationType,
+ );
+ }
+
+ view() {
+ if (this.state.isLoading()) {
+ return m(Spinner);
+ }
+
+ return m(
+ 'figure',
+ {
+ className: 'pf-histogram-view',
+ },
+ m(VegaView, {
+ spec: stringifyJsonWithBigints(this.state.spec),
+ data: {},
+ }),
+ );
+ }
+
+ isLoading(): boolean {
+ return this.state.isLoading();
+ }
+}
diff --git a/ui/src/frontend/widgets/charts/histogram/state.ts b/ui/src/frontend/widgets/charts/histogram/state.ts
new file mode 100644
index 0000000..db6d67f
--- /dev/null
+++ b/ui/src/frontend/widgets/charts/histogram/state.ts
@@ -0,0 +1,122 @@
+// 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 {stringifyJsonWithBigints} from '../../../../base/json_utils';
+import {raf} from '../../../../core/raf_scheduler';
+import {Engine} from '../../../../trace_processor/engine';
+import {Row} from '../../../../trace_processor/query_result';
+import {ChartData, ChartState, VegaLiteChartSpec} from '../chart';
+
+export interface HistogramChartConfig extends VegaLiteChartSpec {
+ binAxisType: 'nominal' | 'quantitative';
+ binAxis: 'x' | 'y';
+ countAxis: 'x' | 'y';
+ sort: string;
+ isBinned: boolean;
+ labelLimit?: number;
+}
+
+export class HistogramState implements ChartState {
+ data?: ChartData;
+ spec?: VegaLiteChartSpec;
+
+ constructor(
+ readonly engine: Engine,
+ readonly query: string,
+ readonly columns: string[],
+ private aggregationType?: 'nominal' | 'quantitative',
+ ) {
+ this.loadData();
+ }
+
+ createHistogramVegaSpec(): VegaLiteChartSpec {
+ const binAxisEncoding = {
+ bin: this.aggregationType !== 'nominal',
+ field: this.columns[0],
+ type: this.aggregationType,
+ title: this.columns[0],
+ sort: this.aggregationType === 'nominal' && {
+ op: 'count',
+ order: 'descending',
+ },
+ axis: {
+ labelLimit: 500,
+ },
+ };
+
+ const countAxisEncoding = {
+ aggregate: 'count',
+ title: 'Count',
+ };
+
+ const spec: VegaLiteChartSpec = {
+ $schema: 'https://vega.github.io/schema/vega-lite/v5.json',
+ width: 'container',
+ mark: 'bar',
+ data: {
+ values: this.data?.rows,
+ },
+ encoding: {
+ x:
+ this.aggregationType !== 'nominal'
+ ? binAxisEncoding
+ : countAxisEncoding,
+ y:
+ this.aggregationType !== 'nominal'
+ ? countAxisEncoding
+ : binAxisEncoding,
+ },
+ };
+
+ return spec;
+ }
+
+ async loadData() {
+ const res = await this.engine.query(`
+ SELECT ${this.columns[0]}
+ FROM (
+ ${this.query}
+ )
+ `);
+
+ const rows: Row[] = [];
+
+ let hasQuantitativeData = false;
+
+ for (const it = res.iter({}); it.valid(); it.next()) {
+ const rowVal = it.get(this.columns[0]);
+ if (typeof rowVal === 'bigint') {
+ hasQuantitativeData = true;
+ }
+
+ rows.push({
+ [this.columns[0]]: rowVal,
+ });
+ }
+
+ if (this.aggregationType === undefined) {
+ this.aggregationType = hasQuantitativeData ? 'quantitative' : 'nominal';
+ }
+
+ this.data = {
+ rows,
+ };
+
+ this.spec = this.createHistogramVegaSpec();
+ raf.scheduleFullRedraw();
+ }
+
+ isLoading(): boolean {
+ return this.data === undefined;
+ }
+}
diff --git a/ui/src/frontend/widgets/sched.ts b/ui/src/frontend/widgets/sched.ts
index 91ce75d..665ca9c 100644
--- a/ui/src/frontend/widgets/sched.ts
+++ b/ui/src/frontend/widgets/sched.ts
@@ -14,20 +14,16 @@
import m from 'mithril';
import {SchedSqlId} from '../../trace_processor/sql_utils/core_types';
-import {duration, time} from '../../base/time';
import {Anchor} from '../../widgets/anchor';
import {Icons} from '../../base/semantic_icons';
-import {globals} from '../globals';
-import {CPU_SLICE_TRACK_KIND} from '../../public/track_kinds';
-import {scrollTo} from '../../public/scroll_helper';
+import {AppImpl} from '../../core/app_impl';
interface SchedRefAttrs {
- id: SchedSqlId;
- ts: time;
- dur: duration;
- cpu: number;
+ // The id of the referenced sched slice in the sched_slice table.
+ readonly id: SchedSqlId;
+
// If not present, a placeholder name will be used.
- name?: string;
+ readonly name?: string;
// Whether clicking on the reference should change the current tab
// to "current selection" tab in addition to updating the selection
@@ -35,21 +31,10 @@
readonly switchToCurrentSelectionTab?: boolean;
}
-export function findSchedTrack(cpu: number): string | undefined {
- return globals.trackManager.findTrack((t) => {
- return t.tags?.kind === CPU_SLICE_TRACK_KIND && t.tags.cpu === cpu;
- })?.uri;
-}
-
-export function goToSchedSlice(cpu: number, id: SchedSqlId, ts: time) {
- const trackUri = findSchedTrack(cpu);
- if (trackUri === undefined) {
- return;
- }
- globals.selectionManager.selectSqlEvent('sched_slice', id);
- scrollTo({
- track: {uri: trackUri, expandGroup: true},
- time: {start: ts},
+export function goToSchedSlice(id: SchedSqlId) {
+ // TODO(primiano): the Trace object should be properly injected here.
+ AppImpl.instance.trace?.selection.selectSqlEvent('sched_slice', id, {
+ scrollToSelection: true,
});
}
@@ -60,21 +45,16 @@
{
icon: Icons.UpdateSelection,
onclick: () => {
- const trackUri = findSchedTrack(vnode.attrs.cpu);
- if (trackUri === undefined) return;
-
- globals.selectionManager.selectSqlEvent(
+ // TODO(primiano): the Trace object should be properly injected here.
+ AppImpl.instance.trace?.selection.selectSqlEvent(
'sched_slice',
vnode.attrs.id,
{
switchToCurrentSelectionTab:
vnode.attrs.switchToCurrentSelectionTab ?? true,
+ scrollToSelection: true,
},
);
- scrollTo({
- track: {uri: trackUri, expandGroup: true},
- time: {start: vnode.attrs.ts},
- });
},
},
vnode.attrs.name ?? `Sched ${vnode.attrs.id}`,
diff --git a/ui/src/frontend/widgets/slice.ts b/ui/src/frontend/widgets/slice.ts
index 3dc98e3..48ad80a 100644
--- a/ui/src/frontend/widgets/slice.ts
+++ b/ui/src/frontend/widgets/slice.ts
@@ -13,28 +13,22 @@
// limitations under the License.
import m from 'mithril';
-import {Time, duration, time} from '../../base/time';
import {
asSliceSqlId,
SliceSqlId,
} from '../../trace_processor/sql_utils/core_types';
import {Anchor} from '../../widgets/anchor';
import {Icons} from '../../base/semantic_icons';
-import {globals} from '../globals';
-import {BigintMath} from '../../base/bigint_math';
import {getSlice, SliceDetails} from '../../trace_processor/sql_utils/slice';
import {
createSqlIdRefRenderer,
sqlIdRegistry,
} from './sql/details/sql_ref_renderer_registry';
-import {scrollTo} from '../../public/scroll_helper';
+import {AppImpl} from '../../core/app_impl';
interface SliceRefAttrs {
readonly id: SliceSqlId;
readonly name: string;
- readonly ts: time;
- readonly dur: duration;
- readonly sqlTrackId: number;
// Whether clicking on the reference should change the current tab
// to "current selection" tab in addition to updating the selection
@@ -49,28 +43,16 @@
{
icon: Icons.UpdateSelection,
onclick: () => {
- const track = globals.trackManager.findTrack((td) => {
- return td.tags?.trackIds?.includes(vnode.attrs.sqlTrackId);
- });
- if (track === undefined) return;
- scrollTo({
- track: {
- uri: track.uri,
- expandGroup: true,
+ // TODO(primiano): the Trace object should be properly injected here.
+ AppImpl.instance.trace?.selection.selectSqlEvent(
+ 'slice',
+ vnode.attrs.id,
+ {
+ switchToCurrentSelectionTab:
+ vnode.attrs.switchToCurrentSelectionTab,
+ scrollToSelection: true,
},
- });
- // Clamp duration to 1 - i.e. for instant events
- const dur = BigintMath.max(1n, vnode.attrs.dur);
- scrollTo({
- time: {
- start: vnode.attrs.ts,
- end: Time.fromRaw(vnode.attrs.ts + dur),
- },
- });
- globals.selectionManager.selectSqlEvent('slice', vnode.attrs.id, {
- switchToCurrentSelectionTab:
- vnode.attrs.switchToCurrentSelectionTab,
- });
+ );
},
},
vnode.attrs.name,
@@ -82,9 +64,6 @@
return m(SliceRef, {
id: slice.id,
name: name ?? slice.name,
- ts: slice.ts,
- dur: slice.dur,
- sqlTrackId: slice.trackId,
});
}
diff --git a/ui/src/frontend/widgets/sql/table/state.ts b/ui/src/frontend/widgets/sql/table/state.ts
index 1f513b8..4540214 100644
--- a/ui/src/frontend/widgets/sql/table/state.ts
+++ b/ui/src/frontend/widgets/sql/table/state.ts
@@ -331,8 +331,15 @@
this.rowCount = undefined;
}
- // Run a delayed UI update to avoid flickering if the query returns quickly.
- raf.scheduleDelayedFullRedraw();
+ // Schedule a full redraw to happen after a short delay (50 ms).
+ // This is done to prevent flickering / visual noise and allow the UI to fetch
+ // the initial data from the Trace Processor.
+ // There is a chance that someone else schedules a full redraw in the
+ // meantime, forcing the flicker, but in practice it works quite well and
+ // avoids a lot of complexity for the callers.
+ // 50ms is half of the responsiveness threshold (100ms):
+ // https://web.dev/rail/#response-process-events-in-under-50ms
+ setTimeout(() => raf.scheduleFullRedraw(), 50);
if (!filtersMatch) {
this.rowCount = await this.loadRowCount();
diff --git a/ui/src/frontend/widgets/sql/table/state_unittest.ts b/ui/src/frontend/widgets/sql/table/state_unittest.ts
index c4985b0..e6161d9 100644
--- a/ui/src/frontend/widgets/sql/table/state_unittest.ts
+++ b/ui/src/frontend/widgets/sql/table/state_unittest.ts
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {createFakeTraceImpl} from '../../../../common/fake_trace_impl';
+import {createFakeTraceImpl} from '../../../../core/fake_trace_impl';
import {tableColumnId} from './column';
import {SqlTableState} from './state';
import {SqlTableDescription} from './table_description';
diff --git a/ui/src/frontend/widgets/sql/table/table.ts b/ui/src/frontend/widgets/sql/table/table.ts
index 7aeafd0..761a32c 100644
--- a/ui/src/frontend/widgets/sql/table/table.ts
+++ b/ui/src/frontend/widgets/sql/table/table.ts
@@ -22,12 +22,7 @@
TableManager,
} from './column';
import {Button} from '../../../../widgets/button';
-import {
- MenuDivider,
- MenuItem,
- MenuItemAttrs,
- PopupMenu2,
-} from '../../../../widgets/menu';
+import {MenuDivider, MenuItem, PopupMenu2} from '../../../../widgets/menu';
import {buildSqlQuery} from './query_builder';
import {Icons} from '../../../../base/semantic_icons';
import {sqliteString} from '../../../../base/string_utils';
@@ -45,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,
@@ -70,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;
@@ -110,6 +111,91 @@
}
}
+interface ColumnFilterAttrs {
+ filterOption: FilterOption;
+ columns: SqlColumn[];
+ state: SqlTableState;
+}
+
+// Separating out an individual column filter into a class
+// so that we can store the raw input value.
+class ColumnFilter implements m.ClassComponent<ColumnFilterAttrs> {
+ // Holds the raw string value from the filter text input element
+ private inputValue: string;
+
+ constructor() {
+ this.inputValue = '';
+ }
+
+ view({attrs}: m.Vnode<ColumnFilterAttrs>) {
+ const {filterOption, columns, state} = attrs;
+
+ const {op, requiresParam} = FILTER_OPTION_TO_OP[filterOption];
+
+ return m(
+ MenuItem,
+ {
+ label: filterOption,
+ // Filter options that do not need an input value will filter the
+ // table directly when clicking on the menu item
+ // (ex: IS NULL or IS NOT NULL)
+ onclick: !requiresParam
+ ? () => {
+ state.addFilter({
+ op: (cols) => `${cols[0]} ${op}`,
+ columns,
+ });
+ }
+ : undefined,
+ },
+ // All non-null filter options will have a submenu that allows
+ // the user to enter a value into textfield and filter using
+ // the Filter button.
+ requiresParam &&
+ m(
+ Form,
+ {
+ onSubmit: () => {
+ // Convert the string extracted from
+ // the input text field into the correct data type for
+ // filtering. The order in which each data type is
+ // checked matters: string, number (floating), and bigint.
+ if (this.inputValue === '') return;
+
+ let filterValue: ColumnType;
+
+ if (Number.isNaN(Number.parseFloat(this.inputValue))) {
+ filterValue = sqliteString(this.inputValue);
+ } else if (
+ !Number.isInteger(Number.parseFloat(this.inputValue))
+ ) {
+ filterValue = Number(this.inputValue);
+ } else {
+ filterValue = BigInt(this.inputValue);
+ }
+
+ state.addFilter({
+ op: (cols) => `${cols[0]} ${op} ${filterValue}`,
+ columns,
+ });
+ },
+ submitLabel: 'Filter',
+ },
+ m(TextInput, {
+ id: 'column_filter_value',
+ ref: 'COLUMN_FILTER_VALUE',
+ autofocus: true,
+ oninput: (e: KeyboardEvent) => {
+ if (!e.target) return;
+
+ this.inputValue = (e.target as HTMLInputElement).value;
+ },
+ }),
+ ),
+ );
+ }
+}
+
export class SqlTable implements m.ClassComponent<SqlTableConfig> {
private readonly table: SqlTableDescription;
@@ -184,76 +270,21 @@
renderColumnFilterOptions(
c: TableColumn,
- value: string,
- state: SqlTableState,
- ): m.Vnode<MenuItemAttrs, unknown>[] {
- const filterOptions = Object.values(FilterOption);
- const columns = [c.primaryColumn()];
-
- const filterOptionMenuItems = filterOptions.map((filterOption) => {
- const {op, requiresParam} = FILTER_OPTION_TO_OP[filterOption];
-
- return m(
- MenuItem,
- {
- label: filterOption,
- // Filter options that do not need an input value will filter the
- // table directly when clicking on the menu item
- // (ex: IS NULL or IS NOT NULL)
- onclick: !requiresParam
- ? () => {
- state.addFilter({
- op: (cols) => `${cols[0]} ${op}`,
- columns,
- });
- }
- : undefined,
- },
- // All non-null filter options will have a submenu that allows
- // the user to enter a value into textfield and filter using
- // the Filter button.
- requiresParam &&
- m(
- Form,
- {
- onSubmit: () => {
- // Convert the string extracted from
- // the input text field into the correct data type for
- // filtering. The order in which each data type is
- // checked matters: string, number (floating), and bigint.
- let filterValue: ColumnType;
- if (Number.isNaN(Number.parseFloat(value))) {
- filterValue = sqliteString(value);
- } else if (!Number.isInteger(Number.parseFloat(value))) {
- filterValue = Number(value);
- } else {
- filterValue = BigInt(value);
- }
-
- state.addFilter({
- op: (cols) => `${cols[0]} ${op} ${filterValue}`,
- columns,
- });
- },
- submitLabel: 'Filter',
- },
- m(TextInput, {
- id: 'column_filter_value',
- ref: 'COLUMN_FILTER_VALUE',
- autofocus: true,
- oninput: (e: KeyboardEvent) => {
- if (!e.target) return;
- value = (e.target as HTMLInputElement).value;
- },
- }),
- ),
- );
- });
-
- return filterOptionMenuItems;
+ ): m.Vnode<ColumnFilterAttrs, unknown>[] {
+ return Object.values(FilterOption).map((filterOption) =>
+ m(ColumnFilter, {
+ filterOption,
+ columns: [c.primaryColumn()],
+ state: this.state,
+ }),
+ );
}
- renderColumnHeader(column: TableColumn, index: number) {
+ renderColumnHeader(
+ column: TableColumn,
+ index: number,
+ additionalColumnHeaderMenuItems?: m.Children,
+ ) {
const sorted = this.state.isSortedBy(column);
const icon =
sorted === 'ASC'
@@ -262,8 +293,6 @@
? Icons.SortedDesc
: Icons.ContextMenu;
- const filterValue = '';
-
return m(
PopupMenu2,
{
@@ -306,31 +335,9 @@
m(
MenuItem,
{label: 'Add filter', icon: Icons.Filter},
- this.renderColumnFilterOptions(column, filterValue, this.state),
+ this.renderColumnFilterOptions(column),
),
- m(MenuItem, {
- label: 'Create histogram',
- icon: Icons.Chart,
- onclick: () => {
- const columnAlias =
- this.state.getCurrentRequest().columns[
- sqlColumnId(column.primaryColumn())
- ];
- addHistogramTab(
- {
- 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,
- },
- this.state.trace,
- );
- },
- }),
+ additionalColumnHeaderMenuItems,
// Menu items before divider apply to selected column
m(MenuDivider),
// Menu items after divider apply to entire table
@@ -338,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/sql/table/well_known_columns.ts b/ui/src/frontend/widgets/sql/table/well_known_columns.ts
index a4e52bc..7c1eb98 100644
--- a/ui/src/frontend/widgets/sql/table/well_known_columns.ts
+++ b/ui/src/frontend/widgets/sql/table/well_known_columns.ts
@@ -241,9 +241,6 @@
return m(SliceRef, {
id: asSliceSqlId(Number(id)),
name: `${id}`,
- ts: Time.fromRaw(ts),
- dur: dur,
- sqlTrackId: Number(trackId),
switchToCurrentSelectionTab: false,
});
}
@@ -385,9 +382,6 @@
return m(SchedRef, {
id: asSchedSqlId(Number(id)),
- ts: Time.fromRaw(ts),
- dur: dur,
- cpu: Number(cpu),
name: `${id}`,
switchToCurrentSelectionTab: false,
});
@@ -471,9 +465,6 @@
return m(ThreadStateRef, {
id: asThreadStateSqlId(Number(id)),
- ts: Time.fromRaw(ts),
- dur: dur,
- utid: asUtid(Number(utid)),
name: `${id}`,
switchToCurrentSelectionTab: false,
});
diff --git a/ui/src/frontend/widgets/thread_state.ts b/ui/src/frontend/widgets/thread_state.ts
index 284369c..b7cfd69 100644
--- a/ui/src/frontend/widgets/thread_state.ts
+++ b/ui/src/frontend/widgets/thread_state.ts
@@ -13,23 +13,14 @@
// limitations under the License.
import m from 'mithril';
-import {
- ThreadStateSqlId,
- Utid,
-} from '../../trace_processor/sql_utils/core_types';
-import {duration, time} from '../../base/time';
+import {ThreadStateSqlId} from '../../trace_processor/sql_utils/core_types';
import {Anchor} from '../../widgets/anchor';
import {Icons} from '../../base/semantic_icons';
-import {globals} from '../globals';
-import {THREAD_STATE_TRACK_KIND} from '../../public/track_kinds';
import {ThreadState} from '../../trace_processor/sql_utils/thread_state';
-import {scrollTo} from '../../public/scroll_helper';
+import {AppImpl} from '../../core/app_impl';
interface ThreadStateRefAttrs {
id: ThreadStateSqlId;
- ts: time;
- dur: duration;
- utid: Utid;
// If not present, a placeholder name will be used.
name?: string;
@@ -46,28 +37,16 @@
{
icon: Icons.UpdateSelection,
onclick: () => {
- const trackDescriptor = globals.trackManager
- .getAllTracks()
- .find(
- (td) =>
- td.tags?.kind === THREAD_STATE_TRACK_KIND &&
- td.tags?.utid === vnode.attrs.utid,
- );
-
- if (trackDescriptor === undefined) return;
-
- globals.selectionManager.selectSqlEvent(
+ // TODO(primiano): the Trace object should be properly injected here.
+ AppImpl.instance.trace?.selection.selectSqlEvent(
'thread_state',
vnode.attrs.id,
{
switchToCurrentSelectionTab:
vnode.attrs.switchToCurrentSelectionTab,
+ scrollToSelection: true,
},
);
- scrollTo({
- track: {uri: trackDescriptor.uri, expandGroup: true},
- time: {start: vnode.attrs.ts},
- });
},
},
vnode.attrs.name ?? `Thread State ${vnode.attrs.id}`,
@@ -79,9 +58,6 @@
if (state.thread === undefined) return null;
return m(ThreadStateRef, {
- id: state.threadStateSqlId,
- ts: state.ts,
- dur: state.dur,
- utid: state.thread?.utid,
+ id: state.id,
});
}
diff --git a/ui/src/frontend/widgets/timestamp.ts b/ui/src/frontend/widgets/timestamp.ts
index d27b95b..7fd44f1 100644
--- a/ui/src/frontend/widgets/timestamp.ts
+++ b/ui/src/frontend/widgets/timestamp.ts
@@ -24,7 +24,9 @@
import {raf} from '../../core/raf_scheduler';
import {Anchor} from '../../widgets/anchor';
import {MenuDivider, MenuItem, PopupMenu2} from '../../widgets/menu';
-import {globals} from '../globals';
+import {Trace} from '../../public/trace';
+import {AppImpl} from '../../core/app_impl';
+import {assertExists} from '../../base/logging';
// import {MenuItem, PopupMenu2} from './menu';
@@ -39,22 +41,29 @@
}
export class Timestamp implements m.ClassComponent<TimestampAttrs> {
+ private readonly trace: Trace;
+
+ constructor() {
+ // TODO(primiano): the Trace object should be injected into the attrs, but
+ // there are too many users of this class and doing so requires a larger
+ // refactoring CL. Either that or we should find a different way to plumb
+ // the hoverCursorTimestamp.
+ this.trace = assertExists(AppImpl.instance.trace);
+ }
+
view({attrs}: m.Vnode<TimestampAttrs>) {
const {ts} = attrs;
+ const timeline = this.trace.timeline;
return m(
PopupMenu2,
{
trigger: m(
Anchor,
{
- onmouseover: () => {
- globals.trace.timeline.hoverCursorTimestamp = ts;
- },
- onmouseout: () => {
- globals.trace.timeline.hoverCursorTimestamp = undefined;
- },
+ onmouseover: () => (timeline.hoverCursorTimestamp = ts),
+ onmouseout: () => (timeline.hoverCursorTimestamp = undefined),
},
- attrs.display ?? renderTimestamp(ts),
+ attrs.display ?? renderTimestamp(timeline.toDomainTime(ts)),
),
},
m(MenuItem, {
@@ -100,9 +109,8 @@
});
}
-function renderTimestamp(time: time): m.Children {
+function renderTimestamp(domainTime: time): m.Children {
const fmt = timestampFormat();
- const domainTime = globals.trace.timeline.toDomainTime(time);
switch (fmt) {
case TimestampFormat.UTC:
case TimestampFormat.TraceTz:
diff --git a/ui/src/plugins/com.android.GpuWorkPeriod/OWNERS b/ui/src/plugins/com.android.GpuWorkPeriod/OWNERS
new file mode 100644
index 0000000..0ac4632
--- /dev/null
+++ b/ui/src/plugins/com.android.GpuWorkPeriod/OWNERS
@@ -0,0 +1,2 @@
+varadgautam@google.com
+vitalyvv@google.com
diff --git a/ui/src/plugins/com.android.GpuWorkPeriod/index.ts b/ui/src/plugins/com.android.GpuWorkPeriod/index.ts
new file mode 100644
index 0000000..1bae632
--- /dev/null
+++ b/ui/src/plugins/com.android.GpuWorkPeriod/index.ts
@@ -0,0 +1,91 @@
+// 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 {NUM, STR} from '../../trace_processor/query_result';
+import {PerfettoPlugin} from '../../public/plugin';
+import {Trace} from '../../public/trace';
+import {TrackNode} from '../../public/workspace';
+import {SLICE_TRACK_KIND} from '../../public/track_kinds';
+import {createQuerySliceTrack} from '../../public/lib/tracks/query_slice_track';
+
+export default class implements PerfettoPlugin {
+ static readonly id = 'com.android.GpuWorkPeriod';
+
+ async onTraceLoad(ctx: Trace): Promise<void> {
+ const {engine} = ctx;
+ const result = await engine.query(`
+ include perfetto module android.gpu.work_period;
+
+ with grouped_packages as materialized (
+ select
+ uid,
+ group_concat(package_name, ',') as package_name,
+ count() as cnt
+ from package_list
+ group by uid
+ )
+ select
+ t.id trackId,
+ t.uid as uid,
+ t.gpu_id as gpuId,
+ iif(g.cnt = 1, g.package_name, 'UID ' || t.uid) as packageName
+ from android_gpu_work_period_track t
+ left join grouped_packages g using (uid)
+ order by uid
+ `);
+
+ const it = result.iter({
+ trackId: NUM,
+ uid: NUM,
+ gpuId: NUM,
+ packageName: STR,
+ });
+
+ const workPeriodByGpu = new Map<number, TrackNode>();
+ for (; it.valid(); it.next()) {
+ const {trackId, gpuId, uid, packageName} = it;
+ const uri = `/gpu_work_period_${gpuId}_${uid}`;
+ const track = await createQuerySliceTrack({
+ trace: ctx,
+ uri,
+ data: {
+ sqlSource: `
+ select ts, dur, name
+ from slice
+ where track_id = ${trackId}
+ `,
+ },
+ });
+ ctx.tracks.registerTrack({
+ uri,
+ title: packageName,
+ tags: {
+ trackIds: [trackId],
+ kind: SLICE_TRACK_KIND,
+ },
+ track,
+ });
+ let workPeriod = workPeriodByGpu.get(gpuId);
+ if (workPeriod === undefined) {
+ workPeriod = new TrackNode({
+ title: `GPU Work Period (GPU ${gpuId})`,
+ isSummary: true,
+ });
+ workPeriodByGpu.set(gpuId, workPeriod);
+ ctx.workspace.addChildInOrder(workPeriod);
+ }
+ workPeriod.addChildInOrder(new TrackNode({title: packageName, uri}));
+ }
+ }
+}
diff --git a/ui/src/plugins/com.android.InputEvents/OWNERS b/ui/src/plugins/com.android.InputEvents/OWNERS
new file mode 100644
index 0000000..0e76d10
--- /dev/null
+++ b/ui/src/plugins/com.android.InputEvents/OWNERS
@@ -0,0 +1,2 @@
+kholm@google.com
+sadrul@google.com
diff --git a/ui/src/plugins/com.android.InputEvents/index.ts b/ui/src/plugins/com.android.InputEvents/index.ts
new file mode 100644
index 0000000..b8e7184
--- /dev/null
+++ b/ui/src/plugins/com.android.InputEvents/index.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.
+
+import {LONG} from '../../trace_processor/query_result';
+import {PerfettoPlugin} from '../../public/plugin';
+import {Trace} from '../../public/trace';
+import {createQuerySliceTrack} from '../../public/lib/tracks/query_slice_track';
+import {TrackNode} from '../../public/workspace';
+import {getOrCreateUserInteractionGroup} from '../../public/standard_groups';
+
+export default class implements PerfettoPlugin {
+ static readonly id = 'com.android.InputEvents';
+
+ async onTraceLoad(ctx: Trace): Promise<void> {
+ const cnt = await ctx.engine.query(`
+ SELECT
+ count(*) as cnt
+ FROM slice
+ WHERE name GLOB 'UnwantedInteractionBlocker::notifyMotion*'
+ `);
+ if (cnt.firstRow({cnt: LONG}).cnt == 0n) {
+ return;
+ }
+
+ const SQL_SOURCE = `
+ SELECT
+ read_time as ts,
+ end_to_end_latency_dur as dur,
+ CONCAT(event_type, ' ', event_action, ': ', process_name, ' (', input_event_id, ')') as name
+ FROM android_input_events
+ WHERE end_to_end_latency_dur IS NOT NULL
+ `;
+
+ await ctx.engine.query('INCLUDE PERFETTO MODULE android.input;');
+ const uri = 'com.android.InputEvents#InputEventsTrack';
+ const title = 'Input Events';
+ const track = await createQuerySliceTrack({
+ trace: ctx,
+ uri,
+ data: {
+ sqlSource: SQL_SOURCE,
+ },
+ });
+ ctx.tracks.registerTrack({
+ uri,
+ title: title,
+ track,
+ });
+ const node = new TrackNode({uri, title});
+ const group = getOrCreateUserInteractionGroup(ctx.workspace);
+ group.addChildInOrder(node);
+ }
+}
diff --git a/ui/src/plugins/com.example.ExampleNestedTracks/index.ts b/ui/src/plugins/com.example.ExampleNestedTracks/index.ts
new file mode 100644
index 0000000..be99948
--- /dev/null
+++ b/ui/src/plugins/com.example.ExampleNestedTracks/index.ts
@@ -0,0 +1,76 @@
+// 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 {Trace} from '../../public/trace';
+import {PerfettoPlugin} from '../../public/plugin';
+import {createQuerySliceTrack} from '../../public/lib/tracks/query_slice_track';
+import {TrackNode} from '../../public/workspace';
+
+export default class implements PerfettoPlugin {
+ static readonly id = 'com.example.ExampleNestedTracks';
+ async onTraceLoad(ctx: Trace): Promise<void> {
+ const traceStartTime = ctx.traceInfo.start;
+ const traceDur = ctx.traceInfo.end - ctx.traceInfo.start;
+ await ctx.engine.query(`
+ create table example_events (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ name TEXT,
+ ts INTEGER,
+ dur INTEGER,
+ arg INTEGER
+ );
+
+ insert into example_events (name, ts, dur, arg)
+ values
+ ('Foo', ${traceStartTime}, ${traceDur}, 'aaa'),
+ ('Bar', ${traceStartTime}, ${traceDur / 2n}, 'bbb'),
+ ('Baz', ${traceStartTime}, ${traceDur / 3n}, 'bbb');
+ `);
+
+ const title = 'Test Track';
+ const uri = `com.example.ExampleNestedTracks#TestTrack`;
+ const track = await createQuerySliceTrack({
+ trace: ctx,
+ uri,
+ data: {
+ sqlSource: 'select * from example_events',
+ },
+ });
+ ctx.tracks.registerTrack({
+ uri,
+ title,
+ track,
+ });
+
+ this.addNestedTracks(ctx, uri);
+ }
+
+ private addNestedTracks(ctx: Trace, uri: string): void {
+ const trackRoot = new TrackNode({uri, title: 'Root'});
+ const track1 = new TrackNode({uri, title: '1'});
+ const track2 = new TrackNode({uri, title: '2'});
+ const track11 = new TrackNode({uri, title: '1.1'});
+ const track12 = new TrackNode({uri, title: '1.2'});
+ const track121 = new TrackNode({uri, title: '1.2.1'});
+ const track21 = new TrackNode({uri, title: '2.1'});
+
+ ctx.workspace.addChildInOrder(trackRoot);
+ trackRoot.addChildLast(track1);
+ trackRoot.addChildLast(track2);
+ track1.addChildLast(track11);
+ track1.addChildLast(track12);
+ track12.addChildLast(track121);
+ track2.addChildLast(track21);
+ }
+}
diff --git a/ui/src/plugins/dev.perfetto.ExampleSimpleCommand/index.ts b/ui/src/plugins/com.example.ExampleSimpleCommand/index.ts
similarity index 71%
rename from ui/src/plugins/dev.perfetto.ExampleSimpleCommand/index.ts
rename to ui/src/plugins/com.example.ExampleSimpleCommand/index.ts
index ad3a399..cf473b3 100644
--- a/ui/src/plugins/dev.perfetto.ExampleSimpleCommand/index.ts
+++ b/ui/src/plugins/com.example.ExampleSimpleCommand/index.ts
@@ -13,20 +13,16 @@
// limitations under the License.
import {App} from '../../public/app';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
+import {PerfettoPlugin} from '../../public/plugin';
// This is just an example plugin, used to prove that the plugin system works.
-class ExampleSimpleCommand implements PerfettoPlugin {
- onActivate(ctx: App): void {
+export default class implements PerfettoPlugin {
+ static readonly id = 'com.example.ExampleSimpleCommand';
+ static onActivate(ctx: App): void {
ctx.commands.registerCommand({
- id: 'dev.perfetto.ExampleSimpleCommand#LogHelloWorld',
+ id: 'com.example.ExampleSimpleCommand#LogHelloWorld',
name: 'Log "Hello, world!"',
callback: () => console.log('Hello, world!'),
});
}
}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'dev.perfetto.ExampleSimpleCommand',
- plugin: ExampleSimpleCommand,
-};
diff --git a/ui/src/plugins/dev.perfetto.ExampleState/index.ts b/ui/src/plugins/com.example.ExampleState/index.ts
similarity index 83%
rename from ui/src/plugins/dev.perfetto.ExampleState/index.ts
rename to ui/src/plugins/com.example.ExampleState/index.ts
index ee6da79..9e1c296 100644
--- a/ui/src/plugins/dev.perfetto.ExampleState/index.ts
+++ b/ui/src/plugins/com.example.ExampleState/index.ts
@@ -15,7 +15,7 @@
import {createStore, Store} from '../../base/store';
import {exists} from '../../base/utils';
import {Trace} from '../../public/trace';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
+import {PerfettoPlugin} from '../../public/plugin';
import {addQueryResultsTab} from '../../public/lib/query_table/query_result_tab';
interface State {
@@ -24,7 +24,8 @@
// This example plugin shows using state that is persisted in the
// permalink.
-class ExampleState implements PerfettoPlugin {
+export default class implements PerfettoPlugin {
+ static readonly id = 'com.example.ExampleState';
private store: Store<State> = createStore({counter: 0});
private migrate(initialState: unknown): State {
@@ -42,9 +43,10 @@
async onTraceLoad(ctx: Trace): Promise<void> {
this.store = ctx.mountStore((init: unknown) => this.migrate(init));
+ ctx.trash.use(this.store);
ctx.commands.registerCommand({
- id: 'dev.perfetto.ExampleState#ShowCounter',
+ id: 'com.example.ExampleState#ShowCounter',
name: 'Show ExampleState counter',
callback: () => {
const counter = this.store.state.counter;
@@ -58,13 +60,4 @@
},
});
}
-
- async onTraceUnload(_: Trace): Promise<void> {
- this.store[Symbol.dispose]();
- }
}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'dev.perfetto.ExampleState',
- plugin: ExampleState,
-};
diff --git a/ui/src/plugins/com.example.Skeleton/index.ts b/ui/src/plugins/com.example.Skeleton/index.ts
index 8a03f8a..5746950 100644
--- a/ui/src/plugins/com.example.Skeleton/index.ts
+++ b/ui/src/plugins/com.example.Skeleton/index.ts
@@ -15,16 +15,12 @@
import {Trace} from '../../public/trace';
import {App} from '../../public/app';
import {MetricVisualisation} from '../../public/plugin';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
-import {createStore, Store} from '../../base/store';
-
-interface State {
- foo: string;
-}
+import {PerfettoPlugin} from '../../public/plugin';
// SKELETON: Rename this class to match your plugin.
-class Skeleton implements PerfettoPlugin {
- private store: Store<State> = createStore({foo: 'foo'});
+export default class implements PerfettoPlugin {
+ // SKELETON: Update pluginId to match the directory of the plugin.
+ static readonly id = 'com.example.Skeleton';
/**
* This hook is called when the plugin is activated manually, or when the UI
@@ -35,8 +31,8 @@
* This hook should be used for adding commands that don't depend on the
* trace.
*/
- onActivate(_: App): void {
- //
+ static onActivate(app: App): void {
+ console.log('SkeletonPlugin::onActivate()', app.pluginId);
}
/**
@@ -47,68 +43,53 @@
* 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,
+ );
}
+
+ /**
+ * 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
+ * them.
+ *
+ * Examples of things that could be done here:
+ * - Pinning tracks
+ * - Focusing on a slice
+ * - Adding debug tracks
+ *
+ * Postmessage args might be useful here - e.g. if you would like to pin a
+ * specific track, pass the track details through the postmessage args
+ * interface and react to it here.
+ *
+ * Note: Any tracks registered in this hook will not be displayed in the
+ * timeline, unless they are manually added through the ctx.timeline API.
+ * However this part of the code is in flux at the moment and the semantics
+ * of how this works might change, though it's still good practice to use
+ * the onTraceLoad hook to add tracks as it means that all tracks are
+ * available by the time this hook gets called.
+ *
+ * TODO(stevegolton): Update this comment if the semantics of track adding
+ * changes.
+ */
+ trace.addEventListener('traceready', async () => {
+ console.log('SkeletonPlugin::traceready');
+ });
}
- /**
- * 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.
- *
- * 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
- * them.
- *
- * Examples of things that could be done here:
- * - Pinning tracks
- * - Focusing on a slice
- * - Adding debug tracks
- *
- * Postmessage args might be useful here - e.g. if you would like to pin a
- * specific track, pass the track details through the postmessage args
- * interface and react to it here.
- *
- * Note: Any tracks registered in this hook will not be displayed in the
- * timeline, unless they are manually added through the ctx.timeline API.
- * However this part of the code is in flux at the moment and the semantics of
- * how this works might change, though it's still good practice to use the
- * onTraceLoad hook to add tracks as it means that all tracks are available by
- * the time this hook gets called.
- *
- * TODO(stevegolton): Update this comment if the semantics of track adding
- * changes.
- */
- async onTraceReady(_ctx: Trace): Promise<void> {}
-
- /**
- * This hook is called as the trace is being unloaded, such as when switching
- * traces. It should be used to clean up any trace related resources.
- */
- async onTraceUnload(_: Trace): Promise<void> {
- this.store[Symbol.dispose]();
- }
-
- metricVisualisations(_: Trace): MetricVisualisation[] {
+ static metricVisualisations(): MetricVisualisation[] {
return [];
}
}
-
-export const plugin: PluginDescriptor = {
- // SKELETON: Update pluginId to match the directory of the plugin.
- pluginId: 'com.example.Skeleton',
- plugin: Skeleton,
-};
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/com.google.PixelMemory/index.ts b/ui/src/plugins/com.google.PixelMemory/index.ts
index 5cc57f7..30d1520 100644
--- a/ui/src/plugins/com.google.PixelMemory/index.ts
+++ b/ui/src/plugins/com.google.PixelMemory/index.ts
@@ -13,10 +13,12 @@
// limitations under the License.
import {Trace} from '../../public/trace';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
-import {addDebugCounterTrack} from '../../public/lib/debug_tracks/debug_tracks';
+import {PerfettoPlugin} from '../../public/plugin';
+import {addDebugCounterTrack} from '../../public/lib/tracks/debug_tracks';
-class PixelMemory implements PerfettoPlugin {
+export default class implements PerfettoPlugin {
+ static readonly id = 'com.google.PixelMemory';
+
async onTraceLoad(ctx: Trace): Promise<void> {
ctx.commands.registerCommand({
id: 'dev.perfetto.PixelMemory#ShowTotalMemory',
@@ -40,9 +42,9 @@
);
`;
await ctx.engine.query(RSS_ALL);
- await addDebugCounterTrack(
- ctx,
- {
+ await addDebugCounterTrack({
+ trace: ctx,
+ data: {
sqlSource: `
SELECT
ts,
@@ -52,15 +54,9 @@
`,
columns: ['ts', 'value'],
},
- pid + '_rss_anon_file_swap_shmem_gpu',
- {ts: 'ts', value: 'value'},
- );
+ title: pid + '_rss_anon_file_swap_shmem_gpu',
+ });
},
});
}
}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'com.google.PixelMemory',
- plugin: PixelMemory,
-};
diff --git a/ui/src/plugins/com.google.android.GoogleCamera/index.ts b/ui/src/plugins/com.google.android.GoogleCamera/index.ts
index bd25852..1debd41 100644
--- a/ui/src/plugins/com.google.android.GoogleCamera/index.ts
+++ b/ui/src/plugins/com.google.android.GoogleCamera/index.ts
@@ -13,20 +13,17 @@
// limitations under the License.
import {Trace} from '../../public/trace';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
+import {PerfettoPlugin} from '../../public/plugin';
import * as cameraConstants from './googleCameraConstants';
-class GoogleCamera implements PerfettoPlugin {
- private ctx!: Trace;
-
+export default class implements PerfettoPlugin {
+ static readonly id = 'com.google.android.GoogleCamera';
async onTraceLoad(ctx: Trace): Promise<void> {
- this.ctx = ctx;
-
ctx.commands.registerCommand({
id: 'com.google.android.GoogleCamera#LoadGoogleCameraStartupView',
name: 'Load google camera startup view',
callback: () => {
- this.loadGCAStartupView();
+ this.loadGCAStartupView(ctx);
},
});
@@ -43,18 +40,18 @@
) {
return item.trim();
});
- this.pinTracks(trackNameList);
+ this.pinTracks(ctx, trackNameList);
},
});
}
- private loadGCAStartupView() {
- this.pinTracks(cameraConstants.MAIN_THREAD_TRACK);
- this.pinTracks(cameraConstants.STARTUP_RELATED_TRACKS);
+ private loadGCAStartupView(ctx: Trace) {
+ this.pinTracks(ctx, cameraConstants.MAIN_THREAD_TRACK);
+ this.pinTracks(ctx, cameraConstants.STARTUP_RELATED_TRACKS);
}
- private pinTracks(trackNames: ReadonlyArray<string>) {
- this.ctx.workspace.flatTracks.forEach((track) => {
+ private pinTracks(ctx: Trace, trackNames: ReadonlyArray<string>) {
+ ctx.workspace.flatTracks.forEach((track) => {
trackNames.forEach((trackName) => {
if (track.title.match(trackName)) {
track.pin();
@@ -63,8 +60,3 @@
});
}
}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'com.google.android.GoogleCamera',
- plugin: GoogleCamera,
-};
diff --git a/ui/src/plugins/dev.perfetto.AndroidBinderViz/index.ts b/ui/src/plugins/dev.perfetto.AndroidBinderViz/index.ts
index fa14da5..1525057 100644
--- a/ui/src/plugins/dev.perfetto.AndroidBinderViz/index.ts
+++ b/ui/src/plugins/dev.perfetto.AndroidBinderViz/index.ts
@@ -13,7 +13,7 @@
// limitations under the License.
import {MetricVisualisation} from '../../public/plugin';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
+import {PerfettoPlugin} from '../../public/plugin';
const SPEC = `
{
@@ -32,8 +32,10 @@
}
`;
-class AndroidBinderVizPlugin implements PerfettoPlugin {
- metricVisualisations(): MetricVisualisation[] {
+export default class implements PerfettoPlugin {
+ static readonly id = 'dev.perfetto.AndroidBinderVizPlugin';
+
+ static metricVisualisations(): MetricVisualisation[] {
return [
{
metric: 'android_binder',
@@ -43,8 +45,3 @@
];
}
}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'dev.perfetto.AndroidBinderVizPlugin',
- plugin: AndroidBinderVizPlugin,
-};
diff --git a/ui/src/plugins/dev.perfetto.AndroidClientServer/index.ts b/ui/src/plugins/dev.perfetto.AndroidClientServer/index.ts
index 13000c2..ab095b4 100644
--- a/ui/src/plugins/dev.perfetto.AndroidClientServer/index.ts
+++ b/ui/src/plugins/dev.perfetto.AndroidClientServer/index.ts
@@ -14,10 +14,11 @@
import {NUM, STR} from '../../trace_processor/query_result';
import {Trace} from '../../public/trace';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
+import {PerfettoPlugin} from '../../public/plugin';
import {addDebugSliceTrack} from '../../public/debug_tracks';
-class AndroidClientServer implements PerfettoPlugin {
+export default class implements PerfettoPlugin {
+ static readonly id = 'dev.perfetto.AndroidClientServer';
async onTraceLoad(ctx: Trace): Promise<void> {
ctx.commands.registerCommand({
id: 'dev.perfetto.AndroidClientServer#ThreadRuntimeIPC',
@@ -187,26 +188,19 @@
name: STR,
});
for (; it.valid(); it.next()) {
- await addDebugSliceTrack(
- ctx,
- {
+ await addDebugSliceTrack({
+ trace: ctx,
+ data: {
sqlSource: `
SELECT ts, dur, name
FROM __enhanced_binder_for_slice_${sliceId}
WHERE binder_id = ${it.id}
`,
},
- it.name,
- {ts: 'ts', dur: 'dur', name: 'name'},
- [],
- );
+ title: it.name,
+ });
}
},
});
}
}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'dev.perfetto.AndroidClientServer',
- plugin: AndroidClientServer,
-};
diff --git a/ui/src/plugins/dev.perfetto.AndroidCujs/index.ts b/ui/src/plugins/dev.perfetto.AndroidCujs/index.ts
index 5f0a8fe..2fef0d4 100644
--- a/ui/src/plugins/dev.perfetto.AndroidCujs/index.ts
+++ b/ui/src/plugins/dev.perfetto.AndroidCujs/index.ts
@@ -12,11 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {SimpleSliceTrackConfig} from '../../frontend/simple_slice_track';
import {addDebugSliceTrack} from '../../public/debug_tracks';
import {Trace} from '../../public/trace';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
-import {addAndPinSliceTrack} from './trackUtils';
+import {PerfettoPlugin} from '../../public/plugin';
import {addQueryResultsTab} from '../../public/lib/query_table/query_result_tab';
/**
@@ -31,9 +29,8 @@
trackName: string,
cujNames?: string | string[],
) {
- const jankCujTrackConfig: SimpleSliceTrackConfig =
- generateJankCujTrackConfig(cujNames);
- addAndPinSliceTrack(ctx, jankCujTrackConfig, trackName);
+ const jankCujTrackConfig = generateJankCujTrackConfig(cujNames);
+ addDebugSliceTrack({trace: ctx, title: trackName, ...jankCujTrackConfig});
}
const JANK_CUJ_QUERY_PRECONDITIONS = `
@@ -45,11 +42,9 @@
* Generate the Track config for a multiple Jank CUJ slices
*
* @param {string | string[]} cujNames List of Jank CUJs to pin, default empty
- * @returns {SimpleSliceTrackConfig} Returns the track config for given CUJs
+ * @returns Returns the track config for given CUJs
*/
-function generateJankCujTrackConfig(
- cujNames: string | string[] = [],
-): SimpleSliceTrackConfig {
+function generateJankCujTrackConfig(cujNames: string | string[] = []) {
// This method expects the caller to have run JANK_CUJ_QUERY_PRECONDITIONS
// Not running the precondition query here to save time in case already run
const jankCujQuery = JANK_CUJ_QUERY;
@@ -62,15 +57,13 @@
.join(',')})`
: '';
- const jankCujTrackConfig: SimpleSliceTrackConfig = {
+ return {
data: {
sqlSource: `${jankCujQuery}${filterCuj}`,
columns: jankCujColumns,
},
- columns: {ts: 'ts', dur: 'dur', name: 'name'},
argColumns: jankCujColumns,
};
- return jankCujTrackConfig;
}
const JANK_CUJ_QUERY = `
@@ -215,7 +208,8 @@
'table_name',
];
-class AndroidCujs implements PerfettoPlugin {
+export default class implements PerfettoPlugin {
+ static readonly id = 'dev.perfetto.AndroidCujs';
async onTraceLoad(ctx: Trace): Promise<void> {
ctx.commands.registerCommand({
id: 'dev.perfetto.AndroidCujs#PinJankCUJs',
@@ -244,16 +238,14 @@
id: 'dev.perfetto.AndroidCujs#PinLatencyCUJs',
name: 'Add track: Android latency CUJs',
callback: () => {
- addDebugSliceTrack(
- ctx,
- {
+ addDebugSliceTrack({
+ trace: ctx,
+ data: {
sqlSource: LATENCY_CUJ_QUERY,
columns: LATENCY_COLUMNS,
},
- 'Latency CUJs',
- {ts: 'ts', dur: 'dur', name: 'name'},
- [],
- );
+ title: 'Latency CUJs',
+ });
},
});
@@ -272,23 +264,17 @@
name: 'Add track: Android Blocking calls during CUJs',
callback: () => {
ctx.engine.query(JANK_CUJ_QUERY_PRECONDITIONS).then(() =>
- addDebugSliceTrack(
- ctx,
- {
+ addDebugSliceTrack({
+ trace: ctx,
+ data: {
sqlSource: BLOCKING_CALLS_DURING_CUJS_QUERY,
columns: BLOCKING_CALLS_DURING_CUJS_COLUMNS,
},
- 'Blocking calls during CUJs',
- {ts: 'ts', dur: 'dur', name: 'name'},
- BLOCKING_CALLS_DURING_CUJS_COLUMNS,
- ),
+ title: 'Blocking calls during CUJs',
+ argColumns: BLOCKING_CALLS_DURING_CUJS_COLUMNS,
+ }),
);
},
});
}
}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'dev.perfetto.AndroidCujs',
- plugin: AndroidCujs,
-};
diff --git a/ui/src/plugins/dev.perfetto.AndroidCujs/trackUtils.ts b/ui/src/plugins/dev.perfetto.AndroidCujs/trackUtils.ts
index 28f9d7a..ca01328 100644
--- a/ui/src/plugins/dev.perfetto.AndroidCujs/trackUtils.ts
+++ b/ui/src/plugins/dev.perfetto.AndroidCujs/trackUtils.ts
@@ -12,50 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {SimpleSliceTrackConfig} from '../../frontend/simple_slice_track';
-import {addDebugSliceTrack} from '../../public/debug_tracks';
import {Trace} from '../../public/trace';
/**
- * Adds debug tracks from SimpleSliceTrackConfig
- * Static tracks cannot be added on command
- * TODO: b/349502258 - To be removed later
- *
- * @param {Trace} ctx Context for trace methods and properties
- * @param {SimpleSliceTrackConfig} config Track config to add
- * @param {string} trackName Track name to display
- */
-export function addDebugTrackOnCommand(
- ctx: Trace,
- config: SimpleSliceTrackConfig,
- trackName: string,
-) {
- addDebugSliceTrack(
- ctx,
- config.data,
- trackName,
- config.columns,
- config.argColumns,
- );
-}
-
-/**
- * Registers and pins tracks on traceload or command
- *
- * @param {Trace} ctx Context for trace methods and properties
- * @param {SimpleSliceTrackConfig} config Track config to add
- * @param {string} trackName Track name to display
- * type 'static' expects caller to pass uri string
- */
-export function addAndPinSliceTrack(
- ctx: Trace,
- config: SimpleSliceTrackConfig,
- trackName: string,
-) {
- addDebugTrackOnCommand(ctx, config, trackName);
-}
-
-/**
* Sets focus on a specific slice within the trace data.
*
* Takes and adds desired slice to current selection
@@ -63,6 +22,6 @@
*/
export function focusOnSlice(ctx: Trace, sqlSliceId: number) {
ctx.selection.selectSqlEvent('slice', sqlSliceId, {
- pendingScrollId: sqlSliceId,
+ scrollToSelection: true,
});
}
diff --git a/ui/src/plugins/dev.perfetto.AndroidDesktopMode/OWNERS b/ui/src/plugins/dev.perfetto.AndroidDesktopMode/OWNERS
new file mode 100644
index 0000000..d0be0cc
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.AndroidDesktopMode/OWNERS
@@ -0,0 +1,2 @@
+benm@google.com
+
diff --git a/ui/src/plugins/dev.perfetto.AndroidDesktopMode/index.ts b/ui/src/plugins/dev.perfetto.AndroidDesktopMode/index.ts
new file mode 100644
index 0000000..59f6bbf
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.AndroidDesktopMode/index.ts
@@ -0,0 +1,70 @@
+// 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 {createQuerySliceTrack} from '../../public/lib/tracks/query_slice_track';
+import {PerfettoPlugin} from '../../public/plugin';
+import {Trace} from '../../public/trace';
+import {TrackNode} from '../../public/workspace';
+
+const INCLUDE_DESKTOP_MODULE_QUERY = `INCLUDE PERFETTO MODULE android.desktop_mode`;
+
+const QUERY = `
+SELECT
+ ROW_NUMBER() OVER (ORDER BY ts) AS id,
+ ts,
+ dur,
+ ifnull(p.package_name, 'uid=' || dw.uid) AS name
+FROM android_desktop_mode_windows dw
+LEFT JOIN package_list p ON CAST (dw.uid AS INT) % 100000 = p.uid AND p.uid != 1000
+`;
+
+const COLUMNS = ['id', 'ts', 'dur', 'name'];
+const TRACK_NAME = 'Desktop Mode Windows';
+const TRACK_URI = '/desktop_windows';
+
+export default class implements PerfettoPlugin {
+ static readonly id = 'dev.perfetto.AndroidDesktopMode';
+
+ async onTraceLoad(ctx: Trace): Promise<void> {
+ await ctx.engine.query(INCLUDE_DESKTOP_MODULE_QUERY);
+ await this.registerTrack(ctx, QUERY);
+ ctx.commands.registerCommand({
+ id: 'dev.perfetto.DesktopMode#AddTrackDesktopWindowss',
+ name: 'Add Track: ' + TRACK_NAME,
+ callback: () => this.addSimpleTrack(ctx),
+ });
+ }
+
+ private async registerTrack(ctx: Trace, sql: string) {
+ const track = await createQuerySliceTrack({
+ trace: ctx,
+ uri: TRACK_URI,
+ data: {
+ sqlSource: sql,
+ columns: COLUMNS,
+ },
+ });
+ ctx.tracks.registerTrack({
+ uri: TRACK_URI,
+ title: TRACK_NAME,
+ track,
+ });
+ }
+
+ private addSimpleTrack(ctx: Trace) {
+ const trackNode = new TrackNode({uri: TRACK_URI, title: TRACK_NAME});
+ ctx.workspace.addChildInOrder(trackNode);
+ trackNode.pin();
+ }
+}
diff --git a/ui/src/plugins/dev.perfetto.AndroidDmabuf/index.ts b/ui/src/plugins/dev.perfetto.AndroidDmabuf/index.ts
index e8f4712..a946cc2 100644
--- a/ui/src/plugins/dev.perfetto.AndroidDmabuf/index.ts
+++ b/ui/src/plugins/dev.perfetto.AndroidDmabuf/index.ts
@@ -12,55 +12,74 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {Trace} from '../../public/trace';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
import {
- SimpleCounterTrack,
- SimpleCounterTrackConfig,
-} from '../../frontend/simple_counter_track';
+ createQueryCounterTrack,
+ SqlDataSource,
+} from '../../public/lib/tracks/query_counter_track';
+import {PerfettoPlugin} from '../../public/plugin';
+import {
+ getOrCreateGroupForProcess,
+ getOrCreateGroupForThread,
+} from '../../public/standard_groups';
+import {Trace} from '../../public/trace';
import {TrackNode} from '../../public/workspace';
-import {globals} from '../../frontend/globals';
-import {getOrCreateGroupForProcess} from '../../public/standard_groups';
-import {NUM} from '../../trace_processor/query_result';
+import {NUM_NULL} from '../../trace_processor/query_result';
-class AndroidDmabuf implements PerfettoPlugin {
+async function registerAllocsTrack(
+ ctx: Trace,
+ uri: string,
+ dataSource: SqlDataSource,
+) {
+ const track = await createQueryCounterTrack({
+ trace: ctx,
+ uri,
+ data: dataSource,
+ });
+ ctx.tracks.registerTrack({
+ uri,
+ title: `dmabuf allocs`,
+ track: track,
+ });
+}
+
+export default class implements PerfettoPlugin {
+ static readonly id = 'dev.perfetto.AndroidDmabuf';
async onTraceLoad(ctx: Trace): Promise<void> {
const e = ctx.engine;
await e.query(`INCLUDE PERFETTO MODULE android.memory.dmabuf`);
await e.query(`
CREATE PERFETTO TABLE _android_memory_cumulative_dmabuf AS
- SELECT upid, ts, SUM(buf_size) OVER(PARTITION BY upid ORDER BY ts) AS value
- FROM android_dmabuf_allocs`);
+ SELECT
+ upid, utid, ts,
+ SUM(buf_size) OVER(PARTITION BY COALESCE(upid, utid) ORDER BY ts) AS value
+ FROM android_dmabuf_allocs;`);
const pids = await e.query(
- `SELECT DISTINCT upid FROM _android_memory_cumulative_dmabuf`,
+ `SELECT DISTINCT upid, IIF(upid IS NULL, utid, NULL) AS utid FROM _android_memory_cumulative_dmabuf`,
);
- const it = pids.iter({upid: NUM});
+ const it = pids.iter({upid: NUM_NULL, utid: NUM_NULL});
for (; it.valid(); it.next()) {
- const upid = it.upid;
- const uri = `/android_process_dmabuf_${upid}`;
- const config: SimpleCounterTrackConfig = {
- data: {
- sqlSource: `SELECT ts, value FROM _android_memory_cumulative_dmabuf WHERE upid = ${upid}`,
- columns: ['ts', 'value'],
- },
- columns: {ts: 'ts', value: 'value'},
- };
-
- ctx.tracks.registerTrack({
- uri,
- title: `dmabuf allocs`,
- track: new SimpleCounterTrack(ctx, {trackUri: uri}, config),
- });
- const track = new TrackNode({uri, title: 'mem.dmabuf.alloc'});
- getOrCreateGroupForProcess(globals.workspace, upid).addChildInOrder(
- track,
- );
+ if (it.upid != null) {
+ const uri = `/android_process_dmabuf_upid_${it.upid}`;
+ const config: SqlDataSource = {
+ sqlSource: `SELECT ts, value FROM _android_memory_cumulative_dmabuf
+ WHERE upid = ${it.upid}`,
+ };
+ await registerAllocsTrack(ctx, uri, config);
+ getOrCreateGroupForProcess(ctx.workspace, it.upid).addChildInOrder(
+ new TrackNode({uri, title: 'dmabuf allocs'}),
+ );
+ } else if (it.utid != null) {
+ const uri = `/android_process_dmabuf_utid_${it.utid}`;
+ const config: SqlDataSource = {
+ sqlSource: `SELECT ts, value FROM _android_memory_cumulative_dmabuf
+ WHERE utid = ${it.utid}`,
+ };
+ await registerAllocsTrack(ctx, uri, config);
+ getOrCreateGroupForThread(ctx.workspace, it.utid).addChildInOrder(
+ new TrackNode({uri, title: 'dmabuf allocs'}),
+ );
+ }
}
}
}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'dev.perfetto.AndroidDmabuf',
- plugin: AndroidDmabuf,
-};
diff --git a/ui/src/core_plugins/android_log/index.ts b/ui/src/plugins/dev.perfetto.AndroidLog/index.ts
similarity index 90%
rename from ui/src/core_plugins/android_log/index.ts
rename to ui/src/plugins/dev.perfetto.AndroidLog/index.ts
index 7852fa5..8736fcd 100644
--- a/ui/src/core_plugins/android_log/index.ts
+++ b/ui/src/plugins/dev.perfetto.AndroidLog/index.ts
@@ -16,14 +16,14 @@
import {LogFilteringCriteria, LogPanel} from './logs_panel';
import {ANDROID_LOGS_TRACK_KIND} from '../../public/track_kinds';
import {Trace} from '../../public/trace';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
-import {addSqlTableTab} from '../../frontend/sql_table_tab_interface';
+import {PerfettoPlugin} from '../../public/plugin';
import {sqlTableRegistry} from '../../frontend/widgets/sql/table/sql_table_registry';
import {NUM} from '../../trace_processor/query_result';
import {AndroidLogTrack} from './logs_track';
import {exists} from '../../base/utils';
import {TrackNode} from '../../public/workspace';
import {getAndroidLogsTable} from './table';
+import {extensions} from '../../public/lib/extensions';
const VERSION = 1;
@@ -43,7 +43,8 @@
filter: LogFilteringCriteria;
}
-class AndroidLog implements PerfettoPlugin {
+export default class implements PerfettoPlugin {
+ static readonly id = 'dev.perfetto.AndroidLog';
async onTraceLoad(ctx: Trace): Promise<void> {
const store = ctx.mountStore<AndroidLogPluginState>((init) => {
return exists(init) && (init as {version: unknown}).version === VERSION
@@ -102,15 +103,10 @@
id: 'perfetto.ShowTable.android_logs',
name: 'Open table: android_logs',
callback: () => {
- addSqlTableTab(ctx, {
+ extensions.addSqlTableTab(ctx, {
table: getAndroidLogsTable(),
});
},
});
}
}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'perfetto.AndroidLog',
- plugin: AndroidLog,
-};
diff --git a/ui/src/core_plugins/android_log/logs_panel.ts b/ui/src/plugins/dev.perfetto.AndroidLog/logs_panel.ts
similarity index 94%
rename from ui/src/core_plugins/android_log/logs_panel.ts
rename to ui/src/plugins/dev.perfetto.AndroidLog/logs_panel.ts
index 6aa3530..48714ba 100644
--- a/ui/src/core_plugins/android_log/logs_panel.ts
+++ b/ui/src/plugins/dev.perfetto.AndroidLog/logs_panel.ts
@@ -14,7 +14,6 @@
import m from 'mithril';
import {time, Time, TimeSpan} from '../../base/time';
-import {raf} from '../../core/raf_scheduler';
import {DetailsShell} from '../../widgets/details_shell';
import {Timestamp} from '../../frontend/widgets/timestamp';
import {Engine} from '../../trace_processor/engine';
@@ -97,7 +96,7 @@
{
title: 'Android Logs',
description: `Total messages: ${totalEvents}`,
- buttons: m(LogsFilters, {store: attrs.filterStore}),
+ buttons: m(LogsFilters, {trace: attrs.trace, store: attrs.filterStore}),
},
m(VirtualTable, {
className: 'pf-android-logs-table',
@@ -146,7 +145,7 @@
this.pagination,
);
- raf.scheduleFullRedraw();
+ attrs.trace.scheduleFullRedraw();
});
}
@@ -200,9 +199,10 @@
const IGNORED_STATES = 2;
interface LogPriorityWidgetAttrs {
- options: string[];
- selectedIndex: number;
- onSelect: (id: number) => void;
+ readonly trace: Trace;
+ readonly options: string[];
+ readonly selectedIndex: number;
+ readonly onSelect: (id: number) => void;
}
class LogPriorityWidget implements m.ClassComponent<LogPriorityWidgetAttrs> {
@@ -221,6 +221,7 @@
onchange: (e: Event) => {
const selectionValue = (e.target as HTMLSelectElement).value;
attrs.onSelect(Number(selectionValue));
+ attrs.trace.scheduleFullRedraw();
},
},
optionComponents,
@@ -229,7 +230,8 @@
}
interface LogTextWidgetAttrs {
- onChange: (value: string) => void;
+ readonly trace: Trace;
+ readonly onChange: (value: string) => void;
}
class LogTextWidget implements m.ClassComponent<LogTextWidgetAttrs> {
@@ -241,15 +243,16 @@
// updated with the latest key (onkeyup).
const htmlElement = e.target as HTMLInputElement;
attrs.onChange(htmlElement.value);
+ attrs.trace.scheduleFullRedraw();
},
});
}
}
interface FilterByTextWidgetAttrs {
- hideNonMatching: boolean;
- disabled: boolean;
- onClick: () => void;
+ readonly hideNonMatching: boolean;
+ readonly disabled: boolean;
+ readonly onClick: () => void;
}
class FilterByTextWidget implements m.ClassComponent<FilterByTextWidgetAttrs> {
@@ -268,7 +271,8 @@
}
interface LogsFiltersAttrs {
- store: Store<LogFilteringCriteria>;
+ readonly trace: Trace;
+ readonly store: Store<LogFilteringCriteria>;
}
export class LogsFilters implements m.ClassComponent<LogsFiltersAttrs> {
@@ -276,6 +280,7 @@
return [
m('.log-label', 'Log Level'),
m(LogPriorityWidget, {
+ trace: attrs.trace,
options: LOG_PRIORITIES,
selectedIndex: attrs.store.state.minimumLevel,
onSelect: (minimumLevel) => {
@@ -299,6 +304,7 @@
},
}),
m(LogTextWidget, {
+ trace: attrs.trace,
onChange: (text) => {
attrs.store.edit((draft) => {
draft.textEntry = text;
diff --git a/ui/src/core_plugins/android_log/logs_track.ts b/ui/src/plugins/dev.perfetto.AndroidLog/logs_track.ts
similarity index 100%
rename from ui/src/core_plugins/android_log/logs_track.ts
rename to ui/src/plugins/dev.perfetto.AndroidLog/logs_track.ts
diff --git a/ui/src/core_plugins/android_log/table.ts b/ui/src/plugins/dev.perfetto.AndroidLog/table.ts
similarity index 100%
rename from ui/src/core_plugins/android_log/table.ts
rename to ui/src/plugins/dev.perfetto.AndroidLog/table.ts
diff --git a/ui/src/plugins/dev.perfetto.AndroidLongBatteryTracing/index.ts b/ui/src/plugins/dev.perfetto.AndroidLongBatteryTracing/index.ts
index 1190a1c..45c471c 100644
--- a/ui/src/plugins/dev.perfetto.AndroidLongBatteryTracing/index.ts
+++ b/ui/src/plugins/dev.perfetto.AndroidLongBatteryTracing/index.ts
@@ -13,17 +13,11 @@
// limitations under the License.
import {Trace} from '../../public/trace';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
+import {PerfettoPlugin} from '../../public/plugin';
import {Engine} from '../../trace_processor/engine';
-import {
- SimpleSliceTrack,
- SimpleSliceTrackConfig,
-} from '../../frontend/simple_slice_track';
+import {createQuerySliceTrack} from '../../public/lib/tracks/query_slice_track';
import {CounterOptions} from '../../frontend/base_counter_track';
-import {
- SimpleCounterTrack,
- SimpleCounterTrackConfig,
-} from '../../frontend/simple_counter_track';
+import {createQueryCounterTrack} from '../../public/lib/tracks/query_counter_track';
import {TrackNode} from '../../public/workspace';
interface ContainedTrace {
@@ -171,54 +165,6 @@
)
select * from final where ts is not null`;
-const MODEM_ACTIVITY_INFO = `
- drop table if exists modem_activity_info;
- create table modem_activity_info as
- with modem_raw as (
- select
- ts,
- EXTRACT_ARG(arg_set_id, 'modem_activity_info.timestamp_millis') as timestamp_millis,
- EXTRACT_ARG(arg_set_id, 'modem_activity_info.sleep_time_millis') as sleep_time_millis,
- EXTRACT_ARG(arg_set_id, 'modem_activity_info.controller_idle_time_millis') as controller_idle_time_millis,
- EXTRACT_ARG(arg_set_id, 'modem_activity_info.controller_tx_time_pl0_millis') as controller_tx_time_pl0_millis,
- EXTRACT_ARG(arg_set_id, 'modem_activity_info.controller_tx_time_pl1_millis') as controller_tx_time_pl1_millis,
- EXTRACT_ARG(arg_set_id, 'modem_activity_info.controller_tx_time_pl2_millis') as controller_tx_time_pl2_millis,
- EXTRACT_ARG(arg_set_id, 'modem_activity_info.controller_tx_time_pl3_millis') as controller_tx_time_pl3_millis,
- EXTRACT_ARG(arg_set_id, 'modem_activity_info.controller_tx_time_pl4_millis') as controller_tx_time_pl4_millis,
- EXTRACT_ARG(arg_set_id, 'modem_activity_info.controller_rx_time_millis') as controller_rx_time_millis
- from track t join slice s on t.id = s.track_id
- where t.name = 'Statsd Atoms'
- and s.name = 'modem_activity_info'
- ),
- deltas as (
- select
- timestamp_millis * 1000000 as ts,
- lead(timestamp_millis) over (order by ts) - timestamp_millis as dur_millis,
- lead(sleep_time_millis) over (order by ts) - sleep_time_millis as sleep_time_millis,
- lead(controller_idle_time_millis) over (order by ts) - controller_idle_time_millis as controller_idle_time_millis,
- lead(controller_tx_time_pl0_millis) over (order by ts) - controller_tx_time_pl0_millis as controller_tx_time_pl0_millis,
- lead(controller_tx_time_pl1_millis) over (order by ts) - controller_tx_time_pl1_millis as controller_tx_time_pl1_millis,
- lead(controller_tx_time_pl2_millis) over (order by ts) - controller_tx_time_pl2_millis as controller_tx_time_pl2_millis,
- lead(controller_tx_time_pl3_millis) over (order by ts) - controller_tx_time_pl3_millis as controller_tx_time_pl3_millis,
- lead(controller_tx_time_pl4_millis) over (order by ts) - controller_tx_time_pl4_millis as controller_tx_time_pl4_millis,
- lead(controller_rx_time_millis) over (order by ts) - controller_rx_time_millis as controller_rx_time_millis
- from modem_raw
- ),
- ratios as (
- select
- ts,
- 100.0 * sleep_time_millis / dur_millis as sleep_time_ratio,
- 100.0 * controller_idle_time_millis / dur_millis as controller_idle_time_ratio,
- 100.0 * controller_tx_time_pl0_millis / dur_millis as controller_tx_time_pl0_ratio,
- 100.0 * controller_tx_time_pl1_millis / dur_millis as controller_tx_time_pl1_ratio,
- 100.0 * controller_tx_time_pl2_millis / dur_millis as controller_tx_time_pl2_ratio,
- 100.0 * controller_tx_time_pl3_millis / dur_millis as controller_tx_time_pl3_ratio,
- 100.0 * controller_tx_time_pl4_millis / dur_millis as controller_tx_time_pl4_ratio,
- 100.0 * controller_rx_time_millis / dur_millis as controller_rx_time_ratio
- from deltas
- )
- select * from ratios where sleep_time_ratio is not null and sleep_time_ratio >= 0`;
-
const MODEM_RIL_STRENGTH = `
DROP VIEW IF EXISTS ScreenOn;
CREATE VIEW ScreenOn AS
@@ -1148,7 +1094,8 @@
from step2
`;
-class AndroidLongBatteryTracing implements PerfettoPlugin {
+export default class implements PerfettoPlugin {
+ static readonly id = 'dev.perfetto.AndroidLongBatteryTracing';
private readonly groups = new Map<string, TrackNode>();
private addTrack(ctx: Trace, track: TrackNode, groupName?: string): void {
@@ -1167,69 +1114,69 @@
}
}
- addSliceTrack(
+ async addSliceTrack(
ctx: Trace,
name: string,
query: string,
groupName?: string,
columns: string[] = [],
- ): void {
- const config: SimpleSliceTrackConfig = {
+ ) {
+ const uri = `/long_battery_tracing_${name}`;
+ const track = await createQuerySliceTrack({
+ trace: ctx,
+ uri,
data: {
sqlSource: query,
columns: ['ts', 'dur', 'name', ...columns],
},
- columns: {ts: 'ts', dur: 'dur', name: 'name'},
argColumns: columns,
- };
-
- const uri = `/long_battery_tracing_${name}`;
+ });
ctx.tracks.registerTrack({
uri,
title: name,
- track: new SimpleSliceTrack(ctx, {trackUri: uri}, config),
+ track,
});
- const track = new TrackNode({uri, title: name});
- this.addTrack(ctx, track, groupName);
+ const trackNode = new TrackNode({uri, title: name});
+ this.addTrack(ctx, trackNode, groupName);
}
- addCounterTrack(
+ async addCounterTrack(
ctx: Trace,
name: string,
query: string,
groupName: string,
options?: Partial<CounterOptions>,
- ): void {
- const config: SimpleCounterTrackConfig = {
+ ) {
+ const uri = `/long_battery_tracing_${name}`;
+ const track = await createQueryCounterTrack({
+ trace: ctx,
+ uri,
data: {
sqlSource: query,
columns: ['ts', 'value'],
},
- columns: {ts: 'ts', value: 'value'},
options,
- };
-
- const uri = `/long_battery_tracing_${name}`;
+ });
ctx.tracks.registerTrack({
uri,
title: name,
- track: new SimpleCounterTrack(ctx, {trackUri: uri}, config),
+ track,
});
- const track = new TrackNode({uri, title: name});
- this.addTrack(ctx, track, groupName);
+ const trackNode = new TrackNode({uri, title: name});
+ this.addTrack(ctx, trackNode, groupName);
}
- addBatteryStatsState(
+ async addBatteryStatsState(
ctx: Trace,
name: string,
track: string,
groupName: string,
features: Set<string>,
- ): void {
+ ) {
if (!features.has(`track.${track}`)) {
return;
}
- this.addSliceTrack(
+ await this.addSliceTrack(
ctx,
name,
`SELECT ts, safe_dur AS dur, value_name AS name
@@ -1239,18 +1186,18 @@
);
}
- addBatteryStatsEvent(
+ async addBatteryStatsEvent(
ctx: Trace,
name: string,
track: string,
groupName: string | undefined,
features: Set<string>,
- ): void {
+ ) {
if (!features.has(`track.${track}`)) {
return;
}
- this.addSliceTrack(
+ await this.addSliceTrack(
ctx,
name,
`SELECT ts, safe_dur AS dur, str_value AS name
@@ -1273,15 +1220,19 @@
await e.query(`INCLUDE PERFETTO MODULE android.suspend;`);
await e.query(`INCLUDE PERFETTO MODULE counters.intervals;`);
- this.addSliceTrack(ctx, 'Device State: Screen state', SCREEN_STATE);
- this.addSliceTrack(ctx, 'Device State: Charging', CHARGING);
- this.addSliceTrack(ctx, 'Device State: Suspend / resume', SUSPEND_RESUME);
- this.addSliceTrack(ctx, 'Device State: Doze light state', DOZE_LIGHT);
- this.addSliceTrack(ctx, 'Device State: Doze deep state', DOZE_DEEP);
+ await this.addSliceTrack(ctx, 'Device State: Screen state', SCREEN_STATE);
+ await this.addSliceTrack(ctx, 'Device State: Charging', CHARGING);
+ await this.addSliceTrack(
+ ctx,
+ 'Device State: Suspend / resume',
+ SUSPEND_RESUME,
+ );
+ await this.addSliceTrack(ctx, 'Device State: Doze light state', DOZE_LIGHT);
+ await this.addSliceTrack(ctx, 'Device State: Doze deep state', DOZE_DEEP);
query('Device State: Top app', 'battery_stats.top');
- this.addSliceTrack(
+ await this.addSliceTrack(
ctx,
'Device State: Long wakelocks',
`SELECT
@@ -1302,7 +1253,7 @@
query('Device State: Jobs', 'battery_stats.job');
if (features.has('atom.thermal_throttling_severity_state_changed')) {
- this.addSliceTrack(
+ await this.addSliceTrack(
ctx,
'Device State: Thermal throttling',
THERMAL_THROTTLING,
@@ -1310,6 +1261,122 @@
}
}
+ async addAtomCounters(ctx: Trace): Promise<void> {
+ const e = ctx.engine;
+
+ try {
+ await e.query(
+ `INCLUDE PERFETTO MODULE
+ google3.wireless.android.telemetry.trace_extractor.modules.atom_counters_slices`,
+ );
+ } catch (e) {
+ return;
+ }
+
+ const counters = await e.query(
+ `select distinct ui_group, ui_name, ui_unit, counter_name
+ from atom_counters
+ where ui_name is not null`,
+ );
+ const countersIt = counters.iter({
+ ui_group: 'str',
+ ui_name: 'str',
+ ui_unit: 'str',
+ counter_name: 'str',
+ });
+ for (; countersIt.valid(); countersIt.next()) {
+ const unit = countersIt.ui_unit;
+ const opts =
+ unit === '%'
+ ? {yOverrideMaximum: 100, unit: '%'}
+ : unit !== undefined
+ ? {unit}
+ : undefined;
+
+ await this.addCounterTrack(
+ ctx,
+ countersIt.ui_name,
+ `select ts, ${unit === '%' ? 100.0 : 1.0} * counter_value as value
+ from atom_counters
+ where counter_name = '${countersIt.counter_name}'`,
+ countersIt.ui_group,
+ opts,
+ );
+ }
+ }
+
+ async addAtomSlices(ctx: Trace): Promise<void> {
+ const e = ctx.engine;
+
+ try {
+ await e.query(
+ `INCLUDE PERFETTO MODULE
+ google3.wireless.android.telemetry.trace_extractor.modules.atom_counters_slices`,
+ );
+ } catch (e) {
+ return;
+ }
+
+ const sliceTracks = await e.query(
+ `select distinct ui_group, ui_name, atom, field
+ from atom_slices
+ where ui_name is not null
+ order by 1, 2, 3, 4`,
+ );
+ const slicesIt = sliceTracks.iter({
+ atom: 'str',
+ ui_group: 'str',
+ ui_name: 'str',
+ field: 'str',
+ });
+
+ const tracks = new Map<
+ string,
+ {
+ ui_group: string;
+ ui_name: string;
+ }
+ >();
+ const fields = new Map<string, string[]>();
+ for (; slicesIt.valid(); slicesIt.next()) {
+ const atom = slicesIt.atom;
+ let args = fields.get(atom);
+ if (args === undefined) {
+ args = [];
+ fields.set(atom, args);
+ }
+ args.push(slicesIt.field);
+ tracks.set(atom, {
+ ui_group: slicesIt.ui_group,
+ ui_name: slicesIt.ui_name,
+ });
+ }
+
+ for (const [atom, args] of fields) {
+ function safeArg(arg: string) {
+ return arg.replaceAll(/[[\]]/g, '').replaceAll(/\./g, '_');
+ }
+
+ // We need to make arg names compatible with SQL here because they pass through several
+ // layers of SQL without being quoted in "".
+ function argSql(arg: string) {
+ return `max(case when field = '${arg}' then ifnull(string_value, int_value) end)
+ as ${safeArg(arg)}`;
+ }
+
+ await this.addSliceTrack(
+ ctx,
+ tracks.get(atom)!.ui_name,
+ `select ts, dur, slice_name as name, ${args.map((a) => argSql(a)).join(', ')}
+ from atom_slices
+ where atom = '${atom}'
+ group by ts, dur, name`,
+ tracks.get(atom)!.ui_group,
+ args.map((a) => safeArg(a)),
+ );
+ }
+ }
+
async addNetworkSummary(ctx: Trace, features: Set<string>): Promise<void> {
if (!features.has('net.modem') && !features.has('net.wifi')) {
return;
@@ -1323,13 +1390,18 @@
await e.query(NETWORK_SUMMARY);
await e.query(RADIO_TRANSPORT_TYPE);
- this.addSliceTrack(ctx, 'Default network', DEFAULT_NETWORK, groupName);
+ await this.addSliceTrack(
+ ctx,
+ 'Default network',
+ DEFAULT_NETWORK,
+ groupName,
+ );
if (features.has('atom.network_tethering_reported')) {
- this.addSliceTrack(ctx, 'Tethering', TETHERING, groupName);
+ await this.addSliceTrack(ctx, 'Tethering', TETHERING, groupName);
}
if (features.has('net.wifi')) {
- this.addCounterTrack(
+ await this.addCounterTrack(
ctx,
'Wifi total bytes',
`select ts, sum(value) as value from network_summary where dev_type = 'wifi' group by 1`,
@@ -1341,7 +1413,7 @@
);
const it = result.iter({pkg: 'str'});
for (; it.valid(); it.next()) {
- this.addCounterTrack(
+ await this.addCounterTrack(
ctx,
`Top wifi: ${it.pkg}`,
`select ts, value from network_summary where dev_type = 'wifi' and pkg = '${it.pkg}'`,
@@ -1372,7 +1444,7 @@
features,
);
if (features.has('net.modem')) {
- this.addCounterTrack(
+ await this.addCounterTrack(
ctx,
'Modem total bytes',
`select ts, sum(value) as value from network_summary where dev_type = 'modem' group by 1`,
@@ -1384,7 +1456,7 @@
);
const it = result.iter({pkg: 'str'});
for (; it.valid(); it.next()) {
- this.addCounterTrack(
+ await this.addCounterTrack(
ctx,
`Top modem: ${it.pkg}`,
`select ts, value from network_summary where dev_type = 'modem' and pkg = '${it.pkg}'`,
@@ -1400,7 +1472,7 @@
groupName,
features,
);
- this.addSliceTrack(
+ await this.addSliceTrack(
ctx,
'Cellular connection',
`select ts, dur, name from radio_transport`,
@@ -1416,40 +1488,16 @@
}
async addModemDetail(ctx: Trace, features: Set<string>): Promise<void> {
- if (!features.has('atom.modem_activity_info')) {
- return;
- }
const groupName = 'Modem Detail';
- await this.addModemActivityInfo(ctx, groupName);
if (features.has('track.ril')) {
await this.addModemRil(ctx, groupName);
}
- }
-
- async addModemActivityInfo(ctx: Trace, groupName: string): Promise<void> {
- const query = (name: string, col: string): void =>
- this.addCounterTrack(
- ctx,
- name,
- `select ts, ${col}_ratio as value from modem_activity_info`,
- groupName,
- {yOverrideMaximum: 100, unit: '%'},
- );
-
- await ctx.engine.query(MODEM_ACTIVITY_INFO);
- query('Modem sleep', 'sleep_time');
- query('Modem controller idle', 'controller_idle_time');
- query('Modem RX time', 'controller_rx_time');
- query('Modem TX time power 0', 'controller_tx_time_pl0');
- query('Modem TX time power 1', 'controller_tx_time_pl1');
- query('Modem TX time power 2', 'controller_tx_time_pl2');
- query('Modem TX time power 3', 'controller_tx_time_pl3');
- query('Modem TX time power 4', 'controller_tx_time_pl4');
+ await this.addModemTeaData(ctx, groupName);
}
async addModemRil(ctx: Trace, groupName: string): Promise<void> {
- const rilStrength = (band: string, value: string): void =>
- this.addSliceTrack(
+ const rilStrength = async (band: string, value: string) =>
+ await this.addSliceTrack(
ctx,
`Modem signal strength ${band} ${value}`,
`SELECT ts, dur, name FROM RilScreenOn WHERE band_name = '${band}' AND value_name = '${value}'`,
@@ -1458,59 +1506,22 @@
const e = ctx.engine;
- let thrown = false;
- try {
- await e.query(
- `INCLUDE PERFETTO MODULE
- google3.wireless.android.telemetry.trace_extractor.modules.modem_tea_metrics`,
- );
- } catch {
- thrown = true;
- }
- if (!thrown) {
- const counters = await e.query(
- `select distinct name from pixel_modem_counters`,
- );
- const countersIt = counters.iter({name: 'str'});
- for (; countersIt.valid(); countersIt.next()) {
- this.addCounterTrack(
- ctx,
- countersIt.name,
- `select ts, value from pixel_modem_counters where name = '${countersIt.name}'`,
- groupName,
- );
- }
- const slices = await e.query(
- `select distinct track_name from pixel_modem_slices`,
- );
- const slicesIt = slices.iter({track_name: 'str'});
- for (; slicesIt.valid(); slicesIt.next()) {
- this.addSliceTrack(
- ctx,
- it.name,
- `select ts dur, slice_name as name from pixel_modem_counters
- where track_name = '${slicesIt.track_name}'`,
- groupName,
- );
- }
- }
-
await e.query(MODEM_RIL_STRENGTH);
await e.query(MODEM_RIL_CHANNELS_PREAMBLE);
- rilStrength('LTE', 'rsrp');
- rilStrength('LTE', 'rssi');
- rilStrength('NR', 'rsrp');
- rilStrength('NR', 'rssi');
+ await rilStrength('LTE', 'rsrp');
+ await rilStrength('LTE', 'rssi');
+ await rilStrength('NR', 'rsrp');
+ await rilStrength('NR', 'rssi');
- this.addSliceTrack(
+ await this.addSliceTrack(
ctx,
'Modem channel config',
MODEM_RIL_CHANNELS,
groupName,
);
- this.addSliceTrack(
+ await this.addSliceTrack(
ctx,
'Modem cell reselection',
MODEM_CELL_RESELECTION,
@@ -1519,6 +1530,45 @@
);
}
+ async addModemTeaData(ctx: Trace, groupName: string): Promise<void> {
+ const e = ctx.engine;
+
+ try {
+ await e.query(
+ `INCLUDE PERFETTO MODULE
+ google3.wireless.android.telemetry.trace_extractor.modules.modem_tea_metrics`,
+ );
+ } catch {
+ return;
+ }
+
+ const counters = await e.query(
+ `select distinct name from pixel_modem_counters`,
+ );
+ const countersIt = counters.iter({name: 'str'});
+ for (; countersIt.valid(); countersIt.next()) {
+ await this.addCounterTrack(
+ ctx,
+ countersIt.name,
+ `select ts, value from pixel_modem_counters where name = '${countersIt.name}'`,
+ groupName,
+ );
+ }
+ const slices = await e.query(
+ `select distinct track_name from pixel_modem_slices`,
+ );
+ const slicesIt = slices.iter({track_name: 'str'});
+ for (; slicesIt.valid(); slicesIt.next()) {
+ await this.addSliceTrack(
+ ctx,
+ slicesIt.track_name,
+ `select ts, dur, slice_name as name from pixel_modem_slices
+ where track_name = '${slicesIt.track_name}'`,
+ groupName,
+ );
+ }
+ }
+
async addKernelWakelocks(ctx: Trace, features: Set<string>): Promise<void> {
if (!features.has('atom.kernel_wakelock')) {
return;
@@ -1531,7 +1581,7 @@
const result = await e.query(KERNEL_WAKELOCKS_SUMMARY);
const it = result.iter({wakelock_name: 'str'});
for (; it.valid(); it.next()) {
- this.addCounterTrack(
+ await this.addCounterTrack(
ctx,
it.wakelock_name,
`select ts, dur, value from kernel_wakelocks where wakelock_name = "${it.wakelock_name}"`,
@@ -1579,7 +1629,7 @@
let labelOther = false;
for (; it.valid(); it.next()) {
labelOther = true;
- this.addSliceTrack(
+ await this.addSliceTrack(
ctx,
`Wakeup ${it.item}`,
`${sqlPrefix} where item="${it.item}"`,
@@ -1588,7 +1638,7 @@
);
items.push(it.item);
}
- this.addSliceTrack(
+ await this.addSliceTrack(
ctx,
labelOther ? 'Other wakeups' : 'Wakeups',
`${sqlPrefix} where item not in ('${items.join("','")}')`,
@@ -1611,7 +1661,7 @@
);
const it = result.iter({pkg: 'str', cluster: 'str'});
for (; it.valid(); it.next()) {
- this.addCounterTrack(
+ await this.addCounterTrack(
ctx,
`CPU (${it.cluster}): ${it.pkg}`,
`select ts, value from high_cpu where pkg = "${it.pkg}" and cluster="${it.cluster}"`,
@@ -1630,140 +1680,140 @@
return;
}
const groupName = 'Bluetooth';
- this.addSliceTrack(
+ await this.addSliceTrack(
ctx,
'BLE Scans (opportunistic)',
bleScanQuery('opportunistic'),
groupName,
);
- this.addSliceTrack(
+ await this.addSliceTrack(
ctx,
'BLE Scans (filtered)',
bleScanQuery('filtered'),
groupName,
);
- this.addSliceTrack(
+ await this.addSliceTrack(
ctx,
'BLE Scans (unfiltered)',
bleScanQuery('not filtered'),
groupName,
);
- this.addSliceTrack(ctx, 'BLE Scan Results', BLE_RESULTS, groupName);
- this.addSliceTrack(ctx, 'Connections (ACL)', BT_CONNS_ACL, groupName);
- this.addSliceTrack(ctx, 'Connections (SCO)', BT_CONNS_SCO, groupName);
- this.addSliceTrack(
+ await this.addSliceTrack(ctx, 'BLE Scan Results', BLE_RESULTS, groupName);
+ await this.addSliceTrack(ctx, 'Connections (ACL)', BT_CONNS_ACL, groupName);
+ await this.addSliceTrack(ctx, 'Connections (SCO)', BT_CONNS_SCO, groupName);
+ await this.addSliceTrack(
ctx,
'Link-level Events',
BT_LINK_LEVEL_EVENTS,
groupName,
BT_LINK_LEVEL_EVENTS_COLUMNS,
);
- this.addSliceTrack(ctx, 'A2DP Audio', BT_A2DP_AUDIO, groupName);
- this.addSliceTrack(
+ await this.addSliceTrack(ctx, 'A2DP Audio', BT_A2DP_AUDIO, groupName);
+ await this.addSliceTrack(
ctx,
'Bytes Transferred (L2CAP/RFCOMM)',
BT_BYTES,
groupName,
);
await ctx.engine.query(BT_ACTIVITY);
- this.addCounterTrack(
+ await this.addCounterTrack(
ctx,
'ACL Classic Active Count',
'select ts, dur, acl_active_count as value from bt_activity',
groupName,
);
- this.addCounterTrack(
+ await this.addCounterTrack(
ctx,
'ACL Classic Sniff Count',
'select ts, dur, acl_sniff_count as value from bt_activity',
groupName,
);
- this.addCounterTrack(
+ await this.addCounterTrack(
ctx,
'ACL BLE Count',
'select ts, dur, acl_ble_count as value from bt_activity',
groupName,
);
- this.addCounterTrack(
+ await this.addCounterTrack(
ctx,
'Advertising Instance Count',
'select ts, dur, advertising_count as value from bt_activity',
groupName,
);
- this.addCounterTrack(
+ await this.addCounterTrack(
ctx,
'LE Scan Duty Cycle Maximum',
'select ts, dur, le_scan_duty_cycle as value from bt_activity',
groupName,
{unit: '%'},
);
- this.addSliceTrack(
+ await this.addSliceTrack(
ctx,
'Inquiry Active',
"select ts, dur, 'Active' as name from bt_activity where inquiry_active",
groupName,
);
- this.addSliceTrack(
+ await this.addSliceTrack(
ctx,
'SCO Active',
"select ts, dur, 'Active' as name from bt_activity where sco_active",
groupName,
);
- this.addSliceTrack(
+ await this.addSliceTrack(
ctx,
'A2DP Active',
"select ts, dur, 'Active' as name from bt_activity where a2dp_active",
groupName,
);
- this.addSliceTrack(
+ await this.addSliceTrack(
ctx,
'LE Audio Active',
"select ts, dur, 'Active' as name from bt_activity where le_audio_active",
groupName,
);
- this.addCounterTrack(
+ await this.addCounterTrack(
ctx,
'Controller Idle Time',
'select ts, dur, controller_idle_pct as value from bt_activity',
groupName,
{yRangeSharingKey: 'bt_controller_time', unit: '%'},
);
- this.addCounterTrack(
+ await this.addCounterTrack(
ctx,
'Controller TX Time',
'select ts, dur, controller_tx_pct as value from bt_activity',
groupName,
{yRangeSharingKey: 'bt_controller_time', unit: '%'},
);
- this.addCounterTrack(
+ await this.addCounterTrack(
ctx,
'Controller RX Time',
'select ts, dur, controller_rx_pct as value from bt_activity',
groupName,
{yRangeSharingKey: 'bt_controller_time', unit: '%'},
);
- this.addSliceTrack(
+ await this.addSliceTrack(
ctx,
'Quality reports',
BT_QUALITY_REPORTS,
groupName,
BT_QUALITY_REPORTS_COLUMNS,
);
- this.addSliceTrack(
+ await this.addSliceTrack(
ctx,
'RSSI Reports',
BT_RSSI_REPORTS,
groupName,
BT_RSSI_REPORTS_COLUMNS,
);
- this.addSliceTrack(
+ await this.addSliceTrack(
ctx,
'HAL Crashes',
BT_HAL_CRASHES,
groupName,
BT_HAL_CRASHES_COLUMNS,
);
- this.addSliceTrack(
+ await this.addSliceTrack(
ctx,
'Code Path Counter',
BT_CODE_PATH_COUNTER,
@@ -1784,8 +1834,8 @@
bySubscription.get(trace.subscription)!.push(trace);
}
- bySubscription.forEach((traces, subscription) =>
- this.addSliceTrack(
+ for (const [subscription, traces] of bySubscription) {
+ await this.addSliceTrack(
ctx,
subscription,
traces
@@ -1800,8 +1850,8 @@
.join(' UNION ALL '),
'Other traces',
['link'],
- ),
- );
+ );
+ }
}
async findFeatures(e: Engine): Promise<Set<string>> {
@@ -1848,12 +1898,14 @@
await ctx.engine.query(PACKAGE_LOOKUP);
await this.addNetworkSummary(ctx, features);
+ await this.addBluetooth(ctx, features);
+ await this.addAtomCounters(ctx);
+ await this.addAtomSlices(ctx);
await this.addModemDetail(ctx, features);
await this.addKernelWakelocks(ctx, features);
await this.addWakeups(ctx, features);
await this.addDeviceState(ctx, features);
await this.addHighCpu(ctx, features);
- await this.addBluetooth(ctx, features);
await this.addContainedTraces(ctx, containedTraces);
}
@@ -1861,8 +1913,3 @@
await this.addTracks(ctx);
}
}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'dev.perfetto.AndroidLongBatteryTracing',
- plugin: AndroidLongBatteryTracing,
-};
diff --git a/ui/src/plugins/dev.perfetto.AndroidNetwork/index.ts b/ui/src/plugins/dev.perfetto.AndroidNetwork/index.ts
index 5159f97..56e57fb 100644
--- a/ui/src/plugins/dev.perfetto.AndroidNetwork/index.ts
+++ b/ui/src/plugins/dev.perfetto.AndroidNetwork/index.ts
@@ -13,10 +13,11 @@
// limitations under the License.
import {Trace} from '../../public/trace';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
+import {PerfettoPlugin} from '../../public/plugin';
import {addDebugSliceTrack} from '../../public/debug_tracks';
-class AndroidNetwork implements PerfettoPlugin {
+export default class implements PerfettoPlugin {
+ static readonly id = 'dev.perfetto.AndroidNetwork';
// Adds a debug track using the provided query and given columns. The columns
// must be start with ts, dur, and a name column. The name column and all
// following columns are shown as arguments in slice details.
@@ -26,16 +27,16 @@
tableOrQuery: string,
columns: string[],
): Promise<void> {
- await addDebugSliceTrack(
- ctx,
- {
+ await addDebugSliceTrack({
+ trace: ctx,
+ data: {
sqlSource: `SELECT ${columns.join(',')} FROM ${tableOrQuery}`,
columns: columns,
},
- trackName,
- {ts: columns[0], dur: columns[1], name: columns[2]},
- columns.slice(2),
- );
+ title: trackName,
+ columns: {ts: columns[0], dur: columns[1], name: columns[2]},
+ argColumns: columns.slice(2),
+ });
}
async onTraceLoad(ctx: Trace): Promise<void> {
@@ -98,8 +99,3 @@
});
}
}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'dev.perfetto.AndroidNetwork',
- plugin: AndroidNetwork,
-};
diff --git a/ui/src/plugins/dev.perfetto.AndroidPerf/index.ts b/ui/src/plugins/dev.perfetto.AndroidPerf/index.ts
index 792ac9f..ab56328 100644
--- a/ui/src/plugins/dev.perfetto.AndroidPerf/index.ts
+++ b/ui/src/plugins/dev.perfetto.AndroidPerf/index.ts
@@ -14,11 +14,12 @@
import {addDebugSliceTrack} from '../../public/debug_tracks';
import {Trace} from '../../public/trace';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
+import {PerfettoPlugin} from '../../public/plugin';
import {getTimeSpanOfSelectionOrVisibleWindow} from '../../public/utils';
import {addQueryResultsTab} from '../../public/lib/query_table/query_result_tab';
-class AndroidPerf implements PerfettoPlugin {
+export default class implements PerfettoPlugin {
+ static readonly id = 'dev.perfetto.AndroidPerf';
async addAppProcessStartsDebugTrack(
ctx: Trace,
reason: string,
@@ -33,9 +34,9 @@
'intent',
'table_name',
];
- await addDebugSliceTrack(
- ctx,
- {
+ await addDebugSliceTrack({
+ trace: ctx,
+ data: {
sqlSource: `
SELECT
start_id AS id,
@@ -50,10 +51,9 @@
`,
columns: sliceColumns,
},
- 'app_' + sliceName + '_start reason: ' + reason,
- {ts: 'ts', dur: 'dur', name: sliceName},
- sliceColumns,
- );
+ title: 'app_' + sliceName + '_start reason: ' + reason,
+ argColumns: sliceColumns,
+ });
}
async onTraceLoad(ctx: Trace): Promise<void> {
@@ -96,7 +96,7 @@
callback: () =>
addQueryResultsTab(ctx, {
query: `INCLUDE PERFETTO MODULE android.binder;
- SELECT * FROM android_binder_graph(-1000, title: 1000, -1000, 1000})`,
+ SELECT * FROM android_binder_graph(-1000, 1000, -1000, 1000)`,
title: 'all process binder graph',
}),
});
@@ -121,7 +121,7 @@
WHERE t.tid = ${tid}
)
SELECT
- c.cluster_type AS cluster, title: sum(dur})/1e6 AS total_dur_ms,
+ c.cluster_type AS cluster, sum(dur)/1e6 AS total_dur_ms,
sum(dur) * 1.0 / (SELECT * FROM total_runtime) AS percentage
FROM sched s
LEFT JOIN thread t
@@ -145,10 +145,10 @@
}
addQueryResultsTab(ctx, {
query: `
- SELECT ts.*, title: t.tid, t.name, tt.id AS track_id
+ SELECT ts.*, t.tid, t.name, tt.id AS track_id
FROM thread_state ts
LEFT JOIN thread_track tt
- USING (utid})
+ USING (utid)
LEFT JOIN thread t
USING (utid)
WHERE ts.state IN ('R', 'R+') AND tid = ${tid}
@@ -218,8 +218,3 @@
});
}
}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'dev.perfetto.AndroidPerf',
- plugin: AndroidPerf,
-};
diff --git a/ui/src/plugins/dev.perfetto.AndroidPerfTraceCounters/index.ts b/ui/src/plugins/dev.perfetto.AndroidPerfTraceCounters/index.ts
index 914e33b..c78abf8 100644
--- a/ui/src/plugins/dev.perfetto.AndroidPerfTraceCounters/index.ts
+++ b/ui/src/plugins/dev.perfetto.AndroidPerfTraceCounters/index.ts
@@ -13,7 +13,7 @@
// limitations under the License.
import {Trace} from '../../public/trace';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
+import {PerfettoPlugin} from '../../public/plugin';
import {addDebugSliceTrack} from '../../public/debug_tracks';
import {addQueryResultsTab} from '../../public/lib/query_table/query_result_tab';
@@ -26,7 +26,8 @@
AND str_value GLOB '*ftrace_events: "perf_trace_counters/sched_switch_with_ctrs"*'
`;
-class AndroidPerfTraceCounters implements PerfettoPlugin {
+export default class implements PerfettoPlugin {
+ static readonly id = 'dev.perfetto.AndroidPerfTraceCounters';
async onTraceLoad(ctx: Trace): Promise<void> {
const resp = await ctx.engine.query(PERF_TRACE_COUNTERS_PRECONDITION);
if (resp.numRows() === 0) return;
@@ -84,18 +85,23 @@
)
`;
- await addDebugSliceTrack(
- ctx,
- {
+ await addDebugSliceTrack({
+ trace: ctx,
+ data: {
sqlSource:
sqlPrefix +
`
SELECT * FROM target_thread_ipc_slice WHERE ts IS NOT NULL`,
},
- 'Rutime IPC:' + tid,
- {ts: 'ts', dur: 'dur', name: 'ipc'},
- ['instruction', 'cycle', 'stall_backend_mem', 'l3_cache_miss'],
- );
+ title: 'Rutime IPC:' + tid,
+ columns: {ts: 'ts', dur: 'dur', name: 'ipc'},
+ argColumns: [
+ 'instruction',
+ 'cycle',
+ 'stall_backend_mem',
+ 'l3_cache_miss',
+ ],
+ });
addQueryResultsTab(ctx, {
query:
sqlPrefix +
@@ -114,8 +120,3 @@
});
}
}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'dev.perfetto.AndroidPerfTraceCounters',
- plugin: AndroidPerfTraceCounters,
-};
diff --git a/ui/src/plugins/dev.perfetto.AndroidStartup/index.ts b/ui/src/plugins/dev.perfetto.AndroidStartup/index.ts
index e734528..88cf8b6 100644
--- a/ui/src/plugins/dev.perfetto.AndroidStartup/index.ts
+++ b/ui/src/plugins/dev.perfetto.AndroidStartup/index.ts
@@ -14,46 +14,72 @@
import {LONG} from '../../trace_processor/query_result';
import {Trace} from '../../public/trace';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
-import {
- SimpleSliceTrack,
- SimpleSliceTrackConfig,
-} from '../../frontend/simple_slice_track';
+import {PerfettoPlugin} from '../../public/plugin';
+import {createQuerySliceTrack} from '../../public/lib/tracks/query_slice_track';
import {TrackNode} from '../../public/workspace';
-class AndroidStartup implements PerfettoPlugin {
+export default class implements PerfettoPlugin {
+ static readonly id = 'dev.perfetto.AndroidStartup';
async onTraceLoad(ctx: Trace): Promise<void> {
const e = ctx.engine;
- await e.query(`include perfetto module android.startup.startups;`);
+ await e.query(`
+ include perfetto module android.startup.startups;
+ include perfetto module android.startup.startup_breakdowns;
+ `);
const cnt = await e.query('select count() cnt from android_startups');
if (cnt.firstRow({cnt: LONG}).cnt === 0n) {
return;
}
- const config: SimpleSliceTrackConfig = {
- data: {
- sqlSource: `
+ const trackSource = `
SELECT l.ts AS ts, l.dur AS dur, l.package AS name
FROM android_startups l
- `,
+ `;
+ const trackBreakdownSource = `
+ SELECT
+ ts,
+ dur,
+ reason AS name
+ FROM android_startup_opinionated_breakdown
+ `;
+
+ const trackNode = await this.loadStartupTrack(
+ ctx,
+ trackSource,
+ `/android_startups`,
+ 'Android App Startups',
+ );
+ const trackBreakdownNode = await this.loadStartupTrack(
+ ctx,
+ trackBreakdownSource,
+ `/android_startups_breakdown`,
+ 'Android App Startups Breakdown',
+ );
+
+ ctx.workspace.addChildInOrder(trackNode);
+ trackNode.addChildLast(trackBreakdownNode);
+ }
+
+ private async loadStartupTrack(
+ ctx: Trace,
+ sqlSource: string,
+ uri: string,
+ title: string,
+ ): Promise<TrackNode> {
+ const track = await createQuerySliceTrack({
+ trace: ctx,
+ uri,
+ data: {
+ sqlSource,
columns: ['ts', 'dur', 'name'],
},
- columns: {ts: 'ts', dur: 'dur', name: 'name'},
- argColumns: [],
- };
- const uri = `/android_startups`;
- const title = 'Android App Startups';
+ });
ctx.tracks.registerTrack({
uri,
- title: 'Android App Startups',
- track: new SimpleSliceTrack(ctx, {trackUri: uri}, config),
+ title,
+ track,
});
- const track = new TrackNode({title, uri});
- ctx.workspace.addChildInOrder(track);
+ // Needs a sort order lower than 'Ftrace Events' so that it is prioritized in the UI.
+ return new TrackNode({title, uri, sortOrder: -6});
}
}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'dev.perfetto.AndroidStartup',
- plugin: AndroidStartup,
-};
diff --git a/ui/src/plugins/dev.perfetto.AsyncSlices/async_slice_track.ts b/ui/src/plugins/dev.perfetto.AsyncSlices/async_slice_track.ts
new file mode 100644
index 0000000..1bb31a5
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.AsyncSlices/async_slice_track.ts
@@ -0,0 +1,107 @@
+// 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 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 {BigintMath as BIMath} from '../../base/bigint_math';
+import {clamp} from '../../base/math_utils';
+import {NAMED_ROW, NamedSliceTrack} from '../../frontend/named_slice_track';
+import {SLICE_LAYOUT_FIT_CONTENT_DEFAULTS} from '../../frontend/slice_layout';
+import {NewTrackArgs} from '../../frontend/track';
+import {TrackEventDetails} from '../../public/selection';
+import {Slice} from '../../public/track';
+import {LONG_NULL} from '../../trace_processor/query_result';
+
+export const THREAD_SLICE_ROW = {
+ // Base columns (tsq, ts, dur, id, depth).
+ ...NAMED_ROW,
+
+ // Thread-specific columns.
+ threadDur: LONG_NULL,
+};
+export type ThreadSliceRow = typeof THREAD_SLICE_ROW;
+
+export class AsyncSliceTrack extends NamedSliceTrack<Slice, ThreadSliceRow> {
+ constructor(
+ args: NewTrackArgs,
+ maxDepth: number,
+ private readonly trackIds: number[],
+ ) {
+ super(args);
+ this.sliceLayout = {
+ ...SLICE_LAYOUT_FIT_CONTENT_DEFAULTS,
+ depthGuess: maxDepth,
+ };
+ }
+
+ getRowSpec(): ThreadSliceRow {
+ return THREAD_SLICE_ROW;
+ }
+
+ rowToSlice(row: ThreadSliceRow): Slice {
+ const namedSlice = this.rowToSliceBase(row);
+
+ if (row.dur > 0n && row.threadDur !== null) {
+ const fillRatio = clamp(BIMath.ratio(row.threadDur, row.dur), 0, 1);
+ return {...namedSlice, fillRatio};
+ } else {
+ return namedSlice;
+ }
+ }
+
+ getSqlSource(): string {
+ // If we only have one track ID we can avoid the overhead of
+ // experimental_slice_layout, and just go straight to the slice table.
+ if (this.trackIds.length === 1) {
+ return `
+ select
+ ts,
+ dur,
+ id,
+ depth,
+ ifnull(name, '[null]') as name,
+ thread_dur as threadDur
+ from slice
+ where track_id = ${this.trackIds[0]}
+ `;
+ } else {
+ return `
+ select
+ id,
+ ts,
+ dur,
+ layout_depth as depth,
+ ifnull(name, '[null]') as name,
+ thread_dur as threadDur
+ from experimental_slice_layout
+ where filter_track_ids = '${this.trackIds.join(',')}'
+ `;
+ }
+ }
+
+ onUpdatedSlices(slices: Slice[]) {
+ for (const slice of slices) {
+ slice.isHighlighted = slice === this.hoveredSlice;
+ }
+ }
+
+ 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/plugins/dev.perfetto.AsyncSlices/index.ts b/ui/src/plugins/dev.perfetto.AsyncSlices/index.ts
new file mode 100644
index 0000000..9b0c6e7
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.AsyncSlices/index.ts
@@ -0,0 +1,407 @@
+// 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.
+
+import {removeFalsyValues} from '../../base/array_utils';
+import {TrackNode} from '../../public/workspace';
+import {SLICE_TRACK_KIND} from '../../public/track_kinds';
+import {Trace} from '../../public/trace';
+import {PerfettoPlugin} from '../../public/plugin';
+import {getThreadUriPrefix, getTrackName} from '../../public/utils';
+import {NUM, NUM_NULL, STR, STR_NULL} from '../../trace_processor/query_result';
+import {AsyncSliceTrack} from './async_slice_track';
+import {
+ getOrCreateGroupForProcess,
+ getOrCreateGroupForThread,
+} from '../../public/standard_groups';
+import {exists} from '../../base/utils';
+import {assertExists, assertTrue} from '../../base/logging';
+import {SliceSelectionAggregator} from './slice_selection_aggregator';
+import {sqlTableRegistry} from '../../frontend/widgets/sql/table/sql_table_registry';
+import {getSliceTable} from './table';
+import {extensions} from '../../public/lib/extensions';
+
+export default class implements PerfettoPlugin {
+ static readonly id = 'dev.perfetto.AsyncSlices';
+ async onTraceLoad(ctx: Trace): Promise<void> {
+ const trackIdsToUris = new Map<number, string>();
+
+ await this.addGlobalAsyncTracks(ctx, trackIdsToUris);
+ await this.addProcessAsyncSliceTracks(ctx, trackIdsToUris);
+ await this.addThreadAsyncSliceTracks(ctx, trackIdsToUris);
+
+ ctx.selection.registerSqlSelectionResolver({
+ sqlTableName: 'slice',
+ callback: async (id: number) => {
+ // Locate the track for a given id in the slice table
+ const result = await ctx.engine.query(`
+ select
+ track_id as trackId
+ from
+ slice
+ where slice.id = ${id}
+ `);
+
+ if (result.numRows() === 0) {
+ return undefined;
+ }
+
+ const {trackId} = result.firstRow({
+ trackId: NUM,
+ });
+
+ const trackUri = trackIdsToUris.get(trackId);
+ if (!trackUri) {
+ return undefined;
+ }
+
+ return {
+ trackUri,
+ eventId: id,
+ };
+ },
+ });
+
+ ctx.selection.registerAreaSelectionAggreagtor(
+ new SliceSelectionAggregator(),
+ );
+
+ sqlTableRegistry['slice'] = getSliceTable();
+
+ ctx.commands.registerCommand({
+ id: 'perfetto.ShowTable.slice',
+ name: 'Open table: slice',
+ callback: () => {
+ extensions.addSqlTableTab(ctx, {
+ table: getSliceTable(),
+ });
+ },
+ });
+ }
+
+ async addGlobalAsyncTracks(
+ ctx: Trace,
+ trackIdsToUris: Map<number, string>,
+ ): Promise<void> {
+ const {engine} = ctx;
+ // TODO(stevegolton): The track exclusion logic is currently a hack. This will be replaced
+ // by a mechanism for more specific plugins to override tracks from more generic plugins.
+ const suspendResumeLatencyTrackName = 'Suspend/Resume Latency';
+ const rawGlobalAsyncTracks = await engine.query(`
+ include perfetto module graphs.search;
+ include perfetto module viz.summary.tracks;
+
+ with global_tracks_grouped as (
+ select
+ t.parent_id,
+ t.name,
+ group_concat(id) as trackIds,
+ count() as trackCount,
+ min(a.order_id) as order_id
+ from track t
+ join _slice_track_summary using (id)
+ left join _track_event_tracks_ordered a USING (id)
+ where
+ t.type in ('__intrinsic_track', 'gpu_track', '__intrinsic_cpu_track')
+ and (name != '${suspendResumeLatencyTrackName}' or name is null)
+ and classification not in (
+ 'linux_rpm',
+ 'linux_device_frequency',
+ 'irq_counter',
+ 'softirq_counter',
+ 'android_energy_estimation_breakdown',
+ 'android_energy_estimation_breakdown_per_uid'
+ )
+ group by parent_id, name
+ order by parent_id, order_id
+ ),
+ intermediate_groups as (
+ select
+ t.name,
+ t.id,
+ t.parent_id
+ from graph_reachable_dfs!(
+ (
+ select id as source_node_id, parent_id as dest_node_id
+ from track
+ where parent_id is not null
+ ),
+ (
+ select distinct parent_id as node_id
+ from global_tracks_grouped
+ where parent_id is not null
+ )
+ ) g
+ join track t on g.node_id = t.id
+ )
+ select
+ t.name as name,
+ t.parent_id as parentId,
+ t.trackIds as trackIds,
+ __max_layout_depth(t.trackCount, t.trackIds) as maxDepth
+ from global_tracks_grouped t
+ union all
+ select
+ t.name as name,
+ t.parent_id as parentId,
+ cast_string!(t.id) as trackIds,
+ NULL as maxDepth
+ from intermediate_groups t
+ left join _slice_track_summary s using (id)
+ where s.id is null
+ `);
+ const it = rawGlobalAsyncTracks.iter({
+ name: STR_NULL,
+ parentId: NUM_NULL,
+ trackIds: STR,
+ maxDepth: NUM_NULL,
+ });
+
+ // Create a map of track nodes by id
+ const trackMap = new Map<
+ number,
+ {parentId: number | null; trackNode: TrackNode}
+ >();
+
+ for (; it.valid(); it.next()) {
+ const rawName = it.name === null ? undefined : it.name;
+ const title = getTrackName({
+ name: rawName,
+ kind: SLICE_TRACK_KIND,
+ });
+ const rawTrackIds = it.trackIds;
+ const trackIds = rawTrackIds.split(',').map((v) => Number(v));
+ const maxDepth = it.maxDepth;
+
+ if (maxDepth === null) {
+ assertTrue(trackIds.length == 1);
+ const trackNode = new TrackNode({title, sortOrder: -25});
+ trackMap.set(trackIds[0], {parentId: it.parentId, trackNode});
+ } else {
+ const uri = `/async_slices_${rawName}_${it.parentId}`;
+ ctx.tracks.registerTrack({
+ uri,
+ title,
+ tags: {
+ trackIds,
+ kind: SLICE_TRACK_KIND,
+ scope: 'global',
+ },
+ track: new AsyncSliceTrack({trace: ctx, uri}, maxDepth, trackIds),
+ });
+ const trackNode = new TrackNode({
+ uri,
+ title,
+ sortOrder: it.parentId === undefined ? -25 : 0,
+ });
+ trackIds.forEach((id) => {
+ trackMap.set(id, {parentId: it.parentId, trackNode});
+ trackIdsToUris.set(id, uri);
+ });
+ }
+ }
+
+ // Attach track nodes to parents / or the workspace if they have no parent
+ trackMap.forEach(({parentId, trackNode}) => {
+ if (exists(parentId)) {
+ const parent = assertExists(trackMap.get(parentId));
+ parent.trackNode.addChildInOrder(trackNode);
+ } else {
+ ctx.workspace.addChildInOrder(trackNode);
+ }
+ });
+ }
+
+ async addProcessAsyncSliceTracks(
+ ctx: Trace,
+ trackIdsToUris: Map<number, string>,
+ ): Promise<void> {
+ const result = await ctx.engine.query(`
+ select
+ upid,
+ t.name as trackName,
+ t.track_ids as trackIds,
+ process.name as processName,
+ process.pid as pid,
+ t.parent_id as parentId,
+ __max_layout_depth(t.track_count, t.track_ids) as maxDepth
+ from _process_track_summary_by_upid_and_parent_id_and_name t
+ join process using (upid)
+ where t.name is null or t.name not glob "* Timeline"
+ `);
+
+ const it = result.iter({
+ upid: NUM,
+ parentId: NUM_NULL,
+ trackName: STR_NULL,
+ trackIds: STR,
+ processName: STR_NULL,
+ pid: NUM_NULL,
+ maxDepth: NUM,
+ });
+
+ const trackMap = new Map<
+ number,
+ {parentId: number | null; upid: number; trackNode: TrackNode}
+ >();
+
+ for (; it.valid(); it.next()) {
+ const upid = it.upid;
+ const trackName = it.trackName;
+ const rawTrackIds = it.trackIds;
+ const trackIds = rawTrackIds.split(',').map((v) => Number(v));
+ const processName = it.processName;
+ const pid = it.pid;
+ const maxDepth = it.maxDepth;
+
+ const kind = SLICE_TRACK_KIND;
+ const title = getTrackName({
+ name: trackName,
+ upid,
+ pid,
+ processName,
+ kind,
+ });
+
+ const uri = `/process_${upid}/async_slices_${rawTrackIds}`;
+ ctx.tracks.registerTrack({
+ uri,
+ title,
+ tags: {
+ trackIds,
+ kind: SLICE_TRACK_KIND,
+ scope: 'process',
+ upid,
+ },
+ track: new AsyncSliceTrack({trace: ctx, uri}, maxDepth, trackIds),
+ });
+ const track = new TrackNode({uri, title, sortOrder: 30});
+ trackIds.forEach((id) => {
+ trackMap.set(id, {trackNode: track, parentId: it.parentId, upid});
+ trackIdsToUris.set(id, uri);
+ });
+ }
+
+ // Attach track nodes to parents / or the workspace if they have no parent
+ trackMap.forEach((t) => {
+ const parent = exists(t.parentId) && trackMap.get(t.parentId);
+ if (parent !== false && parent !== undefined) {
+ parent.trackNode.addChildInOrder(t.trackNode);
+ } else {
+ const processGroup = getOrCreateGroupForProcess(ctx.workspace, t.upid);
+ processGroup.addChildInOrder(t.trackNode);
+ }
+ });
+ }
+
+ async addThreadAsyncSliceTracks(
+ ctx: Trace,
+ trackIdsToUris: Map<number, string>,
+ ): Promise<void> {
+ const result = await ctx.engine.query(`
+ include perfetto module viz.summary.slices;
+ include perfetto module viz.summary.threads;
+ include perfetto module viz.threads;
+
+ select
+ t.utid,
+ t.parent_id as parentId,
+ thread.upid,
+ t.name as trackName,
+ thread.name as threadName,
+ thread.tid as tid,
+ t.track_ids as trackIds,
+ __max_layout_depth(t.track_count, t.track_ids) as maxDepth,
+ k.is_main_thread as isMainThread,
+ k.is_kernel_thread AS isKernelThread
+ from _thread_track_summary_by_utid_and_name t
+ join _threads_with_kernel_flag k using(utid)
+ join thread using (utid)
+ `);
+
+ const it = result.iter({
+ utid: NUM,
+ parentId: NUM_NULL,
+ upid: NUM_NULL,
+ trackName: STR_NULL,
+ trackIds: STR,
+ maxDepth: NUM,
+ isMainThread: NUM_NULL,
+ isKernelThread: NUM,
+ threadName: STR_NULL,
+ tid: NUM_NULL,
+ });
+
+ const trackMap = new Map<
+ number,
+ {parentId: number | null; utid: number; trackNode: TrackNode}
+ >();
+
+ for (; it.valid(); it.next()) {
+ const {
+ utid,
+ parentId,
+ upid,
+ trackName,
+ isMainThread,
+ isKernelThread,
+ maxDepth,
+ threadName,
+ tid,
+ } = it;
+ const rawTrackIds = it.trackIds;
+ const trackIds = rawTrackIds.split(',').map((v) => Number(v));
+ const title = getTrackName({
+ name: trackName,
+ utid,
+ tid,
+ threadName,
+ kind: 'Slices',
+ });
+
+ const uri = `/${getThreadUriPrefix(upid, utid)}_slice_${rawTrackIds}`;
+ ctx.tracks.registerTrack({
+ uri,
+ title,
+ tags: {
+ trackIds,
+ kind: SLICE_TRACK_KIND,
+ scope: 'thread',
+ utid,
+ upid: upid ?? undefined,
+ ...(isKernelThread === 1 && {kernelThread: true}),
+ },
+ chips: removeFalsyValues([
+ isKernelThread === 0 && isMainThread === 1 && 'main thread',
+ ]),
+ track: new AsyncSliceTrack({trace: ctx, uri}, maxDepth, trackIds),
+ });
+ const track = new TrackNode({uri, title, sortOrder: 20});
+ trackIds.forEach((id) => {
+ trackMap.set(id, {trackNode: track, parentId, utid});
+ trackIdsToUris.set(id, uri);
+ });
+ }
+
+ // Attach track nodes to parents / or the workspace if they have no parent
+ trackMap.forEach((t) => {
+ const parent = exists(t.parentId) && trackMap.get(t.parentId);
+ if (parent !== false && parent !== undefined) {
+ parent.trackNode.addChildInOrder(t.trackNode);
+ } else {
+ const group = getOrCreateGroupForThread(ctx.workspace, t.utid);
+ group.addChildInOrder(t.trackNode);
+ }
+ });
+ }
+}
diff --git a/ui/src/core_plugins/slice/async_and_thread_slice_selection_aggregator.ts b/ui/src/plugins/dev.perfetto.AsyncSlices/slice_selection_aggregator.ts
similarity index 85%
rename from ui/src/core_plugins/slice/async_and_thread_slice_selection_aggregator.ts
rename to ui/src/plugins/dev.perfetto.AsyncSlices/slice_selection_aggregator.ts
index 5b8003b..55f7c95 100644
--- a/ui/src/core_plugins/slice/async_and_thread_slice_selection_aggregator.ts
+++ b/ui/src/plugins/dev.perfetto.AsyncSlices/slice_selection_aggregator.ts
@@ -16,14 +16,9 @@
import {AreaSelection} from '../../public/selection';
import {Engine} from '../../trace_processor/engine';
import {AreaSelectionAggregator} from '../../public/selection';
-import {
- ASYNC_SLICE_TRACK_KIND,
- THREAD_SLICE_TRACK_KIND,
-} from '../../public/track_kinds';
+import {SLICE_TRACK_KIND} from '../../public/track_kinds';
-export class AsyncAndThreadSliceSelectionAggregator
- implements AreaSelectionAggregator
-{
+export class SliceSelectionAggregator implements AreaSelectionAggregator {
readonly id = 'slice_aggregation';
async createAggregateView(engine: Engine, area: AreaSelection) {
@@ -92,11 +87,7 @@
function getSelectedTrackSqlIds(area: AreaSelection): number[] {
const selectedTrackKeys: number[] = [];
for (const trackInfo of area.tracks) {
- if (trackInfo?.tags?.kind === THREAD_SLICE_TRACK_KIND) {
- trackInfo.tags.trackIds &&
- selectedTrackKeys.push(...trackInfo.tags.trackIds);
- }
- if (trackInfo?.tags?.kind === ASYNC_SLICE_TRACK_KIND) {
+ if (trackInfo?.tags?.kind === SLICE_TRACK_KIND) {
trackInfo.tags.trackIds &&
selectedTrackKeys.push(...trackInfo.tags.trackIds);
}
diff --git a/ui/src/core_plugins/slice/table.ts b/ui/src/plugins/dev.perfetto.AsyncSlices/table.ts
similarity index 100%
rename from ui/src/core_plugins/slice/table.ts
rename to ui/src/plugins/dev.perfetto.AsyncSlices/table.ts
diff --git a/ui/src/plugins/dev.perfetto.BookmarkletApi/index.ts b/ui/src/plugins/dev.perfetto.BookmarkletApi/index.ts
index 9218ad6..7b1769e 100644
--- a/ui/src/plugins/dev.perfetto.BookmarkletApi/index.ts
+++ b/ui/src/plugins/dev.perfetto.BookmarkletApi/index.ts
@@ -14,7 +14,7 @@
import {Trace} from '../../public/trace';
import {App} from '../../public/app';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
+import {PerfettoPlugin} from '../../public/plugin';
declare global {
interface Window {
@@ -22,24 +22,19 @@
}
}
-class BookmarkletApi implements PerfettoPlugin {
- private pluginCtx?: App;
+export default class Plugin implements PerfettoPlugin {
+ static readonly id = 'dev.perfetto.BookmarkletApi';
+ static bookmarkletPluginCtx: App;
- onActivate(pluginCtx: App): void {
- this.pluginCtx = pluginCtx;
+ static onActivate(pluginCtx: App): void {
+ this.bookmarkletPluginCtx = pluginCtx;
window.ctx = pluginCtx;
}
- async onTraceLoad(ctx: Trace): Promise<void> {
- window.ctx = ctx;
- }
-
- async onTraceUnload(_: Trace): Promise<void> {
- window.ctx = this.pluginCtx;
+ async onTraceLoad(trace: Trace): Promise<void> {
+ window.ctx = trace;
+ trace.trash.defer(() => {
+ window.ctx = Plugin.bookmarkletPluginCtx;
+ });
}
}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'dev.perfetto.BookmarkletApi',
- plugin: BookmarkletApi,
-};
diff --git a/ui/src/plugins/dev.perfetto.Chaos/index.ts b/ui/src/plugins/dev.perfetto.Chaos/index.ts
index 9747091..358ecca 100644
--- a/ui/src/plugins/dev.perfetto.Chaos/index.ts
+++ b/ui/src/plugins/dev.perfetto.Chaos/index.ts
@@ -15,10 +15,12 @@
import {Trace} from '../../public/trace';
import {App} from '../../public/app';
import {addDebugSliceTrack} from '../../public/debug_tracks';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
+import {PerfettoPlugin} from '../../public/plugin';
-class Chaos implements PerfettoPlugin {
- onActivate(ctx: App): void {
+export default class implements PerfettoPlugin {
+ static readonly id = 'dev.perfetto.Chaos';
+
+ static onActivate(ctx: App): void {
ctx.commands.registerCommand({
id: 'dev.perfetto.Chaos#CrashNow',
name: 'Chaos: crash now',
@@ -48,29 +50,20 @@
id: 'dev.perfetto.Chaos#AddCrashingDebugTrack',
name: 'Chaos: add crashing debug track',
callback: () => {
- addDebugSliceTrack(
- ctx,
- {
+ addDebugSliceTrack({
+ trace: ctx,
+ data: {
sqlSource: `
- syntactically
- invalid
- query
- over
- many
- `,
+ syntactically
+ invalid
+ query
+ over
+ many
+ `,
},
- `Chaos track`,
- {ts: 'ts', dur: 'dur', name: 'name'},
- [],
- );
+ title: `Chaos track`,
+ });
},
});
}
-
- async onTraceUnload(_: Trace): Promise<void> {}
}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'dev.perfetto.Chaos',
- plugin: Chaos,
-};
diff --git a/ui/src/core_plugins/counter/counter_details_panel.ts b/ui/src/plugins/dev.perfetto.Counter/counter_details_panel.ts
similarity index 75%
rename from ui/src/core_plugins/counter/counter_details_panel.ts
rename to ui/src/plugins/dev.perfetto.Counter/counter_details_panel.ts
index ab9e292..e0ff568 100644
--- a/ui/src/core_plugins/counter/counter_details_panel.ts
+++ b/ui/src/plugins/dev.perfetto.Counter/counter_details_panel.ts
@@ -12,17 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {AsyncLimiter} from '../../base/async_limiter';
import {Time, duration, time} from '../../base/time';
-import {raf} from '../../core/raf_scheduler';
import {Engine} from '../../trace_processor/engine';
+import {Trace} from '../../public/trace';
import {
LONG,
LONG_NULL,
NUM,
NUM_NULL,
} from '../../trace_processor/query_result';
-import {TrackSelectionDetailsPanel} from '../../public/details_panel';
+import {TrackEventDetailsPanel} from '../../public/details_panel';
import m from 'mithril';
import {DetailsShell} from '../../widgets/details_shell';
import {GridLayout} from '../../widgets/grid_layout';
@@ -30,6 +29,10 @@
import {Tree, TreeNode} from '../../widgets/tree';
import {Timestamp} from '../../frontend/widgets/timestamp';
import {DurationWidget} from '../../frontend/widgets/duration';
+import {TrackEventSelection} from '../../public/selection';
+import {hasArgs, renderArguments} from '../../frontend/slice_args';
+import {asArgSetId} from '../../trace_processor/sql_utils/core_types';
+import {Arg, getArgs} from '../../trace_processor/sql_utils/args';
interface CounterDetails {
// The "left" timestamp of the counter sample T(N)
@@ -43,49 +46,51 @@
// The delta between this sample's value and the previous one F(N) - F(N-1)
delta: number;
+
+ args?: Arg[];
}
-export class CounterDetailsPanel implements TrackSelectionDetailsPanel {
- private readonly queryLimiter = new AsyncLimiter();
+export class CounterDetailsPanel implements TrackEventDetailsPanel {
+ private readonly trace: Trace;
private readonly engine: Engine;
private readonly trackId: number;
private readonly rootTable: string;
private readonly trackName: string;
- private id?: number;
private counterDetails?: CounterDetails;
constructor(
- engine: Engine,
+ trace: Trace,
trackId: number,
trackName: string,
rootTable = 'counter',
) {
- this.engine = engine;
+ this.trace = trace;
+ this.engine = trace.engine;
this.trackId = trackId;
this.trackName = trackName;
this.rootTable = rootTable;
}
- render(id: number): m.Children {
- if (id !== this.id) {
- this.id = id;
- this.queryLimiter.schedule(async () => {
- this.counterDetails = await loadCounterDetails(
- this.engine,
- this.trackId,
- id,
- this.rootTable,
- );
- raf.scheduleFullRedraw();
- });
- }
-
- return this.renderView();
+ async load({eventId}: TrackEventSelection) {
+ this.counterDetails = await loadCounterDetails(
+ this.engine,
+ this.trackId,
+ eventId,
+ this.rootTable,
+ );
}
- private renderView() {
+ render() {
const counterInfo = this.counterDetails;
if (counterInfo) {
+ const args =
+ hasArgs(counterInfo.args) &&
+ m(
+ Section,
+ {title: 'Arguments'},
+ m(Tree, renderArguments(this.trace, counterInfo.args)),
+ );
+
return m(
DetailsShell,
{title: 'Counter', description: `${this.trackName}`},
@@ -115,6 +120,7 @@
}),
),
),
+ args,
),
);
} else {
@@ -140,7 +146,8 @@
ts as leftTs,
value,
LAG(value) OVER (ORDER BY ts) AS prevValue,
- LEAD(ts) OVER (ORDER BY ts) AS rightTs
+ LEAD(ts) OVER (ORDER BY ts) AS rightTs,
+ arg_set_id AS argSetId
FROM ${rootTable}
WHERE track_id = ${trackId}
)
@@ -153,6 +160,7 @@
prevValue: NUM_NULL,
leftTs: LONG,
rightTs: LONG_NULL,
+ argSetId: NUM_NULL,
});
const value = row.value;
const leftTs = Time.fromRaw(row.leftTs);
@@ -161,5 +169,8 @@
const delta = value - prevValue;
const duration = rightTs - leftTs;
- return {ts: leftTs, value, delta, duration};
+ const argSetId = row.argSetId;
+ const args =
+ argSetId == null ? undefined : await getArgs(engine, asArgSetId(argSetId));
+ return {ts: leftTs, value, delta, duration, args};
}
diff --git a/ui/src/core_plugins/counter/counter_selection_aggregator.ts b/ui/src/plugins/dev.perfetto.Counter/counter_selection_aggregator.ts
similarity index 100%
rename from ui/src/core_plugins/counter/counter_selection_aggregator.ts
rename to ui/src/plugins/dev.perfetto.Counter/counter_selection_aggregator.ts
diff --git a/ui/src/core_plugins/counter/index.ts b/ui/src/plugins/dev.perfetto.Counter/index.ts
similarity index 77%
rename from ui/src/core_plugins/counter/index.ts
rename to ui/src/plugins/dev.perfetto.Counter/index.ts
index 0908049..074e2c4 100644
--- a/ui/src/core_plugins/counter/index.ts
+++ b/ui/src/plugins/dev.perfetto.Counter/index.ts
@@ -18,18 +18,14 @@
LONG_NULL,
NUM,
STR,
- LONG,
} from '../../trace_processor/query_result';
import {Trace} from '../../public/trace';
-import {Engine} from '../../trace_processor/engine';
import {COUNTER_TRACK_KIND} from '../../public/track_kinds';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
+import {PerfettoPlugin} from '../../public/plugin';
import {getThreadUriPrefix, getTrackName} from '../../public/utils';
import {CounterOptions} from '../../frontend/base_counter_track';
import {TraceProcessorCounterTrack} from './trace_processor_counter_track';
-import {CounterDetailsPanel} from './counter_details_panel';
-import {Time, duration, time} from '../../base/time';
-import {exists, Optional} from '../../base/utils';
+import {exists} from '../../base/utils';
import {TrackNode} from '../../public/workspace';
import {
getOrCreateGroupForProcess,
@@ -114,35 +110,8 @@
return options;
}
-async function getCounterEventBounds(
- engine: Engine,
- trackId: number,
- id: number,
-): Promise<Optional<{ts: time; dur: duration}>> {
- const query = `
- WITH CTE AS (
- SELECT
- id,
- ts as leftTs,
- LEAD(ts) OVER (ORDER BY ts) AS rightTs
- FROM counter
- WHERE track_id = ${trackId}
- )
- SELECT * FROM CTE WHERE id = ${id}
- `;
-
- const counter = await engine.query(query);
- const row = counter.iter({
- leftTs: LONG,
- rightTs: LONG_NULL,
- });
- const leftTs = Time.fromRaw(row.leftTs);
- const rightTs = row.rightTs !== null ? Time.fromRaw(row.rightTs) : leftTs;
- const duration = rightTs - leftTs;
- return {ts: leftTs, dur: duration};
-}
-
-class CounterPlugin implements PerfettoPlugin {
+export default class implements PerfettoPlugin {
+ static readonly id = 'dev.perfetto.Counter';
async onTraceLoad(ctx: Trace): Promise<void> {
await this.addCounterTracks(ctx);
await this.addGpuFrequencyTracks(ctx);
@@ -198,15 +167,12 @@
trace: ctx,
uri,
trackId,
+ trackName: title,
options: {
...getDefaultCounterOptions(title),
unit,
},
}),
- detailsPanel: new CounterDetailsPanel(ctx.engine, trackId, title),
- getEventBounds: async (id) => {
- return await getCounterEventBounds(ctx.engine, trackId, id);
- },
});
const track = new TrackNode({uri, title});
ctx.workspace.addChildInOrder(track);
@@ -280,12 +246,9 @@
trace: ctx,
uri,
trackId: trackId,
+ trackName: name,
options: getDefaultCounterOptions(name),
}),
- detailsPanel: new CounterDetailsPanel(ctx.engine, trackId, name),
- getEventBounds: async (id) => {
- return await getCounterEventBounds(ctx.engine, trackId, id);
- },
});
const trackNode = new TrackNode({uri, title: name, sortOrder: -20});
ctx.workspace.addChildInOrder(trackNode);
@@ -350,12 +313,9 @@
trace: ctx,
uri,
trackId: trackId,
+ trackName: name,
options: getDefaultCounterOptions(name),
}),
- detailsPanel: new CounterDetailsPanel(ctx.engine, trackId, name),
- getEventBounds: async (id) => {
- return await getCounterEventBounds(ctx.engine, trackId, id);
- },
});
const group = getOrCreateGroupForThread(ctx.workspace, utid);
const track = new TrackNode({uri, title: name, sortOrder: 30});
@@ -411,12 +371,9 @@
trace: ctx,
uri,
trackId: trackId,
+ trackName: name,
options: getDefaultCounterOptions(name),
}),
- detailsPanel: new CounterDetailsPanel(ctx.engine, trackId, name),
- getEventBounds: async (id) => {
- return await getCounterEventBounds(ctx.engine, trackId, id);
- },
});
const group = getOrCreateGroupForProcess(ctx.workspace, upid);
const track = new TrackNode({uri, title: name, sortOrder: 20});
@@ -426,49 +383,35 @@
private async addGpuFrequencyTracks(ctx: Trace) {
const engine = ctx.engine;
- const numGpus = ctx.traceInfo.gpuCount;
- for (let gpu = 0; gpu < numGpus; gpu++) {
- // Only add a gpu freq track if we have
- // gpu freq data.
- const freqExistsResult = await engine.query(`
- select id
- from gpu_counter_track
- join _counter_track_summary using (id)
- where name = 'gpufreq' and gpu_id = ${gpu}
- limit 1;
- `);
- if (freqExistsResult.numRows() > 0) {
- const trackId = freqExistsResult.firstRow({id: NUM}).id;
- const uri = `/gpu_frequency_${gpu}`;
- const name = `Gpu ${gpu} Frequency`;
- ctx.tracks.registerTrack({
+ const result = await engine.query(`
+ select id, gpu_id as gpuId
+ from gpu_counter_track
+ join _counter_track_summary using (id)
+ where name = 'gpufreq'
+ `);
+ const it = result.iter({id: NUM, gpuId: NUM});
+ for (; it.valid(); it.next()) {
+ const uri = `/gpu_frequency_${it.gpuId}`;
+ const name = `Gpu ${it.gpuId} Frequency`;
+ ctx.tracks.registerTrack({
+ uri,
+ title: name,
+ tags: {
+ kind: COUNTER_TRACK_KIND,
+ trackIds: [it.id],
+ scope: 'gpuFreq',
+ },
+ track: new TraceProcessorCounterTrack({
+ trace: ctx,
uri,
- title: name,
- tags: {
- kind: COUNTER_TRACK_KIND,
- trackIds: [trackId],
- scope: 'gpuFreq',
- },
- track: new TraceProcessorCounterTrack({
- trace: ctx,
- uri,
- trackId: trackId,
- options: getDefaultCounterOptions(name),
- }),
- detailsPanel: new CounterDetailsPanel(ctx.engine, trackId, name),
- getEventBounds: async (id) => {
- return await getCounterEventBounds(ctx.engine, trackId, id);
- },
- });
- const track = new TrackNode({uri, title: name, sortOrder: -20});
- ctx.workspace.addChildInOrder(track);
- }
+ trackId: it.id,
+ trackName: name,
+ options: getDefaultCounterOptions(name),
+ }),
+ });
+ const track = new TrackNode({uri, title: name, sortOrder: -20});
+ ctx.workspace.addChildInOrder(track);
}
}
}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'perfetto.Counter',
- plugin: CounterPlugin,
-};
diff --git a/ui/src/plugins/dev.perfetto.Counter/trace_processor_counter_track.ts b/ui/src/plugins/dev.perfetto.Counter/trace_processor_counter_track.ts
new file mode 100644
index 0000000..ddbdbcb
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.Counter/trace_processor_counter_track.ts
@@ -0,0 +1,115 @@
+// 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 {LONG, LONG_NULL, NUM} from '../../trace_processor/query_result';
+import {
+ BaseCounterTrack,
+ BaseCounterTrackArgs,
+} from '../../frontend/base_counter_track';
+
+import {TrackMouseEvent} from '../../public/track';
+import {TrackEventDetails} from '../../public/selection';
+import {Time} from '../../base/time';
+import {CounterDetailsPanel} from './counter_details_panel';
+
+interface TraceProcessorCounterTrackArgs extends BaseCounterTrackArgs {
+ readonly trackId: number;
+ readonly trackName: string;
+ readonly rootTable?: string;
+}
+
+export class TraceProcessorCounterTrack extends BaseCounterTrack {
+ private readonly trackId: number;
+ private readonly rootTable: string;
+ private readonly trackName: string;
+
+ constructor(args: TraceProcessorCounterTrackArgs) {
+ super(args);
+ this.trackId = args.trackId;
+ this.rootTable = args.rootTable ?? 'counter';
+ this.trackName = args.trackName;
+ }
+
+ getSqlSource() {
+ return `
+ select
+ id,
+ ts,
+ value
+ from ${this.rootTable}
+ where track_id = ${this.trackId}
+ `;
+ }
+
+ onMouseClick({x, timescale}: TrackMouseEvent): boolean {
+ const time = timescale.pxToHpTime(x).toTime('floor');
+
+ const query = `
+ select
+ id
+ from ${this.rootTable}
+ where
+ track_id = ${this.trackId}
+ and ts < ${time}
+ order by ts DESC
+ limit 1
+ `;
+
+ this.engine.query(query).then((result) => {
+ const it = result.iter({
+ id: NUM,
+ });
+ if (!it.valid()) {
+ return;
+ }
+ const id = it.id;
+ this.trace.selection.selectTrackEvent(this.uri, id);
+ });
+
+ return true;
+ }
+
+ // We must define this here instead of in `BaseCounterTrack` because
+ // `BaseCounterTrack` does not require the query to have an id column. Here,
+ // however, we make the assumption that `rootTable` has an id column, as we
+ // need it ot make selections in `onMouseClick` above. Whether or not we
+ // SHOULD assume `rootTable` has an id column is another matter...
+ async getSelectionDetails(id: number): Promise<TrackEventDetails> {
+ const query = `
+ WITH
+ CTE AS (
+ SELECT
+ id,
+ ts as leftTs,
+ LEAD(ts) OVER (ORDER BY ts) AS rightTs
+ FROM ${this.rootTable}
+ )
+ SELECT * FROM CTE WHERE id = ${id}
+ `;
+
+ const counter = await this.engine.query(query);
+ const row = counter.iter({
+ leftTs: LONG,
+ rightTs: LONG_NULL,
+ });
+ const leftTs = Time.fromRaw(row.leftTs);
+ const rightTs = row.rightTs !== null ? Time.fromRaw(row.rightTs) : leftTs;
+ const duration = rightTs - leftTs;
+ return {ts: leftTs, dur: duration};
+ }
+
+ detailsPanel() {
+ return new CounterDetailsPanel(this.trace, this.trackId, this.trackName);
+ }
+}
diff --git a/ui/src/core_plugins/cpu_freq/cpu_freq_track.ts b/ui/src/plugins/dev.perfetto.CpuFreq/cpu_freq_track.ts
similarity index 95%
rename from ui/src/core_plugins/cpu_freq/cpu_freq_track.ts
rename to ui/src/plugins/dev.perfetto.CpuFreq/cpu_freq_track.ts
index 64e1d01..9e59cf2 100644
--- a/ui/src/core_plugins/cpu_freq/cpu_freq_track.ts
+++ b/ui/src/plugins/dev.perfetto.CpuFreq/cpu_freq_track.ts
@@ -17,12 +17,10 @@
import {assertTrue} from '../../base/logging';
import {duration, time, Time} from '../../base/time';
import {drawTrackHoverTooltip} from '../../base/canvas_utils';
-import {colorForCpu} from '../../core/colorizer';
+import {colorForCpu} from '../../public/lib/colorizer';
import {TrackData} from '../../common/track_data';
import {TimelineFetcher} from '../../common/track_helper';
import {checkerboardExcept} from '../../frontend/checkerboard';
-import {globals} from '../../frontend/globals';
-import {Engine} from '../../trace_processor/engine';
import {Track} from '../../public/track';
import {LONG, NUM} from '../../trace_processor/query_result';
import {uuidv4Sql} from '../../base/uuid';
@@ -30,6 +28,7 @@
import {Point2D} from '../../base/geom';
import {createView, createVirtualTable} from '../../trace_processor/sql_utils';
import {AsyncDisposableStack} from '../../base/disposable_stack';
+import {Trace} from '../../public/trace';
export interface Data extends TrackData {
timestamps: BigInt64Array;
@@ -58,23 +57,21 @@
private hoveredIdle: number | undefined = undefined;
private fetcher = new TimelineFetcher<Data>(this.onBoundsChange.bind(this));
- private engine: Engine;
- private config: Config;
private trackUuid = uuidv4Sql();
private trash!: AsyncDisposableStack;
- constructor(config: Config, engine: Engine) {
- this.config = config;
- this.engine = engine;
- }
+ constructor(
+ private readonly config: Config,
+ private readonly trace: Trace,
+ ) {}
async onCreate() {
this.trash = new AsyncDisposableStack();
if (this.config.idleTrackId === undefined) {
this.trash.use(
await createView(
- this.engine,
+ this.trace.engine,
`raw_freq_idle_${this.trackUuid}`,
`
select ts, dur, value as freqValue, -1 as idleValue
@@ -86,7 +83,7 @@
} else {
this.trash.use(
await createView(
- this.engine,
+ this.trace.engine,
`raw_freq_${this.trackUuid}`,
`
select ts, dur, value as freqValue
@@ -98,7 +95,7 @@
this.trash.use(
await createView(
- this.engine,
+ this.trace.engine,
`raw_idle_${this.trackUuid}`,
`
select
@@ -113,7 +110,7 @@
this.trash.use(
await createVirtualTable(
- this.engine,
+ this.trace.engine,
`raw_freq_idle_${this.trackUuid}`,
`span_join(raw_freq_${this.trackUuid}, raw_idle_${this.trackUuid})`,
),
@@ -122,7 +119,7 @@
this.trash.use(
await createVirtualTable(
- this.engine,
+ this.trace.engine,
`cpu_freq_${this.trackUuid}`,
`
__intrinsic_counter_mipmap((
@@ -135,7 +132,7 @@
this.trash.use(
await createVirtualTable(
- this.engine,
+ this.trace.engine,
`cpu_idle_${this.trackUuid}`,
`
__intrinsic_counter_mipmap((
@@ -167,7 +164,7 @@
// function to make sense.
assertTrue(BIMath.popcount(resolution) === 1, `${resolution} not pow of 2`);
- const freqResult = await this.engine.query(`
+ const freqResult = await this.trace.engine.query(`
SELECT
min_value as minFreq,
max_value as maxFreq,
@@ -179,7 +176,7 @@
${resolution}
);
`);
- const idleResult = await this.engine.query(`
+ const idleResult = await this.trace.engine.query(`
SELECT last_value as lastIdle
FROM cpu_idle_${this.trackUuid}(
${start},
@@ -257,7 +254,7 @@
const color = colorForCpu(this.config.cpu);
let saturation = 45;
- if (globals.state.hoveredUtid !== -1) {
+ if (this.trace.timeline.hoveredUtid !== undefined) {
saturation = 0;
}
diff --git a/ui/src/core_plugins/cpu_freq/index.ts b/ui/src/plugins/dev.perfetto.CpuFreq/index.ts
similarity index 91%
rename from ui/src/core_plugins/cpu_freq/index.ts
rename to ui/src/plugins/dev.perfetto.CpuFreq/index.ts
index 00d9cce..bd7b96c 100644
--- a/ui/src/core_plugins/cpu_freq/index.ts
+++ b/ui/src/plugins/dev.perfetto.CpuFreq/index.ts
@@ -15,11 +15,12 @@
import {TrackNode} from '../../public/workspace';
import {CPU_FREQ_TRACK_KIND} from '../../public/track_kinds';
import {Trace} from '../../public/trace';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
+import {PerfettoPlugin} from '../../public/plugin';
import {NUM, NUM_NULL} from '../../trace_processor/query_result';
import {CpuFreqTrack} from './cpu_freq_track';
-class CpuFreq implements PerfettoPlugin {
+export default class implements PerfettoPlugin {
+ static readonly id = 'dev.perfetto.CpuFreq';
async onTraceLoad(ctx: Trace): Promise<void> {
const {engine} = ctx;
@@ -82,7 +83,7 @@
kind: CPU_FREQ_TRACK_KIND,
cpu,
},
- track: new CpuFreqTrack(config, ctx.engine),
+ track: new CpuFreqTrack(config, ctx),
});
const trackNode = new TrackNode({uri, title, sortOrder: -40});
ctx.workspace.addChildInOrder(trackNode);
@@ -90,8 +91,3 @@
}
}
}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'perfetto.CpuFreq',
- plugin: CpuFreq,
-};
diff --git a/ui/src/plugins/dev.perfetto.CpuProfile/cpu_profile_details_panel.ts b/ui/src/plugins/dev.perfetto.CpuProfile/cpu_profile_details_panel.ts
new file mode 100644
index 0000000..060746e
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.CpuProfile/cpu_profile_details_panel.ts
@@ -0,0 +1,107 @@
+// 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 {time} from '../../base/time';
+import {
+ metricsFromTableOrSubquery,
+ QueryFlamegraph,
+} from '../../public/lib/query_flamegraph';
+import {Timestamp} from '../../frontend/widgets/timestamp';
+import {
+ TrackEventDetailsPanel,
+ TrackEventDetailsPanelSerializeArgs,
+} from '../../public/details_panel';
+import {DetailsShell} from '../../widgets/details_shell';
+import {Trace} from '../../public/trace';
+import {
+ Flamegraph,
+ FLAMEGRAPH_STATE_SCHEMA,
+ FlamegraphState,
+} from '../../widgets/flamegraph';
+
+export class CpuProfileSampleFlamegraphDetailsPanel
+ implements TrackEventDetailsPanel
+{
+ private readonly flamegraph: QueryFlamegraph;
+ readonly serialization: TrackEventDetailsPanelSerializeArgs<FlamegraphState>;
+
+ constructor(
+ trace: Trace,
+ private ts: time,
+ utid: number,
+ ) {
+ const metrics = metricsFromTableOrSubquery(
+ `
+ (
+ select
+ id,
+ parent_id as parentId,
+ name,
+ mapping_name,
+ source_file,
+ cast(line_number AS text) as line_number,
+ self_count
+ from _callstacks_for_callsites!((
+ select p.callsite_id
+ from cpu_profile_stack_sample p
+ where p.ts = ${ts} and p.utid = ${utid}
+ ))
+ )
+ `,
+ [
+ {
+ name: 'CPU Profile Samples',
+ unit: '',
+ columnName: 'self_count',
+ },
+ ],
+ 'include perfetto module callstacks.stack_profile',
+ [{name: 'mapping_name', displayName: 'Mapping'}],
+ [
+ {
+ name: 'source_file',
+ displayName: 'Source File',
+ mergeAggregation: 'ONE_OR_NULL',
+ },
+ {
+ name: 'line_number',
+ displayName: 'Line Number',
+ mergeAggregation: 'ONE_OR_NULL',
+ },
+ ],
+ );
+ this.serialization = {
+ schema: FLAMEGRAPH_STATE_SCHEMA,
+ state: Flamegraph.createDefaultState(metrics),
+ };
+ this.flamegraph = new QueryFlamegraph(trace, metrics, this.serialization);
+ }
+
+ render() {
+ return m(
+ '.flamegraph-profile',
+ m(
+ DetailsShell,
+ {
+ fillParent: true,
+ title: m('.title', 'CPU Profile Samples'),
+ description: [],
+ buttons: [m('div.time', `Timestamp: `, m(Timestamp, {ts: this.ts}))],
+ },
+ this.flamegraph.render(),
+ ),
+ );
+ }
+}
diff --git a/ui/src/core_plugins/cpu_profile/cpu_profile_track.ts b/ui/src/plugins/dev.perfetto.CpuProfile/cpu_profile_track.ts
similarity index 73%
rename from ui/src/core_plugins/cpu_profile/cpu_profile_track.ts
rename to ui/src/plugins/dev.perfetto.CpuProfile/cpu_profile_track.ts
index 5f23f7a..4baeddf 100644
--- a/ui/src/core_plugins/cpu_profile/cpu_profile_track.ts
+++ b/ui/src/plugins/dev.perfetto.CpuProfile/cpu_profile_track.ts
@@ -13,18 +13,17 @@
// limitations under the License.
import {assertExists} from '../../base/logging';
-import {Time} from '../../base/time';
-import {LegacySelection} from '../../public/selection';
-import {getColorForSample} from '../../core/colorizer';
+import {TrackEventDetails, TrackEventSelection} from '../../public/selection';
+import {getColorForSample} from '../../public/lib/colorizer';
import {
BaseSliceTrack,
OnSliceClickArgs,
} from '../../frontend/base_slice_track';
-import {globals} from '../../frontend/globals';
import {NAMED_ROW, NamedRow} from '../../frontend/named_slice_track';
import {NewTrackArgs} from '../../frontend/track';
import {NUM} from '../../trace_processor/query_result';
import {Slice} from '../../public/track';
+import {CpuProfileSampleFlamegraphDetailsPanel} from './cpu_profile_details_panel';
interface CpuProfileRow extends NamedRow {
callsiteId: number;
@@ -49,10 +48,6 @@
return {...baseSlice, title: name, colorScheme};
}
- isSelectionHandled(selection: LegacySelection): boolean {
- return selection.kind === 'CPU_PROFILE_SAMPLE';
- }
-
onUpdatedSlices(slices: Slice[]) {
for (const slice of slices) {
slice.isHighlighted = slice === this.hoveredSlice;
@@ -75,11 +70,23 @@
}
onSliceClick({slice}: OnSliceClickArgs<Slice>) {
- globals.selectionManager.selectLegacy({
- kind: 'CPU_PROFILE_SAMPLE',
- id: slice.id,
- utid: this.utid,
- ts: Time.fromRaw(slice.ts),
- });
+ this.trace.selection.selectTrackEvent(this.uri, slice.id);
+ }
+
+ async getSelectionDetails(
+ id: number,
+ ): Promise<TrackEventDetails | undefined> {
+ const baseDetails = await super.getSelectionDetails(id);
+ if (baseDetails === undefined) return undefined;
+ return {...baseDetails, utid: this.utid};
+ }
+
+ detailsPanel(selection: TrackEventSelection) {
+ const {ts, utid} = selection;
+ return new CpuProfileSampleFlamegraphDetailsPanel(
+ this.trace,
+ ts,
+ assertExists(utid),
+ );
}
}
diff --git a/ui/src/plugins/dev.perfetto.CpuProfile/index.ts b/ui/src/plugins/dev.perfetto.CpuProfile/index.ts
new file mode 100644
index 0000000..31e49fe
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.CpuProfile/index.ts
@@ -0,0 +1,76 @@
+// 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.
+
+import {CPU_PROFILE_TRACK_KIND} from '../../public/track_kinds';
+import {Trace} from '../../public/trace';
+import {PerfettoPlugin} from '../../public/plugin';
+import {NUM, NUM_NULL, STR_NULL} from '../../trace_processor/query_result';
+import {CpuProfileTrack} from './cpu_profile_track';
+import {getThreadUriPrefix} from '../../public/utils';
+import {exists} from '../../base/utils';
+import {getOrCreateGroupForThread} from '../../public/standard_groups';
+import {TrackNode} from '../../public/workspace';
+
+export default class implements PerfettoPlugin {
+ static readonly id = 'dev.perfetto.CpuProfile';
+ async onTraceLoad(ctx: Trace): Promise<void> {
+ const result = await ctx.engine.query(`
+ with thread_cpu_sample as (
+ select distinct utid
+ from cpu_profile_stack_sample
+ where utid != 0
+ )
+ select
+ utid,
+ tid,
+ upid,
+ thread.name as threadName
+ from thread_cpu_sample
+ join thread using(utid)
+ `);
+
+ const it = result.iter({
+ utid: NUM,
+ upid: NUM_NULL,
+ tid: NUM_NULL,
+ threadName: STR_NULL,
+ });
+ for (; it.valid(); it.next()) {
+ const utid = it.utid;
+ const upid = it.upid;
+ const threadName = it.threadName;
+ const uri = `${getThreadUriPrefix(upid, utid)}_cpu_samples`;
+ const title = `${threadName} (CPU Stack Samples)`;
+ ctx.tracks.registerTrack({
+ uri,
+ title,
+ tags: {
+ kind: CPU_PROFILE_TRACK_KIND,
+ utid,
+ ...(exists(upid) && {upid}),
+ },
+ track: new CpuProfileTrack(
+ {
+ trace: ctx,
+ uri,
+ },
+ utid,
+ ),
+ });
+ const group = getOrCreateGroupForThread(ctx.workspace, utid);
+ const track = new TrackNode({uri, title, sortOrder: -40});
+ group.addChildInOrder(track);
+ }
+ }
+}
diff --git a/ui/src/core_plugins/cpu_slices/cpu_slice_by_process_selection_aggregator.ts b/ui/src/plugins/dev.perfetto.CpuSlices/cpu_slice_by_process_selection_aggregator.ts
similarity index 100%
rename from ui/src/core_plugins/cpu_slices/cpu_slice_by_process_selection_aggregator.ts
rename to ui/src/plugins/dev.perfetto.CpuSlices/cpu_slice_by_process_selection_aggregator.ts
diff --git a/ui/src/core_plugins/cpu_slices/cpu_slice_selection_aggregator.ts b/ui/src/plugins/dev.perfetto.CpuSlices/cpu_slice_selection_aggregator.ts
similarity index 100%
rename from ui/src/core_plugins/cpu_slices/cpu_slice_selection_aggregator.ts
rename to ui/src/plugins/dev.perfetto.CpuSlices/cpu_slice_selection_aggregator.ts
diff --git a/ui/src/core_plugins/cpu_slices/cpu_slice_track.ts b/ui/src/plugins/dev.perfetto.CpuSlices/cpu_slice_track.ts
similarity index 69%
rename from ui/src/core_plugins/cpu_slices/cpu_slice_track.ts
rename to ui/src/plugins/dev.perfetto.CpuSlices/cpu_slice_track.ts
index d6d30e3..2c5b456 100644
--- a/ui/src/core_plugins/cpu_slices/cpu_slice_track.ts
+++ b/ui/src/plugins/dev.perfetto.CpuSlices/cpu_slice_track.ts
@@ -16,7 +16,6 @@
import {search, searchEq, searchSegment} from '../../base/binary_search';
import {assertExists, assertTrue} from '../../base/logging';
import {Duration, duration, Time, time} from '../../base/time';
-import {Actions} from '../../common/actions';
import {
drawDoubleHeadedArrow,
drawIncompleteSlice,
@@ -24,17 +23,25 @@
} from '../../base/canvas_utils';
import {cropText} from '../../base/string_utils';
import {Color} from '../../public/color';
-import {colorForThread} from '../../core/colorizer';
+import {colorForThread} from '../../public/lib/colorizer';
import {TrackData} from '../../common/track_data';
import {TimelineFetcher} from '../../common/track_helper';
import {checkerboardExcept} from '../../frontend/checkerboard';
-import {globals} from '../../frontend/globals';
import {Point2D} from '../../base/geom';
-import {Engine} from '../../trace_processor/engine';
import {Track} from '../../public/track';
import {LONG, NUM} from '../../trace_processor/query_result';
import {uuidv4Sql} from '../../base/uuid';
import {TrackMouseEvent, TrackRenderContext} from '../../public/track';
+import {TrackEventDetails} from '../../public/selection';
+import {asSchedSqlId} from '../../trace_processor/sql_utils/core_types';
+import {
+ getSched,
+ getSchedWakeupInfo,
+} from '../../trace_processor/sql_utils/sched';
+import {SchedSliceDetailsPanel} from './sched_details_tab';
+import {Trace} from '../../public/trace';
+import {exists} from '../../base/utils';
+import {ThreadMap} from '../dev.perfetto.Thread/threads';
export interface Data extends TrackData {
// Slices are stored in a columnar fashion. All fields have the same length.
@@ -55,23 +62,21 @@
export class CpuSliceTrack implements Track {
private mousePos?: Point2D;
- private utidHoveredInThisTrack = -1;
+ private utidHoveredInThisTrack?: number;
private fetcher = new TimelineFetcher<Data>(this.onBoundsChange.bind(this));
private lastRowId = -1;
- private engine: Engine;
- private cpu: number;
- private uri: string;
private trackUuid = uuidv4Sql();
- constructor(engine: Engine, uri: string, cpu: number) {
- this.engine = engine;
- this.uri = uri;
- this.cpu = cpu;
- }
+ constructor(
+ private readonly trace: Trace,
+ private readonly uri: string,
+ private readonly cpu: number,
+ private readonly threads: ThreadMap,
+ ) {}
async onCreate() {
- await this.engine.query(`
+ await this.trace.engine.query(`
create virtual table cpu_slice_${this.trackUuid}
using __intrinsic_slice_mipmap((
select
@@ -83,7 +88,7 @@
where cpu = ${this.cpu} and utid != 0
));
`);
- const it = await this.engine.query(`
+ const it = await this.trace.engine.query(`
select coalesce(max(id), -1) as lastRowId
from sched
where cpu = ${this.cpu} and utid != 0
@@ -105,7 +110,7 @@
): Promise<Data> {
assertTrue(BIMath.popcount(resolution) === 1, `${resolution} not pow of 2`);
- const queryRes = await this.engine.query(`
+ const queryRes = await this.trace.engine.query(`
select
(z.ts / ${resolution}) * ${resolution} as tsQ,
(((z.ts + z.dur) / ${resolution}) + 1) * ${resolution} as tsEndQ,
@@ -157,7 +162,7 @@
}
async onDestroy() {
- await this.engine.tryQuery(
+ await this.trace.engine.tryQuery(
`drop table if exists cpu_slice_${this.trackUuid}`,
);
this.fetcher[Symbol.dispose]();
@@ -231,13 +236,13 @@
const rectEnd = timescale.timeToPx(tEnd);
const rectWidth = Math.max(1, rectEnd - rectStart);
- const threadInfo = globals.threads.get(utid);
+ const threadInfo = this.threads.get(utid);
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
const pid = threadInfo && threadInfo.pid ? threadInfo.pid : -1;
- const isHovering = globals.state.hoveredUtid !== -1;
- const isThreadHovered = globals.state.hoveredUtid === utid;
- const isProcessHovered = globals.state.hoveredPid === pid;
+ const isHovering = this.trace.timeline.hoveredUtid !== undefined;
+ const isThreadHovered = this.trace.timeline.hoveredUtid === utid;
+ const isProcessHovered = this.trace.timeline.hoveredPid === pid;
const colorScheme = colorForThread(threadInfo);
let color: Color;
let textColor: Color;
@@ -307,62 +312,68 @@
ctx.fillText(subTitle, rectXCenter, MARGIN_TOP + RECT_HEIGHT / 2 + 9);
}
- const selection = globals.selectionManager.legacySelection;
- const details = globals.selectionManager.legacySelectionDetails;
- if (selection !== null && selection.kind === 'SCHED_SLICE') {
- const [startIndex, endIndex] = searchEq(data.ids, selection.id);
- if (startIndex !== endIndex) {
- const tStart = Time.fromRaw(data.startQs[startIndex]);
- const tEnd = Time.fromRaw(data.endQs[startIndex]);
- const utid = data.utids[startIndex];
- const color = colorForThread(globals.threads.get(utid));
- const rectStart = timescale.timeToPx(tStart);
- const rectEnd = timescale.timeToPx(tEnd);
- const rectWidth = Math.max(1, rectEnd - rectStart);
+ const selection = this.trace.selection.selection;
+ if (selection.kind === 'track_event') {
+ if (selection.trackUri === this.uri) {
+ const [startIndex, endIndex] = searchEq(data.ids, selection.eventId);
+ if (startIndex !== endIndex) {
+ const tStart = Time.fromRaw(data.startQs[startIndex]);
+ const tEnd = Time.fromRaw(data.endQs[startIndex]);
+ const utid = data.utids[startIndex];
+ const color = colorForThread(this.threads.get(utid));
+ const rectStart = timescale.timeToPx(tStart);
+ const rectEnd = timescale.timeToPx(tEnd);
+ const rectWidth = Math.max(1, rectEnd - rectStart);
- // Draw a rectangle around the slice that is currently selected.
- ctx.strokeStyle = color.base.setHSL({l: 30}).cssString;
- ctx.beginPath();
- ctx.lineWidth = 3;
- ctx.strokeRect(rectStart, MARGIN_TOP - 1.5, rectWidth, RECT_HEIGHT + 3);
- ctx.closePath();
- // Draw arrow from wakeup time of current slice.
- if (details?.wakeupTs) {
- const wakeupPos = timescale.timeToPx(details.wakeupTs);
- const latencyWidth = rectStart - wakeupPos;
- drawDoubleHeadedArrow(
- ctx,
- wakeupPos,
- MARGIN_TOP + RECT_HEIGHT,
- latencyWidth,
- latencyWidth >= 20,
+ // Draw a rectangle around the slice that is currently selected.
+ ctx.strokeStyle = color.base.setHSL({l: 30}).cssString;
+ ctx.beginPath();
+ ctx.lineWidth = 3;
+ ctx.strokeRect(
+ rectStart,
+ MARGIN_TOP - 1.5,
+ rectWidth,
+ RECT_HEIGHT + 3,
);
- // Latency time with a white semi-transparent background.
- const latency = tStart - details.wakeupTs;
- const displayText = Duration.humanise(latency);
- const measured = ctx.measureText(displayText);
- if (latencyWidth >= measured.width + 2) {
- ctx.fillStyle = 'rgba(255,255,255,0.7)';
- ctx.fillRect(
- wakeupPos + latencyWidth / 2 - measured.width / 2 - 1,
- MARGIN_TOP + RECT_HEIGHT - 12,
- measured.width + 2,
- 11,
+ ctx.closePath();
+ // Draw arrow from wakeup time of current slice.
+ if (selection.wakeupTs) {
+ const wakeupPos = timescale.timeToPx(selection.wakeupTs);
+ const latencyWidth = rectStart - wakeupPos;
+ drawDoubleHeadedArrow(
+ ctx,
+ wakeupPos,
+ MARGIN_TOP + RECT_HEIGHT,
+ latencyWidth,
+ latencyWidth >= 20,
);
- ctx.textBaseline = 'bottom';
- ctx.fillStyle = 'black';
- ctx.fillText(
- displayText,
- wakeupPos + latencyWidth / 2,
- MARGIN_TOP + RECT_HEIGHT - 1,
- );
+ // Latency time with a white semi-transparent background.
+ const latency = tStart - selection.wakeupTs;
+ const displayText = Duration.humanise(latency);
+ const measured = ctx.measureText(displayText);
+ if (latencyWidth >= measured.width + 2) {
+ ctx.fillStyle = 'rgba(255,255,255,0.7)';
+ ctx.fillRect(
+ wakeupPos + latencyWidth / 2 - measured.width / 2 - 1,
+ MARGIN_TOP + RECT_HEIGHT - 12,
+ measured.width + 2,
+ 11,
+ );
+ ctx.textBaseline = 'bottom';
+ ctx.fillStyle = 'black';
+ ctx.fillText(
+ displayText,
+ wakeupPos + latencyWidth / 2,
+ MARGIN_TOP + RECT_HEIGHT - 1,
+ );
+ }
}
}
}
// Draw diamond if the track being drawn is the cpu of the waker.
- if (details && this.cpu === details.wakerCpu && details.wakeupTs) {
- const wakeupPos = Math.floor(timescale.timeToPx(details.wakeupTs));
+ if (this.cpu === selection.wakerCpu && selection.wakeupTs) {
+ const wakeupPos = Math.floor(timescale.timeToPx(selection.wakeupTs));
ctx.beginPath();
ctx.moveTo(wakeupPos, MARGIN_TOP + RECT_HEIGHT / 2 + 8);
ctx.fillStyle = 'black';
@@ -372,19 +383,21 @@
ctx.fill();
ctx.closePath();
}
- }
- const hoveredThread = globals.threads.get(this.utidHoveredInThisTrack);
- if (hoveredThread !== undefined && this.mousePos !== undefined) {
- const tidText = `T: ${hoveredThread.threadName}
- [${hoveredThread.tid}]`;
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
- if (hoveredThread.pid) {
- const pidText = `P: ${hoveredThread.procName}
- [${hoveredThread.pid}]`;
- drawTrackHoverTooltip(ctx, this.mousePos, size, pidText, tidText);
- } else {
- drawTrackHoverTooltip(ctx, this.mousePos, size, tidText);
+ if (this.utidHoveredInThisTrack !== undefined) {
+ const hoveredThread = this.threads.get(this.utidHoveredInThisTrack);
+ if (hoveredThread && this.mousePos !== undefined) {
+ const tidText = `T: ${hoveredThread.threadName}
+ [${hoveredThread.tid}]`;
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
+ if (hoveredThread.pid) {
+ const pidText = `P: ${hoveredThread.procName}
+ [${hoveredThread.pid}]`;
+ drawTrackHoverTooltip(ctx, this.mousePos, size, pidText, tidText);
+ } else {
+ drawTrackHoverTooltip(ctx, this.mousePos, size, tidText);
+ }
+ }
}
}
}
@@ -394,12 +407,13 @@
this.mousePos = {x, y};
if (data === undefined) return;
if (y < MARGIN_TOP || y > MARGIN_TOP + RECT_HEIGHT) {
- this.utidHoveredInThisTrack = -1;
- globals.dispatch(Actions.setHoveredUtidAndPid({utid: -1, pid: -1}));
+ this.utidHoveredInThisTrack = undefined;
+ this.trace.timeline.hoveredUtid = undefined;
+ this.trace.timeline.hoveredPid = undefined;
return;
}
const t = timescale.pxToHpTime(x);
- let hoveredUtid = -1;
+ let hoveredUtid = undefined;
for (let i = 0; i < data.startQs.length; i++) {
const tStart = Time.fromRaw(data.startQs[i]);
@@ -411,17 +425,17 @@
}
}
this.utidHoveredInThisTrack = hoveredUtid;
- const threadInfo = globals.threads.get(hoveredUtid);
+ const threadInfo = exists(hoveredUtid) && this.threads.get(hoveredUtid);
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
const hoveredPid = threadInfo ? (threadInfo.pid ? threadInfo.pid : -1) : -1;
- globals.dispatch(
- Actions.setHoveredUtidAndPid({utid: hoveredUtid, pid: hoveredPid}),
- );
+ this.trace.timeline.hoveredUtid = hoveredUtid;
+ this.trace.timeline.hoveredPid = hoveredPid;
}
onMouseOut() {
this.utidHoveredInThisTrack = -1;
- globals.dispatch(Actions.setHoveredUtidAndPid({utid: -1, pid: -1}));
+ this.trace.timeline.hoveredUtid = undefined;
+ this.trace.timeline.hoveredPid = undefined;
this.mousePos = undefined;
}
@@ -434,14 +448,29 @@
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
if (!id || this.utidHoveredInThisTrack === -1) return false;
- globals.selectionManager.selectLegacy({
- kind: 'SCHED_SLICE',
- id,
- trackUri: this.uri,
- });
-
+ this.trace.selection.selectTrackEvent(this.uri, id);
return true;
}
+
+ async getSelectionDetails?(
+ eventId: number,
+ ): Promise<TrackEventDetails | undefined> {
+ const sched = await getSched(this.trace.engine, asSchedSqlId(eventId));
+ if (sched === undefined) {
+ return undefined;
+ }
+ const wakeup = await getSchedWakeupInfo(this.trace.engine, sched);
+ return {
+ ts: sched.ts,
+ dur: sched.dur,
+ wakeupTs: wakeup?.wakeupTs,
+ wakerCpu: wakeup?.wakerCpu,
+ };
+ }
+
+ detailsPanel() {
+ return new SchedSliceDetailsPanel(this.trace, this.threads);
+ }
}
// Creates a diagonal hatched pattern to be used for distinguishing slices with
diff --git a/ui/src/core_plugins/cpu_slices/index.ts b/ui/src/plugins/dev.perfetto.CpuSlices/index.ts
similarity index 74%
rename from ui/src/core_plugins/cpu_slices/index.ts
rename to ui/src/plugins/dev.perfetto.CpuSlices/index.ts
index 655bf17..4e3f3d3 100644
--- a/ui/src/core_plugins/cpu_slices/index.ts
+++ b/ui/src/plugins/dev.perfetto.CpuSlices/index.ts
@@ -13,24 +13,24 @@
// limitations under the License.
import {CPU_SLICE_TRACK_KIND} from '../../public/track_kinds';
-import {SchedDetailsTab} from './sched_details_tab';
import {Engine} from '../../trace_processor/engine';
import {Trace} from '../../public/trace';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
+import {PerfettoPlugin} from '../../public/plugin';
import {NUM, STR_NULL} from '../../trace_processor/query_result';
import {CpuSliceTrack} from './cpu_slice_track';
import {TrackNode} from '../../public/workspace';
-import {BottomTabToSCSAdapter} from '../../public/utils';
-import {uuidv4} from '../../base/uuid';
-import {asSchedSqlId} from '../../trace_processor/sql_utils/core_types';
import {CpuSliceSelectionAggregator} from './cpu_slice_selection_aggregator';
import {CpuSliceByProcessSelectionAggregator} from './cpu_slice_by_process_selection_aggregator';
+import ThreadPlugin from '../dev.perfetto.Thread';
function uriForSchedTrack(cpu: number): string {
return `/sched_cpu${cpu}`;
}
-class CpuSlices implements PerfettoPlugin {
+export default class implements PerfettoPlugin {
+ static readonly id = 'dev.perfetto.CpuSlices';
+ static readonly dependencies = [ThreadPlugin];
+
async onTraceLoad(ctx: Trace): Promise<void> {
ctx.selection.registerAreaSelectionAggreagtor(
new CpuSliceSelectionAggregator(),
@@ -46,6 +46,8 @@
const size = cpuToClusterType.get(cpu);
const uri = uriForSchedTrack(cpu);
+ const threads = ctx.plugins.getPlugin(ThreadPlugin).getThreadMap();
+
const name = size === undefined ? `Cpu ${cpu}` : `Cpu ${cpu} (${size})`;
ctx.tracks.registerTrack({
uri,
@@ -54,29 +56,12 @@
kind: CPU_SLICE_TRACK_KIND,
cpu,
},
- track: new CpuSliceTrack(ctx.engine, uri, cpu),
+ track: new CpuSliceTrack(ctx, uri, cpu, threads),
});
const trackNode = new TrackNode({uri, title: name, sortOrder: -50});
ctx.workspace.addChildInOrder(trackNode);
}
- ctx.tabs.registerDetailsPanel(
- new BottomTabToSCSAdapter({
- tabFactory: (sel) => {
- if (sel.kind !== 'SCHED_SLICE') {
- return undefined;
- }
- return new SchedDetailsTab({
- config: {
- id: asSchedSqlId(sel.id),
- },
- trace: ctx,
- uuid: uuidv4(),
- });
- },
- }),
- );
-
ctx.selection.registerSqlSelectionResolver({
sqlTableName: 'sched_slice',
callback: async (id: number) => {
@@ -92,12 +77,8 @@
}).cpu;
return {
- kind: 'legacy',
- legacySelection: {
- kind: 'SCHED_SLICE',
- id,
- trackUri: uriForSchedTrack(cpu),
- },
+ eventId: id,
+ trackUri: uriForSchedTrack(cpu),
};
},
});
@@ -130,8 +111,3 @@
return cpuToClusterType;
}
}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'perfetto.CpuSlices',
- plugin: CpuSlices,
-};
diff --git a/ui/src/core_plugins/cpu_slices/sched_details_tab.ts b/ui/src/plugins/dev.perfetto.CpuSlices/sched_details_tab.ts
similarity index 77%
rename from ui/src/core_plugins/cpu_slices/sched_details_tab.ts
rename to ui/src/plugins/dev.perfetto.CpuSlices/sched_details_tab.ts
index 48f1be1..f4b3b93 100644
--- a/ui/src/core_plugins/cpu_slices/sched_details_tab.ts
+++ b/ui/src/plugins/dev.perfetto.CpuSlices/sched_details_tab.ts
@@ -19,13 +19,9 @@
import {Section} from '../../widgets/section';
import {SqlRef} from '../../widgets/sql_ref';
import {Tree, TreeNode} from '../../widgets/tree';
-import {globals, ThreadDesc} from '../../frontend/globals';
import {DurationWidget} from '../../frontend/widgets/duration';
import {Timestamp} from '../../frontend/widgets/timestamp';
-import {THREAD_STATE_TRACK_KIND} from '../../public/track_kinds';
-import {scrollTo} from '../../public/scroll_helper';
-import {SchedSqlId} from '../../trace_processor/sql_utils/core_types';
-import {BottomTab, NewBottomTabArgs} from '../../public/lib/bottom_tab';
+import {asSchedSqlId} from '../../trace_processor/sql_utils/core_types';
import {
getSched,
getSchedWakeupInfo,
@@ -33,15 +29,15 @@
SchedWakeupInfo,
} from '../../trace_processor/sql_utils/sched';
import {exists} from '../../base/utils';
-import {raf} from '../../core/raf_scheduler';
import {translateState} from '../../trace_processor/sql_utils/thread_state';
+import {Trace} from '../../public/trace';
+import {TrackEventDetailsPanel} from '../../public/details_panel';
+import {TrackEventSelection} from '../../public/selection';
+import {ThreadDesc, ThreadMap} from '../dev.perfetto.Thread/threads';
+import {assetSrc} from '../../base/assets';
const MIN_NORMAL_SCHED_PRIORITY = 100;
-interface SchedDetailsTabConfig {
- id: SchedSqlId;
-}
-
function getDisplayName(
name: string | undefined,
id: number | undefined,
@@ -58,44 +54,29 @@
wakeup?: SchedWakeupInfo;
}
-export class SchedDetailsTab extends BottomTab<SchedDetailsTabConfig> {
+export class SchedSliceDetailsPanel implements TrackEventDetailsPanel {
private details?: Data;
- static create(
- args: NewBottomTabArgs<SchedDetailsTabConfig>,
- ): SchedDetailsTab {
- return new SchedDetailsTab(args);
- }
+ constructor(
+ private readonly trace: Trace,
+ private readonly threads: ThreadMap,
+ ) {}
- constructor(args: NewBottomTabArgs<SchedDetailsTabConfig>) {
- super(args);
-
- this.load();
- }
-
- async load() {
- const sched = await getSched(this.engine, this.config.id);
+ async load({eventId}: TrackEventSelection) {
+ const sched = await getSched(this.trace.engine, asSchedSqlId(eventId));
if (sched === undefined) {
return;
}
- const wakeup = await getSchedWakeupInfo(this.engine, sched);
+ const wakeup = await getSchedWakeupInfo(this.trace.engine, sched);
this.details = {sched, wakeup};
- raf.scheduleRedraw();
+ this.trace.scheduleFullRedraw();
}
- override isLoading() {
- return this.details === undefined;
- }
-
- getTitle(): string {
- return `Sched ${this.config.id}`;
- }
-
- viewTab() {
+ render() {
if (this.details === undefined) {
return m(DetailsShell, {title: 'Sched', description: 'Loading...'});
}
- const threadInfo = globals.threads.get(this.details.sched.thread.utid);
+ const threadInfo = this.threads.get(this.details.sched.thread.utid);
return m(
DetailsShell,
@@ -112,7 +93,7 @@
}
private renderTitle(data: Data) {
- const threadInfo = globals.threads.get(data.sched.thread.utid);
+ const threadInfo = this.threads.get(data.sched.thread.utid);
if (!threadInfo) {
return null;
}
@@ -132,7 +113,7 @@
m(
'.slice-details-latency-panel',
m('img.slice-details-image', {
- src: `${globals.root}assets/scheduling_latency.png`,
+ src: assetSrc('assets/scheduling_latency.png'),
}),
this.renderWakeupText(data),
this.renderDisplayLatencyText(data),
@@ -148,7 +129,7 @@
) {
return null;
}
- const threadInfo = globals.threads.get(data.wakeup.wakerUtid);
+ const threadInfo = this.threads.get(data.wakeup.wakerUtid);
if (!threadInfo) {
return null;
}
@@ -270,27 +251,12 @@
}
goToThread(data: Data) {
- const threadInfo = globals.threads.get(data.sched.thread.utid);
-
- if (threadInfo === undefined) {
- return;
- }
-
- const trackDescriptor = globals.trackManager.findTrack(
- (td) =>
- td.tags?.kind === THREAD_STATE_TRACK_KIND &&
- td.tags?.utid === threadInfo.utid,
- );
-
- if (trackDescriptor && data.sched.threadStateId) {
- globals.selectionManager.selectSqlEvent(
+ if (data.sched.threadStateId) {
+ this.trace.selection.selectSqlEvent(
'thread_state',
data.sched.threadStateId,
+ {scrollToSelection: true},
);
- scrollTo({
- track: {uri: trackDescriptor.uri, expandGroup: true},
- time: {start: data.sched.ts},
- });
}
}
diff --git a/ui/src/plugins/dev.perfetto.CpuidleTimeInState/OWNERS b/ui/src/plugins/dev.perfetto.CpuidleTimeInState/OWNERS
new file mode 100644
index 0000000..4dd7964
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.CpuidleTimeInState/OWNERS
@@ -0,0 +1 @@
+zhaon@google.com
\ No newline at end of file
diff --git a/ui/src/plugins/dev.perfetto.CpuidleTimeInState/index.ts b/ui/src/plugins/dev.perfetto.CpuidleTimeInState/index.ts
new file mode 100644
index 0000000..3e4aaa1
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.CpuidleTimeInState/index.ts
@@ -0,0 +1,81 @@
+// 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 {Trace} from '../../public/trace';
+import {PerfettoPlugin} from '../../public/plugin';
+import {CounterOptions} from '../../frontend/base_counter_track';
+import {TrackNode} from '../../public/workspace';
+import {createQueryCounterTrack} from '../../public/lib/tracks/query_counter_track';
+
+export default class implements PerfettoPlugin {
+ static readonly id = 'dev.perfetto.CpuidleTimeInState';
+ private async addCounterTrack(
+ ctx: Trace,
+ name: string,
+ query: string,
+ group?: TrackNode,
+ options?: Partial<CounterOptions>,
+ ) {
+ const uri = `/cpuidle_time_in_state_${name}`;
+ const track = await createQueryCounterTrack({
+ trace: ctx,
+ uri,
+ data: {
+ sqlSource: query,
+ columns: ['ts', 'value'],
+ },
+ columns: {ts: 'ts', value: 'value'},
+ options,
+ });
+ ctx.tracks.registerTrack({
+ uri,
+ title: name,
+ track,
+ });
+ const trackNode = new TrackNode({uri, title: name});
+ if (group) {
+ group.addChildInOrder(trackNode);
+ }
+ }
+
+ async onTraceLoad(ctx: Trace): Promise<void> {
+ const group = new TrackNode({
+ title: 'Cpuidle Time In State',
+ isSummary: true,
+ });
+
+ const e = ctx.engine;
+ await e.query(`INCLUDE PERFETTO MODULE linux.cpu.idle_time_in_state;`);
+ const result = await e.query(
+ `select distinct state_name from cpu_idle_time_in_state_counters`,
+ );
+ const it = result.iter({state_name: 'str'});
+ for (; it.valid(); it.next()) {
+ this.addCounterTrack(
+ ctx,
+ it.state_name,
+ `select
+ ts,
+ idle_percentage as value
+ from cpu_idle_time_in_state_counters
+ where state_name='${it.state_name}'`,
+ group,
+ {unit: 'percent'},
+ );
+ }
+ if (group.hasChildren) {
+ ctx.workspace.addChildInOrder(group);
+ }
+ }
+}
diff --git a/ui/src/core_plugins/critical_path/OWNERS b/ui/src/plugins/dev.perfetto.CriticalPath/OWNERS
similarity index 100%
rename from ui/src/core_plugins/critical_path/OWNERS
rename to ui/src/plugins/dev.perfetto.CriticalPath/OWNERS
diff --git a/ui/src/core_plugins/critical_path/index.ts b/ui/src/plugins/dev.perfetto.CriticalPath/index.ts
similarity index 85%
rename from ui/src/core_plugins/critical_path/index.ts
rename to ui/src/plugins/dev.perfetto.CriticalPath/index.ts
index d9770a8..94acec3 100644
--- a/ui/src/core_plugins/critical_path/index.ts
+++ b/ui/src/plugins/dev.perfetto.CriticalPath/index.ts
@@ -19,11 +19,10 @@
import {addDebugSliceTrack} from '../../public/debug_tracks';
import {Trace} from '../../public/trace';
import {THREAD_STATE_TRACK_KIND} from '../../public/track_kinds';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
+import {PerfettoPlugin} from '../../public/plugin';
import {asUtid, Utid} from '../../trace_processor/sql_utils/core_types';
import {addQueryResultsTab} from '../../public/lib/query_table/query_result_tab';
import {showModal} from '../../widgets/modal';
-import {Optional} from '../../base/utils';
import {
CRITICAL_PATH_CMD,
CRITICAL_PATH_LITE_CMD,
@@ -113,24 +112,21 @@
async function getThreadInfoForUtidOrSelection(
trace: Trace,
utid?: Utid,
-): Promise<Optional<ThreadInfo>> {
+): Promise<ThreadInfo | undefined> {
if (utid === undefined) {
- if (
- trace.selection.selection.kind !== 'legacy' ||
- trace.selection.selection.legacySelection.kind !== 'THREAD_STATE'
- ) {
- return undefined;
+ const selection = trace.selection.selection;
+ if (selection.kind === 'track_event') {
+ if (selection.utid !== undefined) {
+ utid = asUtid(selection.utid);
+ }
}
- const trackUri = trace.selection.selection.legacySelection.trackUri;
- if (trackUri === undefined) return undefined;
- const track = trace.tracks.getTrack(trackUri);
- utid = asUtid(track?.tags?.utid);
- if (utid === undefined) return undefined;
}
+ if (utid === undefined) return undefined;
return getThreadInfo(trace.engine, utid);
}
-class CriticalPath implements PerfettoPlugin {
+export default class implements PerfettoPlugin {
+ static readonly id = 'dev.perfetto.CriticalPath';
async onTraceLoad(ctx: Trace): Promise<void> {
// The 3 commands below are used in two contextes:
// 1. By clicking a slice and using the command palette. In this case the
@@ -149,9 +145,9 @@
ctx.engine
.query(`INCLUDE PERFETTO MODULE sched.thread_executing_span;`)
.then(() =>
- addDebugSliceTrack(
- ctx,
- {
+ addDebugSliceTrack({
+ trace: ctx,
+ data: {
sqlSource: `
SELECT
cr.id,
@@ -172,10 +168,10 @@
`,
columns: sliceLiteColumnNames,
},
- `${thdInfo.name}`,
- sliceLiteColumns,
- sliceLiteColumnNames,
- ),
+ title: `${thdInfo.name}`,
+ columns: sliceLiteColumns,
+ argColumns: sliceLiteColumnNames,
+ }),
);
},
});
@@ -193,9 +189,9 @@
`INCLUDE PERFETTO MODULE sched.thread_executing_span_with_slice;`,
)
.then(() =>
- addDebugSliceTrack(
- ctx,
- {
+ addDebugSliceTrack({
+ trace: ctx,
+ data: {
sqlSource: `
SELECT cr.id, cr.utid, cr.ts, cr.dur, cr.name, cr.table_name
FROM
@@ -207,10 +203,10 @@
`,
columns: sliceColumnNames,
},
- `${thdInfo.name}`,
- sliceColumns,
- sliceColumnNames,
- ),
+ title: `${thdInfo.name}`,
+ columns: sliceColumns,
+ argColumns: sliceColumnNames,
+ }),
);
},
});
@@ -227,9 +223,9 @@
await ctx.engine.query(
`INCLUDE PERFETTO MODULE sched.thread_executing_span;`,
);
- await addDebugSliceTrack(
- ctx,
- {
+ await addDebugSliceTrack({
+ trace: ctx,
+ data: {
sqlSource: `
SELECT
cr.id,
@@ -249,11 +245,12 @@
`,
columns: criticalPathsliceLiteColumnNames,
},
- (await getThreadInfo(ctx.engine, trackUtid as Utid)).name ??
+ title:
+ (await getThreadInfo(ctx.engine, trackUtid as Utid)).name ??
'<thread name>',
- criticalPathsliceLiteColumns,
- criticalPathsliceLiteColumnNames,
- );
+ columns: criticalPathsliceLiteColumns,
+ argColumns: criticalPathsliceLiteColumnNames,
+ });
},
});
@@ -269,9 +266,9 @@
await ctx.engine.query(
`INCLUDE PERFETTO MODULE sched.thread_executing_span_with_slice;`,
);
- await addDebugSliceTrack(
- ctx,
- {
+ await addDebugSliceTrack({
+ trace: ctx,
+ data: {
sqlSource: `
SELECT cr.id, cr.utid, cr.ts, cr.dur, cr.name, cr.table_name
FROM
@@ -283,11 +280,12 @@
`,
columns: criticalPathsliceColumnNames,
},
- (await getThreadInfo(ctx.engine, trackUtid as Utid)).name ??
+ title:
+ (await getThreadInfo(ctx.engine, trackUtid as Utid)).name ??
'<thread name>',
- criticalPathSliceColumns,
- criticalPathsliceColumnNames,
- );
+ columns: criticalPathSliceColumns,
+ argColumns: criticalPathsliceColumnNames,
+ });
},
});
@@ -316,8 +314,3 @@
});
}
}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'perfetto.CriticalPath',
- plugin: CriticalPath,
-};
diff --git a/ui/src/plugins/dev.perfetto.Debug/index.ts b/ui/src/plugins/dev.perfetto.Debug/index.ts
new file mode 100644
index 0000000..90d9ba2
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.Debug/index.ts
@@ -0,0 +1,77 @@
+// 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 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 {
+ addDebugCounterTrack,
+ addDebugSliceTrack,
+} from '../../public/lib/tracks/debug_tracks';
+import {Trace} from '../../public/trace';
+import {PerfettoPlugin} from '../../public/plugin';
+import {exists} from '../../base/utils';
+
+export default class implements PerfettoPlugin {
+ static readonly id = 'dev.perfetto.DebugTracks';
+ async onTraceLoad(ctx: Trace): Promise<void> {
+ ctx.commands.registerCommand({
+ id: 'perfetto.DebugTracks#addDebugSliceTrack',
+ name: 'Add debug slice track',
+ callback: async (arg: unknown) => {
+ // This command takes a query and creates a debug track out of it The
+ // query can be passed in using the first arg, or if this is not defined
+ // or is the wrong type, we prompt the user for it.
+ const query = await getStringFromArgOrPrompt(ctx, arg);
+ if (exists(query)) {
+ await addDebugSliceTrack({
+ trace: ctx,
+ data: {
+ sqlSource: query,
+ },
+ title: 'Debug slice track',
+ });
+ }
+ },
+ });
+
+ ctx.commands.registerCommand({
+ id: 'perfetto.DebugTracks#addDebugCounterTrack',
+ name: 'Add debug counter track',
+ callback: async (arg: unknown) => {
+ const query = await getStringFromArgOrPrompt(ctx, arg);
+ if (exists(query)) {
+ await addDebugCounterTrack({
+ trace: ctx,
+ data: {
+ sqlSource: query,
+ },
+ title: 'Debug slice track',
+ });
+ }
+ },
+ });
+ }
+}
+
+// If arg is a string, return it, otherwise prompt the user for a string. An
+// exception is thrown if the prompt is cancelled, so this function handles this
+// and returns undefined in this case.
+async function getStringFromArgOrPrompt(
+ ctx: Trace,
+ arg: unknown,
+): Promise<string | undefined> {
+ if (typeof arg === 'string') {
+ return arg;
+ } else {
+ return await ctx.omnibox.prompt('Enter a query...');
+ }
+}
diff --git a/ui/src/plugins/dev.perfetto.DeeplinkQuerystring/index.ts b/ui/src/plugins/dev.perfetto.DeeplinkQuerystring/index.ts
new file mode 100644
index 0000000..f351854
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.DeeplinkQuerystring/index.ts
@@ -0,0 +1,125 @@
+// 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.
+
+// When deep linking into Perfetto UI it is possible to pass arguments in the
+// query string to automatically select a slice or run a query once the
+// trace is loaded. This plugin deals with kicking off the relevant logic
+// once the trace has loaded.
+
+import {Trace} from '../../public/trace';
+import {PerfettoPlugin} from '../../public/plugin';
+import {addQueryResultsTab} from '../../public/lib/query_table/query_result_tab';
+import {Time} from '../../base/time';
+import {RouteArgs} from '../../public/route_schema';
+import {App} from '../../public/app';
+import {exists} from '../../base/utils';
+import {NUM} from '../../trace_processor/query_result';
+
+let routeArgsForFirstTrace: RouteArgs | undefined;
+
+/**
+ * Uses URL args (table, ts, dur) to select events on trace load.
+ *
+ * E.g. ?table=thread_state&ts=39978672284068&dur=18995809
+ *
+ * Note: `ts` and `dur` are used rather than id as id is not stable over TP
+ * versions.
+ *
+ * The table passed must have `ts`, `dur` (if a dur value is supplied) and `id`
+ * columns, and SQL resolvers must be available for those tables (usually from
+ * plugins).
+ */
+export default class implements PerfettoPlugin {
+ static readonly id = 'dev.perfetto.DeeplinkQuerystring';
+
+ static onActivate(app: App): void {
+ routeArgsForFirstTrace = app.initialRouteArgs;
+ }
+
+ async onTraceLoad(trace: Trace) {
+ trace.addEventListener('traceready', async () => {
+ const initialRouteArgs = routeArgsForFirstTrace;
+ routeArgsForFirstTrace = undefined;
+ if (initialRouteArgs === undefined) return;
+
+ await selectInitialRouteArgs(trace, initialRouteArgs);
+ if (
+ initialRouteArgs.visStart !== undefined &&
+ initialRouteArgs.visEnd !== undefined
+ ) {
+ zoomPendingDeeplink(
+ trace,
+ initialRouteArgs.visStart,
+ initialRouteArgs.visEnd,
+ );
+ }
+ if (initialRouteArgs.query !== undefined) {
+ addQueryResultsTab(trace, {
+ query: initialRouteArgs.query,
+ title: 'Deeplink Query',
+ });
+ }
+ });
+ }
+}
+
+function zoomPendingDeeplink(trace: Trace, visStart: string, visEnd: string) {
+ const visualStart = Time.fromRaw(BigInt(visStart));
+ const visualEnd = Time.fromRaw(BigInt(visEnd));
+ if (
+ !(
+ visualStart < visualEnd &&
+ trace.traceInfo.start <= visualStart &&
+ visualEnd <= trace.traceInfo.end
+ )
+ ) {
+ return;
+ }
+ trace.timeline.setViewportTime(visualStart, visualEnd);
+}
+
+async function selectInitialRouteArgs(trace: Trace, args: RouteArgs) {
+ const {table = 'slice', ts, dur} = args;
+
+ // We need at least a ts
+ if (!exists(ts)) {
+ return;
+ }
+
+ const conditions = [];
+ conditions.push(`ts = ${ts}`);
+ exists(dur) && conditions.push(`dur = ${dur}`);
+
+ // Find the id of the slice with this ts & dur in the given table
+ const result = await trace.engine.query(`
+ select
+ id
+ from
+ ${table}
+ where ${conditions.join(' AND ')}
+ `);
+
+ if (result.numRows() === 0) {
+ return;
+ }
+
+ const {id} = result.firstRow({
+ id: NUM,
+ });
+
+ trace.selection.selectSqlEvent(table, id, {
+ scrollToSelection: true,
+ switchToCurrentSelectionTab: false,
+ });
+}
diff --git a/ui/src/plugins/dev.perfetto.ExplorePage/OWNERS b/ui/src/plugins/dev.perfetto.ExplorePage/OWNERS
new file mode 100644
index 0000000..99ba254
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.ExplorePage/OWNERS
@@ -0,0 +1 @@
+lydiatse@google.com
diff --git a/ui/src/plugins/dev.perfetto.ExplorePage/explore_page.ts b/ui/src/plugins/dev.perfetto.ExplorePage/explore_page.ts
new file mode 100644
index 0000000..1c60d4f
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.ExplorePage/explore_page.ts
@@ -0,0 +1,192 @@
+// 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 {PageWithTraceAttrs} from '../../public/page';
+import {Trace} from '../../public/trace';
+import {
+ DurationColumn,
+ ProcessColumnSet,
+ StandardColumn,
+ ThreadColumnSet,
+ TimestampColumn,
+} from '../../frontend/widgets/sql/table/well_known_columns';
+import {SqlTableState} from '../../frontend/widgets/sql/table/state';
+import {SqlTable} from '../../frontend/widgets/sql/table/table';
+import {exists} from '../../base/utils';
+import {Menu, MenuItem, MenuItemAttrs} from '../../widgets/menu';
+import {
+ TableColumn,
+ TableColumnSet,
+} from '../../frontend/widgets/sql/table/column';
+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 ExploreTableState {
+ sqlTableState?: SqlTableState;
+ selectedTable?: ExplorableTable;
+}
+
+interface ExplorableTable {
+ name: string;
+ module: string;
+ columns: (TableColumn | TableColumnSet)[];
+}
+
+export class ExplorePage implements m.ClassComponent<PageWithTraceAttrs> {
+ private readonly state: ExploreTableState;
+ private readonly charts: Chart[];
+
+ constructor() {
+ this.charts = [];
+ this.state = {};
+ }
+
+ // Show menu with standard library tables
+ private renderSelectableTablesMenuItems(
+ trace: Trace,
+ ): m.Vnode<MenuItemAttrs, unknown>[] {
+ // TODO (lydiatse@): The following is purely for prototyping and
+ // should be derived from the actual stdlib itself rather than
+ // being hardcoded.
+ const explorableTables: ExplorableTable[] = [
+ {
+ name: 'android_binder_txns',
+ module: 'android.binder',
+ columns: [
+ new StandardColumn('aidl_name'),
+ new StandardColumn('aidl_ts'),
+ new StandardColumn('aidl_dur'),
+ new StandardColumn('binder_txn_id', {startsHidden: true}),
+ new ProcessColumnSet('client_upid', {title: 'client_upid'}),
+ new ThreadColumnSet('client_utid', {title: 'client_utid'}),
+ new StandardColumn('is_main_thread'),
+ new TimestampColumn('client_ts'),
+ new DurationColumn('client_dur'),
+ new StandardColumn('binder_reply_id', {startsHidden: true}),
+ new ProcessColumnSet('server_upid', {title: 'server_upid'}),
+ new ThreadColumnSet('server_utid', {title: 'server_utid'}),
+ new TimestampColumn('server_ts'),
+ new DurationColumn('server_dur'),
+ new StandardColumn('client_oom_score', {aggregationType: 'nominal'}),
+ new StandardColumn('server_oom_score', {aggregationType: 'nominal'}),
+ new StandardColumn('is_sync', {startsHidden: true}),
+ new StandardColumn('client_monotonic_dur', {startsHidden: true}),
+ new StandardColumn('server_monotonic_dur', {startsHidden: true}),
+ new StandardColumn('client_package_version_code', {
+ startsHidden: true,
+ }),
+ new StandardColumn('server_package_version_code', {
+ startsHidden: true,
+ }),
+ new StandardColumn('is_client_package_debuggable', {
+ startsHidden: true,
+ }),
+ new StandardColumn('is_server_package_debuggable', {
+ startsHidden: true,
+ }),
+ ],
+ },
+ ];
+
+ return explorableTables.map((table) => {
+ return m(MenuItem, {
+ label: table.name,
+ onclick: () => {
+ if (
+ this.state.selectedTable &&
+ table.name === this.state.selectedTable.name
+ ) {
+ return;
+ }
+
+ this.state.selectedTable = table;
+
+ this.state.sqlTableState = new SqlTableState(
+ trace,
+ {
+ name: table.name,
+ columns: table.columns,
+ },
+ {imports: [table.module]},
+ );
+ },
+ });
+ });
+ }
+
+ private renderSqlTable() {
+ const sqlTableState = this.state.sqlTableState;
+
+ if (sqlTableState === undefined) return;
+
+ const range = sqlTableState.getDisplayedRange();
+ const rowCount = sqlTableState.getTotalRowCount();
+
+ const navigation = [
+ exists(range) &&
+ exists(rowCount) &&
+ `Showing rows ${range.from}-${range.to} of ${rowCount}`,
+ m(Button, {
+ icon: Icons.GoBack,
+ disabled: !sqlTableState.canGoBack(),
+ onclick: () => sqlTableState!.goBack(),
+ }),
+ m(Button, {
+ icon: Icons.GoForward,
+ disabled: !sqlTableState.canGoForward(),
+ onclick: () => sqlTableState!.goForward(),
+ }),
+ ];
+
+ return m(
+ DetailsShell,
+ {
+ title: 'Explore Table',
+ buttons: navigation,
+ fillParent: false,
+ },
+ m(SqlTable, {
+ state: sqlTableState,
+ addColumnMenuItems: (column, columnAlias) =>
+ m(AddChartMenuItem, {
+ chartConfig: createChartConfigFromSqlTableState(
+ column,
+ columnAlias,
+ sqlTableState,
+ ),
+ chartOptions: [ChartOption.HISTOGRAM],
+ addChart: (chart) => this.charts.push(chart),
+ }),
+ }),
+ );
+ }
+
+ view({attrs}: m.CVnode<PageWithTraceAttrs>) {
+ 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.ExplorePage/index.ts b/ui/src/plugins/dev.perfetto.ExplorePage/index.ts
new file mode 100644
index 0000000..5c18701
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.ExplorePage/index.ts
@@ -0,0 +1,31 @@
+// 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 {PerfettoPlugin} from '../../public/plugin';
+import {Trace} from '../../public/trace';
+import {ExplorePage} from './explore_page';
+
+export default class implements PerfettoPlugin {
+ static readonly id = 'dev.perfetto.ExplorePage';
+
+ async onTraceLoad(trace: Trace): Promise<void> {
+ trace.pages.registerPage({route: '/explore', page: ExplorePage});
+ trace.sidebar.addMenuItem({
+ section: 'current_trace',
+ text: 'Explore',
+ href: '#!/explore',
+ icon: 'data_exploration',
+ });
+ }
+}
diff --git a/ui/src/core_plugins/frames/actual_frames_track.ts b/ui/src/plugins/dev.perfetto.Frames/actual_frames_track.ts
similarity index 91%
rename from ui/src/core_plugins/frames/actual_frames_track.ts
rename to ui/src/plugins/dev.perfetto.Frames/actual_frames_track.ts
index 37a9095..d75dd77 100644
--- a/ui/src/core_plugins/frames/actual_frames_track.ts
+++ b/ui/src/plugins/dev.perfetto.Frames/actual_frames_track.ts
@@ -13,13 +13,14 @@
// limitations under the License.
import {HSLColor} from '../../public/color';
-import {makeColorScheme} from '../../core/colorizer';
+import {makeColorScheme} from '../../public/lib/colorizer';
import {ColorScheme} from '../../public/color_scheme';
import {NAMED_ROW, NamedSliceTrack} from '../../frontend/named_slice_track';
import {SLICE_LAYOUT_FIT_CONTENT_DEFAULTS} from '../../frontend/slice_layout';
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/core_plugins/frames/expected_frames_track.ts b/ui/src/plugins/dev.perfetto.Frames/expected_frames_track.ts
similarity index 81%
rename from ui/src/core_plugins/frames/expected_frames_track.ts
rename to ui/src/plugins/dev.perfetto.Frames/expected_frames_track.ts
index c016f46..ff04311 100644
--- a/ui/src/core_plugins/frames/expected_frames_track.ts
+++ b/ui/src/plugins/dev.perfetto.Frames/expected_frames_track.ts
@@ -13,7 +13,7 @@
// limitations under the License.
import {HSLColor} from '../../public/color';
-import {makeColorScheme} from '../../core/colorizer';
+import {makeColorScheme} from '../../public/lib/colorizer';
import {
NAMED_ROW,
NamedRow,
@@ -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/core_plugins/frames/frame_selection_aggregator.ts b/ui/src/plugins/dev.perfetto.Frames/frame_selection_aggregator.ts
similarity index 100%
rename from ui/src/core_plugins/frames/frame_selection_aggregator.ts
rename to ui/src/plugins/dev.perfetto.Frames/frame_selection_aggregator.ts
diff --git a/ui/src/core_plugins/frames/index.ts b/ui/src/plugins/dev.perfetto.Frames/index.ts
similarity index 95%
rename from ui/src/core_plugins/frames/index.ts
rename to ui/src/plugins/dev.perfetto.Frames/index.ts
index 6181768..8d3abec 100644
--- a/ui/src/core_plugins/frames/index.ts
+++ b/ui/src/plugins/dev.perfetto.Frames/index.ts
@@ -17,7 +17,7 @@
EXPECTED_FRAMES_SLICE_TRACK_KIND,
} from '../../public/track_kinds';
import {Trace} from '../../public/trace';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
+import {PerfettoPlugin} from '../../public/plugin';
import {getOrCreateGroupForProcess} from '../../public/standard_groups';
import {getTrackName} from '../../public/utils';
import {TrackNode} from '../../public/workspace';
@@ -26,7 +26,8 @@
import {ExpectedFramesTrack} from './expected_frames_track';
import {FrameSelectionAggregator} from './frame_selection_aggregator';
-class FramesPlugin implements PerfettoPlugin {
+export default class implements PerfettoPlugin {
+ static readonly id = 'dev.perfetto.Frames';
async onTraceLoad(ctx: Trace): Promise<void> {
this.addExpectedFrames(ctx);
this.addActualFrames(ctx);
@@ -156,8 +157,3 @@
}
}
}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'perfetto.Frames',
- plugin: FramesPlugin,
-};
diff --git a/ui/src/core_plugins/ftrace/common.ts b/ui/src/plugins/dev.perfetto.Ftrace/common.ts
similarity index 100%
rename from ui/src/core_plugins/ftrace/common.ts
rename to ui/src/plugins/dev.perfetto.Ftrace/common.ts
diff --git a/ui/src/core_plugins/ftrace/ftrace_explorer.ts b/ui/src/plugins/dev.perfetto.Ftrace/ftrace_explorer.ts
similarity index 98%
rename from ui/src/core_plugins/ftrace/ftrace_explorer.ts
rename to ui/src/plugins/dev.perfetto.Ftrace/ftrace_explorer.ts
index 1bcd6d6..b4036e5 100644
--- a/ui/src/core_plugins/ftrace/ftrace_explorer.ts
+++ b/ui/src/plugins/dev.perfetto.Ftrace/ftrace_explorer.ts
@@ -14,7 +14,7 @@
import m from 'mithril';
import {time, Time} from '../../base/time';
-import {colorForFtrace} from '../../core/colorizer';
+import {colorForFtrace} from '../../public/lib/colorizer';
import {DetailsShell} from '../../widgets/details_shell';
import {
MultiSelectDiff,
@@ -26,7 +26,6 @@
import {FtraceFilter, FtraceStat} from './common';
import {Engine} from '../../trace_processor/engine';
import {LONG, NUM, STR, STR_NULL} from '../../trace_processor/query_result';
-import {raf} from '../../core/raf_scheduler';
import {AsyncLimiter} from '../../base/async_limiter';
import {Monitor} from '../../base/monitor';
import {Button} from '../../widgets/button';
@@ -175,7 +174,7 @@
this.pagination.count,
attrs.filterStore.state,
);
- raf.scheduleFullRedraw();
+ attrs.trace.scheduleFullRedraw();
});
}
diff --git a/ui/src/core_plugins/ftrace/ftrace_track.ts b/ui/src/plugins/dev.perfetto.Ftrace/ftrace_track.ts
similarity index 98%
rename from ui/src/core_plugins/ftrace/ftrace_track.ts
rename to ui/src/plugins/dev.perfetto.Ftrace/ftrace_track.ts
index 2f253df..31ea7de 100644
--- a/ui/src/core_plugins/ftrace/ftrace_track.ts
+++ b/ui/src/plugins/dev.perfetto.Ftrace/ftrace_track.ts
@@ -13,7 +13,7 @@
// limitations under the License.
import {duration, Time, time} from '../../base/time';
-import {colorForFtrace} from '../../core/colorizer';
+import {colorForFtrace} from '../../public/lib/colorizer';
import {LIMIT} from '../../common/track_data';
import {Store, TimelineFetcher} from '../../common/track_helper';
import {checkerboardExcept} from '../../frontend/checkerboard';
diff --git a/ui/src/core_plugins/ftrace/index.ts b/ui/src/plugins/dev.perfetto.Ftrace/index.ts
similarity index 87%
rename from ui/src/core_plugins/ftrace/index.ts
rename to ui/src/plugins/dev.perfetto.Ftrace/index.ts
index 80e6c83..7b0b6d4 100644
--- a/ui/src/core_plugins/ftrace/index.ts
+++ b/ui/src/plugins/dev.perfetto.Ftrace/index.ts
@@ -16,11 +16,10 @@
import {FtraceExplorer, FtraceExplorerCache} from './ftrace_explorer';
import {Engine} from '../../trace_processor/engine';
import {Trace} from '../../public/trace';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
+import {PerfettoPlugin} from '../../public/plugin';
import {NUM} from '../../trace_processor/query_result';
import {FtraceFilter, FtracePluginState} from './common';
import {FtraceRawTrack} from './ftrace_track';
-import {DisposableStack} from '../../base/disposable_stack';
import {TrackNode} from '../../public/workspace';
const VERSION = 1;
@@ -32,9 +31,8 @@
},
};
-class FtraceRawPlugin implements PerfettoPlugin {
- private trash = new DisposableStack();
-
+export default class implements PerfettoPlugin {
+ static readonly id = 'dev.perfetto.Ftrace';
async onTraceLoad(ctx: Trace): Promise<void> {
const store = ctx.mountStore<FtracePluginState>((init: unknown) => {
if (
@@ -48,13 +46,13 @@
return DEFAULT_STATE;
}
});
- this.trash.use(store);
+ ctx.trash.use(store);
const filterStore = store.createSubStore(
['filter'],
(x) => x as FtraceFilter,
);
- this.trash.use(filterStore);
+ ctx.trash.use(filterStore);
const cpus = await this.lookupCpuCores(ctx.engine);
const group = new TrackNode({
@@ -115,10 +113,6 @@
});
}
- async onTraceUnload(): Promise<void> {
- this.trash[Symbol.dispose]();
- }
-
private async lookupCpuCores(engine: Engine): Promise<number[]> {
const query = 'select distinct cpu from ftrace_event order by cpu';
@@ -134,8 +128,3 @@
return cpuCores;
}
}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'perfetto.FtraceRaw',
- plugin: FtraceRawPlugin,
-};
diff --git a/ui/src/plugins/dev.perfetto.GpuByProcess/index.ts b/ui/src/plugins/dev.perfetto.GpuByProcess/index.ts
index 5b11c58..9705156 100644
--- a/ui/src/plugins/dev.perfetto.GpuByProcess/index.ts
+++ b/ui/src/plugins/dev.perfetto.GpuByProcess/index.ts
@@ -15,7 +15,7 @@
import {NUM_NULL, STR_NULL} from '../../trace_processor/query_result';
import {Trace} from '../../public/trace';
import {Slice} from '../../public/track';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
+import {PerfettoPlugin} from '../../public/plugin';
import {
NAMED_ROW,
NamedRow,
@@ -48,7 +48,8 @@
}
}
-class GpuByProcess implements PerfettoPlugin {
+export default class implements PerfettoPlugin {
+ static readonly id = 'dev.perfetto.GpuByProcess';
async onTraceLoad(ctx: Trace): Promise<void> {
// Find all unique upid values in gpu_slices and join with process table.
const results = await ctx.engine.query(`
@@ -91,11 +92,4 @@
ctx.workspace.addChildInOrder(track);
}
}
-
- async onTraceUnload(_: Trace): Promise<void> {}
}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'dev.perfetto.GpuByProcess',
- plugin: GpuByProcess,
-};
diff --git a/ui/src/plugins/dev.perfetto.HeapProfile/heap_profile_details_panel.ts b/ui/src/plugins/dev.perfetto.HeapProfile/heap_profile_details_panel.ts
new file mode 100644
index 0000000..c3e145e
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.HeapProfile/heap_profile_details_panel.ts
@@ -0,0 +1,414 @@
+// 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 {assertExists, assertFalse} from '../../base/logging';
+import {time} from '../../base/time';
+import {
+ QueryFlamegraph,
+ QueryFlamegraphMetric,
+ metricsFromTableOrSubquery,
+} from '../../public/lib/query_flamegraph';
+import {convertTraceToPprofAndDownload} from '../../frontend/trace_converter';
+import {Timestamp} from '../../frontend/widgets/timestamp';
+import {
+ TrackEventDetailsPanel,
+ TrackEventDetailsPanelSerializeArgs,
+} from '../../public/details_panel';
+import {ProfileType, TrackEventSelection} from '../../public/selection';
+import {Trace} from '../../public/trace';
+import {NUM} from '../../trace_processor/query_result';
+import {Button} from '../../widgets/button';
+import {Intent} from '../../widgets/common';
+import {DetailsShell} from '../../widgets/details_shell';
+import {Icon} from '../../widgets/icon';
+import {Modal, showModal} from '../../widgets/modal';
+import {Popup} from '../../widgets/popup';
+import {
+ Flamegraph,
+ FLAMEGRAPH_STATE_SCHEMA,
+ FlamegraphState,
+} from '../../widgets/flamegraph';
+
+interface Props {
+ ts: time;
+ type: ProfileType;
+}
+
+export class HeapProfileFlamegraphDetailsPanel
+ implements TrackEventDetailsPanel
+{
+ private readonly flamegraph: QueryFlamegraph;
+ private readonly props: Props;
+ private flamegraphModalDismissed = false;
+
+ readonly serialization: TrackEventDetailsPanelSerializeArgs<FlamegraphState>;
+
+ constructor(
+ private trace: Trace,
+ private heapGraphIncomplete: boolean,
+ private upid: number,
+ sel: TrackEventSelection,
+ ) {
+ const {profileType, ts} = sel;
+ const metrics = flamegraphMetrics(assertExists(profileType), ts, upid);
+ this.serialization = {
+ schema: FLAMEGRAPH_STATE_SCHEMA,
+ state: Flamegraph.createDefaultState(metrics),
+ };
+ this.flamegraph = new QueryFlamegraph(trace, metrics, this.serialization);
+ this.props = {ts, type: assertExists(profileType)};
+ }
+
+ render() {
+ const {type, ts} = this.props;
+ return m(
+ '.flamegraph-profile',
+ this.maybeShowModal(this.trace, type, this.heapGraphIncomplete),
+ m(
+ DetailsShell,
+ {
+ fillParent: true,
+ title: m(
+ '.title',
+ getFlamegraphTitle(type),
+ type === ProfileType.MIXED_HEAP_PROFILE &&
+ m(
+ Popup,
+ {
+ trigger: m(Icon, {icon: 'warning'}),
+ },
+ m(
+ '',
+ {style: {width: '300px'}},
+ 'This is a mixed java/native heap profile, free()s are not visualized. To visualize free()s, remove "all_heaps: true" from the config.',
+ ),
+ ),
+ ),
+ description: [],
+ buttons: [
+ m('.time', `Snapshot time: `, m(Timestamp, {ts})),
+ (type === ProfileType.NATIVE_HEAP_PROFILE ||
+ type === ProfileType.JAVA_HEAP_SAMPLES) &&
+ m(Button, {
+ icon: 'file_download',
+ intent: Intent.Primary,
+ onclick: () => {
+ downloadPprof(this.trace, this.upid, ts);
+ this.trace.scheduleFullRedraw();
+ },
+ }),
+ ],
+ },
+ assertExists(this.flamegraph).render(),
+ ),
+ );
+ }
+
+ private maybeShowModal(
+ trace: Trace,
+ type: ProfileType,
+ heapGraphIncomplete: boolean,
+ ) {
+ if (type !== ProfileType.JAVA_HEAP_GRAPH || !heapGraphIncomplete) {
+ return undefined;
+ }
+ if (this.flamegraphModalDismissed) {
+ return undefined;
+ }
+ return m(Modal, {
+ title: 'The flamegraph is incomplete',
+ vAlign: 'TOP',
+ content: m(
+ 'div',
+ 'The current trace does not have a fully formed flamegraph',
+ ),
+ buttons: [
+ {
+ text: 'Show the errors',
+ primary: true,
+ action: () => trace.navigate('#!/info'),
+ },
+ {
+ text: 'Skip',
+ action: () => {
+ this.flamegraphModalDismissed = true;
+ trace.scheduleFullRedraw();
+ },
+ },
+ ],
+ });
+ }
+}
+
+function flamegraphMetrics(
+ type: ProfileType,
+ ts: time,
+ upid: number,
+): ReadonlyArray<QueryFlamegraphMetric> {
+ switch (type) {
+ case ProfileType.NATIVE_HEAP_PROFILE:
+ return flamegraphMetricsForHeapProfile(ts, upid, [
+ {
+ name: 'Unreleased Malloc Size',
+ unit: 'B',
+ columnName: 'self_size',
+ },
+ {
+ name: 'Unreleased Malloc Count',
+ unit: '',
+ columnName: 'self_count',
+ },
+ {
+ name: 'Total Malloc Size',
+ unit: 'B',
+ columnName: 'self_alloc_size',
+ },
+ {
+ name: 'Total Malloc Count',
+ unit: '',
+ columnName: 'self_alloc_count',
+ },
+ ]);
+ case ProfileType.HEAP_PROFILE:
+ return flamegraphMetricsForHeapProfile(ts, upid, [
+ {
+ name: 'Unreleased Size',
+ unit: 'B',
+ columnName: 'self_size',
+ },
+ {
+ name: 'Unreleased Count',
+ unit: '',
+ columnName: 'self_count',
+ },
+ {
+ name: 'Total Size',
+ unit: 'B',
+ columnName: 'self_alloc_size',
+ },
+ {
+ name: 'Total Count',
+ unit: '',
+ columnName: 'self_alloc_count',
+ },
+ ]);
+ case ProfileType.JAVA_HEAP_SAMPLES:
+ return flamegraphMetricsForHeapProfile(ts, upid, [
+ {
+ name: 'Unreleased Allocation Size',
+ unit: 'B',
+ columnName: 'self_size',
+ },
+ {
+ name: 'Unreleased Allocation Count',
+ unit: '',
+ columnName: 'self_count',
+ },
+ ]);
+ case ProfileType.MIXED_HEAP_PROFILE:
+ return flamegraphMetricsForHeapProfile(ts, upid, [
+ {
+ name: 'Unreleased Allocation Size (malloc + java)',
+ unit: 'B',
+ columnName: 'self_size',
+ },
+ {
+ name: 'Unreleased Allocation Count (malloc + java)',
+ unit: '',
+ columnName: 'self_count',
+ },
+ ]);
+ case ProfileType.JAVA_HEAP_GRAPH:
+ return [
+ {
+ name: 'Object Size',
+ unit: 'B',
+ dependencySql:
+ 'include perfetto module android.memory.heap_graph.class_tree;',
+ statement: `
+ select
+ id,
+ parent_id as parentId,
+ ifnull(name, '[Unknown]') as name,
+ root_type,
+ self_size as value,
+ self_count
+ from _heap_graph_class_tree
+ where graph_sample_ts = ${ts} and upid = ${upid}
+ `,
+ unaggregatableProperties: [
+ {name: 'root_type', displayName: 'Root Type'},
+ ],
+ aggregatableProperties: [
+ {
+ name: 'self_count',
+ displayName: 'Self Count',
+ mergeAggregation: 'SUM',
+ },
+ ],
+ },
+ {
+ name: 'Object Count',
+ unit: '',
+ dependencySql:
+ 'include perfetto module android.memory.heap_graph.class_tree;',
+ statement: `
+ select
+ id,
+ parent_id as parentId,
+ ifnull(name, '[Unknown]') as name,
+ root_type,
+ self_size,
+ self_count as value
+ from _heap_graph_class_tree
+ where graph_sample_ts = ${ts} and upid = ${upid}
+ `,
+ unaggregatableProperties: [
+ {name: 'root_type', displayName: 'Root Type'},
+ ],
+ },
+ {
+ name: 'Dominated Object Size',
+ unit: 'B',
+ dependencySql:
+ 'include perfetto module android.memory.heap_graph.dominator_class_tree;',
+ statement: `
+ select
+ id,
+ parent_id as parentId,
+ ifnull(name, '[Unknown]') as name,
+ root_type,
+ self_size as value,
+ self_count
+ from _heap_graph_dominator_class_tree
+ where graph_sample_ts = ${ts} and upid = ${upid}
+ `,
+ unaggregatableProperties: [
+ {name: 'root_type', displayName: 'Root Type'},
+ ],
+ aggregatableProperties: [
+ {
+ name: 'self_count',
+ displayName: 'Self Count',
+ mergeAggregation: 'SUM',
+ },
+ ],
+ },
+ {
+ name: 'Dominated Object Count',
+ unit: '',
+ dependencySql:
+ 'include perfetto module android.memory.heap_graph.dominator_class_tree;',
+ statement: `
+ select
+ id,
+ parent_id as parentId,
+ ifnull(name, '[Unknown]') as name,
+ root_type,
+ self_size,
+ self_count as value
+ from _heap_graph_class_tree
+ where graph_sample_ts = ${ts} and upid = ${upid}
+ `,
+ unaggregatableProperties: [
+ {name: 'root_type', displayName: 'Root Type'},
+ ],
+ },
+ ];
+ case ProfileType.PERF_SAMPLE:
+ throw new Error('Perf sample not supported');
+ }
+}
+
+function flamegraphMetricsForHeapProfile(
+ ts: time,
+ upid: number,
+ metrics: {name: string; unit: string; columnName: string}[],
+) {
+ return metricsFromTableOrSubquery(
+ `
+ (
+ select
+ id,
+ parent_id as parentId,
+ name,
+ mapping_name,
+ source_file,
+ cast(line_number AS text) as line_number,
+ self_size,
+ self_count,
+ self_alloc_size,
+ self_alloc_count
+ from _android_heap_profile_callstacks_for_allocations!((
+ select
+ callsite_id,
+ size,
+ count,
+ max(size, 0) as alloc_size,
+ max(count, 0) as alloc_count
+ from heap_profile_allocation a
+ where a.ts <= ${ts} and a.upid = ${upid}
+ ))
+ )
+ `,
+ metrics,
+ 'include perfetto module android.memory.heap_profile.callstacks',
+ [{name: 'mapping_name', displayName: 'Mapping'}],
+ [
+ {
+ name: 'source_file',
+ displayName: 'Source File',
+ mergeAggregation: 'ONE_OR_NULL',
+ },
+ {
+ name: 'line_number',
+ displayName: 'Line Number',
+ mergeAggregation: 'ONE_OR_NULL',
+ },
+ ],
+ );
+}
+
+function getFlamegraphTitle(type: ProfileType) {
+ switch (type) {
+ case ProfileType.HEAP_PROFILE:
+ return 'Heap profile';
+ case ProfileType.JAVA_HEAP_GRAPH:
+ return 'Java heap graph';
+ case ProfileType.JAVA_HEAP_SAMPLES:
+ return 'Java heap samples';
+ case ProfileType.MIXED_HEAP_PROFILE:
+ return 'Mixed heap profile';
+ case ProfileType.NATIVE_HEAP_PROFILE:
+ return 'Native heap profile';
+ case ProfileType.PERF_SAMPLE:
+ assertFalse(false, 'Perf sample not supported');
+ return 'Impossible';
+ }
+}
+
+async function downloadPprof(trace: Trace, upid: number, ts: time) {
+ const pid = await trace.engine.query(
+ `select pid from process where upid = ${upid}`,
+ );
+ if (!trace.traceInfo.downloadable) {
+ showModal({
+ title: 'Download not supported',
+ content: m('div', 'This trace file does not support downloads'),
+ });
+ }
+ const blob = await trace.getTraceFile();
+ convertTraceToPprofAndDownload(blob, pid.firstRow({pid: NUM}).pid, ts);
+}
diff --git a/ui/src/plugins/dev.perfetto.HeapProfile/heap_profile_track.ts b/ui/src/plugins/dev.perfetto.HeapProfile/heap_profile_track.ts
new file mode 100644
index 0000000..d4effa4
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.HeapProfile/heap_profile_track.ts
@@ -0,0 +1,117 @@
+// 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 {Duration, Time} from '../../base/time';
+import {
+ BASE_ROW,
+ BaseSliceTrack,
+ OnSliceClickArgs,
+ OnSliceOverArgs,
+} from '../../frontend/base_slice_track';
+import {NewTrackArgs} from '../../frontend/track';
+import {
+ ProfileType,
+ profileType,
+ TrackEventDetails,
+ TrackEventSelection,
+} from '../../public/selection';
+import {Slice} from '../../public/track';
+import {LONG, STR} from '../../trace_processor/query_result';
+import {HeapProfileFlamegraphDetailsPanel} from './heap_profile_details_panel';
+
+const HEAP_PROFILE_ROW = {
+ ...BASE_ROW,
+ type: STR,
+};
+type HeapProfileRow = typeof HEAP_PROFILE_ROW;
+interface HeapProfileSlice extends Slice {
+ type: ProfileType;
+}
+
+export class HeapProfileTrack extends BaseSliceTrack<
+ HeapProfileSlice,
+ HeapProfileRow
+> {
+ constructor(
+ args: NewTrackArgs,
+ private readonly tableName: string,
+ private readonly upid: number,
+ private readonly heapProfileIsIncomplete: boolean,
+ ) {
+ super(args);
+ }
+
+ getSqlSource(): string {
+ return this.tableName;
+ }
+
+ getRowSpec(): HeapProfileRow {
+ return HEAP_PROFILE_ROW;
+ }
+
+ rowToSlice(row: HeapProfileRow): HeapProfileSlice {
+ const slice = this.rowToSliceBase(row);
+ return {
+ ...slice,
+ type: profileType(row.type),
+ };
+ }
+
+ onSliceOver(args: OnSliceOverArgs<HeapProfileSlice>) {
+ args.tooltip = [args.slice.type];
+ }
+
+ onSliceClick(args: OnSliceClickArgs<HeapProfileSlice>) {
+ this.trace.selection.selectTrackEvent(this.uri, args.slice.id);
+ }
+
+ async getSelectionDetails(
+ id: number,
+ ): Promise<TrackEventDetails | undefined> {
+ const query = `
+ SELECT
+ ts,
+ dur,
+ type
+ FROM (${this.getSqlSource()})
+ WHERE id = ${id}
+ `;
+
+ const result = await this.engine.query(query);
+ if (result.numRows() === 0) {
+ return undefined;
+ }
+
+ const row = result.iter({
+ ts: LONG,
+ dur: LONG,
+ type: STR,
+ });
+
+ return {
+ ts: Time.fromRaw(row.ts),
+ dur: Duration.fromRaw(row.dur),
+ profileType: profileType(row.type),
+ };
+ }
+
+ detailsPanel(sel: TrackEventSelection) {
+ return new HeapProfileFlamegraphDetailsPanel(
+ this.trace,
+ this.heapProfileIsIncomplete,
+ this.upid,
+ sel,
+ );
+ }
+}
diff --git a/ui/src/plugins/dev.perfetto.HeapProfile/index.ts b/ui/src/plugins/dev.perfetto.HeapProfile/index.ts
new file mode 100644
index 0000000..f4f9121
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.HeapProfile/index.ts
@@ -0,0 +1,138 @@
+// 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.
+
+import {HEAP_PROFILE_TRACK_KIND} from '../../public/track_kinds';
+import {Trace} from '../../public/trace';
+import {PerfettoPlugin} from '../../public/plugin';
+import {LONG, NUM, STR} from '../../trace_processor/query_result';
+import {HeapProfileTrack} from './heap_profile_track';
+import {getOrCreateGroupForProcess} from '../../public/standard_groups';
+import {TrackNode} from '../../public/workspace';
+import {createPerfettoTable} from '../../trace_processor/sql_utils';
+
+function getUriForTrack(upid: number): string {
+ return `/process_${upid}/heap_profile`;
+}
+
+export default class implements PerfettoPlugin {
+ static readonly id = 'dev.perfetto.HeapProfile';
+ async onTraceLoad(ctx: Trace): Promise<void> {
+ const it = await ctx.engine.query(`
+ select value from stats
+ where name = 'heap_graph_non_finalized_graph'
+ `);
+ const incomplete = it.firstRow({value: NUM}).value > 0;
+
+ const result = await ctx.engine.query(`
+ select distinct upid from heap_profile_allocation
+ union
+ select distinct upid from heap_graph_object
+ `);
+ for (const it = result.iter({upid: NUM}); it.valid(); it.next()) {
+ const upid = it.upid;
+ const uri = getUriForTrack(upid);
+ const title = 'Heap Profile';
+ const tableName = `_heap_profile_${upid}`;
+
+ createPerfettoTable(
+ ctx.engine,
+ tableName,
+ `
+ with
+ heaps as (select group_concat(distinct heap_name) h from heap_profile_allocation where upid = ${upid}),
+ allocation_tses as (select distinct ts from heap_profile_allocation where upid = ${upid}),
+ graph_tses as (select distinct graph_sample_ts from heap_graph_object where upid = ${upid})
+ select
+ *,
+ 0 AS dur,
+ 0 AS depth
+ from (
+ select
+ (
+ select a.id
+ from heap_profile_allocation a
+ where a.ts = t.ts
+ order by a.id
+ limit 1
+ ) as id,
+ ts,
+ 'heap_profile:' || (select h from heaps) AS type
+ from allocation_tses t
+ union all
+ select
+ (
+ select o.id
+ from heap_graph_object o
+ where o.graph_sample_ts = g.graph_sample_ts
+ order by o.id
+ limit 1
+ ) as id,
+ graph_sample_ts AS ts,
+ 'graph' AS type
+ from graph_tses g
+ )
+ `,
+ );
+
+ ctx.tracks.registerTrack({
+ uri,
+ title,
+ tags: {
+ kind: HEAP_PROFILE_TRACK_KIND,
+ upid,
+ },
+ track: new HeapProfileTrack(
+ {
+ trace: ctx,
+ uri,
+ },
+ tableName,
+ upid,
+ incomplete,
+ ),
+ });
+ const group = getOrCreateGroupForProcess(ctx.workspace, upid);
+ const track = new TrackNode({uri, title, sortOrder: -30});
+ group.addChildInOrder(track);
+ }
+
+ ctx.addEventListener('traceready', async () => {
+ await selectFirstHeapProfile(ctx);
+ });
+ }
+}
+
+async function selectFirstHeapProfile(ctx: Trace) {
+ const query = `
+ select * from (
+ select
+ min(ts) AS ts,
+ 'heap_profile:' || group_concat(distinct heap_name) AS type,
+ upid
+ from heap_profile_allocation
+ group by upid
+ union
+ select distinct graph_sample_ts as ts, 'graph' as type, upid
+ from heap_graph_object
+ )
+ order by ts
+ limit 1
+ `;
+ const profile = await ctx.engine.query(query);
+ if (profile.numRows() !== 1) return;
+ const row = profile.firstRow({ts: LONG, type: STR, upid: NUM});
+ const upid = row.upid;
+
+ ctx.selection.selectTrackEvent(getUriForTrack(upid), 0);
+}
diff --git a/ui/src/plugins/dev.perfetto.InsightsPage/index.ts b/ui/src/plugins/dev.perfetto.InsightsPage/index.ts
new file mode 100644
index 0000000..fdd84a1
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.InsightsPage/index.ts
@@ -0,0 +1,31 @@
+// 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 {PerfettoPlugin} from '../../public/plugin';
+import {Trace} from '../../public/trace';
+import {InsightsPage} from './insights_page';
+
+export default class implements PerfettoPlugin {
+ static readonly id = 'dev.perfetto.InsightsPage';
+
+ async onTraceLoad(trace: Trace): Promise<void> {
+ trace.pages.registerPage({route: '/insights', page: InsightsPage});
+ trace.sidebar.addMenuItem({
+ section: 'current_trace',
+ text: 'Insights',
+ href: '#!/insights',
+ icon: 'insights',
+ });
+ }
+}
diff --git a/ui/src/frontend/insights_page.ts b/ui/src/plugins/dev.perfetto.InsightsPage/insights_page.ts
similarity index 93%
rename from ui/src/frontend/insights_page.ts
rename to ui/src/plugins/dev.perfetto.InsightsPage/insights_page.ts
index 77f8496..5b0b742 100644
--- a/ui/src/frontend/insights_page.ts
+++ b/ui/src/plugins/dev.perfetto.InsightsPage/insights_page.ts
@@ -13,7 +13,7 @@
// limitations under the License.
import m from 'mithril';
-import {PageWithTraceAttrs} from './pages';
+import {PageWithTraceAttrs} from '../../public/page';
export class InsightsPage implements m.ClassComponent<PageWithTraceAttrs> {
view() {
diff --git a/ui/src/plugins/dev.perfetto.LargeScreensPerf/index.ts b/ui/src/plugins/dev.perfetto.LargeScreensPerf/index.ts
index 6bf7dc2..ffbbd7c 100644
--- a/ui/src/plugins/dev.perfetto.LargeScreensPerf/index.ts
+++ b/ui/src/plugins/dev.perfetto.LargeScreensPerf/index.ts
@@ -13,9 +13,10 @@
// limitations under the License.
import {Trace} from '../../public/trace';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
+import {PerfettoPlugin} from '../../public/plugin';
-class LargeScreensPerf implements PerfettoPlugin {
+export default class implements PerfettoPlugin {
+ static readonly id = 'dev.perfetto.LargeScreensPerf';
async onTraceLoad(ctx: Trace): Promise<void> {
ctx.commands.registerCommand({
id: 'dev.perfetto.LargeScreensPerf#PinUnfoldLatencyTracks',
@@ -41,8 +42,3 @@
});
}
}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'dev.perfetto.LargeScreensPerf',
- plugin: LargeScreensPerf,
-};
diff --git a/ui/src/plugins/dev.perfetto.MetricsPage/index.ts b/ui/src/plugins/dev.perfetto.MetricsPage/index.ts
new file mode 100644
index 0000000..55cda40
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.MetricsPage/index.ts
@@ -0,0 +1,32 @@
+// 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 {PerfettoPlugin} from '../../public/plugin';
+import {Trace} from '../../public/trace';
+import {MetricsPage} from './metrics_page';
+
+export default class implements PerfettoPlugin {
+ static readonly id = 'dev.perfetto.MetricsPage';
+
+ async onTraceLoad(trace: Trace): Promise<void> {
+ trace.pages.registerPage({route: '/metrics', page: MetricsPage});
+ trace.sidebar.addMenuItem({
+ section: 'current_trace',
+ text: 'Metrics',
+ href: '#!/metrics',
+ icon: 'speed',
+ sortOrder: 9,
+ });
+ }
+}
diff --git a/ui/src/frontend/metrics_page.ts b/ui/src/plugins/dev.perfetto.MetricsPage/metrics_page.ts
similarity index 86%
rename from ui/src/frontend/metrics_page.ts
rename to ui/src/plugins/dev.perfetto.MetricsPage/metrics_page.ts
index 685ab58..8f1f0f3 100644
--- a/ui/src/frontend/metrics_page.ts
+++ b/ui/src/plugins/dev.perfetto.MetricsPage/metrics_page.ts
@@ -20,17 +20,16 @@
pending,
Result,
success,
-} from '../base/result';
-import {pluginManager, PluginManager} from '../common/plugins';
-import {raf} from '../core/raf_scheduler';
-import {MetricVisualisation} from '../public/plugin';
-import {Engine} from '../trace_processor/engine';
-import {STR} from '../trace_processor/query_result';
-import {Select} from '../widgets/select';
-import {Spinner} from '../widgets/spinner';
-import {VegaView} from '../widgets/vega_view';
-import {PageWithTraceAttrs} from './pages';
-import {assertExists} from '../base/logging';
+} from '../../base/result';
+import {MetricVisualisation} from '../../public/plugin';
+import {Engine} from '../../trace_processor/engine';
+import {STR} from '../../trace_processor/query_result';
+import {Select} from '../../widgets/select';
+import {Spinner} from '../../widgets/spinner';
+import {VegaView} from '../../widgets/vega_view';
+import {PageWithTraceAttrs} from '../../public/page';
+import {assertExists} from '../../base/logging';
+import {Trace} from '../../public/trace';
type Format = 'json' | 'prototext' | 'proto';
const FORMATS: Format[] = ['json', 'prototext', 'proto'];
@@ -58,8 +57,8 @@
}
class MetricsController {
- engine: Engine;
- plugins: PluginManager;
+ private readonly trace: Trace;
+ private readonly engine: Engine;
private _metrics: string[];
private _selected?: string;
private _result: Result<string>;
@@ -67,9 +66,9 @@
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private _json: any;
- constructor(plugins: PluginManager, engine: Engine) {
- this.plugins = plugins;
- this.engine = engine;
+ constructor(trace: Trace) {
+ this.trace = trace;
+ this.engine = trace.engine.getProxy('MetricsPage');
this._metrics = [];
this._result = success('');
this._json = {};
@@ -84,7 +83,7 @@
}
get visualisations(): MetricVisualisation[] {
- return this.plugins
+ return this.trace.plugins
.metricVisualisations()
.filter((v) => v.metric === this.selected);
}
@@ -147,10 +146,10 @@
}
})
.finally(() => {
- raf.scheduleFullRedraw();
+ this.trace.scheduleFullRedraw();
});
}
- raf.scheduleFullRedraw();
+ this.trace.scheduleFullRedraw();
}
}
@@ -246,8 +245,7 @@
private controller?: MetricsController;
oninit({attrs}: m.Vnode<PageWithTraceAttrs>) {
- const engine = attrs.trace.engine.getProxy('MetricsPage');
- this.controller = new MetricsController(pluginManager, engine);
+ this.controller = new MetricsController(attrs.trace);
}
view() {
diff --git a/ui/src/plugins/dev.perfetto.PerfSamplesProfile/index.ts b/ui/src/plugins/dev.perfetto.PerfSamplesProfile/index.ts
new file mode 100644
index 0000000..26ac305
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.PerfSamplesProfile/index.ts
@@ -0,0 +1,144 @@
+// 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.
+
+import {TrackData} from '../../common/track_data';
+import {PERF_SAMPLES_PROFILE_TRACK_KIND} from '../../public/track_kinds';
+import {Trace} from '../../public/trace';
+import {PerfettoPlugin} from '../../public/plugin';
+import {NUM, NUM_NULL, STR_NULL} from '../../trace_processor/query_result';
+import {assertExists} from '../../base/logging';
+import {
+ ProcessPerfSamplesProfileTrack,
+ ThreadPerfSamplesProfileTrack,
+} from './perf_samples_profile_track';
+import {getThreadUriPrefix} from '../../public/utils';
+import {
+ getOrCreateGroupForProcess,
+ getOrCreateGroupForThread,
+} from '../../public/standard_groups';
+import {TrackNode} from '../../public/workspace';
+
+export interface Data extends TrackData {
+ tsStarts: BigInt64Array;
+}
+
+function makeUriForProc(upid: number) {
+ return `/process_${upid}/perf_samples_profile`;
+}
+
+export default class implements PerfettoPlugin {
+ static readonly id = 'dev.perfetto.PerfSamplesProfile';
+ async onTraceLoad(ctx: Trace): Promise<void> {
+ const pResult = await ctx.engine.query(`
+ select distinct upid
+ from perf_sample
+ join thread using (utid)
+ where callsite_id is not null and upid is not null
+ `);
+ for (const it = pResult.iter({upid: NUM}); it.valid(); it.next()) {
+ const upid = it.upid;
+ const uri = makeUriForProc(upid);
+ const title = `Process Callstacks`;
+ ctx.tracks.registerTrack({
+ uri,
+ title,
+ tags: {
+ kind: PERF_SAMPLES_PROFILE_TRACK_KIND,
+ upid,
+ },
+ track: new ProcessPerfSamplesProfileTrack(
+ {
+ trace: ctx,
+ uri,
+ },
+ upid,
+ ),
+ });
+ const group = getOrCreateGroupForProcess(ctx.workspace, upid);
+ const track = new TrackNode({uri, title, sortOrder: -40});
+ group.addChildInOrder(track);
+ }
+ const tResult = await ctx.engine.query(`
+ select distinct
+ utid,
+ tid,
+ thread.name as threadName,
+ upid
+ from perf_sample
+ join thread using (utid)
+ where callsite_id is not null
+ `);
+ for (
+ const it = tResult.iter({
+ utid: NUM,
+ tid: NUM,
+ threadName: STR_NULL,
+ upid: NUM_NULL,
+ });
+ it.valid();
+ it.next()
+ ) {
+ const {threadName, utid, tid, upid} = it;
+ const title =
+ threadName === null
+ ? `Thread Callstacks ${tid}`
+ : `${threadName} Callstacks ${tid}`;
+ const uri = `${getThreadUriPrefix(upid, utid)}_perf_samples_profile`;
+ ctx.tracks.registerTrack({
+ uri,
+ title,
+ tags: {
+ kind: PERF_SAMPLES_PROFILE_TRACK_KIND,
+ utid,
+ upid: upid ?? undefined,
+ },
+ track: new ThreadPerfSamplesProfileTrack(
+ {
+ trace: ctx,
+ uri,
+ },
+ utid,
+ ),
+ });
+ const group = getOrCreateGroupForThread(ctx.workspace, utid);
+ const track = new TrackNode({uri, title, sortOrder: -50});
+ group.addChildInOrder(track);
+ }
+
+ ctx.addEventListener('traceready', async () => {
+ await selectPerfSample(ctx);
+ });
+ }
+}
+
+async function selectPerfSample(ctx: Trace) {
+ const profile = await assertExists(ctx.engine).query(`
+ select upid
+ from perf_sample
+ join thread using (utid)
+ where callsite_id is not null
+ order by ts desc
+ limit 1
+ `);
+ if (profile.numRows() !== 1) return;
+ const row = profile.firstRow({upid: NUM});
+ const upid = row.upid;
+
+ // Create an area selection over the first process with a perf samples track
+ ctx.selection.selectArea({
+ start: ctx.traceInfo.start,
+ end: ctx.traceInfo.end,
+ trackUris: [makeUriForProc(upid)],
+ });
+}
diff --git a/ui/src/plugins/dev.perfetto.PerfSamplesProfile/perf_samples_profile_track.ts b/ui/src/plugins/dev.perfetto.PerfSamplesProfile/perf_samples_profile_track.ts
new file mode 100644
index 0000000..839d0d1
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.PerfSamplesProfile/perf_samples_profile_track.ts
@@ -0,0 +1,293 @@
+// 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 {NUM} from '../../trace_processor/query_result';
+import {Slice} from '../../public/track';
+import {
+ BaseSliceTrack,
+ OnSliceClickArgs,
+} from '../../frontend/base_slice_track';
+import {NewTrackArgs} from '../../frontend/track';
+import {NAMED_ROW, NamedRow} from '../../frontend/named_slice_track';
+import {getColorForSample} from '../../public/lib/colorizer';
+import {
+ ProfileType,
+ TrackEventDetails,
+ TrackEventSelection,
+} from '../../public/selection';
+import {assertExists} from '../../base/logging';
+import {
+ metricsFromTableOrSubquery,
+ QueryFlamegraph,
+} from '../../public/lib/query_flamegraph';
+import {DetailsShell} from '../../widgets/details_shell';
+import {Timestamp} from '../../frontend/widgets/timestamp';
+import {time} from '../../base/time';
+import {TrackEventDetailsPanel} from '../../public/details_panel';
+import {Flamegraph, FLAMEGRAPH_STATE_SCHEMA} from '../../widgets/flamegraph';
+
+interface PerfSampleRow extends NamedRow {
+ callsiteId: number;
+}
+
+abstract class BasePerfSamplesProfileTrack extends BaseSliceTrack<
+ Slice,
+ PerfSampleRow
+> {
+ constructor(args: NewTrackArgs) {
+ super(args);
+ }
+
+ protected getRowSpec(): PerfSampleRow {
+ return {...NAMED_ROW, callsiteId: NUM};
+ }
+
+ protected rowToSlice(row: PerfSampleRow): Slice {
+ const baseSlice = super.rowToSliceBase(row);
+ const name = assertExists(row.name);
+ const colorScheme = getColorForSample(row.callsiteId);
+ return {...baseSlice, title: name, colorScheme};
+ }
+
+ onUpdatedSlices(slices: Slice[]) {
+ for (const slice of slices) {
+ slice.isHighlighted = slice === this.hoveredSlice;
+ }
+ }
+
+ onSliceClick(args: OnSliceClickArgs<Slice>): void {
+ // TODO(stevegolton): Perhaps we could just move this to BaseSliceTrack?
+ this.trace.selection.selectTrackEvent(this.uri, args.slice.id);
+ }
+}
+
+export class ProcessPerfSamplesProfileTrack extends BasePerfSamplesProfileTrack {
+ constructor(
+ args: NewTrackArgs,
+ private upid: number,
+ ) {
+ super(args);
+ }
+
+ getSqlSource(): string {
+ return `
+ select
+ p.id,
+ ts,
+ 0 as dur,
+ 0 as depth,
+ 'Perf Sample' as name,
+ callsite_id as callsiteId
+ from perf_sample p
+ join thread using (utid)
+ where upid = ${this.upid} and callsite_id is not null
+ order by ts
+ `;
+ }
+
+ async getSelectionDetails(
+ id: number,
+ ): Promise<TrackEventDetails | undefined> {
+ const details = await super.getSelectionDetails(id);
+ if (details === undefined) return undefined;
+ return {
+ ...details,
+ upid: this.upid,
+ profileType: ProfileType.PERF_SAMPLE,
+ };
+ }
+
+ detailsPanel(sel: TrackEventSelection) {
+ const upid = assertExists(sel.upid);
+ const ts = sel.ts;
+
+ const metrics = metricsFromTableOrSubquery(
+ `
+ (
+ select
+ id,
+ parent_id as parentId,
+ name,
+ mapping_name,
+ source_file,
+ cast(line_number AS text) as line_number,
+ self_count
+ from _callstacks_for_callsites!((
+ select p.callsite_id
+ from perf_sample p
+ join thread t using (utid)
+ where p.ts >= ${ts}
+ and p.ts <= ${ts}
+ and t.upid = ${upid}
+ ))
+ )
+ `,
+ [
+ {
+ name: 'Perf Samples',
+ unit: '',
+ columnName: 'self_count',
+ },
+ ],
+ 'include perfetto module linux.perf.samples',
+ [{name: 'mapping_name', displayName: 'Mapping'}],
+ [
+ {
+ name: 'source_file',
+ displayName: 'Source File',
+ mergeAggregation: 'ONE_OR_NULL',
+ },
+ {
+ name: 'line_number',
+ displayName: 'Line Number',
+ mergeAggregation: 'ONE_OR_NULL',
+ },
+ ],
+ );
+ const serialization = {
+ schema: FLAMEGRAPH_STATE_SCHEMA,
+ state: Flamegraph.createDefaultState(metrics),
+ };
+ const flamegraph = new QueryFlamegraph(this.trace, metrics, serialization);
+ return {
+ render: () => renderDetailsPanel(flamegraph, ts),
+ serialization,
+ };
+ }
+}
+
+export class ThreadPerfSamplesProfileTrack extends BasePerfSamplesProfileTrack {
+ constructor(
+ args: NewTrackArgs,
+ private utid: number,
+ ) {
+ super(args);
+ }
+
+ getSqlSource(): string {
+ return `
+ select
+ p.id,
+ ts,
+ 0 as dur,
+ 0 as depth,
+ 'Perf Sample' as name,
+ callsite_id as callsiteId
+ from perf_sample p
+ where utid = ${this.utid} and callsite_id is not null
+ order by ts
+ `;
+ }
+
+ async getSelectionDetails(
+ id: number,
+ ): Promise<TrackEventDetails | undefined> {
+ const details = await super.getSelectionDetails(id);
+ if (details === undefined) return undefined;
+ return {
+ ...details,
+ utid: this.utid,
+ profileType: ProfileType.PERF_SAMPLE,
+ };
+ }
+
+ detailsPanel(sel: TrackEventSelection): TrackEventDetailsPanel {
+ const utid = assertExists(sel.utid);
+ const ts = sel.ts;
+
+ const metrics = metricsFromTableOrSubquery(
+ `
+ (
+ select
+ id,
+ parent_id as parentId,
+ name,
+ mapping_name,
+ source_file,
+ cast(line_number AS text) as line_number,
+ self_count
+ from _callstacks_for_callsites!((
+ select p.callsite_id
+ from perf_sample p
+ where p.ts >= ${ts}
+ and p.ts <= ${ts}
+ and p.utid = ${utid}
+ ))
+ )
+ `,
+ [
+ {
+ name: 'Perf Samples',
+ unit: '',
+ columnName: 'self_count',
+ },
+ ],
+ 'include perfetto module linux.perf.samples',
+ [{name: 'mapping_name', displayName: 'Mapping'}],
+ [
+ {
+ name: 'source_file',
+ displayName: 'Source File',
+ mergeAggregation: 'ONE_OR_NULL',
+ },
+ {
+ name: 'line_number',
+ displayName: 'Line Number',
+ mergeAggregation: 'ONE_OR_NULL',
+ },
+ ],
+ );
+ const serialization = {
+ schema: FLAMEGRAPH_STATE_SCHEMA,
+ state: Flamegraph.createDefaultState(metrics),
+ };
+ const flamegraph = new QueryFlamegraph(this.trace, metrics, serialization);
+ return {
+ render: () => renderDetailsPanel(flamegraph, ts),
+ serialization,
+ };
+ }
+}
+
+function renderDetailsPanel(flamegraph: QueryFlamegraph, ts: time) {
+ return m(
+ '.flamegraph-profile',
+ m(
+ DetailsShell,
+ {
+ fillParent: true,
+ title: m('.title', 'Perf Samples'),
+ description: [],
+ buttons: [
+ m(
+ 'div.time',
+ `First timestamp: `,
+ m(Timestamp, {
+ ts,
+ }),
+ ),
+ m(
+ 'div.time',
+ `Last timestamp: `,
+ m(Timestamp, {
+ ts,
+ }),
+ ),
+ ],
+ },
+ flamegraph.render(),
+ ),
+ );
+}
diff --git a/ui/src/plugins/dev.perfetto.PinAndroidPerfMetrics/handlers/fullTraceJankMetricHandler.ts b/ui/src/plugins/dev.perfetto.PinAndroidPerfMetrics/handlers/fullTraceJankMetricHandler.ts
index 55d1821..f3704ac 100644
--- a/ui/src/plugins/dev.perfetto.PinAndroidPerfMetrics/handlers/fullTraceJankMetricHandler.ts
+++ b/ui/src/plugins/dev.perfetto.PinAndroidPerfMetrics/handlers/fullTraceJankMetricHandler.ts
@@ -19,8 +19,7 @@
MetricHandler,
} from './metricUtils';
import {Trace} from '../../../public/trace';
-import {addAndPinSliceTrack} from '../../dev.perfetto.AndroidCujs/trackUtils';
-import {SimpleSliceTrackConfig} from '../../../frontend/simple_slice_track';
+import {addDebugSliceTrack} from '../../../public/debug_tracks';
class FullTraceJankMetricHandler implements MetricHandler {
/**
@@ -55,16 +54,12 @@
INCLUDE PERFETTO MODULE android.frames.jank_type;
INCLUDE PERFETTO MODULE slices.slices;
`;
- const {config: fullTraceJankConfig, trackName: trackName} =
- this.fullTraceJankConfig(metricData);
+ const config = this.fullTraceJankConfig(metricData);
await ctx.engine.query(INCLUDE_PREQUERY);
- addAndPinSliceTrack(ctx, fullTraceJankConfig, trackName);
+ addDebugSliceTrack({trace: ctx, ...config});
}
- private fullTraceJankConfig(metricData: FullTraceMetricData): {
- config: SimpleSliceTrackConfig;
- trackName: string;
- } {
+ private fullTraceJankConfig(metricData: FullTraceMetricData) {
let jankTypeFilter;
let jankTypeDisplayName;
if (metricData.jankType?.includes('app')) {
@@ -115,18 +110,18 @@
'process_name',
'pid',
];
- const fullTraceJankConfig: SimpleSliceTrackConfig = {
+
+ const trackName = jankTypeDisplayName + ' missed frames in ' + processName;
+
+ return {
data: {
sqlSource: fullTraceJankQuery,
columns: fullTraceJankColumns,
},
columns: {ts: 'ts', dur: 'dur', name: 'name'},
argColumns: fullTraceJankColumns,
+ tableName: trackName,
};
-
- const trackName = jankTypeDisplayName + ' missed frames in ' + processName;
-
- return {config: fullTraceJankConfig, trackName: trackName};
}
}
diff --git a/ui/src/plugins/dev.perfetto.PinAndroidPerfMetrics/handlers/pinBlockingCall.ts b/ui/src/plugins/dev.perfetto.PinAndroidPerfMetrics/handlers/pinBlockingCall.ts
index 3ca5f30..1ae9ac6 100644
--- a/ui/src/plugins/dev.perfetto.PinAndroidPerfMetrics/handlers/pinBlockingCall.ts
+++ b/ui/src/plugins/dev.perfetto.PinAndroidPerfMetrics/handlers/pinBlockingCall.ts
@@ -18,9 +18,8 @@
MetricHandler,
} from './metricUtils';
import {Trace} from '../../../public/trace';
-import {SimpleSliceTrackConfig} from '../../../frontend/simple_slice_track';
import {addJankCUJDebugTrack} from '../../dev.perfetto.AndroidCujs';
-import {addAndPinSliceTrack} from '../../dev.perfetto.AndroidCujs/trackUtils';
+import {addDebugSliceTrack} from '../../../public/debug_tracks';
class BlockingCallMetricHandler implements MetricHandler {
/**
@@ -54,9 +53,8 @@
*/
public addMetricTrack(metricData: BlockingCallMetricData, ctx: Trace): void {
this.pinSingleCuj(ctx, metricData);
- const {config: blockingCallMetricConfig, trackName: trackName} =
- this.blockingCallTrackConfig(metricData);
- addAndPinSliceTrack(ctx, blockingCallMetricConfig, trackName);
+ const config = this.blockingCallTrackConfig(metricData);
+ addDebugSliceTrack({trace: ctx, ...config});
}
private pinSingleCuj(ctx: Trace, metricData: BlockingCallMetricData) {
@@ -64,10 +62,7 @@
addJankCUJDebugTrack(ctx, trackName, metricData.cujName);
}
- private blockingCallTrackConfig(metricData: BlockingCallMetricData): {
- config: SimpleSliceTrackConfig;
- trackName: string;
- } {
+ private blockingCallTrackConfig(metricData: BlockingCallMetricData) {
const cuj = metricData.cujName;
const processName = metricData.process;
const blockingCallName = metricData.blockingCallName;
@@ -81,18 +76,16 @@
AND name = "${blockingCallName}"
`;
- const blockingCallMetricConfig: SimpleSliceTrackConfig = {
+ const trackName = 'Blocking calls in ' + processName;
+ return {
data: {
sqlSource: blockingCallDuringCujQuery,
columns: ['name', 'ts', 'dur'],
},
columns: {ts: 'ts', dur: 'dur', name: 'name'},
argColumns: ['name', 'ts', 'dur'],
+ trackName,
};
-
- const trackName = 'Blocking calls in ' + processName;
-
- return {config: blockingCallMetricConfig, trackName: trackName};
}
}
diff --git a/ui/src/plugins/dev.perfetto.PinAndroidPerfMetrics/handlers/pinCujScoped.ts b/ui/src/plugins/dev.perfetto.PinAndroidPerfMetrics/handlers/pinCujScoped.ts
index 4ae752a..5d61843 100644
--- a/ui/src/plugins/dev.perfetto.PinAndroidPerfMetrics/handlers/pinCujScoped.ts
+++ b/ui/src/plugins/dev.perfetto.PinAndroidPerfMetrics/handlers/pinCujScoped.ts
@@ -20,14 +20,11 @@
} from './metricUtils';
import {NUM} from '../../../trace_processor/query_result';
import {Trace} from '../../../public/trace';
-import {SimpleSliceTrackConfig} from '../../../frontend/simple_slice_track';
// TODO(primiano): make deps check stricter, we shouldn't allow plugins to
// depend on each other.
-import {
- addAndPinSliceTrack,
- focusOnSlice,
-} from '../../dev.perfetto.AndroidCujs/trackUtils';
+import {focusOnSlice} from '../../dev.perfetto.AndroidCujs/trackUtils';
+import {addDebugSliceTrack} from '../../../public/debug_tracks';
const ENABLE_FOCUS_ON_FIRST_JANK = true;
@@ -63,12 +60,11 @@
*/
public async addMetricTrack(metricData: CujScopedMetricData, ctx: Trace) {
// TODO: b/349502258 - Refactor to single API
- const {
- config: cujScopedJankSlice,
- trackName: trackName,
- tableName: tableName,
- } = await this.cujScopedTrackConfig(metricData, ctx);
- addAndPinSliceTrack(ctx, cujScopedJankSlice, trackName);
+ const {tableName, ...config} = await this.cujScopedTrackConfig(
+ metricData,
+ ctx,
+ );
+ addDebugSliceTrack({trace: ctx, ...config});
if (ENABLE_FOCUS_ON_FIRST_JANK) {
await this.focusOnFirstJank(ctx, tableName);
}
@@ -77,11 +73,7 @@
private async cujScopedTrackConfig(
metricData: CujScopedMetricData,
ctx: Trace,
- ): Promise<{
- config: SimpleSliceTrackConfig;
- trackName: string;
- tableName: string;
- }> {
+ ) {
let jankTypeFilter;
let jankTypeDisplayName = 'all';
if (metricData.jankType?.includes('app')) {
@@ -114,20 +106,20 @@
FROM ${tableWithJankyFramesName}
`;
- const cujScopedJankSlice: SimpleSliceTrackConfig = {
+ const trackName = jankTypeDisplayName + ' missed frames in ' + processName;
+
+ const cujScopedJankSlice = {
data: {
sqlSource: jankyFramesDuringCujQuery,
columns: ['id', 'ts', 'dur'],
},
columns: {ts: 'ts', dur: 'dur', name: 'id'},
argColumns: ['id', 'ts', 'dur'],
+ trackName,
};
- const trackName = jankTypeDisplayName + ' missed frames in ' + processName;
-
return {
- config: cujScopedJankSlice,
- trackName: trackName,
+ ...cujScopedJankSlice,
tableName: tableWithJankyFramesName,
};
}
diff --git a/ui/src/plugins/dev.perfetto.PinAndroidPerfMetrics/index.ts b/ui/src/plugins/dev.perfetto.PinAndroidPerfMetrics/index.ts
index 53c6964..430acd8 100644
--- a/ui/src/plugins/dev.perfetto.PinAndroidPerfMetrics/index.ts
+++ b/ui/src/plugins/dev.perfetto.PinAndroidPerfMetrics/index.ts
@@ -13,15 +13,35 @@
// limitations under the License.
import {Trace} from '../../public/trace';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
+import {PerfettoPlugin} from '../../public/plugin';
import {METRIC_HANDLERS} from './handlers/handlerRegistry';
import {MetricData, MetricHandlerMatch} from './handlers/metricUtils';
import {PLUGIN_ID} from './pluginId';
+import AndroidCujsPlugin from '../dev.perfetto.AndroidCujs';
const JANK_CUJ_QUERY_PRECONDITIONS = `
SELECT RUN_METRIC('android/android_blocking_calls_cuj_metric.sql');
`;
+function getMetricsFromHash(): string[] {
+ const metricVal = location.hash;
+ const regex = new RegExp(`${PLUGIN_ID}:metrics=(.*)`);
+ const match = metricVal.match(regex);
+ if (match === null) {
+ return [];
+ }
+ const capturedString = match[1];
+ let metricList: string[] = [];
+ if (capturedString.includes('--')) {
+ metricList = capturedString.split('--');
+ } else {
+ metricList = [capturedString];
+ }
+ return metricList.map((metric) => decodeURIComponent(metric));
+}
+
+let metrics: string[];
+
/**
* Plugin that adds and pins the debug track for the metric passed
* For more context -
@@ -32,14 +52,15 @@
* the regression, the user will not have to manually search for the
* slices related to the regressed metric
*/
-class PinAndroidPerfMetrics implements PerfettoPlugin {
- private metrics: string[] = [];
+export default class implements PerfettoPlugin {
+ static readonly id = PLUGIN_ID;
+ static readonly dependencies = [AndroidCujsPlugin];
- onActivate(): void {
- this.metrics = this.getMetricsFromHash();
+ static onActivate(): void {
+ metrics = getMetricsFromHash();
}
- async onTraceReady(ctx: Trace) {
+ async onTraceLoad(ctx: Trace) {
ctx.commands.registerCommand({
id: 'dev.perfetto.PinAndroidPerfMetrics#PinAndroidPerfMetrics',
name: 'Add and Pin: Jank Metric Slice',
@@ -50,8 +71,8 @@
this.callHandlers(metricList, ctx);
},
});
- if (this.metrics.length !== 0) {
- this.callHandlers(this.metrics, ctx);
+ if (metrics.length !== 0) {
+ this.callHandlers(metrics, ctx);
}
}
@@ -70,23 +91,6 @@
}
}
- private getMetricsFromHash(): string[] {
- const metricVal = location.hash;
- const regex = new RegExp(`${PLUGIN_ID}:metrics=(.*)`);
- const match = metricVal.match(regex);
- if (match === null) {
- return [];
- }
- const capturedString = match[1];
- let metricList: string[] = [];
- if (capturedString.includes('--')) {
- metricList = capturedString.split('--');
- } else {
- metricList = [capturedString];
- }
- return metricList.map((metric) => decodeURIComponent(metric));
- }
-
private getMetricsToShow(metricList: string[]): MetricHandlerMatch[] {
const sortedMetricList = [...metricList].sort();
const validMetrics: MetricHandlerMatch[] = [];
@@ -113,8 +117,3 @@
return JSON.stringify(metricData, Object.keys(metricData).sort());
}
}
-
-export const plugin: PluginDescriptor = {
- pluginId: PLUGIN_ID,
- plugin: PinAndroidPerfMetrics,
-};
diff --git a/ui/src/plugins/dev.perfetto.PinSysUITracks/index.ts b/ui/src/plugins/dev.perfetto.PinSysUITracks/index.ts
index 88b2473..eb53737 100644
--- a/ui/src/plugins/dev.perfetto.PinSysUITracks/index.ts
+++ b/ui/src/plugins/dev.perfetto.PinSysUITracks/index.ts
@@ -14,7 +14,7 @@
import {NUM} from '../../trace_processor/query_result';
import {Trace} from '../../public/trace';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
+import {PerfettoPlugin} from '../../public/plugin';
// List of tracks to pin
const TRACKS_TO_PIN: string[] = [
@@ -29,7 +29,8 @@
const SYSTEM_UI_PROCESS: string = 'com.android.systemui';
// Plugin that pins the tracks relevant to System UI
-class PinSysUITracks implements PerfettoPlugin {
+export default class implements PerfettoPlugin {
+ static readonly id = 'dev.perfetto.PinSysUITracks';
async onTraceLoad(ctx: Trace): Promise<void> {
// Find the upid for the sysui process
const result = await ctx.engine.query(`
@@ -75,8 +76,3 @@
});
}
}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'dev.perfetto.PinSysUITracks',
- plugin: PinSysUITracks,
-};
diff --git a/ui/src/core_plugins/process/index.ts b/ui/src/plugins/dev.perfetto.Process/index.ts
similarity index 76%
rename from ui/src/core_plugins/process/index.ts
rename to ui/src/plugins/dev.perfetto.Process/index.ts
index 7f07992..52ce021 100644
--- a/ui/src/core_plugins/process/index.ts
+++ b/ui/src/plugins/dev.perfetto.Process/index.ts
@@ -12,28 +12,24 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {addSqlTableTab} from '../../frontend/sql_table_tab_interface';
import {sqlTableRegistry} from '../../frontend/widgets/sql/table/sql_table_registry';
import {Trace} from '../../public/trace';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
+import {PerfettoPlugin} from '../../public/plugin';
import {getProcessTable} from './table';
+import {extensions} from '../../public/lib/extensions';
-class ProcessPlugin implements PerfettoPlugin {
+export default class implements PerfettoPlugin {
+ static readonly id = 'dev.perfetto.Process';
async onTraceLoad(ctx: Trace) {
sqlTableRegistry['process'] = getProcessTable();
ctx.commands.registerCommand({
id: 'perfetto.ShowTable.process',
name: 'Open table: process',
callback: () => {
- addSqlTableTab(ctx, {
+ extensions.addSqlTableTab(ctx, {
table: getProcessTable(),
});
},
});
}
}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'perfetto.Process',
- plugin: ProcessPlugin,
-};
diff --git a/ui/src/core_plugins/process/table.ts b/ui/src/plugins/dev.perfetto.Process/table.ts
similarity index 100%
rename from ui/src/core_plugins/process/table.ts
rename to ui/src/plugins/dev.perfetto.Process/table.ts
diff --git a/ui/src/core_plugins/process_summary/index.ts b/ui/src/plugins/dev.perfetto.ProcessSummary/index.ts
similarity index 93%
rename from ui/src/core_plugins/process_summary/index.ts
rename to ui/src/plugins/dev.perfetto.ProcessSummary/index.ts
index d62887e..20e1803 100644
--- a/ui/src/core_plugins/process_summary/index.ts
+++ b/ui/src/plugins/dev.perfetto.ProcessSummary/index.ts
@@ -13,7 +13,7 @@
// limitations under the License.
import {Trace} from '../../public/trace';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
+import {PerfettoPlugin} from '../../public/plugin';
import {getThreadOrProcUri} from '../../public/utils';
import {NUM, NUM_NULL, STR} from '../../trace_processor/query_result';
import {
@@ -26,16 +26,22 @@
PROCESS_SUMMARY_TRACK,
ProcessSummaryTrack,
} from './process_summary_track';
+import ThreadPlugin from '../dev.perfetto.Thread';
// This plugin is responsible for adding summary tracks for process and thread
// groups.
-class ProcessSummaryPlugin implements PerfettoPlugin {
+export default class implements PerfettoPlugin {
+ static readonly id = 'dev.perfetto.ProcessSummary';
+ static readonly dependencies = [ThreadPlugin];
+
async onTraceLoad(ctx: Trace): Promise<void> {
await this.addProcessTrackGroups(ctx);
await this.addKernelThreadSummary(ctx);
}
private async addProcessTrackGroups(ctx: Trace): Promise<void> {
+ const threads = ctx.plugins.getPlugin(ThreadPlugin).getThreadMap();
+
const cpuCount = Math.max(...ctx.traceInfo.cpus, -1) + 1;
const result = await ctx.engine.query(`
@@ -122,7 +128,7 @@
kind: PROCESS_SCHEDULING_TRACK_KIND,
},
chips,
- track: new ProcessSchedulingTrack(ctx.engine, config, cpuCount),
+ track: new ProcessSchedulingTrack(ctx, config, cpuCount, threads),
subtitle,
});
} else {
@@ -201,8 +207,3 @@
});
}
}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'perfetto.ProcessSummary',
- plugin: ProcessSummaryPlugin,
-};
diff --git a/ui/src/core_plugins/process_summary/process_scheduling_track.ts b/ui/src/plugins/dev.perfetto.ProcessSummary/process_scheduling_track.ts
similarity index 86%
rename from ui/src/core_plugins/process_summary/process_scheduling_track.ts
rename to ui/src/plugins/dev.perfetto.ProcessSummary/process_scheduling_track.ts
index e09aa08..91f3646 100644
--- a/ui/src/core_plugins/process_summary/process_scheduling_track.ts
+++ b/ui/src/plugins/dev.perfetto.ProcessSummary/process_scheduling_track.ts
@@ -16,20 +16,19 @@
import {searchEq, searchRange} from '../../base/binary_search';
import {assertExists, assertTrue} from '../../base/logging';
import {duration, time, Time} from '../../base/time';
-import {Actions} from '../../common/actions';
import {drawTrackHoverTooltip} from '../../base/canvas_utils';
import {Color} from '../../public/color';
-import {colorForThread} from '../../core/colorizer';
+import {colorForThread} from '../../public/lib/colorizer';
import {TrackData} from '../../common/track_data';
import {TimelineFetcher} from '../../common/track_helper';
import {checkerboardExcept} from '../../frontend/checkerboard';
-import {globals} from '../../frontend/globals';
-import {Engine} from '../../trace_processor/engine';
import {Track} from '../../public/track';
import {LONG, NUM, QueryResult} from '../../trace_processor/query_result';
import {uuidv4Sql} from '../../base/uuid';
import {TrackMouseEvent, TrackRenderContext} from '../../public/track';
import {Point2D} from '../../base/geom';
+import {Trace} from '../../public/trace';
+import {ThreadMap} from '../dev.perfetto.Thread/threads';
export const PROCESS_SCHEDULING_TRACK_KIND = 'ProcessSchedulingTrack';
@@ -58,20 +57,18 @@
private mousePos?: Point2D;
private utidHoveredInThisTrack = -1;
private fetcher = new TimelineFetcher(this.onBoundsChange.bind(this));
- private cpuCount: number;
- private engine: Engine;
private trackUuid = uuidv4Sql();
- private config: Config;
- constructor(engine: Engine, config: Config, cpuCount: number) {
- this.engine = engine;
- this.config = config;
- this.cpuCount = cpuCount;
- }
+ constructor(
+ private readonly trace: Trace,
+ private readonly config: Config,
+ private readonly cpuCount: number,
+ private readonly threads: ThreadMap,
+ ) {}
async onCreate(): Promise<void> {
if (this.config.upid !== null) {
- await this.engine.query(`
+ await this.trace.engine.query(`
create virtual table process_scheduling_${this.trackUuid}
using __intrinsic_slice_mipmap((
select
@@ -91,7 +88,7 @@
`);
} else {
assertExists(this.config.utid);
- await this.engine.query(`
+ await this.trace.engine.query(`
create virtual table process_scheduling_${this.trackUuid}
using __intrinsic_slice_mipmap((
select
@@ -119,7 +116,7 @@
async onDestroy(): Promise<void> {
this.fetcher[Symbol.dispose]();
- await this.engine.tryQuery(`
+ await this.trace.engine.tryQuery(`
drop table process_scheduling_${this.trackUuid}
`);
}
@@ -173,7 +170,7 @@
end: time,
bucketSize: duration,
): Promise<QueryResult> {
- return this.engine.query(`
+ return this.trace.engine.query(`
select
(z.ts / ${bucketSize}) * ${bucketSize} as ts,
iif(s.dur = -1, s.dur, max(z.dur, ${bucketSize})) as dur,
@@ -227,13 +224,13 @@
const rectEnd = Math.floor(timescale.timeToPx(tEnd));
const rectWidth = Math.max(1, rectEnd - rectStart);
- const threadInfo = globals.threads.get(utid);
+ const threadInfo = this.threads.get(utid);
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
const pid = (threadInfo ? threadInfo.pid : -1) || -1;
- const isHovering = globals.state.hoveredUtid !== -1;
- const isThreadHovered = globals.state.hoveredUtid === utid;
- const isProcessHovered = globals.state.hoveredPid === pid;
+ const isHovering = this.trace.timeline.hoveredUtid !== undefined;
+ const isThreadHovered = this.trace.timeline.hoveredUtid === utid;
+ const isProcessHovered = this.trace.timeline.hoveredPid === pid;
const colorScheme = colorForThread(threadInfo);
let color: Color;
if (isHovering && !isThreadHovered) {
@@ -250,7 +247,7 @@
ctx.fillRect(rectStart, y, rectWidth, cpuTrackHeight);
}
- const hoveredThread = globals.threads.get(this.utidHoveredInThisTrack);
+ const hoveredThread = this.threads.get(this.utidHoveredInThisTrack);
if (hoveredThread !== undefined && this.mousePos !== undefined) {
const tidText = `T: ${hoveredThread.threadName} [${hoveredThread.tid}]`;
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
@@ -269,7 +266,8 @@
if (data === undefined) return;
if (y < MARGIN_TOP || y > MARGIN_TOP + RECT_HEIGHT) {
this.utidHoveredInThisTrack = -1;
- globals.dispatch(Actions.setHoveredUtidAndPid({utid: -1, pid: -1}));
+ this.trace.timeline.hoveredUtid = undefined;
+ this.trace.timeline.hoveredPid = undefined;
return;
}
@@ -280,21 +278,24 @@
const [i, j] = searchRange(data.starts, t, searchEq(data.cpus, cpu));
if (i === j || i >= data.starts.length || t > data.ends[i]) {
this.utidHoveredInThisTrack = -1;
- globals.dispatch(Actions.setHoveredUtidAndPid({utid: -1, pid: -1}));
+ this.trace.timeline.hoveredUtid = undefined;
+ this.trace.timeline.hoveredPid = undefined;
return;
}
const utid = data.utids[i];
this.utidHoveredInThisTrack = utid;
- const threadInfo = globals.threads.get(utid);
+ const threadInfo = this.threads.get(utid);
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
const pid = threadInfo ? (threadInfo.pid ? threadInfo.pid : -1) : -1;
- globals.dispatch(Actions.setHoveredUtidAndPid({utid, pid}));
+ this.trace.timeline.hoveredUtid = utid;
+ this.trace.timeline.hoveredPid = pid;
}
onMouseOut() {
this.utidHoveredInThisTrack = -1;
- globals.dispatch(Actions.setHoveredUtidAndPid({utid: -1, pid: -1}));
+ this.trace.timeline.hoveredUtid = undefined;
+ this.trace.timeline.hoveredPid = undefined;
this.mousePos = undefined;
}
}
diff --git a/ui/src/core_plugins/process_summary/process_summary_track.ts b/ui/src/plugins/dev.perfetto.ProcessSummary/process_summary_track.ts
similarity index 98%
rename from ui/src/core_plugins/process_summary/process_summary_track.ts
rename to ui/src/plugins/dev.perfetto.ProcessSummary/process_summary_track.ts
index 0bc8c77..778c068 100644
--- a/ui/src/core_plugins/process_summary/process_summary_track.ts
+++ b/ui/src/plugins/dev.perfetto.ProcessSummary/process_summary_track.ts
@@ -15,7 +15,7 @@
import {BigintMath} from '../../base/bigint_math';
import {assertExists, assertTrue} from '../../base/logging';
import {duration, Time, time} from '../../base/time';
-import {colorForTid} from '../../core/colorizer';
+import {colorForTid} from '../../public/lib/colorizer';
import {TrackData} from '../../common/track_data';
import {TimelineFetcher} from '../../common/track_helper';
import {checkerboardExcept} from '../../frontend/checkerboard';
diff --git a/ui/src/core_plugins/process_thread_groups/index.ts b/ui/src/plugins/dev.perfetto.ProcessThreadGroups/index.ts
similarity index 84%
rename from ui/src/core_plugins/process_thread_groups/index.ts
rename to ui/src/plugins/dev.perfetto.ProcessThreadGroups/index.ts
index 8aef32c..60aa11c 100644
--- a/ui/src/core_plugins/process_thread_groups/index.ts
+++ b/ui/src/plugins/dev.perfetto.ProcessThreadGroups/index.ts
@@ -13,7 +13,7 @@
// limitations under the License.
import {Trace} from '../../public/trace';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
+import {PerfettoPlugin} from '../../public/plugin';
import {
getOrCreateGroupForProcess,
getOrCreateGroupForThread,
@@ -39,17 +39,11 @@
// This plugin is responsible for organizing all process and thread groups
// including the kernel groups, sorting, and adding summary tracks.
-class ProcessThreadGroupsPlugin implements PerfettoPlugin {
- private readonly processGroups = new Map<number, TrackNode>();
- private readonly threadGroups = new Map<number, TrackNode>();
-
+export default class implements PerfettoPlugin {
+ static readonly id = 'dev.perfetto.ProcessThreadGroups';
async onTraceLoad(ctx: Trace): Promise<void> {
- await this.addProcessAndThreadGroups(ctx);
- }
-
- private async addProcessAndThreadGroups(ctx: Trace): Promise<void> {
- this.processGroups.clear();
- this.threadGroups.clear();
+ const processGroups = new Map<number, TrackNode>();
+ const threadGroups = new Map<number, TrackNode>();
// Pre-group all kernel "threads" (actually processes) if this is a linux
// system trace. Below, addProcessTrackGroups will skip them due to an
@@ -57,17 +51,32 @@
// per-thread tracks. Quirk: since all threads will appear to be
// TrackKindPriority.MAIN_THREAD, any process-level tracks will end up
// pushed to the bottom of the group in the UI.
- await this.addKernelThreadGrouping(ctx);
+ await this.addKernelThreadGrouping(ctx, threadGroups);
// Create the per-process track groups. Note that this won't necessarily
// create a track per process. If a process has been completely idle and has
// no sched events, no track group will be emitted.
// Will populate this.addTrackGroupActions
- await this.addProcessGroups(ctx);
- await this.addThreadGroups(ctx);
+ await this.addProcessGroups(ctx, processGroups, threadGroups);
+ await this.addThreadGroups(ctx, processGroups, threadGroups);
+
+ ctx.addEventListener('traceready', () => {
+ // If, by the time the trace has finished loading, some of the process or
+ // thread group tracks nodes have no children, just remove them.
+ const removeIfEmpty = (g: TrackNode) => {
+ if (!g.hasChildren) {
+ g.remove();
+ }
+ };
+ processGroups.forEach(removeIfEmpty);
+ threadGroups.forEach(removeIfEmpty);
+ });
}
- private async addKernelThreadGrouping(ctx: Trace): Promise<void> {
+ private async addKernelThreadGrouping(
+ ctx: Trace,
+ threadGroups: Map<number, TrackNode>,
+ ): Promise<void> {
// Identify kernel threads if this is a linux system trace, and sufficient
// process information is available. Kernel threads are identified by being
// children of kthreadd (always pid 2).
@@ -124,13 +133,17 @@
threadGroup.headless = true;
kernelThreadsGroup.addChildInOrder(threadGroup);
- this.threadGroups.set(utid, threadGroup);
+ threadGroups.set(utid, threadGroup);
}
}
// Adds top level groups for processes and thread that don't belong to a
// process.
- private async addProcessGroups(ctx: Trace): Promise<void> {
+ private async addProcessGroups(
+ ctx: Trace,
+ processGroups: Map<number, TrackNode>,
+ threadGroups: Map<number, TrackNode>,
+ ): Promise<void> {
const result = await ctx.engine.query(`
with processGroups as (
select
@@ -218,7 +231,7 @@
if (kind === 'process') {
// Ignore kernel process groups
- if (this.processGroups.has(uid)) {
+ if (processGroups.has(uid)) {
continue;
}
@@ -241,10 +254,10 @@
// Re-insert the child node to sort it
ctx.workspace.addChildInOrder(group);
- this.processGroups.set(uid, group);
+ processGroups.set(uid, group);
} else {
// Ignore kernel process groups
- if (this.threadGroups.has(uid)) {
+ if (threadGroups.has(uid)) {
continue;
}
@@ -256,14 +269,18 @@
// Re-insert the child node to sort it
ctx.workspace.addChildInOrder(group);
- this.threadGroups.set(uid, group);
+ threadGroups.set(uid, group);
}
}
}
// Create all the nested & headless thread groups that live inside existing
// process groups.
- private async addThreadGroups(ctx: Trace): Promise<void> {
+ private async addThreadGroups(
+ ctx: Trace,
+ processGroups: Map<number, TrackNode>,
+ threadGroups: Map<number, TrackNode>,
+ ): Promise<void> {
const result = await ctx.engine.query(`
with threadGroups as (
select
@@ -312,32 +329,15 @@
const {utid, tid, upid, threadName} = it;
// Ignore kernel thread groups
- if (this.threadGroups.has(utid)) {
+ if (threadGroups.has(utid)) {
continue;
}
const group = getOrCreateGroupForThread(ctx.workspace, utid);
group.title = getThreadDisplayName(threadName ?? undefined, tid);
- this.threadGroups.set(utid, group);
+ threadGroups.set(utid, group);
group.headless = true;
- this.processGroups.get(upid)?.addChildInOrder(group);
+ processGroups.get(upid)?.addChildInOrder(group);
}
}
-
- async onTraceReady(_: Trace): Promise<void> {
- // If, by the time the trace has finished loading, some of the process or
- // thread group tracks nodes have no children, just remove them.
- const removeIfEmpty = (g: TrackNode) => {
- if (!g.hasChildren) {
- g.remove();
- }
- };
- this.processGroups.forEach(removeIfEmpty);
- this.threadGroups.forEach(removeIfEmpty);
- }
}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'perfetto.ProcessThreadGroups',
- plugin: ProcessThreadGroupsPlugin,
-};
diff --git a/ui/src/plugins/dev.perfetto.QueryPage/index.ts b/ui/src/plugins/dev.perfetto.QueryPage/index.ts
new file mode 100644
index 0000000..2fd4b77
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.QueryPage/index.ts
@@ -0,0 +1,32 @@
+// 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 {PerfettoPlugin} from '../../public/plugin';
+import {Trace} from '../../public/trace';
+import {QueryPage} from './query_page';
+
+export default class implements PerfettoPlugin {
+ static readonly id = 'dev.perfetto.QueryPage';
+
+ async onTraceLoad(trace: Trace): Promise<void> {
+ trace.pages.registerPage({route: '/query', page: QueryPage});
+ trace.sidebar.addMenuItem({
+ section: 'current_trace',
+ text: 'Query (SQL)',
+ href: '#!/query',
+ icon: 'database',
+ sortOrder: 1,
+ });
+ }
+}
diff --git a/ui/src/frontend/query_history.ts b/ui/src/plugins/dev.perfetto.QueryPage/query_history.ts
similarity index 92%
rename from ui/src/frontend/query_history.ts
rename to ui/src/plugins/dev.perfetto.QueryPage/query_history.ts
index 1b2129e..a98c987 100644
--- a/ui/src/frontend/query_history.ts
+++ b/ui/src/plugins/dev.perfetto.QueryPage/query_history.ts
@@ -13,15 +13,16 @@
// limitations under the License.
import m from 'mithril';
-import {Icons} from '../base/semantic_icons';
-import {assertTrue} from '../base/logging';
-import {Icon} from '../widgets/icon';
-import {raf} from '../core/raf_scheduler';
+import {Icons} from '../../base/semantic_icons';
+import {assertTrue} from '../../base/logging';
+import {Icon} from '../../widgets/icon';
import {z} from 'zod';
+import {Trace} from '../../public/trace';
const QUERY_HISTORY_KEY = 'queryHistory';
export interface QueryHistoryComponentAttrs {
+ trace: Trace;
runQuery: (query: string) => void;
setQuery: (query: string) => void;
}
@@ -37,7 +38,7 @@
for (let i = queryHistoryStorage.data.length - 1; i >= 0; i--) {
const entry = queryHistoryStorage.data[i];
const arr = entry.starred ? starred : unstarred;
- arr.push({index: i, entry, runQuery, setQuery});
+ arr.push({trace: attrs.trace, index: i, entry, runQuery, setQuery});
}
return m(
'.query-history',
@@ -52,6 +53,7 @@
}
export interface HistoryItemComponentAttrs {
+ trace: Trace;
index: number;
entry: QueryHistoryEntry;
runQuery: (query: string) => void;
@@ -75,7 +77,7 @@
vnode.attrs.index,
!vnode.attrs.entry.starred,
);
- raf.scheduleFullRedraw();
+ vnode.attrs.trace.scheduleFullRedraw();
},
},
m(Icon, {icon: Icons.Star, filled: vnode.attrs.entry.starred}),
@@ -99,7 +101,7 @@
{
onclick: () => {
queryHistoryStorage.remove(vnode.attrs.index);
- raf.scheduleFullRedraw();
+ vnode.attrs.trace.scheduleFullRedraw();
},
},
m(Icon, {icon: 'delete'}),
diff --git a/ui/src/frontend/query_page.ts b/ui/src/plugins/dev.perfetto.QueryPage/query_page.ts
similarity index 82%
rename from ui/src/frontend/query_page.ts
rename to ui/src/plugins/dev.perfetto.QueryPage/query_page.ts
index d5d46d4..9ceaefc 100644
--- a/ui/src/frontend/query_page.ts
+++ b/ui/src/plugins/dev.perfetto.QueryPage/query_page.ts
@@ -13,17 +13,16 @@
// limitations under the License.
import m from 'mithril';
-import {SimpleResizeObserver} from '../base/resize_observer';
-import {undoCommonChatAppReplacements} from '../base/string_utils';
-import {QueryResponse, runQuery} from '../public/lib/query_table/queries';
-import {raf} from '../core/raf_scheduler';
-import {Callout} from '../widgets/callout';
-import {Editor} from '../widgets/editor';
-import {PageWithTraceAttrs} from './pages';
+import {SimpleResizeObserver} from '../../base/resize_observer';
+import {undoCommonChatAppReplacements} from '../../base/string_utils';
+import {QueryResponse, runQuery} from '../../public/lib/query_table/queries';
+import {Callout} from '../../widgets/callout';
+import {Editor} from '../../widgets/editor';
+import {PageWithTraceAttrs} from '../../public/page';
import {QueryHistoryComponent, queryHistoryStorage} from './query_history';
-import {Trace, TraceAttrs} from '../public/trace';
-import {addQueryResultsTab} from '../public/lib/query_table/query_result_tab';
-import {QueryTable} from '../public/lib/query_table/query_table';
+import {Trace, TraceAttrs} from '../../public/trace';
+import {addQueryResultsTab} from '../../public/lib/query_table/query_result_tab';
+import {QueryTable} from '../../public/lib/query_table/query_table';
interface QueryPageState {
enteredText: string;
@@ -59,10 +58,9 @@
return;
}
state.queryResult = resp;
- raf.scheduleFullRedraw();
+ trace.scheduleFullRedraw();
},
);
- raf.scheduleDelayedFullRedraw();
}
export type QueryInputAttrs = TraceAttrs;
@@ -99,7 +97,7 @@
onUpdate: (text: string) => {
state.enteredText = text;
- raf.scheduleFullRedraw();
+ attrs.trace.scheduleFullRedraw();
},
});
}
@@ -122,16 +120,18 @@
state.executedQuery === undefined
? null
: m(QueryTable, {
+ trace: attrs.trace,
query: state.executedQuery,
resp: state.queryResult,
fillParent: false,
}),
m(QueryHistoryComponent, {
+ trace: attrs.trace,
runQuery: (q: string) => runManualQuery(attrs.trace, q),
setQuery: (q: string) => {
state.enteredText = q;
state.generation++;
- raf.scheduleFullRedraw();
+ attrs.trace.scheduleFullRedraw();
},
}),
);
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 88%
rename from ui/src/controller/adb_base_controller.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/adb_base_controller.ts
index beab6c9..c447df5 100644
--- a/ui/src/controller/adb_base_controller.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/adb_base_controller.ts
@@ -12,13 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {exists} from '../base/utils';
-import {isAdbTarget} from '../common/state';
+import {exists} from '../../base/utils';
+import {RecordingState, RecordingTarget, isAdbTarget} from './state';
import {
extractDurationFromTraceConfig,
extractTraceConfig,
-} from '../core/trace_config_utils';
-import {globals} from '../frontend/globals';
+} from './trace_config_utils';
import {Adb} from './adb_interfaces';
import {ReadBuffersResponse} from './consumer_port_types';
import {Consumer, RpcConsumerPort} from './record_controller_interfaces';
@@ -44,10 +43,16 @@
protected adb: Adb;
protected state = AdbConnectionState.READY_TO_CONNECT;
protected device?: USBDevice;
+ protected recState: RecordingState;
- protected constructor(adb: Adb, consumer: Consumer) {
+ protected constructor(
+ adb: Adb,
+ consumer: Consumer,
+ recState: RecordingState,
+ ) {
super(consumer);
this.adb = adb;
+ this.recState = recState;
}
async handleCommand(method: string, params: Uint8Array) {
@@ -74,10 +79,10 @@
this.deviceDisconnected()
) {
this.state = AdbConnectionState.AUTH_IN_PROGRESS;
- this.device = await this.findDevice();
+ this.device = await this.findDevice(this.recState.recordingTarget);
if (!this.device) {
this.state = AdbConnectionState.READY_TO_CONNECT;
- const target = globals.state.recordingTarget;
+ const target = this.recState.recordingTarget;
throw Error(
`Device with serial ${
isAdbTarget(target) ? target.serial : 'n/a'
@@ -91,7 +96,7 @@
await this.adb.connect(this.device);
// During the authentication the device may have been disconnected.
- if (!globals.state.recordingInProgress || this.deviceDisconnected()) {
+ if (!this.recState.recordingInProgress || this.deviceDisconnected()) {
throw Error('Recording not in progress after adb authorization.');
}
@@ -140,9 +145,10 @@
};
}
- async findDevice(): Promise<USBDevice | undefined> {
+ async findDevice(
+ connectedDevice: RecordingTarget,
+ ): Promise<USBDevice | undefined> {
if (!('usb' in navigator)) return undefined;
- const connectedDevice = globals.state.recordingTarget;
if (!isAdbTarget(connectedDevice)) return undefined;
const devices = await navigator.usb.getDevices();
return devices.find((d) => d.serialNumber === connectedDevice.serial);
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 91%
rename from ui/src/controller/adb_record_controller_jsdomtest.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/adb_record_controller_jsdomtest.ts
index c25de72..6078a59 100644
--- a/ui/src/controller/adb_record_controller_jsdomtest.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/adb_record_controller_jsdomtest.ts
@@ -13,11 +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 './empty_state';
function generateMockConsumer(): Consumer {
return {
@@ -28,7 +29,11 @@
}
const mainCallback = generateMockConsumer();
const adbMock = new MockAdb();
-const adbController = new AdbConsumerPort(adbMock, mainCallback);
+const adbController = new AdbConsumerPort(
+ adbMock,
+ mainCallback,
+ createEmptyState(),
+);
const mockIntArray = new Uint8Array();
const enableTracingRequest = new EnableTracingRequest();
@@ -60,7 +65,11 @@
test('enableTracing', async () => {
const mainCallback = generateMockConsumer();
const adbMock = new MockAdb();
- const adbController = new AdbConsumerPort(adbMock, mainCallback);
+ const adbController = new AdbConsumerPort(
+ adbMock,
+ mainCallback,
+ createEmptyState(),
+ );
adbController.sendErrorMessage = jest
.fn()
diff --git a/ui/src/controller/adb_shell_controller.ts b/ui/src/plugins/dev.perfetto.RecordTrace/adb_shell_controller.ts
similarity index 95%
rename from ui/src/controller/adb_shell_controller.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/adb_shell_controller.ts
index 7f7f359..623dc5d 100644
--- a/ui/src/controller/adb_shell_controller.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/adb_shell_controller.ts
@@ -12,8 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {base64Encode, utf8Decode} from '../base/string_utils';
-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';
@@ -31,8 +32,8 @@
shellState: AdbShellState = AdbShellState.READY;
private recordShell?: AdbStream;
- constructor(adb: Adb, consumer: Consumer) {
- super(adb, consumer);
+ constructor(adb: Adb, consumer: Consumer, recState: RecordingState) {
+ super(adb, consumer, recState);
this.adb = adb;
}
diff --git a/ui/src/controller/adb_socket_controller.ts b/ui/src/plugins/dev.perfetto.RecordTrace/adb_socket_controller.ts
similarity index 97%
rename from ui/src/controller/adb_socket_controller.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/adb_socket_controller.ts
index 4908c27..a676747 100644
--- a/ui/src/controller/adb_socket_controller.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/adb_socket_controller.ts
@@ -20,13 +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 {exists} from '../../base/utils';
+import {assertTrue} from '../../base/logging';
+import {RecordingState} from './state';
enum SocketState {
DISCONNECTED,
@@ -82,8 +83,8 @@
private socketCommandQueue: Command[] = [];
- constructor(adb: Adb, consumer: Consumer) {
- super(adb, consumer);
+ constructor(adb: Adb, consumer: Consumer, recState: RecordingState) {
+ super(adb, consumer, recState);
}
async invoke(method: string, params: Uint8Array) {
diff --git a/ui/src/frontend/recording/advanced_settings.ts b/ui/src/plugins/dev.perfetto.RecordTrace/advanced_settings.ts
similarity index 91%
rename from ui/src/frontend/recording/advanced_settings.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/advanced_settings.ts
index c933093..35e6fe2 100644
--- a/ui/src/frontend/recording/advanced_settings.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/advanced_settings.ts
@@ -13,18 +13,7 @@
// limitations under the License.
import m from 'mithril';
-import {
- Dropdown,
- DropdownAttrs,
- Probe,
- ProbeAttrs,
- Slider,
- SliderAttrs,
- Textarea,
- TextareaAttrs,
- Toggle,
- ToggleAttrs,
-} from '../record_widgets';
+import {Dropdown, Probe, Slider, Textarea, Toggle} from './record_widgets';
import {RecordingSectionAttrs} from './recording_sections';
const FTRACE_CATEGORIES = new Map<string, string>();
@@ -52,6 +41,7 @@
implements m.ClassComponent<RecordingSectionAttrs>
{
view({attrs}: m.CVnode<RecordingSectionAttrs>) {
+ const recCfg = attrs.recState.recordConfig;
return m(
`.record-section${attrs.cssClass}`,
m(
@@ -64,7 +54,8 @@
enabled by other probes.`,
setEnabled: (cfg, val) => (cfg.ftrace = val),
isEnabled: (cfg) => cfg.ftrace,
- } as ProbeAttrs,
+ recCfg,
+ },
m(Toggle, {
title: 'Resolve kernel symbols',
cssClass: '.thin',
@@ -73,7 +64,8 @@
(userdebug/eng builds only).`,
setEnabled: (cfg, val) => (cfg.symbolizeKsyms = val),
isEnabled: (cfg) => cfg.symbolizeKsyms,
- } as ToggleAttrs),
+ recCfg,
+ }),
m(Slider, {
title: 'Buf size',
cssClass: '.thin',
@@ -82,7 +74,8 @@
zeroIsDefault: true,
set: (cfg, val) => (cfg.ftraceBufferSizeKb = val),
get: (cfg) => cfg.ftraceBufferSizeKb,
- } as SliderAttrs),
+ recCfg,
+ }),
m(Slider, {
title: 'Drain rate',
cssClass: '.thin',
@@ -91,14 +84,16 @@
zeroIsDefault: true,
set: (cfg, val) => (cfg.ftraceDrainPeriodMs = val),
get: (cfg) => cfg.ftraceDrainPeriodMs,
- } as SliderAttrs),
+ recCfg,
+ }),
m(Dropdown, {
title: 'Event groups',
cssClass: '.multicolumn.ftrace-events',
options: FTRACE_CATEGORIES,
set: (cfg, val) => (cfg.ftraceEvents = val),
get: (cfg) => cfg.ftraceEvents,
- } as DropdownAttrs),
+ recCfg,
+ }),
m(Textarea, {
placeholder:
'Add extra events, one per line, e.g.:\n' +
@@ -106,7 +101,8 @@
'kmem/*',
set: (cfg, val) => (cfg.ftraceExtraEvents = val),
get: (cfg) => cfg.ftraceExtraEvents,
- } as TextareaAttrs),
+ recCfg,
+ }),
),
);
}
diff --git a/ui/src/frontend/recording/android_settings.ts b/ui/src/plugins/dev.perfetto.RecordTrace/android_settings.ts
similarity index 66%
rename from ui/src/frontend/recording/android_settings.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/android_settings.ts
index 25463a5..7c0d741 100644
--- a/ui/src/frontend/recording/android_settings.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/android_settings.ts
@@ -13,21 +13,24 @@
// limitations under the License.
import m from 'mithril';
-import {DataSourceDescriptor} from '../../protos';
-import {globals} from '../globals';
-import {
- Dropdown,
- DropdownAttrs,
- Probe,
- ProbeAttrs,
- Slider,
- SliderAttrs,
- Textarea,
- TextareaAttrs,
- Toggle,
- ToggleAttrs,
-} from '../record_widgets';
+import {AtomId, DataSourceDescriptor} from '../../protos';
+import {Dropdown, Probe, Slider, Textarea, Toggle} from './record_widgets';
import {RecordingSectionAttrs} from './recording_sections';
+import {RecordConfig} from './record_config_types';
+
+const PUSH_ATOM_IDS = new Map<string, string>();
+const PULL_ATOM_IDS = new Map<string, string>();
+for (const key in AtomId) {
+ if (!Object.hasOwn(AtomId, key)) continue;
+ const value = Number(AtomId[key]);
+ if (!isNaN(value)) {
+ if (value > 2 && value < 9999) {
+ PUSH_ATOM_IDS.set(String(value), key);
+ } else if (value >= 10000 && value < 99999) {
+ PULL_ATOM_IDS.set(String(value), key);
+ }
+ }
+}
const LOG_BUFFERS = new Map<string, string>();
LOG_BUFFERS.set('LID_CRASH', 'Crash');
@@ -77,9 +80,13 @@
return false;
}
-class AtraceAppsList implements m.ClassComponent {
- view() {
- if (globals.state.recordConfig.allAtraceApps) {
+interface AtraceAppsListAttrs {
+ recCfg: RecordConfig;
+}
+
+class AtraceAppsList implements m.ClassComponent<AtraceAppsListAttrs> {
+ view({attrs}: m.CVnode<AtraceAppsListAttrs>) {
+ if (attrs.recCfg.allAtraceApps) {
return m('div');
}
@@ -92,7 +99,8 @@
cssClass: '.record-apps-list',
set: (cfg, val) => (cfg.atraceApps = val),
get: (cfg) => cfg.atraceApps,
- } as TextareaAttrs);
+ recCfg: attrs.recCfg,
+ });
}
}
@@ -100,6 +108,7 @@
implements m.ClassComponent<RecordingSectionAttrs>
{
view({attrs}: m.CVnode<RecordingSectionAttrs>) {
+ const recCfg = attrs.recState.recordConfig;
let atraceCategories = DEFAULT_ATRACE_CATEGORIES;
for (const dataSource of attrs.dataSources) {
if (
@@ -132,21 +141,24 @@
os.Trace())`,
setEnabled: (cfg, val) => (cfg.atrace = val),
isEnabled: (cfg) => cfg.atrace,
- } as ProbeAttrs,
+ recCfg,
+ },
m(Dropdown, {
title: 'Categories',
cssClass: '.multicolumn.atrace-categories',
options: atraceCategories,
set: (cfg, val) => (cfg.atraceCats = val),
get: (cfg) => cfg.atraceCats,
- } as DropdownAttrs),
+ recCfg,
+ }),
m(Toggle, {
title: 'Record events from all Android apps and services',
descr: '',
setEnabled: (cfg, val) => (cfg.allAtraceApps = val),
isEnabled: (cfg) => cfg.allAtraceApps,
- } as ToggleAttrs),
- m(AtraceAppsList),
+ recCfg,
+ }),
+ m(AtraceAppsList, {recCfg}),
),
m(
Probe,
@@ -157,14 +169,16 @@
specified, all buffers are selected.`,
setEnabled: (cfg, val) => (cfg.androidLogs = val),
isEnabled: (cfg) => cfg.androidLogs,
- } as ProbeAttrs,
+ recCfg,
+ },
m(Dropdown, {
title: 'Buffers',
cssClass: '.multicolumn',
options: LOG_BUFFERS,
set: (cfg, val) => (cfg.androidLogBuffers = val),
get: (cfg) => cfg.androidLogBuffers,
- } as DropdownAttrs),
+ recCfg,
+ }),
),
m(Probe, {
title: 'Frame timeline',
@@ -173,7 +187,8 @@
Requires Android 12 (S) or above.`,
setEnabled: (cfg, val) => (cfg.androidFrameTimeline = val),
isEnabled: (cfg) => cfg.androidFrameTimeline,
- } as ProbeAttrs),
+ recCfg,
+ }),
m(Probe, {
title: 'Game intervention list',
img: '',
@@ -181,7 +196,8 @@
Requires Android 13 (T) or above.`,
setEnabled: (cfg, val) => (cfg.androidGameInterventionList = val),
isEnabled: (cfg) => cfg.androidGameInterventionList,
- } as ProbeAttrs),
+ recCfg,
+ }),
m(
Probe,
{
@@ -191,7 +207,8 @@
Requires Android 14 (U) or above.`,
setEnabled: (cfg, val) => (cfg.androidNetworkTracing = val),
isEnabled: (cfg) => cfg.androidNetworkTracing,
- } as ProbeAttrs,
+ recCfg,
+ },
m(Slider, {
title: 'Poll interval',
cssClass: '.thin',
@@ -199,7 +216,69 @@
unit: 'ms',
set: (cfg, val) => (cfg.androidNetworkTracingPollMs = val),
get: (cfg) => cfg.androidNetworkTracingPollMs,
- } as SliderAttrs),
+ recCfg,
+ }),
+ ),
+ m(
+ Probe,
+ {
+ title: 'Statsd Atoms',
+ img: '',
+ descr:
+ "Record instances of statsd atoms to the 'Statsd Atoms' track.",
+ setEnabled: (cfg, val) => (cfg.androidStatsd = val),
+ isEnabled: (cfg) => cfg.androidStatsd,
+ recCfg,
+ },
+ m(Dropdown, {
+ title: 'Pushed Atoms',
+ cssClass: '.singlecolumn',
+ options: PUSH_ATOM_IDS,
+ set: (cfg, val) => (cfg.androidStatsdPushedAtoms = val),
+ get: (cfg) => cfg.androidStatsdPushedAtoms,
+ recCfg,
+ }),
+ m(Textarea, {
+ placeholder:
+ 'Add raw pushed atoms IDs, one per line, e.g.:\n' + '818\n' + '819',
+ set: (cfg, val) => (cfg.androidStatsdRawPushedAtoms = val),
+ get: (cfg) => cfg.androidStatsdRawPushedAtoms,
+ recCfg,
+ }),
+ m(Dropdown, {
+ title: 'Pulled Atoms',
+ cssClass: '.singlecolumn',
+ options: PULL_ATOM_IDS,
+ set: (cfg, val) => (cfg.androidStatsdPulledAtoms = val),
+ get: (cfg) => cfg.androidStatsdPulledAtoms,
+ recCfg,
+ }),
+ m(Textarea, {
+ placeholder:
+ 'Add raw pulled atom IDs, one per line, e.g.:\n' +
+ '10063\n' +
+ '10064\n',
+ set: (cfg, val) => (cfg.androidStatsdRawPulledAtoms = val),
+ get: (cfg) => cfg.androidStatsdRawPulledAtoms,
+ recCfg,
+ }),
+ m(Slider, {
+ title: 'Pulled atom pull frequency (ms)',
+ cssClass: '.thin',
+ values: [500, 1000, 5000, 30000, 60000],
+ unit: 'ms',
+ set: (cfg, val) => (cfg.androidStatsdPulledAtomPullFrequencyMs = val),
+ get: (cfg) => cfg.androidStatsdPulledAtomPullFrequencyMs,
+ recCfg,
+ }),
+ m(Textarea, {
+ placeholder:
+ 'Add pulled atom packages, one per line, e.g.:\n' +
+ 'com.android.providers.telephony',
+ set: (cfg, val) => (cfg.androidStatsdPulledAtomPackages = val),
+ get: (cfg) => cfg.androidStatsdPulledAtomPackages,
+ recCfg,
+ }),
),
);
}
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 83%
rename from ui/src/frontend/recording/chrome_settings.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/chrome_settings.ts
index d48bc67..fd09d82 100644
--- a/ui/src/frontend/recording/chrome_settings.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/chrome_settings.ts
@@ -12,24 +12,20 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {produce} from 'immer';
import m from 'mithril';
-import {Actions} from '../../common/actions';
-import {DataSource} from '../../common/recordingV2/recording_interfaces_v2';
-import {getBuiltinChromeCategoryList, isChromeTarget} from '../../common/state';
+import {DataSource} from './recordingV2/recording_interfaces_v2';
+import {
+ RecordingState,
+ getBuiltinChromeCategoryList,
+ isChromeTarget,
+} from './state';
import {
MultiSelect,
MultiSelectDiff,
Option as MultiSelectOption,
} from '../../widgets/multiselect';
import {Section} from '../../widgets/section';
-import {globals} from '../globals';
-import {
- CategoryGetter,
- CompactProbe,
- Toggle,
- ToggleAttrs,
-} from '../record_widgets';
+import {CategoryGetter, CompactProbe, Toggle} from './record_widgets';
import {RecordingSectionAttrs} from './recording_sections';
function extractChromeCategories(
@@ -46,26 +42,28 @@
class ChromeCategoriesSelection
implements m.ClassComponent<RecordingSectionAttrs>
{
+ private recState: RecordingState;
private defaultCategoryOptions: MultiSelectOption[] | undefined = undefined;
private disabledByDefaultCategoryOptions: MultiSelectOption[] | undefined =
undefined;
- static updateValue(attrs: CategoryGetter, diffs: MultiSelectDiff[]) {
- const traceCfg = produce(globals.state.recordConfig, (draft) => {
- const values = attrs.get(draft);
- for (const diff of diffs) {
- const value = diff.id;
- const index = values.indexOf(value);
- const enabled = diff.checked;
- if (enabled && index === -1) {
- values.push(value);
- }
- if (!enabled && index !== -1) {
- values.splice(index, 1);
- }
+ constructor({attrs}: m.CVnode<RecordingSectionAttrs>) {
+ this.recState = attrs.recState;
+ }
+
+ private updateValue(attrs: CategoryGetter, diffs: MultiSelectDiff[]) {
+ const values = attrs.get(this.recState.recordConfig);
+ for (const diff of diffs) {
+ const value = diff.id;
+ const index = values.indexOf(value);
+ const enabled = diff.checked;
+ if (enabled && index === -1) {
+ values.push(value);
}
- });
- globals.dispatch(Actions.setRecordConfig({config: traceCfg}));
+ if (!enabled && index !== -1) {
+ values.splice(index, 1);
+ }
+ }
}
view({attrs}: m.CVnode<RecordingSectionAttrs>) {
@@ -83,12 +81,12 @@
// back to an integrated list of categories from a recent version of
// Chrome.
const enabled = new Set(
- categoryConfigGetter.get(globals.state.recordConfig),
+ categoryConfigGetter.get(this.recState.recordConfig),
);
let categories =
- globals.state.chromeCategories ||
+ attrs.recState.chromeCategories ||
extractChromeCategories(attrs.dataSources);
- if (!categories || !isChromeTarget(globals.state.recordingTarget)) {
+ if (!categories || !isChromeTarget(attrs.recState.recordingTarget)) {
categories = getBuiltinChromeCategoryList();
}
this.defaultCategoryOptions = [];
@@ -139,7 +137,7 @@
}
}
});
- ChromeCategoriesSelection.updateValue(categoryConfigGetter, diffs);
+ this.updateValue(categoryConfigGetter, diffs);
},
}),
),
@@ -161,7 +159,7 @@
}
}
});
- ChromeCategoriesSelection.updateValue(categoryConfigGetter, diffs);
+ this.updateValue(categoryConfigGetter, diffs);
},
}),
),
@@ -171,57 +169,68 @@
export class ChromeSettings implements m.ClassComponent<RecordingSectionAttrs> {
view({attrs}: m.CVnode<RecordingSectionAttrs>) {
+ const recCfg = attrs.recState.recordConfig;
return m(
`.record-section${attrs.cssClass}`,
CompactProbe({
title: 'Task scheduling',
setEnabled: (cfg, val) => (cfg.taskScheduling = val),
isEnabled: (cfg) => cfg.taskScheduling,
+ recCfg,
}),
CompactProbe({
title: 'IPC flows',
setEnabled: (cfg, val) => (cfg.ipcFlows = val),
isEnabled: (cfg) => cfg.ipcFlows,
+ recCfg,
}),
CompactProbe({
title: 'Javascript execution',
setEnabled: (cfg, val) => (cfg.jsExecution = val),
isEnabled: (cfg) => cfg.jsExecution,
+ recCfg,
}),
CompactProbe({
title: 'Web content rendering, layout and compositing',
setEnabled: (cfg, val) => (cfg.webContentRendering = val),
isEnabled: (cfg) => cfg.webContentRendering,
+ recCfg,
}),
CompactProbe({
title: 'UI rendering & surface compositing',
setEnabled: (cfg, val) => (cfg.uiRendering = val),
isEnabled: (cfg) => cfg.uiRendering,
+ recCfg,
}),
CompactProbe({
title: 'Input events',
setEnabled: (cfg, val) => (cfg.inputEvents = val),
isEnabled: (cfg) => cfg.inputEvents,
+ recCfg,
}),
CompactProbe({
title: 'Navigation & Loading',
setEnabled: (cfg, val) => (cfg.navigationAndLoading = val),
isEnabled: (cfg) => cfg.navigationAndLoading,
+ recCfg,
}),
CompactProbe({
title: 'Chrome Logs',
setEnabled: (cfg, val) => (cfg.chromeLogs = val),
isEnabled: (cfg) => cfg.chromeLogs,
+ recCfg,
}),
CompactProbe({
title: 'Audio',
setEnabled: (cfg, val) => (cfg.audio = val),
isEnabled: (cfg) => cfg.audio,
+ recCfg,
}),
CompactProbe({
title: 'Video',
setEnabled: (cfg, val) => (cfg.video = val),
isEnabled: (cfg) => cfg.video,
+ recCfg,
}),
m(Toggle, {
title: 'Remove untyped and sensitive data like URLs from the trace',
@@ -230,7 +239,8 @@
' with third-parties.',
setEnabled: (cfg, val) => (cfg.chromePrivacyFiltering = val),
isEnabled: (cfg) => cfg.chromePrivacyFiltering,
- } as ToggleAttrs),
+ recCfg,
+ }),
m(ChromeCategoriesSelection, attrs),
);
}
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 90%
rename from ui/src/frontend/recording/cpu_settings.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/cpu_settings.ts
index cf20d22..06b2713 100644
--- a/ui/src/frontend/recording/cpu_settings.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/cpu_settings.ts
@@ -13,11 +13,12 @@
// limitations under the License.
import m from 'mithril';
-import {Probe, ProbeAttrs, Slider, SliderAttrs} from '../record_widgets';
+import {Probe, Slider} from './record_widgets';
import {POLL_INTERVAL_MS, RecordingSectionAttrs} from './recording_sections';
export class CpuSettings implements m.ClassComponent<RecordingSectionAttrs> {
view({attrs}: m.CVnode<RecordingSectionAttrs>) {
+ const recCfg = attrs.recState.recordConfig;
return m(
`.record-section${attrs.cssClass}`,
m(
@@ -29,7 +30,8 @@
Allows to periodically monitor CPU usage.`,
setEnabled: (cfg, val) => (cfg.cpuCoarse = val),
isEnabled: (cfg) => cfg.cpuCoarse,
- } as ProbeAttrs,
+ recCfg,
+ },
m(Slider, {
title: 'Poll interval',
cssClass: '.thin',
@@ -37,7 +39,8 @@
unit: 'ms',
set: (cfg, val) => (cfg.cpuCoarsePollMs = val),
get: (cfg) => cfg.cpuCoarsePollMs,
- } as SliderAttrs),
+ recCfg,
+ }),
),
m(Probe, {
title: 'Scheduling details',
@@ -45,7 +48,8 @@
descr: 'Enables high-detailed tracking of scheduling events',
setEnabled: (cfg, val) => (cfg.cpuSched = val),
isEnabled: (cfg) => cfg.cpuSched,
- } as ProbeAttrs),
+ recCfg,
+ }),
m(
Probe,
{
@@ -55,7 +59,8 @@
'Records cpu frequency and idle state changes via ftrace and sysfs',
setEnabled: (cfg, val) => (cfg.cpuFreq = val),
isEnabled: (cfg) => cfg.cpuFreq,
- } as ProbeAttrs,
+ recCfg,
+ },
m(Slider, {
title: 'Sysfs poll interval',
cssClass: '.thin',
@@ -63,7 +68,8 @@
unit: 'ms',
set: (cfg, val) => (cfg.cpuFreqPollMs = val),
get: (cfg) => cfg.cpuFreqPollMs,
- } as SliderAttrs),
+ recCfg,
+ }),
),
m(Probe, {
title: 'Syscalls',
@@ -72,7 +78,8 @@
requires a userdebug or eng build.`,
setEnabled: (cfg, val) => (cfg.cpuSyscall = val),
isEnabled: (cfg) => cfg.cpuSyscall,
- } as ProbeAttrs),
+ recCfg,
+ }),
);
}
}
diff --git a/ui/src/plugins/dev.perfetto.RecordTrace/empty_state.ts b/ui/src/plugins/dev.perfetto.RecordTrace/empty_state.ts
new file mode 100644
index 0000000..bfafe3f
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/empty_state.ts
@@ -0,0 +1,34 @@
+// 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.
+
+import {autosaveConfigStore, recordTargetStore} from './record_config';
+import {RecordingState} from './state';
+
+export function createEmptyState(): RecordingState {
+ return {
+ recordConfig: autosaveConfigStore.get(),
+ lastLoadedConfig: {type: 'NONE'},
+
+ recordingInProgress: false,
+ recordingCancelled: false,
+ extensionInstalled: false,
+ recordingTarget: recordTargetStore.getValidTarget(),
+ availableAdbDevices: [],
+
+ fetchChromeCategories: false,
+ chromeCategories: undefined,
+ bufferUsage: 0,
+ recordingLog: '',
+ };
+}
diff --git a/ui/src/frontend/recording/etw_settings.ts b/ui/src/plugins/dev.perfetto.RecordTrace/etw_settings.ts
similarity index 90%
rename from ui/src/frontend/recording/etw_settings.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/etw_settings.ts
index 53b913a..eefb8ac 100644
--- a/ui/src/frontend/recording/etw_settings.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/etw_settings.ts
@@ -13,11 +13,12 @@
// limitations under the License.
import m from 'mithril';
-import {Probe, ProbeAttrs} from '../record_widgets';
+import {Probe} from './record_widgets';
import {RecordingSectionAttrs} from './recording_sections';
export class EtwSettings implements m.ClassComponent<RecordingSectionAttrs> {
view({attrs}: m.CVnode<RecordingSectionAttrs>) {
+ const recCfg = attrs.recState.recordConfig;
return m(
`.record-section${attrs.cssClass}`,
m(Probe, {
@@ -26,14 +27,16 @@
descr: `Enables to recording of context switches.`,
setEnabled: (cfg, val) => (cfg.etwCSwitch = val),
isEnabled: (cfg) => cfg.etwCSwitch,
- } as ProbeAttrs),
+ recCfg,
+ }),
m(Probe, {
title: 'Dispatcher',
img: null,
descr: 'Enables to get thread state.',
setEnabled: (cfg, val) => (cfg.etwThreadState = val),
isEnabled: (cfg) => cfg.etwThreadState,
- } as ProbeAttrs),
+ recCfg,
+ }),
);
}
}
diff --git a/ui/src/frontend/recording/gpu_settings.ts b/ui/src/plugins/dev.perfetto.RecordTrace/gpu_settings.ts
similarity index 91%
rename from ui/src/frontend/recording/gpu_settings.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/gpu_settings.ts
index 45d0700..1040f75 100644
--- a/ui/src/frontend/recording/gpu_settings.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/gpu_settings.ts
@@ -13,11 +13,12 @@
// limitations under the License.
import m from 'mithril';
-import {Probe, ProbeAttrs} from '../record_widgets';
+import {Probe} from './record_widgets';
import {RecordingSectionAttrs} from './recording_sections';
export class GpuSettings implements m.ClassComponent<RecordingSectionAttrs> {
view({attrs}: m.CVnode<RecordingSectionAttrs>) {
+ const recCfg = attrs.recState.recordConfig;
return m(
`.record-section${attrs.cssClass}`,
m(Probe, {
@@ -26,7 +27,8 @@
descr: 'Records gpu frequency via ftrace',
setEnabled: (cfg, val) => (cfg.gpuFreq = val),
isEnabled: (cfg) => cfg.gpuFreq,
- } as ProbeAttrs),
+ recCfg,
+ }),
m(Probe, {
title: 'GPU memory',
img: 'rec_gpu_mem_total.png',
@@ -34,7 +36,8 @@
(Available on recent Android 12+ kernels)`,
setEnabled: (cfg, val) => (cfg.gpuMemTotal = val),
isEnabled: (cfg) => cfg.gpuMemTotal,
- } as ProbeAttrs),
+ recCfg,
+ }),
m(Probe, {
title: 'GPU work period',
img: 'rec_cpu_voltage.png',
@@ -42,7 +45,8 @@
(Available on recent Android 14+ kernels)`,
setEnabled: (cfg, val) => (cfg.gpuWorkPeriod = val),
isEnabled: (cfg) => cfg.gpuWorkPeriod,
- } as ProbeAttrs),
+ recCfg,
+ }),
);
}
}
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 90%
rename from ui/src/frontend/recording/linux_perf_settings.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/linux_perf_settings.ts
index 00f1324..a0fcf9f 100644
--- a/ui/src/frontend/recording/linux_perf_settings.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/linux_perf_settings.ts
@@ -13,14 +13,7 @@
// limitations under the License.
import m from 'mithril';
-import {
- Probe,
- ProbeAttrs,
- Slider,
- SliderAttrs,
- Textarea,
- TextareaAttrs,
-} 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.:
@@ -37,6 +30,7 @@
{
config = {targets: []} as LinuxPerfConfiguration;
view({attrs}: m.CVnode<RecordingSectionAttrs>) {
+ const recCfg = attrs.recState.recordConfig;
return m(
`.record-section${attrs.cssClass}`,
m(
@@ -48,7 +42,8 @@
function calls) of processes.`,
setEnabled: (cfg, val) => (cfg.tracePerf = val),
isEnabled: (cfg) => cfg.tracePerf,
- } as ProbeAttrs,
+ recCfg,
+ },
m(Slider, {
title: 'Sampling Frequency',
cssClass: '.thin',
@@ -56,7 +51,8 @@
unit: 'hz',
set: (cfg, val) => (cfg.timebaseFrequency = val),
get: (cfg) => cfg.timebaseFrequency,
- } as SliderAttrs),
+ recCfg,
+ }),
m(Textarea, {
placeholder: PLACEHOLDER_TEXT,
cssClass: '.record-apps-list',
@@ -64,7 +60,8 @@
cfg.targetCmdLine = val.split('\n');
},
get: (cfg) => cfg.targetCmdLine.join('\n'),
- } as TextareaAttrs),
+ recCfg,
+ }),
),
);
}
diff --git a/ui/src/frontend/recording/memory_settings.ts b/ui/src/plugins/dev.perfetto.RecordTrace/memory_settings.ts
similarity index 89%
rename from ui/src/frontend/recording/memory_settings.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/memory_settings.ts
index 857051f..231306f 100644
--- a/ui/src/frontend/recording/memory_settings.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/memory_settings.ts
@@ -14,19 +14,7 @@
import m from 'mithril';
import {MeminfoCounters, VmstatCounters} from '../../protos';
-import {globals} from '../globals';
-import {
- Dropdown,
- DropdownAttrs,
- Probe,
- ProbeAttrs,
- Slider,
- SliderAttrs,
- Textarea,
- TextareaAttrs,
- Toggle,
- ToggleAttrs,
-} 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> {
@@ -61,7 +49,7 @@
256 * 1024 * 1024,
512 * 1024 * 1024,
];
-
+ const recCfg = attrs.recState.recordConfig;
return m(
`.${attrs.cssClass}`,
m(Textarea, {
@@ -75,7 +63,8 @@
'1503',
set: (cfg, val) => (cfg.hpProcesses = val),
get: (cfg) => cfg.hpProcesses,
- } as TextareaAttrs),
+ recCfg,
+ }),
m(Slider, {
title: 'Sampling interval',
cssClass: '.thin',
@@ -87,7 +76,8 @@
min: 0,
set: (cfg, val) => (cfg.hpSamplingIntervalBytes = val),
get: (cfg) => cfg.hpSamplingIntervalBytes,
- } as SliderAttrs),
+ recCfg,
+ }),
m(Slider, {
title: 'Continuous dumps interval ',
description: 'Time between following dumps (0 = disabled)',
@@ -99,22 +89,24 @@
cfg.hpContinuousDumpsInterval = val;
},
get: (cfg) => cfg.hpContinuousDumpsInterval,
- } as SliderAttrs),
+ recCfg,
+ }),
m(Slider, {
title: 'Continuous dumps phase',
description: 'Time before first dump',
cssClass: `.thin${
- globals.state.recordConfig.hpContinuousDumpsInterval === 0
+ attrs.recState.recordConfig.hpContinuousDumpsInterval === 0
? '.greyed-out'
: ''
}`,
values: valuesForMS,
unit: 'ms',
min: 0,
- disabled: globals.state.recordConfig.hpContinuousDumpsInterval === 0,
+ disabled: attrs.recState.recordConfig.hpContinuousDumpsInterval === 0,
set: (cfg, val) => (cfg.hpContinuousDumpsPhase = val),
get: (cfg) => cfg.hpContinuousDumpsPhase,
- } as SliderAttrs),
+ recCfg,
+ }),
m(Slider, {
title: `Shared memory buffer`,
cssClass: '.thin',
@@ -125,14 +117,16 @@
min: 0,
set: (cfg, val) => (cfg.hpSharedMemoryBuffer = val),
get: (cfg) => cfg.hpSharedMemoryBuffer,
- } as SliderAttrs),
+ recCfg,
+ }),
m(Toggle, {
title: 'Block client',
cssClass: '.thin',
descr: `Slow down target application if profiler cannot keep up.`,
setEnabled: (cfg, val) => (cfg.hpBlockClient = val),
isEnabled: (cfg) => cfg.hpBlockClient,
- } as ToggleAttrs),
+ recCfg,
+ }),
m(Toggle, {
title: 'All custom allocators (Q+)',
cssClass: '.thin',
@@ -140,7 +134,8 @@
sample from those.`,
setEnabled: (cfg, val) => (cfg.hpAllHeaps = val),
isEnabled: (cfg) => cfg.hpAllHeaps,
- } as ToggleAttrs),
+ recCfg,
+ }),
// TODO(hjd): Add advanced options.
);
}
@@ -159,7 +154,7 @@
30 * 60 * 1000,
60 * 60 * 1000,
];
-
+ const recCfg = attrs.recState.recordConfig;
return m(
`.${attrs.cssClass}`,
m(Textarea, {
@@ -167,7 +162,8 @@
placeholder: 'One per line, e.g.:\n' + 'com.android.vending\n' + '1503',
set: (cfg, val) => (cfg.jpProcesses = val),
get: (cfg) => cfg.jpProcesses,
- } as TextareaAttrs),
+ recCfg,
+ }),
m(Slider, {
title: 'Continuous dumps interval ',
description: 'Time between following dumps (0 = disabled)',
@@ -179,28 +175,31 @@
cfg.jpContinuousDumpsInterval = val;
},
get: (cfg) => cfg.jpContinuousDumpsInterval,
- } as SliderAttrs),
+ recCfg,
+ }),
m(Slider, {
title: 'Continuous dumps phase',
description: 'Time before first dump',
cssClass: `.thin${
- globals.state.recordConfig.jpContinuousDumpsInterval === 0
+ attrs.recState.recordConfig.jpContinuousDumpsInterval === 0
? '.greyed-out'
: ''
}`,
values: valuesForMS,
unit: 'ms',
min: 0,
- disabled: globals.state.recordConfig.jpContinuousDumpsInterval === 0,
+ disabled: attrs.recState.recordConfig.jpContinuousDumpsInterval === 0,
set: (cfg, val) => (cfg.jpContinuousDumpsPhase = val),
get: (cfg) => cfg.jpContinuousDumpsPhase,
- } as SliderAttrs),
+ recCfg,
+ }),
);
}
}
export class MemorySettings implements m.ClassComponent<RecordingSectionAttrs> {
view({attrs}: m.CVnode<RecordingSectionAttrs>) {
+ const recCfg = attrs.recState.recordConfig;
const meminfoOpts = new Map<string, string>();
for (const x in MeminfoCounters) {
if (
@@ -230,7 +229,8 @@
process. (Available on Android 10+)`,
setEnabled: (cfg, val) => (cfg.heapProfiling = val),
isEnabled: (cfg) => cfg.heapProfiling,
- } as ProbeAttrs,
+ recCfg,
+ },
m(HeapSettings, attrs),
),
m(
@@ -242,7 +242,8 @@
Android app. (Available on Android 11+)`,
setEnabled: (cfg, val) => (cfg.javaHeapDump = val),
isEnabled: (cfg) => cfg.javaHeapDump,
- } as ProbeAttrs,
+ recCfg,
+ },
m(JavaHeapDumpSettings, attrs),
),
m(
@@ -253,7 +254,8 @@
descr: 'Polling of /proc/meminfo',
setEnabled: (cfg, val) => (cfg.meminfo = val),
isEnabled: (cfg) => cfg.meminfo,
- } as ProbeAttrs,
+ recCfg,
+ },
m(Slider, {
title: 'Poll interval',
cssClass: '.thin',
@@ -261,14 +263,16 @@
unit: 'ms',
set: (cfg, val) => (cfg.meminfoPeriodMs = val),
get: (cfg) => cfg.meminfoPeriodMs,
- } as SliderAttrs),
+ recCfg,
+ }),
m(Dropdown, {
title: 'Select counters',
cssClass: '.multicolumn',
options: meminfoOpts,
set: (cfg, val) => (cfg.meminfoCounters = val),
get: (cfg) => cfg.meminfoCounters,
- } as DropdownAttrs),
+ recCfg,
+ }),
),
m(Probe, {
title: 'High-frequency memory events',
@@ -278,7 +282,8 @@
on recent Android Q+ kernels`,
setEnabled: (cfg, val) => (cfg.memHiFreq = val),
isEnabled: (cfg) => cfg.memHiFreq,
- } as ProbeAttrs),
+ recCfg,
+ }),
m(Probe, {
title: 'Low memory killer',
img: 'rec_lmk.png',
@@ -287,7 +292,8 @@
adjustments.`,
setEnabled: (cfg, val) => (cfg.memLmk = val),
isEnabled: (cfg) => cfg.memLmk,
- } as ProbeAttrs),
+ recCfg,
+ }),
m(
Probe,
{
@@ -298,7 +304,8 @@
/proc/status counters) and oom_score_adj.`,
setEnabled: (cfg, val) => (cfg.procStats = val),
isEnabled: (cfg) => cfg.procStats,
- } as ProbeAttrs,
+ recCfg,
+ },
m(Slider, {
title: 'Poll interval',
cssClass: '.thin',
@@ -306,7 +313,8 @@
unit: 'ms',
set: (cfg, val) => (cfg.procStatsPeriodMs = val),
get: (cfg) => cfg.procStatsPeriodMs,
- } as SliderAttrs),
+ recCfg,
+ }),
),
m(
Probe,
@@ -318,7 +326,8 @@
compression and pagecache efficiency`,
setEnabled: (cfg, val) => (cfg.vmstat = val),
isEnabled: (cfg) => cfg.vmstat,
- } as ProbeAttrs,
+ recCfg,
+ },
m(Slider, {
title: 'Poll interval',
cssClass: '.thin',
@@ -326,14 +335,16 @@
unit: 'ms',
set: (cfg, val) => (cfg.vmstatPeriodMs = val),
get: (cfg) => cfg.vmstatPeriodMs,
- } as SliderAttrs),
+ recCfg,
+ }),
m(Dropdown, {
title: 'Select counters',
cssClass: '.multicolumn',
options: vmstatOpts,
set: (cfg, val) => (cfg.vmstatCounters = val),
get: (cfg) => cfg.vmstatCounters,
- } as DropdownAttrs),
+ recCfg,
+ }),
),
);
}
diff --git a/ui/src/frontend/recording/power_settings.ts b/ui/src/plugins/dev.perfetto.RecordTrace/power_settings.ts
similarity index 89%
rename from ui/src/frontend/recording/power_settings.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/power_settings.ts
index 318904e..bf88217 100644
--- a/ui/src/frontend/recording/power_settings.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/power_settings.ts
@@ -13,12 +13,13 @@
// limitations under the License.
import m from 'mithril';
-import {globals} from '../globals';
-import {Probe, ProbeAttrs, Slider, SliderAttrs} 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> {
view({attrs}: m.CVnode<RecordingSectionAttrs>) {
+ const recCfg = attrs.recState.recordConfig;
const DOC_URL = 'https://perfetto.dev/docs/data-sources/battery-counters';
const descr = [
m(
@@ -33,6 +34,7 @@
m('span', ')'),
),
];
+ // TODO(primiano): figure out a better story for isInternalUser.
if (globals.isInternalUser) {
descr.push(
m(
@@ -61,7 +63,8 @@
descr,
setEnabled: (cfg, val) => (cfg.batteryDrain = val),
isEnabled: (cfg) => cfg.batteryDrain,
- } as ProbeAttrs,
+ recCfg,
+ },
m(Slider, {
title: 'Poll interval',
cssClass: '.thin',
@@ -69,7 +72,8 @@
unit: 'ms',
set: (cfg, val) => (cfg.batteryDrainPollMs = val),
get: (cfg) => cfg.batteryDrainPollMs,
- } as SliderAttrs),
+ recCfg,
+ }),
),
m(Probe, {
title: 'Board voltages & frequencies',
@@ -77,7 +81,8 @@
descr: 'Tracks voltage and frequency changes from board sensors',
setEnabled: (cfg, val) => (cfg.boardSensors = val),
isEnabled: (cfg) => cfg.boardSensors,
- } as ProbeAttrs),
+ recCfg,
+ }),
);
}
}
diff --git a/ui/src/frontend/record_config.ts b/ui/src/plugins/dev.perfetto.RecordTrace/record_config.ts
similarity index 96%
rename from ui/src/frontend/record_config.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/record_config.ts
index 0201dc1..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';
@@ -156,7 +156,7 @@
export const recordConfigStore = new RecordConfigStore();
export class AutosaveConfigStore {
- config: RecordConfig;
+ private config: RecordConfig;
// Whether the current config is a default one or has been saved before.
// Used to determine whether the button to load "last started config" should
diff --git a/ui/src/controller/record_config_types.ts b/ui/src/plugins/dev.perfetto.RecordTrace/record_config_types.ts
similarity index 91%
rename from ui/src/controller/record_config_types.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/record_config_types.ts
index e97bc0f..199158a 100644
--- a/ui/src/controller/record_config_types.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/record_config_types.ts
@@ -47,6 +47,13 @@
androidGameInterventionList: z.boolean().default(false),
androidNetworkTracing: z.boolean().default(false),
androidNetworkTracingPollMs: z.number().default(250),
+ androidStatsd: z.boolean().default(false),
+ androidStatsdRawPushedAtoms: z.string().default(''),
+ androidStatsdRawPulledAtoms: z.string().default(''),
+ androidStatsdPushedAtoms: z.array(z.string()).default([]),
+ androidStatsdPulledAtoms: z.array(z.string()).default([]),
+ androidStatsdPulledAtomPackages: z.string().default(''),
+ androidStatsdPulledAtomPullFrequencyMs: z.number().default(5000),
cpuCoarse: z.boolean().default(false),
cpuCoarsePollMs: z.number().default(1000),
diff --git a/ui/src/controller/record_controller.ts b/ui/src/plugins/dev.perfetto.RecordTrace/record_controller.ts
similarity index 83%
rename from ui/src/controller/record_controller.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/record_controller.ts
index 757ea1f..8062650 100644
--- a/ui/src/controller/record_controller.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/record_controller.ts
@@ -13,22 +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 {Actions} from '../common/actions';
-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 {globals} from '../frontend/globals';
-import {publishBufferUsage, publishTrackData} from '../frontend/publish';
-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';
@@ -42,9 +39,11 @@
isGetTraceStatsResponse,
isReadBuffersResponse,
} from './consumer_port_types';
-import {Controller} from './controller';
import {RecordConfig} from './record_config_types';
import {Consumer, RpcConsumerPort} from './record_controller_interfaces';
+import {RecordingManager} from './recording_manager';
+import {scheduleFullRedraw} from '../../widgets/raf';
+import {App} from '../../public/app';
type RPCImplMethod = Method | rpc.ServiceMethod<Message<{}>, Message<{}>>;
@@ -133,6 +132,7 @@
value.startsWith('STAT_') ||
value.startsWith('LID_') ||
value.startsWith('BATTERY_COUNTER_') ||
+ value.startsWith('ATOM_') ||
value === 'DISCARD' ||
value === 'RING_BUFFER' ||
value === 'BACKGROUND' ||
@@ -188,7 +188,9 @@
return [...message(json, 0)].join('');
}
-export class RecordController extends Controller<'main'> implements Consumer {
+export class RecordController implements Consumer {
+ private app: App;
+ private recMgr: RecordingManager;
private config: RecordConfig | null = null;
private readonly extensionPort: MessagePort;
private recordingInProgress = false;
@@ -205,34 +207,32 @@
// char, it is the 'targetOS'
private controllerPromises = new Map<string, Promise<RpcConsumerPort>>();
- constructor(args: {extensionPort: MessagePort}) {
- super('main');
+ constructor(app: App, recMgr: RecordingManager, extensionPort: MessagePort) {
+ this.app = app;
+ this.recMgr = recMgr;
this.consumerPort = ConsumerPort.create(this.rpcImpl.bind(this));
- this.extensionPort = args.extensionPort;
+ this.extensionPort = extensionPort;
}
- run() {
+ private get state() {
+ return this.recMgr.state;
+ }
+
+ refreshOnStateChange() {
// TODO(eseckler): Use ConsumerPort's QueryServiceState instead
// of posting a custom extension message to retrieve the category list.
- if (globals.state.fetchChromeCategories && !this.fetchedCategories) {
+ scheduleFullRedraw();
+ if (this.state.fetchChromeCategories && !this.fetchedCategories) {
this.fetchedCategories = true;
- if (globals.state.extensionInstalled) {
+ if (this.state.extensionInstalled) {
this.extensionPort.postMessage({method: 'GetCategories'});
}
- globals.dispatch(Actions.setFetchChromeCategories({fetch: false}));
+ this.recMgr.setFetchChromeCategories(false);
}
- if (
- globals.state.recordConfig === this.config &&
- globals.state.recordingInProgress === this.recordingInProgress
- ) {
- return;
- }
- this.config = globals.state.recordConfig;
- const configProto = genConfigProto(
- this.config,
- globals.state.recordingTarget,
- );
+ this.config = this.state.recordConfig;
+
+ const configProto = genConfigProto(this.config, this.state.recordingTarget);
const configProtoText = toPbtxt(configProto);
const configProtoBase64 = base64Encode(configProto);
const commandline = `
@@ -243,23 +243,18 @@
`;
const traceConfig = convertToRecordingV2Input(
this.config,
- globals.state.recordingTarget,
+ this.state.recordingTarget,
);
- // TODO(hjd): This should not be TrackData after we unify the stores.
- publishTrackData({
- id: 'config',
- data: {
- commandline,
- pbBase64: configProtoBase64,
- pbtxt: configProtoText,
- traceConfig,
- },
- });
+ this.state.recordCmd = {
+ commandline,
+ pbBase64: configProtoBase64,
+ pbtxt: configProtoText,
+ };
// If the recordingInProgress boolean state is different, it means that we
// have to start or stop recording a trace.
- if (globals.state.recordingInProgress === this.recordingInProgress) return;
- this.recordingInProgress = globals.state.recordingInProgress;
+ if (this.state.recordingInProgress === this.recordingInProgress) return;
+ this.recordingInProgress = this.state.recordingInProgress;
if (this.recordingInProgress) {
this.startRecordTrace(traceConfig);
@@ -276,6 +271,7 @@
stopRecordTrace() {
if (this.bufferUpdateInterval) clearInterval(this.bufferUpdateInterval);
+ this.consumerPort.flush({});
this.consumerPort.disableTracing({});
}
@@ -306,7 +302,7 @@
} else if (isGetTraceStatsResponse(data)) {
const percentage = this.getBufferUsagePercentage(data);
if (percentage) {
- publishBufferUsage({percentage});
+ this.recMgr.state.bufferUsage = percentage;
}
} else if (isFreeBuffersResponse(data)) {
// No action required.
@@ -319,22 +315,18 @@
onTraceComplete() {
this.consumerPort.freeBuffers({});
- globals.dispatch(Actions.setRecordingStatus({status: undefined}));
- if (globals.state.recordingCancelled) {
- globals.dispatch(
- Actions.setLastRecordingError({error: 'Recording cancelled.'}),
- );
+ this.recMgr.setRecordingStatus(undefined);
+ if (this.state.recordingCancelled) {
+ this.recMgr.setLastRecordingError('Recording cancelled.');
this.traceBuffer = [];
return;
}
const trace = this.generateTrace();
- globals.dispatch(
- Actions.openTraceFromBuffer({
- title: 'Recorded trace',
- buffer: trace.buffer,
- fileName: `recorded_trace${this.recordedTraceSuffix}`,
- }),
- );
+ this.app.openTraceFromBuffer({
+ title: 'Recorded trace',
+ buffer: trace.buffer,
+ fileName: `recorded_trace${this.recordedTraceSuffix}`,
+ });
this.traceBuffer = [];
}
@@ -366,14 +358,12 @@
onError(message: string) {
// TODO(octaviant): b/204998302
console.error('Error in record controller: ', message);
- globals.dispatch(
- Actions.setLastRecordingError({error: message.substr(0, 150)}),
- );
- globals.dispatch(Actions.stopRecording({}));
+ this.recMgr.setLastRecordingError(message.substring(0, 150));
+ this.recMgr.stopRecording();
}
onStatus(message: string) {
- globals.dispatch(Actions.setRecordingStatus({status: message}));
+ this.recMgr.setRecordingStatus(message);
}
// Depending on the recording target, different implementation of the
@@ -408,8 +398,8 @@
const socketAccess = await this.hasSocketAccess(target);
controller = socketAccess
- ? new AdbSocketConsumerPort(this.adb, this)
- : new AdbConsumerPort(this.adb, this);
+ ? new AdbSocketConsumerPort(this.adb, this, this.recMgr.state)
+ : new AdbConsumerPort(this.adb, this, this.recMgr.state);
} else {
throw Error(`No device connected`);
}
@@ -443,7 +433,7 @@
_callback: RPCImplCallback,
) {
try {
- const state = globals.state;
+ const state = this.state;
// TODO(hjd): This is a bit weird. We implicitly send each RPC message to
// whichever target is currently selected (creating that target if needed)
// it would be nicer if the setup/teardown was more explicit.
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 62%
rename from ui/src/frontend/record_page.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/record_page.ts
index c9aac81..021a5db 100644
--- a/ui/src/frontend/record_page.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/record_page.ts
@@ -13,9 +13,7 @@
// limitations under the License.
import m from 'mithril';
-import {Actions} from '../common/actions';
import {
- AdbRecordingTarget,
getDefaultRecordingTargets,
hasActiveProbes,
isAdbTarget,
@@ -28,41 +26,32 @@
LoadedConfig,
MAX_TIME,
RecordingTarget,
-} from '../common/state';
-import {AdbOverWebUsb} from '../controller/adb';
-import {
- createEmptyRecordConfig,
- RecordConfig,
-} from '../controller/record_config_types';
-import {featureFlags} from '../core/feature_flags';
-import {raf} from '../core/raf_scheduler';
-import {globals} from './globals';
-import {PageAttrs} from './pages';
+} 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 {RecordingSectionAttrs} from './recording/recording_sections';
-import {RecordingSettings} from './recording/recording_settings';
-import {EtwSettings} from './recording/etw_settings';
-import {createPermalink} from './permalink';
-
-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',
@@ -79,28 +68,28 @@
'advanced',
];
-function RecordHeader() {
+function RecordHeader(recMgr: RecordingManager) {
return m(
'.record-header',
m(
'.top-part',
m(
'.target-and-status',
- RecordingPlatformSelection(),
- RecordingStatusLabel(),
- ErrorLabel(),
+ RecordingPlatformSelection(recMgr),
+ RecordingStatusLabel(recMgr),
+ ErrorLabel(recMgr),
),
- recordingButtons(),
+ recordingButtons(recMgr),
),
- RecordingNotes(),
+ RecordingNotes(recMgr),
);
}
-function RecordingPlatformSelection() {
- if (globals.state.recordingInProgress) return [];
+function RecordingPlatformSelection(recMgr: RecordingManager) {
+ if (recMgr.state.recordingInProgress) return [];
- const availableAndroidDevices = globals.state.availableAdbDevices;
- const recordingTarget = globals.state.recordingTarget;
+ const availableAndroidDevices = recMgr.state.availableAdbDevices;
+ const recordingTarget = recMgr.state.recordingTarget;
const targets = [];
for (const {os, name} of getDefaultRecordingTargets()) {
@@ -124,7 +113,7 @@
{
selectedIndex,
onchange: (e: Event) => {
- onTargetChange((e.target as HTMLSelectElement).value);
+ onTargetChange(recMgr, (e.target as HTMLSelectElement).value);
},
onupdate: (select) => {
// Work around mithril bug
@@ -143,7 +132,7 @@
),
m(
'.chip',
- {onclick: addAndroidDevice},
+ {onclick: () => addAndroidDevice(recMgr)},
m('button', 'Add ADB Device'),
m('i.material-icons', 'add'),
),
@@ -151,38 +140,36 @@
}
// |target| can be the TargetOs or the android serial.
-function onTargetChange(target: string) {
+function onTargetChange(recMgr: RecordingManager, target: string) {
const recordingTarget: RecordingTarget =
- globals.state.availableAdbDevices.find((d) => d.serial === target) ||
+ recMgr.state.availableAdbDevices.find((d) => d.serial === target) ||
getDefaultRecordingTargets().find((t) => t.os === target) ||
getDefaultRecordingTargets()[0];
if (isChromeTarget(recordingTarget)) {
- globals.dispatch(Actions.setFetchChromeCategories({fetch: true}));
+ recMgr.setFetchChromeCategories(true);
}
- globals.dispatch(Actions.setRecordingTarget({target: recordingTarget}));
+ recMgr.setRecordingTarget(recordingTarget);
recordTargetStore.save(target);
- raf.scheduleFullRedraw();
+ scheduleFullRedraw();
}
-function Instructions(cssClass: string) {
+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,
- RecordingSnippet(),
- BufferUsageProgressBar(),
- m('.buttons', StopCancelButtons()),
- recordingLog(),
+ m(
+ 'button.permalinkconfig',
+ {
+ onclick: () => uploadRecordingConfig(recMgr.state.recordConfig),
+ },
+ 'Share recording settings',
+ ),
+ RecordingSnippet(recMgr),
+ BufferUsageProgressBar(recMgr),
+ m('.buttons', StopCancelButtons(recMgr)),
+ recordingLog(recMgr),
);
}
@@ -196,6 +183,7 @@
}
export function loadConfigButton(
+ recMgr: RecordingManager,
config: RecordConfig,
configType: LoadedConfig,
): m.Vnode {
@@ -204,23 +192,25 @@
{
class: 'config-button',
title: 'Apply configuration settings',
- disabled: loadedConfigEqual(configType, globals.state.lastLoadedConfig),
+ disabled: loadedConfigEqual(configType, recMgr.state.lastLoadedConfig),
onclick: () => {
- globals.dispatch(Actions.setRecordConfig({config, configType}));
- raf.scheduleFullRedraw();
+ recMgr.setRecordConfig(config, configType);
+ scheduleFullRedraw();
},
},
m('i.material-icons', 'file_upload'),
);
}
-export function displayRecordConfigs() {
+export function displayRecordConfigs(recMgr: RecordingManager) {
const configs = [];
if (autosaveConfigStore.hasSavedConfig) {
configs.push(
m('.config', [
m('span.title-config', m('strong', 'Latest started recording')),
- loadConfigButton(autosaveConfigStore.get(), {type: 'AUTOMATIC'}),
+ loadConfigButton(recMgr, autosaveConfigStore.get(), {
+ type: 'AUTOMATIC',
+ }),
]),
);
}
@@ -228,7 +218,10 @@
configs.push(
m('.config', [
m('span.title-config', item.title),
- loadConfigButton(item.config, {type: 'NAMED', name: item.title}),
+ loadConfigButton(recMgr, item.config, {
+ type: 'NAMED',
+ name: item.title,
+ }),
m(
'button',
{
@@ -241,16 +234,14 @@
)
) {
recordConfigStore.overwrite(
- globals.state.recordConfig,
+ recMgr.state.recordConfig,
item.key,
);
- globals.dispatch(
- Actions.setRecordConfig({
- config: item.config,
- configType: {type: 'NAMED', name: item.title},
- }),
- );
- raf.scheduleFullRedraw();
+ recMgr.setRecordConfig(item.config, {
+ type: 'NAMED',
+ name: item.title,
+ });
+ scheduleFullRedraw();
}
},
},
@@ -263,7 +254,7 @@
title: 'Remove configuration',
onclick: () => {
recordConfigStore.delete(item.key);
- raf.scheduleFullRedraw();
+ scheduleFullRedraw();
},
},
m('i.material-icons', 'delete'),
@@ -287,7 +278,7 @@
},
};
-export function Configurations(cssClass: string) {
+export function Configurations(recMgr: RecordingManager, cssClass: string) {
const canSave = recordConfigStore.canSave(ConfigTitleState.getTitle());
return m(
`.record-section${cssClass}`,
@@ -298,7 +289,7 @@
placeholder: 'Title for config',
oninput() {
ConfigTitleState.setTitle(this.value);
- raf.scheduleFullRedraw();
+ scheduleFullRedraw();
},
}),
m(
@@ -311,10 +302,10 @@
: 'Duplicate name, saving disabled',
onclick: () => {
recordConfigStore.save(
- globals.state.recordConfig,
+ recMgr.state.recordConfig,
ConfigTitleState.getTitle(),
);
- raf.scheduleFullRedraw();
+ scheduleFullRedraw();
ConfigTitleState.clearTitle();
},
},
@@ -331,27 +322,22 @@
'Current configuration will be cleared. ' + 'Are you sure?',
)
) {
- globals.dispatch(
- Actions.setRecordConfig({
- config: createEmptyRecordConfig(),
- configType: {type: 'NONE'},
- }),
- );
- raf.scheduleFullRedraw();
+ recMgr.clearRecordConfig();
+ scheduleFullRedraw();
}
},
},
m('i.material-icons', 'delete_forever'),
),
]),
- displayRecordConfigs(),
+ displayRecordConfigs(recMgr),
);
}
-function BufferUsageProgressBar() {
- if (!globals.state.recordingInProgress) return [];
+function BufferUsageProgressBar(recMgr: RecordingManager) {
+ if (!recMgr.state.recordingInProgress) return [];
- const bufferUsage = globals.bufferUsage ?? 0.0;
+ const bufferUsage = recMgr.state.bufferUsage;
// Buffer usage is not available yet on Android.
if (bufferUsage === 0) return [];
@@ -362,7 +348,7 @@
);
}
-function RecordingNotes() {
+function RecordingNotes(recMgr: RecordingManager) {
const sideloadUrl =
'https://perfetto.dev/docs/contributing/build-instructions#get-the-code';
const linuxUrl = 'https://perfetto.dev/docs/quickstart/linux-tracing';
@@ -445,14 +431,14 @@
'Please add at least one to get a non-empty trace.',
);
- if (!hasActiveProbes(globals.state.recordConfig)) {
+ if (!hasActiveProbes(recMgr.state.recordConfig)) {
notes.push(msgZeroProbes);
}
- if (isAdbTarget(globals.state.recordingTarget)) {
+ if (isAdbTarget(recMgr.state.recordingTarget)) {
notes.push(msgRecordingNotSupported);
}
- switch (globals.state.recordingTarget.os) {
+ switch (recMgr.state.recordingTarget.os) {
case 'Q':
break;
case 'P':
@@ -465,30 +451,29 @@
notes.push(msgLinux);
break;
case 'C':
- if (!globals.state.extensionInstalled) notes.push(msgChrome);
+ if (!recMgr.state.extensionInstalled) notes.push(msgChrome);
break;
case 'CrOS':
- if (!globals.state.extensionInstalled) notes.push(msgChrome);
+ if (!recMgr.state.extensionInstalled) notes.push(msgChrome);
break;
case 'Win':
- if (!globals.state.extensionInstalled) notes.push(msgWinEtw);
+ if (!recMgr.state.extensionInstalled) notes.push(msgWinEtw);
break;
default:
}
- if (globals.state.recordConfig.mode === 'LONG_TRACE') {
+ if (recMgr.state.recordConfig.mode === 'LONG_TRACE') {
notes.unshift(msgLongTraces);
}
return notes.length > 0 ? m('div', notes) : [];
}
-function RecordingSnippet() {
- const target = globals.state.recordingTarget;
+function RecordingSnippet(recMgr: RecordingManager) {
+ const target = recMgr.state.recordingTarget;
// We don't need commands to start tracing on chrome
if (isChromeTarget(target)) {
- return globals.state.extensionInstalled &&
- !globals.state.recordingInProgress
+ return recMgr.state.extensionInstalled && !recMgr.state.recordingInProgress
? m(
'div',
m(
@@ -499,17 +484,13 @@
)
: [];
}
- return m(CodeSnippet, {text: getRecordCommand(target)});
+ return m(CodeSnippet, {text: getRecordCommand(recMgr, target)});
}
-function getRecordCommand(target: RecordingTarget) {
- const data = globals.trackDataStore.get('config') as {
- commandline: string;
- pbtxt: string;
- pbBase64: string;
- } | null;
+function getRecordCommand(recMgr: RecordingManager, target: RecordingTarget) {
+ const data = recMgr.state.recordCmd;
- const cfg = globals.state.recordConfig;
+ const cfg = recMgr.state.recordConfig;
let time = cfg.durationMs / 1000;
if (time > MAX_TIME) {
@@ -536,8 +517,8 @@
return cmd;
}
-function recordingButtons() {
- const state = globals.state;
+function recordingButtons(recMgr: RecordingManager) {
+ const state = recMgr.state;
const target = state.recordingTarget;
const recInProgress = state.recordingInProgress;
@@ -545,7 +526,7 @@
`button`,
{
class: recInProgress ? '' : 'selected',
- onclick: onStartRecordingPressed,
+ onclick: () => onStartRecordingPressed(recMgr),
},
'Start Recording',
);
@@ -556,7 +537,7 @@
if (
!recInProgress &&
isAdbTarget(target) &&
- globals.state.recordConfig.mode !== 'LONG_TRACE'
+ recMgr.state.recordConfig.mode !== 'LONG_TRACE'
) {
buttons.push(start);
}
@@ -569,54 +550,57 @@
return m('.button', buttons);
}
-function StopCancelButtons() {
- if (!globals.state.recordingInProgress) return [];
+function StopCancelButtons(recMgr: RecordingManager) {
+ if (!recMgr.state.recordingInProgress) return [];
const stop = m(
`button.selected`,
- {onclick: () => globals.dispatch(Actions.stopRecording({}))},
+ {onclick: () => recMgr.stopRecording()},
'Stop',
);
const cancel = m(
`button`,
- {onclick: () => globals.dispatch(Actions.cancelRecording({}))},
+ {onclick: () => recMgr.cancelRecording()},
'Cancel',
);
return [stop, cancel];
}
-function onStartRecordingPressed() {
+function onStartRecordingPressed(recMgr: RecordingManager) {
location.href = '#!/record/instructions';
- raf.scheduleFullRedraw();
- autosaveConfigStore.save(globals.state.recordConfig);
+ scheduleFullRedraw();
+ autosaveConfigStore.save(recMgr.state.recordConfig);
- const target = globals.state.recordingTarget;
+ const target = recMgr.state.recordingTarget;
if (
isAndroidTarget(target) ||
isChromeTarget(target) ||
isWindowsTarget(target)
) {
- globals.logging.logEvent('Record Trace', `Record trace (${target.os})`);
- globals.dispatch(Actions.startRecording({}));
+ recMgr.app.analytics.logEvent(
+ 'Record Trace',
+ `Record trace (${target.os})`,
+ );
+ recMgr.startRecording();
}
}
-function RecordingStatusLabel() {
- const recordingStatus = globals.state.recordingStatus;
+function RecordingStatusLabel(recMgr: RecordingManager) {
+ const recordingStatus = recMgr.state.recordingStatus;
if (!recordingStatus) return [];
return m('label', recordingStatus);
}
-export function ErrorLabel() {
- const lastRecordingError = globals.state.lastRecordingError;
+export function ErrorLabel(recMgr: RecordingManager) {
+ const lastRecordingError = recMgr.state.lastRecordingError;
if (!lastRecordingError) return [];
return m('label.error-label', `Error: ${lastRecordingError}`);
}
-function recordingLog() {
- const logs = globals.recordingLog;
+function recordingLog(recMgr: RecordingManager) {
+ const logs = recMgr.state.recordingLog;
if (logs === undefined) return [];
return m('.code-snippet.no-top-bar', m('code', logs));
}
@@ -624,7 +608,7 @@
// The connection must be done in the frontend. After it, the serial ID will
// be inserted in the state, and the worker will be able to connect to the
// correct device.
-async function addAndroidDevice() {
+async function addAndroidDevice(recMgr: RecordingManager) {
let device: USBDevice;
try {
device = await new AdbOverWebUsb().findDevice();
@@ -643,92 +627,11 @@
// After the user has selected a device with the chrome UI, it will be
// available when listing all the available device from WebUSB. Therefore,
// we update the list of available devices.
- await updateAvailableAdbDevices(device.serialNumber);
+ await recMgr.updateAvailableAdbDevices(device.serialNumber);
}
-// We really should be getting the API version from the adb target, but
-// currently its too complicated to do that (== most likely, we need to finish
-// recordingV2 migration). For now, add an escape hatch to use Android S as a
-// default, given that the main features we want are gated by API level 31 and S
-// is old enough to be the default most of the time.
-const USE_ANDROID_S_AS_DEFAULT_FLAG = featureFlags.register({
- id: 'recordingPageUseSAsDefault',
- name: 'Use Android S as a default recording target',
- description: 'Use Android S as a default recording target instead of Q',
- defaultValue: false,
-});
-
-export async function updateAvailableAdbDevices(
- preferredDeviceSerial?: string,
-) {
- const devices = await new AdbOverWebUsb().getPairedDevices();
-
- let recordingTarget: AdbRecordingTarget | undefined = undefined;
-
- const availableAdbDevices: AdbRecordingTarget[] = [];
- devices.forEach((d) => {
- if (d.productName && d.serialNumber) {
- // TODO(nicomazz): At this stage, we can't know the OS version, so we
- // assume it is 'Q'. This can create problems with devices with an old
- // version of perfetto. The os detection should be done after the adb
- // connection, from adb_record_controller
- availableAdbDevices.push({
- name: d.productName,
- serial: d.serialNumber,
- os: USE_ANDROID_S_AS_DEFAULT_FLAG.get() ? 'S' : 'Q',
- });
- if (preferredDeviceSerial && preferredDeviceSerial === d.serialNumber) {
- recordingTarget = availableAdbDevices[availableAdbDevices.length - 1];
- }
- }
- });
-
- globals.dispatch(
- Actions.setAvailableAdbDevices({devices: availableAdbDevices}),
- );
- selectAndroidDeviceIfAvailable(availableAdbDevices, recordingTarget);
- raf.scheduleFullRedraw();
- return availableAdbDevices;
-}
-
-function selectAndroidDeviceIfAvailable(
- availableAdbDevices: AdbRecordingTarget[],
- recordingTarget?: RecordingTarget,
-) {
- if (!recordingTarget) {
- recordingTarget = globals.state.recordingTarget;
- }
- const deviceConnected = isAdbTarget(recordingTarget);
- const connectedDeviceDisconnected =
- deviceConnected &&
- availableAdbDevices.find(
- (e) => e.serial === (recordingTarget as AdbRecordingTarget).serial,
- ) === undefined;
-
- if (availableAdbDevices.length) {
- // If there's an Android device available and the current selection isn't
- // one, select the Android device by default. If the current device isn't
- // available anymore, but another Android device is, select the other
- // Android device instead.
- if (!deviceConnected || connectedDeviceDisconnected) {
- recordingTarget = availableAdbDevices[0];
- }
-
- globals.dispatch(Actions.setRecordingTarget({target: recordingTarget}));
- return;
- }
-
- // If the currently selected device was disconnected, reset the recording
- // target to the default one.
- if (connectedDeviceDisconnected) {
- globals.dispatch(
- Actions.setRecordingTarget({target: getDefaultRecordingTargets()[0]}),
- );
- }
-}
-
-function recordMenu(routePage: string) {
- const target = globals.state.recordingTarget;
+function recordMenu(recMgr: RecordingManager, routePage: string) {
+ const target = recMgr.state.recordingTarget;
const chromeProbe = m(
'a[href="#!/record/chrome"]',
m(
@@ -810,7 +713,7 @@
m('.sub', 'Context switch, Thread state'),
),
);
- const recInProgress = globals.state.recordingInProgress;
+ const recInProgress = recMgr.state.recordingInProgress;
const probes = [];
if (isLinuxTarget(target)) {
@@ -836,7 +739,7 @@
'.record-menu',
{
class: recInProgress ? 'disabled' : '',
- onclick: () => raf.scheduleFullRedraw(),
+ onclick: () => scheduleFullRedraw(),
},
m('header', 'Trace config'),
m(
@@ -859,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),
@@ -885,24 +786,57 @@
return routePage === section ? '.active' : '';
}
-export class RecordPage implements m.ClassComponent<PageAttrs> {
- view({attrs}: m.CVnode<PageAttrs>) {
+export interface RecordPageAttrs extends PageAttrs {
+ app: App;
+ recMgr: RecordingManager;
+}
+
+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) : '';
if (!RECORDING_SECTIONS.includes(routePage)) {
routePage = 'buffers';
}
- pages.push(recordMenu(routePage));
+ pages.push(recordMenu(this.recMgr, routePage));
pages.push(
m(RecordingSettings, {
dataSources: [],
cssClass: maybeGetActiveCss(routePage, 'buffers'),
- } as RecordingSectionAttrs),
+ recState: this.recMgr.state,
+ }),
);
- pages.push(Instructions(maybeGetActiveCss(routePage, 'instructions')));
- pages.push(Configurations(maybeGetActiveCss(routePage, 'config')));
+ pages.push(
+ Instructions(this.recMgr, maybeGetActiveCss(routePage, 'instructions')),
+ );
+ pages.push(
+ Configurations(this.recMgr, maybeGetActiveCss(routePage, 'config')),
+ );
const settingsSections = new Map([
['cpu', CpuSettings],
@@ -920,22 +854,59 @@
m(component, {
dataSources: [],
cssClass: maybeGetActiveCss(routePage, section),
- } as RecordingSectionAttrs),
+ recState: this.recMgr.state,
+ }),
);
}
- if (isChromeTarget(globals.state.recordingTarget)) {
- globals.dispatch(Actions.setFetchChromeCategories({fetch: true}));
+ if (isChromeTarget(this.recMgr.state.recordingTarget)) {
+ this.recMgr.setFetchChromeCategories(true);
}
return m(
'.record-page',
- globals.state.recordingInProgress ? m('.hider') : [],
+ this.recMgr.state.recordingInProgress ? m('.hider') : [],
m(
'.record-container',
- RecordHeader(),
- m('.record-container-content', recordMenu(routePage), pages),
+ RecordHeader(this.recMgr),
+ m(
+ '.record-container-content',
+ recordMenu(this.recMgr, routePage),
+ pages,
+ ),
),
);
}
}
+
+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 77%
rename from ui/src/frontend/record_page_v2.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/record_page_v2.ts
index 731f991..3559332 100644
--- a/ui/src/frontend/record_page_v2.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/record_page_v2.ts
@@ -14,52 +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 {globals} from './globals';
-import {PageAttrs} from './pages';
+} 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 {RecordingSectionAttrs} from './recording/recording_sections';
-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 {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';
-const controller = new RecordingPageController();
-const recordConfigUtils = new RecordingConfigUtils();
+// 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;
+let recordConfigUtils: RecordingConfigUtils;
// Options for displaying a target selection menu.
export interface TargetSelectionOptions {
@@ -76,11 +76,11 @@
return ['CHROME', 'CHROME_OS', 'WINDOWS'].includes(targetInfo.targetType);
}
-function RecordHeader() {
+function RecordHeader(recMgr: RecordingManager) {
const platformSelection = RecordingPlatformSelection();
- const statusLabel = RecordingStatusLabel();
- const buttons = RecordingButton();
- const notes = RecordingNotes();
+ const statusLabel = RecordingStatusLabel(recMgr);
+ const buttons = RecordingButton(recMgr.state.recordConfig);
+ const notes = RecordingNotes(recMgr.state.recordConfig);
if (!platformSelection && !statusLabel && !buttons && !notes) {
// The header should not be displayed when it has no content.
return undefined;
@@ -163,13 +163,13 @@
// This will display status messages which are informative, but do not require
// user action, such as: "Recording in progress for X seconds" in the recording
// page header.
-function RecordingStatusLabel() {
- const recordingStatus = globals.state.recordingStatus;
+function RecordingStatusLabel(recMgr: RecordingManager) {
+ const recordingStatus = recMgr.state.recordingStatus;
if (!recordingStatus) return undefined;
return m('label', recordingStatus);
}
-function Instructions(cssClass: string) {
+function Instructions(recCfg: RecordConfig, cssClass: string) {
if (controller.getState() < RecordingState.TARGET_SELECTED) {
return undefined;
}
@@ -179,16 +179,14 @@
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,
- RecordingSnippet(targetInfo),
+ m(
+ 'button.permalinkconfig',
+ {
+ onclick: () => uploadRecordingConfig(recCfg),
+ },
+ 'Share recording settings',
+ ),
+ RecordingSnippet(recCfg, targetInfo),
BufferUsageProgressBar(),
m('.buttons', StopCancelButtons()),
);
@@ -213,7 +211,7 @@
);
}
-function RecordingNotes() {
+function RecordingNotes(recCfg: RecordConfig) {
if (controller.getState() !== RecordingState.TARGET_INFO_DISPLAYED) {
return undefined;
}
@@ -258,10 +256,8 @@
);
if (
- !recordConfigUtils.fetchLatestRecordCommand(
- globals.state.recordConfig,
- targetInfo,
- ).hasDataSources
+ !recordConfigUtils.fetchLatestRecordCommand(recCfg, targetInfo)
+ .hasDataSources
) {
notes.push(
m(
@@ -305,14 +301,14 @@
default:
}
- if (globals.state.recordConfig.mode === 'LONG_TRACE') {
+ if (recCfg.mode === 'LONG_TRACE') {
notes.unshift(msgLongTraces);
}
return notes.length > 0 ? m('div', notes) : undefined;
}
-function RecordingSnippet(targetInfo: TargetInfo) {
+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) {
@@ -329,12 +325,15 @@
),
);
}
- return m(CodeSnippet, {text: getRecordCommand(targetInfo)});
+ return m(CodeSnippet, {text: getRecordCommand(recCfg, targetInfo)});
}
-function getRecordCommand(targetInfo: TargetInfo): string {
+function getRecordCommand(
+ recCfg: RecordConfig,
+ targetInfo: TargetInfo,
+): string {
const recordCommand = recordConfigUtils.fetchLatestRecordCommand(
- globals.state.recordConfig,
+ recCfg,
targetInfo,
);
@@ -362,7 +361,7 @@
return cmd;
}
-function RecordingButton() {
+function RecordingButton(recCfg: RecordConfig) {
if (
controller.getState() !== RecordingState.TARGET_INFO_DISPLAYED ||
!controller.canCreateTracingSession()
@@ -373,7 +372,7 @@
// We know we have a target because we checked the state.
const targetInfo = assertExists(controller.getTargetInfo());
const hasDataSources = recordConfigUtils.fetchLatestRecordCommand(
- globals.state.recordConfig,
+ recCfg,
targetInfo,
).hasDataSources;
if (!hasDataSources) {
@@ -523,7 +522,7 @@
controller.getState() > RecordingState.TARGET_INFO_DISPLAYED
? 'disabled'
: '',
- onclick: () => raf.scheduleFullRedraw(),
+ onclick: () => scheduleFullRedraw(),
},
m('header', 'Trace config'),
m(
@@ -546,31 +545,29 @@
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),
);
}
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-function getRecordContainer(subpage?: string): m.Vnode<any, any> {
- const components: m.Children[] = [RecordHeader()];
+function getRecordContainer(recMgr: RecordingManager, subpage?: string) {
+ const recCfg = recMgr.state.recordConfig;
+ const components: m.Children[] = [RecordHeader(recMgr)];
if (controller.getState() === RecordingState.NO_TARGET) {
components.push(m('.full-centered', 'Please connect a valid target.'));
return m('.record-container', components);
@@ -610,10 +607,13 @@
m(RecordingSettings, {
dataSources: [],
cssClass: maybeGetActiveCss(routePage, 'buffers'),
- } as RecordingSectionAttrs),
+ recState: recMgr.state,
+ }),
);
- pages.push(Instructions(maybeGetActiveCss(routePage, 'instructions')));
- pages.push(Configurations(maybeGetActiveCss(routePage, 'config')));
+ pages.push(
+ Instructions(recCfg, maybeGetActiveCss(routePage, 'instructions')),
+ );
+ pages.push(Configurations(recMgr, maybeGetActiveCss(routePage, 'config')));
const settingsSections = new Map([
['cpu', CpuSettings],
@@ -631,7 +631,8 @@
m(component, {
dataSources: controller.getTargetInfo()?.dataSources || [],
cssClass: maybeGetActiveCss(routePage, section),
- } as RecordingSectionAttrs),
+ recState: recMgr.state,
+ }),
);
}
@@ -639,18 +640,43 @@
return m('.record-container', components);
}
-export class RecordPageV2 implements m.ClassComponent<PageAttrs> {
- oninit(): void {
- controller.initFactories();
+export interface RecordPageV2Attrs extends PageAttrs {
+ app: App;
+ recCtl: RecordingPageController;
+ recMgr: RecordingManager;
+}
+
+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
? m('.hider')
: [],
- getRecordContainer(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 82%
rename from ui/src/frontend/record_widgets.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/record_widgets.ts
index 2eeb0df..325237b 100644
--- a/ui/src/frontend/record_widgets.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/record_widgets.ts
@@ -12,15 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {Draft, produce} from 'immer';
import m from 'mithril';
-import {copyToClipboard} from '../base/clipboard';
-import {assertExists} from '../base/logging';
-import {Actions} from '../common/actions';
-import {RecordConfig} from '../controller/record_config_types';
-import {globals} from './globals';
+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> = (draft: Draft<RecordConfig>, val: T) => void;
+export declare type Setter<T> = (cfg: RecordConfig, val: T) => void;
export declare type Getter<T> = (cfg: RecordConfig) => T;
function defaultSort(a: string, b: string) {
@@ -51,6 +50,7 @@
// +---------------------------------------------------------------------------+
export interface ProbeAttrs {
+ recCfg: RecordConfig;
title: string;
img: string | null;
compact?: boolean;
@@ -62,19 +62,17 @@
export class Probe implements m.ClassComponent<ProbeAttrs> {
view({attrs, children}: m.CVnode<ProbeAttrs>) {
const onToggle = (enabled: boolean) => {
- const traceCfg = produce(globals.state.recordConfig, (draft) => {
- attrs.setEnabled(draft, enabled);
- });
- globals.dispatch(Actions.setRecordConfig({config: traceCfg}));
+ attrs.setEnabled(attrs.recCfg, enabled);
+ scheduleFullRedraw();
};
- const enabled = attrs.isEnabled(globals.state.recordConfig);
+ const enabled = attrs.isEnabled(attrs.recCfg);
return m(
`.probe${attrs.compact ? '.compact' : ''}${enabled ? '.enabled' : ''}`,
attrs.img &&
m('img', {
- src: `${globals.root}assets/${attrs.img}`,
+ src: assetSrc(`assets/${attrs.img}`),
onclick: () => onToggle(!enabled),
}),
m(
@@ -99,18 +97,20 @@
}
export function CompactProbe(args: {
+ recCfg: RecordConfig;
title: string;
isEnabled: Getter<boolean>;
setEnabled: Setter<boolean>;
}) {
return m(Probe, {
+ recCfg: args.recCfg,
title: args.title,
img: null,
compact: true,
descr: '',
isEnabled: args.isEnabled,
setEnabled: args.setEnabled,
- } as ProbeAttrs);
+ });
}
// +-------------------------------------------------------------+
@@ -118,6 +118,7 @@
// +-------------------------------------------------------------+
export interface ToggleAttrs {
+ recCfg: RecordConfig;
title: string;
descr: string;
cssClass?: string;
@@ -128,13 +129,11 @@
export class Toggle implements m.ClassComponent<ToggleAttrs> {
view({attrs}: m.CVnode<ToggleAttrs>) {
const onToggle = (enabled: boolean) => {
- const traceCfg = produce(globals.state.recordConfig, (draft) => {
- attrs.setEnabled(draft, enabled);
- });
- globals.dispatch(Actions.setRecordConfig({config: traceCfg}));
+ attrs.setEnabled(attrs.recCfg, enabled);
+ scheduleFullRedraw();
};
- const enabled = attrs.isEnabled(globals.state.recordConfig);
+ const enabled = attrs.isEnabled(attrs.recCfg);
return m(
`.toggle${enabled ? '.enabled' : ''}${attrs.cssClass ?? ''}`,
@@ -158,6 +157,7 @@
// +---------------------------------------------------------------------------+
export interface SliderAttrs {
+ recCfg: RecordConfig;
title: string;
icon?: string;
cssClass?: string;
@@ -174,10 +174,8 @@
export class Slider implements m.ClassComponent<SliderAttrs> {
onValueChange(attrs: SliderAttrs, newVal: number) {
- const traceCfg = produce(globals.state.recordConfig, (draft) => {
- attrs.set(draft, newVal);
- });
- globals.dispatch(Actions.setRecordConfig({config: traceCfg}));
+ attrs.set(attrs.recCfg, newVal);
+ scheduleFullRedraw();
}
onTimeValueChange(attrs: SliderAttrs, hms: string) {
@@ -195,7 +193,7 @@
view({attrs}: m.CVnode<SliderAttrs>) {
const id = attrs.title.replace(/[^a-z0-9]/gim, '_').toLowerCase();
const maxIdx = attrs.values.length - 1;
- const val = attrs.get(globals.state.recordConfig);
+ const val = attrs.get(attrs.recCfg);
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
let min = attrs.min || 1;
if (attrs.zeroIsDefault) {
@@ -251,6 +249,7 @@
// +---------------------------------------------------------------------------+
export interface DropdownAttrs {
+ recCfg: RecordConfig;
title: string;
cssClass?: string;
options: Map<string, string>;
@@ -276,15 +275,13 @@
const item = assertExists(dom.selectedOptions.item(i));
selKeys.push(item.value);
}
- const traceCfg = produce(globals.state.recordConfig, (draft) => {
- attrs.set(draft, selKeys);
- });
- globals.dispatch(Actions.setRecordConfig({config: traceCfg}));
+ attrs.set(attrs.recCfg, selKeys);
+ scheduleFullRedraw();
}
view({attrs}: m.CVnode<DropdownAttrs>) {
const options: m.Children = [];
- const selItems = attrs.get(globals.state.recordConfig);
+ const selItems = attrs.get(attrs.recCfg);
let numSelected = 0;
const entries = [...attrs.options.entries()];
const f = attrs.sort === undefined ? defaultSort : attrs.sort;
@@ -317,6 +314,7 @@
// +---------------------------------------------------------------------------+
export interface TextareaAttrs {
+ recCfg: RecordConfig;
placeholder: string;
docsLink?: string;
cssClass?: string;
@@ -327,10 +325,8 @@
export class Textarea implements m.ClassComponent<TextareaAttrs> {
onChange(attrs: TextareaAttrs, dom: HTMLTextAreaElement) {
- const traceCfg = produce(globals.state.recordConfig, (draft) => {
- attrs.set(draft, dom.value);
- });
- globals.dispatch(Actions.setRecordConfig({config: traceCfg}));
+ attrs.set(attrs.recCfg, dom.value);
+ scheduleFullRedraw();
}
view({attrs}: m.CVnode<TextareaAttrs>) {
@@ -345,7 +341,7 @@
onchange: (e: Event) =>
this.onChange(attrs, e.target as HTMLTextAreaElement),
placeholder: attrs.placeholder,
- value: attrs.get(globals.state.recordConfig),
+ value: attrs.get(attrs.recCfg),
}),
);
}
@@ -383,6 +379,7 @@
}
type CategoriesCheckboxListParams = CategoryGetter & {
+ recCfg: RecordConfig;
categories: Map<string, string>;
title: string;
};
@@ -395,21 +392,19 @@
value: string,
enabled: boolean,
) {
- const traceCfg = produce(globals.state.recordConfig, (draft) => {
- const values = attrs.get(draft);
- const index = values.indexOf(value);
- if (enabled && index === -1) {
- values.push(value);
- }
- if (!enabled && index !== -1) {
- values.splice(index, 1);
- }
- });
- globals.dispatch(Actions.setRecordConfig({config: traceCfg}));
+ const values = attrs.get(attrs.recCfg);
+ const index = values.indexOf(value);
+ if (enabled && index === -1) {
+ values.push(value);
+ }
+ if (!enabled && index !== -1) {
+ values.splice(index, 1);
+ }
+ scheduleFullRedraw();
}
view({attrs}: m.CVnode<CategoriesCheckboxListParams>) {
- const enabled = new Set(attrs.get(globals.state.recordConfig));
+ const enabled = new Set(attrs.get(attrs.recCfg));
return m(
'.categories-list',
m(
@@ -419,10 +414,7 @@
'button.config-button',
{
onclick: () => {
- const config = produce(globals.state.recordConfig, (draft) => {
- attrs.set(draft, Array.from(attrs.categories.keys()));
- });
- globals.dispatch(Actions.setRecordConfig({config}));
+ attrs.set(attrs.recCfg, Array.from(attrs.categories.keys()));
},
},
'All',
@@ -431,10 +423,7 @@
'button.config-button',
{
onclick: () => {
- const config = produce(globals.state.recordConfig, (draft) => {
- attrs.set(draft, []);
- });
- globals.dispatch(Actions.setRecordConfig({config}));
+ attrs.set(attrs.recCfg, []);
},
},
'None',
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 96%
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 becd039..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 {globals} from '../../../frontend/globals';
+import {assetSrc} from '../../../../base/assets';
import {AdbKey} from './adb_auth';
function isPasswordCredential(
@@ -37,7 +37,7 @@
id: 'webusb-adb-key',
password: key.serializeKey(),
name: 'WebUSB ADB Key',
- iconURL: `${globals.root}assets/favicon.png`,
+ iconURL: assetSrc('assets/favicon.png'),
});
// The 'Save password?' Chrome dialogue only appears if the key is
// not already stored in Chrome.
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 91%
rename from ui/src/common/recordingV2/recording_config_utils.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/recording_config_utils.ts
index ff7fcf0..e4eca50 100644
--- a/ui/src/common/recordingV2/recording_config_utils.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/recording_config_utils.ts
@@ -12,14 +12,15 @@
// 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,
AndroidPowerConfig,
+ AtomId,
BufferConfig,
ChromeConfig,
DataSourceConfig,
@@ -34,11 +35,13 @@
PerfEventConfig,
PerfEvents,
ProcessStatsConfig,
+ StatsdTracingConfig,
+ StatsdPullAtomConfig,
SysStatsConfig,
TraceConfig,
TrackEventConfig,
VmstatCounters,
-} from '../../protos';
+} from '../../../protos';
import {TargetInfo} from './recording_interfaces_v2';
import PerfClock = PerfEvents.PerfClock;
import Timebase = PerfEvents.Timebase;
@@ -443,6 +446,58 @@
}
}
+ if (uiCfg.androidStatsd) {
+ const ds = new TraceConfig.DataSource();
+ ds.config = new DataSourceConfig();
+ ds.config.name = 'android.statsd';
+ ds.config.statsdTracingConfig = new StatsdTracingConfig();
+
+ if (uiCfg.androidStatsdRawPushedAtoms.length > 0) {
+ ds.config.statsdTracingConfig.rawPushAtomId = [];
+ for (const line of uiCfg.androidStatsdRawPushedAtoms.split('\n')) {
+ if (line.trim().length > 0) {
+ ds.config.statsdTracingConfig.rawPushAtomId.push(parseInt(line));
+ }
+ }
+ }
+
+ if (uiCfg.androidStatsdPushedAtoms.length > 0) {
+ ds.config.statsdTracingConfig.pushAtomId =
+ uiCfg.androidStatsdPushedAtoms.map((atom) => atom as unknown as AtomId);
+ }
+
+ const needPulledAtomConfig =
+ uiCfg.androidStatsdRawPulledAtoms.length > 0 ||
+ uiCfg.androidStatsdPulledAtoms.length > 0;
+
+ if (needPulledAtomConfig) {
+ const pullAtomConfig = new StatsdPullAtomConfig();
+ if (uiCfg.androidStatsdRawPulledAtoms.length > 0) {
+ for (const line of uiCfg.androidStatsdRawPulledAtoms.split('\n')) {
+ if (line.trim().length > 0) {
+ pullAtomConfig.rawPullAtomId.push(parseInt(line));
+ }
+ }
+ }
+ pullAtomConfig.pullAtomId = uiCfg.androidStatsdPulledAtoms.map(
+ (atom) => atom as unknown as AtomId,
+ );
+ pullAtomConfig.pullFrequencyMs =
+ uiCfg.androidStatsdPulledAtomPullFrequencyMs;
+ if (uiCfg.androidStatsdPulledAtomPackages.length > 0) {
+ for (const line of uiCfg.androidStatsdPulledAtomPackages.split('\n')) {
+ if (line.trim().length > 0) {
+ pullAtomConfig.packages.push(line);
+ }
+ }
+ }
+ ds.config.statsdTracingConfig.pullConfig = [pullAtomConfig];
+ }
+ if (targetInfo.targetType !== 'CHROME') {
+ protoCfg.dataSources.push(ds);
+ }
+ }
+
if (uiCfg.chromeLogs) {
chromeCategories.add('log');
}
@@ -654,6 +709,13 @@
dataSource.config.chromeConfig = chromeConfig;
protoCfg.dataSources.push(dataSource);
}
+ if (chromeCategories.has('disabled-by-default-system_metrics')) {
+ const dataSource = new TraceConfig.DataSource();
+ dataSource.config = new DataSourceConfig();
+ dataSource.config.name = 'org.chromium.system_metrics';
+ dataSource.config.chromeConfig = chromeConfig;
+ protoCfg.dataSources.push(dataSource);
+ }
}
// Keep these last. The stages above can enrich them.
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 92%
rename from ui/src/common/recordingV2/recording_page_controller.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/recordingV2/recording_page_controller.ts
index d8447bd..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 {raf} from '../../core/raf_scheduler';
-import {globals} from '../../frontend/globals';
-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 {Actions} from '../actions';
-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,9 @@
// 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
// transitions. This is queried by the UI in order to
private state: RecordingState = RecordingState.NO_TARGET;
@@ -265,6 +268,11 @@
// transitions don't override one another in async functions.
private stateGeneration = 0;
+ constructor(app: App, recMgr: RecordingManager) {
+ this.app = app;
+ this.recMgr = recMgr;
+ }
+
getBufferUsagePercentage(): number {
return this.bufferUsagePercentage;
}
@@ -289,8 +297,8 @@
throw new RecordingError('Recording page state transition out of order.');
}
this.setState(state);
- globals.dispatch(Actions.setRecordingStatus({status: undefined}));
- raf.scheduleFullRedraw();
+ this.recMgr.setRecordingStatus(undefined);
+ scheduleFullRedraw();
}
maybeClearRecordingState(tracingSessionWrapper: TracingSessionWrapper): void {
@@ -306,13 +314,11 @@
if (this.tracingSessionWrapper !== tracingSessionWrapper) {
return;
}
- globals.dispatch(
- Actions.openTraceFromBuffer({
- title: 'Recorded trace',
- buffer: trace.buffer,
- fileName: `trace_${currentDateHourAndMinute()}${TRACE_SUFFIX}`,
- }),
- );
+ this.app.openTraceFromBuffer({
+ title: 'Recorded trace',
+ buffer: trace.buffer,
+ fileName: `trace_${currentDateHourAndMinute()}${TRACE_SUFFIX}`,
+ });
this.clearRecordingState();
}
@@ -323,7 +329,7 @@
// For the 'Recording in progress for 7000ms we don't show a
// modal.'
if (message.startsWith(RECORDING_IN_PROGRESS)) {
- globals.dispatch(Actions.setRecordingStatus({status: message}));
+ this.recMgr.setRecordingStatus(message);
} else {
// For messages such as 'Please allow USB debugging on your
// device, which require a user action, we show a modal.
@@ -386,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();
@@ -423,15 +429,18 @@
onStartRecordingPressed(): void {
assertTrue(RecordingState.TARGET_INFO_DISPLAYED === this.state);
location.href = '#!/record/instructions';
- autosaveConfigStore.save(globals.state.recordConfig);
+ autosaveConfigStore.save(this.recMgr.state.recordConfig);
const target = this.getTarget();
const targetInfo = target.getInfo();
- globals.logging.logEvent(
+ this.app.analytics.logEvent(
'Record Trace',
`Record trace (${targetInfo.targetType})`,
);
- const traceConfig = genTraceConfig(globals.state.recordConfig, targetInfo);
+ const traceConfig = genTraceConfig(
+ this.recMgr.state.recordConfig,
+ targetInfo,
+ );
this.tracingSessionWrapper = this.createTracingSessionWrapper(target);
this.tracingSessionWrapper.start(traceConfig);
@@ -477,7 +486,7 @@
// We redraw if:
// 1. We received a correct buffer usage value.
// 2. We receive a RecordingError.
- raf.scheduleFullRedraw();
+ scheduleFullRedraw();
}
initFactories() {
@@ -524,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
@@ -542,10 +551,10 @@
this.bufferUsagePercentage = 0;
this.tracingSessionWrapper = undefined;
this.setState(RecordingState.TARGET_INFO_DISPLAYED);
- globals.dispatch(Actions.setRecordingStatus({status: undefined}));
+ 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/plugins/dev.perfetto.RecordTrace/recording_manager.ts b/ui/src/plugins/dev.perfetto.RecordTrace/recording_manager.ts
new file mode 100644
index 0000000..be29691
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/recording_manager.ts
@@ -0,0 +1,233 @@
+// 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 {createEmptyState} from './empty_state';
+import {
+ AdbRecordingTarget,
+ LoadedConfig,
+ RecordingState,
+ RecordingTarget,
+ getDefaultRecordingTargets,
+ isAdbTarget,
+} 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;
+
+ constructor(app: App, useRecordingV2: boolean) {
+ this.app = app;
+ const extensionLocalChannel = new MessageChannel();
+ this.recCtl = new RecordController(app, this, extensionLocalChannel.port1);
+ this.setupExtentionPort(extensionLocalChannel);
+
+ if (useRecordingV2) {
+ targetFactoryRegistry.register(new AndroidWebsocketTargetFactory());
+ if (exists(navigator.usb)) {
+ targetFactoryRegistry.register(
+ new AndroidWebusbTargetFactory(navigator.usb),
+ );
+ }
+ } else {
+ this.updateAvailableAdbDevices();
+ try {
+ navigator.usb.addEventListener('connect', () =>
+ this.updateAvailableAdbDevices(),
+ );
+ navigator.usb.addEventListener('disconnect', () =>
+ this.updateAvailableAdbDevices(),
+ );
+ } catch (e) {
+ console.error('WebUSB API not supported');
+ }
+ }
+ }
+
+ clearRecordConfig(): void {
+ this._state.recordConfig = createEmptyRecordConfig();
+ this._state.lastLoadedConfig = {type: 'NONE'};
+ this.recCtl.refreshOnStateChange();
+ }
+
+ setRecordConfig(config: RecordConfig, configType?: LoadedConfig): void {
+ this._state.recordConfig = config;
+ this._state.lastLoadedConfig = configType || {type: 'NONE'};
+ this.recCtl.refreshOnStateChange();
+ }
+
+ startRecording(): void {
+ this._state.recordingInProgress = true;
+ this._state.lastRecordingError = undefined;
+ this._state.recordingCancelled = false;
+ this.recCtl.refreshOnStateChange();
+ }
+
+ stopRecording(): void {
+ this._state.recordingInProgress = false;
+ this.recCtl.refreshOnStateChange();
+ }
+
+ cancelRecording(): void {
+ this._state.recordingInProgress = false;
+ this._state.recordingCancelled = true;
+ this.recCtl.refreshOnStateChange();
+ }
+
+ setRecordingTarget(target: RecordingTarget): void {
+ this._state.recordingTarget = target;
+ this.recCtl.refreshOnStateChange();
+ }
+
+ setFetchChromeCategories(fetch: boolean): void {
+ this._state.fetchChromeCategories = fetch;
+ this.recCtl.refreshOnStateChange();
+ }
+
+ setAvailableAdbDevices(devices: AdbRecordingTarget[]): void {
+ this._state.availableAdbDevices = devices;
+ this.recCtl.refreshOnStateChange();
+ }
+
+ setLastRecordingError(error?: string): void {
+ this._state.lastRecordingError = error;
+ this._state.recordingStatus = undefined;
+ this.recCtl.refreshOnStateChange();
+ }
+
+ setRecordingStatus(status?: string): void {
+ this._state.recordingStatus = status;
+ this._state.lastRecordingError = undefined;
+ this.recCtl.refreshOnStateChange();
+ }
+
+ get state() {
+ return this._state;
+ }
+
+ private setupExtentionPort(extensionLocalChannel: MessageChannel) {
+ // We proxy messages between the extension and the controller because the
+ // controller's worker can't access chrome.runtime.
+ const extensionPort =
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
+ window.chrome && chrome.runtime
+ ? chrome.runtime.connect(EXTENSION_ID)
+ : undefined;
+
+ this._state.extensionInstalled = extensionPort !== undefined;
+
+ if (extensionPort) {
+ // Send messages to keep-alive the extension port.
+ const interval = setInterval(() => {
+ extensionPort.postMessage({
+ method: 'ExtensionVersion',
+ });
+ }, 25000);
+ extensionPort.onDisconnect.addListener((_) => {
+ this._state.extensionInstalled = false;
+ clearInterval(interval);
+ void chrome.runtime.lastError; // Needed to not receive an error log.
+ });
+ // This forwards the messages from the extension to the controller.
+ extensionPort.onMessage.addListener(
+ (message: object, _port: chrome.runtime.Port) => {
+ if (isGetCategoriesResponse(message)) {
+ this._state.chromeCategories = message.categories;
+ scheduleFullRedraw();
+ return;
+ }
+ extensionLocalChannel.port2.postMessage(message);
+ },
+ );
+ }
+
+ // This forwards the messages from the controller to the extension
+ extensionLocalChannel.port2.onmessage = ({data}) => {
+ if (extensionPort) extensionPort.postMessage(data);
+ };
+ }
+
+ async updateAvailableAdbDevices(preferredDeviceSerial?: string) {
+ const devices = await new AdbOverWebUsb().getPairedDevices();
+
+ let recordingTarget: AdbRecordingTarget | undefined = undefined;
+
+ const availableAdbDevices: AdbRecordingTarget[] = [];
+ devices.forEach((d) => {
+ if (d.productName && d.serialNumber) {
+ availableAdbDevices.push({
+ name: d.productName,
+ serial: d.serialNumber,
+ os: 'S',
+ });
+ if (preferredDeviceSerial && preferredDeviceSerial === d.serialNumber) {
+ recordingTarget = availableAdbDevices[availableAdbDevices.length - 1];
+ }
+ }
+ });
+
+ this.setAvailableAdbDevices(availableAdbDevices);
+ this.selectAndroidDeviceIfAvailable(availableAdbDevices, recordingTarget);
+ scheduleFullRedraw();
+ return availableAdbDevices;
+ }
+
+ private selectAndroidDeviceIfAvailable(
+ availableAdbDevices: AdbRecordingTarget[],
+ recordingTarget?: RecordingTarget,
+ ) {
+ if (!recordingTarget) {
+ recordingTarget = this.state.recordingTarget;
+ }
+ const deviceConnected = isAdbTarget(recordingTarget);
+ const connectedDeviceDisconnected =
+ deviceConnected &&
+ availableAdbDevices.find(
+ (e) => e.serial === (recordingTarget as AdbRecordingTarget).serial,
+ ) === undefined;
+
+ if (availableAdbDevices.length) {
+ // If there's an Android device available and the current selection isn't
+ // one, select the Android device by default. If the current device isn't
+ // available anymore, but another Android device is, select the other
+ // Android device instead.
+ if (!deviceConnected || connectedDeviceDisconnected) {
+ recordingTarget = availableAdbDevices[0];
+ }
+
+ this.setRecordingTarget(recordingTarget);
+ return;
+ }
+
+ // If the currently selected device was disconnected, reset the recording
+ // target to the default one.
+ if (connectedDeviceDisconnected) {
+ this.setRecordingTarget(getDefaultRecordingTargets()[0]);
+ }
+ }
+}
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 85%
rename from ui/src/frontend/recording/recording_sections.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/recording_sections.ts
index f0e3fa1..c83b9e0 100644
--- a/ui/src/frontend/recording/recording_sections.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/recording_sections.ts
@@ -12,9 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {DataSource} from '../../common/recordingV2/recording_interfaces_v2';
+import {DataSource} from './recordingV2/recording_interfaces_v2';
+import {RecordingState} from './state';
export interface RecordingSectionAttrs {
+ recState: RecordingState;
dataSources: DataSource[];
cssClass: string;
}
diff --git a/ui/src/frontend/recording/recording_settings.ts b/ui/src/plugins/dev.perfetto.RecordTrace/recording_settings.ts
similarity index 76%
rename from ui/src/frontend/recording/recording_settings.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/recording_settings.ts
index a818fb3..e3058be 100644
--- a/ui/src/frontend/recording/recording_settings.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/recording_settings.ts
@@ -12,13 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {produce} from 'immer';
import m from 'mithril';
-import {Actions} from '../../common/actions';
-import {RecordMode} from '../../common/state';
-import {globals} from '../globals';
-import {Slider, SliderAttrs} from '../record_widgets';
+import {RecordMode} from './state';
+import {Slider} from './record_widgets';
import {RecordingSectionAttrs} from './recording_sections';
+import {assetSrc} from '../../base/assets';
export class RecordingSettings
implements m.ClassComponent<RecordingSectionAttrs>
@@ -28,24 +26,21 @@
const M = (x: number) => x * 1000 * 60;
const H = (x: number) => x * 1000 * 60 * 60;
- const cfg = globals.state.recordConfig;
+ const recCfg = attrs.recState.recordConfig;
const recButton = (mode: RecordMode, title: string, img: string) => {
const checkboxArgs = {
- checked: cfg.mode === mode,
+ checked: recCfg.mode === mode,
onchange: (e: InputEvent) => {
const checked = (e.target as HTMLInputElement).checked;
if (!checked) return;
- const traceCfg = produce(globals.state.recordConfig, (draft) => {
- draft.mode = mode;
- });
- globals.dispatch(Actions.setRecordConfig({config: traceCfg}));
+ recCfg.mode = mode;
},
};
return m(
- `label${cfg.mode === mode ? '.selected' : ''}`,
+ `label${recCfg.mode === mode ? '.selected' : ''}`,
m(`input[type=radio][name=rec_mode]`, checkboxArgs),
- m(`img[src=${globals.root}assets/${img}]`),
+ m(`img[src=${assetSrc(`assets/${img}`)}]`),
m('span', title),
);
};
@@ -67,7 +62,8 @@
unit: 'MB',
set: (cfg, val) => (cfg.bufferSizeMb = val),
get: (cfg) => cfg.bufferSizeMb,
- } as SliderAttrs),
+ recCfg,
+ }),
m(Slider, {
title: 'Max duration',
@@ -77,25 +73,28 @@
unit: 'h:m:s',
set: (cfg, val) => (cfg.durationMs = val),
get: (cfg) => cfg.durationMs,
- } as SliderAttrs),
+ recCfg,
+ }),
m(Slider, {
title: 'Max file size',
icon: 'save',
- cssClass: cfg.mode !== 'LONG_TRACE' ? '.hide' : '',
+ cssClass: recCfg.mode !== 'LONG_TRACE' ? '.hide' : '',
values: [5, 25, 50, 100, 500, 1000, 1000 * 5, 1000 * 10],
unit: 'MB',
set: (cfg, val) => (cfg.maxFileSizeMb = val),
get: (cfg) => cfg.maxFileSizeMb,
- } as SliderAttrs),
+ recCfg,
+ }),
m(Slider, {
title: 'Flush on disk every',
- cssClass: cfg.mode !== 'LONG_TRACE' ? '.hide' : '',
+ cssClass: recCfg.mode !== 'LONG_TRACE' ? '.hide' : '',
icon: 'av_timer',
values: [100, 250, 500, 1000, 2500, 5000],
unit: 'ms',
set: (cfg, val) => (cfg.fileWritePeriodMs = val),
get: (cfg) => cfg.fileWritePeriodMs || 0,
- } as SliderAttrs),
+ recCfg,
+ }),
);
}
}
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 65%
rename from ui/src/common/state.ts
rename to ui/src/plugins/dev.perfetto.RecordTrace/state.ts
index f8960eb..b94074b 100644
--- a/ui/src/common/state.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTrace/state.ts
@@ -12,125 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {time} from '../base/time';
-import {RecordConfig} from '../controller/record_config_types';
-import {TraceSource} from '../public/trace_source';
-
-/**
- * A plain js object, holding objects of type |Class| keyed by string id.
- * We use this instead of using |Map| object since it is simpler and faster to
- * serialize for use in postMessage.
- */
-export interface ObjectById<Class extends {id: string}> {
- [id: string]: Class;
-}
-
-// Same as ObjectById but the key parameter is called `key` rather than `id`.
-export interface ObjectByKey<Class extends {key: string}> {
- [key: string]: Class;
-}
+import {RecordConfig} from './record_config_types';
export const MAX_TIME = 180;
-// 3: TrackKindPriority and related sorting changes.
-// 5: Move a large number of items off frontendLocalState and onto state.
-// 6: Common PivotTableConfig and pivot table specific PivotTableState.
-// 7: Split Chrome categories in two and add 'symbolize ksyms' flag.
-// 8: Rename several variables
-// "[...]HeapProfileFlamegraph[...]" -> "[...]Flamegraph[...]".
-// 9: Add a field to track last loaded recording profile name
-// 10: Change last loaded profile tracking type to accommodate auto-save.
-// 11: Rename updateChromeCategories to fetchChromeCategories.
-// 12: Add a field to cache mapping from UI track ID to trace track ID in order
-// to speed up flow arrows rendering.
-// 13: FlamegraphState changed to support area selection.
-// 14: Changed the type of uiTrackIdByTraceTrackId from `Map` to an object with
-// typed key/value because a `Map` does not preserve type during
-// serialisation+deserialisation.
-// 15: Added state for Pivot Table V2
-// 16: Added boolean tracking if the flamegraph modal was dismissed
-// 17:
-// - add currentEngineId to track the id of the current engine
-// - remove nextNoteId, nextAreaId and use nextId as a unique counter for all
-// indexing except the indexing of the engines
-// 18: areaSelection change see b/235869542
-// 19: Added visualisedArgs state.
-// 20: Refactored thread sorting order.
-// 21: Updated perf sample selection to include a ts range instead of single ts
-// 22: Add log selection kind.
-// 23: Add log filtering criteria for Android log entries.
-// 24: Store only a single Engine.
-// 25: Move omnibox state off VisibleState.
-// 26: Add tags for filtering Android log entries.
-// 27. Add a text entry for filtering Android log entries.
-// 28. Add a boolean indicating if non matching log entries are hidden.
-// 29. Add ftrace state. <-- Borked, state contains a non-serializable object.
-// 30. Convert ftraceFilter.excludedNames from Set<string> to string[].
-// 31. Convert all timestamps to bigints.
-// 32. Add pendingDeeplink.
-// 33. Add plugins state.
-// 34. Add additional pendingDeeplink fields (query, pid).
-// 35. Add force to OmniboxState
-// 36. Remove metrics
-// 37. Add additional pendingDeeplink fields (visStart, visEnd).
-// 38. Add track tags.
-// 39. Ported cpu_slice, ftrace, and android_log tracks to plugin tracks. Track
-// state entries now require a URI and old track implementations are no
-// longer registered.
-// 40. Ported counter, process summary/sched, & cpu_freq to plugin tracks.
-// 41. Ported all remaining tracks.
-// 42. Rename trackId -> trackKey.
-// 43. Remove visibleTracks.
-// 44. Add TabsV2 state.
-// 45. Remove v1 tracks.
-// 46. Remove trackKeyByTrackId.
-// 47. Selection V2
-// 48. Rename legacySelection -> selection and introduce new Selection type.
-// 49. Remove currentTab, which is only relevant to TabsV1.
-// 50. Remove ftrace filter state.
-// 51. Changed structure of FlamegraphState.expandedCallsiteByViewingOption.
-// 52. Update track group state - don't make the summary track the first track.
-// 53. Remove android log state.
-// 54. Remove traceTime.
-// 55. Rename TrackGroupState.id -> TrackGroupState.key.
-// 56. Renamed chrome slice to thread slice everywhere.
-// 57. Remove flamegraph related code from state.
-// 58. Remove area map.
-// 59. Deprecate old area selection type.
-// 60. Deprecate old note selection type.
-// 61. Remove params/state from TrackState.
-export const STATE_VERSION = 61;
-
-export const SCROLLING_TRACK_GROUP = 'ScrollingTracks';
-
-export type EngineMode = 'WASM' | 'HTTP_RPC';
-
-export type NewEngineMode = 'USE_HTTP_RPC_IF_AVAILABLE' | 'FORCE_BUILTIN_WASM';
-
-export interface EngineConfig {
- id: string;
- mode?: EngineMode; // Is undefined until |ready| is true.
- ready: boolean;
- failed?: string; // If defined the engine has crashed with the given message.
- source: TraceSource;
-}
-
-export interface QueryConfig {
- id: string;
- engineId?: string;
- query: string;
-}
-
-export interface Status {
- msg: string;
- timestamp: number; // Epoch in seconds (Date.now() / 1000).
-}
-
-export interface Pagination {
- offset: number;
- count: number;
-}
-
export interface RecordingTarget {
name: string;
os: TargetOs;
@@ -158,54 +43,20 @@
| LoadedConfigAutomatic
| LoadedConfigNamed;
-export interface PendingDeeplinkState {
- ts?: string;
- dur?: string;
- tid?: string;
- pid?: string;
- query?: string;
- visStart?: string;
- visEnd?: string;
+export interface RecordCommand {
+ commandline: string;
+ pbtxt: string;
+ pbBase64: string;
}
-export interface State {
- version: number;
- nextId: string;
-
+export interface RecordingState {
/**
* State of the ConfigEditor.
*/
recordConfig: RecordConfig;
- displayConfigAsPbtxt: boolean;
lastLoadedConfig: LoadedConfig;
/**
- * Open traces.
- */
- newEngineMode: NewEngineMode;
- engine?: EngineConfig;
- traceUuid?: string;
-
- debugTrackId?: string;
- lastTrackReloadRequest?: number;
- queries: ObjectById<QueryConfig>;
- status: Status;
- traceConversionInProgress: boolean;
- flamegraphModalDismissed: boolean;
-
- // Show track perf debugging overlay
- perfDebug: boolean;
-
- // Show the sidebar extended
- sidebarVisible: boolean;
-
- // Hovered and focused events
- hoveredUtid: number;
- hoveredPid: number;
- hoveredNoteTimestamp: time;
- highlightedSliceId: number;
-
- /**
* Trace recording
*/
recordingInProgress: boolean;
@@ -219,15 +70,9 @@
fetchChromeCategories: boolean;
chromeCategories: string[] | undefined;
- // Pending deeplink which will happen when we first finish opening a
- // trace.
- pendingDeeplink?: PendingDeeplinkState;
-
- trackFilterTerm: string | undefined;
-
- // TODO(primiano): this is a hack to force-re-run controllers required for the
- // controller->managers migration. Remove once controllers are gone.
- forceRunControllers: number;
+ bufferUsage: number;
+ recordingLog: string;
+ recordCmd?: RecordCommand;
}
export declare type RecordMode =
@@ -252,7 +97,7 @@
}
export function isAndroidTarget(target: RecordingTarget) {
- return ['Q', 'P', 'O'].includes(target.os);
+ return ['Q', 'P', 'O', 'S'].includes(target.os);
}
export function isChromeTarget(target: RecordingTarget) {
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.RestorePinnedTracks/index.ts b/ui/src/plugins/dev.perfetto.RestorePinnedTracks/index.ts
index d67d6d4..b20ecef 100644
--- a/ui/src/plugins/dev.perfetto.RestorePinnedTracks/index.ts
+++ b/ui/src/plugins/dev.perfetto.RestorePinnedTracks/index.ts
@@ -12,11 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {Optional} from '../../base/utils';
import {TrackNode} from '../../public/workspace';
import {Trace} from '../../public/trace';
-import {App} from '../../public/app';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
+import {PerfettoPlugin} from '../../public/plugin';
+import {TrackDescriptor} from '../../public/track';
const PLUGIN_ID = 'dev.perfetto.RestorePinnedTrack';
const SAVED_TRACKS_KEY = `${PLUGIN_ID}#savedPerfettoTracks`;
@@ -28,9 +27,8 @@
* and group name. When no match is found for a saved track, it tries again
* without numbers.
*/
-class RestorePinnedTrack implements PerfettoPlugin {
- onActivate(_ctx: App): void {}
-
+export default class implements PerfettoPlugin {
+ static readonly id = PLUGIN_ID;
private ctx!: Trace;
async onTraceLoad(ctx: Trace): Promise<void> {
@@ -54,72 +52,187 @@
private saveTracks() {
const workspace = this.ctx.workspace;
const pinnedTracks = workspace.pinnedTracks;
- const tracksToSave: SavedPinnedTrack[] = pinnedTracks.map((track) => ({
- groupName: groupName(track),
- trackName: track.title,
- }));
- window.localStorage.setItem(SAVED_TRACKS_KEY, JSON.stringify(tracksToSave));
+ const tracksToSave: SavedPinnedTrack[] = pinnedTracks.map((track) =>
+ this.toSavedTrack(track),
+ );
+
+ this.savedState = {
+ tracks: tracksToSave,
+ };
}
private restoreTracks() {
- const savedTracks = window.localStorage.getItem(SAVED_TRACKS_KEY);
- if (!savedTracks) {
+ const savedState = this.savedState;
+ if (!savedState) {
alert('No saved tracks. Use the Save command first');
return;
}
- const tracksToRestore: SavedPinnedTrack[] = JSON.parse(savedTracks);
- const workspace = this.ctx.workspace;
- const tracks = workspace.flatTracks;
+ const tracksToRestore: SavedPinnedTrack[] = savedState.tracks;
+
+ const localTracks: Array<LocalTrack> = this.ctx.workspace.flatTracks.map(
+ (track) => ({
+ savedTrack: this.toSavedTrack(track),
+ track: track,
+ }),
+ );
+
tracksToRestore.forEach((trackToRestore) => {
- // Check for an exact match
- const exactMatch = tracks.find((track) => {
- return (
- trackToRestore.trackName === track.title &&
- trackToRestore.groupName === groupName(track)
- );
- });
-
- if (exactMatch) {
- exactMatch.pin();
+ const foundTrack = this.findMatchingTrack(localTracks, trackToRestore);
+ if (foundTrack) {
+ foundTrack.pin();
} else {
- // We attempt a match after removing numbers to potentially pin a
- // "similar" track from a different trace. Removing numbers allows
- // flexibility; for instance, with multiple 'sysui' processes (e.g.
- // track group name: "com.android.systemui 123") without this approach,
- // any could be mistakenly pinned. The goal is to restore specific
- // tracks within the same trace, ensuring that a previously pinned track
- // is pinned again.
- // If the specific process with that PID is unavailable, pinning any
- // other process matching the package name is attempted.
- const fuzzyMatch = tracks.find((track) => {
- return (
- this.removeNumbers(trackToRestore.trackName) ===
- this.removeNumbers(track.title) &&
- this.removeNumbers(trackToRestore.groupName) ===
- this.removeNumbers(groupName(track))
- );
- });
-
- if (fuzzyMatch) {
- fuzzyMatch.pin();
- } else {
- console.warn(
- '[RestorePinnedTracks] No track found that matches',
- trackToRestore,
- );
- }
+ console.warn(
+ '[RestorePinnedTracks] No track found that matches',
+ trackToRestore,
+ );
}
});
}
+ private findMatchingTrack(
+ localTracks: Array<LocalTrack>,
+ savedTrack: SavedPinnedTrack,
+ ): TrackNode | null {
+ let mostSimilarTrack: LocalTrack | null = null;
+ let mostSimilarTrackDifferenceScore: number = 0;
+
+ for (let i = 0; i < localTracks.length; i++) {
+ const localTrack = localTracks[i];
+ const differenceScore = this.calculateSimilarityScore(
+ localTrack.savedTrack,
+ savedTrack,
+ );
+
+ // Return immediately if we found the exact match
+ if (differenceScore === Number.MAX_SAFE_INTEGER) {
+ return localTrack.track;
+ }
+
+ // Ignore too different objects
+ if (differenceScore === 0) {
+ continue;
+ }
+
+ if (differenceScore > mostSimilarTrackDifferenceScore) {
+ mostSimilarTrackDifferenceScore = differenceScore;
+ mostSimilarTrack = localTrack;
+ }
+ }
+
+ return mostSimilarTrack?.track || null;
+ }
+
+ /**
+ * Returns the similarity score where 0 means the objects are completely
+ * different, and the higher the number, the smaller the difference is.
+ * Returns Number.MAX_SAFE_INTEGER if the objects are completely equal.
+ * We attempt a fuzzy match based on the similarity score.
+ * For example, one of the ways we do this is we remove the numbers
+ * from the title to potentially pin a "similar" track from a different trace.
+ * Removing numbers allows flexibility; for instance, with multiple 'sysui'
+ * processes (e.g. track group name: "com.android.systemui 123") without
+ * this approach, any could be mistakenly pinned. The goal is to restore
+ * specific tracks within the same trace, ensuring that a previously pinned
+ * track is pinned again.
+ * If the specific process with that PID is unavailable, pinning any
+ * other process matching the package name is attempted.
+ * @param track1 first saved track to compare
+ * @param track2 second saved track to compare
+ * @private
+ */
+ private calculateSimilarityScore(
+ track1: SavedPinnedTrack,
+ track2: SavedPinnedTrack,
+ ): number {
+ // Return immediately when objects are equal
+ if (
+ track1.trackName === track2.trackName &&
+ track1.groupName === track2.groupName &&
+ track1.pluginId === track2.pluginId &&
+ track1.kind === track2.kind &&
+ track1.isMainThread === track2.isMainThread
+ ) {
+ return Number.MAX_SAFE_INTEGER;
+ }
+
+ let similarityScore = 0;
+ if (track1.trackName === track2.trackName) {
+ similarityScore += 100;
+ } else if (
+ this.removeNumbers(track1.trackName) ===
+ this.removeNumbers(track2.trackName)
+ ) {
+ similarityScore += 50;
+ }
+
+ if (track1.groupName === track2.groupName) {
+ similarityScore += 90;
+ } else if (
+ this.removeNumbers(track1.groupName) ===
+ this.removeNumbers(track2.groupName)
+ ) {
+ similarityScore += 45;
+ }
+
+ // Do not consider other parameters if there is no match in name/group
+ if (similarityScore === 0) return similarityScore;
+
+ if (track1.pluginId === track2.pluginId) {
+ similarityScore += 30;
+ }
+
+ if (track1.kind === track2.kind) {
+ similarityScore += 20;
+ }
+
+ if (track1.isMainThread === track2.isMainThread) {
+ similarityScore += 10;
+ }
+
+ return similarityScore;
+ }
+
private removeNumbers(inputString?: string): string | undefined {
return inputString?.replace(/\d+/g, '');
}
+
+ private toSavedTrack(track: TrackNode): SavedPinnedTrack {
+ let trackDescriptor: TrackDescriptor | undefined = undefined;
+ if (track.uri != null) {
+ trackDescriptor = this.ctx.tracks.getTrack(track.uri);
+ }
+
+ return {
+ groupName: groupName(track),
+ trackName: track.title,
+ pluginId: trackDescriptor?.pluginId,
+ kind: trackDescriptor?.tags?.kind,
+ isMainThread: trackDescriptor?.chips?.includes('main thread') || false,
+ };
+ }
+
+ private get savedState(): SavedState | null {
+ const savedStateString = window.localStorage.getItem(SAVED_TRACKS_KEY);
+ if (!savedStateString) {
+ return null;
+ }
+
+ const savedState: SavedState = JSON.parse(savedStateString);
+ if (!(savedState.tracks instanceof Array)) {
+ return null;
+ }
+
+ return savedState;
+ }
+
+ private set savedState(state: SavedState) {
+ window.localStorage.setItem(SAVED_TRACKS_KEY, JSON.stringify(state));
+ }
}
// Return the displayname of the containing group
// If the track is a child of a workspace, return undefined...
-function groupName(track: TrackNode): Optional<string> {
+function groupName(track: TrackNode): string | undefined {
const parent = track.parent;
if (parent instanceof TrackNode) {
return parent.title;
@@ -127,15 +240,28 @@
return undefined;
}
+interface SavedState {
+ tracks: Array<SavedPinnedTrack>;
+}
+
interface SavedPinnedTrack {
// Optional: group name for the track. Usually matches with process name.
groupName?: string;
// Track name to restore.
trackName: string;
+
+ // Plugin used to create this track
+ pluginId?: string;
+
+ // Kind of the track
+ kind?: string;
+
+ // If it's a thread track, it should be true in case it's a main thread track
+ isMainThread: boolean;
}
-export const plugin: PluginDescriptor = {
- pluginId: PLUGIN_ID,
- plugin: RestorePinnedTrack,
-};
+interface LocalTrack {
+ savedTrack: SavedPinnedTrack;
+ track: TrackNode;
+}
diff --git a/ui/src/core_plugins/sched/active_cpu_count.ts b/ui/src/plugins/dev.perfetto.Sched/active_cpu_count.ts
similarity index 100%
rename from ui/src/core_plugins/sched/active_cpu_count.ts
rename to ui/src/plugins/dev.perfetto.Sched/active_cpu_count.ts
diff --git a/ui/src/core_plugins/sched/index.ts b/ui/src/plugins/dev.perfetto.Sched/index.ts
similarity index 74%
rename from ui/src/core_plugins/sched/index.ts
rename to ui/src/plugins/dev.perfetto.Sched/index.ts
index a33a55b..68d3d0b 100644
--- a/ui/src/core_plugins/sched/index.ts
+++ b/ui/src/plugins/dev.perfetto.Sched/index.ts
@@ -12,16 +12,20 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {addSqlTableTab} from '../../frontend/sql_table_tab_interface';
import {sqlTableRegistry} from '../../frontend/widgets/sql/table/sql_table_registry';
import {TrackNode} from '../../public/workspace';
import {Trace} from '../../public/trace';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
+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';
-class SchedPlugin implements PerfettoPlugin {
+export default class implements PerfettoPlugin {
+ static readonly id = 'dev.perfetto.Sched';
async onTraceLoad(ctx: Trace) {
const runnableThreadCountUri = `/runnable_thread_count`;
ctx.tracks.registerTrack({
@@ -39,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({
@@ -73,7 +97,7 @@
id: 'perfetto.ShowTable.sched',
name: 'Open table: sched',
callback: () => {
- addSqlTableTab(ctx, {
+ extensions.addSqlTableTab(ctx, {
table: getSchedTable(),
});
},
@@ -96,8 +120,3 @@
ctx.workspace.addChildFirst(track);
track.pin();
}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'perfetto.Sched',
- plugin: SchedPlugin,
-};
diff --git a/ui/src/core_plugins/sched/table.ts b/ui/src/plugins/dev.perfetto.Sched/table.ts
similarity index 100%
rename from ui/src/core_plugins/sched/table.ts
rename to ui/src/plugins/dev.perfetto.Sched/table.ts
diff --git a/ui/src/core_plugins/sched/runnable_thread_count.ts b/ui/src/plugins/dev.perfetto.Sched/thread_count.ts
similarity index 77%
rename from ui/src/core_plugins/sched/runnable_thread_count.ts
rename to ui/src/plugins/dev.perfetto.Sched/thread_count.ts
index 9b5e9c5..88fe892 100644
--- a/ui/src/core_plugins/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.Screenshots/index.ts b/ui/src/plugins/dev.perfetto.Screenshots/index.ts
new file mode 100644
index 0000000..68c7d64
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.Screenshots/index.ts
@@ -0,0 +1,50 @@
+// 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 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 {TrackNode} from '../../public/workspace';
+import {NUM} from '../../trace_processor/query_result';
+import {Trace} from '../../public/trace';
+import {PerfettoPlugin} from '../../public/plugin';
+import {ScreenshotsTrack} from './screenshots_track';
+
+export default class implements PerfettoPlugin {
+ static readonly id = 'dev.perfetto.Screenshots';
+ async onTraceLoad(ctx: Trace): Promise<void> {
+ const res = await ctx.engine.query(`
+ INCLUDE PERFETTO MODULE android.screenshots;
+ select
+ count() as count
+ from android_screenshots
+ `);
+ const {count} = res.firstRow({count: NUM});
+
+ if (count > 0) {
+ const title = 'Screenshots';
+ const uri = '/screenshots';
+ ctx.tracks.registerTrack({
+ uri,
+ title,
+ track: new ScreenshotsTrack({
+ trace: ctx,
+ uri,
+ }),
+ tags: {
+ kind: ScreenshotsTrack.kind,
+ },
+ });
+ const trackNode = new TrackNode({uri, title, sortOrder: -60});
+ ctx.workspace.addChildInOrder(trackNode);
+ }
+ }
+}
diff --git a/ui/src/core_plugins/screenshots/screenshot_panel.ts b/ui/src/plugins/dev.perfetto.Screenshots/screenshot_panel.ts
similarity index 61%
rename from ui/src/core_plugins/screenshots/screenshot_panel.ts
rename to ui/src/plugins/dev.perfetto.Screenshots/screenshot_panel.ts
index a1d8cd4..a5424af 100644
--- a/ui/src/core_plugins/screenshots/screenshot_panel.ts
+++ b/ui/src/plugins/dev.perfetto.Screenshots/screenshot_panel.ts
@@ -15,44 +15,25 @@
import m from 'mithril';
import {assertTrue} from '../../base/logging';
import {exists} from '../../base/utils';
-import {BottomTab, NewBottomTabArgs} from '../../public/lib/bottom_tab';
-import {GenericSliceDetailsTabConfig} from '../../frontend/generic_slice_details_tab';
import {getSlice, SliceDetails} from '../../trace_processor/sql_utils/slice';
import {asSliceSqlId} from '../../trace_processor/sql_utils/core_types';
import {Engine} from '../../trace_processor/engine';
+import {TrackEventDetailsPanel} from '../../public/details_panel';
+import {TrackEventSelection} from '../../public/selection';
-async function getSliceDetails(
- engine: Engine,
- id: number,
-): Promise<SliceDetails | undefined> {
- return getSlice(engine, asSliceSqlId(id));
-}
-
-export class ScreenshotTab extends BottomTab<GenericSliceDetailsTabConfig> {
- static readonly kind = 'dev.perfetto.ScreenshotDetailsPanel';
-
+export class ScreenshotDetailsPanel implements TrackEventDetailsPanel {
private sliceDetails?: SliceDetails;
- static create(
- args: NewBottomTabArgs<GenericSliceDetailsTabConfig>,
- ): ScreenshotTab {
- return new ScreenshotTab(args);
- }
+ constructor(private readonly engine: Engine) {}
- constructor(args: NewBottomTabArgs<GenericSliceDetailsTabConfig>) {
- super(args);
- getSliceDetails(this.engine, this.config.id).then(
- (sliceDetails) => (this.sliceDetails = sliceDetails),
+ async load(selection: TrackEventSelection) {
+ this.sliceDetails = await getSlice(
+ this.engine,
+ asSliceSqlId(selection.eventId),
);
}
- renderTabCanvas() {}
-
- getTitle() {
- return this.config.title;
- }
-
- viewTab() {
+ render() {
if (
!exists(this.sliceDetails) ||
!exists(this.sliceDetails.args) ||
diff --git a/ui/src/core_plugins/screenshots/screenshots_track.ts b/ui/src/plugins/dev.perfetto.Screenshots/screenshots_track.ts
similarity index 78%
rename from ui/src/core_plugins/screenshots/screenshots_track.ts
rename to ui/src/plugins/dev.perfetto.Screenshots/screenshots_track.ts
index d88010e..2364af3 100644
--- a/ui/src/core_plugins/screenshots/screenshots_track.ts
+++ b/ui/src/plugins/dev.perfetto.Screenshots/screenshots_track.ts
@@ -13,11 +13,11 @@
// limitations under the License.
import {
- CustomSqlDetailsPanelConfig,
CustomSqlTableDefConfig,
CustomSqlTableSliceTrack,
} from '../../frontend/tracks/custom_sql_table_slice_track';
-import {ScreenshotTab} from './screenshot_panel';
+import {TrackEventSelection} from '../../public/selection';
+import {ScreenshotDetailsPanel} from './screenshot_panel';
export class ScreenshotsTrack extends CustomSqlTableSliceTrack {
static readonly kind = 'dev.perfetto.ScreenshotsTrack';
@@ -29,13 +29,7 @@
};
}
- getDetailsPanel(): CustomSqlDetailsPanelConfig {
- return {
- kind: ScreenshotTab.kind,
- config: {
- sqlTableName: this.tableName,
- title: 'Screenshots',
- },
- };
+ override detailsPanel(_sel: TrackEventSelection) {
+ return new ScreenshotDetailsPanel(this.trace.engine);
}
}
diff --git a/ui/src/plugins/dev.perfetto.Thread/index.ts b/ui/src/plugins/dev.perfetto.Thread/index.ts
new file mode 100644
index 0000000..b57bd9d
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.Thread/index.ts
@@ -0,0 +1,80 @@
+// 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 {sqlTableRegistry} from '../../frontend/widgets/sql/table/sql_table_registry';
+import {Trace} from '../../public/trace';
+import {PerfettoPlugin} from '../../public/plugin';
+import {getThreadTable} from './table';
+import {extensions} from '../../public/lib/extensions';
+import {ThreadDesc, ThreadMap} from '../dev.perfetto.Thread/threads';
+import {NUM, NUM_NULL, STR, STR_NULL} from '../../trace_processor/query_result';
+import {assertExists} from '../../base/logging';
+
+async function listThreads(trace: Trace) {
+ const query = `select
+ utid,
+ tid,
+ pid,
+ ifnull(thread.name, '') as threadName,
+ ifnull(
+ case when length(process.name) > 0 then process.name else null end,
+ thread.name) as procName,
+ process.cmdline as cmdline
+ from (select * from thread order by upid) as thread
+ left join (select * from process order by upid) as process
+ using(upid)`;
+ const result = await trace.engine.query(query);
+ const threads = new Map<number, ThreadDesc>();
+ const it = result.iter({
+ utid: NUM,
+ tid: NUM,
+ pid: NUM_NULL,
+ threadName: STR,
+ procName: STR_NULL,
+ cmdline: STR_NULL,
+ });
+ for (; it.valid(); it.next()) {
+ const utid = it.utid;
+ const tid = it.tid;
+ const pid = it.pid === null ? undefined : it.pid;
+ const threadName = it.threadName;
+ const procName = it.procName === null ? undefined : it.procName;
+ const cmdline = it.cmdline === null ? undefined : it.cmdline;
+ threads.set(utid, {utid, tid, threadName, pid, procName, cmdline});
+ }
+ return threads;
+}
+
+export default class implements PerfettoPlugin {
+ static readonly id = 'dev.perfetto.Thread';
+ private threads?: ThreadMap;
+
+ async onTraceLoad(ctx: Trace) {
+ sqlTableRegistry['thread'] = getThreadTable();
+ ctx.commands.registerCommand({
+ id: 'perfetto.ShowTable.thread',
+ name: 'Open table: thread',
+ callback: () => {
+ extensions.addSqlTableTab(ctx, {
+ table: getThreadTable(),
+ });
+ },
+ });
+ this.threads = await listThreads(ctx);
+ }
+
+ getThreadMap() {
+ return assertExists(this.threads);
+ }
+}
diff --git a/ui/src/core_plugins/thread/table.ts b/ui/src/plugins/dev.perfetto.Thread/table.ts
similarity index 100%
rename from ui/src/core_plugins/thread/table.ts
rename to ui/src/plugins/dev.perfetto.Thread/table.ts
diff --git a/ui/src/common/state_unittest.ts b/ui/src/plugins/dev.perfetto.Thread/threads.ts
similarity index 68%
rename from ui/src/common/state_unittest.ts
rename to ui/src/plugins/dev.perfetto.Thread/threads.ts
index af8f5f9..22c1eb9 100644
--- a/ui/src/common/state_unittest.ts
+++ b/ui/src/plugins/dev.perfetto.Thread/threads.ts
@@ -1,4 +1,4 @@
-// Copyright (C) 2018 The Android Open Source Project
+// 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.
@@ -12,10 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {createEmptyState} from './empty_state';
-import {State} from './state';
+export interface ThreadDesc {
+ utid: number;
+ tid: number;
+ threadName: string;
+ pid?: number;
+ procName?: string;
+ cmdline?: string;
+}
-test('createEmptyState', () => {
- const state: State = createEmptyState();
- expect(state.engine).toEqual(undefined);
-});
+export type ThreadMap = ReadonlyMap<number, ThreadDesc>;
diff --git a/ui/src/core_plugins/thread_state/index.ts b/ui/src/plugins/dev.perfetto.ThreadState/index.ts
similarity index 75%
rename from ui/src/core_plugins/thread_state/index.ts
rename to ui/src/plugins/dev.perfetto.ThreadState/index.ts
index f650ad8..a106654 100644
--- a/ui/src/core_plugins/thread_state/index.ts
+++ b/ui/src/plugins/dev.perfetto.ThreadState/index.ts
@@ -12,29 +12,26 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {uuidv4} from '../../base/uuid';
import {THREAD_STATE_TRACK_KIND} from '../../public/track_kinds';
-import {asThreadStateSqlId} from '../../trace_processor/sql_utils/core_types';
-import {ThreadStateTab} from '../../frontend/thread_state_tab';
-import {BottomTabToSCSAdapter} from '../../public/utils';
import {Trace} from '../../public/trace';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
+import {PerfettoPlugin} from '../../public/plugin';
import {getThreadUriPrefix, getTrackName} from '../../public/utils';
import {NUM, NUM_NULL, STR_NULL} from '../../trace_processor/query_result';
import {ThreadStateTrack} from './thread_state_track';
import {removeFalsyValues} from '../../base/array_utils';
import {getThreadStateTable} from './table';
import {sqlTableRegistry} from '../../frontend/widgets/sql/table/sql_table_registry';
-import {addSqlTableTab} from '../../frontend/sql_table_tab_interface';
import {TrackNode} from '../../public/workspace';
import {getOrCreateGroupForThread} from '../../public/standard_groups';
import {ThreadStateSelectionAggregator} from './thread_state_selection_aggregator';
+import {extensions} from '../../public/lib/extensions';
function uriForThreadStateTrack(upid: number | null, utid: number): string {
return `${getThreadUriPrefix(upid, utid)}_state`;
}
-class ThreadState implements PerfettoPlugin {
+export default class implements PerfettoPlugin {
+ static readonly id = 'dev.perfetto.ThreadState';
async onTraceLoad(ctx: Trace): Promise<void> {
const {engine} = ctx;
@@ -101,29 +98,12 @@
group.addChildInOrder(track);
}
- ctx.tabs.registerDetailsPanel(
- new BottomTabToSCSAdapter({
- tabFactory: (sel) => {
- if (sel.kind !== 'THREAD_STATE') {
- return undefined;
- }
- return new ThreadStateTab({
- config: {
- id: asThreadStateSqlId(sel.id),
- },
- trace: ctx,
- uuid: uuidv4(),
- });
- },
- }),
- );
-
sqlTableRegistry['thread_state'] = getThreadStateTable();
ctx.commands.registerCommand({
id: 'perfetto.ShowTable.thread_state',
name: 'Open table: thread_state',
callback: () => {
- addSqlTableTab(ctx, {
+ extensions.addSqlTableTab(ctx, {
table: getThreadStateTable(),
});
},
@@ -135,10 +115,10 @@
const result = await ctx.engine.query(`
select
thread_state.utid,
- t.upid
+ thread.upid
from
thread_state
- join _threads_with_kernel_flag t
+ join thread on thread_state.utid = thread.id
where thread_state.id = ${id}
`);
@@ -148,20 +128,10 @@
});
return {
- kind: 'legacy',
- legacySelection: {
- kind: 'THREAD_STATE',
- id,
- trackUri: uriForThreadStateTrack(upid, utid),
- table: 'slice',
- },
+ eventId: id,
+ trackUri: uriForThreadStateTrack(upid, utid),
};
},
});
}
}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'perfetto.ThreadState',
- plugin: ThreadState,
-};
diff --git a/ui/src/core_plugins/thread_state/table.ts b/ui/src/plugins/dev.perfetto.ThreadState/table.ts
similarity index 82%
rename from ui/src/core_plugins/thread_state/table.ts
rename to ui/src/plugins/dev.perfetto.ThreadState/table.ts
index 9c86c5c..c93fb40 100644
--- a/ui/src/core_plugins/thread_state/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
new file mode 100644
index 0000000..0a2287b7
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.ThreadState/thread_state_details_panel.ts
@@ -0,0 +1,335 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use size 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 {Anchor} from '../../widgets/anchor';
+import {Button} from '../../widgets/button';
+import {DetailsShell} from '../../widgets/details_shell';
+import {GridLayout} from '../../widgets/grid_layout';
+import {Section} from '../../widgets/section';
+import {SqlRef} from '../../widgets/sql_ref';
+import {Tree, TreeNode} from '../../widgets/tree';
+import {Intent} from '../../widgets/common';
+import {SchedSqlId} from '../../trace_processor/sql_utils/core_types';
+import {
+ getThreadState,
+ getThreadStateFromConstraints,
+ ThreadState,
+} from '../../trace_processor/sql_utils/thread_state';
+import {DurationWidget, renderDuration} from '../../frontend/widgets/duration';
+import {Timestamp} from '../../frontend/widgets/timestamp';
+import {getProcessName} from '../../trace_processor/sql_utils/process';
+import {
+ getFullThreadName,
+ getThreadName,
+} from '../../trace_processor/sql_utils/thread';
+import {ThreadStateRef} from '../../frontend/widgets/thread_state';
+import {
+ CRITICAL_PATH_CMD,
+ CRITICAL_PATH_LITE_CMD,
+} from '../../public/exposed_commands';
+import {goToSchedSlice} from '../../frontend/widgets/sched';
+import {TrackEventDetailsPanel} from '../../public/details_panel';
+import {Trace} from '../../public/trace';
+
+interface RelatedThreadStates {
+ prev?: ThreadState;
+ next?: ThreadState;
+ waker?: ThreadState;
+ wakerInterruptCtx?: boolean;
+ wakee?: ThreadState[];
+}
+
+export class ThreadStateDetailsPanel implements TrackEventDetailsPanel {
+ private threadState?: ThreadState;
+ private relatedStates?: RelatedThreadStates;
+
+ constructor(
+ private readonly trace: Trace,
+ private readonly id: number,
+ ) {}
+
+ async load() {
+ const id = this.id;
+ this.threadState = await getThreadState(this.trace.engine, id);
+
+ if (!this.threadState) {
+ return;
+ }
+
+ const relatedStates: RelatedThreadStates = {};
+ relatedStates.prev = (
+ await getThreadStateFromConstraints(this.trace.engine, {
+ filters: [
+ `ts + dur = ${this.threadState.ts}`,
+ `utid = ${this.threadState.thread?.utid}`,
+ ],
+ limit: 1,
+ })
+ )[0];
+ relatedStates.next = (
+ await getThreadStateFromConstraints(this.trace.engine, {
+ filters: [
+ `ts = ${this.threadState.ts + this.threadState.dur}`,
+ `utid = ${this.threadState.thread?.utid}`,
+ ],
+ limit: 1,
+ })
+ )[0];
+ if (this.threadState.wakerId !== undefined) {
+ relatedStates.waker = await getThreadState(
+ this.trace.engine,
+ 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.threadState.wakerInterruptCtx;
+
+ relatedStates.wakee = await getThreadStateFromConstraints(
+ this.trace.engine,
+ {
+ filters: [
+ `waker_id = ${id}`,
+ `(irq_context is null or irq_context = 0)`,
+ ],
+ },
+ );
+ this.relatedStates = relatedStates;
+ }
+
+ render() {
+ // TODO(altimin/stevegolton): Differentiate between "Current Selection" and
+ // "Pinned" views in DetailsShell.
+ return m(
+ DetailsShell,
+ {title: 'Thread State', description: this.renderLoadingText()},
+ m(
+ GridLayout,
+ m(
+ Section,
+ {title: 'Details'},
+ this.threadState && this.renderTree(this.threadState),
+ ),
+ m(
+ Section,
+ {title: 'Related thread states'},
+ this.renderRelatedThreadStates(),
+ ),
+ ),
+ );
+ }
+
+ private renderLoadingText() {
+ if (!this.threadState) {
+ return 'Loading';
+ }
+ return this.id;
+ }
+
+ 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: threadState.ts}),
+ }),
+ m(TreeNode, {
+ left: 'Duration',
+ right: m(DurationWidget, {dur: threadState.dur}),
+ }),
+ m(TreeNode, {
+ left: 'State',
+ right: this.renderState(
+ threadState.state,
+ threadState.cpu,
+ threadState.schedSqlId,
+ ),
+ }),
+ threadState.blockedFunction &&
+ m(TreeNode, {
+ left: 'Blocked function',
+ right: threadState.blockedFunction,
+ }),
+ process &&
+ m(TreeNode, {
+ left: 'Process',
+ 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: threadState.id}),
+ }),
+ );
+ }
+
+ private renderState(
+ state: string,
+ cpu: number | undefined,
+ id: SchedSqlId | undefined,
+ ): m.Children {
+ if (!state) {
+ return null;
+ }
+ if (id === undefined || cpu === undefined) {
+ return state;
+ }
+ return m(
+ Anchor,
+ {
+ title: 'Go to CPU slice',
+ icon: 'call_made',
+ onclick: () => goToSchedSlice(id),
+ },
+ `${state} on CPU ${cpu}`,
+ );
+ }
+
+ private renderRelatedThreadStates(): m.Children {
+ if (this.threadState === undefined || this.relatedStates === undefined) {
+ return 'Loading';
+ }
+ const startTs = this.threadState.ts;
+ const renderRef = (state: ThreadState, name?: string) =>
+ m(ThreadStateRef, {
+ id: state.id,
+ name,
+ });
+
+ const nameForNextOrPrev = (threadState: ThreadState) =>
+ `${threadState.state} for ${renderDuration(threadState.dur)}`;
+
+ const renderWaker = (related: RelatedThreadStates) => {
+ // Could be absent if:
+ // * this thread state wasn't woken up (e.g. it is a running slice).
+ // * the wakeup is from an interrupt during the idle process (which
+ // isn't populated in thread_state).
+ // * at the start of the trace, before all per-cpu scheduling is known.
+ const hasWakerId = related.waker !== undefined;
+ // Interrupt context for the wakeups is absent from older traces.
+ const hasInterruptCtx = related.wakerInterruptCtx !== undefined;
+
+ if (!hasWakerId && !hasInterruptCtx) {
+ return null;
+ }
+ if (related.wakerInterruptCtx) {
+ return m(TreeNode, {
+ left: 'Woken by',
+ right: `Interrupt`,
+ });
+ }
+ return (
+ related.waker &&
+ m(TreeNode, {
+ left: hasInterruptCtx ? 'Woken by' : 'Woken by (maybe interrupt)',
+ right: renderRef(
+ related.waker,
+ getFullThreadName(related.waker.thread),
+ ),
+ })
+ );
+ };
+
+ const renderWakees = (related: RelatedThreadStates) => {
+ if (related.wakee === undefined || related.wakee.length == 0) {
+ return null;
+ }
+ const hasInterruptCtx = related.wakee[0].wakerInterruptCtx !== undefined;
+ return m(
+ TreeNode,
+ {
+ left: hasInterruptCtx
+ ? 'Woken threads'
+ : 'Woken threads (maybe interrupt)',
+ },
+ related.wakee.map((state) =>
+ m(TreeNode, {
+ left: m(Timestamp, {
+ ts: state.ts,
+ display: `+${renderDuration(state.ts - startTs)}`,
+ }),
+ right: renderRef(state, getFullThreadName(state.thread)),
+ }),
+ ),
+ );
+ };
+
+ return [
+ m(
+ Tree,
+ this.relatedStates.prev &&
+ m(TreeNode, {
+ left: 'Previous state',
+ right: renderRef(
+ this.relatedStates.prev,
+ nameForNextOrPrev(this.relatedStates.prev),
+ ),
+ }),
+ this.relatedStates.next &&
+ m(TreeNode, {
+ left: 'Next state',
+ right: renderRef(
+ this.relatedStates.next,
+ nameForNextOrPrev(this.relatedStates.next),
+ ),
+ }),
+ renderWaker(this.relatedStates),
+ renderWakees(this.relatedStates),
+ ),
+ this.trace.commands.hasCommand(CRITICAL_PATH_LITE_CMD) &&
+ m(Button, {
+ label: 'Critical path lite',
+ intent: Intent.Primary,
+ onclick: () => {
+ this.trace.commands.runCommand(
+ CRITICAL_PATH_LITE_CMD,
+ this.threadState?.thread?.utid,
+ );
+ },
+ }),
+ this.trace.commands.hasCommand(CRITICAL_PATH_CMD) &&
+ m(Button, {
+ label: 'Critical path',
+ intent: Intent.Primary,
+ onclick: () => {
+ this.trace.commands.runCommand(
+ CRITICAL_PATH_CMD,
+ this.threadState?.thread?.utid,
+ );
+ },
+ }),
+ ];
+ }
+
+ isLoading() {
+ return this.threadState === undefined || this.relatedStates === undefined;
+ }
+}
diff --git a/ui/src/core_plugins/thread_state/thread_state_selection_aggregator.ts b/ui/src/plugins/dev.perfetto.ThreadState/thread_state_selection_aggregator.ts
similarity index 100%
rename from ui/src/core_plugins/thread_state/thread_state_selection_aggregator.ts
rename to ui/src/plugins/dev.perfetto.ThreadState/thread_state_selection_aggregator.ts
diff --git a/ui/src/core_plugins/thread_state/thread_state_track.ts b/ui/src/plugins/dev.perfetto.ThreadState/thread_state_track.ts
similarity index 78%
rename from ui/src/core_plugins/thread_state/thread_state_track.ts
rename to ui/src/plugins/dev.perfetto.ThreadState/thread_state_track.ts
index 0a61919..3935465 100644
--- a/ui/src/core_plugins/thread_state/thread_state_track.ts
+++ b/ui/src/plugins/dev.perfetto.ThreadState/thread_state_track.ts
@@ -12,14 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {colorForState} from '../../core/colorizer';
-import {LegacySelection} from '../../public/selection';
+import {colorForState} from '../../public/lib/colorizer';
import {
BASE_ROW,
BaseSliceTrack,
OnSliceClickArgs,
} from '../../frontend/base_slice_track';
-import {globals} from '../../frontend/globals';
import {
SLICE_LAYOUT_FLAT_DEFAULTS,
SliceLayout,
@@ -28,6 +26,8 @@
import {NUM_NULL, STR} from '../../trace_processor/query_result';
import {Slice} from '../../public/track';
import {translateState} from '../../trace_processor/sql_utils/thread_state';
+import {TrackEventDetails, TrackEventSelection} from '../../public/selection';
+import {ThreadStateDetailsPanel} from './thread_state_details_panel';
export const THREAD_STATE_ROW = {
...BASE_ROW,
@@ -85,14 +85,18 @@
}
onSliceClick(args: OnSliceClickArgs<Slice>) {
- globals.selectionManager.selectLegacy({
- kind: 'THREAD_STATE',
- id: args.slice.id,
- trackUri: this.uri,
- });
+ this.trace.selection.selectTrackEvent(this.uri, args.slice.id);
}
- protected isSelectionHandled(selection: LegacySelection): boolean {
- return selection.kind === 'THREAD_STATE';
+ // Add utid to selection details
+ override async getSelectionDetails(
+ id: number,
+ ): Promise<TrackEventDetails | undefined> {
+ const details = await super.getSelectionDetails(id);
+ return details && {...details, utid: this.utid};
+ }
+
+ detailsPanel({eventId}: TrackEventSelection) {
+ return new ThreadStateDetailsPanel(this.trace, eventId);
}
}
diff --git a/ui/src/plugins/dev.perfetto.TimelineSync/index.ts b/ui/src/plugins/dev.perfetto.TimelineSync/index.ts
index 2efd321..aef7a76 100644
--- a/ui/src/plugins/dev.perfetto.TimelineSync/index.ts
+++ b/ui/src/plugins/dev.perfetto.TimelineSync/index.ts
@@ -14,8 +14,7 @@
import m from 'mithril';
import {Trace} from '../../public/trace';
-import {App} from '../../public/app';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
+import {PerfettoPlugin} from '../../public/plugin';
import {Time, TimeSpan} from '../../base/time';
import {redrawModal, showModal} from '../../widgets/modal';
import {assertExists} from '../../base/logging';
@@ -40,7 +39,8 @@
* their durations don't match. The initial viewport bound for each trace is
* selected when the enable command is called.
*/
-class TimelineSync implements PerfettoPlugin {
+export default class implements PerfettoPlugin {
+ static readonly id = PLUGIN_ID;
private _chan?: BroadcastChannel;
private _ctx?: Trace;
private _traceLoadTime = 0;
@@ -64,7 +64,7 @@
ViewportBoundsSnapshot
>();
- onActivate(ctx: App): void {
+ async onTraceLoad(ctx: Trace) {
ctx.commands.registerCommand({
id: `dev.perfetto.SplitScreen#enableTimelineSync`,
name: 'Enable timeline sync with other Perfetto UI tabs',
@@ -98,20 +98,17 @@
? parseInt(m[1].substring(1))
: DEFAULT_SESSION_ID;
}
- }
- async onTraceLoad(ctx: Trace) {
this._ctx = ctx;
this._traceLoadTime = Date.now();
this.advertise();
if (this._sessionidFromUrl !== 0) {
this.enableTimelineSync(this._sessionidFromUrl);
}
- }
-
- async onTraceUnload(_: Trace) {
- this.disableTimelineSync(this._sessionId);
- this._ctx = undefined;
+ ctx.trash.defer(() => {
+ this.disableTimelineSync(this._sessionId);
+ this._ctx = undefined;
+ });
}
private advertise() {
@@ -457,8 +454,3 @@
lastHeartbeat: number; // Datetime.now() of the last MSG_ADVERTISE.
traceLoadTime: number; // Datetime.now() of the onTraceLoad().
}
-
-export const plugin: PluginDescriptor = {
- pluginId: PLUGIN_ID,
- plugin: TimelineSync,
-};
diff --git a/ui/src/plugins/dev.perfetto.TraceInfoPage/index.ts b/ui/src/plugins/dev.perfetto.TraceInfoPage/index.ts
new file mode 100644
index 0000000..f019331
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.TraceInfoPage/index.ts
@@ -0,0 +1,32 @@
+// 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 {PerfettoPlugin} from '../../public/plugin';
+import {Trace} from '../../public/trace';
+import {TraceInfoPage} from './trace_info_page';
+
+export default class implements PerfettoPlugin {
+ static readonly id = 'dev.perfetto.TraceInfoPage';
+
+ async onTraceLoad(trace: Trace): Promise<void> {
+ trace.pages.registerPage({route: '/info', page: TraceInfoPage});
+ trace.sidebar.addMenuItem({
+ section: 'current_trace',
+ text: 'Info and stats',
+ href: '#!/info',
+ icon: 'info',
+ sortOrder: 10,
+ });
+ }
+}
diff --git a/ui/src/frontend/trace_info_page.ts b/ui/src/plugins/dev.perfetto.TraceInfoPage/trace_info_page.ts
similarity index 94%
rename from ui/src/frontend/trace_info_page.ts
rename to ui/src/plugins/dev.perfetto.TraceInfoPage/trace_info_page.ts
index 4b1eb9e..686b976 100644
--- a/ui/src/frontend/trace_info_page.ts
+++ b/ui/src/plugins/dev.perfetto.TraceInfoPage/trace_info_page.ts
@@ -13,12 +13,11 @@
// limitations under the License.
import m from 'mithril';
-import {raf} from '../core/raf_scheduler';
-import {Engine, EngineAttrs} from '../trace_processor/engine';
-import {globals} from './globals';
-import {PageWithTraceAttrs} from './pages';
-import {QueryResult, UNKNOWN} from '../trace_processor/query_result';
-import {assertExists} from '../base/logging';
+import {Engine, EngineAttrs} from '../../trace_processor/engine';
+import {QueryResult, UNKNOWN} from '../../trace_processor/query_result';
+import {assertExists} from '../../base/logging';
+import {TraceAttrs} from '../../public/trace';
+import {PageWithTraceAttrs} from '../../public/page';
/**
* Extracts and copies fields from a source object based on the keys present in
@@ -102,8 +101,6 @@
data.push(pickFields(it, statsSpec));
}
this.data = data;
-
- raf.scheduleFullRedraw();
});
}
@@ -140,14 +137,15 @@
}
}
-class MetricErrors implements m.ClassComponent {
- view() {
- if (!globals.metricError) return;
+class LoadingErrors implements m.ClassComponent<TraceAttrs> {
+ view({attrs}: m.CVnode<TraceAttrs>) {
+ const errors = attrs.trace.loadingErrors;
+ if (errors.length === 0) return;
return m(
`section.errors`,
- m('h2', `Metric Errors`),
- m('h3', `One or more metrics were not computed successfully:`),
- m('div.metric-error', globals.metricError),
+ m('h2', `Loading errors`),
+ m('h3', `The following errors were encountered while loading the trace:`),
+ m('pre.metric-error', errors.join('\n')),
);
}
}
@@ -193,7 +191,6 @@
tableRows.push(pickFields(it, traceMetadataRowSpec));
}
this.data = tableRows;
- raf.scheduleFullRedraw();
});
}
@@ -271,7 +268,6 @@
data.push(pickFields(it, androidGameInterventionRowSpec));
}
this.data = data;
- raf.scheduleFullRedraw();
});
}
@@ -385,7 +381,6 @@
}
this.packageList = packageList;
- raf.scheduleFullRedraw();
}
view() {
@@ -432,11 +427,11 @@
this.engine = attrs.trace.engine.getProxy('TraceInfoPage');
}
- view() {
+ view({attrs}: m.CVnode<PageWithTraceAttrs>) {
const engine = assertExists(this.engine);
return m(
'.trace-info-page',
- m(MetricErrors),
+ m(LoadingErrors, {trace: attrs.trace}),
m(StatsSection, {
engine,
queryId: 'info_errors',
diff --git a/ui/src/plugins/dev.perfetto.TraceMetadata/index.ts b/ui/src/plugins/dev.perfetto.TraceMetadata/index.ts
index 4199c44..a10afe1 100644
--- a/ui/src/plugins/dev.perfetto.TraceMetadata/index.ts
+++ b/ui/src/plugins/dev.perfetto.TraceMetadata/index.ts
@@ -14,10 +14,12 @@
import {NUM} from '../../trace_processor/query_result';
import {Trace} from '../../public/trace';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
-import {SimpleSliceTrack} from '../../frontend/simple_slice_track';
+import {PerfettoPlugin} from '../../public/plugin';
+import {createQuerySliceTrack} from '../../public/lib/tracks/query_slice_track';
import {TrackNode} from '../../public/workspace';
-class TraceMetadata implements PerfettoPlugin {
+
+export default class implements PerfettoPlugin {
+ static readonly id = 'dev.perfetto.TraceMetadata';
async onTraceLoad(ctx: Trace): Promise<void> {
const res = await ctx.engine.query(`
select count() as cnt from (select 1 from clock_snapshot limit 1)
@@ -28,31 +30,22 @@
}
const uri = `/clock_snapshots`;
const title = 'Clock Snapshots';
+ const track = await createQuerySliceTrack({
+ trace: ctx,
+ uri,
+ data: {
+ sqlSource: `
+ select ts, 0 as dur, 'Snapshot' as name
+ from clock_snapshot
+ `,
+ },
+ });
ctx.tracks.registerTrack({
uri,
title,
- track: new SimpleSliceTrack(
- ctx,
- {trackUri: uri},
- {
- data: {
- sqlSource: `
- select ts, 0 as dur, 'Snapshot' as name
- from clock_snapshot
- `,
- columns: ['ts', 'dur', 'name'],
- },
- columns: {ts: 'ts', dur: 'dur', name: 'name'},
- argColumns: [],
- },
- ),
+ track,
});
- const track = new TrackNode({uri, title});
- ctx.workspace.addChildInOrder(track);
+ const trackNode = new TrackNode({uri, title});
+ ctx.workspace.addChildInOrder(trackNode);
}
}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'dev.perfetto.TraceMetadata',
- plugin: TraceMetadata,
-};
diff --git a/ui/src/plugins/dev.perfetto.VizPage/index.ts b/ui/src/plugins/dev.perfetto.VizPage/index.ts
new file mode 100644
index 0000000..9476479
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.VizPage/index.ts
@@ -0,0 +1,32 @@
+// 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 {PerfettoPlugin} from '../../public/plugin';
+import {Trace} from '../../public/trace';
+import {VizPage} from './viz_page';
+
+export default class implements PerfettoPlugin {
+ static readonly id = 'dev.perfetto.VizPage';
+
+ async onTraceLoad(trace: Trace): Promise<void> {
+ trace.pages.registerPage({route: '/viz', page: VizPage});
+ trace.sidebar.addMenuItem({
+ section: 'current_trace',
+ text: 'Viz',
+ href: '#!/viz',
+ icon: 'area_chart',
+ sortOrder: 2,
+ });
+ }
+}
diff --git a/ui/src/frontend/viz_page.ts b/ui/src/plugins/dev.perfetto.VizPage/viz_page.ts
similarity index 68%
rename from ui/src/frontend/viz_page.ts
rename to ui/src/plugins/dev.perfetto.VizPage/viz_page.ts
index 1a9e9c4..a838a99 100644
--- a/ui/src/frontend/viz_page.ts
+++ b/ui/src/plugins/dev.perfetto.VizPage/viz_page.ts
@@ -13,35 +13,32 @@
// limitations under the License.
import m from 'mithril';
-import {raf} from '../core/raf_scheduler';
-import {Editor} from '../widgets/editor';
-import {VegaView} from '../widgets/vega_view';
-import {PageWithTraceAttrs} from './pages';
-import {assertExists} from '../base/logging';
-import {Engine} from '../trace_processor/engine';
+import {Editor} from '../../widgets/editor';
+import {VegaView} from '../../widgets/vega_view';
+import {PageWithTraceAttrs} from '../../public/page';
+import {Engine} from '../../trace_processor/engine';
let SPEC = '';
export class VizPage implements m.ClassComponent<PageWithTraceAttrs> {
- private engine?: Engine;
+ private engine: Engine;
- oninit({attrs}: m.CVnode<PageWithTraceAttrs>) {
+ constructor({attrs}: m.CVnode<PageWithTraceAttrs>) {
this.engine = attrs.trace.engine.getProxy('VizPage');
}
- view() {
- const engine = assertExists(this.engine);
+ view({attrs}: m.CVnode<PageWithTraceAttrs>) {
return m(
'.viz-page',
m(VegaView, {
spec: SPEC,
- engine: engine,
+ engine: this.engine,
data: {},
}),
m(Editor, {
onUpdate: (text: string) => {
SPEC = text;
- raf.scheduleFullRedraw();
+ attrs.trace.scheduleFullRedraw();
},
}),
);
diff --git a/ui/src/plugins/dev.perfetto.WidgetsPage/index.ts b/ui/src/plugins/dev.perfetto.WidgetsPage/index.ts
new file mode 100644
index 0000000..44fade1
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.WidgetsPage/index.ts
@@ -0,0 +1,36 @@
+// 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 {App} from '../../public/app';
+import {PerfettoPlugin} from '../../public/plugin';
+import {WidgetsPage} from './widgets_page';
+
+export default class implements PerfettoPlugin {
+ static readonly id = 'dev.perfetto.WidgetsPage';
+
+ static onActivate(app: App): void {
+ app.pages.registerPage({
+ route: '/widgets',
+ page: WidgetsPage,
+ traceless: true,
+ });
+ app.sidebar.addMenuItem({
+ section: 'navigation',
+ text: 'Widgets',
+ href: '#!/widgets',
+ icon: 'widgets',
+ sortOrder: 99,
+ });
+ }
+}
diff --git a/ui/src/frontend/tables/table_showcase.ts b/ui/src/plugins/dev.perfetto.WidgetsPage/table_showcase.ts
similarity index 97%
rename from ui/src/frontend/tables/table_showcase.ts
rename to ui/src/plugins/dev.perfetto.WidgetsPage/table_showcase.ts
index c0da7c1..00936bc 100644
--- a/ui/src/frontend/tables/table_showcase.ts
+++ b/ui/src/plugins/dev.perfetto.WidgetsPage/table_showcase.ts
@@ -19,7 +19,7 @@
stringColumn,
Table,
TableData,
-} from './table';
+} from '../../widgets/table';
// This file serves as an example of a table component present in the widgets
// showcase. Since table is somewhat complicated component that requires some
diff --git a/ui/src/frontend/widgets_page.ts b/ui/src/plugins/dev.perfetto.WidgetsPage/widgets_page.ts
similarity index 92%
rename from ui/src/frontend/widgets_page.ts
rename to ui/src/plugins/dev.perfetto.WidgetsPage/widgets_page.ts
index 847b1ee..327a179 100644
--- a/ui/src/frontend/widgets_page.ts
+++ b/ui/src/plugins/dev.perfetto.WidgetsPage/widgets_page.ts
@@ -13,51 +13,51 @@
// limitations under the License.
import m from 'mithril';
-import {classNames} from '../base/classnames';
-import {Hotkey, Platform} from '../base/hotkeys';
-import {isString} from '../base/object_utils';
-import {Icons} from '../base/semantic_icons';
-import {raf} from '../core/raf_scheduler';
-import {Anchor} from '../widgets/anchor';
-import {Button} from '../widgets/button';
-import {Callout} from '../widgets/callout';
-import {Checkbox} from '../widgets/checkbox';
-import {Editor} from '../widgets/editor';
-import {EmptyState} from '../widgets/empty_state';
-import {Form, FormLabel} from '../widgets/form';
-import {HotkeyGlyphs} from '../widgets/hotkey_glyphs';
-import {Icon} from '../widgets/icon';
-import {Menu, MenuDivider, MenuItem, PopupMenu2} from '../widgets/menu';
-import {showModal} from '../widgets/modal';
+import {classNames} from '../../base/classnames';
+import {Hotkey, Platform} from '../../base/hotkeys';
+import {isString} from '../../base/object_utils';
+import {Icons} from '../../base/semantic_icons';
+import {Anchor} from '../../widgets/anchor';
+import {Button} from '../../widgets/button';
+import {Callout} from '../../widgets/callout';
+import {Checkbox} from '../../widgets/checkbox';
+import {Editor} from '../../widgets/editor';
+import {EmptyState} from '../../widgets/empty_state';
+import {Form, FormLabel} from '../../widgets/form';
+import {HotkeyGlyphs} from '../../widgets/hotkey_glyphs';
+import {Icon} from '../../widgets/icon';
+import {Menu, MenuDivider, MenuItem, PopupMenu2} from '../../widgets/menu';
+import {showModal} from '../../widgets/modal';
import {
MultiSelect,
MultiSelectDiff,
PopupMultiSelect,
-} from '../widgets/multiselect';
-import {Popup, PopupPosition} from '../widgets/popup';
-import {Portal} from '../widgets/portal';
-import {Select} from '../widgets/select';
-import {Spinner} from '../widgets/spinner';
-import {Switch} from '../widgets/switch';
-import {TextInput} from '../widgets/text_input';
-import {MultiParagraphText, TextParagraph} from '../widgets/text_paragraph';
-import {LazyTreeNode, Tree, TreeNode} from '../widgets/tree';
-import {VegaView} from '../widgets/vega_view';
-import {PageAttrs} from './pages';
-import {PopupMenuButton} from './popup_menu';
-import {TableShowcase} from './tables/table_showcase';
-import {TreeTable, TreeTableAttrs} from './widgets/treetable';
-import {Intent} from '../widgets/common';
+} from '../../widgets/multiselect';
+import {Popup, PopupPosition} from '../../widgets/popup';
+import {Portal} from '../../widgets/portal';
+import {Select} from '../../widgets/select';
+import {Spinner} from '../../widgets/spinner';
+import {Switch} from '../../widgets/switch';
+import {TextInput} from '../../widgets/text_input';
+import {MultiParagraphText, TextParagraph} from '../../widgets/text_paragraph';
+import {LazyTreeNode, Tree, TreeNode} from '../../widgets/tree';
+import {VegaView} from '../../widgets/vega_view';
+import {PageAttrs} from '../../public/page';
+import {TableShowcase} from './table_showcase';
+import {TreeTable, TreeTableAttrs} from '../../frontend/widgets/treetable';
+import {Intent} from '../../widgets/common';
import {
VirtualTable,
VirtualTableAttrs,
VirtualTableRow,
-} from '../widgets/virtual_table';
-import {TagInput} from '../widgets/tag_input';
-import {SegmentedButtons} from '../widgets/segmented_buttons';
-import {MiddleEllipsis} from '../widgets/middle_ellipsis';
-import {Chip, ChipBar} from '../widgets/chip';
-import {TrackWidget} from '../widgets/track_widget';
+} from '../../widgets/virtual_table';
+import {TagInput} from '../../widgets/tag_input';
+import {SegmentedButtons} from '../../widgets/segmented_buttons';
+import {MiddleEllipsis} from '../../widgets/middle_ellipsis';
+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: [
@@ -310,7 +310,7 @@
intent: Intent.Primary,
onclick: () => {
portalOpen = !portalOpen;
- raf.scheduleFullRedraw();
+ scheduleFullRedraw();
},
}),
portalOpen &&
@@ -362,7 +362,7 @@
label: 'Close Popup',
onclick: () => {
popupOpen = !popupOpen;
- raf.scheduleFullRedraw();
+ scheduleFullRedraw();
},
}),
);
@@ -498,7 +498,7 @@
label: key,
onchange: () => {
this.optValues[key] = !Boolean(this.optValues[key]);
- raf.scheduleFullRedraw();
+ scheduleFullRedraw();
},
});
}
@@ -512,7 +512,7 @@
value: this.optValues[key],
oninput: (e: Event) => {
this.optValues[key] = (e.target as HTMLInputElement).value;
- raf.scheduleFullRedraw();
+ scheduleFullRedraw();
},
}),
);
@@ -530,7 +530,7 @@
this.optValues[key] = Number.parseInt(
(e.target as HTMLInputElement).value,
);
- raf.scheduleFullRedraw();
+ scheduleFullRedraw();
},
}),
);
@@ -550,7 +550,7 @@
onchange: (e: Event) => {
const el = e.target as HTMLSelectElement;
this.optValues[key] = el.value;
- raf.scheduleFullRedraw();
+ scheduleFullRedraw();
},
},
optionElements,
@@ -642,14 +642,14 @@
onTagAdd: (tag) => {
tags.push(tag);
tagInputValue = '';
- raf.scheduleFullRedraw();
+ scheduleFullRedraw();
},
onChange: (value) => {
tagInputValue = value;
},
onTagRemove: (index) => {
tags.splice(index, 1);
- raf.scheduleFullRedraw();
+ scheduleFullRedraw();
},
});
},
@@ -666,7 +666,7 @@
selectedOption: selectedIdx,
onOptionSelected: (num) => {
selectedIdx = num;
- raf.scheduleFullRedraw();
+ scheduleFullRedraw();
},
});
},
@@ -775,13 +775,24 @@
href: 'https://perfetto.dev/docs/',
target: '_blank',
},
- 'Docs',
+ 'This is some really long text and it will probably overflow the container',
),
initialOpts: {
icon: true,
},
}),
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: {},
@@ -854,7 +865,7 @@
diffs.forEach(({id, checked}) => {
options[id] = checked;
});
- raf.scheduleFullRedraw();
+ scheduleFullRedraw();
},
...rest,
}),
@@ -881,7 +892,7 @@
diffs.forEach(({id, checked}) => {
options[id] = checked;
});
- raf.scheduleFullRedraw();
+ scheduleFullRedraw();
},
...rest,
}),
@@ -892,30 +903,6 @@
},
}),
m(WidgetShowcase, {
- label: 'PopupMenu',
- renderWidget: () => {
- return m(PopupMenuButton, {
- icon: 'description',
- items: [
- {itemType: 'regular', text: 'New', callback: () => {}},
- {itemType: 'regular', text: 'Open', callback: () => {}},
- {itemType: 'regular', text: 'Save', callback: () => {}},
- {itemType: 'regular', text: 'Delete', callback: () => {}},
- {
- itemType: 'group',
- text: 'Share',
- itemId: 'foo',
- children: [
- {itemType: 'regular', text: 'Friends', callback: () => {}},
- {itemType: 'regular', text: 'Family', callback: () => {}},
- {itemType: 'regular', text: 'Everyone', callback: () => {}},
- ],
- },
- ],
- });
- },
- }),
- m(WidgetShowcase, {
label: 'Menu',
renderWidget: () =>
m(
@@ -1285,7 +1272,7 @@
offset: rowOffset,
rows,
};
- raf.scheduleFullRedraw();
+ scheduleFullRedraw();
},
};
return m(VirtualTable, attrs);
@@ -1345,7 +1332,7 @@
label: 'Track',
description: `A track`,
renderWidget: (opts) => {
- const {error, buttons, chips, multipleTracks, ...rest} = opts;
+ const {buttons, chips, multipleTracks, ...rest} = opts;
const dummyButtons = () => [
m(Button, {icon: 'info', compact: true}),
m(Button, {icon: 'settings', compact: true}),
@@ -1354,9 +1341,6 @@
const renderTrack = () =>
m(TrackWidget, {
- error: Boolean(error)
- ? new Error('Something went wrong')
- : undefined,
buttons: Boolean(buttons) ? dummyButtons() : undefined,
chips: Boolean(chips) ? dummyChips() : undefined,
...rest,
@@ -1384,6 +1368,7 @@
highlight: false,
error: false,
multipleTracks: false,
+ reorderable: false,
},
}),
);
@@ -1416,7 +1401,7 @@
},
view: function (vnode: m.Vnode<{}, {progress: number}>) {
vnode.state.progress = (vnode.state.progress + 1) % 100;
- raf.scheduleFullRedraw();
+ scheduleFullRedraw();
return m(
'div',
m('div', 'You should see an animating progress bar'),
diff --git a/ui/src/plugins/org.chromium.ChromeCriticalUserInteractions/critical_user_interaction_track.ts b/ui/src/plugins/org.chromium.ChromeCriticalUserInteractions/critical_user_interaction_track.ts
new file mode 100644
index 0000000..d377b23
--- /dev/null
+++ b/ui/src/plugins/org.chromium.ChromeCriticalUserInteractions/critical_user_interaction_track.ts
@@ -0,0 +1,129 @@
+// 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 {NAMED_ROW} from '../../frontend/named_slice_track';
+import {LONG, NUM, STR} from '../../trace_processor/query_result';
+import {Slice} from '../../public/track';
+import {
+ CustomSqlImportConfig,
+ CustomSqlTableDefConfig,
+ CustomSqlTableSliceTrack,
+} from '../../frontend/tracks/custom_sql_table_slice_track';
+import {TrackEventDetails, TrackEventSelection} from '../../public/selection';
+import {Duration, Time} from '../../base/time';
+import {PageLoadDetailsPanel} from './page_load_details_panel';
+import {StartupDetailsPanel} from './startup_details_panel';
+import {WebContentInteractionPanel} from './web_content_interaction_details_panel';
+import {GenericSliceDetailsTab} from '../../frontend/generic_slice_details_tab';
+
+export const CRITICAL_USER_INTERACTIONS_KIND =
+ 'org.chromium.CriticalUserInteraction.track';
+
+export const CRITICAL_USER_INTERACTIONS_ROW = {
+ ...NAMED_ROW,
+ scopedId: NUM,
+ type: STR,
+};
+export type CriticalUserInteractionRow = typeof CRITICAL_USER_INTERACTIONS_ROW;
+
+export interface CriticalUserInteractionSlice extends Slice {
+ scopedId: number;
+ type: string;
+}
+
+export class CriticalUserInteractionTrack extends CustomSqlTableSliceTrack {
+ static readonly kind = `/critical_user_interactions`;
+
+ getSqlDataSource(): CustomSqlTableDefConfig {
+ return {
+ columns: [
+ // The scoped_id is not a unique identifier within the table; generate
+ // a unique id from type and scoped_id on the fly to use for slice
+ // selection.
+ 'hash(type, scoped_id) AS id',
+ 'scoped_id AS scopedId',
+ 'name',
+ 'ts',
+ 'dur',
+ 'type',
+ ],
+ sqlTableName: 'chrome_interactions',
+ };
+ }
+
+ async getSelectionDetails(
+ id: number,
+ ): Promise<TrackEventDetails | undefined> {
+ const query = `
+ SELECT
+ ts,
+ dur,
+ type
+ FROM (${this.getSqlSource()})
+ WHERE id = ${id}
+ `;
+
+ const result = await this.engine.query(query);
+ if (result.numRows() === 0) {
+ return undefined;
+ }
+
+ const row = result.iter({
+ ts: LONG,
+ dur: LONG,
+ type: STR,
+ });
+
+ return {
+ ts: Time.fromRaw(row.ts),
+ dur: Duration.fromRaw(row.dur),
+ interactionType: row.type,
+ };
+ }
+
+ getSqlImports(): CustomSqlImportConfig {
+ return {
+ modules: ['chrome.interactions'],
+ };
+ }
+
+ getRowSpec(): CriticalUserInteractionRow {
+ return CRITICAL_USER_INTERACTIONS_ROW;
+ }
+
+ rowToSlice(row: CriticalUserInteractionRow): CriticalUserInteractionSlice {
+ const baseSlice = super.rowToSlice(row);
+ const scopedId = row.scopedId;
+ const type = row.type;
+ return {...baseSlice, scopedId, type};
+ }
+
+ override detailsPanel(sel: TrackEventSelection) {
+ switch (sel.interactionType) {
+ case 'chrome_page_loads':
+ return new PageLoadDetailsPanel(this.trace, sel.eventId);
+ case 'chrome_startups':
+ return new StartupDetailsPanel(this.trace, sel.eventId);
+ case 'chrome_web_content_interactions':
+ return new WebContentInteractionPanel(this.trace, sel.eventId);
+ default:
+ return new GenericSliceDetailsTab(
+ this.trace,
+ 'chrome_interactions',
+ sel.eventId,
+ 'Chrome Interaction',
+ );
+ }
+ }
+}
diff --git a/ui/src/plugins/org.chromium.ChromeCriticalUserInteractions/index.ts b/ui/src/plugins/org.chromium.ChromeCriticalUserInteractions/index.ts
new file mode 100644
index 0000000..6b96209
--- /dev/null
+++ b/ui/src/plugins/org.chromium.ChromeCriticalUserInteractions/index.ts
@@ -0,0 +1,48 @@
+// 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 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 {Trace} from '../../public/trace';
+import {PerfettoPlugin} from '../../public/plugin';
+import {CriticalUserInteractionTrack} from './critical_user_interaction_track';
+import {TrackNode} from '../../public/workspace';
+
+export default class implements PerfettoPlugin {
+ static readonly id = 'org.chromium.CriticalUserInteraction';
+ async onTraceLoad(ctx: Trace): Promise<void> {
+ ctx.commands.registerCommand({
+ id: 'perfetto.CriticalUserInteraction.AddInteractionTrack',
+ name: 'Add track: Chrome interactions',
+ callback: () => {
+ const track = new TrackNode({
+ uri: CriticalUserInteractionTrack.kind,
+ title: 'Chrome Interactions',
+ });
+ ctx.workspace.addChildInOrder(track);
+ track.pin();
+ },
+ });
+
+ ctx.tracks.registerTrack({
+ uri: CriticalUserInteractionTrack.kind,
+ tags: {
+ kind: CriticalUserInteractionTrack.kind,
+ },
+ title: 'Chrome Interactions',
+ track: new CriticalUserInteractionTrack({
+ trace: ctx,
+ uri: CriticalUserInteractionTrack.kind,
+ }),
+ });
+ }
+}
diff --git a/ui/src/core_plugins/chrome_critical_user_interactions/page_load_details_panel.ts b/ui/src/plugins/org.chromium.ChromeCriticalUserInteractions/page_load_details_panel.ts
similarity index 72%
rename from ui/src/core_plugins/chrome_critical_user_interactions/page_load_details_panel.ts
rename to ui/src/plugins/org.chromium.ChromeCriticalUserInteractions/page_load_details_panel.ts
index e01e4a8..e2d0b54 100644
--- a/ui/src/core_plugins/chrome_critical_user_interactions/page_load_details_panel.ts
+++ b/ui/src/plugins/org.chromium.ChromeCriticalUserInteractions/page_load_details_panel.ts
@@ -13,29 +13,24 @@
// limitations under the License.
import m from 'mithril';
-import {BottomTab, NewBottomTabArgs} from '../../public/lib/bottom_tab';
-import {GenericSliceDetailsTabConfig} from '../../frontend/generic_slice_details_tab';
import {
Details,
DetailsSchema,
} from '../../frontend/widgets/sql/details/details';
import {DetailsShell} from '../../widgets/details_shell';
import {GridLayout, GridLayoutColumn} from '../../widgets/grid_layout';
+import {TrackEventDetailsPanel} from '../../public/details_panel';
+import {Trace} from '../../public/trace';
import d = DetailsSchema;
-export class PageLoadDetailsPanel extends BottomTab<GenericSliceDetailsTabConfig> {
- static readonly kind = 'org.perfetto.PageLoadDetailsPanel';
+export class PageLoadDetailsPanel implements TrackEventDetailsPanel {
private data: Details;
- static create(
- args: NewBottomTabArgs<GenericSliceDetailsTabConfig>,
- ): PageLoadDetailsPanel {
- return new PageLoadDetailsPanel(args);
- }
-
- constructor(args: NewBottomTabArgs<GenericSliceDetailsTabConfig>) {
- super(args);
- this.data = new Details(this.trace, 'chrome_page_loads', this.config.id, {
+ constructor(
+ private readonly trace: Trace,
+ id: number,
+ ) {
+ this.data = new Details(this.trace, 'chrome_page_loads', id, {
'Navigation start': d.Timestamp('navigation_start_ts'),
'FCP event': d.Timestamp('fcp_ts'),
'FCP': d.Interval('navigation_start_ts', 'fcp'),
@@ -65,21 +60,13 @@
});
}
- viewTab() {
+ render() {
return m(
DetailsShell,
{
- title: this.getTitle(),
+ title: 'Chrome Page Load',
},
m(GridLayout, m(GridLayoutColumn, this.data.render())),
);
}
-
- getTitle(): string {
- return this.config.title;
- }
-
- isLoading() {
- return this.data.isLoading();
- }
}
diff --git a/ui/src/core_plugins/chrome_critical_user_interactions/startup_details_panel.ts b/ui/src/plugins/org.chromium.ChromeCriticalUserInteractions/startup_details_panel.ts
similarity index 77%
rename from ui/src/core_plugins/chrome_critical_user_interactions/startup_details_panel.ts
rename to ui/src/plugins/org.chromium.ChromeCriticalUserInteractions/startup_details_panel.ts
index ec8667f..0c26623 100644
--- a/ui/src/core_plugins/chrome_critical_user_interactions/startup_details_panel.ts
+++ b/ui/src/plugins/org.chromium.ChromeCriticalUserInteractions/startup_details_panel.ts
@@ -14,8 +14,6 @@
import m from 'mithril';
import {duration, Time, time} from '../../base/time';
-import {BottomTab, NewBottomTabArgs} from '../../public/lib/bottom_tab';
-import {GenericSliceDetailsTabConfig} from '../../frontend/generic_slice_details_tab';
import {DurationWidget} from '../../frontend/widgets/duration';
import {Timestamp} from '../../frontend/widgets/timestamp';
import {LONG, NUM, STR, STR_NULL} from '../../trace_processor/query_result';
@@ -25,6 +23,8 @@
import {SqlRef} from '../../widgets/sql_ref';
import {dictToTreeNodes, Tree} from '../../widgets/tree';
import {asUpid, Upid} from '../../trace_processor/sql_utils/core_types';
+import {Trace} from '../../public/trace';
+import {TrackEventDetailsPanel} from '../../public/details_panel';
interface Data {
startupId: number;
@@ -35,24 +35,16 @@
upid: Upid;
}
-export class StartupDetailsPanel extends BottomTab<GenericSliceDetailsTabConfig> {
- static readonly kind = 'org.perfetto.StartupDetailsPanel';
- private loaded = false;
- private data: Data | undefined;
+export class StartupDetailsPanel implements TrackEventDetailsPanel {
+ private data?: Data;
- static create(
- args: NewBottomTabArgs<GenericSliceDetailsTabConfig>,
- ): StartupDetailsPanel {
- return new StartupDetailsPanel(args);
- }
+ constructor(
+ private readonly trace: Trace,
+ private readonly id: number,
+ ) {}
- constructor(args: NewBottomTabArgs<GenericSliceDetailsTabConfig>) {
- super(args);
- this.loadData();
- }
-
- private async loadData() {
- const queryResult = await this.engine.query(`
+ async load() {
+ const queryResult = await this.trace.engine.query(`
SELECT
activity_id AS startupId,
name,
@@ -64,7 +56,7 @@
launch_cause AS launchCause,
browser_upid AS upid
FROM chrome_startups
- WHERE id = ${this.config.id};
+ WHERE id = ${this.id};
`);
const iter = queryResult.firstRow({
@@ -87,8 +79,6 @@
if (iter.launchCause) {
this.data.launchCause = iter.launchCause;
}
-
- this.loaded = true;
}
private getDetailsDictionary() {
@@ -106,20 +96,20 @@
}
details['SQL ID'] = m(SqlRef, {
table: 'chrome_startups',
- id: this.config.id,
+ id: this.id,
});
return details;
}
- viewTab() {
- if (this.isLoading()) {
+ render() {
+ if (!this.data) {
return m('h2', 'Loading');
}
return m(
DetailsShell,
{
- title: this.getTitle(),
+ title: 'Chrome Startup',
},
m(
GridLayout,
@@ -134,12 +124,4 @@
),
);
}
-
- getTitle(): string {
- return this.config.title;
- }
-
- isLoading() {
- return !this.loaded;
- }
}
diff --git a/ui/src/core_plugins/chrome_critical_user_interactions/web_content_interaction_details_panel.ts b/ui/src/plugins/org.chromium.ChromeCriticalUserInteractions/web_content_interaction_details_panel.ts
similarity index 77%
rename from ui/src/core_plugins/chrome_critical_user_interactions/web_content_interaction_details_panel.ts
rename to ui/src/plugins/org.chromium.ChromeCriticalUserInteractions/web_content_interaction_details_panel.ts
index cb6a7fb..25e49aa 100644
--- a/ui/src/core_plugins/chrome_critical_user_interactions/web_content_interaction_details_panel.ts
+++ b/ui/src/plugins/org.chromium.ChromeCriticalUserInteractions/web_content_interaction_details_panel.ts
@@ -28,8 +28,6 @@
import m from 'mithril';
import {duration, Time, time} from '../../base/time';
-import {BottomTab, NewBottomTabArgs} from '../../public/lib/bottom_tab';
-import {GenericSliceDetailsTabConfig} from '../../frontend/generic_slice_details_tab';
import {asUpid, Upid} from '../../trace_processor/sql_utils/core_types';
import {DurationWidget} from '../../frontend/widgets/duration';
import {Timestamp} from '../../frontend/widgets/timestamp';
@@ -39,6 +37,8 @@
import {Section} from '../../widgets/section';
import {SqlRef} from '../../widgets/sql_ref';
import {dictToTreeNodes, Tree} from '../../widgets/tree';
+import {TrackEventDetailsPanel} from '../../public/details_panel';
+import {Trace} from '../../public/trace';
interface Data {
ts: time;
@@ -48,24 +48,16 @@
upid: Upid;
}
-export class WebContentInteractionPanel extends BottomTab<GenericSliceDetailsTabConfig> {
- static readonly kind = 'org.perfetto.WebContentInteractionPanel';
- private loaded = false;
- private data: Data | undefined;
+export class WebContentInteractionPanel implements TrackEventDetailsPanel {
+ private data?: Data;
- static create(
- args: NewBottomTabArgs<GenericSliceDetailsTabConfig>,
- ): WebContentInteractionPanel {
- return new WebContentInteractionPanel(args);
- }
+ constructor(
+ private readonly trace: Trace,
+ private readonly id: number,
+ ) {}
- constructor(args: NewBottomTabArgs<GenericSliceDetailsTabConfig>) {
- super(args);
- this.loadData();
- }
-
- private async loadData() {
- const queryResult = await this.engine.query(`
+ async load() {
+ const queryResult = await this.trace.engine.query(`
SELECT
ts,
dur,
@@ -73,7 +65,7 @@
total_duration_ms AS totalDurationMs,
renderer_upid AS upid
FROM chrome_web_content_interactions
- WHERE id = ${this.config.id};
+ WHERE id = ${this.id};
`);
const iter = queryResult.firstRow({
@@ -91,8 +83,6 @@
totalDurationMs: iter.totalDurationMs,
upid: asUpid(iter.upid),
};
-
- this.loaded = true;
}
private getDetailsDictionary() {
@@ -107,20 +97,20 @@
});
details['SQL ID'] = m(SqlRef, {
table: 'chrome_web_content_interactions',
- id: this.config.id,
+ id: this.id,
});
return details;
}
- viewTab() {
- if (this.isLoading()) {
+ render() {
+ if (!this.data) {
return m('h2', 'Loading');
}
return m(
DetailsShell,
{
- title: this.getTitle(),
+ title: 'Chrome Web Content Interaction',
},
m(
GridLayout,
@@ -135,12 +125,4 @@
),
);
}
-
- getTitle(): string {
- return this.config.title;
- }
-
- isLoading() {
- return !this.loaded;
- }
}
diff --git a/ui/src/core_plugins/chrome_tasks/details.ts b/ui/src/plugins/org.chromium.ChromeTasks/details.ts
similarity index 65%
rename from ui/src/core_plugins/chrome_tasks/details.ts
rename to ui/src/plugins/org.chromium.ChromeTasks/details.ts
index 56acae1..ac96447 100644
--- a/ui/src/core_plugins/chrome_tasks/details.ts
+++ b/ui/src/plugins/org.chromium.ChromeTasks/details.ts
@@ -13,25 +13,21 @@
// limitations under the License.
import m from 'mithril';
-import {BottomTab, NewBottomTabArgs} from '../../public/lib/bottom_tab';
-import {GenericSliceDetailsTabConfig} from '../../frontend/generic_slice_details_tab';
import {
Details,
DetailsSchema,
} from '../../frontend/widgets/sql/details/details';
import {DetailsShell} from '../../widgets/details_shell';
import {GridLayout, GridLayoutColumn} from '../../widgets/grid_layout';
+import {TrackEventDetailsPanel} from '../../public/details_panel';
+import {Trace} from '../../public/trace';
import d = DetailsSchema;
-export class ChromeTasksDetailsTab extends BottomTab<GenericSliceDetailsTabConfig> {
- static readonly kind = 'org.chromium.ChromeTasks.TaskDetailsTab';
+export class ChromeTasksDetailsPanel implements TrackEventDetailsPanel {
+ private readonly data: Details;
- private data: Details;
-
- constructor(args: NewBottomTabArgs<GenericSliceDetailsTabConfig>) {
- super(args);
-
- this.data = new Details(this.trace, 'chrome_tasks', this.config.id, {
+ constructor(trace: Trace, eventId: number) {
+ this.data = new Details(trace, 'chrome_tasks', eventId, {
'Task name': 'name',
'Start time': d.Timestamp('ts'),
'Duration': d.Interval('ts', 'dur'),
@@ -41,21 +37,13 @@
});
}
- viewTab() {
+ render() {
return m(
DetailsShell,
{
- title: this.getTitle(),
+ title: 'Chrome Tasks',
},
m(GridLayout, m(GridLayoutColumn, this.data.render())),
);
}
-
- getTitle(): string {
- return this.config.title;
- }
-
- isLoading() {
- return this.data.isLoading();
- }
}
diff --git a/ui/src/core_plugins/chrome_tasks/index.ts b/ui/src/plugins/org.chromium.ChromeTasks/index.ts
similarity index 73%
rename from ui/src/core_plugins/chrome_tasks/index.ts
rename to ui/src/plugins/org.chromium.ChromeTasks/index.ts
index 513da9f..6c0f426 100644
--- a/ui/src/core_plugins/chrome_tasks/index.ts
+++ b/ui/src/plugins/org.chromium.ChromeTasks/index.ts
@@ -12,22 +12,17 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {uuidv4} from '../../base/uuid';
-import {GenericSliceDetailsTabConfig} from '../../frontend/generic_slice_details_tab';
-import {addSqlTableTab} from '../../frontend/sql_table_tab_interface';
import {asUtid} from '../../trace_processor/sql_utils/core_types';
-import {BottomTabToSCSAdapter} from '../../public/utils';
import {NUM, NUM_NULL, STR_NULL} from '../../trace_processor/query_result';
import {Trace} from '../../public/trace';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
-import {ChromeTasksDetailsTab} from './details';
+import {PerfettoPlugin} from '../../public/plugin';
import {chromeTasksTable} from './table';
import {ChromeTasksThreadTrack} from './track';
import {TrackNode} from '../../public/workspace';
+import {extensions} from '../../public/lib/extensions';
-class ChromeTasksPlugin implements PerfettoPlugin {
- onActivate() {}
-
+export default class implements PerfettoPlugin {
+ static readonly id = 'org.chromium.ChromeTasks';
async onTraceLoad(ctx: Trace) {
await this.createTracks(ctx);
@@ -35,7 +30,7 @@
id: 'org.chromium.ChromeTasks.ShowChromeTasksTable',
name: 'Show chrome_tasks table',
callback: () =>
- addSqlTableTab(ctx, {
+ extensions.addSqlTableTab(ctx, {
table: chromeTasksTable,
}),
});
@@ -108,29 +103,5 @@
group.addChildInOrder(track);
ctx.workspace.addChildInOrder(group);
}
-
- ctx.tabs.registerDetailsPanel(
- new BottomTabToSCSAdapter({
- tabFactory: (selection) => {
- if (
- selection.kind === 'GENERIC_SLICE' &&
- selection.detailsPanelConfig.kind === ChromeTasksDetailsTab.kind
- ) {
- const config = selection.detailsPanelConfig.config;
- return new ChromeTasksDetailsTab({
- config: config as GenericSliceDetailsTabConfig,
- trace: ctx,
- uuid: uuidv4(),
- });
- }
- return undefined;
- },
- }),
- );
}
}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'org.chromium.ChromeTasks',
- plugin: ChromeTasksPlugin,
-};
diff --git a/ui/src/core_plugins/chrome_tasks/table.ts b/ui/src/plugins/org.chromium.ChromeTasks/table.ts
similarity index 100%
rename from ui/src/core_plugins/chrome_tasks/table.ts
rename to ui/src/plugins/org.chromium.ChromeTasks/table.ts
diff --git a/ui/src/core_plugins/chrome_tasks/track.ts b/ui/src/plugins/org.chromium.ChromeTasks/track.ts
similarity index 80%
rename from ui/src/core_plugins/chrome_tasks/track.ts
rename to ui/src/plugins/org.chromium.ChromeTasks/track.ts
index e96735c..ff2f361 100644
--- a/ui/src/core_plugins/chrome_tasks/track.ts
+++ b/ui/src/plugins/org.chromium.ChromeTasks/track.ts
@@ -14,12 +14,12 @@
import {Utid} from '../../trace_processor/sql_utils/core_types';
import {
- CustomSqlDetailsPanelConfig,
CustomSqlTableDefConfig,
CustomSqlTableSliceTrack,
} from '../../frontend/tracks/custom_sql_table_slice_track';
-import {ChromeTasksDetailsTab} from './details';
import {Trace} from '../../public/trace';
+import {TrackEventSelection} from '../../public/selection';
+import {ChromeTasksDetailsPanel} from './details';
export class ChromeTasksThreadTrack extends CustomSqlTableSliceTrack {
constructor(
@@ -38,13 +38,7 @@
};
}
- getDetailsPanel(): CustomSqlDetailsPanelConfig {
- return {
- kind: ChromeTasksDetailsTab.kind,
- config: {
- sqlTableName: 'chrome_tasks',
- title: 'Chrome Tasks',
- },
- };
+ override detailsPanel(sel: TrackEventSelection) {
+ return new ChromeTasksDetailsPanel(this.trace, sel.eventId);
}
}
diff --git a/ui/src/plugins/org.kernel.LinuxKernelDevices/index.ts b/ui/src/plugins/org.kernel.LinuxKernelDevices/index.ts
deleted file mode 100644
index 06f7d0a..0000000
--- a/ui/src/plugins/org.kernel.LinuxKernelDevices/index.ts
+++ /dev/null
@@ -1,76 +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 {NUM, STR_NULL} from '../../trace_processor/query_result';
-import {Trace} from '../../public/trace';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
-import {AsyncSliceTrack} from '../../core_plugins/async_slices/async_slice_track';
-import {ASYNC_SLICE_TRACK_KIND} from '../../public/track_kinds';
-import {TrackNode} from '../../public/workspace';
-
-// This plugin renders visualizations of runtime power state transitions for
-// Linux kernel devices (devices managed by Linux drivers).
-class LinuxKernelDevices implements PerfettoPlugin {
- async onTraceLoad(ctx: Trace): Promise<void> {
- const result = await ctx.engine.query(`
- select
- t.id as trackId,
- t.name
- from linux_device_track t
- join _slice_track_summary using (id)
- order by t.name;
- `);
-
- const it = result.iter({
- name: STR_NULL,
- trackId: NUM,
- });
-
- for (; it.valid(); it.next()) {
- const trackId = it.trackId;
- const title = it.name ?? `${trackId}`;
-
- const uri = `/kernel_devices/${title}`;
- ctx.tracks.registerTrack({
- uri,
- title,
- track: new AsyncSliceTrack(
- {
- trace: ctx,
- uri,
- },
- 0,
- [trackId],
- ),
- tags: {
- kind: ASYNC_SLICE_TRACK_KIND,
- trackIds: [trackId],
- groupName: `Linux Kernel Devices`,
- },
- });
- const group = new TrackNode({
- title: 'Linux Kernel Devices',
- isSummary: true,
- });
- const track = new TrackNode({uri, title});
- group.addChildInOrder(track);
- ctx.workspace.addChildInOrder(group);
- }
- }
-}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'org.kernel.LinuxKernelDevices',
- plugin: LinuxKernelDevices,
-};
diff --git a/ui/src/plugins/org.kernel.LinuxKernelDevices/OWNERS b/ui/src/plugins/org.kernel.LinuxKernelSubsystems/OWNERS
similarity index 100%
rename from ui/src/plugins/org.kernel.LinuxKernelDevices/OWNERS
rename to ui/src/plugins/org.kernel.LinuxKernelSubsystems/OWNERS
diff --git a/ui/src/plugins/org.kernel.LinuxKernelSubsystems/index.ts b/ui/src/plugins/org.kernel.LinuxKernelSubsystems/index.ts
new file mode 100644
index 0000000..0bca424
--- /dev/null
+++ b/ui/src/plugins/org.kernel.LinuxKernelSubsystems/index.ts
@@ -0,0 +1,88 @@
+// 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 {NUM, STR_NULL} from '../../trace_processor/query_result';
+import {Trace} from '../../public/trace';
+import {PerfettoPlugin} from '../../public/plugin';
+import {AsyncSliceTrack} from '../dev.perfetto.AsyncSlices/async_slice_track';
+import {SLICE_TRACK_KIND} from '../../public/track_kinds';
+import {TrackNode} from '../../public/workspace';
+import AsyncSlicesPlugin from '../dev.perfetto.AsyncSlices';
+
+// This plugin renders visualizations of subsystems of the Linux kernel.
+export default class implements PerfettoPlugin {
+ static readonly id = 'org.kernel.LinuxKernelSubsystems';
+ static readonly dependencies = [AsyncSlicesPlugin];
+
+ async onTraceLoad(ctx: Trace): Promise<void> {
+ const kernel = new TrackNode({
+ title: 'Linux Kernel',
+ isSummary: true,
+ });
+ const rpm = await this.addRpmTracks(ctx);
+ if (rpm.hasChildren) {
+ ctx.workspace.addChildInOrder(kernel);
+ kernel.addChildInOrder(rpm);
+ }
+ }
+
+ // Add tracks to visualize the runtime power state transitions for Linux
+ // kernel devices (devices managed by Linux drivers).
+ async addRpmTracks(ctx: Trace) {
+ const result = await ctx.engine.query(`
+ select
+ t.id as trackId,
+ extract_arg(t.dimension_arg_set_id, 'linux_device_name') as deviceName
+ from track t
+ join _slice_track_summary using (id)
+ where classification = 'linux_rpm'
+ order by deviceName;
+ `);
+
+ const it = result.iter({
+ deviceName: STR_NULL,
+ trackId: NUM,
+ });
+ const rpm = new TrackNode({
+ title: 'Runtime Power Management',
+ isSummary: true,
+ });
+ for (; it.valid(); it.next()) {
+ const trackId = it.trackId;
+ const title = it.deviceName ?? `${trackId}`;
+
+ const uri = `/linux/rpm/${title}`;
+ ctx.tracks.registerTrack({
+ uri,
+ title,
+ track: new AsyncSliceTrack(
+ {
+ trace: ctx,
+ uri,
+ },
+ 0,
+ [trackId],
+ ),
+ tags: {
+ kind: SLICE_TRACK_KIND,
+ trackIds: [trackId],
+ groupName: `Linux Kernel Devices`,
+ },
+ });
+ const track = new TrackNode({uri, title});
+ rpm.addChildInOrder(track);
+ }
+ return rpm;
+ }
+}
diff --git a/ui/src/plugins/org.kernel.SuspendResumeLatency/index.ts b/ui/src/plugins/org.kernel.SuspendResumeLatency/index.ts
index e816465..96b0ddb 100644
--- a/ui/src/plugins/org.kernel.SuspendResumeLatency/index.ts
+++ b/ui/src/plugins/org.kernel.SuspendResumeLatency/index.ts
@@ -13,31 +13,47 @@
// limitations under the License.
import {NUM, STR_NULL} from '../../trace_processor/query_result';
-import {AsyncSliceTrack} from '../../core_plugins/async_slices/async_slice_track';
+import {AsyncSliceTrack} from '../dev.perfetto.AsyncSlices/async_slice_track';
import {NewTrackArgs} from '../../frontend/track';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
+import {PerfettoPlugin} from '../../public/plugin';
import {Trace} from '../../public/trace';
import {TrackNode} from '../../public/workspace';
-import {ASYNC_SLICE_TRACK_KIND} from '../../public/track_kinds';
+import {SLICE_TRACK_KIND} from '../../public/track_kinds';
import {SuspendResumeDetailsPanel} from './suspend_resume_details';
import {Slice} from '../../public/track';
import {OnSliceClickArgs} from '../../frontend/base_slice_track';
-import {globals} from '../../frontend/globals';
+import {ThreadMap} from '../dev.perfetto.Thread/threads';
+import ThreadPlugin from '../dev.perfetto.Thread';
+import AsyncSlicesPlugin from '../dev.perfetto.AsyncSlices';
// SuspendResumeSliceTrack exists so as to override the `onSliceClick` function
// in AsyncSliceTrack.
+// TODO(stevegolton): Remove this?
class SuspendResumeSliceTrack extends AsyncSliceTrack {
- constructor(args: NewTrackArgs, maxDepth: number, trackIds: number[]) {
+ constructor(
+ args: NewTrackArgs,
+ maxDepth: number,
+ trackIds: number[],
+ private readonly threads: ThreadMap,
+ ) {
super(args, maxDepth, trackIds);
}
onSliceClick(args: OnSliceClickArgs<Slice>) {
- globals.selectionManager.selectTrackEvent(this.uri, args.slice.id);
+ this.trace.selection.selectTrackEvent(this.uri, args.slice.id);
+ }
+
+ override detailsPanel() {
+ return new SuspendResumeDetailsPanel(this.trace, this.threads);
}
}
-class SuspendResumeLatency implements PerfettoPlugin {
+export default class implements PerfettoPlugin {
+ static readonly id = 'org.kernel.SuspendResumeLatency';
+ static readonly dependencies = [ThreadPlugin, AsyncSlicesPlugin];
+
async onTraceLoad(ctx: Trace): Promise<void> {
+ const threads = ctx.plugins.getPlugin(ThreadPlugin).getThreadMap();
const {engine} = ctx;
const rawGlobalAsyncTracks = await engine.query(`
with global_tracks_grouped as (
@@ -78,10 +94,14 @@
title: displayName,
tags: {
trackIds,
- kind: ASYNC_SLICE_TRACK_KIND,
+ kind: SLICE_TRACK_KIND,
},
- track: new SuspendResumeSliceTrack({uri, trace: ctx}, maxDepth, trackIds),
- detailsPanel: new SuspendResumeDetailsPanel(ctx.engine),
+ track: new SuspendResumeSliceTrack(
+ {uri, trace: ctx},
+ maxDepth,
+ trackIds,
+ threads,
+ ),
});
// Display the track in the UI.
@@ -89,8 +109,3 @@
ctx.workspace.addChildInOrder(track);
}
}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'org.kernel.SuspendResumeLatency',
- plugin: SuspendResumeLatency,
-};
diff --git a/ui/src/plugins/org.kernel.SuspendResumeLatency/suspend_resume_details.ts b/ui/src/plugins/org.kernel.SuspendResumeLatency/suspend_resume_details.ts
index 4de8404..74bc497 100644
--- a/ui/src/plugins/org.kernel.SuspendResumeLatency/suspend_resume_details.ts
+++ b/ui/src/plugins/org.kernel.SuspendResumeLatency/suspend_resume_details.ts
@@ -12,9 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {AsyncLimiter} from '../../base/async_limiter';
import {Duration, duration, Time, time} from '../../base/time';
-import {raf} from '../../core/raf_scheduler';
import {LONG, NUM, STR_NULL} from '../../trace_processor/query_result';
import m from 'mithril';
import {DetailsShell} from '../../widgets/details_shell';
@@ -24,16 +22,17 @@
import {Timestamp} from '../../frontend/widgets/timestamp';
import {DurationWidget} from '../../frontend/widgets/duration';
import {Anchor} from '../../widgets/anchor';
-import {globals} from '../../frontend/globals';
-import {scrollTo} from '../../public/scroll_helper';
import {Engine} from '../../trace_processor/engine';
-import {TrackSelectionDetailsPanel} from '../../public/details_panel';
-import {THREAD_STATE_TRACK_KIND} from '../../public/track_kinds';
+import {TrackEventDetailsPanel} from '../../public/details_panel';
+import {TrackEventSelection} from '../../public/selection';
+import {Trace} from '../../public/trace';
+import {ThreadMap} from '../dev.perfetto.Thread/threads';
interface SuspendResumeEventDetails {
ts: time;
dur: duration;
utid: number;
+ cpu: number;
event_type: string;
device_name: string;
driver_name: string;
@@ -41,35 +40,25 @@
thread_state_id: number;
}
-export class SuspendResumeDetailsPanel implements TrackSelectionDetailsPanel {
- private readonly queryLimiter = new AsyncLimiter();
- private readonly engine: Engine;
- private id?: number;
+export class SuspendResumeDetailsPanel implements TrackEventDetailsPanel {
private suspendResumeEventDetails?: SuspendResumeEventDetails;
- constructor(engine: Engine) {
- this.engine = engine;
+ constructor(
+ private readonly trace: Trace,
+ private readonly threads: ThreadMap,
+ ) {}
+
+ async load({eventId}: TrackEventSelection) {
+ this.suspendResumeEventDetails = await loadSuspendResumeEventDetails(
+ this.trace.engine,
+ eventId,
+ );
}
- render(id: number): m.Children {
- if (id !== this.id) {
- this.id = id;
- this.queryLimiter.schedule(async () => {
- this.suspendResumeEventDetails = await loadSuspendResumeEventDetails(
- this.engine,
- id,
- );
- raf.scheduleFullRedraw();
- });
- }
-
- return this.renderView();
- }
-
- private renderView() {
+ render() {
const eventDetails = this.suspendResumeEventDetails;
if (eventDetails) {
- const threadInfo = globals.threads.get(eventDetails.utid);
+ const threadInfo = this.threads.get(eventDetails.utid);
if (!threadInfo) {
return null;
}
@@ -110,16 +99,13 @@
{
icon: 'call_made',
onclick: () => {
- this.goToThread(
- eventDetails.utid,
- eventDetails.ts,
- eventDetails.thread_state_id,
- );
+ this.goToThread(eventDetails.thread_state_id);
},
},
`${threadInfo.threadName} [${threadInfo.tid}]`,
),
}),
+ m(TreeNode, {left: 'CPU', right: eventDetails.cpu}),
m(TreeNode, {left: 'Event Type', right: eventDetails.event_type}),
),
),
@@ -137,28 +123,10 @@
return this.suspendResumeEventDetails === undefined;
}
- goToThread(utid: number, ts: time, threadStateId: number) {
- const threadInfo = globals.threads.get(utid);
- if (threadInfo === undefined) {
- return;
- }
-
- const trackDescriptor = globals.trackManager.findTrack(
- (td) =>
- td.tags?.kind === THREAD_STATE_TRACK_KIND &&
- td.tags?.utid === threadInfo.utid,
- );
-
- if (trackDescriptor) {
- globals.selectionManager.selectSqlEvent(
- 'thread_state',
- threadStateId,
- );
- scrollTo({
- track: {uri: trackDescriptor.uri, expandGroup: true},
- time: {start: ts},
- });
- }
+ goToThread(threadStateId: number) {
+ this.trace.selection.selectSqlEvent('thread_state', threadStateId, {
+ scrollToSelection: true,
+ });
}
}
@@ -170,6 +138,7 @@
SELECT ts,
dur,
EXTRACT_ARG(arg_set_id, 'utid') as utid,
+ EXTRACT_ARG(arg_set_id, 'ucpu') as ucpu,
EXTRACT_ARG(arg_set_id, 'event_type') as event_type,
EXTRACT_ARG(arg_set_id, 'device_name') as device_name,
EXTRACT_ARG(arg_set_id, 'driver_name') as driver_name,
@@ -185,6 +154,7 @@
ts: LONG,
dur: LONG,
utid: NUM,
+ ucpu: NUM,
event_type: STR_NULL,
device_name: STR_NULL,
driver_name: STR_NULL,
@@ -195,6 +165,7 @@
ts: Time.fromRaw(0n),
dur: Duration.fromRaw(0n),
utid: 0,
+ cpu: 0,
event_type: 'Error',
device_name: 'Error',
driver_name: 'Error',
@@ -219,10 +190,25 @@
threadStateId = threadStateRow.threadStateId;
}
+ const cpuQuery = `
+ SELECT cpu
+ FROM cpu
+ WHERE cpu.id = ${suspendResumeEventRow.ucpu}
+ `;
+ const cpuResult = await engine.query(cpuQuery);
+ let cpu = 0;
+ if (cpuResult.numRows() > 0) {
+ const cpuRow = cpuResult.firstRow({
+ cpu: NUM,
+ });
+ cpu = cpuRow.cpu;
+ }
+
return {
ts: Time.fromRaw(suspendResumeEventRow.ts),
dur: Duration.fromRaw(suspendResumeEventRow.dur),
utid: suspendResumeEventRow.utid,
+ cpu: cpu,
event_type:
suspendResumeEventRow.event_type !== null
? suspendResumeEventRow.event_type
diff --git a/ui/src/plugins/wattson/OWNERS b/ui/src/plugins/org.kernel.Wattson/OWNERS
similarity index 100%
rename from ui/src/plugins/wattson/OWNERS
rename to ui/src/plugins/org.kernel.Wattson/OWNERS
diff --git a/ui/src/plugins/wattson/estimate_aggregator.ts b/ui/src/plugins/org.kernel.Wattson/estimate_aggregator.ts
similarity index 95%
rename from ui/src/plugins/wattson/estimate_aggregator.ts
rename to ui/src/plugins/org.kernel.Wattson/estimate_aggregator.ts
index 1ffea11..e6038b8 100644
--- a/ui/src/plugins/wattson/estimate_aggregator.ts
+++ b/ui/src/plugins/org.kernel.Wattson/estimate_aggregator.ts
@@ -50,7 +50,7 @@
): string {
const duration = area.end - area.start;
let query = `
- INCLUDE PERFETTO MODULE wattson.curves.ungrouped;
+ INCLUDE PERFETTO MODULE wattson.curves.estimates;
CREATE OR REPLACE PERFETTO TABLE _ui_selection_window AS
SELECT
@@ -93,14 +93,14 @@
columnId: 'name',
},
{
- title: 'Average power (estimated mW)',
+ title: 'Power (estimated mW)',
kind: 'NUMBER',
columnConstructor: Float64Array,
columnId: 'power',
sum: true,
},
{
- title: 'Total energy (estimated mWs)',
+ title: 'Energy (estimated mWs)',
kind: 'NUMBER',
columnConstructor: Float64Array,
columnId: 'energy',
diff --git a/ui/src/plugins/wattson/index.ts b/ui/src/plugins/org.kernel.Wattson/index.ts
similarity index 94%
rename from ui/src/plugins/wattson/index.ts
rename to ui/src/plugins/org.kernel.Wattson/index.ts
index 98bd15a..539366d 100644
--- a/ui/src/plugins/wattson/index.ts
+++ b/ui/src/plugins/org.kernel.Wattson/index.ts
@@ -17,7 +17,7 @@
CounterOptions,
} from '../../frontend/base_counter_track';
import {Trace} from '../../public/trace';
-import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
+import {PerfettoPlugin} from '../../public/plugin';
import {CPUSS_ESTIMATE_TRACK_KIND} from '../../public/track_kinds';
import {TrackNode} from '../../public/workspace';
import {WattsonEstimateSelectionAggregator} from './estimate_aggregator';
@@ -27,12 +27,14 @@
import {Engine} from '../../trace_processor/engine';
import {NUM} from '../../trace_processor/query_result';
-class Wattson implements PerfettoPlugin {
+export default class implements PerfettoPlugin {
+ static readonly id = `org.kernel.Wattson`;
+
async onTraceLoad(ctx: Trace): Promise<void> {
// Short circuit if Wattson is not supported for this Perfetto trace
if (!(await hasWattsonSupport(ctx.engine))) return;
- ctx.engine.query(`INCLUDE PERFETTO MODULE wattson.curves.ungrouped;`);
+ ctx.engine.query(`INCLUDE PERFETTO MODULE wattson.curves.estimates;`);
const group = new TrackNode({title: 'Wattson', isSummary: true});
ctx.workspace.addChildInOrder(group);
@@ -123,11 +125,6 @@
}
}
-export const plugin: PluginDescriptor = {
- pluginId: `org.kernel.Wattson`,
- plugin: Wattson,
-};
-
async function hasWattsonSupport(engine: Engine): Promise<boolean> {
// These tables are hard requirements and are the bare minimum needed for
// Wattson to run, so check that these tables are populated
diff --git a/ui/src/plugins/wattson/package_aggregator.ts b/ui/src/plugins/org.kernel.Wattson/package_aggregator.ts
similarity index 90%
rename from ui/src/plugins/wattson/package_aggregator.ts
rename to ui/src/plugins/org.kernel.Wattson/package_aggregator.ts
index 5551284..7b4d1b0 100644
--- a/ui/src/plugins/wattson/package_aggregator.ts
+++ b/ui/src/plugins/org.kernel.Wattson/package_aggregator.ts
@@ -51,8 +51,8 @@
-- Grouped by UID and made CPU agnostic
CREATE VIEW ${this.id} AS
SELECT
- ROUND(SUM(total_pws) / ${duration}, 2) as avg_mw,
- ROUND(SUM(total_pws) / 1000000000, 2) as total_mws,
+ ROUND(SUM(total_pws) / ${duration}, 2) as active_mw,
+ ROUND(SUM(total_pws) / 1000000000, 2) as active_mws,
ROUND(SUM(dur) / 1000000.0, 2) as dur_ms,
uid,
package_name
@@ -84,17 +84,17 @@
columnId: 'dur_ms',
},
{
- title: 'Average power (estimated mW)',
+ title: 'Active power (estimated mW)',
kind: 'NUMBER',
columnConstructor: Float64Array,
- columnId: 'avg_mw',
+ columnId: 'active_mw',
sum: true,
},
{
- title: 'Total energy (estimated mWs)',
+ title: 'Active energy (estimated mWs)',
kind: 'NUMBER',
columnConstructor: Float64Array,
- columnId: 'total_mws',
+ columnId: 'active_mws',
sum: true,
},
];
@@ -107,6 +107,6 @@
}
getDefaultSorting(): Sorting {
- return {column: 'total_mws', direction: 'DESC'};
+ return {column: 'active_mws', direction: 'DESC'};
}
}
diff --git a/ui/src/plugins/wattson/process_aggregator.ts b/ui/src/plugins/org.kernel.Wattson/process_aggregator.ts
similarity index 87%
rename from ui/src/plugins/wattson/process_aggregator.ts
rename to ui/src/plugins/org.kernel.Wattson/process_aggregator.ts
index be21328..f2325cb 100644
--- a/ui/src/plugins/wattson/process_aggregator.ts
+++ b/ui/src/plugins/org.kernel.Wattson/process_aggregator.ts
@@ -14,10 +14,9 @@
import {exists} from '../../base/utils';
import {ColumnDef, Sorting} from '../../public/aggregation';
-import {AreaSelection} from '../../public/selection';
-import {Engine} from '../../trace_processor/engine';
+import {AreaSelection, AreaSelectionAggregator} from '../../public/selection';
import {CPU_SLICE_TRACK_KIND} from '../../public/track_kinds';
-import {AreaSelectionAggregator} from '../../public/selection';
+import {Engine} from '../../trace_processor/engine';
export class WattsonProcessSelectionAggregator
implements AreaSelectionAggregator
@@ -54,8 +53,8 @@
-- Grouped by UPID and made CPU agnostic
CREATE VIEW ${this.id} AS
SELECT
- ROUND(SUM(total_pws) / ${duration}, 2) as avg_mw,
- ROUND(SUM(total_pws) / 1000000000, 2) as total_mws,
+ ROUND(SUM(total_pws) / ${duration}, 2) as active_mw,
+ ROUND(SUM(total_pws) / 1000000000, 2) as active_mws,
COALESCE(idle_cost_mws, 0) as idle_cost_mws,
pid,
process_name
@@ -82,17 +81,17 @@
columnId: 'pid',
},
{
- title: 'Average power (estimated mW)',
+ title: 'Active power (estimated mW)',
kind: 'NUMBER',
columnConstructor: Float64Array,
- columnId: 'avg_mw',
+ columnId: 'active_mw',
sum: true,
},
{
- title: 'Total energy (estimated mWs)',
+ title: 'Active energy (estimated mWs)',
kind: 'NUMBER',
columnConstructor: Float64Array,
- columnId: 'total_mws',
+ columnId: 'active_mws',
sum: true,
},
{
@@ -112,6 +111,6 @@
}
getDefaultSorting(): Sorting {
- return {column: 'total_mws', direction: 'DESC'};
+ return {column: 'active_mws', direction: 'DESC'};
}
}
diff --git a/ui/src/plugins/wattson/thread_aggregator.ts b/ui/src/plugins/org.kernel.Wattson/thread_aggregator.ts
similarity index 93%
rename from ui/src/plugins/wattson/thread_aggregator.ts
rename to ui/src/plugins/org.kernel.Wattson/thread_aggregator.ts
index 484b92d..2ca428d 100644
--- a/ui/src/plugins/wattson/thread_aggregator.ts
+++ b/ui/src/plugins/org.kernel.Wattson/thread_aggregator.ts
@@ -40,7 +40,7 @@
engine.query(`
INCLUDE PERFETTO MODULE viz.summary.threads_w_processes;
INCLUDE PERFETTO MODULE wattson.curves.idle_attribution;
- INCLUDE PERFETTO MODULE wattson.curves.ungrouped;
+ INCLUDE PERFETTO MODULE wattson.curves.estimates;
CREATE OR REPLACE PERFETTO TABLE _ui_selection_window AS
SELECT
@@ -129,8 +129,8 @@
-- Grouped again by UTID, but this time to make it CPU agnostic
CREATE VIEW ${this.id} AS
SELECT
- ROUND(SUM(total_pws) / ${duration}, 2) as avg_mw,
- ROUND(SUM(total_pws) / 1000000000, 2) as total_mws,
+ ROUND(SUM(total_pws) / ${duration}, 2) as active_mw,
+ ROUND(SUM(total_pws) / 1000000000, 2) as active_mws,
COALESCE(idle_cost_mws, 0) as idle_cost_mws,
thread_name,
utid,
@@ -167,17 +167,17 @@
columnId: 'pid',
},
{
- title: 'Average power (estimated mW)',
+ title: 'Active power (estimated mW)',
kind: 'NUMBER',
columnConstructor: Float64Array,
- columnId: 'avg_mw',
+ columnId: 'active_mw',
sum: true,
},
{
- title: 'Total energy (estimated mWs)',
+ title: 'Active energy (estimated mWs)',
kind: 'NUMBER',
columnConstructor: Float64Array,
- columnId: 'total_mws',
+ columnId: 'active_mws',
sum: true,
},
{
@@ -197,6 +197,6 @@
}
getDefaultSorting(): Sorting {
- return {column: 'total_mws', direction: 'DESC'};
+ return {column: 'active_mws', direction: 'DESC'};
}
}
diff --git a/ui/src/protos/index.ts b/ui/src/protos/index.ts
index 32f020b..7048b49 100644
--- a/ui/src/protos/index.ts
+++ b/ui/src/protos/index.ts
@@ -19,6 +19,7 @@
import AndroidLogConfig = protos.perfetto.protos.AndroidLogConfig;
import AndroidLogId = protos.perfetto.protos.AndroidLogId;
import AndroidPowerConfig = protos.perfetto.protos.AndroidPowerConfig;
+import AtomId = protos.perfetto.protos.AtomId;
import BatteryCounters = protos.perfetto.protos.AndroidPowerConfig.BatteryCounters;
import BufferConfig = protos.perfetto.protos.TraceConfig.BufferConfig;
import ChromeConfig = protos.perfetto.protos.ChromeConfig;
@@ -71,10 +72,12 @@
import QueryServiceStateResponse = protos.perfetto.protos.QueryServiceStateResponse;
import ReadBuffersRequest = protos.perfetto.protos.ReadBuffersRequest;
import ReadBuffersResponse = protos.perfetto.protos.ReadBuffersResponse;
-import RegisterSqlModuleArgs = protos.perfetto.protos.RegisterSqlModuleArgs;
-import RegisterSqlModuleResult = protos.perfetto.protos.RegisterSqlModuleResult;
+import RegisterSqlPackageArgs = protos.perfetto.protos.RegisterSqlPackageArgs;
+import RegisterSqlPackageResult = protos.perfetto.protos.RegisterSqlPackageResult;
import ResetTraceProcessorArgs = protos.perfetto.protos.ResetTraceProcessorArgs;
import StatCounters = protos.perfetto.protos.SysStatsConfig.StatCounters;
+import StatsdPullAtomConfig = protos.perfetto.protos.StatsdPullAtomConfig;;
+import StatsdTracingConfig = protos.perfetto.protos.StatsdTracingConfig;
import StatusResult = protos.perfetto.protos.StatusResult;
import SysStatsConfig = protos.perfetto.protos.SysStatsConfig;
import TraceConfig = protos.perfetto.protos.TraceConfig;
@@ -88,6 +91,7 @@
AndroidLogConfig,
AndroidLogId,
AndroidPowerConfig,
+ AtomId,
BatteryCounters,
BufferConfig,
ChromeConfig,
@@ -140,10 +144,12 @@
QueryServiceStateResponse,
ReadBuffersRequest,
ReadBuffersResponse,
- RegisterSqlModuleArgs,
- RegisterSqlModuleResult,
+ RegisterSqlPackageArgs,
+ RegisterSqlPackageResult,
ResetTraceProcessorArgs,
StatCounters,
+ StatsdPullAtomConfig,
+ StatsdTracingConfig,
StatusResult,
SysStatsConfig,
TraceConfig,
diff --git a/ui/src/frontend/recording/recording_sections.ts b/ui/src/public/analytics.ts
similarity index 62%
copy from ui/src/frontend/recording/recording_sections.ts
copy to ui/src/public/analytics.ts
index f0e3fa1..653b1ea 100644
--- a/ui/src/frontend/recording/recording_sections.ts
+++ b/ui/src/public/analytics.ts
@@ -1,4 +1,4 @@
-// Copyright (C) 2022 The Android Open Source Project
+// 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.
@@ -12,11 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {DataSource} from '../../common/recordingV2/recording_interfaces_v2';
+import {ErrorDetails} from '../base/logging';
-export interface RecordingSectionAttrs {
- dataSources: DataSource[];
- cssClass: string;
+export type TraceCategories = 'Trace Actions' | 'Record Trace' | 'User Actions';
+
+export interface Analytics {
+ logEvent(category: TraceCategories | null, event: string): void;
+ logError(err: ErrorDetails): void;
+ isEnabled(): boolean;
}
-
-export const POLL_INTERVAL_MS = [250, 500, 1000, 2500, 5000, 30000, 60000];
diff --git a/ui/src/public/app.ts b/ui/src/public/app.ts
index 87ec3be..50def57 100644
--- a/ui/src/public/app.ts
+++ b/ui/src/public/app.ts
@@ -12,9 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+import {RouteArgs} from './route_schema';
import {CommandManager} from './command';
import {OmniboxManager} from './omnibox';
import {SidebarManager} from './sidebar';
+import {Analytics} from './analytics';
+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
@@ -29,8 +35,37 @@
readonly commands: CommandManager;
readonly sidebar: SidebarManager;
readonly omnibox: OmniboxManager;
+ readonly analytics: Analytics;
+ readonly plugins: PluginManager;
+ readonly pages: PageManager;
+ readonly featureFlags: FeatureFlagManager;
+
+ /**
+ * The parsed querystring passed when starting the app, before any navigation
+ * happens.
+ */
+ readonly initialRouteArgs: RouteArgs;
+
+ /**
+ * Returns the current trace object, if any. The instance being returned is
+ * bound to the same plugin of App.pluginId.
+ */
+ readonly trace?: Trace;
// TODO(primiano): this should be needed in extremely rare cases. We should
// probably switch to mithril auto-redraw at some point.
- scheduleRedraw(): void;
+ scheduleFullRedraw(): void;
+
+ /**
+ * 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/debug_tracks.ts b/ui/src/public/debug_tracks.ts
index c8e6ac5..15577e3 100644
--- a/ui/src/public/debug_tracks.ts
+++ b/ui/src/public/debug_tracks.ts
@@ -14,4 +14,4 @@
// TODO(primiano): in near future the code to create debug tracks from an App
// context will be moved here. For now i'm just re-exporting the function as-is.
-export {addDebugSliceTrack} from './lib/debug_tracks/debug_tracks';
+export {addDebugSliceTrack} from './lib/tracks/debug_tracks';
diff --git a/ui/src/public/details_panel.ts b/ui/src/public/details_panel.ts
index 160c99d..3046275 100644
--- a/ui/src/public/details_panel.ts
+++ b/ui/src/public/details_panel.ts
@@ -13,35 +13,53 @@
// limitations under the License.
import m from 'mithril';
-import {Selection} from './selection';
+import {Selection, TrackEventSelection} from './selection';
+import {z} from 'zod';
export interface DetailsPanel {
render(selection: Selection): m.Children;
isLoading?(): boolean;
}
-export interface TrackSelectionDetailsPanel {
- render(id: number): m.Children;
- isLoading?(): boolean;
+export interface TrackEventDetailsPanelSerializeArgs<T> {
+ // The Zod schema which will be used the parse the state in a serialized
+ // permalink JSON object.
+ readonly schema: z.ZodType<T>;
+
+ // The serializable state of the details panel. The usage of this field is
+ // as follows
+ // 1) default initialize this field in the constructor.
+ // 2) if the trace is being restored from a permalink, the UI will use
+ // `schema` to parse the serialized state and will write the result into
+ // `state`. If parsing failed or the trace is not being restored,
+ // `state` will not be touched.
+ // 3) if a permalink is requested, the UI will read the value of `state`
+ // and stash it in the permalink serialzed state.
+ //
+ // This flow has the following consequences:
+ // 1) Details panels *must* respect changes to this object between their
+ // constructor and the first call to `load()`. This is the point where
+ // the core will "inject" the permalink deserialized object
+ // if available.
+ // 2) The `state` object *must* be serializable: that is, it should be a
+ // pure Javascript object.
+ state: T;
}
-// TODO(primiano): rationalize this GenericSliceDetailsTabConfig. it should be
-// probably moved to a public/lib/ next.
-export interface ColumnConfig {
- readonly displayName?: string;
+export interface TrackEventDetailsPanel {
+ // Optional: Do any loading required to render the details panel in here and
+ // the core will:
+ // - Ensure that no more than one concurrent loads are enqueued at any given
+ // time in order to keep the UI snappy.
+ // - Hold off switching to this tab for up to around 50ms while this loading
+ // is going, to avoid flickering when loading is fast.
+ load?(id: TrackEventSelection): Promise<void>;
+
+ // Called every render cycle to render the details panel. Note: This function
+ // is called regardless of whether |load| has completed yet.
+ render(): m.Children;
+
+ // Optional interface to implement by details panels which want to support
+ // saving/restoring state from a permalink.
+ readonly serialization?: TrackEventDetailsPanelSerializeArgs<unknown>;
}
-
-export type Columns = {
- readonly [columnName: string]: ColumnConfig;
-};
-
-export interface GenericSliceDetailsTabConfigBase {
- readonly sqlTableName: string;
- readonly title: string;
- // All columns are rendered if |columns| is undefined.
- readonly columns?: Columns;
-}
-
-export type GenericSliceDetailsTabConfig = GenericSliceDetailsTabConfigBase & {
- readonly id: number;
-};
diff --git a/ui/src/common/state_unittest.ts b/ui/src/public/extra_sql_packages.ts
similarity index 64%
copy from ui/src/common/state_unittest.ts
copy to ui/src/public/extra_sql_packages.ts
index af8f5f9..17feab7 100644
--- a/ui/src/common/state_unittest.ts
+++ b/ui/src/public/extra_sql_packages.ts
@@ -1,4 +1,4 @@
-// Copyright (C) 2018 The Android Open Source Project
+// 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.
@@ -12,10 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {createEmptyState} from './empty_state';
-import {State} from './state';
+// Interfaces for extra SQL packages injected via google-internal deployments.
-test('createEmptyState', () => {
- const state: State = createEmptyState();
- expect(state.engine).toEqual(undefined);
-});
+export interface SqlModule {
+ readonly name: string;
+ readonly sql: string;
+}
+
+export interface SqlPackage {
+ readonly name: string;
+ readonly modules: SqlModule[];
+}
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/lib/bottom_tab.ts b/ui/src/public/lib/bottom_tab.ts
deleted file mode 100644
index 09e0ec7..0000000
--- a/ui/src/public/lib/bottom_tab.ts
+++ /dev/null
@@ -1,112 +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 m from 'mithril';
-import {Trace} from '../trace';
-
-export interface NewBottomTabArgs<Config> {
- trace: Trace;
- tag?: string;
- uuid: string;
- config: Config;
-}
-
-// An interface representing a bottom tab displayed on the panel in the bottom
-// of the ui (e.g. "Current Selection").
-//
-// The implementations of this class are provided by different plugins, which
-// register the implementations with bottomTabRegistry, keyed by a unique name
-// for each type of BottomTab.
-//
-// Lifetime: the instances of this class are owned by BottomTabPanel and exist
-// for as long as a tab header is shown to the user in the bottom tab list (with
-// minor exceptions, like a small grace period between when the tab is related).
-//
-// BottomTab implementations should pass the unique identifier(s) for the
-// content displayed via the |Config| and fetch additional details via Engine
-// instead of relying on getting the data from the global storage. For example,
-// for tabs corresponding to details of the selected objects on a track, a new
-// BottomTab should be created for each new selection.
-export abstract class BottomTabBase<Config = {}> {
- // Config for this details panel. Should be serializable.
- protected readonly config: Config;
- // The Trace interface to manipulate the state of the UI.
- readonly trace: Trace;
- // Optional tag, which is used to ensure that only one tab
- // with the same tag can exist - adding a new tab with the same tag
- // (e.g. 'current_selection') would close the previous one. This
- // also can be used to close existing tab.
- readonly tag?: string;
- // Unique id for this details panel. Can be used to close previously opened
- // panel.
- readonly uuid: string;
-
- constructor(args: NewBottomTabArgs<Config>) {
- this.config = args.config;
- this.trace = args.trace;
- this.tag = args.tag;
- this.uuid = args.uuid;
- }
-
- // Entry point for customisation of the displayed title for this panel.
- abstract getTitle(): string;
-
- // Generate a mithril node for this component.
- abstract renderPanel(): m.Children;
-
- // API for the tab to notify the TabList that it's still preparing the data.
- // If true, adding a new tab will be delayed for a short while (~50ms) to
- // reduce the flickering.
- //
- // Note: it's a "poll" rather than "push" API: there is no explicit API
- // for the tabs to notify the tab list, as the tabs are expected to schedule
- // global redraw anyway and the tab list will poll the tabs as necessary
- // during the redraw.
- isLoading(): boolean {
- return false;
- }
-
- protected get engine() {
- return this.trace.engine;
- }
-}
-
-// BottomTabBase provides a more generic API allowing users to provide their
-// custom mithril component, which would allow them to listen to mithril
-// lifecycle events. Most cases, however, don't need them and BottomTab
-// provides a simplified API for the common case.
-export abstract class BottomTab<Config = {}> extends BottomTabBase<Config> {
- constructor(args: NewBottomTabArgs<Config>) {
- super(args);
- }
-
- abstract viewTab(): m.Children;
-
- renderPanel(): m.Children {
- return m(BottomTabAdapter, {
- key: this.uuid,
- panel: this,
- } as BottomTabAdapterAttrs);
- }
-}
-
-interface BottomTabAdapterAttrs {
- panel: BottomTab;
-}
-
-class BottomTabAdapter implements m.ClassComponent<BottomTabAdapterAttrs> {
- view(vnode: m.CVnode<BottomTabAdapterAttrs>): void | m.Children {
- return vnode.attrs.panel.viewTab();
- }
-}
diff --git a/ui/src/core/colorizer.ts b/ui/src/public/lib/colorizer.ts
similarity index 96%
rename from ui/src/core/colorizer.ts
rename to ui/src/public/lib/colorizer.ts
index 8762536..52ef512 100644
--- a/ui/src/core/colorizer.ts
+++ b/ui/src/public/lib/colorizer.ts
@@ -13,11 +13,11 @@
// limitations under the License.
import {hsl} from 'color-convert';
-import {hash} from './hash';
-import {featureFlags} from './feature_flags';
-import {Color, HSLColor, HSLuvColor} from '../public/color';
-import {ColorScheme} from '../public/color_scheme';
-import {RandState, pseudoRand} from '../base/rand';
+import {hash} from '../../base/hash';
+import {featureFlags} from '../../core/feature_flags';
+import {Color, HSLColor, HSLuvColor} from '../color';
+import {ColorScheme} from '../color_scheme';
+import {RandState, pseudoRand} from '../../base/rand';
// 128 would provide equal weighting between dark and light text.
// However, we want to prefer light text for stylistic reasons.
diff --git a/ui/src/core/colorizer_unittest.ts b/ui/src/public/lib/colorizer_unittest.ts
similarity index 100%
rename from ui/src/core/colorizer_unittest.ts
rename to ui/src/public/lib/colorizer_unittest.ts
diff --git a/ui/src/public/lib/debug_tracks/counter_track.ts b/ui/src/public/lib/debug_tracks/counter_track.ts
deleted file mode 100644
index d9c2f7c..0000000
--- a/ui/src/public/lib/debug_tracks/counter_track.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-// 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 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 {BaseCounterTrack} from '../../../frontend/base_counter_track';
-import {TrackContext} from '../../track';
-import {Button} from '../../../widgets/button';
-import {Icons} from '../../../base/semantic_icons';
-import {Trace} from '../../trace';
-
-export class DebugCounterTrack extends BaseCounterTrack {
- private readonly sqlTableName: string;
-
- constructor(trace: Trace, ctx: TrackContext, tableName: string) {
- super({
- trace,
- uri: ctx.trackUri,
- });
- this.sqlTableName = tableName;
- }
-
- getSqlSource(): string {
- return `select * from ${this.sqlTableName}`;
- }
-
- getTrackShellButtons(): m.Children {
- return m(Button, {
- onclick: () => {
- this.trace.workspace.findTrackByUri(this.uri)?.remove();
- },
- icon: Icons.Close,
- title: 'Close',
- compact: true,
- });
- }
-}
diff --git a/ui/src/public/lib/debug_tracks/debug_tracks.ts b/ui/src/public/lib/debug_tracks/debug_tracks.ts
deleted file mode 100644
index f85290f..0000000
--- a/ui/src/public/lib/debug_tracks/debug_tracks.ts
+++ /dev/null
@@ -1,229 +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 {DebugSliceTrack} from './slice_track';
-import {
- createPerfettoTable,
- matchesSqlValue,
- sqlValueToReadableString,
-} from '../../../trace_processor/sql_utils';
-import {DebugCounterTrack} from './counter_track';
-import {ARG_PREFIX} from './details_tab';
-import {TrackNode} from '../../workspace';
-import {Trace} from '../../trace';
-
-let trackCounter = 0; // For reproducible ids.
-
-// Names of the columns of the underlying view to be used as
-// ts / dur / name / pivot.
-export interface SliceColumns {
- ts: string;
- dur: string;
- name: string;
-}
-
-let debugTrackCount = 0;
-
-export interface SqlDataSource {
- // SQL source selecting the necessary data.
- sqlSource: string;
-
- // Optional: Rename columns from the query result.
- // If omitted, original column names from the query are used instead.
- // The caller is responsible for ensuring that the number of items in this
- // list matches the number of columns returned by sqlSource.
- columns?: string[];
-}
-
-// Creates actions to add a debug track. The actions must be dispatched to
-// have an effect. Use this variant if you want to create many tracks at
-// once or want to tweak the actions once produced. Otherwise, use
-// addDebugSliceTrack().
-function addDebugTrack(trace: Trace, trackName: string, uri: string): void {
- const debugTrackId = ++debugTrackCount;
- const title = trackName.trim() || `Debug Track ${debugTrackId}`;
- const track = new TrackNode({uri, title});
- trace.workspace.addChildFirst(track);
- track.pin();
-}
-
-export async function addPivotedTracks(
- trace: Trace,
- data: SqlDataSource,
- trackName: string,
- pivotColumn: string,
- createTrack: (
- trace: Trace,
- data: SqlDataSource,
- trackName: string,
- ) => Promise<void>,
-) {
- const iter = (
- await trace.engine.query(`
- with all_vals as (${data.sqlSource})
- select DISTINCT ${pivotColumn} from all_vals
- order by ${pivotColumn}
- `)
- ).iter({});
-
- for (; iter.valid(); iter.next()) {
- await createTrack(
- trace,
- {
- sqlSource: `select * from
- (${data.sqlSource})
- where ${pivotColumn} ${matchesSqlValue(iter.get(pivotColumn))}`,
- },
- `${trackName.trim() || 'Pivot Track'}: ${sqlValueToReadableString(iter.get(pivotColumn))}`,
- );
- }
-}
-
-// Adds a debug track immediately. Use createDebugSliceTrackActions() if you
-// want to create many tracks at once.
-export async function addDebugSliceTrack(
- trace: Trace,
- data: SqlDataSource,
- trackName: string,
- sliceColumns: SliceColumns,
- argColumns: string[],
-): Promise<void> {
- const cnt = trackCounter++;
- // Create a new table from the debug track definition. This will be used as
- // the backing data source for our track and its details panel.
- const tableName = `__debug_slice_${cnt}`;
-
- // TODO(stevegolton): Right now we ignore the AsyncDisposable that this
- // function returns, and so never clean up this table. The problem is we have
- // no where sensible to do this cleanup.
- // - If we did it in the track's onDestroy function, we could drop the table
- // while the details panel still needs access to it.
- // - If we did it in the plugin's onTraceUnload function, we could risk
- // dropping it n the middle of a track update cycle as track lifecycles are
- // not synchronized with plugin lifecycles.
- await createPerfettoTable(
- trace.engine,
- tableName,
- createDebugSliceTrackTableExpr(data, sliceColumns, argColumns),
- );
-
- const uri = `debug.slice.${cnt}`;
- trace.tracks.registerTrack({
- uri,
- title: trackName,
- track: new DebugSliceTrack(trace, {trackUri: uri}, tableName),
- });
-
- // Create the actions to add this track to the tracklist
- addDebugTrack(trace, trackName, uri);
-}
-
-function createDebugSliceTrackTableExpr(
- data: SqlDataSource,
- sliceColumns: SliceColumns,
- argColumns: string[],
-): string {
- const dataColumns =
- data.columns !== undefined ? `(${data.columns.join(', ')})` : '';
- const dur = sliceColumns.dur === '0' ? 0 : sliceColumns.dur;
- return `
- with data${dataColumns} as (
- ${data.sqlSource}
- ),
- prepared_data as (
- select
- ${sliceColumns.ts} as ts,
- ifnull(cast(${dur} as int), -1) as dur,
- printf('%s', ${sliceColumns.name}) as name
- ${argColumns.length > 0 ? ',' : ''}
- ${argColumns.map((c) => `${c} as ${ARG_PREFIX}${c}`).join(',\n')}
- from data
- )
- select
- row_number() over (order by ts) as id,
- *
- from prepared_data
- order by ts
- `;
-}
-
-// Names of the columns of the underlying view to be used as ts / dur / name.
-export interface CounterColumns {
- ts: string;
- value: string;
-}
-
-export interface CounterDebugTrackConfig {
- data: SqlDataSource;
- columns: CounterColumns;
-}
-
-export interface CounterDebugTrackCreateConfig {
- pinned?: boolean; // default true
- closeable?: boolean; // default true
-}
-
-// Adds a debug track immediately. Use createDebugCounterTrackActions() if you
-// want to create many tracks at once.
-export async function addDebugCounterTrack(
- trace: Trace,
- data: SqlDataSource,
- trackName: string,
- columns: CounterColumns,
-): Promise<void> {
- const cnt = trackCounter++;
- // Create a new table from the debug track definition. This will be used as
- // the backing data source for our track and its details panel.
- const tableName = `__debug_counter_${cnt}`;
-
- // TODO(stevegolton): Right now we ignore the AsyncDisposable that this
- // function returns, and so never clean up this table. The problem is we have
- // no where sensible to do this cleanup.
- // - If we did it in the track's onDestroy function, we could drop the table
- // while the details panel still needs access to it.
- // - If we did it in the plugin's onTraceUnload function, we could risk
- // dropping it n the middle of a track update cycle as track lifecycles are
- // not synchronized with plugin lifecycles.
- await createPerfettoTable(
- trace.engine,
- tableName,
- createDebugCounterTrackTableExpr(data, columns),
- );
-
- const uri = `debug.counter.${cnt}`;
- trace.tracks.registerTrack({
- uri,
- title: trackName,
- track: new DebugCounterTrack(trace, {trackUri: uri}, tableName),
- });
-
- // Create the actions to add this track to the tracklist
- addDebugTrack(trace, trackName, uri);
-}
-
-function createDebugCounterTrackTableExpr(
- data: SqlDataSource,
- columns: CounterColumns,
-): string {
- return `
- with data as (
- ${data.sqlSource}
- )
- select
- ${columns.ts} as ts,
- ${columns.value} as value
- from data
- order by ts
- `;
-}
diff --git a/ui/src/public/lib/debug_tracks/slice_track.ts b/ui/src/public/lib/debug_tracks/slice_track.ts
deleted file mode 100644
index 4cfc74f..0000000
--- a/ui/src/public/lib/debug_tracks/slice_track.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-// 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 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 {
- CustomSqlDetailsPanelConfig,
- CustomSqlTableDefConfig,
- CustomSqlTableSliceTrack,
-} from '../../../frontend/tracks/custom_sql_table_slice_track';
-import {TrackContext} from '../../track';
-import {DebugSliceDetailsTab} from './details_tab';
-import {Button} from '../../../widgets/button';
-import {Icons} from '../../../base/semantic_icons';
-import {Trace} from '../../trace';
-
-export class DebugSliceTrack extends CustomSqlTableSliceTrack {
- private readonly sqlTableName: string;
-
- constructor(trace: Trace, ctx: TrackContext, tableName: string) {
- super({
- trace,
- uri: ctx.trackUri,
- });
- this.sqlTableName = tableName;
- }
-
- async getSqlDataSource(): Promise<CustomSqlTableDefConfig> {
- return {
- sqlTableName: this.sqlTableName,
- };
- }
-
- getDetailsPanel(): CustomSqlDetailsPanelConfig {
- return {
- kind: DebugSliceDetailsTab.kind,
- config: {
- sqlTableName: this.sqlTableName,
- title: 'Debug Slice',
- },
- };
- }
-
- getTrackShellButtons(): m.Children {
- return m(Button, {
- onclick: () => {
- this.trace.workspace.findTrackByUri(this.uri)?.remove();
- },
- icon: Icons.Close,
- title: 'Close',
- compact: true,
- });
- }
-}
diff --git a/ui/src/public/lib/extensions.ts b/ui/src/public/lib/extensions.ts
new file mode 100644
index 0000000..00b9555
--- /dev/null
+++ b/ui/src/public/lib/extensions.ts
@@ -0,0 +1,43 @@
+// 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 {type addDebugSliceTrack} from '../debug_tracks';
+import {type addDebugCounterTrack} from './tracks/debug_tracks';
+import {type addSqlTableTab} from '../../frontend/sql_table_tab';
+import {type addVisualizedArgTracks} from '../../frontend/visualized_args_tracks';
+import {type addQueryResultsTab} from './query_table/query_result_tab';
+
+// TODO(primiano & stevegolton): This injection is to break the circular
+// dependency cycle that there is between various tabs and tracks.
+//
+// For example: DebugSliceTrack has a DebugSliceDetailsTab which shows details
+// about slices, which have a context menu, which allows to create a debug track
+// from it. We will break this cycle "more properly" by either:
+// 1. having a registry for context menu items for slices
+// 2. allowing plugins to expose API for the use of other plugins, and putting
+// these extension points there instead
+
+export interface ExtensionApi {
+ addDebugSliceTrack: typeof addDebugSliceTrack;
+ addDebugCounterTrack: typeof addDebugCounterTrack;
+ addSqlTableTab: typeof addSqlTableTab;
+ addVisualizedArgTracks: typeof addVisualizedArgTracks;
+ addQueryResultsTab: typeof addQueryResultsTab;
+}
+
+export let extensions: ExtensionApi;
+
+export function configureExtensions(e: ExtensionApi) {
+ extensions = e;
+}
diff --git a/ui/src/core/query_flamegraph.ts b/ui/src/public/lib/query_flamegraph.ts
similarity index 78%
rename from ui/src/core/query_flamegraph.ts
rename to ui/src/public/lib/query_flamegraph.ts
index 2108190..ae9474b 100644
--- a/ui/src/core/query_flamegraph.ts
+++ b/ui/src/public/lib/query_flamegraph.ts
@@ -13,23 +13,30 @@
// limitations under the License.
import m from 'mithril';
-import {AsyncLimiter} from '../base/async_limiter';
-import {AsyncDisposableStack} from '../base/disposable_stack';
-import {assertExists} from '../base/logging';
-import {Monitor} from '../base/monitor';
-import {uuidv4Sql} from '../base/uuid';
-import {Engine} from '../trace_processor/engine';
+import {AsyncLimiter} from '../../base/async_limiter';
+import {AsyncDisposableStack} from '../../base/disposable_stack';
+import {assertExists} from '../../base/logging';
+import {Monitor} from '../../base/monitor';
+import {uuidv4Sql} from '../../base/uuid';
+import {Engine} from '../../trace_processor/engine';
import {
createPerfettoIndex,
createPerfettoTable,
-} from '../trace_processor/sql_utils';
-import {NUM, NUM_NULL, STR, STR_NULL} from '../trace_processor/query_result';
+} from '../../trace_processor/sql_utils';
+import {
+ NUM,
+ NUM_NULL,
+ STR,
+ STR_NULL,
+ UNKNOWN,
+} from '../../trace_processor/query_result';
import {
Flamegraph,
- FlamegraphFilters,
FlamegraphQueryData,
+ FlamegraphState,
FlamegraphView,
-} from '../widgets/flamegraph';
+} from '../../widgets/flamegraph';
+import {Trace} from '../trace';
export interface QueryFlamegraphColumn {
// The name of the column in SQL.
@@ -39,6 +46,13 @@
readonly displayName: string;
}
+export interface AggQueryFlamegraphColumn extends QueryFlamegraphColumn {
+ // The aggregation to be run when nodes are merged together in the flamegraph.
+ //
+ // TODO(lalitm): consider adding extra functions here (e.g. a top 5 or similar).
+ readonly mergeAggregation: 'ONE_OR_NULL' | 'SUM';
+}
+
export interface QueryFlamegraphMetric {
// The human readable name of the metric: will be shown to the user to change
// between metrics.
@@ -71,10 +85,11 @@
// will not be shown.
//
// Examples include the source file and line number.
- //
- // TODO(lalitm): reconsider the decision to show nothing, instead maybe show
- // the top 5 options etc.
- readonly aggregatableProperties?: ReadonlyArray<QueryFlamegraphColumn>;
+ readonly aggregatableProperties?: ReadonlyArray<AggQueryFlamegraphColumn>;
+}
+
+export interface QueryFlamegraphState {
+ state: FlamegraphState;
}
// Given a table and columns on those table (corresponding to metrics),
@@ -89,7 +104,7 @@
tableMetrics: ReadonlyArray<{name: string; unit: string; columnName: string}>,
dependencySql?: string,
unaggregatableProperties?: ReadonlyArray<QueryFlamegraphColumn>,
- aggregatableProperties?: ReadonlyArray<QueryFlamegraphColumn>,
+ aggregatableProperties?: ReadonlyArray<AggQueryFlamegraphColumn>,
): QueryFlamegraphMetric[] {
const metrics = [];
for (const {name, unit, columnName} of tableMetrics) {
@@ -108,64 +123,42 @@
return metrics;
}
-export interface QueryFlamegraphAttrs {
- readonly engine: Engine;
- readonly metrics: ReadonlyArray<QueryFlamegraphMetric>;
-}
-
-// A Mithril component which wraps the `Flamegraph` widget and fetches the data
-// for the widget by querying an `Engine`.
-export class QueryFlamegraph implements m.ClassComponent<QueryFlamegraphAttrs> {
- private selectedMetricName;
+// A Perfetto UI component which wraps the `Flamegraph` widget and fetches the
+// data for the widget by querying an `Engine`.
+export class QueryFlamegraph {
private data?: FlamegraphQueryData;
- private filters: FlamegraphFilters = {
- showStack: [],
- hideStack: [],
- showFromFrame: [],
- hideFrame: [],
- view: {kind: 'TOP_DOWN'},
- };
- private attrs: QueryFlamegraphAttrs;
- private selMonitor = new Monitor([() => this.attrs.metrics]);
- private queryLimiter = new AsyncLimiter();
+ private readonly selMonitor = new Monitor([() => this.state.state]);
+ private readonly queryLimiter = new AsyncLimiter();
- constructor({attrs}: m.Vnode<QueryFlamegraphAttrs>) {
- this.attrs = attrs;
- this.selectedMetricName = attrs.metrics[0].name;
- }
+ constructor(
+ private readonly trace: Trace,
+ private readonly metrics: ReadonlyArray<QueryFlamegraphMetric>,
+ private state: QueryFlamegraphState,
+ ) {}
- view({attrs}: m.Vnode<QueryFlamegraphAttrs>) {
- this.attrs = attrs;
+ render() {
if (this.selMonitor.ifStateChanged()) {
- this.selectedMetricName = attrs.metrics[0].name;
+ const metric = assertExists(
+ this.metrics.find(
+ (x) => this.state.state.selectedMetricName === x.name,
+ ),
+ );
+ const engine = this.trace.engine;
+ const state = this.state;
this.data = undefined;
- this.fetchData(attrs);
+ this.queryLimiter.schedule(async () => {
+ this.data = undefined;
+ this.data = await computeFlamegraphTree(engine, metric, state.state);
+ });
}
return m(Flamegraph, {
- metrics: attrs.metrics,
- selectedMetricName: this.selectedMetricName,
+ metrics: this.metrics,
data: this.data,
- onMetricChange: (name) => {
- this.selectedMetricName = name;
- this.data = undefined;
- this.fetchData(attrs);
+ state: this.state.state,
+ onStateChange: (state) => {
+ this.state.state = state;
+ this.trace.scheduleFullRedraw();
},
- onFiltersChanged: (filters) => {
- this.filters = filters;
- this.data = undefined;
- this.fetchData(attrs);
- },
- });
- }
-
- private async fetchData(attrs: QueryFlamegraphAttrs) {
- const metric = assertExists(
- attrs.metrics.find((metric) => metric.name === this.selectedMetricName),
- );
- const engine = attrs.engine;
- const filters = this.filters;
- this.queryLimiter.schedule(async () => {
- this.data = await computeFlamegraphTree(engine, metric, filters);
});
}
}
@@ -178,8 +171,21 @@
unaggregatableProperties,
aggregatableProperties,
}: QueryFlamegraphMetric,
- {showStack, hideStack, showFromFrame, hideFrame, view}: FlamegraphFilters,
+ {filters, view}: FlamegraphState,
): Promise<FlamegraphQueryData> {
+ const showStack = filters
+ .filter((x) => x.kind === 'SHOW_STACK')
+ .map((x) => x.filter);
+ const hideStack = filters
+ .filter((x) => x.kind === 'HIDE_STACK')
+ .map((x) => x.filter);
+ const showFromFrame = filters
+ .filter((x) => x.kind === 'SHOW_FROM_FRAME')
+ .map((x) => x.filter);
+ const hideFrame = filters
+ .filter((x) => x.kind === 'HIDE_FRAME')
+ .map((x) => x.filter);
+
// Pivot also essentially acts as a "show stack" filter so treat it like one.
const showStackAndPivot = [...showStack];
if (view.kind === 'PIVOT') {
@@ -354,7 +360,7 @@
from _viz_flamegraph_merge_hashes!(
_flamegraph_hash_${uuid},
${groupingColumns},
- ${groupedColumns}
+ ${computeGroupedAggExprs(agg)}
)
`,
),
@@ -392,7 +398,7 @@
xStart: NUM,
xEnd: NUM,
...Object.fromEntries(unaggCols.map((m) => [m, STR_NULL])),
- ...Object.fromEntries(aggCols.map((m) => [m, STR_NULL])),
+ ...Object.fromEntries(aggCols.map((m) => [m, UNKNOWN])),
});
let postiveRootsValue = 0;
let negativeRootsValue = 0;
@@ -457,3 +463,15 @@
}
return '0';
}
+
+function computeGroupedAggExprs(agg: ReadonlyArray<AggQueryFlamegraphColumn>) {
+ const aggFor = (x: AggQueryFlamegraphColumn) => {
+ switch (x.mergeAggregation) {
+ case 'ONE_OR_NULL':
+ return `IIF(COUNT() = 1, ${x.name}, NULL) AS ${x.name}`;
+ case 'SUM':
+ return `SUM(${x.name}) AS ${x.name}`;
+ }
+ };
+ return `(${agg.length === 0 ? 'groupedColumn' : agg.map((x) => aggFor(x)).join(',')})`;
+}
diff --git a/ui/src/public/lib/query_table/query_result_tab.ts b/ui/src/public/lib/query_table/query_result_tab.ts
index 36cbe3b..bf30cd0 100644
--- a/ui/src/public/lib/query_table/query_result_tab.ts
+++ b/ui/src/public/lib/query_table/query_result_tab.ts
@@ -17,17 +17,13 @@
import {assertExists} from '../../../base/logging';
import {QueryResponse, runQuery} from './queries';
import {QueryError} from '../../../trace_processor/query_result';
-import {
- AddDebugTrackMenu,
- uuidToViewName,
-} from '../debug_tracks/add_debug_track_menu';
+import {AddDebugTrackMenu} from '../tracks/add_debug_track_menu';
import {Button} from '../../../widgets/button';
import {PopupMenu2} from '../../../widgets/menu';
import {PopupPosition} from '../../../widgets/popup';
-import {BottomTab, NewBottomTabArgs} from '../bottom_tab';
import {QueryTable} from './query_table';
-import {BottomTabToTabAdapter} from '../../../public/utils';
import {Trace} from '../../../public/trace';
+import {Tab} from '../../tab';
interface QueryResultTabConfig {
readonly query: string;
@@ -44,58 +40,44 @@
config: QueryResultTabConfig,
tag?: string,
): void {
- const queryResultsTab = new QueryResultTab({
- trace,
- config,
- uuid: uuidv4(),
- });
+ const queryResultsTab = new QueryResultTab(trace, config);
const uri = 'queryResults#' + (tag ?? uuidv4());
trace.tabs.registerTab({
uri,
- content: new BottomTabToTabAdapter(queryResultsTab),
+ content: queryResultsTab,
isEphemeral: true,
});
trace.tabs.showTab(uri);
}
-export class QueryResultTab extends BottomTab<QueryResultTabConfig> {
- static readonly kind = 'dev.perfetto.QueryResultTab';
+export class QueryResultTab implements Tab {
+ private queryResponse?: QueryResponse;
+ private sqlViewName?: string;
- queryResponse?: QueryResponse;
- sqlViewName?: string;
-
- static create(args: NewBottomTabArgs<QueryResultTabConfig>): QueryResultTab {
- return new QueryResultTab(args);
+ constructor(
+ private readonly trace: Trace,
+ private readonly args: QueryResultTabConfig,
+ ) {
+ this.initTrack();
}
- constructor(args: NewBottomTabArgs<QueryResultTabConfig>) {
- super(args);
-
- this.initTrack(args);
- }
-
- async initTrack(args: NewBottomTabArgs<QueryResultTabConfig>) {
- let uuid = '';
- if (this.config.prefetchedResponse !== undefined) {
- this.queryResponse = this.config.prefetchedResponse;
- uuid = args.uuid;
+ private async initTrack() {
+ if (this.args.prefetchedResponse !== undefined) {
+ this.queryResponse = this.args.prefetchedResponse;
} else {
- const result = await runQuery(this.config.query, this.engine);
+ const result = await runQuery(this.args.query, this.trace.engine);
this.queryResponse = result;
if (result.error !== undefined) {
return;
}
-
- uuid = uuidv4();
}
- if (uuid !== '') {
- this.sqlViewName = await this.createViewForDebugTrack(uuid);
- if (this.sqlViewName) {
- this.trace.scheduleRedraw();
- }
+ // TODO(stevegolton): Do we really need to create this view upfront?
+ this.sqlViewName = await this.createViewForDebugTrack(uuidv4());
+ if (this.sqlViewName) {
+ this.trace.scheduleFullRedraw();
}
}
@@ -103,12 +85,13 @@
const suffix = this.queryResponse
? ` (${this.queryResponse.rows.length})`
: '';
- return `${this.config.title}${suffix}`;
+ return `${this.args.title}${suffix}`;
}
- viewTab(): m.Child {
+ render(): m.Children {
return m(QueryTable, {
- query: this.config.query,
+ trace: this.trace,
+ query: this.args.query,
resp: this.queryResponse,
fillParent: true,
contextButtons: [
@@ -144,9 +127,9 @@
this.queryResponse && this.queryResponse.error === undefined;
const sqlQuery = hasValidQueryResponse
? this.queryResponse!.lastStatementSql
- : this.config.query;
+ : this.args.query;
try {
- const createViewResult = await this.engine.query(
+ const createViewResult = await this.trace.engine.query(
`create view ${viewId} as ${sqlQuery}`,
);
if (createViewResult.error()) {
@@ -163,3 +146,7 @@
return viewId;
}
}
+
+export function uuidToViewName(uuid: string): string {
+ return `view_${uuid.split('-').join('_')}`;
+}
diff --git a/ui/src/public/lib/query_table/query_table.ts b/ui/src/public/lib/query_table/query_table.ts
index d7c7a63..2ab39e1 100644
--- a/ui/src/public/lib/query_table/query_table.ts
+++ b/ui/src/public/lib/query_table/query_table.ts
@@ -24,11 +24,13 @@
import {Callout} from '../../../widgets/callout';
import {DetailsShell} from '../../../widgets/details_shell';
import {downloadData} from '../../../frontend/download_utils';
-import {globals} from '../../../frontend/globals';
-import {Router} from '../../../frontend/router';
+import {Router} from '../../../core/router';
import {scrollTo} from '../../scroll_helper';
+import {AppImpl} from '../../../core/app_impl';
+import {Trace} from '../../trace';
interface QueryTableRowAttrs {
+ trace: Trace;
row: Row;
columns: string[];
}
@@ -93,6 +95,12 @@
}
class QueryTableRow implements m.ClassComponent<QueryTableRowAttrs> {
+ private readonly trace: Trace;
+
+ constructor({attrs}: m.Vnode<QueryTableRowAttrs>) {
+ this.trace = attrs.trace;
+ }
+
view(vnode: m.Vnode<QueryTableRowAttrs>) {
const {row, columns} = vnode.attrs;
const cells = columns.map((col) => this.renderCell(col, row[col]));
@@ -145,7 +153,7 @@
const sliceStart = Time.fromRaw(BigInt(row.ts));
// row.dur can be negative. Clamp to 1ns.
const sliceDur = BigintMath.max(BigInt(row.dur), 1n);
- const trackUri = globals.trackManager.findTrack((td) =>
+ const trackUri = this.trace.tracks.findTrack((td) =>
td.tags?.trackIds?.includes(trackId),
)?.uri;
if (trackUri !== undefined) {
@@ -155,23 +163,21 @@
});
const sliceId = getSliceId(row);
if (sliceId !== undefined) {
- this.selectSlice(sliceId, trackUri, switchToCurrentSelectionTab);
+ this.selectSlice(sliceId, switchToCurrentSelectionTab);
}
}
}
- private selectSlice(
- sliceId: number,
- _trackUuid: string,
- switchToCurrentSelectionTab: boolean,
- ) {
- globals.selectionManager.selectSqlEvent('slice', sliceId, {
+ private selectSlice(sliceId: number, switchToCurrentSelectionTab: boolean) {
+ this.trace.selection.selectSqlEvent('slice', sliceId, {
switchToCurrentSelectionTab,
+ scrollToSelection: true,
});
}
}
interface QueryTableContentAttrs {
+ trace: Trace;
resp: QueryResponse;
}
@@ -192,7 +198,7 @@
const tableHeader = m('tr', cols);
const rows = resp.rows.map((row) =>
- m(QueryTableRow, {row, columns: resp.columns}),
+ m(QueryTableRow, {trace: vnode.attrs.trace, row, columns: resp.columns}),
);
if (resp.error) {
@@ -208,6 +214,7 @@
}
interface QueryTableAttrs {
+ trace: Trace;
query: string;
resp?: QueryResponse;
contextButtons?: m.Child[];
@@ -215,6 +222,12 @@
}
export class QueryTable implements m.ClassComponent<QueryTableAttrs> {
+ private readonly trace: Trace;
+
+ constructor({attrs}: m.CVnode<QueryTableAttrs>) {
+ this.trace = attrs.trace;
+ }
+
view({attrs}: m.CVnode<QueryTableAttrs>) {
const {resp, query, contextButtons = [], fillParent} = attrs;
@@ -235,7 +248,7 @@
return 'Query - running';
}
const result = resp.error ? 'error' : `${resp.rows.length} rows`;
- if (globals.testing) {
+ if (AppImpl.instance.testingMode) {
// Omit the duration in tests, they cause screenshot diff failures.
return `Query result (${result})`;
}
@@ -280,7 +293,7 @@
'Only the results for the last statement are displayed.',
),
),
- m(QueryTableContent, {resp}),
+ m(QueryTableContent, {trace: this.trace, resp}),
);
}
}
diff --git a/ui/src/common/state_unittest.ts b/ui/src/public/lib/stdlib_docs.ts
similarity index 64%
copy from ui/src/common/state_unittest.ts
copy to ui/src/public/lib/stdlib_docs.ts
index af8f5f9..fcdcf18 100644
--- a/ui/src/common/state_unittest.ts
+++ b/ui/src/public/lib/stdlib_docs.ts
@@ -1,4 +1,4 @@
-// Copyright (C) 2018 The Android Open Source Project
+// 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.
@@ -12,10 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {createEmptyState} from './empty_state';
-import {State} from './state';
+import {assetSrc} from '../../base/assets';
-test('createEmptyState', () => {
- const state: State = createEmptyState();
- expect(state.engine).toEqual(undefined);
-});
+// Fetch the stdlib docs
+export async function getStdlibDocs(): Promise<string> {
+ const resp = await fetch(assetSrc('stdlib_docs.json'));
+ const json = await resp.json();
+ return JSON.parse(json);
+}
diff --git a/ui/src/public/lib/debug_tracks/add_debug_track_menu.ts b/ui/src/public/lib/tracks/add_debug_track_menu.ts
similarity index 84%
rename from ui/src/public/lib/debug_tracks/add_debug_track_menu.ts
rename to ui/src/public/lib/tracks/add_debug_track_menu.ts
index 50ade15..19eef19 100644
--- a/ui/src/public/lib/debug_tracks/add_debug_track_menu.ts
+++ b/ui/src/public/lib/tracks/add_debug_track_menu.ts
@@ -18,18 +18,13 @@
import {Select} from '../../../widgets/select';
import {TextInput} from '../../../widgets/text_input';
import {
- CounterColumns,
- SliceColumns,
- SqlDataSource,
addDebugCounterTrack,
addDebugSliceTrack,
addPivotedTracks,
} from './debug_tracks';
import {Trace} from '../../trace';
-
-export function uuidToViewName(uuid: string): string {
- return `view_${uuid.split('-').join('_')}`;
-}
+import {SliceColumnMapping, SqlDataSource} from './query_slice_track';
+import {CounterColumnMapping} from './query_counter_track';
interface AddDebugTrackMenuAttrs {
dataSource: Required<SqlDataSource>;
@@ -121,7 +116,7 @@
this.trackType = (e.target as HTMLSelectElement).value as
| 'slice'
| 'counter';
- trace.scheduleRedraw();
+ trace.scheduleFullRedraw();
},
},
options,
@@ -181,7 +176,7 @@
onSubmit: () => {
switch (this.trackType) {
case 'slice':
- const sliceColumns: SliceColumns = {
+ const sliceColumns: SliceColumnMapping = {
ts: this.renderParams.ts,
dur: this.renderParams.dur,
name: this.renderParams.name,
@@ -193,26 +188,26 @@
this.name,
this.renderParams.pivot,
async (ctx, data, trackName) =>
- addDebugSliceTrack(
- ctx,
+ addDebugSliceTrack({
+ trace: ctx,
data,
- trackName,
- sliceColumns,
- this.columns,
- ),
+ title: trackName,
+ columns: sliceColumns,
+ argColumns: this.columns,
+ }),
);
} else {
- addDebugSliceTrack(
- vnode.attrs.trace,
- vnode.attrs.dataSource,
- this.name,
- sliceColumns,
- this.columns,
- );
+ addDebugSliceTrack({
+ trace: vnode.attrs.trace,
+ data: vnode.attrs.dataSource,
+ title: this.name,
+ columns: sliceColumns,
+ argColumns: this.columns,
+ });
}
break;
case 'counter':
- const counterColumns: CounterColumns = {
+ const counterColumns: CounterColumnMapping = {
ts: this.renderParams.ts,
value: this.renderParams.value,
};
@@ -224,15 +219,20 @@
this.name,
this.renderParams.pivot,
async (ctx, data, trackName) =>
- addDebugCounterTrack(ctx, data, trackName, counterColumns),
+ addDebugCounterTrack({
+ trace: ctx,
+ data,
+ title: trackName,
+ columns: counterColumns,
+ }),
);
} else {
- addDebugCounterTrack(
- vnode.attrs.trace,
- vnode.attrs.dataSource,
- this.name,
- counterColumns,
- );
+ addDebugCounterTrack({
+ trace: vnode.attrs.trace,
+ data: vnode.attrs.dataSource,
+ title: this.name,
+ columns: counterColumns,
+ });
}
break;
}
diff --git a/ui/src/public/lib/tracks/debug_tracks.ts b/ui/src/public/lib/tracks/debug_tracks.ts
new file mode 100644
index 0000000..e1caad0
--- /dev/null
+++ b/ui/src/public/lib/tracks/debug_tracks.ts
@@ -0,0 +1,133 @@
+// 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 {
+ matchesSqlValue,
+ sqlValueToReadableString,
+} from '../../../trace_processor/sql_utils';
+import {TrackNode} from '../../workspace';
+import {Trace} from '../../trace';
+import {
+ createQuerySliceTrack,
+ SliceColumnMapping,
+ SqlDataSource,
+} from './query_slice_track';
+import {
+ CounterColumnMapping,
+ createQueryCounterTrack,
+} from './query_counter_track';
+
+let trackCounter = 0; // For reproducible ids.
+
+export async function addPivotedTracks(
+ trace: Trace,
+ data: SqlDataSource,
+ trackName: string,
+ pivotColumn: string,
+ createTrack: (
+ trace: Trace,
+ data: SqlDataSource,
+ trackName: string,
+ ) => Promise<void>,
+) {
+ const iter = (
+ await trace.engine.query(`
+ with all_vals as (${data.sqlSource})
+ select DISTINCT ${pivotColumn} from all_vals
+ order by ${pivotColumn}
+ `)
+ ).iter({});
+
+ for (; iter.valid(); iter.next()) {
+ await createTrack(
+ trace,
+ {
+ sqlSource: `select * from
+ (${data.sqlSource})
+ where ${pivotColumn} ${matchesSqlValue(iter.get(pivotColumn))}`,
+ },
+ `${trackName.trim() || 'Pivot Track'}: ${sqlValueToReadableString(iter.get(pivotColumn))}`,
+ );
+ }
+}
+
+export interface DebugSliceTrackArgs {
+ readonly trace: Trace;
+ readonly data: SqlDataSource;
+ readonly title?: string;
+ readonly columns?: Partial<SliceColumnMapping>;
+ readonly argColumns?: string[];
+}
+
+/**
+ * Adds a new debug slice track to the workspace.
+ *
+ * See {@link createQuerySliceTrack} for details about the configuration args.
+ *
+ * A debug slice track is a track based on a query which is:
+ * - Based on a query.
+ * - Uses automatic slice layout.
+ * - Automatically added to the top of the current workspace.
+ * - Pinned.
+ * - Has a close button.
+ */
+export async function addDebugSliceTrack(args: DebugSliceTrackArgs) {
+ const trace = args.trace;
+ const cnt = trackCounter++;
+ const uri = `debugSliceTrack/${cnt}`;
+ const title = args.title?.trim() || `Debug Slice Track ${cnt}`;
+
+ // Create & register the track renderer
+ const track = await createQuerySliceTrack({...args, uri});
+ trace.tracks.registerTrack({uri, title, track});
+
+ // Create the track node and pin it
+ const trackNode = new TrackNode({uri, title, removable: true});
+ trace.workspace.addChildFirst(trackNode);
+ trackNode.pin();
+}
+
+export interface DebugCounterTrackArgs {
+ readonly trace: Trace;
+ readonly data: SqlDataSource;
+ readonly title?: string;
+ readonly columns?: Partial<CounterColumnMapping>;
+}
+
+/**
+ * Adds a new debug counter track to the workspace.
+ *
+ * See {@link createQueryCounterTrack} for details about the configuration args.
+ *
+ * A debug counter track is a track based on a query which is:
+ * - Based on a query.
+ * - Automatically added to the top of the current workspace.
+ * - Pinned.
+ * - Has a close button.
+ */
+export async function addDebugCounterTrack(args: DebugCounterTrackArgs) {
+ const trace = args.trace;
+ const cnt = trackCounter++;
+ const uri = `debugCounterTrack/${cnt}`;
+ const title = args.title?.trim() || `Debug Counter Track ${cnt}`;
+
+ // Create & register the track renderer
+ const track = await createQueryCounterTrack({...args, uri});
+ trace.tracks.registerTrack({uri, title, track});
+
+ // Create the track node and pin it
+ const trackNode = new TrackNode({uri, title, removable: true});
+ trace.workspace.addChildFirst(trackNode);
+ trackNode.pin();
+}
diff --git a/ui/src/public/lib/tracks/query_counter_track.ts b/ui/src/public/lib/tracks/query_counter_track.ts
new file mode 100644
index 0000000..dc25d69
--- /dev/null
+++ b/ui/src/public/lib/tracks/query_counter_track.ts
@@ -0,0 +1,122 @@
+// 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 {createPerfettoTable} from '../../../trace_processor/sql_utils';
+import {Trace} from '../../trace';
+import {sqlNameSafe} from '../../../base/string_utils';
+import {
+ BaseCounterTrack,
+ CounterOptions,
+} from '../../../frontend/base_counter_track';
+import {Engine} from '../../../trace_processor/engine';
+
+export interface QueryCounterTrackArgs {
+ // The trace object used to run queries.
+ readonly trace: Trace;
+
+ // A unique, reproducible ID for this track.
+ readonly uri: string;
+
+ // The query and optional column remapping.
+ readonly data: SqlDataSource;
+
+ // Optional: Which columns should be used for ts, and value. If omitted,
+ // the defaults 'ts', and 'value' will be used.
+ readonly columns?: Partial<CounterColumnMapping>;
+
+ // Optional: Display options for the counter track.
+ readonly options?: Partial<CounterOptions>;
+}
+
+export interface SqlDataSource {
+ // SQL source selecting the necessary data.
+ readonly sqlSource: string;
+
+ // Optional: Rename columns from the query result.
+ // If omitted, original column names from the query are used instead.
+ // The caller is responsible for ensuring that the number of items in this
+ // list matches the number of columns returned by sqlSource.
+ readonly columns?: string[];
+}
+
+export interface CounterColumnMapping {
+ readonly ts: string;
+ readonly value: string;
+}
+
+/**
+ * Creates a counter track based on a query.
+ *
+ * The query must provide the following columns:
+ * - ts: INTEGER - The timestamp of each sample.
+ * - value: REAL | INTEGER - The value of each sample.
+ *
+ * The column names don't have to be 'ts' and 'value', and can be remapped if
+ * convenient using the config.columns parameter.
+ */
+export async function createQueryCounterTrack(args: QueryCounterTrackArgs) {
+ const tableName = `__query_counter_track_${sqlNameSafe(args.uri)}`;
+ await createPerfettoTableForTrack(
+ args.trace.engine,
+ tableName,
+ args.data,
+ args.columns,
+ );
+ return new SqlTableCounterTrack(
+ args.trace,
+ args.uri,
+ tableName,
+ args.options,
+ );
+}
+
+async function createPerfettoTableForTrack(
+ engine: Engine,
+ tableName: string,
+ data: SqlDataSource,
+ columnMapping: Partial<CounterColumnMapping> = {},
+) {
+ const {ts = 'ts', value = 'value'} = columnMapping;
+ const query = `
+ with data as (
+ ${data.sqlSource}
+ )
+ select
+ ${ts} as ts,
+ ${value} as value
+ from data
+ order by ts
+ `;
+
+ return await createPerfettoTable(engine, tableName, query);
+}
+
+class SqlTableCounterTrack extends BaseCounterTrack {
+ constructor(
+ trace: Trace,
+ uri: string,
+ private readonly sqlTableName: string,
+ options?: Partial<CounterOptions>,
+ ) {
+ super({
+ trace,
+ uri,
+ options,
+ });
+ }
+
+ getSqlSource(): string {
+ return `select * from ${this.sqlTableName}`;
+ }
+}
diff --git a/ui/src/public/lib/tracks/query_slice_track.ts b/ui/src/public/lib/tracks/query_slice_track.ts
new file mode 100644
index 0000000..f7455a9
--- /dev/null
+++ b/ui/src/public/lib/tracks/query_slice_track.ts
@@ -0,0 +1,159 @@
+// 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 {
+ CustomSqlTableDefConfig,
+ CustomSqlTableSliceTrack,
+} from '../../../frontend/tracks/custom_sql_table_slice_track';
+import {
+ ARG_PREFIX,
+ SqlTableSliceTrackDetailsPanel,
+} from './sql_table_slice_track_details_tab';
+import {createPerfettoTable} from '../../../trace_processor/sql_utils';
+import {Trace} from '../../trace';
+import {TrackEventSelection} from '../../selection';
+import {sqlNameSafe} from '../../../base/string_utils';
+import {Engine} from '../../../trace_processor/engine';
+
+export interface QuerySliceTrackArgs {
+ // The trace object used to run queries.
+ readonly trace: Trace;
+
+ // A unique, reproducible ID for this track.
+ readonly uri: string;
+
+ // The query and optional column remapping.
+ readonly data: SqlDataSource;
+
+ // Optional: Which columns should be used for ts, dur, and name. If omitted,
+ // the defaults 'ts', 'dur', and 'name' will be used.
+ readonly columns?: Partial<SliceColumnMapping>;
+
+ // Optional: A list of column names which are displayed in the details panel
+ // when a slice is selected.
+ readonly argColumns?: string[];
+}
+
+export interface SqlDataSource {
+ // SQL source selecting the necessary data.
+ readonly sqlSource: string;
+
+ // Optional: Rename columns from the query result.
+ // If omitted, original column names from the query are used instead.
+ // The caller is responsible for ensuring that the number of items in this
+ // list matches the number of columns returned by sqlSource.
+ readonly columns?: string[];
+}
+
+export interface SliceColumnMapping {
+ readonly ts: string;
+ readonly dur: string;
+ readonly name: string;
+}
+
+/**
+ * Creates a slice track based on a query with automatic slice layout.
+ *
+ * The query must provide the following columns:
+ * - ts: INTEGER - The timestamp of the start of each slice.
+ * - dur: INTEGER - The length of each slice.
+ * - name: TEXT - A name to show on each slice, which is also used to derive the
+ * color.
+ *
+ * The column names don't have to be 'ts', 'dur', and 'name' and can be remapped
+ * if convenient using the config.columns parameter.
+ *
+ * An optional set of columns can be provided which will be displayed in the
+ * details panel when a slice is selected.
+ *
+ * The layout (vertical depth) of each slice will be determined automatically to
+ * avoid overlapping slices.
+ */
+export async function createQuerySliceTrack(args: QuerySliceTrackArgs) {
+ const tableName = `__query_slice_track_${sqlNameSafe(args.uri)}`;
+ await createPerfettoTableForTrack(
+ args.trace.engine,
+ tableName,
+ args.data,
+ args.columns,
+ args.argColumns,
+ );
+ return new SqlTableSliceTrack(args.trace, args.uri, tableName);
+}
+
+async function createPerfettoTableForTrack(
+ engine: Engine,
+ tableName: string,
+ data: SqlDataSource,
+ columns: Partial<SliceColumnMapping> = {},
+ argColumns: string[] = [],
+) {
+ const {ts = 'ts', dur = 'dur', name = 'name'} = columns;
+
+ // If the view has clashing names (e.g. "name" coming from joining two
+ // different tables, we will see names like "name_1", "name_2", but they
+ // won't be addressable from the SQL. So we explicitly name them through a
+ // list of columns passed to CTE.
+ const dataColumns =
+ data.columns !== undefined ? `(${data.columns.join(', ')})` : '';
+
+ const query = `
+ with data${dataColumns} as (
+ ${data.sqlSource}
+ ),
+ prepared_data as (
+ select
+ ${ts} as ts,
+ ifnull(cast(${dur} as int), -1) as dur,
+ printf('%s', ${name}) as name
+ ${argColumns.length > 0 ? ',' : ''}
+ ${argColumns.map((c) => `${c} as ${ARG_PREFIX}${c}`).join(',\n')}
+ from data
+ )
+ select
+ row_number() over (order by ts) as id,
+ *
+ from prepared_data
+ order by ts
+ `;
+
+ return await createPerfettoTable(engine, tableName, query);
+}
+
+class SqlTableSliceTrack extends CustomSqlTableSliceTrack {
+ constructor(
+ trace: Trace,
+ uri: string,
+ private readonly sqlTableName: string,
+ ) {
+ super({
+ trace,
+ uri,
+ });
+ }
+
+ override async getSqlDataSource(): Promise<CustomSqlTableDefConfig> {
+ return {
+ sqlTableName: this.sqlTableName,
+ };
+ }
+
+ override detailsPanel({eventId}: TrackEventSelection) {
+ return new SqlTableSliceTrackDetailsPanel(
+ this.trace,
+ this.sqlTableName,
+ eventId,
+ );
+ }
+}
diff --git a/ui/src/public/lib/debug_tracks/details_tab.ts b/ui/src/public/lib/tracks/sql_table_slice_track_details_tab.ts
similarity index 86%
rename from ui/src/public/lib/debug_tracks/details_tab.ts
rename to ui/src/public/lib/tracks/sql_table_slice_track_details_tab.ts
index 615f842..621d9d3 100644
--- a/ui/src/public/lib/debug_tracks/details_tab.ts
+++ b/ui/src/public/lib/tracks/sql_table_slice_track_details_tab.ts
@@ -14,8 +14,6 @@
import m from 'mithril';
import {duration, Time, time} from '../../../base/time';
-import {BottomTab, NewBottomTabArgs} from '../bottom_tab';
-import {GenericSliceDetailsTabConfig} from '../../../frontend/generic_slice_details_tab';
import {hasArgs, renderArguments} from '../../../frontend/slice_args';
import {getSlice, SliceDetails} from '../../../trace_processor/sql_utils/slice';
import {
@@ -49,6 +47,8 @@
import {getThreadName} from '../../../trace_processor/sql_utils/thread';
import {getProcessName} from '../../../trace_processor/sql_utils/process';
import {sliceRef} from '../../../frontend/widgets/slice';
+import {TrackEventDetailsPanel} from '../../details_panel';
+import {Trace} from '../../trace';
export const ARG_PREFIX = 'arg_';
@@ -78,10 +78,8 @@
return children;
}
-export class DebugSliceDetailsTab extends BottomTab<GenericSliceDetailsTabConfig> {
- static readonly kind = 'dev.perfetto.DebugSliceDetailsTab';
-
- data?: {
+export class SqlTableSliceTrackDetailsPanel implements TrackEventDetailsPanel {
+ private data?: {
name: string;
ts: time;
dur: duration;
@@ -91,14 +89,14 @@
// tables. These values will be set if the relevant columns exist and
// are consistent (e.g. 'ts' and 'dur' for this slice correspond to values
// in these well-known tables).
- threadState?: ThreadState;
- slice?: SliceDetails;
+ private threadState?: ThreadState;
+ private slice?: SliceDetails;
- static create(
- args: NewBottomTabArgs<GenericSliceDetailsTabConfig>,
- ): DebugSliceDetailsTab {
- return new DebugSliceDetailsTab(args);
- }
+ constructor(
+ private readonly trace: Trace,
+ private readonly tableName: string,
+ private readonly eventId: number,
+ ) {}
private async maybeLoadThreadState(
id: number | undefined,
@@ -110,7 +108,7 @@
if (id === undefined) return undefined;
if (utid === undefined) return undefined;
- const threadState = await getThreadState(this.engine, id);
+ const threadState = await getThreadState(this.trace.engine, id);
if (threadState === undefined) return undefined;
if (
table === 'thread_state' ||
@@ -150,7 +148,7 @@
if (id === undefined) return undefined;
if (table !== 'slice' && trackId === undefined) return undefined;
- const slice = await getSlice(this.engine, asSliceSqlId(id));
+ const slice = await getSlice(this.trace.engine, asSliceSqlId(id));
if (slice === undefined) return undefined;
if (
table === 'slice' ||
@@ -193,9 +191,9 @@
);
}
- private async loadData() {
- const queryResult = await this.engine.query(
- `select * from ${this.config.sqlTableName} where id = ${this.config.id}`,
+ async load() {
+ const queryResult = await this.trace.engine.query(
+ `select * from ${this.tableName} where id = ${this.eventId}`,
);
const row = queryResult.firstRow({
ts: LONG,
@@ -234,15 +232,10 @@
sqlValueToNumber(this.data.args['track_id']),
);
- this.trace.scheduleRedraw();
+ this.trace.scheduleFullRedraw();
}
- constructor(args: NewBottomTabArgs<GenericSliceDetailsTabConfig>) {
- super(args);
- this.loadData();
- }
-
- viewTab() {
+ render() {
if (this.data === undefined) {
return m('h2', 'Loading');
}
@@ -250,7 +243,7 @@
'Name': this.data['name'] as string,
'Start time': m(Timestamp, {ts: timeFromSql(this.data['ts'])}),
'Duration': m(DurationWidget, {dur: durationFromSql(this.data['dur'])}),
- 'Debug slice id': `${this.config.sqlTableName}[${this.config.id}]`,
+ 'Slice id': `${this.tableName}[${this.eventId}]`,
});
details.push(this.renderThreadStateInfo());
details.push(this.renderSliceInfo());
@@ -263,7 +256,7 @@
return m(
DetailsShell,
{
- title: 'Debug Slice',
+ title: 'Slice',
},
m(
GridLayout,
diff --git a/ui/src/public/omnibox.ts b/ui/src/public/omnibox.ts
index 6fd6a71..26e2110 100644
--- a/ui/src/public/omnibox.ts
+++ b/ui/src/public/omnibox.ts
@@ -12,8 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {Optional} from '../base/utils';
-
export interface OmniboxManager {
/**
* Turns the omnibox into an interfactive prompt for the user. Think of
@@ -27,7 +25,7 @@
* the chosen PromptOption.key if `options` was provided; returns undefined
* if the user dimisses the prompt by pressing Esc or clicking eslewhere.
*/
- prompt(text: string, options?: PromptOption[]): Promise<Optional<string>>;
+ prompt(text: string, options?: PromptOption[]): Promise<string | undefined>;
}
export interface PromptOption {
diff --git a/ui/src/public/page.ts b/ui/src/public/page.ts
new file mode 100644
index 0000000..08efbc0
--- /dev/null
+++ b/ui/src/public/page.ts
@@ -0,0 +1,71 @@
+// 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 {Trace} from './trace';
+
+/**
+ * Allows to register custom page endpoints that response to given routes, e.g.
+ * /viewer, /record etc.
+ */
+export interface PageManager {
+ /**
+ * Example usage:
+ * registerPage({route: '/foo', page: FooPage})
+ * class FooPage implements m.ClassComponent<PageWithTrace> {
+ * view({attrs}: m.CVnode<PageWithTrace>) {
+ * return m('div', ...
+ * onclick: () => attrs.trace.timeline.zoom(...);
+ * )
+ * }
+ * }
+ */
+ registerPage(pageHandler: PageHandler): Disposable;
+}
+
+/**
+ * Mithril attrs for pages that don't require a Trace object. These pages are
+ * always accessible, even before a trace is loaded.
+ */
+export interface PageAttrs {
+ subpage?: string;
+ trace?: Trace;
+}
+
+/**
+ * Mithril attrs for pages that require a Trace object. These pages are
+ * reachable only after a trace is loaded. Trying to access the route without a
+ * trace loaded results in the HomePage (route: '/') to be displayed instead.
+ */
+export interface PageWithTraceAttrs extends PageAttrs {
+ trace: Trace;
+}
+
+export type PageHandler<PWT = m.ComponentTypes<PageWithTraceAttrs>> = {
+ route: string; // e.g. '/' (default route), '/viewer'
+ pluginId?: string; // Not needed, the internal impl will fill it.
+} & (
+ | {
+ // If true, the route will be available even when there is no trace
+ // loaded. The component needs to deal with a possibly undefined attr.
+ traceless: true;
+ page: m.ComponentTypes<PageAttrs>;
+ }
+ | {
+ // If is omitted, the route will be available only when a trace is loaded.
+ // The component is guarranteed to get a defined Trace in its attrs.
+ traceless?: false;
+ page: PWT;
+ }
+);
diff --git a/ui/src/public/plugin.ts b/ui/src/public/plugin.ts
index ff16ef2..2360acf 100644
--- a/ui/src/public/plugin.ts
+++ b/ui/src/public/plugin.ts
@@ -15,57 +15,29 @@
import {Trace} from './trace';
import {App} from './app';
-// TODO(primiano): I think we should re-think the plugins lifecycle API. Having
-// onTraceUnload and on(another)TraceLoad on the same object is too brittle.
-// What is going to happen is that plugins will mix the state of old and new
-// trace their `this.xxx` and hit bugs on trace swap.
-// I think a better model is to create a new Plugin instance for each trace, and
-// pass the Trace interface in the ctor. In this way they can save it in
-// `this.trace` if they want, and keep all their trace-related state there.
-// The number of plugins that want to do things before a trace is loaded is
-// extremely low and I'd much rather treat that as a special case (e.g., by
-// having a two different factories in the PluginDescriptor, one for App and
-// one factory invoked on each new trace. Such a model would be incredibly more
-// robust.
+/**
+ * This interface defines the shape of the plugins's class constructor (i.e. the
+ * the constructor and all static members of the plugin's class.
+ *
+ * This class constructor is registered with the core.
+ *
+ * On trace load, the core will create a new class instance by calling new on
+ * this constructor and then call its onTraceLoad() function.
+ */
+export interface PerfettoPluginStatic<T extends PerfettoPlugin> {
+ readonly id: string;
+ readonly dependencies?: ReadonlyArray<PerfettoPluginStatic<PerfettoPlugin>>;
+ onActivate?(app: App): void;
+ metricVisualisations?(): MetricVisualisation[];
+ new (trace: Trace): T;
+}
+/**
+ * This interface defines the shape of a plugin's trace-scoped instance, which
+ * is created from the class constructor above at trace load time.
+ */
export interface PerfettoPlugin {
- // Lifecycle methods.
- onActivate?(ctx: App): void;
onTraceLoad?(ctx: Trace): Promise<void>;
- onTraceReady?(ctx: Trace): Promise<void>;
- onTraceUnload?(ctx: Trace): Promise<void>;
-
- // Extension points.
- metricVisualisations?(ctx: App): MetricVisualisation[];
-}
-// This interface defines what a plugin factory should look like.
-// This can be defined in the plugin class definition by defining a constructor
-// and the relevant static methods:
-// E.g.
-// class MyPlugin implements TracePlugin<MyState> {
-// migrate(initialState: unknown): MyState {...}
-// constructor(store: Store<MyState>, engine: EngineProxy) {...}
-// ... methods from the TracePlugin interface go here ...
-// }
-// ... which can then be passed around by class i.e. MyPlugin
-
-export interface PluginClass {
- // Instantiate the plugin.
- new (): PerfettoPlugin;
-}
-// Plugins can be class refs or concrete plugin implementations.
-
-export type PluginFactory = PluginClass | PerfettoPlugin;
-
-export interface PluginDescriptor {
- // A unique string for your plugin. To ensure the name is unique you
- // may wish to use a URL with reversed components in the manner of
- // Java package names.
- pluginId: string;
-
- // The plugin factory used to instantiate the plugin object, or if this is
- // an actual plugin implementation, it's just used as-is.
- plugin: PluginFactory;
}
export interface MetricVisualisation {
@@ -100,3 +72,8 @@
// And pass that to the vega(-lite) visualisation.
path: string[];
}
+
+export interface PluginManager {
+ getPlugin<T extends PerfettoPlugin>(plugin: PerfettoPluginStatic<T>): T;
+ metricVisualisations(): MetricVisualisation[];
+}
diff --git a/ui/src/public/route_schema.ts b/ui/src/public/route_schema.ts
new file mode 100644
index 0000000..46ae91b
--- /dev/null
+++ b/ui/src/public/route_schema.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 {z} from 'zod';
+
+// We use .catch(undefined) on every field below to make sure that passing an
+// invalid value doesn't invalidate the other keys which might be valid.
+// Zod default behaviour is atomic: either everything validates correctly or
+// the whole parsing fails.
+export const ROUTE_SCHEMA = z
+ .object({
+ // The local_cache_key is special and is persisted across navigations.
+ local_cache_key: z.string().optional().catch(undefined),
+
+ // These are transient and are really set only on startup.
+
+ // Are we loading a trace via ABT.
+ openFromAndroidBugTool: z.boolean().optional().catch(undefined),
+
+ // For permalink hash.
+ s: z.string().optional().catch(undefined),
+
+ // DEPRECATED: for #!/record?p=cpu subpages (b/191255021).
+ p: z.string().optional().catch(undefined),
+
+ // For fetching traces from Cloud Storage or local servers
+ // as with record_android_trace.
+ url: z.string().optional().catch(undefined),
+
+ // For connecting to a trace_processor_shell --httpd instance running on a
+ // non-standard port. This requires the CSP_WS_PERMISSIVE_PORT flag to relax
+ // the Content Security Policy.
+ rpc_port: z.string().regex(/\d+/).optional().catch(undefined),
+
+ // Override the referrer. Useful for scripts such as
+ // record_android_trace to record where the trace is coming from.
+ referrer: z.string().optional().catch(undefined),
+
+ // For the 'mode' of the UI. For example when the mode is 'embedded'
+ // some features are disabled.
+ mode: z.enum(['embedded']).optional().catch(undefined),
+
+ // Should we hide the sidebar?
+ hideSidebar: z.boolean().optional().catch(undefined),
+
+ // A comma-separated list of plugins to enable for the current session.
+ enablePlugins: z.string().optional().catch(undefined),
+
+ // Deep link support
+ table: z.string().optional().catch(undefined),
+ ts: z.string().optional().catch(undefined),
+ dur: z.string().optional().catch(undefined),
+ tid: z.string().optional().catch(undefined),
+ pid: z.string().optional().catch(undefined),
+ query: z.string().optional().catch(undefined),
+ visStart: z.string().optional().catch(undefined),
+ visEnd: z.string().optional().catch(undefined),
+ })
+ // default({}) ensures at compile-time that every entry is either optional or
+ // has a default value.
+ .default({});
+
+export type RouteArgs = z.infer<typeof ROUTE_SCHEMA>;
diff --git a/ui/src/public/scroll_helper.ts b/ui/src/public/scroll_helper.ts
index e5c6aa9..742f826 100644
--- a/ui/src/public/scroll_helper.ts
+++ b/ui/src/public/scroll_helper.ts
@@ -13,7 +13,6 @@
// limitations under the License.
import {time} from '../base/time';
-import {Optional} from '../base/utils';
/**
* A helper to scroll to a combination of tracks and time ranges.
@@ -57,7 +56,7 @@
// to avoid too many invasive refactorings at once.
type ScrollToFunction = (a: ScrollToArgs) => void;
-let _scrollToFunction: Optional<ScrollToFunction> = undefined;
+let _scrollToFunction: ScrollToFunction | undefined = undefined;
// If a Trace object is avilable, prefer Trace.scrollTo(). It points to the
// same function.
diff --git a/ui/src/public/selection.ts b/ui/src/public/selection.ts
index 3bacde0..d515a4c 100644
--- a/ui/src/public/selection.ts
+++ b/ui/src/public/selection.ts
@@ -13,17 +13,14 @@
// limitations under the License.
import {time, duration, TimeSpan} from '../base/time';
-import {Optional} from '../base/utils';
import {Engine} from '../trace_processor/engine';
import {ColumnDef, Sorting, ThreadStateExtra} from './aggregation';
-import {GenericSliceDetailsTabConfigBase} from './details_panel';
import {TrackDescriptor} from './track';
export interface SelectionManager {
readonly selection: Selection;
- readonly legacySelection: LegacySelection | null;
- findTimeRangeOfSelection(): Promise<Optional<TimeSpan>>;
+ findTimeRangeOfSelection(): TimeSpan | undefined;
clear(): void;
/**
@@ -40,6 +37,14 @@
): void;
/**
+ * Select a track.
+ *
+ * @param trackUri - The URI for the track to select.
+ * @param opts - Additional options.
+ */
+ selectTrack(trackUri: string, opts?: SelectionOpts): void;
+
+ /**
* Select a track event via a sql table name + id.
*
* @param sqlTableName - The name of the SQL table to resolve.
@@ -49,14 +54,6 @@
selectSqlEvent(sqlTableName: string, id: number, opts?: SelectionOpts): void;
/**
- * Select a legacy selection.
- *
- * @param selection - The legacy selection to select.
- * @param opts - Additional options.
- */
- selectLegacy(selection: LegacySelection, opts?: SelectionOpts): void;
-
- /**
* Create an area selection for the purposes of aggregation.
*
* @param args - The area to select.
@@ -67,20 +64,6 @@
scrollToCurrentSelection(): void;
registerAreaSelectionAggreagtor(aggr: AreaSelectionAggregator): void;
- // TODO(primiano): I don't undertsand what this generic slice is, but now
- // is exposed to plugins. For now i'm just carrying it forward.
- selectGenericSlice(args: {
- id: number;
- sqlTableName: string;
- start: time;
- duration: duration;
- trackUri: string;
- detailsPanelConfig: {
- kind: string;
- config: GenericSliceDetailsTabConfigBase;
- };
- }): void;
-
/**
* Register a new SQL selection resolver.
*
@@ -89,17 +72,6 @@
* callback is called which can return a selection object or undefined.
*/
registerSqlSelectionResolver(resolver: SqlSelectionResolver): void;
-
- /**
- * Resolve a section object for a given table and ID.
- *
- * @param sqlTableName - The name of the SQL table to resolve.
- * @param id - The ID of the event in that table.
- */
- resolveSqlEvent(
- sqlTableName: string,
- id: number,
- ): Promise<Selection | undefined>;
}
export interface AreaSelectionAggregator {
@@ -115,108 +87,48 @@
}
export type Selection =
- | SingleSelection
+ | TrackEventSelection
+ | TrackSelection
| AreaSelection
| NoteSelection
- | UnionSelection
- | EmptySelection
- | LegacySelectionWrapper;
+ | EmptySelection;
/** Defines how changes to selection affect the rest of the UI state */
export interface SelectionOpts {
clearSearch?: boolean; // Default: true.
switchToCurrentSelectionTab?: boolean; // Default: true.
- pendingScrollId?: number; // Default: no auto-scroll.
+ scrollToSelection?: boolean; // Default: false.
}
-// LEGACY Selection types:
-
-export interface LegacySelectionWrapper {
- readonly kind: 'legacy';
- readonly legacySelection: LegacySelection;
-}
-
-export type LegacySelection = (
- | SliceSelection
- | HeapProfileSelection
- | CpuProfileSampleSelection
- | ThreadSliceSelection
- | ThreadStateSelection
- | PerfSamplesSelection
- | LogSelection
- | GenericSliceSelection
-) & {trackUri?: string};
-
-export type SelectionKind = LegacySelection['kind']; // 'THREAD_STATE' | 'SLICE' ...
-
-export interface SliceSelection {
- readonly kind: 'SCHED_SLICE';
- readonly id: number;
-}
-
-export interface HeapProfileSelection {
- readonly kind: 'HEAP_PROFILE';
- readonly id: number;
- readonly upid: number;
- readonly ts: time;
- readonly type: ProfileType;
-}
-
-export interface PerfSamplesSelection {
- readonly kind: 'PERF_SAMPLES';
- readonly id: number;
- readonly utid?: number;
- readonly upid?: number;
- readonly leftTs: time;
- readonly rightTs: time;
- readonly type: ProfileType;
-}
-
-export interface CpuProfileSampleSelection {
- readonly kind: 'CPU_PROFILE_SAMPLE';
- readonly id: number;
- readonly utid: number;
- readonly ts: time;
-}
-
-export interface ThreadSliceSelection {
- readonly kind: 'SLICE';
- readonly id: number;
- readonly table?: string;
-}
-
-export interface ThreadStateSelection {
- readonly kind: 'THREAD_STATE';
- readonly id: number;
-}
-
-export interface LogSelection {
- readonly kind: 'LOG';
- readonly id: number;
- readonly trackUri: string;
-}
-
-export interface GenericSliceSelection {
- readonly kind: 'GENERIC_SLICE';
- readonly id: number;
- readonly sqlTableName: string;
- readonly start: time;
- readonly duration: duration;
- // NOTE: this config can be expanded for multiple details panel types.
- readonly detailsPanelConfig: {
- readonly kind: string;
- readonly config: GenericSliceDetailsTabConfigBase;
- };
-}
-
-// New Selection types:
-
-export interface SingleSelection {
- readonly kind: 'single';
+export interface TrackEventSelection extends TrackEventDetails {
+ readonly kind: 'track_event';
readonly trackUri: string;
readonly eventId: number;
}
+export interface TrackSelection {
+ readonly kind: 'track';
+ readonly trackUri: string;
+}
+
+export interface TrackEventDetails {
+ // ts and dur are required by the core, and must be provided.
+ readonly ts: time;
+ // Note: dur can be -1 for instant events.
+ readonly dur: duration;
+
+ // Optional additional information.
+ // TODO(stevegolton): Find an elegant way of moving this information out of
+ // the core.
+ readonly wakeupTs?: time;
+ readonly wakerCpu?: number;
+ readonly upid?: number;
+ readonly utid?: number;
+ readonly tableName?: string;
+ readonly profileType?: ProfileType;
+ readonly interactionType?: string;
+}
+
export interface Area {
readonly start: time;
readonly end: time;
@@ -239,11 +151,6 @@
readonly id: string;
}
-export interface UnionSelection {
- readonly kind: 'union';
- readonly selections: ReadonlyArray<Selection>;
-}
-
export interface EmptySelection {
readonly kind: 'empty';
}
@@ -275,5 +182,5 @@
readonly callback: (
id: number,
sqlTable: string,
- ) => Promise<Selection | undefined>;
+ ) => Promise<{trackUri: string; eventId: number} | undefined>;
}
diff --git a/ui/src/public/sidebar.ts b/ui/src/public/sidebar.ts
index e7c3b5a..caba71d 100644
--- a/ui/src/public/sidebar.ts
+++ b/ui/src/public/sidebar.ts
@@ -12,24 +12,104 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+// For now sections are fixed and cannot be extended by plugins.
+export const SIDEBAR_SECTIONS = {
+ navigation: {
+ title: 'Navigation',
+ summary: 'Open or record a new trace',
+ },
+ current_trace: {
+ title: 'Current Trace',
+ summary: 'Actions on the current trace',
+ },
+ convert_trace: {
+ title: 'Convert trace',
+ summary: 'Convert to other formats',
+ },
+ example_traces: {
+ title: 'Example Traces',
+ summary: 'Open an example trace',
+ },
+ support: {
+ title: 'Support',
+ summary: 'Documentation & Bugs',
+ },
+} as const;
+
+export type SidebarSections = keyof typeof SIDEBAR_SECTIONS;
+
export interface SidebarManager {
+ readonly enabled: boolean;
+
/**
* Adds a new menu item to the sidebar.
* All entries must map to a command. This will allow the shortcut and
* optional shortcut to be displayed on the UI.
*/
addMenuItem(menuItem: SidebarMenuItem): void;
+
+ /**
+ * Gets the current visibility of the sidebar.
+ */
+ get visible(): boolean;
+
+ /**
+ * Toggles the visibility of the sidebar. Can only be called when
+ * `sidebarEnabled` returns `ENABLED`.
+ */
+ toggleVisibility(): void;
}
-export interface SidebarMenuItem {
- readonly commandId: string;
- readonly group:
- | 'navigation'
- | 'current_trace'
- | 'convert_trace'
- | 'example_traces'
- | 'support';
- when?(): boolean;
- readonly icon: string;
- readonly priority?: number;
-}
+export type SidebarMenuItem = {
+ readonly section: SidebarSections;
+ readonly sortOrder?: number;
+
+ // The properties below can be mutated by passing a callback rather than a
+ // direct value. The callback is invoked on every render frame, keep it cheap.
+ // readonly text: string | (() => string);
+ readonly icon?: string | (() => string);
+ readonly tooltip?: string | (() => string);
+ readonly cssClass?: string | (() => string); // Without trailing '.'.
+
+ // If false or omitted the item works normally.
+ // If true the item is striken through and the action/href will be a no-op.
+ // If a string, the item acts as disabled and clicking on it shows a popup
+ // that shows the returned text (the string has "disabled reason" semantic);
+ readonly disabled?: string | boolean | (() => string | boolean);
+
+ // One of the three following arguments must be specified.
+} & (
+ | {
+ /** The text of the menu entry. Required. */
+ readonly text: string | (() => string);
+
+ /**
+ * The URL to navigate to. It can be either:
+ * - A local route (e.g. ''#!/query').
+ * - An absolute URL (e.g. 'https://example.com'). In this case the link will
+ * be open in a target=_blank new tag.
+ */
+ readonly href: string;
+ }
+ | {
+ /** The text of the menu entry. Required. */
+ readonly text: string | (() => string);
+
+ /**
+ * The function that will be invoked when clicking. If the function returns
+ * a promise, a spinner will be drawn next to the sidebar entry until the
+ * promise resolves.
+ */
+ readonly action: () => unknown | Promise<unknown>;
+
+ /** Optional. If omitted href = '#'. */
+ readonly href?: string;
+ }
+ | {
+ /** Optional. If omitted uses the command name. */
+ readonly text?: string | (() => string);
+
+ /** The ID of the command that will be invoked when clicking */
+ readonly commandId: string;
+ }
+);
diff --git a/ui/src/public/timeline.ts b/ui/src/public/timeline.ts
index 8eeb0f3..ca881e5 100644
--- a/ui/src/public/timeline.ts
+++ b/ui/src/public/timeline.ts
@@ -28,6 +28,12 @@
// Render a vertical line on the timeline at this timestamp.
hoverCursorTimestamp: time | undefined;
+ hoveredNoteTimestamp: time | undefined;
+ highlightedSliceId: number | undefined;
+
+ hoveredUtid: number | undefined;
+ hoveredPid: number | undefined;
+
// Get the current timestamp offset.
timestampOffset(): time;
diff --git a/ui/src/public/trace.ts b/ui/src/public/trace.ts
index e7e1602..95db546 100644
--- a/ui/src/public/trace.ts
+++ b/ui/src/public/trace.ts
@@ -23,6 +23,13 @@
import {SelectionManager} from './selection';
import {ScrollToArgs} from './scroll_helper';
import {NoteManager} from './note';
+import {DisposableStack} from '../base/disposable_stack';
+
+// Lists all the possible event listeners using the key as the event name and
+// the type as the type of the callback.
+export interface EventListeners {
+ traceready: () => Promise<void> | void;
+}
/**
* The main API endpoint to interact programmaticaly with the UI and alter its
@@ -50,10 +57,30 @@
// Create a store mounted over the top of this plugin's persistent state.
mountStore<T>(migrate: Migrate<T>): Store<T>;
+ // Returns the blob of the current trace file.
+ // If the trace is opened from a file or postmessage, the blob is returned
+ // immediately. If the trace is opened from URL, this causes a re-download of
+ // the trace. It will throw if traceInfo.downloadable === false.
+ getTraceFile(): Promise<Blob>;
+
+ // List of errors that were encountered while loading the trace by the TS
+ // code. These are on top of traceInfo.importErrors, which is a summary of
+ // what TraceProcessor reports on the stats table at import time.
+ get loadingErrors(): ReadonlyArray<string>;
+
// When the trace is opened via postMessage deep-linking, returns the sub-set
// of postMessageData.pluginArgs[pluginId] for the current plugin. If not
// present returns undefined.
readonly openerPluginArgs?: {[key: string]: unknown};
+
+ // Trace scoped disposables. Will be destroyed when the trace is unloaded.
+ readonly trash: DisposableStack;
+
+ // Register event listeners for trace-level events, e.g. trace ready
+ addEventListener<T extends keyof EventListeners>(
+ event: T,
+ callback: EventListeners[T],
+ ): void;
}
/**
@@ -69,3 +96,5 @@
export interface TraceAttrs {
trace: Trace;
}
+
+export const TRACE_SUFFIX = '.perfetto-trace';
diff --git a/ui/src/public/trace_info.ts b/ui/src/public/trace_info.ts
index 1e4ccd5..71d977b 100644
--- a/ui/src/public/trace_info.ts
+++ b/ui/src/public/trace_info.ts
@@ -13,11 +13,8 @@
// limitations under the License.
import {time} from '../base/time';
-import {TraceSource} from './trace_source';
export interface TraceInfo {
- readonly source: TraceSource;
-
readonly traceTitle: string; // File name and size of the current trace.
readonly traceUrl: string; // URL of the Trace.
@@ -40,14 +37,31 @@
// The list of CPUs in the trace
readonly cpus: number[];
- // The number of gpus in the trace
- readonly gpuCount: number;
-
// The number of import/analysis errors present in the `stats` table.
readonly importErrors: number;
// The trace type inferred by TraceProcessor (e.g. 'proto', 'json, ...).
// See TraceTypeToString() in src/trace_processor/util/trace_type.cc for
// all the available types.
- readonly traceType: string;
+ readonly traceType?: string;
+
+ // True if the trace contains any ftrace data (sched or other ftrace events).
+ readonly hasFtrace: boolean;
+
+ // The UUID of the trace. This is generated by TraceProcessor by either
+ // looking at the TraceUuid packet emitted by traced or, as a fallback, by
+ // hashing the first KB of the trace. This can be an empty string in rare
+ // cases (e.g., opening an empty trace).
+ readonly uuid: string;
+
+ // Wheteher the current trace has been successfully stored into cache storage.
+ readonly cached: boolean;
+
+ // Returns true if the current trace can be downloaded via getTraceFile().
+ // The trace isn't downloadable in the following cases:
+ // - It comes from a source (e.g. HTTP+RPC) that doesn't support re-download
+ // due to technical limitations.
+ // - Download is disabled because the trace was pushed via postMessage and
+ // the caller has asked to disable downloads.
+ readonly downloadable: boolean;
}
diff --git a/ui/src/public/track.ts b/ui/src/public/track.ts
index 01b9204..94ac9e7 100644
--- a/ui/src/public/track.ts
+++ b/ui/src/public/track.ts
@@ -14,13 +14,12 @@
import m from 'mithril';
import {duration, time} from '../base/time';
-import {Optional} from '../base/utils';
-import {UntypedEventSet} from '../core/event_set';
import {Size2D, VerticalBounds} from '../base/geom';
import {TimeScale} from '../base/time_scale';
import {HighPrecisionTimeSpan} from '../base/high_precision_time_span';
import {ColorScheme} from './color_scheme';
-import {TrackSelectionDetailsPanel} from './details_panel';
+import {TrackEventDetailsPanel} from './details_panel';
+import {TrackEventDetails, TrackEventSelection} from './selection';
export interface TrackManager {
/**
@@ -101,14 +100,6 @@
readonly chips?: ReadonlyArray<string>;
readonly pluginId?: string;
-
- // Optional: A details panel to use when this track is selected.
- readonly detailsPanel?: TrackSelectionDetailsPanel;
-
- // Optional: method to look up the start and duration of an event on this track
- readonly getEventBounds?: (
- id: number,
- ) => Promise<Optional<{ts: time; dur: duration}>>;
}
/**
@@ -176,7 +167,7 @@
* at a specific depth, given the slice height and padding/spacing that this
* track uses.
*/
- getSliceVerticalBounds?(depth: number): Optional<VerticalBounds>;
+ getSliceVerticalBounds?(depth: number): VerticalBounds | undefined;
getHeight(): number;
getTrackShellButtons?(): m.Children;
onMouseMove?(event: TrackMouseEvent): void;
@@ -184,9 +175,14 @@
onMouseOut?(): void;
/**
- * Optional: Get the event set that represents this track's data.
+ * Optional: Get details of a track event given by eventId on this track.
*/
- getEventSet?(): UntypedEventSet;
+ getSelectionDetails?(eventId: number): Promise<TrackEventDetails | undefined>;
+
+ // Optional: A factory that returns a details panel object for a given track
+ // event selection. This is called each time the selection is changed (and the
+ // selection is relevant to this track).
+ detailsPanel?(sel: TrackEventSelection): TrackEventDetailsPanel;
}
// An set of key/value pairs describing a given track. These are used for
diff --git a/ui/src/public/track_kinds.ts b/ui/src/public/track_kinds.ts
index 05671c9..d01e43e 100644
--- a/ui/src/public/track_kinds.ts
+++ b/ui/src/public/track_kinds.ts
@@ -18,21 +18,12 @@
export const CPU_SLICE_TRACK_KIND = 'CpuSliceTrack';
export const CPU_FREQ_TRACK_KIND = 'CpuFreqTrack';
export const THREAD_STATE_TRACK_KIND = 'ThreadStateTrack';
-export const THREAD_SLICE_TRACK_KIND = 'ThreadSliceTrack';
+export const SLICE_TRACK_KIND = 'SliceTrack';
export const EXPECTED_FRAMES_SLICE_TRACK_KIND = 'ExpectedFramesSliceTrack';
export const ACTUAL_FRAMES_SLICE_TRACK_KIND = 'ActualFramesSliceTrack';
-export const ASYNC_SLICE_TRACK_KIND = 'AsyncSliceTrack';
export const PERF_SAMPLES_PROFILE_TRACK_KIND = 'PerfSamplesProfileTrack';
export const COUNTER_TRACK_KIND = 'CounterTrack';
export const CPUSS_ESTIMATE_TRACK_KIND = 'CpuSubsystemEstimateTrack';
export const CPU_PROFILE_TRACK_KIND = 'CpuProfileTrack';
export const HEAP_PROFILE_TRACK_KIND = 'HeapProfileTrack';
-export const CHROME_TOPLEVEL_SCROLLS_KIND =
- 'org.chromium.TopLevelScrolls.scrolls';
-export const CHROME_EVENT_LATENCY_TRACK_KIND =
- 'org.chromium.ScrollJank.event_latencies';
-export const SCROLL_JANK_V3_TRACK_KIND =
- 'org.chromium.ScrollJank.scroll_jank_v3_track';
-export const CHROME_SCROLL_JANK_TRACK_KIND =
- 'org.chromium.ScrollJank.BrowserUIThreadLongTasks';
export const ANDROID_LOGS_TRACK_KIND = 'AndroidLogTrack';
diff --git a/ui/src/public/utils.ts b/ui/src/public/utils.ts
index 727ae8e..657bed6 100644
--- a/ui/src/public/utils.ts
+++ b/ui/src/public/utils.ts
@@ -12,12 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import m from 'mithril';
-import {LegacySelection, Selection} from '../public/selection';
-import {BottomTab} from './lib/bottom_tab';
-import {Tab} from './tab';
import {exists} from '../base/utils';
-import {DetailsPanel} from './details_panel';
import {Trace} from './trace';
import {TimeSpan} from '../base/time';
@@ -98,82 +93,6 @@
return 'Unknown';
}
-export interface BottomTabAdapterAttrs {
- tabFactory: (sel: LegacySelection) => BottomTab | undefined;
-}
-
-/**
- * This adapter wraps a BottomTab, converting it into a the new "current
- * selection" API.
- * This adapter is required because most bottom tab implementations expect to
- * be created when the selection changes, however current selection sections
- * stick around in memory forever and produce a section only when they detect a
- * relevant selection.
- * This adapter, given a bottom tab factory function, will simply call the
- * factory function whenever the selection changes. It's up to the implementer
- * to work out whether the selection is relevant and to construct a bottom tab.
- *
- * @example
- * new BottomTabAdapter({
- tabFactory: (sel) => {
- if (sel.kind !== 'SLICE') {
- return undefined;
- }
- return new ChromeSliceDetailsTab({
- config: {
- table: sel.table ?? 'slice',
- id: sel.id,
- },
- trace: ctx,
- uuid: uuidv4(),
- });
- },
- })
- */
-export class BottomTabToSCSAdapter implements DetailsPanel {
- private oldSelection?: Selection;
- private bottomTab?: BottomTab;
- private attrs: BottomTabAdapterAttrs;
-
- constructor(attrs: BottomTabAdapterAttrs) {
- this.attrs = attrs;
- }
-
- render(selection: Selection): m.Children {
- // Detect selection changes, assuming selection is immutable
- if (selection !== this.oldSelection) {
- this.oldSelection = selection;
- if (selection.kind === 'legacy') {
- this.bottomTab = this.attrs.tabFactory(selection.legacySelection);
- } else {
- this.bottomTab = undefined;
- }
- }
-
- return this.bottomTab?.renderPanel();
- }
-
- // Note: Must be called after render()
- isLoading(): boolean {
- return this.bottomTab?.isLoading() ?? false;
- }
-}
-
-/**
- * This adapter wraps a BottomTab, converting it to work with the Tab API.
- */
-export class BottomTabToTabAdapter implements Tab {
- constructor(private bottomTab: BottomTab) {}
-
- getTitle(): string {
- return this.bottomTab.getTitle();
- }
-
- render(): m.Children {
- return this.bottomTab.viewTab();
- }
-}
-
export function getThreadOrProcUri(
upid: number | null,
utid: number | null,
diff --git a/ui/src/public/workspace.ts b/ui/src/public/workspace.ts
index 82a6348..a2bab1f 100644
--- a/ui/src/public/workspace.ts
+++ b/ui/src/public/workspace.ts
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {raf} from '../core/raf_scheduler';
+import {assertTrue} from '../base/logging';
export interface WorkspaceManager {
// This is the same of ctx.workspace, exposed for consistency also here.
@@ -66,6 +66,7 @@
export abstract class TrackNodeContainer {
protected _children: Array<TrackNode> = [];
protected readonly tracksById = new Map<string, TrackNode>();
+ protected abstract fireOnChangeListener(): void;
/**
* True if this node has children, false otherwise.
@@ -110,7 +111,7 @@
addChildLast(child: TrackNode): void {
this.adopt(child);
this._children.push(child);
- raf.scheduleFullRedraw();
+ this.fireOnChangeListener();
}
/**
@@ -121,7 +122,7 @@
addChildFirst(child: TrackNode): void {
this.adopt(child);
this._children.unshift(child);
- raf.scheduleFullRedraw();
+ this.fireOnChangeListener();
}
/**
@@ -132,15 +133,15 @@
* before this node.
*/
addChildBefore(child: TrackNode, referenceNode: TrackNode): void {
- const indexOfReference = this.children.indexOf(referenceNode);
- if (indexOfReference === -1) {
- throw new Error('Reference node is not a child of this node');
- }
+ if (child === referenceNode) return;
+
+ assertTrue(this.children.includes(referenceNode));
this.adopt(child);
+ const indexOfReference = this.children.indexOf(referenceNode);
this._children.splice(indexOfReference, 0, child);
- raf.scheduleFullRedraw();
+ this.fireOnChangeListener();
}
/**
@@ -151,15 +152,15 @@
* after this node.
*/
addChildAfter(child: TrackNode, referenceNode: TrackNode): void {
- const indexOfReference = this.children.indexOf(referenceNode);
- if (indexOfReference === -1) {
- throw new Error('Reference node is not a child of this node');
- }
+ if (child === referenceNode) return;
+
+ assertTrue(this.children.includes(referenceNode));
this.adopt(child);
+ const indexOfReference = this.children.indexOf(referenceNode);
this._children.splice(indexOfReference + 1, 0, child);
- raf.scheduleFullRedraw();
+ this.fireOnChangeListener();
}
/**
@@ -171,7 +172,7 @@
this._children = this.children.filter((x) => child !== x);
child.parent = undefined;
child.id && this.tracksById.delete(child.id);
- raf.scheduleFullRedraw();
+ this.fireOnChangeListener();
}
/**
@@ -189,7 +190,7 @@
clear(): void {
this._children = [];
this.tracksById.clear();
- raf.scheduleFullRedraw();
+ this.fireOnChangeListener();
}
/**
@@ -232,6 +233,7 @@
sortOrder: number;
collapsed: boolean;
isSummary: boolean;
+ removable: boolean;
}
/**
@@ -272,6 +274,11 @@
// vertical space.
public isSummary: boolean;
+ // If true, this node will be removable by the user. It will show a little
+ // close button in the track shell which the user can press to remove the
+ // track from the workspace.
+ public removable: boolean;
+
protected _collapsed = true;
constructor(args?: Partial<TrackNodeArgs>) {
@@ -285,6 +292,7 @@
sortOrder,
collapsed = true,
isSummary = false,
+ removable = false,
} = args ?? {};
this.id = id;
@@ -294,6 +302,7 @@
this.sortOrder = sortOrder;
this.isSummary = isSummary;
this._collapsed = collapsed;
+ this.removable = removable;
}
/**
@@ -327,7 +336,7 @@
* list of that workspace.
*/
get isPinned(): boolean {
- return Boolean(this.workspace?.pinnedTracks.includes(this));
+ return Boolean(this.workspace?.hasPinnedTrack(this));
}
/**
@@ -396,7 +405,7 @@
*/
expand(): void {
this._collapsed = false;
- raf.scheduleFullRedraw();
+ this.fireOnChangeListener();
}
/**
@@ -405,7 +414,7 @@
*/
collapse(): void {
this._collapsed = true;
- raf.scheduleFullRedraw();
+ this.fireOnChangeListener();
}
/**
@@ -413,7 +422,7 @@
*/
toggleCollapsed(): void {
this._collapsed = !this._collapsed;
- raf.scheduleFullRedraw();
+ this.fireOnChangeListener();
}
/**
@@ -448,19 +457,31 @@
}
return fullPath;
}
+
+ protected override fireOnChangeListener(): void {
+ this.workspace?.onchange(this.workspace);
+ }
}
/**
* Defines a workspace containing a track tree and a pinned area.
*/
export class Workspace extends TrackNodeContainer {
- public pinnedTracks: Array<TrackNode> = [];
public title = '<untitled-workspace>';
public readonly id: string;
+ onchange: (w: Workspace) => void = () => {};
+
+ // Dummy node to contain the pinned tracks
+ private pinnedRoot = new TrackNode();
+
+ get pinnedTracks(): ReadonlyArray<TrackNode> {
+ return this.pinnedRoot.children;
+ }
constructor() {
super();
this.id = createSessionUniqueId();
+ this.pinnedRoot.parent = this;
}
/**
@@ -468,25 +489,37 @@
*/
override clear(): void {
super.clear();
- this.pinnedTracks = [];
+ this.pinnedRoot.clear();
}
/**
* Adds a track node to this workspace's pinned area.
*/
pinTrack(track: TrackNode): void {
- // TODO(stevegolton): Check if the track exists in this workspace first
- // otherwise we might get surprises.
- this.pinnedTracks.push(track);
- raf.scheduleFullRedraw();
+ // Make a lightweight clone of this track - just the uri and the title.
+ const cloned = new TrackNode({
+ uri: track.uri,
+ title: track.title,
+ removable: track.removable,
+ });
+ this.pinnedRoot.addChildLast(cloned);
}
/**
* Removes a track node from this workspace's pinned area.
*/
unpinTrack(track: TrackNode): void {
- this.pinnedTracks = this.pinnedTracks.filter((t) => t !== track);
- raf.scheduleFullRedraw();
+ const foundNode = this.pinnedRoot.children.find((t) => t.uri === track.uri);
+ if (foundNode) {
+ this.pinnedRoot.removeChild(foundNode);
+ }
+ }
+
+ /**
+ * Check if this workspace has a pinned track with the same URI as |track|.
+ */
+ hasPinnedTrack(track: TrackNode): boolean {
+ return this.pinnedTracks.some((p) => p.uri === track.uri);
}
/**
@@ -502,4 +535,16 @@
findTrackByUri(uri: string): TrackNode | undefined {
return this.flatTracks.find((t) => t.uri === uri);
}
+
+ /**
+ * Find a track by ID, also searching pinned tracks.
+ */
+ override getTrackById(id: string): TrackNode | undefined {
+ // Also search the pinned tracks
+ return this.pinnedRoot.getTrackById(id) || super.getTrackById(id);
+ }
+
+ protected override fireOnChangeListener(): void {
+ this.onchange(this);
+ }
}
diff --git a/ui/src/test/debug_tracks.test.ts b/ui/src/test/debug_tracks.test.ts
index 98e062c..b24f515 100644
--- a/ui/src/test/debug_tracks.test.ts
+++ b/ui/src/test/debug_tracks.test.ts
@@ -48,7 +48,7 @@
await pth.waitForIdleAndScreenshot('debug track added.png');
// Click on a slice on the debug track.
- await page.mouse.click(1454, 290);
+ await page.mouse.click(590, 180);
await pth.waitForPerfettoIdle();
await pth.waitForIdleAndScreenshot('debug slice clicked.png');
diff --git a/ui/src/test/ftrace_tracks_and_tab.test.ts b/ui/src/test/ftrace_tracks_and_tab.test.ts
index e84f45b..7bed34b 100644
--- a/ui/src/test/ftrace_tracks_and_tab.test.ts
+++ b/ui/src/test/ftrace_tracks_and_tab.test.ts
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {test, expect, Page} from '@playwright/test';
+import {test, Page} from '@playwright/test';
import {PerfettoTestHelper} from './perfetto_ui_test_helper';
test.describe.configure({mode: 'serial'});
@@ -28,14 +28,12 @@
test('ftrace tracks', async () => {
await page.click('h1[ref="Ftrace Events"]');
- await pth.waitForPerfettoIdle();
- await expect(page).toHaveScreenshot('ftrace_events.png');
+ await pth.waitForIdleAndScreenshot('ftrace_events.png');
});
test('ftrace tab', async () => {
await page.mouse.move(0, 0);
await page.click('button[title="More Tabs"]');
await page.getByRole('button', {name: 'Ftrace Events'}).click();
- await pth.waitForPerfettoIdle();
- await expect(page).toHaveScreenshot('ftrace_tab.png');
+ await pth.waitForIdleAndScreenshot('ftrace_tab.png');
});
diff --git a/ui/src/test/load_and_tracks.test.ts b/ui/src/test/load_and_tracks.test.ts
index 6078c67..f02611c 100644
--- a/ui/src/test/load_and_tracks.test.ts
+++ b/ui/src/test/load_and_tracks.test.ts
@@ -31,9 +31,9 @@
});
test('info and stats', async () => {
- await page.locator('.sidebar #info_and_stats').click();
+ await pth.navigate('#!/info');
await pth.waitForIdleAndScreenshot('into_and_stats.png');
- await page.locator('.sidebar #show_timeline').click();
+ await pth.navigate('#!/viewer');
await pth.waitForIdleAndScreenshot('back_to_timeline.png');
});
diff --git a/ui/src/test/local_cache_key.test.ts b/ui/src/test/local_cache_key.test.ts
index 2acd534..3816ea1 100644
--- a/ui/src/test/local_cache_key.test.ts
+++ b/ui/src/test/local_cache_key.test.ts
@@ -24,7 +24,7 @@
'#!/?url=http://127.0.0.1:10000/test/data/perf_sample_annotations.pftrace',
);
const cacheKey1 = page.url().match(/local_cache_key=([a-z0-9-]+)/)![1];
- await expect(page).toHaveScreenshot('trace_1.png');
+ await pth.waitForIdleAndScreenshot('trace_1.png');
// Open second trace.
await pth.navigate(
@@ -32,13 +32,13 @@
);
const cacheKey2 = page.url().match(/local_cache_key=([a-z0-9-]+)/)![1];
expect(cacheKey1).not.toEqual(cacheKey2);
- await expect(page).toHaveScreenshot('trace_2.png');
+ await pth.waitForIdleAndScreenshot('trace_2.png');
// Navigate back to the first trace. A confirmation dialog will be shown
await pth.navigate('#!/viewer?local_cache_key=' + cacheKey1);
- await expect(page).toHaveScreenshot('confirmation_dialog.png');
+ await pth.waitForIdleAndScreenshot('confirmation_dialog.png');
await page.locator('button.modal-btn-primary').click();
await pth.waitForPerfettoIdle();
- await expect(page).toHaveScreenshot('back_to_trace_1.png');
+ await pth.waitForIdleAndScreenshot('back_to_trace_1.png');
});
diff --git a/ui/src/test/perfetto_ui_test_helper.ts b/ui/src/test/perfetto_ui_test_helper.ts
index a709f1f..20e1f82 100644
--- a/ui/src/test/perfetto_ui_test_helper.ts
+++ b/ui/src/test/perfetto_ui_test_helper.ts
@@ -79,7 +79,7 @@
) {
await this.page.mouse.move(0, 0); // Move mouse out of the way.
await this.waitForPerfettoIdle();
- await expect(this.page).toHaveScreenshot(screenshotName, opts);
+ await expect.soft(this.page).toHaveScreenshot(screenshotName, opts);
}
locateTrackGroup(name: string): Locator {
@@ -106,7 +106,7 @@
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async runCommand(cmdId: string, ...args: any[]) {
await this.page.evaluate(
- (arg) => self.globals.commandManager.runCommand(arg.cmdId, ...arg.args),
+ (arg) => self.app.commands.runCommand(arg.cmdId, ...arg.args),
{cmdId, args},
);
}
diff --git a/ui/src/test/queries.test.ts b/ui/src/test/queries.test.ts
index db215a7..3882063 100644
--- a/ui/src/test/queries.test.ts
+++ b/ui/src/test/queries.test.ts
@@ -59,7 +59,7 @@
});
test('query page', async () => {
- await page.locator('.sidebar #query__sql_').click();
+ await pth.navigate('#!/query');
await pth.waitForPerfettoIdle();
const textbox = page.locator('.pf-editor div[role=textbox]');
for (let i = 1; i <= 3; i++) {
diff --git a/ui/src/test/track_event_ordered_tracks.test.ts b/ui/src/test/track_event_ordered_tracks.test.ts
new file mode 100644
index 0000000..8f39a3a
--- /dev/null
+++ b/ui/src/test/track_event_ordered_tracks.test.ts
@@ -0,0 +1,55 @@
+// 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 {test, Page} from '@playwright/test';
+import {PerfettoTestHelper} from './perfetto_ui_test_helper';
+
+test.describe.configure({mode: 'serial'});
+
+let pth: PerfettoTestHelper;
+let page: Page;
+
+test.beforeAll(async ({browser}, _testInfo) => {
+ page = await browser.newPage();
+ pth = new PerfettoTestHelper(page);
+ await pth.openTraceFile('track_event_ordered.pb');
+});
+
+test('load trace', async () => {
+ await pth.waitForIdleAndScreenshot('loaded.png');
+});
+
+test('chronological order', async () => {
+ const chronologicalGrp = pth.locateTrackGroup('Root Chronological');
+ await chronologicalGrp.scrollIntoViewIfNeeded();
+ await pth.toggleTrackGroup(chronologicalGrp);
+
+ await pth.waitForIdleAndScreenshot('chronological.png');
+});
+
+test('explicit order', async () => {
+ const explicitGrp = pth.locateTrackGroup('Root Explicit');
+ await explicitGrp.scrollIntoViewIfNeeded();
+ await pth.toggleTrackGroup(explicitGrp);
+
+ await pth.waitForIdleAndScreenshot('explicit.png');
+});
+
+test('lexicographic tracks', async () => {
+ const lexicographicGrp = pth.locateTrackGroup('Root Lexicographic');
+ await lexicographicGrp.scrollIntoViewIfNeeded();
+ await pth.toggleTrackGroup(lexicographicGrp);
+
+ await pth.waitForIdleAndScreenshot('lexicographic.png');
+});
diff --git a/ui/src/trace_processor/engine.ts b/ui/src/trace_processor/engine.ts
index d4727f3..ccb8a03 100644
--- a/ui/src/trace_processor/engine.ts
+++ b/ui/src/trace_processor/engine.ts
@@ -22,7 +22,7 @@
MetatraceCategories,
QueryArgs,
QueryResult as ProtoQueryResult,
- RegisterSqlModuleArgs,
+ RegisterSqlPackageArgs,
ResetTraceProcessorArgs,
TraceProcessorRpc,
TraceProcessorRpcStream,
@@ -37,6 +37,9 @@
import TPM = TraceProcessorRpc.TraceProcessorMethod;
import {exists, Result} from '../base/utils';
+export type EngineMode = 'WASM' | 'HTTP_RPC';
+export type NewEngineMode = 'USE_HTTP_RPC_IF_AVAILABLE' | 'FORCE_BUILTIN_WASM';
+
// This is used to skip the decoding of queryResult from protobufjs and deal
// with it ourselves. See the comment below around `QueryResult.decode = ...`.
interface QueryResultBypass {
@@ -51,6 +54,7 @@
}
export interface Engine {
+ readonly mode: EngineMode;
readonly engineId: string;
/**
@@ -91,8 +95,12 @@
format: 'json' | 'prototext' | 'proto',
): Promise<string | Uint8Array>;
+ enableMetatrace(categories?: MetatraceCategories): void;
+ stopAndGetMetatrace(): Promise<DisableAndReadMetatraceResult>;
+
getProxy(tag: string): EngineProxy;
readonly numRequestsPending: number;
+ readonly failed: string | undefined;
}
// Abstract interface of a trace proccessor.
@@ -106,8 +114,9 @@
// 1. Implement the abstract rpcSendRequestBytes() function, sending the
// proto-encoded TraceProcessorRpc requests to the TraceProcessor instance.
// 2. Call onRpcResponseBytes() when response data is received.
-export abstract class EngineBase implements Engine {
+export abstract class EngineBase implements Engine, Disposable {
abstract readonly id: string;
+ abstract readonly mode: EngineMode;
private txSeqId = 0;
private rxSeqId = 0;
private rxBuf = new ProtoRingBuffer();
@@ -118,9 +127,10 @@
private pendingRestoreTables = new Array<Deferred<void>>();
private pendingComputeMetrics = new Array<Deferred<string | Uint8Array>>();
private pendingReadMetatrace?: Deferred<DisableAndReadMetatraceResult>;
- private pendingRegisterSqlModule?: Deferred<void>;
+ private pendingRegisterSqlPackage?: Deferred<void>;
private _isMetatracingEnabled = false;
private _numRequestsPending = 0;
+ private _failed: string | undefined = undefined;
// TraceController sets this to raf.scheduleFullRedraw().
onResponseReceived?: () => void;
@@ -180,15 +190,16 @@
const rpc = TraceProcessorRpc.decode(rpcMsgEncoded);
if (rpc.fatalError !== undefined && rpc.fatalError.length > 0) {
- throw new Error(`${rpc.fatalError}`);
+ this.fail(`${rpc.fatalError}`);
}
// Allow restarting sequences from zero (when reloading the browser).
if (rpc.seq !== this.rxSeqId + 1 && this.rxSeqId !== 0 && rpc.seq !== 0) {
// "(ERR:rpc_seq)" is intercepted by error_dialog.ts to show a more
// graceful and actionable error.
- throw new Error(
- `RPC sequence id mismatch cur=${rpc.seq} last=${this.rxSeqId} (ERR:rpc_seq)`,
+ this.fail(
+ `RPC sequence id mismatch ` +
+ `cur=${rpc.seq} last=${this.rxSeqId} (ERR:rpc_seq)`,
);
}
@@ -254,9 +265,9 @@
assertExists(this.pendingReadMetatrace).resolve(metatraceRes);
this.pendingReadMetatrace = undefined;
break;
- case TPM.TPM_REGISTER_SQL_MODULE:
- const registerResult = assertExists(rpc.registerSqlModuleResult);
- const res = assertExists(this.pendingRegisterSqlModule);
+ case TPM.TPM_REGISTER_SQL_PACKAGE:
+ const registerResult = assertExists(rpc.registerSqlPackageResult);
+ const res = assertExists(this.pendingRegisterSqlPackage);
if (exists(registerResult.error) && registerResult.error.length > 0) {
res.reject(registerResult.error);
} else {
@@ -465,22 +476,23 @@
return result;
}
- registerSqlModules(p: {
+ registerSqlPackages(p: {
name: string;
modules: {name: string; sql: string}[];
}): Promise<void> {
- if (this.pendingRegisterSqlModule) {
+ if (this.pendingRegisterSqlPackage) {
return Promise.reject(new Error('Already finalising a metatrace'));
}
const result = defer<void>();
const rpc = TraceProcessorRpc.create();
- rpc.request = TPM.TPM_REGISTER_SQL_MODULE;
- const args = (rpc.registerSqlModuleArgs = new RegisterSqlModuleArgs());
- args.topLevelPackageName = p.name;
+ rpc.request = TPM.TPM_REGISTER_SQL_PACKAGE;
+ const args = (rpc.registerSqlPackageArgs = new RegisterSqlPackageArgs());
+ args.packageName = p.name;
args.modules = p.modules;
- this.pendingRegisterSqlModule = result;
+ args.allowOverride = true;
+ this.pendingRegisterSqlPackage = result;
this.rpcSendRequest(rpc);
return result;
}
@@ -509,6 +521,17 @@
getProxy(tag: string): EngineProxy {
return new EngineProxy(this, tag);
}
+
+ protected fail(reason: string) {
+ this._failed = reason;
+ throw new Error(reason);
+ }
+
+ get failed(): string | undefined {
+ return this._failed;
+ }
+
+ abstract [Symbol.dispose](): void;
}
// Lightweight engine proxy which annotates all queries with a tag
@@ -553,6 +576,14 @@
return this.engine.computeMetric(metrics, format);
}
+ enableMetatrace(categories?: MetatraceCategories): void {
+ this.engine.enableMetatrace(categories);
+ }
+
+ stopAndGetMetatrace(): Promise<DisableAndReadMetatraceResult> {
+ return this.engine.stopAndGetMetatrace();
+ }
+
get engineId(): string {
return this.engine.id;
}
@@ -565,6 +596,14 @@
return this.engine.numRequestsPending;
}
+ get mode() {
+ return this.engine.mode;
+ }
+
+ get failed() {
+ return this.engine.failed;
+ }
+
[Symbol.dispose]() {
this._isAlive = false;
}
diff --git a/ui/src/trace_processor/http_rpc_engine.ts b/ui/src/trace_processor/http_rpc_engine.ts
index f8d5de8..c849299 100644
--- a/ui/src/trace_processor/http_rpc_engine.ts
+++ b/ui/src/trace_processor/http_rpc_engine.ts
@@ -14,7 +14,6 @@
import {fetchWithTimeout} from '../base/http_utils';
import {assertExists} from '../base/logging';
-import {globals} from '../frontend/globals';
import {StatusResult} from '../protos';
import {EngineBase} from '../trace_processor/engine';
@@ -27,11 +26,12 @@
}
export class HttpRpcEngine extends EngineBase {
+ readonly mode = 'HTTP_RPC';
readonly id: string;
- errorHandler: (err: string) => void = () => {};
private requestQueue = new Array<Uint8Array>();
private websocket?: WebSocket;
private connected = false;
+ private disposed = false;
// Can be changed by frontend/index.ts when passing ?rpc_port=1234 .
static rpcPort = '9001';
@@ -43,13 +43,14 @@
rpcSendRequestBytes(data: Uint8Array): void {
if (this.websocket === undefined) {
+ if (this.disposed) return;
const wsUrl = `ws://${HttpRpcEngine.hostAndPort}/websocket`;
this.websocket = new WebSocket(wsUrl);
this.websocket.onopen = () => this.onWebsocketConnected();
this.websocket.onmessage = (e) => this.onWebsocketMessage(e);
this.websocket.onclose = (e) => this.onWebsocketClosed(e);
this.websocket.onerror = (e) =>
- this.errorHandler(
+ super.fail(
`WebSocket error rs=${(e.target as WebSocket)?.readyState} (ERR:ws)`,
);
}
@@ -71,6 +72,7 @@
}
private onWebsocketClosed(e: CloseEvent) {
+ if (this.disposed) return;
if (e.code === 1006 && this.connected) {
// On macbooks the act of closing the lid / suspending often causes socket
// disconnections. Try to gracefully re-connect.
@@ -79,7 +81,7 @@
this.connected = false;
this.rpcSendRequestBytes(new Uint8Array()); // Triggers a reconnection.
} else {
- this.errorHandler(`Websocket closed (${e.code}: ${e.reason}) (ERR:ws)`);
+ super.fail(`Websocket closed (${e.code}: ${e.reason}) (ERR:ws)`);
}
}
@@ -92,9 +94,6 @@
}
static async checkConnection(): Promise<HttpRpcState> {
- // Avoid ERR_CONNECTION_REFUSED errors on tests.
- if (globals.testing) return {connected: false};
-
const RPC_URL = `http://${HttpRpcEngine.hostAndPort}/`;
const httpRpcState: HttpRpcState = {connected: false};
console.info(
@@ -124,4 +123,11 @@
static get hostAndPort() {
return `127.0.0.1:${HttpRpcEngine.rpcPort}`;
}
+
+ [Symbol.dispose]() {
+ this.disposed = true;
+ const websocket = this.websocket;
+ this.websocket = undefined;
+ websocket?.close();
+ }
}
diff --git a/ui/src/trace_processor/query_result.ts b/ui/src/trace_processor/query_result.ts
index c65f874..b12e06c 100644
--- a/ui/src/trace_processor/query_result.ts
+++ b/ui/src/trace_processor/query_result.ts
@@ -281,6 +281,9 @@
// first result.
firstRow<T extends Row>(spec: T): T;
+ // Like firstRow() but returns undefined if no rows are available.
+ maybeFirstRow<T extends Row>(spec: T): T | undefined;
+
// If != undefined the query errored out and error() contains the message.
error(): string | undefined;
@@ -406,6 +409,14 @@
return impl as {} as RowIterator<T> as T;
}
+ maybeFirstRow<T extends Row>(spec: T): T | undefined {
+ const impl = new RowIteratorImplWithRowData(spec, this);
+ if (!impl.valid()) {
+ return undefined;
+ }
+ return impl as {} as RowIterator<T> as T;
+ }
+
// Can be called only once.
waitAllRows(): Promise<QueryResult> {
assertTrue(this.allRowsPromise === undefined);
@@ -937,6 +948,9 @@
firstRow<T extends Row>(spec: T) {
return this.impl.firstRow(spec);
}
+ maybeFirstRow<T extends Row>(spec: T) {
+ return this.impl.maybeFirstRow(spec);
+ }
waitAllRows() {
return this.impl.waitAllRows();
}
diff --git a/ui/src/common/internal_layout_utils.ts b/ui/src/trace_processor/sql_utils/layout.ts
similarity index 100%
rename from ui/src/common/internal_layout_utils.ts
rename to ui/src/trace_processor/sql_utils/layout.ts
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/trace_processor/wasm_engine_proxy.ts b/ui/src/trace_processor/wasm_engine_proxy.ts
index a8f815d..123cb86 100644
--- a/ui/src/trace_processor/wasm_engine_proxy.ts
+++ b/ui/src/trace_processor/wasm_engine_proxy.ts
@@ -12,49 +12,44 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+import {assetSrc} from '../base/assets';
import {assertExists, assertTrue} from '../base/logging';
import {EngineBase} from '../trace_processor/engine';
-let bundlePath: string;
let idleWasmWorker: Worker;
-let activeWasmWorker: Worker;
-export function initWasm(root: string) {
- bundlePath = root + 'engine_bundle.js';
- idleWasmWorker = new Worker(bundlePath);
-}
-
-// This method is called trace_controller whenever a new trace is loaded.
-export function resetEngineWorker(): MessagePort {
- const channel = new MessageChannel();
- const port = channel.port1;
-
- // We keep always an idle worker around, the first one is created by the
- // main() below, so we can hide the latency of the Wasm initialization.
- if (activeWasmWorker !== undefined) {
- activeWasmWorker.terminate();
- }
-
- // Swap the active worker with the idle one and create a new idle worker
- // for the next trace.
- activeWasmWorker = assertExists(idleWasmWorker);
- activeWasmWorker.postMessage(port, [port]);
- idleWasmWorker = new Worker(bundlePath);
- return channel.port2;
+export function initWasm() {
+ idleWasmWorker = new Worker(assetSrc('engine_bundle.js'));
}
/**
* This implementation of Engine uses a WASM backend hosted in a separate
- * worker thread.
+ * worker thread. The entrypoint of the worker thread is engine/index.ts.
*/
-export class WasmEngineProxy extends EngineBase {
+export class WasmEngineProxy extends EngineBase implements Disposable {
+ readonly mode = 'WASM';
readonly id: string;
private port: MessagePort;
+ private worker: Worker;
- constructor(id: string, port: MessagePort) {
+ constructor(id: string) {
super();
this.id = id;
- this.port = port;
+
+ const channel = new MessageChannel();
+ const port1 = channel.port1;
+ this.port = channel.port2;
+
+ // We keep an idle instance around to hide the latency of initializing the
+ // instance. Creating the worker (new Worker()) is ~instantaneous, but then
+ // the initialization in the worker thread (i.e. the call to
+ // `new WasmBridge()` that engine/index.ts makes) takes several seconds.
+ // Here we hide that initialization latency by always keeping an idle worker
+ // around. The latency is hidden by the fact that the user usually takes few
+ // seconds until they click on "open trace file" and pick a file.
+ this.worker = assertExists(idleWasmWorker);
+ idleWasmWorker = new Worker(assetSrc('engine_bundle.js'));
+ this.worker.postMessage(port1, [port1]);
this.port.onmessage = this.onMessage.bind(this);
}
@@ -69,4 +64,8 @@
// TypedArray for each decode operation would be too expensive).
this.port.postMessage(data);
}
+
+ [Symbol.dispose]() {
+ this.worker.terminate();
+ }
}
diff --git a/ui/src/traceconv/index.ts b/ui/src/traceconv/index.ts
index 9181920..cc721ba 100644
--- a/ui/src/traceconv/index.ts
+++ b/ui/src/traceconv/index.ts
@@ -20,10 +20,6 @@
reportError,
} from '../base/logging';
import {time} from '../base/time';
-import {
- ConversionJobName,
- ConversionJobStatus,
-} from '../common/conversion_jobs';
import traceconv from '../gen/traceconv';
const selfWorker = self as {} as Worker;
@@ -44,12 +40,8 @@
});
}
-function updateJobStatus(name: ConversionJobName, status: ConversionJobStatus) {
- selfWorker.postMessage({
- kind: 'updateJobStatus',
- name,
- status,
- });
+function notifyJobCompleted() {
+ selfWorker.postMessage({kind: 'jobCompleted'});
}
function downloadFile(buffer: Uint8Array, name: string) {
@@ -131,8 +123,6 @@
format: Format,
truncate?: 'start' | 'end',
): Promise<void> {
- const jobName = format === 'json' ? 'convert_json' : 'convert_systrace';
- updateJobStatus(jobName, ConversionJobStatus.InProgress);
const outPath = '/trace.json';
const args: string[] = [format];
if (truncate !== undefined) {
@@ -145,7 +135,7 @@
downloadFile(fsNodeToBuffer(fsNode), `trace.${format}`);
module.FS.unlink(outPath);
} finally {
- updateJobStatus(jobName, ConversionJobStatus.NotRunning);
+ notifyJobCompleted();
}
}
@@ -168,8 +158,6 @@
trace: Blob,
truncate?: 'start' | 'end',
) {
- const jobName = 'open_in_legacy';
- updateJobStatus(jobName, ConversionJobStatus.InProgress);
const outPath = '/trace.json';
const args: string[] = ['json'];
if (truncate !== undefined) {
@@ -185,7 +173,7 @@
openTraceInLegacy(buffer);
module.FS.unlink(outPath);
} finally {
- updateJobStatus(jobName, ConversionJobStatus.NotRunning);
+ notifyJobCompleted();
}
}
@@ -204,8 +192,6 @@
}
async function ConvertTraceToPprof(trace: Blob, pid: number, ts: time) {
- const jobName = 'convert_pprof';
- updateJobStatus(jobName, ConversionJobStatus.InProgress);
const args = [
'profile',
`--pid`,
@@ -232,7 +218,7 @@
downloadFile(fsNodeToBuffer(fileNode), fileName);
}
} finally {
- updateJobStatus(jobName, ConversionJobStatus.NotRunning);
+ notifyJobCompleted();
}
}
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 192bb85..3c831dd 100644
--- a/ui/src/widgets/flamegraph.ts
+++ b/ui/src/widgets/flamegraph.ts
@@ -24,6 +24,7 @@
import {Spinner} from './spinner';
import {TagInput} from './tag_input';
import {SegmentedButtons} from './segmented_buttons';
+import {z} from 'zod';
const LABEL_FONT_STYLE = '12px Roboto';
const NODE_HEIGHT = 20;
@@ -97,42 +98,56 @@
readonly maxDepth: number;
}
-export interface FlamegraphTopDown {
- readonly kind: 'TOP_DOWN';
-}
+const FLAMEGRAPH_FILTER_SCHEMA = z
+ .object({
+ kind: z
+ .union([
+ z.literal('SHOW_STACK').readonly(),
+ z.literal('HIDE_STACK').readonly(),
+ z.literal('SHOW_FROM_FRAME').readonly(),
+ z.literal('HIDE_FRAME').readonly(),
+ ])
+ .readonly(),
+ filter: z.string().readonly(),
+ })
+ .readonly();
-export interface FlamegraphBottomUp {
- readonly kind: 'BOTTOM_UP';
-}
+type FlamegraphFilter = z.infer<typeof FLAMEGRAPH_FILTER_SCHEMA>;
-export interface FlamegraphPivot {
- readonly kind: 'PIVOT';
- readonly pivot: string;
-}
+const FLAMEGRAPH_VIEW_SCHEMA = z
+ .discriminatedUnion('kind', [
+ z.object({kind: z.literal('TOP_DOWN').readonly()}),
+ z.object({kind: z.literal('BOTTOM_UP').readonly()}),
+ z.object({
+ kind: z.literal('PIVOT').readonly(),
+ pivot: z.string().readonly(),
+ }),
+ ])
+ .readonly();
-export type FlamegraphView =
- | FlamegraphTopDown
- | FlamegraphBottomUp
- | FlamegraphPivot;
+export type FlamegraphView = z.infer<typeof FLAMEGRAPH_VIEW_SCHEMA>;
-export interface FlamegraphFilters {
- readonly showStack: ReadonlyArray<string>;
- readonly hideStack: ReadonlyArray<string>;
- readonly showFromFrame: ReadonlyArray<string>;
- readonly hideFrame: ReadonlyArray<string>;
- readonly view: FlamegraphView;
+export const FLAMEGRAPH_STATE_SCHEMA = z
+ .object({
+ selectedMetricName: z.string().readonly(),
+ filters: z.array(FLAMEGRAPH_FILTER_SCHEMA).readonly(),
+ view: FLAMEGRAPH_VIEW_SCHEMA,
+ })
+ .readonly();
+
+export type FlamegraphState = z.infer<typeof FLAMEGRAPH_STATE_SCHEMA>;
+
+interface FlamegraphMetric {
+ readonly name: string;
+ readonly unit: string;
}
export interface FlamegraphAttrs {
- readonly metrics: ReadonlyArray<{
- readonly name: string;
- readonly unit: string;
- }>;
- readonly selectedMetricName: string;
+ readonly metrics: ReadonlyArray<FlamegraphMetric>;
+ readonly state: FlamegraphState;
readonly data: FlamegraphQueryData | undefined;
- readonly onMetricChange: (metricName: string) => void;
- readonly onFiltersChanged: (filters: FlamegraphFilters) => void;
+ readonly onStateChange: (filters: FlamegraphState) => void;
}
/*
@@ -150,21 +165,15 @@
*
* ```
* const metrics = [...];
- * const selectedMetricName = ...;
- * const filters = ...;
- * const data = ...;
+ * let state = ...;
+ * let data = ...;
*
* m(Flamegraph, {
* metrics,
- * selectedMetricName,
- * onMetricChange: (metricName) => {
- * selectedMetricName = metricName;
- * data = undefined;
- * fetchData();
- * },
+ * state,
* data,
- * onFiltersChanged: (showStack, hideStack, hideFrame) => {
- * updateFilters(showStack, hideStack, hideFrame);
+ * onStateChange: (newState) => {
+ * state = newState,
* data = undefined;
* fetchData();
* },
@@ -175,9 +184,7 @@
private attrs: FlamegraphAttrs;
private rawFilterText: string = '';
- private rawFilters: ReadonlyArray<string> = [];
private filterFocus: boolean = false;
- private switchState: 'TOP_DOWN' | 'BOTTOM_UP' = 'TOP_DOWN';
private dataChangeMonitor = new Monitor([() => this.attrs.data]);
private zoomRegion?: ZoomRegion;
@@ -354,6 +361,16 @@
this.drawCanvas(dom);
}
+ static createDefaultState(
+ metrics: ReadonlyArray<FlamegraphMetric>,
+ ): FlamegraphState {
+ return {
+ selectedMetricName: metrics[0].name,
+ filters: [],
+ view: {kind: 'TOP_DOWN'},
+ };
+ }
+
private drawCanvas(dom: Element) {
// TODO(lalitm): consider migrating to VirtualCanvas to improve performance here.
const canvasContainer = findRef(dom, 'canvas-container');
@@ -489,10 +506,13 @@
m(
Select,
{
- value: attrs.selectedMetricName,
+ value: attrs.state.selectedMetricName,
onchange: (e: Event) => {
const el = e.target as HTMLSelectElement;
- attrs.onMetricChange(el.value);
+ attrs.onStateChange({
+ ...self.attrs.state,
+ selectedMetricName: el.value,
+ });
scheduleFullRedraw();
},
},
@@ -504,30 +524,31 @@
Popup,
{
trigger: m(TagInput, {
- tags: this.rawFilters,
+ tags: toTags(self.attrs.state),
value: this.rawFilterText,
onChange: (value: string) => {
self.rawFilterText = value;
scheduleFullRedraw();
},
onTagAdd: (tag: string) => {
- self.rawFilters = addFilter(
- self.rawFilters,
- normalizeFilter(tag),
- );
self.rawFilterText = '';
- self.attrs.onFiltersChanged(
- computeFilters(self.switchState, self.rawFilters),
- );
+ self.attrs.onStateChange(updateState(self.attrs.state, tag));
scheduleFullRedraw();
},
onTagRemove(index: number) {
- const filters = Array.from(self.rawFilters);
- filters.splice(index, 1);
- self.rawFilters = filters;
- self.attrs.onFiltersChanged(
- computeFilters(self.switchState, self.rawFilters),
- );
+ if (index === self.attrs.state.filters.length) {
+ self.attrs.onStateChange({
+ ...self.attrs.state,
+ view: {kind: 'TOP_DOWN'},
+ });
+ } else {
+ const filters = Array.from(self.attrs.state.filters);
+ filters.splice(index, 1);
+ self.attrs.onStateChange({
+ ...self.attrs.state,
+ filters,
+ });
+ }
scheduleFullRedraw();
},
onfocus() {
@@ -545,15 +566,15 @@
),
m(SegmentedButtons, {
options: [{label: 'Top Down'}, {label: 'Bottom Up'}],
- selectedOption: this.switchState === 'TOP_DOWN' ? 0 : 1,
+ selectedOption: this.attrs.state.view.kind === 'TOP_DOWN' ? 0 : 1,
onOptionSelected: (num) => {
- this.switchState = num === 0 ? 'TOP_DOWN' : 'BOTTOM_UP';
- self.attrs.onFiltersChanged(
- computeFilters(self.switchState, self.rawFilters),
- );
+ self.attrs.onStateChange({
+ ...this.attrs.state,
+ view: {kind: num === 0 ? 'TOP_DOWN' : 'BOTTOM_UP'},
+ });
scheduleFullRedraw();
},
- disabled: hasPivot(this.rawFilters),
+ disabled: this.attrs.state.view.kind === 'PIVOT',
}),
);
}
@@ -597,11 +618,8 @@
parentCumulativeValue,
properties,
} = nodes[queryIdx];
- const filterButtonClick = (filter: string) => {
- this.rawFilters = addFilter(this.rawFilters, filter);
- this.attrs.onFiltersChanged(
- computeFilters(this.switchState, this.rawFilters),
- );
+ const filterButtonClick = (state: FlamegraphState) => {
+ this.attrs.onStateChange(state);
this.tooltipPos = undefined;
scheduleFullRedraw();
};
@@ -665,31 +683,54 @@
m(Button, {
label: 'Show Stack',
onclick: () => {
- filterButtonClick(`Show Stack: ^${name}$`);
+ filterButtonClick(
+ addFilter(this.attrs.state, {
+ kind: 'SHOW_STACK',
+ filter: `^${name}$`,
+ }),
+ );
},
}),
m(Button, {
label: 'Hide Stack',
onclick: () => {
- filterButtonClick(`Hide Stack: ^${name}$`);
+ filterButtonClick(
+ addFilter(this.attrs.state, {
+ kind: 'HIDE_STACK',
+ filter: `^${name}$`,
+ }),
+ );
},
}),
m(Button, {
label: 'Hide Frame',
onclick: () => {
- filterButtonClick(`Hide Frame: ^${name}$`);
+ filterButtonClick(
+ addFilter(this.attrs.state, {
+ kind: 'HIDE_FRAME',
+ filter: `^${name}$`,
+ }),
+ );
},
}),
m(Button, {
label: 'Show From Frame',
onclick: () => {
- filterButtonClick(`Show From Frame: ^${name}$`);
+ filterButtonClick(
+ addFilter(this.attrs.state, {
+ kind: 'SHOW_FROM_FRAME',
+ filter: `^${name}$`,
+ }),
+ );
},
}),
m(Button, {
label: 'Pivot',
onclick: () => {
- filterButtonClick(`Pivot: ^${name}$`);
+ filterButtonClick({
+ ...this.attrs.state,
+ view: {kind: 'PIVOT', pivot: name},
+ });
},
}),
),
@@ -698,7 +739,7 @@
private get selectedMetric() {
return this.attrs.metrics.find(
- (x) => x.name === this.attrs.selectedMetricName,
+ (x) => x.name === this.attrs.state.selectedMetricName,
);
}
}
@@ -895,73 +936,67 @@
return `${((size / totalSize) * 100.0).toFixed(2)}%`;
}
-function normalizeFilter(filter: string): string {
+function updateState(state: FlamegraphState, filter: string): FlamegraphState {
const lwr = filter.toLowerCase();
if (lwr.startsWith('ss: ') || lwr.startsWith('show stack: ')) {
- return 'Show Stack: ' + filter.split(': ', 2)[1];
+ return addFilter(state, {
+ kind: 'SHOW_STACK',
+ filter: filter.split(': ', 2)[1],
+ });
} else if (lwr.startsWith('hs: ') || lwr.startsWith('hide stack: ')) {
- return 'Hide Stack: ' + filter.split(': ', 2)[1];
+ return addFilter(state, {
+ kind: 'HIDE_STACK',
+ filter: filter.split(': ', 2)[1],
+ });
} else if (lwr.startsWith('sff: ') || lwr.startsWith('show from frame: ')) {
- return 'Show From Frame: ' + filter.split(': ', 2)[1];
+ return addFilter(state, {
+ kind: 'SHOW_FROM_FRAME',
+ filter: filter.split(': ', 2)[1],
+ });
} else if (lwr.startsWith('hf: ') || lwr.startsWith('hide frame: ')) {
- return 'Hide Frame: ' + filter.split(': ', 2)[1];
+ return addFilter(state, {
+ kind: 'HIDE_FRAME',
+ filter: filter.split(': ', 2)[1],
+ });
} else if (lwr.startsWith('p:') || lwr.startsWith('pivot: ')) {
- return 'Pivot: ' + filter.split(': ', 2)[1];
+ return {
+ ...state,
+ view: {kind: 'PIVOT', pivot: filter.split(': ', 2)[1]},
+ };
}
- return 'Show Stack: ' + filter;
+ return addFilter(state, {
+ kind: 'SHOW_STACK',
+ filter: filter,
+ });
}
-function addFilter(filters: ReadonlyArray<string>, filter: string): string[] {
- if (filter.startsWith('Pivot: ')) {
- return [...filters.filter((x) => !x.startsWith('Pivot: ')), filter];
- }
- return [...filters, filter];
-}
-
-function computeFilters(
- switchState: 'TOP_DOWN' | 'BOTTOM_UP',
- rawFilters: readonly string[],
-): FlamegraphFilters {
- const showStack = rawFilters
- .filter((x) => x.startsWith('Show Stack: '))
- .map((x) => x.split(': ', 2)[1]);
- assertTrue(
- showStack.length < 32,
- 'More than 32 show stack filters is not supported',
- );
-
- const showFromFrame = rawFilters
- .filter((x) => x.startsWith('Show From Frame: '))
- .map((x) => x.split(': ', 2)[1]);
- assertTrue(
- showFromFrame.length < 32,
- 'More than 32 show from frame filters is not supported',
- );
-
- const pivot = rawFilters.filter((x) => x.startsWith('Pivot: '));
- assertTrue(pivot.length <= 1, 'Only one pivot can be active');
-
- const view: FlamegraphView =
- pivot.length === 0
- ? {kind: switchState}
- : {kind: 'PIVOT', pivot: pivot[0].split(': ', 2)[1]};
- return {
- showStack,
- hideStack: rawFilters
- .filter((x) => x.startsWith('Hide Stack: '))
- .map((x) => x.split(': ', 2)[1]),
- showFromFrame,
- hideFrame: rawFilters
- .filter((x) => x.startsWith('Hide Frame: '))
- .map((x) => x.split(': ', 2)[1]),
- view,
+function toTags(state: FlamegraphState): ReadonlyArray<string> {
+ const toString = (x: FlamegraphFilter) => {
+ switch (x.kind) {
+ case 'HIDE_FRAME':
+ return 'Hide Frame: ' + x.filter;
+ case 'HIDE_STACK':
+ return 'Hide Stack: ' + x.filter;
+ case 'SHOW_FROM_FRAME':
+ return 'Show From Frame: ' + x.filter;
+ case 'SHOW_STACK':
+ return 'Show Stack: ' + x.filter;
+ }
};
+ const filters = state.filters.map((x) => toString(x));
+ return filters.concat(
+ state.view.kind === 'PIVOT' ? ['Pivot: ' + state.view.pivot] : [],
+ );
}
-function hasPivot(rawFilters: readonly string[]) {
- const pivot = rawFilters.filter((x) => x.startsWith('Pivot: '));
- assertTrue(pivot.length <= 1, 'Only one pivot can be active');
- return pivot.length === 1;
+function addFilter(
+ state: FlamegraphState,
+ filter: FlamegraphFilter,
+): FlamegraphState {
+ return {
+ ...state,
+ filters: state.filters.concat([filter]),
+ };
}
function generateColor(name: string, greyed: boolean, hovered: boolean) {
diff --git a/ui/src/frontend/tables/table.ts b/ui/src/widgets/table.ts
similarity index 77%
rename from ui/src/frontend/tables/table.ts
rename to ui/src/widgets/table.ts
index b145c4d..1389907 100644
--- a/ui/src/frontend/tables/table.ts
+++ b/ui/src/widgets/table.ts
@@ -13,7 +13,7 @@
// limitations under the License.
import m from 'mithril';
-import {allUnique, range} from '../../base/array_utils';
+import {allUnique, range} from '../base/array_utils';
import {
compareUniversal,
comparingBy,
@@ -21,18 +21,29 @@
SortableValue,
SortDirection,
withDirection,
-} from '../../base/comparison_utils';
-import {raf} from '../../core/raf_scheduler';
-import {
- menuItem,
- PopupMenuButton,
- popupMenuIcon,
- PopupMenuItem,
-} from '../popup_menu';
+} from '../base/comparison_utils';
+import {scheduleFullRedraw} from './raf';
+import {MenuItem, PopupMenu2} from './menu';
+import {Button} from './button';
+
+// For a table column that can be sorted; the standard popup icon should
+// reflect the current sorting direction. This function returns an icon
+// corresponding to optional SortDirection according to which the column is
+// sorted. (Optional because column might be unsorted)
+export function popupMenuIcon(sortDirection?: SortDirection) {
+ switch (sortDirection) {
+ case undefined:
+ return 'more_horiz';
+ case 'DESC':
+ return 'arrow_drop_down';
+ case 'ASC':
+ return 'arrow_drop_up';
+ }
+}
export interface ColumnDescriptorAttrs<T> {
// Context menu items displayed on the column header.
- contextMenu?: PopupMenuItem[];
+ contextMenu?: m.Child[];
// Unique column ID, used to identify which column is currently sorted.
columnId?: string;
@@ -49,7 +60,7 @@
name: string;
render: (row: T) => m.Child;
id: string;
- contextMenu?: PopupMenuItem[];
+ contextMenu?: m.Child[];
ordering?: ComparisonFn<T>;
constructor(
@@ -81,7 +92,7 @@
export function numberColumn<T>(
name: string,
getter: (t: T) => number,
- contextMenu?: PopupMenuItem[],
+ contextMenu?: m.Child[],
): ColumnDescriptor<T> {
return new ColumnDescriptor<T>(name, getter, {contextMenu, sortKey: getter});
}
@@ -89,7 +100,7 @@
export function stringColumn<T>(
name: string,
getter: (t: T) => string,
- contextMenu?: PopupMenuItem[],
+ contextMenu?: m.Child[],
): ColumnDescriptor<T> {
return new ColumnDescriptor<T>(name, getter, {contextMenu, sortKey: getter});
}
@@ -136,13 +147,13 @@
if (this._sortingInfo !== undefined) {
this.reorder(this._sortingInfo);
}
- raf.scheduleFullRedraw();
+ scheduleFullRedraw();
}
resetOrder() {
this.permutation = range(this.data.length);
this._sortingInfo = undefined;
- raf.scheduleFullRedraw();
+ scheduleFullRedraw();
}
get sortingInfo(): SortingInfo<T> | undefined {
@@ -157,7 +168,7 @@
info.direction,
),
);
- raf.scheduleFullRedraw();
+ scheduleFullRedraw();
}
}
@@ -191,33 +202,42 @@
if (column.ordering !== undefined) {
const ordering = column.ordering;
currDirection = directionOnIndex(column.id, vnode.attrs.data.sortingInfo);
- const newItems: PopupMenuItem[] = [];
+ const newItems: m.Child[] = [];
if (currDirection !== 'ASC') {
newItems.push(
- menuItem('Sort ascending', () => {
- vnode.attrs.data.reorder({
- columnId: column.id,
- direction: 'ASC',
- ordering,
- });
+ m(MenuItem, {
+ label: 'Sort ascending',
+ onclick: () => {
+ vnode.attrs.data.reorder({
+ columnId: column.id,
+ direction: 'ASC',
+ ordering,
+ });
+ },
}),
);
}
if (currDirection !== 'DESC') {
newItems.push(
- menuItem('Sort descending', () => {
- vnode.attrs.data.reorder({
- columnId: column.id,
- direction: 'DESC',
- ordering,
- });
+ m(MenuItem, {
+ label: 'Sort descending',
+ onclick: () => {
+ vnode.attrs.data.reorder({
+ columnId: column.id,
+ direction: 'DESC',
+ ordering,
+ });
+ },
}),
);
}
if (currDirection !== undefined) {
newItems.push(
- menuItem('Restore original order', () => {
- vnode.attrs.data.resetOrder();
+ m(MenuItem, {
+ label: 'Restore original order',
+ onclick: () => {
+ vnode.attrs.data.resetOrder();
+ },
}),
);
}
@@ -227,12 +247,14 @@
return m(
'td',
column.name,
- items === undefined
- ? null
- : m(PopupMenuButton, {
- icon: popupMenuIcon(currDirection),
- items,
- }),
+ items &&
+ m(
+ PopupMenu2,
+ {
+ trigger: m(Button, {icon: popupMenuIcon(currDirection)}),
+ },
+ items,
+ ),
);
}
diff --git a/ui/src/widgets/track_widget.ts b/ui/src/widgets/track_widget.ts
index f0edc21..2369e0d 100644
--- a/ui/src/widgets/track_widget.ts
+++ b/ui/src/widgets/track_widget.ts
@@ -17,12 +17,10 @@
import {currentTargetOffset} from '../base/dom_utils';
import {Bounds2D, Point2D, Vector2D} from '../base/geom';
import {Icons} from '../base/semantic_icons';
-import {Button, ButtonBar} from './button';
+import {ButtonBar} from './button';
import {Chip, ChipBar} from './chip';
-import {Intent} from './common';
import {Icon} from './icon';
import {MiddleEllipsis} from './middle_ellipsis';
-import {Popup} from './popup';
import {clamp} from '../base/math_utils';
/**
@@ -42,40 +40,6 @@
* └──────────────────────────────────────────────────────────────────┘
*/
-export interface CrashButtonAttrs {
- error: Error;
-}
-
-export class CrashButton implements m.ClassComponent<CrashButtonAttrs> {
- view({attrs}: m.Vnode<CrashButtonAttrs>): m.Children {
- return m(
- Popup,
- {
- trigger: m(Button, {
- icon: Icons.Crashed,
- compact: true,
- }),
- },
- this.renderErrorMessage(attrs.error),
- );
- }
-
- private renderErrorMessage(error: Error): m.Children {
- return m(
- '',
- 'This track has crashed',
- m(Button, {
- label: 'Re-raise exception',
- intent: Intent.Primary,
- className: Popup.DISMISS_POPUP_GROUP_CLASS,
- onclick: () => {
- throw error;
- },
- }),
- );
- }
-}
-
export interface TrackComponentAttrs {
// The title of this track.
readonly title: string;
@@ -98,8 +62,8 @@
// Optional list of chips to display after the track title.
readonly chips?: ReadonlyArray<string>;
- // Optional error to display on this track.
- readonly error?: Error | undefined;
+ // Render this track in error colours.
+ readonly error?: boolean;
// The integer indentation level of this track. If omitted, defaults to 0.
readonly indentationLevel?: number;
@@ -123,6 +87,10 @@
// Whether to highlight the track or not.
readonly highlight?: boolean;
+ // Whether the shell should be draggable and emit drag/drop events.
+ readonly reorderable?: boolean;
+
+ // Mouse events.
readonly onTrackContentMouseMove?: (
pos: Point2D,
contentSize: Bounds2D,
@@ -132,6 +100,11 @@
pos: Point2D,
contentSize: Bounds2D,
) => boolean;
+
+ // If reorderable, these functions will be called when track shells are
+ // dragged and dropped.
+ readonly onMoveBefore?: (nodeId: string) => void;
+ readonly onMoveAfter?: (nodeId: string) => void;
}
const TRACK_HEIGHT_MIN_PX = 18;
@@ -212,10 +185,18 @@
),
);
- const {topOffsetPx = 0, collapsible, collapsed} = attrs;
+ const {
+ id,
+ topOffsetPx = 0,
+ collapsible,
+ collapsed,
+ reorderable = false,
+ onMoveAfter = () => {},
+ onMoveBefore = () => {},
+ } = attrs;
return m(
- '.pf-track-shell',
+ `.pf-track-shell[data-track-node-id=${id}]`,
{
className: classNames(collapsible && 'pf-clickable'),
onclick: (e: MouseEvent) => {
@@ -226,6 +207,52 @@
attrs.onToggleCollapsed?.();
}
},
+ draggable: reorderable,
+ ondragstart: (e: DragEvent) => {
+ e.dataTransfer?.setData('text/plain', id);
+ },
+ ondragover: (e: DragEvent) => {
+ if (!reorderable) {
+ return;
+ }
+ const target = e.currentTarget as HTMLElement;
+ const threshold = target.offsetHeight / 2;
+ if (e.offsetY > threshold) {
+ target.classList.remove('pf-drag-before');
+ target.classList.add('pf-drag-after');
+ } else {
+ target.classList.remove('pf-drag-after');
+ target.classList.add('pf-drag-before');
+ }
+ },
+ ondragleave: (e: DragEvent) => {
+ if (!reorderable) {
+ return;
+ }
+ const target = e.currentTarget as HTMLElement;
+ const related = e.relatedTarget as HTMLElement | null;
+ if (related && !target.contains(related)) {
+ target.classList.remove('pf-drag-after');
+ target.classList.remove('pf-drag-before');
+ }
+ },
+ ondrop: (e: DragEvent) => {
+ if (!reorderable) {
+ return;
+ }
+ const id = e.dataTransfer?.getData('text/plain');
+ const target = e.currentTarget as HTMLElement;
+ const threshold = target.offsetHeight / 2;
+ if (id !== undefined) {
+ if (e.offsetY > threshold) {
+ onMoveAfter(id);
+ } else {
+ onMoveBefore(id);
+ }
+ }
+ target.classList.remove('pf-drag-after');
+ target.classList.remove('pf-drag-before');
+ },
},
m(
'.pf-track-menubar',
@@ -257,7 +284,6 @@
onclick: (e: MouseEvent) => e.stopPropagation(),
},
attrs.buttons,
- attrs.error && m(CrashButton, {error: attrs.error}),
),
),
);