Merge "Merge android14-tests-dev" into main
diff --git a/Android.bp b/Android.bp
index d668c2b..2b0c301 100644
--- a/Android.bp
+++ b/Android.bp
@@ -65,6 +65,7 @@
         ":perfetto_protos_perfetto_ipc_wire_protocol_cpp_gen",
         ":perfetto_protos_perfetto_trace_android_zero_gen",
         ":perfetto_protos_perfetto_trace_chrome_zero_gen",
+        ":perfetto_protos_perfetto_trace_etw_zero_gen",
         ":perfetto_protos_perfetto_trace_filesystem_zero_gen",
         ":perfetto_protos_perfetto_trace_ftrace_zero_gen",
         ":perfetto_protos_perfetto_trace_gpu_zero_gen",
@@ -151,6 +152,7 @@
         "perfetto_protos_perfetto_ipc_wire_protocol_cpp_gen_headers",
         "perfetto_protos_perfetto_trace_android_zero_gen_headers",
         "perfetto_protos_perfetto_trace_chrome_zero_gen_headers",
+        "perfetto_protos_perfetto_trace_etw_zero_gen_headers",
         "perfetto_protos_perfetto_trace_filesystem_zero_gen_headers",
         "perfetto_protos_perfetto_trace_ftrace_zero_gen_headers",
         "perfetto_protos_perfetto_trace_gpu_zero_gen_headers",
@@ -331,6 +333,7 @@
         ":perfetto_protos_perfetto_ipc_wire_protocol_cpp_gen",
         ":perfetto_protos_perfetto_trace_android_zero_gen",
         ":perfetto_protos_perfetto_trace_chrome_zero_gen",
+        ":perfetto_protos_perfetto_trace_etw_zero_gen",
         ":perfetto_protos_perfetto_trace_filesystem_zero_gen",
         ":perfetto_protos_perfetto_trace_ftrace_zero_gen",
         ":perfetto_protos_perfetto_trace_gpu_zero_gen",
@@ -426,6 +429,7 @@
         "perfetto_protos_perfetto_ipc_wire_protocol_cpp_gen_headers",
         "perfetto_protos_perfetto_trace_android_zero_gen_headers",
         "perfetto_protos_perfetto_trace_chrome_zero_gen_headers",
+        "perfetto_protos_perfetto_trace_etw_zero_gen_headers",
         "perfetto_protos_perfetto_trace_filesystem_zero_gen_headers",
         "perfetto_protos_perfetto_trace_ftrace_zero_gen_headers",
         "perfetto_protos_perfetto_trace_gpu_zero_gen_headers",
@@ -534,6 +538,7 @@
         ":perfetto_protos_perfetto_ipc_wire_protocol_cpp_gen",
         ":perfetto_protos_perfetto_trace_android_zero_gen",
         ":perfetto_protos_perfetto_trace_chrome_zero_gen",
+        ":perfetto_protos_perfetto_trace_etw_zero_gen",
         ":perfetto_protos_perfetto_trace_filesystem_zero_gen",
         ":perfetto_protos_perfetto_trace_ftrace_zero_gen",
         ":perfetto_protos_perfetto_trace_gpu_zero_gen",
@@ -639,6 +644,7 @@
         "perfetto_protos_perfetto_ipc_wire_protocol_cpp_gen_headers",
         "perfetto_protos_perfetto_trace_android_zero_gen_headers",
         "perfetto_protos_perfetto_trace_chrome_zero_gen_headers",
+        "perfetto_protos_perfetto_trace_etw_zero_gen_headers",
         "perfetto_protos_perfetto_trace_filesystem_zero_gen_headers",
         "perfetto_protos_perfetto_trace_ftrace_zero_gen_headers",
         "perfetto_protos_perfetto_trace_gpu_zero_gen_headers",
@@ -776,6 +782,7 @@
         ":perfetto_protos_perfetto_ipc_wire_protocol_cpp_gen",
         ":perfetto_protos_perfetto_trace_android_zero_gen",
         ":perfetto_protos_perfetto_trace_chrome_zero_gen",
+        ":perfetto_protos_perfetto_trace_etw_zero_gen",
         ":perfetto_protos_perfetto_trace_filesystem_zero_gen",
         ":perfetto_protos_perfetto_trace_ftrace_zero_gen",
         ":perfetto_protos_perfetto_trace_gpu_zero_gen",
@@ -860,6 +867,7 @@
         "perfetto_protos_perfetto_ipc_wire_protocol_cpp_gen_headers",
         "perfetto_protos_perfetto_trace_android_zero_gen_headers",
         "perfetto_protos_perfetto_trace_chrome_zero_gen_headers",
+        "perfetto_protos_perfetto_trace_etw_zero_gen_headers",
         "perfetto_protos_perfetto_trace_filesystem_zero_gen_headers",
         "perfetto_protos_perfetto_trace_ftrace_zero_gen_headers",
         "perfetto_protos_perfetto_trace_gpu_zero_gen_headers",
@@ -945,6 +953,7 @@
         ":perfetto_protos_perfetto_ipc_wire_protocol_cpp_gen",
         ":perfetto_protos_perfetto_trace_android_zero_gen",
         ":perfetto_protos_perfetto_trace_chrome_zero_gen",
+        ":perfetto_protos_perfetto_trace_etw_zero_gen",
         ":perfetto_protos_perfetto_trace_filesystem_zero_gen",
         ":perfetto_protos_perfetto_trace_ftrace_zero_gen",
         ":perfetto_protos_perfetto_trace_gpu_zero_gen",
@@ -1032,6 +1041,7 @@
         "perfetto_protos_perfetto_ipc_wire_protocol_cpp_gen_headers",
         "perfetto_protos_perfetto_trace_android_zero_gen_headers",
         "perfetto_protos_perfetto_trace_chrome_zero_gen_headers",
+        "perfetto_protos_perfetto_trace_etw_zero_gen_headers",
         "perfetto_protos_perfetto_trace_filesystem_zero_gen_headers",
         "perfetto_protos_perfetto_trace_ftrace_zero_gen_headers",
         "perfetto_protos_perfetto_trace_gpu_zero_gen_headers",
@@ -1084,6 +1094,7 @@
         "perfetto_protos_perfetto_ipc_wire_protocol_cpp_gen_headers",
         "perfetto_protos_perfetto_trace_android_zero_gen_headers",
         "perfetto_protos_perfetto_trace_chrome_zero_gen_headers",
+        "perfetto_protos_perfetto_trace_etw_zero_gen_headers",
         "perfetto_protos_perfetto_trace_filesystem_zero_gen_headers",
         "perfetto_protos_perfetto_trace_ftrace_zero_gen_headers",
         "perfetto_protos_perfetto_trace_gpu_zero_gen_headers",
@@ -1166,6 +1177,7 @@
         ":perfetto_protos_perfetto_ipc_wire_protocol_cpp_gen",
         ":perfetto_protos_perfetto_trace_android_zero_gen",
         ":perfetto_protos_perfetto_trace_chrome_zero_gen",
+        ":perfetto_protos_perfetto_trace_etw_zero_gen",
         ":perfetto_protos_perfetto_trace_filesystem_zero_gen",
         ":perfetto_protos_perfetto_trace_ftrace_zero_gen",
         ":perfetto_protos_perfetto_trace_gpu_zero_gen",
@@ -1243,6 +1255,7 @@
         "perfetto_protos_perfetto_ipc_wire_protocol_cpp_gen_headers",
         "perfetto_protos_perfetto_trace_android_zero_gen_headers",
         "perfetto_protos_perfetto_trace_chrome_zero_gen_headers",
+        "perfetto_protos_perfetto_trace_etw_zero_gen_headers",
         "perfetto_protos_perfetto_trace_filesystem_zero_gen_headers",
         "perfetto_protos_perfetto_trace_ftrace_zero_gen_headers",
         "perfetto_protos_perfetto_trace_gpu_zero_gen_headers",
@@ -1334,6 +1347,8 @@
         ":perfetto_protos_perfetto_trace_android_zero_gen",
         ":perfetto_protos_perfetto_trace_chrome_cpp_gen",
         ":perfetto_protos_perfetto_trace_chrome_zero_gen",
+        ":perfetto_protos_perfetto_trace_etw_cpp_gen",
+        ":perfetto_protos_perfetto_trace_etw_zero_gen",
         ":perfetto_protos_perfetto_trace_filesystem_cpp_gen",
         ":perfetto_protos_perfetto_trace_filesystem_zero_gen",
         ":perfetto_protos_perfetto_trace_ftrace_cpp_gen",
@@ -1464,6 +1479,8 @@
         "perfetto_protos_perfetto_trace_android_zero_gen_headers",
         "perfetto_protos_perfetto_trace_chrome_cpp_gen_headers",
         "perfetto_protos_perfetto_trace_chrome_zero_gen_headers",
+        "perfetto_protos_perfetto_trace_etw_cpp_gen_headers",
+        "perfetto_protos_perfetto_trace_etw_zero_gen_headers",
         "perfetto_protos_perfetto_trace_filesystem_cpp_gen_headers",
         "perfetto_protos_perfetto_trace_filesystem_zero_gen_headers",
         "perfetto_protos_perfetto_trace_ftrace_cpp_gen_headers",
@@ -1533,6 +1550,8 @@
         "perfetto_protos_perfetto_trace_android_zero_gen_headers",
         "perfetto_protos_perfetto_trace_chrome_cpp_gen_headers",
         "perfetto_protos_perfetto_trace_chrome_zero_gen_headers",
+        "perfetto_protos_perfetto_trace_etw_cpp_gen_headers",
+        "perfetto_protos_perfetto_trace_etw_zero_gen_headers",
         "perfetto_protos_perfetto_trace_filesystem_cpp_gen_headers",
         "perfetto_protos_perfetto_trace_filesystem_zero_gen_headers",
         "perfetto_protos_perfetto_trace_ftrace_cpp_gen_headers",
@@ -1626,6 +1645,8 @@
         ":perfetto_protos_perfetto_trace_android_zero_gen",
         ":perfetto_protos_perfetto_trace_chrome_cpp_gen",
         ":perfetto_protos_perfetto_trace_chrome_zero_gen",
+        ":perfetto_protos_perfetto_trace_etw_cpp_gen",
+        ":perfetto_protos_perfetto_trace_etw_zero_gen",
         ":perfetto_protos_perfetto_trace_filesystem_cpp_gen",
         ":perfetto_protos_perfetto_trace_filesystem_zero_gen",
         ":perfetto_protos_perfetto_trace_ftrace_cpp_gen",
@@ -1742,6 +1763,8 @@
         "perfetto_protos_perfetto_trace_android_zero_gen_headers",
         "perfetto_protos_perfetto_trace_chrome_cpp_gen_headers",
         "perfetto_protos_perfetto_trace_chrome_zero_gen_headers",
+        "perfetto_protos_perfetto_trace_etw_cpp_gen_headers",
+        "perfetto_protos_perfetto_trace_etw_zero_gen_headers",
         "perfetto_protos_perfetto_trace_filesystem_cpp_gen_headers",
         "perfetto_protos_perfetto_trace_filesystem_zero_gen_headers",
         "perfetto_protos_perfetto_trace_ftrace_cpp_gen_headers",
@@ -1811,6 +1834,8 @@
         "perfetto_protos_perfetto_trace_android_zero_gen_headers",
         "perfetto_protos_perfetto_trace_chrome_cpp_gen_headers",
         "perfetto_protos_perfetto_trace_chrome_zero_gen_headers",
+        "perfetto_protos_perfetto_trace_etw_cpp_gen_headers",
+        "perfetto_protos_perfetto_trace_etw_zero_gen_headers",
         "perfetto_protos_perfetto_trace_filesystem_cpp_gen_headers",
         "perfetto_protos_perfetto_trace_filesystem_zero_gen_headers",
         "perfetto_protos_perfetto_trace_ftrace_cpp_gen_headers",
@@ -2138,6 +2163,9 @@
         ":perfetto_protos_perfetto_trace_chrome_cpp_gen",
         ":perfetto_protos_perfetto_trace_chrome_lite_gen",
         ":perfetto_protos_perfetto_trace_chrome_zero_gen",
+        ":perfetto_protos_perfetto_trace_etw_cpp_gen",
+        ":perfetto_protos_perfetto_trace_etw_lite_gen",
+        ":perfetto_protos_perfetto_trace_etw_zero_gen",
         ":perfetto_protos_perfetto_trace_filesystem_cpp_gen",
         ":perfetto_protos_perfetto_trace_filesystem_lite_gen",
         ":perfetto_protos_perfetto_trace_filesystem_zero_gen",
@@ -2312,6 +2340,8 @@
         ":perfetto_src_traced_probes_statsd_client_statsd_client",
         ":perfetto_src_traced_probes_sys_stats_sys_stats",
         ":perfetto_src_traced_probes_system_info_system_info",
+        ":perfetto_src_traced_relay_integrationtests",
+        ":perfetto_src_traced_relay_lib",
         ":perfetto_src_tracing_client_api_without_backends",
         ":perfetto_src_tracing_common",
         ":perfetto_src_tracing_core_core",
@@ -2406,6 +2436,9 @@
         "perfetto_protos_perfetto_trace_chrome_cpp_gen_headers",
         "perfetto_protos_perfetto_trace_chrome_lite_gen_headers",
         "perfetto_protos_perfetto_trace_chrome_zero_gen_headers",
+        "perfetto_protos_perfetto_trace_etw_cpp_gen_headers",
+        "perfetto_protos_perfetto_trace_etw_lite_gen_headers",
+        "perfetto_protos_perfetto_trace_etw_zero_gen_headers",
         "perfetto_protos_perfetto_trace_filesystem_cpp_gen_headers",
         "perfetto_protos_perfetto_trace_filesystem_lite_gen_headers",
         "perfetto_protos_perfetto_trace_filesystem_zero_gen_headers",
@@ -2489,16 +2522,26 @@
 }
 
 // GN: //protos/perfetto/bigtrace:lite
-genrule {
-    name: "perfetto_protos_perfetto_bigtrace_lite_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_bigtrace_lite",
     srcs: [
         "protos/perfetto/bigtrace/orchestrator.proto",
         "protos/perfetto/bigtrace/worker.proto",
     ],
+}
+
+// GN: //protos/perfetto/bigtrace:lite
+genrule {
+    name: "perfetto_protos_perfetto_bigtrace_lite_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_bigtrace_lite",
+        ":perfetto_protos_perfetto_common_lite",
+        ":perfetto_protos_perfetto_trace_processor_lite",
+    ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_bigtrace_lite)",
     out: [
         "external/perfetto/protos/perfetto/bigtrace/orchestrator.pb.cc",
         "external/perfetto/protos/perfetto/bigtrace/worker.pb.cc",
@@ -2509,13 +2552,14 @@
 genrule {
     name: "perfetto_protos_perfetto_bigtrace_lite_gen_headers",
     srcs: [
-        "protos/perfetto/bigtrace/orchestrator.proto",
-        "protos/perfetto/bigtrace/worker.proto",
+        ":perfetto_protos_perfetto_bigtrace_lite",
+        ":perfetto_protos_perfetto_common_lite",
+        ":perfetto_protos_perfetto_trace_processor_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_bigtrace_lite)",
     out: [
         "external/perfetto/protos/perfetto/bigtrace/orchestrator.pb.h",
         "external/perfetto/protos/perfetto/bigtrace/worker.pb.h",
@@ -2527,8 +2571,8 @@
 }
 
 // GN: //protos/perfetto/common:cpp
-genrule {
-    name: "perfetto_protos_perfetto_common_cpp_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_common_cpp",
     srcs: [
         "protos/perfetto/common/android_energy_consumer_descriptor.proto",
         "protos/perfetto/common/android_log_constants.proto",
@@ -2547,11 +2591,19 @@
         "protos/perfetto/common/tracing_service_state.proto",
         "protos/perfetto/common/track_event_descriptor.proto",
     ],
+}
+
+// GN: //protos/perfetto/common:cpp
+genrule {
+    name: "perfetto_protos_perfetto_common_cpp_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_common_cpp",
+    ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_common_cpp)",
     out: [
         "external/perfetto/protos/perfetto/common/android_energy_consumer_descriptor.gen.cc",
         "external/perfetto/protos/perfetto/common/android_log_constants.gen.cc",
@@ -2576,28 +2628,13 @@
 genrule {
     name: "perfetto_protos_perfetto_common_cpp_gen_headers",
     srcs: [
-        "protos/perfetto/common/android_energy_consumer_descriptor.proto",
-        "protos/perfetto/common/android_log_constants.proto",
-        "protos/perfetto/common/builtin_clock.proto",
-        "protos/perfetto/common/commit_data_request.proto",
-        "protos/perfetto/common/data_source_descriptor.proto",
-        "protos/perfetto/common/descriptor.proto",
-        "protos/perfetto/common/ftrace_descriptor.proto",
-        "protos/perfetto/common/gpu_counter_descriptor.proto",
-        "protos/perfetto/common/interceptor_descriptor.proto",
-        "protos/perfetto/common/observable_events.proto",
-        "protos/perfetto/common/perf_events.proto",
-        "protos/perfetto/common/sys_stats_counters.proto",
-        "protos/perfetto/common/trace_stats.proto",
-        "protos/perfetto/common/tracing_service_capabilities.proto",
-        "protos/perfetto/common/tracing_service_state.proto",
-        "protos/perfetto/common/track_event_descriptor.proto",
+        ":perfetto_protos_perfetto_common_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_common_cpp)",
     out: [
         "external/perfetto/protos/perfetto/common/android_energy_consumer_descriptor.gen.h",
         "external/perfetto/protos/perfetto/common/android_log_constants.gen.h",
@@ -2623,8 +2660,8 @@
 }
 
 // GN: //protos/perfetto/common:lite
-genrule {
-    name: "perfetto_protos_perfetto_common_lite_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_common_lite",
     srcs: [
         "protos/perfetto/common/android_energy_consumer_descriptor.proto",
         "protos/perfetto/common/android_log_constants.proto",
@@ -2643,10 +2680,18 @@
         "protos/perfetto/common/tracing_service_state.proto",
         "protos/perfetto/common/track_event_descriptor.proto",
     ],
+}
+
+// GN: //protos/perfetto/common:lite
+genrule {
+    name: "perfetto_protos_perfetto_common_lite_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_common_lite",
+    ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_common_lite)",
     out: [
         "external/perfetto/protos/perfetto/common/android_energy_consumer_descriptor.pb.cc",
         "external/perfetto/protos/perfetto/common/android_log_constants.pb.cc",
@@ -2671,27 +2716,12 @@
 genrule {
     name: "perfetto_protos_perfetto_common_lite_gen_headers",
     srcs: [
-        "protos/perfetto/common/android_energy_consumer_descriptor.proto",
-        "protos/perfetto/common/android_log_constants.proto",
-        "protos/perfetto/common/builtin_clock.proto",
-        "protos/perfetto/common/commit_data_request.proto",
-        "protos/perfetto/common/data_source_descriptor.proto",
-        "protos/perfetto/common/descriptor.proto",
-        "protos/perfetto/common/ftrace_descriptor.proto",
-        "protos/perfetto/common/gpu_counter_descriptor.proto",
-        "protos/perfetto/common/interceptor_descriptor.proto",
-        "protos/perfetto/common/observable_events.proto",
-        "protos/perfetto/common/perf_events.proto",
-        "protos/perfetto/common/sys_stats_counters.proto",
-        "protos/perfetto/common/trace_stats.proto",
-        "protos/perfetto/common/tracing_service_capabilities.proto",
-        "protos/perfetto/common/tracing_service_state.proto",
-        "protos/perfetto/common/track_event_descriptor.proto",
+        ":perfetto_protos_perfetto_common_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_common_lite)",
     out: [
         "external/perfetto/protos/perfetto/common/android_energy_consumer_descriptor.pb.h",
         "external/perfetto/protos/perfetto/common/android_log_constants.pb.h",
@@ -2717,8 +2747,8 @@
 }
 
 // GN: //protos/perfetto/common:zero
-genrule {
-    name: "perfetto_protos_perfetto_common_zero_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_common_zero",
     srcs: [
         "protos/perfetto/common/android_energy_consumer_descriptor.proto",
         "protos/perfetto/common/android_log_constants.proto",
@@ -2737,11 +2767,19 @@
         "protos/perfetto/common/tracing_service_state.proto",
         "protos/perfetto/common/track_event_descriptor.proto",
     ],
+}
+
+// GN: //protos/perfetto/common:zero
+genrule {
+    name: "perfetto_protos_perfetto_common_zero_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_common_zero",
+    ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_common_zero)",
     out: [
         "external/perfetto/protos/perfetto/common/android_energy_consumer_descriptor.pbzero.cc",
         "external/perfetto/protos/perfetto/common/android_log_constants.pbzero.cc",
@@ -2766,28 +2804,13 @@
 genrule {
     name: "perfetto_protos_perfetto_common_zero_gen_headers",
     srcs: [
-        "protos/perfetto/common/android_energy_consumer_descriptor.proto",
-        "protos/perfetto/common/android_log_constants.proto",
-        "protos/perfetto/common/builtin_clock.proto",
-        "protos/perfetto/common/commit_data_request.proto",
-        "protos/perfetto/common/data_source_descriptor.proto",
-        "protos/perfetto/common/descriptor.proto",
-        "protos/perfetto/common/ftrace_descriptor.proto",
-        "protos/perfetto/common/gpu_counter_descriptor.proto",
-        "protos/perfetto/common/interceptor_descriptor.proto",
-        "protos/perfetto/common/observable_events.proto",
-        "protos/perfetto/common/perf_events.proto",
-        "protos/perfetto/common/sys_stats_counters.proto",
-        "protos/perfetto/common/trace_stats.proto",
-        "protos/perfetto/common/tracing_service_capabilities.proto",
-        "protos/perfetto/common/tracing_service_state.proto",
-        "protos/perfetto/common/track_event_descriptor.proto",
+        ":perfetto_protos_perfetto_common_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_common_zero)",
     out: [
         "external/perfetto/protos/perfetto/common/android_energy_consumer_descriptor.pbzero.h",
         "external/perfetto/protos/perfetto/common/android_log_constants.pbzero.h",
@@ -2813,8 +2836,8 @@
 }
 
 // GN: //protos/perfetto/config/android:cpp
-genrule {
-    name: "perfetto_protos_perfetto_config_android_cpp_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_config_android_cpp",
     srcs: [
         "protos/perfetto/config/android/android_game_intervention_list_config.proto",
         "protos/perfetto/config/android/android_log_config.proto",
@@ -2826,11 +2849,20 @@
         "protos/perfetto/config/android/surfaceflinger_layers_config.proto",
         "protos/perfetto/config/android/surfaceflinger_transactions_config.proto",
     ],
+}
+
+// GN: //protos/perfetto/config/android:cpp
+genrule {
+    name: "perfetto_protos_perfetto_config_android_cpp_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_common_cpp",
+        ":perfetto_protos_perfetto_config_android_cpp",
+    ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_android_cpp)",
     out: [
         "external/perfetto/protos/perfetto/config/android/android_game_intervention_list_config.gen.cc",
         "external/perfetto/protos/perfetto/config/android/android_log_config.gen.cc",
@@ -2848,21 +2880,14 @@
 genrule {
     name: "perfetto_protos_perfetto_config_android_cpp_gen_headers",
     srcs: [
-        "protos/perfetto/config/android/android_game_intervention_list_config.proto",
-        "protos/perfetto/config/android/android_log_config.proto",
-        "protos/perfetto/config/android/android_polled_state_config.proto",
-        "protos/perfetto/config/android/android_sdk_sysprop_guard_config.proto",
-        "protos/perfetto/config/android/android_system_property_config.proto",
-        "protos/perfetto/config/android/network_trace_config.proto",
-        "protos/perfetto/config/android/packages_list_config.proto",
-        "protos/perfetto/config/android/surfaceflinger_layers_config.proto",
-        "protos/perfetto/config/android/surfaceflinger_transactions_config.proto",
+        ":perfetto_protos_perfetto_common_cpp",
+        ":perfetto_protos_perfetto_config_android_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_android_cpp)",
     out: [
         "external/perfetto/protos/perfetto/config/android/android_game_intervention_list_config.gen.h",
         "external/perfetto/protos/perfetto/config/android/android_log_config.gen.h",
@@ -2881,8 +2906,8 @@
 }
 
 // GN: //protos/perfetto/config/android:lite
-genrule {
-    name: "perfetto_protos_perfetto_config_android_lite_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_config_android_lite",
     srcs: [
         "protos/perfetto/config/android/android_game_intervention_list_config.proto",
         "protos/perfetto/config/android/android_log_config.proto",
@@ -2894,10 +2919,19 @@
         "protos/perfetto/config/android/surfaceflinger_layers_config.proto",
         "protos/perfetto/config/android/surfaceflinger_transactions_config.proto",
     ],
+}
+
+// GN: //protos/perfetto/config/android:lite
+genrule {
+    name: "perfetto_protos_perfetto_config_android_lite_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_common_lite",
+        ":perfetto_protos_perfetto_config_android_lite",
+    ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_android_lite)",
     out: [
         "external/perfetto/protos/perfetto/config/android/android_game_intervention_list_config.pb.cc",
         "external/perfetto/protos/perfetto/config/android/android_log_config.pb.cc",
@@ -2915,20 +2949,13 @@
 genrule {
     name: "perfetto_protos_perfetto_config_android_lite_gen_headers",
     srcs: [
-        "protos/perfetto/config/android/android_game_intervention_list_config.proto",
-        "protos/perfetto/config/android/android_log_config.proto",
-        "protos/perfetto/config/android/android_polled_state_config.proto",
-        "protos/perfetto/config/android/android_sdk_sysprop_guard_config.proto",
-        "protos/perfetto/config/android/android_system_property_config.proto",
-        "protos/perfetto/config/android/network_trace_config.proto",
-        "protos/perfetto/config/android/packages_list_config.proto",
-        "protos/perfetto/config/android/surfaceflinger_layers_config.proto",
-        "protos/perfetto/config/android/surfaceflinger_transactions_config.proto",
+        ":perfetto_protos_perfetto_common_lite",
+        ":perfetto_protos_perfetto_config_android_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_android_lite)",
     out: [
         "external/perfetto/protos/perfetto/config/android/android_game_intervention_list_config.pb.h",
         "external/perfetto/protos/perfetto/config/android/android_log_config.pb.h",
@@ -2947,8 +2974,8 @@
 }
 
 // GN: //protos/perfetto/config/android:zero
-genrule {
-    name: "perfetto_protos_perfetto_config_android_zero_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_config_android_zero",
     srcs: [
         "protos/perfetto/config/android/android_game_intervention_list_config.proto",
         "protos/perfetto/config/android/android_log_config.proto",
@@ -2960,11 +2987,20 @@
         "protos/perfetto/config/android/surfaceflinger_layers_config.proto",
         "protos/perfetto/config/android/surfaceflinger_transactions_config.proto",
     ],
+}
+
+// GN: //protos/perfetto/config/android:zero
+genrule {
+    name: "perfetto_protos_perfetto_config_android_zero_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_common_zero",
+        ":perfetto_protos_perfetto_config_android_zero",
+    ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_android_zero)",
     out: [
         "external/perfetto/protos/perfetto/config/android/android_game_intervention_list_config.pbzero.cc",
         "external/perfetto/protos/perfetto/config/android/android_log_config.pbzero.cc",
@@ -2982,21 +3018,14 @@
 genrule {
     name: "perfetto_protos_perfetto_config_android_zero_gen_headers",
     srcs: [
-        "protos/perfetto/config/android/android_game_intervention_list_config.proto",
-        "protos/perfetto/config/android/android_log_config.proto",
-        "protos/perfetto/config/android/android_polled_state_config.proto",
-        "protos/perfetto/config/android/android_sdk_sysprop_guard_config.proto",
-        "protos/perfetto/config/android/android_system_property_config.proto",
-        "protos/perfetto/config/android/network_trace_config.proto",
-        "protos/perfetto/config/android/packages_list_config.proto",
-        "protos/perfetto/config/android/surfaceflinger_layers_config.proto",
-        "protos/perfetto/config/android/surfaceflinger_transactions_config.proto",
+        ":perfetto_protos_perfetto_common_zero",
+        ":perfetto_protos_perfetto_config_android_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_android_zero)",
     out: [
         "external/perfetto/protos/perfetto/config/android/android_game_intervention_list_config.pbzero.h",
         "external/perfetto/protos/perfetto/config/android/android_log_config.pbzero.h",
@@ -3015,8 +3044,8 @@
 }
 
 // GN: //protos/perfetto/config:cpp
-genrule {
-    name: "perfetto_protos_perfetto_config_cpp_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_config_cpp",
     srcs: [
         "protos/perfetto/config/chrome/chrome_config.proto",
         "protos/perfetto/config/chrome/scenario_config.proto",
@@ -3026,11 +3055,32 @@
         "protos/perfetto/config/test_config.proto",
         "protos/perfetto/config/trace_config.proto",
     ],
+}
+
+// GN: //protos/perfetto/config:cpp
+genrule {
+    name: "perfetto_protos_perfetto_config_cpp_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_common_cpp",
+        ":perfetto_protos_perfetto_config_android_cpp",
+        ":perfetto_protos_perfetto_config_cpp",
+        ":perfetto_protos_perfetto_config_ftrace_cpp",
+        ":perfetto_protos_perfetto_config_gpu_cpp",
+        ":perfetto_protos_perfetto_config_inode_file_cpp",
+        ":perfetto_protos_perfetto_config_interceptors_cpp",
+        ":perfetto_protos_perfetto_config_power_cpp",
+        ":perfetto_protos_perfetto_config_process_stats_cpp",
+        ":perfetto_protos_perfetto_config_profiling_cpp",
+        ":perfetto_protos_perfetto_config_statsd_cpp",
+        ":perfetto_protos_perfetto_config_sys_stats_cpp",
+        ":perfetto_protos_perfetto_config_system_info_cpp",
+        ":perfetto_protos_perfetto_config_track_event_cpp",
+    ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_cpp)",
     out: [
         "external/perfetto/protos/perfetto/config/chrome/chrome_config.gen.cc",
         "external/perfetto/protos/perfetto/config/chrome/scenario_config.gen.cc",
@@ -3046,19 +3096,26 @@
 genrule {
     name: "perfetto_protos_perfetto_config_cpp_gen_headers",
     srcs: [
-        "protos/perfetto/config/chrome/chrome_config.proto",
-        "protos/perfetto/config/chrome/scenario_config.proto",
-        "protos/perfetto/config/data_source_config.proto",
-        "protos/perfetto/config/interceptor_config.proto",
-        "protos/perfetto/config/stress_test_config.proto",
-        "protos/perfetto/config/test_config.proto",
-        "protos/perfetto/config/trace_config.proto",
+        ":perfetto_protos_perfetto_common_cpp",
+        ":perfetto_protos_perfetto_config_android_cpp",
+        ":perfetto_protos_perfetto_config_cpp",
+        ":perfetto_protos_perfetto_config_ftrace_cpp",
+        ":perfetto_protos_perfetto_config_gpu_cpp",
+        ":perfetto_protos_perfetto_config_inode_file_cpp",
+        ":perfetto_protos_perfetto_config_interceptors_cpp",
+        ":perfetto_protos_perfetto_config_power_cpp",
+        ":perfetto_protos_perfetto_config_process_stats_cpp",
+        ":perfetto_protos_perfetto_config_profiling_cpp",
+        ":perfetto_protos_perfetto_config_statsd_cpp",
+        ":perfetto_protos_perfetto_config_sys_stats_cpp",
+        ":perfetto_protos_perfetto_config_system_info_cpp",
+        ":perfetto_protos_perfetto_config_track_event_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_cpp)",
     out: [
         "external/perfetto/protos/perfetto/config/chrome/chrome_config.gen.h",
         "external/perfetto/protos/perfetto/config/chrome/scenario_config.gen.h",
@@ -3136,16 +3193,24 @@
 }
 
 // GN: //protos/perfetto/config/ftrace:cpp
+filegroup {
+    name: "perfetto_protos_perfetto_config_ftrace_cpp",
+    srcs: [
+        "protos/perfetto/config/ftrace/ftrace_config.proto",
+    ],
+}
+
+// GN: //protos/perfetto/config/ftrace:cpp
 genrule {
     name: "perfetto_protos_perfetto_config_ftrace_cpp_gen",
     srcs: [
-        "protos/perfetto/config/ftrace/ftrace_config.proto",
+        ":perfetto_protos_perfetto_config_ftrace_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_ftrace_cpp)",
     out: [
         "external/perfetto/protos/perfetto/config/ftrace/ftrace_config.gen.cc",
     ],
@@ -3155,13 +3220,13 @@
 genrule {
     name: "perfetto_protos_perfetto_config_ftrace_cpp_gen_headers",
     srcs: [
-        "protos/perfetto/config/ftrace/ftrace_config.proto",
+        ":perfetto_protos_perfetto_config_ftrace_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_ftrace_cpp)",
     out: [
         "external/perfetto/protos/perfetto/config/ftrace/ftrace_config.gen.h",
     ],
@@ -3172,15 +3237,23 @@
 }
 
 // GN: //protos/perfetto/config/ftrace:lite
+filegroup {
+    name: "perfetto_protos_perfetto_config_ftrace_lite",
+    srcs: [
+        "protos/perfetto/config/ftrace/ftrace_config.proto",
+    ],
+}
+
+// GN: //protos/perfetto/config/ftrace:lite
 genrule {
     name: "perfetto_protos_perfetto_config_ftrace_lite_gen",
     srcs: [
-        "protos/perfetto/config/ftrace/ftrace_config.proto",
+        ":perfetto_protos_perfetto_config_ftrace_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_ftrace_lite)",
     out: [
         "external/perfetto/protos/perfetto/config/ftrace/ftrace_config.pb.cc",
     ],
@@ -3190,12 +3263,12 @@
 genrule {
     name: "perfetto_protos_perfetto_config_ftrace_lite_gen_headers",
     srcs: [
-        "protos/perfetto/config/ftrace/ftrace_config.proto",
+        ":perfetto_protos_perfetto_config_ftrace_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_ftrace_lite)",
     out: [
         "external/perfetto/protos/perfetto/config/ftrace/ftrace_config.pb.h",
     ],
@@ -3206,16 +3279,24 @@
 }
 
 // GN: //protos/perfetto/config/ftrace:zero
+filegroup {
+    name: "perfetto_protos_perfetto_config_ftrace_zero",
+    srcs: [
+        "protos/perfetto/config/ftrace/ftrace_config.proto",
+    ],
+}
+
+// GN: //protos/perfetto/config/ftrace:zero
 genrule {
     name: "perfetto_protos_perfetto_config_ftrace_zero_gen",
     srcs: [
-        "protos/perfetto/config/ftrace/ftrace_config.proto",
+        ":perfetto_protos_perfetto_config_ftrace_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_ftrace_zero)",
     out: [
         "external/perfetto/protos/perfetto/config/ftrace/ftrace_config.pbzero.cc",
     ],
@@ -3225,13 +3306,13 @@
 genrule {
     name: "perfetto_protos_perfetto_config_ftrace_zero_gen_headers",
     srcs: [
-        "protos/perfetto/config/ftrace/ftrace_config.proto",
+        ":perfetto_protos_perfetto_config_ftrace_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_ftrace_zero)",
     out: [
         "external/perfetto/protos/perfetto/config/ftrace/ftrace_config.pbzero.h",
     ],
@@ -3242,17 +3323,25 @@
 }
 
 // GN: //protos/perfetto/config/gpu:cpp
-genrule {
-    name: "perfetto_protos_perfetto_config_gpu_cpp_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_config_gpu_cpp",
     srcs: [
         "protos/perfetto/config/gpu/gpu_counter_config.proto",
         "protos/perfetto/config/gpu/vulkan_memory_config.proto",
     ],
+}
+
+// GN: //protos/perfetto/config/gpu:cpp
+genrule {
+    name: "perfetto_protos_perfetto_config_gpu_cpp_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_config_gpu_cpp",
+    ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_gpu_cpp)",
     out: [
         "external/perfetto/protos/perfetto/config/gpu/gpu_counter_config.gen.cc",
         "external/perfetto/protos/perfetto/config/gpu/vulkan_memory_config.gen.cc",
@@ -3263,14 +3352,13 @@
 genrule {
     name: "perfetto_protos_perfetto_config_gpu_cpp_gen_headers",
     srcs: [
-        "protos/perfetto/config/gpu/gpu_counter_config.proto",
-        "protos/perfetto/config/gpu/vulkan_memory_config.proto",
+        ":perfetto_protos_perfetto_config_gpu_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_gpu_cpp)",
     out: [
         "external/perfetto/protos/perfetto/config/gpu/gpu_counter_config.gen.h",
         "external/perfetto/protos/perfetto/config/gpu/vulkan_memory_config.gen.h",
@@ -3282,16 +3370,24 @@
 }
 
 // GN: //protos/perfetto/config/gpu:lite
-genrule {
-    name: "perfetto_protos_perfetto_config_gpu_lite_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_config_gpu_lite",
     srcs: [
         "protos/perfetto/config/gpu/gpu_counter_config.proto",
         "protos/perfetto/config/gpu/vulkan_memory_config.proto",
     ],
+}
+
+// GN: //protos/perfetto/config/gpu:lite
+genrule {
+    name: "perfetto_protos_perfetto_config_gpu_lite_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_config_gpu_lite",
+    ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_gpu_lite)",
     out: [
         "external/perfetto/protos/perfetto/config/gpu/gpu_counter_config.pb.cc",
         "external/perfetto/protos/perfetto/config/gpu/vulkan_memory_config.pb.cc",
@@ -3302,13 +3398,12 @@
 genrule {
     name: "perfetto_protos_perfetto_config_gpu_lite_gen_headers",
     srcs: [
-        "protos/perfetto/config/gpu/gpu_counter_config.proto",
-        "protos/perfetto/config/gpu/vulkan_memory_config.proto",
+        ":perfetto_protos_perfetto_config_gpu_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_gpu_lite)",
     out: [
         "external/perfetto/protos/perfetto/config/gpu/gpu_counter_config.pb.h",
         "external/perfetto/protos/perfetto/config/gpu/vulkan_memory_config.pb.h",
@@ -3320,17 +3415,25 @@
 }
 
 // GN: //protos/perfetto/config/gpu:zero
-genrule {
-    name: "perfetto_protos_perfetto_config_gpu_zero_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_config_gpu_zero",
     srcs: [
         "protos/perfetto/config/gpu/gpu_counter_config.proto",
         "protos/perfetto/config/gpu/vulkan_memory_config.proto",
     ],
+}
+
+// GN: //protos/perfetto/config/gpu:zero
+genrule {
+    name: "perfetto_protos_perfetto_config_gpu_zero_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_config_gpu_zero",
+    ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_gpu_zero)",
     out: [
         "external/perfetto/protos/perfetto/config/gpu/gpu_counter_config.pbzero.cc",
         "external/perfetto/protos/perfetto/config/gpu/vulkan_memory_config.pbzero.cc",
@@ -3341,14 +3444,13 @@
 genrule {
     name: "perfetto_protos_perfetto_config_gpu_zero_gen_headers",
     srcs: [
-        "protos/perfetto/config/gpu/gpu_counter_config.proto",
-        "protos/perfetto/config/gpu/vulkan_memory_config.proto",
+        ":perfetto_protos_perfetto_config_gpu_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_gpu_zero)",
     out: [
         "external/perfetto/protos/perfetto/config/gpu/gpu_counter_config.pbzero.h",
         "external/perfetto/protos/perfetto/config/gpu/vulkan_memory_config.pbzero.h",
@@ -3360,16 +3462,24 @@
 }
 
 // GN: //protos/perfetto/config/inode_file:cpp
+filegroup {
+    name: "perfetto_protos_perfetto_config_inode_file_cpp",
+    srcs: [
+        "protos/perfetto/config/inode_file/inode_file_config.proto",
+    ],
+}
+
+// GN: //protos/perfetto/config/inode_file:cpp
 genrule {
     name: "perfetto_protos_perfetto_config_inode_file_cpp_gen",
     srcs: [
-        "protos/perfetto/config/inode_file/inode_file_config.proto",
+        ":perfetto_protos_perfetto_config_inode_file_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_inode_file_cpp)",
     out: [
         "external/perfetto/protos/perfetto/config/inode_file/inode_file_config.gen.cc",
     ],
@@ -3379,13 +3489,13 @@
 genrule {
     name: "perfetto_protos_perfetto_config_inode_file_cpp_gen_headers",
     srcs: [
-        "protos/perfetto/config/inode_file/inode_file_config.proto",
+        ":perfetto_protos_perfetto_config_inode_file_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_inode_file_cpp)",
     out: [
         "external/perfetto/protos/perfetto/config/inode_file/inode_file_config.gen.h",
     ],
@@ -3396,15 +3506,23 @@
 }
 
 // GN: //protos/perfetto/config/inode_file:lite
+filegroup {
+    name: "perfetto_protos_perfetto_config_inode_file_lite",
+    srcs: [
+        "protos/perfetto/config/inode_file/inode_file_config.proto",
+    ],
+}
+
+// GN: //protos/perfetto/config/inode_file:lite
 genrule {
     name: "perfetto_protos_perfetto_config_inode_file_lite_gen",
     srcs: [
-        "protos/perfetto/config/inode_file/inode_file_config.proto",
+        ":perfetto_protos_perfetto_config_inode_file_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_inode_file_lite)",
     out: [
         "external/perfetto/protos/perfetto/config/inode_file/inode_file_config.pb.cc",
     ],
@@ -3414,12 +3532,12 @@
 genrule {
     name: "perfetto_protos_perfetto_config_inode_file_lite_gen_headers",
     srcs: [
-        "protos/perfetto/config/inode_file/inode_file_config.proto",
+        ":perfetto_protos_perfetto_config_inode_file_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_inode_file_lite)",
     out: [
         "external/perfetto/protos/perfetto/config/inode_file/inode_file_config.pb.h",
     ],
@@ -3430,16 +3548,24 @@
 }
 
 // GN: //protos/perfetto/config/inode_file:zero
+filegroup {
+    name: "perfetto_protos_perfetto_config_inode_file_zero",
+    srcs: [
+        "protos/perfetto/config/inode_file/inode_file_config.proto",
+    ],
+}
+
+// GN: //protos/perfetto/config/inode_file:zero
 genrule {
     name: "perfetto_protos_perfetto_config_inode_file_zero_gen",
     srcs: [
-        "protos/perfetto/config/inode_file/inode_file_config.proto",
+        ":perfetto_protos_perfetto_config_inode_file_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_inode_file_zero)",
     out: [
         "external/perfetto/protos/perfetto/config/inode_file/inode_file_config.pbzero.cc",
     ],
@@ -3449,13 +3575,13 @@
 genrule {
     name: "perfetto_protos_perfetto_config_inode_file_zero_gen_headers",
     srcs: [
-        "protos/perfetto/config/inode_file/inode_file_config.proto",
+        ":perfetto_protos_perfetto_config_inode_file_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_inode_file_zero)",
     out: [
         "external/perfetto/protos/perfetto/config/inode_file/inode_file_config.pbzero.h",
     ],
@@ -3466,16 +3592,25 @@
 }
 
 // GN: //protos/perfetto/config/interceptors:cpp
+filegroup {
+    name: "perfetto_protos_perfetto_config_interceptors_cpp",
+    srcs: [
+        "protos/perfetto/config/interceptors/console_config.proto",
+    ],
+}
+
+// GN: //protos/perfetto/config/interceptors:cpp
 genrule {
     name: "perfetto_protos_perfetto_config_interceptors_cpp_gen",
     srcs: [
-        "protos/perfetto/config/interceptors/console_config.proto",
+        ":perfetto_protos_perfetto_common_cpp",
+        ":perfetto_protos_perfetto_config_interceptors_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_interceptors_cpp)",
     out: [
         "external/perfetto/protos/perfetto/config/interceptors/console_config.gen.cc",
     ],
@@ -3485,13 +3620,14 @@
 genrule {
     name: "perfetto_protos_perfetto_config_interceptors_cpp_gen_headers",
     srcs: [
-        "protos/perfetto/config/interceptors/console_config.proto",
+        ":perfetto_protos_perfetto_common_cpp",
+        ":perfetto_protos_perfetto_config_interceptors_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_interceptors_cpp)",
     out: [
         "external/perfetto/protos/perfetto/config/interceptors/console_config.gen.h",
     ],
@@ -3502,15 +3638,24 @@
 }
 
 // GN: //protos/perfetto/config/interceptors:lite
+filegroup {
+    name: "perfetto_protos_perfetto_config_interceptors_lite",
+    srcs: [
+        "protos/perfetto/config/interceptors/console_config.proto",
+    ],
+}
+
+// GN: //protos/perfetto/config/interceptors:lite
 genrule {
     name: "perfetto_protos_perfetto_config_interceptors_lite_gen",
     srcs: [
-        "protos/perfetto/config/interceptors/console_config.proto",
+        ":perfetto_protos_perfetto_common_lite",
+        ":perfetto_protos_perfetto_config_interceptors_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_interceptors_lite)",
     out: [
         "external/perfetto/protos/perfetto/config/interceptors/console_config.pb.cc",
     ],
@@ -3520,12 +3665,13 @@
 genrule {
     name: "perfetto_protos_perfetto_config_interceptors_lite_gen_headers",
     srcs: [
-        "protos/perfetto/config/interceptors/console_config.proto",
+        ":perfetto_protos_perfetto_common_lite",
+        ":perfetto_protos_perfetto_config_interceptors_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_interceptors_lite)",
     out: [
         "external/perfetto/protos/perfetto/config/interceptors/console_config.pb.h",
     ],
@@ -3536,16 +3682,25 @@
 }
 
 // GN: //protos/perfetto/config/interceptors:zero
+filegroup {
+    name: "perfetto_protos_perfetto_config_interceptors_zero",
+    srcs: [
+        "protos/perfetto/config/interceptors/console_config.proto",
+    ],
+}
+
+// GN: //protos/perfetto/config/interceptors:zero
 genrule {
     name: "perfetto_protos_perfetto_config_interceptors_zero_gen",
     srcs: [
-        "protos/perfetto/config/interceptors/console_config.proto",
+        ":perfetto_protos_perfetto_common_zero",
+        ":perfetto_protos_perfetto_config_interceptors_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_interceptors_zero)",
     out: [
         "external/perfetto/protos/perfetto/config/interceptors/console_config.pbzero.cc",
     ],
@@ -3555,13 +3710,14 @@
 genrule {
     name: "perfetto_protos_perfetto_config_interceptors_zero_gen_headers",
     srcs: [
-        "protos/perfetto/config/interceptors/console_config.proto",
+        ":perfetto_protos_perfetto_common_zero",
+        ":perfetto_protos_perfetto_config_interceptors_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_interceptors_zero)",
     out: [
         "external/perfetto/protos/perfetto/config/interceptors/console_config.pbzero.h",
     ],
@@ -3572,8 +3728,8 @@
 }
 
 // GN: //protos/perfetto/config:lite
-genrule {
-    name: "perfetto_protos_perfetto_config_lite_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_config_lite",
     srcs: [
         "protos/perfetto/config/chrome/chrome_config.proto",
         "protos/perfetto/config/chrome/scenario_config.proto",
@@ -3583,10 +3739,31 @@
         "protos/perfetto/config/test_config.proto",
         "protos/perfetto/config/trace_config.proto",
     ],
+}
+
+// GN: //protos/perfetto/config:lite
+genrule {
+    name: "perfetto_protos_perfetto_config_lite_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_common_lite",
+        ":perfetto_protos_perfetto_config_android_lite",
+        ":perfetto_protos_perfetto_config_ftrace_lite",
+        ":perfetto_protos_perfetto_config_gpu_lite",
+        ":perfetto_protos_perfetto_config_inode_file_lite",
+        ":perfetto_protos_perfetto_config_interceptors_lite",
+        ":perfetto_protos_perfetto_config_lite",
+        ":perfetto_protos_perfetto_config_power_lite",
+        ":perfetto_protos_perfetto_config_process_stats_lite",
+        ":perfetto_protos_perfetto_config_profiling_lite",
+        ":perfetto_protos_perfetto_config_statsd_lite",
+        ":perfetto_protos_perfetto_config_sys_stats_lite",
+        ":perfetto_protos_perfetto_config_system_info_lite",
+        ":perfetto_protos_perfetto_config_track_event_lite",
+    ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_lite)",
     out: [
         "external/perfetto/protos/perfetto/config/chrome/chrome_config.pb.cc",
         "external/perfetto/protos/perfetto/config/chrome/scenario_config.pb.cc",
@@ -3602,18 +3779,25 @@
 genrule {
     name: "perfetto_protos_perfetto_config_lite_gen_headers",
     srcs: [
-        "protos/perfetto/config/chrome/chrome_config.proto",
-        "protos/perfetto/config/chrome/scenario_config.proto",
-        "protos/perfetto/config/data_source_config.proto",
-        "protos/perfetto/config/interceptor_config.proto",
-        "protos/perfetto/config/stress_test_config.proto",
-        "protos/perfetto/config/test_config.proto",
-        "protos/perfetto/config/trace_config.proto",
+        ":perfetto_protos_perfetto_common_lite",
+        ":perfetto_protos_perfetto_config_android_lite",
+        ":perfetto_protos_perfetto_config_ftrace_lite",
+        ":perfetto_protos_perfetto_config_gpu_lite",
+        ":perfetto_protos_perfetto_config_inode_file_lite",
+        ":perfetto_protos_perfetto_config_interceptors_lite",
+        ":perfetto_protos_perfetto_config_lite",
+        ":perfetto_protos_perfetto_config_power_lite",
+        ":perfetto_protos_perfetto_config_process_stats_lite",
+        ":perfetto_protos_perfetto_config_profiling_lite",
+        ":perfetto_protos_perfetto_config_statsd_lite",
+        ":perfetto_protos_perfetto_config_sys_stats_lite",
+        ":perfetto_protos_perfetto_config_system_info_lite",
+        ":perfetto_protos_perfetto_config_track_event_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_lite)",
     out: [
         "external/perfetto/protos/perfetto/config/chrome/chrome_config.pb.h",
         "external/perfetto/protos/perfetto/config/chrome/scenario_config.pb.h",
@@ -3630,16 +3814,24 @@
 }
 
 // GN: //protos/perfetto/config/power:cpp
+filegroup {
+    name: "perfetto_protos_perfetto_config_power_cpp",
+    srcs: [
+        "protos/perfetto/config/power/android_power_config.proto",
+    ],
+}
+
+// GN: //protos/perfetto/config/power:cpp
 genrule {
     name: "perfetto_protos_perfetto_config_power_cpp_gen",
     srcs: [
-        "protos/perfetto/config/power/android_power_config.proto",
+        ":perfetto_protos_perfetto_config_power_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_power_cpp)",
     out: [
         "external/perfetto/protos/perfetto/config/power/android_power_config.gen.cc",
     ],
@@ -3649,13 +3841,13 @@
 genrule {
     name: "perfetto_protos_perfetto_config_power_cpp_gen_headers",
     srcs: [
-        "protos/perfetto/config/power/android_power_config.proto",
+        ":perfetto_protos_perfetto_config_power_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_power_cpp)",
     out: [
         "external/perfetto/protos/perfetto/config/power/android_power_config.gen.h",
     ],
@@ -3666,15 +3858,23 @@
 }
 
 // GN: //protos/perfetto/config/power:lite
+filegroup {
+    name: "perfetto_protos_perfetto_config_power_lite",
+    srcs: [
+        "protos/perfetto/config/power/android_power_config.proto",
+    ],
+}
+
+// GN: //protos/perfetto/config/power:lite
 genrule {
     name: "perfetto_protos_perfetto_config_power_lite_gen",
     srcs: [
-        "protos/perfetto/config/power/android_power_config.proto",
+        ":perfetto_protos_perfetto_config_power_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_power_lite)",
     out: [
         "external/perfetto/protos/perfetto/config/power/android_power_config.pb.cc",
     ],
@@ -3684,12 +3884,12 @@
 genrule {
     name: "perfetto_protos_perfetto_config_power_lite_gen_headers",
     srcs: [
-        "protos/perfetto/config/power/android_power_config.proto",
+        ":perfetto_protos_perfetto_config_power_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_power_lite)",
     out: [
         "external/perfetto/protos/perfetto/config/power/android_power_config.pb.h",
     ],
@@ -3700,16 +3900,24 @@
 }
 
 // GN: //protos/perfetto/config/power:zero
+filegroup {
+    name: "perfetto_protos_perfetto_config_power_zero",
+    srcs: [
+        "protos/perfetto/config/power/android_power_config.proto",
+    ],
+}
+
+// GN: //protos/perfetto/config/power:zero
 genrule {
     name: "perfetto_protos_perfetto_config_power_zero_gen",
     srcs: [
-        "protos/perfetto/config/power/android_power_config.proto",
+        ":perfetto_protos_perfetto_config_power_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_power_zero)",
     out: [
         "external/perfetto/protos/perfetto/config/power/android_power_config.pbzero.cc",
     ],
@@ -3719,13 +3927,13 @@
 genrule {
     name: "perfetto_protos_perfetto_config_power_zero_gen_headers",
     srcs: [
-        "protos/perfetto/config/power/android_power_config.proto",
+        ":perfetto_protos_perfetto_config_power_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_power_zero)",
     out: [
         "external/perfetto/protos/perfetto/config/power/android_power_config.pbzero.h",
     ],
@@ -3736,16 +3944,24 @@
 }
 
 // GN: //protos/perfetto/config/process_stats:cpp
+filegroup {
+    name: "perfetto_protos_perfetto_config_process_stats_cpp",
+    srcs: [
+        "protos/perfetto/config/process_stats/process_stats_config.proto",
+    ],
+}
+
+// GN: //protos/perfetto/config/process_stats:cpp
 genrule {
     name: "perfetto_protos_perfetto_config_process_stats_cpp_gen",
     srcs: [
-        "protos/perfetto/config/process_stats/process_stats_config.proto",
+        ":perfetto_protos_perfetto_config_process_stats_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_process_stats_cpp)",
     out: [
         "external/perfetto/protos/perfetto/config/process_stats/process_stats_config.gen.cc",
     ],
@@ -3755,13 +3971,13 @@
 genrule {
     name: "perfetto_protos_perfetto_config_process_stats_cpp_gen_headers",
     srcs: [
-        "protos/perfetto/config/process_stats/process_stats_config.proto",
+        ":perfetto_protos_perfetto_config_process_stats_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_process_stats_cpp)",
     out: [
         "external/perfetto/protos/perfetto/config/process_stats/process_stats_config.gen.h",
     ],
@@ -3772,15 +3988,23 @@
 }
 
 // GN: //protos/perfetto/config/process_stats:lite
+filegroup {
+    name: "perfetto_protos_perfetto_config_process_stats_lite",
+    srcs: [
+        "protos/perfetto/config/process_stats/process_stats_config.proto",
+    ],
+}
+
+// GN: //protos/perfetto/config/process_stats:lite
 genrule {
     name: "perfetto_protos_perfetto_config_process_stats_lite_gen",
     srcs: [
-        "protos/perfetto/config/process_stats/process_stats_config.proto",
+        ":perfetto_protos_perfetto_config_process_stats_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_process_stats_lite)",
     out: [
         "external/perfetto/protos/perfetto/config/process_stats/process_stats_config.pb.cc",
     ],
@@ -3790,12 +4014,12 @@
 genrule {
     name: "perfetto_protos_perfetto_config_process_stats_lite_gen_headers",
     srcs: [
-        "protos/perfetto/config/process_stats/process_stats_config.proto",
+        ":perfetto_protos_perfetto_config_process_stats_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_process_stats_lite)",
     out: [
         "external/perfetto/protos/perfetto/config/process_stats/process_stats_config.pb.h",
     ],
@@ -3806,16 +4030,24 @@
 }
 
 // GN: //protos/perfetto/config/process_stats:zero
+filegroup {
+    name: "perfetto_protos_perfetto_config_process_stats_zero",
+    srcs: [
+        "protos/perfetto/config/process_stats/process_stats_config.proto",
+    ],
+}
+
+// GN: //protos/perfetto/config/process_stats:zero
 genrule {
     name: "perfetto_protos_perfetto_config_process_stats_zero_gen",
     srcs: [
-        "protos/perfetto/config/process_stats/process_stats_config.proto",
+        ":perfetto_protos_perfetto_config_process_stats_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_process_stats_zero)",
     out: [
         "external/perfetto/protos/perfetto/config/process_stats/process_stats_config.pbzero.cc",
     ],
@@ -3825,13 +4057,13 @@
 genrule {
     name: "perfetto_protos_perfetto_config_process_stats_zero_gen_headers",
     srcs: [
-        "protos/perfetto/config/process_stats/process_stats_config.proto",
+        ":perfetto_protos_perfetto_config_process_stats_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_process_stats_zero)",
     out: [
         "external/perfetto/protos/perfetto/config/process_stats/process_stats_config.pbzero.h",
     ],
@@ -3842,18 +4074,27 @@
 }
 
 // GN: //protos/perfetto/config/profiling:cpp
-genrule {
-    name: "perfetto_protos_perfetto_config_profiling_cpp_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_config_profiling_cpp",
     srcs: [
         "protos/perfetto/config/profiling/heapprofd_config.proto",
         "protos/perfetto/config/profiling/java_hprof_config.proto",
         "protos/perfetto/config/profiling/perf_event_config.proto",
     ],
+}
+
+// GN: //protos/perfetto/config/profiling:cpp
+genrule {
+    name: "perfetto_protos_perfetto_config_profiling_cpp_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_common_cpp",
+        ":perfetto_protos_perfetto_config_profiling_cpp",
+    ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_profiling_cpp)",
     out: [
         "external/perfetto/protos/perfetto/config/profiling/heapprofd_config.gen.cc",
         "external/perfetto/protos/perfetto/config/profiling/java_hprof_config.gen.cc",
@@ -3865,15 +4106,14 @@
 genrule {
     name: "perfetto_protos_perfetto_config_profiling_cpp_gen_headers",
     srcs: [
-        "protos/perfetto/config/profiling/heapprofd_config.proto",
-        "protos/perfetto/config/profiling/java_hprof_config.proto",
-        "protos/perfetto/config/profiling/perf_event_config.proto",
+        ":perfetto_protos_perfetto_common_cpp",
+        ":perfetto_protos_perfetto_config_profiling_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_profiling_cpp)",
     out: [
         "external/perfetto/protos/perfetto/config/profiling/heapprofd_config.gen.h",
         "external/perfetto/protos/perfetto/config/profiling/java_hprof_config.gen.h",
@@ -3886,17 +4126,26 @@
 }
 
 // GN: //protos/perfetto/config/profiling:lite
-genrule {
-    name: "perfetto_protos_perfetto_config_profiling_lite_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_config_profiling_lite",
     srcs: [
         "protos/perfetto/config/profiling/heapprofd_config.proto",
         "protos/perfetto/config/profiling/java_hprof_config.proto",
         "protos/perfetto/config/profiling/perf_event_config.proto",
     ],
+}
+
+// GN: //protos/perfetto/config/profiling:lite
+genrule {
+    name: "perfetto_protos_perfetto_config_profiling_lite_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_common_lite",
+        ":perfetto_protos_perfetto_config_profiling_lite",
+    ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_profiling_lite)",
     out: [
         "external/perfetto/protos/perfetto/config/profiling/heapprofd_config.pb.cc",
         "external/perfetto/protos/perfetto/config/profiling/java_hprof_config.pb.cc",
@@ -3908,14 +4157,13 @@
 genrule {
     name: "perfetto_protos_perfetto_config_profiling_lite_gen_headers",
     srcs: [
-        "protos/perfetto/config/profiling/heapprofd_config.proto",
-        "protos/perfetto/config/profiling/java_hprof_config.proto",
-        "protos/perfetto/config/profiling/perf_event_config.proto",
+        ":perfetto_protos_perfetto_common_lite",
+        ":perfetto_protos_perfetto_config_profiling_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_profiling_lite)",
     out: [
         "external/perfetto/protos/perfetto/config/profiling/heapprofd_config.pb.h",
         "external/perfetto/protos/perfetto/config/profiling/java_hprof_config.pb.h",
@@ -3928,18 +4176,27 @@
 }
 
 // GN: //protos/perfetto/config/profiling:zero
-genrule {
-    name: "perfetto_protos_perfetto_config_profiling_zero_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_config_profiling_zero",
     srcs: [
         "protos/perfetto/config/profiling/heapprofd_config.proto",
         "protos/perfetto/config/profiling/java_hprof_config.proto",
         "protos/perfetto/config/profiling/perf_event_config.proto",
     ],
+}
+
+// GN: //protos/perfetto/config/profiling:zero
+genrule {
+    name: "perfetto_protos_perfetto_config_profiling_zero_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_common_zero",
+        ":perfetto_protos_perfetto_config_profiling_zero",
+    ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_profiling_zero)",
     out: [
         "external/perfetto/protos/perfetto/config/profiling/heapprofd_config.pbzero.cc",
         "external/perfetto/protos/perfetto/config/profiling/java_hprof_config.pbzero.cc",
@@ -3951,15 +4208,14 @@
 genrule {
     name: "perfetto_protos_perfetto_config_profiling_zero_gen_headers",
     srcs: [
-        "protos/perfetto/config/profiling/heapprofd_config.proto",
-        "protos/perfetto/config/profiling/java_hprof_config.proto",
-        "protos/perfetto/config/profiling/perf_event_config.proto",
+        ":perfetto_protos_perfetto_common_zero",
+        ":perfetto_protos_perfetto_config_profiling_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_profiling_zero)",
     out: [
         "external/perfetto/protos/perfetto/config/profiling/heapprofd_config.pbzero.h",
         "external/perfetto/protos/perfetto/config/profiling/java_hprof_config.pbzero.h",
@@ -3972,17 +4228,25 @@
 }
 
 // GN: //protos/perfetto/config/statsd:cpp
-genrule {
-    name: "perfetto_protos_perfetto_config_statsd_cpp_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_config_statsd_cpp",
     srcs: [
         "protos/perfetto/config/statsd/atom_ids.proto",
         "protos/perfetto/config/statsd/statsd_tracing_config.proto",
     ],
+}
+
+// GN: //protos/perfetto/config/statsd:cpp
+genrule {
+    name: "perfetto_protos_perfetto_config_statsd_cpp_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_config_statsd_cpp",
+    ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_statsd_cpp)",
     out: [
         "external/perfetto/protos/perfetto/config/statsd/atom_ids.gen.cc",
         "external/perfetto/protos/perfetto/config/statsd/statsd_tracing_config.gen.cc",
@@ -3993,14 +4257,13 @@
 genrule {
     name: "perfetto_protos_perfetto_config_statsd_cpp_gen_headers",
     srcs: [
-        "protos/perfetto/config/statsd/atom_ids.proto",
-        "protos/perfetto/config/statsd/statsd_tracing_config.proto",
+        ":perfetto_protos_perfetto_config_statsd_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_statsd_cpp)",
     out: [
         "external/perfetto/protos/perfetto/config/statsd/atom_ids.gen.h",
         "external/perfetto/protos/perfetto/config/statsd/statsd_tracing_config.gen.h",
@@ -4012,16 +4275,24 @@
 }
 
 // GN: //protos/perfetto/config/statsd:lite
-genrule {
-    name: "perfetto_protos_perfetto_config_statsd_lite_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_config_statsd_lite",
     srcs: [
         "protos/perfetto/config/statsd/atom_ids.proto",
         "protos/perfetto/config/statsd/statsd_tracing_config.proto",
     ],
+}
+
+// GN: //protos/perfetto/config/statsd:lite
+genrule {
+    name: "perfetto_protos_perfetto_config_statsd_lite_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_config_statsd_lite",
+    ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_statsd_lite)",
     out: [
         "external/perfetto/protos/perfetto/config/statsd/atom_ids.pb.cc",
         "external/perfetto/protos/perfetto/config/statsd/statsd_tracing_config.pb.cc",
@@ -4032,13 +4303,12 @@
 genrule {
     name: "perfetto_protos_perfetto_config_statsd_lite_gen_headers",
     srcs: [
-        "protos/perfetto/config/statsd/atom_ids.proto",
-        "protos/perfetto/config/statsd/statsd_tracing_config.proto",
+        ":perfetto_protos_perfetto_config_statsd_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_statsd_lite)",
     out: [
         "external/perfetto/protos/perfetto/config/statsd/atom_ids.pb.h",
         "external/perfetto/protos/perfetto/config/statsd/statsd_tracing_config.pb.h",
@@ -4050,17 +4320,25 @@
 }
 
 // GN: //protos/perfetto/config/statsd:zero
-genrule {
-    name: "perfetto_protos_perfetto_config_statsd_zero_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_config_statsd_zero",
     srcs: [
         "protos/perfetto/config/statsd/atom_ids.proto",
         "protos/perfetto/config/statsd/statsd_tracing_config.proto",
     ],
+}
+
+// GN: //protos/perfetto/config/statsd:zero
+genrule {
+    name: "perfetto_protos_perfetto_config_statsd_zero_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_config_statsd_zero",
+    ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_statsd_zero)",
     out: [
         "external/perfetto/protos/perfetto/config/statsd/atom_ids.pbzero.cc",
         "external/perfetto/protos/perfetto/config/statsd/statsd_tracing_config.pbzero.cc",
@@ -4071,14 +4349,13 @@
 genrule {
     name: "perfetto_protos_perfetto_config_statsd_zero_gen_headers",
     srcs: [
-        "protos/perfetto/config/statsd/atom_ids.proto",
-        "protos/perfetto/config/statsd/statsd_tracing_config.proto",
+        ":perfetto_protos_perfetto_config_statsd_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_statsd_zero)",
     out: [
         "external/perfetto/protos/perfetto/config/statsd/atom_ids.pbzero.h",
         "external/perfetto/protos/perfetto/config/statsd/statsd_tracing_config.pbzero.h",
@@ -4090,16 +4367,25 @@
 }
 
 // GN: //protos/perfetto/config/sys_stats:cpp
+filegroup {
+    name: "perfetto_protos_perfetto_config_sys_stats_cpp",
+    srcs: [
+        "protos/perfetto/config/sys_stats/sys_stats_config.proto",
+    ],
+}
+
+// GN: //protos/perfetto/config/sys_stats:cpp
 genrule {
     name: "perfetto_protos_perfetto_config_sys_stats_cpp_gen",
     srcs: [
-        "protos/perfetto/config/sys_stats/sys_stats_config.proto",
+        ":perfetto_protos_perfetto_common_cpp",
+        ":perfetto_protos_perfetto_config_sys_stats_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_sys_stats_cpp)",
     out: [
         "external/perfetto/protos/perfetto/config/sys_stats/sys_stats_config.gen.cc",
     ],
@@ -4109,13 +4395,14 @@
 genrule {
     name: "perfetto_protos_perfetto_config_sys_stats_cpp_gen_headers",
     srcs: [
-        "protos/perfetto/config/sys_stats/sys_stats_config.proto",
+        ":perfetto_protos_perfetto_common_cpp",
+        ":perfetto_protos_perfetto_config_sys_stats_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_sys_stats_cpp)",
     out: [
         "external/perfetto/protos/perfetto/config/sys_stats/sys_stats_config.gen.h",
     ],
@@ -4126,15 +4413,24 @@
 }
 
 // GN: //protos/perfetto/config/sys_stats:lite
+filegroup {
+    name: "perfetto_protos_perfetto_config_sys_stats_lite",
+    srcs: [
+        "protos/perfetto/config/sys_stats/sys_stats_config.proto",
+    ],
+}
+
+// GN: //protos/perfetto/config/sys_stats:lite
 genrule {
     name: "perfetto_protos_perfetto_config_sys_stats_lite_gen",
     srcs: [
-        "protos/perfetto/config/sys_stats/sys_stats_config.proto",
+        ":perfetto_protos_perfetto_common_lite",
+        ":perfetto_protos_perfetto_config_sys_stats_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_sys_stats_lite)",
     out: [
         "external/perfetto/protos/perfetto/config/sys_stats/sys_stats_config.pb.cc",
     ],
@@ -4144,12 +4440,13 @@
 genrule {
     name: "perfetto_protos_perfetto_config_sys_stats_lite_gen_headers",
     srcs: [
-        "protos/perfetto/config/sys_stats/sys_stats_config.proto",
+        ":perfetto_protos_perfetto_common_lite",
+        ":perfetto_protos_perfetto_config_sys_stats_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_sys_stats_lite)",
     out: [
         "external/perfetto/protos/perfetto/config/sys_stats/sys_stats_config.pb.h",
     ],
@@ -4160,16 +4457,25 @@
 }
 
 // GN: //protos/perfetto/config/sys_stats:zero
+filegroup {
+    name: "perfetto_protos_perfetto_config_sys_stats_zero",
+    srcs: [
+        "protos/perfetto/config/sys_stats/sys_stats_config.proto",
+    ],
+}
+
+// GN: //protos/perfetto/config/sys_stats:zero
 genrule {
     name: "perfetto_protos_perfetto_config_sys_stats_zero_gen",
     srcs: [
-        "protos/perfetto/config/sys_stats/sys_stats_config.proto",
+        ":perfetto_protos_perfetto_common_zero",
+        ":perfetto_protos_perfetto_config_sys_stats_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_sys_stats_zero)",
     out: [
         "external/perfetto/protos/perfetto/config/sys_stats/sys_stats_config.pbzero.cc",
     ],
@@ -4179,13 +4485,14 @@
 genrule {
     name: "perfetto_protos_perfetto_config_sys_stats_zero_gen_headers",
     srcs: [
-        "protos/perfetto/config/sys_stats/sys_stats_config.proto",
+        ":perfetto_protos_perfetto_common_zero",
+        ":perfetto_protos_perfetto_config_sys_stats_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_sys_stats_zero)",
     out: [
         "external/perfetto/protos/perfetto/config/sys_stats/sys_stats_config.pbzero.h",
     ],
@@ -4196,16 +4503,25 @@
 }
 
 // GN: //protos/perfetto/config/system_info:cpp
+filegroup {
+    name: "perfetto_protos_perfetto_config_system_info_cpp",
+    srcs: [
+        "protos/perfetto/config/system_info/system_info.proto",
+    ],
+}
+
+// GN: //protos/perfetto/config/system_info:cpp
 genrule {
     name: "perfetto_protos_perfetto_config_system_info_cpp_gen",
     srcs: [
-        "protos/perfetto/config/system_info/system_info.proto",
+        ":perfetto_protos_perfetto_common_cpp",
+        ":perfetto_protos_perfetto_config_system_info_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_system_info_cpp)",
     out: [
         "external/perfetto/protos/perfetto/config/system_info/system_info.gen.cc",
     ],
@@ -4215,13 +4531,14 @@
 genrule {
     name: "perfetto_protos_perfetto_config_system_info_cpp_gen_headers",
     srcs: [
-        "protos/perfetto/config/system_info/system_info.proto",
+        ":perfetto_protos_perfetto_common_cpp",
+        ":perfetto_protos_perfetto_config_system_info_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_system_info_cpp)",
     out: [
         "external/perfetto/protos/perfetto/config/system_info/system_info.gen.h",
     ],
@@ -4232,15 +4549,24 @@
 }
 
 // GN: //protos/perfetto/config/system_info:lite
+filegroup {
+    name: "perfetto_protos_perfetto_config_system_info_lite",
+    srcs: [
+        "protos/perfetto/config/system_info/system_info.proto",
+    ],
+}
+
+// GN: //protos/perfetto/config/system_info:lite
 genrule {
     name: "perfetto_protos_perfetto_config_system_info_lite_gen",
     srcs: [
-        "protos/perfetto/config/system_info/system_info.proto",
+        ":perfetto_protos_perfetto_common_lite",
+        ":perfetto_protos_perfetto_config_system_info_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_system_info_lite)",
     out: [
         "external/perfetto/protos/perfetto/config/system_info/system_info.pb.cc",
     ],
@@ -4250,12 +4576,13 @@
 genrule {
     name: "perfetto_protos_perfetto_config_system_info_lite_gen_headers",
     srcs: [
-        "protos/perfetto/config/system_info/system_info.proto",
+        ":perfetto_protos_perfetto_common_lite",
+        ":perfetto_protos_perfetto_config_system_info_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_system_info_lite)",
     out: [
         "external/perfetto/protos/perfetto/config/system_info/system_info.pb.h",
     ],
@@ -4266,16 +4593,25 @@
 }
 
 // GN: //protos/perfetto/config/system_info:zero
+filegroup {
+    name: "perfetto_protos_perfetto_config_system_info_zero",
+    srcs: [
+        "protos/perfetto/config/system_info/system_info.proto",
+    ],
+}
+
+// GN: //protos/perfetto/config/system_info:zero
 genrule {
     name: "perfetto_protos_perfetto_config_system_info_zero_gen",
     srcs: [
-        "protos/perfetto/config/system_info/system_info.proto",
+        ":perfetto_protos_perfetto_common_zero",
+        ":perfetto_protos_perfetto_config_system_info_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_system_info_zero)",
     out: [
         "external/perfetto/protos/perfetto/config/system_info/system_info.pbzero.cc",
     ],
@@ -4285,13 +4621,14 @@
 genrule {
     name: "perfetto_protos_perfetto_config_system_info_zero_gen_headers",
     srcs: [
-        "protos/perfetto/config/system_info/system_info.proto",
+        ":perfetto_protos_perfetto_common_zero",
+        ":perfetto_protos_perfetto_config_system_info_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_system_info_zero)",
     out: [
         "external/perfetto/protos/perfetto/config/system_info/system_info.pbzero.h",
     ],
@@ -4302,16 +4639,24 @@
 }
 
 // GN: //protos/perfetto/config/track_event:cpp
+filegroup {
+    name: "perfetto_protos_perfetto_config_track_event_cpp",
+    srcs: [
+        "protos/perfetto/config/track_event/track_event_config.proto",
+    ],
+}
+
+// GN: //protos/perfetto/config/track_event:cpp
 genrule {
     name: "perfetto_protos_perfetto_config_track_event_cpp_gen",
     srcs: [
-        "protos/perfetto/config/track_event/track_event_config.proto",
+        ":perfetto_protos_perfetto_config_track_event_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_track_event_cpp)",
     out: [
         "external/perfetto/protos/perfetto/config/track_event/track_event_config.gen.cc",
     ],
@@ -4321,13 +4666,13 @@
 genrule {
     name: "perfetto_protos_perfetto_config_track_event_cpp_gen_headers",
     srcs: [
-        "protos/perfetto/config/track_event/track_event_config.proto",
+        ":perfetto_protos_perfetto_config_track_event_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_track_event_cpp)",
     out: [
         "external/perfetto/protos/perfetto/config/track_event/track_event_config.gen.h",
     ],
@@ -4338,15 +4683,23 @@
 }
 
 // GN: //protos/perfetto/config/track_event:lite
+filegroup {
+    name: "perfetto_protos_perfetto_config_track_event_lite",
+    srcs: [
+        "protos/perfetto/config/track_event/track_event_config.proto",
+    ],
+}
+
+// GN: //protos/perfetto/config/track_event:lite
 genrule {
     name: "perfetto_protos_perfetto_config_track_event_lite_gen",
     srcs: [
-        "protos/perfetto/config/track_event/track_event_config.proto",
+        ":perfetto_protos_perfetto_config_track_event_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_track_event_lite)",
     out: [
         "external/perfetto/protos/perfetto/config/track_event/track_event_config.pb.cc",
     ],
@@ -4356,12 +4709,12 @@
 genrule {
     name: "perfetto_protos_perfetto_config_track_event_lite_gen_headers",
     srcs: [
-        "protos/perfetto/config/track_event/track_event_config.proto",
+        ":perfetto_protos_perfetto_config_track_event_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_track_event_lite)",
     out: [
         "external/perfetto/protos/perfetto/config/track_event/track_event_config.pb.h",
     ],
@@ -4372,16 +4725,24 @@
 }
 
 // GN: //protos/perfetto/config/track_event:zero
+filegroup {
+    name: "perfetto_protos_perfetto_config_track_event_zero",
+    srcs: [
+        "protos/perfetto/config/track_event/track_event_config.proto",
+    ],
+}
+
+// GN: //protos/perfetto/config/track_event:zero
 genrule {
     name: "perfetto_protos_perfetto_config_track_event_zero_gen",
     srcs: [
-        "protos/perfetto/config/track_event/track_event_config.proto",
+        ":perfetto_protos_perfetto_config_track_event_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_track_event_zero)",
     out: [
         "external/perfetto/protos/perfetto/config/track_event/track_event_config.pbzero.cc",
     ],
@@ -4391,13 +4752,13 @@
 genrule {
     name: "perfetto_protos_perfetto_config_track_event_zero_gen_headers",
     srcs: [
-        "protos/perfetto/config/track_event/track_event_config.proto",
+        ":perfetto_protos_perfetto_config_track_event_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_track_event_zero)",
     out: [
         "external/perfetto/protos/perfetto/config/track_event/track_event_config.pbzero.h",
     ],
@@ -4408,8 +4769,8 @@
 }
 
 // GN: //protos/perfetto/config:zero
-genrule {
-    name: "perfetto_protos_perfetto_config_zero_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_config_zero",
     srcs: [
         "protos/perfetto/config/chrome/chrome_config.proto",
         "protos/perfetto/config/chrome/scenario_config.proto",
@@ -4419,11 +4780,32 @@
         "protos/perfetto/config/test_config.proto",
         "protos/perfetto/config/trace_config.proto",
     ],
+}
+
+// GN: //protos/perfetto/config:zero
+genrule {
+    name: "perfetto_protos_perfetto_config_zero_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_common_zero",
+        ":perfetto_protos_perfetto_config_android_zero",
+        ":perfetto_protos_perfetto_config_ftrace_zero",
+        ":perfetto_protos_perfetto_config_gpu_zero",
+        ":perfetto_protos_perfetto_config_inode_file_zero",
+        ":perfetto_protos_perfetto_config_interceptors_zero",
+        ":perfetto_protos_perfetto_config_power_zero",
+        ":perfetto_protos_perfetto_config_process_stats_zero",
+        ":perfetto_protos_perfetto_config_profiling_zero",
+        ":perfetto_protos_perfetto_config_statsd_zero",
+        ":perfetto_protos_perfetto_config_sys_stats_zero",
+        ":perfetto_protos_perfetto_config_system_info_zero",
+        ":perfetto_protos_perfetto_config_track_event_zero",
+        ":perfetto_protos_perfetto_config_zero",
+    ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_zero)",
     out: [
         "external/perfetto/protos/perfetto/config/chrome/chrome_config.pbzero.cc",
         "external/perfetto/protos/perfetto/config/chrome/scenario_config.pbzero.cc",
@@ -4439,19 +4821,26 @@
 genrule {
     name: "perfetto_protos_perfetto_config_zero_gen_headers",
     srcs: [
-        "protos/perfetto/config/chrome/chrome_config.proto",
-        "protos/perfetto/config/chrome/scenario_config.proto",
-        "protos/perfetto/config/data_source_config.proto",
-        "protos/perfetto/config/interceptor_config.proto",
-        "protos/perfetto/config/stress_test_config.proto",
-        "protos/perfetto/config/test_config.proto",
-        "protos/perfetto/config/trace_config.proto",
+        ":perfetto_protos_perfetto_common_zero",
+        ":perfetto_protos_perfetto_config_android_zero",
+        ":perfetto_protos_perfetto_config_ftrace_zero",
+        ":perfetto_protos_perfetto_config_gpu_zero",
+        ":perfetto_protos_perfetto_config_inode_file_zero",
+        ":perfetto_protos_perfetto_config_interceptors_zero",
+        ":perfetto_protos_perfetto_config_power_zero",
+        ":perfetto_protos_perfetto_config_process_stats_zero",
+        ":perfetto_protos_perfetto_config_profiling_zero",
+        ":perfetto_protos_perfetto_config_statsd_zero",
+        ":perfetto_protos_perfetto_config_sys_stats_zero",
+        ":perfetto_protos_perfetto_config_system_info_zero",
+        ":perfetto_protos_perfetto_config_track_event_zero",
+        ":perfetto_protos_perfetto_config_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_config_zero)",
     out: [
         "external/perfetto/protos/perfetto/config/chrome/chrome_config.pbzero.h",
         "external/perfetto/protos/perfetto/config/chrome/scenario_config.pbzero.h",
@@ -4468,17 +4857,39 @@
 }
 
 // GN: //protos/perfetto/ipc:cpp
-genrule {
-    name: "perfetto_protos_perfetto_ipc_cpp_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_ipc_cpp",
     srcs: [
         "protos/perfetto/ipc/consumer_port.proto",
         "protos/perfetto/ipc/producer_port.proto",
     ],
+}
+
+// GN: //protos/perfetto/ipc:cpp
+genrule {
+    name: "perfetto_protos_perfetto_ipc_cpp_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_common_cpp",
+        ":perfetto_protos_perfetto_config_android_cpp",
+        ":perfetto_protos_perfetto_config_cpp",
+        ":perfetto_protos_perfetto_config_ftrace_cpp",
+        ":perfetto_protos_perfetto_config_gpu_cpp",
+        ":perfetto_protos_perfetto_config_inode_file_cpp",
+        ":perfetto_protos_perfetto_config_interceptors_cpp",
+        ":perfetto_protos_perfetto_config_power_cpp",
+        ":perfetto_protos_perfetto_config_process_stats_cpp",
+        ":perfetto_protos_perfetto_config_profiling_cpp",
+        ":perfetto_protos_perfetto_config_statsd_cpp",
+        ":perfetto_protos_perfetto_config_sys_stats_cpp",
+        ":perfetto_protos_perfetto_config_system_info_cpp",
+        ":perfetto_protos_perfetto_config_track_event_cpp",
+        ":perfetto_protos_perfetto_ipc_cpp",
+    ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_ipc_cpp)",
     out: [
         "external/perfetto/protos/perfetto/ipc/consumer_port.gen.cc",
         "external/perfetto/protos/perfetto/ipc/producer_port.gen.cc",
@@ -4489,14 +4900,27 @@
 genrule {
     name: "perfetto_protos_perfetto_ipc_cpp_gen_headers",
     srcs: [
-        "protos/perfetto/ipc/consumer_port.proto",
-        "protos/perfetto/ipc/producer_port.proto",
+        ":perfetto_protos_perfetto_common_cpp",
+        ":perfetto_protos_perfetto_config_android_cpp",
+        ":perfetto_protos_perfetto_config_cpp",
+        ":perfetto_protos_perfetto_config_ftrace_cpp",
+        ":perfetto_protos_perfetto_config_gpu_cpp",
+        ":perfetto_protos_perfetto_config_inode_file_cpp",
+        ":perfetto_protos_perfetto_config_interceptors_cpp",
+        ":perfetto_protos_perfetto_config_power_cpp",
+        ":perfetto_protos_perfetto_config_process_stats_cpp",
+        ":perfetto_protos_perfetto_config_profiling_cpp",
+        ":perfetto_protos_perfetto_config_statsd_cpp",
+        ":perfetto_protos_perfetto_config_sys_stats_cpp",
+        ":perfetto_protos_perfetto_config_system_info_cpp",
+        ":perfetto_protos_perfetto_config_track_event_cpp",
+        ":perfetto_protos_perfetto_ipc_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_ipc_cpp)",
     out: [
         "external/perfetto/protos/perfetto/ipc/consumer_port.gen.h",
         "external/perfetto/protos/perfetto/ipc/producer_port.gen.h",
@@ -4508,17 +4932,41 @@
 }
 
 // GN: //protos/perfetto/ipc:ipc
-genrule {
-    name: "perfetto_protos_perfetto_ipc_ipc_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_ipc_ipc",
     srcs: [
         "protos/perfetto/ipc/consumer_port.proto",
         "protos/perfetto/ipc/producer_port.proto",
     ],
+}
+
+// GN: //protos/perfetto/ipc:ipc
+genrule {
+    name: "perfetto_protos_perfetto_ipc_ipc_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_common_cpp",
+        ":perfetto_protos_perfetto_config_android_cpp",
+        ":perfetto_protos_perfetto_config_cpp",
+        ":perfetto_protos_perfetto_config_ftrace_cpp",
+        ":perfetto_protos_perfetto_config_gpu_cpp",
+        ":perfetto_protos_perfetto_config_inode_file_cpp",
+        ":perfetto_protos_perfetto_config_interceptors_cpp",
+        ":perfetto_protos_perfetto_config_power_cpp",
+        ":perfetto_protos_perfetto_config_process_stats_cpp",
+        ":perfetto_protos_perfetto_config_profiling_cpp",
+        ":perfetto_protos_perfetto_config_statsd_cpp",
+        ":perfetto_protos_perfetto_config_sys_stats_cpp",
+        ":perfetto_protos_perfetto_config_system_info_cpp",
+        ":perfetto_protos_perfetto_config_track_event_cpp",
+        ":perfetto_protos_perfetto_ipc_cpp",
+        ":perfetto_protos_perfetto_ipc_ipc",
+        ":perfetto_protos_perfetto_ipc_wire_protocol_cpp",
+    ],
     tools: [
         "aprotoc",
         "ipc_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location ipc_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location ipc_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_ipc_ipc)",
     out: [
         "external/perfetto/protos/perfetto/ipc/consumer_port.ipc.cc",
         "external/perfetto/protos/perfetto/ipc/producer_port.ipc.cc",
@@ -4529,14 +4977,29 @@
 genrule {
     name: "perfetto_protos_perfetto_ipc_ipc_gen_headers",
     srcs: [
-        "protos/perfetto/ipc/consumer_port.proto",
-        "protos/perfetto/ipc/producer_port.proto",
+        ":perfetto_protos_perfetto_common_cpp",
+        ":perfetto_protos_perfetto_config_android_cpp",
+        ":perfetto_protos_perfetto_config_cpp",
+        ":perfetto_protos_perfetto_config_ftrace_cpp",
+        ":perfetto_protos_perfetto_config_gpu_cpp",
+        ":perfetto_protos_perfetto_config_inode_file_cpp",
+        ":perfetto_protos_perfetto_config_interceptors_cpp",
+        ":perfetto_protos_perfetto_config_power_cpp",
+        ":perfetto_protos_perfetto_config_process_stats_cpp",
+        ":perfetto_protos_perfetto_config_profiling_cpp",
+        ":perfetto_protos_perfetto_config_statsd_cpp",
+        ":perfetto_protos_perfetto_config_sys_stats_cpp",
+        ":perfetto_protos_perfetto_config_system_info_cpp",
+        ":perfetto_protos_perfetto_config_track_event_cpp",
+        ":perfetto_protos_perfetto_ipc_cpp",
+        ":perfetto_protos_perfetto_ipc_ipc",
+        ":perfetto_protos_perfetto_ipc_wire_protocol_cpp",
     ],
     tools: [
         "aprotoc",
         "ipc_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location ipc_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location ipc_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_ipc_ipc)",
     out: [
         "external/perfetto/protos/perfetto/ipc/consumer_port.ipc.h",
         "external/perfetto/protos/perfetto/ipc/producer_port.ipc.h",
@@ -4548,16 +5011,24 @@
 }
 
 // GN: //protos/perfetto/ipc:wire_protocol_cpp
+filegroup {
+    name: "perfetto_protos_perfetto_ipc_wire_protocol_cpp",
+    srcs: [
+        "protos/perfetto/ipc/wire_protocol.proto",
+    ],
+}
+
+// GN: //protos/perfetto/ipc:wire_protocol_cpp
 genrule {
     name: "perfetto_protos_perfetto_ipc_wire_protocol_cpp_gen",
     srcs: [
-        "protos/perfetto/ipc/wire_protocol.proto",
+        ":perfetto_protos_perfetto_ipc_wire_protocol_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_ipc_wire_protocol_cpp)",
     out: [
         "external/perfetto/protos/perfetto/ipc/wire_protocol.gen.cc",
     ],
@@ -4567,13 +5038,13 @@
 genrule {
     name: "perfetto_protos_perfetto_ipc_wire_protocol_cpp_gen_headers",
     srcs: [
-        "protos/perfetto/ipc/wire_protocol.proto",
+        ":perfetto_protos_perfetto_ipc_wire_protocol_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_ipc_wire_protocol_cpp)",
     out: [
         "external/perfetto/protos/perfetto/ipc/wire_protocol.gen.h",
     ],
@@ -4587,6 +5058,8 @@
 genrule {
     name: "perfetto_protos_perfetto_metrics_chrome_descriptor",
     srcs: [
+        ":libprotobuf-internal-descriptor-proto",
+        "protos/perfetto/metrics/android/ad_services_metric.proto",
         "protos/perfetto/metrics/android/android_blocking_call.proto",
         "protos/perfetto/metrics/android/android_blocking_calls_cuj_metric.proto",
         "protos/perfetto/metrics/android/android_boot.proto",
@@ -4668,6 +5141,7 @@
 genrule {
     name: "perfetto_protos_perfetto_metrics_descriptor",
     srcs: [
+        "protos/perfetto/metrics/android/ad_services_metric.proto",
         "protos/perfetto/metrics/android/android_blocking_call.proto",
         "protos/perfetto/metrics/android/android_blocking_calls_cuj_metric.proto",
         "protos/perfetto/metrics/android/android_boot.proto",
@@ -4732,6 +5206,7 @@
 genrule {
     name: "perfetto_protos_perfetto_metrics_webview_descriptor",
     srcs: [
+        "protos/perfetto/metrics/android/ad_services_metric.proto",
         "protos/perfetto/metrics/android/android_blocking_call.proto",
         "protos/perfetto/metrics/android/android_blocking_calls_cuj_metric.proto",
         "protos/perfetto/metrics/android/android_boot.proto",
@@ -4795,8 +5270,8 @@
 }
 
 // GN: //protos/perfetto/trace/android:cpp
-genrule {
-    name: "perfetto_protos_perfetto_trace_android_cpp_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_trace_android_cpp",
     srcs: [
         "protos/perfetto/trace/android/android_game_intervention_list.proto",
         "protos/perfetto/trace/android/android_log.proto",
@@ -4812,11 +5287,20 @@
         "protos/perfetto/trace/android/surfaceflinger_layers.proto",
         "protos/perfetto/trace/android/surfaceflinger_transactions.proto",
     ],
+}
+
+// GN: //protos/perfetto/trace/android:cpp
+genrule {
+    name: "perfetto_protos_perfetto_trace_android_cpp_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_common_cpp",
+        ":perfetto_protos_perfetto_trace_android_cpp",
+    ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_android_cpp)",
     out: [
         "external/perfetto/protos/perfetto/trace/android/android_game_intervention_list.gen.cc",
         "external/perfetto/protos/perfetto/trace/android/android_log.gen.cc",
@@ -4838,25 +5322,14 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_android_cpp_gen_headers",
     srcs: [
-        "protos/perfetto/trace/android/android_game_intervention_list.proto",
-        "protos/perfetto/trace/android/android_log.proto",
-        "protos/perfetto/trace/android/android_system_property.proto",
-        "protos/perfetto/trace/android/camera_event.proto",
-        "protos/perfetto/trace/android/frame_timeline_event.proto",
-        "protos/perfetto/trace/android/gpu_mem_event.proto",
-        "protos/perfetto/trace/android/graphics_frame_event.proto",
-        "protos/perfetto/trace/android/initial_display_state.proto",
-        "protos/perfetto/trace/android/network_trace.proto",
-        "protos/perfetto/trace/android/packages_list.proto",
-        "protos/perfetto/trace/android/surfaceflinger_common.proto",
-        "protos/perfetto/trace/android/surfaceflinger_layers.proto",
-        "protos/perfetto/trace/android/surfaceflinger_transactions.proto",
+        ":perfetto_protos_perfetto_common_cpp",
+        ":perfetto_protos_perfetto_trace_android_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_android_cpp)",
     out: [
         "external/perfetto/protos/perfetto/trace/android/android_game_intervention_list.gen.h",
         "external/perfetto/protos/perfetto/trace/android/android_log.gen.h",
@@ -4879,8 +5352,8 @@
 }
 
 // GN: //protos/perfetto/trace/android:lite
-genrule {
-    name: "perfetto_protos_perfetto_trace_android_lite_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_trace_android_lite",
     srcs: [
         "protos/perfetto/trace/android/android_game_intervention_list.proto",
         "protos/perfetto/trace/android/android_log.proto",
@@ -4896,10 +5369,19 @@
         "protos/perfetto/trace/android/surfaceflinger_layers.proto",
         "protos/perfetto/trace/android/surfaceflinger_transactions.proto",
     ],
+}
+
+// GN: //protos/perfetto/trace/android:lite
+genrule {
+    name: "perfetto_protos_perfetto_trace_android_lite_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_common_lite",
+        ":perfetto_protos_perfetto_trace_android_lite",
+    ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_android_lite)",
     out: [
         "external/perfetto/protos/perfetto/trace/android/android_game_intervention_list.pb.cc",
         "external/perfetto/protos/perfetto/trace/android/android_log.pb.cc",
@@ -4921,24 +5403,13 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_android_lite_gen_headers",
     srcs: [
-        "protos/perfetto/trace/android/android_game_intervention_list.proto",
-        "protos/perfetto/trace/android/android_log.proto",
-        "protos/perfetto/trace/android/android_system_property.proto",
-        "protos/perfetto/trace/android/camera_event.proto",
-        "protos/perfetto/trace/android/frame_timeline_event.proto",
-        "protos/perfetto/trace/android/gpu_mem_event.proto",
-        "protos/perfetto/trace/android/graphics_frame_event.proto",
-        "protos/perfetto/trace/android/initial_display_state.proto",
-        "protos/perfetto/trace/android/network_trace.proto",
-        "protos/perfetto/trace/android/packages_list.proto",
-        "protos/perfetto/trace/android/surfaceflinger_common.proto",
-        "protos/perfetto/trace/android/surfaceflinger_layers.proto",
-        "protos/perfetto/trace/android/surfaceflinger_transactions.proto",
+        ":perfetto_protos_perfetto_common_lite",
+        ":perfetto_protos_perfetto_trace_android_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_android_lite)",
     out: [
         "external/perfetto/protos/perfetto/trace/android/android_game_intervention_list.pb.h",
         "external/perfetto/protos/perfetto/trace/android/android_log.pb.h",
@@ -4979,8 +5450,8 @@
 }
 
 // GN: //protos/perfetto/trace/android:zero
-genrule {
-    name: "perfetto_protos_perfetto_trace_android_zero_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_trace_android_zero",
     srcs: [
         "protos/perfetto/trace/android/android_game_intervention_list.proto",
         "protos/perfetto/trace/android/android_log.proto",
@@ -4996,11 +5467,20 @@
         "protos/perfetto/trace/android/surfaceflinger_layers.proto",
         "protos/perfetto/trace/android/surfaceflinger_transactions.proto",
     ],
+}
+
+// GN: //protos/perfetto/trace/android:zero
+genrule {
+    name: "perfetto_protos_perfetto_trace_android_zero_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_common_zero",
+        ":perfetto_protos_perfetto_trace_android_zero",
+    ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_android_zero)",
     out: [
         "external/perfetto/protos/perfetto/trace/android/android_game_intervention_list.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/android/android_log.pbzero.cc",
@@ -5022,25 +5502,14 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_android_zero_gen_headers",
     srcs: [
-        "protos/perfetto/trace/android/android_game_intervention_list.proto",
-        "protos/perfetto/trace/android/android_log.proto",
-        "protos/perfetto/trace/android/android_system_property.proto",
-        "protos/perfetto/trace/android/camera_event.proto",
-        "protos/perfetto/trace/android/frame_timeline_event.proto",
-        "protos/perfetto/trace/android/gpu_mem_event.proto",
-        "protos/perfetto/trace/android/graphics_frame_event.proto",
-        "protos/perfetto/trace/android/initial_display_state.proto",
-        "protos/perfetto/trace/android/network_trace.proto",
-        "protos/perfetto/trace/android/packages_list.proto",
-        "protos/perfetto/trace/android/surfaceflinger_common.proto",
-        "protos/perfetto/trace/android/surfaceflinger_layers.proto",
-        "protos/perfetto/trace/android/surfaceflinger_transactions.proto",
+        ":perfetto_protos_perfetto_common_zero",
+        ":perfetto_protos_perfetto_trace_android_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_android_zero)",
     out: [
         "external/perfetto/protos/perfetto/trace/android/android_game_intervention_list.pbzero.h",
         "external/perfetto/protos/perfetto/trace/android/android_log.pbzero.h",
@@ -5063,18 +5532,26 @@
 }
 
 // GN: //protos/perfetto/trace/chrome:cpp
-genrule {
-    name: "perfetto_protos_perfetto_trace_chrome_cpp_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_trace_chrome_cpp",
     srcs: [
         "protos/perfetto/trace/chrome/chrome_benchmark_metadata.proto",
         "protos/perfetto/trace/chrome/chrome_metadata.proto",
         "protos/perfetto/trace/chrome/chrome_trace_event.proto",
     ],
+}
+
+// GN: //protos/perfetto/trace/chrome:cpp
+genrule {
+    name: "perfetto_protos_perfetto_trace_chrome_cpp_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_trace_chrome_cpp",
+    ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_chrome_cpp)",
     out: [
         "external/perfetto/protos/perfetto/trace/chrome/chrome_benchmark_metadata.gen.cc",
         "external/perfetto/protos/perfetto/trace/chrome/chrome_metadata.gen.cc",
@@ -5086,15 +5563,13 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_chrome_cpp_gen_headers",
     srcs: [
-        "protos/perfetto/trace/chrome/chrome_benchmark_metadata.proto",
-        "protos/perfetto/trace/chrome/chrome_metadata.proto",
-        "protos/perfetto/trace/chrome/chrome_trace_event.proto",
+        ":perfetto_protos_perfetto_trace_chrome_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_chrome_cpp)",
     out: [
         "external/perfetto/protos/perfetto/trace/chrome/chrome_benchmark_metadata.gen.h",
         "external/perfetto/protos/perfetto/trace/chrome/chrome_metadata.gen.h",
@@ -5107,17 +5582,25 @@
 }
 
 // GN: //protos/perfetto/trace/chrome:lite
-genrule {
-    name: "perfetto_protos_perfetto_trace_chrome_lite_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_trace_chrome_lite",
     srcs: [
         "protos/perfetto/trace/chrome/chrome_benchmark_metadata.proto",
         "protos/perfetto/trace/chrome/chrome_metadata.proto",
         "protos/perfetto/trace/chrome/chrome_trace_event.proto",
     ],
+}
+
+// GN: //protos/perfetto/trace/chrome:lite
+genrule {
+    name: "perfetto_protos_perfetto_trace_chrome_lite_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_trace_chrome_lite",
+    ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_chrome_lite)",
     out: [
         "external/perfetto/protos/perfetto/trace/chrome/chrome_benchmark_metadata.pb.cc",
         "external/perfetto/protos/perfetto/trace/chrome/chrome_metadata.pb.cc",
@@ -5129,14 +5612,12 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_chrome_lite_gen_headers",
     srcs: [
-        "protos/perfetto/trace/chrome/chrome_benchmark_metadata.proto",
-        "protos/perfetto/trace/chrome/chrome_metadata.proto",
-        "protos/perfetto/trace/chrome/chrome_trace_event.proto",
+        ":perfetto_protos_perfetto_trace_chrome_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_chrome_lite)",
     out: [
         "external/perfetto/protos/perfetto/trace/chrome/chrome_benchmark_metadata.pb.h",
         "external/perfetto/protos/perfetto/trace/chrome/chrome_metadata.pb.h",
@@ -5149,18 +5630,26 @@
 }
 
 // GN: //protos/perfetto/trace/chrome:zero
-genrule {
-    name: "perfetto_protos_perfetto_trace_chrome_zero_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_trace_chrome_zero",
     srcs: [
         "protos/perfetto/trace/chrome/chrome_benchmark_metadata.proto",
         "protos/perfetto/trace/chrome/chrome_metadata.proto",
         "protos/perfetto/trace/chrome/chrome_trace_event.proto",
     ],
+}
+
+// GN: //protos/perfetto/trace/chrome:zero
+genrule {
+    name: "perfetto_protos_perfetto_trace_chrome_zero_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_trace_chrome_zero",
+    ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_chrome_zero)",
     out: [
         "external/perfetto/protos/perfetto/trace/chrome/chrome_benchmark_metadata.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/chrome/chrome_metadata.pbzero.cc",
@@ -5172,15 +5661,13 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_chrome_zero_gen_headers",
     srcs: [
-        "protos/perfetto/trace/chrome/chrome_benchmark_metadata.proto",
-        "protos/perfetto/trace/chrome/chrome_metadata.proto",
-        "protos/perfetto/trace/chrome/chrome_trace_event.proto",
+        ":perfetto_protos_perfetto_trace_chrome_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_chrome_zero)",
     out: [
         "external/perfetto/protos/perfetto/trace/chrome/chrome_benchmark_metadata.pbzero.h",
         "external/perfetto/protos/perfetto/trace/chrome/chrome_metadata.pbzero.h",
@@ -5260,6 +5747,9 @@
         "protos/perfetto/trace/chrome/chrome_metadata.proto",
         "protos/perfetto/trace/chrome/chrome_trace_event.proto",
         "protos/perfetto/trace/clock_snapshot.proto",
+        "protos/perfetto/trace/etw/etw.proto",
+        "protos/perfetto/trace/etw/etw_event.proto",
+        "protos/perfetto/trace/etw/etw_event_bundle.proto",
         "protos/perfetto/trace/extension_descriptor.proto",
         "protos/perfetto/trace/filesystem/inode_file_map.proto",
         "protos/perfetto/trace/ftrace/android_fs.proto",
@@ -5398,17 +5888,173 @@
     ],
 }
 
-// GN: //protos/perfetto/trace/filesystem:cpp
-genrule {
-    name: "perfetto_protos_perfetto_trace_filesystem_cpp_gen",
+// GN: //protos/perfetto/trace/etw:cpp
+filegroup {
+    name: "perfetto_protos_perfetto_trace_etw_cpp",
     srcs: [
-        "protos/perfetto/trace/filesystem/inode_file_map.proto",
+        "protos/perfetto/trace/etw/etw.proto",
+        "protos/perfetto/trace/etw/etw_event.proto",
+        "protos/perfetto/trace/etw/etw_event_bundle.proto",
+    ],
+}
+
+// GN: //protos/perfetto/trace/etw:cpp
+genrule {
+    name: "perfetto_protos_perfetto_trace_etw_cpp_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_trace_etw_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_etw_cpp)",
+    out: [
+        "external/perfetto/protos/perfetto/trace/etw/etw.gen.cc",
+        "external/perfetto/protos/perfetto/trace/etw/etw_event.gen.cc",
+        "external/perfetto/protos/perfetto/trace/etw/etw_event_bundle.gen.cc",
+    ],
+}
+
+// GN: //protos/perfetto/trace/etw:cpp
+genrule {
+    name: "perfetto_protos_perfetto_trace_etw_cpp_gen_headers",
+    srcs: [
+        ":perfetto_protos_perfetto_trace_etw_cpp",
+    ],
+    tools: [
+        "aprotoc",
+        "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
+    ],
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_etw_cpp)",
+    out: [
+        "external/perfetto/protos/perfetto/trace/etw/etw.gen.h",
+        "external/perfetto/protos/perfetto/trace/etw/etw_event.gen.h",
+        "external/perfetto/protos/perfetto/trace/etw/etw_event_bundle.gen.h",
+    ],
+    export_include_dirs: [
+        ".",
+        "protos",
+    ],
+}
+
+// GN: //protos/perfetto/trace/etw:lite
+filegroup {
+    name: "perfetto_protos_perfetto_trace_etw_lite",
+    srcs: [
+        "protos/perfetto/trace/etw/etw.proto",
+        "protos/perfetto/trace/etw/etw_event.proto",
+        "protos/perfetto/trace/etw/etw_event_bundle.proto",
+    ],
+}
+
+// GN: //protos/perfetto/trace/etw:lite
+genrule {
+    name: "perfetto_protos_perfetto_trace_etw_lite_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_trace_etw_lite",
+    ],
+    tools: [
+        "aprotoc",
+    ],
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_etw_lite)",
+    out: [
+        "external/perfetto/protos/perfetto/trace/etw/etw.pb.cc",
+        "external/perfetto/protos/perfetto/trace/etw/etw_event.pb.cc",
+        "external/perfetto/protos/perfetto/trace/etw/etw_event_bundle.pb.cc",
+    ],
+}
+
+// GN: //protos/perfetto/trace/etw:lite
+genrule {
+    name: "perfetto_protos_perfetto_trace_etw_lite_gen_headers",
+    srcs: [
+        ":perfetto_protos_perfetto_trace_etw_lite",
+    ],
+    tools: [
+        "aprotoc",
+    ],
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_etw_lite)",
+    out: [
+        "external/perfetto/protos/perfetto/trace/etw/etw.pb.h",
+        "external/perfetto/protos/perfetto/trace/etw/etw_event.pb.h",
+        "external/perfetto/protos/perfetto/trace/etw/etw_event_bundle.pb.h",
+    ],
+    export_include_dirs: [
+        ".",
+        "protos",
+    ],
+}
+
+// GN: //protos/perfetto/trace/etw:zero
+filegroup {
+    name: "perfetto_protos_perfetto_trace_etw_zero",
+    srcs: [
+        "protos/perfetto/trace/etw/etw.proto",
+        "protos/perfetto/trace/etw/etw_event.proto",
+        "protos/perfetto/trace/etw/etw_event_bundle.proto",
+    ],
+}
+
+// GN: //protos/perfetto/trace/etw:zero
+genrule {
+    name: "perfetto_protos_perfetto_trace_etw_zero_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_trace_etw_zero",
+    ],
+    tools: [
+        "aprotoc",
+        "protozero_plugin",
+    ],
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_etw_zero)",
+    out: [
+        "external/perfetto/protos/perfetto/trace/etw/etw.pbzero.cc",
+        "external/perfetto/protos/perfetto/trace/etw/etw_event.pbzero.cc",
+        "external/perfetto/protos/perfetto/trace/etw/etw_event_bundle.pbzero.cc",
+    ],
+}
+
+// GN: //protos/perfetto/trace/etw:zero
+genrule {
+    name: "perfetto_protos_perfetto_trace_etw_zero_gen_headers",
+    srcs: [
+        ":perfetto_protos_perfetto_trace_etw_zero",
+    ],
+    tools: [
+        "aprotoc",
+        "protozero_plugin",
+    ],
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_etw_zero)",
+    out: [
+        "external/perfetto/protos/perfetto/trace/etw/etw.pbzero.h",
+        "external/perfetto/protos/perfetto/trace/etw/etw_event.pbzero.h",
+        "external/perfetto/protos/perfetto/trace/etw/etw_event_bundle.pbzero.h",
+    ],
+    export_include_dirs: [
+        ".",
+        "protos",
+    ],
+}
+
+// GN: //protos/perfetto/trace/filesystem:cpp
+filegroup {
+    name: "perfetto_protos_perfetto_trace_filesystem_cpp",
+    srcs: [
+        "protos/perfetto/trace/filesystem/inode_file_map.proto",
+    ],
+}
+
+// GN: //protos/perfetto/trace/filesystem:cpp
+genrule {
+    name: "perfetto_protos_perfetto_trace_filesystem_cpp_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_trace_filesystem_cpp",
+    ],
+    tools: [
+        "aprotoc",
+        "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
+    ],
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_filesystem_cpp)",
     out: [
         "external/perfetto/protos/perfetto/trace/filesystem/inode_file_map.gen.cc",
     ],
@@ -5418,13 +6064,13 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_filesystem_cpp_gen_headers",
     srcs: [
-        "protos/perfetto/trace/filesystem/inode_file_map.proto",
+        ":perfetto_protos_perfetto_trace_filesystem_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_filesystem_cpp)",
     out: [
         "external/perfetto/protos/perfetto/trace/filesystem/inode_file_map.gen.h",
     ],
@@ -5435,15 +6081,23 @@
 }
 
 // GN: //protos/perfetto/trace/filesystem:lite
+filegroup {
+    name: "perfetto_protos_perfetto_trace_filesystem_lite",
+    srcs: [
+        "protos/perfetto/trace/filesystem/inode_file_map.proto",
+    ],
+}
+
+// GN: //protos/perfetto/trace/filesystem:lite
 genrule {
     name: "perfetto_protos_perfetto_trace_filesystem_lite_gen",
     srcs: [
-        "protos/perfetto/trace/filesystem/inode_file_map.proto",
+        ":perfetto_protos_perfetto_trace_filesystem_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_filesystem_lite)",
     out: [
         "external/perfetto/protos/perfetto/trace/filesystem/inode_file_map.pb.cc",
     ],
@@ -5453,12 +6107,12 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_filesystem_lite_gen_headers",
     srcs: [
-        "protos/perfetto/trace/filesystem/inode_file_map.proto",
+        ":perfetto_protos_perfetto_trace_filesystem_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_filesystem_lite)",
     out: [
         "external/perfetto/protos/perfetto/trace/filesystem/inode_file_map.pb.h",
     ],
@@ -5469,16 +6123,24 @@
 }
 
 // GN: //protos/perfetto/trace/filesystem:zero
+filegroup {
+    name: "perfetto_protos_perfetto_trace_filesystem_zero",
+    srcs: [
+        "protos/perfetto/trace/filesystem/inode_file_map.proto",
+    ],
+}
+
+// GN: //protos/perfetto/trace/filesystem:zero
 genrule {
     name: "perfetto_protos_perfetto_trace_filesystem_zero_gen",
     srcs: [
-        "protos/perfetto/trace/filesystem/inode_file_map.proto",
+        ":perfetto_protos_perfetto_trace_filesystem_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_filesystem_zero)",
     out: [
         "external/perfetto/protos/perfetto/trace/filesystem/inode_file_map.pbzero.cc",
     ],
@@ -5488,13 +6150,13 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_filesystem_zero_gen_headers",
     srcs: [
-        "protos/perfetto/trace/filesystem/inode_file_map.proto",
+        ":perfetto_protos_perfetto_trace_filesystem_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_filesystem_zero)",
     out: [
         "external/perfetto/protos/perfetto/trace/filesystem/inode_file_map.pbzero.h",
     ],
@@ -5505,8 +6167,8 @@
 }
 
 // GN: //protos/perfetto/trace/ftrace:cpp
-genrule {
-    name: "perfetto_protos_perfetto_trace_ftrace_cpp_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_trace_ftrace_cpp",
     srcs: [
         "protos/perfetto/trace/ftrace/android_fs.proto",
         "protos/perfetto/trace/ftrace/binder.proto",
@@ -5575,11 +6237,19 @@
         "protos/perfetto/trace/ftrace/vmscan.proto",
         "protos/perfetto/trace/ftrace/workqueue.proto",
     ],
+}
+
+// GN: //protos/perfetto/trace/ftrace:cpp
+genrule {
+    name: "perfetto_protos_perfetto_trace_ftrace_cpp_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_trace_ftrace_cpp",
+    ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_ftrace_cpp)",
     out: [
         "external/perfetto/protos/perfetto/trace/ftrace/android_fs.gen.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/binder.gen.cc",
@@ -5654,78 +6324,13 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_ftrace_cpp_gen_headers",
     srcs: [
-        "protos/perfetto/trace/ftrace/android_fs.proto",
-        "protos/perfetto/trace/ftrace/binder.proto",
-        "protos/perfetto/trace/ftrace/block.proto",
-        "protos/perfetto/trace/ftrace/cgroup.proto",
-        "protos/perfetto/trace/ftrace/clk.proto",
-        "protos/perfetto/trace/ftrace/cma.proto",
-        "protos/perfetto/trace/ftrace/compaction.proto",
-        "protos/perfetto/trace/ftrace/cpuhp.proto",
-        "protos/perfetto/trace/ftrace/cros_ec.proto",
-        "protos/perfetto/trace/ftrace/dma_fence.proto",
-        "protos/perfetto/trace/ftrace/dmabuf_heap.proto",
-        "protos/perfetto/trace/ftrace/dpu.proto",
-        "protos/perfetto/trace/ftrace/drm.proto",
-        "protos/perfetto/trace/ftrace/ext4.proto",
-        "protos/perfetto/trace/ftrace/f2fs.proto",
-        "protos/perfetto/trace/ftrace/fastrpc.proto",
-        "protos/perfetto/trace/ftrace/fence.proto",
-        "protos/perfetto/trace/ftrace/filemap.proto",
-        "protos/perfetto/trace/ftrace/ftrace.proto",
-        "protos/perfetto/trace/ftrace/ftrace_event.proto",
-        "protos/perfetto/trace/ftrace/ftrace_event_bundle.proto",
-        "protos/perfetto/trace/ftrace/ftrace_stats.proto",
-        "protos/perfetto/trace/ftrace/g2d.proto",
-        "protos/perfetto/trace/ftrace/generic.proto",
-        "protos/perfetto/trace/ftrace/gpu_mem.proto",
-        "protos/perfetto/trace/ftrace/gpu_scheduler.proto",
-        "protos/perfetto/trace/ftrace/hyp.proto",
-        "protos/perfetto/trace/ftrace/i2c.proto",
-        "protos/perfetto/trace/ftrace/ion.proto",
-        "protos/perfetto/trace/ftrace/ipi.proto",
-        "protos/perfetto/trace/ftrace/irq.proto",
-        "protos/perfetto/trace/ftrace/kmem.proto",
-        "protos/perfetto/trace/ftrace/kvm.proto",
-        "protos/perfetto/trace/ftrace/lowmemorykiller.proto",
-        "protos/perfetto/trace/ftrace/lwis.proto",
-        "protos/perfetto/trace/ftrace/mali.proto",
-        "protos/perfetto/trace/ftrace/mdss.proto",
-        "protos/perfetto/trace/ftrace/mm_event.proto",
-        "protos/perfetto/trace/ftrace/net.proto",
-        "protos/perfetto/trace/ftrace/oom.proto",
-        "protos/perfetto/trace/ftrace/panel.proto",
-        "protos/perfetto/trace/ftrace/power.proto",
-        "protos/perfetto/trace/ftrace/printk.proto",
-        "protos/perfetto/trace/ftrace/raw_syscalls.proto",
-        "protos/perfetto/trace/ftrace/regulator.proto",
-        "protos/perfetto/trace/ftrace/samsung.proto",
-        "protos/perfetto/trace/ftrace/sched.proto",
-        "protos/perfetto/trace/ftrace/scm.proto",
-        "protos/perfetto/trace/ftrace/sde.proto",
-        "protos/perfetto/trace/ftrace/signal.proto",
-        "protos/perfetto/trace/ftrace/skb.proto",
-        "protos/perfetto/trace/ftrace/sock.proto",
-        "protos/perfetto/trace/ftrace/sync.proto",
-        "protos/perfetto/trace/ftrace/synthetic.proto",
-        "protos/perfetto/trace/ftrace/systrace.proto",
-        "protos/perfetto/trace/ftrace/task.proto",
-        "protos/perfetto/trace/ftrace/tcp.proto",
-        "protos/perfetto/trace/ftrace/test_bundle_wrapper.proto",
-        "protos/perfetto/trace/ftrace/thermal.proto",
-        "protos/perfetto/trace/ftrace/trusty.proto",
-        "protos/perfetto/trace/ftrace/ufs.proto",
-        "protos/perfetto/trace/ftrace/v4l2.proto",
-        "protos/perfetto/trace/ftrace/virtio_gpu.proto",
-        "protos/perfetto/trace/ftrace/virtio_video.proto",
-        "protos/perfetto/trace/ftrace/vmscan.proto",
-        "protos/perfetto/trace/ftrace/workqueue.proto",
+        ":perfetto_protos_perfetto_trace_ftrace_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_ftrace_cpp)",
     out: [
         "external/perfetto/protos/perfetto/trace/ftrace/android_fs.gen.h",
         "external/perfetto/protos/perfetto/trace/ftrace/binder.gen.h",
@@ -5801,8 +6406,8 @@
 }
 
 // GN: //protos/perfetto/trace/ftrace:lite
-genrule {
-    name: "perfetto_protos_perfetto_trace_ftrace_lite_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_trace_ftrace_lite",
     srcs: [
         "protos/perfetto/trace/ftrace/android_fs.proto",
         "protos/perfetto/trace/ftrace/binder.proto",
@@ -5871,10 +6476,18 @@
         "protos/perfetto/trace/ftrace/vmscan.proto",
         "protos/perfetto/trace/ftrace/workqueue.proto",
     ],
+}
+
+// GN: //protos/perfetto/trace/ftrace:lite
+genrule {
+    name: "perfetto_protos_perfetto_trace_ftrace_lite_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_trace_ftrace_lite",
+    ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_ftrace_lite)",
     out: [
         "external/perfetto/protos/perfetto/trace/ftrace/android_fs.pb.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/binder.pb.cc",
@@ -5949,77 +6562,12 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_ftrace_lite_gen_headers",
     srcs: [
-        "protos/perfetto/trace/ftrace/android_fs.proto",
-        "protos/perfetto/trace/ftrace/binder.proto",
-        "protos/perfetto/trace/ftrace/block.proto",
-        "protos/perfetto/trace/ftrace/cgroup.proto",
-        "protos/perfetto/trace/ftrace/clk.proto",
-        "protos/perfetto/trace/ftrace/cma.proto",
-        "protos/perfetto/trace/ftrace/compaction.proto",
-        "protos/perfetto/trace/ftrace/cpuhp.proto",
-        "protos/perfetto/trace/ftrace/cros_ec.proto",
-        "protos/perfetto/trace/ftrace/dma_fence.proto",
-        "protos/perfetto/trace/ftrace/dmabuf_heap.proto",
-        "protos/perfetto/trace/ftrace/dpu.proto",
-        "protos/perfetto/trace/ftrace/drm.proto",
-        "protos/perfetto/trace/ftrace/ext4.proto",
-        "protos/perfetto/trace/ftrace/f2fs.proto",
-        "protos/perfetto/trace/ftrace/fastrpc.proto",
-        "protos/perfetto/trace/ftrace/fence.proto",
-        "protos/perfetto/trace/ftrace/filemap.proto",
-        "protos/perfetto/trace/ftrace/ftrace.proto",
-        "protos/perfetto/trace/ftrace/ftrace_event.proto",
-        "protos/perfetto/trace/ftrace/ftrace_event_bundle.proto",
-        "protos/perfetto/trace/ftrace/ftrace_stats.proto",
-        "protos/perfetto/trace/ftrace/g2d.proto",
-        "protos/perfetto/trace/ftrace/generic.proto",
-        "protos/perfetto/trace/ftrace/gpu_mem.proto",
-        "protos/perfetto/trace/ftrace/gpu_scheduler.proto",
-        "protos/perfetto/trace/ftrace/hyp.proto",
-        "protos/perfetto/trace/ftrace/i2c.proto",
-        "protos/perfetto/trace/ftrace/ion.proto",
-        "protos/perfetto/trace/ftrace/ipi.proto",
-        "protos/perfetto/trace/ftrace/irq.proto",
-        "protos/perfetto/trace/ftrace/kmem.proto",
-        "protos/perfetto/trace/ftrace/kvm.proto",
-        "protos/perfetto/trace/ftrace/lowmemorykiller.proto",
-        "protos/perfetto/trace/ftrace/lwis.proto",
-        "protos/perfetto/trace/ftrace/mali.proto",
-        "protos/perfetto/trace/ftrace/mdss.proto",
-        "protos/perfetto/trace/ftrace/mm_event.proto",
-        "protos/perfetto/trace/ftrace/net.proto",
-        "protos/perfetto/trace/ftrace/oom.proto",
-        "protos/perfetto/trace/ftrace/panel.proto",
-        "protos/perfetto/trace/ftrace/power.proto",
-        "protos/perfetto/trace/ftrace/printk.proto",
-        "protos/perfetto/trace/ftrace/raw_syscalls.proto",
-        "protos/perfetto/trace/ftrace/regulator.proto",
-        "protos/perfetto/trace/ftrace/samsung.proto",
-        "protos/perfetto/trace/ftrace/sched.proto",
-        "protos/perfetto/trace/ftrace/scm.proto",
-        "protos/perfetto/trace/ftrace/sde.proto",
-        "protos/perfetto/trace/ftrace/signal.proto",
-        "protos/perfetto/trace/ftrace/skb.proto",
-        "protos/perfetto/trace/ftrace/sock.proto",
-        "protos/perfetto/trace/ftrace/sync.proto",
-        "protos/perfetto/trace/ftrace/synthetic.proto",
-        "protos/perfetto/trace/ftrace/systrace.proto",
-        "protos/perfetto/trace/ftrace/task.proto",
-        "protos/perfetto/trace/ftrace/tcp.proto",
-        "protos/perfetto/trace/ftrace/test_bundle_wrapper.proto",
-        "protos/perfetto/trace/ftrace/thermal.proto",
-        "protos/perfetto/trace/ftrace/trusty.proto",
-        "protos/perfetto/trace/ftrace/ufs.proto",
-        "protos/perfetto/trace/ftrace/v4l2.proto",
-        "protos/perfetto/trace/ftrace/virtio_gpu.proto",
-        "protos/perfetto/trace/ftrace/virtio_video.proto",
-        "protos/perfetto/trace/ftrace/vmscan.proto",
-        "protos/perfetto/trace/ftrace/workqueue.proto",
+        ":perfetto_protos_perfetto_trace_ftrace_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_ftrace_lite)",
     out: [
         "external/perfetto/protos/perfetto/trace/ftrace/android_fs.pb.h",
         "external/perfetto/protos/perfetto/trace/ftrace/binder.pb.h",
@@ -6095,8 +6643,8 @@
 }
 
 // GN: //protos/perfetto/trace/ftrace:zero
-genrule {
-    name: "perfetto_protos_perfetto_trace_ftrace_zero_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_trace_ftrace_zero",
     srcs: [
         "protos/perfetto/trace/ftrace/android_fs.proto",
         "protos/perfetto/trace/ftrace/binder.proto",
@@ -6165,11 +6713,19 @@
         "protos/perfetto/trace/ftrace/vmscan.proto",
         "protos/perfetto/trace/ftrace/workqueue.proto",
     ],
+}
+
+// GN: //protos/perfetto/trace/ftrace:zero
+genrule {
+    name: "perfetto_protos_perfetto_trace_ftrace_zero_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_trace_ftrace_zero",
+    ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_ftrace_zero)",
     out: [
         "external/perfetto/protos/perfetto/trace/ftrace/android_fs.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/binder.pbzero.cc",
@@ -6244,78 +6800,13 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_ftrace_zero_gen_headers",
     srcs: [
-        "protos/perfetto/trace/ftrace/android_fs.proto",
-        "protos/perfetto/trace/ftrace/binder.proto",
-        "protos/perfetto/trace/ftrace/block.proto",
-        "protos/perfetto/trace/ftrace/cgroup.proto",
-        "protos/perfetto/trace/ftrace/clk.proto",
-        "protos/perfetto/trace/ftrace/cma.proto",
-        "protos/perfetto/trace/ftrace/compaction.proto",
-        "protos/perfetto/trace/ftrace/cpuhp.proto",
-        "protos/perfetto/trace/ftrace/cros_ec.proto",
-        "protos/perfetto/trace/ftrace/dma_fence.proto",
-        "protos/perfetto/trace/ftrace/dmabuf_heap.proto",
-        "protos/perfetto/trace/ftrace/dpu.proto",
-        "protos/perfetto/trace/ftrace/drm.proto",
-        "protos/perfetto/trace/ftrace/ext4.proto",
-        "protos/perfetto/trace/ftrace/f2fs.proto",
-        "protos/perfetto/trace/ftrace/fastrpc.proto",
-        "protos/perfetto/trace/ftrace/fence.proto",
-        "protos/perfetto/trace/ftrace/filemap.proto",
-        "protos/perfetto/trace/ftrace/ftrace.proto",
-        "protos/perfetto/trace/ftrace/ftrace_event.proto",
-        "protos/perfetto/trace/ftrace/ftrace_event_bundle.proto",
-        "protos/perfetto/trace/ftrace/ftrace_stats.proto",
-        "protos/perfetto/trace/ftrace/g2d.proto",
-        "protos/perfetto/trace/ftrace/generic.proto",
-        "protos/perfetto/trace/ftrace/gpu_mem.proto",
-        "protos/perfetto/trace/ftrace/gpu_scheduler.proto",
-        "protos/perfetto/trace/ftrace/hyp.proto",
-        "protos/perfetto/trace/ftrace/i2c.proto",
-        "protos/perfetto/trace/ftrace/ion.proto",
-        "protos/perfetto/trace/ftrace/ipi.proto",
-        "protos/perfetto/trace/ftrace/irq.proto",
-        "protos/perfetto/trace/ftrace/kmem.proto",
-        "protos/perfetto/trace/ftrace/kvm.proto",
-        "protos/perfetto/trace/ftrace/lowmemorykiller.proto",
-        "protos/perfetto/trace/ftrace/lwis.proto",
-        "protos/perfetto/trace/ftrace/mali.proto",
-        "protos/perfetto/trace/ftrace/mdss.proto",
-        "protos/perfetto/trace/ftrace/mm_event.proto",
-        "protos/perfetto/trace/ftrace/net.proto",
-        "protos/perfetto/trace/ftrace/oom.proto",
-        "protos/perfetto/trace/ftrace/panel.proto",
-        "protos/perfetto/trace/ftrace/power.proto",
-        "protos/perfetto/trace/ftrace/printk.proto",
-        "protos/perfetto/trace/ftrace/raw_syscalls.proto",
-        "protos/perfetto/trace/ftrace/regulator.proto",
-        "protos/perfetto/trace/ftrace/samsung.proto",
-        "protos/perfetto/trace/ftrace/sched.proto",
-        "protos/perfetto/trace/ftrace/scm.proto",
-        "protos/perfetto/trace/ftrace/sde.proto",
-        "protos/perfetto/trace/ftrace/signal.proto",
-        "protos/perfetto/trace/ftrace/skb.proto",
-        "protos/perfetto/trace/ftrace/sock.proto",
-        "protos/perfetto/trace/ftrace/sync.proto",
-        "protos/perfetto/trace/ftrace/synthetic.proto",
-        "protos/perfetto/trace/ftrace/systrace.proto",
-        "protos/perfetto/trace/ftrace/task.proto",
-        "protos/perfetto/trace/ftrace/tcp.proto",
-        "protos/perfetto/trace/ftrace/test_bundle_wrapper.proto",
-        "protos/perfetto/trace/ftrace/thermal.proto",
-        "protos/perfetto/trace/ftrace/trusty.proto",
-        "protos/perfetto/trace/ftrace/ufs.proto",
-        "protos/perfetto/trace/ftrace/v4l2.proto",
-        "protos/perfetto/trace/ftrace/virtio_gpu.proto",
-        "protos/perfetto/trace/ftrace/virtio_video.proto",
-        "protos/perfetto/trace/ftrace/vmscan.proto",
-        "protos/perfetto/trace/ftrace/workqueue.proto",
+        ":perfetto_protos_perfetto_trace_ftrace_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_ftrace_zero)",
     out: [
         "external/perfetto/protos/perfetto/trace/ftrace/android_fs.pbzero.h",
         "external/perfetto/protos/perfetto/trace/ftrace/binder.pbzero.h",
@@ -6391,8 +6882,8 @@
 }
 
 // GN: //protos/perfetto/trace/gpu:cpp
-genrule {
-    name: "perfetto_protos_perfetto_trace_gpu_cpp_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_trace_gpu_cpp",
     srcs: [
         "protos/perfetto/trace/gpu/gpu_counter_event.proto",
         "protos/perfetto/trace/gpu/gpu_log.proto",
@@ -6400,11 +6891,20 @@
         "protos/perfetto/trace/gpu/vulkan_api_event.proto",
         "protos/perfetto/trace/gpu/vulkan_memory_event.proto",
     ],
+}
+
+// GN: //protos/perfetto/trace/gpu:cpp
+genrule {
+    name: "perfetto_protos_perfetto_trace_gpu_cpp_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_common_cpp",
+        ":perfetto_protos_perfetto_trace_gpu_cpp",
+    ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_gpu_cpp)",
     out: [
         "external/perfetto/protos/perfetto/trace/gpu/gpu_counter_event.gen.cc",
         "external/perfetto/protos/perfetto/trace/gpu/gpu_log.gen.cc",
@@ -6418,17 +6918,14 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_gpu_cpp_gen_headers",
     srcs: [
-        "protos/perfetto/trace/gpu/gpu_counter_event.proto",
-        "protos/perfetto/trace/gpu/gpu_log.proto",
-        "protos/perfetto/trace/gpu/gpu_render_stage_event.proto",
-        "protos/perfetto/trace/gpu/vulkan_api_event.proto",
-        "protos/perfetto/trace/gpu/vulkan_memory_event.proto",
+        ":perfetto_protos_perfetto_common_cpp",
+        ":perfetto_protos_perfetto_trace_gpu_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_gpu_cpp)",
     out: [
         "external/perfetto/protos/perfetto/trace/gpu/gpu_counter_event.gen.h",
         "external/perfetto/protos/perfetto/trace/gpu/gpu_log.gen.h",
@@ -6443,8 +6940,8 @@
 }
 
 // GN: //protos/perfetto/trace/gpu:lite
-genrule {
-    name: "perfetto_protos_perfetto_trace_gpu_lite_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_trace_gpu_lite",
     srcs: [
         "protos/perfetto/trace/gpu/gpu_counter_event.proto",
         "protos/perfetto/trace/gpu/gpu_log.proto",
@@ -6452,10 +6949,19 @@
         "protos/perfetto/trace/gpu/vulkan_api_event.proto",
         "protos/perfetto/trace/gpu/vulkan_memory_event.proto",
     ],
+}
+
+// GN: //protos/perfetto/trace/gpu:lite
+genrule {
+    name: "perfetto_protos_perfetto_trace_gpu_lite_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_common_lite",
+        ":perfetto_protos_perfetto_trace_gpu_lite",
+    ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_gpu_lite)",
     out: [
         "external/perfetto/protos/perfetto/trace/gpu/gpu_counter_event.pb.cc",
         "external/perfetto/protos/perfetto/trace/gpu/gpu_log.pb.cc",
@@ -6469,16 +6975,13 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_gpu_lite_gen_headers",
     srcs: [
-        "protos/perfetto/trace/gpu/gpu_counter_event.proto",
-        "protos/perfetto/trace/gpu/gpu_log.proto",
-        "protos/perfetto/trace/gpu/gpu_render_stage_event.proto",
-        "protos/perfetto/trace/gpu/vulkan_api_event.proto",
-        "protos/perfetto/trace/gpu/vulkan_memory_event.proto",
+        ":perfetto_protos_perfetto_common_lite",
+        ":perfetto_protos_perfetto_trace_gpu_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_gpu_lite)",
     out: [
         "external/perfetto/protos/perfetto/trace/gpu/gpu_counter_event.pb.h",
         "external/perfetto/protos/perfetto/trace/gpu/gpu_log.pb.h",
@@ -6493,8 +6996,8 @@
 }
 
 // GN: //protos/perfetto/trace/gpu:zero
-genrule {
-    name: "perfetto_protos_perfetto_trace_gpu_zero_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_trace_gpu_zero",
     srcs: [
         "protos/perfetto/trace/gpu/gpu_counter_event.proto",
         "protos/perfetto/trace/gpu/gpu_log.proto",
@@ -6502,11 +7005,20 @@
         "protos/perfetto/trace/gpu/vulkan_api_event.proto",
         "protos/perfetto/trace/gpu/vulkan_memory_event.proto",
     ],
+}
+
+// GN: //protos/perfetto/trace/gpu:zero
+genrule {
+    name: "perfetto_protos_perfetto_trace_gpu_zero_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_common_zero",
+        ":perfetto_protos_perfetto_trace_gpu_zero",
+    ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_gpu_zero)",
     out: [
         "external/perfetto/protos/perfetto/trace/gpu/gpu_counter_event.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/gpu/gpu_log.pbzero.cc",
@@ -6520,17 +7032,14 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_gpu_zero_gen_headers",
     srcs: [
-        "protos/perfetto/trace/gpu/gpu_counter_event.proto",
-        "protos/perfetto/trace/gpu/gpu_log.proto",
-        "protos/perfetto/trace/gpu/gpu_render_stage_event.proto",
-        "protos/perfetto/trace/gpu/vulkan_api_event.proto",
-        "protos/perfetto/trace/gpu/vulkan_memory_event.proto",
+        ":perfetto_protos_perfetto_common_zero",
+        ":perfetto_protos_perfetto_trace_gpu_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_gpu_zero)",
     out: [
         "external/perfetto/protos/perfetto/trace/gpu/gpu_counter_event.pbzero.h",
         "external/perfetto/protos/perfetto/trace/gpu/gpu_log.pbzero.h",
@@ -6545,16 +7054,29 @@
 }
 
 // GN: //protos/perfetto/trace/interned_data:cpp
+filegroup {
+    name: "perfetto_protos_perfetto_trace_interned_data_cpp",
+    srcs: [
+        "protos/perfetto/trace/interned_data/interned_data.proto",
+    ],
+}
+
+// GN: //protos/perfetto/trace/interned_data:cpp
 genrule {
     name: "perfetto_protos_perfetto_trace_interned_data_cpp_gen",
     srcs: [
-        "protos/perfetto/trace/interned_data/interned_data.proto",
+        ":perfetto_protos_perfetto_common_cpp",
+        ":perfetto_protos_perfetto_trace_android_cpp",
+        ":perfetto_protos_perfetto_trace_gpu_cpp",
+        ":perfetto_protos_perfetto_trace_interned_data_cpp",
+        ":perfetto_protos_perfetto_trace_profiling_cpp",
+        ":perfetto_protos_perfetto_trace_track_event_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_interned_data_cpp)",
     out: [
         "external/perfetto/protos/perfetto/trace/interned_data/interned_data.gen.cc",
     ],
@@ -6564,13 +7086,18 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_interned_data_cpp_gen_headers",
     srcs: [
-        "protos/perfetto/trace/interned_data/interned_data.proto",
+        ":perfetto_protos_perfetto_common_cpp",
+        ":perfetto_protos_perfetto_trace_android_cpp",
+        ":perfetto_protos_perfetto_trace_gpu_cpp",
+        ":perfetto_protos_perfetto_trace_interned_data_cpp",
+        ":perfetto_protos_perfetto_trace_profiling_cpp",
+        ":perfetto_protos_perfetto_trace_track_event_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_interned_data_cpp)",
     out: [
         "external/perfetto/protos/perfetto/trace/interned_data/interned_data.gen.h",
     ],
@@ -6581,15 +7108,28 @@
 }
 
 // GN: //protos/perfetto/trace/interned_data:lite
+filegroup {
+    name: "perfetto_protos_perfetto_trace_interned_data_lite",
+    srcs: [
+        "protos/perfetto/trace/interned_data/interned_data.proto",
+    ],
+}
+
+// GN: //protos/perfetto/trace/interned_data:lite
 genrule {
     name: "perfetto_protos_perfetto_trace_interned_data_lite_gen",
     srcs: [
-        "protos/perfetto/trace/interned_data/interned_data.proto",
+        ":perfetto_protos_perfetto_common_lite",
+        ":perfetto_protos_perfetto_trace_android_lite",
+        ":perfetto_protos_perfetto_trace_gpu_lite",
+        ":perfetto_protos_perfetto_trace_interned_data_lite",
+        ":perfetto_protos_perfetto_trace_profiling_lite",
+        ":perfetto_protos_perfetto_trace_track_event_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_interned_data_lite)",
     out: [
         "external/perfetto/protos/perfetto/trace/interned_data/interned_data.pb.cc",
     ],
@@ -6599,12 +7139,17 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_interned_data_lite_gen_headers",
     srcs: [
-        "protos/perfetto/trace/interned_data/interned_data.proto",
+        ":perfetto_protos_perfetto_common_lite",
+        ":perfetto_protos_perfetto_trace_android_lite",
+        ":perfetto_protos_perfetto_trace_gpu_lite",
+        ":perfetto_protos_perfetto_trace_interned_data_lite",
+        ":perfetto_protos_perfetto_trace_profiling_lite",
+        ":perfetto_protos_perfetto_trace_track_event_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_interned_data_lite)",
     out: [
         "external/perfetto/protos/perfetto/trace/interned_data/interned_data.pb.h",
     ],
@@ -6615,16 +7160,29 @@
 }
 
 // GN: //protos/perfetto/trace/interned_data:zero
+filegroup {
+    name: "perfetto_protos_perfetto_trace_interned_data_zero",
+    srcs: [
+        "protos/perfetto/trace/interned_data/interned_data.proto",
+    ],
+}
+
+// GN: //protos/perfetto/trace/interned_data:zero
 genrule {
     name: "perfetto_protos_perfetto_trace_interned_data_zero_gen",
     srcs: [
-        "protos/perfetto/trace/interned_data/interned_data.proto",
+        ":perfetto_protos_perfetto_common_zero",
+        ":perfetto_protos_perfetto_trace_android_zero",
+        ":perfetto_protos_perfetto_trace_gpu_zero",
+        ":perfetto_protos_perfetto_trace_interned_data_zero",
+        ":perfetto_protos_perfetto_trace_profiling_zero",
+        ":perfetto_protos_perfetto_trace_track_event_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_interned_data_zero)",
     out: [
         "external/perfetto/protos/perfetto/trace/interned_data/interned_data.pbzero.cc",
     ],
@@ -6634,13 +7192,18 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_interned_data_zero_gen_headers",
     srcs: [
-        "protos/perfetto/trace/interned_data/interned_data.proto",
+        ":perfetto_protos_perfetto_common_zero",
+        ":perfetto_protos_perfetto_trace_android_zero",
+        ":perfetto_protos_perfetto_trace_gpu_zero",
+        ":perfetto_protos_perfetto_trace_interned_data_zero",
+        ":perfetto_protos_perfetto_trace_profiling_zero",
+        ":perfetto_protos_perfetto_trace_track_event_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_interned_data_zero)",
     out: [
         "external/perfetto/protos/perfetto/trace/interned_data/interned_data.pbzero.h",
     ],
@@ -6651,19 +7214,41 @@
 }
 
 // GN: //protos/perfetto/trace:minimal_cpp
-genrule {
-    name: "perfetto_protos_perfetto_trace_minimal_cpp_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_trace_minimal_cpp",
     srcs: [
         "protos/perfetto/trace/clock_snapshot.proto",
         "protos/perfetto/trace/system_info.proto",
         "protos/perfetto/trace/trace_uuid.proto",
         "protos/perfetto/trace/trigger.proto",
     ],
+}
+
+// GN: //protos/perfetto/trace:minimal_cpp
+genrule {
+    name: "perfetto_protos_perfetto_trace_minimal_cpp_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_common_cpp",
+        ":perfetto_protos_perfetto_config_android_cpp",
+        ":perfetto_protos_perfetto_config_cpp",
+        ":perfetto_protos_perfetto_config_ftrace_cpp",
+        ":perfetto_protos_perfetto_config_gpu_cpp",
+        ":perfetto_protos_perfetto_config_inode_file_cpp",
+        ":perfetto_protos_perfetto_config_interceptors_cpp",
+        ":perfetto_protos_perfetto_config_power_cpp",
+        ":perfetto_protos_perfetto_config_process_stats_cpp",
+        ":perfetto_protos_perfetto_config_profiling_cpp",
+        ":perfetto_protos_perfetto_config_statsd_cpp",
+        ":perfetto_protos_perfetto_config_sys_stats_cpp",
+        ":perfetto_protos_perfetto_config_system_info_cpp",
+        ":perfetto_protos_perfetto_config_track_event_cpp",
+        ":perfetto_protos_perfetto_trace_minimal_cpp",
+    ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_minimal_cpp)",
     out: [
         "external/perfetto/protos/perfetto/trace/clock_snapshot.gen.cc",
         "external/perfetto/protos/perfetto/trace/system_info.gen.cc",
@@ -6676,16 +7261,27 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_minimal_cpp_gen_headers",
     srcs: [
-        "protos/perfetto/trace/clock_snapshot.proto",
-        "protos/perfetto/trace/system_info.proto",
-        "protos/perfetto/trace/trace_uuid.proto",
-        "protos/perfetto/trace/trigger.proto",
+        ":perfetto_protos_perfetto_common_cpp",
+        ":perfetto_protos_perfetto_config_android_cpp",
+        ":perfetto_protos_perfetto_config_cpp",
+        ":perfetto_protos_perfetto_config_ftrace_cpp",
+        ":perfetto_protos_perfetto_config_gpu_cpp",
+        ":perfetto_protos_perfetto_config_inode_file_cpp",
+        ":perfetto_protos_perfetto_config_interceptors_cpp",
+        ":perfetto_protos_perfetto_config_power_cpp",
+        ":perfetto_protos_perfetto_config_process_stats_cpp",
+        ":perfetto_protos_perfetto_config_profiling_cpp",
+        ":perfetto_protos_perfetto_config_statsd_cpp",
+        ":perfetto_protos_perfetto_config_sys_stats_cpp",
+        ":perfetto_protos_perfetto_config_system_info_cpp",
+        ":perfetto_protos_perfetto_config_track_event_cpp",
+        ":perfetto_protos_perfetto_trace_minimal_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_minimal_cpp)",
     out: [
         "external/perfetto/protos/perfetto/trace/clock_snapshot.gen.h",
         "external/perfetto/protos/perfetto/trace/system_info.gen.h",
@@ -6699,18 +7295,40 @@
 }
 
 // GN: //protos/perfetto/trace:minimal_lite
-genrule {
-    name: "perfetto_protos_perfetto_trace_minimal_lite_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_trace_minimal_lite",
     srcs: [
         "protos/perfetto/trace/clock_snapshot.proto",
         "protos/perfetto/trace/system_info.proto",
         "protos/perfetto/trace/trace_uuid.proto",
         "protos/perfetto/trace/trigger.proto",
     ],
+}
+
+// GN: //protos/perfetto/trace:minimal_lite
+genrule {
+    name: "perfetto_protos_perfetto_trace_minimal_lite_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_common_lite",
+        ":perfetto_protos_perfetto_config_android_lite",
+        ":perfetto_protos_perfetto_config_ftrace_lite",
+        ":perfetto_protos_perfetto_config_gpu_lite",
+        ":perfetto_protos_perfetto_config_inode_file_lite",
+        ":perfetto_protos_perfetto_config_interceptors_lite",
+        ":perfetto_protos_perfetto_config_lite",
+        ":perfetto_protos_perfetto_config_power_lite",
+        ":perfetto_protos_perfetto_config_process_stats_lite",
+        ":perfetto_protos_perfetto_config_profiling_lite",
+        ":perfetto_protos_perfetto_config_statsd_lite",
+        ":perfetto_protos_perfetto_config_sys_stats_lite",
+        ":perfetto_protos_perfetto_config_system_info_lite",
+        ":perfetto_protos_perfetto_config_track_event_lite",
+        ":perfetto_protos_perfetto_trace_minimal_lite",
+    ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_minimal_lite)",
     out: [
         "external/perfetto/protos/perfetto/trace/clock_snapshot.pb.cc",
         "external/perfetto/protos/perfetto/trace/system_info.pb.cc",
@@ -6723,15 +7341,26 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_minimal_lite_gen_headers",
     srcs: [
-        "protos/perfetto/trace/clock_snapshot.proto",
-        "protos/perfetto/trace/system_info.proto",
-        "protos/perfetto/trace/trace_uuid.proto",
-        "protos/perfetto/trace/trigger.proto",
+        ":perfetto_protos_perfetto_common_lite",
+        ":perfetto_protos_perfetto_config_android_lite",
+        ":perfetto_protos_perfetto_config_ftrace_lite",
+        ":perfetto_protos_perfetto_config_gpu_lite",
+        ":perfetto_protos_perfetto_config_inode_file_lite",
+        ":perfetto_protos_perfetto_config_interceptors_lite",
+        ":perfetto_protos_perfetto_config_lite",
+        ":perfetto_protos_perfetto_config_power_lite",
+        ":perfetto_protos_perfetto_config_process_stats_lite",
+        ":perfetto_protos_perfetto_config_profiling_lite",
+        ":perfetto_protos_perfetto_config_statsd_lite",
+        ":perfetto_protos_perfetto_config_sys_stats_lite",
+        ":perfetto_protos_perfetto_config_system_info_lite",
+        ":perfetto_protos_perfetto_config_track_event_lite",
+        ":perfetto_protos_perfetto_trace_minimal_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_minimal_lite)",
     out: [
         "external/perfetto/protos/perfetto/trace/clock_snapshot.pb.h",
         "external/perfetto/protos/perfetto/trace/system_info.pb.h",
@@ -6745,19 +7374,41 @@
 }
 
 // GN: //protos/perfetto/trace:minimal_zero
-genrule {
-    name: "perfetto_protos_perfetto_trace_minimal_zero_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_trace_minimal_zero",
     srcs: [
         "protos/perfetto/trace/clock_snapshot.proto",
         "protos/perfetto/trace/system_info.proto",
         "protos/perfetto/trace/trace_uuid.proto",
         "protos/perfetto/trace/trigger.proto",
     ],
+}
+
+// GN: //protos/perfetto/trace:minimal_zero
+genrule {
+    name: "perfetto_protos_perfetto_trace_minimal_zero_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_common_zero",
+        ":perfetto_protos_perfetto_config_android_zero",
+        ":perfetto_protos_perfetto_config_ftrace_zero",
+        ":perfetto_protos_perfetto_config_gpu_zero",
+        ":perfetto_protos_perfetto_config_inode_file_zero",
+        ":perfetto_protos_perfetto_config_interceptors_zero",
+        ":perfetto_protos_perfetto_config_power_zero",
+        ":perfetto_protos_perfetto_config_process_stats_zero",
+        ":perfetto_protos_perfetto_config_profiling_zero",
+        ":perfetto_protos_perfetto_config_statsd_zero",
+        ":perfetto_protos_perfetto_config_sys_stats_zero",
+        ":perfetto_protos_perfetto_config_system_info_zero",
+        ":perfetto_protos_perfetto_config_track_event_zero",
+        ":perfetto_protos_perfetto_config_zero",
+        ":perfetto_protos_perfetto_trace_minimal_zero",
+    ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_minimal_zero)",
     out: [
         "external/perfetto/protos/perfetto/trace/clock_snapshot.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/system_info.pbzero.cc",
@@ -6770,16 +7421,27 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_minimal_zero_gen_headers",
     srcs: [
-        "protos/perfetto/trace/clock_snapshot.proto",
-        "protos/perfetto/trace/system_info.proto",
-        "protos/perfetto/trace/trace_uuid.proto",
-        "protos/perfetto/trace/trigger.proto",
+        ":perfetto_protos_perfetto_common_zero",
+        ":perfetto_protos_perfetto_config_android_zero",
+        ":perfetto_protos_perfetto_config_ftrace_zero",
+        ":perfetto_protos_perfetto_config_gpu_zero",
+        ":perfetto_protos_perfetto_config_inode_file_zero",
+        ":perfetto_protos_perfetto_config_interceptors_zero",
+        ":perfetto_protos_perfetto_config_power_zero",
+        ":perfetto_protos_perfetto_config_process_stats_zero",
+        ":perfetto_protos_perfetto_config_profiling_zero",
+        ":perfetto_protos_perfetto_config_statsd_zero",
+        ":perfetto_protos_perfetto_config_sys_stats_zero",
+        ":perfetto_protos_perfetto_config_system_info_zero",
+        ":perfetto_protos_perfetto_config_track_event_zero",
+        ":perfetto_protos_perfetto_config_zero",
+        ":perfetto_protos_perfetto_trace_minimal_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_minimal_zero)",
     out: [
         "external/perfetto/protos/perfetto/trace/clock_snapshot.pbzero.h",
         "external/perfetto/protos/perfetto/trace/system_info.pbzero.h",
@@ -6793,8 +7455,8 @@
 }
 
 // GN: //protos/perfetto/trace:non_minimal_cpp
-genrule {
-    name: "perfetto_protos_perfetto_trace_non_minimal_cpp_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_trace_non_minimal_cpp",
     srcs: [
         "protos/perfetto/trace/extension_descriptor.proto",
         "protos/perfetto/trace/memory_graph.proto",
@@ -6805,11 +7467,50 @@
         "protos/perfetto/trace/trace_packet_defaults.proto",
         "protos/perfetto/trace/ui_state.proto",
     ],
+}
+
+// GN: //protos/perfetto/trace:non_minimal_cpp
+genrule {
+    name: "perfetto_protos_perfetto_trace_non_minimal_cpp_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_common_cpp",
+        ":perfetto_protos_perfetto_config_android_cpp",
+        ":perfetto_protos_perfetto_config_cpp",
+        ":perfetto_protos_perfetto_config_ftrace_cpp",
+        ":perfetto_protos_perfetto_config_gpu_cpp",
+        ":perfetto_protos_perfetto_config_inode_file_cpp",
+        ":perfetto_protos_perfetto_config_interceptors_cpp",
+        ":perfetto_protos_perfetto_config_power_cpp",
+        ":perfetto_protos_perfetto_config_process_stats_cpp",
+        ":perfetto_protos_perfetto_config_profiling_cpp",
+        ":perfetto_protos_perfetto_config_statsd_cpp",
+        ":perfetto_protos_perfetto_config_sys_stats_cpp",
+        ":perfetto_protos_perfetto_config_system_info_cpp",
+        ":perfetto_protos_perfetto_config_track_event_cpp",
+        ":perfetto_protos_perfetto_trace_android_cpp",
+        ":perfetto_protos_perfetto_trace_chrome_cpp",
+        ":perfetto_protos_perfetto_trace_etw_cpp",
+        ":perfetto_protos_perfetto_trace_filesystem_cpp",
+        ":perfetto_protos_perfetto_trace_ftrace_cpp",
+        ":perfetto_protos_perfetto_trace_gpu_cpp",
+        ":perfetto_protos_perfetto_trace_interned_data_cpp",
+        ":perfetto_protos_perfetto_trace_minimal_cpp",
+        ":perfetto_protos_perfetto_trace_non_minimal_cpp",
+        ":perfetto_protos_perfetto_trace_perfetto_cpp",
+        ":perfetto_protos_perfetto_trace_power_cpp",
+        ":perfetto_protos_perfetto_trace_profiling_cpp",
+        ":perfetto_protos_perfetto_trace_ps_cpp",
+        ":perfetto_protos_perfetto_trace_statsd_cpp",
+        ":perfetto_protos_perfetto_trace_sys_stats_cpp",
+        ":perfetto_protos_perfetto_trace_system_info_cpp",
+        ":perfetto_protos_perfetto_trace_track_event_cpp",
+        ":perfetto_protos_perfetto_trace_translation_cpp",
+    ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_non_minimal_cpp)",
     out: [
         "external/perfetto/protos/perfetto/trace/extension_descriptor.gen.cc",
         "external/perfetto/protos/perfetto/trace/memory_graph.gen.cc",
@@ -6826,20 +7527,44 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_non_minimal_cpp_gen_headers",
     srcs: [
-        "protos/perfetto/trace/extension_descriptor.proto",
-        "protos/perfetto/trace/memory_graph.proto",
-        "protos/perfetto/trace/test_event.proto",
-        "protos/perfetto/trace/test_extensions.proto",
-        "protos/perfetto/trace/trace.proto",
-        "protos/perfetto/trace/trace_packet.proto",
-        "protos/perfetto/trace/trace_packet_defaults.proto",
-        "protos/perfetto/trace/ui_state.proto",
+        ":perfetto_protos_perfetto_common_cpp",
+        ":perfetto_protos_perfetto_config_android_cpp",
+        ":perfetto_protos_perfetto_config_cpp",
+        ":perfetto_protos_perfetto_config_ftrace_cpp",
+        ":perfetto_protos_perfetto_config_gpu_cpp",
+        ":perfetto_protos_perfetto_config_inode_file_cpp",
+        ":perfetto_protos_perfetto_config_interceptors_cpp",
+        ":perfetto_protos_perfetto_config_power_cpp",
+        ":perfetto_protos_perfetto_config_process_stats_cpp",
+        ":perfetto_protos_perfetto_config_profiling_cpp",
+        ":perfetto_protos_perfetto_config_statsd_cpp",
+        ":perfetto_protos_perfetto_config_sys_stats_cpp",
+        ":perfetto_protos_perfetto_config_system_info_cpp",
+        ":perfetto_protos_perfetto_config_track_event_cpp",
+        ":perfetto_protos_perfetto_trace_android_cpp",
+        ":perfetto_protos_perfetto_trace_chrome_cpp",
+        ":perfetto_protos_perfetto_trace_etw_cpp",
+        ":perfetto_protos_perfetto_trace_filesystem_cpp",
+        ":perfetto_protos_perfetto_trace_ftrace_cpp",
+        ":perfetto_protos_perfetto_trace_gpu_cpp",
+        ":perfetto_protos_perfetto_trace_interned_data_cpp",
+        ":perfetto_protos_perfetto_trace_minimal_cpp",
+        ":perfetto_protos_perfetto_trace_non_minimal_cpp",
+        ":perfetto_protos_perfetto_trace_perfetto_cpp",
+        ":perfetto_protos_perfetto_trace_power_cpp",
+        ":perfetto_protos_perfetto_trace_profiling_cpp",
+        ":perfetto_protos_perfetto_trace_ps_cpp",
+        ":perfetto_protos_perfetto_trace_statsd_cpp",
+        ":perfetto_protos_perfetto_trace_sys_stats_cpp",
+        ":perfetto_protos_perfetto_trace_system_info_cpp",
+        ":perfetto_protos_perfetto_trace_track_event_cpp",
+        ":perfetto_protos_perfetto_trace_translation_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_non_minimal_cpp)",
     out: [
         "external/perfetto/protos/perfetto/trace/extension_descriptor.gen.h",
         "external/perfetto/protos/perfetto/trace/memory_graph.gen.h",
@@ -6857,8 +7582,8 @@
 }
 
 // GN: //protos/perfetto/trace:non_minimal_lite
-genrule {
-    name: "perfetto_protos_perfetto_trace_non_minimal_lite_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_trace_non_minimal_lite",
     srcs: [
         "protos/perfetto/trace/extension_descriptor.proto",
         "protos/perfetto/trace/memory_graph.proto",
@@ -6869,10 +7594,49 @@
         "protos/perfetto/trace/trace_packet_defaults.proto",
         "protos/perfetto/trace/ui_state.proto",
     ],
+}
+
+// GN: //protos/perfetto/trace:non_minimal_lite
+genrule {
+    name: "perfetto_protos_perfetto_trace_non_minimal_lite_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_common_lite",
+        ":perfetto_protos_perfetto_config_android_lite",
+        ":perfetto_protos_perfetto_config_ftrace_lite",
+        ":perfetto_protos_perfetto_config_gpu_lite",
+        ":perfetto_protos_perfetto_config_inode_file_lite",
+        ":perfetto_protos_perfetto_config_interceptors_lite",
+        ":perfetto_protos_perfetto_config_lite",
+        ":perfetto_protos_perfetto_config_power_lite",
+        ":perfetto_protos_perfetto_config_process_stats_lite",
+        ":perfetto_protos_perfetto_config_profiling_lite",
+        ":perfetto_protos_perfetto_config_statsd_lite",
+        ":perfetto_protos_perfetto_config_sys_stats_lite",
+        ":perfetto_protos_perfetto_config_system_info_lite",
+        ":perfetto_protos_perfetto_config_track_event_lite",
+        ":perfetto_protos_perfetto_trace_android_lite",
+        ":perfetto_protos_perfetto_trace_chrome_lite",
+        ":perfetto_protos_perfetto_trace_etw_lite",
+        ":perfetto_protos_perfetto_trace_filesystem_lite",
+        ":perfetto_protos_perfetto_trace_ftrace_lite",
+        ":perfetto_protos_perfetto_trace_gpu_lite",
+        ":perfetto_protos_perfetto_trace_interned_data_lite",
+        ":perfetto_protos_perfetto_trace_minimal_lite",
+        ":perfetto_protos_perfetto_trace_non_minimal_lite",
+        ":perfetto_protos_perfetto_trace_perfetto_lite",
+        ":perfetto_protos_perfetto_trace_power_lite",
+        ":perfetto_protos_perfetto_trace_profiling_lite",
+        ":perfetto_protos_perfetto_trace_ps_lite",
+        ":perfetto_protos_perfetto_trace_statsd_lite",
+        ":perfetto_protos_perfetto_trace_sys_stats_lite",
+        ":perfetto_protos_perfetto_trace_system_info_lite",
+        ":perfetto_protos_perfetto_trace_track_event_lite",
+        ":perfetto_protos_perfetto_trace_translation_lite",
+    ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_non_minimal_lite)",
     out: [
         "external/perfetto/protos/perfetto/trace/extension_descriptor.pb.cc",
         "external/perfetto/protos/perfetto/trace/memory_graph.pb.cc",
@@ -6889,19 +7653,43 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_non_minimal_lite_gen_headers",
     srcs: [
-        "protos/perfetto/trace/extension_descriptor.proto",
-        "protos/perfetto/trace/memory_graph.proto",
-        "protos/perfetto/trace/test_event.proto",
-        "protos/perfetto/trace/test_extensions.proto",
-        "protos/perfetto/trace/trace.proto",
-        "protos/perfetto/trace/trace_packet.proto",
-        "protos/perfetto/trace/trace_packet_defaults.proto",
-        "protos/perfetto/trace/ui_state.proto",
+        ":perfetto_protos_perfetto_common_lite",
+        ":perfetto_protos_perfetto_config_android_lite",
+        ":perfetto_protos_perfetto_config_ftrace_lite",
+        ":perfetto_protos_perfetto_config_gpu_lite",
+        ":perfetto_protos_perfetto_config_inode_file_lite",
+        ":perfetto_protos_perfetto_config_interceptors_lite",
+        ":perfetto_protos_perfetto_config_lite",
+        ":perfetto_protos_perfetto_config_power_lite",
+        ":perfetto_protos_perfetto_config_process_stats_lite",
+        ":perfetto_protos_perfetto_config_profiling_lite",
+        ":perfetto_protos_perfetto_config_statsd_lite",
+        ":perfetto_protos_perfetto_config_sys_stats_lite",
+        ":perfetto_protos_perfetto_config_system_info_lite",
+        ":perfetto_protos_perfetto_config_track_event_lite",
+        ":perfetto_protos_perfetto_trace_android_lite",
+        ":perfetto_protos_perfetto_trace_chrome_lite",
+        ":perfetto_protos_perfetto_trace_etw_lite",
+        ":perfetto_protos_perfetto_trace_filesystem_lite",
+        ":perfetto_protos_perfetto_trace_ftrace_lite",
+        ":perfetto_protos_perfetto_trace_gpu_lite",
+        ":perfetto_protos_perfetto_trace_interned_data_lite",
+        ":perfetto_protos_perfetto_trace_minimal_lite",
+        ":perfetto_protos_perfetto_trace_non_minimal_lite",
+        ":perfetto_protos_perfetto_trace_perfetto_lite",
+        ":perfetto_protos_perfetto_trace_power_lite",
+        ":perfetto_protos_perfetto_trace_profiling_lite",
+        ":perfetto_protos_perfetto_trace_ps_lite",
+        ":perfetto_protos_perfetto_trace_statsd_lite",
+        ":perfetto_protos_perfetto_trace_sys_stats_lite",
+        ":perfetto_protos_perfetto_trace_system_info_lite",
+        ":perfetto_protos_perfetto_trace_track_event_lite",
+        ":perfetto_protos_perfetto_trace_translation_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_non_minimal_lite)",
     out: [
         "external/perfetto/protos/perfetto/trace/extension_descriptor.pb.h",
         "external/perfetto/protos/perfetto/trace/memory_graph.pb.h",
@@ -6919,8 +7707,8 @@
 }
 
 // GN: //protos/perfetto/trace:non_minimal_zero
-genrule {
-    name: "perfetto_protos_perfetto_trace_non_minimal_zero_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_trace_non_minimal_zero",
     srcs: [
         "protos/perfetto/trace/extension_descriptor.proto",
         "protos/perfetto/trace/memory_graph.proto",
@@ -6931,11 +7719,50 @@
         "protos/perfetto/trace/trace_packet_defaults.proto",
         "protos/perfetto/trace/ui_state.proto",
     ],
+}
+
+// GN: //protos/perfetto/trace:non_minimal_zero
+genrule {
+    name: "perfetto_protos_perfetto_trace_non_minimal_zero_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_common_zero",
+        ":perfetto_protos_perfetto_config_android_zero",
+        ":perfetto_protos_perfetto_config_ftrace_zero",
+        ":perfetto_protos_perfetto_config_gpu_zero",
+        ":perfetto_protos_perfetto_config_inode_file_zero",
+        ":perfetto_protos_perfetto_config_interceptors_zero",
+        ":perfetto_protos_perfetto_config_power_zero",
+        ":perfetto_protos_perfetto_config_process_stats_zero",
+        ":perfetto_protos_perfetto_config_profiling_zero",
+        ":perfetto_protos_perfetto_config_statsd_zero",
+        ":perfetto_protos_perfetto_config_sys_stats_zero",
+        ":perfetto_protos_perfetto_config_system_info_zero",
+        ":perfetto_protos_perfetto_config_track_event_zero",
+        ":perfetto_protos_perfetto_config_zero",
+        ":perfetto_protos_perfetto_trace_android_zero",
+        ":perfetto_protos_perfetto_trace_chrome_zero",
+        ":perfetto_protos_perfetto_trace_etw_zero",
+        ":perfetto_protos_perfetto_trace_filesystem_zero",
+        ":perfetto_protos_perfetto_trace_ftrace_zero",
+        ":perfetto_protos_perfetto_trace_gpu_zero",
+        ":perfetto_protos_perfetto_trace_interned_data_zero",
+        ":perfetto_protos_perfetto_trace_minimal_zero",
+        ":perfetto_protos_perfetto_trace_non_minimal_zero",
+        ":perfetto_protos_perfetto_trace_perfetto_zero",
+        ":perfetto_protos_perfetto_trace_power_zero",
+        ":perfetto_protos_perfetto_trace_profiling_zero",
+        ":perfetto_protos_perfetto_trace_ps_zero",
+        ":perfetto_protos_perfetto_trace_statsd_zero",
+        ":perfetto_protos_perfetto_trace_sys_stats_zero",
+        ":perfetto_protos_perfetto_trace_system_info_zero",
+        ":perfetto_protos_perfetto_trace_track_event_zero",
+        ":perfetto_protos_perfetto_trace_translation_zero",
+    ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_non_minimal_zero)",
     out: [
         "external/perfetto/protos/perfetto/trace/extension_descriptor.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/memory_graph.pbzero.cc",
@@ -6952,20 +7779,44 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_non_minimal_zero_gen_headers",
     srcs: [
-        "protos/perfetto/trace/extension_descriptor.proto",
-        "protos/perfetto/trace/memory_graph.proto",
-        "protos/perfetto/trace/test_event.proto",
-        "protos/perfetto/trace/test_extensions.proto",
-        "protos/perfetto/trace/trace.proto",
-        "protos/perfetto/trace/trace_packet.proto",
-        "protos/perfetto/trace/trace_packet_defaults.proto",
-        "protos/perfetto/trace/ui_state.proto",
+        ":perfetto_protos_perfetto_common_zero",
+        ":perfetto_protos_perfetto_config_android_zero",
+        ":perfetto_protos_perfetto_config_ftrace_zero",
+        ":perfetto_protos_perfetto_config_gpu_zero",
+        ":perfetto_protos_perfetto_config_inode_file_zero",
+        ":perfetto_protos_perfetto_config_interceptors_zero",
+        ":perfetto_protos_perfetto_config_power_zero",
+        ":perfetto_protos_perfetto_config_process_stats_zero",
+        ":perfetto_protos_perfetto_config_profiling_zero",
+        ":perfetto_protos_perfetto_config_statsd_zero",
+        ":perfetto_protos_perfetto_config_sys_stats_zero",
+        ":perfetto_protos_perfetto_config_system_info_zero",
+        ":perfetto_protos_perfetto_config_track_event_zero",
+        ":perfetto_protos_perfetto_config_zero",
+        ":perfetto_protos_perfetto_trace_android_zero",
+        ":perfetto_protos_perfetto_trace_chrome_zero",
+        ":perfetto_protos_perfetto_trace_etw_zero",
+        ":perfetto_protos_perfetto_trace_filesystem_zero",
+        ":perfetto_protos_perfetto_trace_ftrace_zero",
+        ":perfetto_protos_perfetto_trace_gpu_zero",
+        ":perfetto_protos_perfetto_trace_interned_data_zero",
+        ":perfetto_protos_perfetto_trace_minimal_zero",
+        ":perfetto_protos_perfetto_trace_non_minimal_zero",
+        ":perfetto_protos_perfetto_trace_perfetto_zero",
+        ":perfetto_protos_perfetto_trace_power_zero",
+        ":perfetto_protos_perfetto_trace_profiling_zero",
+        ":perfetto_protos_perfetto_trace_ps_zero",
+        ":perfetto_protos_perfetto_trace_statsd_zero",
+        ":perfetto_protos_perfetto_trace_sys_stats_zero",
+        ":perfetto_protos_perfetto_trace_system_info_zero",
+        ":perfetto_protos_perfetto_trace_track_event_zero",
+        ":perfetto_protos_perfetto_trace_translation_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_non_minimal_zero)",
     out: [
         "external/perfetto/protos/perfetto/trace/extension_descriptor.pbzero.h",
         "external/perfetto/protos/perfetto/trace/memory_graph.pbzero.h",
@@ -6983,17 +7834,25 @@
 }
 
 // GN: //protos/perfetto/trace/perfetto:cpp
-genrule {
-    name: "perfetto_protos_perfetto_trace_perfetto_cpp_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_trace_perfetto_cpp",
     srcs: [
         "protos/perfetto/trace/perfetto/perfetto_metatrace.proto",
         "protos/perfetto/trace/perfetto/tracing_service_event.proto",
     ],
+}
+
+// GN: //protos/perfetto/trace/perfetto:cpp
+genrule {
+    name: "perfetto_protos_perfetto_trace_perfetto_cpp_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_trace_perfetto_cpp",
+    ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_perfetto_cpp)",
     out: [
         "external/perfetto/protos/perfetto/trace/perfetto/perfetto_metatrace.gen.cc",
         "external/perfetto/protos/perfetto/trace/perfetto/tracing_service_event.gen.cc",
@@ -7004,14 +7863,13 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_perfetto_cpp_gen_headers",
     srcs: [
-        "protos/perfetto/trace/perfetto/perfetto_metatrace.proto",
-        "protos/perfetto/trace/perfetto/tracing_service_event.proto",
+        ":perfetto_protos_perfetto_trace_perfetto_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_perfetto_cpp)",
     out: [
         "external/perfetto/protos/perfetto/trace/perfetto/perfetto_metatrace.gen.h",
         "external/perfetto/protos/perfetto/trace/perfetto/tracing_service_event.gen.h",
@@ -7023,16 +7881,24 @@
 }
 
 // GN: //protos/perfetto/trace/perfetto:lite
-genrule {
-    name: "perfetto_protos_perfetto_trace_perfetto_lite_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_trace_perfetto_lite",
     srcs: [
         "protos/perfetto/trace/perfetto/perfetto_metatrace.proto",
         "protos/perfetto/trace/perfetto/tracing_service_event.proto",
     ],
+}
+
+// GN: //protos/perfetto/trace/perfetto:lite
+genrule {
+    name: "perfetto_protos_perfetto_trace_perfetto_lite_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_trace_perfetto_lite",
+    ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_perfetto_lite)",
     out: [
         "external/perfetto/protos/perfetto/trace/perfetto/perfetto_metatrace.pb.cc",
         "external/perfetto/protos/perfetto/trace/perfetto/tracing_service_event.pb.cc",
@@ -7043,13 +7909,12 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_perfetto_lite_gen_headers",
     srcs: [
-        "protos/perfetto/trace/perfetto/perfetto_metatrace.proto",
-        "protos/perfetto/trace/perfetto/tracing_service_event.proto",
+        ":perfetto_protos_perfetto_trace_perfetto_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_perfetto_lite)",
     out: [
         "external/perfetto/protos/perfetto/trace/perfetto/perfetto_metatrace.pb.h",
         "external/perfetto/protos/perfetto/trace/perfetto/tracing_service_event.pb.h",
@@ -7061,17 +7926,25 @@
 }
 
 // GN: //protos/perfetto/trace/perfetto:zero
-genrule {
-    name: "perfetto_protos_perfetto_trace_perfetto_zero_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_trace_perfetto_zero",
     srcs: [
         "protos/perfetto/trace/perfetto/perfetto_metatrace.proto",
         "protos/perfetto/trace/perfetto/tracing_service_event.proto",
     ],
+}
+
+// GN: //protos/perfetto/trace/perfetto:zero
+genrule {
+    name: "perfetto_protos_perfetto_trace_perfetto_zero_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_trace_perfetto_zero",
+    ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_perfetto_zero)",
     out: [
         "external/perfetto/protos/perfetto/trace/perfetto/perfetto_metatrace.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/perfetto/tracing_service_event.pbzero.cc",
@@ -7082,14 +7955,13 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_perfetto_zero_gen_headers",
     srcs: [
-        "protos/perfetto/trace/perfetto/perfetto_metatrace.proto",
-        "protos/perfetto/trace/perfetto/tracing_service_event.proto",
+        ":perfetto_protos_perfetto_trace_perfetto_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_perfetto_zero)",
     out: [
         "external/perfetto/protos/perfetto/trace/perfetto/perfetto_metatrace.pbzero.h",
         "external/perfetto/protos/perfetto/trace/perfetto/tracing_service_event.pbzero.h",
@@ -7101,19 +7973,28 @@
 }
 
 // GN: //protos/perfetto/trace/power:cpp
-genrule {
-    name: "perfetto_protos_perfetto_trace_power_cpp_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_trace_power_cpp",
     srcs: [
         "protos/perfetto/trace/power/android_energy_estimation_breakdown.proto",
         "protos/perfetto/trace/power/android_entity_state_residency.proto",
         "protos/perfetto/trace/power/battery_counters.proto",
         "protos/perfetto/trace/power/power_rails.proto",
     ],
+}
+
+// GN: //protos/perfetto/trace/power:cpp
+genrule {
+    name: "perfetto_protos_perfetto_trace_power_cpp_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_common_cpp",
+        ":perfetto_protos_perfetto_trace_power_cpp",
+    ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_power_cpp)",
     out: [
         "external/perfetto/protos/perfetto/trace/power/android_energy_estimation_breakdown.gen.cc",
         "external/perfetto/protos/perfetto/trace/power/android_entity_state_residency.gen.cc",
@@ -7126,16 +8007,14 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_power_cpp_gen_headers",
     srcs: [
-        "protos/perfetto/trace/power/android_energy_estimation_breakdown.proto",
-        "protos/perfetto/trace/power/android_entity_state_residency.proto",
-        "protos/perfetto/trace/power/battery_counters.proto",
-        "protos/perfetto/trace/power/power_rails.proto",
+        ":perfetto_protos_perfetto_common_cpp",
+        ":perfetto_protos_perfetto_trace_power_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_power_cpp)",
     out: [
         "external/perfetto/protos/perfetto/trace/power/android_energy_estimation_breakdown.gen.h",
         "external/perfetto/protos/perfetto/trace/power/android_entity_state_residency.gen.h",
@@ -7149,18 +8028,27 @@
 }
 
 // GN: //protos/perfetto/trace/power:lite
-genrule {
-    name: "perfetto_protos_perfetto_trace_power_lite_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_trace_power_lite",
     srcs: [
         "protos/perfetto/trace/power/android_energy_estimation_breakdown.proto",
         "protos/perfetto/trace/power/android_entity_state_residency.proto",
         "protos/perfetto/trace/power/battery_counters.proto",
         "protos/perfetto/trace/power/power_rails.proto",
     ],
+}
+
+// GN: //protos/perfetto/trace/power:lite
+genrule {
+    name: "perfetto_protos_perfetto_trace_power_lite_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_common_lite",
+        ":perfetto_protos_perfetto_trace_power_lite",
+    ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_power_lite)",
     out: [
         "external/perfetto/protos/perfetto/trace/power/android_energy_estimation_breakdown.pb.cc",
         "external/perfetto/protos/perfetto/trace/power/android_entity_state_residency.pb.cc",
@@ -7173,15 +8061,13 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_power_lite_gen_headers",
     srcs: [
-        "protos/perfetto/trace/power/android_energy_estimation_breakdown.proto",
-        "protos/perfetto/trace/power/android_entity_state_residency.proto",
-        "protos/perfetto/trace/power/battery_counters.proto",
-        "protos/perfetto/trace/power/power_rails.proto",
+        ":perfetto_protos_perfetto_common_lite",
+        ":perfetto_protos_perfetto_trace_power_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_power_lite)",
     out: [
         "external/perfetto/protos/perfetto/trace/power/android_energy_estimation_breakdown.pb.h",
         "external/perfetto/protos/perfetto/trace/power/android_entity_state_residency.pb.h",
@@ -7195,19 +8081,28 @@
 }
 
 // GN: //protos/perfetto/trace/power:zero
-genrule {
-    name: "perfetto_protos_perfetto_trace_power_zero_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_trace_power_zero",
     srcs: [
         "protos/perfetto/trace/power/android_energy_estimation_breakdown.proto",
         "protos/perfetto/trace/power/android_entity_state_residency.proto",
         "protos/perfetto/trace/power/battery_counters.proto",
         "protos/perfetto/trace/power/power_rails.proto",
     ],
+}
+
+// GN: //protos/perfetto/trace/power:zero
+genrule {
+    name: "perfetto_protos_perfetto_trace_power_zero_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_common_zero",
+        ":perfetto_protos_perfetto_trace_power_zero",
+    ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_power_zero)",
     out: [
         "external/perfetto/protos/perfetto/trace/power/android_energy_estimation_breakdown.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/power/android_entity_state_residency.pbzero.cc",
@@ -7220,16 +8115,14 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_power_zero_gen_headers",
     srcs: [
-        "protos/perfetto/trace/power/android_energy_estimation_breakdown.proto",
-        "protos/perfetto/trace/power/android_entity_state_residency.proto",
-        "protos/perfetto/trace/power/battery_counters.proto",
-        "protos/perfetto/trace/power/power_rails.proto",
+        ":perfetto_protos_perfetto_common_zero",
+        ":perfetto_protos_perfetto_trace_power_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_power_zero)",
     out: [
         "external/perfetto/protos/perfetto/trace/power/android_energy_estimation_breakdown.pbzero.h",
         "external/perfetto/protos/perfetto/trace/power/android_entity_state_residency.pbzero.h",
@@ -7243,17 +8136,26 @@
 }
 
 // GN: //protos/perfetto/trace_processor:lite
-genrule {
-    name: "perfetto_protos_perfetto_trace_processor_lite_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_trace_processor_lite",
     srcs: [
         "protos/perfetto/trace_processor/metatrace_categories.proto",
         "protos/perfetto/trace_processor/stack.proto",
         "protos/perfetto/trace_processor/trace_processor.proto",
     ],
+}
+
+// GN: //protos/perfetto/trace_processor:lite
+genrule {
+    name: "perfetto_protos_perfetto_trace_processor_lite_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_common_lite",
+        ":perfetto_protos_perfetto_trace_processor_lite",
+    ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_processor_lite)",
     out: [
         "external/perfetto/protos/perfetto/trace_processor/metatrace_categories.pb.cc",
         "external/perfetto/protos/perfetto/trace_processor/stack.pb.cc",
@@ -7265,14 +8167,13 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_processor_lite_gen_headers",
     srcs: [
-        "protos/perfetto/trace_processor/metatrace_categories.proto",
-        "protos/perfetto/trace_processor/stack.proto",
-        "protos/perfetto/trace_processor/trace_processor.proto",
+        ":perfetto_protos_perfetto_common_lite",
+        ":perfetto_protos_perfetto_trace_processor_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_processor_lite)",
     out: [
         "external/perfetto/protos/perfetto/trace_processor/metatrace_categories.pb.h",
         "external/perfetto/protos/perfetto/trace_processor/stack.pb.h",
@@ -7285,16 +8186,24 @@
 }
 
 // GN: //protos/perfetto/trace_processor:metrics_impl_zero
+filegroup {
+    name: "perfetto_protos_perfetto_trace_processor_metrics_impl_zero",
+    srcs: [
+        "protos/perfetto/trace_processor/metrics_impl.proto",
+    ],
+}
+
+// GN: //protos/perfetto/trace_processor:metrics_impl_zero
 genrule {
     name: "perfetto_protos_perfetto_trace_processor_metrics_impl_zero_gen",
     srcs: [
-        "protos/perfetto/trace_processor/metrics_impl.proto",
+        ":perfetto_protos_perfetto_trace_processor_metrics_impl_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_processor_metrics_impl_zero)",
     out: [
         "external/perfetto/protos/perfetto/trace_processor/metrics_impl.pbzero.cc",
     ],
@@ -7304,13 +8213,13 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_processor_metrics_impl_zero_gen_headers",
     srcs: [
-        "protos/perfetto/trace_processor/metrics_impl.proto",
+        ":perfetto_protos_perfetto_trace_processor_metrics_impl_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_processor_metrics_impl_zero)",
     out: [
         "external/perfetto/protos/perfetto/trace_processor/metrics_impl.pbzero.h",
     ],
@@ -7321,18 +8230,27 @@
 }
 
 // GN: //protos/perfetto/trace_processor:zero
-genrule {
-    name: "perfetto_protos_perfetto_trace_processor_zero_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_trace_processor_zero",
     srcs: [
         "protos/perfetto/trace_processor/metatrace_categories.proto",
         "protos/perfetto/trace_processor/stack.proto",
         "protos/perfetto/trace_processor/trace_processor.proto",
     ],
+}
+
+// GN: //protos/perfetto/trace_processor:zero
+genrule {
+    name: "perfetto_protos_perfetto_trace_processor_zero_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_common_zero",
+        ":perfetto_protos_perfetto_trace_processor_zero",
+    ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_processor_zero)",
     out: [
         "external/perfetto/protos/perfetto/trace_processor/metatrace_categories.pbzero.cc",
         "external/perfetto/protos/perfetto/trace_processor/stack.pbzero.cc",
@@ -7344,15 +8262,14 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_processor_zero_gen_headers",
     srcs: [
-        "protos/perfetto/trace_processor/metatrace_categories.proto",
-        "protos/perfetto/trace_processor/stack.proto",
-        "protos/perfetto/trace_processor/trace_processor.proto",
+        ":perfetto_protos_perfetto_common_zero",
+        ":perfetto_protos_perfetto_trace_processor_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_processor_zero)",
     out: [
         "external/perfetto/protos/perfetto/trace_processor/metatrace_categories.pbzero.h",
         "external/perfetto/protos/perfetto/trace_processor/stack.pbzero.h",
@@ -7365,8 +8282,8 @@
 }
 
 // GN: //protos/perfetto/trace/profiling:cpp
-genrule {
-    name: "perfetto_protos_perfetto_trace_profiling_cpp_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_trace_profiling_cpp",
     srcs: [
         "protos/perfetto/trace/profiling/deobfuscation.proto",
         "protos/perfetto/trace/profiling/heap_graph.proto",
@@ -7374,11 +8291,20 @@
         "protos/perfetto/trace/profiling/profile_packet.proto",
         "protos/perfetto/trace/profiling/smaps.proto",
     ],
+}
+
+// GN: //protos/perfetto/trace/profiling:cpp
+genrule {
+    name: "perfetto_protos_perfetto_trace_profiling_cpp_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_common_cpp",
+        ":perfetto_protos_perfetto_trace_profiling_cpp",
+    ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_profiling_cpp)",
     out: [
         "external/perfetto/protos/perfetto/trace/profiling/deobfuscation.gen.cc",
         "external/perfetto/protos/perfetto/trace/profiling/heap_graph.gen.cc",
@@ -7392,17 +8318,14 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_profiling_cpp_gen_headers",
     srcs: [
-        "protos/perfetto/trace/profiling/deobfuscation.proto",
-        "protos/perfetto/trace/profiling/heap_graph.proto",
-        "protos/perfetto/trace/profiling/profile_common.proto",
-        "protos/perfetto/trace/profiling/profile_packet.proto",
-        "protos/perfetto/trace/profiling/smaps.proto",
+        ":perfetto_protos_perfetto_common_cpp",
+        ":perfetto_protos_perfetto_trace_profiling_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_profiling_cpp)",
     out: [
         "external/perfetto/protos/perfetto/trace/profiling/deobfuscation.gen.h",
         "external/perfetto/protos/perfetto/trace/profiling/heap_graph.gen.h",
@@ -7417,8 +8340,8 @@
 }
 
 // GN: //protos/perfetto/trace/profiling:lite
-genrule {
-    name: "perfetto_protos_perfetto_trace_profiling_lite_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_trace_profiling_lite",
     srcs: [
         "protos/perfetto/trace/profiling/deobfuscation.proto",
         "protos/perfetto/trace/profiling/heap_graph.proto",
@@ -7426,10 +8349,19 @@
         "protos/perfetto/trace/profiling/profile_packet.proto",
         "protos/perfetto/trace/profiling/smaps.proto",
     ],
+}
+
+// GN: //protos/perfetto/trace/profiling:lite
+genrule {
+    name: "perfetto_protos_perfetto_trace_profiling_lite_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_common_lite",
+        ":perfetto_protos_perfetto_trace_profiling_lite",
+    ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_profiling_lite)",
     out: [
         "external/perfetto/protos/perfetto/trace/profiling/deobfuscation.pb.cc",
         "external/perfetto/protos/perfetto/trace/profiling/heap_graph.pb.cc",
@@ -7443,16 +8375,13 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_profiling_lite_gen_headers",
     srcs: [
-        "protos/perfetto/trace/profiling/deobfuscation.proto",
-        "protos/perfetto/trace/profiling/heap_graph.proto",
-        "protos/perfetto/trace/profiling/profile_common.proto",
-        "protos/perfetto/trace/profiling/profile_packet.proto",
-        "protos/perfetto/trace/profiling/smaps.proto",
+        ":perfetto_protos_perfetto_common_lite",
+        ":perfetto_protos_perfetto_trace_profiling_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_profiling_lite)",
     out: [
         "external/perfetto/protos/perfetto/trace/profiling/deobfuscation.pb.h",
         "external/perfetto/protos/perfetto/trace/profiling/heap_graph.pb.h",
@@ -7467,8 +8396,8 @@
 }
 
 // GN: //protos/perfetto/trace/profiling:zero
-genrule {
-    name: "perfetto_protos_perfetto_trace_profiling_zero_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_trace_profiling_zero",
     srcs: [
         "protos/perfetto/trace/profiling/deobfuscation.proto",
         "protos/perfetto/trace/profiling/heap_graph.proto",
@@ -7476,11 +8405,20 @@
         "protos/perfetto/trace/profiling/profile_packet.proto",
         "protos/perfetto/trace/profiling/smaps.proto",
     ],
+}
+
+// GN: //protos/perfetto/trace/profiling:zero
+genrule {
+    name: "perfetto_protos_perfetto_trace_profiling_zero_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_common_zero",
+        ":perfetto_protos_perfetto_trace_profiling_zero",
+    ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_profiling_zero)",
     out: [
         "external/perfetto/protos/perfetto/trace/profiling/deobfuscation.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/profiling/heap_graph.pbzero.cc",
@@ -7494,17 +8432,14 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_profiling_zero_gen_headers",
     srcs: [
-        "protos/perfetto/trace/profiling/deobfuscation.proto",
-        "protos/perfetto/trace/profiling/heap_graph.proto",
-        "protos/perfetto/trace/profiling/profile_common.proto",
-        "protos/perfetto/trace/profiling/profile_packet.proto",
-        "protos/perfetto/trace/profiling/smaps.proto",
+        ":perfetto_protos_perfetto_common_zero",
+        ":perfetto_protos_perfetto_trace_profiling_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_profiling_zero)",
     out: [
         "external/perfetto/protos/perfetto/trace/profiling/deobfuscation.pbzero.h",
         "external/perfetto/protos/perfetto/trace/profiling/heap_graph.pbzero.h",
@@ -7519,17 +8454,25 @@
 }
 
 // GN: //protos/perfetto/trace/ps:cpp
-genrule {
-    name: "perfetto_protos_perfetto_trace_ps_cpp_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_trace_ps_cpp",
     srcs: [
         "protos/perfetto/trace/ps/process_stats.proto",
         "protos/perfetto/trace/ps/process_tree.proto",
     ],
+}
+
+// GN: //protos/perfetto/trace/ps:cpp
+genrule {
+    name: "perfetto_protos_perfetto_trace_ps_cpp_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_trace_ps_cpp",
+    ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_ps_cpp)",
     out: [
         "external/perfetto/protos/perfetto/trace/ps/process_stats.gen.cc",
         "external/perfetto/protos/perfetto/trace/ps/process_tree.gen.cc",
@@ -7540,14 +8483,13 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_ps_cpp_gen_headers",
     srcs: [
-        "protos/perfetto/trace/ps/process_stats.proto",
-        "protos/perfetto/trace/ps/process_tree.proto",
+        ":perfetto_protos_perfetto_trace_ps_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_ps_cpp)",
     out: [
         "external/perfetto/protos/perfetto/trace/ps/process_stats.gen.h",
         "external/perfetto/protos/perfetto/trace/ps/process_tree.gen.h",
@@ -7559,16 +8501,24 @@
 }
 
 // GN: //protos/perfetto/trace/ps:lite
-genrule {
-    name: "perfetto_protos_perfetto_trace_ps_lite_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_trace_ps_lite",
     srcs: [
         "protos/perfetto/trace/ps/process_stats.proto",
         "protos/perfetto/trace/ps/process_tree.proto",
     ],
+}
+
+// GN: //protos/perfetto/trace/ps:lite
+genrule {
+    name: "perfetto_protos_perfetto_trace_ps_lite_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_trace_ps_lite",
+    ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_ps_lite)",
     out: [
         "external/perfetto/protos/perfetto/trace/ps/process_stats.pb.cc",
         "external/perfetto/protos/perfetto/trace/ps/process_tree.pb.cc",
@@ -7579,13 +8529,12 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_ps_lite_gen_headers",
     srcs: [
-        "protos/perfetto/trace/ps/process_stats.proto",
-        "protos/perfetto/trace/ps/process_tree.proto",
+        ":perfetto_protos_perfetto_trace_ps_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_ps_lite)",
     out: [
         "external/perfetto/protos/perfetto/trace/ps/process_stats.pb.h",
         "external/perfetto/protos/perfetto/trace/ps/process_tree.pb.h",
@@ -7597,17 +8546,25 @@
 }
 
 // GN: //protos/perfetto/trace/ps:zero
-genrule {
-    name: "perfetto_protos_perfetto_trace_ps_zero_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_trace_ps_zero",
     srcs: [
         "protos/perfetto/trace/ps/process_stats.proto",
         "protos/perfetto/trace/ps/process_tree.proto",
     ],
+}
+
+// GN: //protos/perfetto/trace/ps:zero
+genrule {
+    name: "perfetto_protos_perfetto_trace_ps_zero_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_trace_ps_zero",
+    ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_ps_zero)",
     out: [
         "external/perfetto/protos/perfetto/trace/ps/process_stats.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/ps/process_tree.pbzero.cc",
@@ -7618,14 +8575,13 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_ps_zero_gen_headers",
     srcs: [
-        "protos/perfetto/trace/ps/process_stats.proto",
-        "protos/perfetto/trace/ps/process_tree.proto",
+        ":perfetto_protos_perfetto_trace_ps_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_ps_zero)",
     out: [
         "external/perfetto/protos/perfetto/trace/ps/process_stats.pbzero.h",
         "external/perfetto/protos/perfetto/trace/ps/process_tree.pbzero.h",
@@ -7637,16 +8593,25 @@
 }
 
 // GN: //protos/perfetto/trace/statsd:cpp
+filegroup {
+    name: "perfetto_protos_perfetto_trace_statsd_cpp",
+    srcs: [
+        "protos/perfetto/trace/statsd/statsd_atom.proto",
+    ],
+}
+
+// GN: //protos/perfetto/trace/statsd:cpp
 genrule {
     name: "perfetto_protos_perfetto_trace_statsd_cpp_gen",
     srcs: [
-        "protos/perfetto/trace/statsd/statsd_atom.proto",
+        ":perfetto_protos_perfetto_common_cpp",
+        ":perfetto_protos_perfetto_trace_statsd_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_statsd_cpp)",
     out: [
         "external/perfetto/protos/perfetto/trace/statsd/statsd_atom.gen.cc",
     ],
@@ -7656,13 +8621,14 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_statsd_cpp_gen_headers",
     srcs: [
-        "protos/perfetto/trace/statsd/statsd_atom.proto",
+        ":perfetto_protos_perfetto_common_cpp",
+        ":perfetto_protos_perfetto_trace_statsd_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_statsd_cpp)",
     out: [
         "external/perfetto/protos/perfetto/trace/statsd/statsd_atom.gen.h",
     ],
@@ -7673,15 +8639,24 @@
 }
 
 // GN: //protos/perfetto/trace/statsd:lite
+filegroup {
+    name: "perfetto_protos_perfetto_trace_statsd_lite",
+    srcs: [
+        "protos/perfetto/trace/statsd/statsd_atom.proto",
+    ],
+}
+
+// GN: //protos/perfetto/trace/statsd:lite
 genrule {
     name: "perfetto_protos_perfetto_trace_statsd_lite_gen",
     srcs: [
-        "protos/perfetto/trace/statsd/statsd_atom.proto",
+        ":perfetto_protos_perfetto_common_lite",
+        ":perfetto_protos_perfetto_trace_statsd_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_statsd_lite)",
     out: [
         "external/perfetto/protos/perfetto/trace/statsd/statsd_atom.pb.cc",
     ],
@@ -7691,12 +8666,13 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_statsd_lite_gen_headers",
     srcs: [
-        "protos/perfetto/trace/statsd/statsd_atom.proto",
+        ":perfetto_protos_perfetto_common_lite",
+        ":perfetto_protos_perfetto_trace_statsd_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_statsd_lite)",
     out: [
         "external/perfetto/protos/perfetto/trace/statsd/statsd_atom.pb.h",
     ],
@@ -7707,16 +8683,25 @@
 }
 
 // GN: //protos/perfetto/trace/statsd:zero
+filegroup {
+    name: "perfetto_protos_perfetto_trace_statsd_zero",
+    srcs: [
+        "protos/perfetto/trace/statsd/statsd_atom.proto",
+    ],
+}
+
+// GN: //protos/perfetto/trace/statsd:zero
 genrule {
     name: "perfetto_protos_perfetto_trace_statsd_zero_gen",
     srcs: [
-        "protos/perfetto/trace/statsd/statsd_atom.proto",
+        ":perfetto_protos_perfetto_common_zero",
+        ":perfetto_protos_perfetto_trace_statsd_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_statsd_zero)",
     out: [
         "external/perfetto/protos/perfetto/trace/statsd/statsd_atom.pbzero.cc",
     ],
@@ -7726,13 +8711,14 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_statsd_zero_gen_headers",
     srcs: [
-        "protos/perfetto/trace/statsd/statsd_atom.proto",
+        ":perfetto_protos_perfetto_common_zero",
+        ":perfetto_protos_perfetto_trace_statsd_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_statsd_zero)",
     out: [
         "external/perfetto/protos/perfetto/trace/statsd/statsd_atom.pbzero.h",
     ],
@@ -7743,16 +8729,25 @@
 }
 
 // GN: //protos/perfetto/trace/sys_stats:cpp
+filegroup {
+    name: "perfetto_protos_perfetto_trace_sys_stats_cpp",
+    srcs: [
+        "protos/perfetto/trace/sys_stats/sys_stats.proto",
+    ],
+}
+
+// GN: //protos/perfetto/trace/sys_stats:cpp
 genrule {
     name: "perfetto_protos_perfetto_trace_sys_stats_cpp_gen",
     srcs: [
-        "protos/perfetto/trace/sys_stats/sys_stats.proto",
+        ":perfetto_protos_perfetto_common_cpp",
+        ":perfetto_protos_perfetto_trace_sys_stats_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_sys_stats_cpp)",
     out: [
         "external/perfetto/protos/perfetto/trace/sys_stats/sys_stats.gen.cc",
     ],
@@ -7762,13 +8757,14 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_sys_stats_cpp_gen_headers",
     srcs: [
-        "protos/perfetto/trace/sys_stats/sys_stats.proto",
+        ":perfetto_protos_perfetto_common_cpp",
+        ":perfetto_protos_perfetto_trace_sys_stats_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_sys_stats_cpp)",
     out: [
         "external/perfetto/protos/perfetto/trace/sys_stats/sys_stats.gen.h",
     ],
@@ -7779,15 +8775,24 @@
 }
 
 // GN: //protos/perfetto/trace/sys_stats:lite
+filegroup {
+    name: "perfetto_protos_perfetto_trace_sys_stats_lite",
+    srcs: [
+        "protos/perfetto/trace/sys_stats/sys_stats.proto",
+    ],
+}
+
+// GN: //protos/perfetto/trace/sys_stats:lite
 genrule {
     name: "perfetto_protos_perfetto_trace_sys_stats_lite_gen",
     srcs: [
-        "protos/perfetto/trace/sys_stats/sys_stats.proto",
+        ":perfetto_protos_perfetto_common_lite",
+        ":perfetto_protos_perfetto_trace_sys_stats_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_sys_stats_lite)",
     out: [
         "external/perfetto/protos/perfetto/trace/sys_stats/sys_stats.pb.cc",
     ],
@@ -7797,12 +8802,13 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_sys_stats_lite_gen_headers",
     srcs: [
-        "protos/perfetto/trace/sys_stats/sys_stats.proto",
+        ":perfetto_protos_perfetto_common_lite",
+        ":perfetto_protos_perfetto_trace_sys_stats_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_sys_stats_lite)",
     out: [
         "external/perfetto/protos/perfetto/trace/sys_stats/sys_stats.pb.h",
     ],
@@ -7813,16 +8819,25 @@
 }
 
 // GN: //protos/perfetto/trace/sys_stats:zero
+filegroup {
+    name: "perfetto_protos_perfetto_trace_sys_stats_zero",
+    srcs: [
+        "protos/perfetto/trace/sys_stats/sys_stats.proto",
+    ],
+}
+
+// GN: //protos/perfetto/trace/sys_stats:zero
 genrule {
     name: "perfetto_protos_perfetto_trace_sys_stats_zero_gen",
     srcs: [
-        "protos/perfetto/trace/sys_stats/sys_stats.proto",
+        ":perfetto_protos_perfetto_common_zero",
+        ":perfetto_protos_perfetto_trace_sys_stats_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_sys_stats_zero)",
     out: [
         "external/perfetto/protos/perfetto/trace/sys_stats/sys_stats.pbzero.cc",
     ],
@@ -7832,13 +8847,14 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_sys_stats_zero_gen_headers",
     srcs: [
-        "protos/perfetto/trace/sys_stats/sys_stats.proto",
+        ":perfetto_protos_perfetto_common_zero",
+        ":perfetto_protos_perfetto_trace_sys_stats_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_sys_stats_zero)",
     out: [
         "external/perfetto/protos/perfetto/trace/sys_stats/sys_stats.pbzero.h",
     ],
@@ -7849,16 +8865,24 @@
 }
 
 // GN: //protos/perfetto/trace/system_info:cpp
+filegroup {
+    name: "perfetto_protos_perfetto_trace_system_info_cpp",
+    srcs: [
+        "protos/perfetto/trace/system_info/cpu_info.proto",
+    ],
+}
+
+// GN: //protos/perfetto/trace/system_info:cpp
 genrule {
     name: "perfetto_protos_perfetto_trace_system_info_cpp_gen",
     srcs: [
-        "protos/perfetto/trace/system_info/cpu_info.proto",
+        ":perfetto_protos_perfetto_trace_system_info_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_system_info_cpp)",
     out: [
         "external/perfetto/protos/perfetto/trace/system_info/cpu_info.gen.cc",
     ],
@@ -7868,13 +8892,13 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_system_info_cpp_gen_headers",
     srcs: [
-        "protos/perfetto/trace/system_info/cpu_info.proto",
+        ":perfetto_protos_perfetto_trace_system_info_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_system_info_cpp)",
     out: [
         "external/perfetto/protos/perfetto/trace/system_info/cpu_info.gen.h",
     ],
@@ -7885,15 +8909,23 @@
 }
 
 // GN: //protos/perfetto/trace/system_info:lite
+filegroup {
+    name: "perfetto_protos_perfetto_trace_system_info_lite",
+    srcs: [
+        "protos/perfetto/trace/system_info/cpu_info.proto",
+    ],
+}
+
+// GN: //protos/perfetto/trace/system_info:lite
 genrule {
     name: "perfetto_protos_perfetto_trace_system_info_lite_gen",
     srcs: [
-        "protos/perfetto/trace/system_info/cpu_info.proto",
+        ":perfetto_protos_perfetto_trace_system_info_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_system_info_lite)",
     out: [
         "external/perfetto/protos/perfetto/trace/system_info/cpu_info.pb.cc",
     ],
@@ -7903,12 +8935,12 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_system_info_lite_gen_headers",
     srcs: [
-        "protos/perfetto/trace/system_info/cpu_info.proto",
+        ":perfetto_protos_perfetto_trace_system_info_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_system_info_lite)",
     out: [
         "external/perfetto/protos/perfetto/trace/system_info/cpu_info.pb.h",
     ],
@@ -7919,16 +8951,24 @@
 }
 
 // GN: //protos/perfetto/trace/system_info:zero
+filegroup {
+    name: "perfetto_protos_perfetto_trace_system_info_zero",
+    srcs: [
+        "protos/perfetto/trace/system_info/cpu_info.proto",
+    ],
+}
+
+// GN: //protos/perfetto/trace/system_info:zero
 genrule {
     name: "perfetto_protos_perfetto_trace_system_info_zero_gen",
     srcs: [
-        "protos/perfetto/trace/system_info/cpu_info.proto",
+        ":perfetto_protos_perfetto_trace_system_info_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_system_info_zero)",
     out: [
         "external/perfetto/protos/perfetto/trace/system_info/cpu_info.pbzero.cc",
     ],
@@ -7938,13 +8978,13 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_system_info_zero_gen_headers",
     srcs: [
-        "protos/perfetto/trace/system_info/cpu_info.proto",
+        ":perfetto_protos_perfetto_trace_system_info_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_system_info_zero)",
     out: [
         "external/perfetto/protos/perfetto/trace/system_info/cpu_info.pbzero.h",
     ],
@@ -7955,8 +8995,8 @@
 }
 
 // GN: //protos/perfetto/trace/track_event:cpp
-genrule {
-    name: "perfetto_protos_perfetto_trace_track_event_cpp_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_trace_track_event_cpp",
     srcs: [
         "protos/perfetto/trace/track_event/chrome_active_processes.proto",
         "protos/perfetto/trace/track_event/chrome_application_state_info.proto",
@@ -7986,11 +9026,19 @@
         "protos/perfetto/trace/track_event/track_descriptor.proto",
         "protos/perfetto/trace/track_event/track_event.proto",
     ],
+}
+
+// GN: //protos/perfetto/trace/track_event:cpp
+genrule {
+    name: "perfetto_protos_perfetto_trace_track_event_cpp_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_trace_track_event_cpp",
+    ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_track_event_cpp)",
     out: [
         "external/perfetto/protos/perfetto/trace/track_event/chrome_active_processes.gen.cc",
         "external/perfetto/protos/perfetto/trace/track_event/chrome_application_state_info.gen.cc",
@@ -8026,39 +9074,13 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_track_event_cpp_gen_headers",
     srcs: [
-        "protos/perfetto/trace/track_event/chrome_active_processes.proto",
-        "protos/perfetto/trace/track_event/chrome_application_state_info.proto",
-        "protos/perfetto/trace/track_event/chrome_compositor_scheduler_state.proto",
-        "protos/perfetto/trace/track_event/chrome_content_settings_event_info.proto",
-        "protos/perfetto/trace/track_event/chrome_frame_reporter.proto",
-        "protos/perfetto/trace/track_event/chrome_histogram_sample.proto",
-        "protos/perfetto/trace/track_event/chrome_keyed_service.proto",
-        "protos/perfetto/trace/track_event/chrome_latency_info.proto",
-        "protos/perfetto/trace/track_event/chrome_legacy_ipc.proto",
-        "protos/perfetto/trace/track_event/chrome_message_pump.proto",
-        "protos/perfetto/trace/track_event/chrome_mojo_event_info.proto",
-        "protos/perfetto/trace/track_event/chrome_process_descriptor.proto",
-        "protos/perfetto/trace/track_event/chrome_renderer_scheduler_state.proto",
-        "protos/perfetto/trace/track_event/chrome_thread_descriptor.proto",
-        "protos/perfetto/trace/track_event/chrome_user_event.proto",
-        "protos/perfetto/trace/track_event/chrome_window_handle_event_info.proto",
-        "protos/perfetto/trace/track_event/counter_descriptor.proto",
-        "protos/perfetto/trace/track_event/debug_annotation.proto",
-        "protos/perfetto/trace/track_event/log_message.proto",
-        "protos/perfetto/trace/track_event/process_descriptor.proto",
-        "protos/perfetto/trace/track_event/range_of_interest.proto",
-        "protos/perfetto/trace/track_event/screenshot.proto",
-        "protos/perfetto/trace/track_event/source_location.proto",
-        "protos/perfetto/trace/track_event/task_execution.proto",
-        "protos/perfetto/trace/track_event/thread_descriptor.proto",
-        "protos/perfetto/trace/track_event/track_descriptor.proto",
-        "protos/perfetto/trace/track_event/track_event.proto",
+        ":perfetto_protos_perfetto_trace_track_event_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_track_event_cpp)",
     out: [
         "external/perfetto/protos/perfetto/trace/track_event/chrome_active_processes.gen.h",
         "external/perfetto/protos/perfetto/trace/track_event/chrome_application_state_info.gen.h",
@@ -8136,8 +9158,8 @@
 }
 
 // GN: //protos/perfetto/trace/track_event:lite
-genrule {
-    name: "perfetto_protos_perfetto_trace_track_event_lite_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_trace_track_event_lite",
     srcs: [
         "protos/perfetto/trace/track_event/chrome_active_processes.proto",
         "protos/perfetto/trace/track_event/chrome_application_state_info.proto",
@@ -8167,10 +9189,18 @@
         "protos/perfetto/trace/track_event/track_descriptor.proto",
         "protos/perfetto/trace/track_event/track_event.proto",
     ],
+}
+
+// GN: //protos/perfetto/trace/track_event:lite
+genrule {
+    name: "perfetto_protos_perfetto_trace_track_event_lite_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_trace_track_event_lite",
+    ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_track_event_lite)",
     out: [
         "external/perfetto/protos/perfetto/trace/track_event/chrome_active_processes.pb.cc",
         "external/perfetto/protos/perfetto/trace/track_event/chrome_application_state_info.pb.cc",
@@ -8206,38 +9236,12 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_track_event_lite_gen_headers",
     srcs: [
-        "protos/perfetto/trace/track_event/chrome_active_processes.proto",
-        "protos/perfetto/trace/track_event/chrome_application_state_info.proto",
-        "protos/perfetto/trace/track_event/chrome_compositor_scheduler_state.proto",
-        "protos/perfetto/trace/track_event/chrome_content_settings_event_info.proto",
-        "protos/perfetto/trace/track_event/chrome_frame_reporter.proto",
-        "protos/perfetto/trace/track_event/chrome_histogram_sample.proto",
-        "protos/perfetto/trace/track_event/chrome_keyed_service.proto",
-        "protos/perfetto/trace/track_event/chrome_latency_info.proto",
-        "protos/perfetto/trace/track_event/chrome_legacy_ipc.proto",
-        "protos/perfetto/trace/track_event/chrome_message_pump.proto",
-        "protos/perfetto/trace/track_event/chrome_mojo_event_info.proto",
-        "protos/perfetto/trace/track_event/chrome_process_descriptor.proto",
-        "protos/perfetto/trace/track_event/chrome_renderer_scheduler_state.proto",
-        "protos/perfetto/trace/track_event/chrome_thread_descriptor.proto",
-        "protos/perfetto/trace/track_event/chrome_user_event.proto",
-        "protos/perfetto/trace/track_event/chrome_window_handle_event_info.proto",
-        "protos/perfetto/trace/track_event/counter_descriptor.proto",
-        "protos/perfetto/trace/track_event/debug_annotation.proto",
-        "protos/perfetto/trace/track_event/log_message.proto",
-        "protos/perfetto/trace/track_event/process_descriptor.proto",
-        "protos/perfetto/trace/track_event/range_of_interest.proto",
-        "protos/perfetto/trace/track_event/screenshot.proto",
-        "protos/perfetto/trace/track_event/source_location.proto",
-        "protos/perfetto/trace/track_event/task_execution.proto",
-        "protos/perfetto/trace/track_event/thread_descriptor.proto",
-        "protos/perfetto/trace/track_event/track_descriptor.proto",
-        "protos/perfetto/trace/track_event/track_event.proto",
+        ":perfetto_protos_perfetto_trace_track_event_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_track_event_lite)",
     out: [
         "external/perfetto/protos/perfetto/trace/track_event/chrome_active_processes.pb.h",
         "external/perfetto/protos/perfetto/trace/track_event/chrome_application_state_info.pb.h",
@@ -8274,8 +9278,8 @@
 }
 
 // GN: //protos/perfetto/trace/track_event:zero
-genrule {
-    name: "perfetto_protos_perfetto_trace_track_event_zero_gen",
+filegroup {
+    name: "perfetto_protos_perfetto_trace_track_event_zero",
     srcs: [
         "protos/perfetto/trace/track_event/chrome_active_processes.proto",
         "protos/perfetto/trace/track_event/chrome_application_state_info.proto",
@@ -8305,11 +9309,19 @@
         "protos/perfetto/trace/track_event/track_descriptor.proto",
         "protos/perfetto/trace/track_event/track_event.proto",
     ],
+}
+
+// GN: //protos/perfetto/trace/track_event:zero
+genrule {
+    name: "perfetto_protos_perfetto_trace_track_event_zero_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_trace_track_event_zero",
+    ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_track_event_zero)",
     out: [
         "external/perfetto/protos/perfetto/trace/track_event/chrome_active_processes.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/track_event/chrome_application_state_info.pbzero.cc",
@@ -8345,39 +9357,13 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_track_event_zero_gen_headers",
     srcs: [
-        "protos/perfetto/trace/track_event/chrome_active_processes.proto",
-        "protos/perfetto/trace/track_event/chrome_application_state_info.proto",
-        "protos/perfetto/trace/track_event/chrome_compositor_scheduler_state.proto",
-        "protos/perfetto/trace/track_event/chrome_content_settings_event_info.proto",
-        "protos/perfetto/trace/track_event/chrome_frame_reporter.proto",
-        "protos/perfetto/trace/track_event/chrome_histogram_sample.proto",
-        "protos/perfetto/trace/track_event/chrome_keyed_service.proto",
-        "protos/perfetto/trace/track_event/chrome_latency_info.proto",
-        "protos/perfetto/trace/track_event/chrome_legacy_ipc.proto",
-        "protos/perfetto/trace/track_event/chrome_message_pump.proto",
-        "protos/perfetto/trace/track_event/chrome_mojo_event_info.proto",
-        "protos/perfetto/trace/track_event/chrome_process_descriptor.proto",
-        "protos/perfetto/trace/track_event/chrome_renderer_scheduler_state.proto",
-        "protos/perfetto/trace/track_event/chrome_thread_descriptor.proto",
-        "protos/perfetto/trace/track_event/chrome_user_event.proto",
-        "protos/perfetto/trace/track_event/chrome_window_handle_event_info.proto",
-        "protos/perfetto/trace/track_event/counter_descriptor.proto",
-        "protos/perfetto/trace/track_event/debug_annotation.proto",
-        "protos/perfetto/trace/track_event/log_message.proto",
-        "protos/perfetto/trace/track_event/process_descriptor.proto",
-        "protos/perfetto/trace/track_event/range_of_interest.proto",
-        "protos/perfetto/trace/track_event/screenshot.proto",
-        "protos/perfetto/trace/track_event/source_location.proto",
-        "protos/perfetto/trace/track_event/task_execution.proto",
-        "protos/perfetto/trace/track_event/thread_descriptor.proto",
-        "protos/perfetto/trace/track_event/track_descriptor.proto",
-        "protos/perfetto/trace/track_event/track_event.proto",
+        ":perfetto_protos_perfetto_trace_track_event_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_track_event_zero)",
     out: [
         "external/perfetto/protos/perfetto/trace/track_event/chrome_active_processes.pbzero.h",
         "external/perfetto/protos/perfetto/trace/track_event/chrome_application_state_info.pbzero.h",
@@ -8414,16 +9400,24 @@
 }
 
 // GN: //protos/perfetto/trace/translation:cpp
+filegroup {
+    name: "perfetto_protos_perfetto_trace_translation_cpp",
+    srcs: [
+        "protos/perfetto/trace/translation/translation_table.proto",
+    ],
+}
+
+// GN: //protos/perfetto/trace/translation:cpp
 genrule {
     name: "perfetto_protos_perfetto_trace_translation_cpp_gen",
     srcs: [
-        "protos/perfetto/trace/translation/translation_table.proto",
+        ":perfetto_protos_perfetto_trace_translation_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_translation_cpp)",
     out: [
         "external/perfetto/protos/perfetto/trace/translation/translation_table.gen.cc",
     ],
@@ -8433,13 +9427,13 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_translation_cpp_gen_headers",
     srcs: [
-        "protos/perfetto/trace/translation/translation_table.proto",
+        ":perfetto_protos_perfetto_trace_translation_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_translation_cpp)",
     out: [
         "external/perfetto/protos/perfetto/trace/translation/translation_table.gen.h",
     ],
@@ -8450,15 +9444,23 @@
 }
 
 // GN: //protos/perfetto/trace/translation:lite
+filegroup {
+    name: "perfetto_protos_perfetto_trace_translation_lite",
+    srcs: [
+        "protos/perfetto/trace/translation/translation_table.proto",
+    ],
+}
+
+// GN: //protos/perfetto/trace/translation:lite
 genrule {
     name: "perfetto_protos_perfetto_trace_translation_lite_gen",
     srcs: [
-        "protos/perfetto/trace/translation/translation_table.proto",
+        ":perfetto_protos_perfetto_trace_translation_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_translation_lite)",
     out: [
         "external/perfetto/protos/perfetto/trace/translation/translation_table.pb.cc",
     ],
@@ -8468,12 +9470,12 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_translation_lite_gen_headers",
     srcs: [
-        "protos/perfetto/trace/translation/translation_table.proto",
+        ":perfetto_protos_perfetto_trace_translation_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_translation_lite)",
     out: [
         "external/perfetto/protos/perfetto/trace/translation/translation_table.pb.h",
     ],
@@ -8484,16 +9486,24 @@
 }
 
 // GN: //protos/perfetto/trace/translation:zero
+filegroup {
+    name: "perfetto_protos_perfetto_trace_translation_zero",
+    srcs: [
+        "protos/perfetto/trace/translation/translation_table.proto",
+    ],
+}
+
+// GN: //protos/perfetto/trace/translation:zero
 genrule {
     name: "perfetto_protos_perfetto_trace_translation_zero_gen",
     srcs: [
-        "protos/perfetto/trace/translation/translation_table.proto",
+        ":perfetto_protos_perfetto_trace_translation_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_translation_zero)",
     out: [
         "external/perfetto/protos/perfetto/trace/translation/translation_table.pbzero.cc",
     ],
@@ -8503,13 +9513,13 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_translation_zero_gen_headers",
     srcs: [
-        "protos/perfetto/trace/translation/translation_table.proto",
+        ":perfetto_protos_perfetto_trace_translation_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_translation_zero)",
     out: [
         "external/perfetto/protos/perfetto/trace/translation/translation_table.pbzero.h",
     ],
@@ -8562,16 +9572,24 @@
 }
 
 // GN: //protos/third_party/pprof:zero
+filegroup {
+    name: "perfetto_protos_third_party_pprof_zero",
+    srcs: [
+        "protos/third_party/pprof/profile.proto",
+    ],
+}
+
+// GN: //protos/third_party/pprof:zero
 genrule {
     name: "perfetto_protos_third_party_pprof_zero_gen",
     srcs: [
-        "protos/third_party/pprof/profile.proto",
+        ":perfetto_protos_third_party_pprof_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_third_party_pprof_zero)",
     out: [
         "external/perfetto/protos/third_party/pprof/profile.pbzero.cc",
     ],
@@ -8581,13 +9599,13 @@
 genrule {
     name: "perfetto_protos_third_party_pprof_zero_gen_headers",
     srcs: [
-        "protos/third_party/pprof/profile.proto",
+        ":perfetto_protos_third_party_pprof_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_third_party_pprof_zero)",
     out: [
         "external/perfetto/protos/third_party/pprof/profile.pbzero.h",
     ],
@@ -8598,17 +9616,25 @@
 }
 
 // GN: //protos/third_party/statsd:config_zero
-genrule {
-    name: "perfetto_protos_third_party_statsd_config_zero_gen",
+filegroup {
+    name: "perfetto_protos_third_party_statsd_config_zero",
     srcs: [
         "protos/third_party/statsd/shell_config.proto",
         "protos/third_party/statsd/shell_data.proto",
     ],
+}
+
+// GN: //protos/third_party/statsd:config_zero
+genrule {
+    name: "perfetto_protos_third_party_statsd_config_zero_gen",
+    srcs: [
+        ":perfetto_protos_third_party_statsd_config_zero",
+    ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_third_party_statsd_config_zero)",
     out: [
         "external/perfetto/protos/third_party/statsd/shell_config.pbzero.cc",
         "external/perfetto/protos/third_party/statsd/shell_data.pbzero.cc",
@@ -8619,14 +9645,13 @@
 genrule {
     name: "perfetto_protos_third_party_statsd_config_zero_gen_headers",
     srcs: [
-        "protos/third_party/statsd/shell_config.proto",
-        "protos/third_party/statsd/shell_data.proto",
+        ":perfetto_protos_third_party_statsd_config_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_third_party_statsd_config_zero)",
     out: [
         "external/perfetto/protos/third_party/statsd/shell_config.pbzero.h",
         "external/perfetto/protos/third_party/statsd/shell_data.pbzero.h",
@@ -8872,18 +9897,26 @@
 }
 
 // GN: //src/ipc:test_messages_cpp
-genrule {
-    name: "perfetto_src_ipc_test_messages_cpp_gen",
+filegroup {
+    name: "perfetto_src_ipc_test_messages_cpp",
     srcs: [
         "src/ipc/test/client_unittest_messages.proto",
         "src/ipc/test/deferred_unittest_messages.proto",
         "src/ipc/test/greeter_service.proto",
     ],
+}
+
+// GN: //src/ipc:test_messages_cpp
+genrule {
+    name: "perfetto_src_ipc_test_messages_cpp_gen",
+    srcs: [
+        ":perfetto_src_ipc_test_messages_cpp",
+    ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_src_ipc_test_messages_cpp)",
     out: [
         "external/perfetto/src/ipc/test/client_unittest_messages.gen.cc",
         "external/perfetto/src/ipc/test/deferred_unittest_messages.gen.cc",
@@ -8895,15 +9928,13 @@
 genrule {
     name: "perfetto_src_ipc_test_messages_cpp_gen_headers",
     srcs: [
-        "src/ipc/test/client_unittest_messages.proto",
-        "src/ipc/test/deferred_unittest_messages.proto",
-        "src/ipc/test/greeter_service.proto",
+        ":perfetto_src_ipc_test_messages_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_src_ipc_test_messages_cpp)",
     out: [
         "external/perfetto/src/ipc/test/client_unittest_messages.gen.h",
         "external/perfetto/src/ipc/test/deferred_unittest_messages.gen.h",
@@ -8916,18 +9947,28 @@
 }
 
 // GN: //src/ipc:test_messages_ipc
-genrule {
-    name: "perfetto_src_ipc_test_messages_ipc_gen",
+filegroup {
+    name: "perfetto_src_ipc_test_messages_ipc",
     srcs: [
         "src/ipc/test/client_unittest_messages.proto",
         "src/ipc/test/deferred_unittest_messages.proto",
         "src/ipc/test/greeter_service.proto",
     ],
+}
+
+// GN: //src/ipc:test_messages_ipc
+genrule {
+    name: "perfetto_src_ipc_test_messages_ipc_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_ipc_wire_protocol_cpp",
+        ":perfetto_src_ipc_test_messages_cpp",
+        ":perfetto_src_ipc_test_messages_ipc",
+    ],
     tools: [
         "aprotoc",
         "ipc_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location ipc_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location ipc_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_src_ipc_test_messages_ipc)",
     out: [
         "external/perfetto/src/ipc/test/client_unittest_messages.ipc.cc",
         "external/perfetto/src/ipc/test/deferred_unittest_messages.ipc.cc",
@@ -8939,15 +9980,15 @@
 genrule {
     name: "perfetto_src_ipc_test_messages_ipc_gen_headers",
     srcs: [
-        "src/ipc/test/client_unittest_messages.proto",
-        "src/ipc/test/deferred_unittest_messages.proto",
-        "src/ipc/test/greeter_service.proto",
+        ":perfetto_protos_perfetto_ipc_wire_protocol_cpp",
+        ":perfetto_src_ipc_test_messages_cpp",
+        ":perfetto_src_ipc_test_messages_ipc",
     ],
     tools: [
         "aprotoc",
         "ipc_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location ipc_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location ipc_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_src_ipc_test_messages_ipc)",
     out: [
         "external/perfetto/src/ipc/test/client_unittest_messages.ipc.h",
         "external/perfetto/src/ipc/test/deferred_unittest_messages.ipc.h",
@@ -9038,16 +10079,24 @@
 }
 
 // GN: //src/perfetto_cmd:protos_cpp
+filegroup {
+    name: "perfetto_src_perfetto_cmd_protos_cpp",
+    srcs: [
+        "src/perfetto_cmd/perfetto_cmd_state.proto",
+    ],
+}
+
+// GN: //src/perfetto_cmd:protos_cpp
 genrule {
     name: "perfetto_src_perfetto_cmd_protos_cpp_gen",
     srcs: [
-        "src/perfetto_cmd/perfetto_cmd_state.proto",
+        ":perfetto_src_perfetto_cmd_protos_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_src_perfetto_cmd_protos_cpp)",
     out: [
         "external/perfetto/src/perfetto_cmd/perfetto_cmd_state.gen.cc",
     ],
@@ -9057,13 +10106,13 @@
 genrule {
     name: "perfetto_src_perfetto_cmd_protos_cpp_gen_headers",
     srcs: [
-        "src/perfetto_cmd/perfetto_cmd_state.proto",
+        ":perfetto_src_perfetto_cmd_protos_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_src_perfetto_cmd_protos_cpp)",
     out: [
         "external/perfetto/src/perfetto_cmd/perfetto_cmd_state.gen.h",
     ],
@@ -9533,8 +10582,8 @@
 }
 
 // GN: //src/protozero:testing_messages_cpp
-genrule {
-    name: "perfetto_src_protozero_testing_messages_cpp_gen",
+filegroup {
+    name: "perfetto_src_protozero_testing_messages_cpp",
     srcs: [
         "src/protozero/test/example_proto/extensions.proto",
         "src/protozero/test/example_proto/library.proto",
@@ -9542,11 +10591,19 @@
         "src/protozero/test/example_proto/test_messages.proto",
         "src/protozero/test/example_proto/upper_import.proto",
     ],
+}
+
+// GN: //src/protozero:testing_messages_cpp
+genrule {
+    name: "perfetto_src_protozero_testing_messages_cpp_gen",
+    srcs: [
+        ":perfetto_src_protozero_testing_messages_cpp",
+    ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_src_protozero_testing_messages_cpp)",
     out: [
         "external/perfetto/src/protozero/test/example_proto/extensions.gen.cc",
         "external/perfetto/src/protozero/test/example_proto/library.gen.cc",
@@ -9560,17 +10617,13 @@
 genrule {
     name: "perfetto_src_protozero_testing_messages_cpp_gen_headers",
     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/test_messages.proto",
-        "src/protozero/test/example_proto/upper_import.proto",
+        ":perfetto_src_protozero_testing_messages_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_src_protozero_testing_messages_cpp)",
     out: [
         "external/perfetto/src/protozero/test/example_proto/extensions.gen.h",
         "external/perfetto/src/protozero/test/example_proto/library.gen.h",
@@ -9585,8 +10638,8 @@
 }
 
 // GN: //src/protozero:testing_messages_lite
-genrule {
-    name: "perfetto_src_protozero_testing_messages_lite_gen",
+filegroup {
+    name: "perfetto_src_protozero_testing_messages_lite",
     srcs: [
         "src/protozero/test/example_proto/extensions.proto",
         "src/protozero/test/example_proto/library.proto",
@@ -9594,10 +10647,18 @@
         "src/protozero/test/example_proto/test_messages.proto",
         "src/protozero/test/example_proto/upper_import.proto",
     ],
+}
+
+// GN: //src/protozero:testing_messages_lite
+genrule {
+    name: "perfetto_src_protozero_testing_messages_lite_gen",
+    srcs: [
+        ":perfetto_src_protozero_testing_messages_lite",
+    ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_src_protozero_testing_messages_lite)",
     out: [
         "external/perfetto/src/protozero/test/example_proto/extensions.pb.cc",
         "external/perfetto/src/protozero/test/example_proto/library.pb.cc",
@@ -9611,16 +10672,12 @@
 genrule {
     name: "perfetto_src_protozero_testing_messages_lite_gen_headers",
     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/test_messages.proto",
-        "src/protozero/test/example_proto/upper_import.proto",
+        ":perfetto_src_protozero_testing_messages_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_src_protozero_testing_messages_lite)",
     out: [
         "external/perfetto/src/protozero/test/example_proto/extensions.pb.h",
         "external/perfetto/src/protozero/test/example_proto/library.pb.h",
@@ -9635,8 +10692,8 @@
 }
 
 // GN: //src/protozero:testing_messages_zero
-genrule {
-    name: "perfetto_src_protozero_testing_messages_zero_gen",
+filegroup {
+    name: "perfetto_src_protozero_testing_messages_zero",
     srcs: [
         "src/protozero/test/example_proto/extensions.proto",
         "src/protozero/test/example_proto/library.proto",
@@ -9644,11 +10701,19 @@
         "src/protozero/test/example_proto/test_messages.proto",
         "src/protozero/test/example_proto/upper_import.proto",
     ],
+}
+
+// GN: //src/protozero:testing_messages_zero
+genrule {
+    name: "perfetto_src_protozero_testing_messages_zero_gen",
+    srcs: [
+        ":perfetto_src_protozero_testing_messages_zero",
+    ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_src_protozero_testing_messages_zero)",
     out: [
         "external/perfetto/src/protozero/test/example_proto/extensions.pbzero.cc",
         "external/perfetto/src/protozero/test/example_proto/library.pbzero.cc",
@@ -9662,17 +10727,13 @@
 genrule {
     name: "perfetto_src_protozero_testing_messages_zero_gen_headers",
     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/test_messages.proto",
-        "src/protozero/test/example_proto/upper_import.proto",
+        ":perfetto_src_protozero_testing_messages_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_src_protozero_testing_messages_zero)",
     out: [
         "external/perfetto/src/protozero/test/example_proto/extensions.pbzero.h",
         "external/perfetto/src/protozero/test/example_proto/library.pbzero.h",
@@ -9817,6 +10878,7 @@
 filegroup {
     name: "perfetto_src_trace_processor_db_storage_storage",
     srcs: [
+        "src/trace_processor/db/storage/dummy_storage.cc",
         "src/trace_processor/db/storage/id_storage.cc",
         "src/trace_processor/db/storage/numeric_storage.cc",
         "src/trace_processor/db/storage/storage.cc",
@@ -10430,6 +11492,7 @@
 genrule {
     name: "perfetto_src_trace_processor_metrics_sql_gen_amalgamated_sql_metrics",
     srcs: [
+        "src/trace_processor/metrics/sql/android/ad_services_metric.sql",
         "src/trace_processor/metrics/sql/android/android_anr.sql",
         "src/trace_processor/metrics/sql/android/android_batt.sql",
         "src/trace_processor/metrics/sql/android/android_binder.sql",
@@ -10802,7 +11865,9 @@
         "src/trace_processor/perfetto_sql/stdlib/chrome/chrome_scrolls.sql",
         "src/trace_processor/perfetto_sql/stdlib/chrome/cpu_powerups.sql",
         "src/trace_processor/perfetto_sql/stdlib/chrome/histograms.sql",
+        "src/trace_processor/perfetto_sql/stdlib/chrome/interactions.sql",
         "src/trace_processor/perfetto_sql/stdlib/chrome/metadata.sql",
+        "src/trace_processor/perfetto_sql/stdlib/chrome/page_loads.sql",
         "src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_intervals.sql",
         "src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_v3.sql",
         "src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_v3_cause.sql",
@@ -10817,6 +11882,7 @@
         "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/thread_states.sql",
         "src/trace_processor/perfetto_sql/stdlib/common/timestamps.sql",
         "src/trace_processor/perfetto_sql/stdlib/experimental/android_broadcast.sql",
         "src/trace_processor/perfetto_sql/stdlib/experimental/flat_slices.sql",
@@ -11472,16 +12538,24 @@
 }
 
 // GN: //src/traced/probes/ftrace:test_messages_cpp
+filegroup {
+    name: "perfetto_src_traced_probes_ftrace_test_messages_cpp",
+    srcs: [
+        "src/traced/probes/ftrace/test/test_messages.proto",
+    ],
+}
+
+// GN: //src/traced/probes/ftrace:test_messages_cpp
 genrule {
     name: "perfetto_src_traced_probes_ftrace_test_messages_cpp_gen",
     srcs: [
-        "src/traced/probes/ftrace/test/test_messages.proto",
+        ":perfetto_src_traced_probes_ftrace_test_messages_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_src_traced_probes_ftrace_test_messages_cpp)",
     out: [
         "external/perfetto/src/traced/probes/ftrace/test/test_messages.gen.cc",
     ],
@@ -11491,13 +12565,13 @@
 genrule {
     name: "perfetto_src_traced_probes_ftrace_test_messages_cpp_gen_headers",
     srcs: [
-        "src/traced/probes/ftrace/test/test_messages.proto",
+        ":perfetto_src_traced_probes_ftrace_test_messages_cpp",
     ],
     tools: [
         "aprotoc",
         "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_src_traced_probes_ftrace_test_messages_cpp)",
     out: [
         "external/perfetto/src/traced/probes/ftrace/test/test_messages.gen.h",
     ],
@@ -11508,15 +12582,23 @@
 }
 
 // GN: //src/traced/probes/ftrace:test_messages_lite
+filegroup {
+    name: "perfetto_src_traced_probes_ftrace_test_messages_lite",
+    srcs: [
+        "src/traced/probes/ftrace/test/test_messages.proto",
+    ],
+}
+
+// GN: //src/traced/probes/ftrace:test_messages_lite
 genrule {
     name: "perfetto_src_traced_probes_ftrace_test_messages_lite_gen",
     srcs: [
-        "src/traced/probes/ftrace/test/test_messages.proto",
+        ":perfetto_src_traced_probes_ftrace_test_messages_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_src_traced_probes_ftrace_test_messages_lite)",
     out: [
         "external/perfetto/src/traced/probes/ftrace/test/test_messages.pb.cc",
     ],
@@ -11526,12 +12608,12 @@
 genrule {
     name: "perfetto_src_traced_probes_ftrace_test_messages_lite_gen_headers",
     srcs: [
-        "src/traced/probes/ftrace/test/test_messages.proto",
+        ":perfetto_src_traced_probes_ftrace_test_messages_lite",
     ],
     tools: [
         "aprotoc",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_src_traced_probes_ftrace_test_messages_lite)",
     out: [
         "external/perfetto/src/traced/probes/ftrace/test/test_messages.pb.h",
     ],
@@ -11542,16 +12624,24 @@
 }
 
 // GN: //src/traced/probes/ftrace:test_messages_zero
+filegroup {
+    name: "perfetto_src_traced_probes_ftrace_test_messages_zero",
+    srcs: [
+        "src/traced/probes/ftrace/test/test_messages.proto",
+    ],
+}
+
+// GN: //src/traced/probes/ftrace:test_messages_zero
 genrule {
     name: "perfetto_src_traced_probes_ftrace_test_messages_zero_gen",
     srcs: [
-        "src/traced/probes/ftrace/test/test_messages.proto",
+        ":perfetto_src_traced_probes_ftrace_test_messages_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_src_traced_probes_ftrace_test_messages_zero)",
     out: [
         "external/perfetto/src/traced/probes/ftrace/test/test_messages.pbzero.cc",
     ],
@@ -11561,13 +12651,13 @@
 genrule {
     name: "perfetto_src_traced_probes_ftrace_test_messages_zero_gen_headers",
     srcs: [
-        "src/traced/probes/ftrace/test/test_messages.proto",
+        ":perfetto_src_traced_probes_ftrace_test_messages_zero",
     ],
     tools: [
         "aprotoc",
         "protozero_plugin",
     ],
-    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_src_traced_probes_ftrace_test_messages_zero)",
     out: [
         "external/perfetto/src/traced/probes/ftrace/test/test_messages.pbzero.h",
     ],
@@ -11755,6 +12845,32 @@
     name: "perfetto_src_traced_probes_unittests",
 }
 
+// GN: //src/traced_relay:integrationtests
+filegroup {
+    name: "perfetto_src_traced_relay_integrationtests",
+    srcs: [
+        "src/traced_relay/relay_service_integrationtest.cc",
+    ],
+}
+
+// GN: //src/traced_relay:lib
+filegroup {
+    name: "perfetto_src_traced_relay_lib",
+    srcs: [
+        "src/traced_relay/relay_service.cc",
+        "src/traced_relay/socket_relay_handler.cc",
+    ],
+}
+
+// GN: //src/traced_relay:unittests
+filegroup {
+    name: "perfetto_src_traced_relay_unittests",
+    srcs: [
+        "src/traced_relay/relay_service_unittest.cc",
+        "src/traced_relay/socket_relay_handler_unittest.cc",
+    ],
+}
+
 // GN: //src/traced/service:service
 filegroup {
     name: "perfetto_src_traced_service_service",
@@ -12091,6 +13207,9 @@
         "protos/perfetto/trace/chrome/chrome_metadata.proto",
         "protos/perfetto/trace/chrome/chrome_trace_event.proto",
         "protos/perfetto/trace/clock_snapshot.proto",
+        "protos/perfetto/trace/etw/etw.proto",
+        "protos/perfetto/trace/etw/etw_event.proto",
+        "protos/perfetto/trace/etw/etw_event_bundle.proto",
         "protos/perfetto/trace/extension_descriptor.proto",
         "protos/perfetto/trace/filesystem/inode_file_map.proto",
         "protos/perfetto/trace/ftrace/android_fs.proto",
@@ -12246,6 +13365,7 @@
         ":perfetto_protos_perfetto_config_track_event_lite_gen",
         ":perfetto_protos_perfetto_trace_android_lite_gen",
         ":perfetto_protos_perfetto_trace_chrome_lite_gen",
+        ":perfetto_protos_perfetto_trace_etw_lite_gen",
         ":perfetto_protos_perfetto_trace_filesystem_lite_gen",
         ":perfetto_protos_perfetto_trace_ftrace_lite_gen",
         ":perfetto_protos_perfetto_trace_gpu_lite_gen",
@@ -12283,6 +13403,7 @@
         "perfetto_protos_perfetto_config_track_event_lite_gen_headers",
         "perfetto_protos_perfetto_trace_android_lite_gen_headers",
         "perfetto_protos_perfetto_trace_chrome_lite_gen_headers",
+        "perfetto_protos_perfetto_trace_etw_lite_gen_headers",
         "perfetto_protos_perfetto_trace_filesystem_lite_gen_headers",
         "perfetto_protos_perfetto_trace_ftrace_lite_gen_headers",
         "perfetto_protos_perfetto_trace_gpu_lite_gen_headers",
@@ -12316,6 +13437,7 @@
         "perfetto_protos_perfetto_config_track_event_lite_gen_headers",
         "perfetto_protos_perfetto_trace_android_lite_gen_headers",
         "perfetto_protos_perfetto_trace_chrome_lite_gen_headers",
+        "perfetto_protos_perfetto_trace_etw_lite_gen_headers",
         "perfetto_protos_perfetto_trace_filesystem_lite_gen_headers",
         "perfetto_protos_perfetto_trace_ftrace_lite_gen_headers",
         "perfetto_protos_perfetto_trace_gpu_lite_gen_headers",
@@ -12434,6 +13556,9 @@
         ":perfetto_protos_perfetto_trace_chrome_cpp_gen",
         ":perfetto_protos_perfetto_trace_chrome_lite_gen",
         ":perfetto_protos_perfetto_trace_chrome_zero_gen",
+        ":perfetto_protos_perfetto_trace_etw_cpp_gen",
+        ":perfetto_protos_perfetto_trace_etw_lite_gen",
+        ":perfetto_protos_perfetto_trace_etw_zero_gen",
         ":perfetto_protos_perfetto_trace_filesystem_cpp_gen",
         ":perfetto_protos_perfetto_trace_filesystem_lite_gen",
         ":perfetto_protos_perfetto_trace_filesystem_zero_gen",
@@ -12502,6 +13627,7 @@
         ":perfetto_src_ipc_client",
         ":perfetto_src_ipc_common",
         ":perfetto_src_ipc_host",
+        ":perfetto_src_ipc_perfetto_ipc",
         ":perfetto_src_ipc_test_messages_cpp_gen",
         ":perfetto_src_ipc_test_messages_ipc_gen",
         ":perfetto_src_ipc_unittests",
@@ -12689,6 +13815,8 @@
         ":perfetto_src_traced_probes_system_info_system_info",
         ":perfetto_src_traced_probes_system_info_unittests",
         ":perfetto_src_traced_probes_unittests",
+        ":perfetto_src_traced_relay_lib",
+        ":perfetto_src_traced_relay_unittests",
         ":perfetto_src_traced_service_service",
         ":perfetto_src_traced_service_unittests",
         ":perfetto_src_tracing_client_api_without_backends",
@@ -12783,6 +13911,9 @@
         "perfetto_protos_perfetto_trace_chrome_cpp_gen_headers",
         "perfetto_protos_perfetto_trace_chrome_lite_gen_headers",
         "perfetto_protos_perfetto_trace_chrome_zero_gen_headers",
+        "perfetto_protos_perfetto_trace_etw_cpp_gen_headers",
+        "perfetto_protos_perfetto_trace_etw_lite_gen_headers",
+        "perfetto_protos_perfetto_trace_etw_zero_gen_headers",
         "perfetto_protos_perfetto_trace_filesystem_cpp_gen_headers",
         "perfetto_protos_perfetto_trace_filesystem_lite_gen_headers",
         "perfetto_protos_perfetto_trace_filesystem_zero_gen_headers",
@@ -12947,6 +14078,8 @@
         ":perfetto_protos_perfetto_trace_android_zero_gen",
         ":perfetto_protos_perfetto_trace_chrome_cpp_gen",
         ":perfetto_protos_perfetto_trace_chrome_zero_gen",
+        ":perfetto_protos_perfetto_trace_etw_cpp_gen",
+        ":perfetto_protos_perfetto_trace_etw_zero_gen",
         ":perfetto_protos_perfetto_trace_filesystem_cpp_gen",
         ":perfetto_protos_perfetto_trace_filesystem_zero_gen",
         ":perfetto_protos_perfetto_trace_ftrace_cpp_gen",
@@ -13070,6 +14203,8 @@
         "perfetto_protos_perfetto_trace_android_zero_gen_headers",
         "perfetto_protos_perfetto_trace_chrome_cpp_gen_headers",
         "perfetto_protos_perfetto_trace_chrome_zero_gen_headers",
+        "perfetto_protos_perfetto_trace_etw_cpp_gen_headers",
+        "perfetto_protos_perfetto_trace_etw_zero_gen_headers",
         "perfetto_protos_perfetto_trace_filesystem_cpp_gen_headers",
         "perfetto_protos_perfetto_trace_filesystem_zero_gen_headers",
         "perfetto_protos_perfetto_trace_ftrace_cpp_gen_headers",
@@ -13139,6 +14274,8 @@
         "perfetto_protos_perfetto_trace_android_zero_gen_headers",
         "perfetto_protos_perfetto_trace_chrome_cpp_gen_headers",
         "perfetto_protos_perfetto_trace_chrome_zero_gen_headers",
+        "perfetto_protos_perfetto_trace_etw_cpp_gen_headers",
+        "perfetto_protos_perfetto_trace_etw_zero_gen_headers",
         "perfetto_protos_perfetto_trace_filesystem_cpp_gen_headers",
         "perfetto_protos_perfetto_trace_filesystem_zero_gen_headers",
         "perfetto_protos_perfetto_trace_ftrace_cpp_gen_headers",
@@ -13238,6 +14375,7 @@
         ":perfetto_protos_perfetto_config_zero_gen",
         ":perfetto_protos_perfetto_trace_android_zero_gen",
         ":perfetto_protos_perfetto_trace_chrome_zero_gen",
+        ":perfetto_protos_perfetto_trace_etw_zero_gen",
         ":perfetto_protos_perfetto_trace_filesystem_zero_gen",
         ":perfetto_protos_perfetto_trace_ftrace_zero_gen",
         ":perfetto_protos_perfetto_trace_gpu_zero_gen",
@@ -13354,6 +14492,7 @@
         "perfetto_protos_perfetto_config_zero_gen_headers",
         "perfetto_protos_perfetto_trace_android_zero_gen_headers",
         "perfetto_protos_perfetto_trace_chrome_zero_gen_headers",
+        "perfetto_protos_perfetto_trace_etw_zero_gen_headers",
         "perfetto_protos_perfetto_trace_filesystem_zero_gen_headers",
         "perfetto_protos_perfetto_trace_ftrace_zero_gen_headers",
         "perfetto_protos_perfetto_trace_gpu_zero_gen_headers",
@@ -13471,6 +14610,7 @@
         ":perfetto_protos_perfetto_config_zero_gen",
         ":perfetto_protos_perfetto_trace_android_zero_gen",
         ":perfetto_protos_perfetto_trace_chrome_zero_gen",
+        ":perfetto_protos_perfetto_trace_etw_zero_gen",
         ":perfetto_protos_perfetto_trace_filesystem_zero_gen",
         ":perfetto_protos_perfetto_trace_ftrace_zero_gen",
         ":perfetto_protos_perfetto_trace_gpu_zero_gen",
@@ -13588,6 +14728,7 @@
         "perfetto_protos_perfetto_config_zero_gen_headers",
         "perfetto_protos_perfetto_trace_android_zero_gen_headers",
         "perfetto_protos_perfetto_trace_chrome_zero_gen_headers",
+        "perfetto_protos_perfetto_trace_etw_zero_gen_headers",
         "perfetto_protos_perfetto_trace_filesystem_zero_gen_headers",
         "perfetto_protos_perfetto_trace_ftrace_zero_gen_headers",
         "perfetto_protos_perfetto_trace_gpu_zero_gen_headers",
@@ -13715,6 +14856,7 @@
         ":perfetto_protos_perfetto_ipc_wire_protocol_cpp_gen",
         ":perfetto_protos_perfetto_trace_android_zero_gen",
         ":perfetto_protos_perfetto_trace_chrome_zero_gen",
+        ":perfetto_protos_perfetto_trace_etw_zero_gen",
         ":perfetto_protos_perfetto_trace_filesystem_zero_gen",
         ":perfetto_protos_perfetto_trace_ftrace_zero_gen",
         ":perfetto_protos_perfetto_trace_gpu_zero_gen",
@@ -13813,6 +14955,7 @@
         "perfetto_protos_perfetto_ipc_wire_protocol_cpp_gen_headers",
         "perfetto_protos_perfetto_trace_android_zero_gen_headers",
         "perfetto_protos_perfetto_trace_chrome_zero_gen_headers",
+        "perfetto_protos_perfetto_trace_etw_zero_gen_headers",
         "perfetto_protos_perfetto_trace_filesystem_zero_gen_headers",
         "perfetto_protos_perfetto_trace_ftrace_zero_gen_headers",
         "perfetto_protos_perfetto_trace_gpu_zero_gen_headers",
@@ -13920,6 +15063,7 @@
         ":perfetto_protos_perfetto_ipc_wire_protocol_cpp_gen",
         ":perfetto_protos_perfetto_trace_android_zero_gen",
         ":perfetto_protos_perfetto_trace_chrome_zero_gen",
+        ":perfetto_protos_perfetto_trace_etw_zero_gen",
         ":perfetto_protos_perfetto_trace_filesystem_zero_gen",
         ":perfetto_protos_perfetto_trace_ftrace_zero_gen",
         ":perfetto_protos_perfetto_trace_gpu_zero_gen",
@@ -13993,6 +15137,7 @@
         "perfetto_protos_perfetto_ipc_wire_protocol_cpp_gen_headers",
         "perfetto_protos_perfetto_trace_android_zero_gen_headers",
         "perfetto_protos_perfetto_trace_chrome_zero_gen_headers",
+        "perfetto_protos_perfetto_trace_etw_zero_gen_headers",
         "perfetto_protos_perfetto_trace_filesystem_zero_gen_headers",
         "perfetto_protos_perfetto_trace_ftrace_zero_gen_headers",
         "perfetto_protos_perfetto_trace_gpu_zero_gen_headers",
diff --git a/BUILD b/BUILD
index 42122bd..25902af 100644
--- a/BUILD
+++ b/BUILD
@@ -315,6 +315,7 @@
                ":protos_perfetto_config_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",
@@ -402,6 +403,7 @@
         ":protos_perfetto_config_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",
@@ -516,6 +518,7 @@
         ":protos_perfetto_ipc_ipc",
         ":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",
@@ -1303,6 +1306,8 @@
 perfetto_filegroup(
     name = "src_trace_processor_db_storage_storage",
     srcs = [
+        "src/trace_processor/db/storage/dummy_storage.cc",
+        "src/trace_processor/db/storage/dummy_storage.h",
         "src/trace_processor/db/storage/id_storage.cc",
         "src/trace_processor/db/storage/id_storage.h",
         "src/trace_processor/db/storage/numeric_storage.cc",
@@ -1787,6 +1792,7 @@
 perfetto_filegroup(
     name = "src_trace_processor_metrics_sql_android_android",
     srcs = [
+        "src/trace_processor/metrics/sql/android/ad_services_metric.sql",
         "src/trace_processor/metrics/sql/android/android_anr.sql",
         "src/trace_processor/metrics/sql/android/android_batt.sql",
         "src/trace_processor/metrics/sql/android/android_binder.sql",
@@ -2238,7 +2244,9 @@
         "src/trace_processor/perfetto_sql/stdlib/chrome/chrome_scrolls.sql",
         "src/trace_processor/perfetto_sql/stdlib/chrome/cpu_powerups.sql",
         "src/trace_processor/perfetto_sql/stdlib/chrome/histograms.sql",
+        "src/trace_processor/perfetto_sql/stdlib/chrome/interactions.sql",
         "src/trace_processor/perfetto_sql/stdlib/chrome/metadata.sql",
+        "src/trace_processor/perfetto_sql/stdlib/chrome/page_loads.sql",
         "src/trace_processor/perfetto_sql/stdlib/chrome/speedometer.sql",
         "src/trace_processor/perfetto_sql/stdlib/chrome/tasks.sql",
         "src/trace_processor/perfetto_sql/stdlib/chrome/vsync_intervals.sql",
@@ -2255,6 +2263,7 @@
         "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/thread_states.sql",
         "src/trace_processor/perfetto_sql/stdlib/common/timestamps.sql",
     ],
 )
@@ -3201,6 +3210,7 @@
         ":protos_perfetto_config_track_event_protos",
         ":protos_perfetto_trace_android_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",
@@ -4030,6 +4040,7 @@
 perfetto_proto_library(
     name = "protos_perfetto_metrics_android_protos",
     srcs = [
+        "protos/perfetto/metrics/android/ad_services_metric.proto",
         "protos/perfetto/metrics/android/android_blocking_call.proto",
         "protos/perfetto/metrics/android/android_blocking_calls_cuj_metric.proto",
         "protos/perfetto/metrics/android/android_boot.proto",
@@ -4283,6 +4294,27 @@
     ],
 )
 
+# GN target: //protos/perfetto/trace/etw:source_set
+perfetto_proto_library(
+    name = "protos_perfetto_trace_etw_protos",
+    srcs = [
+        "protos/perfetto/trace/etw/etw.proto",
+        "protos/perfetto/trace/etw/etw_event.proto",
+        "protos/perfetto/trace/etw/etw_event_bundle.proto",
+    ],
+    visibility = [
+        PERFETTO_CONFIG.proto_library_visibility,
+    ],
+)
+
+# GN target: //protos/perfetto/trace/etw:zero
+perfetto_cc_protozero_library(
+    name = "protos_perfetto_trace_etw_zero",
+    deps = [
+        ":protos_perfetto_trace_etw_protos",
+    ],
+)
+
 # GN target: //protos/perfetto/trace/filesystem:source_set
 perfetto_proto_library(
     name = "protos_perfetto_trace_filesystem_protos",
@@ -4529,6 +4561,7 @@
         ":protos_perfetto_config_track_event_protos",
         ":protos_perfetto_trace_android_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",
@@ -4569,6 +4602,7 @@
         ":protos_perfetto_config_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",
@@ -5046,6 +5080,7 @@
         ":protos_perfetto_ipc_ipc",
         ":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",
@@ -5137,6 +5172,7 @@
         ":protos_perfetto_ipc_ipc",
         ":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",
@@ -5266,6 +5302,7 @@
                ":protos_perfetto_config_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",
@@ -5418,6 +5455,7 @@
                ":protos_perfetto_config_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",
@@ -5505,6 +5543,7 @@
         ":protos_perfetto_config_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",
@@ -5639,6 +5678,7 @@
                ":protos_perfetto_config_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",
diff --git a/CHANGELOG b/CHANGELOG
index bec63e0..777d115 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,10 +1,10 @@
 Unreleased:
   Tracing service and probes:
-    *
+    * Added reporting of TZ offset under system_info.timezone_off_mins .
   Trace Processor:
     *
   UI:
-    *
+    * Add a new type of debug tracks: counter.
   SDK:
     *
 
diff --git a/docs/analysis/trace-processor.md b/docs/analysis/trace-processor.md
index 3fb44b5..a459566 100644
--- a/docs/analysis/trace-processor.md
+++ b/docs/analysis/trace-processor.md
@@ -600,53 +600,46 @@
 every change to trace processor code or builtin metrics.
 
 #### Choosing where to add diff tests
-Choosing a folder with a diff tests often can be confusing
-as a test can fall into more than one category. This section is a guide
-to decide which folder to choose.
+`diff_tests/` folder contains four directories corresponding to different
+areas of trace processor.
+1. __stdlib__: Tests focusing on testing Perfetto Standard Library, both
+   prelude and the regular modules. The subdirectories in this folder
+   should generally correspond to directories in `perfetto_sql/stdlib`.
+2. __parser__: Tests focusing on ensuring that different trace files are
+   parsed correctly and the corresponding built-in tables are populated.
+3. __metrics__: Tests focusing on testing metrics located in
+   `trace_processor/metrics/sql`. This organisation is mostly historical
+   and code (and corresponding tests) is expected to move to `stdlib` over time.
+4. __syntax__: Tests focusing on testing the core syntax of PerfettoSQL
+   (i.e. `CREATE PERFETTO TABLE` or `CREATE PERFETTO FUNCTION`).
 
-Broadly, there are two categories which all folders fall into:
-1. __"Area" folders__ which encompass a "vertical" area of interest
-   e.g. startup/ contains Android app startup related tests or chrome/
-   contains all Chrome related tests.
-2. __"Feature" folders__ which encompass a particular feature of
-   trace processor e.g. process_tracking/ tests the lifetime tracking of
-   processes, span_join/ tests the span join operator.
+__Scenario__: A new stdlib module `foo/bar.sql` is being added.
 
-"Area" folders should be preferred for adding tests unless the test is
-applicable to more than one "area"; in this case, one of "feature" folders
-can be used instead.
-
-Here are some common scenarios in which new tests may be added and
-answers on where to add the test:
+_Answer_: Add the test to the `stdlib/foo/bar_tests.py` file.
 
 __Scenario__: A new event is being parsed, the focus of the test is to ensure
-the event is being parsed correctly and the event is focused on a single
-vertical "Area".
+the event is being parsed correctly.
 
-_Answer_: Add the test in one of the "Area" folders.
-
-__Scenario__: A new event is being parsed and the focus of the test is to ensure
-the event is being parsed correctly and the event is applicable to more than one
-vertical "Area".
-
-_Answer_: Add the test to the parsing/ folder.
+_Answer_: Add the test in one of the `parser` subdirectories. Prefer adding a
+test to an existing related directory (i.e. `sched`, `power`) if one exists.
 
 __Scenario__: A new metric is being added and the focus of the test is to
 ensure the metric is being correctly computed.
 
-_Answer_: Add the test in one of the "Area" folders.
+_Answer_: Add the test in one of the `metrics` subdirectories. Prefer adding a
+test to an existing related directory if one exists. Also consider adding the
+code in question to stdlib.
 
 __Scenario__: A new dynamic table is being added and the focus of the test is to
 ensure the dynamic table is being correctly computed...
 
-_Answer_: Add the test to the dynamic/ folder
+_Answer_: Add the test to the `stdlib/dynamic_tables` folder
 
 __Scenario__: The interals of trace processor are being modified and the test
 is to ensure the trace processor is correctly filtering/sorting important
 built-in tables.
 
-_Answer_: Add the test to the tables/ folder.
-
+_Answer_: Add the test to the `parser/core_tables` folder.
 
 ## Appendix: table inheritance
 
diff --git a/docs/contributing/ui-plugins.md b/docs/contributing/ui-plugins.md
index 241e0a7..23fbe25 100644
--- a/docs/contributing/ui-plugins.md
+++ b/docs/contributing/ui-plugins.md
@@ -176,58 +176,73 @@
 }
 ```
 
-This interface will be used as type parameter to the `Plugin` and
-`TracePluginContext` interfaces.
+To access permalink state, call `mountStore()` on your `TracePluginContext`
+object, passing in a migration function.
 ```typescript
-class MyPlugin implements Plugin<MyState> {
-
-  migrate(initialState: unknown): MyState {
-    // ...
+class MyPlugin implements Plugin {
+  async onTraceLoad(ctx: TracePluginContext): Promise<void> {
+    const store = ctx.mountStore(migrate);
   }
+}
 
-  async onTraceLoad(ctx: TracePluginContext<MyState>): Promise<void> {
-    // You can access the store on ctx.store
-  }
-
-  async onTraceUnload(ctx: TracePluginContext<MyState>): Promise<void> {
-    // You can access the store on ctx.store
-  }
-
+function migrate(initialState: unknown): MyState {
   // ...
 }
 ```
 
-`migrate()` is called after `onActivate()` just before `onTraceLoad()`. There
-are two cases to consider:
+When it comes to migration, there are two cases to consider:
 - Loading a new trace
 - Loading from a permalink
 
-In case of a new trace `migrate()` is called with `undefined`. In this
-case you should return a default version of `MyState`:
+In case of a new trace, your migration function is called with `undefined`. In
+this case you should return a default version of `MyState`:
 ```typescript
-class MyPlugin implements Plugin<MyState> {
+const DEFAULT = {favouriteSlices: []};
 
-  migrate(initialState: unknown): MyState {
-    if (initialState === undefined) {
-      return {
-        favouriteSlices: [];
-      };
-    }
-    // ...
+function migrate(initialState: unknown): MyState {
+  if (initialState === undefined) {
+    // Return default version of MyState.
+    return DEFAULT;
+  } else {
+    // Migrate old version here.
   }
-
-  // ...
 }
 ```
 
-In the permalink case `migrate()` is called with the state of the plugin
-store at the time the permalink was generated. This may be from a
-older or newer version of the plugin.
-**Plugin's must not make assumptions about the contents of `initialState`**.
+In the permalink case, your migration function is called with the state of the
+plugin store at the time the permalink was generated. This may be from an older
+or newer version of the plugin.
 
-In this case you need to carefully validate the state object.
+**Plugins must not make assumptions about the contents of `initialState`!**
 
-TODO: Add validation example.
+In this case you need to carefully validate the state object. This could be
+achieved in several ways, none of which are particularly straight forward. State
+migration is difficult!
+
+One brute force way would be to use a version number.
+
+```typescript
+interface MyState {
+  version: number;
+  favouriteSlices: MySliceInfo[];
+}
+
+const VERSION = 3;
+const DEFAULT = {favouriteSlices: []};
+
+function migrate(initialState: unknown): MyState {
+  if (initialState && (initialState as {version: any}).version === VERSION) {
+    // Version number checks out, assume the structure is correct.
+    return initialState as State;
+  } else {
+    // Null, undefined, or bad version number - return default value.
+    return DEFAULT;
+  }
+}
+```
+
+You'll need to remember to update your version number when making changes!
+Migration should be unit-tested to ensure compatibility.
 
 Examples:
 - [dev.perfetto.ExampleState](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/ui/src/plugins/dev.perfetto.ExampleState/index.ts).
diff --git a/docs/toc.md b/docs/toc.md
index d7c34e3..2ac2751 100644
--- a/docs/toc.md
+++ b/docs/toc.md
@@ -72,6 +72,7 @@
   * [Trace Packet proto](reference/trace-packet-proto.autogen)
   * [perfetto cmdline](reference/perfetto-cli.md)
   * [heap_profile cmdline](reference/heap_profile-cli.md)
+  * [UI Plugin API](reference/ui-plugin-api.autogen)
   * [Synthetic TrackEvent](reference/synthetic-track-event.md)
   * [Android Version Notes](reference/android-version-notes.md)
   * [Stats table](analysis/sql-stats.autogen)
diff --git a/gn/BUILD.gn b/gn/BUILD.gn
index bb01ea1..6c1775a 100644
--- a/gn/BUILD.gn
+++ b/gn/BUILD.gn
@@ -14,6 +14,7 @@
 
 import("perfetto.gni")
 import("perfetto_python.gni")
+import("pkg_config.gni")
 import("proto_library.gni")
 
 if (perfetto_root_path == "//") {
@@ -227,8 +228,19 @@
   libs = [ "protoc" ]  # This will link against libprotoc.so
 }
 
+# pkg_deps selects the appropriate pkg-config based on current_toolchain and
+# this is a no-op if |perfetto_use_pkgconfig| is false.
+pkg_config("pkgconfig_protobuf") {
+  pkg_deps = [ "protobuf" ]
+}
+
 config("system_protobuf") {
-  libs = [ "protobuf" ]  # This will link against libprotobuf.so
+  if (perfetto_use_pkgconfig) {
+    configs = [ ":pkgconfig_protobuf" ]
+  } else {
+    # Fallback if pkg-config isn't enabled.
+    libs = [ "protobuf" ]  # This will link against libprotobuf.so
+  }
 }
 
 # protoc compiler library, it's used for building protoc plugins.
diff --git a/gn/perfetto.gni b/gn/perfetto.gni
index ecc7252..d8f8c55 100644
--- a/gn/perfetto.gni
+++ b/gn/perfetto.gni
@@ -338,6 +338,13 @@
   # Skip buildtools dependency checks (needed for ChromeOS).
   skip_buildtools_check = false
 
+  # Used by CrOS builds. Uses pkg-config to determine the appropriate flags
+  # for including and linking system libraries.
+  #   set `pkg_config` to the `BUILD_PKG_CONFIG` and
+  #   set `target_pkg_config` to the target `PKG_CONFIG`.
+  # Note: that if this is enabled `perfetto_use_system_protobuf` should be also.
+  perfetto_use_pkgconfig = false
+
   # Used by CrOS system builds. Uses the system version of protobuf
   # from /usr/include instead of the hermetic one.
   perfetto_use_system_protobuf = false
@@ -393,3 +400,9 @@
 # must be set as well. Doesn't make sense to build traced_probes without the
 # rest. traced_probes integration tests depend on traced.
 assert(!enable_perfetto_traced_probes || enable_perfetto_platform_services)
+
+# |perfetto_use_pkgconfig| changes the behavior of
+# |perfetto_use_system_protobuf|, so if perfetto_use_pkgconfig is set,
+# |perfetto_use_system_protobuf| must be set.
+assert(!perfetto_use_pkgconfig || perfetto_use_system_protobuf,
+       "perfetto_use_pkgconfig requires perfetto_use_system_protobuf")
diff --git a/gn/perfetto_integrationtests.gni b/gn/perfetto_integrationtests.gni
index ff7bb7f..3e65e20 100644
--- a/gn/perfetto_integrationtests.gni
+++ b/gn/perfetto_integrationtests.gni
@@ -49,3 +49,7 @@
   perfetto_integrationtests_targets +=
       [ "src/trace_processor:integrationtests" ]
 }
+
+if (enable_perfetto_traced_relay) {
+  perfetto_integrationtests_targets += [ "src/traced_relay:integrationtests" ]
+}
diff --git a/gn/perfetto_unittests.gni b/gn/perfetto_unittests.gni
index cc51910..91e8d5d 100644
--- a/gn/perfetto_unittests.gni
+++ b/gn/perfetto_unittests.gni
@@ -80,3 +80,7 @@
     perfetto_unittests_targets += [ "src/bigtrace:unittests" ]
   }
 }
+
+if (enable_perfetto_traced_relay) {
+  perfetto_unittests_targets += [ "src/traced_relay:unittests" ]
+}
diff --git a/gn/pkg-config_wrapper.py b/gn/pkg-config_wrapper.py
new file mode 100755
index 0000000..3fc33cd
--- /dev/null
+++ b/gn/pkg-config_wrapper.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 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.
+
+"""Wrapper of pkg-config command line to format output for gn.
+
+Parses the pkg-config output and format it into json,
+so that it can be used in GN files easily.
+
+Usage:
+  pkg-config_wrapper.py pkg-config pkg1 pkg2 ...
+
+Specifically, this script does not expect any additional flags.
+"""
+
+import json
+import shlex
+import subprocess
+import sys
+
+
+def get_shell_output(cmd):
+    """Run |cmd| and return output as a list."""
+    result = subprocess.run(
+        cmd, encoding="utf-8", stdout=subprocess.PIPE, check=False
+    )
+    if result.returncode:
+        sys.exit(result.returncode)
+    return shlex.split(result.stdout)
+
+
+def main(argv):
+    if len(argv) < 2:
+        sys.exit(f"Usage: {sys.argv[0]} <pkg-config> <modules>")
+
+    cflags = get_shell_output(argv + ["--cflags"])
+    libs = []
+    lib_dirs = []
+    ldflags = []
+    for ldflag in get_shell_output(argv + ["--libs"]):
+        if ldflag.startswith("-l"):
+            # Strip -l.
+            libs.append(ldflag[2:])
+        elif ldflag.startswith("-L"):
+            # Strip -L.
+            lib_dirs.append(ldflag[2:])
+        else:
+            ldflags.append(ldflag)
+
+    # Set sort_keys=True for stabilization.
+    result = {
+        "cflags": cflags,
+        "libs": libs,
+        "lib_dirs": lib_dirs,
+        "ldflags": ldflags,
+    }
+    json.dump(result, sys.stdout, sort_keys=True)
+
+
+if __name__ == "__main__":
+    sys.exit(main(sys.argv[1:]))
diff --git a/gn/pkg_config.gni b/gn/pkg_config.gni
new file mode 100644
index 0000000..b77d3de
--- /dev/null
+++ b/gn/pkg_config.gni
@@ -0,0 +1,67 @@
+# 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("perfetto.gni")
+
+declare_args() {
+  pkg_config = "pkg-config"
+  target_pkg_config = "pkg-config"
+}
+
+# Defines a config specifying the result of running pkg-config for the given
+# packages. Put the package names you want to query in the "pkg_deps" variable
+# inside the template invocation.
+template("pkg_config") {
+  config(target_name) {
+    assert(defined(invoker.pkg_deps), "pkg_deps must be set")
+    if (perfetto_use_pkgconfig) {
+      forward_variables_from(invoker, "*")
+
+      if (current_toolchain == host_toolchain) {
+        _cmd = pkg_config
+      } else {
+        _cmd = target_pkg_config
+      }
+      _pkg_config_result =
+          exec_script("//gn/pkg-config_wrapper.py", [ _cmd ] + pkg_deps, "json")
+
+      if (_pkg_config_result.cflags != []) {
+        if (!defined(cflags)) {
+          cflags = []
+        }
+        cflags += _pkg_config_result.cflags
+      }
+      if (_pkg_config_result.libs != []) {
+        if (!defined(libs)) {
+          libs = []
+        }
+        libs += _pkg_config_result.libs
+      }
+      if (_pkg_config_result.lib_dirs != []) {
+        if (!defined(lib_dirs)) {
+          lib_dirs = []
+        }
+        lib_dirs += _pkg_config_result.lib_dirs
+      }
+      if (_pkg_config_result.ldflags != []) {
+        if (!defined(ldflags)) {
+          ldflags = []
+        }
+        ldflags += _pkg_config_result.ldflags
+      }
+    } else {  # !perfetto_use_pkgconfig
+      not_needed(invoker, "*")
+    }
+  }
+}
diff --git a/include/perfetto/base/time.h b/include/perfetto/base/time.h
index f8161ab..ea15dae 100644
--- a/include/perfetto/base/time.h
+++ b/include/perfetto/base/time.h
@@ -20,6 +20,7 @@
 #include <time.h>
 
 #include <chrono>
+#include <optional>
 #include <string>
 
 #include "perfetto/base/build_config.h"
@@ -245,6 +246,8 @@
   return TimeGm(&tms);
 }
 
+std::optional<int32_t> GetTimezoneOffsetMins();
+
 }  // namespace base
 }  // namespace perfetto
 
diff --git a/include/perfetto/ext/base/string_utils.h b/include/perfetto/ext/base/string_utils.h
index 20e5799..c8264a2 100644
--- a/include/perfetto/ext/base/string_utils.h
+++ b/include/perfetto/ext/base/string_utils.h
@@ -130,6 +130,11 @@
                        const std::string& to_replace,
                        const std::string& replacement);
 
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+bool WideToUTF8(const std::wstring& source, std::string& output);
+bool UTF8ToWide(const std::string& source, std::wstring& output);
+#endif // PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+
 // A BSD-style strlcpy without the return value.
 // Copies at most |dst_size|-1 characters. Unlike strncpy, it always \0
 // terminates |dst|, as long as |dst_size| is not 0.
diff --git a/include/perfetto/ext/base/thread_utils.h b/include/perfetto/ext/base/thread_utils.h
index 4a1f0b6..9281fa0 100644
--- a/include/perfetto/ext/base/thread_utils.h
+++ b/include/perfetto/ext/base/thread_utils.h
@@ -21,6 +21,7 @@
 
 #include "perfetto/base/build_config.h"
 #include "perfetto/ext/base/string_utils.h"
+#include "perfetto/base/export.h"
 
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) ||   \
     PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
@@ -69,6 +70,11 @@
   return true;
 }
 
+#elif PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+
+PERFETTO_EXPORT_COMPONENT bool MaybeSetThreadName(const std::string& name);
+PERFETTO_EXPORT_COMPONENT bool GetThreadName(std::string& out_result);
+
 #else
 inline bool MaybeSetThreadName(const std::string&) {
   return false;
diff --git a/include/perfetto/ext/tracing/core/shared_memory_abi.h b/include/perfetto/ext/tracing/core/shared_memory_abi.h
index 451cafb..4e05599 100644
--- a/include/perfetto/ext/tracing/core/shared_memory_abi.h
+++ b/include/perfetto/ext/tracing/core/shared_memory_abi.h
@@ -424,7 +424,7 @@
       ChunkHeader* chunk_header = header();
       auto packets = chunk_header->packets.load(std::memory_order_relaxed);
       if (packets.count < packet_count)
-        packets.count = packet_count;
+        packets.count = packet_count & ChunkHeader::Packets::kMaxCount;
       chunk_header->packets.store(packets, std::memory_order_release);
       return packets.count;
     }
diff --git a/include/perfetto/protozero/field.h b/include/perfetto/protozero/field.h
index d0163b1..e601e06 100644
--- a/include/perfetto/protozero/field.h
+++ b/include/perfetto/protozero/field.h
@@ -156,7 +156,7 @@
                   uint8_t type,
                   uint64_t int_value,
                   uint32_t size) {
-    id_ = id;
+    id_ = id & kMaxId;
     type_ = type;
     int_value_ = int_value;
     size_ = size;
diff --git a/include/perfetto/protozero/scattered_stream_writer.h b/include/perfetto/protozero/scattered_stream_writer.h
index 96329e9..719c490 100644
--- a/include/perfetto/protozero/scattered_stream_writer.h
+++ b/include/perfetto/protozero/scattered_stream_writer.h
@@ -22,6 +22,8 @@
 #include <stdint.h>
 #include <string.h>
 
+#include <algorithm>
+
 #include "perfetto/base/compiler.h"
 #include "perfetto/base/export.h"
 #include "perfetto/base/logging.h"
@@ -76,13 +78,13 @@
 
   // Assumes that the caller checked that there is enough headroom.
   // TODO(primiano): perf optimization, this is a tracing hot path. The
-  // compiler can make strong optimization on memcpy if the size arg is a
+  // compiler can make strong optimization on std::copy if the size arg is a
   // constexpr. Make a templated variant of this for fixed-size writes.
   // TODO(primiano): restrict / noalias might also help.
   inline void WriteBytesUnsafe(const uint8_t* src, size_t size) {
     uint8_t* const end = write_ptr_ + size;
     assert(end <= cur_range_.end);
-    memcpy(write_ptr_, src, size);
+    std::copy(src, src + size, write_ptr_);
     write_ptr_ = end;
   }
 
diff --git a/infra/perfetto.dev/BUILD.gn b/infra/perfetto.dev/BUILD.gn
index 75f69b7..7b9d1f5 100644
--- a/infra/perfetto.dev/BUILD.gn
+++ b/infra/perfetto.dev/BUILD.gn
@@ -37,6 +37,7 @@
     ":gen_toc",
     ":gen_trace_config_proto",
     ":gen_trace_packet_proto",
+    ":gen_ui_plugin_api_html",
     ":node_assets",
     ":readme",
     ":style_scss",
@@ -190,6 +191,31 @@
   out_html = "docs/analysis/sql-stats"
 }
 
+ui_plugin_api_md = "${target_gen_dir}/ui-plugin-api.md"
+
+nodejs_script("gen_ui_plugin_api_md") {
+  script = "src/gen_ui_reference.js"
+  input = "../../ui/src/public/index.ts"
+  inputs = [ input ]
+  outputs = [ ui_plugin_api_md ]
+  args = [
+    "-i",
+    rebase_path(input, root_build_dir),
+    "-o",
+    rebase_path(ui_plugin_api_md, root_build_dir),
+  ]
+}
+
+md_to_html("gen_ui_plugin_api_html") {
+  markdown = ui_plugin_api_md
+  html_template = "src/template_markdown.html"
+  deps = [
+    ":gen_toc",
+    ":gen_ui_plugin_api_md",
+  ]
+  out_html = "docs/reference/ui-plugin-api"
+}
+
 # Generates a html reference for a proto
 # Args:
 # * proto: The path to a .proto file
diff --git a/infra/perfetto.dev/src/gen_ui_reference.js b/infra/perfetto.dev/src/gen_ui_reference.js
new file mode 100644
index 0000000..f6c2571
--- /dev/null
+++ b/infra/perfetto.dev/src/gen_ui_reference.js
@@ -0,0 +1,46 @@
+// 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.
+
+// Generation of UI API references
+
+'use strict';
+
+const fs = require('fs');
+const path = require('path');
+const argv = require('yargs').argv
+
+const PROJECT_ROOT =
+    path.dirname(path.dirname(path.dirname(path.dirname(__filename))));
+
+function main() {
+  const inputPath = argv['i'];
+  const outputPath = argv['o'];
+  if (!inputPath) {
+    console.error('Usage: -i ui/src/public/index.ts [-o out.md]');
+    process.exit(1);
+  }
+
+  const text = fs.readFileSync(inputPath, 'UTF8');
+
+  const generatedMd = '```\n' + text + '```\n';
+
+  if (outputPath) {
+    fs.writeFileSync(outputPath, generatedMd);
+  } else {
+    console.log(generatedMd);
+  }
+  process.exit(0);
+}
+
+main();
diff --git a/protos/perfetto/config/chrome/scenario_config.proto b/protos/perfetto/config/chrome/scenario_config.proto
index 5094f82..e90d6db 100644
--- a/protos/perfetto/config/chrome/scenario_config.proto
+++ b/protos/perfetto/config/chrome/scenario_config.proto
@@ -29,9 +29,16 @@
   // triggered.
   optional float trigger_chance = 2;
 
-  // Additional delay on the trigger below.
+  // Additional delay *after* the trigger below. This is mostly useful
+  // to trace beyond a triggered event in upload rules. Other triggers
+  // can still be serviced during this period.
   optional uint64 delay_ms = 3;
 
+  // Delay *before* which the rule is activated. Trigger events during this
+  // period are ignored by this rule. This is mostly useful to trace for a
+  // minimum duration before watching trigger events.
+  optional uint64 activation_delay_ms = 8;
+
   // Triggers when a value within the specified bounds [min_value,
   // max_value] is emitted into a Chrome histogram.
   message HistogramTrigger {
diff --git a/protos/perfetto/config/interceptor_config.proto b/protos/perfetto/config/interceptor_config.proto
index 7ecc13b..9263bb5 100644
--- a/protos/perfetto/config/interceptor_config.proto
+++ b/protos/perfetto/config/interceptor_config.proto
@@ -27,5 +27,5 @@
   // Matches the name given to RegisterInterceptor().
   optional string name = 1;
 
-  optional ConsoleConfig console_config = 100 [lazy = true];
+  optional ConsoleConfig console_config = 100;
 }
diff --git a/protos/perfetto/config/perfetto_config.proto b/protos/perfetto/config/perfetto_config.proto
index bf19bbb..ade9e6b 100644
--- a/protos/perfetto/config/perfetto_config.proto
+++ b/protos/perfetto/config/perfetto_config.proto
@@ -901,7 +901,7 @@
   // Matches the name given to RegisterInterceptor().
   optional string name = 1;
 
-  optional ConsoleConfig console_config = 100 [lazy = true];
+  optional ConsoleConfig console_config = 100;
 }
 
 // End of protos/perfetto/config/interceptor_config.proto
diff --git a/protos/perfetto/metrics/android/BUILD.gn b/protos/perfetto/metrics/android/BUILD.gn
index a2ae264..2504f4c 100644
--- a/protos/perfetto/metrics/android/BUILD.gn
+++ b/protos/perfetto/metrics/android/BUILD.gn
@@ -20,6 +20,7 @@
     "source_set",
   ]
   sources = [
+    "ad_services_metric.proto",
     "android_blocking_call.proto",
     "android_blocking_calls_cuj_metric.proto",
     "android_boot.proto",
diff --git a/protos/perfetto/metrics/android/ad_services_metric.proto b/protos/perfetto/metrics/android/ad_services_metric.proto
new file mode 100644
index 0000000..c2ddcfd
--- /dev/null
+++ b/protos/perfetto/metrics/android/ad_services_metric.proto
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+package perfetto.protos;
+
+// Next: 2
+message AdServicesUiMetric {
+  optional double latency = 1;
+}
+
+// Next: 2
+message AdServicesAdIdMetric {
+  optional double latency = 1;
+}
+
+// Next: 2
+message AdServicesAppSetIdMetric {
+  optional double latency = 1;
+}
+
+// Next: 4
+message AdServicesMetric {
+  repeated AdServicesUiMetric ui_metric = 1;
+  repeated AdServicesAdIdMetric ad_id_metric = 2;
+  repeated AdServicesAppSetIdMetric app_set_id_metric = 3;
+}
\ No newline at end of file
diff --git a/protos/perfetto/metrics/android/android_boot.proto b/protos/perfetto/metrics/android/android_boot.proto
index 820b4e0..53de221 100644
--- a/protos/perfetto/metrics/android/android_boot.proto
+++ b/protos/perfetto/metrics/android/android_boot.proto
@@ -29,4 +29,10 @@
   optional ProcessStateDurations systemui_durations = 2;
   optional ProcessStateDurations launcher_durations = 3;
   optional ProcessStateDurations gms_durations = 4;
+  // Launcher related boot metrics
+  message LauncherBreakdown {
+    //  reports cold start time of NexusLauncher
+    optional int64 cold_start_dur = 1;
+  }
+  optional LauncherBreakdown launcher_breakdown = 5;
 }
diff --git a/protos/perfetto/metrics/metrics.proto b/protos/perfetto/metrics/metrics.proto
index 95fd3b4..4aaba30 100644
--- a/protos/perfetto/metrics/metrics.proto
+++ b/protos/perfetto/metrics/metrics.proto
@@ -18,6 +18,7 @@
 
 package perfetto.protos;
 
+import "protos/perfetto/metrics/android/ad_services_metric.proto";
 import "protos/perfetto/metrics/android/android_boot.proto";
 import "protos/perfetto/metrics/android/android_frame_timeline_metric.proto";
 import "protos/perfetto/metrics/android/anr_metric.proto";
@@ -77,6 +78,7 @@
   optional string unique_session_name = 8;
   optional string trace_config_pbtxt = 9;
   optional int64 sched_duration_ns = 10;
+  optional int64 tracing_started_ns = 11;
 }
 
 // Stats counters for the trace.
@@ -109,7 +111,7 @@
 
 // Root message for all Perfetto-based metrics.
 //
-// Next id: 58
+// Next id: 59
 message TraceMetrics {
   reserved 4, 10, 13, 14, 16, 19;
 
@@ -261,11 +263,15 @@
 
   // Metrics for App Not Responding (ANR) errors.
   optional AndroidAnrMetric android_anr = 55;
+
   // Aggregated Android Monitor Contention metrics
   optional AndroidMonitorContentionAggMetric android_monitor_contention_agg = 56;
 
   optional AndroidBootMetric android_boot = 57;
 
+  // Metric for AdServices module.
+  optional AdServicesMetric ad_services_metric = 58;
+
   // Demo extensions.
   extensions 450 to 499;
 
diff --git a/protos/perfetto/metrics/perfetto_merged_metrics.proto b/protos/perfetto/metrics/perfetto_merged_metrics.proto
index e940682..c518c7f 100644
--- a/protos/perfetto/metrics/perfetto_merged_metrics.proto
+++ b/protos/perfetto/metrics/perfetto_merged_metrics.proto
@@ -13,6 +13,31 @@
 
 option go_package = "github.com/google/perfetto/perfetto_proto";
 
+// Begin of protos/perfetto/metrics/android/ad_services_metric.proto
+
+// Next: 2
+message AdServicesUiMetric {
+  optional double latency = 1;
+}
+
+// Next: 2
+message AdServicesAdIdMetric {
+  optional double latency = 1;
+}
+
+// Next: 2
+message AdServicesAppSetIdMetric {
+  optional double latency = 1;
+}
+
+// Next: 4
+message AdServicesMetric {
+  repeated AdServicesUiMetric ui_metric = 1;
+  repeated AdServicesAdIdMetric ad_id_metric = 2;
+  repeated AdServicesAppSetIdMetric app_set_id_metric = 3;
+}
+// End of protos/perfetto/metrics/android/ad_services_metric.proto
+
 // Begin of protos/perfetto/metrics/android/android_blocking_call.proto
 
 // Blocking call on the main thread.
@@ -118,6 +143,12 @@
   optional ProcessStateDurations systemui_durations = 2;
   optional ProcessStateDurations launcher_durations = 3;
   optional ProcessStateDurations gms_durations = 4;
+  // Launcher related boot metrics
+  message LauncherBreakdown {
+    //  reports cold start time of NexusLauncher
+    optional int64 cold_start_dur = 1;
+  }
+  optional LauncherBreakdown launcher_breakdown = 5;
 }
 
 // End of protos/perfetto/metrics/android/android_boot.proto
@@ -2251,6 +2282,7 @@
   optional string unique_session_name = 8;
   optional string trace_config_pbtxt = 9;
   optional int64 sched_duration_ns = 10;
+  optional int64 tracing_started_ns = 11;
 }
 
 // Stats counters for the trace.
@@ -2283,7 +2315,7 @@
 
 // Root message for all Perfetto-based metrics.
 //
-// Next id: 58
+// Next id: 59
 message TraceMetrics {
   reserved 4, 10, 13, 14, 16, 19;
 
@@ -2435,11 +2467,15 @@
 
   // Metrics for App Not Responding (ANR) errors.
   optional AndroidAnrMetric android_anr = 55;
+
   // Aggregated Android Monitor Contention metrics
   optional AndroidMonitorContentionAggMetric android_monitor_contention_agg = 56;
 
   optional AndroidBootMetric android_boot = 57;
 
+  // Metric for AdServices module.
+  optional AdServicesMetric ad_services_metric = 58;
+
   // Demo extensions.
   extensions 450 to 499;
 
diff --git a/protos/perfetto/trace/BUILD.gn b/protos/perfetto/trace/BUILD.gn
index f81b05c..c808a93 100644
--- a/protos/perfetto/trace/BUILD.gn
+++ b/protos/perfetto/trace/BUILD.gn
@@ -93,6 +93,7 @@
     "../config:@TYPE@",
     "android:@TYPE@",
     "chrome:@TYPE@",
+    "etw:@TYPE@",
     "filesystem:@TYPE@",
     "ftrace:@TYPE@",
     "gpu:@TYPE@",
diff --git a/protos/perfetto/trace/etw/BUILD.gn b/protos/perfetto/trace/etw/BUILD.gn
new file mode 100644
index 0000000..f22514c
--- /dev/null
+++ b/protos/perfetto/trace/etw/BUILD.gn
@@ -0,0 +1,29 @@
+# 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("../../../../gn/perfetto.gni")
+import("../../../../gn/proto_library.gni")
+import("all_protos.gni")
+
+perfetto_proto_library("@TYPE@") {
+  sources = etw_proto_names
+}
+
+if (perfetto_build_standalone) {
+  perfetto_proto_library("descriptor") {
+    proto_generators = [ "descriptor" ]
+    generate_descriptor = "etw.descriptor"
+    sources = [ "etw_event_bundle.proto" ]
+  }
+}
diff --git a/protos/perfetto/trace/etw/all_protos.gni b/protos/perfetto/trace/etw/all_protos.gni
new file mode 100644
index 0000000..e6c9e9a
--- /dev/null
+++ b/protos/perfetto/trace/etw/all_protos.gni
@@ -0,0 +1,19 @@
+# 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.
+
+etw_proto_names = [
+  "etw.proto",
+  "etw_event.proto",
+  "etw_event_bundle.proto",
+]
diff --git a/protos/perfetto/trace/etw/etw.proto b/protos/perfetto/trace/etw/etw.proto
new file mode 100644
index 0000000..c008be2
--- /dev/null
+++ b/protos/perfetto/trace/etw/etw.proto
@@ -0,0 +1,153 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+package perfetto.protos;
+
+// Proto definition based on the Thread_v2 CSwitch class definition
+// See: https://learn.microsoft.com/en-us/windows/win32/etw/cswitch
+message CSwitchEtwEvent {
+  // New thread ID after the switch.
+  optional uint32 new_thread_id = 1;
+
+  // Previous thread ID.
+  optional uint32 old_thread_id = 2;
+
+  // Thread priority of the new thread.
+  optional sint32 new_thread_priority = 3;
+
+  // Thread priority of the previous thread.
+  optional sint32 old_thread_priority = 4;
+
+  // The index of the C-state that was last used by the processor. A value of 0
+  // represents the lightest idle state with higher values representing deeper
+  // C-states.
+  optional uint32 previous_c_state = 5;
+
+  // Wait reason for the previous thread. The ordering is important as based on
+  // the OldThreadWaitReason definition from the link above. The following are
+  // the possible values:
+  enum OldThreadWaitReason {
+    EXECUTIVE = 0;
+    FREE_PAGE = 1;
+    PAGE_IN = 2;
+    POOL_ALLOCATION = 3;
+    DELAY_EXECUTION = 4;
+    SUSPEND = 5;
+    USER_REQUEST = 6;
+    WR_EXECUTIVE = 7;
+    WR_FREE_PAGE = 8;
+    WR_PAGE_IN = 9;
+    WR_POOL_ALLOCATION = 10;
+    WR_DELAY_EXECUTION = 11;
+    WR_SUSPENDED = 12;
+    WR_USER_REQUEST = 13;
+    WR_EVENT_PAIR = 14;
+    WR_QUEUE = 15;
+    WR_LPC_RECEIVER = 16;
+    WR_LPC_REPLY = 17;
+    WR_VIRTUAL_MEMORY = 18;
+    WR_PAGE_OUT = 19;
+    WR_RENDEZ_VOUS = 20;
+    WR_KEYED_EVENT = 21;
+    WR_TERMINATED = 22;
+    WR_PROCESS_IN_SWAP = 23;
+    WR_CPU_RATE_CONTROL = 24;
+    WR_CALLOUT_STACK = 25;
+    WR_KERNEL = 26;
+    WR_RESOURCE = 27;
+    WR_PUSH_LOCK = 28;
+    WR_MUTEX = 29;
+    WR_QUANTUM_END = 30;
+    WR_DISPATCH_INT = 31;
+    WR_PREEMPTED = 32;
+    WR_YIELD_EXECUTION = 33;
+    WR_FAST_MUTEX = 34;
+    WR_GUARD_MUTEX = 35;
+    WR_RUNDOWN = 36;
+    MAXIMUM_WAIT_REASON = 37;
+  }
+
+  optional OldThreadWaitReason old_thread_wait_reason = 6;
+
+  // Wait mode for the previous thread. The ordering is important as based on
+  // the OldThreadWaitMode definition from the link above. The following are the
+  // possible values:
+  enum OldThreadWaitMode {
+    KERNEL_MODE = 0;
+    USER_MODE = 1;
+  }
+
+  optional OldThreadWaitMode old_thread_wait_mode = 7;
+
+  // State of the previous thread. The ordering is important as based on the
+  // OldThreadState definition from the link above. The following are the
+  // possible state values:
+  enum OldThreadState {
+    INITIALIZED = 0;
+    READY = 1;
+    RUNNING = 2;
+    STANDBY = 3;
+    TERMINATED = 4;
+    WAITING = 5;
+    TRANSITION = 6;
+    DEFERRED_READY = 7;
+  }
+
+  optional OldThreadState old_thread_state = 8;
+
+  // Ideal wait time of the previous thread.
+  optional sint32 old_thread_wait_ideal_processor = 9;
+
+  // Wait time for the new thread.
+  optional uint32 new_thread_wait_time = 10;
+}
+
+// Proto definition based on the Thread_v2 CSwitch class definition
+// See: https://learn.microsoft.com/en-us/windows/win32/etw/readythread
+message ReadyThreadEtwEvent {
+  // The thread identifier of the thread being readied for execution.
+  optional uint32 t_thread_id = 1;
+
+  // The reason for the priority boost. The ordering is important as based on
+  // the AdjustReason definition from the link above.
+  enum AdjustReason {
+    IGNORE_THE_INCREMENT = 0;
+    // Apply the increment, which will decay incrementally at the end of each
+    // quantum.
+    APPLY_INCREMENT = 1;
+    // Apply the increment as a boost that will decay in its entirety at quantum
+    // (typically for priority donation).
+    APPLY_INCREMENT_BOOST = 2;
+  }
+
+  optional AdjustReason adjust_reason = 2;
+
+  //  The value by which the priority is being adjusted.
+  optional sint32 adjust_increment = 3;
+
+  enum TraceFlag {
+    TRACE_FLAG_UNSPECIFIED = 0;
+    // The thread has been readied from DPC (deferred procedure call).
+    THREAD_READIED = 0x1;
+    // The kernel stack is currently swapped out.
+    KERNEL_STACK_SWAPPED_OUT = 0x2;
+    // The process address space is swapped out.
+    PROCESS_ADDRESS_SWAPPED_OUT = 0x4;
+  }
+
+  optional TraceFlag flag = 4;
+}
\ No newline at end of file
diff --git a/protos/perfetto/trace/etw/etw_event.proto b/protos/perfetto/trace/etw/etw_event.proto
new file mode 100644
index 0000000..9ef1b4e
--- /dev/null
+++ b/protos/perfetto/trace/etw/etw_event.proto
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+package perfetto.protos;
+
+import "protos/perfetto/trace/etw/etw.proto";
+
+message EtwTraceEvent {
+  optional uint64 timestamp = 1;
+
+  oneof event {
+    CSwitchEtwEvent c_switch = 2;
+    ReadyThreadEtwEvent ready_thread = 3;
+  }
+}
\ No newline at end of file
diff --git a/protos/perfetto/trace/etw/etw_event_bundle.proto b/protos/perfetto/trace/etw/etw_event_bundle.proto
new file mode 100644
index 0000000..3ea3916
--- /dev/null
+++ b/protos/perfetto/trace/etw/etw_event_bundle.proto
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+import "protos/perfetto/trace/etw/etw_event.proto";
+
+package perfetto.protos;
+
+// The result of tracing one or more etw event uses per-processor buffers where
+// an in-use buffer is assigned to each processor at all times. Therefore,
+// collecting multiple events they should already be synchronized.
+message EtwTraceEventBundle {
+  optional uint32 cpu = 1;
+  repeated EtwTraceEvent event = 2;
+}
\ No newline at end of file
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index 16112ab..6470fdb 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -901,7 +901,7 @@
   // Matches the name given to RegisterInterceptor().
   optional string name = 1;
 
-  optional ConsoleConfig console_config = 100 [lazy = true];
+  optional ConsoleConfig console_config = 100;
 }
 
 // End of protos/perfetto/config/interceptor_config.proto
@@ -5166,6 +5166,167 @@
 
 // End of protos/perfetto/trace/clock_snapshot.proto
 
+// Begin of protos/perfetto/trace/etw/etw.proto
+
+// Proto definition based on the Thread_v2 CSwitch class definition
+// See: https://learn.microsoft.com/en-us/windows/win32/etw/cswitch
+message CSwitchEtwEvent {
+  // New thread ID after the switch.
+  optional uint32 new_thread_id = 1;
+
+  // Previous thread ID.
+  optional uint32 old_thread_id = 2;
+
+  // Thread priority of the new thread.
+  optional sint32 new_thread_priority = 3;
+
+  // Thread priority of the previous thread.
+  optional sint32 old_thread_priority = 4;
+
+  // The index of the C-state that was last used by the processor. A value of 0
+  // represents the lightest idle state with higher values representing deeper
+  // C-states.
+  optional uint32 previous_c_state = 5;
+
+  // Wait reason for the previous thread. The ordering is important as based on
+  // the OldThreadWaitReason definition from the link above. The following are
+  // the possible values:
+  enum OldThreadWaitReason {
+    EXECUTIVE = 0;
+    FREE_PAGE = 1;
+    PAGE_IN = 2;
+    POOL_ALLOCATION = 3;
+    DELAY_EXECUTION = 4;
+    SUSPEND = 5;
+    USER_REQUEST = 6;
+    WR_EXECUTIVE = 7;
+    WR_FREE_PAGE = 8;
+    WR_PAGE_IN = 9;
+    WR_POOL_ALLOCATION = 10;
+    WR_DELAY_EXECUTION = 11;
+    WR_SUSPENDED = 12;
+    WR_USER_REQUEST = 13;
+    WR_EVENT_PAIR = 14;
+    WR_QUEUE = 15;
+    WR_LPC_RECEIVER = 16;
+    WR_LPC_REPLY = 17;
+    WR_VIRTUAL_MEMORY = 18;
+    WR_PAGE_OUT = 19;
+    WR_RENDEZ_VOUS = 20;
+    WR_KEYED_EVENT = 21;
+    WR_TERMINATED = 22;
+    WR_PROCESS_IN_SWAP = 23;
+    WR_CPU_RATE_CONTROL = 24;
+    WR_CALLOUT_STACK = 25;
+    WR_KERNEL = 26;
+    WR_RESOURCE = 27;
+    WR_PUSH_LOCK = 28;
+    WR_MUTEX = 29;
+    WR_QUANTUM_END = 30;
+    WR_DISPATCH_INT = 31;
+    WR_PREEMPTED = 32;
+    WR_YIELD_EXECUTION = 33;
+    WR_FAST_MUTEX = 34;
+    WR_GUARD_MUTEX = 35;
+    WR_RUNDOWN = 36;
+    MAXIMUM_WAIT_REASON = 37;
+  }
+
+  optional OldThreadWaitReason old_thread_wait_reason = 6;
+
+  // Wait mode for the previous thread. The ordering is important as based on
+  // the OldThreadWaitMode definition from the link above. The following are the
+  // possible values:
+  enum OldThreadWaitMode {
+    KERNEL_MODE = 0;
+    USER_MODE = 1;
+  }
+
+  optional OldThreadWaitMode old_thread_wait_mode = 7;
+
+  // State of the previous thread. The ordering is important as based on the
+  // OldThreadState definition from the link above. The following are the
+  // possible state values:
+  enum OldThreadState {
+    INITIALIZED = 0;
+    READY = 1;
+    RUNNING = 2;
+    STANDBY = 3;
+    TERMINATED = 4;
+    WAITING = 5;
+    TRANSITION = 6;
+    DEFERRED_READY = 7;
+  }
+
+  optional OldThreadState old_thread_state = 8;
+
+  // Ideal wait time of the previous thread.
+  optional sint32 old_thread_wait_ideal_processor = 9;
+
+  // Wait time for the new thread.
+  optional uint32 new_thread_wait_time = 10;
+}
+
+// Proto definition based on the Thread_v2 CSwitch class definition
+// See: https://learn.microsoft.com/en-us/windows/win32/etw/readythread
+message ReadyThreadEtwEvent {
+  // The thread identifier of the thread being readied for execution.
+  optional uint32 t_thread_id = 1;
+
+  // The reason for the priority boost. The ordering is important as based on
+  // the AdjustReason definition from the link above.
+  enum AdjustReason {
+    IGNORE_THE_INCREMENT = 0;
+    // Apply the increment, which will decay incrementally at the end of each
+    // quantum.
+    APPLY_INCREMENT = 1;
+    // Apply the increment as a boost that will decay in its entirety at quantum
+    // (typically for priority donation).
+    APPLY_INCREMENT_BOOST = 2;
+  }
+
+  optional AdjustReason adjust_reason = 2;
+
+  //  The value by which the priority is being adjusted.
+  optional sint32 adjust_increment = 3;
+
+  enum TraceFlag {
+    TRACE_FLAG_UNSPECIFIED = 0;
+    // The thread has been readied from DPC (deferred procedure call).
+    THREAD_READIED = 0x1;
+    // The kernel stack is currently swapped out.
+    KERNEL_STACK_SWAPPED_OUT = 0x2;
+    // The process address space is swapped out.
+    PROCESS_ADDRESS_SWAPPED_OUT = 0x4;
+  }
+
+  optional TraceFlag flag = 4;
+}
+// End of protos/perfetto/trace/etw/etw.proto
+
+// Begin of protos/perfetto/trace/etw/etw_event.proto
+
+message EtwTraceEvent {
+  optional uint64 timestamp = 1;
+
+  oneof event {
+    CSwitchEtwEvent c_switch = 2;
+    ReadyThreadEtwEvent ready_thread = 3;
+  }
+}
+// End of protos/perfetto/trace/etw/etw_event.proto
+
+// Begin of protos/perfetto/trace/etw/etw_event_bundle.proto
+
+// The result of tracing one or more etw event uses per-processor buffers where
+// an in-use buffer is assigned to each processor at all times. Therefore,
+// collecting multiple events they should already be synchronized.
+message EtwTraceEventBundle {
+  optional uint32 cpu = 1;
+  repeated EtwTraceEvent event = 2;
+}
+// End of protos/perfetto/trace/etw/etw_event_bundle.proto
+
 // Begin of protos/perfetto/common/descriptor.proto
 
 // The protocol compiler can output a FileDescriptorSet containing the .proto
@@ -12334,7 +12495,7 @@
     optional uint64 user_ns = 2;
 
     // Time spent in user mode (low prio).
-    optional uint64 user_ice_ns = 3;
+    optional uint64 user_nice_ns = 3;
 
     // Time spent in system mode.
     optional uint64 system_mode_ns = 4;
@@ -12452,6 +12613,10 @@
 
   // Kernel page size - sysconf(_SC_PAGESIZE).
   optional uint32 page_size = 6;
+
+  // The timezone offset from UTC, as per strftime("%z"), in minutes.
+  // Introduced in v38 / Android V.
+  optional int32 timezone_off_mins = 7;
 }
 
 // End of protos/perfetto/trace/system_info.proto
@@ -13081,7 +13246,7 @@
 // See the [Buffers and Dataflow](/docs/concepts/buffers.md) doc for details.
 //
 // Next reserved id: 14 (up to 15).
-// Next id: 95.
+// Next id: 96.
 message TracePacket {
   // The timestamp of the TracePacket.
   // By default this timestamps refers to the trace clock (CLOCK_BOOTTIME on
@@ -13201,6 +13366,9 @@
     LayersSnapshotProto surfaceflinger_layers_snapshot = 93;
     TransactionTraceEntry surfaceflinger_transactions = 94;
 
+    // Events from the Windows etw infrastructure.
+    EtwTraceEventBundle etw_events = 95;
+
     // This field is only used for testing.
     // In previous versions of this proto this field had the id 268435455
     // This caused many problems:
diff --git a/protos/perfetto/trace/sys_stats/sys_stats.proto b/protos/perfetto/trace/sys_stats/sys_stats.proto
index 2aad34c..4bc538d 100644
--- a/protos/perfetto/trace/sys_stats/sys_stats.proto
+++ b/protos/perfetto/trace/sys_stats/sys_stats.proto
@@ -45,7 +45,7 @@
     optional uint64 user_ns = 2;
 
     // Time spent in user mode (low prio).
-    optional uint64 user_ice_ns = 3;
+    optional uint64 user_nice_ns = 3;
 
     // Time spent in system mode.
     optional uint64 system_mode_ns = 4;
diff --git a/protos/perfetto/trace/system_info.proto b/protos/perfetto/trace/system_info.proto
index f4c0507..9a75773 100644
--- a/protos/perfetto/trace/system_info.proto
+++ b/protos/perfetto/trace/system_info.proto
@@ -44,4 +44,8 @@
 
   // Kernel page size - sysconf(_SC_PAGESIZE).
   optional uint32 page_size = 6;
+
+  // The timezone offset from UTC, as per strftime("%z"), in minutes.
+  // Introduced in v38 / Android V.
+  optional int32 timezone_off_mins = 7;
 }
diff --git a/protos/perfetto/trace/test_extensions.proto b/protos/perfetto/trace/test_extensions.proto
index 0d33c09..2d21e92 100644
--- a/protos/perfetto/trace/test_extensions.proto
+++ b/protos/perfetto/trace/test_extensions.proto
@@ -28,6 +28,7 @@
 message TestExtension {
   extend TrackEvent {
     optional string string_extension_for_testing = 9900;
+    optional string string_extension_for_testing2 = 9905;
     repeated int32 int_extension_for_testing = 9901;
     optional string omitted_extension_for_testing = 9902;
     optional TestExtensionChild nested_message_extension_for_testing = 9903;
diff --git a/protos/perfetto/trace/trace_packet.proto b/protos/perfetto/trace/trace_packet.proto
index b47981b..0c60cf6 100644
--- a/protos/perfetto/trace/trace_packet.proto
+++ b/protos/perfetto/trace/trace_packet.proto
@@ -35,6 +35,7 @@
 import "protos/perfetto/trace/chrome/chrome_metadata.proto";
 import "protos/perfetto/trace/chrome/chrome_trace_event.proto";
 import "protos/perfetto/trace/clock_snapshot.proto";
+import "protos/perfetto/trace/etw/etw_event_bundle.proto";
 import "protos/perfetto/trace/filesystem/inode_file_map.proto";
 import "protos/perfetto/trace/ftrace/ftrace_event_bundle.proto";
 import "protos/perfetto/trace/ftrace/ftrace_stats.proto";
@@ -96,7 +97,7 @@
 // See the [Buffers and Dataflow](/docs/concepts/buffers.md) doc for details.
 //
 // Next reserved id: 14 (up to 15).
-// Next id: 95.
+// Next id: 96.
 message TracePacket {
   // The timestamp of the TracePacket.
   // By default this timestamps refers to the trace clock (CLOCK_BOOTTIME on
@@ -216,6 +217,9 @@
     LayersSnapshotProto surfaceflinger_layers_snapshot = 93;
     TransactionTraceEntry surfaceflinger_transactions = 94;
 
+    // Events from the Windows etw infrastructure.
+    EtwTraceEventBundle etw_events = 95;
+
     // This field is only used for testing.
     // In previous versions of this proto this field had the id 268435455
     // This caused many problems:
diff --git a/protos/perfetto/trace_processor/trace_processor.proto b/protos/perfetto/trace_processor/trace_processor.proto
index fbf1987..8a6f511 100644
--- a/protos/perfetto/trace_processor/trace_processor.proto
+++ b/protos/perfetto/trace_processor/trace_processor.proto
@@ -45,7 +45,8 @@
   // Changes:
   // 7. Introduce GUESS_CPU_SIZE
   // 8. Add 'json' option to ComputeMetricArgs
-  TRACE_PROCESSOR_CURRENT_API_VERSION = 8;
+  // 9. Add get_thread_state_summary_for_interval.
+  TRACE_PROCESSOR_CURRENT_API_VERSION = 9;
 }
 
 // At lowest level, the wire-format of the RPC procol is a linear sequence of
diff --git a/python/generators/diff_tests/testing.py b/python/generators/diff_tests/testing.py
index 89aea8a..9bd6d84 100644
--- a/python/generators/diff_tests/testing.py
+++ b/python/generators/diff_tests/testing.py
@@ -177,7 +177,7 @@
     self.name = name
     self.blueprint = blueprint
     self.index_dir = index_dir
-    self.test_dir = os.path.dirname(os.path.dirname(os.path.dirname(index_dir)))
+    self.test_dir = os.path.abspath(os.path.join(__file__, '../../../../test'))
 
     if blueprint.is_metric():
       self.type = TestType.METRIC
diff --git a/python/generators/sql_processing/docs_parse.py b/python/generators/sql_processing/docs_parse.py
index cd940e1..55ba205 100644
--- a/python/generators/sql_processing/docs_parse.py
+++ b/python/generators/sql_processing/docs_parse.py
@@ -17,10 +17,10 @@
 from dataclasses import dataclass
 import re
 import sys
-from typing import Any, Dict, List, Optional, Set, Tuple
+from typing import Any, Dict, List, Optional, Set, Tuple, NamedTuple
 
 from python.generators.sql_processing.docs_extractor import DocsExtractor
-from python.generators.sql_processing.utils import ObjKind
+from python.generators.sql_processing.utils import ANY_PATTERN, ARG_DEFINITION_PATTERN, ObjKind
 from python.generators.sql_processing.utils import ARG_ANNOTATION_PATTERN
 from python.generators.sql_processing.utils import NAME_AND_TYPE_PATTERN
 from python.generators.sql_processing.utils import FUNCTION_RETURN_PATTERN
@@ -36,6 +36,13 @@
   return re.fullmatch(r'^[a-z_0-9]*$', s) is not None
 
 
+class Arg(NamedTuple):
+  # TODO(b/307926059): the type is missing on old-style documentation for
+  # tables. Make it "str" after stdlib is migrated.
+  type: Optional[str]
+  description: str
+
+
 class AbstractDocParser(ABC):
 
   @dataclass
@@ -78,8 +85,8 @@
       self._error(f'Unknown documentation annotation {type}')
 
   def _parse_columns(self, ans: List[DocsExtractor.Annotation],
-                     sql_cols_str: str) -> Dict[str, str]:
-    cols = {}
+                     schema: Optional[str]) -> Dict[str, Arg]:
+    column_annotations = {}
     for t in ans:
       if t.key != '@column':
         continue
@@ -88,21 +95,44 @@
         self._error(f'@column annotation value {t.value} does not match '
                     f'pattern {COLUMN_ANNOTATION_PATTERN}')
         continue
-      cols[m.group(1)] = m.group(2).strip()
+      column_annotations[m.group(1)] = Arg(None, m.group(2).strip())
 
-    sql_cols = self._parse_name_and_types_str(sql_cols_str)
-    if sql_cols:
-      for col in set(cols.keys()).difference(sql_cols.keys()):
-        self._error(f'@column "{col}" documented but does not exist in '
-                    'function definition')
-      for col in set(sql_cols.keys()).difference(cols):
-        self._error(f'Column "{col}" defined in SQL but is not documented with '
-                    '@column')
-    return cols
+    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)
+
+    for column in columns:
+      inline_comment = columns[column].description
+      if not inline_comment and column not in column_annotations:
+        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, Any]:
-    args = {}
+                  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
@@ -111,16 +141,24 @@
         self._error(f'Expected arg documentation "{an.value}" to match pattern '
                     f'{ARG_ANNOTATION_PATTERN}')
         continue
-      args[m.group(1)] = {'type': m.group(2), 'desc': m.group(3).strip()}
+      arg_annotations[m.group(1)] = Arg(m.group(2), m.group(3).strip())
 
-    sql_args = self._parse_name_and_types_str(sql_args_str)
-    if sql_args:
-      for col in set(args.keys()).difference(sql_args.keys()):
-        self._error(f'Arg "{col}" documented with @arg but does not exist '
-                    'in function definition')
-      for arg in set(sql_args.keys()).difference(args.keys()):
-        self._error(f'Arg "{arg}" defined in SQL but is not documented with '
-                    '@arg')
+    for arg in args:
+      if not args[arg].description and arg not in arg_annotations:
+        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
 
   def _parse_ret(self, ans: List[DocsExtractor.Annotation],
@@ -144,23 +182,32 @@
       return '', ''
     return ret_type, ret_desc.strip()
 
-  def _parse_name_and_types_str(self, args_str: str) -> Dict[str, str]:
-    if not args_str:
-      return {}
+  # Parse function argument definition list or a table schema, e.g.
+  # arg1 INT, arg2 STRING, including their comments.
+  def _parse_args_definition(self, args_str: str) -> Dict[str, Arg]:
+    result = {}
+    remaining_args = args_str.strip()
+    while remaining_args:
+      m = re.match(fr'^{ARG_DEFINITION_PATTERN}({ANY_PATTERN})', remaining_args)
+      if not m:
+        self._error(f'Expected "{args_str}" to correspond to '
+                    '"-- Comment\n arg_name TYPE" format '
+                    '({ARG_DEFINITION_PATTERN})')
+        return result
+      groups = m.groups()
+      comment = None if groups[0] is None else ' '.join(
+          line.strip().lstrip('--').lstrip() for line in groups[0].split('\n'))
+      name = groups[-3]
+      type = groups[-2]
+      result[name] = Arg(type, comment)
+      # Strip whitespace and comma and parse the next arg.
+      remaining_args = groups[-1].lstrip().lstrip(',').lstrip()
 
-    args = {}
-    for arg_str in args_str.split(","):
-      m = re.match(NAME_AND_TYPE_PATTERN, arg_str)
-      if m is None:
-        self._error(f'Expected "{arg_str}" to match pattern '
-                    f'{NAME_AND_TYPE_PATTERN}')
-        continue
-      args[m.group(1)] = m.group(2).strip()
-    return args
+    return result
 
   def _error(self, error: str):
     self.errors.append(
-        f'Error while parsing documentation for {self.name} in {self.path}: '
+        f'Error while parsing documentation for "{self.name}" in {self.path}: '
         f'{error}')
 
 
@@ -168,7 +215,7 @@
   name: str
   type: str
   desc: str
-  cols: Dict[str, str]
+  cols: Dict[str, Arg]
 
   def __init__(self, name, type, desc, cols):
     self.name = name
@@ -186,23 +233,27 @@
   def parse(self, doc: DocsExtractor.Extract) -> Optional[TableOrView]:
     assert doc.obj_kind == ObjKind.table_view
 
-    self.name = doc.obj_match[1]
+    or_replace, type, self.name, schema = doc.obj_match
+
+    if or_replace is not None:
+      self._error(
+          f'{type} "{self.name}": CREATE OR REPLACE is not allowed in stdlib')
     if is_internal(self.name):
       return None
 
     self._validate_only_contains_annotations(doc.annotations, {'@column'})
     return TableOrView(
         name=self._parse_name(),
-        type=doc.obj_match[0],
+        type=type,
         desc=self._parse_desc_not_empty(doc.description),
-        cols=self._parse_columns(doc.annotations, ''),
+        cols=self._parse_columns(doc.annotations, schema),
     )
 
 
 class Function:
   name: str
   desc: str
-  args: Dict[str, Any]
+  args: Dict[str, Arg]
   return_type: str
   return_desc: str
 
@@ -221,7 +272,11 @@
     super().__init__(path, module)
 
   def parse(self, doc: DocsExtractor.Extract) -> Optional[Function]:
-    self.name, args, ret, _ = doc.obj_match
+    or_replace, self.name, args, ret = doc.obj_match
+
+    if or_replace is not None:
+      self._error(
+          f'Function "{self.name}": CREATE OR REPLACE is not allowed in stdlib')
 
     # Ignore internal functions.
     if is_internal(self.name):
@@ -233,8 +288,8 @@
     name = self._parse_name()
 
     if not is_snake_case(name):
-      self._error('Function name %s is not snake_case (should be %s) ' %
-                  (name, name.casefold()))
+      self._error(f'Function name "{name}" is not snake_case'
+                  f' (should be {name.casefold()})')
 
     return Function(
         name=name,
@@ -248,8 +303,8 @@
 class TableFunction:
   name: str
   desc: str
-  cols: Dict[str, str]
-  args: Dict[str, Any]
+  cols: Dict[str, Arg]
+  args: Dict[str, Arg]
 
   def __init__(self, name, desc, cols, args):
     self.name = name
@@ -265,7 +320,11 @@
     super().__init__(path, module)
 
   def parse(self, doc: DocsExtractor.Extract) -> Optional[TableFunction]:
-    self.name, args, columns, _ = doc.obj_match
+    or_replace, self.name, args, columns = doc.obj_match
+
+    if or_replace is not None:
+      self._error(
+          f'Function "{self.name}": CREATE OR REPLACE is not allowed in stdlib')
 
     # Ignore internal functions.
     if is_internal(self.name):
@@ -276,8 +335,8 @@
     name = self._parse_name()
 
     if not is_snake_case(name):
-      self._error('Function name %s is not snake_case (should be %s) ' %
-                  (name, name.casefold()))
+      self._error(f'Function name "{name}" is not snake_case'
+                  f' (should be "{name.casefold()}")')
 
     return TableFunction(
         name=name,
diff --git a/python/generators/sql_processing/utils.py b/python/generators/sql_processing/utils.py
index f4ee20c..fa9e73d 100644
--- a/python/generators/sql_processing/utils.py
+++ b/python/generators/sql_processing/utils.py
@@ -17,53 +17,67 @@
 from typing import Dict, List
 
 NAME = r'[a-zA-Z_\d\{\}]+'
+ARGS = '[^\)]*'
 ANY_WORDS = r'[^\s].*'
 ANY_NON_QUOTE = r'[^\']*.*'
 TYPE = r'[A-Z]+'
 SQL = r'[\s\S]*?'
 WS = r'\s*'
+COMMENT = r'--[^\n]*'
+NEW_STYLE_ARG = rf'((?: {COMMENT})*) ({NAME}) ({TYPE})'
 
-CREATE_TABLE_VIEW_PATTERN = (
+
+# Make the pattern more readable by allowing the use of spaces
+# and replace then with a wildcard in a separate step.
+def update_pattern(pattern):
+  return pattern.replace(' ', WS)
+
+
+CREATE_TABLE_VIEW_PATTERN = update_pattern(
     # Match create table/view and catch type
-    fr'^CREATE{WS}(?:VIRTUAL|PERFETTO)?{WS}(TABLE|VIEW){WS}(?:IF NOT EXISTS)?'
-    # Catch the name
-    fr'{WS}({NAME}){WS}(?:AS|USING)?{WS}.*')
+    fr'^CREATE (OR REPLACE)? (?:VIRTUAL|PERFETTO)?'
+    fr' (TABLE|VIEW) (?:IF NOT EXISTS)?'
+    # Catch the name and optional schema.
+    fr' ({NAME}) (?: \( ({ARGS}) \) )? (?:AS|USING)? .*')
 
-CREATE_TABLE_AS_PATTERN = (fr'^CREATE{WS}TABLE{WS}({NAME}){WS}AS')
+CREATE_TABLE_AS_PATTERN = update_pattern(fr'^CREATE TABLE ({NAME}) AS')
 
-DROP_TABLE_VIEW_PATTERN = (fr'^DROP{WS}(TABLE|VIEW){WS}IF{WS}EXISTS{WS}'
-                           fr'({NAME});$')
+DROP_TABLE_VIEW_PATTERN = update_pattern(fr'^DROP (TABLE|VIEW) IF EXISTS '
+                                         fr'({NAME});$')
 
-CREATE_PERFETTO_TABLE_PATTERN = (
-    # Match `CREATE PERFETTO TABLE {name} AS` string
-    fr'^CREATE{WS}PERFETTO{WS}TABLE{WS}({NAME}){WS}AS{WS}.*')
-
-CREATE_FUNCTION_PATTERN = (
+CREATE_FUNCTION_PATTERN = update_pattern(
     # Function name.
-    fr"CREATE{WS}PERFETTO{WS}FUNCTION{WS}({NAME}){WS}"
+    fr"CREATE (OR REPLACE)? PERFETTO FUNCTION ({NAME}) "
     # Args: anything in the brackets.
-    fr"{WS}\({WS}({ANY_WORDS}){WS}\){WS}"
+    fr" \( ({ARGS}) \) "
     # Type: word after RETURNS.
-    fr"{WS}RETURNS{WS}({TYPE}){WS}AS{WS}"
-    # Sql: Anything between ' and ');. We are catching \'.
-    fr"{WS}({SQL});")
+    fr" RETURNS ({TYPE}) AS ")
 
-CREATE_TABLE_FUNCTION_PATTERN = (
-    fr"CREATE{WS}PERFETTO{WS}FUNCTION{WS}({NAME}){WS}"
+CREATE_TABLE_FUNCTION_PATTERN = update_pattern(
+    fr"CREATE (OR REPLACE)? PERFETTO FUNCTION ({NAME}) "
     # Args: anything in the brackets.
-    fr"{WS}\({WS}({ANY_WORDS}){WS}\){WS}"
+    fr" \( ({ARGS}) \) "
     # Type: word after RETURNS.
-    fr"{WS}RETURNS{WS}TABLE\({WS}({ANY_WORDS}){WS}\){WS}AS{WS}"
-    # Sql: Anything between ' and ');. We are catching \'.
-    fr"{WS}({SQL});")
+    fr" RETURNS TABLE\( ({ANY_WORDS}) \) AS ")
 
-COLUMN_ANNOTATION_PATTERN = fr'^\s*({NAME})\s*({ANY_WORDS})'
+CREATE_MACRO_PATTERN = update_pattern(
+    fr"CREATE (OR REPLACE)? PERFETTO MACRO ({NAME}) "
+    # Args: anything in the brackets.
+    fr" \( ({ARGS}) \) "
+    # Type: word after RETURNS.
+    fr" RETURNS")
 
-NAME_AND_TYPE_PATTERN = fr'\s*({NAME})\s+({TYPE})\s*'
+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})'
 
-FUNCTION_RETURN_PATTERN = fr'^\s*({TYPE})\s+({ANY_WORDS})'
+ARG_DEFINITION_PATTERN = update_pattern(NEW_STYLE_ARG)
+
+FUNCTION_RETURN_PATTERN = update_pattern(fr'^ ({TYPE})\s+({ANY_WORDS})')
+
+ANY_PATTERN = r'(?:\s|.)*'
 
 
 class ObjKind(str, Enum):
@@ -82,7 +96,9 @@
 # Given a regex pattern and a string to match against, returns all the
 # matching positions. Specifically, it returns a dictionary from the line
 # number of the match to the regex match object.
-def match_pattern(pattern: str, file_str: str) -> Dict[int, re.Match]:
+# Note: this resuts a dict[int, re.Match], but re.Match exists only in later
+# versions of python3, prior to that it was _sre.SRE_Match.
+def match_pattern(pattern: str, file_str: str) -> Dict[int, object]:
   line_number_to_matches = {}
   for match in re.finditer(pattern, file_str, re.MULTILINE):
     line_id = file_str[:match.start()].count('\n')
diff --git a/python/perfetto/trace_processor/metrics.descriptor b/python/perfetto/trace_processor/metrics.descriptor
index 926dee9..b989e77 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/trace_processor.descriptor b/python/perfetto/trace_processor/trace_processor.descriptor
index 33ca764..8b8383f 100644
--- a/python/perfetto/trace_processor/trace_processor.descriptor
+++ b/python/perfetto/trace_processor/trace_processor.descriptor
Binary files differ
diff --git a/python/test/stdlib_unittest.py b/python/test/stdlib_unittest.py
index 93f68b5..0a19caf 100644
--- a/python/test/stdlib_unittest.py
+++ b/python/test/stdlib_unittest.py
@@ -20,30 +20,7 @@
     os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
 sys.path.append(os.path.join(ROOT_DIR))
 
-from python.generators.sql_processing.docs_parse import parse_file
-
-DESC = """--
--- First line.
--- Second line."""
-
-COLS_STR = """--
--- @column slice_id           Id of slice.
--- @column slice_name         Name of slice."""
-
-COLS_SQL_STR = "slice_id INT, slice_name STRING"
-
-ARGS_STR = """--
--- @arg utid INT              Utid of thread.
--- @arg name STRING           String name."""
-
-ARGS_SQL_STR = "utid INT, name STRING"
-
-RET_STR = """--
--- @ret BOOL                  Exists."""
-
-RET_SQL_STR = "BOOL"
-
-SQL_STR = "SELECT * FROM slice"
+from python.generators.sql_processing.docs_parse import Arg, parse_file
 
 
 class TestStdlib(unittest.TestCase):
@@ -51,10 +28,12 @@
   def test_valid_table(self):
     res = parse_file(
         'foo/bar.sql', f'''
-{DESC}
-{COLS_STR}
+-- First line.
+-- Second line.
+-- @column slice_id           Id of slice.
+-- @column slice_name         Name of slice.
 CREATE TABLE foo_table AS
-{SQL_STR};
+SELECT 1;
     '''.strip())
     self.assertListEqual(res.errors, [])
 
@@ -62,21 +41,24 @@
     self.assertEqual(table.name, 'foo_table')
     self.assertEqual(table.desc, 'First line. Second line.')
     self.assertEqual(table.type, 'TABLE')
-    self.assertEqual(table.cols, {
-        'slice_id': 'Id of slice.',
-        'slice_name': 'Name of slice.'
-    })
+    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'''
-{DESC}
-{ARGS_STR}
-{RET_STR}
-CREATE PERFETTO FUNCTION foo_fn({ARGS_SQL_STR})
-RETURNS {RET_SQL_STR}
+-- First line.
+-- Second line.
+-- @arg utid INT              Utid of thread.
+-- @arg name STRING           String name.
+-- @ret BOOL Exists.
+CREATE PERFETTO FUNCTION foo_fn(utid INT, name STRING)
+RETURNS BOOL
 AS
-{SQL_STR};
+SELECT 1;
     '''.strip())
     self.assertListEqual(res.errors, [])
 
@@ -85,14 +67,8 @@
     self.assertEqual(fn.desc, 'First line. Second line.')
     self.assertEqual(
         fn.args, {
-            'utid': {
-                'type': 'INT',
-                'desc': 'Utid of thread.',
-            },
-            'name': {
-                'type': 'STRING',
-                'desc': 'String name.',
-            },
+            'utid': Arg('INT', 'Utid of thread.'),
+            'name': Arg('STRING', 'String name.'),
         })
     self.assertEqual(fn.return_type, 'BOOL')
     self.assertEqual(fn.return_desc, 'Exists.')
@@ -100,41 +76,38 @@
   def test_valid_table_function(self):
     res = parse_file(
         'foo/bar.sql', f'''
-{DESC}
-{ARGS_STR}
-{COLS_STR}
-CREATE PERFETTO FUNCTION foo_view_fn({ARGS_SQL_STR})
-RETURNS TABLE({COLS_SQL_STR})
-AS {SQL_STR};
+-- 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, 'First line. Second line.')
+    self.assertEqual(fn.desc, 'Table comment.')
     self.assertEqual(
         fn.args, {
-            'utid': {
-                'type': 'INT',
-                'desc': 'Utid of thread.',
-            },
-            'name': {
-                'type': 'STRING',
-                'desc': 'String name.',
-            },
+            'utid': Arg('INT', 'Utid of thread.'),
+            'name': Arg('STRING', 'String name.'),
         })
-    self.assertEqual(fn.cols, {
-        'slice_id': 'Id of slice.',
-        'slice_name': 'Name of slice.'
-    })
+    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'''
-{DESC}
-{COLS_STR}
+-- Comment
+-- @column slice_id           Id of slice.
 CREATE TABLE bar_table AS
-{SQL_STR};
+SELECT 1;
     '''.strip())
     # Expecting an error: function prefix (bar) not matching module name (foo).
     self.assertEqual(len(res.errors), 1)
@@ -142,10 +115,10 @@
   def test_common_does_not_include_module_name(self):
     res = parse_file(
         'common/bar.sql', f'''
-{DESC}
-{COLS_STR}
+-- Comment.
+-- @column slice_id           Id of slice.
 CREATE TABLE common_table AS
-{SQL_STR};
+SELECT 1;
     '''.strip())
     # Expecting an error: functions in common/ should not have a module prefix.
     self.assertEqual(len(res.errors), 1)
@@ -153,12 +126,12 @@
   def test_cols_typo(self):
     res = parse_file(
         'foo/bar.sql', f'''
-{DESC}
+-- Comment.
 --
 -- @column slice_id2          Foo.
 -- @column slice_name         Bar.
 CREATE TABLE bar_table AS
-{SQL_STR};
+SELECT 1;
     '''.strip())
     # Expecting an error: column slice_id2 not found in the table.
     self.assertEqual(len(res.errors), 1)
@@ -166,12 +139,12 @@
   def test_cols_no_desc(self):
     res = parse_file(
         'foo/bar.sql', f'''
-{DESC}
+-- Comment.
 --
 -- @column slice_id
 -- @column slice_name         Bar.
 CREATE TABLE bar_table AS
-{SQL_STR};
+SELECT 1;
     '''.strip())
     # Expecting an error: column slice_id is missing a description.
     self.assertEqual(len(res.errors), 1)
@@ -179,15 +152,15 @@
   def test_args_typo(self):
     res = parse_file(
         'foo/bar.sql', f'''
-{DESC}
+-- Comment.
 --
 -- @arg utid2 INT             Uint.
 -- @arg name STRING           String name.
-{RET_STR}
-CREATE PERFETTO FUNCTION foo_fn({ARGS_SQL_STR})
-RETURNS {RET_SQL_STR}
+-- @ret BOOL                  Exists.
+CREATE PERFETTO FUNCTION foo_fn(utid INT, name STRING)
+RETURNS BOOL
 AS
-{SQL_STR};
+SELECT 1;
     '''.strip())
     # Expecting 2 errors:
     # - arg utid2 not found in the function (should be utid);
@@ -197,15 +170,15 @@
   def test_args_no_desc(self):
     res = parse_file(
         'foo/bar.sql', f'''
-{DESC}
+-- Comment.
 --
 -- @arg utid INT
 -- @arg name STRING           String name.
-{RET_STR}
-CREATE PERFETTO FUNCTION foo_fn({ARGS_SQL_STR})
-RETURNS {RET_SQL_STR}
+-- @ret BOOL                  Exists.
+CREATE PERFETTO FUNCTION foo_fn(utid INT, name STRING)
+RETURNS BOOL
 AS
-{SQL_STR};
+SELECT 1;
     '''.strip())
     # Expecting 2 errors:
     # - arg utid is missing a description;
@@ -215,14 +188,13 @@
   def test_ret_no_desc(self):
     res = parse_file(
         'foo/bar.sql', f'''
-{DESC}
-{ARGS_STR}
+-- Comment
 --
 -- @ret BOOL
-CREATE PERFETTO FUNCTION foo_fn({ARGS_SQL_STR})
-RETURNS {RET_SQL_STR}
+CREATE PERFETTO FUNCTION foo_fn()
+RETURNS BOOL
 AS
-{SQL_STR};
+SELECT TRUE;
     '''.strip())
     # Expecting an error: return value is missing a description.
     self.assertEqual(len(res.errors), 1)
@@ -239,12 +211,11 @@
 -- long
 --
 -- description.
-{ARGS_STR}
-{RET_STR}
-CREATE PERFETTO FUNCTION foo_fn({ARGS_SQL_STR})
-RETURNS {RET_SQL_STR}
+-- @ret BOOL                  Exists.
+CREATE PERFETTO FUNCTION foo_fn()
+RETURNS BOOL
 AS
-{SQL_STR};
+SELECT 1;
     '''.strip())
     self.assertListEqual(res.errors, [])
 
@@ -254,7 +225,7 @@
   def test_multiline_arg_desc(self):
     res = parse_file(
         'foo/bar.sql', f'''
-{DESC}
+-- Comment.
 --
 -- @arg utid INT              Uint
 -- spread
@@ -263,37 +234,174 @@
 -- @arg name STRING            String name
 --                             which spans across multiple lines
 -- inconsistently.
-{RET_STR}
-CREATE PERFETTO FUNCTION foo_fn({ARGS_SQL_STR})
-RETURNS {RET_SQL_STR}
+-- @ret BOOL                  Exists.
+CREATE PERFETTO FUNCTION foo_fn(utid INT, name STRING)
+RETURNS BOOL
 AS
-{SQL_STR};
+SELECT 1;
     '''.strip())
 
     fn = res.functions[0]
     self.assertEqual(
         fn.args, {
-            'utid': {
-                'type': 'INT',
-                'desc': 'Uint spread across lines.',
-            },
-            'name': {
-                'type': 'STRING',
-                'desc': 'String name which spans across multiple lines '
-                        'inconsistently.',
-            },
+            '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(
         'foo/bar.sql', f'''
-{DESC}
-{ARGS_STR}
-{RET_STR}
-CREATE PERFETTO FUNCTION foo_SnakeCase({ARGS_SQL_STR})
-RETURNS {RET_SQL_STR}
+-- Function comment.
+-- @ret BOOL                  Exists.
+CREATE PERFETTO FUNCTION foo_SnakeCase()
+RETURNS BOOL
 AS
-{SQL_STR};
+SELECT 1;
     '''.strip())
     # Expecting an error: function name should be using hacker_style.
-    self.assertEqual(len(res.errors), 1)
\ No newline at end of file
+    self.assertEqual(len(res.errors), 1)
+
+  def test_table_with_schema(self):
+    res = parse_file(
+        'foo/bar.sql', f'''
+-- Table comment.
+CREATE PERFETTO TABLE foo_table(
+    -- Id of slice.
+    id INT
+) AS
+SELECT 1 as id;
+    '''.strip())
+    self.assertListEqual(res.errors, [])
+
+    table = res.table_views[0]
+    self.assertEqual(table.name, 'foo_table')
+    self.assertEqual(table.desc, 'Table comment.')
+    self.assertEqual(table.type, 'TABLE')
+    self.assertEqual(table.cols, {
+        'id': Arg('INT', 'Id of slice.'),
+    })
+
+  def test_perfetto_view_with_schema(self):
+    res = parse_file(
+        'foo/bar.sql', f'''
+-- View comment.
+CREATE PERFETTO VIEW foo_table(
+    -- Foo.
+    foo INT,
+    -- Bar.
+    bar STRING
+) AS
+SELECT 1;
+    '''.strip())
+    self.assertListEqual(res.errors, [])
+
+    table = res.table_views[0]
+    self.assertEqual(table.name, 'foo_table')
+    self.assertEqual(table.desc, 'View comment.')
+    self.assertEqual(table.type, 'VIEW')
+    self.assertEqual(table.cols, {
+        'foo': Arg('INT', 'Foo.'),
+        'bar': Arg('STRING', 'Bar.'),
+    })
+
+  def test_function_with_new_style_docs(self):
+    res = parse_file(
+        'foo/bar.sql', f'''
+-- Function foo.
+-- @ret BOOL                  Exists.
+CREATE PERFETTO FUNCTION foo_fn(
+    -- Utid of thread.
+    utid INT,
+    -- String name.
+    name STRING)
+RETURNS BOOL
+AS
+SELECT 1;
+    '''.strip())
+    self.assertListEqual(res.errors, [])
+
+    fn = res.functions[0]
+    self.assertEqual(fn.name, 'foo_fn')
+    self.assertEqual(fn.desc, 'Function foo.')
+    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_function_with_new_style_docs_multiline_comment(self):
+    res = parse_file(
+        'foo/bar.sql', f'''
+-- Function foo.
+-- @ret BOOL                  Exists.
+CREATE PERFETTO FUNCTION foo_fn(
+    -- Multi
+    -- line
+    --
+    -- comment.
+    arg INT)
+RETURNS BOOL
+AS
+SELECT 1;
+    '''.strip())
+    self.assertListEqual(res.errors, [])
+
+    fn = res.functions[0]
+    self.assertEqual(fn.name, 'foo_fn')
+    self.assertEqual(fn.desc, 'Function foo.')
+    self.assertEqual(fn.args, {
+        'arg': Arg('INT', 'Multi line  comment.'),
+    })
+    self.assertEqual(fn.return_type, 'BOOL')
+    self.assertEqual(fn.return_desc, 'Exists.')
+
+  def test_create_or_replace_table_banned(self):
+    res = parse_file(
+        'common/bar.sql', f'''
+-- Table.
+CREATE OR REPLACE PERFETTO TABLE foo(
+    -- Column.
+    x INT,
+)
+RETURNS BOOL
+AS
+SELECT 1;
+
+    '''.strip())
+    # Expecting an error: CREATE OR REPLACE is not allowed in stdlib.
+    self.assertEqual(len(res.errors), 1)
+
+  def test_create_or_replace_view_banned(self):
+    res = parse_file(
+        'common/bar.sql', f'''
+-- Table.
+CREATE OR REPLACE PERFETTO VIEW foo(
+    -- Column.
+    x INT,
+)
+RETURNS BOOL
+AS
+SELECT 1;
+
+    '''.strip())
+    # Expecting an error: CREATE OR REPLACE is not allowed in stdlib.
+    self.assertEqual(len(res.errors), 1)
+
+  def test_create_or_replace_function_banned(self):
+    res = parse_file(
+        'foo/bar.sql', f'''
+-- Function foo.
+-- @ret BOOL                  Exists.
+CREATE OR REPLACE PERFETTO FUNCTION foo_fn()
+RETURNS BOOL
+AS
+SELECT 1;
+    '''.strip())
+    # Expecting an error: CREATE OR REPLACE is not allowed in stdlib.
+    self.assertEqual(len(res.errors), 1)
diff --git a/python/tools/check_imports.py b/python/tools/check_imports.py
index ef4ff29..dd096fb 100755
--- a/python/tools/check_imports.py
+++ b/python/tools/check_imports.py
@@ -37,6 +37,91 @@
     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"];
+
+    _gen [shape=ellipse, label="/gen"];
+    _base [shape=ellipse, label="/base"];
+    _core [shape=ellipse, label="/core"];
+    _engine [shape=ellipse, label="/engine"];
+
+    _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];
+
+    _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" color="green"];
+    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 [color=green];
+    _protos -> _gen [color=green];
+    _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):
 
@@ -183,6 +268,23 @@
         'chrome_extension must be a leaf',
     ),
 
+    # 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',
+    ),
+
     # Fails at the moment as we have several circular dependencies. One
     # example:
     # ui/src/frontend/cookie_consent.ts
@@ -340,6 +442,11 @@
   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)
@@ -371,6 +478,12 @@
       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)
+
   graph = collections.defaultdict(set)
   for path in all_source_files():
     for src, target in find_imports(path):
diff --git a/python/tools/heap_profile.py b/python/tools/heap_profile.py
index 06e7043..463f1b1 100644
--- a/python/tools/heap_profile.py
+++ b/python/tools/heap_profile.py
@@ -491,7 +491,7 @@
     if binary_path is None:
       binary_path = product_out_symbols
     elif product_out_symbols is not None:
-      binary_path += ":" + product_out_symbols
+      binary_path += os.pathsep + product_out_symbols
 
   trace_file = os.path.join(profile_target, 'raw-trace')
   concat_files = [trace_file]
diff --git a/src/base/paged_memory.cc b/src/base/paged_memory.cc
index 957a2dd..c009672 100644
--- a/src/base/paged_memory.cc
+++ b/src/base/paged_memory.cc
@@ -138,7 +138,6 @@
 
 #if TRACK_COMMITTED_SIZE()
 void PagedMemory::EnsureCommitted(size_t committed_size) {
-  PERFETTO_DCHECK(committed_size > 0u);
   PERFETTO_DCHECK(committed_size <= size_);
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
   if (committed_size_ >= committed_size)
diff --git a/src/base/string_utils.cc b/src/base/string_utils.cc
index ba6e52b..bc32301 100644
--- a/src/base/string_utils.cc
+++ b/src/base/string_utils.cc
@@ -24,6 +24,8 @@
 
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
 #include <xlocale.h>
+#elif PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+#include <windows.h>
 #endif
 
 #include <cinttypes>
@@ -217,6 +219,41 @@
   return str;
 }
 
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+bool WideToUTF8(const std::wstring& source, std::string& output) {
+  if (source.empty() ||
+      static_cast<int>(source.size()) > std::numeric_limits<int>::max()) {
+    return false;
+  }
+  int size = ::WideCharToMultiByte(CP_UTF8, 0, &source[0],
+                                   static_cast<int>(source.size()), nullptr, 0,
+                                   nullptr, nullptr);
+  output.assign(size, '\0');
+  if (::WideCharToMultiByte(CP_UTF8, 0, &source[0],
+                            static_cast<int>(source.size()), &output[0], size,
+                            nullptr, nullptr) != size) {
+    return false;
+  }
+  return true;
+}
+
+bool UTF8ToWide(const std::string& source, std::wstring& output) {
+  if (source.empty() ||
+      static_cast<int>(source.size()) > std::numeric_limits<int>::max()) {
+    return false;
+  }
+  int size = ::MultiByteToWideChar(CP_UTF8, 0, &source[0],
+                                   static_cast<int>(source.size()), nullptr, 0);
+  output.assign(size, L'\0');
+  if (::MultiByteToWideChar(CP_UTF8, 0, &source[0],
+                            static_cast<int>(source.size()), &output[0],
+                            size) != size) {
+    return false;
+  }
+  return true;
+}
+#endif // PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+
 size_t SprintfTrunc(char* dst, size_t dst_size, const char* fmt, ...) {
   if (PERFETTO_UNLIKELY(dst_size) == 0)
     return 0;
diff --git a/src/base/string_utils_unittest.cc b/src/base/string_utils_unittest.cc
index f65da9f..0138bf1 100644
--- a/src/base/string_utils_unittest.cc
+++ b/src/base/string_utils_unittest.cc
@@ -96,6 +96,8 @@
 TEST(StringUtilsTest, StringToInt32) {
   EXPECT_EQ(StringToInt32("0"), std::make_optional<int32_t>(0));
   EXPECT_EQ(StringToInt32("1"), std::make_optional<int32_t>(1));
+  EXPECT_EQ(StringToInt32("+42"), std::make_optional<int32_t>(42));
+  EXPECT_EQ(StringToInt32("+0042"), std::make_optional<int32_t>(42));
   EXPECT_EQ(StringToInt32("-42"), std::make_optional<int32_t>(-42));
   EXPECT_EQ(StringToInt32("42", 16), std::make_optional<int32_t>(0x42));
   EXPECT_EQ(StringToInt32("7ffffffe", 16),
diff --git a/src/base/thread_utils.cc b/src/base/thread_utils.cc
index bc28f0f..f0c4e35 100644
--- a/src/base/thread_utils.cc
+++ b/src/base/thread_utils.cc
@@ -15,6 +15,7 @@
  */
 
 #include "perfetto/base/thread_utils.h"
+#include "perfetto/ext/base/thread_utils.h"
 
 #include "perfetto/base/build_config.h"
 
@@ -22,7 +23,9 @@
 #include <zircon/process.h>
 #include <zircon/syscalls.h>
 #include <zircon/types.h>
-#endif  // PERFETTO_BUILDFLAG(PERFETTO_OS_FUCHSIA)
+#elif PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+#include <windows.h>
+#endif
 
 namespace perfetto {
 namespace base {
@@ -39,6 +42,52 @@
   thread_local static PlatformThreadId thread_id = ResolveThreadId();
   return thread_id;
 }
+
+#elif PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+
+// The SetThreadDescription API was brought in version 1607 of Windows 10.
+typedef HRESULT(WINAPI* SetThreadDescription)(HANDLE hThread,
+                                              PCWSTR lpThreadDescription);
+
+// The SetThreadDescription API was brought in version 1607 of Windows 10.
+typedef HRESULT(WINAPI* GetThreadDescription)(HANDLE hThread,
+                                              PWSTR* ppszThreadDescription);
+
+bool MaybeSetThreadName(const std::string& name) {
+  // The SetThreadDescription API works even if no debugger is attached.
+  static auto set_thread_description_func =
+      reinterpret_cast<SetThreadDescription>(::GetProcAddress(
+          ::GetModuleHandle(L"Kernel32.dll"), "SetThreadDescription"));
+  if (!set_thread_description_func) {
+    return false;
+  }
+  std::wstring wide_thread_name;
+  if (!UTF8ToWide(name, wide_thread_name)) {
+    return false;
+  }
+  HRESULT result = set_thread_description_func(::GetCurrentThread(),
+                                               wide_thread_name.c_str());
+  return !FAILED(result);
+}
+
+bool GetThreadName(std::string& out_result) {
+  static auto get_thread_description_func =
+      reinterpret_cast<GetThreadDescription>(::GetProcAddress(
+          ::GetModuleHandle(L"Kernel32.dll"), "GetThreadDescription"));
+  if (!get_thread_description_func) {
+    return false;
+  }
+  wchar_t* wide_thread_name;
+  HRESULT result =
+      get_thread_description_func(::GetCurrentThread(), &wide_thread_name);
+  if (SUCCEEDED(result)) {
+    bool success = WideToUTF8(std::wstring(wide_thread_name), out_result);
+    LocalFree(wide_thread_name);
+    return success;
+  }
+  return false;
+}
+
 #endif  // PERFETTO_BUILDFLAG(PERFETTO_OS_FUCHSIA)
 
 }  // namespace base
diff --git a/src/base/time.cc b/src/base/time.cc
index a02c9d6..1507916 100644
--- a/src/base/time.cc
+++ b/src/base/time.cc
@@ -18,6 +18,7 @@
 
 #include "perfetto/base/build_config.h"
 #include "perfetto/base/logging.h"
+#include "perfetto/ext/base/string_utils.h"
 
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
 #include <Windows.h>
@@ -78,5 +79,17 @@
   return buf;
 }
 
+std::optional<int32_t> GetTimezoneOffsetMins() {
+  std::string tz = GetTimeFmt("%z");
+  if (tz.size() != 5 || (tz[0] != '+' && tz[0] != '-'))
+    return std::nullopt;
+  char sign = '\0';
+  int32_t hh = 0;
+  int32_t mm = 0;
+  if (sscanf(tz.c_str(), "%c%2d%2d", &sign, &hh, &mm) != 3)
+    return std::nullopt;
+  return (hh * 60 + mm) * (sign == '-' ? -1 : 1);
+}
+
 }  // namespace base
 }  // namespace perfetto
diff --git a/src/base/time_unittest.cc b/src/base/time_unittest.cc
index 62c8566..a5b9d78 100644
--- a/src/base/time_unittest.cc
+++ b/src/base/time_unittest.cc
@@ -16,6 +16,7 @@
 
 #include "perfetto/base/time.h"
 
+#include "perfetto/ext/base/utils.h"
 #include "test/gtest_and_gmock.h"
 
 namespace perfetto {
@@ -72,6 +73,32 @@
   EXPECT_LE(elapsed_cputime.count(), 50 * ns_in_ms);
 }
 
+// This test can work only on Posix platforms which respect the TZ env var.
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) ||   \
+    PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
+    PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
+TEST(TimeTest, GetTimezoneOffsetMins) {
+  const char* tz = getenv("TZ");
+  std::string tz_save(tz ? tz : "");
+  auto reset_tz_on_exit = OnScopeExit([&] {
+    if (!tz_save.empty())
+      base::SetEnv("TZ", tz_save.c_str());
+  });
+
+  // Note: the sign is reversed in the semantic of the TZ env var.
+  // UTC+2 means "2 hours to reach UTC", not "2 hours ahead of UTC".
+
+  base::SetEnv("TZ", "UTC+2");
+  EXPECT_EQ(GetTimezoneOffsetMins(), -2 * 60);
+
+  base::SetEnv("TZ", "UTC-2");
+  EXPECT_EQ(GetTimezoneOffsetMins(), 2 * 60);
+
+  base::SetEnv("TZ", "UTC-07:45");
+  EXPECT_EQ(GetTimezoneOffsetMins(), 7 * 60 + 45);
+}
+#endif
+
 }  // namespace
 }  // namespace base
 }  // namespace perfetto
diff --git a/src/perfetto_cmd/perfetto_cmd.cc b/src/perfetto_cmd/perfetto_cmd.cc
index 2ddbf04..323d7bc 100644
--- a/src/perfetto_cmd/perfetto_cmd.cc
+++ b/src/perfetto_cmd/perfetto_cmd.cc
@@ -236,8 +236,9 @@
                              (e.g., file.0, file.1, file.2).
   --txt                    : Parse config as pbtxt. Not for production use.
                              Not a stable API.
-  --query                  : Queries the service state and prints it as
-                             human-readable text.
+  --query [--long]         : Queries the service state and prints it as
+                             human-readable text. --long allows the output to
+                             extend past 80 chars.
   --query-raw              : Like --query, but prints raw proto-encoded bytes
                              of tracing_service_state.proto.
   --help           -h
@@ -298,6 +299,7 @@
     OPT_IS_DETACHED,
     OPT_STOP,
     OPT_QUERY,
+    OPT_LONG,
     OPT_QUERY_RAW,
     OPT_VERSION,
   };
@@ -326,6 +328,7 @@
       {"is_detached", required_argument, nullptr, OPT_IS_DETACHED},
       {"stop", no_argument, nullptr, OPT_STOP},
       {"query", no_argument, nullptr, OPT_QUERY},
+      {"long", no_argument, nullptr, OPT_LONG},
       {"query-raw", no_argument, nullptr, OPT_QUERY_RAW},
       {"version", no_argument, nullptr, OPT_VERSION},
       {"save-for-bugreport", no_argument, nullptr, OPT_BUGREPORT},
@@ -520,6 +523,11 @@
       continue;
     }
 
+    if (option == OPT_LONG) {
+      query_service_long_ = true;
+      continue;
+    }
+
     if (option == OPT_QUERY_RAW) {
       query_service_ = true;
       query_service_output_raw_ = true;
@@ -550,6 +558,11 @@
     return 1;
   }
 
+  if (query_service_long_ && !query_service_) {
+    PERFETTO_ELOG("--long can only be used with --query");
+    return 1;
+  }
+
   if (is_detach() && is_attach()) {
     PERFETTO_ELOG("--attach and --detach are mutually exclusive");
     return 1;
@@ -1396,15 +1409,17 @@
       }
     }
 
-    printf("%-40s %-40s ", ds.ds_descriptor().name().c_str(),
+    printf("%-40s %-28s ", ds.ds_descriptor().name().c_str(),
            producer_id_and_name);
     // Print the category names for clients using the track event SDK.
+    std::string cats;
     if (!ds.ds_descriptor().track_event_descriptor_raw().empty()) {
       const std::string& raw = ds.ds_descriptor().track_event_descriptor_raw();
       protos::gen::TrackEventDescriptor desc;
       if (desc.ParseFromArray(raw.data(), raw.size())) {
         for (const auto& cat : desc.available_categories()) {
-          printf("%s,", cat.name().c_str());
+          cats.append(cats.empty() ? "" : ",");
+          cats.append(cat.name());
         }
       }
     } else if (!ds.ds_descriptor().ftrace_descriptor_raw().empty()) {
@@ -1412,11 +1427,17 @@
       protos::gen::FtraceDescriptor desc;
       if (desc.ParseFromArray(raw.data(), raw.size())) {
         for (const auto& cat : desc.atrace_categories()) {
-          printf("%s,", cat.name().c_str());
+          cats.append(cats.empty() ? "" : ",");
+          cats.append(cat.name());
         }
       }
     }
-    printf("\n");
+    const size_t kCatsShortLen = 40;
+    if (!query_service_long_ && cats.length() > kCatsShortLen) {
+      cats = cats.substr(0, kCatsShortLen);
+      cats.append("... (use --long to expand)");
+    }
+    printf("%s\n", cats.c_str());
   }  // for data_sources()
 
   if (svc_state.supports_tracing_sessions()) {
diff --git a/src/perfetto_cmd/perfetto_cmd.h b/src/perfetto_cmd/perfetto_cmd.h
index b55cbc8..7ac090f 100644
--- a/src/perfetto_cmd/perfetto_cmd.h
+++ b/src/perfetto_cmd/perfetto_cmd.h
@@ -153,6 +153,7 @@
   bool redetach_once_attached_ = false;
   bool query_service_ = false;
   bool query_service_output_raw_ = false;
+  bool query_service_long_ = false;
   bool bugreport_ = false;
   bool background_ = false;
   bool background_wait_ = false;
diff --git a/src/profiling/symbolizer/symbolize_database.cc b/src/profiling/symbolizer/symbolize_database.cc
index 36c0e0d..224874b 100644
--- a/src/profiling/symbolizer/symbolize_database.cc
+++ b/src/profiling/symbolizer/symbolize_database.cc
@@ -148,8 +148,14 @@
 
 std::vector<std::string> GetPerfettoBinaryPath() {
   const char* root = getenv("PERFETTO_BINARY_PATH");
-  if (root != nullptr)
-    return base::SplitString(root, ":");
+  if (root != nullptr) {
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+    const char* delimiter = ";";
+#else
+    const char* delimiter = ":";
+#endif
+    return base::SplitString(root, delimiter);
+  }
   return {};
 }
 
diff --git a/src/protozero/protoc_plugin/protozero_plugin.cc b/src/protozero/protoc_plugin/protozero_plugin.cc
index 55c3c9d..e4c691f 100644
--- a/src/protozero/protoc_plugin/protozero_plugin.cc
+++ b/src/protozero/protoc_plugin/protozero_plugin.cc
@@ -108,6 +108,8 @@
   void SetOption(const std::string& name, const std::string& value) {
     if (name == "wrapper_namespace") {
       wrapper_namespace_ = value;
+    } else if (name == "sdk") {
+      sdk_mode_ = (value == "true" || value == "1");
     } else {
       Abort(std::string() + "Unknown plugin option '" + name + "'.");
     }
@@ -468,27 +470,37 @@
         "#ifndef $guard$\n"
         "#define $guard$\n\n"
         "#include <stddef.h>\n"
-        "#include <stdint.h>\n\n"
-        "#include \"perfetto/protozero/field_writer.h\"\n"
-        "#include \"perfetto/protozero/message.h\"\n"
-        "#include \"perfetto/protozero/packed_repeated_fields.h\"\n"
-        "#include \"perfetto/protozero/proto_decoder.h\"\n"
-        "#include \"perfetto/protozero/proto_utils.h\"\n",
+        "#include <stdint.h>\n\n",
         "greeting", greeting, "guard", guard);
 
-    // Print includes for public imports.
-    for (const FileDescriptor* dependency : public_imports_) {
-      // Dependency name could contain slashes but importing from upper-level
-      // directories is not possible anyway since build system processes each
-      // proto file individually. Hence proto lookup path is always equal to the
-      // directory where particular proto file is located and protoc does not
-      // allow reference to upper directory (aka ..) in import path.
-      //
-      // Laconically said:
-      // - source_->name() may never have slashes,
-      // - dependency->name() may have slashes but always refers to inner path.
-      stub_h_->Print("#include \"$name$.h\"\n", "name",
-                     ProtoStubName(dependency));
+    if (sdk_mode_) {
+      stub_h_->Print("#include \"perfetto.h\"\n");
+    } else {
+      stub_h_->Print(
+          "#include \"perfetto/protozero/field_writer.h\"\n"
+          "#include \"perfetto/protozero/message.h\"\n"
+          "#include \"perfetto/protozero/packed_repeated_fields.h\"\n"
+          "#include \"perfetto/protozero/proto_decoder.h\"\n"
+          "#include \"perfetto/protozero/proto_utils.h\"\n");
+    }
+
+    // Print includes for public imports. In sdk mode, all imports are assumed
+    // to be part of the sdk.
+    if (!sdk_mode_) {
+      for (const FileDescriptor* dependency : public_imports_) {
+        // Dependency name could contain slashes but importing from upper-level
+        // directories is not possible anyway since build system processes each
+        // proto file individually. Hence proto lookup path is always equal to
+        // the directory where particular proto file is located and protoc does
+        // not allow reference to upper directory (aka ..) in import path.
+        //
+        // Laconically said:
+        // - source_->name() may never have slashes,
+        // - dependency->name() may have slashes but always refers to inner
+        // path.
+        stub_h_->Print("#include \"$name$.h\"\n", "name",
+                       ProtoStubName(dependency));
+      }
     }
     stub_h_->Print("\n");
 
@@ -1003,6 +1015,9 @@
   std::vector<const EnumDescriptor*> enums_;
   std::map<std::string, std::vector<const FieldDescriptor*>> extensions_;
 
+  // Generate headers that can be used with the Perfetto SDK.
+  bool sdk_mode_ = false;
+
   // The custom *Comp comparators are to ensure determinism of the generator.
   std::set<const FileDescriptor*, FileDescriptorComp> public_imports_;
   std::set<const FileDescriptor*, FileDescriptorComp> private_imports_;
diff --git a/src/trace_processor/db/query_executor.cc b/src/trace_processor/db/query_executor.cc
index 9193349..b83118d 100644
--- a/src/trace_processor/db/query_executor.cc
+++ b/src/trace_processor/db/query_executor.cc
@@ -28,6 +28,7 @@
 #include "src/trace_processor/db/overlays/storage_overlay.h"
 #include "src/trace_processor/db/overlays/types.h"
 #include "src/trace_processor/db/query_executor.h"
+#include "src/trace_processor/db/storage/dummy_storage.h"
 #include "src/trace_processor/db/storage/id_storage.h"
 #include "src/trace_processor/db/storage/numeric_storage.h"
 #include "src/trace_processor/db/storage/string_storage.h"
@@ -282,9 +283,6 @@
     use_legacy =
         use_legacy || (col.IsSorted() && col.col_type() == ColumnType::kString);
 
-    // Column types
-    use_legacy = use_legacy || col.col_type() == ColumnType::kDummy;
-
     // Mismatched types
     use_legacy = use_legacy || (overlays::FilterOpToOverlayOp(c.op) ==
                                     overlays::OverlayOp::kOther &&
@@ -311,17 +309,26 @@
 
     // Create storage
     std::unique_ptr<Storage> storage;
-    if (col.IsId()) {
-      storage.reset(new storage::IdStorage(column_size));
-    } else if (col.col_type() == ColumnType::kString) {
-      storage.reset(new storage::StringStorage(
-          table->string_pool(),
-          static_cast<const StringPool::Id*>(col.storage_base().data()),
-          col.storage_base().non_null_size()));
-    } else {
-      storage.reset(new storage::NumericStorage(
-          col.storage_base().data(), col.storage_base().non_null_size(),
-          col.col_type(), col.IsSorted()));
+    switch (col.col_type()) {
+      case ColumnType::kDummy:
+        storage.reset(new storage::DummyStorage());
+        break;
+      case ColumnType::kId:
+        storage.reset(new storage::IdStorage(column_size));
+        break;
+      case ColumnType::kString:
+        storage.reset(new storage::StringStorage(
+            table->string_pool(),
+            static_cast<const StringPool::Id*>(col.storage_base().data()),
+            col.storage_base().non_null_size()));
+        break;
+      case ColumnType::kInt64:
+      case ColumnType::kUint32:
+      case ColumnType::kInt32:
+      case ColumnType::kDouble:
+        storage.reset(new storage::NumericStorage(
+            col.storage_base().data(), col.storage_base().non_null_size(),
+            col.col_type(), col.IsSorted()));
     }
     s_col.storage = storage.get();
 
diff --git a/src/trace_processor/db/storage/BUILD.gn b/src/trace_processor/db/storage/BUILD.gn
index f27c9db..152cb71 100644
--- a/src/trace_processor/db/storage/BUILD.gn
+++ b/src/trace_processor/db/storage/BUILD.gn
@@ -16,6 +16,8 @@
 
 source_set("storage") {
   sources = [
+    "dummy_storage.cc",
+    "dummy_storage.h",
     "id_storage.cc",
     "id_storage.h",
     "numeric_storage.cc",
diff --git a/src/trace_processor/db/storage/dummy_storage.cc b/src/trace_processor/db/storage/dummy_storage.cc
new file mode 100644
index 0000000..a65a9ae
--- /dev/null
+++ b/src/trace_processor/db/storage/dummy_storage.cc
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+
+#include "src/trace_processor/db/storage/dummy_storage.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace storage {
+
+RangeOrBitVector DummyStorage::Search(FilterOp, SqlValue, RowMap::Range) const {
+  PERFETTO_FATAL("Shouldn't be called");
+}
+
+RangeOrBitVector DummyStorage::IndexSearch(FilterOp,
+                                           SqlValue,
+                                           uint32_t*,
+                                           uint32_t,
+                                           bool) const {
+  PERFETTO_FATAL("Shouldn't be called");
+}
+
+void DummyStorage::StableSort(uint32_t*, uint32_t) const {
+  PERFETTO_FATAL("Shouldn't be called");
+}
+
+void DummyStorage::Sort(uint32_t*, uint32_t) const {
+  PERFETTO_FATAL("Shouldn't be called");
+}
+
+uint32_t DummyStorage::size() const {
+  return 0;
+}
+
+}  // namespace storage
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/db/storage/dummy_storage.h b/src/trace_processor/db/storage/dummy_storage.h
new file mode 100644
index 0000000..511383d
--- /dev/null
+++ b/src/trace_processor/db/storage/dummy_storage.h
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+#ifndef SRC_TRACE_PROCESSOR_DB_STORAGE_DUMMY_STORAGE_H_
+#define SRC_TRACE_PROCESSOR_DB_STORAGE_DUMMY_STORAGE_H_
+
+#include <variant>
+
+#include "perfetto/base/logging.h"
+#include "src/trace_processor/containers/row_map.h"
+#include "src/trace_processor/db/storage/storage.h"
+#include "src/trace_processor/db/storage/types.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace storage {
+
+// Dummy storage. Used for columns that are not supposed to have operations done
+// on them.
+class DummyStorage final : public Storage {
+ public:
+  DummyStorage() = default;
+
+  RangeOrBitVector Search(FilterOp, SqlValue, RowMap::Range) const override;
+
+  RangeOrBitVector IndexSearch(FilterOp,
+                               SqlValue,
+                               uint32_t*,
+                               uint32_t,
+                               bool) const override;
+
+  void StableSort(uint32_t*, uint32_t) const override;
+
+  void Sort(uint32_t*, uint32_t) const override;
+
+  uint32_t size() const override;
+};
+
+}  // namespace storage
+}  // namespace trace_processor
+}  // namespace perfetto
+#endif  // SRC_TRACE_PROCESSOR_DB_STORAGE_DUMMY_STORAGE_H_
diff --git a/src/trace_processor/db/storage/numeric_storage.cc b/src/trace_processor/db/storage/numeric_storage.cc
index 31f1245..50452ae 100644
--- a/src/trace_processor/db/storage/numeric_storage.cc
+++ b/src/trace_processor/db/storage/numeric_storage.cc
@@ -200,26 +200,6 @@
 RangeOrBitVector NumericStorage::Search(FilterOp op,
                                         SqlValue value,
                                         RowMap::Range range) const {
-  if (is_sorted_)
-    return RangeOrBitVector(BinarySearchIntrinsic(op, value, range));
-  return RangeOrBitVector(LinearSearch(op, value, range));
-}
-
-RangeOrBitVector NumericStorage::IndexSearch(FilterOp op,
-                                             SqlValue value,
-                                             uint32_t* indices,
-                                             uint32_t indices_count,
-                                             bool sorted) const {
-  if (sorted) {
-    return RangeOrBitVector(
-        BinarySearchExtrinsic(op, value, indices, indices_count));
-  }
-  return RangeOrBitVector(IndexSearch(op, value, indices, indices_count));
-}
-
-BitVector NumericStorage::LinearSearch(FilterOp op,
-                                       SqlValue sql_val,
-                                       RowMap::Range range) const {
   PERFETTO_TP_TRACE(metatrace::Category::DB, "NumericStorage::LinearSearch",
                     [&range, op](metatrace::Record* r) {
                       r->AddArg("Start", std::to_string(range.start));
@@ -227,7 +207,33 @@
                       r->AddArg("Op",
                                 std::to_string(static_cast<uint32_t>(op)));
                     });
+  if (is_sorted_)
+    return RangeOrBitVector(BinarySearchIntrinsic(op, value, range));
+  return RangeOrBitVector(LinearSearchInternal(op, value, range));
+}
 
+RangeOrBitVector NumericStorage::IndexSearch(FilterOp op,
+                                             SqlValue value,
+                                             uint32_t* indices,
+                                             uint32_t indices_count,
+                                             bool sorted) const {
+  PERFETTO_TP_TRACE(metatrace::Category::DB, "NumericStorage::IndexSearch",
+                    [indices_count, op](metatrace::Record* r) {
+                      r->AddArg("Count", std::to_string(indices_count));
+                      r->AddArg("Op",
+                                std::to_string(static_cast<uint32_t>(op)));
+                    });
+  if (sorted) {
+    return RangeOrBitVector(
+        BinarySearchExtrinsic(op, value, indices, indices_count));
+  }
+  return RangeOrBitVector(
+      IndexSearchInternal(op, value, indices, indices_count));
+}
+
+BitVector NumericStorage::LinearSearchInternal(FilterOp op,
+                                               SqlValue sql_val,
+                                               RowMap::Range range) const {
   std::optional<NumericValue> val = GetNumericTypeVariant(type_, sql_val);
   if (op == FilterOp::kIsNotNull)
     return BitVector(size(), true);
@@ -254,17 +260,10 @@
   return std::move(builder).Build();
 }
 
-BitVector NumericStorage::IndexSearch(FilterOp op,
-                                      SqlValue sql_val,
-                                      uint32_t* indices,
-                                      uint32_t indices_count) const {
-  PERFETTO_TP_TRACE(metatrace::Category::DB, "NumericStorage::IndexSearch",
-                    [indices_count, op](metatrace::Record* r) {
-                      r->AddArg("Count", std::to_string(indices_count));
-                      r->AddArg("Op",
-                                std::to_string(static_cast<uint32_t>(op)));
-                    });
-
+BitVector NumericStorage::IndexSearchInternal(FilterOp op,
+                                              SqlValue sql_val,
+                                              uint32_t* indices,
+                                              uint32_t indices_count) const {
   std::optional<NumericValue> val = GetNumericTypeVariant(type_, sql_val);
   if (op == FilterOp::kIsNotNull)
     return BitVector(indices_count, true);
diff --git a/src/trace_processor/db/storage/numeric_storage.h b/src/trace_processor/db/storage/numeric_storage.h
index a746142..c67fe0e 100644
--- a/src/trace_processor/db/storage/numeric_storage.h
+++ b/src/trace_processor/db/storage/numeric_storage.h
@@ -51,12 +51,14 @@
   uint32_t size() const override { return size_; }
 
  private:
-  BitVector LinearSearch(FilterOp op, SqlValue val, RowMap::Range) const;
+  BitVector LinearSearchInternal(FilterOp op,
+                                 SqlValue val,
+                                 RowMap::Range) const;
 
-  BitVector IndexSearch(FilterOp op,
-                        SqlValue value,
-                        uint32_t* indices,
-                        uint32_t indices_count) const;
+  BitVector IndexSearchInternal(FilterOp op,
+                                SqlValue value,
+                                uint32_t* indices,
+                                uint32_t indices_count) const;
 
   RowMap::Range BinarySearchIntrinsic(FilterOp op,
                                       SqlValue val,
@@ -66,6 +68,7 @@
                                       SqlValue val,
                                       uint32_t* indices,
                                       uint32_t indices_count) const;
+
   const ColumnType type_ = ColumnType::kDummy;
   const void* data_ = nullptr;
   const uint32_t size_ = 0;
diff --git a/src/trace_processor/importers/common/trace_parser.cc b/src/trace_processor/importers/common/trace_parser.cc
index d075a57..6c586e0 100644
--- a/src/trace_processor/importers/common/trace_parser.cc
+++ b/src/trace_processor/importers/common/trace_parser.cc
@@ -16,6 +16,7 @@
 
 #include "src/trace_processor/importers/common/trace_parser.h"
 
+#include "perfetto/trace_processor/trace_blob_view.h"
 #include "src/trace_processor/importers/common/parser_types.h"
 #include "src/trace_processor/importers/fuchsia/fuchsia_record.h"
 #include "src/trace_processor/importers/systrace/systrace_line.h"
@@ -23,6 +24,9 @@
 namespace perfetto {
 namespace trace_processor {
 
+void TraceParser::ParseTraceBlobView(int64_t, TraceBlobView) {
+  PERFETTO_FATAL("Wrong parser type");
+}
 void TraceParser::ParseTracePacket(int64_t, TracePacketData) {
   PERFETTO_FATAL("Wrong parser type");
 }
diff --git a/src/trace_processor/importers/common/trace_parser.h b/src/trace_processor/importers/common/trace_parser.h
index 7766ef3..d948d27 100644
--- a/src/trace_processor/importers/common/trace_parser.h
+++ b/src/trace_processor/importers/common/trace_parser.h
@@ -24,6 +24,7 @@
 namespace trace_processor {
 
 class PacketSequenceStateGeneration;
+class TraceBlobView;
 struct InlineSchedSwitch;
 class FuchsiaRecord;
 struct SystraceLine;
@@ -35,6 +36,7 @@
  public:
   virtual ~TraceParser();
 
+  virtual void ParseTraceBlobView(int64_t, TraceBlobView);
   virtual void ParseTracePacket(int64_t, TracePacketData);
   virtual void ParseJsonPacket(int64_t, std::string);
   virtual void ParseFuchsiaRecord(int64_t, FuchsiaRecord);
diff --git a/src/trace_processor/importers/proto/proto_trace_reader.cc b/src/trace_processor/importers/proto/proto_trace_reader.cc
index 003f06c..8add6f0 100644
--- a/src/trace_processor/importers/proto/proto_trace_reader.cc
+++ b/src/trace_processor/importers/proto/proto_trace_reader.cc
@@ -447,6 +447,8 @@
         metadata::all_data_source_started_ns, Variadic::Integer(ts));
   }
   if (tse.all_data_sources_flushed()) {
+    context_->metadata_tracker->AppendMetadata(
+        metadata::all_data_source_flushed_ns, Variadic::Integer(ts));
     context_->sorter->NotifyFlushEvent();
   }
   if (tse.read_tracing_buffers_completed()) {
diff --git a/src/trace_processor/importers/proto/system_probes_parser.cc b/src/trace_processor/importers/proto/system_probes_parser.cc
index da6a2d2..aa46613 100644
--- a/src/trace_processor/importers/proto/system_probes_parser.cc
+++ b/src/trace_processor/importers/proto/system_probes_parser.cc
@@ -312,7 +312,7 @@
     track = context_->track_tracker->InternCpuCounterTrack(
         cpu_times_user_nice_ns_id_, ct.cpu_id());
     context_->event_tracker->PushCounter(
-        ts, static_cast<double>(ct.user_ice_ns()), track);
+        ts, static_cast<double>(ct.user_nice_ns()), track);
 
     track = context_->track_tracker->InternCpuCounterTrack(
         cpu_times_system_mode_ns_id_, ct.cpu_id());
@@ -603,6 +603,12 @@
                           Variadic::String(machine_id));
   }
 
+  if (packet.has_timezone_off_mins()) {
+    context_->metadata_tracker->SetMetadata(
+        metadata::timezone_off_mins,
+        Variadic::Integer(packet.timezone_off_mins()));
+  }
+
   if (packet.has_android_build_fingerprint()) {
     context_->metadata_tracker->SetMetadata(
         metadata::android_build_fingerprint,
diff --git a/src/trace_processor/metrics/sql/android/BUILD.gn b/src/trace_processor/metrics/sql/android/BUILD.gn
index 6c31943..a2e64da 100644
--- a/src/trace_processor/metrics/sql/android/BUILD.gn
+++ b/src/trace_processor/metrics/sql/android/BUILD.gn
@@ -20,6 +20,7 @@
 perfetto_sql_source_set("android") {
   sources = [
     "android_anr.sql",
+    "ad_services_metric.sql",
     "android_batt.sql",
     "android_binder.sql",
     "android_blocking_calls_cuj_metric.sql",
diff --git a/src/trace_processor/metrics/sql/android/ad_services_metric.sql b/src/trace_processor/metrics/sql/android/ad_services_metric.sql
new file mode 100644
index 0000000..36885fd
--- /dev/null
+++ b/src/trace_processor/metrics/sql/android/ad_services_metric.sql
@@ -0,0 +1,59 @@
+--
+-- 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.
+--
+
+CREATE OR REPLACE PERFETTO FUNCTION GET_EVENT_LATENCY_TABLE(event_name STRING)
+RETURNS TABLE (latency LONG) AS
+SELECT
+  dur / 1e6 as latency
+FROM
+  slices
+WHERE
+  name = $event_name;
+
+DROP VIEW IF EXISTS ad_services_metric_output;
+CREATE VIEW ad_services_metric_output AS
+SELECT
+  AdServicesMetric(
+    'ui_metric',
+    (
+      SELECT
+        RepeatedField(
+          AdServicesUiMetric('latency', latency)
+        )
+      FROM
+        GET_EVENT_LATENCY_TABLE("NotificationTriggerEvent")
+    ),
+    'app_set_id_metric',
+    (
+      SELECT
+        RepeatedField(
+          AdServicesAppSetIdMetric(
+            'latency', latency
+          )
+        )
+      FROM
+        GET_EVENT_LATENCY_TABLE("AdIdCacheEvent")
+    ),
+    'ad_id_metric',
+    (
+      SELECT
+        RepeatedField(
+          AdServicesAdIdMetric('latency', latency)
+        )
+      FROM
+        GET_EVENT_LATENCY_TABLE("AppSetIdEvent")
+    )
+);
diff --git a/src/trace_processor/metrics/sql/android/android_boot.sql b/src/trace_processor/metrics/sql/android/android_boot.sql
index 46caea8..4dc8936 100644
--- a/src/trace_processor/metrics/sql/android/android_boot.sql
+++ b/src/trace_processor/metrics/sql/android/android_boot.sql
@@ -47,5 +47,9 @@
         SELECT NULL_IF_EMPTY(ProcessStateDurations(
             'total_dur', total_dur,
             'uninterruptible_sleep_dur', uint_sleep_dur))
-        FROM get_durations('com.google.android.gms.persistent'))
+        FROM get_durations('com.google.android.gms.persistent')),
+    'launcher_breakdown', (
+        SELECT NULL_IF_EMPTY(AndroidBootMetric_LauncherBreakdown(
+            'cold_start_dur', dur))
+        FROM slice where name="LauncherColdStartup")
 );
diff --git a/src/trace_processor/metrics/sql/chrome/chrome_slice_names.sql b/src/trace_processor/metrics/sql/chrome/chrome_slice_names.sql
index 95ea995..3aa7167 100644
--- a/src/trace_processor/metrics/sql/chrome/chrome_slice_names.sql
+++ b/src/trace_processor/metrics/sql/chrome/chrome_slice_names.sql
@@ -27,6 +27,7 @@
   'slice_name', (
     SELECT RepeatedField(DISTINCT(name))
     FROM slice
+    WHERE name IS NOT NULL
     ORDER BY name
   )
 );
diff --git a/src/trace_processor/metrics/sql/chrome/gesture_jank.sql b/src/trace_processor/metrics/sql/chrome/gesture_jank.sql
index 06343ee..34674a8 100644
--- a/src/trace_processor/metrics/sql/chrome/gesture_jank.sql
+++ b/src/trace_processor/metrics/sql/chrome/gesture_jank.sql
@@ -206,10 +206,10 @@
 CREATE VIEW {{prefix}}_jank_maybe_null_prev_and_next AS
 SELECT
   *,
-  is_janky_frame({{id_field}}, prev_{{id_field}},
+  internal_is_janky_frame({{id_field}}, prev_{{id_field}},
     prev_ts, begin_ts, maybe_gesture_end,
     gesture_frames_exact, prev_gesture_frames_exact) AS prev_jank,
-  is_janky_frame({{id_field}}, next_{{id_field}},
+  internal_is_janky_frame({{id_field}}, next_{{id_field}},
     next_ts, begin_ts, maybe_gesture_end,
     gesture_frames_exact, next_gesture_frames_exact) AS next_jank
 FROM {{prefix}}_jank_maybe_null_prev_and_next_without_precompute
@@ -233,7 +233,7 @@
   (next_jank IS NOT NULL AND next_jank)
   OR (prev_jank IS NOT NULL AND prev_jank)
   AS jank,
-  jank_budget(gesture_frames_exact, prev_gesture_frames_exact,
+  internal_jank_budget(gesture_frames_exact, prev_gesture_frames_exact,
     next_gesture_frames_exact) * avg_vsync_interval AS jank_budget,
   *
 FROM {{prefix}}_jank_maybe_null_prev_and_next
diff --git a/src/trace_processor/metrics/sql/chrome/scroll_jank_cause_queuing_delay.sql b/src/trace_processor/metrics/sql/chrome/scroll_jank_cause_queuing_delay.sql
index 9257d84..6c246e8 100644
--- a/src/trace_processor/metrics/sql/chrome/scroll_jank_cause_queuing_delay.sql
+++ b/src/trace_processor/metrics/sql/chrome/scroll_jank_cause_queuing_delay.sql
@@ -357,6 +357,8 @@
     "TabGroupUiToolbarView"
   WHEN $name GLOB "*TabGridThumbnailView*" THEN
     "TabGridThumbnailView"
+  WHEN $name GLOB "*TabThumbnailView" THEN
+    "TabThumbnailView"
   WHEN $name GLOB "*TabGridDialogView*" THEN
     "TabGridDialogView"
   WHEN $name GLOB "*BottomContainer*" THEN
diff --git a/src/trace_processor/metrics/sql/trace_metadata.sql b/src/trace_processor/metrics/sql/trace_metadata.sql
index c3cdf88..317b7ab 100644
--- a/src/trace_processor/metrics/sql/trace_metadata.sql
+++ b/src/trace_processor/metrics/sql/trace_metadata.sql
@@ -57,5 +57,9 @@
   ),
   'sched_duration_ns', (
     SELECT MAX(ts) - MIN(ts) FROM sched
+  ),
+  'tracing_started_ns', (
+    SELECT int_value FROM metadata
+    WHERE name='tracing_started_ns'
   )
 );
diff --git a/src/trace_processor/perfetto_sql/engine/created_function.cc b/src/trace_processor/perfetto_sql/engine/created_function.cc
index 3e88ada..162dbc6 100644
--- a/src/trace_processor/perfetto_sql/engine/created_function.cc
+++ b/src/trace_processor/perfetto_sql/engine/created_function.cc
@@ -37,7 +37,7 @@
 
 base::Status CheckNoMoreRows(sqlite3_stmt* stmt,
                              sqlite3* db,
-                             const Prototype& prototype) {
+                             const FunctionPrototype& prototype) {
   int ret = sqlite3_step(stmt);
   RETURN_IF_ERROR(SqliteRetToStatus(db, prototype.function_name, ret));
   if (ret == SQLITE_ROW) {
@@ -54,9 +54,10 @@
 // Note: if the returned type is string / bytes, it will be invalidated by the
 // next call to SQLite, so the caller must take care to either copy or use the
 // value before calling SQLite again.
-base::StatusOr<SqlValue> EvaluateScalarStatement(sqlite3_stmt* stmt,
-                                                 sqlite3* db,
-                                                 const Prototype& prototype) {
+base::StatusOr<SqlValue> EvaluateScalarStatement(
+    sqlite3_stmt* stmt,
+    sqlite3* db,
+    const FunctionPrototype& prototype) {
   int ret = sqlite3_step(stmt);
   RETURN_IF_ERROR(SqliteRetToStatus(db, prototype.function_name, ret));
   if (ret == SQLITE_DONE) {
@@ -89,7 +90,7 @@
 }
 
 base::Status BindArguments(sqlite3_stmt* stmt,
-                           const Prototype& prototype,
+                           const FunctionPrototype& prototype,
                            size_t argc,
                            sqlite3_value** argv) {
   // Bind all the arguments to the appropriate places in the function.
@@ -162,7 +163,7 @@
 
   // Enables memoization.
   // Only functions with a single int argument returning ints are supported.
-  base::Status EnableMemoization(const Prototype& prototype) {
+  base::Status EnableMemoization(const FunctionPrototype& prototype) {
     if (prototype.arguments.size() != 1 ||
         TypeToSqlValueType(prototype.arguments[0].type()) !=
             SqlValue::Type::kLong) {
@@ -277,7 +278,7 @@
  public:
   RecursiveCallUnroller(PerfettoSqlEngine* engine,
                         sqlite3_stmt* stmt,
-                        const Prototype& prototype,
+                        const FunctionPrototype& prototype,
                         Memoizer& memoizer)
       : engine_(engine),
         stmt_(stmt),
@@ -394,7 +395,7 @@
 
   PerfettoSqlEngine* engine_;
   sqlite3_stmt* stmt_;
-  const Prototype& prototype_;
+  const FunctionPrototype& prototype_;
   Memoizer& memoizer_;
 
   // Current state of the evaluation.
@@ -441,8 +442,7 @@
   // Sets the state of the function. Should be called only when the function
   // is invalid (i.e. when it is first created or when the previous statement
   // failed to prepare).
-  void Reset(Prototype prototype,
-             std::string prototype_str,
+  void Reset(FunctionPrototype prototype,
              sql_argument::Type return_type,
              SqlSource sql) {
     // Re-registration of valid functions is not allowed.
@@ -450,7 +450,6 @@
     PERFETTO_DCHECK(stmts_.empty());
 
     prototype_ = std::move(prototype);
-    prototype_str_ = std::move(prototype_str);
     return_type_ = return_type;
     sql_ = std::move(sql);
   }
@@ -541,7 +540,7 @@
 
   PerfettoSqlEngine* engine() const { return engine_; }
 
-  const Prototype& prototype() const { return prototype_; }
+  const FunctionPrototype& prototype() const { return prototype_; }
 
   sql_argument::Type return_type() const { return return_type_; }
 
@@ -553,8 +552,7 @@
 
  private:
   PerfettoSqlEngine* engine_;
-  Prototype prototype_;
-  std::string prototype_str_;
+  FunctionPrototype prototype_;
   sql_argument::Type return_type_;
   std::optional<SqlSource> sql_;
   // Perfetto SQL functions support recursion. Given that each function call in
@@ -680,8 +678,7 @@
 
 base::Status CreatedFunction::ValidateOrPrepare(CreatedFunction::Context* ctx,
                                                 bool replace,
-                                                Prototype prototype,
-                                                std::string prototype_str,
+                                                FunctionPrototype prototype,
                                                 sql_argument::Type return_type,
                                                 std::string return_type_str,
                                                 SqlSource source) {
@@ -694,25 +691,25 @@
     if (state->prototype() != prototype) {
       return base::ErrStatus(
           "CREATE_FUNCTION[prototype=%s]: function prototype changed",
-          prototype_str.c_str());
+          prototype.ToString().c_str());
     }
     if (state->return_type() != return_type) {
       return base::ErrStatus(
           "CREATE_FUNCTION[prototype=%s]: return type changed from %s to %s",
-          prototype_str.c_str(),
+          prototype.ToString().c_str(),
           sql_argument::TypeToHumanFriendlyString(state->return_type()),
           return_type_str.c_str());
     }
     if (state->sql() != source.sql()) {
       return base::ErrStatus(
           "CREATE_FUNCTION[prototype=%s]: function SQL changed from %s to %s",
-          prototype_str.c_str(), state->sql().c_str(), source.sql().c_str());
+          prototype.ToString().c_str(), state->sql().c_str(),
+          source.sql().c_str());
     }
     return base::OkStatus();
   }
 
-  state->Reset(std::move(prototype), std::move(prototype_str), return_type,
-               std::move(source));
+  state->Reset(std::move(prototype), return_type, std::move(source));
 
   // Ideally, we would unregister the function here if the statement prep
   // failed, but SQLite doesn't allow unregistering functions inside active
diff --git a/src/trace_processor/perfetto_sql/engine/created_function.h b/src/trace_processor/perfetto_sql/engine/created_function.h
index 44e23de..743a5ad 100644
--- a/src/trace_processor/perfetto_sql/engine/created_function.h
+++ b/src/trace_processor/perfetto_sql/engine/created_function.h
@@ -52,8 +52,7 @@
   static std::unique_ptr<Context> MakeContext(PerfettoSqlEngine*);
   static base::Status ValidateOrPrepare(Context*,
                                         bool replace,
-                                        Prototype,
-                                        std::string prototype_str,
+                                        FunctionPrototype,
                                         sql_argument::Type return_type,
                                         std::string return_type_str,
                                         SqlSource sql);
diff --git a/src/trace_processor/perfetto_sql/engine/function_util.cc b/src/trace_processor/perfetto_sql/engine/function_util.cc
index dfba021..ae3edde 100644
--- a/src/trace_processor/perfetto_sql/engine/function_util.cc
+++ b/src/trace_processor/perfetto_sql/engine/function_util.cc
@@ -24,6 +24,10 @@
 namespace perfetto {
 namespace trace_processor {
 
+std::string FunctionPrototype::ToString() const {
+  return function_name + "(" + SerializeArguments(arguments) + ")";
+}
+
 base::Status ParseFunctionName(base::StringView raw, base::StringView& out) {
   size_t function_name_end = raw.find('(');
   if (function_name_end == base::StringView::npos)
@@ -38,7 +42,7 @@
   return base::OkStatus();
 }
 
-base::Status ParsePrototype(base::StringView raw, Prototype& out) {
+base::Status ParsePrototype(base::StringView raw, FunctionPrototype& out) {
   // Examples of function prototypes:
   // ANDROID_SDK_LEVEL()
   // STARTUP_SLICE(dur_ns INT)
diff --git a/src/trace_processor/perfetto_sql/engine/function_util.h b/src/trace_processor/perfetto_sql/engine/function_util.h
index aa3515f..1467e7e 100644
--- a/src/trace_processor/perfetto_sql/engine/function_util.h
+++ b/src/trace_processor/perfetto_sql/engine/function_util.h
@@ -28,20 +28,24 @@
 namespace perfetto {
 namespace trace_processor {
 
-struct Prototype {
+struct FunctionPrototype {
   std::string function_name;
   std::vector<sql_argument::ArgumentDefinition> arguments;
 
-  bool operator==(const Prototype& other) const {
+  std::string ToString() const;
+
+  bool operator==(const FunctionPrototype& other) const {
     return function_name == other.function_name && arguments == other.arguments;
   }
-  bool operator!=(const Prototype& other) const { return !(*this == other); }
+  bool operator!=(const FunctionPrototype& other) const {
+    return !(*this == other);
+  }
 };
 
 base::Status ParseFunctionName(base::StringView raw,
                                base::StringView& function_name);
 
-base::Status ParsePrototype(base::StringView raw, Prototype& out);
+base::Status ParsePrototype(base::StringView raw, FunctionPrototype& out);
 
 base::Status SqliteRetToStatus(sqlite3* db,
                                const std::string& function_name,
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 1efd12d..2f163a0 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc
@@ -236,6 +236,11 @@
       RETURN_IF_ERROR(AddTracebackIfNeeded(
           RegisterRuntimeTable(cst->name, cst->sql), parser.statement_sql()));
       source = RewriteToDummySql(parser.statement_sql());
+    } else if (auto* create_view = std::get_if<PerfettoSqlParser::CreateView>(
+                   &parser.statement())) {
+      RETURN_IF_ERROR(AddTracebackIfNeeded(ExecuteCreateView(*create_view),
+                                           parser.statement_sql()));
+      source = RewriteToDummySql(parser.statement_sql());
     } else if (auto* include = std::get_if<PerfettoSqlParser::Include>(
                    &parser.statement())) {
       RETURN_IF_ERROR(ExecuteInclude(*include, parser));
@@ -317,19 +322,11 @@
   return ExecutionResult{std::move(*res), stats};
 }
 
-base::Status PerfettoSqlEngine::RegisterSqlFunction(bool replace,
-                                                    std::string prototype_str,
-                                                    std::string return_type_str,
-                                                    SqlSource sql) {
-  // Parse all the arguments into a more friendly form.
-  Prototype prototype;
-  base::Status status =
-      ParsePrototype(base::StringView(prototype_str), prototype);
-  if (!status.ok()) {
-    return base::ErrStatus("CREATE PERFETTO FUNCTION[prototype=%s]: %s",
-                           prototype_str.c_str(), status.c_message());
-  }
-
+base::Status PerfettoSqlEngine::RegisterSqlFunction(
+    bool replace,
+    const FunctionPrototype& prototype,
+    std::string return_type_str,
+    SqlSource sql) {
   // Parse the return type into a enum format.
   auto opt_return_type =
       sql_argument::ParseType(base::StringView(return_type_str));
@@ -337,7 +334,7 @@
     return base::ErrStatus(
         "CREATE PERFETTO FUNCTION[prototype=%s, return=%s]: unknown return "
         "type specified",
-        prototype_str.c_str(), return_type_str.c_str());
+        prototype.ToString().c_str(), return_type_str.c_str());
   }
 
   int created_argc = static_cast<int>(prototype.arguments.size());
@@ -356,8 +353,8 @@
         std::move(created_fn_ctx)));
   }
   return CreatedFunction::ValidateOrPrepare(
-      ctx, replace, std::move(prototype), std::move(prototype_str),
-      std::move(*opt_return_type), std::move(return_type_str), std::move(sql));
+      ctx, replace, std::move(prototype), std::move(*opt_return_type),
+      std::move(return_type_str), std::move(sql));
 }
 
 base::Status PerfettoSqlEngine::RegisterRuntimeTable(std::string name,
@@ -434,6 +431,11 @@
       .status();
 }
 
+base::Status PerfettoSqlEngine::ExecuteCreateView(
+    const PerfettoSqlParser::CreateView& create_view) {
+  return Execute(create_view.sql).status();
+}
+
 base::Status PerfettoSqlEngine::EnableSqlFunctionMemoization(
     const std::string& name) {
   constexpr size_t kSupportedArgCount = 1;
@@ -489,27 +491,16 @@
     return RewriteToDummySql(parser.statement_sql());
   }
 
-  RuntimeTableFunction::State state{cf.prototype, cf.sql, {}, {}, std::nullopt};
-  base::StringView function_name;
-  RETURN_IF_ERROR(
-      ParseFunctionName(state.prototype_str.c_str(), function_name));
-
-  // Parse all the arguments into a more friendly form.
-  base::Status status =
-      ParsePrototype(state.prototype_str.c_str(), state.prototype);
-  if (!status.ok()) {
-    return base::ErrStatus("CREATE PERFETTO FUNCTION[prototype=%s]: %s",
-                           state.prototype_str.c_str(), status.c_message());
-  }
+  RuntimeTableFunction::State state{cf.sql, cf.prototype, {}, std::nullopt};
 
   // Parse the return type into a enum format.
-  status =
+  base::Status status =
       sql_argument::ParseArgumentDefinitions(cf.returns, state.return_values);
   if (!status.ok()) {
     return base::ErrStatus(
         "CREATE PERFETTO FUNCTION[prototype=%s, return=%s]: unknown return "
         "type specified",
-        state.prototype_str.c_str(), cf.returns.c_str());
+        state.prototype.ToString().c_str(), cf.returns.c_str());
   }
 
   // Verify that the provided SQL prepares to a statement correctly.
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 29c8206..a57a588 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h
@@ -107,7 +107,7 @@
   // Registers a function with the prototype |prototype| which returns a value
   // of |return_type| and is implemented by executing the SQL statement |sql|.
   base::Status RegisterSqlFunction(bool replace,
-                                   std::string prototype,
+                                   const FunctionPrototype& prototype,
                                    std::string return_type,
                                    SqlSource sql);
 
@@ -152,6 +152,8 @@
   // Registers a SQL-defined trace processor C++ table with SQLite.
   base::Status RegisterRuntimeTable(std::string name, SqlSource sql);
 
+  base::Status ExecuteCreateView(const PerfettoSqlParser::CreateView&);
+
   base::Status ExecuteCreateMacro(const PerfettoSqlParser::CreateMacro&);
 
   std::unique_ptr<QueryCache> query_cache_;
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 d577ea0..141ff51 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
@@ -109,6 +109,22 @@
   ASSERT_TRUE(res.ok());
 }
 
+TEST_F(PerfettoSqlEngineTest, CreatePerfettoViewSmoke) {
+  auto res = engine_.Execute(SqlSource::FromExecuteQuery(
+      "CREATE PERFETTO VIEW foo AS SELECT 42 AS bar"));
+  ASSERT_TRUE(res.ok());
+}
+
+TEST_F(PerfettoSqlEngineTest, CreatePerfettoViewWithSchemaSmoke) {
+  auto res = engine_.Execute(SqlSource::FromExecuteQuery(
+      "CREATE PERFETTO VIEW foo(bar INT) AS SELECT 42 AS bar"));
+  ASSERT_TRUE(res.ok());
+
+  res = engine_.Execute(SqlSource::FromExecuteQuery(
+      "CREATE PERFETTO VIEW foo2(bar INT) AS SELECT 42 AS bar; SELECT 1"));
+  ASSERT_TRUE(res.ok());
+}
+
 TEST_F(PerfettoSqlEngineTest, CreateMacro) {
   auto res_create = engine_.Execute(SqlSource::FromExecuteQuery(
       "CREATE PERFETTO MACRO foo() RETURNS TableOrSubquery AS select 42 AS x"));
diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.cc b/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.cc
index 33fc49b..fb6ddf8 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.cc
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.cc
@@ -85,21 +85,6 @@
                       std::not_fn(IsValidModuleWord)) == packages.end();
 }
 
-std::string SerializeArgs(std::vector<std::pair<SqlSource, SqlSource>> args) {
-  bool comma = false;
-  std::string serialized;
-  for (const auto& [name, type] : args) {
-    if (comma) {
-      serialized.append(", ");
-    }
-    comma = true;
-    serialized.append(name.sql().c_str());
-    serialized.push_back(' ');
-    serialized.append(type.sql().c_str());
-  }
-  return serialized;
-}
-
 }  // namespace
 
 PerfettoSqlParser::PerfettoSqlParser(
@@ -210,7 +195,12 @@
               state == State::kCreateOrReplacePerfetto, *first_non_space_token);
         }
         if (TokenIsSqliteKeyword("table", token)) {
-          return ParseCreatePerfettoTable(*first_non_space_token);
+          return ParseCreatePerfettoTableOrView(*first_non_space_token,
+                                                TableOrView::kTable);
+        }
+        if (TokenIsSqliteKeyword("view", token)) {
+          return ParseCreatePerfettoTableOrView(*first_non_space_token,
+                                                TableOrView::kView);
         }
         if (TokenIsCustomKeyword("macro", token)) {
           return ParseCreatePerfettoMacro(state ==
@@ -244,7 +234,9 @@
   return true;
 }
 
-bool PerfettoSqlParser::ParseCreatePerfettoTable(Token first_non_space_token) {
+bool PerfettoSqlParser::ParseCreatePerfettoTableOrView(
+    Token first_non_space_token,
+    TableOrView table_or_view) {
   Token table_name = tokenizer_.NextNonWhitespace();
   if (table_name.token_type != SqliteTokenType::TK_ID) {
     base::StackString<1024> err("Invalid table name %.*s",
@@ -253,8 +245,19 @@
     return ErrorAtToken(table_name, err.c_str());
   }
   std::string name(table_name.str);
+  std::vector<sql_argument::ArgumentDefinition> schema;
 
   auto token = tokenizer_.NextNonWhitespace();
+
+  // If the next token is a left parenthesis, then the table or view have a
+  // schema.
+  if (token.token_type == SqliteTokenType::TK_LP) {
+    if (!ParseArguments(schema)) {
+      return false;
+    }
+    token = tokenizer_.NextNonWhitespace();
+  }
+
   if (!TokenIsSqliteKeyword("as", token)) {
     base::StackString<1024> err(
         "Expected 'AS' after table_name, received "
@@ -265,7 +268,23 @@
 
   Token first = tokenizer_.NextNonWhitespace();
   Token terminal = tokenizer_.NextTerminal();
-  statement_ = CreateTable{std::move(name), tokenizer_.Substr(first, terminal)};
+  switch (table_or_view) {
+    case TableOrView::kTable:
+      statement_ = CreateTable{std::move(name),
+                               tokenizer_.Substr(first, terminal), schema};
+      break;
+    case TableOrView::kView:
+      SqlSource original_statement =
+          tokenizer_.Substr(first_non_space_token, terminal);
+      SqlSource header = SqlSource::FromTraceProcessorImplementation(
+          "CREATE VIEW " + name + " AS ");
+      SqlSource::Rewriter rewriter(original_statement);
+      tokenizer_.Rewrite(rewriter, first_non_space_token, first, header,
+                         SqliteTokenizer::EndToken::kExclusive);
+      statement_ =
+          CreateView{std::move(name), std::move(rewriter).Build(), schema};
+      break;
+  }
   statement_sql_ = tokenizer_.Substr(first_non_space_token, terminal);
   return true;
 }
@@ -273,7 +292,6 @@
 bool PerfettoSqlParser::ParseCreatePerfettoFunction(
     bool replace,
     Token first_non_space_token) {
-  std::string prototype;
   Token function_name = tokenizer_.NextNonWhitespace();
   if (function_name.token_type != SqliteTokenType::TK_ID) {
     // TODO(lalitm): add a link to create function documentation.
@@ -282,7 +300,6 @@
                                 function_name.str.data());
     return ErrorAtToken(function_name, err.c_str());
   }
-  prototype.append(function_name.str);
 
   // TK_LP == '(' (i.e. left parenthesis).
   if (Token lp = tokenizer_.NextNonWhitespace();
@@ -291,15 +308,11 @@
     return ErrorAtToken(lp, "Malformed function prototype: '(' expected");
   }
 
-  std::vector<Argument> args;
-  if (!ParseArgumentDefinitions(args)) {
+  std::vector<sql_argument::ArgumentDefinition> args;
+  if (!ParseArguments(args)) {
     return false;
   }
 
-  prototype.push_back('(');
-  prototype.append(SerializeArgs(args));
-  prototype.push_back(')');
-
   if (Token returns = tokenizer_.NextNonWhitespace();
       !TokenIsCustomKeyword("returns", returns)) {
     // TODO(lalitm): add a link to create function documentation.
@@ -316,11 +329,11 @@
       return ErrorAtToken(lp, "Malformed table return: '(' expected");
     }
     // Table function return.
-    std::vector<Argument> ret_args;
-    if (!ParseArgumentDefinitions(ret_args)) {
+    std::vector<sql_argument::ArgumentDefinition> ret_args;
+    if (!ParseArguments(ret_args)) {
       return false;
     }
-    ret = SerializeArgs(ret_args);
+    ret = sql_argument::SerializeArguments(ret_args);
   } else if (ret_token.token_type != SqliteTokenType::TK_ID) {
     // TODO(lalitm): add a link to create function documentation.
     return ErrorAtToken(ret_token, "Invalid return type");
@@ -337,8 +350,10 @@
 
   Token first = tokenizer_.NextNonWhitespace();
   Token terminal = tokenizer_.NextTerminal();
-  statement_ = CreateFunction{replace, std::move(prototype), std::move(ret),
-                              tokenizer_.Substr(first, terminal), table_return};
+  statement_ = CreateFunction{
+      replace,
+      FunctionPrototype{std::string(function_name.str), std::move(args)},
+      std::move(ret), tokenizer_.Substr(first, terminal), table_return};
   statement_sql_ = tokenizer_.Substr(first_non_space_token, terminal);
   return true;
 }
@@ -360,10 +375,15 @@
     return ErrorAtToken(lp, "Malformed macro prototype: '(' expected");
   }
 
-  std::vector<Argument> args;
-  if (!ParseArgumentDefinitions(args)) {
+  std::vector<RawArgument> raw_args;
+  std::vector<std::pair<SqlSource, SqlSource>> args;
+  if (!ParseRawArguments(raw_args)) {
     return false;
   }
+  for (const auto& arg : raw_args) {
+    args.emplace_back(tokenizer_.SubstrToken(arg.name),
+                      tokenizer_.SubstrToken(arg.type));
+  }
 
   if (Token returns = tokenizer_.NextNonWhitespace();
       !TokenIsCustomKeyword("returns", returns)) {
@@ -391,7 +411,7 @@
   return true;
 }
 
-bool PerfettoSqlParser::ParseArgumentDefinitions(std::vector<Argument>& res) {
+bool PerfettoSqlParser::ParseRawArguments(std::vector<RawArgument>& args) {
   enum TokenType {
     kIdOrRp,
     kId,
@@ -432,8 +452,7 @@
         return ErrorAtToken(tok, err.c_str());
       }
       PERFETTO_CHECK(id);
-      res.push_back(std::make_pair(tokenizer_.SubstrToken(*id),
-                                   tokenizer_.SubstrToken(tok)));
+      args.push_back({*id, tok});
       id = std::nullopt;
       expected = kCommaOrRp;
       continue;
@@ -457,8 +476,46 @@
   }
 }
 
+bool PerfettoSqlParser::ParseArguments(
+    std::vector<sql_argument::ArgumentDefinition>& args) {
+  std::vector<RawArgument> raw_args;
+  if (!ParseRawArguments(raw_args)) {
+    return false;
+  }
+  for (const auto& raw_arg : raw_args) {
+    std::optional<sql_argument::ArgumentDefinition> arg =
+        ResolveRawArgument(raw_arg);
+    if (!arg) {
+      return false;
+    }
+    args.emplace_back(std::move(*arg));
+  }
+  return true;
+}
+
+std::optional<sql_argument::ArgumentDefinition>
+PerfettoSqlParser::ResolveRawArgument(RawArgument arg) {
+  std::string arg_name = tokenizer_.SubstrToken(arg.name).sql();
+  std::string arg_type = tokenizer_.SubstrToken(arg.type).sql();
+  if (!sql_argument::IsValidName(base::StringView(arg_name))) {
+    base::StackString<1024> err("Name %s is not alphanumeric",
+                                arg_name.c_str());
+    ErrorAtToken(arg.name, err.c_str());
+    return std::nullopt;
+  }
+  std::optional<sql_argument::Type> parsed_arg_type =
+      sql_argument::ParseType(base::StringView(arg_type));
+  if (!parsed_arg_type) {
+    base::StackString<1024> err("Invalid type %s", arg_type.c_str());
+    ErrorAtToken(arg.name, err.c_str());
+    return std::nullopt;
+  }
+  return sql_argument::ArgumentDefinition("$" + arg_name, *parsed_arg_type);
+}
+
 bool PerfettoSqlParser::ErrorAtToken(const SqliteTokenizer::Token& token,
-                                     const char* error) {
+                                     const char* error,
+                                     ...) {
   std::string traceback = tokenizer_.AsTraceback(token);
   status_ = base::ErrStatus("%s%s", traceback.c_str(), error);
   return false;
diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.h b/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.h
index 1c38d43..b8120fb 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.h
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.h
@@ -24,6 +24,7 @@
 #include <variant>
 #include <vector>
 
+#include "function_util.h"
 #include "perfetto/ext/base/flat_hash_map.h"
 #include "perfetto/ext/base/status_or.h"
 #include "src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.h"
@@ -52,7 +53,7 @@
   // with the following parameters.
   struct CreateFunction {
     bool replace;
-    std::string prototype;
+    FunctionPrototype prototype;
     std::string returns;
     SqlSource sql;
     bool is_table;
@@ -61,7 +62,18 @@
   // with the following parameters.
   struct CreateTable {
     std::string name;
+    // SQL source for the select statement.
     SqlSource sql;
+    std::vector<sql_argument::ArgumentDefinition> schema;
+  };
+  // Indicates that the specified SQL was a CREATE PERFETTO VIEW statement
+  // with the following parameters.
+  struct CreateView {
+    std::string name;
+    // SQL source corresponding to the rewritten statement creating the
+    // underlying view.
+    SqlSource sql;
+    std::vector<sql_argument::ArgumentDefinition> schema;
   };
   // Indicates that the specified SQL was a INCLUDE PERFETTO MODULE statement
   // with the following parameter.
@@ -77,8 +89,12 @@
     SqlSource returns;
     SqlSource sql;
   };
-  using Statement = std::
-      variant<SqliteSql, CreateFunction, CreateTable, Include, CreateMacro>;
+  using Statement = std::variant<SqliteSql,
+                                 CreateFunction,
+                                 CreateTable,
+                                 CreateView,
+                                 Include,
+                                 CreateMacro>;
 
   // Creates a new SQL parser with the a block of PerfettoSQL statements.
   // Concretely, the passed string can contain >1 statement.
@@ -114,27 +130,46 @@
   const base::Status& status() const { return status_; }
 
  private:
-  using Argument =
-      std::pair<SqlSource /* name token */, SqlSource /* type token */>;
-
   // This cannot be moved because we keep pointers into |sql_| in
   // |preprocessor_|.
   PerfettoSqlParser(PerfettoSqlParser&&) = delete;
   PerfettoSqlParser& operator=(PerfettoSqlParser&&) = delete;
 
+  // Most of the code needs sql_argument::ArgumentDefinition, but we explcitly
+  // track raw arguments separately, as macro implementations need access to
+  // the underlying tokens.
+  struct RawArgument {
+    SqliteTokenizer::Token name;
+    SqliteTokenizer::Token type;
+  };
+
   bool ParseCreatePerfettoFunction(
       bool replace,
       SqliteTokenizer::Token first_non_space_token);
 
-  bool ParseCreatePerfettoTable(SqliteTokenizer::Token first_non_space_token);
+  enum class TableOrView {
+    kTable,
+    kView,
+  };
+  bool ParseCreatePerfettoTableOrView(
+      SqliteTokenizer::Token first_non_space_token,
+      TableOrView table_or_view);
 
   bool ParseIncludePerfettoModule(SqliteTokenizer::Token first_non_space_token);
 
   bool ParseCreatePerfettoMacro(bool replace);
 
-  bool ParseArgumentDefinitions(std::vector<Argument>&);
+  // Convert a "raw" argument (i.e. one that points to specific tokens) to the
+  // argument definition consumed by the rest of the SQL code.
+  // Guarantees to call ErrorAtToken if std::nullopt is returned.
+  std::optional<sql_argument::ArgumentDefinition> ResolveRawArgument(
+      RawArgument arg);
+  // Parse the arguments in their raw token form.
+  bool ParseRawArguments(std::vector<RawArgument>&);
+  // Same as above, but also convert the raw tokens into argument definitions.
+  bool ParseArguments(std::vector<sql_argument::ArgumentDefinition>&);
 
-  bool ErrorAtToken(const SqliteTokenizer::Token&, const char* error);
+  bool ErrorAtToken(const SqliteTokenizer::Token&, const char* error, ...);
 
   PerfettoSqlPreprocessor preprocessor_;
   SqliteTokenizer tokenizer_;
diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser_unittest.cc b/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser_unittest.cc
index d2391c3..7856964 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser_unittest.cc
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser_unittest.cc
@@ -34,6 +34,7 @@
 using SqliteSql = PerfettoSqlParser::SqliteSql;
 using CreateFn = PerfettoSqlParser::CreateFunction;
 using CreateTable = PerfettoSqlParser::CreateTable;
+using CreateView = PerfettoSqlParser::CreateView;
 using Include = PerfettoSqlParser::Include;
 using CreateMacro = PerfettoSqlParser::CreateMacro;
 
@@ -95,23 +96,39 @@
 TEST_F(PerfettoSqlParserTest, CreatePerfettoFunctionScalar) {
   auto res = SqlSource::FromExecuteQuery(
       "create perfetto function foo() returns INT as select 1");
-  ASSERT_THAT(*Parse(res),
-              testing::ElementsAre(CreateFn{
-                  false, "foo()", "INT", FindSubstr(res, "select 1"), false}));
+  ASSERT_THAT(*Parse(res), testing::ElementsAre(CreateFn{
+                               false, FunctionPrototype{"foo", {}}, "INT",
+                               FindSubstr(res, "select 1"), false}));
 
   res = SqlSource::FromExecuteQuery(
       "create perfetto function bar(x INT, y LONG) returns STRING as "
       "select 'foo'");
-  ASSERT_THAT(*Parse(res), testing::ElementsAre(CreateFn{
-                               false, "bar(x INT, y LONG)", "STRING",
-                               FindSubstr(res, "select 'foo'"), false}));
+  ASSERT_THAT(*Parse(res),
+              testing::ElementsAre(
+                  CreateFn{false,
+                           FunctionPrototype{
+                               "bar",
+                               {
+                                   {"$x", sql_argument::Type::kInt},
+                                   {"$y", sql_argument::Type::kLong},
+                               },
+                           },
+                           "STRING", FindSubstr(res, "select 'foo'"), false}));
 
   res = SqlSource::FromExecuteQuery(
       "CREATE perfetto FuNcTiOn bar(x INT, y LONG) returnS STRING As "
       "select 'foo'");
-  ASSERT_THAT(*Parse(res), testing::ElementsAre(CreateFn{
-                               false, "bar(x INT, y LONG)", "STRING",
-                               FindSubstr(res, "select 'foo'"), false}));
+  ASSERT_THAT(*Parse(res),
+              testing::ElementsAre(
+                  CreateFn{false,
+                           FunctionPrototype{
+                               "bar",
+                               {
+                                   {"$x", sql_argument::Type::kInt},
+                                   {"$y", sql_argument::Type::kLong},
+                               },
+                           },
+                           "STRING", FindSubstr(res, "select 'foo'"), false}));
 }
 
 TEST_F(PerfettoSqlParserTest, CreatePerfettoFunctionScalarError) {
@@ -133,7 +150,8 @@
       "create perfetto function foo() returns INT as select 1; select foo()");
   PerfettoSqlParser parser(res, macros_);
   ASSERT_TRUE(parser.Next());
-  CreateFn fn{false, "foo()", "INT", FindSubstr(res, "select 1"), false};
+  CreateFn fn{false, FunctionPrototype{"foo", {}}, "INT",
+              FindSubstr(res, "select 1"), false};
   ASSERT_EQ(parser.statement(), Statement{fn});
   ASSERT_EQ(
       parser.statement_sql().sql(),
@@ -203,6 +221,90 @@
   ASSERT_FALSE(parser.Next());
 }
 
+TEST_F(PerfettoSqlParserTest, CreatePerfettoTable) {
+  auto res = SqlSource::FromExecuteQuery(
+      "CREATE PERFETTO TABLE foo AS SELECT 42 AS bar");
+  PerfettoSqlParser parser(res, macros_);
+  ASSERT_TRUE(parser.Next());
+  ASSERT_EQ(
+      parser.statement(),
+      Statement(CreateTable{"foo", FindSubstr(res, "SELECT 42 AS bar"), {}}));
+  ASSERT_FALSE(parser.Next());
+}
+
+TEST_F(PerfettoSqlParserTest, CreatePerfettoTableWithSchema) {
+  auto res = SqlSource::FromExecuteQuery(
+      "CREATE PERFETTO TABLE foo(bar INT) AS SELECT 42 AS bar");
+  PerfettoSqlParser parser(res, macros_);
+  ASSERT_TRUE(parser.Next());
+  ASSERT_EQ(parser.statement(), Statement(CreateTable{
+                                    "foo",
+                                    FindSubstr(res, "SELECT 42 AS bar"),
+                                    {{"$bar", sql_argument::Type::kInt}},
+                                }));
+  ASSERT_FALSE(parser.Next());
+}
+
+TEST_F(PerfettoSqlParserTest, CreatePerfettoTableAndOther) {
+  auto res = SqlSource::FromExecuteQuery(
+      "CREATE PERFETTO TABLE foo AS SELECT 42 AS bar; select 1");
+  PerfettoSqlParser parser(res, macros_);
+  ASSERT_TRUE(parser.Next());
+  ASSERT_EQ(
+      parser.statement(),
+      Statement(CreateTable{"foo", FindSubstr(res, "SELECT 42 AS bar"), {}}));
+  ASSERT_TRUE(parser.Next());
+  ASSERT_EQ(parser.statement(), Statement(SqliteSql{}));
+  ASSERT_EQ(parser.statement_sql(), FindSubstr(res, "select 1"));
+  ASSERT_FALSE(parser.Next());
+}
+
+TEST_F(PerfettoSqlParserTest, CreatePerfettoView) {
+  auto res = SqlSource::FromExecuteQuery(
+      "CREATE PERFETTO VIEW foo AS SELECT 42 AS bar");
+  PerfettoSqlParser parser(res, macros_);
+  ASSERT_TRUE(parser.Next());
+  ASSERT_EQ(parser.statement(),
+            Statement(CreateView{"foo",
+                                 SqlSource::FromExecuteQuery(
+                                     "CREATE VIEW foo AS SELECT 42 AS bar"),
+                                 {}}));
+  ASSERT_FALSE(parser.Next());
+}
+
+TEST_F(PerfettoSqlParserTest, CreatePerfettoViewAndOther) {
+  auto res = SqlSource::FromExecuteQuery(
+      "CREATE PERFETTO VIEW foo AS SELECT 42 AS bar; select 1");
+  PerfettoSqlParser parser(res, macros_);
+  ASSERT_TRUE(parser.Next());
+  ASSERT_EQ(parser.statement(),
+            Statement(CreateView{"foo",
+                                 SqlSource::FromExecuteQuery(
+                                     "CREATE VIEW foo AS SELECT 42 AS bar"),
+                                 {}}));
+  ASSERT_TRUE(parser.Next());
+  ASSERT_EQ(parser.statement(), Statement(SqliteSql{}));
+  ASSERT_EQ(parser.statement_sql(), FindSubstr(res, "select 1"));
+  ASSERT_FALSE(parser.Next());
+}
+
+TEST_F(PerfettoSqlParserTest, CreatePerfettoViewWithSchema) {
+  auto res = SqlSource::FromExecuteQuery(
+      "CREATE PERFETTO VIEW foo(foo STRING, bar INT) AS SELECT 'a' as foo, 42 "
+      "AS bar");
+  PerfettoSqlParser parser(res, macros_);
+  ASSERT_TRUE(parser.Next());
+  ASSERT_EQ(parser.statement(),
+            Statement(CreateView{
+                "foo",
+                SqlSource::FromExecuteQuery(
+                    "CREATE VIEW foo AS SELECT 'a' as foo, 42 AS bar"),
+                {{"$foo", sql_argument::Type::kString},
+                 {"$bar", sql_argument::Type::kInt}},
+            }));
+  ASSERT_FALSE(parser.Next());
+}
+
 }  // namespace
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_test_utils.h b/src/trace_processor/perfetto_sql/engine/perfetto_sql_test_utils.h
index caca711..8382fe6 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_test_utils.h
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_test_utils.h
@@ -44,6 +44,11 @@
   return std::tie(a.name, a.sql) == std::tie(b.name, b.sql);
 }
 
+inline bool operator==(const PerfettoSqlParser::CreateView& a,
+                       const PerfettoSqlParser::CreateView& b) {
+  return std::tie(a.name, a.sql) == std::tie(b.name, b.sql);
+}
+
 inline bool operator==(const PerfettoSqlParser::Include& a,
                        const PerfettoSqlParser::Include& b) {
   return std::tie(a.key) == std::tie(b.key);
@@ -75,6 +80,10 @@
     return stream << "CreateTable(name=" << testing::PrintToString(tab->name)
                   << ", sql=" << testing::PrintToString(tab->sql) << ")";
   }
+  if (auto* tab = std::get_if<PerfettoSqlParser::CreateView>(&line)) {
+    return stream << "CreateView(name=" << testing::PrintToString(tab->name)
+                  << ", sql=" << testing::PrintToString(tab->sql) << ")";
+  }
   if (auto* macro = std::get_if<PerfettoSqlParser::CreateMacro>(&line)) {
     return stream << "CreateTable(name=" << testing::PrintToString(macro->name)
                   << ", args=" << testing::PrintToString(macro->args)
diff --git a/src/trace_processor/perfetto_sql/engine/runtime_table_function.h b/src/trace_processor/perfetto_sql/engine/runtime_table_function.h
index 9fe8c6e..7c92fcc 100644
--- a/src/trace_processor/perfetto_sql/engine/runtime_table_function.h
+++ b/src/trace_processor/perfetto_sql/engine/runtime_table_function.h
@@ -36,10 +36,9 @@
   // because |RuntimeTableFunction| is owned by Sqlite while |State| is owned by
   // PerfettoSqlEngine.
   struct State {
-    std::string prototype_str;
     SqlSource sql_defn_str;
 
-    Prototype prototype;
+    FunctionPrototype prototype;
     std::vector<sql_argument::ArgumentDefinition> return_values;
 
     std::optional<SqliteEngine::PreparedStatement> reusable_stmt;
diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/create_function.cc b/src/trace_processor/perfetto_sql/intrinsics/functions/create_function.cc
index ac6bfd0..2bada91 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/functions/create_function.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/functions/create_function.cc
@@ -72,8 +72,12 @@
   std::string prototype_str = extract_string(prototype_value).ToStdString();
   std::string return_type_str = extract_string(return_type_value).ToStdString();
   std::string sql_defn_str = extract_string(sql_defn_value).ToStdString();
+
+  FunctionPrototype prototype;
+  RETURN_IF_ERROR(ParsePrototype(base::StringView(prototype_str), prototype));
+
   return engine->RegisterSqlFunction(
-      false, prototype_str, return_type_str,
+      false, prototype, return_type_str,
       SqlSource::FromTraceProcessorImplementation(std::move(sql_defn_str)));
 }
 
diff --git a/src/trace_processor/perfetto_sql/intrinsics/operators/span_join_operator.cc b/src/trace_processor/perfetto_sql/intrinsics/operators/span_join_operator.cc
index 15b1f45..a279829 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/operators/span_join_operator.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/operators/span_join_operator.cc
@@ -802,6 +802,7 @@
 
 void SpanJoinOperatorTable::Query::ReportSqliteResult(sqlite3_context* context,
                                                       size_t index) {
+  const auto kSqliteTransient = reinterpret_cast<sqlite3_destructor_type>(-1);
   if (state_ != State::kReal) {
     sqlite3_result_null(context);
     return;
@@ -820,12 +821,15 @@
       // TODO(lalitm): note for future optimizations: if we knew the addresses
       // of the string intern pool, we could check if the string returned here
       // comes from the pool, and pass it as non-transient.
-      const auto kSqliteTransient =
-          reinterpret_cast<sqlite3_destructor_type>(-1);
       auto ptr = reinterpret_cast<const char*>(sqlite3_column_text(stmt, idx));
       sqlite3_result_text(context, ptr, -1, kSqliteTransient);
       break;
     }
+    case SQLITE_BLOB: {
+      sqlite3_result_blob(context, sqlite3_column_blob(stmt, idx),
+                          sqlite3_column_bytes(stmt, idx), kSqliteTransient);
+      break;
+    }
   }
 }
 
diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/chrome/BUILD.gn
index cac258f..04a1571 100644
--- a/src/trace_processor/perfetto_sql/stdlib/chrome/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/chrome/BUILD.gn
@@ -20,7 +20,9 @@
     "chrome_scrolls.sql",
     "cpu_powerups.sql",
     "histograms.sql",
+    "interactions.sql",
     "metadata.sql",
+    "page_loads.sql",
     "speedometer.sql",
     "tasks.sql",
     "vsync_intervals.sql",
diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/interactions.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/interactions.sql
new file mode 100644
index 0000000..49ce0c8
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/chrome/interactions.sql
@@ -0,0 +1,46 @@
+-- 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.
+
+-- This file specifies common metrics/tables for critical user interactions. It
+-- is expected to be in flux as metrics are added across different CUI types.
+-- Currently we only track Chrome page loads and their associated metrics.
+
+INCLUDE PERFETTO MODULE chrome.page_loads;
+
+-- All critical user interaction events, including type and table with
+-- associated metrics.
+--
+-- @column scoped_id                 Identifier of the interaction; this is not
+--                                   guaranteed to be unique to the table -
+--                                   rather, it is unique within an individual
+--                                   interaction type. Combine with type to get
+--                                   a unique identifier in this table.
+-- @column type                      Type of this interaction, which together
+--                                   with scoped_id uniquely identifies this
+--                                   interaction. Also corresponds to a SQL
+--                                   table name containing more details specific
+--                                   to this type of interaction.
+-- @column name                      Interaction name - e.g. 'PageLoad', 'Tap',
+--                                   etc. Interactions will have unique metrics
+--                                   stored in other tables.
+-- @column ts                        Timestamp of the CUI event.
+-- @column dur                       Duration of the CUI event.
+CREATE PERFETTO TABLE chrome_interactions AS
+SELECT
+  navigation_id AS scoped_id,
+  'chrome_page_loads' AS type,
+  'PageLoad' AS name,
+  navigation_start_ts AS ts,
+  IFNULL(lcp, fcp) AS dur
+FROM chrome_page_loads;
diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/page_loads.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/page_loads.sql
new file mode 100644
index 0000000..fcc5874
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/chrome/page_loads.sql
@@ -0,0 +1,73 @@
+-- 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.
+
+-- TODO(b/306300843): The recorded navigation ids are not guaranteed to be
+-- unique within a trace; they are only guaranteed to be unique within a single
+-- chrome instance. Chrome instance id needs to be recorded, and used here in
+-- combination with navigation id to uniquely identify page load metrics.
+
+INCLUDE PERFETTO MODULE common.slices;
+
+-- Chrome page loads, including associated high-level metrics and properties.
+--
+-- @column navigation_id             ID of the navigation associated with the
+--                                   page load (i.e. the cross-document
+--                                   navigation in primary main frame which
+--                                   created this page's main document). Also
+--                                   note that navigation_id is specific to a
+--                                   given Chrome browser process, and not
+--                                   globally unique.
+-- @column navigation_start_ts       Timestamp of the start of navigation.
+-- @column fcp                       Duration between the navigation start and
+--                                   the first contentful paint event
+--                                   (web.dev/fcp).
+-- @column fcp_ts                    Timestamp of the first contentful paint.
+-- @column lcp                       Duration between the navigation start and
+--                                   the largest contentful paint event
+--                                   (web.dev/lcp).
+-- @column lcp_ts                    Timestamp of the largest contentful paint.
+-- @column url                       URL at the page load event.
+-- @column browser_upid              The unique process id (upid) of the browser
+--                                   process where the page load occurred.
+CREATE PERFETTO TABLE chrome_page_loads AS
+WITH fcp AS (
+  SELECT
+    ts,
+    dur,
+    EXTRACT_ARG(arg_set_id, 'page_load.navigation_id') AS navigation_id,
+    EXTRACT_ARG(arg_set_id, 'page_load.url') AS url,
+    upid AS browser_upid
+  FROM process_slice
+  WHERE name = 'PageLoadMetrics.NavigationToFirstContentfulPaint'
+),
+lcp AS (
+  SELECT
+    ts,
+    dur,
+    EXTRACT_ARG(arg_set_id, 'page_load.navigation_id')
+      AS navigation_id
+  FROM slice
+  WHERE name = 'PageLoadMetrics.NavigationToLargestContentfulPaint'
+)
+SELECT
+ fcp.navigation_id,
+ fcp.ts AS navigation_start_ts,
+ fcp.dur AS fcp,
+ fcp.ts + fcp.dur AS fcp_ts,
+ lcp.dur AS lcp,
+ IFNULL(lcp.dur, 0) + IFNULL(lcp.ts, 0) AS lcp_ts,
+ fcp.url,
+ fcp.browser_upid
+FROM fcp
+LEFT JOIN lcp USING (navigation_id);
diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_v3.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_v3.sql
index c6b505a..3ba33c5 100644
--- a/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_v3.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_v3.sql
@@ -330,7 +330,7 @@
 CREATE VIEW chrome_janky_frames_no_subcause AS
 SELECT
   *,
-  get_v3_jank_cause_id(event_latency_id, prev_event_latency_id) AS cause_id
+  chrome_get_v3_jank_cause_id(event_latency_id, prev_event_latency_id) AS cause_id
 FROM chrome_janky_frames_no_cause;
 
 -- Finds all causes of jank for all janky frames, and a cause of sub jank
@@ -352,7 +352,7 @@
   slice_name_from_id(cause_id) AS cause_of_jank,
   slice_name_from_id(
     -- Getting sub-cause
-    get_v3_jank_cause_id(
+    chrome_get_v3_jank_cause_id(
       -- Here the cause itself is the parent.
       cause_id,
       -- Get the previous cause id as a child to the previous |EventLatency|.
diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_v3_cause.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_v3_cause.sql
index 164ba7a..60bfca8 100644
--- a/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_v3_cause.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_v3_cause.sql
@@ -29,9 +29,9 @@
 --                          cause among it's children.
 -- @arg prev_slice_id  LONG The slice id of the parent slice that's the reference
 --                          in comparison to |janky_slice_id|.
--- @ret breakdown_id   LONG The slice id of the breakdown that has the maximum
+-- @ret LONG The slice id of the breakdown that has the maximum
 --                          duration delta.
-CREATE PERFETTO FUNCTION get_v3_jank_cause_id(
+CREATE PERFETTO FUNCTION chrome_get_v3_jank_cause_id(
   janky_slice_id LONG,
   prev_slice_id LONG
 )
diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/utils.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/utils.sql
index 44e6e1e..6d89427 100644
--- a/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/utils.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/utils.sql
@@ -28,7 +28,7 @@
 -- Function : function takes scroll ids of frames to verify it's from
 -- the same scroll, and makes sure the frame ts occured within the scroll
 -- timestamp of the neighbour and computes whether the frame was janky or not.
-CREATE PERFETTO FUNCTION is_janky_frame(cur_gesture_id LONG,
+CREATE PERFETTO FUNCTION internal_is_janky_frame(cur_gesture_id LONG,
                                       neighbour_gesture_id LONG,
                                       neighbour_ts LONG,
                                       cur_gesture_begin_ts LONG,
@@ -56,7 +56,7 @@
 --
 -- Returns the jank budget in percentage (i.e. 0.75) of vsync interval
 -- percentage.
-CREATE PERFETTO FUNCTION jank_budget(
+CREATE PERFETTO FUNCTION internal_jank_budget(
   cur_frame_exact FLOAT,
   prev_frame_exact FLOAT,
   next_frame_exact FLOAT
diff --git a/src/trace_processor/perfetto_sql/stdlib/common/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/common/BUILD.gn
index 93ffc3d..bb4d758 100644
--- a/src/trace_processor/perfetto_sql/stdlib/common/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/common/BUILD.gn
@@ -22,6 +22,7 @@
     "metadata.sql",
     "percentiles.sql",
     "slices.sql",
+    "thread_states.sql",
     "timestamps.sql",
   ]
 }
diff --git a/src/trace_processor/perfetto_sql/stdlib/common/thread_states.sql b/src/trace_processor/perfetto_sql/stdlib/common/thread_states.sql
new file mode 100644
index 0000000..2e9dce1
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/common/thread_states.sql
@@ -0,0 +1,111 @@
+--
+-- 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 common.timestamps;
+INCLUDE PERFETTO MODULE common.cpus;
+
+-- TODO(altimin): this doesn't handle some corner cases which thread_state.ts
+-- handles (as complex strings manipulations in SQL are pretty painful),
+-- but they are pretty niche.
+-- Translates the thread state name from a single-letter shorthard to
+-- a human-readable name.
+CREATE PERFETTO FUNCTION internal_translate_thread_state_name(name STRING)
+RETURNS STRING AS
+SELECT CASE $name
+WHEN 'Running' THEN 'Running'
+WHEN 'R' THEN 'Runnable'
+WHEN 'R+' THEN 'Runnable (Preempted)'
+WHEN 'S' THEN 'Sleeping'
+WHEN 'D' THEN 'Uninterruptible Sleep'
+WHEN 'T' THEN 'Stopped'
+WHEN 't' THEN 'Traced'
+WHEN 'X' THEN 'Exit (Dead)'
+WHEN 'Z' THEN 'Exit (Zombie)'
+WHEN 'x' THEN 'Task Dead'
+WHEN 'I' THEN 'Idle'
+WHEN 'K' THEN 'Wakekill'
+WHEN 'W' THEN 'Waking'
+WHEN 'P' THEN 'Parked'
+WHEN 'N' THEN 'No Load'
+ELSE $name
+END;
+
+-- Returns a human-readable name for a thread state.
+-- @arg id INT  Thread state id.
+-- @ret STRING  Human-readable name for the thread state.
+CREATE PERFETTO FUNCTION human_readable_thread_state_name(id INT)
+RETURNS STRING AS
+WITH data AS (
+  SELECT
+    internal_translate_thread_state_name(state) AS state,
+    (CASE io_wait
+      WHEN 1 THEN ' (IO)'
+      WHEN 0 THEN ' (non-IO)'
+      ELSE ''
+    END) AS io_wait
+  FROM thread_state
+  WHERE id = $id
+)
+SELECT
+  printf('%s%s', state, io_wait)
+FROM data;
+
+-- Returns an aggregation of thread states (by state and cpu) for a given
+-- interval of time for a given thread.
+-- @arg ts INT       The start of the interval.
+-- @arg dur INT      The duration of the interval.
+-- @arg utid INT     The utid of the thread.
+-- @column state     Human-readable thread state name.
+-- @column raw_state Raw thread state name, alias of `thread_state.state`.
+-- @column cpu_type  The type of CPU if available (e.g. "big" / "mid" / "little").
+-- @column cpu       The CPU index.
+-- @column blocked_function The name of the kernel function execution is blocked in.
+-- @column dur       The total duration.
+CREATE PERFETTO FUNCTION thread_state_summary_for_interval(
+  ts INT, dur INT, utid INT)
+RETURNS TABLE(
+  state STRING, raw_state STRING, cpu_type STRING, cpu INT, blocked_function STRING, dur INT)
+AS
+WITH
+states_starting_inside AS (
+  SELECT id
+  FROM thread_state
+  WHERE $ts <= ts
+    AND ts <= $ts + $dur
+    AND utid = $utid
+),
+first_state_starting_before AS (
+  SELECT id
+  FROM thread_state
+  WHERE ts < $ts AND utid = $utid
+  ORDER BY ts DESC
+  LIMIT 1
+),
+relevant_states AS (
+  SELECT * FROM states_starting_inside
+  UNION ALL
+  SELECT * FROM first_state_starting_before
+)
+SELECT
+  human_readable_thread_state_name(id) as state,
+  state as raw_state,
+  guess_cpu_size(cpu) as cpu_type,
+  cpu,
+  blocked_function,
+  sum(spans_overlapping_dur($ts, $dur, ts, dur)) as dur
+FROM thread_state
+JOIN relevant_states USING (id)
+GROUP BY state, raw_state, cpu_type, cpu, blocked_function
+ORDER BY dur desc;
\ No newline at end of file
diff --git a/src/trace_processor/perfetto_sql/stdlib/experimental/thread_executing_span.sql b/src/trace_processor/perfetto_sql/stdlib/experimental/thread_executing_span.sql
index b0f9310..34531c8 100644
--- a/src/trace_processor/perfetto_sql/stdlib/experimental/thread_executing_span.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/experimental/thread_executing_span.sql
@@ -406,7 +406,7 @@
 
 -- Limited thread_state view that will later be span joined with the |experimental_thread_executing_span_graph|.
 CREATE VIEW internal_span_thread_state_view
-AS SELECT id AS thread_state_id, ts, dur, utid, state, blocked_function as function, cpu FROM thread_state;
+AS SELECT id AS thread_state_id, ts, dur, utid, state, blocked_function as function, io_wait, cpu FROM thread_state;
 
 -- |experimental_thread_executing_span_graph| span joined with thread_state information.
 CREATE VIRTUAL TABLE internal_span_graph_thread_state_sp
@@ -431,7 +431,7 @@
 -- Limited |experimental_thread_executing_span_graph| + thread_state view.
 CREATE VIEW internal_span_graph_thread_state
 AS
-SELECT ts, dur, id, thread_state_id, state, function, cpu
+SELECT ts, dur, id, thread_state_id, state, function, io_wait, cpu
 FROM internal_span_graph_thread_state_sp;
 
 -- Limited |experimental_thread_executing_span_graph| + slice view.
@@ -460,7 +460,8 @@
       thread_state.ts + thread_state.dur AS thread_state_end_ts,
       thread_state.state,
       thread_state.function,
-      thread_state.cpu
+      thread_state.cpu,
+      thread_state.io_wait
     FROM span
     JOIN internal_span_graph_thread_state_sp thread_state USING(id)
   )
@@ -473,6 +474,7 @@
   state,
   function,
   cpu,
+  io_wait,
   critical_path_id,
   critical_path_blocked_dur,
   critical_path_blocked_state,
@@ -506,6 +508,7 @@
     state AS self_state,
     blocked_function AS self_function,
     cpu AS self_cpu,
+    io_wait AS self_io_wait,
     name AS self_slice_name,
     depth AS self_slice_depth
     FROM internal_self_sp;
@@ -552,8 +555,12 @@
       self_slice_id,
       self_slice_name,
       self_slice_depth,
+      self_function,
+      self_io_wait,
       thread_state_id,
       state,
+      function,
+      io_wait,
       slice_id,
       slice_name,
       slice_depth,
@@ -561,8 +568,7 @@
       utid,
       MAX(ts, $ts) AS ts,
       MIN(ts + dur, $ts + $dur) AS end_ts,
-      critical_path_utid,
-      critical_path_blocked_function
+      critical_path_utid
     FROM internal_self_and_critical_path_sp
     WHERE dur > 0 AND critical_path_utid = $critical_path_utid
   ),
@@ -578,8 +584,12 @@
       self_slice_id,
       self_slice_name,
       self_slice_depth,
+      self_function,
+      self_io_wait,
       thread_state_id,
       state,
+      function,
+      io_wait,
       slice_id,
       slice_name,
       slice_depth,
@@ -588,8 +598,7 @@
       ts,
       end_ts - ts AS dur,
       critical_path_utid,
-      utid,
-      critical_path_blocked_function
+      utid
     FROM relevant_spans_starts
     WHERE dur > 0
   ),
@@ -614,7 +623,19 @@
       dur,
       utid,
       1 AS stack_depth,
-      IIF(self_state GLOB 'R*', NULL, 'kernel function: ' || critical_path_blocked_function) AS name,
+      IIF(self_state GLOB 'R*', NULL, 'kernel function: ' || self_function) AS name,
+      'thread_state' AS table_name,
+      critical_path_utid
+    FROM relevant_spans
+    UNION ALL
+    -- Builds the self kernel io_wait
+    SELECT
+      self_thread_state_id AS id,
+      ts,
+      dur,
+      utid,
+      2 AS stack_depth,
+      IIF(self_state GLOB 'R*', NULL, 'io_wait: ' || self_io_wait) AS name,
       'thread_state' AS table_name,
       critical_path_utid
     FROM relevant_spans
@@ -625,7 +646,7 @@
       ts,
       dur,
       thread.utid,
-      2 AS stack_depth,
+      3 AS stack_depth,
       IIF($enable_process_name, 'process_name: ' || process.name, NULL) AS name,
       'thread_state' AS table_name,
       critical_path_utid
@@ -641,7 +662,7 @@
       ts,
       dur,
       thread.utid,
-      3 AS stack_depth,
+      4 AS stack_depth,
       IIF($enable_thread_name, 'thread_name: ' || thread.name, NULL) AS name,
       'thread_state' AS table_name,
       critical_path_utid
@@ -657,7 +678,7 @@
       slice.ts,
       slice.dur,
       slice.utid,
-      anc.depth + 4 AS stack_depth,
+      anc.depth + 5 AS stack_depth,
       IIF($enable_self_slice, anc.name, NULL) AS name,
       'slice' AS table_name,
       critical_path_utid
@@ -670,7 +691,7 @@
       ts,
       dur,
       utid,
-      self_slice_depth + 4 AS stack_depth,
+      self_slice_depth + 5 AS stack_depth,
       IIF($enable_self_slice, self_slice_name, NULL) AS name,
       'slice' AS table_name,
       critical_path_utid
@@ -693,6 +714,8 @@
     SELECT
       thread_state_id,
       state,
+      function,
+      io_wait,
       slice_id,
       slice_name,
       slice_depth,
@@ -733,10 +756,8 @@
       'thread_state' AS table_name,
       critical_path_utid
     FROM critical_path_span
-    LEFT JOIN thread
-      USING (utid)
-    LEFT JOIN process
-      USING (upid)
+    JOIN thread USING (utid)
+    LEFT JOIN process USING (upid)
     UNION ALL
     -- Builds the critical_path thread_name
     SELECT
@@ -749,10 +770,33 @@
       'thread_state' AS table_name,
       critical_path_utid
     FROM critical_path_span
-    JOIN thread
-      USING (utid)
-    JOIN process
-      USING (upid)
+    JOIN thread USING (utid)
+    UNION ALL
+    -- Builds the critical_path kernel blocked_function
+    SELECT
+      thread_state_id AS id,
+      ts,
+      dur,
+      thread.utid,
+      start_depth + 3 AS stack_depth,
+      'blocking kernel_function: ' || function,
+      'thread_state' AS table_name,
+      critical_path_utid
+    FROM critical_path_span
+    JOIN thread USING (utid)
+    UNION ALL
+    -- Builds the critical_path kernel io_wait
+    SELECT
+      thread_state_id AS id,
+      ts,
+      dur,
+      thread.utid,
+      start_depth + 4 AS stack_depth,
+      'blocking io_wait: ' || io_wait,
+      'thread_state' AS table_name,
+      critical_path_utid
+    FROM critical_path_span
+    JOIN thread USING (utid)
     UNION ALL
     -- Builds the critical_path 'ancestor' slice stack
     SELECT
@@ -760,7 +804,7 @@
       slice.ts,
       slice.dur,
       slice.utid,
-      anc.depth + start_depth + 3 AS stack_depth,
+      anc.depth + start_depth + 5 AS stack_depth,
       IIF($enable_critical_path_slice, anc.name, NULL) AS name,
       'slice' AS table_name,
       critical_path_utid
@@ -773,7 +817,7 @@
       ts,
       dur,
       utid,
-      slice_depth + start_depth + 3 AS stack_depth,
+      slice_depth + start_depth + 5 AS stack_depth,
       IIF($enable_critical_path_slice, slice_name, NULL) AS name,
       'slice' AS table_name,
       critical_path_utid
diff --git a/src/trace_processor/rpc/BUILD.gn b/src/trace_processor/rpc/BUILD.gn
index 8604153..26b7528 100644
--- a/src/trace_processor/rpc/BUILD.gn
+++ b/src/trace_processor/rpc/BUILD.gn
@@ -23,9 +23,9 @@
 # interface) and by the :httpd module for the HTTP interface.
 source_set("rpc") {
   sources = [
+    "query_result_serializer.cc",
     "rpc.cc",
     "rpc.h",
-    "query_result_serializer.cc",
   ]
   deps = [
     "..:lib",
@@ -44,8 +44,10 @@
 }
 
 # Static library target for RPC code. Needed for BigTrace in Google3.
-static_library("trace_processor_rpc") {
-  public_deps = [ ":rpc" ]
+if (is_perfetto_build_generator) {
+  static_library("trace_processor_rpc") {
+    public_deps = [ ":rpc" ]
+  }
 }
 
 perfetto_unittest_source_set("unittests") {
diff --git a/src/trace_processor/rpc/rpc.cc b/src/trace_processor/rpc/rpc.cc
index 031e3c8..68e0732 100644
--- a/src/trace_processor/rpc/rpc.cc
+++ b/src/trace_processor/rpc/rpc.cc
@@ -131,23 +131,29 @@
 using ProtoEnum = protos::pbzero::MetatraceCategories;
 TraceProcessor::MetatraceCategories MetatraceCategoriesToPublicEnum(
     ProtoEnum categories) {
-  switch (categories) {
-    case ProtoEnum::QUERY_TIMELINE:
-      return TraceProcessor::MetatraceCategories::QUERY_TIMELINE;
-    case ProtoEnum::QUERY_DETAILED:
-      return TraceProcessor::MetatraceCategories::QUERY_DETAILED;
-    case ProtoEnum::FUNCTION_CALL:
-      return TraceProcessor::MetatraceCategories::FUNCTION_CALL;
-    case ProtoEnum::DB:
-      return TraceProcessor::MetatraceCategories::DB;
-    case ProtoEnum::API_TIMELINE:
-      return TraceProcessor::MetatraceCategories::API_TIMELINE;
-    case ProtoEnum::ALL:
-      return TraceProcessor::MetatraceCategories::ALL;
-    case ProtoEnum::NONE:
-      return TraceProcessor::MetatraceCategories::NONE;
+  TraceProcessor::MetatraceCategories result =
+      TraceProcessor::MetatraceCategories::NONE;
+  if (categories & ProtoEnum::QUERY_TIMELINE) {
+    result = static_cast<TraceProcessor::MetatraceCategories>(
+        result | TraceProcessor::MetatraceCategories::QUERY_TIMELINE);
   }
-  return TraceProcessor::MetatraceCategories::NONE;
+  if (categories & ProtoEnum::QUERY_DETAILED) {
+    result = static_cast<TraceProcessor::MetatraceCategories>(
+        result | TraceProcessor::MetatraceCategories::QUERY_DETAILED);
+  }
+  if (categories & ProtoEnum::FUNCTION_CALL) {
+    result = static_cast<TraceProcessor::MetatraceCategories>(
+        result | TraceProcessor::MetatraceCategories::FUNCTION_CALL);
+  }
+  if (categories & ProtoEnum::DB) {
+    result = static_cast<TraceProcessor::MetatraceCategories>(
+        result | TraceProcessor::MetatraceCategories::DB);
+  }
+  if (categories & ProtoEnum::API_TIMELINE) {
+    result = static_cast<TraceProcessor::MetatraceCategories>(
+        result | TraceProcessor::MetatraceCategories::API_TIMELINE);
+  }
+  return result;
 }
 
 }  // namespace
diff --git a/src/trace_processor/sorter/trace_sorter.cc b/src/trace_processor/sorter/trace_sorter.cc
index f72c892..708def9 100644
--- a/src/trace_processor/sorter/trace_sorter.cc
+++ b/src/trace_processor/sorter/trace_sorter.cc
@@ -177,6 +177,10 @@
 void TraceSorter::ParseTracePacket(const TimestampedEvent& event) {
   TraceTokenBuffer::Id id = GetTokenBufferId(event);
   switch (static_cast<TimestampedEvent::Type>(event.event_type)) {
+    case TimestampedEvent::Type::kTraceBlobView:
+      parser_->ParseTraceBlobView(event.ts,
+                                  token_buffer_.Extract<TraceBlobView>(id));
+      return;
     case TimestampedEvent::Type::kTracePacket:
       parser_->ParseTracePacket(event.ts,
                                 token_buffer_.Extract<TracePacketData>(id));
@@ -224,6 +228,7 @@
     case TimestampedEvent::Type::kTrackEvent:
     case TimestampedEvent::Type::kSystraceLine:
     case TimestampedEvent::Type::kTracePacket:
+    case TimestampedEvent::Type::kTraceBlobView:
     case TimestampedEvent::Type::kJsonValue:
     case TimestampedEvent::Type::kFuchsiaRecord:
       PERFETTO_FATAL("Invalid event type");
@@ -235,6 +240,9 @@
     const TimestampedEvent& event) {
   TraceTokenBuffer::Id id = GetTokenBufferId(event);
   switch (static_cast<TimestampedEvent::Type>(event.event_type)) {
+    case TimestampedEvent::Type::kTraceBlobView:
+      base::ignore_result(token_buffer_.Extract<TraceBlobView>(id));
+      return;
     case TimestampedEvent::Type::kTracePacket:
       base::ignore_result(token_buffer_.Extract<TracePacketData>(id));
       return;
diff --git a/src/trace_processor/sorter/trace_sorter.h b/src/trace_processor/sorter/trace_sorter.h
index 1e0f25a..9c26007 100644
--- a/src/trace_processor/sorter/trace_sorter.h
+++ b/src/trace_processor/sorter/trace_sorter.h
@@ -95,6 +95,11 @@
               SortingMode);
   ~TraceSorter();
 
+  inline void PushTraceBlobView(int64_t timestamp, TraceBlobView tbv) {
+    TraceTokenBuffer::Id id = token_buffer_.Append(std::move(tbv));
+    AppendNonFtraceEvent(timestamp, TimestampedEvent::Type::kTraceBlobView, id);
+  }
+
   inline void PushTracePacket(int64_t timestamp,
                               RefPtr<PacketSequenceStateGeneration> state,
                               TraceBlobView tbv) {
@@ -196,6 +201,7 @@
   struct TimestampedEvent {
     enum class Type : uint8_t {
       kFtraceEvent,
+      kTraceBlobView,
       kTracePacket,
       kInlineSchedSwitch,
       kInlineSchedWaking,
diff --git a/src/trace_processor/sqlite/sql_source.cc b/src/trace_processor/sqlite/sql_source.cc
index 570e87e..ee86e8c 100644
--- a/src/trace_processor/sqlite/sql_source.cc
+++ b/src/trace_processor/sqlite/sql_source.cc
@@ -313,7 +313,7 @@
 void SqlSource::Rewriter::Rewrite(uint32_t rewritten_start,
                                   uint32_t rewritten_end,
                                   SqlSource source) {
-  PERFETTO_CHECK(rewritten_start < rewritten_end);
+  PERFETTO_CHECK(rewritten_start <= rewritten_end);
   PERFETTO_CHECK(rewritten_end <= orig_.rewritten_sql.size());
 
   uint32_t original_start =
diff --git a/src/trace_processor/sqlite/sqlite_utils.cc b/src/trace_processor/sqlite/sqlite_utils.cc
index 3afb3ea..4165f2d 100644
--- a/src/trace_processor/sqlite/sqlite_utils.cc
+++ b/src/trace_processor/sqlite/sqlite_utils.cc
@@ -18,6 +18,8 @@
 #include <bitset>
 #include <sstream>
 #include "perfetto/base/status.h"
+#include "perfetto/ext/base/string_utils.h"
+#include "perfetto/trace_processor/basic_types.h"
 
 namespace perfetto {
 namespace trace_processor {
@@ -136,6 +138,8 @@
                base::CaseInsensitiveEqual(raw_type, "BOOLEAN") ||
                base::CaseInsensitiveEqual(raw_type, "INTEGER")) {
       type = SqlValue::Type::kLong;
+    } else if (base::CaseInsensitiveEqual(raw_type, "BLOB")) {
+      type = SqlValue::Type::kBytes;
     } else if (!*raw_type) {
       PERFETTO_DLOG("Unknown column type for %s %s", raw_table_name.c_str(),
                     name);
diff --git a/src/trace_processor/storage/metadata.h b/src/trace_processor/storage/metadata.h
index 88eac06..1fa85ae 100644
--- a/src/trace_processor/storage/metadata.h
+++ b/src/trace_processor/storage/metadata.h
@@ -29,6 +29,7 @@
 // Compile time list of metadata items.
 // clang-format off
 #define PERFETTO_TP_METADATA(F)                                               \
+  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_sdk_version,               KeyType::kSingle,  Variadic::kInt),    \
@@ -48,6 +49,7 @@
   F(system_name,                       KeyType::kSingle,  Variadic::kString), \
   F(system_release,                    KeyType::kSingle,  Variadic::kString), \
   F(system_version,                    KeyType::kSingle,  Variadic::kString), \
+  F(timezone_off_mins,                 KeyType::kSingle,  Variadic::kInt),    \
   F(trace_config_pbtxt,                KeyType::kSingle,  Variadic::kString), \
   F(trace_size_bytes,                  KeyType::kSingle,  Variadic::kInt),    \
   F(trace_time_clock_id,               KeyType::kSingle,  Variadic::kInt),    \
diff --git a/src/trace_processor/util/descriptors.cc b/src/trace_processor/util/descriptors.cc
index bd365f8..96833ed 100644
--- a/src/trace_processor/util/descriptors.cc
+++ b/src/trace_processor/util/descriptors.cc
@@ -239,7 +239,8 @@
     const std::string file_name = file.name().ToStdString();
     if (base::StartsWithAny(file_name, skip_prefixes))
       continue;
-    if (processed_files_.find(file_name) != processed_files_.end()) {
+    if (!merge_existing_messages &&
+        processed_files_.find(file_name) != processed_files_.end()) {
       // This file has been loaded once already. Skip.
       continue;
     }
diff --git a/src/trace_processor/util/sql_argument.cc b/src/trace_processor/util/sql_argument.cc
index 11cd010..f9ac83c 100644
--- a/src/trace_processor/util/sql_argument.cc
+++ b/src/trace_processor/util/sql_argument.cc
@@ -23,6 +23,9 @@
 namespace sql_argument {
 
 bool IsValidName(base::StringView str) {
+  if (str.empty()) {
+    return false;
+  }
   auto pred = [](char c) { return !(isalnum(c) || c == '_'); };
   return std::find_if(str.begin(), str.end(), pred) == str.end();
 }
@@ -30,21 +33,29 @@
 std::optional<Type> ParseType(base::StringView str) {
   if (str == "BOOL") {
     return Type::kBool;
-  } else if (str == "INT") {
+  }
+  if (str == "INT") {
     return Type::kInt;
-  } else if (str == "UINT") {
+  }
+  if (str == "UINT") {
     return Type::kUint;
-  } else if (str == "LONG") {
+  }
+  if (str == "LONG") {
     return Type::kLong;
-  } else if (str == "FLOAT") {
+  }
+  if (str == "FLOAT") {
     return Type::kFloat;
-  } else if (str == "DOUBLE") {
+  }
+  if (str == "DOUBLE") {
     return Type::kDouble;
-  } else if (str == "STRING") {
+  }
+  if (str == "STRING") {
     return Type::kString;
-  } else if (str == "PROTO") {
+  }
+  if (str == "PROTO") {
     return Type::kProto;
-  } else if (str == "BYTES") {
+  }
+  if (str == "BYTES") {
     return Type::kBytes;
   }
   return std::nullopt;
@@ -123,6 +134,21 @@
   return base::OkStatus();
 }
 
+std::string SerializeArguments(const std::vector<ArgumentDefinition>& args) {
+  bool comma = false;
+  std::string serialized;
+  for (const auto& arg : args) {
+    if (comma) {
+      serialized.append(", ");
+    }
+    comma = true;
+    serialized.append(arg.name().c_str());
+    serialized.push_back(' ');
+    serialized.append(TypeToHumanFriendlyString(arg.type()));
+  }
+  return serialized;
+}
+
 }  // namespace sql_argument
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/util/sql_argument.h b/src/trace_processor/util/sql_argument.h
index 8876bed..3856523 100644
--- a/src/trace_processor/util/sql_argument.h
+++ b/src/trace_processor/util/sql_argument.h
@@ -101,6 +101,9 @@
 base::Status ParseArgumentDefinitions(const std::string& args,
                                       std::vector<ArgumentDefinition>& out);
 
+// Serialises the given argument list into a string.
+std::string SerializeArguments(const std::vector<ArgumentDefinition>& args);
+
 }  // namespace sql_argument
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/traced/probes/sys_stats/sys_stats_data_source.cc b/src/traced/probes/sys_stats/sys_stats_data_source.cc
index e5c02a9..32cd67f 100644
--- a/src/traced/probes/sys_stats/sys_stats_data_source.cc
+++ b/src/traced/probes/sys_stats/sys_stats_data_source.cc
@@ -438,7 +438,7 @@
       auto* cpu_stat = sys_stats->add_cpu_stat();
       cpu_stat->set_cpu_id(static_cast<uint32_t>(cpu_id));
       cpu_stat->set_user_ns(cpu_times[0] * ns_per_user_hz_);
-      cpu_stat->set_user_ice_ns(cpu_times[1] * ns_per_user_hz_);
+      cpu_stat->set_user_nice_ns(cpu_times[1] * ns_per_user_hz_);
       cpu_stat->set_system_mode_ns(cpu_times[2] * ns_per_user_hz_);
       cpu_stat->set_idle_ns(cpu_times[3] * ns_per_user_hz_);
       cpu_stat->set_io_wait_ns(cpu_times[4] * ns_per_user_hz_);
diff --git a/src/traced_relay/BUILD.gn b/src/traced_relay/BUILD.gn
index d4e27cc..6b49f94 100644
--- a/src/traced_relay/BUILD.gn
+++ b/src/traced_relay/BUILD.gn
@@ -14,6 +14,7 @@
 
 import("../../gn/perfetto.gni")
 import("../../gn/perfetto_component.gni")
+import("../../gn/test.gni")
 
 executable("traced_relay") {
   deps = [
@@ -45,3 +46,33 @@
     "//src/ipc:perfetto_ipc",
   ]
 }
+
+perfetto_unittest_source_set("unittests") {
+  testonly = true
+  deps = [
+    ":lib",
+    "../../gn:default_deps",
+    "../../gn:gtest_and_gmock",
+    "../base",
+    "../base:test_support",
+    "../base/threading",
+    "//src/ipc:perfetto_ipc",
+  ]
+  sources = [
+    "relay_service_unittest.cc",
+    "socket_relay_handler_unittest.cc",
+  ]
+}
+
+source_set("integrationtests") {
+  testonly = true
+  deps = [
+    ":lib",
+    "../../gn:default_deps",
+    "../../gn:gtest_and_gmock",
+    "../../test:test_helper",
+    "../base",
+    "../base:test_support",
+  ]
+  sources = [ "relay_service_integrationtest.cc" ]
+}
diff --git a/src/traced_relay/relay_service.cc b/src/traced_relay/relay_service.cc
index 4b1dc6b..1ef5855 100644
--- a/src/traced_relay/relay_service.cc
+++ b/src/traced_relay/relay_service.cc
@@ -70,7 +70,10 @@
   IPCFrame ipc_frame;
   ipc_frame.set_request_id(0);
   auto* set_peer_identity = ipc_frame.mutable_set_peer_identity();
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
+    PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
   set_peer_identity->set_pid(server_conn->peer_pid_linux());
+#endif
   set_peer_identity->set_uid(
       static_cast<int32_t>(server_conn->peer_uid_posix()));
 
diff --git a/src/traced_relay/relay_service_integrationtest.cc b/src/traced_relay/relay_service_integrationtest.cc
new file mode 100644
index 0000000..7e29041
--- /dev/null
+++ b/src/traced_relay/relay_service_integrationtest.cc
@@ -0,0 +1,109 @@
+/*
+ * 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.
+ */
+
+#include <memory>
+#include "src/traced_relay/relay_service.h"
+
+#include "src/base/test/test_task_runner.h"
+#include "test/gtest_and_gmock.h"
+#include "test/test_helper.h"
+
+#include "protos/perfetto/config/test_config.gen.h"
+#include "protos/perfetto/config/trace_config.gen.h"
+#include "protos/perfetto/trace/test_event.gen.h"
+
+namespace perfetto {
+namespace {
+
+TEST(TracedRelayIntegrationTest, BasicCase) {
+  base::TestTaskRunner task_runner;
+
+  std::string sock_name;
+  {
+    // Set up a server UnixSocket to find an unused TCP port.
+    base::UnixSocket::EventListener event_listener;
+    auto srv = base::UnixSocket::Listen("127.0.0.1:0", &event_listener,
+                                        &task_runner, base::SockFamily::kInet,
+                                        base::SockType::kStream);
+    ASSERT_TRUE(srv->is_listening());
+    sock_name = srv->GetSockAddr();
+    // Shut down |srv| here to free the port. It's unlikely that the port will
+    // be taken by another process so quickly before we reach the code below.
+  }
+
+  TestHelper helper(&task_runner, TestHelper::Mode::kStartDaemons,
+                    sock_name.c_str());
+  ASSERT_EQ(helper.num_producers(), 1u);
+  helper.StartServiceIfRequired();
+
+  auto relay_service = std::make_unique<RelayService>(&task_runner);
+
+  relay_service->Start("@traced_relay", sock_name.c_str());
+
+  auto producer_connected =
+      task_runner.CreateCheckpoint("perfetto.FakeProducer.connected");
+  auto noop = []() {};
+  auto connected = [&]() { task_runner.PostTask(producer_connected); };
+
+  // We won't use the built-in fake producer and will start our own.
+  auto producer_thread = std::make_unique<FakeProducerThread>(
+      "@traced_relay", connected, noop, noop, "perfetto.FakeProducer");
+  producer_thread->Connect();
+  task_runner.RunUntilCheckpoint("perfetto.FakeProducer.connected");
+
+  helper.ConnectConsumer();
+  helper.WaitForConsumerConnect();
+
+  TraceConfig trace_config;
+  trace_config.add_buffers()->set_size_kb(1024);
+  trace_config.set_duration_ms(200);
+
+  static constexpr uint32_t kMsgSize = 1024;
+  static constexpr uint32_t kRandomSeed = 42;
+  // Enable the producer.
+  auto* ds_config = trace_config.add_data_sources()->mutable_config();
+  ds_config->set_name("perfetto.FakeProducer");
+  ds_config->set_target_buffer(0);
+  ds_config->mutable_for_testing()->set_seed(kRandomSeed);
+  ds_config->mutable_for_testing()->set_message_count(12);
+  ds_config->mutable_for_testing()->set_message_size(kMsgSize);
+  ds_config->mutable_for_testing()->set_send_batch_on_register(true);
+
+  helper.StartTracing(trace_config);
+  helper.WaitForTracingDisabled();
+
+  helper.ReadData();
+  helper.WaitForReadData();
+
+  const auto& packets = helper.trace();
+  ASSERT_EQ(packets.size(), 12u);
+
+  // The producer is connected from this process. The relay service will inject
+  // the SetPeerIdentity message using the pid and euid of the current process.
+  auto pid = static_cast<int32_t>(getpid());
+  auto uid = static_cast<int32_t>(geteuid());
+
+  std::minstd_rand0 rnd_engine(kRandomSeed);
+  for (const auto& packet : packets) {
+    ASSERT_TRUE(packet.has_for_testing());
+    ASSERT_EQ(packet.trusted_pid(), pid);
+    ASSERT_EQ(packet.trusted_uid(), uid);
+    ASSERT_EQ(packet.for_testing().seq_value(), rnd_engine());
+  }
+}
+
+}  // namespace
+}  // namespace perfetto
diff --git a/src/traced_relay/relay_service_unittest.cc b/src/traced_relay/relay_service_unittest.cc
new file mode 100644
index 0000000..649f552
--- /dev/null
+++ b/src/traced_relay/relay_service_unittest.cc
@@ -0,0 +1,130 @@
+/*
+ * 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.
+ */
+
+#include "src/traced_relay/relay_service.h"
+
+#include <memory>
+
+#include "perfetto/ext/base/unix_socket.h"
+#include "protos/perfetto/ipc/wire_protocol.gen.h"
+#include "src/base/test/test_task_runner.h"
+#include "src/ipc/buffered_frame_deserializer.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace {
+
+using ::testing::_;
+using ::testing::Invoke;
+
+class TestEventListener : public base::UnixSocket::EventListener {
+ public:
+  MOCK_METHOD(void, OnDataAvailable, (base::UnixSocket*), (override));
+  MOCK_METHOD(void, OnConnect, (base::UnixSocket*, bool), (override));
+  MOCK_METHOD(void, OnNewIncomingConnection, (base::UnixSocket*));
+
+  void OnNewIncomingConnection(
+      base::UnixSocket*,
+      std::unique_ptr<base::UnixSocket> new_connection) override {
+    // Need to keep |new_connection| alive.
+    client_connection_ = std::move(new_connection);
+    OnNewIncomingConnection(client_connection_.get());
+  }
+
+ private:
+  std::unique_ptr<base::UnixSocket> client_connection_;
+};
+
+// Exercises the relay service and also validates that the relay service injects
+// a SetPeerIdentity message:
+//
+// producer (client UnixSocket) <- @producer.sock -> relay service
+// <- 127.0.0.1.* -> tcp_server (listening UnixSocet).
+TEST(RelayServiceTest, SetPeerIdentity) {
+  base::TestTaskRunner task_runner;
+  auto relay_service = std::make_unique<RelayService>(&task_runner);
+
+  // Set up a server UnixSocket to find an unused TCP port.
+  // The TCP connection emulates the socket to the host traced.
+  TestEventListener tcp_listener;
+  auto tcp_server = base::UnixSocket::Listen(
+      "127.0.0.1:0", &tcp_listener, &task_runner, base::SockFamily::kInet,
+      base::SockType::kStream);
+  ASSERT_TRUE(tcp_server->is_listening());
+  auto tcp_sock_name = tcp_server->GetSockAddr();
+  auto* unix_sock_name =
+      "@producer.sock";  // Use abstract unix socket for server socket.
+
+  // Start the relay service.
+  relay_service->Start(unix_sock_name, tcp_sock_name.c_str());
+
+  // Emulates the producer connection.
+  TestEventListener producer_listener;
+  auto producer = base::UnixSocket::Connect(
+      unix_sock_name, &producer_listener, &task_runner, base::SockFamily::kUnix,
+      base::SockType::kStream);
+  auto producer_connected = task_runner.CreateCheckpoint("producer_connected");
+  EXPECT_CALL(producer_listener, OnConnect(_, _))
+      .WillOnce(Invoke([&](base::UnixSocket* s, bool conn) {
+        EXPECT_TRUE(conn);
+        EXPECT_EQ(s, producer.get());
+        producer_connected();
+      }));
+  task_runner.RunUntilCheckpoint("producer_connected");
+
+  // Add some producer data.
+  ipc::Frame test_frame;
+  test_frame.add_data_for_testing("test_data");
+  auto test_data = ipc::BufferedFrameDeserializer::Serialize(test_frame);
+  producer->SendStr(test_data);
+
+  base::UnixSocket* tcp_client_connection = nullptr;
+  auto tcp_client_connected =
+      task_runner.CreateCheckpoint("tcp_client_connected");
+  EXPECT_CALL(tcp_listener, OnNewIncomingConnection(_))
+      .WillOnce(Invoke([&](base::UnixSocket* client) {
+        tcp_client_connection = client;
+        tcp_client_connected();
+      }));
+  task_runner.RunUntilCheckpoint("tcp_client_connected");
+
+  // Asserts that we can receive the SetPeerIdentity message.
+  auto peer_identity_recv = task_runner.CreateCheckpoint("peer_identity_recv");
+  ipc::BufferedFrameDeserializer deserializer;
+  EXPECT_CALL(tcp_listener, OnDataAvailable(_))
+      .WillRepeatedly(Invoke([&](base::UnixSocket* tcp_conn) {
+        auto buf = deserializer.BeginReceive();
+        auto rsize = tcp_conn->Receive(buf.data, buf.size);
+        EXPECT_TRUE(deserializer.EndReceive(rsize));
+
+        auto frame = deserializer.PopNextFrame();
+        EXPECT_TRUE(frame->has_set_peer_identity());
+
+        const auto& set_peer_identity = frame->set_peer_identity();
+        EXPECT_EQ(set_peer_identity.pid(), getpid());
+        EXPECT_EQ(set_peer_identity.uid(), static_cast<int32_t>(geteuid()));
+
+        frame = deserializer.PopNextFrame();
+        EXPECT_EQ(1u, frame->data_for_testing().size());
+        EXPECT_EQ(std::string("test_data"), frame->data_for_testing()[0]);
+
+        peer_identity_recv();
+      }));
+  task_runner.RunUntilCheckpoint("peer_identity_recv");
+}
+
+}  // namespace
+}  // namespace perfetto
diff --git a/src/traced_relay/socket_relay_handler.cc b/src/traced_relay/socket_relay_handler.cc
index 2930c86..e8aa556 100644
--- a/src/traced_relay/socket_relay_handler.cc
+++ b/src/traced_relay/socket_relay_handler.cc
@@ -29,7 +29,6 @@
 #include "perfetto/ext/base/thread_checker.h"
 #include "perfetto/ext/base/utils.h"
 #include "perfetto/ext/base/watchdog.h"
-#include "perfetto/ext/base/watchdog_posix.h"
 
 namespace perfetto {
 namespace {
@@ -52,8 +51,8 @@
 void FdPoller::Poll() {
   PERFETTO_DCHECK_THREAD(thread_checker_);
 
-  int num_fds =
-      PERFETTO_EINTR(poll(&poll_fds_[0], poll_fds_.size(), kPollTimeoutMs));
+  int num_fds = PERFETTO_EINTR(poll(
+      &poll_fds_[0], static_cast<nfds_t>(poll_fds_.size()), kPollTimeoutMs));
   if (num_fds == -1 && base::IsAgain(errno))
     return;  // Poll again.
   PERFETTO_DCHECK(num_fds <= static_cast<int>(poll_fds_.size()));
diff --git a/src/traced_relay/socket_relay_handler_unittest.cc b/src/traced_relay/socket_relay_handler_unittest.cc
new file mode 100644
index 0000000..a96308a
--- /dev/null
+++ b/src/traced_relay/socket_relay_handler_unittest.cc
@@ -0,0 +1,204 @@
+/*
+ * 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.
+ */
+
+#include "src/traced_relay/socket_relay_handler.h"
+
+#include <chrono>
+#include <cstring>
+#include <memory>
+#include <random>
+#include <string>
+#include <thread>
+#include <utility>
+
+#include "perfetto/ext/base/threading/thread_pool.h"
+#include "perfetto/ext/base/unix_socket.h"
+
+#include "test/gtest_and_gmock.h"
+
+using testing::Values;
+
+namespace perfetto {
+namespace {
+
+using RawSocketPair = std::pair<base::UnixSocketRaw, base::UnixSocketRaw>;
+using RngValueType = std::minstd_rand0::result_type;
+
+struct TestClient {
+  RawSocketPair endpoint_sockets;
+  std::minstd_rand0 data_prng;
+  std::thread client_thread;
+};
+
+class SocketRelayHandlerTest : public ::testing::TestWithParam<uint32_t> {
+ protected:
+  void SetUp() override {
+    socket_relay_handler_ = std::make_unique<SocketRelayHandler>();
+
+    for (uint32_t i = 0; i < GetParam(); i++) {
+      TestClient client{SetUpEndToEndSockets(), std::minstd_rand0(i), {}};
+      test_clients_.push_back(std::move(client));
+    }
+  }
+  void TearDown() override { socket_relay_handler_ = nullptr; }
+
+  RawSocketPair SetUpEndToEndSockets() {
+    // Creates 2 SocketPairs:
+    // sock1 <-> sock2 <-> SocketRelayHandler <-> sock3 <-> sock4.
+    // sock2 and sock3 are transferred to the SocketRelayHandler.
+    // We test by reading and writing bidirectionally using sock1 and sock4.
+    auto [sock1, sock2] = base::UnixSocketRaw::CreatePairPosix(
+        base::SockFamily::kUnix, base::SockType::kStream);
+    sock2.SetBlocking(false);
+
+    auto [sock3, sock4] = base::UnixSocketRaw::CreatePairPosix(
+        base::SockFamily::kUnix, base::SockType::kStream);
+    sock3.SetBlocking(false);
+
+    auto socket_pair = std::make_unique<SocketPair>();
+    socket_pair->first.sock = std::move(sock2);
+    socket_pair->second.sock = std::move(sock3);
+
+    socket_relay_handler_->AddSocketPair(std::move(socket_pair));
+
+    RawSocketPair endpoint_sockets;
+    endpoint_sockets.first = std::move(sock1);
+    endpoint_sockets.second = std::move(sock4);
+
+    return endpoint_sockets;
+  }
+
+  std::unique_ptr<SocketRelayHandler> socket_relay_handler_;
+  std::vector<TestClient> test_clients_;
+  // Use fewer receiver threads than sender threads.
+  base::ThreadPool receiver_thread_pool_{1 + GetParam() / 10};
+};
+
+TEST(SocketWithBufferTest, EnqueueDequeue) {
+  SocketWithBuffer socket_with_buffer;
+  // No data initially.
+  EXPECT_EQ(0u, socket_with_buffer.data_size());
+
+  // Has room for writing some bytes into.
+  std::string data = "12345678901234567890";
+  EXPECT_GT(socket_with_buffer.available_bytes(), data.size());
+
+  memcpy(socket_with_buffer.buffer(), data.data(), data.size());
+  socket_with_buffer.EnqueueData(data.size());
+  EXPECT_EQ(data.size(), socket_with_buffer.data_size());
+
+  // Dequeue some bytes.
+  socket_with_buffer.DequeueData(5);
+  EXPECT_EQ(socket_with_buffer.data_size(), data.size() - 5);
+  std::string buffered_data(reinterpret_cast<char*>(socket_with_buffer.data()),
+                            socket_with_buffer.data_size());
+  EXPECT_EQ(buffered_data, "678901234567890");
+}
+
+// Test the SocketRelayHander with randomized request and response data.
+TEST_P(SocketRelayHandlerTest, RandomizedRequestResponse) {
+  // The max message size in the number of RNG calls.
+  constexpr size_t kMaxMsgSizeRng = 1 << 20;
+
+  // Create the threads for sending and receiving data through the
+  // SocketRelayHandler.
+  for (auto& client : test_clients_) {
+    auto* thread_pool = &receiver_thread_pool_;
+
+    auto thread_func = [&client, thread_pool]() {
+      auto& rng = client.data_prng;
+
+      // The max number of requests.
+      const size_t num_requests = rng() % 50;
+
+      for (size_t j = 0; j < num_requests; j++) {
+        auto& send_endpoint = client.endpoint_sockets.first;
+        auto& receive_endpoint = client.endpoint_sockets.second;
+
+        auto req_size = rng() % kMaxMsgSizeRng;
+
+        // Generate the random request.
+        std::vector<RngValueType> request;
+        request.reserve(req_size);
+        for (size_t r = 0; r < req_size; r++) {
+          request.emplace_back(rng());
+        }
+
+        // Create a buffer for receiving the request.
+        std::vector<RngValueType> received_request(request.size());
+
+        std::mutex mutex;
+        std::condition_variable cv;
+        std::unique_lock<std::mutex> lock(mutex);
+        bool done = false;
+
+        // Blocking receive on the thread pool.
+        thread_pool->PostTask([&]() {
+          const size_t bytes_to_receive =
+              received_request.size() * sizeof(RngValueType);
+          uint8_t* receive_buffer =
+              reinterpret_cast<uint8_t*>(received_request.data());
+          size_t bytes_received = 0;
+
+          // Perform a blocking read until we received the expected bytes.
+          while (bytes_received < bytes_to_receive) {
+            ssize_t rsize = PERFETTO_EINTR(
+                receive_endpoint.Receive(receive_buffer + bytes_received,
+                                         bytes_to_receive - bytes_received));
+            if (rsize <= 0)
+              break;
+            bytes_received += static_cast<size_t>(rsize);
+
+            std::this_thread::yield();  // Adds some scheduling randomness.
+          }
+
+          std::lock_guard<std::mutex> inner_lock(mutex);
+          done = true;
+          cv.notify_one();
+        });
+
+        // Perform a blocking send of the request data.
+        PERFETTO_EINTR(send_endpoint.Send(
+            request.data(), request.size() * sizeof(RngValueType)));
+
+        // Wait until the request is fully received.
+        cv.wait(lock, [&done] { return done; });
+
+        // Check data integrity.
+        EXPECT_EQ(request, received_request);
+
+        // Add some randomness to timing.
+        std::this_thread::sleep_for(std::chrono::microseconds(rng() % 1000));
+
+        // Emulate the response by reversing the data flow direction.
+        std::swap(send_endpoint, receive_endpoint);
+      }
+    };
+
+    client.client_thread = std::thread(std::move(thread_func));
+  }
+
+  for (auto& client : test_clients_) {
+    client.client_thread.join();
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(ByConnections,
+                         SocketRelayHandlerTest,
+                         Values(1, 5, 50));
+
+}  // namespace
+}  // namespace perfetto
diff --git a/src/tracing/BUILD.gn b/src/tracing/BUILD.gn
index 19b3205..f0a04eb 100644
--- a/src/tracing/BUILD.gn
+++ b/src/tracing/BUILD.gn
@@ -92,7 +92,7 @@
     "../../include/perfetto/tracing/core",
     "../../protos/perfetto/common:zero",
     "../../protos/perfetto/config:cpp",
-    "../../protos/perfetto/config/interceptors:zero",
+    "../../protos/perfetto/config/interceptors:cpp",
     "../../protos/perfetto/config/track_event:cpp",
     "../base",
     "core",
diff --git a/src/tracing/console_interceptor.cc b/src/tracing/console_interceptor.cc
index 2c3221f..a594409 100644
--- a/src/tracing/console_interceptor.cc
+++ b/src/tracing/console_interceptor.cc
@@ -33,7 +33,7 @@
 #include "protos/perfetto/common/interceptor_descriptor.gen.h"
 #include "protos/perfetto/config/data_source_config.gen.h"
 #include "protos/perfetto/config/interceptor_config.gen.h"
-#include "protos/perfetto/config/interceptors/console_config.pbzero.h"
+#include "protos/perfetto/config/interceptors/console_config.gen.h"
 #include "protos/perfetto/trace/interned_data/interned_data.pbzero.h"
 #include "protos/perfetto/trace/trace_packet.pbzero.h"
 #include "protos/perfetto/trace/trace_packet_defaults.pbzero.h"
@@ -275,13 +275,13 @@
 #else
   bool use_colors = false;
 #endif
-  protos::pbzero::ConsoleConfig::Decoder config(
-      args.config.interceptor_config().console_config_raw());
+  const protos::gen::ConsoleConfig& config =
+      args.config.interceptor_config().console_config();
   if (config.has_enable_colors())
     use_colors = config.enable_colors();
-  if (config.output() == protos::pbzero::ConsoleConfig::OUTPUT_STDOUT) {
+  if (config.output() == protos::gen::ConsoleConfig::OUTPUT_STDOUT) {
     fd = STDOUT_FILENO;
-  } else if (config.output() == protos::pbzero::ConsoleConfig::OUTPUT_STDERR) {
+  } else if (config.output() == protos::gen::ConsoleConfig::OUTPUT_STDERR) {
     fd = STDERR_FILENO;
   }
   fd_ = fd;
diff --git a/src/tracing/core/trace_buffer.cc b/src/tracing/core/trace_buffer.cc
index f0e9d31..bd5e4a5 100644
--- a/src/tracing/core/trace_buffer.cc
+++ b/src/tracing/core/trace_buffer.cc
@@ -75,6 +75,7 @@
     return false;
   }
   size_ = size;
+  used_size_ = 0;
   stats_.set_buffer_size(size);
   max_chunk_size_ = std::min(size, ChunkRecord::kMaxSize);
   wptr_ = begin();
@@ -134,7 +135,7 @@
   record.chunk_id = chunk_id;
   record.writer_id = writer_id;
   record.num_fragments = num_fragments;
-  record.flags = chunk_flags;
+  record.flags = chunk_flags & ChunkRecord::kFlagsBitMask;
   ChunkMeta::Key key(record);
 
   // Check whether we have already copied the same chunk previously. This may
@@ -451,7 +452,7 @@
   stats_.set_patches_succeeded(stats_.patches_succeeded() + patches_size);
   if (!other_patches_pending) {
     chunk_meta.flags &= ~kChunkNeedsPatching;
-    chunk_record->flags = chunk_meta.flags;
+    chunk_record->flags = chunk_meta.flags & ChunkRecord::kFlagsBitMask;
   }
   return true;
 }
@@ -915,8 +916,8 @@
 
   // The assignments below must be done after Initialize().
 
-  data_.EnsureCommitted(data_.size());
-  memcpy(data_.Get(), src.data_.Get(), src.data_.size());
+  EnsureCommitted(src.used_size_);
+  memcpy(data_.Get(), src.data_.Get(), src.used_size_);
   last_chunk_id_written_ = src.last_chunk_id_written_;
 
   stats_ = src.stats_;
diff --git a/src/tracing/core/trace_buffer.h b/src/tracing/core/trace_buffer.h
index 9dea557..3ca3161 100644
--- a/src/tracing/core/trace_buffer.h
+++ b/src/tracing/core/trace_buffer.h
@@ -286,6 +286,7 @@
   const WriterStatsMap& writer_stats() const { return writer_stats_; }
   const TraceStats::BufferStats& stats() const { return stats_; }
   size_t size() const { return size_; }
+  size_t used_size() const { return used_size_; }
   OverwritePolicy overwrite_policy() const { return overwrite_policy_; }
   bool has_data() const { return has_data_; }
 
@@ -338,6 +339,8 @@
     uint16_t size;
 
     uint8_t flags : 6;  // See SharedMemoryABI::ChunkHeader::flags.
+    static constexpr size_t kFlagsBitMask = (1 << 6) - 1;
+
     uint8_t is_padding : 1;
     uint8_t unused_flag : 1;
 
@@ -613,11 +616,16 @@
   ChunkRecord* GetChunkRecordAt(uint8_t* ptr) {
     DcheckIsAlignedAndWithinBounds(ptr);
     // We may be accessing a new (empty) record.
-    data_.EnsureCommitted(
-        static_cast<size_t>(ptr + sizeof(ChunkRecord) - begin()));
+    EnsureCommitted(static_cast<size_t>(ptr + sizeof(ChunkRecord) - begin()));
     return reinterpret_cast<ChunkRecord*>(ptr);
   }
 
+  void EnsureCommitted(size_t size) {
+    PERFETTO_DCHECK(size <= size_);
+    data_.EnsureCommitted(size);
+    used_size_ = std::max(used_size_, size);
+  }
+
   void DiscardWrite();
 
   // |src| can be nullptr (in which case |size| must be ==
@@ -639,7 +647,7 @@
     DcheckIsAlignedAndWithinBounds(wptr);
 
     // We may be writing to this area for the first time.
-    data_.EnsureCommitted(static_cast<size_t>(wptr + record.size - begin()));
+    EnsureCommitted(static_cast<size_t>(wptr + record.size - begin()));
 
     // Deliberately not a *D*CHECK.
     PERFETTO_CHECK(wptr + sizeof(record) + size <= end());
@@ -676,6 +684,12 @@
 
   base::PagedMemory data_;
   size_t size_ = 0;            // Size in bytes of |data_|.
+
+  // High watermark. The number of bytes (<= |size_|) written into the buffer
+  // before the first wraparound. This increases as data is written into the
+  // buffer and then saturates at |size_|. Used for CloneReadOnly().
+  size_t used_size_ = 0;
+
   size_t max_chunk_size_ = 0;  // Max size in bytes allowed for a chunk.
   uint8_t* wptr_ = nullptr;    // Write pointer.
 
diff --git a/src/tracing/core/trace_buffer_unittest.cc b/src/tracing/core/trace_buffer_unittest.cc
index 38d343d..44847dd 100644
--- a/src/tracing/core/trace_buffer_unittest.cc
+++ b/src/tracing/core/trace_buffer_unittest.cc
@@ -26,6 +26,7 @@
 #include "perfetto/ext/tracing/core/shared_memory_abi.h"
 #include "perfetto/ext/tracing/core/trace_packet.h"
 #include "perfetto/protozero/proto_utils.h"
+#include "src/base/test/vm_test_utils.h"
 #include "src/tracing/core/trace_buffer.h"
 #include "src/tracing/test/fake_packet.h"
 #include "test/gtest_and_gmock.h"
@@ -49,6 +50,22 @@
   static constexpr uint8_t kChunkNeedsPatching =
       SharedMemoryABI::ChunkHeader::kChunkNeedsPatching;
 
+  void TearDown() override {
+    // Test that the used_size() logic works and that all the data after that
+    // is zero-filled.
+    if (trace_buffer_) {
+      const size_t used_size = trace_buffer_->used_size();
+      ASSERT_LE(used_size, trace_buffer_->size());
+      trace_buffer_->EnsureCommitted(trace_buffer_->size());
+      bool zero_padded = true;
+      for (size_t i = used_size; i < trace_buffer_->size(); ++i) {
+        bool is_zero = static_cast<char*>(trace_buffer_->data_.Get())[i] == 0;
+        zero_padded = zero_padded && is_zero;
+      }
+      ASSERT_TRUE(zero_padded);
+    }
+  }
+
   FakeChunk CreateChunk(ProducerID p, WriterID w, ChunkID c) {
     return FakeChunk(trace_buffer_.get(), p, w, c);
   }
@@ -144,6 +161,7 @@
     return keys;
   }
 
+  uint8_t* GetBufData(const TraceBuffer& buf) { return buf.begin(); }
   TraceBuffer* trace_buffer() { return trace_buffer_.get(); }
   size_t size_to_end() { return trace_buffer_->size_to_end(); }
 
@@ -1854,6 +1872,8 @@
                          .CopyIntoTraceBuffer());
     }
 
+    ASSERT_EQ(trace_buffer()->used_size(), 32u * kNumWriters);
+
     // Make some reads *before* cloning. This is to check that the behaviour of
     // CloneReadOnly() is not affected by reads made before cloning.
     // On every round (|num_pre_reads|), read a different number of packets.
@@ -1866,6 +1886,7 @@
 
     // Now create a snapshot and make sure we always read all the packets.
     std::unique_ptr<TraceBuffer> snap = trace_buffer()->CloneReadOnly();
+    ASSERT_EQ(snap->used_size(), 32u * kNumWriters);
     snap->BeginRead();
     for (char i = 'A'; i < 'A' + kNumWriters; i++) {
       auto frags = ReadPacket(snap);
@@ -1929,4 +1950,69 @@
   ASSERT_THAT(ReadPacket(snap), IsEmpty());
 }
 
+TEST_F(TraceBufferTest, Clone_Wrapping) {
+  ResetBuffer(4096);
+  const size_t kFrgSize = 1024 - 16;  // For perfect wrapping every 4 fragments.
+  for (WriterID i = 0; i < 6; i++) {
+    CreateChunk(ProducerID(1), WriterID(i), ChunkID(0))
+        .AddPacket(kFrgSize, static_cast<char>('a' + i))
+        .CopyIntoTraceBuffer();
+  }
+  std::unique_ptr<TraceBuffer> snap = trace_buffer()->CloneReadOnly();
+  ASSERT_EQ(snap->used_size(), snap->size());
+  snap->BeginRead();
+  ASSERT_THAT(ReadPacket(snap), ElementsAre(FakePacketFragment(kFrgSize, 'c')));
+  ASSERT_THAT(ReadPacket(snap), ElementsAre(FakePacketFragment(kFrgSize, 'd')));
+  ASSERT_THAT(ReadPacket(snap), ElementsAre(FakePacketFragment(kFrgSize, 'e')));
+  ASSERT_THAT(ReadPacket(snap), ElementsAre(FakePacketFragment(kFrgSize, 'f')));
+  ASSERT_THAT(ReadPacket(snap), IsEmpty());
+}
+
+TEST_F(TraceBufferTest, Clone_WrappingWithPadding) {
+  ResetBuffer(4096);
+  // First create one 2KB chunk, so the contents are [aaaaaaaa00000000].
+  CreateChunk(ProducerID(1), WriterID(0), ChunkID(0))
+      .AddPacket(2048, static_cast<char>('a'))
+      .CopyIntoTraceBuffer();
+
+  // Then write a 3KB chunk that fits in the buffer, but requires zero padding
+  // and restarting from the beginning, so the contents are [bbbbbbbbbbbb0000].
+  CreateChunk(ProducerID(1), WriterID(1), ChunkID(0))
+      .AddPacket(3192, static_cast<char>('b'))
+      .CopyIntoTraceBuffer();
+
+  ASSERT_EQ(trace_buffer()->used_size(), trace_buffer()->size());
+  std::unique_ptr<TraceBuffer> snap = trace_buffer()->CloneReadOnly();
+  ASSERT_EQ(snap->used_size(), snap->size());
+  snap->BeginRead();
+  ASSERT_THAT(ReadPacket(snap), ElementsAre(FakePacketFragment(3192, 'b')));
+  ASSERT_THAT(ReadPacket(snap), IsEmpty());
+}
+
+TEST_F(TraceBufferTest, Clone_CommitOnlyUsedSize) {
+  const size_t kPages = 32;
+  const size_t kPageSize = base::GetSysPageSize();
+  ResetBuffer(kPageSize * kPages);
+  CreateChunk(ProducerID(1), WriterID(0), ChunkID(0))
+      .AddPacket(1024, static_cast<char>('a'))
+      .CopyIntoTraceBuffer();
+
+  using base::vm_test_utils::IsMapped;
+  auto is_only_first_page_mapped = [&](const TraceBuffer& buf) {
+    bool first_mapped = IsMapped(GetBufData(buf), kPageSize);
+    bool rest_mapped = IsMapped(GetBufData(buf) + kPageSize, kPages - 1);
+    return first_mapped && !rest_mapped;
+  };
+
+  // If the test doesn't work as expected until here, there is no point checking
+  // that the same assumptions hold true on the cloned buffer. Various platforms
+  // can legitimately pre-fetch memory even if we don't page fault (also asan).
+  if (!is_only_first_page_mapped(*trace_buffer()))
+    GTEST_SKIP() << "VM commit detection not supported";
+
+  std::unique_ptr<TraceBuffer> snap = trace_buffer()->CloneReadOnly();
+  ASSERT_EQ(snap->used_size(), trace_buffer()->used_size());
+  ASSERT_TRUE(is_only_first_page_mapped(*snap));
+}
+
 }  // namespace perfetto
diff --git a/src/tracing/core/tracing_service_impl.cc b/src/tracing/core/tracing_service_impl.cc
index 9040ded..45ce1eb 100644
--- a/src/tracing/core/tracing_service_impl.cc
+++ b/src/tracing/core/tracing_service_impl.cc
@@ -3470,6 +3470,11 @@
   protozero::HeapBuffered<protos::pbzero::TracePacket> packet;
   auto* info = packet->set_system_info();
   info->set_tracing_service_version(base::GetVersionString());
+
+  std::optional<int32_t> tzoff = base::GetTimezoneOffsetMins();
+  if (tzoff.has_value())
+    info->set_timezone_off_mins(*tzoff);
+
 #if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) && \
     !PERFETTO_BUILDFLAG(PERFETTO_OS_NACL)
   struct utsname uname_info;
diff --git a/src/tracing/internal/track_event_internal.cc b/src/tracing/internal/track_event_internal.cc
index 5d67ad8..c594adb 100644
--- a/src/tracing/internal/track_event_internal.cc
+++ b/src/tracing/internal/track_event_internal.cc
@@ -53,6 +53,7 @@
 static constexpr const char kLegacySlowPrefix[] = "disabled-by-default-";
 static constexpr const char kSlowTag[] = "slow";
 static constexpr const char kDebugTag[] = "debug";
+static constexpr const char kFilteredEventName[] = "FILTERED";
 
 constexpr auto kClockIdIncremental =
     TrackEventIncrementalState::kClockIdIncremental;
@@ -515,7 +516,10 @@
 void TrackEventInternal::WriteEventName(perfetto::DynamicString event_name,
                                         perfetto::EventContext& event_ctx,
                                         const TrackEventTlsState& tls_state) {
-  if (PERFETTO_LIKELY(!tls_state.filter_dynamic_event_names)) {
+  if (PERFETTO_UNLIKELY(tls_state.filter_dynamic_event_names)) {
+    event_ctx.event()->set_name(kFilteredEventName,
+                                sizeof(kFilteredEventName) - 1);
+  } else {
     event_ctx.event()->set_name(event_name.value, event_name.length);
   }
 }
diff --git a/src/tracing/test/api_integrationtest.cc b/src/tracing/test/api_integrationtest.cc
index 0660ada..707e775 100644
--- a/src/tracing/test/api_integrationtest.cc
+++ b/src/tracing/test/api_integrationtest.cc
@@ -3560,7 +3560,8 @@
     auto slices = StopSessionAndReadSlicesFromTrace(tracing_session);
     ASSERT_EQ(3u, slices.size());
     EXPECT_EQ("B:test.Event1", slices[0]);
-    EXPECT_EQ(filter_dynamic_names ? "B:test" : "B:test.Event2", slices[1]);
+    EXPECT_EQ(filter_dynamic_names ? "B:test.FILTERED" : "B:test.Event2",
+              slices[1]);
     EXPECT_EQ("B:test.Event3", slices[2]);
   }
 }
diff --git a/test/data/chrome_fcp_lcp_navigations.pftrace.sha256 b/test/data/chrome_fcp_lcp_navigations.pftrace.sha256
new file mode 100644
index 0000000..e4274e2
--- /dev/null
+++ b/test/data/chrome_fcp_lcp_navigations.pftrace.sha256
@@ -0,0 +1 @@
+ae01d849fbd75a98be1b7ddd5a8873217c377b393a1d5bbd788ed3364f7fefc3
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-chrome_rendering_desktop_select_slice_with_flows.png.sha256 b/test/data/ui-screenshots/ui-chrome_rendering_desktop_select_slice_with_flows.png.sha256
index 60017fd..3af03af 100644
--- a/test/data/ui-screenshots/ui-chrome_rendering_desktop_select_slice_with_flows.png.sha256
+++ b/test/data/ui-screenshots/ui-chrome_rendering_desktop_select_slice_with_flows.png.sha256
@@ -1 +1 @@
-d4047dbc06457be945fc612e629fca6a8ffaf15332fe76c653b1dd7a9a58d06b
\ No newline at end of file
+b51988f52a96b6576e714111c96018b8f13541a5ccd44b8bf9b1989185edd515
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-modal_dialog_show_dialog_1.png.sha256 b/test/data/ui-screenshots/ui-modal_dialog_show_dialog_1.png.sha256
index 655b0f9..e268e28 100644
--- a/test/data/ui-screenshots/ui-modal_dialog_show_dialog_1.png.sha256
+++ b/test/data/ui-screenshots/ui-modal_dialog_show_dialog_1.png.sha256
@@ -1 +1 @@
-fff7ed44c6100f75f93e21bdb9dbd093a8e67274d5eee255bf96e7d26a5614ca
\ No newline at end of file
+c753a17a466814841035cd006716128187d838c6c00fb7c600f810b6b36a7be9
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-modal_dialog_show_dialog_2.png.sha256 b/test/data/ui-screenshots/ui-modal_dialog_show_dialog_2.png.sha256
index 8febff4..a26ddd5 100644
--- a/test/data/ui-screenshots/ui-modal_dialog_show_dialog_2.png.sha256
+++ b/test/data/ui-screenshots/ui-modal_dialog_show_dialog_2.png.sha256
@@ -1 +1 @@
-31029a524c31322ce5c72f246de163b28047b839275a30b07061153a963a386b
\ No newline at end of file
+784304442fea618ab4ba75c16ccbc3d08fcfbe3ba19d7f7b1a470aa520d801a1
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_open_invalid_trace_from_blank_page.png.sha256 b/test/data/ui-screenshots/ui-routing_open_invalid_trace_from_blank_page.png.sha256
index 7a976a8..36fadce 100644
--- a/test/data/ui-screenshots/ui-routing_open_invalid_trace_from_blank_page.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_open_invalid_trace_from_blank_page.png.sha256
@@ -1 +1 @@
-4486b3cba3f423541c1efd3f8dba5ee157e8215263c3250e8cefac6382905691
\ No newline at end of file
+ac4b7c10fb3ca19724f2f66b3b4b013cec90bac60d06d09b2bf96e30944750d8
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_invalid_trace.png.sha256 b/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_invalid_trace.png.sha256
index 99e5f07..4afc696 100644
--- a/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_invalid_trace.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_invalid_trace.png.sha256
@@ -1 +1 @@
-4b96c2f13ce655e31532c21eac0652244803043ae6c56b5dcaec0e3158552256
\ No newline at end of file
+3b624767a7b2118e71fb4efa948ffe2021f1e59e30ddfadf0a07fa22203fbb67
\ No newline at end of file
diff --git a/test/trace_processor/diff_tests/chrome/chrome_dropped_frames_metric_test.sql b/test/trace_processor/diff_tests/chrome/chrome_dropped_frames_metric_test.sql
deleted file mode 100644
index 550a34f..0000000
--- a/test/trace_processor/diff_tests/chrome/chrome_dropped_frames_metric_test.sql
+++ /dev/null
@@ -1,18 +0,0 @@
---
--- Copyright 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
---
---     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 RUN_METRIC('experimental/chrome_dropped_frames.sql');
-
-SELECT * FROM dropped_frames_with_process_info;
diff --git a/test/trace_processor/diff_tests/chrome/chrome_speedometer_test.sql b/test/trace_processor/diff_tests/chrome/chrome_speedometer_test.sql
deleted file mode 100644
index 9b40054..0000000
--- a/test/trace_processor/diff_tests/chrome/chrome_speedometer_test.sql
+++ /dev/null
@@ -1,20 +0,0 @@
-INCLUDE PERFETTO MODULE chrome.speedometer;
-
-SELECT
-  iteration,
-  ts,
-  dur,
-  total,
-  format('%.1f', mean) AS mean,
-  format('%.1f', geomean) AS geomean,
-  format('%.1f', score) AS score,
-  num_measurements
-FROM
-  chrome_speedometer_iteration,
-  (
-    SELECT iteration, COUNT(*) AS num_measurements
-    FROM chrome_speedometer_measure
-    GROUP BY iteration
-  )
-USING (iteration)
-ORDER BY iteration;
diff --git a/test/trace_processor/diff_tests/chrome/tests.py b/test/trace_processor/diff_tests/chrome/tests.py
deleted file mode 100644
index b72a098..0000000
--- a/test/trace_processor/diff_tests/chrome/tests.py
+++ /dev/null
@@ -1,572 +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 Chrome(TestSuite):
-  # Tests related to Chrome's use of Perfetto. Chrome histogram hashes
-  def test_chrome_histogram_hashes(self):
-    return DiffTestBlueprint(
-        trace=TextProto(r"""
-        packet {
-          trusted_packet_sequence_id: 1
-          timestamp: 0
-          incremental_state_cleared: true
-          track_event {
-            categories: "cat1"
-            type: 3
-            name_iid: 1
-            chrome_histogram_sample {
-              name_hash: 10
-              sample: 100
-            }
-          }
-        }
-        packet {
-          trusted_packet_sequence_id: 1
-          timestamp: 0
-          incremental_state_cleared: true
-          track_event {
-            categories: "cat2"
-            type: 3
-            name_iid: 2
-            chrome_histogram_sample {
-              name_hash: 20
-            }
-          }
-        }
-        """),
-        query=Metric('chrome_histogram_hashes'),
-        out=TextProto(r"""
-        [perfetto.protos.chrome_histogram_hashes]: {
-          hash: 10
-          hash: 20
-        }
-        """))
-
-  # Chrome user events
-  def test_chrome_user_event_hashes(self):
-    return DiffTestBlueprint(
-        trace=TextProto(r"""
-        packet {
-          trusted_packet_sequence_id: 1
-          timestamp: 0
-          incremental_state_cleared: true
-          track_event {
-            categories: "cat1"
-            type: 3
-            name_iid: 1
-            chrome_user_event {
-              action_hash: 10
-            }
-          }
-        }
-        packet {
-          trusted_packet_sequence_id: 1
-          timestamp: 0
-          incremental_state_cleared: true
-          track_event {
-            categories: "cat2"
-            type: 3
-            name_iid: 2
-            chrome_user_event {
-              action_hash: 20
-            }
-          }
-        }
-        """),
-        query=Metric('chrome_user_event_hashes'),
-        out=TextProto(r"""
-        [perfetto.protos.chrome_user_event_hashes]: {
-          action_hash: 10
-          action_hash: 20
-        }
-        """))
-
-  # Chrome performance mark
-  def test_chrome_performance_mark_hashes(self):
-    return DiffTestBlueprint(
-        trace=TextProto(r"""
-        packet {
-          trusted_packet_sequence_id: 1
-          timestamp: 0
-          incremental_state_cleared: true
-          track_event {
-            categories: "cat1"
-            type: 3
-            name: "name1"
-            [perfetto.protos.ChromeTrackEvent.chrome_hashed_performance_mark] {
-              site_hash: 10
-              mark_hash: 100
-            }
-          }
-        }
-        packet {
-          trusted_packet_sequence_id: 1
-          timestamp: 0
-          incremental_state_cleared: true
-          track_event {
-            categories: "cat2"
-            type: 3
-            name: "name2"
-            [perfetto.protos.ChromeTrackEvent.chrome_hashed_performance_mark] {
-              site_hash: 20
-              mark_hash: 200
-            }
-          }
-        }
-        """),
-        query=Metric('chrome_performance_mark_hashes'),
-        out=TextProto(r"""
-        [perfetto.protos.chrome_performance_mark_hashes]: {
-          site_hash: 10
-          site_hash: 20
-          mark_hash: 100
-          mark_hash: 200
-        }
-        """))
-
-  # Chrome reliable range
-  def test_chrome_reliable_range(self):
-    return DiffTestBlueprint(
-        trace=Path('chrome_reliable_range.textproto'),
-        query=Path('chrome_reliable_range_test.sql'),
-        out=Csv("""
-        "start","reason","debug_limiting_upid","debug_limiting_utid"
-        12,"First slice for utid=2","[NULL]",2
-        """))
-
-  def test_chrome_reliable_range_cropping(self):
-    return DiffTestBlueprint(
-        trace=Path('chrome_reliable_range_cropping.textproto'),
-        query=Path('chrome_reliable_range_test.sql'),
-        out=Csv("""
-        "start","reason","debug_limiting_upid","debug_limiting_utid"
-        10000,"Range of interest packet","[NULL]",2
-        """))
-
-  def test_chrome_reliable_range_missing_processes(self):
-    return DiffTestBlueprint(
-        trace=Path('chrome_reliable_range_missing_processes.textproto'),
-        query=Path('chrome_reliable_range_test.sql'),
-        out=Csv("""
-        "start","reason","debug_limiting_upid","debug_limiting_utid"
-        1011,"Missing process data for upid=2",2,1
-        """))
-
-  def test_chrome_reliable_range_missing_browser_main(self):
-    return DiffTestBlueprint(
-        trace=Path('chrome_reliable_range_missing_browser_main.textproto'),
-        query=Path('chrome_reliable_range_test.sql'),
-        out=Csv("""
-        "start","reason","debug_limiting_upid","debug_limiting_utid"
-        1011,"Missing main thread for upid=1",1,1
-        """))
-
-  def test_chrome_reliable_range_missing_gpu_main(self):
-    return DiffTestBlueprint(
-        trace=Path('chrome_reliable_range_missing_gpu_main.textproto'),
-        query=Path('chrome_reliable_range_test.sql'),
-        out=Csv("""
-        "start","reason","debug_limiting_upid","debug_limiting_utid"
-        1011,"Missing main thread for upid=1",1,1
-        """))
-
-  def test_chrome_reliable_range_missing_renderer_main(self):
-    return DiffTestBlueprint(
-        trace=Path('chrome_reliable_range_missing_renderer_main.textproto'),
-        query=Path('chrome_reliable_range_test.sql'),
-        out=Csv("""
-        "start","reason","debug_limiting_upid","debug_limiting_utid"
-        1011,"Missing main thread for upid=1",1,1
-        """))
-
-  def test_chrome_reliable_range_non_chrome_process(self):
-    return DiffTestBlueprint(
-        # We need a trace with a large number of non-chrome slices, so that the
-        # reliable range is affected by their filtering.
-        trace=DataPath('example_android_trace_30s.pb'),
-        query=Path('chrome_reliable_range_test.sql'),
-        out=Csv("""
-        "start","reason","debug_limiting_upid","debug_limiting_utid"
-        0,"[NULL]","[NULL]","[NULL]"
-        """))
-
-  # Chrome slices
-  def test_chrome_slice_names(self):
-    return DiffTestBlueprint(
-        trace=TextProto(r"""
-        packet {
-          trusted_packet_sequence_id: 1
-          timestamp: 1000
-          track_event {
-            categories: "cat"
-            name: "Looper.Dispatch: class1"
-            type: 3
-          }
-        }
-        packet {
-          trusted_packet_sequence_id: 1
-          timestamp: 2000
-          track_event {
-            categories: "cat"
-            name: "name2"
-            type: 3
-          }
-        }
-        packet {
-          chrome_metadata {
-            chrome_version_code: 123
-          }
-        }
-        """),
-        query=Metric('chrome_slice_names'),
-        out=TextProto(r"""
-        [perfetto.protos.chrome_slice_names]: {
-          chrome_version_code: 123
-          slice_name: "Looper.Dispatch: class1"
-          slice_name: "name2"
-        }
-        """))
-
-  # Chrome tasks.
-  def test_chrome_tasks(self):
-    return DiffTestBlueprint(
-        trace=DataPath(
-            'chrome_page_load_all_categories_not_extended.pftrace.gz'),
-        query="""
-        INCLUDE PERFETTO MODULE chrome.tasks;
-
-        SELECT full_name as name, task_type, count() AS count
-        FROM chrome_tasks
-        GROUP BY full_name, task_type
-        HAVING count >= 5
-        ORDER BY count DESC, name;
-        """,
-        out=Path('chrome_tasks.out'))
-
-  def test_top_level_java_choreographer_slices_top_level_java_chrome_tasks(
-      self):
-    return DiffTestBlueprint(
-        trace=DataPath('top_level_java_choreographer_slices'),
-        query="""
-        INCLUDE PERFETTO MODULE chrome.tasks;
-
-        SELECT
-          full_name,
-          task_type
-        FROM chrome_tasks
-        WHERE category = "toplevel,Java"
-        AND ts < 263904000000000
-        GROUP BY full_name, task_type;
-        """,
-        out=Path(
-            'top_level_java_choreographer_slices_top_level_java_chrome_tasks_test.out'
-        ))
-
-  # Chrome stack samples.
-  def test_chrome_stack_samples_for_task(self):
-    return DiffTestBlueprint(
-        trace=DataPath('chrome_stack_traces_symbolized_trace.pftrace'),
-        query="""
-        SELECT RUN_METRIC('chrome/chrome_stack_samples_for_task.sql',
-            'target_duration_ms', '0.000001',
-            'thread_name', '"CrBrowserMain"',
-            'task_name', '"sendTouchEvent"');
-
-        SELECT
-          sample.description,
-          sample.ts,
-          sample.depth
-        FROM chrome_stack_samples_for_task sample
-        JOIN (
-            SELECT
-              ts,
-              dur
-            FROM slice
-            WHERE ts = 696373965001470
-        ) test_slice
-        ON sample.ts >= test_slice.ts
-          AND sample.ts <= test_slice.ts + test_slice.dur
-        ORDER BY sample.ts, sample.depth;
-        """,
-        out=Path('chrome_stack_samples_for_task_test.out'))
-
-  # Log messages.
-  def test_chrome_log_message(self):
-    return DiffTestBlueprint(
-        trace=TextProto(r"""
-        packet {
-          timestamp: 0
-          incremental_state_cleared: true
-          trusted_packet_sequence_id: 1
-          track_descriptor {
-            uuid: 12345
-            thread {
-              pid: 123
-              tid: 345
-            }
-            parent_uuid: 0
-            chrome_thread {
-              thread_type: THREAD_POOL_FG_WORKER
-            }
-          }
-        }
-
-        packet {
-          trusted_packet_sequence_id: 1
-          timestamp: 10
-          track_event {
-            track_uuid: 12345
-            categories: "cat1"
-            type: TYPE_INSTANT
-            name: "slice1"
-            log_message {
-                body_iid: 1
-                source_location_iid: 3
-            }
-          }
-          interned_data {
-            log_message_body {
-                iid: 1
-                body: "log message"
-            }
-            source_locations {
-                iid: 3
-                function_name: "func"
-                file_name: "foo.cc"
-                line_number: 123
-            }
-          }
-        }
-        """),
-        query="""
-        SELECT utid, tag, msg, prio FROM android_logs;
-        """,
-        # If the log_message_body doesn't have any priority, a default 4 (i.e.
-        # INFO) is assumed (otherwise the UI will not show the message).
-        out=Csv("""
-        "utid","tag","msg","prio"
-        1,"foo.cc:123","log message",4
-        """))
-
-  def test_chrome_log_message_priority(self):
-    return DiffTestBlueprint(
-        trace=TextProto(r"""
-        packet {
-          timestamp: 0
-          incremental_state_cleared: true
-          trusted_packet_sequence_id: 1
-          track_descriptor {
-            uuid: 12345
-            thread {
-              pid: 123
-              tid: 345
-            }
-            parent_uuid: 0
-            chrome_thread {
-              thread_type: THREAD_POOL_FG_WORKER
-            }
-          }
-        }
-
-        packet {
-          trusted_packet_sequence_id: 1
-          timestamp: 10
-          track_event {
-            track_uuid: 12345
-            categories: "cat1"
-            type: TYPE_INSTANT
-            name: "slice1"
-            log_message {
-                body_iid: 1
-                source_location_iid: 3
-                prio: PRIO_WARN
-            }
-          }
-          interned_data {
-            log_message_body {
-                iid: 1
-                body: "log message"
-            }
-            source_locations {
-                iid: 3
-                function_name: "func"
-                file_name: "foo.cc"
-                line_number: 123
-            }
-          }
-        }
-        """),
-        query="""
-        SELECT utid, tag, msg, prio FROM android_logs;
-        """,
-        out=Csv("""
-        "utid","tag","msg","prio"
-        1,"foo.cc:123","log message",5
-        """))
-
-  def test_chrome_log_message_args(self):
-    return DiffTestBlueprint(
-        trace=TextProto(r"""
-        packet {
-          timestamp: 0
-          incremental_state_cleared: true
-          trusted_packet_sequence_id: 1
-          track_descriptor {
-            uuid: 12345
-            thread {
-              pid: 123
-              tid: 345
-            }
-            parent_uuid: 0
-            chrome_thread {
-              thread_type: THREAD_POOL_FG_WORKER
-            }
-          }
-        }
-
-        packet {
-          trusted_packet_sequence_id: 1
-          timestamp: 10
-          track_event {
-            track_uuid: 12345
-            categories: "cat1"
-            type: TYPE_INSTANT
-            name: "slice1"
-            log_message {
-                body_iid: 1
-                source_location_iid: 3
-            }
-          }
-          interned_data {
-            log_message_body {
-                iid: 1
-                body: "log message"
-            }
-            source_locations {
-                iid: 3
-                function_name: "func"
-                file_name: "foo.cc"
-                line_number: 123
-            }
-          }
-        }
-        """),
-        query=Path('chrome_log_message_args_test.sql'),
-        out=Csv("""
-        "log_message","function_name","file_name","line_number"
-        "log message","func","foo.cc",123
-        """))
-
-  # Chrome custom navigation event names
-  def test_chrome_custom_navigation_tasks(self):
-    return DiffTestBlueprint(
-        trace=DataPath('chrome_custom_navigation_trace.gz'),
-        query="""
-        INCLUDE PERFETTO MODULE chrome.tasks;
-
-        SELECT full_name, task_type, count() AS count
-        FROM chrome_tasks
-        WHERE full_name GLOB 'FrameHost::BeginNavigation*'
-          OR full_name GLOB 'FrameHost::DidCommitProvisionalLoad*'
-          OR full_name GLOB 'FrameHost::DidCommitSameDocumentNavigation*'
-          OR full_name GLOB 'FrameHost::DidStopLoading*'
-        GROUP BY full_name, task_type
-        ORDER BY count DESC
-        LIMIT 50;
-        """,
-        out=Csv("""
-        "full_name","task_type","count"
-        "FrameHost::BeginNavigation (SUBFRAME)","navigation_task",5
-        "FrameHost::DidStopLoading (SUBFRAME)","navigation_task",3
-        "FrameHost::BeginNavigation (PRIMARY_MAIN_FRAME)","navigation_task",1
-        "FrameHost::DidCommitProvisionalLoad (SUBFRAME)","navigation_task",1
-        """))
-
-  # Chrome custom navigation event names
-  def test_chrome_histograms(self):
-    return DiffTestBlueprint(
-        trace=DataPath('chrome_5672_histograms.pftrace.gz'),
-        query="""
-        INCLUDE PERFETTO MODULE chrome.histograms;
-
-        SELECT 
-          name,
-          count() as count 
-        FROM chrome_histograms
-        GROUP BY name
-        ORDER BY count DESC, name
-        LIMIT 20;
-        """,
-        out=Csv("""
-        "name","count"
-        "Net.QuicSession.AsyncRead",19207
-        "Net.QuicSession.NumQueuedPacketsBeforeWrite",19193
-        "RendererScheduler.QueueingDuration.NormalPriority",9110
-        "Net.OnTransferSizeUpdated.Experimental.OverridenBy",8525
-        "Compositing.Renderer.AnimationUpdateOnMissingPropertyNode",3489
-        "Net.QuicConnection.WritePacketStatus",3099
-        "Net.QuicSession.PacketWriteTime.Synchronous",3082
-        "Net.QuicSession.SendPacketSize.ForwardSecure",3012
-        "Net.URLLoaderThrottleExecutionTime.WillStartRequest",1789
-        "Net.URLLoaderThrottleExecutionTime.BeforeWillProcessResponse",1773
-        "Net.URLLoaderThrottleExecutionTime.WillProcessResponse",1773
-        "UMA.StackProfiler.SampleInOrder",1534
-        "GPU.SharedImage.ContentConsumed",1037
-        "Gpu.Rasterization.Raster.MSAASampleCountLog2",825
-        "Scheduling.Renderer.DeadlineMode",637
-        "Blink.CullRect.UpdateTime",622
-        "Scheduling.Renderer.BeginImplFrameLatency2",591
-        "Net.QuicSession.CoalesceStreamFrameStatus",551
-        "API.StorageAccess.AllowedRequests2",541
-        "Net.HttpResponseCode",541
-        """))
-
-  # Trace proto content
-  def test_proto_content(self):
-    return DiffTestBlueprint(
-        trace=DataPath('chrome_scroll_without_vsync.pftrace'),
-        query=Path('proto_content_test.sql'),
-        out=Path('proto_content.out'))
-
-  def test_speedometer(self):
-    return DiffTestBlueprint(
-        trace=DataPath('speedometer.perfetto_trace.gz'),
-        query=Path('chrome_speedometer_test.sql'),
-        out=Path('chrome_speedometer.out'))
-
-  # TODO(mayzner): Uncomment when it works
-  # def test_proto_content_path(self):
-  #   return DiffTestBlueprint(
-  #       trace=DataPath('chrome_scroll_without_vsync.pftrace'),
-  #       query=Path('proto_content_path_test.sql'),
-  #       out=Csv("""
-  #       "total_size","field_type","field_name","parent_id","event_category","event_name"
-  #       137426,"TracePacket","[NULL]","[NULL]","[NULL]","[NULL]"
-  #       59475,"TrackEvent","#track_event",415,"[NULL]","[NULL]"
-  #       37903,"TrackEvent","#track_event",17,"[NULL]","[NULL]"
-  #       35904,"int32","#trusted_uid",17,"[NULL]","[NULL]"
-  #       35705,"TracePacket","[NULL]","[NULL]","input,benchmark","LatencyInfo.Flow"
-  #       29403,"TracePacket","[NULL]","[NULL]","cc,input","[NULL]"
-  #       24703,"ChromeLatencyInfo","#chrome_latency_info",18,"[NULL]","[NULL]"
-  #       22620,"uint64","#time_us",26,"[NULL]","[NULL]"
-  #       18711,"TrackEvent","#track_event",1467,"[NULL]","[NULL]"
-  #       15606,"uint64","#timestamp",17,"[NULL]","[NULL]"
-  #       """))
diff --git a/test/trace_processor/diff_tests/graphics/composition_layer.py b/test/trace_processor/diff_tests/graphics/composition_layer.py
deleted file mode 100644
index b69bab2..0000000
--- a/test/trace_processor/diff_tests/graphics/composition_layer.py
+++ /dev/null
@@ -1,46 +0,0 @@
-#!/usr/bin/env python3
-# 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.
-
-# This synthetic trace tests handling of the mm_id field in the rss_stat
-# event when mm_structs are reused on process death.
-
-from os import sys, path
-
-import synth_common
-
-trace = synth_common.create_trace()
-
-trace.add_packet(ts=1)
-trace.add_process(10, 1, "parent_process")
-trace.add_process(11, 10, "child_process")
-
-trace.add_ftrace_packet(1)
-
-trace.add_print(ts=99, tid=11, buf='C|10|HWComposer: Total Layer for PrimaryDisplay(0)|7')
-trace.add_print(ts=100, tid=11, buf='C|10|HWComposer: Total Layer for PrimaryDisplay(0)|5')
-trace.add_print(ts=101, tid=11, buf='C|10|HWComposer: Total Layer for PrimaryDisplay(0)|6')
-trace.add_print(ts=102, tid=11, buf='C|10|HWComposer: Total Layer for PrimaryDisplay(0)|0')
-trace.add_print(ts=103, tid=11, buf='C|10|HWComposer: Total Layer for PrimaryDisplay(0)|4')
-trace.add_print(ts=104, tid=11, buf='C|10|HWComposer: Total Layer for PrimaryDisplay(0)|6')
-trace.add_print(ts=105, tid=11, buf='C|10|HWComposer: Total Layer for PrimaryDisplay(0)|0')
-trace.add_print(ts=106, tid=11, buf='C|10|HWComposer: Total Layer for PrimaryDisplay(0)|4')
-trace.add_print(ts=107, tid=11, buf='C|10|HWComposer: Total Layer for PrimaryDisplay(0)|5')
-trace.add_print(ts=108, tid=11, buf='C|10|HWComposer: Total Layer for PrimaryDisplay(0)|0')
-trace.add_print(ts=108, tid=11, buf='C|10|HWComposer: Total Layer for PrimaryDisplay(0)|3')
-trace.add_print(ts=108, tid=11, buf='C|10|HWComposer: Total Layer for PrimaryDisplay(0)|0')
-
-trace.add_print(ts=109, tid=11, buf='C|10|HWComposer: Total Layer for SecondaryDisplay(1)|5')
-
-sys.stdout.buffer.write(trace.trace.SerializeToString())
diff --git a/test/trace_processor/diff_tests/graphics/tests.py b/test/trace_processor/diff_tests/graphics/tests.py
deleted file mode 100644
index 04a86bd..0000000
--- a/test/trace_processor/diff_tests/graphics/tests.py
+++ /dev/null
@@ -1,496 +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 Graphics(TestSuite):
-  # Contains tests for graphics related events and tables. Graphics frame
-  # trace tests.
-  def test_graphics_frame_events(self):
-    return DiffTestBlueprint(
-        trace=Path('graphics_frame_events.py'),
-        query="""
-        SELECT ts, gpu_track.name AS track_name, dur, frame_slice.name AS slice_name,
-          frame_number, layer_name
-        FROM gpu_track
-        LEFT JOIN frame_slice ON gpu_track.id = frame_slice.track_id
-        WHERE scope = 'graphics_frame_event'
-        ORDER BY ts;
-        """,
-        out=Path('graphics_frame_events.out'))
-
-  # GPU Memory ftrace packets
-  def test_gpu_mem_total(self):
-    return DiffTestBlueprint(
-        trace=Path('gpu_mem_total.py'),
-        query=Path('gpu_mem_total_test.sql'),
-        out=Csv("""
-        "name","unit","description","ts","pid","value"
-        "GPU Memory","7","Total GPU memory used by the entire system",0,"[NULL]",123
-        "GPU Memory","7","Total GPU memory used by this process",0,1,100
-        "GPU Memory","7","Total GPU memory used by the entire system",5,"[NULL]",256
-        "GPU Memory","7","Total GPU memory used by this process",5,1,233
-        "GPU Memory","7","Total GPU memory used by the entire system",10,"[NULL]",123
-        "GPU Memory","7","Total GPU memory used by this process",10,1,0
-        """))
-
-  def test_gpu_mem_total_after_free_gpu_mem_total(self):
-    return DiffTestBlueprint(
-        trace=Path('gpu_mem_total_after_free.py'),
-        query=Path('gpu_mem_total_test.sql'),
-        out=Csv("""
-        "name","unit","description","ts","pid","value"
-        "GPU Memory","7","Total GPU memory used by this process",0,1,100
-        "GPU Memory","7","Total GPU memory used by this process",5,1,233
-        "GPU Memory","7","Total GPU memory used by this process",10,1,50
-        """))
-
-  # Clock sync
-  def test_clock_sync(self):
-    return DiffTestBlueprint(
-        trace=Path('clock_sync.py'),
-        query="""
-        SELECT ts, cast(value AS integer) AS int_value
-        FROM counters
-        WHERE name GLOB 'gpu_counter*';
-        """,
-        out=Csv("""
-        "ts","int_value"
-        1,3
-        102,5
-        1003,7
-        1005,9
-        2006,11
-        2010,12
-        2013,13
-        3007,14
-        3010,15
-        """))
-
-  # 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'),
-        query=Metric('android_surfaceflinger'),
-        out=TextProto(r"""
-        android_surfaceflinger {
-          missed_frames: 3
-          missed_hwc_frames: 0
-          missed_gpu_frames: 0
-          missed_frame_rate: 0.42857142857142855 # = 3/7
-          gpu_invocations: 0
-          metrics_per_display: {
-            display_id: "101"
-            missed_frames: 2
-            missed_hwc_frames: 0
-            missed_gpu_frames: 0
-            missed_frame_rate: 0.5
-          }
-          metrics_per_display: {
-            display_id: "102"
-            missed_frames: 1
-            missed_hwc_frames: 0
-            missed_gpu_frames: 0
-            missed_frame_rate: 0.33333333333333333
-          }
-        }
-        """))
-
-  def test_surfaceflinger_gpu_invocation(self):
-    return DiffTestBlueprint(
-        trace=Path('surfaceflinger_gpu_invocation.py'),
-        query=Metric('android_surfaceflinger'),
-        out=TextProto(r"""
-        android_surfaceflinger {
-          missed_frames: 0
-          missed_hwc_frames: 0
-          missed_gpu_frames: 0
-          gpu_invocations: 4
-          avg_gpu_waiting_dur_ms: 4
-          total_non_empty_gpu_waiting_dur_ms: 11
-        }
-        """))
-
-  # GPU metrics
-  def test_gpu_metric(self):
-    return DiffTestBlueprint(
-        trace=Path('gpu_metric.py'),
-        query=Metric('android_gpu'),
-        out=TextProto(r"""
-        android_gpu {
-          processes {
-            name: "app_1"
-            mem_max: 8
-            mem_min: 2
-            mem_avg: 3
-          }
-          processes {
-            name: "app_2"
-            mem_max: 10
-            mem_min: 6
-            mem_avg: 8
-          }
-          mem_max: 4
-          mem_min: 1
-          mem_avg: 2
-        }
-        """))
-
-  def test_gpu_frequency_metric(self):
-    return DiffTestBlueprint(
-        trace=Path('gpu_frequency_metric.textproto'),
-        query=Metric('android_gpu'),
-        out=Path('gpu_frequency_metric.out'))
-
-  # Android Jank CUJ metric
-  def test_android_jank_cuj(self):
-    return DiffTestBlueprint(
-        trace=Path('android_jank_cuj.py'),
-        query=Metric('android_jank_cuj'),
-        out=Path('android_jank_cuj.out'))
-
-  def test_android_jank_cuj_query(self):
-    return DiffTestBlueprint(
-        trace=Path('android_jank_cuj.py'),
-        query=Path('android_jank_cuj_query_test.sql'),
-        out=Path('android_jank_cuj_query.out'))
-
-  # Frame Timeline event trace tests
-  def test_expected_frame_timeline_events(self):
-    return DiffTestBlueprint(
-        trace=Path('frame_timeline_events.py'),
-        query=Path('expected_frame_timeline_events_test.sql'),
-        out=Csv("""
-        "ts","dur","pid","display_frame_token","surface_frame_token","layer_name"
-        20,6,666,2,0,"[NULL]"
-        21,15,1000,4,1,"Layer1"
-        40,6,666,4,0,"[NULL]"
-        41,15,1000,6,5,"Layer1"
-        80,6,666,6,0,"[NULL]"
-        90,16,1000,8,7,"Layer1"
-        120,6,666,8,0,"[NULL]"
-        140,6,666,12,0,"[NULL]"
-        150,20,1000,15,14,"Layer1"
-        170,6,666,15,0,"[NULL]"
-        200,6,666,17,0,"[NULL]"
-        220,10,666,18,0,"[NULL]"
-        """))
-
-  def test_actual_frame_timeline_events(self):
-    return DiffTestBlueprint(
-        trace=Path('frame_timeline_events.py'),
-        query=Path('actual_frame_timeline_events_test.sql'),
-        out=Path('actual_frame_timeline_events.out'))
-
-  # Composition layer
-  def test_composition_layer_count(self):
-    return DiffTestBlueprint(
-        trace=Path('composition_layer.py'),
-        query="""
-        SELECT RUN_METRIC('android/android_hwcomposer.sql');
-
-        SELECT display_id, AVG(value)
-        FROM total_layers
-        GROUP BY display_id;
-        """,
-        out=Csv("""
-        "display_id","AVG(value)"
-        "0",3.000000
-        "1",5.000000
-        """))
-
-  # G2D metrics TODO(rsavitski): find a real trace and double-check that the
-  # is realistic. One kernel's source I checked had tgid=0 for all counter
-  # Initial support was added/discussed in b/171296908.
-  def test_g2d_metrics(self):
-    return DiffTestBlueprint(
-        trace=Path('g2d_metrics.textproto'),
-        query=Metric('g2d'),
-        out=Path('g2d_metrics.out'))
-
-  # Composer execution
-  def test_composer_execution(self):
-    return DiffTestBlueprint(
-        trace=Path('composer_execution.py'),
-        query="""
-        SELECT RUN_METRIC('android/composer_execution.sql',
-          'output', 'hwc_execution_spans');
-
-        SELECT
-          validation_type,
-          display_id,
-          COUNT(*) AS count,
-          SUM(execution_time_ns) AS total
-        FROM hwc_execution_spans
-        GROUP BY validation_type, display_id
-        ORDER BY validation_type, display_id;
-        """,
-        out=Csv("""
-        "validation_type","display_id","count","total"
-        "separated_validation","1",1,200
-        "skipped_validation","0",2,200
-        "skipped_validation","1",1,100
-        "unknown","1",1,0
-        "unskipped_validation","0",1,200
-        """))
-
-  # Display metrics
-  def test_display_metrics(self):
-    return DiffTestBlueprint(
-        trace=Path('display_metrics.py'),
-        query=Metric('display_metrics'),
-        out=TextProto(r"""
-        display_metrics {
-          total_duplicate_frames: 0
-          duplicate_frames_logged: 0
-          total_dpu_underrun_count: 0
-          refresh_rate_switches: 5
-          refresh_rate_stats {
-            refresh_rate_fps: 60
-            count: 2
-            total_dur_ms: 2
-            avg_dur_ms: 1
-          }
-          refresh_rate_stats {
-            refresh_rate_fps: 90
-            count: 2
-            total_dur_ms: 2
-            avg_dur_ms: 1
-          }
-          refresh_rate_stats {
-            refresh_rate_fps: 120
-            count: 1
-            total_dur_ms: 2
-            avg_dur_ms: 2
-          }
-          update_power_state {
-            avg_runtime_micro_secs: 4000
-          }
-        }
-        """))
-
-  # DPU vote clock and bandwidth
-  def test_dpu_vote_clock_bw(self):
-    return DiffTestBlueprint(
-        trace=Path('dpu_vote_clock_bw.textproto'),
-        query=Metric('android_hwcomposer'),
-        out=TextProto(r"""
-        android_hwcomposer {
-          skipped_validation_count: 0
-          unskipped_validation_count: 0
-          separated_validation_count: 0
-          unknown_validation_count: 0
-          dpu_vote_metrics {
-            tid: 237
-            avg_dpu_vote_clock: 206250
-            avg_dpu_vote_avg_bw: 210000
-            avg_dpu_vote_peak_bw: 205000
-            avg_dpu_vote_rt_bw: 271000
-          }
-          dpu_vote_metrics {
-            tid: 299
-            avg_dpu_vote_clock: 250000
-          }
-        }
-        """))
-
-  # Video 4 Linux 2 related tests
-  def test_v4l2_vidioc_slice(self):
-    return DiffTestBlueprint(
-        trace=Path('v4l2_vidioc.textproto'),
-        query="""
-        SELECT ts, dur, name
-        FROM slice
-        WHERE category = 'Video 4 Linux 2';
-        """,
-        out=Csv("""
-        "ts","dur","name"
-        593268475912,0,"VIDIOC_QBUF minor=0 seq=0 type=9 index=19"
-        593268603800,0,"VIDIOC_QBUF minor=0 seq=0 type=9 index=20"
-        593528238295,0,"VIDIOC_DQBUF minor=0 seq=0 type=9 index=19"
-        593544028229,0,"VIDIOC_DQBUF minor=0 seq=0 type=9 index=20"
-        """))
-
-  def test_v4l2_vidioc_flow(self):
-    return DiffTestBlueprint(
-        trace=Path('v4l2_vidioc.textproto'),
-        query="""
-        SELECT qbuf.ts, qbuf.dur, qbuf.name, dqbuf.ts, dqbuf.dur, dqbuf.name
-        FROM flow
-        JOIN slice qbuf ON flow.slice_out = qbuf.id
-        JOIN slice dqbuf ON flow.slice_in = dqbuf.id;
-        """,
-        out=Path('v4l2_vidioc_flow.out'))
-
-  def test_virtio_video_slice(self):
-    return DiffTestBlueprint(
-        trace=Path('virtio_video.textproto'),
-        query="""
-        SELECT slice.ts, slice.dur, slice.name, track.name
-        FROM slice
-        JOIN track ON slice.track_id = track.id;
-        """,
-        out=Csv("""
-        "ts","dur","name","name"
-        593125003271,84500592,"Resource #102","virtio_video stream #4 OUTPUT"
-        593125003785,100000,"RESOURCE_QUEUE","virtio_video stream #4 Requests"
-        593125084611,709696,"Resource #62","virtio_video stream #3 OUTPUT"
-        593125084935,100000,"RESOURCE_QUEUE","virtio_video stream #3 Requests"
-        593125794194,100000,"RESOURCE_QUEUE","virtio_video stream #3 Responses"
-        593209502603,100000,"RESOURCE_QUEUE","virtio_video stream #4 Responses"
-        """))
-
-  # virtgpu (drm/virtio) related tests
-  def test_virtio_gpu(self):
-    return DiffTestBlueprint(
-        trace=Path('virtio_gpu.textproto'),
-        query="""
-        SELECT
-          ts,
-          dur,
-          name
-        FROM
-          slice
-        ORDER BY ts;
-        """,
-        out=Csv("""
-        "ts","dur","name"
-        1345090723759,1180312,"SUBMIT_3D"
-        1345090746311,1167135,"CTX_DETACH_RESOURCE"
-        """))
-
-  # TODO(b/294866695): Reenable
-  # mali GPU events
-  #def test_mali(self):
-  #  return DiffTestBlueprint(
-  #      trace=TextProto(r"""
-  #      packet {
-  #        ftrace_events {
-  #          cpu: 2
-  #          event {
-  #            timestamp: 751796307210
-  #            pid: 2857
-  #            mali_mali_KCPU_CQS_WAIT_START {
-  #              info_val1: 1
-  #              info_val2: 0
-  #              kctx_tgid: 2201
-  #              kctx_id: 10
-  #              id: 0
-  #            }
-  #          }
-  #          event {
-  #            timestamp: 751800621175
-  #            pid: 2857
-  #            mali_mali_KCPU_CQS_WAIT_END {
-  #              info_val1: 412313493488
-  #              info_val2: 0
-  #              kctx_tgid: 2201
-  #              kctx_id: 10
-  #              id: 0
-  #            }
-  #          }
-  #          event {
-  #            timestamp: 751800638997
-  #            pid: 2857
-  #            mali_mali_KCPU_CQS_SET {
-  #              info_val1: 412313493480
-  #              info_val2: 0
-  #              kctx_tgid: 2201
-  #              kctx_id: 10
-  #              id: 0
-  #            }
-  #          }
-  #        }
-  #      }
-  #      """),
-  #      query="""
-  #      SELECT ts, dur, name FROM slice WHERE name GLOB "mali_KCPU_CQS*";
-  #      """,
-  #      out=Csv("""
-  #      "ts","dur","name"
-  #      751796307210,4313965,"mali_KCPU_CQS_WAIT"
-  #      751800638997,0,"mali_KCPU_CQS_SET"
-  #      """))
-
-  #def test_mali_fence(self):
-  #  return DiffTestBlueprint(
-  #      trace=TextProto(r"""
-  #      packet {
-  #        ftrace_events {
-  #          cpu: 2
-  #          event {
-  #            timestamp: 751796307210
-  #            pid: 2857
-  #            mali_mali_KCPU_FENCE_WAIT_START {
-  #              info_val1: 1
-  #              info_val2: 0
-  #              kctx_tgid: 2201
-  #              kctx_id: 10
-  #              id: 0
-  #            }
-  #          }
-  #          event {
-  #            timestamp: 751800621175
-  #            pid: 2857
-  #            mali_mali_KCPU_FENCE_WAIT_END {
-  #              info_val1: 412313493488
-  #              info_val2: 0
-  #              kctx_tgid: 2201
-  #              kctx_id: 10
-  #              id: 0
-  #            }
-  #          }
-  #          event {
-  #            timestamp: 751800638997
-  #            pid: 2857
-  #            mali_mali_KCPU_FENCE_SIGNAL {
-  #              info_val1: 412313493480
-  #              info_val2: 0
-  #              kctx_tgid: 2201
-  #              kctx_id: 10
-  #              id: 0
-  #            }
-  #          }
-  #        }
-  #      }
-  #      """),
-  #      query="""
-  #      SELECT ts, dur, name FROM slice WHERE name GLOB "mali_KCPU_FENCE*";
-  #      """,
-  #      out=Csv("""
-  #      "ts","dur","name"
-  #      751796307210,4313965,"mali_KCPU_FENCE_WAIT"
-  #      751800638997,0,"mali_KCPU_FENCE_SIGNAL"
-  #      """))
diff --git a/test/trace_processor/diff_tests/include_index.py b/test/trace_processor/diff_tests/include_index.py
index b131e43..7430548 100644
--- a/test/trace_processor/diff_tests/include_index.py
+++ b/test/trace_processor/diff_tests/include_index.py
@@ -25,164 +25,213 @@
     os.path.dirname(os.path.abspath(__file__)))
 sys.path.append(TRACE_PROCESSOR_TEST_DIR)
 
-from diff_tests.android.tests import Android
-from diff_tests.android.tests_bugreport import AndroidBugreport
-from diff_tests.android.tests_games import AndroidGames
-from diff_tests.android.tests_surfaceflinger_layers import SurfaceFlingerLayers
-from diff_tests.android.tests_surfaceflinger_transactions import SurfaceFlingerTransactions
-from diff_tests.atrace.tests import Atrace
-from diff_tests.atrace.tests_error_handling import AtraceErrorHandling
-from diff_tests.camera.tests import Camera
-from diff_tests.chrome.tests import Chrome
-from diff_tests.chrome.tests_args import ChromeArgs
-from diff_tests.chrome.tests_memory_snapshots import ChromeMemorySnapshots
-from diff_tests.chrome.tests_processes import ChromeProcesses
-from diff_tests.chrome.tests_rail_modes import ChromeRailModes
-from diff_tests.chrome.tests_scroll_jank import ChromeScrollJank
-from diff_tests.chrome.tests_touch_gesture import ChromeTouchGesture
-from diff_tests.codecs.tests import Codecs
-from diff_tests.cros.tests import Cros
-from diff_tests.dynamic.tests import Dynamic
-from diff_tests.fs.tests import Fs
-from diff_tests.fuchsia.tests import Fuchsia
-from diff_tests.functions.tests import Functions
-from diff_tests.graphics.tests import Graphics
-from diff_tests.graphics.tests_drm_related_ftrace_events import \
-    GraphicsDrmRelatedFtraceEvents
-from diff_tests.graphics.tests_gpu_trace import GraphicsGpuTrace
-from diff_tests.memory.tests import Memory
-from diff_tests.memory.tests_metrics import MemoryMetrics
-from diff_tests.network.tests import Network
-from diff_tests.parsing.tests import Parsing
-from diff_tests.parsing.tests_debug_annotation import ParsingDebugAnnotation
-from diff_tests.parsing.tests_memory_counters import ParsingMemoryCounters
-from diff_tests.parsing.tests_rss_stats import ParsingRssStats
-from diff_tests.perfetto_sql.tests import PerfettoSql
-from diff_tests.performance.tests import Performance
-from diff_tests.pkvm.tests import Pkvm
-from diff_tests.power.tests import Power
-from diff_tests.power.tests_energy_breakdown import PowerEnergyBreakdown
-from diff_tests.power.tests_entity_state_residency import EntityStateResidency
-from diff_tests.power.tests_linux_sysfs_power import LinuxSysfsPower
-from diff_tests.power.tests_power_rails import PowerPowerRails
-from diff_tests.power.tests_voltage_and_scaling import PowerVoltageAndScaling
-from diff_tests.process_tracking.tests import ProcessTracking
-from diff_tests.profiling.tests import Profiling
-from diff_tests.profiling.tests_heap_graph import ProfilingHeapGraph
-from diff_tests.profiling.tests_heap_profiling import ProfilingHeapProfiling
-from diff_tests.profiling.tests_llvm_symbolizer import ProfilingLlvmSymbolizer
-from diff_tests.profiling.tests_metrics import ProfilingMetrics
-from diff_tests.scheduler.tests import Scheduler
-from diff_tests.slices.tests import Slices
-from diff_tests.smoke.tests import Smoke
-from diff_tests.smoke.tests_compute_metrics import SmokeComputeMetrics
-from diff_tests.smoke.tests_json import SmokeJson
-from diff_tests.smoke.tests_sched_events import SmokeSchedEvents
-from diff_tests.span_join.tests_left_join import SpanJoinLeftJoin
-from diff_tests.span_join.tests_outer_join import SpanJoinOuterJoin
-from diff_tests.span_join.tests_regression import SpanJoinRegression
-from diff_tests.span_join.tests_smoke import SpanJoinSmoke
-from diff_tests.startup.tests import Startup
-from diff_tests.startup.tests_broadcasts import StartupBroadcasts
-from diff_tests.startup.tests_lock_contention import StartupLockContention
-from diff_tests.startup.tests_metrics import StartupMetrics
+from diff_tests.metrics.android.tests import AndroidMetrics
+from diff_tests.metrics.camera.tests import Camera
+from diff_tests.metrics.chrome.tests import ChromeMetrics
+from diff_tests.metrics.chrome.tests_args import ChromeArgs
+from diff_tests.metrics.chrome.tests_processes import ChromeProcesses
+from diff_tests.metrics.chrome.tests_rail_modes import ChromeRailModes
+from diff_tests.metrics.chrome.tests_scroll_jank import ChromeScrollJankMetrics
+from diff_tests.metrics.chrome.tests_touch_gesture import ChromeTouchGesture
+from diff_tests.metrics.codecs.tests import Codecs
+from diff_tests.metrics.frame_timeline.tests import FrameTimeline
+from diff_tests.metrics.graphics.tests import GraphicsMetrics
+from diff_tests.metrics.irq.tests import IRQ
+from diff_tests.metrics.memory.tests import MemoryMetrics
+from diff_tests.metrics.network.tests import NetworkMetrics
+from diff_tests.metrics.power.tests import Power
+from diff_tests.metrics.profiling.tests import ProfilingMetrics
+from diff_tests.metrics.startup.tests import Startup
+from diff_tests.metrics.startup.tests_broadcasts import StartupBroadcasts
+from diff_tests.metrics.startup.tests_lock_contention import StartupLockContention
+from diff_tests.metrics.startup.tests_metrics import StartupMetrics
+from diff_tests.metrics.webview.tests import WebView
+from diff_tests.parser.android.tests import AndroidParser
+from diff_tests.parser.android.tests_bugreport import AndroidBugreport
+from diff_tests.parser.android.tests_games import AndroidGames
+from diff_tests.parser.android.tests_surfaceflinger_layers import SurfaceFlingerLayers
+from diff_tests.parser.android.tests_surfaceflinger_transactions import SurfaceFlingerTransactions
+from diff_tests.parser.android_fs.tests import AndroidFs
+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
+from diff_tests.parser.chrome.tests_memory_snapshots import ChromeMemorySnapshots
+from diff_tests.parser.cros.tests import Cros
+from diff_tests.parser.fs.tests import Fs
+from diff_tests.parser.fuchsia.tests import Fuchsia
+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
+from diff_tests.parser.memory.tests import MemoryParser
+from diff_tests.parser.network.tests import NetworkParser
+from diff_tests.parser.parsing.tests import Parsing
+from diff_tests.parser.parsing.tests_debug_annotation import ParsingDebugAnnotation
+from diff_tests.parser.parsing.tests_memory_counters import ParsingMemoryCounters
+from diff_tests.parser.parsing.tests_rss_stats import ParsingRssStats
+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
+from diff_tests.parser.power.tests_power_rails import PowerPowerRails
+from diff_tests.parser.power.tests_voltage_and_scaling import PowerVoltageAndScaling
+from diff_tests.parser.process_tracking.tests import ProcessTracking
+from diff_tests.parser.profiling.tests import Profiling
+from diff_tests.parser.profiling.tests_heap_graph import ProfilingHeapGraph
+from diff_tests.parser.profiling.tests_heap_profiling import ProfilingHeapProfiling
+from diff_tests.parser.profiling.tests_llvm_symbolizer import ProfilingLlvmSymbolizer
+from diff_tests.parser.sched.tests import SchedParser
+from diff_tests.parser.smoke.tests import Smoke
+from diff_tests.parser.smoke.tests_compute_metrics import SmokeComputeMetrics
+from diff_tests.parser.smoke.tests_json import SmokeJson
+from diff_tests.parser.smoke.tests_sched_events import SmokeSchedEvents
+from diff_tests.parser.track_event.tests import TrackEvent
+from diff_tests.parser.translated_args.tests import TranslatedArgs
+from diff_tests.parser.ufs.tests import Ufs
+from diff_tests.stdlib.android.tests import AndroidStdlib
+from diff_tests.stdlib.common.tests import StdlibCommon
+from diff_tests.stdlib.chrome.tests import ChromeStdlib
+from diff_tests.stdlib.chrome.tests_chrome_interactions import ChromeInteractions
+from diff_tests.stdlib.chrome.tests_scroll_jank import ChromeScrollJankStdlib
+from diff_tests.stdlib.dynamic_tables.tests import DynamicTables
+from diff_tests.stdlib.pkvm.tests import Pkvm
+from diff_tests.stdlib.slices.tests import Slices
+from diff_tests.stdlib.span_join.tests_left_join import SpanJoinLeftJoin
+from diff_tests.stdlib.span_join.tests_outer_join import SpanJoinOuterJoin
+from diff_tests.stdlib.span_join.tests_regression import SpanJoinRegression
+from diff_tests.stdlib.span_join.tests_smoke import SpanJoinSmoke
+from diff_tests.stdlib.timestamps.tests import Timestamps
+from diff_tests.syntax.functions.tests import Functions
+from diff_tests.syntax.perfetto_sql.tests import PerfettoSql
 from diff_tests.tables.tests import Tables
 from diff_tests.tables.tests_counters import TablesCounters
 from diff_tests.tables.tests_sched import TablesSched
-from diff_tests.time.tests import Time
-from diff_tests.track_event.tests import TrackEvent
-from diff_tests.translation.tests import Translation
-from diff_tests.ufs.tests import Ufs
-from diff_tests.webview.tests import WebView
-from diff_tests.android_fs.tests import AndroidFs
 
 sys.path.pop()
 
 
 def fetch_all_diff_tests(index_path: str) -> List['testing.TestCase']:
-  return [
-      *Android(index_path, 'android', 'Android').fetch(),
-      *AndroidBugreport(index_path, 'android', 'AndroidBugreport').fetch(),
-      *AndroidFs(index_path, 'android_fs', 'AndroidFs').fetch(),
-      *AndroidGames(index_path, 'android', 'AndroidGames').fetch(),
-      *Atrace(index_path, 'atrace', 'Atrace').fetch(),
-      *AtraceErrorHandling(index_path, 'atrace', 'AtraceErrorHandling').fetch(),
-      *Camera(index_path, 'camera', 'Camera').fetch(),
-      *ChromeScrollJank(index_path, 'chrome', 'ChromeScrollJank').fetch(),
-      *ChromeTouchGesture(index_path, 'chrome', 'ChromeTouchGesture').fetch(),
-      *ChromeMemorySnapshots(index_path, 'chrome',
+  parser_tests = [
+      *AndroidBugreport(index_path, 'parser/android',
+                        'AndroidBugreport').fetch(),
+      *AndroidFs(index_path, 'parser/android_fs', 'AndroidFs').fetch(),
+      *AndroidGames(index_path, 'parser/android', 'AndroidGames').fetch(),
+      *AndroidParser(index_path, 'parser/android', 'AndroidParser').fetch(),
+      *Atrace(index_path, 'parser/atrace', 'Atrace').fetch(),
+      *AtraceErrorHandling(index_path, 'parser/atrace',
+                           'AtraceErrorHandling').fetch(),
+      *ChromeMemorySnapshots(index_path, 'parser/chrome',
                              'ChromeMemorySnapshots').fetch(),
-      *ChromeRailModes(index_path, 'chrome', 'ChromeRailModes').fetch(),
-      *ChromeProcesses(index_path, 'chrome', 'ChromeProcesses').fetch(),
-      *ChromeArgs(index_path, 'chrome', 'ChromeArgs').fetch(),
-      *Chrome(index_path, 'chrome', 'Chrome').fetch(),
-      *Codecs(index_path, 'codecs', 'Codecs').fetch(),
-      *Cros(index_path, 'cros', 'Cros').fetch(),
-      *Dynamic(index_path, 'dynamic', 'Dynamic').fetch(),
-      *EntityStateResidency(index_path, 'power',
-                            'EntityStateResidency').fetch(),
-      *Fs(index_path, 'fs', 'Fs').fetch(),
-      *Fuchsia(index_path, 'fuchsia', 'Fuchsia').fetch(),
-      *Functions(index_path, 'functions', 'Functions').fetch(),
-      *Graphics(index_path, 'graphics', 'Graphics').fetch(),
-      *GraphicsGpuTrace(index_path, 'graphics', 'GraphicsGpuTrace').fetch(),
-      *GraphicsDrmRelatedFtraceEvents(index_path, 'graphics',
+      *ChromeParser(index_path, 'parser/chrome', 'ChromeParser').fetch(),
+      *Cros(index_path, 'parser/cros', 'Cros').fetch(),
+      *Fs(index_path, 'parser/fs', 'Fs').fetch(),
+      *Fuchsia(index_path, 'parser/fuchsia', 'Fuchsia').fetch(),
+      *GraphicsDrmRelatedFtraceEvents(index_path, 'parser/graphics',
                                       'GraphicsDrmRelatedFtraceEvents').fetch(),
-      *Ufs(index_path, 'ufs', 'Ufs').fetch(),
-      *LinuxSysfsPower(index_path, 'power', 'LinuxSysfsPower').fetch(),
-      *Memory(index_path, 'memory', 'Memory').fetch(),
-      *MemoryMetrics(index_path, 'memory', 'MemoryMetrics').fetch(),
-      *Network(index_path, 'network', 'Network').fetch(),
-      *Parsing(index_path, 'parsing', 'Parsing').fetch(),
-      *ParsingDebugAnnotation(index_path, 'parsing',
-                              'ParsingDebugAnnotation').fetch(),
-      *ParsingRssStats(index_path, 'parsing', 'ParsingRssStats').fetch(),
-      *ParsingMemoryCounters(index_path, 'parsing',
-                             'ParsingMemoryCounters').fetch(),
-      *PerfettoSql(index_path, 'perfetto_sql', 'PerfettoSql').fetch(),
-      *Performance(index_path, 'performance', 'Performance').fetch(),
-      *Pkvm(index_path, 'pkvm', 'Pkvm').fetch(),
-      *Power(index_path, 'power', 'Power').fetch(),
-      *PowerPowerRails(index_path, 'power', 'PowerPowerRails').fetch(),
-      *PowerVoltageAndScaling(index_path, 'power',
-                              'PowerVoltageAndScaling').fetch(),
-      *PowerEnergyBreakdown(index_path, 'power',
+      *GraphicsGpuTrace(index_path, 'parser/graphics',
+                        'GraphicsGpuTrace').fetch(),
+      *GraphicsParser(index_path, 'parser/graphics', 'GraphicsParser').fetch(),
+      *MemoryParser(index_path, 'parser/memory', 'MemoryParser').fetch(),
+      *NetworkParser(index_path, 'parser/network', 'NetworkParser').fetch(),
+      *PowerEnergyBreakdown(index_path, 'parser/power',
                             'PowerEnergyBreakdown').fetch(),
-      *ProcessTracking(index_path, 'process_tracking',
+      *PowerPowerRails(index_path, 'parser/power', 'PowerPowerRails').fetch(),
+      *PowerVoltageAndScaling(index_path, 'parser/power',
+                              'PowerVoltageAndScaling').fetch(),
+      *EntityStateResidency(index_path, 'parser/power',
+                            'EntityStateResidency').fetch(),
+      *LinuxSysfsPower(index_path, 'parser/power', 'LinuxSysfsPower').fetch(),
+      *ProcessTracking(index_path, 'parser/process_tracking',
                        'ProcessTracking').fetch(),
-      *Profiling(index_path, 'profiling', 'Profiling').fetch(),
-      *ProfilingHeapProfiling(index_path, 'profiling',
-                              'ProfilingHeapProfiling').fetch(),
-      *ProfilingHeapGraph(index_path, 'profiling',
+      *Profiling(index_path, 'parser/profiling', 'Profiling').fetch(),
+      *ProfilingHeapGraph(index_path, 'parser/profiling',
                           'ProfilingHeapGraph').fetch(),
-      *ProfilingMetrics(index_path, 'profiling', 'ProfilingMetrics').fetch(),
-      *ProfilingLlvmSymbolizer(index_path, 'profiling',
+      *ProfilingHeapProfiling(index_path, 'parser/profiling',
+                              'ProfilingHeapProfiling').fetch(),
+      *ProfilingLlvmSymbolizer(index_path, 'parser/profiling',
                                'ProfilingLlvmSymbolizer').fetch(),
-      *Scheduler(index_path, 'scheduler', 'Scheduler').fetch(),
-      *Slices(index_path, 'slices', 'Slices').fetch(),
-      *Smoke(index_path, 'smoke', 'Smoke').fetch(),
-      *SmokeComputeMetrics(index_path, 'smoke', 'SmokeComputeMetrics').fetch(),
-      *SmokeJson(index_path, 'smoke', 'SmokeJson').fetch(),
-      *SmokeSchedEvents(index_path, 'smoke', 'SmokeSchedEvents').fetch(),
-      *SpanJoinLeftJoin(index_path, 'span_join', 'SpanJoinLeftJoin').fetch(),
-      *SpanJoinOuterJoin(index_path, 'span_join', 'SpanJoinOuterJoin').fetch(),
-      *SpanJoinSmoke(index_path, 'span_join', 'SpanJoinSmoke').fetch(),
-      *SpanJoinRegression(index_path, 'span_join',
-                          'SpanJoinRegression').fetch(),
-      *Startup(index_path, 'startup', 'Startup').fetch(),
-      *StartupBroadcasts(index_path, 'startup', 'StartupBroadcasts').fetch(),
-      *StartupMetrics(index_path, 'startup', 'StartupMetrics').fetch(),
-      *StartupLockContention(index_path, 'startup',
-                             'StartupLockContention').fetch(),
-      *SurfaceFlingerLayers(index_path, 'android',
+      *SchedParser(index_path, 'parser/sched', 'SchedParser').fetch(),
+      *Smoke(index_path, 'parser/smoke', 'Smoke').fetch(),
+      *SmokeComputeMetrics(index_path, 'parser/smoke',
+                           'SmokeComputeMetrics').fetch(),
+      *SmokeJson(index_path, 'parser/smoke', 'SmokeJson').fetch(),
+      *SmokeSchedEvents(index_path, 'parser/smoke', 'SmokeSchedEvents').fetch(),
+      *SurfaceFlingerLayers(index_path, 'parser/android',
                             'SurfaceFlingerLayers').fetch(),
-      *SurfaceFlingerTransactions(index_path, 'android',
+      *SurfaceFlingerTransactions(index_path, 'parser/android',
                                   'SurfaceFlingerTransactions').fetch(),
+      *TrackEvent(index_path, 'parser/track_event', 'TrackEvent').fetch(),
+      *TranslatedArgs(index_path, 'parser/translated_args',
+                      'TranslatedArgs').fetch(),
+      *Ufs(index_path, 'parser/ufs', 'Ufs').fetch(),
+      # TODO(altimin, lalitm): "parsing" should be split into more specific
+      # directories.
+      *Parsing(index_path, 'parser/parsing', 'Parsing').fetch(),
+      *ParsingDebugAnnotation(index_path, 'parser/parsing',
+                              'ParsingDebugAnnotation').fetch(),
+      *ParsingRssStats(index_path, 'parser/parsing', 'ParsingRssStats').fetch(),
+      *ParsingMemoryCounters(index_path, 'parser/parsing',
+                             'ParsingMemoryCounters').fetch(),
+  ]
+
+  metrics_tests = [
+      *AndroidMetrics(index_path, 'metrics/android', 'AndroidMetrics').fetch(),
+      *Camera(index_path, 'metrics/camera', 'Camera').fetch(),
+      *ChromeArgs(index_path, 'metrics/chrome', 'ChromeArgs').fetch(),
+      *ChromeMetrics(index_path, 'metrics/chrome', 'ChromeMetrics').fetch(),
+      *ChromeProcesses(index_path, 'metrics/chrome', 'ChromeProcesses').fetch(),
+      *ChromeRailModes(index_path, 'metrics/chrome', 'ChromeRailModes').fetch(),
+      *ChromeScrollJankMetrics(index_path, 'metrics/chrome',
+                               'ChromeScrollJankMetrics').fetch(),
+      *ChromeTouchGesture(index_path, 'metrics/chrome',
+                          'ChromeTouchGesture').fetch(),
+      *Codecs(index_path, 'metrics/codecs', 'Codecs').fetch(),
+      *FrameTimeline(index_path, 'metrics/frame_timeline',
+                     'FrameTimeline').fetch(),
+      *GraphicsMetrics(index_path, 'metrics/graphics',
+                       'GraphicsMetrics').fetch(),
+      *IRQ(index_path, 'metrics/irq', 'IRQ').fetch(),
+      *MemoryMetrics(index_path, 'metrics/memory', 'MemoryMetrics').fetch(),
+      *NetworkMetrics(index_path, 'metrics/network', 'NetworkMetrics').fetch(),
+      *Power(index_path, 'metrics/power', 'Power').fetch(),
+      *ProfilingMetrics(index_path, 'metrics/profiling',
+                        'ProfilingMetrics').fetch(),
+      *Startup(index_path, 'metrics/startup', 'Startup').fetch(),
+      *StartupBroadcasts(index_path, 'metrics/startup',
+                         'StartupBroadcasts').fetch(),
+      *StartupLockContention(index_path, 'metrics/startup',
+                             'StartupLockContention').fetch(),
+      *StartupMetrics(index_path, 'metrics/startup', 'StartupMetrics').fetch(),
+      *WebView(index_path, 'metrics/webview', 'WebView').fetch(),
+  ]
+
+  stdlib_tests = [
+      *AndroidStdlib(index_path, 'stdlib/android', 'AndroidStdlib').fetch(),
+      *ChromeInteractions(index_path, 'stdlib/chrome',
+                                      'ChromeInteractions').fetch(),
+      *ChromeScrollJankStdlib(index_path, 'stdlib/chrome',
+                              'ChromeScrollJankStdlib').fetch(),
+      *ChromeStdlib(index_path, 'stdlib/chrome', 'ChromeStdlib').fetch(),
+      *DynamicTables(index_path, 'stdlib/dynamic_tables',
+                     'DynamicTables').fetch(),
+      *Pkvm(index_path, 'stdlib/pkvm', 'Pkvm').fetch(),
+      *StdlibCommon(index_path, 'stdlib/common', 'StdlibCommon').fetch(),
+      *Slices(index_path, 'stdlib/slices', 'Slices').fetch(),
+      *SpanJoinLeftJoin(index_path, 'stdlib/span_join',
+                        'SpanJoinLeftJoin').fetch(),
+      *SpanJoinOuterJoin(index_path, 'stdlib/span_join',
+                         'SpanJoinOuterJoin').fetch(),
+      *SpanJoinRegression(index_path, 'stdlib/span_join',
+                          'SpanJoinRegression').fetch(),
+      *SpanJoinSmoke(index_path, 'stdlib/span_join', 'SpanJoinSmoke').fetch(),
+      *Timestamps(index_path, 'stdlib/timestamps', 'Timestamps').fetch(),
+  ]
+
+  syntax_tests = [
+      *Functions(index_path, 'syntax/functions', 'Functions').fetch(),
+      *PerfettoSql(index_path, 'syntax/perfetto_sql', 'PerfettoSql').fetch(),
+  ]
+
+  return parser_tests + metrics_tests + stdlib_tests + syntax_tests + [
       *Tables(index_path, 'tables', 'Tables').fetch(),
       *TablesCounters(index_path, 'tables', 'TablesCounters').fetch(),
       *TablesSched(index_path, 'tables', 'TablesSched').fetch(),
-      *Time(index_path, 'time', 'Time').fetch(),
-      *TrackEvent(index_path, 'track_event', 'TrackEvent').fetch(),
-      *Translation(index_path, 'translation', 'Translation').fetch(),
-      *WebView(index_path, 'webview', 'WebView').fetch(),
   ]
diff --git a/test/trace_processor/diff_tests/memory/tests_metrics.py b/test/trace_processor/diff_tests/memory/tests_metrics.py
deleted file mode 100644
index 8a45185..0000000
--- a/test/trace_processor/diff_tests/memory/tests_metrics.py
+++ /dev/null
@@ -1,112 +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 MemoryMetrics(TestSuite):
-
-  def test_android_mem_counters(self):
-    return DiffTestBlueprint(
-        trace=DataPath('memory_counters.pb'),
-        query=Metric('android_mem'),
-        out=Path('android_mem_counters.out'))
-
-  def test_trace_metadata(self):
-    return DiffTestBlueprint(
-        trace=DataPath('memory_counters.pb'),
-        query=Metric('trace_metadata'),
-        out=Path('trace_metadata.out'))
-
-  def test_android_mem_by_priority(self):
-    return DiffTestBlueprint(
-        trace=Path('android_mem_by_priority.py'),
-        query=Metric('android_mem'),
-        out=Path('android_mem_by_priority.out'))
-
-  def test_android_mem_lmk(self):
-    return DiffTestBlueprint(
-        trace=Path('android_systrace_lmk.py'),
-        query=Metric('android_lmk'),
-        out=TextProto(r"""
-        android_lmk {
-          total_count: 1
-            by_oom_score {
-            oom_score_adj: 900
-            count: 1
-          }
-          oom_victim_count: 0
-        }
-        """))
-
-  def test_android_lmk_oom(self):
-    return DiffTestBlueprint(
-        trace=TextProto(r"""
-        packet {
-          process_tree {
-            processes {
-              pid: 1000
-              ppid: 1
-              cmdline: "com.google.android.gm"
-            }
-            threads {
-              tid: 1001
-              tgid: 1000
-            }
-          }
-        }
-        packet {
-          ftrace_events {
-            cpu: 4
-            event {
-              timestamp: 1234
-              pid: 4321
-              mark_victim {
-                pid: 1001
-              }
-            }
-          }
-        }
-        """),
-        query=Metric('android_lmk'),
-        out=TextProto(r"""
-        android_lmk {
-          total_count: 0
-          oom_victim_count: 1
-        }
-        """))
-
-  def test_android_mem_delta(self):
-    return DiffTestBlueprint(
-        trace=Path('android_mem_delta.py'),
-        query=Metric('android_mem'),
-        out=TextProto(r"""
-        android_mem {
-          process_metrics {
-            process_name: "com.my.pkg"
-            total_counters {
-              file_rss {
-                min: 2000.0
-                max: 10000.0
-                avg: 6666.666666666667
-                delta: 7000.0
-              }
-            }
-          }
-        }
-        """))
diff --git a/test/trace_processor/diff_tests/metrics/android/ad_services_metric.py b/test/trace_processor/diff_tests/metrics/android/ad_services_metric.py
new file mode 100644
index 0000000..d4b9cf6
--- /dev/null
+++ b/test/trace_processor/diff_tests/metrics/android/ad_services_metric.py
@@ -0,0 +1,41 @@
+#!/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 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.
+
+from os import sys, path
+
+import synth_common
+
+UI_NOTIFICATION_TRIGGER_EVENT = "NotificationTriggerEvent"
+AD_ID_CACHE_EVENT = "AdIdCacheEvent"
+APP_SET_ID_EVENT = "AppSetIdEvent"
+
+trace = synth_common.create_trace()
+
+trace.add_ftrace_packet(cpu=0)
+
+trace.add_sys_enter(ts=100, tid=42, id=64)
+trace.add_sys_exit(ts=200, tid=42, id=64, ret=0)
+
+trace.add_atrace_begin(
+    ts=350, tid=42, pid=42, buf=UI_NOTIFICATION_TRIGGER_EVENT)
+trace.add_atrace_end(ts=650, tid=42, pid=42)
+
+trace.add_atrace_begin(ts=750, tid=42, pid=42, buf=AD_ID_CACHE_EVENT)
+trace.add_atrace_end(ts=850, tid=42, pid=42)
+
+trace.add_atrace_begin(ts=900, tid=42, pid=42, buf=APP_SET_ID_EVENT)
+trace.add_atrace_end(ts=1200, tid=42, pid=42)
+
+sys.stdout.buffer.write(trace.trace.SerializeToString())
diff --git a/test/trace_processor/diff_tests/android/android_anr_metric.out b/test/trace_processor/diff_tests/metrics/android/android_anr_metric.out
similarity index 100%
rename from test/trace_processor/diff_tests/android/android_anr_metric.out
rename to test/trace_processor/diff_tests/metrics/android/android_anr_metric.out
diff --git a/test/trace_processor/diff_tests/android/android_anr_metric.py b/test/trace_processor/diff_tests/metrics/android/android_anr_metric.py
similarity index 100%
rename from test/trace_processor/diff_tests/android/android_anr_metric.py
rename to test/trace_processor/diff_tests/metrics/android/android_anr_metric.py
diff --git a/test/trace_processor/diff_tests/android/android_binder_metric.out b/test/trace_processor/diff_tests/metrics/android/android_binder_metric.out
similarity index 100%
rename from test/trace_processor/diff_tests/android/android_binder_metric.out
rename to test/trace_processor/diff_tests/metrics/android/android_binder_metric.out
diff --git a/test/trace_processor/diff_tests/android/android_blocking_calls_cuj_metric.out b/test/trace_processor/diff_tests/metrics/android/android_blocking_calls_cuj_metric.out
similarity index 100%
rename from test/trace_processor/diff_tests/android/android_blocking_calls_cuj_metric.out
rename to test/trace_processor/diff_tests/metrics/android/android_blocking_calls_cuj_metric.out
diff --git a/test/trace_processor/diff_tests/android/android_blocking_calls_cuj_metric.py b/test/trace_processor/diff_tests/metrics/android/android_blocking_calls_cuj_metric.py
similarity index 90%
rename from test/trace_processor/diff_tests/android/android_blocking_calls_cuj_metric.py
rename to test/trace_processor/diff_tests/metrics/android/android_blocking_calls_cuj_metric.py
index 7ec8cb9..f4eb10f 100755
--- a/test/trace_processor/diff_tests/android/android_blocking_calls_cuj_metric.py
+++ b/test/trace_processor/diff_tests/metrics/android/android_blocking_calls_cuj_metric.py
@@ -60,6 +60,7 @@
       reply_tid=rx_pid,
       reply_pid=rx_pid)
 
+
 # Adds a set of predefined blocking calls in places near the cuj boundaries to
 # verify that only the portion inside the cuj is counted in the metric.
 def add_cuj_with_blocking_calls(trace, cuj_name, pid):
@@ -140,6 +141,7 @@
         pid=pid)
     blocking_call_ts += blocking_call_dur
 
+
 # Creates 2 overlapping cuj, and a blocking call that lasts for both of them.
 def add_overlapping_cujs_with_blocking_calls(trace, start_ts, pid):
   add_async_trace(
@@ -190,20 +192,25 @@
 
 def add_process(trace, package_name, uid, pid):
   trace.add_package_list(ts=0, name=package_name, uid=uid, version_code=1)
-  trace.add_process(
-      pid=pid, ppid=0, cmdline=package_name, uid=uid)
+  trace.add_process(pid=pid, ppid=0, cmdline=package_name, uid=uid)
   trace.add_thread(tid=pid, tgid=pid, cmdline="MainThread", name="MainThread")
 
 
 def setup_trace():
   trace = synth_common.create_trace()
   trace.add_packet()
-  add_process(trace, package_name="com.android.systemui", uid=10001,
-              pid=SYSUI_PID)
-  add_process(trace, package_name="com.google.android.apps.nexuslauncher",
-              uid=10002, pid=LAUNCHER_PID)
-  add_process(trace, package_name="com.google.android.third.process",
-              uid=10003, pid=THIRD_PROCESS_PID)
+  add_process(
+      trace, package_name="com.android.systemui", uid=10001, pid=SYSUI_PID)
+  add_process(
+      trace,
+      package_name="com.google.android.apps.nexuslauncher",
+      uid=10002,
+      pid=LAUNCHER_PID)
+  add_process(
+      trace,
+      package_name="com.google.android.third.process",
+      uid=10003,
+      pid=THIRD_PROCESS_PID)
   trace.add_ftrace_packet(cpu=0)
   add_async_trace(trace, ts=0, ts_end=5, buf="J<IGNORED>", pid=SYSUI_PID)
   return trace
@@ -212,13 +219,13 @@
 trace = setup_trace()
 
 add_cuj_with_blocking_calls(trace, "L<TEST_SYSUI_LATENCY_EVENT>", pid=SYSUI_PID)
-add_cuj_with_blocking_calls(trace, "L<TEST_LAUNCHER_LATENCY_EVENT>",
-                            pid=LAUNCHER_PID)
+add_cuj_with_blocking_calls(
+    trace, "L<TEST_LAUNCHER_LATENCY_EVENT>", pid=LAUNCHER_PID)
 
 add_all_blocking_calls_in_cuj(trace, pid=THIRD_PROCESS_PID)
 
-add_overlapping_cujs_with_blocking_calls(trace, pid=SYSUI_PID,
-                                         start_ts=20_000_000)
+add_overlapping_cujs_with_blocking_calls(
+    trace, pid=SYSUI_PID, start_ts=20_000_000)
 
 add_cuj_with_named_binder_transaction(pid=SYSUI_PID, rx_pid=LAUNCHER_PID)
 
diff --git a/test/trace_processor/diff_tests/android/android_blocking_calls_on_jank_cuj_metric.out b/test/trace_processor/diff_tests/metrics/android/android_blocking_calls_on_jank_cuj_metric.out
similarity index 100%
rename from test/trace_processor/diff_tests/android/android_blocking_calls_on_jank_cuj_metric.out
rename to test/trace_processor/diff_tests/metrics/android/android_blocking_calls_on_jank_cuj_metric.out
diff --git a/test/trace_processor/diff_tests/android/android_monitor_contention.out b/test/trace_processor/diff_tests/metrics/android/android_monitor_contention.out
similarity index 100%
rename from test/trace_processor/diff_tests/android/android_monitor_contention.out
rename to test/trace_processor/diff_tests/metrics/android/android_monitor_contention.out
diff --git a/test/trace_processor/diff_tests/android/android_network_activity.out b/test/trace_processor/diff_tests/metrics/android/android_network_activity.out
similarity index 100%
rename from test/trace_processor/diff_tests/android/android_network_activity.out
rename to test/trace_processor/diff_tests/metrics/android/android_network_activity.out
diff --git a/test/trace_processor/diff_tests/android/android_sysui_notifications_blocking_calls_metric.out b/test/trace_processor/diff_tests/metrics/android/android_sysui_notifications_blocking_calls_metric.out
similarity index 100%
rename from test/trace_processor/diff_tests/android/android_sysui_notifications_blocking_calls_metric.out
rename to test/trace_processor/diff_tests/metrics/android/android_sysui_notifications_blocking_calls_metric.out
diff --git a/test/trace_processor/diff_tests/android/android_sysui_notifications_blocking_calls_metric.py b/test/trace_processor/diff_tests/metrics/android/android_sysui_notifications_blocking_calls_metric.py
similarity index 86%
rename from test/trace_processor/diff_tests/android/android_sysui_notifications_blocking_calls_metric.py
rename to test/trace_processor/diff_tests/metrics/android/android_sysui_notifications_blocking_calls_metric.py
index cab8c8f..43d15cc 100644
--- a/test/trace_processor/diff_tests/android/android_sysui_notifications_blocking_calls_metric.py
+++ b/test/trace_processor/diff_tests/metrics/android/android_sysui_notifications_blocking_calls_metric.py
@@ -23,9 +23,9 @@
 
 # List of blocking calls
 blocking_call_names = [
-    'NotificationStackScrollLayout#onMeasure', 'ExpNotRow#onMeasure(MessagingStyle)',
-    'ExpNotRow#onMeasure(BigTextStyle)', 'NotificationShadeWindowView#onMeasure',
-    'ImageFloatingTextView#onMeasure',
+    'NotificationStackScrollLayout#onMeasure',
+    'ExpNotRow#onMeasure(MessagingStyle)', 'ExpNotRow#onMeasure(BigTextStyle)',
+    'NotificationShadeWindowView#onMeasure', 'ImageFloatingTextView#onMeasure',
     'Should not be in the metric'
 ]
 
@@ -39,6 +39,7 @@
   trace.add_atrace_async_begin(ts=ts, tid=pid, pid=pid, buf=buf)
   trace.add_atrace_async_end(ts=ts_end, tid=pid, pid=pid, buf=buf)
 
+
 # Creates a trace that contains one of each blocking call.
 def add_all_sysui_notifications_blocking_calls(trace, pid):
   blocking_call_dur = 10_000_000
@@ -64,23 +65,21 @@
 
 def add_process(trace, package_name, uid, pid):
   trace.add_package_list(ts=0, name=package_name, uid=uid, version_code=1)
-  trace.add_process(
-      pid=pid, ppid=0, cmdline=package_name, uid=uid)
+  trace.add_process(pid=pid, ppid=0, cmdline=package_name, uid=uid)
   trace.add_thread(tid=pid, tgid=pid, cmdline="MainThread", name="MainThread")
 
 
 def setup_trace():
   trace = synth_common.create_trace()
   trace.add_packet()
-  add_process(trace, package_name="com.android.systemui", uid=10001,
-              pid=SYSUI_PID)
+  add_process(
+      trace, package_name="com.android.systemui", uid=10001, pid=SYSUI_PID)
   trace.add_ftrace_packet(cpu=0)
   return trace
 
 
 trace = setup_trace()
 
-
 add_all_sysui_notifications_blocking_calls(trace, pid=SYSUI_PID)
 
 # See test_android_sysui_notifications_blocking_calls.
diff --git a/test/trace_processor/diff_tests/metrics/android/tests.py b/test/trace_processor/diff_tests/metrics/android/tests.py
new file mode 100644
index 0000000..f5ffcf7
--- /dev/null
+++ b/test/trace_processor/diff_tests/metrics/android/tests.py
@@ -0,0 +1,206 @@
+#!/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, Systrace
+from python.generators.diff_tests.testing import Csv, Json, TextProto, BinaryProto
+from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import TestSuite
+from python.generators.diff_tests.testing import PrintProfileProto
+
+
+class AndroidMetrics(TestSuite):
+
+  def test_android_network_activity(self):
+    # The following should have three activity regions:
+    # * uid=123 from 1000 to 2010 (note: end is max(ts)+idle_ns)
+    # * uid=456 from 1005 to 3115 (note: doesn't group with above due to name)
+    #   * Also tests that groups form based on (ts+dur), not just start ts.
+    # * uid=123 from 3000 to 5500 (note: gap between 1010 to 3000 > idle_ns)
+    # Note: packet_timestamps are delta encoded from the base timestamp.
+    return DiffTestBlueprint(
+        trace=TextProto(r"""
+        packet {
+          timestamp: 0
+          network_packet_bundle {
+            ctx {
+              direction: DIR_EGRESS
+              interface: "wlan"
+              uid: 123
+            }
+            packet_timestamps: [
+              1000, 1010,
+              3000, 3050, 4000, 4500
+            ],
+            packet_lengths: [
+              50, 50,
+              50, 50, 50, 50
+            ],
+          }
+        }
+        packet {
+          timestamp: 1005
+          network_packet_bundle {
+            ctx {
+              direction: DIR_EGRESS
+              interface: "wlan"
+              uid: 456
+            }
+            total_duration: 100
+            total_packets: 2
+            total_length: 300
+          }
+        }
+        packet {
+          timestamp: 2015
+          network_packet_bundle {
+            ctx {
+              direction: DIR_EGRESS
+              interface: "wlan"
+              uid: 456
+            }
+            total_duration: 100
+            total_packets: 1
+            total_length: 50
+          }
+        }
+        packet {
+          timestamp: 0
+          network_packet_bundle {
+            ctx {
+              direction: DIR_INGRESS
+              interface: "loopback"
+              uid: 123
+            }
+            packet_timestamps: [6000]
+            packet_lengths: [100]
+          }
+        }
+        """),
+        query="""
+        SELECT RUN_METRIC(
+          'android/network_activity_template.sql',
+          'view_name', 'android_network_activity',
+          'group_by',  'package_name',
+          'filter',    'iface = "wlan"',
+          'idle_ns',   '1000',
+          'quant_ns',  '100'
+        );
+
+        SELECT * FROM android_network_activity
+        ORDER BY package_name, ts;
+        """,
+        out=Path('android_network_activity.out'))
+
+  def test_anr_metric(self):
+    return DiffTestBlueprint(
+        trace=Path('android_anr_metric.py'),
+        query=Metric('android_anr'),
+        out=Path('android_anr_metric.out'))
+
+  def test_binder_metric(self):
+    return DiffTestBlueprint(
+        trace=DataPath('android_binder_metric_trace.atr'),
+        query=Metric('android_binder'),
+        out=Path('android_binder_metric.out'))
+
+  def test_android_blocking_calls_cuj(self):
+    return DiffTestBlueprint(
+        trace=Path('android_blocking_calls_cuj_metric.py'),
+        query=Metric('android_blocking_calls_cuj_metric'),
+        out=Path('android_blocking_calls_cuj_metric.out'))
+
+  def test_android_blocking_calls_on_jank_cujs(self):
+    return DiffTestBlueprint(
+        trace=Path('../graphics/android_jank_cuj.py'),
+        query=Metric('android_blocking_calls_cuj_metric'),
+        out=Path('android_blocking_calls_on_jank_cuj_metric.out'))
+
+  def test_android_sysui_notifications_blocking_calls(self):
+    return DiffTestBlueprint(
+        trace=Path('android_sysui_notifications_blocking_calls_metric.py'),
+        query=Metric('android_sysui_notifications_blocking_calls_metric'),
+        out=Path('android_sysui_notifications_blocking_calls_metric.out'))
+
+  def test_monitor_contention_metric(self):
+    return DiffTestBlueprint(
+        trace=DataPath('android_monitor_contention_trace.atr'),
+        query=Metric('android_monitor_contention'),
+        out=Path('android_monitor_contention.out'))
+
+  def test_monitor_contention_agg_metric(self):
+    return DiffTestBlueprint(
+        trace=DataPath('android_monitor_contention_trace.atr'),
+        query=Metric('android_monitor_contention_agg'),
+        out=TextProto(r"""
+        android_monitor_contention_agg {
+          process_aggregation {
+            name: "android.process.media"
+            total_contention_count: 12
+            total_contention_dur: 12893198
+            main_thread_contention_count: 12
+            main_thread_contention_dur: 12893198
+          }
+          process_aggregation {
+            name: "com.android.providers.media.module"
+            total_contention_count: 7
+            total_contention_dur: 169793
+          }
+          process_aggregation {
+            name: "com.android.systemui"
+            total_contention_count: 8
+            total_contention_dur: 9445959
+            main_thread_contention_count: 5
+            main_thread_contention_dur: 9228582
+          }
+          process_aggregation {
+            name: "system_server"
+            total_contention_count: 354
+            total_contention_dur: 358898613
+            main_thread_contention_count: 27
+            main_thread_contention_dur: 36904702
+          }
+        }
+        """))
+
+  def test_android_boot(self):
+    return DiffTestBlueprint(
+        trace=DataPath('android_boot.pftrace'),
+        query=Metric('android_boot'),
+        out=TextProto(r"""
+        android_boot {
+          system_server_durations {
+            total_dur: 267193980530
+            uninterruptible_sleep_dur: 3843119529
+          }
+        }
+        """))
+
+  def test_ad_services_metric(self):
+    return DiffTestBlueprint(
+        trace=Path('ad_services_metric.py'),
+        query=Metric('ad_services_metric'),
+        out=TextProto(r"""
+         ad_services_metric {
+           ui_metric {
+             latency: 0.0003
+           }
+           app_set_id_metric {
+             latency: 0.0001
+           }
+           ad_id_metric {
+             latency:0.0003
+           }
+         }
+        """))
diff --git a/test/trace_processor/diff_tests/camera/camera-ion-mem-trace_android_camera_unagg.out b/test/trace_processor/diff_tests/metrics/camera/camera-ion-mem-trace_android_camera_unagg.out
similarity index 100%
rename from test/trace_processor/diff_tests/camera/camera-ion-mem-trace_android_camera_unagg.out
rename to test/trace_processor/diff_tests/metrics/camera/camera-ion-mem-trace_android_camera_unagg.out
diff --git a/test/trace_processor/diff_tests/camera/tests.py b/test/trace_processor/diff_tests/metrics/camera/tests.py
similarity index 100%
rename from test/trace_processor/diff_tests/camera/tests.py
rename to test/trace_processor/diff_tests/metrics/camera/tests.py
diff --git a/test/trace_processor/diff_tests/chrome/actual_power_by_combined_rail_mode.py b/test/trace_processor/diff_tests/metrics/chrome/actual_power_by_combined_rail_mode.py
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/actual_power_by_combined_rail_mode.py
rename to test/trace_processor/diff_tests/metrics/chrome/actual_power_by_combined_rail_mode.py
diff --git a/test/trace_processor/diff_tests/chrome/chrome_input_to_browser_intervals.out b/test/trace_processor/diff_tests/metrics/chrome/chrome_input_to_browser_intervals.out
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/chrome_input_to_browser_intervals.out
rename to test/trace_processor/diff_tests/metrics/chrome/chrome_input_to_browser_intervals.out
diff --git a/test/trace_processor/diff_tests/chrome/chrome_long_tasks_delaying_input_processing_compare_default_test.sql b/test/trace_processor/diff_tests/metrics/chrome/chrome_long_tasks_delaying_input_processing_compare_default_test.sql
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/chrome_long_tasks_delaying_input_processing_compare_default_test.sql
rename to test/trace_processor/diff_tests/metrics/chrome/chrome_long_tasks_delaying_input_processing_compare_default_test.sql
diff --git a/test/trace_processor/diff_tests/chrome/chrome_processes_android_systrace.out b/test/trace_processor/diff_tests/metrics/chrome/chrome_processes_android_systrace.out
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/chrome_processes_android_systrace.out
rename to test/trace_processor/diff_tests/metrics/chrome/chrome_processes_android_systrace.out
diff --git a/test/trace_processor/diff_tests/chrome/chrome_processes_type_android_systrace.out b/test/trace_processor/diff_tests/metrics/chrome/chrome_processes_type_android_systrace.out
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/chrome_processes_type_android_systrace.out
rename to test/trace_processor/diff_tests/metrics/chrome/chrome_processes_type_android_systrace.out
diff --git a/test/trace_processor/diff_tests/chrome/chrome_reliable_range.textproto b/test/trace_processor/diff_tests/metrics/chrome/chrome_reliable_range.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/chrome_reliable_range.textproto
rename to test/trace_processor/diff_tests/metrics/chrome/chrome_reliable_range.textproto
diff --git a/test/trace_processor/diff_tests/chrome/chrome_reliable_range_cropping.textproto b/test/trace_processor/diff_tests/metrics/chrome/chrome_reliable_range_cropping.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/chrome_reliable_range_cropping.textproto
rename to test/trace_processor/diff_tests/metrics/chrome/chrome_reliable_range_cropping.textproto
diff --git a/test/trace_processor/diff_tests/chrome/chrome_reliable_range_missing_browser_main.out b/test/trace_processor/diff_tests/metrics/chrome/chrome_reliable_range_missing_browser_main.out
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/chrome_reliable_range_missing_browser_main.out
rename to test/trace_processor/diff_tests/metrics/chrome/chrome_reliable_range_missing_browser_main.out
diff --git a/test/trace_processor/diff_tests/chrome/chrome_reliable_range_missing_browser_main.textproto b/test/trace_processor/diff_tests/metrics/chrome/chrome_reliable_range_missing_browser_main.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/chrome_reliable_range_missing_browser_main.textproto
rename to test/trace_processor/diff_tests/metrics/chrome/chrome_reliable_range_missing_browser_main.textproto
diff --git a/test/trace_processor/diff_tests/chrome/chrome_reliable_range_missing_gpu_main.textproto b/test/trace_processor/diff_tests/metrics/chrome/chrome_reliable_range_missing_gpu_main.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/chrome_reliable_range_missing_gpu_main.textproto
rename to test/trace_processor/diff_tests/metrics/chrome/chrome_reliable_range_missing_gpu_main.textproto
diff --git a/test/trace_processor/diff_tests/chrome/chrome_reliable_range_missing_processes.textproto b/test/trace_processor/diff_tests/metrics/chrome/chrome_reliable_range_missing_processes.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/chrome_reliable_range_missing_processes.textproto
rename to test/trace_processor/diff_tests/metrics/chrome/chrome_reliable_range_missing_processes.textproto
diff --git a/test/trace_processor/diff_tests/chrome/chrome_reliable_range_missing_renderer_main.out b/test/trace_processor/diff_tests/metrics/chrome/chrome_reliable_range_missing_renderer_main.out
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/chrome_reliable_range_missing_renderer_main.out
rename to test/trace_processor/diff_tests/metrics/chrome/chrome_reliable_range_missing_renderer_main.out
diff --git a/test/trace_processor/diff_tests/chrome/chrome_reliable_range_missing_renderer_main.textproto b/test/trace_processor/diff_tests/metrics/chrome/chrome_reliable_range_missing_renderer_main.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/chrome_reliable_range_missing_renderer_main.textproto
rename to test/trace_processor/diff_tests/metrics/chrome/chrome_reliable_range_missing_renderer_main.textproto
diff --git a/test/trace_processor/diff_tests/chrome/chrome_reliable_range_non_chrome_process.out b/test/trace_processor/diff_tests/metrics/chrome/chrome_reliable_range_non_chrome_process.out
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/chrome_reliable_range_non_chrome_process.out
rename to test/trace_processor/diff_tests/metrics/chrome/chrome_reliable_range_non_chrome_process.out
diff --git a/test/trace_processor/diff_tests/chrome/chrome_reliable_range_test.sql b/test/trace_processor/diff_tests/metrics/chrome/chrome_reliable_range_test.sql
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/chrome_reliable_range_test.sql
rename to test/trace_processor/diff_tests/metrics/chrome/chrome_reliable_range_test.sql
diff --git a/test/trace_processor/diff_tests/chrome/chrome_scroll_helper.py b/test/trace_processor/diff_tests/metrics/chrome/chrome_scroll_helper.py
similarity index 99%
rename from test/trace_processor/diff_tests/chrome/chrome_scroll_helper.py
rename to test/trace_processor/diff_tests/metrics/chrome/chrome_scroll_helper.py
index 7b7cee1..77b6e05 100644
--- a/test/trace_processor/diff_tests/chrome/chrome_scroll_helper.py
+++ b/test/trace_processor/diff_tests/metrics/chrome/chrome_scroll_helper.py
@@ -20,6 +20,7 @@
 import synth_common
 
 from synth_common import ms_to_ns
+
 trace = synth_common.create_trace()
 
 
diff --git a/test/trace_processor/diff_tests/chrome/chrome_scroll_jank_caused_by_scheduling_test.out b/test/trace_processor/diff_tests/metrics/chrome/chrome_scroll_jank_caused_by_scheduling_test.out
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/chrome_scroll_jank_caused_by_scheduling_test.out
rename to test/trace_processor/diff_tests/metrics/chrome/chrome_scroll_jank_caused_by_scheduling_test.out
diff --git a/test/trace_processor/diff_tests/chrome/chrome_stack_samples_for_task_test.out b/test/trace_processor/diff_tests/metrics/chrome/chrome_stack_samples_for_task_test.out
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/chrome_stack_samples_for_task_test.out
rename to test/trace_processor/diff_tests/metrics/chrome/chrome_stack_samples_for_task_test.out
diff --git a/test/trace_processor/diff_tests/chrome/chrome_tasks_delaying_input_processing_test.out b/test/trace_processor/diff_tests/metrics/chrome/chrome_tasks_delaying_input_processing_test.out
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/chrome_tasks_delaying_input_processing_test.out
rename to test/trace_processor/diff_tests/metrics/chrome/chrome_tasks_delaying_input_processing_test.out
diff --git a/test/trace_processor/diff_tests/chrome/chrome_threads.out b/test/trace_processor/diff_tests/metrics/chrome/chrome_threads.out
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/chrome_threads.out
rename to test/trace_processor/diff_tests/metrics/chrome/chrome_threads.out
diff --git a/test/trace_processor/diff_tests/chrome/chrome_threads_android_systrace.out b/test/trace_processor/diff_tests/metrics/chrome/chrome_threads_android_systrace.out
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/chrome_threads_android_systrace.out
rename to test/trace_processor/diff_tests/metrics/chrome/chrome_threads_android_systrace.out
diff --git a/test/trace_processor/diff_tests/chrome/combined_rail_modes.py b/test/trace_processor/diff_tests/metrics/chrome/combined_rail_modes.py
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/combined_rail_modes.py
rename to test/trace_processor/diff_tests/metrics/chrome/combined_rail_modes.py
diff --git a/test/trace_processor/diff_tests/chrome/cpu_time_by_combined_rail_mode.py b/test/trace_processor/diff_tests/metrics/chrome/cpu_time_by_combined_rail_mode.py
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/cpu_time_by_combined_rail_mode.py
rename to test/trace_processor/diff_tests/metrics/chrome/cpu_time_by_combined_rail_mode.py
diff --git a/test/trace_processor/diff_tests/chrome/estimated_power_by_combined_rail_mode.py b/test/trace_processor/diff_tests/metrics/chrome/estimated_power_by_combined_rail_mode.py
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/estimated_power_by_combined_rail_mode.py
rename to test/trace_processor/diff_tests/metrics/chrome/estimated_power_by_combined_rail_mode.py
diff --git a/test/trace_processor/diff_tests/chrome/experimental_reliable_chrome_tasks_delaying_input_processing_test.out b/test/trace_processor/diff_tests/metrics/chrome/experimental_reliable_chrome_tasks_delaying_input_processing_test.out
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/experimental_reliable_chrome_tasks_delaying_input_processing_test.out
rename to test/trace_processor/diff_tests/metrics/chrome/experimental_reliable_chrome_tasks_delaying_input_processing_test.out
diff --git a/test/trace_processor/diff_tests/chrome/frame_times_metric.out b/test/trace_processor/diff_tests/metrics/chrome/frame_times_metric.out
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/frame_times_metric.out
rename to test/trace_processor/diff_tests/metrics/chrome/frame_times_metric.out
diff --git a/test/trace_processor/diff_tests/chrome/frame_times_metric_test.sql b/test/trace_processor/diff_tests/metrics/chrome/frame_times_metric_test.sql
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/frame_times_metric_test.sql
rename to test/trace_processor/diff_tests/metrics/chrome/frame_times_metric_test.sql
diff --git a/test/trace_processor/diff_tests/chrome/long_event_latency.textproto b/test/trace_processor/diff_tests/metrics/chrome/long_event_latency.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/long_event_latency.textproto
rename to test/trace_processor/diff_tests/metrics/chrome/long_event_latency.textproto
diff --git a/test/trace_processor/diff_tests/chrome/long_task_tracking_trace_chrome_long_tasks_delaying_input_processing_compare_default_test.out b/test/trace_processor/diff_tests/metrics/chrome/long_task_tracking_trace_chrome_long_tasks_delaying_input_processing_compare_default_test.out
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/long_task_tracking_trace_chrome_long_tasks_delaying_input_processing_compare_default_test.out
rename to test/trace_processor/diff_tests/metrics/chrome/long_task_tracking_trace_chrome_long_tasks_delaying_input_processing_compare_default_test.out
diff --git a/test/trace_processor/diff_tests/chrome/long_task_tracking_trace_chrome_long_tasks_delaying_input_processing_test.out b/test/trace_processor/diff_tests/metrics/chrome/long_task_tracking_trace_chrome_long_tasks_delaying_input_processing_test.out
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/long_task_tracking_trace_chrome_long_tasks_delaying_input_processing_test.out
rename to test/trace_processor/diff_tests/metrics/chrome/long_task_tracking_trace_chrome_long_tasks_delaying_input_processing_test.out
diff --git a/test/trace_processor/diff_tests/chrome/modified_rail_modes.py b/test/trace_processor/diff_tests/metrics/chrome/modified_rail_modes.py
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/modified_rail_modes.py
rename to test/trace_processor/diff_tests/metrics/chrome/modified_rail_modes.py
diff --git a/test/trace_processor/diff_tests/chrome/modified_rail_modes_extra_long.py b/test/trace_processor/diff_tests/metrics/chrome/modified_rail_modes_extra_long.py
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/modified_rail_modes_extra_long.py
rename to test/trace_processor/diff_tests/metrics/chrome/modified_rail_modes_extra_long.py
diff --git a/test/trace_processor/diff_tests/chrome/modified_rail_modes_long.py b/test/trace_processor/diff_tests/metrics/chrome/modified_rail_modes_long.py
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/modified_rail_modes_long.py
rename to test/trace_processor/diff_tests/metrics/chrome/modified_rail_modes_long.py
diff --git a/test/trace_processor/diff_tests/chrome/modified_rail_modes_no_vsyncs.py b/test/trace_processor/diff_tests/metrics/chrome/modified_rail_modes_no_vsyncs.py
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/modified_rail_modes_no_vsyncs.py
rename to test/trace_processor/diff_tests/metrics/chrome/modified_rail_modes_no_vsyncs.py
diff --git a/test/trace_processor/diff_tests/chrome/modified_rail_modes_with_input.py b/test/trace_processor/diff_tests/metrics/chrome/modified_rail_modes_with_input.py
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/modified_rail_modes_with_input.py
rename to test/trace_processor/diff_tests/metrics/chrome/modified_rail_modes_with_input.py
diff --git a/test/trace_processor/diff_tests/chrome/proto_content.out b/test/trace_processor/diff_tests/metrics/chrome/proto_content.out
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/proto_content.out
rename to test/trace_processor/diff_tests/metrics/chrome/proto_content.out
diff --git a/test/trace_processor/diff_tests/chrome/proto_content_path_test.sql b/test/trace_processor/diff_tests/metrics/chrome/proto_content_path_test.sql
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/proto_content_path_test.sql
rename to test/trace_processor/diff_tests/metrics/chrome/proto_content_path_test.sql
diff --git a/test/trace_processor/diff_tests/chrome/proto_content_test.sql b/test/trace_processor/diff_tests/metrics/chrome/proto_content_test.sql
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/proto_content_test.sql
rename to test/trace_processor/diff_tests/metrics/chrome/proto_content_test.sql
diff --git a/test/trace_processor/diff_tests/chrome/scroll_flow_event.out b/test/trace_processor/diff_tests/metrics/chrome/scroll_flow_event.out
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/scroll_flow_event.out
rename to test/trace_processor/diff_tests/metrics/chrome/scroll_flow_event.out
diff --git a/test/trace_processor/diff_tests/chrome/scroll_flow_event_general_validation.out b/test/trace_processor/diff_tests/metrics/chrome/scroll_flow_event_general_validation.out
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/scroll_flow_event_general_validation.out
rename to test/trace_processor/diff_tests/metrics/chrome/scroll_flow_event_general_validation.out
diff --git a/test/trace_processor/diff_tests/chrome/scroll_flow_event_queuing_delay.out b/test/trace_processor/diff_tests/metrics/chrome/scroll_flow_event_queuing_delay.out
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/scroll_flow_event_queuing_delay.out
rename to test/trace_processor/diff_tests/metrics/chrome/scroll_flow_event_queuing_delay.out
diff --git a/test/trace_processor/diff_tests/chrome/scroll_flow_event_queuing_delay_general_validation_test.sql b/test/trace_processor/diff_tests/metrics/chrome/scroll_flow_event_queuing_delay_general_validation_test.sql
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/scroll_flow_event_queuing_delay_general_validation_test.sql
rename to test/trace_processor/diff_tests/metrics/chrome/scroll_flow_event_queuing_delay_general_validation_test.sql
diff --git a/test/trace_processor/diff_tests/chrome/scroll_jank.out b/test/trace_processor/diff_tests/metrics/chrome/scroll_jank.out
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/scroll_jank.out
rename to test/trace_processor/diff_tests/metrics/chrome/scroll_jank.out
diff --git a/test/trace_processor/diff_tests/chrome/scroll_jank_cause_queuing_delay.out b/test/trace_processor/diff_tests/metrics/chrome/scroll_jank_cause_queuing_delay.out
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/scroll_jank_cause_queuing_delay.out
rename to test/trace_processor/diff_tests/metrics/chrome/scroll_jank_cause_queuing_delay.out
diff --git a/test/trace_processor/diff_tests/chrome/scroll_jank_cause_queuing_delay_general_validation.out b/test/trace_processor/diff_tests/metrics/chrome/scroll_jank_cause_queuing_delay_general_validation.out
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/scroll_jank_cause_queuing_delay_general_validation.out
rename to test/trace_processor/diff_tests/metrics/chrome/scroll_jank_cause_queuing_delay_general_validation.out
diff --git a/test/trace_processor/diff_tests/chrome/scroll_jank_cause_queuing_delay_restricted.out b/test/trace_processor/diff_tests/metrics/chrome/scroll_jank_cause_queuing_delay_restricted.out
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/scroll_jank_cause_queuing_delay_restricted.out
rename to test/trace_processor/diff_tests/metrics/chrome/scroll_jank_cause_queuing_delay_restricted.out
diff --git a/test/trace_processor/diff_tests/chrome/scroll_jank_general_validation.out b/test/trace_processor/diff_tests/metrics/chrome/scroll_jank_general_validation.out
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/scroll_jank_general_validation.out
rename to test/trace_processor/diff_tests/metrics/chrome/scroll_jank_general_validation.out
diff --git a/test/trace_processor/diff_tests/chrome/scroll_jank_general_validation_test.sql b/test/trace_processor/diff_tests/metrics/chrome/scroll_jank_general_validation_test.sql
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/scroll_jank_general_validation_test.sql
rename to test/trace_processor/diff_tests/metrics/chrome/scroll_jank_general_validation_test.sql
diff --git a/test/trace_processor/diff_tests/chrome/scroll_jank_gpu_check.py b/test/trace_processor/diff_tests/metrics/chrome/scroll_jank_gpu_check.py
similarity index 99%
rename from test/trace_processor/diff_tests/chrome/scroll_jank_gpu_check.py
rename to test/trace_processor/diff_tests/metrics/chrome/scroll_jank_gpu_check.py
index c9ba45c..3de4221 100644
--- a/test/trace_processor/diff_tests/chrome/scroll_jank_gpu_check.py
+++ b/test/trace_processor/diff_tests/metrics/chrome/scroll_jank_gpu_check.py
@@ -22,6 +22,7 @@
 import synth_common
 
 from synth_common import ms_to_ns
+
 trace = synth_common.create_trace()
 
 from chrome_scroll_helper import ChromeScrollHelper
diff --git a/test/trace_processor/diff_tests/chrome/scroll_jank_mojo_simple_watcher.out b/test/trace_processor/diff_tests/metrics/chrome/scroll_jank_mojo_simple_watcher.out
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/scroll_jank_mojo_simple_watcher.out
rename to test/trace_processor/diff_tests/metrics/chrome/scroll_jank_mojo_simple_watcher.out
diff --git a/test/trace_processor/diff_tests/chrome/scroll_jank_mojo_simple_watcher.py b/test/trace_processor/diff_tests/metrics/chrome/scroll_jank_mojo_simple_watcher.py
similarity index 99%
rename from test/trace_processor/diff_tests/chrome/scroll_jank_mojo_simple_watcher.py
rename to test/trace_processor/diff_tests/metrics/chrome/scroll_jank_mojo_simple_watcher.py
index 72dfa7d..e8839a7 100644
--- a/test/trace_processor/diff_tests/chrome/scroll_jank_mojo_simple_watcher.py
+++ b/test/trace_processor/diff_tests/metrics/chrome/scroll_jank_mojo_simple_watcher.py
@@ -22,6 +22,7 @@
 import synth_common
 
 from synth_common import ms_to_ns
+
 trace = synth_common.create_trace()
 
 process_track1 = 1234
diff --git a/test/trace_processor/diff_tests/metrics/chrome/tests.py b/test/trace_processor/diff_tests/metrics/chrome/tests.py
new file mode 100644
index 0000000..f005556
--- /dev/null
+++ b/test/trace_processor/diff_tests/metrics/chrome/tests.py
@@ -0,0 +1,300 @@
+#!/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 ChromeMetrics(TestSuite):
+  # Tests related to Chrome's use of Perfetto. Chrome histogram hashes
+  def test_chrome_histogram_hashes(self):
+    return DiffTestBlueprint(
+        trace=TextProto(r"""
+        packet {
+          trusted_packet_sequence_id: 1
+          timestamp: 0
+          incremental_state_cleared: true
+          track_event {
+            categories: "cat1"
+            type: 3
+            name_iid: 1
+            chrome_histogram_sample {
+              name_hash: 10
+              sample: 100
+            }
+          }
+        }
+        packet {
+          trusted_packet_sequence_id: 1
+          timestamp: 0
+          incremental_state_cleared: true
+          track_event {
+            categories: "cat2"
+            type: 3
+            name_iid: 2
+            chrome_histogram_sample {
+              name_hash: 20
+            }
+          }
+        }
+        """),
+        query=Metric('chrome_histogram_hashes'),
+        out=TextProto(r"""
+        [perfetto.protos.chrome_histogram_hashes]: {
+          hash: 10
+          hash: 20
+        }
+        """))
+
+  # Chrome user events
+  def test_chrome_user_event_hashes(self):
+    return DiffTestBlueprint(
+        trace=TextProto(r"""
+        packet {
+          trusted_packet_sequence_id: 1
+          timestamp: 0
+          incremental_state_cleared: true
+          track_event {
+            categories: "cat1"
+            type: 3
+            name_iid: 1
+            chrome_user_event {
+              action_hash: 10
+            }
+          }
+        }
+        packet {
+          trusted_packet_sequence_id: 1
+          timestamp: 0
+          incremental_state_cleared: true
+          track_event {
+            categories: "cat2"
+            type: 3
+            name_iid: 2
+            chrome_user_event {
+              action_hash: 20
+            }
+          }
+        }
+        """),
+        query=Metric('chrome_user_event_hashes'),
+        out=TextProto(r"""
+        [perfetto.protos.chrome_user_event_hashes]: {
+          action_hash: 10
+          action_hash: 20
+        }
+        """))
+
+  # Chrome performance mark
+  def test_chrome_performance_mark_hashes(self):
+    return DiffTestBlueprint(
+        trace=TextProto(r"""
+        packet {
+          trusted_packet_sequence_id: 1
+          timestamp: 0
+          incremental_state_cleared: true
+          track_event {
+            categories: "cat1"
+            type: 3
+            name: "name1"
+            [perfetto.protos.ChromeTrackEvent.chrome_hashed_performance_mark] {
+              site_hash: 10
+              mark_hash: 100
+            }
+          }
+        }
+        packet {
+          trusted_packet_sequence_id: 1
+          timestamp: 0
+          incremental_state_cleared: true
+          track_event {
+            categories: "cat2"
+            type: 3
+            name: "name2"
+            [perfetto.protos.ChromeTrackEvent.chrome_hashed_performance_mark] {
+              site_hash: 20
+              mark_hash: 200
+            }
+          }
+        }
+        """),
+        query=Metric('chrome_performance_mark_hashes'),
+        out=TextProto(r"""
+        [perfetto.protos.chrome_performance_mark_hashes]: {
+          site_hash: 10
+          site_hash: 20
+          mark_hash: 100
+          mark_hash: 200
+        }
+        """))
+
+  # Chrome reliable range
+  def test_chrome_reliable_range(self):
+    return DiffTestBlueprint(
+        trace=Path('chrome_reliable_range.textproto'),
+        query=Path('chrome_reliable_range_test.sql'),
+        out=Csv("""
+        "start","reason","debug_limiting_upid","debug_limiting_utid"
+        12,"First slice for utid=2","[NULL]",2
+        """))
+
+  def test_chrome_reliable_range_cropping(self):
+    return DiffTestBlueprint(
+        trace=Path('chrome_reliable_range_cropping.textproto'),
+        query=Path('chrome_reliable_range_test.sql'),
+        out=Csv("""
+        "start","reason","debug_limiting_upid","debug_limiting_utid"
+        10000,"Range of interest packet","[NULL]",2
+        """))
+
+  def test_chrome_reliable_range_missing_processes(self):
+    return DiffTestBlueprint(
+        trace=Path('chrome_reliable_range_missing_processes.textproto'),
+        query=Path('chrome_reliable_range_test.sql'),
+        out=Csv("""
+        "start","reason","debug_limiting_upid","debug_limiting_utid"
+        1011,"Missing process data for upid=2",2,1
+        """))
+
+  def test_chrome_reliable_range_missing_browser_main(self):
+    return DiffTestBlueprint(
+        trace=Path('chrome_reliable_range_missing_browser_main.textproto'),
+        query=Path('chrome_reliable_range_test.sql'),
+        out=Csv("""
+        "start","reason","debug_limiting_upid","debug_limiting_utid"
+        1011,"Missing main thread for upid=1",1,1
+        """))
+
+  def test_chrome_reliable_range_missing_gpu_main(self):
+    return DiffTestBlueprint(
+        trace=Path('chrome_reliable_range_missing_gpu_main.textproto'),
+        query=Path('chrome_reliable_range_test.sql'),
+        out=Csv("""
+        "start","reason","debug_limiting_upid","debug_limiting_utid"
+        1011,"Missing main thread for upid=1",1,1
+        """))
+
+  def test_chrome_reliable_range_missing_renderer_main(self):
+    return DiffTestBlueprint(
+        trace=Path('chrome_reliable_range_missing_renderer_main.textproto'),
+        query=Path('chrome_reliable_range_test.sql'),
+        out=Csv("""
+        "start","reason","debug_limiting_upid","debug_limiting_utid"
+        1011,"Missing main thread for upid=1",1,1
+        """))
+
+  def test_chrome_reliable_range_non_chrome_process(self):
+    return DiffTestBlueprint(
+        # We need a trace with a large number of non-chrome slices, so that the
+        # reliable range is affected by their filtering.
+        trace=DataPath('example_android_trace_30s.pb'),
+        query=Path('chrome_reliable_range_test.sql'),
+        out=Csv("""
+        "start","reason","debug_limiting_upid","debug_limiting_utid"
+        0,"[NULL]","[NULL]","[NULL]"
+        """))
+
+  # Chrome slices
+  def test_chrome_slice_names(self):
+    return DiffTestBlueprint(
+        trace=TextProto(r"""
+        packet {
+          trusted_packet_sequence_id: 1
+          timestamp: 1000
+          track_event {
+            categories: "cat"
+            name: "Looper.Dispatch: class1"
+            type: 3
+          }
+        }
+        packet {
+          trusted_packet_sequence_id: 1
+          timestamp: 2000
+          track_event {
+            categories: "cat"
+            name: "name2"
+            type: 3
+          }
+        }
+        packet {
+          chrome_metadata {
+            chrome_version_code: 123
+          }
+        }
+        """),
+        query=Metric('chrome_slice_names'),
+        out=TextProto(r"""
+        [perfetto.protos.chrome_slice_names]: {
+          chrome_version_code: 123
+          slice_name: "Looper.Dispatch: class1"
+          slice_name: "name2"
+        }
+        """))
+
+  # Chrome stack samples.
+  def test_chrome_stack_samples_for_task(self):
+    return DiffTestBlueprint(
+        trace=DataPath('chrome_stack_traces_symbolized_trace.pftrace'),
+        query="""
+        SELECT RUN_METRIC('chrome/chrome_stack_samples_for_task.sql',
+            'target_duration_ms', '0.000001',
+            'thread_name', '"CrBrowserMain"',
+            'task_name', '"sendTouchEvent"');
+
+        SELECT
+          sample.description,
+          sample.ts,
+          sample.depth
+        FROM chrome_stack_samples_for_task sample
+        JOIN (
+            SELECT
+              ts,
+              dur
+            FROM slice
+            WHERE ts = 696373965001470
+        ) test_slice
+        ON sample.ts >= test_slice.ts
+          AND sample.ts <= test_slice.ts + test_slice.dur
+        ORDER BY sample.ts, sample.depth;
+        """,
+        out=Path('chrome_stack_samples_for_task_test.out'))
+
+  # Trace proto content
+  def test_proto_content(self):
+    return DiffTestBlueprint(
+        trace=DataPath('chrome_scroll_without_vsync.pftrace'),
+        query=Path('proto_content_test.sql'),
+        out=Path('proto_content.out'))
+
+  # TODO(mayzner): Uncomment when it works
+  # def test_proto_content_path(self):
+  #   return DiffTestBlueprint(
+  #       trace=DataPath('chrome_scroll_without_vsync.pftrace'),
+  #       query=Path('proto_content_path_test.sql'),
+  #       out=Csv("""
+  #       "total_size","field_type","field_name","parent_id","event_category","event_name"
+  #       137426,"TracePacket","[NULL]","[NULL]","[NULL]","[NULL]"
+  #       59475,"TrackEvent","#track_event",415,"[NULL]","[NULL]"
+  #       37903,"TrackEvent","#track_event",17,"[NULL]","[NULL]"
+  #       35904,"int32","#trusted_uid",17,"[NULL]","[NULL]"
+  #       35705,"TracePacket","[NULL]","[NULL]","input,benchmark","LatencyInfo.Flow"
+  #       29403,"TracePacket","[NULL]","[NULL]","cc,input","[NULL]"
+  #       24703,"ChromeLatencyInfo","#chrome_latency_info",18,"[NULL]","[NULL]"
+  #       22620,"uint64","#time_us",26,"[NULL]","[NULL]"
+  #       18711,"TrackEvent","#track_event",1467,"[NULL]","[NULL]"
+  #       15606,"uint64","#timestamp",17,"[NULL]","[NULL]"
+  #       """))
diff --git a/test/trace_processor/diff_tests/chrome/tests_args.py b/test/trace_processor/diff_tests/metrics/chrome/tests_args.py
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/tests_args.py
rename to test/trace_processor/diff_tests/metrics/chrome/tests_args.py
diff --git a/test/trace_processor/diff_tests/chrome/tests_processes.py b/test/trace_processor/diff_tests/metrics/chrome/tests_processes.py
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/tests_processes.py
rename to test/trace_processor/diff_tests/metrics/chrome/tests_processes.py
diff --git a/test/trace_processor/diff_tests/chrome/tests_rail_modes.py b/test/trace_processor/diff_tests/metrics/chrome/tests_rail_modes.py
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/tests_rail_modes.py
rename to test/trace_processor/diff_tests/metrics/chrome/tests_rail_modes.py
diff --git a/test/trace_processor/diff_tests/chrome/tests_scroll_jank.py b/test/trace_processor/diff_tests/metrics/chrome/tests_scroll_jank.py
similarity index 81%
rename from test/trace_processor/diff_tests/chrome/tests_scroll_jank.py
rename to test/trace_processor/diff_tests/metrics/chrome/tests_scroll_jank.py
index ef8ec6a..c2336ce 100644
--- a/test/trace_processor/diff_tests/chrome/tests_scroll_jank.py
+++ b/test/trace_processor/diff_tests/metrics/chrome/tests_scroll_jank.py
@@ -19,7 +19,7 @@
 from python.generators.diff_tests.testing import TestSuite
 
 
-class ChromeScrollJank(TestSuite):
+class ChromeScrollJankMetrics(TestSuite):
   # Scroll jank metrics
   def test_scroll_jank_general_validation(self):
     return DiffTestBlueprint(
@@ -44,33 +44,6 @@
         """,
         out=Path('scroll_jank.out'))
 
-  def test_chrome_frames_with_missed_vsyncs(self):
-    return DiffTestBlueprint(
-        trace=DataPath('chrome_input_with_frame_view.pftrace'),
-        query="""
-        INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_jank_v3;
-
-        SELECT
-          cause_of_jank,
-          sub_cause_of_jank,
-          delay_since_last_frame,
-          vsync_interval
-        FROM chrome_janky_frames;
-        """,
-        out=Path('scroll_jank_v3.out'))
-
-  def test_chrome_frames_with_missed_vsyncs_percentage(self):
-    return DiffTestBlueprint(
-        trace=DataPath('chrome_input_with_frame_view.pftrace'),
-        query="""
-        INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_jank_v3;
-
-        SELECT
-          delayed_frame_percentage
-        FROM chrome_janky_frames_percentage;
-        """,
-        out=Path('scroll_jank_v3_percentage.out'))
-
   def test_scroll_flow_event(self):
     return DiffTestBlueprint(
         trace=DataPath('chrome_scroll_without_vsync.pftrace'),
@@ -355,7 +328,7 @@
 
   def test_chrome_thread_slice_repeated(self):
     return DiffTestBlueprint(
-        trace=Path('../track_event/track_event_counters.textproto'),
+        trace=Path('../../parser/track_event/track_event_counters.textproto'),
         query="""
         SELECT RUN_METRIC('chrome/chrome_thread_slice.sql');
 
@@ -450,49 +423,6 @@
         115000000,0
         """))
 
-  def test_chrome_scrolls(self):
-    return DiffTestBlueprint(
-        trace=Path('chrome_scroll_check.py'),
-        query="""
-        INCLUDE PERFETTO MODULE chrome.chrome_scrolls;
-
-        SELECT
-          id,
-          ts,
-          dur,
-          gesture_scroll_begin_ts,
-          gesture_scroll_end_ts
-        FROM chrome_scrolls
-        ORDER by id;
-        """,
-        out=Csv("""
-        "id","ts","dur","gesture_scroll_begin_ts","gesture_scroll_end_ts"
-        5678,0,55000000,0,45000000
-        5679,60000000,40000000,60000000,90000000
-        5680,80000000,30000000,80000000,100000000
-        5681,120000000,70000000,120000000,"[NULL]"
-        """))
-
-  def test_chrome_scroll_intervals(self):
-    return DiffTestBlueprint(
-        trace=Path('chrome_scroll_check.py'),
-        query="""
-        INCLUDE PERFETTO MODULE chrome.chrome_scrolls;
-
-        SELECT
-          id,
-          ts,
-          dur
-        FROM chrome_scrolling_intervals
-        ORDER by id;
-        """,
-        out=Csv("""
-        "id","ts","dur"
-        1,0,55000000
-        2,60000000,50000000
-        3,120000000,70000000
-        """))
-
   def test_chrome_scroll_jank_v3(self):
     return DiffTestBlueprint(
         trace=DataPath('chrome_input_with_frame_view.pftrace'),
@@ -528,52 +458,4 @@
             }
           }
         }
-        """))
-
-  def test_chrome_scroll_input_offsets(self):
-    return DiffTestBlueprint(
-        trace=DataPath('scroll_offsets.pftrace'),
-        query="""
-        SELECT IMPORT('chrome.scroll_jank.scroll_offsets');
-
-        SELECT
-          scroll_update_id,
-          ts,
-          delta_y,
-          offset_y
-        FROM chrome_scroll_input_offsets
-        ORDER by ts
-        LIMIT 5;
-        """,
-        out=Csv("""
-        "scroll_update_id","ts","delta_y","offset_y"
-        1983,4687296612739,-36.999939,-36.999939
-        1983,4687307175845,-39.000092,-76.000031
-        1987,4687313206739,-35.999969,-112.000000
-        1987,4687323152462,-35.000000,-147.000000
-        1991,4687329240739,-28.999969,-175.999969
-        """))
-
-  def test_chrome_presented_scroll_offsets(self):
-    return DiffTestBlueprint(
-        trace=DataPath('scroll_offsets.pftrace'),
-        query="""
-        SELECT IMPORT('chrome.scroll_jank.scroll_offsets');
-
-        SELECT
-          scroll_update_id,
-          ts,
-          delta_y,
-          offset_y
-        FROM chrome_presented_scroll_offsets
-        ORDER by ts
-        LIMIT 5;
-        """,
-        out=Csv("""
-        "scroll_update_id","ts","delta_y","offset_y"
-        1983,4687296612739,"[NULL]",0
-        1987,4687313206739,-50,-50
-        1991,4687329240739,-50,-100
-        1993,4687336155739,-81,-181
-        1996,4687346164739,-66,-247
-        """))
+        """))
\ No newline at end of file
diff --git a/test/trace_processor/diff_tests/chrome/tests_touch_gesture.py b/test/trace_processor/diff_tests/metrics/chrome/tests_touch_gesture.py
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/tests_touch_gesture.py
rename to test/trace_processor/diff_tests/metrics/chrome/tests_touch_gesture.py
diff --git a/test/trace_processor/diff_tests/chrome/touch_flow_event.out b/test/trace_processor/diff_tests/metrics/chrome/touch_flow_event.out
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/touch_flow_event.out
rename to test/trace_processor/diff_tests/metrics/chrome/touch_flow_event.out
diff --git a/test/trace_processor/diff_tests/chrome/touch_flow_event_queuing_delay.out b/test/trace_processor/diff_tests/metrics/chrome/touch_flow_event_queuing_delay.out
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/touch_flow_event_queuing_delay.out
rename to test/trace_processor/diff_tests/metrics/chrome/touch_flow_event_queuing_delay.out
diff --git a/test/trace_processor/diff_tests/chrome/touch_flow_event_queuing_delay_synth.out b/test/trace_processor/diff_tests/metrics/chrome/touch_flow_event_queuing_delay_synth.out
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/touch_flow_event_queuing_delay_synth.out
rename to test/trace_processor/diff_tests/metrics/chrome/touch_flow_event_queuing_delay_synth.out
diff --git a/test/trace_processor/diff_tests/chrome/touch_flow_event_synth.out b/test/trace_processor/diff_tests/metrics/chrome/touch_flow_event_synth.out
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/touch_flow_event_synth.out
rename to test/trace_processor/diff_tests/metrics/chrome/touch_flow_event_synth.out
diff --git a/test/trace_processor/diff_tests/chrome/touch_jank.out b/test/trace_processor/diff_tests/metrics/chrome/touch_jank.out
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/touch_jank.out
rename to test/trace_processor/diff_tests/metrics/chrome/touch_jank.out
diff --git a/test/trace_processor/diff_tests/chrome/touch_jank.py b/test/trace_processor/diff_tests/metrics/chrome/touch_jank.py
similarity index 99%
rename from test/trace_processor/diff_tests/chrome/touch_jank.py
rename to test/trace_processor/diff_tests/metrics/chrome/touch_jank.py
index 0dd0e8d..8262bc7 100644
--- a/test/trace_processor/diff_tests/chrome/touch_jank.py
+++ b/test/trace_processor/diff_tests/metrics/chrome/touch_jank.py
@@ -22,6 +22,7 @@
 import synth_common
 
 from synth_common import ms_to_ns
+
 trace = synth_common.create_trace()
 
 process_track1 = 1234
diff --git a/test/trace_processor/diff_tests/chrome/unsymbolized_args.textproto b/test/trace_processor/diff_tests/metrics/chrome/unsymbolized_args.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/unsymbolized_args.textproto
rename to test/trace_processor/diff_tests/metrics/chrome/unsymbolized_args.textproto
diff --git a/test/trace_processor/diff_tests/codecs/codec-framedecoder-trace.out b/test/trace_processor/diff_tests/metrics/codecs/codec-framedecoder-trace.out
similarity index 100%
rename from test/trace_processor/diff_tests/codecs/codec-framedecoder-trace.out
rename to test/trace_processor/diff_tests/metrics/codecs/codec-framedecoder-trace.out
diff --git a/test/trace_processor/diff_tests/codecs/tests.py b/test/trace_processor/diff_tests/metrics/codecs/tests.py
similarity index 100%
rename from test/trace_processor/diff_tests/codecs/tests.py
rename to test/trace_processor/diff_tests/metrics/codecs/tests.py
diff --git a/test/trace_processor/diff_tests/performance/frame_timeline_metric.out b/test/trace_processor/diff_tests/metrics/frame_timeline/frame_timeline_metric.out
similarity index 100%
rename from test/trace_processor/diff_tests/performance/frame_timeline_metric.out
rename to test/trace_processor/diff_tests/metrics/frame_timeline/frame_timeline_metric.out
diff --git a/test/trace_processor/diff_tests/performance/frame_timeline_metric.py b/test/trace_processor/diff_tests/metrics/frame_timeline/frame_timeline_metric.py
similarity index 99%
rename from test/trace_processor/diff_tests/performance/frame_timeline_metric.py
rename to test/trace_processor/diff_tests/metrics/frame_timeline/frame_timeline_metric.py
index 05cfee3..c7c1885 100755
--- a/test/trace_processor/diff_tests/performance/frame_timeline_metric.py
+++ b/test/trace_processor/diff_tests/metrics/frame_timeline/frame_timeline_metric.py
@@ -181,7 +181,6 @@
     prediction_type=PredictionType.PREDICTION_VALID)
 trace.add_frame_end_event(ts=14000000, cookie=25)
 
-
 trace.add_actual_surface_frame_start_event(
     ts=14500000,
     cookie=30,
@@ -196,7 +195,6 @@
     prediction_type=PredictionType.PREDICTION_VALID)
 trace.add_frame_end_event(ts=15000000, cookie=30)
 
-
 trace.add_actual_surface_frame_start_event(
     ts=15500000,
     cookie=35,
@@ -211,7 +209,6 @@
     prediction_type=PredictionType.PREDICTION_VALID)
 trace.add_frame_end_event(ts=16000000, cookie=35)
 
-
 trace.add_actual_surface_frame_start_event(
     ts=16500000,
     cookie=40,
diff --git a/test/trace_processor/diff_tests/metrics/frame_timeline/tests.py b/test/trace_processor/diff_tests/metrics/frame_timeline/tests.py
new file mode 100644
index 0000000..24ef27a
--- /dev/null
+++ b/test/trace_processor/diff_tests/metrics/frame_timeline/tests.py
@@ -0,0 +1,28 @@
+#!/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 FrameTimeline(TestSuite):
+  # frame_timeline_metric collects App_Deadline_Missed metrics
+  def test_frame_timeline_metric(self):
+    return DiffTestBlueprint(
+        trace=Path('frame_timeline_metric.py'),
+        query=Metric('android_frame_timeline_metric'),
+        out=Path('frame_timeline_metric.out'))
diff --git a/test/trace_processor/diff_tests/graphics/android_jank_cuj.out b/test/trace_processor/diff_tests/metrics/graphics/android_jank_cuj.out
similarity index 100%
rename from test/trace_processor/diff_tests/graphics/android_jank_cuj.out
rename to test/trace_processor/diff_tests/metrics/graphics/android_jank_cuj.out
diff --git a/test/trace_processor/diff_tests/graphics/android_jank_cuj.py b/test/trace_processor/diff_tests/metrics/graphics/android_jank_cuj.py
similarity index 92%
rename from test/trace_processor/diff_tests/graphics/android_jank_cuj.py
rename to test/trace_processor/diff_tests/metrics/graphics/android_jank_cuj.py
index beb39f7..4909eba 100644
--- a/test/trace_processor/diff_tests/graphics/android_jank_cuj.py
+++ b/test/trace_processor/diff_tests/metrics/graphics/android_jank_cuj.py
@@ -36,6 +36,7 @@
 SHADE_CUJ_TRACK = 654
 CANCELED_CUJ_TRACK = 987
 
+
 def add_instant_for_track(trace, ts, track, name):
   trace.add_track_event_slice(ts=ts, dur=0, track=track, name=name)
 
@@ -98,9 +99,9 @@
   add_main_thread_atrace(trace, ts_do_frame, ts_end_do_frame,
                          "Choreographer#doFrame %d" % vsync)
   if resync:
-    add_main_thread_atrace(trace, ts_do_frame, ts_end_do_frame,
-                           "Choreographer#doFrame - resynced to %d in 0.0s"
-                           % (vsync+1))
+    add_main_thread_atrace(
+        trace, ts_do_frame, ts_end_do_frame,
+        "Choreographer#doFrame - resynced to %d in 0.0s" % (vsync + 1))
   gpu_idx = 1000 + vsync * 10 + 1
   if ts_gpu is None:
     gpu_fence_message = "GPU completion fence %d has signaled"
@@ -117,13 +118,13 @@
 
 
 def add_sf_frame(trace,
-    vsync,
-    ts_commit,
-    ts_end_commit,
-    ts_composite,
-    ts_end_composite,
-    ts_compose_surfaces=None,
-    ts_end_compose_surfaces=None):
+                 vsync,
+                 ts_commit,
+                 ts_end_commit,
+                 ts_composite,
+                 ts_end_composite,
+                 ts_compose_surfaces=None,
+                 ts_end_compose_surfaces=None):
   add_sf_main_thread_atrace(trace, ts_commit, ts_end_commit,
                             "commit %d" % vsync)
   add_sf_main_thread_atrace_begin(trace, ts_composite, "composite %d" % vsync)
@@ -168,13 +169,13 @@
 
 
 def add_actual_surface_frame_events(ts,
-    dur,
-    token,
-    cookie=None,
-    jank=None,
-    on_time_finish_override=None,
-    display_frame_token_override=None,
-    layer_name=LAYER):
+                                    dur,
+                                    token,
+                                    cookie=None,
+                                    jank=None,
+                                    on_time_finish_override=None,
+                                    display_frame_token_override=None,
+                                    layer_name=LAYER):
   if cookie is None:
     cookie = token + 1
   jank_type = jank if jank is not None else 1
@@ -220,11 +221,11 @@
 trace.add_track_descriptor(FIRST_CUJ_TRACK, parent=PROCESS_TRACK)
 trace.add_track_descriptor(SHADE_CUJ_TRACK, parent=PROCESS_TRACK)
 trace.add_track_descriptor(CANCELED_CUJ_TRACK, parent=PROCESS_TRACK)
-trace.add_track_event_slice_begin(ts=5, track=FIRST_CUJ_TRACK,
-                                  name="J<FIRST_CUJ>")
+trace.add_track_event_slice_begin(
+    ts=5, track=FIRST_CUJ_TRACK, name="J<FIRST_CUJ>")
 trace.add_track_event_slice_end(ts=100_000_000, track=FIRST_CUJ_TRACK)
-trace.add_track_event_slice_begin(ts=10, track=SHADE_CUJ_TRACK,
-                                  name="J<SHADE_ROW_EXPAND>")
+trace.add_track_event_slice_begin(
+    ts=10, track=SHADE_CUJ_TRACK, name="J<SHADE_ROW_EXPAND>")
 trace.add_track_event_slice_end(ts=901_000_010, track=SHADE_CUJ_TRACK)
 add_instant_for_track(trace, ts=11, track=SHADE_CUJ_TRACK, name="FT#layerId#0")
 add_instant_for_track(
@@ -244,29 +245,13 @@
 trace.add_ftrace_packet(cpu=0)
 
 trace.add_atrace_counter(
-    ts=150_000_000,
-    tid=PID,
-    pid=PID,
-    buf="J<FIRST_CUJ>#totalFrames",
-    cnt=6)
+    ts=150_000_000, tid=PID, pid=PID, buf="J<FIRST_CUJ>#totalFrames", cnt=6)
 trace.add_atrace_counter(
-    ts=150_100_000,
-    tid=PID,
-    pid=PID,
-    buf="J<FIRST_CUJ>#missedFrames",
-    cnt=5)
+    ts=150_100_000, tid=PID, pid=PID, buf="J<FIRST_CUJ>#missedFrames", cnt=5)
 trace.add_atrace_counter(
-    ts=150_200_000,
-    tid=PID,
-    pid=PID,
-    buf="J<FIRST_CUJ>#missedAppFrames",
-    cnt=5)
+    ts=150_200_000, tid=PID, pid=PID, buf="J<FIRST_CUJ>#missedAppFrames", cnt=5)
 trace.add_atrace_counter(
-    ts=150_300_000,
-    tid=PID,
-    pid=PID,
-    buf="J<FIRST_CUJ>#missedSfFrames",
-    cnt=1)
+    ts=150_300_000, tid=PID, pid=PID, buf="J<FIRST_CUJ>#missedSfFrames", cnt=1)
 trace.add_atrace_counter(
     ts=150_400_000,
     tid=PID,
@@ -644,8 +629,8 @@
     ts_gpu=1_400_000_000,
     ts_end_gpu=1_500_000_000)
 
-add_instant_for_track(trace, ts=990_000_000, track=CANCELED_CUJ_TRACK,
-                      name="FT#cancel#0")
+add_instant_for_track(
+    trace, ts=990_000_000, track=CANCELED_CUJ_TRACK, name="FT#cancel#0")
 
 add_expected_display_frame_events(ts=1_000_000_000, dur=16_000_000, token=10)
 add_actual_display_frame_events(ts=1_000_000_000, dur=16_000_000, token=10)
@@ -704,11 +689,15 @@
     ts=200_000_000, dur=22_000_000, token=100, jank=34)
 
 add_expected_surface_frame_events(ts=300_000_000, dur=20_000_000, token=110)
-add_actual_surface_frame_events(ts=300_000_000, cookie=112, dur=61_000_000,
-                                token=110)
-add_actual_surface_frame_events(ts=300_000_000, cookie=114, dur=80_000_000,
-                                token=110, jank=64,
-                                layer_name="TX - JankyLayer#1")
+add_actual_surface_frame_events(
+    ts=300_000_000, cookie=112, dur=61_000_000, token=110)
+add_actual_surface_frame_events(
+    ts=300_000_000,
+    cookie=114,
+    dur=80_000_000,
+    token=110,
+    jank=64,
+    layer_name="TX - JankyLayer#1")
 
 add_expected_surface_frame_events(ts=400_000_000, dur=20_000_000, token=120)
 add_actual_surface_frame_events(
@@ -732,10 +721,7 @@
 # Surface flinger stuffing frame not classified as missed
 add_expected_surface_frame_events(ts=650_000_000, dur=20_000_000, token=145)
 add_actual_surface_frame_events(
-    ts=650_000_000,
-    dur=20_000_000,
-    token=145,
-    jank=512)
+    ts=650_000_000, dur=20_000_000, token=145, jank=512)
 
 add_expected_surface_frame_events(ts=700_000_000, dur=20_000_000, token=150)
 add_actual_surface_frame_events(ts=700_500_000, dur=14_500_000, token=150)
diff --git a/test/trace_processor/diff_tests/graphics/android_jank_cuj_query.out b/test/trace_processor/diff_tests/metrics/graphics/android_jank_cuj_query.out
similarity index 100%
rename from test/trace_processor/diff_tests/graphics/android_jank_cuj_query.out
rename to test/trace_processor/diff_tests/metrics/graphics/android_jank_cuj_query.out
diff --git a/test/trace_processor/diff_tests/graphics/android_jank_cuj_query_test.sql b/test/trace_processor/diff_tests/metrics/graphics/android_jank_cuj_query_test.sql
similarity index 100%
rename from test/trace_processor/diff_tests/graphics/android_jank_cuj_query_test.sql
rename to test/trace_processor/diff_tests/metrics/graphics/android_jank_cuj_query_test.sql
diff --git a/test/trace_processor/diff_tests/graphics/composer_execution.py b/test/trace_processor/diff_tests/metrics/graphics/composer_execution.py
similarity index 94%
rename from test/trace_processor/diff_tests/graphics/composer_execution.py
rename to test/trace_processor/diff_tests/metrics/graphics/composer_execution.py
index b93044d..35e1184 100644
--- a/test/trace_processor/diff_tests/graphics/composer_execution.py
+++ b/test/trace_processor/diff_tests/metrics/graphics/composer_execution.py
@@ -48,10 +48,12 @@
 # separated validation where HwcValidateDisplay is executed from worker thread
 trace.add_atrace_begin(ts=2_100, tid=10335, pid=10335, buf="composite 3")
 trace.add_atrace_begin(ts=2_200, tid=15000, pid=10335, buf="otherFunction")
-trace.add_atrace_begin(ts=2_300, tid=15000, pid=10335, buf="HwcValidateDisplay 1")
+trace.add_atrace_begin(
+    ts=2_300, tid=15000, pid=10335, buf="HwcValidateDisplay 1")
 trace.add_atrace_end(ts=2_400, tid=15000, pid=10335)
 trace.add_atrace_end(ts=2_500, tid=15000, pid=10335)
-trace.add_atrace_begin(ts=2_600, tid=10335, pid=10335, buf="HwcPresentDisplay 1")
+trace.add_atrace_begin(
+    ts=2_600, tid=10335, pid=10335, buf="HwcPresentDisplay 1")
 trace.add_atrace_end(ts=2_700, tid=10335, pid=10335)
 trace.add_atrace_end(ts=2_800, tid=10335, pid=10335)
 
@@ -73,4 +75,4 @@
     ts=4_200, tid=10335, pid=10335, buf="HwcValidateDisplay 1")
 trace.add_atrace_end(ts=4_300, tid=10335, pid=10335)
 
-sys.stdout.buffer.write(trace.trace.SerializeToString())
\ No newline at end of file
+sys.stdout.buffer.write(trace.trace.SerializeToString())
diff --git a/test/trace_processor/diff_tests/metrics/graphics/composition_layer.py b/test/trace_processor/diff_tests/metrics/graphics/composition_layer.py
new file mode 100644
index 0000000..7788c06
--- /dev/null
+++ b/test/trace_processor/diff_tests/metrics/graphics/composition_layer.py
@@ -0,0 +1,61 @@
+#!/usr/bin/env python3
+# 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.
+
+# This synthetic trace tests handling of the mm_id field in the rss_stat
+# event when mm_structs are reused on process death.
+
+from os import sys, path
+
+import synth_common
+
+trace = synth_common.create_trace()
+
+trace.add_packet(ts=1)
+trace.add_process(10, 1, "parent_process")
+trace.add_process(11, 10, "child_process")
+
+trace.add_ftrace_packet(1)
+
+trace.add_print(
+    ts=99, tid=11, buf='C|10|HWComposer: Total Layer for PrimaryDisplay(0)|7')
+trace.add_print(
+    ts=100, tid=11, buf='C|10|HWComposer: Total Layer for PrimaryDisplay(0)|5')
+trace.add_print(
+    ts=101, tid=11, buf='C|10|HWComposer: Total Layer for PrimaryDisplay(0)|6')
+trace.add_print(
+    ts=102, tid=11, buf='C|10|HWComposer: Total Layer for PrimaryDisplay(0)|0')
+trace.add_print(
+    ts=103, tid=11, buf='C|10|HWComposer: Total Layer for PrimaryDisplay(0)|4')
+trace.add_print(
+    ts=104, tid=11, buf='C|10|HWComposer: Total Layer for PrimaryDisplay(0)|6')
+trace.add_print(
+    ts=105, tid=11, buf='C|10|HWComposer: Total Layer for PrimaryDisplay(0)|0')
+trace.add_print(
+    ts=106, tid=11, buf='C|10|HWComposer: Total Layer for PrimaryDisplay(0)|4')
+trace.add_print(
+    ts=107, tid=11, buf='C|10|HWComposer: Total Layer for PrimaryDisplay(0)|5')
+trace.add_print(
+    ts=108, tid=11, buf='C|10|HWComposer: Total Layer for PrimaryDisplay(0)|0')
+trace.add_print(
+    ts=108, tid=11, buf='C|10|HWComposer: Total Layer for PrimaryDisplay(0)|3')
+trace.add_print(
+    ts=108, tid=11, buf='C|10|HWComposer: Total Layer for PrimaryDisplay(0)|0')
+
+trace.add_print(
+    ts=109,
+    tid=11,
+    buf='C|10|HWComposer: Total Layer for SecondaryDisplay(1)|5')
+
+sys.stdout.buffer.write(trace.trace.SerializeToString())
diff --git a/test/trace_processor/diff_tests/graphics/display_metrics.py b/test/trace_processor/diff_tests/metrics/graphics/display_metrics.py
similarity index 100%
rename from test/trace_processor/diff_tests/graphics/display_metrics.py
rename to test/trace_processor/diff_tests/metrics/graphics/display_metrics.py
diff --git a/test/trace_processor/diff_tests/graphics/dpu_vote_clock_bw.textproto b/test/trace_processor/diff_tests/metrics/graphics/dpu_vote_clock_bw.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/graphics/dpu_vote_clock_bw.textproto
rename to test/trace_processor/diff_tests/metrics/graphics/dpu_vote_clock_bw.textproto
diff --git a/test/trace_processor/diff_tests/graphics/frame_missed.py b/test/trace_processor/diff_tests/metrics/graphics/frame_missed.py
similarity index 100%
rename from test/trace_processor/diff_tests/graphics/frame_missed.py
rename to test/trace_processor/diff_tests/metrics/graphics/frame_missed.py
diff --git a/test/trace_processor/diff_tests/graphics/g2d_metrics.out b/test/trace_processor/diff_tests/metrics/graphics/g2d_metrics.out
similarity index 100%
rename from test/trace_processor/diff_tests/graphics/g2d_metrics.out
rename to test/trace_processor/diff_tests/metrics/graphics/g2d_metrics.out
diff --git a/test/trace_processor/diff_tests/graphics/g2d_metrics.textproto b/test/trace_processor/diff_tests/metrics/graphics/g2d_metrics.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/graphics/g2d_metrics.textproto
rename to test/trace_processor/diff_tests/metrics/graphics/g2d_metrics.textproto
diff --git a/test/trace_processor/diff_tests/graphics/gpu_frequency_metric.out b/test/trace_processor/diff_tests/metrics/graphics/gpu_frequency_metric.out
similarity index 100%
rename from test/trace_processor/diff_tests/graphics/gpu_frequency_metric.out
rename to test/trace_processor/diff_tests/metrics/graphics/gpu_frequency_metric.out
diff --git a/test/trace_processor/diff_tests/graphics/gpu_frequency_metric.textproto b/test/trace_processor/diff_tests/metrics/graphics/gpu_frequency_metric.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/graphics/gpu_frequency_metric.textproto
rename to test/trace_processor/diff_tests/metrics/graphics/gpu_frequency_metric.textproto
diff --git a/test/trace_processor/diff_tests/graphics/gpu_metric.py b/test/trace_processor/diff_tests/metrics/graphics/gpu_metric.py
similarity index 100%
rename from test/trace_processor/diff_tests/graphics/gpu_metric.py
rename to test/trace_processor/diff_tests/metrics/graphics/gpu_metric.py
diff --git a/test/trace_processor/diff_tests/graphics/surfaceflinger_gpu_invocation.py b/test/trace_processor/diff_tests/metrics/graphics/surfaceflinger_gpu_invocation.py
similarity index 100%
rename from test/trace_processor/diff_tests/graphics/surfaceflinger_gpu_invocation.py
rename to test/trace_processor/diff_tests/metrics/graphics/surfaceflinger_gpu_invocation.py
diff --git a/test/trace_processor/diff_tests/metrics/graphics/tests.py b/test/trace_processor/diff_tests/metrics/graphics/tests.py
new file mode 100644
index 0000000..f5bb896
--- /dev/null
+++ b/test/trace_processor/diff_tests/metrics/graphics/tests.py
@@ -0,0 +1,237 @@
+#!/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 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'),
+        query=Metric('android_surfaceflinger'),
+        out=TextProto(r"""
+        android_surfaceflinger {
+          missed_frames: 3
+          missed_hwc_frames: 0
+          missed_gpu_frames: 0
+          missed_frame_rate: 0.42857142857142855 # = 3/7
+          gpu_invocations: 0
+          metrics_per_display: {
+            display_id: "101"
+            missed_frames: 2
+            missed_hwc_frames: 0
+            missed_gpu_frames: 0
+            missed_frame_rate: 0.5
+          }
+          metrics_per_display: {
+            display_id: "102"
+            missed_frames: 1
+            missed_hwc_frames: 0
+            missed_gpu_frames: 0
+            missed_frame_rate: 0.33333333333333333
+          }
+        }
+        """))
+
+  def test_surfaceflinger_gpu_invocation(self):
+    return DiffTestBlueprint(
+        trace=Path('surfaceflinger_gpu_invocation.py'),
+        query=Metric('android_surfaceflinger'),
+        out=TextProto(r"""
+        android_surfaceflinger {
+          missed_frames: 0
+          missed_hwc_frames: 0
+          missed_gpu_frames: 0
+          gpu_invocations: 4
+          avg_gpu_waiting_dur_ms: 4
+          total_non_empty_gpu_waiting_dur_ms: 11
+        }
+        """))
+
+  # GPU metrics
+  def test_gpu_metric(self):
+    return DiffTestBlueprint(
+        trace=Path('gpu_metric.py'),
+        query=Metric('android_gpu'),
+        out=TextProto(r"""
+        android_gpu {
+          processes {
+            name: "app_1"
+            mem_max: 8
+            mem_min: 2
+            mem_avg: 3
+          }
+          processes {
+            name: "app_2"
+            mem_max: 10
+            mem_min: 6
+            mem_avg: 8
+          }
+          mem_max: 4
+          mem_min: 1
+          mem_avg: 2
+        }
+        """))
+
+  def test_gpu_frequency_metric(self):
+    return DiffTestBlueprint(
+        trace=Path('gpu_frequency_metric.textproto'),
+        query=Metric('android_gpu'),
+        out=Path('gpu_frequency_metric.out'))
+
+  # Android Jank CUJ metric
+  def test_android_jank_cuj(self):
+    return DiffTestBlueprint(
+        trace=Path('android_jank_cuj.py'),
+        query=Metric('android_jank_cuj'),
+        out=Path('android_jank_cuj.out'))
+
+  def test_android_jank_cuj_query(self):
+    return DiffTestBlueprint(
+        trace=Path('android_jank_cuj.py'),
+        query=Path('android_jank_cuj_query_test.sql'),
+        out=Path('android_jank_cuj_query.out'))
+
+  # Composition layer
+  def test_composition_layer_count(self):
+    return DiffTestBlueprint(
+        trace=Path('composition_layer.py'),
+        query="""
+        SELECT RUN_METRIC('android/android_hwcomposer.sql');
+
+        SELECT display_id, AVG(value)
+        FROM total_layers
+        GROUP BY display_id;
+        """,
+        out=Csv("""
+        "display_id","AVG(value)"
+        "0",3.000000
+        "1",5.000000
+        """))
+
+  # G2D metrics TODO(rsavitski): find a real trace and double-check that the
+  # is realistic. One kernel's source I checked had tgid=0 for all counter
+  # Initial support was added/discussed in b/171296908.
+  def test_g2d_metrics(self):
+    return DiffTestBlueprint(
+        trace=Path('g2d_metrics.textproto'),
+        query=Metric('g2d'),
+        out=Path('g2d_metrics.out'))
+
+  # Composer execution
+  def test_composer_execution(self):
+    return DiffTestBlueprint(
+        trace=Path('composer_execution.py'),
+        query="""
+        SELECT RUN_METRIC('android/composer_execution.sql',
+          'output', 'hwc_execution_spans');
+
+        SELECT
+          validation_type,
+          display_id,
+          COUNT(*) AS count,
+          SUM(execution_time_ns) AS total
+        FROM hwc_execution_spans
+        GROUP BY validation_type, display_id
+        ORDER BY validation_type, display_id;
+        """,
+        out=Csv("""
+        "validation_type","display_id","count","total"
+        "separated_validation","1",1,200
+        "skipped_validation","0",2,200
+        "skipped_validation","1",1,100
+        "unknown","1",1,0
+        "unskipped_validation","0",1,200
+        """))
+
+  # Display metrics
+  def test_display_metrics(self):
+    return DiffTestBlueprint(
+        trace=Path('display_metrics.py'),
+        query=Metric('display_metrics'),
+        out=TextProto(r"""
+        display_metrics {
+          total_duplicate_frames: 0
+          duplicate_frames_logged: 0
+          total_dpu_underrun_count: 0
+          refresh_rate_switches: 5
+          refresh_rate_stats {
+            refresh_rate_fps: 60
+            count: 2
+            total_dur_ms: 2
+            avg_dur_ms: 1
+          }
+          refresh_rate_stats {
+            refresh_rate_fps: 90
+            count: 2
+            total_dur_ms: 2
+            avg_dur_ms: 1
+          }
+          refresh_rate_stats {
+            refresh_rate_fps: 120
+            count: 1
+            total_dur_ms: 2
+            avg_dur_ms: 2
+          }
+          update_power_state {
+            avg_runtime_micro_secs: 4000
+          }
+        }
+        """))
+
+  # DPU vote clock and bandwidth
+  def test_dpu_vote_clock_bw(self):
+    return DiffTestBlueprint(
+        trace=Path('dpu_vote_clock_bw.textproto'),
+        query=Metric('android_hwcomposer'),
+        out=TextProto(r"""
+        android_hwcomposer {
+          skipped_validation_count: 0
+          unskipped_validation_count: 0
+          separated_validation_count: 0
+          unknown_validation_count: 0
+          dpu_vote_metrics {
+            tid: 237
+            avg_dpu_vote_clock: 206250
+            avg_dpu_vote_avg_bw: 210000
+            avg_dpu_vote_peak_bw: 205000
+            avg_dpu_vote_rt_bw: 271000
+          }
+          dpu_vote_metrics {
+            tid: 299
+            avg_dpu_vote_clock: 250000
+          }
+        }
+        """))
\ No newline at end of file
diff --git a/test/trace_processor/diff_tests/performance/irq_runtime_metric.out b/test/trace_processor/diff_tests/metrics/irq/irq_runtime_metric.out
similarity index 100%
rename from test/trace_processor/diff_tests/performance/irq_runtime_metric.out
rename to test/trace_processor/diff_tests/metrics/irq/irq_runtime_metric.out
diff --git a/test/trace_processor/diff_tests/performance/irq_runtime_metric.textproto b/test/trace_processor/diff_tests/metrics/irq/irq_runtime_metric.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/performance/irq_runtime_metric.textproto
rename to test/trace_processor/diff_tests/metrics/irq/irq_runtime_metric.textproto
diff --git a/test/trace_processor/diff_tests/metrics/irq/tests.py b/test/trace_processor/diff_tests/metrics/irq/tests.py
new file mode 100644
index 0000000..f7b11fd
--- /dev/null
+++ b/test/trace_processor/diff_tests/metrics/irq/tests.py
@@ -0,0 +1,28 @@
+#!/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 IRQ(TestSuite):
+  # IRQ max runtime and count over 1ms
+  def test_irq_runtime_metric(self):
+    return DiffTestBlueprint(
+        trace=Path('irq_runtime_metric.textproto'),
+        query=Metric('android_irq_runtime'),
+        out=Path('irq_runtime_metric.out'))
\ No newline at end of file
diff --git a/test/trace_processor/diff_tests/memory/android_ion.py b/test/trace_processor/diff_tests/metrics/memory/android_ion.py
similarity index 100%
rename from test/trace_processor/diff_tests/memory/android_ion.py
rename to test/trace_processor/diff_tests/metrics/memory/android_ion.py
diff --git a/test/trace_processor/diff_tests/memory/android_lmk_reason.out b/test/trace_processor/diff_tests/metrics/memory/android_lmk_reason.out
similarity index 100%
rename from test/trace_processor/diff_tests/memory/android_lmk_reason.out
rename to test/trace_processor/diff_tests/metrics/memory/android_lmk_reason.out
diff --git a/test/trace_processor/diff_tests/memory/android_mem_by_priority.out b/test/trace_processor/diff_tests/metrics/memory/android_mem_by_priority.out
similarity index 100%
rename from test/trace_processor/diff_tests/memory/android_mem_by_priority.out
rename to test/trace_processor/diff_tests/metrics/memory/android_mem_by_priority.out
diff --git a/test/trace_processor/diff_tests/memory/android_mem_by_priority.py b/test/trace_processor/diff_tests/metrics/memory/android_mem_by_priority.py
similarity index 100%
rename from test/trace_processor/diff_tests/memory/android_mem_by_priority.py
rename to test/trace_processor/diff_tests/metrics/memory/android_mem_by_priority.py
diff --git a/test/trace_processor/diff_tests/memory/android_mem_counters.out b/test/trace_processor/diff_tests/metrics/memory/android_mem_counters.out
similarity index 100%
rename from test/trace_processor/diff_tests/memory/android_mem_counters.out
rename to test/trace_processor/diff_tests/metrics/memory/android_mem_counters.out
diff --git a/test/trace_processor/diff_tests/memory/android_mem_delta.py b/test/trace_processor/diff_tests/metrics/memory/android_mem_delta.py
similarity index 100%
rename from test/trace_processor/diff_tests/memory/android_mem_delta.py
rename to test/trace_processor/diff_tests/metrics/memory/android_mem_delta.py
diff --git a/test/trace_processor/diff_tests/memory/android_systrace_lmk.py b/test/trace_processor/diff_tests/metrics/memory/android_systrace_lmk.py
similarity index 100%
rename from test/trace_processor/diff_tests/memory/android_systrace_lmk.py
rename to test/trace_processor/diff_tests/metrics/memory/android_systrace_lmk.py
diff --git a/test/trace_processor/diff_tests/memory/tests.py b/test/trace_processor/diff_tests/metrics/memory/tests.py
similarity index 78%
rename from test/trace_processor/diff_tests/memory/tests.py
rename to test/trace_processor/diff_tests/metrics/memory/tests.py
index aca4edf..7ea5fda 100644
--- a/test/trace_processor/diff_tests/memory/tests.py
+++ b/test/trace_processor/diff_tests/metrics/memory/tests.py
@@ -19,7 +19,7 @@
 from python.generators.diff_tests.testing import TestSuite
 
 
-class Memory(TestSuite):
+class MemoryMetrics(TestSuite):
   # Contains test for Android memory metrics. ION metric
   def test_android_ion(self):
     return DiffTestBlueprint(
@@ -132,48 +132,6 @@
         }
         """))
 
-  def test_android_dma_buffer_tracks(self):
-    return DiffTestBlueprint(
-        trace=TextProto(r"""
-        packet {
-          ftrace_events {
-            cpu: 0
-            event {
-              timestamp: 100
-              pid: 1
-              dma_heap_stat {
-                inode: 123
-                len: 1024
-                total_allocated: 2048
-              }
-            }
-          }
-        }
-        packet {
-          ftrace_events {
-            cpu: 0
-            event {
-              timestamp: 200
-              pid: 1
-              dma_heap_stat {
-                inode: 123
-                len: -1024
-                total_allocated: 1024
-              }
-            }
-          }
-        }
-        """),
-        query="""
-        SELECT track.name, slice.ts, slice.dur, slice.name
-        FROM slice JOIN track ON slice.track_id = track.id
-        WHERE track.name = 'mem.dma_buffer';
-        """,
-        out=Csv("""
-        "name","ts","dur","name"
-        "mem.dma_buffer",100,100,"1 kB"
-        """))
-
   # fastrpc metric
   def test_android_fastrpc_dma_stat(self):
     return DiffTestBlueprint(
@@ -220,55 +178,94 @@
         }
         """))
 
-  # shrink slab
-  def test_shrink_slab(self):
+  def test_android_mem_counters(self):
+    return DiffTestBlueprint(
+        trace=DataPath('memory_counters.pb'),
+        query=Metric('android_mem'),
+        out=Path('android_mem_counters.out'))
+
+  def test_trace_metadata(self):
+    return DiffTestBlueprint(
+        trace=DataPath('memory_counters.pb'),
+        query=Metric('trace_metadata'),
+        out=Path('trace_metadata.out'))
+
+  def test_android_mem_by_priority(self):
+    return DiffTestBlueprint(
+        trace=Path('android_mem_by_priority.py'),
+        query=Metric('android_mem'),
+        out=Path('android_mem_by_priority.out'))
+
+  def test_android_mem_lmk(self):
+    return DiffTestBlueprint(
+        trace=Path('android_systrace_lmk.py'),
+        query=Metric('android_lmk'),
+        out=TextProto(r"""
+        android_lmk {
+          total_count: 1
+            by_oom_score {
+            oom_score_adj: 900
+            count: 1
+          }
+          oom_victim_count: 0
+        }
+        """))
+
+  def test_android_lmk_oom(self):
     return DiffTestBlueprint(
         trace=TextProto(r"""
         packet {
-          ftrace_events {
-            cpu: 7
-            event {
-              timestamp: 36448185787847
-              pid: 156
-              mm_shrink_slab_start {
-                cache_items: 1
-                delta: 0
-                gfp_flags: 3264
-                nr_objects_to_shrink: 0
-                shr: 18446743882920355600
-                shrink: 90
-                total_scan: 0
-                nid: 0
-                priority: 12
-              }
+          process_tree {
+            processes {
+              pid: 1000
+              ppid: 1
+              cmdline: "com.google.android.gm"
+            }
+            threads {
+              tid: 1001
+              tgid: 1000
             }
           }
         }
         packet {
           ftrace_events {
-            cpu: 7
+            cpu: 4
             event {
-              timestamp: 36448185788539
-              pid: 156
-              mm_shrink_slab_end {
-                new_scan: 0
-                retval: 0
-                shr: 18446743882920355600
-                shrink: 90
-                total_scan: 0
-                unused_scan: 0
-                nid: 0
+              timestamp: 1234
+              pid: 4321
+              mark_victim {
+                pid: 1001
               }
             }
           }
         }
         """),
-        query="""
-        SELECT ts, dur, name FROM slice WHERE name = 'mm_vmscan_shrink_slab';
-        """,
-        out=Csv("""
-        "ts","dur","name"
-        36448185787847,692,"mm_vmscan_shrink_slab"
+        query=Metric('android_lmk'),
+        out=TextProto(r"""
+        android_lmk {
+          total_count: 0
+          oom_victim_count: 1
+        }
+        """))
+
+  def test_android_mem_delta(self):
+    return DiffTestBlueprint(
+        trace=Path('android_mem_delta.py'),
+        query=Metric('android_mem'),
+        out=TextProto(r"""
+        android_mem {
+          process_metrics {
+            process_name: "com.my.pkg"
+            total_counters {
+              file_rss {
+                min: 2000.0
+                max: 10000.0
+                avg: 6666.666666666667
+                delta: 7000.0
+              }
+            }
+          }
+        }
         """))
 
   # cma alloc
@@ -321,3 +318,45 @@
         "ts","dur","name"
         74288080958099,110151652,"mm_cma_alloc"
         """))
+
+  def test_android_dma_buffer_tracks(self):
+    return DiffTestBlueprint(
+        trace=TextProto(r"""
+        packet {
+          ftrace_events {
+            cpu: 0
+            event {
+              timestamp: 100
+              pid: 1
+              dma_heap_stat {
+                inode: 123
+                len: 1024
+                total_allocated: 2048
+              }
+            }
+          }
+        }
+        packet {
+          ftrace_events {
+            cpu: 0
+            event {
+              timestamp: 200
+              pid: 1
+              dma_heap_stat {
+                inode: 123
+                len: -1024
+                total_allocated: 1024
+              }
+            }
+          }
+        }
+        """),
+        query="""
+        SELECT track.name, slice.ts, slice.dur, slice.name
+        FROM slice JOIN track ON slice.track_id = track.id
+        WHERE track.name = 'mem.dma_buffer';
+        """,
+        out=Csv("""
+        "name","ts","dur","name"
+        "mem.dma_buffer",100,100,"1 kB"
+        """))
diff --git a/test/trace_processor/diff_tests/memory/trace_metadata.out b/test/trace_processor/diff_tests/metrics/memory/trace_metadata.out
similarity index 100%
rename from test/trace_processor/diff_tests/memory/trace_metadata.out
rename to test/trace_processor/diff_tests/metrics/memory/trace_metadata.out
diff --git a/test/trace_processor/diff_tests/network/netperf_metric.out b/test/trace_processor/diff_tests/metrics/network/netperf_metric.out
similarity index 100%
rename from test/trace_processor/diff_tests/network/netperf_metric.out
rename to test/trace_processor/diff_tests/metrics/network/netperf_metric.out
diff --git a/test/trace_processor/diff_tests/network/netperf_metric.textproto b/test/trace_processor/diff_tests/metrics/network/netperf_metric.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/network/netperf_metric.textproto
rename to test/trace_processor/diff_tests/metrics/network/netperf_metric.textproto
diff --git a/test/trace_processor/diff_tests/metrics/network/tests.py b/test/trace_processor/diff_tests/metrics/network/tests.py
new file mode 100644
index 0000000..d59879f
--- /dev/null
+++ b/test/trace_processor/diff_tests/metrics/network/tests.py
@@ -0,0 +1,27 @@
+#!/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, Metric
+from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import TestSuite
+
+
+class NetworkMetrics(TestSuite):
+
+  def test_netperf_metric(self):
+    return DiffTestBlueprint(
+        trace=Path('netperf_metric.textproto'),
+        query=Metric('android_netperf'),
+        out=Path('netperf_metric.out'))
diff --git a/test/trace_processor/diff_tests/power/cpu_counters_p_state_test.out b/test/trace_processor/diff_tests/metrics/power/cpu_counters_p_state_test.out
similarity index 100%
rename from test/trace_processor/diff_tests/power/cpu_counters_p_state_test.out
rename to test/trace_processor/diff_tests/metrics/power/cpu_counters_p_state_test.out
diff --git a/test/trace_processor/diff_tests/power/tests.py b/test/trace_processor/diff_tests/metrics/power/tests.py
similarity index 75%
rename from test/trace_processor/diff_tests/power/tests.py
rename to test/trace_processor/diff_tests/metrics/power/tests.py
index b776eda..a1b7cdd 100644
--- a/test/trace_processor/diff_tests/power/tests.py
+++ b/test/trace_processor/diff_tests/metrics/power/tests.py
@@ -29,19 +29,4 @@
 
         SELECT * FROM P_STATE_OVER_INTERVAL(2579596465618, 2579606465618);
         """,
-        out=Path('cpu_counters_p_state_test.out'))
-
-  # CPU power ups
-  def test_cpu_powerups(self):
-    return DiffTestBlueprint(
-        trace=DataPath('cpu_powerups_1.pb'),
-        query="""
-        INCLUDE PERFETTO MODULE chrome.cpu_powerups;
-        SELECT * FROM chrome_cpu_power_first_toplevel_slice_after_powerup;
-        """,
-        out=Csv("""
-        "slice_id","previous_power_state"
-        424,2
-        703,2
-        708,2
-        """))
+        out=Path('cpu_counters_p_state_test.out'))
\ No newline at end of file
diff --git a/test/trace_processor/diff_tests/profiling/heap_graph.textproto b/test/trace_processor/diff_tests/metrics/profiling/heap_graph.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/profiling/heap_graph.textproto
rename to test/trace_processor/diff_tests/metrics/profiling/heap_graph.textproto
diff --git a/test/trace_processor/diff_tests/profiling/heap_graph_closest_proc.textproto b/test/trace_processor/diff_tests/metrics/profiling/heap_graph_closest_proc.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/profiling/heap_graph_closest_proc.textproto
rename to test/trace_processor/diff_tests/metrics/profiling/heap_graph_closest_proc.textproto
diff --git a/test/trace_processor/diff_tests/profiling/heap_profile_no_symbols.textproto b/test/trace_processor/diff_tests/metrics/profiling/heap_profile_no_symbols.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/profiling/heap_profile_no_symbols.textproto
rename to test/trace_processor/diff_tests/metrics/profiling/heap_profile_no_symbols.textproto
diff --git a/test/trace_processor/diff_tests/profiling/heap_stats_closest_proc.out b/test/trace_processor/diff_tests/metrics/profiling/heap_stats_closest_proc.out
similarity index 100%
rename from test/trace_processor/diff_tests/profiling/heap_stats_closest_proc.out
rename to test/trace_processor/diff_tests/metrics/profiling/heap_stats_closest_proc.out
diff --git a/test/trace_processor/diff_tests/profiling/java_heap_histogram.out b/test/trace_processor/diff_tests/metrics/profiling/java_heap_histogram.out
similarity index 100%
rename from test/trace_processor/diff_tests/profiling/java_heap_histogram.out
rename to test/trace_processor/diff_tests/metrics/profiling/java_heap_histogram.out
diff --git a/test/trace_processor/diff_tests/profiling/simpleperf_event.out b/test/trace_processor/diff_tests/metrics/profiling/simpleperf_event.out
similarity index 100%
rename from test/trace_processor/diff_tests/profiling/simpleperf_event.out
rename to test/trace_processor/diff_tests/metrics/profiling/simpleperf_event.out
diff --git a/test/trace_processor/diff_tests/profiling/simpleperf_event.py b/test/trace_processor/diff_tests/metrics/profiling/simpleperf_event.py
similarity index 100%
rename from test/trace_processor/diff_tests/profiling/simpleperf_event.py
rename to test/trace_processor/diff_tests/metrics/profiling/simpleperf_event.py
diff --git a/test/trace_processor/diff_tests/profiling/tests_metrics.py b/test/trace_processor/diff_tests/metrics/profiling/tests.py
similarity index 100%
rename from test/trace_processor/diff_tests/profiling/tests_metrics.py
rename to test/trace_processor/diff_tests/metrics/profiling/tests.py
diff --git a/test/trace_processor/diff_tests/startup/android_startup.out b/test/trace_processor/diff_tests/metrics/startup/android_startup.out
similarity index 100%
rename from test/trace_processor/diff_tests/startup/android_startup.out
rename to test/trace_processor/diff_tests/metrics/startup/android_startup.out
diff --git a/test/trace_processor/diff_tests/startup/android_startup.py b/test/trace_processor/diff_tests/metrics/startup/android_startup.py
similarity index 100%
rename from test/trace_processor/diff_tests/startup/android_startup.py
rename to test/trace_processor/diff_tests/metrics/startup/android_startup.py
diff --git a/test/trace_processor/diff_tests/startup/android_startup_attribution.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_attribution.out
similarity index 100%
rename from test/trace_processor/diff_tests/startup/android_startup_attribution.out
rename to test/trace_processor/diff_tests/metrics/startup/android_startup_attribution.out
diff --git a/test/trace_processor/diff_tests/startup/android_startup_attribution.py b/test/trace_processor/diff_tests/metrics/startup/android_startup_attribution.py
similarity index 98%
rename from test/trace_processor/diff_tests/startup/android_startup_attribution.py
rename to test/trace_processor/diff_tests/metrics/startup/android_startup_attribution.py
index 59301e0..a48edcc 100644
--- a/test/trace_processor/diff_tests/startup/android_startup_attribution.py
+++ b/test/trace_processor/diff_tests/metrics/startup/android_startup_attribution.py
@@ -78,7 +78,7 @@
 
 trace.add_atrace_begin(
     ts=170, pid=APP_PID, tid=APP_TID, buf='OpenDexFilesFromOat(something else)')
-trace.add_atrace_end(ts=5*10**8, pid=APP_PID, tid=APP_TID)
+trace.add_atrace_end(ts=5 * 10**8, pid=APP_PID, tid=APP_TID)
 
 # OpenDex slice outside the startup.
 trace.add_atrace_begin(
diff --git a/test/trace_processor/diff_tests/startup/android_startup_attribution_slow.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_attribution_slow.out
similarity index 100%
rename from test/trace_processor/diff_tests/startup/android_startup_attribution_slow.out
rename to test/trace_processor/diff_tests/metrics/startup/android_startup_attribution_slow.out
diff --git a/test/trace_processor/diff_tests/startup/android_startup_attribution_slow.py b/test/trace_processor/diff_tests/metrics/startup/android_startup_attribution_slow.py
similarity index 100%
rename from test/trace_processor/diff_tests/startup/android_startup_attribution_slow.py
rename to test/trace_processor/diff_tests/metrics/startup/android_startup_attribution_slow.py
diff --git a/test/trace_processor/diff_tests/startup/android_startup_battery.py b/test/trace_processor/diff_tests/metrics/startup/android_startup_battery.py
similarity index 100%
rename from test/trace_processor/diff_tests/startup/android_startup_battery.py
rename to test/trace_processor/diff_tests/metrics/startup/android_startup_battery.py
diff --git a/test/trace_processor/diff_tests/startup/android_startup_breakdown.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_breakdown.out
similarity index 100%
rename from test/trace_processor/diff_tests/startup/android_startup_breakdown.out
rename to test/trace_processor/diff_tests/metrics/startup/android_startup_breakdown.out
diff --git a/test/trace_processor/diff_tests/startup/android_startup_breakdown.py b/test/trace_processor/diff_tests/metrics/startup/android_startup_breakdown.py
similarity index 100%
rename from test/trace_processor/diff_tests/startup/android_startup_breakdown.py
rename to test/trace_processor/diff_tests/metrics/startup/android_startup_breakdown.py
diff --git a/test/trace_processor/diff_tests/startup/android_startup_breakdown_slow.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_breakdown_slow.out
similarity index 100%
rename from test/trace_processor/diff_tests/startup/android_startup_breakdown_slow.out
rename to test/trace_processor/diff_tests/metrics/startup/android_startup_breakdown_slow.out
diff --git a/test/trace_processor/diff_tests/startup/android_startup_breakdown_slow.py b/test/trace_processor/diff_tests/metrics/startup/android_startup_breakdown_slow.py
similarity index 97%
rename from test/trace_processor/diff_tests/startup/android_startup_breakdown_slow.py
rename to test/trace_processor/diff_tests/metrics/startup/android_startup_breakdown_slow.py
index f4ea962..b8f473f 100644
--- a/test/trace_processor/diff_tests/startup/android_startup_breakdown_slow.py
+++ b/test/trace_processor/diff_tests/metrics/startup/android_startup_breakdown_slow.py
@@ -29,7 +29,10 @@
 trace.add_process(3, 1, 'com.google.android.calendar', uid=10001)
 
 trace.add_package_list(
-    ts=to_s(100), name='com.google.android.calendar', uid=10001, version_code=123)
+    ts=to_s(100),
+    name='com.google.android.calendar',
+    uid=10001,
+    version_code=123)
 
 trace.add_ftrace_packet(cpu=0)
 
diff --git a/test/trace_processor/diff_tests/startup/android_startup_broadcast.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_broadcast.out
similarity index 100%
rename from test/trace_processor/diff_tests/startup/android_startup_broadcast.out
rename to test/trace_processor/diff_tests/metrics/startup/android_startup_broadcast.out
diff --git a/test/trace_processor/diff_tests/startup/android_startup_broadcast.py b/test/trace_processor/diff_tests/metrics/startup/android_startup_broadcast.py
similarity index 100%
rename from test/trace_processor/diff_tests/startup/android_startup_broadcast.py
rename to test/trace_processor/diff_tests/metrics/startup/android_startup_broadcast.py
diff --git a/test/trace_processor/diff_tests/startup/android_startup_broadcast_multiple.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_broadcast_multiple.out
similarity index 100%
rename from test/trace_processor/diff_tests/startup/android_startup_broadcast_multiple.out
rename to test/trace_processor/diff_tests/metrics/startup/android_startup_broadcast_multiple.out
diff --git a/test/trace_processor/diff_tests/startup/android_startup_broadcast_multiple.py b/test/trace_processor/diff_tests/metrics/startup/android_startup_broadcast_multiple.py
similarity index 100%
rename from test/trace_processor/diff_tests/startup/android_startup_broadcast_multiple.py
rename to test/trace_processor/diff_tests/metrics/startup/android_startup_broadcast_multiple.py
diff --git a/test/trace_processor/diff_tests/startup/android_startup_cpu.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_cpu.out
similarity index 100%
rename from test/trace_processor/diff_tests/startup/android_startup_cpu.out
rename to test/trace_processor/diff_tests/metrics/startup/android_startup_cpu.out
diff --git a/test/trace_processor/diff_tests/startup/android_startup_cpu.py b/test/trace_processor/diff_tests/metrics/startup/android_startup_cpu.py
similarity index 100%
rename from test/trace_processor/diff_tests/startup/android_startup_cpu.py
rename to test/trace_processor/diff_tests/metrics/startup/android_startup_cpu.py
diff --git a/test/trace_processor/diff_tests/startup/android_startup_installd_dex2oat.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_installd_dex2oat.out
similarity index 100%
rename from test/trace_processor/diff_tests/startup/android_startup_installd_dex2oat.out
rename to test/trace_processor/diff_tests/metrics/startup/android_startup_installd_dex2oat.out
diff --git a/test/trace_processor/diff_tests/startup/android_startup_installd_dex2oat.py b/test/trace_processor/diff_tests/metrics/startup/android_startup_installd_dex2oat.py
similarity index 100%
rename from test/trace_processor/diff_tests/startup/android_startup_installd_dex2oat.py
rename to test/trace_processor/diff_tests/metrics/startup/android_startup_installd_dex2oat.py
diff --git a/test/trace_processor/diff_tests/startup/android_startup_installd_dex2oat_slow.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_installd_dex2oat_slow.out
similarity index 100%
rename from test/trace_processor/diff_tests/startup/android_startup_installd_dex2oat_slow.out
rename to test/trace_processor/diff_tests/metrics/startup/android_startup_installd_dex2oat_slow.out
diff --git a/test/trace_processor/diff_tests/startup/android_startup_installd_dex2oat_slow.py b/test/trace_processor/diff_tests/metrics/startup/android_startup_installd_dex2oat_slow.py
similarity index 100%
rename from test/trace_processor/diff_tests/startup/android_startup_installd_dex2oat_slow.py
rename to test/trace_processor/diff_tests/metrics/startup/android_startup_installd_dex2oat_slow.py
diff --git a/test/trace_processor/diff_tests/startup/android_startup_lock_contention.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_lock_contention.out
similarity index 100%
rename from test/trace_processor/diff_tests/startup/android_startup_lock_contention.out
rename to test/trace_processor/diff_tests/metrics/startup/android_startup_lock_contention.out
diff --git a/test/trace_processor/diff_tests/startup/android_startup_lock_contention.py b/test/trace_processor/diff_tests/metrics/startup/android_startup_lock_contention.py
similarity index 100%
rename from test/trace_processor/diff_tests/startup/android_startup_lock_contention.py
rename to test/trace_processor/diff_tests/metrics/startup/android_startup_lock_contention.py
diff --git a/test/trace_processor/diff_tests/startup/android_startup_lock_contention_slow.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_lock_contention_slow.out
similarity index 100%
rename from test/trace_processor/diff_tests/startup/android_startup_lock_contention_slow.out
rename to test/trace_processor/diff_tests/metrics/startup/android_startup_lock_contention_slow.out
diff --git a/test/trace_processor/diff_tests/startup/android_startup_lock_contention_slow.py b/test/trace_processor/diff_tests/metrics/startup/android_startup_lock_contention_slow.py
similarity index 100%
rename from test/trace_processor/diff_tests/startup/android_startup_lock_contention_slow.py
rename to test/trace_processor/diff_tests/metrics/startup/android_startup_lock_contention_slow.py
diff --git a/test/trace_processor/diff_tests/startup/android_startup_minsdk33.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_minsdk33.out
similarity index 100%
rename from test/trace_processor/diff_tests/startup/android_startup_minsdk33.out
rename to test/trace_processor/diff_tests/metrics/startup/android_startup_minsdk33.out
diff --git a/test/trace_processor/diff_tests/startup/android_startup_minsdk33.py b/test/trace_processor/diff_tests/metrics/startup/android_startup_minsdk33.py
similarity index 100%
rename from test/trace_processor/diff_tests/startup/android_startup_minsdk33.py
rename to test/trace_processor/diff_tests/metrics/startup/android_startup_minsdk33.py
diff --git a/test/trace_processor/diff_tests/startup/android_startup_powrails.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_powrails.out
similarity index 100%
rename from test/trace_processor/diff_tests/startup/android_startup_powrails.out
rename to test/trace_processor/diff_tests/metrics/startup/android_startup_powrails.out
diff --git a/test/trace_processor/diff_tests/startup/android_startup_powrails.py b/test/trace_processor/diff_tests/metrics/startup/android_startup_powrails.py
similarity index 100%
rename from test/trace_processor/diff_tests/startup/android_startup_powrails.py
rename to test/trace_processor/diff_tests/metrics/startup/android_startup_powrails.py
diff --git a/test/trace_processor/diff_tests/startup/android_startup_process_track.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_process_track.out
similarity index 100%
rename from test/trace_processor/diff_tests/startup/android_startup_process_track.out
rename to test/trace_processor/diff_tests/metrics/startup/android_startup_process_track.out
diff --git a/test/trace_processor/diff_tests/startup/android_startup_process_track.py b/test/trace_processor/diff_tests/metrics/startup/android_startup_process_track.py
similarity index 100%
rename from test/trace_processor/diff_tests/startup/android_startup_process_track.py
rename to test/trace_processor/diff_tests/metrics/startup/android_startup_process_track.py
diff --git a/test/trace_processor/diff_tests/startup/android_startup_slow.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_slow.out
similarity index 100%
rename from test/trace_processor/diff_tests/startup/android_startup_slow.out
rename to test/trace_processor/diff_tests/metrics/startup/android_startup_slow.out
diff --git a/test/trace_processor/diff_tests/startup/android_startup_slow.py b/test/trace_processor/diff_tests/metrics/startup/android_startup_slow.py
similarity index 100%
rename from test/trace_processor/diff_tests/startup/android_startup_slow.py
rename to test/trace_processor/diff_tests/metrics/startup/android_startup_slow.py
diff --git a/test/trace_processor/diff_tests/startup/android_startup_unlock.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_unlock.out
similarity index 100%
rename from test/trace_processor/diff_tests/startup/android_startup_unlock.out
rename to test/trace_processor/diff_tests/metrics/startup/android_startup_unlock.out
diff --git a/test/trace_processor/diff_tests/startup/android_startup_unlock.py b/test/trace_processor/diff_tests/metrics/startup/android_startup_unlock.py
similarity index 100%
rename from test/trace_processor/diff_tests/startup/android_startup_unlock.py
rename to test/trace_processor/diff_tests/metrics/startup/android_startup_unlock.py
diff --git a/test/trace_processor/diff_tests/startup/tests.py b/test/trace_processor/diff_tests/metrics/startup/tests.py
similarity index 100%
rename from test/trace_processor/diff_tests/startup/tests.py
rename to test/trace_processor/diff_tests/metrics/startup/tests.py
diff --git a/test/trace_processor/diff_tests/startup/tests_broadcasts.py b/test/trace_processor/diff_tests/metrics/startup/tests_broadcasts.py
similarity index 100%
rename from test/trace_processor/diff_tests/startup/tests_broadcasts.py
rename to test/trace_processor/diff_tests/metrics/startup/tests_broadcasts.py
diff --git a/test/trace_processor/diff_tests/startup/tests_lock_contention.py b/test/trace_processor/diff_tests/metrics/startup/tests_lock_contention.py
similarity index 100%
rename from test/trace_processor/diff_tests/startup/tests_lock_contention.py
rename to test/trace_processor/diff_tests/metrics/startup/tests_lock_contention.py
diff --git a/test/trace_processor/diff_tests/startup/tests_metrics.py b/test/trace_processor/diff_tests/metrics/startup/tests_metrics.py
similarity index 100%
rename from test/trace_processor/diff_tests/startup/tests_metrics.py
rename to test/trace_processor/diff_tests/metrics/startup/tests_metrics.py
diff --git a/test/trace_processor/diff_tests/webview/tests.py b/test/trace_processor/diff_tests/metrics/webview/tests.py
similarity index 100%
rename from test/trace_processor/diff_tests/webview/tests.py
rename to test/trace_processor/diff_tests/metrics/webview/tests.py
diff --git a/test/trace_processor/diff_tests/android/android_bugreport_dumpstate_test.out b/test/trace_processor/diff_tests/parser/android/android_bugreport_dumpstate_test.out
similarity index 100%
rename from test/trace_processor/diff_tests/android/android_bugreport_dumpstate_test.out
rename to test/trace_processor/diff_tests/parser/android/android_bugreport_dumpstate_test.out
diff --git a/test/trace_processor/diff_tests/android/android_bugreport_dumpsys_test.out b/test/trace_processor/diff_tests/parser/android/android_bugreport_dumpsys_test.out
similarity index 100%
rename from test/trace_processor/diff_tests/android/android_bugreport_dumpsys_test.out
rename to test/trace_processor/diff_tests/parser/android/android_bugreport_dumpsys_test.out
diff --git a/test/trace_processor/diff_tests/android/android_bugreport_logs_test.out b/test/trace_processor/diff_tests/parser/android/android_bugreport_logs_test.out
similarity index 100%
rename from test/trace_processor/diff_tests/android/android_bugreport_logs_test.out
rename to test/trace_processor/diff_tests/parser/android/android_bugreport_logs_test.out
diff --git a/test/trace_processor/diff_tests/android/android_system_property_slice.out b/test/trace_processor/diff_tests/parser/android/android_system_property_slice.out
similarity index 100%
rename from test/trace_processor/diff_tests/android/android_system_property_slice.out
rename to test/trace_processor/diff_tests/parser/android/android_system_property_slice.out
diff --git a/test/trace_processor/diff_tests/android/game_intervention_list_test.out b/test/trace_processor/diff_tests/parser/android/game_intervention_list_test.out
similarity index 100%
rename from test/trace_processor/diff_tests/android/game_intervention_list_test.out
rename to test/trace_processor/diff_tests/parser/android/game_intervention_list_test.out
diff --git a/test/trace_processor/diff_tests/android/surfaceflinger_layers.textproto b/test/trace_processor/diff_tests/parser/android/surfaceflinger_layers.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/android/surfaceflinger_layers.textproto
rename to test/trace_processor/diff_tests/parser/android/surfaceflinger_layers.textproto
diff --git a/test/trace_processor/diff_tests/android/surfaceflinger_transactions.textproto b/test/trace_processor/diff_tests/parser/android/surfaceflinger_transactions.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/android/surfaceflinger_transactions.textproto
rename to test/trace_processor/diff_tests/parser/android/surfaceflinger_transactions.textproto
diff --git a/test/trace_processor/diff_tests/parser/android/tests.py b/test/trace_processor/diff_tests/parser/android/tests.py
new file mode 100644
index 0000000..9fca28e
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/android/tests.py
@@ -0,0 +1,205 @@
+#!/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, Systrace
+from python.generators.diff_tests.testing import Csv, Json, TextProto, BinaryProto
+from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import TestSuite
+from python.generators.diff_tests.testing import PrintProfileProto
+
+
+class AndroidParser(TestSuite):
+
+  def test_android_system_property_counter(self):
+    return DiffTestBlueprint(
+        trace=TextProto(r"""
+        packet {
+          timestamp: 1000
+          android_system_property {
+            values {
+              name: "debug.tracing.screen_state"
+              value: "2"
+            }
+            values {
+              name: "debug.tracing.device_state"
+              value: "some_state_from_sysprops"
+            }
+          }
+        }
+        packet {
+          ftrace_events {
+            cpu: 1
+            event {
+              timestamp: 2000
+              pid: 1
+              print {
+                buf: "C|1000|ScreenState|1\n"
+              }
+            }
+            event {
+              timestamp: 3000
+              pid: 1
+              print {
+                buf: "N|1000|DeviceStateChanged|some_state_from_atrace\n"
+              }
+            }
+          }
+        }
+        """),
+        query="""
+        SELECT t.type, t.name, c.id, c.ts, c.type, c.value
+        FROM counter_track t JOIN counter c ON t.id = c.track_id
+        WHERE name = 'ScreenState';
+        """,
+        out=Csv("""
+        "type","name","id","ts","type","value"
+        "counter_track","ScreenState",0,1000,"counter",2.000000
+        "counter_track","ScreenState",1,2000,"counter",1.000000
+        """))
+
+  def test_android_system_property_slice(self):
+    return DiffTestBlueprint(
+        trace=TextProto(r"""
+        packet {
+          timestamp: 1000
+          android_system_property {
+            values {
+              name: "debug.tracing.screen_state"
+              value: "2"
+            }
+            values {
+              name: "debug.tracing.device_state"
+              value: "some_state_from_sysprops"
+            }
+          }
+        }
+        packet {
+          ftrace_events {
+            cpu: 1
+            event {
+              timestamp: 2000
+              pid: 1
+              print {
+                buf: "C|1000|ScreenState|1\n"
+              }
+            }
+            event {
+              timestamp: 3000
+              pid: 1
+              print {
+                buf: "N|1000|DeviceStateChanged|some_state_from_atrace\n"
+              }
+            }
+          }
+        }
+        """),
+        query="""
+        SELECT t.type, t.name, s.id, s.ts, s.dur, s.type, s.name
+        FROM track t JOIN slice s ON s.track_id = t.id
+        WHERE t.name = 'DeviceStateChanged';
+        """,
+        out=Path('android_system_property_slice.out'))
+
+  def test_binder_txn_sync_good(self):
+    return DiffTestBlueprint(
+        trace=Systrace(
+            """          client-521390  [005] ..... 137012.464739: binder_command: cmd=0x40406300 BC_TRANSACTION
+          client-521390  [005] ..... 137012.464741: binder_transaction: transaction=5149 dest_node=5143 dest_proc=521383 dest_thread=0 reply=0 flags=0x0 code=0x3
+          server-521383  [004] ..... 137012.464771: binder_transaction_received: transaction=5149
+          server-521383  [004] ..... 137012.464772: binder_return: cmd=0x80407202 BR_TRANSACTION
+          server-521383  [004] ..... 137012.464815: binder_command: cmd=0x40086303 BC_FREE_BUFFER
+          server-521383  [004] ..... 137012.464823: binder_command: cmd=0x40406301 BC_REPLY
+          server-521383  [004] ..... 137012.464826: binder_transaction: transaction=5150 dest_node=0 dest_proc=521390 dest_thread=521390 reply=1 flags=0x20 code=0x3
+          server-521383  [004] ..... 137012.464837: binder_return: cmd=0x7206 BR_TRANSACTION_COMPLETE
+          client-521390  [005] ..... 137012.464847: binder_return: cmd=0x7206 BR_TRANSACTION_COMPLETE
+          client-521390  [005] ..... 137012.464848: binder_transaction_received: transaction=5150
+          client-521390  [005] ..... 137012.464849: binder_return: cmd=0x80407203 BR_REPLY
+          """),
+        query="""
+      SELECT
+        dur
+      FROM slice
+      ORDER BY dur;
+      """,
+        out=Csv("""
+      "dur"
+      55000
+      107000
+      """))
+
+  def test_binder_txn_sync_bad_request(self):
+    return DiffTestBlueprint(
+        trace=Systrace(
+            """          client-521349  [005] ..... 137004.281009: binder_command: cmd=0x40406300 BC_TRANSACTION
+          client-521349  [005] ..... 137004.281010: binder_transaction: transaction=5135 dest_node=5129 dest_proc=521347 dest_thread=0 reply=0 flags=0x0 code=0x3
+          client-521349  [005] ..... 137004.281410: binder_return: cmd=0x7211 BR_FAILED_REPLY
+          """),
+        query="""
+      SELECT
+        dur
+      FROM slice
+      ORDER BY dur;
+      """,
+        out=Csv("""
+      "dur"
+      400000
+      """))
+
+  def test_binder_txn_sync_bad_reply(self):
+    return DiffTestBlueprint(
+        trace=Systrace(
+            """          client-521332  [007] ..... 136996.112660: binder_command: cmd=0x40406300 BC_TRANSACTION
+          client-521332  [007] ..... 136996.112662: binder_transaction: transaction=5120 dest_node=5114 dest_proc=521330 dest_thread=0 reply=0 flags=0x0 code=0x3
+          server-521330  [000] ..... 136996.112714: binder_transaction_received: transaction=5120
+          server-521330  [000] ..... 136996.112715: binder_return: cmd=0x80407202 BR_TRANSACTION
+          server-521330  [000] ..... 136996.112752: binder_command: cmd=0x40086303 BC_FREE_BUFFER
+          server-521330  [000] ..... 136996.112758: binder_command: cmd=0x40406301 BC_REPLY
+          server-521330  [000] ..... 136996.112760: binder_transaction: transaction=5121 dest_node=0 dest_proc=521332 dest_thread=521332 reply=1 flags=0x20 code=0x3
+          server-521330  [000] ..... 136996.113163: binder_return: cmd=0x7206 BR_TRANSACTION_COMPLETE
+          client-521332  [007] ..... 136996.113201: binder_return: cmd=0x7206 BR_TRANSACTION_COMPLETE
+          client-521332  [007] ..... 136996.113201: binder_return: cmd=0x7211 BR_FAILED_REPLY
+          """),
+        query="""
+      SELECT
+        dur
+      FROM slice
+      ORDER BY dur;
+      """,
+        out=Csv("""
+      "dur"
+      46000
+      539000
+      """))
+
+  def test_binder_txn_oneway_good(self):
+    return DiffTestBlueprint(
+        trace=Systrace(
+            """          client-521406  [003] ..... 137020.679833: binder_command: cmd=0x40406300 BC_TRANSACTION
+          client-521406  [003] ..... 137020.679834: binder_transaction: transaction=5161 dest_node=5155 dest_proc=521404 dest_thread=0 reply=0 flags=0x1 code=0x3
+          client-521406  [003] ..... 137020.679843: binder_return: cmd=0x7206 BR_TRANSACTION_COMPLETE
+          server-521404  [006] ..... 137020.679890: binder_transaction_received: transaction=5161
+          server-521404  [006] ..... 137020.679890: binder_return: cmd=0x80407202 BR_TRANSACTION
+          """),
+        query="""
+      SELECT
+        dur
+      FROM slice
+      ORDER BY dur;
+      """,
+        out=Csv("""
+      "dur"
+      0
+      0
+      """))
diff --git a/test/trace_processor/diff_tests/android/tests_bugreport.py b/test/trace_processor/diff_tests/parser/android/tests_bugreport.py
similarity index 100%
rename from test/trace_processor/diff_tests/android/tests_bugreport.py
rename to test/trace_processor/diff_tests/parser/android/tests_bugreport.py
diff --git a/test/trace_processor/diff_tests/android/tests_games.py b/test/trace_processor/diff_tests/parser/android/tests_games.py
similarity index 100%
rename from test/trace_processor/diff_tests/android/tests_games.py
rename to test/trace_processor/diff_tests/parser/android/tests_games.py
diff --git a/test/trace_processor/diff_tests/android/tests_surfaceflinger_layers.py b/test/trace_processor/diff_tests/parser/android/tests_surfaceflinger_layers.py
similarity index 99%
rename from test/trace_processor/diff_tests/android/tests_surfaceflinger_layers.py
rename to test/trace_processor/diff_tests/parser/android/tests_surfaceflinger_layers.py
index 1ccf102..ac1fcab 100644
--- a/test/trace_processor/diff_tests/android/tests_surfaceflinger_layers.py
+++ b/test/trace_processor/diff_tests/parser/android/tests_surfaceflinger_layers.py
@@ -20,6 +20,7 @@
 
 
 class SurfaceFlingerLayers(TestSuite):
+
   def test_snapshot_table(self):
     return DiffTestBlueprint(
         trace=Path('surfaceflinger_layers.textproto'),
@@ -80,4 +81,3 @@
         2,1,"surfaceflinger_layer"
         3,1,"surfaceflinger_layer"
         """))
-
diff --git a/test/trace_processor/diff_tests/android/tests_surfaceflinger_transactions.py b/test/trace_processor/diff_tests/parser/android/tests_surfaceflinger_transactions.py
similarity index 99%
rename from test/trace_processor/diff_tests/android/tests_surfaceflinger_transactions.py
rename to test/trace_processor/diff_tests/parser/android/tests_surfaceflinger_transactions.py
index 1c13e67..cf83ce4 100644
--- a/test/trace_processor/diff_tests/android/tests_surfaceflinger_transactions.py
+++ b/test/trace_processor/diff_tests/parser/android/tests_surfaceflinger_transactions.py
@@ -20,6 +20,7 @@
 
 
 class SurfaceFlingerTransactions(TestSuite):
+
   def test_has_expected_transactions_rows(self):
     return DiffTestBlueprint(
         trace=Path('surfaceflinger_transactions.textproto'),
diff --git a/test/trace_processor/diff_tests/android_fs/tests.py b/test/trace_processor/diff_tests/parser/android_fs/tests.py
similarity index 100%
rename from test/trace_processor/diff_tests/android_fs/tests.py
rename to test/trace_processor/diff_tests/parser/android_fs/tests.py
diff --git a/test/trace_processor/diff_tests/atrace/android_b2b_async_begin.textproto b/test/trace_processor/diff_tests/parser/atrace/android_b2b_async_begin.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/atrace/android_b2b_async_begin.textproto
rename to test/trace_processor/diff_tests/parser/atrace/android_b2b_async_begin.textproto
diff --git a/test/trace_processor/diff_tests/atrace/async_track_atrace.py b/test/trace_processor/diff_tests/parser/atrace/async_track_atrace.py
similarity index 100%
rename from test/trace_processor/diff_tests/atrace/async_track_atrace.py
rename to test/trace_processor/diff_tests/parser/atrace/async_track_atrace.py
diff --git a/test/trace_processor/diff_tests/atrace/bad_print.systrace b/test/trace_processor/diff_tests/parser/atrace/bad_print.systrace
similarity index 100%
rename from test/trace_processor/diff_tests/atrace/bad_print.systrace
rename to test/trace_processor/diff_tests/parser/atrace/bad_print.systrace
diff --git a/test/trace_processor/diff_tests/atrace/bad_print.textproto b/test/trace_processor/diff_tests/parser/atrace/bad_print.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/atrace/bad_print.textproto
rename to test/trace_processor/diff_tests/parser/atrace/bad_print.textproto
diff --git a/test/trace_processor/diff_tests/atrace/instant_async_atrace.py b/test/trace_processor/diff_tests/parser/atrace/instant_async_atrace.py
similarity index 100%
rename from test/trace_processor/diff_tests/atrace/instant_async_atrace.py
rename to test/trace_processor/diff_tests/parser/atrace/instant_async_atrace.py
diff --git a/test/trace_processor/diff_tests/atrace/instant_atrace.py b/test/trace_processor/diff_tests/parser/atrace/instant_atrace.py
similarity index 100%
rename from test/trace_processor/diff_tests/atrace/instant_atrace.py
rename to test/trace_processor/diff_tests/parser/atrace/instant_atrace.py
diff --git a/test/trace_processor/diff_tests/atrace/process_track_slices_android_async_slice.out b/test/trace_processor/diff_tests/parser/atrace/process_track_slices_android_async_slice.out
similarity index 100%
rename from test/trace_processor/diff_tests/atrace/process_track_slices_android_async_slice.out
rename to test/trace_processor/diff_tests/parser/atrace/process_track_slices_android_async_slice.out
diff --git a/test/trace_processor/diff_tests/atrace/sys_write_and_atrace.py b/test/trace_processor/diff_tests/parser/atrace/sys_write_and_atrace.py
similarity index 100%
rename from test/trace_processor/diff_tests/atrace/sys_write_and_atrace.py
rename to test/trace_processor/diff_tests/parser/atrace/sys_write_and_atrace.py
diff --git a/test/trace_processor/diff_tests/atrace/tests.py b/test/trace_processor/diff_tests/parser/atrace/tests.py
similarity index 100%
rename from test/trace_processor/diff_tests/atrace/tests.py
rename to test/trace_processor/diff_tests/parser/atrace/tests.py
diff --git a/test/trace_processor/diff_tests/atrace/tests_error_handling.py b/test/trace_processor/diff_tests/parser/atrace/tests_error_handling.py
similarity index 100%
rename from test/trace_processor/diff_tests/atrace/tests_error_handling.py
rename to test/trace_processor/diff_tests/parser/atrace/tests_error_handling.py
diff --git a/test/trace_processor/diff_tests/chrome/chrome_log_message_args_test.sql b/test/trace_processor/diff_tests/parser/chrome/chrome_log_message_args_test.sql
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/chrome_log_message_args_test.sql
rename to test/trace_processor/diff_tests/parser/chrome/chrome_log_message_args_test.sql
diff --git a/test/trace_processor/diff_tests/chrome/memory_snapshot_chrome_dump_events.out b/test/trace_processor/diff_tests/parser/chrome/memory_snapshot_chrome_dump_events.out
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/memory_snapshot_chrome_dump_events.out
rename to test/trace_processor/diff_tests/parser/chrome/memory_snapshot_chrome_dump_events.out
diff --git a/test/trace_processor/diff_tests/chrome/memory_snapshot_edges.out b/test/trace_processor/diff_tests/parser/chrome/memory_snapshot_edges.out
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/memory_snapshot_edges.out
rename to test/trace_processor/diff_tests/parser/chrome/memory_snapshot_edges.out
diff --git a/test/trace_processor/diff_tests/chrome/memory_snapshot_general_validation.out b/test/trace_processor/diff_tests/parser/chrome/memory_snapshot_general_validation.out
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/memory_snapshot_general_validation.out
rename to test/trace_processor/diff_tests/parser/chrome/memory_snapshot_general_validation.out
diff --git a/test/trace_processor/diff_tests/chrome/memory_snapshot_node_args.out b/test/trace_processor/diff_tests/parser/chrome/memory_snapshot_node_args.out
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/memory_snapshot_node_args.out
rename to test/trace_processor/diff_tests/parser/chrome/memory_snapshot_node_args.out
diff --git a/test/trace_processor/diff_tests/chrome/memory_snapshot_nodes.out b/test/trace_processor/diff_tests/parser/chrome/memory_snapshot_nodes.out
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/memory_snapshot_nodes.out
rename to test/trace_processor/diff_tests/parser/chrome/memory_snapshot_nodes.out
diff --git a/test/trace_processor/diff_tests/chrome/memory_snapshot_os_dump_events.out b/test/trace_processor/diff_tests/parser/chrome/memory_snapshot_os_dump_events.out
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/memory_snapshot_os_dump_events.out
rename to test/trace_processor/diff_tests/parser/chrome/memory_snapshot_os_dump_events.out
diff --git a/test/trace_processor/diff_tests/chrome/memory_snapshot_os_dump_events_test.sql b/test/trace_processor/diff_tests/parser/chrome/memory_snapshot_os_dump_events_test.sql
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/memory_snapshot_os_dump_events_test.sql
rename to test/trace_processor/diff_tests/parser/chrome/memory_snapshot_os_dump_events_test.sql
diff --git a/test/trace_processor/diff_tests/chrome/memory_snapshot_smaps.out b/test/trace_processor/diff_tests/parser/chrome/memory_snapshot_smaps.out
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/memory_snapshot_smaps.out
rename to test/trace_processor/diff_tests/parser/chrome/memory_snapshot_smaps.out
diff --git a/test/trace_processor/diff_tests/parser/chrome/tests.py b/test/trace_processor/diff_tests/parser/chrome/tests.py
new file mode 100644
index 0000000..6fe4547
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/chrome/tests.py
@@ -0,0 +1,188 @@
+#!/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 ChromeParser(TestSuite):
+  # Log messages.
+  def test_chrome_log_message(self):
+    return DiffTestBlueprint(
+        trace=TextProto(r"""
+        packet {
+          timestamp: 0
+          incremental_state_cleared: true
+          trusted_packet_sequence_id: 1
+          track_descriptor {
+            uuid: 12345
+            thread {
+              pid: 123
+              tid: 345
+            }
+            parent_uuid: 0
+            chrome_thread {
+              thread_type: THREAD_POOL_FG_WORKER
+            }
+          }
+        }
+
+        packet {
+          trusted_packet_sequence_id: 1
+          timestamp: 10
+          track_event {
+            track_uuid: 12345
+            categories: "cat1"
+            type: TYPE_INSTANT
+            name: "slice1"
+            log_message {
+                body_iid: 1
+                source_location_iid: 3
+            }
+          }
+          interned_data {
+            log_message_body {
+                iid: 1
+                body: "log message"
+            }
+            source_locations {
+                iid: 3
+                function_name: "func"
+                file_name: "foo.cc"
+                line_number: 123
+            }
+          }
+        }
+        """),
+        query="""
+        SELECT utid, tag, msg, prio FROM android_logs;
+        """,
+        # If the log_message_body doesn't have any priority, a default 4 (i.e.
+        # INFO) is assumed (otherwise the UI will not show the message).
+        out=Csv("""
+        "utid","tag","msg","prio"
+        1,"foo.cc:123","log message",4
+        """))
+
+  def test_chrome_log_message_priority(self):
+    return DiffTestBlueprint(
+        trace=TextProto(r"""
+        packet {
+          timestamp: 0
+          incremental_state_cleared: true
+          trusted_packet_sequence_id: 1
+          track_descriptor {
+            uuid: 12345
+            thread {
+              pid: 123
+              tid: 345
+            }
+            parent_uuid: 0
+            chrome_thread {
+              thread_type: THREAD_POOL_FG_WORKER
+            }
+          }
+        }
+
+        packet {
+          trusted_packet_sequence_id: 1
+          timestamp: 10
+          track_event {
+            track_uuid: 12345
+            categories: "cat1"
+            type: TYPE_INSTANT
+            name: "slice1"
+            log_message {
+                body_iid: 1
+                source_location_iid: 3
+                prio: PRIO_WARN
+            }
+          }
+          interned_data {
+            log_message_body {
+                iid: 1
+                body: "log message"
+            }
+            source_locations {
+                iid: 3
+                function_name: "func"
+                file_name: "foo.cc"
+                line_number: 123
+            }
+          }
+        }
+        """),
+        query="""
+        SELECT utid, tag, msg, prio FROM android_logs;
+        """,
+        out=Csv("""
+        "utid","tag","msg","prio"
+        1,"foo.cc:123","log message",5
+        """))
+
+  def test_chrome_log_message_args(self):
+    return DiffTestBlueprint(
+        trace=TextProto(r"""
+        packet {
+          timestamp: 0
+          incremental_state_cleared: true
+          trusted_packet_sequence_id: 1
+          track_descriptor {
+            uuid: 12345
+            thread {
+              pid: 123
+              tid: 345
+            }
+            parent_uuid: 0
+            chrome_thread {
+              thread_type: THREAD_POOL_FG_WORKER
+            }
+          }
+        }
+
+        packet {
+          trusted_packet_sequence_id: 1
+          timestamp: 10
+          track_event {
+            track_uuid: 12345
+            categories: "cat1"
+            type: TYPE_INSTANT
+            name: "slice1"
+            log_message {
+                body_iid: 1
+                source_location_iid: 3
+            }
+          }
+          interned_data {
+            log_message_body {
+                iid: 1
+                body: "log message"
+            }
+            source_locations {
+                iid: 3
+                function_name: "func"
+                file_name: "foo.cc"
+                line_number: 123
+            }
+          }
+        }
+        """),
+        query=Path('chrome_log_message_args_test.sql'),
+        out=Csv("""
+        "log_message","function_name","file_name","line_number"
+        "log message","func","foo.cc",123
+        """))
\ No newline at end of file
diff --git a/test/trace_processor/diff_tests/chrome/tests_memory_snapshots.py b/test/trace_processor/diff_tests/parser/chrome/tests_memory_snapshots.py
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/tests_memory_snapshots.py
rename to test/trace_processor/diff_tests/parser/chrome/tests_memory_snapshots.py
diff --git a/test/trace_processor/diff_tests/cros/cros_ec_sensorhub_data.out b/test/trace_processor/diff_tests/parser/cros/cros_ec_sensorhub_data.out
similarity index 100%
rename from test/trace_processor/diff_tests/cros/cros_ec_sensorhub_data.out
rename to test/trace_processor/diff_tests/parser/cros/cros_ec_sensorhub_data.out
diff --git a/test/trace_processor/diff_tests/cros/tests.py b/test/trace_processor/diff_tests/parser/cros/tests.py
similarity index 100%
rename from test/trace_processor/diff_tests/cros/tests.py
rename to test/trace_processor/diff_tests/parser/cros/tests.py
diff --git a/test/trace_processor/diff_tests/fs/f2fs_iostat.out b/test/trace_processor/diff_tests/parser/fs/f2fs_iostat.out
similarity index 100%
rename from test/trace_processor/diff_tests/fs/f2fs_iostat.out
rename to test/trace_processor/diff_tests/parser/fs/f2fs_iostat.out
diff --git a/test/trace_processor/diff_tests/fs/f2fs_iostat.textproto b/test/trace_processor/diff_tests/parser/fs/f2fs_iostat.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/fs/f2fs_iostat.textproto
rename to test/trace_processor/diff_tests/parser/fs/f2fs_iostat.textproto
diff --git a/test/trace_processor/diff_tests/fs/f2fs_iostat_latency.out b/test/trace_processor/diff_tests/parser/fs/f2fs_iostat_latency.out
similarity index 100%
rename from test/trace_processor/diff_tests/fs/f2fs_iostat_latency.out
rename to test/trace_processor/diff_tests/parser/fs/f2fs_iostat_latency.out
diff --git a/test/trace_processor/diff_tests/fs/f2fs_iostat_latency.textproto b/test/trace_processor/diff_tests/parser/fs/f2fs_iostat_latency.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/fs/f2fs_iostat_latency.textproto
rename to test/trace_processor/diff_tests/parser/fs/f2fs_iostat_latency.textproto
diff --git a/test/trace_processor/diff_tests/fs/tests.py b/test/trace_processor/diff_tests/parser/fs/tests.py
similarity index 100%
rename from test/trace_processor/diff_tests/fs/tests.py
rename to test/trace_processor/diff_tests/parser/fs/tests.py
diff --git a/test/trace_processor/diff_tests/fuchsia/fuchsia_workstation_smoke_slices.out b/test/trace_processor/diff_tests/parser/fuchsia/fuchsia_workstation_smoke_slices.out
similarity index 100%
rename from test/trace_processor/diff_tests/fuchsia/fuchsia_workstation_smoke_slices.out
rename to test/trace_processor/diff_tests/parser/fuchsia/fuchsia_workstation_smoke_slices.out
diff --git a/test/trace_processor/diff_tests/fuchsia/tests.py b/test/trace_processor/diff_tests/parser/fuchsia/tests.py
similarity index 100%
rename from test/trace_processor/diff_tests/fuchsia/tests.py
rename to test/trace_processor/diff_tests/parser/fuchsia/tests.py
diff --git a/test/trace_processor/diff_tests/graphics/actual_frame_timeline_events.out b/test/trace_processor/diff_tests/parser/graphics/actual_frame_timeline_events.out
similarity index 100%
rename from test/trace_processor/diff_tests/graphics/actual_frame_timeline_events.out
rename to test/trace_processor/diff_tests/parser/graphics/actual_frame_timeline_events.out
diff --git a/test/trace_processor/diff_tests/graphics/actual_frame_timeline_events_test.sql b/test/trace_processor/diff_tests/parser/graphics/actual_frame_timeline_events_test.sql
similarity index 100%
rename from test/trace_processor/diff_tests/graphics/actual_frame_timeline_events_test.sql
rename to test/trace_processor/diff_tests/parser/graphics/actual_frame_timeline_events_test.sql
diff --git a/test/trace_processor/diff_tests/graphics/clock_sync.py b/test/trace_processor/diff_tests/parser/graphics/clock_sync.py
similarity index 100%
rename from test/trace_processor/diff_tests/graphics/clock_sync.py
rename to test/trace_processor/diff_tests/parser/graphics/clock_sync.py
diff --git a/test/trace_processor/diff_tests/graphics/drm_dma_fence.textproto b/test/trace_processor/diff_tests/parser/graphics/drm_dma_fence.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/graphics/drm_dma_fence.textproto
rename to test/trace_processor/diff_tests/parser/graphics/drm_dma_fence.textproto
diff --git a/test/trace_processor/diff_tests/graphics/drm_sched.textproto b/test/trace_processor/diff_tests/parser/graphics/drm_sched.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/graphics/drm_sched.textproto
rename to test/trace_processor/diff_tests/parser/graphics/drm_sched.textproto
diff --git a/test/trace_processor/diff_tests/graphics/expected_frame_timeline_events_test.sql b/test/trace_processor/diff_tests/parser/graphics/expected_frame_timeline_events_test.sql
similarity index 100%
rename from test/trace_processor/diff_tests/graphics/expected_frame_timeline_events_test.sql
rename to test/trace_processor/diff_tests/parser/graphics/expected_frame_timeline_events_test.sql
diff --git a/test/trace_processor/diff_tests/graphics/frame_timeline_events.py b/test/trace_processor/diff_tests/parser/graphics/frame_timeline_events.py
similarity index 100%
rename from test/trace_processor/diff_tests/graphics/frame_timeline_events.py
rename to test/trace_processor/diff_tests/parser/graphics/frame_timeline_events.py
diff --git a/test/trace_processor/diff_tests/graphics/gpu_counter_specs.textproto b/test/trace_processor/diff_tests/parser/graphics/gpu_counter_specs.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/graphics/gpu_counter_specs.textproto
rename to test/trace_processor/diff_tests/parser/graphics/gpu_counter_specs.textproto
diff --git a/test/trace_processor/diff_tests/graphics/gpu_counters.py b/test/trace_processor/diff_tests/parser/graphics/gpu_counters.py
similarity index 100%
rename from test/trace_processor/diff_tests/graphics/gpu_counters.py
rename to test/trace_processor/diff_tests/parser/graphics/gpu_counters.py
diff --git a/test/trace_processor/diff_tests/graphics/gpu_log.py b/test/trace_processor/diff_tests/parser/graphics/gpu_log.py
similarity index 100%
rename from test/trace_processor/diff_tests/graphics/gpu_log.py
rename to test/trace_processor/diff_tests/parser/graphics/gpu_log.py
diff --git a/test/trace_processor/diff_tests/graphics/gpu_mem_total.py b/test/trace_processor/diff_tests/parser/graphics/gpu_mem_total.py
similarity index 100%
rename from test/trace_processor/diff_tests/graphics/gpu_mem_total.py
rename to test/trace_processor/diff_tests/parser/graphics/gpu_mem_total.py
diff --git a/test/trace_processor/diff_tests/graphics/gpu_mem_total_after_free.py b/test/trace_processor/diff_tests/parser/graphics/gpu_mem_total_after_free.py
similarity index 100%
rename from test/trace_processor/diff_tests/graphics/gpu_mem_total_after_free.py
rename to test/trace_processor/diff_tests/parser/graphics/gpu_mem_total_after_free.py
diff --git a/test/trace_processor/diff_tests/graphics/gpu_mem_total_test.sql b/test/trace_processor/diff_tests/parser/graphics/gpu_mem_total_test.sql
similarity index 100%
rename from test/trace_processor/diff_tests/graphics/gpu_mem_total_test.sql
rename to test/trace_processor/diff_tests/parser/graphics/gpu_mem_total_test.sql
diff --git a/test/trace_processor/diff_tests/graphics/gpu_render_stages.out b/test/trace_processor/diff_tests/parser/graphics/gpu_render_stages.out
similarity index 100%
rename from test/trace_processor/diff_tests/graphics/gpu_render_stages.out
rename to test/trace_processor/diff_tests/parser/graphics/gpu_render_stages.out
diff --git a/test/trace_processor/diff_tests/graphics/gpu_render_stages.py b/test/trace_processor/diff_tests/parser/graphics/gpu_render_stages.py
similarity index 100%
rename from test/trace_processor/diff_tests/graphics/gpu_render_stages.py
rename to test/trace_processor/diff_tests/parser/graphics/gpu_render_stages.py
diff --git a/test/trace_processor/diff_tests/graphics/gpu_render_stages_interned_spec.out b/test/trace_processor/diff_tests/parser/graphics/gpu_render_stages_interned_spec.out
similarity index 100%
rename from test/trace_processor/diff_tests/graphics/gpu_render_stages_interned_spec.out
rename to test/trace_processor/diff_tests/parser/graphics/gpu_render_stages_interned_spec.out
diff --git a/test/trace_processor/diff_tests/graphics/gpu_render_stages_interned_spec.textproto b/test/trace_processor/diff_tests/parser/graphics/gpu_render_stages_interned_spec.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/graphics/gpu_render_stages_interned_spec.textproto
rename to test/trace_processor/diff_tests/parser/graphics/gpu_render_stages_interned_spec.textproto
diff --git a/test/trace_processor/diff_tests/graphics/gpu_render_stages_test.sql b/test/trace_processor/diff_tests/parser/graphics/gpu_render_stages_test.sql
similarity index 100%
rename from test/trace_processor/diff_tests/graphics/gpu_render_stages_test.sql
rename to test/trace_processor/diff_tests/parser/graphics/gpu_render_stages_test.sql
diff --git a/test/trace_processor/diff_tests/graphics/graphics_frame_events.out b/test/trace_processor/diff_tests/parser/graphics/graphics_frame_events.out
similarity index 100%
rename from test/trace_processor/diff_tests/graphics/graphics_frame_events.out
rename to test/trace_processor/diff_tests/parser/graphics/graphics_frame_events.out
diff --git a/test/trace_processor/diff_tests/graphics/graphics_frame_events.py b/test/trace_processor/diff_tests/parser/graphics/graphics_frame_events.py
similarity index 100%
rename from test/trace_processor/diff_tests/graphics/graphics_frame_events.py
rename to test/trace_processor/diff_tests/parser/graphics/graphics_frame_events.py
diff --git a/test/trace_processor/diff_tests/parser/graphics/tests.py b/test/trace_processor/diff_tests/parser/graphics/tests.py
new file mode 100644
index 0000000..563ddbe
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/graphics/tests.py
@@ -0,0 +1,280 @@
+#!/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 GraphicsParser(TestSuite):
+  # Contains tests for graphics related events and tables. Graphics frame
+  # trace tests.
+  def test_graphics_frame_events(self):
+    return DiffTestBlueprint(
+        trace=Path('graphics_frame_events.py'),
+        query="""
+        SELECT ts, gpu_track.name AS track_name, dur, frame_slice.name AS slice_name,
+          frame_number, layer_name
+        FROM gpu_track
+        LEFT JOIN frame_slice ON gpu_track.id = frame_slice.track_id
+        WHERE scope = 'graphics_frame_event'
+        ORDER BY ts;
+        """,
+        out=Path('graphics_frame_events.out'))
+
+  # GPU Memory ftrace packets
+  def test_gpu_mem_total(self):
+    return DiffTestBlueprint(
+        trace=Path('gpu_mem_total.py'),
+        query=Path('gpu_mem_total_test.sql'),
+        out=Csv("""
+        "name","unit","description","ts","pid","value"
+        "GPU Memory","7","Total GPU memory used by the entire system",0,"[NULL]",123
+        "GPU Memory","7","Total GPU memory used by this process",0,1,100
+        "GPU Memory","7","Total GPU memory used by the entire system",5,"[NULL]",256
+        "GPU Memory","7","Total GPU memory used by this process",5,1,233
+        "GPU Memory","7","Total GPU memory used by the entire system",10,"[NULL]",123
+        "GPU Memory","7","Total GPU memory used by this process",10,1,0
+        """))
+
+  def test_gpu_mem_total_after_free_gpu_mem_total(self):
+    return DiffTestBlueprint(
+        trace=Path('gpu_mem_total_after_free.py'),
+        query=Path('gpu_mem_total_test.sql'),
+        out=Csv("""
+        "name","unit","description","ts","pid","value"
+        "GPU Memory","7","Total GPU memory used by this process",0,1,100
+        "GPU Memory","7","Total GPU memory used by this process",5,1,233
+        "GPU Memory","7","Total GPU memory used by this process",10,1,50
+        """))
+
+  # Clock sync
+  def test_clock_sync(self):
+    return DiffTestBlueprint(
+        trace=Path('clock_sync.py'),
+        query="""
+        SELECT ts, cast(value AS integer) AS int_value
+        FROM counters
+        WHERE name GLOB 'gpu_counter*';
+        """,
+        out=Csv("""
+        "ts","int_value"
+        1,3
+        102,5
+        1003,7
+        1005,9
+        2006,11
+        2010,12
+        2013,13
+        3007,14
+        3010,15
+        """))
+
+  # Frame Timeline event trace tests
+  def test_expected_frame_timeline_events(self):
+    return DiffTestBlueprint(
+        trace=Path('frame_timeline_events.py'),
+        query=Path('expected_frame_timeline_events_test.sql'),
+        out=Csv("""
+        "ts","dur","pid","display_frame_token","surface_frame_token","layer_name"
+        20,6,666,2,0,"[NULL]"
+        21,15,1000,4,1,"Layer1"
+        40,6,666,4,0,"[NULL]"
+        41,15,1000,6,5,"Layer1"
+        80,6,666,6,0,"[NULL]"
+        90,16,1000,8,7,"Layer1"
+        120,6,666,8,0,"[NULL]"
+        140,6,666,12,0,"[NULL]"
+        150,20,1000,15,14,"Layer1"
+        170,6,666,15,0,"[NULL]"
+        200,6,666,17,0,"[NULL]"
+        220,10,666,18,0,"[NULL]"
+        """))
+
+  def test_actual_frame_timeline_events(self):
+    return DiffTestBlueprint(
+        trace=Path('frame_timeline_events.py'),
+        query=Path('actual_frame_timeline_events_test.sql'),
+        out=Path('actual_frame_timeline_events.out'))
+
+  # Video 4 Linux 2 related tests
+  def test_v4l2_vidioc_slice(self):
+    return DiffTestBlueprint(
+        trace=Path('v4l2_vidioc.textproto'),
+        query="""
+        SELECT ts, dur, name
+        FROM slice
+        WHERE category = 'Video 4 Linux 2';
+        """,
+        out=Csv("""
+        "ts","dur","name"
+        593268475912,0,"VIDIOC_QBUF minor=0 seq=0 type=9 index=19"
+        593268603800,0,"VIDIOC_QBUF minor=0 seq=0 type=9 index=20"
+        593528238295,0,"VIDIOC_DQBUF minor=0 seq=0 type=9 index=19"
+        593544028229,0,"VIDIOC_DQBUF minor=0 seq=0 type=9 index=20"
+        """))
+
+  def test_v4l2_vidioc_flow(self):
+    return DiffTestBlueprint(
+        trace=Path('v4l2_vidioc.textproto'),
+        query="""
+        SELECT qbuf.ts, qbuf.dur, qbuf.name, dqbuf.ts, dqbuf.dur, dqbuf.name
+        FROM flow
+        JOIN slice qbuf ON flow.slice_out = qbuf.id
+        JOIN slice dqbuf ON flow.slice_in = dqbuf.id;
+        """,
+        out=Path('v4l2_vidioc_flow.out'))
+
+  def test_virtio_video_slice(self):
+    return DiffTestBlueprint(
+        trace=Path('virtio_video.textproto'),
+        query="""
+        SELECT slice.ts, slice.dur, slice.name, track.name
+        FROM slice
+        JOIN track ON slice.track_id = track.id;
+        """,
+        out=Csv("""
+        "ts","dur","name","name"
+        593125003271,84500592,"Resource #102","virtio_video stream #4 OUTPUT"
+        593125003785,100000,"RESOURCE_QUEUE","virtio_video stream #4 Requests"
+        593125084611,709696,"Resource #62","virtio_video stream #3 OUTPUT"
+        593125084935,100000,"RESOURCE_QUEUE","virtio_video stream #3 Requests"
+        593125794194,100000,"RESOURCE_QUEUE","virtio_video stream #3 Responses"
+        593209502603,100000,"RESOURCE_QUEUE","virtio_video stream #4 Responses"
+        """))
+
+  # virtgpu (drm/virtio) related tests
+  def test_virtio_gpu(self):
+    return DiffTestBlueprint(
+        trace=Path('virtio_gpu.textproto'),
+        query="""
+        SELECT
+          ts,
+          dur,
+          name
+        FROM
+          slice
+        ORDER BY ts;
+        """,
+        out=Csv("""
+        "ts","dur","name"
+        1345090723759,1180312,"SUBMIT_3D"
+        1345090746311,1167135,"CTX_DETACH_RESOURCE"
+        """))
+
+  # TODO(b/294866695): Reenable
+  # mali GPU events
+  #def test_mali(self):
+  #  return DiffTestBlueprint(
+  #      trace=TextProto(r"""
+  #      packet {
+  #        ftrace_events {
+  #          cpu: 2
+  #          event {
+  #            timestamp: 751796307210
+  #            pid: 2857
+  #            mali_mali_KCPU_CQS_WAIT_START {
+  #              info_val1: 1
+  #              info_val2: 0
+  #              kctx_tgid: 2201
+  #              kctx_id: 10
+  #              id: 0
+  #            }
+  #          }
+  #          event {
+  #            timestamp: 751800621175
+  #            pid: 2857
+  #            mali_mali_KCPU_CQS_WAIT_END {
+  #              info_val1: 412313493488
+  #              info_val2: 0
+  #              kctx_tgid: 2201
+  #              kctx_id: 10
+  #              id: 0
+  #            }
+  #          }
+  #          event {
+  #            timestamp: 751800638997
+  #            pid: 2857
+  #            mali_mali_KCPU_CQS_SET {
+  #              info_val1: 412313493480
+  #              info_val2: 0
+  #              kctx_tgid: 2201
+  #              kctx_id: 10
+  #              id: 0
+  #            }
+  #          }
+  #        }
+  #      }
+  #      """),
+  #      query="""
+  #      SELECT ts, dur, name FROM slice WHERE name GLOB "mali_KCPU_CQS*";
+  #      """,
+  #      out=Csv("""
+  #      "ts","dur","name"
+  #      751796307210,4313965,"mali_KCPU_CQS_WAIT"
+  #      751800638997,0,"mali_KCPU_CQS_SET"
+  #      """))
+
+  #def test_mali_fence(self):
+  #  return DiffTestBlueprint(
+  #      trace=TextProto(r"""
+  #      packet {
+  #        ftrace_events {
+  #          cpu: 2
+  #          event {
+  #            timestamp: 751796307210
+  #            pid: 2857
+  #            mali_mali_KCPU_FENCE_WAIT_START {
+  #              info_val1: 1
+  #              info_val2: 0
+  #              kctx_tgid: 2201
+  #              kctx_id: 10
+  #              id: 0
+  #            }
+  #          }
+  #          event {
+  #            timestamp: 751800621175
+  #            pid: 2857
+  #            mali_mali_KCPU_FENCE_WAIT_END {
+  #              info_val1: 412313493488
+  #              info_val2: 0
+  #              kctx_tgid: 2201
+  #              kctx_id: 10
+  #              id: 0
+  #            }
+  #          }
+  #          event {
+  #            timestamp: 751800638997
+  #            pid: 2857
+  #            mali_mali_KCPU_FENCE_SIGNAL {
+  #              info_val1: 412313493480
+  #              info_val2: 0
+  #              kctx_tgid: 2201
+  #              kctx_id: 10
+  #              id: 0
+  #            }
+  #          }
+  #        }
+  #      }
+  #      """),
+  #      query="""
+  #      SELECT ts, dur, name FROM slice WHERE name GLOB "mali_KCPU_FENCE*";
+  #      """,
+  #      out=Csv("""
+  #      "ts","dur","name"
+  #      751796307210,4313965,"mali_KCPU_FENCE_WAIT"
+  #      751800638997,0,"mali_KCPU_FENCE_SIGNAL"
+  #      """))
diff --git a/test/trace_processor/diff_tests/graphics/tests_drm_related_ftrace_events.py b/test/trace_processor/diff_tests/parser/graphics/tests_drm_related_ftrace_events.py
similarity index 100%
rename from test/trace_processor/diff_tests/graphics/tests_drm_related_ftrace_events.py
rename to test/trace_processor/diff_tests/parser/graphics/tests_drm_related_ftrace_events.py
diff --git a/test/trace_processor/diff_tests/graphics/tests_gpu_trace.py b/test/trace_processor/diff_tests/parser/graphics/tests_gpu_trace.py
similarity index 100%
rename from test/trace_processor/diff_tests/graphics/tests_gpu_trace.py
rename to test/trace_processor/diff_tests/parser/graphics/tests_gpu_trace.py
diff --git a/test/trace_processor/diff_tests/graphics/v4l2_vidioc.textproto b/test/trace_processor/diff_tests/parser/graphics/v4l2_vidioc.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/graphics/v4l2_vidioc.textproto
rename to test/trace_processor/diff_tests/parser/graphics/v4l2_vidioc.textproto
diff --git a/test/trace_processor/diff_tests/graphics/v4l2_vidioc_flow.out b/test/trace_processor/diff_tests/parser/graphics/v4l2_vidioc_flow.out
similarity index 100%
rename from test/trace_processor/diff_tests/graphics/v4l2_vidioc_flow.out
rename to test/trace_processor/diff_tests/parser/graphics/v4l2_vidioc_flow.out
diff --git a/test/trace_processor/diff_tests/graphics/virtio_gpu.textproto b/test/trace_processor/diff_tests/parser/graphics/virtio_gpu.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/graphics/virtio_gpu.textproto
rename to test/trace_processor/diff_tests/parser/graphics/virtio_gpu.textproto
diff --git a/test/trace_processor/diff_tests/graphics/virtio_video.textproto b/test/trace_processor/diff_tests/parser/graphics/virtio_video.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/graphics/virtio_video.textproto
rename to test/trace_processor/diff_tests/parser/graphics/virtio_video.textproto
diff --git a/test/trace_processor/diff_tests/graphics/vulkan_api_events.out b/test/trace_processor/diff_tests/parser/graphics/vulkan_api_events.out
similarity index 100%
rename from test/trace_processor/diff_tests/graphics/vulkan_api_events.out
rename to test/trace_processor/diff_tests/parser/graphics/vulkan_api_events.out
diff --git a/test/trace_processor/diff_tests/graphics/vulkan_api_events.py b/test/trace_processor/diff_tests/parser/graphics/vulkan_api_events.py
similarity index 100%
rename from test/trace_processor/diff_tests/graphics/vulkan_api_events.py
rename to test/trace_processor/diff_tests/parser/graphics/vulkan_api_events.py
diff --git a/test/trace_processor/diff_tests/parser/memory/tests.py b/test/trace_processor/diff_tests/parser/memory/tests.py
new file mode 100644
index 0000000..f7bdf1c
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/memory/tests.py
@@ -0,0 +1,113 @@
+#!/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 Csv, TextProto
+from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import TestSuite
+
+
+class MemoryParser(TestSuite):
+  # cma alloc
+  def test_cma(self):
+    return DiffTestBlueprint(
+        trace=TextProto(r"""
+        packet {
+          system_info {
+            utsname {
+              sysname: "Linux"
+              release: "5.10.0"
+            }
+          }
+        }
+        packet {
+          ftrace_events {
+            cpu: 4
+            event {
+              timestamp: 74288080958099
+              pid: 537
+              cma_alloc_start {
+                align: 4
+                count: 6592
+                name: "farawimg"
+              }
+            }
+            event {
+              timestamp: 74288191109751
+              pid: 537
+              cma_alloc_info {
+                align: 4
+                count: 6592
+                err_iso: 0
+                err_mig: 0
+                err_test: 0
+                name: "farawimg"
+                nr_mapped: 832596
+                nr_migrated: 6365
+                nr_reclaimed: 7
+                pfn: 10365824
+              }
+            }
+          }
+        }
+        """),
+        query="""
+        SELECT ts, dur, name FROM slice WHERE name = 'mm_cma_alloc';
+        """,
+        out=Csv("""
+        "ts","dur","name"
+        74288080958099,110151652,"mm_cma_alloc"
+        """))
+
+  def test_android_dma_buffer_tracks(self):
+    return DiffTestBlueprint(
+        trace=TextProto(r"""
+        packet {
+          ftrace_events {
+            cpu: 0
+            event {
+              timestamp: 100
+              pid: 1
+              dma_heap_stat {
+                inode: 123
+                len: 1024
+                total_allocated: 2048
+              }
+            }
+          }
+        }
+        packet {
+          ftrace_events {
+            cpu: 0
+            event {
+              timestamp: 200
+              pid: 1
+              dma_heap_stat {
+                inode: 123
+                len: -1024
+                total_allocated: 1024
+              }
+            }
+          }
+        }
+        """),
+        query="""
+        SELECT track.name, slice.ts, slice.dur, slice.name
+        FROM slice JOIN track ON slice.track_id = track.id
+        WHERE track.name = 'mem.dma_buffer';
+        """,
+        out=Csv("""
+        "name","ts","dur","name"
+        "mem.dma_buffer",100,100,"1 kB"
+        """))
diff --git a/test/trace_processor/diff_tests/network/inet_sock_set_state.textproto b/test/trace_processor/diff_tests/parser/network/inet_sock_set_state.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/network/inet_sock_set_state.textproto
rename to test/trace_processor/diff_tests/parser/network/inet_sock_set_state.textproto
diff --git a/test/trace_processor/diff_tests/network/napi_gro_receive.textproto b/test/trace_processor/diff_tests/parser/network/napi_gro_receive.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/network/napi_gro_receive.textproto
rename to test/trace_processor/diff_tests/parser/network/napi_gro_receive.textproto
diff --git a/test/trace_processor/diff_tests/network/net_dev_xmit.textproto b/test/trace_processor/diff_tests/parser/network/net_dev_xmit.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/network/net_dev_xmit.textproto
rename to test/trace_processor/diff_tests/parser/network/net_dev_xmit.textproto
diff --git a/test/trace_processor/diff_tests/network/netif_receive_skb.textproto b/test/trace_processor/diff_tests/parser/network/netif_receive_skb.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/network/netif_receive_skb.textproto
rename to test/trace_processor/diff_tests/parser/network/netif_receive_skb.textproto
diff --git a/test/trace_processor/diff_tests/network/tests.py b/test/trace_processor/diff_tests/parser/network/tests.py
similarity index 94%
rename from test/trace_processor/diff_tests/network/tests.py
rename to test/trace_processor/diff_tests/parser/network/tests.py
index a8bdd72..76f2446 100644
--- a/test/trace_processor/diff_tests/network/tests.py
+++ b/test/trace_processor/diff_tests/parser/network/tests.py
@@ -13,13 +13,13 @@
 # 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 Path, Metric
+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 Network(TestSuite):
+class NetworkParser(TestSuite):
   # Network performance
   def test_netif_receive_skb(self):
     return DiffTestBlueprint(
@@ -74,12 +74,6 @@
         12000,"wlan0",4,1300
         """))
 
-  def test_netperf_metric(self):
-    return DiffTestBlueprint(
-        trace=Path('netperf_metric.textproto'),
-        query=Metric('android_netperf'),
-        out=Path('netperf_metric.out'))
-
   def test_inet_sock_set_state(self):
     return DiffTestBlueprint(
         trace=Path('inet_sock_set_state.textproto'),
diff --git a/test/trace_processor/diff_tests/parsing/all_atoms_test.sql b/test/trace_processor/diff_tests/parser/parsing/all_atoms_test.sql
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/all_atoms_test.sql
rename to test/trace_processor/diff_tests/parser/parsing/all_atoms_test.sql
diff --git a/test/trace_processor/diff_tests/parsing/android_binder.py b/test/trace_processor/diff_tests/parser/parsing/android_binder.py
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/android_binder.py
rename to test/trace_processor/diff_tests/parser/parsing/android_binder.py
diff --git a/test/trace_processor/diff_tests/parsing/android_log_counts_test.sql b/test/trace_processor/diff_tests/parser/parsing/android_log_counts_test.sql
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/android_log_counts_test.sql
rename to test/trace_processor/diff_tests/parser/parsing/android_log_counts_test.sql
diff --git a/test/trace_processor/diff_tests/parsing/android_log_msgs.out b/test/trace_processor/diff_tests/parser/parsing/android_log_msgs.out
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/android_log_msgs.out
rename to test/trace_processor/diff_tests/parser/parsing/android_log_msgs.out
diff --git a/test/trace_processor/diff_tests/parsing/android_log_msgs_test.sql b/test/trace_processor/diff_tests/parser/parsing/android_log_msgs_test.sql
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/android_log_msgs_test.sql
rename to test/trace_processor/diff_tests/parser/parsing/android_log_msgs_test.sql
diff --git a/test/trace_processor/diff_tests/parsing/android_multiuser_switch.textproto b/test/trace_processor/diff_tests/parser/parsing/android_multiuser_switch.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/android_multiuser_switch.textproto
rename to test/trace_processor/diff_tests/parser/parsing/android_multiuser_switch.textproto
diff --git a/test/trace_processor/diff_tests/parsing/android_package_list.py b/test/trace_processor/diff_tests/parser/parsing/android_package_list.py
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/android_package_list.py
rename to test/trace_processor/diff_tests/parser/parsing/android_package_list.py
diff --git a/test/trace_processor/diff_tests/parsing/android_sched_and_ps_stats.out b/test/trace_processor/diff_tests/parser/parsing/android_sched_and_ps_stats.out
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/android_sched_and_ps_stats.out
rename to test/trace_processor/diff_tests/parser/parsing/android_sched_and_ps_stats.out
diff --git a/test/trace_processor/diff_tests/parsing/args_string_filter_null_test.sql b/test/trace_processor/diff_tests/parser/parsing/args_string_filter_null_test.sql
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/args_string_filter_null_test.sql
rename to test/trace_processor/diff_tests/parser/parsing/args_string_filter_null_test.sql
diff --git a/test/trace_processor/diff_tests/parsing/b120487929_test.sql b/test/trace_processor/diff_tests/parser/parsing/b120487929_test.sql
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/b120487929_test.sql
rename to test/trace_processor/diff_tests/parser/parsing/b120487929_test.sql
diff --git a/test/trace_processor/diff_tests/parsing/cgroup_attach_task_post_s_print_systrace.out b/test/trace_processor/diff_tests/parser/parsing/cgroup_attach_task_post_s_print_systrace.out
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/cgroup_attach_task_post_s_print_systrace.out
rename to test/trace_processor/diff_tests/parser/parsing/cgroup_attach_task_post_s_print_systrace.out
diff --git a/test/trace_processor/diff_tests/parsing/cgroup_attach_task_pre_s_print_systrace.out b/test/trace_processor/diff_tests/parser/parsing/cgroup_attach_task_pre_s_print_systrace.out
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/cgroup_attach_task_pre_s_print_systrace.out
rename to test/trace_processor/diff_tests/parser/parsing/cgroup_attach_task_pre_s_print_systrace.out
diff --git a/test/trace_processor/diff_tests/parsing/chrome_metadata.out b/test/trace_processor/diff_tests/parser/parsing/chrome_metadata.out
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/chrome_metadata.out
rename to test/trace_processor/diff_tests/parser/parsing/chrome_metadata.out
diff --git a/test/trace_processor/diff_tests/parsing/cpu_counters_b120487929.out b/test/trace_processor/diff_tests/parser/parsing/cpu_counters_b120487929.out
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/cpu_counters_b120487929.out
rename to test/trace_processor/diff_tests/parser/parsing/cpu_counters_b120487929.out
diff --git a/test/trace_processor/diff_tests/parsing/cpu_freq.out b/test/trace_processor/diff_tests/parser/parsing/cpu_freq.out
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/cpu_freq.out
rename to test/trace_processor/diff_tests/parser/parsing/cpu_freq.out
diff --git a/test/trace_processor/diff_tests/parsing/cpu_info.textproto b/test/trace_processor/diff_tests/parser/parsing/cpu_info.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/cpu_info.textproto
rename to test/trace_processor/diff_tests/parser/parsing/cpu_info.textproto
diff --git a/test/trace_processor/diff_tests/parsing/flow_events_json_v1.json b/test/trace_processor/diff_tests/parser/parsing/flow_events_json_v1.json
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/flow_events_json_v1.json
rename to test/trace_processor/diff_tests/parser/parsing/flow_events_json_v1.json
diff --git a/test/trace_processor/diff_tests/parsing/flow_events_json_v2.json b/test/trace_processor/diff_tests/parser/parsing/flow_events_json_v2.json
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/flow_events_json_v2.json
rename to test/trace_processor/diff_tests/parser/parsing/flow_events_json_v2.json
diff --git a/test/trace_processor/diff_tests/parsing/ftrace_with_tracing_start.py b/test/trace_processor/diff_tests/parser/parsing/ftrace_with_tracing_start.py
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/ftrace_with_tracing_start.py
rename to test/trace_processor/diff_tests/parser/parsing/ftrace_with_tracing_start.py
diff --git a/test/trace_processor/diff_tests/parsing/funcgraph_trace.textproto b/test/trace_processor/diff_tests/parser/parsing/funcgraph_trace.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/funcgraph_trace.textproto
rename to test/trace_processor/diff_tests/parser/parsing/funcgraph_trace.textproto
diff --git a/test/trace_processor/diff_tests/parsing/kernel_dpu_tmw_counter_thread_counter_and_track.out b/test/trace_processor/diff_tests/parser/parsing/kernel_dpu_tmw_counter_thread_counter_and_track.out
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/kernel_dpu_tmw_counter_thread_counter_and_track.out
rename to test/trace_processor/diff_tests/parser/parsing/kernel_dpu_tmw_counter_thread_counter_and_track.out
diff --git a/test/trace_processor/diff_tests/parsing/kernel_tmw_counter.textproto b/test/trace_processor/diff_tests/parser/parsing/kernel_tmw_counter.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/kernel_tmw_counter.textproto
rename to test/trace_processor/diff_tests/parser/parsing/kernel_tmw_counter.textproto
diff --git a/test/trace_processor/diff_tests/parsing/kernel_tmw_counter_thread_counter_and_track.out b/test/trace_processor/diff_tests/parser/parsing/kernel_tmw_counter_thread_counter_and_track.out
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/kernel_tmw_counter_thread_counter_and_track.out
rename to test/trace_processor/diff_tests/parser/parsing/kernel_tmw_counter_thread_counter_and_track.out
diff --git a/test/trace_processor/diff_tests/parsing/mm_event.out b/test/trace_processor/diff_tests/parser/parsing/mm_event.out
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/mm_event.out
rename to test/trace_processor/diff_tests/parser/parsing/mm_event.out
diff --git a/test/trace_processor/diff_tests/parsing/oom_query_test.sql b/test/trace_processor/diff_tests/parser/parsing/oom_query_test.sql
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/oom_query_test.sql
rename to test/trace_processor/diff_tests/parser/parsing/oom_query_test.sql
diff --git a/test/trace_processor/diff_tests/parsing/otheruuids.textproto b/test/trace_processor/diff_tests/parser/parsing/otheruuids.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/otheruuids.textproto
rename to test/trace_processor/diff_tests/parser/parsing/otheruuids.textproto
diff --git a/test/trace_processor/diff_tests/parsing/print_systrace_lmk_userspace.out b/test/trace_processor/diff_tests/parser/parsing/print_systrace_lmk_userspace.out
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/print_systrace_lmk_userspace.out
rename to test/trace_processor/diff_tests/parser/parsing/print_systrace_lmk_userspace.out
diff --git a/test/trace_processor/diff_tests/parsing/print_systrace_unsigned.out b/test/trace_processor/diff_tests/parser/parsing/print_systrace_unsigned.out
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/print_systrace_unsigned.out
rename to test/trace_processor/diff_tests/parser/parsing/print_systrace_unsigned.out
diff --git a/test/trace_processor/diff_tests/parsing/print_systrace_unsigned.py b/test/trace_processor/diff_tests/parser/parsing/print_systrace_unsigned.py
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/print_systrace_unsigned.py
rename to test/trace_processor/diff_tests/parser/parsing/print_systrace_unsigned.py
diff --git a/test/trace_processor/diff_tests/parsing/process_stats_poll_oom_score.out b/test/trace_processor/diff_tests/parser/parsing/process_stats_poll_oom_score.out
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/process_stats_poll_oom_score.out
rename to test/trace_processor/diff_tests/parser/parsing/process_stats_poll_oom_score.out
diff --git a/test/trace_processor/diff_tests/parsing/rss_stat_after_free.py b/test/trace_processor/diff_tests/parser/parsing/rss_stat_after_free.py
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/rss_stat_after_free.py
rename to test/trace_processor/diff_tests/parser/parsing/rss_stat_after_free.py
diff --git a/test/trace_processor/diff_tests/parsing/rss_stat_legacy.py b/test/trace_processor/diff_tests/parser/parsing/rss_stat_legacy.py
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/rss_stat_legacy.py
rename to test/trace_processor/diff_tests/parser/parsing/rss_stat_legacy.py
diff --git a/test/trace_processor/diff_tests/parsing/rss_stat_mm_id.py b/test/trace_processor/diff_tests/parser/parsing/rss_stat_mm_id.py
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/rss_stat_mm_id.py
rename to test/trace_processor/diff_tests/parser/parsing/rss_stat_mm_id.py
diff --git a/test/trace_processor/diff_tests/parsing/rss_stat_mm_id_clone.py b/test/trace_processor/diff_tests/parser/parsing/rss_stat_mm_id_clone.py
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/rss_stat_mm_id_clone.py
rename to test/trace_processor/diff_tests/parser/parsing/rss_stat_mm_id_clone.py
diff --git a/test/trace_processor/diff_tests/parsing/rss_stat_mm_id_reuse.py b/test/trace_processor/diff_tests/parser/parsing/rss_stat_mm_id_reuse.py
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/rss_stat_mm_id_reuse.py
rename to test/trace_processor/diff_tests/parser/parsing/rss_stat_mm_id_reuse.py
diff --git a/test/trace_processor/diff_tests/parsing/sched_blocked_proto.py b/test/trace_processor/diff_tests/parser/parsing/sched_blocked_proto.py
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/sched_blocked_proto.py
rename to test/trace_processor/diff_tests/parser/parsing/sched_blocked_proto.py
diff --git a/test/trace_processor/diff_tests/parsing/sched_blocked_reason_symbolized.textproto b/test/trace_processor/diff_tests/parser/parsing/sched_blocked_reason_symbolized.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/sched_blocked_reason_symbolized.textproto
rename to test/trace_processor/diff_tests/parser/parsing/sched_blocked_reason_symbolized.textproto
diff --git a/test/trace_processor/diff_tests/parsing/sched_blocked_reason_symbolized_to_systrace.out b/test/trace_processor/diff_tests/parser/parsing/sched_blocked_reason_symbolized_to_systrace.out
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/sched_blocked_reason_symbolized_to_systrace.out
rename to test/trace_processor/diff_tests/parser/parsing/sched_blocked_reason_symbolized_to_systrace.out
diff --git a/test/trace_processor/diff_tests/parsing/sched_blocked_systrace.systrace b/test/trace_processor/diff_tests/parser/parsing/sched_blocked_systrace.systrace
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/sched_blocked_systrace.systrace
rename to test/trace_processor/diff_tests/parser/parsing/sched_blocked_systrace.systrace
diff --git a/test/trace_processor/diff_tests/parsing/sched_slices_sched_switch_compact.out b/test/trace_processor/diff_tests/parser/parsing/sched_slices_sched_switch_compact.out
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/sched_slices_sched_switch_compact.out
rename to test/trace_processor/diff_tests/parser/parsing/sched_slices_sched_switch_compact.out
diff --git a/test/trace_processor/diff_tests/parsing/sched_slices_sched_switch_original.out b/test/trace_processor/diff_tests/parser/parsing/sched_slices_sched_switch_original.out
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/sched_slices_sched_switch_original.out
rename to test/trace_processor/diff_tests/parser/parsing/sched_slices_sched_switch_original.out
diff --git a/test/trace_processor/diff_tests/parsing/sched_waking_instants_compact_sched.out b/test/trace_processor/diff_tests/parser/parsing/sched_waking_instants_compact_sched.out
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/sched_waking_instants_compact_sched.out
rename to test/trace_processor/diff_tests/parser/parsing/sched_waking_instants_compact_sched.out
diff --git a/test/trace_processor/diff_tests/parsing/sched_waking_raw_compact_sched.out b/test/trace_processor/diff_tests/parser/parsing/sched_waking_raw_compact_sched.out
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/sched_waking_raw_compact_sched.out
rename to test/trace_processor/diff_tests/parser/parsing/sched_waking_raw_compact_sched.out
diff --git a/test/trace_processor/diff_tests/parsing/sched_waking_raw_test.sql b/test/trace_processor/diff_tests/parser/parsing/sched_waking_raw_test.sql
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/sched_waking_raw_test.sql
rename to test/trace_processor/diff_tests/parser/parsing/sched_waking_raw_test.sql
diff --git a/test/trace_processor/diff_tests/parsing/statsd_atoms_all_atoms.out b/test/trace_processor/diff_tests/parser/parsing/statsd_atoms_all_atoms.out
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/statsd_atoms_all_atoms.out
rename to test/trace_processor/diff_tests/parser/parsing/statsd_atoms_all_atoms.out
diff --git a/test/trace_processor/diff_tests/parsing/synth_oom.py b/test/trace_processor/diff_tests/parser/parsing/synth_oom.py
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/synth_oom.py
rename to test/trace_processor/diff_tests/parser/parsing/synth_oom.py
diff --git a/test/trace_processor/diff_tests/parsing/synth_oom_oom_query.out b/test/trace_processor/diff_tests/parser/parsing/synth_oom_oom_query.out
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/synth_oom_oom_query.out
rename to test/trace_processor/diff_tests/parser/parsing/synth_oom_oom_query.out
diff --git a/test/trace_processor/diff_tests/parsing/syscall.py b/test/trace_processor/diff_tests/parser/parsing/syscall.py
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/syscall.py
rename to test/trace_processor/diff_tests/parser/parsing/syscall.py
diff --git a/test/trace_processor/diff_tests/parsing/systrace_html.out b/test/trace_processor/diff_tests/parser/parsing/systrace_html.out
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/systrace_html.out
rename to test/trace_processor/diff_tests/parser/parsing/systrace_html.out
diff --git a/test/trace_processor/diff_tests/parsing/tests.py b/test/trace_processor/diff_tests/parser/parsing/tests.py
similarity index 97%
rename from test/trace_processor/diff_tests/parsing/tests.py
rename to test/trace_processor/diff_tests/parser/parsing/tests.py
index 83a986c..5261b09 100644
--- a/test/trace_processor/diff_tests/parsing/tests.py
+++ b/test/trace_processor/diff_tests/parser/parsing/tests.py
@@ -1213,3 +1213,27 @@
         "name","dur","tid"
         "test_event",100,584
         """))
+
+  def test_all_data_source_flushed_metadata(self):
+    return DiffTestBlueprint(
+        trace=TextProto(r"""
+        packet {
+          timestamp: 12344
+          service_event {
+            all_data_sources_flushed: true
+          }
+        }
+        packet {
+          timestamp: 12345
+          service_event {
+            all_data_sources_flushed: true
+          }
+        }
+        """),
+        query="""
+        SELECT name, int_value FROM metadata WHERE name = 'all_data_source_flushed_ns'""",
+        out=Csv("""
+        "name","int_value"
+        "all_data_source_flushed_ns",12344
+        "all_data_source_flushed_ns",12345
+        """))
diff --git a/test/trace_processor/diff_tests/parsing/tests_debug_annotation.py b/test/trace_processor/diff_tests/parser/parsing/tests_debug_annotation.py
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/tests_debug_annotation.py
rename to test/trace_processor/diff_tests/parser/parsing/tests_debug_annotation.py
diff --git a/test/trace_processor/diff_tests/parsing/tests_memory_counters.py b/test/trace_processor/diff_tests/parser/parsing/tests_memory_counters.py
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/tests_memory_counters.py
rename to test/trace_processor/diff_tests/parser/parsing/tests_memory_counters.py
diff --git a/test/trace_processor/diff_tests/parsing/tests_rss_stats.py b/test/trace_processor/diff_tests/parser/parsing/tests_rss_stats.py
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/tests_rss_stats.py
rename to test/trace_processor/diff_tests/parser/parsing/tests_rss_stats.py
diff --git a/test/trace_processor/diff_tests/parsing/thread_counter_and_track_test.sql b/test/trace_processor/diff_tests/parser/parsing/thread_counter_and_track_test.sql
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/thread_counter_and_track_test.sql
rename to test/trace_processor/diff_tests/parser/parsing/thread_counter_and_track_test.sql
diff --git a/test/trace_processor/diff_tests/parsing/thread_time_in_state.out b/test/trace_processor/diff_tests/parser/parsing/thread_time_in_state.out
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/thread_time_in_state.out
rename to test/trace_processor/diff_tests/parser/parsing/thread_time_in_state.out
diff --git a/test/trace_processor/diff_tests/parsing/thread_time_in_state_event.out b/test/trace_processor/diff_tests/parser/parsing/thread_time_in_state_event.out
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/thread_time_in_state_event.out
rename to test/trace_processor/diff_tests/parser/parsing/thread_time_in_state_event.out
diff --git a/test/trace_processor/diff_tests/parsing/thread_time_in_state_event.py b/test/trace_processor/diff_tests/parser/parsing/thread_time_in_state_event.py
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/thread_time_in_state_event.py
rename to test/trace_processor/diff_tests/parser/parsing/thread_time_in_state_event.py
diff --git a/test/trace_processor/diff_tests/parsing/triggers_packets_test.sql b/test/trace_processor/diff_tests/parser/parsing/triggers_packets_test.sql
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/triggers_packets_test.sql
rename to test/trace_processor/diff_tests/parser/parsing/triggers_packets_test.sql
diff --git a/test/trace_processor/diff_tests/parsing/very_long_sched.py b/test/trace_processor/diff_tests/parser/parsing/very_long_sched.py
similarity index 100%
rename from test/trace_processor/diff_tests/parsing/very_long_sched.py
rename to test/trace_processor/diff_tests/parser/parsing/very_long_sched.py
diff --git a/test/trace_processor/diff_tests/power/dvfs_metric.out b/test/trace_processor/diff_tests/parser/power/dvfs_metric.out
similarity index 100%
rename from test/trace_processor/diff_tests/power/dvfs_metric.out
rename to test/trace_processor/diff_tests/parser/power/dvfs_metric.out
diff --git a/test/trace_processor/diff_tests/power/dvfs_metric.textproto b/test/trace_processor/diff_tests/parser/power/dvfs_metric.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/power/dvfs_metric.textproto
rename to test/trace_processor/diff_tests/parser/power/dvfs_metric.textproto
diff --git a/test/trace_processor/diff_tests/power/energy_breakdown.textproto b/test/trace_processor/diff_tests/parser/power/energy_breakdown.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/power/energy_breakdown.textproto
rename to test/trace_processor/diff_tests/parser/power/energy_breakdown.textproto
diff --git a/test/trace_processor/diff_tests/power/energy_breakdown_uid.textproto b/test/trace_processor/diff_tests/parser/power/energy_breakdown_uid.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/power/energy_breakdown_uid.textproto
rename to test/trace_processor/diff_tests/parser/power/energy_breakdown_uid.textproto
diff --git a/test/trace_processor/diff_tests/power/entity_state_residency.textproto b/test/trace_processor/diff_tests/parser/power/entity_state_residency.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/power/entity_state_residency.textproto
rename to test/trace_processor/diff_tests/parser/power/entity_state_residency.textproto
diff --git a/test/trace_processor/diff_tests/power/power_rails.textproto b/test/trace_processor/diff_tests/parser/power/power_rails.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/power/power_rails.textproto
rename to test/trace_processor/diff_tests/parser/power/power_rails.textproto
diff --git a/test/trace_processor/diff_tests/power/power_rails_custom_clock.textproto b/test/trace_processor/diff_tests/parser/power/power_rails_custom_clock.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/power/power_rails_custom_clock.textproto
rename to test/trace_processor/diff_tests/parser/power/power_rails_custom_clock.textproto
diff --git a/test/trace_processor/diff_tests/power/suspend_period.textproto b/test/trace_processor/diff_tests/parser/power/suspend_period.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/power/suspend_period.textproto
rename to test/trace_processor/diff_tests/parser/power/suspend_period.textproto
diff --git a/test/trace_processor/diff_tests/power/suspend_resume.textproto b/test/trace_processor/diff_tests/parser/power/suspend_resume.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/power/suspend_resume.textproto
rename to test/trace_processor/diff_tests/parser/power/suspend_resume.textproto
diff --git a/test/trace_processor/diff_tests/power/tests_energy_breakdown.py b/test/trace_processor/diff_tests/parser/power/tests_energy_breakdown.py
similarity index 100%
rename from test/trace_processor/diff_tests/power/tests_energy_breakdown.py
rename to test/trace_processor/diff_tests/parser/power/tests_energy_breakdown.py
diff --git a/test/trace_processor/diff_tests/power/tests_entity_state_residency.py b/test/trace_processor/diff_tests/parser/power/tests_entity_state_residency.py
similarity index 100%
rename from test/trace_processor/diff_tests/power/tests_entity_state_residency.py
rename to test/trace_processor/diff_tests/parser/power/tests_entity_state_residency.py
diff --git a/test/trace_processor/diff_tests/power/tests_linux_sysfs_power.py b/test/trace_processor/diff_tests/parser/power/tests_linux_sysfs_power.py
similarity index 100%
rename from test/trace_processor/diff_tests/power/tests_linux_sysfs_power.py
rename to test/trace_processor/diff_tests/parser/power/tests_linux_sysfs_power.py
diff --git a/test/trace_processor/diff_tests/power/tests_power_rails.py b/test/trace_processor/diff_tests/parser/power/tests_power_rails.py
similarity index 100%
rename from test/trace_processor/diff_tests/power/tests_power_rails.py
rename to test/trace_processor/diff_tests/parser/power/tests_power_rails.py
diff --git a/test/trace_processor/diff_tests/power/tests_voltage_and_scaling.py b/test/trace_processor/diff_tests/parser/power/tests_voltage_and_scaling.py
similarity index 100%
rename from test/trace_processor/diff_tests/power/tests_voltage_and_scaling.py
rename to test/trace_processor/diff_tests/parser/power/tests_voltage_and_scaling.py
diff --git a/test/trace_processor/diff_tests/power/wakesource.textproto b/test/trace_processor/diff_tests/parser/power/wakesource.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/power/wakesource.textproto
rename to test/trace_processor/diff_tests/parser/power/wakesource.textproto
diff --git a/test/trace_processor/diff_tests/process_tracking/process_parent_pid_tracking_1.py b/test/trace_processor/diff_tests/parser/process_tracking/process_parent_pid_tracking_1.py
similarity index 100%
rename from test/trace_processor/diff_tests/process_tracking/process_parent_pid_tracking_1.py
rename to test/trace_processor/diff_tests/parser/process_tracking/process_parent_pid_tracking_1.py
diff --git a/test/trace_processor/diff_tests/process_tracking/process_parent_pid_tracking_2.py b/test/trace_processor/diff_tests/parser/process_tracking/process_parent_pid_tracking_2.py
similarity index 100%
rename from test/trace_processor/diff_tests/process_tracking/process_parent_pid_tracking_2.py
rename to test/trace_processor/diff_tests/parser/process_tracking/process_parent_pid_tracking_2.py
diff --git a/test/trace_processor/diff_tests/process_tracking/process_tracking_exec.py b/test/trace_processor/diff_tests/parser/process_tracking/process_tracking_exec.py
similarity index 100%
rename from test/trace_processor/diff_tests/process_tracking/process_tracking_exec.py
rename to test/trace_processor/diff_tests/parser/process_tracking/process_tracking_exec.py
diff --git a/test/trace_processor/diff_tests/process_tracking/process_tracking_short_lived_1.py b/test/trace_processor/diff_tests/parser/process_tracking/process_tracking_short_lived_1.py
similarity index 100%
rename from test/trace_processor/diff_tests/process_tracking/process_tracking_short_lived_1.py
rename to test/trace_processor/diff_tests/parser/process_tracking/process_tracking_short_lived_1.py
diff --git a/test/trace_processor/diff_tests/process_tracking/process_tracking_short_lived_2.py b/test/trace_processor/diff_tests/parser/process_tracking/process_tracking_short_lived_2.py
similarity index 100%
rename from test/trace_processor/diff_tests/process_tracking/process_tracking_short_lived_2.py
rename to test/trace_processor/diff_tests/parser/process_tracking/process_tracking_short_lived_2.py
diff --git a/test/trace_processor/diff_tests/process_tracking/reused_thread_print.py b/test/trace_processor/diff_tests/parser/process_tracking/reused_thread_print.py
similarity index 100%
rename from test/trace_processor/diff_tests/process_tracking/reused_thread_print.py
rename to test/trace_processor/diff_tests/parser/process_tracking/reused_thread_print.py
diff --git a/test/trace_processor/diff_tests/process_tracking/synth_process_tracking.py b/test/trace_processor/diff_tests/parser/process_tracking/synth_process_tracking.py
similarity index 100%
rename from test/trace_processor/diff_tests/process_tracking/synth_process_tracking.py
rename to test/trace_processor/diff_tests/parser/process_tracking/synth_process_tracking.py
diff --git a/test/trace_processor/diff_tests/process_tracking/tests.py b/test/trace_processor/diff_tests/parser/process_tracking/tests.py
similarity index 100%
rename from test/trace_processor/diff_tests/process_tracking/tests.py
rename to test/trace_processor/diff_tests/parser/process_tracking/tests.py
diff --git a/test/trace_processor/diff_tests/process_tracking/unknown_thread_name.systrace b/test/trace_processor/diff_tests/parser/process_tracking/unknown_thread_name.systrace
similarity index 100%
rename from test/trace_processor/diff_tests/process_tracking/unknown_thread_name.systrace
rename to test/trace_processor/diff_tests/parser/process_tracking/unknown_thread_name.systrace
diff --git a/test/trace_processor/diff_tests/profiling/callstack_sampling_flamegraph.out b/test/trace_processor/diff_tests/parser/profiling/callstack_sampling_flamegraph.out
similarity index 100%
rename from test/trace_processor/diff_tests/profiling/callstack_sampling_flamegraph.out
rename to test/trace_processor/diff_tests/parser/profiling/callstack_sampling_flamegraph.out
diff --git a/test/trace_processor/diff_tests/profiling/heap_graph.textproto b/test/trace_processor/diff_tests/parser/profiling/heap_graph.textproto
similarity index 100%
copy from test/trace_processor/diff_tests/profiling/heap_graph.textproto
copy to test/trace_processor/diff_tests/parser/profiling/heap_graph.textproto
diff --git a/test/trace_processor/diff_tests/profiling/heap_graph_baseapk.textproto b/test/trace_processor/diff_tests/parser/profiling/heap_graph_baseapk.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/profiling/heap_graph_baseapk.textproto
rename to test/trace_processor/diff_tests/parser/profiling/heap_graph_baseapk.textproto
diff --git a/test/trace_processor/diff_tests/profiling/heap_graph_branching.textproto b/test/trace_processor/diff_tests/parser/profiling/heap_graph_branching.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/profiling/heap_graph_branching.textproto
rename to test/trace_processor/diff_tests/parser/profiling/heap_graph_branching.textproto
diff --git a/test/trace_processor/diff_tests/profiling/heap_graph_deobfuscate_pkg.textproto b/test/trace_processor/diff_tests/parser/profiling/heap_graph_deobfuscate_pkg.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/profiling/heap_graph_deobfuscate_pkg.textproto
rename to test/trace_processor/diff_tests/parser/profiling/heap_graph_deobfuscate_pkg.textproto
diff --git a/test/trace_processor/diff_tests/profiling/heap_graph_duplicate_flamegraph.out b/test/trace_processor/diff_tests/parser/profiling/heap_graph_duplicate_flamegraph.out
similarity index 100%
rename from test/trace_processor/diff_tests/profiling/heap_graph_duplicate_flamegraph.out
rename to test/trace_processor/diff_tests/parser/profiling/heap_graph_duplicate_flamegraph.out
diff --git a/test/trace_processor/diff_tests/profiling/heap_graph_flamegraph.out b/test/trace_processor/diff_tests/parser/profiling/heap_graph_flamegraph.out
similarity index 100%
rename from test/trace_processor/diff_tests/profiling/heap_graph_flamegraph.out
rename to test/trace_processor/diff_tests/parser/profiling/heap_graph_flamegraph.out
diff --git a/test/trace_processor/diff_tests/profiling/heap_graph_flamegraph_focused.out b/test/trace_processor/diff_tests/parser/profiling/heap_graph_flamegraph_focused.out
similarity index 100%
rename from test/trace_processor/diff_tests/profiling/heap_graph_flamegraph_focused.out
rename to test/trace_processor/diff_tests/parser/profiling/heap_graph_flamegraph_focused.out
diff --git a/test/trace_processor/diff_tests/profiling/heap_graph_flamegraph_system-server-heap-graph.out b/test/trace_processor/diff_tests/parser/profiling/heap_graph_flamegraph_system-server-heap-graph.out
similarity index 100%
rename from test/trace_processor/diff_tests/profiling/heap_graph_flamegraph_system-server-heap-graph.out
rename to test/trace_processor/diff_tests/parser/profiling/heap_graph_flamegraph_system-server-heap-graph.out
diff --git a/test/trace_processor/diff_tests/profiling/heap_graph_huge_size.textproto b/test/trace_processor/diff_tests/parser/profiling/heap_graph_huge_size.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/profiling/heap_graph_huge_size.textproto
rename to test/trace_processor/diff_tests/parser/profiling/heap_graph_huge_size.textproto
diff --git a/test/trace_processor/diff_tests/profiling/heap_graph_interleaved.textproto b/test/trace_processor/diff_tests/parser/profiling/heap_graph_interleaved.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/profiling/heap_graph_interleaved.textproto
rename to test/trace_processor/diff_tests/parser/profiling/heap_graph_interleaved.textproto
diff --git a/test/trace_processor/diff_tests/profiling/heap_graph_interleaved_object.out b/test/trace_processor/diff_tests/parser/profiling/heap_graph_interleaved_object.out
similarity index 100%
rename from test/trace_processor/diff_tests/profiling/heap_graph_interleaved_object.out
rename to test/trace_processor/diff_tests/parser/profiling/heap_graph_interleaved_object.out
diff --git a/test/trace_processor/diff_tests/profiling/heap_graph_interleaved_reference.out b/test/trace_processor/diff_tests/parser/profiling/heap_graph_interleaved_reference.out
similarity index 100%
rename from test/trace_processor/diff_tests/profiling/heap_graph_interleaved_reference.out
rename to test/trace_processor/diff_tests/parser/profiling/heap_graph_interleaved_reference.out
diff --git a/test/trace_processor/diff_tests/profiling/heap_graph_legacy.textproto b/test/trace_processor/diff_tests/parser/profiling/heap_graph_legacy.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/profiling/heap_graph_legacy.textproto
rename to test/trace_processor/diff_tests/parser/profiling/heap_graph_legacy.textproto
diff --git a/test/trace_processor/diff_tests/profiling/heap_graph_native_size.textproto b/test/trace_processor/diff_tests/parser/profiling/heap_graph_native_size.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/profiling/heap_graph_native_size.textproto
rename to test/trace_processor/diff_tests/parser/profiling/heap_graph_native_size.textproto
diff --git a/test/trace_processor/diff_tests/profiling/heap_graph_object.out b/test/trace_processor/diff_tests/parser/profiling/heap_graph_object.out
similarity index 100%
rename from test/trace_processor/diff_tests/profiling/heap_graph_object.out
rename to test/trace_processor/diff_tests/parser/profiling/heap_graph_object.out
diff --git a/test/trace_processor/diff_tests/profiling/heap_graph_reference.out b/test/trace_processor/diff_tests/parser/profiling/heap_graph_reference.out
similarity index 100%
rename from test/trace_processor/diff_tests/profiling/heap_graph_reference.out
rename to test/trace_processor/diff_tests/parser/profiling/heap_graph_reference.out
diff --git a/test/trace_processor/diff_tests/profiling/heap_graph_superclass.textproto b/test/trace_processor/diff_tests/parser/profiling/heap_graph_superclass.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/profiling/heap_graph_superclass.textproto
rename to test/trace_processor/diff_tests/parser/profiling/heap_graph_superclass.textproto
diff --git a/test/trace_processor/diff_tests/profiling/heap_graph_two_locations.out b/test/trace_processor/diff_tests/parser/profiling/heap_graph_two_locations.out
similarity index 100%
rename from test/trace_processor/diff_tests/profiling/heap_graph_two_locations.out
rename to test/trace_processor/diff_tests/parser/profiling/heap_graph_two_locations.out
diff --git a/test/trace_processor/diff_tests/profiling/heap_graph_two_locations.textproto b/test/trace_processor/diff_tests/parser/profiling/heap_graph_two_locations.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/profiling/heap_graph_two_locations.textproto
rename to test/trace_processor/diff_tests/parser/profiling/heap_graph_two_locations.textproto
diff --git a/test/trace_processor/diff_tests/profiling/heap_profile.textproto b/test/trace_processor/diff_tests/parser/profiling/heap_profile.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/profiling/heap_profile.textproto
rename to test/trace_processor/diff_tests/parser/profiling/heap_profile.textproto
diff --git a/test/trace_processor/diff_tests/profiling/heap_profile_data_local_tmp.textproto b/test/trace_processor/diff_tests/parser/profiling/heap_profile_data_local_tmp.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/profiling/heap_profile_data_local_tmp.textproto
rename to test/trace_processor/diff_tests/parser/profiling/heap_profile_data_local_tmp.textproto
diff --git a/test/trace_processor/diff_tests/profiling/heap_profile_deobfuscate.textproto b/test/trace_processor/diff_tests/parser/profiling/heap_profile_deobfuscate.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/profiling/heap_profile_deobfuscate.textproto
rename to test/trace_processor/diff_tests/parser/profiling/heap_profile_deobfuscate.textproto
diff --git a/test/trace_processor/diff_tests/profiling/heap_profile_deobfuscate_memfd.textproto b/test/trace_processor/diff_tests/parser/profiling/heap_profile_deobfuscate_memfd.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/profiling/heap_profile_deobfuscate_memfd.textproto
rename to test/trace_processor/diff_tests/parser/profiling/heap_profile_deobfuscate_memfd.textproto
diff --git a/test/trace_processor/diff_tests/profiling/heap_profile_deobfuscate_test.sql b/test/trace_processor/diff_tests/parser/profiling/heap_profile_deobfuscate_test.sql
similarity index 100%
rename from test/trace_processor/diff_tests/profiling/heap_profile_deobfuscate_test.sql
rename to test/trace_processor/diff_tests/parser/profiling/heap_profile_deobfuscate_test.sql
diff --git a/test/trace_processor/diff_tests/profiling/heap_profile_dump_max.textproto b/test/trace_processor/diff_tests/parser/profiling/heap_profile_dump_max.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/profiling/heap_profile_dump_max.textproto
rename to test/trace_processor/diff_tests/parser/profiling/heap_profile_dump_max.textproto
diff --git a/test/trace_processor/diff_tests/profiling/heap_profile_dump_max_legacy.textproto b/test/trace_processor/diff_tests/parser/profiling/heap_profile_dump_max_legacy.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/profiling/heap_profile_dump_max_legacy.textproto
rename to test/trace_processor/diff_tests/parser/profiling/heap_profile_dump_max_legacy.textproto
diff --git a/test/trace_processor/diff_tests/profiling/heap_profile_flamegraph_system-server-native-profile.out b/test/trace_processor/diff_tests/parser/profiling/heap_profile_flamegraph_system-server-native-profile.out
similarity index 100%
rename from test/trace_processor/diff_tests/profiling/heap_profile_flamegraph_system-server-native-profile.out
rename to test/trace_processor/diff_tests/parser/profiling/heap_profile_flamegraph_system-server-native-profile.out
diff --git a/test/trace_processor/diff_tests/profiling/heap_profile_jit.textproto b/test/trace_processor/diff_tests/parser/profiling/heap_profile_jit.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/profiling/heap_profile_jit.textproto
rename to test/trace_processor/diff_tests/parser/profiling/heap_profile_jit.textproto
diff --git a/test/trace_processor/diff_tests/profiling/heap_profile_tracker_new_stack.textproto b/test/trace_processor/diff_tests/parser/profiling/heap_profile_tracker_new_stack.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/profiling/heap_profile_tracker_new_stack.textproto
rename to test/trace_processor/diff_tests/parser/profiling/heap_profile_tracker_new_stack.textproto
diff --git a/test/trace_processor/diff_tests/profiling/heap_profile_tracker_twoheaps.textproto b/test/trace_processor/diff_tests/parser/profiling/heap_profile_tracker_twoheaps.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/profiling/heap_profile_tracker_twoheaps.textproto
rename to test/trace_processor/diff_tests/parser/profiling/heap_profile_tracker_twoheaps.textproto
diff --git a/test/trace_processor/diff_tests/profiling/perf_sample_rvc.out b/test/trace_processor/diff_tests/parser/profiling/perf_sample_rvc.out
similarity index 100%
rename from test/trace_processor/diff_tests/profiling/perf_sample_rvc.out
rename to test/trace_processor/diff_tests/parser/profiling/perf_sample_rvc.out
diff --git a/test/trace_processor/diff_tests/profiling/perf_sample_sc.out b/test/trace_processor/diff_tests/parser/profiling/perf_sample_sc.out
similarity index 100%
rename from test/trace_processor/diff_tests/profiling/perf_sample_sc.out
rename to test/trace_processor/diff_tests/parser/profiling/perf_sample_sc.out
diff --git a/test/trace_processor/diff_tests/profiling/perf_sample_switch_interp.textproto b/test/trace_processor/diff_tests/parser/profiling/perf_sample_switch_interp.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/profiling/perf_sample_switch_interp.textproto
rename to test/trace_processor/diff_tests/parser/profiling/perf_sample_switch_interp.textproto
diff --git a/test/trace_processor/diff_tests/profiling/stack_profile_symbols.out b/test/trace_processor/diff_tests/parser/profiling/stack_profile_symbols.out
similarity index 100%
rename from test/trace_processor/diff_tests/profiling/stack_profile_symbols.out
rename to test/trace_processor/diff_tests/parser/profiling/stack_profile_symbols.out
diff --git a/test/trace_processor/diff_tests/profiling/tests.py b/test/trace_processor/diff_tests/parser/profiling/tests.py
similarity index 100%
rename from test/trace_processor/diff_tests/profiling/tests.py
rename to test/trace_processor/diff_tests/parser/profiling/tests.py
diff --git a/test/trace_processor/diff_tests/profiling/tests_heap_graph.py b/test/trace_processor/diff_tests/parser/profiling/tests_heap_graph.py
similarity index 100%
rename from test/trace_processor/diff_tests/profiling/tests_heap_graph.py
rename to test/trace_processor/diff_tests/parser/profiling/tests_heap_graph.py
diff --git a/test/trace_processor/diff_tests/profiling/tests_heap_profiling.py b/test/trace_processor/diff_tests/parser/profiling/tests_heap_profiling.py
similarity index 100%
rename from test/trace_processor/diff_tests/profiling/tests_heap_profiling.py
rename to test/trace_processor/diff_tests/parser/profiling/tests_heap_profiling.py
diff --git a/test/trace_processor/diff_tests/profiling/tests_llvm_symbolizer.py b/test/trace_processor/diff_tests/parser/profiling/tests_llvm_symbolizer.py
similarity index 100%
rename from test/trace_processor/diff_tests/profiling/tests_llvm_symbolizer.py
rename to test/trace_processor/diff_tests/parser/profiling/tests_llvm_symbolizer.py
diff --git a/test/trace_processor/diff_tests/performance/cpu_frequency_limits.textproto b/test/trace_processor/diff_tests/parser/sched/cpu_frequency_limits.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/performance/cpu_frequency_limits.textproto
rename to test/trace_processor/diff_tests/parser/sched/cpu_frequency_limits.textproto
diff --git a/test/trace_processor/diff_tests/scheduler/sched_cpu_util_cfs.textproto b/test/trace_processor/diff_tests/parser/sched/sched_cpu_util_cfs.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/scheduler/sched_cpu_util_cfs.textproto
rename to test/trace_processor/diff_tests/parser/sched/sched_cpu_util_cfs.textproto
diff --git a/test/trace_processor/diff_tests/scheduler/sched_cpu_util_cfs_test.sql b/test/trace_processor/diff_tests/parser/sched/sched_cpu_util_cfs_test.sql
similarity index 100%
rename from test/trace_processor/diff_tests/scheduler/sched_cpu_util_cfs_test.sql
rename to test/trace_processor/diff_tests/parser/sched/sched_cpu_util_cfs_test.sql
diff --git a/test/trace_processor/diff_tests/performance/tests.py b/test/trace_processor/diff_tests/parser/sched/tests.py
similarity index 74%
rename from test/trace_processor/diff_tests/performance/tests.py
rename to test/trace_processor/diff_tests/parser/sched/tests.py
index 7358373..ea04126 100644
--- a/test/trace_processor/diff_tests/performance/tests.py
+++ b/test/trace_processor/diff_tests/parser/sched/tests.py
@@ -19,14 +19,7 @@
 from python.generators.diff_tests.testing import TestSuite
 
 
-class Performance(TestSuite):
-  # IRQ max runtime and count over 1ms
-  def test_irq_runtime_metric(self):
-    return DiffTestBlueprint(
-        trace=Path('irq_runtime_metric.textproto'),
-        query=Metric('android_irq_runtime'),
-        out=Path('irq_runtime_metric.out'))
-
+class SchedParser(TestSuite):
   # CPU frequency maximum & minimum limits change
   def test_cpu_frequency_limits(self):
     return DiffTestBlueprint(
@@ -61,9 +54,22 @@
         130000000,800000.000000,"Cpu 4 Min"
         """))
 
-  # frame_timeline_metric collects App_Deadline_Missed metrics
-  def test_frame_timeline_metric(self):
+  def test_sched_cpu_util_cfs(self):
     return DiffTestBlueprint(
-        trace=Path('frame_timeline_metric.py'),
-        query=Metric('android_frame_timeline_metric'),
-        out=Path('frame_timeline_metric.out'))
+        trace=Path('sched_cpu_util_cfs.textproto'),
+        query=Path('sched_cpu_util_cfs_test.sql'),
+        out=Csv("""
+        "name","ts","value"
+        "Cpu 6 Util",10000,1.000000
+        "Cpu 6 Cap",10000,1004.000000
+        "Cpu 6 Nr Running",10000,0.000000
+        "Cpu 7 Util",11000,1.000000
+        "Cpu 7 Cap",11000,1007.000000
+        "Cpu 7 Nr Running",11000,0.000000
+        "Cpu 4 Util",12000,43.000000
+        "Cpu 4 Cap",12000,760.000000
+        "Cpu 4 Nr Running",12000,0.000000
+        "Cpu 5 Util",13000,125.000000
+        "Cpu 5 Cap",13000,757.000000
+        "Cpu 5 Nr Running",13000,1.000000
+        """))
\ No newline at end of file
diff --git a/test/trace_processor/diff_tests/smoke/proxy_power.out b/test/trace_processor/diff_tests/parser/smoke/proxy_power.out
similarity index 100%
rename from test/trace_processor/diff_tests/smoke/proxy_power.out
rename to test/trace_processor/diff_tests/parser/smoke/proxy_power.out
diff --git a/test/trace_processor/diff_tests/smoke/tests.py b/test/trace_processor/diff_tests/parser/smoke/tests.py
similarity index 100%
rename from test/trace_processor/diff_tests/smoke/tests.py
rename to test/trace_processor/diff_tests/parser/smoke/tests.py
diff --git a/test/trace_processor/diff_tests/smoke/tests_compute_metrics.py b/test/trace_processor/diff_tests/parser/smoke/tests_compute_metrics.py
similarity index 100%
rename from test/trace_processor/diff_tests/smoke/tests_compute_metrics.py
rename to test/trace_processor/diff_tests/parser/smoke/tests_compute_metrics.py
diff --git a/test/trace_processor/diff_tests/smoke/tests_json.py b/test/trace_processor/diff_tests/parser/smoke/tests_json.py
similarity index 100%
rename from test/trace_processor/diff_tests/smoke/tests_json.py
rename to test/trace_processor/diff_tests/parser/smoke/tests_json.py
diff --git a/test/trace_processor/diff_tests/smoke/tests_sched_events.py b/test/trace_processor/diff_tests/parser/smoke/tests_sched_events.py
similarity index 97%
rename from test/trace_processor/diff_tests/smoke/tests_sched_events.py
rename to test/trace_processor/diff_tests/parser/smoke/tests_sched_events.py
index 86e39fd..fec730f 100644
--- a/test/trace_processor/diff_tests/smoke/tests_sched_events.py
+++ b/test/trace_processor/diff_tests/parser/smoke/tests_sched_events.py
@@ -56,7 +56,7 @@
   # Sched events from sythetic trace
   def test_synth_1_smoke(self):
     return DiffTestBlueprint(
-        trace=Path('../common/synth_1.py'),
+        trace=Path('../../common/synth_1.py'),
         query="""
         SELECT
           ts,
diff --git a/test/trace_processor/diff_tests/smoke/thread_cpu_time_example_android_trace_30s.out b/test/trace_processor/diff_tests/parser/smoke/thread_cpu_time_example_android_trace_30s.out
similarity index 100%
rename from test/trace_processor/diff_tests/smoke/thread_cpu_time_example_android_trace_30s.out
rename to test/trace_processor/diff_tests/parser/smoke/thread_cpu_time_example_android_trace_30s.out
diff --git a/test/trace_processor/diff_tests/track_event/experimental_slice_layout_depth.py b/test/trace_processor/diff_tests/parser/track_event/experimental_slice_layout_depth.py
similarity index 99%
rename from test/trace_processor/diff_tests/track_event/experimental_slice_layout_depth.py
rename to test/trace_processor/diff_tests/parser/track_event/experimental_slice_layout_depth.py
index 3baf368..25b7668 100644
--- a/test/trace_processor/diff_tests/track_event/experimental_slice_layout_depth.py
+++ b/test/trace_processor/diff_tests/parser/track_event/experimental_slice_layout_depth.py
@@ -21,6 +21,7 @@
 import synth_common
 
 from synth_common import ms_to_ns
+
 trace = synth_common.create_trace()
 
 track1 = 1234
diff --git a/test/trace_processor/diff_tests/track_event/flow_events_proto_v1.textproto b/test/trace_processor/diff_tests/parser/track_event/flow_events_proto_v1.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/track_event/flow_events_proto_v1.textproto
rename to test/trace_processor/diff_tests/parser/track_event/flow_events_proto_v1.textproto
diff --git a/test/trace_processor/diff_tests/track_event/flow_events_proto_v2.textproto b/test/trace_processor/diff_tests/parser/track_event/flow_events_proto_v2.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/track_event/flow_events_proto_v2.textproto
rename to test/trace_processor/diff_tests/parser/track_event/flow_events_proto_v2.textproto
diff --git a/test/trace_processor/diff_tests/track_event/flow_events_track_event.textproto b/test/trace_processor/diff_tests/parser/track_event/flow_events_track_event.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/track_event/flow_events_track_event.textproto
rename to test/trace_processor/diff_tests/parser/track_event/flow_events_track_event.textproto
diff --git a/test/trace_processor/diff_tests/track_event/legacy_async_event.out b/test/trace_processor/diff_tests/parser/track_event/legacy_async_event.out
similarity index 100%
rename from test/trace_processor/diff_tests/track_event/legacy_async_event.out
rename to test/trace_processor/diff_tests/parser/track_event/legacy_async_event.out
diff --git a/test/trace_processor/diff_tests/track_event/legacy_async_event.textproto b/test/trace_processor/diff_tests/parser/track_event/legacy_async_event.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/track_event/legacy_async_event.textproto
rename to test/trace_processor/diff_tests/parser/track_event/legacy_async_event.textproto
diff --git a/test/trace_processor/diff_tests/track_event/range_of_interest.textproto b/test/trace_processor/diff_tests/parser/track_event/range_of_interest.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/track_event/range_of_interest.textproto
rename to test/trace_processor/diff_tests/parser/track_event/range_of_interest.textproto
diff --git a/test/trace_processor/diff_tests/track_event/tests.py b/test/trace_processor/diff_tests/parser/track_event/tests.py
similarity index 100%
rename from test/trace_processor/diff_tests/track_event/tests.py
rename to test/trace_processor/diff_tests/parser/track_event/tests.py
diff --git a/test/trace_processor/diff_tests/track_event/track_event_args_test.sql b/test/trace_processor/diff_tests/parser/track_event/track_event_args_test.sql
similarity index 100%
rename from test/trace_processor/diff_tests/track_event/track_event_args_test.sql
rename to test/trace_processor/diff_tests/parser/track_event/track_event_args_test.sql
diff --git a/test/trace_processor/diff_tests/track_event/track_event_chrome_histogram_sample.textproto b/test/trace_processor/diff_tests/parser/track_event/track_event_chrome_histogram_sample.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/track_event/track_event_chrome_histogram_sample.textproto
rename to test/trace_processor/diff_tests/parser/track_event/track_event_chrome_histogram_sample.textproto
diff --git a/test/trace_processor/diff_tests/track_event/track_event_chrome_histogram_sample_args.out b/test/trace_processor/diff_tests/parser/track_event/track_event_chrome_histogram_sample_args.out
similarity index 100%
rename from test/trace_processor/diff_tests/track_event/track_event_chrome_histogram_sample_args.out
rename to test/trace_processor/diff_tests/parser/track_event/track_event_chrome_histogram_sample_args.out
diff --git a/test/trace_processor/diff_tests/track_event/track_event_counters.textproto b/test/trace_processor/diff_tests/parser/track_event/track_event_counters.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/track_event/track_event_counters.textproto
rename to test/trace_processor/diff_tests/parser/track_event/track_event_counters.textproto
diff --git a/test/trace_processor/diff_tests/track_event/track_event_counters_counters.out b/test/trace_processor/diff_tests/parser/track_event/track_event_counters_counters.out
similarity index 100%
rename from test/trace_processor/diff_tests/track_event/track_event_counters_counters.out
rename to test/trace_processor/diff_tests/parser/track_event/track_event_counters_counters.out
diff --git a/test/trace_processor/diff_tests/track_event/track_event_merged_debug_annotations.textproto b/test/trace_processor/diff_tests/parser/track_event/track_event_merged_debug_annotations.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/track_event/track_event_merged_debug_annotations.textproto
rename to test/trace_processor/diff_tests/parser/track_event/track_event_merged_debug_annotations.textproto
diff --git a/test/trace_processor/diff_tests/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
similarity index 100%
rename from test/trace_processor/diff_tests/track_event/track_event_merged_debug_annotations_args.out
rename to test/trace_processor/diff_tests/parser/track_event/track_event_merged_debug_annotations_args.out
diff --git a/test/trace_processor/diff_tests/track_event/track_event_tracks.textproto b/test/trace_processor/diff_tests/parser/track_event/track_event_tracks.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/track_event/track_event_tracks.textproto
rename to test/trace_processor/diff_tests/parser/track_event/track_event_tracks.textproto
diff --git a/test/trace_processor/diff_tests/track_event/track_event_tracks_slices.out b/test/trace_processor/diff_tests/parser/track_event/track_event_tracks_slices.out
similarity index 100%
rename from test/trace_processor/diff_tests/track_event/track_event_tracks_slices.out
rename to test/trace_processor/diff_tests/parser/track_event/track_event_tracks_slices.out
diff --git a/test/trace_processor/diff_tests/track_event/track_event_typed_args.textproto b/test/trace_processor/diff_tests/parser/track_event/track_event_typed_args.textproto
similarity index 82%
rename from test/trace_processor/diff_tests/track_event/track_event_typed_args.textproto
rename to test/trace_processor/diff_tests/parser/track_event/track_event_typed_args.textproto
index b2d9208..a85ef2c 100644
--- a/test/trace_processor/diff_tests/track_event/track_event_typed_args.textproto
+++ b/test/trace_processor/diff_tests/parser/track_event/track_event_typed_args.textproto
@@ -72,6 +72,8 @@
     }
     [perfetto.protos.TestExtension.string_extension_for_testing]:
         "an extension string!"
+    [perfetto.protos.TestExtension.string_extension_for_testing2]:
+        "a second extension string!"
     [perfetto.protos.TestExtension.int_extension_for_testing]: 42
     [perfetto.protos.TestExtension.int_extension_for_testing]: 1337
     [perfetto.protos.TestExtension.omitted_extension_for_testing]:
@@ -99,6 +101,7 @@
     extension_set {
       file {
         package: "perfetto.protos"
+        name: "test_track_event_extensions.proto"
         message_type {
           extension {
             name: "string_extension_for_testing"
@@ -143,6 +146,30 @@
     }
   }
 }
+# Test that a field specified in a second descriptor for the same proto file is
+# also detected. This emulates the case of multiple instances of the same app
+# (e.g. Chrome Beta + Chrome Canary) both emitting a descriptor.
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 5001
+  extension_descriptor {
+    extension_set {
+      file {
+        package: "perfetto.protos"
+        name: "test_track_event_extensions.proto"
+        message_type {
+          extension {
+            name: "string_extension_for_testing2"
+            extendee: ".perfetto.protos.TrackEvent"
+            number: 9905
+            type: TYPE_STRING
+            label: LABEL_OPTIONAL
+          }
+        }
+      }
+    }
+  }
+}
 packet {
   trusted_packet_sequence_id: 1
   timestamp: 6000
diff --git a/test/trace_processor/diff_tests/track_event/track_event_typed_args_args.out b/test/trace_processor/diff_tests/parser/track_event/track_event_typed_args_args.out
similarity index 96%
rename from test/trace_processor/diff_tests/track_event/track_event_typed_args_args.out
rename to test/trace_processor/diff_tests/parser/track_event/track_event_typed_args_args.out
index cb6ade4..47a173a 100644
--- a/test/trace_processor/diff_tests/track_event/track_event_typed_args_args.out
+++ b/test/trace_processor/diff_tests/parser/track_event/track_event_typed_args_args.out
@@ -37,3 +37,4 @@
 "source_id","source_id",1,"[NULL]"
 "source_location_iid","source_location_iid",1,"[NULL]"
 "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!"
diff --git a/test/trace_processor/diff_tests/track_event/track_event_with_atrace.textproto b/test/trace_processor/diff_tests/parser/track_event/track_event_with_atrace.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/track_event/track_event_with_atrace.textproto
rename to test/trace_processor/diff_tests/parser/track_event/track_event_with_atrace.textproto
diff --git a/test/trace_processor/diff_tests/track_event/track_event_with_atrace_separate_tracks.textproto b/test/trace_processor/diff_tests/parser/track_event/track_event_with_atrace_separate_tracks.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/track_event/track_event_with_atrace_separate_tracks.textproto
rename to test/trace_processor/diff_tests/parser/track_event/track_event_with_atrace_separate_tracks.textproto
diff --git a/test/trace_processor/diff_tests/translation/chrome_args_test.sql b/test/trace_processor/diff_tests/parser/translated_args/chrome_args_test.sql
similarity index 100%
rename from test/trace_processor/diff_tests/translation/chrome_args_test.sql
rename to test/trace_processor/diff_tests/parser/translated_args/chrome_args_test.sql
diff --git a/test/trace_processor/diff_tests/translation/chrome_histogram.out b/test/trace_processor/diff_tests/parser/translated_args/chrome_histogram.out
similarity index 100%
rename from test/trace_processor/diff_tests/translation/chrome_histogram.out
rename to test/trace_processor/diff_tests/parser/translated_args/chrome_histogram.out
diff --git a/test/trace_processor/diff_tests/translation/chrome_histogram.textproto b/test/trace_processor/diff_tests/parser/translated_args/chrome_histogram.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/translation/chrome_histogram.textproto
rename to test/trace_processor/diff_tests/parser/translated_args/chrome_histogram.textproto
diff --git a/test/trace_processor/diff_tests/translation/chrome_performance_mark.out b/test/trace_processor/diff_tests/parser/translated_args/chrome_performance_mark.out
similarity index 100%
rename from test/trace_processor/diff_tests/translation/chrome_performance_mark.out
rename to test/trace_processor/diff_tests/parser/translated_args/chrome_performance_mark.out
diff --git a/test/trace_processor/diff_tests/translation/chrome_user_event.out b/test/trace_processor/diff_tests/parser/translated_args/chrome_user_event.out
similarity index 100%
rename from test/trace_processor/diff_tests/translation/chrome_user_event.out
rename to test/trace_processor/diff_tests/parser/translated_args/chrome_user_event.out
diff --git a/test/trace_processor/diff_tests/translation/chrome_user_event.textproto b/test/trace_processor/diff_tests/parser/translated_args/chrome_user_event.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/translation/chrome_user_event.textproto
rename to test/trace_processor/diff_tests/parser/translated_args/chrome_user_event.textproto
diff --git a/test/trace_processor/diff_tests/translation/java_class_name_arg.out b/test/trace_processor/diff_tests/parser/translated_args/java_class_name_arg.out
similarity index 100%
rename from test/trace_processor/diff_tests/translation/java_class_name_arg.out
rename to test/trace_processor/diff_tests/parser/translated_args/java_class_name_arg.out
diff --git a/test/trace_processor/diff_tests/translation/java_class_name_arg.textproto b/test/trace_processor/diff_tests/parser/translated_args/java_class_name_arg.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/translation/java_class_name_arg.textproto
rename to test/trace_processor/diff_tests/parser/translated_args/java_class_name_arg.textproto
diff --git a/test/trace_processor/diff_tests/translation/native_symbol_arg.out b/test/trace_processor/diff_tests/parser/translated_args/native_symbol_arg.out
similarity index 100%
rename from test/trace_processor/diff_tests/translation/native_symbol_arg.out
rename to test/trace_processor/diff_tests/parser/translated_args/native_symbol_arg.out
diff --git a/test/trace_processor/diff_tests/translation/native_symbol_arg.textproto b/test/trace_processor/diff_tests/parser/translated_args/native_symbol_arg.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/translation/native_symbol_arg.textproto
rename to test/trace_processor/diff_tests/parser/translated_args/native_symbol_arg.textproto
diff --git a/test/trace_processor/diff_tests/translation/native_symbol_arg_incomplete.textproto b/test/trace_processor/diff_tests/parser/translated_args/native_symbol_arg_incomplete.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/translation/native_symbol_arg_incomplete.textproto
rename to test/trace_processor/diff_tests/parser/translated_args/native_symbol_arg_incomplete.textproto
diff --git a/test/trace_processor/diff_tests/translation/slice_name.textproto b/test/trace_processor/diff_tests/parser/translated_args/slice_name.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/translation/slice_name.textproto
rename to test/trace_processor/diff_tests/parser/translated_args/slice_name.textproto
diff --git a/test/trace_processor/diff_tests/translation/slice_name_negative_timestamp.textproto b/test/trace_processor/diff_tests/parser/translated_args/slice_name_negative_timestamp.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/translation/slice_name_negative_timestamp.textproto
rename to test/trace_processor/diff_tests/parser/translated_args/slice_name_negative_timestamp.textproto
diff --git a/test/trace_processor/diff_tests/translation/tests.py b/test/trace_processor/diff_tests/parser/translated_args/tests.py
similarity index 98%
rename from test/trace_processor/diff_tests/translation/tests.py
rename to test/trace_processor/diff_tests/parser/translated_args/tests.py
index 9856205..7e28f38 100644
--- a/test/trace_processor/diff_tests/translation/tests.py
+++ b/test/trace_processor/diff_tests/parser/translated_args/tests.py
@@ -19,7 +19,7 @@
 from python.generators.diff_tests.testing import TestSuite
 
 
-class Translation(TestSuite):
+class TranslatedArgs(TestSuite):
 
   def test_java_class_name_arg(self):
     return DiffTestBlueprint(
diff --git a/test/trace_processor/diff_tests/ufs/tests.py b/test/trace_processor/diff_tests/parser/ufs/tests.py
similarity index 100%
rename from test/trace_processor/diff_tests/ufs/tests.py
rename to test/trace_processor/diff_tests/parser/ufs/tests.py
diff --git a/test/trace_processor/diff_tests/ufs/ufshcd_command.textproto b/test/trace_processor/diff_tests/parser/ufs/ufshcd_command.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/ufs/ufshcd_command.textproto
rename to test/trace_processor/diff_tests/parser/ufs/ufshcd_command.textproto
diff --git a/test/trace_processor/diff_tests/ufs/ufshcd_command_tag.textproto b/test/trace_processor/diff_tests/parser/ufs/ufshcd_command_tag.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/ufs/ufshcd_command_tag.textproto
rename to test/trace_processor/diff_tests/parser/ufs/ufshcd_command_tag.textproto
diff --git a/test/trace_processor/diff_tests/scheduler/tests.py b/test/trace_processor/diff_tests/scheduler/tests.py
deleted file mode 100644
index b935c6a..0000000
--- a/test/trace_processor/diff_tests/scheduler/tests.py
+++ /dev/null
@@ -1,42 +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 Scheduler(TestSuite):
-  # Scheduler
-  def test_sched_cpu_util_cfs(self):
-    return DiffTestBlueprint(
-        trace=Path('sched_cpu_util_cfs.textproto'),
-        query=Path('sched_cpu_util_cfs_test.sql'),
-        out=Csv("""
-        "name","ts","value"
-        "Cpu 6 Util",10000,1.000000
-        "Cpu 6 Cap",10000,1004.000000
-        "Cpu 6 Nr Running",10000,0.000000
-        "Cpu 7 Util",11000,1.000000
-        "Cpu 7 Cap",11000,1007.000000
-        "Cpu 7 Nr Running",11000,0.000000
-        "Cpu 4 Util",12000,43.000000
-        "Cpu 4 Cap",12000,760.000000
-        "Cpu 4 Nr Running",12000,0.000000
-        "Cpu 5 Util",13000,125.000000
-        "Cpu 5 Cap",13000,757.000000
-        "Cpu 5 Nr Running",13000,1.000000
-        """))
diff --git a/test/trace_processor/diff_tests/android/android_battery_stats_event_slices.out b/test/trace_processor/diff_tests/stdlib/android/android_battery_stats_event_slices.out
similarity index 100%
rename from test/trace_processor/diff_tests/android/android_battery_stats_event_slices.out
rename to test/trace_processor/diff_tests/stdlib/android/android_battery_stats_event_slices.out
diff --git a/test/trace_processor/diff_tests/android/android_battery_stats_state.out b/test/trace_processor/diff_tests/stdlib/android/android_battery_stats_state.out
similarity index 100%
rename from test/trace_processor/diff_tests/android/android_battery_stats_state.out
rename to test/trace_processor/diff_tests/stdlib/android/android_battery_stats_state.out
diff --git a/test/trace_processor/diff_tests/android/android_slice_standardization.out b/test/trace_processor/diff_tests/stdlib/android/android_slice_standardization.out
similarity index 100%
rename from test/trace_processor/diff_tests/android/android_slice_standardization.out
rename to test/trace_processor/diff_tests/stdlib/android/android_slice_standardization.out
diff --git a/test/trace_processor/diff_tests/android/android_slice_standardization.py b/test/trace_processor/diff_tests/stdlib/android/android_slice_standardization.py
similarity index 77%
rename from test/trace_processor/diff_tests/android/android_slice_standardization.py
rename to test/trace_processor/diff_tests/stdlib/android/android_slice_standardization.py
index a0556df..dc75d91 100644
--- a/test/trace_processor/diff_tests/android/android_slice_standardization.py
+++ b/test/trace_processor/diff_tests/stdlib/android/android_slice_standardization.py
@@ -21,13 +21,12 @@
 trace = synth_common.create_trace()
 trace.add_packet()
 
-trace.add_package_list(ts=0, name="com.android.systemui", uid=SYSUI_PID,
-                       version_code=1)
-trace.add_process(pid=SYSUI_PID, ppid=0, cmdline="com.android.systemui",
-                  uid=SYSUI_PID)
+trace.add_package_list(
+    ts=0, name="com.android.systemui", uid=SYSUI_PID, version_code=1)
+trace.add_process(
+    pid=SYSUI_PID, ppid=0, cmdline="com.android.systemui", uid=SYSUI_PID)
 trace.add_ftrace_packet(cpu=0)
 
-
 slices_to_standardize = [
     "Lock contention on thread list lock (owner tid: 1665)",
     "monitor contention with owner BG Thread #1 (30) at",
@@ -43,9 +42,9 @@
 ]
 
 for name in slices_to_standardize:
-  trace.add_atrace_async_begin(ts=1_000_000, tid=SYSUI_PID, pid=SYSUI_PID,
-                               buf=name)
-  trace.add_atrace_async_end(ts=2_000_000, tid=SYSUI_PID, pid=SYSUI_PID,
-                             buf=name)
+  trace.add_atrace_async_begin(
+      ts=1_000_000, tid=SYSUI_PID, pid=SYSUI_PID, buf=name)
+  trace.add_atrace_async_end(
+      ts=2_000_000, tid=SYSUI_PID, pid=SYSUI_PID, buf=name)
 
 sys.stdout.buffer.write(trace.trace.SerializeToString())
diff --git a/test/trace_processor/diff_tests/android/tests.py b/test/trace_processor/diff_tests/stdlib/android/tests.py
similarity index 72%
rename from test/trace_processor/diff_tests/android/tests.py
rename to test/trace_processor/diff_tests/stdlib/android/tests.py
index 8bbbeb7..3d143a8 100644
--- a/test/trace_processor/diff_tests/android/tests.py
+++ b/test/trace_processor/diff_tests/stdlib/android/tests.py
@@ -19,97 +19,8 @@
 from python.generators.diff_tests.testing import TestSuite
 from python.generators.diff_tests.testing import PrintProfileProto
 
-class Android(TestSuite):
 
-  def test_android_system_property_counter(self):
-    return DiffTestBlueprint(
-        trace=TextProto(r"""
-        packet {
-          timestamp: 1000
-          android_system_property {
-            values {
-              name: "debug.tracing.screen_state"
-              value: "2"
-            }
-            values {
-              name: "debug.tracing.device_state"
-              value: "some_state_from_sysprops"
-            }
-          }
-        }
-        packet {
-          ftrace_events {
-            cpu: 1
-            event {
-              timestamp: 2000
-              pid: 1
-              print {
-                buf: "C|1000|ScreenState|1\n"
-              }
-            }
-            event {
-              timestamp: 3000
-              pid: 1
-              print {
-                buf: "N|1000|DeviceStateChanged|some_state_from_atrace\n"
-              }
-            }
-          }
-        }
-        """),
-        query="""
-        SELECT t.type, t.name, c.id, c.ts, c.type, c.value
-        FROM counter_track t JOIN counter c ON t.id = c.track_id
-        WHERE name = 'ScreenState';
-        """,
-        out=Csv("""
-        "type","name","id","ts","type","value"
-        "counter_track","ScreenState",0,1000,"counter",2.000000
-        "counter_track","ScreenState",1,2000,"counter",1.000000
-        """))
-
-  def test_android_system_property_slice(self):
-    return DiffTestBlueprint(
-        trace=TextProto(r"""
-        packet {
-          timestamp: 1000
-          android_system_property {
-            values {
-              name: "debug.tracing.screen_state"
-              value: "2"
-            }
-            values {
-              name: "debug.tracing.device_state"
-              value: "some_state_from_sysprops"
-            }
-          }
-        }
-        packet {
-          ftrace_events {
-            cpu: 1
-            event {
-              timestamp: 2000
-              pid: 1
-              print {
-                buf: "C|1000|ScreenState|1\n"
-              }
-            }
-            event {
-              timestamp: 3000
-              pid: 1
-              print {
-                buf: "N|1000|DeviceStateChanged|some_state_from_atrace\n"
-              }
-            }
-          }
-        }
-        """),
-        query="""
-        SELECT t.type, t.name, s.id, s.ts, s.dur, s.type, s.name
-        FROM track t JOIN slice s ON s.track_id = t.id
-        WHERE t.name = 'DeviceStateChanged';
-        """,
-        out=Path('android_system_property_slice.out'))
+class AndroidStdlib(TestSuite):
 
   def test_android_battery_stats_event_slices(self):
     # The following has three events
@@ -196,90 +107,9 @@
         """,
         out=Path('android_battery_stats_state.out'))
 
-  def test_android_network_activity(self):
-    # The following should have three activity regions:
-    # * uid=123 from 1000 to 2010 (note: end is max(ts)+idle_ns)
-    # * uid=456 from 1005 to 3115 (note: doesn't group with above due to name)
-    #   * Also tests that groups form based on (ts+dur), not just start ts.
-    # * uid=123 from 3000 to 5500 (note: gap between 1010 to 3000 > idle_ns)
-    # Note: packet_timestamps are delta encoded from the base timestamp.
-    return DiffTestBlueprint(
-        trace=TextProto(r"""
-        packet {
-          timestamp: 0
-          network_packet_bundle {
-            ctx {
-              direction: DIR_EGRESS
-              interface: "wlan"
-              uid: 123
-            }
-            packet_timestamps: [
-              1000, 1010,
-              3000, 3050, 4000, 4500
-            ],
-            packet_lengths: [
-              50, 50,
-              50, 50, 50, 50
-            ],
-          }
-        }
-        packet {
-          timestamp: 1005
-          network_packet_bundle {
-            ctx {
-              direction: DIR_EGRESS
-              interface: "wlan"
-              uid: 456
-            }
-            total_duration: 100
-            total_packets: 2
-            total_length: 300
-          }
-        }
-        packet {
-          timestamp: 2015
-          network_packet_bundle {
-            ctx {
-              direction: DIR_EGRESS
-              interface: "wlan"
-              uid: 456
-            }
-            total_duration: 100
-            total_packets: 1
-            total_length: 50
-          }
-        }
-        packet {
-          timestamp: 0
-          network_packet_bundle {
-            ctx {
-              direction: DIR_INGRESS
-              interface: "loopback"
-              uid: 123
-            }
-            packet_timestamps: [6000]
-            packet_lengths: [100]
-          }
-        }
-        """),
-        query="""
-        SELECT RUN_METRIC(
-          'android/network_activity_template.sql',
-          'view_name', 'android_network_activity',
-          'group_by',  'package_name',
-          'filter',    'iface = "wlan"',
-          'idle_ns',   '1000',
-          'quant_ns',  '100'
-        );
-
-        SELECT * FROM android_network_activity
-        ORDER BY package_name, ts;
-        """,
-        out=Path('android_network_activity.out'))
-
   def test_anrs(self):
     return DiffTestBlueprint(
-        trace=Path('android_anr_metric.py'),
+        trace=Path('../../metrics/android/android_anr_metric.py'),
         query="""
         INCLUDE PERFETTO MODULE android.anrs;
         SELECT *
@@ -292,12 +122,6 @@
         "com.google.android.app3","[NULL]","[NULL]","c25916a0-a8f0-41f3-87df-319e06471a0f",3000,"[NULL]"
       """))
 
-  def test_anr_metric(self):
-    return DiffTestBlueprint(
-        trace=Path('android_anr_metric.py'),
-        query=Metric('android_anr'),
-        out=Path('android_anr_metric.out'))
-
   def test_binder_sync_binder_metrics(self):
     return DiffTestBlueprint(
         trace=DataPath('android_binder_metric_trace.atr'),
@@ -390,116 +214,6 @@
       34382,25505818197,492,34383,25505891588,1596,"binder_reply","filemap_fault",864664,1
       """))
 
-  def test_binder_txn_sync_good(self):
-    return DiffTestBlueprint(
-        trace=Systrace(
-"""          client-521390  [005] ..... 137012.464739: binder_command: cmd=0x40406300 BC_TRANSACTION
-          client-521390  [005] ..... 137012.464741: binder_transaction: transaction=5149 dest_node=5143 dest_proc=521383 dest_thread=0 reply=0 flags=0x0 code=0x3
-          server-521383  [004] ..... 137012.464771: binder_transaction_received: transaction=5149
-          server-521383  [004] ..... 137012.464772: binder_return: cmd=0x80407202 BR_TRANSACTION
-          server-521383  [004] ..... 137012.464815: binder_command: cmd=0x40086303 BC_FREE_BUFFER
-          server-521383  [004] ..... 137012.464823: binder_command: cmd=0x40406301 BC_REPLY
-          server-521383  [004] ..... 137012.464826: binder_transaction: transaction=5150 dest_node=0 dest_proc=521390 dest_thread=521390 reply=1 flags=0x20 code=0x3
-          server-521383  [004] ..... 137012.464837: binder_return: cmd=0x7206 BR_TRANSACTION_COMPLETE
-          client-521390  [005] ..... 137012.464847: binder_return: cmd=0x7206 BR_TRANSACTION_COMPLETE
-          client-521390  [005] ..... 137012.464848: binder_transaction_received: transaction=5150
-          client-521390  [005] ..... 137012.464849: binder_return: cmd=0x80407203 BR_REPLY
-          """),
-        query="""
-      SELECT
-        dur
-      FROM slice
-      ORDER BY dur;
-      """,
-        out=Csv("""
-      "dur"
-      55000
-      107000
-      """))
-
-  def test_binder_txn_sync_bad_request(self):
-    return DiffTestBlueprint(
-        trace=Systrace(
-"""          client-521349  [005] ..... 137004.281009: binder_command: cmd=0x40406300 BC_TRANSACTION
-          client-521349  [005] ..... 137004.281010: binder_transaction: transaction=5135 dest_node=5129 dest_proc=521347 dest_thread=0 reply=0 flags=0x0 code=0x3
-          client-521349  [005] ..... 137004.281410: binder_return: cmd=0x7211 BR_FAILED_REPLY
-          """),
-        query="""
-      SELECT
-        dur
-      FROM slice
-      ORDER BY dur;
-      """,
-        out=Csv("""
-      "dur"
-      400000
-      """))
-
-  def test_binder_txn_sync_bad_reply(self):
-    return DiffTestBlueprint(
-        trace=Systrace(
-"""          client-521332  [007] ..... 136996.112660: binder_command: cmd=0x40406300 BC_TRANSACTION
-          client-521332  [007] ..... 136996.112662: binder_transaction: transaction=5120 dest_node=5114 dest_proc=521330 dest_thread=0 reply=0 flags=0x0 code=0x3
-          server-521330  [000] ..... 136996.112714: binder_transaction_received: transaction=5120
-          server-521330  [000] ..... 136996.112715: binder_return: cmd=0x80407202 BR_TRANSACTION
-          server-521330  [000] ..... 136996.112752: binder_command: cmd=0x40086303 BC_FREE_BUFFER
-          server-521330  [000] ..... 136996.112758: binder_command: cmd=0x40406301 BC_REPLY
-          server-521330  [000] ..... 136996.112760: binder_transaction: transaction=5121 dest_node=0 dest_proc=521332 dest_thread=521332 reply=1 flags=0x20 code=0x3
-          server-521330  [000] ..... 136996.113163: binder_return: cmd=0x7206 BR_TRANSACTION_COMPLETE
-          client-521332  [007] ..... 136996.113201: binder_return: cmd=0x7206 BR_TRANSACTION_COMPLETE
-          client-521332  [007] ..... 136996.113201: binder_return: cmd=0x7211 BR_FAILED_REPLY
-          """),
-        query="""
-      SELECT
-        dur
-      FROM slice
-      ORDER BY dur;
-      """,
-        out=Csv("""
-      "dur"
-      46000
-      539000
-      """))
-
-  def test_binder_txn_oneway_good(self):
-    return DiffTestBlueprint(
-        trace=Systrace(
-"""          client-521406  [003] ..... 137020.679833: binder_command: cmd=0x40406300 BC_TRANSACTION
-          client-521406  [003] ..... 137020.679834: binder_transaction: transaction=5161 dest_node=5155 dest_proc=521404 dest_thread=0 reply=0 flags=0x1 code=0x3
-          client-521406  [003] ..... 137020.679843: binder_return: cmd=0x7206 BR_TRANSACTION_COMPLETE
-          server-521404  [006] ..... 137020.679890: binder_transaction_received: transaction=5161
-          server-521404  [006] ..... 137020.679890: binder_return: cmd=0x80407202 BR_TRANSACTION
-          """),
-        query="""
-      SELECT
-        dur
-      FROM slice
-      ORDER BY dur;
-      """,
-        out=Csv("""
-      "dur"
-      0
-      0
-      """))
-
-  def test_binder_metric(self):
-    return DiffTestBlueprint(
-        trace=DataPath('android_binder_metric_trace.atr'),
-        query=Metric('android_binder'),
-        out=Path('android_binder_metric.out'))
-
-  def test_android_blocking_calls_cuj(self):
-    return DiffTestBlueprint(
-        trace=Path('android_blocking_calls_cuj_metric.py'),
-        query=Metric('android_blocking_calls_cuj_metric'),
-        out=Path('android_blocking_calls_cuj_metric.out'))
-
-  def test_android_blocking_calls_on_jank_cujs(self):
-    return DiffTestBlueprint(
-        trace=Path('../graphics/android_jank_cuj.py'),
-        query=Metric('android_blocking_calls_cuj_metric'),
-        out=Path('android_blocking_calls_on_jank_cuj_metric.out'))
-
   def test_android_slices_standardization_for_aggregation(self):
     return DiffTestBlueprint(
         trace=Path('android_slice_standardization.py'),
@@ -511,12 +225,6 @@
         """,
         out=Path('android_slice_standardization.out'))
 
-  def test_android_sysui_notifications_blocking_calls(self):
-    return DiffTestBlueprint(
-        trace=Path('android_sysui_notifications_blocking_calls_metric.py'),
-        query=Metric('android_sysui_notifications_blocking_calls_metric'),
-        out=Path('android_sysui_notifications_blocking_calls_metric.out'))
-
   def test_monitor_contention_extraction(self):
     return DiffTestBlueprint(
         trace=DataPath('android_monitor_contention_trace.atr'),
@@ -587,47 +295,6 @@
         949,"void com.android.server.am.ActivityManagerService$AppDeathRecipient.binderDied()","int com.android.server.am.ActivityManagerService.getMemoryTrimLevel()","com.android.server.am.ActivityManagerService$AppDeathRecipient.binderDied","com.android.server.am.ActivityManagerService.getMemoryTrimLevel","ActivityManagerService.java:1478","ActivityManagerService.java:9183",1,250,"system_server",656,"binder:642_12",2720,250,"system_server",956,1737123891932,17577143,1215,1,642,0,2720,"[NULL]","[NULL]","[NULL]",642,"[NULL]"
       """))
 
-  def test_monitor_contention_metric(self):
-    return DiffTestBlueprint(
-        trace=DataPath('android_monitor_contention_trace.atr'),
-        query=Metric('android_monitor_contention'),
-        out=Path('android_monitor_contention.out'))
-
-  def test_monitor_contention_agg_metric(self):
-    return DiffTestBlueprint(
-        trace=DataPath('android_monitor_contention_trace.atr'),
-        query=Metric('android_monitor_contention_agg'),
-        out=TextProto(r"""
-        android_monitor_contention_agg {
-          process_aggregation {
-            name: "android.process.media"
-            total_contention_count: 12
-            total_contention_dur: 12893198
-            main_thread_contention_count: 12
-            main_thread_contention_dur: 12893198
-          }
-          process_aggregation {
-            name: "com.android.providers.media.module"
-            total_contention_count: 7
-            total_contention_dur: 169793
-          }
-          process_aggregation {
-            name: "com.android.systemui"
-            total_contention_count: 8
-            total_contention_dur: 9445959
-            main_thread_contention_count: 5
-            main_thread_contention_dur: 9228582
-          }
-          process_aggregation {
-            name: "system_server"
-            total_contention_count: 354
-            total_contention_dur: 358898613
-            main_thread_contention_count: 27
-            main_thread_contention_dur: 36904702
-          }
-        }
-        """))
-
   def test_monitor_contention_graph(self):
     return DiffTestBlueprint(
         trace=DataPath('android_monitor_contention_trace.atr'),
@@ -1173,16 +840,3 @@
         /system/bin/servicemanager (0x0)
         /system/bin/storaged (0x0)
         """))
-
-  def test_android_boot(self):
-    return DiffTestBlueprint(
-        trace=DataPath('android_boot.pftrace'),
-        query=Metric('android_boot'),
-        out=TextProto(r"""
-        android_boot {
-          system_server_durations {
-            total_dur: 267193980530
-            uninterruptible_sleep_dur: 3843119529
-          }
-        }
-        """))
diff --git a/test/trace_processor/diff_tests/chrome/chrome_scroll_check.py b/test/trace_processor/diff_tests/stdlib/chrome/chrome_scroll_check.py
similarity index 99%
rename from test/trace_processor/diff_tests/chrome/chrome_scroll_check.py
rename to test/trace_processor/diff_tests/stdlib/chrome/chrome_scroll_check.py
index 5b93959..909ea1d 100644
--- a/test/trace_processor/diff_tests/chrome/chrome_scroll_check.py
+++ b/test/trace_processor/diff_tests/stdlib/chrome/chrome_scroll_check.py
@@ -22,6 +22,7 @@
 import synth_common
 
 from synth_common import ms_to_ns
+
 trace = synth_common.create_trace()
 
 from chrome_scroll_helper import ChromeScrollHelper
diff --git a/test/trace_processor/diff_tests/chrome/chrome_scroll_helper.py b/test/trace_processor/diff_tests/stdlib/chrome/chrome_scroll_helper.py
similarity index 99%
copy from test/trace_processor/diff_tests/chrome/chrome_scroll_helper.py
copy to test/trace_processor/diff_tests/stdlib/chrome/chrome_scroll_helper.py
index 7b7cee1..77b6e05 100644
--- a/test/trace_processor/diff_tests/chrome/chrome_scroll_helper.py
+++ b/test/trace_processor/diff_tests/stdlib/chrome/chrome_scroll_helper.py
@@ -20,6 +20,7 @@
 import synth_common
 
 from synth_common import ms_to_ns
+
 trace = synth_common.create_trace()
 
 
diff --git a/test/trace_processor/diff_tests/chrome/chrome_speedometer.out b/test/trace_processor/diff_tests/stdlib/chrome/chrome_speedometer.out
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/chrome_speedometer.out
rename to test/trace_processor/diff_tests/stdlib/chrome/chrome_speedometer.out
diff --git a/test/trace_processor/diff_tests/chrome/chrome_tasks.out b/test/trace_processor/diff_tests/stdlib/chrome/chrome_tasks.out
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/chrome_tasks.out
rename to test/trace_processor/diff_tests/stdlib/chrome/chrome_tasks.out
diff --git a/test/trace_processor/diff_tests/chrome/scroll_jank_v3.out b/test/trace_processor/diff_tests/stdlib/chrome/scroll_jank_v3.out
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/scroll_jank_v3.out
rename to test/trace_processor/diff_tests/stdlib/chrome/scroll_jank_v3.out
diff --git a/test/trace_processor/diff_tests/chrome/scroll_jank_v3_percentage.out b/test/trace_processor/diff_tests/stdlib/chrome/scroll_jank_v3_percentage.out
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/scroll_jank_v3_percentage.out
rename to test/trace_processor/diff_tests/stdlib/chrome/scroll_jank_v3_percentage.out
diff --git a/test/trace_processor/diff_tests/stdlib/chrome/tests.py b/test/trace_processor/diff_tests/stdlib/chrome/tests.py
new file mode 100644
index 0000000..cc709b1
--- /dev/null
+++ b/test/trace_processor/diff_tests/stdlib/chrome/tests.py
@@ -0,0 +1,162 @@
+#!/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 ChromeStdlib(TestSuite):
+  # Chrome tasks.
+  def test_chrome_tasks(self):
+    return DiffTestBlueprint(
+        trace=DataPath(
+            'chrome_page_load_all_categories_not_extended.pftrace.gz'),
+        query="""
+        INCLUDE PERFETTO MODULE chrome.tasks;
+
+        SELECT full_name as name, task_type, count() AS count
+        FROM chrome_tasks
+        GROUP BY full_name, task_type
+        HAVING count >= 5
+        ORDER BY count DESC, name;
+        """,
+        out=Path('chrome_tasks.out'))
+
+  def test_top_level_java_choreographer_slices_top_level_java_chrome_tasks(
+      self):
+    return DiffTestBlueprint(
+        trace=DataPath('top_level_java_choreographer_slices'),
+        query="""
+        INCLUDE PERFETTO MODULE chrome.tasks;
+
+        SELECT
+          full_name,
+          task_type
+        FROM chrome_tasks
+        WHERE category = "toplevel,Java"
+        AND ts < 263904000000000
+        GROUP BY full_name, task_type;
+        """,
+        out=Path(
+            'top_level_java_choreographer_slices_top_level_java_chrome_tasks_test.out'
+        ))
+
+  # Chrome custom navigation event names
+  def test_chrome_custom_navigation_tasks(self):
+    return DiffTestBlueprint(
+        trace=DataPath('chrome_custom_navigation_trace.gz'),
+        query="""
+        INCLUDE PERFETTO MODULE chrome.tasks;
+
+        SELECT full_name, task_type, count() AS count
+        FROM chrome_tasks
+        WHERE full_name GLOB 'FrameHost::BeginNavigation*'
+          OR full_name GLOB 'FrameHost::DidCommitProvisionalLoad*'
+          OR full_name GLOB 'FrameHost::DidCommitSameDocumentNavigation*'
+          OR full_name GLOB 'FrameHost::DidStopLoading*'
+        GROUP BY full_name, task_type
+        ORDER BY count DESC
+        LIMIT 50;
+        """,
+        out=Csv("""
+        "full_name","task_type","count"
+        "FrameHost::BeginNavigation (SUBFRAME)","navigation_task",5
+        "FrameHost::DidStopLoading (SUBFRAME)","navigation_task",3
+        "FrameHost::BeginNavigation (PRIMARY_MAIN_FRAME)","navigation_task",1
+        "FrameHost::DidCommitProvisionalLoad (SUBFRAME)","navigation_task",1
+        """))
+
+  # Chrome custom navigation event names
+  def test_chrome_histograms(self):
+    return DiffTestBlueprint(
+        trace=DataPath('chrome_5672_histograms.pftrace.gz'),
+        query="""
+        INCLUDE PERFETTO MODULE chrome.histograms;
+
+        SELECT
+          name,
+          count() as count
+        FROM chrome_histograms
+        GROUP BY name
+        ORDER BY count DESC, name
+        LIMIT 20;
+        """,
+        out=Csv("""
+        "name","count"
+        "Net.QuicSession.AsyncRead",19207
+        "Net.QuicSession.NumQueuedPacketsBeforeWrite",19193
+        "RendererScheduler.QueueingDuration.NormalPriority",9110
+        "Net.OnTransferSizeUpdated.Experimental.OverridenBy",8525
+        "Compositing.Renderer.AnimationUpdateOnMissingPropertyNode",3489
+        "Net.QuicConnection.WritePacketStatus",3099
+        "Net.QuicSession.PacketWriteTime.Synchronous",3082
+        "Net.QuicSession.SendPacketSize.ForwardSecure",3012
+        "Net.URLLoaderThrottleExecutionTime.WillStartRequest",1789
+        "Net.URLLoaderThrottleExecutionTime.BeforeWillProcessResponse",1773
+        "Net.URLLoaderThrottleExecutionTime.WillProcessResponse",1773
+        "UMA.StackProfiler.SampleInOrder",1534
+        "GPU.SharedImage.ContentConsumed",1037
+        "Gpu.Rasterization.Raster.MSAASampleCountLog2",825
+        "Scheduling.Renderer.DeadlineMode",637
+        "Blink.CullRect.UpdateTime",622
+        "Scheduling.Renderer.BeginImplFrameLatency2",591
+        "Net.QuicSession.CoalesceStreamFrameStatus",551
+        "API.StorageAccess.AllowedRequests2",541
+        "Net.HttpResponseCode",541
+        """))
+
+  def test_speedometer(self):
+    return DiffTestBlueprint(
+        trace=DataPath('speedometer.perfetto_trace.gz'),
+        query="""
+        INCLUDE PERFETTO MODULE chrome.speedometer;
+
+        SELECT
+          iteration,
+          ts,
+          dur,
+          total,
+          format('%.1f', mean) AS mean,
+          format('%.1f', geomean) AS geomean,
+          format('%.1f', score) AS score,
+          num_measurements
+        FROM
+          chrome_speedometer_iteration,
+          (
+            SELECT iteration, COUNT(*) AS num_measurements
+            FROM chrome_speedometer_measure
+            GROUP BY iteration
+          )
+        USING (iteration)
+        ORDER BY iteration;
+        """,
+        out=Path('chrome_speedometer.out'))
+
+  # CPU power ups
+  def test_cpu_powerups(self):
+    return DiffTestBlueprint(
+        trace=DataPath('cpu_powerups_1.pb'),
+        query="""
+        INCLUDE PERFETTO MODULE chrome.cpu_powerups;
+        SELECT * FROM chrome_cpu_power_first_toplevel_slice_after_powerup;
+        """,
+        out=Csv("""
+        "slice_id","previous_power_state"
+        424,2
+        703,2
+        708,2
+        """))
\ No newline at end of file
diff --git a/test/trace_processor/diff_tests/stdlib/chrome/tests_chrome_interactions.py b/test/trace_processor/diff_tests/stdlib/chrome/tests_chrome_interactions.py
new file mode 100644
index 0000000..5019e54
--- /dev/null
+++ b/test/trace_processor/diff_tests/stdlib/chrome/tests_chrome_interactions.py
@@ -0,0 +1,50 @@
+#!/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 ChromeInteractions(TestSuite):
+  def test_chrome_fcp_lcp_navigations(self):
+    return DiffTestBlueprint(
+        trace=DataPath('chrome_fcp_lcp_navigations.pftrace'),
+        query="""
+        INCLUDE PERFETTO MODULE chrome.page_loads;
+
+        SELECT
+          navigation_id,
+          navigation_start_ts,
+          fcp,
+          fcp_ts,
+          lcp,
+          lcp_ts,
+          browser_upid
+        FROM chrome_page_loads
+        ORDER by navigation_start_ts;
+        """,
+        out=Csv("""
+        "navigation_id","navigation_start_ts","fcp","fcp_ts","lcp","lcp_ts","browser_upid"
+        6,687425601436243,950000000,687426551436243,950000000,687426551436243,1
+        7,687427799068243,888000000,687428687068243,888000000,687428687068243,1
+        8,687429970749243,1031000000,687431001749243,1132000000,687431102749243,1
+        9,687432344113243,539000000,687432883113243,539000000,687432883113243,1
+        10,687434796215243,475000000,687435271215243,475000000,687435271215243,1
+        11,687435970742243,763000000,687436733742243,852000000,687436822742243,1
+        13,687438343638243,1005000000,687439348638243,1005000000,687439348638243,1
+        14,687440258111243,900000000,687441158111243,"[NULL]",0,1
+        """))
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
new file mode 100644
index 0000000..c8e8f56
--- /dev/null
+++ b/test/trace_processor/diff_tests/stdlib/chrome/tests_scroll_jank.py
@@ -0,0 +1,140 @@
+#!/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 ChromeScrollJankStdlib(TestSuite):
+
+  def test_chrome_frames_with_missed_vsyncs(self):
+    return DiffTestBlueprint(
+        trace=DataPath('chrome_input_with_frame_view.pftrace'),
+        query="""
+        INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_jank_v3;
+
+        SELECT
+          cause_of_jank,
+          sub_cause_of_jank,
+          delay_since_last_frame,
+          vsync_interval
+        FROM chrome_janky_frames;
+        """,
+        out=Path('scroll_jank_v3.out'))
+
+  def test_chrome_frames_with_missed_vsyncs_percentage(self):
+    return DiffTestBlueprint(
+        trace=DataPath('chrome_input_with_frame_view.pftrace'),
+        query="""
+        INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_jank_v3;
+
+        SELECT
+          delayed_frame_percentage
+        FROM chrome_janky_frames_percentage;
+        """,
+        out=Path('scroll_jank_v3_percentage.out'))
+
+  def test_chrome_scrolls(self):
+    return DiffTestBlueprint(
+        trace=Path('chrome_scroll_check.py'),
+        query="""
+        INCLUDE PERFETTO MODULE chrome.chrome_scrolls;
+
+        SELECT
+          id,
+          ts,
+          dur,
+          gesture_scroll_begin_ts,
+          gesture_scroll_end_ts
+        FROM chrome_scrolls
+        ORDER by id;
+        """,
+        out=Csv("""
+        "id","ts","dur","gesture_scroll_begin_ts","gesture_scroll_end_ts"
+        5678,0,55000000,0,45000000
+        5679,60000000,40000000,60000000,90000000
+        5680,80000000,30000000,80000000,100000000
+        5681,120000000,70000000,120000000,"[NULL]"
+        """))
+
+  def test_chrome_scroll_intervals(self):
+    return DiffTestBlueprint(
+        trace=Path('chrome_scroll_check.py'),
+        query="""
+        INCLUDE PERFETTO MODULE chrome.chrome_scrolls;
+
+        SELECT
+          id,
+          ts,
+          dur
+        FROM chrome_scrolling_intervals
+        ORDER by id;
+        """,
+        out=Csv("""
+        "id","ts","dur"
+        1,0,55000000
+        2,60000000,50000000
+        3,120000000,70000000
+        """))
+
+  def test_chrome_scroll_input_offsets(self):
+    return DiffTestBlueprint(
+        trace=DataPath('scroll_offsets.pftrace'),
+        query="""
+        INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_offsets;
+
+        SELECT
+          scroll_update_id,
+          ts,
+          delta_y,
+          offset_y
+        FROM chrome_scroll_input_offsets
+        ORDER by ts
+        LIMIT 5;
+        """,
+        out=Csv("""
+        "scroll_update_id","ts","delta_y","offset_y"
+        1983,4687296612739,-36.999939,-36.999939
+        1983,4687307175845,-39.000092,-76.000031
+        1987,4687313206739,-35.999969,-112.000000
+        1987,4687323152462,-35.000000,-147.000000
+        1991,4687329240739,-28.999969,-175.999969
+        """))
+
+  def test_chrome_presented_scroll_offsets(self):
+    return DiffTestBlueprint(
+        trace=DataPath('scroll_offsets.pftrace'),
+        query="""
+        INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_offsets;
+
+        SELECT
+          scroll_update_id,
+          ts,
+          delta_y,
+          offset_y
+        FROM chrome_presented_scroll_offsets
+        ORDER by ts
+        LIMIT 5;
+        """,
+        out=Csv("""
+        "scroll_update_id","ts","delta_y","offset_y"
+        1983,4687296612739,"[NULL]",0
+        1987,4687313206739,-50,-50
+        1991,4687329240739,-50,-100
+        1993,4687336155739,-81,-181
+        1996,4687346164739,-66,-247
+        """))
diff --git a/test/trace_processor/diff_tests/chrome/top_level_java_choreographer_slices_top_level_java_chrome_tasks_test.out b/test/trace_processor/diff_tests/stdlib/chrome/top_level_java_choreographer_slices_top_level_java_chrome_tasks_test.out
similarity index 100%
rename from test/trace_processor/diff_tests/chrome/top_level_java_choreographer_slices_top_level_java_chrome_tasks_test.out
rename to test/trace_processor/diff_tests/stdlib/chrome/top_level_java_choreographer_slices_top_level_java_chrome_tasks_test.out
diff --git a/test/trace_processor/diff_tests/stdlib/common/tests.py b/test/trace_processor/diff_tests/stdlib/common/tests.py
new file mode 100644
index 0000000..a902b5b
--- /dev/null
+++ b/test/trace_processor/diff_tests/stdlib/common/tests.py
@@ -0,0 +1,48 @@
+#!/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_thread_state_summary(self):
+    return DiffTestBlueprint(
+        trace=Path('../../common/synth_1.py'),
+        query="""
+        INCLUDE PERFETTO MODULE common.thread_states;
+
+        SELECT
+          state,
+          cpu,
+          dur
+        FROM thread_state_summary_for_interval(
+          25,
+          75,
+          (
+            SELECT utid
+            FROM thread
+            WHERE name = 'init'
+          )
+        )
+        """,
+        out=Csv("""
+        "state","cpu","dur"
+        "Running",1,50
+        "Runnable","[NULL]",25
+        """))
\ No newline at end of file
diff --git a/test/trace_processor/diff_tests/dynamic/ancestor_slice.out b/test/trace_processor/diff_tests/stdlib/dynamic_tables/ancestor_slice.out
similarity index 100%
rename from test/trace_processor/diff_tests/dynamic/ancestor_slice.out
rename to test/trace_processor/diff_tests/stdlib/dynamic_tables/ancestor_slice.out
diff --git a/test/trace_processor/diff_tests/dynamic/connected_flow.out b/test/trace_processor/diff_tests/stdlib/dynamic_tables/connected_flow.out
similarity index 100%
rename from test/trace_processor/diff_tests/dynamic/connected_flow.out
rename to test/trace_processor/diff_tests/stdlib/dynamic_tables/connected_flow.out
diff --git a/test/trace_processor/diff_tests/dynamic/connected_flow_data.json b/test/trace_processor/diff_tests/stdlib/dynamic_tables/connected_flow_data.json
similarity index 100%
rename from test/trace_processor/diff_tests/dynamic/connected_flow_data.json
rename to test/trace_processor/diff_tests/stdlib/dynamic_tables/connected_flow_data.json
diff --git a/test/trace_processor/diff_tests/dynamic/connected_flow_test.sql b/test/trace_processor/diff_tests/stdlib/dynamic_tables/connected_flow_test.sql
similarity index 100%
rename from test/trace_processor/diff_tests/dynamic/connected_flow_test.sql
rename to test/trace_processor/diff_tests/stdlib/dynamic_tables/connected_flow_test.sql
diff --git a/test/trace_processor/diff_tests/dynamic/descendant_slice.out b/test/trace_processor/diff_tests/stdlib/dynamic_tables/descendant_slice.out
similarity index 100%
rename from test/trace_processor/diff_tests/dynamic/descendant_slice.out
rename to test/trace_processor/diff_tests/stdlib/dynamic_tables/descendant_slice.out
diff --git a/test/trace_processor/diff_tests/dynamic/perf_sample_sc_annotated_callstack.out b/test/trace_processor/diff_tests/stdlib/dynamic_tables/perf_sample_sc_annotated_callstack.out
similarity index 100%
rename from test/trace_processor/diff_tests/dynamic/perf_sample_sc_annotated_callstack.out
rename to test/trace_processor/diff_tests/stdlib/dynamic_tables/perf_sample_sc_annotated_callstack.out
diff --git a/test/trace_processor/diff_tests/dynamic/relationship_tables.textproto b/test/trace_processor/diff_tests/stdlib/dynamic_tables/relationship_tables.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/dynamic/relationship_tables.textproto
rename to test/trace_processor/diff_tests/stdlib/dynamic_tables/relationship_tables.textproto
diff --git a/test/trace_processor/diff_tests/dynamic/slice_stacks.textproto b/test/trace_processor/diff_tests/stdlib/dynamic_tables/slice_stacks.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/dynamic/slice_stacks.textproto
rename to test/trace_processor/diff_tests/stdlib/dynamic_tables/slice_stacks.textproto
diff --git a/test/trace_processor/diff_tests/dynamic/tests.py b/test/trace_processor/diff_tests/stdlib/dynamic_tables/tests.py
similarity index 99%
rename from test/trace_processor/diff_tests/dynamic/tests.py
rename to test/trace_processor/diff_tests/stdlib/dynamic_tables/tests.py
index 3c79262..60b170c 100644
--- a/test/trace_processor/diff_tests/dynamic/tests.py
+++ b/test/trace_processor/diff_tests/stdlib/dynamic_tables/tests.py
@@ -19,7 +19,7 @@
 from python.generators.diff_tests.testing import TestSuite
 
 
-class Dynamic(TestSuite):
+class DynamicTables(TestSuite):
   # Tests for custom dynamic tables. Ancestor slice table.
   def test_ancestor_slice(self):
     return DiffTestBlueprint(
diff --git a/test/trace_processor/diff_tests/dynamic/various_clocks.textproto b/test/trace_processor/diff_tests/stdlib/dynamic_tables/various_clocks.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/dynamic/various_clocks.textproto
rename to test/trace_processor/diff_tests/stdlib/dynamic_tables/various_clocks.textproto
diff --git a/test/trace_processor/diff_tests/pkvm/pkvm_hypervisor_events.textproto b/test/trace_processor/diff_tests/stdlib/pkvm/pkvm_hypervisor_events.textproto
similarity index 100%
rename from test/trace_processor/diff_tests/pkvm/pkvm_hypervisor_events.textproto
rename to test/trace_processor/diff_tests/stdlib/pkvm/pkvm_hypervisor_events.textproto
diff --git a/test/trace_processor/diff_tests/pkvm/tests.py b/test/trace_processor/diff_tests/stdlib/pkvm/tests.py
similarity index 100%
rename from test/trace_processor/diff_tests/pkvm/tests.py
rename to test/trace_processor/diff_tests/stdlib/pkvm/tests.py
diff --git a/test/trace_processor/diff_tests/slices/tests.py b/test/trace_processor/diff_tests/stdlib/slices/tests.py
similarity index 100%
rename from test/trace_processor/diff_tests/slices/tests.py
rename to test/trace_processor/diff_tests/stdlib/slices/tests.py
diff --git a/test/trace_processor/diff_tests/slices/trace.py b/test/trace_processor/diff_tests/stdlib/slices/trace.py
similarity index 100%
rename from test/trace_processor/diff_tests/slices/trace.py
rename to test/trace_processor/diff_tests/stdlib/slices/trace.py
diff --git a/test/trace_processor/diff_tests/span_join/android_sched_and_ps_slice_span_join_b118665515.out b/test/trace_processor/diff_tests/stdlib/span_join/android_sched_and_ps_slice_span_join_b118665515.out
similarity index 100%
rename from test/trace_processor/diff_tests/span_join/android_sched_and_ps_slice_span_join_b118665515.out
rename to test/trace_processor/diff_tests/stdlib/span_join/android_sched_and_ps_slice_span_join_b118665515.out
diff --git a/test/trace_processor/diff_tests/span_join/slice_span_join_b118665515_test.sql b/test/trace_processor/diff_tests/stdlib/span_join/slice_span_join_b118665515_test.sql
similarity index 100%
rename from test/trace_processor/diff_tests/span_join/slice_span_join_b118665515_test.sql
rename to test/trace_processor/diff_tests/stdlib/span_join/slice_span_join_b118665515_test.sql
diff --git a/test/trace_processor/diff_tests/span_join/span_join_unordered_cols_reverse_test.sql b/test/trace_processor/diff_tests/stdlib/span_join/span_join_unordered_cols_reverse_test.sql
similarity index 100%
rename from test/trace_processor/diff_tests/span_join/span_join_unordered_cols_reverse_test.sql
rename to test/trace_processor/diff_tests/stdlib/span_join/span_join_unordered_cols_reverse_test.sql
diff --git a/test/trace_processor/diff_tests/span_join/span_join_unordered_cols_test.sql b/test/trace_processor/diff_tests/stdlib/span_join/span_join_unordered_cols_test.sql
similarity index 100%
rename from test/trace_processor/diff_tests/span_join/span_join_unordered_cols_test.sql
rename to test/trace_processor/diff_tests/stdlib/span_join/span_join_unordered_cols_test.sql
diff --git a/test/trace_processor/diff_tests/span_join/span_join_zero_negative_dur_test.sql b/test/trace_processor/diff_tests/stdlib/span_join/span_join_zero_negative_dur_test.sql
similarity index 100%
rename from test/trace_processor/diff_tests/span_join/span_join_zero_negative_dur_test.sql
rename to test/trace_processor/diff_tests/stdlib/span_join/span_join_zero_negative_dur_test.sql
diff --git a/test/trace_processor/diff_tests/span_join/span_left_join.out b/test/trace_processor/diff_tests/stdlib/span_join/span_left_join.out
similarity index 100%
rename from test/trace_processor/diff_tests/span_join/span_left_join.out
rename to test/trace_processor/diff_tests/stdlib/span_join/span_left_join.out
diff --git a/test/trace_processor/diff_tests/span_join/span_left_join_left_partitioned.out b/test/trace_processor/diff_tests/stdlib/span_join/span_left_join_left_partitioned.out
similarity index 100%
rename from test/trace_processor/diff_tests/span_join/span_left_join_left_partitioned.out
rename to test/trace_processor/diff_tests/stdlib/span_join/span_left_join_left_partitioned.out
diff --git a/test/trace_processor/diff_tests/span_join/span_left_join_left_partitioned_test.sql b/test/trace_processor/diff_tests/stdlib/span_join/span_left_join_left_partitioned_test.sql
similarity index 100%
rename from test/trace_processor/diff_tests/span_join/span_left_join_left_partitioned_test.sql
rename to test/trace_processor/diff_tests/stdlib/span_join/span_left_join_left_partitioned_test.sql
diff --git a/test/trace_processor/diff_tests/span_join/span_left_join_left_unpartitioned.out b/test/trace_processor/diff_tests/stdlib/span_join/span_left_join_left_unpartitioned.out
similarity index 100%
rename from test/trace_processor/diff_tests/span_join/span_left_join_left_unpartitioned.out
rename to test/trace_processor/diff_tests/stdlib/span_join/span_left_join_left_unpartitioned.out
diff --git a/test/trace_processor/diff_tests/span_join/span_left_join_left_unpartitioned_test.sql b/test/trace_processor/diff_tests/stdlib/span_join/span_left_join_left_unpartitioned_test.sql
similarity index 100%
rename from test/trace_processor/diff_tests/span_join/span_left_join_left_unpartitioned_test.sql
rename to test/trace_processor/diff_tests/stdlib/span_join/span_left_join_left_unpartitioned_test.sql
diff --git a/test/trace_processor/diff_tests/span_join/span_left_join_test.sql b/test/trace_processor/diff_tests/stdlib/span_join/span_left_join_test.sql
similarity index 100%
rename from test/trace_processor/diff_tests/span_join/span_left_join_test.sql
rename to test/trace_processor/diff_tests/stdlib/span_join/span_left_join_test.sql
diff --git a/test/trace_processor/diff_tests/span_join/span_left_join_unpartitioned.out b/test/trace_processor/diff_tests/stdlib/span_join/span_left_join_unpartitioned.out
similarity index 100%
rename from test/trace_processor/diff_tests/span_join/span_left_join_unpartitioned.out
rename to test/trace_processor/diff_tests/stdlib/span_join/span_left_join_unpartitioned.out
diff --git a/test/trace_processor/diff_tests/span_join/span_left_join_unpartitioned_test.sql b/test/trace_processor/diff_tests/stdlib/span_join/span_left_join_unpartitioned_test.sql
similarity index 100%
rename from test/trace_processor/diff_tests/span_join/span_left_join_unpartitioned_test.sql
rename to test/trace_processor/diff_tests/stdlib/span_join/span_left_join_unpartitioned_test.sql
diff --git a/test/trace_processor/diff_tests/span_join/span_outer_join.out b/test/trace_processor/diff_tests/stdlib/span_join/span_outer_join.out
similarity index 100%
rename from test/trace_processor/diff_tests/span_join/span_outer_join.out
rename to test/trace_processor/diff_tests/stdlib/span_join/span_outer_join.out
diff --git a/test/trace_processor/diff_tests/span_join/span_outer_join_mixed.out b/test/trace_processor/diff_tests/stdlib/span_join/span_outer_join_mixed.out
similarity index 100%
rename from test/trace_processor/diff_tests/span_join/span_outer_join_mixed.out
rename to test/trace_processor/diff_tests/stdlib/span_join/span_outer_join_mixed.out
diff --git a/test/trace_processor/diff_tests/span_join/span_outer_join_mixed_test.sql b/test/trace_processor/diff_tests/stdlib/span_join/span_outer_join_mixed_test.sql
similarity index 100%
rename from test/trace_processor/diff_tests/span_join/span_outer_join_mixed_test.sql
rename to test/trace_processor/diff_tests/stdlib/span_join/span_outer_join_mixed_test.sql
diff --git a/test/trace_processor/diff_tests/span_join/span_outer_join_test.sql b/test/trace_processor/diff_tests/stdlib/span_join/span_outer_join_test.sql
similarity index 100%
rename from test/trace_processor/diff_tests/span_join/span_outer_join_test.sql
rename to test/trace_processor/diff_tests/stdlib/span_join/span_outer_join_test.sql
diff --git a/test/trace_processor/diff_tests/span_join/span_outer_join_unpartitioned.out b/test/trace_processor/diff_tests/stdlib/span_join/span_outer_join_unpartitioned.out
similarity index 100%
rename from test/trace_processor/diff_tests/span_join/span_outer_join_unpartitioned.out
rename to test/trace_processor/diff_tests/stdlib/span_join/span_outer_join_unpartitioned.out
diff --git a/test/trace_processor/diff_tests/span_join/span_outer_join_unpartitioned_test.sql b/test/trace_processor/diff_tests/stdlib/span_join/span_outer_join_unpartitioned_test.sql
similarity index 100%
rename from test/trace_processor/diff_tests/span_join/span_outer_join_unpartitioned_test.sql
rename to test/trace_processor/diff_tests/stdlib/span_join/span_outer_join_unpartitioned_test.sql
diff --git a/test/trace_processor/diff_tests/span_join/tests_left_join.py b/test/trace_processor/diff_tests/stdlib/span_join/tests_left_join.py
similarity index 91%
rename from test/trace_processor/diff_tests/span_join/tests_left_join.py
rename to test/trace_processor/diff_tests/stdlib/span_join/tests_left_join.py
index 2bd2e58..30fd6ce 100644
--- a/test/trace_processor/diff_tests/span_join/tests_left_join.py
+++ b/test/trace_processor/diff_tests/stdlib/span_join/tests_left_join.py
@@ -23,31 +23,31 @@
 
   def test_span_left_join(self):
     return DiffTestBlueprint(
-        trace=Path('../common/synth_1.py'),
+        trace=Path('../../common/synth_1.py'),
         query=Path('span_left_join_test.sql'),
         out=Path('span_left_join.out'))
 
   def test_span_left_join_unpartitioned(self):
     return DiffTestBlueprint(
-        trace=Path('../common/synth_1.py'),
+        trace=Path('../../common/synth_1.py'),
         query=Path('span_left_join_unpartitioned_test.sql'),
         out=Path('span_left_join_unpartitioned.out'))
 
   def test_span_left_join_left_unpartitioned(self):
     return DiffTestBlueprint(
-        trace=Path('../common/synth_1.py'),
+        trace=Path('../../common/synth_1.py'),
         query=Path('span_left_join_left_unpartitioned_test.sql'),
         out=Path('span_left_join_left_unpartitioned.out'))
 
   def test_span_left_join_left_partitioned(self):
     return DiffTestBlueprint(
-        trace=Path('../common/synth_1.py'),
+        trace=Path('../../common/synth_1.py'),
         query=Path('span_left_join_left_partitioned_test.sql'),
         out=Path('span_left_join_left_partitioned.out'))
 
   def test_span_left_join_empty_right(self):
     return DiffTestBlueprint(
-        trace=Path('../common/synth_1.py'),
+        trace=Path('../../common/synth_1.py'),
         query="""
         CREATE TABLE t1(
           ts BIGINT,
@@ -79,7 +79,7 @@
 
   def test_span_left_join_unordered_android_sched_and_ps(self):
     return DiffTestBlueprint(
-        trace=Path('../common/synth_1.py'),
+        trace=Path('../../common/synth_1.py'),
         query="""
         CREATE TABLE t1(
           ts BIGINT,
diff --git a/test/trace_processor/diff_tests/span_join/tests_outer_join.py b/test/trace_processor/diff_tests/stdlib/span_join/tests_outer_join.py
similarity index 92%
rename from test/trace_processor/diff_tests/span_join/tests_outer_join.py
rename to test/trace_processor/diff_tests/stdlib/span_join/tests_outer_join.py
index c2ac4f5..d0354ca 100644
--- a/test/trace_processor/diff_tests/span_join/tests_outer_join.py
+++ b/test/trace_processor/diff_tests/stdlib/span_join/tests_outer_join.py
@@ -23,13 +23,13 @@
 
   def test_span_outer_join(self):
     return DiffTestBlueprint(
-        trace=Path('../common/synth_1.py'),
+        trace=Path('../../common/synth_1.py'),
         query=Path('span_outer_join_test.sql'),
         out=Path('span_outer_join.out'))
 
   def test_span_outer_join_empty(self):
     return DiffTestBlueprint(
-        trace=Path('../common/synth_1.py'),
+        trace=Path('../../common/synth_1.py'),
         query="""
         CREATE TABLE t1(
           ts BIGINT,
@@ -61,7 +61,7 @@
 
   def test_span_outer_join_unpartitioned_empty(self):
     return DiffTestBlueprint(
-        trace=Path('../common/synth_1.py'),
+        trace=Path('../../common/synth_1.py'),
         query="""
         CREATE TABLE t1(
           ts BIGINT,
@@ -86,7 +86,7 @@
 
   def test_span_outer_join_unpartitioned_left_empty(self):
     return DiffTestBlueprint(
-        trace=Path('../common/synth_1.py'),
+        trace=Path('../../common/synth_1.py'),
         query="""
         CREATE TABLE t1(
           ts BIGINT,
@@ -119,7 +119,7 @@
 
   def test_span_outer_join_unpartitioned_right_empty(self):
     return DiffTestBlueprint(
-        trace=Path('../common/synth_1.py'),
+        trace=Path('../../common/synth_1.py'),
         query="""
         CREATE TABLE t1(
           ts BIGINT,
@@ -152,13 +152,13 @@
 
   def test_span_outer_join_mixed(self):
     return DiffTestBlueprint(
-        trace=Path('../common/synth_1.py'),
+        trace=Path('../../common/synth_1.py'),
         query=Path('span_outer_join_mixed_test.sql'),
         out=Path('span_outer_join_mixed.out'))
 
   def test_span_outer_join_mixed_empty(self):
     return DiffTestBlueprint(
-        trace=Path('../common/synth_1.py'),
+        trace=Path('../../common/synth_1.py'),
         query="""
         CREATE TABLE t1(
           ts BIGINT,
@@ -184,7 +184,7 @@
 
   def test_span_outer_join_mixed_left_empty(self):
     return DiffTestBlueprint(
-        trace=Path('../common/synth_1.py'),
+        trace=Path('../../common/synth_1.py'),
         query="""
         CREATE TABLE t1(
           ts BIGINT,
@@ -215,7 +215,7 @@
 
   def test_span_outer_join_mixed_left_empty_rev(self):
     return DiffTestBlueprint(
-        trace=Path('../common/synth_1.py'),
+        trace=Path('../../common/synth_1.py'),
         query="""
         CREATE TABLE t1(
           ts BIGINT,
@@ -249,7 +249,7 @@
 
   def test_span_outer_join_mixed_right_empty(self):
     return DiffTestBlueprint(
-        trace=Path('../common/synth_1.py'),
+        trace=Path('../../common/synth_1.py'),
         query="""
         CREATE TABLE t1(
           ts BIGINT,
@@ -284,7 +284,7 @@
 
   def test_span_outer_join_mixed_right_empty_rev(self):
     return DiffTestBlueprint(
-        trace=Path('../common/synth_1.py'),
+        trace=Path('../../common/synth_1.py'),
         query="""
         CREATE TABLE t1(
           ts BIGINT,
@@ -316,6 +316,6 @@
 
   def test_span_outer_join_mixed_2(self):
     return DiffTestBlueprint(
-        trace=Path('../common/synth_1.py'),
+        trace=Path('../../common/synth_1.py'),
         query=Path('span_outer_join_mixed_test.sql'),
         out=Path('span_outer_join_mixed.out'))
diff --git a/test/trace_processor/diff_tests/span_join/tests_regression.py b/test/trace_processor/diff_tests/stdlib/span_join/tests_regression.py
similarity index 100%
rename from test/trace_processor/diff_tests/span_join/tests_regression.py
rename to test/trace_processor/diff_tests/stdlib/span_join/tests_regression.py
diff --git a/test/trace_processor/diff_tests/span_join/tests_smoke.py b/test/trace_processor/diff_tests/stdlib/span_join/tests_smoke.py
similarity index 95%
rename from test/trace_processor/diff_tests/span_join/tests_smoke.py
rename to test/trace_processor/diff_tests/stdlib/span_join/tests_smoke.py
index e94a6c8..bacaa9a 100644
--- a/test/trace_processor/diff_tests/span_join/tests_smoke.py
+++ b/test/trace_processor/diff_tests/stdlib/span_join/tests_smoke.py
@@ -23,7 +23,7 @@
 
   def test_span_join_unordered_cols_synth_1(self):
     return DiffTestBlueprint(
-        trace=Path('../common/synth_1.py'),
+        trace=Path('../../common/synth_1.py'),
         query=Path('span_join_unordered_cols_test.sql'),
         out=Csv("""
         "ts","dur","part","b1","b2","b3","a1","a2","a3"
@@ -38,7 +38,7 @@
 
   def test_span_join_unordered_cols_synth_1_2(self):
     return DiffTestBlueprint(
-        trace=Path('../common/synth_1.py'),
+        trace=Path('../../common/synth_1.py'),
         query=Path('span_join_unordered_cols_reverse_test.sql'),
         out=Csv("""
         "ts","dur","part","b1","b2","b3","a1","a2","a3"
diff --git a/test/trace_processor/diff_tests/time/tests.py b/test/trace_processor/diff_tests/stdlib/timestamps/tests.py
similarity index 98%
rename from test/trace_processor/diff_tests/time/tests.py
rename to test/trace_processor/diff_tests/stdlib/timestamps/tests.py
index ee8771d..a029ce5 100644
--- a/test/trace_processor/diff_tests/time/tests.py
+++ b/test/trace_processor/diff_tests/stdlib/timestamps/tests.py
@@ -20,7 +20,7 @@
 from google.protobuf import text_format
 
 
-class Time(TestSuite):
+class Timestamps(TestSuite):
 
   def test_ns(self):
     return DiffTestBlueprint(
diff --git a/test/trace_processor/diff_tests/functions/tests.py b/test/trace_processor/diff_tests/syntax/functions/tests.py
similarity index 99%
rename from test/trace_processor/diff_tests/functions/tests.py
rename to test/trace_processor/diff_tests/syntax/functions/tests.py
index 966e87d..f613fb5 100644
--- a/test/trace_processor/diff_tests/functions/tests.py
+++ b/test/trace_processor/diff_tests/syntax/functions/tests.py
@@ -20,6 +20,7 @@
 from python.generators.diff_tests.testing import PrintProfileProto
 from google.protobuf import text_format
 
+
 class Functions(TestSuite):
 
   def test_create_function(self):
diff --git a/test/trace_processor/diff_tests/perfetto_sql/tests.py b/test/trace_processor/diff_tests/syntax/perfetto_sql/tests.py
similarity index 100%
rename from test/trace_processor/diff_tests/perfetto_sql/tests.py
rename to test/trace_processor/diff_tests/syntax/perfetto_sql/tests.py
diff --git a/test/trace_processor/diff_tests/tables/tests.py b/test/trace_processor/diff_tests/tables/tests.py
index 83775a3..a6ff813 100644
--- a/test/trace_processor/diff_tests/tables/tests.py
+++ b/test/trace_processor/diff_tests/tables/tests.py
@@ -266,18 +266,52 @@
 
   def test_thread_state_flattened_aggregated(self):
     return DiffTestBlueprint(
-      trace=DataPath('android_monitor_contention_trace.atr'),
-      query="""
+        trace=DataPath('android_monitor_contention_trace.atr'),
+        query="""
       INCLUDE PERFETTO MODULE experimental.thread_state_flattened;
       select * from experimental_get_flattened_thread_state_aggregated(11155, NULL);
       """,
-      out=Path('thread_state_flattened_aggregated_csv.out'))
+        out=Path('thread_state_flattened_aggregated_csv.out'))
 
   def test_thread_state_flattened(self):
     return DiffTestBlueprint(
-      trace=DataPath('android_monitor_contention_trace.atr'),
-      query="""
+        trace=DataPath('android_monitor_contention_trace.atr'),
+        query="""
       INCLUDE PERFETTO MODULE experimental.thread_state_flattened;
       select * from experimental_get_flattened_thread_state(11155, NULL);
       """,
-      out=Path('thread_state_flattened_csv.out'))
+        out=Path('thread_state_flattened_csv.out'))
+
+  def test_metadata(self):
+    return DiffTestBlueprint(
+        trace=TextProto(r"""
+        packet {
+          system_info {
+            tracing_service_version: "Perfetto v38.0-0bb49ab54 (0bb49ab54dbe55ce5b9dfea3a2ada68b87aecb65)"
+            timezone_off_mins: 60
+            utsname {
+              sysname: "Darwin"
+              version: "Foobar"
+              machine: "x86_64"
+              release: "22.6.0"
+            }
+          }
+          trusted_uid: 158158
+          trusted_packet_sequence_id: 1
+        }
+        """),
+        query=r"""SELECT name, COALESCE(str_value, int_value) as val
+              FROM metadata
+              WHERE name IN (
+                  "system_name", "system_version", "system_machine",
+                  "system_release", "timezone_off_mins")
+              ORDER BY name
+        """,
+        out=Csv(r"""
+                "name","val"
+                "system_machine","x86_64"
+                "system_name","Darwin"
+                "system_release","22.6.0"
+                "system_version","Foobar"
+                "timezone_off_mins",60
+                """))
diff --git a/test/trace_processor/diff_tests/tables/tests_sched.py b/test/trace_processor/diff_tests/tables/tests_sched.py
index 14e9f32..d2c4c67 100644
--- a/test/trace_processor/diff_tests/tables/tests_sched.py
+++ b/test/trace_processor/diff_tests/tables/tests_sched.py
@@ -303,20 +303,21 @@
           critical_path_utid
         FROM experimental_thread_executing_span_critical_path_stack((select utid from thread where tid = 3487), start_ts, end_ts), trace_bounds
         ORDER BY ts
-        LIMIT 10
+        LIMIT 11
         """,
         out=Csv("""
         "id","ts","dur","utid","stack_depth","name","table_name","critical_path_utid"
         11889,1737349401439,57188,1477,0,"thread_state: R","thread_state",1477
         11889,1737349401439,57188,1477,1,"[NULL]","thread_state",1477
-        11889,1737349401439,57188,1477,2,"process_name: com.android.providers.media.module","thread_state",1477
-        11889,1737349401439,57188,1477,3,"thread_name: rs.media.module","thread_state",1477
+        11889,1737349401439,57188,1477,2,"[NULL]","thread_state",1477
+        11889,1737349401439,57188,1477,3,"process_name: com.android.providers.media.module","thread_state",1477
+        11889,1737349401439,57188,1477,4,"thread_name: rs.media.module","thread_state",1477
         11891,1737349458627,1884896,1477,0,"thread_state: Running","thread_state",1477
         11891,1737349458627,1884896,1477,1,"[NULL]","thread_state",1477
-        11891,1737349458627,1884896,1477,2,"process_name: com.android.providers.media.module","thread_state",1477
-        11891,1737349458627,1884896,1477,3,"thread_name: rs.media.module","thread_state",1477
-        11891,1737349458627,1884896,1477,4,"cpu: 0","thread_state",1477
-        11891,1737351343523,2494,1477,0,"thread_state: Running","thread_state",1477
+        11891,1737349458627,1884896,1477,2,"[NULL]","thread_state",1477
+        11891,1737349458627,1884896,1477,3,"process_name: com.android.providers.media.module","thread_state",1477
+        11891,1737349458627,1884896,1477,4,"thread_name: rs.media.module","thread_state",1477
+        11891,1737349458627,1884896,1477,5,"cpu: 0","thread_state",1477
         """))
 
   def test_thread_executing_span_critical_path_graph(self):
diff --git a/tools/check_sql_metrics.py b/tools/check_sql_metrics.py
index ac70183..ea7c2ba 100755
--- a/tools/check_sql_metrics.py
+++ b/tools/check_sql_metrics.py
@@ -54,7 +54,16 @@
 }
 
 
-def match_pattern_to_dict(sql: str, pattern: str) -> Dict[str, Tuple[int, str]]:
+def match_create_table_pattern_to_dict(
+    sql: str, pattern: str) -> Dict[str, Tuple[int, str]]:
+  res = {}
+  for line_num, matches in match_pattern(pattern, sql).items():
+    res[matches[2]] = [line_num, str(matches[1])]
+  return res
+
+
+def match_drop_view_pattern_to_dict(sql: str,
+                                    pattern: str) -> Dict[str, Tuple[int, str]]:
   res = {}
   for line_num, matches in match_pattern(pattern, sql).items():
     res[matches[1]] = [line_num, str(matches[0])]
@@ -66,8 +75,10 @@
     sql = f.read()
 
   # Check that CREATE VIEW/TABLE has a matching DROP VIEW/TABLE before it.
-  create_table_view_dir = match_pattern_to_dict(sql, CREATE_TABLE_VIEW_PATTERN)
-  drop_table_view_dir = match_pattern_to_dict(sql, DROP_TABLE_VIEW_PATTERN)
+  create_table_view_dir = match_create_table_pattern_to_dict(
+      sql, CREATE_TABLE_VIEW_PATTERN)
+  drop_table_view_dir = match_drop_view_pattern_to_dict(
+      sql, DROP_TABLE_VIEW_PATTERN)
   errors = check_banned_create_table_as(sql,
                                         path.split(ROOT_DIR)[1],
                                         CREATE_TABLE_ALLOWLIST)
diff --git a/tools/gen_android_bp b/tools/gen_android_bp
index 552c7d3..2a06847 100755
--- a/tools/gen_android_bp
+++ b/tools/gen_android_bp
@@ -182,7 +182,6 @@
 # The directory where the generated perfetto_build_flags.h will be copied into.
 buildflags_dir = 'include/perfetto/base/build_configs/android_tree'
 
-
 def enumerate_data_deps():
   with open(os.path.join(ROOT_DIR, 'tools', 'test_data.txt')) as f:
     lines = f.readlines()
@@ -708,8 +707,11 @@
   cmd = ['mkdir -p %s &&' % cpp_out_dir, '$(location aprotoc)']
   cmd += ['--proto_path=%s' % tree_path]
 
+  tool_files = set()
   if buildtools_protobuf_src in target.proto_paths:
     cmd += ['--proto_path=%s' % android_protobuf_src]
+    # Add `google/protobuf/descriptor.proto` to implicit deps
+    tool_files.add(':libprotobuf-internal-descriptor-proto')
 
   # Descriptor targets only generate a single target.
   if target.proto_plugin == 'descriptor':
@@ -728,6 +730,12 @@
     # add them to srcs.
     descriptor_module.srcs.update(
         gn_utils.label_to_path(src) for src in target.sources)
+    # Add the tool_files to srcs so that they get copied if this action is
+    # sandboxed in Soong.
+    # Add to `srcs` instead of `tool_files` (the latter will require a
+    # --proto_path that depends on Soong's sandbox implementation.)
+    descriptor_module.srcs.update(
+        src for src in tool_files)
     for dep in target.transitive_proto_deps():
       current_target = gn.get_target(dep.name)
       descriptor_module.srcs.update(
@@ -740,11 +748,31 @@
   # generated files needs to declare two different types of dependencies --
   # source files in 'srcs' and headers in 'generated_headers' -- and it's not
   # valid to generate .h files from a source dependency and vice versa.
+  #
+  # We create an additional filegroup for .proto
+  # The .proto filegroup will be added to `tool_files` of rdeps so that the
+  # genrules can be sandboxed.
+
+  tool_files = set()
+  for proto_dep in target.proto_deps().union(target.transitive_proto_deps()):
+    tool_files.add(":" + label_to_module_name(proto_dep.name))
+
+  filegroup_module = Module('filegroup', target_module_name, target.name)
+  filegroup_module.srcs.update(
+      gn_utils.label_to_path(src) for src in target.sources)
+  blueprint.add_module(filegroup_module)
+
   source_module_name = target_module_name + '_gen'
   source_module = Module('genrule', source_module_name, target.name)
-  blueprint.add_module(source_module)
+  # Add the "root" .proto filegroup to srcs
+  source_module.srcs = set([':' + target_module_name])
+  # Add the tool_files to srcs so that they get copied if this action is
+  # sandboxed in Soong.
+  # Add to `srcs` instead of `tool_files` (the latter will require a
+  # --proto_path that depends on Soong's sandbox implementation.)
   source_module.srcs.update(
-      gn_utils.label_to_path(src) for src in target.sources)
+      src for src in tool_files)
+  blueprint.add_module(source_module)
 
   header_module = Module('genrule', source_module_name + '_headers',
                          target.name)
@@ -789,19 +817,20 @@
   else:
     raise Error('Unsupported proto plugin: %s' % target.proto_plugin)
 
-  cmd += ['$(in)']
+  cmd += ['$(locations :%s)' % target_module_name]
   source_module.cmd = ' '.join(cmd)
   header_module.cmd = source_module.cmd
   source_module.tools = tools
   header_module.tools = tools
 
+
   for sfx in suffixes:
     source_module.out.update('%s/%s' %
                              (tree_path, src.replace('.proto', '.%s.cc' % sfx))
-                             for src in source_module.srcs)
+                             for src in filegroup_module.srcs)
     header_module.out.update('%s/%s' %
                              (tree_path, src.replace('.proto', '.%s.h' % sfx))
-                             for src in header_module.srcs)
+                             for src in filegroup_module.srcs)
   return source_module
 
 
@@ -998,7 +1027,10 @@
   module.host_supported = (name_without_toolchain in target_host_supported)
   module.vendor_available = (name_without_toolchain in target_vendor_available)
   module.init_rc.update(target_initrc.get(target.name, []))
-  module.srcs.update(
+  if target.type != 'proto_library':
+    # proto_library embeds a "root" filegroup in its srcs.
+    # Skip to prevent adding dups
+    module.srcs.update(
       gn_utils.label_to_path(src)
       for src in target.sources
       if is_supported_source_file(src))
diff --git a/tools/gen_stdlib_docs_json.py b/tools/gen_stdlib_docs_json.py
index 27a1d06..f5f8e5d 100755
--- a/tools/gen_stdlib_docs_json.py
+++ b/tools/gen_stdlib_docs_json.py
@@ -83,20 +83,40 @@
             'name': table.name,
             'desc': table.desc,
             'type': table.type,
-            'cols': table.cols,
+            'cols': {
+                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,
-            'args': function.args,
+            'args': {
+                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,
-            'args': function.args,
-            'cols': function.cols,
+            '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()
+            },
         } for function in docs.table_functions],
     }
     modules[module_name].append(file_dict)
diff --git a/tools/heap_profile b/tools/heap_profile
index 6c2a399..cd6770b 100755
--- a/tools/heap_profile
+++ b/tools/heap_profile
@@ -754,7 +754,7 @@
     if binary_path is None:
       binary_path = product_out_symbols
     elif product_out_symbols is not None:
-      binary_path += ":" + product_out_symbols
+      binary_path += os.pathsep + product_out_symbols
 
   trace_file = os.path.join(profile_target, 'raw-trace')
   concat_files = [trace_file]
diff --git a/tools/install-build-deps b/tools/install-build-deps
index de0f53c..c3822f6 100755
--- a/tools/install-build-deps
+++ b/tools/install-build-deps
@@ -649,12 +649,22 @@
   parser.add_argument('--verify', help='Check all URLs', action='store_true')
   parser.add_argument(
       '--no-toolchain', help='Do not download toolchain', action='store_true')
+  parser.add_argument(
+      '--build-os',
+      default=system().lower(),
+      choices=['windows', 'darwin', 'linux'],
+      help='Override the autodetected build operating system')
+  parser.add_argument(
+      '--build-arch',
+      default=GetArch(),
+      choices=['arm64', 'x64'],
+      help='Override the autodetected build CPU architecture')
   args = parser.parse_args()
   if args.verify:
     CheckHashes()
     return 0
 
-  target_os = system().lower()
+  target_os = args.build_os
   if args.ui and target_os == 'windows':
     print('Building the UI on Windows is unsupported')
     return 1
@@ -680,7 +690,7 @@
     RmtreeIfExists(os.path.join(ROOT_DIR, old_dir))
 
   for dep in deps:
-    target_arch = GetArch()
+    target_arch = args.build_arch
     matches_os = dep.target_os == 'all' or target_os == dep.target_os
     matches_arch = dep.target_arch == 'all' or target_arch == dep.target_arch
     if not matches_os or not matches_arch:
diff --git a/tools/java_heap_dump b/tools/java_heap_dump
index 5ef3cfd..36e6428 100755
--- a/tools/java_heap_dump
+++ b/tools/java_heap_dump
@@ -344,7 +344,7 @@
 
   subprocess.check_call(['adb', 'pull', PROFILE_PATH, output_file], stdout=NULL)
 
-  subprocess.check_call(['adb', 'shell', 'rm', PROFILE_PATH], stdout=NULL)
+  subprocess.check_call(['adb', 'shell', 'rm', '-f', PROFILE_PATH], stdout=NULL)
 
   print("Wrote profile to {}".format(output_file))
   print("This can be viewed using https://ui.perfetto.dev.")
diff --git a/ui/src/base/comparison_utils.ts b/ui/src/base/comparison_utils.ts
index 842c812..4d84a4d 100644
--- a/ui/src/base/comparison_utils.ts
+++ b/ui/src/base/comparison_utils.ts
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {isString} from './object_utils';
+
 export type ComparisonFn<X> = (a: X, b: X) => number;
 
 export type SortDirection = 'DESC'|'ASC';
@@ -49,7 +51,7 @@
   if (typeof a === 'number') {
     return 2;
   }
-  if (typeof a === 'string') {
+  if (isString(a)) {
     return 3;
   }
   // a instanceof Uint8Array
@@ -66,7 +68,7 @@
   if (typeof a === 'number' && typeof b === 'number') {
     return a - b;
   }
-  if (typeof a === 'string' && typeof b === 'string') {
+  if (isString(a) && isString(b)) {
     return a.localeCompare(b);
   }
   if (a instanceof Uint8Array && b instanceof Uint8Array) {
diff --git a/ui/src/base/disposable.ts b/ui/src/base/disposable.ts
index cc5e397..15b0346 100644
--- a/ui/src/base/disposable.ts
+++ b/ui/src/base/disposable.ts
@@ -51,6 +51,9 @@
   }
 }
 
+export class NullDisposable implements Disposable {
+  dispose() {}
+}
 
 // A collection of Disposables.
 // Disposables can be added one by one, (e.g. during the lifecycle of a
diff --git a/ui/src/base/mithril_utils.ts b/ui/src/base/mithril_utils.ts
index 854221f..1f07578 100644
--- a/ui/src/base/mithril_utils.ts
+++ b/ui/src/base/mithril_utils.ts
@@ -14,10 +14,8 @@
 
 import m from 'mithril';
 
-import {exists} from './utils';
-
 // Check if a mithril component vnode has children
 export function hasChildren({children}: m.Vnode<any>): boolean {
   return Array.isArray(children) && children.length > 0 &&
-      children.some(exists);
+      children.some((value) => value);
 }
diff --git a/ui/src/base/time.ts b/ui/src/base/time.ts
index 528fdbf..c8e5837 100644
--- a/ui/src/base/time.ts
+++ b/ui/src/base/time.ts
@@ -319,6 +319,10 @@
     this.end = end;
   }
 
+  static fromTimeAndDuration(start: time, duration: duration): TimeSpan {
+    return new TimeSpan(start, Time.add(start, duration));
+  }
+
   get duration(): duration {
     return this.end - this.start;
   }
diff --git a/ui/src/base/validators.ts b/ui/src/base/validators.ts
index cc072b7..8125a2d 100644
--- a/ui/src/base/validators.ts
+++ b/ui/src/base/validators.ts
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {isString} from './object_utils';
+
 // Execution context of object validator
 interface ValidatorContext {
   // Path to the current value starting from the root. Object field names are
@@ -110,7 +112,7 @@
 
 class StringValidator extends PrimitiveValidator<string> {
   predicate(input: unknown): input is string {
-    return typeof input === 'string';
+    return isString(input);
   }
 }
 
diff --git a/ui/src/common/actions.ts b/ui/src/common/actions.ts
index 0999b31..8e8297a 100644
--- a/ui/src/common/actions.ts
+++ b/ui/src/common/actions.ts
@@ -30,8 +30,7 @@
   tableColumnEquals,
   toggleEnabled,
 } from '../frontend/pivot_table_types';
-import {PrimaryTrackSortKey, TrackTags} from '../public/index';
-import {DebugTrackV2Config} from '../tracks/debug/slice_track';
+import {PrimaryTrackSortKey} from '../public/index';
 
 import {randomColor} from './colorizer';
 import {
@@ -77,21 +76,16 @@
   VisibleState,
 } from './state';
 
-export const DEBUG_SLICE_TRACK_KIND = 'DebugSliceTrack';
-
 type StateDraft = Draft<State>;
 
 export interface AddTrackArgs {
-  id?: string;
-  engineId: string;
-  kind: string;
+  key?: string;
+  uri: string;
   name: string;
   labels?: string[];
   trackSortKey: TrackSortKey;
   trackGroup?: string;
-  config: {};
-  tags?: TrackTags;
-  uri?: string;  // Only used for new PLUGIN_TRACK tracks
+  params?: unknown;
 }
 
 export interface PostedTrace {
@@ -140,15 +134,15 @@
 // A helper to clean the state for a given removeable track.
 // This is not exported as action to make it clear that not all
 // tracks are removeable.
-function removeTrack(state: StateDraft, trackId: string) {
-  const track = state.tracks[trackId];
+function removeTrack(state: StateDraft, trackKey: string) {
+  const track = state.tracks[trackKey];
   if (track === undefined) {
     return;
   }
-  delete state.tracks[trackId];
+  delete state.tracks[trackKey];
 
   const removeTrackId = (arr: string[]) => {
-    const index = arr.indexOf(trackId);
+    const index = arr.indexOf(trackKey);
     if (index !== -1) arr.splice(index, 1);
   };
 
@@ -160,7 +154,7 @@
       removeTrackId(trackGroup.tracks);
     }
   }
-  state.pinnedTracks = state.pinnedTracks.filter((id) => id !== trackId);
+  state.pinnedTracks = state.pinnedTracks.filter((key) => key !== trackKey);
 }
 
 let statusTraceEvent: TraceEventScope|undefined;
@@ -212,38 +206,24 @@
   },
 
   fillUiTrackIdByTraceTrackId(
-      state: StateDraft, trackState: TrackState, uiTrackId: string) {
-    const namespace = (trackState.config as {namespace?: string}).namespace;
-    if (namespace !== undefined) return;
-
-    const setUiTrackId = (trackId: number, uiTrackId: string) => {
-      if (state.uiTrackIdByTraceTrackId[trackId] !== undefined &&
-          state.uiTrackIdByTraceTrackId[trackId] !== uiTrackId) {
+      state: StateDraft, trackState: TrackState, trackKey: string) {
+    const setTrackKey = (trackId: number, trackKey: string) => {
+      if (state.trackKeyByTrackId[trackId] !== undefined &&
+          state.trackKeyByTrackId[trackId] !== trackKey) {
         throw new Error(`Trying to map track id ${trackId} to UI track ${
-            uiTrackId}, already mapped to ${
-            state.uiTrackIdByTraceTrackId[trackId]}`);
+            trackKey}, already mapped to ${state.trackKeyByTrackId[trackId]}`);
       }
-      state.uiTrackIdByTraceTrackId[trackId] = uiTrackId;
+      state.trackKeyByTrackId[trackId] = trackKey;
     };
 
-    const {uri, config} = trackState;
+    const {uri} = trackState;
     if (exists(uri)) {
       // If track is a new "plugin" type track (i.e. it has a uri), resolve the
       // track ids from through the pluginManager.
       const trackInfo = pluginManager.resolveTrackInfo(uri);
       if (trackInfo?.trackIds) {
         for (const trackId of trackInfo.trackIds) {
-          setUiTrackId(trackId, uiTrackId);
-        }
-      }
-    } else {
-      // Traditional track - resolve track ids through the config.
-      const {trackId, trackIds} = config;
-      if (exists(trackId)) {
-        setUiTrackId(trackId, uiTrackId);
-      } else if (exists(trackIds)) {
-        for (const trackId of trackIds) {
-          setUiTrackId(trackId, uiTrackId);
+          setTrackKey(trackId, trackKey);
         }
       }
     }
@@ -251,33 +231,43 @@
 
   addTracks(state: StateDraft, args: {tracks: AddTrackArgs[]}) {
     args.tracks.forEach((track) => {
-      const id = track.id === undefined ? generateNextId(state) : track.id;
+      const trackKey =
+          track.key === undefined ? generateNextId(state) : track.key;
       const name = track.name;
-      const tags = track.tags ?? {name};
-      state.tracks[id] = {
-        id,
-        engineId: track.engineId,
-        kind: track.kind,
+      state.tracks[trackKey] = {
+        key: trackKey,
         name,
         trackSortKey: track.trackSortKey,
         trackGroup: track.trackGroup,
-        tags,
-        config: track.config,
         labels: track.labels,
         uri: track.uri,
+        params: track.params,
       };
-      this.fillUiTrackIdByTraceTrackId(state, track as TrackState, id);
+      this.fillUiTrackIdByTraceTrackId(state, track as TrackState, trackKey);
       if (track.trackGroup === SCROLLING_TRACK_GROUP) {
-        state.scrollingTracks.push(id);
+        state.scrollingTracks.push(trackKey);
       } else if (track.trackGroup !== undefined) {
         const group = state.trackGroups[track.trackGroup];
         if (group !== undefined) {
-          group.tracks.push(id);
+          group.tracks.push(trackKey);
         }
       }
     });
   },
 
+  // Note: While this action has traditionally been omitted, with more and more
+  // dynamic tracks being added and existing ones being moved to plugins, it
+  // makes sense to have a generic "removeTracks" action which is un-opinionated
+  // about what type of tracks we are removing.
+  // E.g. Once debug tracks have been moved to a plugin, it makes no sense to
+  // keep the "removeDebugTrack()" action, as the core should have no concept of
+  // what debug tracks are.
+  removeTracks(state: StateDraft, args: {trackKeys: string[]}) {
+    for (const trackKey of args.trackKeys) {
+      removeTrack(state, trackKey);
+    }
+  },
+
   setUtidToTrackSortKey(
       state: StateDraft, args: {threadOrderingMetadata: UtidToTrackSortKey}) {
     state.utidToThreadSortKey = args.threadOrderingMetadata;
@@ -292,58 +282,18 @@
       // Define ID in action so a track group can be referred to without running
       // the reducer.
       args: {
-        engineId: string; name: string; id: string; summaryTrackId: string;
-        collapsed: boolean;
+        name: string; id: string; summaryTrackKey: string; collapsed: boolean;
+        fixedOrdering?: boolean;
       }): void {
     state.trackGroups[args.id] = {
-      engineId: args.engineId,
       name: args.name,
       id: args.id,
       collapsed: args.collapsed,
-      tracks: [args.summaryTrackId],
+      tracks: [args.summaryTrackKey],
+      fixedOrdering: args.fixedOrdering,
     };
   },
 
-  addDebugTrack(
-      state: StateDraft,
-      args: {engineId: string, name: string, config: DebugTrackV2Config}):
-      void {
-        if (state.debugTrackId !== undefined) return;
-        const trackId = generateNextId(state);
-        this.addTrack(state, {
-          id: trackId,
-          engineId: args.engineId,
-          kind: DEBUG_SLICE_TRACK_KIND,
-          name: args.name,
-          trackSortKey: PrimaryTrackSortKey.DEBUG_SLICE_TRACK,
-          trackGroup: SCROLLING_TRACK_GROUP,
-          config: args.config,
-        });
-        this.toggleTrackPinned(state, {trackId});
-      },
-
-  removeDebugTrack(state: StateDraft, args: {trackId: string}): void {
-    const track = state.tracks[args.trackId];
-    if (track !== undefined) {
-      assertTrue(track.kind === DEBUG_SLICE_TRACK_KIND);
-      removeTrack(state, args.trackId);
-    }
-  },
-
-  removeVisualisedArgTracks(state: StateDraft, args: {trackIds: string[]}) {
-    for (const trackId of args.trackIds) {
-      const track = state.tracks[trackId];
-
-      const namespace = (track.config as {namespace?: string}).namespace;
-      if (namespace === undefined) {
-        throw new Error(
-            'All visualised arg tracks should have non-empty namespace');
-      }
-
-      removeTrack(state, trackId);
-    }
-  },
-
   maybeExpandOnlyTrackGroup(state: StateDraft, _: {}): void {
     const trackGroups = Object.values(state.trackGroups);
     if (trackGroups.length === 1) {
@@ -379,6 +329,8 @@
     // rather than T1, T10, T11, ..., T2, T20, T21 .
     const coll = new Intl.Collator([], {sensitivity: 'base', numeric: true});
     for (const group of Object.values(state.trackGroups)) {
+      if (group.fixedOrdering) continue;
+
       group.tracks.sort((a: string, b: string) => {
         const aRank = getFullKey(a);
         const bRank = getFullKey(b);
@@ -450,21 +402,21 @@
     moveWithinTrackList(state.scrollingTracks);
   },
 
-  toggleTrackPinned(state: StateDraft, args: {trackId: string}): void {
-    const id = args.trackId;
-    const isPinned = state.pinnedTracks.includes(id);
-    const trackGroup = assertExists(state.tracks[id]).trackGroup;
+  toggleTrackPinned(state: StateDraft, args: {trackKey: string}): void {
+    const key = args.trackKey;
+    const isPinned = state.pinnedTracks.includes(key);
+    const trackGroup = assertExists(state.tracks[key]).trackGroup;
 
     if (isPinned) {
-      state.pinnedTracks.splice(state.pinnedTracks.indexOf(id), 1);
+      state.pinnedTracks.splice(state.pinnedTracks.indexOf(key), 1);
       if (trackGroup === SCROLLING_TRACK_GROUP) {
-        state.scrollingTracks.unshift(id);
+        state.scrollingTracks.unshift(key);
       }
     } else {
       if (trackGroup === SCROLLING_TRACK_GROUP) {
-        state.scrollingTracks.splice(state.scrollingTracks.indexOf(id), 1);
+        state.scrollingTracks.splice(state.scrollingTracks.indexOf(key), 1);
       }
-      state.pinnedTracks.push(id);
+      state.pinnedTracks.push(key);
     }
   },
 
@@ -686,27 +638,26 @@
 
   selectSlice(
       state: StateDraft,
-      args: {id: number, trackId: string, scroll?: boolean}): void {
+      args: {id: number, trackKey: string, scroll?: boolean}): void {
     state.currentSelection = {
       kind: 'SLICE',
       id: args.id,
-      trackId: args.trackId,
+      trackKey: args.trackKey,
     };
     state.pendingScrollId = args.scroll ? args.id : undefined;
   },
 
   selectCounter(
       state: StateDraft,
-      args: {leftTs: time, rightTs: time, id: number, trackId: string}):
-      void {
-        state.currentSelection = {
-          kind: 'COUNTER',
-          leftTs: args.leftTs,
-          rightTs: args.rightTs,
-          id: args.id,
-          trackId: args.trackId,
-        };
-      },
+      args: {leftTs: time, rightTs: time, id: number, trackKey: string}): void {
+    state.currentSelection = {
+      kind: 'COUNTER',
+      leftTs: args.leftTs,
+      rightTs: args.rightTs,
+      id: args.id,
+      trackKey: args.trackKey,
+    };
+  },
 
   selectHeapProfile(
       state: StateDraft,
@@ -801,12 +752,12 @@
 
   selectChromeSlice(
       state: StateDraft,
-      args: {id: number, trackId: string, table: string, scroll?: boolean}):
+      args: {id: number, trackKey: string, table?: string, scroll?: boolean}):
       void {
         state.currentSelection = {
           kind: 'CHROME_SLICE',
           id: args.id,
-          trackId: args.trackId,
+          trackKey: args.trackKey,
           table: args.table,
         };
         state.pendingScrollId = args.scroll ? args.id : undefined;
@@ -817,7 +768,7 @@
     sqlTableName: string,
     start: time,
     duration: duration,
-    trackId: string,
+    trackKey: string,
     detailsPanelConfig:
         {kind: string, config: GenericSliceDetailsTabConfigBase},
   }): void {
@@ -832,7 +783,7 @@
       sqlTableName: args.sqlTableName,
       start: args.start,
       duration: args.duration,
-      trackId: args.trackId,
+      trackKey: args.trackKey,
       detailsPanelConfig:
           {kind: args.detailsPanelConfig.kind, config: detailsPanelConfig},
     };
@@ -842,25 +793,25 @@
     state.pendingScrollId = undefined;
   },
 
-  selectThreadState(state: StateDraft, args: {id: number, trackId: string}):
+  selectThreadState(state: StateDraft, args: {id: number, trackKey: string}):
       void {
         state.currentSelection = {
           kind: 'THREAD_STATE',
           id: args.id,
-          trackId: args.trackId,
+          trackKey: args.trackKey,
         };
       },
 
   selectLog(
-      state: StateDraft, args: {id: number, trackId: string, scroll?: boolean}):
-      void {
-        state.currentSelection = {
-          kind: 'LOG',
-          id: args.id,
-          trackId: args.trackId,
-        };
-        state.pendingScrollId = args.scroll ? args.id : undefined;
-      },
+      state: StateDraft,
+      args: {id: number, trackKey: string, scroll?: boolean}): void {
+    state.currentSelection = {
+      kind: 'LOG',
+      id: args.id,
+      trackKey: args.trackKey,
+    };
+    state.pendingScrollId = args.scroll ? args.id : undefined;
+  },
 
   deselect(state: StateDraft, _: {}): void {
     state.currentSelection = null;
@@ -1066,8 +1017,8 @@
   clearAllPinnedTracks(state: StateDraft, _: {}) {
     const pinnedTracks = state.pinnedTracks.slice();
     for (let index = pinnedTracks.length-1; index >= 0; index--) {
-      const trackId = pinnedTracks[index];
-      this.toggleTrackPinned(state, {trackId});
+      const trackKey = pinnedTracks[index];
+      this.toggleTrackPinned(state, {trackKey});
     }
   },
 
@@ -1138,17 +1089,6 @@
             }));
   },
 
-  addVisualisedArg(state: StateDraft, args: {argName: string}) {
-    if (!state.visualisedArgs.includes(args.argName)) {
-      state.visualisedArgs.push(args.argName);
-    }
-  },
-
-  removeVisualisedArg(state: StateDraft, args: {argName: string}) {
-    state.visualisedArgs =
-        state.visualisedArgs.filter((val) => val !== args.argName);
-  },
-
   setPivotTableArgumentNames(
       state: StateDraft, args: {argumentNames: string[]}) {
     state.nonSerializableState.pivotTable.argumentNames = args.argumentNames;
diff --git a/ui/src/common/actions_unittest.ts b/ui/src/common/actions_unittest.ts
index 8713c17..3fb0246 100644
--- a/ui/src/common/actions_unittest.ts
+++ b/ui/src/common/actions_unittest.ts
@@ -36,8 +36,8 @@
 } from './state';
 
 function fakeTrack(state: State, args: {
-  id: string,
-  kind?: string,
+  key: string,
+  uri?: string,
   trackGroup?: string,
   trackSortKey?: TrackSortKey,
   name?: string,
@@ -45,15 +45,13 @@
 }): State {
   return produce(state, (draft) => {
     StateActions.addTrack(draft, {
-      id: args.id,
-      engineId: '0',
-      kind: args.kind || 'SOME_TRACK_KIND',
+      uri: args.uri || 'sometrack',
+      key: args.key,
       name: args.name || 'A track',
       trackSortKey: args.trackSortKey === undefined ?
           PrimaryTrackSortKey.ORDINARY_TRACK :
           args.trackSortKey,
       trackGroup: args.trackGroup || SCROLLING_TRACK_GROUP,
-      config: {tid: args.tid || '0'},
     });
   });
 }
@@ -64,20 +62,19 @@
     StateActions.addTrackGroup(draft, {
       name: 'A group',
       id: args.id,
-      engineId: '0',
       collapsed: false,
-      summaryTrackId: args.summaryTrackId,
+      summaryTrackKey: args.summaryTrackId,
     });
   });
 }
 
 function pinnedAndScrollingTracks(
     state: State,
-    ids: string[],
+    keys: string[],
     pinnedTracks: string[],
     scrollingTracks: string[]): State {
-  for (const id of ids) {
-    state = fakeTrack(state, {id});
+  for (const key of keys) {
+    state = fakeTrack(state, {key});
   }
   state = produce(state, (draft) => {
     draft.pinnedTracks = pinnedTracks;
@@ -89,22 +86,18 @@
 test('add scrolling tracks', () => {
   const once = produce(createEmptyState(), (draft) => {
     StateActions.addTrack(draft, {
-      engineId: '1',
-      kind: 'cpu',
+      uri: 'cpu',
       name: 'Cpu 1',
       trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK,
       trackGroup: SCROLLING_TRACK_GROUP,
-      config: {},
     });
   });
   const twice = produce(once, (draft) => {
     StateActions.addTrack(draft, {
-      engineId: '2',
-      kind: 'cpu',
+      uri: 'cpu',
       name: 'Cpu 2',
       trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK,
       trackGroup: SCROLLING_TRACK_GROUP,
-      config: {},
     });
   });
 
@@ -114,27 +107,24 @@
 
 test('add track to track group', () => {
   let state = createEmptyState();
-  state = fakeTrack(state, {id: 's'});
+  state = fakeTrack(state, {key: 's'});
 
   const afterGroup = produce(state, (draft) => {
     StateActions.addTrackGroup(draft, {
-      engineId: '1',
       name: 'A track group',
       id: '123-123-123',
-      summaryTrackId: 's',
+      summaryTrackKey: 's',
       collapsed: false,
     });
   });
 
   const afterTrackAdd = produce(afterGroup, (draft) => {
     StateActions.addTrack(draft, {
-      id: '1',
-      engineId: '1',
-      kind: 'slices',
+      key: '1',
+      uri: 'slices',
       name: 'renderer 1',
       trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK,
       trackGroup: '123-123-123',
-      config: {},
     });
   });
 
@@ -145,18 +135,14 @@
 test('reorder tracks', () => {
   const once = produce(createEmptyState(), (draft) => {
     StateActions.addTrack(draft, {
-      engineId: '1',
-      kind: 'cpu',
+      uri: 'cpu',
       name: 'Cpu 1',
       trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK,
-      config: {},
     });
     StateActions.addTrack(draft, {
-      engineId: '2',
-      kind: 'cpu',
+      uri: 'cpu',
       name: 'Cpu 2',
       trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK,
-      config: {},
     });
   });
 
@@ -241,7 +227,7 @@
 
   const after = produce(state, (draft) => {
     StateActions.toggleTrackPinned(draft, {
-      trackId: 'c',
+      trackKey: 'c',
     });
   });
   expect(after.pinnedTracks).toEqual(['a', 'c']);
@@ -254,7 +240,7 @@
 
   const after = produce(state, (draft) => {
     StateActions.toggleTrackPinned(draft, {
-      trackId: 'a',
+      trackKey: 'a',
     });
   });
   expect(after.pinnedTracks).toEqual(['b']);
@@ -285,11 +271,9 @@
 
   const twice = produce(once, (draft) => {
     StateActions.addTrack(draft, {
-      engineId: '1',
-      kind: 'cpu',
+      uri: 'cpu',
       name: 'Cpu 1',
       trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK,
-      config: {},
     });
   });
 
@@ -331,14 +315,14 @@
   let state = createEmptyState();
   state = fakeTrackGroup(state, {id: 'g', summaryTrackId: 'b'});
   state = fakeTrack(state, {
-    id: 'b',
-    kind: HEAP_PROFILE_TRACK_KIND,
+    key: 'b',
+    uri: HEAP_PROFILE_TRACK_KIND,
     trackSortKey: PrimaryTrackSortKey.HEAP_PROFILE_TRACK,
     trackGroup: 'g',
   });
   state = fakeTrack(state, {
-    id: 'a',
-    kind: PROCESS_SCHEDULING_TRACK_KIND,
+    key: 'a',
+    uri: PROCESS_SCHEDULING_TRACK_KIND,
     trackSortKey: PrimaryTrackSortKey.PROCESS_SCHEDULING_TRACK,
     trackGroup: 'g',
   });
@@ -356,35 +340,35 @@
   let state = createEmptyState();
   state = fakeTrackGroup(state, {id: 'g', summaryTrackId: 'b'});
   state = fakeTrack(state, {
-    id: 'a',
-    kind: PROCESS_SCHEDULING_TRACK_KIND,
+    key: 'a',
+    uri: PROCESS_SCHEDULING_TRACK_KIND,
     trackSortKey: PrimaryTrackSortKey.PROCESS_SCHEDULING_TRACK,
     trackGroup: 'g',
   });
   state = fakeTrack(state, {
-    id: 'b',
-    kind: SLICE_TRACK_KIND,
+    key: 'b',
+    uri: SLICE_TRACK_KIND,
     trackGroup: 'g',
     trackSortKey: PrimaryTrackSortKey.MAIN_THREAD,
   });
   state = fakeTrack(state, {
-    id: 'c',
-    kind: SLICE_TRACK_KIND,
+    key: 'c',
+    uri: SLICE_TRACK_KIND,
     trackGroup: 'g',
     trackSortKey: PrimaryTrackSortKey.RENDER_THREAD,
   });
   state = fakeTrack(state, {
-    id: 'd',
-    kind: SLICE_TRACK_KIND,
+    key: 'd',
+    uri: SLICE_TRACK_KIND,
     trackGroup: 'g',
     trackSortKey: PrimaryTrackSortKey.GPU_COMPLETION_THREAD,
   });
   state = fakeTrack(
-      state, {id: 'e', kind: HEAP_PROFILE_TRACK_KIND, trackGroup: 'g'});
+      state, {key: 'e', uri: HEAP_PROFILE_TRACK_KIND, trackGroup: 'g'});
   state = fakeTrack(
-      state, {id: 'f', kind: SLICE_TRACK_KIND, trackGroup: 'g', name: 'T2'});
+      state, {key: 'f', uri: SLICE_TRACK_KIND, trackGroup: 'g', name: 'T2'});
   state = fakeTrack(
-      state, {id: 'g', kind: SLICE_TRACK_KIND, trackGroup: 'g', name: 'T10'});
+      state, {key: 'g', uri: SLICE_TRACK_KIND, trackGroup: 'g', name: 'T10'});
 
   const after = produce(state, (draft) => {
     StateActions.sortThreadTracks(draft, {});
@@ -403,8 +387,8 @@
   let state = createEmptyState();
   state = fakeTrackGroup(state, {id: 'g', summaryTrackId: 'a'});
   state = fakeTrack(state, {
-    id: 'a',
-    kind: SLICE_TRACK_KIND,
+    key: 'a',
+    uri: SLICE_TRACK_KIND,
     trackSortKey: {
       utid: 1,
       priority: InThreadTrackSortKey.ORDINARY,
@@ -414,8 +398,8 @@
     tid: '1',
   });
   state = fakeTrack(state, {
-    id: 'b',
-    kind: SLICE_TRACK_KIND,
+    key: 'b',
+    uri: SLICE_TRACK_KIND,
     trackSortKey: {
       utid: 2,
       priority: InThreadTrackSortKey.ORDINARY,
@@ -425,8 +409,8 @@
     tid: '2',
   });
   state = fakeTrack(state, {
-    id: 'c',
-    kind: THREAD_STATE_TRACK_KIND,
+    key: 'c',
+    uri: THREAD_STATE_TRACK_KIND,
     trackSortKey: {
       utid: 1,
       priority: InThreadTrackSortKey.ORDINARY,
diff --git a/ui/src/common/array_buffer_builder.ts b/ui/src/common/array_buffer_builder.ts
index 58e6980..fd86c04 100644
--- a/ui/src/common/array_buffer_builder.ts
+++ b/ui/src/common/array_buffer_builder.ts
@@ -18,13 +18,14 @@
 } from '@protobufjs/utf8';
 
 import {assertTrue} from '../base/logging';
+import {isString} from '../base/object_utils';
 
 // A token that can be appended to an `ArrayBufferBuilder`.
 export type ArrayBufferToken = string|number|Uint8Array;
 
 // Return the length, in bytes, of a token to be inserted.
 function tokenLength(token: ArrayBufferToken): number {
-  if (typeof token === 'string') {
+  if (isString(token)) {
     return utf8Len(token);
   } else if (token instanceof Uint8Array) {
     return token.byteLength;
@@ -46,7 +47,7 @@
     typedArray: Uint8Array,
     byteOffset: number,
     token: ArrayBufferToken): void {
-  if (typeof token === 'string') {
+  if (isString(token)) {
     // Encode the string in UTF-8
     const written = utf8Write(token, typedArray, byteOffset);
     assertTrue(written === utf8Len(token));
diff --git a/ui/src/common/basic_async_track.ts b/ui/src/common/basic_async_track.ts
index 2faf829..f22c333 100644
--- a/ui/src/common/basic_async_track.ts
+++ b/ui/src/common/basic_async_track.ts
@@ -19,8 +19,7 @@
 import {globals} from '../frontend/globals';
 import {PxSpan, TimeScale} from '../frontend/time_scale';
 import {SliceRect} from '../frontend/track';
-import {TrackButtonAttrs} from '../frontend/track_panel';
-import {Track} from '../public';
+import {Track, TrackContext} from '../public';
 
 import {TrackData} from './track_data';
 
@@ -46,7 +45,7 @@
   private currentState?: TrackData;
   protected data?: Data;
 
-  onCreate(): void {}
+  onCreate(_ctx: TrackContext): void {}
 
   onDestroy(): void {
     this.queuedRequest = false;
@@ -67,14 +66,10 @@
 
   abstract getHeight(): number;
 
-  getTrackShellButtons(): m.Vnode<TrackButtonAttrs, {}>[] {
+  getTrackShellButtons(): m.Children {
     return [];
   }
 
-  getContextMenu(): m.Vnode<any, {}>|null {
-    return null;
-  }
-
   onMouseMove(_position: {x: number; y: number;}): void {}
 
   onMouseClick(_position: {x: number; y: number;}): boolean {
diff --git a/ui/src/common/empty_state.ts b/ui/src/common/empty_state.ts
index 23644e5..de6e8f2 100644
--- a/ui/src/common/empty_state.ts
+++ b/ui/src/common/empty_state.ts
@@ -93,7 +93,7 @@
     newEngineMode: 'USE_HTTP_RPC_IF_AVAILABLE',
     traceTime: {...defaultTraceTime},
     tracks: {},
-    uiTrackIdByTraceTrackId: {},
+    trackKeyByTrackId: {},
     utidToThreadSortKey: {},
     aggregatePreferences: {},
     trackGroups: {},
@@ -104,7 +104,6 @@
     queries: {},
     permalink: {},
     notes: {},
-    visualisedArgs: [],
 
     recordConfig: AUTOLOAD_STARTED_CONFIG_FLAG.get() ?
         autosaveConfigStore.get() :
diff --git a/ui/src/common/event_set.ts b/ui/src/common/event_set.ts
index 88e7319..c420296 100644
--- a/ui/src/common/event_set.ts
+++ b/ui/src/common/event_set.ts
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 import {arrayEquals, isArrayOf} from '../base/array_utils';
+import {isString} from '../base/object_utils';
 import {intersect} from '../base/set_utils';
 
 // Contents:
@@ -796,7 +797,7 @@
     const value = this.value;
     if (value === null) {
       return 'NULL';
-    } else if (typeof value === 'string') {
+    } else if (isString(value)) {
       return `'${value}'`;
     } else if (typeof value === 'boolean') {
       return value ? 'TRUE' : 'FALSE';
@@ -1000,7 +1001,7 @@
 function primativeToRank(p: Primitive) {
   if (p === null) {
     return 0;
-  } else if (typeof p === 'string') {
+  } else if (isString(p)) {
     return 2;
   } else {
     return 1;
diff --git a/ui/src/common/plugins.ts b/ui/src/common/plugins.ts
index 13a4e10..cb27e61 100644
--- a/ui/src/common/plugins.ts
+++ b/ui/src/common/plugins.ts
@@ -15,29 +15,22 @@
 import {Disposable, Trash} from '../base/disposable';
 import {assertFalse} from '../base/logging';
 import {ViewerImpl, ViewerProxy} from '../common/viewer';
-import {
-  TrackControllerFactory,
-  trackControllerRegistry,
-} from '../controller/track_controller';
 import {globals} from '../frontend/globals';
-import {TrackCreator} from '../frontend/track';
-import {trackRegistry} from '../frontend/track_registry';
 import {
-  BasePlugin,
   Command,
   EngineProxy,
   MetricVisualisation,
+  Migrate,
   Plugin,
   PluginClass,
   PluginContext,
   PluginContextTrace,
   PluginDescriptor,
-  StatefulPlugin,
   Store,
   Track,
   TrackContext,
   TrackDescriptor,
-  TrackInstanceDescriptor,
+  TrackRef,
   Viewer,
 } from '../public';
 
@@ -73,18 +66,6 @@
     });
   }
 
-  LEGACY_registerTrackController(track: TrackControllerFactory): void {
-    if (!this.alive) return;
-    const unregister = trackControllerRegistry.register(track);
-    this.trash.add(unregister);
-  }
-
-  LEGACY_registerTrack(track: TrackCreator): void {
-    if (!this.alive) return;
-    const unregister = trackRegistry.register(track);
-    this.trash.add(unregister);
-  }
-
   dispose(): void {
     this.trash.dispose();
     this.alive = false;
@@ -95,30 +76,16 @@
 // related resources, such as the engine and the store.
 // The TracePluginContext exists for the whole duration a plugin is active AND a
 // trace is loaded.
-class TracePluginContextImpl<T> implements PluginContextTrace<T>, Disposable {
+class TracePluginContextImpl implements PluginContextTrace, Disposable {
   private trash = new Trash();
   private alive = true;
 
   constructor(
-      private ctx: PluginContext, readonly store: Store<T>,
-      readonly engine: EngineProxy,
+      private ctx: PluginContext, readonly engine: EngineProxy,
       readonly trackRegistry: Map<string, TrackDescriptor>,
-      private suggestedTracks: Set<TrackInstanceDescriptor>,
+      private defaultTracks: Set<TrackRef>,
       private commandRegistry: Map<string, Command>) {
     this.trash.add(engine);
-    this.trash.add(store);
-  }
-
-  LEGACY_registerTrackController(track: TrackControllerFactory): void {
-    // Silently ignore if context is dead.
-    if (!this.alive) return;
-    this.ctx.LEGACY_registerTrackController(track);
-  }
-
-  LEGACY_registerTrack(track: TrackCreator): void {
-    // Silently ignore if context is dead.
-    if (!this.alive) return;
-    this.ctx.LEGACY_registerTrack(track);
   }
 
   addCommand(cmd: Command): void {
@@ -140,51 +107,81 @@
     return this.ctx.viewer;
   }
 
+  get pluginId(): string {
+    return this.ctx.pluginId;
+  }
+
   // Register a new track in this context.
   // All tracks registered through this method are removed when this context is
   // destroyed, i.e. when the trace is unloaded.
-  addTrack(trackDetails: TrackDescriptor): void {
+  registerTrack(trackDesc: TrackDescriptor): void {
     // Silently ignore if context is dead.
     if (!this.alive) return;
-    const {uri} = trackDetails;
-    this.trackRegistry.set(uri, trackDetails);
-    this.trash.addCallback(() => this.trackRegistry.delete(uri));
+
+    this.trackRegistry.set(trackDesc.uri, trackDesc);
+    this.trash.addCallback(() => this.trackRegistry.delete(trackDesc.uri));
   }
 
-  // Ask Perfetto to add a track to the track list when a fresh trace is loaded.
-  // Ignored when a trace is loaded from a permalink.
-  // This is a direct replacement for findPotentialTracks().
-  // Note: This interface is likely to be deprecated soon, but is required while
-  // both plugin and original type tracks coexist.
-  suggestTrack(trackInfo: TrackInstanceDescriptor): void {
-    this.suggestedTracks.add(trackInfo);
-    this.trash.addCallback(() => this.suggestedTracks.delete(trackInfo));
+  addDefaultTrack(track: TrackRef): void {
+    // Silently ignore if context is dead.
+    if (!this.alive) return;
+
+    this.defaultTracks.add(track);
+    this.trash.addCallback(() => this.defaultTracks.delete(track));
+  }
+
+  registerStaticTrack(track: TrackDescriptor&TrackRef): void {
+    this.registerTrack(track);
+
+    // TODO(stevegolton): Once we've sorted out track_decider, we should also
+    // add this track to the default track list here. E.g.
+    // this.addDefaultTrack({
+    //   uri: trackDetails.uri,
+    //   displayName: trackDetails.displayName,
+    //   sortKey: PrimaryTrackSortKey.ORDINARY_TRACK,
+    // });
   }
 
   dispose(): void {
     this.trash.dispose();
     this.alive = false;
   }
+
+  mountStore<T>(migrate: Migrate<T>): Store<T> {
+    const globalStore = globals.store;
+
+    // Migrate initial state
+    const initialState = globalStore.state.plugins[this.pluginId];
+    const migratedState = migrate(initialState);
+
+    // Update global store with migrated plugin state
+    globalStore.edit((draft) => {
+      draft.plugins[this.pluginId] = migratedState;
+    });
+
+    // Return proxy store for this plugin
+    return globalStore.createProxy<T>(['plugins', this.pluginId]);
+  }
 }
 
 // 'Static' registry of all known plugins.
-export class PluginRegistry extends Registry<PluginDescriptor<unknown>> {
+export class PluginRegistry extends Registry<PluginDescriptor> {
   constructor() {
     super((info) => info.pluginId);
   }
 }
 
-interface PluginDetails<T> {
-  plugin: Plugin<T>;
+interface PluginDetails {
+  plugin: Plugin;
   context: PluginContext&Disposable;
-  traceContext?: TracePluginContextImpl<unknown>;
+  traceContext?: TracePluginContextImpl;
 }
 
-function isPluginClass<T>(v: unknown): v is PluginClass<T> {
+function isPluginClass(v: unknown): v is PluginClass {
   return typeof v === 'function' && !!(v.prototype.onActivate);
 }
 
-function makePlugin<T>(info: PluginDescriptor<T>): Plugin<T> {
+function makePlugin(info: PluginDescriptor): Plugin {
   const {plugin} = info;
 
   if (typeof plugin === 'function') {
@@ -201,11 +198,11 @@
 
 export class PluginManager {
   private registry: PluginRegistry;
-  private plugins: Map<string, PluginDetails<unknown>>;
+  private plugins: Map<string, PluginDetails>;
   private engine?: Engine;
   readonly trackRegistry = new Map<string, TrackDescriptor>();
   readonly commandRegistry = new Map<string, Command>();
-  readonly suggestedTracks = new Set<TrackInstanceDescriptor>();
+  readonly defaultTracks = new Set<TrackRef>();
 
   constructor(registry: PluginRegistry) {
     this.registry = registry;
@@ -226,7 +223,7 @@
 
     plugin.onActivate && plugin.onActivate(context);
 
-    const pluginDetails: PluginDetails<unknown> = {
+    const pluginDetails: PluginDetails = {
       plugin,
       context,
     };
@@ -259,12 +256,12 @@
     return this.getPluginContext(pluginId) !== undefined;
   }
 
-  getPluginContext(pluginId: string): PluginDetails<unknown>|undefined {
+  getPluginContext(pluginId: string): PluginDetails|undefined {
     return this.plugins.get(pluginId);
   }
 
-  findPotentialTracks(): TrackInstanceDescriptor[] {
-    return Array.from(this.suggestedTracks);
+  findPotentialTracks(): TrackRef[] {
+    return Array.from(this.defaultTracks);
   }
 
   onTraceLoad(engine: Engine): void {
@@ -309,59 +306,26 @@
     return trackInfo && trackInfo.track(trackCtx);
   }
 
-  private doPluginTraceLoad<T>(
-      pluginDetails: PluginDetails<T>, engine: Engine, pluginId: string): void {
+  private doPluginTraceLoad(
+      pluginDetails: PluginDetails, engine: Engine, pluginId: string): void {
     const {plugin, context} = pluginDetails;
 
     const engineProxy = engine.getProxy(pluginId);
 
-    // Migrate state & write back to store.
-    if (isStatefulPlugin(plugin)) {
-      const initialState = globals.store.state.plugins[pluginId];
-      const migratedState = plugin.migrate(initialState);
-      globals.store.edit((draft) => {
-        draft.plugins[pluginId] = migratedState;
-      });
+    const traceCtx = new TracePluginContextImpl(
+        context,
+        engineProxy,
+        this.trackRegistry,
+        this.defaultTracks,
+        this.commandRegistry);
+    pluginDetails.traceContext = traceCtx;
 
-      const proxyStore = globals.store.createProxy<T>(['plugins', pluginId]);
-      const traceCtx = new TracePluginContextImpl(
-          context,
-          proxyStore,
-          engineProxy,
-          this.trackRegistry,
-          this.suggestedTracks,
-          this.commandRegistry);
-      pluginDetails.traceContext = traceCtx;
-
-      // TODO(stevegolton): Await onTraceLoad.
-      plugin.onTraceLoad && plugin.onTraceLoad(traceCtx);
-    } else {
-      // Stateless plugin i.e. the plugin's state type is undefined.
-      // Just provide a store proxy over this plugin's state, the plugin can
-      // work the state out for itself if it wants to, but we're not going to
-      // help it out by calling migrate().
-      const proxyStore = globals.store.createProxy<T>(['plugins', pluginId]);
-      const traceCtx = new TracePluginContextImpl(
-          context,
-          proxyStore,
-          engineProxy,
-          this.trackRegistry,
-          this.suggestedTracks,
-          this.commandRegistry);
-      pluginDetails.traceContext = traceCtx;
-
-      // TODO(stevegolton): Await onTraceLoad.
-      plugin.onTraceLoad && plugin.onTraceLoad(traceCtx);
-    }
+    // TODO(stevegolton): Await onTraceLoad.
+    plugin.onTraceLoad && plugin.onTraceLoad(traceCtx);
   }
 }
 
-function isStatefulPlugin<T>(plugin: BasePlugin<T>|
-                             StatefulPlugin<T>): plugin is StatefulPlugin<T> {
-  return 'migrate' in plugin;
-}
-
-function maybeDoPluginTraceUnload(pluginDetails: PluginDetails<unknown>): void {
+function maybeDoPluginTraceUnload(pluginDetails: PluginDetails): void {
   const {traceContext, plugin} = pluginDetails;
 
   if (traceContext) {
diff --git a/ui/src/common/plugins_unittest.ts b/ui/src/common/plugins_unittest.ts
index 5be1f4a..4d3d4c2 100644
--- a/ui/src/common/plugins_unittest.ts
+++ b/ui/src/common/plugins_unittest.ts
@@ -26,9 +26,8 @@
   rpcSendRequestBytes(_data: Uint8Array) {}
 }
 
-function makeMockPlugin(): Plugin<any> {
+function makeMockPlugin(): Plugin {
   return {
-    migrate: jest.fn(),
     onActivate: jest.fn(),
     onDeactivate: jest.fn(),
     onTraceLoad: jest.fn(),
@@ -91,17 +90,4 @@
 
     expect(mockPlugin.onTraceUnload).toHaveBeenCalledTimes(1);
   });
-
-  it('does not invoke migrate at activation time', () => {
-    manager.activatePlugin('foo', viewer);
-
-    expect(mockPlugin.migrate).not.toHaveBeenCalled();
-  });
-
-  it('invokes migrate when trace is loaded', () => {
-    manager.activatePlugin('foo', viewer);
-    manager.onTraceLoad(engine);
-
-    expect(mockPlugin.migrate).toHaveBeenCalledTimes(1);
-  });
 });
diff --git a/ui/src/common/recordingV2/adb_connection_over_webusb.ts b/ui/src/common/recordingV2/adb_connection_over_webusb.ts
index 1c118b0..bd42c52 100644
--- a/ui/src/common/recordingV2/adb_connection_over_webusb.ts
+++ b/ui/src/common/recordingV2/adb_connection_over_webusb.ts
@@ -16,6 +16,7 @@
 
 import {defer, Deferred} from '../../base/deferred';
 import {assertExists, assertFalse, assertTrue} from '../../base/logging';
+import {isString} from '../../base/object_utils';
 import {CmdType} from '../../controller/adb_interfaces';
 
 import {AdbConnectionImpl} from './adb_connection_impl';
@@ -196,7 +197,7 @@
   }
 
   streamWrite(msg: string|Uint8Array, stream: AdbOverWebusbStream): void {
-    const raw = (typeof msg === 'string') ? textEncoder.encode(msg) : msg;
+    const raw = (isString(msg)) ? textEncoder.encode(msg) : msg;
     if (this.writeInProgress) {
       this.writeQueue.push({message: raw, localStreamId: stream.localStreamId});
       return;
@@ -604,7 +605,7 @@
 
   static encodeData(data?: Uint8Array|string): Uint8Array {
     if (data === undefined) return new Uint8Array([]);
-    if (typeof data === 'string') return textEncoder.encode(data + '\0');
+    if (isString(data)) return textEncoder.encode(data + '\0');
     return data;
   }
 }
diff --git a/ui/src/common/recordingV2/recording_config_utils.ts b/ui/src/common/recordingV2/recording_config_utils.ts
index dd33e36..a134f9a 100644
--- a/ui/src/common/recordingV2/recording_config_utils.ts
+++ b/ui/src/common/recordingV2/recording_config_utils.ts
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 
+import {isString} from '../../base/object_utils';
 import {base64Encode} from '../../base/string_utils';
 import {RecordConfig} from '../../controller/record_config_types';
 import {
@@ -721,7 +722,7 @@
       const isNested = typeof value === 'object' && !isRepeated;
       for (const entry of (isRepeated ? value as Array<{}>: [value])) {
         yield ' '.repeat(indent) + `${snakeCase(key)}${isNested ? '' : ':'} `;
-        if (typeof entry === 'string') {
+        if (isString(entry)) {
           if (isEnum(entry) || is64BitNumber(key)) {
             yield entry;
           } else {
diff --git a/ui/src/common/search_data.ts b/ui/src/common/search_data.ts
index 2854d3c..b81dfb2 100644
--- a/ui/src/common/search_data.ts
+++ b/ui/src/common/search_data.ts
@@ -22,7 +22,7 @@
   sliceIds: Float64Array;
   tsStarts: BigInt64Array;
   utids: Float64Array;
-  trackIds: string[];
+  trackKeys: string[];
   sources: string[];
   totalResults: number;
 }
diff --git a/ui/src/common/state.ts b/ui/src/common/state.ts
index 1a8c68e..2fdf467 100644
--- a/ui/src/common/state.ts
+++ b/ui/src/common/state.ts
@@ -23,7 +23,7 @@
   PivotTree,
   TableColumn,
 } from '../frontend/pivot_table_types';
-import {PrimaryTrackSortKey, TrackTags} from '../public/index';
+import {PrimaryTrackSortKey} from '../public/index';
 
 import {Direction} from './event_set';
 
@@ -34,6 +34,11 @@
  */
 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;
+}
+
 export interface Timestamped {
   lastUpdate: number;
 }
@@ -123,7 +128,9 @@
 //     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.
-export const STATE_VERSION = 40;
+// 41. Ported all remaining tracks.
+// 42. Rename trackId -> trackKey.
+export const STATE_VERSION = 42;
 
 export const SCROLLING_TRACK_GROUP = 'ScrollingTracks';
 
@@ -218,29 +225,23 @@
     TraceFileSource|TraceArrayBufferSource|TraceUrlSource|TraceHttpRpcSource;
 
 export interface TrackState {
-  id: string;
-  engineId: string;
-  kind: string;
+  uri: string;
+  key: string;
   name: string;
   labels?: string[];
   trackSortKey: TrackSortKey;
   trackGroup?: string;
-  tags: TrackTags;
-  config: {
-    trackId?: number;
-    trackIds?: number[];
-  };
-  uri?: string;
+  params?: unknown;
   state?: unknown;
 }
 
 export interface TrackGroupState {
   id: string;
-  engineId: string;
   name: string;
   collapsed: boolean;
   tracks: string[];  // Child track ids.
   state?: unknown;
+  fixedOrdering?: boolean;  // Render tracks without sorting.
 }
 
 export interface EngineConfig {
@@ -349,7 +350,7 @@
 export interface ChromeSliceSelection {
   kind: 'CHROME_SLICE';
   id: number;
-  table: string;
+  table?: string;
 }
 
 export interface ThreadStateSelection {
@@ -360,7 +361,7 @@
 export interface LogSelection {
   kind: 'LOG';
   id: number;
-  trackId: string;
+  trackKey: string;
 }
 
 export interface GenericSliceSelection {
@@ -377,7 +378,7 @@
     (NoteSelection|SliceSelection|CounterSelection|HeapProfileSelection|
      CpuProfileSampleSelection|ChromeSliceSelection|ThreadStateSelection|
      AreaSelection|PerfSamplesSelection|LogSelection|GenericSliceSelection)&
-    {trackId?: string};
+    {trackKey?: string};
 export type SelectionKind = Selection['kind'];  // 'THREAD_STATE' | 'SLICE' ...
 
 export interface Pagination {
@@ -533,8 +534,8 @@
   traceTime: TraceTime;
   traceUuid?: string;
   trackGroups: ObjectById<TrackGroupState>;
-  tracks: ObjectById<TrackState>;
-  uiTrackIdByTraceTrackId: {[key: number]: string;};
+  tracks: ObjectByKey<TrackState>;
+  trackKeyByTrackId: {[key: number]: string;};
   utidToThreadSortKey: UtidToTrackSortKey;
   areas: ObjectById<AreaById>;
   aggregatePreferences: ObjectById<AggregationState>;
@@ -553,7 +554,6 @@
   ftracePagination: Pagination;
   ftraceFilter: FtraceFilterState;
   traceConversionInProgress: boolean;
-  visualisedArgs: string[];
 
   /**
    * This state is updated on the frontend at 60Hz and eventually syncronised to
@@ -944,9 +944,9 @@
   ];
 }
 
-export function getContainingTrackId(state: State, trackId: string): null|
+export function getContainingTrackId(state: State, trackKey: string): null|
     string {
-  const track = state.tracks[trackId];
+  const track = state.tracks[trackKey];
   if (!track) {
     return null;
   }
diff --git a/ui/src/common/state_unittest.ts b/ui/src/common/state_unittest.ts
index 34646ba..fa251fc 100644
--- a/ui/src/common/state_unittest.ts
+++ b/ui/src/common/state_unittest.ts
@@ -26,24 +26,18 @@
 test('getContainingTrackId', () => {
   const state: State = createEmptyState();
   state.tracks['a'] = {
-    id: 'a',
-    engineId: 'engine',
-    kind: 'Foo',
+    key: 'a',
+    uri: 'Foo',
     name: 'a track',
     trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK,
-    config: {},
-    tags: {},
   };
 
   state.tracks['b'] = {
-    id: 'b',
-    engineId: 'engine',
-    kind: 'Foo',
+    key: 'b',
+    uri: 'Foo',
     name: 'b track',
     trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK,
-    config: {},
     trackGroup: 'containsB',
-    tags: {},
   };
 
   expect(getContainingTrackId(state, 'z')).toEqual(null);
diff --git a/ui/src/common/track_adapter.ts b/ui/src/common/track_adapter.ts
index 1c145d3..16edb58 100644
--- a/ui/src/common/track_adapter.ts
+++ b/ui/src/common/track_adapter.ts
@@ -20,7 +20,6 @@
 import {EngineProxy} from '../common/engine';
 import {PxSpan, TimeScale} from '../frontend/time_scale';
 import {NewTrackArgs, SliceRect} from '../frontend/track';
-import {TrackButtonAttrs} from '../frontend/track_panel';
 
 import {BasicAsyncTrack} from './basic_async_track';
 
@@ -44,12 +43,12 @@
   private isSetup = false;
 
   constructor(
-      engine: EngineProxy, trackInstanceId: string, config: Config,
+      engine: EngineProxy, trackKey: string, config: Config,
       Track: TrackAdapterClass<Config, Data>,
       Controller: TrackControllerAdapterClass<Config, Data>) {
     super();
     const args: NewTrackArgs = {
-      trackId: trackInstanceId,
+      trackKey,
       engine,
     };
     this.track = new Track(args);
@@ -76,14 +75,10 @@
     return this.track.getHeight();
   }
 
-  getTrackShellButtons(): m.Vnode<TrackButtonAttrs, {}>[] {
+  getTrackShellButtons(): m.Children {
     return this.track.getTrackShellButtons();
   }
 
-  getContextMenu(): m.Vnode<any, {}>|null {
-    return this.track.getContextMenu();
-  }
-
   onMouseMove(position: {x: number; y: number;}): void {
     this.track.onMouseMove(position);
   }
@@ -119,7 +114,7 @@
 export abstract class TrackAdapter<Config, Data> {
   private _config?: Config;
   private dataSource?: () => Data | undefined;
-  protected id: string;
+  protected trackKey: string;
 
   get config(): Config {
     return assertExists(this._config);
@@ -139,7 +134,7 @@
   }
 
   constructor(args: NewTrackArgs) {
-    this.id = args.trackId;
+    this.trackKey = args.trackKey;
   }
 
   abstract renderCanvas(ctx: CanvasRenderingContext2D): void;
@@ -155,14 +150,10 @@
     return 40;
   }
 
-  getTrackShellButtons(): Array<m.Vnode<TrackButtonAttrs>> {
+  getTrackShellButtons(): m.Children {
     return [];
   }
 
-  getContextMenu(): m.Vnode<any>|null {
-    return null;
-  }
-
   onMouseMove(_position: {x: number, y: number}) {}
 
   // Returns whether the mouse click has selected something.
diff --git a/ui/src/common/upload_utils.ts b/ui/src/common/upload_utils.ts
index 0c97921..8ffdc11 100644
--- a/ui/src/common/upload_utils.ts
+++ b/ui/src/common/upload_utils.ts
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {isString} from '../base/object_utils';
 import {RecordConfig} from '../controller/record_config_types';
 
 export const BUCKET_NAME = 'perfetto-ui-data';
@@ -50,7 +51,7 @@
     return false;
   }
   if ('__kind' in value && 'value' in value) {
-    return value.__kind === 'bigint' && typeof value.value === 'string';
+    return value.__kind === 'bigint' && isString(value.value);
   }
   return false;
 }
diff --git a/ui/src/common/viewer.ts b/ui/src/common/viewer.ts
index e380c5c..99318ea 100644
--- a/ui/src/common/viewer.ts
+++ b/ui/src/common/viewer.ts
@@ -44,9 +44,9 @@
       const tags = {
         name: track.name,
       };
-      if (predicate(tags) && !this.isPinned(track.id)) {
+      if (predicate(tags) && !this.isPinned(track.key)) {
         globals.dispatch(Actions.toggleTrackPinned({
-          trackId: track.id,
+          trackKey: track.key,
         }));
       }
     }
@@ -58,9 +58,9 @@
       const tags = {
         name: track.name,
       };
-      if (predicate(tags) && this.isPinned(track.id)) {
+      if (predicate(tags) && this.isPinned(track.key)) {
         globals.dispatch(Actions.toggleTrackPinned({
-          trackId: track.id,
+          trackKey: track.key,
         }));
       }
     }
diff --git a/ui/src/controller/adb.ts b/ui/src/controller/adb.ts
index 18ca7fe..2b592a2 100644
--- a/ui/src/controller/adb.ts
+++ b/ui/src/controller/adb.ts
@@ -15,6 +15,7 @@
 import {_TextDecoder, _TextEncoder} from 'custom_utils';
 
 import {assertExists} from '../base/logging';
+import {isString} from '../base/object_utils';
 
 import {Adb, AdbMsg, AdbStream, CmdType} from './adb_interfaces';
 
@@ -435,7 +436,7 @@
   }
 
   async write(msg: string|Uint8Array) {
-    const raw = (typeof msg === 'string') ? textEncoder.encode(msg) : msg;
+    const raw = (isString(msg)) ? textEncoder.encode(msg) : msg;
     if (this.sendInProgress ||
         this.state === AdbStreamState.WAITING_INITIAL_OKAY) {
       this.writeQueue.push(raw);
@@ -579,7 +580,7 @@
 
   static encodeData(data?: Uint8Array|string): Uint8Array {
     if (data === undefined) return new Uint8Array([]);
-    if (typeof data === 'string') return textEncoder.encode(data + '\0');
+    if (isString(data)) return textEncoder.encode(data + '\0');
     return data;
   }
 }
diff --git a/ui/src/controller/aggregation/aggregation_controller.ts b/ui/src/controller/aggregation/aggregation_controller.ts
index f5c855b..522131a 100644
--- a/ui/src/controller/aggregation/aggregation_controller.ts
+++ b/ui/src/controller/aggregation/aggregation_controller.ts
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {isString} from '../../base/object_utils';
 import {
   AggregateData,
   Column,
@@ -144,7 +145,7 @@
         const item = it.get(column.columnId);
         if (item === null) {
           column.data[i] = isStringColumn(column) ? internString('NULL') : 0;
-        } else if (typeof item === 'string') {
+        } else if (isString(item)) {
           column.data[i] = internString(item);
         } else if (item instanceof Uint8Array) {
           column.data[i] = internString('<Binary blob>');
diff --git a/ui/src/controller/aggregation/counter_aggregation_controller.ts b/ui/src/controller/aggregation/counter_aggregation_controller.ts
index ae3e1d7..fcdb4d0 100644
--- a/ui/src/controller/aggregation/counter_aggregation_controller.ts
+++ b/ui/src/controller/aggregation/counter_aggregation_controller.ts
@@ -27,8 +27,8 @@
     await engine.query(`drop view if exists ${this.kind};`);
 
     const trackIds: (string|number)[] = [];
-    for (const trackId of area.tracks) {
-      const track = globals.state.tracks[trackId];
+    for (const trackKey of area.tracks) {
+      const track = globals.state.tracks[trackKey];
       if (track?.uri) {
         const trackInfo = pluginManager.resolveTrackInfo(track.uri);
         if (trackInfo?.kind === COUNTER_TRACK_KIND) {
diff --git a/ui/src/controller/aggregation/cpu_aggregation_controller.ts b/ui/src/controller/aggregation/cpu_aggregation_controller.ts
index db176d9..ec3cf61 100644
--- a/ui/src/controller/aggregation/cpu_aggregation_controller.ts
+++ b/ui/src/controller/aggregation/cpu_aggregation_controller.ts
@@ -27,8 +27,8 @@
     await engine.query(`drop view if exists ${this.kind};`);
 
     const selectedCpus: number[] = [];
-    for (const trackId of area.tracks) {
-      const track = globals.state.tracks[trackId];
+    for (const trackKey of area.tracks) {
+      const track = globals.state.tracks[trackKey];
       if (track?.uri) {
         const trackInfo = pluginManager.resolveTrackInfo(track.uri);
         if (trackInfo?.kind === CPU_SLICE_TRACK_KIND) {
diff --git a/ui/src/controller/aggregation/cpu_by_process_aggregation_controller.ts b/ui/src/controller/aggregation/cpu_by_process_aggregation_controller.ts
index dbdf07c..deff299 100644
--- a/ui/src/controller/aggregation/cpu_by_process_aggregation_controller.ts
+++ b/ui/src/controller/aggregation/cpu_by_process_aggregation_controller.ts
@@ -26,8 +26,8 @@
     await engine.query(`drop view if exists ${this.kind};`);
 
     const selectedCpus: number[] = [];
-    for (const trackId of area.tracks) {
-      const track = globals.state.tracks[trackId];
+    for (const trackKey of area.tracks) {
+      const track = globals.state.tracks[trackKey];
       if (track?.uri) {
         const trackInfo = pluginManager.resolveTrackInfo(track.uri);
         if (trackInfo?.kind === CPU_SLICE_TRACK_KIND) {
diff --git a/ui/src/controller/aggregation/frame_aggregation_controller.ts b/ui/src/controller/aggregation/frame_aggregation_controller.ts
index a22a83d..cbe2448 100644
--- a/ui/src/controller/aggregation/frame_aggregation_controller.ts
+++ b/ui/src/controller/aggregation/frame_aggregation_controller.ts
@@ -14,12 +14,10 @@
 
 import {ColumnDef} from '../../common/aggregation_data';
 import {Engine} from '../../common/engine';
+import {pluginManager} from '../../common/plugins';
 import {Area, Sorting} from '../../common/state';
 import {globals} from '../../frontend/globals';
-import {
-  ACTUAL_FRAMES_SLICE_TRACK_KIND,
-  Config,
-} from '../../tracks/actual_frames';
+import {ACTUAL_FRAMES_SLICE_TRACK_KIND} from '../../tracks/actual_frames';
 
 import {AggregationController} from './aggregation_controller';
 
@@ -27,13 +25,15 @@
   async createAggregateView(engine: Engine, area: Area) {
     await engine.query(`drop view if exists ${this.kind};`);
 
-    const selectedSqlTrackIds = [];
-    for (const trackId of area.tracks) {
-      const track = globals.state.tracks[trackId];
+    const selectedSqlTrackIds: number[] = [];
+    for (const trackKey of area.tracks) {
+      const track = globals.state.tracks[trackKey];
       // Track will be undefined for track groups.
-      if (track !== undefined &&
-          track.kind === ACTUAL_FRAMES_SLICE_TRACK_KIND) {
-        selectedSqlTrackIds.push((track.config as Config).trackIds);
+      if (track?.uri !== undefined) {
+        const trackInfo = pluginManager.resolveTrackInfo(track.uri);
+        if (trackInfo?.kind === ACTUAL_FRAMES_SLICE_TRACK_KIND) {
+          trackInfo.trackIds && selectedSqlTrackIds.push(...trackInfo.trackIds);
+        }
       }
     }
     if (selectedSqlTrackIds.length === 0) return false;
diff --git a/ui/src/controller/aggregation/slice_aggregation_controller.ts b/ui/src/controller/aggregation/slice_aggregation_controller.ts
index 8dcaccc..cb5e6f4 100644
--- a/ui/src/controller/aggregation/slice_aggregation_controller.ts
+++ b/ui/src/controller/aggregation/slice_aggregation_controller.ts
@@ -14,46 +14,39 @@
 
 import {ColumnDef} from '../../common/aggregation_data';
 import {Engine} from '../../common/engine';
+import {pluginManager} from '../../common/plugins';
 import {Area, Sorting} from '../../common/state';
 import {globals} from '../../frontend/globals';
-import {
-  ASYNC_SLICE_TRACK_KIND,
-  Config as AsyncSliceConfig,
-} from '../../tracks/async_slices';
-import {
-  Config as SliceConfig,
-  SLICE_TRACK_KIND,
-} from '../../tracks/chrome_slices';
+import {ASYNC_SLICE_TRACK_KIND} from '../../tracks/async_slices';
+import {SLICE_TRACK_KIND} from '../../tracks/chrome_slices';
 
 import {AggregationController} from './aggregation_controller';
 
-export function getSelectedTrackIds(area: Area): number[] {
-  const selectedTrackIds = [];
-  for (const trackId of area.tracks) {
-    const track = globals.state.tracks[trackId];
+export function getSelectedTrackKeys(area: Area): number[] {
+  const selectedTrackKeys: number[] = [];
+  for (const trackKey of area.tracks) {
+    const track = globals.state.tracks[trackKey];
     // Track will be undefined for track groups.
-    if (track !== undefined) {
-      if (track.kind === SLICE_TRACK_KIND) {
-        selectedTrackIds.push((track.config as SliceConfig).trackId);
+    if (track?.uri !== undefined) {
+      const trackInfo = pluginManager.resolveTrackInfo(track.uri);
+      if (trackInfo?.kind === SLICE_TRACK_KIND) {
+        trackInfo.trackIds && selectedTrackKeys.push(...trackInfo.trackIds);
       }
-      if (track.kind === ASYNC_SLICE_TRACK_KIND) {
-        const config = track.config as AsyncSliceConfig;
-        for (const id of config.trackIds) {
-          selectedTrackIds.push(id);
-        }
+      if (trackInfo?.kind === ASYNC_SLICE_TRACK_KIND) {
+        trackInfo.trackIds && selectedTrackKeys.push(...trackInfo.trackIds);
       }
     }
   }
-  return selectedTrackIds;
+  return selectedTrackKeys;
 }
 
 export class SliceAggregationController extends AggregationController {
   async createAggregateView(engine: Engine, area: Area) {
     await engine.query(`drop view if exists ${this.kind};`);
 
-    const selectedTrackIds = getSelectedTrackIds(area);
+    const selectedTrackKeys = getSelectedTrackKeys(area);
 
-    if (selectedTrackIds.length === 0) return false;
+    if (selectedTrackKeys.length === 0) return false;
 
     const query = `create view ${this.kind} as
         SELECT
@@ -62,7 +55,7 @@
         sum(dur)/count(1) as avg_dur,
         count(1) as occurrences
         FROM slices
-        WHERE track_id IN (${selectedTrackIds}) AND
+        WHERE track_id IN (${selectedTrackKeys}) AND
         ts + dur > ${area.start} AND
         ts < ${area.end} group by name`;
 
diff --git a/ui/src/controller/aggregation/thread_aggregation_controller.ts b/ui/src/controller/aggregation/thread_aggregation_controller.ts
index 7436a57..6818cfb 100644
--- a/ui/src/controller/aggregation/thread_aggregation_controller.ts
+++ b/ui/src/controller/aggregation/thread_aggregation_controller.ts
@@ -14,14 +14,12 @@
 
 import {ColumnDef, ThreadStateExtra} from '../../common/aggregation_data';
 import {Engine} from '../../common/engine';
+import {pluginManager} from '../../common/plugins';
 import {NUM, NUM_NULL, STR_NULL} from '../../common/query_result';
 import {Area, Sorting} from '../../common/state';
 import {translateState} from '../../common/thread_state';
 import {globals} from '../../frontend/globals';
-import {
-  Config,
-  THREAD_STATE_TRACK_KIND,
-} from '../../tracks/thread_state';
+import {THREAD_STATE_TRACK_KIND} from '../../tracks/thread_state';
 
 import {AggregationController} from './aggregation_controller';
 
@@ -33,8 +31,11 @@
     for (const trackId of tracks) {
       const track = globals.state.tracks[trackId];
       // Track will be undefined for track groups.
-      if (track !== undefined && track.kind === THREAD_STATE_TRACK_KIND) {
-        this.utids.push((track.config as Config).utid);
+      if (track?.uri) {
+        const trackInfo = pluginManager.resolveTrackInfo(track.uri);
+        if (trackInfo?.kind === THREAD_STATE_TRACK_KIND) {
+          trackInfo.utid && this.utids.push(trackInfo.utid);
+        }
       }
     }
   }
diff --git a/ui/src/controller/args_parser.ts b/ui/src/controller/args_parser.ts
index 97cd70e..b36c30b 100644
--- a/ui/src/controller/args_parser.ts
+++ b/ui/src/controller/args_parser.ts
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {isString} from '../base/object_utils';
 import {exists} from '../base/utils';
 
 export type Key = string|number;
@@ -102,7 +103,7 @@
 
 function parseNodes<A extends ArgLike<T>, T>(nodes: ArgNode<A>[]):
     ObjectType<T> {
-  if (nodes.every(({key}) => typeof key === 'string')) {
+  if (nodes.every(({key}) => isString(key))) {
     const dict: ObjectType<T> = {};
     for (const node of nodes) {
       if (node.key in dict) {
diff --git a/ui/src/controller/flamegraph_controller.ts b/ui/src/controller/flamegraph_controller.ts
index 20bdb04..c9e6f02 100644
--- a/ui/src/controller/flamegraph_controller.ts
+++ b/ui/src/controller/flamegraph_controller.ts
@@ -26,14 +26,12 @@
   PERF_SAMPLES_KEY,
   SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY,
 } from '../common/flamegraph_util';
+import {pluginManager} from '../common/plugins';
 import {NUM, STR} from '../common/query_result';
 import {CallsiteInfo, FlamegraphState, ProfileType} from '../common/state';
 import {FlamegraphDetails, globals} from '../frontend/globals';
 import {publishFlamegraphDetails} from '../frontend/publish';
-import {
-  Config as PerfSampleConfig,
-  PERF_SAMPLES_PROFILE_TRACK_KIND,
-} from '../tracks/perf_samples_profile';
+import {PERF_SAMPLES_PROFILE_TRACK_KIND} from '../tracks/perf_samples_profile';
 
 import {AreaSelectionHandler} from './area_selection_handler';
 import {Controller} from './controller';
@@ -131,12 +129,13 @@
         return;
       }
       for (const trackId of area.tracks) {
-        const trackState = globals.state.tracks[trackId];
-        if (!trackState ||
-            trackState.kind !== PERF_SAMPLES_PROFILE_TRACK_KIND) {
-          continue;
+        const track = globals.state.tracks[trackId];
+        if (track?.uri) {
+          const trackInfo = pluginManager.resolveTrackInfo(track.uri);
+          if (trackInfo?.kind === PERF_SAMPLES_PROFILE_TRACK_KIND) {
+            trackInfo.upid && upids.push(trackInfo.upid);
+          }
         }
-        upids.push((trackState.config as PerfSampleConfig).upid);
       }
       if (upids.length === 0) {
         this.checkCompletionAndPublishFlamegraph(
diff --git a/ui/src/controller/flow_events_controller.ts b/ui/src/controller/flow_events_controller.ts
index a1c4170..bf586d9 100644
--- a/ui/src/controller/flow_events_controller.ts
+++ b/ui/src/controller/flow_events_controller.ts
@@ -21,14 +21,8 @@
 import {Flow, globals} from '../frontend/globals';
 import {publishConnectedFlows, publishSelectedFlows} from '../frontend/publish';
 import {asSliceSqlId} from '../frontend/sql_types';
-import {
-  ACTUAL_FRAMES_SLICE_TRACK_KIND,
-  Config as ActualConfig,
-} from '../tracks/actual_frames';
-import {
-  Config as SliceConfig,
-  SLICE_TRACK_KIND,
-} from '../tracks/chrome_slices';
+import {ACTUAL_FRAMES_SLICE_TRACK_KIND} from '../tracks/actual_frames';
+import {SLICE_TRACK_KIND} from '../tracks/chrome_slices';
 
 import {Controller} from './controller';
 
@@ -206,7 +200,7 @@
     const uiTrackIdToInfo = new Map<string, null|Info>();
     const trackIdToInfo = new Map<number, null|Info>();
 
-    const trackIdToUiTrackId = globals.state.uiTrackIdByTraceTrackId;
+    const trackIdToUiTrackId = globals.state.trackKeyByTrackId;
     const tracks = globals.state.tracks;
 
     const getInfo = (trackId: number): null|Info => {
@@ -237,24 +231,14 @@
       // anything if there is only one TP track in this async track. In
       // that case experimental_slice_layout is just an expensive way
       // to find out depth === layout_depth.
-      const trackIds = track.config.trackIds;
+      const trackInfo = pluginManager.resolveTrackInfo(track.uri);
+      const trackIds = trackInfo?.trackIds;
       if (trackIds === undefined || trackIds.length <= 1) {
         uiTrackIdToInfo.set(uiTrackId, null);
         trackIdToInfo.set(trackId, null);
         return null;
       }
 
-      // Perform the same check for "plugin" style tracks.
-      if (track.uri) {
-        const trackInfo = pluginManager.resolveTrackInfo(track.uri);
-        const trackIds = trackInfo?.trackIds;
-        if (trackIds === undefined || trackIds.length <= 1) {
-          uiTrackIdToInfo.set(uiTrackId, null);
-          trackIdToInfo.set(trackId, null);
-          return null;
-        }
-      }
-
       const newInfo = {
         uiTrackId,
         siblingTrackIds: trackIds,
@@ -390,19 +374,16 @@
 
     for (const uiTrackId of area.tracks) {
       const track = globals.state.tracks[uiTrackId];
-      if (track === undefined) {
-        continue;
-      }
-      if (track.kind === SLICE_TRACK_KIND) {
-        trackIds.push((track.config as SliceConfig).trackId);
-      } else if (track.kind === ACTUAL_FRAMES_SLICE_TRACK_KIND) {
-        const actualConfig = track.config as ActualConfig;
-        for (const trackId of actualConfig.trackIds) {
-          trackIds.push(trackId);
-        }
-      } else if (track.config.trackIds !== undefined) {
-        for (const trackId of track.config.trackIds) {
-          trackIds.push(trackId);
+      if (track?.uri !== undefined) {
+        const trackInfo = pluginManager.resolveTrackInfo(track.uri);
+        const kind = trackInfo?.kind;
+        if (kind === SLICE_TRACK_KIND ||
+            kind === ACTUAL_FRAMES_SLICE_TRACK_KIND) {
+          if (trackInfo?.trackIds) {
+            for (const trackId of trackInfo.trackIds) {
+              trackIds.push(trackId);
+            }
+          }
         }
       }
     }
diff --git a/ui/src/controller/record_controller.ts b/ui/src/controller/record_controller.ts
index 32d5e75..fe22ffb 100644
--- a/ui/src/controller/record_controller.ts
+++ b/ui/src/controller/record_controller.ts
@@ -14,6 +14,7 @@
 
 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';
@@ -153,7 +154,7 @@
       const isNested = typeof value === 'object' && !isRepeated;
       for (const entry of (isRepeated ? value as Array<{}> : [value])) {
         yield ' '.repeat(indent) + `${snakeCase(key)}${isNested ? '' : ':'} `;
-        if (typeof entry === 'string') {
+        if (isString(entry)) {
           if (isEnum(entry) || is64BitNumber(key)) {
             yield entry;
           } else {
diff --git a/ui/src/controller/search_controller.ts b/ui/src/controller/search_controller.ts
index 6055221..28f8e55 100644
--- a/ui/src/controller/search_controller.ts
+++ b/ui/src/controller/search_controller.ts
@@ -108,7 +108,7 @@
         tsStarts: new BigInt64Array(0),
         utids: new Float64Array(0),
         sources: [],
-        trackIds: [],
+        trackKeys: [],
         totalResults: 0,
       });
       return;
@@ -206,7 +206,7 @@
         const trackInfo = pluginManager.resolveTrackInfo(track.uri);
         if (trackInfo?.kind === CPU_SLICE_TRACK_KIND) {
           const cpu = trackInfo?.cpu;
-          cpu && cpuToTrackId.set(cpu, track.id);
+          cpu && cpuToTrackId.set(cpu, track.key);
         }
       }
     }
@@ -268,7 +268,7 @@
       sliceIds: new Float64Array(rows),
       tsStarts: new BigInt64Array(rows),
       utids: new Float64Array(rows),
-      trackIds: [],
+      trackKeys: [],
       sources: [],
       totalResults: 0,
     };
@@ -280,12 +280,15 @@
       if (it.source === 'cpu') {
         trackId = cpuToTrackId.get(it.sourceId);
       } else if (it.source === 'track') {
-        trackId = globals.state.uiTrackIdByTraceTrackId[it.sourceId];
+        trackId = globals.state.trackKeyByTrackId[it.sourceId];
       } else if (it.source === 'log') {
-        const logTracks = Object.values(globals.state.tracks)
-                              .filter((t) => t.kind === 'AndroidLogTrack');
+        const logTracks =
+            Object.values(globals.state.tracks).filter((track) => {
+              const trackDesc = pluginManager.resolveTrackInfo(track.uri);
+              return (trackDesc && trackDesc.kind === 'AndroidLogTrack');
+            });
         if (logTracks.length > 0) {
-          trackId = logTracks[0].id;
+          trackId = logTracks[0].key;
         }
       }
 
@@ -295,7 +298,7 @@
       }
 
       const i = searchResults.totalResults++;
-      searchResults.trackIds.push(trackId);
+      searchResults.trackKeys.push(trackId);
       searchResults.sources.push(it.source);
       searchResults.sliceIds[i] = it.sliceId;
       searchResults.tsStarts[i] = it.ts;
diff --git a/ui/src/controller/selection_controller.ts b/ui/src/controller/selection_controller.ts
index b2c8ee5..7c44935 100644
--- a/ui/src/controller/selection_controller.ts
+++ b/ui/src/controller/selection_controller.ts
@@ -16,6 +16,7 @@
 import {Time, time} from '../base/time';
 import {Args, ArgValue} from '../common/arg_types';
 import {Engine} from '../common/engine';
+import {pluginManager} from '../common/plugins';
 import {
   durationFromSql,
   LONG,
@@ -303,18 +304,23 @@
     const trackIdQuery = `select track_id as trackId from slice
     where slice_id = ${sliceId}`;
     const result = await this.args.engine.query(trackIdQuery);
-    const trackIdTp = result.firstRow({trackId: NUM}).trackId;
+    const trackId = result.firstRow({trackId: NUM}).trackId;
     // TODO(hjd): If we had a consistent mapping from TP track_id
     // UI track id for slice tracks this would be unnecessary.
-    let trackId = '';
+    let trackKey = '';
     for (const track of Object.values(globals.state.tracks)) {
-      if (track.kind === SLICE_TRACK_KIND &&
-          (track.config as {trackId: number}).trackId === Number(trackIdTp)) {
-        trackId = track.id;
-        break;
+      if (track.uri) {
+        const trackInfo = pluginManager.resolveTrackInfo(track.uri);
+        if (trackInfo?.kind === SLICE_TRACK_KIND) {
+          const trackIds = trackInfo?.trackIds;
+          if (trackIds && trackIds.length > 0 && trackIds[0] === trackId) {
+            trackKey = track.key;
+            break;
+          }
+        }
       }
     }
-    return trackId;
+    return trackKey;
   }
 
   // TODO(altimin): We currently rely on the ThreadStateDetails for supporting
@@ -417,8 +423,8 @@
     const endTs = rightTs !== -1n ? rightTs : globals.state.traceTime.end;
     const delta = value - previousValue;
     const duration = endTs - ts;
-    const uiTrackId = globals.state.uiTrackIdByTraceTrackId[trackId];
-    const name = uiTrackId ? globals.state.tracks[uiTrackId].name : undefined;
+    const trackKey = globals.state.trackKeyByTrackId[trackId];
+    const name = trackKey ? globals.state.tracks[trackKey].name : undefined;
     return {startTime: ts, value, delta, duration, name};
   }
 
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index 31000d6..f323cb6 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -128,9 +128,7 @@
   TraceHttpStream,
   TraceStream,
 } from './trace_stream';
-import {TrackControllerArgs, trackControllerRegistry} from './track_controller';
 import {decideTracks} from './track_decider';
-import {VisualisedArgController} from './visualised_args_controller';
 
 type States = 'init' | 'loading_trace' | 'ready';
 
@@ -217,6 +215,31 @@
   });
 }
 
+// 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(`
+    select create_function(
+      'max_layout_depth(track_count INT, track_ids STRING)',
+      'INT',
+      '
+        select iif(
+          $track_count = 1,
+          (
+            select max(depth)
+            from slice
+            where track_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
@@ -262,21 +285,6 @@
         const engine = assertExists(this.engine);
         const childControllers: Children = [];
 
-        // Create a TrackController for each track.
-        for (const trackId of Object.keys(globals.state.tracks)) {
-          const trackCfg = globals.state.tracks[trackId];
-          if (trackCfg.engineId !== this.engineId) continue;
-          if (!trackControllerRegistry.has(trackCfg.kind)) continue;
-          const trackCtlFactory = trackControllerRegistry.get(trackCfg.kind);
-          const trackArgs: TrackControllerArgs = {trackId, engine};
-          childControllers.push(Child(trackId, trackCtlFactory, trackArgs));
-        }
-
-        for (const argName of globals.state.visualisedArgs) {
-          childControllers.push(
-            Child(argName, VisualisedArgController, {argName, engine}));
-        }
-
         const selectionArgs: SelectionControllerArgs = {engine};
         childControllers.push(
           Child('selection', SelectionController, selectionArgs));
@@ -499,6 +507,8 @@
     // Make sure the helper views are available before we start adding tracks.
     await this.initialiseHelperViews();
 
+    await defineMaxLayoutDepthSqlFunction(engine);
+
     pluginManager.onTraceLoad(engine);
 
     {
@@ -706,13 +716,13 @@
       });
 
       const id = row.traceProcessorTrackId;
-      const trackId = globals.state.uiTrackIdByTraceTrackId[id];
-      if (trackId === undefined) {
+      const trackKey = globals.state.trackKeyByTrackId[id];
+      if (trackKey === undefined) {
         return;
       }
       globals.makeSelection(Actions.selectChromeSlice({
         id: row.id,
-        trackId,
+        trackKey,
         table: '',
         scroll: true,
       }));
@@ -722,7 +732,7 @@
   private async listTracks() {
     this.updateStatus('Loading tracks');
     const engine = assertExists<Engine>(this.engine);
-    const actions = await decideTracks(this.engineId, engine);
+    const actions = await decideTracks(engine);
     globals.dispatchMultiple(actions);
   }
 
diff --git a/ui/src/controller/track_controller.ts b/ui/src/controller/track_controller.ts
deleted file mode 100644
index 539e561..0000000
--- a/ui/src/controller/track_controller.ts
+++ /dev/null
@@ -1,202 +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 {BigintMath} from '../base/bigint_math';
-import {assertExists} from '../base/logging';
-import {duration, time, Time, TimeSpan} from '../base/time';
-import {Engine} from '../common/engine';
-import {Registry} from '../common/registry';
-import {RESOLUTION_DEFAULT, TraceTime, TrackState} from '../common/state';
-import {LIMIT, TrackData} from '../common/track_data';
-import {globals} from '../frontend/globals';
-import {publishTrackData} from '../frontend/publish';
-
-import {Controller, ControllerFactory} from './controller';
-
-interface TrackConfig {}
-
-type TrackConfigWithNamespace = TrackConfig&{namespace: string};
-
-// TrackController is a base class overridden by track implementations (e.g.,
-// sched slices, nestable slices, counters).
-export abstract class TrackController<
-    Config extends TrackConfig, Data extends TrackData = TrackData> extends
-    Controller<'main'> {
-  readonly trackId: string;
-  readonly engine: Engine;
-  private data?: TrackData;
-  private requestingData = false;
-  private queuedRequest = false;
-  private isSetup = false;
-  private lastReloadHandled = 0;
-
-  constructor(args: TrackControllerArgs) {
-    super('main');
-    this.trackId = args.trackId;
-    this.engine = args.engine;
-  }
-
-  // Can be overriden by the track implementation to allow one time setup work
-  // to be performed before the first onBoundsChange invcation.
-  async onSetup() {}
-
-  // Can be overriden by the track implementation to allow some one-off work
-  // when requested reload (e.g. recalculating height).
-  async onReload() {}
-
-  // Must be overridden by the track implementation. Is invoked when the track
-  // frontend runs out of cached data. The derived track controller is expected
-  // to publish new track data in response to this call.
-  abstract onBoundsChange(start: time, end: time, resolution: duration):
-      Promise<Data>;
-
-  get trackState(): TrackState {
-    return assertExists(globals.state.tracks[this.trackId]);
-  }
-
-  get config(): Config {
-    return this.trackState.config as Config;
-  }
-
-  configHasNamespace(config: TrackConfig): config is TrackConfigWithNamespace {
-    return 'namespace' in config;
-  }
-
-  namespaceTable(tableName: string): string {
-    if (this.configHasNamespace(this.config)) {
-      return this.config.namespace + '_' + tableName;
-    } else {
-      return tableName;
-    }
-  }
-
-  publish(data: Data): void {
-    this.data = data;
-    publishTrackData({id: this.trackId, data});
-  }
-
-  // Returns a valid SQL table name with the given prefix that should be unique
-  // for each track.
-  tableName(prefix: string) {
-    // Derive table name from, since that is unique for each track.
-    // Track ID can be UUID but '-' is not valid for sql table name.
-    const idSuffix = this.trackId.split('-').join('_');
-    return `${prefix}_${idSuffix}`;
-  }
-
-  shouldSummarize(resolution: number): boolean {
-    // |resolution| is in s/px (to nearest power of 10) assuming a display
-    // of ~1000px 0.0008 is 0.8s.
-    return resolution >= 0.0008;
-  }
-
-  protected async query(query: string) {
-    const result = await this.engine.query(query);
-    return result;
-  }
-
-  private shouldReload(): boolean {
-    const {lastTrackReloadRequest} = globals.state;
-    return !!lastTrackReloadRequest &&
-        this.lastReloadHandled < lastTrackReloadRequest;
-  }
-
-  private markReloadHandled() {
-    this.lastReloadHandled = globals.state.lastTrackReloadRequest || 0;
-  }
-
-  shouldRequestData(traceTime: TraceTime): boolean {
-    const tspan = new TimeSpan(traceTime.start, traceTime.end);
-    if (this.data === undefined) return true;
-    if (this.shouldReload()) return true;
-
-    // If at the limit only request more data if the view has moved.
-    const atLimit = this.data.length === LIMIT;
-    if (atLimit) {
-      // We request more data than the window, so add window duration to find
-      // the previous window.
-      const prevWindowStart = this.data.start + tspan.duration;
-      return tspan.start !== prevWindowStart;
-    }
-
-    // Otherwise request more data only when out of range of current data or
-    // resolution has changed.
-    const inRange =
-        tspan.start >= this.data.start && tspan.end <= this.data.end;
-    return !inRange ||
-        this.data.resolution !==
-        globals.state.frontendLocalState.visibleState.resolution;
-  }
-
-  run() {
-    const visibleState = globals.state.frontendLocalState.visibleState;
-    if (visibleState === undefined) {
-      return;
-    }
-    const visibleTimeSpan = globals.stateVisibleTime();
-    const dur = visibleTimeSpan.duration;
-    if (globals.state.visibleTracks.includes(this.trackId) &&
-        this.shouldRequestData(visibleState)) {
-      if (this.requestingData) {
-        this.queuedRequest = true;
-      } else {
-        this.requestingData = true;
-        let promise = Promise.resolve();
-        if (!this.isSetup) {
-          promise = this.onSetup();
-        } else if (this.shouldReload()) {
-          promise = this.onReload().then(() => this.markReloadHandled());
-        }
-        promise
-            .then(() => {
-              this.isSetup = true;
-              let resolution = visibleState.resolution;
-
-              // If resolution is not a power of 2, reset to the default value
-              if (BigintMath.popcount(resolution) !== 1) {
-                resolution = RESOLUTION_DEFAULT;
-              }
-
-              return this.onBoundsChange(
-                  Time.sub(visibleTimeSpan.start, dur),
-                  Time.add(visibleTimeSpan.end, dur),
-                  resolution);
-            })
-            .then((data) => {
-              this.publish(data);
-            })
-            .finally(() => {
-              this.requestingData = false;
-              if (this.queuedRequest) {
-                this.queuedRequest = false;
-                this.run();
-              }
-            });
-      }
-    }
-  }
-}
-
-export interface TrackControllerArgs {
-  trackId: string;
-  engine: Engine;
-}
-
-export interface TrackControllerFactory extends
-    ControllerFactory<TrackControllerArgs> {
-  kind: string;
-}
-
-export const trackControllerRegistry =
-    Registry.kindRegistry<TrackControllerFactory>();
diff --git a/ui/src/controller/track_decider.ts b/ui/src/controller/track_decider.ts
index b4a8228..c07c1ab 100644
--- a/ui/src/controller/track_decider.ts
+++ b/ui/src/controller/track_decider.ts
@@ -43,27 +43,18 @@
 import {
   ENABLE_SCROLL_JANK_PLUGIN_V2,
   getScrollJankTracks,
-  INPUT_LATENCY_TRACK,
 } from '../tracks/chrome_scroll_jank';
 import {
   decideTracks as scrollJankDecideTracks,
 } from '../tracks/chrome_scroll_jank/chrome_tasks_scroll_jank_track';
 import {SLICE_TRACK_KIND} from '../tracks/chrome_slices';
 import {COUNTER_TRACK_KIND} from '../tracks/counter';
-import {CPU_PROFILE_TRACK_KIND} from '../tracks/cpu_profile';
-import {
-  EXPECTED_FRAMES_SLICE_TRACK_KIND,
-} from '../tracks/expected_frames';
-import {HEAP_PROFILE_TRACK_KIND} from '../tracks/heap_profile';
-import {NULL_TRACK_KIND} from '../tracks/null_track';
-import {
-  PERF_SAMPLES_PROFILE_TRACK_KIND,
-} from '../tracks/perf_samples_profile';
+import {EXPECTED_FRAMES_SLICE_TRACK_KIND} from '../tracks/expected_frames';
+import {NULL_TRACK_URI} from '../tracks/null_track';
 import {
   decideTracks as screenshotDecideTracks,
 } from '../tracks/screenshots';
 import {THREAD_STATE_TRACK_KIND} from '../tracks/thread_state';
-import {THREAD_STATE_TRACK_V2_KIND} from '../tracks/thread_state_v2';
 
 const TRACKS_V2_FLAG = featureFlags.register({
   id: 'tracksV2.1',
@@ -80,11 +71,6 @@
   defaultValue: false,
 });
 
-// Special kind reserved for plugin tracks.
-// There is no significance to this value, it simply something that's unlikely
-// to be used as a key in the trackRegistry.
-const PLUGIN_TRACK_KIND = 'PLUGIN_TRACK';
-
 function showV2(): boolean {
   return TRACKS_V2_FLAG.get();
 }
@@ -125,21 +111,18 @@
 const CHROME_TRACK_GROUP = 'Chrome Global Tracks';
 const MISC_GROUP = 'Misc Global Tracks';
 
-export async function decideTracks(
-    engineId: string, engine: Engine): Promise<DeferredAction[]> {
-  return (new TrackDecider(engineId, engine)).decideTracks();
+export async function decideTracks(engine: Engine): Promise<DeferredAction[]> {
+  return (new TrackDecider(engine)).decideTracks();
 }
 
 class TrackDecider {
-  private engineId: string;
   private engine: Engine;
   private upidToUuid = new Map<number, string>();
   private utidToUuid = new Map<number, string>();
   private tracksToAdd: AddTrackArgs[] = [];
   private addTrackGroupActions: DeferredAction[] = [];
 
-  constructor(engineId: string, engine: Engine) {
-    this.engineId = engineId;
+  constructor(engine: Engine) {
     this.engine = engine;
   }
 
@@ -175,29 +158,14 @@
       const size = cpuToSize.get(cpu);
       const name = size === undefined ? `Cpu ${cpu}` : `Cpu ${cpu} (${size})`;
       this.tracksToAdd.push({
-        engineId: this.engineId,
-        kind: PLUGIN_TRACK_KIND,
+        uri: `perfetto.CpuSlices#cpu${cpu}`,
         trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK,
         name,
         trackGroup: SCROLLING_TRACK_GROUP,
-        config: {},
-        uri: `perfetto.CpuSlices#cpu${cpu}`,
       });
     }
   }
 
-  async addScrollJankTracks(engine: Engine): Promise<void> {
-    const scrollJankTracks = getScrollJankTracks(engine);
-    const scrollJankTracksResult = await scrollJankTracks;
-    const originalLength = this.tracksToAdd.length;
-    this.tracksToAdd.length += scrollJankTracksResult.tracksToAdd.length;
-
-    for (let i = 0; i < scrollJankTracksResult.tracksToAdd.length; ++i) {
-      this.tracksToAdd[i + originalLength] =
-          scrollJankTracksResult.tracksToAdd[i];
-    }
-  }
-
   async addCpuFreqTracks(engine: EngineProxy): Promise<void> {
     const cpus = await this.engine.getCpus();
 
@@ -223,13 +191,10 @@
 
       if (cpuFreqIdleResult.numRows() > 0) {
         this.tracksToAdd.push({
-          engineId: this.engineId,
-          kind: PLUGIN_TRACK_KIND,
+          uri: `perfetto.CpuFreq#${cpu}`,
           trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK,
           name: `Cpu ${cpu} Frequency`,
           trackGroup: SCROLLING_TRACK_GROUP,
-          config: {},
-          uri: `perfetto.CpuFreq#${cpu}`,
         });
       }
     }
@@ -276,20 +241,16 @@
       name: STR_NULL,
       parentName: STR_NULL,
       parentId: NUM_NULL,
-      trackIds: STR,
       maxDepth: NUM_NULL,
     });
 
     const parentIdToGroupId = new Map<number, string>();
-    let scrollJankRendered = false;
 
     for (; it.valid(); it.next()) {
       const kind = ASYNC_SLICE_TRACK_KIND;
       const rawName = it.name === null ? undefined : it.name;
       const rawParentName = it.parentName === null ? undefined : it.parentName;
       const name = getTrackName({name: rawName, kind});
-      const rawTrackIds = it.trackIds;
-      const trackIds = rawTrackIds.split(',').map((v) => Number(v));
       const parentTrackId = it.parentId;
       const maxDepth = it.maxDepth;
       let trackGroup = SCROLLING_TRACK_GROUP;
@@ -307,20 +268,17 @@
 
           const parentName = getTrackName({name: rawParentName, kind});
 
-          const summaryTrackId = uuidv4();
+          const summaryTrackKey = uuidv4();
           this.tracksToAdd.push({
-            id: summaryTrackId,
-            engineId: this.engineId,
-            kind: NULL_TRACK_KIND,
+            uri: NULL_TRACK_URI,
+            key: summaryTrackKey,
             trackSortKey: PrimaryTrackSortKey.NULL_TRACK,
             trackGroup: undefined,
             name: parentName,
-            config: {},
           });
 
           this.addTrackGroupActions.push(Actions.addTrackGroup({
-            engineId: this.engineId,
-            summaryTrackId,
+            summaryTrackKey: summaryTrackKey,
             name: parentName,
             id: trackGroup,
             collapsed: true,
@@ -330,23 +288,11 @@
         }
       }
 
-      if (ENABLE_SCROLL_JANK_PLUGIN_V2.get() && !scrollJankRendered &&
-          name.includes(INPUT_LATENCY_TRACK)) {
-        // This ensures that the scroll jank tracks render above the tracks
-        // for GestureScrollUpdate.
-        await this.addScrollJankTracks(this.engine);
-        scrollJankRendered = true;
-      }
-      const track = {
-        engineId: this.engineId,
-        kind,
+      const track: AddTrackArgs = {
+        uri: `perfetto.AsyncSlices#${rawName}`,
         trackSortKey: PrimaryTrackSortKey.ASYNC_SLICE_TRACK,
         trackGroup,
         name,
-        config: {
-          maxDepth,
-          trackIds,
-        },
       };
 
       this.tracksToAdd.push(track);
@@ -366,13 +312,10 @@
     `);
       if (freqExistsResult.numRows() > 0) {
         this.tracksToAdd.push({
-          engineId: this.engineId,
-          kind: PLUGIN_TRACK_KIND,
+          uri: `perfetto.Counter#gpu_freq${gpu}`,
           name: `Gpu ${gpu} Frequency`,
           trackSortKey: PrimaryTrackSortKey.COUNTER_TRACK,
           trackGroup: SCROLLING_TRACK_GROUP,
-          config: {},
-          uri: `perfetto.Counter#gpu_freq${gpu}`,
         });
       }
     }
@@ -416,13 +359,10 @@
       const name = it.name;
       const trackId = it.id;
       this.tracksToAdd.push({
-        engineId: this.engineId,
-        kind: PLUGIN_TRACK_KIND,
+        uri: `perfetto.Counter#cpu${trackId}`,
         name,
         trackSortKey: PrimaryTrackSortKey.COUNTER_TRACK,
         trackGroup: SCROLLING_TRACK_GROUP,
-        config: {},
-        uri: `perfetto.Counter#cpu${trackId}`,
       });
     }
   }
@@ -447,14 +387,14 @@
     }
 
     const id = uuidv4();
-    const summaryTrackId = uuidv4();
+    const summaryTrackKey = uuidv4();
     let foundSummary = false;
 
     for (const track of ionTracks) {
       if (!foundSummary &&
           [MEM_DMA_COUNTER_NAME, MEM_ION].includes(track.name)) {
         foundSummary = true;
-        track.id = summaryTrackId;
+        track.key = summaryTrackKey;
         track.trackGroup = undefined;
       } else {
         track.trackGroup = id;
@@ -462,8 +402,7 @@
     }
 
     const addGroup = Actions.addTrackGroup({
-      engineId: this.engineId,
-      summaryTrackId,
+      summaryTrackKey,
       name: MEM_DMA_COUNTER_NAME,
       id,
       collapsed: true,
@@ -497,21 +436,18 @@
 
     for (const [key, value] of devMap) {
       const groupName = group + key;
-      const summaryTrackId = uuidv4();
+      const summaryTrackKey = uuidv4();
 
       this.tracksToAdd.push({
-        id: summaryTrackId,
-        engineId: this.engineId,
-        kind: NULL_TRACK_KIND,
+        uri: NULL_TRACK_URI,
+        key: summaryTrackKey,
         trackSortKey: PrimaryTrackSortKey.NULL_TRACK,
         name: groupName,
         trackGroup: undefined,
-        config: {},
       });
 
       const addGroup = Actions.addTrackGroup({
-        engineId: this.engineId,
-        summaryTrackId,
+        summaryTrackKey,
         name: groupName,
         id: value,
         collapsed: true,
@@ -534,15 +470,14 @@
     }
 
     const id = uuidv4();
-    const summaryTrackId = uuidv4();
-    ufsCmdTagTracks[0].id = summaryTrackId;
+    const summaryTrackKey = uuidv4();
+    ufsCmdTagTracks[0].key = summaryTrackKey;
     for (const track of ufsCmdTagTracks) {
       track.trackGroup = id;
     }
 
     const addGroup = Actions.addTrackGroup({
-      engineId: this.engineId,
-      summaryTrackId,
+      summaryTrackKey,
       name: group,
       id,
       collapsed: true,
@@ -580,21 +515,18 @@
 
     for (const [key, value] of devMap) {
       const groupName = key;
-      const summaryTrackId = uuidv4();
+      const summaryTrackKey = uuidv4();
 
       this.tracksToAdd.push({
-        id: summaryTrackId,
-        engineId: this.engineId,
-        kind: NULL_TRACK_KIND,
+        uri: NULL_TRACK_URI,
+        key: summaryTrackKey,
         trackSortKey: PrimaryTrackSortKey.NULL_TRACK,
         name: groupName,
         trackGroup: undefined,
-        config: {},
       });
 
       const addGroup = Actions.addTrackGroup({
-        engineId: this.engineId,
-        summaryTrackId,
+        summaryTrackKey,
         name: groupName,
         id: value,
         collapsed: true,
@@ -614,7 +546,7 @@
             track.trackGroup !== SCROLLING_TRACK_GROUP) {
           continue;
         }
-        if (track.kind === NULL_TRACK_KIND) {
+        if (track.uri === NULL_TRACK_URI) {
           continue;
         }
         if (groupUuid === undefined) {
@@ -625,20 +557,17 @@
     }
 
     if (groupUuid !== undefined) {
-      const summaryTrackId = uuidv4();
+      const summaryTrackKey = uuidv4();
       this.tracksToAdd.push({
-        id: summaryTrackId,
-        engineId: this.engineId,
-        kind: NULL_TRACK_KIND,
+        uri: NULL_TRACK_URI,
+        key: summaryTrackKey,
         trackSortKey: PrimaryTrackSortKey.NULL_TRACK,
         name: groupName,
         trackGroup: undefined,
-        config: {},
       });
 
       const addGroup = Actions.addTrackGroup({
-        engineId: this.engineId,
-        summaryTrackId,
+        summaryTrackKey,
         name: groupName,
         id: groupUuid,
         collapsed: true,
@@ -650,8 +579,8 @@
   async groupMiscNonAllowlistedTracks(groupName: string): Promise<void> {
     // List of allowlisted track names.
     const ALLOWLIST_REGEXES = [
-      new RegExp('^Cpu .*$'),
-      new RegExp('^Gpu .*$'),
+      new RegExp('^Cpu .*$', 'i'),
+      new RegExp('^Gpu .*$', 'i'),
       new RegExp('^Trace Triggers$'),
       new RegExp('^Android App Startups$'),
     ];
@@ -662,7 +591,7 @@
           track.trackGroup !== SCROLLING_TRACK_GROUP) {
         continue;
       }
-      if (track.kind === NULL_TRACK_KIND) {
+      if (track.uri === NULL_TRACK_URI) {
         continue;
       }
       let allowlisted = false;
@@ -679,20 +608,17 @@
     }
 
     if (groupUuid !== undefined) {
-      const summaryTrackId = uuidv4();
+      const summaryTrackKey = uuidv4();
       this.tracksToAdd.push({
-        id: summaryTrackId,
-        engineId: this.engineId,
-        kind: NULL_TRACK_KIND,
+        uri: NULL_TRACK_URI,
+        key: summaryTrackKey,
         trackSortKey: PrimaryTrackSortKey.NULL_TRACK,
         name: groupName,
         trackGroup: undefined,
-        config: {},
       });
 
       const addGroup = Actions.addTrackGroup({
-        engineId: this.engineId,
-        summaryTrackId,
+        summaryTrackKey,
         name: groupName,
         id: groupUuid,
         collapsed: true,
@@ -710,7 +636,7 @@
             track.trackGroup !== SCROLLING_TRACK_GROUP) {
           continue;
         }
-        if (track.kind === NULL_TRACK_KIND) {
+        if (track.uri === NULL_TRACK_URI) {
           continue;
         }
         if (groupUuid === undefined) {
@@ -721,20 +647,17 @@
     }
 
     if (groupUuid !== undefined) {
-      const summaryTrackId = uuidv4();
+      const summaryTrackKey = uuidv4();
       this.tracksToAdd.push({
-        id: summaryTrackId,
-        engineId: this.engineId,
-        kind: NULL_TRACK_KIND,
+        uri: NULL_TRACK_URI,
+        key: summaryTrackKey,
         trackSortKey: PrimaryTrackSortKey.NULL_TRACK,
         name: groupName,
         trackGroup: undefined,
-        config: {},
       });
 
       const addGroup = Actions.addTrackGroup({
-        engineId: this.engineId,
-        summaryTrackId,
+        summaryTrackKey: summaryTrackKey,
         name: groupName,
         id: groupUuid,
         collapsed: true,
@@ -750,13 +673,10 @@
 
     if (count > 0) {
       this.tracksToAdd.push({
-        engineId: this.engineId,
-        kind: PLUGIN_TRACK_KIND,
+        uri: 'perfetto.AndroidLog',
         name: 'Android logs',
         trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK,
         trackGroup: SCROLLING_TRACK_GROUP,
-        config: {},
-        uri: 'perfetto.AndroidLog',
       });
     }
   }
@@ -768,41 +688,35 @@
     const it = result.iter({cpu: NUM});
 
     let groupUuid = undefined;
-    let summaryTrackId = undefined;
+    let summaryTrackKey = undefined;
 
     // use the first one as the summary track
     for (let row = 0; it.valid(); it.next(), row++) {
       if (groupUuid === undefined) {
         groupUuid = 'ftrace-track-group';
-        summaryTrackId = uuidv4();
+        summaryTrackKey = uuidv4();
         this.tracksToAdd.push({
-          engineId: this.engineId,
-          kind: NULL_TRACK_KIND,
+          uri: NULL_TRACK_URI,
           trackSortKey: PrimaryTrackSortKey.NULL_TRACK,
           name: `Ftrace Events`,
           trackGroup: undefined,
-          config: {},
-          id: summaryTrackId,
+          key: summaryTrackKey,
         });
       }
       this.tracksToAdd.push({
-        engineId: this.engineId,
-        kind: PLUGIN_TRACK_KIND,
+        uri: `perfetto.FtraceRaw#cpu${it.cpu}`,
         trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK,
         name: `Ftrace Events Cpu ${it.cpu}`,
         trackGroup: groupUuid,
-        config: {},
-        uri: `perfetto.FtraceRaw#cpu${it.cpu}`,
       });
     }
 
-    if (groupUuid !== undefined && summaryTrackId !== undefined) {
+    if (groupUuid !== undefined && summaryTrackKey !== undefined) {
       const addGroup = Actions.addTrackGroup({
-        engineId: this.engineId,
         name: 'Ftrace Events',
         id: groupUuid,
         collapsed: true,
-        summaryTrackId,
+        summaryTrackKey,
       });
       this.addTrackGroupActions.push(addGroup);
     }
@@ -824,7 +738,7 @@
 
     interface GroupIds {
       id: string;
-      summaryTrackId: string;
+      summaryTrackKey: string;
     }
 
     const groupNameToIds = new Map<string, GroupIds>();
@@ -835,7 +749,7 @@
       const upid = sliceIt.upid;
       const groupName = sliceIt.group_name;
 
-      let summaryTrackId = undefined;
+      let summaryTrackKey = undefined;
       let trackGroupId =
           upid === 0 ? SCROLLING_TRACK_GROUP : this.upidToUuid.get(upid);
 
@@ -848,33 +762,26 @@
           trackGroupId = groupIds.id;
         } else {
           trackGroupId = uuidv4();
-          summaryTrackId = uuidv4();
+          summaryTrackKey = uuidv4();
           groupNameToIds.set(groupName, {
             id: trackGroupId,
-            summaryTrackId,
+            summaryTrackKey,
           });
         }
       }
 
       this.tracksToAdd.push({
-        id: summaryTrackId,
-        engineId: this.engineId,
-        kind: SLICE_TRACK_KIND,
+        uri: `perfetto.Annotation#${id}`,
+        key: summaryTrackKey,
         name,
         trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK,
         trackGroup: trackGroupId,
-        config: {
-          maxDepth: 0,
-          namespace: 'annotation',
-          trackId: id,
-        },
       });
     }
 
     for (const [groupName, groupIds] of groupNameToIds) {
       const addGroup = Actions.addTrackGroup({
-        engineId: this.engineId,
-        summaryTrackId: groupIds.summaryTrackId,
+        summaryTrackKey: groupIds.summaryTrackKey,
         name: groupName,
         id: groupIds.id,
         collapsed: true,
@@ -897,16 +804,11 @@
       const name = counterIt.name;
       const upid = counterIt.upid;
       this.tracksToAdd.push({
-        engineId: this.engineId,
-        kind: PLUGIN_TRACK_KIND,
+        uri: `perfetto.Annotation#counter${id}`,
         name,
         trackSortKey: PrimaryTrackSortKey.COUNTER_TRACK,
         trackGroup: upid === 0 ? SCROLLING_TRACK_GROUP :
                                  this.upidToUuid.get(upid),
-        config: {
-          namespace: 'annotation',
-        },
-        uri: `perfetto.Annotation#counter${id}`,
       });
     }
   }
@@ -951,30 +853,26 @@
       if (showV1()) {
         const kind = THREAD_STATE_TRACK_KIND;
         this.tracksToAdd.push({
-          engineId: this.engineId,
-          kind: THREAD_STATE_TRACK_KIND,
+          uri: `perfetto.ThreadState#${upid}.${utid}`,
           name: getTrackName({utid, tid, threadName, kind}),
           trackGroup: uuid,
           trackSortKey: {
             utid,
             priority,
           },
-          config: {utid, tid},
         });
       }
 
       if (showV2()) {
-        const kind = THREAD_STATE_TRACK_V2_KIND;
         this.tracksToAdd.push({
-          engineId: this.engineId,
-          kind,
-          name: getTrackName({utid, tid, threadName, kind}),
+          uri: `perfetto.ThreadState#${utid}.v2`,
+          name:
+              getTrackName({utid, tid, threadName, kind: 'ThreadStateTrackV2'}),
           trackGroup: uuid,
           trackSortKey: {
             utid,
             priority,
           },
-          config: {utid, tid},
         });
       }
     }
@@ -1008,15 +906,13 @@
       const threadName = it.threadName;
       const uuid = this.getUuid(utid, upid);
       this.tracksToAdd.push({
-        engineId: this.engineId,
-        kind: CPU_PROFILE_TRACK_KIND,
+        uri: `perfetto.CpuProfile#${utid}`,
         trackSortKey: {
           utid,
           priority: InThreadTrackSortKey.CPU_STACK_SAMPLES_TRACK,
         },
         name: `${threadName} (CPU Stack Samples)`,
         trackGroup: uuid,
-        config: {utid},
       });
     }
   }
@@ -1061,16 +957,13 @@
         threadTrack: true,
       });
       this.tracksToAdd.push({
-        engineId: this.engineId,
-        kind: PLUGIN_TRACK_KIND,
+        uri: `perfetto.Counter#thread${trackId}`,
         name,
         trackSortKey: {
           utid,
           priority: InThreadTrackSortKey.ORDINARY,
         },
         trackGroup: uuid,
-        config: {},
-        uri: `perfetto.Counter#thread${trackId}`,
       });
     }
   }
@@ -1112,7 +1005,6 @@
       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;
@@ -1123,20 +1015,18 @@
       }
 
       const uuid = this.getUuid(0, upid);
-
-      const kind = ASYNC_SLICE_TRACK_KIND;
-      const name =
-          getTrackName({name: trackName, upid, pid, processName, kind});
+      const name = getTrackName({
+        name: trackName,
+        upid,
+        pid,
+        processName,
+        kind: ASYNC_SLICE_TRACK_KIND,
+      });
       this.tracksToAdd.push({
-        engineId: this.engineId,
-        kind,
+        uri: `perfetto.AsyncSlices#process.${pid}${rawTrackIds}`,
         name,
         trackSortKey: PrimaryTrackSortKey.ASYNC_SLICE_TRACK,
         trackGroup: uuid,
-        config: {
-          trackIds,
-          maxDepth,
-        },
       });
     }
   }
@@ -1167,7 +1057,6 @@
     const it = result.iter({
       upid: NUM,
       trackName: STR_NULL,
-      trackIds: STR,
       processName: STR_NULL,
       pid: NUM_NULL,
       maxDepth: NUM_NULL,
@@ -1175,8 +1064,6 @@
     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;
@@ -1192,15 +1079,10 @@
       const name =
           getTrackName({name: trackName, upid, pid, processName, kind});
       this.tracksToAdd.push({
-        engineId: this.engineId,
-        kind,
+        uri: `perfetto.ActualFrames#${upid}`,
         name,
         trackSortKey: PrimaryTrackSortKey.ACTUAL_FRAMES_SLICE_TRACK,
         trackGroup: uuid,
-        config: {
-          trackIds,
-          maxDepth,
-        },
       });
     }
   }
@@ -1231,7 +1113,6 @@
     const it = result.iter({
       upid: NUM,
       trackName: STR_NULL,
-      trackIds: STR,
       processName: STR_NULL,
       pid: NUM_NULL,
       maxDepth: NUM_NULL,
@@ -1240,8 +1121,6 @@
     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;
@@ -1257,15 +1136,10 @@
       const name =
           getTrackName({name: trackName, upid, pid, processName, kind});
       this.tracksToAdd.push({
-        engineId: this.engineId,
-        kind,
+        uri: `perfetto.ExpectedFrames#${upid}`,
         name,
         trackSortKey: PrimaryTrackSortKey.EXPECTED_FRAMES_SLICE_TRACK,
         trackGroup: uuid,
-        config: {
-          trackIds,
-          maxDepth,
-        },
       });
     }
   }
@@ -1280,7 +1154,6 @@
                       'is_root_in_scope') as isDefaultTrackForScope,
           tid,
           thread.name as threadName,
-          max(slice.depth) as maxDepth,
           process.upid as upid
         from slice
         join thread_track on slice.track_id = thread_track.id
@@ -1296,7 +1169,6 @@
       isDefaultTrackForScope: NUM_NULL,
       tid: NUM_NULL,
       threadName: STR_NULL,
-      maxDepth: NUM,
       upid: NUM_NULL,
     });
     for (; it.valid(); it.next()) {
@@ -1308,7 +1180,6 @@
       const tid = it.tid;
       const threadName = it.threadName;
       const upid = it.upid;
-      const maxDepth = it.maxDepth;
 
       const uuid = this.getUuid(utid, upid);
 
@@ -1316,8 +1187,7 @@
       const name = getTrackName({name: trackName, utid, tid, threadName, kind});
       if (showV1()) {
         this.tracksToAdd.push({
-          engineId: this.engineId,
-          kind,
+          uri: `perfetto.ChromeSlices#${trackId}`,
           name,
           trackGroup: uuid,
           trackSortKey: {
@@ -1326,18 +1196,12 @@
                 InThreadTrackSortKey.DEFAULT_TRACK :
                 InThreadTrackSortKey.ORDINARY,
           },
-          config: {
-            trackId,
-            maxDepth,
-            tid,
-          },
         });
       }
 
       if (showV2()) {
         this.tracksToAdd.push({
-          engineId: this.engineId,
-          kind: 'GenericSliceTrack',
+          uri: `perfetto.ChromeSlices#${trackId}.v2`,
           name,
           trackGroup: uuid,
           trackSortKey: {
@@ -1346,7 +1210,6 @@
                 InThreadTrackSortKey.DEFAULT_TRACK :
                 InThreadTrackSortKey.ORDINARY,
           },
-          config: {sqlTrackId: trackId},
         });
       }
     }
@@ -1380,14 +1243,11 @@
       const name = getTrackName(
           {name: trackName, upid, pid, kind: COUNTER_TRACK_KIND, processName});
       this.tracksToAdd.push({
-        engineId: this.engineId,
-        kind: PLUGIN_TRACK_KIND,
+        uri: `perfetto.Counter#process${trackId}`,
         name,
         trackSortKey: await this.resolveTrackSortKeyForProcessCounterTrack(
             upid, trackName || undefined),
         trackGroup: uuid,
-        config: {},
-        uri: `perfetto.Counter#process${trackId}`,
       });
     }
   }
@@ -1402,12 +1262,10 @@
       const upid = it.upid;
       const uuid = this.getUuid(0, upid);
       this.tracksToAdd.push({
-        engineId: this.engineId,
-        kind: HEAP_PROFILE_TRACK_KIND,
+        uri: `perfetto.HeapProfile#${upid}`,
         trackSortKey: PrimaryTrackSortKey.HEAP_PROFILE_TRACK,
         name: `Heap Profile`,
         trackGroup: uuid,
-        config: {upid},
       });
     }
   }
@@ -1423,12 +1281,10 @@
       const pid = it.pid;
       const uuid = this.getUuid(0, upid);
       this.tracksToAdd.push({
-        engineId: this.engineId,
-        kind: PERF_SAMPLES_PROFILE_TRACK_KIND,
+        uri: `perfetto.PerfSamplesProfile#${upid}`,
         trackSortKey: PrimaryTrackSortKey.PERF_SAMPLES_PROFILE_TRACK,
         name: `Callstacks ${pid}`,
         trackGroup: uuid,
-        config: {upid},
       });
     }
   }
@@ -1501,19 +1357,15 @@
     // but creating a dedicated track type is out of scope at the time of
     // writing.
     const kthreadGroupUuid = uuidv4();
-    const summaryTrackId = uuidv4();
+    const summaryTrackKey = uuidv4();
     this.tracksToAdd.push({
-      id: summaryTrackId,
-      engineId: this.engineId,
-      kind: PLUGIN_TRACK_KIND,
+      uri: 'perfetto.ProcessSummary#kernel',
+      key: summaryTrackKey,
       trackSortKey: PrimaryTrackSortKey.PROCESS_SUMMARY_TRACK,
       name: `Kernel thread summary`,
-      config: {},
-      uri: 'perfetto.ProcessSummary#kernel',
     });
     const addTrackGroup = Actions.addTrackGroup({
-      engineId: this.engineId,
-      summaryTrackId,
+      summaryTrackKey,
       name: `Kernel threads`,
       id: kthreadGroupUuid,
       collapsed: true,
@@ -1675,28 +1527,24 @@
       // These should only happen once for each track group.
       if (pUuid === undefined) {
         pUuid = this.getOrCreateUuid(utid, upid);
-        const summaryTrackId = uuidv4();
+        const summaryTrackKey = uuidv4();
         const type = hasSched ? 'schedule' : 'summary';
         const uri = `perfetto.ProcessScheduling#${upid}.${utid}.${type}`;
 
         this.tracksToAdd.push({
-          id: summaryTrackId,
-          engineId: this.engineId,
-          kind: PLUGIN_TRACK_KIND,
+          uri,
+          key: summaryTrackKey,
           trackSortKey: hasSched ?
               PrimaryTrackSortKey.PROCESS_SCHEDULING_TRACK :
               PrimaryTrackSortKey.PROCESS_SUMMARY_TRACK,
           name: `${upid === null ? tid : pid} summary`,
-          config: {},
           labels: it.chromeProcessLabels.split(','),
-          uri,
         });
 
         const name =
             getTrackName({utid, processName, pid, threadName, tid, upid});
         const addTrackGroup = Actions.addTrackGroup({
-          engineId: this.engineId,
-          summaryTrackId,
+          summaryTrackKey,
           name,
           id: pUuid,
           // Perf profiling tracks remain collapsed, otherwise we would have too
@@ -1737,49 +1585,29 @@
     return threadOrderingMetadata;
   }
 
-  private async defineMaxLayoutDepthSqlFunction(): Promise<void> {
-    await this.engine.query(`
-      select create_function(
-        'max_layout_depth(track_count INT, track_ids STRING)',
-        'INT',
-        '
-          select iif(
-            $track_count = 1,
-            (
-              select max(depth)
-              from slice
-              where track_id = cast($track_ids AS int)
-            ),
-            (
-              select max(layout_depth)
-              from experimental_slice_layout($track_ids)
-            )
-          );
-        '
-      );
-    `);
-  }
-
   addPluginTracks(): void {
     const tracks = pluginManager.findPotentialTracks();
     for (const info of tracks) {
       this.tracksToAdd.push({
-        engineId: this.engineId,
-        kind: PLUGIN_TRACK_KIND,
-        name: info.name,
         uri: info.uri,
+        name: info.displayName,
         // TODO(hjd): Fix how sorting works. Plugins should expose
         // 'sort keys' which the user can use to choose a sort order.
-        trackSortKey: info.sortKey,
+        trackSortKey: info.sortKey ?? PrimaryTrackSortKey.ORDINARY_TRACK,
         trackGroup: SCROLLING_TRACK_GROUP,
-        config: {},
       });
     }
   }
 
-  async decideTracks(): Promise<DeferredAction[]> {
-    await this.defineMaxLayoutDepthSqlFunction();
+  async addScrollJankPluginTracks(): Promise<void> {
+    if (ENABLE_SCROLL_JANK_PLUGIN_V2.get()) {
+      const result = await getScrollJankTracks(this.engine);
+      this.tracksToAdd = this.tracksToAdd.concat(result.tracks.tracksToAdd);
+      this.addTrackGroupActions.push(result.addTrackGroup);
+    }
+  }
 
+  async decideTracks(): Promise<DeferredAction[]> {
     {
       const result = screenshotDecideTracks(this.engine);
       if (result !== null) {
@@ -1789,6 +1617,7 @@
     }
 
     // Add first the global tracks that don't require per-process track groups.
+    await this.addScrollJankPluginTracks();
     await this.addCpuSchedulingTracks();
     await this.addFtraceTrack(
         this.engine.getProxy('TrackDecider::addFtraceTrack'));
diff --git a/ui/src/controller/visualised_args_controller.ts b/ui/src/controller/visualised_args_controller.ts
deleted file mode 100644
index 8e709f5..0000000
--- a/ui/src/controller/visualised_args_controller.ts
+++ /dev/null
@@ -1,133 +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 {v4 as uuidv4} from 'uuid';
-
-import {Actions, AddTrackArgs} from '../common/actions';
-import {Engine} from '../common/engine';
-import {NUM} from '../common/query_result';
-import {InThreadTrackSortKey} from '../common/state';
-import {globals} from '../frontend/globals';
-import {
-  VISUALISED_ARGS_SLICE_TRACK_KIND,
-} from '../tracks/visualised_args/index';
-
-import {Controller} from './controller';
-
-export interface VisualisedArgControllerArgs {
-  argName: string;
-  engine: Engine;
-}
-
-export class VisualisedArgController extends Controller<'init'|'running'> {
-  private engine: Engine;
-  private argName: string;
-  private escapedArgName: string;
-  private tableName: string;
-  private addedTrackIds: string[];
-
-  constructor(args: VisualisedArgControllerArgs) {
-    super('init');
-    this.argName = args.argName;
-    this.engine = args.engine;
-    this.escapedArgName = this.argName.replace(/[^a-zA-Z]/g, '_');
-    this.tableName = `__arg_visualisation_helper_${this.escapedArgName}_slice`;
-    this.addedTrackIds = [];
-  }
-
-  onDestroy() {
-    this.engine.query(`drop table if exists ${this.tableName}`);
-    globals.dispatch(
-        Actions.removeVisualisedArgTracks({trackIds: this.addedTrackIds}));
-  }
-
-  async createTracks() {
-    const result = await this.engine.query(`
-        drop table if exists ${this.tableName};
-
-        create table ${this.tableName} as
-        with slice_with_arg as (
-          select
-            slice.id,
-            slice.track_id,
-            slice.ts,
-            slice.dur,
-            slice.thread_dur,
-            NULL as cat,
-            args.display_value as name
-          from slice
-          join args using (arg_set_id)
-          where args.key='${this.argName}'
-        )
-        select
-          *,
-          (select count()
-           from ancestor_slice(s1.id) s2
-           join slice_with_arg s3 on s2.id=s3.id
-          ) as depth
-        from slice_with_arg s1
-        order by id;
-
-        select
-          track_id as trackId,
-          max(depth) as maxDepth
-        from ${this.tableName}
-        group by track_id;
-    `);
-
-    const tracksToAdd: AddTrackArgs[] = [];
-    const it = result.iter({'trackId': NUM, 'maxDepth': NUM});
-    for (; it.valid(); it.next()) {
-      const track =
-          globals.state
-              .tracks[globals.state.uiTrackIdByTraceTrackId[it.trackId]];
-      const utid = (track.trackSortKey as {utid?: number}).utid;
-      const id = uuidv4();
-      this.addedTrackIds.push(id);
-      tracksToAdd.push({
-        id,
-        trackGroup: track.trackGroup,
-        engineId: this.engine.id,
-        kind: VISUALISED_ARGS_SLICE_TRACK_KIND,
-        name: this.argName,
-        trackSortKey: utid === undefined ?
-            track.trackSortKey :
-            {utid, priority: InThreadTrackSortKey.VISUALISED_ARGS_TRACK},
-        config: {
-          maxDepth: it.maxDepth,
-          namespace: `__arg_visualisation_helper_${this.escapedArgName}`,
-          trackId: it.trackId,
-          argName: this.argName,
-          tid: (track.config as {tid?: number}).tid,
-        },
-      });
-    }
-    globals.dispatch(Actions.addTracks({tracks: tracksToAdd}));
-    globals.dispatch(Actions.sortThreadTracks({}));
-  }
-
-  run() {
-    switch (this.state) {
-      case 'init':
-        this.createTracks();
-        this.setState('running');
-        break;
-      case 'running':
-        // Nothing to do here.
-        break;
-      default:
-        throw new Error(`Unexpected state ${this.state}`);
-    }
-  }
-}
diff --git a/ui/src/frontend/app.ts b/ui/src/frontend/app.ts
index 955df02..04d8012 100644
--- a/ui/src/frontend/app.ts
+++ b/ui/src/frontend/app.ts
@@ -336,14 +336,14 @@
                                      .find(({uri}) => uri === selectedUri);
               if (firstTrack) {
                 console.log(firstTrack);
-                verticalScrollToTrack(firstTrack.id, true);
+                verticalScrollToTrack(firstTrack.key, true);
                 const traceTime = globals.stateTraceTimeTP();
                 globals.makeSelection(
                     Actions.selectArea({
                       area: {
                         start: traceTime.start,
                         end: traceTime.end,
-                        tracks: [firstTrack.id],
+                        tracks: [firstTrack.key],
                       },
                     }),
                 );
diff --git a/ui/src/frontend/base_counter_track.ts b/ui/src/frontend/base_counter_track.ts
new file mode 100644
index 0000000..0f441de
--- /dev/null
+++ b/ui/src/frontend/base_counter_track.ts
@@ -0,0 +1,549 @@
+// 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 {searchSegment} from '../base/binary_search';
+import {assertTrue} from '../base/logging';
+import {duration, Time, time} from '../base/time';
+import {drawTrackHoverTooltip} from '../common/canvas_utils';
+import {raf} from '../core/raf_scheduler';
+import {LONG, NUM} from '../public';
+import {CounterScaleOptions} from '../tracks/counter';
+import {Button} from '../widgets/button';
+import {MenuItem, PopupMenu2} from '../widgets/menu';
+
+import {checkerboardExcept} from './checkerboard';
+import {globals} from './globals';
+import {constraintsToQuerySuffix} from './sql_utils';
+import {NewTrackArgs, TrackBase} from './track';
+import {CacheKey, TrackCache} from './track_cache';
+
+interface CounterData {
+  timestamps: BigInt64Array;
+  minValues: Float64Array;
+  maxValues: Float64Array;
+  lastValues: Float64Array;
+  totalDeltas: Float64Array;
+  rate: Float64Array;
+  maximumValue: number;
+  minimumValue: number;
+  maximumDelta: number;
+  minimumDelta: number;
+  maximumRate: number;
+  minimumRate: number;
+}
+
+// 0.5 Makes the horizontal lines sharp.
+const MARGIN_TOP = 3.5;
+
+export abstract class BaseCounterTrack<Config> extends TrackBase<Config> {
+  protected readonly tableName: string;
+
+  // This is the over-skirted cached bounds:
+  private countersKey: CacheKey = CacheKey.zero();
+
+  private counters: CounterData = {
+    timestamps: new BigInt64Array(0),
+    minValues: new Float64Array(0),
+    maxValues: new Float64Array(0),
+    lastValues: new Float64Array(0),
+    totalDeltas: new Float64Array(0),
+    rate: new Float64Array(0),
+    maximumValue: 0,
+    minimumValue: 0,
+    maximumDelta: 0,
+    minimumDelta: 0,
+    maximumRate: 0,
+    minimumRate: 0,
+  };
+
+  private cache: TrackCache<CounterData> = new TrackCache(5);
+
+  private sqlState: 'UNINITIALIZED'|'INITIALIZING'|'QUERY_PENDING'|
+      'QUERY_DONE' = 'UNINITIALIZED';
+  private isDestroyed: boolean = false;
+
+  private maximumValueSeen = 0;
+  private minimumValueSeen = 0;
+  private maximumDeltaSeen = 0;
+  private minimumDeltaSeen = 0;
+  private maxDurNs: duration = 0n;
+
+  private mousePos = {x: 0, y: 0};
+  private hoveredValue: number|undefined = undefined;
+  private hoveredTs: time|undefined = undefined;
+  private hoveredTsEnd: time|undefined = undefined;
+
+  private scale?: CounterScaleOptions;
+
+  // Extension points.
+  abstract initSqlTable(_tableName: string): Promise<void>;
+
+  constructor(args: NewTrackArgs) {
+    super(args);
+    this.tableName = `track_${this.trackKey}`.replace(/[^a-zA-Z0-9_]+/g, '_');
+  }
+
+  getHeight() {
+    return 30;
+  }
+
+  getCounterContextMenuItems(): m.Children {
+    const currentScale = this.scale;
+    const scales: {name: CounterScaleOptions, humanName: string}[] = [
+      {name: 'ZERO_BASED', humanName: 'Zero based'},
+      {name: 'MIN_MAX', humanName: 'Min/Max'},
+      {name: 'DELTA_FROM_PREVIOUS', humanName: 'Delta'},
+      {name: 'RATE', humanName: 'Rate'},
+    ];
+    return scales.map((scale) => {
+      return m(MenuItem, {
+        label: scale.humanName,
+        active: currentScale === scale.name,
+        onclick: () => {
+          this.scale = scale.name;
+          raf.scheduleFullRedraw();
+        },
+      });
+    });
+  }
+
+  getCounterContextMenu(): m.Child {
+    return m(
+        PopupMenu2,
+        {
+          trigger: m(Button, {icon: 'show_chart', minimal: true}),
+        },
+        this.getCounterContextMenuItems(),
+    );
+  }
+
+  getTrackShellButtons(): m.Children {
+    return this.getCounterContextMenu();
+  }
+
+  renderCanvas(ctx: CanvasRenderingContext2D) {
+    const {
+      visibleTimeScale: timeScale,
+      visibleWindowTime: vizTime,
+      windowSpan,
+    } = globals.frontendLocalState;
+
+    {
+      const windowSizePx = Math.max(1, timeScale.pxSpan.delta);
+      const rawStartNs = vizTime.start.toTime();
+      const rawEndNs = vizTime.end.toTime();
+      const rawCountersKey =
+          CacheKey.create(rawStartNs, rawEndNs, windowSizePx);
+
+      // If the visible time range is outside the cached area, requests
+      // asynchronously new data from the SQL engine.
+      this.maybeRequestData(rawCountersKey);
+    }
+
+    // In any case, draw whatever we have (which might be stale/incomplete).
+
+    if (this.counters === undefined || this.counters.timestamps.length === 0) {
+      return;
+    }
+
+    const data = this.counters;
+    assertTrue(data.timestamps.length === data.minValues.length);
+    assertTrue(data.timestamps.length === data.maxValues.length);
+    assertTrue(data.timestamps.length === data.lastValues.length);
+    assertTrue(data.timestamps.length === data.totalDeltas.length);
+    assertTrue(data.timestamps.length === data.rate.length);
+
+    const scale: CounterScaleOptions = this.scale ?? 'ZERO_BASED';
+
+    let minValues = data.minValues;
+    let maxValues = data.maxValues;
+    let lastValues = data.lastValues;
+    let maximumValue = data.maximumValue;
+    let minimumValue = data.minimumValue;
+    if (scale === 'DELTA_FROM_PREVIOUS') {
+      lastValues = data.totalDeltas;
+      minValues = data.totalDeltas;
+      maxValues = data.totalDeltas;
+      maximumValue = data.maximumDelta;
+      minimumValue = data.minimumDelta;
+    }
+    if (scale === 'RATE') {
+      lastValues = data.rate;
+      minValues = data.rate;
+      maxValues = data.rate;
+      maximumValue = data.maximumRate;
+      minimumValue = data.minimumRate;
+    }
+
+    const effectiveHeight = this.getHeight() - MARGIN_TOP;
+    const endPx = windowSpan.end;
+    const zeroY = MARGIN_TOP + effectiveHeight / (minimumValue < 0 ? 2 : 1);
+
+    // Quantize the Y axis to quarters of powers of tens (7.5K, 10K, 12.5K).
+    const maxValue = Math.max(maximumValue, 0);
+
+    let yMax = Math.max(Math.abs(minimumValue), maxValue);
+    const kUnits = ['', 'K', 'M', 'G', 'T', 'E'];
+    const exp = Math.ceil(Math.log10(Math.max(yMax, 1)));
+    const pow10 = Math.pow(10, exp);
+    yMax = Math.ceil(yMax / (pow10 / 4)) * (pow10 / 4);
+    let yRange = 0;
+    const unitGroup = Math.floor(exp / 3);
+    let yMin = 0;
+    let yLabel = '';
+    if (scale === 'MIN_MAX') {
+      yRange = maximumValue - minimumValue;
+      yMin = minimumValue;
+      yLabel = 'min - max';
+    } else {
+      yRange = minimumValue < 0 ? yMax * 2 : yMax;
+      yMin = minimumValue < 0 ? -yMax : 0;
+      yLabel = `${yMax / Math.pow(10, unitGroup * 3)} ${kUnits[unitGroup]}`;
+      if (scale === 'DELTA_FROM_PREVIOUS') {
+        yLabel += '\u0394';
+      } else if (scale === 'RATE') {
+        yLabel += '\u0394/t';
+      }
+    }
+
+    // There are 360deg of hue. We want a scale that starts at green with
+    // exp <= 3 (<= 1KB), goes orange around exp = 6 (~1MB) and red/violet
+    // around exp >= 9 (1GB).
+    // The hue scale looks like this:
+    // 0                              180                                 360
+    // Red        orange         green | blue         purple          magenta
+    // So we want to start @ 180deg with pow=0, go down to 0deg and then wrap
+    // back from 360deg back to 180deg.
+    const expCapped = Math.min(Math.max(exp - 3), 9);
+    const hue = (180 - Math.floor(expCapped * (180 / 6)) + 360) % 360;
+
+    ctx.fillStyle = `hsl(${hue}, 45%, 75%)`;
+    ctx.strokeStyle = `hsl(${hue}, 45%, 45%)`;
+
+    const calculateX = (ts: time) => {
+      return Math.floor(timeScale.timeToPx(ts));
+    };
+    const calculateY = (value: number) => {
+      return MARGIN_TOP + effectiveHeight -
+          Math.round(((value - yMin) / yRange) * effectiveHeight);
+    };
+
+    ctx.beginPath();
+    const timestamp = Time.fromRaw(data.timestamps[0]);
+    ctx.moveTo(calculateX(timestamp), zeroY);
+    let lastDrawnY = zeroY;
+    for (let i = 0; i < this.counters.timestamps.length; i++) {
+      const timestamp = Time.fromRaw(data.timestamps[i]);
+      const x = calculateX(timestamp);
+      const minY = calculateY(minValues[i]);
+      const maxY = calculateY(maxValues[i]);
+      const lastY = calculateY(lastValues[i]);
+
+      ctx.lineTo(x, lastDrawnY);
+      if (minY === maxY) {
+        assertTrue(lastY === minY);
+        ctx.lineTo(x, lastY);
+      } else {
+        ctx.lineTo(x, minY);
+        ctx.lineTo(x, maxY);
+        ctx.lineTo(x, lastY);
+      }
+      lastDrawnY = lastY;
+    }
+    ctx.lineTo(endPx, lastDrawnY);
+    ctx.lineTo(endPx, zeroY);
+    ctx.closePath();
+    ctx.fill();
+    ctx.stroke();
+
+    // Draw the Y=0 dashed line.
+    ctx.strokeStyle = `hsl(${hue}, 10%, 71%)`;
+    ctx.beginPath();
+    ctx.setLineDash([2, 4]);
+    ctx.moveTo(0, zeroY);
+    ctx.lineTo(endPx, zeroY);
+    ctx.closePath();
+    ctx.stroke();
+    ctx.setLineDash([]);
+
+    ctx.font = '10px Roboto Condensed';
+
+    if (this.hoveredValue !== undefined && this.hoveredTs !== undefined) {
+      // TODO(hjd): Add units.
+      let text: string;
+      if (scale === 'DELTA_FROM_PREVIOUS') {
+        text = 'delta: ';
+      } else if (scale === 'RATE') {
+        text = 'delta/t: ';
+      } else {
+        text = 'value: ';
+      }
+
+      text += `${this.hoveredValue.toLocaleString()}`;
+
+      ctx.fillStyle = `hsl(${hue}, 45%, 75%)`;
+      ctx.strokeStyle = `hsl(${hue}, 45%, 45%)`;
+
+      const xStart = Math.floor(timeScale.timeToPx(this.hoveredTs));
+      const xEnd = this.hoveredTsEnd === undefined ?
+          endPx :
+          Math.floor(timeScale.timeToPx(this.hoveredTsEnd));
+      const y = MARGIN_TOP + effectiveHeight -
+          Math.round(((this.hoveredValue - yMin) / yRange) * effectiveHeight);
+
+      // Highlight line.
+      ctx.beginPath();
+      ctx.moveTo(xStart, y);
+      ctx.lineTo(xEnd, y);
+      ctx.lineWidth = 3;
+      ctx.stroke();
+      ctx.lineWidth = 1;
+
+      // Draw change marker.
+      ctx.beginPath();
+      ctx.arc(
+          xStart, y, 3 /* r*/, 0 /* start angle*/, 2 * Math.PI /* end angle*/);
+      ctx.fill();
+      ctx.stroke();
+
+      // Draw the tooltip.
+      drawTrackHoverTooltip(ctx, this.mousePos, this.getHeight(), text);
+    }
+
+    // Write the Y scale on the top left corner.
+    ctx.fillStyle = 'rgba(255, 255, 255, 0.6)';
+    ctx.fillRect(0, 0, 42, 16);
+    ctx.fillStyle = '#666';
+    ctx.textAlign = 'left';
+    ctx.textBaseline = 'alphabetic';
+    ctx.fillText(`${yLabel}`, 5, 14);
+
+    // TODO(hjd): Refactor this into checkerboardExcept
+    {
+      const counterEndPx = Infinity;
+      // Grey out RHS.
+      if (counterEndPx < endPx) {
+        ctx.fillStyle = '#0000001f';
+        ctx.fillRect(counterEndPx, 0, endPx - counterEndPx, this.getHeight());
+      }
+    }
+
+    // If the cached trace slices don't fully cover the visible time range,
+    // show a gray rectangle with a "Loading..." label.
+    checkerboardExcept(
+        ctx,
+        this.getHeight(),
+        windowSpan.start,
+        windowSpan.end,
+        timeScale.timeToPx(this.countersKey.start),
+        timeScale.timeToPx(this.countersKey.end));
+  }
+
+  onMouseMove(pos: {x: number, y: number}) {
+    const data = this.counters;
+    if (data === undefined) return;
+    this.mousePos = pos;
+    const {visibleTimeScale} = globals.frontendLocalState;
+    const time = visibleTimeScale.pxToHpTime(pos.x);
+
+    let values = data.lastValues;
+    if (this.scale === 'DELTA_FROM_PREVIOUS') {
+      values = data.totalDeltas;
+    }
+    if (this.scale === 'RATE') {
+      values = data.rate;
+    }
+
+    const [left, right] = searchSegment(data.timestamps, time.toTime());
+    this.hoveredTs =
+        left === -1 ? undefined : Time.fromRaw(data.timestamps[left]);
+    this.hoveredTsEnd =
+        right === -1 ? undefined : Time.fromRaw(data.timestamps[right]);
+    this.hoveredValue = left === -1 ? undefined : values[left];
+  }
+
+  onMouseOut() {
+    this.hoveredValue = undefined;
+    this.hoveredTs = undefined;
+  }
+
+  // The underlying table has `ts` and `value` columns, but we also want to
+  // query `dur` and `delta` - we create a CTE to help with that.
+  private getSqlPreamble(): string {
+    return `
+      WITH data AS (
+        SELECT
+          ts,
+          value,
+          lead(ts, 1, ts) over (order by ts) - ts as dur,
+          lead(value, 1, value) over (order by ts) - value as delta
+        FROM ${this.tableName}
+      )
+    `;
+  }
+
+  private async maybeRequestData(rawCountersKey: CacheKey) {
+    // Important: this method is async and is invoked on every frame. Care
+    // must be taken to avoid piling up queries on every frame, hence the FSM.
+    // TODO(altimin): Currently this is a copy of the logic in base_slice_track.
+    // Consider merging it.
+    if (this.sqlState === 'UNINITIALIZED') {
+      this.sqlState = 'INITIALIZING';
+
+      if (this.isDestroyed) {
+        return;
+      }
+      await this.initSqlTable(this.tableName);
+
+      if (this.isDestroyed) {
+        return;
+      }
+
+      {
+        const queryRes = (await this.engine.query(`
+          ${this.getSqlPreamble()}
+          SELECT
+            ifnull(max(value), 0) as maxValue,
+            ifnull(min(value), 0) as minValue,
+            ifnull(max(delta), 0) as maxDelta,
+            ifnull(min(delta), 0) as minDelta,
+            max(
+              iif(dur != -1, dur, (select end_ts from trace_bounds) - ts)
+            ) as maxDur
+          FROM data
+        `)).firstRow({
+          maxValue: NUM,
+          minValue: NUM,
+          maxDelta: NUM,
+          minDelta: NUM,
+          maxDur: LONG,
+        });
+
+        this.minimumValueSeen = queryRes.minValue;
+        this.maximumValueSeen = queryRes.maxValue;
+        this.minimumDeltaSeen = queryRes.minDelta;
+        this.maximumDeltaSeen = queryRes.maxDelta;
+        this.maxDurNs = queryRes.maxDur;
+      }
+
+      this.sqlState = 'QUERY_DONE';
+    } else if (
+        this.sqlState === 'INITIALIZING' || this.sqlState === 'QUERY_PENDING') {
+      return;
+    }
+
+    if (rawCountersKey.isCoveredBy(this.countersKey)) {
+      return;  // We have the data already, no need to re-query.
+    }
+
+    const countersKey = rawCountersKey.normalize();
+    if (!rawCountersKey.isCoveredBy(countersKey)) {
+      throw new Error(`Normalization error ${countersKey.toString()} ${
+          rawCountersKey.toString()}`);
+    }
+
+    const maybeCachedCounters = this.cache.lookup(countersKey);
+    if (maybeCachedCounters) {
+      this.countersKey = countersKey;
+      this.counters = maybeCachedCounters;
+    }
+
+    this.sqlState = 'QUERY_PENDING';
+    const bucketNs = countersKey.bucketSize;
+
+    const constraint = constraintsToQuerySuffix({
+      filters: [
+        `ts >= ${countersKey.start} - ${this.maxDurNs}`,
+        `ts <= ${countersKey.end}`,
+      ],
+      groupBy: [
+        'tsq',
+      ],
+      orderBy: [
+        'tsq',
+      ],
+    });
+
+    if (this.isDestroyed) {
+      this.sqlState = 'QUERY_DONE';
+      return;
+    }
+
+    const queryRes = await this.engine.query(`
+      ${this.getSqlPreamble()}
+      SELECT
+        (ts + ${bucketNs / 2n}) / ${bucketNs} * ${bucketNs} as tsq,
+        min(value) as minValue,
+        max(value) as maxValue,
+        sum(delta) as totalDelta,
+        value_at_max_ts(ts, value) as lastValue
+      FROM data
+      ${constraint}
+    `);
+
+    const it = queryRes.iter({
+      tsq: LONG,
+      minValue: NUM,
+      maxValue: NUM,
+      totalDelta: NUM,
+      lastValue: NUM,
+    });
+
+    const numRows = queryRes.numRows();
+    const data: CounterData = {
+      maximumValue: this.maximumValueSeen,
+      minimumValue: this.minimumValueSeen,
+      maximumDelta: this.maximumDeltaSeen,
+      minimumDelta: this.minimumDeltaSeen,
+      maximumRate: 0,
+      minimumRate: 0,
+      timestamps: new BigInt64Array(numRows),
+      minValues: new Float64Array(numRows),
+      maxValues: new Float64Array(numRows),
+      lastValues: new Float64Array(numRows),
+      totalDeltas: new Float64Array(numRows),
+      rate: new Float64Array(numRows),
+    };
+
+    let lastValue = 0;
+    let lastTs = 0n;
+    for (let row = 0; it.valid(); it.next(), row++) {
+      const ts = Time.fromRaw(it.tsq);
+      const value = it.lastValue;
+      const rate = (value - lastValue) / (Time.toSeconds(Time.sub(ts, lastTs)));
+      lastTs = ts;
+      lastValue = value;
+
+      data.timestamps[row] = ts;
+      data.minValues[row] = it.minValue;
+      data.maxValues[row] = it.maxValue;
+      data.lastValues[row] = value;
+      data.totalDeltas[row] = it.totalDelta;
+      data.rate[row] = rate;
+      if (row > 0) {
+        data.rate[row - 1] = rate;
+        data.maximumRate = Math.max(data.maximumRate, rate);
+        data.minimumRate = Math.min(data.minimumRate, rate);
+      }
+    }
+
+    this.cache.insert(countersKey, data);
+    this.counters = data;
+
+    this.sqlState = 'QUERY_DONE';
+    raf.scheduleRedraw();
+  }
+}
diff --git a/ui/src/frontend/base_slice_track.ts b/ui/src/frontend/base_slice_track.ts
index e95fb20..efef610 100644
--- a/ui/src/frontend/base_slice_track.ts
+++ b/ui/src/frontend/base_slice_track.ts
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {Disposable, NullDisposable} from '../base/disposable';
 import {assertExists} from '../base/logging';
 import {
   duration,
@@ -144,12 +145,14 @@
 // If you need temporally overlapping slices, look at AsyncSliceTrack, which
 // merges several tracks into one visual track.
 export const BASE_SLICE_ROW = {
-  id: NUM,       // The slice ID, for selection / lookups.
+  id: NUM,     // The slice ID, for selection / lookups.
+  ts: LONG,    // Start time in nanoseconds.
+  dur: LONG,   // Duration in nanoseconds. -1 = incomplete, 0 = instant.
+  depth: NUM,  // Vertical depth.
+
+  // These are computed by the base class:
   tsq: LONG,     // Quantized |ts|. This class owns the quantization logic.
   tsqEnd: LONG,  // Quantized |ts+dur|. The end bucket.
-  ts: LONG,      // Start time in nanoseconds.
-  dur: LONG,     // Duration in nanoseconds. -1 = incomplete, 0 = instant.
-  depth: NUM,    // Vertical depth.
 };
 
 export type BaseSliceRow = typeof BASE_SLICE_ROW;
@@ -203,7 +206,6 @@
   // than just remembering it when we see it.
   private selectedSlice?: CastInternal<T['slice']>;
 
-  protected readonly tableName: string;
   private maxDurNs: duration = 0n;
 
   private sqlState: 'UNINITIALIZED'|'INITIALIZING'|'QUERY_PENDING'|
@@ -227,11 +229,30 @@
   // TODO(hjd): Replace once we have cancellable query sequences.
   private isDestroyed = false;
 
+  // Cleanup hook for onInit.
+  private initState?: Disposable;
+
   // Extension points.
   // Each extension point should take a dedicated argument type (e.g.,
   // OnSliceOverArgs {slice?: T['slice']}) so it makes future extensions
   // non-API-breaking (e.g. if we want to add the X position).
-  abstract initSqlTable(_tableName: string): Promise<void>;
+
+  // onInit hook lets you do asynchronous set up e.g. creating a table
+  // etc. We guarantee that this will be resolved before doing any
+  // queries using the result of getSqlSource(). All persistent
+  // state in trace_processor should be cleaned up when dispose is
+  // called on the returned hook. In the common case of where
+  // the data for this track is d
+  async onInit(): Promise<Disposable> {
+    return new NullDisposable();
+  }
+
+  // This should be an SQL expression returning all the columns listed
+  // metioned by getRowSpec() exluding tsq and tsqEnd.
+  // For example you might return an SQL expression of the form:
+  // `select id, ts, dur, 0 as depth from foo where bar = 'baz'`
+  abstract getSqlSource(): string;
+
   getRowSpec(): T['row'] {
     return BASE_SLICE_ROW;
   }
@@ -256,11 +277,6 @@
 
   constructor(args: NewTrackArgs) {
     super(args);
-    this.frontendOnly = true;  // Disable auto checkerboarding.
-    // TODO(hjd): Handle pinned tracks, which current cause a crash
-    // since the tableName we generate is the same for both.
-    this.tableName = `track_${this.trackId}`.replace(/[^a-zA-Z0-9_]+/g, '_');
-
     // Work out the extra columns.
     // This is the union of the embedder-defined columns and the base columns
     // we know about (ts, dur, ...).
@@ -508,7 +524,10 @@
   onDestroy() {
     super.onDestroy();
     this.isDestroyed = true;
-    this.engine.query(`DROP VIEW IF EXISTS ${this.tableName}`);
+    if (this.initState) {
+      this.initState.dispose();
+      this.initState = undefined;
+    }
   }
 
   // This method figures out if the visible window is outside the bounds of
@@ -523,14 +542,14 @@
       if (this.isDestroyed) {
         return;
       }
-      await this.initSqlTable(this.tableName);
+      this.initState = await this.onInit();
 
       if (this.isDestroyed) {
         return;
       }
       const queryRes = await this.engine.query(`select
           ifnull(max(dur), 0) as maxDur, count(1) as rowCount
-          from ${this.tableName}`);
+          from (${this.getSqlSource()})`);
       const row = queryRes.firstRow({maxDur: LONG, rowCount: NUM});
       this.maxDurNs = row.maxDur;
 
@@ -563,7 +582,7 @@
             id,
             ${this.depthColumn()}
             ${extraCols ? ',' + extraCols : ''}
-          from ${this.tableName}
+          from (${this.getSqlSource()})
           where dur = -1;
         `);
         const incomplete =
@@ -650,7 +669,7 @@
         id,
         ${this.depthColumn()}
         ${extraCols ? ',' + extraCols : ''}
-      FROM ${this.tableName} ${constraint}
+      FROM (${this.getSqlSource()}) ${constraint}
     `);
 
     // Here convert each row to a Slice. We do what we can do
diff --git a/ui/src/frontend/chrome_slice_details_tab.ts b/ui/src/frontend/chrome_slice_details_tab.ts
index 93b5e15..e4210fe 100644
--- a/ui/src/frontend/chrome_slice_details_tab.ts
+++ b/ui/src/frontend/chrome_slice_details_tab.ts
@@ -15,12 +15,13 @@
 import m from 'mithril';
 
 import {Icons} from '../base/semantic_icons';
-import {duration, Time} from '../base/time';
+import {duration, Time, TimeSpan} from '../base/time';
 import {exists} from '../base/utils';
 import {EngineProxy} from '../common/engine';
 import {runQuery} from '../common/queries';
 import {LONG, LONG_NULL, NUM, STR_NULL} from '../common/query_result';
-import {addDebugTrack} from '../tracks/debug/slice_track';
+import {raf} from '../core/raf_scheduler';
+import {addDebugSliceTrack} from '../tracks/debug/slice_track';
 import {Button} from '../widgets/button';
 import {DetailsShell} from '../widgets/details_shell';
 import {DurationWidget} from '../widgets/duration';
@@ -39,6 +40,10 @@
 import {renderArguments} from './slice_args';
 import {renderDetails} from './slice_details';
 import {getSlice, SliceDetails, SliceRef} from './sql/slice';
+import {
+  BreakdownByThreadState,
+  breakDownIntervalByThreadState,
+} from './sql/thread_state';
 import {asSliceSqlId} from './sql_types';
 
 interface ContextMenuItem {
@@ -99,12 +104,14 @@
     run: (slice: SliceDetails) => {
       const engine = getEngine();
       if (engine === undefined) return;
-      runQuery(`
+      runQuery(
+          `
         INCLUDE PERFETTO MODULE android.binder;
         INCLUDE PERFETTO MODULE android.monitor_contention;
-      `, engine)
+      `,
+          engine)
           .then(
-              () => addDebugTrack(
+              () => addDebugSliceTrack(
                   engine,
                   {
                     sqlSource: `
@@ -138,7 +145,8 @@
                                   JOIN thread ON thread.utid = thread_track.utid
                                   JOIN process ON process.upid = thread.upid
                                   WHERE process.pid = ${getPidFromSlice(slice)}
-                                        AND thread.tid = ${getTidFromSlice(slice)}
+                                        AND thread.tid = ${
+                        getTidFromSlice(slice)}
                                         AND short_blocked_method IS NOT NULL
                                   ORDER BY depth
                                 ) SELECT ts, dur, name FROM merged`,
@@ -197,7 +205,7 @@
     name: it.name ?? 'null',
     ts: Time.fromRaw(it.ts),
     dur: it.dur,
-    sqlTrackId: it.trackId,
+    trackId: it.trackId,
     threadDur: it.threadDur ?? undefined,
     category: it.cat ?? undefined,
     absTime: it.absTime ?? undefined,
@@ -223,6 +231,7 @@
   static readonly kind = 'dev.perfetto.ChromeSliceDetailsTab';
 
   private sliceDetails?: SliceDetails;
+  private breakdownByThreadState?: BreakdownByThreadState;
 
   static create(args: NewBottomTabArgs): ChromeSliceDetailsTab {
     return new ChromeSliceDetailsTab(args);
@@ -230,11 +239,23 @@
 
   constructor(args: NewBottomTabArgs) {
     super(args);
+    this.load();
+  }
 
+  async load() {
     // Start loading the slice details
     const {id, table} = this.config;
-    getSliceDetails(this.engine, id, table)
-        .then((sliceDetails) => this.sliceDetails = sliceDetails);
+    const details = await getSliceDetails(this.engine, id, table);
+
+    if (details !== undefined && details.thread !== undefined) {
+      this.breakdownByThreadState = await breakDownIntervalByThreadState(
+          this.engine,
+          TimeSpan.fromTimeAndDuration(details.ts, details.dur),
+          details.thread.utid);
+    }
+
+    this.sliceDetails = details;
+    raf.scheduleFullRedraw();
   }
 
   getTitle(): string {
@@ -242,24 +263,23 @@
   }
 
   viewTab() {
-    if (exists(this.sliceDetails)) {
-      const slice = this.sliceDetails;
-      return m(
-          DetailsShell,
-          {
-            title: 'Slice',
-            description: slice.name,
-            buttons: this.renderContextButton(slice),
-          },
-          m(
-              GridLayout,
-              renderDetails(slice),
-              this.renderRhs(this.engine, slice),
-              ),
-      );
-    } else {
+    if (!exists(this.sliceDetails)) {
       return m(DetailsShell, {title: 'Slice', description: 'Loading...'});
     }
+    const slice = this.sliceDetails;
+    return m(
+        DetailsShell,
+        {
+          title: 'Slice',
+          description: slice.name,
+          buttons: this.renderContextButton(slice),
+        },
+        m(
+            GridLayout,
+            renderDetails(slice, this.breakdownByThreadState),
+            this.renderRhs(this.engine, slice),
+            ),
+    );
   }
 
   isLoading() {
diff --git a/ui/src/frontend/flow_events_panel.ts b/ui/src/frontend/flow_events_panel.ts
index 7bf78ce..af17ae7 100644
--- a/ui/src/frontend/flow_events_panel.ts
+++ b/ui/src/frontend/flow_events_panel.ts
@@ -44,11 +44,10 @@
     }
 
     const flowClickHandler = (sliceId: number, trackId: number) => {
-      const uiTrackId = globals.state.uiTrackIdByTraceTrackId[trackId];
-      if (uiTrackId) {
+      const trackKey = globals.state.trackKeyByTrackId[trackId];
+      if (trackKey) {
         globals.makeSelection(
-            Actions.selectChromeSlice(
-                {id: sliceId, trackId: uiTrackId, table: 'slice'}),
+            Actions.selectChromeSlice({id: sliceId, trackKey, table: 'slice'}),
             {tab: 'bound_flows'});
       }
     };
diff --git a/ui/src/frontend/flow_events_renderer.ts b/ui/src/frontend/flow_events_renderer.ts
index c414454..f06704e 100644
--- a/ui/src/frontend/flow_events_renderer.ts
+++ b/ui/src/frontend/flow_events_renderer.ts
@@ -12,10 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {TrackState} from 'src/common/state';
-
 import {time} from '../base/time';
 import {pluginManager} from '../common/plugins';
+import {TrackState} from '../common/state';
 
 import {TRACK_SHELL_WIDTH} from './css_constants';
 import {ALL_CATEGORIES, getFlowCategories} from './flow_events_panel';
@@ -54,16 +53,8 @@
   height: number;
 }
 
-function hasTrackId(obj: {}): obj is {trackId: number} {
-  return (obj as {trackId?: number}).trackId !== undefined;
-}
-
-function hasManyTrackIds(obj: {}): obj is {trackIds: number[]} {
-  return (obj as {trackIds?: number}).trackIds !== undefined;
-}
-
-function hasId(obj: {}): obj is {id: number} {
-  return (obj as {id?: number}).id !== undefined;
+function hasTrackKey(obj: {}): obj is {trackKey: number} {
+  return (obj as {trackKey?: number}).trackKey !== undefined;
 }
 
 function hasTrackGroupId(obj: {}): obj is {trackGroupId: string} {
@@ -71,19 +62,8 @@
 }
 
 function getTrackIds(track: TrackState): number[] {
-  if (track.uri) {
-    const trackInfo = pluginManager.resolveTrackInfo(track.uri);
-    if (trackInfo?.trackIds) return trackInfo?.trackIds;
-  } else {
-    const config = track.config;
-    if (hasTrackId(config)) {
-      return [config.trackId];
-    }
-    if (hasManyTrackIds(config)) {
-      return config.trackIds;
-    }
-  }
-  return [];
+  const trackDesc = pluginManager.resolveTrackInfo(track.uri);
+  return trackDesc?.trackIds ?? [];
 }
 
 export class FlowEventsRendererArgs {
@@ -96,11 +76,22 @@
   }
 
   registerPanel(panel: PanelVNode, yStart: number, height: number) {
-    if (panel.state instanceof TrackPanel && hasId(panel.attrs)) {
-      const track = globals.state.tracks[panel.attrs.id];
+    if (panel.state instanceof TrackPanel && hasTrackKey(panel.attrs)) {
+      const track = globals.state.tracks[panel.attrs.trackKey];
       for (const trackId of getTrackIds(track)) {
         this.trackIdToTrackPanel.set(trackId, {panel: panel.state, yStart});
       }
+
+      // Register new "plugin track" ids
+      const trackState = globals.state.tracks[panel.attrs.trackKey];
+      if (trackState.uri) {
+        const trackInfo = pluginManager.resolveTrackInfo(trackState.uri);
+        if (trackInfo?.trackIds) {
+          for (const trackId of trackInfo.trackIds) {
+            this.trackIdToTrackPanel.set(trackId, {panel: panel.state, yStart});
+          }
+        }
+      }
     } else if (
         panel.state instanceof TrackGroupPanel &&
         hasTrackGroupId(panel.attrs)) {
@@ -112,8 +103,8 @@
 
 export class FlowEventsRenderer {
   private getTrackGroupIdByTrackId(trackId: number): string|undefined {
-    const uiTrackId = globals.state.uiTrackIdByTraceTrackId[trackId];
-    return uiTrackId ? globals.state.tracks[uiTrackId].trackGroup : undefined;
+    const trackKey = globals.state.trackKeyByTrackId[trackId];
+    return trackKey ? globals.state.tracks[trackKey].trackGroup : undefined;
   }
 
   private getTrackGroupYCoordinate(
diff --git a/ui/src/frontend/frontend_local_state.ts b/ui/src/frontend/frontend_local_state.ts
index d9d36d9..fb6d045 100644
--- a/ui/src/frontend/frontend_local_state.ts
+++ b/ui/src/frontend/frontend_local_state.ts
@@ -159,7 +159,7 @@
   showCookieConsent = false;
   visibleTracks = new Set<string>();
   prevVisibleTracks = new Set<string>();
-  scrollToTrackId?: string|number;
+  scrollToTrackKey?: string|number;
   httpRpcState: HttpRpcState = {connected: false};
   newVersionAvailable = false;
 
@@ -193,8 +193,8 @@
     raf.scheduleFullRedraw();
   }
 
-  addVisibleTrack(trackId: string) {
-    this.visibleTracks.add(trackId);
+  addVisibleTrack(trackKey: string) {
+    this.visibleTracks.add(trackKey);
   }
 
   // Called when beginning a canvas redraw.
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index 037a65f..b1b6d3e 100644
--- a/ui/src/frontend/globals.ts
+++ b/ui/src/frontend/globals.ts
@@ -283,7 +283,7 @@
     sliceIds: new Float64Array(0),
     tsStarts: new BigInt64Array(0),
     utids: new Float64Array(0),
-    trackIds: [],
+    trackKeys: [],
     sources: [],
     totalResults: 0,
   };
@@ -663,7 +663,7 @@
       sliceIds: new Float64Array(0),
       tsStarts: new BigInt64Array(0),
       utids: new Float64Array(0),
-      trackIds: [],
+      trackKeys: [],
       sources: [],
       totalResults: 0,
     };
diff --git a/ui/src/frontend/keyboard_event_handler.ts b/ui/src/frontend/keyboard_event_handler.ts
index 92863c3..e27f83c 100644
--- a/ui/src/frontend/keyboard_event_handler.ts
+++ b/ui/src/frontend/keyboard_event_handler.ts
@@ -172,12 +172,11 @@
   for (const flow of globals.connectedFlows) {
     if (flow.id === flowId) {
       const flowPoint = (direction === 'Backward' ? flow.begin : flow.end);
-      const uiTrackId =
-          globals.state.uiTrackIdByTraceTrackId[flowPoint.trackId];
-      if (uiTrackId) {
+      const trackKey = globals.state.trackKeyByTrackId[flowPoint.trackId];
+      if (trackKey) {
         globals.makeSelection(Actions.selectChromeSlice({
           id: flowPoint.sliceId,
-          trackId: uiTrackId,
+          trackKey,
           table: 'slice',
           scroll: true,
         }));
@@ -190,8 +189,8 @@
   const range = globals.findTimeRangeOfSelection();
   if (range.start !== -1n && range.end !== -1n &&
       globals.state.currentSelection !== null) {
-    const tracks = globals.state.currentSelection.trackId ?
-        [globals.state.currentSelection.trackId] :
+    const tracks = globals.state.currentSelection.trackKey ?
+        [globals.state.currentSelection.trackKey] :
         [];
     const area: Area = {start: range.start, end: range.end, tracks};
     globals.dispatch(Actions.markArea({area, persistent}));
@@ -207,7 +206,7 @@
     focusHorizontalRange(range.start, range.end);
   }
 
-  if (selection.trackId) {
-    verticalScrollToTrack(selection.trackId, true);
+  if (selection.trackKey) {
+    verticalScrollToTrack(selection.trackKey, true);
   }
 }
diff --git a/ui/src/frontend/legacy_trace_viewer.ts b/ui/src/frontend/legacy_trace_viewer.ts
index e5242a9..657758f 100644
--- a/ui/src/frontend/legacy_trace_viewer.ts
+++ b/ui/src/frontend/legacy_trace_viewer.ts
@@ -14,7 +14,10 @@
 
 import m from 'mithril';
 import {inflate} from 'pako';
+
 import {assertTrue} from '../base/logging';
+import {isString} from '../base/object_utils';
+
 import {globals} from './globals';
 import {showModal} from './modal';
 
@@ -43,7 +46,7 @@
   return new Promise((resolve, reject) => {
     const reader = new FileReader();
     reader.onload = () => {
-      if (typeof reader.result === 'string') {
+      if (isString(reader.result)) {
         return resolve(reader.result);
       }
     };
diff --git a/ui/src/frontend/named_slice_track.ts b/ui/src/frontend/named_slice_track.ts
index 1601b70..759acb9 100644
--- a/ui/src/frontend/named_slice_track.ts
+++ b/ui/src/frontend/named_slice_track.ts
@@ -70,7 +70,7 @@
   onSliceClick(args: OnSliceClickArgs<T['slice']>) {
     globals.makeSelection(Actions.selectChromeSlice({
       id: args.slice.id,
-      trackId: this.trackId,
+      trackKey: this.trackKey,
 
       // |table| here can be either 'slice' or 'annotation'. The
       // AnnotationSliceTrack overrides the onSliceClick and sets this to
diff --git a/ui/src/frontend/omnibox.ts b/ui/src/frontend/omnibox.ts
index fc36b56..634abc9 100644
--- a/ui/src/frontend/omnibox.ts
+++ b/ui/src/frontend/omnibox.ts
@@ -16,6 +16,7 @@
 
 import {classNames} from '../base/classnames';
 import {FuzzySegment} from '../base/fuzzy';
+import {isString} from '../base/object_utils';
 import {exists} from '../base/utils';
 import {raf} from '../core/raf_scheduler';
 import {EmptyState} from '../widgets/empty_state';
@@ -62,7 +63,7 @@
   }
 
   private renderTitle(title: FuzzySegment[]|string): m.Children {
-    if (typeof title === 'string') {
+    if (isString(title)) {
       return title;
     } else {
       return title.map(({matching, value}) => {
diff --git a/ui/src/frontend/pivot_table_query_generator.ts b/ui/src/frontend/pivot_table_query_generator.ts
index b59aebe..8641c11 100644
--- a/ui/src/frontend/pivot_table_query_generator.ts
+++ b/ui/src/frontend/pivot_table_query_generator.ts
@@ -21,7 +21,7 @@
   PivotTableState,
 } from '../common/state';
 import {
-  getSelectedTrackIds,
+  getSelectedTrackKeys,
 } from '../controller/aggregation/slice_aggregation_controller';
 
 import {globals} from './globals';
@@ -101,7 +101,7 @@
   return [
     `ts + dur > ${area.start}`,
     `ts < ${area.end}`,
-    `track_id in (${getSelectedTrackIds(area).join(', ')})`,
+    `track_id in (${getSelectedTrackKeys(area).join(', ')})`,
   ];
 }
 
diff --git a/ui/src/frontend/post_message_handler.ts b/ui/src/frontend/post_message_handler.ts
index ed9d078..c6b5546 100644
--- a/ui/src/frontend/post_message_handler.ts
+++ b/ui/src/frontend/post_message_handler.ts
@@ -22,6 +22,8 @@
 import {showModal} from './modal';
 import {focusHorizontalRange} from './scroll_helper';
 
+const TRUSTED_ORIGINS_KEY = 'trustedOrigins';
+
 interface PostedTraceWrapped {
   perfetto: PostedTrace;
 }
@@ -40,6 +42,7 @@
   ];
   if (origin === window.origin) return true;
   if (TRUSTED_ORIGINS.includes(origin)) return true;
+  if (isUserTrustedOrigin(origin)) return true;
 
   const hostname = new URL(origin).hostname;
   if (hostname.endsWith('corp.google.com')) return true;
@@ -47,6 +50,33 @@
   return false;
 }
 
+// Returns whether the user saved this as an always-trusted origin.
+function isUserTrustedOrigin(hostname: string): boolean {
+  const trustedOrigins = window.localStorage.getItem(TRUSTED_ORIGINS_KEY);
+  if (trustedOrigins === null) return false;
+  try {
+    return JSON.parse(trustedOrigins).includes(hostname);
+  } catch {
+    return false;
+  }
+}
+
+// Saves the given hostname as a trusted origin.
+// This is used for user convenience: if it fails for any reason, it's not a
+// big deal.
+function saveUserTrustedOrigin(hostname: string) {
+  const s = window.localStorage.getItem(TRUSTED_ORIGINS_KEY);
+  let origins: string[];
+  try {
+    origins = JSON.parse(s || '[]');
+    if (origins.includes(hostname)) return;
+    origins.push(hostname);
+    window.localStorage.setItem(TRUSTED_ORIGINS_KEY, JSON.stringify(origins));
+  } catch (e) {
+    console.warn('unable to save trusted origins to localStorage', e);
+  }
+}
+
 // Returns whether we should ignore a given message based on the value of
 // the 'perfettoIgnore' field in the event data.
 function shouldGracefullyIgnoreMessage(messageEvent: MessageEvent) {
@@ -162,6 +192,11 @@
     globals.dispatch(Actions.openTraceFromBuffer(postedTrace));
   };
 
+  const trustAndOpenTrace = () => {
+    saveUserTrustedOrigin(messageEvent.origin);
+    openTrace();
+  };
+
   // If the origin is trusted open the trace directly.
   if (isTrustedOrigin(messageEvent.origin)) {
     openTrace();
@@ -176,8 +211,9 @@
           m('div', `${messageEvent.origin} is trying to open a trace file.`),
           m('div', 'Do you trust the origin and want to proceed?')),
     buttons: [
-      {text: 'NO', primary: true},
-      {text: 'YES', primary: false, action: openTrace},
+      {text: 'No', primary: true},
+      {text: 'Yes', primary: false, action: openTrace},
+      {text: 'Always trust', primary: false, action: trustAndOpenTrace},
     ],
   });
 }
diff --git a/ui/src/frontend/query_history.ts b/ui/src/frontend/query_history.ts
index 59690e9..4bc3480 100644
--- a/ui/src/frontend/query_history.ts
+++ b/ui/src/frontend/query_history.ts
@@ -32,18 +32,20 @@
 
 export interface QueryHistoryComponentAttrs {
   runQuery: (query: string) => void;
+  setQuery: (query: string) => void;
 }
 
 export class QueryHistoryComponent implements
     m.ClassComponent<QueryHistoryComponentAttrs> {
   view({attrs}: m.CVnode<QueryHistoryComponentAttrs>): m.Child {
     const runQuery = attrs.runQuery;
+    const setQuery = attrs.setQuery;
     const unstarred: HistoryItemComponentAttrs[] = [];
     const starred: HistoryItemComponentAttrs[] = [];
     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});
+      arr.push({index: i, entry, runQuery, setQuery});
     }
     return m(
         '.query-history',
@@ -58,6 +60,7 @@
   index: number;
   entry: QueryHistoryEntry;
   runQuery: (query: string) => void;
+  setQuery: (query: string) => void;
 }
 
 export class HistoryItemComponent implements
@@ -80,6 +83,11 @@
               ),
           m('button',
             {
+              onclick: () => vnode.attrs.setQuery(query),
+            },
+            m(Icon, {icon: 'edit'})),
+          m('button',
+            {
               onclick: () => vnode.attrs.runQuery(query),
             },
             m(Icon, {icon: 'play_arrow'})),
diff --git a/ui/src/frontend/query_page.ts b/ui/src/frontend/query_page.ts
index 97a655d..711ef17 100644
--- a/ui/src/frontend/query_page.ts
+++ b/ui/src/frontend/query_page.ts
@@ -36,11 +36,13 @@
   executedQuery?: string;
   queryResult?: QueryResponse;
   heightPx: string;
+  generation: number;
 }
 
 const state: QueryPageState = {
   enteredText: '',
   heightPx: '100px',
+  generation: 0,
 };
 
 function runManualQuery(query: string) {
@@ -99,6 +101,7 @@
 
   view() {
     return m(Editor, {
+      generation: state.generation,
       initialText: state.enteredText,
 
       onExecute: (text: string) => {
@@ -112,6 +115,7 @@
       onUpdate: (text: string) => {
         state.enteredText = text;
       },
+
     });
   }
 }
@@ -134,6 +138,11 @@
         }),
         m(QueryHistoryComponent, {
           runQuery: runManualQuery,
+          setQuery: (q: string) => {
+            state.enteredText = q;
+            state.generation++;
+            raf.scheduleFullRedraw();
+          },
         }));
   },
 });
diff --git a/ui/src/frontend/query_table.ts b/ui/src/frontend/query_table.ts
index c84e7af..6676794 100644
--- a/ui/src/frontend/query_table.ts
+++ b/ui/src/frontend/query_table.ts
@@ -17,6 +17,7 @@
 
 import {BigintMath} from '../base/bigint_math';
 import {copyToClipboard} from '../base/clipboard';
+import {isString} from '../base/object_utils';
 import {Duration, Time} from '../base/time';
 import {Actions} from '../common/actions';
 import {QueryResponse} from '../common/queries';
@@ -57,7 +58,7 @@
 }
 
 function hasType(row: Row): row is Row&{type: string} {
-  return ('type' in row && typeof row.type === 'string');
+  return ('type' in row && isString(row.type));
 }
 
 function hasId(row: Row): row is Row&{id: Numeric} {
@@ -141,20 +142,20 @@
     const sliceStart = Time.fromRaw(BigInt(row.ts));
     // row.dur can be negative. Clamp to 1ns.
     const sliceDur = BigintMath.max(BigInt(row.dur), 1n);
-    const uiTrackId = globals.state.uiTrackIdByTraceTrackId[trackId];
-    if (uiTrackId !== undefined) {
-      reveal(uiTrackId, sliceStart, Time.add(sliceStart, sliceDur), true);
+    const trackKey = globals.state.trackKeyByTrackId[trackId];
+    if (trackKey !== undefined) {
+      reveal(trackKey, sliceStart, Time.add(sliceStart, sliceDur), true);
       const sliceId = getSliceId(row);
       if (sliceId !== undefined) {
-        this.selectSlice(sliceId, uiTrackId, nextTab);
+        this.selectSlice(sliceId, trackKey, nextTab);
       }
     }
   }
 
-  private selectSlice(sliceId: number, uiTrackId: string, nextTab?: string) {
+  private selectSlice(sliceId: number, trackKey: string, nextTab?: string) {
     const action = Actions.selectChromeSlice({
       id: sliceId,
-      trackId: uiTrackId,
+      trackKey,
       table: 'slice',
     });
     globals.makeSelection(action, {tab: nextTab});
diff --git a/ui/src/frontend/scroll_helper.ts b/ui/src/frontend/scroll_helper.ts
index 38c5eb4..dccad06 100644
--- a/ui/src/frontend/scroll_helper.ts
+++ b/ui/src/frontend/scroll_helper.ts
@@ -109,9 +109,9 @@
 // track is nested inside a track group, scroll to that track group instead.
 // If |openGroup| then open the track group and scroll to the track.
 export function verticalScrollToTrack(
-    trackId: string|number, openGroup = false) {
-  const trackIdString = `${trackId}`;
-  const track = document.querySelector('#track_' + trackIdString);
+    trackKey: string|number, openGroup = false) {
+  const trackKeyString = `${trackKey}`;
+  const track = document.querySelector('#track_' + trackKeyString);
 
   if (track) {
     // block: 'nearest' means that it will only scroll if the track is not
@@ -121,13 +121,13 @@
   }
 
   let trackGroup = null;
-  const trackGroupId = getContainingTrackId(globals.state, trackIdString);
+  const trackGroupId = getContainingTrackId(globals.state, trackKeyString);
   if (trackGroupId) {
     trackGroup = document.querySelector('#track_' + trackGroupId);
   }
 
   if (!trackGroupId || !trackGroup) {
-    console.error(`Can't scroll, track (${trackIdString}) not found.`);
+    console.error(`Can't scroll, track (${trackKeyString}) not found.`);
     return;
   }
 
@@ -135,7 +135,7 @@
   // group and scroll to the track or just scroll to the track group.
   if (openGroup) {
     // After the track exists in the dom, it will be scrolled to.
-    globals.frontendLocalState.scrollToTrackId = trackId;
+    globals.frontendLocalState.scrollToTrackKey = trackKey;
     globals.dispatch(Actions.toggleTrackGroupCollapsed({trackGroupId}));
     return;
   } else {
@@ -144,18 +144,18 @@
 }
 
 
-// Scroll vertically and horizontally to reach track (|trackId|) at |ts|.
+// Scroll vertically and horizontally to reach track (|trackKey|) at |ts|.
 export function scrollToTrackAndTs(
-    trackId: string|number|undefined, ts: time, openGroup = false) {
-  if (trackId !== undefined) {
-    verticalScrollToTrack(trackId, openGroup);
+    trackKey: string|number|undefined, ts: time, openGroup = false) {
+  if (trackKey !== undefined) {
+    verticalScrollToTrack(trackKey, openGroup);
   }
   horizontalScrollToTs(ts);
 }
 
 // Scroll vertically and horizontally to a track and time range
 export function reveal(
-    trackId: string|number, start: time, end: time, openGroup = false) {
-  verticalScrollToTrack(trackId, openGroup);
+    trackKey: string|number, start: time, end: time, openGroup = false) {
+  verticalScrollToTrack(trackKey, openGroup);
   focusHorizontalRange(start, end);
 }
diff --git a/ui/src/frontend/search_handler.ts b/ui/src/frontend/search_handler.ts
index c0ef384..76dd4bf 100644
--- a/ui/src/frontend/search_handler.ts
+++ b/ui/src/frontend/search_handler.ts
@@ -80,18 +80,18 @@
   const searchIndex = globals.state.searchIndex;
   const source = globals.currentSearchResults.sources[searchIndex];
   const currentId = globals.currentSearchResults.sliceIds[searchIndex];
-  const trackId = globals.currentSearchResults.trackIds[searchIndex];
+  const trackKey = globals.currentSearchResults.trackKeys[searchIndex];
 
   if (currentId === undefined) return;
 
   if (source === 'cpu') {
     globals.makeSelection(
-        Actions.selectSlice({id: currentId, trackId, scroll: true}),
+        Actions.selectSlice({id: currentId, trackKey, scroll: true}),
         {clearSearch: false},
     );
   } else if (source === 'log') {
     globals.makeSelection(
-        Actions.selectLog({id: currentId, trackId, scroll: true}),
+        Actions.selectLog({id: currentId, trackKey, scroll: true}),
         {clearSearch: false},
     );
   } else {
@@ -99,7 +99,7 @@
     // When we include annotations we need to pass the correct table.
     globals.makeSelection(
         Actions.selectChromeSlice(
-            {id: currentId, trackId, table: 'slice', scroll: true}),
+            {id: currentId, trackKey, table: 'slice', scroll: true}),
         {clearSearch: false},
     );
   }
diff --git a/ui/src/frontend/sidebar.ts b/ui/src/frontend/sidebar.ts
index 6fc7615..9960af0 100644
--- a/ui/src/frontend/sidebar.ts
+++ b/ui/src/frontend/sidebar.ts
@@ -15,6 +15,7 @@
 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';
@@ -769,8 +770,8 @@
         let css = '';
         let attrs = {
           onclick: typeof item.a === 'function' ? item.a : null,
-          href: typeof item.a === 'string' ? item.a : '#',
-          target: typeof item.a === 'string' ? '_blank' : null,
+          href: isString(item.a) ? item.a : '#',
+          target: isString(item.a) ? '_blank' : null,
           disabled: false,
           id: item.t.toLowerCase().replace(/[^\w]/g, '_'),
         };
diff --git a/ui/src/frontend/slice_args.ts b/ui/src/frontend/slice_args.ts
index 5bb8a8c..893fc5b 100644
--- a/ui/src/frontend/slice_args.ts
+++ b/ui/src/frontend/slice_args.ts
@@ -13,13 +13,21 @@
 // limitations under the License.
 
 import m from 'mithril';
+import {v4 as uuidv4} from 'uuid';
 
+import {isString} from '../base/object_utils';
 import {Icons} from '../base/semantic_icons';
 import {sqliteString} from '../base/string_utils';
 import {exists} from '../base/utils';
-import {Actions} from '../common/actions';
+import {Actions, AddTrackArgs} from '../common/actions';
 import {EngineProxy} from '../common/engine';
+import {NUM} from '../common/query_result';
+import {InThreadTrackSortKey} from '../common/state';
 import {ArgNode, convertArgsToTree, Key} from '../controller/args_parser';
+import {
+  VISUALISED_ARGS_SLICE_TRACK_URI,
+  VisualisedArgsState,
+} from '../tracks/visualised_args';
 import {Anchor} from '../widgets/anchor';
 import {MenuItem, PopupMenu2} from '../widgets/menu';
 import {Section} from '../widgets/section';
@@ -62,7 +70,7 @@
       return m(
           TreeNode,
           {
-            left: renderArgKey(stringifyKey(key), value),
+            left: renderArgKey(engine, stringifyKey(key), value),
             right: exists(value) && renderArgValue(value),
             summary: children && renderSummary(children),
           },
@@ -72,7 +80,8 @@
   });
 }
 
-function renderArgKey(key: string, value?: Arg): m.Children {
+function renderArgKey(
+    engine: EngineProxy, key: string, value?: Arg): m.Children {
   if (value === undefined) {
     return key;
   } else {
@@ -107,13 +116,84 @@
           label: 'Visualise argument values',
           icon: 'query_stats',
           onclick: () => {
-            globals.dispatch(Actions.addVisualisedArg({argName: fullKey}));
+            addVisualisedArg(engine, fullKey);
           },
         }),
     );
   }
 }
 
+async function addVisualisedArg(engine: EngineProxy, argName: string) {
+  const escapedArgName = argName.replace(/[^a-zA-Z]/g, '_');
+  const tableName = `__arg_visualisation_helper_${escapedArgName}_slice`;
+
+  const result = await engine.query(`
+        drop table if exists ${tableName};
+
+        create table ${tableName} as
+        with slice_with_arg as (
+          select
+            slice.id,
+            slice.track_id,
+            slice.ts,
+            slice.dur,
+            slice.thread_dur,
+            NULL as cat,
+            args.display_value as name
+          from slice
+          join args using (arg_set_id)
+          where args.key='${argName}'
+        )
+        select
+          *,
+          (select count()
+           from ancestor_slice(s1.id) s2
+           join slice_with_arg s3 on s2.id=s3.id
+          ) as depth
+        from slice_with_arg s1
+        order by id;
+
+        select
+          track_id as trackId,
+          max(depth) as maxDepth
+        from ${tableName}
+        group by track_id;
+    `);
+
+  const tracksToAdd: AddTrackArgs[] = [];
+  const it = result.iter({'trackId': NUM, 'maxDepth': NUM});
+  const addedTrackKeys: string[] = [];
+  for (; it.valid(); it.next()) {
+    const track =
+        globals.state.tracks[globals.state.trackKeyByTrackId[it.trackId]];
+    const utid = (track.trackSortKey as {utid?: number}).utid;
+    const key = uuidv4();
+    addedTrackKeys.push(key);
+
+    const params: VisualisedArgsState = {
+      maxDepth: it.maxDepth,
+      trackId: it.trackId,
+      argName: argName,
+    };
+
+    tracksToAdd.push({
+      key,
+      trackGroup: track.trackGroup,
+      name: argName,
+      trackSortKey: utid === undefined ?
+          track.trackSortKey :
+          {utid, priority: InThreadTrackSortKey.VISUALISED_ARGS_TRACK},
+      params,
+      uri: VISUALISED_ARGS_SLICE_TRACK_URI,
+    });
+  }
+
+  globals.dispatchMultiple([
+    Actions.addTracks({tracks: tracksToAdd}),
+    Actions.sortThreadTracks({}),
+  ]);
+}
+
 function renderArgValue({value}: Arg): m.Children {
   if (isWebLink(value)) {
     return renderWebLink(value);
@@ -145,7 +225,7 @@
 }
 
 function isWebLink(value: unknown): value is string {
-  return typeof value === 'string' &&
+  return isString(value) &&
       (value.startsWith('http://') || value.startsWith('https://'));
 }
 
diff --git a/ui/src/frontend/slice_details.ts b/ui/src/frontend/slice_details.ts
index 1f782a7..6056bbc 100644
--- a/ui/src/frontend/slice_details.ts
+++ b/ui/src/frontend/slice_details.ts
@@ -28,6 +28,10 @@
 import {addTab} from './bottom_tab';
 import {globals} from './globals';
 import {SliceDetails} from './sql/slice';
+import {
+  BreakdownByThreadState,
+  BreakdownByThreadStateTreeNode,
+} from './sql/thread_state';
 import {SqlTableTab} from './sql_table/tab';
 import {SqlTables} from './sql_table/well_known_tables';
 import {getProcessName, getThreadName} from './thread_and_process_info';
@@ -44,7 +48,8 @@
 
 // Renders a widget storing all of the generic details for a slice from the
 // slice table.
-export function renderDetails(slice: SliceDetails) {
+export function renderDetails(
+    slice: SliceDetails, durationBreakdown?: BreakdownByThreadState) {
   return m(
       Section,
       {title: 'Details'},
@@ -84,10 +89,18 @@
           }),
           exists(slice.absTime) &&
               m(TreeNode, {left: 'Absolute Time', right: slice.absTime}),
-          m(TreeNode, {
-            left: 'Duration',
-            right: computeDuration(slice.ts, slice.dur),
-          }),
+          m(
+              TreeNode,
+              {
+                left: 'Duration',
+                right: computeDuration(slice.ts, slice.dur),
+              },
+              exists(durationBreakdown) && slice.dur > 0 &&
+                  m(BreakdownByThreadStateTreeNode, {
+                    data: durationBreakdown,
+                    dur: slice.dur,
+                  }),
+              ),
           renderThreadDuration(slice),
           slice.thread && m(TreeNode, {
             left: 'Thread',
diff --git a/ui/src/frontend/slice_details_panel.ts b/ui/src/frontend/slice_details_panel.ts
index 28bd796..c212249 100644
--- a/ui/src/frontend/slice_details_panel.ts
+++ b/ui/src/frontend/slice_details_panel.ts
@@ -15,7 +15,9 @@
 import m from 'mithril';
 
 import {Actions} from '../common/actions';
+import {pluginManager} from '../common/plugins';
 import {translateState} from '../common/thread_state';
+import {THREAD_STATE_TRACK_KIND} from '../tracks/thread_state';
 import {Anchor} from '../widgets/anchor';
 import {DetailsShell} from '../widgets/details_shell';
 import {DurationWidget} from '../widgets/duration';
@@ -204,21 +206,23 @@
       return;
     }
 
-    let trackId: string|number|undefined;
+    let trackKey: string|number|undefined;
     for (const track of Object.values(globals.state.tracks)) {
-      if (track.kind === 'ThreadStateTrack' &&
-          (track.config as {utid: number}).utid === threadInfo.utid) {
-        trackId = track.id;
+      const trackDesc = pluginManager.resolveTrackInfo(track.uri);
+      // TODO(stevegolton): Handle v2.
+      if (trackDesc && trackDesc.kind === THREAD_STATE_TRACK_KIND &&
+          trackDesc.utid === threadInfo.utid) {
+        trackKey = track.key;
       }
     }
 
-    if (trackId && sliceInfo.threadStateId) {
+    if (trackKey && sliceInfo.threadStateId) {
       globals.makeSelection(Actions.selectThreadState({
         id: sliceInfo.threadStateId,
-        trackId: trackId.toString(),
+        trackKey: trackKey.toString(),
       }));
 
-      scrollToTrackAndTs(trackId, sliceInfo.ts, true);
+      scrollToTrackAndTs(trackKey, sliceInfo.ts, true);
     }
   }
 
diff --git a/ui/src/frontend/slice_track_base.ts b/ui/src/frontend/slice_track_base.ts
new file mode 100644
index 0000000..99eddc3
--- /dev/null
+++ b/ui/src/frontend/slice_track_base.ts
@@ -0,0 +1,339 @@
+// 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 {duration, Span, Time, time} from '../base/time';
+import {Actions} from '../common/actions';
+import {BasicAsyncTrack} from '../common/basic_async_track';
+import {cropText, drawIncompleteSlice} from '../common/canvas_utils';
+import {
+  colorForThreadIdleSlice,
+  getColorForSlice,
+} from '../common/colorizer';
+import {HighPrecisionTime} from '../common/high_precision_time';
+import {TrackData} from '../common/track_data';
+
+import {checkerboardExcept} from './checkerboard';
+import {globals} from './globals';
+import {cachedHsluvToHex} from './hsluv_cache';
+import {PxSpan, TimeScale} from './time_scale';
+import {SliceRect} from './track';
+
+export const SLICE_TRACK_KIND = 'ChromeSliceTrack';
+const SLICE_HEIGHT = 18;
+const TRACK_PADDING = 2;
+const CHEVRON_WIDTH_PX = 10;
+const HALF_CHEVRON_WIDTH_PX = CHEVRON_WIDTH_PX / 2;
+
+export interface SliceData extends TrackData {
+  // Slices are stored in a columnar fashion.
+  strings: string[];
+  sliceIds: Float64Array;
+  starts: BigInt64Array;
+  ends: BigInt64Array;
+  depths: Uint16Array;
+  titles: Uint16Array;   // Index into strings.
+  colors?: Uint16Array;  // Index into strings.
+  isInstant: Uint16Array;
+  isIncomplete: Uint16Array;
+  cpuTimeRatio?: Float64Array;
+}
+
+// Track base class which handles rendering slices in a generic way.
+// This is the old way of rendering slices - i.e. "track v1" format  - and
+// exists as a patch to allow old tracks to be converted to controller-less
+// tracks before they are ported to v2.
+// Slice tracks should extend this class and implement the abstract methods,
+// notably onBoundsChange().
+export abstract class SliceTrackBase extends BasicAsyncTrack<SliceData> {
+  constructor(
+      private maxDepth: number, protected trackKey: string,
+      private tableName: string, private namespace?: string) {
+    super();
+  }
+
+  protected namespaceTable(tableName: string = this.tableName): string {
+    if (this.namespace) {
+      return this.namespace + '_' + tableName;
+    } else {
+      return tableName;
+    }
+  }
+
+  private hoveredTitleId = -1;
+
+  // Font used to render the slice name on the current track.
+  protected getFont() {
+    return '12px Roboto Condensed';
+  }
+
+  renderCanvas(ctx: CanvasRenderingContext2D): void {
+    // TODO: fonts and colors should come from the CSS and not hardcoded here.
+    const data = this.data;
+    if (data === undefined) return;  // Can't possibly draw anything.
+
+    const {visibleTimeSpan, visibleWindowTime, visibleTimeScale, windowSpan} =
+        globals.frontendLocalState;
+
+    // If the cached trace slices don't fully cover the visible time range,
+    // show a gray rectangle with a "Loading..." label.
+    checkerboardExcept(
+        ctx,
+        this.getHeight(),
+        visibleTimeScale.hpTimeToPx(visibleWindowTime.start),
+        visibleTimeScale.hpTimeToPx(visibleWindowTime.end),
+        visibleTimeScale.timeToPx(data.start),
+        visibleTimeScale.timeToPx(data.end),
+    );
+
+    ctx.textAlign = 'center';
+
+    // measuretext is expensive so we only use it once.
+    const charWidth = ctx.measureText('ACBDLqsdfg').width / 10;
+
+    // The draw of the rect on the selected slice must happen after the other
+    // drawings, otherwise it would result under another rect.
+    let drawRectOnSelected = () => {};
+
+
+    for (let i = 0; i < data.starts.length; i++) {
+      const tStart = Time.fromRaw(data.starts[i]);
+      let tEnd = Time.fromRaw(data.ends[i]);
+      const depth = data.depths[i];
+      const titleId = data.titles[i];
+      const sliceId = data.sliceIds[i];
+      const isInstant = data.isInstant[i];
+      const isIncomplete = data.isIncomplete[i];
+      const title = data.strings[titleId];
+      const colorOverride = data.colors && data.strings[data.colors[i]];
+      if (isIncomplete) {  // incomplete slice
+        // TODO(stevegolton): This isn't exactly equivalent, ideally we should
+        // choose tEnd once we've converted to screen space coords.
+        tEnd = visibleWindowTime.end.toTime('ceil');
+      }
+
+      if (!visibleTimeSpan.intersects(tStart, tEnd)) {
+        continue;
+      }
+
+      const rect = this.getSliceRect(
+          visibleTimeScale, visibleTimeSpan, windowSpan, tStart, tEnd, depth);
+      if (!rect || !rect.visible) {
+        continue;
+      }
+
+      const currentSelection = globals.state.currentSelection;
+      const isSelected = currentSelection &&
+          currentSelection.kind === 'CHROME_SLICE' &&
+          currentSelection.id !== undefined && currentSelection.id === sliceId;
+
+      const highlighted = titleId === this.hoveredTitleId ||
+          globals.state.highlightedSliceId === sliceId;
+
+      const hasFocus = highlighted || isSelected;
+      const colorObj = getColorForSlice(title, hasFocus);
+
+      let color: string;
+      if (colorOverride === undefined) {
+        color = colorObj.c;
+      } else {
+        color = colorOverride;
+      }
+      ctx.fillStyle = color;
+
+      // We draw instant events as upward facing chevrons starting at A:
+      //     A
+      //    ###
+      //   ##C##
+      //  ##   ##
+      // D       B
+      // Then B, C, D and back to A:
+      if (isInstant) {
+        if (isSelected) {
+          drawRectOnSelected = () => {
+            ctx.save();
+            ctx.translate(rect.left, rect.top);
+
+            // Draw a rectangle around the selected slice
+            ctx.strokeStyle = cachedHsluvToHex(colorObj.h, 100, 10);
+            ctx.beginPath();
+            ctx.lineWidth = 3;
+            ctx.strokeRect(
+                -HALF_CHEVRON_WIDTH_PX, 0, CHEVRON_WIDTH_PX, SLICE_HEIGHT);
+            ctx.closePath();
+
+            // Draw inner chevron as interior
+            ctx.fillStyle = color;
+            this.drawChevron(ctx);
+
+            ctx.restore();
+          };
+        } else {
+          ctx.save();
+          ctx.translate(rect.left, rect.top);
+          this.drawChevron(ctx);
+          ctx.restore();
+        }
+        continue;
+      }
+
+      if (isIncomplete && rect.width > SLICE_HEIGHT / 4) {
+        drawIncompleteSlice(ctx, rect.left, rect.top, rect.width, SLICE_HEIGHT);
+      } else if (
+          data.cpuTimeRatio !== undefined && data.cpuTimeRatio[i] < 1 - 1e-9) {
+        // We draw two rectangles, representing the ratio between wall time and
+        // time spent on cpu.
+        const cpuTimeRatio = data.cpuTimeRatio![i];
+        const firstPartWidth = rect.width * cpuTimeRatio;
+        const secondPartWidth = rect.width * (1 - cpuTimeRatio);
+        ctx.fillRect(rect.left, rect.top, firstPartWidth, SLICE_HEIGHT);
+        ctx.fillStyle = colorForThreadIdleSlice(
+            colorObj.h, colorObj.s, colorObj.l, hasFocus);
+        ctx.fillRect(
+            rect.left + firstPartWidth,
+            rect.top,
+            secondPartWidth,
+            SLICE_HEIGHT);
+      } else {
+        ctx.fillRect(rect.left, rect.top, rect.width, SLICE_HEIGHT);
+      }
+
+      // Selected case
+      if (isSelected) {
+        drawRectOnSelected = () => {
+          ctx.strokeStyle = cachedHsluvToHex(colorObj.h, 100, 10);
+          ctx.beginPath();
+          ctx.lineWidth = 3;
+          ctx.strokeRect(
+              rect.left, rect.top - 1.5, rect.width, SLICE_HEIGHT + 3);
+          ctx.closePath();
+        };
+      }
+
+      // Don't render text when we have less than 5px to play with.
+      if (rect.width >= 5) {
+        ctx.fillStyle = colorObj.l > 65 ? '#404040' : 'white';
+        const displayText = cropText(title, charWidth, rect.width);
+        const rectXCenter = rect.left + rect.width / 2;
+        ctx.textBaseline = 'middle';
+        ctx.font = this.getFont();
+        ctx.fillText(displayText, rectXCenter, rect.top + SLICE_HEIGHT / 2);
+      }
+    }
+    drawRectOnSelected();
+  }
+
+  drawChevron(ctx: CanvasRenderingContext2D) {
+    // Draw a chevron at a fixed location and size. Should be used with
+    // ctx.translate and ctx.scale to alter location and size.
+    ctx.beginPath();
+    ctx.moveTo(0, 0);
+    ctx.lineTo(HALF_CHEVRON_WIDTH_PX, SLICE_HEIGHT);
+    ctx.lineTo(0, SLICE_HEIGHT - HALF_CHEVRON_WIDTH_PX);
+    ctx.lineTo(-HALF_CHEVRON_WIDTH_PX, SLICE_HEIGHT);
+    ctx.lineTo(0, 0);
+    ctx.fill();
+  }
+
+  getSliceIndex({x, y}: {x: number, y: number}): number|void {
+    const data = this.data;
+    if (data === undefined) return;
+    const {
+      visibleTimeScale: timeScale,
+      visibleWindowTime: visibleHPTimeSpan,
+    } = globals.frontendLocalState;
+    if (y < TRACK_PADDING) return;
+    const instantWidthTime = timeScale.pxDeltaToDuration(HALF_CHEVRON_WIDTH_PX);
+    const t = timeScale.pxToHpTime(x);
+    const depth = Math.floor((y - TRACK_PADDING) / SLICE_HEIGHT);
+
+    for (let i = 0; i < data.starts.length; i++) {
+      if (depth !== data.depths[i]) {
+        continue;
+      }
+      const start = Time.fromRaw(data.starts[i]);
+      const tStart = HighPrecisionTime.fromTime(start);
+      if (data.isInstant[i]) {
+        if (tStart.sub(t).abs().lt(instantWidthTime)) {
+          return i;
+        }
+      } else {
+        const end = Time.fromRaw(data.ends[i]);
+        let tEnd = HighPrecisionTime.fromTime(end);
+        if (data.isIncomplete[i]) {
+          tEnd = visibleHPTimeSpan.end;
+        }
+        if (tStart.lte(t) && t.lte(tEnd)) {
+          return i;
+        }
+      }
+    }
+  }
+
+  onMouseMove({x, y}: {x: number, y: number}) {
+    this.hoveredTitleId = -1;
+    globals.dispatch(Actions.setHighlightedSliceId({sliceId: -1}));
+    const sliceIndex = this.getSliceIndex({x, y});
+    if (sliceIndex === undefined) return;
+    const data = this.data;
+    if (data === undefined) return;
+    this.hoveredTitleId = data.titles[sliceIndex];
+    const sliceId = data.sliceIds[sliceIndex];
+    globals.dispatch(Actions.setHighlightedSliceId({sliceId}));
+  }
+
+  onMouseOut() {
+    this.hoveredTitleId = -1;
+    globals.dispatch(Actions.setHighlightedSliceId({sliceId: -1}));
+  }
+
+  onMouseClick({x, y}: {x: number, y: number}): boolean {
+    const sliceIndex = this.getSliceIndex({x, y});
+    if (sliceIndex === undefined) return false;
+    const data = this.data;
+    if (data === undefined) return false;
+    const sliceId = data.sliceIds[sliceIndex];
+    if (sliceId !== undefined && sliceId !== -1) {
+      globals.makeSelection(Actions.selectChromeSlice({
+        id: sliceId,
+        trackKey: this.trackKey,
+        table: this.namespace,
+      }));
+      return true;
+    }
+    return false;
+  }
+
+  getHeight() {
+    return SLICE_HEIGHT * (this.maxDepth + 1) + 2 * TRACK_PADDING;
+  }
+
+  getSliceRect(
+      visibleTimeScale: TimeScale, visibleWindow: Span<time, duration>,
+      windowSpan: PxSpan, tStart: time, tEnd: time, depth: number): SliceRect
+      |undefined {
+    const pxEnd = windowSpan.end;
+    const left = Math.max(visibleTimeScale.timeToPx(tStart), 0);
+    const right = Math.min(visibleTimeScale.timeToPx(tEnd), pxEnd);
+
+    const visible = visibleWindow.intersects(tStart, tEnd);
+
+    return {
+      left,
+      width: Math.max(right - left, 1),
+      top: TRACK_PADDING + depth * SLICE_HEIGHT,
+      height: SLICE_HEIGHT,
+      visible,
+    };
+  }
+}
diff --git a/ui/src/frontend/sql/slice.ts b/ui/src/frontend/sql/slice.ts
index 8247487..8be8134 100644
--- a/ui/src/frontend/sql/slice.ts
+++ b/ui/src/frontend/sql/slice.ts
@@ -55,7 +55,7 @@
   ts: time;
   absTime?: string;
   dur: duration;
-  sqlTrackId: number;
+  trackId: number;
   thread?: ThreadInfo;
   process?: ProcessInfo;
   threadTs?: time;
@@ -150,7 +150,7 @@
       name: it.name,
       ts: Time.fromRaw(it.ts),
       dur: it.dur,
-      sqlTrackId: it.trackId,
+      trackId: it.trackId,
       thread,
       process,
       threadDur: it.threadDur ?? undefined,
@@ -198,17 +198,17 @@
         {
           icon: Icons.UpdateSelection,
           onclick: () => {
-            const uiTrackId =
-                globals.state.uiTrackIdByTraceTrackId[vnode.attrs.sqlTrackId];
-            if (uiTrackId === undefined) return;
-            verticalScrollToTrack(uiTrackId, true);
+            const trackKey =
+                globals.state.trackKeyByTrackId[vnode.attrs.sqlTrackId];
+            if (trackKey === undefined) return;
+            verticalScrollToTrack(trackKey, true);
             // Clamp duration to 1 - i.e. for instant events
             const dur = BigintMath.max(1n, vnode.attrs.dur);
             focusHorizontalRange(
                 vnode.attrs.ts, Time.fromRaw(vnode.attrs.ts + dur));
             globals.makeSelection(
                 Actions.selectChromeSlice(
-                    {id: vnode.attrs.id, trackId: uiTrackId, table: 'slice'}),
+                    {id: vnode.attrs.id, trackKey, table: 'slice'}),
                 {tab: switchTab ? 'current_selection' : null});
           },
         },
@@ -222,6 +222,6 @@
     name: name ?? slice.name,
     ts: slice.ts,
     dur: slice.dur,
-    sqlTrackId: slice.sqlTrackId,
+    sqlTrackId: slice.trackId,
   });
 }
diff --git a/ui/src/frontend/sql/thread_state.ts b/ui/src/frontend/sql/thread_state.ts
new file mode 100644
index 0000000..2de948b
--- /dev/null
+++ b/ui/src/frontend/sql/thread_state.ts
@@ -0,0 +1,139 @@
+// 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 {Duration, duration, TimeSpan} from '../../base/time';
+import {LONG, NUM_NULL, STR, STR_NULL} from '../../common/query_result';
+import {EngineProxy} from '../../public';
+import {TreeNode} from '../../widgets/tree';
+import {Utid} from '../sql_types';
+
+// An individual node of the thread state breakdown tree.
+class Node {
+  parent?: Node;
+  children: Map<string, Node>;
+  dur: duration;
+  startsCollapsed: boolean = true;
+
+  constructor(parent?: Node) {
+    this.parent = parent;
+    this.children = new Map();
+    this.dur = 0n;
+  }
+
+  getOrCreateChild(name: string) {
+    let child = this.children.get(name);
+    if (!child) {
+      child = new Node(this);
+      this.children.set(name, child);
+    }
+    return child;
+  }
+
+  addDuration(dur: duration) {
+    let node: Node|undefined = this;
+    while (node !== undefined) {
+      node.dur += dur;
+      node = node.parent;
+    }
+  }
+}
+
+// Thread state breakdown data (tree).
+// Can be passed to ThreadStateBreakdownTreeNode to be rendered as a part of a
+// tree.
+export interface BreakdownByThreadState {
+  root: Node;
+}
+
+// Compute a breakdown of thread states for a given thread for a given time
+// interval.
+export async function breakDownIntervalByThreadState(
+    engine: EngineProxy, range: TimeSpan, utid: Utid):
+    Promise<BreakdownByThreadState> {
+  // TODO(altimin): this probably should share some code with pivot tables when
+  // we actually get some pivot tables we like.
+  const query = await engine.query(`
+    INCLUDE PERFETTO MODULE common.thread_states;
+
+    SELECT
+      state,
+      raw_state as rawState,
+      cpu_type as cpuType,
+      cpu,
+      blocked_function as blockedFunction,
+      dur
+    FROM thread_state_summary_for_interval(${range.start}, ${range.duration}, ${
+      utid});
+  `);
+  const it = query.iter({
+    state: STR,
+    rawState: STR,
+    cpuType: STR_NULL,
+    cpu: NUM_NULL,
+    blockedFunction: STR_NULL,
+    dur: LONG,
+  });
+  const root = new Node();
+  for (; it.valid(); it.next()) {
+    let currentNode = root;
+    currentNode = currentNode.getOrCreateChild(it.state);
+    // If the CPU time is not null, add it to the breakdown.
+    if (it.cpuType !== null) {
+      currentNode = currentNode.getOrCreateChild(it.cpuType);
+    }
+    if (it.cpu !== null) {
+      currentNode = currentNode.getOrCreateChild(`CPU ${it.cpu}`);
+    }
+    if (it.blockedFunction !== null) {
+      currentNode = currentNode.getOrCreateChild(`${it.blockedFunction}`);
+    }
+    currentNode.addDuration(it.dur);
+  }
+  return {
+    root,
+  };
+}
+
+function renderChildren(node: Node, totalDur: duration): m.Child[] {
+  const res = Array.from(node.children.entries())
+                  .map(([name, child]) => renderNode(child, name, totalDur));
+  return res;
+}
+
+function renderNode(node: Node, name: string, totalDur: duration): m.Child {
+  const durPercent = 100. * Number(node.dur) / Number(totalDur);
+  return m(
+      TreeNode,
+      {
+        left: name,
+        right: `${Duration.humanise(node.dur)} (${durPercent.toFixed(2)}%)`,
+        startsCollapsed: node.startsCollapsed,
+      },
+      renderChildren(node, totalDur));
+}
+
+interface BreakdownByThreadStateTreeNodeAttrs {
+  dur: duration;
+  data: BreakdownByThreadState;
+}
+
+// A tree node that displays a nested breakdown a time interval by thread state.
+export class BreakdownByThreadStateTreeNode implements
+    m.ClassComponent<BreakdownByThreadStateTreeNodeAttrs> {
+  view({attrs}: m.Vnode<BreakdownByThreadStateTreeNodeAttrs>): m.Child[] {
+    return renderChildren(attrs.data.root, attrs.dur);
+  }
+}
diff --git a/ui/src/frontend/sql_table/render_cell.ts b/ui/src/frontend/sql_table/render_cell.ts
index e5d5b1c..b871142 100644
--- a/ui/src/frontend/sql_table/render_cell.ts
+++ b/ui/src/frontend/sql_table/render_cell.ts
@@ -15,6 +15,7 @@
 import m from 'mithril';
 
 import {copyToClipboard} from '../../base/clipboard';
+import {isString} from '../../base/object_utils';
 import {Icons} from '../../base/semantic_icons';
 import {sqliteString} from '../../base/string_utils';
 import {duration, Duration, Time} from '../../base/time';
@@ -52,7 +53,7 @@
       filterOptionMenuItem('is not null', `${c.expression} is not null`, state),
     ];
   }
-  if (typeof value === 'string') {
+  if (isString(value)) {
     return [
       filterOptionMenuItem(
           'equals to', `${c.expression} = ${sqliteString(value)}`, state),
@@ -125,7 +126,7 @@
     result.push(
         copyMenuItem('Copy formatted duration', displayDuration(value)));
   }
-  if (typeof value === 'string') {
+  if (isString(value)) {
     result.push(copyMenuItem('Copy', value));
   }
 
diff --git a/ui/src/frontend/sql_table/state.ts b/ui/src/frontend/sql_table/state.ts
index 806c330..423b35a 100644
--- a/ui/src/frontend/sql_table/state.ts
+++ b/ui/src/frontend/sql_table/state.ts
@@ -14,6 +14,7 @@
 
 import {arrayEquals} from '../../base/array_utils';
 import {SortDirection} from '../../base/comparison_utils';
+import {isString} from '../../base/object_utils';
 import {sqliteString} from '../../base/string_utils';
 import {EngineProxy} from '../../common/engine';
 import {NUM, Row} from '../../common/query_result';
@@ -134,7 +135,7 @@
     };
     let cteId = 0;
     for (const filter of this.filters) {
-      if (typeof filter === 'string') {
+      if (isString(filter)) {
         result.filters!.push(filter);
       } else {
         const cteName = `arg_sets_${cteId++}`;
diff --git a/ui/src/frontend/sql_table/table.ts b/ui/src/frontend/sql_table/table.ts
index 92f8fc9..578a75e 100644
--- a/ui/src/frontend/sql_table/table.ts
+++ b/ui/src/frontend/sql_table/table.ts
@@ -14,6 +14,7 @@
 
 import m from 'mithril';
 
+import {isString} from '../../base/object_utils';
 import {Icons} from '../../base/semantic_icons';
 import {EngineProxy} from '../../common/engine';
 import {Row} from '../../common/query_result';
@@ -48,9 +49,8 @@
   renderFilters(): m.Children {
     const filters: m.Child[] = [];
     for (const filter of this.state.getFilters()) {
-      const label = typeof filter === 'string' ?
-          filter :
-          `Arg(${filter.argName}) ${filter.op}`;
+      const label =
+          isString(filter) ? filter : `Arg(${filter.argName}) ${filter.op}`;
       filters.push(m(Button, {
         label,
         icon: 'close',
diff --git a/ui/src/frontend/sql_utils.ts b/ui/src/frontend/sql_utils.ts
index 990090c..b201de1 100644
--- a/ui/src/frontend/sql_utils.ts
+++ b/ui/src/frontend/sql_utils.ts
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {isString} from '../base/object_utils';
 import {EngineProxy} from '../common/engine';
 import {ColumnType, NUM} from '../common/query_result';
 import {SortDirection} from '../common/state';
@@ -68,7 +69,7 @@
   const orderBy = (c.orderBy ?? []).filter(isDefined);
   if (orderBy.length > 0) {
     const orderBys = orderBy.map((clause) => {
-      if (typeof clause === 'string') {
+      if (isString(clause)) {
         return clause;
       } else {
         const direction = clause.direction ? ` ${clause.direction}` : '';
diff --git a/ui/src/frontend/thread_state.ts b/ui/src/frontend/thread_state.ts
index 80f690b..c1989c4 100644
--- a/ui/src/frontend/thread_state.ts
+++ b/ui/src/frontend/thread_state.ts
@@ -27,6 +27,7 @@
 import {LONG, NUM, NUM_NULL, STR_NULL} from '../common/query_result';
 import {translateState} from '../common/thread_state';
 import {CPU_SLICE_TRACK_KIND} from '../tracks/cpu_slices';
+import {THREAD_STATE_TRACK_KIND} from '../tracks/thread_state';
 import {Anchor} from '../widgets/anchor';
 
 import {globals} from './globals';
@@ -148,7 +149,7 @@
       const trackInfo = pluginManager.resolveTrackInfo(track.uri);
       if (trackInfo?.kind === CPU_SLICE_TRACK_KIND) {
         if (trackInfo?.cpu === cpu) {
-          trackId = track.id;
+          trackId = track.key;
           break;
         }
       }
@@ -157,7 +158,7 @@
   if (trackId === undefined) {
     return;
   }
-  globals.makeSelection(Actions.selectSlice({id, trackId}));
+  globals.makeSelection(Actions.selectSlice({id, trackKey: trackId}));
   scrollToTrackAndTs(trackId, ts);
 }
 
@@ -177,21 +178,23 @@
         {
           icon: Icons.UpdateSelection,
           onclick: () => {
-            let trackId: string|number|undefined;
+            let trackKey: string|number|undefined;
             for (const track of Object.values(globals.state.tracks)) {
-              if (track.kind === 'ThreadStateTrack' &&
-                  (track.config as {utid: number}).utid === vnode.attrs.utid) {
-                trackId = track.id;
+              const trackDesc = pluginManager.resolveTrackInfo(track.uri);
+              // TODO(stevegolton): Handle v2.
+              if (trackDesc && trackDesc.kind === THREAD_STATE_TRACK_KIND &&
+                  trackDesc.utid === vnode.attrs.utid) {
+                trackKey = track.key;
               }
             }
 
-            if (trackId) {
+            if (trackKey) {
               globals.makeSelection(Actions.selectThreadState({
                 id: vnode.attrs.id,
-                trackId: trackId.toString(),
+                trackKey: trackKey.toString(),
               }));
 
-              scrollToTrackAndTs(trackId, vnode.attrs.ts, true);
+              scrollToTrackAndTs(trackKey, vnode.attrs.ts, true);
             }
           },
         },
diff --git a/ui/src/frontend/thread_state_tab.ts b/ui/src/frontend/thread_state_tab.ts
index 4e25723..35d3b9f 100644
--- a/ui/src/frontend/thread_state_tab.ts
+++ b/ui/src/frontend/thread_state_tab.ts
@@ -17,7 +17,7 @@
 import {Duration, time} from '../base/time';
 import {runQuery} from '../common/queries';
 import {raf} from '../core/raf_scheduler';
-import {addDebugTrack} from '../tracks/debug/slice_track';
+import {addDebugSliceTrack} from '../tracks/debug/slice_track';
 import {Anchor} from '../widgets/anchor';
 import {Button} from '../widgets/button';
 import {DetailsShell} from '../widgets/details_shell';
@@ -292,7 +292,7 @@
            {
           label: 'Critical path lite',
           onclick: () => runQuery(`INCLUDE PERFETTO MODULE experimental.thread_executing_span;`, this.engine)
-              .then(() => addDebugTrack(
+              .then(() => addDebugSliceTrack(
               this.engine,
                   {
                     sqlSource:
@@ -324,7 +324,7 @@
            {
           label: 'Critical path',
           onclick: () => runQuery(`INCLUDE PERFETTO MODULE experimental.thread_executing_span;`, this.engine)
-              .then(() => addDebugTrack(
+              .then(() => addDebugSliceTrack(
               this.engine,
                   {
                     sqlSource:
diff --git a/ui/src/frontend/track.ts b/ui/src/frontend/track.ts
index 6ddbc51..15f4f4e 100644
--- a/ui/src/frontend/track.ts
+++ b/ui/src/frontend/track.ts
@@ -17,18 +17,14 @@
 import {assertExists} from '../base/logging';
 import {duration, Span, time} from '../base/time';
 import {EngineProxy} from '../common/engine';
-import {TrackState} from '../common/state';
-import {TrackData} from '../common/track_data';
-import {Track} from '../public';
+import {Track, TrackContext} from '../public';
 
-import {checkerboard} from './checkerboard';
 import {globals} from './globals';
 import {PxSpan, TimeScale} from './time_scale';
-import {TrackButtonAttrs} from './track_panel';
 
 // Args passed to the track constructors when creating a new track.
 export interface NewTrackArgs {
-  trackId: string;
+  trackKey: string;
   engine: EngineProxy;
 }
 
@@ -54,28 +50,25 @@
 }
 
 // The abstract class that needs to be implemented by all tracks.
-export abstract class TrackBase<Config = {}, Data extends TrackData = TrackData>
-    implements Track {
-  // The UI-generated track ID (not to be confused with the SQL track.id).
-  protected readonly trackId: string;
+export abstract class TrackBase<Config = {}> implements Track {
+  protected readonly trackKey: string;
   protected readonly engine: EngineProxy;
+  private _config?: Config;
 
-  // When true this is a new controller-less track type.
-  // TODO(hjd): eventually all tracks will be controller-less and this
-  // should be removed then.
-  protected frontendOnly = false;
-
-  // Caches the last state.track[this.trackId]. This is to deal with track
-  // deletion, see comments in trackState() below.
-  private lastTrackState: TrackState;
-
-  constructor(args: NewTrackArgs) {
-    this.trackId = args.trackId;
-    this.engine = args.engine;
-    this.lastTrackState = assertExists(globals.state.tracks[this.trackId]);
+  get config(): Config {
+    return assertExists(this._config);
   }
 
-  onCreate() {}
+  set config(x: Config) {
+    this._config = x;
+  }
+
+  constructor(args: NewTrackArgs) {
+    this.trackKey = args.trackKey;
+    this.engine = args.engine;
+  }
+
+  onCreate(_ctx: TrackContext) {}
 
   // Last call the track will receive. Called just before the last reference to
   // this object is removed.
@@ -83,44 +76,14 @@
 
   protected abstract renderCanvas(ctx: CanvasRenderingContext2D): void;
 
-  protected get trackState(): TrackState {
-    // We can end up in a state where a Track is still in the mithril renderer
-    // tree but its corresponding state has been deleted. This can happen in the
-    // interval of time between a track being removed from the state and the
-    // next animation frame that would remove the Track object. If a mouse event
-    // is dispatched in the meanwhile (or a promise is resolved), we need to be
-    // able to access the state. Hence the caching logic here.
-    const trackState = globals.state.tracks[this.trackId];
-    if (trackState === undefined) {
-      return this.lastTrackState;
-    }
-    this.lastTrackState = trackState;
-    return trackState;
-  }
-
-  get config(): Config {
-    return this.trackState.config as Config;
-  }
-
-  data(): Data|undefined {
-    if (this.frontendOnly) {
-      return undefined;
-    }
-    return globals.trackDataStore.get(this.trackId) as Data;
-  }
-
   getHeight(): number {
     return 40;
   }
 
-  getTrackShellButtons(): Array<m.Vnode<TrackButtonAttrs>> {
+  getTrackShellButtons(): m.Children {
     return [];
   }
 
-  getContextMenu(): m.Vnode<any>|null {
-    return null;
-  }
-
   onMouseMove(_position: {x: number, y: number}) {}
 
   // Returns whether the mouse click has selected something.
@@ -134,17 +97,8 @@
   onFullRedraw(): void {}
 
   render(ctx: CanvasRenderingContext2D) {
-    globals.frontendLocalState.addVisibleTrack(this.trackState.id);
-    if (this.data() === undefined && !this.frontendOnly) {
-      const {visibleWindowTime, visibleTimeScale} = globals.frontendLocalState;
-      const startPx =
-          Math.floor(visibleTimeScale.hpTimeToPx(visibleWindowTime.start));
-      const endPx =
-          Math.ceil(visibleTimeScale.hpTimeToPx(visibleWindowTime.end));
-      checkerboard(ctx, this.getHeight(), startPx, endPx);
-    } else {
-      this.renderCanvas(ctx);
-    }
+    globals.frontendLocalState.addVisibleTrack(this.trackKey);
+    this.renderCanvas(ctx);
   }
 
   // Returns a place where a given slice should be drawn. Should be implemented
diff --git a/ui/src/frontend/track_group_panel.ts b/ui/src/frontend/track_group_panel.ts
index b378189..a524dfb 100644
--- a/ui/src/frontend/track_group_panel.ts
+++ b/ui/src/frontend/track_group_panel.ts
@@ -19,7 +19,6 @@
 import {Icons} from '../base/semantic_icons';
 import {Actions} from '../common/actions';
 import {pluginManager} from '../common/plugins';
-import {RegistryError} from '../common/registry';
 import {
   getContainingTrackId,
   TrackGroupState,
@@ -31,7 +30,6 @@
 import {drawGridLines} from './gridline_helper';
 import {Panel, PanelSize} from './panel';
 import {renderChips, TrackContent} from './track_panel';
-import {trackRegistry} from './track_registry';
 import {
   drawVerticalLineAtTime,
 } from './vertical_line_helper';
@@ -53,25 +51,25 @@
   }
 
   private tryLoadTrack() {
-    const trackId = this.trackGroupId;
+    const groupId = this.trackGroupId;
     const trackState = this.summaryTrackState;
 
-    const {id, uri} = trackState;
+    const {key, uri, params} = trackState;
 
     const ctx: TrackContext = {
-      trackInstanceId: id,
+      trackKey: key,
       mountStore: <T>(migrate: Migrate<T>) => {
         const {store, state} = globals;
-        const migratedState = migrate(state.trackGroups[trackId].state);
+        const migratedState = migrate(state.trackGroups[groupId].state);
         store.edit((draft) => {
-          draft.trackGroups[trackId].state = migratedState;
+          draft.trackGroups[groupId].state = migratedState;
         });
-        return store.createProxy<T>(['trackGroups', trackId, 'state']);
+        return store.createProxy<T>(['trackGroups', groupId, 'state']);
       },
+      params,
     };
 
-    this.summaryTrack =
-        uri ? pluginManager.createTrack(uri, ctx) : loadTrack(trackState, id);
+    this.summaryTrack = pluginManager.createTrack(uri, ctx);
   }
 
   get trackGroupState(): TrackGroupState {
@@ -98,8 +96,8 @@
     let highlightClass = '';
     const searchIndex = globals.state.searchIndex;
     if (searchIndex !== -1) {
-      const trackId = globals.currentSearchResults.trackIds[searchIndex];
-      const parentTrackId = getContainingTrackId(globals.state, trackId);
+      const trackKey = globals.currentSearchResults.trackKeys[searchIndex];
+      const parentTrackId = getContainingTrackId(globals.state, trackKey);
       if (parentTrackId === attrs.trackGroupId) {
         highlightClass = 'flash';
       }
@@ -304,25 +302,3 @@
 function StripPathFromExecutable(path: string) {
   return path.split('/').slice(-1)[0];
 }
-
-function loadTrack(trackState: TrackState, trackId: string): Track|undefined {
-  const engine = globals.engines.get(trackState.engineId);
-  if (engine === undefined) {
-    return undefined;
-  }
-
-  try {
-    const trackCreator = trackRegistry.get(trackState.kind);
-    return trackCreator.create({
-      trackId,
-      engine:
-          engine.getProxy(`Track; kind: ${trackState.kind}; id: ${trackId}`),
-    });
-  } catch (e) {
-    if (e instanceof RegistryError) {
-      return undefined;
-    } else {
-      throw e;
-    }
-  }
-}
diff --git a/ui/src/frontend/track_panel.ts b/ui/src/frontend/track_panel.ts
index c280ced..90a593a 100644
--- a/ui/src/frontend/track_panel.ts
+++ b/ui/src/frontend/track_panel.ts
@@ -18,10 +18,8 @@
 import {currentTargetOffset} from '../base/dom_utils';
 import {Icons} from '../base/semantic_icons';
 import {duration, Span, time} from '../base/time';
-import {exists} from '../base/utils';
 import {Actions} from '../common/actions';
 import {pluginManager} from '../common/plugins';
-import {RegistryError} from '../common/registry';
 import {TrackState} from '../common/state';
 import {raf} from '../core/raf_scheduler';
 import {Migrate, Track, TrackContext} from '../public';
@@ -33,7 +31,6 @@
 import {verticalScrollToTrack} from './scroll_helper';
 import {PxSpan, TimeScale} from './time_scale';
 import {SliceRect} from './track';
-import {trackRegistry} from './track_registry';
 import {
   drawVerticalLineAtTime,
 } from './vertical_line_helper';
@@ -79,24 +76,12 @@
   }
 }
 
-export function renderChips({uri, config}: TrackState) {
+export function renderChips({uri}: TrackState) {
   const tagElements: m.Children = [];
-  if (exists(uri)) {
-    const trackInfo = pluginManager.resolveTrackInfo(uri);
-    const tags = trackInfo?.tags;
-    tags?.metric && tagElements.push(m(TrackChip, {text: 'metric'}));
-    tags?.debuggable && tagElements.push(m(TrackChip, {text: 'debuggable'}));
-  } else {
-    if (config && typeof config === 'object') {
-      if ('namespace' in config) {
-        tagElements.push(m(TrackChip, {text: 'metric'}));
-      }
-      if ('isDebuggable' in config && config.isDebuggable) {
-        tagElements.push(m(TrackChip, {text: 'debuggable'}));
-      }
-    }
-  }
-
+  const trackInfo = pluginManager.resolveTrackInfo(uri);
+  const tags = trackInfo?.tags;
+  tags?.metric && tagElements.push(m(TrackChip, {text: 'metric'}));
+  tags?.debuggable && tagElements.push(m(TrackChip, {text: 'debuggable'}));
   return tagElements;
 }
 
@@ -121,8 +106,8 @@
     let highlightClass = '';
     const searchIndex = globals.state.searchIndex;
     if (searchIndex !== -1) {
-      const trackId = globals.currentSearchResults.trackIds[searchIndex];
-      if (trackId === attrs.trackState.id) {
+      const trackKey = globals.currentSearchResults.trackKeys[searchIndex];
+      if (trackKey === attrs.trackState.key) {
         highlightClass = 'flash';
       }
     }
@@ -152,16 +137,15 @@
             ),
         m('.track-buttons',
           attrs.track.getTrackShellButtons(),
-          attrs.track.getContextMenu(),
           m(TrackButton, {
             action: () => {
               globals.dispatch(
-                  Actions.toggleTrackPinned({trackId: attrs.trackState.id}));
+                  Actions.toggleTrackPinned({trackKey: attrs.trackState.key}));
             },
             i: Icons.Pin,
-            filledIcon: isPinned(attrs.trackState.id),
-            tooltip: isPinned(attrs.trackState.id) ? 'Unpin' : 'Pin to top',
-            showButton: isPinned(attrs.trackState.id),
+            filledIcon: isPinned(attrs.trackState.key),
+            tooltip: isPinned(attrs.trackState.key) ? 'Unpin' : 'Pin to top',
+            showButton: isPinned(attrs.trackState.key),
             fullHeight: true,
           }),
           globals.state.currentSelection !== null &&
@@ -169,12 +153,12 @@
               m(TrackButton, {
                 action: (e: MouseEvent) => {
                   globals.dispatch(Actions.toggleTrackSelection(
-                      {id: attrs.trackState.id, isTrackGroup: false}));
+                      {id: attrs.trackState.key, isTrackGroup: false}));
                   e.stopPropagation();
                 },
-                i: isSelected(attrs.trackState.id) ? Icons.Checkbox :
-                                                     Icons.BlankCheckbox,
-                tooltip: isSelected(attrs.trackState.id) ?
+                i: isSelected(attrs.trackState.key) ? Icons.Checkbox :
+                                                      Icons.BlankCheckbox,
+                tooltip: isSelected(attrs.trackState.key) ?
                     'Remove track' :
                     'Add track to selection',
                 showButton: true,
@@ -187,7 +171,7 @@
     if (dataTransfer === null) return;
     this.dragging = true;
     raf.scheduleFullRedraw();
-    dataTransfer.setData('perfetto/track', `${this.attrs!.trackState.id}`);
+    dataTransfer.setData('perfetto/track', `${this.attrs!.trackState.key}`);
     dataTransfer.setDragImage(new Image(), 0, 0);
   }
 
@@ -226,7 +210,7 @@
     if (dataTransfer === null) return;
     raf.scheduleFullRedraw();
     const srcId = dataTransfer.getData('perfetto/track');
-    const dstId = this.attrs!.trackState.id;
+    const dstId = this.attrs!.trackState.key;
     globals.dispatch(Actions.moveTrack({srcId, op: this.dropping, dstId}));
     this.dropping = undefined;
   }
@@ -305,7 +289,7 @@
           style: {
             height: `${Math.max(18, attrs.track.getHeight())}px`,
           },
-          id: 'track_' + attrs.trackState.id,
+          id: 'track_' + attrs.trackState.key,
         },
         [
           m(TrackShell, {track: attrs.track, trackState: attrs.trackState}),
@@ -314,9 +298,9 @@
   }
 
   oncreate({attrs}: m.CVnode<TrackComponentAttrs>) {
-    if (globals.frontendLocalState.scrollToTrackId === attrs.trackState.id) {
-      verticalScrollToTrack(attrs.trackState.id);
-      globals.frontendLocalState.scrollToTrackId = undefined;
+    if (globals.frontendLocalState.scrollToTrackKey === attrs.trackState.key) {
+      verticalScrollToTrack(attrs.trackState.key);
+      globals.frontendLocalState.scrollToTrackKey = undefined;
     }
   }
 }
@@ -348,7 +332,7 @@
 }
 
 interface TrackPanelAttrs {
-  id: string;
+  trackKey: string;
   selectable: boolean;
 }
 
@@ -360,29 +344,29 @@
   private trackState: TrackState|undefined;
 
   private tryLoadTrack(vnode: m.CVnode<TrackPanelAttrs>) {
-    const trackId = vnode.attrs.id;
-    const trackState = globals.state.tracks[trackId];
+    const trackKey = vnode.attrs.trackKey;
+    const trackState = globals.state.tracks[trackKey];
 
     if (!trackState) return;
 
-    const {id, uri} = trackState;
+    const {uri, params} = trackState;
 
     const trackCtx: TrackContext = {
-      trackInstanceId: id,
+      trackKey,
       mountStore: <T>(migrate: Migrate<T>) => {
         const {store, state} = globals;
-        const migratedState = migrate(state.tracks[trackId].state);
+        const migratedState = migrate(state.tracks[trackKey].state);
         globals.store.edit((draft) => {
-          draft.tracks[trackId].state = migratedState;
+          draft.tracks[trackKey].state = migratedState;
         });
-        return store.createProxy<T>(['tracks', trackId, 'state']);
+        return store.createProxy<T>(['tracks', trackKey, 'state']);
       },
+      params,
     };
 
-    this.track = uri ? pluginManager.createTrack(uri, trackCtx) :
-                       loadTrack(trackState, id);
+    this.track = pluginManager.createTrack(uri, trackCtx);
 
-    this.track?.onCreate();
+    this.track?.onCreate(trackCtx);
     this.trackState = trackState;
   }
 
@@ -425,7 +409,7 @@
     }
     const selectedArea = globals.state.areas[selection.areaId];
     const selectedAreaDuration = selectedArea.end - selectedArea.start;
-    if (selectedArea.tracks.includes(trackState.id)) {
+    if (selectedArea.tracks.includes(trackState.key)) {
       ctx.fillStyle = SELECTION_FILL_COLOR;
       ctx.fillRect(
           visibleTimeScale.timeToPx(selectedArea.start) + TRACK_SHELL_WIDTH,
@@ -519,25 +503,3 @@
         visibleTimeScale, visibleWindow, windowSpan, tStart, tDur, depth);
   }
 }
-
-function loadTrack(trackState: TrackState, trackId: string): Track|undefined {
-  const engine = globals.engines.get(trackState.engineId);
-  if (engine === undefined) {
-    return undefined;
-  }
-
-  try {
-    const trackCreator = trackRegistry.get(trackState.kind);
-    return trackCreator.create({
-      trackId,
-      engine:
-          engine.getProxy(`Track; kind: ${trackState.kind}; id: ${trackId}`),
-    });
-  } catch (e) {
-    if (e instanceof RegistryError) {
-      return undefined;
-    } else {
-      throw e;
-    }
-  }
-}
diff --git a/ui/src/frontend/viewer_page.ts b/ui/src/frontend/viewer_page.ts
index 0297f81..86c8357 100644
--- a/ui/src/frontend/viewer_page.ts
+++ b/ui/src/frontend/viewer_page.ts
@@ -228,7 +228,7 @@
 
   view() {
     const scrollingPanels: AnyAttrsVnode[] = globals.state.scrollingTracks.map(
-        (id) => m(TrackPanel, {key: id, id, selectable: true}));
+        (key) => m(TrackPanel, {key, trackKey: key, selectable: true}));
 
     for (const group of Object.values(globals.state.trackGroups)) {
       const headerPanel = m(TrackGroupPanel, {
@@ -245,7 +245,7 @@
           const id = group.tracks[i];
           childTracks.push(m(TrackPanel, {
             key: `track-${group.id}-${id}`,
-            id,
+            trackKey: id,
             selectable: true,
           }));
         }
@@ -285,7 +285,9 @@
                   m(NotesPanel, {key: 'notes'}),
                   m(TickmarkPanel, {key: 'searchTickmarks'}),
                   ...globals.state.pinnedTracks.map(
-                      (id) => m(TrackPanel, {key: id, id, selectable: true})),
+                      (id) =>
+                          m(TrackPanel,
+                            {key: id, trackKey: id, selectable: true})),
                 ],
                 kind: 'OVERVIEW',
               })),
diff --git a/ui/src/frontend/widgets/vega_view.ts b/ui/src/frontend/widgets/vega_view.ts
index 5dd3a1a..05c4f93 100644
--- a/ui/src/frontend/widgets/vega_view.ts
+++ b/ui/src/frontend/widgets/vega_view.ts
@@ -17,7 +17,7 @@
 import * as vegaLite from 'vega-lite';
 
 import {Disposable} from '../../base/disposable';
-import {shallowEquals} from '../../base/object_utils';
+import {isString, shallowEquals} from '../../base/object_utils';
 import {SimpleResizeObserver} from '../../base/resize_observer';
 import {EngineProxy} from '../../common/engine';
 import {getErrorMessage} from '../../common/errors';
@@ -28,7 +28,7 @@
 function isVegaLite(spec: unknown): boolean {
   if (typeof spec === 'object') {
     const schema = (spec as {'$schema': unknown})['$schema'];
-    if (schema !== undefined && typeof schema === 'string') {
+    if (schema !== undefined && isString(schema)) {
       // If the schema is available use that:
       return schema.includes('vega-lite');
     }
diff --git a/ui/src/frontend/widgets_page.ts b/ui/src/frontend/widgets_page.ts
index c3b6787..7743df9 100644
--- a/ui/src/frontend/widgets_page.ts
+++ b/ui/src/frontend/widgets_page.ts
@@ -16,6 +16,7 @@
 
 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';
@@ -381,7 +382,7 @@
             this.optValues[key] = option.initial;
           } else if (typeof option === 'boolean') {
             this.optValues[key] = option;
-          } else if (typeof option === 'string') {
+          } else if (isString(option)) {
             this.optValues[key] = option;
           }
         }
@@ -428,7 +429,7 @@
       return this.renderEnumOption(key, value);
     } else if (typeof value === 'boolean') {
       return this.renderBooleanOption(key);
-    } else if (typeof value === 'string') {
+    } else if (isString(value)) {
       return this.renderStringOption(key);
     } else {
       return null;
diff --git a/ui/src/plugins/com.example.Skeleton/index.ts b/ui/src/plugins/com.example.Skeleton/index.ts
index 354cb7f..282d615 100644
--- a/ui/src/plugins/com.example.Skeleton/index.ts
+++ b/ui/src/plugins/com.example.Skeleton/index.ts
@@ -13,12 +13,13 @@
 // limitations under the License.
 
 import {
+  createStore,
   MetricVisualisation,
   Plugin,
   PluginContext,
   PluginContextTrace,
   PluginDescriptor,
-  TrackInstanceDescriptor,
+  Store,
 } from '../../public';
 
 interface State {
@@ -26,20 +27,24 @@
 }
 
 // SKELETON: Rename this class to match your plugin.
-class Skeleton implements Plugin<State> {
+class Skeleton implements Plugin {
+  private store: Store<State> = createStore({foo: 'foo'});
+
   onActivate(_: PluginContext): void {
     //
   }
 
-  migrate(_initialState: unknown): State {
-    return {foo: 'bar'};
+  async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
+    this.store = ctx.mountStore((_: unknown): State => {
+      return {foo: 'bar'};
+    });
+
+    this.store.edit((state) => {
+      state.foo = 'baz';
+    });
   }
 
-  async onTraceLoad(_: PluginContextTrace<State>): Promise<void> {
-    //
-  }
-
-  async onTraceUnload(_: PluginContextTrace<State>): Promise<void> {
+  async onTraceUnload(_: PluginContextTrace): Promise<void> {
     //
   }
 
@@ -47,17 +52,12 @@
     //
   }
 
-  async findPotentialTracks(_: PluginContextTrace<State>):
-      Promise<TrackInstanceDescriptor[]> {
-    return [];
-  }
-
-  metricVisualisations(_: PluginContextTrace<State>): MetricVisualisation[] {
+  metricVisualisations(_: PluginContextTrace): MetricVisualisation[] {
     return [];
   }
 }
 
-export const plugin: PluginDescriptor<State> = {
+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/dev.perfetto.ExampleState/index.ts b/ui/src/plugins/dev.perfetto.ExampleState/index.ts
index a18cfe2..9e4e31c 100644
--- a/ui/src/plugins/dev.perfetto.ExampleState/index.ts
+++ b/ui/src/plugins/dev.perfetto.ExampleState/index.ts
@@ -25,8 +25,8 @@
 
 // This example plugin shows using state that is persisted in the
 // permalink.
-class ExampleState implements Plugin<State> {
-  migrate(initialState: unknown): State {
+class ExampleState implements Plugin {
+  private migrate(initialState: unknown): State {
     if (initialState && typeof initialState === 'object' &&
         'counter' in initialState && typeof initialState.counter === 'number') {
       return {counter: initialState.counter};
@@ -39,8 +39,10 @@
     //
   }
 
-  async onTraceLoad(ctx: PluginContextTrace<State>): Promise<void> {
-    const {viewer, store} = ctx;
+  async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
+    const {viewer} = ctx;
+    const store = ctx.mountStore((init: unknown) => this.migrate(init));
+
     ctx.addCommand({
       id: 'dev.perfetto.ExampleState#ShowCounter',
       name: 'Show ExampleState counter',
@@ -54,7 +56,7 @@
   }
 }
 
-export const plugin: PluginDescriptor<State> = {
+export const plugin: PluginDescriptor = {
   pluginId: 'dev.perfetto.ExampleState',
   plugin: ExampleState,
 };
diff --git a/ui/src/public/index.ts b/ui/src/public/index.ts
index 60ab2b8..bd61971 100644
--- a/ui/src/public/index.ts
+++ b/ui/src/public/index.ts
@@ -17,11 +17,9 @@
 import {Hotkey} from '../base/hotkeys';
 import {duration, Span, time} from '../base/time';
 import {EngineProxy} from '../common/engine';
-import {TrackControllerFactory} from '../controller/track_controller';
 import {Store} from '../frontend/store';
 import {PxSpan, TimeScale} from '../frontend/time_scale';
-import {SliceRect, TrackCreator} from '../frontend/track';
-import {TrackButtonAttrs} from '../frontend/track_panel';
+import {SliceRect} from '../frontend/track';
 
 export {EngineProxy} from '../common/engine';
 export {
@@ -32,7 +30,7 @@
   STR,
   STR_NULL,
 } from '../common/query_result';
-export {Store} from '../frontend/store';
+export {createStore, Store} from '../frontend/store';
 
 
 // An imperative API for plugins to change the UI.
@@ -112,60 +110,15 @@
   path: string[];
 }
 
-export interface MetricVisualisation {
-  // The name of the metric e.g. 'android_camera'
-  metric: string;
-
-  // A vega or vega-lite visualisation spec.
-  // The data from the metric under path will be exposed as a
-  // datasource named "metric" in Vega(-Lite)
-  spec: string;
-
-  // A path index into the metric.
-  // For example if the metric returns the folowing protobuf:
-  // {
-  //   foo {
-  //     bar {
-  //       baz: { name: "a" }
-  //       baz: { name: "b" }
-  //       baz: { name: "c" }
-  //     }
-  //   }
-  // }
-  // That becomes the following json:
-  // { "foo": { "bar": { "baz": [
-  //  {"name": "a"},
-  //  {"name": "b"},
-  //  {"name": "c"},
-  // ]}}}
-  // And given path = ["foo", "bar", "baz"]
-  // We extract:
-  // [ {"name": "a"}, {"name": "b"}, {"name": "c"} ]
-  // And pass that to the vega(-lite) visualisation.
-  path: string[];
-}
-
 // This interface defines a context for a plugin, which is an object passed to
 // most hooks within the plugin. It should be used to interact with Perfetto.
 export interface PluginContext {
+  // The unique ID for this plugin.
+  readonly pluginId: string;
+
+  // The viewer API, used to interface with Perfetto.
   readonly viewer: Viewer;
 
-  // DEPRECATED. In prior versions of the UI tracks were split into a
-  // 'TrackController' and a 'Track'. In more recent versions of the UI
-  // the functionality of |TrackController| has been merged into Track so
-  // |TrackController|s are not necessary in new code.
-  LEGACY_registerTrackController(track: TrackControllerFactory): void;
-
-  // Register a track factory. The core UI invokes |TrackCreator| to
-  // construct tracks discovered by invoking |TrackProvider|s.
-  // The split between 'construction' and 'discovery' allows
-  // plugins to reuse common tracks for new data. For example: the
-  // dev.perfetto.AndroidGpu plugin could register a TrackProvider
-  // which returns GPU counter tracks. The counter track factory itself
-  // could be registered in dev.perfetto.CounterTrack - a whole
-  // different plugin.
-  LEGACY_registerTrack(track: TrackCreator): void;
-
   // Add a command.
   addCommand(command: Command): void;
 }
@@ -173,8 +126,11 @@
 export type Migrate<State> = (init: unknown) => State;
 
 export interface TrackContext {
-  // The ID of this track instance.
-  trackInstanceId: string;
+  // This track's key, used for making selections et al.
+  trackKey: string;
+
+  // Set of params passed in when the track was created.
+  params: unknown;
 
   // Creates a new store overlaying the track instance's state object.
   // A migrate function must be passed to convert any existing state to a
@@ -188,7 +144,7 @@
 }
 
 export interface Track {
-  onCreate(): void;
+  onCreate(ctx: TrackContext): void;
   render(ctx: CanvasRenderingContext2D): void;
   onFullRedraw(): void;
   getSliceRect(
@@ -196,32 +152,28 @@
       windowSpan: PxSpan, tStart: time, tEnd: time, depth: number): SliceRect
       |undefined;
   getHeight(): number;
-  getTrackShellButtons(): Array<m.Vnode<TrackButtonAttrs>>;
-  getContextMenu(): m.Vnode<any>|null;
+  getTrackShellButtons(): m.Children;
   onMouseMove(position: {x: number, y: number}): void;
   onMouseClick(position: {x: number, y: number}): boolean;
   onMouseOut(): void;
   onDestroy(): void;
 }
 
+// A definition of a track, including a renderer implementation and metadata.
 export interface TrackDescriptor {
-  // A unique identifier for the track. This must be unique within all tracks.
+  // A unique identifier for this track.
   uri: string;
 
-  // A human friendly name for this track. Used when displaying the list of
-  // tracks to the user. E.g. when adding a new track to the workspace.
-  displayName: string;
-
-  // A factory function returning the track object.
+  // A factory function returning a track object.
   track: (ctx: TrackContext) => Track;
 
-  // The track "kind" Uued by various subsystems e.g. aggregation controllers.
+  // The track "kind", used by various subsystems e.g. aggregation controllers.
   // This is where "XXX_TRACK_KIND" values should be placed.
   // TODO(stevegolton): This will be deprecated once we handle group selections
   // in a more generic way - i.e. EventSet.
-  kind: string;
+  kind?: string;
 
-  // An optional list of track IDs represented by this trace.
+  // Optional: list of track IDs represented by this trace.
   // This list is used for participation in track indexing by track ID.
   // This index is used by various subsystems to find links between tracks based
   // on the track IDs used by trace processor.
@@ -230,6 +182,12 @@
   // Optional: The CPU number associated with this track.
   cpu?: number;
 
+  // Optional: The UTID associated with this track.
+  utid?: number;
+
+  // Optional: The UPID associated with this track.
+  upid?: number;
+
   // Optional: A list of tags used for sorting, grouping and "chips".
   tags?: TrackTags;
 }
@@ -245,7 +203,7 @@
 // (for non-thread tracks) or a tid and secondary sort key (mapping of tid to
 // primary sort key is done independently).
 export enum PrimaryTrackSortKey {
-  DEBUG_SLICE_TRACK,
+  DEBUG_TRACK,
   NULL_TRACK,
   PROCESS_SCHEDULING_TRACK,
   PROCESS_SUMMARY_TRACK,
@@ -264,45 +222,45 @@
   ORDINARY_TRACK,
 }
 
-// Similar to PluginContext but with additional properties to operate on the
-// currently loaded trace. Passed to trace-relevant hooks instead of
+// Similar to PluginContext but with additional methods to operate on the
+// currently loaded trace. Passed to trace-relevant hooks on a plugin instead of
 // PluginContext.
-export interface PluginContextTrace<T = undefined> extends PluginContext {
+export interface PluginContextTrace extends PluginContext {
   readonly engine: EngineProxy;
-  readonly store: Store<T>;
 
-  // Add a new track from this plugin. The track is just made available here,
-  // it's not automatically shown until it's added to a workspace.
-  addTrack(trackDetails: TrackDescriptor): void;
+  // Register a new track against a unique key known as a URI.
+  // Once a track is registered it can be referenced multiple times on the
+  // timeline.
+  registerTrack(trackDesc: TrackDescriptor): void;
 
-  // Suggest a track be added to the workspace on a fresh trace load.
-  // Supersedes `findPotentialTracks()` which has been removed.
-  // Note: this API will be deprecated soon.
-  suggestTrack(trackInfo: TrackInstanceDescriptor): void;
+  // Add a new entry to the pool of default tracks. Default tracks are a list of
+  // track references that describe the list of tracks that should be added to
+  // the main timeline on startup.
+  // Default tracks are only used when a trace is first loaded, not when loading
+  // from a permalink, where the existing list of tracks from the shared state
+  // is used instead.
+  addDefaultTrack(track: TrackRef): void;
+
+  // Simultaneously register a track and add it as a default track in one go.
+  // This is simply a helper which calls registerTrack() then addDefaultTrack()
+  // with the same URI.
+  registerStaticTrack(track: TrackDescriptor&TrackRef): void;
+
+  // Create a store mounted over the top of this plugin's persistent state.
+  mountStore<T>(migrate: Migrate<T>): Store<T>;
 }
 
-export interface BasePlugin<State> {
+export interface Plugin {
   // Lifecycle methods.
   onActivate(ctx: PluginContext): void;
-  onTraceLoad?(ctx: PluginContextTrace<State>): Promise<void>;
-  onTraceUnload?(ctx: PluginContextTrace<State>): Promise<void>;
+  onTraceLoad?(ctx: PluginContextTrace): Promise<void>;
+  onTraceUnload?(ctx: PluginContextTrace): Promise<void>;
   onDeactivate?(ctx: PluginContext): void;
 
   // Extension points.
   metricVisualisations?(ctx: PluginContext): MetricVisualisation[];
 }
 
-export interface StatefulPlugin<State> extends BasePlugin<State> {
-  // Function to migrate the persistent state.
-  migrate(initialState: unknown): State;
-}
-
-// Generic interface all plugins must implement.
-// If a state type is passed, the plugin must implement migrate(). Otherwise if
-// the state type is omitted, migrate need not be defined.
-export type Plugin<State = undefined> =
-    State extends undefined ? BasePlugin<State>: StatefulPlugin<State>;
-
 // 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:
@@ -313,22 +271,25 @@
 //   ... methods from the TracePlugin interface go here ...
 // }
 // ... which can then be passed around by class i.e. MyPlugin
-export interface PluginClass<T> {
+export interface PluginClass {
   // Instantiate the plugin.
-  new(): Plugin<T>;
+  new(): Plugin;
 }
 
-export interface TrackInstanceDescriptor {
-  // A human readable name for this specific track. It will normally be
-  // displayed on the left-hand-side of the track.
-  name: string;
-
-  // Used to define default sort order for new traces.
-  // Note: sortKey will be deprecated soon in favour of tags.
-  sortKey: PrimaryTrackSortKey;
-
-  // URI of the suggested track.
+// Describes a reference to a registered track.
+export interface TrackRef {
+  // URI of the registered track.
   uri: string;
+
+  // A human readable name for this track - displayed in the track shell.
+  displayName: string;
+
+  // Optional: An opaque object used to customize this instance of the track.
+  params?: unknown;
+
+  // Optional: Used to define default sort order for new traces.
+  // Note: This will be deprecated soon in favour of tags & sort rules.
+  sortKey?: PrimaryTrackSortKey;
 }
 
 // A predicate for selecting a groups of tracks.
@@ -357,9 +318,9 @@
 
 // Plugins can be passed as class refs, factory functions, or concrete plugin
 // implementations.
-export type PluginFactory<T> = PluginClass<T>|Plugin<T>|(() => Plugin<T>);
+export type PluginFactory = PluginClass|Plugin|(() => Plugin);
 
-export interface PluginDescriptor<T = undefined> {
+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.
@@ -367,5 +328,5 @@
 
   // 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<T>;
+  plugin: PluginFactory;
 }
diff --git a/ui/src/tracks/actual_frames/index.ts b/ui/src/tracks/actual_frames/index.ts
index 90de29a..00c9e00 100644
--- a/ui/src/tracks/actual_frames/index.ts
+++ b/ui/src/tracks/actual_frames/index.ts
@@ -14,33 +14,26 @@
 
 import {BigintMath as BIMath} from '../../base/bigint_math';
 import {duration, time} from '../../base/time';
-import {LONG, LONG_NULL, NUM, STR} from '../../common/query_result';
-import {TrackData} from '../../common/track_data';
-import {TrackController} from '../../controller/track_controller';
-import {NewTrackArgs, TrackBase} from '../../frontend/track';
-import {Plugin, PluginContext, PluginDescriptor} from '../../public';
-import {ChromeSliceTrack} from '../chrome_slices';
+import {
+  LONG,
+  LONG_NULL,
+  NUM,
+  NUM_NULL,
+  STR,
+  STR_NULL,
+} from '../../common/query_result';
+import {SliceData, SliceTrackBase} from '../../frontend/slice_track_base';
+import {
+  EngineProxy,
+  Plugin,
+  PluginContext,
+  PluginContextTrace,
+  PluginDescriptor,
+} from '../../public';
+import {getTrackName} from '../../public/utils';
 
 export const ACTUAL_FRAMES_SLICE_TRACK_KIND = 'ActualFramesSliceTrack';
 
-export interface Config {
-  maxDepth: number;
-  trackIds: number[];
-}
-
-export interface Data extends TrackData {
-  // Slices are stored in a columnar fashion. All fields have the same length.
-  strings: string[];
-  sliceIds: Float64Array;
-  starts: BigInt64Array;
-  ends: BigInt64Array;
-  depths: Uint16Array;
-  titles: Uint16Array;   // Index in |strings|.
-  colors?: Uint16Array;  // Index in |strings|.
-  isInstant: Uint16Array;
-  isIncomplete: Uint16Array;
-}
-
 const BLUE_COLOR = '#03A9F4';         // Blue 500
 const GREEN_COLOR = '#4CAF50';        // Green 500
 const YELLOW_COLOR = '#FFEB3B';       // Yellow 500
@@ -48,55 +41,60 @@
 const LIGHT_GREEN_COLOR = '#C0D588';  // Light Green 500
 const PINK_COLOR = '#F515E0';         // Pink 500
 
-class ActualFramesSliceTrackController extends TrackController<Config, Data> {
-  static readonly kind = ACTUAL_FRAMES_SLICE_TRACK_KIND;
+class SliceTrack extends SliceTrackBase {
   private maxDur = 0n;
 
+  constructor(
+      private engine: EngineProxy, maxDepth: number, trackKey: string,
+      private trackIds: number[], namespace?: string) {
+    super(maxDepth, trackKey, 'actual_frame_timeline_slice', namespace);
+  }
+
   async onBoundsChange(start: time, end: time, resolution: duration):
-      Promise<Data> {
+      Promise<SliceData> {
     if (this.maxDur === 0n) {
-      const maxDurResult = await this.query(`
-        select
-          max(iif(dur = -1, (SELECT end_ts FROM trace_bounds) - ts, dur))
-            as maxDur
-        from experimental_slice_layout
-        where filter_track_ids = '${this.config.trackIds.join(',')}'
-      `);
+      const maxDurResult = await this.engine.query(`
+    select
+      max(iif(dur = -1, (SELECT end_ts FROM trace_bounds) - ts, dur))
+        as maxDur
+    from experimental_slice_layout
+    where filter_track_ids = '${this.trackIds.join(',')}'
+  `);
       this.maxDur = maxDurResult.firstRow({maxDur: LONG_NULL}).maxDur || 0n;
     }
 
-    const rawResult = await this.query(`
-      SELECT
-        (s.ts + ${resolution / 2n}) / ${resolution} * ${resolution} as tsq,
-        s.ts as ts,
-        max(iif(s.dur = -1, (SELECT end_ts FROM trace_bounds) - s.ts, s.dur))
-            as dur,
-        s.layout_depth as layoutDepth,
-        s.name as name,
-        s.id as id,
-        s.dur = 0 as isInstant,
-        s.dur = -1 as isIncomplete,
-        CASE afs.jank_tag
-          WHEN 'Self Jank' THEN '${RED_COLOR}'
-          WHEN 'Other Jank' THEN '${YELLOW_COLOR}'
-          WHEN 'Dropped Frame' THEN '${BLUE_COLOR}'
-          WHEN 'Buffer Stuffing' THEN '${LIGHT_GREEN_COLOR}'
-          WHEN 'SurfaceFlinger Stuffing' THEN '${LIGHT_GREEN_COLOR}'
-          WHEN 'No Jank' THEN '${GREEN_COLOR}'
-          ELSE '${PINK_COLOR}'
-        END as color
-      from experimental_slice_layout s
-      join actual_frame_timeline_slice afs using(id)
-      where
-        filter_track_ids = '${this.config.trackIds.join(',')}' and
-        s.ts >= ${start - this.maxDur} and
-        s.ts <= ${end}
-      group by tsq, s.layout_depth
-      order by tsq, s.layout_depth
-    `);
+    const rawResult = await this.engine.query(`
+  SELECT
+    (s.ts + ${resolution / 2n}) / ${resolution} * ${resolution} as tsq,
+    s.ts as ts,
+    max(iif(s.dur = -1, (SELECT end_ts FROM trace_bounds) - s.ts, s.dur))
+        as dur,
+    s.layout_depth as layoutDepth,
+    s.name as name,
+    s.id as id,
+    s.dur = 0 as isInstant,
+    s.dur = -1 as isIncomplete,
+    CASE afs.jank_tag
+      WHEN 'Self Jank' THEN '${RED_COLOR}'
+      WHEN 'Other Jank' THEN '${YELLOW_COLOR}'
+      WHEN 'Dropped Frame' THEN '${BLUE_COLOR}'
+      WHEN 'Buffer Stuffing' THEN '${LIGHT_GREEN_COLOR}'
+      WHEN 'SurfaceFlinger Stuffing' THEN '${LIGHT_GREEN_COLOR}'
+      WHEN 'No Jank' THEN '${GREEN_COLOR}'
+      ELSE '${PINK_COLOR}'
+    END as color
+  from experimental_slice_layout s
+  join actual_frame_timeline_slice afs using(id)
+  where
+    filter_track_ids = '${this.trackIds.join(',')}' and
+    s.ts >= ${start - this.maxDur} and
+    s.ts <= ${end}
+  group by tsq, s.layout_depth
+  order by tsq, s.layout_depth
+`);
 
     const numRows = rawResult.numRows();
-    const slices: Data = {
+    const slices: SliceData = {
       start,
       end,
       resolution,
@@ -154,17 +152,74 @@
   }
 }
 
-export class ActualFramesSliceTrack extends ChromeSliceTrack {
-  static readonly kind = ACTUAL_FRAMES_SLICE_TRACK_KIND;
-  static create(args: NewTrackArgs): TrackBase {
-    return new ActualFramesSliceTrack(args);
-  }
-}
-
 class ActualFrames implements Plugin {
-  onActivate(ctx: PluginContext): void {
-    ctx.LEGACY_registerTrackController(ActualFramesSliceTrackController);
-    ctx.LEGACY_registerTrack(ActualFramesSliceTrack);
+  onActivate(_ctx: PluginContext): void {}
+
+  async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
+    const {engine} = ctx;
+    const result = await engine.query(`
+      with process_async_tracks as materialized (
+        select
+          process_track.upid as upid,
+          process_track.name as trackName,
+          process.name as processName,
+          process.pid as pid,
+          group_concat(process_track.id) as trackIds,
+          count(1) as trackCount
+        from process_track
+        left join process using(upid)
+        where process_track.name = "Actual Timeline"
+        group by
+          process_track.upid,
+          process_track.name
+      )
+      select
+        t.*,
+        max_layout_depth(t.trackCount, t.trackIds) as maxDepth
+      from process_async_tracks t;
+  `);
+
+    const it = result.iter({
+      upid: NUM,
+      trackName: STR_NULL,
+      trackIds: STR,
+      processName: STR_NULL,
+      pid: NUM_NULL,
+      maxDepth: NUM_NULL,
+    });
+    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;
+
+      if (maxDepth === null) {
+        // If there are no slices in this track, skip it.
+        continue;
+      }
+
+      const kind = 'ActualFrames';
+      const displayName =
+          getTrackName({name: trackName, upid, pid, processName, kind});
+
+      ctx.registerStaticTrack({
+        uri: `perfetto.ActualFrames#${upid}`,
+        displayName,
+        trackIds,
+        kind: ACTUAL_FRAMES_SLICE_TRACK_KIND,
+        track: ({trackKey}) => {
+          return new SliceTrack(
+              engine,
+              maxDepth,
+              trackKey,
+              trackIds,
+          );
+        },
+      });
+    }
   }
 }
 
diff --git a/ui/src/tracks/android_log/index.ts b/ui/src/tracks/android_log/index.ts
index 743d665..bc26268 100644
--- a/ui/src/tracks/android_log/index.ts
+++ b/ui/src/tracks/android_log/index.ts
@@ -154,14 +154,14 @@
         await ctx.engine.query(`select count(1) as cnt from android_logs`);
     const count = result.firstRow({cnt: NUM}).cnt;
     if (count > 0) {
-      ctx.addTrack({
+      ctx.registerStaticTrack({
         uri: 'perfetto.AndroidLog',
         displayName: 'Android logs',
         kind: ANDROID_LOGS_TRACK_KIND,
-        track: ({trackInstanceId}) => {
+        track: ({trackKey}) => {
           return new TrackWithControllerAdapter<Config, Data>(
               ctx.engine,
-              trackInstanceId,
+              trackKey,
               {},
               AndroidLogTrack,
               AndroidLogTrackController);
diff --git a/ui/src/tracks/annotation/index.ts b/ui/src/tracks/annotation/index.ts
index 2c8649a..860ee20 100644
--- a/ui/src/tracks/annotation/index.ts
+++ b/ui/src/tracks/annotation/index.ts
@@ -23,6 +23,7 @@
   PluginContextTrace,
   PluginDescriptor,
 } from '../../public';
+import {ChromeSliceTrack, SLICE_TRACK_KIND} from '../chrome_slices/';
 import {
   Config as CounterTrackConfig,
   COUNTER_TRACK_KIND,
@@ -33,9 +34,48 @@
   onActivate(_ctx: PluginContext): void {}
 
   async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
+    await this.addAnnotationTracks(ctx);
     await this.addAnnotationCounterTracks(ctx);
   }
 
+  private async addAnnotationTracks(ctx: PluginContextTrace) {
+    const {engine} = ctx;
+
+    const result = await engine.query(`
+      select id, name
+      from annotation_slice_track
+      order by name
+    `);
+
+    const it = result.iter({
+      id: NUM,
+      name: STR,
+    });
+
+    for (; it.valid(); it.next()) {
+      const id = it.id;
+      const name = it.name;
+
+      ctx.registerStaticTrack({
+        uri: `perfetto.Annotation#${id}`,
+        displayName: name,
+        kind: SLICE_TRACK_KIND,
+        tags: {
+          metric: true,
+        },
+        track: (({trackKey}) => {
+          return new ChromeSliceTrack(
+              engine,
+              0,
+              trackKey,
+              id,
+              'annotation',
+          );
+        }),
+      });
+    }
+  }
+
   private async addAnnotationCounterTracks(ctx: PluginContextTrace) {
     const {engine} = ctx;
     const counterResult = await engine.query(`
@@ -69,7 +109,7 @@
         maximumValue,
       };
 
-      ctx.addTrack({
+      ctx.registerStaticTrack({
         uri: `perfetto.Annotation#counter${id}`,
         displayName: name,
         kind: COUNTER_TRACK_KIND,
diff --git a/ui/src/tracks/async_slices/index.ts b/ui/src/tracks/async_slices/index.ts
index 09d0dda..787ec7b 100644
--- a/ui/src/tracks/async_slices/index.ts
+++ b/ui/src/tracks/async_slices/index.ts
@@ -14,62 +14,57 @@
 
 import {BigintMath as BIMath} from '../../base/bigint_math';
 import {duration, time} from '../../base/time';
-import {LONG, LONG_NULL, NUM, STR} from '../../common/query_result';
-import {TrackData} from '../../common/track_data';
 import {
-  TrackController,
-} from '../../controller/track_controller';
-import {NewTrackArgs, TrackBase} from '../../frontend/track';
-import {Plugin, PluginContext, PluginDescriptor} from '../../public';
-import {ChromeSliceTrack} from '../chrome_slices';
+  LONG,
+  LONG_NULL,
+  NUM,
+  NUM_NULL,
+  STR,
+  STR_NULL,
+} from '../../common/query_result';
+import {SliceData, SliceTrackBase} from '../../frontend/slice_track_base';
+import {
+  EngineProxy,
+  Plugin,
+  PluginContext,
+  PluginContextTrace,
+  PluginDescriptor,
+} from '../../public';
+import {getTrackName} from '../../public/utils';
 
 export const ASYNC_SLICE_TRACK_KIND = 'AsyncSliceTrack';
 
-export interface Config {
-  maxDepth: number;
-  trackIds: number[];
-}
-
-export interface Data extends TrackData {
-  // Slices are stored in a columnar fashion. All fields have the same length.
-  strings: string[];
-  sliceIds: Float64Array;
-  starts: BigInt64Array;
-  ends: BigInt64Array;
-  depths: Uint16Array;
-  titles: Uint16Array;  // Index in |strings|.
-  isInstant: Uint16Array;
-  isIncomplete: Uint16Array;
-}
-
-class AsyncSliceTrackController extends TrackController<Config, Data> {
-  static readonly kind = ASYNC_SLICE_TRACK_KIND;
+class AsyncSliceTrack extends SliceTrackBase {
   private maxDurNs: duration = 0n;
 
+  constructor(
+      private engine: EngineProxy, maxDepth: number, trackKey: string,
+      private trackIds: number[], namespace?: string) {
+    // TODO is 'slice' right here?
+    super(maxDepth, trackKey, 'slice', namespace);
+  }
+
   async onBoundsChange(start: time, end: time, resolution: duration):
-      Promise<Data> {
+      Promise<SliceData> {
     if (this.maxDurNs === 0n) {
-      const maxDurResult = await this.query(`
-        select max(iif(dur = -1, (SELECT end_ts FROM trace_bounds) - ts, dur))
-        as maxDur from experimental_slice_layout
-        where filter_track_ids = '${this.config.trackIds.join(',')}'
+      const maxDurResult = await this.engine.query(`
+        select max(iif(dur = -1, (SELECT end_ts FROM trace_bounds) - ts,
+        dur)) as maxDur from experimental_slice_layout where filter_track_ids
+        = '${this.trackIds.join(',')}'
       `);
       this.maxDurNs = maxDurResult.firstRow({maxDur: LONG_NULL}).maxDur || 0n;
     }
 
-    const queryRes = await this.query(`
+    const queryRes = await this.engine.query(`
       SELECT
       (ts + ${resolution / 2n}) / ${resolution} * ${resolution} as tsq,
         ts,
-        max(iif(dur = -1, (SELECT end_ts FROM trace_bounds) - ts, dur)) as dur,
-        layout_depth as depth,
-        ifnull(name, '[null]') as name,
-        id,
-        dur = 0 as isInstant,
-        dur = -1 as isIncomplete
+        max(iif(dur = -1, (SELECT end_ts FROM trace_bounds) - ts, dur)) as
+        dur, layout_depth as depth, ifnull(name, '[null]') as name, id, dur =
+        0 as isInstant, dur = -1 as isIncomplete
       from experimental_slice_layout
       where
-        filter_track_ids = '${this.config.trackIds.join(',')}' and
+        filter_track_ids = '${this.trackIds.join(',')}' and
         ts >= ${start - this.maxDurNs} and
         ts <= ${end}
       group by tsq, layout_depth
@@ -77,7 +72,7 @@
     `);
 
     const numRows = queryRes.numRows();
-    const slices: Data = {
+    const slices: SliceData = {
       start,
       end,
       resolution,
@@ -132,17 +127,168 @@
   }
 }
 
-export class AsyncSliceTrack extends ChromeSliceTrack {
-  static readonly kind = ASYNC_SLICE_TRACK_KIND;
-  static create(args: NewTrackArgs): TrackBase {
-    return new AsyncSliceTrack(args);
-  }
-}
-
 class AsyncSlicePlugin implements Plugin {
-  onActivate(ctx: PluginContext) {
-    ctx.LEGACY_registerTrackController(AsyncSliceTrackController);
-    ctx.LEGACY_registerTrack(AsyncSliceTrack);
+  onActivate(_ctx: PluginContext) {}
+
+  async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
+    await this.addGlobalAsyncTracks(ctx);
+    await this.addProcessAsyncSliceTracks(ctx);
+  }
+
+  async addGlobalAsyncTracks(ctx: PluginContextTrace): Promise<void> {
+    const {engine} = ctx;
+    const rawGlobalAsyncTracks = await engine.query(`
+      with tracks_with_slices as materialized (
+        select distinct track_id
+        from slice
+      ),
+      global_tracks as (
+        select
+          track.parent_id as parent_id,
+          track.id as track_id,
+          track.name as name
+        from track
+        join tracks_with_slices on tracks_with_slices.track_id = track.id
+        where
+          track.type = "track"
+          or track.type = "gpu_track"
+          or track.type = "cpu_track"
+      ),
+      global_tracks_grouped as (
+        select
+          parent_id,
+          name,
+          group_concat(track_id) as trackIds,
+          count(track_id) as trackCount
+        from global_tracks track
+        group by parent_id, name
+      )
+      select
+        t.parent_id as parentId,
+        p.name as parentName,
+        t.name as name,
+        t.trackIds as trackIds,
+        max_layout_depth(t.trackCount, t.trackIds) as maxDepth
+      from global_tracks_grouped AS t
+      left join track p on (t.parent_id = p.id)
+      order by p.name, t.name;
+    `);
+    const it = rawGlobalAsyncTracks.iter({
+      name: STR_NULL,
+      parentName: STR_NULL,
+      parentId: NUM_NULL,
+      trackIds: STR,
+      maxDepth: NUM_NULL,
+    });
+
+    // let scrollJankRendered = false;
+
+    for (; it.valid(); it.next()) {
+      const rawName = it.name === null ? undefined : it.name;
+      // const rawParentName = it.parentName === null ? undefined :
+      // it.parentName;
+      const displayName = getTrackName({name: rawName, kind: 'AsyncSlice'});
+      const rawTrackIds = it.trackIds;
+      const trackIds = rawTrackIds.split(',').map((v) => Number(v));
+      // const parentTrackId = it.parentId;
+      const maxDepth = it.maxDepth;
+
+      // If there are no slices in this track, skip it.
+      if (maxDepth === null) {
+        continue;
+      }
+
+      // if (ENABLE_SCROLL_JANK_PLUGIN_V2.get() && !scrollJankRendered &&
+      //     name.includes(INPUT_LATENCY_TRACK)) {
+      //   // This ensures that the scroll jank tracks render above the tracks
+      //   // for GestureScrollUpdate.
+      //   await this.addScrollJankTracks(this.engine);
+      //   scrollJankRendered = true;
+      // }
+
+      ctx.registerStaticTrack({
+        uri: `perfetto.AsyncSlices#${rawName}`,
+        displayName,
+        trackIds,
+        kind: ASYNC_SLICE_TRACK_KIND,
+        track: ({trackKey}) => {
+          return new AsyncSliceTrack(
+              engine,
+              maxDepth,
+              trackKey,
+              trackIds,
+          );
+        },
+      });
+    }
+  }
+
+  async addProcessAsyncSliceTracks(ctx: PluginContextTrace): Promise<void> {
+    const result = await ctx.engine.query(`
+      with process_async_tracks as materialized (
+        select
+          process_track.upid as upid,
+          process_track.name as trackName,
+          process.name as processName,
+          process.pid as pid,
+          group_concat(process_track.id) as trackIds,
+          count(1) as trackCount
+        from process_track
+        left join process using(upid)
+        where
+            process_track.name is null or
+            process_track.name not like "% Timeline"
+        group by
+          process_track.upid,
+          process_track.name
+      )
+      select
+        t.*,
+        max_layout_depth(t.trackCount, t.trackIds) as maxDepth
+      from process_async_tracks t;
+    `);
+
+    const it = result.iter({
+      upid: NUM,
+      trackName: STR_NULL,
+      trackIds: STR,
+      processName: STR_NULL,
+      pid: NUM_NULL,
+      maxDepth: NUM_NULL,
+    });
+    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;
+
+      if (maxDepth === null) {
+        // If there are no slices in this track, skip it.
+        continue;
+      }
+
+      const kind = ASYNC_SLICE_TRACK_KIND;
+      const displayName =
+          getTrackName({name: trackName, upid, pid, processName, kind});
+
+      ctx.registerStaticTrack({
+        uri: `perfetto.AsyncSlices#process.${pid}${rawTrackIds}`,
+        displayName,
+        trackIds,
+        kind: ASYNC_SLICE_TRACK_KIND,
+        track: ({trackKey}) => {
+          return new AsyncSliceTrack(
+              ctx.engine,
+              maxDepth,
+              trackKey,
+              trackIds,
+          );
+        },
+      });
+    }
   }
 }
 
diff --git a/ui/src/tracks/chrome_critical_user_interactions/details_panel.ts b/ui/src/tracks/chrome_critical_user_interactions/details_panel.ts
new file mode 100644
index 0000000..9eea78f
--- /dev/null
+++ b/ui/src/tracks/chrome_critical_user_interactions/details_panel.ts
@@ -0,0 +1,236 @@
+// 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 {duration, Time, time} from '../../base/time';
+import {exists} from '../../base/utils';
+import {LONG, LONG_NULL, NUM, STR} from '../../common/query_result';
+import {raf} from '../../core/raf_scheduler';
+import {
+  BottomTab,
+  bottomTabRegistry,
+  NewBottomTabArgs,
+} from '../../frontend/bottom_tab';
+import {
+  GenericSliceDetailsTabConfig,
+} from '../../frontend/generic_slice_details_tab';
+import {sqlValueToString} from '../../frontend/sql_utils';
+import {Timestamp} from '../../frontend/widgets/timestamp';
+import {Anchor} from '../../widgets/anchor';
+import {DetailsShell} from '../../widgets/details_shell';
+import {DurationWidget} from '../../widgets/duration';
+import {GridLayout, GridLayoutColumn} from '../../widgets/grid_layout';
+import {Section} from '../../widgets/section';
+import {SqlRef} from '../../widgets/sql_ref';
+import {dictToTreeNodes, Tree} from '../../widgets/tree';
+
+interface PageLoadMetrics {
+  url: string;
+  navigationId: number;
+  fcpDuration?: duration;
+  lcpDuration?: duration;
+  fcpTs: time, lcpTs?: time,
+}
+
+enum CriticalUserJourneyType {
+  UNKNOWN = 'Unknown',
+  PAGE_LOAD = 'PageLoad',
+}
+
+function convertToCriticalUserJourneyType(cujType: string):
+    CriticalUserJourneyType {
+  switch (cujType) {
+    case CriticalUserJourneyType.PAGE_LOAD:
+      return CriticalUserJourneyType.PAGE_LOAD;
+    default:
+      return CriticalUserJourneyType.UNKNOWN;
+  }
+}
+
+interface Data {
+  name: string;
+  // Timestamp of the beginning of this slice in nanoseconds.
+  ts: time;
+  // Duration of this slice in nanoseconds.
+  dur: duration;
+  type: CriticalUserJourneyType;
+  tableName: string;
+  // Metrics for |type| = CriticalUserJourney.PAGE_LOAD
+  pageLoadMetrics?: PageLoadMetrics;
+}
+
+export class CriticalUserInteractionDetailsPanel extends
+    BottomTab<GenericSliceDetailsTabConfig> {
+  static readonly kind = 'org.perfetto.CriticalUserInteractionDetailsPanel';
+  data: Data|undefined;
+  loaded = false;
+
+  static create(args: NewBottomTabArgs): CriticalUserInteractionDetailsPanel {
+    return new CriticalUserInteractionDetailsPanel(args);
+  }
+
+  constructor(args: NewBottomTabArgs) {
+    super(args);
+    this.loadData();
+  }
+
+  private async loadData() {
+    const queryResult = await this.engine.query(`
+      SELECT
+        name,
+        ts,
+        dur,
+        type AS tableName
+      FROM chrome_interactions
+      WHERE scoped_id = ${this.config.id}`);
+
+    const iter = queryResult.firstRow({
+      name: STR,
+      ts: LONG,
+      dur: LONG,
+      tableName: STR,
+    });
+
+    this.data = {
+      name: iter.name,
+      ts: Time.fromRaw(iter.ts),
+      dur: iter.dur,
+      type: convertToCriticalUserJourneyType(iter.name),
+      tableName: iter.tableName,
+    };
+
+    await this.loadMetrics();
+
+    this.loaded = true;
+    raf.scheduleFullRedraw();
+  }
+
+  private async loadMetrics() {
+    if (exists(this.data)) {
+      switch (this.data.type) {
+        case CriticalUserJourneyType.PAGE_LOAD:
+          await this.loadPageLoadMetrics();
+          break;
+        default:
+          break;
+      }
+    }
+  }
+
+  private async loadPageLoadMetrics() {
+    if (exists(this.data)) {
+      const queryResult = await this.engine.query(`
+      SELECT
+        navigation_id AS navigationId,
+        url,
+        fcp AS fcpDuration,
+        lcp AS lcpDuration,
+        fcp_ts AS fcpTs,
+        lcp_ts AS lcpTs
+      FROM chrome_page_loads
+      WHERE navigation_id = ${this.config.id}`);
+
+      const iter = queryResult.firstRow({
+        navigationId: NUM,
+        url: STR,
+        fcpDuration: LONG_NULL,
+        lcpDuration: LONG_NULL,
+        fcpTs: LONG,
+        lcpTs: LONG,
+      });
+
+      this.data.pageLoadMetrics = {
+        navigationId: iter.navigationId,
+        url: iter.url,
+        fcpTs: Time.fromRaw(iter.fcpTs),
+      };
+
+      if (exists(iter.fcpDuration)) {
+        this.data.pageLoadMetrics.fcpDuration = iter.fcpDuration;
+      }
+
+      if (exists(iter.lcpDuration)) {
+        this.data.pageLoadMetrics.lcpDuration = iter.lcpDuration;
+      }
+
+      if (Number(iter.lcpTs) != 0) {
+        this.data.pageLoadMetrics.lcpTs = Time.fromRaw(iter.lcpTs);
+      }
+    }
+  }
+
+  private renderDetailsDictionary(): m.Child[] {
+    const details: {[key: string]: m.Child} = {};
+    if (exists(this.data)) {
+      details['Name'] = sqlValueToString(this.data.name);
+      details['Timestamp'] = m(Timestamp, {ts: this.data.ts});
+      if (exists(this.data.pageLoadMetrics)) {
+        details['FCP Timestamp'] =
+            m(Timestamp, {ts: this.data.pageLoadMetrics.fcpTs});
+        if (exists(this.data.pageLoadMetrics.fcpDuration)) {
+          details['FCP Duration'] =
+              m(DurationWidget, {dur: this.data.pageLoadMetrics.fcpDuration});
+        }
+        if (exists(this.data.pageLoadMetrics.lcpTs)) {
+          details['LCP Timestamp'] =
+              m(Timestamp, {ts: this.data.pageLoadMetrics.lcpTs});
+        }
+        if (exists(this.data.pageLoadMetrics.lcpDuration)) {
+          details['LCP Duration'] =
+              m(DurationWidget, {dur: this.data.pageLoadMetrics.lcpDuration});
+        }
+        details['Navigation ID'] = this.data.pageLoadMetrics.navigationId;
+        const url = this.data.pageLoadMetrics.url;
+        details['URL'] =
+            m(Anchor, {href: url, target: '_blank', icon: 'open_in_new'}, url);
+      }
+      details['SQL ID'] =
+          m(SqlRef, {table: 'chrome_interactions', id: this.config.id});
+    }
+
+    return dictToTreeNodes(details);
+  }
+
+  viewTab() {
+    if (this.data === undefined) {
+      return m('h2', 'Loading');
+    }
+
+    return m(
+        DetailsShell,
+        {
+          title: this.getTitle(),
+        },
+        m(GridLayout,
+          m(
+              GridLayoutColumn,
+              m(
+                  Section,
+                  {title: 'Details'},
+                  m(Tree, this.renderDetailsDictionary()),
+                  ),
+              )));
+  }
+
+  getTitle(): string {
+    return this.config.title;
+  }
+
+  isLoading() {
+    return !this.loaded;
+  }
+}
+
+bottomTabRegistry.register(CriticalUserInteractionDetailsPanel);
diff --git a/ui/src/tracks/chrome_critical_user_interactions/index.ts b/ui/src/tracks/chrome_critical_user_interactions/index.ts
new file mode 100644
index 0000000..3d1b498
--- /dev/null
+++ b/ui/src/tracks/chrome_critical_user_interactions/index.ts
@@ -0,0 +1,110 @@
+// 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 {Actions} from '../../common/actions';
+import {SCROLLING_TRACK_GROUP} from '../../common/state';
+import {globals} from '../../frontend/globals';
+import {NamedSliceTrackTypes} from '../../frontend/named_slice_track';
+import {NewTrackArgs, TrackBase} from '../../frontend/track';
+import {
+  Plugin,
+  PluginContext,
+  PluginContextTrace,
+  PluginDescriptor,
+  PrimaryTrackSortKey,
+} from '../../public';
+import {
+  CustomSqlDetailsPanelConfig,
+  CustomSqlImportConfig,
+  CustomSqlTableDefConfig,
+  CustomSqlTableSliceTrack,
+} from '../custom_sql_table_slices';
+
+import {CriticalUserInteractionDetailsPanel} from './details_panel';
+
+export const CRITICAL_USER_INTERACTIONS_KIND =
+    'org.chromium.TopLevelScrolls.scrolls';
+
+export class CriticalUserInteractionTrack extends
+    CustomSqlTableSliceTrack<NamedSliceTrackTypes> {
+  static readonly kind = CRITICAL_USER_INTERACTIONS_KIND;
+
+  static create(args: NewTrackArgs): TrackBase {
+    return new CriticalUserInteractionTrack(args);
+  }
+
+  getSqlDataSource(): CustomSqlTableDefConfig {
+    return {
+      columns: ['scoped_id AS id', 'name', 'ts', 'dur', 'type'],
+      sqlTableName: 'chrome_interactions',
+    };
+  }
+
+  getDetailsPanel(): CustomSqlDetailsPanelConfig {
+    return {
+      kind: CriticalUserInteractionDetailsPanel.kind,
+      config: {
+        sqlTableName: this.tableName,
+        title: 'Chrome Critical User Interaction',
+      },
+    };
+  }
+
+  getSqlImports(): CustomSqlImportConfig {
+    return {
+      modules: ['chrome.interactions'],
+    };
+  }
+}
+
+export function addCriticalUserInteractionTrack() {
+  const trackKey = uuidv4();
+  globals.dispatchMultiple([
+    Actions.addTrack({
+      key: trackKey,
+      uri: CriticalUserInteractionTrack.kind,
+      name: `Chrome Interactions`,
+      trackSortKey: PrimaryTrackSortKey.DEBUG_TRACK,
+      trackGroup: SCROLLING_TRACK_GROUP,
+    }),
+    Actions.toggleTrackPinned({trackKey}),
+  ]);
+}
+
+class CriticalUserInteractionPlugin implements Plugin {
+  async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
+    ctx.registerStaticTrack({
+      uri: CriticalUserInteractionTrack.kind,
+      kind: CriticalUserInteractionTrack.kind,
+      displayName: 'Chrome Interactions',
+      track: (trackCtx) => new CriticalUserInteractionTrack(
+          {engine: ctx.engine, trackKey: trackCtx.trackKey}),
+    });
+  }
+
+  onActivate(ctx: PluginContext): void {
+    ctx.addCommand({
+      id: 'perfetto.CriticalUserInteraction.AddInteractionTrack',
+      name: 'Add Chrome Interactions track',
+      callback: () => addCriticalUserInteractionTrack(),
+    });
+  }
+}
+
+export const plugin: PluginDescriptor = {
+  pluginId: 'perfetto.CriticalUserInteraction',
+  plugin: CriticalUserInteractionPlugin,
+};
diff --git a/ui/src/tracks/chrome_scroll_jank/chrome_tasks_scroll_jank_track.ts b/ui/src/tracks/chrome_scroll_jank/chrome_tasks_scroll_jank_track.ts
index 1026328..e9d1fa4 100644
--- a/ui/src/tracks/chrome_scroll_jank/chrome_tasks_scroll_jank_track.ts
+++ b/ui/src/tracks/chrome_scroll_jank/chrome_tasks_scroll_jank_track.ts
@@ -12,8 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {v4 as uuidv4} from 'uuid';
-
 import {Engine} from '../../common/engine';
 import {NUM} from '../../common/query_result';
 import {InThreadTrackSortKey} from '../../common/state';
@@ -42,13 +40,14 @@
     return new ChromeTasksScrollJankTrack(args);
   }
 
-  async initSqlTable(tableName: string) {
-    await this.engine.query(`
-create view ${tableName} as
-select s2.ts, s2.dur, s2.id, 0 as depth, s1.full_name as name
+  constructor(args: NewTrackArgs) {
+    super(args);
+  }
+
+  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
-    `);
+join slice s2 on s2.id=s1.slice_id`;
   }
 }
 export type GetTrackGroupUuidFn = (utid: number, upid: number|null) => string;
@@ -79,20 +78,18 @@
   }
 
   result.tracksToAdd.push({
-    id: uuidv4(),
-    engineId: engine.id,
-    kind: ChromeTasksScrollJankTrack.kind,
+    uri: 'perfetto.ChromeScrollJank',
     trackSortKey: {
       utid: it.utid,
       priority: InThreadTrackSortKey.ORDINARY,
     },
     name: 'Scroll Jank causes - long tasks',
-    config: {},
     trackGroup: getTrackGroupUuid(it.utid, it.upid),
   });
 
   // Initialise the chrome_tasks_delaying_input_processing table. It will be
   // used in the sql table above.
+  // TODO(stevegolton): Use viewer.tabs.openQuery().
   await engine.query(`
 select RUN_METRIC(
    'chrome/chrome_tasks_delaying_input_processing.sql',
diff --git a/ui/src/tracks/chrome_scroll_jank/event_latency_track.ts b/ui/src/tracks/chrome_scroll_jank/event_latency_track.ts
index cfcaada..498591e 100644
--- a/ui/src/tracks/chrome_scroll_jank/event_latency_track.ts
+++ b/ui/src/tracks/chrome_scroll_jank/event_latency_track.ts
@@ -12,16 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {v4 as uuidv4} from 'uuid';
-
 import {
   getColorForSlice,
 } from '../../common/colorizer';
-import {Engine} from '../../common/engine';
-import {
-  generateSqlWithInternalLayout,
-} from '../../common/internal_layout_utils';
-import {SCROLLING_TRACK_GROUP} from '../../common/state';
 import {globals} from '../../frontend/globals';
 import {
   NamedSliceTrackTypes,
@@ -36,20 +29,24 @@
 
 import {EventLatencySliceDetailsPanel} from './event_latency_details_panel';
 import {
+  SCROLL_JANK_GROUP_ID,
   ScrollJankPluginState,
   ScrollJankTracks as DecideTracksResult,
 } from './index';
 import {DEEP_RED_COLOR, RED_COLOR} from './jank_colors';
 
-const JANKY_LATENCY_NAME = 'Janky EventLatency';
+export const JANKY_LATENCY_NAME = 'Janky EventLatency';
 
 export interface EventLatencyTrackTypes extends NamedSliceTrackTypes {
   config: {baseTable: string;}
 }
 
+const CHROME_EVENT_LATENCY_TRACK_KIND =
+    'org.chromium.ScrollJank.event_latencies';
+
 export class EventLatencyTrack extends
     CustomSqlTableSliceTrack<EventLatencyTrackTypes> {
-  static readonly kind = 'org.chromium.ScrollJank.event_latencies';
+  static readonly kind = CHROME_EVENT_LATENCY_TRACK_KIND;
 
   static create(args: NewTrackArgs): TrackBase {
     return new EventLatencyTrack(args);
@@ -59,7 +56,7 @@
     super(args);
     ScrollJankPluginState.getInstance().registerTrack({
       kind: EventLatencyTrack.kind,
-      trackId: this.trackId,
+      trackKey: this.trackKey,
       tableName: this.tableName,
       detailsPanelConfig: this.getDetailsPanel(),
     });
@@ -70,11 +67,8 @@
     ScrollJankPluginState.getInstance().unregisterTrack(EventLatencyTrack.kind);
   }
 
-  async initSqlTable(tableName: string) {
-    const sql =
-        `CREATE VIEW ${tableName} AS SELECT * FROM ${this.config.baseTable}`;
-
-    await this.engine.query(sql);
+  getSqlSource(): string {
+    return `SELECT * FROM ${this.config.baseTable}`;
   }
 
   getDetailsPanel(): CustomSqlDetailsPanelConfig {
@@ -117,84 +111,16 @@
   // this behavior should be customized to show jank-related data.
 }
 
-export async function addLatencyTracks(engine: Engine):
-    Promise<DecideTracksResult> {
+export async function addLatencyTracks(): Promise<DecideTracksResult> {
   const result: DecideTracksResult = {
     tracksToAdd: [],
   };
 
-  const subTableSql = generateSqlWithInternalLayout({
-    columns: ['id', 'ts', 'dur', 'track_id', 'name'],
-    sourceTable: 'slice',
-    ts: 'ts',
-    dur: 'dur',
-    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')`,
-  });
-
-  // Table name must be unique - it cannot include '-' characters or begin with
-  // a numeric value.
-  const baseTable =
-      `table_${uuidv4().split('-').join('_')}_janky_event_latencies_v3`;
-  const tableDefSql = `CREATE TABLE ${baseTable} AS
-      WITH event_latencies AS (
-        ${subTableSql}
-      ), latency_stages AS (
-      SELECT
-        d.id,
-        d.ts,
-        d.dur,
-        d.track_id,
-        d.name,
-        d.depth,
-        min(a.id) AS parent_id
-      FROM slice s
-        JOIN descendant_slice(s.id) d
-        JOIN ancestor_slice(d.id) a
-      WHERE s.id IN (SELECT id FROM event_latencies)
-      GROUP BY d.id, d.ts, d.dur, d.track_id, d.name, d.parent_id, d.depth)
-    SELECT
-      id,
-      ts,
-      dur,
-      CASE
-        WHEN id IN (
-          SELECT id FROM chrome_janky_event_latencies_v3)
-        THEN '${JANKY_LATENCY_NAME}'
-        ELSE name
-      END
-      AS name,
-      depth * 3 AS depth
-    FROM event_latencies
-    UNION ALL
-    SELECT
-      ls.id,
-      ls.ts,
-      ls.dur,
-      ls.name,
-      depth + (
-        (SELECT depth FROM event_latencies
-        WHERE id = ls.parent_id LIMIT 1) * 3) AS depth
-    FROM latency_stages ls;`;
-
-  await engine.query(
-      `INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_jank_intervals`);
-  await engine.query(tableDefSql);
-
   result.tracksToAdd.push({
-    id: uuidv4(),
-    engineId: engine.id,
-    kind: EventLatencyTrack.kind,
+    uri: 'perfetto.ChromeScrollJank#eventLatency',
     trackSortKey: PrimaryTrackSortKey.ASYNC_SLICE_TRACK,
     name: 'Chrome Scroll Input Latencies',
-    config: {baseTable: baseTable},
-    trackGroup: SCROLLING_TRACK_GROUP,
+    trackGroup: SCROLL_JANK_GROUP_ID,
   });
 
   return result;
diff --git a/ui/src/tracks/chrome_scroll_jank/index.ts b/ui/src/tracks/chrome_scroll_jank/index.ts
index 592d9a5..7aedb64 100644
--- a/ui/src/tracks/chrome_scroll_jank/index.ts
+++ b/ui/src/tracks/chrome_scroll_jank/index.ts
@@ -12,22 +12,40 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {AddTrackArgs} from '../../common/actions';
+import {v4 as uuidv4} from 'uuid';
+
+import {Actions, AddTrackArgs, DeferredAction} from '../../common/actions';
 import {Engine} from '../../common/engine';
 import {featureFlags} from '../../common/feature_flags';
-import {ObjectById} from '../../common/state';
-import {Plugin, PluginContext, PluginDescriptor} from '../../public';
+import {
+  generateSqlWithInternalLayout,
+} from '../../common/internal_layout_utils';
+import {ObjectByKey} from '../../common/state';
+import {
+  Plugin,
+  PluginContext,
+  PluginContextTrace,
+  PluginDescriptor,
+  PrimaryTrackSortKey,
+} from '../../public';
 import {CustomSqlDetailsPanelConfig} from '../custom_sql_table_slices';
+import {NULL_TRACK_URI} from '../null_track';
 
 import {ChromeTasksScrollJankTrack} from './chrome_tasks_scroll_jank_track';
-import {addLatencyTracks, EventLatencyTrack} from './event_latency_track';
+import {
+  addLatencyTracks,
+  EventLatencyTrack,
+  JANKY_LATENCY_NAME,
+} from './event_latency_track';
 import {
   addScrollJankV3ScrollTrack,
   ScrollJankV3Track,
 } from './scroll_jank_v3_track';
-import {addTopLevelScrollTrack, TopLevelScrollTrack} from './scroll_track';
-
-export {Data} from '../chrome_slices';
+import {
+  addTopLevelScrollTrack,
+  CHROME_TOPLEVEL_SCROLLS_KIND,
+  TopLevelScrollTrack,
+} from './scroll_track';
 
 export const ENABLE_CHROME_SCROLL_JANK_PLUGIN = featureFlags.register({
   id: 'enableChromeScrollJankPlugin',
@@ -36,8 +54,6 @@
   defaultValue: false,
 });
 
-export const INPUT_LATENCY_TRACK = 'InputLatency::';
-
 export const ENABLE_SCROLL_JANK_PLUGIN_V2 = featureFlags.register({
   id: 'enableScrollJankPluginV2',
   name: 'Enable Scroll Jank plugin V2',
@@ -45,12 +61,18 @@
   defaultValue: false,
 });
 
+export const SCROLL_JANK_GROUP_ID = 'chrome-scroll-jank-track-group';
+
 export type ScrollJankTracks = {
   tracksToAdd: AddTrackArgs[],
 };
 
+export type ScrollJankTrackGroup = {
+  tracks: ScrollJankTracks; addTrackGroup: DeferredAction
+}
+
 export interface ScrollJankTrackSpec {
-  id: string;
+  key: string;
   sqlTableName: string;
   detailsPanelConfig: CustomSqlDetailsPanelConfig;
 }
@@ -58,7 +80,7 @@
 // Global state for the scroll jank plugin.
 export class ScrollJankPluginState {
   private static instance: ScrollJankPluginState;
-  private tracks: ObjectById<ScrollJankTrackSpec>;
+  private tracks: ObjectByKey<ScrollJankTrackSpec>;
 
   private constructor() {
     this.tracks = {};
@@ -74,12 +96,12 @@
 
   public registerTrack(args: {
     kind: string,
-    trackId: string,
+    trackKey: string,
     tableName: string,
     detailsPanelConfig: CustomSqlDetailsPanelConfig,
   }): void {
     this.tracks[args.kind] = {
-      id: args.trackId,
+      key: args.trackKey,
       sqlTableName: args.tableName,
       detailsPanelConfig: args.detailsPanelConfig,
     };
@@ -94,45 +116,185 @@
   }
 }
 
-export async function getScrollJankTracks(engine: Engine):
-    Promise<ScrollJankTracks> {
+export async function getScrollJankTracks(_engine: Engine):
+    Promise<ScrollJankTrackGroup> {
   const result: ScrollJankTracks = {
     tracksToAdd: [],
   };
 
-  const scrolls = addTopLevelScrollTrack(engine);
-  const scrollsResult = await scrolls;
-  let originalLength = result.tracksToAdd.length;
-  result.tracksToAdd.length += scrollsResult.tracksToAdd.length;
-  for (let i = 0; i < scrollsResult.tracksToAdd.length; ++i) {
-    result.tracksToAdd[i + originalLength] = scrollsResult.tracksToAdd[i];
-  }
+  const scrolls = await addTopLevelScrollTrack();
+  result.tracksToAdd = result.tracksToAdd.concat(scrolls.tracksToAdd);
 
-  const janks = addScrollJankV3ScrollTrack(engine);
-  const janksResult = await janks;
-  originalLength = result.tracksToAdd.length;
-  result.tracksToAdd.length += janksResult.tracksToAdd.length;
-  for (let i = 0; i < janksResult.tracksToAdd.length; ++i) {
-    result.tracksToAdd[i + originalLength] = janksResult.tracksToAdd[i];
-  }
+  const janks = await addScrollJankV3ScrollTrack();
+  result.tracksToAdd = result.tracksToAdd.concat(janks.tracksToAdd);
 
-  originalLength = result.tracksToAdd.length;
-  const eventLatencies = addLatencyTracks(engine);
-  const eventLatencyResult = await eventLatencies;
-  result.tracksToAdd.length += eventLatencyResult.tracksToAdd.length;
-  for (let i = 0; i < eventLatencyResult.tracksToAdd.length; ++i) {
-    result.tracksToAdd[i + originalLength] = eventLatencyResult.tracksToAdd[i];
-  }
+  const eventLatencies = await addLatencyTracks();
+  result.tracksToAdd = result.tracksToAdd.concat(eventLatencies.tracksToAdd);
 
-  return result;
+  const summaryTrackKey = uuidv4();
+  result.tracksToAdd.push({
+    uri: NULL_TRACK_URI,
+    trackSortKey: PrimaryTrackSortKey.ASYNC_SLICE_TRACK,
+    name: '',  // TODO(stevegolton): We should probably put some name here.
+    trackGroup: undefined,
+    key: summaryTrackKey,
+  });
+
+  const addTrackGroup = Actions.addTrackGroup({
+    name: 'Chrome Scroll Jank',
+    id: SCROLL_JANK_GROUP_ID,
+    collapsed: false,
+    summaryTrackKey,
+    fixedOrdering: true,
+  });
+
+  return {tracks: result, addTrackGroup};
 }
 
 class ChromeScrollJankPlugin implements Plugin {
-  onActivate(ctx: PluginContext): void {
-    ctx.LEGACY_registerTrack(ChromeTasksScrollJankTrack);
-    ctx.LEGACY_registerTrack(EventLatencyTrack);
-    ctx.LEGACY_registerTrack(ScrollJankV3Track);
-    ctx.LEGACY_registerTrack(TopLevelScrollTrack);
+  onActivate(_ctx: PluginContext): void {}
+
+  async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
+    await this.addChromeScrollJankTrack(ctx);
+    await this.addTopLevelScrollTrack(ctx);
+    await this.addEventLatencyTrack(ctx);
+    await this.addScrollJankV3ScrollTrack(ctx);
+  }
+
+  private async addChromeScrollJankTrack(ctx: PluginContextTrace):
+      Promise<void> {
+    ctx.registerStaticTrack({
+      uri: 'perfetto.ChromeScrollJank',
+      displayName: 'Scroll Jank causes - long tasks',
+      kind: ChromeTasksScrollJankTrack.kind,
+      track: ({trackKey}) => {
+        return new ChromeTasksScrollJankTrack({
+          engine: ctx.engine,
+          trackKey,
+        });
+      },
+    });
+  }
+
+  private async addTopLevelScrollTrack(ctx: PluginContextTrace): Promise<void> {
+    await ctx.engine.query(`
+      INCLUDE PERFETTO MODULE chrome.chrome_scrolls;
+      INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_offsets;
+    `);
+
+    ctx.registerStaticTrack({
+      uri: 'perfetto.ChromeScrollJank#toplevelScrolls',
+      displayName: 'Chrome Scrolls',
+      kind: CHROME_TOPLEVEL_SCROLLS_KIND,
+      track: ({trackKey}) => {
+        return new TopLevelScrollTrack({
+          engine: ctx.engine,
+          trackKey,
+        });
+      },
+    });
+  }
+
+  private async addEventLatencyTrack(ctx: PluginContextTrace): Promise<void> {
+    const subTableSql = generateSqlWithInternalLayout({
+      columns: ['id', 'ts', 'dur', 'track_id', 'name'],
+      sourceTable: 'slice',
+      ts: 'ts',
+      dur: 'dur',
+      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')`,
+    });
+
+    // Table name must be unique - it cannot include '-' characters or begin
+    // with a numeric value.
+    const baseTable =
+        `table_${uuidv4().split('-').join('_')}_janky_event_latencies_v3`;
+    const tableDefSql = `CREATE TABLE ${baseTable} AS
+        WITH event_latencies AS (
+          ${subTableSql}
+        ), latency_stages AS (
+        SELECT
+          d.id,
+          d.ts,
+          d.dur,
+          d.track_id,
+          d.name,
+          d.depth,
+          min(a.id) AS parent_id
+        FROM slice s
+          JOIN descendant_slice(s.id) d
+          JOIN ancestor_slice(d.id) a
+        WHERE s.id IN (SELECT id FROM event_latencies)
+        GROUP BY d.id, d.ts, d.dur, d.track_id, d.name, d.parent_id, d.depth)
+      SELECT
+        id,
+        ts,
+        dur,
+        CASE
+          WHEN id IN (
+            SELECT id FROM chrome_janky_event_latencies_v3)
+          THEN '${JANKY_LATENCY_NAME}'
+          ELSE name
+        END
+        AS name,
+        depth * 3 AS depth
+      FROM event_latencies
+      UNION ALL
+      SELECT
+        ls.id,
+        ls.ts,
+        ls.dur,
+        ls.name,
+        depth + (
+          (SELECT depth FROM event_latencies
+          WHERE id = ls.parent_id LIMIT 1) * 3) AS depth
+      FROM latency_stages ls;`;
+
+    await ctx.engine.query(
+        `INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_jank_intervals`);
+    await ctx.engine.query(tableDefSql);
+
+    ctx.registerStaticTrack({
+      uri: 'perfetto.ChromeScrollJank#eventLatency',
+      displayName: 'Chrome Scroll Input Latencies',
+      kind: EventLatencyTrack.kind,
+      track: ({trackKey}) => {
+        const track = new EventLatencyTrack({
+          engine: ctx.engine,
+          trackKey,
+        });
+
+        track.config = {
+          baseTable,
+        };
+
+        return track;
+      },
+    });
+  }
+
+  private async addScrollJankV3ScrollTrack(ctx: PluginContextTrace):
+      Promise<void> {
+    await ctx.engine.query(
+        `INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_jank_intervals`);
+
+    ctx.registerStaticTrack({
+      uri: 'perfetto.ChromeScrollJank#scrollJankV3',
+      displayName: 'Chrome Scroll Janks',
+      kind: ScrollJankV3Track.kind,
+      track: ({trackKey}) => {
+        return new ScrollJankV3Track({
+          engine: ctx.engine,
+          trackKey,
+        });
+      },
+    });
   }
 }
 
diff --git a/ui/src/tracks/chrome_scroll_jank/scroll_jank_slice.ts b/ui/src/tracks/chrome_scroll_jank/scroll_jank_slice.ts
index 00d8d8c..f07c41e 100644
--- a/ui/src/tracks/chrome_scroll_jank/scroll_jank_slice.ts
+++ b/ui/src/tracks/chrome_scroll_jank/scroll_jank_slice.ts
@@ -171,11 +171,11 @@
               sqlTableName: track.sqlTableName,
               start: vnode.attrs.ts,
               duration: vnode.attrs.dur,
-              trackId: track.id,
+              trackKey: track.key,
               detailsPanelConfig: track.detailsPanelConfig,
             }));
 
-            scrollToTrackAndTs(track.id, vnode.attrs.ts, true);
+            scrollToTrackAndTs(track.key, vnode.attrs.ts, true);
           },
         },
         vnode.attrs.name,
diff --git a/ui/src/tracks/chrome_scroll_jank/scroll_jank_v3_track.ts b/ui/src/tracks/chrome_scroll_jank/scroll_jank_v3_track.ts
index f1db56e..70f6f0a 100644
--- a/ui/src/tracks/chrome_scroll_jank/scroll_jank_v3_track.ts
+++ b/ui/src/tracks/chrome_scroll_jank/scroll_jank_v3_track.ts
@@ -12,15 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {v4 as uuidv4} from 'uuid';
-
 import {
   getColorForSlice,
 } from '../../common/colorizer';
-import {Engine} from '../../common/engine';
-import {
-  SCROLLING_TRACK_GROUP,
-} from '../../common/state';
 import {globals} from '../../frontend/globals';
 import {NamedSliceTrackTypes} from '../../frontend/named_slice_track';
 import {NewTrackArgs, TrackBase} from '../../frontend/track';
@@ -33,14 +27,13 @@
 
 import {EventLatencyTrackTypes} from './event_latency_track';
 import {
+  SCROLL_JANK_GROUP_ID,
   ScrollJankPluginState,
   ScrollJankTracks as DecideTracksResult,
 } from './index';
 import {DEEP_RED_COLOR, RED_COLOR} from './jank_colors';
 import {ScrollJankV3DetailsPanel} from './scroll_jank_v3_details_panel';
 
-export {Data} from '../chrome_slices';
-
 const UNKNOWN_SLICE_NAME = 'Unknown';
 const JANK_SLICE_NAME = ' Jank';
 
@@ -56,7 +49,7 @@
     super(args);
     ScrollJankPluginState.getInstance().registerTrack({
       kind: ScrollJankV3Track.kind,
-      trackId: this.trackId,
+      trackKey: this.trackKey,
       tableName: this.tableName,
       detailsPanelConfig: this.getDetailsPanel(),
     });
@@ -127,23 +120,17 @@
   }
 }
 
-export async function addScrollJankV3ScrollTrack(engine: Engine):
+export async function addScrollJankV3ScrollTrack():
     Promise<DecideTracksResult> {
   const result: DecideTracksResult = {
     tracksToAdd: [],
   };
 
-  await engine.query(
-      `INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_jank_intervals`);
-
   result.tracksToAdd.push({
-    id: uuidv4(),
-    engineId: engine.id,
-    kind: ScrollJankV3Track.kind,
+    uri: 'perfetto.ChromeScrollJank#scrollJankV3',
     trackSortKey: PrimaryTrackSortKey.ASYNC_SLICE_TRACK,
     name: 'Chrome Scroll Janks',
-    config: {},
-    trackGroup: SCROLLING_TRACK_GROUP,
+    trackGroup: SCROLL_JANK_GROUP_ID,
   });
 
   return result;
diff --git a/ui/src/tracks/chrome_scroll_jank/scroll_track.ts b/ui/src/tracks/chrome_scroll_jank/scroll_track.ts
index 97ccc4a..7eaba65 100644
--- a/ui/src/tracks/chrome_scroll_jank/scroll_track.ts
+++ b/ui/src/tracks/chrome_scroll_jank/scroll_track.ts
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {v4 as uuidv4} from 'uuid';
-
-import {Engine} from '../../common/engine';
-import {SCROLLING_TRACK_GROUP} from '../../common/state';
 import {NamedSliceTrackTypes} from '../../frontend/named_slice_track';
 import {NewTrackArgs, TrackBase} from '../../frontend/track';
 import {PrimaryTrackSortKey} from '../../public';
@@ -24,19 +20,19 @@
   CustomSqlTableDefConfig,
   CustomSqlTableSliceTrack,
 } from '../custom_sql_table_slices';
-
 import {
+  SCROLL_JANK_GROUP_ID,
   ScrollJankPluginState,
   ScrollJankTracks as DecideTracksResult,
 } from './index';
 import {ScrollDetailsPanel} from './scroll_details_panel';
 
-export {Data} from '../chrome_slices';
+export const CHROME_TOPLEVEL_SCROLLS_KIND =
+    'org.chromium.TopLevelScrolls.scrolls';
 
 export class TopLevelScrollTrack extends
     CustomSqlTableSliceTrack<NamedSliceTrackTypes> {
-  static readonly kind = 'org.chromium.TopLevelScrolls.scrolls';
-
+  public static kind = CHROME_TOPLEVEL_SCROLLS_KIND;
   static create(args: NewTrackArgs): TrackBase {
     return new TopLevelScrollTrack(args);
   }
@@ -63,7 +59,7 @@
 
     ScrollJankPluginState.getInstance().registerTrack({
       kind: TopLevelScrollTrack.kind,
-      trackId: this.trackId,
+      trackKey: this.trackKey,
       tableName: this.tableName,
       detailsPanelConfig: this.getDetailsPanel(),
     });
@@ -76,25 +72,16 @@
   }
 }
 
-export async function addTopLevelScrollTrack(engine: Engine):
-    Promise<DecideTracksResult> {
+export async function addTopLevelScrollTrack(): Promise<DecideTracksResult> {
   const result: DecideTracksResult = {
     tracksToAdd: [],
   };
 
-  await engine.query(`
-    INCLUDE PERFETTO MODULE chrome.chrome_scrolls;
-    INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_offsets;
-  `);
-
   result.tracksToAdd.push({
-    id: uuidv4(),
-    engineId: engine.id,
-    kind: TopLevelScrollTrack.kind,
+    uri: 'perfetto.ChromeScrollJank#toplevelScrolls',
     trackSortKey: PrimaryTrackSortKey.ASYNC_SLICE_TRACK,
     name: 'Chrome Scrolls',
-    config: {},
-    trackGroup: SCROLLING_TRACK_GROUP,
+    trackGroup: SCROLL_JANK_GROUP_ID,
   });
 
   return result;
diff --git a/ui/src/tracks/generic_slice_track/index.ts b/ui/src/tracks/chrome_slices/generic_slice_track.ts
similarity index 69%
rename from ui/src/tracks/generic_slice_track/index.ts
rename to ui/src/tracks/chrome_slices/generic_slice_track.ts
index 6053621..d19f3db 100644
--- a/ui/src/tracks/generic_slice_track/index.ts
+++ b/ui/src/tracks/chrome_slices/generic_slice_track.ts
@@ -17,7 +17,6 @@
   NamedSliceTrackTypes,
 } from '../../frontend/named_slice_track';
 import {NewTrackArgs} from '../../frontend/track';
-import {Plugin, PluginContext, PluginDescriptor} from '../../public';
 
 export interface GenericSliceTrackConfig {
   sqlTrackId: number;
@@ -37,21 +36,8 @@
     super(args);
   }
 
-  async initSqlTable(tableName: string): Promise<void> {
-    const sql = `create view ${tableName} as
-    select ts, dur, id, depth, ifnull(name, '') as name
+  getSqlSource(): string {
+    return `select ts, dur, id, depth, ifnull(name, '') as name
     from slice where track_id = ${this.config.sqlTrackId}`;
-    await this.engine.query(sql);
   }
 }
-
-class GenericSliceTrackPlugin implements Plugin {
-  onActivate(ctx: PluginContext): void {
-    ctx.LEGACY_registerTrack(GenericSliceTrack);
-  }
-}
-
-export const plugin: PluginDescriptor = {
-  pluginId: 'perfetto.GenericSliceTrack',
-  plugin: GenericSliceTrackPlugin,
-};
diff --git a/ui/src/tracks/chrome_slices/index.ts b/ui/src/tracks/chrome_slices/index.ts
index 28efb24..12fe4d9 100644
--- a/ui/src/tracks/chrome_slices/index.ts
+++ b/ui/src/tracks/chrome_slices/index.ts
@@ -13,63 +13,50 @@
 // limitations under the License.
 
 import {BigintMath as BIMath} from '../../base/bigint_math';
-import {duration, Span, Time, time} from '../../base/time';
-import {Actions} from '../../common/actions';
-import {cropText, drawIncompleteSlice} from '../../common/canvas_utils';
+import {Duration, duration, time} from '../../base/time';
 import {
-  colorForThreadIdleSlice,
-  getColorForSlice,
-} from '../../common/colorizer';
-import {HighPrecisionTime} from '../../common/high_precision_time';
-import {LONG, LONG_NULL, NUM, STR} from '../../common/query_result';
-import {TrackData} from '../../common/track_data';
-import {TrackController} from '../../controller/track_controller';
-import {checkerboardExcept} from '../../frontend/checkerboard';
-import {globals} from '../../frontend/globals';
-import {cachedHsluvToHex} from '../../frontend/hsluv_cache';
-import {PxSpan, TimeScale} from '../../frontend/time_scale';
-import {NewTrackArgs, SliceRect, TrackBase} from '../../frontend/track';
-import {Plugin, PluginContext, PluginDescriptor} from '../../public';
+  LONG,
+  LONG_NULL,
+  NUM,
+  NUM_NULL,
+  STR,
+  STR_NULL,
+} from '../../common/query_result';
+import {
+  SliceData,
+  SliceTrackBase,
+} from '../../frontend/slice_track_base';
+import {
+  EngineProxy,
+  Plugin,
+  PluginContext,
+  PluginContextTrace,
+  PluginDescriptor,
+} from '../../public';
+import {getTrackName} from '../../public/utils';
+
+import {GenericSliceTrack} from './generic_slice_track';
 
 export const SLICE_TRACK_KIND = 'ChromeSliceTrack';
-const SLICE_HEIGHT = 18;
-const TRACK_PADDING = 2;
-const CHEVRON_WIDTH_PX = 10;
-const HALF_CHEVRON_WIDTH_PX = CHEVRON_WIDTH_PX / 2;
 
-export interface Config {
-  maxDepth: number;
-  namespace: string;
-  trackId: number;
-}
-
-export interface Data extends TrackData {
-  // Slices are stored in a columnar fashion.
-  strings: string[];
-  sliceIds: Float64Array;
-  starts: BigInt64Array;
-  ends: BigInt64Array;
-  depths: Uint16Array;
-  titles: Uint16Array;   // Index into strings.
-  colors?: Uint16Array;  // Index into strings.
-  isInstant: Uint16Array;
-  isIncomplete: Uint16Array;
-  cpuTimeRatio?: Float64Array;
-}
-
-export class ChromeSliceTrackController extends TrackController<Config, Data> {
-  static kind = SLICE_TRACK_KIND;
+export class ChromeSliceTrack extends SliceTrackBase {
   private maxDurNs: duration = 0n;
 
+  constructor(
+      protected engine: EngineProxy, maxDepth: number, trackKey: string,
+      private trackId: number, namespace?: string) {
+    super(maxDepth, trackKey, 'slice', namespace);
+  }
+
   async onBoundsChange(start: time, end: time, resolution: duration):
-      Promise<Data> {
+      Promise<SliceData> {
     const tableName = this.namespaceTable('slice');
 
-    if (this.maxDurNs === 0n) {
+    if (this.maxDurNs === Duration.ZERO) {
       const query = `
           SELECT max(iif(dur = -1, (SELECT end_ts FROM trace_bounds) - ts, dur))
-          AS maxDur FROM ${tableName} WHERE track_id = ${this.config.trackId}`;
-      const queryRes = await this.query(query);
+          AS maxDur FROM ${tableName} WHERE track_id = ${this.trackId}`;
+      const queryRes = await this.engine.query(query);
       this.maxDurNs = queryRes.firstRow({maxDur: LONG_NULL}).maxDur || 0n;
     }
 
@@ -85,14 +72,14 @@
         dur = -1 as isIncomplete,
         thread_dur as threadDur
       FROM ${tableName}
-      WHERE track_id = ${this.config.trackId} AND
+      WHERE track_id = ${this.trackId} AND
         ts >= (${start - this.maxDurNs}) AND
         ts <= ${end}
       GROUP BY depth, tsq`;
-    const queryRes = await this.query(query);
+    const queryRes = await this.engine.query(query);
 
     const numRows = queryRes.numRows();
-    const slices: Data = {
+    const slices: SliceData = {
       start,
       end,
       resolution,
@@ -159,288 +146,87 @@
   }
 }
 
-export class ChromeSliceTrack extends TrackBase<Config, Data> {
-  static readonly kind: string = SLICE_TRACK_KIND;
-  static create(args: NewTrackArgs): TrackBase {
-    return new ChromeSliceTrack(args);
-  }
-
-  private hoveredTitleId = -1;
-
-  constructor(args: NewTrackArgs) {
-    super(args);
-  }
-
-  // Font used to render the slice name on the current track.
-  protected getFont() {
-    return '12px Roboto Condensed';
-  }
-
-  renderCanvas(ctx: CanvasRenderingContext2D): void {
-    // TODO: fonts and colors should come from the CSS and not hardcoded here.
-    const data = this.data();
-    if (data === undefined) return;  // Can't possibly draw anything.
-
-    const {visibleTimeSpan, visibleWindowTime, visibleTimeScale, windowSpan} =
-        globals.frontendLocalState;
-
-    // If the cached trace slices don't fully cover the visible time range,
-    // show a gray rectangle with a "Loading..." label.
-    checkerboardExcept(
-        ctx,
-        this.getHeight(),
-        visibleTimeScale.hpTimeToPx(visibleWindowTime.start),
-        visibleTimeScale.hpTimeToPx(visibleWindowTime.end),
-        visibleTimeScale.timeToPx(data.start),
-        visibleTimeScale.timeToPx(data.end),
-    );
-
-    ctx.textAlign = 'center';
-
-    // measuretext is expensive so we only use it once.
-    const charWidth = ctx.measureText('ACBDLqsdfg').width / 10;
-
-    // The draw of the rect on the selected slice must happen after the other
-    // drawings, otherwise it would result under another rect.
-    let drawRectOnSelected = () => {};
-
-
-    for (let i = 0; i < data.starts.length; i++) {
-      const tStart = Time.fromRaw(data.starts[i]);
-      let tEnd = Time.fromRaw(data.ends[i]);
-      const depth = data.depths[i];
-      const titleId = data.titles[i];
-      const sliceId = data.sliceIds[i];
-      const isInstant = data.isInstant[i];
-      const isIncomplete = data.isIncomplete[i];
-      const title = data.strings[titleId];
-      const colorOverride = data.colors && data.strings[data.colors[i]];
-      if (isIncomplete) {  // incomplete slice
-        // TODO(stevegolton): This isn't exactly equivalent, ideally we should
-        // choose tEnd once we've conerted to screen space coords.
-        tEnd = visibleWindowTime.end.toTime('ceil');
-      }
-
-      if (!visibleTimeSpan.intersects(tStart, tEnd)) {
-        continue;
-      }
-
-      const rect = this.getSliceRect(
-          visibleTimeScale, visibleTimeSpan, windowSpan, tStart, tEnd, depth);
-      if (!rect || !rect.visible) {
-        continue;
-      }
-
-      const currentSelection = globals.state.currentSelection;
-      const isSelected = currentSelection &&
-          currentSelection.kind === 'CHROME_SLICE' &&
-          currentSelection.id !== undefined && currentSelection.id === sliceId;
-
-      const highlighted = titleId === this.hoveredTitleId ||
-          globals.state.highlightedSliceId === sliceId;
-
-      const hasFocus = highlighted || isSelected;
-      const colorObj = getColorForSlice(title, hasFocus);
-
-      let color: string;
-      if (colorOverride === undefined) {
-        color = colorObj.c;
-      } else {
-        color = colorOverride;
-      }
-      ctx.fillStyle = color;
-
-      // We draw instant events as upward facing chevrons starting at A:
-      //     A
-      //    ###
-      //   ##C##
-      //  ##   ##
-      // D       B
-      // Then B, C, D and back to A:
-      if (isInstant) {
-        if (isSelected) {
-          drawRectOnSelected = () => {
-            ctx.save();
-            ctx.translate(rect.left, rect.top);
-
-            // Draw a rectangle around the selected slice
-            ctx.strokeStyle = cachedHsluvToHex(colorObj.h, 100, 10);
-            ctx.beginPath();
-            ctx.lineWidth = 3;
-            ctx.strokeRect(
-                -HALF_CHEVRON_WIDTH_PX, 0, CHEVRON_WIDTH_PX, SLICE_HEIGHT);
-            ctx.closePath();
-
-            // Draw inner chevron as interior
-            ctx.fillStyle = color;
-            this.drawChevron(ctx);
-
-            ctx.restore();
-          };
-        } else {
-          ctx.save();
-          ctx.translate(rect.left, rect.top);
-          this.drawChevron(ctx);
-          ctx.restore();
-        }
-        continue;
-      }
-
-      if (isIncomplete && rect.width > SLICE_HEIGHT / 4) {
-        drawIncompleteSlice(ctx, rect.left, rect.top, rect.width, SLICE_HEIGHT);
-      } else if (
-          data.cpuTimeRatio !== undefined && data.cpuTimeRatio[i] < 1 - 1e-9) {
-        // We draw two rectangles, representing the ratio between wall time and
-        // time spent on cpu.
-        const cpuTimeRatio = data.cpuTimeRatio![i];
-        const firstPartWidth = rect.width * cpuTimeRatio;
-        const secondPartWidth = rect.width * (1 - cpuTimeRatio);
-        ctx.fillRect(rect.left, rect.top, firstPartWidth, SLICE_HEIGHT);
-        ctx.fillStyle = colorForThreadIdleSlice(
-            colorObj.h, colorObj.s, colorObj.l, hasFocus);
-        ctx.fillRect(
-            rect.left + firstPartWidth,
-            rect.top,
-            secondPartWidth,
-            SLICE_HEIGHT);
-      } else {
-        ctx.fillRect(rect.left, rect.top, rect.width, SLICE_HEIGHT);
-      }
-
-      // Selected case
-      if (isSelected) {
-        drawRectOnSelected = () => {
-          ctx.strokeStyle = cachedHsluvToHex(colorObj.h, 100, 10);
-          ctx.beginPath();
-          ctx.lineWidth = 3;
-          ctx.strokeRect(
-              rect.left, rect.top - 1.5, rect.width, SLICE_HEIGHT + 3);
-          ctx.closePath();
-        };
-      }
-
-      // Don't render text when we have less than 5px to play with.
-      if (rect.width >= 5) {
-        ctx.fillStyle = colorObj.l > 65 ? '#404040' : 'white';
-        const displayText = cropText(title, charWidth, rect.width);
-        const rectXCenter = rect.left + rect.width / 2;
-        ctx.textBaseline = 'middle';
-        ctx.font = this.getFont();
-        ctx.fillText(displayText, rectXCenter, rect.top + SLICE_HEIGHT / 2);
-      }
-    }
-    drawRectOnSelected();
-  }
-
-  drawChevron(ctx: CanvasRenderingContext2D) {
-    // Draw a chevron at a fixed location and size. Should be used with
-    // ctx.translate and ctx.scale to alter location and size.
-    ctx.beginPath();
-    ctx.moveTo(0, 0);
-    ctx.lineTo(HALF_CHEVRON_WIDTH_PX, SLICE_HEIGHT);
-    ctx.lineTo(0, SLICE_HEIGHT - HALF_CHEVRON_WIDTH_PX);
-    ctx.lineTo(-HALF_CHEVRON_WIDTH_PX, SLICE_HEIGHT);
-    ctx.lineTo(0, 0);
-    ctx.fill();
-  }
-
-  getSliceIndex({x, y}: {x: number, y: number}): number|void {
-    const data = this.data();
-    if (data === undefined) return;
-    const {
-      visibleTimeScale: timeScale,
-      visibleWindowTime: visibleHPTimeSpan,
-    } = globals.frontendLocalState;
-    if (y < TRACK_PADDING) return;
-    const instantWidthTime = timeScale.pxDeltaToDuration(HALF_CHEVRON_WIDTH_PX);
-    const t = timeScale.pxToHpTime(x);
-    const depth = Math.floor((y - TRACK_PADDING) / SLICE_HEIGHT);
-
-    for (let i = 0; i < data.starts.length; i++) {
-      if (depth !== data.depths[i]) {
-        continue;
-      }
-      const start = Time.fromRaw(data.starts[i]);
-      const tStart = HighPrecisionTime.fromTime(start);
-      if (data.isInstant[i]) {
-        if (tStart.sub(t).abs().lt(instantWidthTime)) {
-          return i;
-        }
-      } else {
-        const end = Time.fromRaw(data.ends[i]);
-        let tEnd = HighPrecisionTime.fromTime(end);
-        if (data.isIncomplete[i]) {
-          tEnd = visibleHPTimeSpan.end;
-        }
-        if (tStart.lte(t) && t.lte(tEnd)) {
-          return i;
-        }
-      }
-    }
-  }
-
-  onMouseMove({x, y}: {x: number, y: number}) {
-    this.hoveredTitleId = -1;
-    globals.dispatch(Actions.setHighlightedSliceId({sliceId: -1}));
-    const sliceIndex = this.getSliceIndex({x, y});
-    if (sliceIndex === undefined) return;
-    const data = this.data();
-    if (data === undefined) return;
-    this.hoveredTitleId = data.titles[sliceIndex];
-    const sliceId = data.sliceIds[sliceIndex];
-    globals.dispatch(Actions.setHighlightedSliceId({sliceId}));
-  }
-
-  onMouseOut() {
-    this.hoveredTitleId = -1;
-    globals.dispatch(Actions.setHighlightedSliceId({sliceId: -1}));
-  }
-
-  onMouseClick({x, y}: {x: number, y: number}): boolean {
-    const sliceIndex = this.getSliceIndex({x, y});
-    if (sliceIndex === undefined) return false;
-    const data = this.data();
-    if (data === undefined) return false;
-    const sliceId = data.sliceIds[sliceIndex];
-    if (sliceId !== undefined && sliceId !== -1) {
-      globals.makeSelection(Actions.selectChromeSlice({
-        id: sliceId,
-        trackId: this.trackState.id,
-        table: this.config.namespace,
-      }));
-      return true;
-    }
-    return false;
-  }
-
-  getHeight() {
-    return SLICE_HEIGHT * (this.config.maxDepth + 1) + 2 * TRACK_PADDING;
-  }
-
-  getSliceRect(
-      visibleTimeScale: TimeScale, visibleWindow: Span<time, duration>,
-      windowSpan: PxSpan, tStart: time, tEnd: time, depth: number): SliceRect
-      |undefined {
-    const pxEnd = windowSpan.end;
-    const left = Math.max(visibleTimeScale.timeToPx(tStart), 0);
-    const right = Math.min(visibleTimeScale.timeToPx(tEnd), pxEnd);
-
-    const visible = visibleWindow.intersects(tStart, tEnd);
-
-    return {
-      left,
-      width: Math.max(right - left, 1),
-      top: TRACK_PADDING + depth * SLICE_HEIGHT,
-      height: SLICE_HEIGHT,
-      visible,
-    };
-  }
-}
-
 class ChromeSlicesPlugin implements Plugin {
-  onActivate(ctx: PluginContext): void {
-    ctx.LEGACY_registerTrackController(ChromeSliceTrackController);
-    ctx.LEGACY_registerTrack(ChromeSliceTrack);
+  onActivate(_ctx: PluginContext): void {}
+
+  async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
+    const {engine} = ctx;
+    const result = await engine.query(`
+        select
+          thread_track.utid as utid,
+          thread_track.id as trackId,
+          thread_track.name as trackName,
+          EXTRACT_ARG(thread_track.source_arg_set_id,
+                      'is_root_in_scope') as isDefaultTrackForScope,
+          tid,
+          thread.name as threadName,
+          max(slice.depth) as maxDepth,
+          process.upid as upid
+        from slice
+        join thread_track on slice.track_id = thread_track.id
+        join thread using(utid)
+        left join process using(upid)
+        group by thread_track.id
+  `);
+
+    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,
+    });
+
+    for (; it.valid(); it.next()) {
+      const utid = it.utid;
+      const trackId = it.trackId;
+      const trackName = it.trackName;
+      const tid = it.tid;
+      const threadName = it.threadName;
+      const maxDepth = it.maxDepth;
+
+      const displayName = getTrackName({
+        name: trackName,
+        utid,
+        tid,
+        threadName,
+        kind: 'Slices',
+      });
+
+      ctx.registerStaticTrack({
+        uri: `perfetto.ChromeSlices#${trackId}`,
+        displayName,
+        trackIds: [trackId],
+        kind: SLICE_TRACK_KIND,
+        track: ({trackKey}) => {
+          return new ChromeSliceTrack(
+              engine,
+              maxDepth,
+              trackKey,
+              trackId,
+          );
+        },
+      });
+
+      // trackIds can only be registered by one track at a time.
+      // TODO(hjd): Move trackIds to only be on V2.
+      ctx.registerStaticTrack({
+        uri: `perfetto.ChromeSlices#${trackId}.v2`,
+        displayName,
+        kind: SLICE_TRACK_KIND,
+        track: ({trackKey}) => {
+          const track = GenericSliceTrack.create({
+            engine: ctx.engine,
+            trackKey,
+          });
+          track.config = {sqlTrackId: trackId};
+          return track;
+        },
+      });
+    }
   }
 }
 
diff --git a/ui/src/tracks/counter/index.ts b/ui/src/tracks/counter/index.ts
index 1fbc53f..21ef5e6 100644
--- a/ui/src/tracks/counter/index.ts
+++ b/ui/src/tracks/counter/index.ts
@@ -17,6 +17,7 @@
 
 import {searchSegment} from '../../base/binary_search';
 import {assertTrue} from '../../base/logging';
+import {isString} from '../../base/object_utils';
 import {duration, time, Time} from '../../base/time';
 import {Actions} from '../../common/actions';
 import {
@@ -115,7 +116,7 @@
 
 function isCounterState(x: unknown): x is CounterTrackState {
   if (x && typeof x === 'object' && 'scale' in x) {
-    if (typeof x.scale === 'string') {
+    if (isString(x.scale)) {
       return true;
     } else {
       return false;
@@ -132,14 +133,14 @@
   private minimumDeltaSeen = 0;
   private maxDurNs: duration = 0n;
   private store: Store<CounterTrackState>;
-  private id: string;
+  private trackKey: string;
   private uuid = uuidv4();
   private isSetup = false;
 
   constructor(
       ctx: TrackContext, private config: Config, private engine: EngineProxy) {
     super();
-    this.id = ctx.trackInstanceId;
+    this.trackKey = ctx.trackKey;
     this.store = ctx.mountStore<CounterTrackState>((init: unknown) => {
       if (isCounterState(init)) {
         return init;
@@ -318,7 +319,7 @@
     return MARGIN_TOP + RECT_HEIGHT;
   }
 
-  getContextMenu(): m.Vnode<any> {
+  getTrackShellButtons(): m.Children {
     const currentScale = this.store.state.scale;
     const scales: {name: CounterScaleOptions, humanName: string}[] = [
       {name: 'ZERO_BASED', humanName: 'Zero based'},
@@ -598,7 +599,7 @@
         leftTs: Time.fromRaw(data.timestamps[left]),
         rightTs: Time.fromRaw(right !== -1 ? data.timestamps[right] : -1n),
         id: counterId,
-        trackId: this.id,
+        trackKey: this.trackKey,
       }));
       return true;
     }
@@ -633,7 +634,7 @@
       const config:
           Config = {name, trackId, defaultScale: getCounterScale(name)};
       const uri = `perfetto.Counter#${trackId}`;
-      ctx.addTrack({
+      ctx.registerStaticTrack({
         uri,
         displayName: name,
         kind: COUNTER_TRACK_KIND,
@@ -642,9 +643,9 @@
           return new CounterTrack(trackCtx, config, ctx.engine);
         },
       });
-      ctx.suggestTrack({
+      ctx.addDefaultTrack({
         uri,
-        name,
+        displayName: name,
         sortKey: PrimaryTrackSortKey.COUNTER_TRACK,
       });
     }
@@ -712,7 +713,7 @@
           maximumValue,
           defaultScale: getCounterScale(name),
         };
-        ctx.addTrack({
+        ctx.registerStaticTrack({
           uri,
           displayName: name,
           kind: COUNTER_TRACK_KIND,
@@ -768,7 +769,7 @@
         trackId,
         defaultScale: getCounterScale(name),
       };
-      ctx.addTrack({
+      ctx.registerStaticTrack({
         uri: `perfetto.Counter#cpu${trackId}`,
         displayName: name,
         kind: COUNTER_TRACK_KIND,
@@ -831,7 +832,7 @@
         endTs: Time.fromRaw(endTs),
         defaultScale: getCounterScale(name),
       };
-      ctx.addTrack({
+      ctx.registerStaticTrack({
         uri: `perfetto.Counter#thread${trackId}`,
         displayName: name,
         kind,
@@ -888,7 +889,7 @@
         endTs: Time.fromRaw(endTs),
         defaultScale: getCounterScale(name),
       };
-      ctx.addTrack({
+      ctx.registerStaticTrack({
         uri: `perfetto.Counter#process${trackId}`,
         displayName: name,
         kind: COUNTER_TRACK_KIND,
diff --git a/ui/src/tracks/cpu_freq/index.ts b/ui/src/tracks/cpu_freq/index.ts
index 35faefc..923940a 100644
--- a/ui/src/tracks/cpu_freq/index.ts
+++ b/ui/src/tracks/cpu_freq/index.ts
@@ -533,15 +533,15 @@
         const freqTrackId = row.cpuFreqId;
         const idleTrackId = row.cpuIdleId === null ? undefined : row.cpuIdleId;
 
-        ctx.addTrack({
+        ctx.registerStaticTrack({
           uri: `perfetto.CpuFreq#${cpu}`,
           displayName: `Cpu ${cpu} Frequency`,
           kind: CPU_FREQ_TRACK_KIND,
           cpu,
-          track: ({trackInstanceId}) => {
+          track: ({trackKey}) => {
             return new TrackWithControllerAdapter<Config, Data>(
                 engine,
-                trackInstanceId,
+                trackKey,
                 {
                   cpu,
                   maximumValue: maxCpuFreq,
diff --git a/ui/src/tracks/cpu_profile/index.ts b/ui/src/tracks/cpu_profile/index.ts
index 616e9a2..d41a5ed 100644
--- a/ui/src/tracks/cpu_profile/index.ts
+++ b/ui/src/tracks/cpu_profile/index.ts
@@ -17,16 +17,23 @@
 import {duration, Time, time} from '../../base/time';
 import {Actions} from '../../common/actions';
 import {hslForSlice} from '../../common/colorizer';
-import {LONG, NUM} from '../../common/query_result';
-import {TrackData} from '../../common/track_data';
+import {LONG, NUM, NUM_NULL, STR_NULL} from '../../common/query_result';
 import {
-  TrackController,
-} from '../../controller/track_controller';
+  TrackAdapter,
+  TrackControllerAdapter,
+  TrackWithControllerAdapter,
+} from '../../common/track_adapter';
+import {TrackData} from '../../common/track_data';
 import {globals} from '../../frontend/globals';
 import {cachedHsluvToHex} from '../../frontend/hsluv_cache';
 import {TimeScale} from '../../frontend/time_scale';
-import {NewTrackArgs, TrackBase} from '../../frontend/track';
-import {Plugin, PluginContext, PluginDescriptor} from '../../public';
+import {NewTrackArgs} from '../../frontend/track';
+import {
+  Plugin,
+  PluginContext,
+  PluginContextTrace,
+  PluginDescriptor,
+} from '../../public';
 
 const BAR_HEIGHT = 3;
 const MARGIN_TOP = 4.5;
@@ -44,8 +51,7 @@
   utid: number;
 }
 
-class CpuProfileTrackController extends TrackController<Config, Data> {
-  static readonly kind = CPU_PROFILE_TRACK_KIND;
+class CpuProfileTrackController extends TrackControllerAdapter<Config, Data> {
   async onBoundsChange(start: time, end: time, resolution: duration):
       Promise<Data> {
     const query = `select
@@ -85,8 +91,7 @@
   return cachedHsluvToHex(hue, saturation, lightness);
 }
 
-class CpuProfileTrack extends TrackBase<Config, Data> {
-  static readonly kind = CPU_PROFILE_TRACK_KIND;
+class CpuProfileTrack extends TrackAdapter<Config, Data> {
   static create(args: NewTrackArgs): CpuProfileTrack {
     return new CpuProfileTrack(args);
   }
@@ -245,9 +250,48 @@
 }
 
 class CpuProfile implements Plugin {
-  onActivate(ctx: PluginContext): void {
-    ctx.LEGACY_registerTrackController(CpuProfileTrackController);
-    ctx.LEGACY_registerTrack(CpuProfileTrack);
+  onActivate(_ctx: PluginContext): void {}
+
+  async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
+    const result = await ctx.engine.query(`
+      select
+        utid,
+        tid,
+        upid,
+        thread.name as threadName
+      from
+        thread
+        join (select utid
+            from cpu_profile_stack_sample group by utid
+        ) using(utid)
+        left join process using(upid)
+      where utid != 0
+      group by 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 threadName = it.threadName;
+      ctx.registerStaticTrack({
+        uri: `perfetto.CpuProfile#${utid}`,
+        displayName: `${threadName} (CPU Stack Samples)`,
+        kind: CPU_PROFILE_TRACK_KIND,
+        utid,
+        track: ({trackKey}) => {
+          return new TrackWithControllerAdapter(
+              ctx.engine,
+              trackKey,
+              {utid},
+              CpuProfileTrack,
+              CpuProfileTrackController);
+        },
+      });
+    }
   }
 }
 
diff --git a/ui/src/tracks/cpu_slices/index.ts b/ui/src/tracks/cpu_slices/index.ts
index c2de3f6..88a020f 100644
--- a/ui/src/tracks/cpu_slices/index.ts
+++ b/ui/src/tracks/cpu_slices/index.ts
@@ -454,7 +454,6 @@
   }
 
   onMouseClick({x}: {x: number}) {
-    console.log(this.mousePos);
     const data = this.data();
     if (data === undefined) return false;
     const {visibleTimeScale} = globals.frontendLocalState;
@@ -462,7 +461,7 @@
     const index = search(data.starts, time.toTime());
     const id = index === -1 ? undefined : data.ids[index];
     if (!id || this.utidHoveredInThisTrack === -1) return false;
-    globals.makeSelection(Actions.selectSlice({id, trackId: this.id}));
+    globals.makeSelection(Actions.selectSlice({id, trackKey: this.trackKey}));
     return true;
   }
 }
@@ -481,15 +480,15 @@
       const uri = `perfetto.CpuSlices#cpu${cpu}`;
       const name = size === undefined ? `Cpu ${cpu}` : `Cpu ${cpu} (${size})`;
       const config: Config = {cpu};
-      ctx.addTrack({
+      ctx.registerStaticTrack({
         uri,
         displayName: name,
         kind: CPU_SLICE_TRACK_KIND,
         cpu,
-        track: ({trackInstanceId}) => {
+        track: ({trackKey}) => {
           return new TrackWithControllerAdapter<Config, Data>(
               ctx.engine,
-              trackInstanceId,
+              trackKey,
               config,
               CpuSliceTrack,
               CpuSliceTrackController);
diff --git a/ui/src/tracks/custom_sql_table_slices/index.ts b/ui/src/tracks/custom_sql_table_slices/index.ts
index 8ffe388..8389a8e 100644
--- a/ui/src/tracks/custom_sql_table_slices/index.ts
+++ b/ui/src/tracks/custom_sql_table_slices/index.ts
@@ -12,6 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {v4 as uuidv4} from 'uuid';
+
+import {Disposable, DisposableCallback} from '../../base/disposable';
 import {Actions} from '../../common/actions';
 import {
   generateSqlWithInternalLayout,
@@ -29,6 +32,10 @@
 import {NewTrackArgs} from '../../frontend/track';
 import {Plugin, PluginContext, PluginDescriptor} from '../../public';
 
+export interface CustomSqlImportConfig {
+  modules: string[];
+}
+
 export interface CustomSqlTableDefConfig {
   // Table name
   sqlTableName: string;
@@ -46,8 +53,12 @@
 
 export abstract class CustomSqlTableSliceTrack<
     T extends NamedSliceTrackTypes> extends NamedSliceTrack<T> {
+  protected readonly tableName;
+
   constructor(args: NewTrackArgs) {
     super(args);
+    this.tableName =
+        `customsqltableslicetrack_${uuidv4().split('-').join('_')}`;
   }
 
   abstract getSqlDataSource(): CustomSqlTableDefConfig;
@@ -55,29 +66,43 @@
   // Override by subclasses.
   abstract getDetailsPanel(): CustomSqlDetailsPanelConfig;
 
-  async initSqlTable(tableName: string) {
+  getSqlImports(): CustomSqlImportConfig {
+    return {
+      modules: [] as string[],
+    };
+  }
+
+  async onInit(): Promise<Disposable> {
+    await this.loadImports();
     const config = this.getSqlDataSource();
     let columns = ['*'];
     if (config.columns !== undefined) {
       columns = config.columns;
     }
 
-    const sql = `CREATE VIEW ${tableName} AS ` + generateSqlWithInternalLayout({
-                  columns: columns,
-                  sourceTable: config.sqlTableName,
-                  ts: 'ts',
-                  dur: 'dur',
-                  whereClause: config.whereClause,
-                });
-
+    const sql =
+        `CREATE VIEW ${this.tableName} AS ` + generateSqlWithInternalLayout({
+          columns: columns,
+          sourceTable: config.sqlTableName,
+          ts: 'ts',
+          dur: 'dur',
+          whereClause: config.whereClause,
+        });
     await this.engine.query(sql);
+    return DisposableCallback.from(() => {
+      this.engine.query(`DROP VIEW ${this.tableName}`);
+    });
+  }
+
+  getSqlSource(): string {
+    return `SELECT * FROM ${this.tableName}`;
   }
 
   isSelectionHandled(selection: Selection) {
     if (selection.kind !== 'GENERIC_SLICE') {
       return false;
     }
-    return selection.trackId === this.trackId;
+    return selection.trackKey === this.trackKey;
   }
 
   onSliceClick(args: OnSliceClickArgs<NamedSliceTrackTypes['slice']>) {
@@ -91,13 +116,19 @@
       sqlTableName: this.tableName,
       start: args.slice.ts,
       duration: args.slice.dur,
-      trackId: this.trackId,
+      trackKey: this.trackKey,
       detailsPanelConfig: {
         kind: detailsPanelConfig.kind,
         config: detailsPanelConfig.config,
       },
     }));
   }
+
+  async loadImports() {
+    for (const importModule of this.getSqlImports().modules) {
+      await this.engine.query(`INCLUDE PERFETTO MODULE ${importModule};`);
+    }
+  }
 }
 
 class CustomSqlTrackPlugin implements Plugin {
diff --git a/ui/src/tracks/debug/add_debug_track_menu.ts b/ui/src/tracks/debug/add_debug_track_menu.ts
index e7712a0..ccdc71d 100644
--- a/ui/src/tracks/debug/add_debug_track_menu.ts
+++ b/ui/src/tracks/debug/add_debug_track_menu.ts
@@ -16,11 +16,13 @@
 
 import {findRef} from '../../base/dom_utils';
 import {EngineProxy} from '../../common/engine';
+import {raf} from '../../core/raf_scheduler';
 import {Form, FormLabel} from '../../widgets/form';
 import {Select} from '../../widgets/select';
 import {TextInput} from '../../widgets/text_input';
 
-import {addDebugTrack, SliceColumns, SqlDataSource} from './slice_track';
+import {addDebugCounterTrack} from './counter_track';
+import {addDebugSliceTrack, SqlDataSource} from './slice_track';
 
 export const ARG_PREFIX = 'arg_';
 
@@ -40,11 +42,13 @@
   readonly columns: string[];
 
   name: string = '';
-  sliceColumns: SliceColumns;
-  arrangeBy?: {
-    type: 'thread'|'process',
-    column: string,
-  };
+  trackType: 'slice'|'counter' = 'slice';
+  // Names of columns which will be used as data sources for rendering.
+  // We store the config for all possible columns used for rendering (i.e.
+  // 'value' for slice and 'name' for counter) and then just don't the values
+  // which don't match the currently selected track type (so changing track type
+  // from A to B and back to A is a no-op).
+  renderParams: {ts: string; dur: string; name: string; value: string;};
 
   constructor(vnode: m.Vnode<AddDebugTrackMenuAttrs>) {
     this.columns = [...vnode.attrs.dataSource.columns];
@@ -64,10 +68,11 @@
       return this.columns[0];
     };
 
-    this.sliceColumns = {
+    this.renderParams = {
       ts: chooseDefaultOption('ts'),
       dur: chooseDefaultOption('dur'),
       name: chooseDefaultOption('name'),
+      value: chooseDefaultOption('value'),
     };
   }
 
@@ -84,21 +89,46 @@
     }
   }
 
+  private renderTrackTypeSelect() {
+    const options = [];
+    for (const type of ['slice', 'counter']) {
+      options.push(
+          m('option',
+            {
+              value: type,
+              selected: this.trackType === type ? true : undefined,
+            },
+            type));
+    }
+    return m(
+        Select,
+        {
+          id: 'track_type',
+          oninput: (e: Event) => {
+            if (!e.target) return;
+            this.trackType =
+                (e.target as HTMLSelectElement).value as 'slice' | 'counter';
+            raf.scheduleFullRedraw();
+          },
+        },
+        options);
+  }
+
   view(vnode: m.Vnode<AddDebugTrackMenuAttrs>) {
-    const renderSelect = (name: 'ts'|'dur'|'name') => {
+    const renderSelect = (name: 'ts'|'dur'|'name'|'value') => {
       const options = [];
       for (const column of this.columns) {
         options.push(
             m('option',
               {
-                selected: this.sliceColumns[name] === column ? true : undefined,
+                selected: this.renderParams[name] === column ? true : undefined,
               },
               column));
       }
       if (name === 'dur') {
         options.push(
             m('option',
-              {selected: this.sliceColumns[name] === '0' ? true : undefined},
+              {selected: this.renderParams[name] === '0' ? true : undefined},
               m('i', '0')));
       }
       return [
@@ -111,7 +141,7 @@
             id: name,
             oninput: (e: Event) => {
               if (!e.target) return;
-              this.sliceColumns[name] = (e.target as HTMLSelectElement).value;
+              this.renderParams[name] = (e.target as HTMLSelectElement).value;
             },
           },
           options),
@@ -121,12 +151,27 @@
         Form,
         {
           onSubmit: () => {
-            addDebugTrack(
-                vnode.attrs.engine,
-                vnode.attrs.dataSource,
-                this.name,
-                this.sliceColumns,
-                this.columns);
+            switch (this.trackType) {
+              case 'slice':
+                addDebugSliceTrack(
+                    vnode.attrs.engine,
+                    vnode.attrs.dataSource,
+                    this.name,
+                    {
+                      ts: this.renderParams.ts,
+                      dur: this.renderParams.dur,
+                      name: this.renderParams.name,
+                    },
+                    this.columns);
+                break;
+              case 'counter':
+                addDebugCounterTrack(
+                    vnode.attrs.engine, vnode.attrs.dataSource, this.name, {
+                      ts: this.renderParams.ts,
+                      value: this.renderParams.value,
+                    });
+                break;
+            }
           },
           submitLabel: 'Show',
         },
@@ -146,9 +191,15 @@
             this.name = (e.target as HTMLInputElement).value;
           },
         }),
+        m(FormLabel,
+          {for: 'track_type',
+          },
+          'Track type'),
+        this.renderTrackTypeSelect(),
         renderSelect('ts'),
-        renderSelect('dur'),
-        renderSelect('name'),
+        this.trackType === 'slice' && renderSelect('dur'),
+        this.trackType === 'slice' && renderSelect('name'),
+        this.trackType === 'counter' && renderSelect('value'),
     );
   }
 }
diff --git a/ui/src/tracks/debug/counter_track.ts b/ui/src/tracks/debug/counter_track.ts
new file mode 100644
index 0000000..fd6aa30
--- /dev/null
+++ b/ui/src/tracks/debug/counter_track.ts
@@ -0,0 +1,124 @@
+// 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 {v4 as uuidv4} from 'uuid';
+
+import {Actions} from '../../common/actions';
+import {EngineProxy} from '../../common/engine';
+import {SCROLLING_TRACK_GROUP} from '../../common/state';
+import {BaseCounterTrack} from '../../frontend/base_counter_track';
+import {globals} from '../../frontend/globals';
+import {TrackButton} from '../../frontend/track_panel';
+import {PrimaryTrackSortKey, TrackContext} from '../../public';
+
+import {DEBUG_COUNTER_TRACK_URI} from '.';
+
+// 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 {
+  sqlTableName: string;
+  columns: CounterColumns;
+}
+
+export class DebugCounterTrack extends
+    BaseCounterTrack<CounterDebugTrackConfig> {
+  constructor(engine: EngineProxy, trackKey: string) {
+    super({
+      engine,
+      trackKey,
+    });
+  }
+
+  onCreate(ctx: TrackContext): void {
+    // TODO(stevegolton): Validate params before type asserting.
+    // TODO(stevegolton): Avoid just pushing this config up for some base
+    // class to use. Be more explicit.
+    this.config = ctx.params as CounterDebugTrackConfig;
+  }
+
+  getTrackShellButtons(): m.Children {
+    return [
+      this.getCounterContextMenu(),
+      m(TrackButton, {
+        action: () => {
+          globals.dispatch(Actions.removeTracks({trackKeys: [this.trackKey]}));
+        },
+        i: 'close',
+        tooltip: 'Close',
+        showButton: true,
+      }),
+    ];
+  }
+
+  async initSqlTable(tableName: string): Promise<void> {
+    await this.engine.query(`
+      create view ${tableName} as
+      select * from ${this.config.sqlTableName};
+    `);
+  }
+}
+
+let debugTrackCount = 0;
+
+export interface SqlDataSource {
+  // SQL source selecting the necessary data.
+  sqlSource: string;
+  // The caller is responsible for ensuring that the number of items in this
+  // list matches the number of columns returned by sqlSource.
+  columns: string[];
+}
+
+export async function addDebugCounterTrack(
+    engine: EngineProxy,
+    data: SqlDataSource,
+    trackName: string,
+    columns: CounterColumns) {
+  // To prepare displaying the provided data as a track, materialize it and
+  // compute depths.
+  const debugTrackId = ++debugTrackCount;
+  const sqlTableName = `__debug_counter_${debugTrackId}`;
+
+  // TODO(altimin): Support removing this table when the track is closed.
+  await engine.query(`
+      create table ${sqlTableName} as
+      with data as (
+        ${data.sqlSource}
+      )
+      select
+        ${columns.ts} as ts,
+        ${columns.value} as value
+      from data
+      order by ts;`);
+
+  const trackKey = uuidv4();
+  globals.dispatchMultiple([
+    Actions.addTrack({
+      key: trackKey,
+      uri: DEBUG_COUNTER_TRACK_URI,
+      name: trackName.trim() || `Debug Track ${debugTrackId}`,
+      trackSortKey: PrimaryTrackSortKey.DEBUG_TRACK,
+      trackGroup: SCROLLING_TRACK_GROUP,
+      params: {
+        sqlTableName,
+        columns,
+      },
+    }),
+    Actions.toggleTrackPinned({trackKey}),
+  ]);
+}
diff --git a/ui/src/tracks/debug/details_tab.ts b/ui/src/tracks/debug/details_tab.ts
index 8b89ec9..ee27bee 100644
--- a/ui/src/tracks/debug/details_tab.ts
+++ b/ui/src/tracks/debug/details_tab.ts
@@ -140,15 +140,14 @@
 
   private async maybeLoadSlice(
       id: number|undefined, ts: time, dur: duration, table: string|undefined,
-      sqlTrackId?: number): Promise<SliceDetails|undefined> {
+      trackId?: number): Promise<SliceDetails|undefined> {
     if (id === undefined) return undefined;
-    if ((table !== 'slice') && sqlTrackId === undefined) return undefined;
+    if ((table !== 'slice') && trackId === undefined) return undefined;
 
     const slice = await getSlice(this.engine, asSliceSqlId(id));
     if (slice === undefined) return undefined;
     if ((table === 'slice') ||
-        (slice.ts === ts && slice.dur === dur &&
-         slice.sqlTrackId === sqlTrackId)) {
+        (slice.ts === ts && slice.dur === dur && slice.trackId === trackId)) {
       return slice;
     } else {
       return undefined;
diff --git a/ui/src/tracks/debug/index.ts b/ui/src/tracks/debug/index.ts
index 78c12e7..985f068 100644
--- a/ui/src/tracks/debug/index.ts
+++ b/ui/src/tracks/debug/index.ts
@@ -12,13 +12,31 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {Plugin, PluginContext, PluginDescriptor} from '../../public';
+import {
+  Plugin,
+  PluginContext,
+  PluginContextTrace,
+  PluginDescriptor,
+} from '../../public';
 
+import {DebugCounterTrack} from './counter_track';
 import {DebugTrackV2} from './slice_track';
 
+export const DEBUG_SLICE_TRACK_URI = 'perfetto.DebugSlices';
+export const DEBUG_COUNTER_TRACK_URI = 'perfetto.DebugCounter';
+
 class DebugTrackPlugin implements Plugin {
-  onActivate(ctx: PluginContext): void {
-    ctx.LEGACY_registerTrack(DebugTrackV2);
+  onActivate(_ctx: PluginContext): void {}
+
+  async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
+    ctx.registerTrack({
+      uri: DEBUG_SLICE_TRACK_URI,
+      track: ({trackKey}) => new DebugTrackV2(ctx.engine, trackKey),
+    });
+    ctx.registerTrack({
+      uri: DEBUG_COUNTER_TRACK_URI,
+      track: ({trackKey}) => new DebugCounterTrack(ctx.engine, trackKey),
+    });
   }
 }
 
diff --git a/ui/src/tracks/debug/slice_track.ts b/ui/src/tracks/debug/slice_track.ts
index 739980a..bce33f2 100644
--- a/ui/src/tracks/debug/slice_track.ts
+++ b/ui/src/tracks/debug/slice_track.ts
@@ -13,21 +13,25 @@
 // limitations under the License.
 
 import m from 'mithril';
+import {v4 as uuidv4} from 'uuid';
 
-import {Actions, DEBUG_SLICE_TRACK_KIND} from '../../common/actions';
+import {Disposable} from '../../base/disposable';
+import {Actions} from '../../common/actions';
 import {EngineProxy} from '../../common/engine';
+import {SCROLLING_TRACK_GROUP} from '../../common/state';
 import {globals} from '../../frontend/globals';
 import {
   NamedSliceTrackTypes,
 } from '../../frontend/named_slice_track';
-import {NewTrackArgs} from '../../frontend/track';
-import {TrackButton, TrackButtonAttrs} from '../../frontend/track_panel';
+import {TrackButton} from '../../frontend/track_panel';
+import {PrimaryTrackSortKey, TrackContext} from '../../public';
 import {
   CustomSqlDetailsPanelConfig,
   CustomSqlTableDefConfig,
   CustomSqlTableSliceTrack,
 } from '../custom_sql_table_slices';
 
+import {DEBUG_SLICE_TRACK_URI} from '.';
 import {ARG_PREFIX} from './add_debug_track_menu';
 import {DebugSliceDetailsTab} from './details_tab';
 
@@ -48,14 +52,18 @@
 }
 
 export class DebugTrackV2 extends CustomSqlTableSliceTrack<DebugTrackV2Types> {
-  static readonly kind = DEBUG_SLICE_TRACK_KIND;
-
-  static create(args: NewTrackArgs) {
-    return new DebugTrackV2(args);
+  constructor(engine: EngineProxy, trackKey: string) {
+    super({
+      engine,
+      trackKey,
+    });
   }
 
-  constructor(args: NewTrackArgs) {
-    super(args);
+  onCreate(ctx: TrackContext): void {
+    // TODO(stevegolton): Validate params before type asserting.
+    // TODO(stevegolton): Avoid just pushing this config up for some base
+    // class to use. Be more explicit.
+    this.config = ctx.params as DebugTrackV2Config;
   }
 
   getSqlDataSource(): CustomSqlTableDefConfig {
@@ -74,19 +82,19 @@
     };
   }
 
-  async initSqlTable(tableName: string): Promise<void> {
-    super.initSqlTable(tableName);
+  async onInit(): Promise<Disposable> {
+    return super.onInit();
   }
 
-  getTrackShellButtons(): Array<m.Vnode<TrackButtonAttrs>> {
-    return [m(TrackButton, {
+  getTrackShellButtons(): m.Children {
+    return m(TrackButton, {
       action: () => {
-        globals.dispatch(Actions.removeDebugTrack({trackId: this.trackId}));
+        globals.dispatch(Actions.removeTracks({trackKeys: [this.trackKey]}));
       },
       i: 'close',
       tooltip: 'Close',
       showButton: true,
-    })];
+    });
   }
 }
 
@@ -100,7 +108,7 @@
   columns: string[];
 }
 
-export async function addDebugTrack(
+export async function addDebugSliceTrack(
     engine: EngineProxy,
     data: SqlDataSource,
     trackName: string,
@@ -140,12 +148,19 @@
       from prepared_data
       order by ts;`);
 
-  globals.dispatch(Actions.addDebugTrack({
-    engineId: engine.engineId,
-    name: trackName.trim() || `Debug Track ${debugTrackId}`,
-    config: {
-      sqlTableName,
-      columns: sliceColumns,
-    },
-  }));
+  const trackKey = uuidv4();
+  globals.dispatchMultiple([
+    Actions.addTrack({
+      key: trackKey,
+      name: trackName.trim() || `Debug Track ${debugTrackId}`,
+      uri: DEBUG_SLICE_TRACK_URI,
+      trackSortKey: PrimaryTrackSortKey.DEBUG_TRACK,
+      trackGroup: SCROLLING_TRACK_GROUP,
+      params: {
+        sqlTableName,
+        columns: sliceColumns,
+      },
+    }),
+    Actions.toggleTrackPinned({trackKey}),
+  ]);
 }
diff --git a/ui/src/tracks/expected_frames/index.ts b/ui/src/tracks/expected_frames/index.ts
index b30d31e..4894722 100644
--- a/ui/src/tracks/expected_frames/index.ts
+++ b/ui/src/tracks/expected_frames/index.ts
@@ -12,56 +12,53 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {TrackData} from '../../common/track_data';
+import {BigintMath as BIMath} from '../../base/bigint_math';
+import {Duration, duration, time} from '../../base/time';
+import {
+  LONG,
+  LONG_NULL,
+  NUM,
+  NUM_NULL,
+  STR,
+  STR_NULL,
+} from '../../common/query_result';
+import {
+  SliceData,
+  SliceTrackBase,
+} from '../../frontend/slice_track_base';
+import {
+  EngineProxy,
+  Plugin,
+  PluginContext,
+  PluginContextTrace,
+  PluginDescriptor,
+} from '../../public';
+import {getTrackName} from '../../public/utils';
 
 export const EXPECTED_FRAMES_SLICE_TRACK_KIND = 'ExpectedFramesSliceTrack';
 
-import {NewTrackArgs, TrackBase} from '../../frontend/track';
-import {ChromeSliceTrack} from '../chrome_slices';
+class SliceTrack extends SliceTrackBase {
+  private maxDur = Duration.ZERO;
 
-import {LONG, LONG_NULL, NUM, STR} from '../../common/query_result';
-import {duration, time} from '../../base/time';
-import {
-  TrackController,
-} from '../../controller/track_controller';
-import {Plugin, PluginContext, PluginDescriptor} from '../../public';
-import {BigintMath as BIMath} from '../../base/bigint_math';
-
-export interface Config {
-  maxDepth: number;
-  trackIds: number[];
-}
-
-export interface Data extends TrackData {
-  // Slices are stored in a columnar fashion. All fields have the same length.
-  strings: string[];
-  sliceIds: Float64Array;
-  starts: BigInt64Array;
-  ends: BigInt64Array;
-  depths: Uint16Array;
-  titles: Uint16Array;   // Index in |strings|.
-  colors?: Uint16Array;  // Index in |strings|.
-  isInstant: Uint16Array;
-  isIncomplete: Uint16Array;
-}
-
-class ExpectedFramesSliceTrackController extends TrackController<Config, Data> {
-  static readonly kind = EXPECTED_FRAMES_SLICE_TRACK_KIND;
-  private maxDurNs: duration = 0n;
+  constructor(
+      private engine: EngineProxy, maxDepth: number, trackKey: string,
+      private trackIds: number[], namespace?: string) {
+    super(maxDepth, trackKey, '', namespace);
+  }
 
   async onBoundsChange(start: time, end: time, resolution: duration):
-      Promise<Data> {
-    if (this.maxDurNs === 0n) {
-      const maxDurResult = await this.query(`
+      Promise<SliceData> {
+    if (this.maxDur === Duration.ZERO) {
+      const maxDurResult = await this.engine.query(`
         select max(iif(dur = -1, (SELECT end_ts FROM trace_bounds) - ts, dur))
           as maxDur
         from experimental_slice_layout
-        where filter_track_ids = '${this.config.trackIds.join(',')}'
+        where filter_track_ids = '${this.trackIds.join(',')}'
       `);
-      this.maxDurNs = maxDurResult.firstRow({maxDur: LONG_NULL}).maxDur || 0n;
+      this.maxDur = maxDurResult.firstRow({maxDur: LONG_NULL}).maxDur || 0n;
     }
 
-    const queryRes = await this.query(`
+    const queryRes = await this.engine.query(`
       SELECT
         (ts + ${resolution / 2n}) / ${resolution} * ${resolution} as tsq,
         ts,
@@ -73,15 +70,15 @@
         dur = -1 as isIncomplete
       from experimental_slice_layout
       where
-        filter_track_ids = '${this.config.trackIds.join(',')}' and
-        ts >= ${start - this.maxDurNs} and
+        filter_track_ids = '${this.trackIds.join(',')}' and
+        ts >= ${start - this.maxDur} and
         ts <= ${end}
       group by tsq, layout_depth
       order by tsq, layout_depth
     `);
 
     const numRows = queryRes.numRows();
-    const slices: Data = {
+    const slices: SliceData = {
       start,
       end,
       resolution,
@@ -139,18 +136,74 @@
   }
 }
 
-
-export class ExpectedFramesSliceTrack extends ChromeSliceTrack {
-  static readonly kind = EXPECTED_FRAMES_SLICE_TRACK_KIND;
-  static create(args: NewTrackArgs): TrackBase {
-    return new ExpectedFramesSliceTrack(args);
-  }
-}
-
 class ExpectedFramesPlugin implements Plugin {
-  onActivate(ctx: PluginContext): void {
-    ctx.LEGACY_registerTrackController(ExpectedFramesSliceTrackController);
-    ctx.LEGACY_registerTrack(ExpectedFramesSliceTrack);
+  onActivate(_ctx: PluginContext): void {}
+
+  async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
+    const {engine} = ctx;
+    const result = await engine.query(`
+      with process_async_tracks as materialized (
+        select
+          process_track.upid as upid,
+          process_track.name as trackName,
+          process.name as processName,
+          process.pid as pid,
+          group_concat(process_track.id) as trackIds,
+          count(1) as trackCount
+        from process_track
+        left join process using(upid)
+        where process_track.name = "Expected Timeline"
+        group by
+          process_track.upid,
+          process_track.name
+      )
+      select
+        t.*,
+        max_layout_depth(t.trackCount, t.trackIds) as maxDepth
+      from process_async_tracks t;
+  `);
+
+    const it = result.iter({
+      upid: NUM,
+      trackName: STR_NULL,
+      trackIds: STR,
+      processName: STR_NULL,
+      pid: NUM_NULL,
+      maxDepth: NUM_NULL,
+    });
+
+    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;
+
+      if (maxDepth === null) {
+        // If there are no slices in this track, skip it.
+        continue;
+      }
+
+      const displayName = getTrackName(
+          {name: trackName, upid, pid, processName, kind: 'ExpectedFrames'});
+
+      ctx.registerStaticTrack({
+        uri: `perfetto.ExpectedFrames#${upid}`,
+        displayName,
+        trackIds,
+        kind: EXPECTED_FRAMES_SLICE_TRACK_KIND,
+        track: ({trackKey}) => {
+          return new SliceTrack(
+              engine,
+              maxDepth,
+              trackKey,
+              trackIds,
+          );
+        },
+      });
+    }
   }
 }
 
diff --git a/ui/src/tracks/ftrace/index.ts b/ui/src/tracks/ftrace/index.ts
index f2ad696..58b59b0 100644
--- a/ui/src/tracks/ftrace/index.ts
+++ b/ui/src/tracks/ftrace/index.ts
@@ -144,7 +144,7 @@
     for (const cpuNum of cpus) {
       const uri = `perfetto.FtraceRaw#cpu${cpuNum}`;
 
-      ctx.addTrack({
+      ctx.registerStaticTrack({
         uri,
         displayName: `Ftrace Track for CPU ${cpuNum}`,
         kind: FTRACE_RAW_TRACK_KIND,
diff --git a/ui/src/tracks/heap_profile/index.ts b/ui/src/tracks/heap_profile/index.ts
index 38b19e7..b85827b 100644
--- a/ui/src/tracks/heap_profile/index.ts
+++ b/ui/src/tracks/heap_profile/index.ts
@@ -15,16 +15,25 @@
 import {searchSegment} from '../../base/binary_search';
 import {duration, Time, time} from '../../base/time';
 import {Actions} from '../../common/actions';
-import {LONG, STR} from '../../common/query_result';
+import {LONG, NUM, STR} from '../../common/query_result';
 import {ProfileType} from '../../common/state';
+import {
+  TrackAdapter,
+  TrackControllerAdapter,
+  TrackWithControllerAdapter,
+} from '../../common/track_adapter';
 import {TrackData} from '../../common/track_data';
 import {profileType} from '../../controller/flamegraph_controller';
-import {TrackController} from '../../controller/track_controller';
 import {FLAMEGRAPH_HOVERED_COLOR} from '../../frontend/flamegraph';
 import {globals} from '../../frontend/globals';
 import {TimeScale} from '../../frontend/time_scale';
-import {NewTrackArgs, TrackBase} from '../../frontend/track';
-import {Plugin, PluginContext, PluginDescriptor} from '../../public';
+import {NewTrackArgs} from '../../frontend/track';
+import {
+  Plugin,
+  PluginContext,
+  PluginContextTrace,
+  PluginDescriptor,
+} from '../../public';
 
 export const HEAP_PROFILE_TRACK_KIND = 'HeapProfileTrack';
 
@@ -37,8 +46,7 @@
   upid: number;
 }
 
-class HeapProfileTrackController extends TrackController<Config, Data> {
-  static readonly kind = HEAP_PROFILE_TRACK_KIND;
+class HeapProfileTrackController extends TrackControllerAdapter<Config, Data> {
   async onBoundsChange(start: time, end: time, resolution: duration):
       Promise<Data> {
     if (this.config.upid === undefined) {
@@ -88,8 +96,7 @@
 const MARGIN_TOP = 4.5;
 const RECT_HEIGHT = 30.5;
 
-class HeapProfileTrack extends TrackBase<Config, Data> {
-  static readonly kind = HEAP_PROFILE_TRACK_KIND;
+class HeapProfileTrack extends TrackAdapter<Config, Data> {
   static create(args: NewTrackArgs): HeapProfileTrack {
     return new HeapProfileTrack(args);
   }
@@ -216,9 +223,30 @@
 }
 
 class HeapProfilePlugin implements Plugin {
-  onActivate(ctx: PluginContext): void {
-    ctx.LEGACY_registerTrackController(HeapProfileTrackController);
-    ctx.LEGACY_registerTrack(HeapProfileTrack);
+  onActivate(_ctx: PluginContext): void {}
+  async onTraceLoad(ctx: PluginContextTrace): 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;
+      ctx.registerStaticTrack({
+        uri: `perfetto.HeapProfile#${upid}`,
+        displayName: 'Heap Profile',
+        kind: HEAP_PROFILE_TRACK_KIND,
+        upid,
+        track: ({trackKey}) => {
+          return new TrackWithControllerAdapter(
+              ctx.engine,
+              trackKey,
+              {upid},
+              HeapProfileTrack,
+              HeapProfileTrackController);
+        },
+      });
+    }
   }
 }
 
diff --git a/ui/src/tracks/null_track/index.ts b/ui/src/tracks/null_track/index.ts
index a007185..1b2bb24 100644
--- a/ui/src/tracks/null_track/index.ts
+++ b/ui/src/tracks/null_track/index.ts
@@ -13,15 +13,19 @@
 // limitations under the License.
 
 import {NewTrackArgs, TrackBase} from '../../frontend/track';
-import {Plugin, PluginContext, PluginDescriptor} from '../../public';
+import {
+  Plugin,
+  PluginContext,
+  PluginContextTrace,
+  PluginDescriptor,
+} from '../../public';
 
+export const NULL_TRACK_URI = 'perfetto.NullTrack';
 export const NULL_TRACK_KIND = 'NullTrack';
 
 export class NullTrack extends TrackBase {
-  static readonly kind = NULL_TRACK_KIND;
   constructor(args: NewTrackArgs) {
     super(args);
-    this.frontendOnly = true;
   }
 
   static create(args: NewTrackArgs): NullTrack {
@@ -36,8 +40,21 @@
 }
 
 class NullTrackPlugin implements Plugin {
-  onActivate(ctx: PluginContext): void {
-    ctx.LEGACY_registerTrack(NullTrack);
+  onActivate(_ctx: PluginContext): void {}
+
+  async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
+    // TODO(stevegolton): This is not the right way to handle blank tracks,
+    // instead we should probably just render some blank element at render time
+    // if no track uri is supplied.
+    ctx.registerStaticTrack({
+      uri: NULL_TRACK_URI,
+      displayName: 'Null Track',
+      kind: NULL_TRACK_KIND,
+      track: ({trackKey}) => NullTrack.create({
+        engine: ctx.engine,
+        trackKey,
+      }),
+    });
   }
 }
 
diff --git a/ui/src/tracks/perf_samples_profile/index.ts b/ui/src/tracks/perf_samples_profile/index.ts
index b6e624a..330637c 100644
--- a/ui/src/tracks/perf_samples_profile/index.ts
+++ b/ui/src/tracks/perf_samples_profile/index.ts
@@ -15,15 +15,24 @@
 import {searchSegment} from '../../base/binary_search';
 import {duration, Time, time} from '../../base/time';
 import {Actions} from '../../common/actions';
-import {LONG} from '../../common/query_result';
+import {LONG, NUM} from '../../common/query_result';
 import {ProfileType} from '../../common/state';
+import {
+  TrackAdapter,
+  TrackControllerAdapter,
+  TrackWithControllerAdapter,
+} from '../../common/track_adapter';
 import {TrackData} from '../../common/track_data';
-import {TrackController} from '../../controller/track_controller';
 import {FLAMEGRAPH_HOVERED_COLOR} from '../../frontend/flamegraph';
 import {globals} from '../../frontend/globals';
 import {TimeScale} from '../../frontend/time_scale';
-import {NewTrackArgs, TrackBase} from '../../frontend/track';
-import {Plugin, PluginContext, PluginDescriptor} from '../../public';
+import {NewTrackArgs} from '../../frontend/track';
+import {
+  Plugin,
+  PluginContext,
+  PluginContextTrace,
+  PluginDescriptor,
+} from '../../public';
 
 export const PERF_SAMPLES_PROFILE_TRACK_KIND = 'PerfSamplesProfileTrack';
 
@@ -35,8 +44,8 @@
   upid: number;
 }
 
-class PerfSamplesProfileTrackController extends TrackController<Config, Data> {
-  static readonly kind = PERF_SAMPLES_PROFILE_TRACK_KIND;
+class PerfSamplesProfileTrackController extends
+    TrackControllerAdapter<Config, Data> {
   async onBoundsChange(start: time, end: time, resolution: duration):
       Promise<Data> {
     if (this.config.upid === undefined) {
@@ -77,8 +86,7 @@
 const MARGIN_TOP = 4.5;
 const RECT_HEIGHT = 30.5;
 
-class PerfSamplesProfileTrack extends TrackBase<Config, Data> {
-  static readonly kind = PERF_SAMPLES_PROFILE_TRACK_KIND;
+class PerfSamplesProfileTrack extends TrackAdapter<Config, Data> {
   static create(args: NewTrackArgs): PerfSamplesProfileTrack {
     return new PerfSamplesProfileTrack(args);
   }
@@ -209,9 +217,32 @@
 }
 
 class PerfSamplesProfilePlugin implements Plugin {
-  onActivate(ctx: PluginContext): void {
-    ctx.LEGACY_registerTrackController(PerfSamplesProfileTrackController);
-    ctx.LEGACY_registerTrack(PerfSamplesProfileTrack);
+  onActivate(_ctx: PluginContext): void {}
+
+  async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
+    const result = await ctx.engine.query(`
+      select distinct upid, pid
+      from perf_sample join thread using (utid) join process using (upid)
+      where callsite_id is not null
+  `);
+    for (const it = result.iter({upid: NUM, pid: NUM}); it.valid(); it.next()) {
+      const upid = it.upid;
+      const pid = it.pid;
+      ctx.registerStaticTrack({
+        uri: `perfetto.PerfSamplesProfile#${upid}`,
+        displayName: `Callstacks ${pid}`,
+        kind: PERF_SAMPLES_PROFILE_TRACK_KIND,
+        upid,
+        track: ({trackKey}) => {
+          return new TrackWithControllerAdapter(
+              ctx.engine,
+              trackKey,
+              {upid},
+              PerfSamplesProfileTrack,
+              PerfSamplesProfileTrackController);
+        },
+      });
+    }
   }
 }
 
diff --git a/ui/src/tracks/process_summary/index.ts b/ui/src/tracks/process_summary/index.ts
index 24cae70..da73975 100644
--- a/ui/src/tracks/process_summary/index.ts
+++ b/ui/src/tracks/process_summary/index.ts
@@ -217,19 +217,19 @@
             utid,
           };
 
-          ctx.addTrack({
+          ctx.registerStaticTrack({
             uri,
             displayName: `${upid === null ? tid : pid} schedule`,
             kind: PROCESS_SCHEDULING_TRACK_KIND,
             tags: {
               isDebuggable,
             },
-            track: ({trackInstanceId}) => {
+            track: ({trackKey}) => {
               return new TrackWithControllerAdapter<
                   ProcessSchedulingTrackConfig,
                   ProcessSchedulingTrackData>(
                   ctx.engine,
-                  trackInstanceId,
+                  trackKey,
                   config,
                   ProcessSchedulingTrack,
                   ProcessSchedulingTrackController);
@@ -242,19 +242,19 @@
             utid,
           };
 
-          ctx.addTrack({
+          ctx.registerStaticTrack({
             uri,
             displayName: `${upid === null ? tid : pid} summary`,
             kind: PROCESS_SUMMARY_TRACK,
             tags: {
               isDebuggable,
             },
-            track: ({trackInstanceId}) => {
+            track: ({trackKey}) => {
               return new TrackWithControllerAdapter<
                   ProcessSummaryTrackConfig,
                   ProcessSummaryTrackData>(
                   ctx.engine,
-                  trackInstanceId,
+                  trackKey,
                   config,
                   ProcessSummaryTrack,
                   ProcessSummaryTrackController);
@@ -310,16 +310,16 @@
       utid: it.utid,
     };
 
-    ctx.addTrack({
+    ctx.registerStaticTrack({
       uri: 'perfetto.ProcessSummary#kernel',
       displayName: `Kernel thread summary`,
       kind: PROCESS_SUMMARY_TRACK,
-      track: ({trackInstanceId}) => {
+      track: ({trackKey}) => {
         return new TrackWithControllerAdapter<
             ProcessSummaryTrackConfig,
             ProcessSummaryTrackData>(
             ctx.engine,
-            trackInstanceId,
+            trackKey,
             config,
             ProcessSummaryTrack,
             ProcessSummaryTrackController);
diff --git a/ui/src/tracks/screenshots/index.ts b/ui/src/tracks/screenshots/index.ts
index cc85c16..5ca8efb 100644
--- a/ui/src/tracks/screenshots/index.ts
+++ b/ui/src/tracks/screenshots/index.ts
@@ -12,8 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {v4 as uuidv4} from 'uuid';
-
 import {AddTrackArgs} from '../../common/actions';
 import {Engine} from '../../common/engine';
 import {
@@ -23,6 +21,7 @@
 import {
   Plugin,
   PluginContext,
+  PluginContextTrace,
   PluginDescriptor,
   PrimaryTrackSortKey,
 } from '../../public';
@@ -36,8 +35,6 @@
   ScreenshotTab,
 } from './screenshot_panel';
 
-export {Data} from '../chrome_slices';
-
 class ScreenshotsTrack extends CustomSqlTableSliceTrack<NamedSliceTrackTypes> {
   static readonly kind = 'dev.perfetto.ScreenshotsTrack';
   static create(args: NewTrackArgs): TrackBase {
@@ -66,6 +63,7 @@
   tracksToAdd: AddTrackArgs[],
 };
 
+// TODO(stevegolton): Use suggestTrack().
 export async function decideTracks(engine: Engine):
     Promise<DecideTracksResult> {
   const result: DecideTracksResult = {
@@ -75,20 +73,28 @@
   await engine.query(`INCLUDE PERFETTO MODULE android.screenshots`);
 
   result.tracksToAdd.push({
-    id: uuidv4(),
-    engineId: engine.id,
-    kind: ScreenshotsTrack.kind,
-    trackSortKey: PrimaryTrackSortKey.ASYNC_SLICE_TRACK,
+    uri: 'perfetto.Screenshots',
     name: 'Screenshots',
-    config: {},
-    trackGroup: undefined,
+    trackSortKey: PrimaryTrackSortKey.ASYNC_SLICE_TRACK,
   });
   return result;
 }
 
 class ScreenshotsPlugin implements Plugin {
-  onActivate(ctx: PluginContext): void {
-    ctx.LEGACY_registerTrack(ScreenshotsTrack);
+  onActivate(_ctx: PluginContext): void {}
+
+  async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
+    ctx.registerStaticTrack({
+      uri: 'perfetto.Screenshots',
+      displayName: 'Screenshots',
+      kind: ScreenshotsTrack.kind,
+      track: ({trackKey}) => {
+        return new ScreenshotsTrack({
+          engine: ctx.engine,
+          trackKey,
+        });
+      },
+    });
   }
 }
 
diff --git a/ui/src/tracks/thread_state/index.ts b/ui/src/tracks/thread_state/index.ts
index 807f8f9..e6bbc84 100644
--- a/ui/src/tracks/thread_state/index.ts
+++ b/ui/src/tracks/thread_state/index.ts
@@ -21,17 +21,31 @@
 import {colorForState} from '../../common/colorizer';
 import {LONG, NUM, NUM_NULL, STR_NULL} from '../../common/query_result';
 import {translateState} from '../../common/thread_state';
+import {
+  TrackAdapter,
+  TrackControllerAdapter,
+  TrackWithControllerAdapter,
+} from '../../common/track_adapter';
 import {TrackData} from '../../common/track_data';
-import {TrackController} from '../../controller/track_controller';
 import {checkerboardExcept} from '../../frontend/checkerboard';
 import {globals} from '../../frontend/globals';
-import {NewTrackArgs, TrackBase} from '../../frontend/track';
-import {Plugin, PluginContext, PluginDescriptor} from '../../public';
+import {NewTrackArgs} from '../../frontend/track';
+import {
+  Plugin,
+  PluginContext,
+  PluginContextTrace,
+  PluginDescriptor,
+} from '../../public';
+import {getTrackName} from '../../public/utils';
 
+import {
+  ThreadStateTrack as ThreadStateTrackV2,
+} from './thread_state_v2';
 
 export const THREAD_STATE_TRACK_KIND = 'ThreadStateTrack';
+export const THREAD_STATE_TRACK_V2_KIND = 'ThreadStateTrackV2';
 
-export interface Data extends TrackData {
+interface Data extends TrackData {
   strings: string[];
   ids: Float64Array;
   starts: BigInt64Array;
@@ -40,13 +54,11 @@
   state: Uint16Array;  // Index into |strings|.
 }
 
-export interface Config {
+interface Config {
   utid: number;
 }
 
-class ThreadStateTrackController extends TrackController<Config, Data> {
-  static readonly kind = THREAD_STATE_TRACK_KIND;
-
+class ThreadStateTrackController extends TrackControllerAdapter<Config, Data> {
   private maxDurNs: duration = 0n;
 
   async onSetup() {
@@ -162,8 +174,7 @@
 const RECT_HEIGHT = 12;
 const EXCESS_WIDTH = 10;
 
-class ThreadStateTrack extends TrackBase<Config, Data> {
-  static readonly kind = THREAD_STATE_TRACK_KIND;
+class ThreadStateTrack extends TrackAdapter<Config, Data> {
   static create(args: NewTrackArgs): ThreadStateTrack {
     return new ThreadStateTrack(args);
   }
@@ -278,15 +289,76 @@
     if (index === -1) return false;
     const id = data.ids[index];
     globals.makeSelection(
-        Actions.selectThreadState({id, trackId: this.trackState.id}));
+        Actions.selectThreadState({id, trackKey: this.trackKey}));
     return true;
   }
 }
 
+
 class ThreadState implements Plugin {
-  onActivate(ctx: PluginContext): void {
-    ctx.LEGACY_registerTrack(ThreadStateTrack);
-    ctx.LEGACY_registerTrackController(ThreadStateTrackController);
+  onActivate(_ctx: PluginContext): void {}
+
+  async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
+    const {engine} = ctx;
+    const result = await engine.query(`
+      select
+        utid,
+        upid,
+        tid,
+        pid,
+        thread.name as threadName
+      from
+        thread_state
+        left join thread using(utid)
+        left join process using(upid)
+      where utid != 0
+      group by utid`);
+
+    const it = result.iter({
+      utid: NUM,
+      upid: NUM_NULL,
+      tid: NUM_NULL,
+      pid: NUM_NULL,
+      threadName: STR_NULL,
+    });
+    for (; it.valid(); it.next()) {
+      const utid = it.utid;
+      const upid = it.upid;
+      const tid = it.tid;
+      const threadName = it.threadName;
+      const displayName =
+          getTrackName({utid, tid, threadName, kind: THREAD_STATE_TRACK_KIND});
+
+      ctx.registerStaticTrack({
+        uri: `perfetto.ThreadState#${upid}.${utid}`,
+        displayName,
+        kind: THREAD_STATE_TRACK_KIND,
+        utid: utid,
+        track: ({trackKey}) => {
+          return new TrackWithControllerAdapter<Config, Data>(
+              ctx.engine,
+              trackKey,
+              {utid},
+              ThreadStateTrack,
+              ThreadStateTrackController);
+        },
+      });
+
+      ctx.registerStaticTrack({
+        uri: `perfetto.ThreadState#${utid}.v2`,
+        displayName,
+        kind: THREAD_STATE_TRACK_V2_KIND,
+        utid,
+        track: ({trackKey}) => {
+          const track = ThreadStateTrackV2.create({
+            engine: ctx.engine,
+            trackKey,
+          });
+          track.config = {utid};
+          return track;
+        },
+      });
+    }
   }
 }
 
diff --git a/ui/src/tracks/thread_state_v2/index.ts b/ui/src/tracks/thread_state/thread_state_track_v2.ts
similarity index 82%
copy from ui/src/tracks/thread_state_v2/index.ts
copy to ui/src/tracks/thread_state/thread_state_track_v2.ts
index 9c465b6..e84505d 100644
--- a/ui/src/tracks/thread_state_v2/index.ts
+++ b/ui/src/tracks/thread_state/thread_state_track_v2.ts
@@ -1,4 +1,4 @@
-// Copyright (C) 2023 The Android Open Source Project
+// 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.
@@ -29,15 +29,14 @@
   SliceLayout,
 } from '../../frontend/slice_layout';
 import {NewTrackArgs} from '../../frontend/track';
-import {Plugin, PluginContext, PluginDescriptor} from '../../public';
 
 export const THREAD_STATE_ROW = {
   ...BASE_SLICE_ROW,
   state: STR,
   ioWait: NUM_NULL,
 };
-export type ThreadStateRow = typeof THREAD_STATE_ROW;
 
+export type ThreadStateRow = typeof THREAD_STATE_ROW;
 
 export interface ThreadStateTrackConfig {
   utid: number;
@@ -48,10 +47,7 @@
   config: ThreadStateTrackConfig;
 }
 
-export const THREAD_STATE_TRACK_V2_KIND = 'ThreadStateTrackV2';
-
 export class ThreadStateTrack extends BaseSliceTrack<ThreadStateTrackTypes> {
-  static readonly kind = THREAD_STATE_TRACK_V2_KIND;
   static create(args: NewTrackArgs) {
     return new ThreadStateTrack(args);
   }
@@ -67,10 +63,9 @@
     return THREAD_STATE_ROW;
   }
 
-  async initSqlTable(tableName: string): Promise<void> {
+  getSqlSource(): string {
     // Do not display states 'x' and 'S' (dead & sleeping).
     const sql = `
-      create view ${tableName} as
       select
         id,
         ts,
@@ -85,7 +80,7 @@
         state != 'x' and
         state != 'S'
     `;
-    await this.engine.query(sql);
+    return sql;
   }
 
   rowToSlice(row: ThreadStateTrackTypes['row']):
@@ -115,7 +110,7 @@
   onSliceClick(args: OnSliceClickArgs<ThreadStateTrackTypes['slice']>) {
     globals.makeSelection(Actions.selectThreadState({
       id: args.slice.id,
-      trackId: this.trackId,
+      trackKey: this.trackKey,
     }));
   }
 
@@ -123,14 +118,3 @@
     return selection.kind === 'THREAD_STATE';
   }
 }
-
-class ThreadStateTrackV2 implements Plugin {
-  onActivate(ctx: PluginContext): void {
-    ctx.LEGACY_registerTrack(ThreadStateTrack);
-  }
-}
-
-export const plugin: PluginDescriptor = {
-  pluginId: 'perfetto.ThreadStateTrackV2',
-  plugin: ThreadStateTrackV2,
-};
diff --git a/ui/src/tracks/thread_state_v2/index.ts b/ui/src/tracks/thread_state/thread_state_v2.ts
similarity index 85%
rename from ui/src/tracks/thread_state_v2/index.ts
rename to ui/src/tracks/thread_state/thread_state_v2.ts
index 9c465b6..d5e0fa5 100644
--- a/ui/src/tracks/thread_state_v2/index.ts
+++ b/ui/src/tracks/thread_state/thread_state_v2.ts
@@ -29,7 +29,6 @@
   SliceLayout,
 } from '../../frontend/slice_layout';
 import {NewTrackArgs} from '../../frontend/track';
-import {Plugin, PluginContext, PluginDescriptor} from '../../public';
 
 export const THREAD_STATE_ROW = {
   ...BASE_SLICE_ROW,
@@ -51,7 +50,6 @@
 export const THREAD_STATE_TRACK_V2_KIND = 'ThreadStateTrackV2';
 
 export class ThreadStateTrack extends BaseSliceTrack<ThreadStateTrackTypes> {
-  static readonly kind = THREAD_STATE_TRACK_V2_KIND;
   static create(args: NewTrackArgs) {
     return new ThreadStateTrack(args);
   }
@@ -67,10 +65,9 @@
     return THREAD_STATE_ROW;
   }
 
-  async initSqlTable(tableName: string): Promise<void> {
+  getSqlSource(): string {
     // Do not display states 'x' and 'S' (dead & sleeping).
-    const sql = `
-      create view ${tableName} as
+    return `
       select
         id,
         ts,
@@ -85,7 +82,6 @@
         state != 'x' and
         state != 'S'
     `;
-    await this.engine.query(sql);
   }
 
   rowToSlice(row: ThreadStateTrackTypes['row']):
@@ -115,7 +111,7 @@
   onSliceClick(args: OnSliceClickArgs<ThreadStateTrackTypes['slice']>) {
     globals.makeSelection(Actions.selectThreadState({
       id: args.slice.id,
-      trackId: this.trackId,
+      trackKey: this.trackKey,
     }));
   }
 
@@ -123,14 +119,3 @@
     return selection.kind === 'THREAD_STATE';
   }
 }
-
-class ThreadStateTrackV2 implements Plugin {
-  onActivate(ctx: PluginContext): void {
-    ctx.LEGACY_registerTrack(ThreadStateTrack);
-  }
-}
-
-export const plugin: PluginDescriptor = {
-  pluginId: 'perfetto.ThreadStateTrackV2',
-  plugin: ThreadStateTrackV2,
-};
diff --git a/ui/src/tracks/visualised_args/index.ts b/ui/src/tracks/visualised_args/index.ts
index 3385bf9..0111078 100644
--- a/ui/src/tracks/visualised_args/index.ts
+++ b/ui/src/tracks/visualised_args/index.ts
@@ -12,63 +12,121 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+// import {NewTrackArgs, Track} from '../../frontend/track';
+// import {TrackButton, TrackButtonAttrs} from '../../frontend/track_panel';
 import m from 'mithril';
+import {v4 as uuidv4} from 'uuid';
 
 import {Actions} from '../../common/actions';
 import {globals} from '../../frontend/globals';
-import {NewTrackArgs, TrackBase} from '../../frontend/track';
-import {TrackButton, TrackButtonAttrs} from '../../frontend/track_panel';
-import {Plugin, PluginContext, PluginDescriptor} from '../../public';
+import {TrackButton} from '../../frontend/track_panel';
 import {
-  ChromeSliceTrack,
-  ChromeSliceTrackController,
-  Config as ChromeSliceConfig,
-} from '../chrome_slices';
+  EngineProxy,
+  Plugin,
+  PluginContext,
+  PluginContextTrace,
+  PluginDescriptor,
+  TrackContext,
+} from '../../public';
+import {ChromeSliceTrack} from '../chrome_slices';
 
-export {Data} from '../chrome_slices';
+export const VISUALISED_ARGS_SLICE_TRACK_URI = 'perfetto.VisualisedArgs';
 
-export const VISUALISED_ARGS_SLICE_TRACK_KIND = 'VisualisedArgsTrack';
-
-export interface Config extends ChromeSliceConfig {
+export interface VisualisedArgsState {
   argName: string;
-}
-
-// The controller for arg visualisation is exactly the same as the controller
-// for Chrome slices. All customisation is done on the frontend.
-class VisualisedArgsTrackController extends ChromeSliceTrackController {
-  static readonly kind = VISUALISED_ARGS_SLICE_TRACK_KIND;
+  maxDepth: number;
+  trackId: number;
 }
 
 export class VisualisedArgsTrack extends ChromeSliceTrack {
-  static readonly kind = VISUALISED_ARGS_SLICE_TRACK_KIND;
-  static create(args: NewTrackArgs): TrackBase {
-    return new VisualisedArgsTrack(args);
+  private helperViewName: string;
+
+  constructor(
+      engine: EngineProxy, maxDepth: number, trackKey: string, trackId: number,
+      private argName: string) {
+    const uuid = uuidv4();
+    const namespace = `__arg_visualisation_helper_${argName}_${uuid}`;
+    const escapedNamespace = namespace.replace(/[^a-zA-Z]/g, '_');
+    super(engine, maxDepth, trackKey, trackId, escapedNamespace);
+    this.helperViewName = `${escapedNamespace}_slice`;
+  }
+
+  async onCreate(_ctx: TrackContext): Promise<void> {
+    // Create the helper view - just one which is relevant to this slice
+    await this.engine.query(`
+          create view ${this.helperViewName} as
+          with slice_with_arg as (
+            select
+              slice.id,
+              slice.track_id,
+              slice.ts,
+              slice.dur,
+              slice.thread_dur,
+              NULL as cat,
+              args.display_value as name
+            from slice
+            join args using (arg_set_id)
+            where args.key='${this.argName}'
+          )
+          select
+            *,
+            (select count()
+            from ancestor_slice(s1.id) s2
+            join slice_with_arg s3 on s2.id=s3.id
+            ) as depth
+          from slice_with_arg s1
+          order by id;
+      `);
+  }
+
+  async onDestroy(): Promise<void> {
+    this.engine.query(`drop view ${this.helperViewName}`);
   }
 
   getFont() {
     return 'italic 11px Roboto';
   }
 
-  getTrackShellButtons(): Array<m.Vnode<TrackButtonAttrs>> {
-    const config = this.config as Config;
-    const buttons: Array<m.Vnode<TrackButtonAttrs>> = [];
-    buttons.push(m(TrackButton, {
+  getTrackShellButtons(): m.Children {
+    return m(TrackButton, {
       action: () => {
-        globals.dispatch(
-            Actions.removeVisualisedArg({argName: config.argName}));
+        // This behavior differs to the original behavior a little.
+        // Originally, hitting the close button on a single track removed ALL
+        // tracks with this argName, whereas this one only closes the single
+        // track.
+        // This will be easily fixable once we transition to using dynamic
+        // tracks instead of this "initial state" approach to add these tracks.
+        globals.dispatch(Actions.removeTracks({trackKeys: [this.trackKey]}));
       },
       i: 'close',
       tooltip: 'Close',
       showButton: true,
-    }));
-    return buttons;
+    });
   }
 }
 
 class VisualisedArgsPlugin implements Plugin {
-  onActivate(ctx: PluginContext): void {
-    ctx.LEGACY_registerTrackController(VisualisedArgsTrackController);
-    ctx.LEGACY_registerTrack(VisualisedArgsTrack);
+  onActivate(_ctx: PluginContext): void {}
+
+  async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
+    ctx.registerTrack({
+      uri: VISUALISED_ARGS_SLICE_TRACK_URI,
+      tags: {
+        metric: true,  // TODO(stevegolton): Is this track really a metric?
+      },
+      track: (trackCtx) => {
+        // TODO(stevegolton): Validate params properly. Note, this is no
+        // worse than the situation we had before with track config.
+        const params = trackCtx.params as VisualisedArgsState;
+        return new VisualisedArgsTrack(
+            ctx.engine,
+            params.maxDepth,
+            trackCtx.trackKey,
+            params.trackId,
+            params.argName,
+        );
+      },
+    });
   }
 }
 
diff --git a/ui/src/widgets/editor.ts b/ui/src/widgets/editor.ts
index 3bb02dd..5b26daa 100644
--- a/ui/src/widgets/editor.ts
+++ b/ui/src/widgets/editor.ts
@@ -23,6 +23,10 @@
   // Initial state for the editor.
   initialText?: string;
 
+  // Changing generation is used to force resetting of the editor state
+  // to the current value of initialText.
+  generation?: number;
+
   // Callback for the Ctrl/Cmd + Enter key binding.
   onExecute?: (text: string) => void;
 
@@ -32,6 +36,7 @@
 
 export class Editor implements m.ClassComponent<EditorAttrs> {
   private editorView?: EditorView;
+  private generation?: number;
 
   oncreate({dom, attrs}: m.CVnodeDOM<EditorAttrs>) {
     const keymaps = [indentWithTab];
@@ -69,6 +74,8 @@
       };
     }
 
+    this.generation = attrs.generation;
+
     this.editorView = new EditorView({
       doc: attrs.initialText ?? '',
       extensions: [
@@ -81,6 +88,17 @@
     });
   }
 
+  onupdate({attrs}: m.CVnodeDOM<EditorAttrs>): void {
+    const {initialText, generation} = attrs;
+    const editorView = this.editorView;
+    if (editorView && this.generation !== generation) {
+      const state = editorView.state;
+      editorView.dispatch(state.update(
+          {changes: {from: 0, to: state.doc.length, insert: initialText}}));
+      this.generation = generation;
+    }
+  }
+
   onremove(): void {
     if (this.editorView) {
       this.editorView.destroy();
@@ -89,8 +107,6 @@
   }
 
   view({}: m.Vnode<EditorAttrs, this>): void|m.Children {
-    return m(
-        '.pf-editor',
-    );
+    return m('.pf-editor');
   }
 }
diff --git a/ui/src/widgets/tree.ts b/ui/src/widgets/tree.ts
index e046a35..034bf7c 100644
--- a/ui/src/widgets/tree.ts
+++ b/ui/src/widgets/tree.ts
@@ -59,6 +59,8 @@
   // Whether this node is collapsed or not.
   // If omitted, collapsed state 'uncontrolled' - i.e. controlled internally.
   collapsed?: boolean;
+  // Whether the node should start collapsed or not, default: false.
+  startsCollapsed?: boolean;
   loading?: boolean;
   showCaret?: boolean;
   // Optional icon to show to the left of the text.
@@ -69,7 +71,12 @@
 }
 
 export class TreeNode implements m.ClassComponent<TreeNodeAttrs> {
-  private collapsed = false;
+  private collapsed;
+
+  constructor({attrs}: m.CVnode<TreeNodeAttrs>) {
+    this.collapsed = attrs.startsCollapsed ?? false;
+  }
+
   view(vnode: m.CVnode<TreeNodeAttrs>): m.Children {
     const {children, attrs, attrs: {left, onCollapseChanged = () => {}}} =
         vnode;