Merge "add support ADB_SERVER_SOCKET" into main
diff --git a/Android.bp b/Android.bp
index c3a576a..ef5737b 100644
--- a/Android.bp
+++ b/Android.bp
@@ -37,6 +37,8 @@
         ":perfetto_protos_perfetto_config_android_cpp_gen",
         ":perfetto_protos_perfetto_config_android_zero_gen",
         ":perfetto_protos_perfetto_config_cpp_gen",
+        ":perfetto_protos_perfetto_config_etw_cpp_gen",
+        ":perfetto_protos_perfetto_config_etw_zero_gen",
         ":perfetto_protos_perfetto_config_ftrace_cpp_gen",
         ":perfetto_protos_perfetto_config_ftrace_zero_gen",
         ":perfetto_protos_perfetto_config_gpu_cpp_gen",
@@ -124,6 +126,8 @@
         "perfetto_protos_perfetto_config_android_cpp_gen_headers",
         "perfetto_protos_perfetto_config_android_zero_gen_headers",
         "perfetto_protos_perfetto_config_cpp_gen_headers",
+        "perfetto_protos_perfetto_config_etw_cpp_gen_headers",
+        "perfetto_protos_perfetto_config_etw_zero_gen_headers",
         "perfetto_protos_perfetto_config_ftrace_cpp_gen_headers",
         "perfetto_protos_perfetto_config_ftrace_zero_gen_headers",
         "perfetto_protos_perfetto_config_gpu_cpp_gen_headers",
@@ -305,6 +309,8 @@
         ":perfetto_protos_perfetto_config_android_cpp_gen",
         ":perfetto_protos_perfetto_config_android_zero_gen",
         ":perfetto_protos_perfetto_config_cpp_gen",
+        ":perfetto_protos_perfetto_config_etw_cpp_gen",
+        ":perfetto_protos_perfetto_config_etw_zero_gen",
         ":perfetto_protos_perfetto_config_ftrace_cpp_gen",
         ":perfetto_protos_perfetto_config_ftrace_zero_gen",
         ":perfetto_protos_perfetto_config_gpu_cpp_gen",
@@ -401,6 +407,8 @@
         "perfetto_protos_perfetto_config_android_cpp_gen_headers",
         "perfetto_protos_perfetto_config_android_zero_gen_headers",
         "perfetto_protos_perfetto_config_cpp_gen_headers",
+        "perfetto_protos_perfetto_config_etw_cpp_gen_headers",
+        "perfetto_protos_perfetto_config_etw_zero_gen_headers",
         "perfetto_protos_perfetto_config_ftrace_cpp_gen_headers",
         "perfetto_protos_perfetto_config_ftrace_zero_gen_headers",
         "perfetto_protos_perfetto_config_gpu_cpp_gen_headers",
@@ -510,6 +518,8 @@
         ":perfetto_protos_perfetto_config_android_cpp_gen",
         ":perfetto_protos_perfetto_config_android_zero_gen",
         ":perfetto_protos_perfetto_config_cpp_gen",
+        ":perfetto_protos_perfetto_config_etw_cpp_gen",
+        ":perfetto_protos_perfetto_config_etw_zero_gen",
         ":perfetto_protos_perfetto_config_ftrace_cpp_gen",
         ":perfetto_protos_perfetto_config_ftrace_zero_gen",
         ":perfetto_protos_perfetto_config_gpu_cpp_gen",
@@ -616,6 +626,8 @@
         "perfetto_protos_perfetto_config_android_cpp_gen_headers",
         "perfetto_protos_perfetto_config_android_zero_gen_headers",
         "perfetto_protos_perfetto_config_cpp_gen_headers",
+        "perfetto_protos_perfetto_config_etw_cpp_gen_headers",
+        "perfetto_protos_perfetto_config_etw_zero_gen_headers",
         "perfetto_protos_perfetto_config_ftrace_cpp_gen_headers",
         "perfetto_protos_perfetto_config_ftrace_zero_gen_headers",
         "perfetto_protos_perfetto_config_gpu_cpp_gen_headers",
@@ -754,6 +766,8 @@
         ":perfetto_protos_perfetto_config_android_cpp_gen",
         ":perfetto_protos_perfetto_config_android_zero_gen",
         ":perfetto_protos_perfetto_config_cpp_gen",
+        ":perfetto_protos_perfetto_config_etw_cpp_gen",
+        ":perfetto_protos_perfetto_config_etw_zero_gen",
         ":perfetto_protos_perfetto_config_ftrace_cpp_gen",
         ":perfetto_protos_perfetto_config_ftrace_zero_gen",
         ":perfetto_protos_perfetto_config_gpu_cpp_gen",
@@ -839,6 +853,8 @@
         "perfetto_protos_perfetto_config_android_cpp_gen_headers",
         "perfetto_protos_perfetto_config_android_zero_gen_headers",
         "perfetto_protos_perfetto_config_cpp_gen_headers",
+        "perfetto_protos_perfetto_config_etw_cpp_gen_headers",
+        "perfetto_protos_perfetto_config_etw_zero_gen_headers",
         "perfetto_protos_perfetto_config_ftrace_cpp_gen_headers",
         "perfetto_protos_perfetto_config_ftrace_zero_gen_headers",
         "perfetto_protos_perfetto_config_gpu_cpp_gen_headers",
@@ -925,6 +941,8 @@
         ":perfetto_protos_perfetto_config_android_cpp_gen",
         ":perfetto_protos_perfetto_config_android_zero_gen",
         ":perfetto_protos_perfetto_config_cpp_gen",
+        ":perfetto_protos_perfetto_config_etw_cpp_gen",
+        ":perfetto_protos_perfetto_config_etw_zero_gen",
         ":perfetto_protos_perfetto_config_ftrace_cpp_gen",
         ":perfetto_protos_perfetto_config_ftrace_zero_gen",
         ":perfetto_protos_perfetto_config_gpu_cpp_gen",
@@ -1003,6 +1021,7 @@
     ],
     host_supported: true,
     vendor_available: true,
+    product_available: true,
     export_include_dirs: [
         "include",
         "include/perfetto/base/build_configs/android_tree",
@@ -1013,6 +1032,8 @@
         "perfetto_protos_perfetto_config_android_cpp_gen_headers",
         "perfetto_protos_perfetto_config_android_zero_gen_headers",
         "perfetto_protos_perfetto_config_cpp_gen_headers",
+        "perfetto_protos_perfetto_config_etw_cpp_gen_headers",
+        "perfetto_protos_perfetto_config_etw_zero_gen_headers",
         "perfetto_protos_perfetto_config_ftrace_cpp_gen_headers",
         "perfetto_protos_perfetto_config_ftrace_zero_gen_headers",
         "perfetto_protos_perfetto_config_gpu_cpp_gen_headers",
@@ -1066,6 +1087,8 @@
         "perfetto_protos_perfetto_config_android_cpp_gen_headers",
         "perfetto_protos_perfetto_config_android_zero_gen_headers",
         "perfetto_protos_perfetto_config_cpp_gen_headers",
+        "perfetto_protos_perfetto_config_etw_cpp_gen_headers",
+        "perfetto_protos_perfetto_config_etw_zero_gen_headers",
         "perfetto_protos_perfetto_config_ftrace_cpp_gen_headers",
         "perfetto_protos_perfetto_config_ftrace_zero_gen_headers",
         "perfetto_protos_perfetto_config_gpu_cpp_gen_headers",
@@ -1117,10 +1140,8 @@
         "perfetto_defaults",
     ],
     apex_available: [
+        "//apex_available:anyapex",
         "//apex_available:platform",
-        "com.android.art",
-        "com.android.art.debug",
-        "com.android.tethering",
     ],
     min_sdk_version: "30",
 }
@@ -1149,6 +1170,8 @@
         ":perfetto_protos_perfetto_config_android_cpp_gen",
         ":perfetto_protos_perfetto_config_android_zero_gen",
         ":perfetto_protos_perfetto_config_cpp_gen",
+        ":perfetto_protos_perfetto_config_etw_cpp_gen",
+        ":perfetto_protos_perfetto_config_etw_zero_gen",
         ":perfetto_protos_perfetto_config_ftrace_cpp_gen",
         ":perfetto_protos_perfetto_config_ftrace_zero_gen",
         ":perfetto_protos_perfetto_config_gpu_cpp_gen",
@@ -1227,6 +1250,8 @@
         "perfetto_protos_perfetto_config_android_cpp_gen_headers",
         "perfetto_protos_perfetto_config_android_zero_gen_headers",
         "perfetto_protos_perfetto_config_cpp_gen_headers",
+        "perfetto_protos_perfetto_config_etw_cpp_gen_headers",
+        "perfetto_protos_perfetto_config_etw_zero_gen_headers",
         "perfetto_protos_perfetto_config_ftrace_cpp_gen_headers",
         "perfetto_protos_perfetto_config_ftrace_zero_gen_headers",
         "perfetto_protos_perfetto_config_gpu_cpp_gen_headers",
@@ -1317,6 +1342,8 @@
         ":perfetto_protos_perfetto_config_android_cpp_gen",
         ":perfetto_protos_perfetto_config_android_zero_gen",
         ":perfetto_protos_perfetto_config_cpp_gen",
+        ":perfetto_protos_perfetto_config_etw_cpp_gen",
+        ":perfetto_protos_perfetto_config_etw_zero_gen",
         ":perfetto_protos_perfetto_config_ftrace_cpp_gen",
         ":perfetto_protos_perfetto_config_ftrace_zero_gen",
         ":perfetto_protos_perfetto_config_gpu_cpp_gen",
@@ -1449,6 +1476,8 @@
         "perfetto_protos_perfetto_config_android_cpp_gen_headers",
         "perfetto_protos_perfetto_config_android_zero_gen_headers",
         "perfetto_protos_perfetto_config_cpp_gen_headers",
+        "perfetto_protos_perfetto_config_etw_cpp_gen_headers",
+        "perfetto_protos_perfetto_config_etw_zero_gen_headers",
         "perfetto_protos_perfetto_config_ftrace_cpp_gen_headers",
         "perfetto_protos_perfetto_config_ftrace_zero_gen_headers",
         "perfetto_protos_perfetto_config_gpu_cpp_gen_headers",
@@ -1520,6 +1549,8 @@
         "perfetto_protos_perfetto_config_android_cpp_gen_headers",
         "perfetto_protos_perfetto_config_android_zero_gen_headers",
         "perfetto_protos_perfetto_config_cpp_gen_headers",
+        "perfetto_protos_perfetto_config_etw_cpp_gen_headers",
+        "perfetto_protos_perfetto_config_etw_zero_gen_headers",
         "perfetto_protos_perfetto_config_ftrace_cpp_gen_headers",
         "perfetto_protos_perfetto_config_ftrace_zero_gen_headers",
         "perfetto_protos_perfetto_config_gpu_cpp_gen_headers",
@@ -1615,6 +1646,8 @@
         ":perfetto_protos_perfetto_config_android_cpp_gen",
         ":perfetto_protos_perfetto_config_android_zero_gen",
         ":perfetto_protos_perfetto_config_cpp_gen",
+        ":perfetto_protos_perfetto_config_etw_cpp_gen",
+        ":perfetto_protos_perfetto_config_etw_zero_gen",
         ":perfetto_protos_perfetto_config_ftrace_cpp_gen",
         ":perfetto_protos_perfetto_config_ftrace_zero_gen",
         ":perfetto_protos_perfetto_config_gpu_cpp_gen",
@@ -1733,6 +1766,8 @@
         "perfetto_protos_perfetto_config_android_cpp_gen_headers",
         "perfetto_protos_perfetto_config_android_zero_gen_headers",
         "perfetto_protos_perfetto_config_cpp_gen_headers",
+        "perfetto_protos_perfetto_config_etw_cpp_gen_headers",
+        "perfetto_protos_perfetto_config_etw_zero_gen_headers",
         "perfetto_protos_perfetto_config_ftrace_cpp_gen_headers",
         "perfetto_protos_perfetto_config_ftrace_zero_gen_headers",
         "perfetto_protos_perfetto_config_gpu_cpp_gen_headers",
@@ -1804,6 +1839,8 @@
         "perfetto_protos_perfetto_config_android_cpp_gen_headers",
         "perfetto_protos_perfetto_config_android_zero_gen_headers",
         "perfetto_protos_perfetto_config_cpp_gen_headers",
+        "perfetto_protos_perfetto_config_etw_cpp_gen_headers",
+        "perfetto_protos_perfetto_config_etw_zero_gen_headers",
         "perfetto_protos_perfetto_config_ftrace_cpp_gen_headers",
         "perfetto_protos_perfetto_config_ftrace_zero_gen_headers",
         "perfetto_protos_perfetto_config_gpu_cpp_gen_headers",
@@ -2114,6 +2151,9 @@
         ":perfetto_protos_perfetto_config_android_lite_gen",
         ":perfetto_protos_perfetto_config_android_zero_gen",
         ":perfetto_protos_perfetto_config_cpp_gen",
+        ":perfetto_protos_perfetto_config_etw_cpp_gen",
+        ":perfetto_protos_perfetto_config_etw_lite_gen",
+        ":perfetto_protos_perfetto_config_etw_zero_gen",
         ":perfetto_protos_perfetto_config_ftrace_cpp_gen",
         ":perfetto_protos_perfetto_config_ftrace_lite_gen",
         ":perfetto_protos_perfetto_config_ftrace_zero_gen",
@@ -2253,7 +2293,6 @@
         ":perfetto_src_shared_lib_test_utils",
         ":perfetto_src_trace_processor_containers_containers",
         ":perfetto_src_trace_processor_db_db",
-        ":perfetto_src_trace_processor_db_overlays_overlays",
         ":perfetto_src_trace_processor_db_storage_storage",
         ":perfetto_src_trace_processor_export_json",
         ":perfetto_src_trace_processor_importers_android_bugreport_android_bugreport",
@@ -2388,6 +2427,9 @@
         "perfetto_protos_perfetto_config_android_lite_gen_headers",
         "perfetto_protos_perfetto_config_android_zero_gen_headers",
         "perfetto_protos_perfetto_config_cpp_gen_headers",
+        "perfetto_protos_perfetto_config_etw_cpp_gen_headers",
+        "perfetto_protos_perfetto_config_etw_lite_gen_headers",
+        "perfetto_protos_perfetto_config_etw_zero_gen_headers",
         "perfetto_protos_perfetto_config_ftrace_cpp_gen_headers",
         "perfetto_protos_perfetto_config_ftrace_lite_gen_headers",
         "perfetto_protos_perfetto_config_ftrace_zero_gen_headers",
@@ -3011,6 +3053,7 @@
         ":perfetto_protos_perfetto_common_cpp",
         ":perfetto_protos_perfetto_config_android_cpp",
         ":perfetto_protos_perfetto_config_cpp",
+        ":perfetto_protos_perfetto_config_etw_cpp",
         ":perfetto_protos_perfetto_config_ftrace_cpp",
         ":perfetto_protos_perfetto_config_gpu_cpp",
         ":perfetto_protos_perfetto_config_inode_file_cpp",
@@ -3046,6 +3089,7 @@
         ":perfetto_protos_perfetto_common_cpp",
         ":perfetto_protos_perfetto_config_android_cpp",
         ":perfetto_protos_perfetto_config_cpp",
+        ":perfetto_protos_perfetto_config_etw_cpp",
         ":perfetto_protos_perfetto_config_ftrace_cpp",
         ":perfetto_protos_perfetto_config_gpu_cpp",
         ":perfetto_protos_perfetto_config_inode_file_cpp",
@@ -3110,6 +3154,7 @@
         "protos/perfetto/config/chrome/chrome_config.proto",
         "protos/perfetto/config/chrome/scenario_config.proto",
         "protos/perfetto/config/data_source_config.proto",
+        "protos/perfetto/config/etw/etw_config.proto",
         "protos/perfetto/config/ftrace/ftrace_config.proto",
         "protos/perfetto/config/gpu/gpu_counter_config.proto",
         "protos/perfetto/config/gpu/vulkan_memory_config.proto",
@@ -3139,6 +3184,136 @@
     ],
 }
 
+// GN: //protos/perfetto/config/etw:cpp
+filegroup {
+    name: "perfetto_protos_perfetto_config_etw_cpp",
+    srcs: [
+        "protos/perfetto/config/etw/etw_config.proto",
+    ],
+}
+
+// GN: //protos/perfetto/config/etw:cpp
+genrule {
+    name: "perfetto_protos_perfetto_config_etw_cpp_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_config_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_config_etw_cpp)",
+    out: [
+        "external/perfetto/protos/perfetto/config/etw/etw_config.gen.cc",
+    ],
+}
+
+// GN: //protos/perfetto/config/etw:cpp
+genrule {
+    name: "perfetto_protos_perfetto_config_etw_cpp_gen_headers",
+    srcs: [
+        ":perfetto_protos_perfetto_config_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_config_etw_cpp)",
+    out: [
+        "external/perfetto/protos/perfetto/config/etw/etw_config.gen.h",
+    ],
+    export_include_dirs: [
+        ".",
+        "protos",
+    ],
+}
+
+// GN: //protos/perfetto/config/etw:lite
+filegroup {
+    name: "perfetto_protos_perfetto_config_etw_lite",
+    srcs: [
+        "protos/perfetto/config/etw/etw_config.proto",
+    ],
+}
+
+// GN: //protos/perfetto/config/etw:lite
+genrule {
+    name: "perfetto_protos_perfetto_config_etw_lite_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_config_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_config_etw_lite)",
+    out: [
+        "external/perfetto/protos/perfetto/config/etw/etw_config.pb.cc",
+    ],
+}
+
+// GN: //protos/perfetto/config/etw:lite
+genrule {
+    name: "perfetto_protos_perfetto_config_etw_lite_gen_headers",
+    srcs: [
+        ":perfetto_protos_perfetto_config_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_config_etw_lite)",
+    out: [
+        "external/perfetto/protos/perfetto/config/etw/etw_config.pb.h",
+    ],
+    export_include_dirs: [
+        ".",
+        "protos",
+    ],
+}
+
+// GN: //protos/perfetto/config/etw:zero
+filegroup {
+    name: "perfetto_protos_perfetto_config_etw_zero",
+    srcs: [
+        "protos/perfetto/config/etw/etw_config.proto",
+    ],
+}
+
+// GN: //protos/perfetto/config/etw:zero
+genrule {
+    name: "perfetto_protos_perfetto_config_etw_zero_gen",
+    srcs: [
+        ":perfetto_protos_perfetto_config_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_config_etw_zero)",
+    out: [
+        "external/perfetto/protos/perfetto/config/etw/etw_config.pbzero.cc",
+    ],
+}
+
+// GN: //protos/perfetto/config/etw:zero
+genrule {
+    name: "perfetto_protos_perfetto_config_etw_zero_gen_headers",
+    srcs: [
+        ":perfetto_protos_perfetto_config_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_config_etw_zero)",
+    out: [
+        "external/perfetto/protos/perfetto/config/etw/etw_config.pbzero.h",
+    ],
+    export_include_dirs: [
+        ".",
+        "protos",
+    ],
+}
+
 // GN: //protos/perfetto/config/ftrace:cpp
 filegroup {
     name: "perfetto_protos_perfetto_config_ftrace_cpp",
@@ -3694,6 +3869,7 @@
     srcs: [
         ":perfetto_protos_perfetto_common_lite",
         ":perfetto_protos_perfetto_config_android_lite",
+        ":perfetto_protos_perfetto_config_etw_lite",
         ":perfetto_protos_perfetto_config_ftrace_lite",
         ":perfetto_protos_perfetto_config_gpu_lite",
         ":perfetto_protos_perfetto_config_inode_file_lite",
@@ -3728,6 +3904,7 @@
     srcs: [
         ":perfetto_protos_perfetto_common_lite",
         ":perfetto_protos_perfetto_config_android_lite",
+        ":perfetto_protos_perfetto_config_etw_lite",
         ":perfetto_protos_perfetto_config_ftrace_lite",
         ":perfetto_protos_perfetto_config_gpu_lite",
         ":perfetto_protos_perfetto_config_inode_file_lite",
@@ -4735,6 +4912,7 @@
     srcs: [
         ":perfetto_protos_perfetto_common_zero",
         ":perfetto_protos_perfetto_config_android_zero",
+        ":perfetto_protos_perfetto_config_etw_zero",
         ":perfetto_protos_perfetto_config_ftrace_zero",
         ":perfetto_protos_perfetto_config_gpu_zero",
         ":perfetto_protos_perfetto_config_inode_file_zero",
@@ -4770,6 +4948,7 @@
     srcs: [
         ":perfetto_protos_perfetto_common_zero",
         ":perfetto_protos_perfetto_config_android_zero",
+        ":perfetto_protos_perfetto_config_etw_zero",
         ":perfetto_protos_perfetto_config_ftrace_zero",
         ":perfetto_protos_perfetto_config_gpu_zero",
         ":perfetto_protos_perfetto_config_inode_file_zero",
@@ -4819,6 +4998,7 @@
         ":perfetto_protos_perfetto_common_cpp",
         ":perfetto_protos_perfetto_config_android_cpp",
         ":perfetto_protos_perfetto_config_cpp",
+        ":perfetto_protos_perfetto_config_etw_cpp",
         ":perfetto_protos_perfetto_config_ftrace_cpp",
         ":perfetto_protos_perfetto_config_gpu_cpp",
         ":perfetto_protos_perfetto_config_inode_file_cpp",
@@ -4850,6 +5030,7 @@
         ":perfetto_protos_perfetto_common_cpp",
         ":perfetto_protos_perfetto_config_android_cpp",
         ":perfetto_protos_perfetto_config_cpp",
+        ":perfetto_protos_perfetto_config_etw_cpp",
         ":perfetto_protos_perfetto_config_ftrace_cpp",
         ":perfetto_protos_perfetto_config_gpu_cpp",
         ":perfetto_protos_perfetto_config_inode_file_cpp",
@@ -4894,6 +5075,7 @@
         ":perfetto_protos_perfetto_common_cpp",
         ":perfetto_protos_perfetto_config_android_cpp",
         ":perfetto_protos_perfetto_config_cpp",
+        ":perfetto_protos_perfetto_config_etw_cpp",
         ":perfetto_protos_perfetto_config_ftrace_cpp",
         ":perfetto_protos_perfetto_config_gpu_cpp",
         ":perfetto_protos_perfetto_config_inode_file_cpp",
@@ -4927,6 +5109,7 @@
         ":perfetto_protos_perfetto_common_cpp",
         ":perfetto_protos_perfetto_config_android_cpp",
         ":perfetto_protos_perfetto_config_cpp",
+        ":perfetto_protos_perfetto_config_etw_cpp",
         ":perfetto_protos_perfetto_config_ftrace_cpp",
         ":perfetto_protos_perfetto_config_gpu_cpp",
         ":perfetto_protos_perfetto_config_inode_file_cpp",
@@ -5239,6 +5422,7 @@
         "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/shell_transition.proto",
         "protos/perfetto/trace/android/surfaceflinger_common.proto",
         "protos/perfetto/trace/android/surfaceflinger_layers.proto",
         "protos/perfetto/trace/android/surfaceflinger_transactions.proto",
@@ -5268,6 +5452,7 @@
         "external/perfetto/protos/perfetto/trace/android/initial_display_state.gen.cc",
         "external/perfetto/protos/perfetto/trace/android/network_trace.gen.cc",
         "external/perfetto/protos/perfetto/trace/android/packages_list.gen.cc",
+        "external/perfetto/protos/perfetto/trace/android/shell_transition.gen.cc",
         "external/perfetto/protos/perfetto/trace/android/surfaceflinger_common.gen.cc",
         "external/perfetto/protos/perfetto/trace/android/surfaceflinger_layers.gen.cc",
         "external/perfetto/protos/perfetto/trace/android/surfaceflinger_transactions.gen.cc",
@@ -5297,6 +5482,7 @@
         "external/perfetto/protos/perfetto/trace/android/initial_display_state.gen.h",
         "external/perfetto/protos/perfetto/trace/android/network_trace.gen.h",
         "external/perfetto/protos/perfetto/trace/android/packages_list.gen.h",
+        "external/perfetto/protos/perfetto/trace/android/shell_transition.gen.h",
         "external/perfetto/protos/perfetto/trace/android/surfaceflinger_common.gen.h",
         "external/perfetto/protos/perfetto/trace/android/surfaceflinger_layers.gen.h",
         "external/perfetto/protos/perfetto/trace/android/surfaceflinger_transactions.gen.h",
@@ -5321,6 +5507,7 @@
         "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/shell_transition.proto",
         "protos/perfetto/trace/android/surfaceflinger_common.proto",
         "protos/perfetto/trace/android/surfaceflinger_layers.proto",
         "protos/perfetto/trace/android/surfaceflinger_transactions.proto",
@@ -5349,6 +5536,7 @@
         "external/perfetto/protos/perfetto/trace/android/initial_display_state.pb.cc",
         "external/perfetto/protos/perfetto/trace/android/network_trace.pb.cc",
         "external/perfetto/protos/perfetto/trace/android/packages_list.pb.cc",
+        "external/perfetto/protos/perfetto/trace/android/shell_transition.pb.cc",
         "external/perfetto/protos/perfetto/trace/android/surfaceflinger_common.pb.cc",
         "external/perfetto/protos/perfetto/trace/android/surfaceflinger_layers.pb.cc",
         "external/perfetto/protos/perfetto/trace/android/surfaceflinger_transactions.pb.cc",
@@ -5377,6 +5565,7 @@
         "external/perfetto/protos/perfetto/trace/android/initial_display_state.pb.h",
         "external/perfetto/protos/perfetto/trace/android/network_trace.pb.h",
         "external/perfetto/protos/perfetto/trace/android/packages_list.pb.h",
+        "external/perfetto/protos/perfetto/trace/android/shell_transition.pb.h",
         "external/perfetto/protos/perfetto/trace/android/surfaceflinger_common.pb.h",
         "external/perfetto/protos/perfetto/trace/android/surfaceflinger_layers.pb.h",
         "external/perfetto/protos/perfetto/trace/android/surfaceflinger_transactions.pb.h",
@@ -5391,6 +5580,7 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_android_winscope_descriptor",
     srcs: [
+        "protos/perfetto/trace/android/shell_transition.proto",
         "protos/perfetto/trace/android/surfaceflinger_common.proto",
         "protos/perfetto/trace/android/surfaceflinger_layers.proto",
         "protos/perfetto/trace/android/surfaceflinger_transactions.proto",
@@ -5419,6 +5609,7 @@
         "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/shell_transition.proto",
         "protos/perfetto/trace/android/surfaceflinger_common.proto",
         "protos/perfetto/trace/android/surfaceflinger_layers.proto",
         "protos/perfetto/trace/android/surfaceflinger_transactions.proto",
@@ -5448,6 +5639,7 @@
         "external/perfetto/protos/perfetto/trace/android/initial_display_state.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/android/network_trace.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/android/packages_list.pbzero.cc",
+        "external/perfetto/protos/perfetto/trace/android/shell_transition.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/android/surfaceflinger_common.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/android/surfaceflinger_layers.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/android/surfaceflinger_transactions.pbzero.cc",
@@ -5477,6 +5669,7 @@
         "external/perfetto/protos/perfetto/trace/android/initial_display_state.pbzero.h",
         "external/perfetto/protos/perfetto/trace/android/network_trace.pbzero.h",
         "external/perfetto/protos/perfetto/trace/android/packages_list.pbzero.h",
+        "external/perfetto/protos/perfetto/trace/android/shell_transition.pbzero.h",
         "external/perfetto/protos/perfetto/trace/android/surfaceflinger_common.pbzero.h",
         "external/perfetto/protos/perfetto/trace/android/surfaceflinger_layers.pbzero.h",
         "external/perfetto/protos/perfetto/trace/android/surfaceflinger_transactions.pbzero.h",
@@ -5667,6 +5860,7 @@
         "protos/perfetto/config/chrome/chrome_config.proto",
         "protos/perfetto/config/chrome/scenario_config.proto",
         "protos/perfetto/config/data_source_config.proto",
+        "protos/perfetto/config/etw/etw_config.proto",
         "protos/perfetto/config/ftrace/ftrace_config.proto",
         "protos/perfetto/config/gpu/gpu_counter_config.proto",
         "protos/perfetto/config/gpu/vulkan_memory_config.proto",
@@ -5696,6 +5890,7 @@
         "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/shell_transition.proto",
         "protos/perfetto/trace/android/surfaceflinger_common.proto",
         "protos/perfetto/trace/android/surfaceflinger_layers.proto",
         "protos/perfetto/trace/android/surfaceflinger_transactions.proto",
@@ -5749,6 +5944,7 @@
         "protos/perfetto/trace/ftrace/net.proto",
         "protos/perfetto/trace/ftrace/oom.proto",
         "protos/perfetto/trace/ftrace/panel.proto",
+        "protos/perfetto/trace/ftrace/perf_trace_counters.proto",
         "protos/perfetto/trace/ftrace/power.proto",
         "protos/perfetto/trace/ftrace/printk.proto",
         "protos/perfetto/trace/ftrace/raw_syscalls.proto",
@@ -6167,6 +6363,7 @@
         "protos/perfetto/trace/ftrace/net.proto",
         "protos/perfetto/trace/ftrace/oom.proto",
         "protos/perfetto/trace/ftrace/panel.proto",
+        "protos/perfetto/trace/ftrace/perf_trace_counters.proto",
         "protos/perfetto/trace/ftrace/power.proto",
         "protos/perfetto/trace/ftrace/printk.proto",
         "protos/perfetto/trace/ftrace/raw_syscalls.proto",
@@ -6248,6 +6445,7 @@
         "external/perfetto/protos/perfetto/trace/ftrace/net.gen.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/oom.gen.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/panel.gen.cc",
+        "external/perfetto/protos/perfetto/trace/ftrace/perf_trace_counters.gen.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/power.gen.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/printk.gen.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/raw_syscalls.gen.cc",
@@ -6329,6 +6527,7 @@
         "external/perfetto/protos/perfetto/trace/ftrace/net.gen.h",
         "external/perfetto/protos/perfetto/trace/ftrace/oom.gen.h",
         "external/perfetto/protos/perfetto/trace/ftrace/panel.gen.h",
+        "external/perfetto/protos/perfetto/trace/ftrace/perf_trace_counters.gen.h",
         "external/perfetto/protos/perfetto/trace/ftrace/power.gen.h",
         "external/perfetto/protos/perfetto/trace/ftrace/printk.gen.h",
         "external/perfetto/protos/perfetto/trace/ftrace/raw_syscalls.gen.h",
@@ -6406,6 +6605,7 @@
         "protos/perfetto/trace/ftrace/net.proto",
         "protos/perfetto/trace/ftrace/oom.proto",
         "protos/perfetto/trace/ftrace/panel.proto",
+        "protos/perfetto/trace/ftrace/perf_trace_counters.proto",
         "protos/perfetto/trace/ftrace/power.proto",
         "protos/perfetto/trace/ftrace/printk.proto",
         "protos/perfetto/trace/ftrace/raw_syscalls.proto",
@@ -6486,6 +6686,7 @@
         "external/perfetto/protos/perfetto/trace/ftrace/net.pb.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/oom.pb.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/panel.pb.cc",
+        "external/perfetto/protos/perfetto/trace/ftrace/perf_trace_counters.pb.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/power.pb.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/printk.pb.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/raw_syscalls.pb.cc",
@@ -6566,6 +6767,7 @@
         "external/perfetto/protos/perfetto/trace/ftrace/net.pb.h",
         "external/perfetto/protos/perfetto/trace/ftrace/oom.pb.h",
         "external/perfetto/protos/perfetto/trace/ftrace/panel.pb.h",
+        "external/perfetto/protos/perfetto/trace/ftrace/perf_trace_counters.pb.h",
         "external/perfetto/protos/perfetto/trace/ftrace/power.pb.h",
         "external/perfetto/protos/perfetto/trace/ftrace/printk.pb.h",
         "external/perfetto/protos/perfetto/trace/ftrace/raw_syscalls.pb.h",
@@ -6643,6 +6845,7 @@
         "protos/perfetto/trace/ftrace/net.proto",
         "protos/perfetto/trace/ftrace/oom.proto",
         "protos/perfetto/trace/ftrace/panel.proto",
+        "protos/perfetto/trace/ftrace/perf_trace_counters.proto",
         "protos/perfetto/trace/ftrace/power.proto",
         "protos/perfetto/trace/ftrace/printk.proto",
         "protos/perfetto/trace/ftrace/raw_syscalls.proto",
@@ -6724,6 +6927,7 @@
         "external/perfetto/protos/perfetto/trace/ftrace/net.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/oom.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/panel.pbzero.cc",
+        "external/perfetto/protos/perfetto/trace/ftrace/perf_trace_counters.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/power.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/printk.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/raw_syscalls.pbzero.cc",
@@ -6805,6 +7009,7 @@
         "external/perfetto/protos/perfetto/trace/ftrace/net.pbzero.h",
         "external/perfetto/protos/perfetto/trace/ftrace/oom.pbzero.h",
         "external/perfetto/protos/perfetto/trace/ftrace/panel.pbzero.h",
+        "external/perfetto/protos/perfetto/trace/ftrace/perf_trace_counters.pbzero.h",
         "external/perfetto/protos/perfetto/trace/ftrace/power.pbzero.h",
         "external/perfetto/protos/perfetto/trace/ftrace/printk.pbzero.h",
         "external/perfetto/protos/perfetto/trace/ftrace/raw_syscalls.pbzero.h",
@@ -7187,6 +7392,7 @@
         ":perfetto_protos_perfetto_common_cpp",
         ":perfetto_protos_perfetto_config_android_cpp",
         ":perfetto_protos_perfetto_config_cpp",
+        ":perfetto_protos_perfetto_config_etw_cpp",
         ":perfetto_protos_perfetto_config_ftrace_cpp",
         ":perfetto_protos_perfetto_config_gpu_cpp",
         ":perfetto_protos_perfetto_config_inode_file_cpp",
@@ -7220,6 +7426,7 @@
         ":perfetto_protos_perfetto_common_cpp",
         ":perfetto_protos_perfetto_config_android_cpp",
         ":perfetto_protos_perfetto_config_cpp",
+        ":perfetto_protos_perfetto_config_etw_cpp",
         ":perfetto_protos_perfetto_config_ftrace_cpp",
         ":perfetto_protos_perfetto_config_gpu_cpp",
         ":perfetto_protos_perfetto_config_inode_file_cpp",
@@ -7267,6 +7474,7 @@
     srcs: [
         ":perfetto_protos_perfetto_common_lite",
         ":perfetto_protos_perfetto_config_android_lite",
+        ":perfetto_protos_perfetto_config_etw_lite",
         ":perfetto_protos_perfetto_config_ftrace_lite",
         ":perfetto_protos_perfetto_config_gpu_lite",
         ":perfetto_protos_perfetto_config_inode_file_lite",
@@ -7299,6 +7507,7 @@
     srcs: [
         ":perfetto_protos_perfetto_common_lite",
         ":perfetto_protos_perfetto_config_android_lite",
+        ":perfetto_protos_perfetto_config_etw_lite",
         ":perfetto_protos_perfetto_config_ftrace_lite",
         ":perfetto_protos_perfetto_config_gpu_lite",
         ":perfetto_protos_perfetto_config_inode_file_lite",
@@ -7346,6 +7555,7 @@
     srcs: [
         ":perfetto_protos_perfetto_common_zero",
         ":perfetto_protos_perfetto_config_android_zero",
+        ":perfetto_protos_perfetto_config_etw_zero",
         ":perfetto_protos_perfetto_config_ftrace_zero",
         ":perfetto_protos_perfetto_config_gpu_zero",
         ":perfetto_protos_perfetto_config_inode_file_zero",
@@ -7379,6 +7589,7 @@
     srcs: [
         ":perfetto_protos_perfetto_common_zero",
         ":perfetto_protos_perfetto_config_android_zero",
+        ":perfetto_protos_perfetto_config_etw_zero",
         ":perfetto_protos_perfetto_config_ftrace_zero",
         ":perfetto_protos_perfetto_config_gpu_zero",
         ":perfetto_protos_perfetto_config_inode_file_zero",
@@ -7432,6 +7643,7 @@
         ":perfetto_protos_perfetto_common_cpp",
         ":perfetto_protos_perfetto_config_android_cpp",
         ":perfetto_protos_perfetto_config_cpp",
+        ":perfetto_protos_perfetto_config_etw_cpp",
         ":perfetto_protos_perfetto_config_ftrace_cpp",
         ":perfetto_protos_perfetto_config_gpu_cpp",
         ":perfetto_protos_perfetto_config_inode_file_cpp",
@@ -7486,6 +7698,7 @@
         ":perfetto_protos_perfetto_common_cpp",
         ":perfetto_protos_perfetto_config_android_cpp",
         ":perfetto_protos_perfetto_config_cpp",
+        ":perfetto_protos_perfetto_config_etw_cpp",
         ":perfetto_protos_perfetto_config_ftrace_cpp",
         ":perfetto_protos_perfetto_config_gpu_cpp",
         ":perfetto_protos_perfetto_config_inode_file_cpp",
@@ -7558,6 +7771,7 @@
     srcs: [
         ":perfetto_protos_perfetto_common_lite",
         ":perfetto_protos_perfetto_config_android_lite",
+        ":perfetto_protos_perfetto_config_etw_lite",
         ":perfetto_protos_perfetto_config_ftrace_lite",
         ":perfetto_protos_perfetto_config_gpu_lite",
         ":perfetto_protos_perfetto_config_inode_file_lite",
@@ -7611,6 +7825,7 @@
     srcs: [
         ":perfetto_protos_perfetto_common_lite",
         ":perfetto_protos_perfetto_config_android_lite",
+        ":perfetto_protos_perfetto_config_etw_lite",
         ":perfetto_protos_perfetto_config_ftrace_lite",
         ":perfetto_protos_perfetto_config_gpu_lite",
         ":perfetto_protos_perfetto_config_inode_file_lite",
@@ -7683,6 +7898,7 @@
     srcs: [
         ":perfetto_protos_perfetto_common_zero",
         ":perfetto_protos_perfetto_config_android_zero",
+        ":perfetto_protos_perfetto_config_etw_zero",
         ":perfetto_protos_perfetto_config_ftrace_zero",
         ":perfetto_protos_perfetto_config_gpu_zero",
         ":perfetto_protos_perfetto_config_inode_file_zero",
@@ -7737,6 +7953,7 @@
     srcs: [
         ":perfetto_protos_perfetto_common_zero",
         ":perfetto_protos_perfetto_config_android_zero",
+        ":perfetto_protos_perfetto_config_etw_zero",
         ":perfetto_protos_perfetto_config_ftrace_zero",
         ":perfetto_protos_perfetto_config_gpu_zero",
         ":perfetto_protos_perfetto_config_inode_file_zero",
@@ -10744,24 +10961,11 @@
     ],
 }
 
-// GN: //src/trace_processor/db/overlays:overlays
+// GN: //src/trace_processor/db/storage:fake_storage
 filegroup {
-    name: "perfetto_src_trace_processor_db_overlays_overlays",
+    name: "perfetto_src_trace_processor_db_storage_fake_storage",
     srcs: [
-        "src/trace_processor/db/overlays/arrangement_overlay.cc",
-        "src/trace_processor/db/overlays/null_overlay.cc",
-        "src/trace_processor/db/overlays/selector_overlay.cc",
-        "src/trace_processor/db/overlays/storage_overlay.cc",
-    ],
-}
-
-// GN: //src/trace_processor/db/overlays:unittests
-filegroup {
-    name: "perfetto_src_trace_processor_db_overlays_unittests",
-    srcs: [
-        "src/trace_processor/db/overlays/arrangement_overlay_unittest.cc",
-        "src/trace_processor/db/overlays/null_overlay_unittest.cc",
-        "src/trace_processor/db/overlays/selector_overlay_unittest.cc",
+        "src/trace_processor/db/storage/fake_storage.cc",
     ],
 }
 
@@ -10769,9 +10973,13 @@
 filegroup {
     name: "perfetto_src_trace_processor_db_storage_storage",
     srcs: [
+        "src/trace_processor/db/storage/arrangement_storage.cc",
+        "src/trace_processor/db/storage/dense_null_storage.cc",
         "src/trace_processor/db/storage/dummy_storage.cc",
         "src/trace_processor/db/storage/id_storage.cc",
+        "src/trace_processor/db/storage/null_storage.cc",
         "src/trace_processor/db/storage/numeric_storage.cc",
+        "src/trace_processor/db/storage/selector_storage.cc",
         "src/trace_processor/db/storage/set_id_storage.cc",
         "src/trace_processor/db/storage/storage.cc",
         "src/trace_processor/db/storage/string_storage.cc",
@@ -10782,8 +10990,12 @@
 filegroup {
     name: "perfetto_src_trace_processor_db_storage_unittests",
     srcs: [
+        "src/trace_processor/db/storage/arrangement_storage_unittest.cc",
+        "src/trace_processor/db/storage/dense_null_storage_unittest.cc",
         "src/trace_processor/db/storage/id_storage_unittest.cc",
+        "src/trace_processor/db/storage/null_storage_unittest.cc",
         "src/trace_processor/db/storage/numeric_storage_unittest.cc",
+        "src/trace_processor/db/storage/selector_storage_unittest.cc",
         "src/trace_processor/db/storage/set_id_storage_unittest.cc",
         "src/trace_processor/db/storage/string_storage_unittest.cc",
     ],
@@ -11259,6 +11471,8 @@
 filegroup {
     name: "perfetto_src_trace_processor_importers_proto_winscope_full",
     srcs: [
+        "src/trace_processor/importers/proto/winscope/shell_transitions_parser.cc",
+        "src/trace_processor/importers/proto/winscope/shell_transitions_tracker.cc",
         "src/trace_processor/importers/proto/winscope/surfaceflinger_layers_parser.cc",
         "src/trace_processor/importers/proto/winscope/surfaceflinger_transactions_parser.cc",
         "src/trace_processor/importers/proto/winscope/winscope_args_parser.cc",
@@ -13081,6 +13295,7 @@
         "protos/perfetto/config/chrome/chrome_config.proto",
         "protos/perfetto/config/chrome/scenario_config.proto",
         "protos/perfetto/config/data_source_config.proto",
+        "protos/perfetto/config/etw/etw_config.proto",
         "protos/perfetto/config/ftrace/ftrace_config.proto",
         "protos/perfetto/config/gpu/gpu_counter_config.proto",
         "protos/perfetto/config/gpu/vulkan_memory_config.proto",
@@ -13110,6 +13325,7 @@
         "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/shell_transition.proto",
         "protos/perfetto/trace/android/surfaceflinger_common.proto",
         "protos/perfetto/trace/android/surfaceflinger_layers.proto",
         "protos/perfetto/trace/android/surfaceflinger_transactions.proto",
@@ -13163,6 +13379,7 @@
         "protos/perfetto/trace/ftrace/net.proto",
         "protos/perfetto/trace/ftrace/oom.proto",
         "protos/perfetto/trace/ftrace/panel.proto",
+        "protos/perfetto/trace/ftrace/perf_trace_counters.proto",
         "protos/perfetto/trace/ftrace/power.proto",
         "protos/perfetto/trace/ftrace/printk.proto",
         "protos/perfetto/trace/ftrace/raw_syscalls.proto",
@@ -13261,6 +13478,7 @@
     srcs: [
         ":perfetto_protos_perfetto_common_lite_gen",
         ":perfetto_protos_perfetto_config_android_lite_gen",
+        ":perfetto_protos_perfetto_config_etw_lite_gen",
         ":perfetto_protos_perfetto_config_ftrace_lite_gen",
         ":perfetto_protos_perfetto_config_gpu_lite_gen",
         ":perfetto_protos_perfetto_config_inode_file_lite_gen",
@@ -13299,6 +13517,7 @@
     generated_headers: [
         "perfetto_protos_perfetto_common_lite_gen_headers",
         "perfetto_protos_perfetto_config_android_lite_gen_headers",
+        "perfetto_protos_perfetto_config_etw_lite_gen_headers",
         "perfetto_protos_perfetto_config_ftrace_lite_gen_headers",
         "perfetto_protos_perfetto_config_gpu_lite_gen_headers",
         "perfetto_protos_perfetto_config_inode_file_lite_gen_headers",
@@ -13333,6 +13552,7 @@
     export_generated_headers: [
         "perfetto_protos_perfetto_common_lite_gen_headers",
         "perfetto_protos_perfetto_config_android_lite_gen_headers",
+        "perfetto_protos_perfetto_config_etw_lite_gen_headers",
         "perfetto_protos_perfetto_config_ftrace_lite_gen_headers",
         "perfetto_protos_perfetto_config_gpu_lite_gen_headers",
         "perfetto_protos_perfetto_config_inode_file_lite_gen_headers",
@@ -13420,6 +13640,9 @@
         ":perfetto_protos_perfetto_config_android_lite_gen",
         ":perfetto_protos_perfetto_config_android_zero_gen",
         ":perfetto_protos_perfetto_config_cpp_gen",
+        ":perfetto_protos_perfetto_config_etw_cpp_gen",
+        ":perfetto_protos_perfetto_config_etw_lite_gen",
+        ":perfetto_protos_perfetto_config_etw_zero_gen",
         ":perfetto_protos_perfetto_config_ftrace_cpp_gen",
         ":perfetto_protos_perfetto_config_ftrace_lite_gen",
         ":perfetto_protos_perfetto_config_ftrace_zero_gen",
@@ -13590,8 +13813,7 @@
         ":perfetto_src_trace_processor_containers_containers",
         ":perfetto_src_trace_processor_containers_unittests",
         ":perfetto_src_trace_processor_db_db",
-        ":perfetto_src_trace_processor_db_overlays_overlays",
-        ":perfetto_src_trace_processor_db_overlays_unittests",
+        ":perfetto_src_trace_processor_db_storage_fake_storage",
         ":perfetto_src_trace_processor_db_storage_storage",
         ":perfetto_src_trace_processor_db_storage_unittests",
         ":perfetto_src_trace_processor_db_unittests",
@@ -13773,6 +13995,9 @@
         "perfetto_protos_perfetto_config_android_lite_gen_headers",
         "perfetto_protos_perfetto_config_android_zero_gen_headers",
         "perfetto_protos_perfetto_config_cpp_gen_headers",
+        "perfetto_protos_perfetto_config_etw_cpp_gen_headers",
+        "perfetto_protos_perfetto_config_etw_lite_gen_headers",
+        "perfetto_protos_perfetto_config_etw_zero_gen_headers",
         "perfetto_protos_perfetto_config_ftrace_cpp_gen_headers",
         "perfetto_protos_perfetto_config_ftrace_lite_gen_headers",
         "perfetto_protos_perfetto_config_ftrace_zero_gen_headers",
@@ -13953,6 +14178,8 @@
         ":perfetto_protos_perfetto_config_android_cpp_gen",
         ":perfetto_protos_perfetto_config_android_zero_gen",
         ":perfetto_protos_perfetto_config_cpp_gen",
+        ":perfetto_protos_perfetto_config_etw_cpp_gen",
+        ":perfetto_protos_perfetto_config_etw_zero_gen",
         ":perfetto_protos_perfetto_config_ftrace_cpp_gen",
         ":perfetto_protos_perfetto_config_ftrace_zero_gen",
         ":perfetto_protos_perfetto_config_gpu_cpp_gen",
@@ -14078,6 +14305,8 @@
         "perfetto_protos_perfetto_config_android_cpp_gen_headers",
         "perfetto_protos_perfetto_config_android_zero_gen_headers",
         "perfetto_protos_perfetto_config_cpp_gen_headers",
+        "perfetto_protos_perfetto_config_etw_cpp_gen_headers",
+        "perfetto_protos_perfetto_config_etw_zero_gen_headers",
         "perfetto_protos_perfetto_config_ftrace_cpp_gen_headers",
         "perfetto_protos_perfetto_config_ftrace_zero_gen_headers",
         "perfetto_protos_perfetto_config_gpu_cpp_gen_headers",
@@ -14149,6 +14378,8 @@
         "perfetto_protos_perfetto_config_android_cpp_gen_headers",
         "perfetto_protos_perfetto_config_android_zero_gen_headers",
         "perfetto_protos_perfetto_config_cpp_gen_headers",
+        "perfetto_protos_perfetto_config_etw_cpp_gen_headers",
+        "perfetto_protos_perfetto_config_etw_zero_gen_headers",
         "perfetto_protos_perfetto_config_ftrace_cpp_gen_headers",
         "perfetto_protos_perfetto_config_ftrace_zero_gen_headers",
         "perfetto_protos_perfetto_config_gpu_cpp_gen_headers",
@@ -14266,6 +14497,7 @@
         ":perfetto_include_perfetto_trace_processor_trace_processor",
         ":perfetto_protos_perfetto_common_zero_gen",
         ":perfetto_protos_perfetto_config_android_zero_gen",
+        ":perfetto_protos_perfetto_config_etw_zero_gen",
         ":perfetto_protos_perfetto_config_ftrace_zero_gen",
         ":perfetto_protos_perfetto_config_gpu_zero_gen",
         ":perfetto_protos_perfetto_config_inode_file_zero_gen",
@@ -14311,7 +14543,6 @@
         ":perfetto_src_protozero_protozero",
         ":perfetto_src_trace_processor_containers_containers",
         ":perfetto_src_trace_processor_db_db",
-        ":perfetto_src_trace_processor_db_overlays_overlays",
         ":perfetto_src_trace_processor_db_storage_storage",
         ":perfetto_src_trace_processor_export_json",
         ":perfetto_src_trace_processor_importers_android_bugreport_android_bugreport",
@@ -14384,6 +14615,7 @@
     generated_headers: [
         "perfetto_protos_perfetto_common_zero_gen_headers",
         "perfetto_protos_perfetto_config_android_zero_gen_headers",
+        "perfetto_protos_perfetto_config_etw_zero_gen_headers",
         "perfetto_protos_perfetto_config_ftrace_zero_gen_headers",
         "perfetto_protos_perfetto_config_gpu_zero_gen_headers",
         "perfetto_protos_perfetto_config_inode_file_zero_gen_headers",
@@ -14461,7 +14693,7 @@
         host: {
             static_libs: [
                 "libprotobuf-cpp-full",
-                "libsqlite",
+                "libsqlite_static_noicu",
                 "libz",
                 "sqlite_ext_percentile",
             ],
@@ -14502,6 +14734,7 @@
         ":perfetto_include_perfetto_trace_processor_trace_processor",
         ":perfetto_protos_perfetto_common_zero_gen",
         ":perfetto_protos_perfetto_config_android_zero_gen",
+        ":perfetto_protos_perfetto_config_etw_zero_gen",
         ":perfetto_protos_perfetto_config_ftrace_zero_gen",
         ":perfetto_protos_perfetto_config_gpu_zero_gen",
         ":perfetto_protos_perfetto_config_inode_file_zero_gen",
@@ -14545,7 +14778,6 @@
         ":perfetto_src_protozero_protozero",
         ":perfetto_src_trace_processor_containers_containers",
         ":perfetto_src_trace_processor_db_db",
-        ":perfetto_src_trace_processor_db_overlays_overlays",
         ":perfetto_src_trace_processor_db_storage_storage",
         ":perfetto_src_trace_processor_export_json",
         ":perfetto_src_trace_processor_importers_android_bugreport_android_bugreport",
@@ -14613,7 +14845,7 @@
         ":perfetto_src_traceconv_utils",
     ],
     static_libs: [
-        "libsqlite",
+        "libsqlite_static_noicu",
         "libz",
         "perfetto_src_trace_processor_demangle",
         "sqlite_ext_percentile",
@@ -14621,6 +14853,7 @@
     generated_headers: [
         "perfetto_protos_perfetto_common_zero_gen_headers",
         "perfetto_protos_perfetto_config_android_zero_gen_headers",
+        "perfetto_protos_perfetto_config_etw_zero_gen_headers",
         "perfetto_protos_perfetto_config_ftrace_zero_gen_headers",
         "perfetto_protos_perfetto_config_gpu_zero_gen_headers",
         "perfetto_protos_perfetto_config_inode_file_zero_gen_headers",
@@ -14735,6 +14968,8 @@
         ":perfetto_protos_perfetto_config_android_cpp_gen",
         ":perfetto_protos_perfetto_config_android_zero_gen",
         ":perfetto_protos_perfetto_config_cpp_gen",
+        ":perfetto_protos_perfetto_config_etw_cpp_gen",
+        ":perfetto_protos_perfetto_config_etw_zero_gen",
         ":perfetto_protos_perfetto_config_ftrace_cpp_gen",
         ":perfetto_protos_perfetto_config_ftrace_zero_gen",
         ":perfetto_protos_perfetto_config_gpu_cpp_gen",
@@ -14834,6 +15069,8 @@
         "perfetto_protos_perfetto_config_android_cpp_gen_headers",
         "perfetto_protos_perfetto_config_android_zero_gen_headers",
         "perfetto_protos_perfetto_config_cpp_gen_headers",
+        "perfetto_protos_perfetto_config_etw_cpp_gen_headers",
+        "perfetto_protos_perfetto_config_etw_zero_gen_headers",
         "perfetto_protos_perfetto_config_ftrace_cpp_gen_headers",
         "perfetto_protos_perfetto_config_ftrace_zero_gen_headers",
         "perfetto_protos_perfetto_config_gpu_cpp_gen_headers",
@@ -14942,6 +15179,8 @@
         ":perfetto_protos_perfetto_config_android_cpp_gen",
         ":perfetto_protos_perfetto_config_android_zero_gen",
         ":perfetto_protos_perfetto_config_cpp_gen",
+        ":perfetto_protos_perfetto_config_etw_cpp_gen",
+        ":perfetto_protos_perfetto_config_etw_zero_gen",
         ":perfetto_protos_perfetto_config_ftrace_cpp_gen",
         ":perfetto_protos_perfetto_config_ftrace_zero_gen",
         ":perfetto_protos_perfetto_config_gpu_cpp_gen",
@@ -15016,6 +15255,8 @@
         "perfetto_protos_perfetto_config_android_cpp_gen_headers",
         "perfetto_protos_perfetto_config_android_zero_gen_headers",
         "perfetto_protos_perfetto_config_cpp_gen_headers",
+        "perfetto_protos_perfetto_config_etw_cpp_gen_headers",
+        "perfetto_protos_perfetto_config_etw_zero_gen_headers",
         "perfetto_protos_perfetto_config_ftrace_cpp_gen_headers",
         "perfetto_protos_perfetto_config_ftrace_zero_gen_headers",
         "perfetto_protos_perfetto_config_gpu_cpp_gen_headers",
@@ -15242,3 +15483,29 @@
         "LICENSE",
     ],
 }
+
+// TODO(b/315118713): use list of proto file sources instead of merged proto
+gensrcs {
+    name: "perfetto_trace_javastream_protos",
+    srcs: [
+        "protos/perfetto/trace/perfetto_trace.proto",
+    ],
+    tools: [
+        "aprotoc",
+        "protoc-gen-javastream",
+        "soong_zip",
+    ],
+    cmd: "mkdir -p $(genDir)/$(in) " +
+      "&& $(location aprotoc) " +
+        "--plugin=$(location protoc-gen-javastream) " +
+        "--javastream_out=$(genDir)/$(in) " +
+        "-Iexternal/protobuf/src " +
+        "-Iexternal/perfetto " +
+        "-I . $(in) " +
+      "&& $(location soong_zip) " +
+        "-jar -o $(out) -C $(genDir)/$(in) -D $(genDir)/$(in)",
+    data: [
+        ":libprotobuf-internal-protos",
+    ],
+    output_extension: "srcjar",
+}
diff --git a/Android.bp.extras b/Android.bp.extras
index 3292ad9..9c7b0b0 100644
--- a/Android.bp.extras
+++ b/Android.bp.extras
@@ -171,3 +171,29 @@
         "LICENSE",
     ],
 }
+
+// TODO(b/315118713): use list of proto file sources instead of merged proto
+gensrcs {
+    name: "perfetto_trace_javastream_protos",
+    srcs: [
+        "protos/perfetto/trace/perfetto_trace.proto",
+    ],
+    tools: [
+        "aprotoc",
+        "protoc-gen-javastream",
+        "soong_zip",
+    ],
+    cmd: "mkdir -p $(genDir)/$(in) " +
+      "&& $(location aprotoc) " +
+        "--plugin=$(location protoc-gen-javastream) " +
+        "--javastream_out=$(genDir)/$(in) " +
+        "-Iexternal/protobuf/src " +
+        "-Iexternal/perfetto " +
+        "-I . $(in) " +
+      "&& $(location soong_zip) " +
+        "-jar -o $(out) -C $(genDir)/$(in) -D $(genDir)/$(in)",
+    data: [
+        ":libprotobuf-internal-protos",
+    ],
+    output_extension: "srcjar",
+}
diff --git a/BUILD b/BUILD
index 9353be2..5c8fbe6 100644
--- a/BUILD
+++ b/BUILD
@@ -172,6 +172,7 @@
         ":protos_perfetto_common_cpp",
         ":protos_perfetto_config_android_cpp",
         ":protos_perfetto_config_cpp",
+        ":protos_perfetto_config_etw_cpp",
         ":protos_perfetto_config_ftrace_cpp",
         ":protos_perfetto_config_gpu_cpp",
         ":protos_perfetto_config_inode_file_cpp",
@@ -217,7 +218,6 @@
         ":src_kernel_utils_syscall_table",
         ":src_protozero_proto_ring_buffer",
         ":src_trace_processor_db_db",
-        ":src_trace_processor_db_overlays_overlays",
         ":src_trace_processor_db_storage_storage",
         ":src_trace_processor_export_json",
         ":src_trace_processor_importers_android_bugreport_android_bugreport",
@@ -302,6 +302,7 @@
     deps = [
                ":protos_perfetto_common_zero",
                ":protos_perfetto_config_android_zero",
+               ":protos_perfetto_config_etw_zero",
                ":protos_perfetto_config_ftrace_zero",
                ":protos_perfetto_config_gpu_zero",
                ":protos_perfetto_config_inode_file_zero",
@@ -379,6 +380,8 @@
         ":protos_perfetto_config_android_cpp",
         ":protos_perfetto_config_android_zero",
         ":protos_perfetto_config_cpp",
+        ":protos_perfetto_config_etw_cpp",
+        ":protos_perfetto_config_etw_zero",
         ":protos_perfetto_config_ftrace_cpp",
         ":protos_perfetto_config_ftrace_zero",
         ":protos_perfetto_config_gpu_cpp",
@@ -492,6 +495,8 @@
         ":protos_perfetto_config_android_cpp",
         ":protos_perfetto_config_android_zero",
         ":protos_perfetto_config_cpp",
+        ":protos_perfetto_config_etw_cpp",
+        ":protos_perfetto_config_etw_zero",
         ":protos_perfetto_config_ftrace_cpp",
         ":protos_perfetto_config_ftrace_zero",
         ":protos_perfetto_config_gpu_cpp",
@@ -854,6 +859,7 @@
         "include/perfetto/tracing/console_interceptor.h",
         "include/perfetto/tracing/data_source.h",
         "include/perfetto/tracing/debug_annotation.h",
+        "include/perfetto/tracing/default_socket.h",
         "include/perfetto/tracing/event_context.h",
         "include/perfetto/tracing/interceptor.h",
         "include/perfetto/tracing/internal/basic_types.h",
@@ -1283,37 +1289,31 @@
         "src/trace_processor/containers/string_pool.h",
     ],
     deps = [
+        ":protos_perfetto_common_zero",
+        ":protos_perfetto_trace_processor_zero",
         ":src_base_base",
     ],
     linkstatic = True,
 )
 
-# GN target: //src/trace_processor/db/overlays:overlays
-perfetto_filegroup(
-    name = "src_trace_processor_db_overlays_overlays",
-    srcs = [
-        "src/trace_processor/db/overlays/arrangement_overlay.cc",
-        "src/trace_processor/db/overlays/arrangement_overlay.h",
-        "src/trace_processor/db/overlays/null_overlay.cc",
-        "src/trace_processor/db/overlays/null_overlay.h",
-        "src/trace_processor/db/overlays/selector_overlay.cc",
-        "src/trace_processor/db/overlays/selector_overlay.h",
-        "src/trace_processor/db/overlays/storage_overlay.cc",
-        "src/trace_processor/db/overlays/storage_overlay.h",
-        "src/trace_processor/db/overlays/types.h",
-    ],
-)
-
 # GN target: //src/trace_processor/db/storage:storage
 perfetto_filegroup(
     name = "src_trace_processor_db_storage_storage",
     srcs = [
+        "src/trace_processor/db/storage/arrangement_storage.cc",
+        "src/trace_processor/db/storage/arrangement_storage.h",
+        "src/trace_processor/db/storage/dense_null_storage.cc",
+        "src/trace_processor/db/storage/dense_null_storage.h",
         "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/null_storage.cc",
+        "src/trace_processor/db/storage/null_storage.h",
         "src/trace_processor/db/storage/numeric_storage.cc",
         "src/trace_processor/db/storage/numeric_storage.h",
+        "src/trace_processor/db/storage/selector_storage.cc",
+        "src/trace_processor/db/storage/selector_storage.h",
         "src/trace_processor/db/storage/set_id_storage.cc",
         "src/trace_processor/db/storage/set_id_storage.h",
         "src/trace_processor/db/storage/storage.cc",
@@ -1576,6 +1576,10 @@
 perfetto_filegroup(
     name = "src_trace_processor_importers_proto_winscope_full",
     srcs = [
+        "src/trace_processor/importers/proto/winscope/shell_transitions_parser.cc",
+        "src/trace_processor/importers/proto/winscope/shell_transitions_parser.h",
+        "src/trace_processor/importers/proto/winscope/shell_transitions_tracker.cc",
+        "src/trace_processor/importers/proto/winscope/shell_transitions_tracker.h",
         "src/trace_processor/importers/proto/winscope/surfaceflinger_layers_parser.cc",
         "src/trace_processor/importers/proto/winscope/surfaceflinger_layers_parser.h",
         "src/trace_processor/importers/proto/winscope/surfaceflinger_transactions_parser.cc",
@@ -3169,6 +3173,7 @@
     deps = [
         ":protos_perfetto_common_protos",
         ":protos_perfetto_config_android_protos",
+        ":protos_perfetto_config_etw_protos",
         ":protos_perfetto_config_ftrace_protos",
         ":protos_perfetto_config_gpu_protos",
         ":protos_perfetto_config_inode_file_protos",
@@ -3235,6 +3240,7 @@
     deps = [
         ":protos_perfetto_common_protos",
         ":protos_perfetto_config_android_protos",
+        ":protos_perfetto_config_etw_protos",
         ":protos_perfetto_config_ftrace_protos",
         ":protos_perfetto_config_gpu_protos",
         ":protos_perfetto_config_inode_file_protos",
@@ -3583,6 +3589,7 @@
     deps = [
         ":protos_perfetto_common_cpp",
         ":protos_perfetto_config_android_cpp",
+        ":protos_perfetto_config_etw_cpp",
         ":protos_perfetto_config_ftrace_cpp",
         ":protos_perfetto_config_gpu_cpp",
         ":protos_perfetto_config_inode_file_cpp",
@@ -3609,6 +3616,33 @@
     ],
 )
 
+# GN target: //protos/perfetto/config/etw:cpp
+perfetto_cc_protocpp_library(
+    name = "protos_perfetto_config_etw_cpp",
+    deps = [
+        ":protos_perfetto_config_etw_protos",
+    ],
+)
+
+# GN target: //protos/perfetto/config/etw:source_set
+perfetto_proto_library(
+    name = "protos_perfetto_config_etw_protos",
+    srcs = [
+        "protos/perfetto/config/etw/etw_config.proto",
+    ],
+    visibility = [
+        PERFETTO_CONFIG.proto_library_visibility,
+    ],
+)
+
+# GN target: //protos/perfetto/config/etw:zero
+perfetto_cc_protozero_library(
+    name = "protos_perfetto_config_etw_zero",
+    deps = [
+        ":protos_perfetto_config_etw_protos",
+    ],
+)
+
 # GN target: //protos/perfetto/config/ftrace:cpp
 perfetto_cc_protocpp_library(
     name = "protos_perfetto_config_ftrace_cpp",
@@ -3829,6 +3863,7 @@
     deps = [
         ":protos_perfetto_common_protos",
         ":protos_perfetto_config_android_protos",
+        ":protos_perfetto_config_etw_protos",
         ":protos_perfetto_config_ftrace_protos",
         ":protos_perfetto_config_gpu_protos",
         ":protos_perfetto_config_inode_file_protos",
@@ -3968,6 +4003,7 @@
     deps = [
         ":protos_perfetto_common_zero",
         ":protos_perfetto_config_android_zero",
+        ":protos_perfetto_config_etw_zero",
         ":protos_perfetto_config_ftrace_zero",
         ":protos_perfetto_config_gpu_zero",
         ":protos_perfetto_config_inode_file_zero",
@@ -3990,6 +4026,7 @@
         ":protos_perfetto_common_cpp",
         ":protos_perfetto_config_android_cpp",
         ":protos_perfetto_config_cpp",
+        ":protos_perfetto_config_etw_cpp",
         ":protos_perfetto_config_ftrace_cpp",
         ":protos_perfetto_config_gpu_cpp",
         ":protos_perfetto_config_inode_file_cpp",
@@ -4012,6 +4049,7 @@
         ":protos_perfetto_common_cpp",
         ":protos_perfetto_config_android_cpp",
         ":protos_perfetto_config_cpp",
+        ":protos_perfetto_config_etw_cpp",
         ":protos_perfetto_config_ftrace_cpp",
         ":protos_perfetto_config_gpu_cpp",
         ":protos_perfetto_config_inode_file_cpp",
@@ -4041,6 +4079,7 @@
     deps = [
         ":protos_perfetto_common_protos",
         ":protos_perfetto_config_android_protos",
+        ":protos_perfetto_config_etw_protos",
         ":protos_perfetto_config_ftrace_protos",
         ":protos_perfetto_config_gpu_protos",
         ":protos_perfetto_config_inode_file_protos",
@@ -4259,6 +4298,7 @@
         "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/shell_transition.proto",
         "protos/perfetto/trace/android/surfaceflinger_common.proto",
         "protos/perfetto/trace/android/surfaceflinger_layers.proto",
         "protos/perfetto/trace/android/surfaceflinger_transactions.proto",
@@ -4275,6 +4315,7 @@
 perfetto_proto_library(
     name = "protos_perfetto_trace_android_winscope_deps_protos",
     srcs = [
+        "protos/perfetto/trace/android/shell_transition.proto",
         "protos/perfetto/trace/android/surfaceflinger_common.proto",
         "protos/perfetto/trace/android/surfaceflinger_layers.proto",
         "protos/perfetto/trace/android/surfaceflinger_transactions.proto",
@@ -4421,6 +4462,7 @@
         "protos/perfetto/trace/ftrace/net.proto",
         "protos/perfetto/trace/ftrace/oom.proto",
         "protos/perfetto/trace/ftrace/panel.proto",
+        "protos/perfetto/trace/ftrace/perf_trace_counters.proto",
         "protos/perfetto/trace/ftrace/power.proto",
         "protos/perfetto/trace/ftrace/printk.proto",
         "protos/perfetto/trace/ftrace/raw_syscalls.proto",
@@ -4533,6 +4575,7 @@
     deps = [
         ":protos_perfetto_common_protos",
         ":protos_perfetto_config_android_protos",
+        ":protos_perfetto_config_etw_protos",
         ":protos_perfetto_config_ftrace_protos",
         ":protos_perfetto_config_gpu_protos",
         ":protos_perfetto_config_inode_file_protos",
@@ -4554,6 +4597,7 @@
     deps = [
         ":protos_perfetto_common_zero",
         ":protos_perfetto_config_android_zero",
+        ":protos_perfetto_config_etw_zero",
         ":protos_perfetto_config_ftrace_zero",
         ":protos_perfetto_config_gpu_zero",
         ":protos_perfetto_config_inode_file_zero",
@@ -4589,6 +4633,7 @@
     deps = [
         ":protos_perfetto_common_protos",
         ":protos_perfetto_config_android_protos",
+        ":protos_perfetto_config_etw_protos",
         ":protos_perfetto_config_ftrace_protos",
         ":protos_perfetto_config_gpu_protos",
         ":protos_perfetto_config_inode_file_protos",
@@ -4630,6 +4675,7 @@
     deps = [
         ":protos_perfetto_common_zero",
         ":protos_perfetto_config_android_zero",
+        ":protos_perfetto_config_etw_zero",
         ":protos_perfetto_config_ftrace_zero",
         ":protos_perfetto_config_gpu_zero",
         ":protos_perfetto_config_inode_file_zero",
@@ -5096,6 +5142,8 @@
         ":protos_perfetto_config_android_cpp",
         ":protos_perfetto_config_android_zero",
         ":protos_perfetto_config_cpp",
+        ":protos_perfetto_config_etw_cpp",
+        ":protos_perfetto_config_etw_zero",
         ":protos_perfetto_config_ftrace_cpp",
         ":protos_perfetto_config_ftrace_zero",
         ":protos_perfetto_config_gpu_cpp",
@@ -5188,6 +5236,8 @@
         ":protos_perfetto_config_android_cpp",
         ":protos_perfetto_config_android_zero",
         ":protos_perfetto_config_cpp",
+        ":protos_perfetto_config_etw_cpp",
+        ":protos_perfetto_config_etw_zero",
         ":protos_perfetto_config_ftrace_cpp",
         ":protos_perfetto_config_ftrace_zero",
         ":protos_perfetto_config_gpu_cpp",
@@ -5246,7 +5296,6 @@
     srcs = [
         ":src_kernel_utils_syscall_table",
         ":src_trace_processor_db_db",
-        ":src_trace_processor_db_overlays_overlays",
         ":src_trace_processor_db_storage_storage",
         ":src_trace_processor_export_json",
         ":src_trace_processor_importers_android_bugreport_android_bugreport",
@@ -5332,6 +5381,7 @@
     deps = [
                ":protos_perfetto_common_zero",
                ":protos_perfetto_config_android_zero",
+               ":protos_perfetto_config_etw_zero",
                ":protos_perfetto_config_ftrace_zero",
                ":protos_perfetto_config_gpu_zero",
                ":protos_perfetto_config_inode_file_zero",
@@ -5412,7 +5462,6 @@
         ":src_profiling_symbolizer_symbolizer",
         ":src_protozero_proto_ring_buffer",
         ":src_trace_processor_db_db",
-        ":src_trace_processor_db_overlays_overlays",
         ":src_trace_processor_db_storage_storage",
         ":src_trace_processor_export_json",
         ":src_trace_processor_importers_android_bugreport_android_bugreport",
@@ -5486,6 +5535,7 @@
     deps = [
                ":protos_perfetto_common_zero",
                ":protos_perfetto_config_android_zero",
+               ":protos_perfetto_config_etw_zero",
                ":protos_perfetto_config_ftrace_zero",
                ":protos_perfetto_config_gpu_zero",
                ":protos_perfetto_config_inode_file_zero",
@@ -5574,6 +5624,7 @@
     deps = [
         ":protos_perfetto_common_zero",
         ":protos_perfetto_config_android_zero",
+        ":protos_perfetto_config_etw_zero",
         ":protos_perfetto_config_ftrace_zero",
         ":protos_perfetto_config_gpu_zero",
         ":protos_perfetto_config_inode_file_zero",
@@ -5635,7 +5686,6 @@
         ":src_profiling_symbolizer_symbolizer",
         ":src_protozero_proto_ring_buffer",
         ":src_trace_processor_db_db",
-        ":src_trace_processor_db_overlays_overlays",
         ":src_trace_processor_db_storage_storage",
         ":src_trace_processor_export_json",
         ":src_trace_processor_importers_android_bugreport_android_bugreport",
@@ -5710,6 +5760,7 @@
     deps = [
                ":protos_perfetto_common_zero",
                ":protos_perfetto_config_android_zero",
+               ":protos_perfetto_config_etw_zero",
                ":protos_perfetto_config_ftrace_zero",
                ":protos_perfetto_config_gpu_zero",
                ":protos_perfetto_config_inode_file_zero",
diff --git a/CHANGELOG b/CHANGELOG
index 1c5e81b..35291b7 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -9,6 +9,17 @@
     *
 
 
+v40.0 - 2023-12-04:
+  Tracing service and probes:
+    * Added support for collecting battery voltage from the Android Power HAL.
+    * Added support for emitting machine id from producers on remote hosts.
+  Trace Processor:
+    * Added of flow id from trace as a column in the flow table.
+    * Fixed computation of trace_bounds table when using UI native acceleration.
+  UI:
+    * Switched to use "v2" querying and rendering system for tracks by default.
+
+
 v39.0 - 2023-11-15:
   Tracing service and probes:
     * Added reporting of TZ offset under system_info.timezone_off_mins .
@@ -18,12 +29,12 @@
     * Added support for running on Linux & Android systems configured with 16K
       pagetables.
   Trace Processor:
-    * New android_boot metric.
+    * Added android_boot metric.
     * Added new PerfettoSQL syntax (CREATE PERFETTO VIEW) for adding schemas to views.
-    * Support perf.data import format.
-    * Add dvfs and cpu_idle to stdlib.
+    * Added support for the perf.data import format.
+    * Added dvfs and cpu_idle to stdlib.
   UI:
-    * Add a new type of debug tracks: counter.
+    * Added a new type of debug tracks: counter.
     * Improved visualization of timestamps for durations.
 
 
diff --git a/docs/analysis/common-queries.md b/docs/analysis/common-queries.md
index adae686..6675bc0 100644
--- a/docs/analysis/common-queries.md
+++ b/docs/analysis/common-queries.md
@@ -52,8 +52,8 @@
 SELECT
   slice_id,
   slice_name,
-  SUM(CASE state = 'R' THEN dur ELSE 0 END) AS runnable_time,
-  SUM(CASE state = 'D' THEN dur ELSE 0 END) AS uninterruptible_time
+  SUM(IIF(state = 'R', dur, 0)) AS runnable_time,
+  SUM(IIF(state = 'D', dur, 0)) AS uninterruptible_time
 FROM slice_thread_state_breakdown
 GROUP BY slice_id;
 ```
diff --git a/docs/contributing/ui-plugins.md b/docs/contributing/ui-plugins.md
index 939e334..809018a 100644
--- a/docs/contributing/ui-plugins.md
+++ b/docs/contributing/ui-plugins.md
@@ -14,7 +14,7 @@
 ```sh
 git clone https://android.googlesource.com/platform/external/perfetto/
 cd perfetto
-./tool/install-build-deps --ui
+./tools/install-build-deps --ui
 ```
 
 ### Copy the plugin skeleton
diff --git a/docs/data-sources/cpu-scheduling.md b/docs/data-sources/cpu-scheduling.md
index b8447ff..6023136 100644
--- a/docs/data-sources/cpu-scheduling.md
+++ b/docs/data-sources/cpu-scheduling.md
@@ -126,10 +126,11 @@
 
 NOTE: `sched_waking` and `sched_wakeup` provide nearly the same information. The
       difference lies in wakeup events across CPUs, which involve
-      inter-processor interrupts. The former is emitted on the source (wakee)
-      CPU, the latter on the destination (waked) CPU. `sched_waking` is usually
-      sufficient for latency analysis, unless you are looking into breaking down
-      latency due to inter-processor signaling.
+      inter-processor interrupts. The former is always emitted on the source (wakee)
+      CPU, the latter may be executed on either the source or the destination (waked) CPU
+      depending on several factors. `sched_waking` is usually sufficient for latency
+      analysis, unless you are looking into breaking down latency due to
+      the scheduler's wake up path, such as inter-processor signaling.
 
 When enabling `sched_waking` events, the following will appear in the UI when
 selecting a CPU slice:
diff --git a/include/perfetto/base/build_config.h b/include/perfetto/base/build_config.h
index 5002529..a0e6bb9 100644
--- a/include/perfetto/base/build_config.h
+++ b/include/perfetto/base/build_config.h
@@ -129,6 +129,16 @@
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_ANDROID_USERDEBUG_BUILD() 0
 #endif
 
+// Processor architecture detection.  For more info on what's defined, see:
+//   http://msdn.microsoft.com/en-us/library/b0084kay.aspx
+//   http://www.agner.org/optimize/calling_conventions.pdf
+//   or with gcc, run: "echo | gcc -E -dM -"
+#if defined(__aarch64__) || defined(_M_ARM64)
+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_ARCH_CPU_ARM64() 1
+#else
+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_ARCH_CPU_ARM64() 0
+#endif
+
 // perfetto_build_flags.h contains the tweakable build flags defined via GN.
 // - In GN builds (e.g., standalone, chromium, v8) this file is generated at
 //   build time via the gen_rule //gn/gen_buildflags.
diff --git a/include/perfetto/base/platform_handle.h b/include/perfetto/base/platform_handle.h
index 879fa85..88f6d59 100644
--- a/include/perfetto/base/platform_handle.h
+++ b/include/perfetto/base/platform_handle.h
@@ -17,6 +17,8 @@
 #ifndef INCLUDE_PERFETTO_BASE_PLATFORM_HANDLE_H_
 #define INCLUDE_PERFETTO_BASE_PLATFORM_HANDLE_H_
 
+#include <stdint.h>
+
 #include "perfetto/base/build_config.h"
 
 namespace perfetto {
@@ -30,10 +32,17 @@
 //    in Windows.h take an int, not a HANDLE.
 // 2. Handles returned by old-school WINAPI like CreateFile, CreateEvent etc.
 //    These are proper HANDLE(s). PlatformHandle should be used here.
+//
+// On Windows, sockets have their own type (SOCKET) which is neither a HANDLE
+// nor an int. However Windows SOCKET(s) can have an event HANDLE attached
+// to them (which in Perfetto is a PlatformHandle), and that can be used in
+// WaitForMultipleObjects, hence in base::TaskRunner.AddFileDescriptorWatch().
+// On POSIX OSes, a SocketHandle is really just an int (a file descriptor).
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
-// Windows.h typedefs HANDLE to void*. We use void* here to avoid leaking
-// Windows.h through our headers.
+// Windows.h typedefs HANDLE to void*, and SOCKET to uintptr_t. We use their
+// types to avoid leaking Windows.h through our headers.
 using PlatformHandle = void*;
+using SocketHandle = uintptr_t;
 
 // On Windows both nullptr and 0xffff... (INVALID_HANDLE_VALUE) are invalid.
 struct PlatformHandleChecker {
@@ -43,6 +52,7 @@
 };
 #else
 using PlatformHandle = int;
+using SocketHandle = int;
 struct PlatformHandleChecker {
   static inline bool IsValid(PlatformHandle h) { return h >= 0; }
 };
diff --git a/include/perfetto/base/time.h b/include/perfetto/base/time.h
index fd40946..3e5b101 100644
--- a/include/perfetto/base/time.h
+++ b/include/perfetto/base/time.h
@@ -49,6 +49,7 @@
 }
 
 void SleepMicroseconds(unsigned interval_us);
+void InitializeTime();
 
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
 
diff --git a/include/perfetto/ext/base/sys_types.h b/include/perfetto/ext/base/sys_types.h
index 999a152..49eeb11 100644
--- a/include/perfetto/ext/base/sys_types.h
+++ b/include/perfetto/ext/base/sys_types.h
@@ -21,6 +21,7 @@
 // missing on Windows.
 
 #include <sys/types.h>
+#include <cstdint>
 
 #include "perfetto/base/build_config.h"
 
@@ -43,6 +44,11 @@
 namespace perfetto {
 namespace base {
 
+// The machine ID used in the tracing core.
+using MachineID = uint32_t;
+// The default value reserved for the host trace.
+constexpr MachineID kDefaultMachineID = 0;
+
 constexpr uid_t kInvalidUid = static_cast<uid_t>(-1);
 constexpr pid_t kInvalidPid = static_cast<pid_t>(-1);
 
diff --git a/include/perfetto/ext/base/thread_annotations.h b/include/perfetto/ext/base/thread_annotations.h
index beb8c45..6aee16a 100644
--- a/include/perfetto/ext/base/thread_annotations.h
+++ b/include/perfetto/ext/base/thread_annotations.h
@@ -24,15 +24,13 @@
 extern "C" {
 void AnnotateBenignRaceSized(const char* file,
                              int line,
-                             unsigned long address,
-                             unsigned long size,
+                             const volatile void* address,
+                             size_t size,
                              const char* description);
 }
 
-#define PERFETTO_ANNOTATE_BENIGN_RACE_SIZED(pointer, size, description)   \
-  AnnotateBenignRaceSized(__FILE__, __LINE__,                             \
-                          reinterpret_cast<unsigned long>(pointer), size, \
-                          description);
+#define PERFETTO_ANNOTATE_BENIGN_RACE_SIZED(pointer, size, description) \
+  AnnotateBenignRaceSized(__FILE__, __LINE__, pointer, size, description);
 #else  // defined(ADDRESS_SANITIZER)
 #define PERFETTO_ANNOTATE_BENIGN_RACE_SIZED(pointer, size, description)
 #endif  // defined(ADDRESS_SANITIZER)
diff --git a/include/perfetto/ext/base/unix_socket.h b/include/perfetto/ext/base/unix_socket.h
index 74ce2d9..e5cc536 100644
--- a/include/perfetto/ext/base/unix_socket.h
+++ b/include/perfetto/ext/base/unix_socket.h
@@ -27,6 +27,7 @@
 #include "perfetto/base/build_config.h"
 #include "perfetto/base/export.h"
 #include "perfetto/base/logging.h"
+#include "perfetto/base/platform_handle.h"
 #include "perfetto/ext/base/scoped_file.h"
 #include "perfetto/ext/base/utils.h"
 #include "perfetto/ext/base/weak_ptr.h"
@@ -36,23 +37,13 @@
 namespace perfetto {
 namespace base {
 
-// Define the SocketHandle and ScopedSocketHandle types.
-// On POSIX OSes, a SocketHandle is really just an int (a file descriptor).
-// On Windows, sockets are have their own type (SOCKET) which is neither a
-// HANDLE nor an int. However Windows SOCKET(s) can have a event HANDLE attached
-// to them (which in Perfetto is a PlatformHandle), and that can be used in
-// WaitForMultipleObjects, hence in base::TaskRunner.AddFileDescriptorWatch().
+// Define the ScopedSocketHandle type.
 
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
-// uintptr_t really reads as SOCKET here (Windows headers typedef to that).
-// As usual we don't just use SOCKET here to avoid leaking Windows.h includes
-// in our headers.
-using SocketHandle = uintptr_t;  // SOCKET
 int CloseSocket(SocketHandle);   // A wrapper around ::closesocket().
 using ScopedSocketHandle =
     ScopedResource<SocketHandle, CloseSocket, static_cast<SocketHandle>(-1)>;
 #else
-using SocketHandle = int;
 using ScopedSocketHandle = ScopedFile;
 #endif
 
diff --git a/include/perfetto/ext/ipc/client_info.h b/include/perfetto/ext/ipc/client_info.h
index b7c997d..132ab69 100644
--- a/include/perfetto/ext/ipc/client_info.h
+++ b/include/perfetto/ext/ipc/client_info.h
@@ -18,6 +18,7 @@
 #define INCLUDE_PERFETTO_EXT_IPC_CLIENT_INFO_H_
 
 #include "perfetto/base/logging.h"
+#include "perfetto/ext/base/sys_types.h"
 #include "perfetto/ext/base/utils.h"
 #include "perfetto/ext/ipc/basic_types.h"
 
@@ -28,12 +29,16 @@
 class ClientInfo {
  public:
   ClientInfo() = default;
-  ClientInfo(ClientID client_id, uid_t uid, pid_t pid)
-      : client_id_(client_id), uid_(uid), pid_(pid) {}
+  ClientInfo(ClientID client_id,
+             uid_t uid,
+             pid_t pid,
+             base::MachineID machine_id)
+      : client_id_(client_id), uid_(uid), pid_(pid), machine_id_(machine_id) {}
 
   bool operator==(const ClientInfo& other) const {
-    return std::tie(client_id_, uid_, pid_) ==
-           std::tie(other.client_id_, other.uid_, other.pid_);
+    return std::tie(client_id_, uid_, pid_, machine_id_) ==
+           std::tie(other.client_id_, other.uid_, other.pid_,
+                    other.machine_id_);
   }
   bool operator!=(const ClientInfo& other) const { return !(*this == other); }
 
@@ -54,12 +59,16 @@
   // Posix process ID. Comes from the kernel and can be trusted.
   int32_t pid() const { return pid_; }
 
+  // An integral ID that identifies the machine the client is on.
+  base::MachineID machine_id() const { return machine_id_; }
+
  private:
   ClientID client_id_ = 0;
   // The following fields are emitted to trace packets and should be kept in
   // sync with perfetto::ClientIdentity.
   uid_t uid_ = kInvalidUid;
   pid_t pid_ = base::kInvalidPid;
+  base::MachineID machine_id_ = base::kDefaultMachineID;
 };
 
 }  // namespace ipc
diff --git a/include/perfetto/ext/tracing/core/basic_types.h b/include/perfetto/ext/tracing/core/basic_types.h
index b0090e9..c3484ac 100644
--- a/include/perfetto/ext/tracing/core/basic_types.h
+++ b/include/perfetto/ext/tracing/core/basic_types.h
@@ -98,6 +98,10 @@
 constexpr TracingSessionID kBugreportSessionId =
     static_cast<TracingSessionID>(-1);
 
+// The ID of a machine in a multi-machine tracing session.
+using MachineID = base::MachineID;
+constexpr MachineID kDefaultMachineID = base::kDefaultMachineID;
+
 }  // namespace perfetto
 
 #endif  // INCLUDE_PERFETTO_EXT_TRACING_CORE_BASIC_TYPES_H_
diff --git a/include/perfetto/ext/tracing/core/client_identity.h b/include/perfetto/ext/tracing/core/client_identity.h
index 1ab009a..3a18846 100644
--- a/include/perfetto/ext/tracing/core/client_identity.h
+++ b/include/perfetto/ext/tracing/core/client_identity.h
@@ -18,6 +18,7 @@
 #define INCLUDE_PERFETTO_EXT_TRACING_CORE_CLIENT_IDENTITY_H_
 
 #include "perfetto/ext/base/sys_types.h"
+#include "perfetto/ext/tracing/core/basic_types.h"
 
 namespace perfetto {
 
@@ -26,7 +27,8 @@
 class ClientIdentity {
  public:
   ClientIdentity() = default;
-  ClientIdentity(uid_t uid, pid_t pid) : uid_(uid), pid_(pid) {}
+  ClientIdentity(uid_t uid, pid_t pid, MachineID machine_id = kDefaultMachineID)
+      : uid_(uid), pid_(pid), machine_id_(machine_id) {}
 
   bool has_uid() const { return uid_ != base::kInvalidUid; }
   uid_t uid() const { return uid_; }
@@ -34,9 +36,15 @@
   bool has_pid() const { return pid_ != base::kInvalidPid; }
   pid_t pid() const { return pid_; }
 
+  bool has_non_default_machine_id() const {
+    return machine_id_ != kDefaultMachineID;
+  }
+  base::MachineID machine_id() const { return machine_id_; }
+
  private:
   uid_t uid_ = base::kInvalidUid;
   pid_t pid_ = base::kInvalidPid;
+  MachineID machine_id_ = kDefaultMachineID;
 };
 }  // namespace perfetto
 
diff --git a/include/perfetto/ext/tracing/ipc/default_socket.h b/include/perfetto/ext/tracing/ipc/default_socket.h
index 86d7b78..b7d667b 100644
--- a/include/perfetto/ext/tracing/ipc/default_socket.h
+++ b/include/perfetto/ext/tracing/ipc/default_socket.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -17,25 +17,9 @@
 #ifndef INCLUDE_PERFETTO_EXT_TRACING_IPC_DEFAULT_SOCKET_H_
 #define INCLUDE_PERFETTO_EXT_TRACING_IPC_DEFAULT_SOCKET_H_
 
-#include <string>
-#include <vector>
-
-#include "perfetto/base/export.h"
-
-namespace perfetto {
-
-PERFETTO_EXPORT_COMPONENT const char* GetConsumerSocket();
-// This function is used for tokenize the |producer_socket_names| string into
-// multiple producer socket names.
-PERFETTO_EXPORT_COMPONENT std::vector<std::string> TokenizeProducerSockets(
-    const char* producer_socket_names);
-PERFETTO_EXPORT_COMPONENT const char* GetProducerSocket();
-
-// Optionally returns the relay socket name (nullable). The relay socket is used
-// for forwarding the IPC messages between the local producers and the remote
-// tracing service.
-PERFETTO_EXPORT_COMPONENT const char* GetRelaySocket();
-
-}  // namespace perfetto
+// TODO(khokhlov): Migrate usages of "perfetto/ext/tracing/ipc/default_socket.h"
+// in Chromium to include "perfetto/tracing/internal/default_socket.h" instead,
+// then delete this file.
+#include "perfetto/tracing/default_socket.h"
 
 #endif  // INCLUDE_PERFETTO_EXT_TRACING_IPC_DEFAULT_SOCKET_H_
diff --git a/include/perfetto/ext/tracing/ipc/producer_ipc_client.h b/include/perfetto/ext/tracing/ipc/producer_ipc_client.h
index 1af399b..33e19b4 100644
--- a/include/perfetto/ext/tracing/ipc/producer_ipc_client.h
+++ b/include/perfetto/ext/tracing/ipc/producer_ipc_client.h
@@ -25,6 +25,7 @@
 #include "perfetto/ext/tracing/core/shared_memory.h"
 #include "perfetto/ext/tracing/core/shared_memory_arbiter.h"
 #include "perfetto/ext/tracing/core/tracing_service.h"
+#include "perfetto/tracing/tracing_backend.h"
 
 namespace perfetto {
 
@@ -89,7 +90,8 @@
       size_t shared_memory_size_hint_bytes = 0,
       size_t shared_memory_page_size_hint_bytes = 0,
       std::unique_ptr<SharedMemory> shm = nullptr,
-      std::unique_ptr<SharedMemoryArbiter> shm_arbiter = nullptr);
+      std::unique_ptr<SharedMemoryArbiter> shm_arbiter = nullptr,
+      CreateSocketAsync create_socket_async = {});
 
  protected:
   ProducerIPCClient() = delete;
diff --git a/include/perfetto/tracing/BUILD.gn b/include/perfetto/tracing/BUILD.gn
index 159e155..3e0e9f2 100644
--- a/include/perfetto/tracing/BUILD.gn
+++ b/include/perfetto/tracing/BUILD.gn
@@ -33,6 +33,7 @@
     "console_interceptor.h",
     "data_source.h",
     "debug_annotation.h",
+    "default_socket.h",
     "event_context.h",
     "interceptor.h",
     "internal/basic_types.h",
diff --git a/include/perfetto/tracing/default_socket.h b/include/perfetto/tracing/default_socket.h
new file mode 100644
index 0000000..1989724
--- /dev/null
+++ b/include/perfetto/tracing/default_socket.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef INCLUDE_PERFETTO_TRACING_DEFAULT_SOCKET_H_
+#define INCLUDE_PERFETTO_TRACING_DEFAULT_SOCKET_H_
+
+#include <string>
+#include <vector>
+
+#include "perfetto/base/export.h"
+
+namespace perfetto {
+
+PERFETTO_EXPORT_COMPONENT const char* GetConsumerSocket();
+// This function is used for tokenize the |producer_socket_names| string into
+// multiple producer socket names.
+PERFETTO_EXPORT_COMPONENT std::vector<std::string> TokenizeProducerSockets(
+    const char* producer_socket_names);
+PERFETTO_EXPORT_COMPONENT const char* GetProducerSocket();
+
+// Optionally returns the relay socket name (nullable). The relay socket is used
+// for forwarding the IPC messages between the local producers and the remote
+// tracing service.
+PERFETTO_EXPORT_COMPONENT const char* GetRelaySocket();
+
+}  // namespace perfetto
+
+#endif  // INCLUDE_PERFETTO_TRACING_DEFAULT_SOCKET_H_
diff --git a/include/perfetto/tracing/internal/system_tracing_backend.h b/include/perfetto/tracing/internal/system_tracing_backend.h
index c1ea664..fdcdc99 100644
--- a/include/perfetto/tracing/internal/system_tracing_backend.h
+++ b/include/perfetto/tracing/internal/system_tracing_backend.h
@@ -18,6 +18,7 @@
 #define INCLUDE_PERFETTO_TRACING_INTERNAL_SYSTEM_TRACING_BACKEND_H_
 
 #include "perfetto/base/export.h"
+#include "perfetto/tracing/default_socket.h"
 #include "perfetto/tracing/tracing_backend.h"
 
 namespace perfetto {
diff --git a/include/perfetto/tracing/internal/track_event_data_source.h b/include/perfetto/tracing/internal/track_event_data_source.h
index 9fd18e3..1d924b2 100644
--- a/include/perfetto/tracing/internal/track_event_data_source.h
+++ b/include/perfetto/tracing/internal/track_event_data_source.h
@@ -1080,6 +1080,9 @@
       // We haven't seen this category before. Let's figure out if it's enabled.
       // This requires grabbing a lock to read the session's trace config.
       auto ds = ctx->GetDataSourceLocked();
+      if (!ds) {
+        return false;
+      }
       Category category{Category::FromDynamicCategory(dynamic_category)};
       bool enabled = TrackEventInternal::IsCategoryEnabled(
           *Registry, ds->config_, category);
diff --git a/include/perfetto/tracing/tracing.h b/include/perfetto/tracing/tracing.h
index 44503ba..824f492 100644
--- a/include/perfetto/tracing/tracing.h
+++ b/include/perfetto/tracing/tracing.h
@@ -152,6 +152,16 @@
   // event tracks for the same thread.
   bool disallow_merging_with_system_tracks = false;
 
+  // If set, this function will be called by the producer client to create a
+  // socket for connection to the system service. The function takes two
+  // arguments: a name of the socket, and a callback that takes an open file
+  // descriptor. It should create a socket with the given name, connect to it,
+  // and return the corresponding descriptor via the callback.
+  // This is intended for the use-case where a process being traced is run
+  // inside a sandbox and can't create sockets directly.
+  // Not yet supported for consumer connections currently.
+  CreateSocketAsync create_socket_async;
+
  protected:
   friend class Tracing;
   friend class internal::TracingMuxerImpl;
diff --git a/include/perfetto/tracing/tracing_backend.h b/include/perfetto/tracing/tracing_backend.h
index 965d726..b915776 100644
--- a/include/perfetto/tracing/tracing_backend.h
+++ b/include/perfetto/tracing/tracing_backend.h
@@ -17,10 +17,12 @@
 #ifndef INCLUDE_PERFETTO_TRACING_TRACING_BACKEND_H_
 #define INCLUDE_PERFETTO_TRACING_TRACING_BACKEND_H_
 
+#include <functional>
 #include <memory>
 #include <string>
 
 #include "perfetto/base/export.h"
+#include "perfetto/base/platform_handle.h"
 
 // The embedder can (but doesn't have to) extend the TracingBackend class and
 // pass as an argument to Tracing::Initialize(kCustomBackend) to override the
@@ -43,6 +45,9 @@
 class Producer;
 class ProducerEndpoint;
 
+using CreateSocketCallback = std::function<void(base::SocketHandle)>;
+using CreateSocketAsync = std::function<void(CreateSocketCallback)>;
+
 // Responsible for connecting to the producer.
 class PERFETTO_EXPORT_COMPONENT TracingProducerBackend {
  public:
@@ -74,6 +79,10 @@
     // it to the service when connecting.
     // It's used in startup tracing.
     bool use_producer_provided_smb = false;
+
+    // If set, the producer will call this function to create and connect to a
+    // socket. See the corresponding field in TracingInitArgs for more info.
+    CreateSocketAsync create_socket_async;
   };
 
   virtual std::unique_ptr<ProducerEndpoint> ConnectProducer(
diff --git a/protos/perfetto/config/BUILD.gn b/protos/perfetto/config/BUILD.gn
index 1366a44..c9cf032 100644
--- a/protos/perfetto/config/BUILD.gn
+++ b/protos/perfetto/config/BUILD.gn
@@ -22,6 +22,7 @@
   deps = [
     "../common:@TYPE@",
     "android:@TYPE@",
+    "etw:@TYPE@",
     "ftrace:@TYPE@",
     "gpu:@TYPE@",
     "inode_file:@TYPE@",
diff --git a/protos/perfetto/config/data_source_config.proto b/protos/perfetto/config/data_source_config.proto
index d0f8bbf..7af57dc 100644
--- a/protos/perfetto/config/data_source_config.proto
+++ b/protos/perfetto/config/data_source_config.proto
@@ -28,6 +28,7 @@
 import "protos/perfetto/config/android/surfaceflinger_layers_config.proto";
 import "protos/perfetto/config/android/surfaceflinger_transactions_config.proto";
 import "protos/perfetto/config/chrome/chrome_config.proto";
+import "protos/perfetto/config/etw/etw_config.proto";
 import "protos/perfetto/config/ftrace/ftrace_config.proto";
 import "protos/perfetto/config/gpu/gpu_counter_config.proto";
 import "protos/perfetto/config/gpu/vulkan_memory_config.proto";
@@ -45,7 +46,7 @@
 import "protos/perfetto/config/system_info/system_info.proto";
 
 // The configuration that is passed to each data source when starting tracing.
-// Next id: 125
+// Next id: 126
 message DataSourceConfig {
   enum SessionInitiator {
     SESSION_INITIATOR_UNSPECIFIED = 0;
@@ -181,6 +182,9 @@
   optional AndroidSdkSyspropGuardConfig android_sdk_sysprop_guard_config = 124
       [lazy = true];
 
+  // Data source name: windows.etw
+  optional EtwConfig etw_config = 125 [lazy = true];
+
   // This is a fallback mechanism to send a free-form text config to the
   // producer. In theory this should never be needed. All the code that
   // is part of the platform (i.e. traced service) is supposed to *not* truncate
diff --git a/protos/perfetto/config/etw/BUILD.gn b/protos/perfetto/config/etw/BUILD.gn
new file mode 100644
index 0000000..3e298f6
--- /dev/null
+++ b/protos/perfetto/config/etw/BUILD.gn
@@ -0,0 +1,20 @@
+# 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")
+
+perfetto_proto_library("@TYPE@") {
+  sources = [ "etw_config.proto" ]
+}
diff --git a/protos/perfetto/config/etw/etw_config.proto b/protos/perfetto/config/etw/etw_config.proto
new file mode 100644
index 0000000..c8f7e83
--- /dev/null
+++ b/protos/perfetto/config/etw/etw_config.proto
@@ -0,0 +1,36 @@
+/*
+ * 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 struct _EVENT_TRACE_PROPERTIES definition
+// See: https://learn.microsoft.com/en-us/windows/win32/api/evntrace/
+// ns-evntrace-event_trace_properties
+message EtwConfig {
+  // The KernelFlag represent list of kernel flags that we are intrested in.
+  // To get a more extensive list run 'xperf -providers k'.
+  enum KernelFlag {
+    C_SWITCH = 0;
+    IDLE_STATES = 1;
+  }
+
+  // The kernel_flags determines the flags that will be used by the etw tracing
+  // session. These kernel flags have been built to expose the useful events
+  // captured from the kernel mode only.
+  repeated KernelFlag kernel_flags = 1;
+}
\ No newline at end of file
diff --git a/protos/perfetto/config/perfetto_config.proto b/protos/perfetto/config/perfetto_config.proto
index c245505..96aa5c8 100644
--- a/protos/perfetto/config/perfetto_config.proto
+++ b/protos/perfetto/config/perfetto_config.proto
@@ -645,6 +645,26 @@
 
 // End of protos/perfetto/config/chrome/chrome_config.proto
 
+// Begin of protos/perfetto/config/etw/etw_config.proto
+
+// Proto definition based on the struct _EVENT_TRACE_PROPERTIES definition
+// See: https://learn.microsoft.com/en-us/windows/win32/api/evntrace/
+// ns-evntrace-event_trace_properties
+message EtwConfig {
+  // The KernelFlag represent list of kernel flags that we are intrested in.
+  // To get a more extensive list run 'xperf -providers k'.
+  enum KernelFlag {
+    C_SWITCH = 0;
+    IDLE_STATES = 1;
+  }
+
+  // The kernel_flags determines the flags that will be used by the etw tracing
+  // session. These kernel flags have been built to expose the useful events
+  // captured from the kernel mode only.
+  repeated KernelFlag kernel_flags = 1;
+}
+// End of protos/perfetto/config/etw/etw_config.proto
+
 // Begin of protos/perfetto/config/ftrace/ftrace_config.proto
 
 // Next id: 26.
@@ -1950,7 +1970,6 @@
   ATOM_PERFETTO_TRIGGER = 329;
   ATOM_TRANSCODING_DATA = 330;
   ATOM_IMS_SERVICE_ENTITLEMENT_UPDATED = 331;
-  ATOM_ART_DATUM_REPORTED = 332;
   ATOM_DEVICE_ROTATED = 333;
   ATOM_SIM_SPECIFIC_SETTINGS_RESTORED = 334;
   ATOM_TEXT_CLASSIFIER_DOWNLOAD_REPORTED = 335;
@@ -1980,7 +1999,6 @@
   ATOM_LOCATION_TIME_ZONE_PROVIDER_STATE_CHANGED = 359;
   ATOM_FDTRACK_EVENT_OCCURRED = 364;
   ATOM_TIMEOUT_AUTO_EXTENDED_REPORTED = 365;
-  ATOM_ODREFRESH_REPORTED = 366;
   ATOM_ALARM_BATCH_DELIVERED = 367;
   ATOM_ALARM_SCHEDULED = 368;
   ATOM_CAR_WATCHDOG_IO_OVERUSE_STATS_REPORTED = 369;
@@ -2048,6 +2066,8 @@
   ATOM_HOTWORD_DETECTION_SERVICE_RESTARTED = 432;
   ATOM_HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED = 433;
   ATOM_HOTWORD_DETECTOR_EVENTS = 434;
+  ATOM_AD_SERVICES_API_CALLED = 435;
+  ATOM_AD_SERVICES_MESUREMENT_REPORTS_UPLOADED = 436;
   ATOM_BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED = 437;
   ATOM_CONTACTS_INDEXER_UPDATE_STATS_REPORTED = 440;
   ATOM_APP_BACKGROUND_RESTRICTIONS_INFO = 441;
@@ -2069,24 +2089,47 @@
   ATOM_ISOLATED_COMPILATION_SCHEDULED = 457;
   ATOM_ISOLATED_COMPILATION_ENDED = 458;
   ATOM_ONS_OPPORTUNISTIC_ESIM_PROVISIONING_COMPLETE = 459;
+  ATOM_SYSTEM_SERVER_PRE_WATCHDOG_OCCURRED = 460;
   ATOM_TELEPHONY_ANOMALY_DETECTED = 461;
   ATOM_LETTERBOX_POSITION_CHANGED = 462;
   ATOM_REMOTE_KEY_PROVISIONING_ATTEMPT = 463;
   ATOM_REMOTE_KEY_PROVISIONING_NETWORK_INFO = 464;
   ATOM_REMOTE_KEY_PROVISIONING_TIMING = 465;
   ATOM_MEDIAOUTPUT_OP_INTERACTION_REPORT = 466;
-  ATOM_BACKGROUND_DEXOPT_JOB_ENDED = 467;
   ATOM_SYNC_EXEMPTION_OCCURRED = 468;
   ATOM_AUTOFILL_PRESENTATION_EVENT_REPORTED = 469;
   ATOM_DOCK_STATE_CHANGED = 470;
+  ATOM_SAFETY_SOURCE_STATE_COLLECTED = 471;
+  ATOM_SAFETY_CENTER_SYSTEM_EVENT_REPORTED = 472;
+  ATOM_SAFETY_CENTER_INTERACTION_REPORTED = 473;
+  ATOM_SETTINGS_PROVIDER_SETTING_CHANGED = 474;
   ATOM_BROADCAST_DELIVERY_EVENT_REPORTED = 475;
   ATOM_SERVICE_REQUEST_EVENT_REPORTED = 476;
   ATOM_PROVIDER_ACQUISITION_EVENT_REPORTED = 477;
   ATOM_BLUETOOTH_DEVICE_NAME_REPORTED = 478;
+  ATOM_CB_CONFIG_UPDATED = 479;
+  ATOM_CB_MODULE_ERROR_REPORTED = 480;
+  ATOM_CB_SERVICE_FEATURE_CHANGED = 481;
+  ATOM_CB_RECEIVER_FEATURE_CHANGED = 482;
+  ATOM_JSSCRIPTENGINE_LATENCY_REPORTED = 483;
+  ATOM_PRIVACY_SIGNAL_NOTIFICATION_INTERACTION = 484;
+  ATOM_PRIVACY_SIGNAL_ISSUE_CARD_INTERACTION = 485;
+  ATOM_PRIVACY_SIGNALS_JOB_FAILURE = 486;
   ATOM_VIBRATION_REPORTED = 487;
   ATOM_UWB_RANGING_START = 489;
+  ATOM_MOBILE_DATA_DOWNLOAD_FILE_GROUP_STATUS_REPORTED = 490;
+  ATOM_APP_COMPACTED_V2 = 491;
+  ATOM_AD_SERVICES_SETTINGS_USAGE_REPORTED = 493;
   ATOM_DISPLAY_BRIGHTNESS_CHANGED = 494;
   ATOM_ACTIVITY_ACTION_BLOCKED = 495;
+  ATOM_BACKGROUND_FETCH_PROCESS_REPORTED = 496;
+  ATOM_UPDATE_CUSTOM_AUDIENCE_PROCESS_REPORTED = 497;
+  ATOM_RUN_AD_BIDDING_PROCESS_REPORTED = 498;
+  ATOM_RUN_AD_SCORING_PROCESS_REPORTED = 499;
+  ATOM_RUN_AD_SELECTION_PROCESS_REPORTED = 500;
+  ATOM_RUN_AD_BIDDING_PER_CA_PROCESS_REPORTED = 501;
+  ATOM_MOBILE_DATA_DOWNLOAD_DOWNLOAD_RESULT_REPORTED = 502;
+  ATOM_MOBILE_DATA_DOWNLOAD_FILE_GROUP_STORAGE_STATS_REPORTED = 503;
   ATOM_NETWORK_DNS_SERVER_SUPPORT_REPORTED = 504;
   ATOM_VM_BOOTED = 505;
   ATOM_VM_EXITED = 506;
@@ -2095,13 +2138,19 @@
   ATOM_MEDIAMETRICS_SPATIALIZERDEVICEENABLED_REPORTED = 509;
   ATOM_MEDIAMETRICS_HEADTRACKERDEVICEENABLED_REPORTED = 510;
   ATOM_MEDIAMETRICS_HEADTRACKERDEVICESUPPORTED_REPORTED = 511;
+  ATOM_AD_SERVICES_MEASUREMENT_REGISTRATIONS = 512;
   ATOM_HEARING_AID_INFO_REPORTED = 513;
   ATOM_DEVICE_WIDE_JOB_CONSTRAINT_CHANGED = 514;
+  ATOM_AMBIENT_MODE_CHANGED = 515;
+  ATOM_ANR_LATENCY_REPORTED = 516;
+  ATOM_RESOURCE_API_INFO = 517;
+  ATOM_SYSTEM_DEFAULT_NETWORK_CHANGED = 518;
   ATOM_IWLAN_SETUP_DATA_CALL_RESULT_REPORTED = 519;
   ATOM_IWLAN_PDN_DISCONNECTED_REASON_REPORTED = 520;
   ATOM_AIRPLANE_MODE_SESSION_REPORTED = 521;
   ATOM_VM_CPU_STATUS_REPORTED = 522;
   ATOM_VM_MEM_STATUS_REPORTED = 523;
+  ATOM_PACKAGE_INSTALLATION_SESSION_REPORTED = 524;
   ATOM_DEFAULT_NETWORK_REMATCH_INFO = 525;
   ATOM_NETWORK_SELECTION_PERFORMANCE = 526;
   ATOM_NETWORK_NSD_REPORTED = 527;
@@ -2111,22 +2160,65 @@
   ATOM_BLUETOOTH_LOCAL_SUPPORTED_FEATURES_REPORTED = 532;
   ATOM_BLUETOOTH_GATT_APP_INFO = 533;
   ATOM_BRIGHTNESS_CONFIGURATION_UPDATED = 534;
+  ATOM_AD_SERVICES_GET_TOPICS_REPORTED = 535;
+  ATOM_AD_SERVICES_EPOCH_COMPUTATION_GET_TOP_TOPICS_REPORTED = 536;
+  ATOM_AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED = 537;
+  ATOM_WEAR_MEDIA_OUTPUT_SWITCHER_LAUNCHED = 538;
+  ATOM_WEAR_MEDIA_OUTPUT_SWITCHER_FINISHED = 539;
+  ATOM_WEAR_MEDIA_OUTPUT_SWITCHER_CONNECTION_REPORTED = 540;
+  ATOM_WEAR_MEDIA_OUTPUT_SWITCHER_DEVICE_SCAN_TRIGGERED = 541;
+  ATOM_WEAR_MEDIA_OUTPUT_SWITCHER_FIRST_DEVICE_SCAN_LATENCY = 542;
+  ATOM_WEAR_MEDIA_OUTPUT_SWITCHER_CONNECT_DEVICE_LATENCY = 543;
+  ATOM_PACKAGE_MANAGER_SNAPSHOT_REPORTED = 544;
+  ATOM_PACKAGE_MANAGER_APPS_FILTER_CACHE_BUILD_REPORTED = 545;
+  ATOM_PACKAGE_MANAGER_APPS_FILTER_CACHE_UPDATE_REPORTED = 546;
   ATOM_LAUNCHER_IMPRESSION_EVENT = 547;
-  ATOM_ODSIGN_REPORTED = 548;
-  ATOM_ART_DEVICE_DATUM_REPORTED = 550;
+  ATOM_WEAR_MEDIA_OUTPUT_SWITCHER_ALL_DEVICES_SCAN_LATENCY = 549;
+  ATOM_WS_WATCH_FACE_EDITED = 551;
+  ATOM_WS_WATCH_FACE_FAVORITE_ACTION_REPORTED = 552;
+  ATOM_WS_WATCH_FACE_SET_ACTION_REPORTED = 553;
+  ATOM_PACKAGE_UNINSTALLATION_REPORTED = 554;
+  ATOM_GAME_MODE_CHANGED = 555;
+  ATOM_GAME_MODE_CONFIGURATION_CHANGED = 556;
+  ATOM_BEDTIME_MODE_STATE_CHANGED = 557;
   ATOM_NETWORK_SLICE_SESSION_ENDED = 558;
   ATOM_NETWORK_SLICE_DAILY_DATA_USAGE_REPORTED = 559;
   ATOM_NFC_TAG_TYPE_OCCURRED = 560;
   ATOM_NFC_AID_CONFLICT_OCCURRED = 561;
   ATOM_NFC_READER_CONFLICT_OCCURRED = 562;
-  ATOM_ART_DATUM_DELTA_REPORTED = 565;
+  ATOM_WS_TILE_LIST_CHANGED = 563;
+  ATOM_GET_TYPE_ACCESSED_WITHOUT_PERMISSION = 564;
+  ATOM_MOBILE_BUNDLED_APP_INFO_GATHERED = 566;
+  ATOM_WS_WATCH_FACE_COMPLICATION_SET_CHANGED = 567;
   ATOM_MEDIA_DRM_CREATED = 568;
   ATOM_MEDIA_DRM_ERRORED = 569;
   ATOM_MEDIA_DRM_SESSION_OPENED = 570;
   ATOM_MEDIA_DRM_SESSION_CLOSED = 571;
+  ATOM_USER_SELECTED_RESOLUTION = 572;
+  ATOM_UNSAFE_INTENT_EVENT_REPORTED = 573;
   ATOM_PERFORMANCE_HINT_SESSION_REPORTED = 574;
+  ATOM_MEDIAMETRICS_MIDI_DEVICE_CLOSE_REPORTED = 576;
+  ATOM_BIOMETRIC_TOUCH_REPORTED = 577;
   ATOM_HOTWORD_AUDIO_EGRESS_EVENT_REPORTED = 578;
+  ATOM_APP_SEARCH_SCHEMA_MIGRATION_STATS_REPORTED = 579;
+  ATOM_LOCATION_ENABLED_STATE_CHANGED = 580;
+  ATOM_IME_REQUEST_FINISHED = 581;
+  ATOM_USB_COMPLIANCE_WARNINGS_REPORTED = 582;
+  ATOM_APP_SUPPORTED_LOCALES_CHANGED = 583;
+  ATOM_GRAMMATICAL_INFLECTION_CHANGED = 584;
+  ATOM_MEDIA_PROVIDER_VOLUME_RECOVERY_REPORTED = 586;
+  ATOM_BIOMETRIC_PROPERTIES_COLLECTED = 587;
+  ATOM_KERNEL_WAKEUP_ATTRIBUTED = 588;
+  ATOM_SCREEN_STATE_CHANGED_V2 = 589;
+  ATOM_WS_BACKUP_ACTION_REPORTED = 590;
+  ATOM_WS_RESTORE_ACTION_REPORTED = 591;
+  ATOM_DEVICE_LOG_ACCESS_EVENT_REPORTED = 592;
+  ATOM_MEDIA_SESSION_UPDATED = 594;
+  ATOM_WEAR_OOBE_STATE_CHANGED = 595;
+  ATOM_WS_NOTIFICATION_UPDATED = 596;
   ATOM_NETWORK_VALIDATION_FAILURE_STATS_DAILY_REPORTED = 601;
+  ATOM_WS_COMPLICATION_TAPPED = 602;
+  ATOM_WS_WEAR_TIME_SESSION = 610;
   ATOM_WIFI_BYTES_TRANSFER = 10000;
   ATOM_WIFI_BYTES_TRANSFER_BY_FG_BG = 10001;
   ATOM_MOBILE_BYTES_TRANSFER = 10002;
@@ -2279,15 +2371,160 @@
   ATOM_TELEPHONY_NETWORK_REQUESTS_V2 = 10153;
   ATOM_DEVICE_TELEPHONY_PROPERTIES = 10154;
   ATOM_REMOTE_KEY_PROVISIONING_ERROR_COUNTS = 10155;
+  ATOM_SAFETY_STATE = 10156;
   ATOM_INCOMING_MMS = 10157;
   ATOM_OUTGOING_MMS = 10158;
   ATOM_MULTI_USER_INFO = 10160;
   ATOM_NETWORK_BPF_MAP_INFO = 10161;
+  ATOM_OUTGOING_SHORT_CODE_SMS = 10162;
   ATOM_CONNECTIVITY_STATE_SAMPLE = 10163;
   ATOM_NETWORK_SELECTION_REMATCH_REASONS_INFO = 10164;
+  ATOM_GAME_MODE_INFO = 10165;
+  ATOM_GAME_MODE_CONFIGURATION = 10166;
+  ATOM_GAME_MODE_LISTENER = 10167;
   ATOM_NETWORK_SLICE_REQUEST_COUNT = 10168;
+  ATOM_WS_TILE_SNAPSHOT = 10169;
+  ATOM_WS_ACTIVE_WATCH_FACE_COMPLICATION_SET_SNAPSHOT = 10170;
+  ATOM_PROCESS_STATE = 10171;
+  ATOM_PROCESS_ASSOCIATION = 10172;
   ATOM_ADPF_SYSTEM_COMPONENT_INFO = 10173;
   ATOM_NOTIFICATION_MEMORY_USE = 10174;
+  ATOM_HDR_CAPABILITIES = 10175;
+  ATOM_WS_FAVOURITE_WATCH_FACE_LIST_SNAPSHOT = 10176;
+  ATOM_WIFI_AWARE_NDP_REPORTED = 638;
+  ATOM_WIFI_AWARE_ATTACH_REPORTED = 639;
+  ATOM_WIFI_SELF_RECOVERY_TRIGGERED = 661;
+  ATOM_SOFT_AP_STARTED = 680;
+  ATOM_SOFT_AP_STOPPED = 681;
+  ATOM_WIFI_LOCK_RELEASED = 687;
+  ATOM_WIFI_LOCK_DEACTIVATED = 688;
+  ATOM_WIFI_CONFIG_SAVED = 689;
+  ATOM_WIFI_AWARE_RESOURCE_USING_CHANGED = 690;
+  ATOM_WIFI_AWARE_HAL_API_CALLED = 691;
+  ATOM_WIFI_LOCAL_ONLY_REQUEST_RECEIVED = 692;
+  ATOM_WIFI_LOCAL_ONLY_REQUEST_SCAN_TRIGGERED = 693;
+  ATOM_WIFI_THREAD_TASK_EXECUTED = 694;
+  ATOM_WIFI_STATE_CHANGED = 700;
+  ATOM_WIFI_AWARE_CAPABILITIES = 10190;
+  ATOM_WIFI_MODULE_INFO = 10193;
+  ATOM_SETTINGS_SPA_REPORTED = 622;
+  ATOM_EXPRESS_EVENT_REPORTED = 528;
+  ATOM_EXPRESS_HISTOGRAM_SAMPLE_REPORTED = 593;
+  ATOM_EXPRESS_UID_EVENT_REPORTED = 644;
+  ATOM_EXPRESS_UID_HISTOGRAM_SAMPLE_REPORTED = 658;
+  ATOM_PERMISSION_RATIONALE_DIALOG_VIEWED = 645;
+  ATOM_PERMISSION_RATIONALE_DIALOG_ACTION_REPORTED = 646;
+  ATOM_APP_DATA_SHARING_UPDATES_NOTIFICATION_INTERACTION = 647;
+  ATOM_APP_DATA_SHARING_UPDATES_FRAGMENT_VIEWED = 648;
+  ATOM_APP_DATA_SHARING_UPDATES_FRAGMENT_ACTION_REPORTED = 649;
+  ATOM_WS_INCOMING_CALL_ACTION_REPORTED = 626;
+  ATOM_WS_CALL_DISCONNECTION_REPORTED = 627;
+  ATOM_WS_CALL_DURATION_REPORTED = 628;
+  ATOM_WS_CALL_USER_EXPERIENCE_LATENCY_REPORTED = 629;
+  ATOM_WS_CALL_INTERACTION_REPORTED = 630;
+  ATOM_FULL_SCREEN_INTENT_LAUNCHED = 631;
+  ATOM_BAL_ALLOWED = 632;
+  ATOM_IN_TASK_ACTIVITY_STARTED = 685;
+  ATOM_CACHED_APPS_HIGH_WATERMARK = 10189;
+  ATOM_ODREFRESH_REPORTED = 366;
+  ATOM_ODSIGN_REPORTED = 548;
+  ATOM_ART_DATUM_REPORTED = 332;
+  ATOM_ART_DEVICE_DATUM_REPORTED = 550;
+  ATOM_ART_DATUM_DELTA_REPORTED = 565;
+  ATOM_BACKGROUND_DEXOPT_JOB_ENDED = 467;
+  ATOM_WEAR_ADAPTIVE_SUSPEND_STATS_REPORTED = 619;
+  ATOM_WEAR_POWER_ANOMALY_SERVICE_OPERATIONAL_STATS_REPORTED = 620;
+  ATOM_WEAR_POWER_ANOMALY_SERVICE_EVENT_STATS_REPORTED = 621;
+  ATOM_EMERGENCY_STATE_CHANGED = 633;
+  ATOM_DND_STATE_CHANGED = 657;
+  ATOM_MTE_STATE = 10181;
+  ATOM_AD_SERVICES_BACK_COMPAT_GET_TOPICS_REPORTED = 598;
+  ATOM_AD_SERVICES_BACK_COMPAT_EPOCH_COMPUTATION_CLASSIFIER_REPORTED = 599;
+  ATOM_AD_SERVICES_MEASUREMENT_DEBUG_KEYS = 640;
+  ATOM_AD_SERVICES_ERROR_REPORTED = 662;
+  ATOM_AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED = 663;
+  ATOM_AD_SERVICES_MEASUREMENT_DELAYED_SOURCE_REGISTRATION = 673;
+  ATOM_AD_SERVICES_MEASUREMENT_ATTRIBUTION = 674;
+  ATOM_AD_SERVICES_MEASUREMENT_JOBS = 675;
+  ATOM_AD_SERVICES_MEASUREMENT_WIPEOUT = 676;
+  ATOM_AD_SERVICES_CONSENT_MIGRATED = 702;
+  ATOM_RKPD_POOL_STATS = 664;
+  ATOM_RKPD_CLIENT_OPERATION = 665;
+  ATOM_AUTOFILL_UI_EVENT_REPORTED = 603;
+  ATOM_AUTOFILL_FILL_REQUEST_REPORTED = 604;
+  ATOM_AUTOFILL_FILL_RESPONSE_REPORTED = 605;
+  ATOM_AUTOFILL_SAVE_EVENT_REPORTED = 606;
+  ATOM_AUTOFILL_SESSION_COMMITTED = 607;
+  ATOM_AUTOFILL_FIELD_CLASSIFICATION_EVENT_REPORTED = 659;
+  ATOM_TEST_EXTENSION_ATOM_REPORTED = 660;
+  ATOM_TEST_RESTRICTED_ATOM_REPORTED = 672;
+  ATOM_STATS_SOCKET_LOSS_REPORTED = 752;
+  ATOM_PLUGIN_INITIALIZED = 655;
+  ATOM_TV_LOW_POWER_STANDBY_POLICY = 679;
+  ATOM_LOCKSCREEN_SHORTCUT_SELECTED = 611;
+  ATOM_LOCKSCREEN_SHORTCUT_TRIGGERED = 612;
+  ATOM_EMERGENCY_NUMBERS_INFO = 10180;
+  ATOM_QUALIFIED_RAT_LIST_CHANGED = 634;
+  ATOM_QNS_IMS_CALL_DROP_STATS = 635;
+  ATOM_QNS_FALLBACK_RESTRICTION_CHANGED = 636;
+  ATOM_QNS_RAT_PREFERENCE_MISMATCH_INFO = 10177;
+  ATOM_QNS_HANDOVER_TIME_MILLIS = 10178;
+  ATOM_QNS_HANDOVER_PINGPONG = 10179;
+  ATOM_SATELLITE_CONTROLLER = 10182;
+  ATOM_SATELLITE_SESSION = 10183;
+  ATOM_SATELLITE_INCOMING_DATAGRAM = 10184;
+  ATOM_SATELLITE_OUTGOING_DATAGRAM = 10185;
+  ATOM_SATELLITE_PROVISION = 10186;
+  ATOM_SATELLITE_SOS_MESSAGE_RECOMMENDER = 10187;
+  ATOM_IKE_SESSION_TERMINATED = 678;
+  ATOM_IKE_LIVENESS_CHECK_SESSION_VALIDATED = 760;
+  ATOM_BLUETOOTH_HASHED_DEVICE_NAME_REPORTED = 613;
+  ATOM_BLUETOOTH_L2CAP_COC_CLIENT_CONNECTION = 614;
+  ATOM_BLUETOOTH_L2CAP_COC_SERVER_CONNECTION = 615;
+  ATOM_BLUETOOTH_LE_SESSION_CONNECTED = 656;
+  ATOM_RESTRICTED_BLUETOOTH_DEVICE_NAME_REPORTED = 666;
+  ATOM_BLUETOOTH_PROFILE_CONNECTION_ATTEMPTED = 696;
+  ATOM_HEALTH_CONNECT_UI_IMPRESSION = 623;
+  ATOM_HEALTH_CONNECT_UI_INTERACTION = 624;
+  ATOM_HEALTH_CONNECT_APP_OPENED_REPORTED = 625;
+  ATOM_HEALTH_CONNECT_API_CALLED = 616;
+  ATOM_HEALTH_CONNECT_USAGE_STATS = 617;
+  ATOM_HEALTH_CONNECT_STORAGE_STATS = 618;
+  ATOM_HEALTH_CONNECT_API_INVOKED = 643;
+  ATOM_EXERCISE_ROUTE_API_CALLED = 654;
+  ATOM_ATOM_9999 = 9999;
+  ATOM_ATOM_99999 = 99999;
+  ATOM_THREADNETWORK_TELEMETRY_DATA_REPORTED = 738;
+  ATOM_THREADNETWORK_TOPO_ENTRY_REPEATED = 739;
+  ATOM_THREADNETWORK_DEVICE_INFO_REPORTED = 740;
+  ATOM_EMERGENCY_NUMBER_DIALED = 637;
+  ATOM_SANDBOX_API_CALLED = 488;
+  ATOM_SANDBOX_ACTIVITY_EVENT_OCCURRED = 735;
+  ATOM_SANDBOX_SDK_STORAGE = 10159;
+  ATOM_CRONET_ENGINE_CREATED = 703;
+  ATOM_CRONET_TRAFFIC_REPORTED = 704;
+  ATOM_CRONET_ENGINE_BUILDER_INITIALIZED = 762;
+  ATOM_CRONET_HTTP_FLAGS_INITIALIZED = 763;
+  ATOM_CRONET_INITIALIZED = 764;
+  ATOM_DAILY_KEEPALIVE_INFO_REPORTED = 650;
+  ATOM_IP_CLIENT_RA_INFO_REPORTED = 778;
+  ATOM_APF_SESSION_INFO_REPORTED = 777;
+  ATOM_CREDENTIAL_MANAGER_API_CALLED = 585;
+  ATOM_CREDENTIAL_MANAGER_INIT_PHASE_REPORTED = 651;
+  ATOM_CREDENTIAL_MANAGER_CANDIDATE_PHASE_REPORTED = 652;
+  ATOM_CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED = 653;
+  ATOM_CREDENTIAL_MANAGER_TOTAL_REPORTED = 667;
+  ATOM_CREDENTIAL_MANAGER_FINALNOUID_REPORTED = 668;
+  ATOM_CREDENTIAL_MANAGER_GET_REPORTED = 669;
+  ATOM_CREDENTIAL_MANAGER_AUTH_CLICK_REPORTED = 670;
+  ATOM_CREDENTIAL_MANAGER_APIV2_CALLED = 671;
+  ATOM_UWB_ACTIVITY_INFO = 10188;
+  ATOM_MEDIA_ACTION_REPORTED = 608;
+  ATOM_MEDIA_CONTROLS_LAUNCHED = 609;
+  ATOM_MEDIA_CODEC_RECLAIM_REQUEST_COMPLETED = 600;
+  ATOM_MEDIA_CODEC_STARTED = 641;
+  ATOM_MEDIA_CODEC_STOPPED = 642;
+  ATOM_MEDIA_CODEC_RENDERED = 684;
 }
 // End of protos/perfetto/config/statsd/atom_ids.proto
 
@@ -2702,7 +2939,7 @@
 // Begin of protos/perfetto/config/data_source_config.proto
 
 // The configuration that is passed to each data source when starting tracing.
-// Next id: 125
+// Next id: 126
 message DataSourceConfig {
   enum SessionInitiator {
     SESSION_INITIATOR_UNSPECIFIED = 0;
@@ -2838,6 +3075,9 @@
   optional AndroidSdkSyspropGuardConfig android_sdk_sysprop_guard_config = 124
       [lazy = true];
 
+  // Data source name: windows.etw
+  optional EtwConfig etw_config = 125 [lazy = true];
+
   // This is a fallback mechanism to send a free-form text config to the
   // producer. In theory this should never be needed. All the code that
   // is part of the platform (i.e. traced service) is supposed to *not* truncate
diff --git a/protos/perfetto/config/statsd/atom_ids.proto b/protos/perfetto/config/statsd/atom_ids.proto
index fbdd194..c436571 100644
--- a/protos/perfetto/config/statsd/atom_ids.proto
+++ b/protos/perfetto/config/statsd/atom_ids.proto
@@ -349,7 +349,6 @@
   ATOM_PERFETTO_TRIGGER = 329;
   ATOM_TRANSCODING_DATA = 330;
   ATOM_IMS_SERVICE_ENTITLEMENT_UPDATED = 331;
-  ATOM_ART_DATUM_REPORTED = 332;
   ATOM_DEVICE_ROTATED = 333;
   ATOM_SIM_SPECIFIC_SETTINGS_RESTORED = 334;
   ATOM_TEXT_CLASSIFIER_DOWNLOAD_REPORTED = 335;
@@ -379,7 +378,6 @@
   ATOM_LOCATION_TIME_ZONE_PROVIDER_STATE_CHANGED = 359;
   ATOM_FDTRACK_EVENT_OCCURRED = 364;
   ATOM_TIMEOUT_AUTO_EXTENDED_REPORTED = 365;
-  ATOM_ODREFRESH_REPORTED = 366;
   ATOM_ALARM_BATCH_DELIVERED = 367;
   ATOM_ALARM_SCHEDULED = 368;
   ATOM_CAR_WATCHDOG_IO_OVERUSE_STATS_REPORTED = 369;
@@ -447,6 +445,8 @@
   ATOM_HOTWORD_DETECTION_SERVICE_RESTARTED = 432;
   ATOM_HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED = 433;
   ATOM_HOTWORD_DETECTOR_EVENTS = 434;
+  ATOM_AD_SERVICES_API_CALLED = 435;
+  ATOM_AD_SERVICES_MESUREMENT_REPORTS_UPLOADED = 436;
   ATOM_BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED = 437;
   ATOM_CONTACTS_INDEXER_UPDATE_STATS_REPORTED = 440;
   ATOM_APP_BACKGROUND_RESTRICTIONS_INFO = 441;
@@ -468,24 +468,47 @@
   ATOM_ISOLATED_COMPILATION_SCHEDULED = 457;
   ATOM_ISOLATED_COMPILATION_ENDED = 458;
   ATOM_ONS_OPPORTUNISTIC_ESIM_PROVISIONING_COMPLETE = 459;
+  ATOM_SYSTEM_SERVER_PRE_WATCHDOG_OCCURRED = 460;
   ATOM_TELEPHONY_ANOMALY_DETECTED = 461;
   ATOM_LETTERBOX_POSITION_CHANGED = 462;
   ATOM_REMOTE_KEY_PROVISIONING_ATTEMPT = 463;
   ATOM_REMOTE_KEY_PROVISIONING_NETWORK_INFO = 464;
   ATOM_REMOTE_KEY_PROVISIONING_TIMING = 465;
   ATOM_MEDIAOUTPUT_OP_INTERACTION_REPORT = 466;
-  ATOM_BACKGROUND_DEXOPT_JOB_ENDED = 467;
   ATOM_SYNC_EXEMPTION_OCCURRED = 468;
   ATOM_AUTOFILL_PRESENTATION_EVENT_REPORTED = 469;
   ATOM_DOCK_STATE_CHANGED = 470;
+  ATOM_SAFETY_SOURCE_STATE_COLLECTED = 471;
+  ATOM_SAFETY_CENTER_SYSTEM_EVENT_REPORTED = 472;
+  ATOM_SAFETY_CENTER_INTERACTION_REPORTED = 473;
+  ATOM_SETTINGS_PROVIDER_SETTING_CHANGED = 474;
   ATOM_BROADCAST_DELIVERY_EVENT_REPORTED = 475;
   ATOM_SERVICE_REQUEST_EVENT_REPORTED = 476;
   ATOM_PROVIDER_ACQUISITION_EVENT_REPORTED = 477;
   ATOM_BLUETOOTH_DEVICE_NAME_REPORTED = 478;
+  ATOM_CB_CONFIG_UPDATED = 479;
+  ATOM_CB_MODULE_ERROR_REPORTED = 480;
+  ATOM_CB_SERVICE_FEATURE_CHANGED = 481;
+  ATOM_CB_RECEIVER_FEATURE_CHANGED = 482;
+  ATOM_JSSCRIPTENGINE_LATENCY_REPORTED = 483;
+  ATOM_PRIVACY_SIGNAL_NOTIFICATION_INTERACTION = 484;
+  ATOM_PRIVACY_SIGNAL_ISSUE_CARD_INTERACTION = 485;
+  ATOM_PRIVACY_SIGNALS_JOB_FAILURE = 486;
   ATOM_VIBRATION_REPORTED = 487;
   ATOM_UWB_RANGING_START = 489;
+  ATOM_MOBILE_DATA_DOWNLOAD_FILE_GROUP_STATUS_REPORTED = 490;
+  ATOM_APP_COMPACTED_V2 = 491;
+  ATOM_AD_SERVICES_SETTINGS_USAGE_REPORTED = 493;
   ATOM_DISPLAY_BRIGHTNESS_CHANGED = 494;
   ATOM_ACTIVITY_ACTION_BLOCKED = 495;
+  ATOM_BACKGROUND_FETCH_PROCESS_REPORTED = 496;
+  ATOM_UPDATE_CUSTOM_AUDIENCE_PROCESS_REPORTED = 497;
+  ATOM_RUN_AD_BIDDING_PROCESS_REPORTED = 498;
+  ATOM_RUN_AD_SCORING_PROCESS_REPORTED = 499;
+  ATOM_RUN_AD_SELECTION_PROCESS_REPORTED = 500;
+  ATOM_RUN_AD_BIDDING_PER_CA_PROCESS_REPORTED = 501;
+  ATOM_MOBILE_DATA_DOWNLOAD_DOWNLOAD_RESULT_REPORTED = 502;
+  ATOM_MOBILE_DATA_DOWNLOAD_FILE_GROUP_STORAGE_STATS_REPORTED = 503;
   ATOM_NETWORK_DNS_SERVER_SUPPORT_REPORTED = 504;
   ATOM_VM_BOOTED = 505;
   ATOM_VM_EXITED = 506;
@@ -494,13 +517,19 @@
   ATOM_MEDIAMETRICS_SPATIALIZERDEVICEENABLED_REPORTED = 509;
   ATOM_MEDIAMETRICS_HEADTRACKERDEVICEENABLED_REPORTED = 510;
   ATOM_MEDIAMETRICS_HEADTRACKERDEVICESUPPORTED_REPORTED = 511;
+  ATOM_AD_SERVICES_MEASUREMENT_REGISTRATIONS = 512;
   ATOM_HEARING_AID_INFO_REPORTED = 513;
   ATOM_DEVICE_WIDE_JOB_CONSTRAINT_CHANGED = 514;
+  ATOM_AMBIENT_MODE_CHANGED = 515;
+  ATOM_ANR_LATENCY_REPORTED = 516;
+  ATOM_RESOURCE_API_INFO = 517;
+  ATOM_SYSTEM_DEFAULT_NETWORK_CHANGED = 518;
   ATOM_IWLAN_SETUP_DATA_CALL_RESULT_REPORTED = 519;
   ATOM_IWLAN_PDN_DISCONNECTED_REASON_REPORTED = 520;
   ATOM_AIRPLANE_MODE_SESSION_REPORTED = 521;
   ATOM_VM_CPU_STATUS_REPORTED = 522;
   ATOM_VM_MEM_STATUS_REPORTED = 523;
+  ATOM_PACKAGE_INSTALLATION_SESSION_REPORTED = 524;
   ATOM_DEFAULT_NETWORK_REMATCH_INFO = 525;
   ATOM_NETWORK_SELECTION_PERFORMANCE = 526;
   ATOM_NETWORK_NSD_REPORTED = 527;
@@ -510,22 +539,65 @@
   ATOM_BLUETOOTH_LOCAL_SUPPORTED_FEATURES_REPORTED = 532;
   ATOM_BLUETOOTH_GATT_APP_INFO = 533;
   ATOM_BRIGHTNESS_CONFIGURATION_UPDATED = 534;
+  ATOM_AD_SERVICES_GET_TOPICS_REPORTED = 535;
+  ATOM_AD_SERVICES_EPOCH_COMPUTATION_GET_TOP_TOPICS_REPORTED = 536;
+  ATOM_AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED = 537;
+  ATOM_WEAR_MEDIA_OUTPUT_SWITCHER_LAUNCHED = 538;
+  ATOM_WEAR_MEDIA_OUTPUT_SWITCHER_FINISHED = 539;
+  ATOM_WEAR_MEDIA_OUTPUT_SWITCHER_CONNECTION_REPORTED = 540;
+  ATOM_WEAR_MEDIA_OUTPUT_SWITCHER_DEVICE_SCAN_TRIGGERED = 541;
+  ATOM_WEAR_MEDIA_OUTPUT_SWITCHER_FIRST_DEVICE_SCAN_LATENCY = 542;
+  ATOM_WEAR_MEDIA_OUTPUT_SWITCHER_CONNECT_DEVICE_LATENCY = 543;
+  ATOM_PACKAGE_MANAGER_SNAPSHOT_REPORTED = 544;
+  ATOM_PACKAGE_MANAGER_APPS_FILTER_CACHE_BUILD_REPORTED = 545;
+  ATOM_PACKAGE_MANAGER_APPS_FILTER_CACHE_UPDATE_REPORTED = 546;
   ATOM_LAUNCHER_IMPRESSION_EVENT = 547;
-  ATOM_ODSIGN_REPORTED = 548;
-  ATOM_ART_DEVICE_DATUM_REPORTED = 550;
+  ATOM_WEAR_MEDIA_OUTPUT_SWITCHER_ALL_DEVICES_SCAN_LATENCY = 549;
+  ATOM_WS_WATCH_FACE_EDITED = 551;
+  ATOM_WS_WATCH_FACE_FAVORITE_ACTION_REPORTED = 552;
+  ATOM_WS_WATCH_FACE_SET_ACTION_REPORTED = 553;
+  ATOM_PACKAGE_UNINSTALLATION_REPORTED = 554;
+  ATOM_GAME_MODE_CHANGED = 555;
+  ATOM_GAME_MODE_CONFIGURATION_CHANGED = 556;
+  ATOM_BEDTIME_MODE_STATE_CHANGED = 557;
   ATOM_NETWORK_SLICE_SESSION_ENDED = 558;
   ATOM_NETWORK_SLICE_DAILY_DATA_USAGE_REPORTED = 559;
   ATOM_NFC_TAG_TYPE_OCCURRED = 560;
   ATOM_NFC_AID_CONFLICT_OCCURRED = 561;
   ATOM_NFC_READER_CONFLICT_OCCURRED = 562;
-  ATOM_ART_DATUM_DELTA_REPORTED = 565;
+  ATOM_WS_TILE_LIST_CHANGED = 563;
+  ATOM_GET_TYPE_ACCESSED_WITHOUT_PERMISSION = 564;
+  ATOM_MOBILE_BUNDLED_APP_INFO_GATHERED = 566;
+  ATOM_WS_WATCH_FACE_COMPLICATION_SET_CHANGED = 567;
   ATOM_MEDIA_DRM_CREATED = 568;
   ATOM_MEDIA_DRM_ERRORED = 569;
   ATOM_MEDIA_DRM_SESSION_OPENED = 570;
   ATOM_MEDIA_DRM_SESSION_CLOSED = 571;
+  ATOM_USER_SELECTED_RESOLUTION = 572;
+  ATOM_UNSAFE_INTENT_EVENT_REPORTED = 573;
   ATOM_PERFORMANCE_HINT_SESSION_REPORTED = 574;
+  ATOM_MEDIAMETRICS_MIDI_DEVICE_CLOSE_REPORTED = 576;
+  ATOM_BIOMETRIC_TOUCH_REPORTED = 577;
   ATOM_HOTWORD_AUDIO_EGRESS_EVENT_REPORTED = 578;
+  ATOM_APP_SEARCH_SCHEMA_MIGRATION_STATS_REPORTED = 579;
+  ATOM_LOCATION_ENABLED_STATE_CHANGED = 580;
+  ATOM_IME_REQUEST_FINISHED = 581;
+  ATOM_USB_COMPLIANCE_WARNINGS_REPORTED = 582;
+  ATOM_APP_SUPPORTED_LOCALES_CHANGED = 583;
+  ATOM_GRAMMATICAL_INFLECTION_CHANGED = 584;
+  ATOM_MEDIA_PROVIDER_VOLUME_RECOVERY_REPORTED = 586;
+  ATOM_BIOMETRIC_PROPERTIES_COLLECTED = 587;
+  ATOM_KERNEL_WAKEUP_ATTRIBUTED = 588;
+  ATOM_SCREEN_STATE_CHANGED_V2 = 589;
+  ATOM_WS_BACKUP_ACTION_REPORTED = 590;
+  ATOM_WS_RESTORE_ACTION_REPORTED = 591;
+  ATOM_DEVICE_LOG_ACCESS_EVENT_REPORTED = 592;
+  ATOM_MEDIA_SESSION_UPDATED = 594;
+  ATOM_WEAR_OOBE_STATE_CHANGED = 595;
+  ATOM_WS_NOTIFICATION_UPDATED = 596;
   ATOM_NETWORK_VALIDATION_FAILURE_STATS_DAILY_REPORTED = 601;
+  ATOM_WS_COMPLICATION_TAPPED = 602;
+  ATOM_WS_WEAR_TIME_SESSION = 610;
   ATOM_WIFI_BYTES_TRANSFER = 10000;
   ATOM_WIFI_BYTES_TRANSFER_BY_FG_BG = 10001;
   ATOM_MOBILE_BYTES_TRANSFER = 10002;
@@ -678,13 +750,158 @@
   ATOM_TELEPHONY_NETWORK_REQUESTS_V2 = 10153;
   ATOM_DEVICE_TELEPHONY_PROPERTIES = 10154;
   ATOM_REMOTE_KEY_PROVISIONING_ERROR_COUNTS = 10155;
+  ATOM_SAFETY_STATE = 10156;
   ATOM_INCOMING_MMS = 10157;
   ATOM_OUTGOING_MMS = 10158;
   ATOM_MULTI_USER_INFO = 10160;
   ATOM_NETWORK_BPF_MAP_INFO = 10161;
+  ATOM_OUTGOING_SHORT_CODE_SMS = 10162;
   ATOM_CONNECTIVITY_STATE_SAMPLE = 10163;
   ATOM_NETWORK_SELECTION_REMATCH_REASONS_INFO = 10164;
+  ATOM_GAME_MODE_INFO = 10165;
+  ATOM_GAME_MODE_CONFIGURATION = 10166;
+  ATOM_GAME_MODE_LISTENER = 10167;
   ATOM_NETWORK_SLICE_REQUEST_COUNT = 10168;
+  ATOM_WS_TILE_SNAPSHOT = 10169;
+  ATOM_WS_ACTIVE_WATCH_FACE_COMPLICATION_SET_SNAPSHOT = 10170;
+  ATOM_PROCESS_STATE = 10171;
+  ATOM_PROCESS_ASSOCIATION = 10172;
   ATOM_ADPF_SYSTEM_COMPONENT_INFO = 10173;
   ATOM_NOTIFICATION_MEMORY_USE = 10174;
+  ATOM_HDR_CAPABILITIES = 10175;
+  ATOM_WS_FAVOURITE_WATCH_FACE_LIST_SNAPSHOT = 10176;
+  ATOM_WIFI_AWARE_NDP_REPORTED = 638;
+  ATOM_WIFI_AWARE_ATTACH_REPORTED = 639;
+  ATOM_WIFI_SELF_RECOVERY_TRIGGERED = 661;
+  ATOM_SOFT_AP_STARTED = 680;
+  ATOM_SOFT_AP_STOPPED = 681;
+  ATOM_WIFI_LOCK_RELEASED = 687;
+  ATOM_WIFI_LOCK_DEACTIVATED = 688;
+  ATOM_WIFI_CONFIG_SAVED = 689;
+  ATOM_WIFI_AWARE_RESOURCE_USING_CHANGED = 690;
+  ATOM_WIFI_AWARE_HAL_API_CALLED = 691;
+  ATOM_WIFI_LOCAL_ONLY_REQUEST_RECEIVED = 692;
+  ATOM_WIFI_LOCAL_ONLY_REQUEST_SCAN_TRIGGERED = 693;
+  ATOM_WIFI_THREAD_TASK_EXECUTED = 694;
+  ATOM_WIFI_STATE_CHANGED = 700;
+  ATOM_WIFI_AWARE_CAPABILITIES = 10190;
+  ATOM_WIFI_MODULE_INFO = 10193;
+  ATOM_SETTINGS_SPA_REPORTED = 622;
+  ATOM_EXPRESS_EVENT_REPORTED = 528;
+  ATOM_EXPRESS_HISTOGRAM_SAMPLE_REPORTED = 593;
+  ATOM_EXPRESS_UID_EVENT_REPORTED = 644;
+  ATOM_EXPRESS_UID_HISTOGRAM_SAMPLE_REPORTED = 658;
+  ATOM_PERMISSION_RATIONALE_DIALOG_VIEWED = 645;
+  ATOM_PERMISSION_RATIONALE_DIALOG_ACTION_REPORTED = 646;
+  ATOM_APP_DATA_SHARING_UPDATES_NOTIFICATION_INTERACTION = 647;
+  ATOM_APP_DATA_SHARING_UPDATES_FRAGMENT_VIEWED = 648;
+  ATOM_APP_DATA_SHARING_UPDATES_FRAGMENT_ACTION_REPORTED = 649;
+  ATOM_WS_INCOMING_CALL_ACTION_REPORTED = 626;
+  ATOM_WS_CALL_DISCONNECTION_REPORTED = 627;
+  ATOM_WS_CALL_DURATION_REPORTED = 628;
+  ATOM_WS_CALL_USER_EXPERIENCE_LATENCY_REPORTED = 629;
+  ATOM_WS_CALL_INTERACTION_REPORTED = 630;
+  ATOM_FULL_SCREEN_INTENT_LAUNCHED = 631;
+  ATOM_BAL_ALLOWED = 632;
+  ATOM_IN_TASK_ACTIVITY_STARTED = 685;
+  ATOM_CACHED_APPS_HIGH_WATERMARK = 10189;
+  ATOM_ODREFRESH_REPORTED = 366;
+  ATOM_ODSIGN_REPORTED = 548;
+  ATOM_ART_DATUM_REPORTED = 332;
+  ATOM_ART_DEVICE_DATUM_REPORTED = 550;
+  ATOM_ART_DATUM_DELTA_REPORTED = 565;
+  ATOM_BACKGROUND_DEXOPT_JOB_ENDED = 467;
+  ATOM_WEAR_ADAPTIVE_SUSPEND_STATS_REPORTED = 619;
+  ATOM_WEAR_POWER_ANOMALY_SERVICE_OPERATIONAL_STATS_REPORTED = 620;
+  ATOM_WEAR_POWER_ANOMALY_SERVICE_EVENT_STATS_REPORTED = 621;
+  ATOM_EMERGENCY_STATE_CHANGED = 633;
+  ATOM_DND_STATE_CHANGED = 657;
+  ATOM_MTE_STATE = 10181;
+  ATOM_AD_SERVICES_BACK_COMPAT_GET_TOPICS_REPORTED = 598;
+  ATOM_AD_SERVICES_BACK_COMPAT_EPOCH_COMPUTATION_CLASSIFIER_REPORTED = 599;
+  ATOM_AD_SERVICES_MEASUREMENT_DEBUG_KEYS = 640;
+  ATOM_AD_SERVICES_ERROR_REPORTED = 662;
+  ATOM_AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED = 663;
+  ATOM_AD_SERVICES_MEASUREMENT_DELAYED_SOURCE_REGISTRATION = 673;
+  ATOM_AD_SERVICES_MEASUREMENT_ATTRIBUTION = 674;
+  ATOM_AD_SERVICES_MEASUREMENT_JOBS = 675;
+  ATOM_AD_SERVICES_MEASUREMENT_WIPEOUT = 676;
+  ATOM_AD_SERVICES_CONSENT_MIGRATED = 702;
+  ATOM_RKPD_POOL_STATS = 664;
+  ATOM_RKPD_CLIENT_OPERATION = 665;
+  ATOM_AUTOFILL_UI_EVENT_REPORTED = 603;
+  ATOM_AUTOFILL_FILL_REQUEST_REPORTED = 604;
+  ATOM_AUTOFILL_FILL_RESPONSE_REPORTED = 605;
+  ATOM_AUTOFILL_SAVE_EVENT_REPORTED = 606;
+  ATOM_AUTOFILL_SESSION_COMMITTED = 607;
+  ATOM_AUTOFILL_FIELD_CLASSIFICATION_EVENT_REPORTED = 659;
+  ATOM_TEST_EXTENSION_ATOM_REPORTED = 660;
+  ATOM_TEST_RESTRICTED_ATOM_REPORTED = 672;
+  ATOM_STATS_SOCKET_LOSS_REPORTED = 752;
+  ATOM_PLUGIN_INITIALIZED = 655;
+  ATOM_TV_LOW_POWER_STANDBY_POLICY = 679;
+  ATOM_LOCKSCREEN_SHORTCUT_SELECTED = 611;
+  ATOM_LOCKSCREEN_SHORTCUT_TRIGGERED = 612;
+  ATOM_EMERGENCY_NUMBERS_INFO = 10180;
+  ATOM_QUALIFIED_RAT_LIST_CHANGED = 634;
+  ATOM_QNS_IMS_CALL_DROP_STATS = 635;
+  ATOM_QNS_FALLBACK_RESTRICTION_CHANGED = 636;
+  ATOM_QNS_RAT_PREFERENCE_MISMATCH_INFO = 10177;
+  ATOM_QNS_HANDOVER_TIME_MILLIS = 10178;
+  ATOM_QNS_HANDOVER_PINGPONG = 10179;
+  ATOM_SATELLITE_CONTROLLER = 10182;
+  ATOM_SATELLITE_SESSION = 10183;
+  ATOM_SATELLITE_INCOMING_DATAGRAM = 10184;
+  ATOM_SATELLITE_OUTGOING_DATAGRAM = 10185;
+  ATOM_SATELLITE_PROVISION = 10186;
+  ATOM_SATELLITE_SOS_MESSAGE_RECOMMENDER = 10187;
+  ATOM_IKE_SESSION_TERMINATED = 678;
+  ATOM_IKE_LIVENESS_CHECK_SESSION_VALIDATED = 760;
+  ATOM_BLUETOOTH_HASHED_DEVICE_NAME_REPORTED = 613;
+  ATOM_BLUETOOTH_L2CAP_COC_CLIENT_CONNECTION = 614;
+  ATOM_BLUETOOTH_L2CAP_COC_SERVER_CONNECTION = 615;
+  ATOM_BLUETOOTH_LE_SESSION_CONNECTED = 656;
+  ATOM_RESTRICTED_BLUETOOTH_DEVICE_NAME_REPORTED = 666;
+  ATOM_BLUETOOTH_PROFILE_CONNECTION_ATTEMPTED = 696;
+  ATOM_HEALTH_CONNECT_UI_IMPRESSION = 623;
+  ATOM_HEALTH_CONNECT_UI_INTERACTION = 624;
+  ATOM_HEALTH_CONNECT_APP_OPENED_REPORTED = 625;
+  ATOM_HEALTH_CONNECT_API_CALLED = 616;
+  ATOM_HEALTH_CONNECT_USAGE_STATS = 617;
+  ATOM_HEALTH_CONNECT_STORAGE_STATS = 618;
+  ATOM_HEALTH_CONNECT_API_INVOKED = 643;
+  ATOM_EXERCISE_ROUTE_API_CALLED = 654;
+  ATOM_ATOM_9999 = 9999;
+  ATOM_ATOM_99999 = 99999;
+  ATOM_THREADNETWORK_TELEMETRY_DATA_REPORTED = 738;
+  ATOM_THREADNETWORK_TOPO_ENTRY_REPEATED = 739;
+  ATOM_THREADNETWORK_DEVICE_INFO_REPORTED = 740;
+  ATOM_EMERGENCY_NUMBER_DIALED = 637;
+  ATOM_SANDBOX_API_CALLED = 488;
+  ATOM_SANDBOX_ACTIVITY_EVENT_OCCURRED = 735;
+  ATOM_SANDBOX_SDK_STORAGE = 10159;
+  ATOM_CRONET_ENGINE_CREATED = 703;
+  ATOM_CRONET_TRAFFIC_REPORTED = 704;
+  ATOM_CRONET_ENGINE_BUILDER_INITIALIZED = 762;
+  ATOM_CRONET_HTTP_FLAGS_INITIALIZED = 763;
+  ATOM_CRONET_INITIALIZED = 764;
+  ATOM_DAILY_KEEPALIVE_INFO_REPORTED = 650;
+  ATOM_IP_CLIENT_RA_INFO_REPORTED = 778;
+  ATOM_APF_SESSION_INFO_REPORTED = 777;
+  ATOM_CREDENTIAL_MANAGER_API_CALLED = 585;
+  ATOM_CREDENTIAL_MANAGER_INIT_PHASE_REPORTED = 651;
+  ATOM_CREDENTIAL_MANAGER_CANDIDATE_PHASE_REPORTED = 652;
+  ATOM_CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED = 653;
+  ATOM_CREDENTIAL_MANAGER_TOTAL_REPORTED = 667;
+  ATOM_CREDENTIAL_MANAGER_FINALNOUID_REPORTED = 668;
+  ATOM_CREDENTIAL_MANAGER_GET_REPORTED = 669;
+  ATOM_CREDENTIAL_MANAGER_AUTH_CLICK_REPORTED = 670;
+  ATOM_CREDENTIAL_MANAGER_APIV2_CALLED = 671;
+  ATOM_UWB_ACTIVITY_INFO = 10188;
+  ATOM_MEDIA_ACTION_REPORTED = 608;
+  ATOM_MEDIA_CONTROLS_LAUNCHED = 609;
+  ATOM_MEDIA_CODEC_RECLAIM_REQUEST_COMPLETED = 600;
+  ATOM_MEDIA_CODEC_STARTED = 641;
+  ATOM_MEDIA_CODEC_STOPPED = 642;
+  ATOM_MEDIA_CODEC_RENDERED = 684;
 }
\ No newline at end of file
diff --git a/protos/perfetto/ipc/wire_protocol.proto b/protos/perfetto/ipc/wire_protocol.proto
index 8117316..e01a798 100644
--- a/protos/perfetto/ipc/wire_protocol.proto
+++ b/protos/perfetto/ipc/wire_protocol.proto
@@ -72,8 +72,16 @@
   // endpoont of the connection. for AF_UNIX sockets, this is ignored and traced
   // uses instead the SO_PEERCRED.
   message SetPeerIdentity {
+    // The UID and PID of the producer process.
     optional int32 pid = 1;
     optional int32 uid = 2;
+
+    // The hint for the tracing service to infer the machine ID. This field
+    // should satisfy the requriement that different machines should have
+    // different values. In practice, this filed contains the Linux kernel
+    // boot_id, or a hash of kernel bootup timestamp and uname(2) if boot_id
+    // isn't available.
+    optional string machine_id_hint = 3;
   }
 
   // The client is expected to send requests with monotonically increasing
diff --git a/protos/perfetto/trace/android/BUILD.gn b/protos/perfetto/trace/android/BUILD.gn
index c5e842f..509c448 100644
--- a/protos/perfetto/trace/android/BUILD.gn
+++ b/protos/perfetto/trace/android/BUILD.gn
@@ -28,6 +28,7 @@
     "initial_display_state.proto",
     "network_trace.proto",
     "packages_list.proto",
+    "shell_transition.proto",
     "surfaceflinger_common.proto",
     "surfaceflinger_layers.proto",
     "surfaceflinger_transactions.proto",
@@ -37,6 +38,7 @@
 perfetto_proto_library("winscope_deps") {
   proto_generators = [ "source_set" ]
   sources = [
+    "shell_transition.proto",
     "surfaceflinger_common.proto",
     "surfaceflinger_layers.proto",
     "surfaceflinger_transactions.proto",
diff --git a/protos/perfetto/trace/android/frame_timeline_event.proto b/protos/perfetto/trace/android/frame_timeline_event.proto
index f1f0df8..db6ed90 100644
--- a/protos/perfetto/trace/android/frame_timeline_event.proto
+++ b/protos/perfetto/trace/android/frame_timeline_event.proto
@@ -45,6 +45,14 @@
     JANK_DROPPED = 1024;
   };
 
+  // Specifies the severity of a jank.
+  enum JankSeverityType {
+    SEVERITY_UNKNOWN = 0;
+    SEVERITY_NONE = 1;
+    SEVERITY_PARTIAL = 2;
+    SEVERITY_FULL = 3;
+  }
+
   // Specifies how a frame was presented on screen w.r.t. timing.
   // Can be different for SurfaceFrame and DisplayFrame.
   enum PresentType {
@@ -121,6 +129,7 @@
     optional int32 jank_type = 9;
     optional PredictionType prediction_type = 10;
     optional bool is_buffer = 11;
+    optional JankSeverityType jank_severity_type = 12;
   };
 
   // Indicates the start of expected timeline slice for DisplayFrames.
@@ -166,6 +175,7 @@
     // frame.
     optional int32 jank_type = 7;
     optional PredictionType prediction_type = 8;
+    optional JankSeverityType jank_severity_type = 9;
   };
 
   // FrameEnd just sends the cookie to indicate that the corresponding
diff --git a/protos/perfetto/trace/android/shell_transition.proto b/protos/perfetto/trace/android/shell_transition.proto
new file mode 100644
index 0000000..9a34d3a
--- /dev/null
+++ b/protos/perfetto/trace/android/shell_transition.proto
@@ -0,0 +1,113 @@
+/*
+ * 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;
+
+// ShellTransition messages record information about the shell transitions in
+// the system. This is used to track the animations that are created and execute
+// through the shell transition system.
+message ShellTransition {
+  // The unique identifier of the transition.
+  optional int32 id = 1;
+
+  // The time the transition was created on the WM side
+  // (using SystemClock.elapsedRealtimeNanos())
+  optional int64 create_time_ns = 2;
+  // The time the transition was sent from the WM side to shell
+  // (using SystemClock.elapsedRealtimeNanos())
+  optional int64 send_time_ns = 3;
+  // The time the transition was dispatched by shell to execute
+  // (using SystemClock.elapsedRealtimeNanos())
+  optional int64 dispatch_time_ns = 4;
+  // If the transition merge was accepted by the transition handler, this
+  // contains the time the transition was merged into transition with id
+  // `merge_target`.
+  // (using SystemClock.elapsedRealtimeNanos())
+  optional int64 merge_time_ns = 5;
+  // The time shell proposed the transition should be merged to the transition
+  // handler into transition with id `merge_target`.
+  // (using SystemClock.elapsedRealtimeNanos()).
+  optional int64 merge_request_time_ns = 6;
+  // If the transition was aborted on the shell side, this is the time that
+  // occured.
+  // (using SystemClock.elapsedRealtimeNanos())
+  optional int64 shell_abort_time_ns = 7;
+  // If the transition was aborted on the wm side, this is the time that
+  // occured.
+  // (using SystemClock.elapsedRealtimeNanos())
+  optional int64 wm_abort_time_ns = 8;
+  // The time WM considers the transition to be complete.
+  // (using SystemClock.elapsedRealtimeNanos())
+  optional int64 finish_time_ns = 9;
+
+  // The id of the transaction that WM proposed to use as the starting
+  // transaction. It contains all the layer changes required to setup the
+  // transition and should be executed right at the start of the transition
+  // by the transition handler.
+  optional uint64 start_transaction_id = 10;
+  // The if of the transaction that WM proposed to use as the finish
+  // transaction. It contains all the layer changes required to set the final
+  // state of the transition.
+  optional uint64 finish_transaction_id = 11;
+
+  // The id of the handler that executed the transition. A HandlerMappings
+  // message in the trace will contain the mapping of id to a string
+  // representation of the handler.
+  optional int32 handler = 12;
+  // The transition type of this transition (e.g. TO_FRONT, OPEN, CLOSE).
+  optional int32 type = 13;
+
+  // The list of targets that are part of this transition.
+  repeated Target targets = 14;
+  // The id of the transition we have requested to merge or have merged this
+  // transition into.
+  optional int32 merge_target = 15;
+
+  // The flags set on this transition.
+  optional int32 flags = 16;
+  // The time the starting window was removed. Tracked because this can
+  // happen after the transition finishes, but the app may not yet be visible
+  // until the starting window is removed. So in a sense the transition is not
+  // finished until the starting window is removed. (b/284302118)
+  // (using SystemClock.elapsedRealtimeNanos())
+  optional int64 starting_window_remove_time_ns = 17;
+
+  // Contains the information about the windows targeted in a transition.
+  message Target {
+    // The transition mode of this target (e.g. TO_FRONT, CLOSE...)
+    optional int32 mode = 1;
+    // The layer id of this target.
+    optional int32 layer_id = 2;
+    // The window id of this target.
+    optional int32 window_id = 3;
+    // The flags set on this target.
+    optional int32 flags = 4;
+  }
+}
+
+// Contains mappings from handler ids to string representation of the handlers.
+message ShellHandlerMappings {
+  repeated ShellHandlerMapping mapping = 1;
+}
+
+message ShellHandlerMapping {
+  // The id of the handler used in the ShellTransition message.
+  optional int32 id = 1;
+  // A human readable and meaningful string representation of the handler.
+  optional string name = 2;
+}
diff --git a/protos/perfetto/trace/android/winscope.proto b/protos/perfetto/trace/android/winscope.proto
index 78fbdcc..296e3ee 100644
--- a/protos/perfetto/trace/android/winscope.proto
+++ b/protos/perfetto/trace/android/winscope.proto
@@ -18,6 +18,7 @@
 
 package perfetto.protos;
 
+import "protos/perfetto/trace/android/shell_transition.proto";
 import "protos/perfetto/trace/android/surfaceflinger_layers.proto";
 import "protos/perfetto/trace/android/surfaceflinger_transactions.proto";
 
@@ -26,4 +27,5 @@
 message WinscopeTraceData {
   optional LayersSnapshotProto layers_snapshot = 1;
   optional TransactionTraceEntry transactions = 2;
+  optional ShellTransition shell_transition = 3;
 }
diff --git a/protos/perfetto/trace/etw/etw_event.proto b/protos/perfetto/trace/etw/etw_event.proto
index 9ef1b4e..c52ad6c 100644
--- a/protos/perfetto/trace/etw/etw_event.proto
+++ b/protos/perfetto/trace/etw/etw_event.proto
@@ -21,6 +21,7 @@
 
 message EtwTraceEvent {
   optional uint64 timestamp = 1;
+  optional uint32 cpu = 4;
 
   oneof event {
     CSwitchEtwEvent c_switch = 2;
diff --git a/protos/perfetto/trace/ftrace/all_protos.gni b/protos/perfetto/trace/ftrace/all_protos.gni
index bd055ab..2237aef 100644
--- a/protos/perfetto/trace/ftrace/all_protos.gni
+++ b/protos/perfetto/trace/ftrace/all_protos.gni
@@ -57,6 +57,7 @@
   "net.proto",
   "oom.proto",
   "panel.proto",
+  "perf_trace_counters.proto",
   "power.proto",
   "printk.proto",
   "raw_syscalls.proto",
diff --git a/protos/perfetto/trace/ftrace/ftrace_event.proto b/protos/perfetto/trace/ftrace/ftrace_event.proto
index a068c9d..f7e314b 100644
--- a/protos/perfetto/trace/ftrace/ftrace_event.proto
+++ b/protos/perfetto/trace/ftrace/ftrace_event.proto
@@ -57,6 +57,7 @@
 import "protos/perfetto/trace/ftrace/net.proto";
 import "protos/perfetto/trace/ftrace/oom.proto";
 import "protos/perfetto/trace/ftrace/panel.proto";
+import "protos/perfetto/trace/ftrace/perf_trace_counters.proto";
 import "protos/perfetto/trace/ftrace/power.proto";
 import "protos/perfetto/trace/ftrace/printk.proto";
 import "protos/perfetto/trace/ftrace/raw_syscalls.proto";
@@ -601,5 +602,6 @@
     SamsungTracingMarkWriteFtraceEvent samsung_tracing_mark_write = 484;
     BinderCommandFtraceEvent binder_command = 485;
     BinderReturnFtraceEvent binder_return = 486;
+    SchedSwitchWithCtrsFtraceEvent sched_switch_with_ctrs = 487;
   }
 }
diff --git a/protos/perfetto/trace/ftrace/perf_trace_counters.proto b/protos/perfetto/trace/ftrace/perf_trace_counters.proto
new file mode 100644
index 0000000..0e3531c
--- /dev/null
+++ b/protos/perfetto/trace/ftrace/perf_trace_counters.proto
@@ -0,0 +1,26 @@
+// Autogenerated by:
+// ../../src/tools/ftrace_proto_gen/ftrace_proto_gen.cc
+// Do not edit.
+
+syntax = "proto2";
+package perfetto.protos;
+
+message SchedSwitchWithCtrsFtraceEvent {
+  optional int32 old_pid = 1;
+  optional int32 new_pid = 2;
+  optional uint32 cctr = 3;
+  optional uint32 ctr0 = 4;
+  optional uint32 ctr1 = 5;
+  optional uint32 ctr2 = 6;
+  optional uint32 ctr3 = 7;
+  optional uint32 lctr0 = 8;
+  optional uint32 lctr1 = 9;
+  optional uint32 ctr4 = 10;
+  optional uint32 ctr5 = 11;
+  optional string prev_comm = 12;
+  optional int32 prev_pid = 13;
+  optional uint32 cyc = 14;
+  optional uint32 inst = 15;
+  optional uint32 stallbm = 16;
+  optional uint32 l3dm = 17;
+}
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index 2ae554d..8cfa67e 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -645,6 +645,26 @@
 
 // End of protos/perfetto/config/chrome/chrome_config.proto
 
+// Begin of protos/perfetto/config/etw/etw_config.proto
+
+// Proto definition based on the struct _EVENT_TRACE_PROPERTIES definition
+// See: https://learn.microsoft.com/en-us/windows/win32/api/evntrace/
+// ns-evntrace-event_trace_properties
+message EtwConfig {
+  // The KernelFlag represent list of kernel flags that we are intrested in.
+  // To get a more extensive list run 'xperf -providers k'.
+  enum KernelFlag {
+    C_SWITCH = 0;
+    IDLE_STATES = 1;
+  }
+
+  // The kernel_flags determines the flags that will be used by the etw tracing
+  // session. These kernel flags have been built to expose the useful events
+  // captured from the kernel mode only.
+  repeated KernelFlag kernel_flags = 1;
+}
+// End of protos/perfetto/config/etw/etw_config.proto
+
 // Begin of protos/perfetto/config/ftrace/ftrace_config.proto
 
 // Next id: 26.
@@ -1950,7 +1970,6 @@
   ATOM_PERFETTO_TRIGGER = 329;
   ATOM_TRANSCODING_DATA = 330;
   ATOM_IMS_SERVICE_ENTITLEMENT_UPDATED = 331;
-  ATOM_ART_DATUM_REPORTED = 332;
   ATOM_DEVICE_ROTATED = 333;
   ATOM_SIM_SPECIFIC_SETTINGS_RESTORED = 334;
   ATOM_TEXT_CLASSIFIER_DOWNLOAD_REPORTED = 335;
@@ -1980,7 +1999,6 @@
   ATOM_LOCATION_TIME_ZONE_PROVIDER_STATE_CHANGED = 359;
   ATOM_FDTRACK_EVENT_OCCURRED = 364;
   ATOM_TIMEOUT_AUTO_EXTENDED_REPORTED = 365;
-  ATOM_ODREFRESH_REPORTED = 366;
   ATOM_ALARM_BATCH_DELIVERED = 367;
   ATOM_ALARM_SCHEDULED = 368;
   ATOM_CAR_WATCHDOG_IO_OVERUSE_STATS_REPORTED = 369;
@@ -2048,6 +2066,8 @@
   ATOM_HOTWORD_DETECTION_SERVICE_RESTARTED = 432;
   ATOM_HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED = 433;
   ATOM_HOTWORD_DETECTOR_EVENTS = 434;
+  ATOM_AD_SERVICES_API_CALLED = 435;
+  ATOM_AD_SERVICES_MESUREMENT_REPORTS_UPLOADED = 436;
   ATOM_BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED = 437;
   ATOM_CONTACTS_INDEXER_UPDATE_STATS_REPORTED = 440;
   ATOM_APP_BACKGROUND_RESTRICTIONS_INFO = 441;
@@ -2069,24 +2089,47 @@
   ATOM_ISOLATED_COMPILATION_SCHEDULED = 457;
   ATOM_ISOLATED_COMPILATION_ENDED = 458;
   ATOM_ONS_OPPORTUNISTIC_ESIM_PROVISIONING_COMPLETE = 459;
+  ATOM_SYSTEM_SERVER_PRE_WATCHDOG_OCCURRED = 460;
   ATOM_TELEPHONY_ANOMALY_DETECTED = 461;
   ATOM_LETTERBOX_POSITION_CHANGED = 462;
   ATOM_REMOTE_KEY_PROVISIONING_ATTEMPT = 463;
   ATOM_REMOTE_KEY_PROVISIONING_NETWORK_INFO = 464;
   ATOM_REMOTE_KEY_PROVISIONING_TIMING = 465;
   ATOM_MEDIAOUTPUT_OP_INTERACTION_REPORT = 466;
-  ATOM_BACKGROUND_DEXOPT_JOB_ENDED = 467;
   ATOM_SYNC_EXEMPTION_OCCURRED = 468;
   ATOM_AUTOFILL_PRESENTATION_EVENT_REPORTED = 469;
   ATOM_DOCK_STATE_CHANGED = 470;
+  ATOM_SAFETY_SOURCE_STATE_COLLECTED = 471;
+  ATOM_SAFETY_CENTER_SYSTEM_EVENT_REPORTED = 472;
+  ATOM_SAFETY_CENTER_INTERACTION_REPORTED = 473;
+  ATOM_SETTINGS_PROVIDER_SETTING_CHANGED = 474;
   ATOM_BROADCAST_DELIVERY_EVENT_REPORTED = 475;
   ATOM_SERVICE_REQUEST_EVENT_REPORTED = 476;
   ATOM_PROVIDER_ACQUISITION_EVENT_REPORTED = 477;
   ATOM_BLUETOOTH_DEVICE_NAME_REPORTED = 478;
+  ATOM_CB_CONFIG_UPDATED = 479;
+  ATOM_CB_MODULE_ERROR_REPORTED = 480;
+  ATOM_CB_SERVICE_FEATURE_CHANGED = 481;
+  ATOM_CB_RECEIVER_FEATURE_CHANGED = 482;
+  ATOM_JSSCRIPTENGINE_LATENCY_REPORTED = 483;
+  ATOM_PRIVACY_SIGNAL_NOTIFICATION_INTERACTION = 484;
+  ATOM_PRIVACY_SIGNAL_ISSUE_CARD_INTERACTION = 485;
+  ATOM_PRIVACY_SIGNALS_JOB_FAILURE = 486;
   ATOM_VIBRATION_REPORTED = 487;
   ATOM_UWB_RANGING_START = 489;
+  ATOM_MOBILE_DATA_DOWNLOAD_FILE_GROUP_STATUS_REPORTED = 490;
+  ATOM_APP_COMPACTED_V2 = 491;
+  ATOM_AD_SERVICES_SETTINGS_USAGE_REPORTED = 493;
   ATOM_DISPLAY_BRIGHTNESS_CHANGED = 494;
   ATOM_ACTIVITY_ACTION_BLOCKED = 495;
+  ATOM_BACKGROUND_FETCH_PROCESS_REPORTED = 496;
+  ATOM_UPDATE_CUSTOM_AUDIENCE_PROCESS_REPORTED = 497;
+  ATOM_RUN_AD_BIDDING_PROCESS_REPORTED = 498;
+  ATOM_RUN_AD_SCORING_PROCESS_REPORTED = 499;
+  ATOM_RUN_AD_SELECTION_PROCESS_REPORTED = 500;
+  ATOM_RUN_AD_BIDDING_PER_CA_PROCESS_REPORTED = 501;
+  ATOM_MOBILE_DATA_DOWNLOAD_DOWNLOAD_RESULT_REPORTED = 502;
+  ATOM_MOBILE_DATA_DOWNLOAD_FILE_GROUP_STORAGE_STATS_REPORTED = 503;
   ATOM_NETWORK_DNS_SERVER_SUPPORT_REPORTED = 504;
   ATOM_VM_BOOTED = 505;
   ATOM_VM_EXITED = 506;
@@ -2095,13 +2138,19 @@
   ATOM_MEDIAMETRICS_SPATIALIZERDEVICEENABLED_REPORTED = 509;
   ATOM_MEDIAMETRICS_HEADTRACKERDEVICEENABLED_REPORTED = 510;
   ATOM_MEDIAMETRICS_HEADTRACKERDEVICESUPPORTED_REPORTED = 511;
+  ATOM_AD_SERVICES_MEASUREMENT_REGISTRATIONS = 512;
   ATOM_HEARING_AID_INFO_REPORTED = 513;
   ATOM_DEVICE_WIDE_JOB_CONSTRAINT_CHANGED = 514;
+  ATOM_AMBIENT_MODE_CHANGED = 515;
+  ATOM_ANR_LATENCY_REPORTED = 516;
+  ATOM_RESOURCE_API_INFO = 517;
+  ATOM_SYSTEM_DEFAULT_NETWORK_CHANGED = 518;
   ATOM_IWLAN_SETUP_DATA_CALL_RESULT_REPORTED = 519;
   ATOM_IWLAN_PDN_DISCONNECTED_REASON_REPORTED = 520;
   ATOM_AIRPLANE_MODE_SESSION_REPORTED = 521;
   ATOM_VM_CPU_STATUS_REPORTED = 522;
   ATOM_VM_MEM_STATUS_REPORTED = 523;
+  ATOM_PACKAGE_INSTALLATION_SESSION_REPORTED = 524;
   ATOM_DEFAULT_NETWORK_REMATCH_INFO = 525;
   ATOM_NETWORK_SELECTION_PERFORMANCE = 526;
   ATOM_NETWORK_NSD_REPORTED = 527;
@@ -2111,22 +2160,65 @@
   ATOM_BLUETOOTH_LOCAL_SUPPORTED_FEATURES_REPORTED = 532;
   ATOM_BLUETOOTH_GATT_APP_INFO = 533;
   ATOM_BRIGHTNESS_CONFIGURATION_UPDATED = 534;
+  ATOM_AD_SERVICES_GET_TOPICS_REPORTED = 535;
+  ATOM_AD_SERVICES_EPOCH_COMPUTATION_GET_TOP_TOPICS_REPORTED = 536;
+  ATOM_AD_SERVICES_EPOCH_COMPUTATION_CLASSIFIER_REPORTED = 537;
+  ATOM_WEAR_MEDIA_OUTPUT_SWITCHER_LAUNCHED = 538;
+  ATOM_WEAR_MEDIA_OUTPUT_SWITCHER_FINISHED = 539;
+  ATOM_WEAR_MEDIA_OUTPUT_SWITCHER_CONNECTION_REPORTED = 540;
+  ATOM_WEAR_MEDIA_OUTPUT_SWITCHER_DEVICE_SCAN_TRIGGERED = 541;
+  ATOM_WEAR_MEDIA_OUTPUT_SWITCHER_FIRST_DEVICE_SCAN_LATENCY = 542;
+  ATOM_WEAR_MEDIA_OUTPUT_SWITCHER_CONNECT_DEVICE_LATENCY = 543;
+  ATOM_PACKAGE_MANAGER_SNAPSHOT_REPORTED = 544;
+  ATOM_PACKAGE_MANAGER_APPS_FILTER_CACHE_BUILD_REPORTED = 545;
+  ATOM_PACKAGE_MANAGER_APPS_FILTER_CACHE_UPDATE_REPORTED = 546;
   ATOM_LAUNCHER_IMPRESSION_EVENT = 547;
-  ATOM_ODSIGN_REPORTED = 548;
-  ATOM_ART_DEVICE_DATUM_REPORTED = 550;
+  ATOM_WEAR_MEDIA_OUTPUT_SWITCHER_ALL_DEVICES_SCAN_LATENCY = 549;
+  ATOM_WS_WATCH_FACE_EDITED = 551;
+  ATOM_WS_WATCH_FACE_FAVORITE_ACTION_REPORTED = 552;
+  ATOM_WS_WATCH_FACE_SET_ACTION_REPORTED = 553;
+  ATOM_PACKAGE_UNINSTALLATION_REPORTED = 554;
+  ATOM_GAME_MODE_CHANGED = 555;
+  ATOM_GAME_MODE_CONFIGURATION_CHANGED = 556;
+  ATOM_BEDTIME_MODE_STATE_CHANGED = 557;
   ATOM_NETWORK_SLICE_SESSION_ENDED = 558;
   ATOM_NETWORK_SLICE_DAILY_DATA_USAGE_REPORTED = 559;
   ATOM_NFC_TAG_TYPE_OCCURRED = 560;
   ATOM_NFC_AID_CONFLICT_OCCURRED = 561;
   ATOM_NFC_READER_CONFLICT_OCCURRED = 562;
-  ATOM_ART_DATUM_DELTA_REPORTED = 565;
+  ATOM_WS_TILE_LIST_CHANGED = 563;
+  ATOM_GET_TYPE_ACCESSED_WITHOUT_PERMISSION = 564;
+  ATOM_MOBILE_BUNDLED_APP_INFO_GATHERED = 566;
+  ATOM_WS_WATCH_FACE_COMPLICATION_SET_CHANGED = 567;
   ATOM_MEDIA_DRM_CREATED = 568;
   ATOM_MEDIA_DRM_ERRORED = 569;
   ATOM_MEDIA_DRM_SESSION_OPENED = 570;
   ATOM_MEDIA_DRM_SESSION_CLOSED = 571;
+  ATOM_USER_SELECTED_RESOLUTION = 572;
+  ATOM_UNSAFE_INTENT_EVENT_REPORTED = 573;
   ATOM_PERFORMANCE_HINT_SESSION_REPORTED = 574;
+  ATOM_MEDIAMETRICS_MIDI_DEVICE_CLOSE_REPORTED = 576;
+  ATOM_BIOMETRIC_TOUCH_REPORTED = 577;
   ATOM_HOTWORD_AUDIO_EGRESS_EVENT_REPORTED = 578;
+  ATOM_APP_SEARCH_SCHEMA_MIGRATION_STATS_REPORTED = 579;
+  ATOM_LOCATION_ENABLED_STATE_CHANGED = 580;
+  ATOM_IME_REQUEST_FINISHED = 581;
+  ATOM_USB_COMPLIANCE_WARNINGS_REPORTED = 582;
+  ATOM_APP_SUPPORTED_LOCALES_CHANGED = 583;
+  ATOM_GRAMMATICAL_INFLECTION_CHANGED = 584;
+  ATOM_MEDIA_PROVIDER_VOLUME_RECOVERY_REPORTED = 586;
+  ATOM_BIOMETRIC_PROPERTIES_COLLECTED = 587;
+  ATOM_KERNEL_WAKEUP_ATTRIBUTED = 588;
+  ATOM_SCREEN_STATE_CHANGED_V2 = 589;
+  ATOM_WS_BACKUP_ACTION_REPORTED = 590;
+  ATOM_WS_RESTORE_ACTION_REPORTED = 591;
+  ATOM_DEVICE_LOG_ACCESS_EVENT_REPORTED = 592;
+  ATOM_MEDIA_SESSION_UPDATED = 594;
+  ATOM_WEAR_OOBE_STATE_CHANGED = 595;
+  ATOM_WS_NOTIFICATION_UPDATED = 596;
   ATOM_NETWORK_VALIDATION_FAILURE_STATS_DAILY_REPORTED = 601;
+  ATOM_WS_COMPLICATION_TAPPED = 602;
+  ATOM_WS_WEAR_TIME_SESSION = 610;
   ATOM_WIFI_BYTES_TRANSFER = 10000;
   ATOM_WIFI_BYTES_TRANSFER_BY_FG_BG = 10001;
   ATOM_MOBILE_BYTES_TRANSFER = 10002;
@@ -2279,15 +2371,160 @@
   ATOM_TELEPHONY_NETWORK_REQUESTS_V2 = 10153;
   ATOM_DEVICE_TELEPHONY_PROPERTIES = 10154;
   ATOM_REMOTE_KEY_PROVISIONING_ERROR_COUNTS = 10155;
+  ATOM_SAFETY_STATE = 10156;
   ATOM_INCOMING_MMS = 10157;
   ATOM_OUTGOING_MMS = 10158;
   ATOM_MULTI_USER_INFO = 10160;
   ATOM_NETWORK_BPF_MAP_INFO = 10161;
+  ATOM_OUTGOING_SHORT_CODE_SMS = 10162;
   ATOM_CONNECTIVITY_STATE_SAMPLE = 10163;
   ATOM_NETWORK_SELECTION_REMATCH_REASONS_INFO = 10164;
+  ATOM_GAME_MODE_INFO = 10165;
+  ATOM_GAME_MODE_CONFIGURATION = 10166;
+  ATOM_GAME_MODE_LISTENER = 10167;
   ATOM_NETWORK_SLICE_REQUEST_COUNT = 10168;
+  ATOM_WS_TILE_SNAPSHOT = 10169;
+  ATOM_WS_ACTIVE_WATCH_FACE_COMPLICATION_SET_SNAPSHOT = 10170;
+  ATOM_PROCESS_STATE = 10171;
+  ATOM_PROCESS_ASSOCIATION = 10172;
   ATOM_ADPF_SYSTEM_COMPONENT_INFO = 10173;
   ATOM_NOTIFICATION_MEMORY_USE = 10174;
+  ATOM_HDR_CAPABILITIES = 10175;
+  ATOM_WS_FAVOURITE_WATCH_FACE_LIST_SNAPSHOT = 10176;
+  ATOM_WIFI_AWARE_NDP_REPORTED = 638;
+  ATOM_WIFI_AWARE_ATTACH_REPORTED = 639;
+  ATOM_WIFI_SELF_RECOVERY_TRIGGERED = 661;
+  ATOM_SOFT_AP_STARTED = 680;
+  ATOM_SOFT_AP_STOPPED = 681;
+  ATOM_WIFI_LOCK_RELEASED = 687;
+  ATOM_WIFI_LOCK_DEACTIVATED = 688;
+  ATOM_WIFI_CONFIG_SAVED = 689;
+  ATOM_WIFI_AWARE_RESOURCE_USING_CHANGED = 690;
+  ATOM_WIFI_AWARE_HAL_API_CALLED = 691;
+  ATOM_WIFI_LOCAL_ONLY_REQUEST_RECEIVED = 692;
+  ATOM_WIFI_LOCAL_ONLY_REQUEST_SCAN_TRIGGERED = 693;
+  ATOM_WIFI_THREAD_TASK_EXECUTED = 694;
+  ATOM_WIFI_STATE_CHANGED = 700;
+  ATOM_WIFI_AWARE_CAPABILITIES = 10190;
+  ATOM_WIFI_MODULE_INFO = 10193;
+  ATOM_SETTINGS_SPA_REPORTED = 622;
+  ATOM_EXPRESS_EVENT_REPORTED = 528;
+  ATOM_EXPRESS_HISTOGRAM_SAMPLE_REPORTED = 593;
+  ATOM_EXPRESS_UID_EVENT_REPORTED = 644;
+  ATOM_EXPRESS_UID_HISTOGRAM_SAMPLE_REPORTED = 658;
+  ATOM_PERMISSION_RATIONALE_DIALOG_VIEWED = 645;
+  ATOM_PERMISSION_RATIONALE_DIALOG_ACTION_REPORTED = 646;
+  ATOM_APP_DATA_SHARING_UPDATES_NOTIFICATION_INTERACTION = 647;
+  ATOM_APP_DATA_SHARING_UPDATES_FRAGMENT_VIEWED = 648;
+  ATOM_APP_DATA_SHARING_UPDATES_FRAGMENT_ACTION_REPORTED = 649;
+  ATOM_WS_INCOMING_CALL_ACTION_REPORTED = 626;
+  ATOM_WS_CALL_DISCONNECTION_REPORTED = 627;
+  ATOM_WS_CALL_DURATION_REPORTED = 628;
+  ATOM_WS_CALL_USER_EXPERIENCE_LATENCY_REPORTED = 629;
+  ATOM_WS_CALL_INTERACTION_REPORTED = 630;
+  ATOM_FULL_SCREEN_INTENT_LAUNCHED = 631;
+  ATOM_BAL_ALLOWED = 632;
+  ATOM_IN_TASK_ACTIVITY_STARTED = 685;
+  ATOM_CACHED_APPS_HIGH_WATERMARK = 10189;
+  ATOM_ODREFRESH_REPORTED = 366;
+  ATOM_ODSIGN_REPORTED = 548;
+  ATOM_ART_DATUM_REPORTED = 332;
+  ATOM_ART_DEVICE_DATUM_REPORTED = 550;
+  ATOM_ART_DATUM_DELTA_REPORTED = 565;
+  ATOM_BACKGROUND_DEXOPT_JOB_ENDED = 467;
+  ATOM_WEAR_ADAPTIVE_SUSPEND_STATS_REPORTED = 619;
+  ATOM_WEAR_POWER_ANOMALY_SERVICE_OPERATIONAL_STATS_REPORTED = 620;
+  ATOM_WEAR_POWER_ANOMALY_SERVICE_EVENT_STATS_REPORTED = 621;
+  ATOM_EMERGENCY_STATE_CHANGED = 633;
+  ATOM_DND_STATE_CHANGED = 657;
+  ATOM_MTE_STATE = 10181;
+  ATOM_AD_SERVICES_BACK_COMPAT_GET_TOPICS_REPORTED = 598;
+  ATOM_AD_SERVICES_BACK_COMPAT_EPOCH_COMPUTATION_CLASSIFIER_REPORTED = 599;
+  ATOM_AD_SERVICES_MEASUREMENT_DEBUG_KEYS = 640;
+  ATOM_AD_SERVICES_ERROR_REPORTED = 662;
+  ATOM_AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED = 663;
+  ATOM_AD_SERVICES_MEASUREMENT_DELAYED_SOURCE_REGISTRATION = 673;
+  ATOM_AD_SERVICES_MEASUREMENT_ATTRIBUTION = 674;
+  ATOM_AD_SERVICES_MEASUREMENT_JOBS = 675;
+  ATOM_AD_SERVICES_MEASUREMENT_WIPEOUT = 676;
+  ATOM_AD_SERVICES_CONSENT_MIGRATED = 702;
+  ATOM_RKPD_POOL_STATS = 664;
+  ATOM_RKPD_CLIENT_OPERATION = 665;
+  ATOM_AUTOFILL_UI_EVENT_REPORTED = 603;
+  ATOM_AUTOFILL_FILL_REQUEST_REPORTED = 604;
+  ATOM_AUTOFILL_FILL_RESPONSE_REPORTED = 605;
+  ATOM_AUTOFILL_SAVE_EVENT_REPORTED = 606;
+  ATOM_AUTOFILL_SESSION_COMMITTED = 607;
+  ATOM_AUTOFILL_FIELD_CLASSIFICATION_EVENT_REPORTED = 659;
+  ATOM_TEST_EXTENSION_ATOM_REPORTED = 660;
+  ATOM_TEST_RESTRICTED_ATOM_REPORTED = 672;
+  ATOM_STATS_SOCKET_LOSS_REPORTED = 752;
+  ATOM_PLUGIN_INITIALIZED = 655;
+  ATOM_TV_LOW_POWER_STANDBY_POLICY = 679;
+  ATOM_LOCKSCREEN_SHORTCUT_SELECTED = 611;
+  ATOM_LOCKSCREEN_SHORTCUT_TRIGGERED = 612;
+  ATOM_EMERGENCY_NUMBERS_INFO = 10180;
+  ATOM_QUALIFIED_RAT_LIST_CHANGED = 634;
+  ATOM_QNS_IMS_CALL_DROP_STATS = 635;
+  ATOM_QNS_FALLBACK_RESTRICTION_CHANGED = 636;
+  ATOM_QNS_RAT_PREFERENCE_MISMATCH_INFO = 10177;
+  ATOM_QNS_HANDOVER_TIME_MILLIS = 10178;
+  ATOM_QNS_HANDOVER_PINGPONG = 10179;
+  ATOM_SATELLITE_CONTROLLER = 10182;
+  ATOM_SATELLITE_SESSION = 10183;
+  ATOM_SATELLITE_INCOMING_DATAGRAM = 10184;
+  ATOM_SATELLITE_OUTGOING_DATAGRAM = 10185;
+  ATOM_SATELLITE_PROVISION = 10186;
+  ATOM_SATELLITE_SOS_MESSAGE_RECOMMENDER = 10187;
+  ATOM_IKE_SESSION_TERMINATED = 678;
+  ATOM_IKE_LIVENESS_CHECK_SESSION_VALIDATED = 760;
+  ATOM_BLUETOOTH_HASHED_DEVICE_NAME_REPORTED = 613;
+  ATOM_BLUETOOTH_L2CAP_COC_CLIENT_CONNECTION = 614;
+  ATOM_BLUETOOTH_L2CAP_COC_SERVER_CONNECTION = 615;
+  ATOM_BLUETOOTH_LE_SESSION_CONNECTED = 656;
+  ATOM_RESTRICTED_BLUETOOTH_DEVICE_NAME_REPORTED = 666;
+  ATOM_BLUETOOTH_PROFILE_CONNECTION_ATTEMPTED = 696;
+  ATOM_HEALTH_CONNECT_UI_IMPRESSION = 623;
+  ATOM_HEALTH_CONNECT_UI_INTERACTION = 624;
+  ATOM_HEALTH_CONNECT_APP_OPENED_REPORTED = 625;
+  ATOM_HEALTH_CONNECT_API_CALLED = 616;
+  ATOM_HEALTH_CONNECT_USAGE_STATS = 617;
+  ATOM_HEALTH_CONNECT_STORAGE_STATS = 618;
+  ATOM_HEALTH_CONNECT_API_INVOKED = 643;
+  ATOM_EXERCISE_ROUTE_API_CALLED = 654;
+  ATOM_ATOM_9999 = 9999;
+  ATOM_ATOM_99999 = 99999;
+  ATOM_THREADNETWORK_TELEMETRY_DATA_REPORTED = 738;
+  ATOM_THREADNETWORK_TOPO_ENTRY_REPEATED = 739;
+  ATOM_THREADNETWORK_DEVICE_INFO_REPORTED = 740;
+  ATOM_EMERGENCY_NUMBER_DIALED = 637;
+  ATOM_SANDBOX_API_CALLED = 488;
+  ATOM_SANDBOX_ACTIVITY_EVENT_OCCURRED = 735;
+  ATOM_SANDBOX_SDK_STORAGE = 10159;
+  ATOM_CRONET_ENGINE_CREATED = 703;
+  ATOM_CRONET_TRAFFIC_REPORTED = 704;
+  ATOM_CRONET_ENGINE_BUILDER_INITIALIZED = 762;
+  ATOM_CRONET_HTTP_FLAGS_INITIALIZED = 763;
+  ATOM_CRONET_INITIALIZED = 764;
+  ATOM_DAILY_KEEPALIVE_INFO_REPORTED = 650;
+  ATOM_IP_CLIENT_RA_INFO_REPORTED = 778;
+  ATOM_APF_SESSION_INFO_REPORTED = 777;
+  ATOM_CREDENTIAL_MANAGER_API_CALLED = 585;
+  ATOM_CREDENTIAL_MANAGER_INIT_PHASE_REPORTED = 651;
+  ATOM_CREDENTIAL_MANAGER_CANDIDATE_PHASE_REPORTED = 652;
+  ATOM_CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED = 653;
+  ATOM_CREDENTIAL_MANAGER_TOTAL_REPORTED = 667;
+  ATOM_CREDENTIAL_MANAGER_FINALNOUID_REPORTED = 668;
+  ATOM_CREDENTIAL_MANAGER_GET_REPORTED = 669;
+  ATOM_CREDENTIAL_MANAGER_AUTH_CLICK_REPORTED = 670;
+  ATOM_CREDENTIAL_MANAGER_APIV2_CALLED = 671;
+  ATOM_UWB_ACTIVITY_INFO = 10188;
+  ATOM_MEDIA_ACTION_REPORTED = 608;
+  ATOM_MEDIA_CONTROLS_LAUNCHED = 609;
+  ATOM_MEDIA_CODEC_RECLAIM_REQUEST_COMPLETED = 600;
+  ATOM_MEDIA_CODEC_STARTED = 641;
+  ATOM_MEDIA_CODEC_STOPPED = 642;
+  ATOM_MEDIA_CODEC_RENDERED = 684;
 }
 // End of protos/perfetto/config/statsd/atom_ids.proto
 
@@ -2702,7 +2939,7 @@
 // Begin of protos/perfetto/config/data_source_config.proto
 
 // The configuration that is passed to each data source when starting tracing.
-// Next id: 125
+// Next id: 126
 message DataSourceConfig {
   enum SessionInitiator {
     SESSION_INITIATOR_UNSPECIFIED = 0;
@@ -2838,6 +3075,9 @@
   optional AndroidSdkSyspropGuardConfig android_sdk_sysprop_guard_config = 124
       [lazy = true];
 
+  // Data source name: windows.etw
+  optional EtwConfig etw_config = 125 [lazy = true];
+
   // This is a fallback mechanism to send a free-form text config to the
   // producer. In theory this should never be needed. All the code that
   // is part of the platform (i.e. traced service) is supposed to *not* truncate
@@ -3981,6 +4221,14 @@
     JANK_DROPPED = 1024;
   };
 
+  // Specifies the severity of a jank.
+  enum JankSeverityType {
+    SEVERITY_UNKNOWN = 0;
+    SEVERITY_NONE = 1;
+    SEVERITY_PARTIAL = 2;
+    SEVERITY_FULL = 3;
+  }
+
   // Specifies how a frame was presented on screen w.r.t. timing.
   // Can be different for SurfaceFrame and DisplayFrame.
   enum PresentType {
@@ -4057,6 +4305,7 @@
     optional int32 jank_type = 9;
     optional PredictionType prediction_type = 10;
     optional bool is_buffer = 11;
+    optional JankSeverityType jank_severity_type = 12;
   };
 
   // Indicates the start of expected timeline slice for DisplayFrames.
@@ -4102,6 +4351,7 @@
     // frame.
     optional int32 jank_type = 7;
     optional PredictionType prediction_type = 8;
+    optional JankSeverityType jank_severity_type = 9;
   };
 
   // FrameEnd just sends the cookie to indicate that the corresponding
@@ -4284,6 +4534,104 @@
 
 // End of protos/perfetto/trace/android/packages_list.proto
 
+// Begin of protos/perfetto/trace/android/shell_transition.proto
+
+// ShellTransition messages record information about the shell transitions in
+// the system. This is used to track the animations that are created and execute
+// through the shell transition system.
+message ShellTransition {
+  // The unique identifier of the transition.
+  optional int32 id = 1;
+
+  // The time the transition was created on the WM side
+  // (using SystemClock.elapsedRealtimeNanos())
+  optional int64 create_time_ns = 2;
+  // The time the transition was sent from the WM side to shell
+  // (using SystemClock.elapsedRealtimeNanos())
+  optional int64 send_time_ns = 3;
+  // The time the transition was dispatched by shell to execute
+  // (using SystemClock.elapsedRealtimeNanos())
+  optional int64 dispatch_time_ns = 4;
+  // If the transition merge was accepted by the transition handler, this
+  // contains the time the transition was merged into transition with id
+  // `merge_target`.
+  // (using SystemClock.elapsedRealtimeNanos())
+  optional int64 merge_time_ns = 5;
+  // The time shell proposed the transition should be merged to the transition
+  // handler into transition with id `merge_target`.
+  // (using SystemClock.elapsedRealtimeNanos()).
+  optional int64 merge_request_time_ns = 6;
+  // If the transition was aborted on the shell side, this is the time that
+  // occured.
+  // (using SystemClock.elapsedRealtimeNanos())
+  optional int64 shell_abort_time_ns = 7;
+  // If the transition was aborted on the wm side, this is the time that
+  // occured.
+  // (using SystemClock.elapsedRealtimeNanos())
+  optional int64 wm_abort_time_ns = 8;
+  // The time WM considers the transition to be complete.
+  // (using SystemClock.elapsedRealtimeNanos())
+  optional int64 finish_time_ns = 9;
+
+  // The id of the transaction that WM proposed to use as the starting
+  // transaction. It contains all the layer changes required to setup the
+  // transition and should be executed right at the start of the transition
+  // by the transition handler.
+  optional uint64 start_transaction_id = 10;
+  // The if of the transaction that WM proposed to use as the finish
+  // transaction. It contains all the layer changes required to set the final
+  // state of the transition.
+  optional uint64 finish_transaction_id = 11;
+
+  // The id of the handler that executed the transition. A HandlerMappings
+  // message in the trace will contain the mapping of id to a string
+  // representation of the handler.
+  optional int32 handler = 12;
+  // The transition type of this transition (e.g. TO_FRONT, OPEN, CLOSE).
+  optional int32 type = 13;
+
+  // The list of targets that are part of this transition.
+  repeated Target targets = 14;
+  // The id of the transition we have requested to merge or have merged this
+  // transition into.
+  optional int32 merge_target = 15;
+
+  // The flags set on this transition.
+  optional int32 flags = 16;
+  // The time the starting window was removed. Tracked because this can
+  // happen after the transition finishes, but the app may not yet be visible
+  // until the starting window is removed. So in a sense the transition is not
+  // finished until the starting window is removed. (b/284302118)
+  // (using SystemClock.elapsedRealtimeNanos())
+  optional int64 starting_window_remove_time_ns = 17;
+
+  // Contains the information about the windows targeted in a transition.
+  message Target {
+    // The transition mode of this target (e.g. TO_FRONT, CLOSE...)
+    optional int32 mode = 1;
+    // The layer id of this target.
+    optional int32 layer_id = 2;
+    // The window id of this target.
+    optional int32 window_id = 3;
+    // The flags set on this target.
+    optional int32 flags = 4;
+  }
+}
+
+// Contains mappings from handler ids to string representation of the handlers.
+message ShellHandlerMappings {
+  repeated ShellHandlerMapping mapping = 1;
+}
+
+message ShellHandlerMapping {
+  // The id of the handler used in the ShellTransition message.
+  optional int32 id = 1;
+  // A human readable and meaningful string representation of the handler.
+  optional string name = 2;
+}
+
+// End of protos/perfetto/trace/android/shell_transition.proto
+
 // Begin of protos/perfetto/trace/android/surfaceflinger_common.proto
 
 message RegionProto {
@@ -5320,6 +5668,7 @@
 
 message EtwTraceEvent {
   optional uint64 timestamp = 1;
+  optional uint32 cpu = 4;
 
   oneof event {
     CSwitchEtwEvent c_switch = 2;
@@ -8137,6 +8486,30 @@
 
 // End of protos/perfetto/trace/ftrace/panel.proto
 
+// Begin of protos/perfetto/trace/ftrace/perf_trace_counters.proto
+
+message SchedSwitchWithCtrsFtraceEvent {
+  optional int32 old_pid = 1;
+  optional int32 new_pid = 2;
+  optional uint32 cctr = 3;
+  optional uint32 ctr0 = 4;
+  optional uint32 ctr1 = 5;
+  optional uint32 ctr2 = 6;
+  optional uint32 ctr3 = 7;
+  optional uint32 lctr0 = 8;
+  optional uint32 lctr1 = 9;
+  optional uint32 ctr4 = 10;
+  optional uint32 ctr5 = 11;
+  optional string prev_comm = 12;
+  optional int32 prev_pid = 13;
+  optional uint32 cyc = 14;
+  optional uint32 inst = 15;
+  optional uint32 stallbm = 16;
+  optional uint32 l3dm = 17;
+}
+
+// End of protos/perfetto/trace/ftrace/perf_trace_counters.proto
+
 // Begin of protos/perfetto/trace/ftrace/power.proto
 
 message CpuFrequencyFtraceEvent {
@@ -9419,6 +9792,7 @@
     SamsungTracingMarkWriteFtraceEvent samsung_tracing_mark_write = 484;
     BinderCommandFtraceEvent binder_command = 485;
     BinderReturnFtraceEvent binder_return = 486;
+    SchedSwitchWithCtrsFtraceEvent sched_switch_with_ctrs = 487;
   }
 }
 
@@ -13257,7 +13631,7 @@
 // See the [Buffers and Dataflow](/docs/concepts/buffers.md) doc for details.
 //
 // Next reserved id: 14 (up to 15).
-// Next id: 96.
+// Next id: 99.
 message TracePacket {
   // The timestamp of the TracePacket.
   // By default this timestamps refers to the trace clock (CLOCK_BOOTTIME on
@@ -13376,6 +13750,8 @@
     // Winscope traces
     LayersSnapshotProto surfaceflinger_layers_snapshot = 93;
     TransactionTraceEntry surfaceflinger_transactions = 94;
+    ShellTransition shell_transition = 96;
+    ShellHandlerMappings shell_handler_mappings = 97;
 
     // Events from the Windows etw infrastructure.
     EtwTraceEventBundle etw_events = 95;
@@ -13483,6 +13859,11 @@
   // being present for a particular sequence does not necessarily imply data
   // loss.
   optional bool first_packet_on_sequence = 87;
+
+  // The machine ID for identifying trace packets in a multi-machine tracing
+  // session. Is emitted by the tracing service for producers running on a
+  // remote host (e.g. a VM guest). For more context: go/crosetto-vm-tracing.
+  optional uint32 machine_id = 98;
 }
 
 // End of protos/perfetto/trace/trace_packet.proto
diff --git a/protos/perfetto/trace/trace_packet.proto b/protos/perfetto/trace/trace_packet.proto
index 9966894..e8306b7 100644
--- a/protos/perfetto/trace/trace_packet.proto
+++ b/protos/perfetto/trace/trace_packet.proto
@@ -29,6 +29,7 @@
 import "protos/perfetto/trace/android/initial_display_state.proto";
 import "protos/perfetto/trace/android/network_trace.proto";
 import "protos/perfetto/trace/android/packages_list.proto";
+import "protos/perfetto/trace/android/shell_transition.proto";
 import "protos/perfetto/trace/android/surfaceflinger_layers.proto";
 import "protos/perfetto/trace/android/surfaceflinger_transactions.proto";
 import "protos/perfetto/trace/chrome/chrome_benchmark_metadata.proto";
@@ -97,7 +98,7 @@
 // See the [Buffers and Dataflow](/docs/concepts/buffers.md) doc for details.
 //
 // Next reserved id: 14 (up to 15).
-// Next id: 96.
+// Next id: 99.
 message TracePacket {
   // The timestamp of the TracePacket.
   // By default this timestamps refers to the trace clock (CLOCK_BOOTTIME on
@@ -216,6 +217,8 @@
     // Winscope traces
     LayersSnapshotProto surfaceflinger_layers_snapshot = 93;
     TransactionTraceEntry surfaceflinger_transactions = 94;
+    ShellTransition shell_transition = 96;
+    ShellHandlerMappings shell_handler_mappings = 97;
 
     // Events from the Windows etw infrastructure.
     EtwTraceEventBundle etw_events = 95;
@@ -323,4 +326,9 @@
   // being present for a particular sequence does not necessarily imply data
   // loss.
   optional bool first_packet_on_sequence = 87;
+
+  // The machine ID for identifying trace packets in a multi-machine tracing
+  // session. Is emitted by the tracing service for producers running on a
+  // remote host (e.g. a VM guest). For more context: go/crosetto-vm-tracing.
+  optional uint32 machine_id = 98;
 }
diff --git a/protos/perfetto/trace_processor/serialization.proto b/protos/perfetto/trace_processor/serialization.proto
index 35b7cf8..f559bd9 100644
--- a/protos/perfetto/trace_processor/serialization.proto
+++ b/protos/perfetto/trace_processor/serialization.proto
@@ -39,43 +39,12 @@
 
 // Schema for serializing the column of Trace Processor table.
 message SerializedColumn {
-  // Schema used to store a serialized vector of data.
-  message Vector {
-    optional uint64 size = 1;
-    optional bytes data = 2;
-  }
-
   // Schema used to store a serialized |BitVector|.
   message BitVector {
     optional bytes words = 1;
     optional bytes counts = 2;
     optional uint32 size = 3;
   }
-  // A schema for serialization of any of the descendants of
-  // |overlays::StorageOverlay|.
-  message Overlay {
-    // A schema for serialization of |overlays::ArrangementOverlay|.
-    message ArrangementOverlay {
-      optional Vector vector = 1;
-    }
-
-    // A schema for serialization of |overlays::NullOverlay|.
-    message NullOverlay {
-      optional BitVector bit_vector = 1;
-    }
-
-    // A schema for serialization of |overlays::NullOverlay|.
-    message SelectorOverlay {
-      optional BitVector bit_vector = 1;
-    }
-
-    // Overlays are saved as either vectors or BitVectors.
-    oneof data {
-      ArrangementOverlay arrangement_overlay = 1;
-      NullOverlay null_overlay = 2;
-      SelectorOverlay selector_overlay = 3;
-    }
-  }
   // A schema for serialization of any of the descendants of |storage::Storage|.
   message Storage {
     // Dummy storage should not contain any data. It's used to signify that
@@ -89,35 +58,61 @@
 
     // A schema for serialization of |storage::Numeric|.
     message NumericStorage {
-      optional Vector values = 1;
+      optional bytes values = 1;
       optional bool is_sorted = 2;
       optional uint32 column_type = 3;
     }
 
     // A schema for serialization of |storage::SetIdStorage|.
     message SetIdStorage {
-      optional Vector values = 1;
+      optional bytes values = 1;
     }
 
     // A schema for serialization of |storage::StringStorage|.
     message StringStorage {
-      optional Vector values = 1;
+      optional bytes values = 1;
       optional bool is_sorted = 2;
     }
 
+    // A schema for serialization of |storage::NullStorage|.
+    message NullStorage {
+      optional BitVector bit_vector = 1;
+      optional Storage storage = 2;
+    }
+
+    // A schema for serialization of |storage::ArrangementStorage|.
+    message ArrangementStorage {
+      optional bytes values = 1;
+      optional Storage storage = 2;
+    }
+
+    // A schema for serialization of |storage::SelectorStorage|.
+    message SelectorStorage {
+      optional BitVector bit_vector = 1;
+      optional Storage storage = 2;
+    }
+
+    // A schema for serialization of |storage::DenseNullStorage|.
+    message DenseNullStorage {
+      optional BitVector bit_vector = 1;
+      optional Storage storage = 2;
+    }
+
     oneof data {
       DummyStorage dummy_storage = 1;
       IdStorage id_storage = 2;
       NumericStorage numeric_storage = 3;
       SetIdStorage set_id_storage = 4;
       StringStorage string_storage = 5;
+      NullStorage null_storage = 6;
+      ArrangementStorage arrangement_storage = 7;
+      SelectorStorage selector_storage = 8;
+      DenseNullStorage dense_null_storage = 9;
     }
   }
 
   // Name of the table this column is part of.
   optional string table_name = 1;
   optional string column_name = 2;
-
   optional Storage storage = 3;
-  repeated Overlay overlay = 4;
 }
diff --git a/protos/third_party/chromium/chrome_track_event.proto b/protos/third_party/chromium/chrome_track_event.proto
index 90cde7d..a68feef 100644
--- a/protos/third_party/chromium/chrome_track_event.proto
+++ b/protos/third_party/chromium/chrome_track_event.proto
@@ -1460,7 +1460,7 @@
 
 message StartUp {
   // This enum must be kept up to date with LaunchCauseMetrics.LaunchCause.
-  enum LauchCauseType {
+  enum LaunchCauseType {
     OTHER = 0;
     CUSTOM_TAB = 1;
     TWA = 2;
@@ -1483,7 +1483,8 @@
   }
 
   optional int64 activity_id = 1;
-  optional LauchCauseType launch_cause = 2;
+  // deprecated field 2.
+  optional LaunchCauseType launch_cause = 3;
 }
 
 message WebContentInteraction {
diff --git a/python/perfetto/prebuilts/manifests/trace_processor_shell.py b/python/perfetto/prebuilts/manifests/trace_processor_shell.py
index 2f881f5..0196c4b 100755
--- a/python/perfetto/prebuilts/manifests/trace_processor_shell.py
+++ b/python/perfetto/prebuilts/manifests/trace_processor_shell.py
@@ -1,15 +1,15 @@
-# This file has been generated by: tools/roll-prebuilts 6ba75cc5d93c39d4f86e2777709a460b30ce06fc
+# This file has been generated by: tools/roll-prebuilts v40.0
 TRACE_PROCESSOR_SHELL_MANIFEST = [{
     'arch':
         'mac-amd64',
     'file_name':
         'trace_processor_shell',
     'file_size':
-        9830664,
+        9978200,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/mac-amd64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/mac-amd64/trace_processor_shell',
     'sha256':
-        'd617f9237f77477a6e80065cbcfc84d6ce80505ee34851a0b435f0815aba2645',
+        'f3e21eb29fb51cb2ea9b81b69132c5ae93ce3276c57ccd27fcf7c675306b4e41',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -19,11 +19,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        8328808,
+        8493976,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/mac-arm64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/mac-arm64/trace_processor_shell',
     'sha256':
-        'cc5b81618ddfa0a6eb63e0d418c0a52e302ca2617906fae6743d71f82c454611',
+        '84f35765141374b8d883813ac533e0c004cf72d1c6f05aef0c973364ff541eb9',
     'platform':
         'darwin',
     'machine': ['arm64']
@@ -33,11 +33,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        9682088,
+        9830856,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/linux-amd64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-amd64/trace_processor_shell',
     'sha256':
-        '0beb26c1d744cb982630b23ce031a767d6ed7113b81dca0710f792111592ee9e',
+        'b3dc0a9c641b84a57fa5d59637921ae2237e4f05b1778341a691df220faf0cd7',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -47,11 +47,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        7087544,
+        7231096,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/linux-arm/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-arm/trace_processor_shell',
     'sha256':
-        '4df289643b09261bd5718d6894e301023b4101e94e2dd81ead63037fb454054d',
+        'a21252830fb1bbb7b3fd9665ce6e70920cffa6b1e72c16589c90896c002c3348',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -61,11 +61,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        9090808,
+        9238056,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/linux-arm64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-arm64/trace_processor_shell',
     'sha256':
-        '9cb0ce22fba1929bef599d89a197a2ea718061840006c4122032cf0fc5fc0c90',
+        'f77519ec19743ec2c22ed78fe3a20106a482a28d77c4154378af108c5f7bdd4a',
     'platform':
         'linux',
     'machine': ['aarch64']
@@ -75,55 +75,55 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        6723512,
+        6870968,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/android-arm/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-arm/trace_processor_shell',
     'sha256':
-        'c5949a736b1b5edb285f56c22e9af83e1d0ec6d331adaac77330a277d8bee07c'
+        '2c7055fb44085ec60ad8bb970d495c9c88070fce08902f11fcd44e0ae3369876'
 }, {
     'arch':
         'android-arm64',
     'file_name':
         'trace_processor_shell',
     'file_size':
-        8267112,
+        8414568,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/android-arm64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-arm64/trace_processor_shell',
     'sha256':
-        '32475b5fe7e1af557eab0d898accb37c657bc4b2dd221135b55114e61bd8be39'
+        'd8ca0dc2bab7ea604a6721f0ac0e2b433b43261f247c6c98c510dc17aafe5a72'
 }, {
     'arch':
         'android-x86',
     'file_name':
         'trace_processor_shell',
     'file_size':
-        9164668,
+        9328508,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/android-x86/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-x86/trace_processor_shell',
     'sha256':
-        '02aeff08c0c3e553c95c851401ade9aac6784ad2f19e312dabbb24801d02c0e5'
+        'de6a6ea45769888e59a1678d37b6e355b27b834d34a0b9e4980a942d333b88cc'
 }, {
     'arch':
         'android-x64',
     'file_name':
         'trace_processor_shell',
     'file_size':
-        9430440,
+        9577896,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/android-x64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-x64/trace_processor_shell',
     'sha256':
-        'de75622f96f2551a7bbc28a0f063e0dfcc39241cd4fe09c39d0b2541d860a30a'
+        'cd4b16c5f78a060934204737ba8b312e824ff7cc28f3732daf7d64e733a727f9'
 }, {
     'arch':
         'windows-amd64',
     'file_name':
         'trace_processor_shell.exe',
     'file_size':
-        9100288,
+        9248256,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/windows-amd64/trace_processor_shell.exe',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/windows-amd64/trace_processor_shell.exe',
     'sha256':
-        'f0065f1dfa3f8001959bf18f5cd30138aa58409e39a13ccdd52a1860384e34fb',
+        '26584b4bbab40f8b0ad991a869e7483f92d7223e1473b879a6ceafa49b76390a',
     'platform':
         'win32',
     'machine': ['amd64']
diff --git a/python/perfetto/prebuilts/manifests/tracebox.py b/python/perfetto/prebuilts/manifests/tracebox.py
index a1fd44e..21698c1 100755
--- a/python/perfetto/prebuilts/manifests/tracebox.py
+++ b/python/perfetto/prebuilts/manifests/tracebox.py
@@ -1,15 +1,15 @@
-# This file has been generated by: tools/roll-prebuilts 6ba75cc5d93c39d4f86e2777709a460b30ce06fc
+# This file has been generated by: tools/roll-prebuilts v40.0
 TRACEBOX_MANIFEST = [{
     'arch':
         'mac-amd64',
     'file_name':
         'tracebox',
     'file_size':
-        1498680,
+        1498816,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/mac-amd64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/mac-amd64/tracebox',
     'sha256':
-        '57252aaf73b6a82cfb3bbc6cd68a9ec953f61296cf2c2d651125bb437dc50144',
+        '185014447d35357edbd20e7ce9924842a0d5c6576bd2257abae2ed48b65fd3b8',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -19,11 +19,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        1376136,
+        1392776,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/mac-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/mac-arm64/tracebox',
     'sha256':
-        '5f37217af7d47b39624c62625847ec6b98db2ebd1a512e85921eb16bcb402b35',
+        '082bb50e64df5e232673eebb1cd8b0dd752a394105f600cb0262730833f6b7f3',
     'platform':
         'darwin',
     'machine': ['arm64']
@@ -33,11 +33,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        2218840,
+        2229096,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/linux-amd64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-amd64/tracebox',
     'sha256':
-        '89b1412e66b0a227cd8d16e6bf3d7416609c153d4b5c64d6e1a7c600870cd27b',
+        'c99120caedb845e1c3fad4428263a683b44c357c76d65848dd8e437250066e38',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -47,11 +47,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        1332292,
+        1339796,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/linux-arm/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-arm/tracebox',
     'sha256':
-        'b5a3a8e0869b35bc9ceb06f3e4ca5bb6c1e1b1c5e6ea23cfed541e40a45ad96a',
+        '6732165916b74f0b820991d1aaed2086a6b56e91f6c604291efe6636f0bdda71',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -61,11 +61,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        2147464,
+        2157312,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/linux-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-arm64/tracebox',
     'sha256':
-        '00fdbdfc5877352b9323b6ea1a119132b66e696941f380da117d0d47ae1baaf9',
+        '7d09865a6d7118e67d2acd0c56b2a94ce8bd5f614869d29a72fe633515ab1fbd',
     'platform':
         'linux',
     'machine': ['aarch64']
@@ -75,11 +75,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        1230804,
+        1247188,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/android-arm/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-arm/tracebox',
     'sha256':
-        '02ee157f995708e78f57d6e5e110054d2e2b407dbf2db120cc967c7c553081e7'
+        '4ecc192172ac2bca49557cbdbb1f7d660718d4fb4a7314fd19b2b2e52be8bc0c'
 }, {
     'arch':
         'android-arm64',
@@ -88,9 +88,9 @@
     'file_size':
         1854120,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/android-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-arm64/tracebox',
     'sha256':
-        'e0031963f623da25c387b78e20747e07829043732f5afe16cfdea9bad3c0f27c'
+        '1ca89113279d5c6a9ae273bde03b4d84373efe6923dc637cb840908f13b9639e'
 }, {
     'arch':
         'android-x86',
@@ -99,9 +99,9 @@
     'file_size':
         1853356,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/android-x86/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-x86/tracebox',
     'sha256':
-        '6e9e4b7b0d3773ab5011e476eca883eeb0ab7538fc7d2cd4642b6abc01711ba9'
+        'cf689a191c1252734ebbfda3106600da324610f761515cfbffbeac2ebdfee715'
 }, {
     'arch':
         'android-x64',
@@ -110,7 +110,7 @@
     'file_size':
         2149032,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/android-x64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-x64/tracebox',
     'sha256':
-        '4b494907a43e898047ce98271796464deb3010b6755cee8d9c8a6c341502979b'
+        '99e9ebdb5b5308d95551a4ad060d615d7defb6877c4061d21c783c45a71d372f'
 }]
diff --git a/python/perfetto/prebuilts/manifests/traceconv.py b/python/perfetto/prebuilts/manifests/traceconv.py
index 7ada0fa..f01ef0a 100755
--- a/python/perfetto/prebuilts/manifests/traceconv.py
+++ b/python/perfetto/prebuilts/manifests/traceconv.py
@@ -1,15 +1,15 @@
-# This file has been generated by: tools/roll-prebuilts 6ba75cc5d93c39d4f86e2777709a460b30ce06fc
+# This file has been generated by: tools/roll-prebuilts v40.0
 TRACECONV_MANIFEST = [{
     'arch':
         'mac-amd64',
     'file_name':
         'traceconv',
     'file_size':
-        9020880,
+        9184800,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/mac-amd64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/mac-amd64/traceconv',
     'sha256':
-        'ae7903f9bb8a98b32fdf96c0124797cf9b4b58a9bcb45a05574d74d33b11ada9',
+        'b651d0a5b5606c1c3e24723e94d8ecb233a01f0dfccc95a2c6a4e773cb8f52d7',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -19,11 +19,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        7580200,
+        7761896,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/mac-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/mac-arm64/traceconv',
     'sha256':
-        '619f31cd84a70fcb75e2a1cbe911f77feca0273ea0b1e706ae4be5ac020cdf7d',
+        '3b019f5ddd5293d3181f7c30f91dc7b08f3a2e83ebb3b52b8f3905dc5161747d',
     'platform':
         'darwin',
     'machine': ['arm64']
@@ -33,11 +33,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        8773576,
+        8928296,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/linux-amd64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-amd64/traceconv',
     'sha256':
-        'abc0b4191abe5106e1d4382bc0d238e53c39a83e1ba91237fbed5f789ef1c82b',
+        '830d20ffec266218d49f6b6c8efed4538bc59b51d8d2f735cbbb6a1435131b50',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -47,11 +47,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        6620748,
+        6770204,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/linux-arm/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-arm/traceconv',
     'sha256':
-        'f2dd9f1bfabea567ff20aedd0798e90093eff2bfdc15aacbfafe0abe52aa1fd1',
+        '93a9e5ccb94559b871af8f6da45f858aee01801b31776703892dcf3d7ea769b7',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -61,11 +61,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        8240792,
+        8393944,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/linux-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-arm64/traceconv',
     'sha256':
-        '76428eca01ced4b769241915fb5233b43b9b59e9062917e45d523db10f33e546',
+        '88a92ccbcd8e851673e018b7f599514daf05dde9b7e4de9641fa5629124abf12',
     'platform':
         'linux',
     'machine': ['aarch64']
@@ -75,55 +75,55 @@
     'file_name':
         'traceconv',
     'file_size':
-        6247672,
+        6378744,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/android-arm/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-arm/traceconv',
     'sha256':
-        '3eb80bbd78c6210b73ef20654b9d80994747e552b3d342a8d2ec4aa58dabcc33'
+        '6cb7d30d656aa4f172e6724f105a56e249e7043ecf637c65e1e3868885535cff'
 }, {
     'arch':
         'android-arm64',
     'file_name':
         'traceconv',
     'file_size':
-        7545032,
+        7692488,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/android-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-arm64/traceconv',
     'sha256':
-        '30037aba74f7718f71273a83141373e623ddfe561294d01b41433dc9b572fee5'
+        '1668808efbdf8d5b116d4716d61d2bd002f71ce465206d3b83af4fcc7a4c19cd'
 }, {
     'arch':
         'android-x86',
     'file_name':
         'traceconv',
     'file_size':
-        8410300,
+        8557756,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/android-x86/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-x86/traceconv',
     'sha256':
-        '9ad420368453699da12ce6359db514283e92b2caa2d415d9db61977cc79649a9'
+        '653733582cae0021eae0e1b5d8db387c1bae772d77b307f1e2111b78ec4ea67c'
 }, {
     'arch':
         'android-x64',
     'file_name':
         'traceconv',
     'file_size':
-        8544512,
+        8708352,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/android-x64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-x64/traceconv',
     'sha256':
-        '60a62f8712186c5f984ff2fbc4ff8c47b6226b62a83b31f878e0597c8bb90cd8'
+        '7fc564ac581b81d79573f57dae027c47bd7a857ff0f89df984380c3c657d5876'
 }, {
     'arch':
         'windows-amd64',
     'file_name':
         'traceconv.exe',
     'file_size':
-        8049664,
+        8204288,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/windows-amd64/traceconv.exe',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/windows-amd64/traceconv.exe',
     'sha256':
-        '9e89ccd0cdb466ea5fe960b71538b0ff8a2858b9e7ecdbcafa9ffe60839c9afc',
+        'e33bad8061f08f9c3cfe6e91ef6f1696b6ac90d0799edcb57052f24888b436e2',
     'platform':
         'win32',
     'machine': ['amd64']
diff --git a/python/tools/record_android_trace.py b/python/tools/record_android_trace.py
index 06a4fa7..7dbca53 100755
--- a/python/tools/record_android_trace.py
+++ b/python/tools/record_android_trace.py
@@ -431,7 +431,7 @@
   fname = os.path.basename(path)
   socketserver.TCPServer.allow_reuse_address = True
   with socketserver.TCPServer(('127.0.0.1', PORT), HttpHandler) as httpd:
-    address = f'{origin}/#!/?url=http://127.0.0.1:{PORT}/{fname}'
+    address = f'{origin}/#!/?url=http://127.0.0.1:{PORT}/{fname}&referrer=record_android_trace'
     if open_browser:
       webbrowser.open_new_tab(address)
     else:
diff --git a/src/base/time.cc b/src/base/time.cc
index 1507916..e799542 100644
--- a/src/base/time.cc
+++ b/src/base/time.cc
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include <atomic>
+
 #include "perfetto/base/time.h"
 
 #include "perfetto/base/build_config.h"
@@ -30,6 +32,86 @@
 namespace base {
 
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+#if !PERFETTO_BUILDFLAG(PERFETTO_ARCH_CPU_ARM64)
+namespace {
+
+// Returns the current value of the performance counter.
+int64_t QPCNowRaw() {
+  LARGE_INTEGER perf_counter_now = {};
+  // According to the MSDN documentation for QueryPerformanceCounter(), this
+  // will never fail on systems that run XP or later.
+  // https://msdn.microsoft.com/library/windows/desktop/ms644904.aspx
+  ::QueryPerformanceCounter(&perf_counter_now);
+  return perf_counter_now.QuadPart;
+}
+
+double TSCTicksPerSecond() {
+  // The value returned by QueryPerformanceFrequency() cannot be used as the TSC
+  // frequency, because there is no guarantee that the TSC frequency is equal to
+  // the performance counter frequency.
+  // The TSC frequency is cached in a static variable because it takes some time
+  // to compute it.
+  static std::atomic<double> tsc_ticks_per_second = 0;
+  double value = tsc_ticks_per_second.load(std::memory_order_relaxed);
+  if (value != 0)
+    return value;
+
+  // Increase the thread priority to reduces the chances of having a context
+  // switch during a reading of the TSC and the performance counter.
+  const int previous_priority = ::GetThreadPriority(::GetCurrentThread());
+  ::SetThreadPriority(::GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
+
+  // The first time that this function is called, make an initial reading of the
+  // TSC and the performance counter. Initialization of static variable is
+  // thread-safe. Threads can race initializing tsc_initial vs
+  // perf_counter_initial, although they should be storing very similar values.
+
+  static const uint64_t tsc_initial = __rdtsc();
+  static const int64_t perf_counter_initial = QPCNowRaw();
+
+  // Make a another reading of the TSC and the performance counter every time
+  // that this function is called.
+  const uint64_t tsc_now = __rdtsc();
+  const int64_t perf_counter_now = QPCNowRaw();
+
+  // Reset the thread priority.
+  ::SetThreadPriority(::GetCurrentThread(), previous_priority);
+
+  // Make sure that at least 50 ms elapsed between the 2 readings. The first
+  // time that this function is called, we don't expect this to be the case.
+  // Note: The longer the elapsed time between the 2 readings is, the more
+  //   accurate the computed TSC frequency will be. The 50 ms value was
+  //   chosen because local benchmarks show that it allows us to get a
+  //   stddev of less than 1 tick/us between multiple runs.
+  // Note: According to the MSDN documentation for QueryPerformanceFrequency(),
+  //   this will never fail on systems that run XP or later.
+  //   https://msdn.microsoft.com/library/windows/desktop/ms644905.aspx
+  LARGE_INTEGER perf_counter_frequency = {};
+  ::QueryPerformanceFrequency(&perf_counter_frequency);
+  PERFETTO_CHECK(perf_counter_now >= perf_counter_initial);
+  const int64_t perf_counter_ticks = perf_counter_now - perf_counter_initial;
+  const double elapsed_time_seconds =
+      static_cast<double>(perf_counter_ticks) /
+      static_cast<double>(perf_counter_frequency.QuadPart);
+
+  constexpr double kMinimumEvaluationPeriodSeconds = 0.05;
+  if (elapsed_time_seconds < kMinimumEvaluationPeriodSeconds)
+    return 0;
+
+  // Compute the frequency of the TSC.
+  PERFETTO_CHECK(tsc_now >= tsc_initial);
+  const uint64_t tsc_ticks = tsc_now - tsc_initial;
+  // Racing with another thread to write |tsc_ticks_per_second| is benign
+  // because both threads will write a valid result.
+  tsc_ticks_per_second.store(
+      static_cast<double>(tsc_ticks) / elapsed_time_seconds,
+      std::memory_order_relaxed);
+
+  return tsc_ticks_per_second.load(std::memory_order_relaxed);
+}
+
+}  // namespace
+#endif  // !PERFETTO_BUILDFLAG(PERFETTO_ARCH_CPU_ARM64)
 
 TimeNanos GetWallTimeNs() {
   LARGE_INTEGER freq;
@@ -42,6 +124,13 @@
 }
 
 TimeNanos GetThreadCPUTimeNs() {
+#if PERFETTO_BUILDFLAG(PERFETTO_ARCH_CPU_ARM64)
+  // QueryThreadCycleTime versus TSCTicksPerSecond doesn't have much relation to
+  // actual elapsed time on Windows on Arm, because QueryThreadCycleTime is
+  // backed by the actual number of CPU cycles executed, rather than a
+  // constant-rate timer like Intel. To work around this, use GetThreadTimes
+  // (which isn't as accurate but is meaningful as a measure of elapsed
+  // per-thread time).
   FILETIME dummy, kernel_ftime, user_ftime;
   ::GetThreadTimes(GetCurrentThread(), &dummy, &dummy, &kernel_ftime,
                    &user_ftime);
@@ -51,6 +140,23 @@
       user_ftime.dwHighDateTime * 0x100000000 + user_ftime.dwLowDateTime;
 
   return TimeNanos((kernel_time + user_time) * 100);
+#else   // !PERFETTO_BUILDFLAG(PERFETTO_ARCH_CPU_ARM64)
+  // Get the number of TSC ticks used by the current thread.
+  ULONG64 thread_cycle_time = 0;
+  ::QueryThreadCycleTime(GetCurrentThread(), &thread_cycle_time);
+
+  // Get the frequency of the TSC.
+  const double tsc_ticks_per_second = TSCTicksPerSecond();
+  if (tsc_ticks_per_second == 0)
+    return TimeNanos();
+
+  // Return the CPU time of the current thread.
+  const double thread_time_seconds =
+      static_cast<double>(thread_cycle_time) / tsc_ticks_per_second;
+  constexpr int64_t kNanosecondsPerSecond = 1000 * 1000 * 1000;
+  return TimeNanos(
+      static_cast<int64_t>(thread_time_seconds * kNanosecondsPerSecond));
+#endif  // !PERFETTO_BUILDFLAG(PERFETTO_ARCH_CPU_ARM64)
 }
 
 void SleepMicroseconds(unsigned interval_us) {
@@ -61,12 +167,22 @@
   ::Sleep(static_cast<DWORD>((interval_us + 999) / 1000));
 }
 
+void InitializeTime() {
+#if !PERFETTO_BUILDFLAG(PERFETTO_ARCH_CPU_ARM64)
+  // Make an early first call to TSCTicksPerSecond() to start 50 ms elapsed time
+  // (see comment in TSCTicksPerSecond()).
+  TSCTicksPerSecond();
+#endif  // !PERFETTO_BUILDFLAG(PERFETTO_ARCH_CPU_ARM64)
+}
+
 #else  // PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
 
 void SleepMicroseconds(unsigned interval_us) {
   ::usleep(static_cast<useconds_t>(interval_us));
 }
 
+void InitializeTime() {}
+
 #endif  // PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
 
 std::string GetTimeFmt(const std::string& fmt) {
diff --git a/src/ipc/host_impl.cc b/src/ipc/host_impl.cc
index 029b450..0c862ee 100644
--- a/src/ipc/host_impl.cc
+++ b/src/ipc/host_impl.cc
@@ -21,6 +21,7 @@
 #include <utility>
 
 #include "perfetto/base/build_config.h"
+#include "perfetto/base/compiler.h"
 #include "perfetto/base/logging.h"
 #include "perfetto/base/task_runner.h"
 #include "perfetto/base/time.h"
@@ -44,6 +45,47 @@
     kUseTCPSocket ? base::SockFamily::kInet : base::SockFamily::kUnix;
 
 base::CrashKey g_crash_key_uid("ipc_uid");
+
+base::MachineID GenerateMachineID(base::UnixSocket* sock,
+                                  const std::string& machine_id_hint) {
+  // The special value of base::kDefaultMachineID is reserved for local
+  // producers.
+  if (!sock->is_connected() || sock->family() == base::SockFamily::kUnix)
+    return base::kDefaultMachineID;
+
+  base::Hasher hasher;
+  // Use the hint from the client, or fallback to hostname if the client
+  // doesn't provide a hint.
+  if (!machine_id_hint.empty()) {
+    hasher.Update(machine_id_hint);
+  } else {
+    // Use the socket address without the port number part as the hint.
+    auto host_id = sock->GetSockAddr();
+    auto pos = std::string::npos;
+    switch (sock->family()) {
+      case base::SockFamily::kInet:
+        PERFETTO_FALLTHROUGH;
+      case base::SockFamily::kInet6:
+        PERFETTO_FALLTHROUGH;
+      case base::SockFamily::kVsock:
+        pos = host_id.rfind(":");
+        if (pos != std::string::npos)
+          host_id.resize(pos);
+        break;
+      case base::SockFamily::kUnspec:
+        PERFETTO_FALLTHROUGH;
+      case base::SockFamily::kUnix:
+        PERFETTO_DFATAL("Should be unreachable.");
+        return base::kDefaultMachineID;
+    }
+    hasher.Update(host_id);
+  }
+
+  // Take the lower 32-bit from the hash.
+  uint32_t digest = static_cast<uint32_t>(hasher.digest());
+  // Avoid the extremely unlikely case that the hasher digest happens to be 0.
+  return digest == base::kDefaultMachineID ? 1 : digest;
+}
 }  // namespace
 
 uid_t HostImpl::ClientConnection::GetPosixPeerUid() const {
@@ -285,8 +327,8 @@
 
   auto peer_uid = client->GetPosixPeerUid();
   auto scoped_key = g_crash_key_uid.SetScoped(static_cast<int64_t>(peer_uid));
-  service->client_info_ =
-      ClientInfo(client->id, peer_uid, client->GetLinuxPeerPid());
+  service->client_info_ = ClientInfo(
+      client->id, peer_uid, client->GetLinuxPeerPid(), client->GetMachineID());
   service->received_fd_ = &client->received_fd;
   method.invoker(service, *decoded_req_args, std::move(deferred_reply));
   service->received_fd_ = nullptr;
@@ -307,9 +349,12 @@
     return;
   }
 
-  client->pid_override = req_frame.set_peer_identity().pid();
-  client->uid_override =
-      static_cast<uid_t>(req_frame.set_peer_identity().uid());
+  const auto& set_peer_identity = req_frame.set_peer_identity();
+  client->pid_override = set_peer_identity.pid();
+  client->uid_override = static_cast<uid_t>(set_peer_identity.uid());
+
+  client->machine_id = GenerateMachineID(client->sock.get(),
+                                         set_peer_identity.machine_id_hint());
 }
 
 void HostImpl::ReplyToMethodInvocation(ClientID client_id,
@@ -375,7 +420,8 @@
   ClientID client_id = client->id;
 
   ClientInfo client_info(client_id, client->GetPosixPeerUid(),
-                         client->GetLinuxPeerPid());
+                         client->GetLinuxPeerPid(), client->GetMachineID());
+
   clients_by_socket_.erase(it);
   PERFETTO_DCHECK(clients_.count(client_id));
   clients_.erase(client_id);
diff --git a/src/ipc/host_impl.h b/src/ipc/host_impl.h
index 8738459..c54a9e7 100644
--- a/src/ipc/host_impl.h
+++ b/src/ipc/host_impl.h
@@ -67,14 +67,19 @@
     BufferedFrameDeserializer frame_deserializer;
     base::ScopedFile received_fd;
     std::function<bool(int)> send_fd_cb_fuchsia;
-    // Peer identity set using IPCFrame sent by the client. These 2 fields
+    // Peer identity set using IPCFrame sent by the client. These 3 fields
     // should be used only for non-AF_UNIX connections AF_UNIX connections
     // should only rely on the peer identity obtained from the socket.
     uid_t uid_override = base::kInvalidUid;
     pid_t pid_override = base::kInvalidPid;
 
+    // |machine_id| is mapped from machine_id_hint (or socket hostname if
+    // |the client doesn't support machine_id_hint).
+    base::MachineID machine_id = base::kDefaultMachineID;
+
     pid_t GetLinuxPeerPid() const;
     uid_t GetPosixPeerUid() const;
+    base::MachineID GetMachineID() const { return machine_id; }
   };
   struct ExposedService {
     ExposedService(ServiceID, const std::string&, std::unique_ptr<Service>);
diff --git a/src/ipc/host_impl_unittest.cc b/src/ipc/host_impl_unittest.cc
index c6e0404..927a5a6 100644
--- a/src/ipc/host_impl_unittest.cc
+++ b/src/ipc/host_impl_unittest.cc
@@ -15,11 +15,12 @@
  */
 
 #include "src/ipc/host_impl.h"
-
+#include <sys/socket.h>
 #include <memory>
 
 #include "perfetto/ext/base/file_utils.h"
 #include "perfetto/ext/base/scoped_file.h"
+#include "perfetto/ext/base/sys_types.h"
 #include "perfetto/ext/base/temp_file.h"
 #include "perfetto/ext/base/unix_socket.h"
 #include "perfetto/ext/base/utils.h"
@@ -37,6 +38,7 @@
 namespace ipc {
 namespace {
 
+using ::perfetto::ipc::Frame;
 using ::perfetto::ipc::gen::ReplyProto;
 using ::perfetto::ipc::gen::RequestProto;
 using ::testing::_;
@@ -95,6 +97,12 @@
                                       base::SockType::kStream);
   }
 
+  FakeClient(const char* sock_name, base::TaskRunner* task_runner) {
+    auto sock_family = base::GetSockFamily(sock_name);
+    sock_ = base::UnixSocket::Connect(sock_name, this, task_runner, sock_family,
+                                      base::SockType::kStream);
+  }
+
   FakeClient(base::ScopedSocketHandle connected_socket,
              base::TaskRunner* task_runner) {
     sock_ = base::UnixSocket::AdoptConnected(std::move(connected_socket), this,
@@ -114,6 +122,21 @@
     SendFrame(frame);
   }
 
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
+    PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
+  void SetPeerIdentity(uid_t uid,
+                       pid_t pid,
+                       const std::string& machine_id_hint) {
+    Frame ipc_frame;
+    ipc_frame.set_request_id(0);
+    auto* set_peer_identity = ipc_frame.mutable_set_peer_identity();
+    set_peer_identity->set_pid(pid);
+    set_peer_identity->set_uid(static_cast<int32_t>(uid));
+    set_peer_identity->set_machine_id_hint(machine_id_hint);
+    SendFrame(ipc_frame);
+  }
+#endif
+
   void InvokeMethod(ServiceID service_id,
                     MethodID method_id,
                     const ProtoMessage& args,
@@ -530,6 +553,97 @@
   EXPECT_CALL(*cli_, OnInvokeMethodReply(_)).WillOnce(Return());
   task_runner_->RunUntilIdle();
 }
+
+TEST_F(HostImplTest, SetPeerIdentityUnixSocket) {
+  FakeService* fake_service = new FakeService("FakeService");
+  ASSERT_TRUE(host_->ExposeService(std::unique_ptr<Service>(fake_service)));
+  // SetPeerIdentity must be the first message. Use getpid()+1/geteuid+1 to
+  // check that this message doesn't take effect for Unix socket.
+  cli_->SetPeerIdentity(geteuid() + 1, getpid() + 1, "test_machine_id_hint");
+
+  auto on_bind = task_runner_->CreateCheckpoint("on_bind");
+  cli_->BindService("FakeService");
+  EXPECT_CALL(*cli_, OnServiceBound(_)).WillOnce(InvokeWithoutArgs(on_bind));
+  task_runner_->RunUntilCheckpoint("on_bind");
+
+  RequestProto req_args;
+  req_args.set_data("foo");
+  cli_->InvokeMethod(cli_->last_bound_service_id_, 1, req_args);
+  EXPECT_CALL(*fake_service, OnFakeMethod1(_, _))
+      .WillOnce(
+          Invoke([fake_service](const RequestProto& req, DeferredBase* reply) {
+            ASSERT_EQ("foo", req.data());
+            std::unique_ptr<ReplyProto> reply_args(new ReplyProto());
+            reply_args->set_data("bar");
+            reply->Resolve(AsyncResult<ProtoMessage>(
+                std::unique_ptr<ProtoMessage>(reply_args.release())));
+            // Verifies the pid() and uid() values in ClientInfo.
+            const auto& client_info = fake_service->client_info();
+            ASSERT_EQ(client_info.uid(), getuid());
+            ASSERT_EQ(client_info.pid(), getpid());
+            ASSERT_EQ(client_info.machine_id(), base::kDefaultMachineID);
+          }));
+
+  EXPECT_CALL(*cli_, OnInvokeMethodReply(_)).WillOnce(Return());
+  task_runner_->RunUntilIdle();
+}
+
+TEST(HostImpl, SetPeerIdentityTcpSocket) {
+  std::unique_ptr<base::TestTaskRunner> task_runner(new base::TestTaskRunner());
+  std::unique_ptr<HostImpl> host_impl;
+  std::unique_ptr<FakeClient> cli;
+
+  auto tear_down = base::OnScopeExit([&]() {
+    task_runner->RunUntilIdle();
+    cli.reset();
+    host_impl.reset();
+    task_runner->RunUntilIdle();
+    task_runner.reset();
+  });
+
+  Host* host = Host::CreateInstance("127.0.0.1:0", task_runner.get()).release();
+  ASSERT_NE(nullptr, host);
+  host_impl.reset(static_cast<HostImpl*>(host));
+
+  auto sock_name = host_impl->sock()->GetSockAddr();
+  cli.reset(new FakeClient(sock_name.c_str(), task_runner.get()));
+
+  auto on_connect = task_runner->CreateCheckpoint("on_connect");
+  EXPECT_CALL(*cli, OnConnect()).WillOnce(Invoke(on_connect));
+  task_runner->RunUntilCheckpoint("on_connect");
+
+  FakeService* fake_service = new FakeService("FakeService");
+  ASSERT_TRUE(host->ExposeService(std::unique_ptr<Service>(fake_service)));
+  // Set peer identity with fake values.
+  cli->SetPeerIdentity(123, 456, "test_machine_id_hint");
+
+  auto on_bind = task_runner->CreateCheckpoint("on_bind");
+  cli->BindService("FakeService");
+  EXPECT_CALL(*cli, OnServiceBound(_)).WillOnce(InvokeWithoutArgs(on_bind));
+  task_runner->RunUntilCheckpoint("on_bind");
+
+  RequestProto req_args;
+  req_args.set_data("foo");
+  cli->InvokeMethod(cli->last_bound_service_id_, 1, req_args);
+  EXPECT_CALL(*fake_service, OnFakeMethod1(_, _))
+      .WillOnce(
+          Invoke([fake_service](const RequestProto& req, DeferredBase* reply) {
+            ASSERT_EQ("foo", req.data());
+            std::unique_ptr<ReplyProto> reply_args(new ReplyProto());
+            reply_args->set_data("bar");
+            reply->Resolve(AsyncResult<ProtoMessage>(
+                std::unique_ptr<ProtoMessage>(reply_args.release())));
+            // Verify peer identity.
+            const auto& client_info = fake_service->client_info();
+            ASSERT_EQ(client_info.uid(), 123u);
+            ASSERT_EQ(client_info.pid(), 456);
+            // ClientInfo contains non-default raw machine ID.
+            ASSERT_NE(client_info.machine_id(), base::kDefaultMachineID);
+          }));
+
+  EXPECT_CALL(*cli, OnInvokeMethodReply(_)).WillOnce(Return());
+  task_runner->RunUntilIdle();
+}
 #endif  // OS_WIN
 
 // TODO(primiano): add the tests below in next CLs.
diff --git a/src/tools/ftrace_proto_gen/event_list b/src/tools/ftrace_proto_gen/event_list
index f2dcbb3..68bb2ba 100644
--- a/src/tools/ftrace_proto_gen/event_list
+++ b/src/tools/ftrace_proto_gen/event_list
@@ -481,3 +481,4 @@
 samsung/tracing_mark_write
 binder/binder_command
 binder/binder_return
+perf_trace_counters/sched_switch_with_ctrs
diff --git a/src/trace_processor/BUILD.gn b/src/trace_processor/BUILD.gn
index b09ed17..b89512e 100644
--- a/src/trace_processor/BUILD.gn
+++ b/src/trace_processor/BUILD.gn
@@ -269,7 +269,6 @@
     ":top_level_unittests",
     "containers:unittests",
     "db:unittests",
-    "db/overlays:unittests",
     "db/storage:unittests",
     "importers/android_bugreport:unittests",
     "importers/common:unittests",
diff --git a/src/trace_processor/containers/BUILD.gn b/src/trace_processor/containers/BUILD.gn
index e4750d2..4c4cb8d 100644
--- a/src/trace_processor/containers/BUILD.gn
+++ b/src/trace_processor/containers/BUILD.gn
@@ -38,6 +38,7 @@
   deps = [
     "../../../gn:default_deps",
     "../../../include/perfetto/protozero",
+    "../../../protos/perfetto/trace_processor:zero",
     "../../base",
   ]
 }
@@ -55,6 +56,8 @@
     ":containers",
     "../../../gn:default_deps",
     "../../../gn:gtest_and_gmock",
+    "../../../include/perfetto/protozero",
+    "../../../protos/perfetto/trace_processor:zero",
   ]
 }
 
diff --git a/src/trace_processor/containers/bit_vector.cc b/src/trace_processor/containers/bit_vector.cc
index dc5de84..b566558 100644
--- a/src/trace_processor/containers/bit_vector.cc
+++ b/src/trace_processor/containers/bit_vector.cc
@@ -18,6 +18,7 @@
 
 #include <limits>
 
+#include "protos/perfetto/trace_processor/serialization.pbzero.h"
 #include "src/trace_processor/containers/bit_vector_iterators.h"
 
 #if PERFETTO_BUILDFLAG(PERFETTO_X64_CPU_OPT)
@@ -329,5 +330,38 @@
   return std::move(builder).Build();
 }
 
+void BitVector::Serialize(
+    protos::pbzero::SerializedColumn::BitVector* msg) const {
+  msg->set_size(size_);
+  if (!counts_.empty()) {
+    msg->set_counts(reinterpret_cast<const uint8_t*>(counts_.data()),
+                    sizeof(uint32_t) * counts_.size());
+  }
+  if (!words_.empty()) {
+    msg->set_words(reinterpret_cast<const uint8_t*>(words_.data()),
+                   sizeof(uint64_t) * words_.size());
+  }
+}
+
+// Deserialize BitVector from proto.
+void BitVector::Deserialize(
+    const protos::pbzero::SerializedColumn::BitVector::Decoder& bv_msg) {
+  size_ = bv_msg.size();
+  if (bv_msg.has_counts()) {
+    counts_.resize(
+        static_cast<size_t>(bv_msg.counts().size / sizeof(uint32_t)));
+    memcpy(counts_.data(), bv_msg.counts().data, bv_msg.counts().size);
+  } else {
+    counts_.clear();
+  }
+
+  if (bv_msg.has_words()) {
+    words_.resize(static_cast<size_t>(bv_msg.words().size / sizeof(uint64_t)));
+    memcpy(words_.data(), bv_msg.words().data, bv_msg.words().size);
+  } else {
+    words_.clear();
+  }
+}
+
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/containers/bit_vector.h b/src/trace_processor/containers/bit_vector.h
index b7bbec1..0e5e72b 100644
--- a/src/trace_processor/containers/bit_vector.h
+++ b/src/trace_processor/containers/bit_vector.h
@@ -23,12 +23,21 @@
 
 #include <algorithm>
 #include <array>
+#include <cstring>
 #include <optional>
 #include <vector>
 
 #include "perfetto/base/logging.h"
 
 namespace perfetto {
+
+namespace protos {
+namespace pbzero {
+class SerializedColumn_BitVector;
+class SerializedColumn_BitVector_Decoder;
+}  // namespace pbzero
+}  // namespace protos
+
 namespace trace_processor {
 
 namespace internal {
@@ -395,6 +404,13 @@
     return BlockCount(n) * Block::kBits + BlockCount(n) * sizeof(uint32_t);
   }
 
+  // Serialize internals of BitVector to proto.
+  void Serialize(protos::pbzero::SerializedColumn_BitVector* msg) const;
+
+  // Deserialize BitVector from proto.
+  void Deserialize(
+      const protos::pbzero::SerializedColumn_BitVector_Decoder& bv_msg);
+
  private:
   friend class internal::BaseIterator;
   friend class internal::AllBitsIterator;
diff --git a/src/trace_processor/containers/bit_vector_unittest.cc b/src/trace_processor/containers/bit_vector_unittest.cc
index a324c33..ad282dc 100644
--- a/src/trace_processor/containers/bit_vector_unittest.cc
+++ b/src/trace_processor/containers/bit_vector_unittest.cc
@@ -19,6 +19,8 @@
 #include <bitset>
 #include <random>
 
+#include "perfetto/protozero/scattered_heap_buffer.h"
+#include "protos/perfetto/trace_processor/serialization.pbzero.h"
 #include "src/trace_processor/containers/bit_vector_iterators.h"
 #include "test/gtest_and_gmock.h"
 
@@ -720,6 +722,38 @@
   ASSERT_FALSE(set_it);
 }
 
+TEST(BitVectorUnittest, SerializeSimple) {
+  BitVector bv{1, 0, 1, 0, 1, 0, 1};
+  protozero::HeapBuffered<protos::pbzero::SerializedColumn::BitVector> msg;
+  bv.Serialize(msg.get());
+  auto buffer = msg.SerializeAsArray();
+
+  protos::pbzero::SerializedColumn::BitVector::Decoder decoder(buffer.data(),
+                                                               buffer.size());
+  ASSERT_EQ(decoder.size(), 7u);
+}
+
+TEST(BitVectorUnittest, SerializeDeserializeSimple) {
+  BitVector bv{1, 0, 1, 0, 1, 0, 1};
+  protozero::HeapBuffered<protos::pbzero::SerializedColumn::BitVector> msg;
+  bv.Serialize(msg.get());
+  auto buffer = msg.SerializeAsArray();
+
+  protos::pbzero::SerializedColumn::BitVector::Decoder decoder(buffer.data(),
+                                                               buffer.size());
+
+  BitVector des;
+  des.Deserialize(decoder);
+
+  ASSERT_EQ(des.size(), 7u);
+  ASSERT_EQ(des.CountSetBits(), 4u);
+
+  ASSERT_TRUE(des.IsSet(0));
+  ASSERT_TRUE(des.IsSet(2));
+  ASSERT_TRUE(des.IsSet(4));
+  ASSERT_TRUE(des.IsSet(6));
+}
+
 }  // namespace
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/db/BUILD.gn b/src/trace_processor/db/BUILD.gn
index 067d768..6b02d28 100644
--- a/src/trace_processor/db/BUILD.gn
+++ b/src/trace_processor/db/BUILD.gn
@@ -44,7 +44,6 @@
     "../util:glob",
     "../util:regex",
     "../util:util",
-    "overlays",
     "storage",
   ]
 }
@@ -67,11 +66,12 @@
     ":view_unittest",
     "../../../gn:default_deps",
     "../../../gn:gtest_and_gmock",
+    "../../../include/perfetto/trace_processor:basic_types",
     "../../base",
     "../tables",
     "../views",
-    "overlays",
     "storage",
+    "storage:fake_storage",
   ]
 }
 
@@ -84,6 +84,7 @@
       "../../../gn:default_deps",
       "../../../include/perfetto/base",
       "../../../include/perfetto/ext/base",
+      "../../../include/perfetto/trace_processor:basic_types",
       "../../base:test_support",
       "../tables:tables_python",
     ]
diff --git a/src/trace_processor/db/overlays/BUILD.gn b/src/trace_processor/db/overlays/BUILD.gn
deleted file mode 100644
index aad8e15..0000000
--- a/src/trace_processor/db/overlays/BUILD.gn
+++ /dev/null
@@ -1,50 +0,0 @@
-# Copyright (C) 2023 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import("../../../../gn/perfetto_tp_tables.gni")
-import("../../../../gn/test.gni")
-
-source_set("overlays") {
-  sources = [
-    "arrangement_overlay.cc",
-    "arrangement_overlay.h",
-    "null_overlay.cc",
-    "null_overlay.h",
-    "selector_overlay.cc",
-    "selector_overlay.h",
-    "storage_overlay.cc",
-    "storage_overlay.h",
-    "types.h",
-  ]
-  deps = [
-    "../../../../gn:default_deps",
-    "../../../base",
-    "../../containers",
-    "../storage",
-  ]
-}
-
-perfetto_unittest_source_set("unittests") {
-  testonly = true
-  sources = [
-    "arrangement_overlay_unittest.cc",
-    "null_overlay_unittest.cc",
-    "selector_overlay_unittest.cc",
-  ]
-  deps = [
-    ":overlays",
-    "../../../../gn:default_deps",
-    "../../../../gn:gtest_and_gmock",
-  ]
-}
diff --git a/src/trace_processor/db/overlays/arrangement_overlay.cc b/src/trace_processor/db/overlays/arrangement_overlay.cc
deleted file mode 100644
index 0ee1952..0000000
--- a/src/trace_processor/db/overlays/arrangement_overlay.cc
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "src/trace_processor/db/overlays/arrangement_overlay.h"
-#include <iterator>
-#include "perfetto/ext/base/flat_hash_map.h"
-#include "src/trace_processor/containers/bit_vector.h"
-#include "src/trace_processor/db/overlays/types.h"
-
-namespace perfetto {
-namespace trace_processor {
-namespace overlays {
-
-using Range = RowMap::Range;
-
-StorageRange ArrangementOverlay::MapToStorageRange(TableRange t_range) const {
-  PERFETTO_CHECK(t_range.range.end <= arrangement_->size());
-  const auto [min, max] =
-      std::minmax_element(arrangement_->data() + t_range.range.start,
-                          arrangement_->data() + t_range.range.end);
-
-  return StorageRange(*min, *max + 1);
-}
-
-TableRangeOrBitVector ArrangementOverlay::MapToTableRangeOrBitVector(
-    StorageRange s_range,
-    OverlayOp) const {
-  BitVector ret(static_cast<uint32_t>(arrangement_->size()), false);
-  for (uint32_t i = 0; i < arrangement_->size(); ++i) {
-    if (s_range.range.Contains((*arrangement_)[i]))
-      ret.Set(i);
-  }
-  return TableRangeOrBitVector(std::move(ret));
-}
-
-TableBitVector ArrangementOverlay::MapToTableBitVector(StorageBitVector s_bv,
-                                                       OverlayOp) const {
-  BitVector::Builder builder(static_cast<uint32_t>(arrangement_->size()));
-  uint32_t cur_idx = 0;
-
-  // Fast path: we compare as many groups of 64 elements as we can.
-  // This should be very easy for the compiler to auto-vectorize.
-  uint32_t fast_path_elements = builder.BitsInCompleteWordsUntilFull();
-  for (uint32_t i = 0; i < fast_path_elements; i += BitVector::kBitsInWord) {
-    uint64_t word = 0;
-    // This part should be optimised by SIMD and is expected to be fast.
-    for (uint32_t k = 0; k < BitVector::kBitsInWord; ++k, ++cur_idx) {
-      bool comp_result = s_bv.bv.IsSet((*arrangement_)[cur_idx]);
-      word |= static_cast<uint64_t>(comp_result) << k;
-    }
-    builder.AppendWord(word);
-  }
-
-  // Slow path: we compare <64 elements and append to fill the Builder.
-  uint32_t back_elements = builder.BitsUntilFull();
-  for (uint32_t i = 0; i < back_elements; ++i, ++cur_idx) {
-    builder.Append(s_bv.bv.IsSet((*arrangement_)[cur_idx]));
-  }
-  return TableBitVector{std::move(builder).Build()};
-}
-
-BitVector ArrangementOverlay::IsStorageLookupRequired(
-    OverlayOp,
-    const TableIndexVector& t_iv) const {
-  return BitVector(t_iv.size(), true);
-}
-
-StorageIndexVector ArrangementOverlay::MapToStorageIndexVector(
-    TableIndexVector t_iv) const {
-  std::vector<uint32_t> ret;
-  for (const auto& i : t_iv.indices) {
-    ret.push_back((*arrangement_)[i]);
-  }
-  return StorageIndexVector{ret};
-}
-
-BitVector ArrangementOverlay::IndexSearch(OverlayOp,
-                                          const TableIndexVector&) const {
-  PERFETTO_FATAL("IndexSearch should not be called inside ArrangementOverlay");
-}
-
-CostEstimatePerRow ArrangementOverlay::EstimateCostPerRow(OverlayOp) const {
-  CostEstimatePerRow estimate;
-  // Cost of std::min and std::max
-  estimate.to_storage_range = 20;
-  // Free
-  estimate.to_table_bit_vector = 0;
-  // Cost of creating trivial vector of 1s
-  estimate.is_storage_search_required = 0;
-  // Cost of a lookup inside |arrangement_|
-  estimate.map_to_storage_index_vector = 10;
-  // Shouldn't be called
-  estimate.index_search = 0;
-
-  return estimate;
-}
-
-}  // namespace overlays
-}  // namespace trace_processor
-}  // namespace perfetto
diff --git a/src/trace_processor/db/overlays/arrangement_overlay.h b/src/trace_processor/db/overlays/arrangement_overlay.h
deleted file mode 100644
index 1f590cb..0000000
--- a/src/trace_processor/db/overlays/arrangement_overlay.h
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef SRC_TRACE_PROCESSOR_DB_OVERLAYS_ARRANGEMENT_OVERLAY_H_
-#define SRC_TRACE_PROCESSOR_DB_OVERLAYS_ARRANGEMENT_OVERLAY_H_
-
-#include "src/trace_processor/db/overlays/storage_overlay.h"
-
-namespace perfetto {
-namespace trace_processor {
-namespace overlays {
-
-// Overlay responsible for arranging the elements of Storage. It deals with
-// duplicates, permutations and selection. For selection only it's more
-// efficient to use `SelectorOverlay`.
-class ArrangementOverlay : public StorageOverlay {
- public:
-  explicit ArrangementOverlay(const std::vector<uint32_t>* arrangement)
-      : arrangement_(std::move(arrangement)) {}
-
-  StorageRange MapToStorageRange(TableRange) const override;
-
-  TableRangeOrBitVector MapToTableRangeOrBitVector(StorageRange,
-                                                   OverlayOp) const override;
-
-  TableBitVector MapToTableBitVector(StorageBitVector,
-                                     OverlayOp) const override;
-
-  BitVector IsStorageLookupRequired(OverlayOp,
-                                    const TableIndexVector&) const override;
-
-  StorageIndexVector MapToStorageIndexVector(TableIndexVector) const override;
-
-  BitVector IndexSearch(OverlayOp, const TableIndexVector&) const override;
-
-  CostEstimatePerRow EstimateCostPerRow(OverlayOp) const override;
-
- private:
-  const std::vector<uint32_t>* arrangement_;
-};
-
-}  // namespace overlays
-}  // namespace trace_processor
-}  // namespace perfetto
-
-#endif  // SRC_TRACE_PROCESSOR_DB_OVERLAYS_ARRANGEMENT_OVERLAY_H_
diff --git a/src/trace_processor/db/overlays/arrangement_overlay_unittest.cc b/src/trace_processor/db/overlays/arrangement_overlay_unittest.cc
deleted file mode 100644
index 1418bab..0000000
--- a/src/trace_processor/db/overlays/arrangement_overlay_unittest.cc
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "src/trace_processor/db/overlays/arrangement_overlay.h"
-#include "test/gtest_and_gmock.h"
-
-namespace perfetto {
-namespace trace_processor {
-namespace overlays {
-namespace {
-
-TEST(ArrangementOverlay, MapToStorageRangeFirst) {
-  std::vector<uint32_t> arrangement{1, 1, 2, 2, 3, 3, 4, 4, 1, 1};
-  ArrangementOverlay overlay(&arrangement);
-  StorageRange r = overlay.MapToStorageRange(TableRange(2, 4));
-
-  ASSERT_EQ(r.range.start, 2u);
-  ASSERT_EQ(r.range.end, 3u);
-}
-
-TEST(ArrangementOverlay, MapToStorageRangeSecond) {
-  std::vector<uint32_t> arrangement{1, 1, 2, 2, 3, 3, 4, 4, 1, 1};
-  ArrangementOverlay overlay(&arrangement);
-  StorageRange r = overlay.MapToStorageRange(TableRange(5, 10));
-
-  ASSERT_EQ(r.range.start, 1u);
-  ASSERT_EQ(r.range.end, 5u);
-}
-
-TEST(ArrangementOverlay, MapToTableBitVector) {
-  std::vector<uint32_t> arrangement{1, 1, 2, 2, 3, 3, 4, 4, 1, 1};
-  ArrangementOverlay overlay(&arrangement);
-
-  BitVector storage_bv{0, 1, 0, 1, 0};
-
-  // Table bv:
-  // 1, 1, 0, 0, 1, 1, 0, 0, 1, 1
-  TableBitVector table_bv =
-      overlay.MapToTableBitVector({std::move(storage_bv)}, OverlayOp::kOther);
-
-  ASSERT_EQ(table_bv.bv.size(), 10u);
-  ASSERT_EQ(table_bv.bv.CountSetBits(), 6u);
-
-  ASSERT_TRUE(table_bv.bv.IsSet(0));
-  ASSERT_TRUE(table_bv.bv.IsSet(1));
-  ASSERT_TRUE(table_bv.bv.IsSet(4));
-  ASSERT_TRUE(table_bv.bv.IsSet(5));
-  ASSERT_TRUE(table_bv.bv.IsSet(8));
-  ASSERT_TRUE(table_bv.bv.IsSet(9));
-}
-
-TEST(ArrangementOverlay, IsStorageLookupRequired) {
-  std::vector<uint32_t> arrangement{0, 1, 1, 0, 0, 1, 1, 0};
-  ArrangementOverlay overlay(&arrangement);
-
-  std::vector<uint32_t> table_idx{0, 1, 2};
-  BitVector lookup_bv =
-      overlay.IsStorageLookupRequired(OverlayOp::kIsNull, {table_idx});
-
-  ASSERT_EQ(lookup_bv.size(), 3u);
-}
-
-TEST(ArrangementOverlay, MapToStorageIndexVector) {
-  std::vector<uint32_t> arrangement{1, 1, 2, 2, 3, 3, 4, 4, 1, 1};
-  ArrangementOverlay overlay(&arrangement);
-
-  std::vector<uint32_t> table_idx{1, 3, 7};
-  StorageIndexVector storage_iv = overlay.MapToStorageIndexVector({table_idx});
-
-  std::vector<uint32_t> res{1, 2, 4};
-  ASSERT_EQ(storage_iv.indices, res);
-}
-
-}  // namespace
-}  // namespace overlays
-}  // namespace trace_processor
-}  // namespace perfetto
diff --git a/src/trace_processor/db/overlays/null_overlay.cc b/src/trace_processor/db/overlays/null_overlay.cc
deleted file mode 100644
index 7bc1b43..0000000
--- a/src/trace_processor/db/overlays/null_overlay.cc
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "src/trace_processor/db/overlays/null_overlay.h"
-#include "perfetto/ext/base/flat_hash_map.h"
-#include "src/trace_processor/containers/bit_vector.h"
-#include "src/trace_processor/db/overlays/types.h"
-
-namespace perfetto {
-namespace trace_processor {
-namespace overlays {
-
-using Range = RowMap::Range;
-
-StorageRange NullOverlay::MapToStorageRange(TableRange t_range) const {
-  uint32_t start = non_null_->CountSetBits(t_range.range.start);
-  uint32_t end = non_null_->CountSetBits(t_range.range.end);
-
-  return StorageRange(start, end);
-}
-
-TableRangeOrBitVector NullOverlay::MapToTableRangeOrBitVector(
-    StorageRange s_range,
-    OverlayOp op) const {
-  PERFETTO_DCHECK(s_range.range.end <= non_null_->CountSetBits());
-
-  BitVector range_to_bv(s_range.range.start, false);
-  range_to_bv.Resize(s_range.range.end, true);
-
-  return TableRangeOrBitVector(
-      MapToTableBitVector(StorageBitVector{std::move(range_to_bv)}, op).bv);
-}
-
-TableBitVector NullOverlay::MapToTableBitVector(StorageBitVector s_bv,
-                                                OverlayOp op) const {
-  BitVector res = non_null_->Copy();
-  res.UpdateSetBits(s_bv.bv);
-
-  if (op != OverlayOp::kIsNull)
-    return {std::move(res)};
-
-  BitVector not_non_null = non_null_->Copy();
-  not_non_null.Not();
-
-  if (res.CountSetBits() == 0)
-    return {std::move(not_non_null)};
-
-  res.Or(not_non_null);
-  return {std::move(res)};
-}
-
-BitVector NullOverlay::IsStorageLookupRequired(
-    OverlayOp op,
-    const TableIndexVector& t_iv) const {
-  PERFETTO_DCHECK(t_iv.indices.size() <= non_null_->size());
-
-  if (op != OverlayOp::kOther)
-    return BitVector(t_iv.size(), false);
-
-  BitVector in_storage(static_cast<uint32_t>(t_iv.indices.size()), false);
-
-  // For each index in TableIndexVector check whether this index is in storage.
-  for (uint32_t i = 0; i < t_iv.indices.size(); ++i) {
-    if (non_null_->IsSet(t_iv.indices[i]))
-      in_storage.Set(i);
-  }
-
-  return in_storage;
-}
-
-StorageIndexVector NullOverlay::MapToStorageIndexVector(
-    TableIndexVector t_iv_with_idx_in_storage) const {
-  PERFETTO_DCHECK(t_iv_with_idx_in_storage.indices.size() <=
-                  non_null_->CountSetBits());
-
-  std::vector<uint32_t> storage_index_vector;
-  storage_index_vector.reserve(t_iv_with_idx_in_storage.indices.size());
-  for (auto t_idx : t_iv_with_idx_in_storage.indices) {
-    storage_index_vector.push_back(non_null_->CountSetBits(t_idx));
-  }
-
-  return StorageIndexVector({std::move(storage_index_vector)});
-}
-
-BitVector NullOverlay::IndexSearch(
-    OverlayOp op,
-    const TableIndexVector& t_iv_overlay_idx) const {
-  if (op == OverlayOp::kOther)
-    return BitVector(t_iv_overlay_idx.size(), false);
-
-  BitVector res(static_cast<uint32_t>(t_iv_overlay_idx.indices.size()), false);
-  if (op == OverlayOp::kIsNull) {
-    for (uint32_t i = 0; i < res.size(); ++i) {
-      if (!non_null_->IsSet(t_iv_overlay_idx.indices[i]))
-        res.Set(i);
-    }
-    return res;
-  }
-
-  PERFETTO_DCHECK(op == OverlayOp::kIsNotNull);
-  for (uint32_t i = 0; i < res.size(); ++i) {
-    if (non_null_->IsSet(t_iv_overlay_idx.indices[i]))
-      res.Set(i);
-  }
-  return res;
-}
-
-CostEstimatePerRow NullOverlay::EstimateCostPerRow(OverlayOp op) const {
-  // TODO(b/283763282): Replace with benchmarked data.
-  CostEstimatePerRow res;
-
-  // Two |BitVector::CountSetBits| calls.
-  res.to_storage_range = 100;
-
-  // Cost of |BitVector::UpdateSetBits|
-  res.to_table_bit_vector = 100;
-
-  if (op == OverlayOp::kOther) {
-    // Cost of |BitVector::IsSet| and |BitVector::Set|
-    res.is_storage_search_required = 10;
-
-    // Cost of iterating all set bits and looping the index vector divided by
-    // number of indices.
-    res.map_to_storage_index_vector = 100;
-
-    // Won't be called.
-    res.index_search = 0;
-  } else {
-    // Cost of creating trivial BitVector.
-    res.is_storage_search_required = 0;
-
-    // Won't be called
-    res.map_to_storage_index_vector = 0;
-
-    // Cost of calling |BitVector::IsSet|
-    res.index_search = 10;
-  }
-
-  return res;
-}
-
-}  // namespace overlays
-}  // namespace trace_processor
-}  // namespace perfetto
diff --git a/src/trace_processor/db/overlays/null_overlay.h b/src/trace_processor/db/overlays/null_overlay.h
deleted file mode 100644
index 5753fa3..0000000
--- a/src/trace_processor/db/overlays/null_overlay.h
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef SRC_TRACE_PROCESSOR_DB_OVERLAYS_NULL_OVERLAY_H_
-#define SRC_TRACE_PROCESSOR_DB_OVERLAYS_NULL_OVERLAY_H_
-
-#include "src/trace_processor/db/overlays/storage_overlay.h"
-#include "src/trace_processor/db/overlays/types.h"
-
-namespace perfetto {
-namespace trace_processor {
-namespace overlays {
-
-// Introduces the layer of nullability - spreads out the storage with nulls
-// using BitVector.
-class NullOverlay : public StorageOverlay {
- public:
-  explicit NullOverlay(const BitVector* null) : non_null_(std::move(null)) {}
-
-  StorageRange MapToStorageRange(TableRange) const override;
-
-  TableRangeOrBitVector MapToTableRangeOrBitVector(StorageRange,
-                                                   OverlayOp) const override;
-
-  TableBitVector MapToTableBitVector(StorageBitVector,
-                                     OverlayOp) const override;
-
-  BitVector IsStorageLookupRequired(OverlayOp,
-                                    const TableIndexVector&) const override;
-
-  StorageIndexVector MapToStorageIndexVector(TableIndexVector) const override;
-
-  BitVector IndexSearch(OverlayOp, const TableIndexVector&) const override;
-
-  CostEstimatePerRow EstimateCostPerRow(OverlayOp) const override;
-
- private:
-  // Non null data in the overlay.
-  const BitVector* non_null_;
-};
-
-}  // namespace overlays
-}  // namespace trace_processor
-}  // namespace perfetto
-
-#endif  // SRC_TRACE_PROCESSOR_DB_OVERLAYS_NULL_OVERLAY_H_
diff --git a/src/trace_processor/db/overlays/null_overlay_unittest.cc b/src/trace_processor/db/overlays/null_overlay_unittest.cc
deleted file mode 100644
index aff9474..0000000
--- a/src/trace_processor/db/overlays/null_overlay_unittest.cc
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "src/trace_processor/db/overlays/null_overlay.h"
-#include "test/gtest_and_gmock.h"
-
-namespace perfetto {
-namespace trace_processor {
-namespace overlays {
-namespace {
-
-TEST(NullOverlay, MapToStorageRangeOutsideBoundary) {
-  BitVector bv{0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0};
-  NullOverlay overlay(&bv);
-  StorageRange r = overlay.MapToStorageRange(TableRange(1, 6));
-
-  ASSERT_EQ(r.range.start, 0u);
-  ASSERT_EQ(r.range.end, 2u);
-}
-
-TEST(NullOverlay, MapToStorageRangeOnBoundary) {
-  BitVector bv{0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0};
-  NullOverlay overlay(&bv);
-  StorageRange r = overlay.MapToStorageRange(TableRange(3, 8));
-
-  ASSERT_EQ(r.range.start, 1u);
-  ASSERT_EQ(r.range.end, 4u);
-}
-
-TEST(NullOverlay, MapToTableRangeOutsideBoundary) {
-  BitVector bv{0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0};
-  NullOverlay overlay(&bv);
-  auto r =
-      overlay.MapToTableRangeOrBitVector(StorageRange(1, 3), OverlayOp::kOther);
-
-  // All set bits between |bv| index 3 and 6.
-  ASSERT_EQ(std::move(r).TakeIfBitVector().CountSetBits(), 2u);
-}
-
-TEST(NullOverlay, MapToTableRangeOnBoundary) {
-  BitVector bv{0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0};
-  NullOverlay overlay(&bv);
-  auto r =
-      overlay.MapToTableRangeOrBitVector(StorageRange(0, 5), OverlayOp::kOther);
-
-  ASSERT_EQ(std::move(r).TakeIfBitVector().CountSetBits(), 5u);
-}
-
-TEST(NullOverlay, MapToTableBitVector) {
-  BitVector bv{0, 1, 1, 0, 0, 1, 1, 0};
-  NullOverlay overlay(&bv);
-
-  BitVector storage_bv{0, 1, 0, 1};
-  TableBitVector table_bv =
-      overlay.MapToTableBitVector({std::move(storage_bv)}, OverlayOp::kOther);
-
-  ASSERT_EQ(table_bv.bv.CountSetBits(), 2u);
-  ASSERT_TRUE(table_bv.bv.IsSet(2));
-  ASSERT_TRUE(table_bv.bv.IsSet(6));
-}
-
-TEST(NullOverlay, MapToTableBitVectorIsNull) {
-  BitVector bv{0, 1, 1, 0, 0, 1, 1, 0};
-  NullOverlay overlay(&bv);
-
-  BitVector storage_bv{0, 1, 0, 1};
-  TableBitVector table_bv =
-      overlay.MapToTableBitVector({std::move(storage_bv)}, OverlayOp::kIsNull);
-
-  // Result is all of the zeroes from |bv| and set bits from |storage_bv|
-  // 1, 0, 1, 1, 1, 0, 1, 1
-
-  ASSERT_EQ(table_bv.bv.CountSetBits(), 6u);
-  ASSERT_FALSE(table_bv.bv.IsSet(1));
-  ASSERT_FALSE(table_bv.bv.IsSet(5));
-}
-
-TEST(NullOverlay, IsStorageLookupRequiredNullOp) {
-  BitVector bv{0, 1, 1, 0, 0, 1, 1, 0};
-  NullOverlay overlay(&bv);
-
-  std::vector<uint32_t> table_idx{0, 2, 4, 6};
-  BitVector lookup_bv =
-      overlay.IsStorageLookupRequired(OverlayOp::kIsNull, {table_idx});
-
-  ASSERT_EQ(lookup_bv.CountSetBits(), 0u);
-}
-
-TEST(NullOverlay, IsStorageLookupRequiredOtherOp) {
-  BitVector bv{0, 1, 1, 0, 0, 1, 1, 0};
-  NullOverlay overlay(&bv);
-
-  std::vector<uint32_t> table_idx{0, 2, 4, 6};
-  BitVector lookup_bv =
-      overlay.IsStorageLookupRequired(OverlayOp::kOther, {table_idx});
-
-  ASSERT_EQ(lookup_bv.size(), 4u);
-  ASSERT_EQ(lookup_bv.CountSetBits(), 2u);
-  ASSERT_TRUE(lookup_bv.IsSet(1));
-  ASSERT_TRUE(lookup_bv.IsSet(3));
-}
-
-TEST(NullOverlay, MapToStorageIndexVector) {
-  BitVector bv{0, 1, 1, 0, 0, 1, 1, 0};
-  NullOverlay overlay(&bv);
-
-  std::vector<uint32_t> table_idx{1, 5, 2};
-  StorageIndexVector storage_iv = overlay.MapToStorageIndexVector({table_idx});
-
-  std::vector<uint32_t> res{0, 2, 1};
-  ASSERT_EQ(storage_iv.indices, res);
-}
-
-TEST(NullOverlay, IndexSearchOtherOp) {
-  BitVector bv{0, 1, 1, 0, 0, 1, 1, 0};
-  NullOverlay overlay(&bv);
-
-  std::vector<uint32_t> table_idx{0, 3, 4};
-  BitVector idx_search_bv = overlay.IndexSearch(OverlayOp::kOther, {table_idx});
-
-  ASSERT_EQ(idx_search_bv.CountSetBits(), 0u);
-}
-
-TEST(NullOverlay, IndexSearchIsNullOp) {
-  BitVector bv{0, 1, 1, 0, 0, 1, 1, 0};
-  NullOverlay overlay(&bv);
-
-  std::vector<uint32_t> table_idx{0, 3, 4};
-  BitVector idx_search_bv =
-      overlay.IndexSearch(OverlayOp::kIsNull, {table_idx});
-
-  ASSERT_EQ(idx_search_bv.size(), 3u);
-  ASSERT_EQ(idx_search_bv.CountSetBits(), 3u);
-}
-
-TEST(NullOverlay, IndexSearchIsNotNullOp) {
-  BitVector bv{0, 1, 1, 0, 0, 1, 1, 0};
-  NullOverlay overlay(&bv);
-
-  std::vector<uint32_t> table_idx{0, 3, 4};
-  BitVector idx_search_bv =
-      overlay.IndexSearch(OverlayOp::kIsNotNull, {table_idx});
-
-  ASSERT_EQ(idx_search_bv.size(), 3u);
-  ASSERT_EQ(idx_search_bv.CountSetBits(), 0u);
-}
-
-}  // namespace
-}  // namespace overlays
-}  // namespace trace_processor
-}  // namespace perfetto
diff --git a/src/trace_processor/db/overlays/selector_overlay.cc b/src/trace_processor/db/overlays/selector_overlay.cc
deleted file mode 100644
index bf31071..0000000
--- a/src/trace_processor/db/overlays/selector_overlay.cc
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "src/trace_processor/db/overlays/selector_overlay.h"
-#include "src/trace_processor/containers/bit_vector.h"
-#include "src/trace_processor/db/overlays/types.h"
-
-namespace perfetto {
-namespace trace_processor {
-namespace overlays {
-
-using Range = RowMap::Range;
-
-StorageRange SelectorOverlay::MapToStorageRange(TableRange t_range) const {
-  // Table data is smaller than Storage, so we need to expand the data.
-  return StorageRange{
-      Range(selected_->IndexOfNthSet(t_range.range.start),
-            selected_->IndexOfNthSet(t_range.range.end - 1) + 1)};
-}
-
-TableRangeOrBitVector SelectorOverlay::MapToTableRangeOrBitVector(
-    StorageRange s_range,
-    OverlayOp) const {
-  if (s_range.range.size() == 0)
-    return TableRangeOrBitVector(Range());
-
-  uint32_t start = selected_->CountSetBits(s_range.range.start);
-  uint32_t end = selected_->CountSetBits(s_range.range.end);
-
-  return TableRangeOrBitVector(Range(start, end));
-}
-
-TableBitVector SelectorOverlay::MapToTableBitVector(StorageBitVector s_bv,
-                                                    OverlayOp) const {
-  PERFETTO_DCHECK(s_bv.bv.size() <= selected_->size());
-  BitVector res(selected_->CountSetBits());
-  // TODO(b/283763282): Implement this variation of |UpdateSetBits| in
-  // BitVector.
-  for (auto it = selected_->IterateSetBits(); it && it.index() < s_bv.bv.size();
-       it.Next()) {
-    if (s_bv.bv.IsSet(it.index()))
-      res.Set(it.ordinal());
-  }
-  return TableBitVector({std::move(res)});
-}
-
-BitVector SelectorOverlay::IsStorageLookupRequired(
-    OverlayOp,
-    const TableIndexVector& t_iv) const {
-  return BitVector(static_cast<uint32_t>(t_iv.indices.size()), true);
-}
-
-StorageIndexVector SelectorOverlay::MapToStorageIndexVector(
-    TableIndexVector t_iv) const {
-  PERFETTO_DCHECK(t_iv.indices.empty() ||
-                  *std::max_element(t_iv.indices.begin(), t_iv.indices.end()) <=
-                      selected_->size());
-  // To go from TableIndexVector to StorageIndexVector we need to find index in
-  // |selector_| by looking only into set bits.
-  std::vector<uint32_t> s_iv;
-  s_iv.reserve(t_iv.indices.size());
-  for (auto t_idx : t_iv.indices) {
-    s_iv.push_back(selected_->IndexOfNthSet(t_idx));
-  }
-
-  return StorageIndexVector({std::move(s_iv)});
-}
-
-BitVector SelectorOverlay::IndexSearch(OverlayOp,
-                                       const TableIndexVector&) const {
-  // |t_iv| doesn't contain any values that are null in |selected_| as other
-  // overlays are not able to access them. This function should not be called.
-  PERFETTO_FATAL("Should not be called in SelectorOverlay.");
-}
-
-CostEstimatePerRow SelectorOverlay::EstimateCostPerRow(OverlayOp) const {
-  CostEstimatePerRow estimate;
-  // Cost of two |IndexOfNthSet|
-  estimate.to_storage_range = 20;
-  // Cost of iterating over all selected bits and calling |IsSet| each time (and
-  // |Set| if true)
-  estimate.to_table_bit_vector = 100;
-  // Cost of creating trivial vector of 1s
-  estimate.is_storage_search_required = 0;
-  // Cost of |IndexOfNthSet| for each row
-  estimate.map_to_storage_index_vector = 10;
-  // Shouldn't be called
-  estimate.index_search = 0;
-
-  return estimate;
-}
-
-}  // namespace overlays
-}  // namespace trace_processor
-}  // namespace perfetto
diff --git a/src/trace_processor/db/overlays/selector_overlay.h b/src/trace_processor/db/overlays/selector_overlay.h
deleted file mode 100644
index ac591d3..0000000
--- a/src/trace_processor/db/overlays/selector_overlay.h
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef SRC_TRACE_PROCESSOR_DB_OVERLAYS_SELECTOR_OVERLAY_H_
-#define SRC_TRACE_PROCESSOR_DB_OVERLAYS_SELECTOR_OVERLAY_H_
-
-#include "src/trace_processor/db/overlays/storage_overlay.h"
-#include "src/trace_processor/db/overlays/types.h"
-
-namespace perfetto {
-namespace trace_processor {
-namespace overlays {
-
-// Overlay responsible for selecting specific rows from Storage.
-class SelectorOverlay : public StorageOverlay {
- public:
-  explicit SelectorOverlay(const BitVector* selected) : selected_(selected) {}
-
-  StorageRange MapToStorageRange(TableRange) const override;
-
-  TableRangeOrBitVector MapToTableRangeOrBitVector(StorageRange,
-                                                   OverlayOp) const override;
-
-  TableBitVector MapToTableBitVector(StorageBitVector,
-                                     OverlayOp) const override;
-
-  BitVector IsStorageLookupRequired(OverlayOp,
-                                    const TableIndexVector&) const override;
-
-  StorageIndexVector MapToStorageIndexVector(TableIndexVector) const override;
-
-  BitVector IndexSearch(OverlayOp, const TableIndexVector&) const override;
-
-  CostEstimatePerRow EstimateCostPerRow(OverlayOp) const override;
-
- private:
-  const BitVector* selected_;
-};
-
-}  // namespace overlays
-}  // namespace trace_processor
-}  // namespace perfetto
-
-#endif  // SRC_TRACE_PROCESSOR_DB_OVERLAYS_SELECTOR_OVERLAY_H_
diff --git a/src/trace_processor/db/overlays/selector_overlay_unittest.cc b/src/trace_processor/db/overlays/selector_overlay_unittest.cc
deleted file mode 100644
index 28b4f02..0000000
--- a/src/trace_processor/db/overlays/selector_overlay_unittest.cc
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "src/trace_processor/db/overlays/selector_overlay.h"
-#include "src/trace_processor/db/overlays/types.h"
-#include "test/gtest_and_gmock.h"
-
-namespace perfetto {
-namespace trace_processor {
-namespace overlays {
-namespace {
-
-TEST(SelectorOverlay, MapToStorageRangeFirst) {
-  BitVector selector{0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1};
-  SelectorOverlay overlay(&selector);
-  StorageRange r = overlay.MapToStorageRange(TableRange(1, 4));
-
-  ASSERT_EQ(r.range.start, 4u);
-  ASSERT_EQ(r.range.end, 8u);
-}
-
-TEST(SelectorOverlay, MapToStorageRangeSecond) {
-  BitVector selector{0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0};
-  SelectorOverlay overlay(&selector);
-  StorageRange r = overlay.MapToStorageRange(TableRange(1, 3));
-
-  ASSERT_EQ(r.range.start, 4u);
-  ASSERT_EQ(r.range.end, 7u);
-}
-
-TEST(SelectorOverlay, MapToTableRangeFirst) {
-  BitVector selector{0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1};
-  SelectorOverlay overlay(&selector);
-  auto r =
-      overlay.MapToTableRangeOrBitVector(StorageRange(2, 5), OverlayOp::kOther);
-
-  Range range = std::move(r).TakeIfRange();
-  ASSERT_EQ(range.start, 1u);
-  ASSERT_EQ(range.end, 3u);
-}
-
-TEST(SelectorOverlay, MapToTableRangeSecond) {
-  BitVector selector{0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0};
-  SelectorOverlay overlay(&selector);
-  auto r = overlay.MapToTableRangeOrBitVector(StorageRange(0, 10),
-                                              OverlayOp::kOther);
-
-  Range range = std::move(r).TakeIfRange();
-  ASSERT_EQ(range.start, 0u);
-  ASSERT_EQ(range.end, 6u);
-}
-
-TEST(SelectorOverlay, MapToTableBitVector) {
-  BitVector selector{0, 1, 1, 0, 0, 1, 1, 0};
-  SelectorOverlay overlay(&selector);
-
-  BitVector storage_bv{1, 0, 1, 0, 1, 0, 1, 0};
-  TableBitVector table_bv =
-      overlay.MapToTableBitVector({std::move(storage_bv)}, OverlayOp::kOther);
-
-  ASSERT_EQ(table_bv.bv.size(), 4u);
-  ASSERT_EQ(table_bv.bv.CountSetBits(), 2u);
-  ASSERT_TRUE(table_bv.bv.IsSet(1));
-  ASSERT_TRUE(table_bv.bv.IsSet(3));
-}
-
-TEST(SelectorOverlay, IsStorageLookupRequired) {
-  BitVector selector{0, 1, 1, 0, 0, 1, 1, 0};
-  SelectorOverlay overlay(&selector);
-
-  std::vector<uint32_t> table_idx{0, 1, 2};
-  BitVector lookup_bv =
-      overlay.IsStorageLookupRequired(OverlayOp::kIsNull, {table_idx});
-
-  ASSERT_EQ(lookup_bv.size(), 3u);
-}
-
-TEST(SelectorOverlay, MapToStorageIndexVector) {
-  BitVector selector{0, 1, 1, 0, 0, 1, 1, 0};
-  SelectorOverlay overlay(&selector);
-
-  std::vector<uint32_t> table_idx{1, 3, 2};
-  StorageIndexVector storage_iv = overlay.MapToStorageIndexVector({table_idx});
-
-  std::vector<uint32_t> res{2, 6, 5};
-  ASSERT_EQ(storage_iv.indices, res);
-}
-
-}  // namespace
-}  // namespace overlays
-}  // namespace trace_processor
-}  // namespace perfetto
diff --git a/src/trace_processor/db/overlays/storage_overlay.cc b/src/trace_processor/db/overlays/storage_overlay.cc
deleted file mode 100644
index 56897f2..0000000
--- a/src/trace_processor/db/overlays/storage_overlay.cc
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "src/trace_processor/db/overlays/storage_overlay.h"
-
-namespace perfetto {
-namespace trace_processor {
-namespace overlays {
-
-StorageOverlay::~StorageOverlay() = default;
-
-}  // namespace overlays
-}  // namespace trace_processor
-}  // namespace perfetto
diff --git a/src/trace_processor/db/overlays/storage_overlay.h b/src/trace_processor/db/overlays/storage_overlay.h
deleted file mode 100644
index d6ad8df..0000000
--- a/src/trace_processor/db/overlays/storage_overlay.h
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef SRC_TRACE_PROCESSOR_DB_OVERLAYS_STORAGE_OVERLAY_H_
-#define SRC_TRACE_PROCESSOR_DB_OVERLAYS_STORAGE_OVERLAY_H_
-
-#include "src/trace_processor/db/overlays/types.h"
-
-namespace perfetto {
-namespace trace_processor {
-namespace overlays {
-
-// Abstract class which is layered on top of Storage transforming how the
-// storage should be interpreted. The main purpose of this class is to be
-// responsible for for mapping between table indices and storage indices (i.e.
-// in both directions).
-//
-// Overlays are designed to be "layered" on top of each other (i.e. the mapping
-// algorithms compose). To make it easier to reason about this class, we
-// ignore any other overlays and assume we are mapping directly between table
-// indices and storage indices. i.e. even if "table indices" we are working with
-// come from another overlay, we still consider them as having come from the
-// table and vice versa for "storage indices".
-//
-// The core functions in this class work with input and output arguments which
-// use the same data structure but have different semantics (i.e. input might
-// be in terms of storage indices and output might be in terms of table
-// indices).
-//
-// For this reason, we use the defined wrapper structs which "tag" the data
-// structure with the semantics.
-class StorageOverlay {
- public:
-  virtual ~StorageOverlay();
-
-  // Maps a range of indices in table space to an equivalent range of
-  // indices in the storage space.
-  virtual StorageRange MapToStorageRange(TableRange) const = 0;
-
-  // Returns the smallest Range or BitVector containing all of the elements
-  // matching the OverlayOp.
-  virtual TableRangeOrBitVector MapToTableRangeOrBitVector(StorageRange,
-                                                           OverlayOp) const = 0;
-
-  // Maps a BitVector of indices in storage space to an equivalent range of
-  // indices in the table space.
-  virtual TableBitVector MapToTableBitVector(StorageBitVector,
-                                             OverlayOp) const = 0;
-
-  // Returns a BitVector where each boolean indicates if the corresponding index
-  // in |indices| needs to be mapped and searched in the storage or if the
-  // overlay can provide the answer without storage lookup.
-  virtual BitVector IsStorageLookupRequired(OverlayOp,
-                                            const TableIndexVector&) const = 0;
-
-  // Maps a vector of indices in the table space with an equivalent range
-  // of indices in the storage space.
-  //
-  // Note: callers must call |IsStorageSearchRequired| first and only call
-  // this method with indices where |IsStorageSearchRequired| returned true.
-  // Passing indices here which are not mappable is undefined behaviour.
-  virtual StorageIndexVector MapToStorageIndexVector(
-      TableIndexVector) const = 0;
-
-  // Given a vector of indices given in table space, returns whether the index
-  // matches the operation given by |op|.
-  //
-  // Note: callers must call |IsStorageSearchRequired| first and only call
-  // this method with indices where |IsStorageSearchRequired| returned false.
-  // Passing indices here which are not searchable is undefined behaviour.
-  virtual BitVector IndexSearch(OverlayOp, const TableIndexVector&) const = 0;
-
-  // Estimates the per-row costs of the methods of this class. Allows for
-  // deciding which algorithm to use to search/sort the storage.
-  virtual CostEstimatePerRow EstimateCostPerRow(OverlayOp) const = 0;
-};
-
-}  // namespace overlays
-}  // namespace trace_processor
-}  // namespace perfetto
-
-#endif  // SRC_TRACE_PROCESSOR_DB_OVERLAYS_STORAGE_OVERLAY_H_
diff --git a/src/trace_processor/db/overlays/types.h b/src/trace_processor/db/overlays/types.h
deleted file mode 100644
index 8975084..0000000
--- a/src/trace_processor/db/overlays/types.h
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#ifndef SRC_TRACE_PROCESSOR_DB_OVERLAYS_TYPES_H_
-#define SRC_TRACE_PROCESSOR_DB_OVERLAYS_TYPES_H_
-
-#include "perfetto/base/logging.h"
-#include "src/trace_processor/containers/bit_vector.h"
-#include "src/trace_processor/db/storage/types.h"
-
-namespace perfetto {
-namespace trace_processor {
-namespace overlays {
-
-using Range = RowMap::Range;
-
-// A range of indices in the table space.
-struct TableRange {
-  TableRange(uint32_t start, uint32_t end) : range(start, end) {}
-  explicit TableRange(Range r) : range(r) {}
-
-  Range range;
-};
-
-// A range of indices in the storage space.
-struct StorageRange {
-  StorageRange(uint32_t start, uint32_t end) : range(start, end) {}
-  explicit StorageRange(Range r) : range(r) {}
-
-  Range range;
-};
-
-// A BitVector with set bits corresponding to indices in the table space.
-struct TableBitVector {
-  BitVector bv;
-};
-
-// A BitVector with set bits corresponding to indices in the table space.
-struct StorageBitVector {
-  BitVector bv;
-};
-
-// RangeOrBitVector of indices in the table space.
-struct TableRangeOrBitVector {
-  explicit TableRangeOrBitVector(Range range) : val(range) {}
-  explicit TableRangeOrBitVector(BitVector bv) : val(std::move(bv)) {}
-  explicit TableRangeOrBitVector(RangeOrBitVector r_or_bv)
-      : val(std::move(r_or_bv)) {}
-
-  bool IsRange() const { return val.IsRange(); }
-  bool IsBitVector() const { return val.IsBitVector(); }
-
-  BitVector TakeIfBitVector() && { return std::move(val).TakeIfBitVector(); }
-  Range TakeIfRange() && { return std::move(val).TakeIfRange(); }
-
-  RangeOrBitVector val = RangeOrBitVector(Range());
-};
-
-// Represents a vector of indices in the table space.
-struct TableIndexVector {
-  std::vector<uint32_t> indices;
-
-  uint32_t size() const { return static_cast<uint32_t>(indices.size()); }
-};
-
-// Represents a vector of indices in the storage space.
-struct StorageIndexVector {
-  std::vector<uint32_t> indices;
-
-  uint32_t size() const { return static_cast<uint32_t>(indices.size()); }
-};
-
-// A subset of FilterOp containing operations which can be handled by
-// overlays.
-enum class OverlayOp {
-  kIsNull,
-  kIsNotNull,
-  kOther,
-};
-
-inline OverlayOp FilterOpToOverlayOp(FilterOp op) {
-  if (op == FilterOp::kIsNull) {
-    return OverlayOp::kIsNull;
-  }
-  if (op == FilterOp::kIsNotNull) {
-    return OverlayOp::kIsNotNull;
-  }
-  return OverlayOp::kOther;
-}
-
-// Contains estimates of the cost for each of method in this class per row.
-struct CostEstimatePerRow {
-  uint32_t to_storage_range;
-  uint32_t to_table_bit_vector;
-  uint32_t is_storage_search_required;
-  uint32_t map_to_storage_index_vector;
-  uint32_t index_search;
-};
-
-}  // namespace overlays
-}  // namespace trace_processor
-}  // namespace perfetto
-
-#endif  // SRC_TRACE_PROCESSOR_DB_OVERLAYS_TYPES_H_
diff --git a/src/trace_processor/db/query_executor.cc b/src/trace_processor/db/query_executor.cc
index 21e4c7c..046aee4 100644
--- a/src/trace_processor/db/query_executor.cc
+++ b/src/trace_processor/db/query_executor.cc
@@ -24,16 +24,16 @@
 
 #include "perfetto/base/logging.h"
 #include "perfetto/ext/base/status_or.h"
+#include "perfetto/trace_processor/basic_types.h"
 #include "src/trace_processor/containers/string_pool.h"
-#include "src/trace_processor/db/overlays/arrangement_overlay.h"
-#include "src/trace_processor/db/overlays/null_overlay.h"
-#include "src/trace_processor/db/overlays/selector_overlay.h"
-#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/arrangement_storage.h"
+#include "src/trace_processor/db/storage/dense_null_storage.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/null_storage.h"
 #include "src/trace_processor/db/storage/numeric_storage.h"
+#include "src/trace_processor/db/storage/selector_storage.h"
 #include "src/trace_processor/db/storage/set_id_storage.h"
 #include "src/trace_processor/db/storage/string_storage.h"
 #include "src/trace_processor/db/storage/types.h"
@@ -45,96 +45,25 @@
 namespace {
 
 using Range = RowMap::Range;
-using OverlayOp = overlays::OverlayOp;
-using StorageRange = overlays::StorageRange;
-using TableRange = overlays::TableRange;
 using Storage = storage::Storage;
-using StorageOverlay = overlays::StorageOverlay;
-using TableIndexVector = overlays::TableIndexVector;
-using StorageIndexVector = overlays::StorageIndexVector;
-using TableBitVector = overlays::TableBitVector;
-using StorageBitVector = overlays::StorageBitVector;
-using OverlaysVec = base::SmallVector<const overlays::StorageOverlay*,
-                                      QueryExecutor::kMaxOverlayCount>;
 
-// Helper struct to simplify operations on |global| and |current| sets of
-// indices. Having this coupling enables efficient implementation of
-// IndexedColumnFilter.
-struct IndexFilterHelper {
-  explicit IndexFilterHelper(std::vector<uint32_t> indices) {
-    current_ = indices;
-    global_ = std::move(indices);
-  }
-
-  // Removes pairs of elements that are not set in the |bv| and returns
-  // Indices made of them.
-  static std::pair<IndexFilterHelper, IndexFilterHelper> Partition(
-      IndexFilterHelper indices,
-      const BitVector& bv) {
-    if (bv.CountSetBits() == 0) {
-      return {IndexFilterHelper(), indices};
-    }
-
-    IndexFilterHelper set_partition;
-    IndexFilterHelper non_set_partition;
-    for (auto it = bv.IterateAllBits(); it; it.Next()) {
-      uint32_t idx = it.index();
-      if (it.IsSet()) {
-        set_partition.PushBack({indices.current_[idx], indices.global_[idx]});
-      } else {
-        non_set_partition.PushBack(
-            {indices.current_[idx], indices.global_[idx]});
-      }
-    }
-    return {set_partition, non_set_partition};
-  }
-
-  // Removes pairs of elements that are not set in the |bv|. Returns count of
-  // removed elements.
-  uint32_t KeepAtSet(BitVector filter_nulls) {
-    PERFETTO_DCHECK(filter_nulls.size() == current_.size() ||
-                    filter_nulls.CountSetBits() == 0);
-    uint32_t count_removed =
-        static_cast<uint32_t>(current_.size()) - filter_nulls.CountSetBits();
-
-    uint32_t i = 0;
-    auto filter = [&i, &filter_nulls](uint32_t) {
-      return !filter_nulls.IsSet(i++);
-    };
-
-    auto current_it = std::remove_if(current_.begin(), current_.end(), filter);
-    current_.erase(current_it, current_.end());
-
-    i = 0;
-    auto global_it = std::remove_if(global_.begin(), global_.end(), filter);
-    global_.erase(global_it, global_.end());
-
-    return count_removed;
-  }
-
-  std::vector<uint32_t>& current() { return current_; }
-
-  std::vector<uint32_t>& global() { return global_; }
-
- private:
-  IndexFilterHelper() = default;
-
-  void PushBack(std::pair<uint32_t, uint32_t> cur_and_global_idx) {
-    current_.push_back(cur_and_global_idx.first);
-    global_.push_back(cur_and_global_idx.second);
-  }
-
-  std::vector<uint32_t> current_;
-  std::vector<uint32_t> global_;
-};
 }  // namespace
 
 void QueryExecutor::FilterColumn(const Constraint& c,
-                                 const SimpleColumn& col,
+                                 const storage::Storage& storage,
                                  RowMap* rm) {
+  // Shortcut of empty row map.
   if (rm->empty())
     return;
 
+  // Comparison of NULL with any operation apart from |IS_NULL| and
+  // |IS_NOT_NULL| should return no rows.
+  if (c.value.is_null() && c.op != FilterOp::kIsNull &&
+      c.op != FilterOp::kIsNotNull) {
+    rm->Clear();
+    return;
+  }
+
   uint32_t rm_size = rm->size();
   uint32_t rm_first = rm->Get(0);
   uint32_t rm_last = rm->Get(rm_size - 1);
@@ -146,43 +75,22 @@
   bool disallows_index_search = rm->IsRange();
   bool prefers_index_search =
       rm->IsIndexVector() || rm_size < 1024 || rm_size * 10 < range_size;
+
   if (!disallows_index_search && prefers_index_search) {
-    *rm = IndexSearch(c, col, rm);
+    IndexSearch(c, storage, rm);
     return;
   }
-  LinearSearch(c, col, rm);
+  LinearSearch(c, storage, rm);
 }
 
 void QueryExecutor::LinearSearch(const Constraint& c,
-                                 const SimpleColumn& col,
+                                 const storage::Storage& storage,
                                  RowMap* rm) {
   // TODO(b/283763282): Align these to word boundaries.
-  TableRange bounds{Range(rm->Get(0), rm->Get(rm->size() - 1) + 1)};
-
-  // Translate the bounds to the storage level.
-  for (const auto& overlay : col.overlays) {
-    bounds = TableRange({overlay->MapToStorageRange(bounds).range});
-  }
+  Range bounds(rm->Get(0), rm->Get(rm->size() - 1) + 1);
 
   // Search the storage.
-  overlays::TableRangeOrBitVector res(
-      col.storage->Search(c.op, c.value, bounds.range));
-
-  // Translate the result to global level.
-  OverlayOp op = overlays::FilterOpToOverlayOp(c.op);
-  for (uint32_t i = 0; i < col.overlays.size(); ++i) {
-    uint32_t rev_i = static_cast<uint32_t>(col.overlays.size()) - 1 - i;
-
-    if (res.IsBitVector()) {
-      TableBitVector t_bv = col.overlays[rev_i]->MapToTableBitVector(
-          StorageBitVector{std::move(res).TakeIfBitVector()}, op);
-      res.val = RangeOrBitVector(std::move(t_bv.bv));
-    } else {
-      res = col.overlays[rev_i]->MapToTableRangeOrBitVector(
-          StorageRange(std::move(res).TakeIfRange()), op);
-    }
-  }
-
+  RangeOrBitVector res = storage.Search(c.op, c.value, bounds);
   if (rm->IsRange()) {
     if (res.IsRange()) {
       Range range = std::move(res).TakeIfRange();
@@ -203,75 +111,44 @@
   rm->Intersect(RowMap(std::move(res).TakeIfBitVector()));
 }
 
-RowMap QueryExecutor::IndexSearch(const Constraint& c,
-                                  const SimpleColumn& col,
-                                  RowMap* rm) {
+void QueryExecutor::IndexSearch(const Constraint& c,
+                                const storage::Storage& storage,
+                                RowMap* rm) {
   // Create outmost TableIndexVector.
   std::vector<uint32_t> table_indices = std::move(*rm).TakeAsIndexVector();
 
-  // Datastructures for storing data across overlays.
-  IndexFilterHelper to_filter(std::move(table_indices));
-  std::vector<uint32_t> matched;
-  uint32_t count_removed = 0;
-  uint32_t count_starting_indices =
-      static_cast<uint32_t>(to_filter.current().size());
+  RangeOrBitVector matched = storage.IndexSearch(
+      c.op, c.value, table_indices.data(),
+      static_cast<uint32_t>(table_indices.size()), false /* sorted */);
 
-  // Fetch the list of indices that require storage lookup and deal with all
-  // of the indices that can be compared before it.
-  OverlayOp op = overlays::FilterOpToOverlayOp(c.op);
-  for (const auto& overlay : col.overlays) {
-    BitVector partition =
-        overlay->IsStorageLookupRequired(op, {to_filter.current()});
-
-    // Most overlays don't require partitioning.
-    if (partition.CountSetBits() == partition.size()) {
-      to_filter.current() =
-          overlay->MapToStorageIndexVector({to_filter.current()}).indices;
-      continue;
-    }
-
-    // Separate indices that don't require storage lookup. Those can be dealt
-    // with in each pass.
-    auto [storage_lookup, no_storage_lookup] =
-        IndexFilterHelper::Partition(to_filter, partition);
-    to_filter = storage_lookup;
-
-    // Erase the values which don't match the constraint and add the
-    // remaining ones to the result.
-    BitVector valid_bv =
-        overlay->IndexSearch(op, {no_storage_lookup.current()});
-    count_removed += no_storage_lookup.KeepAtSet(std::move(valid_bv));
-    matched.insert(matched.end(), no_storage_lookup.global().begin(),
-                   no_storage_lookup.global().end());
-
-    // Update the current indices to the next storage overlay.
-    to_filter.current() =
-        overlay->MapToStorageIndexVector({to_filter.current()}).indices;
+  if (matched.IsBitVector()) {
+    BitVector res = std::move(matched).TakeIfBitVector();
+    uint32_t i = 0;
+    table_indices.erase(
+        std::remove_if(table_indices.begin(), table_indices.end(),
+                       [&i, &res](uint32_t) { return !res.IsSet(i++); }),
+        table_indices.end());
+    *rm = RowMap(std::move(table_indices));
+    return;
   }
 
-  RangeOrBitVector matched_in_storage = col.storage->IndexSearch(
-      c.op, c.value, to_filter.current().data(),
-      static_cast<uint32_t>(to_filter.current().size()));
-
+  Range res = std::move(matched).TakeIfRange();
+  if (res.size() == 0) {
+    rm->Clear();
+    return;
+  }
+  if (res.size() == table_indices.size()) {
+    return;
+  }
   // TODO(b/283763282): Remove after implementing extrinsic binary search.
-  PERFETTO_DCHECK(matched_in_storage.IsBitVector());
-
-  count_removed +=
-      to_filter.KeepAtSet(std::move(matched_in_storage).TakeIfBitVector());
-  matched.insert(matched.end(), to_filter.global().begin(),
-                 to_filter.global().end());
-
-  PERFETTO_CHECK(count_starting_indices == matched.size() + count_removed);
-
-  std::sort(matched.begin(), matched.end());
-  return RowMap(std::move(matched));
+  PERFETTO_FATAL("Extrinsic binary search is not implemented.");
 }
 
 RowMap QueryExecutor::FilterLegacy(const Table* table,
                                    const std::vector<Constraint>& c_vec) {
   RowMap rm(0, table->row_count());
   for (const auto& c : c_vec) {
-    if (rm.size() == 0) {
+    if (rm.empty()) {
       return rm;
     }
     const Column& col = table->columns()[c.col_idx];
@@ -285,13 +162,14 @@
     use_legacy = use_legacy || (col.overlay().size() != column_size &&
                                 col.overlay().row_map().IsRange());
 
-    // Mismatched types.
-    use_legacy = use_legacy || (overlays::FilterOpToOverlayOp(c.op) ==
-                                    overlays::OverlayOp::kOther &&
-                                col.type() != c.value.type);
-
-    // Dense columns.
-    use_legacy = use_legacy || col.IsDense();
+    // Comparing ints with doubles and doubles with ints.
+    bool int_with_double =
+        col.type() == SqlValue::kLong && c.value.type == SqlValue::kDouble;
+    bool double_with_int =
+        col.type() == SqlValue::kDouble && c.value.type == SqlValue::kLong;
+    use_legacy = use_legacy ||
+                 (c.op != FilterOp::kIsNull && c.op != FilterOp::kIsNotNull &&
+                  (int_with_double || double_with_int));
 
     // Extrinsically sorted columns.
     use_legacy = use_legacy ||
@@ -302,108 +180,97 @@
       continue;
     }
 
-    // String columns are inherently nullable: null values are signified with
-    // Id::Null().
-    PERFETTO_CHECK(
-        !(col.col_type() == ColumnType::kString && col.IsNullable()));
-
-    SimpleColumn s_col{OverlaysVec(), nullptr};
-
     // Create storage
     std::unique_ptr<Storage> storage;
     if (col.IsSetId()) {
       if (col.IsNullable()) {
-        storage.reset(new storage::SetIdStorage(
-            &col.storage<std::optional<uint32_t>>().non_null_vector()));
+        storage = std::make_unique<storage::SetIdStorage>(
+            &col.storage<std::optional<uint32_t>>().non_null_vector());
       } else {
-        storage.reset(
-            new storage::SetIdStorage(&col.storage<uint32_t>().vector()));
+        storage = std::make_unique<storage::SetIdStorage>(
+            &col.storage<uint32_t>().vector());
       }
     } else {
       switch (col.col_type()) {
         case ColumnType::kDummy:
-          storage.reset(new storage::DummyStorage());
+          storage = std::make_unique<storage::DummyStorage>();
           break;
         case ColumnType::kId:
-          storage.reset(new storage::IdStorage(column_size));
+          storage = std::make_unique<storage::IdStorage>(column_size);
           break;
         case ColumnType::kString:
-          storage.reset(new storage::StringStorage(
+          storage = std::make_unique<storage::StringStorage>(
               table->string_pool(), &col.storage<StringPool::Id>().vector(),
-              col.IsSorted()));
+              col.IsSorted());
           break;
         case ColumnType::kInt64:
           if (col.IsNullable()) {
-            storage.reset(new storage::NumericStorage<int64_t>(
+            storage = std::make_unique<storage::NumericStorage<int64_t>>(
                 &col.storage<std::optional<int64_t>>().non_null_vector(),
-                col.col_type(), col.IsSorted()));
+                col.col_type(), col.IsSorted());
 
           } else {
-            storage.reset(new storage::NumericStorage<int64_t>(
+            storage = std::make_unique<storage::NumericStorage<int64_t>>(
                 &col.storage<int64_t>().vector(), col.col_type(),
-                col.IsSorted()));
+                col.IsSorted());
           }
           break;
         case ColumnType::kUint32:
           if (col.IsNullable()) {
-            storage.reset(new storage::NumericStorage<uint32_t>(
+            storage = std::make_unique<storage::NumericStorage<uint32_t>>(
                 &col.storage<std::optional<uint32_t>>().non_null_vector(),
-                col.col_type(), col.IsSorted()));
-
+                col.col_type(), col.IsSorted());
           } else {
-            storage.reset(new storage::NumericStorage<uint32_t>(
+            storage = std::make_unique<storage::NumericStorage<uint32_t>>(
                 &col.storage<uint32_t>().vector(), col.col_type(),
-                col.IsSorted()));
+                col.IsSorted());
           }
           break;
         case ColumnType::kInt32:
           if (col.IsNullable()) {
-            storage.reset(new storage::NumericStorage<int32_t>(
+            storage = std::make_unique<storage::NumericStorage<int32_t>>(
                 &col.storage<std::optional<int32_t>>().non_null_vector(),
-                col.col_type(), col.IsSorted()));
-
+                col.col_type(), col.IsSorted());
           } else {
-            storage.reset(new storage::NumericStorage<int32_t>(
+            storage = std::make_unique<storage::NumericStorage<int32_t>>(
                 &col.storage<int32_t>().vector(), col.col_type(),
-                col.IsSorted()));
+                col.IsSorted());
           }
           break;
         case ColumnType::kDouble:
           if (col.IsNullable()) {
-            storage.reset(new storage::NumericStorage<double_t>(
-                &col.storage<std::optional<double_t>>().non_null_vector(),
-                col.col_type(), col.IsSorted()));
-
+            storage = std::make_unique<storage::NumericStorage<double>>(
+                &col.storage<std::optional<double>>().non_null_vector(),
+                col.col_type(), col.IsSorted());
           } else {
-            storage.reset(new storage::NumericStorage<double_t>(
-                &col.storage<double_t>().vector(), col.col_type(),
-                col.IsSorted()));
+            storage = std::make_unique<storage::NumericStorage<double>>(
+                &col.storage<double>().vector(), col.col_type(),
+                col.IsSorted());
           }
       }
     }
-    s_col.storage = storage.get();
-
-    // Create cEngine overlays based on col.overlay()
-    overlays::SelectorOverlay selector_overlay(
-        col.overlay().row_map().GetIfBitVector());
-    if (col.overlay().size() != column_size &&
-        col.overlay().row_map().IsBitVector())
-      s_col.overlays.emplace_back(&selector_overlay);
-
-    overlays::ArrangementOverlay arrangement_overlay(
-        col.overlay().row_map().GetIfIndexVector());
-    if (col.overlay().row_map().IsIndexVector())
-      s_col.overlays.emplace_back(&arrangement_overlay);
-
-    // Add nullability
-    BitVector null_bv;
-    overlays::NullOverlay null_overlay(
-        col.IsNullable() ? col.storage_base().bv() : &null_bv);
-    if (col.IsNullable())
-      s_col.overlays.emplace_back(&null_overlay);
-
+    if (col.IsNullable()) {
+      // String columns are inherently nullable: null values are signified
+      // with Id::Null().
+      PERFETTO_CHECK(col.col_type() != ColumnType::kString);
+      if (col.IsDense()) {
+        storage = std::make_unique<storage::DenseNullStorage>(
+            std::move(storage), col.storage_base().bv());
+      } else {
+        storage = std::make_unique<storage::NullStorage>(
+            std::move(storage), col.storage_base().bv());
+      }
+    }
+    if (col.overlay().row_map().IsIndexVector()) {
+      storage = std::make_unique<storage::ArrangementStorage>(
+          std::move(storage), col.overlay().row_map().GetIfIndexVector());
+    }
+    if (col.overlay().row_map().IsBitVector()) {
+      storage = std::make_unique<storage::SelectorStorage>(
+          std::move(storage), col.overlay().row_map().GetIfBitVector());
+    }
     uint32_t pre_count = rm.size();
-    FilterColumn(c, s_col, &rm);
+    FilterColumn(c, *storage.get(), &rm);
     PERFETTO_DCHECK(rm.size() <= pre_count);
   }
   return rm;
diff --git a/src/trace_processor/db/query_executor.h b/src/trace_processor/db/query_executor.h
index fd566a6..013dbf2 100644
--- a/src/trace_processor/db/query_executor.h
+++ b/src/trace_processor/db/query_executor.h
@@ -24,8 +24,6 @@
 #include "src/trace_processor/containers/bit_vector.h"
 #include "src/trace_processor/containers/row_map.h"
 #include "src/trace_processor/db/column.h"
-#include "src/trace_processor/db/overlays/storage_overlay.h"
-#include "src/trace_processor/db/overlays/types.h"
 #include "src/trace_processor/db/storage/storage.h"
 
 namespace perfetto {
@@ -37,22 +35,16 @@
  public:
   static constexpr uint32_t kMaxOverlayCount = 8;
 
-  // Overlay-based definition of the column.
-  struct SimpleColumn {
-    base::SmallVector<const overlays::StorageOverlay*, kMaxOverlayCount>
-        overlays;
-    const storage::Storage* storage;
-  };
-
   // |row_count| is the size of the last overlay.
-  QueryExecutor(const std::vector<SimpleColumn>& columns, uint32_t row_count)
+  QueryExecutor(const std::vector<storage::Storage*>& columns,
+                uint32_t row_count)
       : columns_(columns), row_count_(row_count) {}
 
   // Apply all the constraints on the data and return the filtered RowMap.
   RowMap Filter(const std::vector<Constraint>& cs) {
     RowMap rm(0, row_count_);
     for (const auto& c : cs) {
-      FilterColumn(c, columns_[c.col_idx], &rm);
+      FilterColumn(c, *columns_[c.col_idx], &rm);
     }
     return rm;
   }
@@ -72,31 +64,31 @@
 
   // Used only in unittests. Exposes private function.
   static void BoundedColumnFilterForTesting(const Constraint& c,
-                                            const SimpleColumn& col,
+                                            const storage::Storage& col,
                                             RowMap* rm) {
     LinearSearch(c, col, rm);
   }
 
   // Used only in unittests. Exposes private function.
-  static RowMap IndexedColumnFilterForTesting(const Constraint& c,
-                                              const SimpleColumn& col,
-                                              RowMap* rm) {
-    return IndexSearch(c, col, rm);
+  static void IndexedColumnFilterForTesting(const Constraint& c,
+                                            const storage::Storage& col,
+                                            RowMap* rm) {
+    IndexSearch(c, col, rm);
   }
 
  private:
   // Updates RowMap with result of filtering single column using the Constraint.
-  static void FilterColumn(const Constraint&, const SimpleColumn&, RowMap*);
+  static void FilterColumn(const Constraint&, const storage::Storage&, RowMap*);
 
   // Filters the column using Range algorithm - tries to find the smallest Range
   // to filter the storage with.
-  static void LinearSearch(const Constraint&, const SimpleColumn&, RowMap*);
+  static void LinearSearch(const Constraint&, const storage::Storage&, RowMap*);
 
   // Filters the column using Index algorithm - finds the indices to filter the
   // storage with.
-  static RowMap IndexSearch(const Constraint&, const SimpleColumn&, RowMap*);
+  static void IndexSearch(const Constraint&, const storage::Storage&, RowMap*);
 
-  std::vector<SimpleColumn> columns_;
+  std::vector<storage::Storage*> columns_;
 
   // Number of rows in the outmost overlay.
   uint32_t row_count_ = 0;
diff --git a/src/trace_processor/db/query_executor_benchmark.cc b/src/trace_processor/db/query_executor_benchmark.cc
index 5bbc99f..eb23903 100644
--- a/src/trace_processor/db/query_executor_benchmark.cc
+++ b/src/trace_processor/db/query_executor_benchmark.cc
@@ -20,9 +20,11 @@
 
 #include "perfetto/ext/base/file_utils.h"
 #include "perfetto/ext/base/string_utils.h"
+#include "perfetto/trace_processor/basic_types.h"
 #include "src/base/test/utils.h"
 #include "src/trace_processor/db/table.h"
 #include "src/trace_processor/tables/metadata_tables_py.h"
+#include "src/trace_processor/tables/profiler_tables_py.h"
 #include "src/trace_processor/tables/slice_tables_py.h"
 #include "src/trace_processor/tables/track_tables_py.h"
 
@@ -35,6 +37,7 @@
 using ExpectedFrameTimelineSliceTable = tables::ExpectedFrameTimelineSliceTable;
 using RawTable = tables::RawTable;
 using FtraceEventTable = tables::FtraceEventTable;
+using HeapGraphObjectTable = tables::HeapGraphObjectTable;
 
 // `SELECT * FROM SLICE` on android_monitor_contention_trace.at
 static char kSliceTable[] = "test/data/slice_table_for_benchmarks.csv";
@@ -50,6 +53,10 @@
 static char kFtraceEventTable[] =
     "test/data/ftrace_event_cpu_for_benchmarks.csv";
 
+// `SELECT id, upid, reference_set_id FROM heap_graph_object` on
+static char kHeapGraphObjectTable[] =
+    "test/data/heap_pgraph_object_for_benchmarks_query.csv";
+
 enum DB { V1, V2 };
 
 std::vector<std::string> SplitCSVLine(const std::string& line) {
@@ -193,6 +200,24 @@
   tables::FtraceEventTable table_{&pool_, &raw_};
 };
 
+struct HeapGraphObjectTableForBenchmark {
+  explicit HeapGraphObjectTableForBenchmark(benchmark::State& state) {
+    std::vector<std::string> table_rows_as_string =
+        ReadCSV(state, kHeapGraphObjectTable);
+
+    for (size_t i = 1; i < table_rows_as_string.size(); ++i) {
+      std::vector<std::string> row_vec = SplitCSVLine(table_rows_as_string[i]);
+
+      HeapGraphObjectTable::Row row;
+      row.upid = *base::StringToUInt32(row_vec[1]);
+      row.reference_set_id = base::StringToUInt32(row_vec[2]);
+      table_.Insert(row);
+    }
+  }
+  StringPool pool_;
+  HeapGraphObjectTable table_{&pool_};
+};
+
 void BenchmarkSliceTable(benchmark::State& state,
                          SliceTableForBenchmark& table,
                          std::initializer_list<Constraint> c) {
@@ -346,6 +371,38 @@
 
 BENCHMARK(BM_QEFilterWithArrangement)->ArgsProduct({{DB::V1, DB::V2}});
 
+static void BM_QEDenseNullFilter(benchmark::State& state) {
+  Table::kUseFilterV2 = state.range(0) == 1;
+
+  HeapGraphObjectTableForBenchmark table(state);
+  Constraint c{table.table_.reference_set_id().index_in_table(), FilterOp::kGt,
+               SqlValue::Long(1000)};
+  for (auto _ : state) {
+    benchmark::DoNotOptimize(table.table_.FilterToRowMap({c}));
+  }
+  state.counters["s/row"] =
+      benchmark::Counter(static_cast<double>(table.table_.row_count()),
+                         benchmark::Counter::kIsIterationInvariantRate |
+                             benchmark::Counter::kInvert);
+}
+BENCHMARK(BM_QEDenseNullFilter)->ArgsProduct({{DB::V1, DB::V2}});
+
+static void BM_QEDenseNullFilterIsNull(benchmark::State& state) {
+  Table::kUseFilterV2 = state.range(0) == 1;
+
+  HeapGraphObjectTableForBenchmark table(state);
+  Constraint c{table.table_.reference_set_id().index_in_table(),
+               FilterOp::kIsNull, SqlValue()};
+  for (auto _ : state) {
+    benchmark::DoNotOptimize(table.table_.FilterToRowMap({c}));
+  }
+  state.counters["s/row"] =
+      benchmark::Counter(static_cast<double>(table.table_.row_count()),
+                         benchmark::Counter::kIsIterationInvariantRate |
+                             benchmark::Counter::kInvert);
+}
+BENCHMARK(BM_QEDenseNullFilterIsNull)->ArgsProduct({{DB::V1, DB::V2}});
+
 }  // namespace
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/db/query_executor_unittest.cc b/src/trace_processor/db/query_executor_unittest.cc
index 73f88d2..a2c05d8 100644
--- a/src/trace_processor/db/query_executor_unittest.cc
+++ b/src/trace_processor/db/query_executor_unittest.cc
@@ -15,12 +15,16 @@
  */
 
 #include "src/trace_processor/db/query_executor.h"
-#include "src/trace_processor/db/overlays/arrangement_overlay.h"
-#include "src/trace_processor/db/overlays/null_overlay.h"
-#include "src/trace_processor/db/overlays/selector_overlay.h"
+
+#include "perfetto/trace_processor/basic_types.h"
+#include "src/trace_processor/db/storage/arrangement_storage.h"
+#include "src/trace_processor/db/storage/fake_storage.h"
 #include "src/trace_processor/db/storage/id_storage.h"
+#include "src/trace_processor/db/storage/null_storage.h"
 #include "src/trace_processor/db/storage/numeric_storage.h"
+#include "src/trace_processor/db/storage/selector_storage.h"
 #include "src/trace_processor/db/storage/set_id_storage.h"
+#include "src/trace_processor/db/storage/storage.h"
 #include "src/trace_processor/db/storage/string_storage.h"
 #include "test/gtest_and_gmock.h"
 
@@ -28,26 +32,22 @@
 namespace trace_processor {
 namespace {
 
-using OverlaysVec = base::SmallVector<const overlays::StorageOverlay*,
-                                      QueryExecutor::kMaxOverlayCount>;
-using SimpleColumn = QueryExecutor::SimpleColumn;
+using testing::ElementsAre;
 
 using IdStorage = storage::IdStorage;
 using SetIdStorage = storage::SetIdStorage;
 using StringStorage = storage::StringStorage;
-
-using ArrangementOverlay = overlays::ArrangementOverlay;
-using NullOverlay = overlays::NullOverlay;
-using SelectorOverlay = overlays::SelectorOverlay;
+using NullStorage = storage::NullStorage;
+using ArrangementStorage = storage::ArrangementStorage;
+using SelectorStorage = storage::SelectorStorage;
 
 TEST(QueryExecutor, OnlyStorageRange) {
   std::vector<int64_t> storage_data{1, 2, 3, 4, 5};
   storage::NumericStorage<int64_t> storage(&storage_data, ColumnType::kInt64);
-  SimpleColumn col{OverlaysVec(), &storage};
 
   Constraint c{0, FilterOp::kGe, SqlValue::Long(3)};
   RowMap rm(0, 5);
-  QueryExecutor::BoundedColumnFilterForTesting(c, col, &rm);
+  QueryExecutor::BoundedColumnFilterForTesting(c, storage, &rm);
 
   ASSERT_EQ(rm.size(), 3u);
   ASSERT_EQ(rm.Get(0), 2u);
@@ -56,11 +56,10 @@
 TEST(QueryExecutor, OnlyStorageRangeIsNull) {
   std::vector<int64_t> storage_data{1, 2, 3, 4, 5};
   storage::NumericStorage<int64_t> storage(&storage_data, ColumnType::kInt64);
-  SimpleColumn col{OverlaysVec(), &storage};
 
-  Constraint c{0, FilterOp::kIsNull, SqlValue::Long(3)};
+  Constraint c{0, FilterOp::kIsNull, SqlValue()};
   RowMap rm(0, 5);
-  QueryExecutor::BoundedColumnFilterForTesting(c, col, &rm);
+  QueryExecutor::BoundedColumnFilterForTesting(c, storage, &rm);
 
   ASSERT_EQ(rm.size(), 0u);
 }
@@ -73,64 +72,56 @@
                  [](int64_t n) { return n % 5; });
   storage::NumericStorage<int64_t> storage(&storage_data, ColumnType::kInt64);
 
-  SimpleColumn col{OverlaysVec(), &storage};
   Constraint c{0, FilterOp::kLt, SqlValue::Long(2)};
   RowMap rm(0, 10);
-  RowMap res = QueryExecutor::IndexedColumnFilterForTesting(c, col, &rm);
+  QueryExecutor::IndexedColumnFilterForTesting(c, storage, &rm);
 
-  ASSERT_EQ(res.size(), 4u);
-  ASSERT_EQ(res.Get(0), 0u);
-  ASSERT_EQ(res.Get(1), 1u);
-  ASSERT_EQ(res.Get(2), 5u);
-  ASSERT_EQ(res.Get(3), 6u);
+  ASSERT_EQ(rm.size(), 4u);
+  ASSERT_EQ(rm.Get(0), 0u);
+  ASSERT_EQ(rm.Get(1), 1u);
+  ASSERT_EQ(rm.Get(2), 5u);
+  ASSERT_EQ(rm.Get(3), 6u);
 }
 
 TEST(QueryExecutor, OnlyStorageIndexIsNull) {
   std::vector<int64_t> storage_data{1, 2, 3, 4, 5};
   storage::NumericStorage<int64_t> storage(&storage_data, ColumnType::kInt64);
-  SimpleColumn col{OverlaysVec(), &storage};
 
-  Constraint c{0, FilterOp::kIsNull, SqlValue::Long(3)};
+  Constraint c{0, FilterOp::kIsNull, SqlValue()};
   RowMap rm(0, 5);
-  RowMap res = QueryExecutor::IndexedColumnFilterForTesting(c, col, &rm);
+  QueryExecutor::IndexedColumnFilterForTesting(c, storage, &rm);
 
-  ASSERT_EQ(res.size(), 0u);
+  ASSERT_EQ(rm.size(), 0u);
 }
 
-TEST(QueryExecutor, NullOverlayBounds) {
+TEST(QueryExecutor, NullBounds) {
   std::vector<int64_t> storage_data(5);
   std::iota(storage_data.begin(), storage_data.end(), 0);
-  storage::NumericStorage<int64_t> storage(&storage_data, ColumnType::kInt64);
+  auto numeric = std::make_unique<storage::NumericStorage<int64_t>>(
+      &storage_data, ColumnType::kInt64);
   BitVector bv{1, 1, 0, 1, 1, 0, 0, 0, 1, 0};
-  overlays::NullOverlay overlay(&bv);
-  OverlaysVec overlays_vec;
-  overlays_vec.emplace_back(&overlay);
-
-  SimpleColumn col{overlays_vec, &storage};
+  storage::NullStorage storage(std::move(numeric), &bv);
 
   Constraint c{0, FilterOp::kGe, SqlValue::Long(3)};
   RowMap rm(0, 10);
-  QueryExecutor::BoundedColumnFilterForTesting(c, col, &rm);
+  QueryExecutor::BoundedColumnFilterForTesting(c, storage, &rm);
 
   ASSERT_EQ(rm.size(), 2u);
   ASSERT_EQ(rm.Get(0), 4u);
   ASSERT_EQ(rm.Get(1), 8u);
 }
 
-TEST(QueryExecutor, NullOverlayRangeIsNull) {
+TEST(QueryExecutor, NullRangeIsNull) {
   std::vector<int64_t> storage_data(5);
   std::iota(storage_data.begin(), storage_data.end(), 0);
-  storage::NumericStorage<int64_t> storage(&storage_data, ColumnType::kInt64);
+  auto numeric = std::make_unique<storage::NumericStorage<int64_t>>(
+      &storage_data, ColumnType::kInt64);
   BitVector bv{1, 1, 0, 1, 1, 0, 0, 0, 1, 0};
-  overlays::NullOverlay overlay(&bv);
-  OverlaysVec overlays_vec;
-  overlays_vec.emplace_back(&overlay);
+  storage::NullStorage storage(std::move(numeric), &bv);
 
-  SimpleColumn col{overlays_vec, &storage};
-
-  Constraint c{0, FilterOp::kIsNull, SqlValue::Long(3)};
+  Constraint c{0, FilterOp::kIsNull, SqlValue()};
   RowMap rm(0, 10);
-  QueryExecutor::BoundedColumnFilterForTesting(c, col, &rm);
+  QueryExecutor::BoundedColumnFilterForTesting(c, storage, &rm);
 
   ASSERT_EQ(rm.size(), 5u);
   ASSERT_EQ(rm.Get(0), 2u);
@@ -140,163 +131,174 @@
   ASSERT_EQ(rm.Get(4), 9u);
 }
 
-TEST(QueryExecutor, NullOverlayIndex) {
+TEST(QueryExecutor, NullIndex) {
   std::vector<int64_t> storage_data(6);
   std::iota(storage_data.begin(), storage_data.end(), 0);
   std::transform(storage_data.begin(), storage_data.end(), storage_data.begin(),
                  [](int64_t n) { return n % 3; });
-  storage::NumericStorage<int64_t> storage(&storage_data, ColumnType::kInt64);
+  auto numeric = std::make_unique<storage::NumericStorage<int64_t>>(
+      &storage_data, ColumnType::kInt64);
 
   BitVector bv{1, 1, 0, 1, 1, 0, 1, 0, 0, 1};
-  NullOverlay overlay(&bv);
-  OverlaysVec overlays_vec;
-  overlays_vec.emplace_back(&overlay);
-
-  SimpleColumn col{overlays_vec, &storage};
+  storage::NullStorage storage(std::move(numeric), &bv);
 
   Constraint c{0, FilterOp::kGe, SqlValue::Long(1)};
   RowMap rm(0, 10);
-  RowMap res = QueryExecutor::IndexedColumnFilterForTesting(c, col, &rm);
+  QueryExecutor::IndexedColumnFilterForTesting(c, storage, &rm);
 
-  ASSERT_EQ(res.size(), 4u);
-  ASSERT_EQ(res.Get(0), 1u);
-  ASSERT_EQ(res.Get(1), 3u);
-  ASSERT_EQ(res.Get(2), 6u);
-  ASSERT_EQ(res.Get(3), 9u);
+  ASSERT_EQ(rm.size(), 4u);
+  ASSERT_EQ(rm.Get(0), 1u);
+  ASSERT_EQ(rm.Get(1), 3u);
+  ASSERT_EQ(rm.Get(2), 6u);
+  ASSERT_EQ(rm.Get(3), 9u);
 }
 
-TEST(QueryExecutor, NullOverlayIndexIsNull) {
+TEST(QueryExecutor, NullIndexIsNull) {
   std::vector<int64_t> storage_data(5);
   std::iota(storage_data.begin(), storage_data.end(), 0);
-  storage::NumericStorage<int64_t> storage(&storage_data, ColumnType::kInt64);
+  auto numeric = std::make_unique<storage::NumericStorage<int64_t>>(
+      &storage_data, ColumnType::kInt64);
+
   BitVector bv{1, 1, 0, 1, 1, 0, 0, 0, 1, 0};
-  overlays::NullOverlay overlay(&bv);
-  OverlaysVec overlays_vec;
-  overlays_vec.emplace_back(&overlay);
+  storage::NullStorage storage(std::move(numeric), &bv);
 
-  SimpleColumn col{overlays_vec, &storage};
-
-  Constraint c{0, FilterOp::kIsNull, SqlValue::Long(3)};
+  Constraint c{0, FilterOp::kIsNull, SqlValue()};
   RowMap rm(0, 10);
-  RowMap res = QueryExecutor::IndexedColumnFilterForTesting(c, col, &rm);
+  QueryExecutor::IndexedColumnFilterForTesting(c, storage, &rm);
 
-  ASSERT_EQ(res.size(), 5u);
-  ASSERT_EQ(res.Get(0), 2u);
-  ASSERT_EQ(res.Get(1), 5u);
-  ASSERT_EQ(res.Get(2), 6u);
-  ASSERT_EQ(res.Get(3), 7u);
-  ASSERT_EQ(res.Get(4), 9u);
+  ASSERT_EQ(rm.size(), 5u);
+  ASSERT_EQ(rm.Get(0), 2u);
+  ASSERT_EQ(rm.Get(1), 5u);
+  ASSERT_EQ(rm.Get(2), 6u);
+  ASSERT_EQ(rm.Get(3), 7u);
+  ASSERT_EQ(rm.Get(4), 9u);
 }
 
-TEST(QueryExecutor, SelectorOverlayBounds) {
+TEST(QueryExecutor, SelectorStorageBounds) {
   std::vector<int64_t> storage_data(5);
   std::iota(storage_data.begin(), storage_data.end(), 0);
-  storage::NumericStorage<int64_t> storage(&storage_data, ColumnType::kInt64);
+  auto numeric = std::make_unique<storage::NumericStorage<int64_t>>(
+      &storage_data, ColumnType::kInt64);
 
   BitVector bv{1, 1, 0, 0, 1};
-  SelectorOverlay overlay(&bv);
-  OverlaysVec overlays_vec;
-  overlays_vec.emplace_back(&overlay);
-
-  SimpleColumn col{overlays_vec, &storage};
+  SelectorStorage storage(std::move(numeric), &bv);
 
   Constraint c{0, FilterOp::kGt, SqlValue::Long(1)};
   RowMap rm(0, 3);
-  QueryExecutor::BoundedColumnFilterForTesting(c, col, &rm);
+  QueryExecutor::BoundedColumnFilterForTesting(c, storage, &rm);
 
-  ASSERT_EQ(rm.size(), 1u);
-  ASSERT_EQ(rm.Get(0), 2u);
+  ASSERT_THAT(rm.GetAllIndices(), ElementsAre(2u));
 }
 
-TEST(QueryExecutor, SelectorOverlayIndex) {
+TEST(QueryExecutor, SelectorStorageIndex) {
   std::vector<int64_t> storage_data(10);
   std::iota(storage_data.begin(), storage_data.end(), 0);
   std::transform(storage_data.begin(), storage_data.end(), storage_data.begin(),
                  [](int64_t n) { return n % 5; });
-  storage::NumericStorage<int64_t> storage(&storage_data, ColumnType::kInt64);
+  auto numeric = std::make_unique<storage::NumericStorage<int64_t>>(
+      &storage_data, ColumnType::kInt64);
 
   BitVector bv{1, 1, 0, 1, 1, 0, 1, 0, 0, 1};
-  SelectorOverlay overlay(&bv);
-  OverlaysVec overlays_vec;
-  overlays_vec.emplace_back(&overlay);
-
-  SimpleColumn col{overlays_vec, &storage};
+  SelectorStorage storage(std::move(numeric), &bv);
 
   Constraint c{0, FilterOp::kGe, SqlValue::Long(2)};
   RowMap rm(0, 6);
-  RowMap res = QueryExecutor::IndexedColumnFilterForTesting(c, col, &rm);
+  QueryExecutor::IndexedColumnFilterForTesting(c, storage, &rm);
 
-  ASSERT_EQ(res.size(), 3u);
-  ASSERT_EQ(res.Get(0), 2u);
-  ASSERT_EQ(res.Get(1), 3u);
-  ASSERT_EQ(res.Get(2), 5u);
+  ASSERT_THAT(rm.GetAllIndices(), ElementsAre(2u, 3u, 5u));
 }
 
-TEST(QueryExecutor, ArrangementOverlayBounds) {
+TEST(QueryExecutor, ArrangementStorageBounds) {
   std::vector<int64_t> storage_data(5);
   std::iota(storage_data.begin(), storage_data.end(), 0);
-  storage::NumericStorage<int64_t> storage(&storage_data, ColumnType::kInt64);
+  auto numeric = std::make_unique<storage::NumericStorage<int64_t>>(
+      &storage_data, ColumnType::kInt64);
 
   std::vector<uint32_t> arrangement{4, 1, 2, 2, 3};
-  overlays::ArrangementOverlay overlay(&arrangement);
-  OverlaysVec overlays_vec;
-  overlays_vec.emplace_back(&overlay);
-
-  SimpleColumn col{overlays_vec, &storage};
+  storage::ArrangementStorage storage(std::move(numeric), &arrangement);
 
   Constraint c{0, FilterOp::kGe, SqlValue::Long(3)};
   RowMap rm(0, 5);
-  QueryExecutor::BoundedColumnFilterForTesting(c, col, &rm);
+  QueryExecutor::BoundedColumnFilterForTesting(c, storage, &rm);
 
-  ASSERT_EQ(rm.size(), 2u);
-  ASSERT_EQ(rm.Get(0), 0u);
-  ASSERT_EQ(rm.Get(1), 4u);
+  ASSERT_THAT(rm.GetAllIndices(), ElementsAre(0u, 4u));
 }
 
-TEST(QueryExecutor, ArrangmentOverlayIndex) {
-  std::vector<int64_t> storage_data(5);
-  std::iota(storage_data.begin(), storage_data.end(), 0);
-  storage::NumericStorage<int64_t> storage(&storage_data, ColumnType::kInt64);
+TEST(QueryExecutor, ArrangementStorageSubsetInputRange) {
+  std::unique_ptr<storage::Storage> fake =
+      storage::FakeStorage::SearchSubset(5u, RowMap::Range(2u, 4u));
 
   std::vector<uint32_t> arrangement{4, 1, 2, 2, 3};
-  overlays::ArrangementOverlay overlay(&arrangement);
-  OverlaysVec overlays_vec;
-  overlays_vec.emplace_back(&overlay);
+  storage::ArrangementStorage storage(std::move(fake), &arrangement);
 
-  SimpleColumn col{overlays_vec, &storage};
+  Constraint c{0, FilterOp::kGe, SqlValue::Long(0u)};
+  RowMap rm(1, 3);
+  QueryExecutor::BoundedColumnFilterForTesting(c, storage, &rm);
+
+  ASSERT_THAT(rm.GetAllIndices(), ElementsAre(2u));
+}
+
+TEST(QueryExecutor, ArrangementStorageSubsetInputBitvector) {
+  std::unique_ptr<storage::Storage> fake =
+      storage::FakeStorage::SearchSubset(5u, BitVector({0, 0, 1, 1, 0}));
+
+  std::vector<uint32_t> arrangement{4, 1, 2, 2, 3};
+  storage::ArrangementStorage storage(std::move(fake), &arrangement);
+
+  Constraint c{0, FilterOp::kGe, SqlValue::Long(0u)};
+  RowMap rm(1, 3);
+  QueryExecutor::BoundedColumnFilterForTesting(c, storage, &rm);
+
+  ASSERT_THAT(rm.GetAllIndices(), ElementsAre(2u));
+}
+
+TEST(QueryExecutor, ArrangementStorageIndex) {
+  std::vector<int64_t> storage_data(5);
+  std::iota(storage_data.begin(), storage_data.end(), 0);
+  auto numeric = std::make_unique<storage::NumericStorage<int64_t>>(
+      &storage_data, ColumnType::kInt64);
+
+  std::vector<uint32_t> arrangement{4, 1, 2, 2, 3};
+  storage::ArrangementStorage storage(std::move(numeric), &arrangement);
 
   Constraint c{0, FilterOp::kGe, SqlValue::Long(3)};
   RowMap rm(0, 5);
-  RowMap res = QueryExecutor::IndexedColumnFilterForTesting(c, col, &rm);
+  QueryExecutor::IndexedColumnFilterForTesting(c, storage, &rm);
 
-  ASSERT_EQ(res.size(), 2u);
-  ASSERT_EQ(res.Get(0), 0u);
-  ASSERT_EQ(res.Get(1), 4u);
+  ASSERT_THAT(rm.GetAllIndices(), ElementsAre(0u, 4u));
+}
+
+TEST(QueryExecutor, MismatchedTypeNullWithOtherOperations) {
+  std::vector<int64_t> storage_data{0, 1, 2, 3, 0, 1, 2, 3};
+  storage::NumericStorage<int64_t> storage(&storage_data, ColumnType::kInt64);
+
+  // Filter.
+  Constraint c{0, FilterOp::kGe, SqlValue()};
+  QueryExecutor exec({&storage}, 6);
+  RowMap res = exec.Filter({c});
+
+  ASSERT_TRUE(res.empty());
 }
 
 TEST(QueryExecutor, SingleConstraintWithNullAndSelector) {
   std::vector<int64_t> storage_data{0, 1, 2, 3, 0, 1, 2, 3};
-  storage::NumericStorage<int64_t> storage(&storage_data, ColumnType::kInt64);
+  auto numeric = std::make_unique<storage::NumericStorage<int64_t>>(
+      &storage_data, ColumnType::kInt64);
 
   // Current vector
   // 0, 1, NULL, 2, 3, 0, NULL, NULL, 1, 2, 3, NULL
   BitVector null_bv{1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0};
-  NullOverlay null_overlay(&null_bv);
+  auto null =
+      std::make_unique<storage::NullStorage>(std::move(numeric), &null_bv);
 
   // Final vector
   // 0, NULL, 3, NULL, 1, 3
   BitVector selector_bv{1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0};
-  SelectorOverlay selector_overlay(&selector_bv);
-
-  // Create the column.
-  OverlaysVec overlays_vec;
-  overlays_vec.emplace_back(&selector_overlay);
-  overlays_vec.emplace_back(&null_overlay);
-  SimpleColumn col{overlays_vec, &storage};
+  SelectorStorage storage(std::move(null), &selector_bv);
 
   // Filter.
   Constraint c{0, FilterOp::kGe, SqlValue::Long(2)};
-  QueryExecutor exec({col}, 6);
+  QueryExecutor exec({&storage}, 6);
   RowMap res = exec.Filter({c});
 
   ASSERT_EQ(res.size(), 2u);
@@ -306,27 +308,23 @@
 
 TEST(QueryExecutor, SingleConstraintWithNullAndArrangement) {
   std::vector<int64_t> storage_data{0, 1, 2, 3, 0, 1, 2, 3};
-  storage::NumericStorage<int64_t> storage(&storage_data, ColumnType::kInt64);
+  auto numeric = std::make_unique<storage::NumericStorage<int64_t>>(
+      &storage_data, ColumnType::kInt64);
 
   // Current vector
   // 0, 1, NULL, 2, 3, 0, NULL, NULL, 1, 2, 3, NULL
   BitVector null_bv{1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0};
-  NullOverlay null_overlay(&null_bv);
+  auto null =
+      std::make_unique<storage::NullStorage>(std::move(numeric), &null_bv);
 
   // Final vector
   // NULL, 3, NULL, NULL, 3, NULL
   std::vector<uint32_t> arrangement{2, 4, 6, 2, 4, 6};
-  ArrangementOverlay arrangement_overlay(&arrangement);
-
-  // Create the column.
-  OverlaysVec overlays_vec;
-  overlays_vec.emplace_back(&arrangement_overlay);
-  overlays_vec.emplace_back(&null_overlay);
-  SimpleColumn col{overlays_vec, &storage};
+  ArrangementStorage storage(std::move(null), &arrangement);
 
   // Filter.
   Constraint c{0, FilterOp::kGe, SqlValue::Long(1)};
-  QueryExecutor exec({col}, 6);
+  QueryExecutor exec({&storage}, 6);
   RowMap res = exec.Filter({c});
 
   ASSERT_EQ(res.size(), 2u);
@@ -336,27 +334,23 @@
 
 TEST(QueryExecutor, IsNullWithSelector) {
   std::vector<int64_t> storage_data{0, 1, 2, 3, 0, 1, 2, 3};
-  storage::NumericStorage<int64_t> storage(&storage_data, ColumnType::kInt64);
+  auto numeric = std::make_unique<storage::NumericStorage<int64_t>>(
+      &storage_data, ColumnType::kInt64);
 
   // Current vector
   // 0, 1, NULL, 2, 3, 0, NULL, NULL, 1, 2, 3, NULL
   BitVector null_bv{1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0};
-  NullOverlay null_overlay(&null_bv);
+  auto null =
+      std::make_unique<storage::NullStorage>(std::move(numeric), &null_bv);
 
   // Final vector
   // 0, NULL, 3, NULL, 1, 3
   BitVector selector_bv{1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0};
-  SelectorOverlay selector_overlay(&selector_bv);
-
-  // Create the column.
-  OverlaysVec overlays_vec;
-  overlays_vec.emplace_back(&selector_overlay);
-  overlays_vec.emplace_back(&null_overlay);
-  SimpleColumn col{overlays_vec, &storage};
+  SelectorStorage storage(std::move(null), &selector_bv);
 
   // Filter.
-  Constraint c{0, FilterOp::kIsNull, SqlValue::Long(0)};
-  QueryExecutor exec({col}, 6);
+  Constraint c{0, FilterOp::kIsNull, SqlValue()};
+  QueryExecutor exec({&storage}, 6);
   RowMap res = exec.Filter({c});
 
   ASSERT_EQ(res.size(), 2u);
@@ -366,26 +360,21 @@
 
 TEST(QueryExecutor, BinarySearch) {
   std::vector<int64_t> storage_data{0, 1, 2, 3, 4, 5, 6};
-  storage::NumericStorage<int64_t> storage(&storage_data, ColumnType::kInt64,
-                                           true);
+  auto numeric = std::make_unique<storage::NumericStorage<int64_t>>(
+      &storage_data, ColumnType::kInt64, true);
 
   // Add nulls - {0, 1, NULL, NULL, 2, 3, NULL, NULL, 4, 5, 6, NULL}
   BitVector null_bv{1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0};
-  NullOverlay null_overlay(&null_bv);
+  auto null =
+      std::make_unique<storage::NullStorage>(std::move(numeric), &null_bv);
 
   // Final vector {1, NULL, 3, NULL, 5, NULL}.
   BitVector selector_bv{0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1};
-  SelectorOverlay selector_overlay(&selector_bv);
-
-  // Create the column.
-  OverlaysVec overlays_vec;
-  overlays_vec.emplace_back(&selector_overlay);
-  overlays_vec.emplace_back(&null_overlay);
-  SimpleColumn col{overlays_vec, &storage};
+  SelectorStorage storage(std::move(null), &selector_bv);
 
   // Filter.
   Constraint c{0, FilterOp::kGe, SqlValue::Long(3)};
-  QueryExecutor exec({col}, 6);
+  QueryExecutor exec({&storage}, 6);
   RowMap res = exec.Filter({c});
 
   ASSERT_EQ(res.size(), 2u);
@@ -395,26 +384,21 @@
 
 TEST(QueryExecutor, BinarySearchIsNull) {
   std::vector<int64_t> storage_data{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
-  storage::NumericStorage<int64_t> storage(&storage_data, ColumnType::kInt64,
-                                           true);
+  auto numeric = std::make_unique<storage::NumericStorage<int64_t>>(
+      &storage_data, ColumnType::kInt64, true);
 
   // Select 6 elements from storage, resulting in a vector {0, 1, 3, 4, 6, 7}.
   BitVector selector_bv{1, 1, 0, 1, 1, 0, 1, 1, 0, 0};
-  SelectorOverlay selector_overlay(&selector_bv);
+  auto selector =
+      std::make_unique<SelectorStorage>(std::move(numeric), &selector_bv);
 
   // Add nulls, final vector {NULL, NULL, NULL 0, 1, 3, 4, 6, 7}.
   BitVector null_bv{0, 0, 0, 1, 1, 1, 1, 1, 1};
-  NullOverlay null_overlay(&null_bv);
-
-  // Create the column.
-  OverlaysVec overlays_vec;
-  overlays_vec.emplace_back(&null_overlay);
-  overlays_vec.emplace_back(&selector_overlay);
-  SimpleColumn col{overlays_vec, &storage};
+  storage::NullStorage storage(std::move(selector), &null_bv);
 
   // Filter.
-  Constraint c{0, FilterOp::kIsNull, SqlValue::Long(0)};
-  QueryExecutor exec({col}, 9);
+  Constraint c{0, FilterOp::kIsNull, SqlValue()};
+  QueryExecutor exec({&storage}, 9);
   RowMap res = exec.Filter({c});
 
   ASSERT_EQ(res.size(), 3u);
@@ -425,25 +409,20 @@
 
 TEST(QueryExecutor, SetIdStorage) {
   std::vector<uint32_t> storage_data{0, 0, 0, 3, 3, 3, 6, 6, 6, 9, 9, 9};
-  SetIdStorage storage(&storage_data);
+  auto numeric = std::make_unique<storage::SetIdStorage>(&storage_data);
 
   // Select 6 elements from storage, resulting in a vector {0, 3, 3, 6, 9, 9}.
   BitVector selector_bv{0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1};
-  SelectorOverlay selector_overlay(&selector_bv);
+  auto selector =
+      std::make_unique<SelectorStorage>(std::move(numeric), &selector_bv);
 
   // Add nulls - vector (size 10) {NULL, 0, 3, NULL, 3, 6, NULL, 9, 9, NULL}.
   BitVector null_bv{0, 1, 1, 0, 1, 1, 0, 1, 1, 0};
-  NullOverlay null_overlay(&null_bv);
-
-  // Create the column.
-  OverlaysVec overlays_vec;
-  overlays_vec.emplace_back(&null_overlay);
-  overlays_vec.emplace_back(&selector_overlay);
-  SimpleColumn col{overlays_vec, &storage};
+  storage::NullStorage storage(std::move(selector), &null_bv);
 
   // Filter.
-  Constraint c{0, FilterOp::kIsNull, SqlValue::Long(0)};
-  QueryExecutor exec({col}, 10);
+  Constraint c{0, FilterOp::kIsNull, SqlValue()};
+  QueryExecutor exec({&storage}, 10);
   RowMap res = exec.Filter({c});
 
   ASSERT_EQ(res.size(), 4u);
@@ -458,12 +437,9 @@
   storage::NumericStorage<int64_t> storage(&storage_data, ColumnType::kInt64,
                                            true);
 
-  OverlaysVec overlays_vec;
-  SimpleColumn col{overlays_vec, &storage};
-
   // Filter.
   Constraint c{0, FilterOp::kNe, SqlValue::Long(5)};
-  QueryExecutor exec({col}, 10);
+  QueryExecutor exec({&storage}, 10);
   RowMap res = exec.Filter({c});
 
   ASSERT_EQ(res.size(), 9u);
@@ -471,12 +447,10 @@
 
 TEST(QueryExecutor, IdSearchIsNull) {
   IdStorage storage(5);
-  OverlaysVec overlays_vec;
-  SimpleColumn col{overlays_vec, &storage};
 
   // Filter.
-  Constraint c{0, FilterOp::kIsNull, SqlValue::Long(0)};
-  QueryExecutor exec({col}, 5);
+  Constraint c{0, FilterOp::kIsNull, SqlValue()};
+  QueryExecutor exec({&storage}, 5);
   RowMap res = exec.Filter({c});
 
   ASSERT_EQ(res.size(), 0u);
@@ -484,12 +458,10 @@
 
 TEST(QueryExecutor, IdSearchIsNotNull) {
   IdStorage storage(5);
-  OverlaysVec overlays_vec;
-  SimpleColumn col{overlays_vec, &storage};
 
   // Filter.
-  Constraint c{0, FilterOp::kIsNotNull, SqlValue::Long(0)};
-  QueryExecutor exec({col}, 5);
+  Constraint c{0, FilterOp::kIsNotNull, SqlValue()};
+  QueryExecutor exec({&storage}, 5);
   RowMap res = exec.Filter({c});
 
   ASSERT_EQ(res.size(), 5u);
@@ -497,12 +469,10 @@
 
 TEST(QueryExecutor, IdSearchNotEq) {
   IdStorage storage(5);
-  OverlaysVec overlays_vec;
-  SimpleColumn col{overlays_vec, &storage};
 
   // Filter.
   Constraint c{0, FilterOp::kNe, SqlValue::Long(3)};
-  QueryExecutor exec({col}, 5);
+  QueryExecutor exec({&storage}, 5);
   RowMap res = exec.Filter({c});
 
   ASSERT_EQ(res.size(), 4u);
@@ -517,20 +487,15 @@
     ids.push_back(pool.InternString(base::StringView(string)));
   }
   ids.insert(ids.begin() + 3, StringPool::Id::Null());
-  StringStorage storage(&pool, &ids);
+  auto string = std::make_unique<StringStorage>(&pool, &ids);
 
   // Final vec {"cheese", "pasta", "NULL", "pierogi", "fries"}.
   BitVector selector_bv{1, 1, 0, 1, 1, 0, 1};
-  SelectorOverlay selector_overlay(&selector_bv);
-
-  // Create the column.
-  OverlaysVec overlays_vec;
-  overlays_vec.emplace_back(&selector_overlay);
-  SimpleColumn col{overlays_vec, &storage};
+  SelectorStorage storage(std::move(string), &selector_bv);
 
   // Filter.
-  Constraint c{0, FilterOp::kIsNull, SqlValue::Long(0)};
-  QueryExecutor exec({col}, 5);
+  Constraint c{0, FilterOp::kIsNull, SqlValue()};
+  QueryExecutor exec({&storage}, 5);
   RowMap res = exec.Filter({c});
 
   ASSERT_EQ(res.size(), 1u);
@@ -545,20 +510,15 @@
   for (const auto& string : strings) {
     ids.push_back(pool.InternString(base::StringView(string)));
   }
-  StringStorage storage(&pool, &ids, true);
+  auto string = std::make_unique<StringStorage>(&pool, &ids, true);
 
   // Final vec {"apple", "burger", "doughnut", "eggplant"}.
   BitVector selector_bv{1, 1, 0, 1, 1, 0};
-  SelectorOverlay selector_overlay(&selector_bv);
-
-  // Create the column.
-  OverlaysVec overlays_vec;
-  overlays_vec.emplace_back(&selector_overlay);
-  SimpleColumn col{overlays_vec, &storage};
+  SelectorStorage storage(std::move(string), &selector_bv);
 
   // Filter.
   Constraint c{0, FilterOp::kGe, SqlValue::String("camembert")};
-  QueryExecutor exec({col}, 4);
+  QueryExecutor exec({&storage}, 4);
   RowMap res = exec.Filter({c});
 
   ASSERT_EQ(res.size(), 2u);
@@ -573,26 +533,32 @@
   for (const auto& string : strings) {
     ids.push_back(pool.InternString(base::StringView(string)));
   }
-  StringStorage storage(&pool, &ids, true);
+  auto string = std::make_unique<StringStorage>(&pool, &ids, true);
 
   // Final vec {"apple", "burger", "doughnut", "eggplant"}.
   BitVector selector_bv{1, 1, 0, 1, 1, 0};
-  SelectorOverlay selector_overlay(&selector_bv);
-
-  // Create the column.
-  OverlaysVec overlays_vec;
-  overlays_vec.emplace_back(&selector_overlay);
-  SimpleColumn col{overlays_vec, &storage};
+  SelectorStorage storage(std::move(string), &selector_bv);
 
   // Filter.
   Constraint c{0, FilterOp::kNe, SqlValue::String("doughnut")};
-  QueryExecutor exec({col}, 4);
+  QueryExecutor exec({&storage}, 4);
   RowMap res = exec.Filter({c});
 
   ASSERT_EQ(res.size(), 3u);
   ASSERT_EQ(res.Get(0), 0u);
 }
 
+TEST(QueryExecutor, MismatchedTypeIdWithString) {
+  IdStorage storage(5);
+
+  // Filter.
+  Constraint c{0, FilterOp::kGe, SqlValue::String("cheese")};
+  QueryExecutor exec({&storage}, 5);
+  RowMap res = exec.Filter({c});
+
+  ASSERT_EQ(res.size(), 0u);
+}
+
 #if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
 TEST(QueryExecutor, StringBinarySearchRegex) {
   StringPool pool;
@@ -603,20 +569,15 @@
     ids.push_back(pool.InternString(base::StringView(string)));
   }
   ids.insert(ids.begin() + 3, StringPool::Id::Null());
-  StringStorage storage(&pool, &ids);
+  auto string = std::make_unique<StringStorage>(&pool, &ids);
 
   // Final vec {"cheese", "pasta", "NULL", "pierogi", "fries"}.
   BitVector selector_bv{1, 1, 0, 1, 1, 0, 1};
-  SelectorOverlay selector_overlay(&selector_bv);
-
-  // Create the column.
-  OverlaysVec overlays_vec;
-  overlays_vec.emplace_back(&selector_overlay);
-  SimpleColumn col{overlays_vec, &storage};
+  SelectorStorage storage(std::move(string), &selector_bv);
 
   // Filter.
   Constraint c{0, FilterOp::kRegex, SqlValue::String("p.*")};
-  QueryExecutor exec({col}, 5);
+  QueryExecutor exec({&storage}, 5);
   RowMap res = exec.Filter({c});
 
   ASSERT_EQ(res.size(), 2u);
@@ -633,20 +594,15 @@
     ids.push_back(pool.InternString(base::StringView(string)));
   }
   ids.insert(ids.begin() + 3, StringPool::Id::Null());
-  StringStorage storage(&pool, &ids);
+  auto string = std::make_unique<StringStorage>(&pool, &ids);
 
   // Final vec {"cheese", "pasta", "NULL", "pierogi", "fries"}.
   BitVector selector_bv{1, 1, 0, 1, 1, 0, 1};
-  SelectorOverlay selector_overlay(&selector_bv);
-
-  // Create the column.
-  OverlaysVec overlays_vec;
-  overlays_vec.emplace_back(&selector_overlay);
-  SimpleColumn col{overlays_vec, &storage};
+  SelectorStorage storage(std::move(string), &selector_bv);
 
   // Filter.
   Constraint c{0, FilterOp::kRegex, SqlValue::Long(4)};
-  QueryExecutor exec({col}, 5);
+  QueryExecutor exec({&storage}, 5);
   RowMap res = exec.Filter({c});
 
   ASSERT_EQ(res.size(), 0u);
diff --git a/src/trace_processor/db/storage/BUILD.gn b/src/trace_processor/db/storage/BUILD.gn
index 3b90470..bd80ec7 100644
--- a/src/trace_processor/db/storage/BUILD.gn
+++ b/src/trace_processor/db/storage/BUILD.gn
@@ -16,12 +16,20 @@
 
 source_set("storage") {
   sources = [
+    "arrangement_storage.cc",
+    "arrangement_storage.h",
+    "dense_null_storage.cc",
+    "dense_null_storage.h",
     "dummy_storage.cc",
     "dummy_storage.h",
     "id_storage.cc",
     "id_storage.h",
+    "null_storage.cc",
+    "null_storage.h",
     "numeric_storage.cc",
     "numeric_storage.h",
+    "selector_storage.cc",
+    "selector_storage.h",
     "set_id_storage.cc",
     "set_id_storage.h",
     "storage.cc",
@@ -43,17 +51,37 @@
   ]
 }
 
-perfetto_unittest_source_set("unittests") {
+perfetto_unittest_source_set("fake_storage") {
   testonly = true
   sources = [
-    "id_storage_unittest.cc",
-    "numeric_storage_unittest.cc",
-    "set_id_storage_unittest.cc",
-    "string_storage_unittest.cc",
+    "fake_storage.cc",
+    "fake_storage.h",
   ]
   deps = [
     ":storage",
     "../../../../gn:default_deps",
     "../../../../gn:gtest_and_gmock",
+    "../../containers",
+  ]
+}
+
+perfetto_unittest_source_set("unittests") {
+  testonly = true
+  sources = [
+    "arrangement_storage_unittest.cc",
+    "dense_null_storage_unittest.cc",
+    "id_storage_unittest.cc",
+    "null_storage_unittest.cc",
+    "numeric_storage_unittest.cc",
+    "selector_storage_unittest.cc",
+    "set_id_storage_unittest.cc",
+    "string_storage_unittest.cc",
+  ]
+  deps = [
+    ":fake_storage",
+    ":storage",
+    "../../../../gn:default_deps",
+    "../../../../gn:gtest_and_gmock",
+    "../../containers",
   ]
 }
diff --git a/src/trace_processor/db/storage/arrangement_storage.cc b/src/trace_processor/db/storage/arrangement_storage.cc
new file mode 100644
index 0000000..fac7f68
--- /dev/null
+++ b/src/trace_processor/db/storage/arrangement_storage.cc
@@ -0,0 +1,133 @@
+/*
+ * 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/arrangement_storage.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <vector>
+
+#include "protos/perfetto/trace_processor/serialization.pbzero.h"
+#include "src/trace_processor/containers/bit_vector.h"
+#include "src/trace_processor/tp_metatrace.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace storage {
+namespace {
+
+using Range = RowMap::Range;
+
+}  // namespace
+
+ArrangementStorage::ArrangementStorage(std::unique_ptr<Storage> inner,
+                                       const std::vector<uint32_t>* arrangement)
+    : inner_(std::move(inner)), arrangement_(arrangement) {
+  PERFETTO_DCHECK(*std::max_element(arrangement->begin(), arrangement->end()) <=
+                  inner_->size());
+}
+
+Storage::SearchValidationResult ArrangementStorage::ValidateSearchConstraints(
+    SqlValue sql_val,
+    FilterOp op) const {
+  return inner_->ValidateSearchConstraints(sql_val, op);
+}
+
+RangeOrBitVector ArrangementStorage::Search(FilterOp op,
+                                            SqlValue sql_val,
+                                            Range in) const {
+  PERFETTO_TP_TRACE(metatrace::Category::DB, "ArrangementStorage::Search");
+
+  const auto& arrangement = *arrangement_;
+  PERFETTO_DCHECK(in.end <= arrangement.size());
+  const auto [min_i, max_i] =
+      std::minmax_element(arrangement.begin() + static_cast<int32_t>(in.start),
+                          arrangement.begin() + static_cast<int32_t>(in.end));
+
+  auto storage_result = inner_->Search(op, sql_val, Range(*min_i, *max_i + 1));
+  BitVector::Builder builder(in.end, in.start);
+  if (storage_result.IsRange()) {
+    Range storage_range = std::move(storage_result).TakeIfRange();
+    for (uint32_t i = in.start; i < in.end; ++i) {
+      builder.Append(storage_range.Contains(arrangement[i]));
+    }
+  } else {
+    BitVector storage_bitvector = std::move(storage_result).TakeIfBitVector();
+    PERFETTO_DCHECK(storage_bitvector.size() == *max_i + 1);
+
+    // After benchmarking, it turns out this complexity *is* actually worthwhile
+    // and has a noticable impact on the performance of this function in real
+    // world tables.
+
+    // Fast path: we compare as many groups of 64 elements as we can.
+    // This should be very easy for the compiler to auto-vectorize.
+    const uint32_t* arrangement_idx = arrangement.data() + in.start;
+    uint32_t fast_path_elements = builder.BitsInCompleteWordsUntilFull();
+    for (uint32_t i = 0; i < fast_path_elements; i += BitVector::kBitsInWord) {
+      uint64_t word = 0;
+      // This part should be optimised by SIMD and is expected to be fast.
+      for (uint32_t k = 0; k < BitVector::kBitsInWord; ++k, ++arrangement_idx) {
+        bool comp_result = storage_bitvector.IsSet(*arrangement_idx);
+        word |= static_cast<uint64_t>(comp_result) << k;
+      }
+      builder.AppendWord(word);
+    }
+
+    // Slow path: we compare <64 elements and append to fill the Builder.
+    uint32_t back_elements = builder.BitsUntilFull();
+    for (uint32_t i = 0; i < back_elements; ++i, ++arrangement_idx) {
+      builder.Append(storage_bitvector.IsSet(*arrangement_idx));
+    }
+  }
+  return RangeOrBitVector(std::move(builder).Build());
+}
+
+RangeOrBitVector ArrangementStorage::IndexSearch(FilterOp op,
+                                                 SqlValue sql_val,
+                                                 uint32_t* indices,
+                                                 uint32_t indices_size,
+                                                 bool sorted) const {
+  PERFETTO_TP_TRACE(metatrace::Category::DB, "ArrangementStorage::IndexSearch");
+
+  std::vector<uint32_t> storage_iv;
+  for (uint32_t* it = indices; it != indices + indices_size; ++it) {
+    storage_iv.push_back((*arrangement_)[*it]);
+  }
+  return inner_->IndexSearch(op, sql_val, storage_iv.data(),
+                             static_cast<uint32_t>(storage_iv.size()), sorted);
+}
+
+void ArrangementStorage::StableSort(uint32_t*, uint32_t) const {
+  // TODO(b/307482437): Implement.
+  PERFETTO_FATAL("Not implemented");
+}
+
+void ArrangementStorage::Sort(uint32_t*, uint32_t) const {
+  // TODO(b/307482437): Implement.
+  PERFETTO_FATAL("Not implemented");
+}
+
+void ArrangementStorage::Serialize(StorageProto* storage) const {
+  auto* arrangement_storage = storage->set_arrangement_storage();
+  arrangement_storage->set_values(
+      reinterpret_cast<const uint8_t*>(arrangement_->data()),
+      sizeof(uint32_t) * arrangement_->size());
+  inner_->Serialize(arrangement_storage->set_storage());
+}
+
+}  // namespace storage
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/db/storage/arrangement_storage.h b/src/trace_processor/db/storage/arrangement_storage.h
new file mode 100644
index 0000000..97a944e
--- /dev/null
+++ b/src/trace_processor/db/storage/arrangement_storage.h
@@ -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.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_DB_STORAGE_ARRANGEMENT_STORAGE_H_
+#define SRC_TRACE_PROCESSOR_DB_STORAGE_ARRANGEMENT_STORAGE_H_
+
+#include <memory>
+#include "src/trace_processor/db/storage/storage.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace storage {
+
+// Storage responsible for rearranging the elements of another Storage. It deals
+// with duplicates, permutations and selection; for selection only, it's more
+// efficient to use `SelectorStorage`.
+class ArrangementStorage : public Storage {
+ public:
+  explicit ArrangementStorage(std::unique_ptr<Storage> inner,
+                              const std::vector<uint32_t>* arrangement);
+
+  Storage::SearchValidationResult ValidateSearchConstraints(SqlValue, FilterOp)
+      const override;
+
+  RangeOrBitVector Search(FilterOp op,
+                          SqlValue value,
+                          RowMap::Range range) const override;
+
+  RangeOrBitVector IndexSearch(FilterOp op,
+                               SqlValue value,
+                               uint32_t* indices,
+                               uint32_t indices_count,
+                               bool sorted) const override;
+
+  void StableSort(uint32_t* rows, uint32_t rows_size) const override;
+
+  void Sort(uint32_t* rows, uint32_t rows_size) const override;
+
+  void Serialize(StorageProto*) const override;
+
+  uint32_t size() const override {
+    return static_cast<uint32_t>(arrangement_->size());
+  }
+
+ private:
+  std::unique_ptr<Storage> inner_;
+  const std::vector<uint32_t>* arrangement_;
+};
+
+}  // namespace storage
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_DB_STORAGE_ARRANGEMENT_STORAGE_H_
diff --git a/src/trace_processor/db/storage/arrangement_storage_unittest.cc b/src/trace_processor/db/storage/arrangement_storage_unittest.cc
new file mode 100644
index 0000000..f7b30f1
--- /dev/null
+++ b/src/trace_processor/db/storage/arrangement_storage_unittest.cc
@@ -0,0 +1,98 @@
+/*
+ * 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/arrangement_storage.h"
+
+#include "src/trace_processor/db/storage/fake_storage.h"
+#include "src/trace_processor/db/storage/types.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace storage {
+namespace {
+
+using testing::ElementsAre;
+using testing::IsEmpty;
+
+using Range = RowMap::Range;
+
+std::vector<uint32_t> ToIndexVector(RangeOrBitVector& r_or_bv) {
+  RowMap rm;
+  if (r_or_bv.IsBitVector()) {
+    rm = RowMap(std::move(r_or_bv).TakeIfBitVector());
+  } else {
+    Range range = std::move(r_or_bv).TakeIfRange();
+    rm = RowMap(range.start, range.end);
+  }
+  return rm.GetAllIndices();
+}
+
+TEST(ArrangementStorage, SearchAll) {
+  std::vector<uint32_t> arrangement{1, 1, 2, 2, 3, 3, 4, 4, 1, 1};
+  ArrangementStorage storage(FakeStorage::SearchAll(5), &arrangement);
+
+  auto res =
+      storage.Search(FilterOp::kGe, SqlValue::Long(0u), RowMap::Range(2, 4));
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(2u, 3u));
+}
+
+TEST(ArrangementStorage, SearchNone) {
+  std::vector<uint32_t> arrangement{1, 1, 2, 2, 3, 3, 4, 4, 1, 1};
+  ArrangementStorage storage(FakeStorage::SearchNone(5), &arrangement);
+
+  auto res =
+      storage.Search(FilterOp::kGe, SqlValue::Long(0u), RowMap::Range(2, 4));
+  ASSERT_THAT(ToIndexVector(res), IsEmpty());
+}
+
+TEST(ArrangementStorage, DISABLED_SearchLimited) {
+  std::vector<uint32_t> arrangement{1, 1, 2, 2, 3, 3, 4, 4, 1, 1};
+  ArrangementStorage storage(FakeStorage::SearchSubset(5, Range(4, 5)),
+                             &arrangement);
+
+  auto res = storage.Search(FilterOp::kGe, SqlValue::Long(0u), Range(2, 7));
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(6u));
+}
+
+TEST(ArrangementStorage, SearchBitVector) {
+  std::vector<uint32_t> arrangement{1, 1, 2, 2, 3, 3, 4, 4, 1, 1};
+  ArrangementStorage storage(
+      FakeStorage::SearchSubset(5, BitVector({0, 1, 0, 1, 0})), &arrangement);
+
+  // Table bv:
+  // 1, 1, 0, 0, 1, 1, 0, 0, 1, 1
+  auto res = storage.Search(FilterOp::kGe, SqlValue::Long(0u), Range(0, 10));
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(0, 1, 4, 5, 8, 9));
+}
+
+TEST(ArrangementStorage, IndexSearch) {
+  std::vector<uint32_t> arrangement{1, 1, 2, 2, 3, 3, 4, 4, 1, 1};
+  ArrangementStorage storage(
+      FakeStorage::SearchSubset(5, BitVector({0, 1, 0, 1, 0})), &arrangement);
+
+  std::vector<uint32_t> table_idx{7u, 1u, 3u};
+  RangeOrBitVector res =
+      storage.IndexSearch(FilterOp::kGe, SqlValue::Long(0u), table_idx.data(),
+                          static_cast<uint32_t>(table_idx.size()), false);
+
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(1u));
+}
+
+}  // namespace
+}  // namespace storage
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/db/storage/dense_null_storage.cc b/src/trace_processor/db/storage/dense_null_storage.cc
new file mode 100644
index 0000000..9f764a4
--- /dev/null
+++ b/src/trace_processor/db/storage/dense_null_storage.cc
@@ -0,0 +1,134 @@
+/*
+ * 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/dense_null_storage.h"
+
+#include <cstdint>
+#include <variant>
+
+#include "protos/perfetto/trace_processor/serialization.pbzero.h"
+#include "src/trace_processor/containers/bit_vector.h"
+#include "src/trace_processor/db/storage/types.h"
+#include "src/trace_processor/tp_metatrace.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace storage {
+
+DenseNullStorage::DenseNullStorage(std::unique_ptr<Storage> inner,
+                                   const BitVector* non_null)
+    : inner_(std::move(inner)), non_null_(non_null) {}
+
+Storage::SearchValidationResult DenseNullStorage::ValidateSearchConstraints(
+    SqlValue sql_val,
+    FilterOp op) const {
+  return inner_->ValidateSearchConstraints(sql_val, op);
+}
+
+RangeOrBitVector DenseNullStorage::Search(FilterOp op,
+                                          SqlValue sql_val,
+                                          RowMap::Range in) const {
+  PERFETTO_TP_TRACE(metatrace::Category::DB, "DenseNullStorage::Search");
+
+  RangeOrBitVector inner_res = inner_->Search(op, sql_val, in);
+  BitVector res;
+  if (inner_res.IsRange()) {
+    // If the inner storage returns a range, mask out the appropriate values in
+    // |non_null_| which matches the range. Then, resize to |in.end| as this
+    // is mandated by the API contract of |Storage::Search|.
+    RowMap::Range inner_range = std::move(inner_res).TakeIfRange();
+    PERFETTO_DCHECK(inner_range.end <= in.end);
+    PERFETTO_DCHECK(inner_range.start >= in.start);
+    res = non_null_->IntersectRange(inner_range.start, inner_range.end);
+    res.Resize(in.end, false);
+  } else {
+    res = std::move(inner_res).TakeIfBitVector();
+  }
+  PERFETTO_DCHECK(res.size() == in.end);
+
+  if (op == FilterOp::kIsNull) {
+    // For IS NULL, we need to add any rows in |non_null_| which are zeros: we
+    // do this by taking the appropriate number of rows, inverting it and then
+    // bitwise or-ing the result with it.
+    BitVector non_null_copy = non_null_->Copy();
+    non_null_copy.Resize(in.end);
+    non_null_copy.Not();
+    res.Or(non_null_copy);
+  } else {
+    // For anything else, we just need to ensure that any rows which are null
+    // are removed as they would not match.
+    res.And(*non_null_);
+  }
+  return RangeOrBitVector(std::move(res));
+}
+
+RangeOrBitVector DenseNullStorage::IndexSearch(FilterOp op,
+                                               SqlValue sql_val,
+                                               uint32_t* indices,
+                                               uint32_t indices_size,
+                                               bool sorted) const {
+  PERFETTO_TP_TRACE(metatrace::Category::DB, "DenseNullStorage::IndexSearch");
+
+  RangeOrBitVector inner_res =
+      inner_->IndexSearch(op, sql_val, indices, indices_size, sorted);
+  if (inner_res.IsRange()) {
+    RowMap::Range inner_range = std::move(inner_res).TakeIfRange();
+    BitVector::Builder builder(indices_size, inner_range.start);
+    for (uint32_t i = inner_range.start; i < inner_range.end; ++i) {
+      builder.Append(non_null_->IsSet(indices[i]));
+    }
+    return RangeOrBitVector(std::move(builder).Build());
+  }
+
+  BitVector::Builder builder(indices_size);
+  for (uint32_t i = 0; i < indices_size; ++i) {
+    builder.Append(non_null_->IsSet(indices[i]));
+  }
+  BitVector non_null = std::move(builder).Build();
+  PERFETTO_DCHECK(non_null.size() == indices_size);
+
+  BitVector res = std::move(inner_res).TakeIfBitVector();
+  PERFETTO_DCHECK(res.size() == indices_size);
+
+  if (op == FilterOp::kIsNull) {
+    BitVector null = std::move(non_null);
+    null.Not();
+    res.Or(null);
+  } else {
+    res.And(non_null);
+  }
+  return RangeOrBitVector(std::move(res));
+}
+
+void DenseNullStorage::StableSort(uint32_t*, uint32_t) const {
+  // TODO(b/307482437): Implement.
+  PERFETTO_FATAL("Not implemented");
+}
+
+void DenseNullStorage::Sort(uint32_t*, uint32_t) const {
+  // TODO(b/307482437): Implement.
+  PERFETTO_FATAL("Not implemented");
+}
+
+void DenseNullStorage::Serialize(StorageProto* storage) const {
+  auto* null_storage = storage->set_dense_null_storage();
+  non_null_->Serialize(null_storage->set_bit_vector());
+  inner_->Serialize(null_storage->set_storage());
+}
+
+}  // namespace storage
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/db/storage/dense_null_storage.h b/src/trace_processor/db/storage/dense_null_storage.h
new file mode 100644
index 0000000..ec7b6e9
--- /dev/null
+++ b/src/trace_processor/db/storage/dense_null_storage.h
@@ -0,0 +1,68 @@
+/*
+ * 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_DENSE_NULL_STORAGE_H_
+#define SRC_TRACE_PROCESSOR_DB_STORAGE_DENSE_NULL_STORAGE_H_
+
+#include <memory>
+#include <variant>
+
+#include "src/trace_processor/containers/bit_vector.h"
+#include "src/trace_processor/db/storage/storage.h"
+#include "src/trace_processor/db/storage/types.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace storage {
+
+// Storage which introduces the layer of nullability but without changing the
+// "spacing" of the underlying storage i.e. this storage simply "masks" out
+// rows in the underlying storage with nulls.
+class DenseNullStorage : public Storage {
+ public:
+  DenseNullStorage(std::unique_ptr<Storage> inner, const BitVector* non_null);
+
+  SearchValidationResult ValidateSearchConstraints(SqlValue,
+                                                   FilterOp) const override;
+
+  RangeOrBitVector Search(FilterOp op,
+                          SqlValue value,
+                          RowMap::Range range) const override;
+
+  RangeOrBitVector IndexSearch(FilterOp op,
+                               SqlValue value,
+                               uint32_t* indices,
+                               uint32_t indices_count,
+                               bool sorted) const override;
+
+  void StableSort(uint32_t* rows, uint32_t rows_size) const override;
+
+  void Sort(uint32_t* rows, uint32_t rows_size) const override;
+
+  void Serialize(StorageProto*) const override;
+
+  uint32_t size() const override { return non_null_->size(); }
+
+ private:
+  std::unique_ptr<Storage> inner_;
+  const BitVector* non_null_ = nullptr;
+};
+
+}  // namespace storage
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_DB_STORAGE_DENSE_NULL_STORAGE_H_
diff --git a/src/trace_processor/db/storage/dense_null_storage_unittest.cc b/src/trace_processor/db/storage/dense_null_storage_unittest.cc
new file mode 100644
index 0000000..d8ec93c
--- /dev/null
+++ b/src/trace_processor/db/storage/dense_null_storage_unittest.cc
@@ -0,0 +1,131 @@
+/*
+ * 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/dense_null_storage.h"
+#include <cstdint>
+#include <memory>
+#include <vector>
+
+#include "src/trace_processor/containers/bit_vector.h"
+#include "src/trace_processor/db/storage/fake_storage.h"
+#include "src/trace_processor/db/storage/numeric_storage.h"
+#include "src/trace_processor/db/storage/types.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace storage {
+namespace {
+
+using testing::ElementsAre;
+using testing::IsEmpty;
+using Range = RowMap::Range;
+
+std::vector<uint32_t> ToIndexVector(RangeOrBitVector& r_or_bv) {
+  RowMap rm;
+  if (r_or_bv.IsBitVector()) {
+    rm = RowMap(std::move(r_or_bv).TakeIfBitVector());
+  } else {
+    Range range = std::move(r_or_bv).TakeIfRange();
+    rm = RowMap(range.start, range.end);
+  }
+  return rm.GetAllIndices();
+}
+
+TEST(DenseNullStorage, NoFilteringSearch) {
+  std::vector<uint32_t> data{0, 1, 0, 1, 0};
+  auto numeric =
+      std::make_unique<NumericStorage<uint32_t>>(&data, ColumnType::kUint32);
+
+  BitVector bv{0, 1, 0, 1, 0};
+  DenseNullStorage storage(std::move(numeric), &bv);
+
+  auto res = storage.Search(FilterOp::kGe, SqlValue::Long(0), Range(0, 5));
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(1, 3));
+}
+
+TEST(DenseNullStorage, RestrictInputSearch) {
+  std::vector<uint32_t> data{0, 1, 0, 1, 0};
+  auto numeric =
+      std::make_unique<NumericStorage<uint32_t>>(&data, ColumnType::kUint32);
+
+  BitVector bv{0, 1, 0, 1, 0};
+  DenseNullStorage storage(std::move(numeric), &bv);
+
+  auto res = storage.Search(FilterOp::kGe, SqlValue::Long(0), Range(1, 3));
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(1));
+}
+
+TEST(DenseNullStorage, RangeFilterSearch) {
+  auto fake = FakeStorage::SearchSubset(5, Range(1, 3));
+
+  BitVector bv{0, 1, 0, 1, 0};
+  DenseNullStorage storage(std::move(fake), &bv);
+
+  auto res = storage.Search(FilterOp::kGe, SqlValue::Long(0), Range(0, 5));
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(1));
+}
+
+TEST(DenseNullStorage, BitvectorFilterSearch) {
+  auto fake = FakeStorage::SearchSubset(5, BitVector({0, 1, 1, 0, 0}));
+
+  BitVector bv{0, 1, 0, 1, 0};
+  DenseNullStorage storage(std::move(fake), &bv);
+
+  auto res = storage.Search(FilterOp::kGe, SqlValue::Long(0), Range(0, 5));
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(1));
+}
+
+TEST(DenseNullStorage, IsNullSearch) {
+  auto fake = FakeStorage::SearchSubset(5, BitVector({1, 1, 0, 0, 1}));
+
+  BitVector bv{1, 0, 0, 1, 1};
+  DenseNullStorage storage(std::move(fake), &bv);
+
+  auto res = storage.Search(FilterOp::kIsNull, SqlValue(), Range(0, 5));
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(0, 1, 2, 4));
+}
+
+TEST(DenseNullStorage, IndexSearch) {
+  std::vector<uint32_t> data{1, 0, 0, 1, 1, 1};
+  auto numeric =
+      std::make_unique<NumericStorage<uint32_t>>(&data, ColumnType::kUint32);
+
+  BitVector bv{1, 0, 0, 1, 1, 1};
+  DenseNullStorage storage(std::move(numeric), &bv);
+
+  std::vector<uint32_t> index({5, 2, 3, 4, 1});
+  auto res = storage.IndexSearch(FilterOp::kGe, SqlValue::Long(0), index.data(),
+                                 static_cast<uint32_t>(index.size()), false);
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(0, 2, 3));
+}
+
+TEST(DenseNullStorage, IsNullIndexSearch) {
+  auto fake = FakeStorage::SearchSubset(6, BitVector({0, 0, 0, 1, 1, 1}));
+
+  BitVector bv{0, 1, 0, 1, 1, 1};
+  DenseNullStorage storage(std::move(fake), &bv);
+
+  std::vector<uint32_t> index({5, 2, 3, 4, 1});
+  auto res = storage.IndexSearch(FilterOp::kIsNull, SqlValue(), index.data(),
+                                 static_cast<uint32_t>(index.size()), false);
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(0, 1, 2, 3));
+}
+
+}  // namespace
+}  // namespace storage
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/db/storage/dummy_storage.cc b/src/trace_processor/db/storage/dummy_storage.cc
index 286a028..b4f0be5 100644
--- a/src/trace_processor/db/storage/dummy_storage.cc
+++ b/src/trace_processor/db/storage/dummy_storage.cc
@@ -21,6 +21,12 @@
 namespace trace_processor {
 namespace storage {
 
+DummyStorage::SearchValidationResult DummyStorage::ValidateSearchConstraints(
+    SqlValue,
+    FilterOp) const {
+  PERFETTO_FATAL("Shouldn't be called");
+}
+
 RangeOrBitVector DummyStorage::Search(FilterOp, SqlValue, RowMap::Range) const {
   PERFETTO_FATAL("Shouldn't be called");
 }
@@ -45,7 +51,7 @@
   return 0;
 }
 
-void DummyStorage::Serialize(protos::pbzero::SerializedColumn::Storage*) const {
+void DummyStorage::Serialize(StorageProto*) const {
   PERFETTO_FATAL("Shouldn't be called");
 }
 
diff --git a/src/trace_processor/db/storage/dummy_storage.h b/src/trace_processor/db/storage/dummy_storage.h
index 62dd0ef..7fd3a40 100644
--- a/src/trace_processor/db/storage/dummy_storage.h
+++ b/src/trace_processor/db/storage/dummy_storage.h
@@ -36,6 +36,9 @@
 
   RangeOrBitVector Search(FilterOp, SqlValue, RowMap::Range) const override;
 
+  SearchValidationResult ValidateSearchConstraints(SqlValue,
+                                                   FilterOp) const override;
+
   RangeOrBitVector IndexSearch(FilterOp,
                                SqlValue,
                                uint32_t*,
@@ -46,7 +49,7 @@
 
   void Sort(uint32_t*, uint32_t) const override;
 
-  void Serialize(protos::pbzero::SerializedColumn_Storage*) const override;
+  void Serialize(StorageProto*) const override;
 
   uint32_t size() const override;
 };
diff --git a/src/trace_processor/db/storage/fake_storage.cc b/src/trace_processor/db/storage/fake_storage.cc
new file mode 100644
index 0000000..fd9ac2c
--- /dev/null
+++ b/src/trace_processor/db/storage/fake_storage.cc
@@ -0,0 +1,94 @@
+/*
+ * 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/fake_storage.h"
+#include "src/trace_processor/containers/bit_vector.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 {
+
+FakeStorage::FakeStorage(uint32_t size, SearchStrategy strategy)
+    : size_(size), strategy_(strategy) {}
+
+FakeStorage::SearchValidationResult FakeStorage::ValidateSearchConstraints(
+    SqlValue,
+    FilterOp) const {
+  return SearchValidationResult::kOk;
+}
+
+RangeOrBitVector FakeStorage::Search(FilterOp,
+                                     SqlValue,
+                                     RowMap::Range in) const {
+  switch (strategy_) {
+    case kAll:
+      return RangeOrBitVector(in);
+    case kNone:
+      return RangeOrBitVector(RowMap::Range());
+    case kRange:
+      return RangeOrBitVector(RowMap::Range(std::max(in.start, range_.start),
+                                            std::min(in.end, range_.end)));
+    case kBitVector:
+      return RangeOrBitVector{bit_vector_.IntersectRange(in.start, in.end)};
+  }
+  PERFETTO_FATAL("For GCC");
+}
+
+RangeOrBitVector FakeStorage::IndexSearch(FilterOp,
+                                          SqlValue,
+                                          uint32_t* indices,
+                                          uint32_t indices_size,
+                                          bool) const {
+  switch (strategy_) {
+    case kAll:
+      return RangeOrBitVector(RowMap::Range(0, indices_size));
+    case kNone:
+      return RangeOrBitVector(RowMap::Range());
+    case kRange:
+    case kBitVector: {
+      BitVector::Builder builder(indices_size);
+      for (uint32_t* it = indices; it != indices + indices_size; ++it) {
+        bool in_range = strategy_ == kRange && range_.Contains(*it);
+        bool in_bv = strategy_ == kBitVector && bit_vector_.IsSet(*it);
+        builder.Append(in_range || in_bv);
+      }
+      return RangeOrBitVector(std::move(builder).Build());
+    }
+  }
+  PERFETTO_FATAL("For GCC");
+}
+
+void FakeStorage::StableSort(uint32_t*, uint32_t) const {
+  // TODO(b/307482437): Implement.
+  PERFETTO_FATAL("Not implemented");
+}
+
+void FakeStorage::Sort(uint32_t*, uint32_t) const {
+  // TODO(b/307482437): Implement.
+  PERFETTO_FATAL("Not implemented");
+}
+
+void FakeStorage::Serialize(StorageProto*) const {
+  // FakeStorage doesn't really make sense to serialize.
+  PERFETTO_FATAL("Not implemented");
+}
+
+}  // namespace storage
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/db/storage/fake_storage.h b/src/trace_processor/db/storage/fake_storage.h
new file mode 100644
index 0000000..2320269
--- /dev/null
+++ b/src/trace_processor/db/storage/fake_storage.h
@@ -0,0 +1,91 @@
+/*
+ * 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_FAKE_STORAGE_H_
+#define SRC_TRACE_PROCESSOR_DB_STORAGE_FAKE_STORAGE_H_
+
+#include <memory>
+#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 {
+
+// Fake implementation of Storage for use in tests.
+class FakeStorage final : public Storage {
+ public:
+  SearchValidationResult ValidateSearchConstraints(SqlValue,
+                                                   FilterOp) const override;
+
+  RangeOrBitVector Search(FilterOp op,
+                          SqlValue value,
+                          RowMap::Range range) const override;
+
+  RangeOrBitVector IndexSearch(FilterOp op,
+                               SqlValue value,
+                               uint32_t* indices,
+                               uint32_t indices_count,
+                               bool sorted) const override;
+
+  void StableSort(uint32_t* rows, uint32_t rows_size) const override;
+
+  void Sort(uint32_t* rows, uint32_t rows_size) const override;
+
+  void Serialize(StorageProto*) const override;
+
+  static std::unique_ptr<Storage> SearchAll(uint32_t size) {
+    return std::unique_ptr<Storage>(
+        new FakeStorage(size, SearchStrategy::kAll));
+  }
+
+  static std::unique_ptr<Storage> SearchNone(uint32_t size) {
+    return std::unique_ptr<Storage>(
+        new FakeStorage(size, SearchStrategy::kNone));
+  }
+
+  static std::unique_ptr<Storage> SearchSubset(uint32_t size, RowMap::Range r) {
+    std::unique_ptr<FakeStorage> storage(
+        new FakeStorage(size, SearchStrategy::kRange));
+    storage->range_ = r;
+    return std::move(storage);
+  }
+
+  static std::unique_ptr<Storage> SearchSubset(uint32_t size, BitVector bv) {
+    std::unique_ptr<FakeStorage> storage(
+        new FakeStorage(size, SearchStrategy::kBitVector));
+    storage->bit_vector_ = std::move(bv);
+    return std::move(storage);
+  }
+
+  uint32_t size() const override { return size_; }
+
+ private:
+  enum SearchStrategy { kNone, kAll, kRange, kBitVector };
+  FakeStorage(uint32_t size, SearchStrategy strategy);
+
+  uint32_t size_ = 0;
+  SearchStrategy strategy_ = SearchStrategy::kNone;
+  RowMap::Range range_;
+  BitVector bit_vector_;
+};
+
+}  // namespace storage
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_DB_STORAGE_FAKE_STORAGE_H_
diff --git a/src/trace_processor/db/storage/id_storage.cc b/src/trace_processor/db/storage/id_storage.cc
index 528ca3c..bec9b041 100644
--- a/src/trace_processor/db/storage/id_storage.cc
+++ b/src/trace_processor/db/storage/id_storage.cc
@@ -16,12 +16,15 @@
 
 #include "src/trace_processor/db/storage/id_storage.h"
 
+#include <optional>
+
 #include "perfetto/base/logging.h"
-#include "perfetto/trace_processor/basic_types.h"
+#include "perfetto/public/compiler.h"
 #include "protos/perfetto/trace_processor/serialization.pbzero.h"
 #include "src/trace_processor/containers/bit_vector.h"
 #include "src/trace_processor/containers/row_map.h"
 #include "src/trace_processor/db/storage/types.h"
+#include "src/trace_processor/db/storage/utils.h"
 #include "src/trace_processor/tp_metatrace.h"
 
 namespace perfetto {
@@ -68,33 +71,112 @@
   }
   return RangeOrBitVector(std::move(builder).Build());
 }
+
 }  // namespace
 
+IdStorage::SearchValidationResult IdStorage::ValidateSearchConstraints(
+    SqlValue val,
+    FilterOp op) const {
+  // NULL checks.
+  if (PERFETTO_UNLIKELY(val.is_null())) {
+    if (op == FilterOp::kIsNotNull) {
+      return SearchValidationResult::kAllData;
+    }
+    if (op == FilterOp::kIsNull) {
+      return SearchValidationResult::kNoData;
+    }
+    PERFETTO_DFATAL(
+        "Invalid filter operation. NULL should only be compared with 'IS NULL' "
+        "and 'IS NOT NULL'");
+    return SearchValidationResult::kNoData;
+  }
+
+  // FilterOp checks. Switch so that we get a warning if new FilterOp is not
+  // handled.
+  switch (op) {
+    case FilterOp::kEq:
+    case FilterOp::kNe:
+    case FilterOp::kLt:
+    case FilterOp::kLe:
+    case FilterOp::kGt:
+    case FilterOp::kGe:
+      break;
+    case FilterOp::kIsNull:
+    case FilterOp::kIsNotNull:
+      PERFETTO_FATAL("Invalid constraint");
+    case FilterOp::kGlob:
+    case FilterOp::kRegex:
+      return SearchValidationResult::kNoData;
+  }
+
+  // Type checks.
+  switch (val.type) {
+    case SqlValue::kNull:
+    case SqlValue::kLong:
+    case SqlValue::kDouble:
+      break;
+    case SqlValue::kString:
+      // Any string is always more than any numeric.
+      if (op == FilterOp::kLt || op == FilterOp::kLe) {
+        return Storage::SearchValidationResult::kAllData;
+      }
+      return Storage::SearchValidationResult::kNoData;
+    case SqlValue::kBytes:
+      return Storage::SearchValidationResult::kNoData;
+  }
+
+  // TODO(b/307482437): Remove after adding support for double
+  PERFETTO_CHECK(val.type != SqlValue::kDouble);
+
+  // Bounds of the value.
+  if (PERFETTO_UNLIKELY(val.AsLong() > std::numeric_limits<uint32_t>::max())) {
+    if (op == FilterOp::kLe || op == FilterOp::kLt || op == FilterOp::kNe) {
+      return SearchValidationResult::kAllData;
+    }
+    return SearchValidationResult::kNoData;
+  }
+  if (PERFETTO_UNLIKELY(val.AsLong() < std::numeric_limits<uint32_t>::min())) {
+    if (op == FilterOp::kGe || op == FilterOp::kGt || op == FilterOp::kNe) {
+      return SearchValidationResult::kAllData;
+    }
+    return SearchValidationResult::kNoData;
+  }
+
+  return SearchValidationResult::kOk;
+}
+
 RangeOrBitVector IdStorage::Search(FilterOp op,
                                    SqlValue sql_val,
-                                   RowMap::Range range) const {
+                                   RowMap::Range search_range) const {
   PERFETTO_TP_TRACE(metatrace::Category::DB, "IdStorage::Search",
-                    [&range, op](metatrace::Record* r) {
-                      r->AddArg("Start", std::to_string(range.start));
-                      r->AddArg("End", std::to_string(range.end));
+                    [&search_range, op](metatrace::Record* r) {
+                      r->AddArg("Start", std::to_string(search_range.start));
+                      r->AddArg("End", std::to_string(search_range.end));
                       r->AddArg("Op",
                                 std::to_string(static_cast<uint32_t>(op)));
                     });
 
+  PERFETTO_DCHECK(search_range.end <= size_);
+
+  // After this switch we assume the search is valid.
+  switch (ValidateSearchConstraints(sql_val, op)) {
+    case SearchValidationResult::kOk:
+      break;
+    case SearchValidationResult::kAllData:
+      return RangeOrBitVector(search_range);
+    case SearchValidationResult::kNoData:
+      return RangeOrBitVector(Range());
+  }
+
+  uint32_t val = static_cast<uint32_t>(sql_val.AsLong());
   if (op == FilterOp::kNe) {
-    if (sql_val.AsLong() > std::numeric_limits<uint32_t>::max() ||
-        sql_val.AsLong() < std::numeric_limits<uint32_t>::min())
-      return RangeOrBitVector(Range(0, size_));
-
-    uint32_t val = static_cast<uint32_t>(sql_val.AsLong());
-    BitVector ret(range.start, false);
-    ret.Resize(range.end, true);
+    BitVector ret(search_range.start, false);
+    ret.Resize(search_range.end, true);
     ret.Resize(size_, false);
-
     ret.Clear(val);
     return RangeOrBitVector(std::move(ret));
   }
-  return RangeOrBitVector(BinarySearchIntrinsic(op, sql_val, range));
+  return RangeOrBitVector(BinarySearchIntrinsic(op, val, search_range));
 }
 
 RangeOrBitVector IdStorage::IndexSearch(FilterOp op,
@@ -108,29 +190,17 @@
                       r->AddArg("Op",
                                 std::to_string(static_cast<uint32_t>(op)));
                     });
-  // Validate sql_val
-  if (PERFETTO_UNLIKELY(sql_val.is_null())) {
-    if (op == FilterOp::kIsNotNull) {
-      return RangeOrBitVector(Range(indices_size, true));
-    }
-    return RangeOrBitVector(Range());
+
+  // After this switch we assume the search is valid.
+  switch (ValidateSearchConstraints(sql_val, op)) {
+    case SearchValidationResult::kOk:
+      break;
+    case SearchValidationResult::kAllData:
+      return RangeOrBitVector(Range(0, indices_size));
+    case SearchValidationResult::kNoData:
+      return RangeOrBitVector(Range());
   }
 
-  if (PERFETTO_UNLIKELY(sql_val.AsLong() >
-                        std::numeric_limits<uint32_t>::max())) {
-    if (op == FilterOp::kLe || op == FilterOp::kLt) {
-      return RangeOrBitVector(Range(indices_size, true));
-    }
-    return RangeOrBitVector(Range());
-  }
-
-  if (PERFETTO_UNLIKELY(sql_val.AsLong() <
-                        std::numeric_limits<uint32_t>::min())) {
-    if (op == FilterOp::kGe || op == FilterOp::kGt) {
-      return RangeOrBitVector(Range(indices_size, true));
-    }
-    return RangeOrBitVector(Range());
-  }
   uint32_t val = static_cast<uint32_t>(sql_val.AsLong());
 
   switch (op) {
@@ -153,46 +223,15 @@
       return IndexSearchWithComparator(val, indices, indices_size,
                                        std::greater_equal<uint32_t>());
     case FilterOp::kIsNotNull:
-      return RangeOrBitVector(Range(indices_size, true));
     case FilterOp::kIsNull:
     case FilterOp::kGlob:
     case FilterOp::kRegex:
-      return RangeOrBitVector(Range());
+      PERFETTO_FATAL("Invalid filter operation");
   }
   PERFETTO_FATAL("FilterOp not matched");
 }
 
-Range IdStorage::BinarySearchIntrinsic(FilterOp op,
-                                       SqlValue sql_val,
-                                       Range range) const {
-  PERFETTO_DCHECK(range.end <= size_);
-
-  // Validate sql_value
-  if (PERFETTO_UNLIKELY(sql_val.is_null())) {
-    if (op == FilterOp::kIsNotNull) {
-      return range;
-    }
-    return Range();
-  }
-
-  if (PERFETTO_UNLIKELY(sql_val.AsLong() >
-                        std::numeric_limits<uint32_t>::max())) {
-    if (op == FilterOp::kLe || op == FilterOp::kLt) {
-      return range;
-    }
-    return Range();
-  }
-
-  if (PERFETTO_UNLIKELY(sql_val.AsLong() <
-                        std::numeric_limits<uint32_t>::min())) {
-    if (op == FilterOp::kGe || op == FilterOp::kGt) {
-      return range;
-    }
-    return Range();
-  }
-
-  uint32_t val = static_cast<uint32_t>(sql_val.AsLong());
-
+Range IdStorage::BinarySearchIntrinsic(FilterOp op, Id val, Range range) const {
   switch (op) {
     case FilterOp::kEq:
       return Range(val, val + (range.start <= val && val < range.end));
@@ -205,13 +244,11 @@
     case FilterOp::kGt:
       return RowMap::Range(std::max(val + 1, range.start), range.end);
     case FilterOp::kIsNotNull:
-      return range;
     case FilterOp::kNe:
-      PERFETTO_FATAL("Shouldn't be called");
     case FilterOp::kIsNull:
     case FilterOp::kGlob:
     case FilterOp::kRegex:
-      return RowMap::Range();
+      PERFETTO_FATAL("Invalid filter operation");
   }
   PERFETTO_FATAL("FilterOp not matched");
 }
@@ -225,8 +262,7 @@
   std::sort(indices, indices + indices_size);
 }
 
-void IdStorage::Serialize(
-    protos::pbzero::SerializedColumn::Storage* storage) const {
+void IdStorage::Serialize(StorageProto* storage) const {
   auto* id_storage = storage->set_id_storage();
   id_storage->set_size(size_);
 }
diff --git a/src/trace_processor/db/storage/id_storage.h b/src/trace_processor/db/storage/id_storage.h
index 6f60d9c..475c29b 100644
--- a/src/trace_processor/db/storage/id_storage.h
+++ b/src/trace_processor/db/storage/id_storage.h
@@ -16,6 +16,10 @@
 #ifndef SRC_TRACE_PROCESSOR_DB_STORAGE_ID_STORAGE_H_
 #define SRC_TRACE_PROCESSOR_DB_STORAGE_ID_STORAGE_H_
 
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/status_or.h"
+#include "perfetto/trace_processor/basic_types.h"
+#include "src/trace_processor/containers/bit_vector.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"
@@ -34,6 +38,9 @@
  public:
   explicit IdStorage(uint32_t size) : size_(size) {}
 
+  SearchValidationResult ValidateSearchConstraints(SqlValue,
+                                                   FilterOp) const override;
+
   RangeOrBitVector Search(FilterOp op,
                           SqlValue value,
                           RowMap::Range range) const override;
@@ -48,14 +55,16 @@
 
   void Sort(uint32_t* rows, uint32_t rows_size) const override;
 
-  void Serialize(protos::pbzero::SerializedColumn_Storage*) const override;
+  void Serialize(StorageProto*) const override;
 
   uint32_t size() const override { return size_; }
 
  private:
-  BitVector IndexSearch(FilterOp, SqlValue, uint32_t*, uint32_t) const;
+  using Id = uint32_t;
+
+  BitVector IndexSearch(FilterOp, Id, uint32_t*, uint32_t) const;
   RowMap::Range BinarySearchIntrinsic(FilterOp op,
-                                      SqlValue val,
+                                      Id,
                                       RowMap::Range search_range) const;
 
   const uint32_t size_ = 0;
diff --git a/src/trace_processor/db/storage/id_storage_unittest.cc b/src/trace_processor/db/storage/id_storage_unittest.cc
index 13229e3..0db3159 100644
--- a/src/trace_processor/db/storage/id_storage_unittest.cc
+++ b/src/trace_processor/db/storage/id_storage_unittest.cc
@@ -14,18 +14,101 @@
  * limitations under the License.
  */
 #include "src/trace_processor/db/storage/id_storage.h"
+#include <limits>
 
 #include "src/trace_processor/db/storage/types.h"
 #include "test/gtest_and_gmock.h"
 
 namespace perfetto {
 namespace trace_processor {
+
+inline bool operator==(const RowMap::Range& a, const RowMap::Range& b) {
+  return std::tie(a.start, a.end) == std::tie(b.start, b.end);
+}
+
 namespace storage {
 namespace {
 
 using Range = RowMap::Range;
 
-TEST(IdStorageUnittest, BinarySearchIntrinsicEqSimple) {
+TEST(IdStorageUnittest, InvalidSearchConstraints) {
+  IdStorage storage(100);
+  Range test_range(10, 20);
+  Range empty_range;
+
+  // NULL checks
+  SqlValue val;
+  val.type = SqlValue::kNull;
+  Range search_result =
+      storage.Search(FilterOp::kIsNull, val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+  search_result =
+      storage.Search(FilterOp::kIsNotNull, val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, test_range);
+
+  // FilterOp checks
+  search_result =
+      storage.Search(FilterOp::kGlob, SqlValue::Long(15), test_range)
+          .TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+  search_result =
+      storage.Search(FilterOp::kRegex, SqlValue::Long(15), test_range)
+          .TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+
+  // Type checks
+  search_result =
+      storage.Search(FilterOp::kGe, SqlValue::String("cheese"), test_range)
+          .TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+
+  // Value bounds
+  SqlValue max_val = SqlValue::Long(
+      static_cast<int64_t>(std::numeric_limits<uint32_t>::max()) + 10);
+  search_result =
+      storage.Search(FilterOp::kGe, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+  search_result =
+      storage.Search(FilterOp::kGt, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+  search_result =
+      storage.Search(FilterOp::kEq, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+
+  search_result =
+      storage.Search(FilterOp::kLe, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, test_range);
+  search_result =
+      storage.Search(FilterOp::kLt, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, test_range);
+  search_result =
+      storage.Search(FilterOp::kNe, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, test_range);
+
+  SqlValue min_val = SqlValue::Long(
+      static_cast<int64_t>(std::numeric_limits<uint32_t>::min()) - 1);
+  search_result =
+      storage.Search(FilterOp::kGe, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, test_range);
+  search_result =
+      storage.Search(FilterOp::kGt, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, test_range);
+  search_result =
+      storage.Search(FilterOp::kNe, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, test_range);
+
+  search_result =
+      storage.Search(FilterOp::kLe, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+  search_result =
+      storage.Search(FilterOp::kLt, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+  search_result =
+      storage.Search(FilterOp::kEq, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+}
+
+TEST(IdStorageUnittest, SearchEqSimple) {
   IdStorage storage(100);
   Range range = storage.Search(FilterOp::kEq, SqlValue::Long(15), Range(10, 20))
                     .TakeIfRange();
@@ -34,21 +117,21 @@
   ASSERT_EQ(range.end, 16u);
 }
 
-TEST(IdStorageUnittest, BinarySearchIntrinsicEqOnRangeBoundary) {
+TEST(IdStorageUnittest, SearchEqOnRangeBoundary) {
   IdStorage storage(100);
   Range range = storage.Search(FilterOp::kEq, SqlValue::Long(20), Range(10, 20))
                     .TakeIfRange();
   ASSERT_EQ(range.size(), 0u);
 }
 
-TEST(IdStorageUnittest, BinarySearchIntrinsicEqOutsideRange) {
+TEST(IdStorageUnittest, SearchEqOutsideRange) {
   IdStorage storage(100);
   Range range = storage.Search(FilterOp::kEq, SqlValue::Long(25), Range(10, 20))
                     .TakeIfRange();
   ASSERT_EQ(range.size(), 0u);
 }
 
-TEST(IdStorageUnittest, BinarySearchIntrinsicEqTooBig) {
+TEST(IdStorageUnittest, SearchEqTooBig) {
   IdStorage storage(100);
   Range range =
       storage.Search(FilterOp::kEq, SqlValue::Long(125), Range(10, 20))
@@ -56,7 +139,7 @@
   ASSERT_EQ(range.size(), 0u);
 }
 
-TEST(IdStorageUnittest, BinarySearchIntrinsicLe) {
+TEST(IdStorageUnittest, SearchLe) {
   IdStorage storage(100);
   Range range = storage.Search(FilterOp::kLe, SqlValue::Long(50), Range(30, 70))
                     .TakeIfRange();
@@ -64,7 +147,7 @@
   ASSERT_EQ(range.end, 51u);
 }
 
-TEST(IdStorageUnittest, BinarySearchIntrinsicLt) {
+TEST(IdStorageUnittest, SearchLt) {
   IdStorage storage(100);
   Range range = storage.Search(FilterOp::kLt, SqlValue::Long(50), Range(30, 70))
                     .TakeIfRange();
@@ -72,7 +155,7 @@
   ASSERT_EQ(range.end, 50u);
 }
 
-TEST(IdStorageUnittest, BinarySearchIntrinsicGe) {
+TEST(IdStorageUnittest, SearchGe) {
   IdStorage storage(100);
   Range range = storage.Search(FilterOp::kGe, SqlValue::Long(40), Range(30, 70))
                     .TakeIfRange();
@@ -80,7 +163,7 @@
   ASSERT_EQ(range.end, 70u);
 }
 
-TEST(IdStorageUnittest, BinarySearchIntrinsicGt) {
+TEST(IdStorageUnittest, SearchGt) {
   IdStorage storage(100);
   Range range = storage.Search(FilterOp::kGt, SqlValue::Long(40), Range(30, 70))
                     .TakeIfRange();
@@ -88,7 +171,7 @@
   ASSERT_EQ(range.end, 70u);
 }
 
-TEST(IdStorageUnittest, BinarySearchIntrinsicNe) {
+TEST(IdStorageUnittest, SearchNe) {
   IdStorage storage(100);
   BitVector bv =
       storage.Search(FilterOp::kNe, SqlValue::Long(40), Range(30, 70))
@@ -96,11 +179,110 @@
   ASSERT_EQ(bv.CountSetBits(), 39u);
 }
 
-TEST(IdStorageUnittest, BinarySearchIntrinsicNeInvalidNum) {
+TEST(IdStorageUnittest, SearchNeInvalidNum) {
   IdStorage storage(100);
   Range r = storage.Search(FilterOp::kNe, SqlValue::Long(-1), Range(30, 70))
                 .TakeIfRange();
-  ASSERT_EQ(r.size(), 100u);
+  ASSERT_EQ(r.size(), 40u);
+}
+
+TEST(IdStorageUnittest, IndexSearchEqSimple) {
+  IdStorage storage(12);
+  std::vector<uint32_t> indices{1, 3, 5, 7, 9, 11, 2, 4};
+
+  BitVector bv =
+      storage
+          .IndexSearch(FilterOp::kEq, SqlValue::Long(3), indices.data(),
+                       static_cast<uint32_t>(indices.size()), false)
+          .TakeIfBitVector();
+
+  ASSERT_EQ(bv.CountSetBits(), 1u);
+  ASSERT_TRUE(bv.IsSet(1));
+}
+
+TEST(IdStorageUnittest, IndexSearchEqTooBig) {
+  IdStorage storage(12);
+  std::vector<uint32_t> indices{1, 3, 5, 7, 9, 11, 2, 4};
+
+  BitVector bv =
+      storage
+          .IndexSearch(FilterOp::kEq, SqlValue::Long(20), indices.data(),
+                       static_cast<uint32_t>(indices.size()), false)
+          .TakeIfBitVector();
+
+  ASSERT_EQ(bv.CountSetBits(), 0u);
+}
+
+TEST(IdStorageUnittest, IndexSearchNe) {
+  IdStorage storage(12);
+  std::vector<uint32_t> indices{1, 3, 5, 7, 9, 11, 2, 4};
+
+  BitVector bv =
+      storage
+          .IndexSearch(FilterOp::kNe, SqlValue::Long(3), indices.data(),
+                       static_cast<uint32_t>(indices.size()), false)
+          .TakeIfBitVector();
+
+  ASSERT_EQ(bv.CountSetBits(), 7u);
+  ASSERT_FALSE(bv.IsSet(1));
+}
+
+TEST(IdStorageUnittest, IndexSearchLe) {
+  IdStorage storage(12);
+  std::vector<uint32_t> indices{1, 3, 5, 7, 9, 11, 2, 4};
+
+  BitVector bv =
+      storage
+          .IndexSearch(FilterOp::kLe, SqlValue::Long(3), indices.data(),
+                       static_cast<uint32_t>(indices.size()), false)
+          .TakeIfBitVector();
+
+  ASSERT_EQ(bv.CountSetBits(), 3u);
+  ASSERT_TRUE(bv.IsSet(0));
+  ASSERT_TRUE(bv.IsSet(1));
+  ASSERT_TRUE(bv.IsSet(6));
+}
+
+TEST(IdStorageUnittest, IndexSearchLt) {
+  IdStorage storage(12);
+  std::vector<uint32_t> indices{1, 3, 5, 7, 9, 11, 2, 4};
+
+  BitVector bv =
+      storage
+          .IndexSearch(FilterOp::kLt, SqlValue::Long(3), indices.data(),
+                       static_cast<uint32_t>(indices.size()), false)
+          .TakeIfBitVector();
+
+  ASSERT_EQ(bv.CountSetBits(), 2u);
+}
+
+TEST(IdStorageUnittest, IndexSearchGe) {
+  IdStorage storage(12);
+  std::vector<uint32_t> indices{1, 3, 5, 7, 9, 11, 2, 4};
+
+  BitVector bv =
+      storage
+          .IndexSearch(FilterOp::kGe, SqlValue::Long(6), indices.data(),
+                       static_cast<uint32_t>(indices.size()), false)
+          .TakeIfBitVector();
+
+  ASSERT_EQ(bv.CountSetBits(), 3u);
+}
+
+TEST(IdStorageUnittest, IndexSearchGt) {
+  IdStorage storage(12);
+  std::vector<uint32_t> indices{1, 3, 5, 7, 9, 11, 2, 4};
+
+  BitVector bv =
+      storage
+          .IndexSearch(FilterOp::kGt, SqlValue::Long(6), indices.data(),
+                       static_cast<uint32_t>(indices.size()), false)
+          .TakeIfBitVector();
+
+  ASSERT_EQ(bv.CountSetBits(), 3u);
+  ASSERT_TRUE(bv.IsSet(3));
+  ASSERT_TRUE(bv.IsSet(4));
+  ASSERT_TRUE(bv.IsSet(5));
 }
 
 TEST(IdStorageUnittest, Sort) {
diff --git a/src/trace_processor/db/storage/null_storage.cc b/src/trace_processor/db/storage/null_storage.cc
new file mode 100644
index 0000000..6de2759
--- /dev/null
+++ b/src/trace_processor/db/storage/null_storage.cc
@@ -0,0 +1,144 @@
+/*
+ * 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/null_storage.h"
+
+#include <cstdint>
+#include <variant>
+
+#include "protos/perfetto/trace_processor/serialization.pbzero.h"
+#include "src/trace_processor/containers/bit_vector.h"
+#include "src/trace_processor/containers/row_map.h"
+#include "src/trace_processor/db/storage/types.h"
+#include "src/trace_processor/tp_metatrace.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace storage {
+namespace {
+
+using Range = RowMap::Range;
+
+RangeOrBitVector ReconcileStorageResult(FilterOp op,
+                                        const BitVector& non_null,
+                                        RangeOrBitVector storage_result,
+                                        Range in_range) {
+  PERFETTO_CHECK(in_range.end <= non_null.size());
+
+  // Reconcile the results of the Search operation with the non-null indices
+  // to ensure only those positions are set.
+  BitVector res;
+  if (storage_result.IsRange()) {
+    Range range = std::move(storage_result).TakeIfRange();
+    if (range.size() > 0) {
+      res = non_null.IntersectRange(non_null.IndexOfNthSet(range.start),
+                                    non_null.IndexOfNthSet(range.end - 1) + 1);
+
+      // We should always have at least as many elements as the input range
+      // itself.
+      PERFETTO_CHECK(res.size() <= in_range.end);
+    }
+  } else {
+    res = non_null.Copy();
+    res.UpdateSetBits(std::move(storage_result).TakeIfBitVector());
+  }
+
+  // Ensure that |res| exactly matches the size which we need to return,
+  // padding with zeros or truncating if necessary.
+  res.Resize(in_range.end, false);
+
+  // For the IS NULL constraint, we also need to include all the null indices
+  // themselves.
+  if (PERFETTO_UNLIKELY(op == FilterOp::kIsNull)) {
+    BitVector null = non_null.IntersectRange(in_range.start, in_range.end);
+    null.Resize(in_range.end, false);
+    null.Not();
+    res.Or(null);
+  }
+  return RangeOrBitVector(std::move(res));
+}
+
+}  // namespace
+
+Storage::SearchValidationResult NullStorage::ValidateSearchConstraints(
+    SqlValue sql_val,
+    FilterOp op) const {
+  return storage_->ValidateSearchConstraints(sql_val, op);
+}
+
+NullStorage::NullStorage(std::unique_ptr<Storage> storage,
+                         const BitVector* non_null)
+    : storage_(std::move(storage)), non_null_(non_null) {
+  PERFETTO_DCHECK(non_null_->CountSetBits() <= storage_->size());
+}
+
+RangeOrBitVector NullStorage::Search(FilterOp op,
+                                     SqlValue sql_val,
+                                     RowMap::Range in) const {
+  PERFETTO_TP_TRACE(metatrace::Category::DB, "NullStorage::Search");
+
+  // Figure out the bounds of the indices in the underlying storage and search
+  // it.
+  uint32_t start = non_null_->CountSetBits(in.start);
+  uint32_t end = non_null_->CountSetBits(in.end);
+  return ReconcileStorageResult(
+      op, *non_null_, storage_->Search(op, sql_val, RowMap::Range(start, end)),
+      in);
+}
+
+RangeOrBitVector NullStorage::IndexSearch(FilterOp op,
+                                          SqlValue sql_val,
+                                          uint32_t* indices,
+                                          uint32_t indices_size,
+                                          bool sorted) const {
+  PERFETTO_TP_TRACE(metatrace::Category::DB, "NullStorage::IndexSearch");
+
+  BitVector::Builder storage_non_null(indices_size);
+  std::vector<uint32_t> storage_iv;
+  storage_iv.reserve(indices_size);
+  for (uint32_t* it = indices; it != indices + indices_size; it++) {
+    bool is_non_null = non_null_->IsSet(*it);
+    if (is_non_null) {
+      storage_iv.push_back(non_null_->CountSetBits(*it));
+    }
+    storage_non_null.Append(is_non_null);
+  }
+  RangeOrBitVector range_or_bv =
+      storage_->IndexSearch(op, sql_val, storage_iv.data(),
+                            static_cast<uint32_t>(storage_iv.size()), sorted);
+  return ReconcileStorageResult(op, std::move(storage_non_null).Build(),
+                                std::move(range_or_bv), Range(0, indices_size));
+}
+
+void NullStorage::StableSort(uint32_t*, uint32_t) const {
+  // TODO(b/307482437): Implement.
+  PERFETTO_FATAL("Not implemented");
+}
+
+void NullStorage::Sort(uint32_t*, uint32_t) const {
+  // TODO(b/307482437): Implement.
+  PERFETTO_FATAL("Not implemented");
+}
+
+void NullStorage::Serialize(StorageProto* storage) const {
+  auto* null_storage = storage->set_null_storage();
+  non_null_->Serialize(null_storage->set_bit_vector());
+  storage_->Serialize(null_storage->set_storage());
+}
+
+}  // namespace storage
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/db/storage/null_storage.h b/src/trace_processor/db/storage/null_storage.h
new file mode 100644
index 0000000..3087749
--- /dev/null
+++ b/src/trace_processor/db/storage/null_storage.h
@@ -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.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_DB_STORAGE_NULL_STORAGE_H_
+#define SRC_TRACE_PROCESSOR_DB_STORAGE_NULL_STORAGE_H_
+
+#include <memory>
+#include <variant>
+
+#include "src/trace_processor/containers/bit_vector.h"
+#include "src/trace_processor/db/storage/storage.h"
+#include "src/trace_processor/db/storage/types.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace storage {
+
+// Storage which introduces the layer of nullability. Specifically, spreads out
+// the storage with nulls using a BitVector.
+class NullStorage : public Storage {
+ public:
+  NullStorage(std::unique_ptr<Storage> storage, const BitVector* non_null);
+
+  SearchValidationResult ValidateSearchConstraints(SqlValue,
+                                                   FilterOp) const override;
+
+  RangeOrBitVector Search(FilterOp op,
+                          SqlValue value,
+                          RowMap::Range range) const override;
+
+  RangeOrBitVector IndexSearch(FilterOp op,
+                               SqlValue value,
+                               uint32_t* indices,
+                               uint32_t indices_count,
+                               bool sorted) const override;
+
+  void StableSort(uint32_t* rows, uint32_t rows_size) const override;
+
+  void Sort(uint32_t* rows, uint32_t rows_size) const override;
+
+  void Serialize(StorageProto*) const override;
+
+  uint32_t size() const override { return non_null_->size(); }
+
+ private:
+  std::unique_ptr<Storage> storage_ = nullptr;
+  const BitVector* non_null_ = nullptr;
+};
+
+}  // namespace storage
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_DB_STORAGE_NULL_STORAGE_H_
diff --git a/src/trace_processor/db/storage/null_storage_unittest.cc b/src/trace_processor/db/storage/null_storage_unittest.cc
new file mode 100644
index 0000000..daa235f
--- /dev/null
+++ b/src/trace_processor/db/storage/null_storage_unittest.cc
@@ -0,0 +1,157 @@
+/*
+ * 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/null_storage.h"
+
+#include <memory>
+#include <vector>
+
+#include "src/trace_processor/containers/bit_vector.h"
+#include "src/trace_processor/containers/row_map.h"
+#include "src/trace_processor/db/storage/fake_storage.h"
+#include "src/trace_processor/db/storage/numeric_storage.h"
+#include "src/trace_processor/db/storage/types.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace storage {
+namespace {
+
+using testing::ElementsAre;
+using testing::IsEmpty;
+using Range = RowMap::Range;
+
+std::vector<uint32_t> ToIndexVector(RangeOrBitVector& r_or_bv) {
+  RowMap rm;
+  if (r_or_bv.IsBitVector()) {
+    rm = RowMap(std::move(r_or_bv).TakeIfBitVector());
+  } else {
+    Range range = std::move(r_or_bv).TakeIfRange();
+    rm = RowMap(range.start, range.end);
+  }
+  return rm.GetAllIndices();
+}
+
+TEST(NullStorage, SearchInputInsideBoundary) {
+  BitVector bv{0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0};
+  NullStorage storage(FakeStorage::SearchAll(4u), &bv);
+
+  auto res = storage.Search(FilterOp::kGt, SqlValue::Long(0), Range(1, 6));
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(3, 4));
+}
+
+TEST(NullStorage, SearchInputOutsideBoundary) {
+  BitVector bv{0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0};
+  NullStorage storage(FakeStorage::SearchAll(5u), &bv);
+
+  auto res = storage.Search(FilterOp::kGt, SqlValue::Long(0), Range(3, 8));
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(3, 4, 7));
+}
+
+TEST(NullStorage, SubsetResultOutsideBoundary) {
+  BitVector bv{0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0};
+  NullStorage storage(FakeStorage::SearchSubset(5u, RowMap::Range(1, 3)), &bv);
+
+  auto res = storage.Search(FilterOp::kGt, SqlValue::Long(0), Range(0, 11));
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(3, 4));
+}
+
+TEST(NullStorage, SubsetResultOnBoundary) {
+  BitVector bv{0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0};
+  NullStorage storage(FakeStorage::SearchAll(5u), &bv);
+
+  auto res = storage.Search(FilterOp::kGt, SqlValue::Long(0), Range(0, 11));
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(1, 3, 4, 7, 8));
+}
+
+TEST(NullStorage, BitVectorSubset) {
+  BitVector bv{0, 1, 1, 0, 0, 1, 1, 0};
+  NullStorage storage(FakeStorage::SearchSubset(4u, BitVector{0, 1, 0, 1}),
+                      &bv);
+
+  auto res = storage.Search(FilterOp::kGt, SqlValue::Long(0), Range(0, 8));
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(2, 6));
+}
+
+TEST(NullStorage, BitVectorSubsetIsNull) {
+  BitVector bv{0, 1, 1, 0, 0, 1, 1, 0};
+  NullStorage storage(FakeStorage::SearchSubset(4u, BitVector{0, 1, 0, 1}),
+                      &bv);
+
+  auto res = storage.Search(FilterOp::kIsNull, SqlValue(), Range(0, 8));
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(0, 2, 3, 4, 6, 7));
+}
+
+TEST(NullStorage, IndexSearchAllElements) {
+  BitVector bv{0, 1, 1, 0, 0, 1, 1, 0};
+  NullStorage storage(FakeStorage::SearchAll(4u), &bv);
+
+  std::vector<uint32_t> table_idx{1, 5, 2};
+  auto res =
+      storage.IndexSearch(FilterOp::kGt, SqlValue::Long(0), table_idx.data(),
+                          uint32_t(table_idx.size()), false);
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(0, 1, 2));
+}
+
+TEST(NullStorage, IndexSearchPartialElements) {
+  BitVector bv{0, 1, 1, 0, 0, 1, 1, 0};
+  NullStorage storage(FakeStorage::SearchAll(4u), &bv);
+
+  std::vector<uint32_t> table_idx{1, 4, 2};
+  auto res =
+      storage.IndexSearch(FilterOp::kGt, SqlValue::Long(0), table_idx.data(),
+                          uint32_t(table_idx.size()), false);
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(0, 2));
+}
+
+TEST(NullStorage, IndexSearchIsNullOpEmptyRes) {
+  BitVector bv{0, 1, 1, 0, 0, 1, 1, 0};
+  NullStorage storage(FakeStorage::SearchNone(4u), &bv);
+
+  std::vector<uint32_t> table_idx{0, 3, 5, 4, 2};
+  auto res =
+      storage.IndexSearch(FilterOp::kIsNull, SqlValue(), table_idx.data(),
+                          uint32_t(table_idx.size()), false);
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(0, 1, 3));
+}
+
+TEST(NullStorage, IndexSearchIsNullOp) {
+  BitVector bv{0, 1, 1, 0, 0, 1, 1, 0};
+  NullStorage storage(FakeStorage::SearchSubset(4u, Range(2, 3)), &bv);
+
+  std::vector<uint32_t> table_idx{0, 3, 2, 4, 5};
+  auto res =
+      storage.IndexSearch(FilterOp::kIsNull, SqlValue(), table_idx.data(),
+                          uint32_t(table_idx.size()), false);
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(0, 1, 3, 4));
+}
+
+TEST(NullStorage, IndexSearchIsNotNullOp) {
+  BitVector bv{0, 1, 1, 0, 0, 1, 1, 0};
+  NullStorage storage(FakeStorage::SearchAll(4u), &bv);
+
+  std::vector<uint32_t> table_idx{0, 3, 4};
+  auto res =
+      storage.IndexSearch(FilterOp::kIsNotNull, SqlValue(), table_idx.data(),
+                          uint32_t(table_idx.size()), false);
+  ASSERT_THAT(ToIndexVector(res), IsEmpty());
+}
+
+}  // namespace
+}  // namespace storage
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/db/storage/numeric_storage.cc b/src/trace_processor/db/storage/numeric_storage.cc
index abf9a14..0b792de 100644
--- a/src/trace_processor/db/storage/numeric_storage.cc
+++ b/src/trace_processor/db/storage/numeric_storage.cc
@@ -17,12 +17,17 @@
 
 #include "src/trace_processor/db/storage/numeric_storage.h"
 
+#include <cmath>
 #include <cstddef>
 #include <string>
 
+#include "perfetto/base/compiler.h"
+#include "perfetto/base/logging.h"
+#include "perfetto/public/compiler.h"
 #include "protos/perfetto/trace_processor/serialization.pbzero.h"
 #include "src/trace_processor/containers/bit_vector.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"
 #include "src/trace_processor/db/storage/utils.h"
 #include "src/trace_processor/tp_metatrace.h"
@@ -32,7 +37,7 @@
 namespace storage {
 namespace {
 
-// All viable numeric values for ColumnTypes.
+using Range = RowMap::Range;
 using NumericValue = std::variant<uint32_t, int32_t, int64_t, double_t>;
 
 // Using the fact that binary operators in std are operators() of classes, we
@@ -46,33 +51,22 @@
                                      std::equal_to<T>,
                                      std::not_equal_to<T>>;
 
-// Based on SqlValue and ColumnType, casts SqlValue to proper type, returns
-// std::nullopt if SqlValue can't be cast and should be considered invalid for
-// comparison.
-inline std::optional<NumericValue> GetNumericTypeVariant(ColumnType type,
-                                                         SqlValue val) {
-  if (val.is_null())
-    return std::nullopt;
-
+// Based on SqlValue and ColumnType, casts SqlValue to proper type. Assumes the
+// |val| and |type| are correct.
+inline NumericValue GetNumericTypeVariant(ColumnType type, SqlValue val) {
   switch (type) {
     case ColumnType::kDouble:
       return val.AsDouble();
     case ColumnType::kInt64:
       return val.AsLong();
     case ColumnType::kInt32:
-      if (val.AsLong() > std::numeric_limits<int32_t>::max() ||
-          val.AsLong() < std::numeric_limits<int32_t>::min())
-        return std::nullopt;
       return static_cast<int32_t>(val.AsLong());
     case ColumnType::kUint32:
-      if (val.AsLong() > std::numeric_limits<uint32_t>::max() ||
-          val.AsLong() < std::numeric_limits<uint32_t>::min())
-        return std::nullopt;
       return static_cast<uint32_t>(val.AsLong());
     case ColumnType::kString:
     case ColumnType::kDummy:
     case ColumnType::kId:
-      return std::nullopt;
+      PERFETTO_FATAL("Invalid type");
   }
   PERFETTO_FATAL("For GCC");
 }
@@ -201,73 +195,198 @@
 
 }  // namespace
 
+NumericStorageBase::SearchValidationResult
+NumericStorageBase::ValidateSearchConstraints(SqlValue val, FilterOp op) const {
+  // NULL checks.
+  if (PERFETTO_UNLIKELY(val.is_null())) {
+    if (op == FilterOp::kIsNotNull) {
+      return SearchValidationResult::kAllData;
+    }
+    if (op == FilterOp::kIsNull) {
+      return SearchValidationResult::kNoData;
+    }
+    PERFETTO_FATAL(
+        "Invalid path. NULL should only be compared with 'IS NULL' and 'IS NOT "
+        "NULL'");
+  }
+
+  // FilterOp checks. Switch so that we get a warning if new FilterOp is not
+  // handled.
+  switch (op) {
+    case FilterOp::kEq:
+    case FilterOp::kNe:
+    case FilterOp::kLt:
+    case FilterOp::kLe:
+    case FilterOp::kGt:
+    case FilterOp::kGe:
+      break;
+    case FilterOp::kIsNull:
+    case FilterOp::kIsNotNull:
+      PERFETTO_FATAL("Invalid constraint");
+    case FilterOp::kGlob:
+    case FilterOp::kRegex:
+      return SearchValidationResult::kNoData;
+  }
+
+  // Type checks.
+  switch (val.type) {
+    case SqlValue::kNull:
+    case SqlValue::kLong:
+    case SqlValue::kDouble:
+      break;
+    case SqlValue::kString:
+      // Any string is always more than any numeric.
+      if (op == FilterOp::kLt || op == FilterOp::kLe) {
+        return Storage::SearchValidationResult::kAllData;
+      }
+      return Storage::SearchValidationResult::kNoData;
+    case SqlValue::kBytes:
+      return Storage::SearchValidationResult::kNoData;
+  }
+
+  // TODO(b/307482437): There is currently no support for comparison with double
+  // and it is prevented on QueryExecutor level.
+  if (type_ != ColumnType::kDouble) {
+    PERFETTO_CHECK(val.type != SqlValue::kDouble);
+  }
+
+  // Bounds of the value.
+  enum ExtremeVal { kTooBig, kTooSmall, kOk };
+  ExtremeVal extreme_validator = kOk;
+
+  switch (type_) {
+    case ColumnType::kDouble:
+      // Any value would make a sensible comparison with a double.
+    case ColumnType::kInt64:
+      // TODO(b/307482437): As long as the type is not double there is nothing
+      // to verify here, as all values are going to be in the int64_t limits.
+      break;
+    case ColumnType::kInt32:
+      if (val.AsLong() > std::numeric_limits<int32_t>::max()) {
+        extreme_validator = kTooBig;
+        break;
+      }
+      if (val.AsLong() < std::numeric_limits<int32_t>::min()) {
+        extreme_validator = kTooSmall;
+        break;
+      }
+      break;
+    case ColumnType::kUint32:
+      if (val.AsLong() > std::numeric_limits<uint32_t>::max()) {
+        extreme_validator = kTooBig;
+        break;
+      }
+      if (val.AsLong() < std::numeric_limits<uint32_t>::min()) {
+        extreme_validator = kTooSmall;
+        break;
+      }
+      break;
+    case ColumnType::kString:
+    case ColumnType::kDummy:
+    case ColumnType::kId:
+      break;
+  }
+
+  switch (extreme_validator) {
+    case kOk:
+      return Storage::SearchValidationResult::kOk;
+    case kTooBig:
+      if (op == FilterOp::kLt || op == FilterOp::kLe || op == FilterOp::kNe) {
+        return SearchValidationResult::kAllData;
+      }
+      return SearchValidationResult::kNoData;
+    case kTooSmall:
+      if (op == FilterOp::kGt || op == FilterOp::kGe || op == FilterOp::kNe) {
+        return SearchValidationResult::kAllData;
+      }
+      return SearchValidationResult::kNoData;
+  }
+
+  PERFETTO_FATAL("For GCC");
+}
+
 RangeOrBitVector NumericStorageBase::Search(FilterOp op,
-                                            SqlValue value,
-                                            RowMap::Range range) const {
+                                            SqlValue sql_val,
+                                            RowMap::Range search_range) const {
   PERFETTO_TP_TRACE(metatrace::Category::DB, "NumericStorage::Search",
-                    [&range, op](metatrace::Record* r) {
-                      r->AddArg("Start", std::to_string(range.start));
-                      r->AddArg("End", std::to_string(range.end));
+                    [&search_range, op](metatrace::Record* r) {
+                      r->AddArg("Start", std::to_string(search_range.start));
+                      r->AddArg("End", std::to_string(search_range.end));
                       r->AddArg("Op",
                                 std::to_string(static_cast<uint32_t>(op)));
                     });
 
+  // After this switch we assume the search is valid.
+  switch (ValidateSearchConstraints(sql_val, op)) {
+    case SearchValidationResult::kOk:
+      break;
+    case SearchValidationResult::kAllData:
+      return RangeOrBitVector(Range(0, search_range.end));
+    case SearchValidationResult::kNoData:
+      return RangeOrBitVector(Range());
+  }
+
+  NumericValue val = GetNumericTypeVariant(type_, sql_val);
+
   if (is_sorted_) {
     if (op != FilterOp::kNe) {
-      return RangeOrBitVector(BinarySearchIntrinsic(op, value, range));
+      return RangeOrBitVector(BinarySearchIntrinsic(op, val, search_range));
     }
     // Not equal is a special operation on binary search, as it doesn't define a
     // range, and rather just `not` range returned with `equal` operation.
-    RowMap::Range r = BinarySearchIntrinsic(FilterOp::kEq, value, range);
+    RowMap::Range r = BinarySearchIntrinsic(FilterOp::kEq, val, search_range);
     BitVector bv(r.start, true);
-    bv.Resize(r.end);
-    bv.Resize(range.end, true);
+    bv.Resize(r.end, false);
+    bv.Resize(search_range.end, true);
     return RangeOrBitVector(std::move(bv));
   }
 
-  return RangeOrBitVector(LinearSearchInternal(op, value, range));
+  return RangeOrBitVector(LinearSearchInternal(op, val, search_range));
 }
 
 RangeOrBitVector NumericStorageBase::IndexSearch(FilterOp op,
-                                                 SqlValue value,
+                                                 SqlValue sql_val,
                                                  uint32_t* indices,
-                                                 uint32_t indices_count,
+                                                 uint32_t indices_size,
                                                  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));
+                    [indices_size, op](metatrace::Record* r) {
+                      r->AddArg("Count", std::to_string(indices_size));
                       r->AddArg("Op",
                                 std::to_string(static_cast<uint32_t>(op)));
                     });
+
+  // After this switch we assume the search is valid.
+  switch (ValidateSearchConstraints(sql_val, op)) {
+    case SearchValidationResult::kOk:
+      break;
+    case SearchValidationResult::kAllData:
+      return RangeOrBitVector(Range(0, indices_size));
+    case SearchValidationResult::kNoData:
+      return RangeOrBitVector(Range());
+  }
+  NumericValue val = GetNumericTypeVariant(type_, sql_val);
   if (sorted) {
     return RangeOrBitVector(
-        BinarySearchExtrinsic(op, value, indices, indices_count));
+        BinarySearchExtrinsic(op, val, indices, indices_size));
   }
-  return RangeOrBitVector(
-      IndexSearchInternal(op, value, indices, indices_count));
+  return RangeOrBitVector(IndexSearchInternal(op, val, indices, indices_size));
 }
 
 BitVector NumericStorageBase::LinearSearchInternal(FilterOp op,
-                                                   SqlValue sql_val,
+                                                   NumericValue val,
                                                    RowMap::Range range) const {
-  std::optional<NumericValue> val = GetNumericTypeVariant(type_, sql_val);
-  if (op == FilterOp::kIsNotNull)
-    return BitVector(size(), true);
-
-  if (!val.has_value() || op == FilterOp::kIsNull || op == FilterOp::kGlob)
-    return BitVector(size(), false);
-
   BitVector::Builder builder(range.end, range.start);
-  if (const auto* u32 = std::get_if<uint32_t>(&*val)) {
+  if (const auto* u32 = std::get_if<uint32_t>(&val)) {
     auto* start = static_cast<const uint32_t*>(data_) + range.start;
     TypedLinearSearch(*u32, start, op, builder);
-  } else if (const auto* i64 = std::get_if<int64_t>(&*val)) {
+  } else if (const auto* i64 = std::get_if<int64_t>(&val)) {
     auto* start = static_cast<const int64_t*>(data_) + range.start;
     TypedLinearSearch(*i64, start, op, builder);
-  } else if (const auto* i32 = std::get_if<int32_t>(&*val)) {
+  } else if (const auto* i32 = std::get_if<int32_t>(&val)) {
     auto* start = static_cast<const int32_t*>(data_) + range.start;
     TypedLinearSearch(*i32, start, op, builder);
-  } else if (const auto* db = std::get_if<double>(&*val)) {
+  } else if (const auto* db = std::get_if<double>(&val)) {
     auto* start = static_cast<const double*>(data_) + range.start;
     TypedLinearSearch(*db, start, op, builder);
   } else {
@@ -278,16 +397,9 @@
 
 BitVector NumericStorageBase::IndexSearchInternal(
     FilterOp op,
-    SqlValue sql_val,
+    NumericValue 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);
-
-  if (!val.has_value() || op == FilterOp::kIsNull || op == FilterOp::kGlob)
-    return BitVector(indices_count, false);
-
   BitVector::Builder builder(indices_count);
   std::visit(
       [this, indices, op, &builder](auto val) {
@@ -300,37 +412,30 @@
             },
             GetFilterOpVariant<T>(op));
       },
-      *val);
+      val);
   return std::move(builder).Build();
 }
 
 RowMap::Range NumericStorageBase::BinarySearchIntrinsic(
     FilterOp op,
-    SqlValue sql_val,
+    NumericValue val,
     RowMap::Range search_range) const {
-  std::optional<NumericValue> val = GetNumericTypeVariant(type_, sql_val);
-  if (op == FilterOp::kIsNotNull)
-    return search_range;
-
-  if (!val.has_value() || op == FilterOp::kIsNull || op == FilterOp::kGlob)
-    return RowMap::Range();
-
   switch (op) {
     case FilterOp::kEq:
-      return RowMap::Range(LowerBoundIntrinsic(data_, *val, search_range),
-                           UpperBoundIntrinsic(data_, *val, search_range));
+      return RowMap::Range(LowerBoundIntrinsic(data_, val, search_range),
+                           UpperBoundIntrinsic(data_, val, search_range));
     case FilterOp::kLe: {
       return RowMap::Range(search_range.start,
-                           UpperBoundIntrinsic(data_, *val, search_range));
+                           UpperBoundIntrinsic(data_, val, search_range));
     }
     case FilterOp::kLt:
       return RowMap::Range(search_range.start,
-                           LowerBoundIntrinsic(data_, *val, search_range));
+                           LowerBoundIntrinsic(data_, val, search_range));
     case FilterOp::kGe:
-      return RowMap::Range(LowerBoundIntrinsic(data_, *val, search_range),
+      return RowMap::Range(LowerBoundIntrinsic(data_, val, search_range),
                            search_range.end);
     case FilterOp::kGt:
-      return RowMap::Range(UpperBoundIntrinsic(data_, *val, search_range),
+      return RowMap::Range(UpperBoundIntrinsic(data_, val, search_range),
                            search_range.end);
     case FilterOp::kNe:
     case FilterOp::kIsNull:
@@ -344,34 +449,26 @@
 
 RowMap::Range NumericStorageBase::BinarySearchExtrinsic(
     FilterOp op,
-    SqlValue sql_val,
+    NumericValue val,
     uint32_t* indices,
     uint32_t indices_count) const {
-  std::optional<NumericValue> val = GetNumericTypeVariant(type_, sql_val);
-
-  if (op == FilterOp::kIsNotNull)
-    return RowMap::Range(0, size());
-
-  if (!val.has_value() || op == FilterOp::kIsNull || op == FilterOp::kGlob)
-    return RowMap::Range();
-
   switch (op) {
     case FilterOp::kEq:
       return RowMap::Range(
-          LowerBoundExtrinsic(data_, *val, indices, indices_count),
-          UpperBoundExtrinsic(data_, *val, indices, indices_count));
+          LowerBoundExtrinsic(data_, val, indices, indices_count),
+          UpperBoundExtrinsic(data_, val, indices, indices_count));
     case FilterOp::kLe:
       return RowMap::Range(
-          0, UpperBoundExtrinsic(data_, *val, indices, indices_count));
+          0, UpperBoundExtrinsic(data_, val, indices, indices_count));
     case FilterOp::kLt:
       return RowMap::Range(
-          0, LowerBoundExtrinsic(data_, *val, indices, indices_count));
+          0, LowerBoundExtrinsic(data_, val, indices, indices_count));
     case FilterOp::kGe:
       return RowMap::Range(
-          LowerBoundExtrinsic(data_, *val, indices, indices_count), size_);
+          LowerBoundExtrinsic(data_, val, indices, indices_count), size_);
     case FilterOp::kGt:
       return RowMap::Range(
-          UpperBoundExtrinsic(data_, *val, indices, indices_count), size_);
+          UpperBoundExtrinsic(data_, val, indices, indices_count), size_);
     case FilterOp::kNe:
     case FilterOp::kIsNull:
     case FilterOp::kIsNotNull:
@@ -383,7 +480,6 @@
 }
 
 void NumericStorageBase::StableSort(uint32_t* rows, uint32_t rows_size) const {
-  NumericValue val = *GetNumericTypeVariant(type_, SqlValue::Long(0));
   std::visit(
       [this, &rows, rows_size](auto val_data) {
         using T = decltype(val_data);
@@ -395,20 +491,19 @@
                            return first_val < second_val;
                          });
       },
-      val);
+      GetNumericTypeVariant(type_, SqlValue::Long(0)));
 }
 
-void NumericStorageBase::Sort(uint32_t*, uint32_t) const {}
+void NumericStorageBase::Sort(uint32_t*, uint32_t) const {
+  // TODO(b/307482437): Implement.
+  PERFETTO_ELOG("Not implemented");
+}
 
-void NumericStorageBase::Serialize(
-    protos::pbzero::SerializedColumn::Storage* msg) const {
+void NumericStorageBase::Serialize(StorageProto* msg) const {
   auto* numeric_storage_msg = msg->set_numeric_storage();
   numeric_storage_msg->set_is_sorted(is_sorted_);
   numeric_storage_msg->set_column_type(static_cast<uint32_t>(type_));
 
-  auto* values_msg = numeric_storage_msg->set_values();
-  values_msg->set_size(size_);
-
   uint32_t type_size;
   switch (type_) {
     case ColumnType::kInt64:
@@ -428,8 +523,8 @@
     case ColumnType::kString:
       PERFETTO_FATAL("Invalid column type for NumericStorage");
   }
-  values_msg->set_data(static_cast<const uint8_t*>(data_),
-                       static_cast<size_t>(type_size * size_));
+  numeric_storage_msg->set_values(static_cast<const uint8_t*>(data_),
+                                  static_cast<size_t>(type_size) * size_);
 }
 
 }  // namespace storage
diff --git a/src/trace_processor/db/storage/numeric_storage.h b/src/trace_processor/db/storage/numeric_storage.h
index 3618361..741a8e0 100644
--- a/src/trace_processor/db/storage/numeric_storage.h
+++ b/src/trace_processor/db/storage/numeric_storage.h
@@ -18,6 +18,7 @@
 
 #include <variant>
 
+#include "perfetto/trace_processor/basic_types.h"
 #include "src/trace_processor/db/storage/storage.h"
 #include "src/trace_processor/db/storage/types.h"
 
@@ -33,6 +34,9 @@
 // Storage for all numeric type data (i.e. doubles, int32, int64, uint32).
 class NumericStorageBase : public Storage {
  public:
+  SearchValidationResult ValidateSearchConstraints(SqlValue,
+                                                   FilterOp) const override;
+
   RangeOrBitVector Search(FilterOp op,
                           SqlValue value,
                           RowMap::Range range) const override;
@@ -47,7 +51,7 @@
 
   void Sort(uint32_t* rows, uint32_t rows_size) const override;
 
-  void Serialize(protos::pbzero::SerializedColumn_Storage*) const override;
+  void Serialize(StorageProto*) const override;
 
   inline uint32_t size() const override { return size_; }
 
@@ -59,21 +63,24 @@
       : size_(size), data_(data), type_(type), is_sorted_(is_sorted) {}
 
  private:
+  // All viable numeric values for ColumnTypes.
+  using NumericValue = std::variant<uint32_t, int32_t, int64_t, double>;
+
   BitVector LinearSearchInternal(FilterOp op,
-                                 SqlValue val,
+                                 NumericValue val,
                                  RowMap::Range) const;
 
   BitVector IndexSearchInternal(FilterOp op,
-                                SqlValue value,
+                                NumericValue value,
                                 uint32_t* indices,
                                 uint32_t indices_count) const;
 
   RowMap::Range BinarySearchIntrinsic(FilterOp op,
-                                      SqlValue val,
+                                      NumericValue val,
                                       RowMap::Range search_range) const;
 
   RowMap::Range BinarySearchExtrinsic(FilterOp op,
-                                      SqlValue val,
+                                      NumericValue val,
                                       uint32_t* indices,
                                       uint32_t indices_count) const;
 
diff --git a/src/trace_processor/db/storage/numeric_storage_unittest.cc b/src/trace_processor/db/storage/numeric_storage_unittest.cc
index b6ffb59..36fa51a 100644
--- a/src/trace_processor/db/storage/numeric_storage_unittest.cc
+++ b/src/trace_processor/db/storage/numeric_storage_unittest.cc
@@ -20,11 +20,160 @@
 
 namespace perfetto {
 namespace trace_processor {
+
+inline bool operator==(const RowMap::Range& a, const RowMap::Range& b) {
+  return std::tie(a.start, a.end) == std::tie(b.start, b.end);
+}
+
 namespace storage {
 namespace {
 
 using Range = RowMap::Range;
 
+TEST(NumericStorageUnittest, InvalidSearchConstraintsGeneralChecks) {
+  std::vector<uint32_t> data_vec(128);
+  std::iota(data_vec.begin(), data_vec.end(), 0);
+  NumericStorage<uint32_t> storage(&data_vec, ColumnType::kUint32);
+
+  Range test_range(20, 100);
+  Range full_range(0, 100);
+  Range empty_range;
+
+  // NULL checks
+  SqlValue val;
+  val.type = SqlValue::kNull;
+  Range search_result =
+      storage.Search(FilterOp::kIsNull, val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+  search_result =
+      storage.Search(FilterOp::kIsNotNull, val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, full_range);
+
+  // FilterOp checks
+  search_result =
+      storage.Search(FilterOp::kGlob, SqlValue::Long(15), test_range)
+          .TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+  search_result =
+      storage.Search(FilterOp::kRegex, SqlValue::Long(15), test_range)
+          .TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+
+  // Type checks
+  search_result =
+      storage.Search(FilterOp::kGe, SqlValue::String("cheese"), test_range)
+          .TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+}
+
+TEST(NumericStorageUnittest, InvalidValueBoundsUint32) {
+  std::vector<uint32_t> data_vec(128);
+  std::iota(data_vec.begin(), data_vec.end(), 0);
+  NumericStorage<uint32_t> storage(&data_vec, ColumnType::kUint32);
+
+  Range test_range(20, 100);
+  Range full_range(0, 100);
+  Range empty_range;
+
+  SqlValue max_val = SqlValue::Long(
+      static_cast<int64_t>(std::numeric_limits<uint32_t>::max()) + 10);
+  Range search_result =
+      storage.Search(FilterOp::kGe, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+  search_result =
+      storage.Search(FilterOp::kGt, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+  search_result =
+      storage.Search(FilterOp::kEq, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+
+  search_result =
+      storage.Search(FilterOp::kLe, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, full_range);
+  search_result =
+      storage.Search(FilterOp::kLt, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, full_range);
+  search_result =
+      storage.Search(FilterOp::kNe, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, full_range);
+
+  SqlValue min_val = SqlValue::Long(
+      static_cast<int64_t>(std::numeric_limits<uint32_t>::min()) - 1);
+  search_result =
+      storage.Search(FilterOp::kGe, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, full_range);
+  search_result =
+      storage.Search(FilterOp::kGt, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, full_range);
+  search_result =
+      storage.Search(FilterOp::kNe, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, full_range);
+
+  search_result =
+      storage.Search(FilterOp::kLe, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+  search_result =
+      storage.Search(FilterOp::kLt, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+  search_result =
+      storage.Search(FilterOp::kEq, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+}
+
+TEST(NumericStorageUnittest, InvalidValueBoundsInt32) {
+  std::vector<int32_t> data_vec(128);
+  std::iota(data_vec.begin(), data_vec.end(), 0);
+  NumericStorage<int32_t> storage(&data_vec, ColumnType::kInt32);
+
+  Range test_range(20, 100);
+  Range full_range(0, 100);
+  Range empty_range;
+
+  SqlValue max_val = SqlValue::Long(
+      static_cast<int64_t>(std::numeric_limits<int32_t>::max()) + 10);
+  Range search_result =
+      storage.Search(FilterOp::kGe, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+  search_result =
+      storage.Search(FilterOp::kGt, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+  search_result =
+      storage.Search(FilterOp::kEq, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+
+  search_result =
+      storage.Search(FilterOp::kLe, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, full_range);
+  search_result =
+      storage.Search(FilterOp::kLt, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, full_range);
+  search_result =
+      storage.Search(FilterOp::kNe, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, full_range);
+
+  SqlValue min_val = SqlValue::Long(
+      static_cast<int64_t>(std::numeric_limits<int32_t>::min()) - 1);
+  search_result =
+      storage.Search(FilterOp::kGe, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, full_range);
+  search_result =
+      storage.Search(FilterOp::kGt, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, full_range);
+  search_result =
+      storage.Search(FilterOp::kNe, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, full_range);
+
+  search_result =
+      storage.Search(FilterOp::kLe, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+  search_result =
+      storage.Search(FilterOp::kLt, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+  search_result =
+      storage.Search(FilterOp::kEq, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+}
+
 TEST(NumericStorageUnittest, StableSortTrivial) {
   std::vector<uint32_t> data_vec{0, 1, 2, 0, 1, 2, 0, 1, 2};
   std::vector<uint32_t> out = {0, 1, 2, 3, 4, 5, 6, 7, 8};
diff --git a/src/trace_processor/db/storage/selector_storage.cc b/src/trace_processor/db/storage/selector_storage.cc
new file mode 100644
index 0000000..934b900
--- /dev/null
+++ b/src/trace_processor/db/storage/selector_storage.cc
@@ -0,0 +1,111 @@
+/*
+ * 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/selector_storage.h"
+
+#include "protos/perfetto/trace_processor/serialization.pbzero.h"
+#include "src/trace_processor/containers/bit_vector.h"
+#include "src/trace_processor/db/storage/types.h"
+#include "src/trace_processor/tp_metatrace.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace storage {
+
+using Range = RowMap::Range;
+
+SelectorStorage::SelectorStorage(std::unique_ptr<Storage> inner,
+                                 const BitVector* selector)
+    : inner_(std::move(inner)), selector_(selector) {}
+
+Storage::SearchValidationResult SelectorStorage::ValidateSearchConstraints(
+    SqlValue sql_val,
+    FilterOp op) const {
+  return inner_->ValidateSearchConstraints(sql_val, op);
+}
+
+RangeOrBitVector SelectorStorage::Search(FilterOp op,
+                                         SqlValue sql_val,
+                                         RowMap::Range in) const {
+  PERFETTO_TP_TRACE(metatrace::Category::DB, "SelectorStorage::Search");
+
+  // Figure out the bounds of the indices in the underlying storage and search
+  // it.
+  uint32_t start_idx = selector_->IndexOfNthSet(in.start);
+  uint32_t end_idx = selector_->IndexOfNthSet(in.end - 1) + 1;
+
+  auto storage_result = inner_->Search(op, sql_val, Range(start_idx, end_idx));
+  if (storage_result.IsRange()) {
+    Range storage_range = std::move(storage_result).TakeIfRange();
+    uint32_t out_start = selector_->CountSetBits(storage_range.start);
+    uint32_t out_end = selector_->CountSetBits(storage_range.end);
+    return RangeOrBitVector(Range(out_start, out_end));
+  }
+
+  BitVector storage_bitvector = std::move(storage_result).TakeIfBitVector();
+  PERFETTO_DCHECK(storage_bitvector.size() <= selector_->size());
+
+  // TODO(b/283763282): implement ParallelExtractBits to optimize this
+  // operation.
+  BitVector::Builder res(in.end);
+  for (auto it = selector_->IterateSetBits();
+       it && it.index() < storage_bitvector.size(); it.Next()) {
+    res.Append(storage_bitvector.IsSet(it.index()));
+  }
+  return RangeOrBitVector(std::move(res).Build());
+}
+
+RangeOrBitVector SelectorStorage::IndexSearch(FilterOp op,
+                                              SqlValue sql_val,
+                                              uint32_t* indices,
+                                              uint32_t indices_size,
+                                              bool sorted) const {
+  PERFETTO_DCHECK(indices_size == 0 ||
+                  *std::max_element(indices, indices + indices_size) <=
+                      selector_->size());
+
+  PERFETTO_TP_TRACE(metatrace::Category::DB, "SelectorStorage::IndexSearch");
+
+  // To go from TableIndexVector to StorageIndexVector we need to find index in
+  // |selector_| by looking only into set bits.
+  std::vector<uint32_t> storage_iv;
+  storage_iv.reserve(indices_size);
+  for (const uint32_t* it = indices; it != indices + indices_size; ++it) {
+    storage_iv.push_back(selector_->IndexOfNthSet(*it));
+  }
+  return inner_->IndexSearch(op, sql_val, storage_iv.data(),
+                             static_cast<uint32_t>(storage_iv.size()), sorted);
+}
+
+void SelectorStorage::StableSort(uint32_t*, uint32_t) const {
+  // TODO(b/307482437): Implement.
+  PERFETTO_FATAL("Not implemented");
+}
+
+void SelectorStorage::Sort(uint32_t*, uint32_t) const {
+  // TODO(b/307482437): Implement.
+  PERFETTO_FATAL("Not implemented");
+}
+
+void SelectorStorage::Serialize(StorageProto* storage) const {
+  auto* selector_storage = storage->set_selector_storage();
+  inner_->Serialize(selector_storage->set_storage());
+  selector_->Serialize(selector_storage->set_bit_vector());
+}
+
+}  // namespace storage
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/db/storage/selector_storage.h b/src/trace_processor/db/storage/selector_storage.h
new file mode 100644
index 0000000..9d368e2
--- /dev/null
+++ b/src/trace_processor/db/storage/selector_storage.h
@@ -0,0 +1,64 @@
+/*
+ * 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_SELECTOR_STORAGE_H_
+#define SRC_TRACE_PROCESSOR_DB_STORAGE_SELECTOR_STORAGE_H_
+
+#include "src/trace_processor/db/storage/storage.h"
+#include "src/trace_processor/db/storage/types.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace storage {
+
+// Storage which "selects" specific rows from an underlying storage using a
+// BitVector. See ArrangementStorage for a more generic class which allows
+// duplication and rearragement but is less performant.
+class SelectorStorage : public Storage {
+ public:
+  SelectorStorage(std::unique_ptr<Storage> storage, const BitVector* non_null);
+
+  Storage::SearchValidationResult ValidateSearchConstraints(SqlValue, FilterOp)
+      const override;
+
+  RangeOrBitVector Search(FilterOp op,
+                          SqlValue value,
+                          RowMap::Range range) const override;
+
+  RangeOrBitVector IndexSearch(FilterOp op,
+                               SqlValue value,
+                               uint32_t* indices,
+                               uint32_t indices_count,
+                               bool sorted) const override;
+
+  void StableSort(uint32_t* rows, uint32_t rows_size) const override;
+
+  void Sort(uint32_t* rows, uint32_t rows_size) const override;
+
+  void Serialize(StorageProto*) const override;
+
+  uint32_t size() const override { return selector_->size(); }
+
+ private:
+  std::unique_ptr<Storage> inner_ = nullptr;
+  const BitVector* selector_ = nullptr;
+};
+
+}  // namespace storage
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_DB_STORAGE_SELECTOR_STORAGE_H_
diff --git a/src/trace_processor/db/storage/selector_storage_unittest.cc b/src/trace_processor/db/storage/selector_storage_unittest.cc
new file mode 100644
index 0000000..04c3de2
--- /dev/null
+++ b/src/trace_processor/db/storage/selector_storage_unittest.cc
@@ -0,0 +1,97 @@
+/*
+ * 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/selector_storage.h"
+
+#include "src/trace_processor/db/storage/fake_storage.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace storage {
+namespace {
+
+using testing::ElementsAre;
+using testing::IsEmpty;
+
+using Range = RowMap::Range;
+
+std::vector<uint32_t> ToIndexVector(RangeOrBitVector& r_or_bv) {
+  RowMap rm;
+  if (r_or_bv.IsBitVector()) {
+    rm = RowMap(std::move(r_or_bv).TakeIfBitVector());
+  } else {
+    Range range = std::move(r_or_bv).TakeIfRange();
+    rm = RowMap(range.start, range.end);
+  }
+  return rm.GetAllIndices();
+}
+
+TEST(SelectorStorage, SearchAll) {
+  BitVector selector{0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1};
+  SelectorStorage storage(FakeStorage::SearchAll(10), &selector);
+
+  auto res =
+      storage.Search(FilterOp::kGe, SqlValue::Long(0u), RowMap::Range(1, 4));
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(1u, 2u, 3u));
+}
+
+TEST(SelectorStorage, SearchNone) {
+  BitVector selector{0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1};
+  SelectorStorage storage(FakeStorage::SearchNone(10), &selector);
+
+  auto res =
+      storage.Search(FilterOp::kGe, SqlValue::Long(0u), RowMap::Range(1, 4));
+  ASSERT_THAT(ToIndexVector(res), IsEmpty());
+}
+
+TEST(SelectorStorage, SearchLimited) {
+  BitVector selector{0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1};
+  SelectorStorage storage(FakeStorage::SearchSubset(10, Range(4, 5)),
+                          &selector);
+
+  auto res =
+      storage.Search(FilterOp::kGe, SqlValue::Long(0u), RowMap::Range(1, 5));
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(2u));
+}
+
+TEST(SelectorStorage, SearchBitVector) {
+  BitVector selector{0, 1, 1, 0, 0, 1, 1, 0};
+  SelectorStorage storage(
+      FakeStorage::SearchSubset(8, BitVector({0, 1, 0, 1, 0, 1, 0, 0})),
+      &selector);
+
+  auto res = storage.Search(FilterOp::kGe, SqlValue::Long(0u), Range(0, 4));
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(0, 2));
+}
+
+TEST(SelectorStorage, IndexSearch) {
+  BitVector selector{0, 1, 1, 0, 0, 1, 1, 0};
+  SelectorStorage storage(
+      FakeStorage::SearchSubset(8, BitVector({0, 1, 0, 1, 0, 1, 0, 0})),
+      &selector);
+
+  std::vector<uint32_t> table_idx{1u, 0u, 3u};
+  RangeOrBitVector res =
+      storage.IndexSearch(FilterOp::kGe, SqlValue::Long(0u), table_idx.data(),
+                          static_cast<uint32_t>(table_idx.size()), false);
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(1u));
+}
+
+}  // namespace
+}  // namespace storage
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/db/storage/set_id_storage.cc b/src/trace_processor/db/storage/set_id_storage.cc
index 61bc2aa..94b94dc 100644
--- a/src/trace_processor/db/storage/set_id_storage.cc
+++ b/src/trace_processor/db/storage/set_id_storage.cc
@@ -58,73 +58,140 @@
 
 }  // namespace
 
+SetIdStorage::SearchValidationResult SetIdStorage::ValidateSearchConstraints(
+    SqlValue val,
+    FilterOp op) const {
+  // NULL checks.
+  if (PERFETTO_UNLIKELY(val.is_null())) {
+    if (op == FilterOp::kIsNotNull) {
+      return SearchValidationResult::kAllData;
+    }
+    if (op == FilterOp::kIsNull) {
+      return SearchValidationResult::kNoData;
+    }
+    PERFETTO_FATAL(
+        "Invalid filter operation. NULL should only be compared with 'IS NULL' "
+        "and 'IS NOT NULL'");
+  }
+
+  // FilterOp checks. Switch so that we get a warning if new FilterOp is not
+  // handled.
+  switch (op) {
+    case FilterOp::kEq:
+    case FilterOp::kNe:
+    case FilterOp::kLt:
+    case FilterOp::kLe:
+    case FilterOp::kGt:
+    case FilterOp::kGe:
+      break;
+    case FilterOp::kIsNull:
+    case FilterOp::kIsNotNull:
+      PERFETTO_FATAL("Invalid constraints.");
+    case FilterOp::kGlob:
+    case FilterOp::kRegex:
+      return SearchValidationResult::kNoData;
+  }
+
+  // Type checks.
+  switch (val.type) {
+    case SqlValue::kNull:
+    case SqlValue::kLong:
+    case SqlValue::kDouble:
+      break;
+    case SqlValue::kString:
+      // Any string is always more than any numeric.
+      if (op == FilterOp::kLt || op == FilterOp::kLe) {
+        return Storage::SearchValidationResult::kAllData;
+      }
+      return Storage::SearchValidationResult::kNoData;
+    case SqlValue::kBytes:
+      return Storage::SearchValidationResult::kNoData;
+  }
+
+  // TODO(b/307482437): Remove after adding support for double
+  PERFETTO_CHECK(val.type != SqlValue::kDouble);
+
+  // Bounds of the value.
+  if (PERFETTO_UNLIKELY(val.AsLong() > std::numeric_limits<uint32_t>::max())) {
+    if (op == FilterOp::kLe || op == FilterOp::kLt || op == FilterOp::kNe) {
+      return SearchValidationResult::kAllData;
+    }
+    return SearchValidationResult::kNoData;
+  }
+  if (PERFETTO_UNLIKELY(val.AsLong() < std::numeric_limits<uint32_t>::min())) {
+    if (op == FilterOp::kGe || op == FilterOp::kGt || op == FilterOp::kNe) {
+      return SearchValidationResult::kAllData;
+    }
+    return SearchValidationResult::kNoData;
+  }
+
+  return SearchValidationResult::kOk;
+}
+
 RangeOrBitVector SetIdStorage::Search(FilterOp op,
                                       SqlValue sql_val,
-                                      RowMap::Range range) const {
+                                      RowMap::Range search_range) const {
   PERFETTO_TP_TRACE(metatrace::Category::DB, "SetIdStorage::Search",
-                    [&range, op](metatrace::Record* r) {
-                      r->AddArg("Start", std::to_string(range.start));
-                      r->AddArg("End", std::to_string(range.end));
+                    [&search_range, op](metatrace::Record* r) {
+                      r->AddArg("Start", std::to_string(search_range.start));
+                      r->AddArg("End", std::to_string(search_range.end));
                       r->AddArg("Op",
                                 std::to_string(static_cast<uint32_t>(op)));
                     });
 
-  PERFETTO_DCHECK(range.end <= size());
+  PERFETTO_DCHECK(search_range.end <= size());
+
+  // After this switch we assume the search is valid.
+  switch (ValidateSearchConstraints(sql_val, op)) {
+    case SearchValidationResult::kOk:
+      break;
+    case SearchValidationResult::kAllData:
+      return RangeOrBitVector(Range(0, search_range.end));
+    case SearchValidationResult::kNoData:
+      return RangeOrBitVector(Range());
+  }
+
+  uint32_t val = static_cast<uint32_t>(sql_val.AsLong());
 
   if (op == FilterOp::kNe) {
-    if (sql_val.is_null()) {
-      return RangeOrBitVector(Range());
-    }
     // Not equal is a special operation on binary search, as it doesn't define a
     // range, and rather just `not` range returned with `equal` operation.
     RowMap::Range eq_range =
-        BinarySearchIntrinsic(FilterOp::kEq, sql_val, range);
-    BitVector bv(eq_range.start, true);
-    bv.Resize(eq_range.end);
-    bv.Resize(std::min(range.end - 1, eq_range.end), true);
+        BinarySearchIntrinsic(FilterOp::kEq, val, search_range);
+    BitVector bv(search_range.start, false);
+    bv.Resize(eq_range.start, true);
+    bv.Resize(eq_range.end, false);
+    bv.Resize(search_range.end, true);
     return RangeOrBitVector(std::move(bv));
   }
-  return RangeOrBitVector(BinarySearchIntrinsic(op, sql_val, range));
+  return RangeOrBitVector(BinarySearchIntrinsic(op, val, search_range));
 }
 
 RangeOrBitVector SetIdStorage::IndexSearch(FilterOp op,
                                            SqlValue sql_val,
                                            uint32_t* indices,
-                                           uint32_t indices_count,
+                                           uint32_t indices_size,
                                            bool) const {
   PERFETTO_TP_TRACE(metatrace::Category::DB, "SetIdStorage::IndexSearch",
-                    [indices_count, op](metatrace::Record* r) {
-                      r->AddArg("Count", std::to_string(indices_count));
+                    [indices_size, op](metatrace::Record* r) {
+                      r->AddArg("Count", std::to_string(indices_size));
                       r->AddArg("Op",
                                 std::to_string(static_cast<uint32_t>(op)));
                     });
 
-  // Validate sql_val
-  if (PERFETTO_UNLIKELY(sql_val.is_null())) {
-    if (op == FilterOp::kIsNotNull) {
-      return RangeOrBitVector(Range(indices_count, true));
-    }
-    return RangeOrBitVector(Range());
+  // After this switch we assume the search is valid.
+  switch (ValidateSearchConstraints(sql_val, op)) {
+    case SearchValidationResult::kOk:
+      break;
+    case SearchValidationResult::kAllData:
+      return RangeOrBitVector(Range(0, indices_size));
+    case SearchValidationResult::kNoData:
+      return RangeOrBitVector(Range());
   }
 
-  if (PERFETTO_UNLIKELY(sql_val.AsLong() >
-                        std::numeric_limits<uint32_t>::max())) {
-    if (op == FilterOp::kLe || op == FilterOp::kLt) {
-      return RangeOrBitVector(Range(indices_count, true));
-    }
-    return RangeOrBitVector(Range());
-  }
-
-  if (PERFETTO_UNLIKELY(sql_val.AsLong() <
-                        std::numeric_limits<uint32_t>::min())) {
-    if (op == FilterOp::kGe || op == FilterOp::kGt) {
-      return RangeOrBitVector(Range(indices_count, true));
-    }
-    return RangeOrBitVector(Range());
-  }
   uint32_t val = static_cast<uint32_t>(sql_val.AsLong());
 
-  BitVector::Builder builder(indices_count);
+  BitVector::Builder builder(indices_size);
 
   // TODO(mayzner): Instead of utils::IndexSearchWithComparator, use the
   // property of SetId data - that for each index i, data[i] <= i.
@@ -154,7 +221,7 @@
                                        std::greater_equal<uint32_t>(), builder);
       break;
     case FilterOp::kIsNotNull:
-      return RangeOrBitVector(Range(0, indices_count));
+      return RangeOrBitVector(Range(0, indices_size));
     case FilterOp::kIsNull:
       return RangeOrBitVector(Range());
     case FilterOp::kGlob:
@@ -165,34 +232,8 @@
 }
 
 Range SetIdStorage::BinarySearchIntrinsic(FilterOp op,
-                                          SqlValue sql_val,
+                                          SetId val,
                                           Range range) const {
-  // Validate sql_value
-  if (PERFETTO_UNLIKELY(sql_val.is_null())) {
-    if (op == FilterOp::kIsNotNull) {
-      return range;
-    }
-    return Range();
-  }
-
-  if (PERFETTO_UNLIKELY(sql_val.AsLong() >
-                        std::numeric_limits<uint32_t>::max())) {
-    if (op == FilterOp::kLe || op == FilterOp::kLt) {
-      return range;
-    }
-    return Range();
-  }
-
-  if (PERFETTO_UNLIKELY(sql_val.AsLong() <
-                        std::numeric_limits<uint32_t>::min())) {
-    if (op == FilterOp::kGe || op == FilterOp::kGt) {
-      return range;
-    }
-    return Range();
-  }
-
-  uint32_t val = static_cast<uint32_t>(sql_val.AsLong());
-
   switch (op) {
     case FilterOp::kEq:
       return Range(LowerBoundIntrinsic(values_->data(), val, range),
@@ -232,10 +273,9 @@
 
 void SetIdStorage::Serialize(
     protos::pbzero::SerializedColumn::Storage* msg) const {
-  auto* vec_msg = msg->set_set_id_storage()->set_values();
-  vec_msg->set_size(size());
-  vec_msg->set_data(reinterpret_cast<const uint8_t*>(values_->data()),
-                    sizeof(SetId) * size());
+  auto* vec_msg = msg->set_set_id_storage();
+  vec_msg->set_values(reinterpret_cast<const uint8_t*>(values_->data()),
+                      sizeof(SetId) * size());
 }
 
 }  // namespace storage
diff --git a/src/trace_processor/db/storage/set_id_storage.h b/src/trace_processor/db/storage/set_id_storage.h
index 91dab18..6886bef 100644
--- a/src/trace_processor/db/storage/set_id_storage.h
+++ b/src/trace_processor/db/storage/set_id_storage.h
@@ -36,6 +36,9 @@
 
   explicit SetIdStorage(const std::vector<uint32_t>* data) : values_(data) {}
 
+  SearchValidationResult ValidateSearchConstraints(SqlValue,
+                                                   FilterOp) const override;
+
   RangeOrBitVector Search(FilterOp op,
                           SqlValue value,
                           RowMap::Range range) const override;
@@ -50,16 +53,16 @@
 
   void Sort(uint32_t* rows, uint32_t rows_size) const override;
 
-  void Serialize(protos::pbzero::SerializedColumn_Storage*) const override;
+  void Serialize(StorageProto*) const override;
 
   uint32_t size() const override {
     return static_cast<uint32_t>(values_->size());
   }
 
  private:
-  BitVector IndexSearch(FilterOp, SqlValue, uint32_t*, uint32_t) const;
-  RowMap::Range BinarySearchIntrinsic(FilterOp op,
-                                      SqlValue val,
+  BitVector IndexSearch(FilterOp, SetId, uint32_t*, uint32_t) const;
+  RowMap::Range BinarySearchIntrinsic(FilterOp,
+                                      SetId,
                                       RowMap::Range search_range) const;
 
   // TODO(b/307482437): After the migration vectors should be owned by storage,
diff --git a/src/trace_processor/db/storage/set_id_storage_unittest.cc b/src/trace_processor/db/storage/set_id_storage_unittest.cc
index f487dfa..5023345 100644
--- a/src/trace_processor/db/storage/set_id_storage_unittest.cc
+++ b/src/trace_processor/db/storage/set_id_storage_unittest.cc
@@ -19,11 +19,96 @@
 
 namespace perfetto {
 namespace trace_processor {
+
+inline bool operator==(const RowMap::Range& a, const RowMap::Range& b) {
+  return std::tie(a.start, a.end) == std::tie(b.start, b.end);
+}
+
 namespace storage {
 namespace {
 
 using Range = RowMap::Range;
 
+TEST(SetIdStorageUnittest, InvalidSearchConstraints) {
+  std::vector<uint32_t> storage_data{0, 0, 0, 3, 3, 3, 6, 6, 6, 9, 9, 9};
+  SetIdStorage storage(&storage_data);
+
+  Range test_range(3, 9);
+  Range full_range(0, 9);
+  Range empty_range;
+
+  // NULL checks
+  SqlValue val;
+  val.type = SqlValue::kNull;
+  Range search_result =
+      storage.Search(FilterOp::kIsNull, val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+  search_result =
+      storage.Search(FilterOp::kIsNotNull, val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, full_range);
+
+  // FilterOp checks
+  search_result =
+      storage.Search(FilterOp::kGlob, SqlValue::Long(15), test_range)
+          .TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+  search_result =
+      storage.Search(FilterOp::kRegex, SqlValue::Long(15), test_range)
+          .TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+
+  // Type checks
+  search_result =
+      storage.Search(FilterOp::kGe, SqlValue::String("cheese"), test_range)
+          .TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+
+  // Value bounds
+  SqlValue max_val = SqlValue::Long(
+      static_cast<int64_t>(std::numeric_limits<uint32_t>::max()) + 10);
+  search_result =
+      storage.Search(FilterOp::kGe, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+  search_result =
+      storage.Search(FilterOp::kGt, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+  search_result =
+      storage.Search(FilterOp::kEq, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+
+  search_result =
+      storage.Search(FilterOp::kLe, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, full_range);
+  search_result =
+      storage.Search(FilterOp::kLt, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, full_range);
+  search_result =
+      storage.Search(FilterOp::kNe, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, full_range);
+
+  SqlValue min_val = SqlValue::Long(
+      static_cast<int64_t>(std::numeric_limits<uint32_t>::min()) - 1);
+  search_result =
+      storage.Search(FilterOp::kGe, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, full_range);
+  search_result =
+      storage.Search(FilterOp::kGt, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, full_range);
+  search_result =
+      storage.Search(FilterOp::kNe, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, full_range);
+
+  search_result =
+      storage.Search(FilterOp::kLe, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+  search_result =
+      storage.Search(FilterOp::kLt, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+  search_result =
+      storage.Search(FilterOp::kEq, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+}
+
 TEST(SetIdStorageUnittest, SearchEqSimple) {
   std::vector<uint32_t> storage_data{0, 0, 0, 3, 3, 3, 6, 6, 6, 9, 9, 9};
 
diff --git a/src/trace_processor/db/storage/storage.h b/src/trace_processor/db/storage/storage.h
index 32edc4d..2a9c9fb 100644
--- a/src/trace_processor/db/storage/storage.h
+++ b/src/trace_processor/db/storage/storage.h
@@ -16,13 +16,11 @@
 #ifndef SRC_TRACE_PROCESSOR_DB_STORAGE_STORAGE_H_
 #define SRC_TRACE_PROCESSOR_DB_STORAGE_STORAGE_H_
 
-#include "perfetto/trace_processor/basic_types.h"
 #include "src/trace_processor/containers/bit_vector.h"
 #include "src/trace_processor/containers/row_map.h"
 #include "src/trace_processor/db/storage/types.h"
 
 namespace perfetto {
-
 namespace protos::pbzero {
 class SerializedColumn_Storage;
 }
@@ -33,23 +31,62 @@
 // Backing storage for columnar tables.
 class Storage {
  public:
+  using StorageProto = protos::pbzero::SerializedColumn_Storage;
+
+  enum class SearchValidationResult { kOk = 0, kAllData = 1, kNoData = 2 };
+
   virtual ~Storage();
 
+  // Verifies whether any further filtering is needed and if not, whether the
+  // search would return all values or none of them. This allows for skipping
+  // the |Search| and |IndexSearch| in special cases.
+  //
+  // Notes for callers:
+  // * This function is being called by Search and IndexSearch and there is no
+  //   need to call it before searches.
+  // * The SqlValue and FilterOp have to be valid in Sqlite: it will crash if
+  //   either: value is NULL and operation is different than "IS NULL" and "IS
+  //   NOT NULL" or the operation is "IS NULL" and "IS NOT NULL" and value is
+  //   different than NULL.
+  virtual SearchValidationResult ValidateSearchConstraints(SqlValue,
+                                                           FilterOp) const = 0;
+
   // Searches for elements which match |op| and |value| between |range.start|
   // and |range.end|.
-  virtual RangeOrBitVector Search(FilterOp op,
-                                  SqlValue value,
-                                  RowMap::Range range) const = 0;
+  //
+  // Returns either a range or BitVector which indicate the positions in |range|
+  // which match the constraint. If a BitVector is returned, it will be
+  // *precisely* as large as |range.end|.
+  //
+  // Notes for implementors:
+  //  * Implementations should ensure that the return value *only* includes
+  //    positions in |range| as callers will expect this to be true and can
+  //    optimize based on this.
+  //  * Implementations should ensure that, if they return a BitVector, it is
+  //    precisely of size |range.end|.
+  virtual RangeOrBitVector Search(FilterOp, SqlValue, RowMap::Range) const = 0;
 
   // Searches for elements which match |op| and |value| at the positions given
-  // by |indices| array.
-  // If the order defined by |indices| makes storage sorted, |sorted| flag
-  // should be set to true.
-  virtual RangeOrBitVector IndexSearch(FilterOp op,
-                                       SqlValue value,
+  // by |indices| array. The |sorted| flag allows the caller to specify if the
+  // order defined by |indices| makes storage sorted; implementations can use
+  // this to optimize how they search the storage.
+  //
+  // Returns either a range of BitVector which indicate the positions in
+  // |indices| which match the constraint. If a BitVector is returned, it will
+  // be *precisely* as large as |indices_count|.
+  //
+  // Notes for callers:
+  //  * Callers should note that the return value of this function corresponds
+  //    to positions in |indices| *not* positions in the storage.
+  //
+  // Notes for implementors:
+  //  * Implementations should ensure that, if they return a BitVector, it is
+  //    precisely of size |indices_count|.
+  virtual RangeOrBitVector IndexSearch(FilterOp,
+                                       SqlValue,
                                        uint32_t* indices,
                                        uint32_t indices_count,
-                                       bool sorted = false) const = 0;
+                                       bool sorted) const = 0;
 
   // Sorts |rows| in ascending order with the comparator:
   // data[rows[a]] < data[rows[b]].
@@ -60,7 +97,7 @@
   virtual void StableSort(uint32_t* rows, uint32_t rows_size) const = 0;
 
   // Serializes storage data to proto format.
-  virtual void Serialize(protos::pbzero::SerializedColumn_Storage*) const = 0;
+  virtual void Serialize(StorageProto*) const = 0;
 
   // Number of elements in stored data.
   virtual uint32_t size() const = 0;
diff --git a/src/trace_processor/db/storage/string_storage.cc b/src/trace_processor/db/storage/string_storage.cc
index 2179e86..d9c9b98 100644
--- a/src/trace_processor/db/storage/string_storage.cc
+++ b/src/trace_processor/db/storage/string_storage.cc
@@ -19,7 +19,6 @@
 #include "perfetto/ext/base/scoped_file.h"
 #include "perfetto/ext/base/status_or.h"
 #include "perfetto/ext/base/string_utils.h"
-#include "perfetto/trace_processor/basic_types.h"
 #include "protos/perfetto/trace_processor/serialization.pbzero.h"
 
 #include "perfetto/base/logging.h"
@@ -27,6 +26,7 @@
 #include "src/trace_processor/containers/null_term_string_view.h"
 #include "src/trace_processor/containers/row_map.h"
 #include "src/trace_processor/containers/string_pool.h"
+#include "src/trace_processor/db/storage/storage.h"
 #include "src/trace_processor/db/storage/types.h"
 
 #include "src/trace_processor/db/storage/utils.h"
@@ -163,63 +163,105 @@
 
 }  // namespace
 
+StringStorage::SearchValidationResult StringStorage::ValidateSearchConstraints(
+    SqlValue val,
+    FilterOp op) const {
+  // Type checks.
+  switch (val.type) {
+    case SqlValue::kNull:
+    case SqlValue::kString:
+      break;
+    case SqlValue::kLong:
+    case SqlValue::kDouble:
+      // Any string is always more than any numeric.
+      if (op == FilterOp::kGt || op == FilterOp::kGe) {
+        return Storage::SearchValidationResult::kAllData;
+      }
+      return Storage::SearchValidationResult::kNoData;
+    case SqlValue::kBytes:
+      return Storage::SearchValidationResult::kNoData;
+  }
+
+  return SearchValidationResult::kOk;
+}
+
 RangeOrBitVector StringStorage::Search(FilterOp op,
-                                       SqlValue value,
-                                       RowMap::Range range) const {
-  PERFETTO_TP_TRACE(metatrace::Category::DB, "StringStorage::LinearSearch",
-                    [&range, op](metatrace::Record* r) {
-                      r->AddArg("Start", std::to_string(range.start));
-                      r->AddArg("End", std::to_string(range.end));
+                                       SqlValue sql_val,
+                                       Range search_range) const {
+  PERFETTO_TP_TRACE(metatrace::Category::DB, "StringStorage::Search",
+                    [&search_range, op](metatrace::Record* r) {
+                      r->AddArg("Start", std::to_string(search_range.start));
+                      r->AddArg("End", std::to_string(search_range.end));
                       r->AddArg("Op",
                                 std::to_string(static_cast<uint32_t>(op)));
                     });
 
+  // After this switch we assume the search is valid.
+  switch (ValidateSearchConstraints(sql_val, op)) {
+    case SearchValidationResult::kOk:
+      break;
+    case SearchValidationResult::kAllData:
+      return RangeOrBitVector(Range(0, search_range.end));
+    case SearchValidationResult::kNoData:
+      return RangeOrBitVector(Range());
+  }
+
   if (is_sorted_) {
     if (op != FilterOp::kNe) {
-      return RangeOrBitVector(BinarySearchIntrinsic(op, value, range));
+      return RangeOrBitVector(BinarySearchIntrinsic(op, sql_val, search_range));
     }
     // Not equal is a special operation on binary search, as it doesn't define
     // a range, and rather just `not` range returned with `equal` operation.
-    RowMap::Range r = BinarySearchIntrinsic(FilterOp::kEq, value, range);
+    Range r = BinarySearchIntrinsic(FilterOp::kEq, sql_val, search_range);
     BitVector bv(r.start, true);
     bv.Resize(r.end);
-    bv.Resize(range.end, true);
+    bv.Resize(search_range.end, true);
     return RangeOrBitVector(std::move(bv));
   }
-  return RangeOrBitVector(LinearSearchInternal(op, value, range));
+  return RangeOrBitVector(LinearSearch(op, sql_val, search_range));
 }
 
 RangeOrBitVector StringStorage::IndexSearch(FilterOp op,
-                                            SqlValue value,
+                                            SqlValue sql_val,
                                             uint32_t* indices,
-                                            uint32_t indices_count,
-                                            bool sorted) const {
+                                            uint32_t indices_size,
+                                            bool indices_sorted) const {
   PERFETTO_TP_TRACE(metatrace::Category::DB, "StringStorage::IndexSearch",
-                    [indices_count, op](metatrace::Record* r) {
-                      r->AddArg("Count", std::to_string(indices_count));
+                    [indices_size, op](metatrace::Record* r) {
+                      r->AddArg("Count", std::to_string(indices_size));
                       r->AddArg("Op",
                                 std::to_string(static_cast<uint32_t>(op)));
                     });
 
-  if (sorted) {
+  // After this switch we assume the search is valid.
+  switch (ValidateSearchConstraints(sql_val, op)) {
+    case SearchValidationResult::kOk:
+      break;
+    case SearchValidationResult::kAllData:
+      return RangeOrBitVector(Range(0, indices_size));
+    case SearchValidationResult::kNoData:
+      return RangeOrBitVector(Range());
+  }
+
+  if (indices_sorted) {
     return RangeOrBitVector(
-        BinarySearchExtrinsic(op, value, indices, indices_count));
+        BinarySearchExtrinsic(op, sql_val, indices, indices_size));
   }
   return RangeOrBitVector(
-      IndexSearchInternal(op, value, indices, indices_count, sorted));
+      IndexSearchInternal(op, sql_val, indices, indices_size));
 }
 
-BitVector StringStorage::LinearSearchInternal(FilterOp op,
-                                              SqlValue sql_val,
-                                              RowMap::Range range) const {
+BitVector StringStorage::LinearSearch(FilterOp op,
+                                      SqlValue sql_val,
+                                      RowMap::Range range) const {
   if (sql_val.is_null() &&
       (op != FilterOp::kIsNotNull && op != FilterOp::kIsNull)) {
-    return BitVector();
+    return BitVector(range.end, false);
   }
 
   if (sql_val.type != SqlValue::kString &&
       (op == FilterOp::kGlob || op == FilterOp::kRegex)) {
-    return BitVector();
+    return BitVector(range.end, false);
   }
 
   StringPool::Id val =
@@ -227,16 +269,6 @@
           ? StringPool::Id::Null()
           : string_pool_->InternString(base::StringView(sql_val.AsString()));
   const StringPool::Id* start = values_->data() + range.start;
-  PERFETTO_TP_TRACE(
-      metatrace::Category::DB, "StringStorage::Search",
-      [range, op, &sql_val](metatrace::Record* r) {
-        r->AddArg("Start", std::to_string(range.start));
-        r->AddArg("End", std::to_string(range.end));
-        r->AddArg("Op", std::to_string(static_cast<uint32_t>(op)));
-        r->AddArg("String", sql_val.type == SqlValue::Type::kString
-                                ? sql_val.AsString()
-                                : "NULL");
-      });
 
   BitVector::Builder builder(range.end, range.start);
   switch (op) {
@@ -318,11 +350,11 @@
   return std::move(builder).Build();
 }
 
-RangeOrBitVector StringStorage::IndexSearchInternal(FilterOp op,
-                                                    SqlValue sql_val,
-                                                    uint32_t* indices,
-                                                    uint32_t indices_size,
-                                                    bool) const {
+RangeOrBitVector StringStorage::IndexSearchInternal(
+    FilterOp op,
+    SqlValue sql_val,
+    uint32_t* indices,
+    uint32_t indices_size) const {
   if (sql_val.is_null() &&
       (op != FilterOp::kIsNotNull && op != FilterOp::kIsNull)) {
     return RangeOrBitVector(Range());
@@ -482,13 +514,12 @@
 
 void StringStorage::Serialize(
     protos::pbzero::SerializedColumn::Storage* msg) const {
-  auto* string_storage = msg->set_string_storage();
-  string_storage->set_is_sorted(is_sorted_);
+  auto* string_storage_msg = msg->set_string_storage();
+  string_storage_msg->set_is_sorted(is_sorted_);
 
-  auto* vec_msg = string_storage->set_values();
-  vec_msg->set_size(size());
-  vec_msg->set_data(reinterpret_cast<const uint8_t*>(values_->data()),
-                    sizeof(StringPool::Id) * size());
+  string_storage_msg->set_values(
+      reinterpret_cast<const uint8_t*>(values_->data()),
+      sizeof(StringPool::Id) * size());
 }
 
 }  // namespace storage
diff --git a/src/trace_processor/db/storage/string_storage.h b/src/trace_processor/db/storage/string_storage.h
index 48ea5e8..1e89f24 100644
--- a/src/trace_processor/db/storage/string_storage.h
+++ b/src/trace_processor/db/storage/string_storage.h
@@ -16,6 +16,7 @@
 #ifndef SRC_TRACE_PROCESSOR_DB_STORAGE_STRING_STORAGE_H_
 #define SRC_TRACE_PROCESSOR_DB_STORAGE_STRING_STORAGE_H_
 
+#include "perfetto/trace_processor/basic_types.h"
 #include "src/trace_processor/containers/row_map.h"
 #include "src/trace_processor/containers/string_pool.h"
 #include "src/trace_processor/db/storage/storage.h"
@@ -38,6 +39,9 @@
                 bool is_sorted = false)
       : values_(data), string_pool_(string_pool), is_sorted_(is_sorted) {}
 
+  SearchValidationResult ValidateSearchConstraints(SqlValue,
+                                                   FilterOp) const override;
+
   RangeOrBitVector Search(FilterOp op,
                           SqlValue value,
                           RowMap::Range range) const override;
@@ -51,20 +55,19 @@
 
   void Sort(uint32_t* rows, uint32_t rows_size) const override;
 
-  void Serialize(protos::pbzero::SerializedColumn_Storage*) const override;
+  void Serialize(StorageProto*) const override;
 
   uint32_t size() const override {
     return static_cast<uint32_t>(values_->size());
   }
 
  private:
-  BitVector LinearSearchInternal(FilterOp, SqlValue, RowMap::Range) const;
+  BitVector LinearSearch(FilterOp, SqlValue, RowMap::Range) const;
 
   RangeOrBitVector IndexSearchInternal(FilterOp op,
                                        SqlValue sql_val,
                                        uint32_t* indices,
-                                       uint32_t indices_size,
-                                       bool) const;
+                                       uint32_t indices_size) const;
 
   RowMap::Range BinarySearchExtrinsic(FilterOp,
                                       SqlValue,
diff --git a/src/trace_processor/db/storage/types.h b/src/trace_processor/db/storage/types.h
index ff3fc57..6884988 100644
--- a/src/trace_processor/db/storage/types.h
+++ b/src/trace_processor/db/storage/types.h
@@ -26,7 +26,8 @@
 // Used for result of filtering, which is sometimes (for more optimised
 // operations) a Range and BitVector otherwise. Stores a variant of Range and
 // BitVector.
-struct RangeOrBitVector {
+class RangeOrBitVector {
+ public:
   using Range = RowMap::Range;
   explicit RangeOrBitVector(Range range) : val(range) {}
   explicit RangeOrBitVector(BitVector bv) : val(std::move(bv)) {}
@@ -43,6 +44,7 @@
     return std::move(*std::get_if<Range>(&val));
   }
 
+ private:
   std::variant<RowMap::Range, BitVector> val = Range();
 };
 
diff --git a/src/trace_processor/importers/common/args_tracker.h b/src/trace_processor/importers/common/args_tracker.h
index 4751fef..2a3a8b5 100644
--- a/src/trace_processor/importers/common/args_tracker.h
+++ b/src/trace_processor/importers/common/args_tracker.h
@@ -139,6 +139,12 @@
         context_->storage->mutable_surfaceflinger_transactions_table(), id);
   }
 
+  BoundInserter AddArgsTo(tables::WindowManagerShellTransitionsTable::Id id) {
+    return AddArgsTo(
+        context_->storage->mutable_window_manager_shell_transitions_table(),
+        id);
+  }
+
   BoundInserter AddArgsTo(MetadataId id) {
     auto* table = context_->storage->mutable_metadata_table();
     uint32_t row = *table->id().IndexOf(id);
diff --git a/src/trace_processor/importers/ftrace/ftrace_descriptors.cc b/src/trace_processor/importers/ftrace/ftrace_descriptors.cc
index 557512c..225f951 100644
--- a/src/trace_processor/importers/ftrace/ftrace_descriptors.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_descriptors.cc
@@ -24,7 +24,7 @@
 namespace trace_processor {
 namespace {
 
-std::array<FtraceMessageDescriptor, 487> descriptors{{
+std::array<FtraceMessageDescriptor, 488> descriptors{{
     {nullptr, 0, {}},
     {nullptr, 0, {}},
     {nullptr, 0, {}},
@@ -5351,6 +5351,30 @@
             {"cmd", ProtoSchemaType::kUint32},
         },
     },
+    {
+        "sched_switch_with_ctrs",
+        17,
+        {
+            {},
+            {"old_pid", ProtoSchemaType::kInt32},
+            {"new_pid", ProtoSchemaType::kInt32},
+            {"cctr", ProtoSchemaType::kUint32},
+            {"ctr0", ProtoSchemaType::kUint32},
+            {"ctr1", ProtoSchemaType::kUint32},
+            {"ctr2", ProtoSchemaType::kUint32},
+            {"ctr3", ProtoSchemaType::kUint32},
+            {"lctr0", ProtoSchemaType::kUint32},
+            {"lctr1", ProtoSchemaType::kUint32},
+            {"ctr4", ProtoSchemaType::kUint32},
+            {"ctr5", ProtoSchemaType::kUint32},
+            {"prev_comm", ProtoSchemaType::kString},
+            {"prev_pid", ProtoSchemaType::kInt32},
+            {"cyc", ProtoSchemaType::kUint32},
+            {"inst", ProtoSchemaType::kUint32},
+            {"stallbm", ProtoSchemaType::kUint32},
+            {"l3dm", ProtoSchemaType::kUint32},
+        },
+    },
 }};
 
 }  // namespace
diff --git a/src/trace_processor/importers/ftrace/ftrace_tokenizer.cc b/src/trace_processor/importers/ftrace/ftrace_tokenizer.cc
index c699ab9..711415f 100644
--- a/src/trace_processor/importers/ftrace/ftrace_tokenizer.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_tokenizer.cc
@@ -53,6 +53,47 @@
   return context->clock_tracker->ToTraceTime(clock_id, ts);
 }
 
+// Fast path for parsing the event id of an ftrace event.
+// Speculate on the fact that, if the timestamp was found, the common pid
+// will appear immediately after and the event id immediately after that.
+uint64_t TryFastParseFtraceEventId(const uint8_t* start, const uint8_t* end) {
+  constexpr auto kPidFieldNumber = protos::pbzero::FtraceEvent::kPidFieldNumber;
+  constexpr auto kPidFieldTag = MakeTagVarInt(kPidFieldNumber);
+
+  // If the next byte is not the common pid's tag, just skip the field.
+  constexpr uint32_t kMaxPidLength = 5;
+  if (PERFETTO_UNLIKELY(static_cast<uint32_t>(end - start) <= kMaxPidLength ||
+                        start[0] != kPidFieldTag)) {
+    return 0;
+  }
+
+  // Skip the common pid.
+  uint64_t common_pid = 0;
+  const uint8_t* common_pid_end = ParseVarInt(start + 1, end, &common_pid);
+  if (PERFETTO_UNLIKELY(common_pid_end == start + 1)) {
+    return 0;
+  }
+
+  // Read the next varint: this should be the event id tag.
+  uint64_t event_tag = 0;
+  const uint8_t* event_id_end = ParseVarInt(common_pid_end, end, &event_tag);
+  if (event_id_end == common_pid_end) {
+    return 0;
+  }
+
+  constexpr uint8_t kFieldTypeNumBits = 3;
+  constexpr uint64_t kFieldTypeMask =
+      (1 << kFieldTypeNumBits) - 1;  // 0000 0111;
+
+  // The event wire type should be length delimited.
+  auto wire_type = static_cast<protozero::proto_utils::ProtoWireType>(
+      event_tag & kFieldTypeMask);
+  if (wire_type != protozero::proto_utils::ProtoWireType::kLengthDelimited) {
+    return 0;
+  }
+  return event_tag >> kFieldTypeNumBits;
+}
+
 }  // namespace
 
 PERFETTO_ALWAYS_INLINE
@@ -125,29 +166,53 @@
 
   const uint8_t* data = event.data();
   const size_t length = event.length();
-  ProtoDecoder decoder(data, length);
 
-  // Speculate on the fact that the timestamp is often the 1st field of the
-  // event.
+  // Speculate on the following sequence of varints
+  //  - timestamp tag
+  //  - timestamp (64 bit)
+  //  - common pid tag
+  //  - common pid (32 bit)
+  //  - event tag
   uint64_t raw_timestamp = 0;
   bool timestamp_found = false;
+  uint64_t event_id = 0;
   if (PERFETTO_LIKELY(length > 10 && data[0] == kTimestampFieldTag)) {
     // Fastpath.
-    const uint8_t* next = ParseVarInt(data + 1, data + 11, &raw_timestamp);
-    timestamp_found = next != data + 1;
-    decoder.Reset(next);
-  } else {
-    // Slowpath.
+    const uint8_t* ts_end = ParseVarInt(data + 1, data + 11, &raw_timestamp);
+    timestamp_found = ts_end != data + 1;
+    if (PERFETTO_LIKELY(timestamp_found)) {
+      event_id = TryFastParseFtraceEventId(ts_end, data + length);
+    }
+  }
+
+  // Slowpath for finding the timestamp.
+  if (PERFETTO_UNLIKELY(!timestamp_found)) {
+    ProtoDecoder decoder(data, length);
     if (auto ts_field = decoder.FindField(kTimestampFieldNumber)) {
       timestamp_found = true;
       raw_timestamp = ts_field.as_uint64();
     }
+    if (PERFETTO_UNLIKELY(!timestamp_found)) {
+      context_->storage->IncrementStats(stats::ftrace_bundle_tokenizer_errors);
+      return;
+    }
   }
 
-  if (PERFETTO_UNLIKELY(!timestamp_found)) {
-    PERFETTO_ELOG("Timestamp field not found in FtraceEvent");
-    context_->storage->IncrementStats(stats::ftrace_bundle_tokenizer_errors);
-    return;
+  // Slowpath for finding the event id.
+  if (PERFETTO_UNLIKELY(event_id == 0)) {
+    ProtoDecoder decoder(data, length);
+    for (auto f = decoder.ReadField(); f.valid(); f = decoder.ReadField()) {
+      // Find the first length-delimited tag as this corresponds to the ftrace
+      // event.
+      if (f.type() == protozero::proto_utils::ProtoWireType::kLengthDelimited) {
+        event_id = f.id();
+        break;
+      }
+    }
+    if (PERFETTO_UNLIKELY(!event_id)) {
+      context_->storage->IncrementStats(stats::ftrace_bundle_tokenizer_errors);
+      return;
+    }
   }
 
   // ClockTracker will increment some error stats if it failed to convert the
diff --git a/src/trace_processor/importers/json/json_trace_parser.cc b/src/trace_processor/importers/json/json_trace_parser.cc
index 4b4330b..70b8e81 100644
--- a/src/trace_processor/importers/json/json_trace_parser.cc
+++ b/src/trace_processor/importers/json/json_trace_parser.cc
@@ -109,8 +109,7 @@
   base::StringView name = value.isMember("name")
                               ? base::StringView(value["name"].asCString())
                               : base::StringView();
-  StringId name_id = name.empty() ? storage->InternString("[No name]")
-                                  : storage->InternString(name);
+  StringId name_id = name.empty() ? kNullStringId : storage->InternString(name);
 
   auto args_inserter = [this, &value](ArgsTracker::BoundInserter* inserter) {
     if (value.isMember("args")) {
@@ -128,7 +127,8 @@
     row.ts = timestamp;
     row.track_id = track_id;
     row.category = cat_id;
-    row.name = name_id;
+    row.name =
+        name_id == kNullStringId ? storage->InternString("[No name]") : name_id;
     row.thread_ts = json::CoerceToTs(value["tts"]);
     // tdur will only exist on 'X' events.
     row.thread_dur = json::CoerceToTs(value["tdur"]);
@@ -166,15 +166,31 @@
     case 'b':
     case 'e':
     case 'n': {
-      if (!opt_pid || id.empty()) {
+      Json::Value id2 = value.isMember("id2") ? value["id2"] : Json::Value();
+      std::string local = id2.isMember("local") ? id2["local"].asString() : "";
+      std::string global =
+          id2.isMember("global") ? id2["global"].asString() : "";
+      if (!opt_pid || (id.empty() && global.empty() && local.empty())) {
         context_->storage->IncrementStats(stats::json_parser_failure);
         break;
       }
       UniquePid upid = context_->process_tracker->GetOrCreateProcess(pid);
-      int64_t cookie = static_cast<int64_t>(base::Hasher::Combine(id.c_str()));
-      StringId scope = kNullStringId;
-      TrackId track_id = context_->track_tracker->InternLegacyChromeAsyncTrack(
-          name_id, upid, cookie, true /* source_id_is_process_scoped */, scope);
+      TrackId track_id;
+      if (!id.empty() || !global.empty()) {
+        const std::string& real_id = id.empty() ? global : id;
+        int64_t cookie = static_cast<int64_t>(
+            base::Hasher::Combine(cat_id.raw_id(), real_id));
+        track_id = context_->track_tracker->InternLegacyChromeAsyncTrack(
+            name_id, upid, cookie, false /* source_id_is_process_scoped */,
+            kNullStringId /* source_scope */);
+      } else {
+        PERFETTO_DCHECK(!local.empty());
+        int64_t cookie =
+            static_cast<int64_t>(base::Hasher::Combine(cat_id.raw_id(), local));
+        track_id = context_->track_tracker->InternLegacyChromeAsyncTrack(
+            name_id, upid, cookie, true /* source_id_is_process_scoped */,
+            kNullStringId /* source_scope */);
+      }
 
       if (phase == 'b') {
         slice_tracker->BeginTyped(storage->mutable_slice_table(),
diff --git a/src/trace_processor/importers/proto/atoms.descriptor b/src/trace_processor/importers/proto/atoms.descriptor
index 2d4c191..923f583 100644
--- a/src/trace_processor/importers/proto/atoms.descriptor
+++ b/src/trace_processor/importers/proto/atoms.descriptor
Binary files differ
diff --git a/src/trace_processor/importers/proto/frame_timeline_event_parser.cc b/src/trace_processor/importers/proto/frame_timeline_event_parser.cc
index 04e10bd..c631a3d 100644
--- a/src/trace_processor/importers/proto/frame_timeline_event_parser.cc
+++ b/src/trace_processor/importers/proto/frame_timeline_event_parser.cc
@@ -163,6 +163,10 @@
                "Expired Prediction") /* PREDICTION_EXPIRED */,
            context->storage->InternString(
                "Unknown Prediction") /* PREDICTION_UNKNOWN */}},
+      jank_severity_type_ids_{{context->storage->InternString("Unknown"),
+                               context->storage->InternString("None"),
+                               context->storage->InternString("Partial"),
+                               context->storage->InternString("Full")}},
       expected_timeline_track_name_(
           context->storage->InternString("Expected Timeline")),
       actual_timeline_track_name_(
@@ -175,6 +179,8 @@
       on_time_finish_id_(context->storage->InternString("On time finish")),
       gpu_composition_id_(context->storage->InternString("GPU composition")),
       jank_type_id_(context->storage->InternString("Jank type")),
+      jank_severity_type_id_(
+          context->storage->InternString("Jank severity type")),
       layer_name_id_(context->storage->InternString("Layer name")),
       prediction_type_id_(context->storage->InternString("Prediction type")),
       is_buffer_id_(context->storage->InternString("Is Buffer?")),
@@ -277,16 +283,36 @@
   actual_row.name = name_id;
   actual_row.display_frame_token = token;
   actual_row.upid = upid;
+  actual_row.on_time_finish = event.on_time_finish();
+  actual_row.gpu_composition = event.gpu_composition();
+
+  // parse present type
   StringId present_type = present_type_ids_[0];
   if (event.has_present_type() &&
       ValidatePresentType(context_, event.present_type())) {
     present_type = present_type_ids_[static_cast<size_t>(event.present_type())];
   }
   actual_row.present_type = present_type;
-  actual_row.on_time_finish = event.on_time_finish();
-  actual_row.gpu_composition = event.gpu_composition();
+
+  // parse jank type
   StringId jank_type = JankTypeBitmaskToStringId(context_, event.jank_type());
   actual_row.jank_type = jank_type;
+
+  // parse jank severity type
+  if (event.has_jank_severity_type()) {
+    actual_row.jank_severity_type = jank_severity_type_ids_[static_cast<size_t>(
+        event.jank_severity_type())];
+  } else {
+    // NOTE: Older traces don't have this field. If JANK_NONE use
+    // |severity_type| "None", and is not present, use "Unknown".
+    actual_row.jank_severity_type =
+        (event.jank_type() == FrameTimelineEvent::JANK_NONE)
+            ? jank_severity_type_ids_[1]  /* None */
+            : jank_severity_type_ids_[0]; /* Unknown */
+  }
+  StringId jank_severity_type = actual_row.jank_severity_type;
+
+  // parse prediction type
   StringId prediction_type = prediction_type_ids_[0];
   if (event.has_prediction_type() &&
       ValidatePredictionType(context_, event.prediction_type())) {
@@ -294,6 +320,7 @@
         prediction_type_ids_[static_cast<size_t>(event.prediction_type())];
   }
   actual_row.prediction_type = prediction_type;
+
   if (DisplayFrameJanky(event.jank_type())) {
     actual_row.jank_tag = jank_tag_self_id_;
   } else if (event.jank_type() == FrameTimelineEvent::JANK_SF_STUFFING) {
@@ -307,8 +334,8 @@
   std::optional<SliceId> opt_slice_id = context_->slice_tracker->BeginTyped(
       context_->storage->mutable_actual_frame_timeline_slice_table(),
       actual_row,
-      [this, token, jank_type, present_type, prediction_type,
-       &event](ArgsTracker::BoundInserter* inserter) {
+      [this, token, jank_type, jank_severity_type, present_type,
+       prediction_type, &event](ArgsTracker::BoundInserter* inserter) {
         inserter->AddArg(display_frame_token_id_, Variadic::Integer(token));
         inserter->AddArg(present_type_id_, Variadic::String(present_type));
         inserter->AddArg(on_time_finish_id_,
@@ -316,6 +343,8 @@
         inserter->AddArg(gpu_composition_id_,
                          Variadic::Integer(event.gpu_composition()));
         inserter->AddArg(jank_type_id_, Variadic::String(jank_type));
+        inserter->AddArg(jank_severity_type_id_,
+                         Variadic::String(jank_severity_type));
         inserter->AddArg(prediction_type_id_,
                          Variadic::String(prediction_type));
       });
@@ -471,6 +500,10 @@
   actual_row.display_frame_token = display_frame_token;
   actual_row.upid = upid;
   actual_row.layer_name = layer_name_id;
+  actual_row.on_time_finish = event.on_time_finish();
+  actual_row.gpu_composition = event.gpu_composition();
+
+  // parse present type
   StringId present_type = present_type_ids_[0];
   bool present_type_validated = false;
   if (event.has_present_type() &&
@@ -479,10 +512,26 @@
     present_type = present_type_ids_[static_cast<size_t>(event.present_type())];
   }
   actual_row.present_type = present_type;
-  actual_row.on_time_finish = event.on_time_finish();
-  actual_row.gpu_composition = event.gpu_composition();
+
+  // parse jank type
   StringId jank_type = JankTypeBitmaskToStringId(context_, event.jank_type());
   actual_row.jank_type = jank_type;
+
+  // parse jank severity type
+  if (event.has_jank_severity_type()) {
+    actual_row.jank_severity_type = jank_severity_type_ids_[static_cast<size_t>(
+        event.jank_severity_type())];
+  } else {
+    // NOTE: Older traces don't have this field. If JANK_NONE use
+    // |severity_type| "None", and is not present, use "Unknown".
+    actual_row.jank_severity_type =
+        (event.jank_type() == FrameTimelineEvent::JANK_NONE)
+            ? jank_severity_type_ids_[1]  /* None */
+            : jank_severity_type_ids_[0]; /* Unknown */
+  }
+  StringId jank_severity_type = actual_row.jank_severity_type;
+
+  // parse prediction type
   StringId prediction_type = prediction_type_ids_[0];
   if (event.has_prediction_type() &&
       ValidatePredictionType(context_, event.prediction_type())) {
@@ -490,6 +539,7 @@
         prediction_type_ids_[static_cast<size_t>(event.prediction_type())];
   }
   actual_row.prediction_type = prediction_type;
+
   if (SurfaceFrameJanky(event.jank_type())) {
     actual_row.jank_tag = jank_tag_self_id_;
   } else if (DisplayFrameJanky(event.jank_type())) {
@@ -513,8 +563,8 @@
   std::optional<SliceId> opt_slice_id = context_->slice_tracker->BeginTyped(
       context_->storage->mutable_actual_frame_timeline_slice_table(),
       actual_row,
-      [this, jank_type, present_type, token, layer_name_id, display_frame_token,
-       prediction_type, is_buffer,
+      [this, jank_type, jank_severity_type, present_type, token, layer_name_id,
+       display_frame_token, prediction_type, is_buffer,
        &event](ArgsTracker::BoundInserter* inserter) {
         inserter->AddArg(surface_frame_token_id_, Variadic::Integer(token));
         inserter->AddArg(display_frame_token_id_,
@@ -526,6 +576,8 @@
         inserter->AddArg(gpu_composition_id_,
                          Variadic::Integer(event.gpu_composition()));
         inserter->AddArg(jank_type_id_, Variadic::String(jank_type));
+        inserter->AddArg(jank_severity_type_id_,
+                         Variadic::String(jank_severity_type));
         inserter->AddArg(prediction_type_id_,
                          Variadic::String(prediction_type));
         inserter->AddArg(is_buffer_id_, Variadic::String(is_buffer));
diff --git a/src/trace_processor/importers/proto/frame_timeline_event_parser.h b/src/trace_processor/importers/proto/frame_timeline_event_parser.h
index 9c4e75e..596f415 100644
--- a/src/trace_processor/importers/proto/frame_timeline_event_parser.h
+++ b/src/trace_processor/importers/proto/frame_timeline_event_parser.h
@@ -66,6 +66,7 @@
   std::map<int64_t, TrackSetId> cookie_track_set_id_map_;
   std::array<StringId, 6> present_type_ids_;
   std::array<StringId, 4> prediction_type_ids_;
+  std::array<StringId, 4> jank_severity_type_ids_;
   StringId expected_timeline_track_name_;
   StringId actual_timeline_track_name_;
 
@@ -75,6 +76,7 @@
   StringId on_time_finish_id_;
   StringId gpu_composition_id_;
   StringId jank_type_id_;
+  StringId jank_severity_type_id_;
   StringId layer_name_id_;
   StringId prediction_type_id_;
   StringId is_buffer_id_;
diff --git a/src/trace_processor/importers/proto/proto_trace_reader.cc b/src/trace_processor/importers/proto/proto_trace_reader.cc
index 8add6f0..6d68464 100644
--- a/src/trace_processor/importers/proto/proto_trace_reader.cc
+++ b/src/trace_processor/importers/proto/proto_trace_reader.cc
@@ -403,7 +403,8 @@
     tables::ClockSnapshotTable::Row row;
     row.ts = *opt_trace_ts;
     row.clock_id = static_cast<int64_t>(clock_timestamp.clock.id);
-    row.clock_value = clock_timestamp.timestamp;
+    row.clock_value =
+        clock_timestamp.timestamp * clock_timestamp.clock.unit_multiplier_ns;
     row.clock_name = GetBuiltinClockNameOrNull(clock_timestamp.clock.id);
     row.snapshot_id = *snapshot_id;
 
diff --git a/src/trace_processor/importers/proto/statsd_module.cc b/src/trace_processor/importers/proto/statsd_module.cc
index e536e11..49a7300 100644
--- a/src/trace_processor/importers/proto/statsd_module.cc
+++ b/src/trace_processor/importers/proto/statsd_module.cc
@@ -141,6 +141,54 @@
   TraceStorage& storage_;
 };
 
+// If we don't know about the atom format put whatever details we
+// can. This has the following restrictions:
+// - We can't tell the difference between double, fixed64, sfixed64
+//   so those all show up as double
+// - We can't tell the difference between float, fixed32, sfixed32
+//   so those all show up as float
+// - We can't tell the difference between int32, int64 and sint32
+//   and sint64. We assume int32/int64.
+// - We only show the length of strings, nested messages, packed ints
+//   and any other length delimited fields.
+base::Status ParseGenericEvent(const protozero::ConstBytes& cb,
+                               util::ProtoToArgsParser::Delegate& delegate) {
+  protozero::ProtoDecoder decoder(cb);
+  for (auto f = decoder.ReadField(); f.valid(); f = decoder.ReadField()) {
+    switch (f.type()) {
+      case protozero::proto_utils::ProtoWireType::kLengthDelimited: {
+        base::StackString<64> name("field_%u", f.id());
+        std::string name_str = name.ToStdString();
+        util::ProtoToArgsParser::Key key{name_str, name_str};
+        delegate.AddBytes(key, f.as_bytes());
+        break;
+      }
+      case protozero::proto_utils::ProtoWireType::kVarInt: {
+        base::StackString<64> name("field_%u", f.id());
+        std::string name_str = name.ToStdString();
+        util::ProtoToArgsParser::Key key{name_str, name_str};
+        delegate.AddInteger(key, f.as_int64());
+        break;
+      }
+      case protozero::proto_utils::ProtoWireType::kFixed32: {
+        base::StackString<64> name("field_%u_assuming_float", f.id());
+        std::string name_str = name.ToStdString();
+        util::ProtoToArgsParser::Key key{name_str, name_str};
+        delegate.AddDouble(key, static_cast<double>(f.as_float()));
+        break;
+      }
+      case protozero::proto_utils::ProtoWireType::kFixed64: {
+        base::StackString<64> name("field_%u_assuming_double", f.id());
+        std::string name_str = name.ToStdString();
+        util::ProtoToArgsParser::Key key{name_str, name_str};
+        delegate.AddDouble(key, f.as_double());
+        break;
+      }
+    }
+  }
+  return base::OkStatus();
+}
+
 }  // namespace
 
 using perfetto::protos::pbzero::StatsdAtom;
@@ -247,10 +295,26 @@
   SliceId slice = opt_slice.value();
   auto inserter = context_->args_tracker->AddArgsTo(slice);
   InserterDelegate delegate(inserter, *context_->storage.get());
-  base::Status result = args_parser_.ParseMessage(
-      nested_bytes, kAtomProtoName, nullptr /* parse all fields */, delegate);
-  if (!result.ok()) {
-    PERFETTO_ELOG("%s", result.c_message());
+
+  const auto& fields = pool_.descriptor()->fields();
+  const auto& field_it = fields.find(nested_field_id);
+  base::Status status;
+
+  if (field_it == fields.end()) {
+    /// Field ids 100000 and over are OEM atoms - we can't have the
+    // descriptor for them so don't report errors. See:
+    // https://cs.android.com/android/platform/superproject/main/+/main:frameworks/proto_logging/stats/atoms.proto;l=1290;drc=a34b11bfebe897259a0340a59f1793ae2dffd762
+    if (nested_field_id < 100000) {
+      context_->storage->IncrementStats(stats::atom_unknown);
+    }
+
+    status = ParseGenericEvent(field.as_bytes(), delegate);
+  } else {
+    status = args_parser_.ParseMessage(
+        nested_bytes, kAtomProtoName, nullptr /* parse all fields */, delegate);
+  }
+
+  if (!status.ok()) {
     context_->storage->IncrementStats(stats::atom_unknown);
   }
 }
@@ -263,18 +327,18 @@
       return context_->storage->InternString("Could not load atom descriptor");
     }
 
+    StringId name_id;
     const auto& fields = pool_.descriptor()->fields();
     const auto& field_it = fields.find(atom_field_id);
     if (field_it == fields.end()) {
-      context_->storage->IncrementStats(stats::atom_unknown);
-      return context_->storage->InternString("Unknown atom");
+      base::StackString<255> name("atom_%u", atom_field_id);
+      name_id = context_->storage->InternString(name.string_view());
+    } else {
+      const FieldDescriptor& field = field_it->second;
+      name_id = context_->storage->InternString(base::StringView(field.name()));
     }
-
-    const FieldDescriptor& field = field_it->second;
-    StringId name =
-        context_->storage->InternString(base::StringView(field.name()));
-    atom_names_[atom_field_id] = name;
-    return name;
+    atom_names_[atom_field_id] = name_id;
+    return name_id;
   }
   return *cached_name;
 }
diff --git a/src/trace_processor/importers/proto/winscope/BUILD.gn b/src/trace_processor/importers/proto/winscope/BUILD.gn
index 2d48f0c..7b04e85 100644
--- a/src/trace_processor/importers/proto/winscope/BUILD.gn
+++ b/src/trace_processor/importers/proto/winscope/BUILD.gn
@@ -16,30 +16,35 @@
 
 source_set("full") {
   sources = [
+    "shell_transitions_parser.cc",
+    "shell_transitions_parser.h",
+    "shell_transitions_tracker.cc",
+    "shell_transitions_tracker.h",
     "surfaceflinger_layers_parser.cc",
     "surfaceflinger_layers_parser.h",
     "surfaceflinger_transactions_parser.cc",
     "surfaceflinger_transactions_parser.h",
-    "winscope_args_parser.h",
     "winscope_args_parser.cc",
+    "winscope_args_parser.h",
     "winscope_module.cc",
     "winscope_module.h",
   ]
   deps = [
     ":gen_cc_winscope_descriptor",
+    "../:proto_importer_module",
     "../../../../../gn:default_deps",
-    "../../../../../protos/perfetto/trace/android:zero",
     "../../../../../protos/perfetto/trace:zero",
+    "../../../../../protos/perfetto/trace/android:zero",
     "../../../storage",
     "../../../tables",
     "../../../types",
     "../../common",
     "../../common:parser_types",
-    "../:proto_importer_module",
   ]
 }
 
 perfetto_cc_proto_descriptor("gen_cc_winscope_descriptor") {
   descriptor_name = "winscope.descriptor"
-  descriptor_target = "../../../../../protos/perfetto/trace/android:winscope_descriptor"
+  descriptor_target =
+      "../../../../../protos/perfetto/trace/android:winscope_descriptor"
 }
diff --git a/src/trace_processor/importers/proto/winscope/shell_transitions_parser.cc b/src/trace_processor/importers/proto/winscope/shell_transitions_parser.cc
new file mode 100644
index 0000000..68d2a82
--- /dev/null
+++ b/src/trace_processor/importers/proto/winscope/shell_transitions_parser.cc
@@ -0,0 +1,80 @@
+/*
+ * 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/importers/proto/winscope/shell_transitions_parser.h"
+#include "src/trace_processor/importers/proto/winscope/shell_transitions_tracker.h"
+
+#include "protos/perfetto/trace/android/shell_transition.pbzero.h"
+#include "src/trace_processor/importers/common/args_tracker.h"
+#include "src/trace_processor/importers/proto/winscope/winscope.descriptor.h"
+#include "src/trace_processor/importers/proto/winscope/winscope_args_parser.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+ShellTransitionsParser::ShellTransitionsParser(TraceProcessorContext* context)
+    : context_(context), args_parser_{pool_} {
+  pool_.AddFromFileDescriptorSet(kWinscopeDescriptor.data(),
+                                 kWinscopeDescriptor.size());
+}
+
+void ShellTransitionsParser::ParseTransition(protozero::ConstBytes blob) {
+  protos::pbzero::ShellTransition::Decoder transition(blob);
+
+  auto row_id =
+      ShellTransitionsTracker::GetOrCreate(context_)->InternTransition(
+          transition.id());
+
+  auto* window_manager_shell_transitions_table =
+      context_->storage->mutable_window_manager_shell_transitions_table();
+  auto row = window_manager_shell_transitions_table->FindById(row_id).value();
+
+  if (transition.has_dispatch_time_ns()) {
+    row.set_ts(transition.dispatch_time_ns());
+  }
+
+  auto inserter = context_->args_tracker->AddArgsTo(row_id);
+  WinscopeArgsParser writer(inserter, *context_->storage.get());
+  base::Status status = args_parser_.ParseMessage(
+      blob, kShellTransitionsProtoName, nullptr /* parse all fields */, writer);
+
+  if (!status.ok()) {
+    context_->storage->IncrementStats(
+        stats::winscope_shell_transitions_parse_errors);
+  }
+}
+
+void ShellTransitionsParser::ParseHandlerMappings(protozero::ConstBytes blob) {
+  auto* shell_handlers_table =
+      context_->storage
+          ->mutable_window_manager_shell_transition_handlers_table();
+
+  protos::pbzero::ShellHandlerMappings::Decoder handler_mappings(blob);
+  for (auto it = handler_mappings.mapping(); it; ++it) {
+    protos::pbzero::ShellHandlerMapping::Decoder mapping(it.field().as_bytes());
+
+    tables::WindowManagerShellTransitionHandlersTable::Row row;
+    row.handler_id = mapping.id();
+    row.handler_name = context_->storage->InternString(
+        base::StringView(mapping.name().ToStdString()));
+    shell_handlers_table->Insert(row);
+  }
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/proto/winscope/shell_transitions_parser.h b/src/trace_processor/importers/proto/winscope/shell_transitions_parser.h
new file mode 100644
index 0000000..44b86d1
--- /dev/null
+++ b/src/trace_processor/importers/proto/winscope/shell_transitions_parser.h
@@ -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.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_WINSCOPE_SHELL_TRANSITIONS_PARSER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_WINSCOPE_SHELL_TRANSITIONS_PARSER_H_
+
+#include "src/trace_processor/util/descriptors.h"
+#include "src/trace_processor/util/proto_to_args_parser.h"
+
+namespace perfetto {
+
+namespace trace_processor {
+
+class TraceProcessorContext;
+
+class ShellTransitionsParser {
+ public:
+  explicit ShellTransitionsParser(TraceProcessorContext*);
+  void ParseTransition(protozero::ConstBytes);
+  void ParseHandlerMappings(protozero::ConstBytes);
+
+ private:
+  static constexpr auto* kShellTransitionsProtoName =
+      ".perfetto.protos.ShellTransition";
+
+  TraceProcessorContext* const context_;
+  DescriptorPool pool_;
+  util::ProtoToArgsParser args_parser_;
+};
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_WINSCOPE_SHELL_TRANSITIONS_PARSER_H_
diff --git a/src/trace_processor/importers/proto/winscope/shell_transitions_tracker.cc b/src/trace_processor/importers/proto/winscope/shell_transitions_tracker.cc
new file mode 100644
index 0000000..6025d91
--- /dev/null
+++ b/src/trace_processor/importers/proto/winscope/shell_transitions_tracker.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 "shell_transitions_tracker.h"
+#include "perfetto/ext/base/crash_keys.h"
+#include "src/trace_processor/importers/common/process_tracker.h"
+#include "src/trace_processor/storage/metadata.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto {
+namespace trace_processor {
+ShellTransitionsTracker::ShellTransitionsTracker(TraceProcessorContext* context)
+    : context_(context) {}
+
+ShellTransitionsTracker::~ShellTransitionsTracker() = default;
+
+tables::WindowManagerShellTransitionsTable::Id
+ShellTransitionsTracker::InternTransition(int32_t transition_id) {
+  auto pos = transition_id_to_row_mapping_.find(transition_id);
+  if (pos != transition_id_to_row_mapping_.end()) {
+    return pos->second;
+  }
+
+  auto* window_manager_shell_transitions_table =
+      context_->storage->mutable_window_manager_shell_transitions_table();
+
+  tables::WindowManagerShellTransitionsTable::Row row;
+  row.transition_id = transition_id;
+  auto row_id = window_manager_shell_transitions_table->Insert(row).id;
+
+  transition_id_to_row_mapping_.insert({transition_id, row_id});
+
+  return row_id;
+}
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/proto/winscope/shell_transitions_tracker.h b/src/trace_processor/importers/proto/winscope/shell_transitions_tracker.h
new file mode 100644
index 0000000..07ef736
--- /dev/null
+++ b/src/trace_processor/importers/proto/winscope/shell_transitions_tracker.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_IMPORTERS_PROTO_WINSCOPE_SHELL_TRANSITIONS_TRACKER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_WINSCOPE_SHELL_TRANSITIONS_TRACKER_H_
+
+#include "perfetto/trace_processor/basic_types.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+// Tracks information in the transition table.
+class ShellTransitionsTracker : public Destructible {
+ public:
+  explicit ShellTransitionsTracker(TraceProcessorContext*);
+  virtual ~ShellTransitionsTracker() override;
+
+  static ShellTransitionsTracker* GetOrCreate(TraceProcessorContext* context) {
+    if (!context->shell_transitions_tracker) {
+      context->shell_transitions_tracker.reset(
+          new ShellTransitionsTracker(context));
+    }
+    return static_cast<ShellTransitionsTracker*>(
+        context->shell_transitions_tracker.get());
+  }
+
+  tables::WindowManagerShellTransitionsTable::Id InternTransition(
+      int32_t transition_id);
+
+ private:
+  TraceProcessorContext* context_;
+  std::unordered_map<int32_t, tables::WindowManagerShellTransitionsTable::Id>
+      transition_id_to_row_mapping_;
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_WINSCOPE_SHELL_TRANSITIONS_TRACKER_H_
diff --git a/src/trace_processor/importers/proto/winscope/winscope_module.cc b/src/trace_processor/importers/proto/winscope/winscope_module.cc
index de9e224..7f6c154 100644
--- a/src/trace_processor/importers/proto/winscope/winscope_module.cc
+++ b/src/trace_processor/importers/proto/winscope/winscope_module.cc
@@ -23,11 +23,14 @@
 
 WinscopeModule::WinscopeModule(TraceProcessorContext* context)
     : surfaceflinger_layers_parser_(context),
-      surfaceflinger_transactions_parser_(context) {
+      surfaceflinger_transactions_parser_(context),
+      shell_transitions_parser_(context) {
   RegisterForField(TracePacket::kSurfaceflingerLayersSnapshotFieldNumber,
                    context);
   RegisterForField(TracePacket::kSurfaceflingerTransactionsFieldNumber,
                    context);
+  RegisterForField(TracePacket::kShellTransitionFieldNumber, context);
+  RegisterForField(TracePacket::kShellHandlerMappingsFieldNumber, context);
 }
 
 void WinscopeModule::ParseTracePacketData(const TracePacket::Decoder& decoder,
@@ -43,6 +46,13 @@
       surfaceflinger_transactions_parser_.Parse(
           timestamp, decoder.surfaceflinger_transactions());
       return;
+    case TracePacket::kShellTransitionFieldNumber:
+      shell_transitions_parser_.ParseTransition(decoder.shell_transition());
+      return;
+    case TracePacket::kShellHandlerMappingsFieldNumber:
+      shell_transitions_parser_.ParseHandlerMappings(
+          decoder.shell_handler_mappings());
+      return;
   }
 }
 
diff --git a/src/trace_processor/importers/proto/winscope/winscope_module.h b/src/trace_processor/importers/proto/winscope/winscope_module.h
index d9428d0..fffe42c 100644
--- a/src/trace_processor/importers/proto/winscope/winscope_module.h
+++ b/src/trace_processor/importers/proto/winscope/winscope_module.h
@@ -21,6 +21,7 @@
 #include "perfetto/base/build_config.h"
 #include "src/trace_processor/importers/common/parser_types.h"
 #include "src/trace_processor/importers/proto/proto_importer_module.h"
+#include "src/trace_processor/importers/proto/winscope/shell_transitions_parser.h"
 #include "src/trace_processor/importers/proto/winscope/surfaceflinger_layers_parser.h"
 #include "src/trace_processor/importers/proto/winscope/surfaceflinger_transactions_parser.h"
 
@@ -41,6 +42,7 @@
  private:
   SurfaceFlingerLayersParser surfaceflinger_layers_parser_;
   SurfaceFlingerTransactionsParser surfaceflinger_transactions_parser_;
+  ShellTransitionsParser shell_transitions_parser_;
 };
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/perfetto_sql/engine/created_function.cc b/src/trace_processor/perfetto_sql/engine/created_function.cc
index 162dbc6..9c32a97 100644
--- a/src/trace_processor/perfetto_sql/engine/created_function.cc
+++ b/src/trace_processor/perfetto_sql/engine/created_function.cc
@@ -587,6 +587,10 @@
                                   SqlValue& out,
                                   Destructors&) {
   State* state = static_cast<State*>(ctx);
+
+  // Enter the function and ensure that we have a statement allocated.
+  RETURN_IF_ERROR(state->PushStackEntry());
+
   if (argc != state->prototype().arguments.size()) {
     return base::ErrStatus(
         "%s: invalid number of args; expected %zu, received %zu",
@@ -608,9 +612,6 @@
     }
   }
 
-  // Enter the function and ensure that we have a statement allocated.
-  RETURN_IF_ERROR(state->PushStackEntry());
-
   std::optional<Memoizer::MemoizedArgs> memoized_args =
       Memoizer::AsMemoizedArgs(argc, argv);
 
diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_cause_map.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_cause_map.sql
index f049075..0a21d27 100644
--- a/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_cause_map.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_cause_map.sql
@@ -2,6 +2,8 @@
 -- Use of this source code is governed by a BSD-style license that can be
 -- found in the LICENSE file.
 
+INCLUDE PERFETTO MODULE chrome.event_latency_description;
+
 -- Source of truth of the descriptions of EventLatency-based scroll jank causes.
 CREATE PERFETTO TABLE chrome_scroll_jank_cause_descriptions (
   -- The name of the EventLatency stage.
@@ -92,3 +94,27 @@
   cause_thread,
   cause_description
 FROM cause_descriptions;
+
+-- Combined description of scroll jank cause and associated event latency stage.
+CREATE PERFETTO VIEW chrome_scroll_jank_causes_with_event_latencies(
+  -- The name of the EventLatency stage.
+  name STRING,
+  -- Description of the EventLatency stage.
+  description STRING,
+  -- The process name that may cause scroll jank.
+  cause_process STRING,
+  -- The thread name that may cause scroll jank. The thread will be on the
+  -- cause_process.
+  cause_thread STRING,
+  -- Description of the cause of scroll jank on this process and thread.
+  cause_description STRING
+) AS
+SELECT
+  stages.name,
+  stages.description,
+  causes.cause_process,
+  causes.cause_thread,
+  causes.cause_description
+FROM chrome_event_latency_stage_descriptions stages
+LEFT JOIN chrome_scroll_jank_cause_descriptions causes
+    ON causes.event_latency_stage = stages.name;
diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_cause_utils.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_cause_utils.sql
index 2c6d0d0..35780fb 100644
--- a/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_cause_utils.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_cause_utils.sql
@@ -3,46 +3,181 @@
 -- found in the LICENSE file.
 
 
--- Retrieve the thread id of the thread on a particular process, if the name of
--- that process is known. Returns an error if there are multiple threads in
--- the given process with the same name.
-CREATE PERFETTO FUNCTION internal_find_utid_by_upid_and_name(
-  -- Unique process id
-  upid INT,
-  -- The name of the thread
-  thread_name STRING)
+-- Function to retrieve the upid for a surfaceflinger, as these are attributed
+-- to the GPU but are recorded on a different data source (and track group).
+CREATE PERFETTO FUNCTION internal_get_process_id_for_surfaceflinger()
+-- The process id for surfaceflinger.
+RETURNS INT AS
+SELECT
+ upid
+FROM process
+WHERE name GLOB '*surfaceflinger*'
+LIMIT 1;
+
+-- Map a generic process type to a specific name or substring of a name that
+-- can be found in the trace process table.
+CREATE PERFETTO TABLE internal_process_type_to_name (
+  -- The process type: one of 'Browser' or 'GPU'.
+  process_type STRING,
+  -- The process name for Chrome traces.
+  process_name STRING,
+  -- Substring identifying the process for system traces.
+  process_glob STRING
+) AS
+WITH process_names (
+  process_type,
+  process_name,
+  process_glob
+  )
+AS (
+VALUES
+  ('Browser', 'Browser', '*.chrome'),
+  ('GPU', 'Gpu', '*.chrome:privileged_process*'))
+SELECT
+  process_type,
+  process_name,
+  process_glob
+FROM process_names;
+
+CREATE PERFETTO FUNCTION internal_get_process_name(
+  -- The process type: one of 'Browser' or 'GPU'.
+  type STRING
+)
+-- The process name
+RETURNS STRING AS
+SELECT
+    process_name
+FROM internal_process_type_to_name
+WHERE process_type = $type
+LIMIT 1;
+
+CREATE PERFETTO FUNCTION internal_get_process_glob(
+  -- The process type: one of 'Browser' or 'GPU'.
+  type STRING
+)
+-- A substring of the process name that can be used in GLOB calculations.
+RETURNS STRING AS
+SELECT
+    process_glob
+FROM internal_process_type_to_name
+WHERE process_type = $type
+LIMIT 1;
+
+-- TODO(b/309937901): Add chrome instance id for multiple chromes/webviews in a
+-- trace, as this may result in  multiple browser and GPU processes.
+-- Function to retrieve the chrome process ID for a specific process type. Does
+-- not retrieve the Renderer process, as this is determined when the
+-- EventLatency is known. See function
+-- internal_get_renderer_upid_for_event_latency below.
+CREATE PERFETTO FUNCTION internal_get_process_id_by_type(
+  -- The process type: one of 'Browser' or 'GPU'.
+  type STRING
+)
 RETURNS TABLE (
-  -- Unique thread id.
-  utid INT
+    -- The process id for the process type.
+    upid INT
 ) AS
 SELECT
-  DISTINCT utid
-FROM thread
-WHERE upid = $upid
-  AND name = $thread_name;
+  upid
+FROM process
+WHERE name = internal_get_process_name($type)
+  OR name GLOB internal_get_process_glob($type);
 
--- Function to retrieve the track id of the thread on a particular process if
+-- Function to retrieve the chrome process ID that a given EventLatency slice
+-- occurred on. This is the Renderer process.
+CREATE PERFETTO FUNCTION internal_get_renderer_upid_for_event_latency(
+  -- The slice id for an EventLatency slice.
+  id INT
+)
+-- The process id for an EventLatency slice. This is the Renderer process.
+RETURNS INT AS
+SELECT
+  upid
+FROM process_slice
+WHERE id = $id;
+
+-- Helper function to retrieve all of the upids for a given process, thread,
+-- or EventLatency.
+CREATE PERFETTO FUNCTION internal_processes_by_type_for_event_latency(
+  -- The process type that the thread is on: one of 'Browser', 'Renderer' or
+  -- 'GPU'.
+  type STRING,
+  -- The name of the thread.
+  thread STRING,
+  -- The slice id of an EventLatency slice.
+  event_latency_id INT)
+RETURNS TABLE (
+    upid INT
+) AS
+WITH all_upids AS (
+  -- Renderer process upids
+  SELECT
+    $type AS process,
+    $thread AS thread,
+    $event_latency_id AS event_latency_id,
+    internal_get_renderer_upid_for_event_latency($event_latency_id) AS upid
+  WHERE $type = 'Renderer'
+  UNION ALL
+  -- surfaceflinger upids
+  SELECT
+    $type AS process,
+    $thread AS thread,
+    $event_latency_id AS event_latency_id,
+    internal_get_process_id_for_surfaceflinger() AS upid
+  WHERE $type = 'GPU' AND $thread = 'surfaceflinger'
+  UNION ALL
+  -- Generic Browser and GPU process upids
+  SELECT
+    $type AS process,
+    $thread AS thread,
+    $event_latency_id AS event_latency_id,
+    upid
+  FROM internal_get_process_id_by_type($type)
+  WHERE $type = 'Browser'
+    OR ($type = 'GPU' AND $thread != 'surfaceflinger')
+)
+SELECT
+  upid
+FROM all_upids;
+
+-- Function to retrieve the thread id of the thread on a particular process if
 -- there are any slices during a particular EventLatency slice duration; this
 -- upid/thread combination refers to a cause of Scroll Jank.
-CREATE PERFETTO FUNCTION chrome_select_scroll_jank_cause_track(
+CREATE PERFETTO FUNCTION chrome_select_scroll_jank_cause_thread(
   -- The slice id of an EventLatency slice.
   event_latency_id INT,
-  -- The process id that the thread is on.
-  upid INT,
+  -- The process type that the thread is on: one of 'Browser', 'Renderer' or
+  -- 'GPU'.
+  process_type STRING,
   -- The name of the thread.
   thread_name STRING)
 RETURNS TABLE (
-  -- The track id associated with |thread| on the process with |upid|.
-  track_id INT
+  -- The utid associated with |thread| on the process with |upid|.
+  utid INT
 ) AS
+WITH threads AS (
+  SELECT
+    utid
+  FROM thread
+  WHERE upid IN
+    (
+      SELECT DISTINCT
+        upid
+      FROM internal_processes_by_type_for_event_latency(
+        $process_type,
+        $thread_name,
+        $event_latency_id)
+    )
+    AND name = $thread_name
+)
 SELECT
- DISTINCT track_id
+ DISTINCT utid
 FROM thread_slice
 WHERE utid IN
   (
     SELECT
       utid
-    FROM internal_find_utid_by_upid_and_name($upid, $thread_name)
+    FROM threads
   )
   AND ts >= (SELECT ts FROM slice WHERE id = $event_latency_id LIMIT 1)
   AND ts <= (SELECT ts + dur FROM slice WHERE id = $event_latency_id LIMIT 1);
diff --git a/src/trace_processor/sqlite/sqlite_engine.cc b/src/trace_processor/sqlite/sqlite_engine.cc
index b1d1be4..9f845b1 100644
--- a/src/trace_processor/sqlite/sqlite_engine.cc
+++ b/src/trace_processor/sqlite/sqlite_engine.cc
@@ -18,6 +18,7 @@
 
 #include <memory>
 #include <optional>
+#include <unordered_set>
 #include <utility>
 #include <vector>
 
@@ -105,11 +106,17 @@
   // them) because |OnSqliteTableDestroyed| will be called as each DROP is
   // executed.
   std::vector<std::string> drop_stmts;
-  for (auto it = sqlite_tables_.GetIterator(); it; ++it) {
-    if (it.value() != SqliteTable::TableType::kExplicitCreate) {
+  std::unordered_set<std::string> dropped_tables;
+  for (auto it = all_created_sqlite_tables_.rbegin();
+       it != all_created_sqlite_tables_.rend(); it++) {
+    if (auto* type = sqlite_tables_.Find(*it);
+        !type || *type != SqliteTable::TableType::kExplicitCreate) {
       continue;
     }
-    base::StackString<1024> drop("DROP TABLE %s", it.key().c_str());
+    if (auto it_and_ins = dropped_tables.insert(*it); !it_and_ins.second) {
+      continue;
+    }
+    base::StackString<1024> drop("DROP TABLE %s", it->c_str());
     drop_stmts.emplace_back(drop.ToStdString());
   }
   for (const auto& drop : drop_stmts) {
@@ -248,6 +255,7 @@
                                         SqliteTable::TableType type) {
   auto it_and_inserted = sqlite_tables_.Insert(name, type);
   PERFETTO_CHECK(it_and_inserted.second);
+  all_created_sqlite_tables_.push_back(name);
 }
 
 void SqliteEngine::OnSqliteTableDestroyed(const std::string& name) {
diff --git a/src/trace_processor/sqlite/sqlite_engine.h b/src/trace_processor/sqlite/sqlite_engine.h
index 42ef5ae..9af23fe 100644
--- a/src/trace_processor/sqlite/sqlite_engine.h
+++ b/src/trace_processor/sqlite/sqlite_engine.h
@@ -24,6 +24,7 @@
 #include <optional>
 #include <string>
 #include <type_traits>
+#include <vector>
 
 #include "perfetto/base/status.h"
 #include "perfetto/ext/base/flat_hash_map.h"
@@ -137,6 +138,7 @@
   SqliteEngine& operator=(SqliteEngine&&) = delete;
 
   base::FlatHashMap<std::string, SqliteTable::TableType> sqlite_tables_;
+  std::vector<std::string> all_created_sqlite_tables_;
   base::FlatHashMap<std::string, std::unique_ptr<SqliteTable>> saved_tables_;
   base::FlatHashMap<std::pair<std::string, int>, void*, FnHasher> fn_ctx_;
 
diff --git a/src/trace_processor/sqlite/sqlite_table.h b/src/trace_processor/sqlite/sqlite_table.h
index 3f7c3bd..d6d40b3 100644
--- a/src/trace_processor/sqlite/sqlite_table.h
+++ b/src/trace_processor/sqlite/sqlite_table.h
@@ -324,7 +324,7 @@
         module.xDisconnect = &xDestroy;
         break;
       case TableType::kExplicitCreate:
-        // xConnect and xDestroy will be called when the table is CREATE-ed and
+        // xCreate and xDestroy will be called when the table is CREATE-ed and
         // DROP-ed respectively.
         module.xCreate = &xCreate;
         module.xDestroy = &xDestroy;
diff --git a/src/trace_processor/storage/stats.h b/src/trace_processor/storage/stats.h
index d583632..1e40489 100644
--- a/src/trace_processor/storage/stats.h
+++ b/src/trace_processor/storage/stats.h
@@ -262,6 +262,11 @@
                                           kSingle,  kInfo,     kAnalysis,      \
       "SurfaceFlinger transactions packet has unknown fields, which results "  \
       "in some arguments missing. You may need a newer version of trace "      \
+      "processor to parse them."),                                             \
+  F(winscope_shell_transitions_parse_errors,                                   \
+                                          kSingle,  kInfo,     kAnalysis,      \
+      "Shell transition packet has unknown fields, which results "  \
+      "in some arguments missing. You may need a newer version of trace "      \
       "processor to parse them.")
 // clang-format on
 
diff --git a/src/trace_processor/storage/trace_storage.h b/src/trace_processor/storage/trace_storage.h
index e75233e..79463f0 100644
--- a/src/trace_processor/storage/trace_storage.h
+++ b/src/trace_processor/storage/trace_storage.h
@@ -738,6 +738,24 @@
     return &surfaceflinger_transactions_table_;
   }
 
+  const tables::WindowManagerShellTransitionsTable&
+  window_manager_shell_transitions_table() const {
+    return window_manager_shell_transitions_table_;
+  }
+  tables::WindowManagerShellTransitionsTable*
+  mutable_window_manager_shell_transitions_table() {
+    return &window_manager_shell_transitions_table_;
+  }
+
+  const tables::WindowManagerShellTransitionHandlersTable&
+  window_manager_shell_transition_handlers_table() const {
+    return window_manager_shell_transition_handlers_table_;
+  }
+  tables::WindowManagerShellTransitionHandlersTable*
+  mutable_window_manager_shell_transition_handlers_table() {
+    return &window_manager_shell_transition_handlers_table_;
+  }
+
   const tables::ExperimentalProtoPathTable& experimental_proto_path_table()
       const {
     return experimental_proto_path_table_;
@@ -995,6 +1013,10 @@
   tables::SurfaceFlingerLayerTable surfaceflinger_layer_table_{&string_pool_};
   tables::SurfaceFlingerTransactionsTable surfaceflinger_transactions_table_{
       &string_pool_};
+  tables::WindowManagerShellTransitionsTable
+      window_manager_shell_transitions_table_{&string_pool_};
+  tables::WindowManagerShellTransitionHandlersTable
+      window_manager_shell_transition_handlers_table_{&string_pool_};
 
   tables::ExperimentalProtoPathTable experimental_proto_path_table_{
       &string_pool_};
diff --git a/src/trace_processor/tables/slice_tables.py b/src/trace_processor/tables/slice_tables.py
index 7548637..241fa10 100644
--- a/src/trace_processor/tables/slice_tables.py
+++ b/src/trace_processor/tables/slice_tables.py
@@ -190,13 +190,24 @@
     ],
     parent=SLICE_TABLE,
     tabledoc=TableDoc(
-        doc='''''',
+        doc='''
+        This table contains information on the expected timeline of either
+        a display frame or a surface frame.
+        ''',
         group='Slice',
         columns={
-            'display_frame_token': '''''',
-            'surface_frame_token': '''''',
-            'upid': '''''',
-            'layer_name': ''''''
+            'display_frame_token':
+                'Display frame token (vsync id).',
+            'surface_frame_token':
+                '''
+                Surface frame token (vsync id), null if this is a display frame.
+                ''',
+            'upid':
+                '''
+                Unique process id of the app that generates the surface frame.
+                ''',
+            'layer_name':
+                'Layer name if this is a surface frame.',
         }))
 
 ACTUAL_FRAME_TIMELINE_SLICE_TABLE = Table(
@@ -212,24 +223,48 @@
         C('on_time_finish', CppInt32()),
         C('gpu_composition', CppInt32()),
         C('jank_type', CppString()),
+        C('jank_severity_type', CppString()),
         C('prediction_type', CppString()),
         C('jank_tag', CppString()),
     ],
     parent=SLICE_TABLE,
     tabledoc=TableDoc(
-        doc='''''',
+        doc='''
+        This table contains information on the actual timeline and additional
+        analysis related to the performance of either a display frame or a
+        surface frame.
+        ''',
         group='Slice',
         columns={
-            'display_frame_token': '''''',
-            'surface_frame_token': '''''',
-            'upid': '''''',
-            'layer_name': '''''',
-            'present_type': '''''',
-            'on_time_finish': '''''',
-            'gpu_composition': '''''',
-            'jank_type': '''''',
-            'prediction_type': '''''',
-            'jank_tag': ''''''
+            'display_frame_token':
+                'Display frame token (vsync id).',
+            'surface_frame_token':
+                '''
+                Surface frame token (vsync id), null if this is a display frame.
+                ''',
+            'upid':
+                '''
+                Unique process id of the app that generates the surface frame.
+                ''',
+            'layer_name':
+                'Layer name if this is a surface frame.',
+            'present_type':
+                'Frame\'s present type (eg. on time / early / late).',
+            'on_time_finish':
+                'Whether the frame finishes on time.',
+            'gpu_composition':
+                'Whether the frame used gpu composition.',
+            'jank_type':
+                '''
+                Specify the jank types for this frame if there's jank, or
+                none if no jank occured.
+                ''',
+            'jank_severity_type':
+                'Severity of the jank: none if no jank.',
+            'prediction_type':
+                'Frame\'s prediction type (eg. valid / expired).',
+            'jank_tag':
+                'Jank tag based on jank type, used for slice visualization.'
         }))
 
 EXPERIMENTAL_FLAT_SLICE_TABLE = Table(
diff --git a/src/trace_processor/tables/table_destructors.cc b/src/trace_processor/tables/table_destructors.cc
index 16981fa..989d5e2 100644
--- a/src/trace_processor/tables/table_destructors.cc
+++ b/src/trace_processor/tables/table_destructors.cc
@@ -120,6 +120,10 @@
     default;
 SurfaceFlingerLayerTable::~SurfaceFlingerLayerTable() = default;
 SurfaceFlingerTransactionsTable::~SurfaceFlingerTransactionsTable() = default;
+WindowManagerShellTransitionsTable::~WindowManagerShellTransitionsTable() =
+    default;
+WindowManagerShellTransitionHandlersTable::
+    ~WindowManagerShellTransitionHandlersTable() = default;
 
 }  // namespace tables
 
diff --git a/src/trace_processor/tables/winscope_tables.py b/src/trace_processor/tables/winscope_tables.py
index 22f2080..12e7cef 100644
--- a/src/trace_processor/tables/winscope_tables.py
+++ b/src/trace_processor/tables/winscope_tables.py
@@ -18,6 +18,7 @@
 from python.generators.trace_processor_table.public import CppTableId
 from python.generators.trace_processor_table.public import TableDoc
 from python.generators.trace_processor_table.public import CppUint32
+from python.generators.trace_processor_table.public import CppString
 
 SURFACE_FLINGER_LAYERS_SNAPSHOT_TABLE = Table(
     python_module=__file__,
@@ -60,16 +61,53 @@
         C('arg_set_id', CppUint32()),
     ],
     tabledoc=TableDoc(
-        doc='SurfaceFlinger transactions. Each row contains a set of transactions that SurfaceFlinger committed together.',
+        doc='SurfaceFlinger transactions. Each row contains a set of ' +
+        'transactions that SurfaceFlinger committed together.',
         group='Winscope',
         columns={
             'ts': 'Timestamp of the transactions commit',
             'arg_set_id': 'Extra args parsed from the proto message',
         }))
 
+WINDOW_MANAGER_SHELL_TRANSITIONS_TABLE = Table(
+    python_module=__file__,
+    class_name='WindowManagerShellTransitionsTable',
+    sql_name='window_manager_shell_transitions',
+    columns=[
+        C('ts', CppInt64()),
+        C('transition_id', CppInt64()),
+        C('arg_set_id', CppUint32()),
+    ],
+    tabledoc=TableDoc(
+        doc='Window Manager Shell Transitions',
+        group='Winscope',
+        columns={
+            'ts': 'The timestamp the transition started playing',
+            'transition_id': 'The id of the transition',
+            'arg_set_id': 'Extra args parsed from the proto message',
+        }))
+
+WINDOW_MANAGER_SHELL_TRANSITION_HANDLERS_TABLE = Table(
+    python_module=__file__,
+    class_name='WindowManagerShellTransitionHandlersTable',
+    sql_name='window_manager_shell_transition_handlers',
+    columns=[
+        C('handler_id', CppInt64()),
+        C('handler_name', CppString()),
+    ],
+    tabledoc=TableDoc(
+        doc='Window Manager Shell Transition Handlers',
+        group='Winscope',
+        columns={
+            'handler_id': 'The id of the handler',
+            'handler_name': 'The name of the handler',
+        }))
+
 # Keep this list sorted.
 ALL_TABLES = [
     SURFACE_FLINGER_LAYERS_SNAPSHOT_TABLE,
     SURFACE_FLINGER_LAYER_TABLE,
     SURFACE_FLINGER_TRANSACTIONS_TABLE,
+    WINDOW_MANAGER_SHELL_TRANSITIONS_TABLE,
+    WINDOW_MANAGER_SHELL_TRANSITION_HANDLERS_TABLE,
 ]
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index 976357c..766df13 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -479,12 +479,13 @@
   PERFETTO_CHECK(tables_views_in_sqlite_count_ >=
                  engine_->RuntimeTablesAndViewsCount());
 
-  // Tables and views in sqlite with all objects from Perfeto Sql Engine without
-  // tables and views.
+  // Add the number of tables/views registered with SQLite to the number of
+  // "objects" (tables, views, functions etc) that we've registered and take
+  // away the number of "runtime" tables/views which are registered (which will
+  // be double counted).
   uint64_t registered_count_before = tables_views_in_sqlite_count_ +
                                      engine_->AllRegisteredObjectsCount() -
                                      engine_->RuntimeTablesAndViewsCount();
-
   InitPerfettoSqlEngine();
   return static_cast<size_t>(registered_count_before -
                              engine_->AllRegisteredObjectsCount());
@@ -854,6 +855,10 @@
   RegisterStaticTable(storage->surfaceflinger_layer_table());
   RegisterStaticTable(storage->surfaceflinger_transactions_table());
 
+  RegisterStaticTable(storage->window_manager_shell_transitions_table());
+  RegisterStaticTable(
+      storage->window_manager_shell_transition_handlers_table());
+
   RegisterStaticTable(storage->metadata_table());
   RegisterStaticTable(storage->cpu_table());
   RegisterStaticTable(storage->cpu_freq_table());
diff --git a/src/trace_processor/types/trace_processor_context.h b/src/trace_processor/types/trace_processor_context.h
index c2644ee..1eb16e4 100644
--- a/src/trace_processor/types/trace_processor_context.h
+++ b/src/trace_processor/types/trace_processor_context.h
@@ -123,6 +123,7 @@
   std::unique_ptr<Destructible> i2c_tracker;             // I2CTracker
   std::unique_ptr<Destructible> perf_data_tracker;       // PerfDataTracker
   std::unique_ptr<Destructible> content_analyzer;
+  std::unique_ptr<Destructible> shell_transitions_tracker;
 
   // These fields are trace readers which will be called by |forwarding_parser|
   // once the format of the trace is discovered. They are placed here as they
diff --git a/src/traceconv/pprof_builder.cc b/src/traceconv/pprof_builder.cc
index 1ca8654..d8de2dc 100644
--- a/src/traceconv/pprof_builder.cc
+++ b/src/traceconv/pprof_builder.cc
@@ -559,7 +559,7 @@
   bool WriteMappings(trace_processor::TraceProcessor* tp,
                      const std::set<int64_t>& seen_mappings) {
     Iterator mapping_it = tp->ExecuteQuery(
-        "SELECT id, exact_offset, start, end, name "
+        "SELECT id, exact_offset, start, end, name, build_id "
         "FROM stack_profile_mapping;");
     size_t mappings_no = 0;
     while (mapping_it.Next()) {
@@ -569,10 +569,10 @@
       ++mappings_no;
       auto interned_filename = ToStringTableId(
           interner_->InternString(mapping_it.Get(4).AsString()));
+      auto interned_build_id = ToStringTableId(
+          interner_->InternString(mapping_it.Get(5).AsString()));
       auto* gmapping = result_->add_mapping();
       gmapping->set_id(ToPprofId(id));
-      // Do not set the build_id here to avoid downstream services
-      // trying to symbolize (e.g. b/141735056)
       gmapping->set_file_offset(
           static_cast<uint64_t>(mapping_it.Get(1).AsLong()));
       gmapping->set_memory_start(
@@ -580,6 +580,7 @@
       gmapping->set_memory_limit(
           static_cast<uint64_t>(mapping_it.Get(3).AsLong()));
       gmapping->set_filename(interned_filename);
+      gmapping->set_build_id(interned_build_id);
     }
     if (!mapping_it.Status().ok()) {
       PERFETTO_DFATAL_OR_ELOG("Invalid mapping iterator: %s",
diff --git a/src/traced/probes/ftrace/event_info.cc b/src/traced/probes/ftrace/event_info.cc
index 7ad51cf..62bd4b9 100644
--- a/src/traced/probes/ftrace/event_info.cc
+++ b/src/traced/probes/ftrace/event_info.cc
@@ -7361,6 +7361,64 @@
        kUnsetFtraceId,
        430,
        kUnsetSize},
+      {"sched_switch_with_ctrs",
+       "perf_trace_counters",
+       {
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "old_pid", 1, ProtoSchemaType::kInt32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "new_pid", 2, ProtoSchemaType::kInt32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "cctr", 3, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "ctr0", 4, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "ctr1", 5, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "ctr2", 6, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "ctr3", 7, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "lctr0", 8, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "lctr1", 9, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "ctr4", 10, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "ctr5", 11, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "prev_comm", 12, ProtoSchemaType::kString,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "prev_pid", 13, ProtoSchemaType::kInt32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "cyc", 14, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "inst", 15, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "stallbm", 16, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "l3dm", 17, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+       },
+       kUnsetFtraceId,
+       487,
+       kUnsetSize},
       {"cpu_frequency",
        "power",
        {
diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/perf_trace_counters/sched_switch_with_ctrs/format b/src/traced/probes/ftrace/test/data/synthetic/events/perf_trace_counters/sched_switch_with_ctrs/format
new file mode 100644
index 0000000..e357f11
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/synthetic/events/perf_trace_counters/sched_switch_with_ctrs/format
@@ -0,0 +1,16 @@
+name: sched_switch_with_ctrs
+ID: 1237
+format:
+	field:unsigned short common_type;	offset:0;	size:2;	signed:0;
+	field:unsigned char common_flags;	offset:2;	size:1;	signed:0;
+	field:unsigned char common_preempt_count;	offset:3;	size:1;	signed:0;
+	field:int common_pid;	offset:4;	size:4;	signed:1;
+
+	field:char prev_comm[16];	offset:8;	size:16;	signed:0;
+	field:pid_t prev_pid;	offset:24;	size:4;	signed:1;
+	field:u32 cyc;	offset:28;	size:4;	signed:0;
+	field:u32 inst;	offset:32;	size:4;	signed:0;
+	field:u32 stallbm;	offset:36;	size:4;	signed:0;
+	field:u32 l3dm;	offset:40;	size:4;	signed:0;
+
+print fmt: "prev_comm=%s, prev_pid=%d, CYC=%u, INST=%u, STALLBM=%u, L3DM=%u", REC->prev_comm, REC->prev_pid, REC->cyc, REC->inst, REC->stallbm, REC->l3dm
diff --git a/src/traced_relay/relay_service.cc b/src/traced_relay/relay_service.cc
index 1ef5855..1a67439 100644
--- a/src/traced_relay/relay_service.cc
+++ b/src/traced_relay/relay_service.cc
@@ -15,22 +15,37 @@
  */
 
 #include "src/traced_relay/relay_service.h"
+
 #include <memory>
 
+#include "perfetto/base/build_config.h"
 #include "perfetto/base/logging.h"
 #include "perfetto/base/task_runner.h"
+#include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/hash.h"
+#include "perfetto/ext/base/string_utils.h"
 #include "perfetto/ext/base/unix_socket.h"
 #include "perfetto/ext/base/utils.h"
 #include "protos/perfetto/ipc/wire_protocol.gen.h"
 #include "src/ipc/buffered_frame_deserializer.h"
 #include "src/traced_relay/socket_relay_handler.h"
 
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
+    PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) ||   \
+    PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/utsname.h>
+#include <unistd.h>
+#endif
+
 using ::perfetto::protos::gen::IPCFrame;
 
 namespace perfetto {
 
 RelayService::RelayService(base::TaskRunner* task_runner)
-    : task_runner_(task_runner) {}
+    : task_runner_(task_runner), machine_id_hint_(GetMachineIdHint()) {}
 
 void RelayService::Start(const char* listening_socket_name,
                          const char* client_socket_name) {
@@ -77,6 +92,8 @@
   set_peer_identity->set_uid(
       static_cast<int32_t>(server_conn->peer_uid_posix()));
 
+  set_peer_identity->set_machine_id_hint(machine_id_hint_);
+
   // Buffer the SetPeerIdentity request.
   auto req = ipc::BufferedFrameDeserializer::Serialize(ipc_frame);
   SocketWithBuffer server, client;
@@ -123,4 +140,61 @@
   PERFETTO_DFATAL("Should be unreachable.");
 }
 
+std::string RelayService::GetMachineIdHint(
+    bool use_pseudo_boot_id_for_testing) {
+  // Gets kernel boot ID if possible.
+  std::string boot_id;
+  if (!use_pseudo_boot_id_for_testing &&
+      base::ReadFile("/proc/sys/kernel/random/boot_id", &boot_id)) {
+    return base::StripSuffix(boot_id, "\n");
+  }
+
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
+    PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) ||   \
+    PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
+  auto get_pseudo_boot_id = []() -> std::string {
+    base::Hasher hasher;
+    const char* dev_path = "/dev";
+    // Generate a pseudo-unique identifier for the current machine.
+    // Source 1: system boot timestamp from the creation time of /dev inode.
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
+    // Mac or iOS, just use stat(2).
+    struct stat stat_buf {};
+    int rc = PERFETTO_EINTR(stat(dev_path, &stat_buf));
+    if (rc == -1)
+      return std::string();
+    hasher.Update(reinterpret_cast<const char*>(&stat_buf.st_birthtimespec),
+                  sizeof(stat_buf.st_birthtimespec));
+#else
+    // Android or Linux, use statx(2)
+    struct statx stat_buf {};
+    auto rc = PERFETTO_EINTR(syscall(__NR_statx, /*dirfd=*/-1, dev_path,
+                                     /*flags=*/0, STATX_BTIME, &stat_buf));
+    if (rc == -1)
+      return std::string();
+    hasher.Update(reinterpret_cast<const char*>(&stat_buf.stx_btime),
+                  sizeof(stat_buf.stx_btime));
+#endif
+
+    // Source 2: uname(2).
+    utsname kernel_info{};
+    if (uname(&kernel_info) == -1)
+      return std::string();
+
+    // Create a non-cryptographic digest of bootup timestamp and everything in
+    // utsname.
+    hasher.Update(reinterpret_cast<const char*>(&kernel_info),
+                  sizeof(kernel_info));
+    return base::Uint64ToHexStringNoPrefix(hasher.digest());
+  };
+
+  auto pseudo_boot_id = get_pseudo_boot_id();
+  if (!pseudo_boot_id.empty())
+    return pseudo_boot_id;
+#endif
+
+  // If all above failed, return nothing.
+  return std::string();
+}
+
 }  // namespace perfetto
diff --git a/src/traced_relay/relay_service.h b/src/traced_relay/relay_service.h
index 8e5bf6d..1760df3 100644
--- a/src/traced_relay/relay_service.h
+++ b/src/traced_relay/relay_service.h
@@ -40,6 +40,13 @@
   // |server_socket_name| and |client_socket_name| ports.
   void Start(const char* server_socket_name, const char* client_socket_name);
 
+  static std::string GetMachineIdHint(
+      bool use_pseudo_boot_id_for_testing = false);
+
+  void SetMachineIdHintForTesting(std::string machine_id_hint) {
+    machine_id_hint_ = machine_id_hint;
+  }
+
  private:
   struct PendingConnection {
     // This keeps a connected UnixSocketRaw server socket in its first element.
@@ -60,6 +67,9 @@
 
   base::TaskRunner* const task_runner_ = nullptr;
 
+  // A hint to the host traced for inferring the identifier of this machine.
+  std::string machine_id_hint_;
+
   std::unique_ptr<base::UnixSocket> listening_socket_;
   std::string client_socket_name_;
 
diff --git a/src/traced_relay/relay_service_integrationtest.cc b/src/traced_relay/relay_service_integrationtest.cc
index 7e29041..bb83ecf 100644
--- a/src/traced_relay/relay_service_integrationtest.cc
+++ b/src/traced_relay/relay_service_integrationtest.cc
@@ -15,6 +15,9 @@
  */
 
 #include <memory>
+#include <string>
+#include <vector>
+#include "perfetto/ext/base/unix_socket.h"
 #include "src/traced_relay/relay_service.h"
 
 #include "src/base/test/test_task_runner.h"
@@ -28,6 +31,17 @@
 namespace perfetto {
 namespace {
 
+struct TestParams {
+  std::string id;
+  std::string tcp_sock_name;
+  std::string unix_sock_name;
+  std::string producer_name;
+
+  std::unique_ptr<RelayService> relay_service;
+  std::unique_ptr<base::UnixSocket> server_socket;
+  std::unique_ptr<FakeProducerThread> producer_thread;
+};
+
 TEST(TracedRelayIntegrationTest, BasicCase) {
   base::TestTaskRunner task_runner;
 
@@ -102,6 +116,125 @@
     ASSERT_EQ(packet.trusted_pid(), pid);
     ASSERT_EQ(packet.trusted_uid(), uid);
     ASSERT_EQ(packet.for_testing().seq_value(), rnd_engine());
+    // The tracing service should emit non-default machine ID in trace packets.
+    ASSERT_NE(packet.machine_id(), 0u);
+  }
+}
+
+TEST(TracedRelayIntegrationTest, MachineID_MultiRelayService) {
+  base::TestTaskRunner task_runner;
+  std::vector<TestParams> test_params(2);
+
+  base::UnixSocket::EventListener event_listener;
+  for (size_t i = 0; i < test_params.size(); i++) {
+    auto& param = test_params[i];
+    param.id = std::to_string(i + 1);
+    param.server_socket = base::UnixSocket::Listen(
+        "127.0.0.1:0", &event_listener, &task_runner, base::SockFamily::kInet,
+        base::SockType::kStream);
+    ASSERT_TRUE(param.server_socket->is_listening());
+    param.tcp_sock_name = param.server_socket->GetSockAddr();
+    param.relay_service = std::make_unique<RelayService>(&task_runner);
+    param.relay_service->SetMachineIdHintForTesting("test-machine-id-" +
+                                                    param.id);
+    param.unix_sock_name = std::string("@traced_relay_") + param.id;
+    param.producer_name = std::string("perfetto.FakeProducer.") + param.id;
+  }
+  for (auto& param : test_params) {
+    // Shut down listening sockets to free the port. It's unlikely that the port
+    // will be taken by another process so quickly before we reach the code
+    // below.
+    param.server_socket = nullptr;
+  }
+  auto relay_sock_name =
+      test_params[0].tcp_sock_name + "," + test_params[1].tcp_sock_name;
+
+  for (auto& param : test_params) {
+    param.relay_service->Start(param.unix_sock_name.c_str(),
+                               param.tcp_sock_name.c_str());
+  }
+
+  TestHelper helper(&task_runner, TestHelper::Mode::kStartDaemons,
+                    relay_sock_name.c_str());
+  ASSERT_EQ(helper.num_producers(), 2u);
+  helper.StartServiceIfRequired();
+
+  for (auto& param : test_params) {
+    auto checkpoint_name = "perfetto.FakeProducer.connected." + param.id;
+    auto producer_connected = task_runner.CreateCheckpoint(checkpoint_name);
+    auto noop = []() {};
+    auto connected = std::bind(
+        [&](std::function<void()> checkpoint) {
+          task_runner.PostTask(checkpoint);
+        },
+        producer_connected);
+    // We won't use the built-in fake producer and will start our own.
+    param.producer_thread = std::make_unique<FakeProducerThread>(
+        param.unix_sock_name, connected, noop, noop, param.producer_name);
+    param.producer_thread->Connect();
+    task_runner.RunUntilCheckpoint(checkpoint_name);
+  }
+
+  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 1st producer.
+  auto* ds_config = trace_config.add_data_sources()->mutable_config();
+  ds_config->set_name("perfetto.FakeProducer.1");
+  ds_config->set_target_buffer(0);
+  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);
+  // Enable the 2nd producer.
+  ds_config = trace_config.add_data_sources()->mutable_config();
+  ds_config->set_name("perfetto.FakeProducer.2");
+  ds_config->set_target_buffer(0);
+  ds_config->mutable_for_testing()->set_message_count(24);
+  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(), 36u);
+
+  // 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);
+  std::map<uint32_t, size_t> packets_counts;  // machine ID => count.
+
+  for (const auto& packet : packets) {
+    ASSERT_TRUE(packet.has_for_testing());
+    ASSERT_EQ(packet.trusted_pid(), pid);
+    ASSERT_EQ(packet.trusted_uid(), uid);
+    packets_counts[packet.machine_id()]++;
+  }
+
+  // Fake producer (1, 2) either gets machine ID (1, 2), or (2, 1), depending on
+  // which on is seen by the tracing service first.
+  ASSERT_EQ(packets_counts.size(), 2u);
+  auto count_1 = packets_counts.begin()->second;
+  auto count_2 = packets_counts.rbegin()->second;
+  ASSERT_TRUE(count_1 == 12u || count_1 == 24u);
+  ASSERT_EQ(count_1 + count_2, 36u);
+
+  for (auto& param : test_params) {
+    param.producer_thread = nullptr;
+    param.relay_service = nullptr;
   }
 }
 
diff --git a/src/traced_relay/relay_service_unittest.cc b/src/traced_relay/relay_service_unittest.cc
index 649f552..0508328 100644
--- a/src/traced_relay/relay_service_unittest.cc
+++ b/src/traced_relay/relay_service_unittest.cc
@@ -116,6 +116,7 @@
         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()));
+        EXPECT_TRUE(set_peer_identity.has_machine_id_hint());
 
         frame = deserializer.PopNextFrame();
         EXPECT_EQ(1u, frame->data_for_testing().size());
@@ -126,5 +127,34 @@
   task_runner.RunUntilCheckpoint("peer_identity_recv");
 }
 
+TEST(RelayServiceTest, MachineIDHint) {
+  base::TestTaskRunner task_runner;
+  auto relay_service = std::make_unique<RelayService>(&task_runner);
+
+  auto hint1 = relay_service->GetMachineIdHint();
+  auto hint2 =
+      relay_service->GetMachineIdHint(/*use_pseudo_boot_id_for_testing=*/true);
+  EXPECT_NE(hint1, hint2);
+
+  // Add a short sleep to verify that pseudo boot ID isn't affected.
+  std::this_thread::sleep_for(std::chrono::milliseconds(1));
+
+  relay_service = std::make_unique<RelayService>(&task_runner);
+  auto hint3 = relay_service->GetMachineIdHint();
+  auto hint4 =
+      relay_service->GetMachineIdHint(/*use_pseudo_boot_id_for_testing=*/true);
+  EXPECT_NE(hint3, hint4);
+
+  EXPECT_FALSE(hint1.empty());
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
+  // This test can run on Android kernel 3.x, but pseudo boot ID uses statx(2)
+  // that requires kernel 4.11.
+  EXPECT_FALSE(hint2.empty());
+#endif
+
+  EXPECT_EQ(hint1, hint3);
+  EXPECT_EQ(hint2, hint4);
+}
+
 }  // namespace
 }  // namespace perfetto
diff --git a/src/traced_relay/socket_relay_handler_unittest.cc b/src/traced_relay/socket_relay_handler_unittest.cc
index a96308a..380c594 100644
--- a/src/traced_relay/socket_relay_handler_unittest.cc
+++ b/src/traced_relay/socket_relay_handler_unittest.cc
@@ -110,8 +110,17 @@
 
 // Test the SocketRelayHander with randomized request and response data.
 TEST_P(SocketRelayHandlerTest, RandomizedRequestResponse) {
+#if defined(ADDRESS_SANITIZER) || defined(THREAD_SANITIZER) || \
+    defined(MEMORY_SANITIZER) || defined(LEAK_SANITIZER)
+  // Reduce the test strength for sanitizer builds.
+  constexpr size_t kMaxMsgSizeRng = 1 << 16;
+  constexpr size_t kMaxNumRequests = 10;
+#else
   // The max message size in the number of RNG calls.
-  constexpr size_t kMaxMsgSizeRng = 1 << 20;
+  constexpr size_t kMaxMsgSizeRng = 1 << 18;
+  // The max number of requests.
+  constexpr size_t kMaxNumRequests = 25;
+#endif
 
   // Create the threads for sending and receiving data through the
   // SocketRelayHandler.
@@ -122,7 +131,7 @@
       auto& rng = client.data_prng;
 
       // The max number of requests.
-      const size_t num_requests = rng() % 50;
+      const size_t num_requests = rng() % kMaxNumRequests;
 
       for (size_t j = 0; j < num_requests; j++) {
         auto& send_endpoint = client.endpoint_sockets.first;
@@ -196,9 +205,12 @@
   }
 }
 
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
+INSTANTIATE_TEST_SUITE_P(ByConnections, SocketRelayHandlerTest, Values(1, 5));
+#else
 INSTANTIATE_TEST_SUITE_P(ByConnections,
                          SocketRelayHandlerTest,
-                         Values(1, 5, 50));
-
+                         Values(1, 5, 25));
+#endif
 }  // namespace
 }  // namespace perfetto
diff --git a/src/tracing/core/packet_stream_validator.cc b/src/tracing/core/packet_stream_validator.cc
index d84eed3..9bed9b7 100644
--- a/src/tracing/core/packet_stream_validator.cc
+++ b/src/tracing/core/packet_stream_validator.cc
@@ -40,6 +40,7 @@
     protos::pbzero::TracePacket::kCompressedPacketsFieldNumber,
     protos::pbzero::TracePacket::kSynchronizationMarkerFieldNumber,
     protos::pbzero::TracePacket::kTrustedPidFieldNumber,
+    protos::pbzero::TracePacket::kMachineIdFieldNumber,
 };
 
 // This translation unit is quite subtle and perf-sensitive. Remember to check
diff --git a/src/tracing/core/packet_stream_validator_unittest.cc b/src/tracing/core/packet_stream_validator_unittest.cc
index 74de04c..62de98a 100644
--- a/src/tracing/core/packet_stream_validator_unittest.cc
+++ b/src/tracing/core/packet_stream_validator_unittest.cc
@@ -181,6 +181,26 @@
   EXPECT_FALSE(PacketStreamValidator::Validate(seq));
 }
 
+TEST(PacketStreamValidatorTest, SimplePacketWithMachineID) {
+  protos::gen::TracePacket proto;
+  proto.set_machine_id(123);
+  std::string ser_buf = proto.SerializeAsString();
+
+  Slices seq;
+  seq.emplace_back(&ser_buf[0], ser_buf.size());
+  EXPECT_FALSE(PacketStreamValidator::Validate(seq));
+}
+
+TEST(PacketStreamValidatorTest, SimplePacketWithZeroMachineID) {
+  protos::gen::TracePacket proto;
+  proto.set_machine_id(0);
+  std::string ser_buf = proto.SerializeAsString();
+
+  Slices seq;
+  seq.emplace_back(&ser_buf[0], ser_buf.size());
+  EXPECT_FALSE(PacketStreamValidator::Validate(seq));
+}
+
 TEST(PacketStreamValidatorTest, ComplexPacketWithPid) {
   protos::gen::TracePacket proto;
   proto.mutable_for_testing()->set_str("string field");
diff --git a/src/tracing/core/tracing_service_impl.cc b/src/tracing/core/tracing_service_impl.cc
index 1e4783a..1d469ac 100644
--- a/src/tracing/core/tracing_service_impl.cc
+++ b/src/tracing/core/tracing_service_impl.cc
@@ -2329,8 +2329,8 @@
       PERFETTO_DCHECK(sequence_properties.producer_id_trusted != 0);
       PERFETTO_DCHECK(sequence_properties.writer_id != 0);
       PERFETTO_DCHECK(sequence_properties.client_identity_trusted.has_uid());
-      // Not checking sequence_properties.producer_pid_trusted: it is
-      // base::kInvalidPid if the platform doesn't support it.
+      // Not checking sequence_properties.client_identity_trusted.has_pid():
+      // it is false if the platform doesn't support it.
 
       PERFETTO_DCHECK(packet.size() > 0);
       if (!PacketStreamValidator::Validate(packet.slices())) {
@@ -2356,6 +2356,7 @@
           static_cast<int32_t>(client_identity_trusted.uid()));
       trusted_packet->set_trusted_packet_sequence_id(
           tracing_session->GetPacketSequenceID(
+              client_identity_trusted.machine_id(),
               sequence_properties.producer_id_trusted,
               sequence_properties.writer_id));
       if (client_identity_trusted.has_pid()) {
@@ -2363,6 +2364,9 @@
         trusted_packet->set_trusted_pid(
             static_cast<int32_t>(client_identity_trusted.pid()));
       }
+      if (client_identity_trusted.has_non_default_machine_id()) {
+        trusted_packet->set_machine_id(client_identity_trusted.machine_id());
+      }
       if (previous_packet_dropped)
         trusted_packet->set_previous_packet_dropped(previous_packet_dropped);
       slice.size = trusted_packet.Finalize();
@@ -3430,7 +3434,8 @@
         }
       }
       auto* wri_stats = trace_stats.add_writer_stats();
-      wri_stats->set_sequence_id(tracing_session->GetPacketSequenceID(p, w));
+      wri_stats->set_sequence_id(
+          tracing_session->GetPacketSequenceID(kDefaultMachineID, p, w));
       for (size_t i = 0; i < hist.num_buckets(); ++i) {
         wri_stats->add_chunk_payload_histogram_counts(hist.GetBucketCount(i));
         wri_stats->add_chunk_payload_histogram_sum(hist.GetBucketSum(i));
diff --git a/src/tracing/core/tracing_service_impl.h b/src/tracing/core/tracing_service_impl.h
index 2639875..0f99493 100644
--- a/src/tracing/core/tracing_service_impl.h
+++ b/src/tracing/core/tracing_service_impl.h
@@ -465,9 +465,10 @@
       return timeout_ms ? timeout_ms : kDataSourceStopTimeoutMs;
     }
 
-    PacketSequenceID GetPacketSequenceID(ProducerID producer_id,
+    PacketSequenceID GetPacketSequenceID(MachineID machine_id,
+                                         ProducerID producer_id,
                                          WriterID writer_id) {
-      auto key = std::make_pair(producer_id, writer_id);
+      auto key = std::make_tuple(machine_id, producer_id, writer_id);
       auto it = packet_sequence_ids.find(key);
       if (it != packet_sequence_ids.end())
         return it->second;
@@ -551,7 +552,7 @@
     // many entries as |config.buffers_size()|.
     std::vector<BufferID> buffers_index;
 
-    std::map<std::pair<ProducerID, WriterID>, PacketSequenceID>
+    std::map<std::tuple<MachineID, ProducerID, WriterID>, PacketSequenceID>
         packet_sequence_ids;
     PacketSequenceID last_packet_sequence_id = kServicePacketSequenceID;
 
diff --git a/src/tracing/internal/system_tracing_backend.cc b/src/tracing/internal/system_tracing_backend.cc
index 9488495..1b83579 100644
--- a/src/tracing/internal/system_tracing_backend.cc
+++ b/src/tracing/internal/system_tracing_backend.cc
@@ -65,11 +65,12 @@
         shm.get(), shmem_page_size_hint, SharedMemoryABI::ShmemMode::kDefault);
   }
 
+  ipc::Client::ConnArgs conn_args(GetProducerSocket(), true);
   auto endpoint = ProducerIPCClient::Connect(
-      GetProducerSocket(), args.producer, args.producer_name, args.task_runner,
+      std::move(conn_args), args.producer, args.producer_name, args.task_runner,
       TracingService::ProducerSMBScrapingMode::kEnabled, shmem_size_hint,
       shmem_page_size_hint, std::move(shm), std::move(arbiter),
-      ProducerIPCClient::ConnectionFlags::kRetryIfUnreachable);
+      args.create_socket_async);
   PERFETTO_CHECK(endpoint);
   return endpoint;
 }
diff --git a/src/tracing/internal/tracing_muxer_impl.cc b/src/tracing/internal/tracing_muxer_impl.cc
index c0d88ac..80e451f 100644
--- a/src/tracing/internal/tracing_muxer_impl.cc
+++ b/src/tracing/internal/tracing_muxer_impl.cc
@@ -970,6 +970,7 @@
   rb.producer_conn_args.shmem_size_hint_bytes = args.shmem_size_hint_kb * 1024;
   rb.producer_conn_args.shmem_page_size_hint_bytes =
       args.shmem_page_size_hint_kb * 1024;
+  rb.producer_conn_args.create_socket_async = args.create_socket_async;
   rb.producer->Initialize(rb.backend->ConnectProducer(rb.producer_conn_args));
 }
 
diff --git a/src/tracing/ipc/BUILD.gn b/src/tracing/ipc/BUILD.gn
index dfd0eda..1423223 100644
--- a/src/tracing/ipc/BUILD.gn
+++ b/src/tracing/ipc/BUILD.gn
@@ -50,6 +50,7 @@
     "../../../gn:default_deps",
     "../../../include/perfetto/ext/ipc",
     "../../../include/perfetto/ext/tracing/core",
+    "../../../include/perfetto/tracing",
     "../../base",
   ]
 }
diff --git a/src/tracing/ipc/default_socket.cc b/src/tracing/ipc/default_socket.cc
index f053756..81ff6df 100644
--- a/src/tracing/ipc/default_socket.cc
+++ b/src/tracing/ipc/default_socket.cc
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "perfetto/ext/tracing/ipc/default_socket.h"
+#include "perfetto/tracing/default_socket.h"
 
 #include "perfetto/base/build_config.h"
 #include "perfetto/base/logging.h"
diff --git a/src/tracing/ipc/producer/producer_ipc_client_impl.cc b/src/tracing/ipc/producer/producer_ipc_client_impl.cc
index ea87953..3959b0a 100644
--- a/src/tracing/ipc/producer/producer_ipc_client_impl.cc
+++ b/src/tracing/ipc/producer/producer_ipc_client_impl.cc
@@ -66,7 +66,7 @@
                ProducerIPCClient::ConnectionFlags::kRetryIfUnreachable},
           producer, producer_name, task_runner, smb_scraping_mode,
           shared_memory_size_hint_bytes, shared_memory_page_size_hint_bytes,
-          std::move(shm), std::move(shm_arbiter)));
+          std::move(shm), std::move(shm_arbiter), CreateSocketAsync()));
 }
 
 // static. (Declared in include/tracing/ipc/producer_ipc_client.h).
@@ -79,13 +79,14 @@
     size_t shared_memory_size_hint_bytes,
     size_t shared_memory_page_size_hint_bytes,
     std::unique_ptr<SharedMemory> shm,
-    std::unique_ptr<SharedMemoryArbiter> shm_arbiter) {
+    std::unique_ptr<SharedMemoryArbiter> shm_arbiter,
+    CreateSocketAsync create_socket_async) {
   return std::unique_ptr<TracingService::ProducerEndpoint>(
-      new ProducerIPCClientImpl(std::move(conn_args), producer, producer_name,
-                                task_runner, smb_scraping_mode,
-                                shared_memory_size_hint_bytes,
-                                shared_memory_page_size_hint_bytes,
-                                std::move(shm), std::move(shm_arbiter)));
+      new ProducerIPCClientImpl(
+          std::move(conn_args), producer, producer_name, task_runner,
+          smb_scraping_mode, shared_memory_size_hint_bytes,
+          shared_memory_page_size_hint_bytes, std::move(shm),
+          std::move(shm_arbiter), create_socket_async));
 }
 
 ProducerIPCClientImpl::ProducerIPCClientImpl(
@@ -97,13 +98,12 @@
     size_t shared_memory_size_hint_bytes,
     size_t shared_memory_page_size_hint_bytes,
     std::unique_ptr<SharedMemory> shm,
-    std::unique_ptr<SharedMemoryArbiter> shm_arbiter)
+    std::unique_ptr<SharedMemoryArbiter> shm_arbiter,
+    CreateSocketAsync create_socket_async)
     : producer_(producer),
       task_runner_(task_runner),
       receive_shmem_fd_cb_fuchsia_(
           std::move(conn_args.receive_shmem_fd_cb_fuchsia)),
-      ipc_channel_(
-          ipc::Client::CreateInstance(std::move(conn_args), task_runner)),
       producer_port_(
           new protos::gen::ProducerPortProxy(this /* event_listener */)),
       shared_memory_(std::move(shm)),
@@ -124,7 +124,28 @@
     shared_buffer_page_size_kb_ = shared_memory_page_size_hint_bytes_ / 1024;
   }
 
-  ipc_channel_->BindService(producer_port_->GetWeakPtr());
+  if (create_socket_async) {
+    PERFETTO_DCHECK(conn_args.socket_name);
+    auto weak_this = weak_factory_.GetWeakPtr();
+    create_socket_async(
+        [weak_this, task_runner = task_runner_](base::SocketHandle fd) {
+          task_runner->PostTask([weak_this, fd] {
+            base::ScopedSocketHandle handle(fd);
+            if (!weak_this) {
+              return;
+            }
+            ipc::Client::ConnArgs args(std::move(handle));
+            weak_this->ipc_channel_ = ipc::Client::CreateInstance(
+                std::move(args), weak_this->task_runner_);
+            weak_this->ipc_channel_->BindService(
+                weak_this->producer_port_->GetWeakPtr());
+          });
+        });
+  } else {
+    ipc_channel_ =
+        ipc::Client::CreateInstance(std::move(conn_args), task_runner);
+    ipc_channel_->BindService(producer_port_->GetWeakPtr());
+  }
   PERFETTO_DCHECK_THREAD(thread_checker_);
 }
 
diff --git a/src/tracing/ipc/producer/producer_ipc_client_impl.h b/src/tracing/ipc/producer/producer_ipc_client_impl.h
index 664b698..f50dcd7 100644
--- a/src/tracing/ipc/producer/producer_ipc_client_impl.h
+++ b/src/tracing/ipc/producer/producer_ipc_client_impl.h
@@ -46,6 +46,8 @@
 // IPC channel to the remote Service. This class is the glue layer between the
 // generic Service interface exposed to the clients of the library and the
 // actual IPC transport.
+// If create_socket_async is set, it will be called to create and connect to a
+// socket to the service. If unset, the producer will create and connect itself.
 class ProducerIPCClientImpl : public TracingService::ProducerEndpoint,
                               public ipc::ServiceProxy::EventListener {
  public:
@@ -57,7 +59,8 @@
                         size_t shared_memory_size_hint_bytes,
                         size_t shared_memory_page_size_hint_bytes,
                         std::unique_ptr<SharedMemory> shm,
-                        std::unique_ptr<SharedMemoryArbiter> shm_arbiter);
+                        std::unique_ptr<SharedMemoryArbiter> shm_arbiter,
+                        CreateSocketAsync create_socket_async);
   ~ProducerIPCClientImpl() override;
 
   // TracingService::ProducerEndpoint implementation.
diff --git a/src/tracing/ipc/service/producer_ipc_service.cc b/src/tracing/ipc/service/producer_ipc_service.cc
index b24c632..1e9e316 100644
--- a/src/tracing/ipc/service/producer_ipc_service.cc
+++ b/src/tracing/ipc/service/producer_ipc_service.cc
@@ -117,7 +117,8 @@
   }
 
   // Copy the data fields to be emitted to trace packets into ClientIdentity.
-  ClientIdentity client_identity(client_info.uid(), client_info.pid());
+  ClientIdentity client_identity(client_info.uid(), client_info.pid(),
+                                 client_info.machine_id());
   // ConnectProducer will call OnConnect() on the next task.
   producer->service_endpoint = core_service_->ConnectProducer(
       producer.get(), client_identity, req.producer_name(),
diff --git a/src/tracing/test/api_integrationtest.cc b/src/tracing/test/api_integrationtest.cc
index 66b2cff..0040714 100644
--- a/src/tracing/test/api_integrationtest.cc
+++ b/src/tracing/test/api_integrationtest.cc
@@ -16,6 +16,9 @@
 
 #include <fcntl.h>
 
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/un.h>
 #include <chrono>
 #include <condition_variable>
 #include <fstream>
@@ -6303,6 +6306,7 @@
 
   // Create a new trace session.
   auto* tracing_session = NewTraceWithCategories({"test"});
+  ASSERT_TRUE(WaitForOneProducerConnected(tracing_session->get()));
   tracing_session->get()->StartBlocking();
 
   // Emit another event after starting.
@@ -6323,6 +6327,7 @@
 
   // Create a new trace session.
   auto* tracing_session = NewTraceWithCategories({"test"});
+  ASSERT_TRUE(WaitForOneProducerConnected(tracing_session->get()));
   tracing_session->get()->StartBlocking();
 
   // Emit another event after starting.
@@ -6340,6 +6345,7 @@
   TRACE_EVENT_BEGIN("test", "Event");
 
   auto* tracing_session = NewTraceWithCategories({"test"});
+  ASSERT_TRUE(WaitForOneProducerConnected(tracing_session->get()));
   tracing_session->get()->StartBlocking();
 
   TRACE_EVENT_END("test");
@@ -6358,7 +6364,9 @@
   ds_cfg->set_name("CustomDataSource");
   SetupStartupTracing(cfg);
   TRACE_EVENT_BEGIN("test", "TrackEvent.Startup");
+
   auto* tracing_session = NewTraceWithCategories({"test"}, {}, cfg);
+  ASSERT_TRUE(WaitForOneProducerConnected(tracing_session->get()));
   tracing_session->get()->StartBlocking();
 
   TRACE_EVENT_BEGIN("test", "TrackEvent.Main");
@@ -6397,7 +6405,9 @@
     auto packet = ctx.NewTracePacket();
     packet->set_for_testing()->set_str("CustomDataSource.Startup");
   });
+
   auto* tracing_session = NewTraceWithCategories({"test"}, {}, cfg);
+  ASSERT_TRUE(WaitForOneProducerConnected(tracing_session->get()));
   tracing_session->get()->StartBlocking();
 
   TRACE_EVENT_BEGIN("test", "TrackEvent.Main");
@@ -6470,6 +6480,7 @@
   TRACE_EVENT_BEGIN("test", "StartupEvent2");
 
   auto* tracing_session = NewTraceWithCategories({"test"});
+  ASSERT_TRUE(WaitForOneProducerConnected(tracing_session->get()));
   tracing_session->get()->StartBlocking();
 
   TRACE_EVENT_BEGIN("test", "MainEvent");
@@ -6555,7 +6566,9 @@
 // during startup tracing session.
 TEST_P(PerfettoStartupTracingApiTest, NoEventInStartupTracing) {
   SetupStartupTracing();
+
   auto* tracing_session = NewTraceWithCategories({"test"});
+  ASSERT_TRUE(WaitForOneProducerConnected(tracing_session->get()));
   tracing_session->get()->StartBlocking();
   // Emit an event now that the session was fully started. This should go
   // strait to the SMB.
@@ -6797,6 +6810,198 @@
   perfetto::Tracing::ResetForTesting();
 }
 
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+namespace {
+
+int ConnectUnixSocket() {
+  std::string socket_name = perfetto::GetProducerSocket();
+  int fd = socket(AF_UNIX, SOCK_STREAM, 0);
+  struct sockaddr_un saddr;
+  memset(&saddr, 0, sizeof(saddr));
+  memcpy(saddr.sun_path, socket_name.data(), socket_name.size());
+  saddr.sun_family = AF_UNIX;
+  auto size = static_cast<socklen_t>(__builtin_offsetof(sockaddr_un, sun_path) +
+                                     socket_name.size() + 1);
+  connect(fd, reinterpret_cast<const struct sockaddr*>(&saddr), size);
+  return fd;
+}
+
+}  // namespace
+
+TEST(PerfettoApiInitTest, AsyncSocket) {
+  auto system_service = perfetto::test::SystemService::Start();
+  // If the system backend isn't supported, skip
+  if (!system_service.valid()) {
+    GTEST_SKIP();
+  }
+
+  EXPECT_FALSE(perfetto::Tracing::IsInitialized());
+
+  perfetto::CreateSocketCallback socket_callback;
+  WaitableTestEvent create_socket_called;
+
+  TracingInitArgs args;
+  args.backends = perfetto::kSystemBackend;
+  args.tracing_policy = g_test_tracing_policy;
+  args.create_socket_async = [&socket_callback, &create_socket_called](
+                                 perfetto::CreateSocketCallback cb) {
+    socket_callback = cb;
+    create_socket_called.Notify();
+  };
+
+  perfetto::Tracing::Initialize(args);
+  create_socket_called.Wait();
+
+  int fd = ConnectUnixSocket();
+  socket_callback(fd);
+
+  perfetto::test::SyncProducers();
+  EXPECT_TRUE(perfetto::Tracing::NewTrace(perfetto::kSystemBackend)
+                  ->QueryServiceStateBlocking()
+                  .success);
+
+  perfetto::Tracing::ResetForTesting();
+}
+
+TEST(PerfettoApiInitTest, AsyncSocketDisconnect) {
+  auto system_service = perfetto::test::SystemService::Start();
+  // If the system backend isn't supported, skip
+  if (!system_service.valid()) {
+    GTEST_SKIP();
+  }
+
+  EXPECT_FALSE(perfetto::Tracing::IsInitialized());
+
+  perfetto::CreateSocketCallback socket_callback;
+  testing::MockFunction<perfetto::CreateSocketAsync> mock_create_socket;
+  WaitableTestEvent create_socket_called1, create_socket_called2;
+
+  TracingInitArgs args;
+  args.backends = perfetto::kSystemBackend;
+  args.tracing_policy = g_test_tracing_policy;
+  args.create_socket_async = mock_create_socket.AsStdFunction();
+
+  EXPECT_CALL(mock_create_socket, Call)
+      .WillOnce(Invoke([&socket_callback, &create_socket_called1](
+                           perfetto::CreateSocketCallback cb) {
+        socket_callback = cb;
+        create_socket_called1.Notify();
+      }))
+      .WillOnce(Invoke([&socket_callback, &create_socket_called2](
+                           perfetto::CreateSocketCallback cb) {
+        socket_callback = cb;
+        create_socket_called2.Notify();
+      }));
+
+  perfetto::Tracing::Initialize(args);
+  create_socket_called1.Wait();
+  int fd = ConnectUnixSocket();
+  socket_callback(fd);
+
+  perfetto::test::SyncProducers();
+  EXPECT_TRUE(perfetto::Tracing::NewTrace(perfetto::kSystemBackend)
+                  ->QueryServiceStateBlocking()
+                  .success);
+
+  // Restart the system service. This will cause the producer and consumer to
+  // disconnect and reconnect. The create_socket_async function should be called
+  // for the second time.
+  system_service.Restart();
+  create_socket_called2.Wait();
+  fd = ConnectUnixSocket();
+  socket_callback(fd);
+
+  perfetto::test::SyncProducers();
+  EXPECT_TRUE(perfetto::Tracing::NewTrace(perfetto::kSystemBackend)
+                  ->QueryServiceStateBlocking()
+                  .success);
+
+  perfetto::Tracing::ResetForTesting();
+}
+
+TEST(PerfettoApiInitTest, AsyncSocketStartupTracing) {
+  auto system_service = perfetto::test::SystemService::Start();
+  // If the system backend isn't supported, skip
+  if (!system_service.valid()) {
+    GTEST_SKIP();
+  }
+
+  EXPECT_FALSE(perfetto::Tracing::IsInitialized());
+
+  perfetto::CreateSocketCallback socket_callback;
+  WaitableTestEvent create_socket_called;
+
+  TracingInitArgs args;
+  args.backends = perfetto::kSystemBackend;
+  args.tracing_policy = g_test_tracing_policy;
+  args.create_socket_async = [&socket_callback, &create_socket_called](
+                                 perfetto::CreateSocketCallback cb) {
+    socket_callback = cb;
+    create_socket_called.Notify();
+  };
+
+  perfetto::Tracing::Initialize(args);
+  perfetto::TrackEvent::Register();
+
+  perfetto::TraceConfig cfg;
+  cfg.set_duration_ms(500);
+  cfg.add_buffers()->set_size_kb(1024);
+  auto* ds_cfg = cfg.add_data_sources()->mutable_config();
+  ds_cfg->set_name("track_event");
+
+  perfetto::protos::gen::TrackEventConfig te_cfg;
+  te_cfg.add_disabled_categories("*");
+  te_cfg.add_enabled_categories("test");
+  ds_cfg->set_track_event_config_raw(te_cfg.SerializeAsString());
+
+  perfetto::Tracing::SetupStartupTracingOpts opts;
+  opts.backend = perfetto::kSystemBackend;
+  auto startup_session =
+      perfetto::Tracing::SetupStartupTracingBlocking(cfg, std::move(opts));
+
+  // Emit a significant number of events to write >1 chunk of data.
+  constexpr size_t kNumEvents = 1000;
+  for (size_t i = 0; i < kNumEvents; i++) {
+    TRACE_EVENT_INSTANT("test", "StartupEvent");
+  }
+
+  // Now proceed with the connection to the service and wait until it completes.
+  int fd = ConnectUnixSocket();
+  socket_callback(fd);
+  perfetto::test::SyncProducers();
+
+  auto session = perfetto::Tracing::NewTrace(perfetto::kSystemBackend);
+  session->Setup(cfg);
+  session->StartBlocking();
+
+  // Write even more events, now with connection established.
+  for (size_t i = 0; i < kNumEvents; i++) {
+    TRACE_EVENT_INSTANT("test", "TraceEvent");
+  }
+
+  perfetto::TrackEvent::Flush();
+  session->StopBlocking();
+
+  auto raw_trace = session->ReadTraceBlocking();
+  perfetto::protos::gen::Trace parsed_trace;
+  EXPECT_TRUE(parsed_trace.ParseFromArray(raw_trace.data(), raw_trace.size()));
+
+  size_t n_track_events = 0;
+  for (const auto& packet : parsed_trace.packet()) {
+    if (packet.has_track_event()) {
+      ++n_track_events;
+    }
+  }
+
+  // Events from both startup and service-initiated sessions should be retained.
+  EXPECT_EQ(n_track_events, kNumEvents * 2);
+
+  startup_session.reset();
+  session.reset();
+  perfetto::Tracing::ResetForTesting();
+}
+#endif  // !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+
 struct BackendTypeAsString {
   std::string operator()(
       const ::testing::TestParamInfo<perfetto::BackendType>& info) const {
diff --git a/src/tracing/tracing.cc b/src/tracing/tracing.cc
index 5da8337..a0ab7b2 100644
--- a/src/tracing/tracing.cc
+++ b/src/tracing/tracing.cc
@@ -20,6 +20,7 @@
 #include <condition_variable>
 #include <mutex>
 
+#include "perfetto/base/time.h"
 #include "perfetto/ext/base/no_destructor.h"
 #include "perfetto/ext/base/waitable_event.h"
 #include "perfetto/tracing/internal/track_event_internal.h"
@@ -38,6 +39,7 @@
 
 // static
 void Tracing::InitializeInternal(const TracingInitArgs& args) {
+  base::InitializeTime();
   std::unique_lock<std::mutex> lock(InitializedMutex());
   // If it's the first time Initialize is called, set some global params.
   if (!g_was_initialized) {
diff --git a/test/data/heap_graph_object_for_benchmarks.pftrace.sha256 b/test/data/heap_graph_object_for_benchmarks.pftrace.sha256
new file mode 100644
index 0000000..dd7380f
--- /dev/null
+++ b/test/data/heap_graph_object_for_benchmarks.pftrace.sha256
@@ -0,0 +1 @@
+d0ee1affa7afdb325620a251f20ff16d5e19a5dae76508bb6db746d55dabd1cb
\ No newline at end of file
diff --git a/test/data/heap_pgraph_object_for_benchmarks_query.csv.sha256 b/test/data/heap_pgraph_object_for_benchmarks_query.csv.sha256
new file mode 100644
index 0000000..35a79ea
--- /dev/null
+++ b/test/data/heap_pgraph_object_for_benchmarks_query.csv.sha256
@@ -0,0 +1 @@
+62d757e7de34b466929f0444f2e097123b857c675fbe160f300f998a9309a3ae
\ No newline at end of file
diff --git a/test/data/statsd_atoms_oem.pb.sha256 b/test/data/statsd_atoms_oem.pb.sha256
new file mode 100644
index 0000000..9ab0459
--- /dev/null
+++ b/test/data/statsd_atoms_oem.pb.sha256
@@ -0,0 +1 @@
+5c38eaf8133ca06b1e9ab800c54430ac4807c98aa4684be3da48c56175bca679
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-android_trace_30s_expand_camera.png.sha256 b/test/data/ui-screenshots/ui-android_trace_30s_expand_camera.png.sha256
index e5e8fa3..a022411 100644
--- a/test/data/ui-screenshots/ui-android_trace_30s_expand_camera.png.sha256
+++ b/test/data/ui-screenshots/ui-android_trace_30s_expand_camera.png.sha256
@@ -1 +1 @@
-041ebd18b8b3f62f76b1613f548c8cf8ea5660f818b4ecfab787c4b08fd55b50
\ No newline at end of file
+35d52b18e2dd2805641fbfa121a66f62a0c602418a46926f5f61ddce91d450f8
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-android_trace_30s_load.png.sha256 b/test/data/ui-screenshots/ui-android_trace_30s_load.png.sha256
index 6cee1b2..9ed3863 100644
--- a/test/data/ui-screenshots/ui-android_trace_30s_load.png.sha256
+++ b/test/data/ui-screenshots/ui-android_trace_30s_load.png.sha256
@@ -1 +1 @@
-ec0a00856b147b2e13d0fe18666a307eb085ac437d67f78787131d4ea4190581
\ No newline at end of file
+2d29987562fb3b106e0f1794d2cb341c72a4a51fb5d94bf347048bbe198b7302
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-chrome_missing_track_names_load.png.sha256 b/test/data/ui-screenshots/ui-chrome_missing_track_names_load.png.sha256
index 6e1e7b7..d8f53e7 100644
--- a/test/data/ui-screenshots/ui-chrome_missing_track_names_load.png.sha256
+++ b/test/data/ui-screenshots/ui-chrome_missing_track_names_load.png.sha256
@@ -1 +1 @@
-875e17941e1dd5c362eef4bc679af41db5a9b2e37bbde5e5b2d1e90fd54b9e28
\ No newline at end of file
+a4b1867255b838c3ab73f6d00c84aa5bd7fc31bb53da5b83f52ceb84d99e96b2
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-chrome_rendering_desktop_expand_browser_proc.png.sha256 b/test/data/ui-screenshots/ui-chrome_rendering_desktop_expand_browser_proc.png.sha256
index 9203ad1..6cbb409 100644
--- a/test/data/ui-screenshots/ui-chrome_rendering_desktop_expand_browser_proc.png.sha256
+++ b/test/data/ui-screenshots/ui-chrome_rendering_desktop_expand_browser_proc.png.sha256
@@ -1 +1 @@
-1d185424a99b85372cecac21728fbf782fe33abf6a0664791ad08a3902bcdc3e
\ No newline at end of file
+ec9873453c3834735d55eb21138bf510069cf863342509a799076d821a8f01dd
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-chrome_rendering_desktop_load.png.sha256 b/test/data/ui-screenshots/ui-chrome_rendering_desktop_load.png.sha256
index 64e5c99..6667e5f 100644
--- a/test/data/ui-screenshots/ui-chrome_rendering_desktop_load.png.sha256
+++ b/test/data/ui-screenshots/ui-chrome_rendering_desktop_load.png.sha256
@@ -1 +1 @@
-1ae776d7033331f560685bfd61aa83a8a3f9639a400150bfe7be19642be3855a
\ No newline at end of file
+351edcf1ade9e6b1b7ced9d4f204e9a939370b7a56066f66793e56c9d93ec972
\ 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 7481c80..ea7e6de 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 @@
-1fa69f1c7b6098b1e7ab944f4900a3b52984522e18ce94a6c68f8c46c6c06154
\ No newline at end of file
+d9f429e9adad0ce321ab12dbdd062a9a8661c6dc3b6357646bcad5910a215407
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-modal_dialog_dismiss_1.png.sha256 b/test/data/ui-screenshots/ui-modal_dialog_dismiss_1.png.sha256
index 9b5f546..108417f 100644
--- a/test/data/ui-screenshots/ui-modal_dialog_dismiss_1.png.sha256
+++ b/test/data/ui-screenshots/ui-modal_dialog_dismiss_1.png.sha256
@@ -1 +1 @@
-846bdc1f6e20082da46d483049322300d91cb38ef72f922c934d46cc5df3507d
\ No newline at end of file
+9605f5ae3dbcb6555520046a73ea276c27601dd74235454af797544097015b19
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-modal_dialog_dismiss_2.png.sha256 b/test/data/ui-screenshots/ui-modal_dialog_dismiss_2.png.sha256
index 6710525..0f29bad 100644
--- a/test/data/ui-screenshots/ui-modal_dialog_dismiss_2.png.sha256
+++ b/test/data/ui-screenshots/ui-modal_dialog_dismiss_2.png.sha256
@@ -1 +1 @@
-5f6f302958f0b26df40b90740bd8786480d7e8aa89232baa4fe2d881c070ba3c
\ No newline at end of file
+c7bb5ef50e96b3bdd96d684cbc65025e60ff9dd3281b220e4b99e07c2a896afe
\ 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 e138b33..1672234 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 @@
-1dd5883861cff0c03811e5e97c479097771b8ab6ee97b5320593eb5f850c9a25
\ No newline at end of file
+df3d897c8b07ef8707df034e264fea6d32d391ea0d42abdf9349614fa5e57b44
\ 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 5aee346..89f4d6f 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 @@
-f93e24fcd03c2f69570c4a01bc8df04b05260b35983d479ab24f7b912ebccd4d
\ No newline at end of file
+8b1beeabcb5321575c4513eb8b572838e61513935098ddde3be368a9325b60f5
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-modal_dialog_switch_page_no_dialog.png.sha256 b/test/data/ui-screenshots/ui-modal_dialog_switch_page_no_dialog.png.sha256
index 38f1e1d..58840ee 100644
--- a/test/data/ui-screenshots/ui-modal_dialog_switch_page_no_dialog.png.sha256
+++ b/test/data/ui-screenshots/ui-modal_dialog_switch_page_no_dialog.png.sha256
@@ -1 +1 @@
-cf12d662d4137c081875afbb1c508827c9430e2837013bd917acd6eaadfac37c
\ No newline at end of file
+b9146d41d92c3e164adb9ada4ed0b384f225220ef458a70c152a14c6bd29aef3
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_navigate_navigate_back_and_forward.png.sha256 b/test/data/ui-screenshots/ui-routing_navigate_navigate_back_and_forward.png.sha256
index 895dc82..be09ae5 100644
--- a/test/data/ui-screenshots/ui-routing_navigate_navigate_back_and_forward.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_navigate_navigate_back_and_forward.png.sha256
@@ -1 +1 @@
-177e27d4d86bee1a17fce48d651b160f1541434aeb0f9e8fc1bac2b8fb07ac6d
\ No newline at end of file
+b9f04ac7c1d9bc25023a22bd2e6d79c48cf44ac62da30db9454f3e367dc9a824
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_navigate_open_trace_from_url.png.sha256 b/test/data/ui-screenshots/ui-routing_navigate_open_trace_from_url.png.sha256
index 5294534..92f9c01 100644
--- a/test/data/ui-screenshots/ui-routing_navigate_open_trace_from_url.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_navigate_open_trace_from_url.png.sha256
@@ -1 +1 @@
-c099f4ab43ee73de87c83ca2bb8cd2c087abdb12512ca3855e5cb6e5203e378b
\ No newline at end of file
+1bcbdbf6d1c2c1c2e77faa90be1c060b350f21472f67aed9477e6ba5e5219e81
\ 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 766073d..88676c9 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 @@
-133e44411f05ea57299a67258d307ec06d381c2852143bf012059365fd2a7716
\ No newline at end of file
+5192001c1342677f7aebade9574bd9de5622c004d88d1a2a4a9453e35a376b16
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_open_trace_and_go_back_to_landing_page.png.sha256 b/test/data/ui-screenshots/ui-routing_open_trace_and_go_back_to_landing_page.png.sha256
index b0f92e9..657b6fe 100644
--- a/test/data/ui-screenshots/ui-routing_open_trace_and_go_back_to_landing_page.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_open_trace_and_go_back_to_landing_page.png.sha256
@@ -1 +1 @@
-3c80ba72b9bd0454af4aac3352c4e8f855f48feeba53d2a5ac7566333b4cf763
\ No newline at end of file
+1f40c5a9cfbd5e7eb6f8536fb7e51a95b2863443e0774d394716c97de2fa4b05
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_access_subpage_then_go_back.png.sha256 b/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_access_subpage_then_go_back.png.sha256
index 33f95ef..ff33850 100644
--- a/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_access_subpage_then_go_back.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_access_subpage_then_go_back.png.sha256
@@ -1 +1 @@
-7506ad1268c6d92743d19f52d37a1e8b7cf00fc7907bf9e3e06966dbbb1b40c1
\ No newline at end of file
+7d8429af94229b4cb96f3e7691e987b4eb0ade9683acce9a2c14c41777415d73
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_first_trace_from_url.png.sha256 b/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_first_trace_from_url.png.sha256
index 5294534..92f9c01 100644
--- a/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_first_trace_from_url.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_first_trace_from_url.png.sha256
@@ -1 +1 @@
-c099f4ab43ee73de87c83ca2bb8cd2c087abdb12512ca3855e5cb6e5203e378b
\ No newline at end of file
+1bcbdbf6d1c2c1c2e77faa90be1c060b350f21472f67aed9477e6ba5e5219e81
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_second_trace_from_url.png.sha256 b/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_second_trace_from_url.png.sha256
index 33f95ef..ff33850 100644
--- a/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_second_trace_from_url.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_second_trace_from_url.png.sha256
@@ -1 +1 @@
-7506ad1268c6d92743d19f52d37a1e8b7cf00fc7907bf9e3e06966dbbb1b40c1
\ No newline at end of file
+7d8429af94229b4cb96f3e7691e987b4eb0ade9683acce9a2c14c41777415d73
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_start_from_no_trace_go_back_to_first_trace.png.sha256 b/test/data/ui-screenshots/ui-routing_start_from_no_trace_go_back_to_first_trace.png.sha256
index 33f95ef..ff33850 100644
--- a/test/data/ui-screenshots/ui-routing_start_from_no_trace_go_back_to_first_trace.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_start_from_no_trace_go_back_to_first_trace.png.sha256
@@ -1 +1 @@
-7506ad1268c6d92743d19f52d37a1e8b7cf00fc7907bf9e3e06966dbbb1b40c1
\ No newline at end of file
+7d8429af94229b4cb96f3e7691e987b4eb0ade9683acce9a2c14c41777415d73
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_start_from_no_trace_go_to_page_with_no_trace.png.sha256 b/test/data/ui-screenshots/ui-routing_start_from_no_trace_go_to_page_with_no_trace.png.sha256
index 22444d8..0aacaa4 100644
--- a/test/data/ui-screenshots/ui-routing_start_from_no_trace_go_to_page_with_no_trace.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_start_from_no_trace_go_to_page_with_no_trace.png.sha256
@@ -1 +1 @@
-52be49df180c482cad1a48979ce0bb2d20a7cdd27ad10cb972b5bad61b9865ca
\ No newline at end of file
+539226bd5412f6f573473f91ae2fc6edda026744c143bdc4b027a069094de96f
\ 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 5949d49..f8f7010 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 @@
-f650a9a968e978dfa37f900cc5509bdc9118e8f3c74197164982285acde6149f
\ No newline at end of file
+428b6a704a5c68df10bc6543112ed9e1c367abf07219c50ceaa8989421f3b373
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_second_trace.png.sha256 b/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_second_trace.png.sha256
index 5294534..92f9c01 100644
--- a/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_second_trace.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_second_trace.png.sha256
@@ -1 +1 @@
-c099f4ab43ee73de87c83ca2bb8cd2c087abdb12512ca3855e5cb6e5203e378b
\ No newline at end of file
+1bcbdbf6d1c2c1c2e77faa90be1c060b350f21472f67aed9477e6ba5e5219e81
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_trace_.png.sha256 b/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_trace_.png.sha256
index 33f95ef..ff33850 100644
--- a/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_trace_.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_trace_.png.sha256
@@ -1 +1 @@
-7506ad1268c6d92743d19f52d37a1e8b7cf00fc7907bf9e3e06966dbbb1b40c1
\ No newline at end of file
+7d8429af94229b4cb96f3e7691e987b4eb0ade9683acce9a2c14c41777415d73
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_start_from_no_trace_refresh.png.sha256 b/test/data/ui-screenshots/ui-routing_start_from_no_trace_refresh.png.sha256
index 33f95ef..ff33850 100644
--- a/test/data/ui-screenshots/ui-routing_start_from_no_trace_refresh.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_start_from_no_trace_refresh.png.sha256
@@ -1 +1 @@
-7506ad1268c6d92743d19f52d37a1e8b7cf00fc7907bf9e3e06966dbbb1b40c1
\ No newline at end of file
+7d8429af94229b4cb96f3e7691e987b4eb0ade9683acce9a2c14c41777415d73
\ No newline at end of file
diff --git a/test/synth_common.py b/test/synth_common.py
index a239fa9..d9970c9 100644
--- a/test/synth_common.py
+++ b/test/synth_common.py
@@ -763,11 +763,19 @@
       event.pid = pid
       event.layer_name = layer_name
 
-  def add_actual_surface_frame_start_event(self, ts, cookie, token,
-                                           display_frame_token, pid, layer_name,
-                                           present_type, on_time_finish,
-                                           gpu_composition, jank_type,
-                                           prediction_type):
+  def add_actual_surface_frame_start_event(self,
+                                           ts,
+                                           cookie,
+                                           token,
+                                           display_frame_token,
+                                           pid,
+                                           layer_name,
+                                           present_type,
+                                           on_time_finish,
+                                           gpu_composition,
+                                           jank_type,
+                                           prediction_type,
+                                           jank_severity_type=None):
     packet = self.add_packet()
     packet.timestamp = ts
     event = packet.frame_timeline_event.actual_surface_frame_start
@@ -781,6 +789,12 @@
       event.on_time_finish = on_time_finish
       event.gpu_composition = gpu_composition
       event.jank_type = jank_type
+      # jank severity type is not available on every trace.
+      # When not set, default to none if no jank; otherwise default to unknown
+      if jank_severity_type is None:
+        event.jank_severity_type = 1 if event.jank_type == 1 else 0
+      else:
+        event.jank_severity_type = jank_severity_type
       event.prediction_type = prediction_type
 
   def add_frame_end_event(self, ts, cookie):
diff --git a/test/trace_processor/diff_tests/include_index.py b/test/trace_processor/diff_tests/include_index.py
index 0aea755..e585f47 100644
--- a/test/trace_processor/diff_tests/include_index.py
+++ b/test/trace_processor/diff_tests/include_index.py
@@ -51,6 +51,7 @@
 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.tests_shell_transitions import ShellTransitions
 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
@@ -171,6 +172,8 @@
                             'SurfaceFlingerLayers').fetch(),
       *SurfaceFlingerTransactions(index_path, 'parser/android',
                                   'SurfaceFlingerTransactions').fetch(),
+      *ShellTransitions(index_path, 'parser/android',
+                        'ShellTransitions').fetch(),
       *TrackEvent(index_path, 'parser/track_event', 'TrackEvent').fetch(),
       *TranslatedArgs(index_path, 'parser/translated_args',
                       'TranslatedArgs').fetch(),
diff --git a/test/trace_processor/diff_tests/parser/android/shell_handlers.textproto b/test/trace_processor/diff_tests/parser/android/shell_handlers.textproto
new file mode 100644
index 0000000..9b0182a
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/android/shell_handlers.textproto
@@ -0,0 +1,21 @@
+packet {
+  trusted_uid: 10225
+  trusted_packet_sequence_id: 12
+  previous_packet_dropped: true
+  trusted_pid: 3981
+  first_packet_on_sequence: true
+  shell_handler_mappings {
+    mapping {
+        id: 1
+        name: "DefaultTransitionHandler"
+    }
+    mapping {
+        id: 2
+        name: "RecentsTransitionHandler"
+    }
+    mapping {
+        id: 3
+        name: "FreeformTaskTransitionHandler"
+    }
+  }
+}
diff --git a/test/trace_processor/diff_tests/parser/android/shell_transitions.textproto b/test/trace_processor/diff_tests/parser/android/shell_transitions.textproto
new file mode 100644
index 0000000..b92eb39
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/android/shell_transitions.textproto
@@ -0,0 +1,167 @@
+packet {
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 2
+  previous_packet_dropped: true
+  trusted_pid: 1305
+  first_packet_on_sequence: true
+  shell_transition {
+    id: 7
+    create_time_ns: 76799049027
+    send_time_ns: 76875395422
+    start_transaction_id: 5604932321952
+    finish_transaction_id: 5604932321954
+  }
+}
+packet {
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 2
+  trusted_pid: 1305
+  shell_transition {
+    id: 10
+    create_time_ns: 77854865352
+    send_time_ns: 77894307328
+    start_transaction_id: 5604932322158
+    finish_transaction_id: 5604932322159
+  }
+}
+packet {
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 2
+  trusted_pid: 1305
+  shell_transition {
+    id: 11
+    create_time_ns: 82498121051
+    send_time_ns: 82535513345
+    start_transaction_id: 5604932322346
+    finish_transaction_id: 5604932322347
+  }
+}
+packet {
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 3
+  previous_packet_dropped: true
+  trusted_pid: 1305
+  first_packet_on_sequence: true
+  shell_transition {
+    id: 8
+    create_time_ns: 76955664017
+    send_time_ns: 77277756832
+    start_transaction_id: 5604932322028
+    finish_transaction_id: 5604932322029
+  }
+}
+packet {
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 4
+  previous_packet_dropped: true
+  trusted_pid: 1305
+  first_packet_on_sequence: true
+  shell_transition {
+    id: 7
+    starting_window_remove_time_ns: 77706603918
+  }
+}
+packet {
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 5
+  previous_packet_dropped: true
+  trusted_pid: 1305
+  first_packet_on_sequence: true
+  shell_transition {
+    id: 9
+    create_time_ns: 77825423417
+    send_time_ns: 77843436723
+    start_transaction_id: 5604932322137
+    finish_transaction_id: 5604932322138
+  }
+}
+packet {
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 5
+  trusted_pid: 1305
+  shell_transition {
+    id: 9
+    finish_time_ns: 77873935462
+  }
+}
+packet {
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 5
+  trusted_pid: 1305
+  shell_transition {
+    id: 10
+    finish_time_ns: 78621610429
+  }
+}
+packet {
+  trusted_uid: 10241
+  trusted_packet_sequence_id: 6
+  previous_packet_dropped: true
+  trusted_pid: 2528
+  first_packet_on_sequence: true
+  shell_transition {
+    id: 7
+    dispatch_time_ns: 76879063147
+    handler: 2
+  }
+}
+packet {
+  trusted_uid: 10241
+  trusted_packet_sequence_id: 6
+  trusted_pid: 2528
+  shell_transition {
+    id: 8
+    merge_time_ns: 77278725500
+    merge_target: 7
+  }
+}
+packet {
+  trusted_uid: 10241
+  trusted_packet_sequence_id: 6
+  trusted_pid: 2528
+  shell_transition {
+    id: 8
+    dispatch_time_ns: 77320527177
+    handler: 3
+  }
+}
+packet {
+  trusted_uid: 10241
+  trusted_packet_sequence_id: 6
+  trusted_pid: 2528
+  shell_transition {
+    id: 9
+    dispatch_time_ns: 77876414832
+    handler: 3
+  }
+}
+packet {
+  trusted_uid: 10241
+  trusted_packet_sequence_id: 6
+  trusted_pid: 2528
+  shell_transition {
+    id: 10
+    dispatch_time_ns: 77899001013
+    handler: 4
+  }
+}
+packet {
+  trusted_uid: 10241
+  trusted_packet_sequence_id: 6
+  trusted_pid: 2528
+  shell_transition {
+    id: 11
+    dispatch_time_ns: 82536817137
+    handler: 2
+  }
+}
+packet {
+  trusted_uid: 10241
+  trusted_packet_sequence_id: 6
+  trusted_pid: 2528
+  shell_transition {
+    id: 12
+    merge_time_ns: 82697060749
+    merge_target: 11
+  }
+}
diff --git a/test/trace_processor/diff_tests/parser/android/shell_transitions_simple_merge.textproto b/test/trace_processor/diff_tests/parser/android/shell_transitions_simple_merge.textproto
new file mode 100644
index 0000000..6c9cb65
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/android/shell_transitions_simple_merge.textproto
@@ -0,0 +1,62 @@
+packet {
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 2
+  previous_packet_dropped: true
+  trusted_pid: 1336
+  first_packet_on_sequence: true
+  shell_transition {
+    id: 15
+    create_time_ns: 2187614568227
+    send_time_ns: 2187671767120
+    start_transaction_id: 5738076308937
+    finish_transaction_id: 5738076308938
+    type: 1
+    targets {
+      mode: 1
+      layer_id: 244
+      window_id: 219481253
+      flags: 0
+    }
+    targets {
+      mode: 4
+      layer_id: 47
+      window_id: 54474511
+      flags: 1
+    }
+    flags: 0
+  }
+}
+packet {
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 3
+  previous_packet_dropped: true
+  trusted_pid: 1336
+  first_packet_on_sequence: true
+  shell_transition {
+    id: 15
+    finish_time_ns: 2188195968659
+  }
+}
+packet {
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 5
+  previous_packet_dropped: true
+  trusted_pid: 1336
+  first_packet_on_sequence: true
+  shell_transition {
+    id: 15
+    starting_window_remove_time_ns: 2188652838898
+  }
+}
+packet {
+  trusted_uid: 10225
+  trusted_packet_sequence_id: 12
+  previous_packet_dropped: true
+  trusted_pid: 3981
+  first_packet_on_sequence: true
+  shell_transition {
+    id: 15
+    dispatch_time_ns: 2187673373973
+    handler: 2
+  }
+}
diff --git a/test/trace_processor/diff_tests/parser/android/tests_shell_transitions.py b/test/trace_processor/diff_tests/parser/android/tests_shell_transitions.py
new file mode 100644
index 0000000..a8e0328
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/android/tests_shell_transitions.py
@@ -0,0 +1,91 @@
+#!/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
+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 ShellTransitions(TestSuite):
+
+  def test_has_expected_transition_rows(self):
+    return DiffTestBlueprint(
+        trace=Path('shell_transitions.textproto'),
+        query="""
+        SELECT
+          id, transition_id
+        FROM
+          window_manager_shell_transitions;
+        """,
+        out=Csv("""
+        "id","transition_id"
+        0,7
+        1,10
+        2,11
+        3,8
+        4,9
+        5,12
+        """))
+
+  def test_has_expected_transition_args(self):
+    return DiffTestBlueprint(
+        trace=Path('shell_transitions_simple_merge.textproto'),
+        query="""
+        SELECT
+          args.key, args.display_value
+        FROM
+          window_manager_shell_transitions JOIN args ON window_manager_shell_transitions.arg_set_id = args.arg_set_id
+        WHERE window_manager_shell_transitions.transition_id = 15
+        ORDER BY args.key;
+        """,
+        out=Csv("""
+        "key","display_value"
+        "create_time_ns","2187614568227"
+        "dispatch_time_ns","2187673373973"
+        "finish_time_ns","2188195968659"
+        "finish_transaction_id","5738076308938"
+        "flags","0"
+        "handler","2"
+        "id","15"
+        "send_time_ns","2187671767120"
+        "start_transaction_id","5738076308937"
+        "starting_window_remove_time_ns","2188652838898"
+        "targets[0].flags","0"
+        "targets[0].layer_id","244"
+        "targets[0].mode","1"
+        "targets[0].window_id","219481253"
+        "targets[1].flags","1"
+        "targets[1].layer_id","47"
+        "targets[1].mode","4"
+        "targets[1].window_id","54474511"
+        "type","1"
+        """))
+
+  def test_has_shell_handlers(self):
+    return DiffTestBlueprint(
+        trace=Path('shell_handlers.textproto'),
+        query="""
+      SELECT
+        handler_id, handler_name
+      FROM
+        window_manager_shell_transition_handlers;
+      """,
+        out=Csv("""
+      "handler_id","handler_name"
+      1,"DefaultTransitionHandler"
+      2,"RecentsTransitionHandler"
+      3,"FreeformTaskTransitionHandler"
+      """))
diff --git a/test/trace_processor/diff_tests/parser/graphics/actual_frame_timeline_events_test.sql b/test/trace_processor/diff_tests/parser/graphics/actual_frame_timeline_events_test.sql
index a46f6aa..269523c 100644
--- a/test/trace_processor/diff_tests/parser/graphics/actual_frame_timeline_events_test.sql
+++ b/test/trace_processor/diff_tests/parser/graphics/actual_frame_timeline_events_test.sql
@@ -13,8 +13,8 @@
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
 
-SELECT ts, dur, process.pid AS pid, display_frame_token, surface_frame_token, layer_name,
-  present_type, on_time_finish, gpu_composition, jank_type, prediction_type, jank_tag
+SELECT ts, dur, process.pid, display_frame_token, surface_frame_token, layer_name,
+  present_type, on_time_finish, gpu_composition, jank_type, prediction_type, jank_tag, jank_severity_type
 FROM
   (SELECT t.*, process_track.name AS track_name FROM
     process_track LEFT JOIN actual_frame_timeline_slice t
diff --git a/test/trace_processor/diff_tests/parser/graphics/frame_timeline_events.py b/test/trace_processor/diff_tests/parser/graphics/frame_timeline_events.py
index 91590fb..90bae3a 100644
--- a/test/trace_processor/diff_tests/parser/graphics/frame_timeline_events.py
+++ b/test/trace_processor/diff_tests/parser/graphics/frame_timeline_events.py
@@ -33,6 +33,13 @@
   JANK_DROPPED = 1024
 
 
+class JankSeverityType:
+  UNKNOWN = 0
+  NONE = 1
+  PARTIAL = 2
+  FULL = 3
+
+
 class PresentType:
   PRESENT_UNSPECIFIED = 0
   PRESENT_ON_TIME = 1
@@ -135,6 +142,7 @@
     on_time_finish=0,
     gpu_composition=0,
     jank_type=JankType.JANK_APP_DEADLINE_MISSED,
+    jank_severity_type=JankSeverityType.FULL,
     prediction_type=PredictionType.PREDICTION_VALID)
 trace.add_frame_end_event(ts=74, cookie=10)
 
@@ -276,6 +284,7 @@
     on_time_finish=0,
     gpu_composition=0,
     jank_type=JankType.JANK_UNKNOWN,
+    jank_severity_type=JankSeverityType.PARTIAL,
     prediction_type=PredictionType.PREDICTION_EXPIRED)
 trace.add_frame_end_event(ts=190, cookie=25)
 
diff --git a/test/trace_processor/diff_tests/parser/graphics/tests.py b/test/trace_processor/diff_tests/parser/graphics/tests.py
index 3a2bf84..7fe4af1 100644
--- a/test/trace_processor/diff_tests/parser/graphics/tests.py
+++ b/test/trace_processor/diff_tests/parser/graphics/tests.py
@@ -110,22 +110,22 @@
         trace=Path('frame_timeline_events.py'),
         query=Path('actual_frame_timeline_events_test.sql'),
         out=Csv("""
-        "ts","dur","pid","display_frame_token","surface_frame_token","layer_name","present_type","on_time_finish","gpu_composition","jank_type","prediction_type","jank_tag"
-        20,6,666,2,0,"[NULL]","On-time Present",1,0,"None","Valid Prediction","No Jank"
-        21,16,1000,4,1,"Layer1","On-time Present",1,0,"None","Valid Prediction","No Jank"
-        41,33,1000,6,5,"Layer1","Late Present",0,0,"App Deadline Missed","Valid Prediction","Self Jank"
-        42,5,666,4,0,"[NULL]","On-time Present",1,0,"None","Valid Prediction","No Jank"
-        80,110,1000,17,16,"Layer1","Unknown Present",0,0,"Unknown Jank","Expired Prediction","Self Jank"
-        81,7,666,6,0,"[NULL]","On-time Present",1,0,"None","Valid Prediction","No Jank"
-        90,16,1000,8,7,"Layer1","Early Present",1,0,"SurfaceFlinger Scheduling","Valid Prediction","Other Jank"
-        108,4,666,8,0,"[NULL]","Early Present",1,0,"SurfaceFlinger Scheduling","Valid Prediction","Self Jank"
-        148,8,666,12,0,"[NULL]","Late Present",0,0,"SurfaceFlinger Scheduling, SurfaceFlinger CPU Deadline Missed","Valid Prediction","Self Jank"
-        150,17,1000,15,14,"Layer1","On-time Present",1,0,"None","Valid Prediction","No Jank"
-        150,17,1000,15,14,"Layer2","On-time Present",1,0,"None","Valid Prediction","No Jank"
-        170,6,666,15,0,"[NULL]","On-time Present",1,0,"None","Valid Prediction","No Jank"
-        200,6,666,17,0,"[NULL]","On-time Present",1,0,"None","Valid Prediction","No Jank"
-        245,-1,666,18,0,"[NULL]","Late Present",0,0,"SurfaceFlinger Stuffing","Valid Prediction","SurfaceFlinger Stuffing"
-        245,15,666,18,0,"[NULL]","Dropped Frame",0,0,"Dropped Frame","Unspecified Prediction","Dropped Frame"
+        "ts","dur","pid","display_frame_token","surface_frame_token","layer_name","present_type","on_time_finish","gpu_composition","jank_type","prediction_type","jank_tag","jank_severity_type"
+        20,6,666,2,0,"[NULL]","On-time Present",1,0,"None","Valid Prediction","No Jank","None"
+        21,16,1000,4,1,"Layer1","On-time Present",1,0,"None","Valid Prediction","No Jank","None"
+        41,33,1000,6,5,"Layer1","Late Present",0,0,"App Deadline Missed","Valid Prediction","Self Jank","Full"
+        42,5,666,4,0,"[NULL]","On-time Present",1,0,"None","Valid Prediction","No Jank","None"
+        80,110,1000,17,16,"Layer1","Unknown Present",0,0,"Unknown Jank","Expired Prediction","Self Jank","Partial"
+        81,7,666,6,0,"[NULL]","On-time Present",1,0,"None","Valid Prediction","No Jank","None"
+        90,16,1000,8,7,"Layer1","Early Present",1,0,"SurfaceFlinger Scheduling","Valid Prediction","Other Jank","Unknown"
+        108,4,666,8,0,"[NULL]","Early Present",1,0,"SurfaceFlinger Scheduling","Valid Prediction","Self Jank","Unknown"
+        148,8,666,12,0,"[NULL]","Late Present",0,0,"SurfaceFlinger Scheduling, SurfaceFlinger CPU Deadline Missed","Valid Prediction","Self Jank","Unknown"
+        150,17,1000,15,14,"Layer1","On-time Present",1,0,"None","Valid Prediction","No Jank","None"
+        150,17,1000,15,14,"Layer2","On-time Present",1,0,"None","Valid Prediction","No Jank","None"
+        170,6,666,15,0,"[NULL]","On-time Present",1,0,"None","Valid Prediction","No Jank","None"
+        200,6,666,17,0,"[NULL]","On-time Present",1,0,"None","Valid Prediction","No Jank","None"
+        245,-1,666,18,0,"[NULL]","Late Present",0,0,"SurfaceFlinger Stuffing","Valid Prediction","SurfaceFlinger Stuffing","Unknown"
+        245,15,666,18,0,"[NULL]","Dropped Frame",0,0,"Dropped Frame","Unspecified Prediction","Dropped Frame","Unknown"
         """))
 
   # Video 4 Linux 2 related tests
diff --git a/test/trace_processor/diff_tests/parser/parsing/tests.py b/test/trace_processor/diff_tests/parser/parsing/tests.py
index 5261b09..90067a7 100644
--- a/test/trace_processor/diff_tests/parser/parsing/tests.py
+++ b/test/trace_processor/diff_tests/parser/parsing/tests.py
@@ -1110,6 +1110,16 @@
         query=Path('all_atoms_test.sql'),
         out=Path('statsd_atoms_all_atoms.out'))
 
+  # Statsd Atoms
+  def test_statsd_atoms_unknown_atoms(self):
+    return DiffTestBlueprint(
+        trace=DataPath('statsd_atoms_oem.pb'),
+        query=Path('all_atoms_test.sql'),
+        out=Csv("""
+          "name","key","display_value"
+          "atom_202001","field_1","1"
+        """))
+
   # Kernel function tracing.
   def test_funcgraph_trace_funcgraph(self):
     return DiffTestBlueprint(
diff --git a/test/trace_processor/diff_tests/stdlib/chrome/tests_scroll_jank.py b/test/trace_processor/diff_tests/stdlib/chrome/tests_scroll_jank.py
index b9d19fd..80b679c 100755
--- a/test/trace_processor/diff_tests/stdlib/chrome/tests_scroll_jank.py
+++ b/test/trace_processor/diff_tests/stdlib/chrome/tests_scroll_jank.py
@@ -105,6 +105,99 @@
         1991,4687329240739,-28.999969,-175.999969
         """))
 
+  def test_chrome_janky_event_latencies_v3(self):
+    return DiffTestBlueprint(
+        trace=DataPath('chrome_input_with_frame_view.pftrace'),
+        query="""
+        INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_jank_intervals;
+
+        SELECT
+          id,
+          ts,
+          dur,
+          track_id,
+          name,
+          cause_of_jank,
+          sub_cause_of_jank,
+          delayed_frame_count,
+          frame_jank_ts,
+          frame_jank_dur
+        FROM chrome_janky_event_latencies_v3
+        ORDER by id;
+        """,
+        out=Csv("""
+        "id","ts","dur","track_id","name","cause_of_jank","sub_cause_of_jank","delayed_frame_count","frame_jank_ts","frame_jank_dur"
+        29926,174795897267797,48088000,1431,"EventLatency","RendererCompositorQueueingDelay","[NULL]",1,174795928261797,17094000
+        38463,174796315541797,131289000,2163,"EventLatency","RendererCompositorFinishedToBeginImplFrame","[NULL]",5,174796362924797,83906000
+        88876,174799556245797,49856000,4329,"EventLatency","RendererCompositorQueueingDelay","[NULL]",1,174799589065797,17036000
+        """))
+
+  def test_chrome_janky_frame_presentation_intervals(self):
+    return DiffTestBlueprint(
+        trace=DataPath('chrome_input_with_frame_view.pftrace'),
+        query="""
+        INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_jank_intervals;
+
+        SELECT
+          id,
+          ts,
+          dur,
+          cause_of_jank,
+          sub_cause_of_jank,
+          delayed_frame_count,
+          event_latency_id
+        FROM chrome_janky_frame_presentation_intervals
+        ORDER by id;
+        """,
+        out=Csv("""
+        "id","ts","dur","cause_of_jank","sub_cause_of_jank","delayed_frame_count","event_latency_id"
+        1,174795928261797,17094000,"RendererCompositorQueueingDelay","[NULL]",1,29926
+        2,174796362924797,83906000,"RendererCompositorFinishedToBeginImplFrame","[NULL]",5,38463
+        3,174799589065797,17036000,"RendererCompositorQueueingDelay","[NULL]",1,88876
+        """))
+
+  def test_chrome_scroll_stats(self):
+    return DiffTestBlueprint(
+        trace=DataPath('chrome_input_with_frame_view.pftrace'),
+        query="""
+        INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_jank_intervals;
+
+        SELECT
+          scroll_id,
+          missed_vsyncs,
+          frame_count,
+          presented_frame_count,
+          janky_frame_count,
+          janky_frame_percent
+        FROM chrome_scroll_stats
+        ORDER by scroll_id;
+        """,
+        out=Csv("""
+        "scroll_id","missed_vsyncs","frame_count","presented_frame_count","janky_frame_count","janky_frame_percent"
+        1186,6,110,105,2,1.900000
+        1889,"[NULL]",101,102,0,0.000000
+        2506,1,84,84,1,1.190000
+        """))
+
+  def test_chrome_scroll_jank_intervals_v3(self):
+    return DiffTestBlueprint(
+        trace=DataPath('chrome_input_with_frame_view.pftrace'),
+        query="""
+        INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_jank_intervals;
+
+        SELECT
+          id,
+          ts,
+          dur
+        FROM chrome_scroll_jank_intervals_v3
+        ORDER by id;
+        """,
+        out=Csv("""
+        "id","ts","dur"
+        1,174795928261797,17094000
+        2,174796362924797,83906000
+        3,174799589065797,17036000
+        """))
   def test_chrome_presented_scroll_offsets(self):
     return DiffTestBlueprint(
         trace=DataPath('scroll_offsets.pftrace'),
diff --git a/test/trace_processor/diff_tests/tables/tests.py b/test/trace_processor/diff_tests/tables/tests.py
index 03c7eb0..5a8f393 100644
--- a/test/trace_processor/diff_tests/tables/tests.py
+++ b/test/trace_processor/diff_tests/tables/tests.py
@@ -356,3 +356,26 @@
           0,"flow",0,1,57,0
           1,"flow",1,2,57,0
                 """))
+
+  def test_clock_snapshot_table_multiplier(self):
+    return DiffTestBlueprint(
+        trace=TextProto("""
+          packet {
+            clock_snapshot {
+              clocks {
+                clock_id: 1
+                timestamp: 42
+                unit_multiplier_ns: 10
+              }
+              clocks {
+                clock_id: 6
+                timestamp: 0
+              }
+            }
+          }
+        """),
+        query="SELECT TO_REALTIME(0);",
+        out=Csv("""
+          "TO_REALTIME(0)"
+          420
+        """))
diff --git a/tools/cpu_profile b/tools/cpu_profile
index 166daff..9654a2a 100755
--- a/tools/cpu_profile
+++ b/tools/cpu_profile
@@ -37,18 +37,18 @@
 
 
 # ----- Amalgamator: begin of python/perfetto/prebuilts/manifests/traceconv.py
-# This file has been generated by: tools/roll-prebuilts 6ba75cc5d93c39d4f86e2777709a460b30ce06fc
+# This file has been generated by: tools/roll-prebuilts v40.0
 TRACECONV_MANIFEST = [{
     'arch':
         'mac-amd64',
     'file_name':
         'traceconv',
     'file_size':
-        9020880,
+        9184800,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/mac-amd64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/mac-amd64/traceconv',
     'sha256':
-        'ae7903f9bb8a98b32fdf96c0124797cf9b4b58a9bcb45a05574d74d33b11ada9',
+        'b651d0a5b5606c1c3e24723e94d8ecb233a01f0dfccc95a2c6a4e773cb8f52d7',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -58,11 +58,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        7580200,
+        7761896,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/mac-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/mac-arm64/traceconv',
     'sha256':
-        '619f31cd84a70fcb75e2a1cbe911f77feca0273ea0b1e706ae4be5ac020cdf7d',
+        '3b019f5ddd5293d3181f7c30f91dc7b08f3a2e83ebb3b52b8f3905dc5161747d',
     'platform':
         'darwin',
     'machine': ['arm64']
@@ -72,11 +72,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        8773576,
+        8928296,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/linux-amd64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-amd64/traceconv',
     'sha256':
-        'abc0b4191abe5106e1d4382bc0d238e53c39a83e1ba91237fbed5f789ef1c82b',
+        '830d20ffec266218d49f6b6c8efed4538bc59b51d8d2f735cbbb6a1435131b50',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -86,11 +86,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        6620748,
+        6770204,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/linux-arm/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-arm/traceconv',
     'sha256':
-        'f2dd9f1bfabea567ff20aedd0798e90093eff2bfdc15aacbfafe0abe52aa1fd1',
+        '93a9e5ccb94559b871af8f6da45f858aee01801b31776703892dcf3d7ea769b7',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -100,11 +100,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        8240792,
+        8393944,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/linux-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-arm64/traceconv',
     'sha256':
-        '76428eca01ced4b769241915fb5233b43b9b59e9062917e45d523db10f33e546',
+        '88a92ccbcd8e851673e018b7f599514daf05dde9b7e4de9641fa5629124abf12',
     'platform':
         'linux',
     'machine': ['aarch64']
@@ -114,55 +114,55 @@
     'file_name':
         'traceconv',
     'file_size':
-        6247672,
+        6378744,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/android-arm/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-arm/traceconv',
     'sha256':
-        '3eb80bbd78c6210b73ef20654b9d80994747e552b3d342a8d2ec4aa58dabcc33'
+        '6cb7d30d656aa4f172e6724f105a56e249e7043ecf637c65e1e3868885535cff'
 }, {
     'arch':
         'android-arm64',
     'file_name':
         'traceconv',
     'file_size':
-        7545032,
+        7692488,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/android-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-arm64/traceconv',
     'sha256':
-        '30037aba74f7718f71273a83141373e623ddfe561294d01b41433dc9b572fee5'
+        '1668808efbdf8d5b116d4716d61d2bd002f71ce465206d3b83af4fcc7a4c19cd'
 }, {
     'arch':
         'android-x86',
     'file_name':
         'traceconv',
     'file_size':
-        8410300,
+        8557756,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/android-x86/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-x86/traceconv',
     'sha256':
-        '9ad420368453699da12ce6359db514283e92b2caa2d415d9db61977cc79649a9'
+        '653733582cae0021eae0e1b5d8db387c1bae772d77b307f1e2111b78ec4ea67c'
 }, {
     'arch':
         'android-x64',
     'file_name':
         'traceconv',
     'file_size':
-        8544512,
+        8708352,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/android-x64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-x64/traceconv',
     'sha256':
-        '60a62f8712186c5f984ff2fbc4ff8c47b6226b62a83b31f878e0597c8bb90cd8'
+        '7fc564ac581b81d79573f57dae027c47bd7a857ff0f89df984380c3c657d5876'
 }, {
     'arch':
         'windows-amd64',
     'file_name':
         'traceconv.exe',
     'file_size':
-        8049664,
+        8204288,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/windows-amd64/traceconv.exe',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/windows-amd64/traceconv.exe',
     'sha256':
-        '9e89ccd0cdb466ea5fe960b71538b0ff8a2858b9e7ecdbcafa9ffe60839c9afc',
+        'e33bad8061f08f9c3cfe6e91ef6f1696b6ac90d0799edcb57052f24888b436e2',
     'platform':
         'win32',
     'machine': ['amd64']
diff --git a/tools/gen_android_bp b/tools/gen_android_bp
index eea7f09..c1c1ea1 100755
--- a/tools/gen_android_bp
+++ b/tools/gen_android_bp
@@ -114,6 +114,10 @@
     '//:libperfetto_client_experimental',
 ]
 
+target_product_available = [
+    '//:libperfetto_client_experimental',
+]
+
 # Proto target groups which will be made public.
 proto_groups = {
     'trace': [
@@ -256,8 +260,7 @@
     ],
     'libperfetto_client_experimental': [
         ('apex_available', {
-            '//apex_available:platform', 'com.android.art',
-            'com.android.art.debug', 'com.android.tethering'
+            '//apex_available:platform', '//apex_available:anyapex'
         }),
         ('min_sdk_version', '30'),
         ('shared_libs', {'liblog'}),
@@ -332,7 +335,7 @@
 
 def enable_sqlite(module):
   if module.type == 'cc_binary_host':
-    module.static_libs.add('libsqlite')
+    module.static_libs.add('libsqlite_static_noicu')
     module.static_libs.add('sqlite_ext_percentile')
   elif module.host_supported:
     # Copy what the sqlite3 command line tool does.
@@ -341,7 +344,7 @@
     module.android.shared_libs.add('liblog')
     module.android.shared_libs.add('libutils')
     module.android.static_libs.add('sqlite_ext_percentile')
-    module.host.static_libs.add('libsqlite')
+    module.host.static_libs.add('libsqlite_static_noicu')
     module.host.static_libs.add('sqlite_ext_percentile')
   else:
     module.shared_libs.add('libsqlite')
@@ -509,6 +512,7 @@
     self.cmd: Optional[str] = None
     self.host_supported = False
     self.vendor_available = False
+    self.product_available = False
     self.init_rc = set()
     self.out = set()
     self.export_include_dirs = set()
@@ -532,6 +536,7 @@
     self.apex_available = set()
     self.min_sdk_version = None
     self.proto = dict()
+    self.output_extension: Optional[str] = None
     # The genrule_XXX below are properties that must to be propagated back
     # on the module(s) that depend on the genrule.
     self.genrule_headers = set()
@@ -558,6 +563,8 @@
       self._output_field(output, 'host_supported')
     if self.vendor_available:
       self._output_field(output, 'vendor_available')
+    if self.product_available:
+      self._output_field(output, 'product_available')
     self._output_field(output, 'init_rc')
     self._output_field(output, 'out')
     self._output_field(output, 'export_include_dirs')
@@ -581,6 +588,7 @@
     self._output_field(output, 'stubs')
     self._output_field(output, 'proto')
     self._output_field(output, 'main')
+    self._output_field(output, 'output_extension')
 
     target_out = []
     self._output_field(target_out, 'android')
@@ -1032,6 +1040,7 @@
   blueprint.add_module(module)
   module.host_supported = (name_without_toolchain in target_host_supported)
   module.vendor_available = (name_without_toolchain in target_vendor_available)
+  module.product_available = (name_without_toolchain in target_product_available)
   module.init_rc.update(target_initrc.get(target.name, []))
   if target.type != 'proto_library':
     # proto_library embeds a "root" filegroup in its srcs.
diff --git a/tools/heap_profile b/tools/heap_profile
index 17155d8..77885fc 100755
--- a/tools/heap_profile
+++ b/tools/heap_profile
@@ -34,18 +34,18 @@
 
 
 # ----- Amalgamator: begin of python/perfetto/prebuilts/manifests/traceconv.py
-# This file has been generated by: tools/roll-prebuilts 6ba75cc5d93c39d4f86e2777709a460b30ce06fc
+# This file has been generated by: tools/roll-prebuilts v40.0
 TRACECONV_MANIFEST = [{
     'arch':
         'mac-amd64',
     'file_name':
         'traceconv',
     'file_size':
-        9020880,
+        9184800,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/mac-amd64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/mac-amd64/traceconv',
     'sha256':
-        'ae7903f9bb8a98b32fdf96c0124797cf9b4b58a9bcb45a05574d74d33b11ada9',
+        'b651d0a5b5606c1c3e24723e94d8ecb233a01f0dfccc95a2c6a4e773cb8f52d7',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -55,11 +55,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        7580200,
+        7761896,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/mac-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/mac-arm64/traceconv',
     'sha256':
-        '619f31cd84a70fcb75e2a1cbe911f77feca0273ea0b1e706ae4be5ac020cdf7d',
+        '3b019f5ddd5293d3181f7c30f91dc7b08f3a2e83ebb3b52b8f3905dc5161747d',
     'platform':
         'darwin',
     'machine': ['arm64']
@@ -69,11 +69,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        8773576,
+        8928296,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/linux-amd64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-amd64/traceconv',
     'sha256':
-        'abc0b4191abe5106e1d4382bc0d238e53c39a83e1ba91237fbed5f789ef1c82b',
+        '830d20ffec266218d49f6b6c8efed4538bc59b51d8d2f735cbbb6a1435131b50',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -83,11 +83,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        6620748,
+        6770204,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/linux-arm/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-arm/traceconv',
     'sha256':
-        'f2dd9f1bfabea567ff20aedd0798e90093eff2bfdc15aacbfafe0abe52aa1fd1',
+        '93a9e5ccb94559b871af8f6da45f858aee01801b31776703892dcf3d7ea769b7',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -97,11 +97,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        8240792,
+        8393944,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/linux-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-arm64/traceconv',
     'sha256':
-        '76428eca01ced4b769241915fb5233b43b9b59e9062917e45d523db10f33e546',
+        '88a92ccbcd8e851673e018b7f599514daf05dde9b7e4de9641fa5629124abf12',
     'platform':
         'linux',
     'machine': ['aarch64']
@@ -111,55 +111,55 @@
     'file_name':
         'traceconv',
     'file_size':
-        6247672,
+        6378744,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/android-arm/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-arm/traceconv',
     'sha256':
-        '3eb80bbd78c6210b73ef20654b9d80994747e552b3d342a8d2ec4aa58dabcc33'
+        '6cb7d30d656aa4f172e6724f105a56e249e7043ecf637c65e1e3868885535cff'
 }, {
     'arch':
         'android-arm64',
     'file_name':
         'traceconv',
     'file_size':
-        7545032,
+        7692488,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/android-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-arm64/traceconv',
     'sha256':
-        '30037aba74f7718f71273a83141373e623ddfe561294d01b41433dc9b572fee5'
+        '1668808efbdf8d5b116d4716d61d2bd002f71ce465206d3b83af4fcc7a4c19cd'
 }, {
     'arch':
         'android-x86',
     'file_name':
         'traceconv',
     'file_size':
-        8410300,
+        8557756,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/android-x86/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-x86/traceconv',
     'sha256':
-        '9ad420368453699da12ce6359db514283e92b2caa2d415d9db61977cc79649a9'
+        '653733582cae0021eae0e1b5d8db387c1bae772d77b307f1e2111b78ec4ea67c'
 }, {
     'arch':
         'android-x64',
     'file_name':
         'traceconv',
     'file_size':
-        8544512,
+        8708352,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/android-x64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-x64/traceconv',
     'sha256':
-        '60a62f8712186c5f984ff2fbc4ff8c47b6226b62a83b31f878e0597c8bb90cd8'
+        '7fc564ac581b81d79573f57dae027c47bd7a857ff0f89df984380c3c657d5876'
 }, {
     'arch':
         'windows-amd64',
     'file_name':
         'traceconv.exe',
     'file_size':
-        8049664,
+        8204288,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/windows-amd64/traceconv.exe',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/windows-amd64/traceconv.exe',
     'sha256':
-        '9e89ccd0cdb466ea5fe960b71538b0ff8a2858b9e7ecdbcafa9ffe60839c9afc',
+        'e33bad8061f08f9c3cfe6e91ef6f1696b6ac90d0799edcb57052f24888b436e2',
     'platform':
         'win32',
     'machine': ['amd64']
diff --git a/tools/install-build-deps b/tools/install-build-deps
index c3822f6..fd17b81 100755
--- a/tools/install-build-deps
+++ b/tools/install-build-deps
@@ -63,7 +63,7 @@
     'buildtools/test_data',  # Moved to test/data by r.android.com/1539381 .
     'buildtools/d8',  # Removed by r.android.com/1424334 .
 
-    # Build toools moved to third_party/ by r.android.com/2327602 .
+    # Build tools moved to third_party/ by r.android.com/2327602 .
     'buildtools/mac/clang-format',
     'buildtools/mac/gn',
     'buildtools/mac/ninja',
@@ -95,6 +95,11 @@
         'f706aaa0676e3e22f5fc9ca482295d7caee8535d1869f99efa2358177b64f5cd',
         'linux', 'x64'),
     Dependency(
+        'third_party/gn/gn',
+        'https://storage.googleapis.com/perfetto/gn-linux-arm64-1968-0725d782',
+        'c2a372cd4f911028d8bc351fbf24835c9b1194fcc92beadf6c5a2b3addae973c',
+        'linux', 'arm64'),
+    Dependency(
         'third_party/gn/gn.exe',
         'https://storage.googleapis.com/perfetto/gn-win-1968-0725d782',
         '001f777f023c7a6959c778fb3a6b6cfc63f6baef953410ecdeaec350fb12285b',
@@ -150,6 +155,11 @@
         'https://storage.googleapis.com/perfetto/ninja-win-182',
         '09ced0fcd1a4dec7d1b798a2cf9ce5d20e5d2fbc2337343827f192ce47d0f491',
         'windows', 'x64'),
+    Dependency(
+        'third_party/ninja/ninja',
+        'https://storage.googleapis.com/perfetto/ninja-linux-arm64-1111',
+        '05031a734ec4310a51b2cfe9f0096b26fce25ab4ff19e5b51abe6371de066cc5',
+        'linux', 'arm64'),
 
     # Keep the revision in sync with Chrome's PACKAGE_VERSION in
     # tools/clang/scripts/update.py.
@@ -463,6 +473,8 @@
   arch = machine()
   if arch == 'arm64':
     return 'arm64'
+  elif arch == 'aarch64':
+    return 'arm64'
   else:
     # Assume everything else is x64 matching previous behaviour.
     return 'x64'
diff --git a/tools/open_trace_in_ui b/tools/open_trace_in_ui
index 30e4b01..0bf8ea7 100755
--- a/tools/open_trace_in_ui
+++ b/tools/open_trace_in_ui
@@ -63,7 +63,7 @@
   fname = os.path.basename(path)
   socketserver.TCPServer.allow_reuse_address = True
   with socketserver.TCPServer(('127.0.0.1', PORT), HttpHandler) as httpd:
-    address = f'{origin}/#!/?url=http://127.0.0.1:{PORT}/{fname}'
+    address = f'{origin}/#!/?url=http://127.0.0.1:{PORT}/{fname}&referrer=open_trace_in_ui'
     if open_browser:
       webbrowser.open_new_tab(address)
     else:
diff --git a/tools/record_android_trace b/tools/record_android_trace
index d463dd9..d4a740a 100755
--- a/tools/record_android_trace
+++ b/tools/record_android_trace
@@ -33,18 +33,18 @@
 
 
 # ----- Amalgamator: begin of python/perfetto/prebuilts/manifests/tracebox.py
-# This file has been generated by: tools/roll-prebuilts 6ba75cc5d93c39d4f86e2777709a460b30ce06fc
+# This file has been generated by: tools/roll-prebuilts v40.0
 TRACEBOX_MANIFEST = [{
     'arch':
         'mac-amd64',
     'file_name':
         'tracebox',
     'file_size':
-        1498680,
+        1498816,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/mac-amd64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/mac-amd64/tracebox',
     'sha256':
-        '57252aaf73b6a82cfb3bbc6cd68a9ec953f61296cf2c2d651125bb437dc50144',
+        '185014447d35357edbd20e7ce9924842a0d5c6576bd2257abae2ed48b65fd3b8',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -54,11 +54,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        1376136,
+        1392776,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/mac-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/mac-arm64/tracebox',
     'sha256':
-        '5f37217af7d47b39624c62625847ec6b98db2ebd1a512e85921eb16bcb402b35',
+        '082bb50e64df5e232673eebb1cd8b0dd752a394105f600cb0262730833f6b7f3',
     'platform':
         'darwin',
     'machine': ['arm64']
@@ -68,11 +68,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        2218840,
+        2229096,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/linux-amd64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-amd64/tracebox',
     'sha256':
-        '89b1412e66b0a227cd8d16e6bf3d7416609c153d4b5c64d6e1a7c600870cd27b',
+        'c99120caedb845e1c3fad4428263a683b44c357c76d65848dd8e437250066e38',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -82,11 +82,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        1332292,
+        1339796,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/linux-arm/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-arm/tracebox',
     'sha256':
-        'b5a3a8e0869b35bc9ceb06f3e4ca5bb6c1e1b1c5e6ea23cfed541e40a45ad96a',
+        '6732165916b74f0b820991d1aaed2086a6b56e91f6c604291efe6636f0bdda71',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -96,11 +96,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        2147464,
+        2157312,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/linux-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-arm64/tracebox',
     'sha256':
-        '00fdbdfc5877352b9323b6ea1a119132b66e696941f380da117d0d47ae1baaf9',
+        '7d09865a6d7118e67d2acd0c56b2a94ce8bd5f614869d29a72fe633515ab1fbd',
     'platform':
         'linux',
     'machine': ['aarch64']
@@ -110,11 +110,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        1230804,
+        1247188,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/android-arm/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-arm/tracebox',
     'sha256':
-        '02ee157f995708e78f57d6e5e110054d2e2b407dbf2db120cc967c7c553081e7'
+        '4ecc192172ac2bca49557cbdbb1f7d660718d4fb4a7314fd19b2b2e52be8bc0c'
 }, {
     'arch':
         'android-arm64',
@@ -123,9 +123,9 @@
     'file_size':
         1854120,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/android-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-arm64/tracebox',
     'sha256':
-        'e0031963f623da25c387b78e20747e07829043732f5afe16cfdea9bad3c0f27c'
+        '1ca89113279d5c6a9ae273bde03b4d84373efe6923dc637cb840908f13b9639e'
 }, {
     'arch':
         'android-x86',
@@ -134,9 +134,9 @@
     'file_size':
         1853356,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/android-x86/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-x86/tracebox',
     'sha256':
-        '6e9e4b7b0d3773ab5011e476eca883eeb0ab7538fc7d2cd4642b6abc01711ba9'
+        'cf689a191c1252734ebbfda3106600da324610f761515cfbffbeac2ebdfee715'
 }, {
     'arch':
         'android-x64',
@@ -145,9 +145,9 @@
     'file_size':
         2149032,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/android-x64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-x64/tracebox',
     'sha256':
-        '4b494907a43e898047ce98271796464deb3010b6755cee8d9c8a6c341502979b'
+        '99e9ebdb5b5308d95551a4ad060d615d7defb6877c4061d21c783c45a71d372f'
 }]
 
 # ----- Amalgamator: end of python/perfetto/prebuilts/manifests/tracebox.py
@@ -718,7 +718,7 @@
   fname = os.path.basename(path)
   socketserver.TCPServer.allow_reuse_address = True
   with socketserver.TCPServer(('127.0.0.1', PORT), HttpHandler) as httpd:
-    address = f'{origin}/#!/?url=http://127.0.0.1:{PORT}/{fname}'
+    address = f'{origin}/#!/?url=http://127.0.0.1:{PORT}/{fname}&referrer=record_android_trace'
     if open_browser:
       webbrowser.open_new_tab(address)
     else:
diff --git a/tools/trace_processor b/tools/trace_processor
index 6328084..60de377 100755
--- a/tools/trace_processor
+++ b/tools/trace_processor
@@ -30,18 +30,18 @@
 
 
 # ----- Amalgamator: begin of python/perfetto/prebuilts/manifests/trace_processor_shell.py
-# This file has been generated by: tools/roll-prebuilts 6ba75cc5d93c39d4f86e2777709a460b30ce06fc
+# This file has been generated by: tools/roll-prebuilts v40.0
 TRACE_PROCESSOR_SHELL_MANIFEST = [{
     'arch':
         'mac-amd64',
     'file_name':
         'trace_processor_shell',
     'file_size':
-        9830664,
+        9978200,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/mac-amd64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/mac-amd64/trace_processor_shell',
     'sha256':
-        'd617f9237f77477a6e80065cbcfc84d6ce80505ee34851a0b435f0815aba2645',
+        'f3e21eb29fb51cb2ea9b81b69132c5ae93ce3276c57ccd27fcf7c675306b4e41',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -51,11 +51,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        8328808,
+        8493976,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/mac-arm64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/mac-arm64/trace_processor_shell',
     'sha256':
-        'cc5b81618ddfa0a6eb63e0d418c0a52e302ca2617906fae6743d71f82c454611',
+        '84f35765141374b8d883813ac533e0c004cf72d1c6f05aef0c973364ff541eb9',
     'platform':
         'darwin',
     'machine': ['arm64']
@@ -65,11 +65,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        9682088,
+        9830856,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/linux-amd64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-amd64/trace_processor_shell',
     'sha256':
-        '0beb26c1d744cb982630b23ce031a767d6ed7113b81dca0710f792111592ee9e',
+        'b3dc0a9c641b84a57fa5d59637921ae2237e4f05b1778341a691df220faf0cd7',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -79,11 +79,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        7087544,
+        7231096,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/linux-arm/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-arm/trace_processor_shell',
     'sha256':
-        '4df289643b09261bd5718d6894e301023b4101e94e2dd81ead63037fb454054d',
+        'a21252830fb1bbb7b3fd9665ce6e70920cffa6b1e72c16589c90896c002c3348',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -93,11 +93,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        9090808,
+        9238056,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/linux-arm64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-arm64/trace_processor_shell',
     'sha256':
-        '9cb0ce22fba1929bef599d89a197a2ea718061840006c4122032cf0fc5fc0c90',
+        'f77519ec19743ec2c22ed78fe3a20106a482a28d77c4154378af108c5f7bdd4a',
     'platform':
         'linux',
     'machine': ['aarch64']
@@ -107,55 +107,55 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        6723512,
+        6870968,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/android-arm/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-arm/trace_processor_shell',
     'sha256':
-        'c5949a736b1b5edb285f56c22e9af83e1d0ec6d331adaac77330a277d8bee07c'
+        '2c7055fb44085ec60ad8bb970d495c9c88070fce08902f11fcd44e0ae3369876'
 }, {
     'arch':
         'android-arm64',
     'file_name':
         'trace_processor_shell',
     'file_size':
-        8267112,
+        8414568,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/android-arm64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-arm64/trace_processor_shell',
     'sha256':
-        '32475b5fe7e1af557eab0d898accb37c657bc4b2dd221135b55114e61bd8be39'
+        'd8ca0dc2bab7ea604a6721f0ac0e2b433b43261f247c6c98c510dc17aafe5a72'
 }, {
     'arch':
         'android-x86',
     'file_name':
         'trace_processor_shell',
     'file_size':
-        9164668,
+        9328508,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/android-x86/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-x86/trace_processor_shell',
     'sha256':
-        '02aeff08c0c3e553c95c851401ade9aac6784ad2f19e312dabbb24801d02c0e5'
+        'de6a6ea45769888e59a1678d37b6e355b27b834d34a0b9e4980a942d333b88cc'
 }, {
     'arch':
         'android-x64',
     'file_name':
         'trace_processor_shell',
     'file_size':
-        9430440,
+        9577896,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/android-x64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-x64/trace_processor_shell',
     'sha256':
-        'de75622f96f2551a7bbc28a0f063e0dfcc39241cd4fe09c39d0b2541d860a30a'
+        'cd4b16c5f78a060934204737ba8b312e824ff7cc28f3732daf7d64e733a727f9'
 }, {
     'arch':
         'windows-amd64',
     'file_name':
         'trace_processor_shell.exe',
     'file_size':
-        9100288,
+        9248256,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/windows-amd64/trace_processor_shell.exe',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/windows-amd64/trace_processor_shell.exe',
     'sha256':
-        'f0065f1dfa3f8001959bf18f5cd30138aa58409e39a13ccdd52a1860384e34fb',
+        '26584b4bbab40f8b0ad991a869e7483f92d7223e1473b879a6ceafa49b76390a',
     'platform':
         'win32',
     'machine': ['amd64']
diff --git a/tools/tracebox b/tools/tracebox
index df4911a..a4c278b 100755
--- a/tools/tracebox
+++ b/tools/tracebox
@@ -30,18 +30,18 @@
 
 
 # ----- Amalgamator: begin of python/perfetto/prebuilts/manifests/tracebox.py
-# This file has been generated by: tools/roll-prebuilts 6ba75cc5d93c39d4f86e2777709a460b30ce06fc
+# This file has been generated by: tools/roll-prebuilts v40.0
 TRACEBOX_MANIFEST = [{
     'arch':
         'mac-amd64',
     'file_name':
         'tracebox',
     'file_size':
-        1498680,
+        1498816,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/mac-amd64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/mac-amd64/tracebox',
     'sha256':
-        '57252aaf73b6a82cfb3bbc6cd68a9ec953f61296cf2c2d651125bb437dc50144',
+        '185014447d35357edbd20e7ce9924842a0d5c6576bd2257abae2ed48b65fd3b8',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -51,11 +51,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        1376136,
+        1392776,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/mac-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/mac-arm64/tracebox',
     'sha256':
-        '5f37217af7d47b39624c62625847ec6b98db2ebd1a512e85921eb16bcb402b35',
+        '082bb50e64df5e232673eebb1cd8b0dd752a394105f600cb0262730833f6b7f3',
     'platform':
         'darwin',
     'machine': ['arm64']
@@ -65,11 +65,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        2218840,
+        2229096,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/linux-amd64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-amd64/tracebox',
     'sha256':
-        '89b1412e66b0a227cd8d16e6bf3d7416609c153d4b5c64d6e1a7c600870cd27b',
+        'c99120caedb845e1c3fad4428263a683b44c357c76d65848dd8e437250066e38',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -79,11 +79,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        1332292,
+        1339796,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/linux-arm/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-arm/tracebox',
     'sha256':
-        'b5a3a8e0869b35bc9ceb06f3e4ca5bb6c1e1b1c5e6ea23cfed541e40a45ad96a',
+        '6732165916b74f0b820991d1aaed2086a6b56e91f6c604291efe6636f0bdda71',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -93,11 +93,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        2147464,
+        2157312,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/linux-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-arm64/tracebox',
     'sha256':
-        '00fdbdfc5877352b9323b6ea1a119132b66e696941f380da117d0d47ae1baaf9',
+        '7d09865a6d7118e67d2acd0c56b2a94ce8bd5f614869d29a72fe633515ab1fbd',
     'platform':
         'linux',
     'machine': ['aarch64']
@@ -107,11 +107,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        1230804,
+        1247188,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/android-arm/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-arm/tracebox',
     'sha256':
-        '02ee157f995708e78f57d6e5e110054d2e2b407dbf2db120cc967c7c553081e7'
+        '4ecc192172ac2bca49557cbdbb1f7d660718d4fb4a7314fd19b2b2e52be8bc0c'
 }, {
     'arch':
         'android-arm64',
@@ -120,9 +120,9 @@
     'file_size':
         1854120,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/android-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-arm64/tracebox',
     'sha256':
-        'e0031963f623da25c387b78e20747e07829043732f5afe16cfdea9bad3c0f27c'
+        '1ca89113279d5c6a9ae273bde03b4d84373efe6923dc637cb840908f13b9639e'
 }, {
     'arch':
         'android-x86',
@@ -131,9 +131,9 @@
     'file_size':
         1853356,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/android-x86/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-x86/tracebox',
     'sha256':
-        '6e9e4b7b0d3773ab5011e476eca883eeb0ab7538fc7d2cd4642b6abc01711ba9'
+        'cf689a191c1252734ebbfda3106600da324610f761515cfbffbeac2ebdfee715'
 }, {
     'arch':
         'android-x64',
@@ -142,9 +142,9 @@
     'file_size':
         2149032,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/android-x64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-x64/tracebox',
     'sha256':
-        '4b494907a43e898047ce98271796464deb3010b6755cee8d9c8a6c341502979b'
+        '99e9ebdb5b5308d95551a4ad060d615d7defb6877c4061d21c783c45a71d372f'
 }]
 
 # ----- Amalgamator: end of python/perfetto/prebuilts/manifests/tracebox.py
diff --git a/tools/traceconv b/tools/traceconv
index 5127ab5..0136e37 100755
--- a/tools/traceconv
+++ b/tools/traceconv
@@ -30,18 +30,18 @@
 
 
 # ----- Amalgamator: begin of python/perfetto/prebuilts/manifests/traceconv.py
-# This file has been generated by: tools/roll-prebuilts 6ba75cc5d93c39d4f86e2777709a460b30ce06fc
+# This file has been generated by: tools/roll-prebuilts v40.0
 TRACECONV_MANIFEST = [{
     'arch':
         'mac-amd64',
     'file_name':
         'traceconv',
     'file_size':
-        9020880,
+        9184800,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/mac-amd64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/mac-amd64/traceconv',
     'sha256':
-        'ae7903f9bb8a98b32fdf96c0124797cf9b4b58a9bcb45a05574d74d33b11ada9',
+        'b651d0a5b5606c1c3e24723e94d8ecb233a01f0dfccc95a2c6a4e773cb8f52d7',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -51,11 +51,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        7580200,
+        7761896,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/mac-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/mac-arm64/traceconv',
     'sha256':
-        '619f31cd84a70fcb75e2a1cbe911f77feca0273ea0b1e706ae4be5ac020cdf7d',
+        '3b019f5ddd5293d3181f7c30f91dc7b08f3a2e83ebb3b52b8f3905dc5161747d',
     'platform':
         'darwin',
     'machine': ['arm64']
@@ -65,11 +65,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        8773576,
+        8928296,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/linux-amd64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-amd64/traceconv',
     'sha256':
-        'abc0b4191abe5106e1d4382bc0d238e53c39a83e1ba91237fbed5f789ef1c82b',
+        '830d20ffec266218d49f6b6c8efed4538bc59b51d8d2f735cbbb6a1435131b50',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -79,11 +79,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        6620748,
+        6770204,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/linux-arm/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-arm/traceconv',
     'sha256':
-        'f2dd9f1bfabea567ff20aedd0798e90093eff2bfdc15aacbfafe0abe52aa1fd1',
+        '93a9e5ccb94559b871af8f6da45f858aee01801b31776703892dcf3d7ea769b7',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -93,11 +93,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        8240792,
+        8393944,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/linux-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/linux-arm64/traceconv',
     'sha256':
-        '76428eca01ced4b769241915fb5233b43b9b59e9062917e45d523db10f33e546',
+        '88a92ccbcd8e851673e018b7f599514daf05dde9b7e4de9641fa5629124abf12',
     'platform':
         'linux',
     'machine': ['aarch64']
@@ -107,55 +107,55 @@
     'file_name':
         'traceconv',
     'file_size':
-        6247672,
+        6378744,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/android-arm/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-arm/traceconv',
     'sha256':
-        '3eb80bbd78c6210b73ef20654b9d80994747e552b3d342a8d2ec4aa58dabcc33'
+        '6cb7d30d656aa4f172e6724f105a56e249e7043ecf637c65e1e3868885535cff'
 }, {
     'arch':
         'android-arm64',
     'file_name':
         'traceconv',
     'file_size':
-        7545032,
+        7692488,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/android-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-arm64/traceconv',
     'sha256':
-        '30037aba74f7718f71273a83141373e623ddfe561294d01b41433dc9b572fee5'
+        '1668808efbdf8d5b116d4716d61d2bd002f71ce465206d3b83af4fcc7a4c19cd'
 }, {
     'arch':
         'android-x86',
     'file_name':
         'traceconv',
     'file_size':
-        8410300,
+        8557756,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/android-x86/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-x86/traceconv',
     'sha256':
-        '9ad420368453699da12ce6359db514283e92b2caa2d415d9db61977cc79649a9'
+        '653733582cae0021eae0e1b5d8db387c1bae772d77b307f1e2111b78ec4ea67c'
 }, {
     'arch':
         'android-x64',
     'file_name':
         'traceconv',
     'file_size':
-        8544512,
+        8708352,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/android-x64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/android-x64/traceconv',
     'sha256':
-        '60a62f8712186c5f984ff2fbc4ff8c47b6226b62a83b31f878e0597c8bb90cd8'
+        '7fc564ac581b81d79573f57dae027c47bd7a857ff0f89df984380c3c657d5876'
 }, {
     'arch':
         'windows-amd64',
     'file_name':
         'traceconv.exe',
     'file_size':
-        8049664,
+        8204288,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/windows-amd64/traceconv.exe',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v40.0/windows-amd64/traceconv.exe',
     'sha256':
-        '9e89ccd0cdb466ea5fe960b71538b0ff8a2858b9e7ecdbcafa9ffe60839c9afc',
+        'e33bad8061f08f9c3cfe6e91ef6f1696b6ac90d0799edcb57052f24888b436e2',
     'platform':
         'win32',
     'machine': ['amd64']
diff --git a/tools/update-statsd-descriptor b/tools/update-statsd-descriptor
index fe9ff0e..eca6c02 100755
--- a/tools/update-statsd-descriptor
+++ b/tools/update-statsd-descriptor
@@ -21,6 +21,7 @@
 import tempfile
 import contextlib
 import argparse
+import itertools
 
 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 TOOLS_DIR = os.path.join(ROOT_DIR, "tools")
@@ -34,8 +35,10 @@
 PROTO_LOGGING_URL = "https://android.googlesource.com/platform/frameworks/proto_logging.git"
 ATOM_RE = r"  message_type {\n.   name: \"Atom\"(\n    .+)+(\n  })"
 FIELD_RE = r"    field {\n      name: \"([^\"]+)\"\n      number: ([0-9]+)"
+EXTENSIONS_RE = r" extension {\n    name: \"([^\"]+)\"\n    extendee: \".android.os.statsd.Atom\"\n    number: ([0-9]+)(\n    .+)+(\n  })"
 ATOM_IDS_PATH = os.path.join(ROOT_DIR, "protos", "perfetto", "config", "statsd",
                              "atom_ids.proto")
+
 ATOM_IDS_TEMPLATE = """/*
  * Copyright (C) 2022 The Android Open Source Project
  *
@@ -75,6 +78,31 @@
     exit(1)
 
 
+# Extract core atoms. To do this we regex the pbtext
+# of the descriptor. This is hopefully:
+# - more stable than regexing atom.proto directly
+# - less complicated than parsing finding, importing, and using the
+#   Python protobuf library.
+def atoms_from_descriptor():
+  with contextlib.ExitStack() as stack:
+    descriptor_in = stack.enter_context(open(DESCRIPTOR_PATH))
+    pbtext = call(
+        PROTOC_PATH,
+        f"--proto_path={PROTOBUF_BUILTINS_DIR}",
+        f"{PROTOBUF_BUILTINS_DIR}/google/protobuf/descriptor.proto",
+        "--decode=google.protobuf.FileDescriptorSet",
+        stdin=descriptor_in).decode("utf8")
+
+    # Core atoms:
+    atom_pbtext = re.search(ATOM_RE, pbtext, re.MULTILINE)[0]
+    for m in re.finditer(FIELD_RE, atom_pbtext):
+      yield m[1], m[2]
+
+    # Extensions
+    for m in re.finditer(EXTENSIONS_RE, pbtext):
+      yield m[1], m[2]
+
+
 def main():
   parser = argparse.ArgumentParser()
   parser.add_argument("--atoms-checkout")
@@ -97,31 +125,29 @@
       pathlib.Path(proto_logging_dir).mkdir(parents=True, exist_ok=True)
       call("git", "clone", PROTO_LOGGING_URL, proto_logging_dir)
 
-    atoms_path = os.path.join(proto_logging_dir, "stats", "atoms.proto")
-    call(PROTOC_PATH, f"--proto_path={PROTOBUF_BUILTINS_DIR}",
-         f"--proto_path={atoms_root}",
-         f"--descriptor_set_out={DESCRIPTOR_PATH}", "--include_imports",
-         atoms_path)
 
-    # Extract and update atom_ids.proto. To do this we regex the pbtext
-    # of the descriptor. This is hopefully:
-    # - more stable than regexing atom.proto directly
-    # - less complicated than parsing finding, importing, and using the
-    #   Python protobuf library.
-    descriptor_in = stack.enter_context(open(DESCRIPTOR_PATH))
-    pbtext = call(
-        PROTOC_PATH,
-        f"--proto_path={PROTOBUF_BUILTINS_DIR}",
-        f"{PROTOBUF_BUILTINS_DIR}/google/protobuf/descriptor.proto",
-        "--decode=google.protobuf.FileDescriptorSet",
-        stdin=descriptor_in)
+    extensions_path = os.path.join(proto_logging_dir, "stats", "atoms")
+    extensions = []
+    if os.path.isdir(extensions_path):
+      for dirpath, dirnames, filenames in os.walk(extensions_path):
+        for name in filenames:
+          if name.endswith(".proto"):
+            path = os.path.join(dirpath, name)
+            extensions.append(path)
 
-    atom_pbtext = re.search(ATOM_RE, pbtext.decode("utf8"), re.MULTILINE)[0]
+    cmd = [
+      f"--proto_path={PROTOBUF_BUILTINS_DIR}",
+      f"--proto_path={atoms_root}",
+      f"--descriptor_set_out={DESCRIPTOR_PATH}",
+      "--include_imports",
+      ] + extensions + [
+      os.path.join(proto_logging_dir, "stats", "atoms.proto")
+    ]
+    call(PROTOC_PATH, *cmd)
 
     lines = []
-    for m in re.finditer(FIELD_RE, atom_pbtext):
-      name = "ATOM_" + m[1].upper()
-      field = m[2]
+    for name, field in atoms_from_descriptor():
+      name = "ATOM_" + name.upper()
       lines.append(f"  {name} = {field};".format(name=name, field=field))
     atom_ids_out = stack.enter_context(open(ATOM_IDS_PATH, "w"))
     atom_ids_out.write(ATOM_IDS_TEMPLATE.format(atoms="\n".join(lines)))
diff --git a/ui/build.js b/ui/build.js
index ba02879..4afe0cd 100644
--- a/ui/build.js
+++ b/ui/build.js
@@ -352,6 +352,9 @@
 
 function copyAssets(src, dst) {
   addTask(cp, [src, pjoin(cfg.outDistDir, 'assets', dst)]);
+  if (cfg.bigtrace) {
+    addTask(cp, [src, pjoin(cfg.outBigtraceDistDir, 'assets', dst)]);
+  }
 }
 
 function copyUiTestArtifactsAssets(src, dst) {
diff --git a/ui/release/channels.json b/ui/release/channels.json
index 91a0c0e..9ade987 100644
--- a/ui/release/channels.json
+++ b/ui/release/channels.json
@@ -2,11 +2,11 @@
   "channels": [
     {
       "name": "stable",
-      "rev": "c69b33b9abcc20fad9ad5f39de883216e4b43130"
+      "rev": "fa6ee75b6f4a9374ade506697c65a8a73edc54be"
     },
     {
       "name": "canary",
-      "rev": "c69b33b9abcc20fad9ad5f39de883216e4b43130"
+      "rev": "9ca89e30931314dec4af1131d516e07e39d8657d"
     },
     {
       "name": "autopush",
diff --git a/ui/src/base/dom_utils.ts b/ui/src/base/dom_utils.ts
index d7c7582..5caf9d9 100644
--- a/ui/src/base/dom_utils.ts
+++ b/ui/src/base/dom_utils.ts
@@ -82,3 +82,25 @@
 
   return {x: e.offsetX, y: e.offsetY};
 }
+
+function calculateScrollbarWidth() {
+  const outer = document.createElement('div');
+  outer.style.overflowY = 'scroll';
+  const inner = document.createElement('div');
+  outer.appendChild(inner);
+  document.body.appendChild(outer);
+  const width =
+      outer.getBoundingClientRect().width - inner.getBoundingClientRect().width;
+  document.body.removeChild(outer);
+  return width;
+}
+
+let cachedScrollBarWidth: number|undefined = undefined;
+
+// Calculate the space a scrollbar takes up.
+export function getScrollbarWidth() {
+  if (cachedScrollBarWidth === undefined) {
+    cachedScrollBarWidth = calculateScrollbarWidth();
+  }
+  return cachedScrollBarWidth;
+}
diff --git a/ui/src/base/mithril_utils.ts b/ui/src/base/mithril_utils.ts
index 576ea24..7458026 100644
--- a/ui/src/base/mithril_utils.ts
+++ b/ui/src/base/mithril_utils.ts
@@ -19,3 +19,34 @@
   return Array.isArray(children) && children.length > 0 &&
       children.some((value) => value);
 }
+
+// A component which simply passes through it's children.
+// Can be used for having something to attach lifecycle hooks to without having
+// to add an extra HTML element to the DOM.
+export const Passthrough = {
+  view({children}: m.VnodeDOM) {
+    return children;
+  },
+};
+
+export interface GateAttrs {
+  open: boolean;
+}
+
+// The gate component is a wrapper which can either be open or closed.
+// - When open, children are rendered inside a div where display = contents.
+// - When closed, children are rendered inside a div where display = none, and
+//   the children's view() function(s) will not be called.
+// Use this component when we want to conditionally render certain children,
+// but we want to maintain their state.
+export const Gate = {
+  view({attrs, children}: m.VnodeDOM<GateAttrs>) {
+    return m(
+        '',
+        {
+          style: {display: attrs.open ? 'contents' : 'none'},
+        },
+        m(Passthrough, {onbeforeupdate: () => attrs.open}, children),
+    );
+  },
+};
diff --git a/ui/src/base/static_initializers.ts b/ui/src/base/static_initializers.ts
index 5abfcd9..f927471 100644
--- a/ui/src/base/static_initializers.ts
+++ b/ui/src/base/static_initializers.ts
@@ -38,7 +38,7 @@
   // from the global state (which is frozen) and later try to update the copies.
   // By doing so, we  accidentally the local copy of global state, which is
   // supposed to be immutable.
-  setAutoFreeze(false);
+  setAutoFreeze(true);
 }
 
 function initializeProtobuf() {
diff --git a/ui/src/common/canvas_utils.ts b/ui/src/common/canvas_utils.ts
index 2d09617..ad78f96 100644
--- a/ui/src/common/canvas_utils.ts
+++ b/ui/src/common/canvas_utils.ts
@@ -75,7 +75,11 @@
     x: number,
     y: number,
     width: number,
-    height: number) {
+    height: number,
+    showGradient: boolean = true) {
+  if (width <= 0 || height <= 0) {
+    return;
+  }
   ctx.beginPath();
   const triangleSize = height / 4;
   ctx.moveTo(x, y);
@@ -92,10 +96,12 @@
 
   const fillStyle = ctx.fillStyle;
   if (isString(fillStyle)) {
-    const gradient = ctx.createLinearGradient(x, y, x + width, y + height);
-    gradient.addColorStop(0.66, fillStyle);
-    gradient.addColorStop(1, '#FFFFFF');
-    ctx.fillStyle = gradient;
+    if (showGradient) {
+      const gradient = ctx.createLinearGradient(x, y, x + width, y + height);
+      gradient.addColorStop(0.66, fillStyle);
+      gradient.addColorStop(1, '#FFFFFF');
+      ctx.fillStyle = gradient;
+    }
   } else {
     throw new Error(
         `drawIncompleteSlice() expects fillStyle to be a simple color not ${
diff --git a/ui/src/common/color.ts b/ui/src/common/color.ts
new file mode 100644
index 0000000..127bfbb
--- /dev/null
+++ b/ui/src/common/color.ts
@@ -0,0 +1,290 @@
+// 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 {hsluvToRgb} from 'hsluv';
+
+import {clamp} from '../base/math_utils';
+
+// This file contains a library for working with colors in various color spaces
+// and formats.
+
+const LIGHTNESS_MIN = 0;
+const LIGHTNESS_MAX = 100;
+
+const SATURATION_MIN = 0;
+const SATURATION_MAX = 100;
+
+// Most color formats can be defined using 3 numbers in a standardized order, so
+// this tuple serves as a compact way to store various color formats.
+// E.g. HSL, RGB
+type ColorTuple = [number, number, number];
+
+// Definition of an HSL color with named fields.
+interface HSL {
+  readonly h: number;  // 0-360
+  readonly s: number;  // 0-100
+  readonly l: number;  // 0-100
+}
+
+// Defines an interface to an immutable color object, which can be defined in
+// any arbitrary format or color space and provides function to modify the color
+// and conversions to CSS compatible style strings.
+// Because this color object is effectively immutable, a new color object is
+// returned when modifying the color, rather than editing the current object
+// in-place.
+// Also, because these objects are immutable, it's expected that readonly
+// properties such as |cssString| are efficient, as they can be computed at
+// creation time, so they may be used in the hot path (render loop).
+export interface Color {
+  readonly cssString: string;
+
+  // The perceived brightness of the color using a weighted average of the
+  // r, g and b channels based on human perception.
+  readonly perceivedBrightness: number;
+
+  // Bring up the lightness by |percent| percent.
+  lighten(percent: number, max?: number): Color;
+
+  // Bring down the lightness by |percent| percent.
+  darken(percent: number, min?: number): Color;
+
+  // Bring up the saturation by |percent| percent.
+  saturate(percent: number, max?: number): Color;
+
+  // Bring down the saturation by |percent| percent.
+  desaturate(percent: number, min?: number): Color;
+
+  // Set one or more HSL values.
+  setHSL(hsl: Partial<HSL>): Color;
+
+  setAlpha(alpha: number|undefined): Color;
+}
+
+// Common base class for HSL colors. Avoids code duplication.
+abstract class HSLColorBase<T extends Color> {
+  readonly hsl: ColorTuple;
+  readonly alpha?: number;
+
+  // Values are in the range:
+  // Hue:        0-360
+  // Saturation: 0-100
+  // Lightness:  0-100
+  // Alpha:      0-1
+  constructor(init: ColorTuple|HSL|string, alpha?: number) {
+    if (Array.isArray(init)) {
+      this.hsl = init;
+    } else if (typeof init === 'string') {
+      const rgb = hexToRgb(init);
+      this.hsl = rgbToHsl(rgb);
+    } else {
+      this.hsl = [init.h, init.s, init.l];
+    }
+    this.alpha = alpha;
+  }
+
+  // Subclasses should implement this to teach the base class how to create a
+  // new object of the subclass type.
+  abstract create(hsl: ColorTuple|HSL, alpha?: number): T;
+
+  lighten(amount: number, max = LIGHTNESS_MAX): T {
+    const [h, s, l] = this.hsl;
+    const newLightness = clamp(l + amount, LIGHTNESS_MIN, max);
+    return this.create([h, s, newLightness], this.alpha);
+  }
+
+  darken(amount: number, min = LIGHTNESS_MIN): T {
+    const [h, s, l] = this.hsl;
+    const newLightness = clamp(l - amount, min, LIGHTNESS_MAX);
+    return this.create([h, s, newLightness], this.alpha);
+  }
+
+  saturate(amount: number, max = SATURATION_MAX): T {
+    const [h, s, l] = this.hsl;
+    const newSaturation = clamp(s + amount, SATURATION_MIN, max);
+    return this.create([h, newSaturation, l], this.alpha);
+  }
+
+  desaturate(amount: number, min = SATURATION_MIN): T {
+    const [h, s, l] = this.hsl;
+    const newSaturation = clamp(s - amount, min, SATURATION_MAX);
+    return this.create([h, newSaturation, l], this.alpha);
+  }
+
+  setHSL(hsl: Partial<HSL>): T {
+    const [h, s, l] = this.hsl;
+    return this.create({h, s, l, ...hsl}, this.alpha);
+  }
+
+  setAlpha(alpha: number|undefined): T {
+    return this.create(this.hsl, alpha);
+  }
+}
+
+// Describes a color defined in standard HSL color space.
+export class HSLColor extends HSLColorBase<HSLColor> implements Color {
+  readonly cssString: string;
+  readonly perceivedBrightness: number;
+
+  // Values are in the range:
+  // Hue:        0-360
+  // Saturation: 0-100
+  // Lightness:  0-100
+  // Alpha:      0-1
+  constructor(hsl: ColorTuple|HSL|string, alpha?: number) {
+    super(hsl, alpha);
+
+    const [r, g, b] = hslToRgb(...this.hsl);
+
+    this.perceivedBrightness = perceivedBrightness(r, g, b);
+
+    if (this.alpha === undefined) {
+      this.cssString = `rgb(${r} ${g} ${b})`;
+    } else {
+      this.cssString = `rgb(${r} ${g} ${b} / ${this.alpha})`;
+    }
+  }
+
+  create(values: ColorTuple|HSL, alpha?: number|undefined): HSLColor {
+    return new HSLColor(values, alpha);
+  }
+}
+
+// Describes a color defined in HSLuv color space.
+// See: https://www.hsluv.org/
+export class HSLuvColor extends HSLColorBase<HSLuvColor> implements Color {
+  readonly cssString: string;
+  readonly perceivedBrightness: number;
+
+  constructor(hsl: ColorTuple|HSL, alpha?: number) {
+    super(hsl, alpha);
+
+    const rgb = hsluvToRgb(this.hsl);
+    const r = Math.floor(rgb[0] * 255);
+    const g = Math.floor(rgb[1] * 255);
+    const b = Math.floor(rgb[2] * 255);
+
+    this.perceivedBrightness = perceivedBrightness(r, g, b);
+
+    if (this.alpha === undefined) {
+      this.cssString = `rgb(${r} ${g} ${b})`;
+    } else {
+      this.cssString = `rgb(${r} ${g} ${b} / ${this.alpha})`;
+    }
+  }
+
+  create(raw: ColorTuple|HSL, alpha?: number|undefined): HSLuvColor {
+    return new HSLuvColor(raw, alpha);
+  }
+}
+
+// Hue: 0-360
+// Saturation: 0-100
+// Lightness: 0-100
+// RGB: 0-255
+export function hslToRgb(h: number, s: number, l: number): ColorTuple {
+  h = h;
+  s = s / SATURATION_MAX;
+  l = l / LIGHTNESS_MAX;
+
+  const c = (1 - Math.abs(2 * l - 1)) * s;
+  const x = c * (1 - Math.abs((h / 60) % 2 - 1));
+  const m = l - c / 2;
+
+  let [r, g, b] = [0, 0, 0];
+
+  if (0 <= h && h < 60) {
+    [r, g, b] = [c, x, 0];
+  } else if (60 <= h && h < 120) {
+    [r, g, b] = [x, c, 0];
+  } else if (120 <= h && h < 180) {
+    [r, g, b] = [0, c, x];
+  } else if (180 <= h && h < 240) {
+    [r, g, b] = [0, x, c];
+  } else if (240 <= h && h < 300) {
+    [r, g, b] = [x, 0, c];
+  } else if (300 <= h && h < 360) {
+    [r, g, b] = [c, 0, x];
+  }
+
+  // Convert to 0-255 range
+  r = Math.round((r + m) * 255);
+  g = Math.round((g + m) * 255);
+  b = Math.round((b + m) * 255);
+
+  return [r, g, b];
+}
+
+export function hexToRgb(hex: string): ColorTuple {
+  // Convert hex to RGB first
+  let r: number = 0;
+  let g: number = 0;
+  let b: number = 0;
+
+  if (hex.length === 4) {
+    r = parseInt(hex[1] + hex[1], 16);
+    g = parseInt(hex[2] + hex[2], 16);
+    b = parseInt(hex[3] + hex[3], 16);
+  } else if (hex.length === 7) {
+    r = parseInt(hex.substring(1, 3), 16);
+    g = parseInt(hex.substring(3, 5), 16);
+    b = parseInt(hex.substring(5, 7), 16);
+  }
+
+  return [r, g, b];
+}
+
+export function rgbToHsl(rgb: ColorTuple): ColorTuple {
+  let [r, g, b] = rgb;
+  r /= 255;
+  g /= 255;
+  b /= 255;
+  const max = Math.max(r, g, b);
+  const min = Math.min(r, g, b);
+  let h: number = (max + min) / 2;
+  let s: number = (max + min) / 2;
+  const l: number = (max + min) / 2;
+
+  if (max === min) {
+    h = s = 0;  // achromatic
+  } else {
+    const d = max - min;
+    s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
+    switch (max) {
+      case r:
+        h = (g - b) / d + (g < b ? 6 : 0);
+        break;
+      case g:
+        h = (b - r) / d + 2;
+        break;
+      case b:
+        h = (r - g) / d + 4;
+        break;
+    }
+    h /= 6;
+  }
+
+  return [h * 360, s * 100, l * 100];
+}
+
+// Return the perceived brightness of a color using a weighted average of the
+// r, g and b channels based on human perception.
+function perceivedBrightness(r: number, g: number, b: number): number {
+  // YIQ calculation from https://24ways.org/2010/calculating-color-contrast
+  return ((r * 299) + (g * 587) + (b * 114)) / 1000;
+}
+
+// Comparison function used for sorting colors.
+export function colorCompare(a: Color, b: Color): number {
+  return a.cssString.localeCompare(b.cssString);
+}
diff --git a/ui/src/common/color_unittest.ts b/ui/src/common/color_unittest.ts
new file mode 100644
index 0000000..4973308
--- /dev/null
+++ b/ui/src/common/color_unittest.ts
@@ -0,0 +1,113 @@
+// 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 {HSLColor, hslToRgb, HSLuvColor} from './color';
+
+describe('HSLColor', () => {
+  const col = new HSLColor({h: 123, s: 66, l: 45});
+
+  test('cssString', () => {
+    expect(col.cssString).toBe('rgb(39 190 47)');
+    expect(new HSLColor({h: 0, s: 0, l: 0}).cssString).toBe('rgb(0 0 0)');
+    expect(new HSLColor({h: 0, s: 100, l: 100}).cssString)
+        .toBe('rgb(255 255 255)');
+    expect(new HSLColor({h: 90, s: 25, l: 55}).cssString)
+        .toBe('rgb(140 169 112)');
+    expect(new HSLColor({h: 180, s: 80, l: 40}, 0.7).cssString)
+        .toBe('rgb(20 184 184 / 0.7)');
+  });
+
+  test('lighten', () => {
+    expect(col.lighten(20).hsl).toEqual([123, 66, 65]);
+    expect(col.lighten(100).hsl).toEqual([123, 66, 100]);
+    expect(col.lighten(-100).hsl).toEqual([123, 66, 0]);
+  });
+
+  test('saturate', () => {
+    expect(col.saturate(20).hsl).toEqual([123, 86, 45]);
+    expect(col.saturate(100).hsl).toEqual([123, 100, 45]);
+    expect(col.saturate(-100).hsl).toEqual([123, 0, 45]);
+  });
+
+  test('setAlpha', () => {
+    expect(col.setAlpha(.7).alpha).toEqual(.7);
+    expect(col.setAlpha(undefined).alpha).toEqual(undefined);
+  });
+
+  test('perceivedBrightness', () => {
+    // Test a few obviously light/dark colours.
+    expect(new HSLColor({h: 0, s: 0, l: 0}).perceivedBrightness)
+        .toBeLessThan(128);
+    expect(new HSLColor({h: 0, s: 0, l: 100}).perceivedBrightness)
+        .toBeGreaterThan(128);
+
+    expect(new HSLColor({h: 0, s: 0, l: 40}).perceivedBrightness)
+        .toBeLessThan(128);
+    expect(new HSLColor({h: 0, s: 0, l: 60}).perceivedBrightness)
+        .toBeGreaterThan(128);
+  });
+});
+
+describe('HSLuvColor', () => {
+  const col = new HSLuvColor({h: 123, s: 66, l: 45});
+
+  test('cssString', () => {
+    expect(col.cssString).toBe('rgb(69 117 58)');
+    expect(new HSLColor({h: 0, s: 0, l: 0}).cssString).toBe('rgb(0 0 0)');
+    expect(new HSLColor({h: 0, s: 100, l: 100}).cssString)
+        .toBe('rgb(255 255 255)');
+    expect(new HSLuvColor({h: 90, s: 25, l: 55}).cssString)
+        .toBe('rgb(131 133 112)');
+    expect(new HSLuvColor({h: 240, s: 100, l: 100}, 0.3).cssString)
+        .toBe('rgb(254 255 255 / 0.3)');
+  });
+
+  test('lighten', () => {
+    expect(col.lighten(20).hsl).toEqual([123, 66, 65]);
+    expect(col.lighten(100).hsl).toEqual([123, 66, 100]);
+    expect(col.lighten(-100).hsl).toEqual([123, 66, 0]);
+  });
+
+  test('saturate', () => {
+    expect(col.saturate(20).hsl).toEqual([123, 86, 45]);
+    expect(col.saturate(100).hsl).toEqual([123, 100, 45]);
+    expect(col.saturate(-100).hsl).toEqual([123, 0, 45]);
+  });
+
+  test('setAlpha', () => {
+    expect(col.setAlpha(.7).alpha).toEqual(.7);
+    expect(col.setAlpha(undefined).alpha).toEqual(undefined);
+  });
+
+  test('perceivedBrightness', () => {
+    // Test a few obviously light/dark colours.
+    expect(new HSLuvColor({h: 0, s: 0, l: 0}).perceivedBrightness)
+        .toBeLessThan(128);
+    expect(new HSLuvColor({h: 0, s: 0, l: 100}).perceivedBrightness)
+        .toBeGreaterThan(128);
+
+    expect(new HSLuvColor({h: 0, s: 0, l: 40}).perceivedBrightness)
+        .toBeLessThan(128);
+    expect(new HSLuvColor({h: 0, s: 0, l: 60}).perceivedBrightness)
+        .toBeGreaterThan(128);
+  });
+});
+
+test('hslToRGB', () => {
+  // Pick a few well-known conversions to check we're in the right ballpark.
+  expect(hslToRgb(0, 0, 0)).toEqual([0, 0, 0]);
+  expect(hslToRgb(0, 100, 50)).toEqual([255, 0, 0]);
+  expect(hslToRgb(120, 100, 50)).toEqual([0, 255, 0]);
+  expect(hslToRgb(240, 100, 50)).toEqual([0, 0, 255]);
+});
diff --git a/ui/src/common/colorizer.ts b/ui/src/common/colorizer.ts
index 3c20520..584deb5 100644
--- a/ui/src/common/colorizer.ts
+++ b/ui/src/common/colorizer.ts
@@ -13,91 +13,161 @@
 // limitations under the License.
 
 import {hsl} from 'color-convert';
-import {hsluvToRgb} from 'hsluv';
 
-import {clamp} from '../base/math_utils';
 import {hash} from '../common/hash';
-import {cachedHsluvToHex} from '../frontend/hsluv_cache';
+import {featureFlags} from '../core/feature_flags';
 
-export interface Color {
-  h: number;
-  s: number;
-  l: number;
-  a?: number;
+import {Color, HSLColor, HSLuvColor} from './color';
+
+// 128 would provide equal weighting between dark and light text, but we want to
+// slightly prefer light text for stylistic reasons.
+// 140 means we must be brighter on average before switching to dark text.
+const PERCEIVED_BRIGHTNESS_LIMIT = 140;
+
+// This file defines some opinionated colors and provides functions to access
+// random but predictable colors based on a seed, as well as standardized ways
+// to access colors for core objects such as slices and thread states.
+
+// We have, over the years, accumulated a number of different color palettes
+// which are used for different parts of the UI.
+// It would be nice to combine these into a single palette in the future, but
+// changing colors is difficult especially for slice colors, as folks get used
+// to certain slices being certain colors and are resistant to change.
+// However we do it, we should make it possible for folks to switch back the a
+// previous palette, or define their own.
+
+const USE_CONSISTENT_COLORS = featureFlags.register({
+  id: 'useConsistentColors',
+  name: 'Use common color palette for timeline elements',
+  description: 'Use the same color palette for all timeline elements.',
+  defaultValue: false,
+});
+
+// |ColorScheme| defines a collection of colors which can be used for various UI
+// elements. In the future we would expand this interface to include light and
+// dark variants.
+export interface ColorScheme {
+  // The base color to be used for the bulk of the element.
+  readonly base: Color;
+
+  // A variant on the base color, commonly used for highlighting.
+  readonly variant: Color;
+
+  // Grayed out color to represent a disabled state.
+  readonly disabled: Color;
+
+  // Appropriate colors for text to be displayed on top of the above colors.
+  readonly textBase: Color;
+  readonly textVariant: Color;
+  readonly textDisabled: Color;
 }
 
-const MD_PALETTE: Color[] = [
-  {h: 4, s: 90, l: 58},
-  {h: 340, s: 82, l: 52},
-  {h: 291, s: 64, l: 42},
-  {h: 262, s: 52, l: 47},
-  {h: 231, s: 48, l: 48},
-  {h: 207, s: 90, l: 54},
-  {h: 199, s: 98, l: 48},
-  {h: 187, s: 100, l: 42},
-  {h: 174, s: 100, l: 29},
-  {h: 122, s: 39, l: 49},
-  {h: 88, s: 50, l: 53},
-  {h: 66, s: 70, l: 54},
-  {h: 45, s: 100, l: 51},
-  {h: 36, s: 100, l: 50},
-  {h: 14, s: 100, l: 57},
-  {h: 16, s: 25, l: 38},
-  {h: 200, s: 18, l: 46},
-  {h: 54, s: 100, l: 62},
+const MD_PALETTE_RAW: Color[] = [
+  new HSLColor({h: 4, s: 90, l: 58}),
+  new HSLColor({h: 340, s: 82, l: 52}),
+  new HSLColor({h: 291, s: 64, l: 42}),
+  new HSLColor({h: 262, s: 52, l: 47}),
+  new HSLColor({h: 231, s: 48, l: 48}),
+  new HSLColor({h: 207, s: 90, l: 54}),
+  new HSLColor({h: 199, s: 98, l: 48}),
+  new HSLColor({h: 187, s: 100, l: 42}),
+  new HSLColor({h: 174, s: 100, l: 29}),
+  new HSLColor({h: 122, s: 39, l: 49}),
+  new HSLColor({h: 88, s: 50, l: 53}),
+  new HSLColor({h: 66, s: 70, l: 54}),
+  new HSLColor({h: 45, s: 100, l: 51}),
+  new HSLColor({h: 36, s: 100, l: 50}),
+  new HSLColor({h: 14, s: 100, l: 57}),
+  new HSLColor({h: 16, s: 25, l: 38}),
+  new HSLColor({h: 200, s: 18, l: 46}),
+  new HSLColor({h: 54, s: 100, l: 62}),
 ];
 
-export const GRAY_COLOR: Color = {
-  h: 0,
-  s: 0,
-  l: 62,
-};
+const WHITE_COLOR = new HSLColor([0, 0, 100]);
+const BLACK_COLOR = new HSLColor([0, 0, 0]);
+const GRAY_COLOR = new HSLColor([0, 0, 90]);
+
+const MD_PALETTE: ColorScheme[] = MD_PALETTE_RAW.map((color): ColorScheme => {
+  const base = color.lighten(10, 60).desaturate(20);
+  const variant = base.lighten(30, 80).desaturate(20);
+
+  return {
+    base,
+    variant,
+    disabled: GRAY_COLOR,
+    textBase: WHITE_COLOR,  // White text suits MD colors quite well
+    textVariant: WHITE_COLOR,
+    textDisabled: WHITE_COLOR,  // Low contrast is on purpose
+  };
+});
+
+// Create a color scheme based on a single color, which defines the variant
+// color as a slightly darker and more saturated version of the base color.
+export function makeColorScheme(base: Color, variant?: Color): ColorScheme {
+  variant = variant ?? base.darken(15).saturate(15);
+
+  return {
+    base,
+    variant,
+    disabled: GRAY_COLOR,
+    textBase: base.perceivedBrightness >= PERCEIVED_BRIGHTNESS_LIMIT ?
+        BLACK_COLOR :
+        WHITE_COLOR,
+    textVariant: variant.perceivedBrightness >= PERCEIVED_BRIGHTNESS_LIMIT ?
+        BLACK_COLOR :
+        WHITE_COLOR,
+    textDisabled: WHITE_COLOR,  // Low contrast is on purpose
+  };
+}
+
+const GRAY = makeColorScheme(new HSLColor([0, 0, 62]));
+const DESAT_RED = makeColorScheme(new HSLColor([3, 30, 49]));
+const DARK_GREEN = makeColorScheme(new HSLColor([120, 44, 34]));
+const LIME_GREEN = makeColorScheme(new HSLColor([75, 55, 47]));
+const TRANSPARENT_WHITE = makeColorScheme(new HSLColor([0, 1, 97], 0.55));
+const ORANGE = makeColorScheme(new HSLColor([36, 100, 50]));
+const INDIGO = makeColorScheme(new HSLColor([231, 48, 48]));
 
 // A piece of wisdom from a long forgotten blog post: "Don't make
 // colors you want to change something normal like grey."
-export const UNEXPECTED_PINK_COLOR: Color = {
-  h: 330,
-  s: 1.0,
-  l: 0.706,
-};
+export const UNEXPECTED_PINK = makeColorScheme(new HSLColor([330, 100, 70]));
 
-export function hueForCpu(cpu: number): number {
-  return (128 + (32 * cpu)) % 256;
+// Selects a predictable color scheme from a palette of material design colors,
+// based on a string seed.
+function materialColorScheme(seed: string): ColorScheme {
+  const colorIdx = hash(seed, MD_PALETTE.length);
+  return MD_PALETTE[colorIdx];
 }
 
-const DESAT_RED: Color = {
-  h: 3,
-  s: 30,
-  l: 49,
-};
-const DARK_GREEN: Color = {
-  h: 120,
-  s: 44,
-  l: 34,
-};
-const LIME_GREEN: Color = {
-  h: 75,
-  s: 55,
-  l: 47,
-};
-const TRANSPARENT_WHITE: Color = {
-  h: 0,
-  s: 1,
-  l: 97,
-  a: 0.55,
-};
-const ORANGE: Color = {
-  h: 36,
-  s: 100,
-  l: 50,
-};
-const INDIGO: Color = {
-  h: 231,
-  s: 48,
-  l: 48,
-};
+const proceduralColorCache = new Map<string, ColorScheme>();
 
-export function colorForState(state: string): Readonly<Color> {
+// Procedurally generates a predictable color scheme based on a string seed.
+function proceduralColorScheme(seed: string): ColorScheme {
+  const colorScheme = proceduralColorCache.get(seed);
+  if (colorScheme) {
+    return colorScheme;
+  } else {
+    const hue = hash(seed, 360);
+    // Saturation 100 would give the most differentiation between colors, but
+    // it's garish.
+    const saturation = 80;
+
+    // Prefer using HSLuv, not the browser's built-in vanilla HSL handling. This
+    // is because this function chooses hue/lightness uniform at random, but HSL
+    // is not perceptually uniform.
+    // See https://www.boronine.com/2012/03/26/Color-Spaces-for-Human-Beings/.
+    const base =
+        new HSLuvColor({h: hue, s: saturation, l: hash(seed + 'x', 40) + 40});
+    const variant = new HSLuvColor({h: hue, s: saturation, l: 30});
+    const colorScheme = makeColorScheme(base, variant);
+
+    proceduralColorCache.set(seed, colorScheme);
+
+    return colorScheme;
+  }
+}
+
+export function colorForState(state: string): ColorScheme {
   if (state === 'Running') {
     return DARK_GREEN;
   } else if (state.startsWith('Runnable')) {
@@ -113,162 +183,59 @@
   return INDIGO;
 }
 
-export function textColorForState(stateCode: string): string {
-  const background = colorForState(stateCode);
-  return background.l > 80 ? '#404040' : '#fff';
+export function colorForTid(tid: number): ColorScheme {
+  return materialColorScheme(tid.toString());
 }
 
-export function colorForString(identifier: string): Color {
-  const colorIdx = hash(identifier, MD_PALETTE.length);
-  return Object.assign({}, MD_PALETTE[colorIdx]);
-}
-
-export function colorForTid(tid: number): Color {
-  return colorForString(tid.toString());
-}
-
-export function colorForThread(thread?: {pid?: number, tid: number}): Color {
+export function colorForThread(thread?: {pid?: number, tid: number}):
+    ColorScheme {
   if (thread === undefined) {
-    return Object.assign({}, GRAY_COLOR);
+    return GRAY;
   }
   const tid = thread.pid ? thread.pid : thread.tid;
   return colorForTid(tid);
 }
 
-// 40 different random hues 9 degrees apart.
+export function colorForCpu(cpu: number): Color {
+  if (USE_CONSISTENT_COLORS.get()) {
+    return materialColorScheme(cpu.toString()).base;
+  } else {
+    const hue = (128 + (32 * cpu)) % 256;
+    return new HSLColor({h: hue, s: 50, l: 50});
+  }
+}
+
 export function randomColor(): string {
-  const hue = Math.floor(Math.random() * 40) * 9;
-  return '#' + hsl.hex([hue, 90, 30]);
+  const rand = Math.random();
+  if (USE_CONSISTENT_COLORS.get()) {
+    return materialColorScheme(rand.toString()).base.cssString;
+  } else {
+    // 40 different random hues 9 degrees apart.
+    const hue = Math.floor(rand * 40) * 9;
+    return '#' + hsl.hex([hue, 90, 30]);
+  }
 }
 
-// Chooses a color uniform at random based on hash(sliceName).  Returns [hue,
-// saturation, lightness].
-//
-// Prefer converting this to an RGB color using hsluv, not the browser's
-// built-in vanilla HSL handling.  This is because this function chooses
-// hue/lightness uniform at random, but HSL is not perceptually uniform.  See
-// https://www.boronine.com/2012/03/26/Color-Spaces-for-Human-Beings/.
-//
-// If isSelected, the color will be particularly dark, making it stand out.
-export function hslForSlice(
-    sliceName: string, isSelected: boolean|null): [number, number, number] {
-  const hue = hash(sliceName, 360);
-  // Saturation 100 would give the most differentiation between colors, but it's
-  // garish.
-  const saturation = 80;
-  const lightness = isSelected ? 30 : hash(sliceName + 'x', 40) + 40;
-  return [hue, saturation, lightness];
-}
-
-// Lightens the color for thread slices to represent wall time.
-export function colorForThreadIdleSlice(
-    hue: number,
-    saturation: number,
-    lightness: number,
-    isSelected: boolean|null): string {
-  // Increase lightness by 80% when selected and 40% otherwise,
-  // without exceeding 88.
-  let newLightness = isSelected ? lightness * 1.8 : lightness * 1.4;
-  newLightness = Math.min(newLightness, 88);
-  return cachedHsluvToHex(hue, saturation, newLightness);
-}
-
-export function colorCompare(x: Color, y: Color): number {
-  return (x.h - y.h) || (x.s - y.s) || (x.l - y.l);
-}
-
-// Return true if two colors have the same value.
-export function colorsEqual(a: Color, b: Color): boolean {
-  return a.h === b.h && a.s === b.s && a.l === b.l && a.a === b.a;
-}
-
-export function getColorForSlice(
-    sliceName: string, hasFocus: boolean|null): Color {
+export function getColorForSlice(sliceName: string): ColorScheme {
   const name = sliceName.replace(/( )?\d+/g, '');
-  const [hue, saturation, lightness] = hslForSlice(name, hasFocus);
-
-  return {
-    h: hue,
-    s: saturation,
-    l: lightness,
-  };
+  if (USE_CONSISTENT_COLORS.get()) {
+    return materialColorScheme(name);
+  } else {
+    return proceduralColorScheme(name);
+  }
 }
 
-const LIGHTNESS_MAX = 100;
-const LIGHTNESS_MIN = 0;
-
-// Lighten color by a percentage.
-export function colorLighten(color: Color, amount: number): Color {
-  return {
-    ...color,
-    l: clamp(color.l + amount, LIGHTNESS_MIN, LIGHTNESS_MAX),
-  };
+export function colorForFtrace(name: string): ColorScheme {
+  return materialColorScheme(name);
 }
 
-// Darken color by a percentage.
-export function colorDarken(color: Color, amount: number): Color {
-  return colorLighten(color, -amount);
-}
-
-const SATURATION_MAX = 100;
-const SATURATION_MIN = 0;
-
-// Saturate color by a percentage.
-export function colorSaturate(color: Color, amount: number): Color {
-  return {
-    ...color,
-    s: clamp(color.s + amount, SATURATION_MIN, SATURATION_MAX),
-  };
-}
-
-// Desaturate color by a percentage.
-export function colorDesaturate(color: Color, amount: number): Color {
-  return colorSaturate(color, -amount);
-}
-
-// Convert color to RGB values in the range 0-255
-export function colorToRGB(color: Color): [number, number, number] {
-  const h = color.h;
-  const s = color.s / SATURATION_MAX;
-  const l = color.l / LIGHTNESS_MAX;
-
-  const c = (1 - Math.abs(2 * l - 1)) * s;
-  const x = c * (1 - Math.abs((h / 60) % 2 - 1));
-  const m = l - c / 2;
-
-  let [r, g, b] = [0, 0, 0];
-
-  if (0 <= h && h < 60) {
-    [r, g, b] = [c, x, 0];
-  } else if (60 <= h && h < 120) {
-    [r, g, b] = [x, c, 0];
-  } else if (120 <= h && h < 180) {
-    [r, g, b] = [0, c, x];
-  } else if (180 <= h && h < 240) {
-    [r, g, b] = [0, x, c];
-  } else if (240 <= h && h < 300) {
-    [r, g, b] = [x, 0, c];
-  } else if (300 <= h && h < 360) {
-    [r, g, b] = [c, 0, x];
+export function colorForSample(callsiteId: number, isHovered: boolean): string {
+  let colorScheme;
+  if (USE_CONSISTENT_COLORS.get()) {
+    colorScheme = materialColorScheme(String(callsiteId));
+  } else {
+    colorScheme = proceduralColorScheme(String(callsiteId));
   }
 
-  // Convert to 0-255 range
-  r = Math.round((r + m) * 255);
-  g = Math.round((g + m) * 255);
-  b = Math.round((b + m) * 255);
-
-  return [r, g, b];
-}
-
-// Get whether a color should be considered "light" based on its perceived
-// brightness.
-export function colorIsLight(color: Color): boolean {
-  // YIQ calculation from https://24ways.org/2010/calculating-color-contrast
-  const [r, g, b] = hsluvToRgb([color.h, color.s, color.l]);
-  const yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000;
-  return (yiq >= 128);
-}
-
-export function colorIsDark(color: Color): boolean {
-  return !colorIsLight(color);
+  return isHovered ? colorScheme.variant.cssString : colorScheme.base.cssString;
 }
diff --git a/ui/src/common/colorizer_unittest.ts b/ui/src/common/colorizer_unittest.ts
index bfd0eb1..ffc931e 100644
--- a/ui/src/common/colorizer_unittest.ts
+++ b/ui/src/common/colorizer_unittest.ts
@@ -13,15 +13,8 @@
 // limitations under the License.
 
 import {
-  Color,
-  colorCompare,
+  colorForCpu,
   colorForThread,
-  colorIsLight,
-  colorLighten,
-  colorSaturate,
-  colorsEqual,
-  colorToRGB,
-  hueForCpu,
 } from './colorizer';
 
 const PROCESS_A_THREAD_A = {
@@ -61,66 +54,12 @@
   expect(colorUnkA).toEqual(colorUnkB);
 });
 
-test('it copies colors', () => {
+test('it doesn\'t copy colors', () => {
   const a = colorForThread(PROCESS_A_THREAD_A);
   const b = colorForThread(PROCESS_A_THREAD_A);
-  expect(a === b).toEqual(false);
+  expect(a).toBe(b);
 });
 
 test('it gives different cpus different hues', () => {
-  expect(hueForCpu(0)).not.toEqual(hueForCpu(1));
-});
-
-test('colorCompare', () => {
-  const col: Color = {h: 123, s: 66, l: 45};
-
-  expect(colorCompare({...col}, col)).toBe(0);
-
-  expect(colorCompare({...col, h: 34}, col)).toBeLessThan(0);
-  expect(colorCompare({...col, h: 156}, col)).toBeGreaterThan(0);
-
-  expect(colorCompare({...col, s: 22}, col)).toBeLessThan(0);
-  expect(colorCompare({...col, s: 100}, col)).toBeGreaterThan(0);
-
-  expect(colorCompare({...col, l: 22}, col)).toBeLessThan(0);
-  expect(colorCompare({...col, l: 76}, col)).toBeGreaterThan(0);
-});
-
-test('colorsEqual', () => {
-  const col: Color = {h: 123, s: 66, l: 45};
-  expect(colorsEqual(col, {h: 123, s: 66, l: 45})).toBeTruthy();
-  expect(colorsEqual(col, {h: 86, s: 66, l: 45})).toBeFalsy();
-  expect(colorsEqual(col, {h: 123, s: 43, l: 45})).toBeFalsy();
-  expect(colorsEqual(col, {h: 123, s: 43, l: 78})).toBeFalsy();
-});
-
-test('colorLighten', () => {
-  const col: Color = {h: 123, s: 66, l: 45};
-  expect(colorLighten(col, 20)).toEqual({...col, l: 65});
-  expect(colorLighten(col, 100)).toEqual({...col, l: 100});
-  expect(colorLighten(col, -100)).toEqual({...col, l: 0});
-});
-
-test('colorSaturate', () => {
-  const col: Color = {h: 123, s: 66, l: 45};
-  expect(colorSaturate(col, 20)).toEqual({...col, s: 86});
-  expect(colorSaturate(col, 100)).toEqual({...col, s: 100});
-  expect(colorSaturate(col, -100)).toEqual({...col, s: 0});
-});
-
-test('colorToRGB', () => {
-  // Pick a few well-known conversions to check we're in the right ballpark.
-  expect(colorToRGB({h: 0, s: 0, l: 0})).toEqual([0, 0, 0]);
-  expect(colorToRGB({h: 0, s: 100, l: 50})).toEqual([255, 0, 0]);
-  expect(colorToRGB({h: 120, s: 100, l: 50})).toEqual([0, 255, 0]);
-  expect(colorToRGB({h: 240, s: 100, l: 50})).toEqual([0, 0, 255]);
-});
-
-test('lightness calculations', () => {
-  // Pick a few obvious light/dark colours to check we're in the right ballpark.
-  expect(colorIsLight({h: 0, s: 0, l: 0})).toBeFalsy();
-  expect(colorIsLight({h: 0, s: 0, l: 100})).toBeTruthy();
-
-  expect(colorIsLight({h: 0, s: 0, l: 49})).toBeFalsy();
-  expect(colorIsLight({h: 0, s: 0, l: 51})).toBeTruthy();
+  expect(colorForCpu(0)).not.toEqual(colorForCpu(1));
 });
diff --git a/ui/src/common/track_adapter.ts b/ui/src/common/track_adapter.ts
index 8dbb089..9afa02b 100644
--- a/ui/src/common/track_adapter.ts
+++ b/ui/src/common/track_adapter.ts
@@ -16,12 +16,12 @@
 import {v4 as uuidv4} from 'uuid';
 
 import {assertExists} from '../base/logging';
-import {duration, Span, time} from '../base/time';
-import {PxSpan, TimeScale} from '../frontend/time_scale';
-import {NewTrackArgs, SliceRect} from '../frontend/track';
+import {duration, time} from '../base/time';
+import {NewTrackArgs} from '../frontend/track';
+import {SliceRect} from '../public';
 import {EngineProxy} from '../trace_processor/engine';
 
-import {BasicAsyncTrack} from './basic_async_track';
+import {TrackHelperLEGACY} from './track_helper';
 
 export {Store} from '../frontend/store';
 export {EngineProxy} from '../trace_processor/engine';
@@ -37,7 +37,7 @@
 // This is an adapter to convert old style controller based tracks to new style
 // tracks.
 export class TrackWithControllerAdapter<Config, Data> extends
-    BasicAsyncTrack<Data> {
+    TrackHelperLEGACY<Data> {
   private track: TrackAdapter<Config, Data>;
   private controller: TrackControllerAdapter<Config, Data>;
   private isSetup = false;
@@ -63,12 +63,8 @@
     super.onDestroy();
   }
 
-  getSliceRect(
-      visibleTimeScale: TimeScale, visibleWindow: Span<time, bigint>,
-      windowSpan: PxSpan, tStart: time, tEnd: time, depth: number): SliceRect
-      |undefined {
-    return this.track.getSliceRect(
-        visibleTimeScale, visibleWindow, windowSpan, tStart, tEnd, depth);
+  getSliceRect(tStart: time, tEnd: time, depth: number): SliceRect|undefined {
+    return this.track.getSliceRect(tStart, tEnd, depth);
   }
 
   getHeight(): number {
@@ -139,10 +135,8 @@
 
   abstract renderCanvas(ctx: CanvasRenderingContext2D): void;
 
-  getSliceRect(
-      _visibleTimeScale: TimeScale, _visibleWindow: Span<time, bigint>,
-      _windowSpan: PxSpan, _tStart: time, _tEnd: time,
-      _depth: number): SliceRect|undefined {
+  getSliceRect(_tStart: time, _tEnd: time, _depth: number): SliceRect
+      |undefined {
     return undefined;
   }
 
diff --git a/ui/src/common/basic_async_track.ts b/ui/src/common/track_helper.ts
similarity index 76%
rename from ui/src/common/basic_async_track.ts
rename to ui/src/common/track_helper.ts
index 41dfe3d..f634627 100644
--- a/ui/src/common/basic_async_track.ts
+++ b/ui/src/common/track_helper.ts
@@ -14,12 +14,10 @@
 
 import m from 'mithril';
 
-import {duration, Span, Time, time} from '../base/time';
+import {duration, Time, time} from '../base/time';
 import {raf} from '../core/raf_scheduler';
 import {globals} from '../frontend/globals';
-import {PxSpan, TimeScale} from '../frontend/time_scale';
-import {SliceRect} from '../frontend/track';
-import {Track, TrackContext} from '../public';
+import {SliceRect, Track, TrackContext} from '../public';
 
 import {TrackData} from './track_data';
 
@@ -34,12 +32,22 @@
   STR_NULL,
 } from '../trace_processor/query_result';
 
-// This shim track provides the base for async style tracks implementing the new
-// plugin track interface.
-// This provides the logic to perform data reloads at appropriate times as the
-// window is panned and zoomed about.
-// The extending class need only define renderCanvas() and onBoundsChange().
-export abstract class BasicAsyncTrack<Data> implements Track {
+// A helper class which provides a base track implementation for tracks which
+// load their content asynchronously from the trace.
+//
+// Tracks extending this base class need only define |renderCanvas()| and
+// |onBoundsChange()|. This helper provides sensible default implementations for
+// all the |Track| interface methods which subclasses may also choose to
+// override if necessary.
+//
+// This helper provides the logic to call |onBoundsChange()| only when more data
+// is needed as the visible window is panned and zoomed about, and includes an
+// FSM to ensure onBoundsChange is not re-entered, and that the track doesn't
+// render stale data.
+//
+// Note: This class is deprecated and should not be used for new tracks. Use
+// |BaseSliceTrack| instead.
+export abstract class TrackHelperLEGACY<Data> implements Track {
   private requestingData = false;
   private queuedRequest = false;
   private currentState?: TrackData;
@@ -57,10 +65,8 @@
   // only for track types that support slices e.g. chrome_slice, async_slices
   // tStart - slice start time in seconds, tEnd - slice end time in seconds,
   // depth - slice depth
-  getSliceRect(
-      _visibleTimeScale: TimeScale, _visibleWindow: Span<time, duration>,
-      _windowSpan: PxSpan, _tStart: time, _tEnd: time,
-      _depth: number): SliceRect|undefined {
+  getSliceRect(_tStart: time, _tEnd: time, _depth: number): SliceRect
+      |undefined {
     return undefined;
   }
 
diff --git a/ui/src/controller/aggregation/frame_aggregation_controller.ts b/ui/src/controller/aggregation/frame_aggregation_controller.ts
index e51bde9..c45a40c 100644
--- a/ui/src/controller/aggregation/frame_aggregation_controller.ts
+++ b/ui/src/controller/aggregation/frame_aggregation_controller.ts
@@ -17,7 +17,7 @@
 import {Area, Sorting} from '../../common/state';
 import {globals} from '../../frontend/globals';
 import {Engine} from '../../trace_processor/engine';
-import {ACTUAL_FRAMES_SLICE_TRACK_KIND} from '../../tracks/actual_frames';
+import {ACTUAL_FRAMES_SLICE_TRACK_KIND} from '../../tracks/frames';
 
 import {AggregationController} from './aggregation_controller';
 
diff --git a/ui/src/controller/aggregation/slice_aggregation_controller.ts b/ui/src/controller/aggregation/slice_aggregation_controller.ts
index 4de7003..7543110 100644
--- a/ui/src/controller/aggregation/slice_aggregation_controller.ts
+++ b/ui/src/controller/aggregation/slice_aggregation_controller.ts
@@ -17,7 +17,9 @@
 import {Area, Sorting} from '../../common/state';
 import {globals} from '../../frontend/globals';
 import {Engine} from '../../trace_processor/engine';
-import {ASYNC_SLICE_TRACK_KIND} from '../../tracks/async_slices';
+import {
+  ASYNC_SLICE_TRACK_KIND,
+} from '../../tracks/async_slices/async_slice_track';
 import {SLICE_TRACK_KIND} from '../../tracks/chrome_slices';
 
 import {AggregationController} from './aggregation_controller';
diff --git a/ui/src/controller/flow_events_controller.ts b/ui/src/controller/flow_events_controller.ts
index 89d870e..a7ad34c 100644
--- a/ui/src/controller/flow_events_controller.ts
+++ b/ui/src/controller/flow_events_controller.ts
@@ -21,8 +21,8 @@
 import {asSliceSqlId} from '../frontend/sql_types';
 import {Engine} from '../trace_processor/engine';
 import {LONG, NUM, STR_NULL} from '../trace_processor/query_result';
-import {ACTUAL_FRAMES_SLICE_TRACK_KIND} from '../tracks/actual_frames';
 import {SLICE_TRACK_KIND} from '../tracks/chrome_slices';
+import {ACTUAL_FRAMES_SLICE_TRACK_KIND} from '../tracks/frames';
 
 import {Controller} from './controller';
 
diff --git a/ui/src/controller/selection_controller.ts b/ui/src/controller/selection_controller.ts
index b75ad6f..232f446 100644
--- a/ui/src/controller/selection_controller.ts
+++ b/ui/src/controller/selection_controller.ts
@@ -309,14 +309,12 @@
     // UI track id for slice tracks this would be unnecessary.
     let trackKey = '';
     for (const track of Object.values(globals.state.tracks)) {
-      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;
-          }
+      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;
         }
       }
     }
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index c1ce372..4f80835 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -780,39 +780,43 @@
 
   private async loadTimelineOverview(trace: Span<time, duration>) {
     clearOverviewData();
-
     const engine = assertExists<Engine>(this.engine);
     const stepSize = Duration.max(1n, trace.duration / 100n);
-    let hasSchedOverview = false;
-    for (let start = trace.start; start < trace.end;
-         start = Time.add(start, stepSize)) {
-      const progress = start - trace.start;
-      const ratio = Number(progress) / Number(trace.duration);
-      this.updateStatus(
-          'Loading overview ' +
-          `${Math.round(ratio * 100)}%`);
-      const end = Time.add(start, stepSize);
-
-      // Sched overview.
-      const schedResult = await engine.query(
-          `select cast(sum(dur) as float)/${
-              stepSize} as load, cpu from sched ` +
-          `where ts >= ${start} and ts < ${end} and utid != 0 ` +
-          'group by cpu order by cpu');
-      const schedData: {[key: string]: QuantizedLoad} = {};
-      const it = schedResult.iter({load: NUM, cpu: NUM});
-      for (; it.valid(); it.next()) {
-        const load = it.load;
-        const cpu = it.cpu;
-        schedData[cpu] = {start, end, load};
-        hasSchedOverview = true;
-      }
-      publishOverviewData(schedData);
-    }
-
+    const hasSchedSql = 'select ts from sched limit 1';
+    const hasSchedOverview = (await engine.query(hasSchedSql)).numRows() > 0;
     if (hasSchedOverview) {
+      const stepPromises = [];
+      for (let start = trace.start; start < trace.end;
+           start = Time.add(start, stepSize)) {
+        const progress = start - trace.start;
+        const ratio = Number(progress) / Number(trace.duration);
+        this.updateStatus(
+            'Loading overview ' +
+            `${Math.round(ratio * 100)}%`);
+        const end = Time.add(start, stepSize);
+        // The (async() => {})() queues all the 100 async promises in one batch.
+        // Without that, we would wait for each step to be rendered before
+        // kicking off the next one. That would interleave an animation frame
+        // between each step, slowing down significantly the overall process.
+        stepPromises.push((async () => {
+          const schedResult = await engine.query(
+              `select cast(sum(dur) as float)/${
+                  stepSize} as load, cpu from sched ` +
+              `where ts >= ${start} and ts < ${end} and utid != 0 ` +
+              'group by cpu order by cpu');
+          const schedData: {[key: string]: QuantizedLoad} = {};
+          const it = schedResult.iter({load: NUM, cpu: NUM});
+          for (; it.valid(); it.next()) {
+            const load = it.load;
+            const cpu = it.cpu;
+            schedData[cpu] = {start, end, load};
+          }
+          publishOverviewData(schedData);
+        })());
+      }  // for(start = ...)
+      await Promise.all(stepPromises);
       return;
-    }
+    }  // if (hasSchedOverview)
 
     // Slices overview.
     const sliceResult = await engine.query(`select
diff --git a/ui/src/controller/track_decider.ts b/ui/src/controller/track_decider.ts
index c123e78..1df6bee 100644
--- a/ui/src/controller/track_decider.ts
+++ b/ui/src/controller/track_decider.ts
@@ -38,8 +38,7 @@
   STR,
   STR_NULL,
 } from '../trace_processor/query_result';
-import {ACTUAL_FRAMES_SLICE_TRACK_KIND} from '../tracks/actual_frames';
-import {ASYNC_SLICE_TRACK_KIND} from '../tracks/async_slices';
+import {ASYNC_SLICE_TRACK_KIND} from '../tracks/async_slices/async_slice_track';
 import {
   ENABLE_SCROLL_JANK_PLUGIN_V2,
   getScrollJankTracks,
@@ -49,7 +48,10 @@
 } 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 {EXPECTED_FRAMES_SLICE_TRACK_KIND} from '../tracks/expected_frames';
+import {
+  ACTUAL_FRAMES_SLICE_TRACK_KIND,
+  EXPECTED_FRAMES_SLICE_TRACK_KIND,
+} from '../tracks/frames';
 import {NULL_TRACK_URI} from '../tracks/null_track';
 import {
   decideTracks as screenshotDecideTracks,
@@ -60,7 +62,7 @@
   id: 'tracksV2.1',
   name: 'Tracks V2',
   description: 'Show tracks built on top of the Track V2 API.',
-  defaultValue: false,
+  defaultValue: true,
 });
 
 const TRACKS_V2_COMPARE_FLAG = featureFlags.register({
@@ -288,14 +290,25 @@
         }
       }
 
-      const track: AddTrackArgs = {
-        uri: `perfetto.AsyncSlices#${rawName}`,
-        trackSortKey: PrimaryTrackSortKey.ASYNC_SLICE_TRACK,
-        trackGroup,
-        name,
-      };
+      if (showV1()) {
+        const track: AddTrackArgs = {
+          uri: `perfetto.AsyncSlices#${rawName}`,
+          trackSortKey: PrimaryTrackSortKey.ASYNC_SLICE_TRACK,
+          trackGroup,
+          name,
+        };
+        this.tracksToAdd.push(track);
+      }
 
-      this.tracksToAdd.push(track);
+      if (showV2()) {
+        const track: AddTrackArgs = {
+          uri: `perfetto.AsyncSlices#${rawName}.v2`,
+          trackSortKey: PrimaryTrackSortKey.ASYNC_SLICE_TRACK,
+          trackGroup,
+          name,
+        };
+        this.tracksToAdd.push(track);
+      }
     }
   }
 
@@ -849,12 +862,13 @@
       }
 
       const priority = InThreadTrackSortKey.THREAD_SCHEDULING_STATE_TRACK;
+      const name =
+          getTrackName({utid, tid, threadName, kind: THREAD_STATE_TRACK_KIND});
 
       if (showV1()) {
-        const kind = THREAD_STATE_TRACK_KIND;
         this.tracksToAdd.push({
           uri: `perfetto.ThreadState#${upid}.${utid}`,
-          name: getTrackName({utid, tid, threadName, kind}),
+          name,
           trackGroup: uuid,
           trackSortKey: {
             utid,
@@ -866,8 +880,7 @@
       if (showV2()) {
         this.tracksToAdd.push({
           uri: `perfetto.ThreadState#${utid}.v2`,
-          name:
-              getTrackName({utid, tid, threadName, kind: 'ThreadStateTrackV2'}),
+          name,
           trackGroup: uuid,
           trackSortKey: {
             utid,
@@ -1022,12 +1035,24 @@
         processName,
         kind: ASYNC_SLICE_TRACK_KIND,
       });
-      this.tracksToAdd.push({
-        uri: `perfetto.AsyncSlices#process.${pid}${rawTrackIds}`,
-        name,
-        trackSortKey: PrimaryTrackSortKey.ASYNC_SLICE_TRACK,
-        trackGroup: uuid,
-      });
+
+      if (showV1()) {
+        this.tracksToAdd.push({
+          uri: `perfetto.AsyncSlices#process.${pid}${rawTrackIds}`,
+          name,
+          trackSortKey: PrimaryTrackSortKey.ASYNC_SLICE_TRACK,
+          trackGroup: uuid,
+        });
+      }
+
+      if (showV2()) {
+        this.tracksToAdd.push({
+          uri: `perfetto.AsyncSlices#process.${pid}${rawTrackIds}.v2`,
+          name,
+          trackSortKey: PrimaryTrackSortKey.ASYNC_SLICE_TRACK,
+          trackGroup: uuid,
+        });
+      }
     }
   }
 
@@ -1078,12 +1103,24 @@
       const kind = ACTUAL_FRAMES_SLICE_TRACK_KIND;
       const name =
           getTrackName({name: trackName, upid, pid, processName, kind});
-      this.tracksToAdd.push({
-        uri: `perfetto.ActualFrames#${upid}`,
-        name,
-        trackSortKey: PrimaryTrackSortKey.ACTUAL_FRAMES_SLICE_TRACK,
-        trackGroup: uuid,
-      });
+
+      if (showV1()) {
+        this.tracksToAdd.push({
+          uri: `perfetto.ActualFrames#${upid}`,
+          name,
+          trackSortKey: PrimaryTrackSortKey.ACTUAL_FRAMES_SLICE_TRACK,
+          trackGroup: uuid,
+        });
+      }
+
+      if (showV2()) {
+        this.tracksToAdd.push({
+          uri: `perfetto.ActualFrames#${upid}.v2`,
+          name,
+          trackSortKey: PrimaryTrackSortKey.ACTUAL_FRAMES_SLICE_TRACK,
+          trackGroup: uuid,
+        });
+      }
     }
   }
 
@@ -1135,12 +1172,24 @@
       const kind = EXPECTED_FRAMES_SLICE_TRACK_KIND;
       const name =
           getTrackName({name: trackName, upid, pid, processName, kind});
-      this.tracksToAdd.push({
-        uri: `perfetto.ExpectedFrames#${upid}`,
-        name,
-        trackSortKey: PrimaryTrackSortKey.EXPECTED_FRAMES_SLICE_TRACK,
-        trackGroup: uuid,
-      });
+
+      if (showV1()) {
+        this.tracksToAdd.push({
+          uri: `perfetto.ExpectedFrames#${upid}`,
+          name,
+          trackSortKey: PrimaryTrackSortKey.EXPECTED_FRAMES_SLICE_TRACK,
+          trackGroup: uuid,
+        });
+      }
+
+      if (showV2()) {
+        this.tracksToAdd.push({
+          uri: `perfetto.ExpectedFrames#${upid}.v2`,
+          name,
+          trackSortKey: PrimaryTrackSortKey.EXPECTED_FRAMES_SLICE_TRACK,
+          trackGroup: uuid,
+        });
+      }
     }
   }
 
diff --git a/ui/src/frontend/aggregation_panel.ts b/ui/src/frontend/aggregation_panel.ts
index ea90a3f..fed608c 100644
--- a/ui/src/frontend/aggregation_panel.ts
+++ b/ui/src/frontend/aggregation_panel.ts
@@ -20,7 +20,7 @@
   Column,
   ThreadStateExtra,
 } from '../common/aggregation_data';
-import {colorForState, textColorForState} from '../common/colorizer';
+import {colorForState} from '../common/colorizer';
 import {translateState} from '../common/thread_state';
 
 import {globals} from './globals';
@@ -122,15 +122,14 @@
     if (data === undefined) return undefined;
     const states = [];
     for (let i = 0; i < data.states.length; i++) {
-      const color = colorForState(data.states[i]);
-      const textColor = textColorForState(data.states[i]);
+      const colorScheme = colorForState(data.states[i]);
       const width = data.values[i] / data.totalMs * 100;
       states.push(
           m('.state',
             {
               style: {
-                background: `hsl(${color.h},${color.s}%,${color.l}%)`,
-                color: `${textColor}`,
+                background: colorScheme.base.cssString,
+                color: colorScheme.textBase.cssString,
                 width: `${width}%`,
               },
             },
diff --git a/ui/src/frontend/analytics.ts b/ui/src/frontend/analytics.ts
index 8b01523..74c42fc 100644
--- a/ui/src/frontend/analytics.ts
+++ b/ui/src/frontend/analytics.ts
@@ -16,11 +16,25 @@
 import {VERSION} from '../gen/perfetto_version';
 
 import {globals} from './globals';
+import {Router} from './router';
 
 type TraceCategories = 'Trace Actions'|'Record Trace'|'User Actions';
 const ANALYTICS_ID = 'G-BD89KT2P3C';
 const PAGE_TITLE = 'no-page-title';
 
+// Get the referrer from either:
+// - If present: the referrer argument if present
+// - document.referrer
+function getReferrer(): string {
+  const route = Router.parseUrl(window.location.href);
+  const referrer = route.args.referrer;
+  if (referrer) {
+    return referrer;
+  } else {
+    return document.referrer.split('?')[0];
+  }
+}
+
 export function initAnalytics() {
   // Only initialize logging on the official site and on localhost (to catch
   // analytics bugs when testing locally).
@@ -95,14 +109,14 @@
     console.log(
         `GA initialized. route=${route}`,
         `isInternalUser=${globals.isInternalUser}`);
-    // GA's reccomendation for SPAs is to disable automatic page views and
+    // GA's recommendation for SPAs is to disable automatic page views and
     // manually send page_view events. See:
     // https://developers.google.com/analytics/devguides/collection/gtagjs/pages#manual_pageviews
     gtagGlobals.gtag('config', ANALYTICS_ID, {
       allow_google_signals: false,
       anonymize_ip: true,
       page_location: route,
-      referrer: document.referrer.split('?')[0],
+      page_referrer: getReferrer(),
       send_page_view: false,
       page_title: PAGE_TITLE,
       perfetto_is_internal_user: globals.isInternalUser ? '1' : '0',
diff --git a/ui/src/frontend/base_counter_track.ts b/ui/src/frontend/base_counter_track.ts
index e4a6bd7..ae7145d 100644
--- a/ui/src/frontend/base_counter_track.ts
+++ b/ui/src/frontend/base_counter_track.ts
@@ -61,7 +61,7 @@
   yBoundaries: 'strict'|'human_readable';
 }
 
-export abstract class BaseCounterTrack<Config = {}> extends TrackBase<Config> {
+export abstract class BaseCounterTrack extends TrackBase {
   protected readonly tableName: string;
 
   // This is the over-skirted cached bounds:
diff --git a/ui/src/frontend/base_slice_track.ts b/ui/src/frontend/base_slice_track.ts
index cd3ccc3..2c873da 100644
--- a/ui/src/frontend/base_slice_track.ts
+++ b/ui/src/frontend/base_slice_track.ts
@@ -17,7 +17,6 @@
 import {clamp, floatEqual} from '../base/math_utils';
 import {
   duration,
-  Span,
   Time,
   time,
 } from '../base/time';
@@ -27,27 +26,19 @@
   drawIncompleteSlice,
   drawTrackHoverTooltip,
 } from '../common/canvas_utils';
-import {
-  Color,
-  colorCompare,
-  colorDesaturate,
-  colorIsLight,
-  colorLighten,
-  colorsEqual,
-  UNEXPECTED_PINK_COLOR,
-} from '../common/colorizer';
+import {colorCompare} from '../common/color';
+import {UNEXPECTED_PINK} from '../common/colorizer';
 import {Selection, SelectionKind} from '../common/state';
+import {featureFlags} from '../core/feature_flags';
 import {raf} from '../core/raf_scheduler';
+import {Slice, SliceRect} from '../public';
 import {LONG, NUM} from '../trace_processor/query_result';
 
 import {checkerboardExcept} from './checkerboard';
 import {globals} from './globals';
-import {cachedHsluvToHex} from './hsluv_cache';
-import {Slice} from './slice';
 import {DEFAULT_SLICE_LAYOUT, SliceLayout} from './slice_layout';
 import {constraintsToQuerySuffix} from './sql_utils';
-import {PxSpan, TimeScale} from './time_scale';
-import {NewTrackArgs, SliceRect, TrackBase} from './track';
+import {NewTrackArgs, TrackBase} from './track';
 import {BUCKETS_PER_PIXEL, CacheKey, TrackCache} from './track_cache';
 
 // The common class that underpins all tracks drawing slices.
@@ -59,7 +50,15 @@
 const SLICE_MIN_WIDTH_FOR_TEXT_PX = 5;
 const SLICE_MIN_WIDTH_PX = 1 / BUCKETS_PER_PIXEL;
 const CHEVRON_WIDTH_PX = 10;
-const DEFAULT_SLICE_COLOR = UNEXPECTED_PINK_COLOR;
+const DEFAULT_SLICE_COLOR = UNEXPECTED_PINK;
+const INCOMPLETE_SLICE_WIDTH_PX = 20;
+
+export const CROP_INCOMPLETE_SLICE_FLAG = featureFlags.register({
+  id: 'cropIncompleteSlice',
+  name: 'Crop incomplete Slice',
+  description: 'Display incomplete slice in short form',
+  defaultValue: false,
+});
 
 // Exposed and standalone to allow for testing without making this
 // visible to subclasses.
@@ -150,7 +149,7 @@
 //   slices at depth 0..N.
 // If you need temporally overlapping slices, look at AsyncSliceTrack, which
 // merges several tracks into one visual track.
-export const BASE_SLICE_ROW = {
+export const BASE_ROW = {
   id: NUM,     // The slice ID, for selection / lookups.
   ts: LONG,    // Start time in nanoseconds.
   dur: LONG,   // Duration in nanoseconds. -1 = incomplete, 0 = instant.
@@ -161,7 +160,7 @@
   tsqEnd: LONG,  // Quantized |ts+dur|. The end bucket.
 };
 
-export type BaseSliceRow = typeof BASE_SLICE_ROW;
+export type BaseRow = typeof BASE_ROW;
 
 // These properties change @ 60FPS and shouldn't be touched by the subclass.
 // since the Impl doesn't see every frame attempting to reason on them in a
@@ -182,13 +181,11 @@
 // Derived classes can extend this interface to override these types if needed.
 export interface BaseSliceTrackTypes {
   slice: Slice;
-  row: BaseSliceRow;
-  config: {};
+  row: BaseRow;
 }
 
-export abstract class BaseSliceTrack<T extends BaseSliceTrackTypes =
-                                                   BaseSliceTrackTypes> extends
-    TrackBase<T['config']> {
+export abstract class BaseSliceTrack<
+    T extends BaseSliceTrackTypes = BaseSliceTrackTypes> extends TrackBase {
   protected sliceLayout: SliceLayout = {...DEFAULT_SLICE_LAYOUT};
 
   // This is the over-skirted cached bounds:
@@ -260,7 +257,7 @@
   abstract getSqlSource(): string;
 
   getRowSpec(): T['row'] {
-    return BASE_SLICE_ROW;
+    return BASE_ROW;
   }
   onSliceOver(_args: OnSliceOverArgs<T['slice']>): void {}
   onSliceOut(_args: OnSliceOutArgs<T['slice']>): void {}
@@ -287,7 +284,7 @@
     // This is the union of the embedder-defined columns and the base columns
     // we know about (ts, dur, ...).
     const allCols = Object.keys(this.getRowSpec());
-    const baseCols = Object.keys(BASE_SLICE_ROW);
+    const baseCols = Object.keys(BASE_ROW);
     this.extraSqlColumns = allCols.filter((key) => !baseCols.includes(key));
   }
 
@@ -401,8 +398,16 @@
         slice.x -= CHEVRON_WIDTH_PX / 2;
         slice.w = CHEVRON_WIDTH_PX;
       } else if (slice.flags & SLICE_FLAGS_INCOMPLETE) {
-        slice.x = Math.max(slice.x, 0);
-        slice.w = pxEnd - slice.x;
+        let widthPx;
+        if (CROP_INCOMPLETE_SLICE_FLAG.get()) {
+          widthPx = slice.x > 0 ? Math.min(pxEnd, INCOMPLETE_SLICE_WIDTH_PX) :
+              Math.max(0, INCOMPLETE_SLICE_WIDTH_PX + slice.x);
+          slice.x = Math.max(slice.x, 0);
+        } else {
+          slice.x = Math.max(slice.x, 0);
+          widthPx = pxEnd - slice.x;
+        }
+        slice.w = widthPx;
       } else {
         // If the slice is an actual slice, intersect the slice geometry with
         // the visible viewport (this affects only the first and last slice).
@@ -425,20 +430,24 @@
 
     // Second pass: fill slices by color.
     const vizSlicesByColor = vizSlices.slice();
-    vizSlicesByColor.sort((a, b) => colorCompare(a.color, b.color));
+    vizSlicesByColor.sort(
+        (a, b) => colorCompare(a.colorScheme.base, b.colorScheme.base));
     let lastColor = undefined;
     for (const slice of vizSlicesByColor) {
-      if (slice.color !== lastColor) {
-        lastColor = slice.color;
-        const {h, s, l} = slice.color;
-        ctx.fillStyle = cachedHsluvToHex(h, s, l);
+      const color = slice.isHighlighted ? slice.colorScheme.variant.cssString :
+                                          slice.colorScheme.base.cssString;
+      if (color !== lastColor) {
+        lastColor = color;
+        ctx.fillStyle = color;
       }
       const y = padding + slice.depth * (sliceHeight + rowSpacing);
       if (slice.flags & SLICE_FLAGS_INSTANT) {
         this.drawChevron(ctx, slice.x, y, sliceHeight);
       } else if (slice.flags & SLICE_FLAGS_INCOMPLETE) {
-        const w = Math.max(slice.w - 2, 2);
-        drawIncompleteSlice(ctx, slice.x, y, w, sliceHeight);
+        const w = CROP_INCOMPLETE_SLICE_FLAG.get() ? slice.w :
+                                                     Math.max(slice.w - 2, 2);
+        drawIncompleteSlice(
+            ctx, slice.x, y, w, sliceHeight, !CROP_INCOMPLETE_SLICE_FLAG.get());
       } else {
         const w = Math.max(slice.w, SLICE_MIN_WIDTH_PX);
         ctx.fillRect(slice.x, y, w, sliceHeight);
@@ -446,7 +455,7 @@
     }
 
     // Pass 2.5: Draw fillRatio light section.
-    let prevColor: Color|undefined;
+    ctx.fillStyle = `#FFFFFF50`;
     for (const slice of vizSlicesByColor) {
       // Can't draw fill ratio on incomplete or instant slices.
       if (slice.flags & (SLICE_FLAGS_INCOMPLETE | SLICE_FLAGS_INSTANT)) {
@@ -470,17 +479,6 @@
         continue;
       }
 
-      // Lighten and desaturate the slice color
-      const color = getFillRatioLightColor(slice.color);
-
-      // Set color if not set previously
-      // Slices are sorted by color and light tint is a pure function of slice
-      // color so we should be able to re-use colors quite frequently
-      if (prevColor === undefined || !colorsEqual(color, prevColor)) {
-        ctx.fillStyle = cachedHsluvToHex(color.h, color.s, color.l);
-        prevColor = color;
-      }
-
       const y = padding + slice.depth * (sliceHeight + rowSpacing);
       const x = slice.x + (sliceDrawWidth - lightSectionDrawWidth);
       ctx.fillRect(x, y, lightSectionDrawWidth, sliceHeight);
@@ -497,7 +495,9 @@
       }
 
       // Change the title color dynamically depending on contrast.
-      ctx.fillStyle = colorIsLight(slice.color) ? 'black' : 'white';
+      const textColor = slice.isHighlighted ? slice.colorScheme.textVariant :
+                                              slice.colorScheme.textBase;
+      ctx.fillStyle = textColor.cssString;
       const title = cropText(slice.title, charWidth, slice.w);
       const rectXCenter = slice.x + slice.w / 2;
       const y = padding + slice.depth * (sliceHeight + rowSpacing);
@@ -529,9 +529,9 @@
 
       // Draw a thicker border around the selected slice (or chevron).
       const slice = discoveredSelection;
-      const color = slice.color;
+      const color = slice.colorScheme;
       const y = padding + slice.depth * (sliceHeight + rowSpacing);
-      ctx.strokeStyle = cachedHsluvToHex(color.h, 100, 10);
+      ctx.strokeStyle = color.base.setHSL({s: 100, l: 10}).cssString;
       ctx.beginPath();
       const THICKNESS = 3;
       ctx.lineWidth = THICKNESS;
@@ -788,8 +788,8 @@
       // The derived class doesn't need to initialize these. They are
       // rewritten on every renderCanvas() call. We just need to initialize
       // them to something.
-      baseColor: DEFAULT_SLICE_COLOR,
-      color: DEFAULT_SLICE_COLOR,
+      colorScheme: DEFAULT_SLICE_COLOR,
+      isHighlighted: false,
     };
   }
 
@@ -815,7 +815,15 @@
     }
 
     for (const slice of this.incomplete) {
-      if (slice.depth === depth && slice.x <= x) {
+      const visibleTimeScale = globals.frontendLocalState.visibleTimeScale;
+      const startPx = CROP_INCOMPLETE_SLICE_FLAG.get() ?
+          visibleTimeScale.timeToPx(slice.startNsQ) :
+          slice.x;
+      const cropUnfinishedSlicesCondition = CROP_INCOMPLETE_SLICE_FLAG.get() ?
+        startPx + INCOMPLETE_SLICE_WIDTH_PX >= x : true;
+
+      if (slice.depth === depth && startPx <= x &&
+          cropUnfinishedSlicesCondition) {
         return slice;
       }
     }
@@ -948,15 +956,7 @@
     for (const slice of slices) {
       const isHovering = globals.state.highlightedSliceId === slice.id ||
           (this.hoveredSlice && this.hoveredSlice.title === slice.title);
-      if (isHovering) {
-        slice.color = {
-          h: slice.baseColor.h,
-          s: slice.baseColor.s,
-          l: 30,
-        };
-      } else {
-        slice.color = slice.baseColor;
-      }
+      slice.isHighlighted = !!isHovering;
     }
   }
 
@@ -965,17 +965,20 @@
     return this.computedTrackHeight;
   }
 
-  getSliceRect(
-      visibleTimeScale: TimeScale, visibleWindow: Span<time, duration>,
-      windowSpan: PxSpan, tStart: time, tEnd: time, depth: number): SliceRect
-      |undefined {
+  getSliceRect(tStart: time, tEnd: time, depth: number): SliceRect|undefined {
     this.updateSliceAndTrackHeight();
 
+    const {
+      windowSpan,
+      visibleTimeScale,
+      visibleTimeSpan,
+    } = globals.frontendLocalState;
+
     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);
+    const visible = visibleTimeSpan.intersects(tStart, tEnd);
 
     const totalSliceHeight = this.computedRowSpacing + this.computedSliceHeight;
 
@@ -1009,7 +1012,3 @@
   // Input args (BaseSliceTrack -> Impl):
   slice: S;  // The slice which is clicked.
 }
-
-function getFillRatioLightColor(color: Color): Color {
-  return colorLighten(colorDesaturate(color, 15), 15);
-}
diff --git a/ui/src/frontend/base_slice_track_unittest.ts b/ui/src/frontend/base_slice_track_unittest.ts
index b179d7e..73fca19 100644
--- a/ui/src/frontend/base_slice_track_unittest.ts
+++ b/ui/src/frontend/base_slice_track_unittest.ts
@@ -13,12 +13,12 @@
 // limitations under the License.
 
 import {Time} from '../base/time';
-import {GRAY_COLOR} from '../common/colorizer';
+import {UNEXPECTED_PINK} from '../common/colorizer';
+import {Slice} from '../public';
 
 import {
   filterVisibleSlicesForTesting as filterVisibleSlices,
 } from './base_slice_track';
-import {Slice} from './slice';
 
 function slice(start: number, duration: number): Slice {
   const startNsQ = Time.fromRaw(BigInt(start));
@@ -35,9 +35,9 @@
     flags: 0,
     title: '',
     subTitle: '',
-    baseColor: GRAY_COLOR,
-    color: GRAY_COLOR,
+    colorScheme: UNEXPECTED_PINK,
     fillRatio: 1,
+    isHighlighted: false,
   };
 }
 
diff --git a/ui/src/frontend/chrome_slice_details_tab.ts b/ui/src/frontend/chrome_slice_details_tab.ts
index 7f83b36..5311cb2 100644
--- a/ui/src/frontend/chrome_slice_details_tab.ts
+++ b/ui/src/frontend/chrome_slice_details_tab.ts
@@ -35,7 +35,7 @@
   NewBottomTabArgs,
 } from './bottom_tab';
 import {FlowPoint, globals} from './globals';
-import {renderArguments} from './slice_args';
+import {hasArgs, renderArguments} from './slice_args';
 import {renderDetails} from './slice_details';
 import {getSlice, SliceDetails, SliceRef} from './sql/slice';
 import {
@@ -288,7 +288,10 @@
   private renderRhs(engine: EngineProxy, slice: SliceDetails): m.Children {
     const precFlows = this.renderPrecedingFlows(slice);
     const followingFlows = this.renderFollowingFlows(slice);
-    const args = renderArguments(engine, slice);
+    const args = hasArgs(slice) &&
+        m(Section,
+          {title: 'Arguments'},
+          m(Tree, renderArguments(engine, slice)));
     if (precFlows ?? followingFlows ?? args) {
       return m(
           GridLayoutColumn,
diff --git a/ui/src/frontend/details_panel.ts b/ui/src/frontend/details_panel.ts
index 209583c..83c4865 100644
--- a/ui/src/frontend/details_panel.ts
+++ b/ui/src/frontend/details_panel.ts
@@ -15,6 +15,7 @@
 import m from 'mithril';
 
 import {Trash} from '../base/disposable';
+import {Gate} from '../base/mithril_utils';
 import {Actions} from '../common/actions';
 import {isEmptyData} from '../common/aggregation_data';
 import {LogExists, LogExistsKey} from '../common/logs';
@@ -410,11 +411,16 @@
         }),
         currentTabKey: currentTabDetails?.key,
       }),
-      m('.details-panel-container',
-        {
-          style: {height: `${this.detailsHeight}px`},
-        },
-        panel),
+      m(
+          '.details-panel-container',
+          {
+            style: {height: `${this.detailsHeight}px`},
+          },
+          detailsPanels.map((tab) => {
+            const active = tab === currentTabDetails;
+            return m(Gate, {open: active}, tab.vnode);
+          }),
+          ),
     ];
   }
 }
diff --git a/ui/src/frontend/flow_events_renderer.ts b/ui/src/frontend/flow_events_renderer.ts
index ed47282..12ce93c 100644
--- a/ui/src/frontend/flow_events_renderer.ts
+++ b/ui/src/frontend/flow_events_renderer.ts
@@ -15,12 +15,12 @@
 import {time} from '../base/time';
 import {pluginManager} from '../common/plugins';
 import {TrackState} from '../common/state';
+import {SliceRect} from '../public';
 
 import {TRACK_SHELL_WIDTH} from './css_constants';
 import {ALL_CATEGORIES, getFlowCategories} from './flow_events_panel';
 import {Flow, FlowPoint, globals} from './globals';
 import {PanelVNode} from './panel';
-import {SliceRect} from './track';
 import {TrackGroupPanel} from './track_group_panel';
 import {TrackPanel} from './track_panel';
 
@@ -81,17 +81,6 @@
       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)) {
@@ -151,19 +140,12 @@
 
   private getSliceRect(args: FlowEventsRendererArgs, point: FlowPoint):
       SliceRect|undefined {
-    const {visibleTimeScale, visibleTimeSpan, windowSpan} =
-        globals.frontendLocalState;
     const trackPanel = args.trackIdToTrackPanel.get(point.trackId) ?.panel;
     if (!trackPanel) {
       return undefined;
     }
     return trackPanel.getSliceRect(
-        visibleTimeScale,
-        visibleTimeSpan,
-        windowSpan,
-        point.sliceStartTs,
-        point.sliceEndTs,
-        point.depth);
+        point.sliceStartTs, point.sliceEndTs, point.depth);
   }
 
   render(ctx: CanvasRenderingContext2D, args: FlowEventsRendererArgs) {
diff --git a/ui/src/frontend/frontend_local_state.ts b/ui/src/frontend/frontend_local_state.ts
index 86c30db..a649966 100644
--- a/ui/src/frontend/frontend_local_state.ts
+++ b/ui/src/frontend/frontend_local_state.ts
@@ -26,7 +26,6 @@
   VisibleState,
 } from '../common/state';
 import {raf} from '../core/raf_scheduler';
-import {HttpRpcState} from '../trace_processor/http_rpc_engine';
 
 import {globals} from './globals';
 import {ratelimit} from './rate_limiters';
@@ -47,20 +46,6 @@
   return current;
 }
 
-// Calculate the space a scrollbar takes up so that we can subtract it from
-// the canvas width.
-function calculateScrollbarWidth() {
-  const outer = document.createElement('div');
-  outer.style.overflowY = 'scroll';
-  const inner = document.createElement('div');
-  outer.appendChild(inner);
-  document.body.appendChild(outer);
-  const width =
-      outer.getBoundingClientRect().width - inner.getBoundingClientRect().width;
-  document.body.removeChild(outer);
-  return width;
-}
-
 // Immutable object describing a (high precision) time window, providing methods
 // for common mutation operations (pan, zoom), and accessors for common
 // properties such as spans and durations in several formats.
@@ -155,17 +140,10 @@
   private visibleWindow = new TimeWindow();
   private _timeScale = this.visibleWindow.createTimeScale(0, 0);
   private _windowSpan = PxSpan.ZERO;
-  showPanningHint = false;
-  showCookieConsent = false;
-  scrollToTrackKey?: string|number;
-  httpRpcState: HttpRpcState = {connected: false};
-  newVersionAvailable = false;
 
   // This is used to calculate the tracks within a Y range for area selection.
   areaY: Range = {};
 
-  private scrollBarWidth?: number;
-
   private _visibleState: VisibleState = {
     lastUpdate: 0,
     start: Time.ZERO,
@@ -179,18 +157,6 @@
   // and a |timeScale| have a notion of time range. That should live in one
   // place only.
 
-  getScrollbarWidth() {
-    if (this.scrollBarWidth === undefined) {
-      this.scrollBarWidth = calculateScrollbarWidth();
-    }
-    return this.scrollBarWidth;
-  }
-
-  setHttpRpcState(httpRpcState: HttpRpcState) {
-    this.httpRpcState = httpRpcState;
-    raf.scheduleFullRedraw();
-  }
-
   zoomVisibleWindow(ratio: number, centerPoint: number) {
     this.visibleWindow = this.visibleWindow.zoom(ratio, centerPoint);
     this._timeScale = this.visibleWindow.createTimeScale(
@@ -231,7 +197,6 @@
     assertTrue(
         end >= start,
         `Impossible select area: start [${start}] >= end [${end}]`);
-    this.showPanningHint = true;
     this._selectedArea = {start, end, tracks};
     raf.scheduleFullRedraw();
   }
diff --git a/ui/src/frontend/ftrace_panel.ts b/ui/src/frontend/ftrace_panel.ts
index c313726..a77d31b 100644
--- a/ui/src/frontend/ftrace_panel.ts
+++ b/ui/src/frontend/ftrace_panel.ts
@@ -16,7 +16,7 @@
 
 import {time, Time} from '../base/time';
 import {Actions} from '../common/actions';
-import {colorForString} from '../common/colorizer';
+import {colorForFtrace} from '../common/colorizer';
 import {StringListPatch} from '../common/state';
 import {DetailsShell} from '../widgets/details_shell';
 import {
@@ -177,12 +177,7 @@
 
         const rank = i + offset;
 
-        const color = colorForString(name);
-        const hsl = `hsl(
-          ${color.h},
-          ${color.s - 20}%,
-          ${Math.min(color.l + 10, 60)}%
-        )`;
+        const color = colorForFtrace(name).base.cssString;
 
         rows.push(m(
             `.row`,
@@ -192,7 +187,7 @@
               onmouseout: this.onRowOut.bind(this),
             },
             m('.cell', timestamp),
-            m('.cell', m('span.colour', {style: {background: hsl}}), name),
+            m('.cell', m('span.colour', {style: {background: color}}), name),
             m('.cell', cpu),
             m('.cell', process),
             m('.cell', args),
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index 7323ba5..435bf99 100644
--- a/ui/src/frontend/globals.ts
+++ b/ui/src/frontend/globals.ts
@@ -47,6 +47,7 @@
 import {setPerfHooks} from '../core/perf';
 import {raf} from '../core/raf_scheduler';
 import {Engine} from '../trace_processor/engine';
+import {HttpRpcState} from '../trace_processor/http_rpc_engine';
 
 import {Analytics, initAnalytics} from './analytics';
 import {BottomTabList} from './bottom_tab';
@@ -283,6 +284,11 @@
   private _utcOffset = Time.ZERO;
   private _openQueryHandler?: OpenQueryHandler;
 
+  scrollToTrackKey?: string|number;
+  httpRpcState: HttpRpcState = {connected: false};
+  newVersionAvailable = false;
+  showPanningHint = false;
+
   // TODO(hjd): Remove once we no longer need to update UUID on redraw.
   private _publishRedraw?: () => void = undefined;
 
diff --git a/ui/src/frontend/hsluv_cache.ts b/ui/src/frontend/hsluv_cache.ts
deleted file mode 100644
index 44cafa6..0000000
--- a/ui/src/frontend/hsluv_cache.ts
+++ /dev/null
@@ -1,39 +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 {hsluvToHex} from 'hsluv';
-
-class HsluvCache {
-  storage = new Map<number, string>();
-
-  get(hue: number, saturation: number, lightness: number): string {
-    const key = hue * 1e6 + saturation * 1e3 + lightness;
-    const value = this.storage.get(key);
-
-    if (value === undefined) {
-      const computed = hsluvToHex([hue, saturation, lightness]);
-      this.storage.set(key, computed);
-      return computed;
-    }
-
-    return value;
-  }
-}
-
-const cache = new HsluvCache();
-
-export function cachedHsluvToHex(
-    hue: number, saturation: number, lightness: number): string {
-  return cache.get(hue, saturation, lightness);
-}
diff --git a/ui/src/frontend/named_slice_track.ts b/ui/src/frontend/named_slice_track.ts
index 588e9c4..d8c3514 100644
--- a/ui/src/frontend/named_slice_track.ts
+++ b/ui/src/frontend/named_slice_track.ts
@@ -19,7 +19,7 @@
 import {STR_NULL} from '../trace_processor/query_result';
 
 import {
-  BASE_SLICE_ROW,
+  BASE_ROW,
   BaseSliceTrack,
   BaseSliceTrackTypes,
   OnSliceClickArgs,
@@ -28,17 +28,17 @@
 import {globals} from './globals';
 import {NewTrackArgs} from './track';
 
-export const NAMED_SLICE_ROW = {
+export const NAMED_ROW = {
   // Base columns (tsq, ts, dur, id, depth).
-  ...BASE_SLICE_ROW,
+  ...BASE_ROW,
 
   // Impl-specific columns.
   name: STR_NULL,
 };
-export type NamedSliceRow = typeof NAMED_SLICE_ROW;
+export type NamedRow = typeof NAMED_ROW;
 
 export interface NamedSliceTrackTypes extends BaseSliceTrackTypes {
-  row: NamedSliceRow;
+  row: NamedRow;
 }
 
 export abstract class NamedSliceTrack<
@@ -50,7 +50,7 @@
 
   // This is used by the base class to call iter().
   getRowSpec(): T['row'] {
-    return NAMED_SLICE_ROW;
+    return NAMED_ROW;
   }
 
   // Converts a SQL result row to an "Impl" Slice.
@@ -58,8 +58,8 @@
     const baseSlice = super.rowToSlice(row);
     // Ignore PIDs or numeric arguments when hashing.
     const name = row.name || '';
-    const baseColor = getColorForSlice(name, false);
-    return {...baseSlice, title: name, baseColor};
+    const colorScheme = getColorForSlice(name);
+    return {...baseSlice, title: name, colorScheme};
   }
 
   onSliceOver(args: OnSliceOverArgs<T['slice']>) {
diff --git a/ui/src/frontend/overview_timeline_panel.ts b/ui/src/frontend/overview_timeline_panel.ts
index 7ad81ff..edda1d3 100644
--- a/ui/src/frontend/overview_timeline_panel.ts
+++ b/ui/src/frontend/overview_timeline_panel.ts
@@ -20,7 +20,7 @@
   Time,
   time,
 } from '../base/time';
-import {hueForCpu} from '../common/colorizer';
+import {colorForCpu} from '../common/colorizer';
 import {timestampFormat, TimestampFormat} from '../common/timestamp_format';
 
 import {
@@ -135,7 +135,8 @@
           const xEnd = Math.ceil(this.timeScale.timeToPx(loads[i].end));
           const yOff = Math.floor(headerHeight + y * trackHeight);
           const lightness = Math.ceil((1 - loads[i].load * 0.7) * 100);
-          ctx.fillStyle = `hsl(${hueForCpu(y)}, 50%, ${lightness}%)`;
+          const color = colorForCpu(y).setHSL({s: 50, l: lightness});
+          ctx.fillStyle = color.cssString;
           ctx.fillRect(xStart, yOff, xEnd - xStart, Math.ceil(trackHeight));
         }
         y++;
diff --git a/ui/src/frontend/panel_container.ts b/ui/src/frontend/panel_container.ts
index 14550e7..5497d8e 100644
--- a/ui/src/frontend/panel_container.ts
+++ b/ui/src/frontend/panel_container.ts
@@ -15,6 +15,7 @@
 import m from 'mithril';
 
 import {Trash} from '../base/disposable';
+import {getScrollbarWidth} from '../base/dom_utils';
 import {assertExists, assertFalse} from '../base/logging';
 import {SimpleResizeObserver} from '../base/resize_observer';
 import {
@@ -333,8 +334,7 @@
     // On non-MacOS if there is a solid scroll bar it can cover important
     // pixels, reduce the size of the canvas so it doesn't overlap with
     // the scroll bar.
-    this.parentWidth =
-        clientRect.width - globals.frontendLocalState.getScrollbarWidth();
+    this.parentWidth = clientRect.width - getScrollbarWidth();
     this.parentHeight = clientRect.height;
     return this.parentHeight !== oldHeight || this.parentWidth !== oldWidth;
   }
diff --git a/ui/src/frontend/publish.ts b/ui/src/frontend/publish.ts
index 8ed0c2d..d23be62 100644
--- a/ui/src/frontend/publish.ts
+++ b/ui/src/frontend/publish.ts
@@ -25,6 +25,7 @@
 import {MetricResult} from '../common/metric_data';
 import {CurrentSearchResults, SearchSummary} from '../common/search_data';
 import {raf} from '../core/raf_scheduler';
+import {HttpRpcState} from '../trace_processor/http_rpc_engine';
 
 import {
   CounterDetails,
@@ -81,6 +82,11 @@
   globals.publishRedraw();
 }
 
+export function publishHttpRpcState(httpRpcState: HttpRpcState) {
+  globals.httpRpcState = httpRpcState;
+  raf.scheduleFullRedraw();
+}
+
 export function publishCounterDetails(click: CounterDetails) {
   globals.counterDetails = click;
   globals.publishRedraw();
@@ -216,3 +222,8 @@
   globals.ftracePanelData = data;
   globals.publishRedraw();
 }
+
+export function publishShowPanningHint() {
+  globals.showPanningHint = true;
+  globals.publishRedraw();
+}
diff --git a/ui/src/frontend/router.ts b/ui/src/frontend/router.ts
index 27ab289..5c7a8ff 100644
--- a/ui/src/frontend/router.ts
+++ b/ui/src/frontend/router.ts
@@ -63,9 +63,14 @@
   // DEPRECATED: for #!/record?p=cpu subpages (b/191255021).
   p: optStr,
 
-  // For fetching traces from Cloud Storage.
+  // For fetching traces from Cloud Storage or local servers
+  // as with record_android_trace.
   url: optStr,
 
+  // Override the referrer. Useful for scripts such as
+  // record_android_trace to record where the trace is coming from.
+  referrer: optStr,
+
   // For the 'mode' of the UI. For example when the mode is 'embedded'
   // some features are disabled.
   mode: oneOf<Mode>(modes, undefined),
diff --git a/ui/src/frontend/rpc_http_dialog.ts b/ui/src/frontend/rpc_http_dialog.ts
index c2a64a2..54796b8 100644
--- a/ui/src/frontend/rpc_http_dialog.ts
+++ b/ui/src/frontend/rpc_http_dialog.ts
@@ -22,6 +22,7 @@
 
 import {globals} from './globals';
 import {showModal} from './modal';
+import {publishHttpRpcState} from './publish';
 
 const CURRENT_API_VERSION =
     TraceProcessorApiVersion.TRACE_PROCESSOR_CURRENT_API_VERSION;
@@ -79,7 +80,7 @@
 // having to open a trace).
 export async function CheckHttpRpcConnection(): Promise<void> {
   const state = await HttpRpcEngine.checkConnection();
-  globals.frontendLocalState.setHttpRpcState(state);
+  publishHttpRpcState(state);
   if (!state.connected) return;
   const tpStatus = assertExists(state.status);
 
diff --git a/ui/src/frontend/scroll_helper.ts b/ui/src/frontend/scroll_helper.ts
index dccad06..b514b61 100644
--- a/ui/src/frontend/scroll_helper.ts
+++ b/ui/src/frontend/scroll_helper.ts
@@ -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.scrollToTrackKey = trackKey;
+    globals.scrollToTrackKey = trackKey;
     globals.dispatch(Actions.toggleTrackGroupCollapsed({trackGroupId}));
     return;
   } else {
diff --git a/ui/src/frontend/service_worker_controller.ts b/ui/src/frontend/service_worker_controller.ts
index a6082fe..de5323e 100644
--- a/ui/src/frontend/service_worker_controller.ts
+++ b/ui/src/frontend/service_worker_controller.ts
@@ -88,7 +88,7 @@
       // Ctrl+Shift+R). In these cases, we are already at the last
       // version.
       if (sw !== this._initialWorker && this._initialWorker) {
-        globals.frontendLocalState.newVersionAvailable = true;
+        globals.newVersionAvailable = true;
       }
     }
   }
diff --git a/ui/src/frontend/sidebar.ts b/ui/src/frontend/sidebar.ts
index c0c23bf..6d3e79b 100644
--- a/ui/src/frontend/sidebar.ts
+++ b/ui/src/frontend/sidebar.ts
@@ -631,7 +631,7 @@
     // RPC server is shut down after we load the UI and cached httpRpcState)
     // this will eventually become  consistent once the engine is created.
     if (mode === undefined) {
-      if (globals.frontendLocalState.httpRpcState.connected &&
+      if (globals.httpRpcState.connected &&
           globals.state.newEngineMode === 'USE_HTTP_RPC_IF_AVAILABLE') {
         mode = 'HTTP_RPC';
       } else {
diff --git a/ui/src/frontend/slice.ts b/ui/src/frontend/slice.ts
deleted file mode 100644
index 7e82268..0000000
--- a/ui/src/frontend/slice.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright (C) 2021 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-import {duration, time} from '../base/time';
-import {Color} from '../common/colorizer';
-
-export interface Slice {
-  // These properties are updated only once per query result when the Slice
-  // object is created and don't change afterwards.
-  readonly id: number;
-  readonly startNsQ: time;
-  readonly endNsQ: time;
-  readonly durNsQ: duration;
-  readonly ts: time;
-  readonly dur: duration;
-  readonly depth: number;
-  readonly flags: number;
-
-  // Each slice can represent some extra numerical information by rendering a
-  // portion of the slice with a lighter tint.
-  // |fillRatio\ describes the ratio of the normal area to the tinted area
-  // width of the slice, normalized between 0.0 -> 1.0.
-  // 0.0 means the whole slice is tinted.
-  // 1.0 means none of the slice is tinted.
-  // E.g. If |fillRatio| = 0.65 the slice will be rendered like this:
-  // [############|*******]
-  // ^------------^-------^
-  //     Normal     Light
-  readonly fillRatio: number;
-
-  // These can be changed by the Impl.
-  title: string;
-  subTitle: string;
-  baseColor: Color;
-  color: Color;
-}
diff --git a/ui/src/frontend/slice_args.ts b/ui/src/frontend/slice_args.ts
index 70beacb..9e1798f 100644
--- a/ui/src/frontend/slice_args.ts
+++ b/ui/src/frontend/slice_args.ts
@@ -30,8 +30,7 @@
 } from '../tracks/visualised_args';
 import {Anchor} from '../widgets/anchor';
 import {MenuItem, PopupMenu2} from '../widgets/menu';
-import {Section} from '../widgets/section';
-import {Tree, TreeNode} from '../widgets/tree';
+import {TreeNode} from '../widgets/tree';
 
 import {addTab} from './bottom_tab';
 import {globals} from './globals';
@@ -40,20 +39,21 @@
 import {SqlTableTab} from './sql_table/tab';
 import {SqlTables} from './sql_table/well_known_tables';
 
-// Renders slice arguments (key/value pairs) into a Tree widget.
+// Renders slice arguments (key/value pairs) as a subtree.
 export function renderArguments(
     engine: EngineProxy, slice: SliceDetails): m.Children {
   if (slice.args && slice.args.length > 0) {
     const tree = convertArgsToTree(slice.args);
-    return m(
-        Section,
-        {title: 'Arguments'},
-        m(Tree, renderArgTreeNodes(engine, tree)));
+    return renderArgTreeNodes(engine, tree);
   } else {
     return undefined;
   }
 }
 
+export function hasArgs(slice: SliceDetails): boolean {
+  return exists(slice.args) && slice.args.length > 0;
+}
+
 function renderArgTreeNodes(
     engine: EngineProxy, args: ArgNode<Arg>[]): m.Children {
   return args.map((arg) => {
diff --git a/ui/src/frontend/slice_track_base.ts b/ui/src/frontend/slice_track.ts
similarity index 80%
rename from ui/src/frontend/slice_track_base.ts
rename to ui/src/frontend/slice_track.ts
index 37c2ec8..0264d07 100644
--- a/ui/src/frontend/slice_track_base.ts
+++ b/ui/src/frontend/slice_track.ts
@@ -12,28 +12,25 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {duration, Span, Time, time} from '../base/time';
+import {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 {getColorForSlice} from '../common/colorizer';
 import {HighPrecisionTime} from '../common/high_precision_time';
 import {TrackData} from '../common/track_data';
+import {TrackHelperLEGACY} from '../common/track_helper';
+import {SliceRect} from '../public';
 
+import {CROP_INCOMPLETE_SLICE_FLAG} from './base_slice_track';
 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;
+const INCOMPLETE_SLICE_WIDTH_PX = 20;
 
 export interface SliceData extends TrackData {
   // Slices are stored in a columnar fashion.
@@ -55,7 +52,9 @@
 // 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> {
+// Note: This class is deprecated and should not be used for new tracks. Use
+// |BaseSliceTrack| instead.
+export abstract class SliceTrackLEGACY extends TrackHelperLEGACY<SliceData> {
   constructor(
       private maxDepth: number, protected trackKey: string,
       private tableName: string, private namespace?: string) {
@@ -119,18 +118,23 @@
       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');
+        tEnd = this.getEndTimeIfInComplete(tStart);
       }
 
       if (!visibleTimeSpan.intersects(tStart, tEnd)) {
         continue;
       }
 
-      const rect = this.getSliceRect(
-          visibleTimeScale, visibleTimeSpan, windowSpan, tStart, tEnd, depth);
-      if (!rect || !rect.visible) {
-        continue;
-      }
+      const pxEnd = windowSpan.end;
+      const left = Math.max(visibleTimeScale.timeToPx(tStart), 0);
+      const right = Math.min(visibleTimeScale.timeToPx(tEnd), pxEnd);
+
+      const rect = {
+        left,
+        width: Math.max(right - left, 1),
+        top: TRACK_PADDING + depth * SLICE_HEIGHT,
+        height: SLICE_HEIGHT,
+      };
 
       const currentSelection = globals.state.currentSelection;
       const isSelected = currentSelection &&
@@ -141,11 +145,14 @@
           globals.state.highlightedSliceId === sliceId;
 
       const hasFocus = highlighted || isSelected;
-      const colorObj = getColorForSlice(title, hasFocus);
+      const colorScheme = getColorForSlice(title);
+      const colorObj = hasFocus ? colorScheme.variant : colorScheme.base;
+      const textColor =
+          hasFocus ? colorScheme.textVariant : colorScheme.textBase;
 
       let color: string;
       if (colorOverride === undefined) {
-        color = cachedHsluvToHex(colorObj.h, colorObj.s, colorObj.l);
+        color = colorObj.cssString;
       } else {
         color = colorOverride;
       }
@@ -165,7 +172,7 @@
             ctx.translate(rect.left, rect.top);
 
             // Draw a rectangle around the selected slice
-            ctx.strokeStyle = cachedHsluvToHex(colorObj.h, 100, 10);
+            ctx.strokeStyle = colorObj.setHSL({s: 100, l: 10}).cssString;
             ctx.beginPath();
             ctx.lineWidth = 3;
             ctx.strokeRect(
@@ -188,7 +195,13 @@
       }
 
       if (isIncomplete && rect.width > SLICE_HEIGHT / 4) {
-        drawIncompleteSlice(ctx, rect.left, rect.top, rect.width, SLICE_HEIGHT);
+        drawIncompleteSlice(
+            ctx,
+            rect.left,
+            rect.top,
+            rect.width,
+            SLICE_HEIGHT,
+            !CROP_INCOMPLETE_SLICE_FLAG.get());
       } else if (
           data.cpuTimeRatio !== undefined && data.cpuTimeRatio[i] < 1 - 1e-9) {
         // We draw two rectangles, representing the ratio between wall time and
@@ -196,9 +209,8 @@
         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, rect.top, rect.width, SLICE_HEIGHT);
+        ctx.fillStyle = '#FFFFFF50';
         ctx.fillRect(
             rect.left + firstPartWidth,
             rect.top,
@@ -211,7 +223,7 @@
       // Selected case
       if (isSelected) {
         drawRectOnSelected = () => {
-          ctx.strokeStyle = cachedHsluvToHex(colorObj.h, 100, 10);
+          ctx.strokeStyle = colorObj.setHSL({s: 100, l: 10}).cssString;
           ctx.beginPath();
           ctx.lineWidth = 3;
           ctx.strokeRect(
@@ -222,7 +234,7 @@
 
       // Don't render text when we have less than 5px to play with.
       if (rect.width >= 5) {
-        ctx.fillStyle = colorObj.l > 65 ? '#404040' : 'white';
+        ctx.fillStyle = textColor.cssString;
         const displayText = cropText(title, charWidth, rect.width);
         const rectXCenter = rect.left + rect.width / 2;
         ctx.textBaseline = 'middle';
@@ -250,7 +262,6 @@
     if (data === undefined) return;
     const {
       visibleTimeScale: timeScale,
-      visibleWindowTime: visibleHPTimeSpan,
     } = globals.frontendLocalState;
     if (y < TRACK_PADDING) return;
     const instantWidthTime = timeScale.pxDeltaToDuration(HALF_CHEVRON_WIDTH_PX);
@@ -271,7 +282,8 @@
         const end = Time.fromRaw(data.ends[i]);
         let tEnd = HighPrecisionTime.fromTime(end);
         if (data.isIncomplete[i]) {
-          tEnd = visibleHPTimeSpan.end;
+          const endTime = this.getEndTimeIfInComplete(start);
+          tEnd = HighPrecisionTime.fromTime(endTime);
         }
         if (tStart.lte(t) && t.lte(tEnd)) {
           return i;
@@ -280,6 +292,20 @@
     }
   }
 
+  getEndTimeIfInComplete(start: time): time {
+    const {visibleTimeScale, visibleWindowTime} = globals.frontendLocalState;
+
+    let end = visibleWindowTime.end.toTime('ceil');
+    if (CROP_INCOMPLETE_SLICE_FLAG.get()) {
+      const widthTime =
+          visibleTimeScale.pxDeltaToDuration(INCOMPLETE_SLICE_WIDTH_PX)
+              .toTime();
+      end = Time.add(start, widthTime);
+    }
+
+    return end;
+  }
+
   onMouseMove({x, y}: {x: number, y: number}) {
     this.hoveredTitleId = -1;
     globals.dispatch(Actions.setHighlightedSliceId({sliceId: -1}));
@@ -318,15 +344,18 @@
     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 {
+  getSliceRect(tStart: time, tEnd: time, depth: number): SliceRect|undefined {
+    const {
+      windowSpan,
+      visibleTimeScale,
+      visibleTimeSpan,
+    } = globals.frontendLocalState;
+
     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);
+    const visible = visibleTimeSpan.intersects(tStart, tEnd);
 
     return {
       left,
diff --git a/ui/src/frontend/sql_table/argument_selector.ts b/ui/src/frontend/sql_table/argument_selector.ts
index 2c86138..f7cd374 100644
--- a/ui/src/frontend/sql_table/argument_selector.ts
+++ b/ui/src/frontend/sql_table/argument_selector.ts
@@ -69,7 +69,7 @@
     this.argList = [];
     const it = queryResult.iter({key: STR});
     for (; it.valid(); it.next()) {
-      const arg = argColumn(attrs.argSetId, it.key);
+      const arg = argColumn(attrs.tableName, attrs.argSetId, it.key);
       if (attrs.alreadySelectedColumns.has(arg.alias)) continue;
       this.argList.push(it.key);
     }
diff --git a/ui/src/frontend/sql_table/column.ts b/ui/src/frontend/sql_table/column.ts
index f4a6543..133c5fb 100644
--- a/ui/src/frontend/sql_table/column.ts
+++ b/ui/src/frontend/sql_table/column.ts
@@ -46,10 +46,11 @@
   };
 }
 
-export function argColumn(c: ArgSetIdColumn, argName: string): Column {
+export function argColumn(
+    tableName: string, c: ArgSetIdColumn, argName: string): Column {
   const escape = (name: string) => name.replace(/[^A-Za-z0-9]/g, '_');
   return {
-    expression: `extract_arg(${c.name}, ${sqliteString(argName)})`,
+    expression: `extract_arg(${tableName}.${c.name}, ${sqliteString(argName)})`,
     alias: `_arg_${c.name}_${escape(argName)}`,
     title: `${c.title ?? c.name} ${argName}`,
   };
diff --git a/ui/src/frontend/sql_table/column_unittest.ts b/ui/src/frontend/sql_table/column_unittest.ts
index 8fd944e..849eb03 100644
--- a/ui/src/frontend/sql_table/column_unittest.ts
+++ b/ui/src/frontend/sql_table/column_unittest.ts
@@ -72,11 +72,12 @@
     },
   });
 
-  expect(argColumn(table.columns[3] as ArgSetIdColumn, 'foo.bar')).toEqual({
-    expression: 'extract_arg(arg_set_id, \'foo.bar\')',
-    alias: '_arg_arg_set_id_foo_bar',
-    title: 'Arg foo.bar',
-  });
+  expect(argColumn('slice', table.columns[3] as ArgSetIdColumn, 'foo.bar'))
+      .toEqual({
+        expression: 'extract_arg(slice.arg_set_id, \'foo.bar\')',
+        alias: '_arg_arg_set_id_foo_bar',
+        title: 'Arg foo.bar',
+      });
 });
 
 function formatSqlProjectionsForColumn(c: Column): string {
diff --git a/ui/src/frontend/sql_table/table.ts b/ui/src/frontend/sql_table/table.ts
index de1c9cb..d6de8e3 100644
--- a/ui/src/frontend/sql_table/table.ts
+++ b/ui/src/frontend/sql_table/table.ts
@@ -89,7 +89,7 @@
                 constraints: this.state.getQueryConstraints(),
                 alreadySelectedColumns: existingColumns,
                 onArgumentSelected: (argument: string) => {
-                  addColumn(argColumn(column, argument));
+                  addColumn(argColumn(this.table.name, column, argument));
                 },
               })));
         continue;
diff --git a/ui/src/frontend/tables/table.ts b/ui/src/frontend/tables/table.ts
index 4d1b773..eb12883 100644
--- a/ui/src/frontend/tables/table.ts
+++ b/ui/src/frontend/tables/table.ts
@@ -89,6 +89,11 @@
   return new ColumnDescriptor<T>(name, getter, {contextMenu, sortKey: getter});
 }
 
+export function widgetColumn<T>(
+    name: string, getter: (t: T) => m.Child): ColumnDescriptor<T> {
+  return new ColumnDescriptor<T>(name, getter);
+}
+
 interface SortingInfo<T> {
   columnId: string;
   direction: SortDirection;
diff --git a/ui/src/frontend/thread_state.ts b/ui/src/frontend/thread_state.ts
index 81b1346..977b608 100644
--- a/ui/src/frontend/thread_state.ts
+++ b/ui/src/frontend/thread_state.ts
@@ -181,7 +181,6 @@
             let trackKey: string|number|undefined;
             for (const track of Object.values(globals.state.tracks)) {
               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;
diff --git a/ui/src/frontend/topbar.ts b/ui/src/frontend/topbar.ts
index 70aed74..4bea985 100644
--- a/ui/src/frontend/topbar.ts
+++ b/ui/src/frontend/topbar.ts
@@ -45,7 +45,7 @@
         m('button.notification-btn.preferred',
           {
             onclick: () => {
-              globals.frontendLocalState.newVersionAvailable = false;
+              globals.newVersionAvailable = false;
               raf.scheduleFullRedraw();
             },
           },
@@ -61,7 +61,7 @@
     // does not persist for iFrames. The host is responsible for communicating
     // to users that they can press '?' for help.
     if (globals.embeddedMode || dismissed === 'true' ||
-        !globals.frontendLocalState.showPanningHint) {
+        !globals.showPanningHint) {
       return;
     }
     return m(
@@ -72,7 +72,7 @@
         m('button.hint-dismiss-button',
           {
             onclick: () => {
-              globals.frontendLocalState.showPanningHint = false;
+              globals.showPanningHint = false;
               localStorage.setItem(DISMISSED_PANNING_HINT_KEY, 'true');
               raf.scheduleFullRedraw();
             },
@@ -113,9 +113,7 @@
     return m(
         '.topbar',
         {class: globals.state.sidebarVisible ? '' : 'hide-sidebar'},
-        globals.frontendLocalState.newVersionAvailable ?
-            m(NewVersionNotification) :
-            omnibox,
+        globals.newVersionAvailable ? m(NewVersionNotification) : omnibox,
         m(Progress),
         m(HelpPanningNotification),
         m(TraceErrorIcon));
diff --git a/ui/src/frontend/track.ts b/ui/src/frontend/track.ts
index ad2336b..7fcce0b 100644
--- a/ui/src/frontend/track.ts
+++ b/ui/src/frontend/track.ts
@@ -14,53 +14,20 @@
 
 import m from 'mithril';
 
-import {assertExists} from '../base/logging';
-import {duration, Span, time} from '../base/time';
-import {Track, TrackContext} from '../public';
+import {time} from '../base/time';
+import {SliceRect, Track, TrackContext} from '../public';
 import {EngineProxy} from '../trace_processor/engine';
 
-import {PxSpan, TimeScale} from './time_scale';
-
 // Args passed to the track constructors when creating a new track.
 export interface NewTrackArgs {
   trackKey: string;
   engine: EngineProxy;
 }
 
-// This interface forces track implementations to have some static properties.
-// Typescript does not have abstract static members, which is why this needs to
-// be in a separate interface.
-export interface TrackCreator {
-  // Store the kind explicitly as a string as opposed to using class.kind in
-  // case we ever minify our code.
-  readonly kind: string;
-
-  // We need the |create| method because the stored value in the registry can be
-  // an abstract class, and we cannot call 'new' on an abstract class.
-  create(args: NewTrackArgs): TrackBase;
-}
-
-export interface SliceRect {
-  left: number;
-  width: number;
-  top: number;
-  height: number;
-  visible: boolean;
-}
-
 // The abstract class that needs to be implemented by all tracks.
-export abstract class TrackBase<Config = {}> implements Track {
+export abstract class TrackBase implements Track {
   protected readonly trackKey: string;
   protected readonly engine: EngineProxy;
-  private _config?: Config;
-
-  get config(): Config {
-    return assertExists(this._config);
-  }
-
-  set config(x: Config) {
-    this._config = x;
-  }
 
   constructor(args: NewTrackArgs) {
     this.trackKey = args.trackKey;
@@ -103,10 +70,8 @@
   // only for track types that support slices e.g. chrome_slice, async_slices
   // tStart - slice start time in seconds, tEnd - slice end time in seconds,
   // depth - slice depth
-  getSliceRect(
-      _visibleTimeScale: TimeScale, _visibleWindow: Span<time, duration>,
-      _windowSpan: PxSpan, _tStart: time, _tEnd: time,
-      _depth: number): SliceRect|undefined {
+  getSliceRect(_tStart: time, _tEnd: time, _depth: number): SliceRect
+      |undefined {
     return undefined;
   }
 }
diff --git a/ui/src/frontend/track_group_panel.ts b/ui/src/frontend/track_group_panel.ts
index a524dfb..ec6e7e6 100644
--- a/ui/src/frontend/track_group_panel.ts
+++ b/ui/src/frontend/track_group_panel.ts
@@ -24,7 +24,7 @@
   TrackGroupState,
   TrackState,
 } from '../common/state';
-import {Migrate, Track, TrackContext} from '../public';
+import {Migrate, Track, TrackContext, TrackTags} from '../public';
 
 import {globals} from './globals';
 import {drawGridLines} from './gridline_helper';
@@ -44,6 +44,7 @@
   private shellWidth = 0;
   private backgroundColor = '#ffffff';  // Updated from CSS later.
   private summaryTrack?: Track;
+  private summaryTrackTags?: TrackTags;
 
   constructor({attrs}: m.CVnode<Attrs>) {
     super();
@@ -70,6 +71,7 @@
     };
 
     this.summaryTrack = pluginManager.createTrack(uri, ctx);
+    this.summaryTrackTags = pluginManager.resolveTrackInfo(uri)?.tags;
   }
 
   get trackGroupState(): TrackGroupState {
@@ -148,7 +150,7 @@
                 'h1.track-title',
                 {title: name},
                 name,
-                renderChips(this.summaryTrackState),
+                renderChips(this.summaryTrackTags),
                 ),
             (this.trackGroupState.collapsed && child !== null) ?
                 m('h2.track-subtitle', child) :
diff --git a/ui/src/frontend/track_panel.ts b/ui/src/frontend/track_panel.ts
index 90a593a..dec3a65 100644
--- a/ui/src/frontend/track_panel.ts
+++ b/ui/src/frontend/track_panel.ts
@@ -17,20 +17,19 @@
 
 import {currentTargetOffset} from '../base/dom_utils';
 import {Icons} from '../base/semantic_icons';
-import {duration, Span, time} from '../base/time';
+import {time} from '../base/time';
 import {Actions} from '../common/actions';
 import {pluginManager} from '../common/plugins';
 import {TrackState} from '../common/state';
 import {raf} from '../core/raf_scheduler';
-import {Migrate, Track, TrackContext} from '../public';
+import {Migrate, SliceRect, Track, TrackContext, TrackTags} from '../public';
 
+import {checkerboard} from './checkerboard';
 import {SELECTION_FILL_COLOR, TRACK_SHELL_WIDTH} from './css_constants';
 import {globals} from './globals';
 import {drawGridLines} from './gridline_helper';
 import {Panel, PanelSize} from './panel';
 import {verticalScrollToTrack} from './scroll_helper';
-import {PxSpan, TimeScale} from './time_scale';
-import {SliceRect} from './track';
 import {
   drawVerticalLineAtTime,
 } from './vertical_line_helper';
@@ -76,29 +75,24 @@
   }
 }
 
-export function renderChips({uri}: TrackState) {
-  const tagElements: m.Children = [];
-  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;
+export function renderChips(tags?: TrackTags) {
+  return [
+    tags?.metric && m(TrackChip, {text: 'metric'}),
+    tags?.debuggable && m(TrackChip, {text: 'debuggable'}),
+  ];
 }
 
 interface TrackShellAttrs {
-  track: Track;
-  trackState: TrackState;
+  trackKey: string;
+  title: string;
+  buttons: m.Children;
+  tags?: TrackTags;
 }
 
 class TrackShell implements m.ClassComponent<TrackShellAttrs> {
   // Set to true when we click down and drag the
   private dragging = false;
   private dropping: 'before'|'after'|undefined = undefined;
-  private attrs?: TrackShellAttrs;
-
-  oninit(vnode: m.Vnode<TrackShellAttrs>) {
-    this.attrs = vnode.attrs;
-  }
 
   view({attrs}: m.CVnode<TrackShellAttrs>) {
     // The shell should be highlighted if the current search result is inside
@@ -107,7 +101,7 @@
     const searchIndex = globals.state.searchIndex;
     if (searchIndex !== -1) {
       const trackKey = globals.currentSearchResults.trackKeys[searchIndex];
-      if (trackKey === attrs.trackState.key) {
+      if (trackKey === attrs.trackKey) {
         highlightClass = 'flash';
       }
     }
@@ -118,34 +112,34 @@
         `.track-shell[draggable=true]`,
         {
           class: `${highlightClass} ${dragClass} ${dropClass}`,
-          ondragstart: this.ondragstart.bind(this),
+          ondragstart: (e: DragEvent) => this.ondragstart(e, attrs.trackKey),
           ondragend: this.ondragend.bind(this),
           ondragover: this.ondragover.bind(this),
           ondragleave: this.ondragleave.bind(this),
-          ondrop: this.ondrop.bind(this),
+          ondrop: (e: DragEvent) => this.ondrop(e, attrs.trackKey),
         },
         m(
             'h1',
             {
-              title: attrs.trackState.name,
+              title: attrs.title,
               style: {
-                'font-size': getTitleSize(attrs.trackState.name),
+                'font-size': getTitleSize(attrs.title),
               },
             },
-            attrs.trackState.name,
-            renderChips(attrs.trackState),
+            attrs.title,
+            renderChips(attrs.tags),
             ),
         m('.track-buttons',
-          attrs.track.getTrackShellButtons(),
+          attrs.buttons,
           m(TrackButton, {
             action: () => {
               globals.dispatch(
-                  Actions.toggleTrackPinned({trackKey: attrs.trackState.key}));
+                  Actions.toggleTrackPinned({trackKey: attrs.trackKey}));
             },
             i: Icons.Pin,
-            filledIcon: isPinned(attrs.trackState.key),
-            tooltip: isPinned(attrs.trackState.key) ? 'Unpin' : 'Pin to top',
-            showButton: isPinned(attrs.trackState.key),
+            filledIcon: isPinned(attrs.trackKey),
+            tooltip: isPinned(attrs.trackKey) ? 'Unpin' : 'Pin to top',
+            showButton: isPinned(attrs.trackKey),
             fullHeight: true,
           }),
           globals.state.currentSelection !== null &&
@@ -153,25 +147,24 @@
               m(TrackButton, {
                 action: (e: MouseEvent) => {
                   globals.dispatch(Actions.toggleTrackSelection(
-                      {id: attrs.trackState.key, isTrackGroup: false}));
+                      {id: attrs.trackKey, isTrackGroup: false}));
                   e.stopPropagation();
                 },
-                i: isSelected(attrs.trackState.key) ? Icons.Checkbox :
-                                                      Icons.BlankCheckbox,
-                tooltip: isSelected(attrs.trackState.key) ?
-                    'Remove track' :
-                    'Add track to selection',
+                i: isSelected(attrs.trackKey) ? Icons.Checkbox :
+                                                Icons.BlankCheckbox,
+                tooltip: isSelected(attrs.trackKey) ? 'Remove track' :
+                                                      'Add track to selection',
                 showButton: true,
               }) :
               ''));
   }
 
-  ondragstart(e: DragEvent) {
+  ondragstart(e: DragEvent, trackKey: string) {
     const dataTransfer = e.dataTransfer;
     if (dataTransfer === null) return;
     this.dragging = true;
     raf.scheduleFullRedraw();
-    dataTransfer.setData('perfetto/track', `${this.attrs!.trackState.key}`);
+    dataTransfer.setData('perfetto/track', `${trackKey}`);
     dataTransfer.setDragImage(new Image(), 0, 0);
   }
 
@@ -204,13 +197,13 @@
     raf.scheduleFullRedraw();
   }
 
-  ondrop(e: DragEvent) {
+  ondrop(e: DragEvent, trackKey: string) {
     if (this.dropping === undefined) return;
     const dataTransfer = e.dataTransfer;
     if (dataTransfer === null) return;
     raf.scheduleFullRedraw();
     const srcId = dataTransfer.getData('perfetto/track');
-    const dstId = this.attrs!.trackState.key;
+    const dstId = trackKey;
     globals.dispatch(Actions.moveTrack({srcId, op: this.dropping, dstId}));
     this.dropping = undefined;
   }
@@ -275,9 +268,14 @@
 }
 
 interface TrackComponentAttrs {
-  trackState: TrackState;
-  track: Track;
+  trackKey: string;
+  heightPx?: number;
+  title: string;
+  buttons?: m.Children;
+  tags?: TrackTags;
+  track?: Track;
 }
+
 class TrackComponent implements m.ClassComponent<TrackComponentAttrs> {
   view({attrs}: m.CVnode<TrackComponentAttrs>) {
     // TODO(hjd): The min height below must match the track_shell_title
@@ -287,20 +285,25 @@
         '.track',
         {
           style: {
-            height: `${Math.max(18, attrs.track.getHeight())}px`,
+            height: `${Math.max(18, attrs.heightPx ?? 0)}px`,
           },
-          id: 'track_' + attrs.trackState.key,
+          id: 'track_' + attrs.trackKey,
         },
         [
-          m(TrackShell, {track: attrs.track, trackState: attrs.trackState}),
-          m(TrackContent, {track: attrs.track}),
+          m(TrackShell, {
+            buttons: attrs.buttons,
+            title: attrs.title,
+            trackKey: attrs.trackKey,
+            tags: attrs.tags,
+          }),
+          attrs.track && m(TrackContent, {track: attrs.track}),
         ]);
   }
 
   oncreate({attrs}: m.CVnode<TrackComponentAttrs>) {
-    if (globals.frontendLocalState.scrollToTrackKey === attrs.trackState.key) {
-      verticalScrollToTrack(attrs.trackState.key);
-      globals.frontendLocalState.scrollToTrackKey = undefined;
+    if (globals.scrollToTrackKey === attrs.trackKey) {
+      verticalScrollToTrack(attrs.trackKey);
+      globals.scrollToTrackKey = undefined;
     }
   }
 }
@@ -342,6 +345,7 @@
   // has disappeared.
   private track: Track|undefined;
   private trackState: TrackState|undefined;
+  private tags: TrackTags|undefined;
 
   private tryLoadTrack(vnode: m.CVnode<TrackPanelAttrs>) {
     const trackKey = vnode.attrs.trackKey;
@@ -365,6 +369,7 @@
     };
 
     this.track = pluginManager.createTrack(uri, trackCtx);
+    this.tags = pluginManager.resolveTrackInfo(uri)?.tags;
 
     this.track?.onCreate(trackCtx);
     this.trackState = trackState;
@@ -376,9 +381,19 @@
     }
 
     if (this.track === undefined || this.trackState === undefined) {
-      return m('div', 'No such track');
+      return m(TrackComponent, {
+        trackKey: vnode.attrs.trackKey,
+        title: this.trackState?.name ?? 'Loading...',
+      });
     }
-    return m(TrackComponent, {trackState: this.trackState, track: this.track});
+    return m(TrackComponent, {
+      tags: this.tags,
+      heightPx: this.track.getHeight(),
+      title: this.trackState.name,
+      trackKey: this.trackState.key,
+      buttons: this.track.getTrackShellButtons(),
+      track: this.track,
+    });
   }
 
   oncreate() {
@@ -430,6 +445,8 @@
     ctx.translate(TRACK_SHELL_WIDTH, 0);
     if (this.track !== undefined) {
       this.track.render(ctx);
+    } else {
+      checkerboard(ctx, size.height, 0, size.width - TRACK_SHELL_WIDTH);
     }
     ctx.restore();
 
@@ -492,14 +509,10 @@
     }
   }
 
-  getSliceRect(
-      visibleTimeScale: TimeScale, visibleWindow: Span<time, duration>,
-      windowSpan: PxSpan, tStart: time, tDur: time, depth: number): SliceRect
-      |undefined {
+  getSliceRect(tStart: time, tDur: time, depth: number): SliceRect|undefined {
     if (this.track === undefined) {
       return undefined;
     }
-    return this.track.getSliceRect(
-        visibleTimeScale, visibleWindow, windowSpan, tStart, tDur, depth);
+    return this.track.getSliceRect(tStart, tDur, depth);
   }
 }
diff --git a/ui/src/frontend/track_registry.ts b/ui/src/frontend/track_registry.ts
deleted file mode 100644
index 92007d7..0000000
--- a/ui/src/frontend/track_registry.ts
+++ /dev/null
@@ -1,21 +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 {Registry} from '../common/registry';
-import {TrackCreator} from './track';
-
-/**
- * Global registry that maps types to TrackCreator.
- */
-export const trackRegistry = Registry.kindRegistry<TrackCreator>();
diff --git a/ui/src/frontend/viewer_page.ts b/ui/src/frontend/viewer_page.ts
index 8aeabde..57aa0a3 100644
--- a/ui/src/frontend/viewer_page.ts
+++ b/ui/src/frontend/viewer_page.ts
@@ -14,6 +14,7 @@
 
 import m from 'mithril';
 
+import {getScrollbarWidth} from '../base/dom_utils';
 import {clamp} from '../base/math_utils';
 import {Time} from '../base/time';
 import {Actions} from '../common/actions';
@@ -28,6 +29,7 @@
 import {createPage} from './pages';
 import {PanAndZoomHandler} from './pan_and_zoom_handler';
 import {AnyAttrsVnode, PanelContainer} from './panel_container';
+import {publishShowPanningHint} from './publish';
 import {TickmarkPanel} from './tickmark_panel';
 import {TimeAxisPanel} from './time_axis_panel';
 import {TimeSelectionPanel} from './time_selection_panel';
@@ -96,9 +98,7 @@
     const updateDimensions = () => {
       const rect = vnode.dom.getBoundingClientRect();
       frontendLocalState.updateLocalLimits(
-          0,
-          rect.width - TRACK_SHELL_WIDTH -
-              frontendLocalState.getScrollbarWidth());
+          0, rect.width - TRACK_SHELL_WIDTH - getScrollbarWidth());
     };
 
     updateDimensions();
@@ -193,6 +193,7 @@
           );
           frontendLocalState.areaY.start = dragStartY;
           frontendLocalState.areaY.end = currentY;
+          publishShowPanningHint();
         }
         raf.scheduleRedraw();
       },
diff --git a/ui/src/plugins/dev.perfetto.AndroidPerf/index.ts b/ui/src/plugins/dev.perfetto.AndroidPerf/index.ts
index 7658fb6..a521724 100644
--- a/ui/src/plugins/dev.perfetto.AndroidPerf/index.ts
+++ b/ui/src/plugins/dev.perfetto.AndroidPerf/index.ts
@@ -58,6 +58,59 @@
            SELECT * FROM android_binder_graph(-1000, 1000, -1000, 1000)`,
           'all process binder graph'),
     });
+
+    ctx.registerCommand({
+      id: 'dev.perfetto.AndroidPerf#ThreadClusterDistribution',
+      name: 'Run query: runtime cluster distribution for a thread',
+      callback: async (tid) => {
+        if (tid === undefined) {
+          tid = prompt('Enter a thread tid', '');
+          if (tid === null) return;
+        }
+        ctx.tabs.openQuery(`
+          INCLUDE PERFETTO MODULE common.cpus;
+          WITH
+            total_runtime AS (
+              SELECT sum(dur) AS total_runtime
+              FROM sched s
+              LEFT JOIN thread t
+                USING (utid)
+              WHERE t.tid = ${tid}
+            )
+            SELECT
+              c.size AS cluster,
+              sum(dur)/1e6 AS total_dur_ms,
+              sum(dur) * 1.0 / (SELECT * FROM total_runtime) AS percentage
+            FROM sched s
+            LEFT JOIN thread t
+              USING (utid)
+            LEFT JOIN cpus c
+              ON s.cpu = c.cpu_index
+            WHERE t.tid = ${tid}
+            GROUP BY 1`, `runtime cluster distrubtion for tid ${tid}`);
+      },
+    });
+
+    ctx.registerCommand({
+      id: 'dev.perfetto.AndroidPerf#SchedLatency',
+      name: 'Run query: top 50 sched latency for a thread',
+      callback: async (tid) => {
+        if (tid === undefined) {
+          tid = prompt('Enter a thread tid', '');
+          if (tid === null) return;
+        }
+        ctx.tabs.openQuery(`
+          SELECT ts.*, t.tid, t.name, tt.id AS track_id
+          FROM thread_state ts
+          LEFT JOIN thread_track tt
+           USING (utid)
+          LEFT JOIN thread t
+           USING (utid)
+          WHERE ts.state IN ('R', 'R+') AND tid = ${tid}
+           ORDER BY dur DESC
+          LIMIT 50`, `top 50 sched latency slice for tid ${tid}`);
+      },
+    });
   }
 }
 
diff --git a/ui/src/plugins/dev.perfetto.AndroidPerfTraceCounters/OWNERS b/ui/src/plugins/dev.perfetto.AndroidPerfTraceCounters/OWNERS
new file mode 100644
index 0000000..e5632b1
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.AndroidPerfTraceCounters/OWNERS
@@ -0,0 +1 @@
+lukechang@google.com
diff --git a/ui/src/plugins/dev.perfetto.AndroidPerfTraceCounters/index.ts b/ui/src/plugins/dev.perfetto.AndroidPerfTraceCounters/index.ts
new file mode 100644
index 0000000..46fbb46
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.AndroidPerfTraceCounters/index.ts
@@ -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.
+
+import {
+  Plugin,
+  PluginContext,
+  PluginContextTrace,
+  PluginDescriptor,
+} from '../../public';
+import {addDebugSliceTrack} from '../../tracks/debug/slice_track';
+
+class AndroidPerfTraceCounters implements Plugin {
+
+  onActivate(_: PluginContext): void {}
+
+  async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
+    ctx.registerCommand({
+      id: 'dev.perfetto.AndroidPerfTraceCounters#ThreadRuntimeIPC',
+      name: 'Add a track to show a thread runtime ipc',
+      callback: async (tid) => {
+        if (tid === undefined) {
+          tid = prompt('Enter a thread tid', '');
+          if (tid === null) return;
+        }
+        const sql_prefix = `
+WITH
+  sched_switch_ipc AS (
+    SELECT
+      ts,
+      EXTRACT_ARG(arg_set_id, 'prev_pid') AS tid,
+      EXTRACT_ARG(arg_set_id, 'prev_comm') AS thread_name,
+      EXTRACT_ARG(arg_set_id, 'inst') / (EXTRACT_ARG(arg_set_id, 'cyc') * 1.0) AS ipc,
+      EXTRACT_ARG(arg_set_id, 'inst') AS instruction,
+      EXTRACT_ARG(arg_set_id, 'cyc') AS cycle,
+      EXTRACT_ARG(arg_set_id, 'stallbm') AS stall_backend_mem,
+      EXTRACT_ARG(arg_set_id, 'l3dm') AS l3_cache_miss
+    FROM ftrace_event
+    WHERE name = 'sched_switch_with_ctrs' AND tid = ${tid}
+  ),
+  target_thread_sched_slice AS (
+    SELECT s.*, t.tid, t.name FROM sched s LEFT JOIN thread t USING (utid) WHERE t.tid = ${tid}
+  ),
+  target_thread_ipc_slice AS (
+    SELECT
+      (
+        SELECT
+          ts
+        FROM target_thread_sched_slice ts
+        WHERE ts.tid = ssi.tid AND ts.ts < ssi.ts
+        ORDER BY ts.ts DESC
+        LIMIT 1
+      ) AS ts,
+      (
+        SELECT
+          dur
+        FROM target_thread_sched_slice ts
+        WHERE ts.tid = ssi.tid AND ts.ts < ssi.ts
+        ORDER BY ts.ts DESC
+        LIMIT 1
+      ) AS dur,
+      ssi.ipc,
+      ssi.instruction,
+      ssi.cycle,
+      ssi.stall_backend_mem,
+      ssi.l3_cache_miss
+    FROM sched_switch_ipc ssi
+  )
+`
+
+        await addDebugSliceTrack(
+          ctx.engine,
+          {
+            sqlSource: sql_prefix + `
+SELECT * FROM target_thread_ipc_slice WHERE ts IS NOT NULL`,
+          },
+          'Rutime IPC:' + tid,
+          {ts: 'ts', dur: 'dur', name: 'ipc'},
+          ['instruction', 'cycle', 'stall_backend_mem', 'l3_cache_miss' ],
+        );
+        ctx.tabs.openQuery(sql_prefix + `
+SELECT
+  (sum(instruction) * 1.0 / sum(cycle)*1.0) AS avg_ipc,
+  sum(dur)/1e6 as total_runtime_ms,
+  sum(instruction) AS total_instructions,
+  sum(cycle) AS total_cycles,
+  sum(stall_backend_mem) as total_stall_backend_mem,
+  sum(l3_cache_miss) as total_l3_cache_miss
+FROM target_thread_ipc_slice WHERE ts IS NOT NULL`,
+          'target thread ipc statistic');
+      },
+    });
+  }
+}
+
+export const plugin: PluginDescriptor = {
+  pluginId: 'dev.perfetto.AndroidPerfTraceCounters',
+  plugin: AndroidPerfTraceCounters,
+};
diff --git a/ui/src/public/index.ts b/ui/src/public/index.ts
index 725c526..81f9225 100644
--- a/ui/src/public/index.ts
+++ b/ui/src/public/index.ts
@@ -15,10 +15,9 @@
 import m from 'mithril';
 
 import {Hotkey} from '../base/hotkeys';
-import {duration, Span, time} from '../base/time';
+import {duration, time} from '../base/time';
+import {ColorScheme} from '../common/colorizer';
 import {Store} from '../frontend/store';
-import {PxSpan, TimeScale} from '../frontend/time_scale';
-import {SliceRect} from '../frontend/track';
 import {EngineProxy} from '../trace_processor/engine';
 
 export {createStore, Store} from '../frontend/store';
@@ -32,6 +31,37 @@
   STR_NULL,
 } from '../trace_processor/query_result';
 
+export interface Slice {
+  // These properties are updated only once per query result when the Slice
+  // object is created and don't change afterwards.
+  readonly id: number;
+  readonly startNsQ: time;
+  readonly endNsQ: time;
+  readonly durNsQ: duration;
+  readonly ts: time;
+  readonly dur: duration;
+  readonly depth: number;
+  readonly flags: number;
+
+  // Each slice can represent some extra numerical information by rendering a
+  // portion of the slice with a lighter tint.
+  // |fillRatio\ describes the ratio of the normal area to the tinted area
+  // width of the slice, normalized between 0.0 -> 1.0.
+  // 0.0 means the whole slice is tinted.
+  // 1.0 means none of the slice is tinted.
+  // E.g. If |fillRatio| = 0.65 the slice will be rendered like this:
+  // [############|*******]
+  // ^------------^-------^
+  //     Normal     Light
+  readonly fillRatio: number;
+
+  // These can be changed by the Impl.
+  title: string;
+  subTitle: string;
+  colorScheme: ColorScheme;
+  isHighlighted: boolean;
+}
+
 export interface Command {
   // A unique id for this command.
   id: string;
@@ -132,14 +162,19 @@
   mountStore<State>(migrate: Migrate<State>): Store<State>;
 }
 
+export interface SliceRect {
+  left: number;
+  width: number;
+  top: number;
+  height: number;
+  visible: boolean;
+}
+
 export interface Track {
   onCreate(ctx: TrackContext): void;
   render(ctx: CanvasRenderingContext2D): void;
   onFullRedraw(): void;
-  getSliceRect(
-      visibleTimeScale: TimeScale, visibleWindow: Span<time, duration>,
-      windowSpan: PxSpan, tStart: time, tEnd: time, depth: number): SliceRect
-      |undefined;
+  getSliceRect(tStart: time, tEnd: time, depth: number): SliceRect|undefined;
   getHeight(): number;
   getTrackShellButtons(): m.Children;
   onMouseMove(position: {x: number, y: number}): void;
diff --git a/ui/src/trace_processor/query_result.ts b/ui/src/trace_processor/query_result.ts
index c568488..798b42f 100644
--- a/ui/src/trace_processor/query_result.ts
+++ b/ui/src/trace_processor/query_result.ts
@@ -150,7 +150,7 @@
   }
 
   toString() {
-    return `Query: ${this.query}\n` + super.toString();
+    return `${super.toString()}\nQuery:\n${this.query}`;
   }
 }
 
@@ -510,6 +510,10 @@
     return assertExists(this.allRowsPromise);
   }
 
+  get errorInfo(): QueryErrorInfo {
+    return this._errorInfo;
+  }
+
   private resolveOrReject(promise: Deferred<QueryResult>, arg: QueryResult) {
     if (this._error === undefined) {
       promise.resolve(arg);
@@ -689,11 +693,14 @@
     return this.isValid;
   }
 
+  private makeError(message: string): QueryError {
+    return new QueryError(message, this.resultObj.errorInfo);
+  }
 
   get(columnName: string): ColumnType {
     const res = this.rowData[columnName];
     if (res === undefined) {
-      throw new Error(
+      throw this.makeError(
           `Column '${columnName}' doesn't exist. ` +
           `Actual columns: [${this.columnNames.join(',')}]`);
     }
@@ -769,7 +776,7 @@
           break;
 
         default:
-          throw new Error(`Invalid cell type ${cellType}`);
+          throw this.makeError(`Invalid cell type ${cellType}`);
       }
     }  // For (cells)
     this.isValid = true;
@@ -802,7 +809,7 @@
     // Check that all the expected columns are present.
     for (const expectedCol of Object.keys(this.rowSpec)) {
       if (this.columnNames.indexOf(expectedCol) < 0) {
-        throw new Error(
+        throw this.makeError(
             `Column ${expectedCol} not found in the SQL result ` +
             `set {${this.columnNames.join(' ')}}`);
       }
@@ -845,9 +852,9 @@
         }
       }
       if (err.length > 0) {
-        throw new Error(
-            `Error @ row: ${Math.floor(i / numColumns)} col: '` +
-            `${colName}': ${err}`);
+        const row = Math.floor(i / numColumns);
+        const message = `Error @ row: ${row} col: '${colName}': ${err}`;
+        throw this.makeError(message);
       }
     }
     return true;
diff --git a/ui/src/tracks/async_slices/async_slice_track.ts b/ui/src/tracks/async_slices/async_slice_track.ts
new file mode 100644
index 0000000..d3d8f92
--- /dev/null
+++ b/ui/src/tracks/async_slices/async_slice_track.ts
@@ -0,0 +1,119 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import {BigintMath as BIMath} from '../../base/bigint_math';
+import {duration, time} from '../../base/time';
+import {SliceData, SliceTrackLEGACY} from '../../frontend/slice_track';
+import {EngineProxy} from '../../public';
+import {
+  LONG,
+  LONG_NULL,
+  NUM,
+  STR,
+} from '../../trace_processor/query_result';
+
+export const ASYNC_SLICE_TRACK_KIND = 'AsyncSliceTrack';
+
+export class AsyncSliceTrack extends SliceTrackLEGACY {
+  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<SliceData> {
+    if (this.maxDurNs === 0n) {
+      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.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
+      from experimental_slice_layout
+      where
+        filter_track_ids = '${this.trackIds.join(',')}' and
+        ts >= ${start - this.maxDurNs} and
+        ts <= ${end}
+      group by tsq, layout_depth
+      order by tsq, layout_depth
+    `);
+
+    const numRows = queryRes.numRows();
+    const slices: SliceData = {
+      start,
+      end,
+      resolution,
+      length: numRows,
+      strings: [],
+      sliceIds: new Float64Array(numRows),
+      starts: new BigInt64Array(numRows),
+      ends: new BigInt64Array(numRows),
+      depths: new Uint16Array(numRows),
+      titles: new Uint16Array(numRows),
+      isInstant: new Uint16Array(numRows),
+      isIncomplete: new Uint16Array(numRows),
+    };
+
+    const stringIndexes = new Map<string, number>();
+    function internString(str: string) {
+      let idx = stringIndexes.get(str);
+      if (idx !== undefined) return idx;
+      idx = slices.strings.length;
+      slices.strings.push(str);
+      stringIndexes.set(str, idx);
+      return idx;
+    }
+
+    const it = queryRes.iter({
+      tsq: LONG,
+      ts: LONG,
+      dur: LONG,
+      depth: NUM,
+      name: STR,
+      id: NUM,
+      isInstant: NUM,
+      isIncomplete: NUM,
+    });
+    for (let row = 0; it.valid(); it.next(), row++) {
+      const startQ = it.tsq;
+      const start = it.ts;
+      const dur = it.dur;
+      const end = start + dur;
+      const minEnd = startQ + resolution;
+      const endQ = BIMath.max(BIMath.quant(end, resolution), minEnd);
+
+      slices.starts[row] = startQ;
+      slices.ends[row] = endQ;
+      slices.depths[row] = it.depth;
+      slices.titles[row] = internString(it.name);
+      slices.sliceIds[row] = it.id;
+      slices.isInstant[row] = it.isInstant;
+      slices.isIncomplete[row] = it.isIncomplete;
+    }
+    return slices;
+  }
+}
diff --git a/ui/src/tracks/async_slices/async_slice_track_v2.ts b/ui/src/tracks/async_slices/async_slice_track_v2.ts
new file mode 100644
index 0000000..4e87c9b
--- /dev/null
+++ b/ui/src/tracks/async_slices/async_slice_track_v2.ts
@@ -0,0 +1,45 @@
+// 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 {NamedSliceTrack} from '../../frontend/named_slice_track';
+import {NewTrackArgs} from '../../frontend/track';
+import {Slice} from '../../public';
+
+export class AsyncSliceTrackV2 extends NamedSliceTrack {
+  constructor(
+      args: NewTrackArgs, maxDepth: number, private trackIds: number[]) {
+    super(args);
+    this.sliceLayout.maxDepth = maxDepth + 1;
+  }
+
+  getSqlSource(): string {
+    return `
+    select
+      ts,
+      dur,
+      layout_depth as depth,
+      ifnull(name, '[null]') as name,
+      id,
+      thread_dur as threadDur
+    from experimental_slice_layout
+    where filter_track_ids = '${this.trackIds.join(',')}'
+    `;
+  }
+
+  onUpdatedSlices(slices: Slice[]) {
+    for (const slice of slices) {
+      slice.isHighlighted = (slice === this.hoveredSlice);
+    }
+  }
+}
diff --git a/ui/src/tracks/async_slices/index.ts b/ui/src/tracks/async_slices/index.ts
index 97d43fa..eb3ad88 100644
--- a/ui/src/tracks/async_slices/index.ts
+++ b/ui/src/tracks/async_slices/index.ts
@@ -12,11 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {BigintMath as BIMath} from '../../base/bigint_math';
-import {duration, time} from '../../base/time';
-import {SliceData, SliceTrackBase} from '../../frontend/slice_track_base';
 import {
-  EngineProxy,
   Plugin,
   PluginContext,
   PluginContextTrace,
@@ -24,109 +20,17 @@
 } from '../../public';
 import {getTrackName} from '../../public/utils';
 import {
-  LONG,
-  LONG_NULL,
   NUM,
   NUM_NULL,
   STR,
   STR_NULL,
 } from '../../trace_processor/query_result';
 
+import {AsyncSliceTrack} from './async_slice_track';
+import {AsyncSliceTrackV2} from './async_slice_track_v2';
+
 export const ASYNC_SLICE_TRACK_KIND = 'AsyncSliceTrack';
 
-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<SliceData> {
-    if (this.maxDurNs === 0n) {
-      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.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
-      from experimental_slice_layout
-      where
-        filter_track_ids = '${this.trackIds.join(',')}' and
-        ts >= ${start - this.maxDurNs} and
-        ts <= ${end}
-      group by tsq, layout_depth
-      order by tsq, layout_depth
-    `);
-
-    const numRows = queryRes.numRows();
-    const slices: SliceData = {
-      start,
-      end,
-      resolution,
-      length: numRows,
-      strings: [],
-      sliceIds: new Float64Array(numRows),
-      starts: new BigInt64Array(numRows),
-      ends: new BigInt64Array(numRows),
-      depths: new Uint16Array(numRows),
-      titles: new Uint16Array(numRows),
-      isInstant: new Uint16Array(numRows),
-      isIncomplete: new Uint16Array(numRows),
-    };
-
-    const stringIndexes = new Map<string, number>();
-    function internString(str: string) {
-      let idx = stringIndexes.get(str);
-      if (idx !== undefined) return idx;
-      idx = slices.strings.length;
-      slices.strings.push(str);
-      stringIndexes.set(str, idx);
-      return idx;
-    }
-
-    const it = queryRes.iter({
-      tsq: LONG,
-      ts: LONG,
-      dur: LONG,
-      depth: NUM,
-      name: STR,
-      id: NUM,
-      isInstant: NUM,
-      isIncomplete: NUM,
-    });
-    for (let row = 0; it.valid(); it.next(), row++) {
-      const startQ = it.tsq;
-      const start = it.ts;
-      const dur = it.dur;
-      const end = start + dur;
-      const minEnd = startQ + resolution;
-      const endQ = BIMath.max(BIMath.quant(end, resolution), minEnd);
-
-      slices.starts[row] = startQ;
-      slices.ends[row] = endQ;
-      slices.depths[row] = it.depth;
-      slices.titles[row] = internString(it.name);
-      slices.sliceIds[row] = it.id;
-      slices.isInstant[row] = it.isInstant;
-      slices.isIncomplete[row] = it.isIncomplete;
-    }
-    return slices;
-  }
-}
-
 class AsyncSlicePlugin implements Plugin {
   onActivate(_ctx: PluginContext) {}
 
@@ -220,6 +124,20 @@
           );
         },
       });
+
+      ctx.registerStaticTrack({
+        uri: `perfetto.AsyncSlices#${rawName}.v2`,
+        displayName,
+        trackIds,
+        kind: ASYNC_SLICE_TRACK_KIND,
+        track: ({trackKey}) => {
+          return new AsyncSliceTrackV2(
+              {engine, trackKey},
+              maxDepth,
+              trackIds,
+          );
+        },
+      });
     }
   }
 
@@ -288,6 +206,20 @@
           );
         },
       });
+
+      ctx.registerStaticTrack({
+        uri: `perfetto.AsyncSlices#process.${pid}${rawTrackIds}.v2`,
+        displayName,
+        trackIds,
+        kind: ASYNC_SLICE_TRACK_KIND,
+        track: ({trackKey}) => {
+          return new AsyncSliceTrackV2(
+              {engine: ctx.engine, trackKey},
+              maxDepth,
+              trackIds,
+          );
+        },
+      });
     }
   }
 }
diff --git a/ui/src/tracks/chrome_critical_user_interactions/index.ts b/ui/src/tracks/chrome_critical_user_interactions/index.ts
index f77b967..8ddb3d4 100644
--- a/ui/src/tracks/chrome_critical_user_interactions/index.ts
+++ b/ui/src/tracks/chrome_critical_user_interactions/index.ts
@@ -20,17 +20,17 @@
 import {GenericSliceDetailsTab} from '../../frontend/generic_slice_details_tab';
 import {globals} from '../../frontend/globals';
 import {
-  NAMED_SLICE_ROW,
+  NAMED_ROW,
   NamedSliceTrackTypes,
 } from '../../frontend/named_slice_track';
-import {Slice} from '../../frontend/slice';
-import {NewTrackArgs, TrackBase} from '../../frontend/track';
 import {
+  NUM,
   Plugin,
   PluginContext,
   PluginContextTrace,
   PluginDescriptor,
   PrimaryTrackSortKey,
+  Slice,
   STR,
 } from '../../public';
 import {
@@ -41,30 +41,33 @@
 } from '../custom_sql_table_slices';
 
 import {PageLoadDetailsPanel} from './page_load_details_panel';
+import {StartupDetailsPanel} from './startup_details_panel';
 
 export const CRITICAL_USER_INTERACTIONS_KIND =
     'org.chromium.CriticalUserInteraction.track';
 
-export const CRITICAL_USER_INTERACTIONS_SLICE_ROW = {
-  ...NAMED_SLICE_ROW,
+export const CRITICAL_USER_INTERACTIONS_ROW = {
+  ...NAMED_ROW,
+  scopedId: NUM,
   type: STR,
 };
-export type CriticalUserInteractionSliceRow =
-    typeof CRITICAL_USER_INTERACTIONS_SLICE_ROW;
+export type CriticalUserInteractionRow = typeof CRITICAL_USER_INTERACTIONS_ROW;
 
 export interface CriticalUserInteractionSlice extends Slice {
+  scopedId: number;
   type: string;
 }
 
 export interface CriticalUserInteractionSliceTrackTypes extends
     NamedSliceTrackTypes {
   slice: CriticalUserInteractionSlice;
-  row: CriticalUserInteractionSliceRow;
+  row: CriticalUserInteractionRow;
 }
 
 enum CriticalUserInteractionType {
   UNKNOWN = 'Unknown',
   PAGE_LOAD = 'chrome_page_loads',
+  STARTUP = 'chrome_startups',
 }
 
 function convertToCriticalUserInteractionType(cujType: string):
@@ -72,6 +75,8 @@
   switch (cujType) {
     case CriticalUserInteractionType.PAGE_LOAD:
       return CriticalUserInteractionType.PAGE_LOAD;
+    case CriticalUserInteractionType.STARTUP:
+      return CriticalUserInteractionType.STARTUP;
     default:
       return CriticalUserInteractionType.UNKNOWN;
   }
@@ -81,13 +86,19 @@
     CustomSqlTableSliceTrack<CriticalUserInteractionSliceTrackTypes> {
   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'],
+      columns: [
+        // The scoped_id is not a unique identifier within the table; generate
+        // a unique id from type and scoped_id on the fly to use for slice
+        // selection.
+        'hash(type, scoped_id) AS id',
+        'scoped_id AS scopedId',
+        'name',
+        'ts',
+        'dur',
+        'type',
+      ],
       sqlTableName: 'chrome_interactions',
     };
   }
@@ -113,12 +124,37 @@
           },
         };
         break;
+      case CriticalUserInteractionType.STARTUP:
+        detailsPanel = {
+          kind: StartupDetailsPanel.kind,
+          config: {
+            sqlTableName: this.tableName,
+            title: 'Chrome Startup',
+          },
+        };
+        break;
       default:
         break;
     }
     return detailsPanel;
   }
 
+  onSliceClick(
+      args: OnSliceClickArgs<CriticalUserInteractionSliceTrackTypes['slice']>) {
+    const detailsPanelConfig = this.getDetailsPanel(args);
+    globals.makeSelection(Actions.selectGenericSlice({
+      id: args.slice.scopedId,
+      sqlTableName: this.tableName,
+      start: args.slice.ts,
+      duration: args.slice.dur,
+      trackKey: this.trackKey,
+      detailsPanelConfig: {
+        kind: detailsPanelConfig.kind,
+        config: detailsPanelConfig.config,
+      },
+    }));
+  }
+
   getSqlImports(): CustomSqlImportConfig {
     return {
       modules: ['chrome.interactions'],
@@ -126,14 +162,15 @@
   }
 
   getRowSpec(): CriticalUserInteractionSliceTrackTypes['row'] {
-    return CRITICAL_USER_INTERACTIONS_SLICE_ROW;
+    return CRITICAL_USER_INTERACTIONS_ROW;
   }
 
   rowToSlice(row: CriticalUserInteractionSliceTrackTypes['row']):
       CriticalUserInteractionSliceTrackTypes['slice'] {
     const baseSlice = super.rowToSlice(row);
+    const scopedId = row.scopedId;
     const type = row.type;
-    return {...baseSlice, type};
+    return {...baseSlice, scopedId, type};
   }
 }
 
diff --git a/ui/src/tracks/chrome_critical_user_interactions/startup_details_panel.ts b/ui/src/tracks/chrome_critical_user_interactions/startup_details_panel.ts
new file mode 100644
index 0000000..dbcde70
--- /dev/null
+++ b/ui/src/tracks/chrome_critical_user_interactions/startup_details_panel.ts
@@ -0,0 +1,147 @@
+// 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 {
+  BottomTab,
+  bottomTabRegistry,
+  NewBottomTabArgs,
+} from '../../frontend/bottom_tab';
+import {
+  GenericSliceDetailsTabConfig,
+} from '../../frontend/generic_slice_details_tab';
+import {DurationWidget} from '../../frontend/widgets/duration';
+import {Timestamp} from '../../frontend/widgets/timestamp';
+import {LONG, NUM, STR, STR_NULL} from '../../trace_processor/query_result';
+import {DetailsShell} from '../../widgets/details_shell';
+import {GridLayout, GridLayoutColumn} from '../../widgets/grid_layout';
+import {Section} from '../../widgets/section';
+import {SqlRef} from '../../widgets/sql_ref';
+import {dictToTreeNodes, Tree} from '../../widgets/tree';
+import {asUpid, Upid} from '../../frontend/sql_types';
+
+interface Data {
+  startupId: number;
+  eventName: string;
+  startupBeginTs: time;
+  durToFirstVisibleContent: duration;
+  launchCause?: string;
+  upid: Upid;
+}
+
+export class StartupDetailsPanel extends
+    BottomTab<GenericSliceDetailsTabConfig> {
+  static readonly kind = 'org.perfetto.StartupDetailsPanel';
+  private loaded = false;
+  private data: Data|undefined;
+
+  static create(args: NewBottomTabArgs): StartupDetailsPanel {
+    return new StartupDetailsPanel(args);
+  }
+
+  constructor(args: NewBottomTabArgs) {
+    super(args);
+    this.loadData();
+  }
+
+  private async loadData() {
+    const queryResult = await this.engine.query(`
+      SELECT
+        activity_id AS startupId,
+        name,
+        startup_begin_ts AS startupBeginTs,
+        CASE
+          WHEN first_visible_content_ts IS NULL THEN 0
+          ELSE first_visible_content_ts - startup_begin_ts
+        END AS durTofirstVisibleContent,
+        launch_cause AS launchCause,
+        browser_upid AS upid
+      FROM chrome_startups
+      WHERE id = ${this.config.id};
+    `);
+
+    const iter = queryResult.firstRow({
+      startupId: NUM,
+      name: STR,
+      startupBeginTs: LONG,
+      durTofirstVisibleContent: LONG,
+      launchCause: STR_NULL,
+      upid: NUM,
+    });
+
+    this.data = {
+      startupId: iter.startupId,
+      eventName: iter.name,
+      startupBeginTs: Time.fromRaw(iter.startupBeginTs),
+      durToFirstVisibleContent: iter.durTofirstVisibleContent,
+      upid: asUpid(iter.upid),
+    };
+
+    if (iter.launchCause) {
+      this.data.launchCause = iter.launchCause;
+    }
+
+    this.loaded = true;
+  }
+
+  private getDetailsDictionary() {
+    const details: {[key: string]: m.Child} = {};
+    if (this.data === undefined) return details;
+    details['Activity ID'] = this.data.startupId;
+    details['Browser Upid'] = this.data.upid;
+    details['Startup Event'] = this.data.eventName;
+    details['Startup Timestamp'] = m(Timestamp, {ts: this.data.startupBeginTs});
+    details['Duration to First Visible Content'] =
+        m(DurationWidget, {dur: this.data.durToFirstVisibleContent});
+    if (this.data.launchCause) {
+      details['Launch Cause'] = this.data.launchCause;
+    }
+    details['SQL ID'] =
+        m(SqlRef, {table: 'chrome_startups', id: this.config.id});
+    return details;
+  }
+
+  viewTab() {
+    if (this.isLoading()) {
+      return m('h2', 'Loading');
+    }
+
+    return m(
+        DetailsShell,
+        {
+          title: this.getTitle(),
+        },
+        m(GridLayout,
+          m(
+              GridLayoutColumn,
+              m(
+                  Section,
+                  {title: 'Details'},
+                  m(Tree, dictToTreeNodes(this.getDetailsDictionary())),
+                  ),
+              )));
+  }
+
+  getTitle(): string {
+    return this.config.title;
+  }
+
+  isLoading() {
+    return !this.loaded;
+  }
+}
+
+bottomTabRegistry.register(StartupDetailsPanel);
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 95d1924..5b87694 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
@@ -17,7 +17,7 @@
   NamedSliceTrack,
   NamedSliceTrackTypes,
 } from '../../frontend/named_slice_track';
-import {NewTrackArgs, TrackBase} from '../../frontend/track';
+import {NewTrackArgs} from '../../frontend/track';
 import {Engine} from '../../trace_processor/engine';
 import {NUM} from '../../trace_processor/query_result';
 
@@ -35,9 +35,6 @@
 export class ChromeTasksScrollJankTrack extends
     NamedSliceTrack<ChromeTasksScrollJankTrackTypes> {
   static readonly kind = 'org.chromium.ScrollJank.BrowserUIThreadLongTasks';
-  static create(args: NewTrackArgs): TrackBase {
-    return new ChromeTasksScrollJankTrack(args);
-  }
 
   constructor(args: NewTrackArgs) {
     super(args);
diff --git a/ui/src/tracks/chrome_scroll_jank/event_latency_details_panel.ts b/ui/src/tracks/chrome_scroll_jank/event_latency_details_panel.ts
index 5014d14..0510eeb 100644
--- a/ui/src/tracks/chrome_scroll_jank/event_latency_details_panel.ts
+++ b/ui/src/tracks/chrome_scroll_jank/event_latency_details_panel.ts
@@ -14,7 +14,7 @@
 
 import m from 'mithril';
 
-import {exists} from '../../base/utils';
+import {duration, time} from '../../base/time';
 import {raf} from '../../core/raf_scheduler';
 import {
   BottomTab,
@@ -27,8 +27,14 @@
 import {renderArguments} from '../../frontend/slice_args';
 import {renderDetails} from '../../frontend/slice_details';
 import {getSlice, SliceDetails, sliceRef} from '../../frontend/sql/slice';
-import {asSliceSqlId} from '../../frontend/sql_types';
-import {NUM} from '../../trace_processor/query_result';
+import {asSliceSqlId, SliceSqlId} from '../../frontend/sql_types';
+import {
+  ColumnDescriptor,
+  Table,
+  TableData,
+  widgetColumn,
+} from '../../frontend/tables/table';
+import {NUM, STR} from '../../trace_processor/query_result';
 import {DetailsShell} from '../../widgets/details_shell';
 import {GridLayout, GridLayoutColumn} from '../../widgets/grid_layout';
 import {Section} from '../../widgets/section';
@@ -36,6 +42,14 @@
 import {Tree, TreeNode} from '../../widgets/tree';
 
 import {
+  EventLatencyCauseThreadTracks,
+  EventLatencyStage,
+  getCauseLink,
+  getEventLatencyCauseTracks,
+  getScrollJankCauseStage,
+} from './scroll_jank_cause_link_utils';
+import {ScrollJankCauseMap} from './scroll_jank_cause_map';
+import {
   getScrollJankSlices,
   getSliceForTrack,
   ScrollJankSlice,
@@ -47,10 +61,22 @@
   static readonly kind = 'dev.perfetto.EventLatencySliceDetailsPanel';
 
   private loaded = false;
+  private name = '';
+  private topEventLatencyId: SliceSqlId|undefined = undefined;
 
   private sliceDetails?: SliceDetails;
   private jankySlice?: ScrollJankSlice;
 
+  // Whether this stage has caused jank. This is also true for top level
+  // EventLatency slices where a descendant is a cause of jank.
+  private isJankStage = false;
+
+  // For top level EventLatency slices - if any descendant is a cause of jank,
+  // this field stores information about that descendant slice. Otherwise, this
+  // is stores information about the current stage;
+  private relevantThreadStage: EventLatencyStage|undefined;
+  private relevantThreadTracks: EventLatencyCauseThreadTracks[] = [];
+
   static create(args: NewBottomTabArgs): EventLatencySliceDetailsPanel {
     return new EventLatencySliceDetailsPanel(args);
   }
@@ -62,8 +88,22 @@
   }
 
   async loadData() {
+    const queryResult = await this.engine.query(`
+      SELECT
+        name
+      FROM ${this.config.sqlTableName}
+      WHERE id = ${this.config.id}
+      `);
+
+    const iter = queryResult.firstRow({
+      name: STR,
+    });
+
+    this.name = iter.name;
+
     await this.loadSlice();
     await this.loadJankSlice();
+    await this.loadRelevantThreads();
     this.loaded = true;
   }
 
@@ -74,37 +114,152 @@
   }
 
   async loadJankSlice() {
-    if (exists(this.sliceDetails)) {
-      // Get the id for the top-level EventLatency slice (this or parent), as
-      // this id is used in the ScrollJankV3 track to identify the corresponding
-      // janky interval.
-      let eventLatencyId = -1;
-      if (this.sliceDetails.name == 'EventLatency') {
-        eventLatencyId = this.sliceDetails.id;
-      } else {
-        const queryResult = await this.engine.query(`
-          SELECT
-            id
-          FROM ancestor_slice(${this.sliceDetails.id})
-          WHERE name = 'EventLatency'
-        `);
-        const it = queryResult.iter({
-          id: NUM,
-        });
-        for (; it.valid(); it.next()) {
-          eventLatencyId = it.id;
-          break;
-        }
-      }
-
-      const possibleSlices =
-          await getScrollJankSlices(this.engine, eventLatencyId);
-      // We may not get any slices if the EventLatency doesn't indicate any
-      // jank occurred.
-      if (possibleSlices.length > 0) {
-        this.jankySlice = possibleSlices[0];
-      }
+    if (!this.sliceDetails) return;
+    // Get the id for the top-level EventLatency slice (this or parent), as
+    // this id is used in the ScrollJankV3 track to identify the corresponding
+    // janky interval.
+    if (this.sliceDetails.name === 'EventLatency') {
+      this.topEventLatencyId = this.sliceDetails.id;
+    } else {
+      this.topEventLatencyId =
+          asSliceSqlId(await this.getOldestAncestorSliceId());
     }
+
+    const possibleSlices =
+        await getScrollJankSlices(this.engine, this.topEventLatencyId);
+    // We may not get any slices if the EventLatency doesn't indicate any
+    // jank occurred.
+    if (possibleSlices.length > 0) {
+      this.jankySlice = possibleSlices[0];
+    }
+  }
+
+  async loadRelevantThreads() {
+    if (!this.sliceDetails) return;
+    if (!this.topEventLatencyId) return;
+
+    // Relevant threads should only be available on a "Janky" EventLatency
+    // slice to allow the user to jump to the possible cause of jank.
+    if (this.sliceDetails.name === 'EventLatency' && !this.jankySlice) return;
+
+    const possibleScrollJankStage =
+        await getScrollJankCauseStage(this.engine, this.topEventLatencyId);
+    if (this.sliceDetails.name === 'EventLatency') {
+      this.isJankStage = true;
+      this.relevantThreadStage = possibleScrollJankStage;
+    } else {
+      if (possibleScrollJankStage &&
+          this.sliceDetails.name === possibleScrollJankStage.name) {
+        this.isJankStage = true;
+      }
+      this.relevantThreadStage = {
+        name: this.sliceDetails.name,
+        eventLatencyId: this.topEventLatencyId,
+        ts: this.sliceDetails.ts,
+        dur: this.sliceDetails.dur,
+      };
+    }
+
+    if (this.relevantThreadStage) {
+      this.relevantThreadTracks = await getEventLatencyCauseTracks(
+          this.engine, this.relevantThreadStage);
+    }
+  }
+
+  private getRelevantLinks(): m.Child {
+    if (!this.sliceDetails) return undefined;
+
+    // Relevant threads should only be available on a "Janky" EventLatency
+    // slice to allow the user to jump to the possible cause of jank.
+    if (this.sliceDetails.name === 'EventLatency' &&
+        !this.relevantThreadStage) {
+      return undefined;
+    }
+
+    const name = this.relevantThreadStage ? this.relevantThreadStage.name :
+                                            this.sliceDetails.name;
+    const ts = this.relevantThreadStage ? this.relevantThreadStage.ts :
+                                          this.sliceDetails.ts;
+    const dur = this.relevantThreadStage ? this.relevantThreadStage.dur :
+                                           this.sliceDetails.dur;
+    const stageDetails = ScrollJankCauseMap.getEventLatencyDetails(name);
+    if (stageDetails === undefined) return undefined;
+
+    const childWidgets: m.Child[] = [];
+    childWidgets.push(m(TextParagraph, {text: stageDetails.description}));
+
+    interface RelevantThreadRow {
+      description: string;
+      tracks: EventLatencyCauseThreadTracks;
+      ts: time;
+      dur: duration;
+    }
+
+    const columns: ColumnDescriptor<RelevantThreadRow>[] = [
+      widgetColumn<RelevantThreadRow>(
+          'Relevant Thread', (x) => getCauseLink(x.tracks, x.ts, x.dur)),
+      widgetColumn<RelevantThreadRow>(
+          'Description',
+          (x) => {
+            if (x.description === '') {
+              return x.description;
+            } else {
+              return m(TextParagraph, {text: x.description});
+            }
+          }),
+    ];
+
+    const trackLinks: RelevantThreadRow[] = [];
+
+    for (let i = 0; i < this.relevantThreadTracks.length; i++) {
+      const track = this.relevantThreadTracks[i];
+      let description = '';
+      if (i == 0 || track.thread != this.relevantThreadTracks[i - 1].thread) {
+        description = track.causeDescription;
+      }
+      trackLinks.push({
+        description: description,
+        tracks: this.relevantThreadTracks[i],
+        ts: ts,
+        dur: dur,
+      });
+    }
+
+    const tableData = new TableData(trackLinks);
+
+    if (trackLinks.length > 0) {
+      childWidgets.push(m(Table, {
+        data: tableData,
+        columns: columns,
+      }));
+    }
+
+    return m(
+        Section,
+        {title: this.isJankStage ? `Jank Cause: ${name}` : name},
+        childWidgets);
+  }
+
+  private async getOldestAncestorSliceId(): Promise<number> {
+    let eventLatencyId = -1;
+    if (!this.sliceDetails) return eventLatencyId;
+    const queryResult = await this.engine.query(`
+      SELECT
+        id
+      FROM ancestor_slice(${this.sliceDetails.id})
+      WHERE name = 'EventLatency'
+    `);
+
+    const it = queryResult.iter({
+      id: NUM,
+    });
+
+    for (; it.valid(); it.next()) {
+      eventLatencyId = it.id;
+      break;
+    }
+
+    return eventLatencyId;
   }
 
   private getLinksSection(): m.Child {
@@ -114,20 +269,20 @@
         m(
             Tree,
             m(TreeNode, {
-              left: exists(this.sliceDetails) ?
+              left: this.sliceDetails ?
                   sliceRef(
                       this.sliceDetails,
                       'EventLatency in context of other Input events') :
                   'EventLatency in context of other Input events',
-              right: exists(this.sliceDetails) ? '' : 'N/A',
+              right: this.sliceDetails ? '' : 'N/A',
             }),
             m(TreeNode, {
-              left: exists(this.jankySlice) ? getSliceForTrack(
-                                                  this.jankySlice,
-                                                  ScrollJankV3Track.kind,
-                                                  'Jank Interval') :
-                                              'Jank Interval',
-              right: exists(this.jankySlice) ? '' : 'N/A',
+              left: this.jankySlice ? getSliceForTrack(
+                                          this.jankySlice,
+                                          ScrollJankV3Track.kind,
+                                          'Jank Interval') :
+                                      'Jank Interval',
+              right: this.jankySlice ? '' : 'N/A',
             }),
             ),
     );
@@ -160,23 +315,34 @@
   }
 
   viewTab() {
-    if (exists(this.sliceDetails)) {
+    if (this.sliceDetails) {
       const slice = this.sliceDetails;
+
+      const rightSideWidgets: m.Child[] = [];
+      rightSideWidgets.push(
+          m(Section,
+            {title: 'Description'},
+            m('.div', this.getDescriptionText())));
+
+      const stageWidget = this.getRelevantLinks();
+      if (stageWidget) {
+        rightSideWidgets.push(stageWidget);
+      }
+      rightSideWidgets.push(this.getLinksSection());
+
       return m(
           DetailsShell,
           {
             title: 'Slice',
-            description: slice.name,
+            description: this.name,
           },
           m(GridLayout,
             m(GridLayoutColumn,
               renderDetails(slice),
-              renderArguments(this.engine, slice)),
-            m(GridLayoutColumn,
               m(Section,
-                {title: 'Description'},
-                m('.div', this.getDescriptionText())),
-              this.getLinksSection())),
+                {title: 'Arguments'},
+                m(Tree, renderArguments(this.engine, slice)))),
+            m(GridLayoutColumn, rightSideWidgets)),
       );
     } else {
       return m(DetailsShell, {title: 'Slice', description: 'Loading...'});
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 fcd0b5d..b85116f 100644
--- a/ui/src/tracks/chrome_scroll_jank/event_latency_track.ts
+++ b/ui/src/tracks/chrome_scroll_jank/event_latency_track.ts
@@ -12,15 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {
-  getColorForSlice,
-} from '../../common/colorizer';
 import {globals} from '../../frontend/globals';
-import {
-  NamedSliceTrackTypes,
-} from '../../frontend/named_slice_track';
-import {NewTrackArgs, TrackBase} from '../../frontend/track';
-import {PrimaryTrackSortKey} from '../../public';
+import {NamedRow, NamedSliceTrackTypes} from '../../frontend/named_slice_track';
+import {NewTrackArgs} from '../../frontend/track';
+import {PrimaryTrackSortKey, Slice} from '../../public';
 import {
   CustomSqlDetailsPanelConfig,
   CustomSqlTableDefConfig,
@@ -33,7 +28,7 @@
   ScrollJankPluginState,
   ScrollJankTracks as DecideTracksResult,
 } from './index';
-import {DEEP_RED_COLOR, RED_COLOR} from './jank_colors';
+import {JANK_COLOR} from './jank_colors';
 
 export const JANKY_LATENCY_NAME = 'Janky EventLatency';
 
@@ -48,11 +43,7 @@
     CustomSqlTableSliceTrack<EventLatencyTrackTypes> {
   static readonly kind = CHROME_EVENT_LATENCY_TRACK_KIND;
 
-  static create(args: NewTrackArgs): TrackBase {
-    return new EventLatencyTrack(args);
-  }
-
-  constructor(args: NewTrackArgs) {
+  constructor(args: NewTrackArgs, private baseTable: string) {
     super(args);
     ScrollJankPluginState.getInstance().registerTrack({
       kind: EventLatencyTrack.kind,
@@ -68,7 +59,7 @@
   }
 
   getSqlSource(): string {
-    return `SELECT * FROM ${this.config.baseTable}`;
+    return `SELECT * FROM ${this.baseTable}`;
   }
 
   getDetailsPanel(): CustomSqlDetailsPanelConfig {
@@ -80,21 +71,18 @@
 
   getSqlDataSource(): CustomSqlTableDefConfig {
     return {
-      sqlTableName: this.config.baseTable,
+      sqlTableName: this.baseTable,
     };
   }
 
-  // TODO(stevegolton): The janky event color should be assigned in rowToSlice.
-  // For example:
-
-  // rowToSlice(row: NamedSliceRow): Slice {
-  //   const baseSlice = super.rowToSlice(row);
-  //   if (baseSlice.title === JANKY_LATENCY_NAME) {
-  //     return {...baseSlice, baseColor: RED_COLOR};
-  //   } else {
-  //     return baseSlice;
-  //   }
-  // }
+  rowToSlice(row: NamedRow): Slice {
+    const baseSlice = super.rowToSlice(row);
+    if (baseSlice.title === JANKY_LATENCY_NAME) {
+      return {...baseSlice, colorScheme: JANK_COLOR};
+    } else {
+      return baseSlice;
+    }
+  }
 
   onUpdatedSlices(slices: EventLatencyTrackTypes['slice'][]) {
     for (const slice of slices) {
@@ -105,16 +93,7 @@
 
       const highlighted = globals.state.highlightedSliceId === slice.id;
       const hasFocus = highlighted || isSelected;
-
-      if (slice.title === JANKY_LATENCY_NAME) {
-        if (hasFocus) {
-          slice.baseColor = DEEP_RED_COLOR;
-        } else {
-          slice.baseColor = RED_COLOR;
-        }
-      } else {
-        slice.baseColor = getColorForSlice(slice.title, hasFocus);
-      }
+      slice.isHighlighted = !!hasFocus;
     }
     super.onUpdatedSlices(slices);
   }
diff --git a/ui/src/tracks/chrome_scroll_jank/index.ts b/ui/src/tracks/chrome_scroll_jank/index.ts
index bec5a05..34df4e1 100644
--- a/ui/src/tracks/chrome_scroll_jank/index.ts
+++ b/ui/src/tracks/chrome_scroll_jank/index.ts
@@ -38,6 +38,7 @@
   EventLatencyTrack,
   JANKY_LATENCY_NAME,
 } from './event_latency_track';
+import {ScrollJankCauseMap} from './scroll_jank_cause_map';
 import {
   addScrollJankV3ScrollTrack,
   ScrollJankV3Track,
@@ -117,7 +118,7 @@
   }
 }
 
-export async function getScrollJankTracks(_engine: Engine):
+export async function getScrollJankTracks(engine: Engine):
     Promise<ScrollJankTrackGroup> {
   const result: ScrollJankTracks = {
     tracksToAdd: [],
@@ -149,6 +150,7 @@
     fixedOrdering: true,
   });
 
+  await ScrollJankCauseMap.initialize(engine);
   return {tracks: result, addTrackGroup};
 }
 
@@ -238,9 +240,11 @@
           'FIRST_GESTURE_SCROLL_UPDATE',
           'GESTURE_SCROLL_UPDATE',
           'INERTIAL_GESTURE_SCROLL_UPDATE')
-        AND HAS_DESCENDANT_SLICE_WITH_NAME(
+        AND has_descendant_slice_with_name(
           id,
-          'SubmitCompositorFrameToPresentationCompositorFrame')`,
+          'SubmitCompositorFrameToPresentationCompositorFrame')
+        AND name = 'EventLatency'
+        AND depth = 0`,
     });
 
     // Table name must be unique - it cannot include '-' characters or begin
@@ -248,22 +252,22 @@
     const baseTable =
         `table_${uuidv4().split('-').join('_')}_janky_event_latencies_v3`;
     const tableDefSql = `CREATE TABLE ${baseTable} AS
-        WITH event_latencies AS (
+        WITH
+        event_latencies AS MATERIALIZED (
           ${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)
+        ),
+        latency_stages AS (
+          SELECT
+            stage.id,
+            stage.ts,
+            stage.dur,
+            stage.track_id,
+            stage.name,
+            stage.depth,
+            event.id as event_latency_id
+          FROM event_latencies event
+          JOIN descendant_slice(event.id) stage
+        )
       SELECT
         id,
         ts,
@@ -279,14 +283,17 @@
       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;`;
+        stage.id,
+        stage.ts,
+        stage.dur,
+        stage.name,
+        stage.depth + (
+          (
+            SELECT depth FROM event_latencies
+            WHERE id = stage.event_latency_id
+          ) * 3
+        ) AS depth
+      FROM latency_stages stage;`;
 
     await ctx.engine.query(
         `INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_jank_intervals`);
@@ -297,16 +304,7 @@
       displayName: 'Chrome Scroll Input Latencies',
       kind: EventLatencyTrack.kind,
       track: ({trackKey}) => {
-        const track = new EventLatencyTrack({
-          engine: ctx.engine,
-          trackKey,
-        });
-
-        track.config = {
-          baseTable,
-        };
-
-        return track;
+        return new EventLatencyTrack({engine: ctx.engine, trackKey}, baseTable);
       },
     });
   }
diff --git a/ui/src/tracks/chrome_scroll_jank/jank_colors.ts b/ui/src/tracks/chrome_scroll_jank/jank_colors.ts
index f556f7d..22d508e 100644
--- a/ui/src/tracks/chrome_scroll_jank/jank_colors.ts
+++ b/ui/src/tracks/chrome_scroll_jank/jank_colors.ts
@@ -12,16 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {Color} from '../../common/colorizer';
+import {HSLColor} from '../../common/color';
+import {makeColorScheme} from '../../common/colorizer';
 
-export const RED_COLOR: Color = {
-  h: 7,
-  s: 100,
-  l: 46,
-};
-
-export const DEEP_RED_COLOR: Color = {
-  h: 11,
-  s: 100,
-  l: 32,
-};
+export const JANK_COLOR = makeColorScheme(new HSLColor([343, 100, 43]));
diff --git a/ui/src/tracks/chrome_scroll_jank/scroll_details_panel.ts b/ui/src/tracks/chrome_scroll_jank/scroll_details_panel.ts
index 63e913f..2837ce1 100644
--- a/ui/src/tracks/chrome_scroll_jank/scroll_details_panel.ts
+++ b/ui/src/tracks/chrome_scroll_jank/scroll_details_panel.ts
@@ -31,6 +31,7 @@
   numberColumn,
   Table,
   TableData,
+  widgetColumn,
 } from '../../frontend/tables/table';
 import {DurationWidget} from '../../frontend/widgets/duration';
 import {Timestamp} from '../../frontend/widgets/timestamp';
@@ -55,11 +56,6 @@
 } from './scroll_jank_slice';
 import {ScrollJankV3Track} from './scroll_jank_v3_track';
 
-function widgetColumn<T>(
-    name: string, getter: (t: T) => m.Child): ColumnDescriptor<T> {
-  return new ColumnDescriptor<T>(name, getter);
-}
-
 interface Data {
   // Scroll ID.
   id: number;
diff --git a/ui/src/tracks/chrome_scroll_jank/scroll_jank_cause_link_utils.ts b/ui/src/tracks/chrome_scroll_jank/scroll_jank_cause_link_utils.ts
new file mode 100644
index 0000000..7209356
--- /dev/null
+++ b/ui/src/tracks/chrome_scroll_jank/scroll_jank_cause_link_utils.ts
@@ -0,0 +1,218 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import m from 'mithril';
+
+import {Icons} from '../../base/semantic_icons';
+import {duration, Time, time} from '../../base/time';
+import {exists} from '../../base/utils';
+import {Actions} from '../../common/actions';
+import {globals} from '../../frontend/globals';
+import {
+  focusHorizontalRange,
+  verticalScrollToTrack,
+} from '../../frontend/scroll_helper';
+import {SliceSqlId} from '../../frontend/sql_types';
+import {EngineProxy} from '../../trace_processor/engine';
+import {LONG, NUM, STR} from '../../trace_processor/query_result';
+import {Anchor} from '../../widgets/anchor';
+
+import {
+  CauseProcess,
+  CauseThread,
+  ScrollJankCauseMap,
+} from './scroll_jank_cause_map';
+
+const UNKNOWN_NAME = 'Unknown';
+
+export interface EventLatencyStage {
+  name: string;
+  // Slice id of the top level EventLatency slice (not a stage).
+  eventLatencyId: SliceSqlId;
+  ts: time;
+  dur: duration;
+}
+
+export interface EventLatencyCauseThreadTracks {
+  // A thread may have multiple tracks associated with it (e.g. from ATrace
+  // events).
+  trackIds: number[];
+  thread: CauseThread;
+  causeDescription: string;
+}
+
+export async function getScrollJankCauseStage(
+    engine: EngineProxy,
+    eventLatencyId: SliceSqlId): Promise<EventLatencyStage|undefined> {
+  const queryResult = await engine.query(`
+    SELECT
+      IFNULL(cause_of_jank, '${UNKNOWN_NAME}') AS causeOfJank,
+      IFNULL(sub_cause_of_jank, '${UNKNOWN_NAME}') AS subCauseOfJank,
+      IFNULL(substage.ts, -1) AS ts,
+      IFNULL(substage.dur, -1) AS dur
+    FROM chrome_janky_frame_presentation_intervals
+      JOIN descendant_slice(event_latency_id) substage
+    WHERE event_latency_id = ${eventLatencyId}
+      AND substage.name = COALESCE(sub_cause_of_jank, cause_of_jank)
+  `);
+
+  const causeIt = queryResult.iter({
+    causeOfJank: STR,
+    subCauseOfJank: STR,
+    ts: LONG,
+    dur: LONG,
+  });
+
+  for (; causeIt.valid(); causeIt.next()) {
+    const causeOfJank = causeIt.causeOfJank;
+    const subCauseOfJank = causeIt.subCauseOfJank;
+
+    if (causeOfJank == '' || causeOfJank == UNKNOWN_NAME) return undefined;
+    const cause = subCauseOfJank == UNKNOWN_NAME ? causeOfJank : subCauseOfJank;
+    const stageDetails: EventLatencyStage = {
+      name: cause,
+      eventLatencyId: eventLatencyId,
+      ts: Time.fromRaw(causeIt.ts),
+      dur: causeIt.dur,
+    };
+
+    return stageDetails;
+  }
+
+  return undefined;
+}
+
+export async function getEventLatencyCauseTracks(
+    engine: EngineProxy, scrollJankCauseStage: EventLatencyStage):
+    Promise<EventLatencyCauseThreadTracks[]> {
+  const threadTracks: EventLatencyCauseThreadTracks[] = [];
+  const causeDetails =
+      ScrollJankCauseMap.getEventLatencyDetails(scrollJankCauseStage.name);
+  if (causeDetails === undefined) return threadTracks;
+
+  for (const cause of causeDetails.jankCauses) {
+    switch (cause.process) {
+      case CauseProcess.RENDERER:
+      case CauseProcess.BROWSER:
+      case CauseProcess.GPU:
+        const tracksForProcess = await getChromeCauseTracks(
+            engine,
+            scrollJankCauseStage.eventLatencyId,
+            cause.process,
+            cause.thread);
+        for (const track of tracksForProcess) {
+          track.causeDescription = cause.description;
+          threadTracks.push(track);
+        }
+        break;
+      case CauseProcess.UNKNOWN:
+      default:
+        break;
+    }
+  }
+
+  return threadTracks;
+}
+
+async function getChromeCauseTracks(
+    engine: EngineProxy,
+    eventLatencySliceId: number,
+    processName: CauseProcess,
+    threadName: CauseThread): Promise<EventLatencyCauseThreadTracks[]> {
+  const queryResult = await engine.query(`
+      INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_jank_cause_utils;
+
+      SELECT DISTINCT
+        utid,
+        id AS trackId
+      FROM thread_track
+      WHERE utid IN (
+        SELECT DISTINCT
+          utid
+        FROM chrome_select_scroll_jank_cause_thread(
+          ${eventLatencySliceId},
+          '${processName}',
+          '${threadName}'
+        )
+      );
+  `);
+
+  const it = queryResult.iter({
+    utid: NUM,
+    trackId: NUM,
+  });
+
+  const threadsWithTrack: {[id: number]: EventLatencyCauseThreadTracks;} = {};
+  const utids: number[] = [];
+  for (; it.valid(); it.next()) {
+    const utid = it.utid;
+    if (!(utid in threadsWithTrack)) {
+      threadsWithTrack[utid] = {
+        trackIds: [it.trackId],
+        thread: threadName,
+        causeDescription: '',
+      };
+      utids.push(utid);
+    } else {
+      threadsWithTrack[utid].trackIds.push(it.trackId);
+    }
+  }
+
+  return utids.map((each) => threadsWithTrack[each]);
+}
+
+export function getCauseLink(
+    threadTracks: EventLatencyCauseThreadTracks,
+    ts: time|undefined,
+    dur: duration|undefined): m.Child {
+  const trackKeys: string[] = [];
+  for (const trackId of threadTracks.trackIds) {
+    const trackKey = globals.state.trackKeyByTrackId[trackId];
+    if (trackKey === undefined) {
+      return `Could not locate track ${trackId} for thread ${
+          threadTracks.thread} in the global state`;
+    }
+    trackKeys.push(trackKey);
+  }
+
+  if (trackKeys.length == 0) {
+    return `No valid tracks for thread ${threadTracks.thread}.`;
+  }
+
+  // Fixed length of a container to ensure that the icon does not overlap with
+  // the text due to table formatting.
+  return m(
+      `div[style='width:250px']`,
+      m(Anchor,
+        {
+          icon: Icons.UpdateSelection,
+          onclick: () => {
+            verticalScrollToTrack(trackKeys[0], true);
+            if (exists(ts) && exists(dur)) {
+              focusHorizontalRange(ts, Time.fromRaw(ts + dur), 0.3);
+              globals.frontendLocalState.selectArea(
+                  ts, Time.fromRaw(ts + dur), trackKeys);
+
+              globals.dispatch(Actions.selectArea({
+                area: {
+                  start: ts,
+                  end: Time.fromRaw(ts + dur),
+                  tracks: trackKeys,
+                },
+              }));
+            }
+          },
+        },
+        threadTracks.thread));
+}
diff --git a/ui/src/tracks/chrome_scroll_jank/scroll_jank_cause_map.ts b/ui/src/tracks/chrome_scroll_jank/scroll_jank_cause_map.ts
new file mode 100644
index 0000000..c4842ef
--- /dev/null
+++ b/ui/src/tracks/chrome_scroll_jank/scroll_jank_cause_map.ts
@@ -0,0 +1,149 @@
+// 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 {exists} from '../../base/utils';
+import {Engine} from '../../trace_processor/engine';
+import {STR} from '../../trace_processor/query_result';
+
+export enum CauseProcess {
+  UNKNOWN,
+  BROWSER = 'Browser',
+  RENDERER = 'Renderer',
+  GPU = 'GPU',
+}
+
+export enum CauseThread {
+  UNKNOWN,
+  BROWSER_MAIN = 'CrBrowserMain',
+  RENDERER_MAIN = 'CrRendererMain',
+  COMPOSITOR = 'Compositor',
+  CHROME_CHILD_IO_THREAD = 'Chrome_ChildIOThread',
+  VIZ_COMPOSITOR = 'VizCompositorThread',
+  SURFACE_FLINGER = 'surfaceflinger'
+}
+
+export interface ScrollJankCause {
+  description: string;
+  process: CauseProcess;
+  thread: CauseThread;
+}
+
+export interface EventLatencyStageDetails {
+  description: string;
+  jankCauses: ScrollJankCause[];
+}
+
+export interface ScrollJankCauseMapInternal {
+  // Key corresponds with the EventLatency stage.
+  [key: string]: EventLatencyStageDetails;
+}
+
+function getScrollJankProcess(process: string): CauseProcess {
+  switch (process) {
+    case CauseProcess.BROWSER:
+      return CauseProcess.BROWSER;
+    case CauseProcess.RENDERER:
+      return CauseProcess.RENDERER;
+    case CauseProcess.GPU:
+      return CauseProcess.GPU;
+    default:
+      return CauseProcess.UNKNOWN;
+  }
+}
+
+function getScrollJankThread(thread: string): CauseThread {
+  switch (thread) {
+    case CauseThread.BROWSER_MAIN:
+      return CauseThread.BROWSER_MAIN;
+    case CauseThread.RENDERER_MAIN:
+      return CauseThread.RENDERER_MAIN;
+    case CauseThread.CHROME_CHILD_IO_THREAD:
+      return CauseThread.CHROME_CHILD_IO_THREAD;
+    case CauseThread.COMPOSITOR:
+      return CauseThread.COMPOSITOR;
+    case CauseThread.VIZ_COMPOSITOR:
+      return CauseThread.VIZ_COMPOSITOR;
+    case CauseThread.SURFACE_FLINGER:
+      return CauseThread.SURFACE_FLINGER;
+    default:
+      return CauseThread.UNKNOWN;
+  }
+}
+
+export class ScrollJankCauseMap {
+  private static instance: ScrollJankCauseMap;
+  private causes: ScrollJankCauseMapInternal;
+
+  private constructor() {
+    this.causes = {};
+  }
+
+  private async initializeCauseMap(engine: Engine) {
+    const queryResult = await engine.query(`
+      INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_jank_cause_map;
+
+      SELECT
+        IFNULL(name, '') AS name,
+        IFNULL(description, '') AS description,
+        IFNULL(cause_process, '') AS causeProcess,
+        IFNULL(cause_thread, '') AS causeThread,
+        IFNULL(cause_description, '') AS causeDescription
+      FROM chrome_scroll_jank_causes_with_event_latencies;
+    `);
+
+    const iter = queryResult.iter({
+      name: STR,
+      description: STR,
+      causeProcess: STR,
+      causeThread: STR,
+      causeDescription: STR,
+    });
+
+    for (; iter.valid(); iter.next()) {
+      const eventLatencyStage = iter.name;
+      if (!(eventLatencyStage in this.causes)) {
+        this.causes[eventLatencyStage] = {
+          description: iter.description,
+          jankCauses: [] as ScrollJankCause[],
+        };
+      }
+
+      const causeProcess = getScrollJankProcess(iter.causeProcess);
+      const causeThread = getScrollJankThread(iter.causeThread);
+
+      this.causes[eventLatencyStage].jankCauses.push({
+        description: iter.causeDescription,
+        process: causeProcess,
+        thread: causeThread,
+      });
+    }
+  }
+
+  // Must be called before this item is accessed, as the object is populated
+  // from SQL data.
+  public static async initialize(engine: Engine) {
+    if (!exists(ScrollJankCauseMap.instance)) {
+      ScrollJankCauseMap.instance = new ScrollJankCauseMap();
+      await ScrollJankCauseMap.instance.initializeCauseMap(engine);
+    }
+  }
+
+  public static getEventLatencyDetails(eventLatency: string):
+      EventLatencyStageDetails|undefined {
+    if (eventLatency in ScrollJankCauseMap.instance.causes) {
+      return ScrollJankCauseMap.instance.causes[eventLatency];
+    }
+    return undefined;
+  }
+}
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 70f6f0a..14d296a 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,13 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {
-  getColorForSlice,
-} from '../../common/colorizer';
 import {globals} from '../../frontend/globals';
-import {NamedSliceTrackTypes} from '../../frontend/named_slice_track';
-import {NewTrackArgs, TrackBase} from '../../frontend/track';
-import {PrimaryTrackSortKey} from '../../public';
+import {NamedRow, NamedSliceTrackTypes} from '../../frontend/named_slice_track';
+import {NewTrackArgs} from '../../frontend/track';
+import {PrimaryTrackSortKey, Slice} from '../../public';
 import {
   CustomSqlDetailsPanelConfig,
   CustomSqlTableDefConfig,
@@ -31,7 +28,7 @@
   ScrollJankPluginState,
   ScrollJankTracks as DecideTracksResult,
 } from './index';
-import {DEEP_RED_COLOR, RED_COLOR} from './jank_colors';
+import {JANK_COLOR} from './jank_colors';
 import {ScrollJankV3DetailsPanel} from './scroll_jank_v3_details_panel';
 
 const UNKNOWN_SLICE_NAME = 'Unknown';
@@ -41,10 +38,6 @@
     CustomSqlTableSliceTrack<NamedSliceTrackTypes> {
   static readonly kind = 'org.chromium.ScrollJank.scroll_jank_v3_track';
 
-  static create(args: NewTrackArgs): TrackBase {
-    return new ScrollJankV3Track(args);
-  }
-
   constructor(args: NewTrackArgs) {
     super(args);
     ScrollJankPluginState.getInstance().registerTrack({
@@ -87,6 +80,24 @@
     ScrollJankPluginState.getInstance().unregisterTrack(ScrollJankV3Track.kind);
   }
 
+  rowToSlice(row: NamedRow): Slice {
+    const slice = super.rowToSlice(row);
+
+    let stage = slice.title.substring(0, slice.title.indexOf(JANK_SLICE_NAME));
+    // Stage may include substage, in which case we use the substage for
+    // color selection.
+    const separator = '::';
+    if (stage.indexOf(separator) != -1) {
+      stage = stage.substring(stage.indexOf(separator) + separator.length);
+    }
+
+    if (stage == UNKNOWN_SLICE_NAME) {
+      return {...slice, colorScheme: JANK_COLOR};
+    } else {
+      return slice;
+    }
+  }
+
   onUpdatedSlices(slices: EventLatencyTrackTypes['slice'][]) {
     for (const slice of slices) {
       const currentSelection = globals.state.currentSelection;
@@ -96,25 +107,7 @@
 
       const highlighted = globals.state.highlightedSliceId === slice.id;
       const hasFocus = highlighted || isSelected;
-
-      let stage =
-          slice.title.substring(0, slice.title.indexOf(JANK_SLICE_NAME));
-      // Stage may include substage, in which case we use the substage for
-      // color selection.
-      const separator = '::';
-      if (stage.indexOf(separator) != -1) {
-        stage = stage.substring(stage.indexOf(separator) + separator.length);
-      }
-
-      if (stage == UNKNOWN_SLICE_NAME) {
-        if (hasFocus) {
-          slice.baseColor = DEEP_RED_COLOR;
-        } else {
-          slice.baseColor = RED_COLOR;
-        }
-      } else {
-        slice.baseColor = getColorForSlice(stage, hasFocus);
-      }
+      slice.isHighlighted = !!hasFocus;
     }
     super.onUpdatedSlices(slices);
   }
diff --git a/ui/src/tracks/chrome_scroll_jank/scroll_track.ts b/ui/src/tracks/chrome_scroll_jank/scroll_track.ts
index 7eaba65..a888844 100644
--- a/ui/src/tracks/chrome_scroll_jank/scroll_track.ts
+++ b/ui/src/tracks/chrome_scroll_jank/scroll_track.ts
@@ -13,13 +13,14 @@
 // limitations under the License.
 
 import {NamedSliceTrackTypes} from '../../frontend/named_slice_track';
-import {NewTrackArgs, TrackBase} from '../../frontend/track';
+import {NewTrackArgs} from '../../frontend/track';
 import {PrimaryTrackSortKey} from '../../public';
 import {
   CustomSqlDetailsPanelConfig,
   CustomSqlTableDefConfig,
   CustomSqlTableSliceTrack,
 } from '../custom_sql_table_slices';
+
 import {
   SCROLL_JANK_GROUP_ID,
   ScrollJankPluginState,
@@ -33,9 +34,6 @@
 export class TopLevelScrollTrack extends
     CustomSqlTableSliceTrack<NamedSliceTrackTypes> {
   public static kind = CHROME_TOPLEVEL_SCROLLS_KIND;
-  static create(args: NewTrackArgs): TrackBase {
-    return new TopLevelScrollTrack(args);
-  }
 
   getSqlDataSource(): CustomSqlTableDefConfig {
     return {
diff --git a/ui/src/tracks/chrome_slices/generic_slice_track.ts b/ui/src/tracks/chrome_slices/generic_slice_track.ts
index d19f3db..1e817a8 100644
--- a/ui/src/tracks/chrome_slices/generic_slice_track.ts
+++ b/ui/src/tracks/chrome_slices/generic_slice_track.ts
@@ -18,26 +18,13 @@
 } from '../../frontend/named_slice_track';
 import {NewTrackArgs} from '../../frontend/track';
 
-export interface GenericSliceTrackConfig {
-  sqlTrackId: number;
-}
-
-export interface GenericSliceTrackTypes extends NamedSliceTrackTypes {
-  config: GenericSliceTrackConfig;
-}
-
-export class GenericSliceTrack extends NamedSliceTrack<GenericSliceTrackTypes> {
-  static readonly kind = 'GenericSliceTrack';
-  static create(args: NewTrackArgs) {
-    return new GenericSliceTrack(args);
-  }
-
-  constructor(args: NewTrackArgs) {
+export class GenericSliceTrack extends NamedSliceTrack<NamedSliceTrackTypes> {
+  constructor(args: NewTrackArgs, private sqlTrackId: number) {
     super(args);
   }
 
   getSqlSource(): string {
     return `select ts, dur, id, depth, ifnull(name, '') as name
-    from slice where track_id = ${this.config.sqlTrackId}`;
+    from slice where track_id = ${this.sqlTrackId}`;
   }
 }
diff --git a/ui/src/tracks/chrome_slices/index.ts b/ui/src/tracks/chrome_slices/index.ts
index 35e9da0..8d36341 100644
--- a/ui/src/tracks/chrome_slices/index.ts
+++ b/ui/src/tracks/chrome_slices/index.ts
@@ -16,14 +16,14 @@
 import {clamp} from '../../base/math_utils';
 import {Duration, duration, time} from '../../base/time';
 import {
-  NAMED_SLICE_ROW,
+  NAMED_ROW,
   NamedSliceTrack,
   NamedSliceTrackTypes,
 } from '../../frontend/named_slice_track';
 import {
   SliceData,
-  SliceTrackBase,
-} from '../../frontend/slice_track_base';
+  SliceTrackLEGACY,
+} from '../../frontend/slice_track';
 import {NewTrackArgs} from '../../frontend/track';
 import {
   EngineProxy,
@@ -44,7 +44,7 @@
 
 export const SLICE_TRACK_KIND = 'ChromeSliceTrack';
 
-export class ChromeSliceTrack extends SliceTrackBase {
+export class ChromeSliceTrack extends SliceTrackLEGACY {
   private maxDurNs: duration = 0n;
 
   constructor(
@@ -153,7 +153,7 @@
 
 export const CHROME_SLICE_ROW = {
   // Base columns (tsq, ts, dur, id, depth).
-  ...NAMED_SLICE_ROW,
+  ...NAMED_ROW,
 
   // Chrome-specific columns.
   threadDur: LONG_NULL,
@@ -201,14 +201,7 @@
 
   onUpdatedSlices(slices: ChromeSliceTrackTypes['slice'][]) {
     for (const slice of slices) {
-      if (slice === this.hoveredSlice) {
-        slice.color = {
-          ...slice.baseColor,
-          l: 30,
-        };
-      } else {
-        slice.color = slice.baseColor;
-      }
+      slice.isHighlighted = (slice === this.hoveredSlice);
     }
   }
 }
diff --git a/ui/src/tracks/counter/index.ts b/ui/src/tracks/counter/index.ts
index 21ef5e6..4f2b7f4 100644
--- a/ui/src/tracks/counter/index.ts
+++ b/ui/src/tracks/counter/index.ts
@@ -20,13 +20,13 @@
 import {isString} from '../../base/object_utils';
 import {duration, time, Time} from '../../base/time';
 import {Actions} from '../../common/actions';
-import {
-  BasicAsyncTrack,
-  NUM_NULL,
-  STR_NULL,
-} from '../../common/basic_async_track';
 import {drawTrackHoverTooltip} from '../../common/canvas_utils';
 import {TrackData} from '../../common/track_data';
+import {
+  NUM_NULL,
+  STR_NULL,
+  TrackHelperLEGACY,
+} from '../../common/track_helper';
 import {checkerboardExcept} from '../../frontend/checkerboard';
 import {globals} from '../../frontend/globals';
 import {
@@ -126,7 +126,7 @@
   }
 }
 
-export class CounterTrack extends BasicAsyncTrack<Data> {
+export class CounterTrack extends TrackHelperLEGACY<Data> {
   private maximumValueSeen = 0;
   private minimumValueSeen = 0;
   private maximumDeltaSeen = 0;
diff --git a/ui/src/tracks/cpu_freq/index.ts b/ui/src/tracks/cpu_freq/index.ts
index 83bc30b..aa421f6 100644
--- a/ui/src/tracks/cpu_freq/index.ts
+++ b/ui/src/tracks/cpu_freq/index.ts
@@ -18,7 +18,7 @@
 import {duration, time, Time} from '../../base/time';
 import {calcCachedBucketSize} from '../../common/cache_utils';
 import {drawTrackHoverTooltip} from '../../common/canvas_utils';
-import {hueForCpu} from '../../common/colorizer';
+import {colorForCpu} from '../../common/colorizer';
 import {
   TrackAdapter,
   TrackControllerAdapter,
@@ -274,10 +274,6 @@
 const RECT_HEIGHT = 20;
 
 class CpuFreqTrack extends TrackAdapter<Config, Data> {
-  static create(args: NewTrackArgs): CpuFreqTrack {
-    return new CpuFreqTrack(args);
-  }
-
   private mousePos = {x: 0, y: 0};
   private hoveredValue: number|undefined = undefined;
   private hoveredTs: time|undefined = undefined;
@@ -326,13 +322,14 @@
     const yLabel = `${num} ${kUnits[unitGroup + 1]}Hz`;
 
     // Draw the CPU frequency graph.
-    const hue = hueForCpu(this.config.cpu);
+    const color = colorForCpu(this.config.cpu);
     let saturation = 45;
     if (globals.state.hoveredUtid !== -1) {
       saturation = 0;
     }
-    ctx.fillStyle = `hsl(${hue}, ${saturation}%, 70%)`;
-    ctx.strokeStyle = `hsl(${hue}, ${saturation}%, 55%)`;
+
+    ctx.fillStyle = color.setHSL({s: saturation, l: 70}).cssString;
+    ctx.strokeStyle = color.setHSL({s: saturation, l: 55}).cssString;
 
     const calculateX = (timestamp: time) => {
       return Math.floor(visibleTimeScale.timeToPx(timestamp));
@@ -386,7 +383,7 @@
     // Draw CPU idle rectangles that overlay the CPU freq graph.
     ctx.fillStyle = `rgba(240, 240, 240, 1)`;
 
-    for (let i = 0; i < data.lastIdleValues.length; i++) {
+    for (let i = startIdx; i < endIdx; i++) {
       if (data.lastIdleValues[i] < 0) {
         continue;
       }
@@ -412,8 +409,8 @@
     if (this.hoveredValue !== undefined && this.hoveredTs !== undefined) {
       let text = `${this.hoveredValue.toLocaleString()}kHz`;
 
-      ctx.fillStyle = `hsl(${hue}, 45%, 75%)`;
-      ctx.strokeStyle = `hsl(${hue}, 45%, 45%)`;
+      ctx.fillStyle = color.setHSL({s: 45, l: 75}).cssString;
+      ctx.strokeStyle = color.setHSL({s: 45, l: 45}).cssString;
 
       const xStart = Math.floor(visibleTimeScale.timeToPx(this.hoveredTs));
       const xEnd = this.hoveredTsEnd === undefined ?
diff --git a/ui/src/tracks/cpu_profile/index.ts b/ui/src/tracks/cpu_profile/index.ts
index d03a842..59ad373 100644
--- a/ui/src/tracks/cpu_profile/index.ts
+++ b/ui/src/tracks/cpu_profile/index.ts
@@ -16,7 +16,7 @@
 import {searchSegment} from '../../base/binary_search';
 import {duration, Time, time} from '../../base/time';
 import {Actions} from '../../common/actions';
-import {hslForSlice} from '../../common/colorizer';
+import {colorForSample} from '../../common/colorizer';
 import {
   TrackAdapter,
   TrackControllerAdapter,
@@ -24,7 +24,6 @@
 } 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} from '../../frontend/track';
 import {
@@ -90,17 +89,7 @@
   }
 }
 
-function colorForSample(callsiteId: number, isHovered: boolean): string {
-  const [hue, saturation, lightness] =
-      hslForSlice(String(callsiteId), isHovered);
-  return cachedHsluvToHex(hue, saturation, lightness);
-}
-
 class CpuProfileTrack extends TrackAdapter<Config, Data> {
-  static create(args: NewTrackArgs): CpuProfileTrack {
-    return new CpuProfileTrack(args);
-  }
-
   private centerY = this.getHeight() / 2 + BAR_HEIGHT;
   private markerWidth = (this.getHeight() - MARGIN_TOP - BAR_HEIGHT) / 2;
   private hoveredTs: time|undefined = undefined;
diff --git a/ui/src/tracks/cpu_slices/index.ts b/ui/src/tracks/cpu_slices/index.ts
index 45babfd..0e9c470 100644
--- a/ui/src/tracks/cpu_slices/index.ts
+++ b/ui/src/tracks/cpu_slices/index.ts
@@ -24,6 +24,7 @@
   drawIncompleteSlice,
   drawTrackHoverTooltip,
 } from '../../common/canvas_utils';
+import {Color} from '../../common/color';
 import {colorForThread} from '../../common/colorizer';
 import {
   TrackAdapter,
@@ -205,10 +206,6 @@
 const TRACK_HEIGHT = MARGIN_TOP * 2 + RECT_HEIGHT;
 
 class CpuSliceTrack extends TrackAdapter<Config, Data> {
-  static create(args: NewTrackArgs): CpuSliceTrack {
-    return new CpuSliceTrack(args);
-  }
-
   private mousePos?: {x: number, y: number};
   private utidHoveredInThisTrack = -1;
 
@@ -285,20 +282,22 @@
       const isHovering = globals.state.hoveredUtid !== -1;
       const isThreadHovered = globals.state.hoveredUtid === utid;
       const isProcessHovered = globals.state.hoveredPid === pid;
-      const color = colorForThread(threadInfo);
+      const colorScheme = colorForThread(threadInfo);
+      let color: Color;
+      let textColor: Color;
       if (isHovering && !isThreadHovered) {
         if (!isProcessHovered) {
-          color.l = 90;
-          color.s = 0;
+          color = colorScheme.disabled;
+          textColor = colorScheme.textDisabled;
         } else {
-          color.l = Math.min(color.l + 30, 80);
-          color.s -= 20;
+          color = colorScheme.variant;
+          textColor = colorScheme.textVariant;
         }
       } else {
-        color.l = Math.min(color.l + 10, 60);
-        color.s -= 20;
+        color = colorScheme.base;
+        textColor = colorScheme.textBase;
       }
-      ctx.fillStyle = `hsl(${color.h}, ${color.s}%, ${color.l}%)`;
+      ctx.fillStyle = color.cssString;
       if (data.isIncomplete[i]) {
         drawIncompleteSlice(ctx, rectStart, MARGIN_TOP, rectWidth, RECT_HEIGHT);
       } else {
@@ -330,10 +329,10 @@
       title = cropText(title, charWidth, visibleWidth);
       subTitle = cropText(subTitle, charWidth, visibleWidth);
       const rectXCenter = left + visibleWidth / 2;
-      ctx.fillStyle = '#fff';
+      ctx.fillStyle = textColor.cssString;
       ctx.font = '12px Roboto Condensed';
       ctx.fillText(title, rectXCenter, MARGIN_TOP + RECT_HEIGHT / 2 - 1);
-      ctx.fillStyle = 'rgba(255, 255, 255, 0.6)';
+      ctx.fillStyle = textColor.setAlpha(0.6).cssString;
       ctx.font = '10px Roboto Condensed';
       ctx.fillText(subTitle, rectXCenter, MARGIN_TOP + RECT_HEIGHT / 2 + 9);
     }
@@ -352,7 +351,7 @@
         const rectWidth = Math.max(1, rectEnd - rectStart);
 
         // Draw a rectangle around the slice that is currently selected.
-        ctx.strokeStyle = `hsl(${color.h}, ${color.s}%, 30%)`;
+        ctx.strokeStyle = color.base.setHSL({l: 30}).cssString;
         ctx.beginPath();
         ctx.lineWidth = 3;
         ctx.strokeRect(rectStart, MARGIN_TOP - 1.5, rectWidth, RECT_HEIGHT + 3);
diff --git a/ui/src/tracks/debug/counter_track.ts b/ui/src/tracks/debug/counter_track.ts
index 840364c..9af2db3 100644
--- a/ui/src/tracks/debug/counter_track.ts
+++ b/ui/src/tracks/debug/counter_track.ts
@@ -36,16 +36,15 @@
   columns: CounterColumns;
 }
 
-export class DebugCounterTrack extends
-    BaseCounterTrack<CounterDebugTrackConfig> {
-  constructor(engine: EngineProxy, trackKey: string) {
+export class DebugCounterTrack extends BaseCounterTrack {
+  private config: CounterDebugTrackConfig;
+
+  constructor(engine: EngineProxy, ctx: TrackContext) {
     super({
       engine,
-      trackKey,
+      trackKey: ctx.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.
diff --git a/ui/src/tracks/debug/details_tab.ts b/ui/src/tracks/debug/details_tab.ts
index 3523f4f..5616a6b 100644
--- a/ui/src/tracks/debug/details_tab.ts
+++ b/ui/src/tracks/debug/details_tab.ts
@@ -24,6 +24,7 @@
 import {
   GenericSliceDetailsTabConfig,
 } from '../../frontend/generic_slice_details_tab';
+import {hasArgs, renderArguments} from '../../frontend/slice_args';
 import {
   getSlice,
   SliceDetails,
@@ -162,11 +163,24 @@
           left: sliceRef(this.slice, 'Slice'),
           right: '',
         },
-        renderTreeContents({
-          'Name': this.slice.name,
-          'Thread': getThreadName(this.slice.thread),
-          'Process': getProcessName(this.slice.process),
-        }));
+        m(TreeNode, {
+          left: 'Name',
+          right: this.slice.name,
+        }),
+        m(TreeNode, {
+          left: 'Thread',
+          right: getThreadName(this.slice.thread),
+        }),
+        m(TreeNode, {
+          left: 'Process',
+          right: getProcessName(this.slice.process),
+        }),
+        hasArgs(this.slice) &&
+            m(TreeNode,
+              {
+                left: 'Args',
+              },
+              renderArguments(this.engine, this.slice)));
   }
 
 
diff --git a/ui/src/tracks/debug/index.ts b/ui/src/tracks/debug/index.ts
index 985f068..cc03350 100644
--- a/ui/src/tracks/debug/index.ts
+++ b/ui/src/tracks/debug/index.ts
@@ -31,11 +31,11 @@
   async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
     ctx.registerTrack({
       uri: DEBUG_SLICE_TRACK_URI,
-      track: ({trackKey}) => new DebugTrackV2(ctx.engine, trackKey),
+      track: (trackCtx) => new DebugTrackV2(ctx.engine, trackCtx),
     });
     ctx.registerTrack({
       uri: DEBUG_COUNTER_TRACK_URI,
-      track: ({trackKey}) => new DebugCounterTrack(ctx.engine, trackKey),
+      track: (trackCtx) => new DebugCounterTrack(ctx.engine, trackCtx),
     });
   }
 }
diff --git a/ui/src/tracks/debug/slice_track.ts b/ui/src/tracks/debug/slice_track.ts
index 30dfe3c..e023655 100644
--- a/ui/src/tracks/debug/slice_track.ts
+++ b/ui/src/tracks/debug/slice_track.ts
@@ -19,9 +19,7 @@
 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 {NamedSliceTrackTypes} from '../../frontend/named_slice_track';
 import {TrackButton} from '../../frontend/track_panel';
 import {PrimaryTrackSortKey, TrackContext} from '../../public';
 import {EngineProxy} from '../../trace_processor/engine';
@@ -47,19 +45,16 @@
   columns: SliceColumns;
 }
 
-interface DebugTrackV2Types extends NamedSliceTrackTypes {
-  config: DebugTrackV2Config;
-}
+export class DebugTrackV2 extends
+    CustomSqlTableSliceTrack<NamedSliceTrackTypes> {
+  private config: DebugTrackV2Config;
 
-export class DebugTrackV2 extends CustomSqlTableSliceTrack<DebugTrackV2Types> {
-  constructor(engine: EngineProxy, trackKey: string) {
+  constructor(engine: EngineProxy, ctx: TrackContext) {
     super({
       engine,
-      trackKey,
+      trackKey: ctx.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.
@@ -68,7 +63,7 @@
 
   getSqlDataSource(): CustomSqlTableDefConfig {
     return {
-      sqlTableName: this.config.sqlTableName,
+      sqlTableName: this.config!.sqlTableName,
     };
   }
 
@@ -76,7 +71,7 @@
     return {
       kind: DebugSliceDetailsTab.kind,
       config: {
-        sqlTableName: this.config.sqlTableName,
+        sqlTableName: this.config!.sqlTableName,
         title: 'Debug Slice',
       },
     };
diff --git a/ui/src/tracks/actual_frames/index.ts b/ui/src/tracks/frames/actual_frames_track.ts
similarity index 65%
rename from ui/src/tracks/actual_frames/index.ts
rename to ui/src/tracks/frames/actual_frames_track.ts
index fbc3cf7..4c46619 100644
--- a/ui/src/tracks/actual_frames/index.ts
+++ b/ui/src/tracks/frames/actual_frames_track.ts
@@ -1,4 +1,4 @@
-// Copyright (C) 2021 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -14,22 +14,15 @@
 
 import {BigintMath as BIMath} from '../../base/bigint_math';
 import {duration, time} from '../../base/time';
-import {SliceData, SliceTrackBase} from '../../frontend/slice_track_base';
+import {SliceData, SliceTrackLEGACY} from '../../frontend/slice_track';
 import {
   EngineProxy,
-  Plugin,
-  PluginContext,
-  PluginContextTrace,
-  PluginDescriptor,
 } from '../../public';
-import {getTrackName} from '../../public/utils';
 import {
   LONG,
   LONG_NULL,
   NUM,
-  NUM_NULL,
   STR,
-  STR_NULL,
 } from '../../trace_processor/query_result';
 
 export const ACTUAL_FRAMES_SLICE_TRACK_KIND = 'ActualFramesSliceTrack';
@@ -41,7 +34,7 @@
 const LIGHT_GREEN_COLOR = '#C0D588';  // Light Green 500
 const PINK_COLOR = '#F515E0';         // Pink 500
 
-class SliceTrack extends SliceTrackBase {
+export class ActualFramesTrack extends SliceTrackLEGACY {
   private maxDur = 0n;
 
   constructor(
@@ -151,79 +144,3 @@
     return slices;
   }
 }
-
-class ActualFrames implements Plugin {
-  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,
-          );
-        },
-      });
-    }
-  }
-}
-
-export const plugin: PluginDescriptor = {
-  pluginId: 'perfetto.ActualFrames',
-  plugin: ActualFrames,
-};
diff --git a/ui/src/tracks/frames/actual_frames_track_v2.ts b/ui/src/tracks/frames/actual_frames_track_v2.ts
new file mode 100644
index 0000000..c30e673
--- /dev/null
+++ b/ui/src/tracks/frames/actual_frames_track_v2.ts
@@ -0,0 +1,96 @@
+// 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 {HSLColor} from '../../common/color';
+import {ColorScheme, makeColorScheme} from '../../common/colorizer';
+import {
+  NAMED_ROW,
+  NamedSliceTrack,
+  NamedSliceTrackTypes,
+} from '../../frontend/named_slice_track';
+import {EngineProxy, Slice, STR_NULL} from '../../public';
+
+const BLUE = makeColorScheme(new HSLColor('#03A9F4'));    // Blue 500
+const GREEN = makeColorScheme(new HSLColor('#4CAF50'));   // Green 500
+const YELLOW = makeColorScheme(new HSLColor('#FFEB3B'));  // Yellow 500
+const RED = makeColorScheme(new HSLColor('#FF5722'));     // Red 500
+const LIGHT_GREEN =
+    makeColorScheme(new HSLColor('#C0D588'));           // Light Green 500
+const PINK = makeColorScheme(new HSLColor('#F515E0'));  // Pink 500
+
+export const ACTUAL_FRAME_ROW = {
+  // Base columns (tsq, ts, dur, id, depth).
+  ...NAMED_ROW,
+
+  // Chrome-specific columns.
+  jankTag: STR_NULL,
+};
+export type ActualFrameRow = typeof ACTUAL_FRAME_ROW;
+
+export interface ActualFrameTrackTypes extends NamedSliceTrackTypes {
+  row: ActualFrameRow;
+}
+
+export class ActualFramesTrack extends NamedSliceTrack<ActualFrameTrackTypes> {
+  constructor(
+      engine: EngineProxy, maxDepth: number, trackKey: string,
+      private trackIds: number[]) {
+    super({engine, trackKey});
+    this.sliceLayout.maxDepth = maxDepth + 1;
+  }
+
+  // This is used by the base class to call iter().
+  getRowSpec() {
+    return ACTUAL_FRAME_ROW;
+  }
+
+  getSqlSource(): string {
+    return `
+      SELECT
+        s.ts as ts,
+        s.dur as dur,
+        s.layout_depth as depth,
+        s.name as name,
+        s.id as id,
+        afs.jank_tag as jankTag
+      from experimental_slice_layout s
+      join actual_frame_timeline_slice afs using(id)
+      where
+        filter_track_ids = '${this.trackIds.join(',')}'
+    `;
+  }
+
+  rowToSlice(row: ActualFrameRow): Slice {
+    const baseSlice = super.rowToSlice(row);
+    return {...baseSlice, colorScheme: getColorSchemeForJank(row.jankTag)};
+  }
+}
+
+function getColorSchemeForJank(jankTag: string|null): ColorScheme {
+  switch (jankTag) {
+    case 'Self Jank':
+      return RED;
+    case 'Other Jank':
+      return YELLOW;
+    case 'Dropped Frame':
+      return BLUE;
+    case 'Buffer Stuffing':
+    case 'SurfaceFlinger Stuffing':
+      return LIGHT_GREEN;
+    case 'No Jank':
+      return GREEN;
+    default:
+      return PINK;
+  }
+}
diff --git a/ui/src/tracks/expected_frames/index.ts b/ui/src/tracks/frames/expected_frames_track.ts
similarity index 60%
rename from ui/src/tracks/expected_frames/index.ts
rename to ui/src/tracks/frames/expected_frames_track.ts
index 8cc1833..05af78a 100644
--- a/ui/src/tracks/expected_frames/index.ts
+++ b/ui/src/tracks/frames/expected_frames_track.ts
@@ -14,30 +14,16 @@
 
 import {BigintMath as BIMath} from '../../base/bigint_math';
 import {Duration, duration, time} from '../../base/time';
-import {
-  SliceData,
-  SliceTrackBase,
-} from '../../frontend/slice_track_base';
-import {
-  EngineProxy,
-  Plugin,
-  PluginContext,
-  PluginContextTrace,
-  PluginDescriptor,
-} from '../../public';
-import {getTrackName} from '../../public/utils';
+import {SliceData, SliceTrackLEGACY} from '../../frontend/slice_track';
+import {EngineProxy} from '../../public';
 import {
   LONG,
   LONG_NULL,
   NUM,
-  NUM_NULL,
   STR,
-  STR_NULL,
 } from '../../trace_processor/query_result';
 
-export const EXPECTED_FRAMES_SLICE_TRACK_KIND = 'ExpectedFramesSliceTrack';
-
-class SliceTrack extends SliceTrackBase {
+export class ExpectedFramesTrack extends SliceTrackLEGACY {
   private maxDur = Duration.ZERO;
 
   constructor(
@@ -135,79 +121,3 @@
     return slices;
   }
 }
-
-class ExpectedFramesPlugin implements Plugin {
-  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,
-          );
-        },
-      });
-    }
-  }
-}
-
-export const plugin: PluginDescriptor = {
-  pluginId: 'perfetto.ExpectedFrames',
-  plugin: ExpectedFramesPlugin,
-};
diff --git a/ui/src/tracks/frames/expected_frames_track_v2.ts b/ui/src/tracks/frames/expected_frames_track_v2.ts
new file mode 100644
index 0000000..268b4b6
--- /dev/null
+++ b/ui/src/tracks/frames/expected_frames_track_v2.ts
@@ -0,0 +1,48 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import {HSLColor} from '../../common/color';
+import {makeColorScheme} from '../../common/colorizer';
+import {NamedRow, NamedSliceTrack} from '../../frontend/named_slice_track';
+import {EngineProxy, Slice} from '../../public';
+
+const GREEN = makeColorScheme(new HSLColor('#4CAF50'));  // Green 500
+
+export class ExpectedFramesTrack extends NamedSliceTrack {
+  constructor(
+      engine: EngineProxy, maxDepth: number, trackKey: string,
+      private trackIds: number[]) {
+    super({engine, trackKey});
+    this.sliceLayout.maxDepth = maxDepth + 1;
+  }
+
+  getSqlSource(): string {
+    return `
+      SELECT
+        ts,
+        dur,
+        layout_depth as depth,
+        name,
+        id
+      from experimental_slice_layout
+      where
+        filter_track_ids = '${this.trackIds.join(',')}'
+    `;
+  }
+
+  rowToSlice(row: NamedRow): Slice {
+    const baseSlice = super.rowToSlice(row);
+    return {...baseSlice, colorScheme: GREEN};
+  }
+}
diff --git a/ui/src/tracks/frames/index.ts b/ui/src/tracks/frames/index.ts
new file mode 100644
index 0000000..e1cfc85
--- /dev/null
+++ b/ui/src/tracks/frames/index.ts
@@ -0,0 +1,217 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import {
+  Plugin,
+  PluginContext,
+  PluginContextTrace,
+  PluginDescriptor,
+} from '../../public';
+import {getTrackName} from '../../public/utils';
+import {
+  NUM,
+  NUM_NULL,
+  STR,
+  STR_NULL,
+} from '../../trace_processor/query_result';
+
+import {ActualFramesTrack} from './actual_frames_track';
+import {
+  ActualFramesTrack as ActualFramesTrackV2,
+} from './actual_frames_track_v2';
+import {ExpectedFramesTrack} from './expected_frames_track';
+import {
+  ExpectedFramesTrack as ExpectedFramesTrackV2,
+} from './expected_frames_track_v2';
+
+export const EXPECTED_FRAMES_SLICE_TRACK_KIND = 'ExpectedFramesSliceTrack';
+export const ACTUAL_FRAMES_SLICE_TRACK_KIND = 'ActualFramesSliceTrack';
+
+class FramesPlugin implements Plugin {
+  onActivate(_ctx: PluginContext): void {}
+
+  async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
+    this.addExpectedFrames(ctx);
+    this.addActualFrames(ctx);
+  }
+
+  async addExpectedFrames(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 ExpectedFramesTrack(
+              engine,
+              maxDepth,
+              trackKey,
+              trackIds,
+          );
+        },
+      });
+
+      ctx.registerStaticTrack({
+        uri: `perfetto.ExpectedFrames#${upid}.v2`,
+        displayName,
+        trackIds,
+        kind: EXPECTED_FRAMES_SLICE_TRACK_KIND,
+        track: ({trackKey}) => {
+          return new ExpectedFramesTrackV2(
+              engine,
+              maxDepth,
+              trackKey,
+              trackIds,
+          );
+        },
+      });
+    }
+  }
+
+  async addActualFrames(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 ActualFramesTrack(
+              engine,
+              maxDepth,
+              trackKey,
+              trackIds,
+          );
+        },
+      });
+
+      ctx.registerStaticTrack({
+        uri: `perfetto.ActualFrames#${upid}.v2`,
+        displayName,
+        trackIds,
+        kind: ACTUAL_FRAMES_SLICE_TRACK_KIND,
+        track: ({trackKey}) => {
+          return new ActualFramesTrackV2(
+              engine,
+              maxDepth,
+              trackKey,
+              trackIds,
+          );
+        },
+      });
+    }
+  }
+}
+
+export const plugin: PluginDescriptor = {
+  pluginId: 'perfetto.Frames',
+  plugin: FramesPlugin,
+};
diff --git a/ui/src/tracks/ftrace/index.ts b/ui/src/tracks/ftrace/index.ts
index 5dd646c..75c741e 100644
--- a/ui/src/tracks/ftrace/index.ts
+++ b/ui/src/tracks/ftrace/index.ts
@@ -13,9 +13,9 @@
 // limitations under the License.
 
 import {duration, Time, time} from '../../base/time';
-import {BasicAsyncTrack} from '../../common/basic_async_track';
-import {colorForString} from '../../common/colorizer';
+import {colorForFtrace} from '../../common/colorizer';
 import {LIMIT, TrackData} from '../../common/track_data';
+import {TrackHelperLEGACY} from '../../common/track_helper';
 import {checkerboardExcept} from '../../frontend/checkerboard';
 import {globals} from '../../frontend/globals';
 import {
@@ -42,7 +42,7 @@
 const RECT_HEIGHT = 18;
 const TRACK_HEIGHT = (RECT_HEIGHT) + (2 * MARGIN);
 
-class FtraceRawTrack extends BasicAsyncTrack<Data> {
+class FtraceRawTrack extends TrackHelperLEGACY<Data> {
   constructor(private engine: EngineProxy, private cpu: number) {
     super();
   }
@@ -116,13 +116,7 @@
 
     for (let i = 0; i < data.timestamps.length; i++) {
       const name = data.names[i];
-      const color = colorForString(name);
-      const hsl = `hsl(
-        ${color.h},
-        ${color.s - 20}%,
-        ${Math.min(color.l + 10, 60)}%
-      )`;
-      ctx.fillStyle = hsl;
+      ctx.fillStyle = colorForFtrace(name).base.cssString;
       const timestamp = Time.fromRaw(data.timestamps[i]);
       const xPos = Math.floor(visibleTimeScale.timeToPx(timestamp));
 
diff --git a/ui/src/tracks/heap_profile/index.ts b/ui/src/tracks/heap_profile/index.ts
index 494dae7..f35d176 100644
--- a/ui/src/tracks/heap_profile/index.ts
+++ b/ui/src/tracks/heap_profile/index.ts
@@ -16,36 +16,36 @@
 import {ProfileType, Selection} from '../../common/state';
 import {profileType} from '../../controller/flamegraph_controller';
 import {
-  BASE_SLICE_ROW,
+  BASE_ROW,
   BaseSliceTrack,
   BaseSliceTrackTypes,
   OnSliceClickArgs,
   OnSliceOverArgs,
 } from '../../frontend/base_slice_track';
 import {globals} from '../../frontend/globals';
-import {Slice} from '../../frontend/slice';
 import {NewTrackArgs} from '../../frontend/track';
 import {
   Plugin,
   PluginContext,
   PluginContextTrace,
   PluginDescriptor,
+  Slice,
 } from '../../public';
 import {NUM, STR} from '../../trace_processor/query_result';
 
 export const HEAP_PROFILE_TRACK_KIND = 'HeapProfileTrack';
 
-const HEAP_PROFILE_SLICE_ROW = {
-  ...BASE_SLICE_ROW,
+const HEAP_PROFILE_ROW = {
+  ...BASE_ROW,
   type: STR,
 };
-type HeapProfileSliceRow = typeof HEAP_PROFILE_SLICE_ROW;
+type HeapProfileRow = typeof HEAP_PROFILE_ROW;
 interface HeapProfileSlice extends Slice {
   type: ProfileType;
 }
 
 interface HeapProfileTrackTypes extends BaseSliceTrackTypes {
-  row: HeapProfileSliceRow;
+  row: HeapProfileRow;
   slice: HeapProfileSlice;
 }
 
@@ -80,11 +80,11 @@
       )`;
   }
 
-  getRowSpec(): HeapProfileSliceRow {
-    return HEAP_PROFILE_SLICE_ROW;
+  getRowSpec(): HeapProfileRow {
+    return HEAP_PROFILE_ROW;
   }
 
-  rowToSlice(row: HeapProfileSliceRow): HeapProfileSlice {
+  rowToSlice(row: HeapProfileRow): HeapProfileSlice {
     const slice = super.rowToSlice(row);
     let type = row.type;
     if (type === 'heap_profile:libc.malloc,com.android.art') {
diff --git a/ui/src/tracks/null_track/index.ts b/ui/src/tracks/null_track/index.ts
index 1b2bb24..7bc77b5 100644
--- a/ui/src/tracks/null_track/index.ts
+++ b/ui/src/tracks/null_track/index.ts
@@ -28,10 +28,6 @@
     super(args);
   }
 
-  static create(args: NewTrackArgs): NullTrack {
-    return new NullTrack(args);
-  }
-
   getHeight(): number {
     return 30;
   }
@@ -50,7 +46,7 @@
       uri: NULL_TRACK_URI,
       displayName: 'Null Track',
       kind: NULL_TRACK_KIND,
-      track: ({trackKey}) => NullTrack.create({
+      track: ({trackKey}) => new NullTrack({
         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 5da1f71..dd30045 100644
--- a/ui/src/tracks/perf_samples_profile/index.ts
+++ b/ui/src/tracks/perf_samples_profile/index.ts
@@ -87,10 +87,6 @@
 const RECT_HEIGHT = 30.5;
 
 class PerfSamplesProfileTrack extends TrackAdapter<Config, Data> {
-  static create(args: NewTrackArgs): PerfSamplesProfileTrack {
-    return new PerfSamplesProfileTrack(args);
-  }
-
   private centerY = this.getHeight() / 2;
   private markerWidth = (this.getHeight() - MARGIN_TOP) / 2;
   private hoveredTs: time|undefined = undefined;
diff --git a/ui/src/tracks/process_summary/process_scheduling_track.ts b/ui/src/tracks/process_summary/process_scheduling_track.ts
index 0d65539..a955278 100644
--- a/ui/src/tracks/process_summary/process_scheduling_track.ts
+++ b/ui/src/tracks/process_summary/process_scheduling_track.ts
@@ -19,6 +19,7 @@
 import {Actions} from '../../common/actions';
 import {calcCachedBucketSize} from '../../common/cache_utils';
 import {drawTrackHoverTooltip} from '../../common/canvas_utils';
+import {Color} from '../../common/color';
 import {colorForThread} from '../../common/colorizer';
 import {
   TrackAdapter,
@@ -188,10 +189,6 @@
 }
 
 export class ProcessSchedulingTrack extends TrackAdapter<Config, Data> {
-  static create(args: NewTrackArgs): ProcessSchedulingTrack {
-    return new ProcessSchedulingTrack(args);
-  }
-
   private mousePos?: {x: number, y: number};
   private utidHoveredInThisTrack = -1;
 
@@ -250,20 +247,18 @@
       const isHovering = globals.state.hoveredUtid !== -1;
       const isThreadHovered = globals.state.hoveredUtid === utid;
       const isProcessHovered = globals.state.hoveredPid === pid;
-      const color = colorForThread(threadInfo);
+      const colorScheme = colorForThread(threadInfo);
+      let color: Color;
       if (isHovering && !isThreadHovered) {
         if (!isProcessHovered) {
-          color.l = 90;
-          color.s = 0;
+          color = colorScheme.disabled;
         } else {
-          color.l = Math.min(color.l + 30, 80);
-          color.s -= 20;
+          color = colorScheme.variant;
         }
       } else {
-        color.l = Math.min(color.l + 10, 60);
-        color.s -= 20;
+        color = colorScheme.base;
       }
-      ctx.fillStyle = `hsl(${color.h}, ${color.s}%, ${color.l}%)`;
+      ctx.fillStyle = color.cssString;
       const y = MARGIN_TOP + cpuTrackHeight * cpu + cpu;
       ctx.fillRect(rectStart, y, rectEnd - rectStart, cpuTrackHeight);
     }
diff --git a/ui/src/tracks/process_summary/process_summary_track.ts b/ui/src/tracks/process_summary/process_summary_track.ts
index 0b29eea..ee0bcc8 100644
--- a/ui/src/tracks/process_summary/process_summary_track.ts
+++ b/ui/src/tracks/process_summary/process_summary_track.ts
@@ -142,10 +142,6 @@
 const SUMMARY_HEIGHT = TRACK_HEIGHT - MARGIN_TOP;
 
 export class ProcessSummaryTrack extends TrackAdapter<Config, Data> {
-  static create(args: NewTrackArgs): ProcessSummaryTrack {
-    return new ProcessSummaryTrack(args);
-  }
-
   constructor(args: NewTrackArgs) {
     super(args);
   }
@@ -184,10 +180,7 @@
 
     // TODO(hjd): Dedupe this math.
     const color = colorForTid(this.config.pidForColor);
-    color.l = Math.min(color.l + 10, 60);
-    color.s -= 20;
-
-    ctx.fillStyle = `hsl(${color.h}, ${color.s}%, ${color.l}%)`;
+    ctx.fillStyle = color.base.cssString;
     ctx.beginPath();
     ctx.moveTo(lastX, lastY);
     for (let i = 0; i < data.utilizations.length; i++) {
diff --git a/ui/src/tracks/sched/active_cpu_count.ts b/ui/src/tracks/sched/active_cpu_count.ts
index 9606137..6dc2103 100644
--- a/ui/src/tracks/sched/active_cpu_count.ts
+++ b/ui/src/tracks/sched/active_cpu_count.ts
@@ -25,8 +25,7 @@
 } from '../../frontend/base_counter_track';
 import {CloseTrackButton} from '../../frontend/close_track_button';
 import {globals} from '../../frontend/globals';
-import {NewTrackArgs} from '../../frontend/track';
-import {PrimaryTrackSortKey, TrackContext} from '../../public';
+import {EngineProxy, PrimaryTrackSortKey, TrackContext} from '../../public';
 
 export function addActiveCPUCountTrack(cpuType?: string) {
   const cpuTypeName = cpuType === undefined ? '' : ` ${cpuType} `;
@@ -52,15 +51,17 @@
   cpuType?: string;
 }
 
-export class ActiveCPUCountTrack extends
-    BaseCounterTrack<ActiveCPUCountTrackConfig> {
+export class ActiveCPUCountTrack extends BaseCounterTrack {
+  private config: ActiveCPUCountTrackConfig;
+
   static readonly kind = 'dev.perfetto.Sched.ActiveCPUCount';
 
-  constructor(args: NewTrackArgs) {
-    super(args);
-  }
+  constructor(ctx: TrackContext, engine: EngineProxy) {
+    super({
+      engine,
+      trackKey: ctx.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.
@@ -87,10 +88,10 @@
   }
 
   getSqlSource() {
-    const sourceTable = this.config.cpuType === undefined ?
+    const sourceTable = this.config!.cpuType === undefined ?
         'sched_active_cpu_count' :
         `sched_active_cpu_count_for_core_type(${
-            sqliteString(this.config.cpuType)})`;
+            sqliteString(this.config!.cpuType)})`;
     return `
     select
       ts,
diff --git a/ui/src/tracks/sched/index.ts b/ui/src/tracks/sched/index.ts
index af411ed..d9d5ed2 100644
--- a/ui/src/tracks/sched/index.ts
+++ b/ui/src/tracks/sched/index.ts
@@ -37,11 +37,7 @@
     });
     ctx.registerTrack({
       uri: ActiveCPUCountTrack.kind,
-      track: (trackCtx) => {
-        const track = new ActiveCPUCountTrack(
-            {engine: ctx.engine, trackKey: trackCtx.trackKey});
-        return track;
-      },
+      track: (trackCtx) => new ActiveCPUCountTrack(trackCtx, ctx.engine),
     });
   }
 
diff --git a/ui/src/tracks/screenshots/index.ts b/ui/src/tracks/screenshots/index.ts
index 32df756..4af3c94 100644
--- a/ui/src/tracks/screenshots/index.ts
+++ b/ui/src/tracks/screenshots/index.ts
@@ -16,7 +16,6 @@
 import {
   NamedSliceTrackTypes,
 } from '../../frontend/named_slice_track';
-import {NewTrackArgs, TrackBase} from '../../frontend/track';
 import {
   Plugin,
   PluginContext,
@@ -36,9 +35,6 @@
 
 class ScreenshotsTrack extends CustomSqlTableSliceTrack<NamedSliceTrackTypes> {
   static readonly kind = 'dev.perfetto.ScreenshotsTrack';
-  static create(args: NewTrackArgs): TrackBase {
-    return new ScreenshotsTrack(args);
-  }
 
   getSqlDataSource(): CustomSqlTableDefConfig {
     return {
diff --git a/ui/src/tracks/thread_state/index.ts b/ui/src/tracks/thread_state/index.ts
index 2b5e2ef..7544ddb 100644
--- a/ui/src/tracks/thread_state/index.ts
+++ b/ui/src/tracks/thread_state/index.ts
@@ -48,7 +48,6 @@
 } from './thread_state_v2';
 
 export const THREAD_STATE_TRACK_KIND = 'ThreadStateTrack';
-export const THREAD_STATE_TRACK_V2_KIND = 'ThreadStateTrackV2';
 
 interface Data extends TrackData {
   strings: string[];
@@ -180,10 +179,6 @@
 const EXCESS_WIDTH = 10;
 
 class ThreadStateTrack extends TrackAdapter<Config, Data> {
-  static create(args: NewTrackArgs): ThreadStateTrack {
-    return new ThreadStateTrack(args);
-  }
-
   constructor(args: NewTrackArgs) {
     super(args);
   }
@@ -246,21 +241,15 @@
           currentSelection.kind === 'THREAD_STATE' &&
           currentSelection.id === data.ids[i];
 
-      const color = colorForState(state);
-
-      let colorStr = `hsl(${color.h},${color.s}%,${color.l}%)`;
-      if (color.a) {
-        colorStr = `hsla(${color.h},${color.s}%,${color.l}%, ${color.a})`;
-      }
-      ctx.fillStyle = colorStr;
-
+      const colorScheme = colorForState(state);
+      ctx.fillStyle = colorScheme.base.cssString;
       ctx.fillRect(rectStart, MARGIN_TOP, rectWidth, RECT_HEIGHT);
 
       // Don't render text when we have less than 10px to play with.
       if (rectWidth < 10 || state === 'Sleeping') continue;
       const title = cropText(state, charWidth, rectWidth);
       const rectXCenter = rectStart + rectWidth / 2;
-      ctx.fillStyle = color.l > 80 ? '#404040' : '#fff';
+      ctx.fillStyle = colorScheme.textBase.cssString;
       ctx.fillText(title, rectXCenter, MARGIN_TOP + RECT_HEIGHT / 2 + 3);
 
       if (isSelected) {
@@ -269,8 +258,7 @@
               Math.max(0 - EXCESS_WIDTH, timeScale.timeToPx(tStart));
           const rectEnd =
               Math.min(windowSpan.end + EXCESS_WIDTH, timeScale.timeToPx(tEnd));
-          const color = colorForState(state);
-          ctx.strokeStyle = `hsl(${color.h},${color.s}%,${color.l * 0.7}%)`;
+          ctx.strokeStyle = colorScheme.base.cssString;
           ctx.beginPath();
           ctx.lineWidth = 3;
           ctx.strokeRect(
@@ -352,15 +340,15 @@
       ctx.registerStaticTrack({
         uri: `perfetto.ThreadState#${utid}.v2`,
         displayName,
-        kind: THREAD_STATE_TRACK_V2_KIND,
+        kind: THREAD_STATE_TRACK_KIND,
         utid,
         track: ({trackKey}) => {
-          const track = ThreadStateTrackV2.create({
-            engine: ctx.engine,
-            trackKey,
-          });
-          track.config = {utid};
-          return track;
+          return new ThreadStateTrackV2(
+              {
+                engine: ctx.engine,
+                trackKey,
+              },
+              utid);
         },
       });
     }
diff --git a/ui/src/tracks/thread_state/thread_state_track_v2.ts b/ui/src/tracks/thread_state/thread_state_track_v2.ts
deleted file mode 100644
index ffcd8d2..0000000
--- a/ui/src/tracks/thread_state/thread_state_track_v2.ts
+++ /dev/null
@@ -1,119 +0,0 @@
-// Copyright (C) 2021 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-import {Actions} from '../../common/actions';
-import {Color, colorForState} from '../../common/colorizer';
-import {Selection} from '../../common/state';
-import {translateState} from '../../common/thread_state';
-import {
-  BASE_SLICE_ROW,
-  BaseSliceTrack,
-  BaseSliceTrackTypes,
-  OnSliceClickArgs,
-} from '../../frontend/base_slice_track';
-import {globals} from '../../frontend/globals';
-import {
-  SLICE_LAYOUT_FLAT_DEFAULTS,
-  SliceLayout,
-} from '../../frontend/slice_layout';
-import {NewTrackArgs} from '../../frontend/track';
-import {NUM_NULL, STR} from '../../trace_processor/query_result';
-
-export const THREAD_STATE_ROW = {
-  ...BASE_SLICE_ROW,
-  state: STR,
-  ioWait: NUM_NULL,
-};
-
-export type ThreadStateRow = typeof THREAD_STATE_ROW;
-
-export interface ThreadStateTrackConfig {
-  utid: number;
-}
-
-export interface ThreadStateTrackTypes extends BaseSliceTrackTypes {
-  row: ThreadStateRow;
-  config: ThreadStateTrackConfig;
-}
-
-export class ThreadStateTrack extends BaseSliceTrack<ThreadStateTrackTypes> {
-  static create(args: NewTrackArgs) {
-    return new ThreadStateTrack(args);
-  }
-
-  protected sliceLayout: SliceLayout = {...SLICE_LAYOUT_FLAT_DEFAULTS};
-
-  constructor(args: NewTrackArgs) {
-    super(args);
-  }
-
-  // This is used by the base class to call iter().
-  getRowSpec(): ThreadStateTrackTypes['row'] {
-    return THREAD_STATE_ROW;
-  }
-
-  getSqlSource(): string {
-    // Do not display states 'x' and 'S' (dead & sleeping).
-    const sql = `
-      select
-        id,
-        ts,
-        dur,
-        cpu,
-        state,
-        io_wait as ioWait,
-        0 as depth
-      from thread_state
-      where
-        utid = ${this.config.utid} and
-        state != 'x' and
-        state != 'S'
-    `;
-    return sql;
-  }
-
-  rowToSlice(row: ThreadStateTrackTypes['row']):
-      ThreadStateTrackTypes['slice'] {
-    const baseSlice = super.rowToSlice(row);
-    const ioWait = row.ioWait === null ? undefined : !!row.ioWait;
-    const title = translateState(row.state, ioWait);
-    const baseColor: Color = colorForState(title);
-    return {...baseSlice, title, baseColor};
-  }
-
-  onUpdatedSlices(slices: ThreadStateTrackTypes['slice'][]) {
-    for (const slice of slices) {
-      if (slice === this.hoveredSlice) {
-        slice.color = {
-          h: slice.baseColor.h,
-          s: slice.baseColor.s,
-          l: 30,
-        };
-      } else {
-        slice.color = slice.baseColor;
-      }
-    }
-  }
-
-  onSliceClick(args: OnSliceClickArgs<ThreadStateTrackTypes['slice']>) {
-    globals.makeSelection(Actions.selectThreadState({
-      id: args.slice.id,
-      trackKey: this.trackKey,
-    }));
-  }
-
-  protected isSelectionHandled(selection: Selection): boolean {
-    return selection.kind === 'THREAD_STATE';
-  }
-}
diff --git a/ui/src/tracks/thread_state/thread_state_v2.ts b/ui/src/tracks/thread_state/thread_state_v2.ts
index 0f93074..c35ed5c 100644
--- a/ui/src/tracks/thread_state/thread_state_v2.ts
+++ b/ui/src/tracks/thread_state/thread_state_v2.ts
@@ -13,11 +13,11 @@
 // limitations under the License.
 
 import {Actions} from '../../common/actions';
-import {Color, colorForState} from '../../common/colorizer';
+import {colorForState} from '../../common/colorizer';
 import {Selection} from '../../common/state';
 import {translateState} from '../../common/thread_state';
 import {
-  BASE_SLICE_ROW,
+  BASE_ROW,
   BaseSliceTrack,
   BaseSliceTrackTypes,
   OnSliceClickArgs,
@@ -31,32 +31,21 @@
 import {NUM_NULL, STR} from '../../trace_processor/query_result';
 
 export const THREAD_STATE_ROW = {
-  ...BASE_SLICE_ROW,
+  ...BASE_ROW,
   state: STR,
   ioWait: NUM_NULL,
 };
+
 export type ThreadStateRow = typeof THREAD_STATE_ROW;
 
-
-export interface ThreadStateTrackConfig {
-  utid: number;
-}
-
 export interface ThreadStateTrackTypes extends BaseSliceTrackTypes {
   row: ThreadStateRow;
-  config: ThreadStateTrackConfig;
 }
 
-export const THREAD_STATE_TRACK_V2_KIND = 'ThreadStateTrackV2';
-
 export class ThreadStateTrack extends BaseSliceTrack<ThreadStateTrackTypes> {
-  static create(args: NewTrackArgs) {
-    return new ThreadStateTrack(args);
-  }
-
   protected sliceLayout: SliceLayout = {...SLICE_LAYOUT_FLAT_DEFAULTS};
 
-  constructor(args: NewTrackArgs) {
+  constructor(args: NewTrackArgs, private utid: number) {
     super(args);
   }
 
@@ -67,18 +56,22 @@
 
   getSqlSource(): string {
     // Do not display states 'x' and 'S' (dead & sleeping).
+    // Note: Thread state tracks V1 basically ignores incomplete slices, faking
+    // their duration as 1 instead. Let's just do this here as well for now to
+    // achieve feature parity with tracks V1 and tackle the issue of overlapping
+    // incomplete slices later.
     return `
       select
         id,
         ts,
-        dur,
+        max(dur, 1) as dur,
         cpu,
         state,
         io_wait as ioWait,
         0 as depth
       from thread_state
       where
-        utid = ${this.config.utid} and
+        utid = ${this.utid} and
         state != 'x' and
         state != 'S'
     `;
@@ -89,21 +82,13 @@
     const baseSlice = super.rowToSlice(row);
     const ioWait = row.ioWait === null ? undefined : !!row.ioWait;
     const title = translateState(row.state, ioWait);
-    const baseColor: Color = colorForState(title);
-    return {...baseSlice, title, baseColor};
+    const color = colorForState(title);
+    return {...baseSlice, title, colorScheme: color};
   }
 
   onUpdatedSlices(slices: ThreadStateTrackTypes['slice'][]) {
     for (const slice of slices) {
-      if (slice === this.hoveredSlice) {
-        slice.color = {
-          h: slice.baseColor.h,
-          s: slice.baseColor.s,
-          l: 30,
-        };
-      } else {
-        slice.color = slice.baseColor;
-      }
+      slice.isHighlighted = (slice === this.hoveredSlice);
     }
   }