Merge "cleanup: update "next id" proto comment"
diff --git a/Android.bp b/Android.bp
index b65d3fe..73880ac 100644
--- a/Android.bp
+++ b/Android.bp
@@ -8425,6 +8425,7 @@
         "src/base/scoped_file_unittest.cc",
         "src/base/small_vector_unittest.cc",
         "src/base/status_or_unittest.cc",
+        "src/base/status_unittest.cc",
         "src/base/string_splitter_unittest.cc",
         "src/base/string_utils_unittest.cc",
         "src/base/string_view_unittest.cc",
@@ -9409,6 +9410,18 @@
 // GN: //src/trace_processor/db/overlays:overlays
 filegroup {
     name: "perfetto_src_trace_processor_db_overlays_overlays",
+    srcs: [
+        "src/trace_processor/db/overlays/null_overlay.cc",
+        "src/trace_processor/db/overlays/storage_overlay.cc",
+    ],
+}
+
+// GN: //src/trace_processor/db/overlays:unittests
+filegroup {
+    name: "perfetto_src_trace_processor_db_overlays_unittests",
+    srcs: [
+        "src/trace_processor/db/overlays/null_overlay_unittest.cc",
+    ],
 }
 
 // GN: //src/trace_processor/db/storage:storage
@@ -10188,6 +10201,7 @@
         "src/trace_processor/prelude/functions/create_view_function.cc",
         "src/trace_processor/prelude/functions/import.cc",
         "src/trace_processor/prelude/functions/layout_functions.cc",
+        "src/trace_processor/prelude/functions/math.cc",
         "src/trace_processor/prelude/functions/pprof_functions.cc",
         "src/trace_processor/prelude/functions/sqlite3_str_split.cc",
         "src/trace_processor/prelude/functions/stack_functions.cc",
@@ -10283,6 +10297,7 @@
         "src/trace_processor/tables/memory_tables.py",
         "src/trace_processor/tables/metadata_tables.py",
         "src/trace_processor/tables/profiler_tables.py",
+        "src/trace_processor/tables/sched_tables.py",
         "src/trace_processor/tables/slice_tables.py",
         "src/trace_processor/tables/trace_proto_tables.py",
         "src/trace_processor/tables/track_tables.py",
@@ -10379,6 +10394,7 @@
         "src/trace_processor/sqlite/sql_stats_table.cc",
         "src/trace_processor/sqlite/sqlite_engine.cc",
         "src/trace_processor/sqlite/sqlite_table.cc",
+        "src/trace_processor/sqlite/sqlite_tokenizer.cc",
         "src/trace_processor/sqlite/sqlite_utils.cc",
         "src/trace_processor/sqlite/stats_table.cc",
     ],
@@ -10390,6 +10406,7 @@
     srcs: [
         "src/trace_processor/sqlite/db_sqlite_table_unittest.cc",
         "src/trace_processor/sqlite/query_constraints_unittest.cc",
+        "src/trace_processor/sqlite/sqlite_tokenizer_unittest.cc",
         "src/trace_processor/sqlite/sqlite_utils_unittest.cc",
     ],
 }
@@ -10412,6 +10429,8 @@
         "src/trace_processor/stdlib/android/statsd.sql",
         "src/trace_processor/stdlib/chrome/chrome_scrolls.sql",
         "src/trace_processor/stdlib/chrome/cpu_powerups.sql",
+        "src/trace_processor/stdlib/chrome/histograms.sql",
+        "src/trace_processor/stdlib/chrome/speedometer.sql",
         "src/trace_processor/stdlib/common/counters.sql",
         "src/trace_processor/stdlib/common/cpus.sql",
         "src/trace_processor/stdlib/common/metadata.sql",
@@ -10498,6 +10517,7 @@
         "src/trace_processor/tables/memory_tables.py",
         "src/trace_processor/tables/metadata_tables.py",
         "src/trace_processor/tables/profiler_tables.py",
+        "src/trace_processor/tables/sched_tables.py",
         "src/trace_processor/tables/slice_tables.py",
         "src/trace_processor/tables/trace_proto_tables.py",
         "src/trace_processor/tables/track_tables.py",
@@ -10513,6 +10533,7 @@
         "src/trace_processor/tables/memory_tables_py.h",
         "src/trace_processor/tables/metadata_tables_py.h",
         "src/trace_processor/tables/profiler_tables_py.h",
+        "src/trace_processor/tables/sched_tables_py.h",
         "src/trace_processor/tables/slice_tables_py.h",
         "src/trace_processor/tables/trace_proto_tables_py.h",
         "src/trace_processor/tables/track_tables_py.h",
@@ -10532,6 +10553,7 @@
         "src/trace_processor/tables/memory_tables.py",
         "src/trace_processor/tables/metadata_tables.py",
         "src/trace_processor/tables/profiler_tables.py",
+        "src/trace_processor/tables/sched_tables.py",
         "src/trace_processor/tables/slice_tables.py",
         "src/trace_processor/tables/trace_proto_tables.py",
         "src/trace_processor/tables/track_tables.py",
@@ -12053,6 +12075,7 @@
         ":perfetto_src_trace_processor_containers_unittests",
         ":perfetto_src_trace_processor_db_db",
         ":perfetto_src_trace_processor_db_overlays_overlays",
+        ":perfetto_src_trace_processor_db_overlays_unittests",
         ":perfetto_src_trace_processor_db_storage_storage",
         ":perfetto_src_trace_processor_db_storage_unittests",
         ":perfetto_src_trace_processor_db_unittests",
diff --git a/BUILD b/BUILD
index a62edb8..ebfc1cc 100644
--- a/BUILD
+++ b/BUILD
@@ -1277,6 +1277,9 @@
 perfetto_filegroup(
     name = "src_trace_processor_db_overlays_overlays",
     srcs = [
+        "src/trace_processor/db/overlays/null_overlay.cc",
+        "src/trace_processor/db/overlays/null_overlay.h",
+        "src/trace_processor/db/overlays/storage_overlay.cc",
         "src/trace_processor/db/overlays/storage_overlay.h",
         "src/trace_processor/db/overlays/types.h",
     ],
@@ -2014,6 +2017,8 @@
         "src/trace_processor/prelude/functions/import.h",
         "src/trace_processor/prelude/functions/layout_functions.cc",
         "src/trace_processor/prelude/functions/layout_functions.h",
+        "src/trace_processor/prelude/functions/math.cc",
+        "src/trace_processor/prelude/functions/math.h",
         "src/trace_processor/prelude/functions/pprof_functions.cc",
         "src/trace_processor/prelude/functions/pprof_functions.h",
         "src/trace_processor/prelude/functions/sqlite3_str_split.cc",
@@ -2174,6 +2179,8 @@
         "src/trace_processor/sqlite/sqlite_engine.h",
         "src/trace_processor/sqlite/sqlite_table.cc",
         "src/trace_processor/sqlite/sqlite_table.h",
+        "src/trace_processor/sqlite/sqlite_tokenizer.cc",
+        "src/trace_processor/sqlite/sqlite_tokenizer.h",
         "src/trace_processor/sqlite/sqlite_utils.cc",
         "src/trace_processor/sqlite/sqlite_utils.h",
         "src/trace_processor/sqlite/stats_table.cc",
@@ -2213,6 +2220,8 @@
     srcs = [
         "src/trace_processor/stdlib/chrome/chrome_scrolls.sql",
         "src/trace_processor/stdlib/chrome/cpu_powerups.sql",
+        "src/trace_processor/stdlib/chrome/histograms.sql",
+        "src/trace_processor/stdlib/chrome/speedometer.sql",
     ],
 )
 
@@ -2293,6 +2302,7 @@
         "src/trace_processor/tables/memory_tables.py",
         "src/trace_processor/tables/metadata_tables.py",
         "src/trace_processor/tables/profiler_tables.py",
+        "src/trace_processor/tables/sched_tables.py",
         "src/trace_processor/tables/slice_tables.py",
         "src/trace_processor/tables/trace_proto_tables.py",
         "src/trace_processor/tables/track_tables.py",
@@ -2304,6 +2314,7 @@
         "src/trace_processor/tables/memory_tables_py.h",
         "src/trace_processor/tables/metadata_tables_py.h",
         "src/trace_processor/tables/profiler_tables_py.h",
+        "src/trace_processor/tables/sched_tables_py.h",
         "src/trace_processor/tables/slice_tables_py.h",
         "src/trace_processor/tables/trace_proto_tables_py.h",
         "src/trace_processor/tables/track_tables_py.h",
diff --git a/include/perfetto/base/status.h b/include/perfetto/base/status.h
index 2939357..f059184 100644
--- a/include/perfetto/base/status.h
+++ b/include/perfetto/base/status.h
@@ -17,7 +17,10 @@
 #ifndef INCLUDE_PERFETTO_BASE_STATUS_H_
 #define INCLUDE_PERFETTO_BASE_STATUS_H_
 
+#include <optional>
 #include <string>
+#include <string_view>
+#include <vector>
 
 #include "perfetto/base/compiler.h"
 #include "perfetto/base/export.h"
@@ -30,6 +33,10 @@
 // This can used as the return type of functions which would usually return an
 // bool for success or int for errno but also wants to add some string context
 // (ususally for logging).
+//
+// Similar to absl::Status, an optional "payload" can also be included with more
+// context about the error. This allows passing additional metadata about the
+// error (e.g. location of errors, potential mitigations etc).
 class PERFETTO_EXPORT_COMPONENT Status {
  public:
   Status() : ok_(true) {}
@@ -52,9 +59,49 @@
   const std::string& message() const { return message_; }
   const char* c_message() const { return message_.c_str(); }
 
+  //////////////////////////////////////////////////////////////////////////////
+  // Payload Management APIs
+  //////////////////////////////////////////////////////////////////////////////
+
+  // Payloads can be attached to error statuses to provide additional context.
+  //
+  // Payloads are (key, value) pairs, where the key is a string acting as a
+  // unique "type URL" and the value is an opaque string. The "type URL" should
+  // be unique, follow the format of a URL and, ideally, documentation on how to
+  // interpret its associated data should be available.
+  //
+  // To attach a payload to a status object, call `Status::SetPayload()`.
+  // Similarly, to extract the payload from a status, call
+  // `Status::GetPayload()`.
+  //
+  // Note: the payload APIs are only meaningful to call when the status is an
+  // error. Otherwise, all methods are noops.
+
+  // Gets the payload for the given |type_url| if one exists.
+  //
+  // Will always return std::nullopt if |ok()|.
+  std::optional<std::string_view> GetPayload(std::string_view type_url);
+
+  // Sets the payload for the given key. The key should
+  //
+  // Will always do nothing if |ok()|.
+  void SetPayload(std::string_view type_url, std::string value);
+
+  // Erases the payload for the given string and returns true if the payload
+  // existed and was erased.
+  //
+  // Will always do nothing if |ok()|.
+  bool ErasePayload(std::string_view type_url);
+
  private:
+  struct Payload {
+    std::string type_url;
+    std::string payload;
+  };
+
   bool ok_ = false;
   std::string message_;
+  std::vector<Payload> payloads_;
 };
 
 // Returns a status object which represents the Ok status.
diff --git a/include/perfetto/tracing/internal/data_source_type.h b/include/perfetto/tracing/internal/data_source_type.h
index aee830a..73c84be 100644
--- a/include/perfetto/tracing/internal/data_source_type.h
+++ b/include/perfetto/tracing/internal/data_source_type.h
@@ -167,8 +167,7 @@
   InstancesIterator BeginIteration(
       uint32_t cached_instances,
       DataSourceThreadLocalState* tls_state,
-      typename TracePointTraits::TracePointData trace_point_data)
-      PERFETTO_ALWAYS_INLINE {
+      typename TracePointTraits::TracePointData trace_point_data) {
     InstancesIterator it{};
     it.cached_instances = cached_instances;
     FirstActiveInstance<TracePointTraits>(&it, tls_state, trace_point_data);
@@ -185,8 +184,7 @@
   template <typename TracePointTraits>
   void NextIteration(InstancesIterator* iterator,
                      DataSourceThreadLocalState* tls_state,
-                     typename TracePointTraits::TracePointData trace_point_data)
-      PERFETTO_ALWAYS_INLINE {
+                     typename TracePointTraits::TracePointData trace_point_data) {
     iterator->i++;
     FirstActiveInstance<TracePointTraits>(iterator, tls_state,
                                           trace_point_data);
diff --git a/include/perfetto/tracing/internal/track_event_data_source.h b/include/perfetto/tracing/internal/track_event_data_source.h
index d1e17a6..2ec348e 100644
--- a/include/perfetto/tracing/internal/track_event_data_source.h
+++ b/include/perfetto/tracing/internal/track_event_data_source.h
@@ -308,276 +308,25 @@
         {category_index});
   }
 
-  // Once we've determined tracing to be enabled for this category, actually
-  // write a trace event onto this thread's default track. Outlined to avoid
-  // bloating code (mostly stack depth) at the actual trace point.
-  //
-  // The following combination of parameters is supported (in the given order):
-  // - Zero or one track,
-  // - Zero or one custom timestamp,
-  // - Arbitrary number of debug annotations.
-  // - Zero or one lambda.
-
-  // Trace point which does not take a track or timestamp.
-  template <typename CategoryType,
-            typename EventNameType,
-            typename... Arguments>
-  static void TraceForCategory(uint32_t instances,
-                               const CategoryType& category,
-                               const EventNameType& event_name,
-                               perfetto::protos::pbzero::TrackEvent::Type type,
-                               Arguments&&... args) PERFETTO_NO_INLINE {
-    TraceForCategoryImpl(instances, category, event_name, type,
-                         TrackEventInternal::kDefaultTrack,
-                         TrackEventInternal::GetTraceTime(),
-                         std::forward<Arguments>(args)...);
+  // The following methods forward all arguments to TraceForCategoryBody
+  // while casting string constants to const char*.
+  template <typename... Arguments>
+  static void TraceForCategory(Arguments&&... args) PERFETTO_ALWAYS_INLINE {
+    TraceForCategoryBody(DecayStrType(args)...);
   }
 
-  // Trace point which takes a track, but not timestamp.
-  // NOTE: Here track should be captured using universal reference (TrackType&&)
-  // instead of const TrackType& to ensure that the proper overload is selected
-  // (otherwise the compiler will fail to disambiguate between adding const& and
-  // parsing track as a part of Arguments...).
-  template <typename TrackType,
-            typename CategoryType,
-            typename EventNameType,
-            typename... Arguments,
-            typename TrackTypeCheck = typename std::enable_if<
-                std::is_convertible<TrackType, Track>::value>::type>
-  static void TraceForCategory(uint32_t instances,
-                               const CategoryType& category,
-                               const EventNameType& event_name,
-                               perfetto::protos::pbzero::TrackEvent::Type type,
-                               TrackType&& track,
-                               Arguments&&... args) PERFETTO_NO_INLINE {
-    TraceForCategoryImpl(
-        instances, category, event_name, type, std::forward<TrackType>(track),
-        TrackEventInternal::GetTraceTime(), std::forward<Arguments>(args)...);
+  template <typename... Arguments>
+  static void TraceForCategoryLegacy(Arguments&&... args)
+      PERFETTO_ALWAYS_INLINE {
+    TraceForCategoryLegacyBody(DecayStrType(args)...);
   }
 
-  // Trace point which takes a timestamp, but not track.
-  template <typename CategoryType,
-            typename EventNameType,
-            typename TimestampType = uint64_t,
-            typename... Arguments,
-            typename TimestampTypeCheck = typename std::enable_if<
-                IsValidTimestamp<TimestampType>()>::type>
-  static void TraceForCategory(uint32_t instances,
-                               const CategoryType& category,
-                               const EventNameType& event_name,
-                               perfetto::protos::pbzero::TrackEvent::Type type,
-                               TimestampType&& timestamp,
-                               Arguments&&... args) PERFETTO_NO_INLINE {
-    TraceForCategoryImpl(instances, category, event_name, type,
-                         TrackEventInternal::kDefaultTrack,
-                         std::forward<TimestampType>(timestamp),
-                         std::forward<Arguments>(args)...);
+  template <typename... Arguments>
+  static void TraceForCategoryLegacyWithId(Arguments&&... args)
+      PERFETTO_ALWAYS_INLINE {
+    TraceForCategoryLegacyWithIdBody(DecayStrType(args)...);
   }
 
-  // Trace point which takes a timestamp and a track.
-  template <typename TrackType,
-            typename CategoryType,
-            typename EventNameType,
-            typename TimestampType = uint64_t,
-            typename... Arguments,
-            typename TrackTypeCheck = typename std::enable_if<
-                std::is_convertible<TrackType, Track>::value>::type,
-            typename TimestampTypeCheck = typename std::enable_if<
-                IsValidTimestamp<TimestampType>()>::type>
-  static void TraceForCategory(uint32_t instances,
-                               const CategoryType& category,
-                               const EventNameType& event_name,
-                               perfetto::protos::pbzero::TrackEvent::Type type,
-                               TrackType&& track,
-                               TimestampType&& timestamp,
-                               Arguments&&... args) PERFETTO_NO_INLINE {
-    TraceForCategoryImpl(instances, category, event_name, type,
-                         std::forward<TrackType>(track),
-                         std::forward<TimestampType>(timestamp),
-                         std::forward<Arguments>(args)...);
-  }
-
-  // Trace point with with a counter sample.
-  template <typename CategoryType, typename EventNameType, typename ValueType>
-  static void TraceForCategory(uint32_t instances,
-                               const CategoryType& category,
-                               const EventNameType&,
-                               perfetto::protos::pbzero::TrackEvent::Type type,
-                               CounterTrack track,
-                               ValueType value) PERFETTO_ALWAYS_INLINE {
-    PERFETTO_DCHECK(type == perfetto::protos::pbzero::TrackEvent::TYPE_COUNTER);
-    TraceForCategory(instances, category, /*name=*/nullptr, type, track,
-                     TrackEventInternal::GetTraceTime(), value);
-  }
-
-  // Trace point with with a timestamp and a counter sample.
-  template <typename CategoryType,
-            typename EventNameType,
-            typename TimestampType = uint64_t,
-            typename TimestampTypeCheck = typename std::enable_if<
-                IsValidTimestamp<TimestampType>()>::type,
-            typename ValueType>
-  static void TraceForCategory(uint32_t instances,
-                               const CategoryType& category,
-                               const EventNameType&,
-                               perfetto::protos::pbzero::TrackEvent::Type type,
-                               CounterTrack track,
-                               TimestampType timestamp,
-                               ValueType value) PERFETTO_ALWAYS_INLINE {
-    PERFETTO_DCHECK(type == perfetto::protos::pbzero::TrackEvent::TYPE_COUNTER);
-    TraceForCategoryImpl(
-        instances, category, /*name=*/nullptr, type, track, timestamp,
-        [&](EventContext event_ctx) {
-          if (std::is_integral<ValueType>::value) {
-            int64_t value_int64 = static_cast<int64_t>(value);
-            if (track.is_incremental()) {
-              TrackEventIncrementalState* incr_state =
-                  event_ctx.GetIncrementalState();
-              PERFETTO_DCHECK(incr_state != nullptr);
-              auto prv_value =
-                  incr_state->last_counter_value_per_track[track.uuid];
-              event_ctx.event()->set_counter_value(value_int64 - prv_value);
-              prv_value = value_int64;
-              incr_state->last_counter_value_per_track[track.uuid] = prv_value;
-            } else {
-              event_ctx.event()->set_counter_value(value_int64);
-            }
-          } else {
-            event_ctx.event()->set_double_counter_value(
-                static_cast<double>(value));
-          }
-        });
-  }
-
-// Additional trace points used in legacy macros.
-// It's possible to implement legacy macros using a common TraceForCategory,
-// by supplying a lambda that sets all necessary legacy fields. But this
-// results in a binary size bloat because every trace point generates its own
-// template instantiation with its own lambda. ICF can't eliminate those as
-// each lambda captures different variables and so the code is not completely
-// identical.
-// What we do instead is define additional TraceForCategoryLegacy templates
-// that take legacy arguments directly. Their instantiations can have the same
-// binary code for at least some macro invocations and so can be successfully
-// folded by the linker.
-#if PERFETTO_ENABLE_LEGACY_TRACE_EVENTS
-  template <typename TrackType,
-            typename CategoryType,
-            typename EventNameType,
-            typename... Arguments,
-            typename TrackTypeCheck = typename std::enable_if<
-                std::is_convertible<TrackType, Track>::value>::type>
-  static void TraceForCategoryLegacy(
-      uint32_t instances,
-      const CategoryType& category,
-      const EventNameType& event_name,
-      perfetto::protos::pbzero::TrackEvent::Type type,
-      TrackType&& track,
-      char phase,
-      uint32_t flags,
-      Arguments&&... args) PERFETTO_NO_INLINE {
-    TraceForCategoryImpl(instances, category, event_name, type, track,
-                         TrackEventInternal::GetTraceTime(),
-                         [&](perfetto::EventContext ctx)
-                             PERFETTO_NO_THREAD_SAFETY_ANALYSIS {
-                               using ::perfetto::internal::TrackEventLegacy;
-                               TrackEventLegacy::WriteLegacyEvent(
-                                   std::move(ctx), phase, flags, args...);
-                             });
-  }
-
-  template <typename TrackType,
-            typename CategoryType,
-            typename EventNameType,
-            typename TimestampType = uint64_t,
-            typename... Arguments,
-            typename TrackTypeCheck = typename std::enable_if<
-                std::is_convertible<TrackType, Track>::value>::type,
-            typename TimestampTypeCheck = typename std::enable_if<
-                IsValidTimestamp<TimestampType>()>::type>
-  static void TraceForCategoryLegacy(
-      uint32_t instances,
-      const CategoryType& category,
-      const EventNameType& event_name,
-      perfetto::protos::pbzero::TrackEvent::Type type,
-      TrackType&& track,
-      char phase,
-      uint32_t flags,
-      TimestampType&& timestamp,
-      Arguments&&... args) PERFETTO_NO_INLINE {
-    TraceForCategoryImpl(
-        instances, category, event_name, type, track, timestamp,
-        [&](perfetto::EventContext ctx) PERFETTO_NO_THREAD_SAFETY_ANALYSIS {
-          using ::perfetto::internal::TrackEventLegacy;
-          TrackEventLegacy::WriteLegacyEvent(std::move(ctx), phase, flags,
-                                             args...);
-        });
-  }
-
-  template <typename TrackType,
-            typename CategoryType,
-            typename EventNameType,
-            typename ThreadIdType,
-            typename LegacyIdType,
-            typename... Arguments,
-            typename TrackTypeCheck = typename std::enable_if<
-                std::is_convertible<TrackType, Track>::value>::type>
-  static void TraceForCategoryLegacyWithId(
-      uint32_t instances,
-      const CategoryType& category,
-      const EventNameType& event_name,
-      perfetto::protos::pbzero::TrackEvent::Type type,
-      TrackType&& track,
-      char phase,
-      uint32_t flags,
-      ThreadIdType thread_id,
-      LegacyIdType legacy_id,
-      Arguments&&... args) PERFETTO_NO_INLINE {
-    TraceForCategoryImpl(
-        instances, category, event_name, type, track,
-        TrackEventInternal::GetTraceTime(),
-        [&](perfetto::EventContext ctx) PERFETTO_NO_THREAD_SAFETY_ANALYSIS {
-          using ::perfetto::internal::TrackEventLegacy;
-          ::perfetto::internal::LegacyTraceId trace_id{legacy_id};
-          TrackEventLegacy::WriteLegacyEventWithIdAndTid(
-              std::move(ctx), phase, flags, trace_id, thread_id, args...);
-        });
-  }
-
-  template <typename TrackType,
-            typename CategoryType,
-            typename EventNameType,
-            typename ThreadIdType,
-            typename LegacyIdType,
-            typename TimestampType = uint64_t,
-            typename... Arguments,
-            typename TrackTypeCheck = typename std::enable_if<
-                std::is_convertible<TrackType, Track>::value>::type,
-            typename TimestampTypeCheck = typename std::enable_if<
-                IsValidTimestamp<TimestampType>()>::type>
-  static void TraceForCategoryLegacyWithId(
-      uint32_t instances,
-      const CategoryType& category,
-      const EventNameType& event_name,
-      perfetto::protos::pbzero::TrackEvent::Type type,
-      TrackType&& track,
-      char phase,
-      uint32_t flags,
-      ThreadIdType thread_id,
-      LegacyIdType legacy_id,
-      TimestampType&& timestamp,
-      Arguments&&... args) PERFETTO_NO_INLINE {
-    TraceForCategoryImpl(
-        instances, category, event_name, type, track, timestamp,
-        [&](perfetto::EventContext ctx) PERFETTO_NO_THREAD_SAFETY_ANALYSIS {
-          using ::perfetto::internal::TrackEventLegacy;
-          ::perfetto::internal::LegacyTraceId trace_id{legacy_id};
-          TrackEventLegacy::WriteLegacyEventWithIdAndTid(
-              std::move(ctx), phase, flags, trace_id, thread_id, args...);
-        });
-  }
-#endif  // PERFETTO_ENABLE_LEGACY_TRACE_EVENTS
-
   // Initialize the track event library. Should be called before tracing is
   // enabled.
   static bool Register() {
@@ -638,6 +387,295 @@
   const protos::gen::TrackEventConfig& GetConfig() const { return config_; }
 
  private:
+  // The DecayStrType method is used to avoid unnecessary instantiations of
+  // templates on string constants of different sizes. Without it, strings
+  // of different lengths have different types: char[10], char[15] etc.
+  // DecayStrType forwards all types of arguments as is, with the exception
+  // of string constants which are all cast to const char*. This allows to
+  // avoid extra instantiations of TraceForCategory templates.
+  template <typename T>
+  static T&& DecayStrType(T&& t) {
+    return std::forward<T>(t);
+  }
+
+  static const char* DecayStrType(const char* t) { return t; }
+
+  // Once we've determined tracing to be enabled for this category, actually
+  // write a trace event onto this thread's default track. Outlined to avoid
+  // bloating code (mostly stack depth) at the actual trace point.
+  //
+  // The following combination of parameters is supported (in the given order):
+  // - Zero or one track,
+  // - Zero or one custom timestamp,
+  // - Arbitrary number of debug annotations.
+  // - Zero or one lambda.
+
+  // Trace point which does not take a track or timestamp.
+  template <typename CategoryType,
+            typename EventNameType,
+            typename... Arguments>
+  static void TraceForCategoryBody(
+      uint32_t instances,
+      const CategoryType& category,
+      const EventNameType& event_name,
+      perfetto::protos::pbzero::TrackEvent::Type type,
+      Arguments&&... args) PERFETTO_NO_INLINE {
+    TraceForCategoryImpl(instances, category, event_name, type,
+                         TrackEventInternal::kDefaultTrack,
+                         TrackEventInternal::GetTraceTime(),
+                         std::forward<Arguments>(args)...);
+  }
+
+  // Trace point which takes a track, but not timestamp.
+  // NOTE: Here track should be captured using universal reference (TrackType&&)
+  // instead of const TrackType& to ensure that the proper overload is selected
+  // (otherwise the compiler will fail to disambiguate between adding const& and
+  // parsing track as a part of Arguments...).
+  template <typename TrackType,
+            typename CategoryType,
+            typename EventNameType,
+            typename... Arguments,
+            typename TrackTypeCheck = typename std::enable_if<
+                std::is_convertible<TrackType, Track>::value>::type>
+  static void TraceForCategoryBody(
+      uint32_t instances,
+      const CategoryType& category,
+      const EventNameType& event_name,
+      perfetto::protos::pbzero::TrackEvent::Type type,
+      TrackType&& track,
+      Arguments&&... args) PERFETTO_NO_INLINE {
+    TraceForCategoryImpl(
+        instances, category, event_name, type, std::forward<TrackType>(track),
+        TrackEventInternal::GetTraceTime(), std::forward<Arguments>(args)...);
+  }
+
+  // Trace point which takes a timestamp, but not track.
+  template <typename CategoryType,
+            typename EventNameType,
+            typename TimestampType = uint64_t,
+            typename... Arguments,
+            typename TimestampTypeCheck = typename std::enable_if<
+                IsValidTimestamp<TimestampType>()>::type>
+  static void TraceForCategoryBody(
+      uint32_t instances,
+      const CategoryType& category,
+      const EventNameType& event_name,
+      perfetto::protos::pbzero::TrackEvent::Type type,
+      TimestampType&& timestamp,
+      Arguments&&... args) PERFETTO_NO_INLINE {
+    TraceForCategoryImpl(instances, category, event_name, type,
+                         TrackEventInternal::kDefaultTrack,
+                         std::forward<TimestampType>(timestamp),
+                         std::forward<Arguments>(args)...);
+  }
+
+  // Trace point which takes a timestamp and a track.
+  template <typename TrackType,
+            typename CategoryType,
+            typename EventNameType,
+            typename TimestampType = uint64_t,
+            typename... Arguments,
+            typename TrackTypeCheck = typename std::enable_if<
+                std::is_convertible<TrackType, Track>::value>::type,
+            typename TimestampTypeCheck = typename std::enable_if<
+                IsValidTimestamp<TimestampType>()>::type>
+  static void TraceForCategoryBody(
+      uint32_t instances,
+      const CategoryType& category,
+      const EventNameType& event_name,
+      perfetto::protos::pbzero::TrackEvent::Type type,
+      TrackType&& track,
+      TimestampType&& timestamp,
+      Arguments&&... args) PERFETTO_NO_INLINE {
+    TraceForCategoryImpl(instances, category, event_name, type,
+                         std::forward<TrackType>(track),
+                         std::forward<TimestampType>(timestamp),
+                         std::forward<Arguments>(args)...);
+  }
+
+  // Trace point with with a counter sample.
+  template <typename CategoryType, typename EventNameType, typename ValueType>
+  static void TraceForCategoryBody(
+      uint32_t instances,
+      const CategoryType& category,
+      const EventNameType&,
+      perfetto::protos::pbzero::TrackEvent::Type type,
+      CounterTrack track,
+      ValueType value) PERFETTO_ALWAYS_INLINE {
+    PERFETTO_DCHECK(type == perfetto::protos::pbzero::TrackEvent::TYPE_COUNTER);
+    TraceForCategory(instances, category, /*name=*/nullptr, type, track,
+                     TrackEventInternal::GetTraceTime(), value);
+  }
+
+  // Trace point with with a timestamp and a counter sample.
+  template <typename CategoryType,
+            typename EventNameType,
+            typename TimestampType = uint64_t,
+            typename TimestampTypeCheck = typename std::enable_if<
+                IsValidTimestamp<TimestampType>()>::type,
+            typename ValueType>
+  static void TraceForCategoryBody(
+      uint32_t instances,
+      const CategoryType& category,
+      const EventNameType&,
+      perfetto::protos::pbzero::TrackEvent::Type type,
+      CounterTrack track,
+      TimestampType timestamp,
+      ValueType value) PERFETTO_ALWAYS_INLINE {
+    PERFETTO_DCHECK(type == perfetto::protos::pbzero::TrackEvent::TYPE_COUNTER);
+    TraceForCategoryImpl(
+        instances, category, /*name=*/nullptr, type, track, timestamp,
+        [&](EventContext event_ctx) {
+          if (std::is_integral<ValueType>::value) {
+            int64_t value_int64 = static_cast<int64_t>(value);
+            if (track.is_incremental()) {
+              TrackEventIncrementalState* incr_state =
+                  event_ctx.GetIncrementalState();
+              PERFETTO_DCHECK(incr_state != nullptr);
+              auto prv_value =
+                  incr_state->last_counter_value_per_track[track.uuid];
+              event_ctx.event()->set_counter_value(value_int64 - prv_value);
+              prv_value = value_int64;
+              incr_state->last_counter_value_per_track[track.uuid] = prv_value;
+            } else {
+              event_ctx.event()->set_counter_value(value_int64);
+            }
+          } else {
+            event_ctx.event()->set_double_counter_value(
+                static_cast<double>(value));
+          }
+        });
+  }
+
+// Additional trace points used in legacy macros.
+// It's possible to implement legacy macros using a common TraceForCategory,
+// by supplying a lambda that sets all necessary legacy fields. But this
+// results in a binary size bloat because every trace point generates its own
+// template instantiation with its own lambda. ICF can't eliminate those as
+// each lambda captures different variables and so the code is not completely
+// identical.
+// What we do instead is define additional TraceForCategoryLegacy templates
+// that take legacy arguments directly. Their instantiations can have the same
+// binary code for at least some macro invocations and so can be successfully
+// folded by the linker.
+#if PERFETTO_ENABLE_LEGACY_TRACE_EVENTS
+  template <typename TrackType,
+            typename CategoryType,
+            typename EventNameType,
+            typename... Arguments,
+            typename TrackTypeCheck = typename std::enable_if<
+                std::is_convertible<TrackType, Track>::value>::type>
+  static void TraceForCategoryLegacyBody(
+      uint32_t instances,
+      const CategoryType& category,
+      const EventNameType& event_name,
+      perfetto::protos::pbzero::TrackEvent::Type type,
+      TrackType&& track,
+      char phase,
+      uint32_t flags,
+      Arguments&&... args) PERFETTO_NO_INLINE {
+    TraceForCategoryImpl(instances, category, event_name, type, track,
+                         TrackEventInternal::GetTraceTime(),
+                         [&](perfetto::EventContext ctx)
+                             PERFETTO_NO_THREAD_SAFETY_ANALYSIS {
+                               using ::perfetto::internal::TrackEventLegacy;
+                               TrackEventLegacy::WriteLegacyEvent(
+                                   std::move(ctx), phase, flags, args...);
+                             });
+  }
+
+  template <typename TrackType,
+            typename CategoryType,
+            typename EventNameType,
+            typename TimestampType = uint64_t,
+            typename... Arguments,
+            typename TrackTypeCheck = typename std::enable_if<
+                std::is_convertible<TrackType, Track>::value>::type,
+            typename TimestampTypeCheck = typename std::enable_if<
+                IsValidTimestamp<TimestampType>()>::type>
+  static void TraceForCategoryLegacyBody(
+      uint32_t instances,
+      const CategoryType& category,
+      const EventNameType& event_name,
+      perfetto::protos::pbzero::TrackEvent::Type type,
+      TrackType&& track,
+      char phase,
+      uint32_t flags,
+      TimestampType&& timestamp,
+      Arguments&&... args) PERFETTO_NO_INLINE {
+    TraceForCategoryImpl(
+        instances, category, event_name, type, track, timestamp,
+        [&](perfetto::EventContext ctx) PERFETTO_NO_THREAD_SAFETY_ANALYSIS {
+          using ::perfetto::internal::TrackEventLegacy;
+          TrackEventLegacy::WriteLegacyEvent(std::move(ctx), phase, flags,
+                                             args...);
+        });
+  }
+
+  template <typename TrackType,
+            typename CategoryType,
+            typename EventNameType,
+            typename ThreadIdType,
+            typename LegacyIdType,
+            typename... Arguments,
+            typename TrackTypeCheck = typename std::enable_if<
+                std::is_convertible<TrackType, Track>::value>::type>
+  static void TraceForCategoryLegacyWithIdBody(
+      uint32_t instances,
+      const CategoryType& category,
+      const EventNameType& event_name,
+      perfetto::protos::pbzero::TrackEvent::Type type,
+      TrackType&& track,
+      char phase,
+      uint32_t flags,
+      ThreadIdType thread_id,
+      LegacyIdType legacy_id,
+      Arguments&&... args) PERFETTO_NO_INLINE {
+    TraceForCategoryImpl(
+        instances, category, event_name, type, track,
+        TrackEventInternal::GetTraceTime(),
+        [&](perfetto::EventContext ctx) PERFETTO_NO_THREAD_SAFETY_ANALYSIS {
+          using ::perfetto::internal::TrackEventLegacy;
+          ::perfetto::internal::LegacyTraceId trace_id{legacy_id};
+          TrackEventLegacy::WriteLegacyEventWithIdAndTid(
+              std::move(ctx), phase, flags, trace_id, thread_id, args...);
+        });
+  }
+
+  template <typename TrackType,
+            typename CategoryType,
+            typename EventNameType,
+            typename ThreadIdType,
+            typename LegacyIdType,
+            typename TimestampType = uint64_t,
+            typename... Arguments,
+            typename TrackTypeCheck = typename std::enable_if<
+                std::is_convertible<TrackType, Track>::value>::type,
+            typename TimestampTypeCheck = typename std::enable_if<
+                IsValidTimestamp<TimestampType>()>::type>
+  static void TraceForCategoryLegacyWithIdBody(
+      uint32_t instances,
+      const CategoryType& category,
+      const EventNameType& event_name,
+      perfetto::protos::pbzero::TrackEvent::Type type,
+      TrackType&& track,
+      char phase,
+      uint32_t flags,
+      ThreadIdType thread_id,
+      LegacyIdType legacy_id,
+      TimestampType&& timestamp,
+      Arguments&&... args) PERFETTO_NO_INLINE {
+    TraceForCategoryImpl(
+        instances, category, event_name, type, track, timestamp,
+        [&](perfetto::EventContext ctx) PERFETTO_NO_THREAD_SAFETY_ANALYSIS {
+          using ::perfetto::internal::TrackEventLegacy;
+          ::perfetto::internal::LegacyTraceId trace_id{legacy_id};
+          TrackEventLegacy::WriteLegacyEventWithIdAndTid(
+              std::move(ctx), phase, flags, trace_id, thread_id, args...);
+        });
+  }
+#endif  // PERFETTO_ENABLE_LEGACY_TRACE_EVENTS
+
   // Each category has its own enabled/disabled state, stored in the category
   // registry.
   struct CategoryTracePointTraits {
diff --git a/python/setup.py b/python/setup.py
index db49cac..a484eda 100644
--- a/python/setup.py
+++ b/python/setup.py
@@ -8,7 +8,7 @@
     ],
     package_data={'perfetto.trace_processor': ['*.descriptor']},
     include_package_data=True,
-    version='0.6.0',
+    version='0.7.0',
     license='apache-2.0',
     description='Python API for Perfetto\'s Trace Processor',
     author='Perfetto',
diff --git a/src/base/BUILD.gn b/src/base/BUILD.gn
index 8d48bf0..7dfce6b 100644
--- a/src/base/BUILD.gn
+++ b/src/base/BUILD.gn
@@ -199,6 +199,7 @@
     "scoped_file_unittest.cc",
     "small_vector_unittest.cc",
     "status_or_unittest.cc",
+    "status_unittest.cc",
     "string_splitter_unittest.cc",
     "string_utils_unittest.cc",
     "string_view_unittest.cc",
diff --git a/src/base/status.cc b/src/base/status.cc
index 30ccc47..d3c13e8 100644
--- a/src/base/status.cc
+++ b/src/base/status.cc
@@ -17,6 +17,7 @@
 #include "perfetto/base/status.h"
 
 #include <stdarg.h>
+#include <algorithm>
 
 namespace perfetto {
 namespace base {
@@ -31,5 +32,42 @@
   return status;
 }
 
+std::optional<std::string_view> Status::GetPayload(std::string_view type_url) {
+  if (ok()) {
+    return std::nullopt;
+  }
+  for (const auto& kv : payloads_) {
+    if (kv.type_url == type_url) {
+      return kv.payload;
+    }
+  }
+  return std::nullopt;
+}
+
+void Status::SetPayload(std::string_view type_url, std::string value) {
+  if (ok()) {
+    return;
+  }
+  for (auto& kv : payloads_) {
+    if (kv.type_url == type_url) {
+      kv.payload = value;
+      return;
+    }
+  }
+  payloads_.push_back(Payload{std::string(type_url), std::move(value)});
+}
+
+bool Status::ErasePayload(std::string_view type_url) {
+  if (ok()) {
+    return false;
+  }
+  auto it = std::remove_if(
+      payloads_.begin(), payloads_.end(),
+      [type_url](const Payload& p) { return p.type_url == type_url; });
+  bool erased = it != payloads_.end();
+  payloads_.erase(it, payloads_.end());
+  return erased;
+}
+
 }  // namespace base
 }  // namespace perfetto
diff --git a/src/base/status_unittest.cc b/src/base/status_unittest.cc
new file mode 100644
index 0000000..df42b31
--- /dev/null
+++ b/src/base/status_unittest.cc
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "perfetto/base/status.h"
+
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace base {
+
+TEST(StatusTest, GetMissingPayload) {
+  base::Status status = base::ErrStatus("Error");
+  ASSERT_EQ(status.GetPayload("test.foo.com/bar"), std::nullopt);
+}
+
+TEST(StatusTest, SetThenGetPayload) {
+  base::Status status = base::ErrStatus("Error");
+  status.SetPayload("test.foo.com/bar", "payload_value");
+  ASSERT_EQ(status.GetPayload("test.foo.com/bar"), "payload_value");
+}
+
+TEST(StatusTest, SetEraseGetPayload) {
+  base::Status status = base::ErrStatus("Error");
+  status.SetPayload("test.foo.com/bar", "payload_value");
+  ASSERT_TRUE(status.ErasePayload("test.foo.com/bar"));
+  ASSERT_EQ(status.GetPayload("test.foo.com/bar"), std::nullopt);
+}
+
+TEST(StatusTest, SetOverride) {
+  base::Status status = base::ErrStatus("Error");
+  status.SetPayload("test.foo.com/bar", "payload_value");
+  status.SetPayload("test.foo.com/bar", "other_value");
+  ASSERT_EQ(status.GetPayload("test.foo.com/bar"), "other_value");
+}
+
+TEST(StatusTest, SetGetOk) {
+  base::Status status = base::OkStatus();
+  status.SetPayload("test.foo.com/bar", "payload_value");
+  ASSERT_EQ(status.GetPayload("test.foo.com/bar"), std::nullopt);
+}
+
+TEST(StatusTest, SetMultipleAndDuplicate) {
+  base::Status status = base::ErrStatus("Error");
+  status.SetPayload("test.foo.com/bar", "payload_value");
+  status.SetPayload("test.foo.com/bar1", "1");
+  status.SetPayload("test.foo.com/bar2", "2");
+  status.SetPayload("test.foo.com/bar", "other_value");
+  ASSERT_EQ(status.GetPayload("test.foo.com/bar"), "other_value");
+  ASSERT_EQ(status.GetPayload("test.foo.com/bar1"), "1");
+  ASSERT_EQ(status.GetPayload("test.foo.com/bar2"), "2");
+}
+
+}  // namespace base
+}  // namespace perfetto
diff --git a/src/protozero/protoc_plugin/protozero_plugin.cc b/src/protozero/protoc_plugin/protozero_plugin.cc
index bcfbf28..55c3c9d 100644
--- a/src/protozero/protoc_plugin/protozero_plugin.cc
+++ b/src/protozero/protoc_plugin/protozero_plugin.cc
@@ -874,8 +874,9 @@
 
       for (int j = 0; j < nested_enum->value_count(); ++j) {
         const EnumValueDescriptor* value = nested_enum->value(j);
-        stub_h_->Print("static const $class$ $name$ = $class$::$name$;\n",
-                       "class", nested_enum->name(), "name", value->name());
+        stub_h_->Print(
+            "static inline const $class$ $name$ = $class$::$name$;\n", "class",
+            nested_enum->name(), "name", value->name());
       }
     }
 
diff --git a/src/tools/proto_merger/proto_file_serializer.cc b/src/tools/proto_merger/proto_file_serializer.cc
index dca5a07..c94a101 100644
--- a/src/tools/proto_merger/proto_file_serializer.cc
+++ b/src/tools/proto_merger/proto_file_serializer.cc
@@ -70,8 +70,11 @@
 
   std::string output;
   output += " [";
-  for (const auto& option : options) {
-    output += option.key + " = " + option.value;
+  size_t n = options.size();
+  for (size_t i = 0; i < n; i++) {
+    output += options[i].key + " = " + options[i].value;
+    if (i != n - 1)
+      output += ", ";
   }
   output += "]";
   return output;
diff --git a/src/trace_processor/BUILD.gn b/src/trace_processor/BUILD.gn
index 6ff25a9..04814a6 100644
--- a/src/trace_processor/BUILD.gn
+++ b/src/trace_processor/BUILD.gn
@@ -268,6 +268,7 @@
     ":top_level_unittests",
     "containers:unittests",
     "db:unittests",
+    "db/overlays:unittests",
     "db/storage:unittests",
     "importers/android_bugreport:unittests",
     "importers/common:unittests",
diff --git a/src/trace_processor/db/overlays/BUILD.gn b/src/trace_processor/db/overlays/BUILD.gn
index c1d50d8..f0b3e72 100644
--- a/src/trace_processor/db/overlays/BUILD.gn
+++ b/src/trace_processor/db/overlays/BUILD.gn
@@ -12,13 +12,30 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import("../../../../gn/perfetto_tp_tables.gni")
+import("../../../../gn/test.gni")
+
 source_set("overlays") {
   sources = [
+    "null_overlay.cc",
+    "null_overlay.h",
+    "storage_overlay.cc",
     "storage_overlay.h",
     "types.h",
   ]
   deps = [
     "../../../../gn:default_deps",
+    "../../../base",
     "../../containers",
   ]
 }
+
+perfetto_unittest_source_set("unittests") {
+  testonly = true
+  sources = [ "null_overlay_unittest.cc" ]
+  deps = [
+    ":overlays",
+    "../../../../gn:default_deps",
+    "../../../../gn:gtest_and_gmock",
+  ]
+}
diff --git a/src/trace_processor/db/overlays/null_overlay.cc b/src/trace_processor/db/overlays/null_overlay.cc
new file mode 100644
index 0000000..c5f5f66
--- /dev/null
+++ b/src/trace_processor/db/overlays/null_overlay.cc
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_processor/db/overlays/null_overlay.h"
+#include "perfetto/ext/base/flat_hash_map.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace overlays {
+
+using Range = RowMap::Range;
+
+StorageRange NullOverlay::MapToStorageRange(TableRange t_range) const {
+  uint32_t start = non_null_->CountSetBits(t_range.range.start);
+  uint32_t end = non_null_->CountSetBits(t_range.range.end);
+
+  return StorageRange({Range(start, end)});
+}
+
+TableBitVector NullOverlay::MapToTableBitVector(StorageBitVector s_bv) const {
+  BitVector res = non_null_->Copy();
+  res.UpdateSetBits(s_bv.bv);
+  return {std::move(res)};
+}
+
+BitVector NullOverlay::IsStorageLookupRequired(
+    OverlayOp op,
+    const TableIndexVector& t_iv) const {
+  PERFETTO_DCHECK(t_iv.indices.size() <= non_null_->size());
+
+  if (op != OverlayOp::kOther)
+    return BitVector();
+
+  BitVector in_storage(static_cast<uint32_t>(t_iv.indices.size()), false);
+
+  // For each index in TableIndexVector check whether this index is in storage.
+  for (uint32_t i = 0; i < t_iv.indices.size(); ++i) {
+    if (non_null_->IsSet(t_iv.indices[i]))
+      in_storage.Set(i);
+  }
+
+  return in_storage;
+}
+
+StorageIndexVector NullOverlay::MapToStorageIndexVector(
+    TableIndexVector t_iv_with_idx_in_storage) const {
+  PERFETTO_DCHECK(t_iv_with_idx_in_storage.indices.size() <=
+                  non_null_->CountSetBits());
+
+  std::vector<uint32_t> storage_index_vector;
+  storage_index_vector.reserve(t_iv_with_idx_in_storage.indices.size());
+  for (auto t_idx : t_iv_with_idx_in_storage.indices) {
+    storage_index_vector.push_back(non_null_->CountSetBits(t_idx));
+  }
+
+  return StorageIndexVector({std::move(storage_index_vector)});
+}
+
+BitVector NullOverlay::IndexSearch(
+    OverlayOp op,
+    const TableIndexVector& t_iv_overlay_idx) const {
+  if (op == OverlayOp::kOther)
+    return BitVector();
+
+  BitVector res(static_cast<uint32_t>(t_iv_overlay_idx.indices.size()), false);
+  if (op == OverlayOp::kIsNull) {
+    for (uint32_t i = 0; i < res.size(); ++i) {
+      if (!non_null_->IsSet(t_iv_overlay_idx.indices[i]))
+        res.Set(i);
+    }
+    return res;
+  }
+
+  PERFETTO_DCHECK(op == OverlayOp::kIsNotNull);
+  for (uint32_t i = 0; i < res.size(); ++i) {
+    if (non_null_->IsSet(t_iv_overlay_idx.indices[i]))
+      res.Set(i);
+  }
+  return res;
+}
+
+CostEstimatePerRow NullOverlay::EstimateCostPerRow(OverlayOp op) const {
+  // TODO(b/283763282): Replace with benchmarked data.
+  CostEstimatePerRow res;
+
+  // Two |BitVector::CountSetBits| calls.
+  res.to_storage_range = 100;
+
+  // Cost of |BitVector::UpdateSetBits|
+  res.to_table_bit_vector = 100;
+
+  if (op == OverlayOp::kOther) {
+    // Cost of |BitVector::IsSet| and |BitVector::Set|
+    res.is_storage_search_required = 10;
+
+    // Cost of iterating all set bits and looping the index vector divided by
+    // number of indices.
+    res.map_to_storage_index_vector = 100;
+
+    // Won't be called.
+    res.index_search = 0;
+  } else {
+    // Cost of creating trivial BitVector.
+    res.is_storage_search_required = 0;
+
+    // Won't be called
+    res.map_to_storage_index_vector = 0;
+
+    // Cost of calling |BitVector::IsSet|
+    res.index_search = 10;
+  }
+
+  return res;
+}
+
+}  // namespace overlays
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/db/overlays/null_overlay.h b/src/trace_processor/db/overlays/null_overlay.h
new file mode 100644
index 0000000..93da2c0
--- /dev/null
+++ b/src/trace_processor/db/overlays/null_overlay.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_DB_OVERLAYS_NULL_OVERLAY_H_
+#define SRC_TRACE_PROCESSOR_DB_OVERLAYS_NULL_OVERLAY_H_
+
+#include "src/trace_processor/db/overlays/storage_overlay.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace overlays {
+
+// Introduces the layer of nullability - spreads out the storage with nulls
+// using BitVector.
+class NullOverlay : public StorageOverlay {
+ public:
+  explicit NullOverlay(BitVector* null) : non_null_(std::move(null)) {}
+
+  StorageRange MapToStorageRange(TableRange) const override;
+
+  TableBitVector MapToTableBitVector(StorageBitVector) const override;
+
+  BitVector IsStorageLookupRequired(OverlayOp,
+                                    const TableIndexVector&) const override;
+
+  StorageIndexVector MapToStorageIndexVector(TableIndexVector) const override;
+
+  BitVector IndexSearch(OverlayOp, const TableIndexVector&) const override;
+
+  CostEstimatePerRow EstimateCostPerRow(OverlayOp) const override;
+
+ private:
+  // Non null data in the overlay.
+  BitVector* non_null_;
+};
+
+}  // namespace overlays
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_DB_OVERLAYS_NULL_OVERLAY_H_
diff --git a/src/trace_processor/db/overlays/null_overlay_unittest.cc b/src/trace_processor/db/overlays/null_overlay_unittest.cc
new file mode 100644
index 0000000..6f80eba
--- /dev/null
+++ b/src/trace_processor/db/overlays/null_overlay_unittest.cc
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_processor/db/overlays/null_overlay.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace overlays {
+namespace {
+
+TEST(NullOverlay, MapToStorageRangeOutsideBoundary) {
+  BitVector bv{0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0};
+  NullOverlay overlay(&bv);
+  StorageRange r = overlay.MapToStorageRange({RowMap::Range(1, 6)});
+
+  ASSERT_EQ(r.range.start, 0u);
+  ASSERT_EQ(r.range.end, 2u);
+}
+
+TEST(NullOverlay, MapToStorageRangeOnBoundary) {
+  BitVector bv{0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0};
+  NullOverlay overlay(&bv);
+  StorageRange r = overlay.MapToStorageRange({RowMap::Range(3, 8)});
+
+  ASSERT_EQ(r.range.start, 1u);
+  ASSERT_EQ(r.range.end, 4u);
+}
+
+TEST(NullOverlay, MapToTableBitVector) {
+  BitVector bv{0, 1, 1, 0, 0, 1, 1, 0};
+  NullOverlay overlay(&bv);
+
+  BitVector storage_bv{0, 1, 0, 1};
+  TableBitVector table_bv =
+      overlay.MapToTableBitVector({std::move(storage_bv)});
+
+  ASSERT_EQ(table_bv.bv.CountSetBits(), 2u);
+  ASSERT_TRUE(table_bv.bv.IsSet(2));
+  ASSERT_TRUE(table_bv.bv.IsSet(6));
+}
+
+TEST(NullOverlay, IsStorageLookupRequiredNullOp) {
+  BitVector bv{0, 1, 1, 0, 0, 1, 1, 0};
+  NullOverlay overlay(&bv);
+
+  std::vector<uint32_t> table_idx{0, 2, 4, 6};
+  BitVector lookup_bv =
+      overlay.IsStorageLookupRequired(OverlayOp::kIsNull, {table_idx});
+
+  ASSERT_EQ(lookup_bv.size(), 0u);
+}
+
+TEST(NullOverlay, IsStorageLookupRequiredOtherOp) {
+  BitVector bv{0, 1, 1, 0, 0, 1, 1, 0};
+  NullOverlay overlay(&bv);
+
+  std::vector<uint32_t> table_idx{0, 2, 4, 6};
+  BitVector lookup_bv =
+      overlay.IsStorageLookupRequired(OverlayOp::kOther, {table_idx});
+
+  ASSERT_EQ(lookup_bv.size(), 4u);
+  ASSERT_EQ(lookup_bv.CountSetBits(), 2u);
+  ASSERT_TRUE(lookup_bv.IsSet(1));
+  ASSERT_TRUE(lookup_bv.IsSet(3));
+}
+
+TEST(NullOverlay, MapToStorageIndexVector) {
+  BitVector bv{0, 1, 1, 0, 0, 1, 1, 0};
+  NullOverlay overlay(&bv);
+
+  std::vector<uint32_t> table_idx{1, 5, 2};
+  StorageIndexVector storage_iv = overlay.MapToStorageIndexVector({table_idx});
+
+  std::vector<uint32_t> res{0, 2, 1};
+  ASSERT_EQ(storage_iv.indices, res);
+}
+
+TEST(NullOverlay, IndexSearchOtherOp) {
+  BitVector bv{0, 1, 1, 0, 0, 1, 1, 0};
+  NullOverlay overlay(&bv);
+
+  std::vector<uint32_t> table_idx{0, 3, 4};
+  BitVector idx_search_bv = overlay.IndexSearch(OverlayOp::kOther, {table_idx});
+
+  ASSERT_EQ(idx_search_bv.size(), 0u);
+}
+
+TEST(NullOverlay, IndexSearchIsNullOp) {
+  BitVector bv{0, 1, 1, 0, 0, 1, 1, 0};
+  NullOverlay overlay(&bv);
+
+  std::vector<uint32_t> table_idx{0, 3, 4};
+  BitVector idx_search_bv =
+      overlay.IndexSearch(OverlayOp::kIsNull, {table_idx});
+
+  ASSERT_EQ(idx_search_bv.size(), 3u);
+  ASSERT_EQ(idx_search_bv.CountSetBits(), 3u);
+}
+
+TEST(NullOverlay, IndexSearchIsNotNullOp) {
+  BitVector bv{0, 1, 1, 0, 0, 1, 1, 0};
+  NullOverlay overlay(&bv);
+
+  std::vector<uint32_t> table_idx{0, 3, 4};
+  BitVector idx_search_bv =
+      overlay.IndexSearch(OverlayOp::kIsNotNull, {table_idx});
+
+  ASSERT_EQ(idx_search_bv.size(), 3u);
+  ASSERT_EQ(idx_search_bv.CountSetBits(), 0u);
+}
+
+}  // namespace
+}  // namespace overlays
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/db/overlays/storage_overlay.cc b/src/trace_processor/db/overlays/storage_overlay.cc
new file mode 100644
index 0000000..56897f2
--- /dev/null
+++ b/src/trace_processor/db/overlays/storage_overlay.cc
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_processor/db/overlays/storage_overlay.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace overlays {
+
+StorageOverlay::~StorageOverlay() = default;
+
+}  // namespace overlays
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/common/parser_types.h b/src/trace_processor/importers/common/parser_types.h
index 2b5648a..7290626 100644
--- a/src/trace_processor/importers/common/parser_types.h
+++ b/src/trace_processor/importers/common/parser_types.h
@@ -35,10 +35,12 @@
 
 struct alignas(8) InlineSchedWaking {
   int32_t pid;
-  int32_t target_cpu;
-  int32_t prio;
+  uint16_t target_cpu;
+  uint16_t prio;
   StringPool::Id comm;
+  uint16_t common_flags;
 };
+static_assert(sizeof(InlineSchedWaking) == 16);
 
 struct alignas(8) JsonEvent {
   std::string value;
diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.cc b/src/trace_processor/importers/ftrace/ftrace_parser.cc
index ec5f264..6ccf70d 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.cc
@@ -1069,9 +1069,9 @@
   }
   using protos::pbzero::FtraceEvent;
   SchedEventTracker* sched_tracker = SchedEventTracker::GetOrCreate(context_);
-  sched_tracker->PushSchedWakingCompact(cpu, ts,
-                                        static_cast<uint32_t>(data.pid),
-                                        data.target_cpu, data.prio, data.comm);
+  sched_tracker->PushSchedWakingCompact(
+      cpu, ts, static_cast<uint32_t>(data.pid), data.target_cpu, data.prio,
+      data.comm, data.common_flags);
   return util::OkStatus();
 }
 
diff --git a/src/trace_processor/importers/ftrace/ftrace_tokenizer.cc b/src/trace_processor/importers/ftrace/ftrace_tokenizer.cc
index 67fccfe..c699ab9 100644
--- a/src/trace_processor/importers/ftrace/ftrace_tokenizer.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_tokenizer.cc
@@ -247,6 +247,7 @@
   auto tcpu_it = compact.waking_target_cpu(&parse_error);
   auto prio_it = compact.waking_prio(&parse_error);
   auto comm_it = compact.waking_comm_index(&parse_error);
+  auto common_flags_it = compact.waking_common_flags(&parse_error);
 
   for (; timestamp_it && pid_it && tcpu_it && prio_it && comm_it;
        ++timestamp_it, ++pid_it, ++tcpu_it, ++prio_it, ++comm_it) {
@@ -261,8 +262,13 @@
     event.comm = string_table[*comm_it];
 
     event.pid = *pid_it;
-    event.target_cpu = *tcpu_it;
-    event.prio = *prio_it;
+    event.target_cpu = static_cast<uint16_t>(*tcpu_it);
+    event.prio = static_cast<uint16_t>(*prio_it);
+
+    if (common_flags_it) {
+      event.common_flags = static_cast<uint16_t>(*common_flags_it);
+      common_flags_it++;
+    }
 
     base::StatusOr<int64_t> timestamp =
         ResolveTraceTime(context_, clock_id, event_timestamp);
diff --git a/src/trace_processor/importers/ftrace/sched_event_tracker.cc b/src/trace_processor/importers/ftrace/sched_event_tracker.cc
index 68fe3b7..09459c4 100644
--- a/src/trace_processor/importers/ftrace/sched_event_tracker.cc
+++ b/src/trace_processor/importers/ftrace/sched_event_tracker.cc
@@ -212,9 +212,10 @@
 void SchedEventTracker::PushSchedWakingCompact(uint32_t cpu,
                                                int64_t ts,
                                                uint32_t wakee_pid,
-                                               int32_t target_cpu,
-                                               int32_t prio,
-                                               StringId comm_id) {
+                                               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(
@@ -239,10 +240,15 @@
   auto curr_utid = pending_sched->last_utid;
 
   if (PERFETTO_LIKELY(context_->config.ingest_ftrace_in_raw_table)) {
+    tables::FtraceEventTable::Row row;
+    row.ts = ts;
+    row.name = sched_waking_id_;
+    row.cpu = cpu;
+    row.utid = curr_utid;
+    row.common_flags = common_flags;
+
     // Add an entry to the raw table.
-    RawId id = context_->storage->mutable_ftrace_event_table()
-                   ->Insert({ts, sched_waking_id_, cpu, curr_utid})
-                   .id;
+    RawId id = context_->storage->mutable_ftrace_event_table()->Insert(row).id;
 
     using SW = protos::pbzero::SchedWakingFtraceEvent;
     auto inserter = context_->args_tracker->AddArgsTo(id);
@@ -258,8 +264,8 @@
 
   // Add a waking entry to the ThreadState table.
   auto wakee_utid = context_->process_tracker->GetOrCreateThread(wakee_pid);
-  ThreadStateTracker::GetOrCreate(context_)->PushWakingEvent(ts, wakee_utid,
-                                                             curr_utid);
+  ThreadStateTracker::GetOrCreate(context_)->PushWakingEvent(
+      ts, wakee_utid, curr_utid, common_flags);
 }
 
 PERFETTO_ALWAYS_INLINE
diff --git a/src/trace_processor/importers/ftrace/sched_event_tracker.h b/src/trace_processor/importers/ftrace/sched_event_tracker.h
index beebcfe..745b7c3 100644
--- a/src/trace_processor/importers/ftrace/sched_event_tracker.h
+++ b/src/trace_processor/importers/ftrace/sched_event_tracker.h
@@ -73,9 +73,10 @@
   void PushSchedWakingCompact(uint32_t cpu,
                               int64_t ts,
                               uint32_t wakee_pid,
-                              int32_t target_cpu,
-                              int32_t prio,
-                              StringId comm_id);
+                              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.
diff --git a/src/trace_processor/importers/ftrace/thread_state_tracker.cc b/src/trace_processor/importers/ftrace/thread_state_tracker.cc
index 3c6b123..a80d6a4 100644
--- a/src/trace_processor/importers/ftrace/thread_state_tracker.cc
+++ b/src/trace_processor/importers/ftrace/thread_state_tracker.cc
@@ -50,7 +50,8 @@
 
 void ThreadStateTracker::PushWakingEvent(int64_t event_ts,
                                          UniqueTid utid,
-                                         UniqueTid waker_utid) {
+                                         UniqueTid waker_utid,
+                                         std::optional<uint16_t> common_flags) {
   // Only open new runnable state if thread already had a sched switch event.
   if (!HasPreviousRowNumbersForUtid(utid)) {
     return;
@@ -62,12 +63,20 @@
   // is running), we just ignore the waking event. See b/186509316 for details
   // and an example on when this happens. Only blocked events can be waken up.
   if (!IsBlocked(last_row_ref.state())) {
+    // If we receive a waking event while we are not blocked, we ignore this
+    // in the |thread_state| table but we track in the |sched_wakeup| table.
+    // The |thread_state_id| in |sched_wakeup| is the current running/runnable
+    // event.
+    storage_->mutable_spurious_sched_wakeup_table()->Insert(
+        {event_ts, prev_row_numbers_for_thread_[utid]->last_row.row_number(),
+         CommonFlagsToIrqContext(*common_flags), utid, waker_utid});
     return;
   }
 
   // Close the sleeping state and open runnable state.
   ClosePendingState(event_ts, utid, false);
-  AddOpenState(event_ts, utid, runnable_string_id_, std::nullopt, waker_utid);
+  AddOpenState(event_ts, utid, runnable_string_id_, std::nullopt, waker_utid,
+               common_flags);
 }
 
 void ThreadStateTracker::PushNewTaskEvent(int64_t event_ts,
@@ -102,8 +111,9 @@
 void ThreadStateTracker::AddOpenState(int64_t ts,
                                       UniqueTid utid,
                                       StringId state,
-                                      std::optional<uint32_t> cpu,
-                                      std::optional<UniqueTid> waker_utid) {
+                                      std::optional<uint16_t> cpu,
+                                      std::optional<UniqueTid> waker_utid,
+                                      std::optional<uint16_t> common_flags) {
   // Ignore utid 0 because it corresponds to the swapper thread which doesn't
   // make sense to insert.
   if (utid == 0)
@@ -117,6 +127,10 @@
   row.dur = -1;
   row.utid = utid;
   row.state = state;
+  if (common_flags.has_value()) {
+    row.irq_context = CommonFlagsToIrqContext(*common_flags);
+  }
+
   auto row_num = storage_->mutable_thread_state_table()->Insert(row).row_number;
 
   if (utid >= prev_row_numbers_for_thread_.size()) {
@@ -136,6 +150,14 @@
   }
 }
 
+uint32_t ThreadStateTracker::CommonFlagsToIrqContext(uint32_t common_flags) {
+  // If common_flags contains TRACE_FLAG_HARDIRQ | TRACE_FLAG_SOFTIRQ, wakeup
+  // was emitted in interrupt context.
+  // See:
+  // https://cs.android.com/android/kernel/superproject/+/common-android-mainline:common/include/trace/trace_events.h
+  return common_flags & (0x08 | 0x10) ? 1 : 0;
+}
+
 void ThreadStateTracker::ClosePendingState(int64_t end_ts,
                                            UniqueTid utid,
                                            bool data_loss) {
diff --git a/src/trace_processor/importers/ftrace/thread_state_tracker.h b/src/trace_processor/importers/ftrace/thread_state_tracker.h
index 7faae9c..839ad31 100644
--- a/src/trace_processor/importers/ftrace/thread_state_tracker.h
+++ b/src/trace_processor/importers/ftrace/thread_state_tracker.h
@@ -49,7 +49,10 @@
                             UniqueTid next_utid);
 
   // Will add a runnable state for utid and close the previously blocked one.
-  void PushWakingEvent(int64_t event_ts, UniqueTid utid, UniqueTid waker_utid);
+  void PushWakingEvent(int64_t event_ts,
+                       UniqueTid utid,
+                       UniqueTid waker_utid,
+                       std::optional<uint16_t> common_flags = std::nullopt);
 
   // Will add a runnable state for utid. For a new task there are no previous
   // states to close.
@@ -64,10 +67,13 @@
   void AddOpenState(int64_t ts,
                     UniqueTid utid,
                     StringId state,
-                    std::optional<uint32_t> cpu = std::nullopt,
-                    std::optional<UniqueTid> waker_utid = std::nullopt);
+                    std::optional<uint16_t> cpu = std::nullopt,
+                    std::optional<UniqueTid> waker_utid = std::nullopt,
+                    std::optional<uint16_t> common_flags = std::nullopt);
   void ClosePendingState(int64_t end_ts, UniqueTid utid, bool data_loss);
 
+  uint32_t CommonFlagsToIrqContext(uint32_t common_flags);
+
   bool IsRunning(StringId state);
   bool IsBlocked(StringId state);
   bool IsRunnable(StringId state);
diff --git a/src/trace_processor/importers/proto/android_camera_event_module.h b/src/trace_processor/importers/proto/android_camera_event_module.h
index 1d55081..e9df6c4 100644
--- a/src/trace_processor/importers/proto/android_camera_event_module.h
+++ b/src/trace_processor/importers/proto/android_camera_event_module.h
@@ -24,6 +24,7 @@
 #include "protos/perfetto/trace/trace_packet.pbzero.h"
 #include "src/trace_processor/importers/common/parser_types.h"
 #include "src/trace_processor/importers/proto/proto_importer_module.h"
+#include "src/trace_processor/tables/sched_tables_py.h"
 #include "src/trace_processor/tables/slice_tables_py.h"
 #include "src/trace_processor/tables/track_tables_py.h"
 #include "src/trace_processor/types/trace_processor_context.h"
diff --git a/src/trace_processor/importers/proto/statsd_module.h b/src/trace_processor/importers/proto/statsd_module.h
index 430b7a2..653c1eb 100644
--- a/src/trace_processor/importers/proto/statsd_module.h
+++ b/src/trace_processor/importers/proto/statsd_module.h
@@ -26,6 +26,7 @@
 #include "src/trace_processor/importers/common/trace_parser.h"
 #include "src/trace_processor/importers/proto/proto_importer_module.h"
 #include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/tables/sched_tables_py.h"
 #include "src/trace_processor/tables/slice_tables_py.h"
 #include "src/trace_processor/tables/track_tables_py.h"
 #include "src/trace_processor/types/trace_processor_context.h"
diff --git a/src/trace_processor/metrics/sql/android/android_blocking_calls_cuj_metric.sql b/src/trace_processor/metrics/sql/android/android_blocking_calls_cuj_metric.sql
index 3538f7d..0724ba7 100644
--- a/src/trace_processor/metrics/sql/android/android_blocking_calls_cuj_metric.sql
+++ b/src/trace_processor/metrics/sql/android/android_blocking_calls_cuj_metric.sql
@@ -126,6 +126,8 @@
             OR s.name GLOB '*CancellableContinuationImpl*'
             OR s.name GLOB 'relayoutWindow*'
             OR s.name GLOB 'ImageDecoder#decode*'
+            OR s.name GLOB 'NotificationStackScrollLayout#onMeasure'
+            OR s.name GLOB 'ExpNotRow#*'
         )
     UNION ALL
     SELECT
diff --git a/src/trace_processor/metrics/sql/chrome/chrome_scroll_jank_v2.sql b/src/trace_processor/metrics/sql/chrome/chrome_scroll_jank_v2.sql
index 9461d2c..47f1876 100644
--- a/src/trace_processor/metrics/sql/chrome/chrome_scroll_jank_v2.sql
+++ b/src/trace_processor/metrics/sql/chrome/chrome_scroll_jank_v2.sql
@@ -18,6 +18,37 @@
 
 SELECT RUN_METRIC('chrome/event_latency_scroll_jank_cause.sql');
 
+DROP VIEW IF EXISTS chrome_scroll_jank_v2;
+
+CREATE VIEW chrome_scroll_jank_v2
+AS
+SELECT
+  100.0 * scroll_jank_processing_ms / scroll_processing_ms
+    AS scroll_jank_percentage,
+  *
+FROM
+  (
+    SELECT
+      COALESCE(SUM(jank.dur), 0) / 1.0e6 AS scroll_processing_ms,
+      COALESCE(
+        SUM(
+          CASE
+            WHEN
+              jank.jank
+              AND cause.cause_of_jank != 'RendererCompositorQueueingDelay'
+              THEN jank.dur
+            ELSE 0
+            END),
+        0)
+        / 1.0e6 AS scroll_jank_processing_ms
+    FROM
+      scroll_event_latency_jank AS jank
+    LEFT JOIN
+      event_latency_scroll_jank_cause AS cause
+      ON
+        jank.id = cause.slice_id
+  );
+
 DROP VIEW IF EXISTS chrome_scroll_jank_v2_output;
 
 CREATE VIEW chrome_scroll_jank_v2_output
@@ -31,31 +62,4 @@
     'scroll_jank_percentage',
     scroll_jank_percentage)
 FROM
-  (
-    SELECT
-      100.0 * scroll_jank_processing_ms / scroll_processing_ms
-        AS scroll_jank_percentage,
-      *
-    FROM
-      (
-        SELECT
-          COALESCE(SUM(jank.dur), 0) / 1.0e6 AS scroll_processing_ms,
-          COALESCE(
-            SUM(
-              CASE
-                WHEN
-                  jank.jank
-                  AND cause.cause_of_jank != 'RendererCompositorQueueingDelay'
-                  THEN jank.dur
-                ELSE 0
-                END),
-            0)
-            / 1.0e6 AS scroll_jank_processing_ms
-        FROM
-          scroll_event_latency_jank AS jank
-        LEFT JOIN
-          event_latency_scroll_jank_cause AS cause
-          ON
-            jank.id = cause.slice_id
-      )
-  );
+  chrome_scroll_jank_v2;
diff --git a/src/trace_processor/prelude/functions/BUILD.gn b/src/trace_processor/prelude/functions/BUILD.gn
index f2f22ba..ddbf904 100644
--- a/src/trace_processor/prelude/functions/BUILD.gn
+++ b/src/trace_processor/prelude/functions/BUILD.gn
@@ -29,6 +29,8 @@
     "import.h",
     "layout_functions.cc",
     "layout_functions.h",
+    "math.cc",
+    "math.h",
     "pprof_functions.cc",
     "pprof_functions.h",
     "sqlite3_str_split.cc",
diff --git a/src/trace_processor/prelude/functions/math.cc b/src/trace_processor/prelude/functions/math.cc
new file mode 100644
index 0000000..5310501
--- /dev/null
+++ b/src/trace_processor/prelude/functions/math.cc
@@ -0,0 +1,86 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/trace_processor/prelude/functions/math.h"
+
+#include <cmath>
+
+#include "perfetto/base/logging.h"
+#include "perfetto/base/status.h"
+#include "perfetto/trace_processor/basic_types.h"
+#include "src/trace_processor/prelude/functions/sql_function.h"
+#include "src/trace_processor/sqlite/sqlite_engine.h"
+#include "src/trace_processor/sqlite/sqlite_utils.h"
+#include "src/trace_processor/util/status_macros.h"
+
+namespace perfetto::trace_processor {
+
+namespace {
+
+struct Ln : public SqlFunction {
+  static base::Status Run(Context*,
+                          size_t argc,
+                          sqlite3_value** argv,
+                          SqlValue& out,
+                          Destructors&) {
+    PERFETTO_CHECK(argc == 1);
+    switch (sqlite3_value_numeric_type(argv[0])) {
+      case SQLITE_INTEGER:
+      case SQLITE_FLOAT: {
+        double value = sqlite3_value_double(argv[0]);
+        if (value > 0.0) {
+          out = SqlValue::Double(std::log(value));
+        }
+        break;
+      }
+      case SqlValue::kNull:
+      case SqlValue::kString:
+      case SqlValue::kBytes:
+        break;
+    }
+
+    return base::OkStatus();
+  }
+};
+
+struct Exp : public SqlFunction {
+  static base::Status Run(Context*,
+                          size_t argc,
+                          sqlite3_value** argv,
+                          SqlValue& out,
+                          Destructors&) {
+    PERFETTO_CHECK(argc == 1);
+    switch (sqlite3_value_numeric_type(argv[0])) {
+      case SQLITE_INTEGER:
+      case SQLITE_FLOAT:
+        out = SqlValue::Double(std::exp(sqlite3_value_double(argv[0])));
+        break;
+      case SqlValue::kNull:
+      case SqlValue::kString:
+      case SqlValue::kBytes:
+        break;
+    }
+
+    return base::OkStatus();
+  }
+};
+
+}  // namespace
+
+base::Status RegisterMathFunctions(SqliteEngine& engine) {
+  RETURN_IF_ERROR(engine.RegisterSqlFunction<Ln>("ln", 1, nullptr, true));
+  return engine.RegisterSqlFunction<Exp>("exp", 1, nullptr, true);
+}
+
+}  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/prelude/functions/math.h b/src/trace_processor/prelude/functions/math.h
new file mode 100644
index 0000000..42e23f5
--- /dev/null
+++ b/src/trace_processor/prelude/functions/math.h
@@ -0,0 +1,31 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TRACE_PROCESSOR_PRELUDE_FUNCTIONS_MATH_H_
+#define SRC_TRACE_PROCESSOR_PRELUDE_FUNCTIONS_MATH_H_
+
+#include "perfetto/base/status.h"
+
+namespace perfetto::trace_processor {
+
+class SqliteEngine;
+
+// Registers LN and EXP.
+// We do not compile the SQLite library with -DSQLITE_ENABLE_MATH_FUNCTIONS so
+// these functions are not provided by default.
+base::Status RegisterMathFunctions(SqliteEngine& engine);
+
+}  // namespace perfetto::trace_processor
+
+#endif  // SRC_TRACE_PROCESSOR_PRELUDE_FUNCTIONS_MATH_H_
diff --git a/src/trace_processor/prelude/table_functions/tables.py b/src/trace_processor/prelude/table_functions/tables.py
index 4ffc526..32e4550 100644
--- a/src/trace_processor/prelude/table_functions/tables.py
+++ b/src/trace_processor/prelude/table_functions/tables.py
@@ -28,7 +28,7 @@
 from src.trace_processor.tables.metadata_tables import PROCESS_TABLE
 from src.trace_processor.tables.profiler_tables import STACK_PROFILE_CALLSITE_TABLE
 from src.trace_processor.tables.slice_tables import SLICE_TABLE
-from src.trace_processor.tables.slice_tables import SCHED_SLICE_TABLE
+from src.trace_processor.tables.sched_tables import SCHED_SLICE_TABLE
 
 ANCESTOR_SLICE_TABLE = Table(
     python_module=__file__,
diff --git a/src/trace_processor/sqlite/BUILD.gn b/src/trace_processor/sqlite/BUILD.gn
index 7045815..b35605a 100644
--- a/src/trace_processor/sqlite/BUILD.gn
+++ b/src/trace_processor/sqlite/BUILD.gn
@@ -28,6 +28,8 @@
     "sqlite_engine.h",
     "sqlite_table.cc",
     "sqlite_table.h",
+    "sqlite_tokenizer.cc",
+    "sqlite_tokenizer.h",
     "sqlite_utils.cc",
     "sqlite_utils.h",
     "sqlite_utils.h",
@@ -72,6 +74,7 @@
   sources = [
     "db_sqlite_table_unittest.cc",
     "query_constraints_unittest.cc",
+    "sqlite_tokenizer_unittest.cc",
     "sqlite_utils_unittest.cc",
   ]
   deps = [
diff --git a/src/trace_processor/sqlite/sqlite_tokenizer.cc b/src/trace_processor/sqlite/sqlite_tokenizer.cc
new file mode 100644
index 0000000..1766baa
--- /dev/null
+++ b/src/trace_processor/sqlite/sqlite_tokenizer.cc
@@ -0,0 +1,448 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_processor/sqlite/sqlite_tokenizer.h"
+
+#include <ctype.h>
+#include <sqlite3.h>
+#include <optional>
+#include <string_view>
+
+#include "perfetto/base/compiler.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+// The contents of this file are ~copied from SQLite with some modifications to
+// minimize the amount copied: i.e. if we can call a libc function/public SQLite
+// API instead of a private one.
+//
+// The changes are as follows:
+// 1. Remove all ifdefs to only keep branches we actually use
+// 2. Change handling of |CC_KYWD0| to remove distinction between different
+//    SQLite kewords, reducing how many things we need to copy over.
+// 3. Constants are changed from be macro defines to be values in
+//    |SqliteTokenType|.
+
+namespace {
+
+const unsigned char sqlite3CtypeMap[256] = {
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 00..07    ........ */
+    0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, /* 08..0f    ........ */
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 10..17    ........ */
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 18..1f    ........ */
+    0x01, 0x00, 0x80, 0x00, 0x40, 0x00, 0x00, 0x80, /* 20..27     !"#$%&' */
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 28..2f    ()*+,-./ */
+    0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, /* 30..37    01234567 */
+    0x0c, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 38..3f    89:;<=>? */
+
+    0x00, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x02, /* 40..47    @ABCDEFG */
+    0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, /* 48..4f    HIJKLMNO */
+    0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, /* 50..57    PQRSTUVW */
+    0x02, 0x02, 0x02, 0x80, 0x00, 0x00, 0x00, 0x40, /* 58..5f    XYZ[\]^_ */
+    0x80, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x22, /* 60..67    `abcdefg */
+    0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, /* 68..6f    hijklmno */
+    0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, /* 70..77    pqrstuvw */
+    0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, /* 78..7f    xyz{|}~. */
+
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* 80..87    ........ */
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* 88..8f    ........ */
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* 90..97    ........ */
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* 98..9f    ........ */
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* a0..a7    ........ */
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* a8..af    ........ */
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* b0..b7    ........ */
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* b8..bf    ........ */
+
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* c0..c7    ........ */
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* c8..cf    ........ */
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* d0..d7    ........ */
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* d8..df    ........ */
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* e0..e7    ........ */
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* e8..ef    ........ */
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* f0..f7    ........ */
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40  /* f8..ff    ........ */
+};
+
+#define CC_X 0        /* The letter 'x', or start of BLOB literal */
+#define CC_KYWD0 1    /* First letter of a keyword */
+#define CC_KYWD 2     /* Alphabetics or '_'.  Usable in a keyword */
+#define CC_DIGIT 3    /* Digits */
+#define CC_DOLLAR 4   /* '$' */
+#define CC_VARALPHA 5 /* '@', '#', ':'.  Alphabetic SQL variables */
+#define CC_VARNUM 6   /* '?'.  Numeric SQL variables */
+#define CC_SPACE 7    /* Space characters */
+#define CC_QUOTE 8    /* '"', '\'', or '`'.  String literals, quoted ids */
+#define CC_QUOTE2 9   /* '['.   [...] style quoted ids */
+#define CC_PIPE 10    /* '|'.   Bitwise OR or concatenate */
+#define CC_MINUS 11   /* '-'.  Minus or SQL-style comment */
+#define CC_LT 12      /* '<'.  Part of < or <= or <> */
+#define CC_GT 13      /* '>'.  Part of > or >= */
+#define CC_EQ 14      /* '='.  Part of = or == */
+#define CC_BANG 15    /* '!'.  Part of != */
+#define CC_SLASH 16   /* '/'.  / or c-style comment */
+#define CC_LP 17      /* '(' */
+#define CC_RP 18      /* ')' */
+#define CC_SEMI 19    /* ';' */
+#define CC_PLUS 20    /* '+' */
+#define CC_STAR 21    /* '*' */
+#define CC_PERCENT 22 /* '%' */
+#define CC_COMMA 23   /* ',' */
+#define CC_AND 24     /* '&' */
+#define CC_TILDA 25   /* '~' */
+#define CC_DOT 26     /* '.' */
+#define CC_ID 27      /* unicode characters usable in IDs */
+#define CC_NUL 29     /* 0x00 */
+#define CC_BOM 30     /* First byte of UTF8 BOM:  0xEF 0xBB 0xBF */
+
+// clang-format off
+static const unsigned char aiClass[] = {
+/*         x0  x1  x2  x3  x4  x5  x6  x7  x8  x9  xa  xb  xc  xd  xe  xf */
+/* 0x */   29, 28, 28, 28, 28, 28, 28, 28, 28,  7,  7, 28,  7,  7, 28, 28,
+/* 1x */   28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
+/* 2x */    7, 15,  8,  5,  4, 22, 24,  8, 17, 18, 21, 20, 23, 11, 26, 16,
+/* 3x */    3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  5, 19, 12, 14, 13,  6,
+/* 4x */    5,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
+/* 5x */    1,  1,  1,  1,  1,  1,  1,  1,  0,  2,  2,  9, 28, 28, 28,  2,
+/* 6x */    8,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
+/* 7x */    1,  1,  1,  1,  1,  1,  1,  1,  0,  2,  2, 28, 10, 28, 25, 28,
+/* 8x */   27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+/* 9x */   27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+/* Ax */   27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+/* Bx */   27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+/* Cx */   27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+/* Dx */   27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+/* Ex */   27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 30,
+/* Fx */   27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27
+};
+// clang-format on
+
+#define IdChar(C) ((sqlite3CtypeMap[static_cast<unsigned char>(C)] & 0x46) != 0)
+
+// Copy of |sqlite3GetToken| for use by the PerfettoSql transpiler.
+//
+// We copy this function because |sqlite3GetToken| is static to sqlite3.c
+// in most distributions of SQLite so we cannot call it from our code.
+//
+// While we could redefine SQLITE_PRIVATE, pragmatically that will not fly in
+// all the places we build trace processor so we need to resort to making a
+// copy.
+int GetSqliteToken(const unsigned char* z, SqliteTokenType* tokenType) {
+  int i, c;
+  switch (aiClass[*z]) { /* Switch on the character-class of the first byte
+                         ** of the token. See the comment on the CC_ defines
+                         ** above. */
+    case CC_SPACE: {
+      for (i = 1; isspace(z[i]); i++) {
+      }
+      *tokenType = SqliteTokenType::TK_SPACE;
+      return i;
+    }
+    case CC_MINUS: {
+      if (z[1] == '-') {
+        for (i = 2; (c = z[i]) != 0 && c != '\n'; i++) {
+        }
+        *tokenType = SqliteTokenType::TK_SPACE; /* IMP: R-22934-25134 */
+        return i;
+      } else if (z[1] == '>') {
+        *tokenType = SqliteTokenType::TK_PTR;
+        return 2 + (z[2] == '>');
+      }
+      *tokenType = SqliteTokenType::TK_MINUS;
+      return 1;
+    }
+    case CC_LP: {
+      *tokenType = SqliteTokenType::TK_LP;
+      return 1;
+    }
+    case CC_RP: {
+      *tokenType = SqliteTokenType::TK_RP;
+      return 1;
+    }
+    case CC_SEMI: {
+      *tokenType = SqliteTokenType::TK_SEMI;
+      return 1;
+    }
+    case CC_PLUS: {
+      *tokenType = SqliteTokenType::TK_PLUS;
+      return 1;
+    }
+    case CC_STAR: {
+      *tokenType = SqliteTokenType::TK_STAR;
+      return 1;
+    }
+    case CC_SLASH: {
+      if (z[1] != '*' || z[2] == 0) {
+        *tokenType = SqliteTokenType::TK_SLASH;
+        return 1;
+      }
+      for (i = 3, c = z[2]; (c != '*' || z[i] != '/') && (c = z[i]) != 0; i++) {
+      }
+      if (c)
+        i++;
+      *tokenType = SqliteTokenType::TK_SPACE; /* IMP: R-22934-25134 */
+      return i;
+    }
+    case CC_PERCENT: {
+      *tokenType = SqliteTokenType::TK_REM;
+      return 1;
+    }
+    case CC_EQ: {
+      *tokenType = SqliteTokenType::TK_EQ;
+      return 1 + (z[1] == '=');
+    }
+    case CC_LT: {
+      if ((c = z[1]) == '=') {
+        *tokenType = SqliteTokenType::TK_LE;
+        return 2;
+      } else if (c == '>') {
+        *tokenType = SqliteTokenType::TK_NE;
+        return 2;
+      } else if (c == '<') {
+        *tokenType = SqliteTokenType::TK_LSHIFT;
+        return 2;
+      } else {
+        *tokenType = SqliteTokenType::TK_LT;
+        return 1;
+      }
+    }
+    case CC_GT: {
+      if ((c = z[1]) == '=') {
+        *tokenType = SqliteTokenType::TK_GE;
+        return 2;
+      } else if (c == '>') {
+        *tokenType = SqliteTokenType::TK_RSHIFT;
+        return 2;
+      } else {
+        *tokenType = SqliteTokenType::TK_GT;
+        return 1;
+      }
+    }
+    case CC_BANG: {
+      if (z[1] != '=') {
+        *tokenType = SqliteTokenType::TK_ILLEGAL;
+        return 1;
+      } else {
+        *tokenType = SqliteTokenType::TK_NE;
+        return 2;
+      }
+    }
+    case CC_PIPE: {
+      if (z[1] != '|') {
+        *tokenType = SqliteTokenType::TK_BITOR;
+        return 1;
+      } else {
+        *tokenType = SqliteTokenType::TK_CONCAT;
+        return 2;
+      }
+    }
+    case CC_COMMA: {
+      *tokenType = SqliteTokenType::TK_COMMA;
+      return 1;
+    }
+    case CC_AND: {
+      *tokenType = SqliteTokenType::TK_BITAND;
+      return 1;
+    }
+    case CC_TILDA: {
+      *tokenType = SqliteTokenType::TK_BITNOT;
+      return 1;
+    }
+    case CC_QUOTE: {
+      int delim = z[0];
+      for (i = 1; (c = z[i]) != 0; i++) {
+        if (c == delim) {
+          if (z[i + 1] == delim) {
+            i++;
+          } else {
+            break;
+          }
+        }
+      }
+      if (c == '\'') {
+        *tokenType = SqliteTokenType::TK_STRING;
+        return i + 1;
+      } else if (c != 0) {
+        *tokenType = SqliteTokenType::TK_ID;
+        return i + 1;
+      } else {
+        *tokenType = SqliteTokenType::TK_ILLEGAL;
+        return i;
+      }
+    }
+    case CC_DOT: {
+      if (!isdigit(z[1])) {
+        *tokenType = SqliteTokenType::TK_DOT;
+        return 1;
+      }
+      [[fallthrough]];
+    }
+    case CC_DIGIT: {
+      *tokenType = SqliteTokenType::TK_INTEGER;
+      if (z[0] == '0' && (z[1] == 'x' || z[1] == 'X') && isxdigit(z[2])) {
+        for (i = 3; isxdigit(z[i]); i++) {
+        }
+        return i;
+      }
+      for (i = 0; isxdigit(z[i]); i++) {
+      }
+      if (z[i] == '.') {
+        i++;
+        while (isxdigit(z[i])) {
+          i++;
+        }
+        *tokenType = SqliteTokenType::TK_FLOAT;
+      }
+      if ((z[i] == 'e' || z[i] == 'E') &&
+          (isdigit(z[i + 1]) ||
+           ((z[i + 1] == '+' || z[i + 1] == '-') && isdigit(z[i + 2])))) {
+        i += 2;
+        while (isdigit(z[i])) {
+          i++;
+        }
+        *tokenType = SqliteTokenType::TK_FLOAT;
+      }
+      while (IdChar(z[i])) {
+        *tokenType = SqliteTokenType::TK_ILLEGAL;
+        i++;
+      }
+      return i;
+    }
+    case CC_QUOTE2: {
+      for (i = 1, c = z[0]; c != ']' && (c = z[i]) != 0; i++) {
+      }
+      *tokenType =
+          c == ']' ? SqliteTokenType::TK_ID : SqliteTokenType::TK_ILLEGAL;
+      return i;
+    }
+    case CC_VARNUM: {
+      *tokenType = SqliteTokenType::TK_VARIABLE;
+      for (i = 1; isdigit(z[i]); i++) {
+      }
+      return i;
+    }
+    case CC_DOLLAR:
+    case CC_VARALPHA: {
+      int n = 0;
+      *tokenType = SqliteTokenType::TK_VARIABLE;
+      for (i = 1; (c = z[i]) != 0; i++) {
+        if (IdChar(c)) {
+          n++;
+        } else if (c == '(' && n > 0) {
+          do {
+            i++;
+          } while ((c = z[i]) != 0 && !isspace(c) && c != ')');
+          if (c == ')') {
+            i++;
+          } else {
+            *tokenType = SqliteTokenType::TK_ILLEGAL;
+          }
+          break;
+        } else if (c == ':' && z[i + 1] == ':') {
+          i++;
+        } else {
+          break;
+        }
+      }
+      if (n == 0)
+        *tokenType = SqliteTokenType::TK_ILLEGAL;
+      return i;
+    }
+    case CC_KYWD0: {
+      for (i = 1; aiClass[z[i]] <= CC_KYWD; i++) {
+      }
+      if (IdChar(z[i])) {
+        /* This token started out using characters that can appear in keywords,
+        ** but z[i] is a character not allowed within keywords, so this must
+        ** be an identifier instead */
+        i++;
+        break;
+      }
+      if (sqlite3_keyword_check(reinterpret_cast<const char*>(z), i)) {
+        *tokenType = SqliteTokenType::TK_GENERIC_KEYWORD;
+      } else {
+        *tokenType = SqliteTokenType::TK_ID;
+      }
+      return i;
+    }
+    case CC_X: {
+      if (z[1] == '\'') {
+        *tokenType = SqliteTokenType::TK_BLOB;
+        for (i = 2; isdigit(z[i]); i++) {
+        }
+        if (z[i] != '\'' || i % 2) {
+          *tokenType = SqliteTokenType::TK_ILLEGAL;
+          while (z[i] && z[i] != '\'') {
+            i++;
+          }
+        }
+        if (z[i])
+          i++;
+        return i;
+      }
+      [[fallthrough]];
+    }
+    case CC_KYWD:
+    case CC_ID: {
+      i = 1;
+      break;
+    }
+    case CC_BOM: {
+      if (z[1] == 0xbb && z[2] == 0xbf) {
+        *tokenType = SqliteTokenType::TK_SPACE;
+        return 3;
+      }
+      i = 1;
+      break;
+    }
+    case CC_NUL: {
+      *tokenType = SqliteTokenType::TK_ILLEGAL;
+      return 0;
+    }
+    default: {
+      *tokenType = SqliteTokenType::TK_ILLEGAL;
+      return 1;
+    }
+  }
+  while (IdChar(z[i])) {
+    i++;
+  }
+  *tokenType = SqliteTokenType::TK_ID;
+  return i;
+}
+
+}  // namespace
+
+SqliteTokenizer::SqliteTokenizer(const char* sql) : ptr_(sql) {}
+
+SqliteTokenizer::Token SqliteTokenizer::Next() {
+  Token token;
+  const char* start = ptr_;
+  int n = GetSqliteToken(unsigned_ptr(), &token.token_type);
+  ptr_ += n;
+  token.str = std::string_view(start, static_cast<uint32_t>(n));
+  return token;
+}
+
+SqliteTokenizer::Token SqliteTokenizer::NextNonWhitespace() {
+  Token t;
+  for (t = Next(); t.token_type == SqliteTokenType::TK_SPACE; t = Next()) {
+  }
+  return t;
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/sqlite/sqlite_tokenizer.h b/src/trace_processor/sqlite/sqlite_tokenizer.h
new file mode 100644
index 0000000..0d3bfb6
--- /dev/null
+++ b/src/trace_processor/sqlite/sqlite_tokenizer.h
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_SQLITE_SQLITE_TOKENIZER_H_
+#define SRC_TRACE_PROCESSOR_SQLITE_SQLITE_TOKENIZER_H_
+
+#include <optional>
+#include <string_view>
+
+namespace perfetto {
+namespace trace_processor {
+
+// List of token types returnable by |SqliteTokenizer|
+// 1:1 matches the defintions in SQLite.
+enum class SqliteTokenType : uint32_t {
+  TK_SEMI = 1,
+  TK_LP = 22,
+  TK_RP = 23,
+  TK_COMMA = 25,
+  TK_NE = 52,
+  TK_EQ = 53,
+  TK_GT = 54,
+  TK_LE = 55,
+  TK_LT = 56,
+  TK_GE = 57,
+  TK_ID = 59,
+  TK_BITAND = 102,
+  TK_BITOR = 103,
+  TK_LSHIFT = 104,
+  TK_RSHIFT = 105,
+  TK_PLUS = 106,
+  TK_MINUS = 107,
+  TK_STAR = 108,
+  TK_SLASH = 109,
+  TK_REM = 110,
+  TK_CONCAT = 111,
+  TK_PTR = 112,
+  TK_BITNOT = 114,
+  TK_STRING = 117,
+  TK_DOT = 141,
+  TK_FLOAT = 153,
+  TK_BLOB = 154,
+  TK_INTEGER = 155,
+  TK_VARIABLE = 156,
+  TK_SPACE = 183,
+  TK_ILLEGAL = 184,
+
+  // Generic constant which replaces all the keywords in SQLite as we do not
+  // care about the distinguishing between the vast majority of them.
+  TK_GENERIC_KEYWORD = 1000,
+};
+
+// Tokenizes SQL statements according to SQLite SQL language specification:
+// https://www2.sqlite.org/hlr40000.html
+//
+// Usage of this class:
+// SqliteTokenizer tzr;
+// tzr.Reset(my_sql_string.c_str());
+// for (auto t = tzr.Next(); t.token_type != TK_SEMI; t = tzr.Next()) {
+//   // Handle t here
+// }
+class SqliteTokenizer {
+ public:
+  // A single SQL token according to the SQLite standard.
+  struct Token {
+    // The string contents of the token.
+    std::string_view str;
+
+    // The type of the token.
+    SqliteTokenType token_type;
+
+    bool operator==(const Token& o) const {
+      return str == o.str && token_type == o.token_type;
+    }
+  };
+
+  explicit SqliteTokenizer(const char* sql);
+
+  // Returns the next SQL token.
+  Token Next();
+
+  // Returns the next SQL token which is not of type TK_SPACE.
+  Token NextNonWhitespace();
+
+  // Returns the pointer to the start of the next token which will be returned.
+  const char* ptr() const { return ptr_; }
+
+ private:
+  const unsigned char* unsigned_ptr() const {
+    return reinterpret_cast<const unsigned char*>(ptr_);
+  }
+
+  const char* ptr_ = nullptr;
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_SQLITE_SQLITE_TOKENIZER_H_
diff --git a/src/trace_processor/sqlite/sqlite_tokenizer_unittest.cc b/src/trace_processor/sqlite/sqlite_tokenizer_unittest.cc
new file mode 100644
index 0000000..44946b7
--- /dev/null
+++ b/src/trace_processor/sqlite/sqlite_tokenizer_unittest.cc
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_processor/sqlite/sqlite_tokenizer.h"
+#include <vector>
+
+#include "perfetto/base/logging.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace {
+
+using Token = SqliteTokenizer::Token;
+using Type = SqliteTokenType;
+
+class SqliteTokenizerTest : public ::testing::Test {
+ protected:
+  std::vector<SqliteTokenizer::Token> Tokenize(const char* ptr) {
+    SqliteTokenizer tokenizer(ptr);
+    std::vector<SqliteTokenizer::Token> tokens;
+    for (auto t = tokenizer.Next(); !t.str.empty(); t = tokenizer.Next()) {
+      tokens.push_back(t);
+    }
+    return tokens;
+  }
+};
+
+TEST_F(SqliteTokenizerTest, EmptyString) {
+  ASSERT_THAT(Tokenize(""), testing::IsEmpty());
+}
+
+TEST_F(SqliteTokenizerTest, OnlySpace) {
+  ASSERT_THAT(Tokenize(" "), testing::ElementsAre(Token{" ", Type::TK_SPACE}));
+}
+
+TEST_F(SqliteTokenizerTest, SpaceColon) {
+  ASSERT_THAT(Tokenize(" ;"), testing::ElementsAre(Token{" ", Type::TK_SPACE},
+                                                   Token{";", Type::TK_SEMI}));
+}
+
+TEST_F(SqliteTokenizerTest, Select) {
+  ASSERT_THAT(
+      Tokenize("SELECT * FROM slice;"),
+      testing::ElementsAre(
+          Token{"SELECT", Type::TK_GENERIC_KEYWORD}, Token{" ", Type::TK_SPACE},
+          Token{"*", Type::TK_STAR}, Token{" ", Type::TK_SPACE},
+          Token{"FROM", Type::TK_GENERIC_KEYWORD}, Token{" ", Type::TK_SPACE},
+          Token{"slice", Type::TK_ID}, Token{";", Type::TK_SEMI}));
+}
+
+}  // namespace
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/stdlib/chrome/BUILD.gn b/src/trace_processor/stdlib/chrome/BUILD.gn
index 7ba85fd..e9881c6 100644
--- a/src/trace_processor/stdlib/chrome/BUILD.gn
+++ b/src/trace_processor/stdlib/chrome/BUILD.gn
@@ -18,5 +18,7 @@
   sources = [
     "chrome_scrolls.sql",
     "cpu_powerups.sql",
+    "histograms.sql",
+    "speedometer.sql",
   ]
 }
diff --git a/src/trace_processor/stdlib/chrome/histograms.sql b/src/trace_processor/stdlib/chrome/histograms.sql
new file mode 100644
index 0000000..db354e3
--- /dev/null
+++ b/src/trace_processor/stdlib/chrome/histograms.sql
@@ -0,0 +1,46 @@
+-- Copyright 2023 The Android Open Source Project
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--     https://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+DROP VIEW IF EXISTS chrome_histograms;
+
+-- A helper view on top of the histogram events emitted by Chrome.
+-- Requires "disabled-by-default-histogram_samples" Chrome category.
+--
+-- @column name          The name of the histogram.
+-- @column value         The value of the histogram sample.
+-- @column ts            Alias of |slice.ts|.
+-- @column thread_name   Thread name.
+-- @column utid          Utid of the thread.
+-- @column tid           Tid of the thread.
+-- @column process_name  Process name.
+-- @column upid          Upid of the process.
+-- @column pid           Pid of the process.
+CREATE VIEW chrome_histograms AS
+SELECT
+  extract_arg(slice.arg_set_id, "chrome_histogram_sample.name") as name,
+  extract_arg(slice.arg_set_id, "chrome_histogram_sample.sample") as value,
+  ts,
+  thread.name as thread_name,
+  thread.utid as utid,
+  thread.tid as tid,
+  process.name as process_name,
+  process.upid as upid,
+  process.pid as pid
+FROM slice
+JOIN thread_track ON thread_track.id = slice.track_id
+JOIN thread USING (utid)
+JOIN process USING (upid)
+WHERE
+  slice.name = "HistogramSample"
+  AND category = "disabled-by-default-histogram_samples";
\ No newline at end of file
diff --git a/src/trace_processor/stdlib/chrome/speedometer.sql b/src/trace_processor/stdlib/chrome/speedometer.sql
new file mode 100644
index 0000000..652940a
--- /dev/null
+++ b/src/trace_processor/stdlib/chrome/speedometer.sql
@@ -0,0 +1,202 @@
+-- Copyright 2023 The Android Open Source Project
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--     https://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+-- Annotates a trace with Speedometer 2.1 related information.
+--
+-- The scripts below analyse traces with the following tracing options
+-- enabled:
+--
+--  - Chromium:
+--      "blink.user_timing".
+--
+--  NOTE: A regular speedometer run (e.g. from the website) will generate the
+--  required events. No need to add any extra JS or anything.
+--
+-- Noteworthy tables:
+--   speedometer_mark: List of marks (event slices) emitted by Speedometer.
+--       These are the points in time Speedometer makes a clock reading to
+--       compute intervals of time for the final score.
+--   speedometer_measure_slice: Augmented slices for Speedometer measurements.
+--       These are the intervals of time Speedometer uses to compute the final
+--       score.
+--   speedometer_iteration_slice: Slice that covers one Speedometer iteration
+--       and has the total_time and score for it. If you average all the scores
+--       over all iterations you get the final Speedometer score for the run.
+
+-- List of marks (event slices) emitted by Speedometer.
+-- These are the points in time Speedometer makes a clock reading to compute
+-- intervals of time for the final score.
+--
+-- @column slice_id      Slice this data refers to.
+-- @column iteration     Speedometer iteration the mark belongs to.
+-- @column suite_name    Suite name
+-- @column test_name     Test name
+-- @column mark_type     Type of mark (start, sync-end, async-end)
+CREATE VIEW internal_chrome_speedometer_mark
+AS
+WITH
+  speedometer_21_suite_name(suite_name) AS (
+    VALUES
+      ('VanillaJS-TodoMVC'),
+      ('Vanilla-ES2015-TodoMVC'),
+      ('Vanilla-ES2015-Babel-Webpack-TodoMVC'),
+      ('React-TodoMVC'),
+      ('React-Redux-TodoMVC'),
+      ('EmberJS-TodoMVC'),
+      ('EmberJS-Debug-TodoMVC'),
+      ('BackboneJS-TodoMVC'),
+      ('AngularJS-TodoMVC'),
+      ('Angular2-TypeScript-TodoMVC'),
+      ('VueJS-TodoMVC'),
+      ('jQuery-TodoMVC'),
+      ('Preact-TodoMVC'),
+      ('Inferno-TodoMVC'),
+      ('Elm-TodoMVC'),
+      ('Flight-TodoMVC')
+  ),
+  speedometer_21_test_name(test_name) AS (
+    VALUES
+      ('Adding100Items'),
+      ('CompletingAllItems'),
+      -- This seems to be an issue with Speedometer 2.1. All tests delete all items,
+      -- but for some reason the test names do not match for all suites.
+      ('DeletingAllItems'),
+      ('DeletingItems')
+  ),
+  speedometer_21_test_mark_type(mark_type) AS (
+    VALUES
+      ('start'),
+      ('sync-end'),
+      ('async-end')
+  ),
+  -- Make sure we only look at slices with names we expect.
+  speedometer_mark_name AS (
+    SELECT
+      s.suite_name || '.' || t.test_name || '-' || m.mark_type AS name,
+      s.suite_name,
+      t.test_name,
+      m.mark_type
+    FROM
+      speedometer_21_suite_name AS s,
+      speedometer_21_test_name AS t,
+      speedometer_21_test_mark_type AS m
+  )
+SELECT
+  s.id AS slice_id,
+  RANK() OVER (PARTITION BY name ORDER BY ts ASC) AS iteration,
+  m.suite_name,
+  m.test_name,
+  m.mark_type
+FROM slice AS s
+JOIN speedometer_mark_name AS m
+  USING (name)
+WHERE category = 'blink.user_timing';
+
+-- Augmented slices for Speedometer measurements.
+-- These are the intervals of time Speedometer uses to compute the final score.
+-- There are two intervals that are measured for every test: sync and async
+-- sync is the time between the start and sync-end marks, async is the time
+-- between the sync-end and async-end marks.
+--
+-- @column iteration     Speedometer iteration the mark belongs to.
+-- @column suite_name    Suite name
+-- @column test_name     Test name
+-- @column measure_type  Type of the measure (sync or async)
+-- @column ts            Start timestamp of the measure
+-- @column dur           Duration of the measure
+CREATE VIEW chrome_speedometer_measure
+AS
+WITH
+  -- Get the 3 test timestamps (start, sync-end, async-end) in one row. Using a
+  -- the LAG window function and partitioning by test. 2 out of the 3 rows
+  -- generated per test will have some NULL ts values.
+  augmented AS (
+    SELECT
+      iteration,
+      suite_name,
+      test_name,
+      ts AS async_end_ts,
+      LAG(ts, 1)
+        OVER (PARTITION BY iteration, suite_name, test_name ORDER BY ts ASC)
+        AS sync_end_ts,
+      LAG(ts, 2)
+        OVER (PARTITION BY iteration, suite_name, test_name ORDER BY ts ASC)
+        AS start_ts,
+      COUNT()
+        OVER (PARTITION BY iteration, suite_name, test_name ORDER BY ts ASC)
+        AS mark_count
+    FROM internal_chrome_speedometer_mark
+    JOIN slice
+      USING (slice_id)
+  ),
+  filtered AS (
+    SELECT *
+    FROM augmented
+    -- This server 2 purposes: make sure we have all the marks (think truncated
+    -- trace), and remove the NULL ts values due to the LAG window function.
+    WHERE mark_count = 3
+  )
+SELECT
+  iteration,
+  suite_name,
+  test_name,
+  'async' AS measure_type,
+  sync_end_ts AS ts,
+  async_end_ts - sync_end_ts AS dur
+FROM filtered
+UNION ALL
+SELECT
+  iteration,
+  suite_name,
+  test_name,
+  'sync' AS measure_type,
+  start_ts AS ts,
+  sync_end_ts - start_ts AS dur
+FROM filtered;
+
+-- Slice that covers one Speedometer iteration.
+-- This slice is actually estimated as a default Speedometer run will not emit
+-- marks to cover this interval. The metrics associated are the same ones
+-- Speedometer would output, but note we use ns precision (Speedometer uses
+-- ~100us) so the actual values might differ a bit. Also note Speedometer
+-- returns the values in ms these here and in ns.
+--
+-- @column iteration Speedometer iteration.
+-- @column ts        Start timestamp of the iteration
+-- @column dur       Duration of the iteration
+-- @column total     Total duration of the measures in this iteration
+-- @column mean      Average suite duration for this iteration.
+-- @column geomean   Geometric mean of the suite durations for this iteration.
+-- @column score     Speedometer score for this iteration (The total score for a
+--                   run in the average of all iteration scores).
+CREATE VIEW chrome_speedometer_iteration
+AS
+SELECT
+  iteration,
+  MIN(start) AS ts,
+  MAX(end) - MIN(start) AS dur,
+  SUM(suite_total) AS total,
+  AVG(suite_total)AS mean,
+  -- Compute geometric mean using LN instead of multiplication to prevent
+  -- overflows
+  EXP(AVG(LN(suite_total))) AS geomean,
+  1e9 / EXP(AVG(LN(suite_total))) * 60 / 3 AS score
+FROM
+  (
+    SELECT
+      iteration, SUM(dur) AS suite_total, MIN(ts) AS start, MAX(ts + dur) AS end
+    FROM chrome_speedometer_measure
+    GROUP BY suite_name, iteration
+  )
+GROUP BY iteration;
diff --git a/src/trace_processor/storage/trace_storage.h b/src/trace_processor/storage/trace_storage.h
index 66fc940..67bdd0b 100644
--- a/src/trace_processor/storage/trace_storage.h
+++ b/src/trace_processor/storage/trace_storage.h
@@ -42,6 +42,7 @@
 #include "src/trace_processor/tables/memory_tables_py.h"
 #include "src/trace_processor/tables/metadata_tables_py.h"
 #include "src/trace_processor/tables/profiler_tables_py.h"
+#include "src/trace_processor/tables/sched_tables_py.h"
 #include "src/trace_processor/tables/slice_tables_py.h"
 #include "src/trace_processor/tables/trace_proto_tables_py.h"
 #include "src/trace_processor/tables/track_tables_py.h"
@@ -457,6 +458,13 @@
   const tables::SliceTable& slice_table() const { return slice_table_; }
   tables::SliceTable* mutable_slice_table() { return &slice_table_; }
 
+  const tables::SpuriousSchedWakeupTable& spurious_sched_wakeup_table() const {
+    return spurious_sched_wakeup_table_;
+  }
+  tables::SpuriousSchedWakeupTable* mutable_spurious_sched_wakeup_table() {
+    return &spurious_sched_wakeup_table_;
+  }
+
   const tables::FlowTable& flow_table() const { return flow_table_; }
   tables::FlowTable* mutable_flow_table() { return &flow_table_; }
 
@@ -888,6 +896,8 @@
   // Slices from CPU scheduling data.
   tables::SchedSliceTable sched_slice_table_{&string_pool_};
 
+  tables::SpuriousSchedWakeupTable spurious_sched_wakeup_table_{&string_pool_};
+
   // Additional attributes for virtual track slices (sub-type of
   // NestableSlices).
   VirtualTrackSlices virtual_track_slices_;
diff --git a/src/trace_processor/tables/BUILD.gn b/src/trace_processor/tables/BUILD.gn
index 9ec397f..b8d69a8 100644
--- a/src/trace_processor/tables/BUILD.gn
+++ b/src/trace_processor/tables/BUILD.gn
@@ -23,6 +23,7 @@
     "memory_tables.py",
     "metadata_tables.py",
     "profiler_tables.py",
+    "sched_tables.py",
     "slice_tables.py",
     "trace_proto_tables.py",
     "track_tables.py",
diff --git a/src/trace_processor/tables/metadata_tables.py b/src/trace_processor/tables/metadata_tables.py
index 5d800d7..093db01 100644
--- a/src/trace_processor/tables/metadata_tables.py
+++ b/src/trace_processor/tables/metadata_tables.py
@@ -170,6 +170,7 @@
         C('cpu', CppUint32()),
         C('utid', CppTableId(THREAD_TABLE)),
         C('arg_set_id', CppUint32()),
+        C('common_flags', CppUint32())
     ],
     tabledoc=TableDoc(
         doc='''
@@ -193,7 +194,9 @@
             'cpu':
                 'The CPU this event was emitted on.',
             'utid':
-                'The thread this event was emitted on.'
+                'The thread this event was emitted on.',
+            'common_flags':
+                'Ftrace event flags for this event. Currently only emitted for sched_waking events.'
         }))
 
 FTRACE_EVENT_TABLE = Table(
diff --git a/src/trace_processor/tables/sched_tables.py b/src/trace_processor/tables/sched_tables.py
new file mode 100644
index 0000000..d65c8be
--- /dev/null
+++ b/src/trace_processor/tables/sched_tables.py
@@ -0,0 +1,166 @@
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains tables for relevant for sched."""
+
+from python.generators.trace_processor_table.public import Column as C
+from python.generators.trace_processor_table.public import ColumnDoc
+from python.generators.trace_processor_table.public import ColumnFlag
+from python.generators.trace_processor_table.public import CppInt32
+from python.generators.trace_processor_table.public import CppInt64
+from python.generators.trace_processor_table.public import CppOptional
+from python.generators.trace_processor_table.public import CppSelfTableId
+from python.generators.trace_processor_table.public import CppString
+from python.generators.trace_processor_table.public import CppTableId
+from python.generators.trace_processor_table.public import CppUint32
+from python.generators.trace_processor_table.public import Table
+from python.generators.trace_processor_table.public import TableDoc
+from python.generators.trace_processor_table.public import WrappingSqlView
+
+SCHED_SLICE_TABLE = Table(
+    python_module=__file__,
+    class_name='SchedSliceTable',
+    sql_name='sched_slice',
+    columns=[
+        C('ts', CppInt64(), flags=ColumnFlag.SORTED),
+        C('dur', CppInt64()),
+        C('cpu', CppUint32()),
+        C('utid', CppUint32()),
+        C('end_state', CppString()),
+        C('priority', CppInt32()),
+    ],
+    tabledoc=TableDoc(
+        doc='''
+          This table holds slices with kernel thread scheduling information.
+          These slices are collected when the Linux "ftrace" data source is
+          used with the "sched/switch" and "sched/wakeup*" events enabled.
+
+          The rows in this table will always have a matching row in the
+          |thread_state| table with |thread_state.state| = 'Running'
+        ''',
+        group='Events',
+        columns={
+            'ts':
+                '''The timestamp at the start of the slice (in nanoseconds).''',
+            'dur':
+                '''The duration of the slice (in nanoseconds).''',
+            'utid':
+                '''The thread's unique id in the trace..''',
+            'cpu':
+                '''The CPU that the slice executed on.''',
+            'end_state':
+                '''
+                  A string representing the scheduling state of the kernel
+                  thread at the end of the slice.  The individual characters in
+                  the string mean the following: R (runnable), S (awaiting a
+                  wakeup), D (in an uninterruptible sleep), T (suspended),
+                  t (being traced), X (exiting), P (parked), W (waking),
+                  I (idle), N (not contributing to the load average),
+                  K (wakeable on fatal signals) and Z (zombie, awaiting
+                  cleanup).
+                ''',
+            'priority':
+                '''The kernel priority that the thread ran at.'''
+        }))
+
+SPURIOUS_SCHED_WAKEUP_TABLE = Table(
+    python_module=__file__,
+    class_name='SpuriousSchedWakeupTable',
+    sql_name='spurious_sched_wakeup',
+    columns=[
+        C('ts', CppInt64(), flags=ColumnFlag.SORTED),
+        C('thread_state_id', CppInt64()),
+        C('irq_context', CppOptional(CppUint32())),
+        C('utid', CppUint32()),
+        C('waker_utid', CppUint32()),
+    ],
+    tabledoc=TableDoc(
+        doc='''
+          This table contains the scheduling wakeups that occurred while a thread was
+          not blocked, i.e. running or runnable. Such wakeups are not tracked in the
+          |thread_state_table|.
+        ''',
+        group='Events',
+        columns={
+            'ts':
+                'The timestamp at the start of the slice (in nanoseconds).',
+            'thread_state_id':
+                'The id of the row in the thread_state table that this row is associated with.',
+            'irq_context':
+                '''Whether the wakeup was from interrupt context or process context.''',
+            'utid':
+                '''The thread's unique id in the trace..''',
+            'waker_utid':
+                '''
+                  The unique thread id of the thread which caused a wakeup of
+                  this thread.
+                '''
+        }))
+
+THREAD_STATE_TABLE = Table(
+    python_module=__file__,
+    class_name='ThreadStateTable',
+    sql_name='thread_state',
+    columns=[
+        C('ts', CppInt64(), flags=ColumnFlag.SORTED),
+        C('dur', CppInt64()),
+        C('cpu', CppOptional(CppUint32())),
+        C('utid', CppUint32()),
+        C('state', CppString()),
+        C('io_wait', CppOptional(CppUint32())),
+        C('blocked_function', CppOptional(CppString())),
+        C('waker_utid', CppOptional(CppUint32())),
+        C('irq_context', CppOptional(CppUint32())),
+    ],
+    tabledoc=TableDoc(
+        doc='''
+          This table contains the scheduling state of every thread on the
+          system during the trace.
+
+          The rows in this table which have |state| = 'Running', will have a
+          corresponding row in the |sched_slice| table.
+        ''',
+        group='Events',
+        columns={
+            'ts':
+                'The timestamp at the start of the slice (in nanoseconds).',
+            'dur':
+                'The duration of the slice (in nanoseconds).',
+            'cpu':
+                '''The CPU that the slice executed on.''',
+            'irq_context':
+                '''Whether the wakeup was from interrupt context or process context.''',
+            'utid':
+                '''The thread's unique id in the trace..''',
+            'state':
+                '''
+                  The scheduling state of the thread. Can be "Running" or any
+                  of the states described in |sched_slice.end_state|.
+                ''',
+            'io_wait':
+                'Indicates whether this thread was blocked on IO.',
+            'blocked_function':
+                'The function in the kernel this thread was blocked on.',
+            'waker_utid':
+                '''
+                  The unique thread id of the thread which caused a wakeup of
+                  this thread.
+                '''
+        }))
+
+# Keep this list sorted.
+ALL_TABLES = [
+    SCHED_SLICE_TABLE,
+    SPURIOUS_SCHED_WAKEUP_TABLE,
+    THREAD_STATE_TABLE,
+]
diff --git a/src/trace_processor/tables/slice_tables.py b/src/trace_processor/tables/slice_tables.py
index 8d85411..f1a4d90 100644
--- a/src/trace_processor/tables/slice_tables.py
+++ b/src/trace_processor/tables/slice_tables.py
@@ -120,100 +120,6 @@
                 ''',
         }))
 
-SCHED_SLICE_TABLE = Table(
-    python_module=__file__,
-    class_name='SchedSliceTable',
-    sql_name='sched_slice',
-    columns=[
-        C('ts', CppInt64(), flags=ColumnFlag.SORTED),
-        C('dur', CppInt64()),
-        C('cpu', CppUint32()),
-        C('utid', CppUint32()),
-        C('end_state', CppString()),
-        C('priority', CppInt32()),
-    ],
-    tabledoc=TableDoc(
-        doc='''
-          This table holds slices with kernel thread scheduling information.
-          These slices are collected when the Linux "ftrace" data source is
-          used with the "sched/switch" and "sched/wakeup*" events enabled.
-
-          The rows in this table will always have a matching row in the
-          |thread_state| table with |thread_state.state| = 'Running'
-        ''',
-        group='Events',
-        columns={
-            'ts':
-                '''The timestamp at the start of the slice (in nanoseconds).''',
-            'dur':
-                '''The duration of the slice (in nanoseconds).''',
-            'utid':
-                '''The thread's unique id in the trace..''',
-            'cpu':
-                '''The CPU that the slice executed on.''',
-            'end_state':
-                '''
-                  A string representing the scheduling state of the kernel
-                  thread at the end of the slice.  The individual characters in
-                  the string mean the following: R (runnable), S (awaiting a
-                  wakeup), D (in an uninterruptible sleep), T (suspended),
-                  t (being traced), X (exiting), P (parked), W (waking),
-                  I (idle), N (not contributing to the load average),
-                  K (wakeable on fatal signals) and Z (zombie, awaiting
-                  cleanup).
-                ''',
-            'priority':
-                '''The kernel priority that the thread ran at.'''
-        }))
-
-THREAD_STATE_TABLE = Table(
-    python_module=__file__,
-    class_name='ThreadStateTable',
-    sql_name='thread_state',
-    columns=[
-        C('ts', CppInt64(), flags=ColumnFlag.SORTED),
-        C('dur', CppInt64()),
-        C('cpu', CppOptional(CppUint32())),
-        C('utid', CppUint32()),
-        C('state', CppString()),
-        C('io_wait', CppOptional(CppUint32())),
-        C('blocked_function', CppOptional(CppString())),
-        C('waker_utid', CppOptional(CppUint32())),
-    ],
-    tabledoc=TableDoc(
-        doc='''
-          This table contains the scheduling state of every thread on the
-          system during the trace.
-
-          The rows in this table which have |state| = 'Running', will have a
-          corresponding row in the |sched_slice| table.
-        ''',
-        group='Events',
-        columns={
-            'ts':
-                'The timestamp at the start of the slice (in nanoseconds).',
-            'dur':
-                'The duration of the slice (in nanoseconds).',
-            'cpu':
-                '''The CPU that the slice executed on.''',
-            'utid':
-                '''The thread's unique id in the trace..''',
-            'state':
-                '''
-                  The scheduling state of the thread. Can be "Running" or any
-                  of the states described in |sched_slice.end_state|.
-                ''',
-            'io_wait':
-                'Indicates whether this thread was blocked on IO.',
-            'blocked_function':
-                'The function in the kernel this thread was blocked on.',
-            'waker_utid':
-                '''
-                  The unique thread id of the thread which caused a wakeup of
-                  this thread.
-                '''
-        }))
-
 GPU_SLICE_TABLE = Table(
     python_module=__file__,
     class_name='GpuSliceTable',
@@ -380,7 +286,5 @@
     EXPERIMENTAL_FLAT_SLICE_TABLE,
     GPU_SLICE_TABLE,
     GRAPHICS_FRAME_SLICE_TABLE,
-    SCHED_SLICE_TABLE,
     SLICE_TABLE,
-    THREAD_STATE_TABLE,
 ]
diff --git a/src/trace_processor/tables/table_destructors.cc b/src/trace_processor/tables/table_destructors.cc
index 3dcf113..ba57b22 100644
--- a/src/trace_processor/tables/table_destructors.cc
+++ b/src/trace_processor/tables/table_destructors.cc
@@ -20,6 +20,7 @@
 #include "src/trace_processor/tables/memory_tables_py.h"
 #include "src/trace_processor/tables/metadata_tables_py.h"
 #include "src/trace_processor/tables/profiler_tables_py.h"
+#include "src/trace_processor/tables/sched_tables_py.h"
 #include "src/trace_processor/tables/slice_tables_py.h"
 #include "src/trace_processor/tables/trace_proto_tables_py.h"
 #include "src/trace_processor/tables/track_tables_py.h"
@@ -71,13 +72,16 @@
 ProfilerSmapsTable::~ProfilerSmapsTable() = default;
 GpuCounterGroupTable::~GpuCounterGroupTable() = default;
 
+// sched_tables_py.h
+SchedSliceTable::~SchedSliceTable() = default;
+SpuriousSchedWakeupTable::~SpuriousSchedWakeupTable() = default;
+ThreadStateTable::~ThreadStateTable() = default;
+
 // slice_tables_py.h
 SliceTable::~SliceTable() = default;
 FlowTable::~FlowTable() = default;
-SchedSliceTable::~SchedSliceTable() = default;
 GpuSliceTable::~GpuSliceTable() = default;
 GraphicsFrameSliceTable::~GraphicsFrameSliceTable() = default;
-ThreadStateTable::~ThreadStateTable() = default;
 ExpectedFrameTimelineSliceTable::~ExpectedFrameTimelineSliceTable() = default;
 ActualFrameTimelineSliceTable::~ActualFrameTimelineSliceTable() = default;
 ExperimentalFlatSliceTable::~ExperimentalFlatSliceTable() = default;
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index f64b1c1..c25595d 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -52,6 +52,7 @@
 #include "src/trace_processor/prelude/functions/create_view_function.h"
 #include "src/trace_processor/prelude/functions/import.h"
 #include "src/trace_processor/prelude/functions/layout_functions.h"
+#include "src/trace_processor/prelude/functions/math.h"
 #include "src/trace_processor/prelude/functions/pprof_functions.h"
 #include "src/trace_processor/prelude/functions/sql_function.h"
 #include "src/trace_processor/prelude/functions/sqlite3_str_split.h"
@@ -565,6 +566,11 @@
     if (!status.ok())
       PERFETTO_ELOG("%s", status.c_message());
   }
+  {
+    base::Status status = RegisterMathFunctions(engine_);
+    if (!status.ok())
+      PERFETTO_ELOG("%s", status.c_message());
+  }
 
   const TraceStorage* storage = context_.storage.get();
 
@@ -650,6 +656,7 @@
   RegisterDbTable(storage->flow_table());
   RegisterDbTable(storage->slice_table());
   RegisterDbTable(storage->sched_slice_table());
+  RegisterDbTable(storage->spurious_sched_wakeup_table());
   RegisterDbTable(storage->thread_state_table());
   RegisterDbTable(storage->gpu_slice_table());
 
diff --git a/src/tracing/test/api_integrationtest.cc b/src/tracing/test/api_integrationtest.cc
index 0755e02..7fdc70c 100644
--- a/src/tracing/test/api_integrationtest.cc
+++ b/src/tracing/test/api_integrationtest.cc
@@ -3472,7 +3472,8 @@
       "foo", perfetto::DynamicString{std::string("Event5")},
       ::perfetto::protos::pbzero::TrackEvent::TYPE_SLICE_END);
   PERFETTO_INTERNAL_TRACK_EVENT(
-      "foo", "Event6", ::perfetto::protos::pbzero::TrackEvent::TYPE_SLICE_END);
+      "foo", perfetto::StaticString{"Event6"},
+      ::perfetto::protos::pbzero::TrackEvent::TYPE_SLICE_END);
 
   auto slices = StopSessionAndReadSlicesFromTrace(tracing_session);
   ASSERT_EQ(6u, slices.size());
diff --git a/test/data/chrome_5672_histograms.pftrace.gz.sha256 b/test/data/chrome_5672_histograms.pftrace.gz.sha256
new file mode 100644
index 0000000..5d22333
--- /dev/null
+++ b/test/data/chrome_5672_histograms.pftrace.gz.sha256
@@ -0,0 +1 @@
+a09bd44078ac71bcfbc901b0544750e8344d0d0f6f96e220f700a5a53fa932ee
\ No newline at end of file
diff --git a/test/data/sched_wakeup_trace.atr.sha256 b/test/data/sched_wakeup_trace.atr.sha256
new file mode 100644
index 0000000..496b259
--- /dev/null
+++ b/test/data/sched_wakeup_trace.atr.sha256
@@ -0,0 +1 @@
+ae61181ded60bf214859c2072b90dca49226338901d368e6aea329681bff30db
\ No newline at end of file
diff --git a/test/data/speedometer.perfetto_trace.gz.sha256 b/test/data/speedometer.perfetto_trace.gz.sha256
new file mode 100644
index 0000000..d330773
--- /dev/null
+++ b/test/data/speedometer.perfetto_trace.gz.sha256
@@ -0,0 +1 @@
+8a159b354d74a3ca0d38ce9cd071ef47de322db4261ee266bfafe04d70310529
\ No newline at end of file
diff --git a/test/trace_processor/diff_tests/android/android_blocking_calls_cuj_metric.out b/test/trace_processor/diff_tests/android/android_blocking_calls_cuj_metric.out
index d75d44d..c4e2b2b 100644
--- a/test/trace_processor/diff_tests/android/android_blocking_calls_cuj_metric.out
+++ b/test/trace_processor/diff_tests/android/android_blocking_calls_cuj_metric.out
@@ -44,7 +44,7 @@
       pid: 3000
     }
     ts: 2000000
-    dur: 150000000
+    dur: 180000000
     blocking_calls {
       name: "Contending for pthread mutex"
       cnt: 1
@@ -60,6 +60,20 @@
       min_dur_ms: 10
     }
     blocking_calls {
+      name: "ExpNotRow#onMeasure(BigTextStyle)"
+      cnt: 1
+      total_dur_ms: 10
+      max_dur_ms: 10
+      min_dur_ms: 10
+    }
+    blocking_calls {
+      name: "ExpNotRow#onMeasure(MessagingStyle)"
+      cnt: 1
+      total_dur_ms: 10
+      max_dur_ms: 10
+      min_dur_ms: 10
+    }
+    blocking_calls {
       name: "ImageDecoder#decodeBitmap"
       cnt: 1
       total_dur_ms: 10
@@ -88,6 +102,13 @@
       min_dur_ms: 10
     }
     blocking_calls {
+      name: "NotificationStackScrollLayout#onMeasure"
+      cnt: 1
+      total_dur_ms: 10
+      max_dur_ms: 10
+      min_dur_ms: 10
+    }
+    blocking_calls {
       name: "SuspendThreadByThreadId <...>"
       cnt: 1
       total_dur_ms: 10
diff --git a/test/trace_processor/diff_tests/android/android_blocking_calls_cuj_metric.py b/test/trace_processor/diff_tests/android/android_blocking_calls_cuj_metric.py
index 1039e47..7ec8cb9 100755
--- a/test/trace_processor/diff_tests/android/android_blocking_calls_cuj_metric.py
+++ b/test/trace_processor/diff_tests/android/android_blocking_calls_cuj_metric.py
@@ -26,11 +26,13 @@
 # List of blocking calls
 blocking_call_names = [
     'monitor contention with something else', 'SuspendThreadByThreadId 123',
-    'LoadApkAssetsFd 123', 'binder transaction',
-    'inflate', 'Lock contention on thread list lock (owner tid: 1665)',
+    'LoadApkAssetsFd 123', 'binder transaction', 'inflate',
+    'Lock contention on thread list lock (owner tid: 1665)',
     'CancellableContinuationImpl#123', 'relayoutWindow*', 'measure', 'layout',
     'configChanged', 'Contending for pthread mutex',
     'ImageDecoder#decodeBitmap', 'ImageDecoder#decodeDrawable',
+    'NotificationStackScrollLayout#onMeasure',
+    'ExpNotRow#onMeasure(MessagingStyle)', 'ExpNotRow#onMeasure(BigTextStyle)',
     'Should not be in the metric'
 ]
 
diff --git a/test/trace_processor/diff_tests/chrome/chrome_scroll_jank_v2.out b/test/trace_processor/diff_tests/chrome/chrome_scroll_jank_v2.out
index dae2c44..b13137f 100644
--- a/test/trace_processor/diff_tests/chrome/chrome_scroll_jank_v2.out
+++ b/test/trace_processor/diff_tests/chrome/chrome_scroll_jank_v2.out
@@ -1,4 +1,3 @@
 
 "scroll_processing_ms","scroll_jank_processing_ms","scroll_jank_percentage"
 12374.560000,154.217000,1.246242
-
diff --git a/test/trace_processor/diff_tests/chrome/chrome_speedometer.out b/test/trace_processor/diff_tests/chrome/chrome_speedometer.out
new file mode 100644
index 0000000..2a52485
--- /dev/null
+++ b/test/trace_processor/diff_tests/chrome/chrome_speedometer.out
@@ -0,0 +1,11 @@
+"iteration","ts","dur","total","mean","geomean","score","num_measurements"
+1,693997310311984,7020297000,5191976000,"324498500.0","254177934.4","78.7",96
+2,694004414619984,6308034000,4600497000,"287531062.5","224382472.1","89.1",96
+3,694010770005984,5878289000,4209484000,"263092750.0","200261720.2","99.9",96
+4,694016699502984,5934578000,4213632000,"263352000.0","201163561.9","99.4",96
+5,694022683560984,5952163000,4259111000,"266194437.5","203014932.4","98.5",96
+6,694028690570984,5966530000,4269728000,"266858000.0","204306068.2","97.9",96
+7,694034719276984,5853043000,4219351000,"263709437.5","200358118.8","99.8",96
+8,694040637173984,6087435000,4261576000,"266348500.0","202962356.2","98.5",96
+9,694046772284984,6040820000,4245060000,"265316250.0","199331263.1","100.3",96
+10,694052857814984,6063770000,4388487000,"274280437.5","208004176.2","96.2",96
diff --git a/test/trace_processor/diff_tests/chrome/chrome_speedometer_test.sql b/test/trace_processor/diff_tests/chrome/chrome_speedometer_test.sql
new file mode 100644
index 0000000..374d72e
--- /dev/null
+++ b/test/trace_processor/diff_tests/chrome/chrome_speedometer_test.sql
@@ -0,0 +1,20 @@
+SELECT IMPORT('chrome.speedometer');
+
+SELECT
+  iteration,
+  ts,
+  dur,
+  total,
+  format('%.1f', mean) AS mean,
+  format('%.1f', geomean) AS geomean,
+  format('%.1f', score) AS score,
+  num_measurements
+FROM
+  chrome_speedometer_iteration,
+  (
+    SELECT iteration, COUNT(*) AS num_measurements
+    FROM chrome_speedometer_measure
+    GROUP BY iteration
+  )
+USING (iteration)
+ORDER BY iteration;
diff --git a/test/trace_processor/diff_tests/chrome/chrome_tasks.out b/test/trace_processor/diff_tests/chrome/chrome_tasks.out
index 7ca510e..7a75cec 100644
--- a/test/trace_processor/diff_tests/chrome/chrome_tasks.out
+++ b/test/trace_processor/diff_tests/chrome/chrome_tasks.out
@@ -1,5 +1,5 @@
 
-"full_name","task_type","count"
+"name","task_type","count"
 "OnLibevent","other",2208
 "RunTask(posted_from=components/favicon/core/large_icon_worker.cc:OnIconLookupComplete)","scheduler",694
 "RunTask(posted_from=components/history/core/browser/history_service.cc:GetLargestFaviconForURL)","scheduler",694
@@ -50,3 +50,164 @@
 "RunTask(posted_from=net/disk_cache/simple/simple_entry_impl.cc:OpenOrCreateEntryInternal)","scheduler",34
 "network.mojom.URLLoaderClient message (hash=2503424824)","mojo",32
 "RunTask(posted_from=components/update_client/component.cc:TransitionState)","scheduler",31
+"RunTask(posted_from=net/http/http_stream_factory_job.cc:RunLoop)","scheduler",31
+"RunTask(posted_from=cc/tiles/tile_manager.cc:TaskSetFinished)","scheduler",30
+"RunTask(posted_from=gpu/command_buffer/service/scheduler.cc:TryScheduleSequence)","scheduler",29
+"viz.mojom.CompositorFrameSinkClient message (hash=50871626)","mojo",29
+"network.mojom.URLLoaderFactory message (hash=4026588969)","mojo",28
+"tracing.mojom.ProducerHost message (hash=1567334432)","mojo",27
+"RunTask(posted_from=services/service_manager/public/cpp/interface_binder.h:BindInterface)","scheduler",26
+"RunTask(posted_from=base/memory/memory_pressure_listener.cc:Notify)","scheduler",25
+"RunTask(posted_from=net/http/http_cache.cc:ProcessQueuedTransactions)","scheduler",25
+"Choreographer(java_views=ToolbarLayout,ToolbarPhone.updateLocationBarLayoutForExpansionAnimation)","choreographer",24
+"tracing.mojom.TracingSessionHost message (hash=167101205)","mojo",24
+"tracing.mojom.TracingSessionHost reply (hash=167101205)","mojo",24
+"RunTask(posted_from=components/history/core/browser/history_service.cc:ScheduleTask)","scheduler",23
+"RunTask(posted_from=net/disk_cache/simple/simple_entry_impl.cc:DoomEntryInternal)","scheduler",23
+"RunTask(posted_from=components/omnibox/browser/history_url_provider.cc:ExecuteWithDB)","scheduler",21
+"content.mojom.ChildProcessHost message (hash=3455726814)","mojo",21
+"Looper.dispatch: android.app.ActivityThread$H(null)","other",20
+"RunTask(posted_from=cc/base/unique_notifier.cc:Schedule)","scheduler",20
+"RunTask(posted_from=ipc/ipc_mojo_bootstrap.cc:NotifyEndpointOfError)","scheduler",20
+"RunTask(posted_from=mojo/public/cpp/bindings/lib/binder_map_internal.h:BindInterface)","scheduler",19
+"RunTask(posted_from=content/browser/gpu/gpu_process_host.cc:CallOnIO)","scheduler",18
+"RunTask(posted_from=services/metrics/public/cpp/delegating_ukm_recorder.cc:AddEntry)","scheduler",18
+"blink.mojom.BrowserInterfaceBroker message (hash=2708892102)","mojo",17
+"RunTask(posted_from=components/update_client/update_engine.cc:HandleComponentComplete)","scheduler",16
+"RunTask(posted_from=net/disk_cache/simple/simple_entry_impl.cc:EntryOperationComplete)","scheduler",16
+"RunTask(posted_from=net/disk_cache/simple/simple_entry_impl.cc:ReturnEntryToCallerAsync)","scheduler",16
+"tracing.mojom.ProducerClient message (hash=1884113734)","mojo",16
+"RunTask(posted_from=cc/tiles/tile_manager.cc:RunOnWorkerThread)","scheduler",15
+"RunTask(posted_from=components/update_client/component.cc:DoHandle)","scheduler",15
+"RunTask(posted_from=gpu/ipc/service/gpu_channel_manager.cc:ScheduleWakeUpGpu)","scheduler",15
+"RunTask(posted_from=third_party/blink/renderer/controller/memory_usage_monitor.cc:StartMonitoringIfNeeded)","scheduler",15
+"content.mojom.FrameHost message (hash=3826696652)","mojo",15
+"tracing.mojom.ProducerClient reply (hash=1377057286)","mojo",15
+"RunTask(posted_from=components/crash/content/browser/crash_metrics_reporter_android.cc:NotifyObservers)","scheduler",14
+"RunTask(posted_from=components/update_client/component.cc:EndState)","scheduler",14
+"RunTask(posted_from=components/viz/service/display_embedder/skia_output_surface_dependency_impl.cc:PostTaskToClientThread)","scheduler",14
+"RunTask(posted_from=mojo/public/cpp/bindings/lib/interface_endpoint_client.cc:SendMessageWithResponder)","scheduler",14
+"RunTask(posted_from=third_party/blink/renderer/platform/widget/input/main_thread_event_queue.cc:PostTaskToMainThread)","scheduler",14
+"viz.mojom.GpuHost message (hash=1989238374)","mojo",14
+"RunTask(posted_from=base/allocator/partition_alloc_support.cc:RunMemoryReclaimer)","scheduler",13
+"RunTask(posted_from=cc/trees/proxy_main.cc:SendCommitRequestToImplThreadIfNeeded)","scheduler",13
+"RunTask(posted_from=components/viz/service/display/display_scheduler.cc:ScheduleBeginFrameDeadline)","scheduler",13
+"RunTask(posted_from=content/browser/webui/web_ui_url_loader_factory.cc:DataAvailable)","scheduler",13
+"RunTask(posted_from=gin/v8_platform.cc:PostJob)","scheduler",13
+"RunTask(posted_from=third_party/blink/renderer/platform/loader/fetch/url_loader/web_resource_request_sender.cc:DeletePendingRequest)","scheduler",13
+"SingleThreadProxy::BeginMainFrame(java_views=)","ui_thread_begin_main_frame",13
+"blink.mojom.WidgetInputHandler message (hash=1243813610)","mojo",13
+"content.mojom.FrameHost message (hash=171518470)","mojo",13
+"tracing.mojom.ProducerClient message (hash=1377057286)","mojo",13
+"RunTask(posted_from=components/offline_pages/task/task_queue.cc:StartTaskIfAvailable)","scheduler",12
+"RunTask(posted_from=components/page_load_metrics/renderer/page_timing_metrics_sender.cc:EnsureSendTimer)","scheduler",12
+"RunTask(posted_from=content/browser/webui/web_ui_data_source_impl.cc:GetDataResourceBytesOnWorkerThread)","scheduler",12
+"RunTask(posted_from=net/spdy/spdy_session.cc:MaybePostWriteLoop)","scheduler",12
+"RunTask(posted_from=third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc:UpdatePolicyLocked)","scheduler",12
+"page_load_metrics.mojom.PageLoadMetrics message (hash=1589948206)","mojo",12
+"viz.mojom.CompositorFrameSink message (hash=1797539858)","mojo",12
+"viz.mojom.CompositorFrameSinkClient message (hash=713406245)","mojo",12
+"RunTask(posted_from=ipc/ipc_channel_mojo.cc:SendMessage)","scheduler",11
+"RunTask(posted_from=ipc/ipc_channel_proxy.cc:OnAssociatedInterfaceRequest)","scheduler",11
+"blink.mojom.LocalMainFrameHost message (hash=2681895571)","mojo",11
+"Looper.dispatch: android.view.ViewRootImpl$ViewRootHandler(android.widget.Editor$Blink@ef6aa9b)","other",10
+"RunTask(posted_from=components/leveldb_proto/internal/proto_database_impl.h:PostTransaction)","scheduler",10
+"RunTask(posted_from=components/leveldb_proto/internal/proto_leveldb_wrapper.cc:LoadEntriesWithFilter)","scheduler",10
+"RunTask(posted_from=gpu/ipc/service/gpu_channel.cc:CreateCommandBuffer)","scheduler",10
+"RunTask(posted_from=cc/trees/layer_tree_host_impl.cc:DidPresentCompositorFrame)","scheduler",9
+"RunTask(posted_from=content/browser/child_process_launcher_helper.cc:PostLaunchOnLauncherThread)","scheduler",9
+"RunTask(posted_from=content/browser/child_process_launcher_helper.cc:StartLaunchOnClientThread)","scheduler",9
+"RunTask(posted_from=content/browser/child_process_launcher_helper_android.cc:LaunchProcessOnLauncherThread)","scheduler",9
+"RunTask(posted_from=content/browser/tracing/background_tracing_manager_impl.cc:ActivateForProcess)","scheduler",9
+"RunTask(posted_from=mojo/core/node_controller.cc:SendBrokerClientInvitation)","scheduler",9
+"blink.mojom.Widget message (hash=1337806005)","mojo",9
+"content.mojom.ChildProcess message (hash=1868925865)","mojo",9
+"device.mojom.DeviceService message (hash=3372813913)","mojo",9
+"device.mojom.PowerMonitor message (hash=1428246654)","mojo",9
+"memory_instrumentation.mojom.CoordinatorConnector message (hash=3189782928)","mojo",9
+"tracing.mojom.PerfettoService message (hash=3821615380)","mojo",9
+"tracing.mojom.TracedProcess reply (hash=2621133919)","mojo",9
+"tracing.mojom.TracingService message (hash=2713683366)","mojo",9
+"ChildProcessConnection.ChildServiceConnection.onServiceConnected","java",8
+"Looper.dispatch: android.net.ConnectivityManager$CallbackHandler(null)","other",8
+"RunTask(posted_from=base/allocator/partition_alloc_support.cc:RunThreadCachePeriodicPurge)","scheduler",8
+"RunTask(posted_from=chrome/browser/data_saver/data_saver.cc:FetchDataSaverOSSettingAsynchronously)","scheduler",8
+"RunTask(posted_from=gpu/command_buffer/service/gr_cache_controller.cc:ScheduleGrContextCleanup)","scheduler",8
+"RunTask(posted_from=ipc/ipc_channel_proxy.cc:AddFilter)","scheduler",8
+"RunTask(posted_from=mojo/public/cpp/bindings/lib/connector.cc:PostDispatchNextMessageFromPipe)","mojo",8
+"RunTask(posted_from=mojo/public/cpp/bindings/lib/connector.cc:StartReceiving)","scheduler",8
+"RunTask(posted_from=net/cert/multi_threaded_cert_verifier.cc:Start)","scheduler",8
+"RunTask(posted_from=net/disk_cache/simple/simple_entry_impl.cc:PostClientCallback)","scheduler",8
+"RunTask(posted_from=ui/events/gesture_detection/gesture_detector.cc:StartTimeout)","scheduler",8
+"blink.mojom.LocalFrameHost message (hash=3716466459)","mojo",8
+"content.mojom.FrameHost message (hash=4063347880)","mojo",8
+"tracing.mojom.ProducerClient message (hash=3526922743)","mojo",8
+"RunTask(posted_from=base/threading/thread.cc:StopSoon)","scheduler",7
+"RunTask(posted_from=components/crash/content/browser/child_process_crash_observer_android.cc:OnChildExit)","scheduler",7
+"RunTask(posted_from=components/viz/service/display_embedder/skia_output_device.cc:FinishSwapBuffers)","scheduler",7
+"RunTask(posted_from=components/viz/service/gl/gpu_service_impl.cc:GetPeakMemoryUsage)","scheduler",7
+"RunTask(posted_from=components/viz/service/gl/gpu_service_impl.cc:GetPeakMemoryUsageOnMainThread)","scheduler",7
+"RunTask(posted_from=components/viz/service/gl/gpu_service_impl.cc:StartPeakMemoryMonitor)","scheduler",7
+"RunTask(posted_from=content/browser/browser_child_process_host_impl.cc:RegisterCoordinatorClient)","scheduler",7
+"RunTask(posted_from=content/browser/service_process_host_impl.cc:Launch)","scheduler",7
+"RunTask(posted_from=content/utility/utility_thread_impl.cc:BindServiceInterface)","scheduler",7
+"RunTask(posted_from=third_party/blink/renderer/core/frame/web_frame_widget_impl.cc:DidSwap)","scheduler",7
+"RunTask(posted_from=third_party/blink/renderer/core/script/module_map.cc:DispatchFinishedNotificationAsync)","scheduler",7
+"RunTask(posted_from=ui/gfx/android/android_surface_control_compat.cc:SetOnCommitCb)","scheduler",7
+"RunTask(posted_from=ui/gfx/android/android_surface_control_compat.cc:SetOnCompleteCb)","scheduler",7
+"content.mojom.ChildProcessHost message (hash=38682745)","mojo",7
+"media_session.mojom.MediaSessionObserver message (hash=3966185760)","mojo",7
+"viz.mojom.GpuService reply (hash=3002350734)","mojo",7
+"ChromeApplication.attachBaseContext","java",6
+"RunTask(posted_from=base/files/important_file_writer.cc:WriteNowWithBackgroundDataProducer)","scheduler",6
+"RunTask(posted_from=base/power_monitor/power_monitor.cc:NotifyPowerStateChange)","scheduler",6
+"RunTask(posted_from=base/task/sequenced_task_runner.h:operator())","scheduler",6
+"RunTask(posted_from=cc/trees/proxy_impl.cc:DrawInternal)","scheduler",6
+"RunTask(posted_from=cc/trees/proxy_impl.cc:~ScopedCommitCompletionEvent)","scheduler",6
+"RunTask(posted_from=cc/trees/single_thread_proxy.cc:DidReceiveCompositorFrameAckOnImplThread)","scheduler",6
+"RunTask(posted_from=chrome/browser/image_decoder/image_decoder.cc:RunDecodeCallbackOnTaskRunner)","scheduler",6
+"RunTask(posted_from=chrome/browser/image_decoder/image_decoder.cc:StartWithOptionsImpl)","scheduler",6
+"RunTask(posted_from=components/history/core/browser/history_service.cc:GetFaviconsForURL)","scheduler",6
+"RunTask(posted_from=components/leveldb_proto/internal/proto_leveldb_wrapper.cc:UpdateEntries)","scheduler",6
+"RunTask(posted_from=components/offline_pages/task/task_queue.cc:TaskCompletedCallback)","scheduler",6
+"RunTask(posted_from=components/omnibox/browser/autocomplete_controller.cc:StartStopTimer)","scheduler",6
+"RunTask(posted_from=components/performance_manager/decorators/page_load_tracker_decorator_helper.cc:NotifyPageLoadTrackerDecoratorOnPMSequence)","scheduler",6
+"RunTask(posted_from=components/performance_manager/performance_manager_impl.cc:CreateNodeImpl)","scheduler",6
+"RunTask(posted_from=components/power_scheduler/power_mode_arbiter.cc:OnTaskRunnerAvailable)","scheduler",6
+"RunTask(posted_from=content/browser/service_worker/service_worker_context_core.cc:NotifyClientIsExecutionReady)","scheduler",6
+"RunTask(posted_from=content/child/child_thread_impl.cc:ExposeInterfacesToBrowser)","scheduler",6
+"RunTask(posted_from=content/child/child_thread_impl.cc:GetBackgroundTracingAgentProvider)","scheduler",6
+"RunTask(posted_from=content/child/child_thread_impl.cc:Init)","scheduler",6
+"RunTask(posted_from=content/common/android/cpu_time_metrics_internal.cc:ProcessCpuTimeMetrics)","scheduler",6
+"RunTask(posted_from=ipc/ipc_channel_proxy.cc:Init)","scheduler",6
+"RunTask(posted_from=ipc/ipc_channel_proxy.cc:OnChannelConnected)","scheduler",6
+"RunTask(posted_from=mojo/core/node_controller.cc:AcceptBrokerClientInvitation)","scheduler",6
+"RunTask(posted_from=mojo/core/node_controller.cc:Create)","scheduler",6
+"RunTask(posted_from=services/device/public/cpp/power_monitor/power_monitor_broadcast_source.cc:Init)","scheduler",6
+"RunTask(posted_from=services/tracing/public/cpp/perfetto/perfetto_traced_process.cc:CreateProducerConnection)","scheduler",6
+"RunTask(posted_from=services/tracing/public/cpp/perfetto/producer_client.cc:Connect)","scheduler",6
+"RunTask(posted_from=third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc:UpdateForInputEventOnCompositorThread)","scheduler",6
+"blink.mojom.PageBroadcast message (hash=1682824955)","mojo",6
+"network.mojom.NetworkService message (hash=121963099)","mojo",6
+"service_manager.mojom.InterfaceProvider message (hash=607834802)","mojo",6
+"tracing.mojom.ProducerClient message (hash=2964700609)","mojo",6
+"tracing.mojom.TracedProcess message (hash=2621133919)","mojo",6
+"Choreographer(java_views=ToolbarLayout,ToolbarPhone.layoutLocationBar,ToolbarPhone.updateLocationBarLayoutForExpansionAnimation)","choreographer",5
+"Looper.dispatch: android.os.Handler(ZM3@26e2110)","other",5
+"RunTask(posted_from=base/files/important_file_writer.cc:ScheduleWrite)","scheduler",5
+"RunTask(posted_from=cc/trees/proxy_main.cc:UpdateBrowserControlsState)","scheduler",5
+"RunTask(posted_from=components/leveldb_proto/internal/proto_database_impl.h:ParseLoadedEntries)","scheduler",5
+"RunTask(posted_from=components/viz/common/gpu/context_cache_controller.cc:PostIdleCallback)","scheduler",5
+"RunTask(posted_from=gin/v8_foreground_task_runner.cc:PostNonNestableTask)","scheduler",5
+"RunTask(posted_from=ipc/ipc_channel_proxy.cc:Pause)","scheduler",5
+"RunTask(posted_from=net/socket/transport_client_socket_pool.cc:StartBackupJobTimer)","scheduler",5
+"RunTask(posted_from=third_party/blink/renderer/core/css/font_face_set.cc:HandlePendingEventsAndPromisesSoon)","scheduler",5
+"RunTask(posted_from=ui/gl/gl_surface_egl_surface_control.cc:CheckPendingPresentationCallbacks)","scheduler",5
+"blink.mojom.LocalFrameHost message (hash=3721282066)","mojo",5
+"blink.mojom.LocalMainFrame message (hash=2603884585)","mojo",5
+"blink.mojom.LocalMainFrameHost message (hash=2936437485)","mojo",5
+"content.mojom.ChildHistogramFetcherFactory message (hash=3650055636)","mojo",5
+"media_session.mojom.MediaSessionObserver message (hash=169447862)","mojo",5
+"network.mojom.DevToolsObserver message (hash=3093371228)","mojo",5
+"viz.mojom.GpuHost message (hash=2261948180)","mojo",5
+"viz.mojom.GpuHost message (hash=4264056690)","mojo",5
diff --git a/test/trace_processor/diff_tests/chrome/tests.py b/test/trace_processor/diff_tests/chrome/tests.py
index ec4181e..c6187aa 100644
--- a/test/trace_processor/diff_tests/chrome/tests.py
+++ b/test/trace_processor/diff_tests/chrome/tests.py
@@ -253,11 +253,11 @@
         query="""
         SELECT RUN_METRIC('chrome/chrome_tasks.sql');
 
-        SELECT full_name, task_type, count() AS count
+        SELECT full_name as name, task_type, count() AS count
         FROM chrome_tasks
         GROUP BY full_name, task_type
-        ORDER BY count DESC
-        LIMIT 50;
+        HAVING count >= 5
+        ORDER BY count DESC, name;
         """,
         out=Path('chrome_tasks.out'))
 
@@ -446,6 +446,45 @@
         "FrameHost::DidCommitProvisionalLoad (SUBFRAME)","navigation_task",1
         """))
 
+  # Chrome custom navigation event names
+  def test_chrome_histograms(self):
+    return DiffTestBlueprint(
+        trace=DataPath('chrome_5672_histograms.pftrace.gz'),
+        query="""
+        SELECT IMPORT('chrome.histograms');
+
+        SELECT 
+          name,
+          count() as count 
+        FROM chrome_histograms
+        GROUP BY name
+        ORDER BY count DESC, name
+        LIMIT 20;
+        """,
+        out=Csv("""
+        "name","count"
+        "Net.QuicSession.AsyncRead",19207
+        "Net.QuicSession.NumQueuedPacketsBeforeWrite",19193
+        "RendererScheduler.QueueingDuration.NormalPriority",9110
+        "Net.OnTransferSizeUpdated.Experimental.OverridenBy",8525
+        "Compositing.Renderer.AnimationUpdateOnMissingPropertyNode",3489
+        "Net.QuicConnection.WritePacketStatus",3099
+        "Net.QuicSession.PacketWriteTime.Synchronous",3082
+        "Net.QuicSession.SendPacketSize.ForwardSecure",3012
+        "Net.URLLoaderThrottleExecutionTime.WillStartRequest",1789
+        "Net.URLLoaderThrottleExecutionTime.BeforeWillProcessResponse",1773
+        "Net.URLLoaderThrottleExecutionTime.WillProcessResponse",1773
+        "UMA.StackProfiler.SampleInOrder",1534
+        "GPU.SharedImage.ContentConsumed",1037
+        "Gpu.Rasterization.Raster.MSAASampleCountLog2",825
+        "Scheduling.Renderer.DeadlineMode",637
+        "Blink.CullRect.UpdateTime",622
+        "Scheduling.Renderer.BeginImplFrameLatency2",591
+        "Net.QuicSession.CoalesceStreamFrameStatus",551
+        "API.StorageAccess.AllowedRequests2",541
+        "Net.HttpResponseCode",541
+        """))
+
   # Trace proto content
   def test_proto_content(self):
     return DiffTestBlueprint(
@@ -453,6 +492,12 @@
         query=Path('proto_content_test.sql'),
         out=Path('proto_content.out'))
 
+  def test_speedometer(self):
+    return DiffTestBlueprint(
+        trace=DataPath('speedometer.perfetto_trace.gz'),
+        query=Path('chrome_speedometer_test.sql'),
+        out=Path('chrome_speedometer.out'))
+
   # TODO(mayzner): Uncomment when it works
   # def test_proto_content_path(self):
   #   return DiffTestBlueprint(
diff --git a/test/trace_processor/diff_tests/chrome/tests_scroll_jank.py b/test/trace_processor/diff_tests/chrome/tests_scroll_jank.py
index c8594d1..ef0f373 100644
--- a/test/trace_processor/diff_tests/chrome/tests_scroll_jank.py
+++ b/test/trace_processor/diff_tests/chrome/tests_scroll_jank.py
@@ -541,17 +541,16 @@
         5680,120000000,70000000,120000000,-1
         """))
 
-  # TODO(283531332): reenable this test after fixing.
-  # def test_chrome_scroll_jank_v2(self):
-  #   return DiffTestBlueprint(
-  #       trace=Path('../../data/event_latency_with_args.perfetto-trace'),
-  #       query="""
-  #       SELECT RUN_METRIC('chrome/chrome_scroll_jank_v2.sql');
+  def test_chrome_scroll_jank_v2(self):
+    return DiffTestBlueprint(
+        trace=DataPath('event_latency_with_args.perfetto-trace'),
+        query="""
+        SELECT RUN_METRIC('chrome/chrome_scroll_jank_v2.sql');
 
-  #       SELECT
-  #         scroll_processing_ms,
-  #         scroll_jank_processing_ms,
-  #         scroll_jank_percentage
-  #       FROM chrome_scroll_jank_v2_output;
-  #       """,
-  #       out=Path('chrome_scroll_jank_v2.out'))
+        SELECT
+          scroll_processing_ms,
+          scroll_jank_processing_ms,
+          scroll_jank_percentage
+        FROM chrome_scroll_jank_v2;
+        """,
+        out=Path('chrome_scroll_jank_v2.out'))
diff --git a/test/trace_processor/diff_tests/functions/tests.py b/test/trace_processor/diff_tests/functions/tests.py
index 7f952ed..e5dc15c 100644
--- a/test/trace_processor/diff_tests/functions/tests.py
+++ b/test/trace_processor/diff_tests/functions/tests.py
@@ -520,3 +520,21 @@
         4,3
         5,3
         """))
+
+  def test_math_functions(self):
+    return DiffTestBlueprint(
+        trace=TextProto(""),
+        query="""
+        SELECT
+          CAST(EXP(1) * 1000 AS INTEGER) AS a,
+          CAST(LN(1) * 1000 AS INTEGER) AS b,
+          CAST(LN(EXP(1)) * 1000 AS INTEGER) AS c,
+          EXP("asd") AS d,
+          EXP(NULL) AS e,
+          LN("as") AS f,
+          LN(NULL) AS g
+        """,
+        out=Csv("""
+        "a","b","c","d","e","f","g"
+        2718,0,1000,"[NULL]","[NULL]","[NULL]","[NULL]"
+        """))
diff --git a/test/trace_processor/diff_tests/tables/tests_sched.py b/test/trace_processor/diff_tests/tables/tests_sched.py
index 1f1ee49..3ef8d14 100644
--- a/test/trace_processor/diff_tests/tables/tests_sched.py
+++ b/test/trace_processor/diff_tests/tables/tests_sched.py
@@ -79,3 +79,44 @@
         81473010341386
         81473010352792
         """))
+
+  def test_sched_wakeup(self):
+    return DiffTestBlueprint(
+        trace=DataPath('sched_wakeup_trace.atr'),
+        query="""
+        SELECT * FROM spurious_sched_wakeup
+        ORDER BY ts LIMIT 10
+        """,
+        out=Csv("""
+        "id","type","ts","thread_state_id","irq_context","utid","waker_utid"
+        0,"spurious_sched_wakeup",1735850782904,395,0,230,1465
+        1,"spurious_sched_wakeup",1736413914899,852,0,230,1467
+        2,"spurious_sched_wakeup",1736977755745,1261,0,230,1469
+        3,"spurious_sched_wakeup",1737046900004,1434,0,1472,1473
+        4,"spurious_sched_wakeup",1737047159060,1463,0,1474,1472
+        5,"spurious_sched_wakeup",1737081636170,2721,0,1214,1319
+        6,"spurious_sched_wakeup",1737108696536,4684,0,501,557
+        7,"spurious_sched_wakeup",1737153309978,6080,0,11,506
+        8,"spurious_sched_wakeup",1737165240546,6562,0,565,499
+        9,"spurious_sched_wakeup",1737211563344,8645,0,178,1195
+        """))
+
+  def test_raw_common_flags(self):
+    return DiffTestBlueprint(
+        trace=DataPath('sched_wakeup_trace.atr'),
+        query="""
+        SELECT * FROM raw WHERE common_flags != 0 ORDER BY ts LIMIT 10
+        """,
+        out=Csv("""
+        "id","type","ts","name","cpu","utid","arg_set_id","common_flags"
+        3,"ftrace_event",1735489788930,"sched_waking",0,300,4,1
+        4,"ftrace_event",1735489812571,"sched_waking",0,300,5,1
+        5,"ftrace_event",1735489833977,"sched_waking",1,305,6,1
+        8,"ftrace_event",1735489876788,"sched_waking",1,297,9,1
+        9,"ftrace_event",1735489879097,"sched_waking",0,304,10,1
+        12,"ftrace_event",1735489933912,"sched_waking",0,428,13,1
+        14,"ftrace_event",1735489972385,"sched_waking",1,232,15,1
+        17,"ftrace_event",1735489999987,"sched_waking",1,232,15,1
+        19,"ftrace_event",1735490039439,"sched_waking",1,298,18,1
+        20,"ftrace_event",1735490042084,"sched_waking",1,298,19,1
+        """))