Merge "ui: Move aggregation tabs into details panel for area selections" into main
diff --git a/Android.bp b/Android.bp
index c81d04a..4541d4c 100644
--- a/Android.bp
+++ b/Android.bp
@@ -11100,12 +11100,10 @@
         "src/trace_processor/importers/common/global_args_tracker.cc",
         "src/trace_processor/importers/common/metadata_tracker.cc",
         "src/trace_processor/importers/common/process_tracker.cc",
-        "src/trace_processor/importers/common/sched_event_tracker.cc",
         "src/trace_processor/importers/common/slice_tracker.cc",
         "src/trace_processor/importers/common/slice_translation_table.cc",
         "src/trace_processor/importers/common/stack_profile_tracker.cc",
         "src/trace_processor/importers/common/system_info_tracker.cc",
-        "src/trace_processor/importers/common/thread_state_tracker.cc",
         "src/trace_processor/importers/common/trace_parser.cc",
         "src/trace_processor/importers/common/track_tracker.cc",
     ],
@@ -11136,7 +11134,6 @@
         "src/trace_processor/importers/common/process_tracker_unittest.cc",
         "src/trace_processor/importers/common/slice_tracker_unittest.cc",
         "src/trace_processor/importers/common/slice_translation_table_unittest.cc",
-        "src/trace_processor/importers/common/thread_state_tracker_unittest.cc",
     ],
 }
 
@@ -11165,13 +11162,14 @@
         "src/trace_processor/importers/ftrace/drm_tracker.cc",
         "src/trace_processor/importers/ftrace/ftrace_module_impl.cc",
         "src/trace_processor/importers/ftrace/ftrace_parser.cc",
-        "src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.cc",
         "src/trace_processor/importers/ftrace/ftrace_tokenizer.cc",
         "src/trace_processor/importers/ftrace/gpu_work_period_tracker.cc",
         "src/trace_processor/importers/ftrace/iostat_tracker.cc",
         "src/trace_processor/importers/ftrace/mali_gpu_event_tracker.cc",
         "src/trace_processor/importers/ftrace/pkvm_hyp_cpu_tracker.cc",
         "src/trace_processor/importers/ftrace/rss_stat_tracker.cc",
+        "src/trace_processor/importers/ftrace/sched_event_tracker.cc",
+        "src/trace_processor/importers/ftrace/thread_state_tracker.cc",
         "src/trace_processor/importers/ftrace/v4l2_tracker.cc",
         "src/trace_processor/importers/ftrace/virtio_gpu_tracker.cc",
         "src/trace_processor/importers/ftrace/virtio_video_tracker.cc",
@@ -11191,7 +11189,8 @@
     name: "perfetto_src_trace_processor_importers_ftrace_unittests",
     srcs: [
         "src/trace_processor/importers/ftrace/binder_tracker_unittest.cc",
-        "src/trace_processor/importers/ftrace/ftrace_sched_event_tracker_unittest.cc",
+        "src/trace_processor/importers/ftrace/sched_event_tracker_unittest.cc",
+        "src/trace_processor/importers/ftrace/thread_state_tracker_unittest.cc",
     ],
 }
 
diff --git a/BUILD b/BUILD
index 071be0d..1e7c962 100644
--- a/BUILD
+++ b/BUILD
@@ -1471,9 +1471,6 @@
         "src/trace_processor/importers/common/metadata_tracker.h",
         "src/trace_processor/importers/common/process_tracker.cc",
         "src/trace_processor/importers/common/process_tracker.h",
-        "src/trace_processor/importers/common/sched_event_state.h",
-        "src/trace_processor/importers/common/sched_event_tracker.cc",
-        "src/trace_processor/importers/common/sched_event_tracker.h",
         "src/trace_processor/importers/common/slice_tracker.cc",
         "src/trace_processor/importers/common/slice_tracker.h",
         "src/trace_processor/importers/common/slice_translation_table.cc",
@@ -1482,8 +1479,6 @@
         "src/trace_processor/importers/common/stack_profile_tracker.h",
         "src/trace_processor/importers/common/system_info_tracker.cc",
         "src/trace_processor/importers/common/system_info_tracker.h",
-        "src/trace_processor/importers/common/thread_state_tracker.cc",
-        "src/trace_processor/importers/common/thread_state_tracker.h",
         "src/trace_processor/importers/common/trace_parser.cc",
         "src/trace_processor/importers/common/track_tracker.cc",
         "src/trace_processor/importers/common/track_tracker.h",
@@ -1538,8 +1533,6 @@
         "src/trace_processor/importers/ftrace/ftrace_module_impl.h",
         "src/trace_processor/importers/ftrace/ftrace_parser.cc",
         "src/trace_processor/importers/ftrace/ftrace_parser.h",
-        "src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.cc",
-        "src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.h",
         "src/trace_processor/importers/ftrace/ftrace_tokenizer.cc",
         "src/trace_processor/importers/ftrace/ftrace_tokenizer.h",
         "src/trace_processor/importers/ftrace/gpu_work_period_tracker.cc",
@@ -1552,6 +1545,10 @@
         "src/trace_processor/importers/ftrace/pkvm_hyp_cpu_tracker.h",
         "src/trace_processor/importers/ftrace/rss_stat_tracker.cc",
         "src/trace_processor/importers/ftrace/rss_stat_tracker.h",
+        "src/trace_processor/importers/ftrace/sched_event_tracker.cc",
+        "src/trace_processor/importers/ftrace/sched_event_tracker.h",
+        "src/trace_processor/importers/ftrace/thread_state_tracker.cc",
+        "src/trace_processor/importers/ftrace/thread_state_tracker.h",
         "src/trace_processor/importers/ftrace/v4l2_tracker.cc",
         "src/trace_processor/importers/ftrace/v4l2_tracker.h",
         "src/trace_processor/importers/ftrace/virtio_gpu_tracker.cc",
diff --git a/src/trace_processor/importers/common/BUILD.gn b/src/trace_processor/importers/common/BUILD.gn
index 466876a..823edd8 100644
--- a/src/trace_processor/importers/common/BUILD.gn
+++ b/src/trace_processor/importers/common/BUILD.gn
@@ -40,9 +40,6 @@
     "metadata_tracker.h",
     "process_tracker.cc",
     "process_tracker.h",
-    "sched_event_state.h",
-    "sched_event_tracker.cc",
-    "sched_event_tracker.h",
     "slice_tracker.cc",
     "slice_tracker.h",
     "slice_translation_table.cc",
@@ -51,8 +48,6 @@
     "stack_profile_tracker.h",
     "system_info_tracker.cc",
     "system_info_tracker.h",
-    "thread_state_tracker.cc",
-    "thread_state_tracker.h",
     "trace_parser.cc",
     "track_tracker.cc",
     "track_tracker.h",
@@ -110,7 +105,6 @@
     "process_tracker_unittest.cc",
     "slice_tracker_unittest.cc",
     "slice_translation_table_unittest.cc",
-    "thread_state_tracker_unittest.cc",
   ]
   testonly = true
   deps = [
diff --git a/src/trace_processor/importers/common/address_range.h b/src/trace_processor/importers/common/address_range.h
index 817ed9f..5d7f430 100644
--- a/src/trace_processor/importers/common/address_range.h
+++ b/src/trace_processor/importers/common/address_range.h
@@ -18,8 +18,10 @@
 #define SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_ADDRESS_RANGE_H_
 
 #include <algorithm>
+
 #include <cstdint>
 #include <map>
+#include <set>
 #include <tuple>
 #include <utility>
 
@@ -33,6 +35,29 @@
 // Note: This means that you can not have a range containing int64_max
 class AddressRange {
  public:
+  struct CompareByEnd {
+    // Allow heterogeneous lookups (https://abseil.io/tips/144)
+    using is_transparent = void;
+    // Keeps ranges sorted by end address
+    bool operator()(const AddressRange& lhs, const AddressRange& rhs) const {
+      return lhs.end() < rhs.end();
+    }
+
+    // Overload to implement PC lookup via upper_bound.
+    bool operator()(const AddressRange& lhs, uint64_t pc) const {
+      return lhs.end() < pc;
+    }
+
+    // Overload to implement PC lookup via upper_bound.
+    bool operator()(uint64_t pc, const AddressRange& rhs) const {
+      return pc < rhs.end();
+    }
+  };
+
+  static constexpr AddressRange FromStartAndSize(uint64_t start,
+                                                 uint64_t size) {
+    return AddressRange(start, start + size);
+  }
   constexpr AddressRange() : AddressRange(0, 0) {}
 
   constexpr AddressRange(uint64_t start, uint64_t end)
@@ -62,7 +87,8 @@
   // there exists a point such that Contains(point) would return true for both
   // ranges.
   constexpr bool Overlaps(const AddressRange& other) const {
-    return start_ < other.end_ && other.start_ < end_;
+    return !empty() && !other.empty() && start_ < other.end_ &&
+           other.start_ < end_;
   }
 
   // Two ranges are the same is their respective limits are the same, that is A
@@ -91,49 +117,119 @@
   uint64_t end_;
 };
 
+// Contains unique collection of addresses. These addresses are kept as
+// sorted collection of non contiguous and non overlapping AddressRange
+// instances. As addresses are added or removed these AddressRange might be
+// merged or spliced as needed to keep the ranges non contiguous and non
+// overlapping.
+class AddressSet {
+ public:
+  // TODO(carlscab): Consider using base::FlatSet. As of now this class is used
+  // so little that it does not really matter.
+  using Impl = std::set<AddressRange, AddressRange::CompareByEnd>;
+
+  using value_type = typename Impl::value_type;
+  using const_iterator = typename Impl::const_iterator;
+  using size_type = typename Impl::size_type;
+
+  const_iterator begin() const { return ranges_.begin(); }
+  const_iterator end() const { return ranges_.end(); }
+
+  // Adds all the addresses in the given range to the set.
+  void Add(AddressRange range) {
+    if (range.empty()) {
+      return;
+    }
+    uint64_t start = range.start();
+    uint64_t end = range.end();
+    // Note lower_bound here as we might need to merge with the range just
+    // before.
+    auto it = ranges_.lower_bound(start);
+
+    PERFETTO_DCHECK(it == ranges_.end() || range.start() <= it->end());
+
+    while (it != ranges_.end() && range.end() >= it->start()) {
+      start = std::min(start, it->start());
+      end = std::max(end, it->end());
+      it = ranges_.erase(it);
+    }
+    ranges_.emplace_hint(it, AddressRange(start, end));
+  }
+
+  // Removes all the addresses in the given range from the set.
+  void Remove(AddressRange range) {
+    if (range.empty()) {
+      return;
+    }
+    auto it = ranges_.upper_bound(range.start());
+    PERFETTO_DCHECK(it == ranges_.end() || range.start() < it->end());
+
+    while (it != ranges_.end() && range.end() > it->start()) {
+      if (range.start() > it->start()) {
+        // range.start() is contained in *it. Split *it at range.start() into
+        // two ranges. Continue the loop at the second of them.
+        PERFETTO_DCHECK(it->Contains(range.start()));
+        auto old = *it;
+        it = ranges_.erase(it);
+        ranges_.emplace_hint(it, old.start(), range.start());
+        it = ranges_.emplace_hint(it, range.start(), old.end());
+      } else if (range.end() < it->end()) {
+        // range.end() is contained in *it. Split *it at range.end() into two
+        // ranges. The first of them needs to be deleted.
+        PERFETTO_DCHECK(it->Contains(range.end()));
+        auto old_end = it->end();
+        it = ranges_.erase(it);
+        ranges_.emplace_hint(it, range.end(), old_end);
+      } else {
+        // range fully contains *it, so it can be removed
+        PERFETTO_DCHECK(range.Contains(*it));
+        it = ranges_.erase(it);
+      }
+    }
+  }
+
+  bool operator==(const AddressSet& o) const { return ranges_ == o.ranges_; }
+  bool operator!=(const AddressSet& o) const { return ranges_ != o.ranges_; }
+
+ private:
+  // Invariants:
+  //   * There are no overlapping ranges.
+  //   * There are no empty ranges.
+  //   * There are no two ranges a, b where a.end == b.start, that is there are
+  //     no contiguous mappings.
+  //   * Ranges are sorted by end
+  // Thus lookups are O(log N) and point lookups are trivial using upper_bound()
+  Impl ranges_;
+};
+
 // Maps AddressRange instances to a given value. These AddressRange instances
 // (basically the keys of the map)  will never overlap, as insertions of
 // overlapping ranges will always fail.
 template <typename Value>
 class AddressRangeMap {
  public:
-  struct CompareByEnd {
-    // Allow heterogeneous lookups (https://abseil.io/tips/144)
-    using is_transparent = void;
-    // Keeps ranges sorted by end address
-    bool operator()(const AddressRange& lhs, const AddressRange& rhs) const {
-      return lhs.end() < rhs.end();
-    }
-
-    // Overload to implement PC lookup via upper_bound.
-    bool operator()(const AddressRange& lhs, uint64_t pc) const {
-      return lhs.end() < pc;
-    }
-
-    // Overload to implement PC lookup via upper_bound.
-    bool operator()(uint64_t pc, const AddressRange& rhs) const {
-      return pc < rhs.end();
-    }
-  };
-
-  using Impl = std::map<AddressRange, Value, CompareByEnd>;
+  using Impl = std::map<AddressRange, Value, AddressRange::CompareByEnd>;
 
   using value_type = typename Impl::value_type;
   using iterator = typename Impl::iterator;
   using const_iterator = typename Impl::const_iterator;
   using size_type = typename Impl::size_type;
 
-  // Fails if the new range overlaps with any existing one.
+  // Fails if the new range overlaps with any existing one or when inserting an
+  // empty range (as there is effectively no key to map from).
   template <typename... Args>
-  std::pair<iterator, bool> Emplace(AddressRange range, Args&&... args) {
+  bool Emplace(AddressRange range, Args&&... args) {
+    if (range.empty()) {
+      return false;
+    }
     auto it = ranges_.upper_bound(range.start());
     if (it != ranges_.end() && range.end() > it->first.start()) {
-      return {it, false};
+      return false;
     }
-    return {ranges_.emplace_hint(
-                it, std::piecewise_construct, std::forward_as_tuple(range),
-                std::forward_as_tuple(std::forward<Args>(args)...)),
-            true};
+    ranges_.emplace_hint(it, std::piecewise_construct,
+                         std::forward_as_tuple(range),
+                         std::forward_as_tuple(std::forward<Args>(args)...));
+    return true;
   }
 
   // Finds the map entry that fully contains the given `range` or `end()` if not
@@ -181,13 +277,15 @@
   // Emplaces a new value into the map by first deleting all overlapping
   // intervals. It takes an optional (set to nullptr to ignore) callback `cb`
   // that will be called for each deleted map entry.
-  // ATTENTION: `range` can not be empty. Supporting it would complicate things
-  // too much for a not needed use case.
+  // Returns true on success, fails if the new range is empty (as there is
+  // effectively no key to map from).
   template <typename Callback, typename... Args>
-  void DeleteOverlapsAndEmplace(Callback cb,
+  bool DeleteOverlapsAndEmplace(Callback cb,
                                 AddressRange range,
                                 Args&&... args) {
-    PERFETTO_CHECK(!range.empty());
+    if (range.empty()) {
+      return false;
+    }
     auto it = ranges_.upper_bound(range.start());
     PERFETTO_DCHECK(it == ranges_.end() || range.start() < it->first.end());
 
@@ -199,21 +297,38 @@
     ranges_.emplace_hint(it, std::piecewise_construct,
                          std::forward_as_tuple(range),
                          std::forward_as_tuple(std::forward<Args>(args)...));
+    return true;
   }
 
   // Same as above but without a callback.
-  template <typename Callback, typename... Args>
-  void DeleteOverlapsAndEmplace(AddressRange range, Args&&... args) {
+  template <typename... Args>
+  bool DeleteOverlapsAndEmplace(AddressRange range, Args&&... args) {
     struct NoOp {
       void operator()(std::pair<const AddressRange, Value>&) {}
     };
-    DeleteOverlapsAndEmplace(NoOp(), range, std::forward<Args>(args)...);
+    return DeleteOverlapsAndEmplace(NoOp(), range, std::forward<Args>(args)...);
+  }
+
+  // Calls `cb` for each entry in the map that overlaps the given `range`. That
+  // is, there is a point so for which `AddressRange::Contains` returns true for
+  // both the entry and the given `range'
+  template <typename Callback>
+  void ForOverlaps(AddressRange range, Callback cb) {
+    if (range.empty()) {
+      return;
+    }
+    for (auto it = ranges_.upper_bound(range.start());
+         it != ranges_.end() && range.end() > it->first.start(); ++it) {
+      cb(*it);
+    }
   }
 
  private:
-  // Invariant: There are no overlapping ranges.
-  // Which makes lookups O(log N). Also, ranges are sorted by end which makes
-  // point lookups trivial using upper_bound()
+  // Invariants:
+  //   * There are no overlapping ranges.
+  //   * There are no empty ranges.
+  //   * Ranges are sorted by end
+  // Thus lookups are O(log N) and point lookups are trivial using upper_bound()
   Impl ranges_;
 };
 
diff --git a/src/trace_processor/importers/common/address_range_unittest.cc b/src/trace_processor/importers/common/address_range_unittest.cc
index 2ae43ff..35f67ae 100644
--- a/src/trace_processor/importers/common/address_range_unittest.cc
+++ b/src/trace_processor/importers/common/address_range_unittest.cc
@@ -38,6 +38,7 @@
 
 namespace {
 
+using ::testing::_;
 using ::testing::A;
 using ::testing::AllOf;
 using ::testing::ElementsAre;
@@ -49,6 +50,11 @@
 using ::testing::Pointee;
 using ::testing::SizeIs;
 
+MATCHER_P2(IteratorPointsTo, container, matcher, "") {
+  return ExplainMatchResult(Ne(container.end()), arg, result_listener) &&
+         ExplainMatchResult(matcher, *arg, result_listener);
+}
+
 auto AppendRangesTo(std::vector<AddressRange>& ranges) {
   return [&ranges](std::pair<const AddressRange, int>& e) {
     ranges.push_back(e.first);
@@ -115,6 +121,18 @@
   EXPECT_THAT(AddressRange(0, 10).IntersectWith(AddressRange()), IsEmpty());
 }
 
+TEST(AddressRange, Overlap) {
+  EXPECT_FALSE(AddressRange(0, 10).Overlaps(AddressRange(5, 5)));
+  EXPECT_FALSE(AddressRange(5, 5).Overlaps(AddressRange(0, 10)));
+  EXPECT_FALSE(AddressRange(0, 10).Overlaps(AddressRange(10, 20)));
+  EXPECT_FALSE(AddressRange(10, 20).Overlaps(AddressRange(0, 10)));
+
+  EXPECT_TRUE(AddressRange(0, 10).Overlaps(AddressRange(9, 10)));
+  EXPECT_TRUE(AddressRange(10, 20).Overlaps(AddressRange(0, 11)));
+  EXPECT_TRUE(AddressRange(0, 10).Overlaps(AddressRange(5, 6)));
+  EXPECT_TRUE(AddressRange(0, 10).Overlaps(AddressRange(5, 20)));
+}
+
 TEST(AddressRangeMap, Empty) {
   AddressRangeMap<int> empty;
   EXPECT_THAT(empty, IsEmpty());
@@ -122,25 +140,43 @@
 
 TEST(AddressRangeMap, EmplaceFailsForOverlaps) {
   AddressRangeMap<int> map;
-  ASSERT_TRUE(map.Emplace(AddressRange(10, 20)).second);
+  ASSERT_TRUE(map.Emplace(AddressRange(10, 20), 42));
 
-  EXPECT_FALSE(map.Emplace(AddressRange(10, 20)).second);
-  EXPECT_FALSE(map.Emplace(AddressRange(11, 19)).second);
-  EXPECT_FALSE(map.Emplace(AddressRange(0, 11)).second);
-  EXPECT_FALSE(map.Emplace(AddressRange(19, 30)).second);
-  EXPECT_THAT(map, SizeIs(1));
+  EXPECT_FALSE(map.Emplace(AddressRange(10, 20)));
+  EXPECT_FALSE(map.Emplace(AddressRange(11, 19)));
+  EXPECT_FALSE(map.Emplace(AddressRange(0, 11)));
+  EXPECT_FALSE(map.Emplace(AddressRange(19, 30)));
+  EXPECT_THAT(map, ElementsAre(Pair(AddressRange(10, 20), 42)));
 }
 
 TEST(AddressRangeMap, EmplaceSucceedsForNonOverlaps) {
   AddressRangeMap<int> map;
 
-  EXPECT_TRUE(map.Emplace(AddressRange(10, 20)).second);
-  EXPECT_TRUE(map.Emplace(AddressRange(0, 10)).second);
-  EXPECT_TRUE(map.Emplace(AddressRange(20, 30)).second);
+  EXPECT_TRUE(map.Emplace(AddressRange(10, 20)));
+  EXPECT_TRUE(map.Emplace(AddressRange(0, 10)));
+  EXPECT_TRUE(map.Emplace(AddressRange(20, 30)));
 
   EXPECT_THAT(map, SizeIs(3));
 }
 
+TEST(AddressRangeMap, EmplaceFailsForEmptyRange) {
+  AddressRangeMap<int> map;
+
+  EXPECT_FALSE(map.Emplace(AddressRange(0, 0)));
+  EXPECT_FALSE(map.Emplace(AddressRange(100, 100)));
+
+  EXPECT_THAT(map, IsEmpty());
+}
+
+TEST(AddressRangeMap, DeleteOverlapsAndEmplaceFailsForEmptyRange) {
+  AddressRangeMap<int> map;
+  EXPECT_TRUE(map.Emplace(AddressRange(0, 10), 42));
+  EXPECT_FALSE(map.Emplace(AddressRange(0, 0)));
+  EXPECT_FALSE(map.Emplace(AddressRange(100, 100)));
+
+  EXPECT_THAT(map, ElementsAre(Pair(AddressRange(0, 10), 42)));
+}
+
 TEST(AddressRangeMap, FindAddress) {
   AddressRangeMap<int> map;
   map.Emplace(AddressRange(0, 10), 0);
@@ -174,30 +210,33 @@
 
 TEST(AddressRangeMap, FindRangeThatContains) {
   AddressRangeMap<int> map;
-  const auto it_1 = map.Emplace(AddressRange(0, 10), 0).first;
-  const auto it_2 = map.Emplace(AddressRange(10, 20), 1).first;
-  const auto it_3 = map.Emplace(AddressRange(25, 30), 2).first;
-  const auto end = map.end();
+  map.Emplace(AddressRange(0, 10), 0);
+  map.Emplace(AddressRange(10, 20), 1);
+  map.Emplace(AddressRange(25, 30), 2);
 
-  EXPECT_THAT(map.FindRangeThatContains({0, 10}), Eq(it_1));
-  EXPECT_THAT(map.FindRangeThatContains({0, 1}), Eq(it_1));
-  EXPECT_THAT(map.FindRangeThatContains({3, 4}), Eq(it_1));
-  EXPECT_THAT(map.FindRangeThatContains({9, 10}), Eq(it_1));
+  auto match_1 = IteratorPointsTo(map, Pair(AddressRange(0, 10), 0));
+  auto match_2 = IteratorPointsTo(map, Pair(AddressRange(10, 20), 1));
+  auto match_3 = IteratorPointsTo(map, Pair(AddressRange(25, 30), 2));
 
-  EXPECT_THAT(map.FindRangeThatContains({10, 11}), Eq(it_2));
-  EXPECT_THAT(map.FindRangeThatContains({11, 12}), Eq(it_2));
-  EXPECT_THAT(map.FindRangeThatContains({19, 20}), Eq(it_2));
-  EXPECT_THAT(map.FindRangeThatContains({10, 20}), Eq(it_2));
+  EXPECT_THAT(map.FindRangeThatContains({0, 10}), match_1);
+  EXPECT_THAT(map.FindRangeThatContains({0, 1}), match_1);
+  EXPECT_THAT(map.FindRangeThatContains({3, 4}), match_1);
+  EXPECT_THAT(map.FindRangeThatContains({9, 10}), match_1);
 
-  EXPECT_THAT(map.FindRangeThatContains({25, 26}), Eq(it_3));
-  EXPECT_THAT(map.FindRangeThatContains({26, 27}), Eq(it_3));
-  EXPECT_THAT(map.FindRangeThatContains({29, 30}), Eq(it_3));
-  EXPECT_THAT(map.FindRangeThatContains({25, 30}), Eq(it_3));
+  EXPECT_THAT(map.FindRangeThatContains({10, 11}), match_2);
+  EXPECT_THAT(map.FindRangeThatContains({11, 12}), match_2);
+  EXPECT_THAT(map.FindRangeThatContains({19, 20}), match_2);
+  EXPECT_THAT(map.FindRangeThatContains({10, 20}), match_2);
 
-  EXPECT_THAT(map.FindRangeThatContains({9, 11}), Eq(end));
-  EXPECT_THAT(map.FindRangeThatContains({20, 21}), Eq(end));
-  EXPECT_THAT(map.FindRangeThatContains({24, 25}), Eq(end));
-  EXPECT_THAT(map.FindRangeThatContains({14, 27}), Eq(end));
+  EXPECT_THAT(map.FindRangeThatContains({25, 26}), match_3);
+  EXPECT_THAT(map.FindRangeThatContains({26, 27}), match_3);
+  EXPECT_THAT(map.FindRangeThatContains({29, 30}), match_3);
+  EXPECT_THAT(map.FindRangeThatContains({25, 30}), match_3);
+
+  EXPECT_THAT(map.FindRangeThatContains({9, 11}), Eq(map.end()));
+  EXPECT_THAT(map.FindRangeThatContains({20, 21}), Eq(map.end()));
+  EXPECT_THAT(map.FindRangeThatContains({24, 25}), Eq(map.end()));
+  EXPECT_THAT(map.FindRangeThatContains({14, 27}), Eq(map.end()));
 }
 
 TEST(AddressRangeMap, DeleteOverlapsAndEmplace) {
@@ -285,6 +324,114 @@
   EXPECT_THAT(map, ElementsAre(entry(5, 11, 5), entry(25, 30, 2)));
 }
 
+TEST(AddressRangeMap, ForOverlapsEmptyRangeDoesNothing) {
+  AddressRangeMap<int> map;
+  map.Emplace(AddressRange(0, 10), 0);
+  map.Emplace(AddressRange(10, 20), 1);
+  map.Emplace(AddressRange(25, 30), 2);
+
+  MockFunction<void(AddressRangeMap<int>::value_type&)> cb;
+  EXPECT_CALL(cb, Call).Times(0);
+
+  map.ForOverlaps(AddressRange(5, 5), cb.AsStdFunction());
+}
+
+TEST(AddressRangeMap, ForOverlaps) {
+  AddressRangeMap<int> map;
+  map.Emplace(AddressRange(0, 10), 0);
+  map.Emplace(AddressRange(10, 20), 1);
+  map.Emplace(AddressRange(20, 30), 2);
+  map.Emplace(AddressRange(35, 40), 3);
+  map.Emplace(AddressRange(40, 50), 4);
+
+  MockFunction<void(AddressRangeMap<int>::value_type&)> cb;
+  EXPECT_CALL(cb, Call(Pair(AddressRange(10, 20), 1)));
+  EXPECT_CALL(cb, Call(Pair(AddressRange(20, 30), 2)));
+  EXPECT_CALL(cb, Call(Pair(AddressRange(35, 40), 3)));
+
+  map.ForOverlaps(AddressRange(15, 36), cb.AsStdFunction());
+}
+
+TEST(AddressSet, Empty) {
+  AddressSet empty;
+  EXPECT_THAT(empty, ElementsAre());
+}
+
+TEST(AddressSet, EmptyRangesAreNotAdded) {
+  AddressSet empty;
+
+  empty.Add({0, 0});
+  empty.Add({10, 10});
+
+  EXPECT_THAT(empty, ElementsAre());
+}
+
+TEST(AddressSet, NonOverlapingNonContiguousAreNotMerged) {
+  AddressSet set;
+  set.Add({0, 10});
+  set.Add({11, 20});
+
+  EXPECT_THAT(set, ElementsAre(AddressRange(0, 10), AddressRange(11, 20)));
+}
+
+TEST(AddressSet, ContiguousAreMerged) {
+  AddressSet set;
+  set.Add({0, 10});
+  set.Add({30, 40});
+  set.Add({10, 30});
+
+  EXPECT_THAT(set, ElementsAre(AddressRange(0, 40)));
+}
+
+TEST(AddressSet, OverlapsAreMerged) {
+  AddressSet set;
+  set.Add({0, 10});
+  set.Add({30, 40});
+  set.Add({5, 35});
+
+  EXPECT_THAT(set, ElementsAre(AddressRange(0, 40)));
+}
+
+TEST(AddressSet, SpliceRemove) {
+  AddressSet set;
+  set.Add({0, 10});
+  set.Remove({2, 5});
+
+  EXPECT_THAT(set, ElementsAre(AddressRange(0, 2), AddressRange(5, 10)));
+}
+
+TEST(AddressSet, PartialRemove) {
+  AddressSet set;
+  set.Add({0, 10});
+  set.Remove({0, 2});
+  set.Remove({8, 10});
+
+  EXPECT_THAT(set, ElementsAre(AddressRange(2, 8)));
+}
+
+TEST(AddressSet, MultipleRemove) {
+  AddressSet set;
+  set.Add({0, 10});
+  set.Add({12, 15});
+  set.Add({20, 30});
+  set.Remove({5, 25});
+
+  EXPECT_THAT(set, ElementsAre(AddressRange(0, 5), AddressRange(25, 30)));
+}
+
+TEST(AddressSet, RemoveEmptyRangeDoesNothing) {
+  AddressSet set;
+  set.Add({0, 10});
+  set.Add({20, 30});
+
+  set.Remove({0, 0});
+  set.Remove({2, 2});
+  set.Remove({10, 10});
+  set.Remove({11, 11});
+
+  EXPECT_THAT(set, ElementsAre(AddressRange(0, 10), AddressRange(20, 30)));
+}
+
 }  // namespace
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/importers/common/sched_event_state.h b/src/trace_processor/importers/common/sched_event_state.h
deleted file mode 100644
index ef66fc2..0000000
--- a/src/trace_processor/importers/common/sched_event_state.h
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_SCHED_EVENT_STATE_H_
-#define SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_SCHED_EVENT_STATE_H_
-
-#include "src/trace_processor/types/version_number.h"
-
-namespace perfetto {
-namespace trace_processor {
-
-// Responsible for keeping the state of pending sched events.
-class SchedEventState {
- public:
-  // Information retained from the preceding sched_switch seen on a given cpu.
-  struct PendingSchedInfo {
-    // The pending scheduling slice that the next event will complete.
-    uint32_t pending_slice_storage_idx = std::numeric_limits<uint32_t>::max();
-
-    // pid/utid/prio corresponding to the last sched_switch seen on this cpu
-    // (its "next_*" fields). There is some duplication with respect to the
-    // slices storage, but we don't always have a slice when decoding events in
-    // the compact format.
-    uint32_t last_pid = std::numeric_limits<uint32_t>::max();
-    UniqueTid last_utid = std::numeric_limits<UniqueTid>::max();
-    int32_t last_prio = std::numeric_limits<int32_t>::max();
-  };
-
-  SchedEventState() {
-    // Pre-allocate space for 128 CPUs, which should be enough for most hosts.
-    // It's OK if this number is too small, the vector will be grown on-demand.
-    pending_sched_per_cpu_.reserve(128);
-  }
-  SchedEventState(const SchedEventState&) = delete;
-  ~SchedEventState() = default;
-
-  // Get the sched info for the given CPU, resizing the vector if necessary.
-  PendingSchedInfo* GetPendingSchedInfoForCpu(uint32_t cpu) {
-    if (PERFETTO_UNLIKELY(cpu >= pending_sched_per_cpu_.size())) {
-      pending_sched_per_cpu_.resize(cpu + 1);
-    }
-    return &pending_sched_per_cpu_[cpu];
-  }
-
- private:
-  // Information retained from the preceding sched_switch seen on a given cpu.
-  std::vector<PendingSchedInfo> pending_sched_per_cpu_;
-};
-
-}  // namespace trace_processor
-}  // namespace perfetto
-
-#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_SCHED_EVENT_STATE_H_
diff --git a/src/trace_processor/importers/common/sched_event_tracker.cc b/src/trace_processor/importers/common/sched_event_tracker.cc
deleted file mode 100644
index e541d58..0000000
--- a/src/trace_processor/importers/common/sched_event_tracker.cc
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "src/trace_processor/importers/common/sched_event_tracker.h"
-
-namespace perfetto {
-namespace trace_processor {
-
-SchedEventTracker::~SchedEventTracker() = default;
-
-}  // namespace trace_processor
-}  // namespace perfetto
diff --git a/src/trace_processor/importers/common/sched_event_tracker.h b/src/trace_processor/importers/common/sched_event_tracker.h
deleted file mode 100644
index 68a2926..0000000
--- a/src/trace_processor/importers/common/sched_event_tracker.h
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_SCHED_EVENT_TRACKER_H_
-#define SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_SCHED_EVENT_TRACKER_H_
-
-#include "perfetto/ext/base/string_view.h"
-#include "perfetto/ext/base/utils.h"
-#include "src/trace_processor/importers/common/event_tracker.h"
-#include "src/trace_processor/importers/common/process_tracker.h"
-#include "src/trace_processor/importers/common/system_info_tracker.h"
-#include "src/trace_processor/importers/common/thread_state_tracker.h"
-#include "src/trace_processor/storage/trace_storage.h"
-#include "src/trace_processor/types/destructible.h"
-#include "src/trace_processor/types/task_state.h"
-#include "src/trace_processor/types/trace_processor_context.h"
-
-namespace perfetto {
-namespace trace_processor {
-
-// Tracks sched events and stores them into the storage as sched slices.
-class SchedEventTracker : public Destructible {
- public:
-  PERFETTO_ALWAYS_INLINE
-  SchedEventTracker(TraceProcessorContext* context) : context_(context) {}
-  SchedEventTracker(const SchedEventTracker&) = delete;
-  ~SchedEventTracker() override;
-
-  PERFETTO_ALWAYS_INLINE
-  uint32_t AddStartSlice(uint32_t cpu,
-                         int64_t ts,
-                         UniqueTid next_utid,
-                         int32_t next_prio) {
-    // Open a new scheduling slice, corresponding to the task that was
-    // just switched to. Set the duration to -1, to indicate that the event is
-    // not finished. Duration will be updated later after event finish.
-    auto* sched = context_->storage->mutable_sched_slice_table();
-    auto row_and_id = sched->Insert(
-        {ts, /* duration */ -1, cpu, next_utid, kNullStringId, next_prio});
-    SchedId sched_id = row_and_id.id;
-    return *sched->id().IndexOf(sched_id);
-  }
-
-  PERFETTO_ALWAYS_INLINE
-  bool UpdateEventTrackerTimestamp(int64_t ts,
-                                   const char* event_name,
-                                   size_t stats) {
-    // At this stage all events should be globally timestamp ordered.
-    if (ts < context_->event_tracker->max_timestamp()) {
-      PERFETTO_ELOG(
-          "%s event out of order by %.4f ms, skipping", event_name,
-          static_cast<double>(context_->event_tracker->max_timestamp() - ts) /
-              1e6);
-      context_->storage->IncrementStats(stats);
-      return false;
-    }
-    context_->event_tracker->UpdateMaxTimestamp(ts);
-    return true;
-  }
-
-  PERFETTO_ALWAYS_INLINE
-  void ClosePendingSlice(uint32_t pending_slice_idx,
-                         int64_t ts,
-                         StringId prev_state) {
-    auto* slices = context_->storage->mutable_sched_slice_table();
-
-    int64_t duration = ts - slices->ts()[pending_slice_idx];
-    slices->mutable_dur()->Set(pending_slice_idx, duration);
-
-    // We store the state as a uint16 as we only consider values up to 2048
-    // when unpacking the information inside; this allows savings of 48 bits
-    // per slice.
-    slices->mutable_end_state()->Set(pending_slice_idx, prev_state);
-  }
-
-  PERFETTO_ALWAYS_INLINE
-  StringId TaskStateToStringId(int64_t task_state_int) {
-    using ftrace_utils::TaskState;
-
-    std::optional<VersionNumber> kernel_version =
-        SystemInfoTracker::GetOrCreate(context_)->GetKernelVersion();
-    TaskState task_state = TaskState::FromRawPrevState(
-        static_cast<uint16_t>(task_state_int), kernel_version);
-    return task_state.is_valid()
-               ? context_->storage->InternString(task_state.ToString().data())
-               : kNullStringId;
-  }
-
- private:
-  TraceProcessorContext* const context_;
-};
-
-}  // namespace trace_processor
-}  // namespace perfetto
-
-#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_SCHED_EVENT_TRACKER_H_
diff --git a/src/trace_processor/importers/ftrace/BUILD.gn b/src/trace_processor/importers/ftrace/BUILD.gn
index 3a533d1..c618978 100644
--- a/src/trace_processor/importers/ftrace/BUILD.gn
+++ b/src/trace_processor/importers/ftrace/BUILD.gn
@@ -37,8 +37,6 @@
     "ftrace_module_impl.h",
     "ftrace_parser.cc",
     "ftrace_parser.h",
-    "ftrace_sched_event_tracker.cc",
-    "ftrace_sched_event_tracker.h",
     "ftrace_tokenizer.cc",
     "ftrace_tokenizer.h",
     "gpu_work_period_tracker.cc",
@@ -51,6 +49,10 @@
     "pkvm_hyp_cpu_tracker.h",
     "rss_stat_tracker.cc",
     "rss_stat_tracker.h",
+    "sched_event_tracker.cc",
+    "sched_event_tracker.h",
+    "thread_state_tracker.cc",
+    "thread_state_tracker.h",
     "v4l2_tracker.cc",
     "v4l2_tracker.h",
     "virtio_gpu_tracker.cc",
@@ -97,7 +99,8 @@
   testonly = true
   sources = [
     "binder_tracker_unittest.cc",
-    "ftrace_sched_event_tracker_unittest.cc",
+    "sched_event_tracker_unittest.cc",
+    "thread_state_tracker_unittest.cc",
   ]
   deps = [
     "../../../../gn:default_deps",
diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.cc b/src/trace_processor/importers/ftrace/ftrace_parser.cc
index 6948fc3..951c03d 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.cc
@@ -26,9 +26,9 @@
 #include "src/trace_processor/importers/common/metadata_tracker.h"
 #include "src/trace_processor/importers/common/parser_types.h"
 #include "src/trace_processor/importers/common/process_tracker.h"
-#include "src/trace_processor/importers/common/thread_state_tracker.h"
 #include "src/trace_processor/importers/common/track_tracker.h"
 #include "src/trace_processor/importers/ftrace/binder_tracker.h"
+#include "src/trace_processor/importers/ftrace/thread_state_tracker.h"
 #include "src/trace_processor/importers/ftrace/v4l2_tracker.h"
 #include "src/trace_processor/importers/ftrace/virtio_video_tracker.h"
 #include "src/trace_processor/importers/i2c/i2c_tracker.h"
@@ -1120,11 +1120,10 @@
   }
 
   using protos::pbzero::FtraceEvent;
-  FtraceSchedEventTracker* ftrace_sched_tracker =
-      FtraceSchedEventTracker::GetOrCreate(context_);
-  ftrace_sched_tracker->PushSchedSwitchCompact(
-      cpu, ts, data.prev_state, static_cast<uint32_t>(data.next_pid),
-      data.next_prio, data.next_comm);
+  SchedEventTracker* sched_tracker = SchedEventTracker::GetOrCreate(context_);
+  sched_tracker->PushSchedSwitchCompact(cpu, ts, data.prev_state,
+                                        static_cast<uint32_t>(data.next_pid),
+                                        data.next_prio, data.next_comm);
   return util::OkStatus();
 }
 
@@ -1139,9 +1138,8 @@
     return util::OkStatus();
   }
   using protos::pbzero::FtraceEvent;
-  FtraceSchedEventTracker* ftrace_sched_tracker =
-      FtraceSchedEventTracker::GetOrCreate(context_);
-  ftrace_sched_tracker->PushSchedWakingCompact(
+  SchedEventTracker* sched_tracker = SchedEventTracker::GetOrCreate(context_);
+  sched_tracker->PushSchedWakingCompact(
       cpu, ts, static_cast<uint32_t>(data.pid), data.target_cpu, data.prio,
       data.comm, data.common_flags);
   return util::OkStatus();
@@ -1323,7 +1321,7 @@
   protos::pbzero::SchedSwitchFtraceEvent::Decoder ss(blob.data, blob.size);
   uint32_t prev_pid = static_cast<uint32_t>(ss.prev_pid());
   uint32_t next_pid = static_cast<uint32_t>(ss.next_pid());
-  FtraceSchedEventTracker::GetOrCreate(context_)->PushSchedSwitch(
+  SchedEventTracker::GetOrCreate(context_)->PushSchedSwitch(
       cpu, timestamp, prev_pid, ss.prev_comm(), ss.prev_prio(), ss.prev_state(),
       next_pid, ss.next_comm(), ss.next_prio());
 }
diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.h b/src/trace_processor/importers/ftrace/ftrace_parser.h
index b3764f1..f3234bd 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.h
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.h
@@ -26,12 +26,12 @@
 #include "src/trace_processor/importers/common/trace_parser.h"
 #include "src/trace_processor/importers/ftrace/drm_tracker.h"
 #include "src/trace_processor/importers/ftrace/ftrace_descriptors.h"
-#include "src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.h"
 #include "src/trace_processor/importers/ftrace/gpu_work_period_tracker.h"
 #include "src/trace_processor/importers/ftrace/iostat_tracker.h"
 #include "src/trace_processor/importers/ftrace/mali_gpu_event_tracker.h"
 #include "src/trace_processor/importers/ftrace/pkvm_hyp_cpu_tracker.h"
 #include "src/trace_processor/importers/ftrace/rss_stat_tracker.h"
+#include "src/trace_processor/importers/ftrace/sched_event_tracker.h"
 #include "src/trace_processor/importers/ftrace/virtio_gpu_tracker.h"
 #include "src/trace_processor/types/trace_processor_context.h"
 
diff --git a/src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.h b/src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.h
deleted file mode 100644
index bc90532..0000000
--- a/src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.h
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_FTRACE_FTRACE_SCHED_EVENT_TRACKER_H_
-#define SRC_TRACE_PROCESSOR_IMPORTERS_FTRACE_FTRACE_SCHED_EVENT_TRACKER_H_
-
-#include <array>
-#include <limits>
-
-#include "perfetto/ext/base/string_view.h"
-#include "perfetto/ext/base/utils.h"
-#include "src/trace_processor/importers/common/sched_event_tracker.h"
-#include "src/trace_processor/importers/common/sched_event_state.h"
-#include "src/trace_processor/storage/trace_storage.h"
-#include "src/trace_processor/types/destructible.h"
-#include "src/trace_processor/types/trace_processor_context.h"
-
-namespace perfetto {
-namespace trace_processor {
-
-class EventTracker;
-
-// Tracks sched events and stores them into the storage as sched slices.
-class FtraceSchedEventTracker : public Destructible {
- public:
-  explicit FtraceSchedEventTracker(TraceProcessorContext*);
-  ~FtraceSchedEventTracker() override;
-
-  FtraceSchedEventTracker(
-      const FtraceSchedEventTracker& ftrace_sched_event_tracker) = delete;
-  FtraceSchedEventTracker& operator=(
-      const FtraceSchedEventTracker& ftrace_sched_event_tracker) = delete;
-
-  static FtraceSchedEventTracker* GetOrCreate(TraceProcessorContext* context) {
-    if (!context->ftrace_sched_tracker) {
-      context->ftrace_sched_tracker.reset(new FtraceSchedEventTracker(context));
-    }
-    return static_cast<FtraceSchedEventTracker*>(
-        context->ftrace_sched_tracker.get());
-  }
-
-  // This method is called when a sched_switch event is seen in the trace.
-  // Virtual for testing.
-  virtual void PushSchedSwitch(uint32_t cpu,
-                               int64_t timestamp,
-                               uint32_t prev_pid,
-                               base::StringView prev_comm,
-                               int32_t prev_prio,
-                               int64_t prev_state,
-                               uint32_t next_pid,
-                               base::StringView next_comm,
-                               int32_t next_prio);
-
-  void AddRawSchedSwitchEvent(uint32_t cpu,
-                              int64_t ts,
-                              UniqueTid prev_utid,
-                              uint32_t prev_pid,
-                              StringId prev_comm_id,
-                              int32_t prev_prio,
-                              int64_t prev_state,
-                              uint32_t next_pid,
-                              StringId next_comm_id,
-                              int32_t next_prio);
-
-  // This method is called when parsing a sched_switch encoded in the compact
-  // format.
-  void PushSchedSwitchCompact(uint32_t cpu,
-                              int64_t ts,
-                              int64_t prev_state,
-                              uint32_t next_pid,
-                              int32_t next_prio,
-                              StringId next_comm_id);
-
-  // This method is called when parsing a sched_waking encoded in the compact
-  // format. Note that the default encoding is handled by
-  // |EventTracker::PushInstant|.
-  void PushSchedWakingCompact(uint32_t cpu,
-                              int64_t ts,
-                              uint32_t wakee_pid,
-                              uint16_t target_cpu,
-                              uint16_t prio,
-                              StringId comm_id,
-                              uint16_t common_flags);
-
- private:
-  static constexpr uint8_t kSchedSwitchMaxFieldId = 7;
-  std::array<StringId, kSchedSwitchMaxFieldId + 1> sched_switch_field_ids_;
-  StringId sched_switch_id_;
-
-  static constexpr uint8_t kSchedWakingMaxFieldId = 5;
-  std::array<StringId, kSchedWakingMaxFieldId + 1> sched_waking_field_ids_;
-  StringId sched_waking_id_;
-
-  TraceProcessorContext* const context_;
-
-  SchedEventState sched_event_state_;
-};
-
-}  // namespace trace_processor
-}  // namespace perfetto
-
-#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_FTRACE_FTRACE_SCHED_EVENT_TRACKER_H_
diff --git a/src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.cc b/src/trace_processor/importers/ftrace/sched_event_tracker.cc
similarity index 67%
rename from src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.cc
rename to src/trace_processor/importers/ftrace/sched_event_tracker.cc
index 36bf028..09459c4 100644
--- a/src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.cc
+++ b/src/trace_processor/importers/ftrace/sched_event_tracker.cc
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.h"
+#include "src/trace_processor/importers/ftrace/sched_event_tracker.h"
 
 #include <math.h>
 
@@ -22,11 +22,9 @@
 #include "src/trace_processor/importers/common/args_tracker.h"
 #include "src/trace_processor/importers/common/event_tracker.h"
 #include "src/trace_processor/importers/common/process_tracker.h"
-#include "src/trace_processor/importers/common/sched_event_tracker.h"
-#include "src/trace_processor/importers/common/sched_event_state.h"
 #include "src/trace_processor/importers/common/system_info_tracker.h"
-#include "src/trace_processor/importers/common/thread_state_tracker.h"
 #include "src/trace_processor/importers/ftrace/ftrace_descriptors.h"
+#include "src/trace_processor/importers/ftrace/thread_state_tracker.h"
 #include "src/trace_processor/storage/stats.h"
 #include "src/trace_processor/types/task_state.h"
 #include "src/trace_processor/types/trace_processor_context.h"
@@ -38,8 +36,9 @@
 namespace perfetto {
 namespace trace_processor {
 
-FtraceSchedEventTracker::FtraceSchedEventTracker(TraceProcessorContext* context)
-    : context_(context) {
+SchedEventTracker::SchedEventTracker(TraceProcessorContext* context)
+    : waker_utid_id_(context->storage->InternString("waker_utid")),
+      context_(context) {
   // pre-parse sched_switch
   auto* switch_descriptor = GetMessageDescriptorForId(
       protos::pbzero::FtraceEvent::kSchedSwitchFieldNumber);
@@ -61,11 +60,15 @@
         context->storage->InternString(waking_descriptor->fields[i].name);
   }
   sched_waking_id_ = context->storage->InternString(waking_descriptor->name);
+
+  // Pre-allocate space for 128 CPUs, which should be enough for most hosts.
+  // It's OK if this number is too small, the vector will be grown on-demand.
+  pending_sched_per_cpu_.reserve(128);
 }
 
-FtraceSchedEventTracker::~FtraceSchedEventTracker() = default;
+SchedEventTracker::~SchedEventTracker() = default;
 
-void FtraceSchedEventTracker::PushSchedSwitch(uint32_t cpu,
+void SchedEventTracker::PushSchedSwitch(uint32_t cpu,
                                         int64_t ts,
                                         uint32_t prev_pid,
                                         base::StringView prev_comm,
@@ -74,10 +77,16 @@
                                         uint32_t next_pid,
                                         base::StringView next_comm,
                                         int32_t next_prio) {
-  if (!context_->sched_event_tracker->UpdateEventTrackerTimestamp(ts,
-      "sched_switch",stats::sched_switch_out_of_order)) {
+  // At this stage all events should be globally timestamp ordered.
+  if (ts < context_->event_tracker->max_timestamp()) {
+    PERFETTO_ELOG(
+        "sched_switch event out of order by %.4f ms, skipping",
+        static_cast<double>(context_->event_tracker->max_timestamp() - ts) /
+            1e6);
+    context_->storage->IncrementStats(stats::sched_switch_out_of_order);
     return;
   }
+  context_->event_tracker->UpdateMaxTimestamp(ts);
 
   StringId next_comm_id = context_->storage->InternString(next_comm);
   UniqueTid next_utid = context_->process_tracker->UpdateThreadName(
@@ -85,18 +94,16 @@
 
   // First use this data to close the previous slice.
   bool prev_pid_match_prev_next_pid = false;
-  auto* pending_sched = sched_event_state_.GetPendingSchedInfoForCpu(cpu);
+  auto* pending_sched = PendingSchedByCPU(cpu);
   uint32_t pending_slice_idx = pending_sched->pending_slice_storage_idx;
-  StringId prev_state_string_id = context_->sched_event_tracker
-                                      ->TaskStateToStringId(prev_state);
+  StringId prev_state_string_id = TaskStateToStringId(prev_state);
   if (prev_state_string_id == kNullStringId) {
     context_->storage->IncrementStats(stats::task_state_invalid);
   }
   if (pending_slice_idx < std::numeric_limits<uint32_t>::max()) {
     prev_pid_match_prev_next_pid = prev_pid == pending_sched->last_pid;
     if (PERFETTO_LIKELY(prev_pid_match_prev_next_pid)) {
-      context_->sched_event_tracker->ClosePendingSlice(pending_slice_idx, ts,
-          prev_state_string_id);
+      ClosePendingSlice(pending_slice_idx, ts, prev_state_string_id);
     } else {
       // If the pids are not consistent, make a note of this.
       context_->storage->IncrementStats(stats::mismatched_sched_switch_tids);
@@ -110,11 +117,9 @@
   UniqueTid prev_utid = context_->process_tracker->UpdateThreadName(
       prev_pid, prev_comm_id, ThreadNamePriority::kFtrace);
 
-  AddRawSchedSwitchEvent(cpu, ts, prev_utid, prev_pid, prev_comm_id, prev_prio,
-                         prev_state, next_pid, next_comm_id, next_prio);
-
-  auto new_slice_idx = context_->sched_event_tracker
-                           ->AddStartSlice(cpu, ts, next_utid, next_prio);
+  auto new_slice_idx = AddRawEventAndStartSlice(
+      cpu, ts, prev_utid, prev_pid, prev_comm_id, prev_prio, prev_state,
+      next_utid, next_pid, next_comm_id, next_prio);
 
   // Finally, update the info for the next sched switch on this CPU.
   pending_sched->pending_slice_storage_idx = new_slice_idx;
@@ -127,21 +132,27 @@
       ts, cpu, prev_utid, prev_state_string_id, next_utid);
 }
 
-void FtraceSchedEventTracker::PushSchedSwitchCompact(uint32_t cpu,
-                                                     int64_t ts,
-                                                     int64_t prev_state,
-                                                     uint32_t next_pid,
-                                                     int32_t next_prio,
-                                                     StringId next_comm_id) {
-  if (!context_->sched_event_tracker->UpdateEventTrackerTimestamp(ts, 
-      "sched_switch", stats::sched_switch_out_of_order)) {
+void SchedEventTracker::PushSchedSwitchCompact(uint32_t cpu,
+                                               int64_t ts,
+                                               int64_t prev_state,
+                                               uint32_t next_pid,
+                                               int32_t next_prio,
+                                               StringId next_comm_id) {
+  // At this stage all events should be globally timestamp ordered.
+  if (ts < context_->event_tracker->max_timestamp()) {
+    PERFETTO_ELOG(
+        "sched_switch event out of order by %.4f ms, skipping",
+        static_cast<double>(context_->event_tracker->max_timestamp() - ts) /
+            1e6);
+    context_->storage->IncrementStats(stats::sched_switch_out_of_order);
     return;
   }
+  context_->event_tracker->UpdateMaxTimestamp(ts);
 
   UniqueTid next_utid = context_->process_tracker->UpdateThreadName(
       next_pid, next_comm_id, ThreadNamePriority::kFtrace);
 
-  auto* pending_sched = sched_event_state_.GetPendingSchedInfoForCpu(cpu);
+  auto* pending_sched = PendingSchedByCPU(cpu);
 
   // If we're processing the first compact event for this cpu, don't start a
   // slice since we're missing the "prev_*" fields. The successive events will
@@ -161,14 +172,12 @@
   // Close the pending slice if any (we won't have one when processing the first
   // two compact events for a given cpu).
   uint32_t pending_slice_idx = pending_sched->pending_slice_storage_idx;
-  StringId prev_state_string_id = context_->sched_event_tracker
-                                      ->TaskStateToStringId(prev_state);
+  StringId prev_state_string_id = TaskStateToStringId(prev_state);
   if (prev_state_string_id == kNullStringId) {
     context_->storage->IncrementStats(stats::task_state_invalid);
   }
   if (pending_slice_idx < std::numeric_limits<uint32_t>::max())
-    context_->sched_event_tracker->ClosePendingSlice(pending_slice_idx, ts,
-        prev_state_string_id);
+    ClosePendingSlice(pending_slice_idx, ts, prev_state_string_id);
 
   // Use the previous event's values to infer this event's "prev_*" fields.
   // There are edge cases, but this assumption should still produce sensible
@@ -183,10 +192,9 @@
       context_->storage->thread_table().name()[prev_utid].value_or(
           kNullStringId);
 
-  AddRawSchedSwitchEvent(cpu, ts, prev_utid, prev_pid, prev_comm_id, prev_prio,
-      prev_state, next_pid, next_comm_id, next_prio);
-  auto new_slice_idx = context_->sched_event_tracker
-                           ->AddStartSlice(cpu, ts, next_utid, next_prio);
+  auto new_slice_idx = AddRawEventAndStartSlice(
+      cpu, ts, prev_utid, prev_pid, prev_comm_id, prev_prio, prev_state,
+      next_utid, next_pid, next_comm_id, next_prio);
 
   // Finally, update the info for the next sched switch on this CPU.
   pending_sched->pending_slice_storage_idx = new_slice_idx;
@@ -201,24 +209,30 @@
 
 // Processes a sched_waking that was decoded from a compact representation,
 // adding to the raw and instants tables.
-void FtraceSchedEventTracker::PushSchedWakingCompact(uint32_t cpu,
-                                                     int64_t ts,
-                                                     uint32_t wakee_pid,
-                                                     uint16_t target_cpu,
-                                                     uint16_t prio,
-                                                     StringId comm_id,
-                                                     uint16_t common_flags) {
-  if (!context_->sched_event_tracker->UpdateEventTrackerTimestamp(ts,
-      "sched_waking", stats::sched_waking_out_of_order)) {
+void SchedEventTracker::PushSchedWakingCompact(uint32_t cpu,
+                                               int64_t ts,
+                                               uint32_t wakee_pid,
+                                               uint16_t target_cpu,
+                                               uint16_t prio,
+                                               StringId comm_id,
+                                               uint16_t common_flags) {
+  // At this stage all events should be globally timestamp ordered.
+  if (ts < context_->event_tracker->max_timestamp()) {
+    PERFETTO_ELOG(
+        "sched_waking event out of order by %.4f ms, skipping",
+        static_cast<double>(context_->event_tracker->max_timestamp() - ts) /
+            1e6);
+    context_->storage->IncrementStats(stats::sched_waking_out_of_order);
     return;
   }
+  context_->event_tracker->UpdateMaxTimestamp(ts);
 
   // We infer the task that emitted the event (i.e. common_pid) from the
   // scheduling slices. Drop the event if we haven't seen any sched_switch
   // events for this cpu yet.
   // Note that if sched_switch wasn't enabled, we will have to skip all
   // compact waking events.
-  auto* pending_sched = sched_event_state_.GetPendingSchedInfoForCpu(cpu);
+  auto* pending_sched = PendingSchedByCPU(cpu);
   if (pending_sched->last_utid == std::numeric_limits<UniqueTid>::max()) {
     context_->storage->IncrementStats(stats::compact_sched_waking_skipped);
     return;
@@ -255,13 +269,14 @@
 }
 
 PERFETTO_ALWAYS_INLINE
-void FtraceSchedEventTracker::AddRawSchedSwitchEvent(uint32_t cpu,
+uint32_t SchedEventTracker::AddRawEventAndStartSlice(uint32_t cpu,
                                                      int64_t ts,
                                                      UniqueTid prev_utid,
                                                      uint32_t prev_pid,
                                                      StringId prev_comm_id,
                                                      int32_t prev_prio,
                                                      int64_t prev_state,
+                                                     UniqueTid next_utid,
                                                      uint32_t next_pid,
                                                      StringId next_comm_id,
                                                      int32_t next_prio) {
@@ -290,6 +305,42 @@
     add_raw_arg(SS::kNextPidFieldNumber, Variadic::Integer(next_pid));
     add_raw_arg(SS::kNextPrioFieldNumber, Variadic::Integer(next_prio));
   }
+
+  // Open a new scheduling slice, corresponding to the task that was
+  // just switched to. Set the duration to -1, to indicate that the event is not
+  // finished. Duration will be updated later after event finish.
+  auto* sched = context_->storage->mutable_sched_slice_table();
+  auto row_and_id = sched->Insert(
+      {ts, /* duration */ -1, cpu, next_utid, kNullStringId, next_prio});
+  SchedId sched_id = row_and_id.id;
+  return *sched->id().IndexOf(sched_id);
+}
+
+StringId SchedEventTracker::TaskStateToStringId(int64_t task_state_int) {
+  using ftrace_utils::TaskState;
+
+  std::optional<VersionNumber> kernel_version =
+      SystemInfoTracker::GetOrCreate(context_)->GetKernelVersion();
+  TaskState task_state = TaskState::FromRawPrevState(
+      static_cast<uint16_t>(task_state_int), kernel_version);
+  return task_state.is_valid()
+             ? context_->storage->InternString(task_state.ToString().data())
+             : kNullStringId;
+}
+
+PERFETTO_ALWAYS_INLINE
+void SchedEventTracker::ClosePendingSlice(uint32_t pending_slice_idx,
+                                          int64_t ts,
+                                          StringId prev_state) {
+  auto* slices = context_->storage->mutable_sched_slice_table();
+
+  int64_t duration = ts - slices->ts()[pending_slice_idx];
+  slices->mutable_dur()->Set(pending_slice_idx, duration);
+
+  // We store the state as a uint16 as we only consider values up to 2048
+  // when unpacking the information inside; this allows savings of 48 bits
+  // per slice.
+  slices->mutable_end_state()->Set(pending_slice_idx, prev_state);
 }
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/importers/ftrace/sched_event_tracker.h b/src/trace_processor/importers/ftrace/sched_event_tracker.h
new file mode 100644
index 0000000..745b7c3
--- /dev/null
+++ b/src/trace_processor/importers/ftrace/sched_event_tracker.h
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_FTRACE_SCHED_EVENT_TRACKER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_FTRACE_SCHED_EVENT_TRACKER_H_
+
+#include <array>
+#include <limits>
+
+#include "perfetto/ext/base/string_view.h"
+#include "perfetto/ext/base/utils.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/types/destructible.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+class EventTracker;
+
+// Tracks sched events and stores them into the storage as sched slices.
+class SchedEventTracker : public Destructible {
+ public:
+  // Declared public for testing only.
+  explicit SchedEventTracker(TraceProcessorContext*);
+  SchedEventTracker(const SchedEventTracker&) = delete;
+  SchedEventTracker& operator=(const SchedEventTracker&) = delete;
+  ~SchedEventTracker() override;
+  static SchedEventTracker* GetOrCreate(TraceProcessorContext* context) {
+    if (!context->sched_tracker) {
+      context->sched_tracker.reset(new SchedEventTracker(context));
+    }
+    return static_cast<SchedEventTracker*>(context->sched_tracker.get());
+  }
+
+  // This method is called when a sched_switch event is seen in the trace.
+  // Virtual for testing.
+  virtual void PushSchedSwitch(uint32_t cpu,
+                               int64_t timestamp,
+                               uint32_t prev_pid,
+                               base::StringView prev_comm,
+                               int32_t prev_prio,
+                               int64_t prev_state,
+                               uint32_t next_pid,
+                               base::StringView next_comm,
+                               int32_t next_prio);
+
+  // This method is called when parsing a sched_switch encoded in the compact
+  // format.
+  void PushSchedSwitchCompact(uint32_t cpu,
+                              int64_t ts,
+                              int64_t prev_state,
+                              uint32_t next_pid,
+                              int32_t next_prio,
+                              StringId next_comm_id);
+
+  // This method is called when parsing a sched_waking encoded in the compact
+  // format. Note that the default encoding is handled by
+  // |EventTracker::PushInstant|.
+  void PushSchedWakingCompact(uint32_t cpu,
+                              int64_t ts,
+                              uint32_t wakee_pid,
+                              uint16_t target_cpu,
+                              uint16_t prio,
+                              StringId comm_id,
+                              uint16_t common_flags);
+
+ private:
+  // Information retained from the preceding sched_switch seen on a given cpu.
+  struct PendingSchedInfo {
+    // The pending scheduling slice that the next event will complete.
+    uint32_t pending_slice_storage_idx = std::numeric_limits<uint32_t>::max();
+
+    // pid/utid/prio corresponding to the last sched_switch seen on this cpu
+    // (its "next_*" fields). There is some duplication with respect to the
+    // slices storage, but we don't always have a slice when decoding events in
+    // the compact format.
+    uint32_t last_pid = std::numeric_limits<uint32_t>::max();
+    UniqueTid last_utid = std::numeric_limits<UniqueTid>::max();
+    int32_t last_prio = std::numeric_limits<int32_t>::max();
+  };
+
+  uint32_t AddRawEventAndStartSlice(uint32_t cpu,
+                                    int64_t ts,
+                                    UniqueTid prev_utid,
+                                    uint32_t prev_pid,
+                                    StringId prev_comm_id,
+                                    int32_t prev_prio,
+                                    int64_t prev_state,
+                                    UniqueTid next_utid,
+                                    uint32_t next_pid,
+                                    StringId next_comm_id,
+                                    int32_t next_prio);
+
+  StringId TaskStateToStringId(int64_t task_state);
+
+  void ClosePendingSlice(uint32_t slice_idx, int64_t ts, StringId prev_state);
+
+  // Information retained from the preceding sched_switch seen on a given cpu.
+  std::vector<PendingSchedInfo> pending_sched_per_cpu_;
+
+  // Get the sched info for the given CPU, resizing the vector if necessary.
+  PendingSchedInfo* PendingSchedByCPU(uint32_t cpu) {
+    if (PERFETTO_UNLIKELY(cpu >= pending_sched_per_cpu_.size())) {
+      pending_sched_per_cpu_.resize(cpu + 1);
+    }
+    return &pending_sched_per_cpu_[cpu];
+  }
+
+  static constexpr uint8_t kSchedSwitchMaxFieldId = 7;
+  std::array<StringId, kSchedSwitchMaxFieldId + 1> sched_switch_field_ids_;
+  StringId sched_switch_id_;
+
+  static constexpr uint8_t kSchedWakingMaxFieldId = 5;
+  std::array<StringId, kSchedWakingMaxFieldId + 1> sched_waking_field_ids_;
+  StringId sched_waking_id_;
+
+  StringId waker_utid_id_;
+
+  TraceProcessorContext* const context_;
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_FTRACE_SCHED_EVENT_TRACKER_H_
diff --git a/src/trace_processor/importers/ftrace/ftrace_sched_event_tracker_unittest.cc b/src/trace_processor/importers/ftrace/sched_event_tracker_unittest.cc
similarity index 93%
rename from src/trace_processor/importers/ftrace/ftrace_sched_event_tracker_unittest.cc
rename to src/trace_processor/importers/ftrace/sched_event_tracker_unittest.cc
index a085078..5d275d6 100644
--- a/src/trace_processor/importers/ftrace/ftrace_sched_event_tracker_unittest.cc
+++ b/src/trace_processor/importers/ftrace/sched_event_tracker_unittest.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,12 +14,11 @@
  * limitations under the License.
  */
 
-#include "src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.h"
+#include "src/trace_processor/importers/ftrace/sched_event_tracker.h"
 
 #include "perfetto/base/logging.h"
 #include "src/trace_processor/importers/common/args_tracker.h"
 #include "src/trace_processor/importers/common/event_tracker.h"
-#include "src/trace_processor/importers/common/sched_event_tracker.h"
 #include "src/trace_processor/importers/common/process_tracker.h"
 #include "test/gtest_and_gmock.h"
 
@@ -40,13 +39,12 @@
     context.args_tracker.reset(new ArgsTracker(&context));
     context.event_tracker.reset(new EventTracker(&context));
     context.process_tracker.reset(new ProcessTracker(&context));
-    context.sched_event_tracker.reset(new SchedEventTracker(&context));
-    sched_tracker = FtraceSchedEventTracker::GetOrCreate(&context);
+    sched_tracker = SchedEventTracker::GetOrCreate(&context);
   }
 
  protected:
   TraceProcessorContext context;
-  FtraceSchedEventTracker* sched_tracker;
+  SchedEventTracker* sched_tracker;
 };
 
 TEST_F(SchedEventTrackerTest, InsertSecondSched) {
diff --git a/src/trace_processor/importers/common/thread_state_tracker.cc b/src/trace_processor/importers/ftrace/thread_state_tracker.cc
similarity index 98%
rename from src/trace_processor/importers/common/thread_state_tracker.cc
rename to src/trace_processor/importers/ftrace/thread_state_tracker.cc
index a2daecd..7eda59e 100644
--- a/src/trace_processor/importers/common/thread_state_tracker.cc
+++ b/src/trace_processor/importers/ftrace/thread_state_tracker.cc
@@ -13,8 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-#include "src/trace_processor/importers/common/thread_state_tracker.h"
+#include "src/trace_processor/importers/ftrace/thread_state_tracker.h"
 #include <optional>
 
 namespace perfetto {
diff --git a/src/trace_processor/importers/common/thread_state_tracker.h b/src/trace_processor/importers/ftrace/thread_state_tracker.h
similarity index 95%
rename from src/trace_processor/importers/common/thread_state_tracker.h
rename to src/trace_processor/importers/ftrace/thread_state_tracker.h
index 2b7206b..839ad31 100644
--- a/src/trace_processor/importers/common/thread_state_tracker.h
+++ b/src/trace_processor/importers/ftrace/thread_state_tracker.h
@@ -13,9 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_THREAD_STATE_TRACKER_H_
-#define SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_THREAD_STATE_TRACKER_H_
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_FTRACE_THREAD_STATE_TRACKER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_FTRACE_THREAD_STATE_TRACKER_H_
 
 #include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/types/destructible.h"
@@ -105,4 +104,4 @@
 }  // namespace trace_processor
 }  // namespace perfetto
 
-#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_THREAD_STATE_TRACKER_H_
+#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_FTRACE_THREAD_STATE_TRACKER_H_
diff --git a/src/trace_processor/importers/common/thread_state_tracker_unittest.cc b/src/trace_processor/importers/ftrace/thread_state_tracker_unittest.cc
similarity index 98%
rename from src/trace_processor/importers/common/thread_state_tracker_unittest.cc
rename to src/trace_processor/importers/ftrace/thread_state_tracker_unittest.cc
index ea6794a..1b0e1a3 100644
--- a/src/trace_processor/importers/common/thread_state_tracker_unittest.cc
+++ b/src/trace_processor/importers/ftrace/thread_state_tracker_unittest.cc
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "src/trace_processor/importers/common/thread_state_tracker.h"
+#include "src/trace_processor/importers/ftrace/thread_state_tracker.h"
 
 #include <algorithm>
 
diff --git a/src/trace_processor/importers/fuchsia/fuchsia_parser_unittest.cc b/src/trace_processor/importers/fuchsia/fuchsia_parser_unittest.cc
index 9f90bed..d644742 100644
--- a/src/trace_processor/importers/fuchsia/fuchsia_parser_unittest.cc
+++ b/src/trace_processor/importers/fuchsia/fuchsia_parser_unittest.cc
@@ -31,7 +31,7 @@
 #include "src/trace_processor/importers/common/slice_tracker.h"
 #include "src/trace_processor/importers/common/stack_profile_tracker.h"
 #include "src/trace_processor/importers/common/track_tracker.h"
-#include "src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.h"
+#include "src/trace_processor/importers/ftrace/sched_event_tracker.h"
 #include "src/trace_processor/importers/proto/additional_modules.h"
 #include "src/trace_processor/importers/proto/default_modules.h"
 #include "src/trace_processor/importers/proto/proto_trace_parser.h"
@@ -91,10 +91,10 @@
 using ::testing::Return;
 using ::testing::ReturnRef;
 using ::testing::UnorderedElementsAreArray;
-class MockSchedEventTracker : public FtraceSchedEventTracker {
+class MockSchedEventTracker : public SchedEventTracker {
  public:
   explicit MockSchedEventTracker(TraceProcessorContext* context)
-      : FtraceSchedEventTracker(context) {}
+      : SchedEventTracker(context) {}
 
   MOCK_METHOD(void,
               PushSchedSwitch,
@@ -244,7 +244,7 @@
     event_ = new MockEventTracker(&context_);
     context_.event_tracker.reset(event_);
     sched_ = new MockSchedEventTracker(&context_);
-    context_.ftrace_sched_tracker.reset(sched_);
+    context_.sched_tracker.reset(sched_);
     process_ = new NiceMock<MockProcessTracker>(&context_);
     context_.process_tracker.reset(process_);
     slice_ = new NiceMock<MockSliceTracker>(&context_);
diff --git a/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc b/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
index 11a45f5..148bf60 100644
--- a/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
+++ b/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
@@ -30,7 +30,7 @@
 #include "src/trace_processor/importers/common/slice_tracker.h"
 #include "src/trace_processor/importers/common/stack_profile_tracker.h"
 #include "src/trace_processor/importers/common/track_tracker.h"
-#include "src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.h"
+#include "src/trace_processor/importers/ftrace/sched_event_tracker.h"
 #include "src/trace_processor/importers/proto/additional_modules.h"
 #include "src/trace_processor/importers/proto/default_modules.h"
 #include "src/trace_processor/importers/proto/proto_trace_parser.h"
@@ -106,10 +106,10 @@
 }
 }  // namespace
 
-class MockSchedEventTracker : public FtraceSchedEventTracker {
+class MockSchedEventTracker : public SchedEventTracker {
  public:
   explicit MockSchedEventTracker(TraceProcessorContext* context)
-      : FtraceSchedEventTracker(context) {}
+      : SchedEventTracker(context) {}
 
   MOCK_METHOD(void,
               PushSchedSwitch,
@@ -260,7 +260,7 @@
     event_ = new MockEventTracker(&context_);
     context_.event_tracker.reset(event_);
     sched_ = new MockSchedEventTracker(&context_);
-    context_.ftrace_sched_tracker.reset(sched_);
+    context_.sched_tracker.reset(sched_);
     process_ = new NiceMock<MockProcessTracker>(&context_);
     context_.process_tracker.reset(process_);
     slice_ = new NiceMock<MockSliceTracker>(&context_);
diff --git a/src/trace_processor/importers/systrace/systrace_line_parser.cc b/src/trace_processor/importers/systrace/systrace_line_parser.cc
index 7bfb844..ccf711e 100644
--- a/src/trace_processor/importers/systrace/systrace_line_parser.cc
+++ b/src/trace_processor/importers/systrace/systrace_line_parser.cc
@@ -23,10 +23,10 @@
 #include "src/trace_processor/importers/common/event_tracker.h"
 #include "src/trace_processor/importers/common/process_tracker.h"
 #include "src/trace_processor/importers/common/slice_tracker.h"
-#include "src/trace_processor/importers/common/thread_state_tracker.h"
 #include "src/trace_processor/importers/common/track_tracker.h"
 #include "src/trace_processor/importers/ftrace/binder_tracker.h"
-#include "src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.h"
+#include "src/trace_processor/importers/ftrace/sched_event_tracker.h"
+#include "src/trace_processor/importers/ftrace/thread_state_tracker.h"
 #include "src/trace_processor/importers/systrace/systrace_parser.h"
 #include "src/trace_processor/types/task_state.h"
 
@@ -107,7 +107,7 @@
       return util::Status("Could not parse sched_switch");
     }
 
-    FtraceSchedEventTracker::GetOrCreate(context_)->PushSchedSwitch(
+    SchedEventTracker::GetOrCreate(context_)->PushSchedSwitch(
         line.cpu, line.ts, prev_pid.value(), prev_comm, prev_prio.value(),
         prev_state, next_pid.value(), next_comm, next_prio.value());
   } else if (line.event_name == "tracing_mark_write" ||
diff --git a/src/trace_processor/perfetto_sql/engine/BUILD.gn b/src/trace_processor/perfetto_sql/engine/BUILD.gn
index 122d576..6a3baf1 100644
--- a/src/trace_processor/perfetto_sql/engine/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/engine/BUILD.gn
@@ -35,7 +35,10 @@
     "../..:metatrace",
     "../../../../gn:default_deps",
     "../../../../gn:sqlite",
+    "../../../../include/perfetto/trace_processor:basic_types",
+    "../../../../protos/perfetto/trace_processor:zero",
     "../../../base",
+    "../../containers",
     "../../db",
     "../../perfetto_sql/intrinsics/functions:interface",
     "../../perfetto_sql/intrinsics/table_functions:interface",
diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc
index e29933b..86d96bc 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc
@@ -16,12 +16,16 @@
 
 #include "src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h"
 
+#include <algorithm>
+#include <array>
 #include <cctype>
 #include <cstddef>
 #include <cstdint>
+#include <cstring>
 #include <memory>
 #include <optional>
 #include <string>
+#include <string_view>
 #include <utility>
 #include <variant>
 #include <vector>
@@ -31,6 +35,7 @@
 #include "perfetto/ext/base/status_or.h"
 #include "perfetto/ext/base/string_utils.h"
 #include "perfetto/ext/base/string_view.h"
+#include "src/trace_processor/containers/string_pool.h"
 #include "src/trace_processor/db/runtime_table.h"
 #include "src/trace_processor/db/table.h"
 #include "src/trace_processor/perfetto_sql/engine/created_function.h"
@@ -40,13 +45,18 @@
 #include "src/trace_processor/perfetto_sql/engine/runtime_table_function.h"
 #include "src/trace_processor/perfetto_sql/intrinsics/table_functions/static_table_function.h"
 #include "src/trace_processor/sqlite/db_sqlite_table.h"
+#include "src/trace_processor/sqlite/query_cache.h"
 #include "src/trace_processor/sqlite/scoped_db.h"
 #include "src/trace_processor/sqlite/sql_source.h"
 #include "src/trace_processor/sqlite/sqlite_engine.h"
 #include "src/trace_processor/sqlite/sqlite_table.h"
 #include "src/trace_processor/tp_metatrace.h"
+#include "src/trace_processor/util/sql_argument.h"
+#include "src/trace_processor/util/sql_modules.h"
 #include "src/trace_processor/util/status_macros.h"
 
+#include "protos/perfetto/trace_processor/metatrace_categories.pbzero.h"
+
 // Implementation details
 // ----------------------
 //
@@ -140,18 +150,18 @@
 });
 
 bool IsTokenAllowedInMacro(const std::string& view) {
-  for (const char* allowed_token : kTokensAllowedInMacro) {
-    if (base::ToLower(view) == base::ToLower(allowed_token)) {
-      return true;
-    }
-  }
-  return false;
+  std::string lower = base::ToLower(view);
+  return std::any_of(kTokensAllowedInMacro.begin(), kTokensAllowedInMacro.end(),
+                     [&lower](const std::string& allowed_token) {
+                       return lower == base::ToLower(allowed_token);
+                     });
 }
 
 std::string GetTokenNamesAllowedInMacro() {
   std::vector<std::string> result;
+  result.reserve(kTokensAllowedInMacro.size());
   for (const char* token : kTokensAllowedInMacro) {
-    result.push_back(token);
+    result.emplace_back(token);
   }
   return base::Join(result, ", ");
 }
@@ -177,7 +187,7 @@
   auto context = std::make_unique<DbSqliteTable::Context>(
       query_cache_.get(),
       [this](const std::string& name) {
-        auto table = runtime_tables_.Find(name);
+        auto* table = runtime_tables_.Find(name);
         PERFETTO_CHECK(table);
         return table->get();
       },
@@ -270,10 +280,9 @@
     std::optional<SqlSource> source;
     if (auto* cf = std::get_if<PerfettoSqlParser::CreateFunction>(
             &parser.statement())) {
-      auto source_or = ExecuteCreateFunction(*cf, parser);
-      RETURN_IF_ERROR(
-          AddTracebackIfNeeded(source_or.status(), parser.statement_sql()));
-      source = std::move(source_or.value());
+      RETURN_IF_ERROR(AddTracebackIfNeeded(ExecuteCreateFunction(*cf),
+                                           parser.statement_sql()));
+      source = RewriteToDummySql(parser.statement_sql());
     } else if (auto* cst = std::get_if<PerfettoSqlParser::CreateTable>(
                    &parser.statement())) {
       RETURN_IF_ERROR(AddTracebackIfNeeded(ExecuteCreateTable(*cst),
@@ -368,7 +377,7 @@
 base::Status PerfettoSqlEngine::RegisterRuntimeFunction(
     bool replace,
     const FunctionPrototype& prototype,
-    std::string return_type_str,
+    const std::string& return_type_str,
     SqlSource sql) {
   // Parse the return type into a enum format.
   auto opt_return_type =
@@ -403,8 +412,8 @@
         std::move(created_fn_ctx)));
     runtime_function_count_++;
   }
-  return CreatedFunction::Prepare(ctx, std::move(prototype),
-                                  std::move(*opt_return_type), std::move(sql));
+  return CreatedFunction::Prepare(ctx, prototype, *opt_return_type,
+                                  std::move(sql));
 }
 
 base::Status PerfettoSqlEngine::ExecuteCreateTable(
@@ -468,6 +477,13 @@
   }
   ASSIGN_OR_RETURN(auto table, std::move(builder).Build(rows));
 
+  // TODO(lalitm): unfortunately, in the (very unlikely) event that there is a
+  // sqlite3_interrupt call between the DROP and CREATE, we can end up with the
+  // non-atomic query execution. Fixing this is extremely difficult as it
+  // involves telling SQLite that we want the drop/create to be atomic.
+  //
+  // We would need to do with the transaction API but given we have no usage of
+  // this until now, investigating that needs some proper work.
   if (runtime_tables_.Find(create_table.name)) {
     if (!create_table.replace) {
       return base::ErrStatus("CREATE PERFETTO TABLE: table '%s' already exists",
@@ -483,9 +499,15 @@
   runtime_tables_.Insert(create_table.name, std::move(table));
   base::StackString<1024> create("CREATE VIRTUAL TABLE %s USING runtime_table",
                                  create_table.name.c_str());
-  return Execute(
-             SqlSource::FromTraceProcessorImplementation(create.ToStdString()))
-      .status();
+  auto status =
+      Execute(SqlSource::FromTraceProcessorImplementation(create.ToStdString()))
+          .status();
+  if (!status.ok()) {
+    // If the registration of the table with SQLite failed, erase the state
+    // we hold.
+    PERFETTO_CHECK(runtime_tables_.Erase(create_table.name));
+  }
+  return status;
 }
 
 base::Status PerfettoSqlEngine::ExecuteCreateView(
@@ -520,8 +542,8 @@
 base::Status PerfettoSqlEngine::EnableSqlFunctionMemoization(
     const std::string& name) {
   constexpr size_t kSupportedArgCount = 1;
-  CreatedFunction::Context* ctx = static_cast<CreatedFunction::Context*>(
-      sqlite_engine()->GetFunctionContext(name.c_str(), kSupportedArgCount));
+  auto* ctx = static_cast<CreatedFunction::Context*>(
+      sqlite_engine()->GetFunctionContext(name, kSupportedArgCount));
   if (!ctx) {
     return base::ErrStatus(
         "EXPERIMENTAL_MEMOIZE: Function %s(INT) does not exist", name.c_str());
@@ -544,7 +566,7 @@
   }
 
   std::string module_name = sql_modules::GetModuleName(key);
-  auto module = FindModule(module_name);
+  auto* module = FindModule(module_name);
   if (!module) {
     return base::ErrStatus("INCLUDE: Unknown module name provided - %s",
                            key.c_str());
@@ -600,25 +622,26 @@
   return base::OkStatus();
 }
 
-base::StatusOr<SqlSource> PerfettoSqlEngine::ExecuteCreateFunction(
-    const PerfettoSqlParser::CreateFunction& cf,
-    const PerfettoSqlParser& parser) {
+base::Status PerfettoSqlEngine::ExecuteCreateFunction(
+    const PerfettoSqlParser::CreateFunction& cf) {
   if (!cf.is_table) {
-    RETURN_IF_ERROR(
-        RegisterRuntimeFunction(cf.replace, cf.prototype, cf.returns, cf.sql));
-    return RewriteToDummySql(parser.statement_sql());
+    return RegisterRuntimeFunction(cf.replace, cf.prototype, cf.returns,
+                                   cf.sql);
   }
 
-  RuntimeTableFunction::State state{cf.sql, cf.prototype, {}, std::nullopt};
+  std::unique_ptr<RuntimeTableFunction::State> state(
+      new RuntimeTableFunction::State{cf.sql, cf.prototype, {}, std::nullopt});
 
   // Parse the return type into a enum format.
-  base::Status status =
-      sql_argument::ParseArgumentDefinitions(cf.returns, state.return_values);
-  if (!status.ok()) {
-    return base::ErrStatus(
-        "CREATE PERFETTO FUNCTION[prototype=%s, return=%s]: unknown return "
-        "type specified",
-        state.prototype.ToString().c_str(), cf.returns.c_str());
+  {
+    base::Status status = sql_argument::ParseArgumentDefinitions(
+        cf.returns, state->return_values);
+    if (!status.ok()) {
+      return base::ErrStatus(
+          "CREATE PERFETTO FUNCTION[prototype=%s, return=%s]: unknown return "
+          "type specified",
+          state->prototype.ToString().c_str(), cf.returns.c_str());
+    }
   }
 
   // Verify that the provided SQL prepares to a statement correctly.
@@ -638,7 +661,7 @@
       return base::ErrStatus(
           "%s: \"Nameless\" SQL parameters cannot be used in the SQL "
           "statements of view functions.",
-          state.prototype.function_name.c_str());
+          state->prototype.function_name.c_str());
     }
 
     if (!base::StringView(name).StartsWith("$")) {
@@ -646,71 +669,81 @@
           "%s: invalid parameter name %s used in the SQL definition of "
           "the view function: all parameters must be prefixed with '$' not "
           "':' or '@'.",
-          state.prototype.function_name.c_str(), name);
+          state->prototype.function_name.c_str(), name);
     }
 
-    auto it = std::find_if(state.prototype.arguments.begin(),
-                           state.prototype.arguments.end(),
+    auto it = std::find_if(state->prototype.arguments.begin(),
+                           state->prototype.arguments.end(),
                            [name](const sql_argument::ArgumentDefinition& arg) {
                              return arg.dollar_name() == name;
                            });
-    if (it == state.prototype.arguments.end()) {
+    if (it == state->prototype.arguments.end()) {
       return base::ErrStatus(
           "%s: parameter %s does not appear in the list of arguments in the "
           "prototype of the view function.",
-          state.prototype.function_name.c_str(), name);
+          state->prototype.function_name.c_str(), name);
     }
   }
 
   // Verify that the prepared statement column count matches the return
   // count.
-  uint32_t col_count =
+  auto col_count =
       static_cast<uint32_t>(sqlite3_column_count(stmt.sqlite_stmt()));
-  if (col_count != state.return_values.size()) {
+  if (col_count != state->return_values.size()) {
     return base::ErrStatus(
         "%s: number of return values %u does not match SQL statement column "
         "count %zu.",
-        state.prototype.function_name.c_str(), col_count,
-        state.return_values.size());
+        state->prototype.function_name.c_str(), col_count,
+        state->return_values.size());
   }
 
   // Verify that the return names matches the prepared statment column names.
   for (uint32_t i = 0; i < col_count; ++i) {
     const char* name =
         sqlite3_column_name(stmt.sqlite_stmt(), static_cast<int>(i));
-    if (name != state.return_values[i].name()) {
+    if (name != state->return_values[i].name()) {
       return base::ErrStatus(
           "%s: column %s at index %u does not match return value name %s.",
-          state.prototype.function_name.c_str(), name, i,
-          state.return_values[i].name().c_str());
+          state->prototype.function_name.c_str(), name, i,
+          state->return_values[i].name().c_str());
     }
   }
-  state.reusable_stmt = std::move(stmt);
+  state->reusable_stmt = std::move(stmt);
 
-  std::string fn_name = state.prototype.function_name;
-  std::string lower_name = base::ToLower(state.prototype.function_name);
+  // TODO(lalitm): this suffers the same non-atomic DROP/CREATE problem as
+  // CREATE PERFETTO TABLE implementation above: see the comment there for
+  // more info on this.
+  std::string fn_name = state->prototype.function_name;
+  std::string lower_name = base::ToLower(state->prototype.function_name);
   if (runtime_table_fn_states_.Find(lower_name)) {
     if (!cf.replace) {
       return base::ErrStatus("Table function named %s already exists",
-                             state.prototype.function_name.c_str());
+                             state->prototype.function_name.c_str());
     }
     // This will cause |OnTableFunctionDestroyed| below to be executed.
     base::StackString<1024> drop("DROP TABLE %s",
-                                 state.prototype.function_name.c_str());
+                                 state->prototype.function_name.c_str());
     auto res = Execute(
         SqlSource::FromTraceProcessorImplementation(drop.ToStdString()));
     RETURN_IF_ERROR(res.status());
   }
 
-  auto it_and_inserted = runtime_table_fn_states_.Insert(
-      lower_name,
-      std::make_unique<RuntimeTableFunction::State>(std::move(state)));
+  auto it_and_inserted =
+      runtime_table_fn_states_.Insert(lower_name, std::move(state));
   PERFETTO_CHECK(it_and_inserted.second);
 
   base::StackString<1024> create(
       "CREATE VIRTUAL TABLE %s USING runtime_table_function", fn_name.c_str());
-  return cf.sql.RewriteAllIgnoreExisting(
-      SqlSource::FromTraceProcessorImplementation(create.ToStdString()));
+  auto status = Execute(cf.sql.RewriteAllIgnoreExisting(
+                            SqlSource::FromTraceProcessorImplementation(
+                                create.ToStdString())))
+                    .status();
+  if (!status.ok()) {
+    // If the registration of the table with SQLite failed, erase the state
+    // we hold.
+    PERFETTO_CHECK(runtime_table_fn_states_.Erase(lower_name));
+  }
+  return status;
 }
 
 base::Status PerfettoSqlEngine::ExecuteCreateMacro(
@@ -736,6 +769,7 @@
   }
 
   std::vector<std::string> args;
+  args.reserve(create_macro.args.size());
   for (const auto& arg : create_macro.args) {
     args.push_back(arg.first.sql());
   }
@@ -745,7 +779,7 @@
       std::move(args),
       create_macro.sql,
   };
-  if (auto it = macros_.Find(create_macro.name.sql()); it) {
+  if (auto* it = macros_.Find(create_macro.name.sql()); it) {
     if (!create_macro.replace) {
       // TODO(lalitm): add a link to create macro documentation.
       return base::ErrStatus("%sMacro already exists",
@@ -762,7 +796,7 @@
 
 RuntimeTableFunction::State* PerfettoSqlEngine::GetRuntimeTableFunctionState(
     const std::string& name) const {
-  auto it = runtime_table_fn_states_.Find(base::ToLower(name));
+  auto* it = runtime_table_fn_states_.Find(base::ToLower(name));
   PERFETTO_CHECK(it);
   return it->get();
 }
@@ -775,8 +809,8 @@
 base::StatusOr<std::vector<std::string>>
 PerfettoSqlEngine::GetColumnNamesFromSelectStatement(
     const SqliteEngine::PreparedStatement& stmt,
-    const char* tag) const {
-  uint32_t columns =
+    const char* tag) {
+  auto columns =
       static_cast<uint32_t>(sqlite3_column_count(stmt.sqlite_stmt()));
   std::vector<std::string> column_names;
   for (uint32_t i = 0; i < columns; ++i) {
@@ -804,7 +838,7 @@
 base::Status PerfettoSqlEngine::ValidateColumnNames(
     const std::vector<std::string>& column_names,
     const std::vector<sql_argument::ArgumentDefinition>& schema,
-    const char* tag) const {
+    const char* tag) {
   // If the user has not provided a schema, we have nothing to validate.
   if (schema.empty()) {
     return base::OkStatus();
@@ -860,13 +894,13 @@
 
 const RuntimeTable* PerfettoSqlEngine::GetRuntimeTableOrNull(
     std::string_view name) const {
-  auto table_ptr = runtime_tables_.Find(name.data());
+  auto* table_ptr = runtime_tables_.Find(name.data());
   return table_ptr ? table_ptr->get() : nullptr;
 }
 
 const Table* PerfettoSqlEngine::GetStaticTableOrNull(
     std::string_view name) const {
-  auto table_ptr = static_tables_.Find(name.data());
+  auto* table_ptr = static_tables_.Find(name.data());
   return table_ptr ? *table_ptr : nullptr;
 }
 
diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h
index 60772ec..69e6b58 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h
@@ -19,24 +19,30 @@
 
 #include <cstdint>
 #include <memory>
-#include <optional>
 #include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
 
 #include "perfetto/base/logging.h"
 #include "perfetto/base/status.h"
 #include "perfetto/ext/base/flat_hash_map.h"
 #include "perfetto/ext/base/status_or.h"
+#include "perfetto/trace_processor/basic_types.h"
+#include "src/trace_processor/containers/string_pool.h"
 #include "src/trace_processor/db/runtime_table.h"
 #include "src/trace_processor/db/table.h"
+#include "src/trace_processor/perfetto_sql/engine/function_util.h"
 #include "src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.h"
 #include "src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.h"
 #include "src/trace_processor/perfetto_sql/engine/runtime_table_function.h"
 #include "src/trace_processor/perfetto_sql/intrinsics/functions/sql_function.h"
 #include "src/trace_processor/perfetto_sql/intrinsics/table_functions/static_table_function.h"
-#include "src/trace_processor/sqlite/scoped_db.h"
+#include "src/trace_processor/sqlite/query_cache.h"
 #include "src/trace_processor/sqlite/sql_source.h"
 #include "src/trace_processor/sqlite/sqlite_engine.h"
 #include "src/trace_processor/sqlite/sqlite_utils.h"
+#include "src/trace_processor/util/sql_argument.h"
 #include "src/trace_processor/util/sql_modules.h"
 
 namespace perfetto::trace_processor {
@@ -111,7 +117,7 @@
   // of |return_type| and is implemented by executing the SQL statement |sql|.
   base::Status RegisterRuntimeFunction(bool replace,
                                        const FunctionPrototype& prototype,
-                                       std::string return_type,
+                                       const std::string& return_type,
                                        SqlSource sql);
 
   // Enables memoization for the given SQL function.
@@ -176,9 +182,7 @@
   const Table* GetStaticTableOrNull(std::string_view) const;
 
  private:
-  base::StatusOr<SqlSource> ExecuteCreateFunction(
-      const PerfettoSqlParser::CreateFunction&,
-      const PerfettoSqlParser& parser);
+  base::Status ExecuteCreateFunction(const PerfettoSqlParser::CreateFunction&);
 
   base::Status ExecuteInclude(const PerfettoSqlParser::Include&,
                               const PerfettoSqlParser& parser);
@@ -200,16 +204,16 @@
 
   // Get the column names from a statement.
   // |operator_name| is used in the error message if the statement is invalid.
-  base::StatusOr<std::vector<std::string>> GetColumnNamesFromSelectStatement(
-      const SqliteEngine::PreparedStatement& stmt,
-      const char* tag) const;
+  static base::StatusOr<std::vector<std::string>>
+  GetColumnNamesFromSelectStatement(const SqliteEngine::PreparedStatement& stmt,
+                                    const char* tag);
 
   // Validates that the column names in |column_names| match the |schema|.
   // |operator_name| is used in the error message if the statement is invalid.
-  base::Status ValidateColumnNames(
+  static base::Status ValidateColumnNames(
       const std::vector<std::string>& column_names,
       const std::vector<sql_argument::ArgumentDefinition>& schema,
-      const char* operator_name) const;
+      const char* operator_name);
 
   // Given a module and a key, include the correct file(s) from the module.
   // The key can contain a wildcard to include all files in the module with the
@@ -255,7 +259,7 @@
 template <typename Function>
 void WrapSqlFunction(sqlite3_context* ctx, int argc, sqlite3_value** argv) {
   using Context = typename Function::Context;
-  Context* ud = static_cast<Context*>(sqlite3_user_data(ctx));
+  auto* ud = static_cast<Context*>(sqlite3_user_data(ctx));
 
   ScopedCleanup<Function> scoped_cleanup{ud};
   SqlValue value{};
@@ -314,14 +318,14 @@
 base::Status PerfettoSqlEngine::RegisterStaticFunction(
     const char* name,
     int argc,
-    std::unique_ptr<typename Function::Context> user_data,
+    std::unique_ptr<typename Function::Context> ctx,
     bool deterministic) {
   // Metric proto builder functions can be reregistered: don't double count when
   // this happens.
   if (!engine_->GetFunctionContext(name, argc)) {
     static_function_count_++;
   }
-  return RegisterFunctionWithSqlite<Function>(name, argc, std::move(user_data),
+  return RegisterFunctionWithSqlite<Function>(name, argc, std::move(ctx),
                                               deterministic);
 }
 
@@ -329,14 +333,14 @@
 base::Status PerfettoSqlEngine::RegisterFunctionWithSqlite(
     const char* name,
     int argc,
-    std::unique_ptr<typename Function::Context> user_data,
+    std::unique_ptr<typename Function::Context> ctx,
     bool deterministic) {
   auto ctx_destructor = [](void* ptr) {
     delete static_cast<typename Function::Context*>(ptr);
   };
   return engine_->RegisterFunction(
       name, argc, perfetto_sql_internal::WrapSqlFunction<Function>,
-      user_data.release(), ctx_destructor, deterministic);
+      ctx.release(), ctx_destructor, deterministic);
 }
 
 }  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/sqlite/sqlite_engine.cc b/src/trace_processor/sqlite/sqlite_engine.cc
index 25cc651..9c5da35 100644
--- a/src/trace_processor/sqlite/sqlite_engine.cc
+++ b/src/trace_processor/sqlite/sqlite_engine.cc
@@ -139,14 +139,14 @@
     }
   }
 
-  // Reset the database itself.
-  db_.reset();
-
-  // SQLite is not guaranteed to pick saved tables back up when destroyed as
-  // from it's perspective, it has called xDisconnect. Make sure to do that
-  // ourselves.
+  // SQLite will not pick saved tables back up when destroyed as, from it's
+  // perspective, it has called xDisconnect. Make sure to do that ourselves.
   saved_tables_.Clear();
 
+  // Reset the database itself. We need to do this after clearing the saved
+  // tables as the saved tables could hold onto prepared statements.
+  db_.reset();
+
   // The above operations should have cleared all the tables.
   if (PERFETTO_UNLIKELY(sqlite_tables_.size() != 0)) {
     std::vector<std::string> tables;
diff --git a/src/trace_processor/trace_processor_context.cc b/src/trace_processor/trace_processor_context.cc
index 908fdea..c7cea38 100644
--- a/src/trace_processor/trace_processor_context.cc
+++ b/src/trace_processor/trace_processor_context.cc
@@ -29,7 +29,6 @@
 #include "src/trace_processor/importers/common/global_args_tracker.h"
 #include "src/trace_processor/importers/common/metadata_tracker.h"
 #include "src/trace_processor/importers/common/process_tracker.h"
-#include "src/trace_processor/importers/common/sched_event_tracker.h"
 #include "src/trace_processor/importers/common/slice_tracker.h"
 #include "src/trace_processor/importers/common/slice_translation_table.h"
 #include "src/trace_processor/importers/common/stack_profile_tracker.h"
diff --git a/src/trace_processor/trace_processor_storage_impl.cc b/src/trace_processor/trace_processor_storage_impl.cc
index 1f06a0f..05b88c0 100644
--- a/src/trace_processor/trace_processor_storage_impl.cc
+++ b/src/trace_processor/trace_processor_storage_impl.cc
@@ -28,7 +28,6 @@
 #include "src/trace_processor/importers/common/flow_tracker.h"
 #include "src/trace_processor/importers/common/metadata_tracker.h"
 #include "src/trace_processor/importers/common/process_tracker.h"
-#include "src/trace_processor/importers/common/sched_event_tracker.h"
 #include "src/trace_processor/importers/common/slice_tracker.h"
 #include "src/trace_processor/importers/common/slice_translation_table.h"
 #include "src/trace_processor/importers/common/stack_profile_tracker.h"
@@ -60,7 +59,6 @@
       new SliceTranslationTable(context_.storage.get()));
   context_.flow_tracker.reset(new FlowTracker(&context_));
   context_.event_tracker.reset(new EventTracker(&context_));
-  context_.sched_event_tracker.reset(new SchedEventTracker(&context_));
   context_.process_tracker.reset(new ProcessTracker(&context_));
   context_.clock_tracker.reset(new ClockTracker(&context_));
   context_.clock_converter.reset(new ClockConverter(&context_));
diff --git a/src/trace_processor/types/trace_processor_context.h b/src/trace_processor/types/trace_processor_context.h
index 3b3336c..ad4e148 100644
--- a/src/trace_processor/types/trace_processor_context.h
+++ b/src/trace_processor/types/trace_processor_context.h
@@ -60,7 +60,6 @@
 class ProtoImporterModule;
 class TrackEventModule;
 class ProcessTracker;
-class SchedEventTracker;
 class SliceTracker;
 class SliceTranslationTable;
 class FlowTracker;
@@ -99,7 +98,6 @@
   std::unique_ptr<FlowTracker> flow_tracker;
   std::unique_ptr<ProcessTracker> process_tracker;
   std::unique_ptr<EventTracker> event_tracker;
-  std::unique_ptr<SchedEventTracker> sched_event_tracker;
   std::unique_ptr<ClockTracker> clock_tracker;
   std::unique_ptr<ClockConverter> clock_converter;
   std::unique_ptr<PerfSampleTracker> perf_sample_tracker;
@@ -114,6 +112,7 @@
   std::unique_ptr<Destructible> android_probes_tracker;  // AndroidProbesTracker
   std::unique_ptr<Destructible> binder_tracker;          // BinderTracker
   std::unique_ptr<Destructible> heap_graph_tracker;      // HeapGraphTracker
+  std::unique_ptr<Destructible> sched_tracker;           // SchedEventTracker
   std::unique_ptr<Destructible> syscall_tracker;         // SyscallTracker
   std::unique_ptr<Destructible> system_info_tracker;     // SystemInfoTracker
   std::unique_ptr<Destructible> v4l2_tracker;            // V4l2Tracker
@@ -126,8 +125,6 @@
   std::unique_ptr<Destructible>
       shell_transitions_tracker;             // ShellTransitionsTracker
   std::unique_ptr<Destructible> v8_tracker;  // V8Tracker
-  std::unique_ptr<Destructible>
-      ftrace_sched_tracker;  // FtraceSchedEventTracker
 
   // These fields are trace readers which will be called by |forwarding_parser|
   // once the format of the trace is discovered. They are placed here as they
diff --git a/ui/src/controller/pivot_table_controller.ts b/ui/src/controller/pivot_table_controller.ts
index 230a151..864c402 100644
--- a/ui/src/controller/pivot_table_controller.ts
+++ b/ui/src/controller/pivot_table_controller.ts
@@ -15,7 +15,6 @@
  */
 
 import {Actions} from '../common/actions';
-import {DEFAULT_CHANNEL, getCurrentChannel} from '../common/channels';
 import {
   AreaSelection,
   PivotTableQuery,
@@ -39,8 +38,7 @@
   id: 'pivotTable',
   name: 'Pivot tables V2',
   description: 'Second version of pivot table',
-  // Enabled in canary and autopush by default.
-  defaultValue: getCurrentChannel() !== DEFAULT_CHANNEL,
+  defaultValue: true,
 });
 
 function expectNumber(value: ColumnType): number {