Merge "Add data source config for Input Tracing on Android" into main
diff --git a/Android.bp b/Android.bp
index 9f3e3e1..aeeb89a 100644
--- a/Android.bp
+++ b/Android.bp
@@ -5968,6 +5968,7 @@
         "protos/perfetto/trace/ftrace/printk.proto",
         "protos/perfetto/trace/ftrace/raw_syscalls.proto",
         "protos/perfetto/trace/ftrace/regulator.proto",
+        "protos/perfetto/trace/ftrace/rpm.proto",
         "protos/perfetto/trace/ftrace/samsung.proto",
         "protos/perfetto/trace/ftrace/sched.proto",
         "protos/perfetto/trace/ftrace/scm.proto",
@@ -6388,6 +6389,7 @@
         "protos/perfetto/trace/ftrace/printk.proto",
         "protos/perfetto/trace/ftrace/raw_syscalls.proto",
         "protos/perfetto/trace/ftrace/regulator.proto",
+        "protos/perfetto/trace/ftrace/rpm.proto",
         "protos/perfetto/trace/ftrace/samsung.proto",
         "protos/perfetto/trace/ftrace/sched.proto",
         "protos/perfetto/trace/ftrace/scm.proto",
@@ -6470,6 +6472,7 @@
         "external/perfetto/protos/perfetto/trace/ftrace/printk.gen.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/raw_syscalls.gen.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/regulator.gen.cc",
+        "external/perfetto/protos/perfetto/trace/ftrace/rpm.gen.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/samsung.gen.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/sched.gen.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/scm.gen.cc",
@@ -6552,6 +6555,7 @@
         "external/perfetto/protos/perfetto/trace/ftrace/printk.gen.h",
         "external/perfetto/protos/perfetto/trace/ftrace/raw_syscalls.gen.h",
         "external/perfetto/protos/perfetto/trace/ftrace/regulator.gen.h",
+        "external/perfetto/protos/perfetto/trace/ftrace/rpm.gen.h",
         "external/perfetto/protos/perfetto/trace/ftrace/samsung.gen.h",
         "external/perfetto/protos/perfetto/trace/ftrace/sched.gen.h",
         "external/perfetto/protos/perfetto/trace/ftrace/scm.gen.h",
@@ -6630,6 +6634,7 @@
         "protos/perfetto/trace/ftrace/printk.proto",
         "protos/perfetto/trace/ftrace/raw_syscalls.proto",
         "protos/perfetto/trace/ftrace/regulator.proto",
+        "protos/perfetto/trace/ftrace/rpm.proto",
         "protos/perfetto/trace/ftrace/samsung.proto",
         "protos/perfetto/trace/ftrace/sched.proto",
         "protos/perfetto/trace/ftrace/scm.proto",
@@ -6711,6 +6716,7 @@
         "external/perfetto/protos/perfetto/trace/ftrace/printk.pb.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/raw_syscalls.pb.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/regulator.pb.cc",
+        "external/perfetto/protos/perfetto/trace/ftrace/rpm.pb.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/samsung.pb.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/sched.pb.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/scm.pb.cc",
@@ -6792,6 +6798,7 @@
         "external/perfetto/protos/perfetto/trace/ftrace/printk.pb.h",
         "external/perfetto/protos/perfetto/trace/ftrace/raw_syscalls.pb.h",
         "external/perfetto/protos/perfetto/trace/ftrace/regulator.pb.h",
+        "external/perfetto/protos/perfetto/trace/ftrace/rpm.pb.h",
         "external/perfetto/protos/perfetto/trace/ftrace/samsung.pb.h",
         "external/perfetto/protos/perfetto/trace/ftrace/sched.pb.h",
         "external/perfetto/protos/perfetto/trace/ftrace/scm.pb.h",
@@ -6870,6 +6877,7 @@
         "protos/perfetto/trace/ftrace/printk.proto",
         "protos/perfetto/trace/ftrace/raw_syscalls.proto",
         "protos/perfetto/trace/ftrace/regulator.proto",
+        "protos/perfetto/trace/ftrace/rpm.proto",
         "protos/perfetto/trace/ftrace/samsung.proto",
         "protos/perfetto/trace/ftrace/sched.proto",
         "protos/perfetto/trace/ftrace/scm.proto",
@@ -6952,6 +6960,7 @@
         "external/perfetto/protos/perfetto/trace/ftrace/printk.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/raw_syscalls.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/regulator.pbzero.cc",
+        "external/perfetto/protos/perfetto/trace/ftrace/rpm.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/samsung.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/sched.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/scm.pbzero.cc",
@@ -7034,6 +7043,7 @@
         "external/perfetto/protos/perfetto/trace/ftrace/printk.pbzero.h",
         "external/perfetto/protos/perfetto/trace/ftrace/raw_syscalls.pbzero.h",
         "external/perfetto/protos/perfetto/trace/ftrace/regulator.pbzero.h",
+        "external/perfetto/protos/perfetto/trace/ftrace/rpm.pbzero.h",
         "external/perfetto/protos/perfetto/trace/ftrace/samsung.pbzero.h",
         "external/perfetto/protos/perfetto/trace/ftrace/sched.pbzero.h",
         "external/perfetto/protos/perfetto/trace/ftrace/scm.pbzero.h",
@@ -11124,6 +11134,7 @@
         "src/trace_processor/importers/common/event_tracker.cc",
         "src/trace_processor/importers/common/flow_tracker.cc",
         "src/trace_processor/importers/common/global_args_tracker.cc",
+        "src/trace_processor/importers/common/jit_cache.cc",
         "src/trace_processor/importers/common/mapping_tracker.cc",
         "src/trace_processor/importers/common/metadata_tracker.cc",
         "src/trace_processor/importers/common/process_tracker.cc",
@@ -11451,6 +11462,7 @@
         "src/trace_processor/importers/proto/chrome_system_probes_module.cc",
         "src/trace_processor/importers/proto/chrome_system_probes_parser.cc",
         "src/trace_processor/importers/proto/default_modules.cc",
+        "src/trace_processor/importers/proto/jit_tracker.cc",
         "src/trace_processor/importers/proto/memory_tracker_snapshot_module.cc",
         "src/trace_processor/importers/proto/memory_tracker_snapshot_parser.cc",
         "src/trace_processor/importers/proto/metadata_minimal_module.cc",
@@ -11491,6 +11503,7 @@
     srcs: [
         "src/trace_processor/importers/proto/active_chrome_processes_tracker_unittest.cc",
         "src/trace_processor/importers/proto/heap_graph_tracker_unittest.cc",
+        "src/trace_processor/importers/proto/jit_tracker_unittest.cc",
         "src/trace_processor/importers/proto/network_trace_module_unittest.cc",
         "src/trace_processor/importers/proto/perf_sample_tracker_unittest.cc",
         "src/trace_processor/importers/proto/profile_packet_sequence_state_unittest.cc",
@@ -11743,6 +11756,7 @@
         "src/trace_processor/metrics/sql/android/power_profile_data/raven.sql",
         "src/trace_processor/metrics/sql/android/power_profile_data/redfin.sql",
         "src/trace_processor/metrics/sql/android/power_profile_data/sargo.sql",
+        "src/trace_processor/metrics/sql/android/power_profile_data/shusky.sql",
         "src/trace_processor/metrics/sql/android/power_profile_data/sunfish.sql",
         "src/trace_processor/metrics/sql/android/power_profile_data/taimen.sql",
         "src/trace_processor/metrics/sql/android/power_profile_data/walleye.sql",
@@ -11871,6 +11885,7 @@
 filegroup {
     name: "perfetto_src_trace_processor_perfetto_sql_intrinsics_functions_functions",
     srcs: [
+        "src/trace_processor/perfetto_sql/intrinsics/functions/base64.cc",
         "src/trace_processor/perfetto_sql/intrinsics/functions/create_function.cc",
         "src/trace_processor/perfetto_sql/intrinsics/functions/create_view_function.cc",
         "src/trace_processor/perfetto_sql/intrinsics/functions/import.cc",
@@ -11940,6 +11955,7 @@
         "src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_sched_upid.cc",
         "src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_slice_layout.cc",
         "src/trace_processor/perfetto_sql/intrinsics/table_functions/flamegraph_construction_algorithms.cc",
+        "src/trace_processor/perfetto_sql/intrinsics/table_functions/interval_intersect.cc",
         "src/trace_processor/perfetto_sql/intrinsics/table_functions/table_info.cc",
     ],
 }
@@ -11970,6 +11986,7 @@
         "src/trace_processor/tables/android_tables.py",
         "src/trace_processor/tables/counter_tables.py",
         "src/trace_processor/tables/flow_tables.py",
+        "src/trace_processor/tables/jit_tables.py",
         "src/trace_processor/tables/memory_tables.py",
         "src/trace_processor/tables/metadata_tables.py",
         "src/trace_processor/tables/profiler_tables.py",
@@ -12048,6 +12065,7 @@
         "src/trace_processor/perfetto_sql/stdlib/common/args.sql",
         "src/trace_processor/perfetto_sql/stdlib/common/counters.sql",
         "src/trace_processor/perfetto_sql/stdlib/common/cpus.sql",
+        "src/trace_processor/perfetto_sql/stdlib/common/jit.sql",
         "src/trace_processor/perfetto_sql/stdlib/common/metadata.sql",
         "src/trace_processor/perfetto_sql/stdlib/common/percentiles.sql",
         "src/trace_processor/perfetto_sql/stdlib/common/slices.sql",
@@ -12066,8 +12084,10 @@
         "src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/timestamps.sql",
         "src/trace_processor/perfetto_sql/stdlib/graphs/dominator_tree.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/heap_graph_dominator_tree.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",
@@ -12076,6 +12096,7 @@
         "src/trace_processor/perfetto_sql/stdlib/sched/thread_executing_span.sql",
         "src/trace_processor/perfetto_sql/stdlib/sched/thread_level_parallelism.sql",
         "src/trace_processor/perfetto_sql/stdlib/sched/thread_state_flattened.sql",
+        "src/trace_processor/perfetto_sql/stdlib/sched/time_in_state.sql",
         "src/trace_processor/perfetto_sql/stdlib/sched/utilization/general.sql",
         "src/trace_processor/perfetto_sql/stdlib/sched/utilization/process.sql",
         "src/trace_processor/perfetto_sql/stdlib/sched/utilization/system.sql",
@@ -12085,6 +12106,7 @@
         "src/trace_processor/perfetto_sql/stdlib/slices/slices.sql",
         "src/trace_processor/perfetto_sql/stdlib/slices/with_context.sql",
         "src/trace_processor/perfetto_sql/stdlib/time/conversion.sql",
+        "src/trace_processor/perfetto_sql/stdlib/v8/jit.sql",
     ],
     cmd: "$(location tools/gen_amalgamated_sql.py) --namespace=stdlib --cpp-out=$(out) $(in)",
     out: [
@@ -12245,6 +12267,7 @@
         "src/trace_processor/tables/android_tables.py",
         "src/trace_processor/tables/counter_tables.py",
         "src/trace_processor/tables/flow_tables.py",
+        "src/trace_processor/tables/jit_tables.py",
         "src/trace_processor/tables/memory_tables.py",
         "src/trace_processor/tables/metadata_tables.py",
         "src/trace_processor/tables/profiler_tables.py",
@@ -12263,6 +12286,7 @@
         "src/trace_processor/tables/android_tables_py.h",
         "src/trace_processor/tables/counter_tables_py.h",
         "src/trace_processor/tables/flow_tables_py.h",
+        "src/trace_processor/tables/jit_tables_py.h",
         "src/trace_processor/tables/memory_tables_py.h",
         "src/trace_processor/tables/metadata_tables_py.h",
         "src/trace_processor/tables/profiler_tables_py.h",
@@ -12285,6 +12309,7 @@
         "src/trace_processor/tables/android_tables.py",
         "src/trace_processor/tables/counter_tables.py",
         "src/trace_processor/tables/flow_tables.py",
+        "src/trace_processor/tables/jit_tables.py",
         "src/trace_processor/tables/memory_tables.py",
         "src/trace_processor/tables/metadata_tables.py",
         "src/trace_processor/tables/profiler_tables.py",
@@ -12491,7 +12516,9 @@
     srcs: [
         "src/trace_redaction/find_package_uid.cc",
         "src/trace_redaction/populate_allow_lists.cc",
+        "src/trace_redaction/proto_util.cc",
         "src/trace_redaction/prune_package_list.cc",
+        "src/trace_redaction/scrub_ftrace_events.cc",
         "src/trace_redaction/scrub_trace_packet.cc",
         "src/trace_redaction/trace_redaction_framework.cc",
         "src/trace_redaction/trace_redactor.cc",
@@ -12503,7 +12530,9 @@
     name: "perfetto_src_trace_redaction_unittests",
     srcs: [
         "src/trace_redaction/find_package_uid_unittest.cc",
+        "src/trace_redaction/proto_util_unittest.cc",
         "src/trace_redaction/prune_package_list_unittest.cc",
+        "src/trace_redaction/scrub_ftrace_events_unittest.cc",
         "src/trace_redaction/scrub_trace_packet_unittest.cc",
     ],
 }
@@ -13478,6 +13507,7 @@
         "protos/perfetto/trace/ftrace/printk.proto",
         "protos/perfetto/trace/ftrace/raw_syscalls.proto",
         "protos/perfetto/trace/ftrace/regulator.proto",
+        "protos/perfetto/trace/ftrace/rpm.proto",
         "protos/perfetto/trace/ftrace/samsung.proto",
         "protos/perfetto/trace/ftrace/sched.proto",
         "protos/perfetto/trace/ftrace/scm.proto",
diff --git a/BUILD b/BUILD
index 587d4f9..64da64a 100644
--- a/BUILD
+++ b/BUILD
@@ -1474,6 +1474,8 @@
         "src/trace_processor/importers/common/flow_tracker.h",
         "src/trace_processor/importers/common/global_args_tracker.cc",
         "src/trace_processor/importers/common/global_args_tracker.h",
+        "src/trace_processor/importers/common/jit_cache.cc",
+        "src/trace_processor/importers/common/jit_cache.h",
         "src/trace_processor/importers/common/mapping_tracker.cc",
         "src/trace_processor/importers/common/mapping_tracker.h",
         "src/trace_processor/importers/common/metadata_tracker.cc",
@@ -1839,6 +1841,8 @@
         "src/trace_processor/importers/proto/chrome_system_probes_parser.h",
         "src/trace_processor/importers/proto/default_modules.cc",
         "src/trace_processor/importers/proto/default_modules.h",
+        "src/trace_processor/importers/proto/jit_tracker.cc",
+        "src/trace_processor/importers/proto/jit_tracker.h",
         "src/trace_processor/importers/proto/memory_tracker_snapshot_module.cc",
         "src/trace_processor/importers/proto/memory_tracker_snapshot_module.h",
         "src/trace_processor/importers/proto/memory_tracker_snapshot_parser.cc",
@@ -2029,6 +2033,7 @@
         "src/trace_processor/metrics/sql/android/power_profile_data/raven.sql",
         "src/trace_processor/metrics/sql/android/power_profile_data/redfin.sql",
         "src/trace_processor/metrics/sql/android/power_profile_data/sargo.sql",
+        "src/trace_processor/metrics/sql/android/power_profile_data/shusky.sql",
         "src/trace_processor/metrics/sql/android/power_profile_data/sunfish.sql",
         "src/trace_processor/metrics/sql/android/power_profile_data/taimen.sql",
         "src/trace_processor/metrics/sql/android/power_profile_data/walleye.sql",
@@ -2232,6 +2237,8 @@
 perfetto_filegroup(
     name = "src_trace_processor_perfetto_sql_intrinsics_functions_functions",
     srcs = [
+        "src/trace_processor/perfetto_sql/intrinsics/functions/base64.cc",
+        "src/trace_processor/perfetto_sql/intrinsics/functions/base64.h",
         "src/trace_processor/perfetto_sql/intrinsics/functions/clock_functions.h",
         "src/trace_processor/perfetto_sql/intrinsics/functions/create_function.cc",
         "src/trace_processor/perfetto_sql/intrinsics/functions/create_function.h",
@@ -2313,6 +2320,8 @@
         "src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_slice_layout.h",
         "src/trace_processor/perfetto_sql/intrinsics/table_functions/flamegraph_construction_algorithms.cc",
         "src/trace_processor/perfetto_sql/intrinsics/table_functions/flamegraph_construction_algorithms.h",
+        "src/trace_processor/perfetto_sql/intrinsics/table_functions/interval_intersect.cc",
+        "src/trace_processor/perfetto_sql/intrinsics/table_functions/interval_intersect.h",
         "src/trace_processor/perfetto_sql/intrinsics/table_functions/table_info.cc",
         "src/trace_processor/perfetto_sql/intrinsics/table_functions/table_info.h",
     ],
@@ -2407,6 +2416,7 @@
         "src/trace_processor/perfetto_sql/stdlib/common/args.sql",
         "src/trace_processor/perfetto_sql/stdlib/common/counters.sql",
         "src/trace_processor/perfetto_sql/stdlib/common/cpus.sql",
+        "src/trace_processor/perfetto_sql/stdlib/common/jit.sql",
         "src/trace_processor/perfetto_sql/stdlib/common/metadata.sql",
         "src/trace_processor/perfetto_sql/stdlib/common/percentiles.sql",
         "src/trace_processor/perfetto_sql/stdlib/common/slices.sql",
@@ -2460,6 +2470,7 @@
 perfetto_filegroup(
     name = "src_trace_processor_perfetto_sql_stdlib_intervals_intervals",
     srcs = [
+        "src/trace_processor/perfetto_sql/stdlib/intervals/intersect.sql",
         "src/trace_processor/perfetto_sql/stdlib/intervals/overlap.sql",
     ],
 )
@@ -2472,6 +2483,14 @@
     ],
 )
 
+# GN target: //src/trace_processor/perfetto_sql/stdlib/memory:memory
+perfetto_filegroup(
+    name = "src_trace_processor_perfetto_sql_stdlib_memory_memory",
+    srcs = [
+        "src/trace_processor/perfetto_sql/stdlib/memory/heap_graph_dominator_tree.sql",
+    ],
+)
+
 # GN target: //src/trace_processor/perfetto_sql/stdlib/pkvm:pkvm
 perfetto_filegroup(
     name = "src_trace_processor_perfetto_sql_stdlib_pkvm_pkvm",
@@ -2509,6 +2528,7 @@
         "src/trace_processor/perfetto_sql/stdlib/sched/thread_executing_span.sql",
         "src/trace_processor/perfetto_sql/stdlib/sched/thread_level_parallelism.sql",
         "src/trace_processor/perfetto_sql/stdlib/sched/thread_state_flattened.sql",
+        "src/trace_processor/perfetto_sql/stdlib/sched/time_in_state.sql",
     ],
 )
 
@@ -2531,6 +2551,14 @@
     ],
 )
 
+# GN target: //src/trace_processor/perfetto_sql/stdlib/v8:v8
+perfetto_filegroup(
+    name = "src_trace_processor_perfetto_sql_stdlib_v8_v8",
+    srcs = [
+        "src/trace_processor/perfetto_sql/stdlib/v8/jit.sql",
+    ],
+)
+
 # GN target: //src/trace_processor/perfetto_sql/stdlib:stdlib
 perfetto_cc_amalgamated_sql(
     name = "src_trace_processor_perfetto_sql_stdlib_stdlib",
@@ -2545,12 +2573,14 @@
         ":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_memory",
         ":src_trace_processor_perfetto_sql_stdlib_pkvm_pkvm",
         ":src_trace_processor_perfetto_sql_stdlib_prelude_prelude",
         ":src_trace_processor_perfetto_sql_stdlib_sched_sched",
         ":src_trace_processor_perfetto_sql_stdlib_sched_utilization_utilization",
         ":src_trace_processor_perfetto_sql_stdlib_slices_slices",
         ":src_trace_processor_perfetto_sql_stdlib_time_time",
+        ":src_trace_processor_perfetto_sql_stdlib_v8_v8",
     ],
     outs = [
         "src/trace_processor/perfetto_sql/stdlib/stdlib.h",
@@ -2658,6 +2688,7 @@
         "src/trace_processor/tables/android_tables.py",
         "src/trace_processor/tables/counter_tables.py",
         "src/trace_processor/tables/flow_tables.py",
+        "src/trace_processor/tables/jit_tables.py",
         "src/trace_processor/tables/memory_tables.py",
         "src/trace_processor/tables/metadata_tables.py",
         "src/trace_processor/tables/profiler_tables.py",
@@ -2672,6 +2703,7 @@
         "src/trace_processor/tables/android_tables_py.h",
         "src/trace_processor/tables/counter_tables_py.h",
         "src/trace_processor/tables/flow_tables_py.h",
+        "src/trace_processor/tables/jit_tables_py.h",
         "src/trace_processor/tables/memory_tables_py.h",
         "src/trace_processor/tables/metadata_tables_py.h",
         "src/trace_processor/tables/profiler_tables_py.h",
@@ -4670,6 +4702,7 @@
         "protos/perfetto/trace/ftrace/printk.proto",
         "protos/perfetto/trace/ftrace/raw_syscalls.proto",
         "protos/perfetto/trace/ftrace/regulator.proto",
+        "protos/perfetto/trace/ftrace/rpm.proto",
         "protos/perfetto/trace/ftrace/samsung.proto",
         "protos/perfetto/trace/ftrace/sched.proto",
         "protos/perfetto/trace/ftrace/scm.proto",
diff --git a/CHANGELOG b/CHANGELOG
index 4f79dd1..46a6cd3 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -4,7 +4,8 @@
   Trace Processor:
     *
   UI:
-    *
+    * Per-cpu scheduling tracks now distinguish real-time priority threads with
+      a hatched pattern and name prefix. Based on priority during switch-in.
   SDK:
     *
 
diff --git a/bazel/deps.bzl b/bazel/deps.bzl
index a105a63..ee17856 100644
--- a/bazel/deps.bzl
+++ b/bazel/deps.bzl
@@ -35,18 +35,18 @@
     _add_repo_if_not_existing(
         http_archive,
         name = "perfetto_dep_sqlite",
-        url = "https://storage.googleapis.com/perfetto/sqlite-amalgamation-3390200.zip",
-        sha256 = "87775784f8b22d0d0f1d7811870d39feaa7896319c7c20b849a4181c5a50609b",
-        strip_prefix = "sqlite-amalgamation-3390200",
+        url = "https://storage.googleapis.com/perfetto/sqlite-amalgamation-3440200.zip",
+        sha256 = "833be89b53b3be8b40a2e3d5fedb635080e3edb204957244f3d6987c2bb2345f",
+        strip_prefix = "sqlite-amalgamation-3440200",
         build_file = "//bazel:sqlite.BUILD",
     )
 
     _add_repo_if_not_existing(
         http_archive,
         name = "perfetto_dep_sqlite_src",
-        url = "https://storage.googleapis.com/perfetto/sqlite-src-3390200.zip",
-        sha256 = "e933d77000f45f3fbc8605f0050586a3013505a8de9b44032bd00ed72f1586f0",
-        strip_prefix = "sqlite-src-3390200",
+        url = "https://storage.googleapis.com/perfetto/sqlite-src-3440200.zip",
+        sha256 = "73187473feb74509357e8fa6cb9fd67153b2d010d00aeb2fddb6ceeb18abaf27",
+        strip_prefix = "sqlite-src-3440200",
         build_file = "//bazel:sqlite.BUILD",
     )
 
diff --git a/buildtools/Android.mk b/buildtools/Android.mk
deleted file mode 100644
index e69de29..0000000
--- a/buildtools/Android.mk
+++ /dev/null
diff --git a/docs/quickstart/android-tracing.md b/docs/quickstart/android-tracing.md
index 5c501b7..3a0d61a 100644
--- a/docs/quickstart/android-tracing.md
+++ b/docs/quickstart/android-tracing.md
@@ -84,7 +84,7 @@
 chmod u+x record_android_trace
 
 # See ./record_android_trace --help for more
-./record_android_trace -o trace_file.perfetto-trace -t 10s -b 32mb \
+./record_android_trace -o trace_file.perfetto-trace -t 30s -b 64mb \
 sched freq idle am wm gfx view binder_driver hal dalvik camera input res memory
 ```
 
@@ -92,7 +92,7 @@
 
 ```bash
 curl -O https://raw.githubusercontent.com/google/perfetto/master/tools/record_android_trace
-python3 record_android_trace -o trace_file.perfetto-trace -t 10s -b 32mb \
+python3 record_android_trace -o trace_file.perfetto-trace -t 30s -b 64mb \
 sched freq idle am wm gfx view binder_driver hal dalvik camera input res memory
 ```
 
diff --git a/docs/reference/synthetic-track-event.md b/docs/reference/synthetic-track-event.md
index 31f8ea0..e382f87 100644
--- a/docs/reference/synthetic-track-event.md
+++ b/docs/reference/synthetic-track-event.md
@@ -456,7 +456,7 @@
   }
   trusted_packet_sequence_id: 3903809   # Generate *once*, use throughout.
   # 2 = SEQ_NEEDS_INCREMENTAL_STATE
-  sequence_flags: 3
+  sequence_flags: 2
 }
 packet {
   timestamp: 203
diff --git a/gn/standalone/sanitizers/BUILD.gn b/gn/standalone/sanitizers/BUILD.gn
index 574dcfb..4fef817 100644
--- a/gn/standalone/sanitizers/BUILD.gn
+++ b/gn/standalone/sanitizers/BUILD.gn
@@ -19,8 +19,9 @@
   visibility = [ "*" ]
   if (using_sanitizer) {
     public_configs = [ ":sanitizers_ldflags" ]
+    deps = [ ":ignorelist_copy" ]
     if (is_android && sanitizer_lib != "" && !sanitizer_lib_dir_is_static) {
-      deps = [ ":copy_sanitizer_lib" ]
+      deps += [ ":copy_sanitizer_lib" ]
     }
   }
 }
@@ -32,9 +33,20 @@
   }
 }
 
+# Add a dependency on the ignorelist.txt file to cause rebuilds when
+# the file changes.
+copy("ignorelist_copy") {
+  sources = [ "ignorelist.txt" ]
+  outputs = [ "${target_out_dir}/ignorelist.txt" ]
+}
+
 config("sanitizers_cflags") {
   if (using_sanitizer) {
-    cflags = [ "-fno-omit-frame-pointer" ]
+    ignorelist_path_ = rebase_path("ignorelist.txt", root_build_dir)
+    cflags = [
+      "-fno-omit-frame-pointer",
+      "-fsanitize-ignorelist=$ignorelist_path_",
+    ]
     defines = []
 
     if (is_asan) {
diff --git a/gn/standalone/sanitizers/ignorelist.txt b/gn/standalone/sanitizers/ignorelist.txt
new file mode 100644
index 0000000..8a3d1d1
--- /dev/null
+++ b/gn/standalone/sanitizers/ignorelist.txt
@@ -0,0 +1,4 @@
+# The rules in this file are only applied at compile time.
+[memory]
+fun:vdbeChangeP4Full
+fun:*OpenDiskFile*
diff --git a/include/perfetto/trace_processor/basic_types.h b/include/perfetto/trace_processor/basic_types.h
index 0bbfcfa..7ecc934 100644
--- a/include/perfetto/trace_processor/basic_types.h
+++ b/include/perfetto/trace_processor/basic_types.h
@@ -20,14 +20,13 @@
 #include <assert.h>
 #include <math.h>
 #include <stdarg.h>
-#include <stdint.h>
-#include <functional>
+#include <cstdint>
+
 #include <string>
 #include <unordered_map>
 #include <utility>
 #include <vector>
 
-#include "perfetto/base/build_config.h"
 #include "perfetto/base/export.h"
 #include "perfetto/base/logging.h"
 
@@ -126,7 +125,7 @@
   bool ingest_ftrace_in_raw_table = true;
 
   // Indicates the event which should be used as a marker to drop ftrace data in
-  // the trace before that event. See the ennu documenetation for more details.
+  // the trace before that event. See the enum documentation for more details.
   DropFtraceDataBefore drop_ftrace_data_before =
       DropFtraceDataBefore::kTracingStarted;
 
@@ -248,8 +247,8 @@
   //
   // It is encouraged that import key should be the path to the SQL file being
   // run, with slashes replaced by dots and without the SQL extension. For
-  // example, 'android/camera/junk.sql' would be imported by
-  // 'android.camera.junk'.
+  // example, 'android/camera/jank.sql' would be imported by
+  // 'android.camera.jank'.
   std::vector<std::pair<std::string, std::string>> files;
 
   // If true, SqlModule will override registered module with the same name. Can
diff --git a/include/perfetto/tracing/internal/in_process_tracing_backend.h b/include/perfetto/tracing/internal/in_process_tracing_backend.h
index c793ae1..bfdc523 100644
--- a/include/perfetto/tracing/internal/in_process_tracing_backend.h
+++ b/include/perfetto/tracing/internal/in_process_tracing_backend.h
@@ -40,6 +40,8 @@
  public:
   static TracingBackend* GetInstance();
 
+  ~InProcessTracingBackend() override;
+
   // TracingBackend implementation.
   std::unique_ptr<ProducerEndpoint> ConnectProducer(
       const ConnectProducerArgs&) override;
diff --git a/protos/perfetto/trace/ftrace/all_protos.gni b/protos/perfetto/trace/ftrace/all_protos.gni
index 2237aef..39e5ba2 100644
--- a/protos/perfetto/trace/ftrace/all_protos.gni
+++ b/protos/perfetto/trace/ftrace/all_protos.gni
@@ -62,6 +62,7 @@
   "printk.proto",
   "raw_syscalls.proto",
   "regulator.proto",
+  "rpm.proto",
   "samsung.proto",
   "sched.proto",
   "scm.proto",
diff --git a/protos/perfetto/trace/ftrace/ftrace_event.proto b/protos/perfetto/trace/ftrace/ftrace_event.proto
index 843b44c..def2a72 100644
--- a/protos/perfetto/trace/ftrace/ftrace_event.proto
+++ b/protos/perfetto/trace/ftrace/ftrace_event.proto
@@ -62,6 +62,7 @@
 import "protos/perfetto/trace/ftrace/printk.proto";
 import "protos/perfetto/trace/ftrace/raw_syscalls.proto";
 import "protos/perfetto/trace/ftrace/regulator.proto";
+import "protos/perfetto/trace/ftrace/rpm.proto";
 import "protos/perfetto/trace/ftrace/samsung.proto";
 import "protos/perfetto/trace/ftrace/sched.proto";
 import "protos/perfetto/trace/ftrace/scm.proto";
@@ -604,5 +605,6 @@
     BinderReturnFtraceEvent binder_return = 486;
     SchedSwitchWithCtrsFtraceEvent sched_switch_with_ctrs = 487;
     GpuWorkPeriodFtraceEvent gpu_work_period = 488;
+    RpmStatusFtraceEvent rpm_status = 489;
   }
 }
diff --git a/protos/perfetto/trace/ftrace/rpm.proto b/protos/perfetto/trace/ftrace/rpm.proto
new file mode 100644
index 0000000..dd542bb
--- /dev/null
+++ b/protos/perfetto/trace/ftrace/rpm.proto
@@ -0,0 +1,11 @@
+// Autogenerated by:
+// ../../src/tools/ftrace_proto_gen/ftrace_proto_gen.cc
+// Do not edit.
+
+syntax = "proto2";
+package perfetto.protos;
+
+message RpmStatusFtraceEvent {
+  optional string name = 1;
+  optional int32 status = 2;
+}
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index a4d34d7..a166cae 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -9388,6 +9388,15 @@
 
 // End of protos/perfetto/trace/ftrace/regulator.proto
 
+// Begin of protos/perfetto/trace/ftrace/rpm.proto
+
+message RpmStatusFtraceEvent {
+  optional string name = 1;
+  optional int32 status = 2;
+}
+
+// End of protos/perfetto/trace/ftrace/rpm.proto
+
 // Begin of protos/perfetto/trace/ftrace/samsung.proto
 
 message SamsungTracingMarkWriteFtraceEvent {
@@ -10572,6 +10581,7 @@
     BinderReturnFtraceEvent binder_return = 486;
     SchedSwitchWithCtrsFtraceEvent sched_switch_with_ctrs = 487;
     GpuWorkPeriodFtraceEvent gpu_work_period = 488;
+    RpmStatusFtraceEvent rpm_status = 489;
   }
 }
 
@@ -13899,9 +13909,6 @@
   optional Utsname utsname = 1;
   optional string android_build_fingerprint = 2;
 
-  // Ticks per second - sysconf(_SC_CLK_TCK).
-  optional int64 hz = 3;
-
   // The version of traced (the same returned by `traced --version`).
   // This is a human readable string with and its format varies depending on
   // the build system and the repo (standalone vs AOSP).
@@ -13915,9 +13922,18 @@
   // Kernel page size - sysconf(_SC_PAGESIZE).
   optional uint32 page_size = 6;
 
+  // Number of cpus - sysconf(_SC_NPROCESSORS_CONF).
+  // Might be different to the number of online cpus.
+  // Introduced in perfetto v44.
+  optional uint32 num_cpus = 8;
+
   // The timezone offset from UTC, as per strftime("%z"), in minutes.
   // Introduced in v38 / Android V.
   optional int32 timezone_off_mins = 7;
+
+  // Ticks per second - sysconf(_SC_CLK_TCK).
+  // Not serialised as of perfetto v44.
+  optional int64 hz = 3;
 }
 
 // End of protos/perfetto/trace/system_info.proto
diff --git a/protos/perfetto/trace/system_info.proto b/protos/perfetto/trace/system_info.proto
index 9a75773..9f38848 100644
--- a/protos/perfetto/trace/system_info.proto
+++ b/protos/perfetto/trace/system_info.proto
@@ -29,9 +29,6 @@
   optional Utsname utsname = 1;
   optional string android_build_fingerprint = 2;
 
-  // Ticks per second - sysconf(_SC_CLK_TCK).
-  optional int64 hz = 3;
-
   // The version of traced (the same returned by `traced --version`).
   // This is a human readable string with and its format varies depending on
   // the build system and the repo (standalone vs AOSP).
@@ -45,7 +42,16 @@
   // Kernel page size - sysconf(_SC_PAGESIZE).
   optional uint32 page_size = 6;
 
+  // Number of cpus - sysconf(_SC_NPROCESSORS_CONF).
+  // Might be different to the number of online cpus.
+  // Introduced in perfetto v44.
+  optional uint32 num_cpus = 8;
+
   // The timezone offset from UTC, as per strftime("%z"), in minutes.
   // Introduced in v38 / Android V.
   optional int32 timezone_off_mins = 7;
+
+  // Ticks per second - sysconf(_SC_CLK_TCK).
+  // Not serialised as of perfetto v44.
+  optional int64 hz = 3;
 }
diff --git a/protos/third_party/chromium/chrome_track_event.proto b/protos/third_party/chromium/chrome_track_event.proto
index a5d6977..7c37b4b 100644
--- a/protos/third_party/chromium/chrome_track_event.proto
+++ b/protos/third_party/chromium/chrome_track_event.proto
@@ -662,6 +662,7 @@
     COOKIE_DISABLED = 56;
     HTTP_AUTH_REQUIRED = 57;
     COOKIE_FLUSHED = 58;
+    BROADCAST_CHANNEL_ON_MESSAGE = 59;
   }
 
   optional BackForwardCacheNotRestoredReason
@@ -1389,6 +1390,25 @@
     STEP_FINISH_BUFFER_SWAP = 13;
     STEP_SWAP_BUFFERS_ACK = 14;
 
+    // While the above steps are part of the cc/viz/gpu pipeline, the STEP_EXO*
+    // and STEP_BACKEND* are somehow special.
+    //
+    // STEP_BACKEND* is platform independent and it can be used to trace any of
+    // the backends. For example, it perfectly fits in ozone/drm, ozone/wayland
+    // or x11 and can be set before and after buffer swap or overlay submission
+    // that helps to understand the flow better.
+    //
+    // The STEP_EXO* is used only by EXO (see below). It relies on a trace id
+    // from a Wayland client, which can use the "augmented_surface" protocol's
+    // "augmented_surface.set_frame_trace_id" to pass that value. As a result,
+    // any of the Wayland clients that use EXO will be able to have a nice trace
+    // connection and see their buffer submissions' flows.
+    //
+    // Speaking about the example, Lacros combines both STEP_BACKEND* and
+    // STEP_EXO* so that developers are able to trace the frame submission from
+    // Lacros (cc/viz/gpu) to Ozone/Wayland backend, then to EXO and to Ash
+    // (viz/gpu).
+
     // Frame submission stages when Exo (Wayland server implementation in Ash
     // browser process) is involved. Wayland clients (such as Lacros or ARC++)
     // submit visual contents via Wayland surface commits, with which Exo
@@ -1396,6 +1416,13 @@
     STEP_EXO_CONSTRUCT_COMPOSITOR_FRAME = 15;
     STEP_EXO_SUBMIT_COMPOSITOR_FRAME = 16;
     STEP_EXO_DISCARD_COMPOSITOR_FRAME = 17;
+
+    // Frame submission stages in backends that viz uses to submit frames to
+    // the gpu or to the system compositor. See the explanation above how these
+    // stages can be used.
+    STEP_BACKEND_SEND_BUFFER_SWAP = 18;
+    STEP_BACKEND_SEND_BUFFER_POST_SUBMIT = 19;
+    STEP_BACKEND_FINISH_BUFFER_SWAP = 20;
   }
   enum FrameSkippedReason {
     SKIPPED_REASON_UNKNOWN = 0;
@@ -1410,6 +1437,9 @@
   optional LocalSurfaceId local_surface_id = 4;
   optional int64 frame_sequence = 5;
   optional FrameSkippedReason frame_skipped_reason = 6;
+
+  // Optional variable that can be set together with STEP_BACKEND*.
+  optional int64 backend_frame_id = 7;
 };
 
 message LibunwindstackUnwinder {
diff --git a/python/generators/sql_processing/utils.py b/python/generators/sql_processing/utils.py
index 539115e..1a76e1f 100644
--- a/python/generators/sql_processing/utils.py
+++ b/python/generators/sql_processing/utils.py
@@ -110,6 +110,7 @@
 ALLOWED_PREFIXES = {
     'counters': 'counter',
     'chrome/util': 'cr',
+    'intervals': 'interval',
     'graphs': 'graph',
     'slices': 'slice'
 }
diff --git a/python/generators/trace_processor_table/serialize.py b/python/generators/trace_processor_table/serialize.py
index 2096f55..b9d5170 100644
--- a/python/generators/trace_processor_table/serialize.py
+++ b/python/generators/trace_processor_table/serialize.py
@@ -730,7 +730,10 @@
 def serialize_header(ifdef_guard: str, tables: List[ParsedTable],
                      include_paths: List[str]) -> str:
   """Serializes a table header file containing the given set of tables."""
-  include_paths_str = '\n'.join([f'#include "{i}"' for i in include_paths])
+  # Replace the backslash with forward slash when building on Windows.
+  # Caused b/327985369 without the replace.
+  include_paths_str = '\n'.join(
+      [f'#include "{i}"' for i in include_paths]).replace("\\", "/")
   tables_str = '\n\n'.join([TableSerializer(t).serialize() for t in tables])
   return f'''
 #ifndef {ifdef_guard}
diff --git a/src/base/file_utils.cc b/src/base/file_utils.cc
index fba722d..a1fa238 100644
--- a/src/base/file_utils.cc
+++ b/src/base/file_utils.cc
@@ -433,7 +433,7 @@
   static_assert(sizeof(decltype(file_size.QuadPart)) <= sizeof(uint64_t));
   return static_cast<uint64_t>(file_size.QuadPart);
 #else
-  struct stat buf;
+  struct stat buf {};
   if (fstat(fd, &buf) == -1) {
     return std::nullopt;
   }
diff --git a/src/tools/ftrace_proto_gen/event_list b/src/tools/ftrace_proto_gen/event_list
index 269b7b9..b4c80bc 100644
--- a/src/tools/ftrace_proto_gen/event_list
+++ b/src/tools/ftrace_proto_gen/event_list
@@ -483,3 +483,4 @@
 binder/binder_return
 perf_trace_counters/sched_switch_with_ctrs
 power/gpu_work_period
+rpm/rpm_status
diff --git a/src/trace_processor/db/column/set_id_storage.cc b/src/trace_processor/db/column/set_id_storage.cc
index 23b63b8..6e4719f 100644
--- a/src/trace_processor/db/column/set_id_storage.cc
+++ b/src/trace_processor/db/column/set_id_storage.cc
@@ -43,13 +43,13 @@
 
 using SetId = SetIdStorage::SetId;
 
-uint32_t UpperBoundIntrinsic(const SetId* data, SetId id, Range range) {
-  if (id >= range.end) {
-    return range.end;
+uint32_t UpperBoundIntrinsic(const SetId* data, SetId val, Range range) {
+  for (uint32_t i = std::max(range.start, val); i < range.end; i++) {
+    if (data[i] > val) {
+      return i;
+    }
   }
-  const auto* upper =
-      std::upper_bound(data + std::max(range.start, id), data + range.end, id);
-  return static_cast<uint32_t>(std::distance(data, upper));
+  return range.end;
 }
 
 uint32_t LowerBoundIntrinsic(const SetId* data, SetId id, Range range) {
@@ -117,6 +117,10 @@
       return SearchValidationResult::kNoData;
   }
 
+  if (PERFETTO_UNLIKELY(values_->empty())) {
+    return SearchValidationResult::kNoData;
+  }
+
   // Type checks.
   switch (val.type) {
     case SqlValue::kNull:
@@ -137,13 +141,16 @@
   double num_val = val.type == SqlValue::kLong
                        ? static_cast<double>(val.AsLong())
                        : val.AsDouble();
-  if (PERFETTO_UNLIKELY(num_val > std::numeric_limits<uint32_t>::max())) {
+
+  // As values are sorted, we can cover special cases for when |num_val| is
+  // bigger than the last value and smaller than the first one.
+  if (PERFETTO_UNLIKELY(num_val > values_->back())) {
     if (op == FilterOp::kLe || op == FilterOp::kLt || op == FilterOp::kNe) {
       return SearchValidationResult::kAllData;
     }
     return SearchValidationResult::kNoData;
   }
-  if (PERFETTO_UNLIKELY(num_val < std::numeric_limits<uint32_t>::min())) {
+  if (PERFETTO_UNLIKELY(num_val < values_->front())) {
     if (op == FilterOp::kGe || op == FilterOp::kGt || op == FilterOp::kNe) {
       return SearchValidationResult::kAllData;
     }
@@ -272,9 +279,14 @@
                                                      SetId val,
                                                      Range range) const {
   switch (op) {
-    case FilterOp::kEq:
-      return {LowerBoundIntrinsic(values_->data(), val, range),
-              UpperBoundIntrinsic(values_->data(), val, range)};
+    case FilterOp::kEq: {
+      if (values_->data()[val] != val) {
+        return Range();
+      }
+      uint32_t start = std::max(val, range.start);
+      uint32_t end = UpperBoundIntrinsic(values_->data(), val, range);
+      return Range(std::min(start, end), end);
+    }
     case FilterOp::kLe: {
       return {range.start, UpperBoundIntrinsic(values_->data(), val, range)};
     }
diff --git a/src/trace_processor/db/column/set_id_storage_unittest.cc b/src/trace_processor/db/column/set_id_storage_unittest.cc
index 7af21ac..e3c40fa 100644
--- a/src/trace_processor/db/column/set_id_storage_unittest.cc
+++ b/src/trace_processor/db/column/set_id_storage_unittest.cc
@@ -251,6 +251,17 @@
   ASSERT_EQ(range.end, 6u);
 }
 
+TEST(SetIdStorageUnittest, SearchEqFalse) {
+  std::vector<uint32_t> storage_data{0, 0, 0, 3, 3, 3, 6, 6, 6, 9, 9, 9};
+  SetIdStorage storage(&storage_data);
+  auto chain = storage.MakeChain();
+
+  Range range = chain->Search(FilterOp::kEq, SqlValue::Long(5), Range(4, 10))
+                    .TakeIfRange();
+
+  ASSERT_TRUE(range.empty());
+}
+
 TEST(SetIdStorage, SearchEqOnRangeBoundary) {
   std::vector<uint32_t> storage_data{0, 0, 0, 3, 3, 3, 6, 6, 6, 9, 9, 9};
 
@@ -293,6 +304,19 @@
   ASSERT_THAT(utils::ExtractPayloadForTesting(indices), IsEmpty());
 }
 
+TEST(SetIdStorageUnittest, IndexSearchEqFalse) {
+  std::vector<uint32_t> storage_data{0, 0, 0, 3, 3, 3, 6, 6, 6, 9, 9, 9};
+  SetIdStorage storage(&storage_data);
+  auto chain = storage.MakeChain();
+
+  // {0, 3, 3, 6, 9, 9, 0, 3}
+  auto indices = Indices::CreateWithIndexPayloadForTesting(
+      {1, 3, 5, 7, 9, 11, 2, 4}, Indices::State::kNonmonotonic);
+  chain->IndexSearch(FilterOp::kEq, SqlValue::Long(5), indices);
+
+  ASSERT_THAT(utils::ExtractPayloadForTesting(indices), IsEmpty());
+}
+
 TEST(SetIdStorage, SearchWithIdAsSimpleDoubleIsInt) {
   std::vector<uint32_t> storage_data{0, 0, 0, 3, 3, 3, 6, 6, 6, 9, 9, 9};
   SetIdStorage storage(&storage_data);
diff --git a/src/trace_processor/db/column/string_storage.cc b/src/trace_processor/db/column/string_storage.cc
index 12d2517..9ab8c1f 100644
--- a/src/trace_processor/db/column/string_storage.cc
+++ b/src/trace_processor/db/column/string_storage.cc
@@ -145,13 +145,8 @@
                              const StringPool::Id* data,
                              NullTermStringView val,
                              Range search_range) {
-  const auto* lower =
-      std::lower_bound(data + search_range.start, data + search_range.end, val,
-                       [pool](StringPool::Id id, NullTermStringView val) {
-                         // TODO(b/328408877): Remove this hack after the
-                         // migration.
-                         return pool->Get(id) < val;
-                       });
+  const auto* lower = std::lower_bound(
+      data + search_range.start, data + search_range.end, val, Less{pool});
   return static_cast<uint32_t>(std::distance(data, lower));
 }
 
@@ -159,12 +154,11 @@
                              const StringPool::Id* data,
                              NullTermStringView val,
                              Range search_range) {
+  Greater comp{pool};
   const auto* upper =
       std::upper_bound(data + search_range.start, data + search_range.end, val,
-                       [pool](NullTermStringView val, StringPool::Id id) {
-                         // TODO(b/328408877): Remove this hack after the
-                         // migration.
-                         return val < pool->Get(id);
+                       [comp](NullTermStringView val, StringPool::Id id) {
+                         return comp(id, val);
                        });
   return static_cast<uint32_t>(std::distance(data, upper));
 }
@@ -173,13 +167,13 @@
                              const StringPool::Id* data,
                              NullTermStringView val,
                              const uint32_t* indices,
-                             uint32_t indices_count) {
+                             uint32_t indices_count,
+                             uint32_t offset) {
+  Less comp{pool};
   const auto* lower =
-      std::lower_bound(indices, indices + indices_count, val,
-                       [pool, data](uint32_t index, NullTermStringView val) {
-                         // TODO(b/328408877): Remove this hack after the
-                         // migration.
-                         return pool->Get(data[index]) < val;
+      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));
 }
@@ -188,13 +182,13 @@
                              const StringPool::Id* data,
                              NullTermStringView val,
                              const uint32_t* indices,
-                             uint32_t indices_count) {
+                             uint32_t indices_count,
+                             uint32_t offset) {
+  Greater comp{pool};
   const auto* upper =
-      std::upper_bound(indices, indices + indices_count, val,
-                       [pool, data](NullTermStringView val, uint32_t index) {
-                         // TODO(b/328408877): Remove this hack after the
-                         // migration.
-                         return val < pool->Get(data[index]);
+      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));
 }
@@ -534,46 +528,42 @@
           : 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),
+                                  indices.data, indices.size, first_non_null),
               UpperBoundExtrinsic(string_pool_, data_->data(), val_str,
-                                  indices.data, indices.size)};
+                                  indices.data, indices.size, first_non_null)};
     case FilterOp::kLe:
-      return {0, UpperBoundExtrinsic(string_pool_, data_->data(), val_str,
-                                     indices.data, indices.size)};
+      return {first_non_null,
+              UpperBoundExtrinsic(string_pool_, data_->data(), val_str,
+                                  indices.data, indices.size, first_non_null)};
     case FilterOp::kLt:
-      return {0, LowerBoundExtrinsic(string_pool_, data_->data(), val_str,
-                                     indices.data, indices.size)};
+      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),
+                                  indices.data, indices.size, first_non_null),
               indices.size};
     case FilterOp::kGt:
       return {UpperBoundExtrinsic(string_pool_, data_->data(), val_str,
-                                  indices.data, indices.size),
+                                  indices.data, indices.size, first_non_null),
               indices.size};
     case FilterOp::kIsNull: {
       // Assuming nulls are at the front.
-      IsNull comp;
-      const auto* first_non_null = std::partition_point(
-          indices.data, indices.data + indices.size, [comp, this](uint32_t i) {
-            return comp((*data_)[i], StringPool::Id::Null());
-          });
-      return Range(0, static_cast<uint32_t>(
-                          std::distance(indices.data, first_non_null)));
+      return Range(0, first_non_null);
     }
     case FilterOp::kIsNotNull: {
       // Assuming nulls are at the front.
-      IsNull comp;
-      const auto* first_non_null = std::partition_point(
-          indices.data, indices.data + indices.size, [comp, this](uint32_t i) {
-            return comp((*data_)[i], StringPool::Id::Null());
-          });
-      return Range(
-          static_cast<uint32_t>(std::distance(indices.data, first_non_null)),
-          indices.size);
+      return Range(first_non_null, indices.size);
     }
 
     case FilterOp::kNe:
@@ -630,20 +620,51 @@
                                           SortToken* end,
                                           SortDirection direction) const {
   switch (direction) {
-    case SortDirection::kAscending:
+    case SortDirection::kAscending: {
       std::stable_sort(start, end,
-                       [this](const SortToken& a, const SortToken& b) {
-                         return string_pool_->Get((*data_)[a.index]) <
-                                string_pool_->Get((*data_)[b.index]);
+                       [this](const SortToken& lhs, const SortToken& rhs) {
+                         // If RHS is NULL, we know that LHS is not less than
+                         // NULL, as nothing is less then null. This check is
+                         // only required to keep the stability of the sort.
+                         if ((*data_)[rhs.index] == StringPool::Id::Null()) {
+                           return false;
+                         }
+
+                         // If LHS is NULL, it will always be smaller than any
+                         // RHS value.
+                         if ((*data_)[lhs.index] == StringPool::Id::Null()) {
+                           return true;
+                         }
+
+                         // If neither LHS or RHS are NULL, we have to simply
+                         // check which string is smaller.
+                         return string_pool_->Get((*data_)[lhs.index]) <
+                                string_pool_->Get((*data_)[rhs.index]);
                        });
       return;
-    case SortDirection::kDescending:
+    }
+    case SortDirection::kDescending: {
       std::stable_sort(start, end,
-                       [this](const SortToken& a, const SortToken& b) {
-                         return string_pool_->Get((*data_)[a.index]) >
-                                string_pool_->Get((*data_)[b.index]);
+                       [this](const SortToken& lhs, const SortToken& rhs) {
+                         // If LHS is NULL, we know that it's not greater than
+                         // any RHS. This check is only required to keep the
+                         // stability of the sort.
+                         if ((*data_)[lhs.index] == StringPool::Id::Null()) {
+                           return false;
+                         }
+
+                         // If RHS is NULL, everything will be greater from it.
+                         if ((*data_)[rhs.index] == StringPool::Id::Null()) {
+                           return true;
+                         }
+
+                         // If neither LHS or RHS are NULL, we have to simply
+                         // check which string is smaller.
+                         return string_pool_->Get((*data_)[lhs.index]) >
+                                string_pool_->Get((*data_)[rhs.index]);
                        });
       return;
+    }
   }
   PERFETTO_FATAL("For GCC");
 }
diff --git a/src/trace_processor/db/column/string_storage_unittest.cc b/src/trace_processor/db/column/string_storage_unittest.cc
index e664ae1..4e06185 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, 0u);
+  ASSERT_EQ(res.start, 1u);
   ASSERT_EQ(res.end, 4u);
 
   op = FilterOp::kLe;
   res = chain->OrderedIndexSearch(op, val, indices);
-  ASSERT_EQ(res.start, 0u);
+  ASSERT_EQ(res.start, 1u);
   ASSERT_EQ(res.end, 5u);
 
   op = FilterOp::kGt;
diff --git a/src/trace_processor/importers/common/BUILD.gn b/src/trace_processor/importers/common/BUILD.gn
index 5108dca..c3ba78a 100644
--- a/src/trace_processor/importers/common/BUILD.gn
+++ b/src/trace_processor/importers/common/BUILD.gn
@@ -37,6 +37,8 @@
     "flow_tracker.h",
     "global_args_tracker.cc",
     "global_args_tracker.h",
+    "jit_cache.cc",
+    "jit_cache.h",
     "mapping_tracker.cc",
     "mapping_tracker.h",
     "metadata_tracker.cc",
diff --git a/src/trace_processor/importers/common/jit_cache.cc b/src/trace_processor/importers/common/jit_cache.cc
new file mode 100644
index 0000000..8380177
--- /dev/null
+++ b/src/trace_processor/importers/common/jit_cache.cc
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_processor/importers/common/jit_cache.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
+#include <memory>
+#include <optional>
+#include <string>
+#include <utility>
+
+#include "perfetto/base/logging.h"
+#include "perfetto/ext/base/base64.h"
+#include "perfetto/ext/base/string_utils.h"
+#include "perfetto/ext/base/string_view.h"
+#include "perfetto/trace_processor/trace_blob_view.h"
+#include "src/trace_processor/importers/common/address_range.h"
+#include "src/trace_processor/importers/common/mapping_tracker.h"
+#include "src/trace_processor/importers/common/stack_profile_tracker.h"
+#include "src/trace_processor/storage/stats.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/tables/jit_tables_py.h"
+#include "src/trace_processor/tables/metadata_tables_py.h"
+#include "src/trace_processor/tables/profiler_tables_py.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+std::pair<FrameId, bool> JitCache::JittedFunction::InternFrame(
+    TraceProcessorContext* context,
+    FrameKey frame_key) {
+  if (FrameId* id = interned_frames_.Find(frame_key); id) {
+    return {*id, false};
+  }
+
+  FrameId frame_id =
+      context->storage->mutable_stack_profile_frame_table()
+          ->Insert({context->storage->jit_code_table()
+                        .FindById(jit_code_id_)
+                        ->function_name(),
+                    frame_key.mapping_id,
+                    static_cast<int64_t>(frame_key.rel_pc), symbol_set_id_})
+          .id;
+  interned_frames_.Insert(frame_key, frame_id);
+  context->stack_profile_tracker->OnFrameCreated(frame_id);
+
+  context->storage->mutable_jit_frame_table()->Insert({jit_code_id_, frame_id});
+
+  return {frame_id, true};
+}
+
+tables::JitCodeTable::Id JitCache::LoadCode(
+    int64_t timestamp,
+    UniqueTid utid,
+    AddressRange code_range,
+    StringId function_name,
+    std::optional<SourceLocation> source_location,
+    TraceBlobView native_code) {
+  PERFETTO_CHECK(range_.Contains(code_range));
+  PERFETTO_CHECK(context_->storage->thread_table()
+                     .FindById(tables::ThreadTable::Id(utid))
+                     ->upid() == upid_);
+
+  PERFETTO_CHECK(native_code.size() == 0 ||
+                 native_code.size() == code_range.size());
+
+  std::optional<uint32_t> symbol_set_id;
+  if (source_location.has_value()) {
+    // TODO(carlscab): Remove duplication via new SymbolTracker class
+    symbol_set_id = context_->storage->symbol_table().row_count();
+    context_->storage->mutable_symbol_table()->Insert(
+        {*symbol_set_id, function_name, source_location->file_name,
+         source_location->line_number});
+  }
+
+  auto* jit_code_table = context_->storage->mutable_jit_code_table();
+  const auto jit_code_id =
+      jit_code_table
+          ->Insert({timestamp, std::nullopt, utid,
+                    static_cast<int64_t>(code_range.start()),
+                    static_cast<int64_t>(code_range.size()), function_name,
+                    Base64Encode(native_code)})
+          .id;
+
+  functions_.DeleteOverlapsAndEmplace(
+      [&](std::pair<const AddressRange, JittedFunction>& entry) {
+        jit_code_table->FindById(entry.second.jit_code_id())
+            ->set_estimated_delete_ts(timestamp);
+      },
+      code_range, jit_code_id, symbol_set_id);
+
+  return jit_code_id;
+}
+
+std::pair<FrameId, bool> JitCache::InternFrame(VirtualMemoryMapping* mapping,
+                                               uint64_t rel_pc,
+                                               base::StringView function_name) {
+  FrameKey key{mapping->mapping_id(), rel_pc};
+
+  if (auto it = functions_.Find(mapping->ToAddress(rel_pc));
+      it != functions_.end()) {
+    return it->second.InternFrame(context_, key);
+  }
+
+  if (FrameId* id = unknown_frames_.Find(key); id) {
+    return {*id, false};
+  }
+
+  context_->storage->IncrementStats(stats::jit_unknown_frame);
+
+  FrameId id =
+      context_->storage->mutable_stack_profile_frame_table()
+          ->Insert({context_->storage->InternString(
+                        function_name.empty()
+                            ? base::StringView(
+                                  "[+" + base::Uint64ToHexString(rel_pc) + "]")
+                            : function_name),
+                    key.mapping_id, static_cast<int64_t>(rel_pc)})
+          .id;
+  unknown_frames_.Insert(key, id);
+  return {id, true};
+}
+
+UserMemoryMapping& JitCache::CreateMapping() {
+  CreateMappingParams params;
+  params.memory_range = range_;
+  params.name = "[jit: " + name_ + "]";
+  return context_->mapping_tracker->CreateUserMemoryMapping(upid_,
+                                                            std::move(params));
+}
+
+StringId JitCache::Base64Encode(const TraceBlobView& data) {
+  return context_->storage->InternString(
+      base::StringView(base::Base64Encode(data.data(), data.size())));
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/common/jit_cache.h b/src/trace_processor/importers/common/jit_cache.h
new file mode 100644
index 0000000..19205b6
--- /dev/null
+++ b/src/trace_processor/importers/common/jit_cache.h
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_JIT_CACHE_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_JIT_CACHE_H_
+
+#include <cstdint>
+#include <optional>
+
+#include "perfetto/ext/base/flat_hash_map.h"
+#include "perfetto/ext/base/string_view.h"
+#include "perfetto/trace_processor/trace_blob_view.h"
+#include "src/trace_processor/importers/common/address_range.h"
+#include "src/trace_processor/importers/common/stack_profile_tracker.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/tables/jit_tables_py.h"
+#include "src/trace_processor/tables/profiler_tables_py.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+class VirtualMemoryMapping;
+class UserMemoryMapping;
+
+// JitCache represents a container of jit code generated by the same VM. Jitted
+// functions can be "added" to the cache and stack frames added to the
+// StackFrameTracker that lie in the code range for such a function will
+// automatically be resolved (associate function name and source location).
+//
+// Jitted functions can also be deleted from the cache by overwriting existing
+// ones.
+class JitCache {
+ public:
+  struct SourceLocation {
+    StringId file_name;
+    uint32_t line_number;
+  };
+
+  JitCache(TraceProcessorContext* context,
+           std::string name,
+           UniquePid upid,
+           AddressRange range)
+      : context_(context), name_(std::move(name)), upid_(upid), range_(range) {}
+
+  // Notify the cache that a jitted function was loaded at the given address
+  // range. Any existing function that fully or partially overlaps with the new
+  // function will be deleted.
+  // The passed in listener will be notified each time a new Frame is created
+  // for this function.
+  tables::JitCodeTable::Id LoadCode(
+      int64_t timestamp,
+      UniqueTid utid,
+      AddressRange code_range,
+      StringId function_name,
+      std::optional<SourceLocation> source_location,
+      TraceBlobView native_code);
+
+  // Forward frame interning request.
+  // MappingTracker allows other trackers to register ranges of memory for
+  // which they need to control when a new frame is created. Jitted code can
+  // move in memory over time, so the same program counter might refer to
+  // different functions at different point in time. MappingTracker does
+  // not keep track of such moves but instead delegates the creation of jitted
+  // frames to a delegate.
+  // Returns frame_id, and whether a new row as created or not.
+  std::pair<FrameId, bool> InternFrame(VirtualMemoryMapping* mapping,
+                                       uint64_t rel_pc,
+                                       base::StringView function_name);
+
+  // Simpleperf does not emit mmap events for jitted ranges (actually for non
+  // file backed executable mappings). So have a way to generate a mapping on
+  // the fly for FindMapping requests in a jitted region with no associated
+  // mapping.
+  UserMemoryMapping& CreateMapping();
+
+ private:
+  struct FrameKey {
+    tables::StackProfileMappingTable::Id mapping_id;
+    uint64_t rel_pc;
+    struct Hasher {
+      size_t operator()(const FrameKey& k) const {
+        return static_cast<size_t>(
+            base::Hasher::Combine(k.mapping_id.value, k.rel_pc));
+      }
+    };
+    bool operator==(const FrameKey& other) const {
+      return mapping_id == other.mapping_id && rel_pc == other.rel_pc;
+    }
+  };
+
+  class JittedFunction {
+   public:
+    JittedFunction(tables::JitCodeTable::Id jit_code_id,
+                   std::optional<uint32_t> symbol_set_id)
+        : jit_code_id_(jit_code_id), symbol_set_id_(symbol_set_id) {}
+
+    tables::JitCodeTable::Id jit_code_id() const { return jit_code_id_; }
+
+    std::pair<FrameId, bool> InternFrame(TraceProcessorContext* context,
+                                         FrameKey frame_key);
+
+   private:
+    const tables::JitCodeTable::Id jit_code_id_;
+    const std::optional<uint32_t> symbol_set_id_;
+    base::FlatHashMap<FrameKey, FrameId, FrameKey::Hasher> interned_frames_;
+  };
+
+  StringId Base64Encode(const TraceBlobView& data);
+
+  TraceProcessorContext* const context_;
+  const std::string name_;
+  const UniquePid upid_;
+  const AddressRange range_;
+  AddressRangeMap<JittedFunction> functions_;
+  base::FlatHashMap<FrameKey, FrameId, FrameKey::Hasher> unknown_frames_;
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_JIT_CACHE_H_
diff --git a/src/trace_processor/importers/common/mapping_tracker.cc b/src/trace_processor/importers/common/mapping_tracker.cc
index 13b8274..0dec3e5 100644
--- a/src/trace_processor/importers/common/mapping_tracker.cc
+++ b/src/trace_processor/importers/common/mapping_tracker.cc
@@ -21,8 +21,10 @@
 #include <memory>
 #include <utility>
 
+#include "perfetto/ext/base/string_utils.h"
 #include "perfetto/ext/base/string_view.h"
 #include "src/trace_processor/importers/common/address_range.h"
+#include "src/trace_processor/importers/common/jit_cache.h"
 #include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/types/trace_processor_context.h"
 #include "src/trace_processor/util/build_id.h"
@@ -31,14 +33,13 @@
 namespace trace_processor {
 namespace {
 
-bool IsKernelModule(base::StringView name) {
-  return !name.StartsWith("[kernel.kallsyms]");
+bool IsKernelModule(const CreateMappingParams& params) {
+  return !base::StartsWith(params.name, "[kernel.kallsyms]") &&
+         !params.memory_range.empty();
 }
 
 }  // namespace
 
-JitDelegate::~JitDelegate() = default;
-
 template <typename MappingImpl>
 MappingImpl& MappingTracker::AddMapping(std::unique_ptr<MappingImpl> mapping) {
   auto ptr = mapping.get();
@@ -57,7 +58,7 @@
   // 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 bool is_module = IsKernelModule(base::StringView(params.name));
+  const bool is_module = IsKernelModule(params);
 
   if (!is_module && kernel_ != nullptr) {
     PERFETTO_CHECK(params.memory_range == kernel_->memory_range());
@@ -68,10 +69,8 @@
       new KernelMemoryMapping(context_, std::move(params)));
 
   if (is_module) {
-    // TODO(carlscab): Overlaps not supported (for now?). Should be fine for
-    // kernel.
-    PERFETTO_CHECK(
-        kernel_modules_.Emplace(mapping->memory_range(), mapping.get()));
+    kernel_modules_.DeleteOverlapsAndEmplace(mapping->memory_range(),
+                                             mapping.get());
   } else {
     kernel_ = mapping.get();
   }
@@ -88,15 +87,15 @@
   const AddressRange mapping_range = params.memory_range;
   std::unique_ptr<UserMemoryMapping> mapping(
       new UserMemoryMapping(context_, upid, std::move(params)));
-  // TODO(carlscab): Overlaps not supported (for now?).
-  PERFETTO_CHECK(user_memory_[upid].Emplace(mapping_range, mapping.get()));
 
-  jit_delegates_[upid].ForOverlaps(
-      mapping_range, [&](std::pair<const AddressRange, JitDelegate*>& entry) {
+  user_memory_[upid].DeleteOverlapsAndEmplace(mapping_range, mapping.get());
+
+  jit_caches_[upid].ForOverlaps(
+      mapping_range, [&](std::pair<const AddressRange, JitCache*>& entry) {
         const auto& jit_range = entry.first;
-        JitDelegate* jit_delegate = entry.second;
+        JitCache* jit_cache = entry.second;
         PERFETTO_CHECK(jit_range.Contains(mapping_range));
-        mapping->SetJitDelegate(jit_delegate);
+        mapping->SetJitCache(jit_cache);
       });
 
   return AddMapping(std::move(mapping));
@@ -122,9 +121,9 @@
     }
   }
 
-  if (auto* delegates = jit_delegates_.Find(upid); delegates) {
+  if (auto* delegates = jit_caches_.Find(upid); delegates) {
     if (auto it = delegates->Find(address); it != delegates->end()) {
-      return it->second->CreateMapping();
+      return &it->second->CreateMapping();
     }
   }
 
@@ -155,13 +154,13 @@
 
 void MappingTracker::AddJitRange(UniquePid upid,
                                  AddressRange jit_range,
-                                 JitDelegate* delegate) {
+                                 JitCache* jit_cache) {
   // TODO(carlscab): Deal with overlaps
-  jit_delegates_[upid].DeleteOverlapsAndEmplace(jit_range, delegate);
+  jit_caches_[upid].DeleteOverlapsAndEmplace(jit_range, jit_cache);
   user_memory_[upid].ForOverlaps(
       jit_range, [&](std::pair<const AddressRange, UserMemoryMapping*>& entry) {
         PERFETTO_CHECK(jit_range.Contains(entry.first));
-        entry.second->SetJitDelegate(delegate);
+        entry.second->SetJitCache(jit_cache);
       });
 }
 
diff --git a/src/trace_processor/importers/common/mapping_tracker.h b/src/trace_processor/importers/common/mapping_tracker.h
index 95dc355..bc45bef 100644
--- a/src/trace_processor/importers/common/mapping_tracker.h
+++ b/src/trace_processor/importers/common/mapping_tracker.h
@@ -35,31 +35,7 @@
 namespace perfetto {
 namespace trace_processor {
 
-// Api used to forward frame interning requests for frames that fall in a
-// jitted memory region.
-// MappingTracker allows other trackers to register ranges of memory for
-// which they need to control when a new frame is created. Jitted code can
-// move in memory over time, so the same program counter might refer to
-// different functions at different point in time. MappingTracker does
-// not keep track of such moves but instead delegates the creation of jitted
-// frames to a delegate.
-class JitDelegate {
- public:
-  virtual ~JitDelegate();
-  // Forward frame interning request.
-  // Implementations are free to intern the frame as needed.
-  // Returns frame_id, and whether a new row as created or not.
-  virtual std::pair<FrameId, bool> InternFrame(
-      VirtualMemoryMapping* mapping,
-      uint64_t rel_pc,
-      base::StringView function_name) = 0;
-
-  // Simpleperf does not emit mmap events for jitted ranges (actually for non
-  // file backed executable mappings). So have a way to generate a mapping on
-  // the fly for FindMapping requests in a jitted region with no associated
-  // mapping.
-  virtual UserMemoryMapping* CreateMapping() = 0;
-};
+class JitCache;
 
 // Keeps track of all aspects relative to memory mappings.
 // This class keeps track of 3 types of mappings: UserMemoryMapping,
@@ -113,7 +89,7 @@
   // If the added region overlaps with other existing ranges the latter are all
   // deleted.
   // Jitted ranges will only be applied to UserMemoryMappings
-  void AddJitRange(UniquePid upid, AddressRange range, JitDelegate* delegate);
+  void AddJitRange(UniquePid upid, AddressRange range, JitCache* jit_cache);
 
  private:
   template <typename MappingImpl>
@@ -159,7 +135,7 @@
   AddressRangeMap<KernelMemoryMapping*> kernel_modules_;
   KernelMemoryMapping* kernel_ = nullptr;
 
-  base::FlatHashMap<UniquePid, AddressRangeMap<JitDelegate*>> jit_delegates_;
+  base::FlatHashMap<UniquePid, AddressRangeMap<JitCache*>> jit_caches_;
 };
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/importers/common/system_info_tracker.cc b/src/trace_processor/importers/common/system_info_tracker.cc
index 89f0baf..3bbed5b 100644
--- a/src/trace_processor/importers/common/system_info_tracker.cc
+++ b/src/trace_processor/importers/common/system_info_tracker.cc
@@ -20,7 +20,7 @@
 namespace perfetto {
 namespace trace_processor {
 
-SystemInfoTracker::SystemInfoTracker() {}
+SystemInfoTracker::SystemInfoTracker() = default;
 SystemInfoTracker::~SystemInfoTracker() = default;
 
 void SystemInfoTracker::SetKernelVersion(base::StringView name,
diff --git a/src/trace_processor/importers/common/system_info_tracker.h b/src/trace_processor/importers/common/system_info_tracker.h
index d9544d1..ec46e80 100644
--- a/src/trace_processor/importers/common/system_info_tracker.h
+++ b/src/trace_processor/importers/common/system_info_tracker.h
@@ -42,13 +42,16 @@
   }
 
   void SetKernelVersion(base::StringView name, base::StringView release);
+  void SetNumCpus(uint32_t num_cpus) { num_cpus_ = num_cpus; }
 
-  std::optional<VersionNumber> GetKernelVersion() { return version_; }
+  std::optional<VersionNumber> GetKernelVersion() const { return version_; }
+  std::optional<uint32_t> GetNumCpus() const { return num_cpus_; }
 
  private:
   explicit SystemInfoTracker();
 
   std::optional<VersionNumber> version_;
+  std::optional<uint32_t> num_cpus_;
 };
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/importers/common/track_tracker.cc b/src/trace_processor/importers/common/track_tracker.cc
index 9db1fa7..640647b 100644
--- a/src/trace_processor/importers/common/track_tracker.cc
+++ b/src/trace_processor/importers/common/track_tracker.cc
@@ -390,6 +390,19 @@
   return track;
 }
 
+TrackId TrackTracker::InternLinuxDeviceTrack(StringId name) {
+  if (auto it = linux_device_tracks_.find(name);
+      it != linux_device_tracks_.end()) {
+    return it->second;
+  }
+
+  tables::LinuxDeviceTrackTable::Row row(name);
+  TrackId track =
+      context_->storage->mutable_linux_device_track_table()->Insert(row).id;
+  linux_device_tracks_[name] = track;
+  return track;
+}
+
 TrackId TrackTracker::CreateGpuCounterTrack(StringId name,
                                             uint32_t gpu_id,
                                             StringId description,
diff --git a/src/trace_processor/importers/common/track_tracker.h b/src/trace_processor/importers/common/track_tracker.h
index 0783081..a7d481a 100644
--- a/src/trace_processor/importers/common/track_tracker.h
+++ b/src/trace_processor/importers/common/track_tracker.h
@@ -129,6 +129,10 @@
                                          int32_t consumer_id,
                                          int32_t uid);
 
+  // Interns a track associated with a Linux device (where a Linux device
+  // implies a kernel-level device managed by a Linux driver).
+  TrackId InternLinuxDeviceTrack(StringId name);
+
   // Creates a counter track associated with a GPU into the storage.
   TrackId CreateGpuCounterTrack(StringId name,
                                 uint32_t gpu_id,
@@ -216,6 +220,7 @@
   std::map<std::pair<StringId, int32_t>, TrackId> uid_counter_tracks_;
   std::map<std::pair<StringId, int32_t>, TrackId>
       energy_per_uid_counter_tracks_;
+  std::map<StringId, TrackId> linux_device_tracks_;
 
   std::optional<TrackId> chrome_global_instant_track_id_;
   std::optional<TrackId> trigger_track_id_;
diff --git a/src/trace_processor/importers/common/virtual_memory_mapping.cc b/src/trace_processor/importers/common/virtual_memory_mapping.cc
index 60166f59..0485243 100644
--- a/src/trace_processor/importers/common/virtual_memory_mapping.cc
+++ b/src/trace_processor/importers/common/virtual_memory_mapping.cc
@@ -25,7 +25,7 @@
 
 #include "perfetto/ext/base/string_view.h"
 #include "src/trace_processor/importers/common/address_range.h"
-#include "src/trace_processor/importers/common/mapping_tracker.h"
+#include "src/trace_processor/importers/common/jit_cache.h"
 #include "src/trace_processor/importers/common/stack_profile_tracker.h"
 #include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/tables/profiler_tables_py.h"
@@ -84,8 +84,8 @@
 FrameId VirtualMemoryMapping::InternFrame(uint64_t rel_pc,
                                           base::StringView function_name) {
   auto [frame_id, was_inserted] =
-      jit_delegate_ ? jit_delegate_->InternFrame(this, rel_pc, function_name)
-                    : InternFrameImpl(rel_pc, function_name);
+      jit_cache_ ? jit_cache_->InternFrame(this, rel_pc, function_name)
+                 : InternFrameImpl(rel_pc, function_name);
   if (was_inserted) {
     frames_by_rel_pc_[rel_pc].push_back(frame_id);
     context_->stack_profile_tracker->OnFrameCreated(frame_id);
diff --git a/src/trace_processor/importers/common/virtual_memory_mapping.h b/src/trace_processor/importers/common/virtual_memory_mapping.h
index 7b8ef58..498a9ef 100644
--- a/src/trace_processor/importers/common/virtual_memory_mapping.h
+++ b/src/trace_processor/importers/common/virtual_memory_mapping.h
@@ -39,7 +39,7 @@
 // TODO(carlscab): Reconsider whether jit is the best abstraction here. All we
 // really care is about mapping a `rel_pc` to a symbol (aka symbolization) and
 // whether is this is constant.
-class JitDelegate;
+class JitCache;
 
 // Represents a mapping in virtual memory.
 class VirtualMemoryMapping {
@@ -61,13 +61,18 @@
   const std::optional<BuildId>& build_id() const { return build_id_; }
 
   // Whether this maps to a region that holds jitted code.
-  bool is_jitted() const { return jit_delegate_ != nullptr; }
+  bool is_jitted() const { return jit_cache_ != nullptr; }
 
   // Converts an absolute address into a relative one.
   uint64_t ToRelativePc(uint64_t address) const {
     return address - memory_range_.start() + offset_ + load_bias_;
   }
 
+  // Converts a relative address to an absolute one.
+  uint64_t ToAddress(uint64_t rel_pc) const {
+    return rel_pc + (memory_range_.start() - offset_ - load_bias_);
+  }
+
   // Creates a frame for the given `rel_pc`. Note that if the mapping
   // `is_jitted()` same `rel_pc` values can return different mappings (as jitted
   // functions can be created and deleted over time.) So for such mappings the
@@ -87,9 +92,7 @@
   std::pair<FrameId, bool> InternFrameImpl(uint64_t rel_pc,
                                            base::StringView function_name);
 
-  void SetJitDelegate(JitDelegate* jit_delegate) {
-    jit_delegate_ = jit_delegate;
-  }
+  void SetJitCache(JitCache* jit_cache) { jit_cache_ = jit_cache; }
 
   TraceProcessorContext* const context_;
   const MappingId mapping_id_;
@@ -98,7 +101,7 @@
   const uint64_t load_bias_;
   const std::string name_;
   std::optional<BuildId> const build_id_;
-  JitDelegate* jit_delegate_ = nullptr;
+  JitCache* jit_cache_ = nullptr;
 
   struct FrameKey {
     uint64_t rel_pc;
diff --git a/src/trace_processor/importers/ftrace/ftrace_descriptors.cc b/src/trace_processor/importers/ftrace/ftrace_descriptors.cc
index acef866..a88cc26 100644
--- a/src/trace_processor/importers/ftrace/ftrace_descriptors.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_descriptors.cc
@@ -24,7 +24,7 @@
 namespace trace_processor {
 namespace {
 
-std::array<FtraceMessageDescriptor, 489> descriptors{{
+std::array<FtraceMessageDescriptor, 490> descriptors{{
     {nullptr, 0, {}},
     {nullptr, 0, {}},
     {nullptr, 0, {}},
@@ -5387,6 +5387,15 @@
             {"total_active_duration_ns", ProtoSchemaType::kUint64},
         },
     },
+    {
+        "rpm_status",
+        2,
+        {
+            {},
+            {"name", ProtoSchemaType::kString},
+            {"status", ProtoSchemaType::kInt32},
+        },
+    },
 }};
 
 }  // namespace
diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.cc b/src/trace_processor/importers/ftrace/ftrace_parser.cc
index 6948fc3..69f1b58 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.cc
@@ -68,6 +68,7 @@
 #include "protos/perfetto/trace/ftrace/oom.pbzero.h"
 #include "protos/perfetto/trace/ftrace/power.pbzero.h"
 #include "protos/perfetto/trace/ftrace/raw_syscalls.pbzero.h"
+#include "protos/perfetto/trace/ftrace/rpm.pbzero.h"
 #include "protos/perfetto/trace/ftrace/samsung.pbzero.h"
 #include "protos/perfetto/trace/ftrace/sched.pbzero.h"
 #include "protos/perfetto/trace/ftrace/scm.pbzero.h"
@@ -87,8 +88,7 @@
 #include "protos/perfetto/trace/interned_data/interned_data.pbzero.h"
 #include "protos/perfetto/trace/profiling/profile_common.pbzero.h"
 
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
 
 namespace {
 
@@ -220,6 +220,14 @@
   }
   return buffer;
 }
+
+enum RpmStatus {
+  RPM_INVALID = -1,
+  RPM_ACTIVE = 0,
+  RPM_RESUMING,
+  RPM_SUSPENDED,
+  RPM_SUSPENDING,
+};
 }  // namespace
 FtraceParser::FtraceParser(TraceProcessorContext* context)
     : context_(context),
@@ -326,7 +334,13 @@
       bytes_read_id_end_(context_->storage->InternString("bytes_read_end")),
       android_fs_category_id_(context_->storage->InternString("android_fs")),
       android_fs_data_read_id_(
-          context_->storage->InternString("android_fs_data_read")) {
+          context_->storage->InternString("android_fs_data_read")),
+      runtime_status_invalid_id_(
+          context->storage->InternString("Invalid State")),
+      runtime_status_active_id_(context->storage->InternString("Active")),
+      runtime_status_suspending_id_(
+          context->storage->InternString("Suspending")),
+      runtime_status_resuming_id_(context->storage->InternString("Resuming")) {
   // Build the lookup table for the strings inside ftrace events (e.g. the
   // name of ftrace event fields and the names of their args).
   for (size_t i = 0; i < GetDescriptorsSize(); i++) {
@@ -556,6 +570,7 @@
   // buffer ABI not matching the data read out of the kernel (while the trace
   // was being recorded). Reject such traces altogether as we need to make such
   // errors hard to ignore (most likely it's a bug in perfetto or the kernel).
+  using protos::pbzero::FtraceParseStatus;
   auto error_it = evt.ftrace_parse_errors();
   if (error_it) {
     auto dev_flag =
@@ -570,13 +585,26 @@
           "native trace_processor_shell as an accelerator with these flags: "
           "\"trace_processor_shell --httpd --dev --dev-flag "
           "ignore-ftrace-parse-errors=true <trace_file.pb>\". Errors: ";
+      size_t error_count = 0;
       for (; error_it; ++error_it) {
-        msg += protos::pbzero::FtraceParseStatus_Name(
-            static_cast<protos::pbzero::FtraceParseStatus>(*error_it));
+        auto error_code = static_cast<FtraceParseStatus>(*error_it);
+        // Relax the strictness of zero-padded page errors, they're prevalent
+        // but also do not affect the actual ftrace payload.
+        // See b/329396486#comment6, b/204564312#comment20.
+        if (error_code ==
+            FtraceParseStatus::FTRACE_STATUS_ABI_ZERO_DATA_LENGTH) {
+          context_->storage->IncrementStats(
+              stats::ftrace_abi_errors_skipped_zero_data_length);
+          continue;
+        }
+        error_count += 1;
+        msg += protos::pbzero::FtraceParseStatus_Name(error_code);
         msg += ", ";
       }
       msg += "(ERR:ftrace_parse)";  // special marker for UI
-      return base::Status(msg);
+      if (error_count > 0) {
+        return base::Status(msg);
+      }
     }
   }
 
@@ -1099,6 +1127,10 @@
         gpu_work_period_tracker_.ParseGpuWorkPeriodEvent(ts, fld_bytes);
         break;
       }
+      case FtraceEvent::kRpmStatusFieldNumber: {
+        ParseRpmStatus(ts, fld_bytes);
+        break;
+      }
       default:
         break;
     }
@@ -1274,8 +1306,6 @@
       case ProtoSchemaType::kInt64:
       case ProtoSchemaType::kSfixed32:
       case ProtoSchemaType::kSfixed64:
-      case ProtoSchemaType::kSint32:
-      case ProtoSchemaType::kSint64:
       case ProtoSchemaType::kBool:
       case ProtoSchemaType::kEnum: {
         inserter.AddArg(name_id, Variadic::Integer(fld.as_int64()));
@@ -1291,6 +1321,11 @@
         inserter.AddArg(name_id, Variadic::UnsignedInteger(fld.as_uint64()));
         break;
       }
+      case ProtoSchemaType::kSint32:
+      case ProtoSchemaType::kSint64: {
+        inserter.AddArg(name_id, Variadic::Integer(fld.as_sint64()));
+        break;
+      }
       case ProtoSchemaType::kString:
       case ProtoSchemaType::kBytes: {
         StringId value = context_->storage->InternString(fld.as_string());
@@ -3202,6 +3237,56 @@
   inode_offset_thread_map_.Erase(key);
 }
 
+StringId FtraceParser::GetRpmStatusStringId(int32_t rpm_status_val) {
+  // `RPM_SUSPENDED` is omitted from this list as it would never be used as a
+  // slice label.
+  switch (rpm_status_val) {
+    case RPM_INVALID:
+      return runtime_status_invalid_id_;
+    case RPM_SUSPENDING:
+      return runtime_status_suspending_id_;
+    case RPM_RESUMING:
+      return runtime_status_resuming_id_;
+    case RPM_ACTIVE:
+      return runtime_status_active_id_;
+  }
+
+  PERFETTO_DLOG(
+      "Invalid runtime status value obtained from rpm_status ftrace event");
+  return runtime_status_invalid_id_;
+}
+
+void FtraceParser::ParseRpmStatus(int64_t ts, protozero::ConstBytes blob) {
+  protos::pbzero::RpmStatusFtraceEvent::Decoder rpm_event(blob.data, blob.size);
+
+  // Device here refers to anything managed by a Linux kernel driver.
+  std::string device_name = rpm_event.name().ToStdString();
+  int32_t rpm_status = rpm_event.status();
+  StringId device_name_string_id =
+      context_->storage->InternString(device_name.c_str());
+  TrackId track_id =
+      context_->track_tracker->InternLinuxDeviceTrack(device_name_string_id);
+
+  // A `runtime_status` event implies a potential change in state. Hence, if an
+  // active slice exists for this device, end that slice.
+  if (devices_with_active_rpm_slice_.find(device_name) !=
+      devices_with_active_rpm_slice_.end()) {
+    context_->slice_tracker->End(ts, track_id);
+  }
+
+  // To reduce visual clutter, the "SUSPENDED" state will be omitted from the
+  // visualization, as devices typically spend the majority of their time in
+  // this state.
+  if (rpm_status == RPM_SUSPENDED) {
+    devices_with_active_rpm_slice_.erase(device_name);
+    return;
+  }
+
+  context_->slice_tracker->Begin(ts, track_id, /*category=*/kNullStringId,
+                                 /*raw_name=*/GetRpmStatusStringId(rpm_status));
+  devices_with_active_rpm_slice_.insert(device_name);
+}
+
 StringId FtraceParser::InternedKernelSymbolOrFallback(
     uint64_t key,
     PacketSequenceStateGeneration* seq_state) {
@@ -3220,5 +3305,4 @@
   return name_id;
 }
 
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.h b/src/trace_processor/importers/ftrace/ftrace_parser.h
index b3764f1..de0b3bb 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.h
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.h
@@ -293,6 +293,8 @@
   void ParseAndroidFsDatareadStart(int64_t ts,
                                    uint32_t pid,
                                    protozero::ConstBytes);
+  StringId GetRpmStatusStringId(int32_t rpm_status_val);
+  void ParseRpmStatus(int64_t ts, protozero::ConstBytes);
 
   TraceProcessorContext* context_;
   RssStatTracker rss_stat_tracker_;
@@ -382,6 +384,10 @@
   const StringId bytes_read_id_end_;
   const StringId android_fs_category_id_;
   const StringId android_fs_data_read_id_;
+  const StringId runtime_status_invalid_id_;
+  const StringId runtime_status_active_id_;
+  const StringId runtime_status_suspending_id_;
+  const StringId runtime_status_resuming_id_;
   std::vector<StringId> syscall_arg_name_ids_;
 
   struct FtraceMessageStrings {
@@ -445,6 +451,10 @@
   // re-emits begin stats on every flush).
   std::unordered_set<uint32_t> seen_errors_for_sequence_id_;
 
+  // Tracks Linux devices with active runtime power management (RPM) status
+  // slices.
+  std::unordered_set<std::string> devices_with_active_rpm_slice_;
+
   struct PairHash {
     std::size_t operator()(const std::pair<uint64_t, int64_t>& p) const {
       base::Hasher hasher;
diff --git a/src/trace_processor/importers/perf/perf_data_tracker_unittest.cc b/src/trace_processor/importers/perf/perf_data_tracker_unittest.cc
index 3cbdc80..129271c 100644
--- a/src/trace_processor/importers/perf/perf_data_tracker_unittest.cc
+++ b/src/trace_processor/importers/perf/perf_data_tracker_unittest.cc
@@ -69,6 +69,7 @@
   PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context_);
 
   PerfDataTracker::Mmap2Record rec;
+  rec.cpu_mode = protos::pbzero::Profiling::MODE_USER;
   rec.filename = "file1";
   rec.num.addr = 1000;
   rec.num.len = 100;
@@ -94,6 +95,7 @@
   PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context_);
 
   PerfDataTracker::Mmap2Record rec;
+  rec.cpu_mode = protos::pbzero::Profiling::MODE_USER;
   rec.filename = "file1";
   rec.num.addr = 1000;
   rec.num.len = 100;
diff --git a/src/trace_processor/importers/proto/BUILD.gn b/src/trace_processor/importers/proto/BUILD.gn
index 2e542d0..03b0399 100644
--- a/src/trace_processor/importers/proto/BUILD.gn
+++ b/src/trace_processor/importers/proto/BUILD.gn
@@ -26,6 +26,8 @@
     "chrome_system_probes_parser.h",
     "default_modules.cc",
     "default_modules.h",
+    "jit_tracker.cc",
+    "jit_tracker.h",
     "memory_tracker_snapshot_module.cc",
     "memory_tracker_snapshot_module.h",
     "memory_tracker_snapshot_parser.cc",
@@ -244,6 +246,7 @@
   sources = [
     "active_chrome_processes_tracker_unittest.cc",
     "heap_graph_tracker_unittest.cc",
+    "jit_tracker_unittest.cc",
     "network_trace_module_unittest.cc",
     "perf_sample_tracker_unittest.cc",
     "profile_packet_sequence_state_unittest.cc",
@@ -272,7 +275,9 @@
     "../../../protozero",
     "../../sorter",
     "../../storage",
+    "../../tables",
     "../../types",
+    "../../util:build_id",
     "../../util:descriptors",
     "../../util:profiler_util",
     "../common",
diff --git a/src/trace_processor/importers/proto/jit_tracker.cc b/src/trace_processor/importers/proto/jit_tracker.cc
new file mode 100644
index 0000000..4cab6f1
--- /dev/null
+++ b/src/trace_processor/importers/proto/jit_tracker.cc
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_processor/importers/proto/jit_tracker.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "perfetto/base/logging.h"
+#include "src/trace_processor/importers/common/address_range.h"
+#include "src/trace_processor/importers/common/jit_cache.h"
+#include "src/trace_processor/importers/common/mapping_tracker.h"
+#include "src/trace_processor/importers/common/stack_profile_tracker.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto::trace_processor {
+
+JitTracker::JitTracker(TraceProcessorContext* context) : context_(context) {}
+
+JitTracker::~JitTracker() = default;
+
+JitCache* JitTracker::CreateJitCache(std::string name,
+                                     UniquePid upid,
+                                     AddressRange range) {
+  auto cache =
+      std::make_unique<JitCache>(context_, std::move(name), upid, range);
+  JitCache* cache_ptr = cache.get();
+  // Dealing with overlaps is complicated. Do we delete the entire range, only
+  // the overlap, how do we deal with requests to the old JitCache. And it
+  // doesn't really happen in practice (e.g. for v8 you would need to delete an
+  // isolate and recreate it.), so just make sure our assumption (this never
+  // happens) is correct with a check.
+  PERFETTO_CHECK(caches_[upid].Emplace(range, std::move(cache)));
+  context_->mapping_tracker->AddJitRange(upid, range, cache_ptr);
+  return cache_ptr;
+}
+
+}  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/proto/jit_tracker.h b/src/trace_processor/importers/proto/jit_tracker.h
new file mode 100644
index 0000000..d6f698d
--- /dev/null
+++ b/src/trace_processor/importers/proto/jit_tracker.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_JIT_TRACKER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_JIT_TRACKER_H_
+
+#include <cstdint>
+#include <memory>
+
+#include "perfetto/ext/base/flat_hash_map.h"
+#include "src/trace_processor/importers/common/address_range.h"
+#include "src/trace_processor/importers/common/stack_profile_tracker.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/types/destructible.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto::trace_processor {
+
+class JitCache;
+
+// Keeps track of Jitted code.
+class JitTracker : public Destructible {
+ public:
+  static JitTracker* GetOrCreate(TraceProcessorContext* context) {
+    if (!context->jit_tracker) {
+      context->jit_tracker.reset(new JitTracker(context));
+    }
+    return static_cast<JitTracker*>(context->jit_tracker.get());
+  }
+
+  ~JitTracker() override;
+
+  // Creates a JitCache. Any frame interning request for the given pid in the
+  // given address range will be forwarded from the StackProfileTracker to this
+  // cache.
+  JitCache* CreateJitCache(std::string name,
+                           UniquePid upid,
+                           AddressRange range);
+
+ private:
+  explicit JitTracker(TraceProcessorContext* context);
+
+  FrameId InternUnknownFrame(MappingId mapping_id, uint64_t rel_pc);
+
+  TraceProcessorContext* const context_;
+
+  base::FlatHashMap<UniquePid, AddressRangeMap<std::unique_ptr<JitCache>>>
+      caches_;
+};
+
+}  // namespace perfetto::trace_processor
+
+#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_JIT_TRACKER_H_
diff --git a/src/trace_processor/importers/proto/jit_tracker_unittest.cc b/src/trace_processor/importers/proto/jit_tracker_unittest.cc
new file mode 100644
index 0000000..1903340
--- /dev/null
+++ b/src/trace_processor/importers/proto/jit_tracker_unittest.cc
@@ -0,0 +1,183 @@
+/*
+ * 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/proto/jit_tracker.h"
+
+#include <cstdint>
+#include <optional>
+#include <string>
+
+#include "perfetto/ext/base/string_view.h"
+#include "perfetto/trace_processor/trace_blob_view.h"
+#include "src/trace_processor/importers/common/address_range.h"
+#include "src/trace_processor/importers/common/jit_cache.h"
+#include "src/trace_processor/importers/common/mapping_tracker.h"
+#include "src/trace_processor/importers/common/process_tracker.h"
+#include "src/trace_processor/importers/common/stack_profile_tracker.h"
+#include "src/trace_processor/storage/stats.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/tables/jit_tables_py.h"
+#include "src/trace_processor/util/build_id.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace {
+
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::Eq;
+using ::testing::FieldsAre;
+using ::testing::IsEmpty;
+using ::testing::Ne;
+using ::testing::Optional;
+using ::testing::SaveArg;
+
+class JitTrackerTest : public testing::Test {
+ public:
+  JitTrackerTest() {
+    context_.storage.reset(new TraceStorage());
+    context_.stack_profile_tracker.reset(new StackProfileTracker(&context_));
+    context_.mapping_tracker.reset(new MappingTracker(&context_));
+    context_.process_tracker.reset(new ProcessTracker(&context_));
+    jit_tracker_ = JitTracker::GetOrCreate(&context_);
+  }
+
+ protected:
+  UserMemoryMapping& AddMapping(UniquePid upid,
+                                AddressRange range,
+                                uint64_t exact_offset = 0,
+                                uint64_t load_bias = 0) {
+    uint32_t id = context_.storage->stack_profile_mapping_table().row_count();
+    CreateMappingParams params;
+    params.memory_range = range;
+    params.build_id =
+        BuildId::FromRaw(reinterpret_cast<const uint8_t*>(&id), sizeof(id));
+    params.exact_offset = exact_offset;
+    params.start_offset = exact_offset;
+    params.load_bias = load_bias;
+    params.name = "Mapping ";
+    params.name += std::to_string(id);
+    return context_.mapping_tracker->CreateUserMemoryMapping(upid,
+                                                             std::move(params));
+  }
+
+  TraceProcessorContext context_;
+  JitTracker* jit_tracker_;
+};
+
+TEST_F(JitTrackerTest, BasicFunctionality) {
+  const UniquePid upid = context_.process_tracker->GetOrCreateProcess(1234);
+  const UniqueTid utid = context_.process_tracker->UpdateThread(4321, 1234);
+  const AddressRange jit_range(0, 1000);
+  auto& mapping = AddMapping(upid, jit_range);
+  JitCache* cache = jit_tracker_->CreateJitCache("name", upid, jit_range);
+
+  const StringId function_name = context_.storage->InternString("Function 1");
+  const StringId source_file = context_.storage->InternString("SourceFile");
+  const int64_t create_ts = 12345;
+  const AddressRange code_range(0, 100);
+
+  auto code_id = cache->LoadCode(create_ts, utid, code_range, function_name,
+                                 JitCache::SourceLocation{source_file, 10},
+                                 TraceBlobView());
+
+  auto code = *context_.storage->jit_code_table().FindById(code_id);
+  EXPECT_THAT(code.create_ts(), Eq(create_ts));
+  EXPECT_THAT(code.estimated_delete_ts(), Eq(std::nullopt));
+  EXPECT_THAT(code.utid(), Eq(utid));
+  EXPECT_THAT(code.start_address(),
+              Eq(static_cast<int64_t>(code_range.start())));
+  EXPECT_THAT(code.size(), Eq(static_cast<int64_t>(code_range.size())));
+  EXPECT_THAT(code.function_name(), Eq(function_name));
+
+  auto frame_id = mapping.InternFrame(50, "");
+
+  auto frame =
+      *context_.storage->stack_profile_frame_table().FindById(frame_id);
+  EXPECT_THAT(frame.name(), Eq(function_name));
+
+  auto row = context_.storage->jit_frame_table().FindById(
+      tables::JitFrameTable::Id(0));
+  ASSERT_THAT(row, Ne(std::nullopt));
+
+  EXPECT_THAT(row->jit_code_id(), Eq(code_id));
+  EXPECT_THAT(row->frame_id(), Eq(frame_id));
+}
+
+TEST_F(JitTrackerTest, FunctionOverlapUpdatesDeleteTs) {
+  const UniquePid upid = context_.process_tracker->GetOrCreateProcess(1234);
+  const UniqueTid utid = context_.process_tracker->UpdateThread(4321, 1234);
+  const AddressRange jit_range(0, 1000);
+  auto& mapping = AddMapping(upid, jit_range);
+  JitCache* cache = jit_tracker_->CreateJitCache("name", upid, jit_range);
+
+  const StringId function_name_1 = context_.storage->InternString("Function 1");
+  const StringId function_name_2 = context_.storage->InternString("Function 2");
+  const StringId source_file = context_.storage->InternString("SourceFile");
+  const int64_t create_ts_1 = 12345;
+  const int64_t create_ts_2 = 23456;
+  const AddressRange code_range_1(0, 100);
+  const AddressRange code_range_2(50, 200);
+
+  auto code_id_1 = cache->LoadCode(
+      create_ts_1, utid, code_range_1, function_name_1,
+      JitCache::SourceLocation{source_file, 10}, TraceBlobView());
+  auto code_id_2 = cache->LoadCode(
+      create_ts_2, utid, code_range_2, function_name_2,
+      JitCache::SourceLocation{source_file, 10}, TraceBlobView());
+  EXPECT_THAT(code_id_1, Ne(code_id_2));
+
+  auto code_1 = *context_.storage->jit_code_table().FindById(code_id_1);
+  auto code_2 = *context_.storage->jit_code_table().FindById(code_id_2);
+
+  // Code 1 has been deleted
+  EXPECT_THAT(code_1.create_ts(), Eq(create_ts_1));
+  EXPECT_THAT(code_1.estimated_delete_ts(), Eq(create_ts_2));
+
+  // The only active code is 2 at this point.
+  EXPECT_THAT(code_2.create_ts(), Eq(create_ts_2));
+  EXPECT_THAT(code_2.estimated_delete_ts(), Eq(std::nullopt));
+
+  // No frame should mention code 1
+  FrameId frame_id = mapping.InternFrame(50, "");
+  auto frame_a =
+      *context_.storage->stack_profile_frame_table().FindById(frame_id);
+  EXPECT_THAT(frame_a.name(), Eq(function_name_2));
+  ASSERT_THAT(context_.storage->jit_frame_table().row_count(), Eq(1u));
+  auto row = context_.storage->jit_frame_table().FindById(
+      tables::JitFrameTable::Id(0));
+  EXPECT_THAT(row->jit_code_id(), Eq(code_id_2));
+  EXPECT_THAT(row->frame_id(), Eq(frame_id));
+
+  // Frames for the old code 1 must fail to resolve to a jitted function but
+  // still generate a frame.
+  EXPECT_THAT(context_.storage->stats().at(stats::jit_unknown_frame).value,
+              Eq(0));
+  frame_id = mapping.InternFrame(0, "custom");
+  EXPECT_THAT(context_.storage->stats().at(stats::jit_unknown_frame).value,
+              Eq(1));
+  auto frame_b =
+      *context_.storage->stack_profile_frame_table().FindById(frame_id);
+  EXPECT_THAT(frame_a.id(), Ne(frame_b.id()));
+  EXPECT_THAT(context_.storage->GetString(frame_b.name()), Eq("custom"));
+  EXPECT_THAT(context_.storage->jit_frame_table().row_count(), Eq(1u));
+}
+
+}  // namespace
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/proto/packet_sequence_state_generation.h b/src/trace_processor/importers/proto/packet_sequence_state_generation.h
index 1f05d5c..f310309 100644
--- a/src/trace_processor/importers/proto/packet_sequence_state_generation.h
+++ b/src/trace_processor/importers/proto/packet_sequence_state_generation.h
@@ -84,6 +84,8 @@
       return generation_->GetOrCreate<T>();
     }
 
+    PacketSequenceState* state() const { return generation_->state(); }
+
    private:
     friend PacketSequenceStateGeneration;
     // Called when the a new generation is created as a result of
diff --git a/src/trace_processor/importers/proto/profile_module.cc b/src/trace_processor/importers/proto/profile_module.cc
index b80cae7..f8e2711 100644
--- a/src/trace_processor/importers/proto/profile_module.cc
+++ b/src/trace_processor/importers/proto/profile_module.cc
@@ -158,7 +158,8 @@
 
   uint32_t pid = static_cast<uint32_t>(sequence_state->state()->pid());
   uint32_t tid = static_cast<uint32_t>(sequence_state->state()->tid());
-  UniqueTid utid = procs->UpdateThread(tid, pid);
+  const UniqueTid utid = procs->UpdateThread(tid, pid);
+  const UniquePid upid = procs->GetOrCreateProcess(pid);
 
   // Iterate through timestamps and callstacks simultaneously.
   auto timestamp_it = packet.timestamp_delta_us();
@@ -172,7 +173,7 @@
     }
 
     auto opt_cs_id =
-        stack_profile_sequence_state.FindOrInsertCallstack(*callstack_it);
+        stack_profile_sequence_state.FindOrInsertCallstack(upid, *callstack_it);
     if (!opt_cs_id) {
       context_->storage->IncrementStats(stats::stackprofile_parser_error);
       continue;
@@ -247,11 +248,16 @@
       ts, static_cast<double>(sample.timebase_count()),
       sampling_stream.timebase_track_id);
 
+  const UniqueTid utid =
+      context_->process_tracker->UpdateThread(sample.tid(), sample.pid());
+  const UniquePid upid =
+      context_->process_tracker->GetOrCreateProcess(sample.pid());
+
   StackProfileSequenceState& stack_profile_sequence_state =
       *sequence_state->GetOrCreate<StackProfileSequenceState>();
   uint64_t callstack_iid = sample.callstack_iid();
   std::optional<CallsiteId> cs_id =
-      stack_profile_sequence_state.FindOrInsertCallstack(callstack_iid);
+      stack_profile_sequence_state.FindOrInsertCallstack(upid, callstack_iid);
 
   // A failed lookup of the interned callstack can mean either:
   // (a) This is a counter-only profile without callstacks. Due to an
@@ -274,9 +280,6 @@
     return;
   }
 
-  UniqueTid utid =
-      context_->process_tracker->UpdateThread(sample.tid(), sample.pid());
-
   using protos::pbzero::Profiling;
   TraceStorage* storage = context_->storage.get();
 
diff --git a/src/trace_processor/importers/proto/profile_packet_sequence_state.cc b/src/trace_processor/importers/proto/profile_packet_sequence_state.cc
index 31841fc..d909768 100644
--- a/src/trace_processor/importers/proto/profile_packet_sequence_state.cc
+++ b/src/trace_processor/importers/proto/profile_packet_sequence_state.cc
@@ -103,8 +103,10 @@
 
 void ProfilePacketSequenceState::AddFrame(SourceFrameId id,
                                           const SourceFrame& frame) {
-  VirtualMemoryMapping** mapping = mappings_.Find(frame.mapping_id);
-  if (!mapping) {
+  VirtualMemoryMapping* mapping;
+  if (auto* ptr = mappings_.Find(frame.mapping_id); ptr) {
+    mapping = *ptr;
+  } else {
     context_->storage->IncrementStats(stats::stackprofile_invalid_mapping_id);
     return;
   }
@@ -116,7 +118,8 @@
   }
 
   FrameId frame_id =
-      (*mapping)->InternFrame(frame.rel_pc, base::StringView(*function_name));
+      mapping->InternFrame(frame.rel_pc, base::StringView(*function_name));
+  PERFETTO_CHECK(!mapping->is_jitted());
   frames_.Insert(id, frame_id);
 }
 
@@ -174,15 +177,14 @@
 }
 
 void ProfilePacketSequenceState::AddAllocation(const SourceAllocation& alloc) {
-  auto opt_callstack_id = FindOrInsertCallstack(alloc.callstack_id);
+  const UniquePid upid = context_->process_tracker->GetOrCreateProcess(
+      static_cast<uint32_t>(alloc.pid));
+  auto opt_callstack_id = FindOrInsertCallstack(upid, alloc.callstack_id);
   if (!opt_callstack_id)
     return;
 
   CallsiteId callstack_id = *opt_callstack_id;
 
-  UniquePid upid = context_->process_tracker->GetOrCreateProcess(
-      static_cast<uint32_t>(alloc.pid));
-
   tables::HeapProfileAllocationTable::Row alloc_row{
       alloc.timestamp,
       upid,
@@ -277,11 +279,13 @@
 }
 
 std::optional<CallsiteId> ProfilePacketSequenceState::FindOrInsertCallstack(
+    UniquePid upid,
     uint64_t iid) {
   if (CallsiteId* id = callstacks_.Find(iid); id) {
     return *id;
   }
-  return GetOrCreate<StackProfileSequenceState>()->FindOrInsertCallstack(iid);
+  return GetOrCreate<StackProfileSequenceState>()->FindOrInsertCallstack(upid,
+                                                                         iid);
 }
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/importers/proto/profile_packet_sequence_state.h b/src/trace_processor/importers/proto/profile_packet_sequence_state.h
index 99661da..3fb8dcd 100644
--- a/src/trace_processor/importers/proto/profile_packet_sequence_state.h
+++ b/src/trace_processor/importers/proto/profile_packet_sequence_state.h
@@ -122,7 +122,7 @@
   // via the Add* methods), and then, if this lookup fails, in the InternedData
   // instead.
   std::optional<MappingId> FindOrInsertMapping(uint64_t iid);
-  std::optional<CallsiteId> FindOrInsertCallstack(uint64_t iid);
+  std::optional<CallsiteId> FindOrInsertCallstack(UniquePid upid, uint64_t iid);
 
   TraceProcessorContext* const context_;
 
diff --git a/src/trace_processor/importers/proto/stack_profile_sequence_state.cc b/src/trace_processor/importers/proto/stack_profile_sequence_state.cc
index a469f92..2688d26 100644
--- a/src/trace_processor/importers/proto/stack_profile_sequence_state.cc
+++ b/src/trace_processor/importers/proto/stack_profile_sequence_state.cc
@@ -16,17 +16,20 @@
 
 #include "src/trace_processor/importers/proto/stack_profile_sequence_state.h"
 
+#include <cstdint>
 #include <optional>
+#include <utility>
 #include <vector>
 
 #include "perfetto/base/logging.h"
+#include "perfetto/ext/base/string_utils.h"
 #include "perfetto/ext/base/string_view.h"
 #include "protos/perfetto/trace/interned_data/interned_data.pbzero.h"
 #include "protos/perfetto/trace/profiling/profile_common.pbzero.h"
 #include "src/trace_processor/importers/common/address_range.h"
 #include "src/trace_processor/importers/common/mapping_tracker.h"
+#include "src/trace_processor/importers/common/process_tracker.h"
 #include "src/trace_processor/importers/common/stack_profile_tracker.h"
-#include "src/trace_processor/importers/proto/packet_sequence_state.h"
 #include "src/trace_processor/importers/proto/packet_sequence_state_generation.h"
 #include "src/trace_processor/importers/proto/profile_packet_utils.h"
 #include "src/trace_processor/storage/stats.h"
@@ -41,6 +44,15 @@
   return base::StringView(reinterpret_cast<const char*>(bytes.data),
                           bytes.size);
 }
+
+// Determine wether this is the magical kernel mapping created in
+// `perfetto::::profiling::Unwinder::SymbolizeKernelCallchain`
+bool IsMagicalKernelMapping(const CreateMappingParams& params) {
+  return params.memory_range.start() == 0 &&
+         params.memory_range.length() == 0 && params.exact_offset == 0 &&
+         !params.build_id.has_value() && (params.name == "/kernel");
+}
+
 }  // namespace
 
 StackProfileSequenceState::StackProfileSequenceState(
@@ -49,17 +61,22 @@
 
 StackProfileSequenceState::~StackProfileSequenceState() = default;
 
-std::optional<MappingId> StackProfileSequenceState::FindOrInsertMapping(
+VirtualMemoryMapping* StackProfileSequenceState::FindOrInsertMapping(
     uint64_t iid) {
-  if (VirtualMemoryMapping* mapping = FindOrInsertMappingImpl(iid); mapping) {
-    return mapping->mapping_id();
+  if (state()->pid_and_tid_valid()) {
+    return FindOrInsertMappingImpl(
+        context_->process_tracker->GetOrCreateProcess(
+            static_cast<uint32_t>(state()->pid())),
+        iid);
   }
-  return std::nullopt;
+
+  return FindOrInsertMappingImpl(std::nullopt, iid);
 }
 
 VirtualMemoryMapping* StackProfileSequenceState::FindOrInsertMappingImpl(
+    std::optional<UniquePid> upid,
     uint64_t iid) {
-  if (auto ptr = cached_mappings_.Find(iid); ptr) {
+  if (auto ptr = cached_mappings_.Find({upid, iid}); ptr) {
     return *ptr;
   }
   auto* decoder =
@@ -87,7 +104,9 @@
   if (!build_id) {
     return nullptr;
   }
-  params.build_id = BuildId::FromRaw(*build_id);
+  if (!build_id->empty()) {
+    params.build_id = BuildId::FromRaw(*build_id);
+  }
 
   params.memory_range = AddressRange(decoder->start(), decoder->end());
   params.exact_offset = decoder->exact_offset();
@@ -95,11 +114,26 @@
   params.load_bias = decoder->load_bias();
   params.name = ProfilePacketUtils::MakeMappingName(path_components);
 
-  VirtualMemoryMapping& mapping =
-      context_->mapping_tracker->InternMemoryMapping(std::move(params));
+  VirtualMemoryMapping* mapping;
 
-  cached_mappings_.Insert(iid, &mapping);
-  return &mapping;
+  if (IsMagicalKernelMapping(params)) {
+    mapping = &context_->mapping_tracker->CreateKernelMemoryMapping(
+        std::move(params));
+    // A lot of tests to not set a proper mapping range
+    // Dummy mappings can also be emitted (e.g. for errors during unwinding)
+  } else if (params.memory_range.empty()) {
+    mapping =
+        &context_->mapping_tracker->InternMemoryMapping(std::move(params));
+  } else if (upid.has_value()) {
+    mapping = &context_->mapping_tracker->CreateUserMemoryMapping(
+        *upid, std::move(params));
+  } else {
+    mapping =
+        &context_->mapping_tracker->InternMemoryMapping(std::move(params));
+  }
+
+  cached_mappings_.Insert({upid, iid}, mapping);
+  return mapping;
 }
 
 std::optional<base::StringView>
@@ -134,8 +168,9 @@
 }
 
 std::optional<CallsiteId> StackProfileSequenceState::FindOrInsertCallstack(
+    UniquePid upid,
     uint64_t iid) {
-  if (CallsiteId* id = cached_callstacks_.Find(iid); id) {
+  if (CallsiteId* id = cached_callstacks_.Find({upid, iid}); id) {
     return *id;
   }
   auto* decoder = LookupInternedMessage<
@@ -149,7 +184,7 @@
   std::optional<CallsiteId> parent_callsite_id;
   uint32_t depth = 0;
   for (auto it = decoder->frame_ids(); it; ++it) {
-    std::optional<FrameId> frame_id = FindOrInsertFrame(*it);
+    std::optional<FrameId> frame_id = FindOrInsertFrame(upid, *it);
     if (!frame_id) {
       return std::nullopt;
     }
@@ -163,14 +198,15 @@
     return std::nullopt;
   }
 
-  cached_callstacks_.Insert(iid, *parent_callsite_id);
+  cached_callstacks_.Insert({upid, iid}, *parent_callsite_id);
 
   return parent_callsite_id;
 }
 
 std::optional<FrameId> StackProfileSequenceState::FindOrInsertFrame(
+    UniquePid upid,
     uint64_t iid) {
-  if (FrameId* id = cached_frames_.Find(iid); id) {
+  if (FrameId* id = cached_frames_.Find({upid, iid}); id) {
     return *id;
   }
   auto* decoder =
@@ -182,7 +218,7 @@
   }
 
   VirtualMemoryMapping* mapping =
-      FindOrInsertMappingImpl(decoder->mapping_id());
+      FindOrInsertMappingImpl(upid, decoder->mapping_id());
   if (!mapping) {
     return std::nullopt;
   }
@@ -198,7 +234,9 @@
   }
 
   FrameId frame_id = mapping->InternFrame(decoder->rel_pc(), function_name);
-  cached_frames_.Insert(iid, frame_id);
+  if (!mapping->is_jitted()) {
+    cached_frames_.Insert({upid, iid}, frame_id);
+  }
 
   return frame_id;
 }
diff --git a/src/trace_processor/importers/proto/stack_profile_sequence_state.h b/src/trace_processor/importers/proto/stack_profile_sequence_state.h
index 82de785..518388d 100644
--- a/src/trace_processor/importers/proto/stack_profile_sequence_state.h
+++ b/src/trace_processor/importers/proto/stack_profile_sequence_state.h
@@ -21,7 +21,9 @@
 #include <optional>
 
 #include "perfetto/ext/base/flat_hash_map.h"
+#include "perfetto/ext/base/hash.h"
 #include "perfetto/ext/base/string_view.h"
+#include "src/trace_processor/importers/common/mapping_tracker.h"
 #include "src/trace_processor/importers/proto/packet_sequence_state_generation.h"
 #include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/types/trace_processor_context.h"
@@ -41,21 +43,64 @@
 
   virtual ~StackProfileSequenceState() override;
 
-  std::optional<MappingId> FindOrInsertMapping(uint64_t iid);
-  std::optional<CallsiteId> FindOrInsertCallstack(uint64_t iid);
+  // Returns `nullptr`if non could be found.
+  VirtualMemoryMapping* FindOrInsertMapping(uint64_t iid);
+  std::optional<CallsiteId> FindOrInsertCallstack(UniquePid upid, uint64_t iid);
 
  private:
-  // Returns `nullptr`if non could be found.
-  VirtualMemoryMapping* FindOrInsertMappingImpl(uint64_t iid);
   std::optional<base::StringView> LookupInternedBuildId(uint64_t iid);
   std::optional<base::StringView> LookupInternedMappingPath(uint64_t iid);
   std::optional<base::StringView> LookupInternedFunctionName(uint64_t iid);
-  std::optional<FrameId> FindOrInsertFrame(uint64_t iid);
+
+  // Returns `nullptr`if non could be found.
+  VirtualMemoryMapping* FindOrInsertMappingImpl(std::optional<UniquePid> upid,
+                                                uint64_t iid);
+  std::optional<FrameId> FindOrInsertFrame(UniquePid upid, uint64_t iid);
 
   TraceProcessorContext* const context_;
-  base::FlatHashMap<uint64_t, VirtualMemoryMapping*> cached_mappings_;
-  base::FlatHashMap<uint64_t, CallsiteId> cached_callstacks_;
-  base::FlatHashMap<uint64_t, FrameId> cached_frames_;
+
+  struct OptionalUniquePidAndIid {
+    std::optional<UniquePid> upid;
+    uint64_t iid;
+
+    bool operator==(const OptionalUniquePidAndIid& o) const {
+      return upid == o.upid && iid == o.iid;
+    }
+
+    struct Hasher {
+      size_t operator()(const OptionalUniquePidAndIid& o) const {
+        base::Hasher h;
+        h.Update(o.iid);
+        if (o.upid) {
+          h.Update(*o.upid);
+        }
+        return static_cast<size_t>(h.digest());
+      }
+    };
+  };
+
+  struct UniquePidAndIid {
+    UniquePid upid;
+    uint64_t iid;
+
+    bool operator==(const UniquePidAndIid& o) const {
+      return upid == o.upid && iid == o.iid;
+    }
+
+    struct Hasher {
+      size_t operator()(const UniquePidAndIid& o) const {
+        return static_cast<size_t>(base::Hasher::Combine(o.upid, o.iid));
+      }
+    };
+  };
+  base::FlatHashMap<OptionalUniquePidAndIid,
+                    VirtualMemoryMapping*,
+                    OptionalUniquePidAndIid::Hasher>
+      cached_mappings_;
+  base::FlatHashMap<UniquePidAndIid, FrameId, UniquePidAndIid::Hasher>
+      cached_frames_;
+  base::FlatHashMap<UniquePidAndIid, CallsiteId, UniquePidAndIid::Hasher>
+      cached_callstacks_;
 };
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/importers/proto/system_probes_parser.cc b/src/trace_processor/importers/proto/system_probes_parser.cc
index 09adb4a..2654fdb 100644
--- a/src/trace_processor/importers/proto/system_probes_parser.cc
+++ b/src/trace_processor/importers/proto/system_probes_parser.cc
@@ -524,7 +524,8 @@
           upid, static_cast<uint32_t>(proc.uid()));
     }
 
-    if (proc.process_start_from_boot() > 0) {
+    // note: early kernel threads can have an age of zero (at tick resolution)
+    if (proc.has_process_start_from_boot()) {
       base::StatusOr<int64_t> start_ts = context_->clock_tracker->ToTraceTime(
           protos::pbzero::BUILTIN_CLOCK_BOOTTIME,
           static_cast<int64_t>(proc.process_start_from_boot()));
@@ -652,6 +653,8 @@
 
 void SystemProbesParser::ParseSystemInfo(ConstBytes blob) {
   protos::pbzero::SystemInfo::Decoder packet(blob.data, blob.size);
+  SystemInfoTracker* system_info_tracker =
+      SystemInfoTracker::GetOrCreate(context_);
   if (packet.has_utsname()) {
     ConstBytes utsname_blob = packet.utsname();
     protos::pbzero::Utsname::Decoder utsname(utsname_blob.data,
@@ -666,8 +669,6 @@
                     machine.ToStdString().c_str());
     }
 
-    SystemInfoTracker* system_info_tracker =
-        SystemInfoTracker::GetOrCreate(context_);
     system_info_tracker->SetKernelVersion(utsname.sysname(), utsname.release());
 
     StringPool::Id sysname_id =
@@ -717,13 +718,14 @@
         metadata::android_sdk_version, Variadic::Integer(*opt_sdk_version));
   }
 
-  int64_t hz = packet.hz();
-  if (hz > 0)
-    ms_per_tick_ = 1000u / static_cast<uint64_t>(hz);
-
   page_size_ = packet.page_size();
-  if (!page_size_)
+  if (!page_size_) {
     page_size_ = 4096;
+  }
+
+  if (packet.has_num_cpus()) {
+    system_info_tracker->SetNumCpus(packet.num_cpus());
+  }
 }
 
 void SystemProbesParser::ParseCpuInfo(ConstBytes blob) {
diff --git a/src/trace_processor/importers/proto/system_probes_parser.h b/src/trace_processor/importers/proto/system_probes_parser.h
index 0378128..776b2c6 100644
--- a/src/trace_processor/importers/proto/system_probes_parser.h
+++ b/src/trace_processor/importers/proto/system_probes_parser.h
@@ -18,7 +18,6 @@
 #define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_SYSTEM_PROBES_PARSER_H_
 
 #include <array>
-#include <set>
 #include <vector>
 
 #include "perfetto/protozero/field.h"
@@ -79,7 +78,6 @@
   std::array<StringId, protos::pbzero::SysStats_PsiSample_PsiResource_MAX + 1>
       sys_stats_psi_resource_names_{};
 
-  uint64_t ms_per_tick_ = 0;
   uint32_t page_size_ = 0;
 
   int64_t prev_read_amount = -1;
diff --git a/src/trace_processor/importers/proto/track_event_parser.cc b/src/trace_processor/importers/proto/track_event_parser.cc
index 00bf52e..cdf2c45 100644
--- a/src/trace_processor/importers/proto/track_event_parser.cc
+++ b/src/trace_processor/importers/proto/track_event_parser.cc
@@ -196,14 +196,15 @@
   }
   // Interned mapping_id loses it's meaning when the sequence ends. So we need
   // to get an id from stack_profile_mapping table.
-  auto mapping_id = delegate.seq_state()
-                        ->GetOrCreate<StackProfileSequenceState>()
-                        ->FindOrInsertMapping(decoder->mapping_id());
-  if (!mapping_id) {
+  auto mapping = delegate.seq_state()
+                     ->GetOrCreate<StackProfileSequenceState>()
+                     ->FindOrInsertMapping(decoder->mapping_id());
+  if (!mapping) {
     return std::nullopt;
   }
   delegate.AddUnsignedInteger(
-      util::ProtoToArgsParser::Key(prefix + ".mapping_id"), mapping_id->value);
+      util::ProtoToArgsParser::Key(prefix + ".mapping_id"),
+      mapping->mapping_id().value);
   delegate.AddUnsignedInteger(util::ProtoToArgsParser::Key(prefix + ".rel_pc"),
                               decoder->rel_pc());
   return base::OkStatus();
diff --git a/src/trace_processor/importers/proto/v8_tracker.h b/src/trace_processor/importers/proto/v8_tracker.h
index 6da4d12..3d85377 100644
--- a/src/trace_processor/importers/proto/v8_tracker.h
+++ b/src/trace_processor/importers/proto/v8_tracker.h
@@ -111,7 +111,7 @@
     size_t operator()(const tables::V8JsFunctionTable::Row& v) const {
       return static_cast<size_t>(base::Hasher::Combine(
           v.name.raw_id(), v.v8_js_script_id.value, v.is_toplevel,
-          v.kind.raw_id(), v.line.value_or(0), v.column.value_or(0)));
+          v.kind.raw_id(), v.line.value_or(0), v.col.value_or(0)));
     }
   };
   base::FlatHashMap<tables::V8JsFunctionTable::Row,
diff --git a/src/trace_processor/importers/syscalls/syscall_tracker.cc b/src/trace_processor/importers/syscalls/syscall_tracker.cc
index ed71317..4560d5f 100644
--- a/src/trace_processor/importers/syscalls/syscall_tracker.cc
+++ b/src/trace_processor/importers/syscalls/syscall_tracker.cc
@@ -24,8 +24,7 @@
 #include "src/kernel_utils/syscall_table.h"
 #include "src/trace_processor/storage/stats.h"
 
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
 
 // TODO(primiano): The current design is broken in case of 32-bit processes
 // running on 64-bit kernel. At least on ARM, the syscal numbers don't match
@@ -48,8 +47,11 @@
     const char* name = syscalls.GetById(i);
     if (name && *name) {
       id = context_->storage->InternString(name);
-      if (!strcmp(name, "sys_write"))
+      if (!strcmp(name, "sys_write")) {
         sys_write_string_id_ = id;
+      } else if (!strcmp(name, "sys_rt_sigreturn")) {
+        sys_rt_sigreturn_string_id_ = id;
+      }
     } else {
       base::StackString<64> unknown_str("sys_%zu", i);
       id = context_->storage->InternString(unknown_str.string_view());
@@ -58,5 +60,4 @@
   }
 }
 
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/syscalls/syscall_tracker.h b/src/trace_processor/importers/syscalls/syscall_tracker.h
index 218256b..0005084 100644
--- a/src/trace_processor/importers/syscalls/syscall_tracker.h
+++ b/src/trace_processor/importers/syscalls/syscall_tracker.h
@@ -30,8 +30,7 @@
 #include "src/trace_processor/types/destructible.h"
 #include "src/trace_processor/types/trace_processor_context.h"
 
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
 
 class SyscallTracker : public Destructible {
  public:
@@ -57,8 +56,16 @@
       return;
 
     TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
-    context_->slice_tracker->Begin(ts, track_id, kNullStringId /* cat */, name,
-                                   args_callback);
+
+    // sys_rt_sigreturn does not return so should be inserted as an instant
+    // event. See https://github.com/google/perfetto/issues/733 for details.
+    if (name == sys_rt_sigreturn_string_id_) {
+      context_->slice_tracker->Scoped(ts, track_id, kNullStringId, name, 0,
+                                      args_callback);
+    } else {
+      context_->slice_tracker->Begin(ts, track_id, kNullStringId /* cat */,
+                                     name, args_callback);
+    }
 
     if (name == sys_write_string_id_) {
       if (utid >= in_sys_write_.size())
@@ -124,11 +131,11 @@
   // the relevant StringId (this avoids having to always do two conversions).
   std::array<StringId, kMaxSyscalls> arch_syscall_to_string_id_{};
   StringId sys_write_string_id_ = std::numeric_limits<StringId>::max();
+  StringId sys_rt_sigreturn_string_id_ = std::numeric_limits<StringId>::max();
   // UniqueTids currently in a sys_write syscall.
   BitVector in_sys_write_;
 };
 
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor
 
 #endif  // SRC_TRACE_PROCESSOR_IMPORTERS_SYSCALLS_SYSCALL_TRACKER_H_
diff --git a/src/trace_processor/importers/syscalls/syscall_tracker_unittest.cc b/src/trace_processor/importers/syscalls/syscall_tracker_unittest.cc
index f424a75..260d60d 100644
--- a/src/trace_processor/importers/syscalls/syscall_tracker_unittest.cc
+++ b/src/trace_processor/importers/syscalls/syscall_tracker_unittest.cc
@@ -49,6 +49,15 @@
                StringId name,
                SetArgsCallback args_callback),
               (override));
+  MOCK_METHOD(std::optional<SliceId>,
+              Scoped,
+              (int64_t timestamp,
+               TrackId track_id,
+               StringId cat,
+               StringId name,
+               int64_t duration,
+               SetArgsCallback args_callback),
+              (override));
 };
 
 class SyscallTrackerTest : public ::testing::Test {
@@ -83,6 +92,14 @@
   EXPECT_EQ(context.storage->GetString(end_name), "sys_57");
 }
 
+TEST_F(SyscallTrackerTest, ReportSysreturn) {
+  EXPECT_CALL(*slice_tracker, Scoped(_, _, _, _, _, _)).Times(1);
+
+  SyscallTracker* syscall_tracker = SyscallTracker::GetOrCreate(&context);
+  syscall_tracker->SetArchitecture(Architecture::kArm64);
+  syscall_tracker->Enter(100 /*ts*/, 42 /*utid*/, 139);
+}
+
 TEST_F(SyscallTrackerTest, Arm64) {
   constexpr TrackId track{0u};
   StringId begin_name = kNullStringId;
diff --git a/src/trace_processor/metrics/sql/android/BUILD.gn b/src/trace_processor/metrics/sql/android/BUILD.gn
index 0aa6e64..c3d3da0 100644
--- a/src/trace_processor/metrics/sql/android/BUILD.gn
+++ b/src/trace_processor/metrics/sql/android/BUILD.gn
@@ -109,6 +109,7 @@
     "power_profile_data/raven.sql",
     "power_profile_data/redfin.sql",
     "power_profile_data/sargo.sql",
+    "power_profile_data/shusky.sql",
     "power_profile_data/sunfish.sql",
     "power_profile_data/taimen.sql",
     "power_profile_data/walleye.sql",
diff --git a/src/trace_processor/metrics/sql/android/power_profile_data.sql b/src/trace_processor/metrics/sql/android/power_profile_data.sql
index 707c301..a79905c 100644
--- a/src/trace_processor/metrics/sql/android/power_profile_data.sql
+++ b/src/trace_processor/metrics/sql/android/power_profile_data.sql
@@ -29,3 +29,5 @@
 SELECT RUN_METRIC('android/power_profile_data/oriole.sql');
 SELECT RUN_METRIC('android/power_profile_data/raven.sql');
 SELECT RUN_METRIC('android/power_profile_data/bluejay.sql');
+SELECT RUN_METRIC('android/power_profile_data/shusky.sql');
+
diff --git a/src/trace_processor/metrics/sql/android/power_profile_data/shusky.sql b/src/trace_processor/metrics/sql/android/power_profile_data/shusky.sql
new file mode 100644
index 0000000..71f6753
--- /dev/null
+++ b/src/trace_processor/metrics/sql/android/power_profile_data/shusky.sql
@@ -0,0 +1,306 @@
+--
+-- 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.
+
+INSERT OR REPLACE INTO power_profile VALUES
+("shiba", 0, 0, 324000, 1.89),
+("shiba", 0, 0, 615000, 6.15),
+("shiba", 0, 0, 820000, 9.34),
+("shiba", 0, 0, 975000, 14.22),
+("shiba", 0, 0, 1098000, 18.94),
+("shiba", 0, 0, 1197000, 21.98),
+("shiba", 0, 0, 1328000, 26.83),
+("shiba", 0, 0, 1475000, 30.17),
+("shiba", 0, 0, 1548000, 41.55),
+("shiba", 0, 0, 1704000, 48.36),
+("shiba", 0, 0, 1844000, 58.45),
+("shiba", 0, 0, 1950000, 68.0),
+("shiba", 0, 0, 2024000, 78.0),
+("shiba", 0, 0, 2098000, 88.0),
+("shiba", 0, 0, 2147000, 98.0),
+("shiba", 1, 0, 324000, 1.89),
+("shiba", 1, 0, 615000, 6.15),
+("shiba", 1, 0, 820000, 9.34),
+("shiba", 1, 0, 975000, 14.22),
+("shiba", 1, 0, 1098000, 18.94),
+("shiba", 1, 0, 1197000, 21.98),
+("shiba", 1, 0, 1328000, 26.83),
+("shiba", 1, 0, 1475000, 30.17),
+("shiba", 1, 0, 1548000, 41.55),
+("shiba", 1, 0, 1704000, 48.36),
+("shiba", 1, 0, 1844000, 58.45),
+("shiba", 1, 0, 1950000, 68.0),
+("shiba", 1, 0, 2024000, 78.0),
+("shiba", 1, 0, 2098000, 88.0),
+("shiba", 1, 0, 2147000, 98.0),
+("shiba", 2, 0, 324000, 1.89),
+("shiba", 2, 0, 615000, 6.15),
+("shiba", 2, 0, 820000, 9.34),
+("shiba", 2, 0, 975000, 14.22),
+("shiba", 2, 0, 1098000, 18.94),
+("shiba", 2, 0, 1197000, 21.98),
+("shiba", 2, 0, 1328000, 26.83),
+("shiba", 2, 0, 1475000, 30.17),
+("shiba", 2, 0, 1548000, 41.55),
+("shiba", 2, 0, 1704000, 48.36),
+("shiba", 2, 0, 1844000, 58.45),
+("shiba", 2, 0, 1950000, 68.0),
+("shiba", 2, 0, 2024000, 78.0),
+("shiba", 2, 0, 2098000, 88.0),
+("shiba", 2, 0, 2147000, 98.0),
+("shiba", 3, 0, 324000, 1.89),
+("shiba", 3, 0, 615000, 6.15),
+("shiba", 3, 0, 820000, 9.34),
+("shiba", 3, 0, 975000, 14.22),
+("shiba", 3, 0, 1098000, 18.94),
+("shiba", 3, 0, 1197000, 21.98),
+("shiba", 3, 0, 1328000, 26.83),
+("shiba", 3, 0, 1475000, 30.17),
+("shiba", 3, 0, 1548000, 41.55),
+("shiba", 3, 0, 1704000, 48.36),
+("shiba", 3, 0, 1844000, 58.45),
+("shiba", 3, 0, 1950000, 68.0),
+("shiba", 3, 0, 2024000, 78.0),
+("shiba", 3, 0, 2098000, 88.0),
+("shiba", 3, 0, 2147000, 98.0),
+("shiba", 4, 1, 402000, 3.71),
+("shiba", 4, 1, 578000, 6.16),
+("shiba", 4, 1, 697000, 8.0),
+("shiba", 4, 1, 721000, 10.94),
+("shiba", 4, 1, 910000, 12.73),
+("shiba", 4, 1, 1082000, 14.4),
+("shiba", 4, 1, 1221000, 21.39),
+("shiba", 4, 1, 1328000, 24.1),
+("shiba", 4, 1, 1418000, 30.42),
+("shiba", 4, 1, 1549000, 42.49),
+("shiba", 4, 1, 1622000, 49.37),
+("shiba", 4, 1, 1836000, 58.09),
+("shiba", 4, 1, 1999000, 67.54),
+("shiba", 4, 1, 2130000, 79.04),
+("shiba", 4, 1, 2245000, 92.0),
+("shiba", 4, 1, 2352000, 104.0),
+("shiba", 4, 1, 2450000, 116.0),
+("shiba", 5, 1, 402000, 3.71),
+("shiba", 5, 1, 578000, 6.16),
+("shiba", 5, 1, 697000, 8.0),
+("shiba", 5, 1, 721000, 10.94),
+("shiba", 5, 1, 910000, 12.73),
+("shiba", 5, 1, 1082000, 14.4),
+("shiba", 5, 1, 1221000, 21.39),
+("shiba", 5, 1, 1328000, 24.1),
+("shiba", 5, 1, 1418000, 30.42),
+("shiba", 5, 1, 1549000, 42.49),
+("shiba", 5, 1, 1622000, 49.37),
+("shiba", 5, 1, 1836000, 58.09),
+("shiba", 5, 1, 1999000, 67.54),
+("shiba", 5, 1, 2130000, 79.04),
+("shiba", 5, 1, 2245000, 92.0),
+("shiba", 5, 1, 2352000, 104.0),
+("shiba", 5, 1, 2450000, 116.0),
+("shiba", 6, 1, 402000, 3.71),
+("shiba", 6, 1, 578000, 6.16),
+("shiba", 6, 1, 697000, 8.0),
+("shiba", 6, 1, 721000, 10.94),
+("shiba", 6, 1, 910000, 12.73),
+("shiba", 6, 1, 1082000, 14.4),
+("shiba", 6, 1, 1221000, 21.39),
+("shiba", 6, 1, 1328000, 24.1),
+("shiba", 6, 1, 1418000, 30.42),
+("shiba", 6, 1, 1549000, 42.49),
+("shiba", 6, 1, 1622000, 49.37),
+("shiba", 6, 1, 1836000, 58.09),
+("shiba", 6, 1, 1999000, 67.54),
+("shiba", 6, 1, 2130000, 79.04),
+("shiba", 6, 1, 2245000, 92.0),
+("shiba", 6, 1, 2352000, 104.0),
+("shiba", 6, 1, 2450000, 116.0),
+("shiba", 7, 1, 402000, 3.71),
+("shiba", 7, 1, 578000, 6.16),
+("shiba", 7, 1, 697000, 8.0),
+("shiba", 7, 1, 721000, 10.94),
+("shiba", 7, 1, 910000, 12.73),
+("shiba", 7, 1, 1082000, 14.4),
+("shiba", 7, 1, 1221000, 21.39),
+("shiba", 7, 1, 1328000, 24.1),
+("shiba", 7, 1, 1418000, 30.42),
+("shiba", 7, 1, 1549000, 42.49),
+("shiba", 7, 1, 1622000, 49.37),
+("shiba", 7, 1, 1836000, 58.09),
+("shiba", 7, 1, 1999000, 67.54),
+("shiba", 7, 1, 2130000, 79.04),
+("shiba", 7, 1, 2245000, 92.0),
+("shiba", 7, 1, 2352000, 104.0),
+("shiba", 7, 1, 2450000, 116.0),
+("shiba", 8, 2, 500000, 8.36),
+("shiba", 8, 2, 893000, 16.33),
+("shiba", 8, 2, 1164000, 19.44),
+("shiba", 8, 2, 1328000, 36.71),
+("shiba", 8, 2, 1557000, 41.42),
+("shiba", 8, 2, 1745000, 48.24),
+("shiba", 8, 2, 1852000, 54.77),
+("shiba", 8, 2, 1901000, 65.32),
+("shiba", 8, 2, 2049000, 69.58),
+("shiba", 8, 2, 2147000, 128.49),
+("shiba", 8, 2, 2294000, 142.15),
+("shiba", 8, 2, 2409000, 149.74),
+("shiba", 8, 2, 2556000, 164.78),
+("shiba", 8, 2, 2687000, 188.68),
+("shiba", 8, 2, 2802000, 193.15),
+("shiba", 8, 2, 2914000, 227.98),
+("shiba", 8, 2, 3015000, 254.25),
+("husky", 0, 0, 324000, 1.89),
+("husky", 0, 0, 615000, 6.15),
+("husky", 0, 0, 820000, 9.34),
+("husky", 0, 0, 975000, 14.22),
+("husky", 0, 0, 1098000, 18.94),
+("husky", 0, 0, 1197000, 21.98),
+("husky", 0, 0, 1328000, 26.83),
+("husky", 0, 0, 1475000, 30.17),
+("husky", 0, 0, 1548000, 41.55),
+("husky", 0, 0, 1704000, 48.36),
+("husky", 0, 0, 1844000, 58.45),
+("husky", 0, 0, 1950000, 68.0),
+("husky", 0, 0, 2024000, 78.0),
+("husky", 0, 0, 2098000, 88.0),
+("husky", 0, 0, 2147000, 98.0),
+("husky", 1, 0, 324000, 1.89),
+("husky", 1, 0, 615000, 6.15),
+("husky", 1, 0, 820000, 9.34),
+("husky", 1, 0, 975000, 14.22),
+("husky", 1, 0, 1098000, 18.94),
+("husky", 1, 0, 1197000, 21.98),
+("husky", 1, 0, 1328000, 26.83),
+("husky", 1, 0, 1475000, 30.17),
+("husky", 1, 0, 1548000, 41.55),
+("husky", 1, 0, 1704000, 48.36),
+("husky", 1, 0, 1844000, 58.45),
+("husky", 1, 0, 1950000, 68.0),
+("husky", 1, 0, 2024000, 78.0),
+("husky", 1, 0, 2098000, 88.0),
+("husky", 1, 0, 2147000, 98.0),
+("husky", 2, 0, 324000, 1.89),
+("husky", 2, 0, 615000, 6.15),
+("husky", 2, 0, 820000, 9.34),
+("husky", 2, 0, 975000, 14.22),
+("husky", 2, 0, 1098000, 18.94),
+("husky", 2, 0, 1197000, 21.98),
+("husky", 2, 0, 1328000, 26.83),
+("husky", 2, 0, 1475000, 30.17),
+("husky", 2, 0, 1548000, 41.55),
+("husky", 2, 0, 1704000, 48.36),
+("husky", 2, 0, 1844000, 58.45),
+("husky", 2, 0, 1950000, 68.0),
+("husky", 2, 0, 2024000, 78.0),
+("husky", 2, 0, 2098000, 88.0),
+("husky", 2, 0, 2147000, 98.0),
+("husky", 3, 0, 324000, 1.89),
+("husky", 3, 0, 615000, 6.15),
+("husky", 3, 0, 820000, 9.34),
+("husky", 3, 0, 975000, 14.22),
+("husky", 3, 0, 1098000, 18.94),
+("husky", 3, 0, 1197000, 21.98),
+("husky", 3, 0, 1328000, 26.83),
+("husky", 3, 0, 1475000, 30.17),
+("husky", 3, 0, 1548000, 41.55),
+("husky", 3, 0, 1704000, 48.36),
+("husky", 3, 0, 1844000, 58.45),
+("husky", 3, 0, 1950000, 68.0),
+("husky", 3, 0, 2024000, 78.0),
+("husky", 3, 0, 2098000, 88.0),
+("husky", 3, 0, 2147000, 98.0),
+("husky", 4, 1, 402000, 3.71),
+("husky", 4, 1, 578000, 6.16),
+("husky", 4, 1, 697000, 8.0),
+("husky", 4, 1, 721000, 10.94),
+("husky", 4, 1, 910000, 12.73),
+("husky", 4, 1, 1082000, 14.4),
+("husky", 4, 1, 1221000, 21.39),
+("husky", 4, 1, 1328000, 24.1),
+("husky", 4, 1, 1418000, 30.42),
+("husky", 4, 1, 1549000, 42.49),
+("husky", 4, 1, 1622000, 49.37),
+("husky", 4, 1, 1836000, 58.09),
+("husky", 4, 1, 1999000, 67.54),
+("husky", 4, 1, 2130000, 79.04),
+("husky", 4, 1, 2245000, 92.0),
+("husky", 4, 1, 2352000, 104.0),
+("husky", 4, 1, 2450000, 116.0),
+("husky", 5, 1, 402000, 3.71),
+("husky", 5, 1, 578000, 6.16),
+("husky", 5, 1, 697000, 8.0),
+("husky", 5, 1, 721000, 10.94),
+("husky", 5, 1, 910000, 12.73),
+("husky", 5, 1, 1082000, 14.4),
+("husky", 5, 1, 1221000, 21.39),
+("husky", 5, 1, 1328000, 24.1),
+("husky", 5, 1, 1418000, 30.42),
+("husky", 5, 1, 1549000, 42.49),
+("husky", 5, 1, 1622000, 49.37),
+("husky", 5, 1, 1836000, 58.09),
+("husky", 5, 1, 1999000, 67.54),
+("husky", 5, 1, 2130000, 79.04),
+("husky", 5, 1, 2245000, 92.0),
+("husky", 5, 1, 2352000, 104.0),
+("husky", 5, 1, 2450000, 116.0),
+("husky", 6, 1, 402000, 3.71),
+("husky", 6, 1, 578000, 6.16),
+("husky", 6, 1, 697000, 8.0),
+("husky", 6, 1, 721000, 10.94),
+("husky", 6, 1, 910000, 12.73),
+("husky", 6, 1, 1082000, 14.4),
+("husky", 6, 1, 1221000, 21.39),
+("husky", 6, 1, 1328000, 24.1),
+("husky", 6, 1, 1418000, 30.42),
+("husky", 6, 1, 1549000, 42.49),
+("husky", 6, 1, 1622000, 49.37),
+("husky", 6, 1, 1836000, 58.09),
+("husky", 6, 1, 1999000, 67.54),
+("husky", 6, 1, 2130000, 79.04),
+("husky", 6, 1, 2245000, 92.0),
+("husky", 6, 1, 2352000, 104.0),
+("husky", 6, 1, 2450000, 116.0),
+("husky", 7, 1, 402000, 3.71),
+("husky", 7, 1, 578000, 6.16),
+("husky", 7, 1, 697000, 8.0),
+("husky", 7, 1, 721000, 10.94),
+("husky", 7, 1, 910000, 12.73),
+("husky", 7, 1, 1082000, 14.4),
+("husky", 7, 1, 1221000, 21.39),
+("husky", 7, 1, 1328000, 24.1),
+("husky", 7, 1, 1418000, 30.42),
+("husky", 7, 1, 1549000, 42.49),
+("husky", 7, 1, 1622000, 49.37),
+("husky", 7, 1, 1836000, 58.09),
+("husky", 7, 1, 1999000, 67.54),
+("husky", 7, 1, 2130000, 79.04),
+("husky", 7, 1, 2245000, 92.0),
+("husky", 7, 1, 2352000, 104.0),
+("husky", 7, 1, 2450000, 116.0),
+("husky", 8, 2, 500000, 8.36),
+("husky", 8, 2, 893000, 16.33),
+("husky", 8, 2, 1164000, 19.44),
+("husky", 8, 2, 1328000, 36.71),
+("husky", 8, 2, 1557000, 41.42),
+("husky", 8, 2, 1745000, 48.24),
+("husky", 8, 2, 1852000, 54.77),
+("husky", 8, 2, 1901000, 65.32),
+("husky", 8, 2, 2049000, 69.58),
+("husky", 8, 2, 2147000, 128.49),
+("husky", 8, 2, 2294000, 142.15),
+("husky", 8, 2, 2409000, 149.74),
+("husky", 8, 2, 2556000, 164.78),
+("husky", 8, 2, 2687000, 188.68),
+("husky", 8, 2, 2802000, 193.15),
+("husky", 8, 2, 2914000, 227.98),
+("husky", 8, 2, 3015000, 254.25);
\ No newline at end of file
diff --git a/src/trace_processor/metrics/sql/experimental/frame_times.sql b/src/trace_processor/metrics/sql/experimental/frame_times.sql
index 80ad550..a3a73c5 100644
--- a/src/trace_processor/metrics/sql/experimental/frame_times.sql
+++ b/src/trace_processor/metrics/sql/experimental/frame_times.sql
@@ -52,20 +52,26 @@
 
 DROP VIEW IF EXISTS InterestingSegments;
 CREATE PERFETTO VIEW InterestingSegments AS
-SELECT  -- 1) Gestures overlapping interactions.
-  ts_ge AS ts,
-  dur_ge AS dur
-FROM InteractionEventsJoinGestureEvents
-WHERE ts_ge IS NOT NULL
-GROUP BY ts_ge
-UNION ALL
-SELECT  -- 2) Interactions without gestures.
-  ts_ir AS ts,
-  dur_ir AS dur
-FROM InteractionEventsJoinGestureEvents
-WHERE ts_ge IS NULL
-GROUP BY ts_ir
-HAVING COUNT(*) = 1;
+WITH pre_cast AS (
+  SELECT  -- 1) Gestures overlapping interactions.
+    ts_ge AS ts,
+    dur_ge AS dur
+  FROM InteractionEventsJoinGestureEvents
+  WHERE ts_ge IS NOT NULL
+  GROUP BY ts_ge
+  UNION ALL
+  SELECT  -- 2) Interactions without gestures.
+    ts_ir AS ts,
+    dur_ir AS dur
+  FROM InteractionEventsJoinGestureEvents
+  WHERE ts_ge IS NULL
+  GROUP BY ts_ir
+  HAVING COUNT(*) = 1
+)
+SELECT
+  CAST(ts AS BIGINT) AS ts,
+  CAST(dur AS BIGINT) AS dur
+FROM pre_cast;
 
 --------------------------------------------------------------------------------
 -- On ChromeOS, DRM events, if they exist, are the source of truth. Otherwise,
diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/BUILD.gn b/src/trace_processor/perfetto_sql/intrinsics/functions/BUILD.gn
index e34973c..8082a5d 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/functions/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/intrinsics/functions/BUILD.gn
@@ -18,6 +18,8 @@
 
 source_set("functions") {
   sources = [
+    "base64.cc",
+    "base64.h",
     "clock_functions.h",
     "create_function.cc",
     "create_function.h",
diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/base64.cc b/src/trace_processor/perfetto_sql/intrinsics/functions/base64.cc
new file mode 100644
index 0000000..9c16baf
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/intrinsics/functions/base64.cc
@@ -0,0 +1,83 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/trace_processor/perfetto_sql/intrinsics/functions/base64.h"
+
+#include <cstddef>
+#include <cstdlib>
+#include <cstring>
+
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/base64.h"
+#include "perfetto/trace_processor/basic_types.h"
+#include "src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h"
+#include "src/trace_processor/perfetto_sql/intrinsics/functions/sql_function.h"
+#include "src/trace_processor/sqlite/sqlite_utils.h"
+
+namespace perfetto::trace_processor {
+
+namespace {
+
+struct Base64Decode : public SqlFunction {
+  static base::Status Run(Context*,
+                          size_t argc,
+                          sqlite3_value** argv,
+                          SqlValue& out,
+                          Destructors& destructors) {
+    if (argc != 1) {
+      return base::ErrStatus("BASE64: expected one arg but got %zu", argc);
+    }
+
+    auto in = sqlite_utils::SqliteValueToSqlValue(argv[0]);
+
+    const char* src = nullptr;
+    size_t src_size = 0;
+    switch (in.type) {
+      case SqlValue::kNull:
+        return base::OkStatus();
+      case SqlValue::kLong:
+      case SqlValue::kDouble:
+        return base::ErrStatus("BASE64: argument must be string or blob");
+      case SqlValue::kString:
+        src = in.AsString();
+        src_size = strlen(src);
+        break;
+      case SqlValue::kBytes:
+        src = reinterpret_cast<const char*>(in.AsBytes());
+        src_size = in.bytes_count;
+        break;
+    }
+
+    size_t dst_size = base::Base64DecSize(src_size);
+    uint8_t* dst = reinterpret_cast<uint8_t*>(malloc(dst_size));
+    ssize_t res = base::Base64Decode(src, src_size, dst, dst_size);
+    if (res < 0) {
+      free(dst);
+      return base::ErrStatus("BASE64: Invalid input");
+    }
+    dst_size = static_cast<size_t>(res);
+    out = SqlValue::Bytes(dst, dst_size);
+    destructors.bytes_destructor = free;
+    return base::OkStatus();
+  }
+};
+
+}  // namespace
+
+base::Status RegisterBase64Functions(PerfettoSqlEngine& engine) {
+  return engine.RegisterStaticFunction<Base64Decode>("base64_decode", 1,
+                                                     nullptr, true);
+}
+
+}  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/base64.h b/src/trace_processor/perfetto_sql/intrinsics/functions/base64.h
new file mode 100644
index 0000000..a7125a5
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/intrinsics/functions/base64.h
@@ -0,0 +1,28 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_FUNCTIONS_BASE64_H_
+#define SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_FUNCTIONS_BASE64_H_
+
+#include "perfetto/base/status.h"
+
+namespace perfetto::trace_processor {
+
+class PerfettoSqlEngine;
+
+base::Status RegisterBase64Functions(PerfettoSqlEngine& engine);
+
+}  // namespace perfetto::trace_processor
+
+#endif  // SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_FUNCTIONS_BASE64_H_
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/BUILD.gn b/src/trace_processor/perfetto_sql/intrinsics/table_functions/BUILD.gn
index 631942d..6edd50b 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/BUILD.gn
@@ -43,6 +43,8 @@
     "experimental_slice_layout.h",
     "flamegraph_construction_algorithms.cc",
     "flamegraph_construction_algorithms.h",
+    "interval_intersect.cc",
+    "interval_intersect.h",
     "table_info.cc",
     "table_info.h",
   ]
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/interval_intersect.cc b/src/trace_processor/perfetto_sql/intrinsics/table_functions/interval_intersect.cc
new file mode 100644
index 0000000..b408d31
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/interval_intersect.cc
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/interval_intersect.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <optional>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "perfetto/base/compiler.h"
+#include "perfetto/base/logging.h"
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/status_or.h"
+#include "perfetto/protozero/proto_decoder.h"
+#include "perfetto/protozero/proto_utils.h"
+#include "perfetto/trace_processor/basic_types.h"
+#include "perfetto/trace_processor/status.h"
+#include "protos/perfetto/trace_processor/metrics_impl.pbzero.h"
+#include "src/trace_processor/containers/string_pool.h"
+#include "src/trace_processor/db/column.h"
+#include "src/trace_processor/db/table.h"
+#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/tables_py.h"
+#include "src/trace_processor/util/status_macros.h"
+
+namespace perfetto::trace_processor {
+namespace tables {
+IntervalIntersectTable::~IntervalIntersectTable() = default;
+}  // namespace tables
+
+namespace {
+
+using RepeatedDecoder = protos::pbzero::RepeatedBuilderResult::Decoder;
+using RepeatedIter = ::protozero::PackedRepeatedFieldIterator<
+    ::protozero::proto_utils::ProtoWireType::kFixed64,
+    int64_t>;
+
+base::StatusOr<RepeatedIter> DecodeArgument(const SqlValue& raw_arg,
+                                            const char* debug_name,
+                                            bool& parse_error) {
+  if (raw_arg.type != SqlValue::kBytes) {
+    return base::ErrStatus(
+        "interval_intersect: '%s' should be a repeated field", debug_name);
+  }
+  protos::pbzero::ProtoBuilderResult::Decoder proto_arg(
+      static_cast<const uint8_t*>(raw_arg.AsBytes()), raw_arg.bytes_count);
+  if (!proto_arg.is_repeated()) {
+    return base::ErrStatus(
+        "interval_intersect: '%s' is not generated by RepeatedField "
+        "function",
+        debug_name);
+  }
+
+  auto iter =
+      protos::pbzero::RepeatedBuilderResult::Decoder(proto_arg.repeated())
+          .int_values(&parse_error);
+  if (parse_error) {
+    return base::ErrStatus(
+        "interval_intersect: error when parsing '%s' values.", debug_name);
+  }
+
+  return iter;
+}
+
+struct Interval {
+  int64_t id;
+  int64_t ts;
+  int64_t dur;
+
+  int64_t end() { return ts + dur; }
+};
+
+struct IntervalsIterator {
+  RepeatedIter ids;
+  RepeatedIter tses;
+  RepeatedIter durs;
+
+  static base::StatusOr<IntervalsIterator> Create(
+      const SqlValue& raw_ids,
+      const SqlValue& raw_timestamps,
+      const SqlValue& raw_durs,
+      bool& parse_error) {
+    ASSIGN_OR_RETURN(RepeatedIter ids,
+                     DecodeArgument(raw_ids, "ids", parse_error));
+    ASSIGN_OR_RETURN(RepeatedIter tses,
+                     DecodeArgument(raw_timestamps, "timestamps", parse_error));
+    ASSIGN_OR_RETURN(RepeatedIter durs,
+                     DecodeArgument(raw_durs, "durations", parse_error));
+
+    return IntervalsIterator{ids, tses, durs};
+  }
+
+  void operator++() {
+    PERFETTO_DCHECK(ids && tses && durs);
+    ids++;
+    tses++;
+    durs++;
+  }
+
+  Interval operator*() const { return Interval{*ids, *tses, *durs}; }
+
+  explicit operator bool() const { return bool(ids); }
+};
+
+}  // namespace
+IntervalIntersect::IntervalIntersect(StringPool* pool) : pool_(pool) {}
+IntervalIntersect::~IntervalIntersect() = default;
+
+Table::Schema IntervalIntersect::CreateSchema() {
+  return tables::IntervalIntersectTable::ComputeStaticSchema();
+}
+
+std::string IntervalIntersect::TableName() {
+  return tables::IntervalIntersectTable::Name();
+}
+
+uint32_t IntervalIntersect::EstimateRowCount() {
+  // TODO(mayzner): Give proper estimate.
+  return 1024;
+}
+
+base::StatusOr<std::unique_ptr<Table>> IntervalIntersect::ComputeTable(
+    const std::vector<SqlValue>& args) {
+  PERFETTO_DCHECK(args.size() == 6);
+
+  // If either of the provided sets of columns is empty return.
+  auto pred = [](const SqlValue& val) { return val.is_null(); };
+  if (std::any_of(args.begin(), args.end(), pred)) {
+    // We expect that either all left table values are empty or all right table
+    // values are empty.
+    if (std::all_of(args.begin(), args.begin() + 3, pred) ||
+        std::all_of(args.begin() + 3, args.begin() + 6, pred)) {
+      return std::unique_ptr<Table>(
+          std::make_unique<tables::IntervalIntersectTable>(pool_));
+    }
+    return base::ErrStatus(
+        "interval_intersect: not all of the arguments of one of the tables are "
+        "null");
+  }
+
+  bool parse_error = false;
+  ASSIGN_OR_RETURN(
+      IntervalsIterator l_it,
+      IntervalsIterator::Create(args[0], args[1], args[2], parse_error));
+  ASSIGN_OR_RETURN(
+      IntervalsIterator r_it,
+      IntervalsIterator::Create(args[3], args[4], args[5], parse_error));
+
+  // If there are no intervals in one of the tables then there are no intervals
+  // returned.
+  if (!l_it || !r_it) {
+    return std::unique_ptr<Table>(
+        std::make_unique<tables::IntervalIntersectTable>(pool_));
+  }
+
+  // We copy |l_it| and |r_it| for the second for loop.
+  IntervalsIterator l_it_2 = l_it;
+  IntervalsIterator r_it_2 = r_it;
+
+  auto table = std::make_unique<tables::IntervalIntersectTable>(pool_);
+
+  // Find all intersections where interval from right table started duringan
+  // interval from left table.
+  for (Interval l_i = *l_it; l_it && r_it && !parse_error;
+       ++l_it, l_i = *l_it) {
+    // If the next |r_i| starts after |l_i| ends, that means that we need to
+    // go the the next |l_i|, so we need to exit the loop.
+    for (Interval r_i = *r_it; r_it && r_i.ts < l_i.end() && !parse_error;
+         ++r_it, r_i = *r_it) {
+      // We already know (because we are in the loop) that |r_i| started before
+      // |l_i| ended, we should not intersect only if |r_i| started before
+      // |l_i|.
+      if (r_i.ts < l_i.ts) {
+        continue;
+      }
+
+      tables::IntervalIntersectTable::Row row;
+      row.ts = static_cast<uint32_t>(std::max(r_i.ts, l_i.ts));
+      row.dur = static_cast<uint32_t>(std::min(r_i.end(), l_i.end())) - row.ts;
+      row.left_id = static_cast<uint32_t>(l_i.id);
+      row.right_id = static_cast<uint32_t>(r_i.id);
+      table->Insert(row);
+    }
+  }
+
+  // Find all intersections where interval from the left table started during an
+  // interval from right table.
+  for (Interval r_i = *r_it_2; r_it_2 && l_it_2 && !parse_error;
+       ++r_it_2, r_i = *r_it_2) {
+    for (Interval l_i = *l_it_2; l_it_2 && l_i.ts < r_i.end() && !parse_error;
+         ++l_it_2, l_i = *l_it_2) {
+      // The only difference between this and above algorithm is not
+      // intersecting if the intervals started at the same time. We do this to
+      // prevent double counting intervals.
+      if (l_i.ts <= r_i.ts) {
+        continue;
+      }
+
+      tables::IntervalIntersectTable::Row row;
+      row.ts = static_cast<uint32_t>(std::max(r_i.ts, l_i.ts));
+      row.dur = static_cast<uint32_t>(std::min(r_i.end(), l_i.end())) - row.ts;
+      row.left_id = static_cast<uint32_t>(l_i.id);
+      row.right_id = static_cast<uint32_t>(r_i.id);
+      table->Insert(row);
+    }
+  }
+
+  if (parse_error) {
+    return base::ErrStatus(
+        "interval_intersect: Error in parsing of one of the arguments.");
+  }
+
+  return std::unique_ptr<Table>(std::move(table));
+}
+
+}  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/interval_intersect.h b/src/trace_processor/perfetto_sql/intrinsics/table_functions/interval_intersect.h
new file mode 100644
index 0000000..ec8ccdf
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/interval_intersect.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_TABLE_FUNCTIONS_INTERVAL_INTERSECT_H_
+#define SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_TABLE_FUNCTIONS_INTERVAL_INTERSECT_H_
+
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "perfetto/ext/base/status_or.h"
+#include "perfetto/trace_processor/basic_types.h"
+#include "src/trace_processor/containers/string_pool.h"
+#include "src/trace_processor/db/table.h"
+#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/static_table_function.h"
+
+namespace perfetto::trace_processor {
+
+// An SQL table-function which computes the intersection of intervals from two
+// tables.
+//
+// Given two sets of sorted non-overlapping intervals (with id, timestamp and
+// duration) returns intervals that are intersections between those two sets,
+// with ids to state what intervals are intersected.
+//
+// LEFT         . - - - - - - .
+// RIGHT        - - . - - . - -
+// Intersection . - . - - . - .
+//
+// Arguments are RepeatedBuilderResult protos containing a column of
+// numerics values:
+// 1) |in_left_ids|(uint32_t): Ids from the left table.
+// 2) |in_left_tses|(uint64_t): Timestamps (starts) of intervals from
+// the left table.
+// 3) |in_left_durs|(uint64_t): Durations of intervals
+// from the left table.
+// 4) |in_right_ids|(uint32_t): Ids from the right table.
+// 5) |in_right_tses|(uint64_t): Timestamps (starts) of intervals
+// from the right table.
+// 6) |in_right_durs|(uint64_t): Durations of intervals from the right table.
+//
+// NOTES:
+// - The first 3 arguments have to have the same number of values.
+// - Timestamps in left and right columns have to be sorted.
+//
+// Returns:
+// 1) |ts|: Start of the intersection.
+// 2) |dur|: Duration of the intersection.
+// 3) |left_id|: Id of the slice that was intersected in the first table.
+// 4) |right_id|: Id of the slice that was intersected in the second table.
+class IntervalIntersect : public StaticTableFunction {
+ public:
+  explicit IntervalIntersect(StringPool*);
+  virtual ~IntervalIntersect() override;
+
+  // StaticTableFunction implementation.
+  Table::Schema CreateSchema() override;
+  std::string TableName() override;
+  uint32_t EstimateRowCount() override;
+  base::StatusOr<std::unique_ptr<Table>> ComputeTable(
+      const std::vector<SqlValue>& arguments) override;
+
+ private:
+  StringPool* pool_;
+};
+
+}  // namespace perfetto::trace_processor
+
+#endif  // SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_TABLE_FUNCTIONS_INTERVAL_INTERSECT_H_
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/tables.py b/src/trace_processor/perfetto_sql/intrinsics/table_functions/tables.py
index f71ab9c..3c86e50 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/tables.py
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/tables.py
@@ -170,6 +170,23 @@
           flags=ColumnFlag.HIDDEN),
     ])
 
+INTERVAL_INTERSECT_TABLE = Table(
+    python_module=__file__,
+    class_name="IntervalIntersectTable",
+    sql_name="__intrinsic_interval_intersect",
+    columns=[
+        C("ts", CppInt64()),
+        C("dur", CppInt64()),
+        C("left_id", CppUint32()),
+        C("right_id", CppUint32()),
+        C("in_left_ids", CppOptional(CppString()), flags=ColumnFlag.HIDDEN),
+        C("in_left_tses", CppOptional(CppString()), flags=ColumnFlag.HIDDEN),
+        C("in_left_durs", CppOptional(CppString()), flags=ColumnFlag.HIDDEN),
+        C("in_right_ids", CppOptional(CppString()), flags=ColumnFlag.HIDDEN),
+        C("in_right_tses", CppOptional(CppString()), flags=ColumnFlag.HIDDEN),
+        C("in_right_durs", CppOptional(CppString()), flags=ColumnFlag.HIDDEN),
+    ])
+
 # Keep this list sorted.
 ALL_TABLES = [
     ANCESTOR_SLICE_BY_STACK_TABLE,
@@ -184,5 +201,6 @@
     EXPERIMENTAL_COUNTER_DUR_TABLE,
     EXPERIMENTAL_SCHED_UPID_TABLE,
     EXPERIMENTAL_SLICE_LAYOUT_TABLE,
+    INTERVAL_INTERSECT_TABLE,
     TABLE_INFO_TABLE,
 ]
diff --git a/src/trace_processor/perfetto_sql/stdlib/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/BUILD.gn
index 4ad989b..69fa506 100644
--- a/src/trace_processor/perfetto_sql/stdlib/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/BUILD.gn
@@ -28,11 +28,13 @@
     "graphs",
     "intervals",
     "linux",
+    "memory",
     "pkvm",
     "prelude",
     "sched",
     "slices",
     "time",
+    "v8",
   ]
   generated_header = "stdlib.h"
   namespace = "stdlib"
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/monitor_contention.sql b/src/trace_processor/perfetto_sql/stdlib/android/monitor_contention.sql
index 34a9b97..005f867 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/monitor_contention.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/monitor_contention.sql
@@ -182,8 +182,10 @@
 LEFT JOIN ANCESTOR_SLICE(slice.id) binder_reply ON binder_reply.name = 'binder reply'
 LEFT JOIN thread_track binder_reply_thread_track ON binder_reply.track_id = binder_reply_thread_track.id
 LEFT JOIN thread binder_reply_thread ON binder_reply_thread_track.utid = binder_reply_thread.utid
+-- Before Android U, we didn't have blocking_thread tid (aosp/3000578). We do a LEFT JOIN instead
+-- of JOIN so that on older devices we can at least capture the list of contentions without edges.
+LEFT JOIN thread blocking_thread ON blocking_thread.tid = blocking_tid AND blocking_thread.upid = thread.upid
 JOIN _valid_android_monitor_contention ON _valid_android_monitor_contention.id = slice.id
-JOIN thread blocking_thread ON blocking_thread.tid = blocking_tid AND blocking_thread.upid = thread.upid
 WHERE slice.name GLOB 'monitor contention*'
   AND slice.dur != -1
   AND short_blocking_method IS NOT NULL
diff --git a/src/trace_processor/perfetto_sql/stdlib/common/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/common/BUILD.gn
index bb4d758..89deaf0 100644
--- a/src/trace_processor/perfetto_sql/stdlib/common/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/common/BUILD.gn
@@ -19,6 +19,7 @@
     "args.sql",
     "counters.sql",
     "cpus.sql",
+    "jit.sql",
     "metadata.sql",
     "percentiles.sql",
     "slices.sql",
diff --git a/src/trace_processor/perfetto_sql/stdlib/common/jit.sql b/src/trace_processor/perfetto_sql/stdlib/common/jit.sql
new file mode 100644
index 0000000..4335a7e
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/common/jit.sql
@@ -0,0 +1,60 @@
+--
+-- 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.
+
+
+-- Represents a jitted code snippet.
+CREATE PERFETTO VIEW _jit_code (
+  -- Unique jit code id.
+  jit_code_id UINT,
+  -- Time this code was created / allocated.
+  create_ts LONG,
+  -- Time this code was destroyed / deallocated. This is a upper bound, as we
+  -- can only detect deletions indirectly when new code is allocated overlapping
+  -- existing one.
+  estimated_delete_ts LONG,
+  -- Thread that generated the code.
+  utid UINT,
+  -- Start address for the generated code.
+  start_address LONG,
+  -- Size in bytes of the generated code.
+  size LONG,
+  -- Function name.
+  function_name STRING,
+  -- Jitted code (binary data).
+  native_code BYTES
+) AS
+SELECT
+  id AS jit_code_id,
+  create_ts,
+  estimated_delete_ts,
+  utid,
+  start_address,
+  size,
+  function_name,
+  base64_decode(native_code_base64) AS native_code
+FROM __intrinsic_jit_code;
+
+-- Represents a jitted frame.
+CREATE PERFETTO VIEW _jit_frame (
+  -- Jitted code snipped the frame is in (joins with jit_code.jit_code_id).
+  jit_code_id UINT,
+  -- Jitted frame (joins with stack_profile_frame.id).
+  frame_id UINT
+) AS
+SELECT
+  jit_code_id,
+  frame_id
+FROM
+  __intrinsic_jit_frame;
diff --git a/src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/thread_states.sql b/src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/thread_states.sql
index 7590ffc..80b3c2e 100644
--- a/src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/thread_states.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/thread_states.sql
@@ -14,6 +14,7 @@
 -- limitations under the License.
 
 INCLUDE PERFETTO MODULE deprecated.v42.common.timestamps;
+INCLUDE PERFETTO MODULE sched.time_in_state;
 INCLUDE PERFETTO MODULE sched.states;
 INCLUDE PERFETTO MODULE cpu.size;
 
@@ -55,34 +56,11 @@
   -- The total duration.
   dur INT
 ) AS
-WITH
-states_starting_inside AS (
-  SELECT id
-  FROM thread_state
-  WHERE $ts <= ts
-    AND ts <= $ts + $dur
-    AND utid = $utid
-),
-first_state_starting_before AS (
-  SELECT id
-  FROM thread_state
-  WHERE ts < $ts AND utid = $utid
-  ORDER BY ts DESC
-  LIMIT 1
-),
-relevant_states AS (
-  SELECT * FROM states_starting_inside
-  UNION ALL
-  SELECT * FROM first_state_starting_before
-)
 SELECT
   sched_state_io_to_human_readable_string(state, io_wait) as state,
-  state as raw_state,
+  state AS raw_state,
   cpu_guess_core_type(cpu) as cpu_type,
   cpu,
   blocked_function,
-  sum(spans_overlapping_dur($ts, $dur, ts, dur)) as dur
-FROM thread_state
-JOIN relevant_states USING (id)
-GROUP BY state, raw_state, cpu_type, cpu, blocked_function
-ORDER BY dur desc;
\ No newline at end of file
+  dur
+FROM sched_time_in_state_and_cpu_for_thread_in_interval($ts, $dur, $utid);
\ No newline at end of file
diff --git a/src/trace_processor/perfetto_sql/stdlib/graphs/search.sql b/src/trace_processor/perfetto_sql/stdlib/graphs/search.sql
index 6d9bf00..4bba25a 100644
--- a/src/trace_processor/perfetto_sql/stdlib/graphs/search.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/graphs/search.sql
@@ -23,7 +23,7 @@
 -- level functions/macros in the standard library can be built.
 --
 -- Example usage on traces containing heap graphs:
---
+-- ```
 -- -- Compute the reachable nodes from the first heap root.
 -- SELECT *
 -- FROM graph_reachable_dfs!(
@@ -73,7 +73,7 @@
 -- The order of the next sibling is undefined if the |sort_key| is not unique.
 --
 -- Example usage:
---
+-- ```
 -- -- Compute the next sibling:
 -- SELECT *
 -- FROM graph_next_sibling!(
diff --git a/src/trace_processor/perfetto_sql/stdlib/intervals/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/intervals/BUILD.gn
index dcac571..e9e22e9 100644
--- a/src/trace_processor/perfetto_sql/stdlib/intervals/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/intervals/BUILD.gn
@@ -15,5 +15,8 @@
 import("../../../../../gn/perfetto_sql.gni")
 
 perfetto_sql_source_set("intervals") {
-  sources = [ "overlap.sql" ]
+  sources = [
+    "intersect.sql",
+    "overlap.sql",
+  ]
 }
diff --git a/src/trace_processor/perfetto_sql/stdlib/intervals/intersect.sql b/src/trace_processor/perfetto_sql/stdlib/intervals/intersect.sql
new file mode 100644
index 0000000..2775e63
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/intervals/intersect.sql
@@ -0,0 +1,53 @@
+--
+-- 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 MACRO _interval_intersect(
+  left_table TableOrSubquery,
+  right_table TableOrSubquery
+)
+RETURNS TableOrSubquery AS
+(
+  WITH
+    __temp_left_table AS (SELECT * FROM $left_table ORDER BY ts),
+    __temp_right_table AS (SELECT * FROM $right_table ORDER BY ts)
+  SELECT ii.ts, ii.dur, ii.left_id, ii.right_id
+  FROM __intrinsic_interval_intersect(
+    (SELECT RepeatedField(id) FROM __temp_left_table),
+    (SELECT RepeatedField(ts) FROM __temp_left_table),
+    (SELECT RepeatedField(dur) FROM __temp_left_table),
+    (SELECT RepeatedField(id) FROM __temp_right_table),
+    (SELECT RepeatedField(ts) FROM __temp_right_table),
+    (SELECT RepeatedField(dur) FROM __temp_right_table)
+  ) ii
+);
+
+CREATE PERFETTO MACRO _interval_intersect_single(
+  ts Expr,
+  dur Expr,
+  intervals_table TableOrSubquery
+) RETURNS TableOrSubquery AS(
+  SELECT
+    left_id AS id,
+    ts,
+    dur
+  FROM _interval_intersect!(
+    $intervals_table,
+    (SELECT
+        0 AS id,
+        $ts AS ts,
+        $dur AS dur
+    )
+  )
+)
diff --git a/src/trace_processor/perfetto_sql/stdlib/memory/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/memory/BUILD.gn
new file mode 100644
index 0000000..ae3ad17
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/memory/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("memory") {
+  sources = [ "heap_graph_dominator_tree.sql" ]
+}
diff --git a/src/trace_processor/perfetto_sql/stdlib/memory/heap_graph_dominator_tree.sql b/src/trace_processor/perfetto_sql/stdlib/memory/heap_graph_dominator_tree.sql
new file mode 100644
index 0000000..2326aab
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/memory/heap_graph_dominator_tree.sql
@@ -0,0 +1,163 @@
+--
+-- 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 graphs.dominator_tree;
+
+-- Excluding following types from the graph as they share objects' ownership
+-- with their real (more interesting) owners and will mask their idom to be the
+-- "super root".
+CREATE PERFETTO TABLE _excluded_type_ids AS
+WITH RECURSIVE class_visitor(type_id) AS (
+  SELECT id AS type_id
+  FROM heap_graph_class
+  WHERE name IN (
+    'java.lang.ref.PhantomReference',
+    'java.lang.ref.FinalizerReference'
+  )
+  UNION ALL
+  SELECT child.id AS type_id
+  FROM heap_graph_class child
+  JOIN class_visitor parent ON parent.type_id = child.superclass_id
+)
+SELECT * FROM class_visitor;
+
+-- The assigned id of the "super root".
+-- Since a Java heap graph is a "forest" structure, we need to add a imaginary
+-- "super root" node which connects all the roots of the forest into a single
+-- connected component, so that the dominator tree algorithm can be performed.
+CREATE PERFETTO FUNCTION memory_heap_graph_super_root_fn()
+-- The assigned id of the "super root".
+RETURNS INT AS
+SELECT max(id) + 1 FROM heap_graph_object;
+
+CREATE PERFETTO VIEW _dominator_compatible_heap_graph AS
+SELECT
+  ref.owner_id AS source_node_id,
+  ref.owned_id AS dest_node_id
+FROM heap_graph_reference ref
+JOIN heap_graph_object source_node ON ref.owner_id = source_node.id
+WHERE source_node.reachable AND source_node.type_id NOT IN _excluded_type_ids
+  AND ref.owned_id IS NOT NULL
+UNION ALL
+SELECT
+  (SELECT memory_heap_graph_super_root_fn()) as source_node_id,
+  id AS dest_node_id
+FROM heap_graph_object
+WHERE root_type IS NOT NULL;
+
+CREATE PERFETTO TABLE _heap_graph_dominator_tree AS
+SELECT
+  node_id AS id,
+  dominator_node_id AS idom_id
+FROM graph_dominator_tree!(
+  _dominator_compatible_heap_graph,
+  (SELECT memory_heap_graph_super_root_fn())
+)
+-- Excluding the imaginary root.
+WHERE dominator_node_id IS NOT NULL
+-- Ordering by idom_id so queries below are faster when joining on idom_id.
+-- TODO(lalitm): support create index for Perfetto tables.
+ORDER BY idom_id;
+
+CREATE PERFETTO TABLE _heap_graph_dominator_tree_depth AS
+WITH RECURSIVE _tree_visitor(id, depth) AS (
+  -- Let the super root have depth 0.
+  SELECT id, 1 AS depth
+  FROM _heap_graph_dominator_tree
+  WHERE idom_id IN (SELECT memory_heap_graph_super_root_fn())
+  UNION ALL
+  SELECT child.id, parent.depth + 1
+  FROM _heap_graph_dominator_tree child
+  JOIN _tree_visitor parent ON child.idom_id = parent.id
+)
+SELECT * FROM _tree_visitor
+ORDER BY id;
+
+-- A performance note: we need 3 memoize functions because EXPERIMENTAL_MEMOIZE
+-- limits the function to return only 1 int.
+-- This means the exact same "memoized dfs pass" on the tree is done 3 times, so
+-- it takes 3x the time taken by only doing 1 pass. Doing only 1 pass would be
+-- possible if EXPERIMENTAL_MEMOIZE could return more than 1 int.
+
+CREATE PERFETTO FUNCTION _subtree_obj_count(id INT)
+RETURNS INT AS
+SELECT 1 + IFNULL((
+  SELECT
+    SUM(_subtree_obj_count(child.id))
+  FROM _heap_graph_dominator_tree child
+  WHERE child.idom_id = $id
+), 0);
+SELECT EXPERIMENTAL_MEMOIZE('_subtree_obj_count');
+
+CREATE PERFETTO FUNCTION _subtree_size_bytes(id INT)
+RETURNS INT AS
+SELECT (
+  SELECT self_size
+  FROM heap_graph_object
+  WHERE heap_graph_object.id = $id
+) +
+IFNULL((
+  SELECT
+    SUM(_subtree_size_bytes(child.id))
+  FROM _heap_graph_dominator_tree child
+  WHERE child.idom_id = $id
+), 0);
+SELECT EXPERIMENTAL_MEMOIZE('_subtree_size_bytes');
+
+CREATE PERFETTO FUNCTION _subtree_native_size_bytes(id INT)
+RETURNS INT AS
+SELECT (
+  SELECT native_size
+  FROM heap_graph_object
+  WHERE heap_graph_object.id = $id
+) +
+IFNULL((
+  SELECT
+    SUM(_subtree_native_size_bytes(child.id))
+  FROM _heap_graph_dominator_tree child
+  WHERE child.idom_id = $id
+), 0);
+SELECT EXPERIMENTAL_MEMOIZE('_subtree_native_size_bytes');
+
+-- All reachable heap graph objects, their immediate dominators and summary of
+-- their dominated sets.
+-- The heap graph dominator tree is calculated by stdlib graphs.dominator_tree.
+-- Each reachable object is a node in the dominator tree, their immediate
+-- dominator is their parent node in the tree, and their dominated set is all
+-- their descendants in the tree. All size information come from the
+-- heap_graph_object prelude table.
+CREATE PERFETTO TABLE memory_heap_graph_dominator_tree (
+  -- Heap graph object id.
+  id INT,
+  -- Immediate dominator object id of the object.
+  idom_id INT,
+  -- Count of all objects dominated by this object, self inclusive.
+  dominated_obj_count INT,
+  -- Total self_size of all objects dominated by this object, self inclusive.
+  dominated_size_bytes INT,
+  -- Total native_size of all objects dominated by this object, self inclusive.
+  dominated_native_size_bytes INT,
+  -- Depth of the object in the dominator tree. Depth of root objects are 1.
+  depth INT
+) AS
+SELECT
+  t.id,
+  t.idom_id,
+  _subtree_obj_count(t.id) AS dominated_obj_count,
+  _subtree_size_bytes(t.id) AS dominated_size_bytes,
+  _subtree_native_size_bytes(t.id) AS dominated_native_size_bytes,
+  d.depth
+FROM _heap_graph_dominator_tree t
+JOIN _heap_graph_dominator_tree_depth d USING(id);
diff --git a/src/trace_processor/perfetto_sql/stdlib/prelude/slices.sql b/src/trace_processor/perfetto_sql/stdlib/prelude/slices.sql
index 24f470a..6c58c60 100644
--- a/src/trace_processor/perfetto_sql/stdlib/prelude/slices.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/prelude/slices.sql
@@ -29,39 +29,4 @@
 FROM slice ancestor
 JOIN slice descendant
 WHERE ancestor.id = $ancestor_id
-  AND descendant.id = $descendant_id;
-
-CREATE PERFETTO MACRO _interval_intersect(
-  left_table TableOrSubquery,
-  right_table TableOrSubquery
-)
-RETURNS TableOrSubquery AS
-(
-  WITH on_left AS (
-    SELECT
-      B.ts,
-      IIF(
-        A.ts + A.dur <= B.ts + B.dur,
-        A.ts + A.dur - B.ts, B.dur) AS dur,
-      A.id AS left_id,
-      B.id as right_id
-    FROM $left_table A
-    JOIN $right_table B ON (A.ts <= B.ts AND A.ts + A.dur > B.ts)
-  ), on_right AS (
-    SELECT
-      B.ts,
-      IIF(
-        A.ts + A.dur <= B.ts + B.dur,
-        A.ts + A.dur - B.ts, B.dur) AS dur,
-      B.id as left_id,
-      A.id AS right_id
-    FROM $right_table A
-    -- The difference between this table and on_left is the lack of equality on
-    -- A.ts <= B.ts. This is to remove the issue of double accounting
-    -- timestamps that start at the same time.
-    JOIN $left_table B ON (A.ts < B.ts AND A.ts + A.dur > B.ts)
-  )
-  SELECT * FROM on_left
-  UNION ALL
-  SELECT * FROM on_right
-);
\ No newline at end of file
+  AND descendant.id = $descendant_id;
\ No newline at end of file
diff --git a/src/trace_processor/perfetto_sql/stdlib/sched/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/sched/BUILD.gn
index 642b822..bd2dc94 100644
--- a/src/trace_processor/perfetto_sql/stdlib/sched/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/sched/BUILD.gn
@@ -21,5 +21,6 @@
     "thread_executing_span.sql",
     "thread_level_parallelism.sql",
     "thread_state_flattened.sql",
+    "time_in_state.sql",
   ]
 }
diff --git a/src/trace_processor/perfetto_sql/stdlib/sched/states.sql b/src/trace_processor/perfetto_sql/stdlib/sched/states.sql
index 0a7c7ee..37f759a 100644
--- a/src/trace_processor/perfetto_sql/stdlib/sched/states.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/sched/states.sql
@@ -69,81 +69,4 @@
     WHEN 0 THEN ' (non-IO)'
     ELSE ''
   END
-);
-
--- The time a thread spent in each scheduling state during it's lifetime.
-CREATE PERFETTO TABLE sched_thread_time_in_state(
-  -- Utid of the thread.
-  utid INT,
-  -- Total runtime of thread.
-  total_runtime INT,
-  -- One of the scheduling states of kernel thread.
-  state STRING,
-  -- Total time spent in the scheduling state.
-  time_in_state INT,
-  -- Percentage of time thread spent in scheduling state in [0-100] range.
-  percentage_in_state INT
-) AS
-WITH total_dur AS (
-  SELECT
-    utid,
-    sum(dur) AS sum_dur
-  FROM thread_state
-  GROUP BY 1
-),
-summed AS (
-  SELECT
-    utid,
-    state,
-    sum(dur) AS time_in_state
-  FROM thread_state group by 1, 2
-)
-SELECT
-  utid,
-  sum_dur AS total_runtime,
-  state,
-  time_in_state,
-  (time_in_state*100)/(sum_dur) AS percentage_in_state
-FROM summed JOIN total_dur USING (utid);
-
-CREATE PERFETTO MACRO _case_for_state(state Expr)
-RETURNS Expr AS
-MAX(CASE WHEN state = $state THEN percentage_in_state END);
-
--- Summary of time spent by thread in each scheduling state, in percentage ([0, 100]
--- ranges). Sum of all states might be smaller than 100, as those values
--- are rounded down.
-CREATE PERFETTO TABLE sched_percentage_of_time_in_state(
-  -- Utid of the thread.
-  utid INT,
-  -- Percentage of time thread spent in running ('Running') state in [0, 100]
-  -- range.
-  running INT,
-  -- Percentage of time thread spent in runnable ('R') state in [0, 100]
-  -- range.
-  runnable INT,
-  -- Percentage of time thread spent in preempted runnable ('R+') state in
-  -- [0, 100] range.
-  runnable_preempted INT,
-  -- Percentage of time thread spent in sleeping ('S') state in [0, 100] range.
-  sleeping INT,
-  -- Percentage of time thread spent in uninterruptible sleep ('D') state in
-  -- [0, 100] range.
-  uninterruptible_sleep INT,
-  -- Percentage of time thread spent in other ('T', 't', 'X', 'Z', 'x', 'I',
-  -- 'K', 'W', 'P', 'N') states in [0, 100] range.
-  other INT
-) AS
-SELECT
-  utid,
-  _case_for_state!('Running') AS running,
-  _case_for_state!('R') AS runnable,
-  _case_for_state!('R+') AS runnable_preempted,
-  _case_for_state!('S') AS sleeping,
-  _case_for_state!('D') AS uninterruptible_sleep,
-  SUM(
-    CASE WHEN state IN ('T', 't', 'X', 'Z', 'x', 'I', 'K', 'W', 'P', 'N')
-    THEN time_in_state END
-  ) * 100/total_runtime AS other
-FROM sched_thread_time_in_state
-GROUP BY utid;
\ No newline at end of file
+);
\ No newline at end of file
diff --git a/src/trace_processor/perfetto_sql/stdlib/sched/time_in_state.sql b/src/trace_processor/perfetto_sql/stdlib/sched/time_in_state.sql
new file mode 100644
index 0000000..8c6bc87
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/sched/time_in_state.sql
@@ -0,0 +1,168 @@
+--
+-- Copyright 2024 The Android Open Source Project
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--     https://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+INCLUDE PERFETTO MODULE intervals.intersect;
+
+-- The time a thread spent in each scheduling state during it's lifetime.
+CREATE PERFETTO TABLE sched_time_in_state_for_thread(
+  -- Utid of the thread.
+  utid INT,
+  -- Total runtime of thread.
+  total_runtime INT,
+  -- One of the scheduling states of kernel thread.
+  state STRING,
+  -- Total time spent in the scheduling state.
+  time_in_state INT,
+  -- Percentage of time thread spent in scheduling state in [0-100] range.
+  percentage_in_state INT
+) AS
+WITH total_dur AS (
+  SELECT
+    utid,
+    sum(dur) AS sum_dur
+  FROM thread_state
+  GROUP BY 1
+),
+summed AS (
+  SELECT
+    utid,
+    state,
+    sum(dur) AS time_in_state
+  FROM thread_state group by 1, 2
+)
+SELECT
+  utid,
+  sum_dur AS total_runtime,
+  state,
+  time_in_state,
+  (time_in_state*100)/(sum_dur) AS percentage_in_state
+FROM summed JOIN total_dur USING (utid);
+
+CREATE PERFETTO MACRO _case_for_state(state Expr)
+RETURNS Expr AS
+MAX(CASE WHEN state = $state THEN percentage_in_state END);
+
+-- Summary of time spent by thread in each scheduling state, in percentage ([0, 100]
+-- ranges). Sum of all states might be smaller than 100, as those values
+-- are rounded down.
+CREATE PERFETTO TABLE sched_percentage_of_time_in_state(
+  -- Utid of the thread.
+  utid INT,
+  -- Percentage of time thread spent in running ('Running') state in [0, 100]
+  -- range.
+  running INT,
+  -- Percentage of time thread spent in runnable ('R') state in [0, 100]
+  -- range.
+  runnable INT,
+  -- Percentage of time thread spent in preempted runnable ('R+') state in
+  -- [0, 100] range.
+  runnable_preempted INT,
+  -- Percentage of time thread spent in sleeping ('S') state in [0, 100] range.
+  sleeping INT,
+  -- Percentage of time thread spent in uninterruptible sleep ('D') state in
+  -- [0, 100] range.
+  uninterruptible_sleep INT,
+  -- Percentage of time thread spent in other ('T', 't', 'X', 'Z', 'x', 'I',
+  -- 'K', 'W', 'P', 'N') states in [0, 100] range.
+  other INT
+) AS
+SELECT
+  utid,
+  _case_for_state!('Running') AS running,
+  _case_for_state!('R') AS runnable,
+  _case_for_state!('R+') AS runnable_preempted,
+  _case_for_state!('S') AS sleeping,
+  _case_for_state!('D') AS uninterruptible_sleep,
+  SUM(
+    CASE WHEN state IN ('T', 't', 'X', 'Z', 'x', 'I', 'K', 'W', 'P', 'N')
+    THEN time_in_state END
+  ) * 100/total_runtime AS other
+FROM sched_time_in_state_for_thread
+GROUP BY utid;
+
+-- Time the thread spent each state in a given interval.
+CREATE PERFETTO FUNCTION sched_time_in_state_for_thread_in_interval(
+  -- The start of the interval.
+  ts INT,
+  -- The duration of the interval.
+  dur INT,
+  -- The utid of the thread.
+  utid INT)
+RETURNS TABLE(
+  -- Thread state (from the `thread_state` table).
+  -- Use `sched_state_to_human_readable_string` function to get full name.
+  state INT,
+  -- A (posssibly NULL) boolean indicating, if the device was in uninterruptible
+  -- sleep, if it was an IO sleep.
+  io_wait BOOL,
+  -- Some states can specify the blocked function. Usually NULL.
+  blocked_function INT,
+  -- Total time spent with this state, cpu and blocked function.
+  dur INT) AS
+SELECT
+  state,
+  io_wait,
+  blocked_function,
+  sum(ii.dur) as dur
+FROM thread_state
+JOIN
+  (SELECT * FROM _interval_intersect_single!(
+    $ts, $dur,
+    (SELECT id, ts, dur
+    FROM thread_state
+    WHERE utid = $utid))) ii USING (id)
+GROUP BY 1, 2, 3
+ORDER BY 4 DESC;
+
+-- Time the thread spent each state and cpu in a given interval.
+CREATE PERFETTO FUNCTION sched_time_in_state_and_cpu_for_thread_in_interval(
+  -- The start of the interval.
+  ts INT,
+  -- The duration of the interval.
+  dur INT,
+  -- The utid of the thread.
+  utid INT)
+RETURNS TABLE(
+  -- Thread state (from the `thread_state` table).
+  -- Use `sched_state_to_human_readable_string` function to get full name.
+  state INT,
+  -- A (posssibly NULL) boolean indicating, if the device was in uninterruptible
+  -- sleep, if it was an IO sleep.
+  io_wait BOOL,
+  -- Id of the CPU.
+  -- Use `cpu_guess_core_type` to get the CPU size (little/mid/big).
+  cpu INT,
+  -- Some states can specify the blocked function. Usually NULL.
+  blocked_function INT,
+  -- Total time spent with this state, cpu and blocked function.
+  dur INT) AS
+SELECT
+  state,
+  io_wait,
+  cpu,
+  blocked_function,
+  sum(ii.dur) as dur
+FROM thread_state
+JOIN
+  (SELECT * FROM _interval_intersect_single!(
+    $ts, $dur,
+    (SELECT id, ts, dur
+    FROM thread_state
+    WHERE utid = $utid))) ii USING (id)
+GROUP BY 1, 2, 3, 4
+ORDER BY 5 DESC;
+
+
+
diff --git a/src/trace_processor/perfetto_sql/stdlib/v8/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/v8/BUILD.gn
new file mode 100644
index 0000000..8bb2833
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/v8/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("v8") {
+  sources = [ "jit.sql" ]
+}
diff --git a/src/trace_processor/perfetto_sql/stdlib/v8/jit.sql b/src/trace_processor/perfetto_sql/stdlib/v8/jit.sql
new file mode 100644
index 0000000..6765272
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/v8/jit.sql
@@ -0,0 +1,151 @@
+--
+-- 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.
+
+-- These are the tables for the V8 jit data source
+-- (protos/perfetto/trace/chrome/v8.proto).
+--
+-- All events are associated to a V8 isolate instance. There can be multiple
+-- instances associated to a given thread, although this is rare.
+--
+-- Generated code in V8 is allocated in the V8 heap (in a special executeable
+-- section), this means that code can be garbage collected (when no longer used)
+-- or can be moved around (e.g. during heap compactation). This means that a
+-- given callsite might correspond to function `A` at one point in time and to
+-- function `B` later on.
+-- In addition V8 code has various levels of optimization, so a function might
+-- have multiple associated code snippets.
+--
+-- V8 does not track code deletion, so we have to indirectly infer it by
+-- detecting code overlaps, if a newer code creation event overlaps with older
+-- code we need to asume that the old code was deleted. Code moves are logged,
+-- and there is an event to track those.
+
+-- A V8 Isolate instance. A V8 Isolate represents an isolated instance of the V8
+-- engine.
+CREATE PERFETTO VIEW v8_isolate(
+  -- Unique V8 isolate id.
+  v8_isolate_id UINT,
+  -- Process the isolate was created in.
+  upid UINT,
+  -- Internal id used by the v8 engine. Unique in a process.
+  internal_isolate_id UINT,
+  -- Absolute start address of the embedded code blob.
+  embedded_blob_code_start_address LONG,
+  -- Size in bytes of the embedded code blob.
+  embedded_blob_code_size LONG,
+  -- Base address of the code range if the isolate defines one.
+  code_range_base_address LONG,
+  -- Size of a code range if the isolate defines one.
+  code_range_size LONG,
+  -- Whether the code range for this Isolate is shared with others in the same
+  -- process. There is at max one such shared code range per process.
+  shared_code_range LONG,
+  -- Used when short builtin calls are enabled, where embedded builtins are
+  -- copied into the CodeRange so calls can be nearer.
+  embedded_blob_code_copy_start_address LONG
+) AS
+SELECT
+  id AS v8_isolate_id,
+  upid,
+  internal_isolate_id,
+  embedded_blob_code_start_address,
+  embedded_blob_code_size,
+  code_range_base_address,
+  code_range_size,
+  shared_code_range,
+  embedded_blob_code_copy_start_address
+FROM
+  __intrinsic_v8_isolate;
+
+
+-- Represents a script that was compiled to generate code. Some V8 code is
+-- generated out of scripts and will reference a V8Script other types of code
+-- will not (e.g. builtins).
+CREATE PERFETTO VIEW v8_js_script (
+  -- Unique V8 JS script id.
+  v8_js_script_id UINT,
+  -- V8 isolate this script belongs to (joinable with v8_isolate.v8_isolate_id).
+  v8_isolate_id UINT,
+  -- Script id used by the V8 engine.
+  internal_script_id UINT,
+  -- Script type.
+  script_type STRING,
+  -- Script name.
+  name STRING,
+  -- Actual contents of the script.
+  source STRING
+) AS
+SELECT
+  id AS v8_js_script_id,
+  v8_isolate_id,
+  internal_script_id,
+  script_type,
+  name,
+  source
+FROM
+  __intrinsic_v8_js_script;
+
+
+-- Represents one WASM script.
+CREATE PERFETTO VIEW v8_wasm_script (
+  -- Unique V8 WASM script id.
+  v8_wasm_script_id UINT,
+  -- V8 Isolate this script belongs to (joinable with v8_isolate.v8_isolate_id).
+  v8_isolate_id UINT,
+  -- Script id used by the V8 engine.
+  internal_script_id UINT,
+  -- URL of the source.
+  url STRING,
+  -- Actual contents of the script.
+  source STRING
+) AS
+SELECT
+  id AS v8_wasm_script_id,
+  v8_isolate_id,
+  internal_script_id,
+  url,
+  source
+FROM
+  __intrinsic_v8_wasm_script;
+
+
+-- Represents a v8 Javascript function.
+CREATE PERFETTO VIEW v8_js_function (
+  -- Unique V8 JS function id.
+  v8_js_function_id UINT,
+  -- Function name.
+  name STRING,
+  -- Script where the function is defined (joinable with
+  -- v8_js_script.v8_js_script_id).
+  v8_js_script_id UINT,
+  -- Whether this function represents the top level script.
+  is_toplevel BOOL,
+  -- Function kind (e.g. regular function or constructor).
+  kind STRING,
+  -- Line in script where function is defined. Starts at 1.
+  line UINT,
+  -- Column in script where function is defined. Starts at 1.
+  col UINT
+) AS
+SELECT
+  id AS v8_js_function_id,
+  name,
+  v8_js_script_id,
+  is_toplevel,
+  kind,
+  line,
+  col
+FROM
+  __intrinsic_v8_js_function;
diff --git a/src/trace_processor/storage/stats.h b/src/trace_processor/storage/stats.h
index 7fb6ce0..ae6f683 100644
--- a/src/trace_processor/storage/stats.h
+++ b/src/trace_processor/storage/stats.h
@@ -72,6 +72,8 @@
   F(ftrace_setup_errors,                  kSingle,  kInfo,     kTrace,         \
        "One or more atrace/ftrace categories were not found or failed to "     \
        "enable. See ftrace_setup_errors in the metadata table for details."),  \
+  F(ftrace_abi_errors_skipped_zero_data_length,                                \
+                                          kSingle,  kInfo,     kAnalysis, ""), \
   F(fuchsia_non_numeric_counters,         kSingle,  kError,    kAnalysis, ""), \
   F(fuchsia_timestamp_overflow,           kSingle,  kError,    kAnalysis, ""), \
   F(fuchsia_invalid_event,                kSingle,  kError,    kAnalysis, ""), \
@@ -310,6 +312,9 @@
   F(winscope_protolog_missing_interned_stacktrace_parse_errors,                \
                                           kSingle,  kInfo,     kAnalysis,      \
       "Failed to find interned ProtoLog stacktrace."),                         \
+  F(jit_unknown_frame,                    kSingle,  kDataLoss, kTrace,         \
+      "Indicates that we were unable to determine the function for a frame in "\
+      "a jitted memory region"),                                               \
   F(ftrace_missing_event_id,              kSingle,  kInfo,    kAnalysis,       \
       "Indicates that the ftrace event was dropped because the event id was "  \
       "missing. This is an 'info' stat rather than an error stat because "     \
diff --git a/src/trace_processor/storage/trace_storage.h b/src/trace_processor/storage/trace_storage.h
index 3c22af9..ec014d7 100644
--- a/src/trace_processor/storage/trace_storage.h
+++ b/src/trace_processor/storage/trace_storage.h
@@ -43,6 +43,7 @@
 #include "src/trace_processor/tables/android_tables_py.h"
 #include "src/trace_processor/tables/counter_tables_py.h"
 #include "src/trace_processor/tables/flow_tables_py.h"
+#include "src/trace_processor/tables/jit_tables_py.h"
 #include "src/trace_processor/tables/memory_tables_py.h"
 #include "src/trace_processor/tables/metadata_tables_py.h"
 #include "src/trace_processor/tables/profiler_tables_py.h"
@@ -381,6 +382,13 @@
     return &energy_counter_track_table_;
   }
 
+  const tables::LinuxDeviceTrackTable& linux_device_track_table() const {
+    return linux_device_track_table_;
+  }
+  tables::LinuxDeviceTrackTable* mutable_linux_device_track_table() {
+    return &linux_device_track_table_;
+  }
+
   const tables::UidCounterTrackTable& uid_counter_track_table() const {
     return uid_counter_track_table_;
   }
@@ -754,6 +762,14 @@
     return &v8_js_function_table_;
   }
 
+  const tables::JitCodeTable& jit_code_table() const { return jit_code_table_; }
+  tables::JitCodeTable* mutable_jit_code_table() { return &jit_code_table_; }
+
+  const tables::JitFrameTable& jit_frame_table() const {
+    return jit_frame_table_;
+  }
+  tables::JitFrameTable* mutable_jit_frame_table() { return &jit_frame_table_; }
+
   const tables::SurfaceFlingerLayersSnapshotTable&
   surfaceflinger_layers_snapshot_table() const {
     return surfaceflinger_layers_snapshot_table_;
@@ -943,6 +959,8 @@
       &string_pool_, &uid_track_table_};
   tables::ProcessTrackTable process_track_table_{&string_pool_, &track_table_};
   tables::ThreadTrackTable thread_track_table_{&string_pool_, &track_table_};
+  tables::LinuxDeviceTrackTable linux_device_track_table_{&string_pool_,
+                                                          &track_table_};
 
   // Track tables for counter events.
   tables::CounterTrackTable counter_track_table_{&string_pool_, &track_table_};
@@ -1058,6 +1076,10 @@
   tables::V8WasmScriptTable v8_wasm_script_table_{&string_pool_};
   tables::V8JsFunctionTable v8_js_function_table_{&string_pool_};
 
+  // Jit tables
+  tables::JitCodeTable jit_code_table_{&string_pool_};
+  tables::JitFrameTable jit_frame_table_{&string_pool_};
+
   // Winscope tables
   tables::SurfaceFlingerLayersSnapshotTable
       surfaceflinger_layers_snapshot_table_{&string_pool_};
@@ -1111,6 +1133,9 @@
 template <>
 struct std::hash<::perfetto::trace_processor::tables::HeapGraphObjectTable::Id>
     : std::hash<::perfetto::trace_processor::BaseId> {};
+template <>
+struct std::hash<::perfetto::trace_processor::tables::JitCodeTable::Id>
+    : std::hash<::perfetto::trace_processor::BaseId> {};
 
 template <>
 struct std::hash<
diff --git a/src/trace_processor/tables/BUILD.gn b/src/trace_processor/tables/BUILD.gn
index b2fcffb..8339d95 100644
--- a/src/trace_processor/tables/BUILD.gn
+++ b/src/trace_processor/tables/BUILD.gn
@@ -20,6 +20,7 @@
     "android_tables.py",
     "counter_tables.py",
     "flow_tables.py",
+    "jit_tables.py",
     "memory_tables.py",
     "metadata_tables.py",
     "profiler_tables.py",
diff --git a/src/trace_processor/tables/jit_tables.py b/src/trace_processor/tables/jit_tables.py
new file mode 100644
index 0000000..aff2c61
--- /dev/null
+++ b/src/trace_processor/tables/jit_tables.py
@@ -0,0 +1,88 @@
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains tables related to Jitted code.
+
+These tables are WIP, the schema is not stable and you should not rely on them
+for any serious business just yet""
+"""
+
+from python.generators.trace_processor_table.public import Alias
+from python.generators.trace_processor_table.public import Column as C
+from python.generators.trace_processor_table.public import CppInt64
+from python.generators.trace_processor_table.public import CppOptional
+from python.generators.trace_processor_table.public import CppString
+from python.generators.trace_processor_table.public import CppUint32
+from python.generators.trace_processor_table.public import CppTableId
+from python.generators.trace_processor_table.public import Table
+from python.generators.trace_processor_table.public import TableDoc
+from .profiler_tables import STACK_PROFILE_FRAME_TABLE
+
+JIT_CODE_TABLE = Table(
+    python_module=__file__,
+    class_name='JitCodeTable',
+    sql_name='__intrinsic_jit_code',
+    columns=[
+        C('create_ts', CppInt64()),
+        C('estimated_delete_ts', CppOptional(CppInt64())),
+        C('utid', CppUint32()),
+        C('start_address', CppInt64()),
+        C('size', CppInt64()),
+        C('function_name', CppString()),
+        C('native_code_base64', CppOptional(CppString())),
+        C('jit_code_id', Alias('id')),
+    ],
+    tabledoc=TableDoc(
+        doc="""
+          Represents a jitted code snippet
+        """,
+        group='jit',
+        columns={
+            'create_ts': """Time this code was created / allocated""",
+            'estimated_delete_ts':
+                ("""Time this code was destroyed / deallocated. This is an upper
+                bound, as we can only detect deletions indirectly when new code
+                is allocated overlapping existing one.
+                """),
+            'utid': 'Thread that generated the code',
+            'start_address': 'Start address for the generated code',
+            'size': 'Size in bytes of the generated code',
+            'function_name': 'Function name',
+            'native_code_base64': 'Jitted code base64 encoded',
+            'jit_code_id': 'Alias for id. Makes joins easier',
+        },
+    ),
+)
+
+JIT_FRAME_TABLE = Table(
+    python_module=__file__,
+    class_name='JitFrameTable',
+    sql_name='__intrinsic_jit_frame',
+    columns=[
+        C('jit_code_id', CppTableId(JIT_CODE_TABLE)),
+        C('frame_id', CppTableId(STACK_PROFILE_FRAME_TABLE)),
+    ],
+    tabledoc=TableDoc(
+        doc="""
+          Represents a jitted frame
+        """,
+        group='jit',
+        columns={
+            'jit_code_id': 'Jitted code snipped the frame is in',
+            'frame_id': 'Jitted frame',
+        },
+    ),
+)
+
+# Keep this list sorted.
+ALL_TABLES = [JIT_CODE_TABLE, JIT_FRAME_TABLE]
diff --git a/src/trace_processor/tables/table_destructors.cc b/src/trace_processor/tables/table_destructors.cc
index c34e4d3..16c2665 100644
--- a/src/trace_processor/tables/table_destructors.cc
+++ b/src/trace_processor/tables/table_destructors.cc
@@ -17,6 +17,7 @@
 #include "src/trace_processor/tables/android_tables_py.h"
 #include "src/trace_processor/tables/counter_tables_py.h"
 #include "src/trace_processor/tables/flow_tables_py.h"
+#include "src/trace_processor/tables/jit_tables_py.h"
 #include "src/trace_processor/tables/memory_tables_py.h"
 #include "src/trace_processor/tables/metadata_tables_py.h"
 #include "src/trace_processor/tables/profiler_tables_py.h"
@@ -43,6 +44,10 @@
 // counter_tables_py.h
 CounterTable::~CounterTable() = default;
 
+// jit_tables.py
+JitCodeTable::~JitCodeTable() = default;
+JitFrameTable::~JitFrameTable() = default;
+
 // metadata_tables_py.h
 RawTable::~RawTable() = default;
 FtraceEventTable::~FtraceEventTable() = default;
@@ -107,6 +112,7 @@
 EnergyCounterTrackTable::~EnergyCounterTrackTable() = default;
 UidCounterTrackTable::~UidCounterTrackTable() = default;
 EnergyPerUidCounterTrackTable::~EnergyPerUidCounterTrackTable() = default;
+LinuxDeviceTrackTable::~LinuxDeviceTrackTable() = default;
 
 // trace_proto_tables_py.h
 ExperimentalProtoPathTable::~ExperimentalProtoPathTable() = default;
diff --git a/src/trace_processor/tables/track_tables.py b/src/trace_processor/tables/track_tables.py
index 7a3bd9e..cfdc59d 100644
--- a/src/trace_processor/tables/track_tables.py
+++ b/src/trace_processor/tables/track_tables.py
@@ -329,6 +329,24 @@
             'ordinal': 'ordinal of energy consumer'
         }))
 
+LINUX_DEVICE_TRACK_TABLE = Table(
+    python_module=__file__,
+    class_name='LinuxDeviceTrackTable',
+    sql_name='linux_device_track',
+    columns=[],
+    parent=TRACK_TABLE,
+    tabledoc=TableDoc(
+        doc='''
+          Slice data corresponding to runtime power state transitions
+          associated with Linux devices (where a Linux device is anything
+          managed by a Linux driver). The name of each track corresponds to the
+          device name as recognized by the linux kernel running on the system.
+        ''',
+        group='Tracks',
+        # No additional columns are needed because the track name implicitly
+        # serves as the device name, providing all required information.
+        columns={}))
+
 UID_COUNTER_TRACK_TABLE = Table(
     python_module=__file__,
     class_name='UidCounterTrackTable',
@@ -366,6 +384,7 @@
     GPU_TRACK_TABLE,
     GPU_WORK_PERIOD_TRACK_TABLE,
     IRQ_COUNTER_TRACK_TABLE,
+    LINUX_DEVICE_TRACK_TABLE,
     PERF_COUNTER_TRACK_TABLE,
     PROCESS_COUNTER_TRACK_TABLE,
     PROCESS_TRACK_TABLE,
diff --git a/src/trace_processor/tables/v8_tables.py b/src/trace_processor/tables/v8_tables.py
index 8fb7c01..76a8cda 100644
--- a/src/trace_processor/tables/v8_tables.py
+++ b/src/trace_processor/tables/v8_tables.py
@@ -35,7 +35,7 @@
 V8_ISOLATE = Table(
     python_module=__file__,
     class_name='V8IsolateTable',
-    sql_name='v8_isolate',
+    sql_name='__intrinsic_v8_isolate',
     columns=[
         C('upid', CppUint32()),
         C('internal_isolate_id', CppInt32()),
@@ -45,7 +45,6 @@
         C('code_range_size', CppOptional(CppInt64())),
         C('shared_code_range', CppOptional(CppBool())),
         C('embedded_blob_code_copy_start_address', CppOptional(CppInt64())),
-        C('v8_isolate_id', Alias('id')),
     ],
     tabledoc=TableDoc(
         doc='Represents one Isolate instance',
@@ -72,8 +71,6 @@
                 'Used when short builtin calls are enabled, where embedded'
                 ' builtins are copied into the CodeRange so calls can be'
                 ' nearer.',
-            'v8_isolate_id':
-                'Alias for id. Makes joins easier',
         },
     ),
 )
@@ -81,14 +78,13 @@
 V8_JS_SCRIPT = Table(
     python_module=__file__,
     class_name='V8JsScriptTable',
-    sql_name='v8_js_script',
+    sql_name='__intrinsic_v8_js_script',
     columns=[
         C('v8_isolate_id', CppTableId(V8_ISOLATE)),
         C('internal_script_id', CppInt32()),
         C('script_type', CppString()),
         C('name', CppString()),
         C('source', CppOptional(CppString())),
-        C('v8_js_script_id', Alias('id')),
     ],
     tabledoc=TableDoc(
         doc='Represents one Javascript script',
@@ -99,7 +95,6 @@
             'script_type': '',
             'name': '',
             'source': 'Actual contents of the script.',
-            'v8_js_script_id': 'Alias for id. Makes joins easier',
         },
     ),
 )
@@ -107,13 +102,12 @@
 V8_WASM_SCRIPT = Table(
     python_module=__file__,
     class_name='V8WasmScriptTable',
-    sql_name='v8_wasm_script',
+    sql_name='__intrinsic_v8_wasm_script',
     columns=[
         C('v8_isolate_id', CppTableId(V8_ISOLATE)),
         C('internal_script_id', CppInt32()),
         C('url', CppString()),
         C('source', CppOptional(CppString())),
-        C('v8_wasm_script_id', Alias('id')),
     ],
     tabledoc=TableDoc(
         doc='Represents one WASM script',
@@ -123,7 +117,6 @@
             'internal_script_id': 'Script id used by the V8 engine',
             'url': 'URL of the source',
             'source': 'Actual contents of the script.',
-            'v8_wasm_script_id': 'Alias for id. Makes joins easier',
         },
     ),
 )
@@ -131,15 +124,14 @@
 V8_JS_FUNCTION = Table(
     python_module=__file__,
     class_name='V8JsFunctionTable',
-    sql_name='v8_js_function',
+    sql_name='__intrinsic_v8_js_function',
     columns=[
         C('name', CppString()),
         C('v8_js_script_id', CppTableId(V8_JS_SCRIPT)),
         C('is_toplevel', CppBool()),
         C('kind', CppString()),
         C('line', CppOptional(CppUint32())),
-        C('column', CppOptional(CppUint32())),
-        C('v8_js_function_id', Alias('id')),
+        C('col', CppOptional(CppUint32())),
     ],
     tabledoc=TableDoc(
         doc='Represents a v8 Javascript function',
@@ -158,10 +150,8 @@
                 'Function kind (e.g. regular function or constructor)',
             'line':
                 'Line in script where function is defined. Starts at 1',
-            'column':
+            'col':
                 'Column in script where function is defined. Starts at 1',
-            'v8_js_function_id':
-                'Alias for id. Makes joins easier',
         },
     ),
 )
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index 8894f47..d0d335f 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -53,6 +53,7 @@
 #include "src/trace_processor/metrics/metrics.h"
 #include "src/trace_processor/metrics/sql/amalgamated_sql_metrics.h"
 #include "src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h"
+#include "src/trace_processor/perfetto_sql/intrinsics/functions/base64.h"
 #include "src/trace_processor/perfetto_sql/intrinsics/functions/clock_functions.h"
 #include "src/trace_processor/perfetto_sql/intrinsics/functions/create_function.h"
 #include "src/trace_processor/perfetto_sql/intrinsics/functions/create_view_function.h"
@@ -78,6 +79,7 @@
 #include "src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_flat_slice.h"
 #include "src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_sched_upid.h"
 #include "src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_slice_layout.h"
+#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/interval_intersect.h"
 #include "src/trace_processor/perfetto_sql/intrinsics/table_functions/table_info.h"
 #include "src/trace_processor/perfetto_sql/prelude/tables_views.h"
 #include "src/trace_processor/perfetto_sql/stdlib/stdlib.h"
@@ -693,6 +695,11 @@
     if (!status.ok())
       PERFETTO_ELOG("%s", status.c_message());
   }
+  {
+    base::Status status = RegisterBase64Functions(*engine_.get());
+    if (!status.ok())
+      PERFETTO_ELOG("%s", status.c_message());
+  }
 
   const TraceStorage* storage = context_.storage.get();
 
@@ -785,6 +792,7 @@
   RegisterStaticTable(storage->gpu_counter_group_table());
   RegisterStaticTable(storage->perf_counter_track_table());
   RegisterStaticTable(storage->energy_counter_track_table());
+  RegisterStaticTable(storage->linux_device_track_table());
   RegisterStaticTable(storage->uid_counter_track_table());
   RegisterStaticTable(storage->energy_per_uid_counter_track_table());
 
@@ -818,6 +826,9 @@
   RegisterStaticTable(storage->v8_wasm_script_table());
   RegisterStaticTable(storage->v8_js_function_table());
 
+  RegisterStaticTable(storage->jit_code_table());
+  RegisterStaticTable(storage->jit_frame_table());
+
   RegisterStaticTable(storage->surfaceflinger_layers_snapshot_table());
   RegisterStaticTable(storage->surfaceflinger_layer_table());
   RegisterStaticTable(storage->surfaceflinger_transactions_table());
@@ -882,6 +893,8 @@
       new ExperimentalFlatSlice(&context_)));
   engine_->RegisterStaticTableFunction(
       std::make_unique<DominatorTree>(context_.storage->mutable_string_pool()));
+  engine_->RegisterStaticTableFunction(std::make_unique<IntervalIntersect>(
+      context_.storage->mutable_string_pool()));
   engine_->RegisterStaticTableFunction(
       std::make_unique<Dfs>(context_.storage->mutable_string_pool()));
 
diff --git a/src/trace_processor/types/trace_processor_context.h b/src/trace_processor/types/trace_processor_context.h
index 26d1ff5..9a89eb3 100644
--- a/src/trace_processor/types/trace_processor_context.h
+++ b/src/trace_processor/types/trace_processor_context.h
@@ -126,10 +126,11 @@
   std::unique_ptr<Destructible> perf_data_tracker;       // PerfDataTracker
   std::unique_ptr<Destructible> content_analyzer;        // ProtoContentAnalyzer
   std::unique_ptr<Destructible>
-      shell_transitions_tracker;             // ShellTransitionsTracker
-  std::unique_ptr<Destructible> v8_tracker;  // V8Tracker
+      shell_transitions_tracker;  // ShellTransitionsTracker
   std::unique_ptr<Destructible>
       ftrace_sched_tracker;  // FtraceSchedEventTracker
+  std::unique_ptr<Destructible> v8_tracker;   // V8Tracker
+  std::unique_ptr<Destructible> jit_tracker;  // JitTracker
 
   // These fields are trace readers which will be called by |forwarding_parser|
   // once the format of the trace is discovered. They are placed here as they
diff --git a/src/trace_redaction/BUILD.gn b/src/trace_redaction/BUILD.gn
index f65c588..b662902 100644
--- a/src/trace_redaction/BUILD.gn
+++ b/src/trace_redaction/BUILD.gn
@@ -32,8 +32,12 @@
     "find_package_uid.h",
     "populate_allow_lists.cc",
     "populate_allow_lists.h",
+    "proto_util.cc",
+    "proto_util.h",
     "prune_package_list.cc",
     "prune_package_list.h",
+    "scrub_ftrace_events.cc",
+    "scrub_ftrace_events.h",
     "scrub_trace_packet.cc",
     "scrub_trace_packet.h",
     "trace_redaction_framework.cc",
@@ -51,13 +55,17 @@
     "../../protos/perfetto/trace:non_minimal_zero",
     "../../protos/perfetto/trace/android:cpp",
     "../../protos/perfetto/trace/android:zero",
+    "../../protos/perfetto/trace/ftrace:zero",
     "../trace_processor:storage_minimal",
   ]
 }
 
 source_set("integrationtests") {
   testonly = true
-  sources = [ "trace_redactor_integrationtest.cc" ]
+  sources = [
+    "scrub_ftrace_events_integrationtest.cc",
+    "trace_redactor_integrationtest.cc",
+  ]
   deps = [
     ":trace_redaction",
     "../../gn:default_deps",
@@ -65,6 +73,7 @@
     "../../include/perfetto/ext/base",
     "../../protos/perfetto/trace:non_minimal_zero",
     "../../protos/perfetto/trace/android:zero",
+    "../../protos/perfetto/trace/ftrace:zero",
     "../base:test_support",
   ]
 }
@@ -73,16 +82,23 @@
   testonly = true
   sources = [
     "find_package_uid_unittest.cc",
+    "proto_util_unittest.cc",
     "prune_package_list_unittest.cc",
+    "scrub_ftrace_events_unittest.cc",
     "scrub_trace_packet_unittest.cc",
   ]
   deps = [
     ":trace_redaction",
     "../../gn:default_deps",
     "../../gn:gtest_and_gmock",
+    "../../include/perfetto/protozero:protozero",
+    "../../protos/perfetto/config:cpp",
+    "../../protos/perfetto/config:zero",
     "../../protos/perfetto/trace:non_minimal_cpp",
     "../../protos/perfetto/trace:zero",
     "../../protos/perfetto/trace/android:cpp",
+    "../../protos/perfetto/trace/ftrace:cpp",
+    "../../protos/perfetto/trace/ftrace:zero",
     "../../protos/perfetto/trace/ps:cpp",
     "../../protos/perfetto/trace/ps:zero",
     "../base:test_support",
diff --git a/src/trace_redaction/main.cc b/src/trace_redaction/main.cc
index 24ad116..153e437 100644
--- a/src/trace_redaction/main.cc
+++ b/src/trace_redaction/main.cc
@@ -18,6 +18,8 @@
 #include "perfetto/base/status.h"
 #include "src/trace_redaction/find_package_uid.h"
 #include "src/trace_redaction/prune_package_list.h"
+#include "src/trace_redaction/scrub_ftrace_events.h"
+#include "src/trace_redaction/scrub_trace_packet.h"
 #include "src/trace_redaction/trace_redaction_framework.h"
 #include "src/trace_redaction/trace_redactor.h"
 
@@ -36,6 +38,8 @@
 
   // Add all transforms.
   redactor.transformers()->emplace_back(new PrunePackageList());
+  redactor.transformers()->emplace_back(new ScrubTracePacket());
+  redactor.transformers()->emplace_back(new ScrubFtraceEvents());
 
   Context context;
   context.package_name = package_name;
diff --git a/src/trace_redaction/populate_allow_lists.cc b/src/trace_redaction/populate_allow_lists.cc
index 1ccaa00..fbfe279 100644
--- a/src/trace_redaction/populate_allow_lists.cc
+++ b/src/trace_redaction/populate_allow_lists.cc
@@ -19,6 +19,7 @@
 #include "perfetto/base/status.h"
 #include "src/trace_redaction/trace_redaction_framework.h"
 
+#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
 #include "protos/perfetto/trace/trace_packet.pbzero.h"
 
 namespace perfetto::trace_redaction {
@@ -50,6 +51,41 @@
       protos::pbzero::TracePacket::kPackagesListFieldNumber,
   };
 
+  context->ftrace_packet_allow_list = {
+      protos::pbzero::FtraceEvent::kSchedSwitchFieldNumber,
+      protos::pbzero::FtraceEvent::kCpuFrequencyFieldNumber,
+      protos::pbzero::FtraceEvent::kCpuIdleFieldNumber,
+      protos::pbzero::FtraceEvent::kSchedBlockedReasonFieldNumber,
+      protos::pbzero::FtraceEvent::kSchedWakingFieldNumber,
+      protos::pbzero::FtraceEvent::kSuspendResumeFieldNumber,
+      protos::pbzero::FtraceEvent::kTaskNewtaskFieldNumber,
+      protos::pbzero::FtraceEvent::kTaskRenameFieldNumber,
+      protos::pbzero::FtraceEvent::kSchedProcessFreeFieldNumber,
+      protos::pbzero::FtraceEvent::kRssStatFieldNumber,
+      protos::pbzero::FtraceEvent::kIonHeapShrinkFieldNumber,
+      protos::pbzero::FtraceEvent::kIonHeapGrowFieldNumber,
+      protos::pbzero::FtraceEvent::kIonStatFieldNumber,
+      protos::pbzero::FtraceEvent::kIonBufferCreateFieldNumber,
+      protos::pbzero::FtraceEvent::kIonBufferDestroyFieldNumber,
+      protos::pbzero::FtraceEvent::kDmaHeapStatFieldNumber,
+      protos::pbzero::FtraceEvent::kRssStatThrottledFieldNumber,
+  };
+
+  // TODO: Some ftrace fields should be retained, but they carry too much risk
+  // without additional redaction. This list should be configured in a build
+  // primitive so that they can be optionally included.
+  //
+  // TODO: Some fields will create new packets (e.g. binder calls may create
+  // new spans. This is currently not supported (generated packets still
+  // need to be redacted).
+  //
+  // protos::pbzero::FtraceEvent::kPrintFieldNumber,
+  // protos::pbzero::FtraceEvent::kBinderTransactionFieldNumber,
+  // protos::pbzero::FtraceEvent::kBinderTransactionReceivedFieldNumber,
+  // protos::pbzero::FtraceEvent::kBinderSetPriorityFieldNumber,
+  // protos::pbzero::FtraceEvent::kBinderLockedFieldNumber,
+  // protos::pbzero::FtraceEvent::kBinderUnlockFieldNumber,
+
   return base::OkStatus();
 }
 
diff --git a/src/trace_redaction/proto_util.cc b/src/trace_redaction/proto_util.cc
new file mode 100644
index 0000000..30962d7
--- /dev/null
+++ b/src/trace_redaction/proto_util.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/proto_util.h"
+
+#include "perfetto/protozero/field.h"
+#include "perfetto/protozero/message.h"
+
+namespace perfetto::trace_redaction {
+namespace proto_util {
+
+// This is copied from "src/protozero/field.cc", but was modified to use the
+// serialization methods provided in "perfetto/protozero/message.h".
+void AppendField(const protozero::Field& field, protozero::Message* message) {
+  auto id = field.id();
+  auto type = field.type();
+
+  switch (type) {
+    case protozero::proto_utils::ProtoWireType::kVarInt: {
+      message->AppendVarInt(id, field.raw_int_value());
+      return;
+    }
+
+    case protozero::proto_utils::ProtoWireType::kFixed32: {
+      message->AppendFixed(id, field.as_uint32());
+      return;
+    }
+
+    case protozero::proto_utils::ProtoWireType::kFixed64: {
+      message->AppendFixed(id, field.as_uint64());
+      return;
+    }
+
+    case protozero::proto_utils::ProtoWireType::kLengthDelimited: {
+      message->AppendBytes(id, field.data(), field.size());
+      return;
+    }
+  }
+
+  // A switch-statement would be preferred, but when using a switch statement,
+  // it complains that about case coverage.
+  PERFETTO_FATAL("Unknown field type %u", static_cast<uint8_t>(type));
+}
+
+}  // namespace proto_util
+}  // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/proto_util.h b/src/trace_redaction/proto_util.h
new file mode 100644
index 0000000..6fac1d8
--- /dev/null
+++ b/src/trace_redaction/proto_util.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_REDACTION_PROTO_UTIL_H_
+#define SRC_TRACE_REDACTION_PROTO_UTIL_H_
+
+#include "perfetto/protozero/field.h"
+#include "perfetto/protozero/message.h"
+
+namespace perfetto::trace_redaction {
+
+// This is here, and not in protozero, because field and message are never found
+// together. Because trace redaction is the only user of this function, it is
+// here.
+namespace proto_util {
+
+void AppendField(const protozero::Field& field, protozero::Message* message);
+
+}  // namespace proto_util
+
+}  // namespace perfetto::trace_redaction
+
+#endif  // SRC_TRACE_REDACTION_PROTO_UTIL_H_
diff --git a/src/trace_redaction/proto_util_unittest.cc b/src/trace_redaction/proto_util_unittest.cc
new file mode 100644
index 0000000..aa5be3a
--- /dev/null
+++ b/src/trace_redaction/proto_util_unittest.cc
@@ -0,0 +1,270 @@
+/*
+ * 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 "perfetto/protozero/scattered_heap_buffer.h"
+
+#include "src/trace_redaction/proto_util.h"
+#include "test/gtest_and_gmock.h"
+
+#include "protos/perfetto/config/test_config.gen.h"
+#include "protos/perfetto/config/test_config.pbzero.h"
+#include "protos/perfetto/trace/trace_packet.gen.h"
+
+namespace perfetto::trace_redaction {
+
+namespace proto_util {
+
+class ProtoUtilTest : public testing::Test {
+ protected:
+  void Reserialize(const protos::gen::TestConfig_DummyFields& fields) {
+    // Serialize the object and then deserialize it with proto decoder. This is
+    // needed to get the fields.
+    auto serialized = fields.SerializeAsString();
+    protozero::ProtoDecoder decoder(serialized);
+
+    protozero::HeapBuffered<protos::pbzero::TestConfig_DummyFields> message;
+
+    for (auto field = decoder.ReadField(); field.valid();
+         field = decoder.ReadField()) {
+      AppendField(field, message.get());
+    }
+
+    auto reserialized = message.SerializeAsString();
+
+    ASSERT_EQ(serialized, reserialized);
+  }
+};
+
+class ProtoUtilUint32Test : public ProtoUtilTest,
+                            public testing::WithParamInterface<uint32_t> {};
+class ProtoUtilUint64Test : public ProtoUtilTest,
+                            public testing::WithParamInterface<uint64_t> {};
+class ProtoUtilInt32Test : public ProtoUtilTest,
+                           public testing::WithParamInterface<int32_t> {};
+class ProtoUtilInt64Test : public ProtoUtilTest,
+                           public testing::WithParamInterface<int64_t> {};
+class ProtoUtilFixed32Test : public ProtoUtilTest,
+                             public testing::WithParamInterface<uint32_t> {};
+class ProtoUtilFixed64Test : public ProtoUtilTest,
+                             public testing::WithParamInterface<uint64_t> {};
+class ProtoUtilSfixed32Test : public ProtoUtilTest,
+                              public testing::WithParamInterface<int32_t> {};
+class ProtoUtilSfixed64Test : public ProtoUtilTest,
+                              public testing::WithParamInterface<int64_t> {};
+class ProtoUtilDoubleTest : public ProtoUtilTest,
+                            public testing::WithParamInterface<double> {};
+class ProtoUtilFloatTest : public ProtoUtilTest,
+                           public testing::WithParamInterface<float> {};
+class ProtoUtilSint32Test : public ProtoUtilTest,
+                            public testing::WithParamInterface<int32_t> {};
+class ProtoUtilSint64Test : public ProtoUtilTest,
+                            public testing::WithParamInterface<int64_t> {};
+class ProtoUtilStringTest : public ProtoUtilTest,
+                            public testing::WithParamInterface<std::string> {};
+class ProtoUtilBytesTest : public ProtoUtilTest,
+                           public testing::WithParamInterface<std::string> {};
+
+TEST_P(ProtoUtilUint32Test, FullDomain) {
+  auto value = GetParam();
+
+  protos::gen::TestConfig_DummyFields fields;
+  fields.set_field_uint32(value);
+  Reserialize(fields);
+}
+
+INSTANTIATE_TEST_SUITE_P(Reserialize,
+                         ProtoUtilUint32Test,
+                         testing::Values(std::numeric_limits<uint32_t>::min(),
+                                         0,
+                                         0xFAAAAAAA,
+                                         std::numeric_limits<uint32_t>::max()));
+
+TEST_P(ProtoUtilUint64Test, FullDomain) {
+  auto value = GetParam();
+
+  protos::gen::TestConfig_DummyFields fields;
+  fields.set_field_uint64(value);
+  Reserialize(fields);
+}
+
+INSTANTIATE_TEST_SUITE_P(Reserialize,
+                         ProtoUtilUint64Test,
+                         testing::Values(std::numeric_limits<uint64_t>::min(),
+                                         0,
+                                         0xFAAAAAAAAAAAAAAA,
+                                         std::numeric_limits<uint64_t>::max()));
+
+TEST_P(ProtoUtilInt32Test, FullDomain) {
+  auto value = GetParam();
+
+  protos::gen::TestConfig_DummyFields fields;
+  fields.set_field_int32(value);
+  Reserialize(fields);
+}
+
+INSTANTIATE_TEST_SUITE_P(Reserialize,
+                         ProtoUtilInt32Test,
+                         testing::Values(std::numeric_limits<int32_t>::min(),
+                                         0xFAAAAAAA,
+                                         0,
+                                         0x0AAAAAAA,
+                                         std::numeric_limits<int32_t>::max()));
+
+TEST_P(ProtoUtilInt64Test, FullDomain) {
+  auto value = GetParam();
+
+  protos::gen::TestConfig_DummyFields fields;
+  fields.set_field_int64(value);
+  Reserialize(fields);
+}
+
+INSTANTIATE_TEST_SUITE_P(Reserialize,
+                         ProtoUtilInt64Test,
+                         testing::Values(std::numeric_limits<int64_t>::min(),
+                                         0xFAAAAAAAAAAAAAAA,
+                                         0,
+                                         0x0AAAAAAAAAAAAAAA,
+                                         std::numeric_limits<int64_t>::max()));
+
+TEST_P(ProtoUtilFixed32Test, FullDomain) {
+  auto value = GetParam();
+
+  protos::gen::TestConfig_DummyFields fields;
+  fields.set_field_fixed32(value);
+  Reserialize(fields);
+}
+
+INSTANTIATE_TEST_SUITE_P(Reserialize,
+                         ProtoUtilFixed32Test,
+                         testing::Values(std::numeric_limits<uint32_t>::min(),
+                                         0,
+                                         0xFAAAAAAAAAAAAAAA,
+                                         std::numeric_limits<uint32_t>::max()));
+
+TEST_P(ProtoUtilSfixed32Test, FullDomain) {
+  auto value = GetParam();
+
+  protos::gen::TestConfig_DummyFields fields;
+  fields.set_field_sfixed32(value);
+  Reserialize(fields);
+}
+
+INSTANTIATE_TEST_SUITE_P(Reserialize,
+                         ProtoUtilSfixed32Test,
+                         testing::Values(std::numeric_limits<int32_t>::min(),
+                                         0xFAAAAAAA,
+                                         0,
+                                         0x0AAAAAAA,
+                                         std::numeric_limits<int32_t>::max()));
+
+TEST_P(ProtoUtilDoubleTest, FullDomain) {
+  auto value = GetParam();
+
+  protos::gen::TestConfig_DummyFields fields;
+  fields.set_field_double(value);
+  Reserialize(fields);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    Reserialize,
+    ProtoUtilDoubleTest,
+    testing::Values(std::numeric_limits<double>::min(),
+                    0.0,
+                    1.0,
+                    std::numeric_limits<double>::infinity(),
+                    std::numeric_limits<double>::max()));
+
+TEST_P(ProtoUtilFloatTest, FullDomain) {
+  auto value = GetParam();
+
+  protos::gen::TestConfig_DummyFields fields;
+  fields.set_field_float(value);
+  Reserialize(fields);
+}
+
+INSTANTIATE_TEST_SUITE_P(Reserialize,
+                         ProtoUtilFloatTest,
+                         testing::Values(std::numeric_limits<float>::min(),
+                                         0.0f,
+                                         1.0f,
+                                         std::numeric_limits<float>::infinity(),
+                                         std::numeric_limits<float>::max()));
+
+TEST_P(ProtoUtilSint64Test, FullDomain) {
+  auto value = GetParam();
+
+  protos::gen::TestConfig_DummyFields fields;
+  fields.set_field_sint64(value);
+  Reserialize(fields);
+}
+
+INSTANTIATE_TEST_SUITE_P(Reserialize,
+                         ProtoUtilSint64Test,
+                         testing::Values(std::numeric_limits<int64_t>::min(),
+                                         0xFAAAAAAAAAAAAAAA,
+                                         0,
+                                         0x0AAAAAAAAAAAAAAA,
+                                         std::numeric_limits<int64_t>::max()));
+
+TEST_P(ProtoUtilSint32Test, FullDomain) {
+  auto value = GetParam();
+
+  protos::gen::TestConfig_DummyFields fields;
+  fields.set_field_sint32(value);
+  Reserialize(fields);
+}
+
+INSTANTIATE_TEST_SUITE_P(Reserialize,
+                         ProtoUtilSint32Test,
+                         testing::Values(std::numeric_limits<int32_t>::min(),
+                                         0xFAAAAAAA,
+                                         0,
+                                         0x0AAAAAAA,
+                                         std::numeric_limits<int32_t>::max()));
+
+TEST_P(ProtoUtilStringTest, Various) {
+  auto value = GetParam();
+
+  protos::gen::TestConfig_DummyFields fields;
+  fields.set_field_string(value);
+  Reserialize(fields);
+}
+
+INSTANTIATE_TEST_SUITE_P(Reserialize,
+                         ProtoUtilStringTest,
+                         testing::Values("",
+                                         "a",
+                                         "abcdefghijklmonpqrstuvwxyz",
+                                         std::string(1024, 'a')));
+
+TEST_P(ProtoUtilBytesTest, Various) {
+  auto value = GetParam();
+
+  protos::gen::TestConfig_DummyFields fields;
+  fields.set_field_bytes(value);
+  Reserialize(fields);
+}
+
+INSTANTIATE_TEST_SUITE_P(Reserialize,
+                         ProtoUtilBytesTest,
+                         testing::Values("",
+                                         "a",
+                                         "abcdefghijklmonpqrstuvwxyz",
+                                         std::string(1024, 'a')));
+
+}  // namespace proto_util
+
+}  // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/scrub_ftrace_events.cc b/src/trace_redaction/scrub_ftrace_events.cc
new file mode 100644
index 0000000..b41fc6e
--- /dev/null
+++ b/src/trace_redaction/scrub_ftrace_events.cc
@@ -0,0 +1,152 @@
+/*
+ * 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/scrub_ftrace_events.h"
+
+#include <string>
+
+#include "perfetto/protozero/scattered_heap_buffer.h"
+#include "src/trace_redaction/proto_util.h"
+
+#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
+#include "protos/perfetto/trace/trace.pbzero.h"
+
+namespace perfetto::trace_redaction {
+namespace {
+
+constexpr auto kFtraceEventsFieldNumber =
+    protos::pbzero::TracePacket::kFtraceEventsFieldNumber;
+
+constexpr auto kEventFieldNumber =
+    protos::pbzero::FtraceEventBundle::kEventFieldNumber;
+
+enum class Redact : uint8_t {
+  // Some resources in the target need to be redacted.
+  kSomething = 0,
+
+  // No resources in the target need to be redacted.
+  kNothing = 1,
+};
+
+// Return kSomething if an event will change after redaction . If a packet
+// will not change, then the packet should skip redaction and be appended
+// to the output.
+//
+// Event packets have few packets (e.g. timestamp, pid, the event payload).
+// because of this, it is relatively cheap to test a packet.
+//
+//  event {
+//    timestamp: 6702095044306682
+//    pid: 0
+//    sched_switch {
+//      prev_comm: "swapper/2"
+//      prev_pid: 0
+//      prev_prio: 120
+//      prev_state: 0
+//      next_comm: "surfaceflinger"
+//      next_pid: 819
+//      next_prio: 120
+//    }
+//  }
+Redact ProbeEvent(const Context& context, const protozero::Field& event) {
+  if (event.id() != kEventFieldNumber) {
+    PERFETTO_FATAL("Invalid proto field. Expected kEventFieldNumber.");
+  }
+
+  protozero::ProtoDecoder decoder(event.data(), event.size());
+
+  for (auto field = decoder.ReadField(); field.valid();
+       field = decoder.ReadField()) {
+    if (context.ftrace_packet_allow_list.count(field.id()) != 0) {
+      return Redact::kNothing;
+    }
+  }
+
+  return Redact::kSomething;
+}
+
+}  // namespace
+
+//  packet {
+//    ftrace_events {
+//      event {                   <-- This is where we test the allow-list
+//        timestamp: 6702095044299807
+//        pid: 0
+//        cpu_idle {              <-- This is the event data (allow-list)
+//          state: 4294967295
+//          cpu_id: 2
+//        }
+//      }
+//    }
+//  }
+base::Status ScrubFtraceEvents::Transform(const Context& context,
+                                          std::string* packet) const {
+  if (packet == nullptr || packet->empty()) {
+    return base::ErrStatus("Cannot scrub null or empty trace packet.");
+  }
+
+  if (context.ftrace_packet_allow_list.empty()) {
+    return base::ErrStatus("Cannot scrub ftrace packets, missing allow-list.");
+  }
+
+  // If the packet has no ftrace events, skip it, leaving it unmodified.
+  protos::pbzero::TracePacket::Decoder query(*packet);
+  if (!query.has_ftrace_events()) {
+    return base::OkStatus();
+  }
+
+  protozero::HeapBuffered<protos::pbzero::TracePacket> packet_msg;
+
+  // packet.foreach_child.foreach( ... )
+  protozero::ProtoDecoder d_packet(*packet);
+  for (auto packet_child_it = d_packet.ReadField(); packet_child_it.valid();
+       packet_child_it = d_packet.ReadField()) {
+    // packet.child_not<ftrace_events>( ).do ( ... )
+    if (packet_child_it.id() != kFtraceEventsFieldNumber) {
+      proto_util::AppendField(packet_child_it, packet_msg.get());
+      continue;
+    }
+
+    // To clarify, "ftrace_events" is the field name and "FtraceEventBundle" is
+    // the field type. The terms are often used interchangeably.
+    auto* ftrace_events_msg = packet_msg->set_ftrace_events();
+
+    // packet.child<ftrace_events>( ).foreach_child( ... )
+    protozero::ProtoDecoder ftrace_events(packet_child_it.as_bytes());
+    for (auto ftrace_events_it = ftrace_events.ReadField();
+         ftrace_events_it.valid();
+         ftrace_events_it = ftrace_events.ReadField()) {
+      // packet.child<ftrace_events>( ).child_not<event>( ).do ( ... )
+      if (ftrace_events_it.id() != kEventFieldNumber) {
+        proto_util::AppendField(ftrace_events_it, ftrace_events_msg);
+        continue;
+      }
+
+      // packet.child<ftrace_events>( ).child_is<event>( ).do ( ... )
+      if (ProbeEvent(context, ftrace_events_it) == Redact::kNothing) {
+        proto_util::AppendField(ftrace_events_it, ftrace_events_msg);
+        continue;
+      }
+
+      // Dropping packet = "is event" and "is redacted"
+    }
+  }
+
+  packet->assign(packet_msg.SerializeAsString());
+  return base::OkStatus();
+}
+
+}  // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/scrub_ftrace_events.h b/src/trace_redaction/scrub_ftrace_events.h
new file mode 100644
index 0000000..77e6835
--- /dev/null
+++ b/src/trace_redaction/scrub_ftrace_events.h
@@ -0,0 +1,58 @@
+/*
+ * 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_REDACTION_SCRUB_FTRACE_EVENTS_H_
+#define SRC_TRACE_REDACTION_SCRUB_FTRACE_EVENTS_H_
+
+#include <string>
+
+#include "src/trace_redaction/trace_redaction_framework.h"
+
+namespace perfetto::trace_redaction {
+
+//  Assumptions:
+//    1. This is a hot path (a lot of ftrace packets)
+//    2. Allocations are slower than CPU cycles.
+//
+//  Overview:
+//    To limit allocations pbzero protos are used to build a new packet. These
+//    protos are append-only, so data is not removed from the packet. Instead,
+//    data is optionally added to a new packet.
+//
+//    To limit allocations, the goal is to add data as large chucks rather than
+//    small fragments. To do this, a reactive strategy is used. All operations
+//    follow a probe-than-act pattern. Before any action can be taken, the
+//    input data must be queries to determine the scope. For example:
+//
+//        [------A------][---B---][------C------]
+//                                [---][-D-][---]
+//
+//        Assume that A and B don't need any work, they can be appended to the
+//        output as two large blocks.
+//
+//        Block C is different, there is a block D that falls within block C.
+//        Block D contains sensitive information and should be dropped. When C
+//        is probed, it will come back saying that C needs additional redaction.
+
+class ScrubFtraceEvents final : public TransformPrimitive {
+ public:
+  base::Status Transform(const Context& context,
+                         std::string* packet) const override;
+};
+
+}  // namespace perfetto::trace_redaction
+
+#endif  // SRC_TRACE_REDACTION_SCRUB_FTRACE_EVENTS_H_
diff --git a/src/trace_redaction/scrub_ftrace_events_integrationtest.cc b/src/trace_redaction/scrub_ftrace_events_integrationtest.cc
new file mode 100644
index 0000000..7584b0f
--- /dev/null
+++ b/src/trace_redaction/scrub_ftrace_events_integrationtest.cc
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/file_utils.h"
+#include "src/base/test/status_matchers.h"
+#include "src/base/test/utils.h"
+#include "src/trace_redaction/scrub_ftrace_events.h"
+#include "src/trace_redaction/trace_redaction_framework.h"
+#include "test/gtest_and_gmock.h"
+
+#include "protos/perfetto/trace//ftrace/ftrace_event.pbzero.h"
+#include "protos/perfetto/trace/android/packages_list.pbzero.h"
+#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
+#include "protos/perfetto/trace/trace.pbzero.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+
+namespace perfetto::trace_redaction {
+
+namespace {
+using FtraceEvent = protos::pbzero::FtraceEvent;
+using PackagesList = protos::pbzero::PackagesList;
+using PackageInfo = protos::pbzero::PackagesList::PackageInfo;
+using Trace = protos::pbzero::Trace;
+using TracePacket = protos::pbzero::TracePacket;
+
+constexpr std::string_view kTracePath =
+    "test/data/trace-redaction-general.pftrace";
+
+// Runs ScrubFtraceEvents over an actual trace, verifying packet integrity when
+// fields are removed.
+class ScrubFtraceEventsIntegrationTest : public testing::Test {
+ public:
+  ScrubFtraceEventsIntegrationTest() = default;
+  ~ScrubFtraceEventsIntegrationTest() override = default;
+
+ protected:
+  void SetUp() override {
+    src_trace_ = base::GetTestDataPath(std::string(kTracePath));
+    context_.ftrace_packet_allow_list.insert(
+        protos::pbzero::FtraceEvent::kSchedSwitchFieldNumber);
+  }
+
+  std::string src_trace_;
+
+  Context context_;  // Used for allowlist.
+  ScrubFtraceEvents transform_;
+
+  static base::StatusOr<std::string> ReadRawTrace(const std::string& path) {
+    std::string redacted_buffer;
+
+    if (base::ReadFile(path, &redacted_buffer)) {
+      return redacted_buffer;
+    }
+
+    return base::ErrStatus("Failed to read %s", path.c_str());
+  }
+
+  // Gets spans for `event` messages that contain `sched_switch` messages.
+  static std::vector<protozero::ConstBytes> GetEventsWithSchedSwitch(
+      protos::pbzero::TracePacket::Decoder packet) {
+    std::vector<protozero::ConstBytes> ranges;
+
+    if (!packet.has_ftrace_events()) {
+      return ranges;
+    }
+
+    protos::pbzero::FtraceEventBundle::Decoder bundle(packet.ftrace_events());
+
+    if (!bundle.has_event()) {
+      return ranges;
+    }
+
+    for (auto event_it = bundle.event(); event_it; ++event_it) {
+      protos::pbzero::FtraceEvent::Decoder event(*event_it);
+
+      if (event.has_sched_switch()) {
+        ranges.push_back(*event_it);
+      }
+    }
+
+    return ranges;
+  }
+
+  // Instead of using the allow-list created by PopulateAllowlist, use a simpler
+  // allowlist; an allowlist that contains most value types.
+  //
+  // uint64....FtraceEvent...............timestamp
+  // uint32....FtraceEvent...............pid
+  //
+  // int32.....SchedSwitchFtraceEvent....prev_pid
+  // int64.....SchedSwitchFtraceEvent....prev_state
+  // string....SchedSwitchFtraceEvent....next_comm
+  //
+  // Compare all switch events in each trace. The comparison is only on the
+  // switch packets, not on the data leading up to or around them.
+  static void ComparePackets(protos::pbzero::TracePacket::Decoder left,
+                             protos::pbzero::TracePacket::Decoder right) {
+    auto left_switches = GetEventsWithSchedSwitch(std::move(left));
+    auto right_switches = GetEventsWithSchedSwitch(std::move(right));
+
+    ASSERT_EQ(left_switches.size(), right_switches.size());
+
+    auto left_switch_it = left_switches.begin();
+    auto right_switch_it = right_switches.begin();
+
+    while (left_switch_it != left_switches.end() &&
+           right_switch_it != right_switches.end()) {
+      auto left_switch_str = left_switch_it->ToStdString();
+      auto right_switch_str = right_switch_it->ToStdString();
+
+      ASSERT_EQ(left_switch_str, right_switch_str);
+
+      ++left_switch_it;
+      ++right_switch_it;
+    }
+
+    ASSERT_EQ(left_switches.size(), right_switches.size());
+  }
+};
+
+TEST_F(ScrubFtraceEventsIntegrationTest, FindsPackageAndFiltersPackageList) {
+  const auto& src_file = src_trace_;
+
+  auto raw_src_trace = ReadRawTrace(src_file);
+  ASSERT_OK(raw_src_trace);
+
+  protos::pbzero::Trace::Decoder source_trace(raw_src_trace.value());
+
+  for (auto packet_it = source_trace.packet(); packet_it; ++packet_it) {
+    auto packet = packet_it->as_std_string();
+    ASSERT_OK(transform_.Transform(context_, &packet));
+
+    protos::pbzero::TracePacket::Decoder left_packet(*packet_it);
+    protos::pbzero::TracePacket::Decoder right_packet(packet);
+
+    ComparePackets(std::move(left_packet), std::move(right_packet));
+  }
+}
+
+}  // namespace
+}  // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/scrub_ftrace_events_unittest.cc b/src/trace_redaction/scrub_ftrace_events_unittest.cc
new file mode 100644
index 0000000..5b5012f
--- /dev/null
+++ b/src/trace_redaction/scrub_ftrace_events_unittest.cc
@@ -0,0 +1,248 @@
+/*
+ * 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/scrub_ftrace_events.h"
+#include "protos/perfetto/trace/ftrace/power.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/ftrace_event_bundle.pbzero.h"
+#include "protos/perfetto/trace/ftrace/task.gen.h"
+#include "protos/perfetto/trace/ps/process_tree.gen.h"
+#include "protos/perfetto/trace/trace_packet.gen.h"
+
+namespace perfetto::trace_redaction {
+
+// Tests which nested messages and fields are removed.
+class ScrubFtraceEventsTest : public testing::Test {
+ public:
+  ScrubFtraceEventsTest() = default;
+  ~ScrubFtraceEventsTest() override = default;
+
+ protected:
+  // task_rename should be in the allow-list.
+  static void AddTaskRename(protos::gen::FtraceEventBundle* bundle,
+                            int32_t pid,
+                            const std::string& old_comm,
+                            const std::string& new_comm) {
+    auto* e = bundle->add_event();
+    e->mutable_task_rename()->set_pid(pid);
+    e->mutable_task_rename()->set_oldcomm(old_comm);
+    e->mutable_task_rename()->set_newcomm(new_comm);
+  }
+
+  static void AddClockSetRate(protos::gen::FtraceEventBundle* bundle,
+                              uint64_t cpu,
+                              const std::string& name,
+                              uint64_t state) {
+    auto* e = bundle->add_event();
+    e->mutable_clock_set_rate()->set_cpu_id(cpu);
+    e->mutable_clock_set_rate()->set_name(name);
+    e->mutable_clock_set_rate()->set_state(state);
+  }
+};
+
+TEST_F(ScrubFtraceEventsTest, ReturnErrorForNullPacket) {
+  // Have something in the allow-list to avoid that error.
+  Context context;
+  context.ftrace_packet_allow_list = {
+      protos::pbzero::FtraceEvent::kTaskRenameFieldNumber};
+
+  ScrubFtraceEvents scrub;
+  ASSERT_FALSE(scrub.Transform(context, nullptr).ok());
+}
+
+TEST_F(ScrubFtraceEventsTest, ReturnErrorForEmptyPacket) {
+  // Have something in the allow-list to avoid that error.
+  Context context;
+  context.ftrace_packet_allow_list = {
+      protos::pbzero::FtraceEvent::kTaskRenameFieldNumber};
+
+  std::string packet_str = "";
+
+  ScrubFtraceEvents scrub;
+  ASSERT_FALSE(scrub.Transform(context, &packet_str).ok());
+}
+
+TEST_F(ScrubFtraceEventsTest, ReturnErrorForEmptyAllowList) {
+  // The context will have no allow-list entries. ScrubFtraceEvents should fail.
+  Context context;
+
+  protos::gen::TracePacket packet;
+  std::string packet_str = packet.SerializeAsString();
+
+  ScrubFtraceEvents scrub;
+  ASSERT_FALSE(scrub.Transform(context, &packet_str).ok());
+}
+
+TEST_F(ScrubFtraceEventsTest, IgnorePacketWithNoFtraceEvents) {
+  protos::gen::TracePacket trace_packet;
+  auto* tree = trace_packet.mutable_process_tree();
+
+  auto& process = tree->mutable_processes()->emplace_back();
+  process.set_pid(1);
+  process.set_ppid(2);
+  process.set_uid(3);
+
+  auto& thread = tree->mutable_threads()->emplace_back();
+  thread.set_name("hello world");
+  thread.set_tgid(1);
+  thread.set_tid(135);
+
+  auto original_packet = trace_packet.SerializeAsString();
+  auto packet = original_packet;
+
+  Context context;
+  context.ftrace_packet_allow_list = {
+      protos::pbzero::FtraceEvent::kTaskRenameFieldNumber};
+
+  ScrubFtraceEvents transform;
+  ASSERT_OK(transform.Transform(context, &packet));
+
+  // The packet doesn't have any ftrace events. It should not be affected by
+  // this transform.
+  ASSERT_EQ(original_packet, packet);
+}
+
+// There are some values in a ftrace event that sits behind the ftrace bundle.
+// These values should be retained.
+TEST_F(ScrubFtraceEventsTest, KeepsFtraceBundleSiblingValues) {
+  protos::gen::TracePacket trace_packet;
+  auto* ftrace_events = trace_packet.mutable_ftrace_events();
+
+  ftrace_events->set_cpu(7);
+  AddTaskRename(ftrace_events, 7, "old_comm", "new_comm_7");
+  AddClockSetRate(ftrace_events, 7, "cool cpu name", 1);
+
+  auto original_packet = trace_packet.SerializeAsString();
+  auto packet = original_packet;
+
+  Context context;
+  context.ftrace_packet_allow_list = {
+      protos::pbzero::FtraceEvent::kTaskRenameFieldNumber};
+
+  ScrubFtraceEvents transform;
+  ASSERT_OK(transform.Transform(context, &packet));
+
+  protos::gen::TracePacket gen_packet;
+  gen_packet.ParseFromString(packet);
+
+  ASSERT_TRUE(gen_packet.has_ftrace_events());
+  const auto& gen_events = gen_packet.ftrace_events();
+
+  // Because the CPU sits beside the event list, and not inside the event list,
+  // the CPU value should be retained.
+  ASSERT_TRUE(gen_events.has_cpu());
+  ASSERT_EQ(gen_events.cpu(), 7u);
+
+  // ClockSetRate should be dropped. Only TaskRename should remain.
+  ASSERT_EQ(gen_events.event_size(), 1);
+  ASSERT_FALSE(gen_events.event().front().has_clock_set_rate());
+  ASSERT_TRUE(gen_events.event().front().has_task_rename());
+}
+
+TEST_F(ScrubFtraceEventsTest, KeepsAllowedEvents) {
+  Context context;
+  context.ftrace_packet_allow_list = {
+      protos::pbzero::FtraceEvent::kTaskRenameFieldNumber,
+  };
+
+  protos::gen::TracePacket before;
+  AddTaskRename(before.mutable_ftrace_events(), 7, "old_comm", "new_comm_7");
+  AddTaskRename(before.mutable_ftrace_events(), 8, "old_comm", "new_comm_8");
+  AddTaskRename(before.mutable_ftrace_events(), 9, "old_comm", "new_comm_9");
+
+  auto before_str = before.SerializeAsString();
+  auto after_str = before_str;
+
+  ScrubFtraceEvents transform;
+  ASSERT_OK(transform.Transform(context, &after_str));
+
+  protos::gen::TracePacket after;
+  after.ParseFromString(after_str);
+
+  // Implementation detail: ScrubFtraceEvents may change entry order. The diff
+  // must be order independent. Sort the events by pid, this will make it easier
+  // to assert values.
+  auto events = after.ftrace_events().event();
+  std::sort(events.begin(), events.end(),
+            [](const auto& l, const auto& r) { return l.pid() < r.pid(); });
+
+  ASSERT_EQ(events.size(), 3u);
+
+  ASSERT_TRUE(events[0].has_task_rename());
+  ASSERT_EQ(events[0].task_rename().pid(), 7);
+  ASSERT_EQ(events[0].task_rename().oldcomm(), "old_comm");
+  ASSERT_EQ(events[0].task_rename().newcomm(), "new_comm_7");
+
+  ASSERT_TRUE(events[1].has_task_rename());
+  ASSERT_EQ(events[1].task_rename().pid(), 8);
+  ASSERT_EQ(events[1].task_rename().oldcomm(), "old_comm");
+  ASSERT_EQ(events[1].task_rename().newcomm(), "new_comm_8");
+
+  ASSERT_TRUE(events[2].has_task_rename());
+  ASSERT_EQ(events[2].task_rename().pid(), 9);
+  ASSERT_EQ(events[2].task_rename().oldcomm(), "old_comm");
+  ASSERT_EQ(events[2].task_rename().newcomm(), "new_comm_9");
+}
+
+// Only the specific non-allowed events should be removed from the event list.
+TEST_F(ScrubFtraceEventsTest, OnlyDropsNotAllowedEvents) {
+  // AddTaskRename >> Keep
+  // AddClockSetRate >> Drop
+  protos::gen::TracePacket original_packet;
+  AddTaskRename(original_packet.mutable_ftrace_events(), 7, "old_comm",
+                "new_comm_7");
+  AddClockSetRate(original_packet.mutable_ftrace_events(), 0, "cool cpu name",
+                  1);
+  AddTaskRename(original_packet.mutable_ftrace_events(), 8, "old_comm",
+                "new_comm_8");
+  AddTaskRename(original_packet.mutable_ftrace_events(), 9, "old_comm",
+                "new_comm_9");
+  auto packet = original_packet.SerializeAsString();
+
+  Context context;
+  context.ftrace_packet_allow_list = {
+      protos::pbzero::FtraceEvent::kTaskRenameFieldNumber};
+
+  ScrubFtraceEvents transform;
+  ASSERT_OK(transform.Transform(context, &packet));
+
+  protos::gen::TracePacket modified_packet;
+  ASSERT_TRUE(modified_packet.ParseFromString(packet));
+
+  // Only the clock set rate event should have been removed (drop 1 of the 4
+  // events).
+  ASSERT_TRUE(modified_packet.has_ftrace_events());
+  ASSERT_EQ(modified_packet.ftrace_events().event_size(), 3);
+
+  // All ftrace events should be rename events.
+  const auto& events = modified_packet.ftrace_events().event();
+
+  ASSERT_TRUE(events.at(0).has_task_rename());
+  ASSERT_EQ(events.at(0).task_rename().pid(), 7);
+
+  ASSERT_TRUE(events.at(1).has_task_rename());
+  ASSERT_EQ(events.at(1).task_rename().pid(), 8);
+
+  ASSERT_TRUE(events.at(2).has_task_rename());
+  ASSERT_EQ(events.at(2).task_rename().pid(), 9);
+}
+
+}  // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/scrub_trace_packet.h b/src/trace_redaction/scrub_trace_packet.h
index fbf89ca..75c5722 100644
--- a/src/trace_redaction/scrub_trace_packet.h
+++ b/src/trace_redaction/scrub_trace_packet.h
@@ -21,8 +21,17 @@
 
 namespace perfetto::trace_redaction {
 
-// Drops whole trace packets based on an allow-list (e.g. retain ProcessTree
-// packets).
+// TODO(vaage): This primitive DOES NOT handle redacting sched_switch and
+// sched_waking ftrace events. These events contain a large amount of "to be
+// redacted" information AND there are a high quantity of them AND they are
+// large packets. As such, this primitive is not enough and an ADDITIONAL
+// primitive is required.
+
+// Goes through individual ftrace packs and drops the ftrace packets from the
+// trace packet without modifying the surround fields.
+//
+// ScrubTracePacket does not respect field order - i.e. the field order going
+// may not match the field order going out.
 class ScrubTracePacket final : public TransformPrimitive {
  public:
   base::Status Transform(const Context& context,
diff --git a/src/trace_redaction/trace_redaction_framework.h b/src/trace_redaction/trace_redaction_framework.h
index 76b8594..d22ecd2 100644
--- a/src/trace_redaction/trace_redaction_framework.h
+++ b/src/trace_redaction/trace_redaction_framework.h
@@ -117,6 +117,45 @@
   // Because "data" is a "one of", if no field in "trace_packet_allow_list" can
   // be found, it packet should be removed.
   base::FlatSet<uint32_t> trace_packet_allow_list;
+
+  // Ftrace packets contain a "one of" entry called "event". Within the scope of
+  // a ftrace event, the event can be considered the payload and other other
+  // values can be considered metadata (e.g. timestamp and pid).
+  //
+  // A ftrace event should be removed if:
+  //
+  //  ... we know it contains too much sensitive information
+  //
+  //  ... we know it contains sensitive information and we have some ideas on
+  //      to remove it, but don't have the resources to do it right now (e.g.
+  //      print).
+  //
+  //  ... we don't see value in including it
+  //
+  // "ftrace_packet_allow_list" contains field ids of ftrace packets that we
+  // want to pass onto later transformations. An example would be:
+  //
+  //  ... kSchedWakingFieldNumber because it contains cpu activity information
+  //
+  // Compared against track days, the rules around removing ftrace packets are
+  // complicated because...
+  //
+  //  packet {
+  //    ftrace_packets {  <-- ONE-OF    (1)
+  //      event {         <-- REPEATED  (2)
+  //        cpu_idle { }  <-- ONE-OF    (3)
+  //      }
+  //      event { ... }
+  //    }
+  //  }
+  //
+  //  1.  A ftrace packet will populate the one-of slot in the trace packet.
+  //
+  //  2.  A ftrace packet can have multiple events
+  //
+  //  3.  In this example, a cpu_idle event populates the one-of slot in the
+  //      ftrace event
+  base::FlatSet<uint32_t> ftrace_packet_allow_list;
 };
 
 // Responsible for extracting low-level data from the trace and storing it in
diff --git a/src/trace_redaction/trace_redactor.cc b/src/trace_redaction/trace_redactor.cc
index 94168e6..49fcf8f 100644
--- a/src/trace_redaction/trace_redactor.cc
+++ b/src/trace_redaction/trace_redactor.cc
@@ -161,17 +161,12 @@
       continue;
     }
 
-    protozero::HeapBuffered<> serializer;
-    auto* packet_message =
-        serializer->BeginNestedMessage<TracePacket>(Trace::kPacketFieldNumber);
-    packet_message->AppendRawProtoBytes(packet.data(), packet.size());
-    packet_message->Finalize();
-    serializer->Finalize();
+    protozero::HeapBuffered<protos::pbzero::Trace> serializer;
+    serializer->add_packet()->AppendRawProtoBytes(packet.data(), packet.size());
+    packet.assign(serializer.SerializeAsString());
 
-    auto encoded_packet = serializer.SerializeAsString();
-
-    if (const auto exported_data = base::WriteAll(
-            dest_fd.get(), encoded_packet.data(), encoded_packet.size());
+    if (const auto exported_data =
+            base::WriteAll(dest_fd.get(), packet.data(), packet.size());
         exported_data <= 0) {
       return base::ErrStatus("Failed to write redacted trace to disk");
     }
diff --git a/src/trace_redaction/trace_redactor_integrationtest.cc b/src/trace_redaction/trace_redactor_integrationtest.cc
index 50074b1..bd7847d 100644
--- a/src/trace_redaction/trace_redactor_integrationtest.cc
+++ b/src/trace_redaction/trace_redactor_integrationtest.cc
@@ -15,28 +15,34 @@
  */
 
 #include <cstdint>
-#include <memory>
 #include <string>
 #include <string_view>
 #include <vector>
 
 #include "perfetto/base/status.h"
 #include "perfetto/ext/base/file_utils.h"
-#include "perfetto/ext/base/temp_file.h"
+#include "src/base/test/status_matchers.h"
 #include "src/base/test/tmp_dir_tree.h"
 #include "src/base/test/utils.h"
 #include "src/trace_redaction/find_package_uid.h"
+#include "src/trace_redaction/populate_allow_lists.h"
 #include "src/trace_redaction/prune_package_list.h"
+#include "src/trace_redaction/scrub_ftrace_events.h"
+#include "src/trace_redaction/scrub_trace_packet.h"
 #include "src/trace_redaction/trace_redaction_framework.h"
 #include "src/trace_redaction/trace_redactor.h"
 #include "test/gtest_and_gmock.h"
 
 #include "protos/perfetto/trace/android/packages_list.pbzero.h"
+#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
+#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
 #include "protos/perfetto/trace/trace.pbzero.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
 
 namespace perfetto::trace_redaction {
 
 namespace {
+using FtraceEvent = protos::pbzero::FtraceEvent;
 using PackagesList = protos::pbzero::PackagesList;
 using PackageInfo = protos::pbzero::PackagesList::PackageInfo;
 using Trace = protos::pbzero::Trace;
@@ -55,6 +61,19 @@
  protected:
   void SetUp() override {
     src_trace_ = base::GetTestDataPath(std::string(kTracePath));
+
+    // Add every primitive to the redactor. This should mirror the production
+    // configuration. This configuration may differ to help with verifying the
+    // results.
+    redactor_.collectors()->emplace_back(new FindPackageUid());
+    redactor_.builders()->emplace_back(new PopulateAllowlists());
+    redactor_.transformers()->emplace_back(new PrunePackageList());
+    redactor_.transformers()->emplace_back(new ScrubTracePacket());
+    redactor_.transformers()->emplace_back(new ScrubFtraceEvents());
+
+    // Set the package name to "just some package name". If a specific package
+    // name is needed, it should overwrite this value.
+    context_.package_name = "com.google.omadm.trigger";
   }
 
   const std::string& src_trace() const { return src_trace_; }
@@ -77,33 +96,80 @@
     return infos;
   }
 
+  static base::StatusOr<std::string> ReadRawTrace(const std::string& path) {
+    std::string redacted_buffer;
+
+    if (base::ReadFile(path, &redacted_buffer)) {
+      return redacted_buffer;
+    }
+
+    return base::ErrStatus("Failed to read %s", path.c_str());
+  }
+
+  // NOTE - this will include fields like "timestamp" and "pid".
+  static void GetEventFields(const Trace::Decoder& trace,
+                             base::FlatSet<uint32_t>* set) {
+    for (auto packet_it = trace.packet(); packet_it; ++packet_it) {
+      TracePacket::Decoder packet(*packet_it);
+
+      if (!packet.has_ftrace_events()) {
+        continue;
+      }
+
+      protos::pbzero::FtraceEventBundle::Decoder bundle(packet.ftrace_events());
+
+      if (!bundle.has_event()) {
+        continue;
+      }
+
+      for (auto events_it = bundle.event(); events_it; ++events_it) {
+        protozero::ProtoDecoder event(*events_it);
+
+        for (auto event_it = event.ReadField(); event_it.valid();
+             event_it = event.ReadField()) {
+          set->insert(event_it.id());
+        }
+      }
+    }
+  }
+
+  static base::StatusOr<protozero::ConstBytes> FindFirstFtraceEvents(
+      const Trace::Decoder& trace) {
+    for (auto packet_it = trace.packet(); packet_it; ++packet_it) {
+      TracePacket::Decoder packet(*packet_it);
+
+      if (packet.has_ftrace_events()) {
+        return packet.ftrace_events();
+      }
+    }
+
+    return base::ErrStatus("Failed to find ftrace events");
+  }
+
   std::string src_trace_;
   base::TmpDirTree tmp_dir_;
+
+  Context context_;
+  TraceRedactor redactor_;
 };
 
 TEST_F(TraceRedactorIntegrationTest, FindsPackageAndFiltersPackageList) {
-  TraceRedactor redaction;
-  redaction.collectors()->emplace_back(new FindPackageUid());
-  redaction.transformers()->emplace_back(new PrunePackageList());
+  context_.package_name = "com.Unity.com.unity.multiplayer.samples.coop";
 
-  Context context;
-  context.package_name = "com.Unity.com.unity.multiplayer.samples.coop";
-
-  auto result = redaction.Redact(
-      src_trace(), tmp_dir_.AbsolutePath("dst.pftrace"), &context);
+  auto result = redactor_.Redact(
+      src_trace(), tmp_dir_.AbsolutePath("dst.pftrace"), &context_);
   tmp_dir_.TrackFile("dst.pftrace");
 
-  ASSERT_TRUE(result.ok()) << result.message();
+  ASSERT_OK(result);
 
-  std::string redacted_buffer;
-  ASSERT_TRUE(
-      base::ReadFile(tmp_dir_.AbsolutePath("dst.pftrace"), &redacted_buffer));
+  ASSERT_OK_AND_ASSIGN(auto redacted_buffer,
+                       ReadRawTrace(tmp_dir_.AbsolutePath("dst.pftrace")));
 
   Trace::Decoder redacted_trace(redacted_buffer);
   std::vector<protozero::ConstBytes> infos = GetPackageInfos(redacted_trace);
 
-  ASSERT_TRUE(context.package_uid.has_value());
-  ASSERT_EQ(NormalizeUid(context.package_uid.value()),
+  ASSERT_TRUE(context_.package_uid.has_value());
+  ASSERT_EQ(NormalizeUid(context_.package_uid.value()),
             NormalizeUid(kPackageUid));
 
   // It is possible for two packages_list to appear in the trace. The
@@ -132,21 +198,15 @@
 // the package list, so there is no way to differentiate these packages (only
 // the uid is used later), so each entry should remain.
 TEST_F(TraceRedactorIntegrationTest, RetainsAllInstancesOfUid) {
-  TraceRedactor redaction;
-  redaction.collectors()->emplace_back(new FindPackageUid());
-  redaction.transformers()->emplace_back(new PrunePackageList());
+  context_.package_name = "com.google.android.networkstack.tethering";
 
-  Context context;
-  context.package_name = "com.google.android.networkstack.tethering";
-
-  auto result = redaction.Redact(
-      src_trace(), tmp_dir_.AbsolutePath("dst.pftrace"), &context);
+  auto result = redactor_.Redact(
+      src_trace(), tmp_dir_.AbsolutePath("dst.pftrace"), &context_);
   tmp_dir_.TrackFile("dst.pftrace");
-  ASSERT_TRUE(result.ok()) << result.message();
+  ASSERT_OK(result);
 
-  std::string redacted_buffer;
-  ASSERT_TRUE(
-      base::ReadFile(tmp_dir_.AbsolutePath("dst.pftrace"), &redacted_buffer));
+  ASSERT_OK_AND_ASSIGN(auto redacted_buffer,
+                       ReadRawTrace(tmp_dir_.AbsolutePath("dst.pftrace")));
 
   Trace::Decoder redacted_trace(redacted_buffer);
   std::vector<protozero::ConstBytes> infos = GetPackageInfos(redacted_trace);
@@ -174,5 +234,110 @@
   ASSERT_EQ(package_names[7], "com.google.android.networkstack.tethering");
 }
 
+// Makes sure all not-allowed ftrace event is removed from a trace.
+TEST_F(TraceRedactorIntegrationTest, RemovesFtraceEvents) {
+  auto pre_redaction_file = src_trace();
+  auto post_redaction_file = tmp_dir_.AbsolutePath("dst.pftrace");
+
+  // We know that there are two oom score updates in the test trace. These
+  // events are not in the allowlist and should be dropped.
+  auto pre_redaction_buffer = ReadRawTrace(pre_redaction_file);
+  ASSERT_OK(pre_redaction_buffer) << pre_redaction_buffer.status().message();
+  Trace::Decoder pre_redaction_trace(*pre_redaction_buffer);
+
+  base::FlatSet<uint32_t> pre_redaction_event_types;
+  GetEventFields(pre_redaction_trace, &pre_redaction_event_types);
+  ASSERT_GT(pre_redaction_event_types.count(
+                FtraceEvent::kOomScoreAdjUpdateFieldNumber),
+            0u);
+
+  auto result =
+      redactor_.Redact(pre_redaction_file, post_redaction_file, &context_);
+  tmp_dir_.TrackFile("dst.pftrace");
+  ASSERT_OK(result) << result.message();
+
+  auto post_redaction_buffer = ReadRawTrace(post_redaction_file);
+  ASSERT_OK(post_redaction_buffer) << post_redaction_buffer.status().message();
+  Trace::Decoder post_redaction_trace(*post_redaction_buffer);
+
+  base::FlatSet<uint32_t> post_redaction_event_types;
+  GetEventFields(post_redaction_trace, &post_redaction_event_types);
+  ASSERT_EQ(post_redaction_event_types.count(
+                FtraceEvent::kOomScoreAdjUpdateFieldNumber),
+            0u);
+}
+
+// When a event is dropped from ftrace_events, only that event should be droped,
+// the other events in the ftrace_events should be retained.
+TEST_F(TraceRedactorIntegrationTest,
+       RetainsFtraceEventsWhenRemovingFtraceEvent) {
+  auto pre_redaction_file = src_trace();
+  auto post_redaction_file = tmp_dir_.AbsolutePath("dst.pftrace");
+
+  auto pre_redaction_buffer = ReadRawTrace(pre_redaction_file);
+  ASSERT_OK(pre_redaction_buffer) << pre_redaction_buffer.status().message();
+
+  Trace::Decoder pre_redaction_trace(*pre_redaction_buffer);
+
+  auto pre_redaction_first_events = FindFirstFtraceEvents(pre_redaction_trace);
+  ASSERT_OK(pre_redaction_first_events)
+      << pre_redaction_first_events.status().message();
+
+  auto result =
+      redactor_.Redact(pre_redaction_file, post_redaction_file, &context_);
+  tmp_dir_.TrackFile("dst.pftrace");
+  ASSERT_OK(result) << result.message();
+
+  auto post_redaction_buffer = ReadRawTrace(post_redaction_file);
+  ASSERT_OK(post_redaction_buffer) << post_redaction_buffer.status().message();
+
+  Trace::Decoder post_redaction_trace(*post_redaction_buffer);
+
+  auto post_redaction_ftrace_events =
+      FindFirstFtraceEvents(post_redaction_trace);
+  ASSERT_OK(post_redaction_ftrace_events)
+      << post_redaction_ftrace_events.status().message();
+
+  base::FlatSet<uint32_t> events_before;
+  GetEventFields(pre_redaction_trace, &events_before);
+  ASSERT_EQ(events_before.size(), 14u);
+  ASSERT_TRUE(events_before.count(FtraceEvent::kTimestampFieldNumber));
+  ASSERT_TRUE(events_before.count(FtraceEvent::kPidFieldNumber));
+  ASSERT_TRUE(events_before.count(FtraceEvent::kPrintFieldNumber));
+  ASSERT_TRUE(events_before.count(FtraceEvent::kSchedSwitchFieldNumber));
+  ASSERT_TRUE(events_before.count(FtraceEvent::kCpuFrequencyFieldNumber));
+  ASSERT_TRUE(events_before.count(FtraceEvent::kCpuIdleFieldNumber));
+  ASSERT_TRUE(events_before.count(FtraceEvent::kSchedWakeupFieldNumber));
+  ASSERT_TRUE(events_before.count(FtraceEvent::kSchedWakingFieldNumber));
+  ASSERT_TRUE(events_before.count(FtraceEvent::kSchedWakeupNewFieldNumber));
+  ASSERT_TRUE(events_before.count(FtraceEvent::kTaskNewtaskFieldNumber));
+  ASSERT_TRUE(events_before.count(FtraceEvent::kTaskRenameFieldNumber));
+  ASSERT_TRUE(events_before.count(FtraceEvent::kSchedProcessExitFieldNumber));
+  ASSERT_TRUE(events_before.count(FtraceEvent::kSchedProcessFreeFieldNumber));
+  ASSERT_TRUE(events_before.count(FtraceEvent::kOomScoreAdjUpdateFieldNumber));
+
+  base::FlatSet<uint32_t> events_after;
+  GetEventFields(post_redaction_trace, &events_after);
+  ASSERT_EQ(events_after.size(), 9u);
+
+  // Retained.
+  ASSERT_TRUE(events_after.count(FtraceEvent::kTimestampFieldNumber));
+  ASSERT_TRUE(events_after.count(FtraceEvent::kPidFieldNumber));
+  ASSERT_TRUE(events_after.count(FtraceEvent::kSchedSwitchFieldNumber));
+  ASSERT_TRUE(events_after.count(FtraceEvent::kCpuFrequencyFieldNumber));
+  ASSERT_TRUE(events_after.count(FtraceEvent::kCpuIdleFieldNumber));
+  ASSERT_TRUE(events_after.count(FtraceEvent::kSchedWakingFieldNumber));
+  ASSERT_TRUE(events_after.count(FtraceEvent::kTaskNewtaskFieldNumber));
+  ASSERT_TRUE(events_after.count(FtraceEvent::kTaskRenameFieldNumber));
+  ASSERT_TRUE(events_after.count(FtraceEvent::kSchedProcessFreeFieldNumber));
+
+  // Dropped.
+  ASSERT_FALSE(events_after.count(FtraceEvent::kPrintFieldNumber));
+  ASSERT_FALSE(events_after.count(FtraceEvent::kSchedWakeupFieldNumber));
+  ASSERT_FALSE(events_after.count(FtraceEvent::kSchedWakeupNewFieldNumber));
+  ASSERT_FALSE(events_after.count(FtraceEvent::kSchedProcessExitFieldNumber));
+  ASSERT_FALSE(events_after.count(FtraceEvent::kOomScoreAdjUpdateFieldNumber));
+}
+
 }  // namespace
 }  // namespace perfetto::trace_redaction
diff --git a/src/traced/probes/ftrace/event_info.cc b/src/traced/probes/ftrace/event_info.cc
index 4c028d6..46b7d24 100644
--- a/src/traced/probes/ftrace/event_info.cc
+++ b/src/traced/probes/ftrace/event_info.cc
@@ -7701,6 +7701,19 @@
        kUnsetFtraceId,
        66,
        kUnsetSize},
+      {"rpm_status",
+       "rpm",
+       {
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "name", 1, ProtoSchemaType::kString,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "status", 2, ProtoSchemaType::kInt32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+       },
+       kUnsetFtraceId,
+       489,
+       kUnsetSize},
       {"tracing_mark_write",
        "samsung",
        {
diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/rpm/rpm_status/format b/src/traced/probes/ftrace/test/data/synthetic/events/rpm/rpm_status/format
new file mode 100644
index 0000000..0d210de
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/synthetic/events/rpm/rpm_status/format
@@ -0,0 +1,12 @@
+name: rpm_status
+ID: 218
+format:
+	field:unsigned short common_type;	offset:0;	size:2;	signed:0;
+	field:unsigned char common_flags;	offset:2;	size:1;	signed:0;
+	field:unsigned char common_preempt_count;	offset:3;	size:1;	signed:0;
+	field:int common_pid;	offset:4;	size:4;	signed:1;
+
+	field:__data_loc char[] name;	offset:8;	size:4;	signed:0;
+	field:int status;	offset:12;	size:4;	signed:1;
+
+print fmt: "%s status=%s", __get_str(name), __print_symbolic(REC->status, { -1, "RPM_INVALID" }, { 0, "RPM_ACTIVE" }, { 1, "RPM_RESUMING" }, { 2, "RPM_SUSPENDED" }, { 3, "RPM_SUSPENDING" })
diff --git a/src/tracing/internal/in_process_tracing_backend.cc b/src/tracing/internal/in_process_tracing_backend.cc
index 1c58c0d..7d2d7e6 100644
--- a/src/tracing/internal/in_process_tracing_backend.cc
+++ b/src/tracing/internal/in_process_tracing_backend.cc
@@ -43,7 +43,8 @@
   return instance;
 }
 
-InProcessTracingBackend::InProcessTracingBackend() {}
+InProcessTracingBackend::InProcessTracingBackend() = default;
+InProcessTracingBackend::~InProcessTracingBackend() = default;
 
 std::unique_ptr<ProducerEndpoint> InProcessTracingBackend::ConnectProducer(
     const ConnectProducerArgs& args) {
diff --git a/src/tracing/service/tracing_service_impl.cc b/src/tracing/service/tracing_service_impl.cc
index a72754c..b57f135 100644
--- a/src/tracing/service/tracing_service_impl.cc
+++ b/src/tracing/service/tracing_service_impl.cc
@@ -16,12 +16,11 @@
 
 #include "src/tracing/service/tracing_service_impl.h"
 
-#include <errno.h>
 #include <limits.h>
-#include <stdint.h>
 #include <string.h>
 
 #include <cinttypes>
+#include <cstdint>
 #include <limits>
 #include <optional>
 #include <regex>
@@ -59,7 +58,7 @@
 #include "perfetto/ext/base/file_utils.h"
 #include "perfetto/ext/base/metatrace.h"
 #include "perfetto/ext/base/string_utils.h"
-#include "perfetto/ext/base/temp_file.h"
+#include "perfetto/ext/base/string_view.h"
 #include "perfetto/ext/base/utils.h"
 #include "perfetto/ext/base/uuid.h"
 #include "perfetto/ext/base/version.h"
@@ -156,8 +155,8 @@
 // Format (by bit range):
 // [   31 ][         30 ][             29:20 ][            19:10 ][        9:0]
 // [unused][has flush id][num chunks to patch][num chunks to move][producer id]
-static int32_t EncodeCommitDataRequest(ProducerID producer_id,
-                                       const CommitDataRequest& req_untrusted) {
+int32_t EncodeCommitDataRequest(ProducerID producer_id,
+                                const CommitDataRequest& req_untrusted) {
   uint32_t cmov = static_cast<uint32_t>(req_untrusted.chunks_to_move_size());
   uint32_t cpatch = static_cast<uint32_t>(req_untrusted.chunks_to_patch_size());
   uint32_t has_flush_id = req_untrusted.flush_request_id() != 0;
@@ -1015,7 +1014,7 @@
     auto range = data_sources_.equal_range(cfg_data_source.config().name());
     for (auto it = range.first; it != range.second; it++) {
       TraceConfig::ProducerConfig producer_config;
-      for (auto& config : cfg.producers()) {
+      for (const auto& config : cfg.producers()) {
         if (GetProducer(it->second.producer_id)->name_ ==
             config.producer_name()) {
           producer_config = config;
@@ -1184,7 +1183,7 @@
       // If it wasn't previously setup, set it up now.
       // (The per-producer config is optional).
       TraceConfig::ProducerConfig producer_config;
-      for (auto& config : tracing_session->config.producers()) {
+      for (const auto& config : tracing_session->config.producers()) {
         if (producer->name_ == config.producer_name()) {
           producer_config = config;
           break;
@@ -1796,13 +1795,13 @@
     data_source_instances[producer_id].push_back(ds_inst.instance_id);
   }
   FlushDataSourceInstances(tracing_session, timeout_ms, data_source_instances,
-                           callback, flush_flags);
+                           std::move(callback), flush_flags);
 }
 
 void TracingServiceImpl::FlushDataSourceInstances(
     TracingSession* tracing_session,
     uint32_t timeout_ms,
-    std::map<ProducerID, std::vector<DataSourceInstanceID>>
+    const std::map<ProducerID, std::vector<DataSourceInstanceID>>&
         data_source_instances,
     ConsumerEndpoint::FlushCallback callback,
     FlushFlags flush_flags) {
@@ -2706,7 +2705,7 @@
     }
 
     TraceConfig::ProducerConfig producer_config;
-    for (auto& config : tracing_session.config.producers()) {
+    for (const auto& config : tracing_session.config.producers()) {
       if (producer->name_ == config.producer_name()) {
         producer_config = config;
         break;
@@ -3519,6 +3518,8 @@
     utsname_info->set_machine(uname_info.machine);
     utsname_info->set_release(uname_info.release);
   }
+  info->set_page_size(static_cast<uint32_t>(sysconf(_SC_PAGESIZE)));
+  info->set_num_cpus(static_cast<uint32_t>(sysconf(_SC_NPROCESSORS_CONF)));
 #endif  // !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
   std::string fingerprint_value = base::GetAndroidProp("ro.build.fingerprint");
@@ -3535,8 +3536,6 @@
   } else {
     PERFETTO_ELOG("Unable to read ro.build.version.sdk");
   }
-  info->set_hz(sysconf(_SC_CLK_TCK));
-  info->set_page_size(static_cast<uint32_t>(sysconf(_SC_PAGESIZE)));
 #endif  // PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
   packet->set_trusted_uid(static_cast<int32_t>(uid_));
   packet->set_trusted_packet_sequence_id(kServicePacketSequenceID);
@@ -3573,7 +3572,7 @@
               return a.first < b.first;
             });
 
-  for (const auto& pair : timestamped_packets)
+  for (auto& pair : timestamped_packets)
     SerializeAndAppendPacket(packets, std::move(pair.second));
 }
 
@@ -3608,14 +3607,14 @@
       auto* sync_exchange_msg = remote_clock_sync->add_synced_clocks();
 
       auto* client_snapshots = sync_exchange_msg->set_client_clocks();
-      for (auto& client_clock : sync_exchange.client_clocks) {
+      for (const auto& client_clock : sync_exchange.client_clocks) {
         auto* clock = client_snapshots->add_clocks();
         clock->set_clock_id(client_clock.clock_id);
         clock->set_timestamp(client_clock.timestamp);
       }
 
       auto* host_snapshots = sync_exchange_msg->set_host_clocks();
-      for (auto& host_clock : sync_exchange.host_clocks) {
+      for (const auto& host_clock : sync_exchange.host_clocks) {
         auto* clock = host_snapshots->add_clocks();
         clock->set_clock_id(host_clock.clock_id);
         clock->set_timestamp(host_clock.timestamp);
@@ -3805,7 +3804,7 @@
 std::map<ProducerID, std::vector<DataSourceInstanceID>>
 TracingServiceImpl::GetFlushableDataSourceInstancesForBuffers(
     TracingSession* session,
-    std::set<BufferID> bufs) {
+    const std::set<BufferID>& bufs) {
   std::map<ProducerID, std::vector<DataSourceInstanceID>> data_source_instances;
 
   for (const auto& [producer_id, ds_inst] : session->data_source_instances) {
@@ -3825,7 +3824,7 @@
 
 void TracingServiceImpl::OnFlushDoneForClone(TracingSessionID tsid,
                                              PendingCloneID clone_id,
-                                             std::set<BufferID> buf_ids,
+                                             const std::set<BufferID>& buf_ids,
                                              bool final_flush_outcome) {
   TracingSession* src = GetTracingSession(tsid);
   // The session might be gone by the time we try to clone it.
@@ -3881,7 +3880,7 @@
 
 bool TracingServiceImpl::DoCloneBuffers(
     TracingSession* src,
-    std::set<BufferID> buf_ids,
+    const std::set<BufferID>& buf_ids,
     std::vector<std::unique_ptr<TraceBuffer>>* buf_snaps) {
   PERFETTO_DCHECK(src->num_buffers() == src->config.buffers().size());
   buf_snaps->resize(src->buffers_index.size());
@@ -4273,7 +4272,7 @@
   int num_started = 0;
   for (const auto& kv : sessions)
     num_started += kv.second.state == TracingSession::State::STARTED ? 1 : 0;
-  svc_state.set_num_sessions_started(static_cast<int>(num_started));
+  svc_state.set_num_sessions_started(num_started);
 
   for (const auto& kv : service_->producers_) {
     if (args.sessions_only)
diff --git a/src/tracing/service/tracing_service_impl.h b/src/tracing/service/tracing_service_impl.h
index 5a583ba..a20fa68 100644
--- a/src/tracing/service/tracing_service_impl.h
+++ b/src/tracing/service/tracing_service_impl.h
@@ -21,7 +21,6 @@
 #include <functional>
 #include <map>
 #include <memory>
-#include <mutex>
 #include <optional>
 #include <random>
 #include <set>
@@ -332,8 +331,8 @@
   void ApplyChunkPatches(ProducerID,
                          const std::vector<CommitDataRequest::ChunkToPatch>&);
   void NotifyFlushDoneForProducer(ProducerID, FlushRequestID);
-  void NotifyDataSourceStarted(ProducerID, const DataSourceInstanceID);
-  void NotifyDataSourceStopped(ProducerID, const DataSourceInstanceID);
+  void NotifyDataSourceStarted(ProducerID, DataSourceInstanceID);
+  void NotifyDataSourceStopped(ProducerID, DataSourceInstanceID);
   void ActivateTriggers(ProducerID, const std::vector<std::string>& triggers);
 
   // Called by ConsumerEndpointImpl.
@@ -804,14 +803,14 @@
   void FlushDataSourceInstances(
       TracingSession*,
       uint32_t timeout_ms,
-      std::map<ProducerID, std::vector<DataSourceInstanceID>>,
+      const std::map<ProducerID, std::vector<DataSourceInstanceID>>&,
       ConsumerEndpoint::FlushCallback,
       FlushFlags);
   std::map<ProducerID, std::vector<DataSourceInstanceID>>
   GetFlushableDataSourceInstancesForBuffers(TracingSession*,
-                                            std::set<BufferID>);
+                                            const std::set<BufferID>&);
   bool DoCloneBuffers(TracingSession*,
-                      std::set<BufferID>,
+                      const std::set<BufferID>&,
                       std::vector<std::unique_ptr<TraceBuffer>>*);
   base::Status FinishCloneSession(ConsumerEndpointImpl*,
                                   TracingSessionID,
@@ -821,7 +820,7 @@
                                   base::Uuid*);
   void OnFlushDoneForClone(TracingSessionID src_tsid,
                            PendingCloneID clone_id,
-                           std::set<BufferID> buf_ids,
+                           const std::set<BufferID>& buf_ids,
                            bool final_flush_outcome);
 
   // Returns true if `*tracing_session` is waiting for a trigger that hasn't
diff --git a/test/cts/heapprofd_java_test_cts.cc b/test/cts/heapprofd_java_test_cts.cc
index b6e46cd..1178c3e 100644
--- a/test/cts/heapprofd_java_test_cts.cc
+++ b/test/cts/heapprofd_java_test_cts.cc
@@ -133,7 +133,7 @@
   trigger_config->set_trigger_timeout_ms(60000);
   auto* oom_trigger = trigger_config->add_triggers();
   oom_trigger->set_name("com.android.telemetry.art-outofmemory");
-  oom_trigger->set_stop_delay_ms(10000);
+  oom_trigger->set_stop_delay_ms(1000);
 
   auto* ds_config = trace_config.add_data_sources()->mutable_config();
   ds_config->set_name("android.java_hprof.oom");
diff --git a/test/cts/heapprofd_test_cts.cc b/test/cts/heapprofd_test_cts.cc
index 99eeeac..6c87cf4 100644
--- a/test/cts/heapprofd_test_cts.cc
+++ b/test/cts/heapprofd_test_cts.cc
@@ -22,6 +22,7 @@
 #include <random>
 
 #include "perfetto/base/logging.h"
+#include "perfetto/ext/base/string_utils.h"
 #include "perfetto/tracing/core/data_source_config.h"
 #include "src/base/test/test_task_runner.h"
 #include "test/android_test_utils.h"
@@ -46,6 +47,11 @@
 static_assert(kExpectedIndividualAllocSz > kTestSamplingInterval,
               "kTestSamplingInterval invalid");
 
+// Path in the app external directory where the app writes an interation
+// counter. It is used to wait for the test apps to actually perform
+// allocations.
+constexpr std::string_view kReportCyclePath = "report_cycle.txt";
+
 // Activity that runs a JNI thread that repeatedly calls
 // malloc(kExpectedIndividualAllocSz).
 static char kMallocActivity[] = "MainActivity";
@@ -65,6 +71,48 @@
   return result;
 }
 
+std::optional<int64_t> ReadInt64FromFile(const std::string& path) {
+  std::string contents;
+  if (!base::ReadFile(path, &contents)) {
+    return std::nullopt;
+  }
+  return base::StringToInt64(contents);
+}
+
+bool WaitForAppAllocationCycle(const std::string& app_name, size_t timeout_ms) {
+  const size_t sleep_per_attempt_us = 100 * 1000;
+  const size_t max_attempts = timeout_ms * 1000 / sleep_per_attempt_us;
+
+  std::string path = std::string("/sdcard/Android/data/") + app_name +
+                     std::string("/files/") + std::string(kReportCyclePath);
+
+  for (size_t attempts = 0; attempts < max_attempts;) {
+    int64_t first_value;
+    for (; attempts < max_attempts; attempts++) {
+      std::optional<int64_t> val = ReadInt64FromFile(path);
+      if (val) {
+        first_value = *val;
+        break;
+      }
+      base::SleepMicroseconds(sleep_per_attempt_us);
+    }
+
+    for (; attempts < max_attempts; attempts++) {
+      std::optional<int64_t> val = ReadInt64FromFile(path);
+      if (!val || *val < first_value) {
+        break;
+      }
+      if (*val >= first_value + 2) {
+        // We've observed the counter being incremented twice. We can be sure
+        // that the app has gone through a full allocation cycle.
+        return true;
+      }
+      base::SleepMicroseconds(sleep_per_attempt_us);
+    }
+  }
+  return false;
+}
+
 // Starts the activity `activity` of the app `app_name` and later starts
 // recording a trace with the allocations in `heap_names`.
 //
@@ -95,7 +143,6 @@
 
   TraceConfig trace_config;
   trace_config.add_buffers()->set_size_kb(10 * 1024);
-  trace_config.set_duration_ms(8000);
   trace_config.set_unique_session_name(RandomSessionName().c_str());
 
   auto* ds_config = trace_config.add_data_sources()->mutable_config();
@@ -114,6 +161,10 @@
 
   // start tracing
   helper.StartTracing(trace_config);
+
+  EXPECT_TRUE(WaitForAppAllocationCycle(app_name, /*timeout_ms=*/10000));
+
+  helper.DisableTracing();
   helper.WaitForTracingDisabled();
   helper.ReadData();
   helper.WaitForReadData();
@@ -148,7 +199,6 @@
 
   TraceConfig trace_config;
   trace_config.add_buffers()->set_size_kb(10 * 1024);
-  trace_config.set_duration_ms(8000);
   trace_config.set_enable_extra_guardrails(enable_extra_guardrails);
   trace_config.set_unique_session_name(RandomSessionName().c_str());
 
@@ -174,6 +224,9 @@
                    /*delay_ms=*/100);
   task_runner.RunUntilCheckpoint("target.app.running", 10000 /*ms*/);
 
+  EXPECT_TRUE(WaitForAppAllocationCycle(app_name, /*timeout_ms=*/10000));
+
+  helper.DisableTracing();
   helper.WaitForTracingDisabled();
   helper.ReadData();
   helper.WaitForReadData();
diff --git a/test/cts/test_apps/jni/target.cc b/test/cts/test_apps/jni/target.cc
index 8f847c4..9b843a2 100644
--- a/test/cts/test_apps/jni/target.cc
+++ b/test/cts/test_apps/jni/target.cc
@@ -15,8 +15,13 @@
  */
 
 #include <jni.h>
-#include <stdlib.h>
 #include <unistd.h>
+#include <cstdint>
+#include <cstdio>
+#include <cstdlib>
+
+#include <fstream>
+#include <limits>
 
 namespace {
 
@@ -24,15 +29,47 @@
 constexpr int kIndividualAllocSz = 4153;
 constexpr int kAllocationIntervalUs = 10 * 1000;
 
-__attribute__((noreturn)) void perfetto_test_allocations() {
-  for (;;) {
-    // volatile & use avoids builtin malloc optimizations
-    volatile char* x = static_cast<char*>(malloc(kIndividualAllocSz));
-    if (x) {
-      x[0] = '\0';
-      free(const_cast<char*>(x));
+// Increments a value in the text file `path`. The file is read by the CTS test
+// to observe the app progress.
+void ReportCycle(const char* path) {
+  int64_t value = 0;
+  {
+    // Read the previous value from the file (it might be from a separate
+    // execution of this app).
+    std::ifstream ifs(path);
+    if (ifs) {
+      ifs >> value;
     }
-    usleep(kAllocationIntervalUs);
+  }
+
+  std::string tmppath = std::string(path) + std::string(".tmp");
+  std::ofstream ofs(tmppath, std::ios::trunc);
+  if (value == std::numeric_limits<int64_t>::max()) {
+    value = std::numeric_limits<int64_t>::min();
+  } else {
+    value++;
+  }
+  ofs << value;
+  ofs.close();
+  if (!ofs) {
+    abort();
+  }
+  rename(tmppath.c_str(), path);
+}
+
+__attribute__((noreturn)) void perfetto_test_allocations(
+    const char* report_cycle_path) {
+  for (;;) {
+    for (size_t j = 0; j < 20; j++) {
+      // volatile & use avoids builtin malloc optimizations
+      volatile char* x = static_cast<char*>(malloc(kIndividualAllocSz));
+      if (x) {
+        x[0] = '\0';
+        free(const_cast<char*>(x));
+      }
+      usleep(kAllocationIntervalUs);
+    }
+    ReportCycle(report_cycle_path);
   }
 }
 
@@ -45,8 +82,13 @@
 }  // namespace
 
 extern "C" JNIEXPORT void JNICALL
-Java_android_perfetto_cts_app_MainActivity_runNative(JNIEnv*, jclass) {
-  perfetto_test_allocations();
+Java_android_perfetto_cts_app_MainActivity_runNative(
+    JNIEnv* env,
+    jclass,
+    jstring jreport_cycle_path) {
+  const char* path = env->GetStringUTFChars(jreport_cycle_path, NULL);
+  perfetto_test_allocations(path);
+  env->ReleaseStringUTFChars(jreport_cycle_path, NULL);
 }
 
 extern "C" JNIEXPORT void JNICALL
diff --git a/test/cts/test_apps/src/android/perfetto/cts/app/JavaAllocActivity.java b/test/cts/test_apps/src/android/perfetto/cts/app/JavaAllocActivity.java
index 077d32f..cffe3e4 100644
--- a/test/cts/test_apps/src/android/perfetto/cts/app/JavaAllocActivity.java
+++ b/test/cts/test_apps/src/android/perfetto/cts/app/JavaAllocActivity.java
@@ -19,9 +19,19 @@
 import android.app.Activity;
 import android.os.Bundle;
 
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
+import java.util.Scanner;
 import java.util.TreeMap;
 
 public class JavaAllocActivity extends Activity {
+    // Keep in sync with heapprofd_test_cts.cc
+    private static final String CYCLE_REPORT_PATH = "report_cycle.txt";
+
     @Override
     public void onCreate(Bundle state) {
         super.onCreate(state);
@@ -29,7 +39,7 @@
         new Thread(new Runnable() {
             public void run() {
                 try {
-                    runAllocationLoop();
+                    runAllocationLoop(getExternalFilesDir(null));
                 } catch (Exception ex) {
                     ex.printStackTrace();
                 }
@@ -40,12 +50,13 @@
     private static TreeMap treeMap = new TreeMap();
     private static long index = 0;
 
-    private static void runAllocationLoop() {
+    private static void runAllocationLoop(File external) throws IOException {
         for (;;) {
-            for (int i = 0; i < 1000; i++) {
+            for (int i = 0; i < 2000; i++) {
                 Object o = new Object();
                 treeMap.put(++index, o);
             }
+            reportCycle(external);
             try {
                 Thread.sleep(10);
             } catch (InterruptedException ignored) {
@@ -53,4 +64,27 @@
             treeMap.clear();
         }
     }
+
+    // Increments a value in a file in the app `external` directory. The file is read by the CTS
+    // test to observe the app progress.
+    private static void reportCycle(File external) throws IOException {
+        File f = new File(external, CYCLE_REPORT_PATH);
+        File tmp = new File(external, CYCLE_REPORT_PATH + ".tmp");
+        long val = 0;
+        // Read the previous value from the file (it might be from a separate execution of this
+        // app).
+        try (Scanner scanner = new Scanner(f)) {
+            if (scanner.hasNextLong()) {
+                val = scanner.nextLong();
+            }
+        } catch (FileNotFoundException ignored) {
+        }
+
+        try (FileWriter wr = new FileWriter(tmp)) {
+            wr.write(Long.toString(val + 1));
+        }
+
+        Files.move(tmp.toPath(), f.toPath(), StandardCopyOption.REPLACE_EXISTING,
+                StandardCopyOption.ATOMIC_MOVE);
+    }
 }
diff --git a/test/cts/test_apps/src/android/perfetto/cts/app/JavaOomActivity.java b/test/cts/test_apps/src/android/perfetto/cts/app/JavaOomActivity.java
index 8dc7bd1..b951347 100644
--- a/test/cts/test_apps/src/android/perfetto/cts/app/JavaOomActivity.java
+++ b/test/cts/test_apps/src/android/perfetto/cts/app/JavaOomActivity.java
@@ -27,6 +27,17 @@
     public void onCreate(Bundle state) {
         super.onCreate(state);
         new Thread(() -> {
+            // Inside ART, the perfetto hprof plugin (used here to dump the heap after an OOM) will
+            // use fork(). If other threads are holding some locks, the forked process might not be
+            // able to make progress, in some rare cases. This is a known limitation of the perfetto
+            // hprof plugin. In this test, we want to minimize the chance that other threads are
+            // holding locks when we cause an OOM. Unfortunately, it looks like the best way of
+            // doing this is sleeping for 500 milliseconds, allowing other threads spawned on app
+            // startup to finish what they're doing. See b/329124210 for more details.
+            try {
+                Thread.sleep(500);
+            } catch (InterruptedException ignored) {
+            }
             try {
                 Log.i(TAG, "Before the allocation");
                 // Try to allocate a big array: it should cause ART to run out of memory.
diff --git a/test/cts/test_apps/src/android/perfetto/cts/app/MainActivity.java b/test/cts/test_apps/src/android/perfetto/cts/app/MainActivity.java
index 0cabd16..efddd67 100644
--- a/test/cts/test_apps/src/android/perfetto/cts/app/MainActivity.java
+++ b/test/cts/test_apps/src/android/perfetto/cts/app/MainActivity.java
@@ -19,7 +19,12 @@
 import android.app.Activity;
 import android.os.Bundle;
 
+import java.io.File;
+
 public class MainActivity extends Activity {
+    // Keep in sync with heapprofd_test_cts.cc
+    private static final String CYCLE_REPORT_PATH = "report_cycle.txt";
+
     static {
         System.loadLibrary("perfettocts_native");
     }
@@ -31,7 +36,8 @@
         new Thread(new Runnable() {
             public void run() {
                 try {
-                    runNative();
+                    File running = new File(getExternalFilesDir(null), CYCLE_REPORT_PATH);
+                    runNative(running.getPath());
                 } catch (Exception ex) {
                     ex.printStackTrace();
                 }
@@ -39,5 +45,5 @@
         }).start();
     }
 
-    private static native void runNative();
+    private static native void runNative(String running);
 }
diff --git a/test/data/ui-screenshots/ui-android_trace_30s_expand_camera.png.sha256 b/test/data/ui-screenshots/ui-android_trace_30s_expand_camera.png.sha256
index 57df59f..221817f 100644
--- a/test/data/ui-screenshots/ui-android_trace_30s_expand_camera.png.sha256
+++ b/test/data/ui-screenshots/ui-android_trace_30s_expand_camera.png.sha256
@@ -1 +1 @@
-d12dc4f32f544a32bca4302314d7eb4984a3e6473d78298435396c7f81522564
\ No newline at end of file
+0d8e9f71b51633ce3927ff1ec94e1ce527c836df8f9fd748924ca46711185331
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-android_trace_30s_load.png.sha256 b/test/data/ui-screenshots/ui-android_trace_30s_load.png.sha256
index c8ecc01..ee40165 100644
--- a/test/data/ui-screenshots/ui-android_trace_30s_load.png.sha256
+++ b/test/data/ui-screenshots/ui-android_trace_30s_load.png.sha256
@@ -1 +1 @@
-2273d163c13ea0e296492ba30a2669c70d0e5ab050317c17f108a1836052c908
\ No newline at end of file
+2801bd62ac70dff544cd6515713d0933895b7405fe40c9929d34ee629bb4c5d2
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-chrome_missing_track_names_load.png.sha256 b/test/data/ui-screenshots/ui-chrome_missing_track_names_load.png.sha256
index 06a3e7b..95e467d 100644
--- a/test/data/ui-screenshots/ui-chrome_missing_track_names_load.png.sha256
+++ b/test/data/ui-screenshots/ui-chrome_missing_track_names_load.png.sha256
@@ -1 +1 @@
-22ca974ba2f262c4e421e95dba686746eb89564a8ff213e50f78c9424277587c
\ No newline at end of file
+8948301117c29da08b8b616501462a3012d3afd58e94bc8fad46eb8ed3199d23
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-chrome_rendering_desktop_expand_browser_proc.png.sha256 b/test/data/ui-screenshots/ui-chrome_rendering_desktop_expand_browser_proc.png.sha256
index a917bab..2e11179 100644
--- a/test/data/ui-screenshots/ui-chrome_rendering_desktop_expand_browser_proc.png.sha256
+++ b/test/data/ui-screenshots/ui-chrome_rendering_desktop_expand_browser_proc.png.sha256
@@ -1 +1 @@
-90505c5620c73940b88d163a73c79005fa8e81d097d0fcabf2585e56f7bc6a99
\ No newline at end of file
+cafa4721383e4b0d4425ec5a1b0f738a91d665196957b70e535df0ccc40e54b8
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-chrome_rendering_desktop_load.png.sha256 b/test/data/ui-screenshots/ui-chrome_rendering_desktop_load.png.sha256
index 89a9c57..8b12476 100644
--- a/test/data/ui-screenshots/ui-chrome_rendering_desktop_load.png.sha256
+++ b/test/data/ui-screenshots/ui-chrome_rendering_desktop_load.png.sha256
@@ -1 +1 @@
-d1a9ace55344d8a2850a8238b698892aca1ea2eacf80886420108043de8d3c56
\ No newline at end of file
+09f18560b7c44f2fb3fde256b6de77cf3e7688dd2f7213a2df73534b29cc6104
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-chrome_rendering_desktop_select_slice_with_flows.png.sha256 b/test/data/ui-screenshots/ui-chrome_rendering_desktop_select_slice_with_flows.png.sha256
index 470075f..32ead5f 100644
--- a/test/data/ui-screenshots/ui-chrome_rendering_desktop_select_slice_with_flows.png.sha256
+++ b/test/data/ui-screenshots/ui-chrome_rendering_desktop_select_slice_with_flows.png.sha256
@@ -1 +1 @@
-a97c6027c4bef120c4227a7ed32e6a6fb6b290938ef8d66652e6d369b7e5d157
\ No newline at end of file
+5f4d43c74cc565e94dc9e77c522dbe0d84c14a1f1c7d123a7e1c6b4d338c25cc
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_navigate_open_trace_from_url.png.sha256 b/test/data/ui-screenshots/ui-routing_navigate_open_trace_from_url.png.sha256
index a0ddb77..20f9f33 100644
--- a/test/data/ui-screenshots/ui-routing_navigate_open_trace_from_url.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_navigate_open_trace_from_url.png.sha256
@@ -1 +1 @@
-47cda8e8a9ed007945bff13d7c10fa4d33fc6e264c1198b2038a216559bec962
\ No newline at end of file
+c006f2550408db44cf35f8e2b18cae1592568fe7d8a5fcdc1d454eb40a7cf34e
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_access_subpage_then_go_back.png.sha256 b/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_access_subpage_then_go_back.png.sha256
index 5b79980..72a3447 100644
--- a/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_access_subpage_then_go_back.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_access_subpage_then_go_back.png.sha256
@@ -1 +1 @@
-ab93fa5646ab5e2bc63922ee8f6c6d7657f128fe7b5426df97d02f19c770a1bc
\ No newline at end of file
+f050c13da6a113631d22b24fcf839026cdb848815119e46f10d2472ce522a3a2
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_first_trace_from_url.png.sha256 b/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_first_trace_from_url.png.sha256
index a0ddb77..20f9f33 100644
--- a/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_first_trace_from_url.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_first_trace_from_url.png.sha256
@@ -1 +1 @@
-47cda8e8a9ed007945bff13d7c10fa4d33fc6e264c1198b2038a216559bec962
\ No newline at end of file
+c006f2550408db44cf35f8e2b18cae1592568fe7d8a5fcdc1d454eb40a7cf34e
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_second_trace_from_url.png.sha256 b/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_second_trace_from_url.png.sha256
index 5b79980..72a3447 100644
--- a/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_second_trace_from_url.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_second_trace_from_url.png.sha256
@@ -1 +1 @@
-ab93fa5646ab5e2bc63922ee8f6c6d7657f128fe7b5426df97d02f19c770a1bc
\ No newline at end of file
+f050c13da6a113631d22b24fcf839026cdb848815119e46f10d2472ce522a3a2
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_start_from_no_trace_go_back_to_first_trace.png.sha256 b/test/data/ui-screenshots/ui-routing_start_from_no_trace_go_back_to_first_trace.png.sha256
index 5b79980..72a3447 100644
--- a/test/data/ui-screenshots/ui-routing_start_from_no_trace_go_back_to_first_trace.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_start_from_no_trace_go_back_to_first_trace.png.sha256
@@ -1 +1 @@
-ab93fa5646ab5e2bc63922ee8f6c6d7657f128fe7b5426df97d02f19c770a1bc
\ No newline at end of file
+f050c13da6a113631d22b24fcf839026cdb848815119e46f10d2472ce522a3a2
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_invalid_trace.png.sha256 b/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_invalid_trace.png.sha256
index d806076..a8a5277 100644
--- a/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_invalid_trace.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_invalid_trace.png.sha256
@@ -1 +1 @@
-2dc0e3ade667e7578fa6b5b8911177ab272300bfddb015c4979c3b4361d93047
\ No newline at end of file
+e37a6224d98083ee764f849bc02af11ebbd09574b3ef0dc51837057e0ec9324a
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_second_trace.png.sha256 b/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_second_trace.png.sha256
index a0ddb77..20f9f33 100644
--- a/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_second_trace.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_second_trace.png.sha256
@@ -1 +1 @@
-47cda8e8a9ed007945bff13d7c10fa4d33fc6e264c1198b2038a216559bec962
\ No newline at end of file
+c006f2550408db44cf35f8e2b18cae1592568fe7d8a5fcdc1d454eb40a7cf34e
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_trace_.png.sha256 b/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_trace_.png.sha256
index 5b79980..72a3447 100644
--- a/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_trace_.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_trace_.png.sha256
@@ -1 +1 @@
-ab93fa5646ab5e2bc63922ee8f6c6d7657f128fe7b5426df97d02f19c770a1bc
\ No newline at end of file
+f050c13da6a113631d22b24fcf839026cdb848815119e46f10d2472ce522a3a2
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_start_from_no_trace_refresh.png.sha256 b/test/data/ui-screenshots/ui-routing_start_from_no_trace_refresh.png.sha256
index 5b79980..72a3447 100644
--- a/test/data/ui-screenshots/ui-routing_start_from_no_trace_refresh.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_start_from_no_trace_refresh.png.sha256
@@ -1 +1 @@
-ab93fa5646ab5e2bc63922ee8f6c6d7657f128fe7b5426df97d02f19c770a1bc
\ No newline at end of file
+f050c13da6a113631d22b24fcf839026cdb848815119e46f10d2472ce522a3a2
\ 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 fb9e893..7da78d7 100644
--- a/test/trace_processor/diff_tests/include_index.py
+++ b/test/trace_processor/diff_tests/include_index.py
@@ -99,9 +99,11 @@
 from diff_tests.stdlib.counters.tests import StdlibCounterIntervals
 from diff_tests.stdlib.dynamic_tables.tests import DynamicTables
 from diff_tests.stdlib.intervals.tests import StdlibIntervals
+from diff_tests.stdlib.intervals.intersect_tests import IntervalsIntersect
 from diff_tests.stdlib.graphs.dominator_tree_tests import DominatorTree
 from diff_tests.stdlib.graphs.search_tests import GraphSearchTests
 from diff_tests.stdlib.linux.tests import LinuxStdlib
+from diff_tests.stdlib.memory.heap_graph_dominator_tree_tests import HeapGraphDominatorTree
 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
@@ -247,12 +249,14 @@
       *GraphSearchTests(index_path, 'stdlib/graphs',
                         'GraphSearchTests').fetch(),
       *StdlibCounterIntervals(index_path, 'stdlib/counters',
-                       'StdlibCounterIntervals').fetch(),
+                              'StdlibCounterIntervals').fetch(),
       *DynamicTables(index_path, 'stdlib/dynamic_tables',
                      'DynamicTables').fetch(),
       *LinuxStdlib(index_path, 'stdlib/linux', 'LinuxStdlib').fetch(),
       *PreludeMathFunctions(index_path, 'stdlib/prelude',
                             'PreludeMathFunctions').fetch(),
+      *HeapGraphDominatorTree(index_path, 'stdlib/memory',
+                              'HeapGraphDominatorTree').fetch(),
       *PreludePprofFunctions(index_path, 'stdlib/prelude',
                              'PreludePprofFunctions').fetch(),
       *PreludeWindowFunctions(index_path, 'stdlib/prelude',
@@ -271,7 +275,9 @@
       *SpanJoinSmoke(index_path, 'stdlib/span_join', 'SpanJoinSmoke').fetch(),
       *StdlibCommon(index_path, 'stdlib/common', 'StdlibCommon').fetch(),
       *StdlibIntervals(index_path, 'stdlib/intervals',
-                       'StdlibIntervals').fetch(),
+                       'StdlibIntervalsIntersect').fetch(),
+      *IntervalsIntersect(index_path, 'stdlib/intervals',
+                          'StdlibIntervals').fetch(),
       *Timestamps(index_path, 'stdlib/timestamps', 'Timestamps').fetch(),
   ] + chrome_stdlib_tests
 
diff --git a/test/trace_processor/diff_tests/metrics/android/android_boot.out b/test/trace_processor/diff_tests/metrics/android/android_boot.out
index 3d5c172..3bf13cc 100644
--- a/test/trace_processor/diff_tests/metrics/android/android_boot.out
+++ b/test/trace_processor/diff_tests/metrics/android/android_boot.out
@@ -40,7 +40,7 @@
         native_alloc_gc_count: 0
         explicit_gc_count: 0
         alloc_gc_count: 0
-        mb_per_ms_of_gc: 0.8829305684617433
+        mb_per_ms_of_gc: 0.8829305684617432
     }
     post_boot_gc_aggregation {
         total_gc_count: 4
@@ -54,6 +54,6 @@
         native_alloc_gc_count: 0
         explicit_gc_count: 0
         alloc_gc_count: 0
-        mb_per_ms_of_gc: 0.8829305684617433
+        mb_per_ms_of_gc: 0.8829305684617432
     }
 }
\ No newline at end of file
diff --git a/test/trace_processor/diff_tests/metrics/camera/tests.py b/test/trace_processor/diff_tests/metrics/camera/tests.py
index fecb492..999c69a 100644
--- a/test/trace_processor/diff_tests/metrics/camera/tests.py
+++ b/test/trace_processor/diff_tests/metrics/camera/tests.py
@@ -30,7 +30,7 @@
           gc_rss_and_dma {
             min: 47779840.0
             max: 2536079360.0
-            avg: 1464706457.7348418
+            avg: 1464706457.734843
           }
         }
         """))
diff --git a/test/trace_processor/diff_tests/metrics/chrome/frame_times_metric.out b/test/trace_processor/diff_tests/metrics/chrome/frame_times_metric.out
index 3ae3370..12a0fc0 100644
--- a/test/trace_processor/diff_tests/metrics/chrome/frame_times_metric.out
+++ b/test/trace_processor/diff_tests/metrics/chrome/frame_times_metric.out
@@ -1205,6 +1205,6 @@
   exp_frame_time: 16.787
   exp_frame_time: 16.623
   exp_frame_time: 16.707
-  avg_surface_fps: 59.86008821173197
-  exp_avg_surface_fps: 59.861591661151287
+  avg_surface_fps: 59.860088211731984
+  exp_avg_surface_fps: 59.86159166115123
 }
diff --git a/test/trace_processor/diff_tests/parser/parsing/tests.py b/test/trace_processor/diff_tests/parser/parsing/tests.py
index 5df7a52..8619bc3 100644
--- a/test/trace_processor/diff_tests/parser/parsing/tests.py
+++ b/test/trace_processor/diff_tests/parser/parsing/tests.py
@@ -1318,3 +1318,39 @@
         "all_data_source_flushed_ns",12344
         "all_data_source_flushed_ns",12345
         """))
+
+  def test_ftrace_abi_errors_skipped_zero_data_length(self):
+    return DiffTestBlueprint(
+        trace=TextProto(r"""
+        packet {
+          ftrace_stats {
+            phase: END_OF_TRACE
+            cpu_stats {
+              cpu: 0
+              entries: 14
+              overrun: 0
+              commit_overrun: 0
+              bytes_read: 840
+              oldest_event_ts: 86557.552705
+              now_ts: 86557.574310
+              dropped_events: 0
+              read_events: 199966062
+            }
+            kernel_symbols_parsed: 128611
+            kernel_symbols_mem_kb: 1322
+            ftrace_parse_errors: FTRACE_STATUS_ABI_ZERO_DATA_LENGTH
+          }
+          trusted_uid: 9999
+          trusted_packet_sequence_id: 2
+          trusted_pid: 1069
+        }
+        """),
+        query="""
+        select name, severity, value
+        from stats
+        where name = "ftrace_abi_errors_skipped_zero_data_length"
+        """,
+        out=Csv("""
+        "name","severity","value"
+        "ftrace_abi_errors_skipped_zero_data_length","info",1
+        """))
diff --git a/test/trace_processor/diff_tests/parser/process_tracking/tests.py b/test/trace_processor/diff_tests/parser/process_tracking/tests.py
index 22eb3b8..9092541 100644
--- a/test/trace_processor/diff_tests/parser/process_tracking/tests.py
+++ b/test/trace_processor/diff_tests/parser/process_tracking/tests.py
@@ -314,3 +314,54 @@
         1088821786436938,1327860000000.000000,"runtime.kernel_ns",9301,157620000000,"/bin/command"
         1088821786436938,16638280000000.000000,"runtime.user_ns",9301,157620000000,"/bin/command"
         """))
+
+  # Distinguish set-to-zero process age (can happen for kthreads) from unset
+  # process age.
+  def test_process_age_optionality(self):
+    return DiffTestBlueprint(
+        trace=TextProto(r"""
+        packet {
+          first_packet_on_sequence: true
+          timestamp: 1088821452006028
+          incremental_state_cleared: true
+          process_tree {
+            processes {
+              pid: 2
+              ppid: 0
+              uid: 0
+              cmdline: "kthreadd"
+              process_start_from_boot: 0
+            }
+            processes {
+              pid: 68
+              ppid: 2
+              uid: 0
+              cmdline: "ksoftirqd/7"
+              process_start_from_boot: 10000000
+            }
+            processes {
+              pid: 9301
+              ppid: 9251
+              uid: 304336
+              cmdline: "no_age_field"
+            }
+            collection_end_timestamp: 1088821520810204
+          }
+          trusted_uid: 304336
+          trusted_packet_sequence_id: 3
+          trusted_pid: 1137063
+          previous_packet_dropped: true
+        }
+        """),
+        query="""
+        select p.pid, p.start_ts, p.cmdline
+        from process p
+        where pid in (2, 68, 9301)
+        order by pid asc;
+        """,
+        out=Csv("""
+        "pid","start_ts","cmdline"
+        2,0,"kthreadd"
+        68,10000000,"ksoftirqd/7"
+        9301,"[NULL]","no_age_field"
+        """))
diff --git a/test/trace_processor/diff_tests/stdlib/intervals/intersect_tests.py b/test/trace_processor/diff_tests/stdlib/intervals/intersect_tests.py
new file mode 100644
index 0000000..3fbdda0
--- /dev/null
+++ b/test/trace_processor/diff_tests/stdlib/intervals/intersect_tests.py
@@ -0,0 +1,274 @@
+#!/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
+from python.generators.diff_tests.testing import Csv, Json, TextProto
+from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import TestSuite
+
+
+class IntervalsIntersect(TestSuite):
+
+  def test_simple_inteval_intersect(self):
+    return DiffTestBlueprint(
+        trace=TextProto(""),
+        #      0 1 2 3 4 5 6 7
+        # A:   _ - - - - - - _
+        # B:   - - _ - - _ - -
+        # res: _ - _ - - _ - _
+        query="""
+        INCLUDE PERFETTO MODULE intervals.intersect;
+
+        CREATE PERFETTO TABLE A AS
+          WITH data(id, ts, dur) AS (
+            VALUES
+            (0, 1, 6)
+          )
+          SELECT * FROM data;
+
+        CREATE PERFETTO TABLE B AS
+          WITH data(id, ts, dur) AS (
+            VALUES
+            (0, 0, 2),
+            (1, 3, 2),
+            (2, 6, 2)
+          )
+          SELECT * FROM data;
+
+        SELECT ts, dur, left_id, right_id
+        FROM _interval_intersect!(A, B)
+        ORDER BY ts;
+        """,
+        out=Csv("""
+        "ts","dur","left_id","right_id"
+        1,1,0,0
+        3,2,0,1
+        6,1,0,2
+        """))
+
+  def test_simple_inteval_intersect_rev(self):
+    return DiffTestBlueprint(
+        trace=TextProto(""),
+        #      0 1 2 3 4 5 6 7
+        # A:   _ - - - - - - _
+        # B:   - - _ - - _ - -
+        # res: _ - _ - - _ - _
+        query="""
+        INCLUDE PERFETTO MODULE intervals.intersect;
+
+        CREATE PERFETTO TABLE A AS
+          WITH data(id, ts, dur) AS (
+            VALUES
+            (0, 1, 6)
+          )
+          SELECT * FROM data;
+
+        CREATE PERFETTO TABLE B AS
+          WITH data(id, ts, dur) AS (
+            VALUES
+            (0, 0, 2),
+            (1, 3, 2),
+            (2, 6, 2)
+          )
+          SELECT * FROM data;
+
+        SELECT ts, dur, left_id, right_id
+        FROM _interval_intersect!(B, A)
+        ORDER BY ts;
+        """,
+        out=Csv("""
+        "ts","dur","left_id","right_id"
+        1,1,0,0
+        3,2,1,0
+        6,1,2,0
+        """))
+
+  def test_no_overlap(self):
+    return DiffTestBlueprint(
+        trace=TextProto(""),
+        # A:   __-
+        # B:   -__
+        # res: ___
+        query="""
+        INCLUDE PERFETTO MODULE intervals.intersect;
+
+        CREATE PERFETTO TABLE A AS
+          WITH data(id, ts, dur) AS (
+            VALUES
+            (0, 2, 1)
+          )
+          SELECT * FROM data;
+
+        CREATE PERFETTO TABLE B AS
+          WITH data(id, ts, dur) AS (
+            VALUES
+            (0, 0, 1)
+          )
+          SELECT * FROM data;
+
+        SELECT ts, dur, left_id, right_id
+        FROM _interval_intersect!(A, B)
+        ORDER BY ts;
+        """,
+        out=Csv("""
+        "ts","dur","left_id","right_id"
+        """))
+
+  def test_no_overlap_rev(self):
+    return DiffTestBlueprint(
+        trace=TextProto(""),
+        # A:   __-
+        # B:   -__
+        # res: ___
+        query="""
+        INCLUDE PERFETTO MODULE intervals.intersect;
+
+        CREATE PERFETTO TABLE A AS
+          WITH data(id, ts, dur) AS (
+            VALUES
+            (0, 2, 1)
+          )
+          SELECT * FROM data;
+
+        CREATE PERFETTO TABLE B AS
+          WITH data(id, ts, dur) AS (
+            VALUES
+            (0, 0, 1)
+          )
+          SELECT * FROM data;
+
+        SELECT ts, dur, left_id, right_id
+        FROM _interval_intersect!(B, A)
+        ORDER BY ts;
+        """,
+        out=Csv("""
+        "ts","dur","left_id","right_id"
+        """))
+
+  def test_no_empty(self):
+    return DiffTestBlueprint(
+        trace=TextProto(""),
+        # A:   __-
+        # B:   -__
+        # res: ___
+        query="""
+        INCLUDE PERFETTO MODULE intervals.intersect;
+
+        CREATE PERFETTO TABLE A AS
+          WITH data(id, ts, dur) AS (
+            VALUES
+            (0, 2, 1)
+          )
+          SELECT * FROM data;
+
+        CREATE PERFETTO TABLE B AS
+        SELECT * FROM A LIMIT 0;
+
+        SELECT ts, dur, left_id, right_id
+        FROM _interval_intersect!(A, B)
+        ORDER BY ts;
+        """,
+        out=Csv("""
+        "ts","dur","left_id","right_id"
+        """))
+
+  def test_no_empty_rev(self):
+    return DiffTestBlueprint(
+        trace=TextProto(""),
+        # A:   __-
+        # B:   -__
+        # res: ___
+        query="""
+        INCLUDE PERFETTO MODULE intervals.intersect;
+
+        CREATE PERFETTO TABLE A AS
+          WITH data(id, ts, dur) AS (
+            VALUES
+            (0, 2, 1)
+          )
+          SELECT * FROM data;
+
+        CREATE PERFETTO TABLE B AS
+        SELECT * FROM A LIMIT 0;
+
+        SELECT ts, dur, left_id, right_id
+        FROM _interval_intersect!(B, A)
+        ORDER BY ts;
+        """,
+        out=Csv("""
+        "ts","dur","left_id","right_id"
+        """))
+
+  def test_single_point_overlap(self):
+    return DiffTestBlueprint(
+        trace=TextProto(""),
+        # A:   _-
+        # B:   -_
+        # res: __
+        query="""
+        INCLUDE PERFETTO MODULE intervals.intersect;
+
+        CREATE PERFETTO TABLE A AS
+          WITH data(id, ts, dur) AS (
+            VALUES
+            (0, 1, 1)
+          )
+          SELECT * FROM data;
+
+        CREATE PERFETTO TABLE B AS
+          WITH data(id, ts, dur) AS (
+            VALUES
+            (0, 0, 1)
+          )
+          SELECT * FROM data;
+
+        SELECT ts, dur, left_id, right_id
+        FROM _interval_intersect!(A, B)
+        ORDER BY ts;
+        """,
+        out=Csv("""
+        "ts","dur","left_id","right_id"
+        """))
+
+  def test_single_point_overlap_rev(self):
+    return DiffTestBlueprint(
+        trace=TextProto(""),
+        # A:   _-
+        # B:   -_
+        # res: __
+        query="""
+        INCLUDE PERFETTO MODULE intervals.intersect;
+
+        CREATE PERFETTO TABLE A AS
+          WITH data(id, ts, dur) AS (
+            VALUES
+            (0, 1, 1)
+          )
+          SELECT * FROM data;
+
+        CREATE PERFETTO TABLE B AS
+          WITH data(id, ts, dur) AS (
+            VALUES
+            (0, 0, 1)
+          )
+          SELECT * FROM data;
+
+        SELECT ts, dur, left_id, right_id
+        FROM _interval_intersect!(B, A)
+        ORDER BY ts;
+        """,
+        out=Csv("""
+        "ts","dur","left_id","right_id"
+        """))
diff --git a/test/trace_processor/diff_tests/stdlib/memory/heap_graph_dominator_tree_tests.py b/test/trace_processor/diff_tests/stdlib/memory/heap_graph_dominator_tree_tests.py
new file mode 100644
index 0000000..52bb465
--- /dev/null
+++ b/test/trace_processor/diff_tests/stdlib/memory/heap_graph_dominator_tree_tests.py
@@ -0,0 +1,82 @@
+#!/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
+from python.generators.diff_tests.testing import Csv
+from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import TestSuite
+
+
+class HeapGraphDominatorTree(TestSuite):
+
+  def test_heap_graph_dominator_tree(self):
+    return DiffTestBlueprint(
+        trace=Path('heap_graph_for_dominator_tree.textproto'),
+        query="""
+          INCLUDE PERFETTO MODULE memory.heap_graph_dominator_tree;
+
+          SELECT
+            node.id,
+            node.idom_id,
+            node.dominated_obj_count,
+            node.dominated_size_bytes,
+            node.depth,
+            cls.name AS type_name
+          FROM memory_heap_graph_dominator_tree node
+          JOIN heap_graph_object obj USING(id)
+          JOIN heap_graph_class cls ON obj.type_id = cls.id
+          ORDER BY type_name;
+        """,
+        out=Csv("""
+          "id","idom_id","dominated_obj_count","dominated_size_bytes",\
+"depth","type_name"
+          0,12,1,3,2,"A"
+          2,12,1,3,2,"B"
+          4,12,4,12,2,"C"
+          1,12,2,6,2,"D"
+          3,12,1,3,2,"E"
+          5,4,1,3,3,"F"
+          6,4,2,6,3,"G"
+          8,12,1,3,2,"H"
+          9,12,1,3,2,"I"
+          10,6,1,3,4,"J"
+          11,12,1,3,2,"K"
+          7,1,1,3,3,"L"
+          13,22,6,922,2,"M"
+          16,22,3,100,2,"N"
+          14,13,4,904,3,"O"
+          15,13,1,16,3,"P"
+          17,16,1,32,3,"Q"
+          12,25,13,39,1,"R"
+          22,25,10,1023,1,"S"
+          18,16,1,64,3,"T"
+          19,14,1,128,4,"U"
+          20,14,1,256,4,"V"
+          21,14,1,512,4,"W"
+          23,25,1,1024,1,"java.lang.ref.FinalizerReference"
+        """))
+
+  def test_heap_graph_super_root_fn(self):
+    return DiffTestBlueprint(
+        trace=Path('heap_graph_for_dominator_tree.textproto'),
+        query="""
+          INCLUDE PERFETTO MODULE memory.heap_graph_dominator_tree;
+
+          SELECT memory_heap_graph_super_root_fn();
+        """,
+        out=Csv("""
+          "memory_heap_graph_super_root_fn()"
+          25
+        """))
diff --git a/test/trace_processor/diff_tests/stdlib/memory/heap_graph_for_dominator_tree.textproto b/test/trace_processor/diff_tests/stdlib/memory/heap_graph_for_dominator_tree.textproto
new file mode 100644
index 0000000..b0de072
--- /dev/null
+++ b/test/trace_processor/diff_tests/stdlib/memory/heap_graph_for_dominator_tree.textproto
@@ -0,0 +1,344 @@
+packet {
+  process_tree {
+    processes {
+      pid: 1
+      ppid: 0
+      cmdline: "init"
+      uid: 0
+    }
+    processes {
+      pid: 2
+      ppid: 1
+      cmdline: "system_server"
+      uid: 1000
+    }
+  }
+}
+packet {
+  trusted_packet_sequence_id: 999
+  timestamp: 10
+
+  # The heap graph contains two separate parts.
+  # Each object has a unique class name for easy demo and debug.
+  # The two parts of the graph are:
+  # 1. the Lengauer-Tarjan example graph, with a root object of type "R". All
+  #    objects in this part are 3 bytes in size.
+  # 2. a synthetic tree whose dominator tree is itself. It's drawn below with
+  #    each object represented by it's class name. Number in the bracket is the
+  #    size of each node in bytes.
+  #                 S[1]     java.lang.ref.FinalizerReference[1024]
+  #                /    \    /
+  #            M[2]      N[4]
+  #           /   \      /   \
+  #        O[8]  P[16] Q[32] T[64]
+  #      /  |  \
+  #     U   V   W
+  # [128] [256] [512]
+
+  heap_graph {
+    pid: 2
+    roots {
+      root_type: ROOT_JNI_GLOBAL
+      object_ids: 0x12  # root of the Lengauer-Tarjan example graph
+      object_ids: 0x13  # root of the synthetic tree
+      object_ids: 0x18  # just to test type exclusion
+    }
+    objects {
+      id: 0x01
+      type_id: 1
+      self_size: 3
+      reference_object_id: 0x04
+    }
+    objects {
+      id: 0x02
+      type_id: 2
+      self_size: 3
+      reference_object_id: 0x01
+      reference_object_id: 0x04
+      reference_object_id: 0x05
+    }
+    objects {
+      id: 0x03
+      type_id: 3
+      self_size: 3
+      reference_object_id: 0x06
+      reference_object_id: 0x07
+    }
+    objects {
+      id: 0x04
+      type_id: 4
+      self_size: 3
+      reference_object_id: 0x0c
+    }
+    objects {
+      id: 0x05
+      type_id: 5
+      self_size: 3
+      reference_field_id: 2
+      reference_object_id: 0x08
+    }
+    objects {
+      id: 0x06
+      type_id: 6
+      self_size: 3
+      reference_object_id: 0x09
+    }
+    objects {
+      id: 0x07
+      type_id: 7
+      self_size: 3
+      reference_object_id: 0x09
+      reference_object_id: 0x0a
+    }
+    objects {
+      id: 0x08
+      type_id: 8
+      self_size: 3
+      reference_object_id: 0x05
+      reference_object_id: 0x0b
+    }
+    objects {
+      id: 0x09
+      type_id: 9
+      self_size: 3
+      reference_object_id: 0x0b
+    }
+    objects {
+      id: 0x0a
+      type_id: 10
+      self_size: 3
+      reference_object_id: 0x09
+    }
+    objects {
+      id: 0x0b
+      type_id: 11
+      self_size: 3
+      reference_object_id: 0x09
+      reference_object_id: 0x12
+    }
+    objects {
+      id: 0x0c
+      type_id: 12
+      self_size: 3
+      reference_object_id: 0x08
+    }
+    objects {
+      id: 0x0d
+      type_id: 13
+      self_size: 2
+      reference_object_id: 0x0f
+      reference_object_id: 0x10
+    }
+    objects {
+      id: 0x0e
+      type_id: 14
+      self_size: 4
+      reference_object_id: 0x11
+      reference_object_id: 0x14
+    }
+    objects {
+      id: 0x0f
+      type_id: 15
+      self_size: 8
+      reference_object_id: 0x15
+      reference_object_id: 0x16
+      reference_object_id: 0x17
+    }
+    objects {
+      id: 0x10
+      type_id: 16
+      self_size: 16
+    }
+    objects {
+      id: 0x11
+      type_id: 17
+      self_size: 32
+    }
+    objects {
+      id: 0x12
+      type_id: 18
+      self_size: 3
+      reference_object_id: 0x01
+      reference_object_id: 0x02
+      reference_object_id: 0x03
+    }
+    objects {
+      id: 0x13
+      type_id: 19
+      self_size: 1
+      reference_object_id: 0x0d
+      reference_object_id: 0x0e
+    }
+    objects {
+      id: 0x14
+      type_id: 20
+      self_size: 64
+    }
+    objects {
+      id: 0x15
+      type_id: 21
+      self_size: 128
+    }
+    objects {
+      id: 0x16
+      type_id: 22
+      self_size: 256
+    }
+    objects {
+      id: 0x17
+      type_id: 23
+      self_size: 512
+    }
+    objects {
+      id: 0x18
+      type_id: 24 # "java.lang.ref.FinalizerReference"
+      self_size: 1024
+      reference_object_id: 0x0e
+    }
+    objects {
+      id: 0x19
+      type_id: 1 # unreachable, should be excluded
+      self_size: 1024
+    }
+    continued: true
+    index: 0
+  }
+}
+packet {
+  trusted_packet_sequence_id: 999
+  timestamp: 10
+  heap_graph {
+    pid: 2
+    location_names {
+      iid: 1
+      str: "/data/app/~~ASDFG==/invalid.test.android-SDASD/test.apk"
+    }
+    types {
+      id: 1
+      class_name: "A"
+      location_id: 1
+      object_size: 64
+    }
+    types {
+      id: 2
+      class_name: "B"
+      location_id: 1
+      object_size: 64
+    }
+    types {
+      id: 3
+      class_name: "C"
+      location_id: 1
+      object_size: 64
+    }
+    types {
+      id: 4
+      class_name: "D"
+      location_id: 1
+      object_size: 128
+    }
+    types {
+      id: 5
+      class_name: "E"
+      location_id: 1
+      object_size: 1024
+    }
+    types {
+      id: 6
+      class_name: "F"
+      location_id: 1
+    }
+    types {
+      id: 7
+      class_name: "G"
+      location_id: 1
+    }
+    types {
+      id: 8
+      class_name: "H"
+      location_id: 1
+    }
+    types {
+      id: 9
+      class_name: "I"
+      location_id: 1
+    }
+    types {
+      id: 10
+      class_name: "J"
+      location_id: 1
+    }
+    types {
+      id: 11
+      class_name: "K"
+      location_id: 1
+    }
+    types {
+      id: 12
+      class_name: "L"
+      location_id: 1
+    }
+    types {
+      id: 13
+      class_name: "M"
+      location_id: 1
+    }
+    types {
+      id: 14
+      class_name: "N"
+      location_id: 1
+    }
+    types {
+      id: 15
+      class_name: "O"
+      location_id: 1
+    }
+    types {
+      id: 16
+      class_name: "P"
+      location_id: 1
+    }
+    types {
+      id: 17
+      class_name: "Q"
+      location_id: 1
+    }
+    types {
+      id: 18
+      class_name: "R"
+      location_id: 1
+    }
+    types {
+      id: 19
+      class_name: "S"
+      location_id: 1
+    }
+    types {
+      id: 20
+      class_name: "T"
+      location_id: 1
+    }
+    types {
+      id: 21
+      class_name: "U"
+      location_id: 1
+    }
+    types {
+      id: 22
+      class_name: "V"
+      location_id: 1
+    }
+    types {
+      id: 23
+      class_name: "W"
+      location_id: 1
+    }
+    types {
+      id: 24
+      class_name: "java.lang.ref.FinalizerReference"
+      location_id: 1
+    }
+    continued: false
+    index: 1
+  }
+}
diff --git a/test/trace_processor/diff_tests/stdlib/sched/tests.py b/test/trace_processor/diff_tests/stdlib/sched/tests.py
index 48a8814..9324d51 100644
--- a/test/trace_processor/diff_tests/stdlib/sched/tests.py
+++ b/test/trace_processor/diff_tests/stdlib/sched/tests.py
@@ -162,14 +162,14 @@
         92000000000,0.000009,0.000071
         """))
 
-  def test_sched_thread_time_in_state(self):
+  def test_sched_time_in_state_for_thread(self):
     return DiffTestBlueprint(
         trace=DataPath('example_android_trace_30s.pb'),
         query="""
-        INCLUDE PERFETTO MODULE sched.states;
+        INCLUDE PERFETTO MODULE sched.time_in_state;
 
         SELECT *
-        FROM sched_thread_time_in_state
+        FROM sched_time_in_state_for_thread
         ORDER BY utid, state
         LIMIT 10;
         """,
@@ -191,7 +191,7 @@
     return DiffTestBlueprint(
         trace=DataPath('example_android_trace_30s.pb'),
         query="""
-        INCLUDE PERFETTO MODULE sched.states;
+        INCLUDE PERFETTO MODULE sched.time_in_state;
 
         SELECT *
         FROM sched_percentage_of_time_in_state
@@ -211,3 +211,37 @@
         9,0,"[NULL]","[NULL]",99,"[NULL]","[NULL]"
         10,0,"[NULL]",0,99,"[NULL]","[NULL]"
         """))
+
+  def test_sched_time_in_state_for_thread_in_interval(self):
+    return DiffTestBlueprint(
+        trace=DataPath('example_android_trace_30s.pb'),
+        query="""
+        INCLUDE PERFETTO MODULE sched.time_in_state;
+
+        SELECT *
+        FROM sched_time_in_state_for_thread_in_interval(71039311397, 10000000000, 44);
+        """,
+        out=Csv("""
+        "state","io_wait","blocked_function","dur"
+        "S","[NULL]","[NULL]",1404466083
+        "Running","[NULL]","[NULL]",4655524
+        "D","[NULL]","[NULL]",563645
+        "R+","[NULL]","[NULL]",380156
+        """))
+
+  def test_sched_time_in_state_and_cpu_for_thread_in_interval(self):
+    return DiffTestBlueprint(
+        trace=DataPath('example_android_trace_30s.pb'),
+        query="""
+        INCLUDE PERFETTO MODULE sched.time_in_state;
+
+        SELECT *
+        FROM sched_time_in_state_and_cpu_for_thread_in_interval(71039311397, 10000000000, 44);
+        """,
+        out=Csv("""
+        "state","io_wait","cpu","blocked_function","dur"
+        "S","[NULL]","[NULL]","[NULL]",1404466083
+        "Running","[NULL]",2,"[NULL]",4655524
+        "D","[NULL]","[NULL]","[NULL]",563645
+        "R+","[NULL]","[NULL]","[NULL]",380156
+        """))
diff --git a/test/trace_processor/diff_tests/syntax/filtering_tests.py b/test/trace_processor/diff_tests/syntax/filtering_tests.py
index adf8c3e..22b4111 100644
--- a/test/trace_processor/diff_tests/syntax/filtering_tests.py
+++ b/test/trace_processor/diff_tests/syntax/filtering_tests.py
@@ -13,7 +13,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from python.generators.diff_tests.testing import DataPath
+from python.generators.diff_tests.testing import DataPath, TextProto
 from python.generators.diff_tests.testing import Csv
 from python.generators.diff_tests.testing import DiffTestBlueprint
 from python.generators.diff_tests.testing import TestSuite
@@ -189,3 +189,49 @@
         "cnt"
         0
         """))
+
+  def test_string_null_vs_empty(self):
+    return DiffTestBlueprint(
+        trace=TextProto(""),
+        query="""
+        CREATE PERFETTO TABLE foo AS
+        SELECT 0 as id, NULL AS strings
+        UNION ALL
+        SELECT 1, 'cheese'
+        UNION ALL
+        SELECT 2, NULL
+        UNION ALL
+        SELECT 3, '';
+
+        SELECT * FROM foo ORDER BY strings ASC;
+        """,
+        out=Csv("""
+        "id","strings"
+        0,"[NULL]"
+        2,"[NULL]"
+        3,""
+        1,"cheese"
+        """))
+
+  def test_string_null_vs_empty_desc(self):
+    return DiffTestBlueprint(
+        trace=TextProto(""),
+        query="""
+        CREATE PERFETTO TABLE foo AS
+        SELECT 0 as id, NULL AS strings
+        UNION ALL
+        SELECT 1, 'cheese'
+        UNION ALL
+        SELECT 2, NULL
+        UNION ALL
+        SELECT 3, '';
+
+        SELECT * FROM foo ORDER BY strings DESC;
+        """,
+        out=Csv("""
+        "id","strings"
+        1,"cheese"
+        3,""
+        0,"[NULL]"
+        2,"[NULL]"
+        """))
diff --git a/tools/install-build-deps b/tools/install-build-deps
index 02149a1..e43f4f9 100755
--- a/tools/install-build-deps
+++ b/tools/install-build-deps
@@ -232,13 +232,13 @@
     # If updating the version, also update bazel/deps.bzl.
     Dependency(
         'buildtools/sqlite.zip',
-        'https://storage.googleapis.com/perfetto/sqlite-amalgamation-3390200.zip',
-        '87775784f8b22d0d0f1d7811870d39feaa7896319c7c20b849a4181c5a50609b',
+        'https://storage.googleapis.com/perfetto/sqlite-amalgamation-3440200.zip',
+        '833be89b53b3be8b40a2e3d5fedb635080e3edb204957244f3d6987c2bb2345f',
         'all', 'all'),
     Dependency(
         'buildtools/sqlite_src',
         'https://chromium.googlesource.com/external/github.com/sqlite/sqlite.git',
-        '202b2a7b54ea2dd13a8a5adfd75523abe4dcf17f',  # refs/tags/version-3.39.2.
+        'c8f9803dc32bfee78a9ca2b1abbe39499729219b',  # refs/tags/version-3.44.2.
         'all',
         'all'),
 
diff --git a/ui/build.js b/ui/build.js
index 4afe0cd..cc6cfb9 100644
--- a/ui/build.js
+++ b/ui/build.js
@@ -471,7 +471,11 @@
 function buildWasm(skipWasmBuild) {
   if (!skipWasmBuild) {
     if (!cfg.noOverrideGnArgs) {
-      const gnArgs = ['gen', `--args=is_debug=${cfg.debug}`, cfg.outDir];
+      let gnVars = `is_debug=${cfg.debug}`;
+      if (childProcess.spawnSync('which', ['ccache']).status === 0) {
+        gnVars += ` cc_wrapper="ccache"`;
+      }
+      const gnArgs = ['gen', `--args=${gnVars}`, cfg.outDir];
       addTask(exec, [pjoin(ROOT_DIR, 'tools/gn'), gnArgs]);
     }
 
diff --git a/ui/src/common/default_plugins.ts b/ui/src/common/default_plugins.ts
index 1a48e28..ea6f263 100644
--- a/ui/src/common/default_plugins.ts
+++ b/ui/src/common/default_plugins.ts
@@ -55,4 +55,5 @@
   'perfetto.Screenshots',
   'perfetto.ThreadState',
   'perfetto.VisualisedArgs',
+  'linuxDeviceTracks',
 ];
diff --git a/ui/src/common/recordingV2/recording_config_utils.ts b/ui/src/common/recordingV2/recording_config_utils.ts
index a14415b..530aa05 100644
--- a/ui/src/common/recordingV2/recording_config_utils.ts
+++ b/ui/src/common/recordingV2/recording_config_utils.ts
@@ -265,6 +265,10 @@
     ftraceEvents.add('task/task_rename');
   }
 
+  if (uiCfg.linuxDeviceRpm) {
+    ftraceEvents.add('rpm/rpm_status');
+  }
+
   if (uiCfg.meminfo) {
     if (sysStatsCfg === undefined) sysStatsCfg = new SysStatsConfig();
     sysStatsCfg.meminfoPeriodMs = uiCfg.meminfoPeriodMs;
diff --git a/ui/src/controller/record_config_types.ts b/ui/src/controller/record_config_types.ts
index 06e4ec8..05bb92c 100644
--- a/ui/src/controller/record_config_types.ts
+++ b/ui/src/controller/record_config_types.ts
@@ -113,6 +113,8 @@
   tracePerf: bool(),
   timebaseFrequency: num(100),
   targetCmdLine: arrayOf(str()),
+
+  linuxDeviceRpm: bool(),
 });
 export const namedRecordConfigValidator = record(
   {title: requiredStr, key: requiredStr, config: recordConfigValidator});
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index 40a8f09..b911a45 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -1159,9 +1159,11 @@
     visibleStart = Time.max(visibleStart, reliableRangeStart);
   }
 
+  // Move start of visible window to the first ftrace event
   const ftraceBounds = await computeFtraceBounds(engine);
   if (ftraceBounds !== null) {
-    visibleStart = ftraceBounds.start;
+    // Avoid moving start of visible window past its end!
+    visibleStart = Time.min(ftraceBounds.start, visibleEnd);
   }
   return HighPrecisionTimeSpan.fromTime(visibleStart, visibleEnd);
 }
diff --git a/ui/src/frontend/slice_details_panel.ts b/ui/src/frontend/slice_details_panel.ts
index a725077..c14d03c 100644
--- a/ui/src/frontend/slice_details_panel.ts
+++ b/ui/src/frontend/slice_details_panel.ts
@@ -30,6 +30,8 @@
 import {DurationWidget} from './widgets/duration';
 import {Timestamp} from './widgets/timestamp';
 
+const MIN_NORMAL_SCHED_PRIORITY = 100;
+
 export class SliceDetailsPanel extends SlicePanel {
   view() {
     const sliceInfo = globals.sliceDetails;
@@ -127,6 +129,15 @@
     }
   }
 
+  private renderPriorityText(priority?: number) {
+    if (priority === undefined) {
+      return undefined;
+    }
+    return priority < MIN_NORMAL_SCHED_PRIORITY ?
+      `${priority} (real-time)` :
+      `${priority}`;
+  }
+
   private renderDetails(sliceInfo: SliceDetails, threadInfo?: ThreadDesc):
       m.Children {
     if (!threadInfo || sliceInfo.ts === undefined ||
@@ -172,8 +183,8 @@
         }),
         this.renderThreadDuration(sliceInfo),
         m(TreeNode, {
-          left: 'Prio',
-          right: sliceInfo.priority,
+          left: 'Priority',
+          right: this.renderPriorityText(sliceInfo.priority),
         }),
         m(TreeNode, {
           left: 'End State',
diff --git a/ui/src/frontend/sql/thread_state.ts b/ui/src/frontend/sql/thread_state.ts
index 24de373..e2740d5 100644
--- a/ui/src/frontend/sql/thread_state.ts
+++ b/ui/src/frontend/sql/thread_state.ts
@@ -72,17 +72,18 @@
   // TODO(altimin): this probably should share some code with pivot tables when
   // we actually get some pivot tables we like.
   const query = await engine.query(`
-    INCLUDE PERFETTO MODULE deprecated.v42.common.thread_states;
+    INCLUDE PERFETTO MODULE sched.time_in_state;
+    INCLUDE PERFETTO MODULE sched.states;
+    INCLUDE PERFETTO MODULE cpu.size;
 
     SELECT
-      state,
-      raw_state as rawState,
-      cpu_type as cpuType,
+      sched_state_io_to_human_readable_string(state, io_wait) as state,
+      state AS rawState,
+      cpu_guess_core_type(cpu) AS cpuType,
       cpu,
-      blocked_function as blockedFunction,
+      blocked_function AS blockedFunction,
       dur
-    FROM thread_state_summary_for_interval(${range.start}, ${range.duration}, ${
-  utid});
+    FROM sched_time_in_state_and_cpu_for_thread_in_interval(${range.start}, ${range.duration}, ${utid});
   `);
   const it = query.iter({
     state: STR,
diff --git a/ui/src/plugins/linuxDeviceTracks/OWNERS b/ui/src/plugins/linuxDeviceTracks/OWNERS
new file mode 100644
index 0000000..3757ab5
--- /dev/null
+++ b/ui/src/plugins/linuxDeviceTracks/OWNERS
@@ -0,0 +1,3 @@
+isaacmanjarres@google.com
+saravanak@google.com
+vilasbhat@google.com
diff --git a/ui/src/plugins/linuxDeviceTracks/index.ts b/ui/src/plugins/linuxDeviceTracks/index.ts
new file mode 100644
index 0000000..9b2d157
--- /dev/null
+++ b/ui/src/plugins/linuxDeviceTracks/index.ts
@@ -0,0 +1,87 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import {
+  NUM,
+  Plugin,
+  PluginContext,
+  PluginContextTrace,
+  PluginDescriptor,
+  STR_NULL,
+} from '../../public';
+import {ASYNC_SLICE_TRACK_KIND} from '../../tracks/async_slices';
+import {AsyncSliceTrackV2} from '../../tracks/async_slices/async_slice_track_v2';
+
+// This plugin renders visualizations of runtime power state transitions for
+// Linux kernel devices (devices managed by Linux drivers).
+class linuxDevices implements Plugin {
+  onActivate(_: PluginContext): void {
+  }
+
+  async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
+    const result = await ctx.engine.query(`
+      with
+        slices_tracks as materialized (
+          select distinct track_id
+          from slice
+        ),
+        tracks as (
+          select
+            linux_device_track.id as track_id,
+            linux_device_track.name
+          from linux_device_track
+          join slices_tracks on
+	    slices_tracks.track_id = linux_device_track.id
+        )
+      select
+        t.name,
+        t.track_id as trackId
+      from tracks as t
+      order by t.name;
+    `);
+
+    const it = result.iter({
+      name: STR_NULL,
+      trackId: NUM,
+    });
+
+    for (; it.valid(); it.next()) {
+      const trackId = it.trackId;
+      const name = it.name ?? `${trackId}`;
+
+      ctx.registerStaticTrack({
+        uri: `linuxDeviceTracks#${name}`,
+        displayName: name,
+        trackIds: [trackId],
+        kind: ASYNC_SLICE_TRACK_KIND,
+        trackFactory: ({trackKey}) => {
+          return new AsyncSliceTrackV2(
+            {
+              engine: ctx.engine,
+              trackKey,
+            },
+            0,
+            [trackId],
+          );
+        },
+        groupName: `Linux Kernel Devices`,
+      });
+    }
+  }
+}
+
+export const plugin: PluginDescriptor = {
+  pluginId: 'linuxDeviceTracks',
+  plugin: linuxDevices,
+};
diff --git a/ui/src/tracks/cpu_slices/index.ts b/ui/src/tracks/cpu_slices/index.ts
index 74e7555..8a2cfbc 100644
--- a/ui/src/tracks/cpu_slices/index.ts
+++ b/ui/src/tracks/cpu_slices/index.ts
@@ -16,7 +16,7 @@
 
 import {BigintMath as BIMath} from '../../base/bigint_math';
 import {search, searchEq, searchSegment} from '../../base/binary_search';
-import {assertTrue} from '../../base/logging';
+import {assertExists, assertTrue} from '../../base/logging';
 import {Duration, duration, Time, time} from '../../base/time';
 import {Actions} from '../../common/actions';
 import {calcCachedBucketSize} from '../../common/cache_utils';
@@ -52,7 +52,7 @@
   starts: BigInt64Array;
   ends: BigInt64Array;
   utids: Uint32Array;
-  isIncomplete: Uint8Array;
+  flags: Uint8Array;
   lastRowId: number;
 }
 
@@ -60,6 +60,10 @@
 const RECT_HEIGHT = 24;
 const TRACK_HEIGHT = MARGIN_TOP * 2 + RECT_HEIGHT;
 
+const CPU_SLICE_FLAGS_INCOMPLETE = 1;
+const CPU_SLICE_FLAGS_REALTIME = 2;
+
+
 class CpuSliceTrack implements Track {
   private mousePos?: { x: number, y: number };
   private utidHoveredInThisTrack = -1;
@@ -96,7 +100,8 @@
         dur,
         utid,
         id,
-        dur = -1 as isIncomplete
+        dur = -1 as isIncomplete,
+        (case when priority < 100 then 1 else 0 end) as isRealtime
       from sched
       where cpu = ${this.cpu} and utid != 0
     `);
@@ -107,7 +112,7 @@
     `);
 
     const queryLastSlice = await this.engine.query(`
-    select ifnull(max(id), -1) as lastSliceId from ${this.tableName('sched')}
+      select ifnull(max(id), -1) as lastSliceId from ${this.tableName('sched')}
     `);
     this.lastRowId = queryLastSlice.firstRow({lastSliceId: NUM}).lastSliceId;
 
@@ -127,7 +132,8 @@
         max(dur) as dur,
         utid,
         id,
-        isIncomplete
+        isIncomplete,
+        isRealtime
       from ${this.tableName('sched')}
       group by cached_tsq, isIncomplete
       order by cached_tsq
@@ -158,7 +164,8 @@
         max(dur) as dur,
         utid,
         id,
-        isIncomplete
+        isIncomplete,
+        isRealtime
       from ${queryTable}
       where
         ${constraintColumn} >= ${start - this.maxDur} and
@@ -178,7 +185,7 @@
       starts: new BigInt64Array(numRows),
       ends: new BigInt64Array(numRows),
       utids: new Uint32Array(numRows),
-      isIncomplete: new Uint8Array(numRows),
+      flags: new Uint8Array(numRows),
     };
 
     const it = queryRes.iter({
@@ -188,6 +195,7 @@
       utid: NUM,
       id: NUM,
       isIncomplete: NUM,
+      isRealtime: NUM,
     });
     for (let row = 0; it.valid(); it.next(), row++) {
       const startQ = it.tsq;
@@ -205,14 +213,21 @@
       slices.starts[row] = startQ;
       slices.utids[row] = it.utid;
       slices.ids[row] = it.id;
-      slices.isIncomplete[row] = it.isIncomplete;
+
+      slices.flags[row] = 0;
+      if (it.isIncomplete) {
+        slices.flags[row] |= CPU_SLICE_FLAGS_INCOMPLETE;
+      }
+      if (it.isRealtime) {
+        slices.flags[row] |= CPU_SLICE_FLAGS_REALTIME;
+      }
     }
 
     // If the slice is incomplete and it is the last slice in the track, the end
     // of the slice would be the end of the visible window. Otherwise we end the
     // slice with the beginning the next one.
     for (let row = 0; row < slices.length; row++) {
-      if (!slices.isIncomplete[row]) {
+      if (!(slices.flags[row] & CPU_SLICE_FLAGS_INCOMPLETE)) {
         continue;
       }
       const endTime = row === slices.length - 1 ? end : slices.starts[row + 1];
@@ -287,7 +302,8 @@
       // If the last slice is incomplete, it should end with the end of the
       // window, else it might spill over the window and the end would not be
       // visible as a zigzag line.
-      if (data.ids[i] === data.lastRowId && data.isIncomplete[i]) {
+      if (data.ids[i] === data.lastRowId &&
+          (data.flags[i] & CPU_SLICE_FLAGS_INCOMPLETE)) {
         tEnd = endTime;
       }
       const rectStart = visibleTimeScale.timeToPx(tStart);
@@ -317,12 +333,18 @@
         textColor = colorScheme.textBase;
       }
       ctx.fillStyle = color.cssString;
-      if (data.isIncomplete[i]) {
+
+      if (data.flags[i] & CPU_SLICE_FLAGS_INCOMPLETE) {
         drawIncompleteSlice(ctx, rectStart, MARGIN_TOP, rectWidth, RECT_HEIGHT);
       } else {
         ctx.fillRect(rectStart, MARGIN_TOP, rectWidth, RECT_HEIGHT);
       }
 
+      if (data.flags[i] & CPU_SLICE_FLAGS_REALTIME) {
+        ctx.fillStyle = getHatchedPattern(ctx);
+        ctx.fillRect(rectStart, MARGIN_TOP, rectWidth, RECT_HEIGHT);
+      }
+
       // Don't render text when we have less than 5px to play with.
       if (rectWidth < 5) continue;
 
@@ -344,6 +366,11 @@
           title = `${threadInfo.threadName} [${threadInfo.tid}]`;
         }
       }
+
+      if (data.flags[i] & CPU_SLICE_FLAGS_REALTIME) {
+        subTitle = subTitle + ' (RT)';
+      }
+
       const right = Math.min(visWindowEndPx, rectEnd);
       const left = Math.max(rectStart, 0);
       const visibleWidth = Math.max(right - left, 1);
@@ -548,6 +575,30 @@
   }
 }
 
+// Creates a diagonal hatched pattern to be used for distinguishing slices with
+// real-time priorities. The pattern is created once as an offscreen canvas and
+// is kept cached inside the Context2D of the main canvas, without making
+// assumptions on the lifetime of the main canvas.
+function getHatchedPattern(mainCtx: CanvasRenderingContext2D) : CanvasPattern {
+  const mctx = mainCtx as CanvasRenderingContext2D & {
+    sliceHatchedPattern ?: CanvasPattern;
+  };
+  if (mctx.sliceHatchedPattern !== undefined) return mctx.sliceHatchedPattern;
+  const canvas = document.createElement('canvas');
+  const SIZE = 8;
+  canvas.width = canvas.height = SIZE;
+  const ctx = assertExists(canvas.getContext('2d'));
+  ctx.strokeStyle = 'rgba(255,255,255,0.3)';
+  ctx.beginPath();
+  ctx.lineWidth = 1;
+  ctx.moveTo(0, SIZE);
+  ctx.lineTo(SIZE, 0);
+  ctx.stroke();
+  mctx.sliceHatchedPattern = assertExists(mctx.createPattern(canvas, 'repeat'));
+  return mctx.sliceHatchedPattern;
+}
+
+
 export const plugin: PluginDescriptor = {
   pluginId: 'perfetto.CpuSlices',
   plugin: CpuSlices,