Merge "Trace Redaction - Add missing test file" into main
diff --git a/Android.bp b/Android.bp
index 722da60..ebc715d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -6041,6 +6041,7 @@
         "protos/perfetto/trace/track_event/counter_descriptor.proto",
         "protos/perfetto/trace/track_event/debug_annotation.proto",
         "protos/perfetto/trace/track_event/log_message.proto",
+        "protos/perfetto/trace/track_event/pixel_modem.proto",
         "protos/perfetto/trace/track_event/process_descriptor.proto",
         "protos/perfetto/trace/track_event/range_of_interest.proto",
         "protos/perfetto/trace/track_event/screenshot.proto",
@@ -9177,6 +9178,7 @@
         "protos/perfetto/trace/track_event/counter_descriptor.proto",
         "protos/perfetto/trace/track_event/debug_annotation.proto",
         "protos/perfetto/trace/track_event/log_message.proto",
+        "protos/perfetto/trace/track_event/pixel_modem.proto",
         "protos/perfetto/trace/track_event/process_descriptor.proto",
         "protos/perfetto/trace/track_event/range_of_interest.proto",
         "protos/perfetto/trace/track_event/screenshot.proto",
@@ -9219,6 +9221,7 @@
         "external/perfetto/protos/perfetto/trace/track_event/counter_descriptor.gen.cc",
         "external/perfetto/protos/perfetto/trace/track_event/debug_annotation.gen.cc",
         "external/perfetto/protos/perfetto/trace/track_event/log_message.gen.cc",
+        "external/perfetto/protos/perfetto/trace/track_event/pixel_modem.gen.cc",
         "external/perfetto/protos/perfetto/trace/track_event/process_descriptor.gen.cc",
         "external/perfetto/protos/perfetto/trace/track_event/range_of_interest.gen.cc",
         "external/perfetto/protos/perfetto/trace/track_event/screenshot.gen.cc",
@@ -9261,6 +9264,7 @@
         "external/perfetto/protos/perfetto/trace/track_event/counter_descriptor.gen.h",
         "external/perfetto/protos/perfetto/trace/track_event/debug_annotation.gen.h",
         "external/perfetto/protos/perfetto/trace/track_event/log_message.gen.h",
+        "external/perfetto/protos/perfetto/trace/track_event/pixel_modem.gen.h",
         "external/perfetto/protos/perfetto/trace/track_event/process_descriptor.gen.h",
         "external/perfetto/protos/perfetto/trace/track_event/range_of_interest.gen.h",
         "external/perfetto/protos/perfetto/trace/track_event/screenshot.gen.h",
@@ -9299,6 +9303,7 @@
         "protos/perfetto/trace/track_event/counter_descriptor.proto",
         "protos/perfetto/trace/track_event/debug_annotation.proto",
         "protos/perfetto/trace/track_event/log_message.proto",
+        "protos/perfetto/trace/track_event/pixel_modem.proto",
         "protos/perfetto/trace/track_event/process_descriptor.proto",
         "protos/perfetto/trace/track_event/range_of_interest.proto",
         "protos/perfetto/trace/track_event/screenshot.proto",
@@ -9340,6 +9345,7 @@
         "protos/perfetto/trace/track_event/counter_descriptor.proto",
         "protos/perfetto/trace/track_event/debug_annotation.proto",
         "protos/perfetto/trace/track_event/log_message.proto",
+        "protos/perfetto/trace/track_event/pixel_modem.proto",
         "protos/perfetto/trace/track_event/process_descriptor.proto",
         "protos/perfetto/trace/track_event/range_of_interest.proto",
         "protos/perfetto/trace/track_event/screenshot.proto",
@@ -9381,6 +9387,7 @@
         "external/perfetto/protos/perfetto/trace/track_event/counter_descriptor.pb.cc",
         "external/perfetto/protos/perfetto/trace/track_event/debug_annotation.pb.cc",
         "external/perfetto/protos/perfetto/trace/track_event/log_message.pb.cc",
+        "external/perfetto/protos/perfetto/trace/track_event/pixel_modem.pb.cc",
         "external/perfetto/protos/perfetto/trace/track_event/process_descriptor.pb.cc",
         "external/perfetto/protos/perfetto/trace/track_event/range_of_interest.pb.cc",
         "external/perfetto/protos/perfetto/trace/track_event/screenshot.pb.cc",
@@ -9422,6 +9429,7 @@
         "external/perfetto/protos/perfetto/trace/track_event/counter_descriptor.pb.h",
         "external/perfetto/protos/perfetto/trace/track_event/debug_annotation.pb.h",
         "external/perfetto/protos/perfetto/trace/track_event/log_message.pb.h",
+        "external/perfetto/protos/perfetto/trace/track_event/pixel_modem.pb.h",
         "external/perfetto/protos/perfetto/trace/track_event/process_descriptor.pb.h",
         "external/perfetto/protos/perfetto/trace/track_event/range_of_interest.pb.h",
         "external/perfetto/protos/perfetto/trace/track_event/screenshot.pb.h",
@@ -9460,6 +9468,7 @@
         "protos/perfetto/trace/track_event/counter_descriptor.proto",
         "protos/perfetto/trace/track_event/debug_annotation.proto",
         "protos/perfetto/trace/track_event/log_message.proto",
+        "protos/perfetto/trace/track_event/pixel_modem.proto",
         "protos/perfetto/trace/track_event/process_descriptor.proto",
         "protos/perfetto/trace/track_event/range_of_interest.proto",
         "protos/perfetto/trace/track_event/screenshot.proto",
@@ -9502,6 +9511,7 @@
         "external/perfetto/protos/perfetto/trace/track_event/counter_descriptor.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/track_event/debug_annotation.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/track_event/log_message.pbzero.cc",
+        "external/perfetto/protos/perfetto/trace/track_event/pixel_modem.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/track_event/process_descriptor.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/track_event/range_of_interest.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/track_event/screenshot.pbzero.cc",
@@ -9544,6 +9554,7 @@
         "external/perfetto/protos/perfetto/trace/track_event/counter_descriptor.pbzero.h",
         "external/perfetto/protos/perfetto/trace/track_event/debug_annotation.pbzero.h",
         "external/perfetto/protos/perfetto/trace/track_event/log_message.pbzero.h",
+        "external/perfetto/protos/perfetto/trace/track_event/pixel_modem.pbzero.h",
         "external/perfetto/protos/perfetto/trace/track_event/process_descriptor.pbzero.h",
         "external/perfetto/protos/perfetto/trace/track_event/range_of_interest.pbzero.h",
         "external/perfetto/protos/perfetto/trace/track_event/screenshot.pbzero.h",
@@ -9712,6 +9723,7 @@
         "protos/perfetto/trace/track_event/counter_descriptor.proto",
         "protos/perfetto/trace/track_event/debug_annotation.proto",
         "protos/perfetto/trace/track_event/log_message.proto",
+        "protos/perfetto/trace/track_event/pixel_modem.proto",
         "protos/perfetto/trace/track_event/process_descriptor.proto",
         "protos/perfetto/trace/track_event/range_of_interest.proto",
         "protos/perfetto/trace/track_event/screenshot.proto",
@@ -12369,6 +12381,7 @@
         "src/trace_processor/perfetto_sql/stdlib/prelude/casts.sql",
         "src/trace_processor/perfetto_sql/stdlib/prelude/slices.sql",
         "src/trace_processor/perfetto_sql/stdlib/prelude/trace_bounds.sql",
+        "src/trace_processor/perfetto_sql/stdlib/sched/runnable.sql",
         "src/trace_processor/perfetto_sql/stdlib/sched/states.sql",
         "src/trace_processor/perfetto_sql/stdlib/sched/thread_executing_span.sql",
         "src/trace_processor/perfetto_sql/stdlib/sched/thread_level_parallelism.sql",
@@ -13870,6 +13883,7 @@
         "protos/perfetto/trace/track_event/counter_descriptor.proto",
         "protos/perfetto/trace/track_event/debug_annotation.proto",
         "protos/perfetto/trace/track_event/log_message.proto",
+        "protos/perfetto/trace/track_event/pixel_modem.proto",
         "protos/perfetto/trace/track_event/process_descriptor.proto",
         "protos/perfetto/trace/track_event/range_of_interest.proto",
         "protos/perfetto/trace/track_event/screenshot.proto",
@@ -15151,70 +15165,38 @@
         ":perfetto_include_perfetto_trace_processor_basic_types",
         ":perfetto_include_perfetto_trace_processor_storage",
         ":perfetto_include_perfetto_trace_processor_trace_processor",
-        ":perfetto_protos_perfetto_common_cpp_gen",
         ":perfetto_protos_perfetto_common_zero_gen",
-        ":perfetto_protos_perfetto_config_android_cpp_gen",
         ":perfetto_protos_perfetto_config_android_zero_gen",
-        ":perfetto_protos_perfetto_config_cpp_gen",
-        ":perfetto_protos_perfetto_config_ftrace_cpp_gen",
         ":perfetto_protos_perfetto_config_ftrace_zero_gen",
-        ":perfetto_protos_perfetto_config_gpu_cpp_gen",
         ":perfetto_protos_perfetto_config_gpu_zero_gen",
-        ":perfetto_protos_perfetto_config_inode_file_cpp_gen",
         ":perfetto_protos_perfetto_config_inode_file_zero_gen",
-        ":perfetto_protos_perfetto_config_interceptors_cpp_gen",
         ":perfetto_protos_perfetto_config_interceptors_zero_gen",
-        ":perfetto_protos_perfetto_config_power_cpp_gen",
         ":perfetto_protos_perfetto_config_power_zero_gen",
-        ":perfetto_protos_perfetto_config_process_stats_cpp_gen",
         ":perfetto_protos_perfetto_config_process_stats_zero_gen",
-        ":perfetto_protos_perfetto_config_profiling_cpp_gen",
         ":perfetto_protos_perfetto_config_profiling_zero_gen",
-        ":perfetto_protos_perfetto_config_statsd_cpp_gen",
         ":perfetto_protos_perfetto_config_statsd_zero_gen",
-        ":perfetto_protos_perfetto_config_sys_stats_cpp_gen",
         ":perfetto_protos_perfetto_config_sys_stats_zero_gen",
-        ":perfetto_protos_perfetto_config_system_info_cpp_gen",
         ":perfetto_protos_perfetto_config_system_info_zero_gen",
-        ":perfetto_protos_perfetto_config_track_event_cpp_gen",
         ":perfetto_protos_perfetto_config_track_event_zero_gen",
         ":perfetto_protos_perfetto_config_zero_gen",
-        ":perfetto_protos_perfetto_trace_android_cpp_gen",
         ":perfetto_protos_perfetto_trace_android_zero_gen",
-        ":perfetto_protos_perfetto_trace_chrome_cpp_gen",
         ":perfetto_protos_perfetto_trace_chrome_zero_gen",
-        ":perfetto_protos_perfetto_trace_etw_cpp_gen",
         ":perfetto_protos_perfetto_trace_etw_zero_gen",
-        ":perfetto_protos_perfetto_trace_filesystem_cpp_gen",
         ":perfetto_protos_perfetto_trace_filesystem_zero_gen",
-        ":perfetto_protos_perfetto_trace_ftrace_cpp_gen",
         ":perfetto_protos_perfetto_trace_ftrace_zero_gen",
-        ":perfetto_protos_perfetto_trace_gpu_cpp_gen",
         ":perfetto_protos_perfetto_trace_gpu_zero_gen",
-        ":perfetto_protos_perfetto_trace_interned_data_cpp_gen",
         ":perfetto_protos_perfetto_trace_interned_data_zero_gen",
-        ":perfetto_protos_perfetto_trace_minimal_cpp_gen",
         ":perfetto_protos_perfetto_trace_minimal_zero_gen",
-        ":perfetto_protos_perfetto_trace_non_minimal_cpp_gen",
         ":perfetto_protos_perfetto_trace_non_minimal_zero_gen",
-        ":perfetto_protos_perfetto_trace_perfetto_cpp_gen",
         ":perfetto_protos_perfetto_trace_perfetto_zero_gen",
-        ":perfetto_protos_perfetto_trace_power_cpp_gen",
         ":perfetto_protos_perfetto_trace_power_zero_gen",
         ":perfetto_protos_perfetto_trace_processor_zero_gen",
-        ":perfetto_protos_perfetto_trace_profiling_cpp_gen",
         ":perfetto_protos_perfetto_trace_profiling_zero_gen",
-        ":perfetto_protos_perfetto_trace_ps_cpp_gen",
         ":perfetto_protos_perfetto_trace_ps_zero_gen",
-        ":perfetto_protos_perfetto_trace_statsd_cpp_gen",
         ":perfetto_protos_perfetto_trace_statsd_zero_gen",
-        ":perfetto_protos_perfetto_trace_sys_stats_cpp_gen",
         ":perfetto_protos_perfetto_trace_sys_stats_zero_gen",
-        ":perfetto_protos_perfetto_trace_system_info_cpp_gen",
         ":perfetto_protos_perfetto_trace_system_info_zero_gen",
-        ":perfetto_protos_perfetto_trace_track_event_cpp_gen",
         ":perfetto_protos_perfetto_trace_track_event_zero_gen",
-        ":perfetto_protos_perfetto_trace_translation_cpp_gen",
         ":perfetto_protos_perfetto_trace_translation_zero_gen",
         ":perfetto_src_base_base",
         ":perfetto_src_protozero_protozero",
@@ -15257,70 +15239,38 @@
         "libz",
     ],
     generated_headers: [
-        "perfetto_protos_perfetto_common_cpp_gen_headers",
         "perfetto_protos_perfetto_common_zero_gen_headers",
-        "perfetto_protos_perfetto_config_android_cpp_gen_headers",
         "perfetto_protos_perfetto_config_android_zero_gen_headers",
-        "perfetto_protos_perfetto_config_cpp_gen_headers",
-        "perfetto_protos_perfetto_config_ftrace_cpp_gen_headers",
         "perfetto_protos_perfetto_config_ftrace_zero_gen_headers",
-        "perfetto_protos_perfetto_config_gpu_cpp_gen_headers",
         "perfetto_protos_perfetto_config_gpu_zero_gen_headers",
-        "perfetto_protos_perfetto_config_inode_file_cpp_gen_headers",
         "perfetto_protos_perfetto_config_inode_file_zero_gen_headers",
-        "perfetto_protos_perfetto_config_interceptors_cpp_gen_headers",
         "perfetto_protos_perfetto_config_interceptors_zero_gen_headers",
-        "perfetto_protos_perfetto_config_power_cpp_gen_headers",
         "perfetto_protos_perfetto_config_power_zero_gen_headers",
-        "perfetto_protos_perfetto_config_process_stats_cpp_gen_headers",
         "perfetto_protos_perfetto_config_process_stats_zero_gen_headers",
-        "perfetto_protos_perfetto_config_profiling_cpp_gen_headers",
         "perfetto_protos_perfetto_config_profiling_zero_gen_headers",
-        "perfetto_protos_perfetto_config_statsd_cpp_gen_headers",
         "perfetto_protos_perfetto_config_statsd_zero_gen_headers",
-        "perfetto_protos_perfetto_config_sys_stats_cpp_gen_headers",
         "perfetto_protos_perfetto_config_sys_stats_zero_gen_headers",
-        "perfetto_protos_perfetto_config_system_info_cpp_gen_headers",
         "perfetto_protos_perfetto_config_system_info_zero_gen_headers",
-        "perfetto_protos_perfetto_config_track_event_cpp_gen_headers",
         "perfetto_protos_perfetto_config_track_event_zero_gen_headers",
         "perfetto_protos_perfetto_config_zero_gen_headers",
-        "perfetto_protos_perfetto_trace_android_cpp_gen_headers",
         "perfetto_protos_perfetto_trace_android_zero_gen_headers",
-        "perfetto_protos_perfetto_trace_chrome_cpp_gen_headers",
         "perfetto_protos_perfetto_trace_chrome_zero_gen_headers",
-        "perfetto_protos_perfetto_trace_etw_cpp_gen_headers",
         "perfetto_protos_perfetto_trace_etw_zero_gen_headers",
-        "perfetto_protos_perfetto_trace_filesystem_cpp_gen_headers",
         "perfetto_protos_perfetto_trace_filesystem_zero_gen_headers",
-        "perfetto_protos_perfetto_trace_ftrace_cpp_gen_headers",
         "perfetto_protos_perfetto_trace_ftrace_zero_gen_headers",
-        "perfetto_protos_perfetto_trace_gpu_cpp_gen_headers",
         "perfetto_protos_perfetto_trace_gpu_zero_gen_headers",
-        "perfetto_protos_perfetto_trace_interned_data_cpp_gen_headers",
         "perfetto_protos_perfetto_trace_interned_data_zero_gen_headers",
-        "perfetto_protos_perfetto_trace_minimal_cpp_gen_headers",
         "perfetto_protos_perfetto_trace_minimal_zero_gen_headers",
-        "perfetto_protos_perfetto_trace_non_minimal_cpp_gen_headers",
         "perfetto_protos_perfetto_trace_non_minimal_zero_gen_headers",
-        "perfetto_protos_perfetto_trace_perfetto_cpp_gen_headers",
         "perfetto_protos_perfetto_trace_perfetto_zero_gen_headers",
-        "perfetto_protos_perfetto_trace_power_cpp_gen_headers",
         "perfetto_protos_perfetto_trace_power_zero_gen_headers",
         "perfetto_protos_perfetto_trace_processor_zero_gen_headers",
-        "perfetto_protos_perfetto_trace_profiling_cpp_gen_headers",
         "perfetto_protos_perfetto_trace_profiling_zero_gen_headers",
-        "perfetto_protos_perfetto_trace_ps_cpp_gen_headers",
         "perfetto_protos_perfetto_trace_ps_zero_gen_headers",
-        "perfetto_protos_perfetto_trace_statsd_cpp_gen_headers",
         "perfetto_protos_perfetto_trace_statsd_zero_gen_headers",
-        "perfetto_protos_perfetto_trace_sys_stats_cpp_gen_headers",
         "perfetto_protos_perfetto_trace_sys_stats_zero_gen_headers",
-        "perfetto_protos_perfetto_trace_system_info_cpp_gen_headers",
         "perfetto_protos_perfetto_trace_system_info_zero_gen_headers",
-        "perfetto_protos_perfetto_trace_track_event_cpp_gen_headers",
         "perfetto_protos_perfetto_trace_track_event_zero_gen_headers",
-        "perfetto_protos_perfetto_trace_translation_cpp_gen_headers",
         "perfetto_protos_perfetto_trace_translation_zero_gen_headers",
         "perfetto_src_trace_processor_importers_proto_gen_cc_chrome_track_event_descriptor",
         "perfetto_src_trace_processor_importers_proto_gen_cc_track_event_descriptor",
diff --git a/BUILD b/BUILD
index d678720..f355d4e 100644
--- a/BUILD
+++ b/BUILD
@@ -2528,6 +2528,7 @@
 perfetto_filegroup(
     name = "src_trace_processor_perfetto_sql_stdlib_sched_sched",
     srcs = [
+        "src/trace_processor/perfetto_sql/stdlib/sched/runnable.sql",
         "src/trace_processor/perfetto_sql/stdlib/sched/states.sql",
         "src/trace_processor/perfetto_sql/stdlib/sched/thread_executing_span.sql",
         "src/trace_processor/perfetto_sql/stdlib/sched/thread_level_parallelism.sql",
@@ -5222,6 +5223,7 @@
         "protos/perfetto/trace/track_event/counter_descriptor.proto",
         "protos/perfetto/trace/track_event/debug_annotation.proto",
         "protos/perfetto/trace/track_event/log_message.proto",
+        "protos/perfetto/trace/track_event/pixel_modem.proto",
         "protos/perfetto/trace/track_event/process_descriptor.proto",
         "protos/perfetto/trace/track_event/range_of_interest.proto",
         "protos/perfetto/trace/track_event/screenshot.proto",
diff --git a/CHANGELOG b/CHANGELOG
index 145aa78..abdf0f9 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -12,7 +12,11 @@
       of RING_BUFFER traces, removing artifacts such as never-ending slices
       starting at the beginning of the trace.
   SDK:
-    *
+    * "track_event" categories are disabled by default in the C API, if they
+      don't match anything in the data source config. This behavior differs from
+      the C++ API. Configs should include either `enabled_categories: "*"` or
+      `disable_categories: "*"` to explicitly specify the desired behavior that
+      work both for C and C++.
 
 
 v43.2 - 2024-03-07:
diff --git a/docs/instrumentation/tracing-sdk.md b/docs/instrumentation/tracing-sdk.md
index 49320e0..ea9393d 100644
--- a/docs/instrumentation/tracing-sdk.md
+++ b/docs/instrumentation/tracing-sdk.md
@@ -147,16 +147,16 @@
 very little complexity.
 
 To include your new track events in the trace, ensure that the `track_event`
-data source is included in the trace config. If you do not specify any
-categories then all non-debug categories will be included by default. However,
-you can also add just the categories you are interested in like so:
+data source is included in the trace config, with a list of enabled and disabled
+categories.
 
 ```protobuf
 data_sources {
   config {
     name: "track_event"
     track_event_config {
-    	enabled_categories: "rendering"
+        enabled_categories: "rendering"
+        disabled_categories: "*"
     }
   }
 }
diff --git a/docs/instrumentation/track-events.md b/docs/instrumentation/track-events.md
index 51618fd..f06ec91 100644
--- a/docs/instrumentation/track-events.md
+++ b/docs/instrumentation/track-events.md
@@ -262,17 +262,22 @@
 7. Pattern matches in disabled categories.
 8. Pattern matches in disabled tags.
 
-If none of the steps produced a match, the category is enabled by default. In
-other words, every category is implicitly enabled unless specifically disabled.
+If none of the steps produced a match, the category:
+* is enabled by default in the C++ API
+* is disabled by default in the C API.
+
+Specifying an `enabled_categories: "*"` or `disabled_categories: "*"` helps
+achieving a consistent behavior explicitly.
+
 For example:
 
 | Setting                         | Needed configuration                         |
 | ------------------------------- | -------------------------------------------- |
-| Enable just specific categories | `enabled_categories = [“foo”, “bar”, “baz”]` |
-|                                 | `disabled_categories = [“*”]`                |
-| Enable all non-slow categories  | (Happens by default.)                        |
-| Enable specific tags            | `disabled_tags = [“*”]`                      |
-|                                 | `enabled_tags = [“foo”, “bar”]`              |
+| Enable just specific categories | `enabled_categories = ["foo", "bar", "baz"]` |
+|                                 | `disabled_categories = ["*"]`                |
+| Enable all non-slow categories  | `enabled_categories = ["*"] `                |
+| Enable specific tags            | `disabled_tags = ["*"]`                      |
+|                                 | `enabled_tags = ["foo", "bar"]`              |
 
 ## Dynamic and test-only categories
 
diff --git a/protos/perfetto/config/perfetto_config.proto b/protos/perfetto/config/perfetto_config.proto
index f11c56d..6623597 100644
--- a/protos/perfetto/config/perfetto_config.proto
+++ b/protos/perfetto/config/perfetto_config.proto
@@ -3162,23 +3162,27 @@
   //   7. Pattern matches in disabled categories.
   //   8. Pattern matches in disabled tags.
   //
-  // If none of the steps produced a match, the category is enabled by default.
+  // If none of the steps produced a match:
+  //  - In the C++ SDK (`perfetto::Category`), categories are enabled by
+  //  default.
+  //  - In the C SDK (`PerfettoTeCategory`), categories are disabled by default.
   //
   // Examples:
   //
   //  - To enable all non-slow/debug categories:
   //
-  //       No configuration needed, happens by default.
+  //       enabled_categories: "*"
   //
-  //  - To enable a specific category:
+  //  - To enable specific categories:
   //
-  //       disabled_categories = ["*"]
-  //       enabled_categories = ["my_category"]
+  //       disabled_categories: "*"
+  //       enabled_categories: "my_category"
+  //       enabled_categories: "my_category2"
   //
   //  - To enable only categories with a specific tag:
   //
-  //       disabled_tags = ["*"]
-  //       enabled_tags = ["my_tag"]
+  //       disabled_tags: "*"
+  //       enabled_tags: "my_tag"
   //
 
   // Default: []
diff --git a/protos/perfetto/config/track_event/track_event_config.proto b/protos/perfetto/config/track_event/track_event_config.proto
index e4c0e4d..5c029e2 100644
--- a/protos/perfetto/config/track_event/track_event_config.proto
+++ b/protos/perfetto/config/track_event/track_event_config.proto
@@ -34,23 +34,27 @@
   //   7. Pattern matches in disabled categories.
   //   8. Pattern matches in disabled tags.
   //
-  // If none of the steps produced a match, the category is enabled by default.
+  // If none of the steps produced a match:
+  //  - In the C++ SDK (`perfetto::Category`), categories are enabled by
+  //  default.
+  //  - In the C SDK (`PerfettoTeCategory`), categories are disabled by default.
   //
   // Examples:
   //
   //  - To enable all non-slow/debug categories:
   //
-  //       No configuration needed, happens by default.
+  //       enabled_categories: "*"
   //
-  //  - To enable a specific category:
+  //  - To enable specific categories:
   //
-  //       disabled_categories = ["*"]
-  //       enabled_categories = ["my_category"]
+  //       disabled_categories: "*"
+  //       enabled_categories: "my_category"
+  //       enabled_categories: "my_category2"
   //
   //  - To enable only categories with a specific tag:
   //
-  //       disabled_tags = ["*"]
-  //       enabled_tags = ["my_tag"]
+  //       disabled_tags: "*"
+  //       enabled_tags: "my_tag"
   //
 
   // Default: []
diff --git a/protos/perfetto/metrics/android/startup_metric.proto b/protos/perfetto/metrics/android/startup_metric.proto
index c5ad849..e2ca850 100644
--- a/protos/perfetto/metrics/android/startup_metric.proto
+++ b/protos/perfetto/metrics/android/startup_metric.proto
@@ -158,13 +158,13 @@
     optional string destination_thread = 3;
     optional string destination_process = 4;
     // From
-    // https://cs.android.com/android/platform/superproject/+/main:external/perfetto/protos/perfetto/trace/ftrace/binder.proto;l=15;drc=7b6a788162a30802f4c9d8d7a30a54e25edd30f1
+    // https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/protos/perfetto/trace/ftrace/binder.proto;l=15;drc=7b6a788162a30802f4c9d8d7a30a54e25edd30f1
     optional string flags = 5;
     // From
-    // https://cs.android.com/android/platform/superproject/+/main:external/perfetto/protos/perfetto/trace/ftrace/binder.proto;l=14;drc=7b6a788162a30802f4c9d8d7a30a54e25edd30f1
+    // https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/protos/perfetto/trace/ftrace/binder.proto;l=14;drc=7b6a788162a30802f4c9d8d7a30a54e25edd30f1
     optional string code = 6;
     // From
-    // https://cs.android.com/android/platform/superproject/+/main:external/perfetto/protos/perfetto/trace/ftrace/binder.proto;l=37;drc=7b6a788162a30802f4c9d8d7a30a54e25edd30f1
+    // https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/protos/perfetto/trace/ftrace/binder.proto;l=37;drc=7b6a788162a30802f4c9d8d7a30a54e25edd30f1
     optional int64 data_size = 7;
   }
 
diff --git a/protos/perfetto/metrics/perfetto_merged_metrics.proto b/protos/perfetto/metrics/perfetto_merged_metrics.proto
index b2c3392..fbd42fd 100644
--- a/protos/perfetto/metrics/perfetto_merged_metrics.proto
+++ b/protos/perfetto/metrics/perfetto_merged_metrics.proto
@@ -2115,13 +2115,13 @@
     optional string destination_thread = 3;
     optional string destination_process = 4;
     // From
-    // https://cs.android.com/android/platform/superproject/+/main:external/perfetto/protos/perfetto/trace/ftrace/binder.proto;l=15;drc=7b6a788162a30802f4c9d8d7a30a54e25edd30f1
+    // https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/protos/perfetto/trace/ftrace/binder.proto;l=15;drc=7b6a788162a30802f4c9d8d7a30a54e25edd30f1
     optional string flags = 5;
     // From
-    // https://cs.android.com/android/platform/superproject/+/main:external/perfetto/protos/perfetto/trace/ftrace/binder.proto;l=14;drc=7b6a788162a30802f4c9d8d7a30a54e25edd30f1
+    // https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/protos/perfetto/trace/ftrace/binder.proto;l=14;drc=7b6a788162a30802f4c9d8d7a30a54e25edd30f1
     optional string code = 6;
     // From
-    // https://cs.android.com/android/platform/superproject/+/main:external/perfetto/protos/perfetto/trace/ftrace/binder.proto;l=37;drc=7b6a788162a30802f4c9d8d7a30a54e25edd30f1
+    // https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/protos/perfetto/trace/ftrace/binder.proto;l=37;drc=7b6a788162a30802f4c9d8d7a30a54e25edd30f1
     optional int64 data_size = 7;
   }
 
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index ca07b6f..74e49b6 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -3162,23 +3162,27 @@
   //   7. Pattern matches in disabled categories.
   //   8. Pattern matches in disabled tags.
   //
-  // If none of the steps produced a match, the category is enabled by default.
+  // If none of the steps produced a match:
+  //  - In the C++ SDK (`perfetto::Category`), categories are enabled by
+  //  default.
+  //  - In the C SDK (`PerfettoTeCategory`), categories are disabled by default.
   //
   // Examples:
   //
   //  - To enable all non-slow/debug categories:
   //
-  //       No configuration needed, happens by default.
+  //       enabled_categories: "*"
   //
-  //  - To enable a specific category:
+  //  - To enable specific categories:
   //
-  //       disabled_categories = ["*"]
-  //       enabled_categories = ["my_category"]
+  //       disabled_categories: "*"
+  //       enabled_categories: "my_category"
+  //       enabled_categories: "my_category2"
   //
   //  - To enable only categories with a specific tag:
   //
-  //       disabled_tags = ["*"]
-  //       enabled_tags = ["my_tag"]
+  //       disabled_tags: "*"
+  //       enabled_tags: "my_tag"
   //
 
   // Default: []
@@ -12126,6 +12130,16 @@
 
 // End of protos/perfetto/trace/track_event/chrome_window_handle_event_info.proto
 
+// Begin of protos/perfetto/trace/track_event/pixel_modem.proto
+
+// Event insights emitted by the Pixel modem.
+message PixelModemEventInsight {
+  // Opaque string containing arguments from the modem.
+  optional string detokenized_message = 1;
+}
+
+// End of protos/perfetto/trace/track_event/pixel_modem.proto
+
 // Begin of protos/perfetto/trace/track_event/screenshot.proto
 
 message Screenshot {
@@ -12211,7 +12225,7 @@
 // their default track association) can be emitted as part of a
 // TrackEventDefaults message.
 //
-// Next reserved id: 13 (up to 15). Next id: 51.
+// Next reserved id: 13 (up to 15). Next id: 52.
 message TrackEvent {
   // Names of categories of the event. In the client library, categories are a
   // way to turn groups of individual events on or off.
@@ -12365,6 +12379,7 @@
       43;
   optional ChromeActiveProcesses chrome_active_processes = 49;
   optional Screenshot screenshot = 50;
+  optional PixelModemEventInsight pixel_modem_event_insight = 51;
 
   // This field is used only if the source location represents the function that
   // executes during this event.
@@ -13771,7 +13786,7 @@
 // trace.
 message StatsdAtom {
   // Atom should be filled with an Atom proto from:
-  // https://cs.android.com/android/platform/superproject/+/master:frameworks/proto_logging/stats/atoms.proto?q=f:stats%2Fatoms.proto$%20message%5C%20Atom
+  // https://cs.android.com/android/platform/superproject/main/+/main:frameworks/proto_logging/stats/atoms.proto?q=f:stats%2Fatoms.proto$%20message%5C%20Atom
   // We don't reference Atom directly here since we don't want to import
   // Atom.proto and all its transitive dependencies into Perfetto.
   // atom and timestamp_nanos have the same cardinality
diff --git a/protos/perfetto/trace/statsd/statsd_atom.proto b/protos/perfetto/trace/statsd/statsd_atom.proto
index 62ca960..8e487e8 100644
--- a/protos/perfetto/trace/statsd/statsd_atom.proto
+++ b/protos/perfetto/trace/statsd/statsd_atom.proto
@@ -26,7 +26,7 @@
 // trace.
 message StatsdAtom {
   // Atom should be filled with an Atom proto from:
-  // https://cs.android.com/android/platform/superproject/+/master:frameworks/proto_logging/stats/atoms.proto?q=f:stats%2Fatoms.proto$%20message%5C%20Atom
+  // https://cs.android.com/android/platform/superproject/main/+/main:frameworks/proto_logging/stats/atoms.proto?q=f:stats%2Fatoms.proto$%20message%5C%20Atom
   // We don't reference Atom directly here since we don't want to import
   // Atom.proto and all its transitive dependencies into Perfetto.
   // atom and timestamp_nanos have the same cardinality
diff --git a/protos/perfetto/trace/track_event/BUILD.gn b/protos/perfetto/trace/track_event/BUILD.gn
index c101c46..00a5ff5 100644
--- a/protos/perfetto/trace/track_event/BUILD.gn
+++ b/protos/perfetto/trace/track_event/BUILD.gn
@@ -35,6 +35,7 @@
     "counter_descriptor.proto",
     "debug_annotation.proto",
     "log_message.proto",
+    "pixel_modem.proto",
     "process_descriptor.proto",
     "range_of_interest.proto",
     "screenshot.proto",
diff --git a/protos/perfetto/trace/track_event/pixel_modem.proto b/protos/perfetto/trace/track_event/pixel_modem.proto
new file mode 100644
index 0000000..ffe9062
--- /dev/null
+++ b/protos/perfetto/trace/track_event/pixel_modem.proto
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+package perfetto.protos;
+
+// Event insights emitted by the Pixel modem.
+message PixelModemEventInsight {
+  // Opaque string containing arguments from the modem.
+  optional string detokenized_message = 1;
+}
diff --git a/protos/perfetto/trace/track_event/track_event.proto b/protos/perfetto/trace/track_event/track_event.proto
index 325465d..2b9d35c 100644
--- a/protos/perfetto/trace/track_event/track_event.proto
+++ b/protos/perfetto/trace/track_event/track_event.proto
@@ -33,6 +33,7 @@
 import "protos/perfetto/trace/track_event/chrome_renderer_scheduler_state.proto";
 import "protos/perfetto/trace/track_event/chrome_user_event.proto";
 import "protos/perfetto/trace/track_event/chrome_window_handle_event_info.proto";
+import "protos/perfetto/trace/track_event/pixel_modem.proto";
 import "protos/perfetto/trace/track_event/screenshot.proto";
 import "protos/perfetto/trace/track_event/source_location.proto";
 
@@ -103,7 +104,7 @@
 // their default track association) can be emitted as part of a
 // TrackEventDefaults message.
 //
-// Next reserved id: 13 (up to 15). Next id: 51.
+// Next reserved id: 13 (up to 15). Next id: 52.
 message TrackEvent {
   // Names of categories of the event. In the client library, categories are a
   // way to turn groups of individual events on or off.
@@ -257,6 +258,7 @@
       43;
   optional ChromeActiveProcesses chrome_active_processes = 49;
   optional Screenshot screenshot = 50;
+  optional PixelModemEventInsight pixel_modem_event_insight = 51;
 
   // This field is used only if the source location represents the function that
   // executes during this event.
diff --git a/protos/third_party/statsd/shell_config.proto b/protos/third_party/statsd/shell_config.proto
index 98a3e67..49ec142 100644
--- a/protos/third_party/statsd/shell_config.proto
+++ b/protos/third_party/statsd/shell_config.proto
@@ -19,7 +19,7 @@
 package perfetto.protos;
 
 // This is a manual import of ShellSubscription:
-// https://cs.android.com/android/platform/superproject/+/main:packages/modules/StatsD/statsd/src/shell/shell_config.proto?q=f:statsD%2Fstatsd%20f:shell_config.proto
+// https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/StatsD/statsd/src/shell/shell_config.proto?q=f:statsD%2Fstatsd%20f:shell_config.proto
 // and its transitive dependencies.
 
 message StatsdShellSubscription {
diff --git a/protos/third_party/statsd/shell_data.proto b/protos/third_party/statsd/shell_data.proto
index c5180e8..3896a36 100644
--- a/protos/third_party/statsd/shell_data.proto
+++ b/protos/third_party/statsd/shell_data.proto
@@ -19,7 +19,7 @@
 package perfetto.proto;
 
 // This is a manual import of ShellData:
-// https://cs.android.com/android/platform/superproject/+/main:packages/modules/StatsD/statsd/src/shell/shell_data.proto;l=27;drc=d2e51ecdf08753688fb889b657dcba60adb994f3
+// https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/StatsD/statsd/src/shell/shell_data.proto;l=27;drc=d2e51ecdf08753688fb889b657dcba60adb994f3
 // This must exactly match perfetto.protos.StatsdAtom.
 message ShellData {
   repeated bytes atom = 1;
diff --git a/src/profiling/memory/README.md b/src/profiling/memory/README.md
index 8e00c7f..23e6846 100644
--- a/src/profiling/memory/README.md
+++ b/src/profiling/memory/README.md
@@ -21,10 +21,10 @@
 
 ### Interceptors
 bionic: uses bionic [malloc dispatch](
-https://cs.android.com/android/platform/superproject/+/main:bionic/libc/private/bionic_malloc_dispatch.h)
+https://cs.android.com/android/platform/superproject/main/+/main:bionic/libc/private/bionic_malloc_dispatch.h)
 to intercept allocation functions on Android. This works by placing a library
 on a pre-defined path, which gets [loaded by Bionic](
-https://cs.android.com/android/platform/superproject/+/main:bionic/libc/bionic/malloc_heapprofd.cpp).
+https://cs.android.com/android/platform/superproject/main/+/main:bionic/libc/bionic/malloc_heapprofd.cpp).
 
 glibc: generates a library exposing the allocation functions. This library
        should be used for `LD_PRELOAD` and uses the glibc specific symbols
diff --git a/src/shared_lib/test/api_integrationtest.cc b/src/shared_lib/test/api_integrationtest.cc
index 0dff900..1fa6278 100644
--- a/src/shared_lib/test/api_integrationtest.cc
+++ b/src/shared_lib/test/api_integrationtest.cc
@@ -1028,7 +1028,7 @@
   EXPECT_FALSE(std::atomic_load(cat3.enabled));
 }
 
-TEST_F(SharedLibTrackEventTest, TrackEventFastpathEmptyConfigEnableAllCats) {
+TEST_F(SharedLibTrackEventTest, TrackEventFastpathEmptyConfigDisablesAllCats) {
   ASSERT_FALSE(std::atomic_load(cat1.enabled));
   ASSERT_FALSE(std::atomic_load(cat2.enabled));
   ASSERT_FALSE(std::atomic_load(cat3.enabled));
@@ -1036,9 +1036,9 @@
   TracingSession tracing_session =
       TracingSession::Builder().set_data_source_name("track_event").Build();
 
-  EXPECT_TRUE(std::atomic_load(cat1.enabled));
-  EXPECT_TRUE(std::atomic_load(cat2.enabled));
-  EXPECT_TRUE(std::atomic_load(cat3.enabled));
+  EXPECT_FALSE(std::atomic_load(cat1.enabled));
+  EXPECT_FALSE(std::atomic_load(cat2.enabled));
+  EXPECT_FALSE(std::atomic_load(cat3.enabled));
 }
 
 TEST_F(SharedLibTrackEventTest, TrackEventFastpathOneCatEnabled) {
@@ -1058,9 +1058,12 @@
 }
 
 TEST_F(SharedLibTrackEventTest, TrackEventHlCategory) {
-  TracingSession tracing_session =
-      TracingSession::Builder().set_data_source_name("track_event").Build();
+  TracingSession tracing_session = TracingSession::Builder()
+                                       .set_data_source_name("track_event")
+                                       .add_enabled_category("*")
+                                       .Build();
 
+  EXPECT_TRUE(std::atomic_load(cat1.enabled));
   PERFETTO_TE(cat1, PERFETTO_TE_INSTANT(""));
 
   tracing_session.StopBlocking();
@@ -1194,8 +1197,10 @@
 }
 
 TEST_F(SharedLibTrackEventTest, TrackEventHlInstant) {
-  TracingSession tracing_session =
-      TracingSession::Builder().set_data_source_name("track_event").Build();
+  TracingSession tracing_session = TracingSession::Builder()
+                                       .set_data_source_name("track_event")
+                                       .add_enabled_category("*")
+                                       .Build();
 
   PERFETTO_TE(cat1, PERFETTO_TE_INSTANT("event"));
 
@@ -1235,8 +1240,10 @@
 }
 
 TEST_F(SharedLibTrackEventTest, TrackEventLlInstant) {
-  TracingSession tracing_session =
-      TracingSession::Builder().set_data_source_name("track_event").Build();
+  TracingSession tracing_session = TracingSession::Builder()
+                                       .set_data_source_name("track_event")
+                                       .add_enabled_category("*")
+                                       .Build();
 
   if (PERFETTO_UNLIKELY(PERFETTO_ATOMIC_LOAD_EXPLICIT(
           cat1.enabled, PERFETTO_MEMORY_ORDER_RELAXED))) {
@@ -1315,8 +1322,10 @@
 }
 
 TEST_F(SharedLibTrackEventTest, TrackEventHlInstantNoIntern) {
-  TracingSession tracing_session =
-      TracingSession::Builder().set_data_source_name("track_event").Build();
+  TracingSession tracing_session = TracingSession::Builder()
+                                       .set_data_source_name("track_event")
+                                       .add_enabled_category("*")
+                                       .Build();
 
   PERFETTO_TE(cat1, PERFETTO_TE_INSTANT("event"), PERFETTO_TE_NO_INTERN());
 
@@ -1345,8 +1354,10 @@
 }
 
 TEST_F(SharedLibTrackEventTest, TrackEventHlDbgArg) {
-  TracingSession tracing_session =
-      TracingSession::Builder().set_data_source_name("track_event").Build();
+  TracingSession tracing_session = TracingSession::Builder()
+                                       .set_data_source_name("track_event")
+                                       .add_enabled_category("*")
+                                       .Build();
 
   PERFETTO_TE(cat1, PERFETTO_TE_INSTANT("event"),
               PERFETTO_TE_ARG_UINT64("arg_name", 42));
@@ -1413,8 +1424,10 @@
 }
 
 TEST_F(SharedLibTrackEventTest, TrackEventHlNamedTrack) {
-  TracingSession tracing_session =
-      TracingSession::Builder().set_data_source_name("track_event").Build();
+  TracingSession tracing_session = TracingSession::Builder()
+                                       .set_data_source_name("track_event")
+                                       .add_enabled_category("*")
+                                       .Build();
 
   PERFETTO_TE(cat1, PERFETTO_TE_INSTANT("event"),
               PERFETTO_TE_NAMED_TRACK("MyTrack", 1, 2));
@@ -1453,8 +1466,10 @@
 }
 
 TEST_F(SharedLibTrackEventTest, TrackEventHlRegisteredCounter) {
-  TracingSession tracing_session =
-      TracingSession::Builder().set_data_source_name("track_event").Build();
+  TracingSession tracing_session = TracingSession::Builder()
+                                       .set_data_source_name("track_event")
+                                       .add_enabled_category("*")
+                                       .Build();
 
   PerfettoTeRegisteredTrack my_counter_track;
   PerfettoTeCounterTrackRegister(&my_counter_track, "MyCounter",
diff --git a/src/shared_lib/test/utils.cc b/src/shared_lib/test/utils.cc
index 7c0ed8d..966c7f0 100644
--- a/src/shared_lib/test/utils.cc
+++ b/src/shared_lib/test/utils.cc
@@ -68,7 +68,7 @@
 
       perfetto_protos_DataSourceConfig_set_cstr_name(&ds_cfg,
                                                      data_source_name_.c_str());
-      if (!enabled_categories_.empty() && !disabled_categories_.empty()) {
+      if (!enabled_categories_.empty() || !disabled_categories_.empty()) {
         perfetto_protos_TrackEventConfig te_cfg;
         perfetto_protos_DataSourceConfig_begin_track_event_config(&ds_cfg,
                                                                   &te_cfg);
diff --git a/src/shared_lib/track_event.cc b/src/shared_lib/track_event.cc
index 5a4b87c..bbafde7 100644
--- a/src/shared_lib/track_event.cc
+++ b/src/shared_lib/track_event.cc
@@ -137,8 +137,9 @@
     }
   }
 
-  // If nothing matched, enable the category by default.
-  return true;
+  // If nothing matched, the category is disabled by default. N.B. this behavior
+  // is different than the C++ TrackEvent API.
+  return false;
 }
 
 static bool IsRegisteredCategoryEnabled(
diff --git a/src/trace_processor/importers/proto/jit_tracker.cc b/src/trace_processor/importers/proto/jit_tracker.cc
index 4cab6f1..224f8d9 100644
--- a/src/trace_processor/importers/proto/jit_tracker.cc
+++ b/src/trace_processor/importers/proto/jit_tracker.cc
@@ -31,7 +31,6 @@
 namespace perfetto::trace_processor {
 
 JitTracker::JitTracker(TraceProcessorContext* context) : context_(context) {}
-
 JitTracker::~JitTracker() = default;
 
 JitCache* JitTracker::CreateJitCache(std::string name,
diff --git a/src/trace_processor/importers/proto/v8_module.cc b/src/trace_processor/importers/proto/v8_module.cc
index 94684e3..f90a71c 100644
--- a/src/trace_processor/importers/proto/v8_module.cc
+++ b/src/trace_processor/importers/proto/v8_module.cc
@@ -16,16 +16,21 @@
 
 #include "src/trace_processor/importers/proto/v8_module.h"
 
+#include <cstdint>
 #include <optional>
 
+#include "perfetto/base/logging.h"
 #include "protos/perfetto/trace/chrome/v8.pbzero.h"
 #include "protos/perfetto/trace/trace_packet.pbzero.h"
 #include "src/trace_processor/importers/common/parser_types.h"
+#include "src/trace_processor/importers/common/process_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/v8_sequence_state.h"
 #include "src/trace_processor/importers/proto/v8_tracker.h"
 #include "src/trace_processor/storage/stats.h"
 #include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/tables/metadata_tables_py.h"
 #include "src/trace_processor/tables/v8_tables_py.h"
 
 namespace perfetto {
@@ -33,6 +38,7 @@
 namespace {
 
 using ::perfetto::protos::pbzero::TracePacket;
+using ::perfetto::protos::pbzero::V8CodeDefaults;
 using ::perfetto::protos::pbzero::V8CodeMove;
 using ::perfetto::protos::pbzero::V8InternalCode;
 using ::perfetto::protos::pbzero::V8JsCode;
@@ -85,6 +91,54 @@
   }
 }
 
+template <typename CodeDecoder>
+std::optional<UniqueTid> V8Module::GetUtid(
+    PacketSequenceStateGeneration& generation,
+    IsolateId isolate_id,
+    const CodeDecoder& code) {
+  auto* pid = isolate_to_pid_.Find(isolate_id);
+  if (!pid) {
+    tables::ProcessTable::Id upid(
+        context_->storage->v8_isolate_table().FindById(isolate_id)->upid());
+    pid = isolate_to_pid_
+              .Insert(isolate_id,
+                      context_->storage->process_table().FindById(upid)->pid())
+              .first;
+  }
+
+  if (code.has_tid()) {
+    return context_->process_tracker->UpdateThread(code.tid(), *pid);
+  }
+
+  if (auto tid = GetDefaultTid(generation); tid.has_value()) {
+    return context_->process_tracker->UpdateThread(*tid, *pid);
+  }
+
+  return std::nullopt;
+}
+
+std::optional<uint32_t> V8Module::GetDefaultTid(
+    PacketSequenceStateGeneration& generation) const {
+  auto* tp_defaults = generation.GetTracePacketDefaults();
+  if (!tp_defaults) {
+    context_->storage->IncrementStats(stats::v8_no_defaults);
+    return std::nullopt;
+  }
+  if (!tp_defaults->has_v8_code_defaults()) {
+    context_->storage->IncrementStats(stats::v8_no_defaults);
+    return std::nullopt;
+  }
+
+  V8CodeDefaults::Decoder v8_defaults(tp_defaults->v8_code_defaults());
+
+  if (!v8_defaults.has_tid()) {
+    context_->storage->IncrementStats(stats::v8_no_defaults);
+    return std::nullopt;
+  }
+
+  return v8_defaults.tid();
+}
+
 void V8Module::ParseV8JsCode(protozero::ConstBytes bytes,
                              int64_t ts,
                              const TracePacketData& data) {
@@ -97,13 +151,19 @@
     return;
   }
 
+  std::optional<UniqueTid> utid =
+      GetUtid(*data.sequence_state, *v8_isolate_id, code);
+  if (!utid) {
+    return;
+  }
+
   auto v8_function_id =
       state.GetOrInsertJsFunction(code.v8_js_function_iid(), *v8_isolate_id);
   if (!v8_function_id) {
     return;
   }
 
-  v8_tracker_->AddJsCode(ts, *v8_isolate_id, *v8_function_id, code);
+  v8_tracker_->AddJsCode(ts, *utid, *v8_isolate_id, *v8_function_id, code);
 }
 
 void V8Module::ParseV8InternalCode(protozero::ConstBytes bytes,
@@ -118,7 +178,13 @@
     return;
   }
 
-  v8_tracker_->AddInternalCode(ts, *v8_isolate_id, code);
+  std::optional<UniqueTid> utid =
+      GetUtid(*data.sequence_state, *v8_isolate_id, code);
+  if (!utid) {
+    return;
+  }
+
+  v8_tracker_->AddInternalCode(ts, *utid, *v8_isolate_id, code);
 }
 
 void V8Module::ParseV8WasmCode(protozero::ConstBytes bytes,
@@ -139,7 +205,13 @@
     return;
   }
 
-  v8_tracker_->AddWasmCode(ts, *v8_isolate_id, *v8_wasm_script_id, code);
+  std::optional<UniqueTid> utid =
+      GetUtid(*data.sequence_state, *v8_isolate_id, code);
+  if (!utid) {
+    return;
+  }
+
+  v8_tracker_->AddWasmCode(ts, *utid, *v8_isolate_id, *v8_wasm_script_id, code);
 }
 
 void V8Module::ParseV8RegExpCode(protozero::ConstBytes bytes,
@@ -154,7 +226,13 @@
     return;
   }
 
-  v8_tracker_->AddRegExpCode(ts, *v8_isolate_id, code);
+  std::optional<UniqueTid> utid =
+      GetUtid(*data.sequence_state, *v8_isolate_id, code);
+  if (!utid) {
+    return;
+  }
+
+  v8_tracker_->AddRegExpCode(ts, *utid, *v8_isolate_id, code);
 }
 
 void V8Module::ParseV8CodeMove(protozero::ConstBytes bytes,
@@ -163,7 +241,7 @@
   V8SequenceState& state = *data.sequence_state->GetOrCreate<V8SequenceState>();
   protos::pbzero::V8CodeMove::Decoder v8_code_move(bytes);
 
-  std::optional<tables::V8IsolateTable::Id> isolate_id =
+  std::optional<IsolateId> isolate_id =
       state.GetOrInsertIsolate(v8_code_move.isolate_iid());
   if (!isolate_id) {
     return;
diff --git a/src/trace_processor/importers/proto/v8_module.h b/src/trace_processor/importers/proto/v8_module.h
index de3f896..ea49b32 100644
--- a/src/trace_processor/importers/proto/v8_module.h
+++ b/src/trace_processor/importers/proto/v8_module.h
@@ -18,9 +18,13 @@
 #define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_V8_MODULE_H_
 
 #include <cstdint>
+#include <optional>
 
+#include "perfetto/ext/base/flat_hash_map.h"
 #include "perfetto/protozero/field.h"
 #include "src/trace_processor/importers/proto/proto_importer_module.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/tables/v8_tables_py.h"
 
 namespace perfetto {
 namespace protos {
@@ -74,8 +78,20 @@
                        int64_t ts,
                        const TracePacketData& data);
 
+  // Determine the utid for a code event.
+  // If the passed in decoder has no tid field this method will try the
+  // TracePacketDefaults.
+  template <typename CodeDecoder>
+  std::optional<UniqueTid> GetUtid(PacketSequenceStateGeneration& generation,
+                                   tables::V8IsolateTable::Id isolate_id,
+                                   const CodeDecoder& code);
+  std::optional<uint32_t> GetDefaultTid(
+      PacketSequenceStateGeneration& generation) const;
   TraceProcessorContext* const context_;
   V8Tracker* const v8_tracker_;
+  // Caches isolate to pid associations. Used to compute the utid for code
+  // events.
+  base::FlatHashMap<tables::V8IsolateTable::Id, uint32_t> isolate_to_pid_;
 };
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/importers/proto/v8_sequence_state.cc b/src/trace_processor/importers/proto/v8_sequence_state.cc
index 33abb24..4f0a4368 100644
--- a/src/trace_processor/importers/proto/v8_sequence_state.cc
+++ b/src/trace_processor/importers/proto/v8_sequence_state.cc
@@ -19,7 +19,6 @@
 
 #include "protos/perfetto/trace/chrome/v8.pbzero.h"
 #include "protos/perfetto/trace/interned_data/interned_data.pbzero.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/string_encoding_utils.h"
 #include "src/trace_processor/importers/proto/v8_tracker.h"
@@ -46,8 +45,7 @@
 
 V8SequenceState::~V8SequenceState() = default;
 
-std::optional<tables::V8IsolateTable::Id> V8SequenceState::GetOrInsertIsolate(
-    uint64_t iid) {
+std::optional<IsolateId> V8SequenceState::GetOrInsertIsolate(uint64_t iid) {
   if (auto* id = isolates_.Find(iid); id != nullptr) {
     return *id;
   }
@@ -64,8 +62,7 @@
 }
 
 std::optional<tables::V8JsFunctionTable::Id>
-V8SequenceState::GetOrInsertJsFunction(uint64_t iid,
-                                       tables::V8IsolateTable::Id isolate_id) {
+V8SequenceState::GetOrInsertJsFunction(uint64_t iid, IsolateId isolate_id) {
   if (auto* id = js_functions_.Find(iid); id != nullptr) {
     return *id;
   }
@@ -98,8 +95,7 @@
 }
 
 std::optional<tables::V8WasmScriptTable::Id>
-V8SequenceState::GetOrInsertWasmScript(uint64_t iid,
-                                       tables::V8IsolateTable::Id isolate_id) {
+V8SequenceState::GetOrInsertWasmScript(uint64_t iid, IsolateId isolate_id) {
   if (auto* id = wasm_scripts_.Find(iid); id != nullptr) {
     return *id;
   }
@@ -118,7 +114,7 @@
 
 std::optional<tables::V8JsScriptTable::Id> V8SequenceState::GetOrInsertJsScript(
     uint64_t iid,
-    tables::V8IsolateTable::Id v8_isolate_id) {
+    IsolateId v8_isolate_id) {
   if (auto* id = js_scripts_.Find(iid); id != nullptr) {
     return *id;
   }
@@ -157,10 +153,10 @@
         base::StringView(ConvertLatin1ToUtf8(function_name.latin1())));
   } else if (function_name.has_utf16_le()) {
     id = storage.InternString(
-        base::StringView(ConvertUtf16LeToUtf8(function_name.latin1())));
+        base::StringView(ConvertUtf16LeToUtf8(function_name.utf16_le())));
   } else if (function_name.has_utf16_be()) {
     id = storage.InternString(
-        base::StringView(ConvertUtf16BeToUtf8(function_name.latin1())));
+        base::StringView(ConvertUtf16BeToUtf8(function_name.utf16_be())));
   } else {
     id = storage.InternString("");
   }
diff --git a/src/trace_processor/importers/proto/v8_tracker.cc b/src/trace_processor/importers/proto/v8_tracker.cc
index 963ad5b..8bacba0 100644
--- a/src/trace_processor/importers/proto/v8_tracker.cc
+++ b/src/trace_processor/importers/proto/v8_tracker.cc
@@ -17,16 +17,28 @@
 #include "src/trace_processor/importers/proto/v8_tracker.h"
 
 #include <cstdint>
+#include <memory>
+#include <optional>
+#include <string>
 #include <utility>
 
 #include "perfetto/base/logging.h"
+#include "perfetto/ext/base/base64.h"
+#include "perfetto/ext/base/flat_hash_map.h"
 #include "perfetto/ext/base/string_view.h"
 #include "perfetto/protozero/field.h"
+#include "perfetto/trace_processor/trace_blob.h"
+#include "perfetto/trace_processor/trace_blob_view.h"
 #include "protos/perfetto/trace/chrome/v8.pbzero.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/proto/jit_tracker.h"
 #include "src/trace_processor/importers/proto/string_encoding_utils.h"
+#include "src/trace_processor/storage/stats.h"
 #include "src/trace_processor/storage/trace_storage.h"
-#include "src/trace_processor/tables/metadata_tables_py.h"
+#include "src/trace_processor/tables/jit_tables_py.h"
 #include "src/trace_processor/tables/v8_tables_py.h"
 #include "src/trace_processor/types/trace_processor_context.h"
 
@@ -40,9 +52,40 @@
 using ::perfetto::protos::pbzero::InternedV8WasmScript;
 using ::perfetto::protos::pbzero::V8InternalCode;
 using ::perfetto::protos::pbzero::V8JsCode;
+using ::perfetto::protos::pbzero::V8RegExpCode;
 using ::perfetto::protos::pbzero::V8String;
 using ::perfetto::protos::pbzero::V8WasmCode;
 
+bool IsInterpretedCode(const V8JsCode::Decoder& code) {
+  switch (code.tier()) {
+    case V8JsCode::TIER_IGNITION:
+      return true;
+
+    case V8JsCode::TIER_UNKNOWN:
+    case V8JsCode::TIER_SPARKPLUG:
+    case V8JsCode::TIER_MAGLEV:
+    case V8JsCode::TIER_TURBOSHAFT:
+    case V8JsCode::TIER_TURBOFAN:
+      return false;
+  }
+  PERFETTO_FATAL("Unreachable");
+}
+
+bool IsNativeCode(const V8JsCode::Decoder& code) {
+  switch (code.tier()) {
+    case V8JsCode::TIER_UNKNOWN:
+    case V8JsCode::TIER_IGNITION:
+      return false;
+
+    case V8JsCode::TIER_SPARKPLUG:
+    case V8JsCode::TIER_MAGLEV:
+    case V8JsCode::TIER_TURBOSHAFT:
+    case V8JsCode::TIER_TURBOFAN:
+      return true;
+  }
+  PERFETTO_FATAL("Unreachable");
+}
+
 base::StringView JsScriptTypeToString(int32_t type) {
   if (type < protos::pbzero::InternedV8JsScript_Type_MIN ||
       type > protos::pbzero::InternedV8JsScript_Type_MAX) {
@@ -65,53 +108,174 @@
   return name.substr(5);
 }
 
+base::StringView JsCodeTierToString(int32_t tier) {
+  if (tier < protos::pbzero::V8JsCode_Tier_MIN ||
+      tier > protos::pbzero::V8JsCode_Tier_MAX) {
+    return "UNKNOWN";
+  }
+  base::StringView name = V8JsCode::Tier_Name(V8JsCode::Tier(tier));
+  // Remove the "TIER_" prefix
+  return name.substr(5);
+}
+
+base::StringView InternalCodeTypeToString(int32_t type) {
+  if (type < protos::pbzero::V8InternalCode_Type_MIN ||
+      type > protos::pbzero::V8InternalCode_Type_MAX) {
+    return "UNKNOWN";
+  }
+  base::StringView name = V8InternalCode::Type_Name(V8InternalCode::Type(type));
+  // Remove the "TYPE_" prefix
+  return name.substr(5);
+}
+
+base::StringView WasmCodeTierToString(int32_t tier) {
+  if (tier < protos::pbzero::V8WasmCode_Tier_MIN ||
+      tier > protos::pbzero::V8WasmCode_Tier_MAX) {
+    return "UNKNOWN";
+  }
+  base::StringView name = V8WasmCode::Tier_Name(V8WasmCode::Tier(tier));
+  // Remove the "TIER_" prefix
+  return name.substr(5);
+}
+
 }  // namespace
 
 V8Tracker::V8Tracker(TraceProcessorContext* context) : context_(context) {}
 
 V8Tracker::~V8Tracker() = default;
 
-tables::V8IsolateTable::Id V8Tracker::InternIsolate(
-    protozero::ConstBytes bytes) {
+IsolateId V8Tracker::InternIsolate(protozero::ConstBytes bytes) {
   InternedV8Isolate::Decoder isolate(bytes);
-  const UniquePid upid =
-      context_->process_tracker->GetOrCreateProcess(isolate.pid());
 
-  if (auto* id =
-          isolate_index_.Find(std::make_pair(upid, isolate.isolate_id()));
-      id) {
+  const IsolateKey isolate_key{
+      context_->process_tracker->GetOrCreateProcess(isolate.pid()),
+      isolate.isolate_id()};
+
+  if (auto* id = isolate_index_.Find(isolate_key); id) {
     return *id;
   }
 
+  return *isolate_index_.Insert(isolate_key, CreateIsolate(isolate)).first;
+}
+
+UserMemoryMapping* V8Tracker::FindEmbeddedBlobMapping(
+    UniquePid upid,
+    AddressRange embedded_blob_code) const {
+  UserMemoryMapping* m = context_->mapping_tracker->FindUserMappingForAddress(
+      upid, embedded_blob_code.start());
+  if (!m) {
+    return nullptr;
+  }
+
+  if (m->memory_range().start() == embedded_blob_code.start() &&
+      embedded_blob_code.end() <= m->memory_range().end()) {
+    return m;
+  }
+
+  return nullptr;
+}
+
+std::pair<V8Tracker::IsolateCodeRanges, bool> V8Tracker::GetIsolateCodeRanges(
+    UniquePid upid,
+    const protos::pbzero::InternedV8Isolate::Decoder& isolate) {
   // TODO(carlscab): Implement support for no code range
   PERFETTO_CHECK(isolate.has_code_range());
 
+  IsolateCodeRanges res;
+
+  InternedV8Isolate::CodeRange::Decoder code_range_proto(isolate.code_range());
+  AddressRange code_range = AddressRange::FromStartAndSize(
+      code_range_proto.base_address(), code_range_proto.size());
+
+  res.heap_code.Add(code_range);
+  if (isolate.has_embedded_blob_code_start_address() &&
+      isolate.embedded_blob_code_size() != 0) {
+    res.embedded_blob = AddressRange::FromStartAndSize(
+        isolate.embedded_blob_code_start_address(),
+        isolate.embedded_blob_code_size());
+
+    if (UserMemoryMapping* m =
+            FindEmbeddedBlobMapping(upid, *res.embedded_blob);
+        m) {
+      res.embedded_blob = m->memory_range();
+    }
+
+    res.heap_code.Remove(*res.embedded_blob);
+  }
+
+  return {std::move(res), code_range_proto.is_process_wide()};
+}
+
+AddressRangeMap<JitCache*> V8Tracker::CreateJitCaches(
+    UniquePid upid,
+    const IsolateCodeRanges& code_ranges) {
+  JitTracker* const jit_tracker = JitTracker::GetOrCreate(context_);
+  AddressRangeMap<JitCache*> jit_caches;
+  for (const AddressRange& r : code_ranges.heap_code) {
+    jit_caches.Emplace(r, jit_tracker->CreateJitCache("v8 code", upid, r));
+  }
+  if (code_ranges.embedded_blob) {
+    jit_caches.Emplace(*code_ranges.embedded_blob,
+                       jit_tracker->CreateJitCache("v8 blob", upid,
+                                                   *code_ranges.embedded_blob));
+  }
+
+  return jit_caches;
+}
+
+AddressRangeMap<JitCache*> V8Tracker::GetOrCreateSharedJitCaches(
+    UniquePid upid,
+    const IsolateCodeRanges& code_ranges) {
+  if (auto* shared = shared_code_ranges_.Find(upid); shared) {
+    PERFETTO_CHECK(shared->code_ranges == code_ranges);
+    return shared->jit_caches;
+  }
+
+  return shared_code_ranges_
+      .Insert(upid, {code_ranges, CreateJitCaches(upid, code_ranges)})
+      .first->jit_caches;
+}
+
+IsolateId V8Tracker::CreateIsolate(
+    const InternedV8Isolate::Decoder& isolate_proto) {
+  auto v8_isolate = InsertIsolate(isolate_proto);
+  const UniquePid upid = v8_isolate.upid();
+
+  auto [code_ranges, is_process_wide] =
+      GetIsolateCodeRanges(upid, isolate_proto);
+
+  PERFETTO_CHECK(isolates_
+                     .Insert(v8_isolate.id(),
+                             is_process_wide
+                                 ? GetOrCreateSharedJitCaches(upid, code_ranges)
+                                 : CreateJitCaches(upid, code_ranges))
+                     .second);
+
+  return v8_isolate.id();
+}
+
+tables::V8IsolateTable::ConstRowReference V8Tracker::InsertIsolate(
+    const InternedV8Isolate::Decoder& isolate) {
   InternedV8Isolate::CodeRange::Decoder code_range(isolate.code_range());
-
-  auto v8_isolate_id =
-      context_->storage->mutable_v8_isolate_table()
-          ->Insert(
-              {upid, isolate.isolate_id(),
-               static_cast<int64_t>(isolate.embedded_blob_code_start_address()),
-               static_cast<int64_t>(isolate.embedded_blob_code_size()),
-               static_cast<int64_t>(code_range.base_address()),
-               static_cast<int64_t>(code_range.size()),
-               code_range.is_process_wide(),
-               code_range.has_embedded_blob_code_copy_start_address()
-                   ? std::make_optional(static_cast<int64_t>(
-                         code_range.embedded_blob_code_copy_start_address()))
-                   : std::nullopt
-
-              })
-          .id;
-  isolate_index_.Insert(std::make_pair(upid, isolate.isolate_id()),
-                        v8_isolate_id);
-  return v8_isolate_id;
+  return context_->storage->mutable_v8_isolate_table()
+      ->Insert(
+          {context_->process_tracker->GetOrCreateProcess(isolate.pid()),
+           isolate.isolate_id(),
+           static_cast<int64_t>(isolate.embedded_blob_code_start_address()),
+           static_cast<int64_t>(isolate.embedded_blob_code_size()),
+           static_cast<int64_t>(code_range.base_address()),
+           static_cast<int64_t>(code_range.size()),
+           code_range.is_process_wide(),
+           code_range.has_embedded_blob_code_copy_start_address()
+               ? std::make_optional(static_cast<int64_t>(
+                     code_range.embedded_blob_code_copy_start_address()))
+               : std::nullopt})
+      .row_reference;
 }
 
 tables::V8JsScriptTable::Id V8Tracker::InternJsScript(
     protozero::ConstBytes bytes,
-    tables::V8IsolateTable::Id isolate_id) {
+    IsolateId isolate_id) {
   InternedV8JsScript::Decoder script(bytes);
 
   if (auto* id =
@@ -137,7 +301,7 @@
 
 tables::V8WasmScriptTable::Id V8Tracker::InternWasmScript(
     protozero::ConstBytes bytes,
-    tables::V8IsolateTable::Id isolate_id) {
+    IsolateId isolate_id) {
   InternedV8WasmScript::Decoder script(bytes);
 
   if (auto* id = wasm_script_index_.Find(
@@ -170,9 +334,13 @@
   row.is_toplevel = function.is_toplevel();
   row.kind =
       context_->storage->InternString(JsFunctionKindToString(function.kind()));
-  // TODO(carlscab): Row and line are hard. Offset is in bytes, row and line are
-  // in characters and we potentially have a multi byte encoding (UTF16). Good
-  // luck!
+  // TODO(carlscab): Line and column are hard. Offset is in bytes, line and
+  // column are in characters and we potentially have a multi byte encoding
+  // (UTF16). Good luck!
+  if (function.has_byte_offset()) {
+    row.line = 1;
+    row.col = function.byte_offset();
+  }
 
   if (auto* id = js_function_index_.Find(row); id) {
     return *id;
@@ -184,35 +352,168 @@
   return function_id;
 }
 
-void V8Tracker::AddJsCode(int64_t,
-                          tables::V8IsolateTable::Id,
-                          tables::V8JsFunctionTable::Id,
-                          const protos::pbzero::V8JsCode::Decoder&) {
-  // TODO(carlscab): Implement
+JitCache* V8Tracker::MaybeFindJitCache(IsolateId isolate_id,
+                                       AddressRange code_range) const {
+  if (code_range.empty()) {
+    context_->storage->IncrementStats(stats::v8_code_load_missing_code_range);
+    return nullptr;
+  }
+  auto* isolate = isolates_.Find(isolate_id);
+  PERFETTO_CHECK(isolate);
+  if (auto it = isolate->FindRangeThatContains(code_range);
+      it != isolate->end()) {
+    return it->second;
+  }
+
+  return nullptr;
 }
 
-void V8Tracker::AddInternalCode(
-    int64_t,
-    tables::V8IsolateTable::Id,
-    const protos::pbzero::V8InternalCode::Decoder&) {
-  // TODO(carlscab): Implement
+JitCache* V8Tracker::FindJitCache(IsolateId isolate_id,
+                                  AddressRange code_range) const {
+  if (code_range.empty()) {
+    context_->storage->IncrementStats(stats::v8_code_load_missing_code_range);
+    return nullptr;
+  }
+  JitCache* cache = MaybeFindJitCache(isolate_id, code_range);
+  if (!cache) {
+    context_->storage->IncrementStats(stats::v8_no_code_range);
+  }
+  return cache;
 }
 
-void V8Tracker::AddWasmCode(int64_t,
-                            tables::V8IsolateTable::Id,
-                            tables::V8WasmScriptTable::Id,
-                            const protos::pbzero::V8WasmCode::Decoder&) {
-  // TODO(carlscab): Implement
+void V8Tracker::AddJsCode(int64_t timestamp,
+                          UniqueTid utid,
+                          IsolateId isolate_id,
+                          tables::V8JsFunctionTable::Id function_id,
+                          const V8JsCode::Decoder& code) {
+  const StringId tier =
+      context_->storage->InternString(JsCodeTierToString(code.tier()));
+
+  const AddressRange code_range = AddressRange::FromStartAndSize(
+      code.instruction_start(), code.instruction_size_bytes());
+
+  JitCache* jit_cache = nullptr;
+
+  if (IsInterpretedCode(code)) {
+    // If --interpreted_frames_native_stack is specified interpreted frames will
+    // also be emitted as native functions.
+    // TODO(carlscab): Add an additional tier to for NATIVE_IGNITION_FRAME. Int
+    // he meantime we can infer that this is the case if we have a hit in the
+    // jit cache. NOte we call MaybeFindJitCache to not log an error if there is
+    // no hit.
+    jit_cache = MaybeFindJitCache(isolate_id, code_range);
+    if (!jit_cache) {
+      context_->storage->mutable_v8_js_code_table()->Insert(
+          {std::nullopt, function_id, tier,
+           context_->storage->InternString(base::StringView(base::Base64Encode(
+               code.bytecode().data, code.bytecode().size)))});
+      return;
+    }
+  } else if (IsNativeCode(code)) {
+    jit_cache = FindJitCache(isolate_id, code_range);
+  } else {
+    context_->storage->IncrementStats(stats::v8_unknown_code_type);
+    return;
+  }
+
+  if (!jit_cache) {
+    return;
+  }
+
+  auto function =
+      *context_->storage->v8_js_function_table().FindById(function_id);
+  auto script = *context_->storage->v8_js_script_table().FindById(
+      function.v8_js_script_id());
+  const auto jit_code_id = jit_cache->LoadCode(
+      timestamp, utid, code_range, function.name(),
+      JitCache::SourceLocation{script.name(), function.line().value_or(0)},
+      code.has_machine_code()
+          ? TraceBlobView(TraceBlob::CopyFrom(code.machine_code().data,
+                                              code.machine_code().size))
+          : TraceBlobView());
+
+  context_->storage->mutable_v8_js_code_table()->Insert(
+      {jit_code_id, function_id, tier});
 }
 
-void V8Tracker::AddRegExpCode(int64_t,
-                              tables::V8IsolateTable::Id,
-                              const protos::pbzero::V8RegExpCode::Decoder&) {
-  // TODO(carlscab): Implement
+void V8Tracker::AddInternalCode(int64_t timestamp,
+                                UniqueTid utid,
+                                IsolateId isolate_id,
+                                const V8InternalCode::Decoder& code) {
+  const AddressRange code_range = AddressRange::FromStartAndSize(
+      code.instruction_start(), code.instruction_size_bytes());
+  JitCache* const jit_cache = FindJitCache(isolate_id, code_range);
+  if (!jit_cache) {
+    return;
+  }
+
+  const StringId function_name = context_->storage->InternString(code.name());
+  const StringId type =
+      context_->storage->InternString(InternalCodeTypeToString(code.type()));
+  const auto jit_code_id = jit_cache->LoadCode(
+      timestamp, utid, code_range, function_name, std::nullopt,
+      code.has_machine_code()
+          ? TraceBlobView(TraceBlob::CopyFrom(code.machine_code().data,
+                                              code.machine_code().size))
+          : TraceBlobView());
+
+  context_->storage->mutable_v8_internal_code_table()->Insert(
+      {jit_code_id, isolate_id, function_name, type});
 }
 
-StringId V8Tracker::InternV8String(
-    const protos::pbzero::V8String::Decoder& v8_string) {
+void V8Tracker::AddWasmCode(int64_t timestamp,
+                            UniqueTid utid,
+                            IsolateId isolate_id,
+                            tables::V8WasmScriptTable::Id script_id,
+                            const V8WasmCode::Decoder& code) {
+  const AddressRange code_range = AddressRange::FromStartAndSize(
+      code.instruction_start(), code.instruction_size_bytes());
+  JitCache* const jit_cache = FindJitCache(isolate_id, code_range);
+  if (!jit_cache) {
+    return;
+  }
+
+  const StringId function_name =
+      context_->storage->InternString(code.function_name());
+  const StringId tier =
+      context_->storage->InternString(WasmCodeTierToString(code.tier()));
+
+  const auto jit_code_id = jit_cache->LoadCode(
+      timestamp, utid, code_range, function_name, std::nullopt,
+      code.has_machine_code()
+          ? TraceBlobView(TraceBlob::CopyFrom(code.machine_code().data,
+                                              code.machine_code().size))
+          : TraceBlobView());
+
+  context_->storage->mutable_v8_wasm_code_table()->Insert(
+      {jit_code_id, isolate_id, script_id, function_name, tier});
+}
+
+void V8Tracker::AddRegExpCode(int64_t timestamp,
+                              UniqueTid utid,
+                              IsolateId isolate_id,
+                              const V8RegExpCode::Decoder& code) {
+  const AddressRange code_range = AddressRange::FromStartAndSize(
+      code.instruction_start(), code.instruction_size_bytes());
+  JitCache* const jit_cache = FindJitCache(isolate_id, code_range);
+  if (!jit_cache) {
+    return;
+  }
+
+  const StringId function_name = context_->storage->InternString("[RegExp]");
+  const StringId pattern = InternV8String(V8String::Decoder(code.pattern()));
+  const auto jit_code_id = jit_cache->LoadCode(
+      timestamp, utid, code_range, function_name, std::nullopt,
+      code.has_machine_code()
+          ? TraceBlobView(TraceBlob::CopyFrom(code.machine_code().data,
+                                              code.machine_code().size))
+          : TraceBlobView());
+
+  context_->storage->mutable_v8_regexp_code_table()->Insert(
+      {jit_code_id, isolate_id, pattern});
+}
+
+StringId V8Tracker::InternV8String(const V8String::Decoder& v8_string) {
   auto& storage = *context_->storage;
   if (v8_string.has_latin1()) {
     return storage.InternString(
@@ -221,12 +522,12 @@
 
   if (v8_string.has_utf16_le()) {
     return storage.InternString(
-        base::StringView(ConvertUtf16LeToUtf8(v8_string.latin1())));
+        base::StringView(ConvertUtf16LeToUtf8(v8_string.utf16_le())));
   }
 
   if (v8_string.has_utf16_be()) {
     return storage.InternString(
-        base::StringView(ConvertUtf16BeToUtf8(v8_string.latin1())));
+        base::StringView(ConvertUtf16BeToUtf8(v8_string.utf16_be())));
   }
   return storage.InternString("");
 }
diff --git a/src/trace_processor/importers/proto/v8_tracker.h b/src/trace_processor/importers/proto/v8_tracker.h
index 3d85377..b17affb 100644
--- a/src/trace_processor/importers/proto/v8_tracker.h
+++ b/src/trace_processor/importers/proto/v8_tracker.h
@@ -19,11 +19,14 @@
 
 #include <cstddef>
 #include <cstdint>
+#include <memory>
 
 #include "perfetto/ext/base/flat_hash_map.h"
 #include "perfetto/ext/base/hash.h"
 #include "perfetto/protozero/field.h"
 #include "protos/perfetto/trace/chrome/v8.pbzero.h"
+#include "src/trace_processor/importers/common/address_range.h"
+#include "src/trace_processor/importers/proto/jit_tracker.h"
 #include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/tables/v8_tables_py.h"
 #include "src/trace_processor/types/destructible.h"
@@ -32,6 +35,11 @@
 namespace perfetto {
 namespace trace_processor {
 
+class TraceStorage;
+class UserMemoryMapping;
+
+using IsolateId = tables::V8IsolateTable::Id;
+
 // Keeps track of V8 related objects.
 class V8Tracker : public Destructible {
  public:
@@ -44,69 +52,39 @@
 
   ~V8Tracker() override;
 
-  tables::V8IsolateTable::Id InternIsolate(protozero::ConstBytes bytes);
-  tables::V8JsScriptTable::Id InternJsScript(
-      protozero::ConstBytes bytes,
-      tables::V8IsolateTable::Id isolate_id);
-  tables::V8WasmScriptTable::Id InternWasmScript(
-      protozero::ConstBytes bytes,
-      tables::V8IsolateTable::Id isolate_id);
+  IsolateId InternIsolate(protozero::ConstBytes bytes);
+  tables::V8JsScriptTable::Id InternJsScript(protozero::ConstBytes bytes,
+                                             IsolateId isolate_id);
+  tables::V8WasmScriptTable::Id InternWasmScript(protozero::ConstBytes bytes,
+                                                 IsolateId isolate_id);
   tables::V8JsFunctionTable::Id InternJsFunction(
       protozero::ConstBytes bytes,
       StringId name,
       tables::V8JsScriptTable::Id script_id);
 
   void AddJsCode(int64_t timestamp,
-                 tables::V8IsolateTable::Id isolate_id,
+                 UniqueTid utid,
+                 IsolateId isolate_id,
                  tables::V8JsFunctionTable::Id function_id,
                  const protos::pbzero::V8JsCode::Decoder& code);
 
   void AddInternalCode(int64_t timestamp,
-                       tables::V8IsolateTable::Id v8_isolate_id,
+                       UniqueTid utid,
+                       IsolateId v8_isolate_id,
                        const protos::pbzero::V8InternalCode::Decoder& code);
 
   void AddWasmCode(int64_t timestamp,
-                   tables::V8IsolateTable::Id isolate_id,
+                   UniqueTid utid,
+                   IsolateId isolate_id,
                    tables::V8WasmScriptTable::Id script_id,
                    const protos::pbzero::V8WasmCode::Decoder& code);
 
   void AddRegExpCode(int64_t timestamp,
-                     tables::V8IsolateTable::Id v8_isolate_id,
+                     UniqueTid utid,
+                     IsolateId v8_isolate_id,
                      const protos::pbzero::V8RegExpCode::Decoder& code);
 
  private:
-  explicit V8Tracker(TraceProcessorContext* context);
-
-  StringId InternV8String(const protos::pbzero::V8String::Decoder& v8_string);
-
-  TraceProcessorContext* const context_;
-
-  struct IsolateIndexHash {
-    size_t operator()(const std::pair<UniquePid, int32_t>& v) const {
-      return static_cast<size_t>(base::Hasher::Combine(v.first, v.second));
-    }
-  };
-  base::FlatHashMap<std::pair<UniquePid, int32_t>,
-                    tables::V8IsolateTable::Id,
-                    IsolateIndexHash>
-      isolate_index_;
-
-  struct ScriptIndexHash {
-    size_t operator()(
-        const std::pair<tables::V8IsolateTable::Id, int32_t>& v) const {
-      return static_cast<size_t>(
-          base::Hasher::Combine(v.first.value, v.second));
-    }
-  };
-  base::FlatHashMap<std::pair<tables::V8IsolateTable::Id, int32_t>,
-                    tables::V8JsScriptTable::Id,
-                    ScriptIndexHash>
-      js_script_index_;
-  base::FlatHashMap<std::pair<tables::V8IsolateTable::Id, int32_t>,
-                    tables::V8WasmScriptTable::Id,
-                    ScriptIndexHash>
-      wasm_script_index_;
-
   struct JsFunctionHash {
     size_t operator()(const tables::V8JsFunctionTable::Row& v) const {
       return static_cast<size_t>(base::Hasher::Combine(
@@ -114,6 +92,94 @@
           v.kind.raw_id(), v.line.value_or(0), v.col.value_or(0)));
     }
   };
+
+  struct IsolateCodeRanges {
+    AddressSet heap_code;
+    std::optional<AddressRange> embedded_blob;
+
+    bool operator==(const IsolateCodeRanges& o) const {
+      return heap_code == o.heap_code && embedded_blob == o.embedded_blob;
+    }
+  };
+
+  struct SharedCodeRanges {
+    IsolateCodeRanges code_ranges;
+    AddressRangeMap<JitCache*> jit_caches;
+  };
+
+  // V8 internal isolate_id and upid uniquely identify an isolate in a trace.
+  struct IsolateKey {
+    struct Hasher {
+      size_t operator()(const IsolateKey& v) const {
+        return base::Hasher::Combine(v.upid, v.isolate_id);
+      }
+    };
+
+    bool operator==(const IsolateKey& other) const {
+      return upid == other.upid && isolate_id == other.isolate_id;
+    }
+
+    bool operator!=(const IsolateKey& other) const { return !(*this == other); }
+    UniquePid upid;
+    int32_t isolate_id;
+  };
+
+  struct ScriptIndexHash {
+    size_t operator()(const std::pair<IsolateId, int32_t>& v) const {
+      return static_cast<size_t>(
+          base::Hasher::Combine(v.first.value, v.second));
+    }
+  };
+
+  explicit V8Tracker(TraceProcessorContext* context);
+
+  StringId InternV8String(const protos::pbzero::V8String::Decoder& v8_string);
+
+  tables::V8IsolateTable::ConstRowReference InsertIsolate(
+      const protos::pbzero::InternedV8Isolate::Decoder& isolate);
+
+  IsolateId CreateIsolate(
+      const protos::pbzero::InternedV8Isolate::Decoder& isolate);
+
+  // Find JitCache that fully contains the given range. Returns null if not
+  // found and updates error counter.
+  JitCache* FindJitCache(IsolateId isolate_id, AddressRange code_range) const;
+  // Same as `FindJitCache` but error counter is not updated if no cache is
+  // found.
+  JitCache* MaybeFindJitCache(IsolateId isolate_id,
+                              AddressRange code_range) const;
+
+  UserMemoryMapping* FindEmbeddedBlobMapping(
+      UniquePid upid,
+      AddressRange embedded_blob_code) const;
+
+  std::pair<IsolateCodeRanges, bool> GetIsolateCodeRanges(
+      UniquePid upid,
+      const protos::pbzero::InternedV8Isolate::Decoder& isolate);
+  AddressRangeMap<JitCache*> GetOrCreateSharedJitCaches(
+      UniquePid upid,
+      const IsolateCodeRanges& code_ranges);
+  AddressRangeMap<JitCache*> CreateJitCaches(
+      UniquePid upid,
+      const IsolateCodeRanges& code_ranges);
+
+  TraceProcessorContext* const context_;
+
+  base::FlatHashMap<IsolateId, AddressRangeMap<JitCache*>> isolates_;
+
+  // Multiple isolates in the same process might share the code. Keep track of
+  // those here.
+  base::FlatHashMap<UniquePid, SharedCodeRanges> shared_code_ranges_;
+
+  base::FlatHashMap<IsolateKey, IsolateId, IsolateKey::Hasher> isolate_index_;
+  base::FlatHashMap<std::pair<IsolateId, int32_t>,
+                    tables::V8JsScriptTable::Id,
+                    ScriptIndexHash>
+      js_script_index_;
+  base::FlatHashMap<std::pair<IsolateId, int32_t>,
+                    tables::V8WasmScriptTable::Id,
+                    ScriptIndexHash>
+      wasm_script_index_;
   base::FlatHashMap<tables::V8JsFunctionTable::Row,
                     tables::V8JsFunctionTable::Id,
                     JsFunctionHash>
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/dfs_weight_bounded.cc b/src/trace_processor/perfetto_sql/intrinsics/table_functions/dfs_weight_bounded.cc
index 4ff33cc..dda42ab 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/dfs_weight_bounded.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/dfs_weight_bounded.cc
@@ -86,20 +86,20 @@
   std::vector<Edge> roots;
   bool parse_error = false;
   auto root_node_ids = start.int_values(&parse_error);
-  auto max_weights = end.int_values(&parse_error);
+  auto target_weights = end.int_values(&parse_error);
 
-  for (; root_node_ids && max_weights; ++root_node_ids, ++max_weights) {
+  for (; root_node_ids && target_weights; ++root_node_ids, ++target_weights) {
     roots.push_back(Edge{static_cast<uint32_t>(*root_node_ids),
-                         static_cast<uint32_t>(*max_weights)});
+                         static_cast<uint32_t>(*target_weights)});
   }
 
   if (parse_error) {
     return base::ErrStatus(
-        "Failed while parsing root_node_ids or root_max_weights");
+        "Failed while parsing root_node_ids or root_target_weights");
   }
-  if (static_cast<bool>(root_node_ids) != static_cast<bool>(max_weights)) {
+  if (static_cast<bool>(root_node_ids) != static_cast<bool>(target_weights)) {
     return base::ErrStatus(
-        "dfs_weight_bounded: length of root_node_ids and root_max_weights "
+        "dfs_weight_bounded: length of root_node_ids and root_target_weights "
         "columns is not the same");
   }
   return roots;
@@ -108,7 +108,8 @@
 void DfsWeightBoundedImpl(
     tables::DfsWeightBoundedTable* table,
     const std::vector<Destinations>& source_to_destinations_map,
-    const std::vector<Edge>& roots) {
+    const std::vector<Edge>& roots,
+    const bool is_target_weight_floor) {
   struct StackState {
     uint32_t id;
     uint32_t weight;
@@ -131,22 +132,26 @@
         continue;
       }
       seen_node_ids[stack_state.id] = true;
-
-      // We want to greedily return all possible edges that are reachable within
-      // the target weight. If an edge already fails the requirement, skip it
-      // and don't include it's weight but continue the search, some other edges
-      // might fit.
-      if (total_weight + stack_state.weight > root.weight) {
-        continue;
-      }
       total_weight += stack_state.weight;
 
+      if (!is_target_weight_floor && total_weight > root.weight) {
+        // If target weight is a ceiling weight then we don't want to include
+        // the last node that crosses the threshold.
+        break;
+      }
+
       tables::DfsWeightBoundedTable::Row row;
       row.root_node_id = root.id;
       row.node_id = stack_state.id;
       row.parent_node_id = stack_state.parent_id;
       table->Insert(row);
 
+      if (total_weight > root.weight) {
+        // If the target weight is a floor weight, we add the last node that
+        // crossed the threshold before exiting the search.
+        break;
+      }
+
       PERFETTO_DCHECK(stack_state.id < source_to_destinations_map.size());
 
       const auto& children = source_to_destinations_map[stack_state.id];
@@ -176,24 +181,29 @@
 
 base::StatusOr<std::unique_ptr<Table>> DfsWeightBounded::ComputeTable(
     const std::vector<SqlValue>& arguments) {
-  PERFETTO_CHECK(arguments.size() == 5);
+  PERFETTO_CHECK(arguments.size() == 6);
 
   const SqlValue& raw_source_ids = arguments[0];
   const SqlValue& raw_dest_ids = arguments[1];
   const SqlValue& raw_edge_weights = arguments[2];
   const SqlValue& raw_root_ids = arguments[3];
-  const SqlValue& raw_root_max_weights = arguments[4];
+  const SqlValue& raw_root_target_weights = arguments[4];
+  const SqlValue& raw_is_target_weight_floor = arguments[5];
 
   if (raw_source_ids.is_null() && raw_dest_ids.is_null() &&
-      raw_edge_weights.is_null() && raw_root_ids.is_null() &&
-      raw_root_max_weights.is_null()) {
+      raw_edge_weights.is_null()) {
+    return std::unique_ptr<Table>(
+        std::make_unique<tables::DfsWeightBoundedTable>(pool_));
+  }
+
+  if (raw_root_ids.is_null() && raw_root_target_weights.is_null()) {
     return std::unique_ptr<Table>(
         std::make_unique<tables::DfsWeightBoundedTable>(pool_));
   }
 
   if (raw_source_ids.is_null() || raw_dest_ids.is_null() ||
       raw_edge_weights.is_null() || raw_root_ids.is_null() ||
-      raw_root_max_weights.is_null()) {
+      raw_root_target_weights.is_null()) {
     return base::ErrStatus(
         "dfs_weight_bounded: either all arguments should be null or none "
         "should be");
@@ -214,9 +224,9 @@
     return base::ErrStatus(
         "dfs_weight_bounded: root_ids should be a repeated field");
   }
-  if (raw_root_max_weights.type != SqlValue::kBytes) {
+  if (raw_root_target_weights.type != SqlValue::kBytes) {
     return base::ErrStatus(
-        "dfs_weight_bounded: root_max_weights should be a repeated field");
+        "dfs_weight_bounded: root_target_weights should be a repeated field");
   }
 
   protos::pbzero::ProtoBuilderResult::Decoder proto_source_ids(
@@ -263,25 +273,27 @@
   protos::pbzero::RepeatedBuilderResult::Decoder root_ids(
       proto_root_ids.repeated());
 
-  protos::pbzero::ProtoBuilderResult::Decoder proto_root_max_weights(
-      static_cast<const uint8_t*>(raw_root_max_weights.AsBytes()),
-      raw_root_max_weights.bytes_count);
-  if (!proto_root_max_weights.is_repeated()) {
+  protos::pbzero::ProtoBuilderResult::Decoder proto_root_target_weights(
+      static_cast<const uint8_t*>(raw_root_target_weights.AsBytes()),
+      raw_root_target_weights.bytes_count);
+  if (!proto_root_target_weights.is_repeated()) {
     return base::ErrStatus(
-        "dfs_weight_bounded: root_max_weights is not generated by "
+        "dfs_weight_bounded: root_target_weights is not generated by "
         "RepeatedField function");
   }
-  protos::pbzero::RepeatedBuilderResult::Decoder root_max_weights(
-      proto_root_max_weights.repeated());
+  protos::pbzero::RepeatedBuilderResult::Decoder root_target_weights(
+      proto_root_target_weights.repeated());
 
+  bool is_target_weight_floor =
+      static_cast<bool>(raw_is_target_weight_floor.AsLong());
   ASSIGN_OR_RETURN(auto map, ParseSourceToDestionationsMap(source_ids, dest_ids,
                                                            edge_weights));
 
   ASSIGN_OR_RETURN(auto roots,
-                   ParseRootToMaxWeightMap(root_ids, root_max_weights));
+                   ParseRootToMaxWeightMap(root_ids, root_target_weights));
 
   auto table = std::make_unique<tables::DfsWeightBoundedTable>(pool_);
-  DfsWeightBoundedImpl(table.get(), map, roots);
+  DfsWeightBoundedImpl(table.get(), map, roots, is_target_weight_floor);
   return std::unique_ptr<Table>(std::move(table));
 }
 
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/dfs_weight_bounded.h b/src/trace_processor/perfetto_sql/intrinsics/table_functions/dfs_weight_bounded.h
index f1af772..92caf25 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/dfs_weight_bounded.h
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/dfs_weight_bounded.h
@@ -54,6 +54,13 @@
 //     uint32 values corresponding to the max sum of edge weights inclusive,
 //     at which point the DFS from the |root_node_ids| stops. This number of
 //     values should be the same as |root_node_ids|.
+//  6) |is_target_weight_floor|: Whether the target_weight is a floor weight or
+//  ceiling weight.
+//     If it's floor, the search stops right after we exceed the target weight,
+//     and we include the node that pushed just passed the target. If ceiling,
+//     the search stops right before the target weight and the node that would
+//     have pushed us passed the target is not included.
+
 //
 // Returns:
 //  A table with the nodes reachable from the start node, their "parent" in
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 0570082..e129565 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/tables.py
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/tables.py
@@ -206,6 +206,9 @@
         C("in_root_max_weights",
           CppOptional(CppUint32()),
           flags=ColumnFlag.HIDDEN),
+        C("in_is_target_weight_floor",
+          CppOptional(CppUint32()),
+          flags=ColumnFlag.HIDDEN),
     ])
 
 # Keep this list sorted.
diff --git a/src/trace_processor/perfetto_sql/stdlib/graphs/search.sql b/src/trace_processor/perfetto_sql/stdlib/graphs/search.sql
index 6214716..34fc1de 100644
--- a/src/trace_processor/perfetto_sql/stdlib/graphs/search.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/graphs/search.sql
@@ -131,7 +131,7 @@
 --      (
 --        SELECT
 --          id AS root_node_id,
---          id - COALESCE(prev_id, id) AS root_max_weight
+--          id - COALESCE(prev_id, id) AS root_target_weight
 --        FROM _wakeup_chain
 --      ));
 -- ```
@@ -150,7 +150,7 @@
   graph_table TableOrSubquery,
   -- A table/view/subquery corresponding to start nodes to |graph_table| which will be the
   -- roots of the reachability trees. This table must have the columns
-  -- "root_node_id" and "root_max_weight" corresponding to the starting node id and the max
+  -- "root_node_id" and "root_target_weight" corresponding to the starting node id and the max
   -- weight allowed on the tree.
   --
   -- Note: the columns must contain uint32 similar to ids in trace processor
@@ -158,7 +158,14 @@
   -- implementation makes assumptions on this for performance reasons and, if
   -- this criteria is not, can lead to enormous amounts of memory being
   -- allocated.
-  root_table TableOrSubquery
+  root_table TableOrSubquery,
+  -- Whether the target_weight is a floor weight or ceiling weight.
+  -- If it's floor, the search stops right after we exceed the target weight, and we
+  -- include the node that pushed just passed the target. If ceiling, the search stops
+  -- right before the target weight and the node that would have pushed us passed the
+  -- target is not included.
+  is_target_weight_floor Expr
+
 )
 -- The returned table has the schema (root_node_id, node_id UINT32, parent_node_id UINT32).
 -- |root_node_id| is the id of the starting node under which this edge was encountered.
@@ -175,6 +182,7 @@
     (SELECT RepeatedField(dest_node_id) FROM __temp_graph_table),
     (SELECT RepeatedField(edge_weight) FROM __temp_graph_table),
     (SELECT RepeatedField(root_node_id) FROM __temp_root_table),
-    (SELECT RepeatedField(root_max_weight) FROM __temp_root_table)
+    (SELECT RepeatedField(root_target_weight) FROM __temp_root_table),
+    $is_target_weight_floor
   ) dt
 );
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
index 2326aab..28edf7f 100644
--- 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
@@ -160,4 +160,5 @@
   _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);
+JOIN _heap_graph_dominator_tree_depth d USING(id)
+ORDER BY id;
diff --git a/src/trace_processor/perfetto_sql/stdlib/sched/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/sched/BUILD.gn
index bd2dc94..d9b8f42 100644
--- a/src/trace_processor/perfetto_sql/stdlib/sched/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/sched/BUILD.gn
@@ -17,6 +17,7 @@
 perfetto_sql_source_set("sched") {
   deps = [ "utilization" ]
   sources = [
+    "runnable.sql",
     "states.sql",
     "thread_executing_span.sql",
     "thread_level_parallelism.sql",
diff --git a/src/trace_processor/perfetto_sql/stdlib/sched/runnable.sql b/src/trace_processor/perfetto_sql/stdlib/sched/runnable.sql
new file mode 100644
index 0000000..38956b1
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/sched/runnable.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.
+--
+
+-- Previous runnable slice on the same thread.
+-- For each "Running" thread state finds:
+-- - previous "Runnable" (or runnable preempted) state.
+-- - previous uninterrupted "Runnable" state with a valid waker thread.
+CREATE PERFETTO TABLE sched_previous_runnable_on_thread(
+    -- `thread_state.id` id.
+    id INT,
+    -- Previous runnable `thread_state.id`.
+    prev_runnable_id INT,
+    -- Previous runnable `thread_state.id` with valid waker
+    -- thread.
+    prev_wakeup_runnable_id INT
+) AS
+WITH running_and_runnable AS (
+  SELECT
+        id,
+        state,
+        MAX(id)
+          FILTER (WHERE state != 'Running')
+          OVER utid_part AS prev_runnable_id,
+        MAX(id)
+          FILTER (WHERE
+            waker_utid IS NOT NULL
+            AND (irq_context IS NULL OR irq_context != 1))
+          OVER utid_part AS prev_wakeup_runnable_id
+    FROM thread_state
+    -- Optimal operation for state IN (R, R+, Running)
+    WHERE state GLOB 'R*' AND dur != -1
+    WINDOW utid_part AS (PARTITION BY utid ORDER BY id)
+)
+SELECT
+  id,
+  prev_runnable_id,
+  prev_wakeup_runnable_id
+FROM running_and_runnable
+WHERE state = 'Running'
+ORDER BY id;
\ No newline at end of file
diff --git a/src/trace_processor/perfetto_sql/stdlib/sched/thread_executing_span.sql b/src/trace_processor/perfetto_sql/stdlib/sched/thread_executing_span.sql
index 348d261..ffafd6d 100644
--- a/src/trace_processor/perfetto_sql/stdlib/sched/thread_executing_span.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/sched/thread_executing_span.sql
@@ -180,6 +180,7 @@
   USING (utid);
 
 -- Mapping from running thread state to runnable
+-- TODO(zezeozue): Switch to use `sched_previous_runnable_on_thread`.
 CREATE PERFETTO TABLE _waker_map
 AS
 WITH x AS (
diff --git a/src/trace_processor/perfetto_sql/stdlib/stack_trace/jit.sql b/src/trace_processor/perfetto_sql/stdlib/stack_trace/jit.sql
index 4335a7e..34c3948 100644
--- a/src/trace_processor/perfetto_sql/stdlib/stack_trace/jit.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/stack_trace/jit.sql
@@ -15,6 +15,7 @@
 
 
 -- Represents a jitted code snippet.
+-- TODO(carlscab): Make public
 CREATE PERFETTO VIEW _jit_code (
   -- Unique jit code id.
   jit_code_id UINT,
@@ -47,8 +48,9 @@
 FROM __intrinsic_jit_code;
 
 -- Represents a jitted frame.
+-- TODO(carlscab): Make public
 CREATE PERFETTO VIEW _jit_frame (
-  -- Jitted code snipped the frame is in (joins with jit_code.jit_code_id).
+  -- 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
diff --git a/src/trace_processor/perfetto_sql/stdlib/v8/jit.sql b/src/trace_processor/perfetto_sql/stdlib/v8/jit.sql
index 6765272..124f530 100644
--- a/src/trace_processor/perfetto_sql/stdlib/v8/jit.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/v8/jit.sql
@@ -76,7 +76,8 @@
 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 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,
@@ -102,7 +103,8 @@
 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 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,
@@ -128,7 +130,7 @@
   -- Function name.
   name STRING,
   -- Script where the function is defined (joinable with
-  -- v8_js_script.v8_js_script_id).
+  -- `v8_js_script.v8_js_script_id`).
   v8_js_script_id UINT,
   -- Whether this function represents the top level script.
   is_toplevel BOOL,
@@ -149,3 +151,108 @@
   col
 FROM
   __intrinsic_v8_js_function;
+
+
+-- Represents a v8 code snippet for a Javascript function. A given function can
+-- have multiple code snippets (e.g. for different compilation tiers, or as the
+-- function moves around the heap).
+-- TODO(carlscab): Make public once `_jit_code` is public too
+CREATE PERFETTO VIEW _v8_js_code(
+  -- Unique id
+  id UINT,
+  -- Associated jit code. Set for all tiers except IGNITION. Joinable with
+  -- `_jit_code.jit_code_id`.
+  jit_code_id UINT,
+  -- JS function for this snippet. Joinable with
+  -- `v8_js_function.v8_js_function_id`.
+  v8_js_function_id UINT,
+  -- Compilation tier
+  tier STRING,
+  -- V8 VM bytecode. Set only for the IGNITION tier.
+  bytecode BYTES
+) AS
+SELECT
+  id,
+  jit_code_id,
+  v8_js_function_id,
+  tier,
+  base64_decode(bytecode_base64) AS bytecode
+FROM
+  __intrinsic_v8_js_code;
+
+
+-- Represents a v8 code snippet for a v8 internal function.
+-- TODO(carlscab): Make public once `_jit_code` is public too
+CREATE PERFETTO VIEW _v8_internal_code(
+  -- Unique id
+  id UINT,
+  -- Associated jit code. Joinable with `_jit_code.jit_code_id`.
+  jit_code_id UINT,
+  -- V8 Isolate this code was created in. Joinable with
+  -- `v8_isolate.v8_isolate_id`.
+  v8_isolate_id UINT,
+  -- Function name.
+  function_name STRING,
+  -- Type of internal code.
+  code_type STRING
+) AS
+SELECT
+  id,
+  jit_code_id,
+  v8_isolate_id,
+  function_name,
+  code_type
+FROM
+  __intrinsic_v8_internal_code;
+
+-- Represents the code associated to a WASM function.
+-- TODO(carlscab): Make public once `_jit_code` is public too
+CREATE PERFETTO VIEW _v8_wasm_code(
+  -- Unique id
+  id UINT,
+  -- Associated jit code. Joinable with `_jit_code.jit_code_id`.
+  jit_code_id UINT,
+  -- V8 Isolate this code was created in. Joinable with
+  -- `v8_isolate.v8_isolate_id`.
+  v8_isolate_id UINT,
+  -- Script where the function is defined. Joinable with
+  -- `v8_wasm_script.v8_wasm_script_id`.
+  v8_wasm_script_id UINT,
+  -- Function name.
+  function_name STRING,
+  -- Compilation tier.
+  tier STRING,
+  -- Offset into the WASM module where the function starts.
+  code_offset_in_module INT
+ ) AS
+SELECT
+  id,
+  jit_code_id,
+  v8_isolate_id,
+  v8_wasm_script_id,
+  function_name,
+  tier,
+  code_offset_in_module
+FROM
+  __intrinsic_v8_wasm_code;
+
+-- Represents the code associated to a regular expression
+-- TODO(carlscab): Make public once `_jit_code` is public too
+CREATE PERFETTO VIEW _v8_regexp_code(
+  -- Unique id
+  id UINT,
+  -- Associated jit code. Joinable with `_jit_code.jit_code_id`.
+  jit_code_id UINT,
+  -- V8 Isolate this code was created in. Joinable with
+  -- `v8_isolate.v8_isolate_id`.
+  v8_isolate_id UINT,
+  -- The pattern the this regular expression was compiled from.
+  pattern STRING
+) AS
+SELECT
+  id,
+  jit_code_id,
+  v8_isolate_id,
+  pattern
+FROM
+  __intrinsic_v8_regexp_code;
diff --git a/src/trace_processor/storage/stats.h b/src/trace_processor/storage/stats.h
index 7085055..7495cfc 100644
--- a/src/trace_processor/storage/stats.h
+++ b/src/trace_processor/storage/stats.h
@@ -289,6 +289,15 @@
   F(v8_intern_errors,                                                          \
                                           kSingle,  kDataLoss, kAnalysis,      \
       "Failed to resolve V8 interned data."),                                  \
+  F(v8_no_defaults,                                                            \
+                                          kSingle,  kDataLoss, kAnalysis,      \
+      "Failed to resolve V8 default data."),                                   \
+  F(v8_no_code_range,                                                          \
+                                          kSingle,  kError,    kAnalysis,      \
+      "V8 isolate had no code range."),                                        \
+  F(v8_unknown_code_type,                 kSingle,  kError,    kAnalysis, ""), \
+  F(v8_code_load_missing_code_range,      kSingle,  kError,    kAnalysis,      \
+      "V8 load had no code range or an empty one. Event ignored."),            \
   F(winscope_sf_layers_parse_errors,      kSingle,  kInfo,     kAnalysis,      \
       "SurfaceFlinger layers snapshot has unknown fields, which results in "   \
       "some arguments missing. You may need a newer version of trace "         \
diff --git a/src/trace_processor/storage/trace_storage.h b/src/trace_processor/storage/trace_storage.h
index ec014d7..c2158d7 100644
--- a/src/trace_processor/storage/trace_storage.h
+++ b/src/trace_processor/storage/trace_storage.h
@@ -761,6 +761,30 @@
   tables::V8JsFunctionTable* mutable_v8_js_function_table() {
     return &v8_js_function_table_;
   }
+  const tables::V8JsCodeTable& v8_js_code_table() const {
+    return v8_js_code_table_;
+  }
+  tables::V8JsCodeTable* mutable_v8_js_code_table() {
+    return &v8_js_code_table_;
+  }
+  const tables::V8InternalCodeTable& v8_internal_code_table() const {
+    return v8_internal_code_table_;
+  }
+  tables::V8InternalCodeTable* mutable_v8_internal_code_table() {
+    return &v8_internal_code_table_;
+  }
+  const tables::V8WasmCodeTable& v8_wasm_code_table() const {
+    return v8_wasm_code_table_;
+  }
+  tables::V8WasmCodeTable* mutable_v8_wasm_code_table() {
+    return &v8_wasm_code_table_;
+  }
+  const tables::V8RegexpCodeTable& v8_regexp_code_table() const {
+    return v8_regexp_code_table_;
+  }
+  tables::V8RegexpCodeTable* mutable_v8_regexp_code_table() {
+    return &v8_regexp_code_table_;
+  }
 
   const tables::JitCodeTable& jit_code_table() const { return jit_code_table_; }
   tables::JitCodeTable* mutable_jit_code_table() { return &jit_code_table_; }
@@ -1075,6 +1099,10 @@
   tables::V8JsScriptTable v8_js_script_table_{&string_pool_};
   tables::V8WasmScriptTable v8_wasm_script_table_{&string_pool_};
   tables::V8JsFunctionTable v8_js_function_table_{&string_pool_};
+  tables::V8JsCodeTable v8_js_code_table_{&string_pool_};
+  tables::V8InternalCodeTable v8_internal_code_table_{&string_pool_};
+  tables::V8WasmCodeTable v8_wasm_code_table_{&string_pool_};
+  tables::V8RegexpCodeTable v8_regexp_code_table_{&string_pool_};
 
   // Jit tables
   tables::JitCodeTable jit_code_table_{&string_pool_};
@@ -1134,6 +1162,9 @@
 struct std::hash<::perfetto::trace_processor::tables::HeapGraphObjectTable::Id>
     : std::hash<::perfetto::trace_processor::BaseId> {};
 template <>
+struct std::hash<::perfetto::trace_processor::tables::V8IsolateTable::Id>
+    : std::hash<::perfetto::trace_processor::BaseId> {};
+template <>
 struct std::hash<::perfetto::trace_processor::tables::JitCodeTable::Id>
     : std::hash<::perfetto::trace_processor::BaseId> {};
 
diff --git a/src/trace_processor/tables/jit_tables.py b/src/trace_processor/tables/jit_tables.py
index aff2c61..44707a6 100644
--- a/src/trace_processor/tables/jit_tables.py
+++ b/src/trace_processor/tables/jit_tables.py
@@ -24,6 +24,7 @@
 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 ColumnFlag
 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
@@ -33,7 +34,7 @@
     class_name='JitCodeTable',
     sql_name='__intrinsic_jit_code',
     columns=[
-        C('create_ts', CppInt64()),
+        C('create_ts', CppInt64(), ColumnFlag.SORTED),
         C('estimated_delete_ts', CppOptional(CppInt64())),
         C('utid', CppUint32()),
         C('start_address', CppInt64()),
diff --git a/src/trace_processor/tables/table_destructors.cc b/src/trace_processor/tables/table_destructors.cc
index 16c2665..826316c 100644
--- a/src/trace_processor/tables/table_destructors.cc
+++ b/src/trace_processor/tables/table_destructors.cc
@@ -129,6 +129,10 @@
 V8JsScriptTable::~V8JsScriptTable() = default;
 V8WasmScriptTable::~V8WasmScriptTable() = default;
 V8JsFunctionTable::~V8JsFunctionTable() = default;
+V8JsCodeTable::~V8JsCodeTable() = default;
+V8InternalCodeTable::~V8InternalCodeTable() = default;
+V8WasmCodeTable::~V8WasmCodeTable() = default;
+V8RegexpCodeTable::~V8RegexpCodeTable() = default;
 
 // winscope_tables_py.h
 SurfaceFlingerLayersSnapshotTable::~SurfaceFlingerLayersSnapshotTable() =
diff --git a/src/trace_processor/tables/v8_tables.py b/src/trace_processor/tables/v8_tables.py
index 76a8cda..28ed330 100644
--- a/src/trace_processor/tables/v8_tables.py
+++ b/src/trace_processor/tables/v8_tables.py
@@ -20,7 +20,6 @@
 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 ColumnDoc
-from python.generators.trace_processor_table.public import ColumnFlag
 from python.generators.trace_processor_table.public import CppInt32
 from python.generators.trace_processor_table.public import CppInt64
 from python.generators.trace_processor_table.public import CppOptional
@@ -30,7 +29,7 @@
 from python.generators.trace_processor_table.public import CppUint32 as CppBool
 from python.generators.trace_processor_table.public import Table
 from python.generators.trace_processor_table.public import TableDoc
-from .profiler_tables import STACK_PROFILE_FRAME_TABLE
+from .jit_tables import JIT_CODE_TABLE
 
 V8_ISOLATE = Table(
     python_module=__file__,
@@ -156,10 +155,165 @@
     ),
 )
 
+V8_JS_CODE = Table(
+    python_module=__file__,
+    class_name='V8JsCodeTable',
+    sql_name='__intrinsic_v8_js_code',
+    columns=[
+        C('jit_code_id', CppOptional(CppTableId(JIT_CODE_TABLE))),
+        C('v8_js_function_id', CppTableId(V8_JS_FUNCTION)),
+        C('tier', CppString()),
+        C('bytecode_base64', CppOptional(CppString())),
+    ],
+    tabledoc=TableDoc(
+        doc="""
+          Represents a v8 code snippet for a Javascript function. A given
+          function can have multiple code snippets (e.g. for different
+          compilation tiers, or as the function moves around the heap)
+        """,
+        group='v8',
+        columns={
+            'jit_code_id':
+                ColumnDoc(
+                    doc="""
+                  Set for all tiers except IGNITION.
+                    """,
+                    joinable='__intrinsic_jit_code.id',
+                ),
+            'v8_js_function_id':
+                ColumnDoc(
+                    doc='JS function for this snippet.',
+                    joinable='__intrinsic_v8_js_function.id',
+                ),
+            'tier':
+                'Compilation tier',
+            'bytecode_base64':
+                'Set only for the IGNITION tier (base64 encoded)',
+        },
+    ),
+)
+
+V8_INTERNAL_CODE = Table(
+    python_module=__file__,
+    class_name='V8InternalCodeTable',
+    sql_name='__intrinsic_v8_internal_code',
+    columns=[
+        C('jit_code_id', CppTableId(JIT_CODE_TABLE)),
+        C('v8_isolate_id', CppTableId(V8_ISOLATE)),
+        C('function_name', CppString()),
+        C('code_type', CppString()),
+    ],
+    tabledoc=TableDoc(
+        doc="""
+          Represents a v8 code snippet for a v8 internal function.
+        """,
+        group='v8',
+        columns={
+            'jit_code_id':
+                ColumnDoc(
+                    doc='Associated JitCode.',
+                    joinable='__intrinsic_jit_code.id',
+                ),
+            'v8_isolate_id':
+                ColumnDoc(
+                    doc="""
+                  V8 Isolate this code was created in.
+                    """,
+                    joinable='__intrinsic_v8_isolate.id'),
+            'function_name':
+                'Function name.',
+            'code_type':
+                'Type of internal function (e.g. BYTECODE_HANDLER, BUILTIN)',
+        },
+    ),
+)
+
+V8_WASM_CODE = Table(
+    python_module=__file__,
+    class_name='V8WasmCodeTable',
+    sql_name='__intrinsic_v8_wasm_code',
+    columns=[
+        C('jit_code_id', CppTableId(JIT_CODE_TABLE)),
+        C('v8_isolate_id', CppTableId(V8_ISOLATE)),
+        C('v8_wasm_script_id', CppTableId(V8_WASM_SCRIPT)),
+        C('function_name', CppString()),
+        C('tier', CppString()),
+        C('code_offset_in_module', CppInt32()),
+    ],
+    tabledoc=TableDoc(
+        doc="""
+          Represents the code associated to a WASM function
+        """,
+        group='v8',
+        columns={
+            'jit_code_id':
+                ColumnDoc(
+                    doc='Associated JitCode.',
+                    joinable='__intrinsic_jit_code.id',
+                ),
+            'v8_isolate_id':
+                ColumnDoc(
+                    doc="""
+                  V8 Isolate this code was created in.
+                    """,
+                    joinable='__intrinsic_v8_isolate.id'),
+            'v8_wasm_script_id':
+                ColumnDoc(
+                    doc="""
+                  Script where the function is defined.
+                    """,
+                    joinable='v8_wasm_script.id',
+                ),
+            'function_name':
+                'Function name.',
+            'tier':
+                'Compilation tier',
+            'code_offset_in_module':
+                """Offset into the WASM module where the function starts""",
+        },
+    ),
+)
+
+V8_REGEXP_CODE = Table(
+    python_module=__file__,
+    class_name='V8RegexpCodeTable',
+    sql_name='__intrinsic_v8_regexp_code',
+    columns=[
+        C('jit_code_id', CppTableId(JIT_CODE_TABLE)),
+        C('v8_isolate_id', CppTableId(V8_ISOLATE)),
+        C('pattern', CppString()),
+    ],
+    tabledoc=TableDoc(
+        doc="""
+          Represents the code associated to a regular expression
+        """,
+        group='v8',
+        columns={
+            'jit_code_id':
+                ColumnDoc(
+                    doc='Associated JitCode.',
+                    joinable='__intrinsic_jit_code.id',
+                ),
+            'v8_isolate_id':
+                ColumnDoc(
+                    doc="""
+                  V8 Isolate this code was created in.
+                    """,
+                    joinable='__intrinsic_v8_isolate.id'),
+            'pattern':
+                """The pattern the this regular expression was compiled from""",
+        },
+    ),
+)
+
 # Keep this list sorted.
 ALL_TABLES = [
     V8_ISOLATE,
     V8_JS_SCRIPT,
     V8_WASM_SCRIPT,
     V8_JS_FUNCTION,
+    V8_JS_CODE,
+    V8_INTERNAL_CODE,
+    V8_WASM_CODE,
+    V8_REGEXP_CODE,
 ]
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index edca26a..1b3dea3 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -853,6 +853,13 @@
   RegisterStaticTable(storage->v8_js_script_table());
   RegisterStaticTable(storage->v8_wasm_script_table());
   RegisterStaticTable(storage->v8_js_function_table());
+  RegisterStaticTable(storage->v8_js_code_table());
+  RegisterStaticTable(storage->v8_internal_code_table());
+  RegisterStaticTable(storage->v8_wasm_code_table());
+  RegisterStaticTable(storage->v8_regexp_code_table());
+
+  RegisterStaticTable(storage->jit_code_table());
+  RegisterStaticTable(storage->jit_frame_table());
 
   RegisterStaticTable(storage->jit_code_table());
   RegisterStaticTable(storage->jit_frame_table());
diff --git a/src/trace_redaction/BUILD.gn b/src/trace_redaction/BUILD.gn
index 455ce10..3630898 100644
--- a/src/trace_redaction/BUILD.gn
+++ b/src/trace_redaction/BUILD.gn
@@ -61,9 +61,7 @@
     "../../include/perfetto/ext/base",
     "../../include/perfetto/protozero:protozero",
     "../../include/perfetto/trace_processor:storage",
-    "../../protos/perfetto/trace:non_minimal_cpp",
     "../../protos/perfetto/trace:non_minimal_zero",
-    "../../protos/perfetto/trace/android:cpp",
     "../../protos/perfetto/trace/android:zero",
     "../../protos/perfetto/trace/ftrace:zero",
     "../../protos/perfetto/trace/ps:zero",
diff --git a/src/trace_redaction/prune_package_list.cc b/src/trace_redaction/prune_package_list.cc
index a85a18f..de6331b 100644
--- a/src/trace_redaction/prune_package_list.cc
+++ b/src/trace_redaction/prune_package_list.cc
@@ -18,15 +18,29 @@
 
 #include <string>
 
+#include "perfetto/base/logging.h"
 #include "perfetto/base/status.h"
 
-#include "protos/perfetto/trace/android/packages_list.gen.h"
-#include "protos/perfetto/trace/trace_packet.gen.h"
+#include "perfetto/protozero/scattered_heap_buffer.h"
+#include "protos/perfetto/trace/android/packages_list.pbzero.h"
+#include "src/trace_redaction/proto_util.h"
 
 namespace perfetto::trace_redaction {
+namespace {
 
-PrunePackageList::PrunePackageList() = default;
-PrunePackageList::~PrunePackageList() = default;
+bool ShouldKeepPackageInfo(protozero::Field package_info, uint64_t uid) {
+  PERFETTO_DCHECK(package_info.id() ==
+                  protos::pbzero::PackagesList::kPackagesFieldNumber);
+
+  protozero::ProtoDecoder decoder(package_info.as_bytes());
+  auto uid_field = decoder.FindField(
+      protos::pbzero::PackagesList::PackageInfo::kUidFieldNumber);
+
+  return uid_field.valid() &&
+         NormalizeUid(uid_field.as_uint64()) == NormalizeUid(uid);
+}
+
+}  // namespace
 
 base::Status PrunePackageList::Transform(const Context& context,
                                          std::string* packet) const {
@@ -34,30 +48,47 @@
     return base::ErrStatus("PrunePackageList: missing package uid.");
   }
 
+  protozero::ProtoDecoder packet_decoder(*packet);
+
   protos::pbzero::TracePacket::Decoder trace_packet_decoder(*packet);
 
-  if (!trace_packet_decoder.has_packages_list()) {
+  auto package_list = packet_decoder.FindField(
+      protos::pbzero::TracePacket::kPackagesListFieldNumber);
+
+  if (!package_list.valid()) {
     return base::OkStatus();
   }
 
-  auto normalized_uid = NormalizeUid(context.package_uid.value());
+  auto uid = context.package_uid.value();
 
-  protos::gen::TracePacket mutable_packet;
-  mutable_packet.ParseFromString(*packet);
+  protozero::HeapBuffered<protos::pbzero::TracePacket> packet_message;
 
-  auto* packages = mutable_packet.mutable_packages_list()->mutable_packages();
+  for (auto packet_field = packet_decoder.ReadField(); packet_field.valid();
+       packet_field = packet_decoder.ReadField()) {
+    if (packet_field.id() !=
+        protos::pbzero::TracePacket::kPackagesListFieldNumber) {
+      proto_util::AppendField(packet_field, packet_message.get());
+      continue;
+    }
 
-  // Remove all entries that don't match the uid. After this, one or more
-  // packages will be left in the list (multiple packages can share a uid).
-  packages->erase(
-      std::remove_if(
-          packages->begin(), packages->end(),
-          [normalized_uid](const protos::gen::PackagesList::PackageInfo& info) {
-            return NormalizeUid(info.uid()) != normalized_uid;
-          }),
-      packages->end());
+    auto* package_list_message = packet_message->set_packages_list();
 
-  packet->assign(mutable_packet.SerializeAsString());
+    protozero::ProtoDecoder package_list_decoder(packet_field.as_bytes());
+
+    for (auto package_field = package_list_decoder.ReadField();
+         package_field.valid();
+         package_field = package_list_decoder.ReadField()) {
+      // If not packages, keep.
+      // If packages and uid matches, keep.
+      if (package_field.id() !=
+              protos::pbzero::PackagesList::kPackagesFieldNumber ||
+          ShouldKeepPackageInfo(package_field, uid)) {
+        proto_util::AppendField(package_field, package_list_message);
+      }
+    }
+  }
+
+  packet->assign(packet_message.SerializeAsString());
 
   return base::OkStatus();
 }
diff --git a/src/trace_redaction/prune_package_list.h b/src/trace_redaction/prune_package_list.h
index 24d9ec2..ff5f060 100644
--- a/src/trace_redaction/prune_package_list.h
+++ b/src/trace_redaction/prune_package_list.h
@@ -28,9 +28,6 @@
 // Returns `base::ErrStatus()` if `Context.package_uid` was not set.
 class PrunePackageList final : public TransformPrimitive {
  public:
-  PrunePackageList();
-  ~PrunePackageList() override;
-
   base::Status Transform(const Context& context,
                          std::string* packet) const override;
 };
diff --git a/src/traceconv/trace_to_hprof.cc b/src/traceconv/trace_to_hprof.cc
index 74f515a..38c3329 100644
--- a/src/traceconv/trace_to_hprof.cc
+++ b/src/traceconv/trace_to_hprof.cc
@@ -32,7 +32,7 @@
 // Spec
 // http://hg.openjdk.java.net/jdk6/jdk6/jdk/raw-file/tip/src/share/demo/jvmti/hprof/manual.html#Basic_Type
 // Parser
-// https://cs.android.com/android/platform/superproject/+/main:art/tools/ahat/src/main/com/android/ahat/heapdump/Parser.java
+// https://cs.android.com/android/platform/superproject/main/+/main:art/tools/ahat/src/main/com/android/ahat/heapdump/Parser.java
 
 namespace perfetto {
 namespace trace_to_text {
diff --git a/test/cts/heapprofd_test_cts.cc b/test/cts/heapprofd_test_cts.cc
index fec7aea..c4eb97f 100644
--- a/test/cts/heapprofd_test_cts.cc
+++ b/test/cts/heapprofd_test_cts.cc
@@ -24,6 +24,7 @@
 #include <string_view>
 
 #include "perfetto/base/logging.h"
+#include "perfetto/ext/base/android_utils.h"
 #include "perfetto/ext/base/string_utils.h"
 #include "perfetto/tracing/core/data_source_config.h"
 #include "src/base/test/test_task_runner.h"
@@ -81,9 +82,19 @@
                                  const std::string& path) {
     tmp_dir_.TrackFile("contents.txt");
     tempfile_ = tmp_dir_.AbsolutePath("contents.txt");
-    cmd_ = std::string("content read --uri content://") + app +
-           std::string("/") + path + " >" + tempfile_;
+
+    std::optional<int32_t> sdk =
+        base::StringToInt32(base::GetAndroidProp("ro.build.version.sdk"));
+    bool multiuser_support = sdk && *sdk >= 34;
+    cmd_ = "content read";
+    if (multiuser_support) {
+      // This command is available only starting from android U.
+      cmd_ += " --user `cmd user get-main-user`";
+    }
+    cmd_ += std::string(" --uri content://") + app + std::string("/") + path;
+    cmd_ += " >" + tempfile_;
   }
+
   std::optional<int64_t> ReadInt64() {
     if (system(cmd_.c_str()) != 0) {
       return std::nullopt;
diff --git a/test/trace_processor/diff_tests/parser/chrome/tests_v8.py b/test/trace_processor/diff_tests/parser/chrome/tests_v8.py
index 08f1b65..d4a0dc4 100644
--- a/test/trace_processor/diff_tests/parser/chrome/tests_v8.py
+++ b/test/trace_processor/diff_tests/parser/chrome/tests_v8.py
@@ -60,3 +60,43 @@
         query=_no_duplicates_query(V8_WASM_SCRIPT),
         out=Csv(""""count"\n1\n"""),
     )
+
+  def test_no_code_overlaps(self):
+    return DiffTestBlueprint(
+        trace=DataPath('parser/v8.code.trace.pb.gz'),
+        query="""
+INCLUDE PERFETTO MODULE stack_trace.jit;
+WITH
+  view AS (
+    SELECT
+      jit_code_id,
+      start_address AS start,
+      start_address + size AS end,
+      create_ts,
+      estimated_delete_ts,
+      t.upid AS upid
+    FROM
+      _JIT_CODE AS c, thread AS t
+    USING (utid)
+    -- Prevent the cross join below from blowing up
+    WHERE jit_code_id < 10000
+  )
+SELECT COUNT(*) AS num_overlaps
+FROM
+  view AS v1, view AS v2
+WHERE
+  -- Prevent comparison with self
+  v1.jit_code_id <> v2.jit_code_id
+  -- Code ranges in same process
+  AND v1.upid = v2.upid
+  -- Address overlap
+  AND v1.start < v2.end
+  AND v2.start < v1.end
+  -- Time overlap
+  AND (v2.estimated_delete_ts IS NULL OR v1.create_ts < v2.estimated_delete_ts)
+  AND (v1.estimated_delete_ts IS NULL OR v2.create_ts < v1.estimated_delete_ts)
+""",
+        out=Csv(""""num_overlaps"
+0
+"""),
+    )
diff --git a/test/trace_processor/diff_tests/stdlib/graphs/search_tests.py b/test/trace_processor/diff_tests/stdlib/graphs/search_tests.py
index 48cc56f..37f5d52 100644
--- a/test/trace_processor/diff_tests/stdlib/graphs/search_tests.py
+++ b/test/trace_processor/diff_tests/stdlib/graphs/search_tests.py
@@ -154,7 +154,7 @@
         2,"[NULL]"
         """))
 
-  def test_weight_bounded_dfs(self):
+  def test_weight_bounded_dfs_floor(self):
     return DiffTestBlueprint(
         trace=DataPath('counters.json'),
         query="""
@@ -174,7 +174,7 @@
           VALUES (5, 6, 0);
 
           CREATE PERFETTO TABLE roots AS
-          SELECT 0 AS root_node_id, 0 AS root_max_weight
+          SELECT 0 AS root_node_id, 0 AS root_target_weight
           UNION ALL
           VALUES (1, 2)
           UNION ALL
@@ -182,7 +182,7 @@
           UNION ALL
           VALUES (2, 0);
 
-          SELECT * FROM graph_reachable_weight_bounded_dfs!(foo, roots);
+          SELECT * FROM graph_reachable_weight_bounded_dfs!(foo, roots, 1);
         """,
         out=Csv("""
         "root_node_id","node_id","parent_node_id"
@@ -190,8 +190,50 @@
         1,1,"[NULL]"
         1,2,1
         1,3,1
-        1,5,3
-        1,6,5
+        1,4,3
+        3,3,"[NULL]"
+        3,4,3
+        3,5,3
+        3,6,5
+        2,2,"[NULL]"
+        """))
+
+  def test_weight_bounded_dfs_ceiling(self):
+    return DiffTestBlueprint(
+        trace=DataPath('counters.json'),
+        query="""
+          INCLUDE PERFETTO MODULE graphs.search;
+
+          CREATE PERFETTO TABLE foo AS
+          SELECT 0 AS source_node_id, 0 AS dest_node_id, 0 AS edge_weight
+          UNION ALL
+          VALUES (1, 2, 1)
+          UNION ALL
+          VALUES (1, 3, 1)
+          UNION ALL
+          VALUES (3, 4, 1)
+          UNION ALL
+          VALUES (3, 5, 0)
+          UNION ALL
+          VALUES (5, 6, 0);
+
+          CREATE PERFETTO TABLE roots AS
+          SELECT 0 AS root_node_id, 0 AS root_target_weight
+          UNION ALL
+          VALUES (1, 2)
+          UNION ALL
+          VALUES (3, 1)
+          UNION ALL
+          VALUES (2, 0);
+
+          SELECT * FROM graph_reachable_weight_bounded_dfs!(foo, roots, 0);
+        """,
+        out=Csv("""
+        "root_node_id","node_id","parent_node_id"
+        0,0,"[NULL]"
+        1,1,"[NULL]"
+        1,2,1
+        1,3,1
         3,3,"[NULL]"
         3,4,3
         3,5,3
diff --git a/test/trace_processor/diff_tests/stdlib/sched/tests.py b/test/trace_processor/diff_tests/stdlib/sched/tests.py
index bb24f1b..3527185 100644
--- a/test/trace_processor/diff_tests/stdlib/sched/tests.py
+++ b/test/trace_processor/diff_tests/stdlib/sched/tests.py
@@ -13,8 +13,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from python.generators.diff_tests.testing import Path, DataPath, Metric
-from python.generators.diff_tests.testing import Csv, Json, TextProto
+from python.generators.diff_tests.testing import Path, DataPath
+from python.generators.diff_tests.testing import Csv
 from python.generators.diff_tests.testing import DiffTestBlueprint
 from python.generators.diff_tests.testing import TestSuite
 
@@ -263,3 +263,29 @@
         "S",3868233011
         "x",35240577
       """))
+
+  def test_sched_previous_runnable_on_thread(self):
+    return DiffTestBlueprint(
+        trace=DataPath('android_boot.pftrace'),
+        query="""
+        INCLUDE PERFETTO MODULE sched.runnable;
+
+        SELECT *
+        FROM sched_previous_runnable_on_thread
+        WHERE prev_wakeup_runnable_id IS NOT NULL
+        ORDER BY id DESC
+        LIMIT 10;
+        """,
+        out=Csv("""
+        "id","prev_runnable_id","prev_wakeup_runnable_id"
+        538199,538191,538191
+        538197,538191,538191
+        538195,538191,538191
+        538190,538136,538136
+        538188,538088,533235
+        538184,538176,524613
+        538181,538178,537492
+        538179,524619,524619
+        538177,537492,537492
+        538175,538174,524613
+        """))
diff --git a/ui/src/base/logging.ts b/ui/src/base/logging.ts
index 10fbbff..74cbe44 100644
--- a/ui/src/base/logging.ts
+++ b/ui/src/base/logging.ts
@@ -60,7 +60,7 @@
 
   if (err instanceof ErrorEvent) {
     errType = 'ERROR';
-    errMsg = err.message;
+    errMsg = `${err.error}`;
     errorObj = err.error;
   } else if (err instanceof PromiseRejectionEvent) {
     errType = 'PROMISE_REJ';
diff --git a/ui/src/common/actions.ts b/ui/src/common/actions.ts
index 40fcef3..4f16d80 100644
--- a/ui/src/common/actions.ts
+++ b/ui/src/common/actions.ts
@@ -80,6 +80,7 @@
   trackSortKey: TrackSortKey;
   trackGroup?: string;
   params?: unknown;
+  closeable?: boolean;
 }
 
 export interface PostedTrace {
@@ -211,6 +212,7 @@
         labels: track.labels,
         uri: track.uri,
         params: track.params,
+        closeable: track.closeable,
       };
       if (track.trackGroup === SCROLLING_TRACK_GROUP) {
         state.scrollingTracks.push(trackKey);
@@ -639,21 +641,6 @@
     }
   },
 
-  selectSlice(
-    state: StateDraft,
-    args: {id: number; trackKey: string; scroll?: boolean},
-  ): void {
-    state.selection = {
-      kind: 'legacy',
-      legacySelection: {
-        kind: 'SLICE',
-        id: args.id,
-        trackKey: args.trackKey,
-      },
-    };
-    state.pendingScrollId = args.scroll ? args.id : undefined;
-  },
-
   selectCounter(
     state: StateDraft,
     args: {leftTs: time; rightTs: time; id: number; trackKey: string},
@@ -741,6 +728,7 @@
       type: args.type,
       viewingOption: args.viewingOption,
       focusRegex: '',
+      expandedCallsiteByViewingOption: {},
     };
   },
 
@@ -761,10 +749,15 @@
 
   expandFlamegraphState(
     state: StateDraft,
-    args: {expandedCallsite?: CallsiteInfo},
+    args: {
+      expandedCallsite?: CallsiteInfo;
+      viewingOption: FlamegraphStateViewingOption;
+    },
   ): void {
     if (state.currentFlamegraphState === null) return;
-    state.currentFlamegraphState.expandedCallsite = args.expandedCallsite;
+    state.currentFlamegraphState.expandedCallsiteByViewingOption[
+      args.viewingOption
+    ] = args.expandedCallsite;
   },
 
   changeViewFlamegraphState(
@@ -835,6 +828,10 @@
     };
   },
 
+  setPendingScrollId(state: StateDraft, args: {pendingScrollId: number}): void {
+    state.pendingScrollId = args.pendingScrollId;
+  },
+
   clearPendingScrollId(state: StateDraft, _: {}): void {
     state.pendingScrollId = undefined;
   },
@@ -853,27 +850,6 @@
     };
   },
 
-  selectLog(
-    state: StateDraft,
-    args: {id: number; trackKey: string; scroll?: boolean},
-  ): void {
-    state.selection = {
-      kind: 'legacy',
-      legacySelection: {
-        kind: 'LOG',
-        id: args.id,
-        trackKey: args.trackKey,
-      },
-    };
-    state.pendingScrollId = args.scroll ? args.id : undefined;
-  },
-
-  deselect(state: StateDraft, _: {}): void {
-    state.selection = {
-      kind: 'empty',
-    };
-  },
-
   updateLogsPagination(state: StateDraft, args: Pagination): void {
     state.logsPagination = args;
   },
diff --git a/ui/src/common/flamegraph_util.ts b/ui/src/common/flamegraph_util.ts
index 63689ca..0817ebf 100644
--- a/ui/src/common/flamegraph_util.ts
+++ b/ui/src/common/flamegraph_util.ts
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {featureFlags} from '../core/feature_flags';
 import {CallsiteInfo, FlamegraphStateViewingOption, ProfileType} from './state';
 
 interface ViewingOption {
@@ -19,6 +20,13 @@
   name: string;
 }
 
+const SHOW_HEAP_GRAPH_DOMINATOR_TREE_FLAG = featureFlags.register({
+  id: 'showHeapGraphDominatorTree',
+  name: 'Show heap graph dominator tree',
+  description: 'Show dominated size and objects tabs in Java heap graph view.',
+  defaultValue: false,
+});
+
 export function viewingOptions(profileType: ProfileType): Array<ViewingOption> {
   switch (profileType) {
     case ProfileType.PERF_SAMPLE:
@@ -39,7 +47,22 @@
           option: FlamegraphStateViewingOption.OBJECTS_ALLOCATED_NOT_FREED_KEY,
           name: 'Objects',
         },
-      ];
+      ].concat(
+        SHOW_HEAP_GRAPH_DOMINATOR_TREE_FLAG.get()
+          ? [
+              {
+                option:
+                  FlamegraphStateViewingOption.DOMINATOR_TREE_OBJ_SIZE_KEY,
+                name: 'Dominated size',
+              },
+              {
+                option:
+                  FlamegraphStateViewingOption.DOMINATOR_TREE_OBJ_COUNT_KEY,
+                name: 'Dominated objects',
+              },
+            ]
+          : [],
+      );
     case ProfileType.HEAP_PROFILE:
       return [
         {
diff --git a/ui/src/common/state.ts b/ui/src/common/state.ts
index 19b156c..92efee5 100644
--- a/ui/src/common/state.ts
+++ b/ui/src/common/state.ts
@@ -22,7 +22,7 @@
 } from '../frontend/pivot_table_types';
 import {PrimaryTrackSortKey} from '../public/index';
 
-import {Direction} from './event_set';
+import {Direction} from '../core/event_set';
 
 import {
   selectionToLegacySelection,
@@ -147,7 +147,8 @@
 // 48. Rename legacySelection -> selection and introduce new Selection type.
 // 49. Remove currentTab, which is only relevant to TabsV1.
 // 50. Remove ftrace filter state.
-export const STATE_VERSION = 50;
+// 51. Changed structure of FlamegraphState.expandedCallsiteByViewingOption.
+export const STATE_VERSION = 51;
 
 export const SCROLLING_TRACK_GROUP = 'ScrollingTracks';
 
@@ -189,6 +190,24 @@
   OBJECTS_ALLOCATED_NOT_FREED_KEY = 'OBJECTS',
   OBJECTS_ALLOCATED_KEY = 'ALLOC_OBJECTS',
   PERF_SAMPLES_KEY = 'PERF_SAMPLES',
+  DOMINATOR_TREE_OBJ_SIZE_KEY = 'DOMINATED_OBJ_SIZE',
+  DOMINATOR_TREE_OBJ_COUNT_KEY = 'DOMINATED_OBJ_COUNT',
+}
+
+const HEAP_GRAPH_DOMINATOR_TREE_VIEWING_OPTIONS = [
+  FlamegraphStateViewingOption.DOMINATOR_TREE_OBJ_SIZE_KEY,
+  FlamegraphStateViewingOption.DOMINATOR_TREE_OBJ_COUNT_KEY,
+] as const;
+
+export type HeapGraphDominatorTreeViewingOption =
+  (typeof HEAP_GRAPH_DOMINATOR_TREE_VIEWING_OPTIONS)[number];
+
+export function isHeapGraphDominatorTreeViewingOption(
+  option: FlamegraphStateViewingOption,
+): option is HeapGraphDominatorTreeViewingOption {
+  return (
+    HEAP_GRAPH_DOMINATOR_TREE_VIEWING_OPTIONS as readonly FlamegraphStateViewingOption[]
+  ).includes(option);
 }
 
 export interface FlamegraphState {
@@ -199,7 +218,7 @@
   type: ProfileType;
   viewingOption: FlamegraphStateViewingOption;
   focusRegex: string;
-  expandedCallsite?: CallsiteInfo;
+  expandedCallsiteByViewingOption: {[key: string]: CallsiteInfo | undefined};
 }
 
 export interface CallsiteInfo {
@@ -262,6 +281,7 @@
   trackGroup?: string;
   params?: unknown;
   state?: unknown;
+  closeable?: boolean;
 }
 
 export interface TrackGroupState {
diff --git a/ui/src/controller/flamegraph_controller.ts b/ui/src/controller/flamegraph_controller.ts
index aa123f7..a10252c 100644
--- a/ui/src/controller/flamegraph_controller.ts
+++ b/ui/src/controller/flamegraph_controller.ts
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {Duration, time} from '../base/time';
+import {Duration, Time, time} from '../base/time';
 import {exists} from '../base/utils';
 import {Actions} from '../common/actions';
 import {
@@ -26,6 +26,7 @@
   FlamegraphState,
   FlamegraphStateViewingOption,
   ProfileType,
+  isHeapGraphDominatorTreeViewingOption,
 } from '../common/state';
 import {FlamegraphDetails, globals} from '../frontend/globals';
 import {publishFlamegraphDetails} from '../frontend/publish';
@@ -115,6 +116,10 @@
   private flamegraphDetails: FlamegraphDetails = {};
   private areaSelectionHandler: AreaSelectionHandler;
   private cache: TablesCache;
+  private heapGraphSelected: {upid: number; timestamp: time} = {
+    upid: -1,
+    timestamp: Time.INVALID,
+  };
 
   constructor(private args: FlamegraphControllerArgs) {
     super('main');
@@ -197,13 +202,12 @@
 
     this.lastSelectedFlamegraphState = {...selection};
 
-    const expandedId = selectedFlamegraphState.expandedCallsite
-      ? selectedFlamegraphState.expandedCallsite.id
-      : -1;
-    const rootSize =
-      selectedFlamegraphState.expandedCallsite === undefined
-        ? undefined
-        : selectedFlamegraphState.expandedCallsite.totalSize;
+    const expandedCallsite =
+      selectedFlamegraphState.expandedCallsiteByViewingOption[
+        selectedFlamegraphState.viewingOption
+      ];
+    const expandedId = expandedCallsite ? expandedCallsite.id : -1;
+    const rootSize = expandedCallsite?.totalSize;
 
     const key = `${selectedFlamegraphState.upids};${selectedFlamegraphState.start};${selectedFlamegraphState.end}`;
 
@@ -238,7 +242,7 @@
           this.lastSelectedFlamegraphState.viewingOption,
           isInAreaSelection,
           rootSize,
-          this.lastSelectedFlamegraphState.expandedCallsite,
+          expandedCallsite,
         );
       }
     } finally {
@@ -264,8 +268,10 @@
         this.lastSelectedFlamegraphState.viewingOption !==
           selection.viewingOption ||
         this.lastSelectedFlamegraphState.focusRegex !== selection.focusRegex ||
-        this.lastSelectedFlamegraphState.expandedCallsite !==
-          selection.expandedCallsite)
+        this.lastSelectedFlamegraphState.expandedCallsiteByViewingOption[
+          selection.viewingOption
+        ] !==
+          selection.expandedCallsiteByViewingOption[selection.viewingOption])
     );
   }
 
@@ -311,7 +317,7 @@
     if (this.flamegraphDatasets.has(key)) {
       currentData = this.flamegraphDatasets.get(key)!;
     } else {
-      // TODO(hjd): Show loading state.
+      // TODO(b/330703412): Show loading state.
 
       // Collecting data for drawing flamegraph for selected profile.
       // Data needs to be in following format:
@@ -322,6 +328,7 @@
         upids,
         type,
         focusRegex,
+        viewingOption,
       );
       currentData = await this.getFlamegraphDataFromTables(
         tableName,
@@ -374,6 +381,18 @@
         totalColumnName = 'cumulativeSize';
         selfColumnName = 'size';
         break;
+      case FlamegraphStateViewingOption.DOMINATOR_TREE_OBJ_COUNT_KEY:
+        orderBy = `where depth < ${maxDepth} order by depth,
+          cumulativeCount desc, name`;
+        totalColumnName = 'cumulativeCount';
+        selfColumnName = 'count';
+        break;
+      case FlamegraphStateViewingOption.DOMINATOR_TREE_OBJ_SIZE_KEY:
+        orderBy = `where depth < ${maxDepth} order by depth,
+          cumulativeSize desc, name`;
+        totalColumnName = 'cumulativeSize';
+        selfColumnName = 'size';
+        break;
       default:
         const exhaustiveCheck: never = viewingOption;
         throw new Error(`Unhandled case: ${exhaustiveCheck}`);
@@ -381,21 +400,21 @@
     }
 
     const callsites = await this.args.engine.query(`
-        SELECT
-        id as hash,
-        IFNULL(IFNULL(DEMANGLE(name), name), '[NULL]') as name,
-        IFNULL(parent_id, -1) as parentHash,
-        depth,
-        cumulative_size as cumulativeSize,
-        cumulative_alloc_size as cumulativeAllocSize,
-        cumulative_count as cumulativeCount,
-        cumulative_alloc_count as cumulativeAllocCount,
-        map_name as mapping,
-        size,
-        count,
-        IFNULL(source_file, '') as sourceFile,
-        IFNULL(line_number, -1) as lineNumber
-        from ${tableName} ${orderBy}`);
+      SELECT
+      id as hash,
+      IFNULL(IFNULL(DEMANGLE(name), name), '[NULL]') as name,
+      IFNULL(parent_id, -1) as parentHash,
+      depth,
+      cumulative_size as cumulativeSize,
+      cumulative_alloc_size as cumulativeAllocSize,
+      cumulative_count as cumulativeCount,
+      cumulative_alloc_count as cumulativeAllocCount,
+      map_name as mapping,
+      size,
+      count,
+      IFNULL(source_file, '') as sourceFile,
+      IFNULL(line_number, -1) as lineNumber
+      from ${tableName} ${orderBy}`);
 
     const flamegraphData: CallsiteInfo[] = [];
     const hashToindex: Map<number, number> = new Map();
@@ -467,6 +486,7 @@
     upids: number[],
     type: ProfileType,
     focusRegex: string,
+    viewingOption: FlamegraphStateViewingOption,
   ): Promise<string> {
     const flamegraphType = getFlamegraphType(type);
     if (type === ProfileType.PERF_SAMPLE) {
@@ -493,6 +513,14 @@
           )`,
       );
     }
+    if (
+      type === ProfileType.JAVA_HEAP_GRAPH &&
+      isHeapGraphDominatorTreeViewingOption(viewingOption)
+    ) {
+      return this.cache.getTableName(
+        await this.loadHeapGraphDominatorTreeQuery(upids[0], end),
+      );
+    }
     return this.cache.getTableName(
       `select id, name, map_name, parent_id, depth, cumulative_size,
           cumulative_alloc_size, cumulative_count, cumulative_alloc_count,
@@ -508,6 +536,91 @@
     );
   }
 
+  private async loadHeapGraphDominatorTreeQuery(upid: number, timestamp: time) {
+    const selectTreeQuery = `
+    -- cache invalidate: upid ${upid}, ts ${timestamp}
+    SELECT * FROM heap_graph_type_dominated`;
+    if (
+      this.heapGraphSelected.upid === upid &&
+      this.heapGraphSelected.timestamp === timestamp
+    ) {
+      return selectTreeQuery;
+    }
+    this.heapGraphSelected = {upid, timestamp};
+    this.args.engine.query(`
+    INCLUDE PERFETTO MODULE memory.heap_graph_dominator_tree;
+
+    -- heap graph dominator tree with objects as nodes and all relavant
+    -- object self stats and dominated stats
+    CREATE PERFETTO TABLE heap_graph_object_dominated AS
+    SELECT
+     node.id,
+     node.idom_id,
+     node.dominated_obj_count,
+     node.dominated_size_bytes + node.dominated_native_size_bytes AS dominated_size,
+     node.depth,
+     obj.type_id,
+     obj.root_type,
+     obj.self_size + obj.native_size AS self_size
+    FROM memory_heap_graph_dominator_tree node
+    JOIN heap_graph_object obj USING(id)
+    WHERE obj.upid = ${upid} AND obj.graph_sample_ts = ${timestamp}
+    -- required to accelerate the recursive cte below
+    ORDER BY idom_id;
+
+    -- calculate for each object node in the dominator tree the
+    -- HASH(path of type_id's from the super root to the object)
+    CREATE PERFETTO TABLE _dominator_tree_path_hash AS
+    WITH RECURSIVE _tree_visitor(id, path, path_hash) AS (
+      SELECT
+        id,
+        CAST(type_id AS text) || '-' || IFNULL(root_type, '') AS path,
+        HASH(
+          CAST(type_id AS text) || '-' || IFNULL(root_type, '')
+        ) AS path_hash
+      FROM heap_graph_object_dominated
+      WHERE depth = 1
+      UNION ALL
+      SELECT
+        child.id,
+        parent.path || '/' || CAST(type_id AS text) AS path,
+        HASH(parent.path || '/' || CAST(type_id AS text)) AS path_hash
+      FROM heap_graph_object_dominated child
+      JOIN _tree_visitor parent ON child.idom_id = parent.id
+    )
+    SELECT * from _tree_visitor
+    ORDER BY id;
+
+    -- merge object nodes with the same path into one "class type node", so the
+    -- end result is a tree where nodes are identified by their types and the
+    -- dominator relationships are preserved.
+    CREATE PERFETTO TABLE heap_graph_type_dominated AS
+    SELECT
+      map.path_hash as id,
+      COALESCE(cls.deobfuscated_name, cls.name, '[NULL]') || IIF(
+        node.root_type IS NOT NULL,
+        ' [' || node.root_type || ']', ''
+      ) AS name,
+      IFNULL(parent_map.path_hash, -1) AS parent_id,
+      node.depth - 1 AS depth,
+      sum(dominated_size) AS cumulative_size,
+      -1 AS cumulative_alloc_size,
+      sum(dominated_obj_count) AS cumulative_count,
+      -1 AS cumulative_alloc_count,
+      '' as map_name,
+      '' as source_file,
+      -1 as line_number,
+      sum(self_size) AS size,
+      count(*) AS count
+    FROM heap_graph_object_dominated node
+    JOIN _dominator_tree_path_hash map USING(id)
+    LEFT JOIN _dominator_tree_path_hash parent_map ON node.idom_id = parent_map.id
+    JOIN heap_graph_class cls ON node.type_id = cls.id
+    GROUP BY map.path_hash, name, parent_id, depth, map_name, source_file, line_number;`);
+
+    return selectTreeQuery;
+  }
+
   getMinSizeDisplayed(
     flamegraphData: CallsiteInfo[],
     rootSize?: number,
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index faa68e7..9e39eed 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -759,13 +759,18 @@
       if (trackKey === undefined) {
         return;
       }
-      globals.makeSelection(
-        Actions.selectChromeSlice({
+      globals.setLegacySelection(
+        {
+          kind: 'CHROME_SLICE',
           id: row.id,
           trackKey,
-          table: '',
-          scroll: true,
-        }),
+          table: 'slice',
+        },
+        {
+          clearSearch: true,
+          pendingScrollId: row.id,
+          switchToCurrentSelectionTab: false,
+        },
       );
     }
   }
diff --git a/ui/src/controller/track_decider.ts b/ui/src/controller/track_decider.ts
index 7d1039b..b71f26a 100644
--- a/ui/src/controller/track_decider.ts
+++ b/ui/src/controller/track_decider.ts
@@ -72,7 +72,7 @@
 const TEMPERATURE_REGEX = new RegExp('^.* Temperature$');
 const TEMPERATURE_GROUP = 'Temperature';
 const IRQ_GROUP = 'IRQs';
-const IRQ_REGEX = new RegExp('^Irq Cpu.*');
+const IRQ_REGEX = new RegExp('^(Irq|SoftIrq) Cpu.*');
 const CHROME_TRACK_REGEX = new RegExp('^Chrome.*|^InputLatency::.*');
 const CHROME_TRACK_GROUP = 'Chrome Global Tracks';
 const MISC_GROUP = 'Misc Global Tracks';
diff --git a/ui/src/common/event_set.ts b/ui/src/core/event_set.ts
similarity index 100%
rename from ui/src/common/event_set.ts
rename to ui/src/core/event_set.ts
diff --git a/ui/src/common/event_set_nocompile_test.ts b/ui/src/core/event_set_nocompile_test.ts
similarity index 100%
rename from ui/src/common/event_set_nocompile_test.ts
rename to ui/src/core/event_set_nocompile_test.ts
diff --git a/ui/src/common/event_set_unittest.ts b/ui/src/core/event_set_unittest.ts
similarity index 100%
rename from ui/src/common/event_set_unittest.ts
rename to ui/src/core/event_set_unittest.ts
diff --git a/ui/src/core/selection_manager.ts b/ui/src/core/selection_manager.ts
index 961ce95..2159847 100644
--- a/ui/src/core/selection_manager.ts
+++ b/ui/src/core/selection_manager.ts
@@ -13,6 +13,8 @@
 // limitations under the License.
 
 import {duration, time} from '../base/time';
+import {Store} from '../base/store';
+import {assertUnreachable} from '../base/logging';
 import {GenericSliceDetailsTabConfigBase} from './generic_slice_details_types';
 
 export enum ProfileType {
@@ -158,8 +160,104 @@
 export function selectionToLegacySelection(
   selection: Selection,
 ): LegacySelection | null {
-  if (selection.kind === 'legacy') {
-    return selection.legacySelection;
+  switch (selection.kind) {
+    case 'area':
+    case 'single':
+    case 'empty':
+      return null;
+    case 'union':
+      for (const child of selection.selections) {
+        const result = selectionToLegacySelection(child);
+        if (result !== null) {
+          return result;
+        }
+      }
+      return null;
+    case 'legacy':
+      return selection.legacySelection;
+    default:
+      assertUnreachable(selection);
+      return null;
   }
-  return null;
+}
+
+interface SelectionState {
+  selection: Selection;
+}
+
+export class SelectionManager {
+  private store: Store<SelectionState>;
+
+  constructor(store: Store<SelectionState>) {
+    this.store = store;
+  }
+
+  clear(): void {
+    this.store.edit((draft) => {
+      draft.selection = {
+        kind: 'empty',
+      };
+    });
+  }
+
+  private addSelection(selection: Selection): void {
+    this.store.edit((draft) => {
+      switch (draft.selection.kind) {
+        case 'empty':
+          draft.selection = selection;
+          break;
+        case 'union':
+          draft.selection.selections.push(selection);
+          break;
+        case 'single':
+        case 'legacy':
+        case 'area':
+          draft.selection = {
+            kind: 'union',
+            selections: [draft.selection, selection],
+          };
+          break;
+        default:
+          assertUnreachable(draft.selection);
+          break;
+      }
+    });
+  }
+
+  // There is no matching addLegacy as we did not support multi-single
+  // selection with the legacy selection system.
+  setLegacy(legacySelection: LegacySelection): void {
+    this.clear();
+    this.addSelection({
+      kind: 'legacy',
+      legacySelection,
+    });
+  }
+
+  setEvent(
+    trackKey: string,
+    eventId: string,
+    legacySelection?: LegacySelection,
+  ) {
+    this.clear();
+    this.addEvent(trackKey, eventId, legacySelection);
+  }
+
+  addEvent(
+    trackKey: string,
+    eventId: string,
+    legacySelection?: LegacySelection,
+  ) {
+    this.addSelection({
+      kind: 'single',
+      trackKey,
+      eventId,
+    });
+    if (legacySelection) {
+      this.addSelection({
+        kind: 'legacy',
+        legacySelection,
+      });
+    }
+  }
 }
diff --git a/ui/src/frontend/app.ts b/ui/src/frontend/app.ts
index 17b0ad9..940ff31 100644
--- a/ui/src/frontend/app.ts
+++ b/ui/src/frontend/app.ts
@@ -554,7 +554,7 @@
       name: 'Deselect',
       callback: () => {
         globals.timeline.deselectArea();
-        globals.makeSelection(Actions.deselect({}));
+        globals.clearSelection();
         globals.dispatch(Actions.removeNote({id: '0'}));
       },
       defaultHotkey: 'Escape',
diff --git a/ui/src/frontend/debug_tracks.ts b/ui/src/frontend/debug_tracks.ts
index 7e2ce7e..6ff6297 100644
--- a/ui/src/frontend/debug_tracks.ts
+++ b/ui/src/frontend/debug_tracks.ts
@@ -67,7 +67,6 @@
   const trackConfig: DebugTrackV2Config = {
     data,
     columns: sliceColumns,
-    closeable,
     argColumns,
   };
 
@@ -79,6 +78,7 @@
       trackSortKey: PrimaryTrackSortKey.DEBUG_TRACK,
       trackGroup: SCROLLING_TRACK_GROUP,
       params: trackConfig,
+      closeable,
     }),
   ];
   if (config?.pinned ?? true) {
@@ -117,7 +117,6 @@
 export interface CounterDebugTrackConfig {
   data: SqlDataSource;
   columns: CounterColumns;
-  closeable: boolean;
 }
 
 export interface CounterDebugTrackCreateConfig {
@@ -143,7 +142,6 @@
   const params: CounterDebugTrackConfig = {
     data,
     columns,
-    closeable,
   };
 
   const trackKey = uuidv4();
@@ -155,6 +153,7 @@
       trackSortKey: PrimaryTrackSortKey.DEBUG_TRACK,
       trackGroup: SCROLLING_TRACK_GROUP,
       params,
+      closeable,
     }),
   ];
   if (config?.pinned ?? true) {
diff --git a/ui/src/frontend/flamegraph_panel.ts b/ui/src/frontend/flamegraph_panel.ts
index 7bf69c9..fea7fb2 100644
--- a/ui/src/frontend/flamegraph_panel.ts
+++ b/ui/src/frontend/flamegraph_panel.ts
@@ -230,7 +230,9 @@
       case ProfileType.JAVA_HEAP_GRAPH:
         if (
           viewingOption ===
-          FlamegraphStateViewingOption.OBJECTS_ALLOCATED_NOT_FREED_KEY
+            FlamegraphStateViewingOption.OBJECTS_ALLOCATED_NOT_FREED_KEY ||
+          viewingOption ===
+            FlamegraphStateViewingOption.DOMINATOR_TREE_OBJ_COUNT_KEY
         ) {
           return RENDER_OBJ_COUNT;
         } else {
@@ -347,7 +349,9 @@
       current.viewingOption ===
         FlamegraphStateViewingOption.SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY ||
       current.viewingOption ===
-        FlamegraphStateViewingOption.ALLOC_SPACE_MEMORY_ALLOCATED_KEY
+        FlamegraphStateViewingOption.ALLOC_SPACE_MEMORY_ALLOCATED_KEY ||
+      current.viewingOption ===
+        FlamegraphStateViewingOption.DOMINATOR_TREE_OBJ_SIZE_KEY
         ? 'B'
         : '';
     this.flamegraph.draw(ctx, width, height, 0, 0, unit);
@@ -355,7 +359,13 @@
 
   private onMouseClick({x, y}: {x: number; y: number}): boolean {
     const expandedCallsite = this.flamegraph.onMouseClick({x, y});
-    globals.dispatch(Actions.expandFlamegraphState({expandedCallsite}));
+    globals.state.currentFlamegraphState &&
+      globals.dispatch(
+        Actions.expandFlamegraphState({
+          expandedCallsite,
+          viewingOption: globals.state.currentFlamegraphState.viewingOption,
+        }),
+      );
     return true;
   }
 
diff --git a/ui/src/frontend/flow_events_panel.ts b/ui/src/frontend/flow_events_panel.ts
index 02bf691..4af4015 100644
--- a/ui/src/frontend/flow_events_panel.ts
+++ b/ui/src/frontend/flow_events_panel.ts
@@ -67,9 +67,18 @@
     const flowClickHandler = (sliceId: number, trackId: number) => {
       const trackKey = globals.trackManager.trackKeyByTrackId.get(trackId);
       if (trackKey) {
-        globals.makeSelection(
-          Actions.selectChromeSlice({id: sliceId, trackKey, table: 'slice'}),
-          {switchToCurrentSelectionTab: false},
+        globals.setLegacySelection(
+          {
+            kind: 'CHROME_SLICE',
+            id: sliceId,
+            trackKey,
+            table: 'slice',
+          },
+          {
+            clearSearch: true,
+            pendingScrollId: undefined,
+            switchToCurrentSelectionTab: false,
+          },
         );
       }
     };
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index d6727f9..1af1399 100644
--- a/ui/src/frontend/globals.ts
+++ b/ui/src/frontend/globals.ts
@@ -14,6 +14,7 @@
 
 import {BigintMath} from '../base/bigint_math';
 import {assertExists} from '../base/logging';
+import {createStore, Store} from '../base/store';
 import {duration, Span, Time, time, TimeSpan} from '../base/time';
 import {Actions, DeferredAction} from '../common/actions';
 import {AggregateData} from '../common/aggregation_data';
@@ -52,8 +53,8 @@
 import {horizontalScrollToTs} from './scroll_helper';
 import {ServiceWorkerController} from './service_worker_controller';
 import {SliceSqlId} from './sql_types';
-import {createStore, Store} from '../base/store';
 import {PxSpan, TimeScale} from './time_scale';
+import {SelectionManager, LegacySelection} from '../core/selection_manager';
 
 const INSTANT_FOCUS_DURATION = 1n;
 const INCOMPLETE_SLICE_DURATION = 30_000n;
@@ -209,6 +210,14 @@
   clearSearch?: boolean;
 }
 
+// All of these control additional things we can do when doing a
+// selection.
+export interface LegacySelectionArgs {
+  clearSearch: boolean;
+  switchToCurrentSelectionTab: boolean;
+  pendingScrollId: number | undefined;
+}
+
 /**
  * Global accessors for state/dispatch in the frontend.
  */
@@ -217,7 +226,7 @@
 
   private _testing = false;
   private _dispatch?: Dispatch = undefined;
-  private _store = createStore(createEmptyState());
+  private _store = createStore<State>(createEmptyState());
   private _timeline?: Timeline = undefined;
   private _serviceWorkerController?: ServiceWorkerController = undefined;
   private _logging?: Analytics = undefined;
@@ -253,6 +262,7 @@
   private _traceTzOffset = Time.ZERO;
   private _tabManager = new TabManager();
   private _trackManager = new TrackManager(this._store);
+  private _selectionManager = new SelectionManager(this._store);
   private _hasFtrace: boolean = false;
 
   scrollToTrackKey?: string | number;
@@ -311,6 +321,7 @@
     this._flamegraphDetails = {};
     this._cpuProfileDetails = {};
     this.engines.clear();
+    this._selectionManager.clear();
   }
 
   // Only initialises the store - useful for testing.
@@ -583,18 +594,42 @@
 
   makeSelection(action: DeferredAction<{}>, opts: MakeSelectionOpts = {}) {
     const {switchToCurrentSelectionTab = true, clearSearch = true} = opts;
-
     const currentSelectionTabUri = 'current_selection';
 
     // A new selection should cancel the current search selection.
     clearSearch && globals.dispatch(Actions.setSearchIndex({index: -1}));
 
-    if (action.type !== 'deselect' && switchToCurrentSelectionTab) {
+    if (switchToCurrentSelectionTab) {
       globals.dispatch(Actions.showTab({uri: currentSelectionTabUri}));
     }
     globals.dispatch(action);
   }
 
+  setLegacySelection(
+    legacySelection: LegacySelection,
+    args: LegacySelectionArgs,
+  ): void {
+    this._selectionManager.setLegacy(legacySelection);
+    if (args.clearSearch) {
+      globals.dispatch(Actions.setSearchIndex({index: -1}));
+    }
+    if (args.pendingScrollId !== undefined) {
+      globals.dispatch(
+        Actions.setPendingScrollId({
+          pendingScrollId: args.pendingScrollId,
+        }),
+      );
+    }
+    if (args.switchToCurrentSelectionTab) {
+      globals.dispatch(Actions.showTab({uri: 'current_selection'}));
+    }
+  }
+
+  clearSelection(): void {
+    globals.dispatch(Actions.setSearchIndex({index: -1}));
+    this._selectionManager.clear();
+  }
+
   resetForTesting() {
     this._dispatch = undefined;
     this._timeline = undefined;
diff --git a/ui/src/frontend/keyboard_event_handler.ts b/ui/src/frontend/keyboard_event_handler.ts
index 60b7c8f..59f1d95 100644
--- a/ui/src/frontend/keyboard_event_handler.ts
+++ b/ui/src/frontend/keyboard_event_handler.ts
@@ -98,13 +98,18 @@
       const trackKeyByTrackId = globals.trackManager.trackKeyByTrackId;
       const trackKey = trackKeyByTrackId.get(flowPoint.trackId);
       if (trackKey) {
-        globals.makeSelection(
-          Actions.selectChromeSlice({
+        globals.setLegacySelection(
+          {
+            kind: 'CHROME_SLICE',
             id: flowPoint.sliceId,
             trackKey,
             table: 'slice',
-            scroll: true,
-          }),
+          },
+          {
+            clearSearch: true,
+            pendingScrollId: flowPoint.sliceId,
+            switchToCurrentSelectionTab: true,
+          },
         );
       }
     }
diff --git a/ui/src/frontend/named_slice_track.ts b/ui/src/frontend/named_slice_track.ts
index 23e0723..5eff6af 100644
--- a/ui/src/frontend/named_slice_track.ts
+++ b/ui/src/frontend/named_slice_track.ts
@@ -12,7 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {Actions} from '../common/actions';
 import {getColorForSlice} from '../core/colorizer';
 import {STR_NULL} from '../trace_processor/query_result';
 
@@ -77,16 +76,21 @@
   }
 
   onSliceClick(args: OnSliceClickArgs<T['slice']>) {
-    globals.makeSelection(
-      Actions.selectChromeSlice({
+    globals.setLegacySelection(
+      {
+        kind: 'CHROME_SLICE',
         id: args.slice.id,
         trackKey: this.trackKey,
-
         // |table| here can be either 'slice' or 'annotation'. The
         // AnnotationSliceTrack overrides the onSliceClick and sets this to
         // 'annotation'
         table: 'slice',
-      }),
+      },
+      {
+        clearSearch: true,
+        pendingScrollId: undefined,
+        switchToCurrentSelectionTab: true,
+      },
     );
   }
 }
diff --git a/ui/src/frontend/search_handler.ts b/ui/src/frontend/search_handler.ts
index fb4eb16..d9e3f0d 100644
--- a/ui/src/frontend/search_handler.ts
+++ b/ui/src/frontend/search_handler.ts
@@ -88,26 +88,46 @@
   if (currentId === undefined) return;
 
   if (source === 'cpu') {
-    globals.makeSelection(
-      Actions.selectSlice({id: currentId, trackKey, scroll: true}),
-      {clearSearch: false},
+    globals.setLegacySelection(
+      {
+        kind: 'SLICE',
+        id: currentId,
+        trackKey,
+      },
+      {
+        clearSearch: false,
+        pendingScrollId: currentId,
+        switchToCurrentSelectionTab: true,
+      },
     );
   } else if (source === 'log') {
-    globals.makeSelection(
-      Actions.selectLog({id: currentId, trackKey, scroll: true}),
-      {clearSearch: false},
+    globals.setLegacySelection(
+      {
+        kind: 'LOG',
+        id: currentId,
+        trackKey,
+      },
+      {
+        clearSearch: false,
+        pendingScrollId: currentId,
+        switchToCurrentSelectionTab: true,
+      },
     );
   } else {
     // Search results only include slices from the slice table for now.
     // When we include annotations we need to pass the correct table.
-    globals.makeSelection(
-      Actions.selectChromeSlice({
+    globals.setLegacySelection(
+      {
+        kind: 'CHROME_SLICE',
         id: currentId,
         trackKey,
         table: 'slice',
-        scroll: true,
-      }),
-      {clearSearch: false},
+      },
+      {
+        clearSearch: false,
+        pendingScrollId: currentId,
+        switchToCurrentSelectionTab: true,
+      },
     );
   }
 }
diff --git a/ui/src/frontend/slice_track.ts b/ui/src/frontend/slice_track.ts
index cde7027..e12cd73 100644
--- a/ui/src/frontend/slice_track.ts
+++ b/ui/src/frontend/slice_track.ts
@@ -362,12 +362,18 @@
     if (data === undefined) return false;
     const sliceId = data.sliceIds[sliceIndex];
     if (sliceId !== undefined && sliceId !== -1) {
-      globals.makeSelection(
-        Actions.selectChromeSlice({
+      globals.setLegacySelection(
+        {
+          kind: 'CHROME_SLICE',
           id: sliceId,
           trackKey: this.trackKey,
           table: this.namespace,
-        }),
+        },
+        {
+          clearSearch: true,
+          pendingScrollId: undefined,
+          switchToCurrentSelectionTab: true,
+        },
       );
       return true;
     }
diff --git a/ui/src/frontend/sql/slice.ts b/ui/src/frontend/sql/slice.ts
index 31a5586..050ec4e 100644
--- a/ui/src/frontend/sql/slice.ts
+++ b/ui/src/frontend/sql/slice.ts
@@ -18,7 +18,6 @@
 import {Icons} from '../../base/semantic_icons';
 import {duration, Time, time} from '../../base/time';
 import {exists} from '../../base/utils';
-import {Actions} from '../../common/actions';
 import {EngineProxy} from '../../trace_processor/engine';
 import {
   LONG,
@@ -233,13 +232,19 @@
             vnode.attrs.ts,
             Time.fromRaw(vnode.attrs.ts + dur),
           );
-          globals.makeSelection(
-            Actions.selectChromeSlice({
+
+          globals.setLegacySelection(
+            {
+              kind: 'CHROME_SLICE',
               id: vnode.attrs.id,
               trackKey,
               table: 'slice',
-            }),
-            {switchToCurrentSelectionTab: switchTab},
+            },
+            {
+              clearSearch: true,
+              pendingScrollId: undefined,
+              switchToCurrentSelectionTab: switchTab,
+            },
           );
         },
       },
diff --git a/ui/src/frontend/thread_state.ts b/ui/src/frontend/thread_state.ts
index ba294a8..8b0d520 100644
--- a/ui/src/frontend/thread_state.ts
+++ b/ui/src/frontend/thread_state.ts
@@ -151,7 +151,19 @@
   if (trackId === undefined) {
     return;
   }
-  globals.makeSelection(Actions.selectSlice({id, trackKey: trackId}));
+  globals.setLegacySelection(
+    {
+      kind: 'SLICE',
+      id,
+      trackKey: trackId,
+    },
+    {
+      clearSearch: true,
+      pendingScrollId: undefined,
+      switchToCurrentSelectionTab: true,
+    },
+  );
+
   scrollToTrackAndTs(trackId, ts);
 }
 
diff --git a/ui/src/frontend/track_panel.ts b/ui/src/frontend/track_panel.ts
index 2e15d67..4240ef4 100644
--- a/ui/src/frontend/track_panel.ts
+++ b/ui/src/frontend/track_panel.ts
@@ -37,6 +37,7 @@
 import {canvasClip} from '../common/canvas_utils';
 import {TimeScale} from './time_scale';
 import {getLegacySelection} from '../common/state';
+import {CloseTrackButton} from './close_track_button';
 
 function getTitleSize(title: string): string | undefined {
   const length = title.length;
@@ -330,6 +331,7 @@
   tags?: TrackTags;
   track?: Track;
   error?: Error | undefined;
+  closeable: boolean;
 
   // Issues a scrollTo() on this DOM element at creation time. Default: false.
   revealOnCreate?: boolean;
@@ -359,6 +361,7 @@
         m(TrackShell, {
           buttons: [
             attrs.error && m(CrashButton, {error: attrs.error}),
+            attrs.closeable && m(CloseTrackButton, {trackKey: attrs.trackKey}),
             attrs.buttons,
           ],
           title: attrs.title,
@@ -426,6 +429,7 @@
   tags?: TrackTags;
   trackFSM?: TrackCacheEntry;
   revealOnCreate?: boolean;
+  closeable: boolean;
 }
 
 export class TrackPanel implements Panel {
@@ -452,6 +456,7 @@
           trackKey: attrs.trackKey,
           error: attrs.trackFSM.getError(),
           track: attrs.trackFSM.track,
+          closeable: attrs.closeable,
         });
       }
       return m(TrackComponent, {
@@ -463,12 +468,14 @@
         track: attrs.trackFSM.track,
         error: attrs.trackFSM.getError(),
         revealOnCreate: attrs.revealOnCreate,
+        closeable: attrs.closeable,
       });
     } else {
       return m(TrackComponent, {
         trackKey: attrs.trackKey,
         title: attrs.title,
         revealOnCreate: attrs.revealOnCreate,
+        closeable: attrs.closeable,
       });
     }
   }
diff --git a/ui/src/frontend/viewer_page.ts b/ui/src/frontend/viewer_page.ts
index 6cd0fb4..719705e 100644
--- a/ui/src/frontend/viewer_page.ts
+++ b/ui/src/frontend/viewer_page.ts
@@ -241,6 +241,7 @@
           title: trackBundle.title,
           tags: trackBundle.tags,
           trackFSM: trackBundle.trackFSM,
+          closeable: trackBundle.closeable,
         });
       },
     );
@@ -270,6 +271,7 @@
             title: trackBundle.title,
             tags: trackBundle.tags,
             trackFSM: trackBundle.trackFSM,
+            closeable: trackBundle.closeable,
           });
           childTracks.push(panel);
         }
@@ -301,7 +303,7 @@
               this.keepCurrentSelection = false;
               return;
             }
-            globals.makeSelection(Actions.deselect({}));
+            globals.clearSelection();
           },
         },
         m(PanelContainer, {
@@ -327,6 +329,7 @@
               tags: trackBundle.tags,
               trackFSM: trackBundle.trackFSM,
               revealOnCreate: true,
+              closeable: trackBundle.closeable,
             });
           }),
           kind: 'TRACKS',
@@ -348,7 +351,7 @@
   // Resolve a track and its metadata through the track cache
   private resolveTrack(key: string): TrackBundle {
     const trackState = globals.state.tracks[key];
-    const {uri, params, name, labels} = trackState;
+    const {uri, params, name, labels, closeable} = trackState;
     const trackDesc = globals.trackManager.resolveTrackInfo(uri);
     const trackCacheEntry =
       trackDesc && globals.trackManager.resolveTrack(key, trackDesc, params);
@@ -361,6 +364,7 @@
       trackFSM,
       labels,
       trackIds,
+      closeable: closeable ?? false,
     };
   }
 
@@ -371,6 +375,7 @@
 
 interface TrackBundle {
   title: string;
+  closeable: boolean;
   trackFSM?: TrackCacheEntry;
   tags?: TrackTags;
   labels?: string[];
diff --git a/ui/src/plugins/dev.perfetto.Chaos/OWNERS b/ui/src/plugins/dev.perfetto.Chaos/OWNERS
new file mode 100644
index 0000000..9ee9fce
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.Chaos/OWNERS
@@ -0,0 +1 @@
+hjd@google.com
diff --git a/ui/src/plugins/dev.perfetto.Chaos/index.ts b/ui/src/plugins/dev.perfetto.Chaos/index.ts
new file mode 100644
index 0000000..c18ff7f
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.Chaos/index.ts
@@ -0,0 +1,81 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import {
+  Plugin,
+  PluginContext,
+  PluginContextTrace,
+  PluginDescriptor,
+  addDebugSliceTrack,
+} from '../../public';
+
+class Chaos implements Plugin {
+  onActivate(ctx: PluginContext): void {
+    ctx.registerCommand({
+      id: 'dev.perfetto.Chaos#CrashNow',
+      name: 'Chaos: crash now',
+      callback: () => {
+        throw new Error('Manual crash from dev.perfetto.Chaos#CrashNow');
+      },
+    });
+  }
+
+  async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
+    ctx.registerCommand({
+      id: 'dev.perfetto.Chaos#CrashNowQuery',
+      name: 'Chaos: run crashing query',
+      callback: () => {
+        ctx.engine.query(`this is a
+          syntactically
+          invalid
+          query
+          over
+          many
+          lines
+        `);
+      },
+    });
+
+    ctx.registerCommand({
+      id: 'dev.perfetto.Chaos#AddCrashingDebugTrack',
+      name: 'Chaos: add crashing debug track',
+      callback: () => {
+        addDebugSliceTrack(
+          ctx.engine,
+          {
+            sqlSource: `
+            syntactically
+            invalid
+            query
+            over
+            many
+          `,
+          },
+          `Chaos track`,
+          {ts: 'ts', dur: 'dur', name: 'name'},
+          [],
+        );
+      },
+    });
+  }
+
+  async onTraceUnload(_: PluginContextTrace): Promise<void> {}
+
+  onDeactivate(_: PluginContext): void {}
+}
+
+export const plugin: PluginDescriptor = {
+  pluginId: 'dev.perfetto.Chaos',
+  plugin: Chaos,
+};
diff --git a/ui/src/public/index.ts b/ui/src/public/index.ts
index 19c1867..97cee3c 100644
--- a/ui/src/public/index.ts
+++ b/ui/src/public/index.ts
@@ -21,7 +21,7 @@
 import {LegacySelection} from '../common/state';
 import {PanelSize} from '../frontend/panel';
 import {EngineProxy} from '../trace_processor/engine';
-import {UntypedEventSet} from '../common/event_set';
+import {UntypedEventSet} from '../core/event_set';
 
 export {EngineProxy} from '../trace_processor/engine';
 export {
diff --git a/ui/src/tracks/cpu_slices/index.ts b/ui/src/tracks/cpu_slices/index.ts
index b4c5fe0..c561cbd 100644
--- a/ui/src/tracks/cpu_slices/index.ts
+++ b/ui/src/tracks/cpu_slices/index.ts
@@ -524,7 +524,20 @@
     const id = index === -1 ? undefined : data.ids[index];
     // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
     if (!id || this.utidHoveredInThisTrack === -1) return false;
-    globals.makeSelection(Actions.selectSlice({id, trackKey: this.trackKey}));
+
+    globals.setLegacySelection(
+      {
+        kind: 'SLICE',
+        id,
+        trackKey: this.trackKey,
+      },
+      {
+        clearSearch: true,
+        pendingScrollId: undefined,
+        switchToCurrentSelectionTab: true,
+      },
+    );
+
     return true;
   }
 }
diff --git a/ui/src/tracks/debug/counter_track.ts b/ui/src/tracks/debug/counter_track.ts
index d61d573..5b37e3d 100644
--- a/ui/src/tracks/debug/counter_track.ts
+++ b/ui/src/tracks/debug/counter_track.ts
@@ -14,10 +14,7 @@
 
 import m from 'mithril';
 
-import {Actions} from '../../common/actions';
 import {BaseCounterTrack} from '../../frontend/base_counter_track';
-import {globals} from '../../frontend/globals';
-import {TrackButton} from '../../frontend/track_panel';
 import {TrackContext} from '../../public';
 import {EngineProxy} from '../../trace_processor/engine';
 import {CounterDebugTrackConfig} from '../../frontend/debug_tracks';
@@ -49,20 +46,7 @@
   }
 
   getTrackShellButtons(): m.Children {
-    return [
-      this.getCounterContextMenu(),
-      this.config.closeable &&
-        m(TrackButton, {
-          action: () => {
-            globals.dispatch(
-              Actions.removeTracks({trackKeys: [this.trackKey]}),
-            );
-          },
-          i: 'close',
-          tooltip: 'Close',
-          showButton: true,
-        }),
-    ];
+    return this.getCounterContextMenu();
   }
 
   getSqlSource(): string {
diff --git a/ui/src/tracks/debug/slice_track.ts b/ui/src/tracks/debug/slice_track.ts
index dbab2be..4ea16dc 100644
--- a/ui/src/tracks/debug/slice_track.ts
+++ b/ui/src/tracks/debug/slice_track.ts
@@ -12,12 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import m from 'mithril';
-
-import {Actions} from '../../common/actions';
-import {globals} from '../../frontend/globals';
 import {NamedSliceTrackTypes} from '../../frontend/named_slice_track';
-import {TrackButton} from '../../frontend/track_panel';
 import {TrackContext} from '../../public';
 import {EngineProxy} from '../../trace_processor/engine';
 import {
@@ -38,7 +33,6 @@
 export interface DebugTrackV2Config {
   data: SqlDataSource;
   columns: SliceColumns;
-  closeable: boolean;
   argColumns: string[];
 }
 
@@ -81,21 +75,6 @@
     };
   }
 
-  getTrackShellButtons(): m.Children {
-    return this.config.closeable
-      ? m(TrackButton, {
-          action: () => {
-            globals.dispatch(
-              Actions.removeTracks({trackKeys: [this.trackKey]}),
-            );
-          },
-          i: 'close',
-          tooltip: 'Close',
-          showButton: true,
-        })
-      : [];
-  }
-
   private async createTrackTable(
     data: SqlDataSource,
     sliceColumns: SliceColumns,