Merge "tp: Fix table functions pattern" into main
diff --git a/CHANGELOG b/CHANGELOG
index 46a6cd3..145aa78 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -6,6 +6,11 @@
   UI:
     * Per-cpu scheduling tracks now distinguish real-time priority threads with
       a hatched pattern and name prefix. Based on priority during switch-in.
+    * Added ftrace event cropping for traces recorded by perfetto v44+.
+      Events are ignored if they precede the earliest timestamp covered by all
+      per-cpu data streams. This should significantly improve the presentation
+      of RING_BUFFER traces, removing artifacts such as never-ending slices
+      starting at the beginning of the trace.
   SDK:
     *
 
diff --git a/docs/contributing/ui-plugins.md b/docs/contributing/ui-plugins.md
index 67bb445..09fa88e 100644
--- a/docs/contributing/ui-plugins.md
+++ b/docs/contributing/ui-plugins.md
@@ -468,7 +468,7 @@
 
 Persistent plugin state works using a `Store<T>` where `T` is some JSON
 serializable object.
-`Store` is implemented [here](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/ui/src/frontend/store.ts).
+`Store` is implemented [here](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/ui/src/base/store.ts).
 `Store` allows for reading and writing `T`.
 Reading:
 ```typescript
diff --git a/gn/standalone/BUILD.gn b/gn/standalone/BUILD.gn
index 98cb01e..ecd5c95 100644
--- a/gn/standalone/BUILD.gn
+++ b/gn/standalone/BUILD.gn
@@ -83,6 +83,7 @@
       "-Wno-gnu-zero-variadic-macro-arguments",
       "-Wno-padded",
       "-Wno-poison-system-directories",
+      "-Wno-pre-c11-compat",
       "-Wno-reserved-id-macro",
       "-Wno-reserved-identifier",
       "-Wno-shadow-uncaptured-local",
diff --git a/include/perfetto/trace_processor/basic_types.h b/include/perfetto/trace_processor/basic_types.h
index 7ecc934..ce468fb 100644
--- a/include/perfetto/trace_processor/basic_types.h
+++ b/include/perfetto/trace_processor/basic_types.h
@@ -93,7 +93,25 @@
   // trace, no data is dropped.
   // This option can be used in cases where R- traces are being considered and
   // |kTracingStart| cannot be used because the event was not present.
-  kAllDataSourcesStarted = 2,
+  kAllDataSourcesStarted = 2
+};
+
+// Specifies whether the ftrace data should be "soft-dropped" until a given
+// global timestamp, meaning we'll still populate the |ftrace_events| table
+// and some other internal storage, but won't persist derived info such as
+// slices. See also |DropFtraceDataBefore| above.
+// Note: this might behave in surprising ways for traces using >1 tracefs
+// instances, but those aren't seen in practice at the time of writing.
+enum class SoftDropFtraceDataBefore {
+  // Drop until the earliest timestamp covered by all per-cpu event bundles.
+  // In other words, the maximum of all per-cpu "valid from" timestamps.
+  // Important for correct parsing of traces where the ftrace data is written
+  // into a central perfetto buffer in ring-buffer mode (as opposed to discard
+  // mode).
+  kAllPerCpuBuffersValid = 0,
+
+  // Keep all events (though DropFtraceDataBefore still applies).
+  kNoDrop = 1
 };
 
 // Enum which encodes which timestamp source (if any) should be used to drop
@@ -129,6 +147,11 @@
   DropFtraceDataBefore drop_ftrace_data_before =
       DropFtraceDataBefore::kTracingStarted;
 
+  // Specifies whether the ftrace data should be "soft-dropped" until a given
+  // global timestamp.
+  SoftDropFtraceDataBefore soft_drop_ftrace_data_before =
+      SoftDropFtraceDataBefore::kAllPerCpuBuffersValid;
+
   // Indicates the source of timestamp before which track events should be
   // dropped. See the enum documentation for more details.
   DropTrackEventDataBefore drop_track_event_data_before =
diff --git a/protos/perfetto/config/ftrace/ftrace_config.proto b/protos/perfetto/config/ftrace/ftrace_config.proto
index 95db90e..1872290 100644
--- a/protos/perfetto/config/ftrace/ftrace_config.proto
+++ b/protos/perfetto/config/ftrace/ftrace_config.proto
@@ -143,9 +143,11 @@
   // Introduced in: Android T.
   optional bool disable_generic_events = 16;
 
-  // The subset of syscalls to record. Enables raw_syscalls/sys_{enter,exit}.
-  // To record all syscalls, leave this unset and add raw_syscalls to
-  // |ftrace_events|.
+  // The subset of syscalls to record. To record all syscalls, leave this unset
+  // and add "ftrace_events: raw_syscalls/sys_{enter,exit}" to the config.
+  // * before perfetto v43, requires the config to also enable
+  //   raw_syscalls/sys_{enter,exit}.
+  // * perfetto v43+ does the right thing if you set only this field.
   // Example: ["sys_read", "sys_open"].
   // Introduced in: Android U.
   repeated string syscall_events = 18;
diff --git a/protos/perfetto/config/perfetto_config.proto b/protos/perfetto/config/perfetto_config.proto
index 881713c..f11c56d 100644
--- a/protos/perfetto/config/perfetto_config.proto
+++ b/protos/perfetto/config/perfetto_config.proto
@@ -975,9 +975,11 @@
   // Introduced in: Android T.
   optional bool disable_generic_events = 16;
 
-  // The subset of syscalls to record. Enables raw_syscalls/sys_{enter,exit}.
-  // To record all syscalls, leave this unset and add raw_syscalls to
-  // |ftrace_events|.
+  // The subset of syscalls to record. To record all syscalls, leave this unset
+  // and add "ftrace_events: raw_syscalls/sys_{enter,exit}" to the config.
+  // * before perfetto v43, requires the config to also enable
+  //   raw_syscalls/sys_{enter,exit}.
+  // * perfetto v43+ does the right thing if you set only this field.
   // Example: ["sys_read", "sys_open"].
   // Introduced in: Android U.
   repeated string syscall_events = 18;
diff --git a/protos/perfetto/trace/ftrace/ftrace_event_bundle.proto b/protos/perfetto/trace/ftrace/ftrace_event_bundle.proto
index 5bfab9dd..2bc2cd3 100644
--- a/protos/perfetto/trace/ftrace/ftrace_event_bundle.proto
+++ b/protos/perfetto/trace/ftrace/ftrace_event_bundle.proto
@@ -112,6 +112,16 @@
     optional FtraceParseStatus status = 2;
   }
   repeated FtraceError error = 8;
+
+  // The timestamp (ftrace clock) of the last event consumed from this per-cpu
+  // kernel buffer prior to starting this bundle. In other words: the last
+  // event in the previous bundle.
+  // Lets the trace processing find an initial timestamp after which ftrace
+  // data is known to be valid across all cpus. Of particular importance when
+  // the perfetto trace buffer is a ring buffer as well, as the overwriting of
+  // oldest bundles can skew the first valid timestamp per cpu significantly.
+  // Added in: perfetto v44.
+  optional uint64 last_read_event_timestamp = 9;
 }
 
 enum FtraceClock {
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index a166cae..b657572 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -975,9 +975,11 @@
   // Introduced in: Android T.
   optional bool disable_generic_events = 16;
 
-  // The subset of syscalls to record. Enables raw_syscalls/sys_{enter,exit}.
-  // To record all syscalls, leave this unset and add raw_syscalls to
-  // |ftrace_events|.
+  // The subset of syscalls to record. To record all syscalls, leave this unset
+  // and add "ftrace_events: raw_syscalls/sys_{enter,exit}" to the config.
+  // * before perfetto v43, requires the config to also enable
+  //   raw_syscalls/sys_{enter,exit}.
+  // * perfetto v43+ does the right thing if you set only this field.
   // Example: ["sys_read", "sys_open"].
   // Introduced in: Android U.
   repeated string syscall_events = 18;
@@ -10805,6 +10807,16 @@
     optional FtraceParseStatus status = 2;
   }
   repeated FtraceError error = 8;
+
+  // The timestamp (ftrace clock) of the last event consumed from this per-cpu
+  // kernel buffer prior to starting this bundle. In other words: the last
+  // event in the previous bundle.
+  // Lets the trace processing find an initial timestamp after which ftrace
+  // data is known to be valid across all cpus. Of particular importance when
+  // the perfetto trace buffer is a ring buffer as well, as the overwriting of
+  // oldest bundles can skew the first valid timestamp per cpu significantly.
+  // Added in: perfetto v44.
+  optional uint64 last_read_event_timestamp = 9;
 }
 
 enum FtraceClock {
diff --git a/protos/perfetto/trace_processor/trace_processor.proto b/protos/perfetto/trace_processor/trace_processor.proto
index 1e579ca..e7a6cd5 100644
--- a/protos/perfetto/trace_processor/trace_processor.proto
+++ b/protos/perfetto/trace_processor/trace_processor.proto
@@ -326,4 +326,5 @@
   optional DropTrackEventDataBefore drop_track_event_data_before = 1;
   optional bool ingest_ftrace_in_raw_table = 2;
   optional bool analyze_trace_proto_content = 3;
+  optional bool ftrace_drop_until_all_cpus_valid = 4;
 }
diff --git a/protos/third_party/chromium/chrome_track_event.proto b/protos/third_party/chromium/chrome_track_event.proto
index 7c37b4b..882d6ff 100644
--- a/protos/third_party/chromium/chrome_track_event.proto
+++ b/protos/third_party/chromium/chrome_track_event.proto
@@ -6,6 +6,7 @@
 
 import public "protos/perfetto/trace/track_event/track_event.proto";
 import public "protos/perfetto/trace/track_event/debug_annotation.proto";
+import public "protos/perfetto/trace/track_event/source_location.proto";
 
 package perfetto.protos;
 
@@ -42,8 +43,9 @@
   optional TaskScopeType type = 1;
   optional int64 scope_task_id = 2;
   optional int64 running_task_id_to_be_restored = 3;
-  optional int64 continuation_task_id_to_be_restored = 4;
-  optional int64 parent_task_id = 5;
+
+  reserved 4, 5;
+  reserved "continuation_task_id_to_be_restored", "parent_task_id";
 }
 
 message ChromeTaskAnnotator {
@@ -1582,9 +1584,260 @@
   optional string name = 1;
 }
 
+// Describes Chrome's Compositor scheduler's current state and associated
+// variables.
+//
+// These protos and enums were adapted from the corresponding original JSON
+// trace event for the scheduler state. In contrast to the JSON, we use strongly
+// typed enum values instead of strings for many fields, and
+// microsecond-granularity timestamps.
+//
+// The original format was generated in JSON by the code at
+// https://cs.chromium.org/chromium/src/cc/scheduler/scheduler.cc?l=870&rcl=5e15eabc9c0eec8daf94fdf78e93f13b6e3b63dd
+//
+// And is now generated as protozero:
+// https://cs.chromium.org/chromium/src/cc/scheduler/scheduler.cc?q=Scheduler::AsPro
+//
+// All non-delta-timestamps are absolute CLOCK_MONOTONIC timestamps.
+
+// Next id: 15
+enum ChromeCompositorSchedulerActionV2 {
+  CC_SCHEDULER_ACTION_V2_UNSPECIFIED = 0;
+  CC_SCHEDULER_ACTION_V2_NONE = 1;
+  CC_SCHEDULER_ACTION_V2_SEND_BEGIN_MAIN_FRAME = 2;
+  CC_SCHEDULER_ACTION_V2_COMMIT = 3;
+  CC_SCHEDULER_ACTION_V2_POST_COMMIT = 14;
+  CC_SCHEDULER_ACTION_V2_ACTIVATE_SYNC_TREE = 4;
+  CC_SCHEDULER_ACTION_V2_DRAW_IF_POSSIBLE = 5;
+  CC_SCHEDULER_ACTION_V2_DRAW_FORCED = 6;
+  CC_SCHEDULER_ACTION_V2_DRAW_ABORT = 7;
+  CC_SCHEDULER_ACTION_V2_BEGIN_LAYER_TREE_FRAME_SINK_CREATION = 8;
+  CC_SCHEDULER_ACTION_V2_PREPARE_TILES = 9;
+  CC_SCHEDULER_ACTION_V2_INVALIDATE_LAYER_TREE_FRAME_SINK = 10;
+  CC_SCHEDULER_ACTION_V2_PERFORM_IMPL_SIDE_INVALIDATION = 11;
+  CC_SCHEDULER_ACTION_V2_NOTIFY_BEGIN_MAIN_FRAME_NOT_EXPECTED_UNTIL = 12;
+  CC_SCHEDULER_ACTION_V2_NOTIFY_BEGIN_MAIN_FRAME_NOT_EXPECTED_SOON = 13;
+}
+
+// Next id: 18
+message ChromeCompositorSchedulerStateV2 {
+  enum BeginImplFrameDeadlineMode {
+    DEADLINE_MODE_UNSPECIFIED = 0;
+    DEADLINE_MODE_NONE = 1;
+    DEADLINE_MODE_IMMEDIATE = 2;
+    DEADLINE_MODE_REGULAR = 3;
+    DEADLINE_MODE_LATE = 4;
+    DEADLINE_MODE_BLOCKED = 5;
+  }
+  optional ChromeCompositorStateMachineV2 state_machine = 1;
+  optional bool observing_begin_frame_source = 2;
+  optional bool begin_impl_frame_deadline_task = 3;
+  optional bool pending_begin_frame_task = 4;
+  optional bool skipped_last_frame_missed_exceeded_deadline = 5;
+  optional ChromeCompositorSchedulerActionV2 inside_action = 7;
+  optional BeginImplFrameDeadlineMode deadline_mode = 8;
+  optional int64 deadline_us = 9;
+  optional int64 deadline_scheduled_at_us = 10;
+  optional int64 now_us = 11;
+  optional int64 now_to_deadline_delta_us = 12;
+  optional int64 now_to_deadline_scheduled_at_delta_us = 13;
+  optional BeginImplFrameArgsV2 begin_impl_frame_args = 14;
+  optional BeginFrameObserverStateV2 begin_frame_observer_state = 15;
+  optional BeginFrameSourceStateV2 begin_frame_source_state = 16;
+  optional CompositorTimingHistoryV2 compositor_timing_history = 17;
+
+  reserved 6;
+}
+
+// Describes the current values stored in the Chrome Compositor state machine.
+// Next id: 3
+message ChromeCompositorStateMachineV2 {
+  // Next id: 6
+  message MajorStateV2 {
+    enum BeginImplFrameState {
+      BEGIN_IMPL_FRAME_UNSPECIFIED = 0;
+      BEGIN_IMPL_FRAME_IDLE = 1;
+      BEGIN_IMPL_FRAME_INSIDE_BEGIN_FRAME = 2;
+      BEGIN_IMPL_FRAME_INSIDE_DEADLINE = 3;
+    }
+    enum BeginMainFrameState {
+      BEGIN_MAIN_FRAME_UNSPECIFIED = 0;
+      BEGIN_MAIN_FRAME_IDLE = 1;
+      BEGIN_MAIN_FRAME_SENT = 2;
+      BEGIN_MAIN_FRAME_READY_TO_COMMIT = 3;
+    }
+    enum LayerTreeFrameSinkState {
+      LAYER_TREE_FRAME_UNSPECIFIED = 0;
+      LAYER_TREE_FRAME_NONE = 1;
+      LAYER_TREE_FRAME_ACTIVE = 2;
+      LAYER_TREE_FRAME_CREATING = 3;
+      LAYER_TREE_FRAME_WAITING_FOR_FIRST_COMMIT = 4;
+      LAYER_TREE_FRAME_WAITING_FOR_FIRST_ACTIVATION = 5;
+    }
+    enum ForcedRedrawOnTimeoutState {
+      FORCED_REDRAW_UNSPECIFIED = 0;
+      FORCED_REDRAW_IDLE = 1;
+      FORCED_REDRAW_WAITING_FOR_COMMIT = 2;
+      FORCED_REDRAW_WAITING_FOR_ACTIVATION = 3;
+      FORCED_REDRAW_WAITING_FOR_DRAW = 4;
+    }
+    optional ChromeCompositorSchedulerActionV2 next_action = 1;
+    optional BeginImplFrameState begin_impl_frame_state = 2;
+    optional BeginMainFrameState begin_main_frame_state = 3;
+    optional LayerTreeFrameSinkState layer_tree_frame_sink_state = 4;
+    optional ForcedRedrawOnTimeoutState forced_redraw_state = 5;
+  }
+  optional MajorStateV2 major_state = 1;
+
+  // Next id: 47
+  message MinorStateV2 {
+    enum TreePriority {
+      TREE_PRIORITY_UNSPECIFIED = 0;
+      TREE_PRIORITY_SAME_PRIORITY_FOR_BOTH_TREES = 1;
+      TREE_PRIORITY_SMOOTHNESS_TAKES_PRIORITY = 2;
+      TREE_PRIORITY_NEW_CONTENT_TAKES_PRIORITY = 3;
+    }
+    enum ScrollHandlerState {
+      SCROLL_HANDLER_UNSPECIFIED = 0;
+      SCROLL_AFFECTS_SCROLL_HANDLER = 1;
+      SCROLL_DOES_NOT_AFFECT_SCROLL_HANDLER = 2;
+    }
+    optional int32 commit_count = 1;
+    optional int32 current_frame_number = 2;
+    optional int32 last_frame_number_submit_performed = 3;
+    optional int32 last_frame_number_draw_performed = 4;
+    optional int32 last_frame_number_begin_main_frame_sent = 5;
+    optional bool did_draw = 6;
+    optional bool did_send_begin_main_frame_for_current_frame = 7;
+    optional bool did_notify_begin_main_frame_not_expected_until = 8;
+    optional bool did_notify_begin_main_frame_not_expected_soon = 9;
+    optional bool wants_begin_main_frame_not_expected = 10;
+    optional bool did_commit_during_frame = 11;
+    optional bool did_invalidate_layer_tree_frame_sink = 12;
+    optional bool did_perform_impl_side_invalidaion = 13;
+    optional bool did_prepare_tiles = 14;
+    optional int32 consecutive_checkerboard_animations = 15;
+    optional int32 pending_submit_frames = 16;
+    optional int32 submit_frames_with_current_layer_tree_frame_sink = 17;
+    optional bool needs_redraw = 18;
+    optional bool needs_prepare_tiles = 19;
+    optional bool needs_begin_main_frame = 20;
+    optional bool needs_one_begin_impl_frame = 21;
+    optional bool visible = 22;
+    optional bool begin_frame_source_paused = 23;
+    optional bool can_draw = 24;
+    optional bool resourceless_draw = 25;
+    optional bool has_pending_tree = 26;
+    optional bool pending_tree_is_ready_for_activation = 27;
+    optional bool active_tree_needs_first_draw = 28;
+    optional bool active_tree_is_ready_to_draw = 29;
+    optional bool did_create_and_initialize_first_layer_tree_frame_sink = 30;
+    optional TreePriority tree_priority = 31;
+    optional ScrollHandlerState scroll_handler_state = 32;
+    optional bool critical_begin_main_frame_to_activate_is_fast = 33;
+    optional bool main_thread_missed_last_deadline = 34;
+    optional bool video_needs_begin_frames = 36;
+    optional bool defer_begin_main_frame = 37;
+    optional bool last_commit_had_no_updates = 38;
+    optional bool did_draw_in_last_frame = 39;
+    optional bool did_submit_in_last_frame = 40;
+    optional bool needs_impl_side_invalidation = 41;
+    optional bool current_pending_tree_is_impl_side = 42;
+    optional bool previous_pending_tree_was_impl_side = 43;
+    optional bool processing_animation_worklets_for_active_tree = 44;
+    optional bool processing_animation_worklets_for_pending_tree = 45;
+    optional bool processing_paint_worklets_for_pending_tree = 46;
+
+    reserved 35;
+  }
+  optional MinorStateV2 minor_state = 2;
+}
+
+// Next id: 12
+message BeginFrameArgsV2 {
+  // JSON format has a "type" field that was always just "BeginFrameArgs" we
+  // drop this in the proto representation, and instead make the JSON format
+  // "subtype" field become the type field.
+  enum BeginFrameArgsType {
+    BEGIN_FRAME_ARGS_TYPE_UNSPECIFIED = 0;
+    BEGIN_FRAME_ARGS_TYPE_INVALID = 1;
+    BEGIN_FRAME_ARGS_TYPE_NORMAL = 2;
+    BEGIN_FRAME_ARGS_TYPE_MISSED = 3;
+  }
+  optional BeginFrameArgsType type = 1;
+  optional uint64 source_id = 2;
+  optional uint64 sequence_number = 3;
+  optional int64 frame_time_us = 4;
+  optional int64 deadline_us = 5;
+  optional int64 interval_delta_us = 6;
+  optional bool on_critical_path = 7;
+  optional bool animate_only = 8;
+  oneof created_from {
+    // The interned SourceLocation.
+    uint64 source_location_iid = 9;
+    // The SourceLocation that this args was created from.
+    // TODO(nuskos): Eventually we will support interning inside of
+    // TypedArgument TraceEvents and then we shouldn't need this SourceLocation
+    // since we can emit it as part of the InternedData message. When we can
+    // remove this |source_location|.
+    SourceLocation source_location = 10;
+  }
+  optional int64 frames_throttled_since_last = 11;
+}
+
+// Next id: 7
+message BeginImplFrameArgsV2 {
+  optional int64 updated_at_us = 1;
+  optional int64 finished_at_us = 2;
+  enum State {
+    BEGIN_FRAME_FINISHED = 0;
+    BEGIN_FRAME_USING = 1;
+  }
+  optional State state = 3;
+  oneof args {
+    // Only set if |state| is BEGIN_FRAME_FINISHED.
+    BeginFrameArgsV2 current_args = 4;
+    // Only set if |state| is BEGIN_FRAME_USING.
+    BeginFrameArgsV2 last_args = 5;
+  }
+  message TimestampsInUs {
+    optional int64 interval_delta = 1;
+    optional int64 now_to_deadline_delta = 2;
+    optional int64 frame_time_to_now_delta = 3;
+    optional int64 frame_time_to_deadline_delta = 4;
+    optional int64 now = 5;
+    optional int64 frame_time = 6;
+    optional int64 deadline = 7;
+  }
+  optional TimestampsInUs timestamps_in_us = 6;
+}
+
+message BeginFrameObserverStateV2 {
+  optional int64 dropped_begin_frame_args = 1;
+  optional BeginFrameArgsV2 last_begin_frame_args = 2;
+}
+
+message BeginFrameSourceStateV2 {
+  optional uint32 source_id = 1;
+  optional bool paused = 2;
+  optional uint32 num_observers = 3;
+  optional BeginFrameArgsV2 last_begin_frame_args = 4;
+}
+
+message CompositorTimingHistoryV2 {
+  optional int64 begin_main_frame_queue_critical_estimate_delta_us = 1;
+  optional int64 begin_main_frame_queue_not_critical_estimate_delta_us = 2;
+  optional int64 begin_main_frame_start_to_ready_to_commit_estimate_delta_us =
+      3;
+  optional int64 commit_to_ready_to_activate_estimate_delta_us = 4;
+  optional int64 prepare_tiles_estimate_delta_us = 5;
+  optional int64 activate_estimate_delta_us = 6;
+  optional int64 draw_estimate_delta_us = 7;
+}
+
 message ChromeTrackEvent {
   // Extension range for Chrome: 1000-1999
-  // Next ID: 1061
+  // Next ID: 1063
   extend TrackEvent {
     optional ChromeAppState chrome_app_state = 1000;
 
@@ -1715,5 +1968,7 @@
         1060;
 
     optional ViewClassName view_class_name = 1061;
+
+    optional ChromeCompositorSchedulerStateV2 cc_scheduler_state = 1062;
   }
 }
diff --git a/python/perfetto/trace_processor/trace_processor.descriptor b/python/perfetto/trace_processor/trace_processor.descriptor
index ce0bc66..bb8e8b9 100644
--- a/python/perfetto/trace_processor/trace_processor.descriptor
+++ b/python/perfetto/trace_processor/trace_processor.descriptor
Binary files differ
diff --git a/src/trace_processor/importers/common/metadata_tracker.cc b/src/trace_processor/importers/common/metadata_tracker.cc
index 7468c42..743e115 100644
--- a/src/trace_processor/importers/common/metadata_tracker.cc
+++ b/src/trace_processor/importers/common/metadata_tracker.cc
@@ -17,7 +17,6 @@
 #include "src/trace_processor/importers/common/metadata_tracker.h"
 
 #include "perfetto/ext/base/crash_keys.h"
-#include "src/trace_processor/importers/common/process_tracker.h"
 #include "src/trace_processor/storage/metadata.h"
 #include "src/trace_processor/types/trace_processor_context.h"
 
@@ -68,21 +67,23 @@
   return id_and_row.id;
 }
 
-SqlValue MetadataTracker::GetMetadata(metadata::KeyId key) {
+std::optional<SqlValue> MetadataTracker::GetMetadata(metadata::KeyId key) {
   // KeyType::kMulti not yet supported by this method:
   PERFETTO_CHECK(metadata::kKeyTypes[key] == metadata::KeyType::kSingle);
 
   auto* metadata_table = storage_->mutable_metadata_table();
   uint32_t key_idx = static_cast<uint32_t>(key);
-  uint32_t row =
-      metadata_table->name().IndexOf(metadata::kNames[key_idx]).value();
+  std::optional<uint32_t> row =
+      metadata_table->name().IndexOf(metadata::kNames[key_idx]);
+  if (!row.has_value())
+    return {};
 
   auto value_type = metadata::kValueTypes[key];
   switch (value_type) {
     case Variadic::kInt:
-      return metadata_table->mutable_int_value()->Get(row);
+      return metadata_table->mutable_int_value()->Get(*row);
     case Variadic::kString:
-      return metadata_table->mutable_str_value()->Get(row);
+      return metadata_table->mutable_str_value()->Get(*row);
     case Variadic::kNull:
       return SqlValue();
     case Variadic::kJson:
diff --git a/src/trace_processor/importers/common/metadata_tracker.h b/src/trace_processor/importers/common/metadata_tracker.h
index 3bd7379..2c63e24 100644
--- a/src/trace_processor/importers/common/metadata_tracker.h
+++ b/src/trace_processor/importers/common/metadata_tracker.h
@@ -20,6 +20,7 @@
 #include <array>
 #include <cstddef>
 #include <cstdint>
+#include <optional>
 
 #include "perfetto/trace_processor/basic_types.h"
 #include "src/trace_processor/storage/metadata.h"
@@ -50,7 +51,7 @@
 
   // Reads back a set metadata value.
   // Only kSingle types are supported right now.
-  SqlValue GetMetadata(metadata::KeyId key);
+  std::optional<SqlValue> GetMetadata(metadata::KeyId key);
 
   // Tracks how many ChromeMetadata bundles have been parsed.
   uint32_t IncrementChromeMetadataBundleCount() {
diff --git a/src/trace_processor/importers/common/sched_event_state.h b/src/trace_processor/importers/common/sched_event_state.h
index 4ebc6ce..6a97aed 100644
--- a/src/trace_processor/importers/common/sched_event_state.h
+++ b/src/trace_processor/importers/common/sched_event_state.h
@@ -17,15 +17,17 @@
 #ifndef SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_SCHED_EVENT_STATE_H_
 #define SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_SCHED_EVENT_STATE_H_
 
-#include <iosfwd>
+#include <limits>
+#include <vector>
 
 #include "src/trace_processor/storage/trace_storage.h"
-#include "src/trace_processor/types/version_number.h"
 
 namespace perfetto {
 namespace trace_processor {
 
 // Responsible for keeping the state of pending sched events.
+// TODO(rsavitski): consider folding back into ftrace parser. The ETW parser is
+// probably better off replicating its own pending state struct.
 class SchedEventState {
  public:
   // Information retained from the preceding sched_switch seen on a given cpu.
diff --git a/src/trace_processor/importers/common/sched_event_tracker.h b/src/trace_processor/importers/common/sched_event_tracker.h
index 68a2926..f83ffb6 100644
--- a/src/trace_processor/importers/common/sched_event_tracker.h
+++ b/src/trace_processor/importers/common/sched_event_tracker.h
@@ -18,11 +18,8 @@
 #define SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_SCHED_EVENT_TRACKER_H_
 
 #include "perfetto/ext/base/string_view.h"
-#include "perfetto/ext/base/utils.h"
 #include "src/trace_processor/importers/common/event_tracker.h"
-#include "src/trace_processor/importers/common/process_tracker.h"
 #include "src/trace_processor/importers/common/system_info_tracker.h"
-#include "src/trace_processor/importers/common/thread_state_tracker.h"
 #include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/types/destructible.h"
 #include "src/trace_processor/types/task_state.h"
@@ -31,11 +28,13 @@
 namespace perfetto {
 namespace trace_processor {
 
-// Tracks sched events and stores them into the storage as sched slices.
+// Tracks per-cpu scheduling events, storing them as slices in the |sched|
+// table.
 class SchedEventTracker : public Destructible {
  public:
   PERFETTO_ALWAYS_INLINE
-  SchedEventTracker(TraceProcessorContext* context) : context_(context) {}
+  explicit SchedEventTracker(TraceProcessorContext* context)
+      : context_(context) {}
   SchedEventTracker(const SchedEventTracker&) = delete;
   ~SchedEventTracker() override;
 
@@ -58,12 +57,11 @@
   bool UpdateEventTrackerTimestamp(int64_t ts,
                                    const char* event_name,
                                    size_t stats) {
-    // At this stage all events should be globally timestamp ordered.
-    if (ts < context_->event_tracker->max_timestamp()) {
-      PERFETTO_ELOG(
-          "%s event out of order by %.4f ms, skipping", event_name,
-          static_cast<double>(context_->event_tracker->max_timestamp() - ts) /
-              1e6);
+    // Post sorter stage, all events should be globally timestamp ordered.
+    int64_t max_ts = context_->event_tracker->max_timestamp();
+    if (ts < max_ts) {
+      PERFETTO_ELOG("%s event out of order by %.4f ms, skipping", event_name,
+                    static_cast<double>(max_ts - ts) / 1e6);
       context_->storage->IncrementStats(stats);
       return false;
     }
@@ -86,6 +84,7 @@
     slices->mutable_end_state()->Set(pending_slice_idx, prev_state);
   }
 
+  // TODO(rsavitski): fold back into ftrace parser, this is specific to Linux.
   PERFETTO_ALWAYS_INLINE
   StringId TaskStateToStringId(int64_t task_state_int) {
     using ftrace_utils::TaskState;
diff --git a/src/trace_processor/importers/ftrace/ftrace_module_impl.cc b/src/trace_processor/importers/ftrace/ftrace_module_impl.cc
index 3c535fb..bd67c64 100644
--- a/src/trace_processor/importers/ftrace/ftrace_module_impl.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_module_impl.cc
@@ -15,10 +15,11 @@
  */
 
 #include "src/trace_processor/importers/ftrace/ftrace_module_impl.h"
-#include "perfetto/base/build_config.h"
+
 #include "perfetto/trace_processor/trace_blob_view.h"
 #include "src/trace_processor/importers/ftrace/ftrace_parser.h"
 #include "src/trace_processor/importers/ftrace/ftrace_tokenizer.h"
+#include "src/trace_processor/importers/proto/proto_importer_module.h"
 
 #include "protos/perfetto/trace/trace_packet.pbzero.h"
 
diff --git a/src/trace_processor/importers/ftrace/ftrace_module_impl.h b/src/trace_processor/importers/ftrace/ftrace_module_impl.h
index a341869..658bafc 100644
--- a/src/trace_processor/importers/ftrace/ftrace_module_impl.h
+++ b/src/trace_processor/importers/ftrace/ftrace_module_impl.h
@@ -17,7 +17,6 @@
 #ifndef SRC_TRACE_PROCESSOR_IMPORTERS_FTRACE_FTRACE_MODULE_IMPL_H_
 #define SRC_TRACE_PROCESSOR_IMPORTERS_FTRACE_FTRACE_MODULE_IMPL_H_
 
-#include "perfetto/base/build_config.h"
 #include "protos/perfetto/trace/trace_packet.pbzero.h"
 #include "src/trace_processor/importers/common/parser_types.h"
 #include "src/trace_processor/importers/ftrace/ftrace_module.h"
diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.cc b/src/trace_processor/importers/ftrace/ftrace_parser.cc
index 69f1b58..95cf0d8 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.cc
@@ -17,18 +17,21 @@
 #include "src/trace_processor/importers/ftrace/ftrace_parser.h"
 
 #include "perfetto/base/logging.h"
+#include "perfetto/base/status.h"
 #include "perfetto/ext/base/string_utils.h"
-#include "perfetto/ext/base/string_writer.h"
 #include "perfetto/protozero/proto_decoder.h"
 
+#include "perfetto/trace_processor/basic_types.h"
 #include "src/trace_processor/importers/common/args_tracker.h"
 #include "src/trace_processor/importers/common/async_track_set_tracker.h"
 #include "src/trace_processor/importers/common/metadata_tracker.h"
 #include "src/trace_processor/importers/common/parser_types.h"
 #include "src/trace_processor/importers/common/process_tracker.h"
+#include "src/trace_processor/importers/common/system_info_tracker.h"
 #include "src/trace_processor/importers/common/thread_state_tracker.h"
 #include "src/trace_processor/importers/common/track_tracker.h"
 #include "src/trace_processor/importers/ftrace/binder_tracker.h"
+#include "src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.h"
 #include "src/trace_processor/importers/ftrace/v4l2_tracker.h"
 #include "src/trace_processor/importers/ftrace/virtio_video_tracker.h"
 #include "src/trace_processor/importers/i2c/i2c_tracker.h"
@@ -59,7 +62,6 @@
 #include "protos/perfetto/trace/ftrace/ion.pbzero.h"
 #include "protos/perfetto/trace/ftrace/irq.pbzero.h"
 #include "protos/perfetto/trace/ftrace/kmem.pbzero.h"
-#include "protos/perfetto/trace/ftrace/lowmemorykiller.pbzero.h"
 #include "protos/perfetto/trace/ftrace/lwis.pbzero.h"
 #include "protos/perfetto/trace/ftrace/mali.pbzero.h"
 #include "protos/perfetto/trace/ftrace/mdss.pbzero.h"
@@ -229,6 +231,7 @@
   RPM_SUSPENDING,
 };
 }  // namespace
+
 FtraceParser::FtraceParser(TraceProcessorContext* context)
     : context_(context),
       rss_stat_tracker_(context),
@@ -654,6 +657,15 @@
       ParseTypedFtraceToRaw(fld.id(), ts, cpu, pid, fld_bytes, seq_state);
     }
 
+    // Skip everything besides the |raw| write if we're at the start of the
+    // trace and not all per-cpu buffers cover this region yet. Otherwise if
+    // this event signifies a beginning of an operation that can end on a
+    // different cpu, we could conclude that the operation never ends.
+    // See b/192586066.
+    if (PERFETTO_UNLIKELY(ts < soft_drop_ftrace_data_before_ts_)) {
+      return base::OkStatus();
+    }
+
     if (PkvmHypervisorCpuTracker::IsPkvmHypervisorEvent(fld.id())) {
       pkvm_hyp_cpu_tracker_.ParseHypEvent(cpu, ts, fld.id(), fld_bytes);
     }
@@ -1145,10 +1157,14 @@
     int64_t ts,
     const InlineSchedSwitch& data) {
   MaybeOnFirstFtraceEvent();
-  if (PERFETTO_UNLIKELY(ts < drop_ftrace_data_before_ts_)) {
-    context_->storage->IncrementStats(
-        stats::ftrace_packet_before_tracing_start);
-    return util::OkStatus();
+  bool parse_only_into_raw = false;
+  if (PERFETTO_UNLIKELY(ts < soft_drop_ftrace_data_before_ts_)) {
+    parse_only_into_raw = true;
+    if (ts < drop_ftrace_data_before_ts_) {
+      context_->storage->IncrementStats(
+          stats::ftrace_packet_before_tracing_start);
+      return util::OkStatus();
+    }
   }
 
   using protos::pbzero::FtraceEvent;
@@ -1156,7 +1172,7 @@
       FtraceSchedEventTracker::GetOrCreate(context_);
   ftrace_sched_tracker->PushSchedSwitchCompact(
       cpu, ts, data.prev_state, static_cast<uint32_t>(data.next_pid),
-      data.next_prio, data.next_comm);
+      data.next_prio, data.next_comm, parse_only_into_raw);
   return util::OkStatus();
 }
 
@@ -1165,17 +1181,22 @@
     int64_t ts,
     const InlineSchedWaking& data) {
   MaybeOnFirstFtraceEvent();
-  if (PERFETTO_UNLIKELY(ts < drop_ftrace_data_before_ts_)) {
-    context_->storage->IncrementStats(
-        stats::ftrace_packet_before_tracing_start);
-    return util::OkStatus();
+  bool parse_only_into_raw = false;
+  if (PERFETTO_UNLIKELY(ts < soft_drop_ftrace_data_before_ts_)) {
+    parse_only_into_raw = true;
+    if (ts < drop_ftrace_data_before_ts_) {
+      context_->storage->IncrementStats(
+          stats::ftrace_packet_before_tracing_start);
+      return util::OkStatus();
+    }
   }
+
   using protos::pbzero::FtraceEvent;
   FtraceSchedEventTracker* ftrace_sched_tracker =
       FtraceSchedEventTracker::GetOrCreate(context_);
   ftrace_sched_tracker->PushSchedWakingCompact(
       cpu, ts, static_cast<uint32_t>(data.pid), data.target_cpu, data.prio,
-      data.comm, data.common_flags);
+      data.comm, data.common_flags, parse_only_into_raw);
   return util::OkStatus();
 }
 
@@ -1184,6 +1205,8 @@
     return;
   }
 
+  // Calculate the timestamp used to skip events that predate the time when
+  // tracing started.
   DropFtraceDataBefore drop_before =
       preserve_ftrace_buffer_ ? DropFtraceDataBefore::kNoDrop
                               : context_->config.drop_ftrace_data_before;
@@ -1198,15 +1221,34 @@
           drop_before == DropFtraceDataBefore::kAllDataSourcesStarted
               ? metadata::all_data_source_started_ns
               : metadata::tracing_started_ns;
-      const auto& metadata = context_->storage->metadata_table();
-      std::optional<uint32_t> opt_row =
-          metadata.name().IndexOf(metadata::kNames[event_key]);
-      if (opt_row) {
-        drop_ftrace_data_before_ts_ = *metadata.int_value()[*opt_row];
-      }
+
+      drop_ftrace_data_before_ts_ =
+          context_->metadata_tracker->GetMetadata(event_key)
+              .value_or(SqlValue::Long(0))
+              .AsLong();
       break;
     }
   }
+
+  // Calculate the timestamp used to skip events since, while still populating
+  // the |ftrace_events| table.
+  switch (context_->config.soft_drop_ftrace_data_before) {
+    case SoftDropFtraceDataBefore::kNoDrop: {
+      soft_drop_ftrace_data_before_ts_ = 0;
+      break;
+    }
+    case SoftDropFtraceDataBefore::kAllPerCpuBuffersValid: {
+      soft_drop_ftrace_data_before_ts_ =
+          context_->metadata_tracker
+              ->GetMetadata(metadata::ftrace_latest_data_start_ns)
+              .value_or(SqlValue::Long(0))
+              .AsLong();
+      break;
+    }
+  }
+  soft_drop_ftrace_data_before_ts_ =
+      std::max(soft_drop_ftrace_data_before_ts_, drop_ftrace_data_before_ts_);
+
   has_seen_first_ftrace_packet_ = true;
 }
 
@@ -1277,7 +1319,7 @@
     StringId name_id = message_strings.field_name_ids[field_id];
 
     // Check if this field represents a kernel function.
-    auto it = std::find_if(
+    const auto* it = std::find_if(
         kKernelFunctionFields.begin(), kKernelFunctionFields.end(),
         [ftrace_id, field_id](const FtraceEventAndFieldId& ev) {
           return ev.event_id == ftrace_id && ev.field_id == field_id;
diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.h b/src/trace_processor/importers/ftrace/ftrace_parser.h
index de0b3bb..a3f8fc6 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.h
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.h
@@ -17,16 +17,19 @@
 #ifndef SRC_TRACE_PROCESSOR_IMPORTERS_FTRACE_FTRACE_PARSER_H_
 #define SRC_TRACE_PROCESSOR_IMPORTERS_FTRACE_FTRACE_PARSER_H_
 
+#include <cstdint>
+
+#include <string>
+#include <unordered_set>
+#include <vector>
+
 #include "perfetto/ext/base/flat_hash_map.h"
 #include "perfetto/ext/base/hash.h"
-#include "perfetto/trace_processor/status.h"
 #include "src/trace_processor/importers/common/event_tracker.h"
 #include "src/trace_processor/importers/common/parser_types.h"
-#include "src/trace_processor/importers/common/system_info_tracker.h"
 #include "src/trace_processor/importers/common/trace_parser.h"
 #include "src/trace_processor/importers/ftrace/drm_tracker.h"
 #include "src/trace_processor/importers/ftrace/ftrace_descriptors.h"
-#include "src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.h"
 #include "src/trace_processor/importers/ftrace/gpu_work_period_tracker.h"
 #include "src/trace_processor/importers/ftrace/iostat_tracker.h"
 #include "src/trace_processor/importers/ftrace/mali_gpu_event_tracker.h"
@@ -35,8 +38,6 @@
 #include "src/trace_processor/importers/ftrace/virtio_gpu_tracker.h"
 #include "src/trace_processor/types/trace_processor_context.h"
 
-#include <unordered_set>
-
 namespace perfetto {
 namespace trace_processor {
 
@@ -439,10 +440,14 @@
 
   bool has_seen_first_ftrace_packet_ = false;
 
-  // Stores information about the timestamp from the metadata table which is
-  // used to filter ftrace packets which happen before this point.
+  // Ftrace events before this timestamp get dropped.
   int64_t drop_ftrace_data_before_ts_ = 0;
 
+  // Ftrace events before this timestamp get parsed into the |ftrace_events|
+  // table, but don't get propagated into other tables/trackers.
+  // Must be no less than drop_ftrace_data_before_ts_.
+  int64_t soft_drop_ftrace_data_before_ts_ = 0;
+
   // Does not skip any ftrace events.
   bool preserve_ftrace_buffer_ = false;
 
diff --git a/src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.cc b/src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.cc
index 36bf028..67c3691 100644
--- a/src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.cc
@@ -16,19 +16,16 @@
 
 #include "src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.h"
 
-#include <math.h>
+#include <limits>
 
-#include "perfetto/ext/base/utils.h"
 #include "src/trace_processor/importers/common/args_tracker.h"
 #include "src/trace_processor/importers/common/event_tracker.h"
 #include "src/trace_processor/importers/common/process_tracker.h"
-#include "src/trace_processor/importers/common/sched_event_tracker.h"
 #include "src/trace_processor/importers/common/sched_event_state.h"
-#include "src/trace_processor/importers/common/system_info_tracker.h"
+#include "src/trace_processor/importers/common/sched_event_tracker.h"
 #include "src/trace_processor/importers/common/thread_state_tracker.h"
 #include "src/trace_processor/importers/ftrace/ftrace_descriptors.h"
 #include "src/trace_processor/storage/stats.h"
-#include "src/trace_processor/types/task_state.h"
 #include "src/trace_processor/types/trace_processor_context.h"
 #include "src/trace_processor/types/variadic.h"
 
@@ -132,21 +129,21 @@
                                                      int64_t prev_state,
                                                      uint32_t next_pid,
                                                      int32_t next_prio,
-                                                     StringId next_comm_id) {
-  if (!context_->sched_event_tracker->UpdateEventTrackerTimestamp(ts, 
-      "sched_switch", stats::sched_switch_out_of_order)) {
+                                                     StringId next_comm_id,
+                                                     bool parse_only_into_raw) {
+  if (!context_->sched_event_tracker->UpdateEventTrackerTimestamp(
+          ts, "sched_switch", stats::sched_switch_out_of_order)) {
     return;
   }
 
   UniqueTid next_utid = context_->process_tracker->UpdateThreadName(
       next_pid, next_comm_id, ThreadNamePriority::kFtrace);
 
-  auto* pending_sched = sched_event_state_.GetPendingSchedInfoForCpu(cpu);
-
   // If we're processing the first compact event for this cpu, don't start a
   // slice since we're missing the "prev_*" fields. The successive events will
   // create slices as normal, but the first per-cpu switch is effectively
   // discarded.
+  auto* pending_sched = sched_event_state_.GetPendingSchedInfoForCpu(cpu);
   if (pending_sched->last_utid == std::numeric_limits<UniqueTid>::max()) {
     context_->storage->IncrementStats(stats::compact_sched_switch_skipped);
 
@@ -161,14 +158,15 @@
   // Close the pending slice if any (we won't have one when processing the first
   // two compact events for a given cpu).
   uint32_t pending_slice_idx = pending_sched->pending_slice_storage_idx;
-  StringId prev_state_string_id = context_->sched_event_tracker
-                                      ->TaskStateToStringId(prev_state);
-  if (prev_state_string_id == kNullStringId) {
+  StringId prev_state_str_id =
+      context_->sched_event_tracker->TaskStateToStringId(prev_state);
+  if (prev_state_str_id == kNullStringId) {
     context_->storage->IncrementStats(stats::task_state_invalid);
   }
-  if (pending_slice_idx < std::numeric_limits<uint32_t>::max())
+  if (pending_slice_idx != std::numeric_limits<uint32_t>::max()) {
     context_->sched_event_tracker->ClosePendingSlice(pending_slice_idx, ts,
-        prev_state_string_id);
+                                                     prev_state_str_id);
+  }
 
   // Use the previous event's values to infer this event's "prev_*" fields.
   // There are edge cases, but this assumption should still produce sensible
@@ -184,19 +182,28 @@
           kNullStringId);
 
   AddRawSchedSwitchEvent(cpu, ts, prev_utid, prev_pid, prev_comm_id, prev_prio,
-      prev_state, next_pid, next_comm_id, next_prio);
-  auto new_slice_idx = context_->sched_event_tracker
-                           ->AddStartSlice(cpu, ts, next_utid, next_prio);
+                         prev_state, next_pid, next_comm_id, next_prio);
 
-  // Finally, update the info for the next sched switch on this CPU.
-  pending_sched->pending_slice_storage_idx = new_slice_idx;
+  // Update the info for the next sched switch on this CPU.
   pending_sched->last_pid = next_pid;
   pending_sched->last_utid = next_utid;
   pending_sched->last_prio = next_prio;
 
-  // Update the ThreadState table.
+  // Subtle: if only inserting into raw, we're ending with:
+  // * updated |pending_sched->last_*| fields
+  // * still-defaulted |pending_slice_storage_idx|
+  // This is similar to the first compact_sched_switch per cpu.
+  if (PERFETTO_UNLIKELY(parse_only_into_raw))
+    return;
+
+  // Update per-cpu Sched table.
+  auto new_slice_idx = context_->sched_event_tracker->AddStartSlice(
+      cpu, ts, next_utid, next_prio);
+  pending_sched->pending_slice_storage_idx = new_slice_idx;
+
+  // Update the per-thread ThreadState table.
   ThreadStateTracker::GetOrCreate(context_)->PushSchedSwitchEvent(
-      ts, cpu, prev_utid, prev_state_string_id, next_utid);
+      ts, cpu, prev_utid, prev_state_str_id, next_utid);
 }
 
 // Processes a sched_waking that was decoded from a compact representation,
@@ -207,9 +214,10 @@
                                                      uint16_t target_cpu,
                                                      uint16_t prio,
                                                      StringId comm_id,
-                                                     uint16_t common_flags) {
-  if (!context_->sched_event_tracker->UpdateEventTrackerTimestamp(ts,
-      "sched_waking", stats::sched_waking_out_of_order)) {
+                                                     uint16_t common_flags,
+                                                     bool parse_only_into_raw) {
+  if (!context_->sched_event_tracker->UpdateEventTrackerTimestamp(
+          ts, "sched_waking", stats::sched_waking_out_of_order)) {
     return;
   }
 
@@ -248,6 +256,9 @@
     add_raw_arg(SW::kTargetCpuFieldNumber, Variadic::Integer(target_cpu));
   }
 
+  if (PERFETTO_UNLIKELY(parse_only_into_raw))
+    return;
+
   // Add a waking entry to the ThreadState table.
   auto wakee_utid = context_->process_tracker->GetOrCreateThread(wakee_pid);
   ThreadStateTracker::GetOrCreate(context_)->PushWakingEvent(
diff --git a/src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.h b/src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.h
index bc90532..42b0d4f 100644
--- a/src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.h
+++ b/src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.h
@@ -17,12 +17,11 @@
 #ifndef SRC_TRACE_PROCESSOR_IMPORTERS_FTRACE_FTRACE_SCHED_EVENT_TRACKER_H_
 #define SRC_TRACE_PROCESSOR_IMPORTERS_FTRACE_FTRACE_SCHED_EVENT_TRACKER_H_
 
+#include <cstdint>
+
 #include <array>
-#include <limits>
 
 #include "perfetto/ext/base/string_view.h"
-#include "perfetto/ext/base/utils.h"
-#include "src/trace_processor/importers/common/sched_event_tracker.h"
 #include "src/trace_processor/importers/common/sched_event_state.h"
 #include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/types/destructible.h"
@@ -82,7 +81,8 @@
                               int64_t prev_state,
                               uint32_t next_pid,
                               int32_t next_prio,
-                              StringId next_comm_id);
+                              StringId next_comm_id,
+                              bool parse_only_into_raw);
 
   // This method is called when parsing a sched_waking encoded in the compact
   // format. Note that the default encoding is handled by
@@ -93,7 +93,8 @@
                               uint16_t target_cpu,
                               uint16_t prio,
                               StringId comm_id,
-                              uint16_t common_flags);
+                              uint16_t common_flags,
+                              bool parse_only_into_raw);
 
  private:
   static constexpr uint8_t kSchedSwitchMaxFieldId = 7;
diff --git a/src/trace_processor/importers/ftrace/ftrace_tokenizer.cc b/src/trace_processor/importers/ftrace/ftrace_tokenizer.cc
index 53ba885..9d678d1 100644
--- a/src/trace_processor/importers/ftrace/ftrace_tokenizer.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_tokenizer.cc
@@ -17,12 +17,18 @@
 #include "src/trace_processor/importers/ftrace/ftrace_tokenizer.h"
 
 #include "perfetto/base/logging.h"
+#include "perfetto/base/status.h"
 #include "perfetto/protozero/proto_decoder.h"
 #include "perfetto/protozero/proto_utils.h"
+#include "perfetto/trace_processor/basic_types.h"
+#include "src/trace_processor/importers/common/metadata_tracker.h"
 #include "src/trace_processor/importers/proto/packet_sequence_state.h"
 #include "src/trace_processor/sorter/trace_sorter.h"
+#include "src/trace_processor/storage/metadata.h"
 #include "src/trace_processor/storage/stats.h"
 #include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/types/variadic.h"
+#include "src/trace_processor/util/status_macros.h"
 
 #include "protos/perfetto/common/builtin_clock.pbzero.h"
 #include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
@@ -160,6 +166,39 @@
     TokenizeFtraceEvent(cpu, clock_id, bundle.slice(it->data(), it->size()),
                         state);
   }
+
+  // First bundle on each cpu is special since ftrace is recorded in per-cpu
+  // buffers. In traces written by perfetto v44+ we know the timestamp from
+  // which this cpu's data stream is valid. This is important for parsing ring
+  // buffer traces, as not all per-cpu data streams will be valid from the same
+  // timestamp.
+  if (cpu >= per_cpu_seen_first_bundle_.size()) {
+    per_cpu_seen_first_bundle_.resize(cpu + 1);
+  }
+  if (!per_cpu_seen_first_bundle_[cpu]) {
+    per_cpu_seen_first_bundle_[cpu] = true;
+
+    // if this cpu's timestamp is the new max, update the metadata table entry
+    if (decoder.has_last_read_event_timestamp()) {
+      int64_t timestamp = 0;
+      ASSIGN_OR_RETURN(
+          timestamp,
+          ResolveTraceTime(
+              context_, clock_id,
+              static_cast<int64_t>(decoder.last_read_event_timestamp())));
+
+      std::optional<SqlValue> curr_latest_timestamp =
+          context_->metadata_tracker->GetMetadata(
+              metadata::ftrace_latest_data_start_ns);
+
+      if (!curr_latest_timestamp.has_value() ||
+          timestamp > curr_latest_timestamp->AsLong()) {
+        context_->metadata_tracker->SetMetadata(
+            metadata::ftrace_latest_data_start_ns,
+            Variadic::Integer(timestamp));
+      }
+    }
+  }
   return base::OkStatus();
 }
 
diff --git a/src/trace_processor/importers/ftrace/ftrace_tokenizer.h b/src/trace_processor/importers/ftrace/ftrace_tokenizer.h
index 6e905c8..915ee9c 100644
--- a/src/trace_processor/importers/ftrace/ftrace_tokenizer.h
+++ b/src/trace_processor/importers/ftrace/ftrace_tokenizer.h
@@ -17,6 +17,8 @@
 #ifndef SRC_TRACE_PROCESSOR_IMPORTERS_FTRACE_FTRACE_TOKENIZER_H_
 #define SRC_TRACE_PROCESSOR_IMPORTERS_FTRACE_FTRACE_TOKENIZER_H_
 
+#include <vector>
+
 #include "perfetto/trace_processor/trace_blob_view.h"
 #include "src/trace_processor/importers/common/clock_tracker.h"
 #include "src/trace_processor/storage/trace_storage.h"
@@ -64,13 +66,14 @@
                                    TraceBlobView event,
                                    PacketSequenceState* state);
 
-  void DlogWithLimit(base::Status status) {
+  void DlogWithLimit(const base::Status& status) {
     static std::atomic<uint32_t> dlog_count(0);
     if (dlog_count++ < 10)
       PERFETTO_DLOG("%s", status.c_message());
   }
 
   int64_t latest_ftrace_clock_snapshot_ts_ = 0;
+  std::vector<bool> per_cpu_seen_first_bundle_;
   TraceProcessorContext* context_;
 };
 
diff --git a/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc b/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
index e612709..545418f 100644
--- a/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
+++ b/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
@@ -2901,7 +2901,8 @@
   ASSERT_TRUE(Tokenize().ok());
   context_.sorter->ExtractEventsForced();
 
-  SqlValue value = context_.metadata_tracker->GetMetadata(metadata::trace_uuid);
+  SqlValue value =
+      context_.metadata_tracker->GetMetadata(metadata::trace_uuid).value();
   EXPECT_STREQ(value.string_value, "00000000-0000-0002-0000-000000000001");
   ASSERT_TRUE(context_.uuid_found_in_trace);
 }
@@ -2914,7 +2915,8 @@
   ASSERT_TRUE(Tokenize().ok());
   context_.sorter->ExtractEventsForced();
 
-  SqlValue value = context_.metadata_tracker->GetMetadata(metadata::trace_uuid);
+  SqlValue value =
+      context_.metadata_tracker->GetMetadata(metadata::trace_uuid).value();
   EXPECT_STREQ(value.string_value, "00000000-0000-0002-0000-000000000001");
   ASSERT_TRUE(context_.uuid_found_in_trace);
 }
@@ -2933,7 +2935,8 @@
   ASSERT_TRUE(Tokenize().ok());
   context_.sorter->ExtractEventsForced();
 
-  SqlValue value = context_.metadata_tracker->GetMetadata(metadata::trace_uuid);
+  SqlValue value =
+      context_.metadata_tracker->GetMetadata(metadata::trace_uuid).value();
   EXPECT_STREQ(value.string_value, "00000000-0000-0002-0000-000000000001");
   ASSERT_TRUE(context_.uuid_found_in_trace);
 }
@@ -2946,7 +2949,8 @@
   context_.sorter->ExtractEventsForced();
 
   SqlValue value =
-      context_.metadata_tracker->GetMetadata(metadata::trace_config_pbtxt);
+      context_.metadata_tracker->GetMetadata(metadata::trace_config_pbtxt)
+          .value();
   EXPECT_THAT(value.string_value, HasSubstr("size_kb: 42"));
 }
 
diff --git a/src/trace_processor/rpc/rpc.cc b/src/trace_processor/rpc/rpc.cc
index 58fb245..b4552e5 100644
--- a/src/trace_processor/rpc/rpc.cc
+++ b/src/trace_processor/rpc/rpc.cc
@@ -28,6 +28,7 @@
 #include "perfetto/ext/trace_processor/rpc/query_result_serializer.h"
 #include "perfetto/protozero/scattered_heap_buffer.h"
 #include "perfetto/protozero/scattered_stream_writer.h"
+#include "perfetto/trace_processor/basic_types.h"
 #include "perfetto/trace_processor/trace_processor.h"
 #include "src/trace_processor/tp_metatrace.h"
 
@@ -346,6 +347,12 @@
     config.analyze_trace_proto_content =
         reset_trace_processor_args.analyze_trace_proto_content();
   }
+  if (reset_trace_processor_args.has_ftrace_drop_until_all_cpus_valid()) {
+    config.soft_drop_ftrace_data_before =
+        reset_trace_processor_args.ftrace_drop_until_all_cpus_valid()
+            ? SoftDropFtraceDataBefore::kAllPerCpuBuffersValid
+            : SoftDropFtraceDataBefore::kNoDrop;
+  }
   ResetTraceProcessorInternal(config);
 }
 
diff --git a/src/trace_processor/storage/metadata.h b/src/trace_processor/storage/metadata.h
index 1fa85ae..864d223 100644
--- a/src/trace_processor/storage/metadata.h
+++ b/src/trace_processor/storage/metadata.h
@@ -19,7 +19,6 @@
 
 #include <stddef.h>
 
-#include "src/trace_processor/containers/string_pool.h"
 #include "src/trace_processor/types/variadic.h"
 
 namespace perfetto {
@@ -43,6 +42,7 @@
   F(benchmark_story_run_time_us,       KeyType::kSingle,  Variadic::kInt),    \
   F(benchmark_story_tags,              KeyType::kMulti,   Variadic::kString), \
   F(ftrace_setup_errors,               KeyType::kMulti,   Variadic::kString), \
+  F(ftrace_latest_data_start_ns,       KeyType::kSingle,  Variadic::kInt),    \
   F(range_of_interest_start_us,        KeyType::kSingle,  Variadic::kInt),    \
   F(statsd_triggering_subscription_id, KeyType::kSingle,  Variadic::kInt),    \
   F(system_machine,                    KeyType::kSingle,  Variadic::kString), \
diff --git a/src/traced/probes/ftrace/cpu_reader.cc b/src/traced/probes/ftrace/cpu_reader.cc
index c161203..462c992 100644
--- a/src/traced/probes/ftrace/cpu_reader.cc
+++ b/src/traced/probes/ftrace/cpu_reader.cc
@@ -114,8 +114,7 @@
 }
 
 // Reads a signed ftrace value as an int64_t, sign extending if necessary.
-static int64_t ReadSignedFtraceValue(const uint8_t* ptr,
-                                     FtraceFieldType ftrace_type) {
+int64_t ReadSignedFtraceValue(const uint8_t* ptr, FtraceFieldType ftrace_type) {
   if (ftrace_type == kFtraceInt32) {
     int32_t value;
     memcpy(&value, reinterpret_cast<const void*>(ptr), sizeof(value));
@@ -302,30 +301,36 @@
     ProcessPagesForDataSource(
         data_source->trace_writer(), data_source->mutable_metadata(), cpu_,
         data_source->parsing_config(), data_source->mutable_parse_errors(),
-        parsing_buf, pages_read, compact_sched_buf, table_, symbolizer_,
-        ftrace_clock_snapshot_, ftrace_clock_);
+        &last_read_event_ts_, parsing_buf, pages_read, compact_sched_buf,
+        table_, symbolizer_, ftrace_clock_snapshot_, ftrace_clock_);
   }
 
   return pages_read;
 }
 
-void CpuReader::Bundler::StartNewPacket(bool lost_events) {
+void CpuReader::Bundler::StartNewPacket(bool lost_events,
+                                        uint64_t last_read_event_timestamp) {
   FinalizeAndRunSymbolizer();
   packet_ = trace_writer_->NewTracePacket();
   bundle_ = packet_->set_ftrace_events();
-  if (ftrace_clock_) {
-    bundle_->set_ftrace_clock(ftrace_clock_);
-
-    if (ftrace_clock_snapshot_ && ftrace_clock_snapshot_->ftrace_clock_ts) {
-      bundle_->set_ftrace_timestamp(ftrace_clock_snapshot_->ftrace_clock_ts);
-      bundle_->set_boot_timestamp(ftrace_clock_snapshot_->boot_clock_ts);
-    }
-  }
 
   bundle_->set_cpu(static_cast<uint32_t>(cpu_));
   if (lost_events) {
     bundle_->set_lost_events(true);
   }
+
+  // note: set-to-zero is valid and expected for the first bundle per cpu
+  // (outside of concurrent tracing), with the effective meaning of "all data is
+  // valid since the data source was started".
+  bundle_->set_last_read_event_timestamp(last_read_event_timestamp);
+
+  if (ftrace_clock_) {
+    bundle_->set_ftrace_clock(ftrace_clock_);
+    if (ftrace_clock_snapshot_ && ftrace_clock_snapshot_->ftrace_clock_ts) {
+      bundle_->set_ftrace_timestamp(ftrace_clock_snapshot_->ftrace_clock_ts);
+      bundle_->set_boot_timestamp(ftrace_clock_snapshot_->boot_clock_ts);
+    }
+  }
 }
 
 void CpuReader::Bundler::FinalizeAndRunSymbolizer() {
@@ -409,6 +414,7 @@
     size_t cpu,
     const FtraceDataSourceConfig* ds_config,
     base::FlatSet<protos::pbzero::FtraceParseStatus>* parse_errors,
+    uint64_t* last_read_event_ts,
     const uint8_t* parsing_buf,
     const size_t pages_read,
     CompactSchedBuffer* compact_sched_buf,
@@ -420,7 +426,7 @@
   Bundler bundler(trace_writer, metadata,
                   ds_config->symbolize_ksyms ? symbolizer : nullptr, cpu,
                   ftrace_clock_snapshot, ftrace_clock, compact_sched_buf,
-                  ds_config->compact_sched.enabled);
+                  ds_config->compact_sched.enabled, *last_read_event_ts);
 
   bool success = true;
   size_t pages_parsed = 0;
@@ -456,11 +462,14 @@
             kCompactSchedInternerThreshold;
 
     if (page_header->lost_events || interner_past_threshold) {
-      bundler.StartNewPacket(page_header->lost_events);
+      // pass in an updated last_read_event_ts since we're starting a new
+      // bundle, which needs to reference the last timestamp from the prior one.
+      bundler.StartNewPacket(page_header->lost_events, *last_read_event_ts);
     }
 
-    FtraceParseStatus status = ParsePagePayload(
-        parse_pos, &page_header.value(), table, ds_config, &bundler, metadata);
+    FtraceParseStatus status =
+        ParsePagePayload(parse_pos, &page_header.value(), table, ds_config,
+                         &bundler, metadata, last_read_event_ts);
 
     if (status != FtraceParseStatus::FTRACE_STATUS_OK) {
       WriteAndSetParseError(&bundler, parse_errors, page_header->timestamp,
@@ -540,11 +549,13 @@
     const ProtoTranslationTable* table,
     const FtraceDataSourceConfig* ds_config,
     Bundler* bundler,
-    FtraceMetadata* metadata) {
+    FtraceMetadata* metadata,
+    uint64_t* last_read_event_ts) {
   const uint8_t* ptr = start_of_payload;
   const uint8_t* const end = ptr + page_header->size;
 
   uint64_t timestamp = page_header->timestamp;
+  uint64_t last_data_record_ts = 0;
 
   while (ptr < end) {
     EventHeader event_header;
@@ -674,11 +685,13 @@
             }
           }
         }
-        // Jump to next event.
-        ptr = next;
+        last_data_record_ts = timestamp;
+        ptr = next;  // jump to next event
       }  // default case
     }    // switch (event_header.type_or_length)
   }      // while (ptr < end)
+  if (last_data_record_ts)
+    *last_read_event_ts = last_data_record_ts;
   return FtraceParseStatus::FTRACE_STATUS_OK;
 }
 
@@ -715,7 +728,7 @@
                         protos::pbzero::FtraceEvent::kGenericFieldNumber)) {
     nested->AppendString(GenericFtraceEvent::kEventNameFieldNumber, info.name);
     for (const Field& field : info.fields) {
-      auto generic_field = nested->BeginNestedMessage<protozero::Message>(
+      auto* generic_field = nested->BeginNestedMessage<protozero::Message>(
           GenericFtraceEvent::kFieldFieldNumber);
       generic_field->AppendString(GenericFtraceEvent::Field::kNameFieldNumber,
                                   field.ftrace_name);
diff --git a/src/traced/probes/ftrace/cpu_reader.h b/src/traced/probes/ftrace/cpu_reader.h
index 9282901..821ba48 100644
--- a/src/traced/probes/ftrace/cpu_reader.h
+++ b/src/traced/probes/ftrace/cpu_reader.h
@@ -17,8 +17,8 @@
 #ifndef SRC_TRACED_PROBES_FTRACE_CPU_READER_H_
 #define SRC_TRACED_PROBES_FTRACE_CPU_READER_H_
 
-#include <stdint.h>
 #include <string.h>
+#include <cstdint>
 
 #include <optional>
 #include <set>
@@ -115,7 +115,8 @@
             const FtraceClockSnapshot* ftrace_clock_snapshot,
             protos::pbzero::FtraceClock ftrace_clock,
             CompactSchedBuffer* compact_sched_buf,
-            bool compact_sched_enabled)
+            bool compact_sched_enabled,
+            uint64_t last_read_event_ts)
         : trace_writer_(trace_writer),
           metadata_(metadata),
           symbolizer_(symbolizer),
@@ -123,7 +124,8 @@
           ftrace_clock_snapshot_(ftrace_clock_snapshot),
           ftrace_clock_(ftrace_clock),
           compact_sched_enabled_(compact_sched_enabled),
-          compact_sched_buf_(compact_sched_buf) {
+          compact_sched_buf_(compact_sched_buf),
+          initial_last_read_event_ts_(last_read_event_ts) {
       if (compact_sched_enabled_)
         compact_sched_buf_->Reset();
     }
@@ -132,13 +134,13 @@
 
     protos::pbzero::FtraceEventBundle* GetOrCreateBundle() {
       if (!bundle_) {
-        StartNewPacket(false);
+        StartNewPacket(false, initial_last_read_event_ts_);
       }
       return bundle_;
     }
 
     // Forces the creation of a new TracePacket.
-    void StartNewPacket(bool lost_events);
+    void StartNewPacket(bool lost_events, uint64_t last_read_event_timestamp);
 
     // This function is called after the contents of a FtraceBundle are written.
     void FinalizeAndRunSymbolizer();
@@ -158,10 +160,11 @@
     const FtraceClockSnapshot* const ftrace_clock_snapshot_;
     protos::pbzero::FtraceClock const ftrace_clock_;
     const bool compact_sched_enabled_;
+    CompactSchedBuffer* const compact_sched_buf_;
+    uint64_t initial_last_read_event_ts_;
 
     TraceWriter::TracePacketHandle packet_;
     protos::pbzero::FtraceEventBundle* bundle_ = nullptr;
-    CompactSchedBuffer* const compact_sched_buf_;
   };
 
   struct PageHeader {
@@ -302,7 +305,8 @@
       const ProtoTranslationTable* table,
       const FtraceDataSourceConfig* ds_config,
       Bundler* bundler,
-      FtraceMetadata* metadata);
+      FtraceMetadata* metadata,
+      uint64_t* last_read_event_ts);
 
   // Parse a single raw ftrace event beginning at |start| and ending at |end|
   // and write it into the provided bundle as a proto.
@@ -370,8 +374,9 @@
       size_t cpu,
       const FtraceDataSourceConfig* ds_config,
       base::FlatSet<protos::pbzero::FtraceParseStatus>* parse_errors,
+      uint64_t* last_read_event_ts,
       const uint8_t* parsing_buf,
-      const size_t pages_read,
+      size_t pages_read,
       CompactSchedBuffer* compact_sched_buf,
       const ProtoTranslationTable* table,
       LazyKernelSymbolizer* symbolizer,
@@ -397,6 +402,7 @@
   const ProtoTranslationTable* table_;
   LazyKernelSymbolizer* symbolizer_;
   base::ScopedFile trace_fd_;
+  uint64_t last_read_event_ts_ = 0;
   protos::pbzero::FtraceClock ftrace_clock_{};
   const FtraceClockSnapshot* ftrace_clock_snapshot_;
 };
diff --git a/src/traced/probes/ftrace/cpu_reader_benchmark.cc b/src/traced/probes/ftrace/cpu_reader_benchmark.cc
index b9a08db..ae5de7d 100644
--- a/src/traced/probes/ftrace/cpu_reader_benchmark.cc
+++ b/src/traced/probes/ftrace/cpu_reader_benchmark.cc
@@ -840,7 +840,7 @@
       &writer, &metadata, /*symbolizer=*/nullptr, /*cpu=*/0,
       /*ftrace_clock_snapshot=*/nullptr,
       protos::pbzero::FTRACE_CLOCK_UNSPECIFIED, compact_sched_buf.get(),
-      /*compact_sched_enabled=*/false);
+      /*compact_sched_enabled=*/false, /*last_read_event_ts=*/0);
 
   ProtoTranslationTable* table = GetTable(test_case.name);
   auto page = PageFromXxd(test_case.data);
@@ -874,8 +874,10 @@
     if (!page_header.has_value())
       return;
 
+    uint64_t last_read_event_ts = 0;
     CpuReader::ParsePagePayload(parse_pos, &page_header.value(), table,
-                                &ds_config, &bundler, &metadata);
+                                &ds_config, &bundler, &metadata,
+                                &last_read_event_ts);
 
     metadata.Clear();
     bundler.FinalizeAndRunSymbolizer();
@@ -967,13 +969,15 @@
 
   FtraceMetadata metadata{};
   auto compact_sched_buf = std::make_unique<CompactSchedBuffer>();
+  uint64_t last_read_event_ts = 0;
   base::FlatSet<protos::pbzero::FtraceParseStatus> parse_errors;
   while (state.KeepRunning()) {
     CpuReader::ProcessPagesForDataSource(
         &writer, &metadata, /*cpu=*/0, &ds_config, &parse_errors,
-        repeated_pages.get(), page_repetition, compact_sched_buf.get(), table,
+        &last_read_event_ts, repeated_pages.get(), page_repetition,
+        compact_sched_buf.get(), table,
         /*symbolizer=*/nullptr, /*ftrace_clock_snapshot=*/nullptr,
-        /*ftrace_clock=*/protos::pbzero::FTRACE_CLOCK_UNSPECIFIED);
+        protos::pbzero::FTRACE_CLOCK_UNSPECIFIED);
 
     metadata.Clear();
   }
diff --git a/src/traced/probes/ftrace/cpu_reader_fuzzer.cc b/src/traced/probes/ftrace/cpu_reader_fuzzer.cc
index afd5bc4..f92aab9 100644
--- a/src/traced/probes/ftrace/cpu_reader_fuzzer.cc
+++ b/src/traced/probes/ftrace/cpu_reader_fuzzer.cc
@@ -15,15 +15,13 @@
  */
 
 #include <stddef.h>
-#include <stdint.h>
+#include <cstdint>
 
 #include <algorithm>
 
 #include "perfetto/base/flat_set.h"
 #include "perfetto/base/logging.h"
 #include "perfetto/ext/base/utils.h"
-#include "perfetto/protozero/scattered_stream_null_delegate.h"
-#include "perfetto/protozero/scattered_stream_writer.h"
 #include "src/traced/probes/ftrace/cpu_reader.h"
 #include "src/traced/probes/ftrace/ftrace_config_muxer.h"
 #include "src/traced/probes/ftrace/test/cpu_reader_support.h"
@@ -69,8 +67,10 @@
   NullTraceWriter null_writer;
   auto compact_sched_buf = std::make_unique<CompactSchedBuffer>();
   base::FlatSet<protos::pbzero::FtraceParseStatus> parse_errors;
+  uint64_t last_read_event_ts = 0;
   CpuReader::ProcessPagesForDataSource(
-      &null_writer, &metadata, /*cpu=*/0, &ds_config, &parse_errors, g_page,
+      &null_writer, &metadata, /*cpu=*/0, &ds_config, &parse_errors,
+      &last_read_event_ts, g_page,
       /*pages_read=*/1, compact_sched_buf.get(), table, /*symbolizer*/ nullptr,
       /*ftrace_clock_snapshot=*/nullptr,
       protos::pbzero::FTRACE_CLOCK_UNSPECIFIED);
diff --git a/src/traced/probes/ftrace/cpu_reader_unittest.cc b/src/traced/probes/ftrace/cpu_reader_unittest.cc
index 2d4671c..33a8ca3 100644
--- a/src/traced/probes/ftrace/cpu_reader_unittest.cc
+++ b/src/traced/probes/ftrace/cpu_reader_unittest.cc
@@ -405,7 +405,8 @@
                      /*cpu=*/0,
                      /*ftrace_clock_snapshot=*/nullptr,
                      protos::pbzero::FTRACE_CLOCK_UNSPECIFIED,
-                     compact_sched_buf_.get(), ds_config.compact_sched.enabled);
+                     compact_sched_buf_.get(), ds_config.compact_sched.enabled,
+                     /*last_read_event_ts=*/0);
     return &bundler_.value();
   }
 
@@ -433,6 +434,7 @@
   std::optional<TraceWriterForTesting> writer_;
   std::unique_ptr<CompactSchedBuffer> compact_sched_buf_;
   std::optional<CpuReader::Bundler> bundler_;
+  uint64_t last_read_event_ts_ = 0;
 };
 
 TEST_F(CpuReaderParsePagePayloadTest, ParseSinglePrint) {
@@ -457,7 +459,7 @@
 
   FtraceParseStatus status = CpuReader::ParsePagePayload(
       parse_pos, &page_header.value(), table, &ds_config,
-      CreateBundler(ds_config), &metadata_);
+      CreateBundler(ds_config), &metadata_, &last_read_event_ts_);
 
   EXPECT_EQ(status, FtraceParseStatus::FTRACE_STATUS_OK);
 
@@ -578,7 +580,8 @@
   EXPECT_LE(parse_pos + page_header->size, page_end);
 
   CpuReader::ParsePagePayload(parse_pos, &page_header.value(), table,
-                              &ds_config, CreateBundler(ds_config), &metadata_);
+                              &ds_config, CreateBundler(ds_config), &metadata_,
+                              &last_read_event_ts_);
 
   auto bundle = GetBundle();
   const protos::gen::FtraceEvent& long_print = bundle.event()[0];
@@ -622,7 +625,7 @@
 
   FtraceParseStatus status = CpuReader::ParsePagePayload(
       parse_pos, &page_header.value(), table, &ds_config,
-      CreateBundler(ds_config), &metadata_);
+      CreateBundler(ds_config), &metadata_, &last_read_event_ts_);
 
   EXPECT_EQ(status, FtraceParseStatus::FTRACE_STATUS_OK);
 
@@ -666,7 +669,7 @@
 
   FtraceParseStatus status = CpuReader::ParsePagePayload(
       parse_pos, &page_header.value(), table, &ds_config,
-      CreateBundler(ds_config), &metadata_);
+      CreateBundler(ds_config), &metadata_, &last_read_event_ts_);
 
   EXPECT_EQ(status, FtraceParseStatus::FTRACE_STATUS_OK);
 
@@ -696,7 +699,7 @@
 
   FtraceParseStatus status = CpuReader::ParsePagePayload(
       parse_pos, &page_header.value(), table, &ds_config,
-      CreateBundler(ds_config), &metadata_);
+      CreateBundler(ds_config), &metadata_, &last_read_event_ts_);
 
   EXPECT_EQ(status, FtraceParseStatus::FTRACE_STATUS_OK);
 
@@ -758,7 +761,7 @@
 
   FtraceParseStatus status = CpuReader::ParsePagePayload(
       parse_pos, &page_header.value(), table, &ds_config,
-      CreateBundler(ds_config), &metadata_);
+      CreateBundler(ds_config), &metadata_, &last_read_event_ts_);
 
   EXPECT_EQ(status, FtraceParseStatus::FTRACE_STATUS_OK);
 
@@ -812,7 +815,7 @@
 
     FtraceParseStatus status = CpuReader::ParsePagePayload(
         parse_pos, &page_header.value(), table, &ds_config_no_filter,
-        CreateBundler(ds_config_no_filter), &metadata_);
+        CreateBundler(ds_config_no_filter), &metadata_, &last_read_event_ts_);
     EXPECT_EQ(status, FtraceParseStatus::FTRACE_STATUS_OK);
 
     auto bundle = GetBundle();
@@ -845,7 +848,7 @@
 
     FtraceParseStatus status = CpuReader::ParsePagePayload(
         parse_pos, &page_header.value(), table, &ds_config_with_filter,
-        CreateBundler(ds_config_with_filter), &metadata_);
+        CreateBundler(ds_config_with_filter), &metadata_, &last_read_event_ts_);
     EXPECT_EQ(status, FtraceParseStatus::FTRACE_STATUS_OK);
 
     auto bundle = GetBundle();
@@ -898,9 +901,11 @@
 
     TraceWriterForTesting trace_writer;
     base::FlatSet<protos::pbzero::FtraceParseStatus> parse_errors;
+    uint64_t last_read_event_ts = 0;
     bool success = CpuReader::ProcessPagesForDataSource(
         &trace_writer, &metadata, /*cpu=*/1, &with_filter, &parse_errors,
-        buf.get(), kTestPages, compact_sched_buf.get(), table,
+        &last_read_event_ts, buf.get(), kTestPages, compact_sched_buf.get(),
+        table,
         /*symbolizer=*/nullptr,
         /*ftrace_clock_snapshot=*/nullptr,
         protos::pbzero::FTRACE_CLOCK_UNSPECIFIED);
@@ -920,9 +925,11 @@
 
     TraceWriterForTesting trace_writer;
     base::FlatSet<protos::pbzero::FtraceParseStatus> parse_errors;
+    uint64_t last_read_event_ts = 0;
     bool success = CpuReader::ProcessPagesForDataSource(
         &trace_writer, &metadata, /*cpu=*/1, &without_filter, &parse_errors,
-        buf.get(), kTestPages, compact_sched_buf.get(), table,
+        &last_read_event_ts, buf.get(), kTestPages, compact_sched_buf.get(),
+        table,
         /*symbolizer=*/nullptr,
         /*ftrace_clock_snapshot=*/nullptr,
         protos::pbzero::FTRACE_CLOCK_UNSPECIFIED);
@@ -1008,13 +1015,13 @@
 
   FtraceParseStatus status = CpuReader::ParsePagePayload(
       parse_pos, &page_header.value(), table, &ds_config,
-      CreateBundler(ds_config), &metadata_);
+      CreateBundler(ds_config), &metadata_, &last_read_event_ts_);
 
   EXPECT_EQ(status, FtraceParseStatus::FTRACE_STATUS_OK);
+  EXPECT_EQ(last_read_event_ts_, 1'045'157'726'697'236ULL);
 
   auto bundle = GetBundle();
   ASSERT_EQ(bundle.event().size(), 6u);
-
   {
     const protos::gen::FtraceEvent& event = bundle.event()[1];
     EXPECT_EQ(event.pid(), 3733ul);
@@ -1057,9 +1064,10 @@
 
   FtraceParseStatus status = CpuReader::ParsePagePayload(
       parse_pos, &page_header.value(), table, &ds_config,
-      CreateBundler(ds_config), &metadata_);
+      CreateBundler(ds_config), &metadata_, &last_read_event_ts_);
 
   EXPECT_EQ(status, FtraceParseStatus::FTRACE_STATUS_OK);
+  EXPECT_EQ(last_read_event_ts_, 1'045'157'726'697'236ULL);
 
   // sched switch fields were buffered:
   EXPECT_LT(0u, bundler_->compact_sched_buf()->sched_switch().size());
@@ -1172,7 +1180,7 @@
 
   FtraceParseStatus status = CpuReader::ParsePagePayload(
       parse_pos, &page_header.value(), table, &ds_config,
-      CreateBundler(ds_config), &metadata_);
+      CreateBundler(ds_config), &metadata_, &last_read_event_ts_);
 
   EXPECT_EQ(status, FtraceParseStatus::FTRACE_STATUS_OK);
 
@@ -1642,9 +1650,11 @@
   TraceWriterForTesting trace_writer;
   auto compact_sched_buf = std::make_unique<CompactSchedBuffer>();
   base::FlatSet<protos::pbzero::FtraceParseStatus> parse_errors;
+  uint64_t last_read_event_ts = 0;
   bool success = CpuReader::ProcessPagesForDataSource(
-      &trace_writer, &metadata, /*cpu=*/1, &ds_config, &parse_errors, buf.get(),
-      kTestPages, compact_sched_buf.get(), table, /*symbolizer=*/nullptr,
+      &trace_writer, &metadata, /*cpu=*/1, &ds_config, &parse_errors,
+      &last_read_event_ts, buf.get(), kTestPages, compact_sched_buf.get(),
+      table, /*symbolizer=*/nullptr,
       /*ftrace_clock_snapshot=*/nullptr,
       protos::pbzero::FTRACE_CLOCK_UNSPECIFIED);
 
@@ -1695,9 +1705,11 @@
   TraceWriterForTesting trace_writer;
   auto compact_sched_buf = std::make_unique<CompactSchedBuffer>();
   base::FlatSet<protos::pbzero::FtraceParseStatus> parse_errors;
+  uint64_t last_read_event_ts = 0;
   bool success = CpuReader::ProcessPagesForDataSource(
-      &trace_writer, &metadata, /*cpu=*/1, &ds_config, &parse_errors, buf.get(),
-      kTestPages, compact_sched_buf.get(), table, /*symbolizer=*/nullptr,
+      &trace_writer, &metadata, /*cpu=*/1, &ds_config, &parse_errors,
+      &last_read_event_ts, buf.get(), kTestPages, compact_sched_buf.get(),
+      table, /*symbolizer=*/nullptr,
       /*ftrace_clock_snapshot=*/nullptr,
       protos::pbzero::FTRACE_CLOCK_UNSPECIFIED);
 
@@ -1926,7 +1938,7 @@
 
   FtraceParseStatus status = CpuReader::ParsePagePayload(
       parse_pos, &page_header.value(), table, &ds_config,
-      CreateBundler(ds_config), &metadata_);
+      CreateBundler(ds_config), &metadata_, &last_read_event_ts_);
 
   EXPECT_EQ(status, FtraceParseStatus::FTRACE_STATUS_OK);
 
@@ -2410,7 +2422,7 @@
 
   FtraceParseStatus status = CpuReader::ParsePagePayload(
       parse_pos, &page_header.value(), table, &ds_config,
-      CreateBundler(ds_config), &metadata_);
+      CreateBundler(ds_config), &metadata_, &last_read_event_ts_);
 
   EXPECT_EQ(status, FtraceParseStatus::FTRACE_STATUS_OK);
 
@@ -2443,6 +2455,8 @@
 //            <...>-9290  [000] ....  1352.724567: suspend_resume: resume_console[1] begin
 //            <...>-9290  [000] ....  1352.724570: suspend_resume: resume_console[1] end
 //            <...>-9290  [000] ....  1352.724574: suspend_resume: thaw_processes[0] begin
+// clang-format on
+
 static ExamplePage g_suspend_resume {
     "synthetic",
     R"(00000000: edba 155a 3201 0000 7401 0000 0000 0000  ...Z2...t.......
@@ -2488,8 +2502,9 @@
       CpuReader::ParsePageHeader(&parse_pos, table->page_header_size_len());
   ASSERT_TRUE(page_header.has_value());
 
-  CpuReader::ParsePagePayload(
-      parse_pos, &page_header.value(), table, &ds_config, CreateBundler(ds_config), &metadata_);
+  CpuReader::ParsePagePayload(parse_pos, &page_header.value(), table,
+                              &ds_config, CreateBundler(ds_config), &metadata_,
+                              &last_read_event_ts_);
   auto bundle = GetBundle();
   ASSERT_EQ(bundle.event().size(), 13u);
   EXPECT_EQ(bundle.event()[0].suspend_resume().action(), "sync_filesystems");
@@ -2933,7 +2948,7 @@
 
   FtraceParseStatus status = CpuReader::ParsePagePayload(
       parse_pos, &page_header.value(), table, &ds_config,
-      CreateBundler(ds_config), &metadata_);
+      CreateBundler(ds_config), &metadata_, &last_read_event_ts_);
 
   EXPECT_EQ(status, FtraceParseStatus::FTRACE_STATUS_OK);
 
@@ -3039,7 +3054,7 @@
 
   FtraceParseStatus status = CpuReader::ParsePagePayload(
       parse_pos, &page_header.value(), table, &ds_config,
-      CreateBundler(ds_config), &metadata_);
+      CreateBundler(ds_config), &metadata_, &last_read_event_ts_);
 
   // successfully parsed the whole 32 byte event
   ASSERT_EQ(32u, page_header->size);
@@ -3321,7 +3336,7 @@
 // the header says there's valid data, but the contents are a run of zeros
 // (which doesn't decode to valid events per the ring buffer ABI). Confirm that
 // the error is reported in the ftrace event bundle.
-TEST_F(CpuReaderParsePagePayloadTest, ZeroPaddedPageWorkaround) {
+TEST_F(CpuReaderParsePagePayloadTest, InvalidZeroPaddedPage) {
   const ExamplePage* test_case = &g_zero_padded;
   ProtoTranslationTable* table = GetTable(test_case->name);
   auto page = PageFromXxd(test_case->data);
@@ -3341,7 +3356,7 @@
 
   FtraceParseStatus status = CpuReader::ParsePagePayload(
       parse_pos, &page_header.value(), table, &ds_config,
-      CreateBundler(ds_config), &metadata_);
+      CreateBundler(ds_config), &metadata_, &last_read_event_ts_);
 
   EXPECT_EQ(0xff0u, page_header->size);
   EXPECT_EQ(status, FtraceParseStatus::FTRACE_STATUS_ABI_ZERO_DATA_LENGTH);
@@ -3375,7 +3390,7 @@
 
   FtraceParseStatus status = CpuReader::ParsePagePayload(
       parse_pos, &page_header.value(), table, &ds_config,
-      CreateBundler(ds_config), &metadata_);
+      CreateBundler(ds_config), &metadata_, &last_read_event_ts_);
 
   EXPECT_EQ(4u, page_header->size);
   EXPECT_EQ(status, FtraceParseStatus::FTRACE_STATUS_ABI_SHORT_DATA_LENGTH);
@@ -3420,7 +3435,7 @@
 
   FtraceParseStatus status = CpuReader::ParsePagePayload(
       parse_pos, &page_header.value(), table, &ds_config,
-      CreateBundler(ds_config), &metadata_);
+      CreateBundler(ds_config), &metadata_, &last_read_event_ts_);
 
   EXPECT_EQ(status, FtraceParseStatus::FTRACE_STATUS_OK);
 
@@ -3474,7 +3489,7 @@
 
   FtraceParseStatus status = CpuReader::ParsePagePayload(
       parse_pos, &page_header.value(), table, &ds_config,
-      CreateBundler(ds_config), &metadata_);
+      CreateBundler(ds_config), &metadata_, &last_read_event_ts_);
 
   EXPECT_EQ(status, FtraceParseStatus::FTRACE_STATUS_OK);
 
@@ -3491,5 +3506,132 @@
   EXPECT_EQ(event.f2fs_truncate_partial_nodes().err(), 3);
 }
 
+// one print
+char g_last_ts_test_page_0[] = R"(
+    00000000: cd79 fb3a 2fa4 0400 2c00 0000 0000 0000  .y.:/...,.......
+    00000010: 7eb6 e5eb 8f11 0000 0800 0000 0500 0000  ~...............
+    00000020: 1e83 1400 42ab e0af ffff ffff 6669 7273  ....B.......firs
+    00000030: 745f 7072 696e 740a 0000 0000 0000 0000  t_print.........
+  )";
+
+// one print
+char g_last_ts_test_page_1[] = R"(
+    00000000: 3c11 d579 99a5 0400 2c00 0000 0000 0000  <..y....,.......
+    00000010: 3ed1 6315 3701 0000 0800 0000 0500 0000  >.c.7...........
+    00000020: 9e8c 1400 42ab e0af ffff ffff 7365 636f  ....B.......seco
+    00000030: 6e64 5f70 7269 6e74 0a00 0000 0000 0000  nd_print........
+  )";
+
+// data loss marker ("since last read") + multiple sched_switch + one print
+char g_last_ts_test_page_2[] = R"(
+    00000000: 8ac6 cb70 a8a5 0400 4c02 0080 ffff ffff  ...p....L.......
+    00000010: 1000 0000 4701 0102 01b1 0f00 636f 6465  ....G.......code
+    00000020: 0000 0000 0000 0000 0000 0000 01b1 0f00  ................
+    00000030: 7800 0000 0100 0000 0000 0000 7377 6170  x...........swap
+    00000040: 7065 722f 3000 0000 0000 0000 0000 0000  per/0...........
+    00000050: 7800 0000 b0e3 f602 4701 0102 0000 0000  x.......G.......
+    00000060: 7377 6170 7065 722f 3000 0000 0000 0000  swapper/0.......
+    00000070: 0000 0000 7800 0000 0000 0000 0000 0000  ....x...........
+    00000080: 6b77 6f72 6b65 722f 303a 3500 0000 0000  kworker/0:5.....
+    00000090: ac85 1400 7800 0000 1002 0300 4701 0102  ....x.......G...
+    000000a0: ac85 1400 6b77 6f72 6b65 722f 303a 3500  ....kworker/0:5.
+    000000b0: 0000 0000 ac85 1400 7800 0000 8000 0000  ........x.......
+    000000c0: 0000 0000 7377 6170 7065 722f 3000 0000  ....swapper/0...
+    000000d0: 0000 0000 0000 0000 7800 0000 f086 7106  ........x.....q.
+    000000e0: 4701 0102 0000 0000 7377 6170 7065 722f  G.......swapper/
+    000000f0: 3000 0000 0000 0000 0000 0000 7800 0000  0...........x...
+    00000100: 0000 0000 0000 0000 6f62 6e6f 2d64 6573  ........obno-des
+    00000110: 6b74 6f70 2d6e 6f00 d513 0000 7800 0000  ktop-no.....x...
+    00000120: 3013 1000 4701 0102 d513 0000 6f62 6e6f  0...G.......obno
+    00000130: 2d64 6573 6b74 6f70 2d6e 6f00 d513 0000  -desktop-no.....
+    00000140: 7800 0000 0100 0000 0000 0000 7377 6170  x...........swap
+    00000150: 7065 722f 3000 0000 0000 0000 0000 0000  per/0...........
+    00000160: 7800 0000 10b0 2703 4701 0102 0000 0000  x.....'.G.......
+    00000170: 7377 6170 7065 722f 3000 0000 0000 0000  swapper/0.......
+    00000180: 0000 0000 7800 0000 0000 0000 0000 0000  ....x...........
+    00000190: 6b77 6f72 6b65 722f 303a 3500 0000 0000  kworker/0:5.....
+    000001a0: ac85 1400 7800 0000 70e7 0200 4701 0102  ....x...p...G...
+    000001b0: ac85 1400 6b77 6f72 6b65 722f 303a 3500  ....kworker/0:5.
+    000001c0: 0000 0000 ac85 1400 7800 0000 8000 0000  ........x.......
+    000001d0: 0000 0000 6b73 6f66 7469 7271 642f 3000  ....ksoftirqd/0.
+    000001e0: 0000 0000 0f00 0000 7800 0000 10a4 0200  ........x.......
+    000001f0: 4701 0102 0f00 0000 6b73 6f66 7469 7271  G.......ksoftirq
+    00000200: 642f 3000 0000 0000 0f00 0000 7800 0000  d/0.........x...
+    00000210: 0100 0000 0000 0000 7377 6170 7065 722f  ........swapper/
+    00000220: 3000 0000 0000 0000 0000 0000 7800 0000  0...........x...
+    00000230: fef2 0a4d 7500 0000 0800 0000 0500 0000  ...Mu...........
+    00000240: 1a8d 1400 42ab e0af ffff ffff 7468 6972  ....B.......thir
+    00000250: 645f 7072 696e 740a 0000 0000 0000 0000  d_print.........
+  )";
+
+// Tests that |last_read_event_timestamp| is correctly updated in cases where a
+// single ProcessPagesForDataSource call produces multiple ftrace bundle packets
+// (due to splitting on data loss markers).
+TEST(CpuReaderTest, LastReadEventTimestampWithSplitBundles) {
+  // build test buffer with 3 pages
+  ProtoTranslationTable* table = GetTable("synthetic");
+  std::vector<std::unique_ptr<uint8_t[]>> test_pages;
+  test_pages.emplace_back(PageFromXxd(g_last_ts_test_page_0));
+  test_pages.emplace_back(PageFromXxd(g_last_ts_test_page_1));
+  test_pages.emplace_back(PageFromXxd(g_last_ts_test_page_2));
+  size_t num_pages = test_pages.size();
+  size_t page_sz = base::GetSysPageSize();
+  auto buf = std::make_unique<uint8_t[]>(page_sz * num_pages);
+  for (size_t i = 0; i < num_pages; i++) {
+    void* dest = buf.get() + (i * page_sz);
+    memcpy(dest, static_cast<const void*>(test_pages[i].get()), page_sz);
+  }
+
+  // build cfg requesting ftrace/print
+  auto compact_sched_buf = std::make_unique<CompactSchedBuffer>();
+  FtraceMetadata metadata{};
+  FtraceDataSourceConfig ftrace_cfg = EmptyConfig();
+  ftrace_cfg.event_filter.AddEnabledEvent(
+      table->EventToFtraceId(GroupAndName("ftrace", "print")));
+
+  // invoke ProcessPagesForDataSource
+  TraceWriterForTesting trace_writer;
+  base::FlatSet<protos::pbzero::FtraceParseStatus> parse_errors;
+  uint64_t last_read_event_ts = 0;
+  bool success = CpuReader::ProcessPagesForDataSource(
+      &trace_writer, &metadata, /*cpu=*/0, &ftrace_cfg, &parse_errors,
+      &last_read_event_ts, buf.get(), num_pages, compact_sched_buf.get(), table,
+      /*symbolizer=*/nullptr,
+      /*ftrace_clock_snapshot=*/nullptr,
+      protos::pbzero::FTRACE_CLOCK_UNSPECIFIED);
+
+  EXPECT_TRUE(success);
+
+  // We've read three pages, one print event on each. There is a data loss
+  // marker on the third page, indicating that the kernel overwrote events
+  // between 2nd and 3rd page (imagine our daemon getting cpu starved between
+  // those reads).
+  //
+  // Therefore we expect two bundles, as we start a new one whenever we
+  // encounter data loss (to set the |lost_events| field in the bundle proto).
+  //
+  // In terms of |last_read_event_timestamp|, the first bundle will emit zero
+  // since that's our initial input. The second bundle needs to emit the
+  // timestamp of the last event in the first bundle.
+  auto packets = trace_writer.GetAllTracePackets();
+  ASSERT_EQ(2u, packets.size());
+
+  // 2 prints
+  auto const& first_bundle = packets[0].ftrace_events();
+  EXPECT_FALSE(first_bundle.lost_events());
+  ASSERT_EQ(2u, first_bundle.event().size());
+  EXPECT_TRUE(first_bundle.has_last_read_event_timestamp());
+  EXPECT_EQ(0u, first_bundle.last_read_event_timestamp());
+
+  const uint64_t kSecondPrintTs = 1308020252356549ULL;
+  EXPECT_EQ(kSecondPrintTs, first_bundle.event()[1].timestamp());
+
+  // 1 print + lost_events + updated last_read_event_timestamp
+  auto const& second_bundle = packets[1].ftrace_events();
+  EXPECT_TRUE(second_bundle.lost_events());
+  EXPECT_EQ(1u, second_bundle.event().size());
+  EXPECT_EQ(kSecondPrintTs, second_bundle.last_read_event_timestamp());
+}
+
 }  // namespace
 }  // namespace perfetto
diff --git a/src/traced/probes/ftrace/cpu_stats_parser.cc b/src/traced/probes/ftrace/cpu_stats_parser.cc
index 1d1cf5a..b3aaf49 100644
--- a/src/traced/probes/ftrace/cpu_stats_parser.cc
+++ b/src/traced/probes/ftrace/cpu_stats_parser.cc
@@ -74,11 +74,13 @@
 }
 
 bool DumpAllCpuStats(FtraceProcfs* ftrace, FtraceStats* stats) {
-  stats->cpu_stats.resize(ftrace->NumberOfCpus(), {});
-  for (size_t cpu = 0; cpu < ftrace->NumberOfCpus(); cpu++) {
+  size_t num_cpus = ftrace->NumberOfCpus();
+  stats->cpu_stats.resize(num_cpus, {});
+  for (size_t cpu = 0; cpu < num_cpus; cpu++) {
     stats->cpu_stats[cpu].cpu = cpu;
-    if (!DumpCpuStats(ftrace->ReadCpuStats(cpu), &stats->cpu_stats[cpu]))
+    if (!DumpCpuStats(ftrace->ReadCpuStats(cpu), &stats->cpu_stats[cpu])) {
       return false;
+    }
   }
   return true;
 }
diff --git a/src/traced/probes/ftrace/ftrace_procfs.cc b/src/traced/probes/ftrace/ftrace_procfs.cc
index 5576627..5346c0d 100644
--- a/src/traced/probes/ftrace/ftrace_procfs.cc
+++ b/src/traced/probes/ftrace/ftrace_procfs.cc
@@ -397,7 +397,7 @@
   // We cannot use PERFETTO_CHECK as we might get a permission denied error
   // on Android. The permissions to these files are configured in
   // platform/framework/native/cmds/atrace/atrace.rc.
-  for (size_t cpu = 0; cpu < NumberOfCpus(); cpu++) {
+  for (size_t cpu = 0, num_cpus = NumberOfCpus(); cpu < num_cpus; cpu++) {
     ClearPerCpuTrace(cpu);
   }
 }
diff --git a/test/trace_processor/diff_tests/include_index.py b/test/trace_processor/diff_tests/include_index.py
index 7da78d7..0d2c515 100644
--- a/test/trace_processor/diff_tests/include_index.py
+++ b/test/trace_processor/diff_tests/include_index.py
@@ -127,6 +127,7 @@
 from diff_tests.tables.tests import Tables
 from diff_tests.tables.tests_counters import TablesCounters
 from diff_tests.tables.tests_sched import TablesSched
+from diff_tests.parser.ftrace.ftrace_crop_tests import FtraceCrop
 
 sys.path.pop()
 
@@ -198,6 +199,7 @@
       *ParsingRssStats(index_path, 'parser/parsing', 'ParsingRssStats').fetch(),
       *ParsingMemoryCounters(index_path, 'parser/parsing',
                              'ParsingMemoryCounters').fetch(),
+      *FtraceCrop(index_path, 'parser/ftrace', 'FtraceCrop').fetch(),
   ]
 
   metrics_tests = [
diff --git a/test/trace_processor/diff_tests/parser/ftrace/ftrace_crop_tests.py b/test/trace_processor/diff_tests/parser/ftrace/ftrace_crop_tests.py
new file mode 100644
index 0000000..d69bce7
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/ftrace/ftrace_crop_tests.py
@@ -0,0 +1,136 @@
+#!/usr/bin/env python3
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License a
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from python.generators.diff_tests.testing import Csv, TextProto
+from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import TestSuite
+
+
+class FtraceCrop(TestSuite):
+
+  # Expect the first begin event on cpu1 gets suppressed as it is below the
+  # maximum of last_read_event_timestamps.
+  def test_crop_atrace_slice(self):
+    return DiffTestBlueprint(
+        trace=TextProto(r"""
+        packet { ftrace_events {
+          cpu: 1
+          last_read_event_timestamp: 1000
+          event {
+            timestamp: 1500
+            pid: 42
+            print { buf: "B|42|FilteredOut\n" }
+          }
+          event {
+            timestamp: 2700
+            pid: 42
+            print { buf: "E|42\n" }
+          }
+        }}
+        packet { ftrace_events {
+          cpu: 0
+          last_read_event_timestamp: 2000
+          event {
+            timestamp: 2200
+            pid: 42
+            print { buf: "B|42|Kept\n" }
+          }
+        }}
+        """),
+        query="""
+        select
+          ts,
+          rtrim(extract_arg(raw.arg_set_id, "buf"), char(0x0a)) as raw_print,
+          slice.dur as slice_dur,
+          slice.name as slice_name
+        from raw left join slice using (ts)
+        """,
+        out=Csv("""
+        "ts","raw_print","slice_dur","slice_name"
+        1500,"B|42|FilteredOut","[NULL]","[NULL]"
+        2200,"B|42|Kept",500,"Kept"
+        2700,"E|42","[NULL]","[NULL]"
+        """))
+
+  # First compact_switch per cpu doesn't generate any events, successive
+  # switches generate a |raw| entry, but no scheduling slices until past all
+  # last_read_event_timestamps.
+  def test_crop_compact_sched_switch(self):
+    return DiffTestBlueprint(
+        trace=TextProto(r"""
+        packet {
+          ftrace_events {
+            last_read_event_timestamp: 1000
+            cpu: 3
+            compact_sched {
+              intern_table: "zero:3"
+              intern_table: "one:3"
+              switch_timestamp: 1100
+              switch_timestamp: 100
+              switch_next_pid: 50
+              switch_next_pid: 51
+              switch_prev_state: 0
+              switch_prev_state: 0
+              switch_next_prio: 120
+              switch_next_prio: 120
+              switch_next_comm_index: 0
+              switch_next_comm_index: 1
+            }
+          }
+        }
+        packet {
+          ftrace_events {
+            last_read_event_timestamp: 0
+            cpu: 6
+            compact_sched {
+              intern_table: "zero:6"
+              intern_table: "one:6"
+              intern_table: "two:6"
+              intern_table: "three:6"
+              switch_timestamp: 500
+              switch_timestamp: 100
+              switch_timestamp: 500
+              switch_timestamp: 100
+              switch_next_pid: 40
+              switch_next_pid: 41
+              switch_next_pid: 42
+              switch_next_pid: 43
+              switch_prev_state: 0
+              switch_prev_state: 0
+              switch_prev_state: 0
+              switch_prev_state: 0
+              switch_next_prio: 120
+              switch_next_prio: 120
+              switch_next_prio: 120
+              switch_next_prio: 120
+              switch_next_comm_index: 0
+              switch_next_comm_index: 1
+              switch_next_comm_index: 2
+              switch_next_comm_index: 3
+            }
+          }
+        }
+        """),
+        query="""
+        select cpu, ts, dur, tid, thread.name
+        from sched join thread using (utid)
+        order by cpu asc, ts asc
+        """,
+        out=Csv("""
+        "cpu","ts","dur","tid","name"
+        3,1200,-1,51,"one:3"
+        6,1100,100,42,"two:6"
+        6,1200,-1,43,"three:6"
+        """))
diff --git a/tools/install-build-deps b/tools/install-build-deps
index e43f4f9..ab15251 100755
--- a/tools/install-build-deps
+++ b/tools/install-build-deps
@@ -165,13 +165,13 @@
     # tools/clang/scripts/update.py.
     Dependency(
         'buildtools/linux64/clang.tgz',
-        'https://commondatastorage.googleapis.com/chromium-browser-clang/Linux_x64/clang-llvmorg-18-init-17730-gf670112a-5.tgz',
-        'ce6201b728ea485d4b116aa3b5e8ae3b34ee2775f25924c5714fdbe42fb2b787',
+        'https://commondatastorage.googleapis.com/chromium-browser-clang/Linux_x64/clang-llvmorg-19-init-2941-ga0b3dbaf-22.tar.xz',
+        '7b33138d8592199f97d132242d7b3e10f460c5c9655d49a3ad3767218fba7a77',
         'linux', 'x64'),
     Dependency(
         'buildtools/win/clang.tgz',
-        'https://commondatastorage.googleapis.com/chromium-browser-clang/Win/clang-llvmorg-18-init-17730-gf670112a-5.tgz',
-        '972011cc8a5bc5d5fbe08773089058a264ee0e19af019df28f0972ee9bd0493f',
+        'https://commondatastorage.googleapis.com/chromium-browser-clang/Win/clang-llvmorg-19-init-2941-ga0b3dbaf-22.tar.xz',
+        'c8e1c41eb36aef6e63d65755d4746f68688c2fcefca44777a205d412c83d25a1',
         'windows', 'x64'),
 ]
 
diff --git a/ui/src/assets/track_panel.scss b/ui/src/assets/track_panel.scss
index 0091fba..5025959 100644
--- a/ui/src/assets/track_panel.scss
+++ b/ui/src/assets/track_panel.scss
@@ -106,11 +106,11 @@
       cursor: pointer;
       width: 22px;
       font-size: 18px;
-      opacity: 0;
+      visibility: hidden;
     }
 
     .track-button.show {
-      opacity: 1;
+      visibility: visible;
     }
     .track-button.full-height {
       display: flex;
@@ -124,7 +124,7 @@
     }
 
     &:hover .track-button {
-      opacity: 1;
+      visibility: visible;
     }
     &.flash {
       background-color: #ffe263;
diff --git a/ui/src/common/array_buffer_builder.ts b/ui/src/base/array_buffer_builder.ts
similarity index 100%
rename from ui/src/common/array_buffer_builder.ts
rename to ui/src/base/array_buffer_builder.ts
diff --git a/ui/src/common/registry.ts b/ui/src/base/registry.ts
similarity index 100%
rename from ui/src/common/registry.ts
rename to ui/src/base/registry.ts
diff --git a/ui/src/common/registry_unittest.ts b/ui/src/base/registry_unittest.ts
similarity index 100%
rename from ui/src/common/registry_unittest.ts
rename to ui/src/base/registry_unittest.ts
diff --git a/ui/src/frontend/store.ts b/ui/src/base/store.ts
similarity index 98%
rename from ui/src/frontend/store.ts
rename to ui/src/base/store.ts
index dfb9f0e..395da5e 100644
--- a/ui/src/frontend/store.ts
+++ b/ui/src/base/store.ts
@@ -14,8 +14,8 @@
 
 import produce, {Draft} from 'immer';
 
-import {Disposable} from '../base/disposable';
-import {getPath, Path, setPath} from '../base/object_utils';
+import {Disposable} from './disposable';
+import {getPath, Path, setPath} from './object_utils';
 
 export type Migrate<T> = (init: unknown) => T;
 export type Edit<T> = (draft: Draft<T>) => void;
diff --git a/ui/src/frontend/store_unittest.ts b/ui/src/base/store_unittest.ts
similarity index 99%
rename from ui/src/frontend/store_unittest.ts
rename to ui/src/base/store_unittest.ts
index 00bd3f2..4d15334 100644
--- a/ui/src/frontend/store_unittest.ts
+++ b/ui/src/base/store_unittest.ts
@@ -14,8 +14,8 @@
 
 import {Draft} from 'immer';
 
+import {using} from './disposable';
 import {createStore} from './store';
-import {using} from '../base/disposable';
 
 interface Bar {
   value: number;
diff --git a/ui/src/common/actions.ts b/ui/src/common/actions.ts
index 530f086..139fc7b 100644
--- a/ui/src/common/actions.ts
+++ b/ui/src/common/actions.ts
@@ -30,7 +30,7 @@
 } from '../frontend/pivot_table_types';
 import {PrimaryTrackSortKey} from '../public/index';
 
-import {randomColor} from './colorizer';
+import {randomColor} from '../core/colorizer';
 import {
   computeIntervals,
   DropDirection,
diff --git a/ui/src/common/commands.ts b/ui/src/common/commands.ts
index 7b44775..8ef8b9e 100644
--- a/ui/src/common/commands.ts
+++ b/ui/src/common/commands.ts
@@ -14,8 +14,8 @@
 
 import {Disposable} from '../base/disposable';
 import {FuzzyFinder, FuzzySegment} from '../base/fuzzy';
+import {Registry} from '../base/registry';
 import {Command} from '../public';
-import {Registry} from './registry';
 
 export interface CommandWithMatchInfo extends Command {
   segments: FuzzySegment[];
diff --git a/ui/src/common/plugins.ts b/ui/src/common/plugins.ts
index 56b88cd..80e9ca7 100644
--- a/ui/src/common/plugins.ts
+++ b/ui/src/common/plugins.ts
@@ -15,6 +15,7 @@
 import {v4 as uuidv4} from 'uuid';
 
 import {Disposable, Trash} from '../base/disposable';
+import {Registry} from '../base/registry';
 import {time} from '../base/time';
 import {globals} from '../frontend/globals';
 import {
@@ -39,7 +40,6 @@
 import {Engine} from '../trace_processor/engine';
 
 import {Actions} from './actions';
-import {Registry} from './registry';
 import {SCROLLING_TRACK_GROUP} from './state';
 import {addQueryResultsTab} from '../frontend/query_result_tab';
 import {Flag, featureFlags} from '../core/feature_flags';
diff --git a/ui/src/common/recordingV2/adb_connection_impl.ts b/ui/src/common/recordingV2/adb_connection_impl.ts
index 071f93f..3378057 100644
--- a/ui/src/common/recordingV2/adb_connection_impl.ts
+++ b/ui/src/common/recordingV2/adb_connection_impl.ts
@@ -15,7 +15,7 @@
 import {_TextDecoder} from 'custom_utils';
 
 import {defer} from '../../base/deferred';
-import {ArrayBufferBuilder} from '../array_buffer_builder';
+import {ArrayBufferBuilder} from '../../base/array_buffer_builder';
 
 import {AdbFileHandler} from './adb_file_handler';
 import {
diff --git a/ui/src/common/recordingV2/adb_file_handler.ts b/ui/src/common/recordingV2/adb_file_handler.ts
index c3a1149..0c538c3 100644
--- a/ui/src/common/recordingV2/adb_file_handler.ts
+++ b/ui/src/common/recordingV2/adb_file_handler.ts
@@ -16,7 +16,7 @@
 
 import {defer, Deferred} from '../../base/deferred';
 import {assertFalse} from '../../base/logging';
-import {ArrayBufferBuilder} from '../array_buffer_builder';
+import {ArrayBufferBuilder} from '../../base/array_buffer_builder';
 
 import {RecordingError} from './recording_error_handling';
 import {ByteStream} from './recording_interfaces_v2';
diff --git a/ui/src/common/recordingV2/target_factory_registry.ts b/ui/src/common/recordingV2/target_factory_registry.ts
index f68ef8e..54cec4a 100644
--- a/ui/src/common/recordingV2/target_factory_registry.ts
+++ b/ui/src/common/recordingV2/target_factory_registry.ts
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {Registry} from '../registry';
+import {Registry} from '../../base/registry';
 
 import {RecordingTargetV2, TargetFactory} from './recording_interfaces_v2';
 
diff --git a/ui/src/common/track_cache.ts b/ui/src/common/track_cache.ts
index 01ff733..3b2eb41 100644
--- a/ui/src/common/track_cache.ts
+++ b/ui/src/common/track_cache.ts
@@ -13,9 +13,10 @@
 // limitations under the License.
 
 import {Disposable, DisposableCallback} from '../base/disposable';
-import {PanelSize} from '../frontend/panel';
 import {exists} from '../base/utils';
-import {Store} from '../frontend/store';
+import {Registry} from '../base/registry';
+import {Store} from '../base/store';
+import {PanelSize} from '../frontend/panel';
 import {
   Migrate,
   Track,
@@ -23,7 +24,6 @@
   TrackDescriptor,
   TrackRef,
 } from '../public';
-import {Registry} from './registry';
 
 import {ObjectByKey, State, TrackState} from './state';
 
diff --git a/ui/src/common/track_helper.ts b/ui/src/common/track_helper.ts
index 7ba951c..4adeee6 100644
--- a/ui/src/common/track_helper.ts
+++ b/ui/src/common/track_helper.ts
@@ -14,10 +14,10 @@
 
 import {Disposable} from '../base/disposable';
 import {duration, Time, time, TimeSpan} from '../base/time';
+export {Store} from '../base/store';
 import {raf} from '../core/raf_scheduler';
 import {globals} from '../frontend/globals';
 
-export {Store} from '../frontend/store';
 export {EngineProxy} from '../trace_processor/engine';
 export {
   LONG,
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index 136607f..56507d9 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -106,7 +106,7 @@
   TraceFileStream,
   TraceHttpStream,
   TraceStream,
-} from './trace_stream';
+} from '../core/trace_stream';
 import {decideTracks} from './track_decider';
 
 type States = 'init' | 'loading_trace' | 'ready';
@@ -169,9 +169,17 @@
 const ANALYZE_TRACE_PROTO_CONTENT_FLAG = featureFlags.register({
   id: 'analyzeTraceProtoContent',
   name: 'Analyze trace proto content',
-  description: 'Enables trace proto content analysis',
+  description:
+    'Enables trace proto content analysis (experimental_proto_content table)',
   defaultValue: false,
 });
+const FTRACE_DROP_UNTIL_FLAG = featureFlags.register({
+  id: 'ftraceDropUntilAllCpusValid',
+  name: 'Crop ftrace events',
+  description:
+    'Drop ftrace events until all per-cpu data streams are known to be valid',
+  defaultValue: true,
+});
 
 // A local storage key where the indication that JSON warning has been shown is
 // stored.
@@ -402,6 +410,7 @@
         cropTrackEvents: CROP_TRACK_EVENTS_FLAG.get(),
         ingestFtraceInRawTable: INGEST_FTRACE_IN_RAW_TABLE_FLAG.get(),
         analyzeTraceProtoContent: ANALYZE_TRACE_PROTO_CONTENT_FLAG.get(),
+        ftraceDropUntilAllCpusValid: FTRACE_DROP_UNTIL_FLAG.get(),
       });
     }
     this.engine = engine;
diff --git a/ui/src/common/color.ts b/ui/src/core/color.ts
similarity index 100%
rename from ui/src/common/color.ts
rename to ui/src/core/color.ts
diff --git a/ui/src/common/color_unittest.ts b/ui/src/core/color_unittest.ts
similarity index 100%
rename from ui/src/common/color_unittest.ts
rename to ui/src/core/color_unittest.ts
diff --git a/ui/src/common/colorizer.ts b/ui/src/core/colorizer.ts
similarity index 98%
rename from ui/src/common/colorizer.ts
rename to ui/src/core/colorizer.ts
index 215079a..3850e1e 100644
--- a/ui/src/common/colorizer.ts
+++ b/ui/src/core/colorizer.ts
@@ -14,8 +14,8 @@
 
 import {hsl} from 'color-convert';
 
-import {hash} from '../common/hash';
-import {featureFlags} from '../core/feature_flags';
+import {hash} from './hash';
+import {featureFlags} from './feature_flags';
 
 import {Color, HSLColor, HSLuvColor} from './color';
 
diff --git a/ui/src/common/colorizer_unittest.ts b/ui/src/core/colorizer_unittest.ts
similarity index 100%
rename from ui/src/common/colorizer_unittest.ts
rename to ui/src/core/colorizer_unittest.ts
diff --git a/ui/src/core/default_plugins.ts b/ui/src/core/default_plugins.ts
index ea6f263..8a03d47 100644
--- a/ui/src/core/default_plugins.ts
+++ b/ui/src/core/default_plugins.ts
@@ -55,5 +55,5 @@
   'perfetto.Screenshots',
   'perfetto.ThreadState',
   'perfetto.VisualisedArgs',
-  'linuxDeviceTracks',
+  'org.kernel.LinuxKernelDevices',
 ];
diff --git a/ui/src/common/hash.ts b/ui/src/core/hash.ts
similarity index 100%
rename from ui/src/common/hash.ts
rename to ui/src/core/hash.ts
diff --git a/ui/src/common/timestamp_format.ts b/ui/src/core/timestamp_format.ts
similarity index 100%
rename from ui/src/common/timestamp_format.ts
rename to ui/src/core/timestamp_format.ts
diff --git a/ui/src/controller/trace_stream.ts b/ui/src/core/trace_stream.ts
similarity index 100%
rename from ui/src/controller/trace_stream.ts
rename to ui/src/core/trace_stream.ts
diff --git a/ui/src/frontend/aggregation_panel.ts b/ui/src/frontend/aggregation_panel.ts
index ff8bfa7..cbd97d9 100644
--- a/ui/src/frontend/aggregation_panel.ts
+++ b/ui/src/frontend/aggregation_panel.ts
@@ -21,7 +21,7 @@
   ThreadStateExtra,
   isEmptyData,
 } from '../common/aggregation_data';
-import {colorForState} from '../common/colorizer';
+import {colorForState} from '../core/colorizer';
 import {translateState} from '../common/thread_state';
 
 import {globals} from './globals';
diff --git a/ui/src/frontend/app.ts b/ui/src/frontend/app.ts
index 6b7b896..0c63e9e 100644
--- a/ui/src/frontend/app.ts
+++ b/ui/src/frontend/app.ts
@@ -28,7 +28,7 @@
   setDurationPrecision,
   setTimestampFormat,
   TimestampFormat,
-} from '../common/timestamp_format';
+} from '../core/timestamp_format';
 import {raf} from '../core/raf_scheduler';
 import {Command} from '../public';
 import {EngineProxy} from '../trace_processor/engine';
diff --git a/ui/src/frontend/base_slice_track.ts b/ui/src/frontend/base_slice_track.ts
index afed340..e4203f0 100644
--- a/ui/src/frontend/base_slice_track.ts
+++ b/ui/src/frontend/base_slice_track.ts
@@ -23,8 +23,8 @@
   drawIncompleteSlice,
   drawTrackHoverTooltip,
 } from '../common/canvas_utils';
-import {colorCompare} from '../common/color';
-import {UNEXPECTED_PINK} from '../common/colorizer';
+import {colorCompare} from '../core/color';
+import {UNEXPECTED_PINK} from '../core/colorizer';
 import {Selection, SelectionKind} from '../common/state';
 import {featureFlags} from '../core/feature_flags';
 import {raf} from '../core/raf_scheduler';
diff --git a/ui/src/frontend/base_slice_track_unittest.ts b/ui/src/frontend/base_slice_track_unittest.ts
index afcca1d..196cc2e 100644
--- a/ui/src/frontend/base_slice_track_unittest.ts
+++ b/ui/src/frontend/base_slice_track_unittest.ts
@@ -13,7 +13,7 @@
 // limitations under the License.
 
 import {Time} from '../base/time';
-import {UNEXPECTED_PINK} from '../common/colorizer';
+import {UNEXPECTED_PINK} from '../core/colorizer';
 import {Slice} from '../public';
 
 import {filterVisibleSlicesForTesting as filterVisibleSlices} from './base_slice_track';
diff --git a/ui/src/frontend/bottom_tab.ts b/ui/src/frontend/bottom_tab.ts
index 7acb31b..4b82001 100644
--- a/ui/src/frontend/bottom_tab.ts
+++ b/ui/src/frontend/bottom_tab.ts
@@ -19,7 +19,7 @@
 import {exists} from '../base/utils';
 import {Actions} from '../common/actions';
 import {traceEvent} from '../common/metatracing';
-import {Registry} from '../common/registry';
+import {Registry} from '../base/registry';
 import {raf} from '../core/raf_scheduler';
 import {EngineProxy} from '../trace_processor/engine';
 
diff --git a/ui/src/frontend/ftrace_panel.ts b/ui/src/frontend/ftrace_panel.ts
index 7766267..c110868 100644
--- a/ui/src/frontend/ftrace_panel.ts
+++ b/ui/src/frontend/ftrace_panel.ts
@@ -16,7 +16,7 @@
 
 import {time, Time} from '../base/time';
 import {Actions} from '../common/actions';
-import {colorForFtrace} from '../common/colorizer';
+import {colorForFtrace} from '../core/colorizer';
 import {StringListPatch} from '../common/state';
 import {DetailsShell} from '../widgets/details_shell';
 import {
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index b4876d7..35cd660 100644
--- a/ui/src/frontend/globals.ts
+++ b/ui/src/frontend/globals.ts
@@ -14,6 +14,7 @@
 
 import {BigintMath} from '../base/bigint_math';
 import {assertExists} from '../base/logging';
+import {createStore, Store} from '../base/store';
 import {duration, Span, Time, time, TimeSpan} from '../base/time';
 import {Actions, DeferredAction} from '../common/actions';
 import {AggregateData} from '../common/aggregation_data';
@@ -39,7 +40,7 @@
   State,
 } from '../common/state';
 import {TabManager} from '../common/tab_registry';
-import {TimestampFormat, timestampFormat} from '../common/timestamp_format';
+import {TimestampFormat, timestampFormat} from '../core/timestamp_format';
 import {TrackManager} from '../common/track_cache';
 import {TABS_V2_FLAG} from '../core/feature_flags';
 import {setPerfHooks} from '../core/perf';
@@ -54,7 +55,6 @@
 import {horizontalScrollToTs} from './scroll_helper';
 import {ServiceWorkerController} from './service_worker_controller';
 import {SliceSqlId} from './sql_types';
-import {createStore, Store} from './store';
 import {PxSpan, TimeScale} from './time_scale';
 
 const INSTANT_FOCUS_DURATION = 1n;
diff --git a/ui/src/frontend/index.ts b/ui/src/frontend/index.ts
index 05c7f0a..468d9af 100644
--- a/ui/src/frontend/index.ts
+++ b/ui/src/frontend/index.ts
@@ -21,6 +21,7 @@
 
 import {defer} from '../base/deferred';
 import {addErrorHandler, reportError} from '../base/logging';
+import {Store} from '../base/store';
 import {Actions, DeferredAction, StateActions} from '../common/actions';
 import {flattenArgs, traceEvent} from '../common/metatracing';
 import {pluginManager} from '../common/plugins';
@@ -50,7 +51,6 @@
 import {RecordPageV2} from './record_page_v2';
 import {Route, Router} from './router';
 import {CheckHttpRpcConnection} from './rpc_http_dialog';
-import {Store} from './store';
 import {TraceInfoPage} from './trace_info_page';
 import {maybeOpenTraceFromRoute} from './trace_url_handler';
 import {ViewerPage} from './viewer_page';
diff --git a/ui/src/frontend/named_slice_track.ts b/ui/src/frontend/named_slice_track.ts
index e2a3717..23e0723 100644
--- a/ui/src/frontend/named_slice_track.ts
+++ b/ui/src/frontend/named_slice_track.ts
@@ -13,7 +13,7 @@
 // limitations under the License.
 
 import {Actions} from '../common/actions';
-import {getColorForSlice} from '../common/colorizer';
+import {getColorForSlice} from '../core/colorizer';
 import {STR_NULL} from '../trace_processor/query_result';
 
 import {
diff --git a/ui/src/frontend/notes_panel.ts b/ui/src/frontend/notes_panel.ts
index 29aff3e..dfc7800 100644
--- a/ui/src/frontend/notes_panel.ts
+++ b/ui/src/frontend/notes_panel.ts
@@ -18,7 +18,7 @@
 import {Icons} from '../base/semantic_icons';
 import {Time} from '../base/time';
 import {Actions} from '../common/actions';
-import {randomColor} from '../common/colorizer';
+import {randomColor} from '../core/colorizer';
 import {AreaNote, Note} from '../common/state';
 import {raf} from '../core/raf_scheduler';
 import {Button} from '../widgets/button';
diff --git a/ui/src/frontend/overview_timeline_panel.ts b/ui/src/frontend/overview_timeline_panel.ts
index d98c249..a47434a 100644
--- a/ui/src/frontend/overview_timeline_panel.ts
+++ b/ui/src/frontend/overview_timeline_panel.ts
@@ -15,8 +15,8 @@
 import m from 'mithril';
 
 import {duration, Span, Time, time} from '../base/time';
-import {colorForCpu} from '../common/colorizer';
-import {timestampFormat, TimestampFormat} from '../common/timestamp_format';
+import {colorForCpu} from '../core/colorizer';
+import {timestampFormat, TimestampFormat} from '../core/timestamp_format';
 
 import {
   OVERVIEW_TIMELINE_NON_VISIBLE_COLOR,
diff --git a/ui/src/frontend/slice_track.ts b/ui/src/frontend/slice_track.ts
index 87c300a..2f9eb40 100644
--- a/ui/src/frontend/slice_track.ts
+++ b/ui/src/frontend/slice_track.ts
@@ -15,7 +15,7 @@
 import {duration, Time, time} from '../base/time';
 import {Actions} from '../common/actions';
 import {cropText, drawIncompleteSlice} from '../common/canvas_utils';
-import {getColorForSlice} from '../common/colorizer';
+import {getColorForSlice} from '../core/colorizer';
 import {HighPrecisionTime} from '../common/high_precision_time';
 import {TrackData} from '../common/track_data';
 import {TimelineFetcher} from '../common/track_helper';
diff --git a/ui/src/frontend/time_axis_panel.ts b/ui/src/frontend/time_axis_panel.ts
index 01f8cc2..d20341f 100644
--- a/ui/src/frontend/time_axis_panel.ts
+++ b/ui/src/frontend/time_axis_panel.ts
@@ -15,7 +15,7 @@
 import m from 'mithril';
 
 import {Time, time, toISODateOnly} from '../base/time';
-import {TimestampFormat, timestampFormat} from '../common/timestamp_format';
+import {TimestampFormat, timestampFormat} from '../core/timestamp_format';
 
 import {TRACK_SHELL_WIDTH} from './css_constants';
 import {globals} from './globals';
diff --git a/ui/src/frontend/time_selection_panel.ts b/ui/src/frontend/time_selection_panel.ts
index c0034b0..0da9a23 100644
--- a/ui/src/frontend/time_selection_panel.ts
+++ b/ui/src/frontend/time_selection_panel.ts
@@ -15,7 +15,7 @@
 import m from 'mithril';
 
 import {duration, Span, time, Time, TimeSpan} from '../base/time';
-import {timestampFormat, TimestampFormat} from '../common/timestamp_format';
+import {timestampFormat, TimestampFormat} from '../core/timestamp_format';
 
 import {
   BACKGROUND_COLOR,
diff --git a/ui/src/frontend/widgets/duration.ts b/ui/src/frontend/widgets/duration.ts
index f07a095..c0357fb 100644
--- a/ui/src/frontend/widgets/duration.ts
+++ b/ui/src/frontend/widgets/duration.ts
@@ -23,7 +23,7 @@
   setDurationPrecision,
   TimestampFormat,
   timestampFormat,
-} from '../../common/timestamp_format';
+} from '../../core/timestamp_format';
 import {raf} from '../../core/raf_scheduler';
 import {Anchor} from '../../widgets/anchor';
 import {MenuDivider, MenuItem, PopupMenu2} from '../../widgets/menu';
diff --git a/ui/src/frontend/widgets/timestamp.ts b/ui/src/frontend/widgets/timestamp.ts
index 0f3f08b..9a89e3c 100644
--- a/ui/src/frontend/widgets/timestamp.ts
+++ b/ui/src/frontend/widgets/timestamp.ts
@@ -22,7 +22,7 @@
   setTimestampFormat,
   TimestampFormat,
   timestampFormat,
-} from '../../common/timestamp_format';
+} from '../../core/timestamp_format';
 import {raf} from '../../core/raf_scheduler';
 import {Anchor} from '../../widgets/anchor';
 import {MenuDivider, MenuItem, PopupMenu2} from '../../widgets/menu';
diff --git a/ui/src/plugins/linuxDeviceTracks/OWNERS b/ui/src/plugins/org.kernel.LinuxKernelDevices/OWNERS
similarity index 100%
rename from ui/src/plugins/linuxDeviceTracks/OWNERS
rename to ui/src/plugins/org.kernel.LinuxKernelDevices/OWNERS
diff --git a/ui/src/plugins/linuxDeviceTracks/index.ts b/ui/src/plugins/org.kernel.LinuxKernelDevices/index.ts
similarity index 89%
rename from ui/src/plugins/linuxDeviceTracks/index.ts
rename to ui/src/plugins/org.kernel.LinuxKernelDevices/index.ts
index 5c5b21c..e9bf9b0 100644
--- a/ui/src/plugins/linuxDeviceTracks/index.ts
+++ b/ui/src/plugins/org.kernel.LinuxKernelDevices/index.ts
@@ -25,7 +25,7 @@
 
 // This plugin renders visualizations of runtime power state transitions for
 // Linux kernel devices (devices managed by Linux drivers).
-class linuxDevices implements Plugin {
+class LinuxKernelDevices implements Plugin {
   onActivate(_: PluginContext): void {}
 
   async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
@@ -57,11 +57,11 @@
 
     for (; it.valid(); it.next()) {
       const trackId = it.trackId;
-      const name = it.name ?? `${trackId}`;
+      const displayName = it.name ?? `${trackId}`;
 
       ctx.registerStaticTrack({
-        uri: `linuxDeviceTracks#${name}`,
-        displayName: name,
+        uri: `org.kernel.LinuxKernelDevices#${displayName}`,
+        displayName,
         trackIds: [trackId],
         kind: ASYNC_SLICE_TRACK_KIND,
         trackFactory: ({trackKey}) => {
@@ -81,6 +81,6 @@
 }
 
 export const plugin: PluginDescriptor = {
-  pluginId: 'linuxDeviceTracks',
-  plugin: linuxDevices,
+  pluginId: 'org.kernel.LinuxKernelDevices',
+  plugin: LinuxKernelDevices,
 };
diff --git a/ui/src/public/index.ts b/ui/src/public/index.ts
index 1e4b984..3ba4c6a 100644
--- a/ui/src/public/index.ts
+++ b/ui/src/public/index.ts
@@ -16,13 +16,12 @@
 
 import {Hotkey} from '../base/hotkeys';
 import {duration, time} from '../base/time';
-import {ColorScheme} from '../common/colorizer';
+import {Migrate, Store} from '../base/store';
+import {ColorScheme} from '../core/colorizer';
 import {Selection} from '../common/state';
 import {PanelSize} from '../frontend/panel';
-import {Migrate, Store} from '../frontend/store';
 import {EngineProxy} from '../trace_processor/engine';
 
-export {createStore, Migrate, Store} from '../frontend/store';
 export {EngineProxy} from '../trace_processor/engine';
 export {
   LONG,
@@ -33,6 +32,7 @@
   STR_NULL,
 } from '../trace_processor/query_result';
 export {BottomTabToSCSAdapter} from './utils';
+export {createStore, Migrate, Store} from '../base/store';
 
 // This is a temporary fix until this is available in the plugin API.
 export {
diff --git a/ui/src/trace_processor/engine.ts b/ui/src/trace_processor/engine.ts
index 6a4dd76..b234c16 100644
--- a/ui/src/trace_processor/engine.ts
+++ b/ui/src/trace_processor/engine.ts
@@ -63,6 +63,7 @@
   cropTrackEvents: boolean;
   ingestFtraceInRawTable: boolean;
   analyzeTraceProtoContent: boolean;
+  ftraceDropUntilAllCpusValid: boolean;
 }
 
 // Abstract interface of a trace proccessor.
@@ -273,6 +274,7 @@
     cropTrackEvents,
     ingestFtraceInRawTable,
     analyzeTraceProtoContent,
+    ftraceDropUntilAllCpusValid,
   }: TraceProcessorConfig): Promise<void> {
     const asyncRes = defer<void>();
     this.pendingResetTraceProcessors.push(asyncRes);
@@ -285,6 +287,7 @@
       : ResetTraceProcessorArgs.DropTrackEventDataBefore.NO_DROP;
     args.ingestFtraceInRawTable = ingestFtraceInRawTable;
     args.analyzeTraceProtoContent = analyzeTraceProtoContent;
+    args.ftraceDropUntilAllCpusValid = ftraceDropUntilAllCpusValid;
     this.rpcSendRequest(rpc);
     return asyncRes;
   }
diff --git a/ui/src/tracks/chrome_scroll_jank/jank_colors.ts b/ui/src/tracks/chrome_scroll_jank/jank_colors.ts
index 22d508e..a18ea03 100644
--- a/ui/src/tracks/chrome_scroll_jank/jank_colors.ts
+++ b/ui/src/tracks/chrome_scroll_jank/jank_colors.ts
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {HSLColor} from '../../common/color';
-import {makeColorScheme} from '../../common/colorizer';
+import {HSLColor} from '../../core/color';
+import {makeColorScheme} from '../../core/colorizer';
 
 export const JANK_COLOR = makeColorScheme(new HSLColor([343, 100, 43]));
diff --git a/ui/src/tracks/chrome_scroll_jank/scroll_jank_v3_track.ts b/ui/src/tracks/chrome_scroll_jank/scroll_jank_v3_track.ts
index 7fa22ec..fb77174 100644
--- a/ui/src/tracks/chrome_scroll_jank/scroll_jank_v3_track.ts
+++ b/ui/src/tracks/chrome_scroll_jank/scroll_jank_v3_track.ts
@@ -30,7 +30,7 @@
 } from './index';
 import {JANK_COLOR} from './jank_colors';
 import {ScrollJankV3DetailsPanel} from './scroll_jank_v3_details_panel';
-import {getColorForSlice} from '../../common/colorizer';
+import {getColorForSlice} from '../../core/colorizer';
 
 const UNKNOWN_SLICE_NAME = 'Unknown';
 const JANK_SLICE_NAME = ' Jank';
diff --git a/ui/src/tracks/cpu_freq/index.ts b/ui/src/tracks/cpu_freq/index.ts
index 743d7bd..a2327fa 100644
--- a/ui/src/tracks/cpu_freq/index.ts
+++ b/ui/src/tracks/cpu_freq/index.ts
@@ -20,7 +20,7 @@
 import {duration, time, Time} from '../../base/time';
 import {calcCachedBucketSize} from '../../common/cache_utils';
 import {drawTrackHoverTooltip} from '../../common/canvas_utils';
-import {colorForCpu} from '../../common/colorizer';
+import {colorForCpu} from '../../core/colorizer';
 import {TrackData} from '../../common/track_data';
 import {TimelineFetcher} from '../../common/track_helper';
 import {checkerboardExcept} from '../../frontend/checkerboard';
diff --git a/ui/src/tracks/cpu_profile/index.ts b/ui/src/tracks/cpu_profile/index.ts
index e725370..fb7c7c5 100644
--- a/ui/src/tracks/cpu_profile/index.ts
+++ b/ui/src/tracks/cpu_profile/index.ts
@@ -15,7 +15,7 @@
 import {searchSegment} from '../../base/binary_search';
 import {duration, Time, time} from '../../base/time';
 import {Actions} from '../../common/actions';
-import {colorForSample} from '../../common/colorizer';
+import {colorForSample} from '../../core/colorizer';
 import {TrackData} from '../../common/track_data';
 import {TimelineFetcher} from '../../common/track_helper';
 import {CpuProfileDetailsPanel} from '../../frontend/cpu_profile_panel';
diff --git a/ui/src/tracks/cpu_slices/index.ts b/ui/src/tracks/cpu_slices/index.ts
index 6bbec00..647baed 100644
--- a/ui/src/tracks/cpu_slices/index.ts
+++ b/ui/src/tracks/cpu_slices/index.ts
@@ -26,8 +26,8 @@
   drawIncompleteSlice,
   drawTrackHoverTooltip,
 } from '../../common/canvas_utils';
-import {Color} from '../../common/color';
-import {colorForThread} from '../../common/colorizer';
+import {Color} from '../../core/color';
+import {colorForThread} from '../../core/colorizer';
 import {TrackData} from '../../common/track_data';
 import {TimelineFetcher} from '../../common/track_helper';
 import {checkerboardExcept} from '../../frontend/checkerboard';
diff --git a/ui/src/tracks/frames/actual_frames_track_v2.ts b/ui/src/tracks/frames/actual_frames_track_v2.ts
index 1f04718..6ce0e3d 100644
--- a/ui/src/tracks/frames/actual_frames_track_v2.ts
+++ b/ui/src/tracks/frames/actual_frames_track_v2.ts
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {HSLColor} from '../../common/color';
-import {ColorScheme, makeColorScheme} from '../../common/colorizer';
+import {HSLColor} from '../../core/color';
+import {ColorScheme, makeColorScheme} from '../../core/colorizer';
 import {
   NAMED_ROW,
   NamedSliceTrack,
diff --git a/ui/src/tracks/frames/expected_frames_track_v2.ts b/ui/src/tracks/frames/expected_frames_track_v2.ts
index ea730b8..ce602a1 100644
--- a/ui/src/tracks/frames/expected_frames_track_v2.ts
+++ b/ui/src/tracks/frames/expected_frames_track_v2.ts
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {HSLColor} from '../../common/color';
-import {makeColorScheme} from '../../common/colorizer';
+import {HSLColor} from '../../core/color';
+import {makeColorScheme} from '../../core/colorizer';
 import {NamedRow, NamedSliceTrack} from '../../frontend/named_slice_track';
 import {SLICE_LAYOUT_FIT_CONTENT_DEFAULTS} from '../../frontend/slice_layout';
 import {EngineProxy, Slice} from '../../public';
diff --git a/ui/src/tracks/ftrace/index.ts b/ui/src/tracks/ftrace/index.ts
index 530bd47..8be7074 100644
--- a/ui/src/tracks/ftrace/index.ts
+++ b/ui/src/tracks/ftrace/index.ts
@@ -15,7 +15,7 @@
 import m from 'mithril';
 
 import {duration, Time, time} from '../../base/time';
-import {colorForFtrace} from '../../common/colorizer';
+import {colorForFtrace} from '../../core/colorizer';
 import {LIMIT, TrackData} from '../../common/track_data';
 import {TimelineFetcher} from '../../common/track_helper';
 import {checkerboardExcept} from '../../frontend/checkerboard';
diff --git a/ui/src/tracks/process_summary/process_scheduling_track.ts b/ui/src/tracks/process_summary/process_scheduling_track.ts
index b059fb6..0edbf58 100644
--- a/ui/src/tracks/process_summary/process_scheduling_track.ts
+++ b/ui/src/tracks/process_summary/process_scheduling_track.ts
@@ -21,8 +21,8 @@
 import {Actions} from '../../common/actions';
 import {calcCachedBucketSize} from '../../common/cache_utils';
 import {drawTrackHoverTooltip} from '../../common/canvas_utils';
-import {Color} from '../../common/color';
-import {colorForThread} from '../../common/colorizer';
+import {Color} from '../../core/color';
+import {colorForThread} from '../../core/colorizer';
 import {TrackData} from '../../common/track_data';
 import {TimelineFetcher} from '../../common/track_helper';
 import {checkerboardExcept} from '../../frontend/checkerboard';
diff --git a/ui/src/tracks/process_summary/process_summary_track.ts b/ui/src/tracks/process_summary/process_summary_track.ts
index b5b4157..6a8e687 100644
--- a/ui/src/tracks/process_summary/process_summary_track.ts
+++ b/ui/src/tracks/process_summary/process_summary_track.ts
@@ -17,7 +17,7 @@
 import {BigintMath} from '../../base/bigint_math';
 import {assertFalse} from '../../base/logging';
 import {duration, Time, time} from '../../base/time';
-import {colorForTid} from '../../common/colorizer';
+import {colorForTid} from '../../core/colorizer';
 import {LIMIT, TrackData} from '../../common/track_data';
 import {EngineProxy, TimelineFetcher} from '../../common/track_helper';
 import {checkerboardExcept} from '../../frontend/checkerboard';
diff --git a/ui/src/tracks/thread_state/thread_state_v2.ts b/ui/src/tracks/thread_state/thread_state_v2.ts
index d48ae1e..631160d 100644
--- a/ui/src/tracks/thread_state/thread_state_v2.ts
+++ b/ui/src/tracks/thread_state/thread_state_v2.ts
@@ -13,7 +13,7 @@
 // limitations under the License.
 
 import {Actions} from '../../common/actions';
-import {colorForState} from '../../common/colorizer';
+import {colorForState} from '../../core/colorizer';
 import {Selection} from '../../common/state';
 import {translateState} from '../../common/thread_state';
 import {