Merge "Add reason, reason_id and launch_dur values to slow_start_reason_with_details." into main
diff --git a/Android.bp b/Android.bp
index c46b914..8b07472 100644
--- a/Android.bp
+++ b/Android.bp
@@ -2346,6 +2346,7 @@
         ":perfetto_protos_perfetto_trace_translation_lite_gen",
         ":perfetto_protos_perfetto_trace_translation_zero_gen",
         ":perfetto_protos_third_party_pprof_zero_gen",
+        ":perfetto_protos_third_party_simpleperf_zero_gen",
         ":perfetto_protos_third_party_statsd_config_zero_gen",
         ":perfetto_src_android_internal_headers",
         ":perfetto_src_android_internal_lazy_library_loader",
@@ -2390,6 +2391,7 @@
         ":perfetto_src_shared_lib_test_utils",
         ":perfetto_src_trace_processor_containers_containers",
         ":perfetto_src_trace_processor_db_column_column",
+        ":perfetto_src_trace_processor_db_compare",
         ":perfetto_src_trace_processor_db_db",
         ":perfetto_src_trace_processor_db_minimal",
         ":perfetto_src_trace_processor_export_json",
@@ -2413,6 +2415,7 @@
         ":perfetto_src_trace_processor_importers_ninja_ninja",
         ":perfetto_src_trace_processor_importers_perf_perf",
         ":perfetto_src_trace_processor_importers_perf_record",
+        ":perfetto_src_trace_processor_importers_perf_tracker",
         ":perfetto_src_trace_processor_importers_proto_full",
         ":perfetto_src_trace_processor_importers_proto_minimal",
         ":perfetto_src_trace_processor_importers_proto_packet_sequence_state_generation_hdr",
@@ -2422,6 +2425,7 @@
         ":perfetto_src_trace_processor_importers_systrace_full",
         ":perfetto_src_trace_processor_importers_systrace_systrace_line",
         ":perfetto_src_trace_processor_importers_systrace_systrace_parser",
+        ":perfetto_src_trace_processor_importers_zip_full",
         ":perfetto_src_trace_processor_lib",
         ":perfetto_src_trace_processor_metatrace",
         ":perfetto_src_trace_processor_metrics_metrics",
@@ -2633,6 +2637,7 @@
         "perfetto_protos_perfetto_trace_translation_lite_gen_headers",
         "perfetto_protos_perfetto_trace_translation_zero_gen_headers",
         "perfetto_protos_third_party_pprof_zero_gen_headers",
+        "perfetto_protos_third_party_simpleperf_zero_gen_headers",
         "perfetto_protos_third_party_statsd_config_zero_gen_headers",
         "perfetto_src_base_version_gen_h",
         "perfetto_src_trace_processor_importers_proto_gen_cc_chrome_track_event_descriptor",
@@ -10387,6 +10392,50 @@
     ],
 }
 
+// GN: //protos/third_party/simpleperf:zero
+filegroup {
+    name: "perfetto_protos_third_party_simpleperf_zero",
+    srcs: [
+        "protos/third_party/simpleperf/record_file.proto",
+    ],
+}
+
+// GN: //protos/third_party/simpleperf:zero
+genrule {
+    name: "perfetto_protos_third_party_simpleperf_zero_gen",
+    srcs: [
+        ":perfetto_protos_third_party_simpleperf_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_third_party_simpleperf_zero)",
+    out: [
+        "external/perfetto/protos/third_party/simpleperf/record_file.pbzero.cc",
+    ],
+}
+
+// GN: //protos/third_party/simpleperf:zero
+genrule {
+    name: "perfetto_protos_third_party_simpleperf_zero_gen_headers",
+    srcs: [
+        ":perfetto_protos_third_party_simpleperf_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_third_party_simpleperf_zero)",
+    out: [
+        "external/perfetto/protos/third_party/simpleperf/record_file.pbzero.h",
+    ],
+    export_include_dirs: [
+        ".",
+        "protos",
+    ],
+}
+
 // GN: //protos/third_party/statsd:config_zero
 filegroup {
     name: "perfetto_protos_third_party_statsd_config_zero",
@@ -12255,6 +12304,14 @@
     ],
 }
 
+// GN: //src/trace_processor/importers/perf:tracker
+filegroup {
+    name: "perfetto_src_trace_processor_importers_perf_tracker",
+    srcs: [
+        "src/trace_processor/importers/perf/dso_tracker.cc",
+    ],
+}
+
 // GN: //src/trace_processor/importers/perf:unittests
 filegroup {
     name: "perfetto_src_trace_processor_importers_perf_unittests",
@@ -12506,6 +12563,14 @@
     ],
 }
 
+// GN: //src/trace_processor/importers/zip:full
+filegroup {
+    name: "perfetto_src_trace_processor_importers_zip_full",
+    srcs: [
+        "src/trace_processor/importers/zip/zip_trace_reader.cc",
+    ],
+}
+
 // GN: //src/trace_processor:lib
 filegroup {
     name: "perfetto_src_trace_processor_lib",
@@ -13050,13 +13115,18 @@
         "src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/slices.sql",
         "src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/thread_states.sql",
         "src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/timestamps.sql",
+        "src/trace_processor/perfetto_sql/stdlib/gpu/frequency.sql",
         "src/trace_processor/perfetto_sql/stdlib/graphs/dominator_tree.sql",
         "src/trace_processor/perfetto_sql/stdlib/graphs/partition.sql",
         "src/trace_processor/perfetto_sql/stdlib/graphs/search.sql",
         "src/trace_processor/perfetto_sql/stdlib/intervals/intersect.sql",
         "src/trace_processor/perfetto_sql/stdlib/intervals/overlap.sql",
         "src/trace_processor/perfetto_sql/stdlib/linux/cpu_idle.sql",
+        "src/trace_processor/perfetto_sql/stdlib/memory/android/gpu.sql",
         "src/trace_processor/perfetto_sql/stdlib/memory/heap_graph_dominator_tree.sql",
+        "src/trace_processor/perfetto_sql/stdlib/memory/linux/general.sql",
+        "src/trace_processor/perfetto_sql/stdlib/memory/linux/high_watermark.sql",
+        "src/trace_processor/perfetto_sql/stdlib/memory/linux/process.sql",
         "src/trace_processor/perfetto_sql/stdlib/pkvm/hypervisor.sql",
         "src/trace_processor/perfetto_sql/stdlib/prelude/casts.sql",
         "src/trace_processor/perfetto_sql/stdlib/prelude/slices.sql",
@@ -13518,10 +13588,10 @@
         "src/trace_redaction/proto_util.cc",
         "src/trace_redaction/prune_package_list.cc",
         "src/trace_redaction/redact_ftrace_event.cc",
-        "src/trace_redaction/redact_process_free.cc",
         "src/trace_redaction/redact_sched_switch.cc",
         "src/trace_redaction/redact_task_newtask.cc",
         "src/trace_redaction/remap_scheduling_events.cc",
+        "src/trace_redaction/remove_process_free_comm.cc",
         "src/trace_redaction/scrub_ftrace_events.cc",
         "src/trace_redaction/scrub_process_stats.cc",
         "src/trace_redaction/scrub_process_trees.cc",
@@ -13529,6 +13599,7 @@
         "src/trace_redaction/suspend_resume.cc",
         "src/trace_redaction/trace_redaction_framework.cc",
         "src/trace_redaction/trace_redactor.cc",
+        "src/trace_redaction/verify_integrity.cc",
     ],
 }
 
@@ -13547,10 +13618,10 @@
         "src/trace_redaction/process_thread_timeline_unittest.cc",
         "src/trace_redaction/proto_util_unittest.cc",
         "src/trace_redaction/prune_package_list_unittest.cc",
-        "src/trace_redaction/redact_process_free_unittest.cc",
         "src/trace_redaction/redact_sched_switch_unittest.cc",
         "src/trace_redaction/redact_task_newtask_unittest.cc",
         "src/trace_redaction/remap_scheduling_events_unittest.cc",
+        "src/trace_redaction/remove_process_free_comm_unittest.cc",
         "src/trace_redaction/suspend_resume_unittest.cc",
     ],
 }
@@ -14899,6 +14970,7 @@
         ":perfetto_protos_perfetto_trace_translation_lite_gen",
         ":perfetto_protos_perfetto_trace_translation_zero_gen",
         ":perfetto_protos_third_party_pprof_zero_gen",
+        ":perfetto_protos_third_party_simpleperf_zero_gen",
         ":perfetto_protos_third_party_statsd_config_zero_gen",
         ":perfetto_src_android_internal_headers",
         ":perfetto_src_android_internal_lazy_library_loader",
@@ -15013,6 +15085,7 @@
         ":perfetto_src_trace_processor_importers_ninja_ninja",
         ":perfetto_src_trace_processor_importers_perf_perf",
         ":perfetto_src_trace_processor_importers_perf_record",
+        ":perfetto_src_trace_processor_importers_perf_tracker",
         ":perfetto_src_trace_processor_importers_perf_unittests",
         ":perfetto_src_trace_processor_importers_proto_full",
         ":perfetto_src_trace_processor_importers_proto_minimal",
@@ -15026,6 +15099,7 @@
         ":perfetto_src_trace_processor_importers_systrace_systrace_line",
         ":perfetto_src_trace_processor_importers_systrace_systrace_parser",
         ":perfetto_src_trace_processor_importers_systrace_unittests",
+        ":perfetto_src_trace_processor_importers_zip_full",
         ":perfetto_src_trace_processor_lib",
         ":perfetto_src_trace_processor_metatrace",
         ":perfetto_src_trace_processor_metrics_metrics",
@@ -15275,6 +15349,7 @@
         "perfetto_protos_perfetto_trace_translation_lite_gen_headers",
         "perfetto_protos_perfetto_trace_translation_zero_gen_headers",
         "perfetto_protos_third_party_pprof_zero_gen_headers",
+        "perfetto_protos_third_party_simpleperf_zero_gen_headers",
         "perfetto_protos_third_party_statsd_config_zero_gen_headers",
         "perfetto_src_base_version_gen_h",
         "perfetto_src_ipc_test_messages_cpp_gen_headers",
@@ -15975,6 +16050,7 @@
         ":perfetto_protos_perfetto_trace_track_event_zero_gen",
         ":perfetto_protos_perfetto_trace_translation_zero_gen",
         ":perfetto_protos_third_party_pprof_zero_gen",
+        ":perfetto_protos_third_party_simpleperf_zero_gen",
         ":perfetto_src_base_base",
         ":perfetto_src_base_http_http",
         ":perfetto_src_base_unix_socket",
@@ -15987,6 +16063,7 @@
         ":perfetto_src_protozero_protozero",
         ":perfetto_src_trace_processor_containers_containers",
         ":perfetto_src_trace_processor_db_column_column",
+        ":perfetto_src_trace_processor_db_compare",
         ":perfetto_src_trace_processor_db_db",
         ":perfetto_src_trace_processor_db_minimal",
         ":perfetto_src_trace_processor_export_json",
@@ -16010,6 +16087,7 @@
         ":perfetto_src_trace_processor_importers_ninja_ninja",
         ":perfetto_src_trace_processor_importers_perf_perf",
         ":perfetto_src_trace_processor_importers_perf_record",
+        ":perfetto_src_trace_processor_importers_perf_tracker",
         ":perfetto_src_trace_processor_importers_proto_full",
         ":perfetto_src_trace_processor_importers_proto_minimal",
         ":perfetto_src_trace_processor_importers_proto_packet_sequence_state_generation_hdr",
@@ -16019,6 +16097,7 @@
         ":perfetto_src_trace_processor_importers_systrace_full",
         ":perfetto_src_trace_processor_importers_systrace_systrace_line",
         ":perfetto_src_trace_processor_importers_systrace_systrace_parser",
+        ":perfetto_src_trace_processor_importers_zip_full",
         ":perfetto_src_trace_processor_lib",
         ":perfetto_src_trace_processor_metatrace",
         ":perfetto_src_trace_processor_metrics_metrics",
@@ -16102,6 +16181,7 @@
         "perfetto_protos_perfetto_trace_track_event_zero_gen_headers",
         "perfetto_protos_perfetto_trace_translation_zero_gen_headers",
         "perfetto_protos_third_party_pprof_zero_gen_headers",
+        "perfetto_protos_third_party_simpleperf_zero_gen_headers",
         "perfetto_src_base_version_gen_h",
         "perfetto_src_trace_processor_importers_proto_gen_cc_chrome_track_event_descriptor",
         "perfetto_src_trace_processor_importers_proto_gen_cc_config_descriptor",
@@ -16216,10 +16296,12 @@
         ":perfetto_protos_perfetto_trace_system_info_zero_gen",
         ":perfetto_protos_perfetto_trace_track_event_zero_gen",
         ":perfetto_protos_perfetto_trace_translation_zero_gen",
+        ":perfetto_protos_third_party_simpleperf_zero_gen",
         ":perfetto_src_base_base",
         ":perfetto_src_protozero_protozero",
         ":perfetto_src_trace_processor_containers_containers",
         ":perfetto_src_trace_processor_db_column_column",
+        ":perfetto_src_trace_processor_db_compare",
         ":perfetto_src_trace_processor_db_minimal",
         ":perfetto_src_trace_processor_importers_common_common",
         ":perfetto_src_trace_processor_importers_common_parser_types",
@@ -16230,6 +16312,7 @@
         ":perfetto_src_trace_processor_importers_json_minimal",
         ":perfetto_src_trace_processor_importers_memory_tracker_graph_processor",
         ":perfetto_src_trace_processor_importers_perf_record",
+        ":perfetto_src_trace_processor_importers_perf_tracker",
         ":perfetto_src_trace_processor_importers_proto_minimal",
         ":perfetto_src_trace_processor_importers_proto_packet_sequence_state_generation_hdr",
         ":perfetto_src_trace_processor_importers_proto_proto_importer_module",
@@ -16295,6 +16378,7 @@
         "perfetto_protos_perfetto_trace_system_info_zero_gen_headers",
         "perfetto_protos_perfetto_trace_track_event_zero_gen_headers",
         "perfetto_protos_perfetto_trace_translation_zero_gen_headers",
+        "perfetto_protos_third_party_simpleperf_zero_gen_headers",
         "perfetto_src_trace_processor_importers_proto_gen_cc_chrome_track_event_descriptor",
         "perfetto_src_trace_processor_importers_proto_gen_cc_track_event_descriptor",
         "perfetto_src_trace_processor_tables_tables_python",
@@ -16366,6 +16450,7 @@
         ":perfetto_protos_perfetto_trace_track_event_zero_gen",
         ":perfetto_protos_perfetto_trace_translation_zero_gen",
         ":perfetto_protos_third_party_pprof_zero_gen",
+        ":perfetto_protos_third_party_simpleperf_zero_gen",
         ":perfetto_src_base_base",
         ":perfetto_src_base_version",
         ":perfetto_src_kernel_utils_syscall_table",
@@ -16376,6 +16461,7 @@
         ":perfetto_src_protozero_protozero",
         ":perfetto_src_trace_processor_containers_containers",
         ":perfetto_src_trace_processor_db_column_column",
+        ":perfetto_src_trace_processor_db_compare",
         ":perfetto_src_trace_processor_db_db",
         ":perfetto_src_trace_processor_db_minimal",
         ":perfetto_src_trace_processor_export_json",
@@ -16399,6 +16485,7 @@
         ":perfetto_src_trace_processor_importers_ninja_ninja",
         ":perfetto_src_trace_processor_importers_perf_perf",
         ":perfetto_src_trace_processor_importers_perf_record",
+        ":perfetto_src_trace_processor_importers_perf_tracker",
         ":perfetto_src_trace_processor_importers_proto_full",
         ":perfetto_src_trace_processor_importers_proto_minimal",
         ":perfetto_src_trace_processor_importers_proto_packet_sequence_state_generation_hdr",
@@ -16408,6 +16495,7 @@
         ":perfetto_src_trace_processor_importers_systrace_full",
         ":perfetto_src_trace_processor_importers_systrace_systrace_line",
         ":perfetto_src_trace_processor_importers_systrace_systrace_parser",
+        ":perfetto_src_trace_processor_importers_zip_full",
         ":perfetto_src_trace_processor_lib",
         ":perfetto_src_trace_processor_metatrace",
         ":perfetto_src_trace_processor_metrics_metrics",
@@ -16493,6 +16581,7 @@
         "perfetto_protos_perfetto_trace_track_event_zero_gen_headers",
         "perfetto_protos_perfetto_trace_translation_zero_gen_headers",
         "perfetto_protos_third_party_pprof_zero_gen_headers",
+        "perfetto_protos_third_party_simpleperf_zero_gen_headers",
         "perfetto_src_base_version_gen_h",
         "perfetto_src_trace_processor_importers_proto_gen_cc_chrome_track_event_descriptor",
         "perfetto_src_trace_processor_importers_proto_gen_cc_config_descriptor",
diff --git a/BUILD b/BUILD
index b3ff934..537ccc4 100644
--- a/BUILD
+++ b/BUILD
@@ -217,6 +217,7 @@
         ":src_kernel_utils_syscall_table",
         ":src_protozero_proto_ring_buffer",
         ":src_trace_processor_db_column_column",
+        ":src_trace_processor_db_compare",
         ":src_trace_processor_db_db",
         ":src_trace_processor_db_minimal",
         ":src_trace_processor_export_json",
@@ -240,6 +241,7 @@
         ":src_trace_processor_importers_ninja_ninja",
         ":src_trace_processor_importers_perf_perf",
         ":src_trace_processor_importers_perf_record",
+        ":src_trace_processor_importers_perf_tracker",
         ":src_trace_processor_importers_proto_full",
         ":src_trace_processor_importers_proto_minimal",
         ":src_trace_processor_importers_proto_packet_sequence_state_generation_hdr",
@@ -249,6 +251,7 @@
         ":src_trace_processor_importers_systrace_full",
         ":src_trace_processor_importers_systrace_systrace_line",
         ":src_trace_processor_importers_systrace_systrace_parser",
+        ":src_trace_processor_importers_zip_full",
         ":src_trace_processor_lib",
         ":src_trace_processor_metatrace",
         ":src_trace_processor_metrics_metrics",
@@ -345,6 +348,7 @@
                ":protos_perfetto_trace_track_event_zero",
                ":protos_perfetto_trace_translation_zero",
                ":protos_third_party_pprof_zero",
+               ":protos_third_party_simpleperf_zero",
                ":protozero",
                ":src_base_base",
                ":src_base_version",
@@ -886,6 +890,7 @@
 perfetto_filegroup(
     name = "include_perfetto_trace_processor_storage",
     srcs = [
+        "include/perfetto/trace_processor/ref_counted.h",
         "include/perfetto/trace_processor/trace_blob.h",
         "include/perfetto/trace_processor/trace_blob_view.h",
         "include/perfetto/trace_processor/trace_processor_storage.h",
@@ -899,7 +904,6 @@
         "include/perfetto/trace_processor/iterator.h",
         "include/perfetto/trace_processor/metatrace_config.h",
         "include/perfetto/trace_processor/read_trace.h",
-        "include/perfetto/trace_processor/ref_counted.h",
         "include/perfetto/trace_processor/trace_processor.h",
     ],
 )
@@ -1423,6 +1427,14 @@
     ],
 )
 
+# GN target: //src/trace_processor/db:compare
+perfetto_filegroup(
+    name = "src_trace_processor_db_compare",
+    srcs = [
+        "src/trace_processor/db/compare.h",
+    ],
+)
+
 # GN target: //src/trace_processor/db:db
 perfetto_filegroup(
     name = "src_trace_processor_db_db",
@@ -1738,6 +1750,15 @@
     ],
 )
 
+# GN target: //src/trace_processor/importers/perf:tracker
+perfetto_filegroup(
+    name = "src_trace_processor_importers_perf_tracker",
+    srcs = [
+        "src/trace_processor/importers/perf/dso_tracker.cc",
+        "src/trace_processor/importers/perf/dso_tracker.h",
+    ],
+)
+
 # GN target: //src/trace_processor/importers/proto/winscope:full
 perfetto_filegroup(
     name = "src_trace_processor_importers_proto_winscope_full",
@@ -1993,6 +2014,15 @@
     ],
 )
 
+# GN target: //src/trace_processor/importers/zip:full
+perfetto_filegroup(
+    name = "src_trace_processor_importers_zip_full",
+    srcs = [
+        "src/trace_processor/importers/zip/zip_trace_reader.cc",
+        "src/trace_processor/importers/zip/zip_trace_reader.h",
+    ],
+)
+
 # GN target: //src/trace_processor/metrics/sql/android:android
 perfetto_filegroup(
     name = "src_trace_processor_metrics_sql_android_android",
@@ -2578,6 +2608,14 @@
     ],
 )
 
+# GN target: //src/trace_processor/perfetto_sql/stdlib/gpu:gpu
+perfetto_filegroup(
+    name = "src_trace_processor_perfetto_sql_stdlib_gpu_gpu",
+    srcs = [
+        "src/trace_processor/perfetto_sql/stdlib/gpu/frequency.sql",
+    ],
+)
+
 # GN target: //src/trace_processor/perfetto_sql/stdlib/graphs:graphs
 perfetto_filegroup(
     name = "src_trace_processor_perfetto_sql_stdlib_graphs_graphs",
@@ -2605,6 +2643,24 @@
     ],
 )
 
+# GN target: //src/trace_processor/perfetto_sql/stdlib/memory/android:android
+perfetto_filegroup(
+    name = "src_trace_processor_perfetto_sql_stdlib_memory_android_android",
+    srcs = [
+        "src/trace_processor/perfetto_sql/stdlib/memory/android/gpu.sql",
+    ],
+)
+
+# GN target: //src/trace_processor/perfetto_sql/stdlib/memory/linux:linux
+perfetto_filegroup(
+    name = "src_trace_processor_perfetto_sql_stdlib_memory_linux_linux",
+    srcs = [
+        "src/trace_processor/perfetto_sql/stdlib/memory/linux/general.sql",
+        "src/trace_processor/perfetto_sql/stdlib/memory/linux/high_watermark.sql",
+        "src/trace_processor/perfetto_sql/stdlib/memory/linux/process.sql",
+    ],
+)
+
 # GN target: //src/trace_processor/perfetto_sql/stdlib/memory:memory
 perfetto_filegroup(
     name = "src_trace_processor_perfetto_sql_stdlib_memory_memory",
@@ -2717,9 +2773,12 @@
         ":src_trace_processor_perfetto_sql_stdlib_cpu_cpu",
         ":src_trace_processor_perfetto_sql_stdlib_cpu_utilization_utilization",
         ":src_trace_processor_perfetto_sql_stdlib_deprecated_v42_common_common",
+        ":src_trace_processor_perfetto_sql_stdlib_gpu_gpu",
         ":src_trace_processor_perfetto_sql_stdlib_graphs_graphs",
         ":src_trace_processor_perfetto_sql_stdlib_intervals_intervals",
         ":src_trace_processor_perfetto_sql_stdlib_linux_linux",
+        ":src_trace_processor_perfetto_sql_stdlib_memory_android_android",
+        ":src_trace_processor_perfetto_sql_stdlib_memory_linux_linux",
         ":src_trace_processor_perfetto_sql_stdlib_memory_memory",
         ":src_trace_processor_perfetto_sql_stdlib_pkvm_pkvm",
         ":src_trace_processor_perfetto_sql_stdlib_prelude_prelude",
@@ -5557,6 +5616,25 @@
     ],
 )
 
+# GN target: //protos/third_party/simpleperf:source_set
+perfetto_proto_library(
+    name = "protos_third_party_simpleperf_protos",
+    srcs = [
+        "protos/third_party/simpleperf/record_file.proto",
+    ],
+    visibility = [
+        PERFETTO_CONFIG.proto_library_visibility,
+    ],
+)
+
+# GN target: //protos/third_party/simpleperf:zero
+perfetto_cc_protozero_library(
+    name = "protos_third_party_simpleperf_zero",
+    deps = [
+        ":protos_third_party_simpleperf_protos",
+    ],
+)
+
 # GN target: //protos/third_party/statsd:config_source_set
 perfetto_proto_library(
     name = "protos_third_party_statsd_config_protos",
@@ -5910,6 +5988,7 @@
     srcs = [
         ":src_kernel_utils_syscall_table",
         ":src_trace_processor_db_column_column",
+        ":src_trace_processor_db_compare",
         ":src_trace_processor_db_db",
         ":src_trace_processor_db_minimal",
         ":src_trace_processor_export_json",
@@ -5933,6 +6012,7 @@
         ":src_trace_processor_importers_ninja_ninja",
         ":src_trace_processor_importers_perf_perf",
         ":src_trace_processor_importers_perf_record",
+        ":src_trace_processor_importers_perf_tracker",
         ":src_trace_processor_importers_proto_full",
         ":src_trace_processor_importers_proto_minimal",
         ":src_trace_processor_importers_proto_packet_sequence_state_generation_hdr",
@@ -5942,6 +6022,7 @@
         ":src_trace_processor_importers_systrace_full",
         ":src_trace_processor_importers_systrace_systrace_line",
         ":src_trace_processor_importers_systrace_systrace_parser",
+        ":src_trace_processor_importers_zip_full",
         ":src_trace_processor_lib",
         ":src_trace_processor_metatrace",
         ":src_trace_processor_metrics_metrics",
@@ -6038,6 +6119,7 @@
                ":protos_perfetto_trace_track_event_zero",
                ":protos_perfetto_trace_translation_zero",
                ":protos_third_party_pprof_zero",
+               ":protos_third_party_simpleperf_zero",
                ":protozero",
                ":src_base_base",
                ":src_trace_processor_containers_containers",
@@ -6086,6 +6168,7 @@
         ":src_profiling_symbolizer_symbolizer",
         ":src_protozero_proto_ring_buffer",
         ":src_trace_processor_db_column_column",
+        ":src_trace_processor_db_compare",
         ":src_trace_processor_db_db",
         ":src_trace_processor_db_minimal",
         ":src_trace_processor_export_json",
@@ -6109,6 +6192,7 @@
         ":src_trace_processor_importers_ninja_ninja",
         ":src_trace_processor_importers_perf_perf",
         ":src_trace_processor_importers_perf_record",
+        ":src_trace_processor_importers_perf_tracker",
         ":src_trace_processor_importers_proto_full",
         ":src_trace_processor_importers_proto_minimal",
         ":src_trace_processor_importers_proto_packet_sequence_state_generation_hdr",
@@ -6118,6 +6202,7 @@
         ":src_trace_processor_importers_systrace_full",
         ":src_trace_processor_importers_systrace_systrace_line",
         ":src_trace_processor_importers_systrace_systrace_parser",
+        ":src_trace_processor_importers_zip_full",
         ":src_trace_processor_lib",
         ":src_trace_processor_metatrace",
         ":src_trace_processor_metrics_metrics",
@@ -6203,6 +6288,7 @@
                ":protos_perfetto_trace_track_event_zero",
                ":protos_perfetto_trace_translation_zero",
                ":protos_third_party_pprof_zero",
+               ":protos_third_party_simpleperf_zero",
                ":protozero",
                ":src_base_base",
                ":src_base_http_http",
@@ -6322,6 +6408,7 @@
         ":src_profiling_symbolizer_symbolizer",
         ":src_protozero_proto_ring_buffer",
         ":src_trace_processor_db_column_column",
+        ":src_trace_processor_db_compare",
         ":src_trace_processor_db_db",
         ":src_trace_processor_db_minimal",
         ":src_trace_processor_export_json",
@@ -6345,6 +6432,7 @@
         ":src_trace_processor_importers_ninja_ninja",
         ":src_trace_processor_importers_perf_perf",
         ":src_trace_processor_importers_perf_record",
+        ":src_trace_processor_importers_perf_tracker",
         ":src_trace_processor_importers_proto_full",
         ":src_trace_processor_importers_proto_minimal",
         ":src_trace_processor_importers_proto_packet_sequence_state_generation_hdr",
@@ -6354,6 +6442,7 @@
         ":src_trace_processor_importers_systrace_full",
         ":src_trace_processor_importers_systrace_systrace_line",
         ":src_trace_processor_importers_systrace_systrace_parser",
+        ":src_trace_processor_importers_zip_full",
         ":src_trace_processor_lib",
         ":src_trace_processor_metatrace",
         ":src_trace_processor_metrics_metrics",
@@ -6439,6 +6528,7 @@
                ":protos_perfetto_trace_track_event_zero",
                ":protos_perfetto_trace_translation_zero",
                ":protos_third_party_pprof_zero",
+               ":protos_third_party_simpleperf_zero",
                ":protozero",
                ":src_base_base",
                ":src_base_version",
diff --git a/CHANGELOG b/CHANGELOG
index 70cb79c..8aae7f5 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,10 +1,13 @@
 Unreleased:
   Tracing service and probes:
-    * 
+    *
   SQL Standard library:
     * Added megacycles support to CPU package. Added tables:
       `cpu_cycles_per_process`, `cpu_cycles_per_thread` and
       `cpu_cycles_per_cpu`.
+    * Improved `memory` package. Added `memory.linux.process`,
+      `memory.linux.high_watermark` and `memory.android.gpu` modules.
+    * Created `gpu` package with `gpu.frequency` module.
     * Migrated `sched.utilization` package to `cpu.utilization`.
   Trace Processor:
     * Added "time to initial display" and "time to full display" metrics to
diff --git a/bazel/proto_gen.bzl b/bazel/proto_gen.bzl
index c5fd127..e237ebc 100644
--- a/bazel/proto_gen.bzl
+++ b/bazel/proto_gen.bzl
@@ -46,13 +46,17 @@
         strip_base_path = ctx.label.package + "/"
     elif ctx.label.workspace_root:
         # This path is hit when proto targets are built as @perfetto//:xxx
-        # instead of //:xxx. This happens in embedder builds. In this case,
-        # workspace_root == "external/perfetto" and we need to rebase the paths
-        # passed to protoc.
+        # instead of //:xxx. This happens in embedder builds.
         proto_path = ctx.label.workspace_root
-        out_dir += "/" + ctx.label.workspace_root
+
+        # We could be using the sibling repository layout, in which case we do nothing.
+        if not ctx.label.workspace_root.startswith("../"):
+            # workspace_root == "external/perfetto" and we need to rebase the paths
+            # passed to protoc.
+            out_dir += "/" + ctx.label.workspace_root
         strip_base_path = ctx.label.workspace_root + "/"
 
+
     out_files = []
     suffix = ctx.attr.suffix
     for src in proto_src:
diff --git a/docs/reference/perfetto-cli.md b/docs/reference/perfetto-cli.md
index 195ceb1..377502e 100644
--- a/docs/reference/perfetto-cli.md
+++ b/docs/reference/perfetto-cli.md
@@ -124,7 +124,7 @@
 :    Specifies the path to a configuration file. In normal mode, some
      configurations may be encoded in a configuration protocol buffer.
      This file must comply with the protocol buffer schema defined in AOSP
-     [`trace_config.proto`](/protos/perfetto/config/data_source_config.proto).
+     [`trace_config.proto`](/protos/perfetto/config/trace_config.proto).
      You select and configure the data sources using the DataSourceConfig member
      of the TraceConfig, as defined in AOSP
      [`data_source_config.proto`](/protos/perfetto/config/data_source_config.proto).
diff --git a/gn/standalone/protoc.py b/gn/standalone/protoc.py
index 52e156e..bd8913f 100644
--- a/gn/standalone/protoc.py
+++ b/gn/standalone/protoc.py
@@ -16,45 +16,9 @@
 This script exists to work-around the bad depfile generation by protoc when
 generating descriptors."""
 
-from __future__ import print_function
-import argparse
-import os
 import sys
 import subprocess
-import tempfile
-import uuid
-
-from codecs import open
-
-
-def main():
-  parser = argparse.ArgumentParser()
-  parser.add_argument('--descriptor_set_out', default=None)
-  parser.add_argument('--dependency_out', default=None)
-  parser.add_argument('protoc')
-  args, remaining = parser.parse_known_args()
-
-  if args.dependency_out and args.descriptor_set_out:
-    tmp_path = os.path.join(tempfile.gettempdir(), str(uuid.uuid4()))
-    custom = [
-        '--descriptor_set_out', args.descriptor_set_out, '--dependency_out',
-        tmp_path
-    ]
-    try:
-      cmd = [args.protoc] + custom + remaining
-      subprocess.check_call(cmd)
-      with open(tmp_path, 'rb') as tmp_rd:
-        dependency_data = tmp_rd.read().decode('utf-8')
-    finally:
-      if os.path.exists(tmp_path):
-        os.unlink(tmp_path)
-
-    with open(args.dependency_out, 'w', encoding='utf-8') as f:
-      f.write(args.descriptor_set_out + ":")
-      f.write(dependency_data)
-  else:
-    subprocess.check_call(sys.argv[1:])
 
 
 if __name__ == '__main__':
-  sys.exit(main())
+  sys.exit(subprocess.call(sys.argv[1:]))
diff --git a/include/perfetto/ext/base/getopt.h b/include/perfetto/ext/base/getopt.h
index bf993fc..abf8cca 100644
--- a/include/perfetto/ext/base/getopt.h
+++ b/include/perfetto/ext/base/getopt.h
@@ -45,7 +45,7 @@
     ::perfetto::base::getopt_compat::required_argument;
 
 #else
-#include <getopt.h>
+#include <getopt.h>  // IWYU pragma: export
 #endif
 
 #endif  // INCLUDE_PERFETTO_EXT_BASE_GETOPT_H_
diff --git a/include/perfetto/trace_processor/BUILD.gn b/include/perfetto/trace_processor/BUILD.gn
index d0dae7f..8c26054 100644
--- a/include/perfetto/trace_processor/BUILD.gn
+++ b/include/perfetto/trace_processor/BUILD.gn
@@ -17,7 +17,6 @@
     "iterator.h",
     "metatrace_config.h",
     "read_trace.h",
-    "ref_counted.h",
     "trace_processor.h",
   ]
   public_deps = [
@@ -28,6 +27,7 @@
 
 source_set("storage") {
   sources = [
+    "ref_counted.h",
     "trace_blob.h",
     "trace_blob_view.h",
     "trace_processor_storage.h",
diff --git a/perfetto.rc b/perfetto.rc
index 3f1801d..e217db2 100644
--- a/perfetto.rc
+++ b/perfetto.rc
@@ -58,7 +58,7 @@
     mkdir /data/misc/perfetto-traces/bugreport 0773 root shell
 
     # Traces in this directory are only accessed by system server
-    mkdir /data/misc/perfetto-traces/profiling 0773 root shell
+    mkdir /data/misc/perfetto-traces/profiling 0733 root shell
 
     # This directory allows shell to save configs file in a place where the
     # perfetto cmdline client can read then. /data/local/tmp/ isn't safe because
diff --git a/protos/perfetto/common/observable_events.proto b/protos/perfetto/common/observable_events.proto
index 85767a6..b841a0b 100644
--- a/protos/perfetto/common/observable_events.proto
+++ b/protos/perfetto/common/observable_events.proto
@@ -63,6 +63,9 @@
     // consumer has no idea of what is the TSID of its own tracing session and
     // there is no other good way to plumb it.
     optional int64 tracing_session_id = 1;
+
+    // The trigger name of the CLONE_SNAPSHOT trigger which was hit.
+    optional string trigger_name = 2;
   }
 
   repeated DataSourceInstanceStateChange instance_state_changes = 1;
diff --git a/protos/perfetto/trace/android/BUILD.gn b/protos/perfetto/trace/android/BUILD.gn
index 900a64b..90234da 100644
--- a/protos/perfetto/trace/android/BUILD.gn
+++ b/protos/perfetto/trace/android/BUILD.gn
@@ -15,7 +15,10 @@
 import("../../../../gn/proto_library.gni")
 
 perfetto_proto_library("@TYPE@") {
-  deps = [ "../../common:@TYPE@", ":winscope_regular_@TYPE@" ]
+  deps = [
+    ":winscope_regular_@TYPE@",
+    "../../common:@TYPE@",
+  ]
 
   sources = [
     "android_game_intervention_list.proto",
@@ -44,8 +47,8 @@
 # Winscope messages added to TracePacket directly
 perfetto_proto_library("winscope_regular_@TYPE@") {
   deps = [
-    "../../common:@TYPE@",
     ":winscope_common_@TYPE@",
+    "../../common:@TYPE@",
   ]
   sources = [
     "protolog.proto",
@@ -64,19 +67,19 @@
   ]
   public_deps = [ ":winscope_common_@TYPE@" ]
   sources = [
-    "inputmethodeditor.proto",
     "graphics/pixelformat.proto",
+    "inputmethodeditor.proto",
     "inputmethodservice/inputmethodservice.proto",
     "inputmethodservice/softinputwindow.proto",
     "server/inputmethod/inputmethodmanagerservice.proto",
     "typedef.proto",
-    "view/inputmethod/editorinfo.proto",
-    "view/inputmethod/inputconnection.proto",
-    "view/inputmethod/inputmethodmanager.proto",
     "view/display.proto",
     "view/displaycutout.proto",
     "view/imefocuscontroller.proto",
     "view/imeinsetssourceconsumer.proto",
+    "view/inputmethod/editorinfo.proto",
+    "view/inputmethod/inputconnection.proto",
+    "view/inputmethod/inputmethodmanager.proto",
     "view/insetsanimationcontrolimpl.proto",
     "view/insetscontroller.proto",
     "view/insetssource.proto",
@@ -95,8 +98,8 @@
   proto_generators = [ "descriptor" ]
   generate_descriptor = "winscope.descriptor"
   deps = [
-    ":winscope_regular_source_set",
     ":winscope_extensions_source_set",
+    ":winscope_regular_source_set",
   ]
   sources = [ "winscope.proto" ]
   import_dirs = [ "${perfetto_protobuf_src_dir}" ]
diff --git a/protos/third_party/simpleperf/BUILD.gn b/protos/third_party/simpleperf/BUILD.gn
new file mode 100644
index 0000000..da934be
--- /dev/null
+++ b/protos/third_party/simpleperf/BUILD.gn
@@ -0,0 +1,19 @@
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import("../../../gn/proto_library.gni")
+
+perfetto_proto_library("@TYPE@") {
+  sources = [ "record_file.proto" ]
+}
diff --git a/protos/third_party/simpleperf/record_file.proto b/protos/third_party/simpleperf/record_file.proto
new file mode 100644
index 0000000..ce2962e
--- /dev/null
+++ b/protos/third_party/simpleperf/record_file.proto
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// message types used in perf.data.
+
+syntax = "proto3";
+
+// This is in perfetto.third_party to avoid clashing with potential other
+// copies of this proto.
+package perfetto.third_party.simpleperf.proto;
+
+message DebugUnwindFeature {
+  message File {
+    string path = 1;
+    uint64 size = 2;
+  }
+
+  repeated File file = 1;
+}
+
+message FileFeature {
+  // This enum is not defined in the original simpleperf proto file.
+  // We added it here for convenience. Simpleperf uses a regular c++ enum
+  // instead. See comment in type field.
+  enum DsoType {
+    DSO_KERNEL = 0;
+    DSO_KERNEL_MODULE = 1;
+    DSO_ELF_FILE = 2;
+    DSO_DEX_FILE = 3;
+    DSO_SYMBOL_MAP_FILE = 4;
+    DSO_UNKNOWN_FILE = 5;
+  }
+  string path = 1;
+  // The original simpleperf proto defines this field as a uint32. We use an
+  // enum here instead for convenience. enum fields are encoded as if they were
+  // int32 values, and both uint32 and int32 values have the same proto wire
+  // format, so using the enum here is fine.
+  DsoType type = 2;
+  uint64 min_vaddr = 3;
+
+  message Symbol {
+    uint64 vaddr = 1;
+    uint32 len = 2;
+    string name = 3;
+  }
+  repeated Symbol symbol = 4;
+
+  message DexFile {
+    repeated uint64 dex_file_offset = 1;
+  }
+  message ElfFile {
+    uint64 file_offset_of_min_vaddr = 1;
+  }
+  message KernelModule {
+    uint64 memory_offset_of_min_vaddr = 1;
+  }
+
+  oneof type_specific_msg {
+    DexFile dex_file = 5;            // Only when type = DSO_DEX_FILE
+    ElfFile elf_file = 6;            // Only when type = DSO_ELF_FILE
+    KernelModule kernel_module = 7;  // Only when type = DSO_KERNEL_MODULE
+  }
+}
\ No newline at end of file
diff --git a/python/generators/diff_tests/runner.py b/python/generators/diff_tests/runner.py
index 0fe0982..53a5df4 100644
--- a/python/generators/diff_tests/runner.py
+++ b/python/generators/diff_tests/runner.py
@@ -379,6 +379,7 @@
       return res
 
     if result.exit_code != 0 or not result.passed:
+      result.passed = False
       str += result.stderr
 
       if result.exit_code == 0:
diff --git a/src/android_internal/statsd_logging.cc b/src/android_internal/statsd_logging.cc
index a164adf..09b55a2 100644
--- a/src/android_internal/statsd_logging.cc
+++ b/src/android_internal/statsd_logging.cc
@@ -16,12 +16,11 @@
 
 #include "src/android_internal/statsd_logging.h"
 
-#include <string.h>
+#include <cstdint>
 
 #include <statslog_perfetto.h>
 
-namespace perfetto {
-namespace android_internal {
+namespace perfetto::android_internal {
 
 void StatsdLogUploadEvent(PerfettoStatsdAtom atom,
                           int64_t uuid_lsb,
@@ -35,5 +34,4 @@
   stats_write(PERFETTO_TRIGGER, static_cast<int32_t>(atom), trigger_name);
 }
 
-}  // namespace android_internal
-}  // namespace perfetto
+}  // namespace perfetto::android_internal
diff --git a/src/android_stats/perfetto_atoms.h b/src/android_stats/perfetto_atoms.h
index 1981a77..6352042 100644
--- a/src/android_stats/perfetto_atoms.h
+++ b/src/android_stats/perfetto_atoms.h
@@ -27,6 +27,8 @@
   // Checkpoints inside perfetto_cmd before tracing is finished.
   kTraceBegin = 1,
   kBackgroundTraceBegin = 2,
+  kCloneTraceBegin = 55,
+  kCloneTriggerTraceBegin = 56,
   kOnConnect = 3,
 
   // Guardrails inside perfetto_cmd before tracing is finished.
@@ -105,7 +107,7 @@
   // longer supports uploading traces using Dropbox.
   // reserved 5, 6, 7;
 
-  // Contained status of guardrail state initalization and upload limit in
+  // Contained status of guardrail state initialization and upload limit in
   // perfetto_cmd. Removed as perfetto no longer manages stateful guardrails
   // reserved 44, 45, 46;
 };
diff --git a/src/android_stats/statsd_logging_helper.cc b/src/android_stats/statsd_logging_helper.cc
index c943027..1f5011a 100644
--- a/src/android_stats/statsd_logging_helper.cc
+++ b/src/android_stats/statsd_logging_helper.cc
@@ -16,10 +16,12 @@
 
 #include "src/android_stats/statsd_logging_helper.h"
 
+#include <cstdint>
 #include <string>
+#include <vector>
 
 #include "perfetto/base/build_config.h"
-#include "perfetto/base/compiler.h"
+#include "src/android_stats/perfetto_atoms.h"
 
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) && \
     PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD)
@@ -27,8 +29,7 @@
 #include "src/android_internal/statsd_logging.h"       // nogncheck
 #endif
 
-namespace perfetto {
-namespace android_stats {
+namespace perfetto::android_stats {
 
 // Make sure we don't accidentally log on non-Android tree build. Note that even
 // removing this ifdef still doesn't make uploads work on OS_ANDROID.
@@ -75,5 +76,4 @@
                            const std::vector<std::string>&) {}
 #endif
 
-}  // namespace android_stats
-}  // namespace perfetto
+}  // namespace perfetto::android_stats
diff --git a/src/android_stats/statsd_logging_helper.h b/src/android_stats/statsd_logging_helper.h
index 5f0911f..0ba2d65 100644
--- a/src/android_stats/statsd_logging_helper.h
+++ b/src/android_stats/statsd_logging_helper.h
@@ -18,6 +18,7 @@
 #define SRC_ANDROID_STATS_STATSD_LOGGING_HELPER_H_
 
 #include <stdint.h>
+#include <optional>
 #include <string>
 #include <vector>
 
diff --git a/src/perfetto_cmd/perfetto_cmd.cc b/src/perfetto_cmd/perfetto_cmd.cc
index 2ccd24e..e11a485 100644
--- a/src/perfetto_cmd/perfetto_cmd.cc
+++ b/src/perfetto_cmd/perfetto_cmd.cc
@@ -16,46 +16,47 @@
 
 #include "src/perfetto_cmd/perfetto_cmd.h"
 
-#include "perfetto/base/build_config.h"
-#include "perfetto/base/proc_utils.h"
-#include "perfetto/ext/base/scoped_file.h"
-#include "perfetto/ext/base/string_splitter.h"
-
 #include <fcntl.h>
 #include <stdio.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <time.h>
 
-// For dup() (and _setmode() on windows).
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
-#include <fcntl.h>
-#include <io.h>
-#else
-#include <unistd.h>
-#endif
-
+#include <algorithm>
+#include <array>
 #include <atomic>
 #include <chrono>
-#include <fstream>
+#include <cinttypes>
+#include <cstdint>
+#include <cstdlib>
+#include <cstring>
+#include <functional>
 #include <iostream>
 #include <iterator>
+#include <memory>
 #include <mutex>
+#include <optional>
 #include <random>
-#include <sstream>
+#include <string>
 #include <thread>
+#include <utility>
+#include <vector>
 
+#include "perfetto/base/build_config.h"
 #include "perfetto/base/compiler.h"
 #include "perfetto/base/logging.h"
-#include "perfetto/base/time.h"
-#include "perfetto/ext/base/android_utils.h"
+#include "perfetto/base/proc_utils.h"         // IWYU pragma: keep
+#include "perfetto/ext/base/android_utils.h"  // IWYU pragma: keep
 #include "perfetto/ext/base/ctrl_c_handler.h"
 #include "perfetto/ext/base/file_utils.h"
-#include "perfetto/ext/base/getopt.h"
+#include "perfetto/ext/base/getopt.h"  // IWYU pragma: keep
 #include "perfetto/ext/base/no_destructor.h"
 #include "perfetto/ext/base/pipe.h"
+#include "perfetto/ext/base/scoped_file.h"
+#include "perfetto/ext/base/string_splitter.h"
+#include "perfetto/ext/base/string_utils.h"
 #include "perfetto/ext/base/string_view.h"
-#include "perfetto/ext/base/temp_file.h"
+#include "perfetto/ext/base/thread_task_runner.h"
 #include "perfetto/ext/base/thread_utils.h"
 #include "perfetto/ext/base/utils.h"
 #include "perfetto/ext/base/uuid.h"
@@ -64,11 +65,14 @@
 #include "perfetto/ext/traced/traced.h"
 #include "perfetto/ext/tracing/core/basic_types.h"
 #include "perfetto/ext/tracing/core/trace_packet.h"
-#include "perfetto/protozero/proto_utils.h"
-#include "perfetto/tracing/core/data_source_descriptor.h"
+#include "perfetto/ext/tracing/core/tracing_service.h"
+#include "perfetto/ext/tracing/ipc/consumer_ipc_client.h"
+#include "perfetto/tracing/core/flush_flags.h"
+#include "perfetto/tracing/core/forward_decls.h"
 #include "perfetto/tracing/core/trace_config.h"
-#include "perfetto/tracing/core/tracing_service_state.h"
 #include "perfetto/tracing/default_socket.h"
+#include "protos/perfetto/common/data_source_descriptor.gen.h"
+#include "src/android_stats/perfetto_atoms.h"
 #include "src/android_stats/statsd_logging_helper.h"
 #include "src/perfetto_cmd/bugreport_path.h"
 #include "src/perfetto_cmd/config.h"
@@ -81,6 +85,14 @@
 #include "protos/perfetto/common/tracing_service_state.gen.h"
 #include "protos/perfetto/common/track_event_descriptor.gen.h"
 
+// For dup() (and _setmode() on windows).
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+#include <fcntl.h>
+#include <io.h>
+#else
+#include <unistd.h>
+#endif
+
 namespace perfetto {
 namespace {
 
@@ -92,7 +104,7 @@
 class LoggingErrorReporter : public ErrorReporter {
  public:
   LoggingErrorReporter(std::string file_name, const char* config)
-      : file_name_(file_name), config_(config) {}
+      : file_name_(std::move(file_name)), config_(config) {}
 
   void AddError(size_t row,
                 size_t column,
@@ -1014,7 +1026,14 @@
     std::this_thread::sleep_for(std::chrono::milliseconds(dist(minstd)));
   }
 
-  if (trace_config_->trigger_config().trigger_timeout_ms() == 0) {
+  if (clone_tsid_) {
+    if (snapshot_trigger_name_.empty()) {
+      LogUploadEvent(PerfettoStatsdAtom::kCloneTraceBegin);
+    } else {
+      LogUploadEvent(PerfettoStatsdAtom::kCloneTriggerTraceBegin,
+                     snapshot_trigger_name_);
+    }
+  } else if (trace_config_->trigger_config().trigger_timeout_ms() == 0) {
     LogUploadEvent(PerfettoStatsdAtom::kTraceBegin);
   } else {
     LogUploadEvent(PerfettoStatsdAtom::kBackgroundTraceBegin);
@@ -1533,11 +1552,15 @@
   }
   if (observable_events.has_clone_trigger_hit()) {
     int64_t tsid = observable_events.clone_trigger_hit().tracing_session_id();
-    OnCloneSnapshotTriggerReceived(static_cast<TracingSessionID>(tsid));
+    std::string trigger_name =
+        observable_events.clone_trigger_hit().trigger_name();
+    OnCloneSnapshotTriggerReceived(static_cast<TracingSessionID>(tsid),
+                                   std::move(trigger_name));
   }
 }
 
-void PerfettoCmd::OnCloneSnapshotTriggerReceived(TracingSessionID tsid) {
+void PerfettoCmd::OnCloneSnapshotTriggerReceived(TracingSessionID tsid,
+                                                 std::string trigger_name) {
   std::string cmdline;
   cmdline.reserve(128);
   ArgsAppend(&cmdline, "perfetto");
@@ -1555,13 +1578,15 @@
   } else {
     PERFETTO_FATAL("Cannot use CLONE_SNAPSHOT with the current cmdline args");
   }
-  CloneSessionOnThread(tsid, cmdline, kSingleExtraThread, nullptr);
+  CloneSessionOnThread(tsid, cmdline, kSingleExtraThread,
+                       std::move(trigger_name), nullptr);
 }
 
 void PerfettoCmd::CloneSessionOnThread(
     TracingSessionID tsid,
     const std::string& cmdline,
     CloneThreadMode thread_mode,
+    std::string trigger_name,
     std::function<void()> on_clone_callback) {
   PERFETTO_DLOG("Creating snapshot for tracing session %" PRIu64, tsid);
 
@@ -1583,7 +1608,7 @@
   std::string trace_config_copy = trace_config_->SerializeAsString();
 
   snapshot_threads_.back().PostTask(
-      [tsid, cmdline, trace_config_copy, on_clone_callback] {
+      [tsid, cmdline, trace_config_copy, trigger_name, on_clone_callback] {
         int argc = 0;
         char* argv[32];
         // `splitter` needs to live on the stack for the whole scope as it owns
@@ -1595,6 +1620,7 @@
         }
         perfetto::PerfettoCmd cmd;
         cmd.snapshot_config_ = std::move(trace_config_copy);
+        cmd.snapshot_trigger_name_ = std::move(trigger_name);
         cmd.on_session_cloned_ = on_clone_callback;
         auto cmdline_res = cmd.ParseCmdlineAndMaybeDaemonize(argc, argv);
         PERFETTO_CHECK(!cmdline_res.has_value());  // No daemonization expected.
@@ -1686,7 +1712,7 @@
     ArgsAppend(&cmdline, "--clone-for-bugreport");
     ArgsAppend(&cmdline, "--out");
     ArgsAppend(&cmdline, out_path);
-    CloneSessionOnThread(it->tsid, cmdline, kNewThreadPerRequest, sync_fn);
+    CloneSessionOnThread(it->tsid, cmdline, kNewThreadPerRequest, "", sync_fn);
   }  // for(sessions)
 
   PERFETTO_DLOG("Issuing %zu CloneSession requests", num_sessions);
@@ -1720,6 +1746,15 @@
   android_stats::MaybeLogUploadEvent(atom, uuid.lsb(), uuid.msb());
 }
 
+void PerfettoCmd::LogUploadEvent(PerfettoStatsdAtom atom,
+                                 const std::string& trigger_name) {
+  if (!statsd_logging_)
+    return;
+  base::Uuid uuid(uuid_);
+  android_stats::MaybeLogUploadEvent(atom, uuid.lsb(), uuid.msb(),
+                                     trigger_name);
+}
+
 void PerfettoCmd::LogTriggerEvents(
     PerfettoTriggerAtom atom,
     const std::vector<std::string>& trigger_names) {
diff --git a/src/perfetto_cmd/perfetto_cmd.h b/src/perfetto_cmd/perfetto_cmd.h
index 844afaf..160a701 100644
--- a/src/perfetto_cmd/perfetto_cmd.h
+++ b/src/perfetto_cmd/perfetto_cmd.h
@@ -17,8 +17,7 @@
 #ifndef SRC_PERFETTO_CMD_PERFETTO_CMD_H_
 #define SRC_PERFETTO_CMD_PERFETTO_CMD_H_
 
-#include <time.h>
-
+#include <cstdint>
 #include <functional>
 #include <list>
 #include <memory>
@@ -32,9 +31,12 @@
 #include "perfetto/ext/base/scoped_file.h"
 #include "perfetto/ext/base/thread_task_runner.h"
 #include "perfetto/ext/base/unix_task_runner.h"
+#include "perfetto/ext/base/uuid.h"
 #include "perfetto/ext/base/weak_ptr.h"
+#include "perfetto/ext/tracing/core/basic_types.h"
 #include "perfetto/ext/tracing/core/consumer.h"
 #include "perfetto/ext/tracing/ipc/consumer_ipc_client.h"
+#include "perfetto/tracing/core/forward_decls.h"
 #include "src/android_stats/perfetto_atoms.h"
 #include "src/perfetto_cmd/packet_writer.h"
 
@@ -86,6 +88,7 @@
   void CloneSessionOnThread(TracingSessionID,
                             const std::string& cmdline,  // \0 separated.
                             CloneThreadMode,
+                            std::string clone_trigger_name,
                             std::function<void()> on_clone_callback);
   void OnTimeout();
   bool is_detach() const { return !detach_key_.empty(); }
@@ -126,7 +129,8 @@
   // will have no effect.
   void NotifyBgProcessPipe(BgProcessStatus status);
 
-  void OnCloneSnapshotTriggerReceived(TracingSessionID);
+  void OnCloneSnapshotTriggerReceived(TracingSessionID,
+                                      std::string trigger_name);
 
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
   static base::ScopedFile CreateUnlinkedTmpFile();
@@ -135,6 +139,7 @@
   void ReportTraceToAndroidFrameworkOrCrash();
 #endif
   void LogUploadEvent(PerfettoStatsdAtom atom);
+  void LogUploadEvent(PerfettoStatsdAtom atom, const std::string& trigger_name);
   void LogTriggerEvents(PerfettoTriggerAtom atom,
                         const std::vector<std::string>& trigger_names);
 
@@ -185,6 +190,7 @@
   std::list<base::ThreadTaskRunner> snapshot_threads_;
   int snapshot_count_ = 0;
   std::string snapshot_config_;
+  std::string snapshot_trigger_name_;
 
   base::WeakPtrFactory<PerfettoCmd> weak_factory_{this};
 };
diff --git a/src/trace_processor/BUILD.gn b/src/trace_processor/BUILD.gn
index b499f0b..802f9bc 100644
--- a/src/trace_processor/BUILD.gn
+++ b/src/trace_processor/BUILD.gn
@@ -123,6 +123,7 @@
     "importers/ftrace:minimal",
     "importers/fuchsia:fuchsia_record",
     "importers/memory_tracker:graph_processor",
+    "importers/perf:tracker",
     "importers/proto:gen_cc_chrome_track_event_descriptor",
     "importers/proto:gen_cc_track_event_descriptor",
     "importers/proto:minimal",
@@ -176,6 +177,7 @@
       "importers/proto:full",
       "importers/proto:minimal",
       "importers/systrace:full",
+      "importers/zip:full",
       "metrics",
       "perfetto_sql/engine",
       "perfetto_sql/intrinsics/functions",
diff --git a/src/trace_processor/containers/string_pool.h b/src/trace_processor/containers/string_pool.h
index 75a57a2..2e03592 100644
--- a/src/trace_processor/containers/string_pool.h
+++ b/src/trace_processor/containers/string_pool.h
@@ -17,21 +17,25 @@
 #ifndef SRC_TRACE_PROCESSOR_CONTAINERS_STRING_POOL_H_
 #define SRC_TRACE_PROCESSOR_CONTAINERS_STRING_POOL_H_
 
-#include <stddef.h>
-#include <stdint.h>
-
+#include <cstddef>
+#include <cstdint>
+#include <functional>
 #include <limits>
+#include <memory>
 #include <optional>
+#include <string>
+#include <utility>
 #include <vector>
 
+#include "perfetto/base/logging.h"
 #include "perfetto/ext/base/flat_hash_map.h"
 #include "perfetto/ext/base/hash.h"
 #include "perfetto/ext/base/paged_memory.h"
+#include "perfetto/ext/base/string_view.h"
 #include "perfetto/protozero/proto_utils.h"
 #include "src/trace_processor/containers/null_term_string_view.h"
 
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
 
 // Interns strings in a string pool and hands out compact StringIds which can
 // be used to retrieve the string in O(1).
@@ -40,34 +44,38 @@
   struct Id {
     Id() = default;
 
-    bool operator==(const Id& other) const { return other.id == id; }
-    bool operator!=(const Id& other) const { return !(other == *this); }
-    bool operator<(const Id& other) const { return id < other.id; }
+    constexpr bool operator==(const Id& other) const { return other.id == id; }
+    constexpr bool operator!=(const Id& other) const {
+      return !(other == *this);
+    }
+    constexpr bool operator<(const Id& other) const { return id < other.id; }
 
-    bool is_null() const { return id == 0u; }
+    constexpr bool is_null() const { return id == 0u; }
 
-    bool is_large_string() const { return id & kLargeStringFlagBitMask; }
+    constexpr bool is_large_string() const {
+      return id & kLargeStringFlagBitMask;
+    }
 
-    uint32_t block_offset() const { return id & kBlockOffsetBitMask; }
+    constexpr uint32_t block_offset() const { return id & kBlockOffsetBitMask; }
 
-    uint32_t block_index() const {
+    constexpr uint32_t block_index() const {
       return (id & kBlockIndexBitMask) >> kNumBlockOffsetBits;
     }
 
-    uint32_t large_string_index() const {
+    constexpr uint32_t large_string_index() const {
       PERFETTO_DCHECK(is_large_string());
       return id & ~kLargeStringFlagBitMask;
     }
 
-    uint32_t raw_id() const { return id; }
+    constexpr uint32_t raw_id() const { return id; }
 
-    static Id LargeString(size_t index) {
+    static constexpr Id LargeString(size_t index) {
       PERFETTO_DCHECK(index <= static_cast<uint32_t>(index));
       PERFETTO_DCHECK(!(index & kLargeStringFlagBitMask));
       return Id(kLargeStringFlagBitMask | static_cast<uint32_t>(index));
     }
 
-    static Id BlockString(size_t index, uint32_t offset) {
+    static constexpr Id BlockString(size_t index, uint32_t offset) {
       PERFETTO_DCHECK(index < (1u << (kNumBlockIndexBits + 1)));
       PERFETTO_DCHECK(offset < (1u << (kNumBlockOffsetBits + 1)));
       return Id(~kLargeStringFlagBitMask &
@@ -80,7 +88,7 @@
     static constexpr Id Null() { return Id(0u); }
 
    private:
-    constexpr Id(uint32_t i) : id(i) {}
+    constexpr explicit Id(uint32_t i) : id(i) {}
 
     uint32_t id;
   };
@@ -88,7 +96,7 @@
   // Iterator over the strings in the pool.
   class Iterator {
    public:
-    Iterator(const StringPool*);
+    explicit Iterator(const StringPool*);
 
     explicit operator bool() const;
     Iterator& operator++();
@@ -278,7 +286,7 @@
   static NullTermStringView GetFromBlockPtr(const uint8_t* ptr) {
     uint32_t size = 0;
     const uint8_t* str_ptr = ReadSize(ptr, &size);
-    return NullTermStringView(reinterpret_cast<const char*>(str_ptr), size);
+    return {reinterpret_cast<const char*>(str_ptr), size};
   }
 
   // Lookup a string in the |large_strings_| vector. |id| should have the MSB
@@ -288,7 +296,7 @@
     size_t index = id.large_string_index();
     PERFETTO_DCHECK(index < large_strings_.size());
     const std::string* str = large_strings_[index].get();
-    return NullTermStringView(str->c_str(), str->size());
+    return {str->c_str(), str->size()};
   }
 
   // The actual memory storing the strings.
@@ -308,8 +316,7 @@
       string_index_{/*initial_capacity=*/4096u};
 };
 
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor
 
 template <>
 struct std::hash<::perfetto::trace_processor::StringPool::Id> {
diff --git a/src/trace_processor/db/column/BUILD.gn b/src/trace_processor/db/column/BUILD.gn
index d53e7ae..f77887f 100644
--- a/src/trace_processor/db/column/BUILD.gn
+++ b/src/trace_processor/db/column/BUILD.gn
@@ -43,6 +43,7 @@
     "utils.h",
   ]
   deps = [
+    "..:compare",
     "../..:metatrace",
     "../../../../gn:default_deps",
     "../../../../include/perfetto/trace_processor",
diff --git a/src/trace_processor/db/column/arrangement_overlay.cc b/src/trace_processor/db/column/arrangement_overlay.cc
index c31d7c2..e7bc8ff 100644
--- a/src/trace_processor/db/column/arrangement_overlay.cc
+++ b/src/trace_processor/db/column/arrangement_overlay.cc
@@ -68,12 +68,21 @@
 
   if (does_arrangement_order_storage_ && op != FilterOp::kGlob &&
       op != FilterOp::kRegex) {
-    Range inner_res = inner_->OrderedIndexSearchValidated(
-        op, sql_val,
-        OrderedIndices{arrangement_->data() + in.start, in.size(),
-                       arrangement_state_});
+    OrderedIndices indices{arrangement_->data() + in.start, in.size(),
+                           arrangement_state_};
+    if (op == FilterOp::kNe) {
+      // Do an equality search and "invert" the range.
+      Range inner_res =
+          inner_->OrderedIndexSearchValidated(FilterOp::kEq, sql_val, indices);
+      BitVector bv(in.start);
+      bv.Resize(in.start + inner_res.start, true);
+      bv.Resize(in.start + inner_res.end, false);
+      bv.Resize(in.end, true);
+      return RangeOrBitVector(std::move(bv));
+    }
+    Range inner_res = inner_->OrderedIndexSearchValidated(op, sql_val, indices);
     return RangeOrBitVector(
-        Range(inner_res.start + in.start, inner_res.end + in.start));
+        Range(in.start + inner_res.start, in.start + inner_res.end));
   }
 
   const auto& arrangement = *arrangement_;
@@ -196,6 +205,11 @@
   return inner_->MinElement(indices);
 }
 
+SqlValue ArrangementOverlay::ChainImpl::Get_AvoidUsingBecauseSlow(
+    uint32_t index) const {
+  return inner_->Get_AvoidUsingBecauseSlow((*arrangement_)[index]);
+}
+
 void ArrangementOverlay::ChainImpl::Serialize(StorageProto* storage) const {
   auto* arrangement_overlay = storage->set_arrangement_overlay();
   arrangement_overlay->set_values(
diff --git a/src/trace_processor/db/column/arrangement_overlay.h b/src/trace_processor/db/column/arrangement_overlay.h
index 3656b07..85b9ce4 100644
--- a/src/trace_processor/db/column/arrangement_overlay.h
+++ b/src/trace_processor/db/column/arrangement_overlay.h
@@ -19,10 +19,10 @@
 
 #include <cstdint>
 #include <memory>
+#include <optional>
 #include <string>
 #include <vector>
 
-#include "perfetto/base/logging.h"
 #include "perfetto/trace_processor/basic_types.h"
 #include "src/trace_processor/db/column/data_layer.h"
 #include "src/trace_processor/db/column/types.h"
@@ -61,13 +61,6 @@
 
     void IndexSearchValidated(FilterOp, SqlValue, Indices&) const override;
 
-    Range OrderedIndexSearchValidated(FilterOp,
-                                      SqlValue,
-                                      const OrderedIndices&) const override {
-      PERFETTO_FATAL(
-          "OrderedIndexSearch can't be called on ArrangementOverlay");
-    }
-
     void StableSort(SortToken* start,
                     SortToken* end,
                     SortDirection) const override;
@@ -78,6 +71,8 @@
 
     std::optional<Token> MinElement(Indices&) const override;
 
+    SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const override;
+
     void Serialize(StorageProto*) const override;
 
     uint32_t size() const override {
diff --git a/src/trace_processor/db/column/arrangement_overlay_unittest.cc b/src/trace_processor/db/column/arrangement_overlay_unittest.cc
index 55f310b..8820336 100644
--- a/src/trace_processor/db/column/arrangement_overlay_unittest.cc
+++ b/src/trace_processor/db/column/arrangement_overlay_unittest.cc
@@ -108,15 +108,20 @@
 }
 
 TEST(ArrangementOverlay, OrderingSearch) {
-  std::vector<uint32_t> arrangement{0, 2, 4, 1, 3};
-  auto fake = FakeStorageChain::SearchSubset(5, BitVector({0, 1, 0, 1, 0}));
+  std::vector<uint32_t> numeric_data{0, 1, 2, 0, 1, 0};
+  NumericStorage<uint32_t> numeric(&numeric_data, ColumnType::kUint32, false);
+
+  std::vector<uint32_t> arrangement{0, 3, 5, 1, 4, 2};
   ArrangementOverlay storage(&arrangement, Indices::State::kNonmonotonic);
-  auto chain =
-      storage.MakeChain(std::move(fake), DataLayer::ChainCreationArgs(true));
+  auto chain = storage.MakeChain(numeric.MakeChain(),
+                                 DataLayer::ChainCreationArgs(true));
 
   RangeOrBitVector res =
-      chain->Search(FilterOp::kGe, SqlValue::Long(0u), Range(0, 5));
+      chain->Search(FilterOp::kGe, SqlValue::Long(1u), Range(0, 5));
   ASSERT_THAT(utils::ToIndexVectorForTests(res), ElementsAre(3, 4));
+
+  res = chain->Search(FilterOp::kNe, SqlValue::Long(1u), Range(1, 6));
+  ASSERT_THAT(utils::ToIndexVectorForTests(res), ElementsAre(1, 2, 5));
 }
 
 TEST(ArrangementOverlay, StableSort) {
diff --git a/src/trace_processor/db/column/data_layer.cc b/src/trace_processor/db/column/data_layer.cc
index 634cbce..a570415 100644
--- a/src/trace_processor/db/column/data_layer.cc
+++ b/src/trace_processor/db/column/data_layer.cc
@@ -16,12 +16,15 @@
 
 #include "src/trace_processor/db/column/data_layer.h"
 
+#include <algorithm>
 #include <cstdint>
+#include <iterator>
 #include <memory>
 #include <utility>
 #include <vector>
 
 #include "perfetto/base/logging.h"
+#include "perfetto/trace_processor/basic_types.h"
 #include "src/trace_processor/containers/bit_vector.h"
 #include "src/trace_processor/containers/string_pool.h"
 #include "src/trace_processor/db/column/arrangement_overlay.h"
@@ -35,6 +38,7 @@
 #include "src/trace_processor/db/column/set_id_storage.h"
 #include "src/trace_processor/db/column/string_storage.h"
 #include "src/trace_processor/db/column/types.h"
+#include "src/trace_processor/db/compare.h"
 
 namespace perfetto::trace_processor::column {
 
@@ -113,6 +117,53 @@
   PERFETTO_FATAL("For GCC");
 }
 
+Range DataLayerChain::OrderedIndexSearchValidated(
+    FilterOp op,
+    SqlValue value,
+    const OrderedIndices& indices) const {
+  auto lb = [&]() {
+    return static_cast<uint32_t>(std::distance(
+        indices.data,
+        std::lower_bound(indices.data, indices.data + indices.size, value,
+                         [this](uint32_t idx, const SqlValue& v) {
+                           return compare::SqlValueComparator(
+                               Get_AvoidUsingBecauseSlow(idx), v);
+                         })));
+  };
+  auto ub = [&]() {
+    return static_cast<uint32_t>(std::distance(
+        indices.data,
+        std::upper_bound(indices.data, indices.data + indices.size, value,
+                         [this](const SqlValue& v, uint32_t idx) {
+                           return compare::SqlValueComparator(
+                               v, Get_AvoidUsingBecauseSlow(idx));
+                         })));
+  };
+  switch (op) {
+    case FilterOp::kEq:
+      return {lb(), ub()};
+    case FilterOp::kLe:
+      return {0, ub()};
+    case FilterOp::kLt:
+      return {0, lb()};
+    case FilterOp::kGe:
+      return {lb(), indices.size};
+    case FilterOp::kGt:
+      return {ub(), indices.size};
+    case FilterOp::kIsNull:
+      PERFETTO_CHECK(value.is_null());
+      return {0, ub()};
+    case FilterOp::kIsNotNull:
+      PERFETTO_CHECK(value.is_null());
+      return {ub(), indices.size};
+    case FilterOp::kNe:
+    case FilterOp::kGlob:
+    case FilterOp::kRegex:
+      PERFETTO_FATAL("Wrong filtering operation");
+  }
+  PERFETTO_FATAL("For GCC");
+}
+
 ArrangementOverlay::ArrangementOverlay(
     const std::vector<uint32_t>* arrangement,
     DataLayerChain::Indices::State arrangement_state)
diff --git a/src/trace_processor/db/column/data_layer.h b/src/trace_processor/db/column/data_layer.h
index 5e34539..b6f9382 100644
--- a/src/trace_processor/db/column/data_layer.h
+++ b/src/trace_processor/db/column/data_layer.h
@@ -321,9 +321,26 @@
 
   // Post-validated implementation of |OrderedIndexSearch|. See
   // |OrderedIndexSearch|'s documentation.
-  virtual Range OrderedIndexSearchValidated(FilterOp,
-                                            SqlValue,
-                                            const OrderedIndices&) const = 0;
+  Range OrderedIndexSearchValidated(FilterOp op,
+                                    SqlValue value,
+                                    const OrderedIndices& indices) const;
+
+  // Returns the SqlValue representing the value at a given index.
+  //
+  // This function might be very tempting to use as it appears cheap on the
+  // surface but because of how DataLayerChains might be layered on top of each
+  // other, this might require *several* virtual function calls per index.
+  // If you're tempted to use this, please consider instead create a new
+  // "vectorized" function instead and only using this as a last resort.
+  //
+  // The correct "class" of algorithms to use this function are cases where you
+  // have a set of indices you want to lookup and based on the value returned
+  // you will only use a fraction of them. In this case, it might be worth
+  // paying the non-vectorized lookup to vastly reduce how many indices need
+  // to be translated.
+  //
+  // An example of such an algorithm is binary search on indices.
+  virtual SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const = 0;
 };
 
 }  // namespace perfetto::trace_processor::column
diff --git a/src/trace_processor/db/column/dense_null_overlay.cc b/src/trace_processor/db/column/dense_null_overlay.cc
index 79ca3c0..426c6d2 100644
--- a/src/trace_processor/db/column/dense_null_overlay.cc
+++ b/src/trace_processor/db/column/dense_null_overlay.cc
@@ -219,50 +219,6 @@
   inner_->IndexSearchValidated(op, sql_val, indices);
 }
 
-Range DenseNullOverlay::ChainImpl::OrderedIndexSearchValidated(
-    FilterOp op,
-    SqlValue sql_val,
-    const OrderedIndices& indices) const {
-  // For NOT EQUAL the further analysis needs to be done by the caller.
-  PERFETTO_CHECK(op != FilterOp::kNe);
-
-  PERFETTO_TP_TRACE(metatrace::Category::DB,
-                    "DenseNullOverlay::ChainImpl::OrderedIndexSearch");
-
-  // We assume all NULLs are ordered to be in the front. We are looking for the
-  // first index that points to non NULL value.
-  const uint32_t* first_non_null =
-      std::partition_point(indices.data, indices.data + indices.size,
-                           [this](uint32_t i) { return !non_null_->IsSet(i); });
-
-  auto non_null_offset =
-      static_cast<uint32_t>(std::distance(indices.data, first_non_null));
-  auto non_null_size = static_cast<uint32_t>(
-      std::distance(first_non_null, indices.data + indices.size));
-
-  if (op == FilterOp::kIsNull) {
-    return {0, non_null_offset};
-  }
-
-  if (op == FilterOp::kIsNotNull) {
-    switch (inner_->ValidateSearchConstraints(op, sql_val)) {
-      case SearchValidationResult::kNoData:
-        return {};
-      case SearchValidationResult::kAllData:
-        return {non_null_offset, indices.size};
-      case SearchValidationResult::kOk:
-        break;
-    }
-  }
-
-  Range inner_range = inner_->OrderedIndexSearchValidated(
-      op, sql_val,
-      OrderedIndices{first_non_null, non_null_size,
-                     Indices::State::kNonmonotonic});
-  return {inner_range.start + non_null_offset,
-          inner_range.end + non_null_offset};
-}
-
 void DenseNullOverlay::ChainImpl::StableSort(SortToken* start,
                                              SortToken* end,
                                              SortDirection direction) const {
@@ -314,6 +270,12 @@
                                                  : *first_null_it;
 }
 
+SqlValue DenseNullOverlay::ChainImpl::Get_AvoidUsingBecauseSlow(
+    uint32_t index) const {
+  return non_null_->IsSet(index) ? inner_->Get_AvoidUsingBecauseSlow(index)
+                                 : SqlValue();
+}
+
 void DenseNullOverlay::ChainImpl::Serialize(StorageProto* storage) const {
   auto* null_overlay = storage->set_dense_null_overlay();
   non_null_->Serialize(null_overlay->set_bit_vector());
diff --git a/src/trace_processor/db/column/dense_null_overlay.h b/src/trace_processor/db/column/dense_null_overlay.h
index e253400..d0a95d2 100644
--- a/src/trace_processor/db/column/dense_null_overlay.h
+++ b/src/trace_processor/db/column/dense_null_overlay.h
@@ -19,6 +19,7 @@
 
 #include <cstdint>
 #include <memory>
+#include <optional>
 #include <string>
 
 #include "perfetto/trace_processor/basic_types.h"
@@ -56,10 +57,6 @@
 
     void IndexSearchValidated(FilterOp, SqlValue, Indices&) const override;
 
-    Range OrderedIndexSearchValidated(FilterOp,
-                                      SqlValue,
-                                      const OrderedIndices&) const override;
-
     void StableSort(SortToken* start,
                     SortToken* end,
                     SortDirection) const override;
@@ -70,6 +67,8 @@
 
     std::optional<Token> MinElement(Indices&) const override;
 
+    SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const override;
+
     void Serialize(StorageProto*) const override;
 
     uint32_t size() const override { return non_null_->size(); }
diff --git a/src/trace_processor/db/column/dense_null_overlay_unittest.cc b/src/trace_processor/db/column/dense_null_overlay_unittest.cc
index 24b1eee..7cd74f6 100644
--- a/src/trace_processor/db/column/dense_null_overlay_unittest.cc
+++ b/src/trace_processor/db/column/dense_null_overlay_unittest.cc
@@ -129,11 +129,12 @@
 }
 
 TEST(DenseNullOverlay, OrderedIndexSearch) {
-  auto fake = FakeStorageChain::SearchSubset(6, BitVector({0, 1, 0, 1, 0, 1}));
+  std::vector<uint32_t> numeric_data{0, 1, 0, 1, 0, 1};
+  NumericStorage<uint32_t> numeric(&numeric_data, ColumnType::kUint32, false);
 
   BitVector bv{0, 1, 0, 1, 0, 1};
   DenseNullOverlay storage(&bv);
-  auto chain = storage.MakeChain(std::move(fake));
+  auto chain = storage.MakeChain(numeric.MakeChain());
 
   std::vector<uint32_t> indices_vec({0, 2, 4, 1, 3, 5});
   OrderedIndices indices{indices_vec.data(), 6, Indices::State::kNonmonotonic};
@@ -146,25 +147,25 @@
   ASSERT_EQ(res.start, 3u);
   ASSERT_EQ(res.end, 6u);
 
-  res = chain->OrderedIndexSearch(FilterOp::kEq, SqlValue::Long(3), indices);
+  res = chain->OrderedIndexSearch(FilterOp::kEq, SqlValue::Long(1), indices);
   ASSERT_EQ(res.start, 3u);
   ASSERT_EQ(res.end, 6u);
 
-  res = chain->OrderedIndexSearch(FilterOp::kGt, SqlValue::Long(3), indices);
+  res = chain->OrderedIndexSearch(FilterOp::kGt, SqlValue::Long(0), indices);
   ASSERT_EQ(res.start, 3u);
   ASSERT_EQ(res.end, 6u);
 
-  res = chain->OrderedIndexSearch(FilterOp::kGe, SqlValue::Long(3), indices);
+  res = chain->OrderedIndexSearch(FilterOp::kGe, SqlValue::Long(1), indices);
   ASSERT_EQ(res.start, 3u);
   ASSERT_EQ(res.end, 6u);
 
-  res = chain->OrderedIndexSearch(FilterOp::kLt, SqlValue::Long(3), indices);
-  ASSERT_EQ(res.start, 3u);
-  ASSERT_EQ(res.end, 6u);
+  res = chain->OrderedIndexSearch(FilterOp::kLt, SqlValue::Long(1), indices);
+  ASSERT_EQ(res.start, 0u);
+  ASSERT_EQ(res.end, 3u);
 
-  res = chain->OrderedIndexSearch(FilterOp::kLe, SqlValue::Long(3), indices);
-  ASSERT_EQ(res.start, 3u);
-  ASSERT_EQ(res.end, 6u);
+  res = chain->OrderedIndexSearch(FilterOp::kLe, SqlValue::Long(0), indices);
+  ASSERT_EQ(res.start, 0u);
+  ASSERT_EQ(res.end, 3u);
 }
 
 TEST(DenseNullOverlay, SingleSearch) {
diff --git a/src/trace_processor/db/column/dummy_storage.cc b/src/trace_processor/db/column/dummy_storage.cc
index 631cbb7..ef9cacf 100644
--- a/src/trace_processor/db/column/dummy_storage.cc
+++ b/src/trace_processor/db/column/dummy_storage.cc
@@ -17,6 +17,7 @@
 #include "src/trace_processor/db/column/dummy_storage.h"
 
 #include <cstdint>
+#include <optional>
 
 #include "perfetto/base/logging.h"
 #include "perfetto/trace_processor/basic_types.h"
@@ -49,13 +50,6 @@
   PERFETTO_FATAL("Shouldn't be called");
 }
 
-Range DummyStorage::ChainImpl::OrderedIndexSearchValidated(
-    FilterOp,
-    SqlValue,
-    const OrderedIndices&) const {
-  PERFETTO_FATAL("Shouldn't be called");
-}
-
 void DummyStorage::ChainImpl::StableSort(SortToken*,
                                          SortToken*,
                                          SortDirection) const {
@@ -78,6 +72,10 @@
   PERFETTO_FATAL("Shouldn't be called");
 }
 
+SqlValue DummyStorage::ChainImpl::Get_AvoidUsingBecauseSlow(uint32_t) const {
+  PERFETTO_FATAL("Shouldn't be called");
+}
+
 void DummyStorage::ChainImpl::Serialize(StorageProto*) const {
   PERFETTO_FATAL("Shouldn't be called");
 }
diff --git a/src/trace_processor/db/column/dummy_storage.h b/src/trace_processor/db/column/dummy_storage.h
index ffa0c73..66f2497 100644
--- a/src/trace_processor/db/column/dummy_storage.h
+++ b/src/trace_processor/db/column/dummy_storage.h
@@ -18,6 +18,7 @@
 
 #include <cstdint>
 #include <memory>
+#include <optional>
 #include <string>
 
 #include "perfetto/trace_processor/basic_types.h"
@@ -45,10 +46,6 @@
 
     void IndexSearchValidated(FilterOp, SqlValue, Indices&) const override;
 
-    Range OrderedIndexSearchValidated(FilterOp,
-                                      SqlValue,
-                                      const OrderedIndices&) const override;
-
     void StableSort(SortToken* start,
                     SortToken* end,
                     SortDirection) const override;
@@ -59,6 +56,8 @@
 
     std::optional<Token> MinElement(Indices&) const override;
 
+    SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const override;
+
     void Serialize(StorageProto*) const override;
 
     uint32_t size() const override;
diff --git a/src/trace_processor/db/column/fake_storage.cc b/src/trace_processor/db/column/fake_storage.cc
index 5a26961..5015bf3 100644
--- a/src/trace_processor/db/column/fake_storage.cc
+++ b/src/trace_processor/db/column/fake_storage.cc
@@ -19,6 +19,7 @@
 #include <algorithm>
 #include <cstdint>
 #include <iterator>
+#include <optional>
 #include <utility>
 
 #include "perfetto/base/logging.h"
@@ -112,43 +113,6 @@
   PERFETTO_FATAL("For GCC");
 }
 
-Range FakeStorageChain::OrderedIndexSearchValidated(
-    FilterOp,
-    SqlValue,
-    const OrderedIndices& indices) const {
-  switch (strategy_) {
-    case kAll:
-      return {0, indices.size};
-    case kNone:
-      return {};
-    case kRange: {
-      // We are looking at intersection of |range_| and |indices_|.
-      const uint32_t* first_in_range = std::partition_point(
-          indices.data, indices.data + indices.size,
-          [this](uint32_t i) { return !range_.Contains(i); });
-      const uint32_t* first_outside_range = std::partition_point(
-          first_in_range, indices.data + indices.size,
-          [this](uint32_t i) { return range_.Contains(i); });
-      return {
-          static_cast<uint32_t>(std::distance(indices.data, first_in_range)),
-          static_cast<uint32_t>(
-              std::distance(indices.data, first_outside_range))};
-    }
-    case kBitVector:
-      // We are looking at intersection of |range_| and |bit_vector_|.
-      const uint32_t* first_set = std::partition_point(
-          indices.data, indices.data + indices.size,
-          [this](uint32_t i) { return !bit_vector_.IsSet(i); });
-      const uint32_t* first_non_set = std::partition_point(
-          first_set, indices.data + indices.size,
-          [this](uint32_t i) { return bit_vector_.IsSet(i); });
-      return {
-          static_cast<uint32_t>(std::distance(indices.data, first_set)),
-          static_cast<uint32_t>(std::distance(indices.data, first_non_set))};
-  }
-  PERFETTO_FATAL("For GCC");
-}
-
 void FakeStorageChain::Distinct(Indices&) const {
   // Fake storage shouldn't implement Distinct as it's not a binary (this index
   // passes or not) operation on a column.
@@ -166,6 +130,10 @@
   PERFETTO_FATAL("Not implemented");
 }
 
+SqlValue FakeStorageChain::Get_AvoidUsingBecauseSlow(uint32_t) const {
+  PERFETTO_FATAL("Not implemented");
+}
+
 void FakeStorageChain::Serialize(StorageProto*) const {
   // FakeStorage doesn't really make sense to serialize.
   PERFETTO_FATAL("Not implemented");
diff --git a/src/trace_processor/db/column/fake_storage.h b/src/trace_processor/db/column/fake_storage.h
index 7adf786..980a951 100644
--- a/src/trace_processor/db/column/fake_storage.h
+++ b/src/trace_processor/db/column/fake_storage.h
@@ -19,6 +19,7 @@
 
 #include <cstdint>
 #include <memory>
+#include <optional>
 #include <string>
 #include <utility>
 #include <vector>
@@ -84,10 +85,6 @@
 
   void IndexSearchValidated(FilterOp, SqlValue, Indices&) const override;
 
-  Range OrderedIndexSearchValidated(FilterOp,
-                                    SqlValue,
-                                    const OrderedIndices&) const override;
-
   void StableSort(SortToken* start,
                   SortToken* end,
                   SortDirection) const override;
@@ -95,8 +92,11 @@
   void Distinct(Indices&) const override;
 
   std::optional<Token> MaxElement(Indices&) const override;
+
   std::optional<Token> MinElement(Indices&) const override;
 
+  SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const override;
+
   void Serialize(StorageProto*) const override;
 
   uint32_t size() const override { return size_; }
diff --git a/src/trace_processor/db/column/fake_storage_unittest.cc b/src/trace_processor/db/column/fake_storage_unittest.cc
index 908b7e0..28c497f 100644
--- a/src/trace_processor/db/column/fake_storage_unittest.cc
+++ b/src/trace_processor/db/column/fake_storage_unittest.cc
@@ -43,7 +43,6 @@
 using testing::IsEmpty;
 
 using Indices = DataLayerChain::Indices;
-using OrderedIndices = DataLayerChain::OrderedIndices;
 
 TEST(FakeStorage, ValidateSearchConstraints) {
   {
@@ -197,48 +196,6 @@
   }
 }
 
-TEST(FakeStorage, OrderedIndexSearchValidated) {
-  std::vector<uint32_t> table_idx{4, 3, 2, 1};
-  OrderedIndices indices{table_idx.data(), uint32_t(table_idx.size()),
-                         Indices::State::kNonmonotonic};
-  {
-    // All passes
-    auto fake = FakeStorageChain::SearchAll(5);
-    Range ret =
-        fake->OrderedIndexSearch(FilterOp::kGe, SqlValue::Long(0u), indices);
-    EXPECT_EQ(ret, Range(0, 4));
-  }
-  {
-    // None passes
-    auto fake = FakeStorageChain::SearchNone(5);
-    Range ret =
-        fake->OrderedIndexSearch(FilterOp::kGe, SqlValue::Long(0u), indices);
-    EXPECT_EQ(ret, Range(0, 0));
-  }
-  {
-    // BitVector
-    auto fake = FakeStorageChain::SearchSubset(5, BitVector{0, 0, 1, 1, 1});
-    Range ret =
-        fake->OrderedIndexSearch(FilterOp::kGe, SqlValue::Long(0u), indices);
-    EXPECT_EQ(ret, Range(0, 3));
-  }
-  {
-    // Index vector
-    auto fake =
-        FakeStorageChain::SearchSubset(5, std::vector<uint32_t>{1, 2, 3});
-    Range ret =
-        fake->OrderedIndexSearch(FilterOp::kGe, SqlValue::Long(0u), indices);
-    EXPECT_EQ(ret, Range(1, 4));
-  }
-  {
-    // Range
-    auto fake = FakeStorageChain::SearchSubset(5, Range(1, 4));
-    Range ret =
-        fake->OrderedIndexSearch(FilterOp::kGe, SqlValue::Long(0u), indices);
-    EXPECT_EQ(ret, Range(1, 4));
-  }
-}
-
 }  // namespace
 }  // namespace column
 }  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/db/column/id_storage.cc b/src/trace_processor/db/column/id_storage.cc
index ea1e909..1ebe459 100644
--- a/src/trace_processor/db/column/id_storage.cc
+++ b/src/trace_processor/db/column/id_storage.cc
@@ -246,47 +246,6 @@
   PERFETTO_FATAL("FilterOp not matched");
 }
 
-Range IdStorage::ChainImpl::OrderedIndexSearchValidated(
-    FilterOp op,
-    SqlValue sql_val,
-    const OrderedIndices& indices) const {
-  PERFETTO_DCHECK(op != FilterOp::kNe);
-
-  PERFETTO_TP_TRACE(
-      metatrace::Category::DB, "IdStorage::ChainImpl::OrderedIndexSearch",
-      [indices, op](metatrace::Record* r) {
-        r->AddArg("Count", std::to_string(indices.size));
-        r->AddArg("Op", std::to_string(static_cast<uint32_t>(op)));
-      });
-
-  // It's a valid filter operation if |sql_val| is a double, although it
-  // requires special logic.
-  if (sql_val.type == SqlValue::kDouble) {
-    switch (utils::CompareIntColumnWithDouble(op, &sql_val)) {
-      case SearchValidationResult::kOk:
-        break;
-      case SearchValidationResult::kAllData:
-        return {0, indices.size};
-      case SearchValidationResult::kNoData:
-        return {};
-    }
-  }
-  auto val = static_cast<uint32_t>(sql_val.AsLong());
-
-  // OrderedIndices are monotonic non contiguous values if OrderedIndexSearch
-  // was called. Look for the first and last index and find the result of
-  // looking for this range in IdStorage.
-  Range indices_range(indices.data[0], indices.data[indices.size - 1] + 1);
-  Range bin_search_ret = BinarySearchIntrinsic(op, val, indices_range);
-
-  const auto* start_ptr = std::lower_bound(
-      indices.data, indices.data + indices.size, bin_search_ret.start);
-  const auto* end_ptr = std::lower_bound(start_ptr, indices.data + indices.size,
-                                         bin_search_ret.end);
-  return {static_cast<uint32_t>(std::distance(indices.data, start_ptr)),
-          static_cast<uint32_t>(std::distance(indices.data, end_ptr))};
-}
-
 Range IdStorage::ChainImpl::BinarySearchIntrinsic(FilterOp op,
                                                   Id val,
                                                   Range range) {
@@ -363,6 +322,10 @@
   return *tok;
 }
 
+SqlValue IdStorage::ChainImpl::Get_AvoidUsingBecauseSlow(uint32_t index) const {
+  return SqlValue::Long(index);
+}
+
 void IdStorage::ChainImpl::Serialize(StorageProto* storage) const {
   storage->set_id_storage();
 }
diff --git a/src/trace_processor/db/column/id_storage.h b/src/trace_processor/db/column/id_storage.h
index 420568d..c3aeadf 100644
--- a/src/trace_processor/db/column/id_storage.h
+++ b/src/trace_processor/db/column/id_storage.h
@@ -19,6 +19,7 @@
 #include <cstdint>
 #include <limits>
 #include <memory>
+#include <optional>
 #include <string>
 
 #include "perfetto/trace_processor/basic_types.h"
@@ -55,10 +56,6 @@
 
     void IndexSearchValidated(FilterOp, SqlValue, Indices&) const override;
 
-    Range OrderedIndexSearchValidated(FilterOp,
-                                      SqlValue,
-                                      const OrderedIndices&) const override;
-
     void StableSort(SortToken* start,
                     SortToken* end,
                     SortDirection) const override;
@@ -69,6 +66,8 @@
 
     std::optional<Token> MinElement(Indices&) const override;
 
+    SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const override;
+
     void Serialize(StorageProto*) const override;
 
     uint32_t size() const override {
diff --git a/src/trace_processor/db/column/null_overlay.cc b/src/trace_processor/db/column/null_overlay.cc
index 128d3ff..372d3b0 100644
--- a/src/trace_processor/db/column/null_overlay.cc
+++ b/src/trace_processor/db/column/null_overlay.cc
@@ -256,56 +256,6 @@
   inner_->IndexSearchValidated(op, sql_val, indices);
 }
 
-Range NullOverlay::ChainImpl::OrderedIndexSearchValidated(
-    FilterOp op,
-    SqlValue sql_val,
-    const OrderedIndices& indices) const {
-  // For NOT EQUAL the translation or results from EQUAL needs to be done by the
-  // caller.
-  PERFETTO_CHECK(op != FilterOp::kNe);
-
-  PERFETTO_TP_TRACE(metatrace::Category::DB,
-                    "NullOverlay::ChainImpl::OrderedIndexSearch");
-
-  // We assume all NULLs are ordered to be in the front. We are looking for the
-  // first index that points to non NULL value.
-  const uint32_t* first_non_null =
-      std::partition_point(indices.data, indices.data + indices.size,
-                           [this](uint32_t i) { return !non_null_->IsSet(i); });
-  auto non_null_offset =
-      static_cast<uint32_t>(std::distance(indices.data, first_non_null));
-  auto non_null_size = static_cast<uint32_t>(
-      std::distance(first_non_null, indices.data + indices.size));
-
-  if (op == FilterOp::kIsNull) {
-    return {0, non_null_offset};
-  }
-
-  if (op == FilterOp::kIsNotNull) {
-    switch (inner_->ValidateSearchConstraints(op, sql_val)) {
-      case SearchValidationResult::kNoData:
-        return {};
-      case SearchValidationResult::kAllData:
-        return {non_null_offset, indices.size};
-      case SearchValidationResult::kOk:
-        break;
-    }
-  }
-
-  std::vector<uint32_t> storage_iv;
-  storage_iv.reserve(non_null_size);
-  for (const uint32_t* it = first_non_null;
-       it != first_non_null + non_null_size; it++) {
-    storage_iv.push_back(non_null_->CountSetBits(*it));
-  }
-
-  Range inner_range = inner_->OrderedIndexSearchValidated(
-      op, sql_val,
-      OrderedIndices{storage_iv.data(), non_null_size, indices.state});
-  return {inner_range.start + non_null_offset,
-          inner_range.end + non_null_offset};
-}
-
 void NullOverlay::ChainImpl::StableSort(SortToken* start,
                                         SortToken* end,
                                         SortDirection direction) const {
@@ -369,6 +319,13 @@
   return inner_->MinElement(indices);
 }
 
+SqlValue NullOverlay::ChainImpl::Get_AvoidUsingBecauseSlow(
+    uint32_t index) const {
+  return non_null_->IsSet(index)
+             ? inner_->Get_AvoidUsingBecauseSlow(non_null_->CountSetBits(index))
+             : SqlValue();
+}
+
 void NullOverlay::ChainImpl::Serialize(StorageProto* storage) const {
   auto* null_storage = storage->set_null_overlay();
   non_null_->Serialize(null_storage->set_bit_vector());
diff --git a/src/trace_processor/db/column/null_overlay.h b/src/trace_processor/db/column/null_overlay.h
index 187c056..a74f211 100644
--- a/src/trace_processor/db/column/null_overlay.h
+++ b/src/trace_processor/db/column/null_overlay.h
@@ -19,6 +19,7 @@
 
 #include <cstdint>
 #include <memory>
+#include <optional>
 #include <string>
 
 #include "perfetto/trace_processor/basic_types.h"
@@ -55,10 +56,6 @@
 
     void IndexSearchValidated(FilterOp, SqlValue, Indices&) const override;
 
-    Range OrderedIndexSearchValidated(FilterOp,
-                                      SqlValue,
-                                      const OrderedIndices&) const override;
-
     void StableSort(SortToken* start,
                     SortToken* end,
                     SortDirection) const override;
@@ -69,6 +66,8 @@
 
     std::optional<Token> MinElement(Indices&) const override;
 
+    SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const override;
+
     void Serialize(StorageProto*) const override;
 
     uint32_t size() const override { return non_null_->size(); }
diff --git a/src/trace_processor/db/column/null_overlay_unittest.cc b/src/trace_processor/db/column/null_overlay_unittest.cc
index 981c7bd..9cff095 100644
--- a/src/trace_processor/db/column/null_overlay_unittest.cc
+++ b/src/trace_processor/db/column/null_overlay_unittest.cc
@@ -200,15 +200,15 @@
 }
 
 TEST(NullOverlay, OrderedIndexSearch) {
+  std::vector<uint32_t> numeric_data{1, 0, 1, 0};
+  NumericStorage<uint32_t> numeric(&numeric_data, ColumnType::kUint32, false);
+
   BitVector bv{0, 1, 1, 1, 0, 1};
-  // Passing values in final storage (on normal operations)
-  // 0, 1, 0, 1, 0, 0
-  auto fake = FakeStorageChain::SearchSubset(4, BitVector{1, 0, 1, 0});
   NullOverlay storage(&bv);
-  auto chain = storage.MakeChain(std::move(fake));
+  auto chain = storage.MakeChain(numeric.MakeChain());
 
   // Passing values on final data
-  // NULL, NULL, 0, 1, 1
+  // NULL, NULL, 0, 0, 1, 1
   std::vector<uint32_t> table_idx{0, 4, 5, 1, 3};
   OrderedIndices indices{table_idx.data(), uint32_t(table_idx.size()),
                          Indices::State::kNonmonotonic};
@@ -218,28 +218,28 @@
   ASSERT_EQ(res.end, 2u);
 
   res = chain->OrderedIndexSearch(FilterOp::kIsNotNull, SqlValue(), indices);
+  ASSERT_EQ(res.start, 2u);
+  ASSERT_EQ(res.end, 5u);
+
+  res = chain->OrderedIndexSearch(FilterOp::kEq, SqlValue::Long(1), indices);
   ASSERT_EQ(res.start, 3u);
   ASSERT_EQ(res.end, 5u);
 
-  res = chain->OrderedIndexSearch(FilterOp::kEq, SqlValue::Long(3), indices);
+  res = chain->OrderedIndexSearch(FilterOp::kGt, SqlValue::Long(0), indices);
   ASSERT_EQ(res.start, 3u);
   ASSERT_EQ(res.end, 5u);
 
-  res = chain->OrderedIndexSearch(FilterOp::kGt, SqlValue::Long(3), indices);
+  res = chain->OrderedIndexSearch(FilterOp::kGe, SqlValue::Long(1), indices);
   ASSERT_EQ(res.start, 3u);
   ASSERT_EQ(res.end, 5u);
 
-  res = chain->OrderedIndexSearch(FilterOp::kGe, SqlValue::Long(3), indices);
-  ASSERT_EQ(res.start, 3u);
-  ASSERT_EQ(res.end, 5u);
+  res = chain->OrderedIndexSearch(FilterOp::kLt, SqlValue::Long(1), indices);
+  ASSERT_EQ(res.start, 0u);
+  ASSERT_EQ(res.end, 3u);
 
-  res = chain->OrderedIndexSearch(FilterOp::kLt, SqlValue::Long(3), indices);
-  ASSERT_EQ(res.start, 3u);
-  ASSERT_EQ(res.end, 5u);
-
-  res = chain->OrderedIndexSearch(FilterOp::kLe, SqlValue::Long(3), indices);
-  ASSERT_EQ(res.start, 3u);
-  ASSERT_EQ(res.end, 5u);
+  res = chain->OrderedIndexSearch(FilterOp::kLe, SqlValue::Long(0), indices);
+  ASSERT_EQ(res.start, 0u);
+  ASSERT_EQ(res.end, 3u);
 }
 
 TEST(NullOverlay, StableSort) {
diff --git a/src/trace_processor/db/column/numeric_storage.cc b/src/trace_processor/db/column/numeric_storage.cc
index b39b4cb..3612356 100644
--- a/src/trace_processor/db/column/numeric_storage.cc
+++ b/src/trace_processor/db/column/numeric_storage.cc
@@ -138,60 +138,6 @@
 }
 
 template <typename T>
-uint32_t TypedLowerBoundExtrinsic(T val,
-                                  const T* data,
-                                  OrderedIndices indices) {
-  const auto* lower = std::lower_bound(
-      indices.data, indices.data + indices.size, val,
-      [data](uint32_t index, T value) { return data[index] < value; });
-  return static_cast<uint32_t>(std::distance(indices.data, lower));
-}
-
-uint32_t LowerBoundExtrinsic(const void* vector_ptr,
-                             NumericValue val,
-                             OrderedIndices indices) {
-  if (const auto* u32 = std::get_if<uint32_t>(&val)) {
-    const auto* start =
-        static_cast<const std::vector<uint32_t>*>(vector_ptr)->data();
-    return TypedLowerBoundExtrinsic(*u32, start, indices);
-  }
-  if (const auto* i64 = std::get_if<int64_t>(&val)) {
-    const auto* start =
-        static_cast<const std::vector<int64_t>*>(vector_ptr)->data();
-    return TypedLowerBoundExtrinsic(*i64, start, indices);
-  }
-  if (const auto* i32 = std::get_if<int32_t>(&val)) {
-    const auto* start =
-        static_cast<const std::vector<int32_t>*>(vector_ptr)->data();
-    return TypedLowerBoundExtrinsic(*i32, start, indices);
-  }
-  if (const auto* db = std::get_if<double>(&val)) {
-    const auto* start =
-        static_cast<const std::vector<double>*>(vector_ptr)->data();
-    return TypedLowerBoundExtrinsic(*db, start, indices);
-  }
-  PERFETTO_FATAL("Type not handled");
-}
-
-uint32_t UpperBoundExtrinsic(const void* vector_ptr,
-                             NumericValue val,
-                             OrderedIndices indices) {
-  return std::visit(
-      [vector_ptr, indices](auto val_data) {
-        using T = decltype(val_data);
-        const T* typed_start =
-            static_cast<const std::vector<T>*>(vector_ptr)->data();
-        const auto* upper =
-            std::upper_bound(indices.data, indices.data + indices.size,
-                             val_data, [typed_start](T value, uint32_t index) {
-                               return value < typed_start[index];
-                             });
-        return static_cast<uint32_t>(std::distance(indices.data, upper));
-      },
-      val);
-}
-
-template <typename T>
 void TypedLinearSearch(T typed_val,
                        const T* start,
                        FilterOp op,
@@ -504,79 +450,6 @@
       val);
 }
 
-Range NumericStorageBase::ChainImpl::OrderedIndexSearchValidated(
-    FilterOp op,
-    SqlValue sql_val,
-    const OrderedIndices& indices) const {
-  PERFETTO_TP_TRACE(
-      metatrace::Category::DB, "NumericStorage::ChainImpl::OrderedIndexSearch",
-      [indices, op](metatrace::Record* r) {
-        r->AddArg("Count", std::to_string(indices.size));
-        r->AddArg("Op", std::to_string(static_cast<uint32_t>(op)));
-      });
-
-  // Mismatched types - value is double and column is int.
-  if (sql_val.type == SqlValue::kDouble &&
-      storage_type_ != ColumnType::kDouble) {
-    if (auto ret = utils::CanReturnEarly(IntColumnWithDouble(op, &sql_val),
-                                         indices.size);
-        ret) {
-      return *ret;
-    }
-  }
-
-  // Mismatched types - column is double and value is int.
-  if (sql_val.type != SqlValue::kDouble &&
-      storage_type_ == ColumnType::kDouble) {
-    if (auto ret = utils::CanReturnEarly(DoubleColumnWithInt(op, &sql_val),
-                                         indices.size);
-        ret) {
-      return *ret;
-    }
-  }
-
-  NumericValue val;
-  switch (storage_type_) {
-    case ColumnType::kDouble:
-      val = sql_val.AsDouble();
-      break;
-    case ColumnType::kInt64:
-      val = sql_val.AsLong();
-      break;
-    case ColumnType::kInt32:
-      val = static_cast<int32_t>(sql_val.AsLong());
-      break;
-    case ColumnType::kUint32:
-      val = static_cast<uint32_t>(sql_val.AsLong());
-      break;
-    case ColumnType::kString:
-    case ColumnType::kDummy:
-    case ColumnType::kId:
-      PERFETTO_FATAL("Invalid type");
-  }
-
-  switch (op) {
-    case FilterOp::kEq:
-      return {LowerBoundExtrinsic(vector_ptr_, val, indices),
-              UpperBoundExtrinsic(vector_ptr_, val, indices)};
-    case FilterOp::kLe:
-      return {0, UpperBoundExtrinsic(vector_ptr_, val, indices)};
-    case FilterOp::kLt:
-      return {0, LowerBoundExtrinsic(vector_ptr_, val, indices)};
-    case FilterOp::kGe:
-      return {LowerBoundExtrinsic(vector_ptr_, val, indices), indices.size};
-    case FilterOp::kGt:
-      return {UpperBoundExtrinsic(vector_ptr_, val, indices), indices.size};
-    case FilterOp::kNe:
-    case FilterOp::kIsNull:
-    case FilterOp::kIsNotNull:
-    case FilterOp::kGlob:
-    case FilterOp::kRegex:
-      PERFETTO_FATAL("Wrong filtering operation");
-  }
-  PERFETTO_FATAL("For GCC");
-}
-
 BitVector NumericStorageBase::ChainImpl::LinearSearchInternal(
     FilterOp op,
     NumericValue val,
diff --git a/src/trace_processor/db/column/numeric_storage.h b/src/trace_processor/db/column/numeric_storage.h
index fd27d26..3c4416b 100644
--- a/src/trace_processor/db/column/numeric_storage.h
+++ b/src/trace_processor/db/column/numeric_storage.h
@@ -23,6 +23,7 @@
 #include <optional>
 #include <set>
 #include <string>
+#include <type_traits>
 #include <unordered_set>
 #include <variant>
 #include <vector>
@@ -49,10 +50,6 @@
 
     void IndexSearchValidated(FilterOp, SqlValue, Indices&) const override;
 
-    Range OrderedIndexSearchValidated(FilterOp,
-                                      SqlValue,
-                                      const OrderedIndices&) const override;
-
     void Serialize(StorageProto*) const override;
 
     std::string DebugString() const override { return "NumericStorage"; }
@@ -145,6 +142,13 @@
       return *tok;
     }
 
+    SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const override {
+      if constexpr (std::is_same_v<T, double>) {
+        return SqlValue::Double((*vector_)[index]);
+      }
+      return SqlValue::Long((*vector_)[index]);
+    }
+
     void StableSort(SortToken* start,
                     SortToken* end,
                     SortDirection direction) const override {
diff --git a/src/trace_processor/db/column/range_overlay.cc b/src/trace_processor/db/column/range_overlay.cc
index 81bb7ae..dee7838 100644
--- a/src/trace_processor/db/column/range_overlay.cc
+++ b/src/trace_processor/db/column/range_overlay.cc
@@ -18,6 +18,7 @@
 
 #include <cstdint>
 #include <memory>
+#include <optional>
 #include <utility>
 #include <vector>
 
@@ -118,22 +119,6 @@
   inner_->IndexSearchValidated(op, sql_val, indices);
 }
 
-Range RangeOverlay::ChainImpl::OrderedIndexSearchValidated(
-    FilterOp op,
-    SqlValue sql_val,
-    const OrderedIndices& indices) const {
-  PERFETTO_TP_TRACE(metatrace::Category::DB, "RangeOverlay::IndexSearch");
-
-  // Should be SIMD optimized.
-  std::vector<uint32_t> storage_iv(indices.size);
-  for (uint32_t i = 0; i < indices.size; ++i) {
-    storage_iv[i] = indices.data[i] + range_->start;
-  }
-  return inner_->OrderedIndexSearchValidated(
-      op, sql_val,
-      OrderedIndices{storage_iv.data(), indices.size, indices.state});
-}
-
 void RangeOverlay::ChainImpl::StableSort(SortToken* start,
                                          SortToken* end,
                                          SortDirection direction) const {
@@ -160,6 +145,11 @@
   return inner_->MaxElement(indices);
 }
 
+SqlValue RangeOverlay::ChainImpl::Get_AvoidUsingBecauseSlow(
+    uint32_t index) const {
+  return inner_->Get_AvoidUsingBecauseSlow(index + range_->start);
+}
+
 std::optional<Token> RangeOverlay::ChainImpl::MinElement(
     Indices& indices) const {
   PERFETTO_TP_TRACE(metatrace::Category::DB, "RangeOverlay::MinElement");
diff --git a/src/trace_processor/db/column/range_overlay.h b/src/trace_processor/db/column/range_overlay.h
index f15e8dc..a58df40 100644
--- a/src/trace_processor/db/column/range_overlay.h
+++ b/src/trace_processor/db/column/range_overlay.h
@@ -19,6 +19,7 @@
 
 #include <cstdint>
 #include <memory>
+#include <optional>
 #include <string>
 
 #include "perfetto/trace_processor/basic_types.h"
@@ -52,10 +53,6 @@
 
     void IndexSearchValidated(FilterOp p, SqlValue, Indices&) const override;
 
-    Range OrderedIndexSearchValidated(FilterOp,
-                                      SqlValue,
-                                      const OrderedIndices&) const override;
-
     void StableSort(SortToken* start,
                     SortToken* end,
                     SortDirection) const override;
@@ -66,6 +63,8 @@
 
     std::optional<Token> MinElement(Indices&) const override;
 
+    SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const override;
+
     void Serialize(StorageProto*) const override;
 
     uint32_t size() const override { return range_->size(); }
diff --git a/src/trace_processor/db/column/selector_overlay.cc b/src/trace_processor/db/column/selector_overlay.cc
index b2cbc43..9e957cb 100644
--- a/src/trace_processor/db/column/selector_overlay.cc
+++ b/src/trace_processor/db/column/selector_overlay.cc
@@ -16,9 +16,9 @@
 
 #include "src/trace_processor/db/column/selector_overlay.h"
 
-#include <algorithm>
 #include <cstdint>
 #include <memory>
+#include <optional>
 #include <utility>
 #include <vector>
 
@@ -35,7 +35,7 @@
 namespace perfetto::trace_processor::column {
 namespace {
 
-static constexpr uint32_t kIndexOfNthSetRatio = 32;
+constexpr uint32_t kIndexOfNthSetRatio = 32;
 
 }  // namespace
 
@@ -97,23 +97,6 @@
   return inner_->IndexSearchValidated(op, sql_val, indices);
 }
 
-Range SelectorOverlay::ChainImpl::OrderedIndexSearchValidated(
-    FilterOp op,
-    SqlValue sql_val,
-    const OrderedIndices& indices) const {
-  // To go from TableIndexVector to StorageIndexVector we need to find index in
-  // |selector_| by looking only into set bits.
-  std::vector<uint32_t> inner_indices(indices.size);
-  for (uint32_t i = 0; i < indices.size; ++i) {
-    inner_indices[i] = selector_->IndexOfNthSet(indices.data[i]);
-  }
-  return inner_->OrderedIndexSearchValidated(
-      op, sql_val,
-      OrderedIndices{inner_indices.data(),
-                     static_cast<uint32_t>(inner_indices.size()),
-                     indices.state});
-}
-
 void SelectorOverlay::ChainImpl::StableSort(SortToken* start,
                                             SortToken* end,
                                             SortDirection direction) const {
@@ -148,6 +131,11 @@
   return inner_->MinElement(indices);
 }
 
+SqlValue SelectorOverlay::ChainImpl::Get_AvoidUsingBecauseSlow(
+    uint32_t index) const {
+  return inner_->Get_AvoidUsingBecauseSlow(selector_->IndexOfNthSet(index));
+}
+
 void SelectorOverlay::ChainImpl::Serialize(StorageProto* storage) const {
   auto* selector_overlay = storage->set_selector_overlay();
   inner_->Serialize(selector_overlay->set_storage());
@@ -164,15 +152,15 @@
     for (auto& token : indices.tokens) {
       token.index = selector_->IndexOfNthSet(token.index);
     }
-  } else {
-    // TODO(mayzner): once we have a reverse index for IndexOfNthSet in
-    // BitVector, this should no longer be necessary.
-    std::vector<uint32_t> lookup = selector_->GetSetBitIndices();
-    for (auto& token : indices.tokens) {
-      token.index = lookup[token.index];
-    }
+    return;
   }
-  return;
+
+  // TODO(mayzner): once we have a reverse index for IndexOfNthSet in
+  // BitVector, this should no longer be necessary.
+  std::vector<uint32_t> lookup = selector_->GetSetBitIndices();
+  for (auto& token : indices.tokens) {
+    token.index = lookup[token.index];
+  }
 }
 
 }  // namespace perfetto::trace_processor::column
diff --git a/src/trace_processor/db/column/selector_overlay.h b/src/trace_processor/db/column/selector_overlay.h
index d54bdf5..3726f9f 100644
--- a/src/trace_processor/db/column/selector_overlay.h
+++ b/src/trace_processor/db/column/selector_overlay.h
@@ -19,6 +19,7 @@
 
 #include <cstdint>
 #include <memory>
+#include <optional>
 #include <string>
 
 #include "perfetto/trace_processor/basic_types.h"
@@ -56,10 +57,6 @@
 
     void IndexSearchValidated(FilterOp p, SqlValue, Indices&) const override;
 
-    Range OrderedIndexSearchValidated(FilterOp,
-                                      SqlValue,
-                                      const OrderedIndices&) const override;
-
     void StableSort(SortToken* start,
                     SortToken* end,
                     SortDirection) const override;
@@ -70,6 +67,8 @@
 
     std::optional<Token> MinElement(Indices&) const override;
 
+    SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const override;
+
     void Serialize(StorageProto*) const override;
 
     uint32_t size() const override { return selector_->size(); }
diff --git a/src/trace_processor/db/column/selector_overlay_unittest.cc b/src/trace_processor/db/column/selector_overlay_unittest.cc
index 0abae4c..def2a77 100644
--- a/src/trace_processor/db/column/selector_overlay_unittest.cc
+++ b/src/trace_processor/db/column/selector_overlay_unittest.cc
@@ -17,6 +17,7 @@
 #include "src/trace_processor/db/column/selector_overlay.h"
 
 #include <cstdint>
+#include <utility>
 #include <vector>
 
 #include "data_layer.h"
@@ -103,35 +104,23 @@
   ASSERT_THAT(utils::ExtractPayloadForTesting(indices), ElementsAre(1u));
 }
 
-TEST(SelectorOverlay, OrderedIndexSearchTrivial) {
+TEST(SelectorOverlay, OrderedIndexSearch) {
+  std::vector<uint32_t> numeric_data{1, 0, 0, 1, 1};
+  NumericStorage<uint32_t> numeric(&numeric_data, ColumnType::kUint32, false);
+
   BitVector selector{1, 0, 1, 0, 1};
-  auto fake = FakeStorageChain::SearchAll(5);
   SelectorOverlay storage(&selector);
-  auto chain = storage.MakeChain(std::move(fake));
+  auto chain = storage.MakeChain(numeric.MakeChain());
 
   std::vector<uint32_t> table_idx{1u, 0u, 2u};
   Range res = chain->OrderedIndexSearch(
-      FilterOp::kGe, SqlValue::Long(0u),
+      FilterOp::kGe, SqlValue::Long(1u),
       OrderedIndices{table_idx.data(), static_cast<uint32_t>(table_idx.size()),
                      Indices::State::kNonmonotonic});
-  ASSERT_EQ(res.start, 0u);
+  ASSERT_EQ(res.start, 1u);
   ASSERT_EQ(res.end, 3u);
 }
 
-TEST(SelectorOverlay, OrderedIndexSearchNone) {
-  BitVector selector{1, 0, 1, 0, 1};
-  auto fake = FakeStorageChain::SearchNone(5);
-  SelectorOverlay storage(&selector);
-  auto chain = storage.MakeChain(std::move(fake));
-
-  std::vector<uint32_t> table_idx{1u, 0u, 2u};
-  Range res = chain->OrderedIndexSearch(
-      FilterOp::kGe, SqlValue::Long(0u),
-      OrderedIndices{table_idx.data(), static_cast<uint32_t>(table_idx.size()),
-                     Indices::State::kNonmonotonic});
-  ASSERT_EQ(res.size(), 0u);
-}
-
 TEST(SelectorOverlay, StableSort) {
   std::vector<uint32_t> numeric_data{3, 1, 0, 0, 2, 4, 3, 4};
   NumericStorage<uint32_t> numeric(&numeric_data, ColumnType::kUint32, false);
diff --git a/src/trace_processor/db/column/set_id_storage.cc b/src/trace_processor/db/column/set_id_storage.cc
index 207b649..42ab4ea 100644
--- a/src/trace_processor/db/column/set_id_storage.cc
+++ b/src/trace_processor/db/column/set_id_storage.cc
@@ -19,10 +19,7 @@
 #include <algorithm>
 #include <cstdint>
 #include <functional>
-#include <iterator>
-#include <limits>
-#include <memory>
-#include <set>
+#include <optional>
 #include <string>
 #include <unordered_set>
 #include <utility>
@@ -250,38 +247,17 @@
   }
 }
 
-Range SetIdStorage::ChainImpl::OrderedIndexSearchValidated(
-    FilterOp op,
-    SqlValue sql_val,
-    const OrderedIndices& indices) const {
-  PERFETTO_TP_TRACE(metatrace::Category::DB,
-                    "SetIdStorage::ChainImpl::OrderedIndexSearch");
-  // OrderedIndices are monotonic non-contiguous values.
-  auto res = SearchValidated(
-      op, sql_val, Range(indices.data[0], indices.data[indices.size - 1] + 1));
-  PERFETTO_CHECK(res.IsRange());
-  Range res_range = std::move(res).TakeIfRange();
-
-  const auto* start_ptr = std::lower_bound(
-      indices.data, indices.data + indices.size, res_range.start);
-  const auto* end_ptr =
-      std::lower_bound(start_ptr, indices.data + indices.size, res_range.end);
-
-  return {static_cast<uint32_t>(std::distance(indices.data, start_ptr)),
-          static_cast<uint32_t>(std::distance(indices.data, end_ptr))};
-}
-
 Range SetIdStorage::ChainImpl::BinarySearchIntrinsic(FilterOp op,
                                                      SetId val,
                                                      Range range) const {
   switch (op) {
     case FilterOp::kEq: {
-      if (values_->data()[val] != val) {
-        return Range();
+      if ((*values_)[val] != val) {
+        return {};
       }
       uint32_t start = std::max(val, range.start);
       uint32_t end = UpperBoundIntrinsic(values_->data(), val, range);
-      return Range(std::min(start, end), end);
+      return {std::min(start, end), end};
     }
     case FilterOp::kLe: {
       return {range.start, UpperBoundIntrinsic(values_->data(), val, range)};
@@ -370,6 +346,11 @@
   return *tok;
 }
 
+SqlValue SetIdStorage::ChainImpl::Get_AvoidUsingBecauseSlow(
+    uint32_t index) const {
+  return SqlValue::Long((*values_)[index]);
+}
+
 void SetIdStorage::ChainImpl::Serialize(StorageProto* msg) const {
   auto* vec_msg = msg->set_set_id_storage();
   vec_msg->set_values(reinterpret_cast<const uint8_t*>(values_->data()),
diff --git a/src/trace_processor/db/column/set_id_storage.h b/src/trace_processor/db/column/set_id_storage.h
index 2fa6c81..e459106 100644
--- a/src/trace_processor/db/column/set_id_storage.h
+++ b/src/trace_processor/db/column/set_id_storage.h
@@ -18,6 +18,7 @@
 
 #include <cstdint>
 #include <memory>
+#include <optional>
 #include <string>
 #include <vector>
 
@@ -54,10 +55,6 @@
 
     void IndexSearchValidated(FilterOp, SqlValue, Indices&) const override;
 
-    Range OrderedIndexSearchValidated(FilterOp,
-                                      SqlValue,
-                                      const OrderedIndices&) const override;
-
     void StableSort(SortToken* start,
                     SortToken* end,
                     SortDirection direction) const override;
@@ -70,6 +67,8 @@
 
     std::optional<Token> MinElement(Indices&) const override;
 
+    SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const override;
+
     uint32_t size() const override {
       return static_cast<uint32_t>(values_->size());
     }
diff --git a/src/trace_processor/db/column/string_storage.cc b/src/trace_processor/db/column/string_storage.cc
index adf3eda..1d25dbb 100644
--- a/src/trace_processor/db/column/string_storage.cc
+++ b/src/trace_processor/db/column/string_storage.cc
@@ -163,36 +163,6 @@
   return static_cast<uint32_t>(std::distance(data, upper));
 }
 
-uint32_t LowerBoundExtrinsic(StringPool* pool,
-                             const StringPool::Id* data,
-                             NullTermStringView val,
-                             const uint32_t* indices,
-                             uint32_t indices_count,
-                             uint32_t offset) {
-  Less comp{pool};
-  const auto* lower =
-      std::lower_bound(indices + offset, indices + indices_count, val,
-                       [comp, data](uint32_t index, NullTermStringView val) {
-                         return comp(data[index], val);
-                       });
-  return static_cast<uint32_t>(std::distance(indices, lower));
-}
-
-uint32_t UpperBoundExtrinsic(StringPool* pool,
-                             const StringPool::Id* data,
-                             NullTermStringView val,
-                             const uint32_t* indices,
-                             uint32_t indices_count,
-                             uint32_t offset) {
-  Greater comp{pool};
-  const auto* upper =
-      std::upper_bound(indices + offset, indices + indices_count, val,
-                       [comp, data](NullTermStringView val, uint32_t index) {
-                         return comp(data[index], val);
-                       });
-  return static_cast<uint32_t>(std::distance(indices, upper));
-}
-
 }  // namespace
 
 StringStorage::ChainImpl::ChainImpl(StringPool* string_pool,
@@ -518,64 +488,6 @@
   return std::move(builder).Build();
 }
 
-Range StringStorage::ChainImpl::OrderedIndexSearchValidated(
-    FilterOp op,
-    SqlValue sql_val,
-    const OrderedIndices& indices) const {
-  PERFETTO_TP_TRACE(metatrace::Category::DB,
-                    "StringStorage::ChainImpl::OrderedIndexSearch");
-  StringPool::Id val =
-      (op == FilterOp::kIsNull || op == FilterOp::kIsNotNull)
-          ? StringPool::Id::Null()
-          : string_pool_->InternString(base::StringView(sql_val.AsString()));
-  NullTermStringView val_str = string_pool_->Get(val);
-
-  auto first_non_null = static_cast<uint32_t>(std::distance(
-      indices.data,
-      std::partition_point(indices.data, indices.data + indices.size,
-                           [this](uint32_t i) {
-                             return (*data_)[i] == StringPool::Id::Null();
-                           })));
-
-  switch (op) {
-    case FilterOp::kEq:
-      return {LowerBoundExtrinsic(string_pool_, data_->data(), val_str,
-                                  indices.data, indices.size, first_non_null),
-              UpperBoundExtrinsic(string_pool_, data_->data(), val_str,
-                                  indices.data, indices.size, first_non_null)};
-    case FilterOp::kLe:
-      return {first_non_null,
-              UpperBoundExtrinsic(string_pool_, data_->data(), val_str,
-                                  indices.data, indices.size, first_non_null)};
-    case FilterOp::kLt:
-      return {first_non_null,
-              LowerBoundExtrinsic(string_pool_, data_->data(), val_str,
-                                  indices.data, indices.size, first_non_null)};
-    case FilterOp::kGe:
-      return {LowerBoundExtrinsic(string_pool_, data_->data(), val_str,
-                                  indices.data, indices.size, first_non_null),
-              indices.size};
-    case FilterOp::kGt:
-      return {UpperBoundExtrinsic(string_pool_, data_->data(), val_str,
-                                  indices.data, indices.size, first_non_null),
-              indices.size};
-    case FilterOp::kIsNull: {
-      // Assuming nulls are at the front.
-      return Range(0, first_non_null);
-    }
-    case FilterOp::kIsNotNull: {
-      // Assuming nulls are at the front.
-      return Range(first_non_null, indices.size);
-    }
-
-    case FilterOp::kNe:
-    case FilterOp::kGlob:
-    case FilterOp::kRegex:
-      PERFETTO_FATAL("Not supported for OrderedIndexSearch");
-  }
-  PERFETTO_FATAL("For GCC");
-}
-
 Range StringStorage::ChainImpl::BinarySearchIntrinsic(
     FilterOp op,
     SqlValue sql_val,
@@ -715,6 +627,14 @@
   return *tok;
 }
 
+SqlValue StringStorage::ChainImpl::Get_AvoidUsingBecauseSlow(
+    uint32_t index) const {
+  StringPool::Id id = (*data_)[index];
+  return id == StringPool::Id::Null()
+             ? SqlValue()
+             : SqlValue::String(string_pool_->Get(id).c_str());
+}
+
 void StringStorage::ChainImpl::Serialize(StorageProto* msg) const {
   auto* string_storage_msg = msg->set_string_storage();
   string_storage_msg->set_is_sorted(is_sorted_);
diff --git a/src/trace_processor/db/column/string_storage.h b/src/trace_processor/db/column/string_storage.h
index 470ccd1..1c05dd7 100644
--- a/src/trace_processor/db/column/string_storage.h
+++ b/src/trace_processor/db/column/string_storage.h
@@ -18,6 +18,7 @@
 
 #include <cstdint>
 #include <memory>
+#include <optional>
 #include <string>
 #include <vector>
 
@@ -57,10 +58,6 @@
 
     void IndexSearchValidated(FilterOp, SqlValue, Indices&) const override;
 
-    Range OrderedIndexSearchValidated(FilterOp,
-                                      SqlValue,
-                                      const OrderedIndices&) const override;
-
     void StableSort(SortToken* start,
                     SortToken* end,
                     SortDirection direction) const override;
@@ -71,6 +68,8 @@
 
     std::optional<Token> MinElement(Indices&) const override;
 
+    SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const override;
+
     void Serialize(StorageProto*) const override;
 
     uint32_t size() const override {
diff --git a/src/trace_processor/db/column/string_storage_unittest.cc b/src/trace_processor/db/column/string_storage_unittest.cc
index 4474ea9..7e7cadb 100644
--- a/src/trace_processor/db/column/string_storage_unittest.cc
+++ b/src/trace_processor/db/column/string_storage_unittest.cc
@@ -383,12 +383,12 @@
 
   op = FilterOp::kLt;
   res = chain->OrderedIndexSearch(op, val, indices);
-  ASSERT_EQ(res.start, 1u);
+  ASSERT_EQ(res.start, 0u);
   ASSERT_EQ(res.end, 4u);
 
   op = FilterOp::kLe;
   res = chain->OrderedIndexSearch(op, val, indices);
-  ASSERT_EQ(res.start, 1u);
+  ASSERT_EQ(res.start, 0u);
   ASSERT_EQ(res.end, 5u);
 
   op = FilterOp::kGt;
@@ -402,6 +402,7 @@
   ASSERT_EQ(res.end, 6u);
 
   op = FilterOp::kIsNull;
+  val = SqlValue();
   res = chain->OrderedIndexSearch(op, val, indices);
   ASSERT_EQ(res.start, 0u);
   ASSERT_EQ(res.end, 1u);
diff --git a/src/trace_processor/db/query_executor_benchmark.cc b/src/trace_processor/db/query_executor_benchmark.cc
index d6cc57f..dcd75f0 100644
--- a/src/trace_processor/db/query_executor_benchmark.cc
+++ b/src/trace_processor/db/query_executor_benchmark.cc
@@ -513,6 +513,29 @@
 }
 BENCHMARK(BM_QEFilterOrderedArrangement);
 
+void BM_QEFilterNullOrderedArrangement(benchmark::State& state) {
+  SliceTableForBenchmark table(state);
+  Order order{table.table_.parent_id().index_in_table(), false};
+  Table slice_sorted_with_parent_id = table.table_.Sort({order});
+
+  Constraint c{table.table_.parent_id().index_in_table(), FilterOp::kGt,
+               SqlValue::Long(26091)};
+  Query q;
+  q.constraints = {c};
+  for (auto _ : state) {
+    benchmark::DoNotOptimize(slice_sorted_with_parent_id.QueryToRowMap(q));
+  }
+  state.counters["s/row"] = benchmark::Counter(
+      static_cast<double>(slice_sorted_with_parent_id.row_count()),
+      benchmark::Counter::kIsIterationInvariantRate |
+          benchmark::Counter::kInvert);
+  state.counters["s/out"] = benchmark::Counter(
+      static_cast<double>(table.table_.QueryToRowMap(q).size()),
+      benchmark::Counter::kIsIterationInvariantRate |
+          benchmark::Counter::kInvert);
+}
+BENCHMARK(BM_QEFilterNullOrderedArrangement);
+
 void BM_QESliceFilterIndexSearchOneElement(benchmark::State& state) {
   SliceTableForBenchmark table(state);
   BenchmarkSliceTableFilter(
diff --git a/src/trace_processor/forwarding_trace_parser.cc b/src/trace_processor/forwarding_trace_parser.cc
index 369917d..a3b6a7f 100644
--- a/src/trace_processor/forwarding_trace_parser.cc
+++ b/src/trace_processor/forwarding_trace_parser.cc
@@ -29,6 +29,7 @@
 #include "src/trace_processor/trace_reader_registry.h"
 #include "src/trace_processor/types/trace_processor_context.h"
 #include "src/trace_processor/util/status_macros.h"
+#include "src/trace_processor/util/trace_type.h"
 
 namespace perfetto {
 namespace trace_processor {
@@ -53,7 +54,7 @@
     case kSystraceTraceType:
     case kGzipTraceType:
     case kCtraceTraceType:
-    case kAndroidBugreportTraceType:
+    case kZipFile:
       return std::nullopt;
 
     case kPerfDataTraceType:
@@ -102,26 +103,11 @@
   reader_ = std::move(*reader_or);
 
   PERFETTO_DLOG("%s detected", ToString(trace_type));
-  std::optional<TraceSorter::SortingMode> minimum_sorting_mode =
-      GetMinimumSortingMode(trace_type, *context_);
+  UpdateSorterForTraceType(trace_type);
 
-  if (minimum_sorting_mode.has_value()) {
-    if (!context_->sorter) {
-      context_->sorter.reset(new TraceSorter(context_, *minimum_sorting_mode));
-    }
-
-    switch (context_->sorter->sorting_mode()) {
-      case TraceSorter::SortingMode::kDefault:
-        PERFETTO_CHECK(minimum_sorting_mode ==
-                       TraceSorter::SortingMode::kDefault);
-        break;
-      case TraceSorter::SortingMode::kFullSort:
-        break;
-    }
-  }
-
-  // TODO(carlscab) Make sure kProtoTraceType and kSystraceTraceType are parsed
-  // first so that we do not get issues with SetPidZeroIsUpidZeroIdleProcess()
+  // TODO(b/334978369) Make sure kProtoTraceType and kSystraceTraceType are
+  // parsed first so that we do not get issues with
+  // SetPidZeroIsUpidZeroIdleProcess()
   if (trace_type == kProtoTraceType || trace_type == kSystraceTraceType) {
     context_->process_tracker->SetPidZeroIsUpidZeroIdleProcess();
   }
@@ -129,6 +115,27 @@
   return base::OkStatus();
 }
 
+void ForwardingTraceParser::UpdateSorterForTraceType(TraceType trace_type) {
+  std::optional<TraceSorter::SortingMode> minimum_sorting_mode =
+      GetMinimumSortingMode(trace_type, *context_);
+  if (!minimum_sorting_mode.has_value()) {
+    return;
+  }
+
+  if (!context_->sorter) {
+    context_->sorter.reset(new TraceSorter(context_, *minimum_sorting_mode));
+  }
+
+  switch (context_->sorter->sorting_mode()) {
+    case TraceSorter::SortingMode::kDefault:
+      PERFETTO_CHECK(minimum_sorting_mode ==
+                     TraceSorter::SortingMode::kDefault);
+      break;
+    case TraceSorter::SortingMode::kFullSort:
+      break;
+  }
+}
+
 base::Status ForwardingTraceParser::Parse(TraceBlobView blob) {
   // If this is the first Parse() call, guess the trace type and create the
   // appropriate parser.
diff --git a/src/trace_processor/forwarding_trace_parser.h b/src/trace_processor/forwarding_trace_parser.h
index 1f32938..12fe447 100644
--- a/src/trace_processor/forwarding_trace_parser.h
+++ b/src/trace_processor/forwarding_trace_parser.h
@@ -20,6 +20,7 @@
 #include "perfetto/base/status.h"
 #include "perfetto/trace_processor/trace_blob_view.h"
 #include "src/trace_processor/importers/common/chunked_trace_reader.h"
+#include "src/trace_processor/util/trace_type.h"
 
 namespace perfetto {
 namespace trace_processor {
@@ -37,6 +38,7 @@
 
  private:
   base::Status Init(const TraceBlobView&);
+  void UpdateSorterForTraceType(TraceType trace_type);
   TraceProcessorContext* const context_;
   std::unique_ptr<ChunkedTraceReader> reader_;
 };
diff --git a/src/trace_processor/importers/android_bugreport/android_bugreport_parser.cc b/src/trace_processor/importers/android_bugreport/android_bugreport_parser.cc
index 87aa0f5..1d46efc 100644
--- a/src/trace_processor/importers/android_bugreport/android_bugreport_parser.cc
+++ b/src/trace_processor/importers/android_bugreport/android_bugreport_parser.cc
@@ -17,54 +17,90 @@
 #include "src/trace_processor/importers/android_bugreport/android_bugreport_parser.h"
 
 #include <algorithm>
+#include <cstddef>
 #include <optional>
+#include <string>
+#include <vector>
 
 #include "perfetto/base/logging.h"
+#include "perfetto/base/status.h"
 #include "perfetto/ext/base/string_utils.h"
-#include "perfetto/trace_processor/trace_blob.h"
-#include "perfetto/trace_processor/trace_blob_view.h"
+#include "protos/perfetto/common/builtin_clock.pbzero.h"
 #include "src/trace_processor/importers/android_bugreport/android_log_parser.h"
 #include "src/trace_processor/importers/common/clock_tracker.h"
 #include "src/trace_processor/importers/common/process_tracker.h"
-#include "src/trace_processor/importers/common/track_tracker.h"
 #include "src/trace_processor/types/trace_processor_context.h"
 #include "src/trace_processor/util/zip_reader.h"
 
-#include "protos/perfetto/common/builtin_clock.pbzero.h"
-
 namespace perfetto {
 namespace trace_processor {
+namespace {
+const util::ZipFile* FindBugReportFile(
+    const std::vector<util::ZipFile>& zip_file_entries) {
+  for (const auto& zf : zip_file_entries) {
+    if (base::StartsWith(zf.name(), "bugreport-") &&
+        base::EndsWith(zf.name(), ".txt")) {
+      return &zf;
+    }
+  }
+  return nullptr;
+}
 
-AndroidBugreportParser::AndroidBugreportParser(TraceProcessorContext* ctx)
-    : context_(ctx), zip_reader_(new util::ZipReader()) {}
+std::optional<int32_t> ExtractYearFromBugReportFilename(
+    const std::string& filename) {
+  // Typical name: "bugreport-product-TP1A.220623.001-2022-06-24-16-24-37.txt".
+  auto year_str =
+      filename.substr(filename.size() - strlen("2022-12-31-23-59-00.txt"), 4);
+  return base::StringToInt32(year_str);
+}
+
+}  // namespace
+
+// static
+bool AndroidBugreportParser::IsAndroidBugReport(
+    const std::vector<util::ZipFile>& zip_file_entries) {
+  if (const util::ZipFile* file = FindBugReportFile(zip_file_entries);
+      file != nullptr) {
+    return ExtractYearFromBugReportFilename(file->name()).has_value();
+  }
+
+  return false;
+}
+
+// static
+util::Status AndroidBugreportParser::Parse(
+    TraceProcessorContext* context,
+    std::vector<util::ZipFile> zip_file_entries) {
+  return AndroidBugreportParser(context, std::move(zip_file_entries))
+      .ParseImpl();
+}
+
+AndroidBugreportParser::AndroidBugreportParser(
+    TraceProcessorContext* context,
+    std::vector<util::ZipFile> zip_file_entries)
+    : context_(context), zip_file_entries_(std::move(zip_file_entries)) {}
 
 AndroidBugreportParser::~AndroidBugreportParser() = default;
 
-util::Status AndroidBugreportParser::Parse(TraceBlobView tbv) {
-  if (!first_chunk_seen_) {
-    first_chunk_seen_ = true;
-    // All logs in Android bugreports use wall time (which creates problems
-    // in case of early boot events before NTP kicks in, which get emitted as
-    // 1970), but that is the state of affairs.
-    context_->clock_tracker->SetTraceTimeClock(
-        protos::pbzero::BUILTIN_CLOCK_REALTIME);
-  }
-
-  return zip_reader_->Parse(tbv.data(), tbv.size());
-}
-
-void AndroidBugreportParser::NotifyEndOfFile() {
+util::Status AndroidBugreportParser::ParseImpl() {
+  // All logs in Android bugreports use wall time (which creates problems
+  // in case of early boot events before NTP kicks in, which get emitted as
+  // 1970), but that is the state of affairs.
+  context_->clock_tracker->SetTraceTimeClock(
+      protos::pbzero::BUILTIN_CLOCK_REALTIME);
   if (!DetectYearAndBrFilename()) {
     context_->storage->IncrementStats(stats::android_br_parse_errors);
-    return;
+    return base::ErrStatus("Zip file does not contain bugreport file.");
   }
 
   ParsePersistentLogcat();
   ParseDumpstateTxt();
   SortAndStoreLogcat();
+  return base::OkStatus();
 }
 
 void AndroidBugreportParser::ParseDumpstateTxt() {
+  PERFETTO_CHECK(dumpstate_file_);
   // Dumpstate is organized in a two level hierarchy, beautifully flattened into
   // one text file with load bearing ----- markers:
   // 1. Various dumpstate sections, examples:
@@ -95,80 +131,81 @@
   // Here we put each line in a dedicated table, android_dumpstate, keeping
   // track of the dumpstate `section` and dumpsys `service`.
   AndroidLogParser log_parser(br_year_, context_->storage.get());
-  util::ZipFile* zf = zip_reader_->Find(dumpstate_fname_);
   StringId section_id = StringId::Null();  // The current dumpstate section.
   StringId service_id = StringId::Null();  // The current dumpsys service.
   static constexpr size_t npos = base::StringView::npos;
   enum { OTHER = 0, DUMPSYS, LOG } cur_sect = OTHER;
-  zf->DecompressLines([&](const std::vector<base::StringView>& lines) {
-    // Optimization for ParseLogLines() below. Avoids ctor/dtor-ing a new vector
-    // on every line.
-    std::vector<base::StringView> log_line(1);
-    for (const base::StringView& line : lines) {
-      if (line.StartsWith("------ ") && line.EndsWith(" ------")) {
-        // These lines mark the beginning and end of dumpstate sections:
-        // ------ DUMPSYS CRITICAL (/system/bin/dumpsys) ------
-        // ------ 0.356s was the duration of 'DUMPSYS CRITICAL' ------
-        base::StringView section = line.substr(7);
-        section = section.substr(0, section.size() - 7);
-        bool end_marker = section.find("was the duration of") != npos;
-        service_id = StringId::Null();
-        if (end_marker) {
-          section_id = StringId::Null();
-        } else {
-          section_id = context_->storage->InternString(section);
-          cur_sect = OTHER;
-          if (section.StartsWith("DUMPSYS")) {
-            cur_sect = DUMPSYS;
-          } else if (section.StartsWith("SYSTEM LOG") ||
-                     section.StartsWith("EVENT LOG") ||
-                     section.StartsWith("RADIO LOG")) {
-            // KERNEL LOG is deliberately omitted because SYSTEM LOG is a
-            // superset. KERNEL LOG contains all dupes.
-            cur_sect = LOG;
-          } else if (section.StartsWith("BLOCK STAT")) {
-            // Coalesce all the block stats into one section. Otherwise they
-            // pollute the table with one section per block device.
-            section_id = context_->storage->InternString("BLOCK STAT");
+  dumpstate_file_->DecompressLines(
+      [&](const std::vector<base::StringView>& lines) {
+        // Optimization for ParseLogLines() below. Avoids ctor/dtor-ing a new
+        // vector on every line.
+        std::vector<base::StringView> log_line(1);
+        for (const base::StringView& line : lines) {
+          if (line.StartsWith("------ ") && line.EndsWith(" ------")) {
+            // These lines mark the beginning and end of dumpstate sections:
+            // ------ DUMPSYS CRITICAL (/system/bin/dumpsys) ------
+            // ------ 0.356s was the duration of 'DUMPSYS CRITICAL' ------
+            base::StringView section = line.substr(7);
+            section = section.substr(0, section.size() - 7);
+            bool end_marker = section.find("was the duration of") != npos;
+            service_id = StringId::Null();
+            if (end_marker) {
+              section_id = StringId::Null();
+            } else {
+              section_id = context_->storage->InternString(section);
+              cur_sect = OTHER;
+              if (section.StartsWith("DUMPSYS")) {
+                cur_sect = DUMPSYS;
+              } else if (section.StartsWith("SYSTEM LOG") ||
+                         section.StartsWith("EVENT LOG") ||
+                         section.StartsWith("RADIO LOG")) {
+                // KERNEL LOG is deliberately omitted because SYSTEM LOG is a
+                // superset. KERNEL LOG contains all dupes.
+                cur_sect = LOG;
+              } else if (section.StartsWith("BLOCK STAT")) {
+                // Coalesce all the block stats into one section. Otherwise they
+                // pollute the table with one section per block device.
+                section_id = context_->storage->InternString("BLOCK STAT");
+              }
+            }
+            continue;
           }
+          // Skip end marker lines for dumpsys sections.
+          if (cur_sect == DUMPSYS && line.StartsWith("--------- ") &&
+              line.find("was the duration of dumpsys") != npos) {
+            service_id = StringId::Null();
+            continue;
+          }
+          if (cur_sect == DUMPSYS && service_id.is_null() &&
+              line.StartsWith(
+                  "----------------------------------------------")) {
+            continue;
+          }
+          if (cur_sect == DUMPSYS && line.StartsWith("DUMP OF SERVICE")) {
+            // DUMP OF SERVICE [CRITICAL|HIGH] ServiceName:
+            base::StringView svc = line.substr(line.rfind(' ') + 1);
+            svc = svc.substr(0, svc.size() - 1);
+            service_id = context_->storage->InternString(svc);
+          } else if (cur_sect == LOG) {
+            // Parse the non-persistent logcat and append to `log_events_`,
+            // together with the persistent one previously parsed by
+            // ParsePersistentLogcat(). Skips entries that are already seen in
+            // the persistent logcat, handling us vs ms truncation.
+            PERFETTO_DCHECK(log_line.size() == 1);
+            log_line[0] = line;
+            log_parser.ParseLogLines(log_line, &log_events_,
+                                     log_events_last_sorted_idx_);
+          }
+
+          if (build_fpr_.empty() && line.StartsWith("Build fingerprint:")) {
+            build_fpr_ = line.substr(20, line.size() - 20).ToStdString();
+          }
+
+          // Append the line to the android_dumpstate table.
+          context_->storage->mutable_android_dumpstate_table()->Insert(
+              {section_id, service_id, context_->storage->InternString(line)});
         }
-        continue;
-      }
-      // Skip end marker lines for dumpsys sections.
-      if (cur_sect == DUMPSYS && line.StartsWith("--------- ") &&
-          line.find("was the duration of dumpsys") != npos) {
-        service_id = StringId::Null();
-        continue;
-      }
-      if (cur_sect == DUMPSYS && service_id.is_null() &&
-          line.StartsWith("----------------------------------------------")) {
-        continue;
-      }
-      if (cur_sect == DUMPSYS && line.StartsWith("DUMP OF SERVICE")) {
-        // DUMP OF SERVICE [CRITICAL|HIGH] ServiceName:
-        base::StringView svc = line.substr(line.rfind(' ') + 1);
-        svc = svc.substr(0, svc.size() - 1);
-        service_id = context_->storage->InternString(svc);
-      } else if (cur_sect == LOG) {
-        // Parse the non-persistent logcat and append to `log_events_`, together
-        // with the persistent one previously parsed by ParsePersistentLogcat().
-        // Skips entries that are already seen in the persistent logcat,
-        // handling us vs ms truncation.
-        PERFETTO_DCHECK(log_line.size() == 1);
-        log_line[0] = line;
-        log_parser.ParseLogLines(log_line, &log_events_,
-                                 log_events_last_sorted_idx_);
-      }
-
-      if (build_fpr_.empty() && line.StartsWith("Build fingerprint:")) {
-        build_fpr_ = line.substr(20, line.size() - 20).ToStdString();
-      }
-
-      // Append the line to the android_dumpstate table.
-      context_->storage->mutable_android_dumpstate_table()->Insert(
-          {section_id, service_id, context_->storage->InternString(line)});
-    }
-  });
+      });
 }
 
 void AndroidBugreportParser::ParsePersistentLogcat() {
@@ -182,22 +219,23 @@
   // Sort files to ease the job of the subsequent line-based sort. Unfortunately
   // lines within each file are not 100% timestamp-ordered, due to things like
   // kernel messages where log time != event time.
-  std::vector<std::pair<uint64_t, std::string>> log_paths;
-  for (const util::ZipFile& zf : zip_reader_->files()) {
+  std::vector<std::pair<uint64_t, const util::ZipFile*>> log_files;
+  for (const util::ZipFile& zf : zip_file_entries_) {
     if (base::StartsWith(zf.name(), "FS/data/misc/logd/logcat") &&
         !base::EndsWith(zf.name(), "logcat.id")) {
-      log_paths.emplace_back(std::make_pair(zf.GetDatetime(), zf.name()));
+      log_files.push_back(std::make_pair(zf.GetDatetime(), &zf));
     }
   }
-  std::sort(log_paths.begin(), log_paths.end());
+
+  std::sort(log_files.begin(), log_files.end());
 
   // Push all events into the AndroidLogParser. It will take care of string
   // interning into the pool. Appends entries into `log_events`.
-  for (const auto& kv : log_paths) {
-    util::ZipFile* zf = zip_reader_->Find(kv.second);
-    zf->DecompressLines([&](const std::vector<base::StringView>& lines) {
-      log_parser.ParseLogLines(lines, &log_events_);
-    });
+  for (const auto& log_file : log_files) {
+    log_file.second->DecompressLines(
+        [&](const std::vector<base::StringView>& lines) {
+          log_parser.ParseLogLines(lines, &log_events_);
+        });
   }
 
   // Do an initial sorting pass. This is not the final sorting because we
@@ -230,30 +268,20 @@
 // This is obviously bugged for cases of bugreports collected across new year
 // but we'll live with that.
 bool AndroidBugreportParser::DetectYearAndBrFilename() {
-  const util::ZipFile* br_file = nullptr;
-  for (const auto& zf : zip_reader_->files()) {
-    if (base::StartsWith(zf.name(), "bugreport-") &&
-        base::EndsWith(zf.name(), ".txt")) {
-      br_file = &zf;
-      break;
-    }
-  }
-
+  const util::ZipFile* br_file = FindBugReportFile(zip_file_entries_);
   if (!br_file) {
     PERFETTO_ELOG("Could not find bugreport-*.txt in the zip file");
     return false;
   }
 
-  // Typical name: "bugreport-product-TP1A.220623.001-2022-06-24-16-24-37.txt".
-  auto year_str = br_file->name().substr(
-      br_file->name().size() - strlen("2022-12-31-23-59-00.txt"), 4);
-  std::optional<int32_t> year = base::StringToInt32(year_str);
+  std::optional<int32_t> year =
+      ExtractYearFromBugReportFilename(br_file->name());
   if (!year.has_value()) {
     PERFETTO_ELOG("Could not parse the year from %s", br_file->name().c_str());
     return false;
   }
   br_year_ = *year;
-  dumpstate_fname_ = br_file->name();
+  dumpstate_file_ = br_file;
   return true;
 }
 
diff --git a/src/trace_processor/importers/android_bugreport/android_bugreport_parser.h b/src/trace_processor/importers/android_bugreport/android_bugreport_parser.h
index 9969159..a9ca322 100644
--- a/src/trace_processor/importers/android_bugreport/android_bugreport_parser.h
+++ b/src/trace_processor/importers/android_bugreport/android_bugreport_parser.h
@@ -17,8 +17,11 @@
 #ifndef SRC_TRACE_PROCESSOR_IMPORTERS_ANDROID_BUGREPORT_ANDROID_BUGREPORT_PARSER_H_
 #define SRC_TRACE_PROCESSOR_IMPORTERS_ANDROID_BUGREPORT_ANDROID_BUGREPORT_PARSER_H_
 
-#include "src/trace_processor/importers/common/chunked_trace_reader.h"
-#include "src/trace_processor/storage/trace_storage.h"
+#include <cstddef>
+#include <vector>
+
+#include "perfetto/trace_processor/status.h"
+#include "src/trace_processor/util/zip_reader.h"
 
 namespace perfetto {
 namespace trace_processor {
@@ -31,16 +34,19 @@
 class TraceProcessorContext;
 
 // Trace importer for Android bugreport.zip archives.
-class AndroidBugreportParser : public ChunkedTraceReader {
+class AndroidBugreportParser {
  public:
-  explicit AndroidBugreportParser(TraceProcessorContext*);
-  ~AndroidBugreportParser() override;
-
-  // ChunkedTraceReader implementation.
-  util::Status Parse(TraceBlobView) override;
-  void NotifyEndOfFile() override;
+  static bool IsAndroidBugReport(
+      const std::vector<util::ZipFile>& zip_file_entries);
+  static util::Status Parse(TraceProcessorContext* context,
+                            std::vector<util::ZipFile> zip_file_entries);
 
  private:
+  AndroidBugreportParser(TraceProcessorContext* context,
+                         std::vector<util::ZipFile> zip_file_entries);
+  ~AndroidBugreportParser();
+  util::Status ParseImpl();
+
   bool DetectYearAndBrFilename();
   void ParsePersistentLogcat();
   void ParseDumpstateTxt();
@@ -48,11 +54,11 @@
   void SortLogEvents();
 
   TraceProcessorContext* const context_;
+  std::vector<util::ZipFile> zip_file_entries_;
   int br_year_ = 0;  // The year when the bugreport has been taken.
-  std::string dumpstate_fname_;  // The name of bugreport-xxx-2022-08-04....txt
+  const util::ZipFile* dumpstate_file_ =
+      nullptr;  // The bugreport-xxx-2022-08-04....txt file
   std::string build_fpr_;
-  bool first_chunk_seen_ = false;
-  std::unique_ptr<util::ZipReader> zip_reader_;
   std::vector<AndroidLogEvent> log_events_;
   size_t log_events_last_sorted_idx_ = 0;
 };
diff --git a/src/trace_processor/importers/common/mapping_tracker.cc b/src/trace_processor/importers/common/mapping_tracker.cc
index 965c15f..d0a2d88 100644
--- a/src/trace_processor/importers/common/mapping_tracker.cc
+++ b/src/trace_processor/importers/common/mapping_tracker.cc
@@ -81,9 +81,6 @@
 UserMemoryMapping& MappingTracker::CreateUserMemoryMapping(
     UniquePid upid,
     CreateMappingParams params) {
-  // TODO(carlscab): Guess build_id if not provided. Some tools like simpleperf
-  // add a mapping file_name ->build_id that we could use here
-
   const AddressRange mapping_range = params.memory_range;
   std::unique_ptr<UserMemoryMapping> mapping(
       new UserMemoryMapping(context_, upid, std::move(params)));
diff --git a/src/trace_processor/importers/common/process_tracker.cc b/src/trace_processor/importers/common/process_tracker.cc
index 3fc95fc..e292801 100644
--- a/src/trace_processor/importers/common/process_tracker.cc
+++ b/src/trace_processor/importers/common/process_tracker.cc
@@ -356,6 +356,17 @@
   UniquePid upid = GetOrCreateProcess(pid);
   auto* process_table = context_->storage->mutable_process_table();
 
+  // If we both know the previous and current parent pid and the two are not
+  // matching, we must have died and restarted: create a new process.
+  if (pupid) {
+    std::optional<UniquePid> prev_parent_upid =
+        process_table->parent_upid()[upid];
+    if (prev_parent_upid && prev_parent_upid != pupid) {
+      upid = StartNewProcess(std::nullopt, ppid, pid, kNullStringId,
+                             ThreadNamePriority::kOther);
+    }
+  }
+
   StringId proc_name_id = context_->storage->InternString(name);
   process_table->mutable_name()->Set(upid, proc_name_id);
   process_table->mutable_cmdline()->Set(
diff --git a/src/trace_processor/importers/perf/BUILD.gn b/src/trace_processor/importers/perf/BUILD.gn
index 7a3f2a3..d782352 100644
--- a/src/trace_processor/importers/perf/BUILD.gn
+++ b/src/trace_processor/importers/perf/BUILD.gn
@@ -34,9 +34,27 @@
     "../../storage",
     "../../tables:tables_python",
     "../../types",
+    "../../util:build_id",
     "../common:parser_types",
   ]
 }
+
+source_set("tracker") {
+  sources = [
+    "dso_tracker.cc",
+    "dso_tracker.h",
+  ]
+  deps = [
+    "../../../../gn:default_deps",
+    "../../../../include/perfetto/ext/base:base",
+    "../../../../protos/third_party/simpleperf:zero",
+    "../..//storage:storage",
+    "../..//tables:tables",
+    "../..//types:types",
+    "../common:common",
+  ]
+}
+
 source_set("perf") {
   sources = [
     "attrs_section_reader.cc",
@@ -55,9 +73,11 @@
   ]
   public_deps = [ ":record" ]
   deps = [
+    ":tracker",
     "../../../../gn:default_deps",
     "../../../../protos/perfetto/trace:zero",
     "../../../../protos/perfetto/trace/profiling:zero",
+    "../../../../protos/third_party/simpleperf:zero",
     "../../sorter",
     "../../storage",
     "../../tables:tables_python",
diff --git a/src/trace_processor/importers/perf/dso_tracker.cc b/src/trace_processor/importers/perf/dso_tracker.cc
new file mode 100644
index 0000000..3ff3cef
--- /dev/null
+++ b/src/trace_processor/importers/perf/dso_tracker.cc
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_processor/importers/perf/dso_tracker.h"
+
+#include <cstdint>
+
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/string_view.h"
+#include "protos/third_party/simpleperf/record_file.pbzero.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/tables/profiler_tables_py.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto::trace_processor::perf_importer {
+namespace {
+
+using third_party::simpleperf::proto::pbzero::FileFeature;
+using DexFile = FileFeature::DexFile;
+using ElfFile = FileFeature::ElfFile;
+using KernelModule = FileFeature::KernelModule;
+using DsoType = FileFeature::DsoType;
+using Symbol = FileFeature::Symbol;
+
+void InsertSymbols(const FileFeature::Decoder& file,
+                   AddressRangeMap<std::string>& out) {
+  for (auto raw_symbol = file.symbol(); raw_symbol; ++raw_symbol) {
+    Symbol::Decoder symbol(*raw_symbol);
+    out.DeleteOverlapsAndEmplace(
+        AddressRange::FromStartAndSize(symbol.vaddr(), symbol.len()),
+        symbol.name().ToStdString());
+  }
+}
+}  // namespace
+
+DsoTracker::DsoTracker(TraceProcessorContext* context)
+    : context_(context),
+      mapping_table_(context_->storage->stack_profile_mapping_table()) {}
+DsoTracker::~DsoTracker() = default;
+
+void DsoTracker::AddSimpleperfFile2(const FileFeature::Decoder& file) {
+  Dso dso;
+  switch (file.type()) {
+    case DsoType::DSO_KERNEL:
+      InsertSymbols(file, kernel_symbols_);
+      return;
+
+    case DsoType::DSO_ELF_FILE: {
+      ElfFile::Decoder elf(file.elf_file());
+      dso.load_bias = file.min_vaddr() - elf.file_offset_of_min_vaddr();
+      break;
+    }
+
+    case DsoType::DSO_KERNEL_MODULE: {
+      KernelModule::Decoder module(file.kernel_module());
+      dso.load_bias = file.min_vaddr() - module.memory_offset_of_min_vaddr();
+      break;
+    }
+
+    case DsoType::DSO_DEX_FILE:
+    case DsoType::DSO_SYMBOL_MAP_FILE:
+    case DsoType::DSO_UNKNOWN_FILE:
+      return;
+  }
+
+  InsertSymbols(file, dso.symbols);
+  files_.Insert(context_->storage->InternString(file.path()), std::move(dso));
+}
+
+void DsoTracker::SymbolizeFrames() {
+  const StringId kEmptyString = context_->storage->InternString("");
+  for (auto frame = context_->storage->mutable_stack_profile_frame_table()
+                        ->IterateRows();
+       frame; ++frame) {
+    if (frame.name() != kNullStringId && frame.name() != kEmptyString) {
+      continue;
+    }
+
+    if (!TrySymbolizeFrame(frame.row_reference())) {
+      SymbolizeKernelFrame(frame.row_reference());
+    }
+  }
+}
+
+void DsoTracker::SymbolizeKernelFrame(
+    tables::StackProfileFrameTable::RowReference frame) {
+  const auto mapping = *mapping_table_.FindById(frame.mapping());
+  uint64_t address = static_cast<uint64_t>(frame.rel_pc()) +
+                     static_cast<uint64_t>(mapping.start());
+  auto symbol = kernel_symbols_.Find(address);
+  if (symbol == kernel_symbols_.end()) {
+    return;
+  }
+  frame.set_name(
+      context_->storage->InternString(base::StringView(symbol->second)));
+}
+
+bool DsoTracker::TrySymbolizeFrame(
+    tables::StackProfileFrameTable::RowReference frame) {
+  const auto mapping = *mapping_table_.FindById(frame.mapping());
+  auto* file = files_.Find(mapping.name());
+  if (!file) {
+    return false;
+  }
+
+  // Load bias is something we can only determine by looking at the actual elf
+  // file. Thus PERF_RECORD_MMAP{2} events do not record it. So we need to
+  // potentially do an adjustment here if the load_bias tracked in the mapping
+  // table and the one reported by the file are mismatched.
+  uint64_t adj = file->load_bias - static_cast<uint64_t>(mapping.load_bias());
+
+  auto symbol = file->symbols.Find(static_cast<uint64_t>(frame.rel_pc()) + adj);
+  if (symbol == file->symbols.end()) {
+    return false;
+  }
+  frame.set_name(
+      context_->storage->InternString(base::StringView(symbol->second)));
+  return true;
+}
+
+}  // namespace perfetto::trace_processor::perf_importer
diff --git a/src/trace_processor/importers/perf/dso_tracker.h b/src/trace_processor/importers/perf/dso_tracker.h
new file mode 100644
index 0000000..314d549
--- /dev/null
+++ b/src/trace_processor/importers/perf/dso_tracker.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PERF_DSO_TRACKER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_PERF_DSO_TRACKER_H_
+
+#include <cstdint>
+#include <string>
+
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/flat_hash_map.h"
+#include "protos/third_party/simpleperf/record_file.pbzero.h"
+#include "src/trace_processor/importers/common/address_range.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/tables/profiler_tables_py.h"
+#include "src/trace_processor/types/destructible.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto::trace_processor::perf_importer {
+
+// Keeps track of DSO symbols to symbolize frames at the end of the trace
+// parsing.
+// TODO(b/334978369): We could potentially use this class (or a similar one) to
+// process the ModuleSymbols proto packets and consolidate all symbolization in
+// one place.
+class DsoTracker : public Destructible {
+ public:
+  static DsoTracker& GetOrCreate(TraceProcessorContext* context) {
+    if (!context->perf_dso_tracker) {
+      context->perf_dso_tracker.reset(new DsoTracker(context));
+    }
+    return static_cast<DsoTracker&>(*context->perf_dso_tracker);
+  }
+  ~DsoTracker() override;
+
+  // Add symbol data contained in a `FileFeature` proto.
+  void AddSimpleperfFile2(
+      const third_party::simpleperf::proto::pbzero::FileFeature::Decoder& file);
+
+  // Tries to symbolize any `STACK_PROFILE_FRAME` frame missing the `name`
+  // attribute. This should be called at the end of parsing when all packets
+  // have been processed and all tables updated.
+  void SymbolizeFrames();
+
+ private:
+  struct Dso {
+    uint64_t load_bias;
+    AddressRangeMap<std::string> symbols;
+  };
+
+  explicit DsoTracker(TraceProcessorContext* context);
+
+  void SymbolizeKernelFrame(tables::StackProfileFrameTable::RowReference frame);
+  // Returns true it the frame was symbolized.
+  bool TrySymbolizeFrame(tables::StackProfileFrameTable::RowReference frame);
+
+  TraceProcessorContext* const context_;
+  const tables::StackProfileMappingTable& mapping_table_;
+  base::FlatHashMap<StringId, Dso> files_;
+  AddressRangeMap<std::string> kernel_symbols_;
+};
+
+}  // namespace perfetto::trace_processor::perf_importer
+
+#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_PERF_DSO_TRACKER_H_
diff --git a/src/trace_processor/importers/perf/features.cc b/src/trace_processor/importers/perf/features.cc
index 861a8bf..6ade833 100644
--- a/src/trace_processor/importers/perf/features.cc
+++ b/src/trace_processor/importers/perf/features.cc
@@ -32,6 +32,37 @@
 namespace perfetto::trace_processor::perf_importer::feature {
 namespace {
 
+struct BuildIdRecord {
+  static constexpr uint8_t kMaxSize = 20;
+  char data[kMaxSize];
+  uint8_t size;
+  uint8_t reserved[3];
+};
+
+uint8_t CountTrailingZeros(const BuildIdRecord& build_id) {
+  for (uint8_t i = 0; i < BuildIdRecord::kMaxSize; ++i) {
+    if (build_id.data[BuildIdRecord::kMaxSize - i - 1] != 0) {
+      return i;
+    }
+  }
+  return sizeof(build_id.data);
+}
+
+// BuildIds are usually SHA-1 hashes (20 bytes), sometimes MD5 (16 bytes),
+// sometimes 8 bytes long. Simpleperf adds trailing zeros up to 20. Do a best
+// guess based on the number of trailing zeros.
+uint8_t GuessBuildIdSize(const BuildIdRecord& build_id) {
+  static_assert(BuildIdRecord::kMaxSize == 20);
+  uint8_t len = BuildIdRecord::kMaxSize - CountTrailingZeros(build_id);
+  if (len > 16) {
+    return BuildIdRecord::kMaxSize;
+  }
+  if (len > 8) {
+    return 16;
+  }
+  return 8;
+}
+
 bool ParseString(Reader& reader, std::string& out) {
   uint32_t len;
   base::StringView str;
@@ -51,11 +82,8 @@
                   TraceBlobView blob,
                   BuildId& out) {
   Reader reader(std::move(blob));
-  struct {
-    char data[20];
-    uint8_t size;
-    uint8_t reserved[3];
-  } build_id;
+
+  BuildIdRecord build_id;
 
   if (!reader.Read(out.pid) || !reader.Read(build_id) ||
       !reader.ReadStringUntilEndOrNull(out.filename)) {
@@ -63,7 +91,7 @@
   }
 
   if (header.misc & PERF_RECORD_MISC_EXT_RESERVED) {
-    if (build_id.size > sizeof(build_id.data)) {
+    if (build_id.size > BuildIdRecord::kMaxSize) {
       return false;
     }
   } else {
@@ -72,15 +100,7 @@
     // build_id.size or build_id.reserved to do any checks.
     // TODO(b/334978369): We should be able to tell for sure whether this is
     // simpleperf or not by checking the existence of SimpleperfMetaInfo.
-    build_id.size = 20;
-    // BuildIds are usually SHA-1 hashes (20 bytes), sometimes MD5 (16 bites).
-    // Simpleperf adds trailing zeros. But zeros could be in the MD5 hash.
-    // But it the last 4 bytes are zeros there is a high chance this was an
-    // MD5.
-    if (build_id.data[16] == 0 && build_id.data[17] == 0 &&
-        build_id.data[18] == 0 && build_id.data[19] == 0) {
-      build_id.size = 16;
-    }
+    build_id.size = GuessBuildIdSize(build_id);
   }
   out.build_id = std::string(build_id.data, build_id.size);
   return true;
@@ -216,9 +236,8 @@
   return util::OkStatus();
 }
 
-util::Status ParseSimpleperfFile2(
-    TraceBlobView bytes,
-    std::function<util::Status(TraceBlobView)> cb) {
+util::Status ParseSimpleperfFile2(TraceBlobView bytes,
+                                  std::function<void(TraceBlobView)> cb) {
   Reader reader(std::move(bytes));
   while (reader.size_left() != 0) {
     uint32_t len;
@@ -230,7 +249,7 @@
       return base::ErrStatus(
           "Failed to parse payload in FEATURE_SIMPLEPERF_FILE2");
     }
-    RETURN_IF_ERROR(cb(std::move(payload)));
+    cb(std::move(payload));
   }
   return util::OkStatus();
 }
diff --git a/src/trace_processor/importers/perf/features.h b/src/trace_processor/importers/perf/features.h
index 2a6c22b..1af9498 100644
--- a/src/trace_processor/importers/perf/features.h
+++ b/src/trace_processor/importers/perf/features.h
@@ -121,9 +121,8 @@
       event_type_info;
 };
 
-util::Status ParseSimpleperfFile2(
-    TraceBlobView,
-    std::function<util::Status(TraceBlobView)> cb);
+util::Status ParseSimpleperfFile2(TraceBlobView,
+                                  std::function<void(TraceBlobView)> cb);
 
 }  // namespace perf_importer::feature
 
diff --git a/src/trace_processor/importers/perf/perf_data_tokenizer.cc b/src/trace_processor/importers/perf/perf_data_tokenizer.cc
index 2dcc3e5..bbecf81 100644
--- a/src/trace_processor/importers/perf/perf_data_tokenizer.cc
+++ b/src/trace_processor/importers/perf/perf_data_tokenizer.cc
@@ -20,6 +20,7 @@
 #include <cstdint>
 #include <cstring>
 #include <optional>
+#include <string>
 #include <utility>
 #include <vector>
 
@@ -30,9 +31,11 @@
 #include "perfetto/public/compiler.h"
 #include "perfetto/trace_processor/trace_blob_view.h"
 #include "protos/perfetto/trace/clock_snapshot.pbzero.h"
+#include "protos/third_party/simpleperf/record_file.pbzero.h"
 #include "src/trace_processor/importers/common/clock_tracker.h"
 #include "src/trace_processor/importers/common/slice_tracker.h"
 #include "src/trace_processor/importers/perf/attrs_section_reader.h"
+#include "src/trace_processor/importers/perf/dso_tracker.h"
 #include "src/trace_processor/importers/perf/features.h"
 #include "src/trace_processor/importers/perf/perf_event.h"
 #include "src/trace_processor/importers/perf/perf_file.h"
@@ -42,6 +45,7 @@
 #include "src/trace_processor/importers/proto/perf_sample_tracker.h"
 #include "src/trace_processor/sorter/trace_sorter.h"
 #include "src/trace_processor/storage/stats.h"
+#include "src/trace_processor/util/build_id.h"
 #include "src/trace_processor/util/status_macros.h"
 
 namespace perfetto {
@@ -387,7 +391,12 @@
 
     case feature::ID_BUILD_ID:
       return feature::BuildId::Parse(
-          std::move(data), [](feature::BuildId) { return base::OkStatus(); });
+          std::move(data), [&](feature::BuildId build_id) {
+            perf_session_->AddBuildId(
+                build_id.pid, std::move(build_id.filename),
+                BuildId::FromRaw(std::move(build_id.build_id)));
+            return base::OkStatus();
+          });
 
     case feature::ID_GROUP_DESC: {
       feature::HeaderGroupDesc group_desc;
@@ -408,7 +417,11 @@
     }
     case feature::ID_SIMPLEPERF_FILE2: {
       RETURN_IF_ERROR(feature::ParseSimpleperfFile2(
-          std::move(data), [&](TraceBlobView) { return util::OkStatus(); }));
+          std::move(data), [&](TraceBlobView blob) {
+            third_party::simpleperf::proto::pbzero::FileFeature::Decoder file(
+                blob.data(), blob.length());
+            DsoTracker::GetOrCreate(context_).AddSimpleperfFile2(file);
+          }));
 
       break;
     }
diff --git a/src/trace_processor/importers/perf/perf_session.cc b/src/trace_processor/importers/perf/perf_session.cc
index db42ae7..4be5d80 100644
--- a/src/trace_processor/importers/perf/perf_session.cc
+++ b/src/trace_processor/importers/perf/perf_session.cc
@@ -20,6 +20,7 @@
 #include <cstddef>
 #include <cstdint>
 #include <cstring>
+#include <limits>
 #include <optional>
 #include <vector>
 
@@ -31,6 +32,7 @@
 #include "src/trace_processor/importers/perf/perf_event.h"
 #include "src/trace_processor/importers/perf/perf_event_attr.h"
 #include "src/trace_processor/importers/perf/reader.h"
+#include "src/trace_processor/util/build_id.h"
 
 namespace perfetto::trace_processor::perf_importer {
 namespace {
@@ -153,4 +155,22 @@
   }
 }
 
+void PerfSession::AddBuildId(int32_t pid,
+                             std::string filename,
+                             BuildId build_id) {
+  build_ids_.Insert({pid, std::move(filename)}, std::move(build_id));
+}
+
+std::optional<BuildId> PerfSession::LookupBuildId(
+    uint32_t pid,
+    const std::string& filename) const {
+  // -1 is used in BUILD_ID feature to match any pid.
+  static constexpr int32_t kAnyPid = -1;
+  auto it = build_ids_.Find({static_cast<int32_t>(pid), filename});
+  if (!it) {
+    it = build_ids_.Find({kAnyPid, filename});
+  }
+  return it ? std::make_optional(*it) : std::nullopt;
+}
+
 }  // namespace perfetto::trace_processor::perf_importer
diff --git a/src/trace_processor/importers/perf/perf_session.h b/src/trace_processor/importers/perf/perf_session.h
index c2a9e66..7153020 100644
--- a/src/trace_processor/importers/perf/perf_session.h
+++ b/src/trace_processor/importers/perf/perf_session.h
@@ -20,13 +20,17 @@
 #include <sys/types.h>
 #include <cstddef>
 #include <cstdint>
+#include <optional>
+#include <string>
 
 #include "perfetto/ext/base/flat_hash_map.h"
+#include "perfetto/ext/base/hash.h"
 #include "perfetto/ext/base/status_or.h"
 #include "perfetto/trace_processor/ref_counted.h"
 #include "perfetto/trace_processor/trace_blob_view.h"
 #include "src/trace_processor/importers/perf/perf_event.h"
 #include "src/trace_processor/importers/perf/perf_event_attr.h"
+#include "src/trace_processor/util/build_id.h"
 
 namespace perfetto::trace_processor {
 
@@ -69,7 +73,26 @@
   void SetEventName(uint64_t event_id, std::string name);
   void SetEventName(uint32_t type, uint64_t config, const std::string& name);
 
+  void AddBuildId(int32_t pid, std::string filename, BuildId build_id);
+  std::optional<BuildId> LookupBuildId(uint32_t pid,
+                                       const std::string& filename) const;
+
  private:
+  struct BuildIdMapKey {
+    int32_t pid;
+    std::string filename;
+
+    struct Hasher {
+      size_t operator()(const BuildIdMapKey& k) const {
+        return static_cast<size_t>(base::Hasher::Combine(k.pid, k.filename));
+      }
+    };
+
+    bool operator==(const BuildIdMapKey& o) const {
+      return pid == o.pid && filename == o.filename;
+    }
+  };
+
   PerfSession(uint32_t perf_session_id,
               base::FlatHashMap<uint64_t, RefPtr<PerfEventAttr>> attrs_by_id,
               bool has_single_perf_event_attr)
@@ -88,6 +111,8 @@
   // associated). This makes the attr lookup given a record trivial and not
   // dependant no having any id field in the records.
   bool has_single_perf_event_attr_;
+
+  base::FlatHashMap<BuildIdMapKey, BuildId, BuildIdMapKey::Hasher> build_ids_;
 };
 
 }  // namespace perf_importer
diff --git a/src/trace_processor/importers/perf/reader.h b/src/trace_processor/importers/perf/reader.h
index 91a4253..25d4f4c 100644
--- a/src/trace_processor/importers/perf/reader.h
+++ b/src/trace_processor/importers/perf/reader.h
@@ -74,7 +74,8 @@
     if (size_left() < size) {
       return false;
     }
-    blob = TraceBlobView(buffer_, static_cast<size_t>(end_ - current_), size);
+    blob = TraceBlobView(buffer_,
+                         static_cast<size_t>(current_ - buffer_->data()), size);
     current_ += size;
     return true;
   }
diff --git a/src/trace_processor/importers/perf/record_parser.cc b/src/trace_processor/importers/perf/record_parser.cc
index 7371adf..c36d3db 100644
--- a/src/trace_processor/importers/perf/record_parser.cc
+++ b/src/trace_processor/importers/perf/record_parser.cc
@@ -51,7 +51,7 @@
 CreateMappingParams BuildCreateMappingParams(
     const CommonMmapRecordFields& fields,
     std::string filename,
-    std::optional<BuildId> build_id = std::nullopt) {
+    std::optional<BuildId> build_id) {
   return {AddressRange::FromStartAndSize(fields.addr, fields.len), fields.pgoff,
           // start_offset: This is the offset into the file where the ELF header
           // starts. We assume all file mappings are ELF files an thus this
@@ -233,14 +233,18 @@
 base::Status RecordParser::ParseMmap(Record record) {
   MmapRecord mmap;
   RETURN_IF_ERROR(mmap.Parse(record));
+  std::optional<BuildId> build_id =
+      record.session->LookupBuildId(mmap.pid, mmap.filename);
   if (IsInKernel(record.GetCpuMode())) {
     context_->mapping_tracker->CreateKernelMemoryMapping(
-        BuildCreateMappingParams(mmap, std::move(mmap.filename)));
+        BuildCreateMappingParams(mmap, std::move(mmap.filename),
+                                 std::move(build_id)));
     return base::OkStatus();
   }
 
   context_->mapping_tracker->CreateUserMemoryMapping(
-      GetUpid(mmap), BuildCreateMappingParams(mmap, std::move(mmap.filename)));
+      GetUpid(mmap), BuildCreateMappingParams(mmap, std::move(mmap.filename),
+                                              std::move(build_id)));
 
   return base::OkStatus();
 }
@@ -248,15 +252,20 @@
 util::Status RecordParser::ParseMmap2(Record record) {
   Mmap2Record mmap2;
   RETURN_IF_ERROR(mmap2.Parse(record));
+  std::optional<BuildId> build_id = mmap2.GetBuildId();
+  if (!build_id.has_value()) {
+    build_id = record.session->LookupBuildId(mmap2.pid, mmap2.filename);
+  }
   if (IsInKernel(record.GetCpuMode())) {
     context_->mapping_tracker->CreateKernelMemoryMapping(
-        BuildCreateMappingParams(mmap2, std::move(mmap2.filename)));
+        BuildCreateMappingParams(mmap2, std::move(mmap2.filename),
+                                 std::move(build_id)));
     return base::OkStatus();
   }
 
   context_->mapping_tracker->CreateUserMemoryMapping(
       GetUpid(mmap2), BuildCreateMappingParams(mmap2, std::move(mmap2.filename),
-                                               mmap2.GetBuildId()));
+                                               std::move(build_id)));
 
   return base::OkStatus();
 }
diff --git a/src/trace_processor/importers/proto/gpu_event_parser.cc b/src/trace_processor/importers/proto/gpu_event_parser.cc
index 9022cbb..d08d946 100644
--- a/src/trace_processor/importers/proto/gpu_event_parser.cc
+++ b/src/trace_processor/importers/proto/gpu_event_parser.cc
@@ -330,6 +330,7 @@
     ConstBytes blob) {
   protos::pbzero::GpuRenderStageEvent::Decoder event(blob.data, blob.size);
 
+  int32_t pid = 0;
   if (event.has_specifications()) {
     protos::pbzero::GpuRenderStageEvent_Specifications::Decoder spec(
         event.specifications().data, event.specifications().size);
@@ -349,6 +350,13 @@
             context_->storage->InternString(stage.description())));
       }
     }
+    if (spec.has_context_spec()) {
+      protos::pbzero::GpuRenderStageEvent_Specifications_ContextSpec::Decoder
+          context_spec(spec.context_spec());
+      if (context_spec.has_pid()) {
+        pid = context_spec.pid();
+      }
+    }
   }
 
   auto args_callback = [this, &event,
@@ -468,7 +476,8 @@
     row.command_buffer_name = command_buffer_name_id;
     row.submission_id = event.submission_id();
     row.hw_queue_id = static_cast<int64_t>(hw_queue_id);
-
+    row.upid = context_->process_tracker->GetOrCreateProcess(
+        static_cast<uint32_t>(pid));
     context_->slice_tracker->ScopedTyped(
         context_->storage->mutable_gpu_slice_table(), row, args_callback);
   }
diff --git a/src/trace_processor/importers/zip/BUILD.gn b/src/trace_processor/importers/zip/BUILD.gn
new file mode 100644
index 0000000..a938fdd
--- /dev/null
+++ b/src/trace_processor/importers/zip/BUILD.gn
@@ -0,0 +1,33 @@
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+source_set("full") {
+  sources = [
+    "zip_trace_reader.cc",
+    "zip_trace_reader.h",
+  ]
+  deps = [
+    "../../../../gn:default_deps",
+    "../../../../gn:zlib",
+    "../../../../include/perfetto/ext/base:base",
+    "../../../trace_processor:storage_minimal",
+    "../../types",
+    "../../util:trace_type",
+    "../../util:util",
+    "../../util:zip_reader",
+    "../android_bugreport",
+    "../common",
+    "../proto:minimal",
+  ]
+}
diff --git a/src/trace_processor/importers/zip/zip_trace_reader.cc b/src/trace_processor/importers/zip/zip_trace_reader.cc
new file mode 100644
index 0000000..20f019f
--- /dev/null
+++ b/src/trace_processor/importers/zip/zip_trace_reader.cc
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_processor/importers/zip/zip_trace_reader.h"
+
+#include <algorithm>
+#include <cinttypes>
+#include <cstdint>
+#include <cstring>
+#include <memory>
+#include <string>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include "perfetto/base/logging.h"
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/status_or.h"
+#include "perfetto/trace_processor/trace_blob.h"
+#include "perfetto/trace_processor/trace_blob_view.h"
+#include "src/trace_processor/forwarding_trace_parser.h"
+#include "src/trace_processor/importers/android_bugreport/android_bugreport_parser.h"
+#include "src/trace_processor/importers/proto/proto_trace_tokenizer.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/util/status_macros.h"
+#include "src/trace_processor/util/trace_type.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace {
+
+// Proto traces should always parsed first as they might contains clock sync
+// data needed to correctly parse other traces.
+// The rest of the types are sorted by position in the enum but this is not
+// something users should rely on.
+// TODO(carlscab): Proto traces with just ModuleSymbols packets should be an
+// exception. We actually need those are the very end (once whe have all the
+// Frames). Alternatively we could build a map address -> symbol during
+// tokenization and use this during parsing to resolve symbols.
+bool CompareTraceType(TraceType lhs, TraceType rhs) {
+  if (rhs == TraceType::kProtoTraceType) {
+    return false;
+  }
+  if (lhs == TraceType::kProtoTraceType) {
+    return true;
+  }
+  return lhs < rhs;
+}
+
+bool HasSymbols(const TraceBlobView& blob) {
+  bool has_symbols = false;
+  ProtoTraceTokenizer().Tokenize(blob.copy(), [&](TraceBlobView raw) {
+    protos::pbzero::TracePacket::Decoder packet(raw.data(), raw.size());
+    has_symbols = packet.has_module_symbols();
+    return base::ErrStatus("break");
+  });
+  return has_symbols;
+}
+
+}  // namespace
+
+ZipTraceReader::ZipTraceReader(TraceProcessorContext* context)
+    : context_(context) {}
+ZipTraceReader::~ZipTraceReader() = default;
+
+bool ZipTraceReader::Entry::operator<(const Entry& rhs) const {
+  // Traces with symbols should be the last ones to be read.
+  if (has_symbols) {
+    return false;
+  }
+  if (rhs.has_symbols) {
+    return true;
+  }
+  if (CompareTraceType(trace_type, rhs.trace_type)) {
+    return true;
+  }
+  if (CompareTraceType(rhs.trace_type, trace_type)) {
+    return false;
+  }
+  return std::tie(name, index) < std::tie(rhs.name, rhs.index);
+}
+
+util::Status ZipTraceReader::Parse(TraceBlobView blob) {
+  zip_reader_.Parse(blob.data(), blob.size());
+  return base::OkStatus();
+}
+
+void ZipTraceReader::NotifyEndOfFile() {
+  base::Status status = NotifyEndOfFileImpl();
+  if (!status.ok()) {
+    PERFETTO_ELOG("ZipTraceReader failed: %s", status.c_message());
+  }
+}
+
+base::Status ZipTraceReader::NotifyEndOfFileImpl() {
+  std::vector<util::ZipFile> files = zip_reader_.TakeFiles();
+
+  // Android bug reports are ZIP files and its files do not get handled
+  // separately.
+  if (AndroidBugreportParser::IsAndroidBugReport(files)) {
+    return AndroidBugreportParser::Parse(context_, std::move(files));
+  }
+
+  base::StatusOr<std::vector<Entry>> entries = ExtractEntries(std::move(files));
+  if (!entries.ok()) {
+    return entries.status();
+  }
+  std::sort(entries->begin(), entries->end());
+
+  for (Entry& e : *entries) {
+    parsers_.push_back(std::make_unique<ForwardingTraceParser>(context_));
+    auto& parser = *parsers_.back();
+    RETURN_IF_ERROR(parser.Parse(std::move(e.uncompressed_data)));
+    parser.NotifyEndOfFile();
+  }
+  return base::OkStatus();
+}
+
+base::StatusOr<std::vector<ZipTraceReader::Entry>>
+ZipTraceReader::ExtractEntries(std::vector<util::ZipFile> files) const {
+  // TODO(carlsacab): There is a lot of unnecessary copying going on here.
+  // ZipTraceReader can directly parse the ZIP file and given that we know the
+  // decompressed size we could directly decompress into TraceBlob chunks and
+  // send them to the tokenizer.
+  std::vector<Entry> entries;
+  std::vector<uint8_t> buffer;
+  for (size_t i = 0; i < files.size(); ++i) {
+    const util::ZipFile& zip_file = files[i];
+    Entry entry;
+    entry.name = zip_file.name();
+    entry.index = i;
+    RETURN_IF_ERROR(files[i].Decompress(&buffer));
+    entry.uncompressed_data =
+        TraceBlobView(TraceBlob::CopyFrom(buffer.data(), buffer.size()));
+    entry.trace_type = GuessTraceType(entry.uncompressed_data.data(),
+                                      entry.uncompressed_data.size());
+    entry.has_symbols = entry.trace_type == TraceType::kProtoTraceType &&
+                        HasSymbols(entry.uncompressed_data);
+    entries.push_back(std::move(entry));
+  }
+  return std::move(entries);
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/zip/zip_trace_reader.h b/src/trace_processor/importers/zip/zip_trace_reader.h
new file mode 100644
index 0000000..d4f3d88
--- /dev/null
+++ b/src/trace_processor/importers/zip/zip_trace_reader.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_ZIP_ZIP_TRACE_READER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_ZIP_ZIP_TRACE_READER_H_
+
+#include <cstddef>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/status_or.h"
+#include "perfetto/trace_processor/trace_blob_view.h"
+#include "src/trace_processor/importers/common/chunked_trace_reader.h"
+#include "src/trace_processor/util/trace_type.h"
+#include "src/trace_processor/util/zip_reader.h"
+
+namespace perfetto::trace_processor {
+
+class ForwardingTraceParser;
+class TraceProcessorContext;
+
+// Forwards files contained in a ZIP to the appropiate ChunkedTraceReader. It is
+// guaranteed that proto traces will be parsed first.
+class ZipTraceReader : public ChunkedTraceReader {
+ public:
+  explicit ZipTraceReader(TraceProcessorContext* context);
+  ~ZipTraceReader() override;
+
+  // ChunkedTraceReader implementation
+  util::Status Parse(TraceBlobView) override;
+  void NotifyEndOfFile() override;
+
+ private:
+  // Represents a file in the ZIP file. Used to sort them before sending the
+  // files one by one to a `ForwardingTraceParser` instance.
+  struct Entry {
+    // File name. Used to break ties.
+    std::string name;
+    // Position in the zip file. Used to break ties.
+    size_t index;
+    // Trace type. This is the main attribute traces are ordered by. Proto
+    // traces are always parsed first as they might contains clock sync
+    // data needed to correctly parse other traces.
+    TraceType trace_type;
+    TraceBlobView uncompressed_data;
+    // True for proto trace_types whose fist message is a ModuleSymbols packet
+    bool has_symbols = false;
+    // Comparator used to determine the order in which files in the ZIP will be
+    // read.
+    bool operator<(const Entry& rhs) const;
+  };
+
+  base::Status NotifyEndOfFileImpl();
+  base::StatusOr<std::vector<Entry>> ExtractEntries(
+      std::vector<util::ZipFile> files) const;
+  base::Status ParseEntry(Entry entry);
+
+  TraceProcessorContext* const context_;
+  util::ZipReader zip_reader_;
+  // For every file in the ZIP we will create a `ForwardingTraceParser`instance
+  // and send that file to it for tokenization. The instances are kept around
+  // here as some tokenizers might keep state that is later needed after
+  // sorting.
+  std::vector<std::unique_ptr<ForwardingTraceParser>> parsers_;
+};
+
+}  // namespace perfetto::trace_processor
+
+#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_ZIP_ZIP_TRACE_READER_H_
diff --git a/src/trace_processor/metrics/sql/android/android_boot.sql b/src/trace_processor/metrics/sql/android/android_boot.sql
index 9140387..5738bff 100644
--- a/src/trace_processor/metrics/sql/android/android_boot.sql
+++ b/src/trace_processor/metrics/sql/android/android_boot.sql
@@ -19,6 +19,24 @@
 INCLUDE PERFETTO MODULE android.garbage_collection;
 INCLUDE PERFETTO MODULE android.oom_adjuster;
 
+DROP VIEW IF EXISTS android_oom_adj_intervals_with_detailed_bucket_name;
+CREATE PERFETTO VIEW android_oom_adj_intervals_with_detailed_bucket_name AS
+SELECT
+  ts,
+  dur,
+  score,
+  android_oom_adj_score_to_detailed_bucket_name(score, android_appid) AS bucket,
+  upid,
+  process_name,
+  oom_adj_id,
+  oom_adj_ts,
+  oom_adj_dur,
+  oom_adj_track_id,
+  oom_adj_thread_name,
+  oom_adj_reason,
+  oom_adj_trigger
+FROM _oom_adjuster_intervals;
+
 CREATE OR REPLACE PERFETTO FUNCTION get_durations(process_name STRING)
 RETURNS TABLE(uint_sleep_dur LONG, total_dur LONG) AS
 SELECT
@@ -41,7 +59,7 @@
   bucket,
   process_name,
   oom_adj_reason
-FROM android_oom_adj_intervals;
+FROM android_oom_adj_intervals_with_detailed_bucket_name;
 
 DROP VIEW IF EXISTS oom_adj_events_by_process_name;
 CREATE PERFETTO VIEW oom_adj_events_by_process_name AS
@@ -280,7 +298,7 @@
           NULL as name,
           bucket,
           SUM(dur) as total_dur
-        FROM android_oom_adj_intervals
+        FROM android_oom_adj_intervals_with_detailed_bucket_name
           WHERE ts > first_user_unlocked()
         GROUP BY bucket)
     ),
@@ -296,7 +314,7 @@
         process_name as name,
         bucket,
         SUM(dur) as total_dur
-      FROM android_oom_adj_intervals
+      FROM android_oom_adj_intervals_with_detailed_bucket_name
       WHERE ts > first_user_unlocked()
       AND process_name IS NOT NULL
       GROUP BY process_name, bucket)
@@ -318,7 +336,7 @@
         AVG(oom_adj_dur) as avg_oom_adj_dur,
         COUNT(DISTINCT(oom_adj_id)) oom_adj_event_count,
         oom_adj_reason
-      FROM android_oom_adj_intervals
+      FROM android_oom_adj_intervals_with_detailed_bucket_name
       WHERE ts > first_user_unlocked()
       GROUP BY oom_adj_reason
     )
diff --git a/src/trace_processor/metrics/sql/android/android_lmk_reason.sql b/src/trace_processor/metrics/sql/android/android_lmk_reason.sql
index 0ca2258..b7c7ca5 100644
--- a/src/trace_processor/metrics/sql/android/android_lmk_reason.sql
+++ b/src/trace_processor/metrics/sql/android/android_lmk_reason.sql
@@ -64,8 +64,8 @@
   FROM lmk_events
   JOIN rss_and_swap_span
   WHERE lmk_events.ts
-    BETWEEN rss_and_swap_span.ts
-    AND rss_and_swap_span.ts + MAX(rss_and_swap_span.dur - 1, 0)
+  BETWEEN rss_and_swap_span.ts
+  AND rss_and_swap_span.ts + MAX(rss_and_swap_span.dur - 1, 0)
 ),
 lmk_process_sizes_output AS (
   SELECT ts, RepeatedField(AndroidLmkReasonMetric_Process(
diff --git a/src/trace_processor/metrics/sql/android/android_oom_adjuster.sql b/src/trace_processor/metrics/sql/android/android_oom_adjuster.sql
index 7b752a8..1564586 100644
--- a/src/trace_processor/metrics/sql/android/android_oom_adjuster.sql
+++ b/src/trace_processor/metrics/sql/android/android_oom_adjuster.sql
@@ -15,6 +15,51 @@
 --
 INCLUDE PERFETTO MODULE android.oom_adjuster;
 
+DROP VIEW IF EXISTS android_oom_adj_intervals_with_detailed_bucket_name;
+CREATE PERFETTO VIEW android_oom_adj_intervals_with_detailed_bucket_name (
+  -- Timestamp the oom_adj score of the process changed
+  ts INT,
+  -- Duration until the next oom_adj score change of the process.
+  dur INT,
+  -- oom_adj score of the process.
+  score INT,
+  -- oom_adj bucket of the process.
+  bucket STRING,
+  -- Upid of the process having an oom_adj update.
+  upid INT,
+  -- Name of the process having an oom_adj update.
+  process_name STRING,
+  -- Slice id of the latest oom_adj update in the system_server.
+  oom_adj_id INT,
+  -- Timestamp of the latest oom_adj update in the system_server.
+  oom_adj_ts INT,
+  -- Duration of the latest oom_adj update in the system_server.
+  oom_adj_dur INT,
+  -- Track id of the latest oom_adj update in the system_server
+  oom_adj_track_id INT,
+  -- Thread name of the latest oom_adj update in the system_server.
+  oom_adj_thread_name STRING,
+  -- Reason for the latest oom_adj update in the system_server.
+  oom_adj_reason STRING,
+  -- Trigger for the latest oom_adj update in the system_server.
+  oom_adj_trigger STRING
+  ) AS
+SELECT
+  ts,
+  dur,
+  score,
+  android_oom_adj_score_to_detailed_bucket_name(score, android_appid) AS bucket,
+  upid,
+  process_name,
+  oom_adj_id,
+  oom_adj_ts,
+  oom_adj_dur,
+  oom_adj_track_id,
+  oom_adj_thread_name,
+  oom_adj_reason,
+  oom_adj_trigger
+FROM _oom_adjuster_intervals;
+
 DROP TABLE IF EXISTS _oom_adj_events_with_src_bucket;
 CREATE PERFETTO TABLE _oom_adj_events_with_src_bucket
 AS
@@ -24,7 +69,7 @@
   bucket,
   process_name,
   oom_adj_reason
-FROM android_oom_adj_intervals;
+FROM android_oom_adj_intervals_with_detailed_bucket_name;
 
 DROP VIEW IF EXISTS oom_adj_events_by_process_name;
 CREATE PERFETTO VIEW oom_adj_events_by_process_name AS
@@ -100,7 +145,7 @@
     )
     FROM (
         SELECT NULL as name, bucket, SUM(dur) as total_dur
-        FROM android_oom_adj_intervals GROUP BY bucket
+        FROM android_oom_adj_intervals_with_detailed_bucket_name GROUP BY bucket
     )
   ),
   'oom_adj_bucket_duration_agg_by_process',(SELECT RepeatedField(
@@ -112,7 +157,7 @@
     )
     FROM (
       SELECT process_name as name, bucket, SUM(dur) as total_dur
-      FROM android_oom_adj_intervals
+      FROM android_oom_adj_intervals_with_detailed_bucket_name
       WHERE process_name IS NOT NULL
       GROUP BY process_name, bucket
     )
@@ -133,7 +178,7 @@
         AVG(oom_adj_dur) as avg_oom_adj_dur,
         COUNT(DISTINCT(oom_adj_id)) oom_adj_event_count,
         oom_adj_reason
-      FROM android_oom_adj_intervals GROUP BY oom_adj_reason
+      FROM android_oom_adj_intervals_with_detailed_bucket_name GROUP BY oom_adj_reason
     )
   )
 );
diff --git a/src/trace_processor/metrics/sql/android/process_mem.sql b/src/trace_processor/metrics/sql/android/process_mem.sql
index f52f63e..f3b1c88 100644
--- a/src/trace_processor/metrics/sql/android/process_mem.sql
+++ b/src/trace_processor/metrics/sql/android/process_mem.sql
@@ -14,75 +14,51 @@
 -- limitations under the License.
 --
 
--- Create all the views used to generate the Android Memory metrics proto.
--- Anon RSS
-SELECT RUN_METRIC('android/process_counter_span_view.sql',
-  'table_name', 'anon_rss',
-  'counter_name', 'mem.rss.anon');
+INCLUDE PERFETTO MODULE memory.linux.process;
 
--- File RSS
-SELECT RUN_METRIC('android/process_counter_span_view.sql',
-  'table_name', 'file_rss',
-  'counter_name', 'mem.rss.file');
-
-SELECT RUN_METRIC('android/process_counter_span_view.sql',
-  'table_name', 'shmem_rss',
-  'counter_name', 'mem.rss.shmem');
-
--- Swap
-SELECT RUN_METRIC('android/process_counter_span_view.sql',
-  'table_name', 'swap',
-  'counter_name', 'mem.swap');
-
--- OOM score
 SELECT RUN_METRIC('android/process_oom_score.sql');
 
--- Anon RSS + Swap
-DROP TABLE IF EXISTS anon_and_swap_join;
-CREATE VIRTUAL TABLE anon_and_swap_join
-USING SPAN_OUTER_JOIN(anon_rss_span PARTITIONED upid, swap_span PARTITIONED upid);
+DROP VIEW IF EXISTS anon_rss_span;
+CREATE PERFETTO VIEW anon_rss_span AS
+SELECT * FROM _anon_rss;
+
+DROP VIEW IF EXISTS file_rss_span;
+CREATE PERFETTO VIEW file_rss_span AS
+SELECT * FROM _file_rss;
+
+DROP VIEW IF EXISTS shmem_rss_span;
+CREATE PERFETTO VIEW shmem_rss_span AS
+SELECT * FROM _shmem_rss;
+
+DROP VIEW IF EXISTS swap_span;
+CREATE PERFETTO VIEW swap_span AS
+SELECT * FROM _swap;
 
 DROP VIEW IF EXISTS anon_and_swap_span;
 CREATE PERFETTO VIEW anon_and_swap_span AS
 SELECT
   ts, dur, upid,
   IFNULL(anon_rss_val, 0) + IFNULL(swap_val, 0) AS anon_and_swap_val
-FROM anon_and_swap_join;
-
--- Anon RSS + file RSS + Swap
-DROP TABLE IF EXISTS anon_and_file_and_swap_join;
-CREATE VIRTUAL TABLE anon_and_file_and_swap_join
-USING SPAN_OUTER_JOIN(
-  anon_and_swap_join PARTITIONED upid,
-  file_rss_span PARTITIONED upid
-);
-
--- RSS + Swap
-DROP TABLE IF EXISTS rss_and_swap_join;
-CREATE VIRTUAL TABLE rss_and_swap_join
-USING SPAN_OUTER_JOIN(
-  anon_and_file_and_swap_join PARTITIONED upid,
-  shmem_rss_span PARTITIONED upid
-);
+FROM _anon_swap_sj;
 
 DROP VIEW IF EXISTS rss_and_swap_span;
 CREATE PERFETTO VIEW rss_and_swap_span AS
 SELECT
-  ts, dur, upid,
-  CAST(IFNULL(file_rss_val, 0) AS INT) AS file_rss_val,
-  CAST(IFNULL(anon_rss_val, 0) AS INT) AS anon_rss_val,
-  CAST(IFNULL(shmem_rss_val, 0) AS INT) AS shmem_rss_val,
-  CAST(IFNULL(swap_val, 0) AS INT) AS swap_val,
-  CAST(
-    IFNULL(anon_rss_val, 0)
-    + IFNULL(file_rss_val, 0)
-    + IFNULL(shmem_rss_val, 0) AS int) AS rss_val,
-  CAST(
-    IFNULL(anon_rss_val, 0)
-    + IFNULL(swap_val, 0)
-    + IFNULL(file_rss_val, 0)
-    + IFNULL(shmem_rss_val, 0) AS int) AS rss_and_swap_val
-FROM rss_and_swap_join;
+  ts,
+  dur,
+  upid,
+  file_rss AS file_rss_val,
+  anon_rss AS anon_rss_val,
+  shmem_rss AS shmem_rss_val,
+  swap AS swap_val,
+  COALESCE(file_rss, 0)
+    + COALESCE(anon_rss, 0)
+    + COALESCE(shmem_rss, 0) AS rss_val,
+  COALESCE(file_rss, 0)
+    + COALESCE(anon_rss, 0)
+    + COALESCE(shmem_rss, 0)
+    + COALESCE(swap, 0) AS rss_and_swap_val
+FROM _memory_rss_and_swap_per_process_table;
 
 -- If we have dalvik events enabled (for ART trace points) we can construct the java heap timeline.
 SELECT RUN_METRIC('android/process_counter_span_view.sql',
@@ -94,22 +70,22 @@
 SELECT ts, dur, upid, java_heap_kb_val * 1024 AS java_heap_val
 FROM java_heap_kb_span;
 
+DROP TABLE IF EXISTS java_heap_by_oom_span;
+CREATE VIRTUAL TABLE java_heap_by_oom_span
+USING SPAN_JOIN(java_heap_span PARTITIONED upid, oom_score_span PARTITIONED upid);
+
 DROP TABLE IF EXISTS anon_rss_by_oom_span;
 CREATE VIRTUAL TABLE anon_rss_by_oom_span
-USING SPAN_JOIN(anon_rss_span PARTITIONED upid, oom_score_span PARTITIONED upid);
+USING SPAN_JOIN(_anon_rss PARTITIONED upid, oom_score_span PARTITIONED upid);
 
 DROP TABLE IF EXISTS file_rss_by_oom_span;
 CREATE VIRTUAL TABLE file_rss_by_oom_span
-USING SPAN_JOIN(file_rss_span PARTITIONED upid, oom_score_span PARTITIONED upid);
+USING SPAN_JOIN(_file_rss PARTITIONED upid, oom_score_span PARTITIONED upid);
 
 DROP TABLE IF EXISTS swap_by_oom_span;
 CREATE VIRTUAL TABLE swap_by_oom_span
-USING SPAN_JOIN(swap_span PARTITIONED upid, oom_score_span PARTITIONED upid);
+USING SPAN_JOIN(_swap PARTITIONED upid, oom_score_span PARTITIONED upid);
 
 DROP TABLE IF EXISTS anon_and_swap_by_oom_span;
 CREATE VIRTUAL TABLE anon_and_swap_by_oom_span
 USING SPAN_JOIN(anon_and_swap_span PARTITIONED upid, oom_score_span PARTITIONED upid);
-
-DROP TABLE IF EXISTS java_heap_by_oom_span;
-CREATE VIRTUAL TABLE java_heap_by_oom_span
-USING SPAN_JOIN(java_heap_span PARTITIONED upid, oom_score_span PARTITIONED upid);
diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc
index 8f163a0..3048e68 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc
@@ -32,6 +32,7 @@
 
 #include "perfetto/base/logging.h"
 #include "perfetto/base/status.h"
+#include "perfetto/ext/base/flat_hash_map.h"
 #include "perfetto/ext/base/status_or.h"
 #include "perfetto/ext/base/string_utils.h"
 #include "perfetto/ext/base/string_view.h"
@@ -848,7 +849,18 @@
     const std::vector<std::string>& column_names,
     const std::vector<sql_argument::ArgumentDefinition>& schema,
     const char* tag) {
-  // If the user has not provided a schema, we have nothing to validate.
+  std::vector<std::string> duplicate_columns;
+  for (auto it = column_names.begin(); it != column_names.end(); ++it) {
+    if (std::count(it + 1, column_names.end(), *it) > 0) {
+      duplicate_columns.push_back(*it);
+    }
+  }
+  if (!duplicate_columns.empty()) {
+    return base::ErrStatus("%s: multiple columns are named: %s", tag,
+                           base::Join(duplicate_columns, ", ").c_str());
+  }
+
+  // If the user has not provided a schema, we have nothing further to validate.
   if (schema.empty()) {
     return base::OkStatus();
   }
diff --git a/src/trace_processor/perfetto_sql/stdlib/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/BUILD.gn
index 349e2f6..e746a1d 100644
--- a/src/trace_processor/perfetto_sql/stdlib/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/BUILD.gn
@@ -25,6 +25,7 @@
     "counters",
     "cpu",
     "deprecated/v42/common",
+    "gpu",
     "graphs",
     "intervals",
     "linux",
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/oom_adjuster.sql b/src/trace_processor/perfetto_sql/stdlib/android/oom_adjuster.sql
index 3ce017e..c09414e 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/oom_adjuster.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/oom_adjuster.sql
@@ -17,8 +17,30 @@
 INCLUDE PERFETTO MODULE slices.with_context;
 INCLUDE PERFETTO MODULE counters.intervals;
 
--- Converts an oom_adj score Integer to String bucket name.
+-- Converts an oom_adj score Integer to String sample name.
+-- One of: cached, background, job, foreground_service, bfgs, foreground and
+-- system.
 CREATE PERFETTO FUNCTION android_oom_adj_score_to_bucket_name(
+  -- `oom_score` value
+  oom_score INT
+)
+-- Returns the sample bucket based on the oom score.
+RETURNS STRING
+AS
+SELECT
+  CASE
+    WHEN $oom_score >= 900 THEN 'cached'
+    WHEN $oom_score BETWEEN 250 AND 900 THEN 'background'
+    WHEN $oom_score BETWEEN 201 AND 250 THEN 'job'
+    WHEN $oom_score = 200 THEN 'foreground_service'
+    WHEN $oom_score BETWEEN 100 AND 200 THEN 'bfgs'
+    WHEN $oom_score BETWEEN 0 AND 100 THEN 'foreground'
+    WHEN $oom_score < 0 THEN 'system'
+END;
+
+-- Converts an oom_adj score Integer to String bucket name.
+-- Deprecated: use `android_oom_adj_score_to_bucket_name` instead.
+CREATE PERFETTO FUNCTION android_oom_adj_score_to_detailed_bucket_name(
   -- oom_adj score.
   value INT,
   -- android_app id of the process.
@@ -52,8 +74,58 @@
     ELSE 'unknown_app'
   END;
 
+CREATE PERFETTO TABLE _oom_adjuster_intervals AS
+WITH reason AS (
+  SELECT
+    thread_slice.id AS oom_adj_id,
+    thread_slice.ts AS oom_adj_ts,
+    thread_slice.dur AS oom_adj_dur,
+    thread_slice.track_id AS oom_adj_track_id,
+    utid AS oom_adj_utid,
+    thread_name AS oom_adj_thread_name,
+    str_split(thread_slice.name, '_', 1) AS oom_adj_reason,
+    slice.name AS oom_adj_trigger,
+    LEAD(thread_slice.ts) OVER (ORDER BY thread_slice.ts) AS oom_adj_next_ts
+  FROM thread_slice
+  LEFT JOIN slice ON slice.id = thread_slice.parent_id AND slice.dur != -1
+  WHERE thread_slice.name GLOB 'updateOomAdj_*' AND process_name = 'system_server'
+)
+SELECT
+  ts,
+  dur,
+  cast_int!(value) AS score,
+  process.upid,
+  process.name AS process_name,
+  reason.oom_adj_id,
+  reason.oom_adj_ts,
+  reason.oom_adj_dur,
+  reason.oom_adj_track_id,
+  reason.oom_adj_thread_name,
+  reason.oom_adj_utid,
+  reason.oom_adj_reason,
+  reason.oom_adj_trigger,
+  android_appid
+FROM
+  counter_leading_intervals
+    !(
+      (
+        SELECT counter.*
+        FROM counter
+        JOIN counter_track track
+          ON track.id = counter.track_id AND track.name = 'oom_score_adj'
+      ))
+      counter
+JOIN process_counter_track track
+  ON counter.track_id = track.id
+JOIN process
+  USING (upid)
+LEFT JOIN reason
+  ON counter.ts BETWEEN oom_adj_ts AND COALESCE(oom_adj_next_ts, trace_end())
+WHERE track.name = 'oom_score_adj';
+
+
 -- All oom adj state intervals across all processes along with the reason for the state update.
-CREATE PERFETTO TABLE android_oom_adj_intervals (
+CREATE PERFETTO VIEW android_oom_adj_intervals (
   -- Timestamp the oom_adj score of the process changed
   ts INT,
   -- Duration until the next oom_adj score change of the process.
@@ -81,49 +153,18 @@
   -- Trigger for the latest oom_adj update in the system_server.
   oom_adj_trigger STRING
   ) AS
-WITH
-  reason AS (
-    SELECT
-      thread_slice.id AS oom_adj_id,
-      thread_slice.ts AS oom_adj_ts,
-      thread_slice.dur AS oom_adj_dur,
-      thread_slice.track_id AS oom_adj_track_id,
-      thread_name AS oom_adj_thread_name,
-      str_split(thread_slice.name, '_', 1) AS oom_adj_reason,
-      slice.name AS oom_adj_trigger,
-      LEAD(thread_slice.ts) OVER (ORDER BY thread_slice.ts) AS oom_adj_next_ts
-    FROM thread_slice
-    LEFT JOIN slice ON slice.id = thread_slice.parent_id AND slice.dur != -1
-    WHERE thread_slice.name GLOB 'updateOomAdj_*' AND process_name = 'system_server'
-  )
 SELECT
   ts,
   dur,
-  value AS score,
-  android_oom_adj_score_to_bucket_name(value, android_appid) AS bucket,
-  process.upid,
-  process.name AS process_name,
-  reason.oom_adj_id,
-  reason.oom_adj_ts,
-  reason.oom_adj_dur,
-  reason.oom_adj_track_id,
-  reason.oom_adj_thread_name,
-  reason.oom_adj_reason,
-  reason.oom_adj_trigger
-FROM
-  counter_leading_intervals
-    !(
-      (
-        SELECT counter.*
-        FROM counter
-        JOIN counter_track track
-          ON track.id = counter.track_id AND track.name = 'oom_score_adj'
-      ))
-      counter
-JOIN process_counter_track track
-  ON counter.track_id = track.id
-JOIN process
-  USING (upid)
-LEFT JOIN reason
-  ON counter.ts BETWEEN oom_adj_ts AND COALESCE(oom_adj_next_ts, trace_end())
-WHERE track.name = 'oom_score_adj';
+  score,
+  android_oom_adj_score_to_bucket_name(score) AS bucket,
+  upid,
+  process_name,
+  oom_adj_id,
+  oom_adj_ts,
+  oom_adj_dur,
+  oom_adj_track_id,
+  oom_adj_thread_name,
+  oom_adj_reason,
+  oom_adj_trigger
+FROM _oom_adjuster_intervals;
diff --git a/src/trace_processor/perfetto_sql/stdlib/counters/intervals.sql b/src/trace_processor/perfetto_sql/stdlib/counters/intervals.sql
index 82427f7..7bb0c96 100644
--- a/src/trace_processor/perfetto_sql/stdlib/counters/intervals.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/counters/intervals.sql
@@ -62,7 +62,7 @@
     ts,
     track_id,
     LEAD(ts, 1, trace_end()) OVER(PARTITION BY track_id ORDER BY ts) - ts AS dur,
-    CAST(value AS INT) AS value
+    value
   FROM base
   WHERE value != lag_value OR lag_value IS NULL
 );
diff --git a/src/trace_processor/perfetto_sql/stdlib/cpu/freq.sql b/src/trace_processor/perfetto_sql/stdlib/cpu/freq.sql
index c0226bf..5dd2a48 100644
--- a/src/trace_processor/perfetto_sql/stdlib/cpu/freq.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/cpu/freq.sql
@@ -37,7 +37,7 @@
   count_w_dur.track_id,
   count_w_dur.ts,
   count_w_dur.dur,
-  count_w_dur.value as freq,
+  cast_int!(count_w_dur.value) as freq,
   cct.cpu
 FROM
 counter_leading_intervals!((
diff --git a/src/trace_processor/perfetto_sql/stdlib/cpu/idle.sql b/src/trace_processor/perfetto_sql/stdlib/cpu/idle.sql
index 7f837a2..703a913 100644
--- a/src/trace_processor/perfetto_sql/stdlib/cpu/idle.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/cpu/idle.sql
@@ -39,7 +39,7 @@
   count_w_dur.track_id,
   count_w_dur.ts,
   count_w_dur.dur,
-  IIF(count_w_dur.value = 4294967295, -1, count_w_dur.value) AS idle,
+  cast_int!(IIF(count_w_dur.value = 4294967295, -1, count_w_dur.value)) AS idle,
   cct.cpu
 FROM
 counter_leading_intervals!((
diff --git a/src/trace_processor/perfetto_sql/stdlib/gpu/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/gpu/BUILD.gn
new file mode 100644
index 0000000..a5f431d
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/gpu/BUILD.gn
@@ -0,0 +1,19 @@
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import("../../../../../gn/perfetto_sql.gni")
+
+perfetto_sql_source_set("gpu") {
+  sources = [ "frequency.sql" ]
+}
diff --git a/src/trace_processor/perfetto_sql/stdlib/gpu/frequency.sql b/src/trace_processor/perfetto_sql/stdlib/gpu/frequency.sql
new file mode 100644
index 0000000..2bd87b2
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/gpu/frequency.sql
@@ -0,0 +1,42 @@
+--
+-- Copyright 2024 The Android Open Source Project
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--     https://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+
+INCLUDE PERFETTO MODULE counters.intervals;
+
+-- GPU frequency counter per GPU.
+CREATE PERFETTO TABLE gpu_frequency(
+  -- Timestamp
+  ts INT,
+  -- Duration
+  dur INT,
+  -- GPU id. Joinable with `gpu_counter_track.gpu_id`.
+  gpu_id INT,
+  -- GPU frequency
+  gpu_freq INT
+) AS
+SELECT
+  ts,
+  dur,
+  gpu_id,
+  cast_int!(value) AS gpu_freq
+FROM counter_leading_intervals!((
+    SELECT c.*
+    FROM counter c
+    JOIN gpu_counter_track t
+    ON t.id = c.track_id AND t.name = 'gpufreq'
+    WHERE gpu_id IS NOT NULL
+))
+JOIN gpu_counter_track t ON t.id = track_id;
\ No newline at end of file
diff --git a/src/trace_processor/perfetto_sql/stdlib/memory/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/memory/BUILD.gn
index ae3ad17..ddc6419 100644
--- a/src/trace_processor/perfetto_sql/stdlib/memory/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/memory/BUILD.gn
@@ -15,5 +15,9 @@
 import("../../../../../gn/perfetto_sql.gni")
 
 perfetto_sql_source_set("memory") {
+  deps = [
+    "android",
+    "linux",
+  ]
   sources = [ "heap_graph_dominator_tree.sql" ]
 }
diff --git a/src/trace_processor/perfetto_sql/stdlib/memory/android/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/memory/android/BUILD.gn
new file mode 100644
index 0000000..55123b6
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/memory/android/BUILD.gn
@@ -0,0 +1,19 @@
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import("../../../../../../gn/perfetto_sql.gni")
+
+perfetto_sql_source_set("android") {
+  sources = [ "gpu.sql" ]
+}
diff --git a/src/trace_processor/perfetto_sql/stdlib/memory/android/gpu.sql b/src/trace_processor/perfetto_sql/stdlib/memory/android/gpu.sql
new file mode 100644
index 0000000..39abaec
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/memory/android/gpu.sql
@@ -0,0 +1,35 @@
+--
+-- Copyright 2024 The Android Open Source Project
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--     https://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+INCLUDE PERFETTO MODULE memory.linux.general;
+
+-- Counter for GPU memory per process with duration.
+CREATE PERFETTO TABLE memory_gpu_per_process(
+    -- Timestamp
+    ts INT,
+    -- Duration
+    dur INT,
+    -- Upid of the process
+    upid INT,
+    -- GPU memory
+    gpu_memory INT
+) AS
+SELECT
+  ts,
+  dur,
+  upid,
+  cast_int!(value) AS gpu_memory
+FROM _all_counters_per_process
+WHERE name = 'GPU Memory';
\ No newline at end of file
diff --git a/src/trace_processor/perfetto_sql/stdlib/memory/linux/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/memory/linux/BUILD.gn
new file mode 100644
index 0000000..0711197
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/memory/linux/BUILD.gn
@@ -0,0 +1,23 @@
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import("../../../../../../gn/perfetto_sql.gni")
+
+perfetto_sql_source_set("linux") {
+  sources = [
+    "general.sql",
+    "high_watermark.sql",
+    "process.sql",
+  ]
+}
diff --git a/src/trace_processor/perfetto_sql/stdlib/memory/linux/general.sql b/src/trace_processor/perfetto_sql/stdlib/memory/linux/general.sql
new file mode 100644
index 0000000..372cbf9
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/memory/linux/general.sql
@@ -0,0 +1,30 @@
+--
+-- Copyright 2024 The Android Open Source Project
+--
+-- Licensed under the Apache License, Version 2.0 (the 'License');
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--     https://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an 'AS IS' BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+CREATE PERFETTO VIEW _all_counters_per_process AS
+SELECT
+  ts,
+  LEAD(
+    ts, 1,
+    (SELECT COALESCE(end_ts, trace_end())
+    FROM process p WHERE p.upid = t.upid) + 1)
+    OVER (PARTITION BY track_id ORDER BY ts) - ts AS dur,
+  upid,
+  value,
+  track_id,
+  name
+FROM counter c JOIN process_counter_track t
+ON t.id = c.track_id
+WHERE upid IS NOT NULL;
\ No newline at end of file
diff --git a/src/trace_processor/perfetto_sql/stdlib/memory/linux/high_watermark.sql b/src/trace_processor/perfetto_sql/stdlib/memory/linux/high_watermark.sql
new file mode 100644
index 0000000..b01f2df
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/memory/linux/high_watermark.sql
@@ -0,0 +1,67 @@
+--
+-- Copyright 2024 The Android Open Source Project
+--
+-- Licensed under the Apache License, Version 2.0 (the 'License');
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--     https://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an 'AS IS' BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+INCLUDE PERFETTO MODULE memory.linux.process;
+INCLUDE PERFETTO MODULE counters.intervals;
+
+CREATE PERFETTO TABLE _memory_rss_high_watermark_per_process_table AS
+WITH with_rss AS (
+    SELECT
+        ts,
+        dur,
+        upid,
+        COALESCE(file_rss, 0) + COALESCE(anon_rss, 0) + COALESCE(shmem_rss, 0) AS rss
+    FROM _memory_rss_and_swap_per_process_table
+),
+high_watermark_as_counter AS (
+SELECT
+    ts,
+    MAX(rss) OVER (PARTITION BY upid ORDER BY ts) AS value,
+    -- `id` and `track_id` are hacks to use this table in
+    -- `counter_leading_intervals` macro. As `track_id` is using for looking
+    -- for duplicates, we are aliasing `upid` with it. `Id` is ignored by the macro.
+    upid AS track_id,
+    0 AS id
+FROM with_rss
+)
+SELECT ts, dur, track_id AS upid, cast_int!(value) AS rss_high_watermark
+FROM counter_leading_intervals!(high_watermark_as_counter);
+
+-- For each process fetches the memory high watermark until or during
+-- timestamp.
+CREATE PERFETTO VIEW memory_rss_high_watermark_per_process
+(
+    -- Timestamp
+    ts INT,
+    -- Duration
+    dur INT,
+    -- Upid of the process
+    upid INT,
+    -- Pid of the process
+    pid INT,
+    -- Name of the process
+    process_name STRING,
+    -- Maximum `rss` value until now
+    rss_high_watermark INT
+) AS
+SELECT
+    ts,
+    dur,
+    upid,
+    pid,
+    name AS process_name,
+    rss_high_watermark
+FROM _memory_rss_high_watermark_per_process_table
+JOIN process USING (upid);
\ No newline at end of file
diff --git a/src/trace_processor/perfetto_sql/stdlib/memory/linux/process.sql b/src/trace_processor/perfetto_sql/stdlib/memory/linux/process.sql
new file mode 100644
index 0000000..4601c21
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/memory/linux/process.sql
@@ -0,0 +1,217 @@
+--
+-- Copyright 2024 The Android Open Source Project
+--
+-- Licensed under the Apache License, Version 2.0 (the 'License');
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--     https://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an 'AS IS' BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+INCLUDE PERFETTO MODULE android.oom_adjuster;
+INCLUDE PERFETTO MODULE memory.linux.general;
+
+-- All memory counters tables.
+
+CREATE PERFETTO VIEW _anon_rss AS
+SELECT
+  ts,
+  dur,
+  upid,
+  value AS anon_rss_val
+FROM _all_counters_per_process
+WHERE name = 'mem.rss.anon';
+
+CREATE PERFETTO VIEW _file_rss AS
+SELECT
+  ts,
+  dur,
+  upid,
+  value AS file_rss_val
+FROM _all_counters_per_process
+WHERE name = 'mem.rss.file';
+
+CREATE PERFETTO VIEW _shmem_rss AS
+SELECT
+  ts,
+  dur,
+  upid,
+  value AS shmem_rss_val
+FROM _all_counters_per_process
+WHERE name = 'mem.rss.shmem';
+
+CREATE PERFETTO VIEW _swap AS
+SELECT
+  ts,
+  dur,
+  upid,
+  value AS swap_val
+FROM _all_counters_per_process
+WHERE name = 'mem.swap';
+
+-- Span joins
+
+CREATE VIRTUAL TABLE _anon_swap_sj
+USING SPAN_OUTER_JOIN(
+  _anon_rss PARTITIONED upid,
+  _swap PARTITIONED upid);
+
+CREATE VIRTUAL TABLE _anon_swap_file_sj
+USING SPAN_OUTER_JOIN(
+  _anon_swap_sj PARTITIONED upid,
+  _file_rss PARTITIONED upid
+);
+
+CREATE VIRTUAL TABLE _rss_swap_sj
+USING SPAN_OUTER_JOIN(
+  _anon_swap_file_sj PARTITIONED upid,
+  _shmem_rss PARTITIONED upid
+);
+
+CREATE PERFETTO TABLE _memory_rss_and_swap_per_process_table AS
+SELECT
+  ts, dur, upid,
+  cast_int!(anon_rss_val) AS anon_rss,
+  cast_int!(file_rss_val) AS file_rss,
+  cast_int!(shmem_rss_val) AS shmem_rss,
+  cast_int!(swap_val) AS swap
+FROM _rss_swap_sj;
+
+
+-- Memory metrics timeline for each process.
+CREATE PERFETTO VIEW memory_rss_and_swap_per_process(
+  -- Timestamp
+  ts INT,
+  -- Duration
+  dur INT,
+  -- Upid of the process
+  upid INT,
+  -- Pid of the process
+  pid INT,
+  -- Name of the process
+  process_name STRING,
+  -- Anon RSS counter value
+  anon_rss INT,
+  -- File RSS counter value
+  file_rss INT,
+  -- Shared memory RSS counter value
+  shmem_rss INT,
+  -- Total RSS value. Sum of `anon_rss`, `file_rss` and `shmem_rss`. Returns
+  -- value even if one of the values is NULL.
+  rss INT,
+  -- Swap counter value
+  swap INT,
+  -- Sum or `anon_rss` and `swap`. Returns value even if one of the values is
+  -- NULL.
+  anon_rss_and_swap INT,
+  -- Sum or `rss` and `swap`. Returns value even if one of the values is NULL.
+  rss_and_swap INT
+) AS
+SELECT
+  ts,
+  dur,
+  upid,
+  pid,
+  name AS process_name,
+  anon_rss,
+  file_rss,
+  shmem_rss,
+  -- We do COALESCE only on `shmem_rss` and `swap`, as it can be expected all
+  -- process start to emit anon rss and file rss events (you'll need to at
+  -- least read code and have some memory to work with) - so the NULLs are real
+  --  values. But it is possible that you will never swap or never use shmem,
+  -- so those values are expected to often be NULLs, which shouldn't propagate
+  -- into the values like `anon_and_swap` or `rss`.
+  file_rss + anon_rss + COALESCE(shmem_rss, 0) AS rss,
+  swap,
+  anon_rss + COALESCE(swap, 0) AS anon_rss_and_swap,
+  anon_rss + file_rss  + COALESCE(shmem_rss, 0) + COALESCE(swap, 0) AS rss_and_swap
+FROM _memory_rss_and_swap_per_process_table
+JOIN process USING (upid);
+
+-- OOM score tables
+
+CREATE VIRTUAL TABLE _mem_ooms_sj
+USING SPAN_OUTER_JOIN(
+  android_oom_adj_intervals PARTITIONED upid,
+  _memory_rss_and_swap_per_process_table PARTITIONED upid);
+
+-- Process memory and it's OOM adjuster scores. Detects transitions, each new
+-- interval means that either the memory or OOM adjuster score of the process changed.
+CREATE PERFETTO TABLE memory_oom_score_with_rss_and_swap_per_process(
+  -- Timestamp the oom_adj score or memory of the process changed
+  ts INT,
+  -- Duration until the next oom_adj score or memory change of the process.
+  dur INT,
+  -- oom adjuster score of the process.
+  score INT,
+  -- oom adjuster bucket of the process.
+  bucket STRING,
+  -- Upid of the process having an oom_adj update.
+  upid INT,
+  -- Name of the process having an oom_adj update.
+  process_name STRING,
+  -- Pid of the process having an oom_adj update.
+  pid INT,
+  -- Slice of the latest oom_adj update in the system_server. Alias of
+  -- `slice.id`.
+  oom_adj_id INT,
+  -- Timestamp of the latest oom_adj update in the system_server.
+  oom_adj_ts INT,
+  -- Duration of the latest oom_adj update in the system_server.
+  oom_adj_dur INT,
+  -- Track of the latest oom_adj update in the system_server. Alias of
+  -- `track.id`.
+  oom_adj_track_id INT,
+  -- Thread name of the latest oom_adj update in the system_server.
+  oom_adj_thread_name STRING,
+  -- Reason for the latest oom_adj update in the system_server.
+  oom_adj_reason STRING,
+  -- Trigger for the latest oom_adj update in the system_server.
+  oom_adj_trigger STRING,
+  -- Anon RSS counter value
+  anon_rss INT,
+  -- File RSS counter value
+  file_rss INT,
+  -- Shared memory RSS counter value
+  shmem_rss INT,
+  -- Total RSS value. Sum of `anon_rss`, `file_rss` and `shmem_rss`. Returns
+  -- value even if one of the values is NULL.
+  rss INT,
+  -- Swap counter value
+  swap INT,
+  -- Sum or `anon_rss` and `swap`. Returns value even if one of the values is
+  -- NULL.
+  anon_rss_and_swap INT,
+  -- Sum or `rss` and `swap`. Returns value even if one of the values is NULL.
+  rss_and_swap INT
+) AS
+SELECT
+  ts,
+  dur,
+  score,
+  bucket,
+  upid,
+  process_name,
+  pid,
+  oom_adj_id,
+  oom_adj_ts,
+  oom_adj_dur,
+  oom_adj_track_id,
+  oom_adj_thread_name,
+  oom_adj_reason,
+  oom_adj_trigger,
+  anon_rss,
+  file_rss,
+  shmem_rss,
+  file_rss + anon_rss + COALESCE(shmem_rss, 0) AS rss,
+  swap,
+  anon_rss + COALESCE(swap, 0) AS anon_rss_and_swap,
+  anon_rss + file_rss  + COALESCE(shmem_rss, 0) + COALESCE(swap, 0) AS rss_and_swap
+FROM _mem_ooms_sj
+JOIN process USING (upid);
diff --git a/src/trace_processor/perfetto_sql/stdlib/sched/thread_executing_span_with_slice.sql b/src/trace_processor/perfetto_sql/stdlib/sched/thread_executing_span_with_slice.sql
index 89085f3..74f73a5 100644
--- a/src/trace_processor/perfetto_sql/stdlib/sched/thread_executing_span_with_slice.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/sched/thread_executing_span_with_slice.sql
@@ -213,7 +213,7 @@
       self_thread_state_id AS id,
       ts,
       dur,
-      utid,
+      critical_path_utid AS utid,
       0 AS stack_depth,
       'thread_state: ' || self_state AS name,
       'thread_state' AS table_name,
@@ -225,7 +225,7 @@
       self_thread_state_id AS id,
       ts,
       dur,
-      utid,
+      critical_path_utid AS utid,
       1 AS stack_depth,
       IIF(self_state GLOB 'R*', NULL, 'kernel function: ' || self_function) AS name,
       'thread_state' AS table_name,
@@ -237,7 +237,7 @@
       self_thread_state_id AS id,
       ts,
       dur,
-      utid,
+      critical_path_utid AS utid,
       2 AS stack_depth,
       IIF(self_state GLOB 'R*', NULL, 'io_wait: ' || self_io_wait) AS name,
       'thread_state' AS table_name,
@@ -281,7 +281,7 @@
       anc.id,
       slice.ts,
       slice.dur,
-      slice.utid,
+      critical_path_utid AS utid,
       anc.depth + 5 AS stack_depth,
       IIF($enable_self_slice, anc.name, NULL) AS name,
       'slice' AS table_name,
@@ -294,7 +294,7 @@
       self_slice_id AS id,
       ts,
       dur,
-      utid,
+      critical_path_utid AS utid,
       self_slice_depth + 5 AS stack_depth,
       IIF($enable_self_slice, self_slice_name, NULL) AS name,
       'slice' AS table_name,
diff --git a/src/trace_processor/rpc/wasm_bridge.cc b/src/trace_processor/rpc/wasm_bridge.cc
index ca27abc..2473e4c 100644
--- a/src/trace_processor/rpc/wasm_bridge.cc
+++ b/src/trace_processor/rpc/wasm_bridge.cc
@@ -15,8 +15,13 @@
  */
 
 #include <emscripten/emscripten.h>
-#include <cstdint>
 
+#include <cstdint>
+#include <cstdio>
+#include <cstdlib>
+#include <new>
+
+#include "perfetto/base/compiler.h"
 #include "src/trace_processor/rpc/rpc.h"
 
 namespace perfetto::trace_processor {
@@ -29,6 +34,12 @@
 // The buffer used to pass the request arguments. The caller (JS) decides how
 // big this buffer should be in the Initialize() call.
 uint8_t* g_req_buf;
+
+PERFETTO_NO_INLINE void OutOfMemoryHandler() {
+  fprintf(stderr, "\nCannot enlarge memory\n");
+  abort();
+}
+
 }  // namespace
 
 // +---------------------------------------------------------------------------+
@@ -41,6 +52,13 @@
 trace_processor_rpc_init(RpcResponseFn* RpcResponseFn, uint32_t);
 uint8_t* trace_processor_rpc_init(RpcResponseFn* resp_function,
                                   uint32_t req_buffer_size) {
+  // Usually OOMs manifest as a failure in dlmalloc() -> sbrk() ->
+  //_emscripten_resize_heap() which aborts itself. However in some rare cases
+  // sbrk() can fail outside of _emscripten_resize_heap and just return null.
+  // When that happens, just abort with the same message that
+  // _emscripten_resize_heap uses, so error_dialog.ts shows a OOM message.
+  std::set_new_handler(&OutOfMemoryHandler);
+
   g_trace_processor_rpc = new Rpc();
 
   // |resp_function| is a JS-bound function passed by wasm_bridge.ts. It will
diff --git a/src/trace_processor/sorter/trace_sorter.cc b/src/trace_processor/sorter/trace_sorter.cc
index 6f0ded8..9ccea77 100644
--- a/src/trace_processor/sorter/trace_sorter.cc
+++ b/src/trace_processor/sorter/trace_sorter.cc
@@ -15,21 +15,28 @@
  */
 
 #include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <cstdlib>
+#include <cstring>
+#include <limits>
 #include <memory>
 #include <utility>
 
 #include "perfetto/base/compiler.h"
+#include "perfetto/base/logging.h"
+#include "perfetto/public/compiler.h"
 #include "src/trace_processor/importers/common/parser_types.h"
 #include "src/trace_processor/importers/common/trace_parser.h"
 #include "src/trace_processor/importers/fuchsia/fuchsia_record.h"
 #include "src/trace_processor/importers/perf/record.h"
 #include "src/trace_processor/sorter/trace_sorter.h"
-#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/sorter/trace_token_buffer.h"
+#include "src/trace_processor/storage/stats.h"
 #include "src/trace_processor/types/trace_processor_context.h"
 #include "src/trace_processor/util/bump_allocator.h"
 
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
 
 TraceSorter::TraceSorter(TraceProcessorContext* context,
                          SortingMode sorting_mode)
@@ -117,10 +124,12 @@
         auto& queue = sorter_data.queues[i];
         if (queue.events_.empty())
           continue;
-        all_queues_empty = false;
-
         PERFETTO_DCHECK(queue.max_ts_ <= append_max_ts_);
-        if (queue.min_ts_ < min_queue_ts[0]) {
+
+        // Checking for |all_queues_empty| is necessary here as in fuzzer cases
+        // we can end up with |int64::max()| as the value here.
+        // See https://crbug.com/oss-fuzz/69164 for an example.
+        if (all_queues_empty || queue.min_ts_ < min_queue_ts[0]) {
           min_queue_ts[1] = min_queue_ts[0];
           min_queue_ts[0] = queue.min_ts_;
           min_queue_idx = i;
@@ -128,6 +137,7 @@
         } else if (queue.min_ts_ < min_queue_ts[1]) {
           min_queue_ts[1] = queue.min_ts_;
         }
+        all_queues_empty = false;
       }
     }
     if (all_queues_empty)
@@ -276,10 +286,9 @@
     const TimestampedEvent& event) {
   TraceTokenBuffer::Id id = GetTokenBufferId(event);
   switch (static_cast<TimestampedEvent::Type>(event.event_type)) {
-    case TimestampedEvent::Type::kPerfRecord:
-      base::ignore_result(token_buffer_.Extract<perf_importer::Record>(id));
-      return;
     case TimestampedEvent::Type::kTracePacket:
+    case TimestampedEvent::Type::kFtraceEvent:
+    case TimestampedEvent::Type::kEtwEvent:
       base::ignore_result(token_buffer_.Extract<TracePacketData>(id));
       return;
     case TimestampedEvent::Type::kTrackEvent:
@@ -300,11 +309,8 @@
     case TimestampedEvent::Type::kInlineSchedWaking:
       base::ignore_result(token_buffer_.Extract<InlineSchedWaking>(id));
       return;
-    case TimestampedEvent::Type::kFtraceEvent:
-      base::ignore_result(token_buffer_.Extract<TracePacketData>(id));
-      return;
-    case TimestampedEvent::Type::kEtwEvent:
-      base::ignore_result(token_buffer_.Extract<TracePacketData>(id));
+    case TimestampedEvent::Type::kPerfRecord:
+      base::ignore_result(token_buffer_.Extract<perf_importer::Record>(id));
       return;
   }
   PERFETTO_FATAL("For GCC");
@@ -343,5 +349,4 @@
   }
 }
 
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/sorter/trace_sorter.h b/src/trace_processor/sorter/trace_sorter.h
index cf747f1..dd81920 100644
--- a/src/trace_processor/sorter/trace_sorter.h
+++ b/src/trace_processor/sorter/trace_sorter.h
@@ -18,16 +18,23 @@
 #define SRC_TRACE_PROCESSOR_SORTER_TRACE_SORTER_H_
 
 #include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <limits>
 #include <memory>
 #include <optional>
+#include <string>
+#include <tuple>
+#include <type_traits>
 #include <utility>
 #include <vector>
 
+#include "perfetto/base/logging.h"
 #include "perfetto/ext/base/circular_queue.h"
-#include "perfetto/ext/base/utils.h"
 #include "perfetto/public/compiler.h"
-#include "perfetto/trace_processor/basic_types.h"
+#include "perfetto/trace_processor/ref_counted.h"
 #include "perfetto/trace_processor/trace_blob_view.h"
+#include "src/trace_processor/importers/common/parser_types.h"
 #include "src/trace_processor/importers/common/trace_parser.h"
 #include "src/trace_processor/importers/fuchsia/fuchsia_record.h"
 #include "src/trace_processor/importers/perf/record.h"
@@ -37,8 +44,7 @@
 #include "src/trace_processor/types/trace_processor_context.h"
 #include "src/trace_processor/util/bump_allocator.h"
 
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
 
 // This class takes care of sorting events parsed from the trace stream in
 // arbitrary order and pushing them to the next pipeline stages (parsing) in
@@ -219,7 +225,7 @@
     SortAndExtractEventsUntilAllocId(end_id);
     for (auto& sorter_data : sorter_data_by_machine_) {
       for (const auto& queue : sorter_data.queues) {
-        PERFETTO_DCHECK(queue.events_.empty());
+        PERFETTO_CHECK(queue.events_.empty());
       }
       sorter_data.queues.clear();
     }
@@ -295,13 +301,13 @@
 
   static_assert(sizeof(TimestampedEvent) == 16,
                 "TimestampedEvent must be equal to 16 bytes");
-  static_assert(std::is_trivially_copyable<TimestampedEvent>::value,
+  static_assert(std::is_trivially_copyable_v<TimestampedEvent>,
                 "TimestampedEvent must be trivially copyable");
-  static_assert(std::is_trivially_move_assignable<TimestampedEvent>::value,
+  static_assert(std::is_trivially_move_assignable_v<TimestampedEvent>,
                 "TimestampedEvent must be trivially move assignable");
-  static_assert(std::is_trivially_move_constructible<TimestampedEvent>::value,
+  static_assert(std::is_trivially_move_constructible_v<TimestampedEvent>,
                 "TimestampedEvent must be trivially move constructible");
-  static_assert(std::is_nothrow_swappable<TimestampedEvent>::value,
+  static_assert(std::is_nothrow_swappable_v<TimestampedEvent>,
                 "TimestampedEvent must be trivially swappable");
 
   struct Queue {
@@ -358,7 +364,7 @@
     auto* queues = &sorter_data_by_machine_[0].queues;
 
     // Find the TraceSorterData instance when |machine_id| is not nullopt.
-    if (PERFETTO_UNLIKELY(!!machine_id)) {
+    if (PERFETTO_UNLIKELY(machine_id.has_value())) {
       auto it = std::find_if(sorter_data_by_machine_.begin() + 1,
                              sorter_data_by_machine_.end(),
                              [machine_id](const TraceSorterData& item) {
@@ -401,7 +407,7 @@
                          const TimestampedEvent&);
   void ExtractAndDiscardTokenizedObject(const TimestampedEvent& event);
 
-  TraceTokenBuffer::Id GetTokenBufferId(const TimestampedEvent& event) {
+  static TraceTokenBuffer::Id GetTokenBufferId(const TimestampedEvent& event) {
     return TraceTokenBuffer::Id{event.alloc_id()};
   }
 
@@ -448,7 +454,6 @@
   int64_t latest_pushed_event_ts_ = std::numeric_limits<int64_t>::min();
 };
 
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor
 
 #endif  // SRC_TRACE_PROCESSOR_SORTER_TRACE_SORTER_H_
diff --git a/src/trace_processor/tables/slice_tables.py b/src/trace_processor/tables/slice_tables.py
index 90c8176..1deca2e 100644
--- a/src/trace_processor/tables/slice_tables.py
+++ b/src/trace_processor/tables/slice_tables.py
@@ -135,6 +135,7 @@
         C('frame_id', CppOptional(CppUint32())),
         C('submission_id', CppOptional(CppUint32())),
         C('hw_queue_id', CppOptional(CppInt64())),
+        C('upid', CppOptional(CppUint32())),
         C('render_subpasses', CppString()),
     ],
     parent=SLICE_TABLE,
@@ -142,17 +143,33 @@
         doc='''''',
         group='Slice',
         columns={
-            'context_id': '''''',
-            'render_target': '''''',
-            'render_target_name': '''''',
-            'render_pass': '''''',
-            'render_pass_name': '''''',
-            'command_buffer': '''''',
-            'command_buffer_name': '''''',
-            'frame_id': '''''',
-            'submission_id': '''''',
-            'hw_queue_id': '''''',
-            'render_subpasses': ''''''
+            'context_id':
+                '''''',
+            'render_target':
+                '''''',
+            'render_target_name':
+                '''''',
+            'render_pass':
+                '''''',
+            'render_pass_name':
+                '''''',
+            'command_buffer':
+                '''''',
+            'command_buffer_name':
+                '''''',
+            'frame_id':
+                '''''',
+            'submission_id':
+                '''''',
+            'hw_queue_id':
+                '''''',
+            'upid':
+                '''
+                  Unique process id of the app that generates this gpu render
+                  stage event.
+                ''',
+            'render_subpasses':
+                ''''''
         }))
 
 GRAPHICS_FRAME_SLICE_TABLE = Table(
diff --git a/src/trace_processor/trace_database_integrationtest.cc b/src/trace_processor/trace_database_integrationtest.cc
index d09c8a7..731f4e3 100644
--- a/src/trace_processor/trace_database_integrationtest.cc
+++ b/src/trace_processor/trace_database_integrationtest.cc
@@ -14,17 +14,23 @@
  * limitations under the License.
  */
 
-#include <algorithm>
+#include <cstdint>
 #include <cstdio>
-#include <map>
-#include <optional>
+#include <cstring>
+#include <memory>
 #include <random>
 #include <string>
+#include <utility>
+#include <vector>
 
+#include "perfetto/base/build_config.h"
 #include "perfetto/base/logging.h"
+#include "perfetto/base/status.h"
 #include "perfetto/ext/base/scoped_file.h"
 #include "perfetto/ext/base/string_utils.h"
 #include "perfetto/trace_processor/basic_types.h"
+#include "perfetto/trace_processor/iterator.h"
+#include "perfetto/trace_processor/status.h"
 #include "perfetto/trace_processor/trace_processor.h"
 #include "protos/perfetto/common/descriptor.pbzero.h"
 #include "protos/perfetto/trace_processor/trace_processor.pbzero.h"
@@ -32,11 +38,12 @@
 #include "src/base/test/utils.h"
 #include "test/gtest_and_gmock.h"
 
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
 namespace {
 
-constexpr size_t kMaxChunkSize = 4 * 1024 * 1024;
+using testing::HasSubstr;
+
+constexpr size_t kMaxChunkSize = 4ul * 1024 * 1024;
 
 TEST(TraceProcessorCustomConfigTest, SkipInternalMetricsMatchingMountPath) {
   auto config = Config();
@@ -97,12 +104,13 @@
       : processor_(TraceProcessor::CreateInstance(Config())) {}
 
  protected:
-  util::Status LoadTrace(const char* name,
+  base::Status LoadTrace(const char* name,
                          size_t min_chunk_size = 512,
                          size_t max_chunk_size = kMaxChunkSize) {
     EXPECT_LE(min_chunk_size, max_chunk_size);
-    base::ScopedFstream f(fopen(
-        base::GetTestDataPath(std::string("test/data/") + name).c_str(), "rb"));
+    base::ScopedFstream f(
+        fopen(base::GetTestDataPath(std::string("test/data/") + name).c_str(),
+              "rbe"));
     std::minstd_rand0 rnd_engine(0);
     std::uniform_int_distribution<size_t> dist(min_chunk_size, max_chunk_size);
     while (!feof(*f)) {
@@ -118,7 +126,7 @@
   }
 
   Iterator Query(const std::string& query) {
-    return processor_->ExecuteQuery(query.c_str());
+    return processor_->ExecuteQuery(query);
   }
 
   TraceProcessor* Processor() { return processor_.get(); }
@@ -280,7 +288,7 @@
 
 TEST_F(TraceProcessorIntegrationTest, ComputeMetricsFormattedExtension) {
   std::string metric_output;
-  util::Status status = Processor()->ComputeMetricText(
+  base::Status status = Processor()->ComputeMetricText(
       std::vector<std::string>{"test_chrome_metric"},
       TraceProcessor::MetricResultFormat::kProtoText, &metric_output);
   ASSERT_TRUE(status.ok());
@@ -293,7 +301,7 @@
 
 TEST_F(TraceProcessorIntegrationTest, ComputeMetricsFormattedNoExtension) {
   std::string metric_output;
-  util::Status status = Processor()->ComputeMetricText(
+  base::Status status = Processor()->ComputeMetricText(
       std::vector<std::string>{"trace_metadata"},
       TraceProcessor::MetricResultFormat::kProtoText, &metric_output);
   ASSERT_TRUE(status.ok());
@@ -321,21 +329,21 @@
 }
 
 TEST_F(TraceProcessorIntegrationTest, Clusterfuzz14762) {
-  ASSERT_TRUE(LoadTrace("clusterfuzz_14762", 4096 * 1024).ok());
+  ASSERT_TRUE(LoadTrace("clusterfuzz_14762", 4096ul * 1024).ok());
   auto it = Query("select sum(value) from stats where severity = 'error';");
   ASSERT_TRUE(it.Next());
   ASSERT_GT(it.Get(0).long_value, 0);
 }
 
 TEST_F(TraceProcessorIntegrationTest, Clusterfuzz14767) {
-  ASSERT_TRUE(LoadTrace("clusterfuzz_14767", 4096 * 1024).ok());
+  ASSERT_TRUE(LoadTrace("clusterfuzz_14767", 4096ul * 1024).ok());
   auto it = Query("select sum(value) from stats where severity = 'error';");
   ASSERT_TRUE(it.Next());
   ASSERT_GT(it.Get(0).long_value, 0);
 }
 
 TEST_F(TraceProcessorIntegrationTest, Clusterfuzz14799) {
-  ASSERT_TRUE(LoadTrace("clusterfuzz_14799", 4096 * 1024).ok());
+  ASSERT_TRUE(LoadTrace("clusterfuzz_14799", 4096ul * 1024).ok());
   auto it = Query("select sum(value) from stats where severity = 'error';");
   ASSERT_TRUE(it.Next());
   ASSERT_GT(it.Get(0).long_value, 0);
@@ -793,6 +801,15 @@
   ASSERT_TRUE(it.Status().ok());
 }
 
+TEST_F(TraceProcessorIntegrationTest, CreateTableDuplicateNames) {
+  auto it = Query(
+      "create perfetto table foo select 1 as duplicate_a, 2 as duplicate_a, 3 "
+      "as duplicate_b, 4 as duplicate_b");
+  ASSERT_FALSE(it.Next());
+  ASSERT_FALSE(it.Status().ok());
+  ASSERT_THAT(it.Status().message(), HasSubstr("duplicate_a"));
+  ASSERT_THAT(it.Status().message(), HasSubstr("duplicate_b"));
+}
+
 }  // namespace
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index fb9b294..117a737 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -59,6 +59,7 @@
 #include "src/trace_processor/importers/proto/additional_modules.h"
 #include "src/trace_processor/importers/proto/content_analyzer.h"
 #include "src/trace_processor/importers/systrace/systrace_trace_parser.h"
+#include "src/trace_processor/importers/zip/zip_trace_reader.h"
 #include "src/trace_processor/iterator_impl.h"
 #include "src/trace_processor/metrics/all_chrome_metrics.descriptor.h"
 #include "src/trace_processor/metrics/all_webview_metrics.descriptor.h"
@@ -121,6 +122,7 @@
 #include "src/trace_processor/util/regex.h"
 #include "src/trace_processor/util/sql_modules.h"
 #include "src/trace_processor/util/status_macros.h"
+#include "src/trace_processor/util/trace_type.h"
 
 #include "protos/perfetto/common/builtin_clock.pbzero.h"
 #include "protos/perfetto/trace/clock_snapshot.pbzero.h"
@@ -308,8 +310,8 @@
       return "ctrace";
     case kNinjaLogTraceType:
       return "ninja_log";
-    case kAndroidBugreportTraceType:
-      return "android_bugreport";
+    case kZipFile:
+      return "zip";
     case kPerfDataTraceType:
       return "perf_data";
   }
@@ -362,8 +364,7 @@
         kGzipTraceType);
     context_.reader_registry->RegisterTraceReader<GzipTraceParser>(
         kCtraceTraceType);
-    context_.reader_registry->RegisterTraceReader<AndroidBugreportParser>(
-        kAndroidBugreportTraceType);
+    context_.reader_registry->RegisterTraceReader<ZipTraceReader>(kZipFile);
   }
 
   if (json::IsJsonSupported()) {
diff --git a/src/trace_processor/trace_processor_storage_impl.cc b/src/trace_processor/trace_processor_storage_impl.cc
index 243e3da..09d5386 100644
--- a/src/trace_processor/trace_processor_storage_impl.cc
+++ b/src/trace_processor/trace_processor_storage_impl.cc
@@ -37,6 +37,7 @@
 #include "src/trace_processor/importers/common/slice_translation_table.h"
 #include "src/trace_processor/importers/common/stack_profile_tracker.h"
 #include "src/trace_processor/importers/common/track_tracker.h"
+#include "src/trace_processor/importers/perf/dso_tracker.h"
 #include "src/trace_processor/importers/proto/chrome_track_event.descriptor.h"
 #include "src/trace_processor/importers/proto/default_modules.h"
 #include "src/trace_processor/importers/proto/packet_analyzer.h"
@@ -106,6 +107,8 @@
     return;
   Flush();
   context_.chunk_reader->NotifyEndOfFile();
+  // NotifyEndOfFile might have pushed packets to the sorter.
+  Flush();
   for (std::unique_ptr<ProtoImporterModule>& module : context_.modules) {
     module->NotifyEndOfFile();
   }
@@ -116,6 +119,9 @@
   context_.slice_tracker->FlushPendingSlices();
   context_.args_tracker->Flush();
   context_.process_tracker->NotifyEndOfFile();
+  if (context_.perf_dso_tracker) {
+    perf_importer::DsoTracker::GetOrCreate(&context_).SymbolizeFrames();
+  }
 }
 
 void TraceProcessorStorageImpl::DestroyContext() {
diff --git a/src/trace_processor/trace_reader_registry.cc b/src/trace_processor/trace_reader_registry.cc
index 09c5c7b..065f6f6 100644
--- a/src/trace_processor/trace_reader_registry.cc
+++ b/src/trace_processor/trace_reader_registry.cc
@@ -30,7 +30,7 @@
   switch (type) {
     case kGzipTraceType:
     case kCtraceTraceType:
-    case kAndroidBugreportTraceType:
+    case kZipFile:
       return true;
 
     case kNinjaLogTraceType:
diff --git a/src/trace_processor/types/trace_processor_context.h b/src/trace_processor/types/trace_processor_context.h
index fd72005..cbf8993 100644
--- a/src/trace_processor/types/trace_processor_context.h
+++ b/src/trace_processor/types/trace_processor_context.h
@@ -143,6 +143,7 @@
   std::unique_ptr<Destructible> ftrace_sched_tracker;      // FtraceSchedEventTracker
   std::unique_ptr<Destructible> v8_tracker;                // V8Tracker
   std::unique_ptr<Destructible> jit_tracker;               // JitTracker
+  std::unique_ptr<Destructible> perf_dso_tracker;          // DsoTracker
   // clang-format on
 
   std::unique_ptr<ProtoTraceParser> proto_trace_parser;
diff --git a/src/trace_processor/util/trace_type.cc b/src/trace_processor/util/trace_type.cc
index d265b90..ee5a56a 100644
--- a/src/trace_processor/util/trace_type.cc
+++ b/src/trace_processor/util/trace_type.cc
@@ -58,8 +58,8 @@
       return "gzip trace";
     case kCtraceTraceType:
       return "ctrace trace";
-    case kAndroidBugreportTraceType:
-      return "Android Bugreport";
+    case kZipFile:
+      return "ZIP file";
     case kPerfDataTraceType:
       return "perf data";
     case kUnknownTraceType:
@@ -124,12 +124,8 @@
   if (base::StartsWith(start, "\x0a"))
     return kProtoTraceType;
 
-  // Android bugreport.zip
-  // TODO(primiano). For now we assume any .zip file is a bugreport. In future,
-  // if we want to support different trace formats based on a .zip arachive we
-  // will need an extra layer similar to what we did kGzipTraceType.
   if (base::StartsWith(start, "PK\x03\x04"))
-    return kAndroidBugreportTraceType;
+    return kZipFile;
 
   return kUnknownTraceType;
 }
diff --git a/src/trace_processor/util/trace_type.h b/src/trace_processor/util/trace_type.h
index fec7a74..2d40d83 100644
--- a/src/trace_processor/util/trace_type.h
+++ b/src/trace_processor/util/trace_type.h
@@ -31,7 +31,7 @@
   kGzipTraceType,
   kCtraceTraceType,
   kNinjaLogTraceType,
-  kAndroidBugreportTraceType,
+  kZipFile,
   kPerfDataTraceType,
 };
 
diff --git a/src/trace_redaction/BUILD.gn b/src/trace_redaction/BUILD.gn
index 45d3bdc..ee876b0 100644
--- a/src/trace_redaction/BUILD.gn
+++ b/src/trace_redaction/BUILD.gn
@@ -59,14 +59,14 @@
     "prune_package_list.h",
     "redact_ftrace_event.cc",
     "redact_ftrace_event.h",
-    "redact_process_free.cc",
-    "redact_process_free.h",
     "redact_sched_switch.cc",
     "redact_sched_switch.h",
     "redact_task_newtask.cc",
     "redact_task_newtask.h",
     "remap_scheduling_events.cc",
     "remap_scheduling_events.h",
+    "remove_process_free_comm.cc",
+    "remove_process_free_comm.h",
     "scrub_ftrace_events.cc",
     "scrub_ftrace_events.h",
     "scrub_process_stats.cc",
@@ -81,6 +81,8 @@
     "trace_redaction_framework.h",
     "trace_redactor.cc",
     "trace_redactor.h",
+    "verify_integrity.cc",
+    "verify_integrity.h",
   ]
   deps = [
     "../../gn:default_deps",
@@ -141,10 +143,10 @@
     "process_thread_timeline_unittest.cc",
     "proto_util_unittest.cc",
     "prune_package_list_unittest.cc",
-    "redact_process_free_unittest.cc",
     "redact_sched_switch_unittest.cc",
     "redact_task_newtask_unittest.cc",
     "remap_scheduling_events_unittest.cc",
+    "remove_process_free_comm_unittest.cc",
     "suspend_resume_unittest.cc",
   ]
   deps = [
diff --git a/src/trace_redaction/main.cc b/src/trace_redaction/main.cc
index 7014c0d..798d1a0 100644
--- a/src/trace_redaction/main.cc
+++ b/src/trace_redaction/main.cc
@@ -28,10 +28,10 @@
 #include "src/trace_redaction/populate_allow_lists.h"
 #include "src/trace_redaction/prune_package_list.h"
 #include "src/trace_redaction/redact_ftrace_event.h"
-#include "src/trace_redaction/redact_process_free.h"
 #include "src/trace_redaction/redact_sched_switch.h"
 #include "src/trace_redaction/redact_task_newtask.h"
 #include "src/trace_redaction/remap_scheduling_events.h"
+#include "src/trace_redaction/remove_process_free_comm.h"
 #include "src/trace_redaction/scrub_ftrace_events.h"
 #include "src/trace_redaction/scrub_process_stats.h"
 #include "src/trace_redaction/scrub_process_trees.h"
@@ -39,6 +39,7 @@
 #include "src/trace_redaction/suspend_resume.h"
 #include "src/trace_redaction/trace_redaction_framework.h"
 #include "src/trace_redaction/trace_redactor.h"
+#include "src/trace_redaction/verify_integrity.h"
 
 namespace perfetto::trace_redaction {
 
@@ -48,6 +49,12 @@
                          std::string_view package_name) {
   TraceRedactor redactor;
 
+  // VerifyIntegrity breaks the CollectPrimitive pattern. Instead of writing to
+  // the context, its job is to read trace packets and return errors if any
+  // packet does not look "correct". This primitive is added first in an effort
+  // to detect and react to bad input before other collectors run.
+  redactor.emplace_collect<VerifyIntegrity>();
+
   // Add all collectors.
   redactor.emplace_collect<FindPackageUid>();
   redactor.emplace_collect<CollectTimelineEvents>();
@@ -84,9 +91,17 @@
 
   auto* redact_ftrace_events = redactor.emplace_transform<RedactFtraceEvent>();
   redact_ftrace_events
-      ->emplace_back<RedactTaskNewTask::kFieldId, RedactTaskNewTask>();
-  redact_ftrace_events
-      ->emplace_back<RedactProcessFree::kFieldId, RedactProcessFree>();
+      ->emplace_back<RemoveProcessFreeComm::kFieldId, RemoveProcessFreeComm>();
+
+  // By default, the comm value is cleared. However, when thread merging is
+  // enabled (kTaskNewtaskFieldNumber + ThreadMergeDropField), the event is
+  // dropped, meaning that this primitive was effectivly a no-op. This primitive
+  // remains so that removing thread merging won't leak thread names via new
+  // task events.
+  auto* redact_new_task =
+      redact_ftrace_events
+          ->emplace_back<RedactTaskNewTask::kFieldId, RedactTaskNewTask>();
+  redact_new_task->emplace_back<ClearComms>();
 
   // This set of transformations will change pids. This will break the
   // connections between pids and the timeline (the synth threads are not in the
diff --git a/src/trace_redaction/redact_ftrace_event.h b/src/trace_redaction/redact_ftrace_event.h
index 9de6cc6..68aff05 100644
--- a/src/trace_redaction/redact_ftrace_event.h
+++ b/src/trace_redaction/redact_ftrace_event.h
@@ -57,8 +57,12 @@
   // Add a new redaction. T must extend FtraceEventRedaction. This relies on the
   // honor system; no more than one redaction can be mapped to a field.
   template <uint32_t field_id, class T>
-  void emplace_back() {
-    redactions_.Insert(field_id, std::make_unique<T>());
+  T* emplace_back() {
+    auto ptr = std::make_unique<T>();
+    T* raw_ptr = ptr.get();
+
+    redactions_.Insert(field_id, std::move(ptr));
+    return raw_ptr;
   }
 
  private:
diff --git a/src/trace_redaction/redact_process_free.cc b/src/trace_redaction/redact_process_free.cc
deleted file mode 100644
index 8f5a83e..0000000
--- a/src/trace_redaction/redact_process_free.cc
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "src/trace_redaction/redact_process_free.h"
-
-#include "src/trace_redaction/proto_util.h"
-
-#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
-#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
-#include "protos/perfetto/trace/ftrace/sched.pbzero.h"
-
-namespace perfetto::trace_redaction {
-
-// Redact sched_process_free events.
-//
-//  event {
-//    timestamp: 6702094703928940
-//    pid: 10
-//    sched_process_free {
-//      comm: "sh"
-//      pid: 7973
-//      prio: 120
-//    }
-//  }
-//
-// In the above message, it should be noted that "event.pid" will not be
-// equal to "event.sched_process_free.pid".
-//
-// The timeline treats "start" as inclusive and "end" as exclusive. This means
-// no pid will connect to the target package at a process free event. Because
-// of this, the timeline is not needed.
-base::Status RedactProcessFree::Redact(
-    const Context&,
-    const protos::pbzero::FtraceEventBundle::Decoder&,
-    protozero::ProtoDecoder& event,
-    protos::pbzero::FtraceEvent* event_message) const {
-  auto sched_process_free = event.FindField(
-      protos::pbzero::FtraceEvent::kSchedProcessFreeFieldNumber);
-  if (!sched_process_free.valid()) {
-    return base::ErrStatus(
-        "RedactProcessFree: was used for unsupported field type");
-  }
-
-  // SchedProcessFreeFtraceEvent
-  protozero::ProtoDecoder process_free_decoder(sched_process_free.as_bytes());
-
-  auto* process_free_message = event_message->set_sched_process_free();
-
-  // Replace the comm with an empty string instead of dropping the comm field.
-  // The perfetto UI doesn't render things correctly if comm values are missing.
-  for (auto field = process_free_decoder.ReadField(); field.valid();
-       field = process_free_decoder.ReadField()) {
-    if (field.id() ==
-        protos::pbzero::SchedProcessFreeFtraceEvent::kCommFieldNumber) {
-      process_free_message->set_comm("");
-    } else {
-      proto_util::AppendField(field, process_free_message);
-    }
-  }
-
-  return base::OkStatus();
-}
-
-}  // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/redact_sched_switch.cc b/src/trace_redaction/redact_sched_switch.cc
index 87412c1..d2ce751 100644
--- a/src/trace_redaction/redact_sched_switch.cc
+++ b/src/trace_redaction/redact_sched_switch.cc
@@ -27,14 +27,62 @@
 namespace perfetto::trace_redaction {
 
 namespace {
-// TODO(vaage): While simple, this function saves us from declaring the sample
-// lambda each time we use the has_fields pattern. Once its usage increases, and
-// its value is obvious, remove this comment.
 bool IsTrue(bool value) {
   return value;
 }
+
+// Copy a field from 'decoder' to 'message' if the field can be found. Returns
+// false if the field cannot be found.
+bool Passthrough(protozero::ProtoDecoder& decoder,
+                 uint32_t field_id,
+                 protozero::Message* message) {
+  auto field = decoder.FindField(field_id);
+
+  if (field.valid()) {
+    proto_util::AppendField(field, message);
+    return true;
+  }
+
+  return false;
+}
 }  // namespace
 
+int64_t InternTable::Push(const char* data, size_t size) {
+  std::string_view outer(data, size);
+
+  for (size_t i = 0; i < interned_comms_.size(); ++i) {
+    auto view = interned_comms_[i];
+
+    if (view == outer) {
+      return static_cast<int64_t>(i);
+    }
+  }
+
+  // No room for the new string, reject the request.
+  if (comms_length_ + size > comms_.size()) {
+    return -1;
+  }
+
+  auto* head = comms_.data() + comms_length_;
+
+  // Important note, the null byte is not copied.
+  memcpy(head, data, size);
+  comms_length_ += size;
+
+  size_t id = interned_comms_.size();
+  interned_comms_.emplace_back(head, size);
+
+  return static_cast<int64_t>(id);
+}
+
+std::string_view InternTable::Find(size_t index) const {
+  if (index < interned_comms_.size()) {
+    return interned_comms_[index];
+  }
+
+  return {};
+}
+
 // Redact sched switch trace events in an ftrace event bundle:
 //
 //  event {
@@ -58,7 +106,7 @@
 // collection of ftrace event messages) because data in a sched_switch message
 // is needed in order to know if the event should be added to the bundle.
 
-SchedSwitchTransform::~SchedSwitchTransform() = default;
+RedactSchedSwitchHarness::Modifier::~Modifier() = default;
 
 base::Status RedactSchedSwitchHarness::Transform(const Context& context,
                                                  std::string* packet) const {
@@ -106,9 +154,10 @@
 
     if (field.id() ==
         protos::pbzero::FtraceEventBundle::kCompactSchedFieldNumber) {
-      // TODO(vaage): Replace this with logic specific to the comp sched data
-      // type.
-      proto_util::AppendField(field, message);
+      protos::pbzero::FtraceEventBundle::CompactSched::Decoder comp_sched(
+          field.as_bytes());
+      RETURN_IF_ERROR(TransformCompSched(context, cpu.as_int32(), comp_sched,
+                                         message->set_compact_sched()));
       continue;
     }
 
@@ -139,14 +188,29 @@
 
   for (auto field = decoder.ReadField(); field.valid();
        field = decoder.ReadField()) {
-    if (field.id() == protos::pbzero::FtraceEvent::kSchedSwitchFieldNumber) {
-      protos::pbzero::SchedSwitchFtraceEvent::Decoder sched_switch(
-          field.as_bytes());
-      RETURN_IF_ERROR(TransformFtraceEventSchedSwitch(
-          context, ts.as_uint64(), cpu, sched_switch, &scratch_str,
-          message->set_sched_switch()));
-    } else {
-      proto_util::AppendField(field, message);
+    switch (field.id()) {
+      case protos::pbzero::FtraceEvent::kSchedSwitchFieldNumber: {
+        protos::pbzero::SchedSwitchFtraceEvent::Decoder sched_switch(
+            field.as_bytes());
+        RETURN_IF_ERROR(TransformFtraceEventSchedSwitch(
+            context, ts.as_uint64(), cpu, sched_switch, &scratch_str,
+            message->set_sched_switch()));
+        break;
+      }
+
+      case protos::pbzero::FtraceEvent::kSchedWakingFieldNumber: {
+        protos::pbzero::SchedWakingFtraceEvent::Decoder sched_waking(
+            field.as_bytes());
+        RETURN_IF_ERROR(TransformFtraceEventSchedWaking(
+            context, ts.as_uint64(), cpu, sched_waking, &scratch_str,
+            message->set_sched_waking()));
+        break;
+      }
+
+      default: {
+        proto_util::AppendField(field, message);
+        break;
+      }
     }
   }
 
@@ -160,6 +224,10 @@
     protos::pbzero::SchedSwitchFtraceEvent::Decoder& sched_switch,
     std::string* scratch_str,
     protos::pbzero::SchedSwitchFtraceEvent* message) const {
+  PERFETTO_DCHECK(modifier_);
+  PERFETTO_DCHECK(scratch_str);
+  PERFETTO_DCHECK(message);
+
   auto has_fields = {
       sched_switch.has_prev_comm(), sched_switch.has_prev_pid(),
       sched_switch.has_prev_prio(), sched_switch.has_prev_state(),
@@ -184,10 +252,7 @@
 
   scratch_str->assign(prev_comm.data, prev_comm.size);
 
-  for (const auto& transform : transforms_) {
-    RETURN_IF_ERROR(
-        transform->Transform(context, ts, cpu, &prev_pid, scratch_str));
-  }
+  RETURN_IF_ERROR(modifier_->Modify(context, ts, cpu, &prev_pid, scratch_str));
 
   message->set_prev_comm(*scratch_str);                // FieldNumber = 1
   message->set_prev_pid(prev_pid);                     // FieldNumber = 2
@@ -196,10 +261,7 @@
 
   scratch_str->assign(next_comm.data, next_comm.size);
 
-  for (const auto& transform : transforms_) {
-    RETURN_IF_ERROR(
-        transform->Transform(context, ts, cpu, &next_pid, scratch_str));
-  }
+  RETURN_IF_ERROR(modifier_->Modify(context, ts, cpu, &next_pid, scratch_str));
 
   message->set_next_comm(*scratch_str);              // FieldNumber = 5
   message->set_next_pid(next_pid);                   // FieldNumber = 6
@@ -208,13 +270,225 @@
   return base::OkStatus();
 }
 
+base::Status RedactSchedSwitchHarness::TransformFtraceEventSchedWaking(
+    const Context& context,
+    uint64_t ts,
+    int32_t cpu,
+    protos::pbzero::SchedWakingFtraceEvent::Decoder& sched_waking,
+    std::string* scratch_str,
+    protos::pbzero::SchedWakingFtraceEvent* message) const {
+  PERFETTO_DCHECK(modifier_);
+  PERFETTO_DCHECK(scratch_str);
+  PERFETTO_DCHECK(message);
+
+  auto has_fields = {sched_waking.has_comm(), sched_waking.has_pid(),
+                     sched_waking.has_prio(), sched_waking.has_success(),
+                     sched_waking.has_target_cpu()};
+
+  if (!std::all_of(has_fields.begin(), has_fields.end(), IsTrue)) {
+    return base::ErrStatus(
+        "RedactSchedSwitchHarness: missing required SchedWakingFtraceEvent "
+        "field.");
+  }
+
+  auto pid = sched_waking.pid();
+  auto comm = sched_waking.comm();
+
+  // There are 5 values in a sched switch message. Since 2 of the 5 can be
+  // replaced, it is easier/cleaner to go value-by-value. Go in proto-defined
+  // order.
+
+  scratch_str->assign(comm.data, comm.size);
+
+  RETURN_IF_ERROR(modifier_->Modify(context, ts, cpu, &pid, scratch_str));
+
+  message->set_comm(*scratch_str);                     // FieldNumber = 1
+  message->set_pid(pid);                               // FieldNumber = 2
+  message->set_prio(sched_waking.prio());              // FieldNumber = 3
+  message->set_success(sched_waking.success());        // FieldNumber = 4
+  message->set_target_cpu(sched_waking.target_cpu());  // FieldNumber = 5
+
+  return base::OkStatus();
+}
+
+base::Status RedactSchedSwitchHarness::TransformCompSched(
+    const Context& context,
+    int32_t cpu,
+    protos::pbzero::FtraceEventBundle::CompactSched::Decoder& comp_sched,
+    protos::pbzero::FtraceEventBundle::CompactSched* message) const {
+  auto has_switch_fields = {
+      comp_sched.has_switch_timestamp(),
+      comp_sched.has_switch_prev_state(),
+      comp_sched.has_switch_next_pid(),
+      comp_sched.has_switch_next_prio(),
+      comp_sched.has_switch_next_comm_index(),
+  };
+
+  auto has_waking_fields = {
+      comp_sched.has_waking_timestamp(),  comp_sched.has_waking_pid(),
+      comp_sched.has_waking_target_cpu(), comp_sched.has_waking_prio(),
+      comp_sched.has_waking_comm_index(), comp_sched.has_waking_common_flags(),
+  };
+
+  // Populate the intern table once; it will be used by both sched and waking.
+  InternTable intern_table;
+
+  for (auto it = comp_sched.intern_table(); it; ++it) {
+    auto chars = it->as_string();
+    auto index = intern_table.Push(chars.data, chars.size);
+
+    if (index < 0) {
+      return base::ErrStatus(
+          "RedactSchedSwitchHarness: failed to insert string into intern "
+          "table.");
+    }
+  }
+
+  if (std::any_of(has_switch_fields.begin(), has_switch_fields.end(), IsTrue)) {
+    RETURN_IF_ERROR(TransformCompSchedSwitch(context, cpu, comp_sched,
+                                             &intern_table, message));
+  }
+
+  if (std::any_of(has_waking_fields.begin(), has_waking_fields.end(), IsTrue)) {
+    // TODO(vaage): Create and call TransformCompSchedWaking().
+  }
+
+  // IMPORTANT: The intern table can only be added after switch and waking
+  // because switch and/or waking can/will modify the intern table.
+  for (auto view : intern_table.values()) {
+    message->add_intern_table(view.data(), view.size());
+  }
+
+  return base::OkStatus();
+}
+
+base::Status RedactSchedSwitchHarness::TransformCompSchedSwitch(
+    const Context& context,
+    int32_t cpu,
+    protos::pbzero::FtraceEventBundle::CompactSched::Decoder& comp_sched,
+    InternTable* intern_table,
+    protos::pbzero::FtraceEventBundle::CompactSched* message) const {
+  PERFETTO_DCHECK(modifier_);
+  PERFETTO_DCHECK(message);
+
+  auto has_fields = {
+      comp_sched.has_intern_table(),
+      comp_sched.has_switch_timestamp(),
+      comp_sched.has_switch_prev_state(),
+      comp_sched.has_switch_next_pid(),
+      comp_sched.has_switch_next_prio(),
+      comp_sched.has_switch_next_comm_index(),
+  };
+
+  if (!std::all_of(has_fields.begin(), has_fields.end(), IsTrue)) {
+    return base::ErrStatus(
+        "RedactSchedSwitchHarness: missing required "
+        "FtraceEventBundle::CompactSched switch field.");
+  }
+
+  std::array<bool, 3> parse_errors = {false, false, false};
+
+  auto it_ts = comp_sched.switch_timestamp(&parse_errors.at(0));
+  auto it_pid = comp_sched.switch_next_pid(&parse_errors.at(1));
+  auto it_comm = comp_sched.switch_next_comm_index(&parse_errors.at(2));
+
+  if (std::any_of(parse_errors.begin(), parse_errors.end(), IsTrue)) {
+    return base::ErrStatus(
+        "RedactSchedSwitchHarness: failed to parse CompactSched.");
+  }
+
+  std::string scratch_str;
+
+  protozero::PackedVarInt packed_comm;
+  protozero::PackedVarInt packed_pid;
+
+  // The first it_ts value is an absolute value, all other values are delta
+  // values.
+  uint64_t ts = 0;
+
+  while (it_ts && it_pid && it_comm) {
+    ts += *it_ts;
+
+    auto pid = *it_pid;
+
+    auto comm_index = *it_comm;
+    auto comm = intern_table->Find(comm_index);
+
+    scratch_str.assign(comm);
+
+    RETURN_IF_ERROR(modifier_->Modify(context, ts, cpu, &pid, &scratch_str));
+
+    auto found = intern_table->Push(scratch_str.data(), scratch_str.size());
+
+    if (found < 0) {
+      return base::ErrStatus(
+          "RedactSchedSwitchHarness: failed to insert string into intern "
+          "table.");
+    }
+
+    packed_comm.Append(found);
+    packed_pid.Append(pid);
+
+    ++it_ts;
+    ++it_pid;
+    ++it_comm;
+  }
+
+  if (it_ts || it_pid || it_comm) {
+    return base::ErrStatus(
+        "RedactSchedSwitchHarness: uneven associative arrays in "
+        "FtraceEventBundle::CompactSched (switch).");
+  }
+
+  message->set_switch_next_pid(packed_pid);
+  message->set_switch_next_comm_index(packed_comm);
+
+  // There's a lot of data in a compact sched message. Most of it is packed data
+  // and most of the data is not going to change. To avoid unpacking, doing
+  // nothing, and then packing... cheat. Find the fields and pass them as opaque
+  // blobs.
+  //
+  // kInternTableFieldNumber:         The intern table will be modified by both
+  //                                  switch events and waking events. It will
+  //                                  be written elsewhere.
+  //
+  // kSwitchNextPidFieldNumber:       The switch pid will change during thread
+  //                                  merging.
+  //
+  // kSwitchNextCommIndexFieldNumber: The switch comm value will change when
+  //                                  clearing thread names and replaced
+  //                                  during thread merging.
+
+  auto passed_through = {
+      Passthrough(comp_sched,
+                  protos::pbzero::FtraceEventBundle::CompactSched::
+                      kSwitchTimestampFieldNumber,
+                  message),
+      Passthrough(comp_sched,
+                  protos::pbzero::FtraceEventBundle::CompactSched::
+                      kSwitchPrevStateFieldNumber,
+                  message),
+      Passthrough(comp_sched,
+                  protos::pbzero::FtraceEventBundle::CompactSched::
+                      kSwitchNextPrioFieldNumber,
+                  message)};
+
+  if (!std::all_of(passed_through.begin(), passed_through.end(), IsTrue)) {
+    return base::ErrStatus(
+        "RedactSchedSwitchHarness: missing required "
+        "FtraceEventBundle::CompactSched switch field.");
+  }
+
+  return base::OkStatus();
+}
+
 // Switch event transformation: Clear the comm value if the thread/process is
 // not part of the target packet.
-base::Status ClearComms::Transform(const Context& context,
-                                   uint64_t ts,
-                                   int32_t,
-                                   int32_t* pid,
-                                   std::string* comm) const {
+base::Status ClearComms::Modify(const Context& context,
+                                uint64_t ts,
+                                int32_t,
+                                int32_t* pid,
+                                std::string* comm) const {
   PERFETTO_DCHECK(pid);
   PERFETTO_DCHECK(comm);
 
diff --git a/src/trace_redaction/redact_sched_switch.h b/src/trace_redaction/redact_sched_switch.h
index 55f9a75..906eb11 100644
--- a/src/trace_redaction/redact_sched_switch.h
+++ b/src/trace_redaction/redact_sched_switch.h
@@ -17,31 +17,55 @@
 #ifndef SRC_TRACE_REDACTION_REDACT_SCHED_SWITCH_H_
 #define SRC_TRACE_REDACTION_REDACT_SCHED_SWITCH_H_
 
+#include "src/trace_redaction/trace_redaction_framework.h"
+
 #include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
 #include "protos/perfetto/trace/ftrace/sched.pbzero.h"
-#include "src/trace_redaction/trace_redaction_framework.h"
 
 namespace perfetto::trace_redaction {
 
-class SchedSwitchTransform {
+class InternTable {
  public:
-  virtual ~SchedSwitchTransform();
-  virtual base::Status Transform(const Context& context,
-                                 uint64_t ts,
-                                 int32_t cpu,
-                                 int32_t* pid,
-                                 std::string* comm) const = 0;
+  int64_t Push(const char* data, size_t size);
+
+  std::string_view Find(size_t index) const;
+
+  const std::vector<std::string_view>& values() const {
+    return interned_comms_;
+  }
+
+ private:
+  constexpr static size_t kExpectedCommLength = 16;
+  constexpr static size_t kMaxElements = 4096;
+
+  std::array<char, kMaxElements * kExpectedCommLength> comms_;
+  size_t comms_length_ = 0;
+
+  std::vector<std::string_view> interned_comms_;
 };
 
-// Goes through all sched switch events are modifies them.
+// TODO(vaage): Rename this class. When it was first created, it only handled
+// switch events, so having "switch" in the name sense. Now that it is
+// expanding to include waking events, a more general name is needed (e.g.
+// scheduling covers both switch and waking events).
 class RedactSchedSwitchHarness : public TransformPrimitive {
  public:
+  class Modifier {
+   public:
+    virtual ~Modifier();
+    virtual base::Status Modify(const Context& context,
+                                uint64_t ts,
+                                int32_t cpu,
+                                int32_t* pid,
+                                std::string* comm) const = 0;
+  };
+
   base::Status Transform(const Context& context,
                          std::string* packet) const override;
 
   template <class Transform>
   void emplace_transform() {
-    transforms_.emplace_back(new Transform());
+    modifier_ = std::make_unique<Transform>();
   }
 
  private:
@@ -65,15 +89,36 @@
       std::string* scratch_str,
       protos::pbzero::SchedSwitchFtraceEvent* message) const;
 
-  std::vector<std::unique_ptr<SchedSwitchTransform>> transforms_;
+  base::Status TransformFtraceEventSchedWaking(
+      const Context& context,
+      uint64_t ts,
+      int32_t cpu,
+      protos::pbzero::SchedWakingFtraceEvent::Decoder& sched_waking,
+      std::string* scratch_str,
+      protos::pbzero::SchedWakingFtraceEvent* message) const;
+
+  base::Status TransformCompSched(
+      const Context& context,
+      int32_t cpu,
+      protos::pbzero::FtraceEventBundle::CompactSched::Decoder& comp_sched,
+      protos::pbzero::FtraceEventBundle::CompactSched* message) const;
+
+  base::Status TransformCompSchedSwitch(
+      const Context& context,
+      int32_t cpu,
+      protos::pbzero::FtraceEventBundle::CompactSched::Decoder& comp_sched,
+      InternTable* intern_table,
+      protos::pbzero::FtraceEventBundle::CompactSched* message) const;
+
+  std::unique_ptr<Modifier> modifier_;
 };
 
-class ClearComms : public SchedSwitchTransform {
-  base::Status Transform(const Context& context,
-                         uint64_t ts,
-                         int32_t cpu,
-                         int32_t* pid,
-                         std::string* comm) const override;
+class ClearComms : public RedactSchedSwitchHarness::Modifier {
+  base::Status Modify(const Context& context,
+                      uint64_t ts,
+                      int32_t cpu,
+                      int32_t* pid,
+                      std::string* comm) const override;
 };
 
 }  // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/redact_sched_switch_unittest.cc b/src/trace_redaction/redact_sched_switch_unittest.cc
index 88280d7..cd05794 100644
--- a/src/trace_redaction/redact_sched_switch_unittest.cc
+++ b/src/trace_redaction/redact_sched_switch_unittest.cc
@@ -35,20 +35,40 @@
 constexpr int32_t kNoParent = 10;
 constexpr int32_t kPidA = 11;
 constexpr int32_t kPidB = 12;
+constexpr int32_t kPidC = 13;
 
 constexpr int32_t kCpuA = 0;
+constexpr int32_t kCpuB = 1;
+constexpr int32_t kCpuC = 2;
 
 constexpr uint64_t kFullStep = 1000;
 constexpr uint64_t kTimeA = 0;
 constexpr uint64_t kTimeB = kFullStep;
+constexpr uint64_t kTimeC = kFullStep * 2;
 
 constexpr auto kCommA = "comm-a";
 constexpr auto kCommB = "comm-b";
+constexpr auto kCommC = "comm-c";
 constexpr auto kCommNone = "";
 
+class ChangePidToMax : public RedactSchedSwitchHarness::Modifier {
+ public:
+  base::Status Modify(const Context& context,
+                      uint64_t ts,
+                      int32_t,
+                      int32_t* pid,
+                      std::string*) const override {
+    if (!context.timeline->PidConnectsToUid(ts, *pid, *context.package_uid)) {
+      *pid = std::numeric_limits<int32_t>::max();
+    }
+
+    return base::OkStatus();
+  }
+};
+
 }  // namespace
 
-class RedactSchedSwitchTest : public testing::Test {
+class RedactSchedSwitchFtraceEventTest : public testing::Test {
  protected:
   void SetUp() override {
     // Create a packet where two pids are swapping back-and-forth.
@@ -103,7 +123,7 @@
 
 // In this case, the target uid will be UID A. That means the comm values for
 // PID B should be removed, and the comm values for PID A should remain.
-TEST_F(RedactSchedSwitchTest, KeepsTargetCommValues) {
+TEST_F(RedactSchedSwitchFtraceEventTest, KeepsTargetCommValues) {
   RedactSchedSwitchHarness redact;
   redact.emplace_transform<ClearComms>();
 
@@ -137,7 +157,7 @@
 // This case is very similar to the "some are connected", expect that it
 // verifies all comm values will be removed when testing against an unused
 // uid.
-TEST_F(RedactSchedSwitchTest, RemovesAllCommsIfPackageDoesntExist) {
+TEST_F(RedactSchedSwitchFtraceEventTest, RemovesAllCommsIfPackageDoesntExist) {
   RedactSchedSwitchHarness redact;
   redact.emplace_transform<ClearComms>();
 
@@ -162,4 +182,347 @@
   ASSERT_EQ(events[1].sched_switch().next_comm(), kCommNone);
 }
 
+class RedactCompactSchedSwitchTest : public testing::Test {
+ protected:
+  void SetUp() override {
+    // PID A and PID B need to be attached to different packages (UID) so that
+    // its possible to include one but not the other.
+    context_.timeline = std::make_unique<ProcessThreadTimeline>();
+    context_.timeline->Append(
+        ProcessThreadTimeline::Event::Open(kTimeA, kPidA, kNoParent, kUidA));
+    context_.timeline->Append(
+        ProcessThreadTimeline::Event::Open(kTimeA, kPidB, kNoParent, kUidB));
+    context_.timeline->Sort();
+
+    auto* bundle = packet_.mutable_ftrace_events();
+    bundle->set_cpu(kCpuA);  // All switch events occur on this CPU
+
+    compact_sched = bundle->mutable_compact_sched();
+
+    compact_sched->add_intern_table(kCommA);
+    compact_sched->add_intern_table(kCommB);
+  }
+
+  void AddSwitchEvent(uint64_t ts,
+                      int32_t next_pid,
+                      int32_t prev_state,
+                      int32_t prio,
+                      uint32_t comm) {
+    compact_sched->add_switch_timestamp(ts);
+    compact_sched->add_switch_next_pid(next_pid);
+    compact_sched->add_switch_prev_state(prev_state);
+    compact_sched->add_switch_next_prio(prio);
+    compact_sched->add_switch_next_comm_index(comm);
+  }
+
+  protos::gen::TracePacket packet_;
+  protos::gen::FtraceEventBundle::CompactSched* compact_sched;
+
+  Context context_;
+};
+
+TEST_F(RedactCompactSchedSwitchTest, KeepsTargetCommValues) {
+  uint32_t kCommIndexA = 0;
+  uint32_t kCommIndexB = 1;
+
+  // The new entry will be appended to the table. Another primitive can be used
+  // to reduce the intern string table.
+  uint32_t kCommIndexNone = 2;
+
+  AddSwitchEvent(kTimeA, kPidA, 0, 0, kCommIndexA);
+  AddSwitchEvent(kTimeB, kPidB, 0, 0, kCommIndexB);
+
+  context_.package_uid = kUidA;
+
+  auto packet_buffer = packet_.SerializeAsString();
+
+  RedactSchedSwitchHarness redact;
+  redact.emplace_transform<ClearComms>();
+
+  ASSERT_OK(redact.Transform(context_, &packet_buffer));
+
+  protos::gen::TracePacket packet;
+  ASSERT_TRUE(packet.ParseFromString(packet_buffer));
+
+  const auto& bundle = packet.ftrace_events();
+  ASSERT_TRUE(bundle.has_compact_sched());
+
+  const auto& compact_sched = bundle.compact_sched();
+
+  // A new entry (empty string) should have been added to the table.
+  ASSERT_EQ(compact_sched.intern_table_size(), 3);
+  ASSERT_EQ(compact_sched.intern_table().back(), kCommNone);
+
+  ASSERT_EQ(compact_sched.switch_next_comm_index_size(), 2);
+  ASSERT_EQ(compact_sched.switch_next_comm_index().at(0), kCommIndexA);
+  ASSERT_EQ(compact_sched.switch_next_comm_index().at(1), kCommIndexNone);
+}
+
+// If two pids use the same comm, but one pid changes, the shared comm should
+// still be available.
+TEST_F(RedactCompactSchedSwitchTest, ChangingSharedCommonRetainsComm) {
+  uint32_t kCommIndexA = 0;
+
+  AddSwitchEvent(kTimeA, kPidA, 0, 0, kCommIndexA);
+  AddSwitchEvent(kTimeB, kPidB, 0, 0, kCommIndexA);
+
+  context_.package_uid = kUidA;
+
+  auto packet_buffer = packet_.SerializeAsString();
+
+  RedactSchedSwitchHarness redact;
+  redact.emplace_transform<ClearComms>();
+
+  ASSERT_OK(redact.Transform(context_, &packet_buffer));
+
+  protos::gen::TracePacket packet;
+  ASSERT_TRUE(packet.ParseFromString(packet_buffer));
+
+  const auto& bundle = packet.ftrace_events();
+  ASSERT_TRUE(bundle.has_compact_sched());
+
+  const auto& compact_sched = bundle.compact_sched();
+
+  // A new entry should have been appended, but comm A (previously shared)
+  // should still exist in the table.
+  ASSERT_EQ(compact_sched.intern_table_size(), 3);
+  ASSERT_EQ(compact_sched.intern_table().front(), kCommA);
+  ASSERT_EQ(compact_sched.intern_table().back(), kCommNone);
+}
+
+TEST_F(RedactCompactSchedSwitchTest, RemovesAllCommsIfPackageDoesntExist) {
+  uint32_t kCommIndexA = 0;
+  uint32_t kCommIndexB = 1;
+
+  // The new entry will be appended to the table. Another primitive can be used
+  // to reduce the intern string table.
+  uint32_t kCommIndexNone = 2;
+
+  AddSwitchEvent(kTimeA, kPidA, 0, 0, kCommIndexA);
+  AddSwitchEvent(kTimeB, kPidB, 0, 0, kCommIndexB);
+
+  context_.package_uid = kUidC;
+
+  auto packet_buffer = packet_.SerializeAsString();
+
+  RedactSchedSwitchHarness redact;
+  redact.emplace_transform<ClearComms>();
+
+  ASSERT_OK(redact.Transform(context_, &packet_buffer));
+
+  protos::gen::TracePacket packet;
+  ASSERT_TRUE(packet.ParseFromString(packet_buffer));
+
+  const auto& bundle = packet.ftrace_events();
+  ASSERT_TRUE(bundle.has_compact_sched());
+
+  const auto& compact_sched = bundle.compact_sched();
+
+  // A new entry (empty string) should have been added to the table.
+  ASSERT_EQ(compact_sched.intern_table_size(), 3);
+  ASSERT_EQ(compact_sched.intern_table().back(), kCommNone);
+
+  ASSERT_EQ(compact_sched.switch_next_comm_index_size(), 2);
+  ASSERT_EQ(compact_sched.switch_next_comm_index().at(0), kCommIndexNone);
+  ASSERT_EQ(compact_sched.switch_next_comm_index().at(1), kCommIndexNone);
+}
+
+TEST_F(RedactCompactSchedSwitchTest, CanChangePid) {
+  uint32_t kCommIndexA = 0;
+  uint32_t kCommIndexB = 1;
+
+  AddSwitchEvent(kTimeA, kPidA, 0, 0, kCommIndexA);
+  AddSwitchEvent(kTimeB, kPidB, 0, 0, kCommIndexB);
+
+  // Because the target is package A, PidA should be remain. PidB should change.
+  context_.package_uid = kUidA;
+
+  auto packet_buffer = packet_.SerializeAsString();
+
+  RedactSchedSwitchHarness redact;
+  redact.emplace_transform<ChangePidToMax>();
+
+  ASSERT_OK(redact.Transform(context_, &packet_buffer));
+
+  protos::gen::TracePacket packet;
+  ASSERT_TRUE(packet.ParseFromString(packet_buffer));
+
+  const auto& bundle = packet.ftrace_events();
+  ASSERT_TRUE(bundle.has_compact_sched());
+
+  const auto& compact_sched = bundle.compact_sched();
+
+  // The intern table should not change.
+  ASSERT_EQ(compact_sched.intern_table_size(), 2);
+
+  ASSERT_EQ(compact_sched.switch_next_pid_size(), 2);
+  ASSERT_EQ(compact_sched.switch_next_pid().at(0), kPidA);
+  ASSERT_NE(compact_sched.switch_next_pid().at(1), kPidB);
+}
+
+class RedactSchedWakingFtraceEventTest : public testing::Test {
+ protected:
+  void SetUp() override {
+    // Create a packet where two pids are swapping back-and-forth.
+    auto* bundle = packet_.mutable_ftrace_events();
+    bundle->set_cpu(kCpuA);
+
+    // Pid A wakes up Pid B at time Time B
+    {
+      auto* event = bundle->add_event();
+
+      event->set_timestamp(kTimeB);
+      event->set_pid(kPidA);
+
+      auto* sched_waking = event->mutable_sched_waking();
+      sched_waking->set_comm(kCommB);
+      sched_waking->set_pid(kPidB);
+      sched_waking->set_prio(0);
+      sched_waking->set_success(true);
+      sched_waking->set_target_cpu(kCpuB);
+    }
+
+    // Pid A wakes up Pid C at time Time C.
+    {
+      auto* event = bundle->add_event();
+
+      event->set_timestamp(kTimeC);
+      event->set_pid(kPidA);
+
+      auto* sched_waking = event->mutable_sched_waking();
+      sched_waking->set_comm(kCommC);
+      sched_waking->set_pid(kPidC);
+      sched_waking->set_prio(0);
+      sched_waking->set_success(true);
+      sched_waking->set_target_cpu(kCpuC);
+    }
+
+    // PID A and PID B need to be attached to different packages (UID) so that
+    // its possible to include one but not the other.
+    context_.timeline = std::make_unique<ProcessThreadTimeline>();
+    context_.timeline->Append(
+        ProcessThreadTimeline::Event::Open(kTimeA, kPidA, kNoParent, kUidA));
+    context_.timeline->Append(
+        ProcessThreadTimeline::Event::Open(kTimeA, kPidB, kNoParent, kUidB));
+    context_.timeline->Append(
+        ProcessThreadTimeline::Event::Open(kTimeA, kPidC, kNoParent, kUidC));
+    context_.timeline->Sort();
+  }
+
+  protos::gen::TracePacket packet_;
+  Context context_;
+};
+
+TEST_F(RedactSchedWakingFtraceEventTest, WakeeKeepsCommWhenConnectedToPackage) {
+  RedactSchedSwitchHarness redact;
+  redact.emplace_transform<ClearComms>();
+
+  context_.package_uid = kUidB;
+
+  auto packet_buffer = packet_.SerializeAsString();
+
+  ASSERT_OK(redact.Transform(context_, &packet_buffer));
+
+  protos::gen::TracePacket packet;
+  ASSERT_TRUE(packet.ParseFromString(packet_buffer));
+
+  const auto& bundle = packet.ftrace_events();
+  const auto& events = bundle.event();
+
+  ASSERT_EQ(events.size(), 2u);
+
+  ASSERT_EQ(events.front().sched_waking().comm(), kCommB);
+  ASSERT_EQ(events.back().sched_waking().comm(), kCommNone);
+}
+
+TEST_F(RedactSchedWakingFtraceEventTest,
+       WakeeLosesCommWhenNotConnectedToPackage) {
+  RedactSchedSwitchHarness redact;
+  redact.emplace_transform<ClearComms>();
+
+  context_.package_uid = kUidA;
+
+  auto packet_buffer = packet_.SerializeAsString();
+
+  ASSERT_OK(redact.Transform(context_, &packet_buffer));
+
+  protos::gen::TracePacket packet;
+  ASSERT_TRUE(packet.ParseFromString(packet_buffer));
+
+  const auto& bundle = packet.ftrace_events();
+  const auto& events = bundle.event();
+
+  ASSERT_EQ(events.size(), 2u);
+
+  ASSERT_EQ(events.front().sched_waking().comm(), kCommNone);
+  ASSERT_EQ(events.back().sched_waking().comm(), kCommNone);
+}
+
+TEST_F(RedactSchedWakingFtraceEventTest, WakeeKeepsPidWhenConnectedToPackage) {
+  RedactSchedSwitchHarness redact;
+  redact.emplace_transform<ChangePidToMax>();
+
+  context_.package_uid = kUidB;
+
+  auto packet_buffer = packet_.SerializeAsString();
+
+  ASSERT_OK(redact.Transform(context_, &packet_buffer));
+
+  protos::gen::TracePacket packet;
+  ASSERT_TRUE(packet.ParseFromString(packet_buffer));
+
+  const auto& bundle = packet.ftrace_events();
+  const auto& events = bundle.event();
+
+  ASSERT_EQ(events.size(), 2u);
+
+  ASSERT_EQ(events.front().sched_waking().pid(), kPidB);
+  ASSERT_NE(events.back().sched_waking().pid(), kPidC);
+}
+
+TEST_F(RedactSchedWakingFtraceEventTest,
+       WakeeLosesPidWhenNotConnectedToPackage) {
+  RedactSchedSwitchHarness redact;
+  redact.emplace_transform<ChangePidToMax>();
+
+  context_.package_uid = kUidA;
+
+  auto packet_buffer = packet_.SerializeAsString();
+
+  ASSERT_OK(redact.Transform(context_, &packet_buffer));
+
+  protos::gen::TracePacket packet;
+  ASSERT_TRUE(packet.ParseFromString(packet_buffer));
+
+  const auto& bundle = packet.ftrace_events();
+  const auto& events = bundle.event();
+
+  ASSERT_EQ(events.size(), 2u);
+
+  ASSERT_NE(events.front().sched_waking().pid(), kPidB);
+  ASSERT_NE(events.back().sched_waking().pid(), kPidC);
+}
+
+TEST_F(RedactSchedWakingFtraceEventTest, WakerPidIsLeftUnaffected) {
+  RedactSchedSwitchHarness redact;
+  redact.emplace_transform<ChangePidToMax>();
+
+  context_.package_uid = kUidB;
+
+  auto packet_buffer = packet_.SerializeAsString();
+
+  ASSERT_OK(redact.Transform(context_, &packet_buffer));
+
+  protos::gen::TracePacket packet;
+  ASSERT_TRUE(packet.ParseFromString(packet_buffer));
+
+  const auto& bundle = packet.ftrace_events();
+  const auto& events = bundle.event();
+
+  ASSERT_EQ(events.size(), 2u);
+
+  ASSERT_EQ(events.front().pid(), static_cast<uint32_t>(kPidA));
+  ASSERT_EQ(events.back().pid(), static_cast<uint32_t>(kPidA));
+}
+
 }  // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/redact_task_newtask.cc b/src/trace_redaction/redact_task_newtask.cc
index fb0cfe8..0286df2 100644
--- a/src/trace_redaction/redact_task_newtask.cc
+++ b/src/trace_redaction/redact_task_newtask.cc
@@ -16,28 +16,15 @@
 
 #include "src/trace_redaction/redact_task_newtask.h"
 
-#include "src/trace_redaction/proto_util.h"
-
 #include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
 #include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
 #include "protos/perfetto/trace/ftrace/task.pbzero.h"
+#include "src/trace_processor/util/status_macros.h"
 
 namespace perfetto::trace_redaction {
 
 namespace {
 
-// TODO(vaage): Merge with RedactComm in redact_sched_switch.cc.
-protozero::ConstChars RedactComm(const Context& context,
-                                 uint64_t ts,
-                                 int32_t pid,
-                                 protozero::ConstChars comm) {
-  if (context.timeline->PidConnectsToUid(ts, pid, *context.package_uid)) {
-    return comm;
-  }
-
-  return {};
-}
-
 }  // namespace
 // Redact sched switch trace events in an ftrace event bundle:
 //
@@ -56,9 +43,11 @@
 // equal to "event.task_newtask.pid" (a thread cannot start itself).
 base::Status RedactTaskNewTask::Redact(
     const Context& context,
-    const protos::pbzero::FtraceEventBundle::Decoder&,
+    const protos::pbzero::FtraceEventBundle::Decoder& bundle,
     protozero::ProtoDecoder& event,
     protos::pbzero::FtraceEvent* event_message) const {
+  PERFETTO_DCHECK(modifier_);
+
   if (!context.package_uid.has_value()) {
     return base::ErrStatus("RedactTaskNewTask: missing package uid");
   }
@@ -68,45 +57,41 @@
   }
 
   // The timestamp is needed to do the timeline look-up. If the packet has no
-  // timestamp, don't add the sched switch event. This is the safest option.
-  auto timestamp =
+  // timestamp, don't add the sched switch event.
+  auto timestamp_field =
       event.FindField(protos::pbzero::FtraceEvent::kTimestampFieldNumber);
-  if (!timestamp.valid()) {
-    return base::OkStatus();
-  }
-
-  auto new_task =
+  auto new_task_field =
       event.FindField(protos::pbzero::FtraceEvent::kTaskNewtaskFieldNumber);
-  if (!new_task.valid()) {
+
+  if (!timestamp_field.valid() || !new_task_field.valid()) {
     return base::ErrStatus(
-        "RedactTaskNewTask: was used for unsupported field type");
+        "RedactTaskNewTask: missing required FtraceEvent field.");
   }
 
-  protozero::ProtoDecoder new_task_decoder(new_task.as_bytes());
+  protos::pbzero::TaskNewtaskFtraceEvent::Decoder new_task(
+      new_task_field.as_bytes());
 
-  auto pid = new_task_decoder.FindField(
-      protos::pbzero::TaskNewtaskFtraceEvent::kPidFieldNumber);
-
-  if (!pid.valid()) {
-    return base::OkStatus();
+  // There are only four fields in a new task event. Since two of them can
+  // change, it is easier to work with them directly.
+  if (!new_task.has_pid() || !new_task.has_comm() ||
+      !new_task.has_clone_flags() || !new_task.has_oom_score_adj()) {
+    return base::ErrStatus(
+        "RedactTaskNewTask: missing required TaskNewtaskFtraceEvent field.");
   }
 
-  // Avoid making the message until we know that we have prev and next pids.
+  auto pid = new_task.pid();
+  auto comm = new_task.comm().ToStdString();
+
+  auto cpu = static_cast<int32_t>(bundle.cpu());
+
+  RETURN_IF_ERROR(modifier_->Modify(context, timestamp_field.as_uint64(), cpu,
+                                    &pid, &comm));
+
   auto* new_task_message = event_message->set_task_newtask();
-
-  for (auto field = new_task_decoder.ReadField(); field.valid();
-       field = new_task_decoder.ReadField()) {
-    // Perfetto view (ui.perfetto.dev) crashes if the comm value is missing.
-    // To work around this, the comm value is replaced with an empty string.
-    // This appears to work.
-    if (field.id() ==
-        protos::pbzero::TaskNewtaskFtraceEvent::kCommFieldNumber) {
-      new_task_message->set_comm(RedactComm(context, timestamp.as_uint64(),
-                                            pid.as_int32(), field.as_string()));
-    } else {
-      proto_util::AppendField(field, new_task_message);
-    }
-  }
+  new_task_message->set_pid(pid);
+  new_task_message->set_comm(comm);
+  new_task_message->set_clone_flags(new_task.clone_flags());
+  new_task_message->set_oom_score_adj(new_task.oom_score_adj());
 
   return base::OkStatus();
 }
diff --git a/src/trace_redaction/redact_task_newtask.h b/src/trace_redaction/redact_task_newtask.h
index 51c1124..61c8b52 100644
--- a/src/trace_redaction/redact_task_newtask.h
+++ b/src/trace_redaction/redact_task_newtask.h
@@ -18,6 +18,7 @@
 #define SRC_TRACE_REDACTION_REDACT_TASK_NEWTASK_H_
 
 #include "src/trace_redaction/redact_ftrace_event.h"
+#include "src/trace_redaction/redact_sched_switch.h"
 #include "src/trace_redaction/trace_redaction_framework.h"
 
 namespace perfetto::trace_redaction {
@@ -34,6 +35,14 @@
       const protos::pbzero::FtraceEventBundle::Decoder& bundle,
       protozero::ProtoDecoder& event,
       protos::pbzero::FtraceEvent* event_message) const override;
+
+  template <class Modifier>
+  void emplace_back() {
+    modifier_ = std::make_unique<Modifier>();
+  }
+
+ public:
+  std::unique_ptr<RedactSchedSwitchHarness::Modifier> modifier_;
 };
 
 }  // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/redact_task_newtask_unittest.cc b/src/trace_redaction/redact_task_newtask_unittest.cc
index 33886f6..11b1405 100644
--- a/src/trace_redaction/redact_task_newtask_unittest.cc
+++ b/src/trace_redaction/redact_task_newtask_unittest.cc
@@ -16,19 +16,21 @@
 
 #include "src/trace_redaction/redact_task_newtask.h"
 #include "perfetto/protozero/scattered_heap_buffer.h"
-#include "protos/perfetto/trace/ftrace/task.gen.h"
+#include "src/base/test/status_matchers.h"
 #include "test/gtest_and_gmock.h"
 
 #include "protos/perfetto/trace/ftrace/ftrace_event.gen.h"
 #include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
 #include "protos/perfetto/trace/ftrace/ftrace_event_bundle.gen.h"
-#include "protos/perfetto/trace/ftrace/sched.gen.h"
+#include "protos/perfetto/trace/ftrace/task.gen.h"
 #include "protos/perfetto/trace/trace.gen.h"
 #include "protos/perfetto/trace/trace_packet.gen.h"
 
 namespace perfetto::trace_redaction {
 
 namespace {
+constexpr uint64_t kCpu = 1;
+
 constexpr uint64_t kUidA = 1;
 constexpr uint64_t kUidB = 2;
 
@@ -44,19 +46,23 @@
 class RedactTaskNewTaskTest : public testing::Test {
  protected:
   void SetUp() override {
-    auto* event = bundle_.add_event();
+    bundle_.set_cpu(kCpu);
 
+    auto* event = bundle_.add_event();
     event->set_timestamp(123456789);
     event->set_pid(kPidA);
 
     auto* new_task = event->mutable_task_newtask();
+    new_task->set_clone_flags(0);
     new_task->set_comm(std::string(kCommA));
+    new_task->set_oom_score_adj(0);
     new_task->set_pid(kPidA);
   }
 
   base::Status Redact(const Context& context,
                       protos::pbzero::FtraceEvent* event_message) {
     RedactTaskNewTask redact;
+    redact.emplace_back<ClearComms>();
 
     auto bundle_str = bundle_.SerializeAsString();
     protos::pbzero::FtraceEventBundle::Decoder bundle_decoder(bundle_str);
@@ -93,8 +99,6 @@
 };
 
 TEST_F(RedactTaskNewTaskTest, RejectMissingPackageUid) {
-  RedactTaskNewTask redact;
-
   Context context;
   context.timeline = std::make_unique<ProcessThreadTimeline>();
 
@@ -106,8 +110,6 @@
 }
 
 TEST_F(RedactTaskNewTaskTest, RejectMissingTimeline) {
-  RedactTaskNewTask redact;
-
   Context context;
   context.package_uid = kUidA;
 
@@ -119,8 +121,6 @@
 }
 
 TEST_F(RedactTaskNewTaskTest, PidInPackageKeepsComm) {
-  RedactTaskNewTask redact;
-
   // Because Uid A is the target, when Pid A starts (new task event), it should
   // keep its comm value.
   Context context;
@@ -130,8 +130,7 @@
   protos::pbzero::FtraceEvent::Decoder event_decoder(event_string());
   protozero::HeapBuffered<protos::pbzero::FtraceEvent> event_message;
 
-  auto result = Redact(context, event_message.get());
-  ASSERT_TRUE(result.ok());
+  ASSERT_OK(Redact(context, event_message.get()));
 
   protos::gen::FtraceEvent redacted_event;
   redacted_event.ParseFromString(event_message.SerializeAsString());
@@ -142,8 +141,6 @@
 }
 
 TEST_F(RedactTaskNewTaskTest, PidOutsidePackageLosesComm) {
-  RedactTaskNewTask redact;
-
   // Because Uid B is the target, when Pid A starts (new task event), it should
   // lose its comm value.
   Context context;
@@ -153,8 +150,7 @@
   protos::pbzero::FtraceEvent::Decoder event_decoder(event_string());
   protozero::HeapBuffered<protos::pbzero::FtraceEvent> event_message;
 
-  auto result = Redact(context, event_message.get());
-  ASSERT_TRUE(result.ok());
+  ASSERT_OK(Redact(context, event_message.get()));
 
   protos::gen::FtraceEvent redacted_event;
   redacted_event.ParseFromString(event_message.SerializeAsString());
diff --git a/src/trace_redaction/remove_process_free_comm.cc b/src/trace_redaction/remove_process_free_comm.cc
new file mode 100644
index 0000000..265df79
--- /dev/null
+++ b/src/trace_redaction/remove_process_free_comm.cc
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_redaction/remove_process_free_comm.h"
+
+#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
+#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
+#include "protos/perfetto/trace/ftrace/sched.pbzero.h"
+
+namespace perfetto::trace_redaction {
+
+base::Status RemoveProcessFreeComm::Redact(
+    const Context&,
+    const protos::pbzero::FtraceEventBundle::Decoder&,
+    protozero::ProtoDecoder& event,
+    protos::pbzero::FtraceEvent* event_message) const {
+  auto sched_process_free = event.FindField(
+      protos::pbzero::FtraceEvent::kSchedProcessFreeFieldNumber);
+  if (!sched_process_free.valid()) {
+    return base::ErrStatus("RemoveProcessFreeComm: missing required field.");
+  }
+
+  // SchedProcessFreeFtraceEvent
+  protozero::ProtoDecoder decoder(sched_process_free.as_bytes());
+
+  auto pid_field = decoder.FindField(
+    protos::pbzero::SchedProcessFreeFtraceEvent::kPidFieldNumber);
+  auto prio_field = decoder.FindField(
+    protos::pbzero::SchedProcessFreeFtraceEvent::kPrioFieldNumber);
+
+  if (!pid_field.valid() || !prio_field.valid()) {
+    return base::ErrStatus("RemoveProcessFreeComm: missing required field.");
+  }
+
+  auto* message = event_message->set_sched_process_free();
+
+  // Replace the comm with an empty string instead of dropping the comm field.
+  // The perfetto UI doesn't render things correctly if comm values are missing.
+  message->set_comm("");
+  message->set_pid(pid_field.as_int32());
+  message->set_prio(prio_field.as_int32());
+
+  return base::OkStatus();
+}
+
+}  // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/redact_process_free.h b/src/trace_redaction/remove_process_free_comm.h
similarity index 65%
rename from src/trace_redaction/redact_process_free.h
rename to src/trace_redaction/remove_process_free_comm.h
index 19e954c..f3dc0d9 100644
--- a/src/trace_redaction/redact_process_free.h
+++ b/src/trace_redaction/remove_process_free_comm.h
@@ -14,17 +14,20 @@
  * limitations under the License.
  */
 
-#ifndef SRC_TRACE_REDACTION_REDACT_PROCESS_FREE_H_
-#define SRC_TRACE_REDACTION_REDACT_PROCESS_FREE_H_
+#ifndef SRC_TRACE_REDACTION_REMOVE_PROCESS_FREE_COMM_H_
+#define SRC_TRACE_REDACTION_REMOVE_PROCESS_FREE_COMM_H_
 
 #include "src/trace_redaction/redact_ftrace_event.h"
 #include "src/trace_redaction/trace_redaction_framework.h"
 
 namespace perfetto::trace_redaction {
 
-// Goes through ftrace events and conditonally removes the comm values from
-// process free events.
-class RedactProcessFree : public FtraceEventRedaction {
+// Process free events will always end up testing positive for "remove comm
+// value", so instead of running the test, always remove the comm value. We know
+// it would fail the test because process free events create close events on the
+// timeline. Because the timeline uses exclusive ends, the event's time will
+// never call within the range it belongs to.
+class RemoveProcessFreeComm : public FtraceEventRedaction {
  public:
   static constexpr auto kFieldId =
       protos::pbzero::FtraceEvent::kSchedProcessFreeFieldNumber;
@@ -38,4 +41,4 @@
 
 }  // namespace perfetto::trace_redaction
 
-#endif  // SRC_TRACE_REDACTION_REDACT_PROCESS_FREE_H_
+#endif  // SRC_TRACE_REDACTION_REMOVE_PROCESS_FREE_COMM_H_
diff --git a/src/trace_redaction/redact_process_free_unittest.cc b/src/trace_redaction/remove_process_free_comm_unittest.cc
similarity index 66%
rename from src/trace_redaction/redact_process_free_unittest.cc
rename to src/trace_redaction/remove_process_free_comm_unittest.cc
index 04064ae..5999a4a 100644
--- a/src/trace_redaction/redact_process_free_unittest.cc
+++ b/src/trace_redaction/remove_process_free_comm_unittest.cc
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
-#include "src/trace_redaction/redact_process_free.h"
+#include "src/trace_redaction/remove_process_free_comm.h"
 #include "perfetto/protozero/scattered_heap_buffer.h"
+#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
 #include "src/base/test/status_matchers.h"
 #include "src/trace_redaction/trace_redaction_framework.h"
 #include "test/gtest_and_gmock.h"
@@ -29,16 +30,18 @@
 
 namespace perfetto::trace_redaction {
 
-class RedactProcessFreeTest : public testing::Test {
+class RemoveProcessFreeCommTest : public testing::Test {
  protected:
   void SetUp() override {
     auto* source_event = bundle.add_event();
     source_event->set_timestamp(123456789);
     source_event->set_pid(10);
+
+    process_free = source_event->mutable_sched_process_free();
   }
 
   base::Status Redact(protos::pbzero::FtraceEvent* event_message) {
-    RedactProcessFree redact;
+    RemoveProcessFreeComm redact;
     Context context;
 
     auto bundle_str = bundle.SerializeAsString();
@@ -51,42 +54,19 @@
   }
 
   protos::gen::FtraceEventBundle bundle;
+  protos::gen::SchedProcessFreeFtraceEvent* process_free;
 };
 
 // A free event will always test as "not active". So the comm value should
 // always be replaced with an empty string.
-TEST_F(RedactProcessFreeTest, ClearsCommValue) {
-  auto* process_free =
-      bundle.mutable_event()->back().mutable_sched_process_free();
+TEST_F(RemoveProcessFreeCommTest, ClearsCommValue) {
   process_free->set_comm("comm-a");
   process_free->set_pid(11);
+  process_free->set_prio(0);
 
   protozero::HeapBuffered<protos::pbzero::FtraceEvent> event_message;
 
-  auto result = Redact(event_message.get());
-  ASSERT_OK(result) << result.c_message();
-
-  protos::gen::FtraceEvent redacted_event;
-  redacted_event.ParseFromString(event_message.SerializeAsString());
-
-  // No process free event should have been added to the ftrace event.
-  ASSERT_TRUE(redacted_event.has_sched_process_free());
-  ASSERT_TRUE(redacted_event.sched_process_free().has_comm());
-  ASSERT_TRUE(redacted_event.sched_process_free().comm().empty());
-}
-
-// Even if there is no pid in the process free event, the comm value should be
-// replaced with an empty string.
-TEST_F(RedactProcessFreeTest, NoPidClearsEvent) {
-  // Don't add a pid. This should have no change in behaviour.
-  auto* process_free =
-      bundle.mutable_event()->back().mutable_sched_process_free();
-  process_free->set_comm("comm-a");
-
-  protozero::HeapBuffered<protos::pbzero::FtraceEvent> event_message;
-
-  auto result = Redact(event_message.get());
-  ASSERT_OK(result) << result.c_message();
+  ASSERT_OK(Redact(event_message.get()));
 
   protos::gen::FtraceEvent redacted_event;
   redacted_event.ParseFromString(event_message.SerializeAsString());
diff --git a/src/trace_redaction/verify_integrity.cc b/src/trace_redaction/verify_integrity.cc
new file mode 100644
index 0000000..a2d58ce
--- /dev/null
+++ b/src/trace_redaction/verify_integrity.cc
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Projectf
+ *
+ * Licensed under the Apache License, Version 2.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_redaction/verify_integrity.h"
+
+#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
+#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+#include "src/trace_processor/util/status_macros.h"
+
+namespace perfetto::trace_redaction {
+namespace {
+// All constants are from
+// "system/core/libcutils/include/private/android_filesystem_config.h"
+//
+// AID 1000 == system (those are probably frame_timeline packets you will see
+// those on) AID 9999 is nobody == traced/traced_probes
+constexpr int32_t kAidSystem = 1000;
+constexpr int32_t kAidNobody = 9999;
+}  // namespace
+
+base::Status VerifyIntegrity::Collect(
+    const protos::pbzero::TracePacket::Decoder& packet,
+    Context*) const {
+  if (!packet.has_trusted_uid()) {
+    return base::ErrStatus(
+        "VerifyIntegrity: missing field (TracePacket.trusted_uid).");
+  }
+
+  if (packet.trusted_uid() != kAidSystem &&
+      packet.trusted_uid() != kAidNobody) {
+    return base::ErrStatus(
+        "VerifyIntegrity: invalid field value (TracePacket.trusted_uid).");
+  }
+
+  if (packet.has_ftrace_events()) {
+    protos::pbzero::FtraceEventBundle::Decoder ftrace_events(
+        packet.ftrace_events());
+
+    // The other clocks in ftrace are only used on very old kernel versions. No
+    // device with V should have such an old version. As a failsafe though,
+    // check that the ftrace_clock field is unset to ensure no invalid
+    // timestamps get by.
+    if (ftrace_events.has_ftrace_clock()) {
+      return base::ErrStatus(
+          "VerifyIntegrity: unexpected field "
+          "(FtraceEventBundle::kFtraceClockFieldNumber).");
+    }
+
+    // Every ftrace event bundle should have a CPU field. This is necessary for
+    // switch/waking redaction to work.
+    if (!ftrace_events.has_cpu()) {
+      return base::ErrStatus(
+          "VerifyIntegrity: missing field "
+          "(FtraceEventBundle::kCpuFieldNumber).");
+    }
+
+    RETURN_IF_ERROR(VerifyFtraceEventsTime(ftrace_events));
+  }
+
+  return base::OkStatus();
+}
+
+base::Status VerifyIntegrity::VerifyFtraceEventsTime(
+    const protos::pbzero::FtraceEventBundle::Decoder& bundle) const {
+  // If a bundle has ftrace events, the events will contain the time stamps.
+  // However, there are no ftrace events, the timestamp will be in the bundle.
+  if (!bundle.has_event() && !bundle.has_ftrace_timestamp()) {
+    return base::ErrStatus(
+        "VerifyIntegrity: missing field "
+        "(FtraceEventBundle::kFtraceTimestampFieldNumber).");
+  }
+
+  for (auto event_buffer = bundle.event(); event_buffer; ++event_buffer) {
+    protos::pbzero::FtraceEvent::Decoder event(*event_buffer);
+
+    if (!protozero::ProtoDecoder(*event_buffer)
+             .FindField(protos::pbzero::FtraceEvent::kTimestampFieldNumber)
+             .valid()) {
+      return base::ErrStatus(
+          "VerifyIntegrity: missing field "
+          "(FtraceEvent::kTimestampFieldNumber)");
+    }
+  }
+
+  return base::OkStatus();
+}
+
+}  // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/verify_integrity.h b/src/trace_redaction/verify_integrity.h
new file mode 100644
index 0000000..6548ce5
--- /dev/null
+++ b/src/trace_redaction/verify_integrity.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
+#include "src/trace_redaction/trace_redaction_framework.h"
+
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+
+#ifndef SRC_TRACE_REDACTION_VERIFY_INTEGRITY_H_
+#define SRC_TRACE_REDACTION_VERIFY_INTEGRITY_H_
+
+namespace perfetto::trace_redaction {
+
+// This breaks the normal collect primitive pattern. Rather than collecting
+// information, it looks at packets and returns an error if the packet violates
+// any requirements.
+class VerifyIntegrity : public CollectPrimitive {
+ public:
+  base::Status Collect(const protos::pbzero::TracePacket::Decoder& packet,
+                       Context* context) const override;
+
+ private:
+  base::Status VerifyFtraceEventsTime(
+      const protos::pbzero::FtraceEventBundle::Decoder& bundle) const;
+};
+
+}  // namespace perfetto::trace_redaction
+
+#endif  // SRC_TRACE_REDACTION_VERIFY_INTEGRITY_H_
diff --git a/src/traceconv/symbolize_profile.cc b/src/traceconv/symbolize_profile.cc
index 00cc7b9..4efaa10 100644
--- a/src/traceconv/symbolize_profile.cc
+++ b/src/traceconv/symbolize_profile.cc
@@ -55,12 +55,12 @@
     PERFETTO_FATAL("Failed to read trace.");
 
   tp->Flush();
+  tp->NotifyEndOfFile();
 
   SymbolizeDatabase(
       tp.get(), symbolizer.get(),
       [output](const std::string& trace_proto) { *output << trace_proto; });
 
-  tp->NotifyEndOfFile();
   return 0;
 }
 
diff --git a/src/traced/probes/ftrace/cpu_reader.cc b/src/traced/probes/ftrace/cpu_reader.cc
index 66b9b1b..4045978 100644
--- a/src/traced/probes/ftrace/cpu_reader.cc
+++ b/src/traced/probes/ftrace/cpu_reader.cc
@@ -869,6 +869,9 @@
     case kDevId64ToUint64:
       ReadDevId<uint64_t>(field_start, field_id, message, metadata);
       return true;
+    case kFtraceSymAddr32ToUint64:
+      ReadSymbolAddr<uint32_t>(field_start, field_id, message, metadata);
+      return true;
     case kFtraceSymAddr64ToUint64:
       ReadSymbolAddr<uint64_t>(field_start, field_id, message, metadata);
       return true;
diff --git a/src/traced/probes/ftrace/event_info_constants.cc b/src/traced/probes/ftrace/event_info_constants.cc
index 37f9c77..4a0d08f 100644
--- a/src/traced/probes/ftrace/event_info_constants.cc
+++ b/src/traced/probes/ftrace/event_info_constants.cc
@@ -106,6 +106,8 @@
     *out = kBoolToUint64;
   } else if (ftrace == kFtraceDataLoc && proto == ProtoSchemaType::kString) {
     *out = kDataLocToString;
+  } else if (ftrace == kFtraceSymAddr32 && proto == ProtoSchemaType::kUint64) {
+    *out = kFtraceSymAddr32ToUint64;
   } else if (ftrace == kFtraceSymAddr64 && proto == ProtoSchemaType::kUint64) {
     *out = kFtraceSymAddr64ToUint64;
   } else {
diff --git a/src/traced/probes/ftrace/event_info_constants.h b/src/traced/probes/ftrace/event_info_constants.h
index 0283f12..cce144f 100644
--- a/src/traced/probes/ftrace/event_info_constants.h
+++ b/src/traced/probes/ftrace/event_info_constants.h
@@ -48,6 +48,7 @@
   kFtraceDevId32,
   kFtraceDevId64,
   kFtraceDataLoc,
+  kFtraceSymAddr32,
   kFtraceSymAddr64,
 };
 
@@ -84,6 +85,7 @@
   kDevId32ToUint64,
   kDevId64ToUint64,
   kDataLocToString,
+  kFtraceSymAddr32ToUint64,
   kFtraceSymAddr64ToUint64,
 };
 
@@ -127,6 +129,7 @@
       return "devid64";
     case kFtraceDataLoc:
       return "__data_loc";
+    case kFtraceSymAddr32:
     case kFtraceSymAddr64:
       return "void*";
     case kInvalidFtraceFieldType:
diff --git a/src/traced/probes/ftrace/proto_translation_table.cc b/src/traced/probes/ftrace/proto_translation_table.cc
index 9dcde14..7e9092e 100644
--- a/src/traced/probes/ftrace/proto_translation_table.cc
+++ b/src/traced/probes/ftrace/proto_translation_table.cc
@@ -239,6 +239,7 @@
     case kFtraceUint64:
     case kFtraceInode32:
     case kFtraceInode64:
+    case kFtraceSymAddr32:
     case kFtraceSymAddr64:
       *proto_type = ProtoSchemaType::kUint64;
       *proto_field_id = GenericFtraceEvent::Field::kUintValueFieldNumber;
@@ -310,13 +311,16 @@
     return true;
   }
 
-  // Kernel addresses that need symbolization via kallsyms. Only 64-bit kernels
-  // are supported for now. 32-bit kernels seems to be going away.
-  if ((base::StartsWith(type_and_name, "void*") ||
-       base::StartsWith(type_and_name, "void *")) &&
-      size == 8) {
-    *out = kFtraceSymAddr64;
-    return true;
+  // Kernel addresses that need symbolization via kallsyms.
+  if (base::StartsWith(type_and_name, "void*") ||
+      base::StartsWith(type_and_name, "void *")) {
+    if (size == 4) {
+      *out = kFtraceSymAddr32;
+      return true;
+    } else if (size == 8) {
+      *out = kFtraceSymAddr64;
+      return true;
+    }
   }
 
   // Variable length strings: "char foo" + size: 0 (as in 'print').
diff --git a/src/tracing/service/tracing_service_impl.cc b/src/tracing/service/tracing_service_impl.cc
index d667767..6631d68 100644
--- a/src/tracing/service/tracing_service_impl.cc
+++ b/src/tracing/service/tracing_service_impl.cc
@@ -24,6 +24,7 @@
 #include <limits>
 #include <optional>
 #include <regex>
+#include <string>
 #include <unordered_set>
 #include "perfetto/base/time.h"
 #include "perfetto/ext/tracing/core/client_identity.h"
@@ -1689,13 +1690,14 @@
               tracing_session.config, tracing_session.trace_uuid,
               PerfettoStatsdAtom::kTracedTriggerCloneSnapshot, iter->name());
           task_runner_->PostDelayedTask(
-              [weak_this, tsid] {
+              [weak_this, tsid, trigger_name = iter->name()] {
                 if (!weak_this)
                   return;
                 auto* tsess = weak_this->GetTracingSession(tsid);
                 if (!tsess || !tsess->consumer_maybe_null)
                   return;
-                tsess->consumer_maybe_null->NotifyCloneSnapshotTrigger();
+                tsess->consumer_maybe_null->NotifyCloneSnapshotTrigger(
+                    trigger_name);
               },
               iter->stop_delay_ms());
           break;
@@ -4265,13 +4267,15 @@
   observable_events->set_all_data_sources_started(true);
 }
 
-void TracingServiceImpl::ConsumerEndpointImpl::NotifyCloneSnapshotTrigger() {
+void TracingServiceImpl::ConsumerEndpointImpl::NotifyCloneSnapshotTrigger(
+    const std::string& trigger_name) {
   if (!(observable_events_mask_ & ObservableEvents::TYPE_CLONE_TRIGGER_HIT)) {
     return;
   }
   auto* observable_events = AddObservableEvents();
   auto* clone_trig = observable_events->mutable_clone_trigger_hit();
   clone_trig->set_tracing_session_id(static_cast<int64_t>(tracing_session_id_));
+  clone_trig->set_trigger_name(trigger_name);
 }
 
 ObservableEvents*
diff --git a/src/tracing/service/tracing_service_impl.h b/src/tracing/service/tracing_service_impl.h
index 9fa84fe..1dbcbe6 100644
--- a/src/tracing/service/tracing_service_impl.h
+++ b/src/tracing/service/tracing_service_impl.h
@@ -32,6 +32,7 @@
 #include "perfetto/base/time.h"
 #include "perfetto/ext/base/circular_queue.h"
 #include "perfetto/ext/base/periodic_task.h"
+#include "perfetto/ext/base/string_view.h"
 #include "perfetto/ext/base/uuid.h"
 #include "perfetto/ext/base/weak_ptr.h"
 #include "perfetto/ext/tracing/core/basic_types.h"
@@ -211,7 +212,7 @@
     ~ConsumerEndpointImpl() override;
 
     void NotifyOnTracingDisabled(const std::string& error);
-    void NotifyCloneSnapshotTrigger();
+    void NotifyCloneSnapshotTrigger(const std::string& trigger_name);
 
     // TracingService::ConsumerEndpoint implementation.
     void EnableTracing(const TraceConfig&, base::ScopedFile) override;
diff --git a/test/.gitignore b/test/.gitignore
index 8280df0..47a77bd 100644
--- a/test/.gitignore
+++ b/test/.gitignore
@@ -18,3 +18,11 @@
 /data/parser/*
 !/data/parser/*.sha256
 !/data/parser/README.md
+
+!/data/simpleperf
+/data/simpleperf/*
+!/data/simpleperf/*.sha256
+
+!/data/zip
+/data/zip/*
+!/data/zip/*.sha256
\ No newline at end of file
diff --git a/test/data/simpleperf/perf.data.sha256 b/test/data/simpleperf/perf.data.sha256
new file mode 100644
index 0000000..8e90588
--- /dev/null
+++ b/test/data/simpleperf/perf.data.sha256
@@ -0,0 +1 @@
+cb3066f4050d84d3e204a37ca4c479113b7623b663c17a3ee8cae5a85b8238bf
\ No newline at end of file
diff --git a/test/data/simpleperf/perf_with_add_counter.data.sha256 b/test/data/simpleperf/perf_with_add_counter.data.sha256
new file mode 100644
index 0000000..06c8a91
--- /dev/null
+++ b/test/data/simpleperf/perf_with_add_counter.data.sha256
@@ -0,0 +1 @@
+f3f44439a4add389b13510d9bb9ed2a151dc6d0a1ee8a7f5c52a369d96c9f0eb
\ No newline at end of file
diff --git a/test/data/zip/perf_track_sym.zip.sha256 b/test/data/zip/perf_track_sym.zip.sha256
new file mode 100644
index 0000000..53dfcb3
--- /dev/null
+++ b/test/data/zip/perf_track_sym.zip.sha256
@@ -0,0 +1 @@
+146b1d8f48743323e91fd63d6ab5a1f6d8fcca8c5ea8455ebe8f087667a23c3f
\ No newline at end of file
diff --git a/test/trace_processor/diff_tests/include_index.py b/test/trace_processor/diff_tests/include_index.py
index 45c972f..e70dfa3 100644
--- a/test/trace_processor/diff_tests/include_index.py
+++ b/test/trace_processor/diff_tests/include_index.py
@@ -88,6 +88,7 @@
 from diff_tests.parser.profiling.tests_heap_profiling import ProfilingHeapProfiling
 from diff_tests.parser.profiling.tests_llvm_symbolizer import ProfilingLlvmSymbolizer
 from diff_tests.parser.sched.tests import SchedParser
+from diff_tests.parser.simpleperf.tests import Simpleperf
 from diff_tests.parser.smoke.tests import Smoke
 from diff_tests.parser.smoke.tests_compute_metrics import SmokeComputeMetrics
 from diff_tests.parser.smoke.tests_json import SmokeJson
@@ -95,6 +96,7 @@
 from diff_tests.parser.track_event.tests import TrackEvent
 from diff_tests.parser.translated_args.tests import TranslatedArgs
 from diff_tests.parser.ufs.tests import Ufs
+from diff_tests.parser.zip.tests import Zip
 from diff_tests.stdlib.android.frames_tests import Frames
 from diff_tests.stdlib.android.startups_tests import Startups
 from diff_tests.stdlib.android.tests import AndroidStdlib
@@ -104,6 +106,7 @@
 from diff_tests.stdlib.counters.tests import StdlibCounterIntervals
 from diff_tests.stdlib.cpu.tests import Cpu
 from diff_tests.stdlib.dynamic_tables.tests import DynamicTables
+from diff_tests.stdlib.gpu.tests import Gpu
 from diff_tests.stdlib.graphs.dominator_tree_tests import DominatorTree
 from diff_tests.stdlib.graphs.partition_tests import GraphPartitionTests
 from diff_tests.stdlib.graphs.search_tests import GraphSearchTests
@@ -111,6 +114,7 @@
 from diff_tests.stdlib.intervals.tests import StdlibIntervals
 from diff_tests.stdlib.linux.tests import LinuxStdlib
 from diff_tests.stdlib.memory.heap_graph_dominator_tree_tests import HeapGraphDominatorTree
+from diff_tests.stdlib.memory.tests import Memory
 from diff_tests.stdlib.pkvm.tests import Pkvm
 from diff_tests.stdlib.prelude.math_functions_tests import PreludeMathFunctions
 from diff_tests.stdlib.prelude.pprof_functions_tests import PreludePprofFunctions
@@ -182,6 +186,7 @@
       *ProfilingLlvmSymbolizer(index_path, 'parser/profiling',
                                'ProfilingLlvmSymbolizer').fetch(),
       *SchedParser(index_path, 'parser/sched', 'SchedParser').fetch(),
+      *Simpleperf(index_path, 'parser/simpleperf', 'Simpleperf').fetch(),
       *StdlibSched(index_path, 'stdlib/sched', 'StdlibSched').fetch(),
       *Smoke(index_path, 'parser/smoke', 'Smoke').fetch(),
       *SmokeComputeMetrics(index_path, 'parser/smoke',
@@ -216,6 +221,7 @@
       *FtraceCrop(index_path, 'parser/ftrace', 'FtraceCrop').fetch(),
       *ParsingTracedStats(index_path, 'parser/parsing',
                           'ParsingTracedStats').fetch(),
+      *Zip(index_path, 'parser/zip', 'Zip').fetch(),
   ]
 
   metrics_tests = [
@@ -262,6 +268,7 @@
       *Cpu(index_path, 'stdlib/cpu', 'Cpu').fetch(),
       *DominatorTree(index_path, 'stdlib/graphs', 'DominatorTree').fetch(),
       *Frames(index_path, 'stdlib/android', 'Frames').fetch(),
+      *Gpu(index_path, 'stdlib/gpu', 'Gpu').fetch(),
       *GraphSearchTests(index_path, 'stdlib/graphs',
                         'GraphSearchTests').fetch(),
       *GraphPartitionTests(index_path, 'stdlib/graphs',
@@ -271,6 +278,7 @@
       *DynamicTables(index_path, 'stdlib/dynamic_tables',
                      'DynamicTables').fetch(),
       *LinuxStdlib(index_path, 'stdlib/linux', 'LinuxStdlib').fetch(),
+      *Memory(index_path, 'stdlib/memory', 'Memory').fetch(),
       *PreludeMathFunctions(index_path, 'stdlib/prelude',
                             'PreludeMathFunctions').fetch(),
       *HeapGraphDominatorTree(index_path, 'stdlib/memory',
diff --git a/test/trace_processor/diff_tests/metrics/memory/tests.py b/test/trace_processor/diff_tests/metrics/memory/tests.py
index 7ea5fda..a5a5cec 100644
--- a/test/trace_processor/diff_tests/metrics/memory/tests.py
+++ b/test/trace_processor/diff_tests/metrics/memory/tests.py
@@ -248,6 +248,17 @@
         }
         """))
 
+  def test_android_lmk_reason(self):
+    return DiffTestBlueprint(
+        trace=DataPath('lmk_userspace.pb'),
+        query=Metric('android_lmk_reason'),
+        # TODO(mayzner): Find a trace that returns results. This is still
+        # beneficial though, as at least this metric is run.
+        out=TextProto(r"""
+        android_lmk_reason {
+        }
+        """))
+
   def test_android_mem_delta(self):
     return DiffTestBlueprint(
         trace=Path('android_mem_delta.py'),
diff --git a/test/trace_processor/diff_tests/parser/process_tracking/process_tracking_exec.py b/test/trace_processor/diff_tests/parser/process_tracking/process_tracking_exec.py
index a706d7e..83e1034 100644
--- a/test/trace_processor/diff_tests/parser/process_tracking/process_tracking_exec.py
+++ b/test/trace_processor/diff_tests/parser/process_tracking/process_tracking_exec.py
@@ -22,24 +22,25 @@
 
 trace = synth_common.create_trace()
 
-# Create a parent process which  will be forked below.
+# Create a parent process which will be forked below.
 trace.add_packet(ts=1)
 trace.add_process(10, 0, "parent")
 
-# Fork off the new process and then kill it 5ns later.
+# Fork the process into a child.
 trace.add_ftrace_packet(0)
 trace.add_newtask(ts=15, tid=10, new_tid=11, new_comm='child', flags=0)
 trace.add_sched(ts=16, prev_pid=10, next_pid=11, next_comm='child')
 
-# Create a parent process which  will be forked below.
+# Scrape event for the forked process.
 trace.add_packet(ts=20)
-trace.add_process(11, 0, "child_process")
+trace.add_process(11, 10, "child_process")
 
+# Rename of the main thread of forked process.
 trace.add_ftrace_packet(0)
 trace.add_rename(
     ts=25, tid=11, old_comm='child', new_comm='true_name', oom_score_adj=1000)
 
-# Create a parent process which  will be forked below.
+# Scrape of the forked process.
 trace.add_packet(ts=30)
 trace.add_process(11, 10, "true_process_name")
 
diff --git a/test/trace_processor/diff_tests/parser/process_tracking/synth_process_tracking.py b/test/trace_processor/diff_tests/parser/process_tracking/synth_process_tracking.py
index 68641ad..19bf834 100644
--- a/test/trace_processor/diff_tests/parser/process_tracking/synth_process_tracking.py
+++ b/test/trace_processor/diff_tests/parser/process_tracking/synth_process_tracking.py
@@ -90,7 +90,7 @@
     ts=28, prev_pid=32, next_pid=40, prev_comm='p3-t2', next_comm='p4-t0')
 
 trace.add_packet(ts=29)
-trace.add_process(40, 0, "process_4")
+trace.add_process(40, 30, "process_4")
 
 # And now, this new process starts a new thread that recycles TID=31 (previously
 # used as p3-t1, now becomes p4-t1).
diff --git a/test/trace_processor/diff_tests/parser/simpleperf/clocks_align_test.sql b/test/trace_processor/diff_tests/parser/simpleperf/clocks_align_test.sql
new file mode 100644
index 0000000..f5a65ee
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/simpleperf/clocks_align_test.sql
@@ -0,0 +1,99 @@
+--
+-- Copyright 2024 The Android Open Source Project
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--     https://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+
+CREATE PERFETTO VIEW perf_sample_in(ts INT, dur INT)
+AS
+SELECT ts, 0 AS dur FROM perf_sample;
+
+CREATE VIRTUAL TABLE span
+USING
+  SPAN_JOIN(perf_sample_in, slice PARTITIONED depth);
+
+CREATE PERFETTO TABLE slice_stack
+AS
+WITH
+  tmp AS (
+    SELECT
+      ts,
+      parent_stack_id,
+      string_AGG(IIF(name = 'Main loop', 'main', name), ',')
+        OVER (
+          PARTITION BY ts
+          ORDER BY depth ASC
+          RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
+        ) AS stack
+    FROM span
+  )
+SELECT ts, stack FROM tmp WHERE parent_stack_id = 0 ORDER BY TS ASC;
+
+CREATE PERFETTO TABLE perf_stack
+AS
+WITH
+  symbol AS (
+    SELECT
+      id,
+      symbol_set_id,
+      replace(replace(name, '(anonymous namespace)::', ''), '()', '') AS name
+    FROM stack_profile_symbol
+  ),
+  symbol_agg AS (
+    SELECT
+      id,
+      symbol_set_id,
+      string_agg(name, ',')
+        OVER (
+          PARTITION BY symbol_set_id
+          ORDER BY id DESC
+          RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
+        ) AS name
+    FROM symbol
+    WHERE name IN ('main', 'A', 'B', 'C', 'D', 'E')
+  ),
+  inline AS (
+    SELECT symbol_set_id, name FROM symbol_agg WHERE id = symbol_set_id
+  ),
+  frame AS (
+    SELECT f.id AS frame_id, i.name
+    FROM STACK_PROFILE_FRAME f, inline i
+    USING (symbol_set_id)
+  ),
+  child AS (
+    SELECT
+      s.ts,
+      spc.id,
+      spc.parent_id,
+      name
+    FROM perf_sample s, stack_profile_callsite spc
+    ON (s.callsite_id = spc.id),
+    frame USING (frame_id)
+    UNION ALL
+    SELECT
+      child.ts,
+      parent.id,
+      parent.parent_id,
+      COALESCE(f.name || ',', '') || child.name AS name
+    FROM child, stack_profile_callsite parent
+    ON (child.parent_id = parent.id)
+    LEFT JOIN frame f
+      USING (frame_id)
+  )
+SELECT ts, name AS stack FROM child WHERE parent_id IS NULL ORDER BY ts ASC;
+
+SELECT COUNT(*) AS misaligned_count
+FROM slice_stack s
+FULL JOIN perf_stack p
+  USING (ts)
+WHERE s.stack <> p.stack;
diff --git a/test/trace_processor/diff_tests/parser/simpleperf/perf_test.sql b/test/trace_processor/diff_tests/parser/simpleperf/perf_test.sql
new file mode 100644
index 0000000..9f93369
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/simpleperf/perf_test.sql
@@ -0,0 +1,46 @@
+WITH
+  counter_delta_base AS (
+    SELECT
+      *,
+      LAG(value) OVER (PARTITION BY track_id ORDER BY ts) AS lag_value
+    FROM counter
+  ),
+  counter_delta AS (
+    SELECT
+      id,
+      type,
+      ts,
+      track_id,
+      IIF(lag_value IS NULL, value, value - lag_value) AS delta,
+      arg_set_id,
+      machine_id
+    FROM counter_delta_base
+  )
+SELECT
+  CAST(SUM(c.delta) AS INTEGER) AS event_count,
+  thread.name AS command,
+  pid,
+  tid,
+  spm.name AS shared_object,
+  IIF(
+    spf.name IS NOT NULL AND spf.name <> '',
+    spf.name,
+    format(
+      '%s[+%x]',
+      -- substring after last /
+      replace(spm.name, rtrim(spm.name, replace(spm.name, '/', '')), ''),
+      spf.rel_pc)) AS symbol
+FROM counter_delta AS c, perf_counter_track AS t
+ON c.track_id = t.id,
+perf_sample AS s
+ON c.ts = s.ts AND t.perf_session_id = s.perf_session_id AND t.cpu = s.cpu,
+thread USING (utid),
+process USING (upid),
+stack_profile_callsite AS spc ON (s.callsite_id = spc.id),
+stack_profile_frame AS spf ON (spc.frame_id = spf.id),
+stack_profile_mapping AS spm
+ON (spf.mapping = spm.id)
+WHERE
+  s.cpu IN (2, 6, 7)
+GROUP BY command, pid, tid, shared_object, symbol
+ORDER BY event_count DESC;
diff --git a/test/trace_processor/diff_tests/parser/simpleperf/perf_with_add_counter_test.sql b/test/trace_processor/diff_tests/parser/simpleperf/perf_with_add_counter_test.sql
new file mode 100644
index 0000000..b81beac
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/simpleperf/perf_with_add_counter_test.sql
@@ -0,0 +1,68 @@
+WITH
+  counter_delta_base AS (
+    SELECT
+      *,
+      LAG(value) OVER (PARTITION BY track_id ORDER BY ts) AS lag_value
+    FROM counter
+  ),
+  counter_delta AS (
+    SELECT
+      id,
+      type,
+      ts,
+      track_id,
+      IIF(lag_value IS NULL, value, value - lag_value) AS delta,
+      arg_set_id,
+      machine_id
+    FROM counter_delta_base
+  ),
+  named_counter AS (
+    SELECT
+      perf_session_id,
+      ts,
+      cpu,
+      SUM(cast_int !(IIF(t.name = 'cpu-cycles', c.delta, 0)))
+        AS cpu_cycles,
+      SUM(cast_int !(IIF(t.name = 'instructions', c.delta, 0)))
+        AS instructions,
+      SUM(
+        cast_int
+          !(IIF(t.name NOT IN ('cpu-cycles', 'instructions'), c.delta, 0)))
+        AS others
+    FROM counter_delta AS c, perf_counter_track AS t
+    ON (c.track_id = t.id)
+    GROUP BY
+      perf_session_id,
+      ts,
+      cpu
+  )
+SELECT
+  SUM(c.cpu_cycles) AS cpu_cycles,
+  SUM(c.instructions) AS instructions,
+  -- Additional column (not present in simpleperf output) to validate that there
+  -- are no other counters.
+  SUM(c.others) AS others,
+  thread.name AS command,
+  pid,
+  tid,
+  spm.name AS shared_object,
+  IIF(
+    spf.name IS NOT NULL AND spf.name <> '',
+    spf.name,
+    format(
+      '%s[+%x]',
+      -- substring after last /
+      replace(spm.name, rtrim(spm.name, replace(spm.name, '/', '')), ''),
+      spf.rel_pc)) AS symbol
+FROM
+  named_counter AS c,
+  perf_sample AS s
+USING (perf_session_id, ts, cpu),
+thread USING (utid),
+process USING (upid),
+stack_profile_callsite AS spc ON (s.callsite_id = spc.id),
+stack_profile_frame AS spf ON (spc.frame_id = spf.id),
+stack_profile_mapping AS spm
+ON (spf.mapping = spm.id)
+GROUP BY command, pid, tid, shared_object, symbol
+ORDER BY cpu_cycles DESC;
diff --git a/test/trace_processor/diff_tests/parser/simpleperf/tests.py b/test/trace_processor/diff_tests/parser/simpleperf/tests.py
new file mode 100644
index 0000000..4f5a22b
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/simpleperf/tests.py
@@ -0,0 +1,153 @@
+#!/usr/bin/env python3
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License a
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from python.generators.diff_tests.testing import Csv, Path, DataPath
+from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import TestSuite
+
+
+# These diff tests are based on the same test data simpleperf uses for its
+# testing
+# (https://android.googlesource.com/platform/system/extras/+/refs/heads/main/simpleperf/testdata).
+# Basically we load these perf files and make sure we can get the same data we
+# would get via `simpleperf report`
+class Simpleperf(TestSuite):
+  # simpleperf report -i perf.data --print-event-count --csv --cpu 2,6,7
+  def test_perf(self):
+    return DiffTestBlueprint(
+        trace=DataPath('simpleperf/perf.data'),
+        query=Path('perf_test.sql'),
+        out=Csv('''
+        "event_count","command","pid","tid","shared_object","symbol"
+        130707953,"t2",26130,26130,"/t2","t2[+51c]"
+        126249237,"elf",26083,26083,"/elf","elf[+51c]"
+        109687208,"t1",26124,26124,"/t1","t1[+523]"
+        107027760,"t1",26124,26124,"/t1","t1[+51c]"
+        101887409,"t2",26130,26130,"/t2","t2[+523]"
+        92421568,"elf",26083,26083,"/elf","elf[+523]"
+        61539363,"t1",26124,26124,"/t1","t1[+518]"
+        60355129,"elf",26083,26083,"/elf","elf[+513]"
+        54840659,"t1",26124,26124,"/t1","t1[+4ed]"
+        52233968,"elf",26083,26083,"/elf","elf[+4ed]"
+        50833094,"t1",26124,26124,"/t1","t1[+4f7]"
+        50746374,"t2",26130,26130,"/t2","t2[+4ed]"
+        49185691,"elf",26083,26083,"/elf","elf[+4f7]"
+        47520901,"t2",26130,26130,"/t2","t2[+513]"
+        45979652,"elf",26083,26083,"/elf","elf[+518]"
+        44834371,"t2",26130,26130,"/t2","t2[+4f7]"
+        42928068,"t2",26130,26130,"/t2","t2[+518]"
+        39608138,"t1",26124,26124,"/t1","t1[+513]"
+        1390415,"t1",26124,26124,"/t1","t1[+4fa]"
+        1390305,"t2",26130,26130,"/t2","t2[+4fa]"
+        1390173,"elf",26083,26083,"/elf","elf[+500]"
+        1389030,"t2",26130,26130,"/t2","t2[+500]"
+        693786,"t2",26130,26130,"/lib/modules/3.13.0-76-generic/kernel/drivers/ata/pata_acpi.ko","pata_acpi.ko[+ffffffffa05c4da4]"
+        '''))
+
+  def test_perf_tracks(self):
+    return DiffTestBlueprint(
+        trace=DataPath('simpleperf/perf.data'),
+        query='''
+        SELECT
+          name,
+          unit,
+          description,
+          perf_session_id,
+          cpu,
+          is_timebase
+        FROM perf_counter_track
+        ORDER BY perf_session_id, name, cpu;
+        ''',
+        out=Csv('''
+        "name","unit","description","perf_session_id","cpu","is_timebase"
+        "","","",0,2,1
+        "","","",0,6,1
+        "","","",0,7,1
+        "","","",0,16,1
+        '''))
+
+  def test_perf_with_add_counter_tracks(self):
+    return DiffTestBlueprint(
+        trace=DataPath('simpleperf/perf_with_add_counter.data'),
+        query='''
+        SELECT
+          name,
+          parent_id,
+          source_arg_set_id,
+          machine_id,
+          unit,
+          description,
+          perf_session_id,
+          cpu,
+          is_timebase
+        FROM perf_counter_track
+        ORDER BY perf_session_id, name, cpu;
+        ''',
+        out=Csv('''
+        "name","parent_id","source_arg_set_id","machine_id","unit","description","perf_session_id","cpu","is_timebase"
+        "cpu-cycles","[NULL]","[NULL]","[NULL]","","",0,40,1
+        "instructions","[NULL]","[NULL]","[NULL]","","",0,40,0
+        '''))
+
+  # simpleperf report -i perf.data --print-event-count --csv
+  # The thread name in this trace changes over time. simpleperf shows samples
+  # with the old and new name. Perfetto does not support threads changing names,
+  # it only keeps the last name, thus there is a slight mismatch in the outputs.
+  def test_perf_with_add_counter(self):
+    return DiffTestBlueprint(
+        trace=DataPath('simpleperf/perf_with_add_counter.data'),
+        query=Path('perf_with_add_counter_test.sql'),
+        out=Csv('''
+        "cpu_cycles","instructions","others","command","pid","tid","shared_object","symbol"
+        1011567,1188389,0,"sleep",689664,689664,"[kernel.kallsyms]","[kernel.kallsyms][+ffffffffa8cc9d30]"
+        219490,233619,0,"sleep",689664,689664,"[kernel.kallsyms]","[kernel.kallsyms][+ffffffffa8e498c6]"
+        191017,157031,0,"sleep",689664,689664,"[kernel.kallsyms]","[kernel.kallsyms][+ffffffffa94d0901]"
+        175099,140443,0,"sleep",689664,689664,"/lib/x86_64-linux-gnu/libc-2.32.so","_dl_addr"
+        152310,130151,0,"sleep",689664,689664,"[kernel.kallsyms]","[kernel.kallsyms][+ffffffffa8e30c70]"
+        122439,87058,0,"sleep",689664,689664,"[kernel.kallsyms]","[kernel.kallsyms][+ffffffffa960015d]"
+        89368,68332,0,"sleep",689664,689664,"[kernel.kallsyms]","[kernel.kallsyms][+ffffffffa8e03757]"
+        40272,30457,0,"sleep",689664,689664,"/lib/x86_64-linux-gnu/ld-2.32.so","ld-2.32.so[+1767b]"
+        14742,7858,0,"sleep",689664,689664,"[kernel.kallsyms]","[kernel.kallsyms][+ffffffffa8ce7a78]"
+        7551,1953,0,"sleep",689664,689664,"[kernel.kallsyms]","[kernel.kallsyms][+ffffffffa8cc90c5]"
+        7080,2940,0,"sleep",689664,689664,"[kernel.kallsyms]","[kernel.kallsyms][+ffffffffa8cc8119]"
+        3520,295,0,"sleep",689664,689664,"[kernel.kallsyms]","[kernel.kallsyms][+ffffffffa8c6b3e6]"
+        '''))
+
+  def test_build_id_feature(self):
+    return DiffTestBlueprint(
+        trace=DataPath('simpleperf/perf.data'),
+        query='''
+        SELECT build_id, name
+        FROM stack_profile_mapping
+        WHERE build_id <> ""
+        ORDER BY name
+        ''',
+        out=Csv('''
+        "build_id","name"
+        "0b12a384a9f4a3f3659b7171ca615dbec3a81f71","/elf"
+        "0b12a384a9f4a3f3659b7171ca615dbec3a81f71","/elf"
+        "47111a47babdcd27ca2f9ff450dc1897ded761ed","/lib/modules/3.13.0-76-generic/kernel/drivers/ata/pata_acpi.ko"
+        "0b12a384a9f4a3f3659b7171ca615dbec3a81f71","/t1"
+        "0b12a384a9f4a3f3659b7171ca615dbec3a81f71","/t2"
+        '''))
+
+  def test_clocks_align(self):
+    return DiffTestBlueprint(
+        trace=DataPath('zip/perf_track_sym.zip'),
+        query=Path('clocks_align_test.sql'),
+        out=Csv('''
+        "misaligned_count"
+        0
+        '''))
\ No newline at end of file
diff --git a/test/trace_processor/diff_tests/parser/zip/stacks_test.sql b/test/trace_processor/diff_tests/parser/zip/stacks_test.sql
new file mode 100644
index 0000000..68543f1
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/zip/stacks_test.sql
@@ -0,0 +1,48 @@
+WITH
+  symbol AS (
+    SELECT
+      id,
+      symbol_set_id,
+      replace(replace(name, '(anonymous namespace)::', ''), '()', '') AS name
+    FROM stack_profile_symbol
+  ),
+  symbol_agg AS (
+    SELECT
+      id,
+      symbol_set_id,
+      string_agg(name, ',')
+        OVER (
+          PARTITION BY symbol_set_id
+          ORDER BY id DESC
+          RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
+        ) AS name
+    FROM symbol
+    WHERE name IN ('main', 'A', 'B', 'C', 'D', 'E')
+  ),
+  inline AS (
+    SELECT symbol_set_id, name FROM symbol_agg WHERE id = symbol_set_id
+  ),
+  frame AS (
+    SELECT f.id AS frame_id, i.name
+    FROM STACK_PROFILE_FRAME f, inline i
+    USING (symbol_set_id)
+  ),
+  child AS (
+    SELECT
+      spc.id,
+      spc.parent_id,
+      name
+    FROM perf_sample s, stack_profile_callsite spc
+    ON (s.callsite_id = spc.id),
+    frame USING (frame_id)
+    UNION ALL
+    SELECT
+      parent.id,
+      parent.parent_id,
+      COALESCE(f.name || ',', '') || child.name AS name
+    FROM child, stack_profile_callsite parent
+    ON (child.parent_id = parent.id)
+    LEFT JOIN frame f
+      USING (frame_id)
+  )
+SELECT DISTINCT name FROM child WHERE parent_id IS NULL ORDER BY name
\ No newline at end of file
diff --git a/test/trace_processor/diff_tests/parser/zip/tests.py b/test/trace_processor/diff_tests/parser/zip/tests.py
new file mode 100644
index 0000000..9827072
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/zip/tests.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python3
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License a
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from python.generators.diff_tests.testing import Csv, Path, DataPath
+from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import TestSuite
+
+
+class Zip(TestSuite):
+
+  def test_perf_proto_sym(self):
+    return DiffTestBlueprint(
+        trace=DataPath('zip/perf_track_sym.zip'),
+        query=Path('stacks_test.sql'),
+        out=Csv('''
+        "name"
+        "main,A"
+        "main,A,B"
+        "main,A,B,C"
+        "main,A,B,C,D"
+        "main,A,B,C,D,E"
+        "main,A,B,C,E"
+        "main,A,B,D"
+        "main,A,B,D,E"
+        "main,A,B,E"
+        "main,A,C"
+        "main,A,C,D"
+        "main,A,C,D,E"
+        "main,A,C,E"
+        "main,A,D"
+        "main,A,D,E"
+        "main,A,E"
+        "main,B"
+        "main,B,C"
+        "main,B,C,D"
+        "main,B,C,D,E"
+        "main,B,C,E"
+        "main,B,D"
+        "main,B,D,E"
+        "main,B,E"
+        "main,C"
+        "main,C,D"
+        "main,C,D,E"
+        "main,C,E"
+        "main,D"
+        "main,D,E"
+        "main,E"
+        '''))
diff --git a/test/trace_processor/diff_tests/stdlib/android/tests.py b/test/trace_processor/diff_tests/stdlib/android/tests.py
index 59a8877..6781e3e 100644
--- a/test/trace_processor/diff_tests/stdlib/android/tests.py
+++ b/test/trace_processor/diff_tests/stdlib/android/tests.py
@@ -1244,16 +1244,16 @@
       """,
         out=Csv("""
         "ts","dur","score","bucket","process_name","oom_adj_ts","oom_adj_dur","oom_adj_thread_name","oom_adj_reason","oom_adj_trigger"
-        1737065264829,701108081,925,"cached_app","com.android.providers.calendar",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212"
-        1737066678827,3470211742,935,"cached_app","com.android.imsserviceentitlement",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212"
-        1737066873002,3470017567,945,"cached_app","com.android.carrierconfig",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212"
-        1737067058812,3469831757,955,"cached_app_lmk_first","com.android.messaging",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212"
-        1737067246975,699224817,955,"cached_app_lmk_first","android.process.acore",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212"
-        1737068421919,3468468650,965,"cached_app_lmk_first","com.android.shell",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212"
-        1737068599673,697908135,965,"cached_app_lmk_first","android.process.media",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212"
-        1737068933602,3467956967,975,"cached_app_lmk_first","com.android.gallery3d",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212"
-        1737069091010,3467799559,975,"cached_app_lmk_first","com.android.packageinstaller",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212"
-        1737069240534,3467650035,985,"cached_app_lmk_first","com.android.managedprovisioning",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212"
+1737065264829,701108081,925,"cached","com.android.providers.calendar",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212"
+1737066678827,3470211742,935,"cached","com.android.imsserviceentitlement",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212"
+1737066873002,3470017567,945,"cached","com.android.carrierconfig",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212"
+1737067058812,3469831757,955,"cached","com.android.messaging",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212"
+1737067246975,699224817,955,"cached","android.process.acore",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212"
+1737068421919,3468468650,965,"cached","com.android.shell",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212"
+1737068599673,697908135,965,"cached","android.process.media",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212"
+1737068933602,3467956967,975,"cached","com.android.gallery3d",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212"
+1737069091010,3467799559,975,"cached","com.android.packageinstaller",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212"
+1737069240534,3467650035,985,"cached","com.android.managedprovisioning",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212"
       """))
 
   def test_broadcast_minsdk_u(self):
diff --git a/test/trace_processor/diff_tests/stdlib/gpu/tests.py b/test/trace_processor/diff_tests/stdlib/gpu/tests.py
new file mode 100644
index 0000000..2c23e80
--- /dev/null
+++ b/test/trace_processor/diff_tests/stdlib/gpu/tests.py
@@ -0,0 +1,40 @@
+#!/usr/bin/env python3
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License a
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from python.generators.diff_tests.testing import Path, DataPath, Metric, Systrace
+from python.generators.diff_tests.testing import Csv, Json, TextProto, BinaryProto
+from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import TestSuite
+from python.generators.diff_tests.testing import PrintProfileProto
+
+
+class Gpu(TestSuite):
+
+  def test_gpu_frequency(self):
+    return DiffTestBlueprint(
+        trace=Path('../../metrics/graphics/gpu_frequency_metric.textproto'),
+        query="""
+        INCLUDE PERFETTO MODULE gpu.frequency;
+        SELECT *
+        FROM gpu_frequency;
+      """,
+        out=Csv("""
+        "ts","dur","gpu_id","gpu_freq"
+        200001000000,2000000,0,585000
+        200003000000,1000000,0,0
+        200004000000,2000000,0,603000
+        200002000000,3000000,1,400000
+        200005000000,1000000,1,758000
+      """))
diff --git a/test/trace_processor/diff_tests/stdlib/memory/tests.py b/test/trace_processor/diff_tests/stdlib/memory/tests.py
new file mode 100644
index 0000000..d781b54
--- /dev/null
+++ b/test/trace_processor/diff_tests/stdlib/memory/tests.py
@@ -0,0 +1,111 @@
+#!/usr/bin/env python3
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License a
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from python.generators.diff_tests.testing import Path, DataPath, Metric, Systrace
+from python.generators.diff_tests.testing import Csv, Json, TextProto, BinaryProto
+from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import TestSuite
+from python.generators.diff_tests.testing import PrintProfileProto
+
+
+class Memory(TestSuite):
+
+  def test_memory_rss_and_swap_per_process(self):
+    return DiffTestBlueprint(
+        trace=DataPath('android_postboot_unlock.pftrace'),
+        query="""
+        INCLUDE PERFETTO MODULE memory.linux.process;
+
+        SELECT *
+        FROM memory_rss_and_swap_per_process
+        WHERE upid = 1
+        LIMIT 5
+        """,
+        out=Csv("""
+        "ts","dur","upid","pid","process_name","anon_rss","file_rss","shmem_rss","rss","swap","anon_rss_and_swap","rss_and_swap"
+        37592474220,12993896,1,1982,"com.android.systemui",125865984,"[NULL]","[NULL]","[NULL]","[NULL]",125865984,"[NULL]"
+        37605468116,1628,1,1982,"com.android.systemui",126050304,"[NULL]","[NULL]","[NULL]","[NULL]",126050304,"[NULL]"
+        37605469744,1302,1,1982,"com.android.systemui",126050304,"[NULL]",2990080,"[NULL]","[NULL]",126050304,"[NULL]"
+        37605471046,685791,1,1982,"com.android.systemui",126046208,"[NULL]",2990080,"[NULL]","[NULL]",126046208,"[NULL]"
+        37606156837,6510,1,1982,"com.android.systemui",126042112,"[NULL]",2990080,"[NULL]","[NULL]",126042112,"[NULL]"
+            """))
+
+  def test_memory_rss_high_watermark_per_process(self):
+    return DiffTestBlueprint(
+        trace=DataPath('android_postboot_unlock.pftrace'),
+        query="""
+        INCLUDE PERFETTO MODULE memory.linux.high_watermark;
+
+        SELECT *
+        FROM memory_rss_high_watermark_per_process
+        WHERE upid = 1
+        LIMIT 10;
+        """,
+        out=Csv("""
+        "ts","dur","upid","pid","process_name","rss_high_watermark"
+        37592474220,12993896,1,1982,"com.android.systemui",125865984
+        37605468116,1628,1,1982,"com.android.systemui",126050304
+        37605469744,333774129,1,1982,"com.android.systemui",129040384
+        37939243873,120479574,1,1982,"com.android.systemui",372977664
+        38059723447,936,1,1982,"com.android.systemui",373043200
+        38059724383,6749186,1,1982,"com.android.systemui",373174272
+        38066473569,7869426,1,1982,"com.android.systemui",373309440
+        38074342995,11596761,1,1982,"com.android.systemui",373444608
+        38085939756,4877848,1,1982,"com.android.systemui",373579776
+        38090817604,11930827,1,1982,"com.android.systemui",373714944
+              """))
+
+  def test_memory_oom_score_with_rss_and_swap_per_process(self):
+    return DiffTestBlueprint(
+        trace=DataPath('sched_wakeup_trace.atr'),
+        query="""
+        INCLUDE PERFETTO MODULE memory.linux.process;
+        SELECT *
+        FROM memory_oom_score_with_rss_and_swap_per_process
+        WHERE oom_adj_reason IS NOT NULL
+        ORDER BY ts
+        LIMIT 10;
+      """,
+        out=Csv("""
+        "ts","dur","score","bucket","upid","process_name","pid","oom_adj_id","oom_adj_ts","oom_adj_dur","oom_adj_track_id","oom_adj_thread_name","oom_adj_reason","oom_adj_trigger","anon_rss","file_rss","shmem_rss","rss","swap","anon_rss_and_swap","rss_and_swap"
+        1737065264829,701108081,925,"cached",269,"com.android.providers.calendar",1937,332,1737064421516,29484835,1217,"binder:642_1","processEnd","IActivityManager#1598246212",49229824,57495552,835584,107560960,0,49229824,107560960
+        1737066678827,2934486383,935,"cached",287,"com.android.imsserviceentitlement",2397,332,1737064421516,29484835,1217,"binder:642_1","processEnd","IActivityManager#1598246212",48881664,57081856,831488,106795008,0,48881664,106795008
+        1737066873002,2934292208,945,"cached",292,"com.android.carrierconfig",2593,332,1737064421516,29484835,1217,"binder:642_1","processEnd","IActivityManager#1598246212",48586752,49872896,823296,99282944,0,48586752,99282944
+        1737067058812,2934106398,955,"cached",288,"com.android.messaging",2416,332,1737064421516,29484835,1217,"binder:642_1","processEnd","IActivityManager#1598246212",54956032,71417856,843776,127217664,0,54956032,127217664
+        1737067246975,699224817,955,"cached",267,"android.process.acore",1866,332,1737064421516,29484835,1217,"binder:642_1","processEnd","IActivityManager#1598246212",52498432,72048640,856064,125403136,0,52498432,125403136
+        1737068421919,2932743291,965,"cached",273,"com.android.shell",2079,332,1737064421516,29484835,1217,"binder:642_1","processEnd","IActivityManager#1598246212",48738304,52056064,823296,101617664,0,48738304,101617664
+        1737068599673,970398,965,"cached",271,"android.process.media",2003,332,1737064421516,29484835,1217,"binder:642_1","processEnd","IActivityManager#1598246212",49917952,60444672,839680,111202304,0,49917952,111202304
+        1737068933602,2932231608,975,"cached",286,"com.android.gallery3d",2371,332,1737064421516,29484835,1217,"binder:642_1","processEnd","IActivityManager#1598246212",49561600,54521856,831488,104914944,0,49561600,104914944
+        1737069091010,682459310,975,"cached",289,"com.android.packageinstaller",2480,332,1737064421516,29484835,1217,"binder:642_1","processEnd","IActivityManager#1598246212",49364992,52539392,827392,102731776,0,49364992,102731776
+        1737069240534,489635,985,"cached",268,"com.android.managedprovisioning",1868,332,1737064421516,29484835,1217,"binder:642_1","processEnd","IActivityManager#1598246212",50683904,53985280,815104,105484288,0,50683904,105484288
+         """))
+
+  def test_memory_gpu_per_process(self):
+    return DiffTestBlueprint(
+        trace=Path('../../metrics/graphics/gpu_metric.py'),
+        query="""
+        INCLUDE PERFETTO MODULE memory.android.gpu;
+        SELECT *
+        FROM memory_gpu_per_process;
+        """,
+        out=Csv("""
+        "ts","dur","upid","gpu_memory"
+        2,2,2,6
+        4,6,2,8
+        4,5,1,2
+        9,1,1,8
+        6,1,3,7
+        7,3,3,10
+         """))
diff --git a/test/trace_processor/diff_tests/tables/tests_sched.py b/test/trace_processor/diff_tests/tables/tests_sched.py
index ad23a5d..f13d42c 100644
--- a/test/trace_processor/diff_tests/tables/tests_sched.py
+++ b/test/trace_processor/diff_tests/tables/tests_sched.py
@@ -487,22 +487,24 @@
           table_name,
           critical_path_utid
         FROM _thread_executing_span_critical_path_stack((select utid from thread where tid = 3487), start_ts, end_ts), trace_bounds
-        ORDER BY ts
-        LIMIT 11
+        WHERE ts = 1737500355691
+        ORDER BY utid, id
         """,
         out=Csv("""
         "id","ts","dur","utid","stack_depth","name","table_name","critical_path_utid"
-        11889,1737349401439,57188,1477,0,"thread_state: R","thread_state",1477
-        11889,1737349401439,57188,1477,1,"[NULL]","thread_state",1477
-        11889,1737349401439,57188,1477,2,"[NULL]","thread_state",1477
-        11889,1737349401439,57188,1477,3,"process_name: com.android.providers.media.module","thread_state",1477
-        11889,1737349401439,57188,1477,4,"thread_name: rs.media.module","thread_state",1477
-        11891,1737349458627,1884896,1477,0,"thread_state: Running","thread_state",1477
-        11891,1737349458627,1884896,1477,1,"[NULL]","thread_state",1477
-        11891,1737349458627,1884896,1477,2,"[NULL]","thread_state",1477
-        11891,1737349458627,1884896,1477,3,"process_name: com.android.providers.media.module","thread_state",1477
-        11891,1737349458627,1884896,1477,4,"thread_name: rs.media.module","thread_state",1477
-        11891,1737349458627,1884896,1477,5,"cpu: 0","thread_state",1477
+        4271,1737500355691,1456753,1477,5,"bindApplication","slice",1477
+        13120,1737500355691,1456753,1477,0,"thread_state: S","thread_state",1477
+        13120,1737500355691,1456753,1477,1,"[NULL]","thread_state",1477
+        13120,1737500355691,1456753,1477,2,"[NULL]","thread_state",1477
+        13120,1737500355691,1456753,1477,3,"process_name: com.android.providers.media.module","thread_state",1477
+        13120,1737500355691,1456753,1477,4,"thread_name: rs.media.module","thread_state",1477
+        4800,1737500355691,1456753,1498,11,"HIDL::IComponentStore::getStructDescriptors::client","slice",1477
+        4801,1737500355691,1456753,1498,12,"binder transaction","slice",1477
+        13648,1737500355691,1456753,1498,6,"blocking thread_state: R+","thread_state",1477
+        13648,1737500355691,1456753,1498,7,"blocking process_name: com.android.providers.media.module","thread_state",1477
+        13648,1737500355691,1456753,1498,8,"blocking thread_name: CodecLooper","thread_state",1477
+        13648,1737500355691,1456753,1498,9,"[NULL]","thread_state",1477
+        13648,1737500355691,1456753,1498,10,"[NULL]","thread_state",1477
         """))
 
   def test_thread_executing_span_critical_path_graph(self):
diff --git a/tools/install-build-deps b/tools/install-build-deps
index 31972d4..62d1ac6 100755
--- a/tools/install-build-deps
+++ b/tools/install-build-deps
@@ -185,6 +185,11 @@
     # Keep in sync with Chromium's //third_party/protobuf.
     Dependency(
         'buildtools/protobuf',
+        # If you revert the below version back to an earlier version of
+        # protobuf, make sure to revert the changes to
+        # //gn/standalone/protoc.py as well.
+        #
+        # This comment can be removed with protobuf is next upreved.
         'https://chromium.googlesource.com/external/github.com/protocolbuffers/protobuf.git',
         'f0dc78d7e6e331b8c6bb2d5283e06aa26883ca7c',  # refs/tags/v21.12
         'all',
diff --git a/ui/src/common/plugins.ts b/ui/src/common/plugins.ts
index ab29fc1..f729597 100644
--- a/ui/src/common/plugins.ts
+++ b/ui/src/common/plugins.ts
@@ -45,6 +45,7 @@
 import {raf} from '../core/raf_scheduler';
 import {defaultPlugins} from '../core/default_plugins';
 import {HighPrecisionTimeSpan} from './high_precision_time';
+import {PromptOption} from '../frontend/omnibox_manager';
 
 // Every plugin gets its own PluginContext. This is how we keep track
 // what each plugin is doing and how we can blame issues on particular
@@ -345,6 +346,13 @@
   get trace(): TraceContext {
     return globals.traceContext;
   }
+
+  async prompt(
+    text: string,
+    options?: PromptOption[] | undefined,
+  ): Promise<string> {
+    return globals.omnibox.prompt(text, options);
+  }
 }
 
 function isPinned(trackId: string): boolean {
diff --git a/ui/src/core/default_plugins.ts b/ui/src/core/default_plugins.ts
index ae40e79..44b4e21 100644
--- a/ui/src/core/default_plugins.ts
+++ b/ui/src/core/default_plugins.ts
@@ -57,4 +57,6 @@
   'perfetto.ThreadState',
   'perfetto.VisualisedArgs',
   'org.kernel.LinuxKernelDevices',
+  'perfetto.TrackUtils',
+  'com.google.PixelMemory',
 ];
diff --git a/ui/src/core_plugins/track_utils/index.ts b/ui/src/core_plugins/track_utils/index.ts
new file mode 100644
index 0000000..937ba70
--- /dev/null
+++ b/ui/src/core_plugins/track_utils/index.ts
@@ -0,0 +1,100 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import {Actions} from '../../common/actions';
+import {
+  getTimeSpanOfSelectionOrVisibleWindow,
+  globals,
+} from '../../frontend/globals';
+import {OmniboxMode} from '../../frontend/omnibox_manager';
+import {verticalScrollToTrack} from '../../frontend/scroll_helper';
+import {
+  Plugin,
+  PluginContextTrace,
+  PluginDescriptor,
+  PromptOption,
+} from '../../public';
+
+class TrackUtilsPlugin implements Plugin {
+  async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
+    ctx.registerCommand({
+      id: 'perfetto.RunQueryInSelectedTimeWindow',
+      name: `Run query in selected time window`,
+      callback: () => {
+        const window = getTimeSpanOfSelectionOrVisibleWindow();
+        globals.omnibox.setMode(OmniboxMode.Query);
+        globals.omnibox.setText(
+          `select  where ts >= ${window.start} and ts < ${window.end}`,
+        );
+        globals.omnibox.focusOmnibox(7);
+      },
+    });
+
+    ctx.registerCommand({
+      // Selects & reveals the first track on the timeline with a given URI.
+      id: 'perfetto.FindTrack',
+      name: 'Find track by URI',
+      callback: async () => {
+        const tracks = globals.trackManager.getAllTracks();
+        const options = tracks.map(({uri}): PromptOption => {
+          return {key: uri, displayName: uri};
+        });
+
+        // Sort tracks in a natural sort order
+        const collator = new Intl.Collator('en', {
+          numeric: true,
+          sensitivity: 'base',
+        });
+        const sortedOptions = options.sort((a, b) => {
+          return collator.compare(a.displayName, b.displayName);
+        });
+
+        try {
+          const selectedUri = await ctx.prompt(
+            'Choose a track...',
+            sortedOptions,
+          );
+
+          // Find the first track with this URI
+          const firstTrack = Object.values(globals.state.tracks).find(
+            ({uri}) => uri === selectedUri,
+          );
+          if (firstTrack) {
+            console.log(firstTrack);
+            verticalScrollToTrack(firstTrack.key, true);
+            const traceTime = globals.stateTraceTimeTP();
+            globals.makeSelection(
+              Actions.selectArea({
+                area: {
+                  start: traceTime.start,
+                  end: traceTime.end,
+                  tracks: [firstTrack.key],
+                },
+              }),
+            );
+          } else {
+            alert(`No tracks with uri ${selectedUri} on the timeline`);
+          }
+        } catch {
+          // Prompt was probably cancelled - do nothing.
+        }
+      },
+    });
+  }
+}
+
+export const plugin: PluginDescriptor = {
+  pluginId: 'perfetto.TrackUtils',
+  plugin: TrackUtilsPlugin,
+};
diff --git a/ui/src/frontend/app.ts b/ui/src/frontend/app.ts
index 989a331..6c90216 100644
--- a/ui/src/frontend/app.ts
+++ b/ui/src/frontend/app.ts
@@ -18,9 +18,8 @@
 import {Trash} from '../base/disposable';
 import {findRef} from '../base/dom_utils';
 import {FuzzyFinder} from '../base/fuzzy';
-import {assertExists} from '../base/logging';
+import {assertExists, assertUnreachable} from '../base/logging';
 import {undoCommonChatAppReplacements} from '../base/string_utils';
-import {duration, Span, time, TimeSpan} from '../base/time';
 import {Actions} from '../common/actions';
 import {getLegacySelection} from '../common/state';
 import {
@@ -30,28 +29,22 @@
   TimestampFormat,
 } from '../core/timestamp_format';
 import {raf} from '../core/raf_scheduler';
-import {Command} from '../public';
-import {Engine} from '../trace_processor/engine';
-import {THREAD_STATE_TRACK_KIND} from '../core_plugins/thread_state';
+import {Command, Engine, addDebugSliceTrack} from '../public';
 import {HotkeyConfig, HotkeyContext} from '../widgets/hotkey_context';
 import {HotkeyGlyphs} from '../widgets/hotkey_glyphs';
 import {maybeRenderFullscreenModalDialog} from '../widgets/modal';
 
 import {onClickCopy} from './clipboard';
 import {CookieConsent} from './cookie_consent';
-import {globals} from './globals';
+import {getTimeSpanOfSelectionOrVisibleWindow, globals} from './globals';
 import {toggleHelp} from './help_modal';
 import {Notes} from './notes';
 import {Omnibox, OmniboxOption} from './omnibox';
 import {addQueryResultsTab} from './query_result_tab';
-import {verticalScrollToTrack} from './scroll_helper';
 import {executeSearch} from './search_handler';
 import {Sidebar} from './sidebar';
-import {Utid} from './sql_types';
-import {getThreadInfo} from './thread_and_process_info';
 import {Topbar} from './topbar';
 import {shareTrace} from './trace_attrs';
-import {addDebugSliceTrack} from './debug_tracks';
 import {AggregationsTabs} from './aggregation_tab';
 import {addSqlTableTab} from './sql_table/tab';
 import {SqlTables} from './sql_table/well_known_tables';
@@ -61,8 +54,11 @@
   lockSliceSpan,
   moveByFocusedFlow,
 } from './keyboard_event_handler';
-import {exists} from '../base/utils';
 import {publishPermalinkHash} from './publish';
+import {OmniboxMode, PromptOption} from './omnibox_manager';
+import {Utid} from './sql_types';
+import {getThreadInfo} from './thread_and_process_info';
+import {THREAD_STATE_TRACK_KIND} from '../core_plugins/thread_state';
 
 function renderPermalink(): m.Children {
   const hash = globals.permalinkHash;
@@ -88,25 +84,6 @@
   }
 }
 
-interface PromptOption {
-  key: string;
-  displayName: string;
-}
-
-interface Prompt {
-  text: string;
-  options?: PromptOption[];
-  resolve(result: string): void;
-  reject(): void;
-}
-
-enum OmniboxMode {
-  Search,
-  Query,
-  Command,
-  Prompt,
-}
-
 const criticalPathSliceColumns = {
   ts: 'ts',
   dur: 'dur',
@@ -138,14 +115,6 @@
 
 export class App implements m.ClassComponent {
   private trash = new Trash();
-
-  private omniboxMode: OmniboxMode = OmniboxMode.Search;
-  private omniboxText = '';
-  private queryText = '';
-  private omniboxSelectionIndex = 0;
-  private focusOmniboxNextRender = false;
-  private pendingCursorPlacement = -1;
-  private pendingPrompt?: Prompt;
   static readonly OMNIBOX_INPUT_REF = 'omnibox';
   private omniboxInputEl?: HTMLInputElement;
   private recentCommands: string[] = [];
@@ -164,80 +133,6 @@
     return engine;
   }
 
-  private enterCommandMode(): void {
-    this.omniboxMode = OmniboxMode.Command;
-    this.resetOmnibox();
-    this.rejectPendingPrompt();
-    this.focusOmniboxNextRender = true;
-
-    raf.scheduleFullRedraw();
-  }
-
-  private enterQueryMode(): void {
-    this.omniboxMode = OmniboxMode.Query;
-    this.resetOmnibox();
-    this.rejectPendingPrompt();
-    this.focusOmniboxNextRender = true;
-
-    raf.scheduleFullRedraw();
-  }
-
-  private enterSearchMode(focusOmnibox: boolean): void {
-    this.omniboxMode = OmniboxMode.Search;
-    this.resetOmnibox();
-    this.rejectPendingPrompt();
-
-    if (focusOmnibox) {
-      this.focusOmniboxNextRender = true;
-    }
-
-    globals.dispatch(Actions.setOmniboxMode({mode: 'SEARCH'}));
-
-    raf.scheduleFullRedraw();
-  }
-
-  // Start a prompt. If options are supplied, the user must pick one from the
-  // list, otherwise the input is free-form text.
-  private prompt(text: string, options?: PromptOption[]): Promise<string> {
-    this.omniboxMode = OmniboxMode.Prompt;
-    this.resetOmnibox();
-    this.rejectPendingPrompt();
-
-    const promise = new Promise<string>((resolve, reject) => {
-      this.pendingPrompt = {
-        text,
-        options,
-        resolve,
-        reject,
-      };
-    });
-
-    this.focusOmniboxNextRender = true;
-    raf.scheduleFullRedraw();
-
-    return promise;
-  }
-
-  // Resolve the pending prompt with a value to return to the prompter.
-  private resolvePrompt(value: string): void {
-    if (this.pendingPrompt) {
-      this.pendingPrompt.resolve(value);
-      this.pendingPrompt = undefined;
-    }
-    this.enterSearchMode(false);
-  }
-
-  // Reject the prompt outright. Doing this will force the owner of the prompt
-  // promise to catch, so only do this when things go seriously wrong.
-  // Use |resolvePrompt(null)| to indicate cancellation.
-  private rejectPrompt(): void {
-    if (this.pendingPrompt) {
-      this.pendingPrompt.reject();
-      this.pendingPrompt = undefined;
-    }
-    this.enterSearchMode(false);
-  }
-
   private getFirstUtidOfSelectionOrVisibleWindow(): number {
     const selection = getLegacySelection(globals.state);
     if (selection && selection.kind === 'AREA') {
@@ -283,7 +178,7 @@
         const promptText = 'Select format...';
 
         try {
-          const result = await this.prompt(promptText, options);
+          const result = await globals.omnibox.prompt(promptText, options);
           setTimestampFormat(result as TimestampFormat);
           raf.scheduleFullRedraw();
         } catch {
@@ -305,7 +200,7 @@
         const promptText = 'Select duration precision mode...';
 
         try {
-          const result = await this.prompt(promptText, options);
+          const result = await globals.omnibox.prompt(promptText, options);
           setDurationPrecision(result as DurationPrecision);
           raf.scheduleFullRedraw();
         } catch {
@@ -452,19 +347,19 @@
     {
       id: 'perfetto.OpenCommandPalette',
       name: 'Open command palette',
-      callback: () => this.enterCommandMode(),
+      callback: () => globals.omnibox.setMode(OmniboxMode.Command),
       defaultHotkey: '!Mod+Shift+P',
     },
     {
       id: 'perfetto.RunQuery',
       name: 'Run query',
-      callback: () => this.enterQueryMode(),
+      callback: () => globals.omnibox.setMode(OmniboxMode.Query),
       defaultHotkey: '!Mod+O',
     },
     {
       id: 'perfetto.Search',
       name: 'Search',
-      callback: () => this.enterSearchMode(true),
+      callback: () => globals.omnibox.setMode(OmniboxMode.Search),
       defaultHotkey: '/',
     },
     {
@@ -474,16 +369,6 @@
       defaultHotkey: '?',
     },
     {
-      id: 'perfetto.RunQueryInSelectedTimeWindow',
-      name: `Run query in selected time window`,
-      callback: () => {
-        const window = getTimeSpanOfSelectionOrVisibleWindow();
-        this.enterQueryMode();
-        this.queryText = `select  where ts >= ${window.start} and ts < ${window.end}`;
-        this.pendingCursorPlacement = 7;
-      },
-    },
-    {
       id: 'perfetto.CopyTimeWindow',
       name: `Copy selected time window to clipboard`,
       callback: () => {
@@ -493,56 +378,6 @@
       },
     },
     {
-      // Selects & reveals the first track on the timeline with a given URI.
-      id: 'perfetto.FindTrack',
-      name: 'Find track by URI',
-      callback: async () => {
-        const tracks = globals.trackManager.getAllTracks();
-        const options = tracks.map(({uri}): PromptOption => {
-          return {key: uri, displayName: uri};
-        });
-
-        // Sort tracks in a natural sort order
-        const collator = new Intl.Collator('en', {
-          numeric: true,
-          sensitivity: 'base',
-        });
-        const sortedOptions = options.sort((a, b) => {
-          return collator.compare(a.displayName, b.displayName);
-        });
-
-        try {
-          const selectedUri = await this.prompt(
-            'Choose a track...',
-            sortedOptions,
-          );
-
-          // Find the first track with this URI
-          const firstTrack = Object.values(globals.state.tracks).find(
-            ({uri}) => uri === selectedUri,
-          );
-          if (firstTrack) {
-            console.log(firstTrack);
-            verticalScrollToTrack(firstTrack.key, true);
-            const traceTime = globals.stateTraceTimeTP();
-            globals.makeSelection(
-              Actions.selectArea({
-                area: {
-                  start: traceTime.start,
-                  end: traceTime.end,
-                  tracks: [firstTrack.key],
-                },
-              }),
-            );
-          } else {
-            alert(`No tracks with uri ${selectedUri} on the timeline`);
-          }
-        } catch {
-          // Prompt was probably cancelled - do nothing.
-        }
-      },
-    },
-    {
       id: 'perfetto.FocusSelection',
       name: 'Focus current selection',
       callback: () => findCurrentSelection(),
@@ -653,18 +488,6 @@
     return this.cmds;
   }
 
-  private rejectPendingPrompt() {
-    if (this.pendingPrompt) {
-      this.pendingPrompt.reject();
-      this.pendingPrompt = undefined;
-    }
-  }
-
-  private resetOmnibox() {
-    this.omniboxText = '';
-    this.omniboxSelectionIndex = 0;
-  }
-
   private renderOmnibox(): m.Children {
     const msgTTL = globals.state.status.timestamp + 1 - Date.now() / 1e3;
     const engineIsBusy =
@@ -681,22 +504,23 @@
       );
     }
 
-    if (this.omniboxMode === OmniboxMode.Command) {
+    const omniboxMode = globals.omnibox.omniboxMode;
+
+    if (omniboxMode === OmniboxMode.Command) {
       return this.renderCommandOmnibox();
-    } else if (this.omniboxMode === OmniboxMode.Prompt) {
+    } else if (omniboxMode === OmniboxMode.Prompt) {
       return this.renderPromptOmnibox();
-    } else if (this.omniboxMode === OmniboxMode.Query) {
+    } else if (omniboxMode === OmniboxMode.Query) {
       return this.renderQueryOmnibox();
-    } else if (this.omniboxMode === OmniboxMode.Search) {
+    } else if (omniboxMode === OmniboxMode.Search) {
       return this.renderSearchOmnibox();
     } else {
-      const x: never = this.omniboxMode;
-      throw new Error(`Unhandled omnibox mode ${x}`);
+      assertUnreachable(omniboxMode);
     }
   }
 
   renderPromptOmnibox(): m.Children {
-    const prompt = assertExists(this.pendingPrompt);
+    const prompt = assertExists(globals.omnibox.pendingPrompt);
 
     let options: OmniboxOption[] | undefined = undefined;
 
@@ -705,7 +529,7 @@
         prompt.options,
         ({displayName}) => displayName,
       );
-      const result = fuzzy.find(this.omniboxText);
+      const result = fuzzy.find(globals.omnibox.text);
       options = result.map((result) => {
         return {
           key: result.item.key,
@@ -715,27 +539,27 @@
     }
 
     return m(Omnibox, {
-      value: this.omniboxText,
+      value: globals.omnibox.text,
       placeholder: prompt.text,
       inputRef: App.OMNIBOX_INPUT_REF,
       extraClasses: 'prompt-mode',
       closeOnOutsideClick: true,
       options,
-      selectedOptionIndex: this.omniboxSelectionIndex,
+      selectedOptionIndex: globals.omnibox.omniboxSelectionIndex,
       onSelectedOptionChanged: (index) => {
-        this.omniboxSelectionIndex = index;
+        globals.omnibox.setOmniboxSelectionIndex(index);
         raf.scheduleFullRedraw();
       },
       onInput: (value) => {
-        this.omniboxText = value;
-        this.omniboxSelectionIndex = 0;
+        globals.omnibox.setText(value);
+        globals.omnibox.setOmniboxSelectionIndex(0);
         raf.scheduleFullRedraw();
       },
       onSubmit: (value, _alt) => {
-        this.resolvePrompt(value);
+        globals.omnibox.resolvePrompt(value);
       },
       onClose: () => {
-        this.rejectPrompt();
+        globals.omnibox.rejectPrompt();
       },
     });
   }
@@ -744,7 +568,7 @@
     const cmdMgr = globals.commandManager;
 
     // Fuzzy-filter commands by the filter string.
-    const filteredCmds = cmdMgr.fuzzyFilterCommands(this.omniboxText);
+    const filteredCmds = cmdMgr.fuzzyFilterCommands(globals.omnibox.text);
 
     // Create an array of commands with attached heuristics from the recent
     // command register.
@@ -775,36 +599,35 @@
     });
 
     return m(Omnibox, {
-      value: this.omniboxText,
+      value: globals.omnibox.text,
       placeholder: 'Filter commands...',
       inputRef: App.OMNIBOX_INPUT_REF,
       extraClasses: 'command-mode',
       options,
       closeOnSubmit: true,
       closeOnOutsideClick: true,
-      selectedOptionIndex: this.omniboxSelectionIndex,
+      selectedOptionIndex: globals.omnibox.omniboxSelectionIndex,
       onSelectedOptionChanged: (index) => {
-        this.omniboxSelectionIndex = index;
+        globals.omnibox.setOmniboxSelectionIndex(index);
         raf.scheduleFullRedraw();
       },
       onInput: (value) => {
-        this.omniboxText = value;
-        this.omniboxSelectionIndex = 0;
+        globals.omnibox.setText(value);
+        globals.omnibox.setOmniboxSelectionIndex(0);
         raf.scheduleFullRedraw();
       },
       onClose: () => {
         if (this.omniboxInputEl) {
           this.omniboxInputEl.blur();
         }
-        this.enterSearchMode(false);
-        raf.scheduleFullRedraw();
+        globals.omnibox.reset();
       },
       onSubmit: (key: string) => {
         this.addRecentCommand(key);
         cmdMgr.runCommand(key);
       },
       onGoBack: () => {
-        this.enterSearchMode(false);
+        globals.omnibox.reset();
       },
     });
   }
@@ -820,13 +643,13 @@
   renderQueryOmnibox(): m.Children {
     const ph = 'e.g. select * from sched left join thread using(utid) limit 10';
     return m(Omnibox, {
-      value: this.queryText,
+      value: globals.omnibox.text,
       placeholder: ph,
       inputRef: App.OMNIBOX_INPUT_REF,
       extraClasses: 'query-mode',
 
       onInput: (value) => {
-        this.queryText = value;
+        globals.omnibox.setText(value);
         raf.scheduleFullRedraw();
       },
       onSubmit: (query, alt) => {
@@ -838,15 +661,15 @@
         addQueryResultsTab(config, tag);
       },
       onClose: () => {
-        this.queryText = '';
+        globals.omnibox.setText('');
         if (this.omniboxInputEl) {
           this.omniboxInputEl.blur();
         }
-        this.enterSearchMode(false);
+        globals.omnibox.reset();
         raf.scheduleFullRedraw();
       },
       onGoBack: () => {
-        this.enterSearchMode(false);
+        globals.omnibox.reset();
       },
     });
   }
@@ -863,10 +686,10 @@
       onInput: (value, prev) => {
         if (prev === '') {
           if (value === '>') {
-            this.enterCommandMode();
+            globals.omnibox.setMode(OmniboxMode.Command);
             return;
           } else if (value === ':') {
-            this.enterQueryMode();
+            globals.omnibox.setMode(OmniboxMode.Query);
             return;
           }
         }
@@ -985,32 +808,20 @@
   }
 
   private maybeFocusOmnibar() {
-    if (this.focusOmniboxNextRender) {
+    if (globals.omnibox.focusOmniboxNextRender) {
       const omniboxEl = this.omniboxInputEl;
       if (omniboxEl) {
         omniboxEl.focus();
-        if (this.pendingCursorPlacement === -1) {
+        if (globals.omnibox.pendingCursorPlacement === undefined) {
           omniboxEl.select();
         } else {
           omniboxEl.setSelectionRange(
-            this.pendingCursorPlacement,
-            this.pendingCursorPlacement,
+            globals.omnibox.pendingCursorPlacement,
+            globals.omnibox.pendingCursorPlacement,
           );
-          this.pendingCursorPlacement = -1;
         }
       }
-      this.focusOmniboxNextRender = false;
+      globals.omnibox.clearOmniboxFocusFlag();
     }
   }
 }
-
-// Returns the time span of the current selection, or the visible window if
-// there is no current selection.
-function getTimeSpanOfSelectionOrVisibleWindow(): Span<time, duration> {
-  const range = globals.findTimeRangeOfSelection();
-  if (exists(range)) {
-    return new TimeSpan(range.start, range.end);
-  } else {
-    return globals.stateVisibleTime();
-  }
-}
diff --git a/ui/src/frontend/error_dialog.ts b/ui/src/frontend/error_dialog.ts
index f2cea37..835292e 100644
--- a/ui/src/frontend/error_dialog.ts
+++ b/ui/src/frontend/error_dialog.ts
@@ -36,6 +36,7 @@
   // Here we rely on the exception message from onCannotGrowMemory function
   if (
     err.message.includes('Cannot enlarge memory') ||
+    err.stack.some((entry) => entry.name.includes('OutOfMemoryHandler')) ||
     err.stack.some((entry) => entry.name.includes('_emscripten_resize_heap')) ||
     err.stack.some((entry) => entry.name.includes('sbrk')) ||
     /^out of memory$/m.exec(err.message)
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index 504b46f..46d6cbe 100644
--- a/ui/src/frontend/globals.ts
+++ b/ui/src/frontend/globals.ts
@@ -56,6 +56,7 @@
 import {PxSpan, TimeScale} from './time_scale';
 import {SelectionManager, LegacySelection} from '../core/selection_manager';
 import {exists} from '../base/utils';
+import {OmniboxManager} from './omnibox_manager';
 
 const INSTANT_FOCUS_DURATION = 1n;
 const INCOMPLETE_SLICE_DURATION = 30_000n;
@@ -299,6 +300,8 @@
   private _selectionManager = new SelectionManager(this._store);
   private _hasFtrace: boolean = false;
 
+  omnibox = new OmniboxManager();
+
   scrollToTrackKey?: string | number;
   httpRpcState: HttpRpcState = {connected: false};
   newVersionAvailable = false;
@@ -868,4 +871,15 @@
   }
 }
 
+// Returns the time span of the current selection, or the visible window if
+// there is no current selection.
+export function getTimeSpanOfSelectionOrVisibleWindow(): Span<time, duration> {
+  const range = globals.findTimeRangeOfSelection();
+  if (exists(range)) {
+    return new TimeSpan(range.start, range.end);
+  } else {
+    return globals.stateVisibleTime();
+  }
+}
+
 export const globals = new Globals();
diff --git a/ui/src/frontend/omnibox_manager.ts b/ui/src/frontend/omnibox_manager.ts
new file mode 100644
index 0000000..41d795b
--- /dev/null
+++ b/ui/src/frontend/omnibox_manager.ts
@@ -0,0 +1,156 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import {raf} from '../core/raf_scheduler';
+
+export enum OmniboxMode {
+  Search,
+  Query,
+  Command,
+  Prompt,
+}
+
+export interface PromptOption {
+  key: string;
+  displayName: string;
+}
+
+interface Prompt {
+  text: string;
+  options?: PromptOption[];
+  resolve(result: string): void;
+  reject(): void;
+}
+
+const defaultMode = OmniboxMode.Search;
+
+export class OmniboxManager {
+  private _omniboxMode = defaultMode;
+  private _focusOmniboxNextRender = false;
+  private _pendingCursorPlacement?: number;
+  private _pendingPrompt?: Prompt;
+  private _text = '';
+  private _omniboxSelectionIndex = 0;
+
+  get omniboxMode(): OmniboxMode {
+    return this._omniboxMode;
+  }
+
+  get pendingPrompt(): Prompt | undefined {
+    return this._pendingPrompt;
+  }
+
+  get text(): string {
+    return this._text;
+  }
+
+  get omniboxSelectionIndex(): number {
+    return this._omniboxSelectionIndex;
+  }
+
+  get focusOmniboxNextRender(): boolean {
+    return this._focusOmniboxNextRender;
+  }
+
+  get pendingCursorPlacement(): number | undefined {
+    return this._pendingCursorPlacement;
+  }
+
+  setText(value: string): void {
+    this._text = value;
+  }
+
+  setOmniboxSelectionIndex(index: number): void {
+    this._omniboxSelectionIndex = index;
+  }
+
+  focusOmnibox(cursorPlacement?: number): void {
+    this._focusOmniboxNextRender = true;
+    this._pendingCursorPlacement = cursorPlacement;
+    raf.scheduleFullRedraw();
+  }
+
+  clearOmniboxFocusFlag(): void {
+    this._focusOmniboxNextRender = false;
+    this._pendingCursorPlacement = undefined;
+  }
+
+  setMode(mode: OmniboxMode): void {
+    this._omniboxMode = mode;
+    this._focusOmniboxNextRender = true;
+    this.resetOmniboxText();
+    this.rejectPendingPrompt();
+    raf.scheduleFullRedraw();
+  }
+
+  // Start a prompt. If options are supplied, the user must pick one from the
+  // list, otherwise the input is free-form text.
+  prompt(text: string, options?: PromptOption[]): Promise<string> {
+    this._omniboxMode = OmniboxMode.Prompt;
+    this.resetOmniboxText();
+    this.rejectPendingPrompt();
+
+    const promise = new Promise<string>((resolve, reject) => {
+      this._pendingPrompt = {
+        text,
+        options,
+        resolve,
+        reject,
+      };
+    });
+
+    this._focusOmniboxNextRender = true;
+    raf.scheduleFullRedraw();
+
+    return promise;
+  }
+
+  // Resolve the pending prompt with a value to return to the prompter.
+  resolvePrompt(value: string): void {
+    if (this._pendingPrompt) {
+      this._pendingPrompt.resolve(value);
+      this._pendingPrompt = undefined;
+    }
+    this.setMode(OmniboxMode.Search);
+  }
+
+  // Reject the prompt outright. Doing this will force the owner of the prompt
+  // promise to catch, so only do this when things go seriously wrong.
+  // Use |resolvePrompt(null)| to indicate cancellation.
+  rejectPrompt(): void {
+    if (this._pendingPrompt) {
+      this._pendingPrompt.reject();
+      this._pendingPrompt = undefined;
+    }
+    this.setMode(OmniboxMode.Search);
+  }
+
+  reset(): void {
+    this.setMode(defaultMode);
+    this.resetOmniboxText();
+    raf.scheduleFullRedraw();
+  }
+
+  private rejectPendingPrompt() {
+    if (this._pendingPrompt) {
+      this._pendingPrompt.reject();
+      this._pendingPrompt = undefined;
+    }
+  }
+
+  private resetOmniboxText() {
+    this._text = '';
+    this._omniboxSelectionIndex = 0;
+  }
+}
diff --git a/ui/src/plugins/com.google.PixelMemory/OWNERS b/ui/src/plugins/com.google.PixelMemory/OWNERS
new file mode 100644
index 0000000..249152a
--- /dev/null
+++ b/ui/src/plugins/com.google.PixelMemory/OWNERS
@@ -0,0 +1 @@
+liumartin@google.com
diff --git a/ui/src/plugins/com.google.PixelMemory/index.ts b/ui/src/plugins/com.google.PixelMemory/index.ts
new file mode 100644
index 0000000..c79faf2
--- /dev/null
+++ b/ui/src/plugins/com.google.PixelMemory/index.ts
@@ -0,0 +1,63 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import {Plugin, PluginContextTrace, PluginDescriptor} from '../../public';
+
+import {addDebugCounterTrack} from '../../frontend/debug_tracks';
+
+class PixelMemory implements Plugin {
+  async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
+    ctx.registerCommand({
+      id: 'dev.perfetto.PixelMemory#ShowTotalMemory',
+      name: 'Add tracks: show a process total memory',
+      callback: async (pid) => {
+        if (pid === undefined) {
+          pid = prompt('Enter a process pid', '');
+          if (pid === null) return;
+        }
+        const RSS_ALL = `
+          INCLUDE PERFETTO MODULE memory.linux.process;
+          INCLUDE PERFETTO MODULE memory.android.gpu;
+
+          DROP TABLE IF EXISTS process_mem_rss_anon_file_shmem_swap_gpu;
+
+          CREATE VIRTUAL TABLE process_mem_rss_anon_file_shmem_swap_gpu
+          USING
+            SPAN_OUTER_JOIN(
+              memory_gpu_per_process PARTITIONED upid, memory_rss_and_swap_per_process PARTITIONED upid);
+        `;
+        await ctx.engine.query(RSS_ALL);
+        await addDebugCounterTrack(
+          {
+            sqlSource: `
+                SELECT
+                  ts,
+                  COALESCE(rss_and_swap, 0) + COALESCE(gpu_memory, 0) AS value
+                FROM process_mem_rss_anon_file_shmem_swap_gpu
+                WHERE pid = ${pid}
+            `,
+            columns: ['ts', 'value'],
+          },
+          pid + '_rss_anon_file_swap_shmem_gpu',
+          {ts: 'ts', value: 'value'},
+        );
+      },
+    });
+  }
+}
+
+export const plugin: PluginDescriptor = {
+  pluginId: 'com.google.PixelMemory',
+  plugin: PixelMemory,
+};
diff --git a/ui/src/plugins/dev.perfetto.TimelineSync/index.ts b/ui/src/plugins/dev.perfetto.TimelineSync/index.ts
index 74f2554..2ebd860 100644
--- a/ui/src/plugins/dev.perfetto.TimelineSync/index.ts
+++ b/ui/src/plugins/dev.perfetto.TimelineSync/index.ts
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import m from 'mithril';
+
 import {
   Plugin,
   PluginContext,
@@ -19,17 +21,23 @@
   PluginDescriptor,
 } from '../../public';
 import {duration, Span, Time, time, TimeSpan} from '../../base/time';
+import {redrawModal, showModal} from '../../widgets/modal';
 
 const PLUGIN_ID = 'dev.perfetto.TimelineSync';
 const DEFAULT_BROADCAST_CHANNEL = `${PLUGIN_ID}#broadcastChannel`;
 const VIEWPORT_UPDATE_THROTTLE_TIME_FOR_SENDING_AFTER_RECEIVING_MS = 1_000;
 const BIGINT_PRECISION_MULTIPLIER = 1_000_000_000n;
+const ADVERTISE_PERIOD_MS = 15_000;
+const DEFAULT_SESSION_ID = 1;
 type ClientId = number;
+type SessionId = number;
 
 /**
  * Synchronizes the timeline of 2 or more perfetto traces.
  *
- * To trigger the sync, the command needs to be enabled.
+ * To trigger the sync, the command needs to be executed on one tab. It will
+ * prompt a list of other tabs to keep in sync. Each tab advertise itself
+ * on a BroadcastChannel upon trace load.
  *
  * This is able to sync between traces recorded at different times, even if
  * their durations don't match. The initial viewport bound for each trace is
@@ -38,11 +46,17 @@
 class TimelineSync implements Plugin {
   private _chan?: BroadcastChannel;
   private _ctx?: PluginContextTrace;
+  private _traceLoadTime = 0;
   // Attached to broadcast messages to allow other windows to remap viewports.
-  private _clientId: ClientId = 0;
+  private readonly _clientId: ClientId = Math.floor(Math.random() * 1_000_000);
   // Used to throttle sending updates after one has been received.
   private _lastReceivedUpdateMillis: number = 0;
   private _lastViewportBounds?: ViewportBounds;
+  private _advertisedClients = new Map<ClientId, ClientInfo>();
+  private _sessionId: SessionId = 0;
+  // Used when the url passes ?dev.perfetto.TimelineSync:enable to auto-enable
+  // timeline sync on trace load.
+  private _sessionidFromUrl: SessionId = 0;
 
   // Contains the Viewport bounds of this window when it received the first sync
   // message from another one. This is used to re-scale timestamps, so that we
@@ -53,42 +67,169 @@
     ViewportBoundsSnapshot
   >();
 
-  private _active: boolean = false;
-
   onActivate(ctx: PluginContext): void {
     ctx.registerCommand({
       id: `dev.perfetto.SplitScreen#enableTimelineSync`,
-      name: 'Enable timeline sync with open windows',
-      callback: this.enableTimelineSync.bind(this),
+      name: 'Enable timeline sync with other Perfetto UI tabs',
+      callback: () => this.showTimelineSyncDialog(),
     });
     ctx.registerCommand({
       id: `dev.perfetto.SplitScreen#disableTimelineSync`,
       name: 'Disable timeline sync',
-      callback: this.disableTimelineSync.bind(this),
+      callback: () => this.disableTimelineSync(this._sessionId),
     });
+
+    // Start advertising this tab. This allows the command run in other
+    // instances to discover us.
+    this._chan = new BroadcastChannel(DEFAULT_BROADCAST_CHANNEL);
+    this._chan.onmessage = this.onmessage.bind(this);
+    document.addEventListener('visibilitychange', () => this.advertise());
+    setInterval(() => this.advertise(), ADVERTISE_PERIOD_MS);
+
+    // Allow auto-enabling of timeline sync from the URI. The user can
+    // optionally specify a session id, otherwise we just use a default one.
+    const m = /dev.perfetto.TimelineSync:enable(=\d+)?/.exec(location.hash);
+    if (m !== null) {
+      this._sessionidFromUrl = m[1]
+        ? parseInt(m[1].substring(1))
+        : DEFAULT_SESSION_ID;
+    }
   }
 
   onDeactivate(_: PluginContext) {
-    this.disableTimelineSync();
+    this.disableTimelineSync(this._sessionId);
   }
 
   async onTraceLoad(ctx: PluginContextTrace) {
     this._ctx = ctx;
+    this._traceLoadTime = Date.now();
+    this.advertise();
+    if (this._sessionidFromUrl !== 0) {
+      this.enableTimelineSync(this._sessionidFromUrl);
+    }
   }
 
-  private enableTimelineSync() {
-    this._active = true;
+  async onTraceUnload(_: PluginContextTrace) {
+    this.disableTimelineSync(this._sessionId);
+    this._ctx = undefined;
+  }
+
+  private advertise() {
+    if (this._ctx === undefined) return; // Don't advertise if no trace loaded.
+    this._chan?.postMessage({
+      perfettoSync: {
+        cmd: 'MSG_ADVERTISE',
+        title: document.title,
+        traceLoadTime: this._traceLoadTime,
+      },
+      clientId: this._clientId,
+    } as SyncMessage);
+  }
+
+  private showTimelineSyncDialog() {
+    const selectedClients = new Array<ClientId>();
+
+    // This nested function is invoked when the modal dialog buton is pressed.
+    const doStartSession = () => {
+      // Disable any prior session.
+      this.disableTimelineSync(this._sessionId);
+
+      const clients = selectedClients.concat(this._clientId);
+      this._sessionId = Math.floor(Math.random() * 1_000_000);
+      this._chan?.postMessage({
+        perfettoSync: {
+          cmd: 'MSG_SESSION_START',
+          sessionId: this._sessionId,
+          clients: clients,
+        },
+        clientId: this._clientId,
+      } as SyncMessage);
+      this._initialBoundsForSibling.clear();
+      this.scheduleViewportUpdateMessage();
+    };
+
+    // The function below is called on every mithril render pass. It's important
+    // that this function re-computes the list of other clients on every pass.
+    // The user will go to other tabs (which causes an advertise due to the
+    // visibilitychange listener) and come back on here while the modal dialog
+    // is still being displayed.
+    const renderModalContents = (): m.Children => {
+      const children: m.Children = [];
+      this.purgeInactiveClients();
+      const clients = Array.from(this._advertisedClients.entries());
+      clients.sort((a, b) => b[1].traceLoadTime - a[1].traceLoadTime);
+      for (const [clientId, info] of clients) {
+        const opened = new Date(info.traceLoadTime).toLocaleTimeString();
+        const attrs: {value: number; selected?: boolean} = {value: clientId};
+        if (this._advertisedClients.size === 1) {
+          attrs.selected = true;
+        }
+        children.push(m('option', attrs, `${info.title} (${opened})`));
+      }
+      return m(
+        'div',
+        {style: 'display: flex;  flex-direction: column;'},
+        m(
+          'div',
+          'Select the perfetto UI tab(s) you want to keep in sync ' +
+            '(Ctrl+Click to select many).',
+        ),
+        m(
+          'div',
+          "If you don't see the trace listed here, temporarily focus the " +
+            'corresponding browser tab and then come back here.',
+        ),
+        m(
+          'select[multiple=multiple][size=8]',
+          {
+            onchange: (e: Event) => {
+              selectedClients.splice(0);
+              const sel = (e.target as HTMLSelectElement).selectedOptions;
+              for (let i = 0; i < sel.length; i++) {
+                const clientId = parseInt(sel[i].value);
+                if (!isNaN(clientId)) selectedClients.push(clientId);
+              }
+            },
+          },
+          children,
+        ),
+      );
+    };
+
+    showModal({
+      title: 'Synchronize timeline across several tabs',
+      content: renderModalContents,
+      buttons: [
+        {
+          primary: true,
+          text: `Synchronize timelines`,
+          action: doStartSession,
+        },
+      ],
+    });
+  }
+
+  private enableTimelineSync(sessionId: SessionId) {
+    if (sessionId === this._sessionId) return; // Already in this session id.
+    this._sessionId = sessionId;
     this._initialBoundsForSibling.clear();
-    this._clientId = this.generateClientId();
-    this._chan = new BroadcastChannel(DEFAULT_BROADCAST_CHANNEL);
-    this._chan.onmessage = this.onmessage.bind(this);
     this.scheduleViewportUpdateMessage();
   }
 
-  private disableTimelineSync() {
-    this._active = false;
+  private disableTimelineSync(sessionId: SessionId, skipMsg = false) {
+    if (sessionId !== this._sessionId || this._sessionId === 0) return;
+
+    if (!skipMsg) {
+      this._chan?.postMessage({
+        perfettoSync: {
+          cmd: 'MSG_SESSION_STOP',
+          sessionId: this._sessionId,
+        },
+        clientId: this._clientId,
+      } as SyncMessage);
+    }
+    this._sessionId = 0;
     this._initialBoundsForSibling.clear();
-    this._chan?.close();
   }
 
   private shouldThrottleViewportUpdates() {
@@ -99,7 +240,7 @@
   }
 
   private scheduleViewportUpdateMessage() {
-    if (!this._active) return;
+    if (!this.active) return;
     const currentViewport = this.getCurrentViewportBounds();
     if (
       (!this._lastViewportBounds ||
@@ -116,6 +257,7 @@
     this._chan?.postMessage({
       perfettoSync: {
         cmd: 'MSG_SET_VIEWPORT',
+        sessionId: this._sessionId,
         viewportBounds,
       },
       clientId: this._clientId,
@@ -123,12 +265,35 @@
   }
 
   private onmessage(msg: MessageEvent) {
+    if (this._ctx === undefined) return; // Trace unloaded
     if (!('perfettoSync' in msg.data)) return;
     const msgData = msg.data as SyncMessage;
     const sync = msgData.perfettoSync;
     switch (sync.cmd) {
+      case 'MSG_ADVERTISE':
+        if (msgData.clientId !== this._clientId) {
+          this._advertisedClients.set(msgData.clientId, {
+            title: sync.title,
+            traceLoadTime: sync.traceLoadTime,
+            lastHeartbeat: Date.now(),
+          });
+          this.purgeInactiveClients();
+          redrawModal();
+        }
+        break;
+      case 'MSG_SESSION_START':
+        if (sync.clients.includes(this._clientId)) {
+          this.enableTimelineSync(sync.sessionId);
+        }
+        break;
+      case 'MSG_SESSION_STOP':
+        this.disableTimelineSync(sync.sessionId, /* skipMsg= */ true);
+        break;
       case 'MSG_SET_VIEWPORT':
-        this.onViewportSyncReceived(sync.viewportBounds, msgData.clientId);
+        if (sync.sessionId === this._sessionId) {
+          this.onViewportSyncReceived(sync.viewportBounds, msgData.clientId);
+        }
+        break;
     }
   }
 
@@ -136,6 +301,7 @@
     requestViewBounds: ViewportBounds,
     source: ClientId,
   ) {
+    if (!this.active) return;
     this.cacheSiblingInitialBoundIfNeeded(requestViewBounds, source);
     const remappedViewport = this.remapViewportBounds(
       requestViewBounds,
@@ -222,8 +388,17 @@
     return this._ctx!.timeline.viewport;
   }
 
-  private generateClientId(): ClientId {
-    return Math.floor(Math.random() * 1_000_000);
+  private purgeInactiveClients() {
+    const now = Date.now();
+    const TIMEOUT_MS = 30_000;
+    for (const [clientId, info] of this._advertisedClients.entries()) {
+      if (now - info.lastHeartbeat < TIMEOUT_MS) continue;
+      this._advertisedClients.delete(clientId);
+    }
+  }
+
+  private get active() {
+    return this._sessionId !== 0;
   }
 }
 
@@ -236,17 +411,45 @@
 
 interface MsgSetViewport {
   cmd: 'MSG_SET_VIEWPORT';
+  sessionId: SessionId;
   viewportBounds: ViewportBounds;
 }
 
+interface MsgAdvertise {
+  cmd: 'MSG_ADVERTISE';
+  title: string;
+  traceLoadTime: number;
+}
+
+interface MsgSessionStart {
+  cmd: 'MSG_SESSION_START';
+  sessionId: SessionId;
+  clients: ClientId[];
+}
+
+interface MsgSessionStop {
+  cmd: 'MSG_SESSION_STOP';
+  sessionId: SessionId;
+}
+
 // In case of new messages, they should be "or-ed" here.
-type SyncMessages = MsgSetViewport;
+type SyncMessages =
+  | MsgSetViewport
+  | MsgAdvertise
+  | MsgSessionStart
+  | MsgSessionStop;
 
 interface SyncMessage {
   perfettoSync: SyncMessages;
   clientId: ClientId;
 }
 
+interface ClientInfo {
+  title: string;
+  lastHeartbeat: number; // Datetime.now() of the last MSG_ADVERTISE.
+  traceLoadTime: number; // Datetime.now() of the onTraceLoad().
+}
+
 export const plugin: PluginDescriptor = {
   pluginId: PLUGIN_ID,
   plugin: TimelineSync,
diff --git a/ui/src/public/index.ts b/ui/src/public/index.ts
index 91f2b36..9e540fd 100644
--- a/ui/src/public/index.ts
+++ b/ui/src/public/index.ts
@@ -23,6 +23,7 @@
 import {Engine} from '../trace_processor/engine';
 import {UntypedEventSet} from '../core/event_set';
 import {TraceContext} from '../frontend/globals';
+import {PromptOption} from '../frontend/omnibox_manager';
 
 export {Engine} from '../trace_processor/engine';
 export {
@@ -35,6 +36,7 @@
 } from '../trace_processor/query_result';
 export {BottomTabToSCSAdapter} from './utils';
 export {createStore, Migrate, Store} from '../base/store';
+export {PromptOption} from '../frontend/omnibox_manager';
 
 // This is a temporary fix until this is available in the plugin API.
 export {
@@ -435,6 +437,8 @@
   mountStore<T>(migrate: Migrate<T>): Store<T>;
 
   trace: TraceContext;
+
+  prompt(text: string, options?: PromptOption[]): Promise<string>;
 }
 
 export interface Plugin {
diff --git a/ui/src/widgets/modal.ts b/ui/src/widgets/modal.ts
index 778f07e..3c85652 100644
--- a/ui/src/widgets/modal.ts
+++ b/ui/src/widgets/modal.ts
@@ -228,6 +228,15 @@
   return returnedClosePromise;
 }
 
+// Technically we don't need to redraw the whole app, but it's the more
+// pragmatic option. This is exposed to keep the plugin code more clear, so it's
+// evident why a redraw is requested.
+export function redrawModal() {
+  if (currentModal !== undefined) {
+    scheduleFullRedraw();
+  }
+}
+
 // Closes the full-screen modal dialog (if any).
 // `key` is optional: if provided it will close the modal dialog only if the key
 // matches. This is to avoid accidentally closing another dialog that popped