Merge "traced: preserve UUID LSB when cloning a session"
diff --git a/Android.bp b/Android.bp
index 0da0445..2586c75 100644
--- a/Android.bp
+++ b/Android.bp
@@ -4405,7 +4405,6 @@
         "protos/perfetto/metrics/android/unsymbolized_frames.proto",
         "protos/perfetto/metrics/chrome/all_chrome_metrics.proto",
         "protos/perfetto/metrics/chrome/args_class_names.proto",
-        "protos/perfetto/metrics/chrome/blink_gc_metric.proto",
         "protos/perfetto/metrics/chrome/dropped_frames.proto",
         "protos/perfetto/metrics/chrome/frame_times.proto",
         "protos/perfetto/metrics/chrome/histogram_hashes.proto",
@@ -10177,7 +10176,6 @@
         "src/trace_processor/metrics/sql/chrome/touch_jank.sql",
         "src/trace_processor/metrics/sql/chrome/vsync_intervals.sql",
         "src/trace_processor/metrics/sql/common/parent_slice.sql",
-        "src/trace_processor/metrics/sql/experimental/blink_gc_metric.sql",
         "src/trace_processor/metrics/sql/experimental/chrome_dropped_frames.sql",
         "src/trace_processor/metrics/sql/experimental/chrome_long_latency.sql",
         "src/trace_processor/metrics/sql/experimental/frame_times.sql",
@@ -10458,6 +10456,7 @@
         "src/trace_processor/stdlib/experimental/android_broadcast.sql",
         "src/trace_processor/stdlib/experimental/proto_path.sql",
         "src/trace_processor/stdlib/experimental/slices.sql",
+        "src/trace_processor/stdlib/experimental/thread_executing_span.sql",
         "src/trace_processor/stdlib/pkvm/hypervisor.sql",
     ],
     cmd: "$(location tools/gen_amalgamated_sql.py) --namespace=stdlib --cpp-out=$(out) $(in)",
diff --git a/BUILD b/BUILD
index 4d4c656..9a05edc 100644
--- a/BUILD
+++ b/BUILD
@@ -354,16 +354,28 @@
         ":libperfetto_client_experimental",
         ":protos_perfetto_common_cpp",
         ":protos_perfetto_common_zero",
+        ":protos_perfetto_config_android_cpp",
         ":protos_perfetto_config_android_zero",
+        ":protos_perfetto_config_cpp",
+        ":protos_perfetto_config_ftrace_cpp",
         ":protos_perfetto_config_ftrace_zero",
+        ":protos_perfetto_config_gpu_cpp",
         ":protos_perfetto_config_gpu_zero",
+        ":protos_perfetto_config_inode_file_cpp",
         ":protos_perfetto_config_inode_file_zero",
+        ":protos_perfetto_config_interceptors_cpp",
         ":protos_perfetto_config_interceptors_zero",
+        ":protos_perfetto_config_power_cpp",
         ":protos_perfetto_config_power_zero",
+        ":protos_perfetto_config_process_stats_cpp",
         ":protos_perfetto_config_process_stats_zero",
+        ":protos_perfetto_config_profiling_cpp",
         ":protos_perfetto_config_profiling_zero",
+        ":protos_perfetto_config_statsd_cpp",
         ":protos_perfetto_config_statsd_zero",
+        ":protos_perfetto_config_sys_stats_cpp",
         ":protos_perfetto_config_sys_stats_zero",
+        ":protos_perfetto_config_system_info_cpp",
         ":protos_perfetto_config_system_info_zero",
         ":protos_perfetto_config_track_event_cpp",
         ":protos_perfetto_config_track_event_zero",
@@ -1919,7 +1931,6 @@
 perfetto_filegroup(
     name = "src_trace_processor_metrics_sql_experimental_experimental",
     srcs = [
-        "src/trace_processor/metrics/sql/experimental/blink_gc_metric.sql",
         "src/trace_processor/metrics/sql/experimental/chrome_dropped_frames.sql",
         "src/trace_processor/metrics/sql/experimental/chrome_long_latency.sql",
         "src/trace_processor/metrics/sql/experimental/frame_times.sql",
@@ -2254,6 +2265,7 @@
         "src/trace_processor/stdlib/experimental/android_broadcast.sql",
         "src/trace_processor/stdlib/experimental/proto_path.sql",
         "src/trace_processor/stdlib/experimental/slices.sql",
+        "src/trace_processor/stdlib/experimental/thread_executing_span.sql",
     ],
 )
 
@@ -3982,7 +3994,6 @@
     srcs = [
         "protos/perfetto/metrics/chrome/all_chrome_metrics.proto",
         "protos/perfetto/metrics/chrome/args_class_names.proto",
-        "protos/perfetto/metrics/chrome/blink_gc_metric.proto",
         "protos/perfetto/metrics/chrome/dropped_frames.proto",
         "protos/perfetto/metrics/chrome/frame_times.proto",
         "protos/perfetto/metrics/chrome/histogram_hashes.proto",
diff --git a/docs/contributing/sdk-releasing.md b/docs/contributing/sdk-releasing.md
index b5b6984..07ebbd7 100644
--- a/docs/contributing/sdk-releasing.md
+++ b/docs/contributing/sdk-releasing.md
@@ -107,7 +107,7 @@
 3. Upload the new release for review.
 
 ```bash
-git cl upload --no-squash
+git cl upload --no-squash --bypass-hooks -o banned-words~skip
 ```
 
 If you get an error about a missing Change-Id field (`remote: ERROR: commit
diff --git a/docs/instrumentation/tracing-sdk.md b/docs/instrumentation/tracing-sdk.md
index 809833b..44d9b2d 100644
--- a/docs/instrumentation/tracing-sdk.md
+++ b/docs/instrumentation/tracing-sdk.md
@@ -30,7 +30,7 @@
 To start using the Client API, first check out the latest SDK release:
 
 ```bash
-git clone https://android.googlesource.com/platform/external/perfetto -b v34.0
+git clone https://android.googlesource.com/platform/external/perfetto -b v35.0
 ```
 
 The SDK consists of two files, `sdk/perfetto.h` and `sdk/perfetto.cc`. These are
diff --git a/examples/sdk/README.md b/examples/sdk/README.md
index 476ebbe..3519006 100644
--- a/examples/sdk/README.md
+++ b/examples/sdk/README.md
@@ -15,7 +15,7 @@
 First, check out the latest Perfetto release:
 
 ```bash
-git clone https://android.googlesource.com/platform/external/perfetto -b v34.0
+git clone https://android.googlesource.com/platform/external/perfetto -b v35.0
 ```
 
 Then, build using CMake:
diff --git a/gn/standalone/BUILD.gn b/gn/standalone/BUILD.gn
index 0af6551..1b23035 100644
--- a/gn/standalone/BUILD.gn
+++ b/gn/standalone/BUILD.gn
@@ -286,6 +286,14 @@
     }
   }
 
+  if (is_wasm) {
+    # As of writing (2023-06-12) WASM 128bit SIMD is supported on
+    # stable Chrome, Safari, and Firefox. See:
+    # - https://webassembly.org/roadmap/
+    # - https://emscripten.org/docs/porting/simd.html
+    cflags += [ "-msimd128" ]
+  }
+
   if (is_linux) {
     # Enable LFS (large file support) for stat() and other syscalls.
     cflags += [
diff --git a/include/perfetto/tracing/BUILD.gn b/include/perfetto/tracing/BUILD.gn
index d417668..159e155 100644
--- a/include/perfetto/tracing/BUILD.gn
+++ b/include/perfetto/tracing/BUILD.gn
@@ -16,6 +16,7 @@
   public_deps = [
     "../../../protos/perfetto/common:cpp",
     "../../../protos/perfetto/common:zero",
+    "../../../protos/perfetto/config:cpp",
     "../../../protos/perfetto/config/track_event:cpp",
     "../../../protos/perfetto/trace:zero",
     "../../../protos/perfetto/trace/interned_data:zero",
diff --git a/include/perfetto/tracing/internal/track_event_data_source.h b/include/perfetto/tracing/internal/track_event_data_source.h
index 56229e7..32993ac 100644
--- a/include/perfetto/tracing/internal/track_event_data_source.h
+++ b/include/perfetto/tracing/internal/track_event_data_source.h
@@ -368,24 +368,119 @@
   }
 
   // The following methods forward all arguments to TraceForCategoryBody
-  // while casting string constants to const char*.
-  template <typename... Arguments>
-  static void TraceForCategory(Arguments&&... args) PERFETTO_ALWAYS_INLINE {
-    TraceForCategoryBody(DecayStrType(args)...);
+  // while casting string constants to const char* and integer arguments to
+  // int64_t, uint64_t or bool.
+  template <typename CategoryType,
+            typename EventNameType,
+            typename... Arguments>
+  static void TraceForCategory(uint32_t instances,
+                               const CategoryType& category,
+                               const EventNameType& name,
+                               perfetto::protos::pbzero::TrackEvent::Type type,
+                               Arguments&&... args) PERFETTO_ALWAYS_INLINE {
+    TraceForCategoryBody(instances, DecayStrType(category), DecayStrType(name),
+                         type, DecayArgType(args)...);
   }
 
-  template <typename... Arguments>
-  static void TraceForCategoryLegacy(Arguments&&... args)
-      PERFETTO_ALWAYS_INLINE {
-    TraceForCategoryLegacyBody(DecayStrType(args)...);
+#if PERFETTO_ENABLE_LEGACY_TRACE_EVENTS
+  template <typename TrackType,
+            typename CategoryType,
+            typename EventNameType,
+            typename... Arguments,
+            typename TrackTypeCheck = typename std::enable_if<
+                std::is_convertible<TrackType, Track>::value>::type>
+  static void TraceForCategoryLegacy(
+      uint32_t instances,
+      const CategoryType& category,
+      const EventNameType& event_name,
+      perfetto::protos::pbzero::TrackEvent::Type type,
+      TrackType&& track,
+      char phase,
+      uint32_t flags,
+      Arguments&&... args) PERFETTO_ALWAYS_INLINE {
+    TraceForCategoryLegacyBody(instances, DecayStrType(category),
+                               DecayStrType(event_name), type, track, phase,
+                               flags, DecayArgType(args)...);
   }
 
-  template <typename... Arguments>
-  static void TraceForCategoryLegacyWithId(Arguments&&... args)
-      PERFETTO_ALWAYS_INLINE {
-    TraceForCategoryLegacyWithIdBody(DecayStrType(args)...);
+  template <typename TrackType,
+            typename CategoryType,
+            typename EventNameType,
+            typename TimestampType = uint64_t,
+            typename... Arguments,
+            typename TrackTypeCheck = typename std::enable_if<
+                std::is_convertible<TrackType, Track>::value>::type,
+            typename TimestampTypeCheck = typename std::enable_if<
+                IsValidTimestamp<TimestampType>()>::type>
+  static void TraceForCategoryLegacy(
+      uint32_t instances,
+      const CategoryType& category,
+      const EventNameType& event_name,
+      perfetto::protos::pbzero::TrackEvent::Type type,
+      TrackType&& track,
+      char phase,
+      uint32_t flags,
+      TimestampType&& timestamp,
+      Arguments&&... args) PERFETTO_ALWAYS_INLINE {
+    TraceForCategoryLegacyBody(instances, DecayStrType(category),
+                               DecayStrType(event_name), type, track, phase,
+                               flags, timestamp, DecayArgType(args)...);
   }
 
+  template <typename TrackType,
+            typename CategoryType,
+            typename EventNameType,
+            typename ThreadIdType,
+            typename LegacyIdType,
+            typename... Arguments,
+            typename TrackTypeCheck = typename std::enable_if<
+                std::is_convertible<TrackType, Track>::value>::type>
+  static void TraceForCategoryLegacyWithId(
+      uint32_t instances,
+      const CategoryType& category,
+      const EventNameType& event_name,
+      perfetto::protos::pbzero::TrackEvent::Type type,
+      TrackType&& track,
+      char phase,
+      uint32_t flags,
+      ThreadIdType thread_id,
+      LegacyIdType legacy_id,
+      Arguments&&... args) PERFETTO_ALWAYS_INLINE {
+    TraceForCategoryLegacyWithIdBody(
+        instances, DecayStrType(category), DecayStrType(event_name), type,
+        track, phase, flags, thread_id, legacy_id, DecayArgType(args)...);
+  }
+
+  template <typename TrackType,
+            typename CategoryType,
+            typename EventNameType,
+            typename ThreadIdType,
+            typename LegacyIdType,
+            typename TimestampType = uint64_t,
+            typename... Arguments,
+            typename TrackTypeCheck = typename std::enable_if<
+                std::is_convertible<TrackType, Track>::value>::type,
+            typename TimestampTypeCheck = typename std::enable_if<
+                IsValidTimestamp<TimestampType>()>::type>
+  static void TraceForCategoryLegacyWithId(
+      uint32_t instances,
+      const CategoryType& category,
+      const EventNameType& event_name,
+      perfetto::protos::pbzero::TrackEvent::Type type,
+      TrackType&& track,
+      char phase,
+      uint32_t flags,
+      ThreadIdType thread_id,
+      LegacyIdType legacy_id,
+      TimestampType&& timestamp,
+      Arguments&&... args) PERFETTO_ALWAYS_INLINE {
+    TraceForCategoryLegacyWithIdBody(instances, DecayStrType(category),
+                                     DecayStrType(event_name), type, track,
+                                     phase, flags, thread_id, legacy_id,
+                                     timestamp, DecayArgType(args)...);
+  }
+#endif
+
   // Initialize the track event library. Should be called before tracing is
   // enabled.
   static bool Register() {
@@ -459,6 +554,30 @@
 
   static const char* DecayStrType(const char* t) { return t; }
 
+  // The DecayArgType method is used to avoid unnecessary instantiations of
+  // templates on:
+  // * string constants of different sizes.
+  // * integers of different sizes or constness.
+  // * floats of different sizes.
+  // This allows to avoid extra instantiations of TraceForCategory templates.
+  template <typename T>
+  static T&& DecayArgType(T&& t) {
+    return std::forward<T>(t);
+  }
+
+  static const char* DecayArgType(const char* s) { return s; }
+  static uint64_t DecayArgType(uint64_t u) { return u; }
+  static uint64_t DecayArgType(uint32_t u) { return u; }
+  static uint64_t DecayArgType(uint16_t u) { return u; }
+  static uint64_t DecayArgType(uint8_t u) { return u; }
+  static int64_t DecayArgType(int64_t i) { return i; }
+  static int64_t DecayArgType(int32_t i) { return i; }
+  static int64_t DecayArgType(int16_t i) { return i; }
+  static int64_t DecayArgType(int8_t i) { return i; }
+  static bool DecayArgType(bool b) { return b; }
+  static double DecayArgType(float f) { return static_cast<double>(f); }
+  static double DecayArgType(double f) { return f; }
+
   // Once we've determined tracing to be enabled for this category, actually
   // write a trace event onto this thread's default track. Outlined to avoid
   // bloating code (mostly stack depth) at the actual trace point.
diff --git a/protos/perfetto/metrics/chrome/BUILD.gn b/protos/perfetto/metrics/chrome/BUILD.gn
index dadd191..1a7ae98 100644
--- a/protos/perfetto/metrics/chrome/BUILD.gn
+++ b/protos/perfetto/metrics/chrome/BUILD.gn
@@ -23,7 +23,6 @@
   sources = [
     "all_chrome_metrics.proto",
     "args_class_names.proto",
-    "blink_gc_metric.proto",
     "dropped_frames.proto",
     "frame_times.proto",
     "histogram_hashes.proto",
diff --git a/protos/perfetto/metrics/chrome/all_chrome_metrics.proto b/protos/perfetto/metrics/chrome/all_chrome_metrics.proto
index 9618fdd..a82d617 100644
--- a/protos/perfetto/metrics/chrome/all_chrome_metrics.proto
+++ b/protos/perfetto/metrics/chrome/all_chrome_metrics.proto
@@ -20,7 +20,6 @@
 
 import "protos/perfetto/metrics/metrics.proto";
 import "protos/perfetto/metrics/chrome/args_class_names.proto";
-import "protos/perfetto/metrics/chrome/blink_gc_metric.proto";
 import "protos/perfetto/metrics/chrome/dropped_frames.proto";
 import "protos/perfetto/metrics/chrome/frame_times.proto";
 import "protos/perfetto/metrics/chrome/histogram_hashes.proto";
@@ -42,7 +41,7 @@
   optional FrameTimes frame_times = 1002;
   optional ReportedByPage reported_by_page = 1003;
   optional ScrollJank scroll_jank = 1004;
-  optional BlinkGcMetric blink_gc_metric = 1005;
+  // reserved 1005, was BlinkGcMetric, removed in aosp/2617418
   optional MediaMetric media_metric = 1006;
   optional TouchJank touch_jank = 1007;
   optional ChromeDroppedFrames chrome_dropped_frames = 1008;
diff --git a/protos/perfetto/metrics/chrome/blink_gc_metric.proto b/protos/perfetto/metrics/chrome/blink_gc_metric.proto
deleted file mode 100644
index 5a367a7..0000000
--- a/protos/perfetto/metrics/chrome/blink_gc_metric.proto
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http_//www.apache.org/licenses/LICENSE_2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-syntax = "proto2";
-
-package perfetto.protos;
-
-import "protos/perfetto/metrics/custom_options.proto";
-
-message BlinkGcMetric {
-  repeated double blink_gc_atomic_pause_mark_epilogue = 1
-      [(unit) = "ms_smallerIsBetter"];
-  repeated double blink_gc_main_thread_cycle_full_atomic_mark_epilogue = 2
-      [(unit) = "ms_smallerIsBetter"];
-  repeated double blink_gc_atomic_pause_mark_prologue = 3
-      [(unit) = "ms_smallerIsBetter"];
-  repeated double blink_gc_main_thread_cycle_full_atomic_mark_prologue = 4
-      [(unit) = "ms_smallerIsBetter"];
-  repeated double blink_gc_atomic_pause_mark_roots = 5
-      [(unit) = "ms_smallerIsBetter"];
-  repeated double blink_gc_main_thread_cycle_full_atomic_mark_roots = 6
-      [(unit) = "ms_smallerIsBetter"];
-  repeated double blink_gc_atomic_pause_sweep_and_compact = 7
-      [(unit) = "ms_smallerIsBetter"];
-  repeated double blink_gc_main_thread_cycle_full_atomic_sweep_compact = 8
-      [(unit) = "ms_smallerIsBetter"];
-  repeated double blink_gc_complete_sweep = 9 [(unit) = "ms_smallerIsBetter"];
-  repeated double blink_gc_main_thread_cycle_full_sweep_complete = 10
-      [(unit) = "ms_smallerIsBetter"];
-  repeated double blink_gc_incremental_start = 11
-      [(unit) = "ms_smallerIsBetter"];
-  repeated double blink_gc_main_thread_cycle_full_incremental_mark_start = 12
-      [(unit) = "ms_smallerIsBetter"];
-  repeated double blink_gc_incremental_step = 13
-      [(unit) = "ms_smallerIsBetter"];
-  repeated double blink_gc_main_thread_cycle_full_incremental_mark_step = 14
-      [(unit) = "ms_smallerIsBetter"];
-  repeated double blink_gc_sweep_allocation = 15
-      [(unit) = "ms_smallerIsBetter"];
-  repeated double blink_gc_main_thread_cycle_full_sweep_on_allocation = 16
-      [(unit) = "ms_smallerIsBetter"];
-  repeated double blink_gc_sweep_task_foreground = 17
-      [(unit) = "ms_smallerIsBetter"];
-  repeated double blink_gc_main_thread_cycle_full_sweep_idle = 18
-      [(unit) = "ms_smallerIsBetter"];
-  repeated double blink_gc_unified_marking_by_v8 = 19
-      [(unit) = "ms_smallerIsBetter"];
-  repeated double unified_gc_main_thread_cycle_full_mark_step = 20
-      [(unit) = "ms_smallerIsBetter"];
-  repeated double blink_gc_atomic_pause = 21 [(unit) = "ms_smallerIsBetter"];
-  repeated double blink_gc_main_thread_cycle_full_atomic = 22
-      [(unit) = "ms_smallerIsBetter"];
-  repeated double blink_gc_atomic_pause_mark_transitive_closure = 23
-      [(unit) = "ms_smallerIsBetter"];
-  repeated double
-      blink_gc_main_thread_cycle_full_atomic_mark_transitive_closure = 24
-      [(unit) = "ms_smallerIsBetter"];
-  repeated double blink_gc_total = 25 [(unit) = "ms_smallerIsBetter"];
-  repeated double blink_gc_main_thread_cycle_full = 26
-      [(unit) = "ms_smallerIsBetter"];
-  repeated double blink_gc_mark_roots = 27 [(unit) = "ms_smallerIsBetter"];
-  repeated double blink_gc_main_thread_cycle_full_mark_roots = 28
-      [(unit) = "ms_smallerIsBetter"];
-  repeated double blink_gc_mark_transitive_closure = 29
-      [(unit) = "ms_smallerIsBetter"];
-  repeated double blink_gc_main_thread_cycle_full_mark_transitive_closure = 30
-      [(unit) = "ms_smallerIsBetter"];
-  repeated double blink_gc_mark_foreground = 31 [(unit) = "ms_smallerIsBetter"];
-  repeated double blink_gc_main_thread_cycle_full_mark = 32
-      [(unit) = "ms_smallerIsBetter"];
-  repeated double blink_gc_mark_foreground_forced = 33
-      [(unit) = "ms_smallerIsBetter"];
-  repeated double blink_gc_main_thread_cycle_full_mark_forced = 34
-      [(unit) = "ms_smallerIsBetter"];
-  repeated double blink_gc_mark_background = 35 [(unit) = "ms_smallerIsBetter"];
-  repeated double blink_gc_concurrent_thread_cycle_full_mark = 36
-      [(unit) = "ms_smallerIsBetter"];
-  repeated double blink_gc_sweep_foreground = 37
-      [(unit) = "ms_smallerIsBetter"];
-  repeated double blink_gc_main_thread_cycle_full_sweep = 38
-      [(unit) = "ms_smallerIsBetter"];
-  repeated double blink_gc_sweep_background = 39
-      [(unit) = "ms_smallerIsBetter"];
-  repeated double blink_gc_concurrent_thread_cycle_full_sweep = 40
-      [(unit) = "ms_smallerIsBetter"];
-  repeated double unified_gc_total = 41 [(unit) = "ms_smallerIsBetter"];
-}
diff --git a/src/profiling/memory/heapprofd_producer.cc b/src/profiling/memory/heapprofd_producer.cc
index add6109..d4b8ccc 100644
--- a/src/profiling/memory/heapprofd_producer.cc
+++ b/src/profiling/memory/heapprofd_producer.cc
@@ -209,9 +209,9 @@
     : task_runner_(task_runner),
       mode_(mode),
       exit_when_done_(exit_when_done),
-      unwinding_workers_(MakeUnwindingWorkers(this, kUnwinderThreads)),
       socket_delegate_(this),
-      weak_factory_(this) {
+      weak_factory_(this),
+      unwinding_workers_(MakeUnwindingWorkers(this, kUnwinderThreads)) {
   CheckDataSourceCpuTask();
   CheckDataSourceMemoryTask();
 }
diff --git a/src/profiling/memory/heapprofd_producer.h b/src/profiling/memory/heapprofd_producer.h
index 83c6cac..fa1f058 100644
--- a/src/profiling/memory/heapprofd_producer.h
+++ b/src/profiling/memory/heapprofd_producer.h
@@ -318,7 +318,6 @@
 
   std::map<FlushRequestID, size_t> flushes_in_progress_;
   std::map<DataSourceInstanceID, DataSource> data_sources_;
-  std::vector<UnwindingWorker> unwinding_workers_;
 
   // Specific to mode_ == kChild
   Process target_process_{base::kInvalidPid, ""};
@@ -326,7 +325,11 @@
 
   SocketDelegate socket_delegate_;
 
-  base::WeakPtrFactory<HeapprofdProducer> weak_factory_;  // Keep last.
+  base::WeakPtrFactory<HeapprofdProducer> weak_factory_;
+
+  // UnwindingWorker's destructor might attempt to post producer tasks, so this
+  // needs to outlive weak_factory_.
+  std::vector<UnwindingWorker> unwinding_workers_;
 };
 
 }  // namespace profiling
diff --git a/src/trace_processor/metrics/sql/experimental/BUILD.gn b/src/trace_processor/metrics/sql/experimental/BUILD.gn
index 1431bd3..bbf3918 100644
--- a/src/trace_processor/metrics/sql/experimental/BUILD.gn
+++ b/src/trace_processor/metrics/sql/experimental/BUILD.gn
@@ -19,7 +19,6 @@
 
 perfetto_sql_source_set("experimental") {
   sources = [
-    "blink_gc_metric.sql",
     "chrome_dropped_frames.sql",
     "chrome_long_latency.sql",
     "frame_times.sql",
diff --git a/src/trace_processor/metrics/sql/experimental/blink_gc_metric.sql b/src/trace_processor/metrics/sql/experimental/blink_gc_metric.sql
deleted file mode 100644
index e8307b6..0000000
--- a/src/trace_processor/metrics/sql/experimental/blink_gc_metric.sql
+++ /dev/null
@@ -1,581 +0,0 @@
--- Maps non-aggregated Blink GC events in timeline to telemetry friendly
--- names.
---
--- This includes the old style or the new naming scheme one which only occur on
--- the main thread.
-DROP VIEW IF EXISTS blink_non_aggregated_gc_event_name;
-CREATE VIEW blink_non_aggregated_gc_event_name AS
-SELECT
-  'BlinkGC.AtomicPauseMarkEpilogue' AS name,
-  'blink-gc-atomic-pause-mark-epilogue' AS old_event_name,
-  'blink:gc:main_thread:cycle:full:atomic:mark:epilogue' AS new_event_name
-UNION ALL
-SELECT
-  'BlinkGC.AtomicPauseMarkPrologue',
-  'blink-gc-atomic-pause-mark-prologue',
-  'blink:gc:main_thread:cycle:full:atomic:mark:prologue'
-UNION ALL
-SELECT
-  'BlinkGC.AtomicPauseMarkRoots',
-  'blink-gc-atomic-pause-mark-roots',
-  'blink:gc:main_thread:cycle:full:atomic:mark:roots'
-UNION ALL
-SELECT
-  'BlinkGC.IncrementalMarkingStartMarking',
-  'blink-gc-incremental-start',
-  'blink:gc:main_thread:cycle:full:incremental:mark:start'
-UNION ALL
-SELECT
-  'BlinkGC.IncrementalMarkingStep',
-  'blink-gc-incremental-step',
-  'blink:gc:main_thread:cycle:full:incremental:mark:step'
-UNION ALL
-SELECT
-  'BlinkGC.UnifiedMarkingStep',
-  'blink-gc-unified-marking-by-v8',
-  'unified:gc:main_thread:cycle:full:mark:step'
-UNION ALL
-SELECT
-  'BlinkGC.CompleteSweep',
-  'blink-gc-complete-sweep',
-  'blink:gc:main_thread:cycle:full:sweep:complete'
-UNION ALL
-SELECT
-  'BlinkGC.LazySweepInIdle',
-  'blink-gc-sweep-task-foreground',
-  'blink:gc:main_thread:cycle:full:sweep:idle'
-UNION ALL
-SELECT
-  'BlinkGC.LazySweepOnAllocation',
-  'blink-gc-sweep-allocation',
-  'blink:gc:main_thread:cycle:full:sweep:on_allocation'
-UNION ALL
-SELECT
-  'BlinkGC.AtomicPauseSweepAndCompact' AS name,
-  'blink-gc-atomic-pause-sweep-and-compact' AS old_event_name,
-  'blink:gc:main_thread:cycle:full:atomic:sweep:compact' AS new_event_name;
-
--- Get all the slices we care about. These are ones that start with V8.GC or
--- BlinkGC. If you need more you need to modify the where clause for
--- blink_gc_cpu_slice.
-DROP TABLE IF EXISTS blink_gc_cpu_slice;
-CREATE TABLE blink_gc_cpu_slice AS
-SELECT
-  CASE WHEN dur != 0 THEN cpuDurNs / 1e6 ELSE 0.0 END AS cpuDurMs,
-  *
-FROM (
-  SELECT
-    COALESCE(EXTRACT_ARG(arg_set_id, 'debug.forced'), FALSE)
-    -- This subquery replaces
-    -- metrics.v8.utils.isForcedGarbageCollectionEvent(event)
-    OR (
-      SELECT
-        id
-      FROM ANCESTOR_SLICE(slice.id) AS ancestor
-      WHERE ancestor.name = 'V8.GCLowMemoryNotification' LIMIT 1
-    ) IS NOT NULL AS forced,
-    -- upid replaces pid, because its more fool proof ensuring uniqueness.
-    thread.upid || ':' || EXTRACT_ARG(arg_set_id, 'debug.epoch') AS epoch,
-    slice.thread_dur AS cpuDurNs,
-    slice.*
-  FROM slice
-  JOIN thread_track ON slice.track_id = thread_track.id
-  JOIN thread ON thread_track.utid = thread.id
-  WHERE
-    slice.dur >= 0 AND (
-      slice.name GLOB "V8.GC*" OR (slice.name GLOB "BlinkGC*" AND NOT forced)
-    )
-);
-
--- This grabs all the single events for "BlinkGC.*", and restricts to only
--- forced events.
-DROP TABLE IF EXISTS blink_slice;
-CREATE TABLE blink_slice AS
-SELECT
-  event_name.old_event_name AS blink_non_aggregated_gc_event_name,
-  event_name.new_event_name AS blink_non_aggregated_gc_events_new_name,
-  blink_gc_cpu_slice.*
-FROM
-  blink_gc_cpu_slice LEFT JOIN
-  blink_non_aggregated_gc_event_name AS event_name ON
-    event_name.name = blink_gc_cpu_slice.name
-WHERE
-  blink_gc_cpu_slice.name GLOB "BlinkGC*" AND NOT forced;
-
--- This groups all the events by name and epoch for from "blink_slice" for easy
--- access.
-DROP TABLE IF EXISTS blink_per_epoch_slice;
-CREATE TABLE blink_per_epoch_slice AS
-SELECT
-  name,
-  epoch,
-  blink_non_aggregated_gc_event_name,
-  blink_non_aggregated_gc_events_new_name,
-  SUM(cpuDurMs) AS cpuDurPerEpochMs
-FROM blink_slice
-GROUP BY 1, 2, 3, 4;
-
--- All events that should be summed up to 'blink-gc-mark-roots'.
-DROP VIEW IF EXISTS blink_top_gc_roots_marking_event;
-CREATE VIEW blink_top_gc_roots_marking_event AS
-SELECT * FROM blink_slice WHERE name IN (
-  'BlinkGC.VisitRoots'
-);
-
--- All events that should be summed up to
--- 'blink-gc-atomic-pause-mark-transitive-closure'.
-DROP VIEW IF EXISTS blink_gc_atomic_pause_transitive_closure_event;
-CREATE VIEW blink_gc_atomic_pause_transitive_closure_event AS
-SELECT * FROM blink_slice WHERE name IN (
-  'BlinkGC.AtomicPauseMarkTransitiveClosure'
-);
-
--- All events that should be summed up to 'blink-gc-mark-transitive-closure'.
-DROP VIEW IF EXISTS blink_gc_foreground_marking_transitive_closure_event;
-CREATE VIEW
-blink_gc_foreground_marking_transitive_closure_event AS
-SELECT * FROM blink_slice WHERE name IN (
-  'BlinkGC.AtomicPauseMarkTransitiveClosure',
-  'BlinkGC.IncrementalMarkingStep',
-  'BlinkGC.UnifiedMarkingStep'
-);
-
--- Names of Blink GC foreground marking events in timeline.
-DROP VIEW IF EXISTS blink_top_gc_foreground_marking_event;
-CREATE VIEW blink_top_gc_foreground_marking_event AS
-SELECT * FROM blink_slice WHERE name IN (
-  'BlinkGC.AtomicPauseMarkEpilogue',
-  'BlinkGC.AtomicPauseMarkPrologue',
-  'BlinkGC.AtomicPauseMarkRoots',
-  'BlinkGC.IncrementalMarkingStartMarking'
-)
-UNION ALL
-SELECT * FROM blink_gc_foreground_marking_transitive_closure_event;
-
--- Names of Blink GC foreground marking events in timeline.
-DROP VIEW IF EXISTS blink_gc_forced_foreground_marking_event;
-CREATE VIEW blink_gc_forced_foreground_marking_event AS
-SELECT * FROM blink_slice WHERE name IN (
-  'BlinkGC.AtomicPauseMarkEpilogue',
-  'BlinkGC.AtomicPauseMarkPrologue',
-  'BlinkGC.AtomicPauseMarkRoots',
-  'BlinkGC.IncrementalMarkingStartMarking',
-  'BlinkGC.MarkBailOutObjects',
-  'BlinkGC.MarkFlushV8References',
-  'BlinkGC.MarkFlushEphemeronPairs'
-);
-
--- Names of Blink GC background marking events in timeline.
-DROP VIEW IF EXISTS blink_top_gc_background_marking_event;
-CREATE VIEW blink_top_gc_background_marking_event AS
-SELECT * FROM blink_slice WHERE name IN (
-  'BlinkGC.ConcurrentMarkingStep'
-);
-
--- Names of Blink GC foreground sweeping events in timeline.
-DROP VIEW IF EXISTS blink_top_gc_foreground_sweeping_event;
-CREATE VIEW blink_top_gc_foreground_sweeping_event AS
-SELECT * FROM blink_slice WHERE name IN (
-  'BlinkGC.CompleteSweep',
-  'BlinkGC.LazySweepInIdle',
-  'BlinkGC.LazySweepOnAllocation'
-);
-
--- Names of Blink GC background sweeping events in timeline.
-DROP VIEW IF EXISTS blink_top_gc_background_sweeping_event;
-CREATE VIEW blink_top_gc_background_sweeping_event AS
-SELECT * FROM blink_slice WHERE name IN (
-  'BlinkGC.ConcurrentSweepingStep'
-);
-
--- Names of all Blink Unified GC events in timeline.
-DROP VIEW IF EXISTS blink_top_gc_event;
-CREATE VIEW blink_top_gc_event AS
-SELECT * FROM blink_slice WHERE name IN (
-  SELECT name FROM blink_non_aggregated_gc_event_name
-) OR name IN (
-  SELECT name FROM blink_gc_atomic_pause_transitive_closure_event
-);
-
--- All events that should be summed up to 'blink-gc-atomic-pause'. Note that
--- this events need to have an epoch counter in args.epoch.
-DROP VIEW IF EXISTS atomic_pause_event;
-CREATE VIEW atomic_pause_event AS
-SELECT * FROM blink_slice WHERE name IN (
-  'BlinkGC.AtomicPauseMarkEpilogue',
-  'BlinkGC.AtomicPauseMarkPrologue',
-  'BlinkGC.AtomicPauseMarkRoots',
-  'BlinkGC.AtomicPauseMarkTransitiveClosure',
-  'BlinkGC.AtomicPauseSweepAndCompact'
-);
-
--- This is a more complex variable so benefits from additional comments so we
--- pull it out of the proto filling.
-DROP VIEW IF EXISTS unified_gc_total;
-CREATE VIEW unified_gc_total AS
-SELECT
-  *
-FROM blink_gc_cpu_slice
-WHERE (
-  -- This subclause replaces
-  -- metrics.v8.utils.isNotForcedTopGarbageCollectionEvent()
-
-  -- These names are found in isTopGarbageCollectionEvent().
-  name IN (
-    'V8.GCCompactor',
-    'V8.GCFinalizeMC',
-    'V8.GCFinalizeMCReduceMemory',
-    'V8.GCIncrementalMarking',
-    'V8.GCIncrementalMarkingFinalize',
-    'V8.GCIncrementalMarkingStart',
-    'V8.GCPhantomHandleProcessingCallback',
-    'V8.GCScavenger'
-  ) AND (
-    -- This replaces isForcedGarbageCollectionEvent.
-    SELECT name FROM ANCESTOR_SLICE(blink_gc_cpu_slice.id) AS ancestor
-    WHERE ancestor.name = 'V8.GCLowMemoryNotification'
-    LIMIT 1
-  ) IS NULL
-) OR (
-  -- This subclause replaces isNonNestedNonForcedBlinkGarbageCollectionEvent().
-  name IN (
-    -- This subquery replaces isNonForcedBlinkGarbageCollectionEvent().
-    SELECT name FROM blink_top_gc_event
-  ) AND (
-    -- This subquery replaces metrics.v8.utils.isGarbageCollectionEvent().
-    SELECT name FROM ANCESTOR_SLICE(blink_gc_cpu_slice.id) AS ancestor
-    WHERE
-      ancestor.name GLOB "V8.GC*"
-      AND ancestor.name != 'V8.GCLowMemoryNotification'
-    LIMIT 1
-  ) IS NULL
-);
-
--- This table name is just "file_name" + "_output" used by TBMv3 to know which
--- view to extract the proto BlinkGcMetric out of.
-DROP VIEW IF EXISTS blink_gc_metric_output;
-CREATE VIEW blink_gc_metric_output AS
-SELECT BlinkGcMetric(
-  'blink_gc_atomic_pause_mark_epilogue',
-  (
-    SELECT
-      RepeatedField(cpuDurMs)
-    FROM blink_slice
-    WHERE
-      blink_non_aggregated_gc_event_name = 'blink-gc-atomic-pause-mark-epilogue'
-  ),
-  'blink_gc_main_thread_cycle_full_atomic_mark_epilogue',
-  (
-    SELECT
-      RepeatedField(cpuDurPerEpochMs)
-    FROM blink_per_epoch_slice
-    WHERE
-      blink_non_aggregated_gc_events_new_name
-      = 'blink:gc:main_thread:cycle:full:atomic:mark:epilogue'
-  ),
-  'blink_gc_atomic_pause_mark_prologue',
-  (
-    SELECT
-      RepeatedField(cpuDurMs)
-    FROM blink_slice
-    WHERE
-      blink_non_aggregated_gc_event_name
-      = 'blink-gc-atomic-pause-mark-prologue'
-  ),
-  'blink_gc_main_thread_cycle_full_atomic_mark_prologue',
-  (
-    SELECT
-      RepeatedField(cpuDurPerEpochMs)
-    FROM blink_per_epoch_slice
-    WHERE
-      blink_non_aggregated_gc_events_new_name
-      = 'blink:gc:main_thread:cycle:full:atomic:mark:prologue'
-  ),
-  'blink_gc_atomic_pause_mark_roots',
-  (
-    SELECT
-      RepeatedField(cpuDurMs)
-    FROM blink_slice
-    WHERE
-      blink_non_aggregated_gc_event_name = 'blink-gc-atomic-pause-mark-roots'
-  ),
-  'blink_gc_main_thread_cycle_full_atomic_mark_roots',
-  (
-    SELECT
-      RepeatedField(cpuDurPerEpochMs)
-    FROM blink_per_epoch_slice
-    WHERE
-      blink_non_aggregated_gc_events_new_name
-      = 'blink:gc:main_thread:cycle:full:atomic:mark:roots'
-  ),
-  'blink_gc_atomic_pause_sweep_and_compact',
-  (
-    SELECT
-      RepeatedField(cpuDurMs)
-    FROM blink_slice
-    WHERE
-      blink_non_aggregated_gc_event_name
-      = 'blink-gc-atomic-pause-sweep-and-compact'
-  ),
-  'blink_gc_main_thread_cycle_full_atomic_sweep_compact',
-  (
-    SELECT
-      RepeatedField(cpuDurPerEpochMs)
-    FROM blink_per_epoch_slice
-    WHERE
-      blink_non_aggregated_gc_events_new_name
-      = 'blink:gc:main_thread:cycle:full:atomic:sweep:compact'
-  ),
-  'blink_gc_complete_sweep',
-  (
-    SELECT
-      RepeatedField(cpuDurMs)
-    FROM blink_slice
-    WHERE blink_non_aggregated_gc_event_name = 'blink-gc-complete-sweep'
-  ),
-  'blink_gc_main_thread_cycle_full_sweep_complete',
-  (
-    SELECT
-      RepeatedField(cpuDurPerEpochMs)
-    FROM blink_per_epoch_slice
-    WHERE
-      blink_non_aggregated_gc_events_new_name
-      = 'blink:gc:main_thread:cycle:full:sweep:complete'
-  ),
-  'blink_gc_incremental_start',
-  (
-    SELECT
-      RepeatedField(cpuDurMs)
-    FROM blink_slice
-    WHERE blink_non_aggregated_gc_event_name = 'blink-gc-incremental-start'
-  ),
-  'blink_gc_main_thread_cycle_full_incremental_mark_start',
-  (
-    SELECT
-      RepeatedField(cpuDurPerEpochMs)
-    FROM blink_per_epoch_slice
-    WHERE
-      blink_non_aggregated_gc_events_new_name
-      = 'blink:gc:main_thread:cycle:full:incremental:mark:start'
-  ),
-  'blink_gc_incremental_step',
-  (
-    SELECT
-      RepeatedField(cpuDurMs)
-    FROM blink_slice
-    WHERE blink_non_aggregated_gc_event_name = 'blink-gc-incremental-step'
-  ),
-  'blink_gc_main_thread_cycle_full_incremental_mark_step',
-  (
-    SELECT
-      RepeatedField(cpuDurPerEpochMs)
-    FROM blink_per_epoch_slice
-    WHERE
-      blink_non_aggregated_gc_events_new_name
-      = 'blink:gc:main_thread:cycle:full:incremental:mark:step'
-  ),
-  'blink_gc_sweep_allocation',
-  (
-    SELECT
-      RepeatedField(cpuDurMs)
-    FROM blink_slice
-    WHERE blink_non_aggregated_gc_event_name = 'blink-gc-sweep-allocation'
-  ),
-  'blink_gc_main_thread_cycle_full_sweep_on_allocation',
-  (
-    SELECT
-      RepeatedField(cpuDurPerEpochMs)
-    FROM blink_per_epoch_slice
-    WHERE
-      blink_non_aggregated_gc_events_new_name
-      = 'blink:gc:main_thread:cycle:full:sweep:on_allocation'
-  ),
-  'blink_gc_sweep_task_foreground',
-  (
-    SELECT
-      RepeatedField(cpuDurMs)
-    FROM blink_slice
-    WHERE blink_non_aggregated_gc_event_name = 'blink-gc-sweep-task-foreground'
-  ),
-  'blink_gc_main_thread_cycle_full_sweep_idle',
-  (
-    SELECT
-      RepeatedField(cpuDurPerEpochMs)
-    FROM blink_per_epoch_slice
-    WHERE
-      blink_non_aggregated_gc_events_new_name
-      = 'blink:gc:main_thread:cycle:full:sweep:idle'
-  ),
-  'blink_gc_unified_marking_by_v8',
-  (
-    SELECT
-      RepeatedField(cpuDurMs)
-    FROM blink_slice
-    WHERE blink_non_aggregated_gc_event_name = 'blink-gc-unified-marking-by-v8'
-  ),
-  'unified_gc_main_thread_cycle_full_mark_step',
-  (
-    SELECT
-      RepeatedField(cpuDurPerEpochMs)
-    FROM blink_per_epoch_slice
-    WHERE
-      blink_non_aggregated_gc_events_new_name
-      = 'unified:gc:main_thread:cycle:full:mark:step'
-  ),
-  'blink_gc_atomic_pause',
-  (
-    SELECT
-      RepeatedField(cpuDurMs)
-    FROM atomic_pause_event
-  ),
-  'blink_gc_main_thread_cycle_full_atomic',
-  (
-    SELECT RepeatedField(val) FROM (
-      SELECT
-        SUM(cpuDurMs) AS val
-      FROM atomic_pause_event
-      GROUP BY epoch
-    )
-  ),
-  'blink_gc_atomic_pause_mark_transitive_closure',
-  (
-    SELECT
-      RepeatedField(cpuDurMs)
-    FROM blink_gc_atomic_pause_transitive_closure_event
-  ),
-  'blink_gc_main_thread_cycle_full_atomic_mark_transitive_closure',
-  (
-    SELECT RepeatedField(val) FROM (
-      SELECT
-        SUM(cpuDurMs) AS val
-      FROM blink_gc_atomic_pause_transitive_closure_event
-      GROUP BY epoch
-    )
-  ),
-  'blink_gc_total',
-  (
-    SELECT
-      RepeatedField(cpuDurMs)
-    FROM blink_top_gc_event
-  ),
-  'blink_gc_main_thread_cycle_full',
-  (
-    SELECT RepeatedField(val) FROM (
-      SELECT
-        SUM(cpuDurMs) AS val
-      FROM blink_top_gc_event
-      GROUP BY epoch
-    )
-  ),
-  'blink_gc_mark_roots',
-  (
-    SELECT
-      RepeatedField(cpuDurMs)
-    FROM blink_top_gc_roots_marking_event
-  ),
-  'blink_gc_main_thread_cycle_full_mark_roots',
-  (
-    SELECT RepeatedField(val) FROM (
-      SELECT
-        SUM(cpuDurMs) AS val
-      FROM blink_top_gc_roots_marking_event
-      GROUP BY epoch
-    )
-  ),
-  'blink_gc_mark_transitive_closure',
-  (
-    SELECT
-      RepeatedField(cpuDurMs)
-    FROM blink_gc_foreground_marking_transitive_closure_event
-  ),
-  'blink_gc_main_thread_cycle_full_mark_transitive_closure',
-  (
-    SELECT RepeatedField(val) FROM (
-      SELECT
-        SUM(cpuDurMs) AS val
-      FROM blink_gc_foreground_marking_transitive_closure_event
-      GROUP BY epoch
-    )
-  ),
-  'blink_gc_mark_foreground',
-  (
-    SELECT
-      RepeatedField(cpuDurMs)
-    FROM blink_top_gc_foreground_marking_event
-  ),
-  'blink_gc_main_thread_cycle_full_mark',
-  (
-    SELECT RepeatedField(val) FROM (
-      SELECT
-        SUM(cpuDurMs) AS val
-      FROM blink_top_gc_foreground_marking_event
-      GROUP BY epoch
-    )
-  ),
-  'blink_gc_mark_foreground_forced',
-  (
-    SELECT
-      RepeatedField(cpuDurMs)
-    FROM blink_gc_forced_foreground_marking_event
-  ),
-  'blink_gc_main_thread_cycle_full_mark_forced',
-  (
-    SELECT RepeatedField(val) FROM (
-      SELECT
-        SUM(cpuDurMs) AS val
-      FROM blink_gc_forced_foreground_marking_event
-      GROUP BY epoch
-    )
-  ),
-  'blink_gc_mark_background',
-  (
-    SELECT
-      RepeatedField(cpuDurMs)
-    FROM blink_top_gc_background_marking_event
-  ),
-  'blink_gc_concurrent_thread_cycle_full_mark',
-  (
-    SELECT RepeatedField(val) FROM (
-      SELECT
-        SUM(cpuDurMs) AS val
-      FROM blink_top_gc_background_marking_event
-      GROUP BY epoch
-    )
-  ),
-  'blink_gc_sweep_foreground',
-  (
-    SELECT
-      RepeatedField(cpuDurMs)
-    FROM blink_top_gc_foreground_sweeping_event
-  ),
-  'blink_gc_main_thread_cycle_full_sweep',
-  (
-    SELECT RepeatedField(val) FROM (
-      SELECT
-        SUM(cpuDurMs) AS val
-      FROM blink_top_gc_foreground_sweeping_event
-      GROUP BY epoch
-    )
-  ),
-  'blink_gc_sweep_background',
-  (
-    SELECT
-      RepeatedField(cpuDurMs)
-    FROM blink_top_gc_background_sweeping_event
-  ),
-  'blink_gc_concurrent_thread_cycle_full_sweep',
-  (
-    SELECT RepeatedField(val) FROM (
-      SELECT
-        SUM(cpuDurMs) AS val
-      FROM blink_top_gc_background_sweeping_event
-      GROUP BY epoch
-    )
-  ),
-  'unified_gc_total',
-  (
-    SELECT
-      RepeatedField(cpuDurMs)
-    FROM unified_gc_total
-  )
-);
diff --git a/src/trace_processor/stdlib/chrome/chrome_scroll_janks.sql b/src/trace_processor/stdlib/chrome/chrome_scroll_janks.sql
index 752b717..9f0f29e 100644
--- a/src/trace_processor/stdlib/chrome/chrome_scroll_janks.sql
+++ b/src/trace_processor/stdlib/chrome/chrome_scroll_janks.sql
@@ -14,6 +14,7 @@
 
 -- TODO(b/286187288): Move this dependency to stdlib.
 SELECT RUN_METRIC('chrome/event_latency_scroll_jank_cause.sql');
+SELECT IMPORT('common.slices');
 
 -- Selects EventLatency slices that correspond with janks in a scroll. This is
 -- based on the V2 version of scroll jank metrics.
@@ -38,7 +39,11 @@
     e.sub_cause_of_jank
 FROM slice s
 JOIN event_latency_scroll_jank_cause e
-  ON s.id = e.slice_id;
+  ON s.id = e.slice_id
+WHERE
+  HAS_DESCENDANT_SLICE_WITH_NAME(
+    s.id,
+    'SubmitCompositorFrameToPresentationCompositorFrame');
 
 -- Defines slices for all of janky scrolling intervals in a trace.
 --
diff --git a/src/trace_processor/stdlib/chrome/chrome_scrolls.sql b/src/trace_processor/stdlib/chrome/chrome_scrolls.sql
index 35b13b9..8576fec 100644
--- a/src/trace_processor/stdlib/chrome/chrome_scrolls.sql
+++ b/src/trace_processor/stdlib/chrome/chrome_scrolls.sql
@@ -77,6 +77,7 @@
 CREATE VIEW chrome_scrolling_intervals AS
 WITH all_scrolls AS (
   SELECT
+    id AS scroll_id,
     s.ts AS start_ts,
     s.ts + s.dur AS end_ts
   FROM chrome_scrolls s),
@@ -100,6 +101,7 @@
   FROM range_starts)
 SELECT
   range_group AS id,
+  GROUP_CONCAT(scroll_id) AS scroll_ids,
   MIN(start_ts) AS ts,
   MAX(end_ts) - MIN(start_ts) AS dur
 FROM range_groups
diff --git a/src/trace_processor/stdlib/experimental/BUILD.gn b/src/trace_processor/stdlib/experimental/BUILD.gn
index 86fbc26..5784707 100644
--- a/src/trace_processor/stdlib/experimental/BUILD.gn
+++ b/src/trace_processor/stdlib/experimental/BUILD.gn
@@ -19,5 +19,6 @@
     "android_broadcast.sql",
     "proto_path.sql",
     "slices.sql",
+    "thread_executing_span.sql",
   ]
 }
diff --git a/src/trace_processor/stdlib/experimental/thread_executing_span.sql b/src/trace_processor/stdlib/experimental/thread_executing_span.sql
new file mode 100644
index 0000000..338ec66
--- /dev/null
+++ b/src/trace_processor/stdlib/experimental/thread_executing_span.sql
@@ -0,0 +1,514 @@
+--
+-- Copyright 2023 The Android Open Source Project
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--     https://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+
+-- A 'thread_executing_span' is thread_state span starting with a runnable slice
+-- until the next runnable slice that's woken up by a process (as opposed
+-- to an interrupt). Note that within a 'thread_executing_span' we can have sleep
+-- spans blocked on an interrupt.
+-- We consider the id of this span to be the id of the first thread_state in the span.
+
+--
+-- Finds all runnable states that are woken up by a process.
+--
+-- We achieve this by checking that the |thread_state.irq_context|
+-- value is NOT 1. In otherwords, it is either 0 or NULL. The NULL check
+-- is important to support older Android versions.
+--
+-- On older versions of Android (<U). We don't have IRQ context information,
+-- so this table might contain wakeups from interrupt context, consequently, the
+-- wakeup graph generated might not be accurate.
+--
+CREATE VIEW internal_runnable_state
+AS
+SELECT
+  thread_state.id,
+  thread_state.ts,
+  thread_state.dur,
+  thread_state.state,
+  thread_state.utid,
+  thread_state.waker_utid
+FROM thread_state
+WHERE thread_state.dur != -1 AND thread_state.waker_utid IS NOT NULL
+   AND thread_state.irq_context = 0 OR thread_state.irq_context IS NULL;
+
+-- Similar to |internal_runnable_state| but finds the runnable states at thread fork.
+CREATE VIEW internal_fork_runnable_state
+AS
+SELECT
+  thread_state.id,
+  thread_state.ts,
+  thread_state.dur,
+  thread_state.state,
+  thread_state.utid,
+  thread_state.waker_utid,
+  thread.start_ts = thread_state.ts AS is_fork
+FROM thread_state
+JOIN thread USING(utid)
+WHERE thread_state.dur != -1 AND thread_state.waker_utid IS NOT NULL
+   AND thread.start_ts = thread_state.ts;
+
+--
+-- Finds all sleep states including interruptible (S) and uninterruptible (D).
+CREATE VIEW internal_sleep_state
+AS
+SELECT
+  thread_state.id,
+  thread_state.ts,
+  thread_state.dur,
+  thread_state.state,
+  thread_state.blocked_function,
+  thread_state.utid
+FROM thread_state
+WHERE dur != -1 AND (state = 'S' OR state = 'D');
+
+--
+-- Finds the last execution for every thread to end executing_spans without a Sleep.
+--
+CREATE VIEW internal_thread_end_ts
+AS
+SELECT
+  MAX(ts) + dur AS end_ts,
+  utid
+FROM thread_state
+WHERE dur != -1
+GROUP BY utid;
+
+-- Similar to |internal_sleep_state| but finds the first sleep state after thread fork.
+CREATE VIEW internal_fork_sleep_state
+AS
+SELECT
+  MIN(thread_state.id) AS id,
+  thread_state.ts,
+  thread_state.dur,
+  thread_state.state,
+  thread_state.blocked_function,
+  thread_state.utid
+FROM thread_state
+WHERE dur != -1 AND (state = 'S' OR state = 'D')
+GROUP BY utid;
+
+--
+-- Finds all neighbouring ('Sleeping', 'Runnable') thread_states pairs from the same thread.
+-- More succintly, pairs of S[n-1]-R[n] where R is woken by a process context and S is an
+-- interruptible or uninterruptible sleep state.
+--
+-- This is achieved by joining the |internal_runnable_state|.ts with the
+-- |internal_sleep_state|.|ts + dur|.
+--
+-- With the S-R pairs of a thread, we can re-align to [R-S) intervals with LEADS and LAGS.
+--
+-- Given the following thread_states on a thread:
+-- S0__|R0__Running0___|S1__|R1__Running1___|S2__|R2__Running2__S2|.
+--
+-- We have 3 thread_executing_spans: [R0, S0), [R1, S1), [R2, S2).
+--
+-- We define the following markers in this table:
+--
+-- prev_start_id    = R0_id.
+-- prev_start_ts    = R0_ts.
+-- prev_start_dur   = R0_dur.
+-- prev_start_state = 'R'.
+--
+-- prev_end_id      = S0_id.
+-- prev_end_ts      = S0_ts.
+-- prev_end_dur     = S0_dur.
+-- prev_end_state   = 'S' or 'D'.
+--
+-- start_id         = R1_id.
+-- start_ts         = R1_ts.
+-- start_dur        = R1_dur.
+-- start_state      = 'R'.
+--
+-- end_id           = S1_id.
+-- end_ts           = S1_ts.
+-- end_dur          = S1_dur.
+-- end_state        = 'S' or 'D'.
+CREATE TABLE internal_wakeup
+AS
+  SELECT
+  LAG(r.id, 1) OVER (PARTITION BY r.utid ORDER BY r.ts) AS prev_start_id,
+  LAG(r.ts, 1) OVER (PARTITION BY r.utid ORDER BY r.ts) AS prev_start_ts,
+  LAG(r.dur, 1) OVER (PARTITION BY r.utid ORDER BY r.ts) AS prev_start_dur,
+  LAG(r.state, 1) OVER (PARTITION BY r.utid ORDER BY r.ts) AS prev_start_state,
+  s.id AS prev_end_id,
+  s.ts AS prev_end_ts,
+  s.dur AS prev_end_dur,
+  s.state AS prev_end_state,
+  s.blocked_function AS prev_blocked_function,
+  r.id AS start_id,
+  r.ts AS start_ts,
+  r.dur AS start_dur,
+  r.state AS start_state,
+  r.utid AS utid,
+  r.waker_utid,
+  0 AS is_fork,
+  LEAD(s.id, 1) OVER (PARTITION BY r.utid ORDER BY r.ts) AS end_id,
+  IFNULL(LEAD(s.ts, 1) OVER (PARTITION BY r.utid ORDER BY r.ts), thread_end.end_ts)  AS end_ts,
+  LEAD(s.dur, 1) OVER (PARTITION BY r.utid ORDER BY r.ts) AS end_dur,
+  LEAD(s.state, 1) OVER (PARTITION BY r.utid ORDER BY r.ts) AS end_state,
+  LEAD(s.blocked_function, 1) OVER (PARTITION BY r.utid ORDER BY r.ts) AS blocked_function
+FROM internal_runnable_state r
+JOIN internal_sleep_state s
+  ON s.utid = r.utid AND (s.ts + s.dur = r.ts)
+LEFT JOIN internal_thread_end_ts thread_end USING(utid)
+UNION ALL
+  SELECT
+  NULL AS prev_start_id,
+  NULL AS prev_start_ts,
+  NULL AS prev_start_dur,
+  NULL AS prev_start_state,
+  NULL AS prev_end_id,
+  NULL AS prev_end_ts,
+  NULL AS prev_end_dur,
+  NULL AS prev_end_state,
+  NULL AS prev_blocked_function,
+  r.id AS start_id,
+  r.ts AS start_ts,
+  r.dur AS start_dur,
+  r.state AS start_state,
+  r.utid AS utid,
+  r.waker_utid,
+  r.is_fork,
+  s.id AS end_id,
+  s.ts AS end_ts,
+  s.dur AS end_dur,
+  s.state AS end_state,
+  s.blocked_function AS blocked_function
+FROM internal_fork_runnable_state r
+JOIN internal_fork_sleep_state s
+  ON s.utid = r.utid;
+
+-- Improves performance of |internal_wakeup_chain| computation.
+CREATE
+  INDEX internal_wakeup_idx
+ON internal_wakeup(waker_utid, start_ts);
+
+--
+-- Builds the parent-child chain from all thread_executing_spans. The parent is the waker and
+-- child is the wakee.
+--
+-- Note that this doesn't include the roots. We'll compute the roots below.
+-- This two step process improves performance because it's more efficient to scan
+-- parent and find a child between than to scan child and find the parent it lies between.
+CREATE VIEW internal_wakeup_chain
+AS
+SELECT parent.start_id AS parent_id, child.*
+FROM internal_wakeup parent
+JOIN internal_wakeup child
+  ON (
+    parent.utid = child.waker_utid
+    AND child.start_ts BETWEEN parent.start_ts AND parent.end_ts);
+
+--
+-- Finds the roots of the |internal_wakeup_chain|.
+CREATE VIEW internal_wakeup_root
+AS
+WITH
+  internal_wakeup_root_id AS (
+    SELECT DISTINCT parent_id AS id FROM internal_wakeup_chain
+    EXCEPT
+    SELECT DISTINCT start_id AS id FROM internal_wakeup_chain
+  )
+SELECT NULL AS parent_id, internal_wakeup.*
+FROM internal_wakeup
+JOIN internal_wakeup_root_id
+  ON internal_wakeup_root_id.id = internal_wakeup.start_id;
+
+CREATE TABLE internal_wakeup_leaf AS
+WITH
+  internal_wakeup_leaf_id AS (
+    SELECT DISTINCT start_id AS id FROM internal_wakeup_chain
+    EXCEPT
+    SELECT DISTINCT parent_id AS id FROM internal_wakeup_chain
+  )
+SELECT internal_wakeup_chain.*
+FROM internal_wakeup_chain
+JOIN internal_wakeup_leaf_id
+  ON internal_wakeup_leaf_id.id = internal_wakeup_chain.start_id;
+
+--
+-- Merges the roots and the rest of the chain.
+CREATE TABLE internal_wakeup_graph
+AS
+SELECT internal_wakeup_chain.*, 0 AS is_root, (internal_wakeup_leaf.start_id IS NOT NULL) AS is_leaf
+FROM internal_wakeup_chain
+LEFT JOIN internal_wakeup_leaf
+  USING (start_id)
+UNION ALL
+SELECT *, 1 AS is_root, 0 AS is_leaf FROM internal_wakeup_root;
+
+-- thread_executing_span graph of all wakeups across all processes.
+--
+-- @column parent_id          Id of thread_executing_span that directly woke |id|
+-- @column id                 Id of the first (runnable) thread state in thread_executing_span.
+-- @column ts                 Timestamp of first thread_state in thread_executing_span.
+-- @column dur                Duration of thread_executing_span.
+-- @column tid                Tid of thread with thread_state.
+-- @column pid                Pid of process with thread_state.
+-- @column utid               Utid of thread with thread_state.
+-- @column upid               Upid of process with thread_state.
+-- @column thread_name        Name of thread with thread_state.
+-- @column process_name       Name of process with thread_state.
+-- @column waker_tid          Tid of thread that woke the first thread_state in thread_executing_span.
+-- @column waker_pid          Pid of process that woke the first thread_state in thread_executing_span.
+-- @column waker_utid         Utid of thread that woke the first thread_state in thread_executing_span.
+-- @column waker_upid         Upid of process that woke the first thread_state in thread_executing_span.
+-- @column waker_thread_name  Name of thread that woke the first thread_state in thread_executing_span.
+-- @column waker_process_name Name of process that woke the first thread_state in thread_executing_span.
+-- @column blocked_dur        Duration of blocking thread state before waking up.
+-- @column blocked_state      Thread state ('D' or 'S') of blocked thread_state before waking up.
+-- @column blocked_function   Kernel blocking function of thread state before waking up.
+CREATE TABLE experimental_thread_executing_span_graph
+AS
+SELECT
+  graph.parent_id,
+  graph.start_id AS id,
+  graph.start_ts AS ts,
+  graph.end_ts - graph.start_ts AS dur,
+  thread.tid,
+  process.pid,
+  graph.utid,
+  process.upid,
+  thread.name AS thread_name,
+  process.name AS process_name,
+  waker_thread.tid AS waker_tid,
+  waker_process.pid AS waker_pid,
+  graph.waker_utid,
+  waker_process.upid AS waker_upid,
+  waker_thread.name AS waker_thread_name,
+  waker_process.name AS waker_process_name,
+  graph.prev_end_dur AS blocked_dur,
+  graph.prev_end_state AS blocked_state,
+  graph.prev_blocked_function AS blocked_function,
+  graph.is_root,
+  graph.is_leaf
+FROM internal_wakeup_graph graph
+JOIN thread
+  ON thread.utid = graph.utid
+LEFT JOIN process
+  ON process.upid = thread.upid
+LEFT JOIN thread waker_thread
+  ON waker_thread.utid = graph.waker_utid
+LEFT JOIN process waker_process
+  ON waker_process.upid = waker_thread.upid;
+
+CREATE
+  INDEX experimental_thread_executing_span_graph_id_idx
+  ON experimental_thread_executing_span_graph(id);
+
+CREATE
+  INDEX experimental_thread_executing_span_graph_parent_id_idx
+ON experimental_thread_executing_span_graph(parent_id);
+
+
+-- All thread_executing_spans that were recursively woken by |root_id|, all thread_executing_spans
+-- in trace.
+-- if root_id IS NULL, empty results if no matching thread_state id found.
+--
+-- @arg root_id INT           Thread state id to start recursion
+--
+-- @column parent_id          Id of thread_executing_span that directly woke |start_id|
+-- @column id                 Id of the first (runnable) thread state in thread_executing_span.
+-- @column ts                 Timestamp of first thread_state in thread_executing_span.
+-- @column dur                Duration of thread_executing_span.
+-- @column tid                Tid of thread with thread_state.
+-- @column pid                Pid of process with thread_state.
+-- @column utid               Utid of thread with thread_state.
+-- @column upid               Upid of process with thread_state.
+-- @column thread_name        Name of thread with thread_state.
+-- @column process_name       Name of process with thread_state.
+-- @column waker_tid          Tid of thread that woke the first thread_state in thread_executing_span.
+-- @column waker_pid          Pid of process that woke the first thread_state in thread_executing_span.
+-- @column waker_utid         Utid of thread that woke the first thread_state in thread_executing_span.
+-- @column waker_upid         Upid of process that woke the first thread_state in thread_executing_span.
+-- @column waker_thread_name  Name of thread that woke the first thread_state in thread_executing_span.
+-- @column waker_process_name Name of process that woke the first thread_state in thread_executing_span.
+-- @column blocked_dur        Duration of blocking thread state before waking up.
+-- @column blocked_state      Thread state ('D' or 'S') of blocked thread_state before waking up.
+-- @column blocked_function   Kernel blocking function of thread state before waking up.
+-- @column depth              Tree depth from |root_id|
+-- @column root_id            Thread state id used to start the recursion. Helpful for SQL JOINs
+SELECT CREATE_VIEW_FUNCTION(
+'EXPERIMENTAL_THREAD_EXECUTING_SPAN_DESCENDANTS(root_id INT)',
+'
+  parent_id LONG,
+  id LONG,
+  ts LONG,
+  dur LONG,
+  tid INT,
+  pid INT,
+  utid INT,
+  upid INT,
+  thread_name STRING,
+  process_name STRING,
+  waker_tid INT,
+  waker_pid INT,
+  waker_utid INT,
+  waker_upid INT,
+  waker_thread_name STRING,
+  waker_process_name STRING,
+  blocked_dur LONG,
+  blocked_state STRING,
+  blocked_function STRING,
+  is_root INT,
+  is_leaf INT,
+  depth INT,
+  root_id INT
+',
+'
+WITH chain AS (
+  SELECT
+    *,
+    0 AS depth,
+    id AS root_id
+  FROM experimental_thread_executing_span_graph
+  WHERE ($root_id IS NOT NULL AND id = $root_id) OR ($root_id IS NULL AND is_root)
+  UNION ALL
+  SELECT
+    graph.*,
+    chain.depth + 1 AS depth,
+    chain.root_id
+  FROM experimental_thread_executing_span_graph graph
+  JOIN chain ON chain.id = graph.parent_id
+)
+SELECT * FROM chain
+');
+
+-- All thread_executing_spans that are ancestors of |leaf_id|
+--
+-- @arg leaf_id INT           Thread state id to start recursion
+--
+-- @column parent_id          Id of thread_executing_span that directly woke |id|
+-- @column id                 Id of the first (runnable) thread state in thread_executing_span.
+-- @column ts                 Timestamp of first thread_state in thread_executing_span.
+-- @column dur                Duration of thread_executing_span.
+-- @column tid                Tid of thread with thread_state.
+-- @column pid                Pid of process with thread_state.
+-- @column utid               Utid of thread with thread_state.
+-- @column upid               Upid of process with thread_state.
+-- @column thread_name        Name of thread with thread_state.
+-- @column process_name       Name of process with thread_state.
+-- @column waker_tid          Tid of thread that woke the first thread_state in thread_executing_span.
+-- @column waker_pid          Pid of process that woke the first thread_state in thread_executing_span.
+-- @column waker_utid         Utid of thread that woke the first thread_state in thread_executing_span.
+-- @column waker_upid         Upid of process that woke the first thread_state in thread_executing_span.
+-- @column waker_thread_name  Name of thread that woke the first thread_state in thread_executing_span.
+-- @column waker_process_name Name of process that woke the first thread_state in thread_executing_span.
+-- @column blocked_dur        Duration of blocking thread state before waking up.
+-- @column blocked_state      Thread state ('D' or 'S') of blocked thread_state before waking up.
+-- @column blocked_function   Kernel blocking function of thread state before waking up.
+-- @column height             Tree height from |leaf_id|
+-- @column leaf_id            Thread state id used to start the recursion. Helpful for SQL JOINs
+SELECT CREATE_VIEW_FUNCTION(
+'EXPERIMENTAL_THREAD_EXECUTING_SPAN_ANCESTORS(leaf_id INT)',
+'
+  parent_id LONG,
+  id LONG,
+  ts LONG,
+  dur LONG,
+  tid INT,
+  pid INT,
+  utid INT,
+  upid INT,
+  thread_name STRING,
+  process_name STRING,
+  waker_tid INT,
+  waker_pid INT,
+  waker_utid INT,
+  waker_upid INT,
+  waker_thread_name STRING,
+  waker_process_name STRING,
+  blocked_dur LONG,
+  blocked_state STRING,
+  blocked_function STRING,
+  is_root INT,
+  is_leaf INT,
+  height INT,
+  leaf_id INT
+',
+'
+WITH
+chain AS (
+  SELECT
+    *,
+    0 AS height,
+    id AS leaf_id,
+    ts AS leaf_ts,
+    blocked_dur AS leaf_blocked_dur,
+    blocked_function AS leaf_blocked_function
+  FROM experimental_thread_executing_span_graph
+  WHERE ($leaf_id IS NOT NULL AND id = $leaf_id) OR ($leaf_id IS NULL AND is_leaf)
+  UNION ALL
+  SELECT
+    graph.*,
+    chain.height + 1 AS height,
+    chain.leaf_id,
+    chain.leaf_ts,
+    chain.leaf_blocked_dur,
+    chain.leaf_blocked_function
+  FROM experimental_thread_executing_span_graph graph
+  JOIN chain ON chain.parent_id = graph.id AND chain.ts >= (leaf_ts - leaf_blocked_dur)
+) SELECT
+    parent_id,
+    id,
+    ts,
+    dur,
+    tid,
+    pid,
+    utid,
+    upid,
+    thread_name,
+    process_name,
+    waker_tid,
+    waker_pid,
+    waker_utid,
+    waker_upid,
+    waker_thread_name,
+    waker_process_name,
+    blocked_dur,
+    blocked_state,
+    blocked_function,
+    is_root,
+    is_leaf,
+    height,
+    leaf_id
+  FROM chain;
+');
+
+-- Gets the thread_executing_span id a thread_state belongs to. Returns NULL if thread state is
+-- sleeping and not blocked on an interrupt.
+--
+-- @arg thread_state_id INT   Id of the thread_state to get the thread_executing_span id for
+-- @ret INT                   thread_executing_span id
+SELECT
+  CREATE_FUNCTION(
+'EXPERIMENTAL_THREAD_EXECUTING_SPAN_ID_FROM_THREAD_STATE_ID(thread_state_id INT)',
+'INT',
+'
+WITH
+  t AS (
+  SELECT
+    ts,
+    utid
+  FROM thread_state
+  WHERE
+    id = $thread_state_id
+  )
+  SELECT
+    MAX(start_id) AS thread_executing_span_id
+  FROM internal_wakeup w, t
+  WHERE t.utid = w.utid AND t.ts >= w.start_ts AND t.ts < w.end_ts;
+');
diff --git a/test/trace_processor/diff_tests/tables/tests_sched.py b/test/trace_processor/diff_tests/tables/tests_sched.py
index 3ef8d14..3314008 100644
--- a/test/trace_processor/diff_tests/tables/tests_sched.py
+++ b/test/trace_processor/diff_tests/tables/tests_sched.py
@@ -120,3 +120,233 @@
         19,"ftrace_event",1735490039439,"sched_waking",1,298,18,1
         20,"ftrace_event",1735490042084,"sched_waking",1,298,19,1
         """))
+
+  def test_thread_executing_span_graph(self):
+    return DiffTestBlueprint(
+        trace=DataPath('sched_wakeup_trace.atr'),
+        query="""
+        SELECT IMPORT('experimental.thread_executing_span');
+        SELECT
+          ts,
+          dur,
+          tid,
+          pid,
+          thread_name,
+          process_name,
+          waker_thread_name,
+          waker_process_name,
+          blocked_dur,
+          blocked_state,
+          blocked_function
+        FROM experimental_thread_executing_span_graph
+          WHERE blocked_function IS NOT NULL
+        ORDER BY ts, tid
+        LIMIT 10
+        """,
+        out=Csv("""
+        "ts","dur","tid","pid","thread_name","process_name","waker_thread_name","waker_process_name","blocked_dur","blocked_state","blocked_function"
+        1736413946850,576475,527,527,"adbd","/apex/com.android.adbd/bin/adbd","shell svc 3476","/apex/com.android.adbd/bin/adbd",507,"D","__down_read_common"
+        1737047193524,1070032,3482,3482,"cmd","cmd","binder:3482_2","cmd",3892,"D","rwsem_down_write_slowpath"
+        1737107227334,13924,15,15,"rcub/0","rcub/0","ActivityManager","system_server",10790,"D","rcu_boost_kthread"
+        1737107244629,14884,17,17,"rcu_exp_gp_kthr","rcu_exp_gp_kthr","system_server","system_server",41867,"D","rcu_exp_sel_wait_wake"
+        1737107251086,7335,15,15,"rcub/0","rcub/0","system_server","system_server",9828,"D","rcu_boost_kthread"
+        1737107254718,3140060,1821,1800,"binder:1800_1","com.android.music","rcu_exp_gp_kthr","rcu_exp_gp_kthr",75180,"D","synchronize_rcu_expedited"
+        1737114706120,6053987,1801,1789,"binder:1789_1","com.android.provision","Jit thread pool","com.android.providers.media.module",5495734,"D","rwsem_down_write_slowpath"
+        1737116846911,95462739,2125,2110,"Profile Saver","com.android.externalstorage","binder:1789_1","com.android.provision",2664091,"D","rwsem_down_write_slowpath"
+        1737120785844,15257,15,15,"rcub/0","rcub/0","ActivityManager","system_server",9143,"D","rcu_boost_kthread"
+        1737120805447,16572,17,17,"rcu_exp_gp_kthr","rcu_exp_gp_kthr","android.bg","system_server",47725,"D","rcu_exp_sel_wait_wake"
+        """))
+
+  def test_thread_executing_span_graph_contains_forked_states(self):
+    return DiffTestBlueprint(
+        trace=DataPath('sched_wakeup_trace.atr'),
+        query="""
+        SELECT IMPORT('experimental.thread_executing_span');
+        SELECT
+          ts,
+          dur,
+          tid,
+          pid,
+          thread_name,
+          process_name,
+          waker_thread_name,
+          waker_process_name,
+          blocked_dur,
+          blocked_state,
+          blocked_function
+        FROM experimental_thread_executing_span_graph
+          WHERE id = 348
+        """,
+        out=Csv("""
+        "ts","dur","tid","pid","thread_name","process_name","waker_thread_name","waker_process_name","blocked_dur","blocked_state","blocked_function"
+        1735842081507,293868,3475,527,"shell svc 3474","/apex/com.android.adbd/bin/adbd","adbd","/apex/com.android.adbd/bin/adbd","[NULL]","[NULL]","[NULL]"
+        """))
+
+  def test_thread_executing_span_graph_has_no_null_dur(self):
+    return DiffTestBlueprint(
+        trace=DataPath('sched_wakeup_trace.atr'),
+        query="""
+        SELECT IMPORT('experimental.thread_executing_span');
+        SELECT ts,dur FROM experimental_thread_executing_span_graph
+          WHERE dur IS NULL OR ts IS NULL
+        """,
+        out=Csv("""
+        "ts","dur"
+        """))
+
+  def test_thread_executing_span_graph_accepts_null_irq_context(self):
+    return DiffTestBlueprint(
+        trace=DataPath('sched_switch_original.pb'),
+        query="""
+        SELECT IMPORT('experimental.thread_executing_span');
+        SELECT COUNT(*) AS count FROM experimental_thread_executing_span_graph
+        """,
+        out=Csv("""
+        "count"
+        9
+        """))
+
+  def test_thread_executing_span_descendants_null(self):
+    return DiffTestBlueprint(
+        trace=DataPath('sched_wakeup_trace.atr'),
+        query="""
+        SELECT IMPORT('experimental.thread_executing_span');
+        SELECT
+          ts,
+          dur,
+          tid,
+          pid,
+          thread_name,
+          process_name,
+          waker_thread_name,
+          waker_process_name,
+          blocked_dur,
+          blocked_state,
+          blocked_function,
+          depth,
+          is_root
+        FROM EXPERIMENTAL_THREAD_EXECUTING_SPAN_DESCENDANTS(NULL)
+        ORDER BY depth DESC, ts, tid
+        LIMIT 10
+        """,
+        out=Csv("""
+        "ts","dur","tid","pid","thread_name","process_name","waker_thread_name","waker_process_name","blocked_dur","blocked_state","blocked_function","depth","is_root"
+        1740321632480,20897,404,398,"binder:398_2","/apex/com.android.os.statsd/bin/statsd","statsd.writer","/apex/com.android.os.statsd/bin/statsd",64173354,"S","[NULL]",324,0
+        1740470009095,113509,3494,3487,"HeapTaskDaemon","com.android.providers.media.module","AsyncTask #1","com.android.providers.media.module",1204928,"S","[NULL]",324,0
+        1740470126280,60885652,3494,3487,"HeapTaskDaemon","com.android.providers.media.module","AsyncTask #1","com.android.providers.media.module",3676,"S","[NULL]",324,0
+        1740321596028,46679,633,398,"statsd.writer","/apex/com.android.os.statsd/bin/statsd","mediametrics","media.metrics",64143546,"S","[NULL]",323,0
+        1740468702535,1449612,3548,3487,"AsyncTask #1","com.android.providers.media.module","HeapTaskDaemon","com.android.providers.media.module",1003391,"S","[NULL]",323,0
+        1740321315576,62532,2161,553,"binder:553_7","/system/bin/mediaserver","binder:551_4","media.extractor",63953635,"S","[NULL]",322,0
+        1740321344727,346525,552,552,"mediametrics","media.metrics","binder:551_4","media.extractor",63860347,"S","[NULL]",322,0
+        1740419776108,13020460,3494,3487,"HeapTaskDaemon","com.android.providers.media.module","AsyncTask #1","com.android.providers.media.module",597159,"S","[NULL]",322,0
+        1740428968606,362233,3515,3487,"ackgroundThread","com.android.providers.media.module","AsyncTask #1","com.android.providers.media.module",9601023,"S","[NULL]",322,0
+        1740432834772,3770512,3494,3487,"HeapTaskDaemon","com.android.providers.media.module","AsyncTask #1","com.android.providers.media.module",38204,"S","[NULL]",322,0
+        """))
+
+  def test_thread_executing_span_ancestors_null(self):
+    return DiffTestBlueprint(
+        trace=DataPath('sched_wakeup_trace.atr'),
+        query="""
+        SELECT IMPORT('experimental.thread_executing_span');
+        SELECT
+          ts,
+          dur,
+          tid,
+          pid,
+          thread_name,
+          process_name,
+          waker_thread_name,
+          waker_process_name,
+          blocked_dur,
+          blocked_state,
+          blocked_function,
+          height,
+          is_leaf
+        FROM EXPERIMENTAL_THREAD_EXECUTING_SPAN_ANCESTORS(NULL)
+        ORDER BY height DESC, ts, tid
+        LIMIT 10
+        """,
+        out=Csv("""
+        "ts","dur","tid","pid","thread_name","process_name","waker_thread_name","waker_process_name","blocked_dur","blocked_state","blocked_function","height","is_leaf"
+        1740252621947,35807217,3548,3487,"AsyncTask #1","com.android.providers.media.module","binder:553_7","/system/bin/mediaserver",16688,"S","[NULL]",153,0
+        1740287823247,9522163,282,282,"f2fs_ckpt-254:5","f2fs_ckpt-254:5","AsyncTask #1","com.android.providers.media.module",332115250,"S","[NULL]",152,0
+        1740252621947,35807217,3548,3487,"AsyncTask #1","com.android.providers.media.module","binder:553_7","/system/bin/mediaserver",16688,"S","[NULL]",151,0
+        1740297282282,13298848,3548,3487,"AsyncTask #1","com.android.providers.media.module","f2fs_ckpt-254:5","f2fs_ckpt-254:5",8853118,"D","f2fs_issue_checkpoint",151,0
+        1740287823247,9522163,282,282,"f2fs_ckpt-254:5","f2fs_ckpt-254:5","AsyncTask #1","com.android.providers.media.module",332115250,"S","[NULL]",150,0
+        1740310563850,141705,2134,553,"binder:553_3","/system/bin/mediaserver","AsyncTask #1","com.android.providers.media.module",53196161,"S","[NULL]",150,0
+        1740297282282,13298848,3548,3487,"AsyncTask #1","com.android.providers.media.module","f2fs_ckpt-254:5","f2fs_ckpt-254:5",8853118,"D","f2fs_issue_checkpoint",149,0
+        1740310673843,167265,3548,3487,"AsyncTask #1","com.android.providers.media.module","binder:553_3","/system/bin/mediaserver",92713,"S","[NULL]",149,0
+        1740310563850,141705,2134,553,"binder:553_3","/system/bin/mediaserver","AsyncTask #1","com.android.providers.media.module",53196161,"S","[NULL]",148,0
+        1740310830675,146216,2134,553,"binder:553_3","/system/bin/mediaserver","AsyncTask #1","com.android.providers.media.module",125120,"S","[NULL]",148,0
+        """))
+
+  def test_thread_executing_span_descendants_id(self):
+    return DiffTestBlueprint(
+        trace=DataPath('sched_wakeup_trace.atr'),
+        query="""
+        SELECT IMPORT('experimental.thread_executing_span');
+        SELECT
+          thread_name,
+          waker_thread_name,
+          depth,
+          is_root,
+          COUNT(thread_name) AS count
+        FROM EXPERIMENTAL_THREAD_EXECUTING_SPAN_DESCENDANTS(10834)
+        GROUP BY 1,2,3,4
+        ORDER BY depth
+        """,
+        out=Csv("""
+        "thread_name","waker_thread_name","depth","is_root","count"
+        "android.hardwar","android.bg",0,0,1
+        "android.bg","android.hardwar",1,0,1
+        "android.hardwar","android.bg",2,0,1
+        "android.bg","android.hardwar",3,0,1
+        "logd.writer","android.bg",4,0,1
+        "statsd.writer","android.bg",4,0,4
+        "system_server","android.bg",4,0,32
+        "binder:398_2","statsd.writer",5,0,3
+        "logd.reader.per","logd.writer",5,0,1
+        "logcat","logd.reader.per",6,0,1
+        """))
+
+  def test_thread_executing_span_ancestors_id(self):
+    return DiffTestBlueprint(
+        trace=DataPath('sched_wakeup_trace.atr'),
+        query="""
+        SELECT IMPORT('experimental.thread_executing_span');
+        SELECT
+          thread_name,
+          waker_thread_name,
+          height,
+          is_leaf
+        FROM EXPERIMENTAL_THREAD_EXECUTING_SPAN_ANCESTORS(10840) ORDER BY height
+        """,
+        out=Csv("""
+        "thread_name","waker_thread_name","height","is_leaf"
+        "android.hardwar","android.bg",0,0
+        "android.bg","android.hardwar",1,0
+        """))
+
+  def test_thread_executing_span_from_non_sleep_thread_state(self):
+    return DiffTestBlueprint(
+        trace=DataPath('sched_wakeup_trace.atr'),
+        query="""
+        SELECT IMPORT('experimental.thread_executing_span');
+        SELECT EXPERIMENTAL_THREAD_EXECUTING_SPAN_ID_FROM_THREAD_STATE_ID(11933) AS thread_executing_span_id
+        """,
+        out=Csv("""
+        "thread_executing_span_id"
+        11888
+        """))
+
+  def test_thread_executing_span_from_sleep_thread_state(self):
+    return DiffTestBlueprint(
+        trace=DataPath('sched_wakeup_trace.atr'),
+        query="""
+        SELECT IMPORT('experimental.thread_executing_span');
+        SELECT EXPERIMENTAL_THREAD_EXECUTING_SPAN_ID_FROM_THREAD_STATE_ID(11845) AS thread_executing_span_id
+        """,
+        out=Csv("""
+        "thread_executing_span_id"
+        "[NULL]"
+        """))
diff --git a/ui/src/assets/common.scss b/ui/src/assets/common.scss
index c26ee4f..a83a9f6 100644
--- a/ui/src/assets/common.scss
+++ b/ui/src/assets/common.scss
@@ -457,7 +457,7 @@
 .scrolling-panel-container {
   position: relative;
   overflow-x: hidden;
-  overflow-y: scroll; // Always show vertical scrollbar
+  overflow-y: auto;
   flex: 1 1 auto;
   will-change: transform; // Force layer creation.
   display: grid;
diff --git a/ui/src/base/math_utils.ts b/ui/src/base/math_utils.ts
new file mode 100644
index 0000000..b86f231
--- /dev/null
+++ b/ui/src/base/math_utils.ts
@@ -0,0 +1,18 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Restrict the value of a number between two values (inclusive)
+export function clamp(val: number, lower: number, upper: number): number {
+  return Math.max(lower, Math.min(upper, val));
+}
diff --git a/ui/src/controller/flow_events_controller.ts b/ui/src/controller/flow_events_controller.ts
index d92090d..efbae6c 100644
--- a/ui/src/controller/flow_events_controller.ts
+++ b/ui/src/controller/flow_events_controller.ts
@@ -41,7 +41,6 @@
   defaultValue: false,
 });
 
-
 export class FlowEventsController extends Controller<'main'> {
   private lastSelectedSliceId?: number;
   private lastSelectedArea?: Area;
@@ -76,109 +75,234 @@
     );`);
   }
 
-  queryFlowEvents(query: string, callback: (flows: Flow[]) => void) {
-    this.args.engine.query(query).then((result) => {
-      const flows: Flow[] = [];
-      const it = result.iter({
-        beginSliceId: NUM,
-        beginTrackId: NUM,
-        beginSliceName: STR_NULL,
-        beginSliceChromeCustomName: STR_NULL,
-        beginSliceCategory: STR_NULL,
-        beginSliceStartTs: LONG,
-        beginSliceEndTs: LONG,
-        beginDepth: NUM,
-        beginThreadName: STR_NULL,
-        beginProcessName: STR_NULL,
-        endSliceId: NUM,
-        endTrackId: NUM,
-        endSliceName: STR_NULL,
-        endSliceChromeCustomName: STR_NULL,
-        endSliceCategory: STR_NULL,
-        endSliceStartTs: LONG,
-        endSliceEndTs: LONG,
-        endDepth: NUM,
-        endThreadName: STR_NULL,
-        endProcessName: STR_NULL,
-        name: STR_NULL,
-        category: STR_NULL,
-        id: NUM,
-      });
-      for (; it.valid(); it.next()) {
-        const beginSliceId = it.beginSliceId;
-        const beginTrackId = it.beginTrackId;
-        const beginSliceName =
-            it.beginSliceName === null ? 'NULL' : it.beginSliceName;
-        const beginSliceChromeCustomName =
-            it.beginSliceChromeCustomName === null ?
-            undefined :
-            it.beginSliceChromeCustomName;
-        const beginSliceCategory =
-            it.beginSliceCategory === null ? 'NULL' : it.beginSliceCategory;
-        const beginSliceStartTs = it.beginSliceStartTs;
-        const beginSliceEndTs = it.beginSliceEndTs;
-        const beginDepth = it.beginDepth;
-        const beginThreadName =
-            it.beginThreadName === null ? 'NULL' : it.beginThreadName;
-        const beginProcessName =
-            it.beginProcessName === null ? 'NULL' : it.beginProcessName;
+  async queryFlowEvents(query: string, callback: (flows: Flow[]) => void) {
+    const result = await this.args.engine.query(query);
+    const flows: Flow[] = [];
 
-        const endSliceId = it.endSliceId;
-        const endTrackId = it.endTrackId;
-        const endSliceName =
-            it.endSliceName === null ? 'NULL' : it.endSliceName;
-        const endSliceChromeCustomName = it.endSliceChromeCustomName === null ?
-            undefined :
-            it.endSliceChromeCustomName;
-        const endSliceCategory =
-            it.endSliceCategory === null ? 'NULL' : it.endSliceCategory;
-        const endSliceStartTs = it.endSliceStartTs;
-        const endSliceEndTs = it.endSliceEndTs;
-        const endDepth = it.endDepth;
-        const endThreadName =
-            it.endThreadName === null ? 'NULL' : it.endThreadName;
-        const endProcessName =
-            it.endProcessName === null ? 'NULL' : it.endProcessName;
-
-        // Category and name present only in version 1 flow events
-        // It is most likelly NULL for all other versions
-        const category = it.category === null ? undefined : it.category;
-        const name = it.name === null ? undefined : it.name;
-        const id = it.id;
-
-        flows.push({
-          id,
-          begin: {
-            trackId: beginTrackId,
-            sliceId: beginSliceId,
-            sliceName: beginSliceName,
-            sliceChromeCustomName: beginSliceChromeCustomName,
-            sliceCategory: beginSliceCategory,
-            sliceStartTs: beginSliceStartTs,
-            sliceEndTs: beginSliceEndTs,
-            depth: beginDepth,
-            threadName: beginThreadName,
-            processName: beginProcessName,
-          },
-          end: {
-            trackId: endTrackId,
-            sliceId: endSliceId,
-            sliceName: endSliceName,
-            sliceChromeCustomName: endSliceChromeCustomName,
-            sliceCategory: endSliceCategory,
-            sliceStartTs: endSliceStartTs,
-            sliceEndTs: endSliceEndTs,
-            depth: endDepth,
-            threadName: endThreadName,
-            processName: endProcessName,
-          },
-          dur: endSliceStartTs - beginSliceEndTs,
-          category,
-          name,
-        });
-      }
-      callback(flows);
+    const it = result.iter({
+      beginSliceId: NUM,
+      beginTrackId: NUM,
+      beginSliceName: STR_NULL,
+      beginSliceChromeCustomName: STR_NULL,
+      beginSliceCategory: STR_NULL,
+      beginSliceStartTs: LONG,
+      beginSliceEndTs: LONG,
+      beginDepth: NUM,
+      beginThreadName: STR_NULL,
+      beginProcessName: STR_NULL,
+      endSliceId: NUM,
+      endTrackId: NUM,
+      endSliceName: STR_NULL,
+      endSliceChromeCustomName: STR_NULL,
+      endSliceCategory: STR_NULL,
+      endSliceStartTs: LONG,
+      endSliceEndTs: LONG,
+      endDepth: NUM,
+      endThreadName: STR_NULL,
+      endProcessName: STR_NULL,
+      name: STR_NULL,
+      category: STR_NULL,
+      id: NUM,
     });
+
+    const nullToStr = (s: null|string): string => {
+      return s === null ? 'NULL' : s;
+    };
+
+    const nullToUndefined = (s: null|string): undefined|string => {
+      return s === null ? undefined : s;
+    };
+
+    const nodes = [];
+
+    for (; it.valid(); it.next()) {
+      // Category and name present only in version 1 flow events
+      // It is most likelly NULL for all other versions
+      const category = nullToUndefined(it.category);
+      const name = nullToUndefined(it.name);
+      const id = it.id;
+
+      const begin = {
+        trackId: it.beginTrackId,
+        sliceId: it.beginSliceId,
+        sliceName: nullToStr(it.beginSliceName),
+        sliceChromeCustomName: nullToUndefined(it.beginSliceChromeCustomName),
+        sliceCategory: nullToStr(it.beginSliceCategory),
+        sliceStartTs: it.beginSliceStartTs,
+        sliceEndTs: it.beginSliceEndTs,
+        depth: it.beginDepth,
+        threadName: nullToStr(it.beginThreadName),
+        processName: nullToStr(it.beginProcessName),
+      };
+
+      const end = {
+        trackId: it.endTrackId,
+        sliceId: it.endSliceId,
+        sliceName: nullToStr(it.endSliceName),
+        sliceChromeCustomName: nullToUndefined(it.endSliceChromeCustomName),
+        sliceCategory: nullToStr(it.endSliceCategory),
+        sliceStartTs: it.endSliceStartTs,
+        sliceEndTs: it.endSliceEndTs,
+        depth: it.endDepth,
+        threadName: nullToStr(it.endThreadName),
+        processName: nullToStr(it.endProcessName),
+      };
+
+      nodes.push(begin);
+      nodes.push(end);
+
+      flows.push({
+        id,
+        begin,
+        end,
+        dur: it.endSliceStartTs - it.beginSliceEndTs,
+        category,
+        name,
+      });
+    }
+
+
+    // Everything below here is a horrible hack to support flows for
+    // async slice tracks.
+    // In short the issue is this:
+    // - For most slice tracks there is a one-to-one mapping between
+    //   the track in the UI and the track in the TP. n.b. Even in this
+    //   case the UI 'trackId' and the TP 'track.id' may not be the
+    //   same. In this case 'depth' in the TP is the exact depth in the
+    //   UI.
+    // - In the case of aysnc tracks however the mapping is
+    //   one-to-many. Each async slice track in the UI is 'backed' but
+    //   multiple TP tracks. In order to render this track we need
+    //   to adjust depth to avoid overlapping slices. In the render
+    //   path we use experimental_slice_layout for this purpose. This
+    //   is a virtual table in the TP which, for an arbitrary collection
+    //   of TP trackIds, computes for each slice a 'layout_depth'.
+    // - Everything above in this function and its callers doesn't
+    //   know anything about layout_depth.
+    //
+    // So if we stopped here we would have incorrect rendering for
+    // async slice tracks. Instead we want to 'fix' depth for these
+    // cases. We do this in two passes.
+    // - First we collect all the information we need in 'Info' POJOs
+    // - Secondly we loop over those Infos querying
+    //   the database to find the layout_depth for each sliceId
+    // TODO(hjd): This should not be needed after TracksV2 lands.
+
+    // We end up with one Info POJOs for each UI async slice track
+    // which has at least  one flow {begin,end}ing in one of its slices.
+    interface Info {
+      uiTrackId: string;
+      siblingTrackIds: number[];
+      sliceIds: number[];
+      nodes: Array<{
+        sliceId: number,
+        depth: number,
+      }>;
+    }
+
+    const uiTrackIdToInfo = new Map<string, null|Info>();
+    const trackIdToInfo = new Map<number, null|Info>();
+
+    const trackIdToUiTrackId = globals.state.uiTrackIdByTraceTrackId;
+    const tracks = globals.state.tracks;
+
+    const getInfo = (trackId: number): null|Info => {
+      let info = trackIdToInfo.get(trackId);
+      if (info !== undefined) {
+        return info;
+      }
+
+      const uiTrackId = trackIdToUiTrackId[trackId];
+      if (uiTrackId === undefined) {
+        trackIdToInfo.set(trackId, null);
+        return null;
+      }
+
+      const track = tracks[uiTrackId];
+      if (track === undefined) {
+        trackIdToInfo.set(trackId, null);
+        return null;
+      }
+
+      info = uiTrackIdToInfo.get(uiTrackId);
+      if (info !== undefined) {
+        return info;
+      }
+
+      // If 'trackIds' is undefined this is not an async slice track so
+      // we don't need to do anything. We also don't need to do
+      // anything if there is only one TP track in this async track. In
+      // that case experimental_slice_layout is just an expensive way
+      // to find out depth === layout_depth.
+      const trackIds = track.config.trackIds;
+      if (trackIds === undefined || trackIds.length <= 1) {
+        uiTrackIdToInfo.set(uiTrackId, null);
+        trackIdToInfo.set(trackId, null);
+        return null;
+      }
+
+      const newInfo = {
+        uiTrackId,
+        siblingTrackIds: trackIds,
+        sliceIds: [],
+        nodes: [],
+      };
+
+      uiTrackIdToInfo.set(uiTrackId, newInfo);
+      trackIdToInfo.set(trackId, newInfo);
+
+      return newInfo;
+    };
+
+    // First pass, collect:
+    // - all slices that belong to async slice track
+    // - grouped by the async slice track in question
+    for (const node of nodes) {
+      const info = getInfo(node.trackId);
+      if (info !== null) {
+        info.sliceIds.push(node.sliceId);
+        info.nodes.push(node);
+      }
+    }
+
+    // Second pass, for each async track:
+    // - Query to find the layout_depth for each relevant sliceId
+    // - Iterate through the nodes updating the depth in place
+    for (const info of uiTrackIdToInfo.values()) {
+      if (info === null) {
+        continue;
+      }
+      const r = await this.args.engine.query(`
+        SELECT
+          id,
+          layout_depth as depth
+        FROM
+          experimental_slice_layout
+        WHERE
+          filter_track_ids = '${info.siblingTrackIds.join(',')}'
+          AND id in (${info.sliceIds.join(', ')})
+      `);
+
+      // Create the sliceId -> new depth map:
+      const it = r.iter({
+        id: NUM,
+        depth: NUM,
+      });
+      const sliceIdToDepth = new Map<number, number>();
+      for (; it.valid(); it.next()) {
+        sliceIdToDepth.set(it.id, it.depth);
+      }
+
+      // For each begin/end from an async track update the depth:
+      for (const node of info.nodes) {
+        const newDepth = sliceIdToDepth.get(node.sliceId);
+        if (newDepth !== undefined) {
+          node.depth = newDepth;
+        }
+      }
+    }
+
+    callback(flows);
   }
 
   sliceSelected(sliceId: number) {
@@ -262,6 +386,10 @@
         for (const trackId of actualConfig.trackIds) {
           trackIds.push(trackId);
         }
+      } else if (track.config.trackIds !== undefined) {
+        for (const trackId of track.config.trackIds) {
+          trackIds.push(trackId);
+        }
       }
     }
 
diff --git a/ui/src/frontend/viewer_page.ts b/ui/src/frontend/viewer_page.ts
index 0f8414c..b61c1c9 100644
--- a/ui/src/frontend/viewer_page.ts
+++ b/ui/src/frontend/viewer_page.ts
@@ -13,8 +13,9 @@
 // limitations under the License.
 
 import m from 'mithril';
-import {BigintMath} from '../base/bigint_math';
 
+import {BigintMath} from '../base/bigint_math';
+import {clamp} from '../base/math_utils';
 import {Actions} from '../common/actions';
 import {featureFlags} from '../common/feature_flags';
 
@@ -187,8 +188,9 @@
           let startPx = Math.min(dragStartX, currentX) - TRACK_SHELL_WIDTH;
           let endPx = Math.max(dragStartX, currentX) - TRACK_SHELL_WIDTH;
           if (startPx < 0 && endPx < 0) return;
-          startPx = Math.max(startPx, visibleTimeScale.pxSpan.start);
-          endPx = Math.min(endPx, visibleTimeScale.pxSpan.end);
+          const {pxSpan} = visibleTimeScale;
+          startPx = clamp(startPx, pxSpan.start, pxSpan.end);
+          endPx = clamp(endPx, pxSpan.start, pxSpan.end);
           frontendLocalState.selectArea(
               visibleTimeScale.pxToHpTime(startPx).toTPTime('floor'),
               visibleTimeScale.pxToHpTime(endPx).toTPTime('ceil'),
diff --git a/ui/src/tracks/scroll_jank/event_latency_track.ts b/ui/src/tracks/scroll_jank/event_latency_track.ts
index 4b695c2..e1a3633 100644
--- a/ui/src/tracks/scroll_jank/event_latency_track.ts
+++ b/ui/src/tracks/scroll_jank/event_latency_track.ts
@@ -19,11 +19,18 @@
   generateSqlWithInternalLayout,
 } from '../../common/internal_layout_utils';
 import {PrimaryTrackSortKey, SCROLLING_TRACK_GROUP} from '../../common/state';
-import {NamedSliceTrack} from '../../frontend/named_slice_track';
+import {
+  NamedSliceTrack,
+  NamedSliceTrackTypes,
+} from '../../frontend/named_slice_track';
 import {NewTrackArgs, Track} from '../../frontend/track';
 import {DecideTracksResult} from '../chrome_scroll_jank';
 
-export class EventLatencyTrack extends NamedSliceTrack {
+export interface EventLatencyTrackTypes extends NamedSliceTrackTypes {
+  config: {baseTable: string;}
+}
+
+export class EventLatencyTrack extends NamedSliceTrack<EventLatencyTrackTypes> {
   static readonly kind = 'org.chromium.ScrollJank.event_latencies';
 
   static create(args: NewTrackArgs): Track {
@@ -36,12 +43,7 @@
 
   async initSqlTable(tableName: string) {
     const sql =
-      `CREATE VIEW ${tableName} AS ` +
-      generateSqlWithInternalLayout({
-        columns: ['id', 'ts', 'dur', 'track_id', 'cause_of_jank AS name'],
-        layoutParams: {ts: 'ts', dur: 'dur'},
-        sourceTable: 'chrome_janky_event_latencies_v2',
-      }) + `;`;
+        `CREATE VIEW ${tableName} AS SELECT * FROM ${this.config.baseTable}`;
 
     await this.engine.query(sql);
   }
@@ -50,21 +52,81 @@
   // this behavior should be customized to show jank-related data.
 }
 
-export async function addLatenciesTrack(engine: Engine):
+export async function addLatencyTracks(engine: Engine):
     Promise<DecideTracksResult> {
   const result: DecideTracksResult = {
     tracksToAdd: [],
   };
 
-  await engine.query(`SELECT IMPORT('chrome.chrome_scroll_janks');`);
+  const subTableSql = generateSqlWithInternalLayout({
+    columns: ['id', 'ts', 'dur', 'track_id', 'name'],
+    layoutParams: {ts: 'ts', dur: 'dur'},
+    sourceTable: 'slice',
+    whereClause: `
+      EXTRACT_ARG(arg_set_id, 'event_latency.event_type') IN (
+        'FIRST_GESTURE_SCROLL_UPDATE',
+        'GESTURE_SCROLL_UPDATE',
+        'INERTIAL_GESTURE_SCROLL_UPDATE')
+      AND HAS_DESCENDANT_SLICE_WITH_NAME(
+        id,
+        'SubmitCompositorFrameToPresentationCompositorFrame')`,
+  });
+
+  // Table name must be unique - it cannot include '-' characters or begin with
+  // a numeric value.
+  const baseTable =
+      `table_${uuidv4().split('-').join('_')}_janky_event_latencies_v2`;
+  const tableDefSql = `CREATE TABLE ${baseTable} AS
+      WITH event_latencies AS (
+        ${subTableSql}
+      ), latency_stages AS (
+      SELECT
+        d.id,
+        d.ts,
+        d.dur,
+        d.track_id,
+        d.name,
+        d.depth,
+        min(a.id) AS parent_id
+      FROM slice s
+        JOIN descendant_slice(s.id) d
+        JOIN ancestor_slice(d.id) a
+      WHERE s.id IN (SELECT id FROM event_latencies)
+      GROUP BY d.id, d.ts, d.dur, d.track_id, d.name, d.parent_id, d.depth)
+    SELECT
+      id,
+      ts,
+      dur,
+      CASE
+        WHEN id IN (
+          SELECT id FROM chrome_janky_event_latencies_v2)
+        THEN 'Janky EventLatency'
+        ELSE name
+      END
+      AS name,
+      depth * 3 AS depth
+    FROM event_latencies
+    UNION ALL
+    SELECT
+      ls.id,
+      ls.ts,
+      ls.dur,
+      ls.name,
+      depth + (
+        (SELECT depth FROM event_latencies
+        WHERE id = ls.parent_id LIMIT 1) * 3) AS depth
+    FROM latency_stages ls;`;
+
+  await engine.query(`SELECT IMPORT('chrome.chrome_scroll_janks')`);
+  await engine.query(tableDefSql);
 
   result.tracksToAdd.push({
     id: uuidv4(),
     engineId: engine.id,
     kind: EventLatencyTrack.kind,
     trackSortKey: PrimaryTrackSortKey.NULL_TRACK,
-    name: 'Scroll Janks',
-    config: {},
+    name: 'Input Event Latencies',
+    config: {baseTable: baseTable},
     trackGroup: SCROLLING_TRACK_GROUP,
   });
 
diff --git a/ui/src/tracks/scroll_jank/index.ts b/ui/src/tracks/scroll_jank/index.ts
index c6d9ef3..c7452c2 100644
--- a/ui/src/tracks/scroll_jank/index.ts
+++ b/ui/src/tracks/scroll_jank/index.ts
@@ -12,17 +12,20 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {AddTrackArgs} from '../../common/actions';
+import {Engine} from '../../common/engine';
 import {featureFlags} from '../../common/feature_flags';
 import {PluginContext} from '../../common/plugin_api';
 
-import {addLatenciesTrack, EventLatencyTrack} from './event_latency_track';
+import {addLatencyTracks, EventLatencyTrack} from './event_latency_track';
 import {
-  addTopLevelScrollTrack,
   TopLevelScrollTrack,
 } from './scroll_track';
-import {Engine} from '../../common/engine';
 import {addTopLevelJankTrack, TopLevelJankTrack} from './top_level_jank_track';
-import {AddTrackArgs} from '../../common/actions';
+import {
+  addJankyLatenciesTrack,
+  TopLevelEventLatencyTrack,
+} from './top_level_janky_event_latencies';
 
 // Constants for rendering plugin tracks.
 export const INPUT_LATENCY_TRACK = 'InputLatency::';
@@ -55,20 +58,20 @@
         topLevelJanksResult.tracksToAdd[i];
   }
 
-  const topLevelScrolls = addTopLevelScrollTrack(engine);
-  const topLevelScrollsResult = await topLevelScrolls;
-  originalLength = result.tracksToAdd.length;
-  result.tracksToAdd.length += topLevelScrollsResult.tracksToAdd.length;
-  for (let i = 0; i < topLevelScrollsResult.tracksToAdd.length; ++i) {
-    result.tracksToAdd[i + originalLength] =
-        topLevelScrollsResult.tracksToAdd[i];
-  }
-
   // TODO(b/278844325): Top Level event latency summary is already rendered in
   // the TopLevelJankTrack; this track should be rendered at a more
   // intuitive location when the descendant slices are rendered.
   originalLength = result.tracksToAdd.length;
-  const eventLatencies = addLatenciesTrack(engine);
+  const jankyEventLatencies = addJankyLatenciesTrack(engine);
+  const jankyEventLatencyResult = await jankyEventLatencies;
+  result.tracksToAdd.length += jankyEventLatencyResult.tracksToAdd.length;
+  for (let i = 0; i < jankyEventLatencyResult.tracksToAdd.length; ++i) {
+    result.tracksToAdd[i + originalLength] =
+        jankyEventLatencyResult.tracksToAdd[i];
+  }
+
+  originalLength = result.tracksToAdd.length;
+  const eventLatencies = addLatencyTracks(engine);
   const eventLatencyResult = await eventLatencies;
   result.tracksToAdd.length += eventLatencyResult.tracksToAdd.length;
   for (let i = 0; i < eventLatencyResult.tracksToAdd.length; ++i) {
@@ -81,6 +84,7 @@
 function activate(ctx: PluginContext) {
   ctx.registerTrack(TopLevelJankTrack);
   ctx.registerTrack(TopLevelScrollTrack);
+  ctx.registerTrack(TopLevelEventLatencyTrack);
   ctx.registerTrack(EventLatencyTrack);
 }
 
diff --git a/ui/src/tracks/scroll_jank/top_level_jank_track.ts b/ui/src/tracks/scroll_jank/top_level_jank_track.ts
index 54074e5..6c2c467 100644
--- a/ui/src/tracks/scroll_jank/top_level_jank_track.ts
+++ b/ui/src/tracks/scroll_jank/top_level_jank_track.ts
@@ -38,7 +38,7 @@
     const sql = `CREATE VIEW ${tableName} AS
     WITH unioned_data AS (
       SELECT
-        "Scrolling" AS name,
+        "Scrolling: " || scroll_ids AS name,
         ts,
         dur,
         0 AS depth
@@ -100,6 +100,7 @@
     tracksToAdd: [],
   };
 
+  await engine.query(`SELECT IMPORT('chrome.chrome_scrolls');`);
   await engine.query(`SELECT IMPORT('chrome.chrome_scroll_janks');`);
 
   result.tracksToAdd.push({
diff --git a/ui/src/tracks/scroll_jank/top_level_janky_event_latencies.ts b/ui/src/tracks/scroll_jank/top_level_janky_event_latencies.ts
new file mode 100644
index 0000000..757691a
--- /dev/null
+++ b/ui/src/tracks/scroll_jank/top_level_janky_event_latencies.ts
@@ -0,0 +1,128 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import {v4 as uuidv4} from 'uuid';
+
+import {Actions} from '../../common/actions';
+import {Engine} from '../../common/engine';
+import {
+  generateSqlWithInternalLayout,
+} from '../../common/internal_layout_utils';
+import {
+  PrimaryTrackSortKey,
+  SCROLLING_TRACK_GROUP,
+  Selection,
+} from '../../common/state';
+import {OnSliceClickArgs} from '../../frontend/base_slice_track';
+import {
+  Columns,
+  GenericSliceDetailsTab,
+} from '../../frontend/generic_slice_details_tab';
+import {globals} from '../../frontend/globals';
+import {
+  NamedSliceTrack,
+  NamedSliceTrackTypes,
+} from '../../frontend/named_slice_track';
+import {NewTrackArgs, Track} from '../../frontend/track';
+import {DecideTracksResult} from '../chrome_scroll_jank';
+
+export class TopLevelEventLatencyTrack extends NamedSliceTrack {
+  static readonly kind = 'org.chromium.ScrollJank.top_level_event_latencies';
+
+  static create(args: NewTrackArgs): Track {
+    return new TopLevelEventLatencyTrack(args);
+  }
+
+  constructor(args: NewTrackArgs) {
+    super(args);
+  }
+
+  async initSqlTable(tableName: string) {
+    const sql =
+        `CREATE VIEW ${tableName} AS ` + generateSqlWithInternalLayout({
+          columns: [
+            'id',
+            'ts',
+            'dur',
+            'track_id',
+            'cause_of_jank || IIF(sub_cause_of_jank IS NOT NULL, "::" || sub_cause_of_jank, "") AS name',
+            'name AS type',
+            'sub_cause_of_jank',
+          ],
+          layoutParams: {ts: 'ts', dur: 'dur'},
+          sourceTable: 'chrome_janky_event_latencies_v2',
+        }) +
+        `;`;
+
+    await this.engine.query(sql);
+  }
+
+  isSelectionHandled(selection: Selection) {
+    if (selection.kind !== 'BASIC_SQL_OBJECT') {
+      return false;
+    }
+    return selection.trackId === this.trackId;
+  }
+
+  onSliceClick(args: OnSliceClickArgs<NamedSliceTrackTypes['slice']>) {
+    const columns: Columns = {};
+    columns['name'] = {displayName: 'Cause of Jank'};
+    columns['sub_cause_of_jank'] = {displayName: 'Sub-cause of Jank'};
+    columns['id'] = {displayName: 'Slice ID'};
+    columns['ts'] = {displayName: 'Start time'};
+    columns['dur'] = {displayName: 'Duration'};
+    columns['type'] = {displayName: 'Slice Type'};
+
+
+    const title = 'Scroll Jank Summary';
+
+    globals.dispatch(Actions.selectBasicSqlSlice({
+      id: args.slice.id,
+      sqlTableName: this.tableName,
+      start: args.slice.start,
+      duration: args.slice.duration,
+      trackId: this.trackId,
+      detailsPanelConfig: {
+        kind: GenericSliceDetailsTab.kind,
+        config: {
+          id: args.slice.id,
+          sqlTableName: this.tableName,
+          title: title,
+          columns: columns,
+        },
+      },
+    }));
+  }
+}
+
+export async function addJankyLatenciesTrack(engine: Engine):
+    Promise<DecideTracksResult> {
+  const result: DecideTracksResult = {
+    tracksToAdd: [],
+  };
+
+  await engine.query(`SELECT IMPORT('chrome.chrome_scroll_janks');`);
+
+  result.tracksToAdd.push({
+    id: uuidv4(),
+    engineId: engine.id,
+    kind: TopLevelEventLatencyTrack.kind,
+    trackSortKey: PrimaryTrackSortKey.NULL_TRACK,
+    name: 'Scroll Janks',
+    config: {},
+    trackGroup: SCROLLING_TRACK_GROUP,
+  });
+
+  return result;
+}