processor: Move TrackEvent tokenizing & parsing into module

Extracts the logic for tokenizing and parsing TrackEvents (+ related
descriptors) into TrackEventModule.

Bug: 141459049
Change-Id: Ib9bfe49d001b3e415be74757ff8f0c9a732b42e6
diff --git a/Android.bp b/Android.bp
index d42ffc9..e7d12ee 100644
--- a/Android.bp
+++ b/Android.bp
@@ -4559,6 +4559,8 @@
     "src/trace_processor/importers/fuchsia/fuchsia_trace_parser.cc",
     "src/trace_processor/importers/fuchsia/fuchsia_trace_tokenizer.cc",
     "src/trace_processor/importers/fuchsia/fuchsia_trace_utils.cc",
+    "src/trace_processor/importers/proto/track_event_parser.cc",
+    "src/trace_processor/importers/proto/track_event_tokenizer.cc",
     "src/trace_processor/importers/systrace/systrace_parser.cc",
     "src/trace_processor/importers/systrace/systrace_trace_parser.cc",
     "src/trace_processor/instants_table.cc",
diff --git a/BUILD b/BUILD
index a716bda..0965d30 100644
--- a/BUILD
+++ b/BUILD
@@ -677,8 +677,14 @@
         "src/trace_processor/importers/json/json_trace_tokenizer.h",
         "src/trace_processor/importers/json/json_trace_utils.cc",
         "src/trace_processor/importers/json/json_trace_utils.h",
+        "src/trace_processor/importers/proto/packet_sequence_state.h",
         "src/trace_processor/importers/proto/proto_importer_module.h",
+        "src/trace_processor/importers/proto/proto_incremental_state.h",
         "src/trace_processor/importers/proto/track_event_module.h",
+        "src/trace_processor/importers/proto/track_event_parser.cc",
+        "src/trace_processor/importers/proto/track_event_parser.h",
+        "src/trace_processor/importers/proto/track_event_tokenizer.cc",
+        "src/trace_processor/importers/proto/track_event_tokenizer.h",
         "src/trace_processor/importers/systrace/systrace_parser.cc",
         "src/trace_processor/importers/systrace/systrace_parser.h",
         "src/trace_processor/importers/systrace/systrace_trace_parser.cc",
@@ -692,7 +698,6 @@
         "src/trace_processor/process_table.h",
         "src/trace_processor/process_tracker.cc",
         "src/trace_processor/process_tracker.h",
-        "src/trace_processor/proto_incremental_state.h",
         "src/trace_processor/proto_trace_parser.cc",
         "src/trace_processor/proto_trace_parser.h",
         "src/trace_processor/proto_trace_tokenizer.cc",
diff --git a/src/trace_processor/BUILD.gn b/src/trace_processor/BUILD.gn
index 4814052..9bc99ea 100644
--- a/src/trace_processor/BUILD.gn
+++ b/src/trace_processor/BUILD.gn
@@ -88,8 +88,14 @@
     "importers/ftrace/ftrace_tokenizer.h",
     "importers/ftrace/sched_event_tracker.h",
     "importers/fuchsia/fuchsia_provider_view.h",
+    "importers/proto/packet_sequence_state.h",
     "importers/proto/proto_importer_module.h",
+    "importers/proto/proto_incremental_state.h",
     "importers/proto/track_event_module.h",
+    "importers/proto/track_event_parser.cc",
+    "importers/proto/track_event_parser.h",
+    "importers/proto/track_event_tokenizer.cc",
+    "importers/proto/track_event_tokenizer.h",
     "importers/systrace/systrace_parser.h",
     "importers/systrace/systrace_trace_parser.h",
     "instants_table.cc",
@@ -101,7 +107,6 @@
     "process_table.h",
     "process_tracker.cc",
     "process_tracker.h",
-    "proto_incremental_state.h",
     "proto_trace_parser.cc",
     "proto_trace_parser.h",
     "proto_trace_tokenizer.cc",
diff --git a/src/trace_processor/importers/ftrace/ftrace_module.h b/src/trace_processor/importers/ftrace/ftrace_module.h
index a08eecd..9d8d489 100644
--- a/src/trace_processor/importers/ftrace/ftrace_module.h
+++ b/src/trace_processor/importers/ftrace/ftrace_module.h
@@ -40,7 +40,8 @@
   ModuleResult TokenizePacket(
       const protos::pbzero::TracePacket::Decoder& decoder,
       TraceBlobView* packet,
-      int64_t /*packet_timestamp*/) {
+      int64_t /*packet_timestamp*/,
+      PacketSequenceState* /*state*/) {
     if (decoder.has_ftrace_events()) {
       auto ftrace_field = decoder.ftrace_events();
       const size_t fld_off = packet->offset_of(ftrace_field.data);
diff --git a/src/trace_processor/importers/proto/packet_sequence_state.h b/src/trace_processor/importers/proto/packet_sequence_state.h
new file mode 100644
index 0000000..8ee597b
--- /dev/null
+++ b/src/trace_processor/importers/proto/packet_sequence_state.h
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_PACKET_SEQUENCE_STATE_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_PACKET_SEQUENCE_STATE_H_
+
+#include <stdint.h>
+
+#include <unordered_map>
+#include <vector>
+
+#include "perfetto/protozero/proto_decoder.h"
+#include "src/trace_processor/stack_profile_tracker.h"
+#include "src/trace_processor/trace_blob_view.h"
+#include "src/trace_processor/trace_processor_context.h"
+#include "src/trace_processor/trace_storage.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+#if PERFETTO_DCHECK_IS_ON() && defined(__GNUC__)
+// When called from GetOrCreateDecoder(), __PRETTY_FUNCTION__ (supported by GCC
+// + clang) should include the stringified name of the MessageType.
+#define PERFETTO_TYPE_IDENTIFIER __PRETTY_FUNCTION__
+#else  // PERFETTO_DCHECK_IS_ON() && defined(__GNUC__)
+#define PERFETTO_TYPE_IDENTIFIER nullptr
+#endif  // PERFETTO_DCHECK_IS_ON() && defined(__GNUC__)
+
+class PacketSequenceState {
+ public:
+  // Entry in an interning index, refers to the interned message.
+  struct InternedMessageView {
+    InternedMessageView(TraceBlobView msg) : message(std::move(msg)) {}
+
+    template <typename MessageType>
+    typename MessageType::Decoder* GetOrCreateDecoder() {
+      if (!decoder) {
+        // Lazy init the decoder and save it away, so that we don't have to
+        // reparse the message every time we access the interning entry.
+        decoder = std::unique_ptr<void, std::function<void(void*)>>(
+            new typename MessageType::Decoder(message.data(), message.length()),
+            [](void* obj) {
+              delete reinterpret_cast<typename MessageType::Decoder*>(obj);
+            });
+        decoder_type = PERFETTO_TYPE_IDENTIFIER;
+      }
+      // Verify that the type of the decoder didn't change.
+      if (PERFETTO_TYPE_IDENTIFIER &&
+          strcmp(decoder_type,
+                 // GCC complains if this arg can be null.
+                 PERFETTO_TYPE_IDENTIFIER ? PERFETTO_TYPE_IDENTIFIER : "") !=
+              0) {
+        PERFETTO_FATAL(
+            "Interning entry accessed under different types! previous type: "
+            "%s. new type: %s.",
+            decoder_type, __PRETTY_FUNCTION__);
+      }
+      return reinterpret_cast<typename MessageType::Decoder*>(decoder.get());
+    }
+
+    TraceBlobView message;
+    std::unique_ptr<void, std::function<void(void*)>> decoder;
+
+   private:
+    const char* decoder_type = nullptr;
+  };
+
+  using InternedMessageMap =
+      std::unordered_map<uint64_t /*iid*/, InternedMessageView>;
+  using InternedFieldMap =
+      std::unordered_map<uint32_t /*field_id*/, InternedMessageMap>;
+  using InternedDataGenerationList = std::vector<InternedFieldMap>;
+
+  PacketSequenceState(TraceProcessorContext* context)
+      : context_(context), stack_profile_tracker_(context) {
+    interned_data_.emplace_back();
+  }
+
+  int64_t IncrementAndGetTrackEventTimeNs(int64_t delta_ns) {
+    PERFETTO_DCHECK(track_event_timestamps_valid());
+    track_event_timestamp_ns_ += delta_ns;
+    return track_event_timestamp_ns_;
+  }
+
+  int64_t IncrementAndGetTrackEventThreadTimeNs(int64_t delta_ns) {
+    PERFETTO_DCHECK(track_event_timestamps_valid());
+    track_event_thread_timestamp_ns_ += delta_ns;
+    return track_event_thread_timestamp_ns_;
+  }
+
+  int64_t IncrementAndGetTrackEventThreadInstructionCount(int64_t delta) {
+    PERFETTO_DCHECK(track_event_timestamps_valid());
+    track_event_thread_instruction_count_ += delta;
+    return track_event_thread_instruction_count_;
+  }
+
+  void OnPacketLoss() {
+    packet_loss_ = true;
+    track_event_timestamps_valid_ = false;
+  }
+
+  void OnIncrementalStateCleared() {
+    packet_loss_ = false;
+    interned_data_.emplace_back();  // Bump generation number
+  }
+
+  void SetThreadDescriptor(int32_t pid,
+                           int32_t tid,
+                           int64_t timestamp_ns,
+                           int64_t thread_timestamp_ns,
+                           int64_t thread_instruction_count) {
+    track_event_timestamps_valid_ = true;
+    pid_and_tid_valid_ = true;
+    pid_ = pid;
+    tid_ = tid;
+    track_event_timestamp_ns_ = timestamp_ns;
+    track_event_thread_timestamp_ns_ = thread_timestamp_ns;
+    track_event_thread_instruction_count_ = thread_instruction_count;
+  }
+
+  bool IsIncrementalStateValid() const { return !packet_loss_; }
+
+  StackProfileTracker& stack_profile_tracker() {
+    return stack_profile_tracker_;
+  }
+
+  // Returns the index of the current generation in the
+  // InternedDataGenerationList.
+  size_t current_generation() const { return interned_data_.size() - 1; }
+
+  bool track_event_timestamps_valid() const {
+    return track_event_timestamps_valid_;
+  }
+
+  bool pid_and_tid_valid() const { return pid_and_tid_valid_; }
+
+  int32_t pid() const { return pid_; }
+  int32_t tid() const { return tid_; }
+
+  void InternMessage(uint32_t field_id, TraceBlobView message) {
+    constexpr auto kIidFieldNumber = 1;
+
+    uint64_t iid = 0;
+    auto message_start = message.data();
+    auto message_size = message.length();
+    protozero::ProtoDecoder decoder(message_start, message_size);
+
+    auto field = decoder.FindField(kIidFieldNumber);
+    if (PERFETTO_UNLIKELY(!field)) {
+      PERFETTO_DLOG("Interned message without interning_id");
+      context_->storage->IncrementStats(stats::interned_data_tokenizer_errors);
+      return;
+    }
+    iid = field.as_uint64();
+
+    auto* map = &interned_data_.back()[field_id];
+    auto res = map->emplace(iid, InternedMessageView(std::move(message)));
+
+    // If a message with this ID is already interned in the same generation,
+    // its data should not have changed (this is forbidden by the InternedData
+    // proto).
+    // TODO(eseckler): This DCHECK assumes that the message is encoded the
+    // same way if it is re-emitted.
+    PERFETTO_DCHECK(res.second ||
+                    (res.first->second.message.length() == message_size &&
+                     memcmp(res.first->second.message.data(), message_start,
+                            message_size) == 0));
+  }
+
+  template <uint32_t FieldId, typename MessageType>
+  typename MessageType::Decoder* LookupInternedMessage(size_t generation,
+                                                       uint64_t iid) {
+    PERFETTO_CHECK(generation <= interned_data_.size());
+    auto* field_map = &interned_data_[generation];
+    auto field_it = field_map->find(FieldId);
+    if (field_it != field_map->end()) {
+      auto* message_map = &field_it->second;
+      auto it = message_map->find(iid);
+      if (it != message_map->end()) {
+        return it->second.GetOrCreateDecoder<MessageType>();
+      }
+    }
+    context_->storage->IncrementStats(stats::interned_data_tokenizer_errors);
+    PERFETTO_DLOG("Could not find interning entry for field ID %" PRIu32
+                  ", generation %zu, and IID %" PRIu64,
+                  FieldId, generation, iid);
+    return nullptr;
+  }
+
+ private:
+  TraceProcessorContext* context_;
+
+  // If true, incremental state on the sequence is considered invalid until we
+  // see the next packet with incremental_state_cleared. We assume that we
+  // missed some packets at the beginning of the trace.
+  bool packet_loss_ = true;
+
+  // We can only consider TrackEvent delta timestamps to be correct after we
+  // have observed a thread descriptor (since the last packet loss).
+  bool track_event_timestamps_valid_ = false;
+
+  // |pid_| and |tid_| are only valid after we parsed at least one
+  // ThreadDescriptor packet on the sequence.
+  bool pid_and_tid_valid_ = false;
+
+  // Process/thread ID of the packet sequence set by a ThreadDescriptor
+  // packet. Used as default values for TrackEvents that don't specify a
+  // pid/tid override. Only valid after |pid_and_tid_valid_| is set to true.
+  int32_t pid_ = 0;
+  int32_t tid_ = 0;
+
+  // Current wall/thread timestamps/counters used as reference for the next
+  // TrackEvent delta timestamp.
+  int64_t track_event_timestamp_ns_ = 0;
+  int64_t track_event_thread_timestamp_ns_ = 0;
+  int64_t track_event_thread_instruction_count_ = 0;
+
+  InternedDataGenerationList interned_data_;
+  StackProfileTracker stack_profile_tracker_;
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_PACKET_SEQUENCE_STATE_H_
diff --git a/src/trace_processor/importers/proto/proto_importer_module.h b/src/trace_processor/importers/proto/proto_importer_module.h
index 23b3fac..d7efaf9 100644
--- a/src/trace_processor/importers/proto/proto_importer_module.h
+++ b/src/trace_processor/importers/proto/proto_importer_module.h
@@ -26,6 +26,7 @@
 namespace perfetto {
 namespace trace_processor {
 
+class PacketSequenceState;
 struct TimestampedTracePiece;
 class TraceProcessorContext;
 
@@ -121,9 +122,11 @@
   ModuleResult TokenizePacket(
       const protos::pbzero::TracePacket::Decoder& decoder,
       TraceBlobView* packet,
-      int64_t packet_timestamp) {
-    if (ModuleType::kEnabled)
-      return impl_->TokenizePacket(decoder, packet, packet_timestamp);
+      int64_t packet_timestamp,
+      PacketSequenceState* state) {
+    if (ModuleType::kEnabled) {
+      return impl_->TokenizePacket(decoder, packet, packet_timestamp, state);
+    }
     return ModuleResult::Ignored();
   }
 
diff --git a/src/trace_processor/importers/proto/proto_incremental_state.h b/src/trace_processor/importers/proto/proto_incremental_state.h
new file mode 100644
index 0000000..aaac42f
--- /dev/null
+++ b/src/trace_processor/importers/proto/proto_incremental_state.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_PROTO_INCREMENTAL_STATE_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_PROTO_INCREMENTAL_STATE_H_
+
+#include <stdint.h>
+
+#include <map>
+
+#include "src/trace_processor/importers/proto/packet_sequence_state.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+class TraceProcessorContext;
+
+// Stores per-packet-sequence incremental state during trace parsing, such as
+// reference timestamps for delta timestamp calculation and interned messages.
+class ProtoIncrementalState {
+ public:
+  ProtoIncrementalState(TraceProcessorContext* context) : context_(context) {}
+
+  // Returns the PacketSequenceState for the packet sequence with the given id.
+  // If this is a new sequence which we haven't tracked before, initializes and
+  // inserts a new PacketSequenceState into the state map.
+  PacketSequenceState* GetOrCreateStateForPacketSequence(uint32_t sequence_id) {
+    auto& ptr = packet_sequence_states_[sequence_id];
+    if (!ptr)
+      ptr.reset(new PacketSequenceState(context_));
+    return ptr.get();
+  }
+
+ private:
+  // Stores unique_ptrs to ensure that pointers to a PacketSequenceState remain
+  // valid even if the map rehashes.
+  std::map<uint32_t, std::unique_ptr<PacketSequenceState>>
+      packet_sequence_states_;
+
+  TraceProcessorContext* context_;
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_PROTO_INCREMENTAL_STATE_H_
diff --git a/src/trace_processor/importers/proto/track_event_module.h b/src/trace_processor/importers/proto/track_event_module.h
index 1c66d34..6822139 100644
--- a/src/trace_processor/importers/proto/track_event_module.h
+++ b/src/trace_processor/importers/proto/track_event_module.h
@@ -19,6 +19,9 @@
 
 #include "perfetto/base/build_config.h"
 #include "src/trace_processor/importers/proto/proto_importer_module.h"
+#include "src/trace_processor/importers/proto/track_event_parser.h"
+#include "src/trace_processor/importers/proto/track_event_tokenizer.h"
+#include "src/trace_processor/timestamped_trace_piece.h"
 
 namespace perfetto {
 namespace trace_processor {
@@ -26,20 +29,56 @@
 class TrackEventModule : public ProtoImporterModuleBase</*IsEnabled=*/1> {
  public:
   explicit TrackEventModule(TraceProcessorContext* context)
-      : ProtoImporterModuleBase(context) {}
+      : ProtoImporterModuleBase(context),
+        tokenizer_(context),
+        parser_(context) {}
 
-  ModuleResult TokenizePacket(const protos::pbzero::TracePacket::Decoder&,
-                              TraceBlobView* /*packet*/,
-                              int64_t /*packet_timestamp*/) {
-    // TODO(eseckler): implement.
+  ModuleResult TokenizePacket(
+      const protos::pbzero::TracePacket::Decoder& decoder,
+      TraceBlobView* packet,
+      int64_t packet_timestamp,
+      PacketSequenceState* state) {
+    if (decoder.has_track_descriptor()) {
+      tokenizer_.TokenizeTrackDescriptorPacket(decoder);
+      return ModuleResult::Handled();
+    }
+
+    if (decoder.has_track_event()) {
+      tokenizer_.TokenizeTrackEventPacket(state, decoder, packet,
+                                          packet_timestamp);
+      return ModuleResult::Handled();
+    }
+
+    // TODO(eseckler): Remove these once Chrome has switched fully over to
+    // TrackDescriptors.
+    if (decoder.has_thread_descriptor()) {
+      tokenizer_.TokenizeThreadDescriptorPacket(state, decoder);
+      return ModuleResult::Handled();
+    }
+    if (decoder.has_process_descriptor()) {
+      tokenizer_.TokenizeProcessDescriptorPacket(decoder);
+      return ModuleResult::Handled();
+    }
+
     return ModuleResult::Ignored();
   }
 
-  ModuleResult ParsePacket(const protos::pbzero::TracePacket::Decoder&,
-                           const TimestampedTracePiece&) {
+  ModuleResult ParsePacket(const protos::pbzero::TracePacket::Decoder& decoder,
+                           const TimestampedTracePiece& ttp) {
     // TODO(eseckler): implement.
+    if (decoder.has_track_event()) {
+      parser_.ParseTrackEvent(
+          ttp.timestamp, ttp.thread_timestamp, ttp.thread_instruction_count,
+          ttp.packet_sequence_state, ttp.packet_sequence_state_generation,
+          decoder.track_event());
+      return ModuleResult::Handled();
+    }
     return ModuleResult::Ignored();
   }
+
+ private:
+  TrackEventTokenizer tokenizer_;
+  TrackEventParser parser_;
 };
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/importers/proto/track_event_parser.cc b/src/trace_processor/importers/proto/track_event_parser.cc
new file mode 100644
index 0000000..6f2b46f
--- /dev/null
+++ b/src/trace_processor/importers/proto/track_event_parser.cc
@@ -0,0 +1,893 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_processor/importers/proto/track_event_parser.h"
+
+#include <string>
+
+#include "perfetto/base/logging.h"
+#include "src/trace_processor/args_tracker.h"
+#include "src/trace_processor/importers/proto/packet_sequence_state.h"
+#include "src/trace_processor/process_tracker.h"
+#include "src/trace_processor/track_tracker.h"
+
+#include "protos/perfetto/trace/interned_data/interned_data.pbzero.h"
+#include "protos/perfetto/trace/track_event/debug_annotation.pbzero.h"
+#include "protos/perfetto/trace/track_event/log_message.pbzero.h"
+#include "protos/perfetto/trace/track_event/source_location.pbzero.h"
+#include "protos/perfetto/trace/track_event/task_execution.pbzero.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+namespace {
+using protozero::ConstBytes;
+
+// Slices which have been opened but haven't been closed yet will be marked
+// with these placeholder values.
+constexpr int64_t kPendingThreadDuration = -1;
+constexpr int64_t kPendingThreadInstructionDelta = -1;
+}  // namespace
+
+TrackEventParser::TrackEventParser(TraceProcessorContext* context)
+    : context_(context),
+      task_file_name_args_key_id_(
+          context->storage->InternString("task.posted_from.file_name")),
+      task_function_name_args_key_id_(
+          context->storage->InternString("task.posted_from.function_name")),
+      task_line_number_args_key_id_(
+          context->storage->InternString("task.posted_from.line_number")),
+      log_message_body_key_id_(
+          context->storage->InternString("track_event.log_message")),
+      raw_legacy_event_id_(
+          context->storage->InternString("track_event.legacy_event")),
+      legacy_event_category_key_id_(
+          context->storage->InternString("legacy_event.category")),
+      legacy_event_name_key_id_(
+          context->storage->InternString("legacy_event.name")),
+      legacy_event_phase_key_id_(
+          context->storage->InternString("legacy_event.phase")),
+      legacy_event_duration_ns_key_id_(
+          context->storage->InternString("legacy_event.duration_ns")),
+      legacy_event_thread_timestamp_ns_key_id_(
+          context->storage->InternString("legacy_event.thread_timestamp_ns")),
+      legacy_event_thread_duration_ns_key_id_(
+          context->storage->InternString("legacy_event.thread_duration_ns")),
+      legacy_event_thread_instruction_count_key_id_(
+          context->storage->InternString(
+              "legacy_event.thread_instruction_count")),
+      legacy_event_thread_instruction_delta_key_id_(
+          context->storage->InternString(
+              "legacy_event.thread_instruction_delta")),
+      legacy_event_use_async_tts_key_id_(
+          context->storage->InternString("legacy_event.use_async_tts")),
+      legacy_event_unscoped_id_key_id_(
+          context->storage->InternString("legacy_event.unscoped_id")),
+      legacy_event_global_id_key_id_(
+          context->storage->InternString("legacy_event.global_id")),
+      legacy_event_local_id_key_id_(
+          context->storage->InternString("legacy_event.local_id")),
+      legacy_event_id_scope_key_id_(
+          context->storage->InternString("legacy_event.id_scope")),
+      legacy_event_bind_id_key_id_(
+          context->storage->InternString("legacy_event.bind_id")),
+      legacy_event_bind_to_enclosing_key_id_(
+          context->storage->InternString("legacy_event.bind_to_enclosing")),
+      legacy_event_flow_direction_key_id_(
+          context->storage->InternString("legacy_event.flow_direction")),
+      flow_direction_value_in_id_(context->storage->InternString("in")),
+      flow_direction_value_out_id_(context->storage->InternString("out")),
+      flow_direction_value_inout_id_(context->storage->InternString("inout")) {}
+
+void TrackEventParser::ParseTrackEvent(int64_t ts,
+                                       int64_t tts,
+                                       int64_t ticount,
+                                       PacketSequenceState* sequence_state,
+                                       size_t sequence_state_generation,
+                                       ConstBytes blob) {
+  using LegacyEvent = protos::pbzero::TrackEvent::LegacyEvent;
+
+  protos::pbzero::TrackEvent::Decoder event(blob.data, blob.size);
+
+  const auto legacy_event_blob = event.legacy_event();
+  LegacyEvent::Decoder legacy_event(legacy_event_blob.data,
+                                    legacy_event_blob.size);
+
+  // TODO(eseckler): This legacy event field will eventually be replaced by
+  // fields in TrackEvent itself.
+  if (PERFETTO_UNLIKELY(!event.type() && !legacy_event.has_phase())) {
+    context_->storage->IncrementStats(stats::track_event_parser_errors);
+    PERFETTO_DLOG("TrackEvent without type or phase");
+    return;
+  }
+
+  ProcessTracker* procs = context_->process_tracker.get();
+  TraceStorage* storage = context_->storage.get();
+  TrackTracker* track_tracker = context_->track_tracker.get();
+  SliceTracker* slice_tracker = context_->slice_tracker.get();
+
+  std::vector<uint64_t> category_iids;
+  for (auto it = event.category_iids(); it; ++it) {
+    category_iids.push_back(*it);
+  }
+  std::vector<protozero::ConstChars> category_strings;
+  for (auto it = event.categories(); it; ++it) {
+    category_strings.push_back(*it);
+  }
+
+  StringId category_id = 0;
+
+  // If there's a single category, we can avoid building a concatenated
+  // string.
+  if (PERFETTO_LIKELY(category_iids.size() == 1 && category_strings.empty())) {
+    auto* decoder = sequence_state->LookupInternedMessage<
+        protos::pbzero::InternedData::kEventCategoriesFieldNumber,
+        protos::pbzero::EventCategory>(sequence_state_generation,
+                                       category_iids[0]);
+    if (decoder)
+      category_id = storage->InternString(decoder->name());
+  } else if (category_iids.empty() && category_strings.size() == 1) {
+    category_id = storage->InternString(category_strings[0]);
+  } else if (category_iids.size() + category_strings.size() > 1) {
+    // We concatenate the category strings together since we currently only
+    // support a single "cat" column.
+    // TODO(eseckler): Support multi-category events in the table schema.
+    std::string categories;
+    for (uint64_t iid : category_iids) {
+      auto* decoder = sequence_state->LookupInternedMessage<
+          protos::pbzero::InternedData::kEventCategoriesFieldNumber,
+          protos::pbzero::EventCategory>(sequence_state_generation, iid);
+      if (!decoder)
+        continue;
+      base::StringView name = decoder->name();
+      if (!categories.empty())
+        categories.append(",");
+      categories.append(name.data(), name.size());
+    }
+    for (const protozero::ConstChars& cat : category_strings) {
+      if (!categories.empty())
+        categories.append(",");
+      categories.append(cat.data, cat.size);
+    }
+    if (!categories.empty())
+      category_id = storage->InternString(base::StringView(categories));
+  }
+
+  StringId name_id = 0;
+
+  uint64_t name_iid = event.name_iid();
+  if (!name_iid)
+    name_iid = legacy_event.name_iid();
+
+  if (PERFETTO_LIKELY(name_iid)) {
+    auto* decoder = sequence_state->LookupInternedMessage<
+        protos::pbzero::InternedData::kEventNamesFieldNumber,
+        protos::pbzero::EventName>(sequence_state_generation, name_iid);
+    if (decoder)
+      name_id = storage->InternString(decoder->name());
+  } else if (event.has_name()) {
+    name_id = storage->InternString(event.name());
+  }
+
+  // TODO(eseckler): Also consider track_uuid from TrackEventDefaults.
+  // Fall back to the default descriptor track (uuid 0).
+  uint64_t track_uuid = event.has_track_uuid() ? event.track_uuid() : 0u;
+  TrackId track_id;
+  base::Optional<UniqueTid> utid;
+  base::Optional<UniqueTid> upid;
+
+  // Determine track from track_uuid specified in either TrackEvent or
+  // TrackEventDefaults. If none is set, fall back to the track specified by the
+  // sequence's (or event's) pid + tid or a default track.
+  if (track_uuid) {
+    base::Optional<TrackId> opt_track_id =
+        track_tracker->GetDescriptorTrack(track_uuid);
+    if (!opt_track_id) {
+      storage->IncrementStats(stats::track_event_parser_errors);
+      PERFETTO_DLOG("TrackEvent with unknown track_uuid %" PRIu64, track_uuid);
+      return;
+    }
+    track_id = *opt_track_id;
+
+    auto thread_track_row =
+        context_->storage->thread_track_table().id().IndexOf(
+            SqlValue::Long(track_id));
+    if (thread_track_row) {
+      utid = storage->thread_track_table().utid()[*thread_track_row];
+      upid = storage->GetThread(*utid).upid;
+    } else {
+      auto process_track_row =
+          context_->storage->process_track_table().id().IndexOf(
+              SqlValue::Long(track_id));
+      if (process_track_row)
+        upid = storage->process_track_table().upid()[*process_track_row];
+    }
+  } else if (sequence_state->pid_and_tid_valid() ||
+             (legacy_event.has_pid_override() &&
+              legacy_event.has_tid_override())) {
+    uint32_t pid = static_cast<uint32_t>(sequence_state->pid());
+    uint32_t tid = static_cast<uint32_t>(sequence_state->tid());
+    if (legacy_event.has_pid_override())
+      pid = static_cast<uint32_t>(legacy_event.pid_override());
+    if (legacy_event.has_tid_override())
+      tid = static_cast<uint32_t>(legacy_event.tid_override());
+
+    utid = procs->UpdateThread(tid, pid);
+    upid = storage->GetThread(*utid).upid;
+    track_id = track_tracker->GetOrCreateDescriptorTrackForThread(*utid);
+  } else {
+    track_id = track_tracker->GetOrCreateDefaultDescriptorTrack();
+  }
+
+  // TODO(eseckler): Replace phase with type and remove handling of
+  // legacy_event.phase() once it is no longer used by producers.
+  int32_t phase = 0;
+  if (legacy_event.has_phase()) {
+    phase = legacy_event.phase();
+
+    switch (phase) {
+      case 'b':
+      case 'e':
+      case 'n': {
+        // Intern tracks for legacy async events based on legacy event ids.
+        int64_t source_id = 0;
+        bool source_id_is_process_scoped = false;
+        if (legacy_event.has_unscoped_id()) {
+          source_id = static_cast<int64_t>(legacy_event.unscoped_id());
+        } else if (legacy_event.has_global_id()) {
+          source_id = static_cast<int64_t>(legacy_event.global_id());
+        } else if (legacy_event.has_local_id()) {
+          if (!upid) {
+            storage->IncrementStats(stats::track_event_parser_errors);
+            PERFETTO_DLOG(
+                "TrackEvent with local_id without process association");
+            return;
+          }
+
+          source_id = static_cast<int64_t>(legacy_event.local_id());
+          source_id_is_process_scoped = true;
+        } else {
+          storage->IncrementStats(stats::track_event_parser_errors);
+          PERFETTO_DLOG("Async LegacyEvent without ID");
+          return;
+        }
+
+        // Catapult treats nestable async events of different categories with
+        // the same ID as separate tracks. We replicate the same behavior here.
+        StringId id_scope = category_id;
+        if (legacy_event.has_id_scope()) {
+          std::string concat = storage->GetString(category_id).ToStdString() +
+                               ":" + legacy_event.id_scope().ToStdString();
+          id_scope = storage->InternString(base::StringView(concat));
+        }
+
+        track_id = context_->track_tracker->InternLegacyChromeAsyncTrack(
+            name_id, upid ? *upid : 0, source_id, source_id_is_process_scoped,
+            id_scope);
+        break;
+      }
+      case 'i':
+      case 'I': {
+        // Intern tracks for global or process-scoped legacy instant events.
+        switch (legacy_event.instant_event_scope()) {
+          case LegacyEvent::SCOPE_UNSPECIFIED:
+          case LegacyEvent::SCOPE_THREAD:
+            // Thread-scoped legacy instant events already have the right track
+            // based on the tid/pid of the sequence.
+            if (!utid) {
+              storage->IncrementStats(stats::track_event_parser_errors);
+              PERFETTO_DLOG(
+                  "Thread-scoped instant event without thread association");
+              return;
+            }
+            break;
+          case LegacyEvent::SCOPE_GLOBAL:
+            track_id = context_->track_tracker
+                           ->GetOrCreateLegacyChromeGlobalInstantTrack();
+            break;
+          case LegacyEvent::SCOPE_PROCESS:
+            if (!upid) {
+              storage->IncrementStats(stats::track_event_parser_errors);
+              PERFETTO_DLOG(
+                  "Process-scoped instant event without process association");
+              return;
+            }
+
+            track_id =
+                context_->track_tracker->InternLegacyChromeProcessInstantTrack(
+                    *upid);
+            break;
+        }
+        break;
+      }
+      default:
+        break;
+    }
+  } else {
+    switch (event.type()) {
+      case protos::pbzero::TrackEvent::TYPE_SLICE_BEGIN:
+        phase = utid ? 'B' : 'b';
+        break;
+      case protos::pbzero::TrackEvent::TYPE_SLICE_END:
+        phase = utid ? 'E' : 'e';
+        break;
+      case protos::pbzero::TrackEvent::TYPE_INSTANT:
+        phase = utid ? 'i' : 'n';
+        break;
+      default:
+        PERFETTO_FATAL("unexpected event type %d", event.type());
+        return;
+    }
+  }
+
+  auto args_callback = [this, &event, &legacy_event, &sequence_state,
+                        sequence_state_generation, ts,
+                        utid](ArgsTracker* args_tracker, RowId row_id) {
+    for (auto it = event.debug_annotations(); it; ++it) {
+      ParseDebugAnnotationArgs(*it, sequence_state, sequence_state_generation,
+                               args_tracker, row_id);
+    }
+
+    if (event.has_task_execution()) {
+      ParseTaskExecutionArgs(event.task_execution(), sequence_state,
+                             sequence_state_generation, args_tracker, row_id);
+    }
+
+    if (event.has_log_message()) {
+      ParseLogMessage(event.log_message(), sequence_state,
+                      sequence_state_generation, ts, utid, args_tracker,
+                      row_id);
+    }
+
+    // TODO(eseckler): Parse legacy flow events into flow events table once we
+    // have a design for it.
+    if (legacy_event.has_bind_id()) {
+      args_tracker->AddArg(row_id, legacy_event_bind_id_key_id_,
+                           legacy_event_bind_id_key_id_,
+                           Variadic::UnsignedInteger(legacy_event.bind_id()));
+    }
+
+    if (legacy_event.bind_to_enclosing()) {
+      args_tracker->AddArg(row_id, legacy_event_bind_to_enclosing_key_id_,
+                           legacy_event_bind_to_enclosing_key_id_,
+                           Variadic::Boolean(true));
+    }
+
+    if (legacy_event.flow_direction()) {
+      StringId value;
+      switch (legacy_event.flow_direction()) {
+        case protos::pbzero::TrackEvent::LegacyEvent::FLOW_IN:
+          value = flow_direction_value_in_id_;
+          break;
+        case protos::pbzero::TrackEvent::LegacyEvent::FLOW_OUT:
+          value = flow_direction_value_out_id_;
+          break;
+        case protos::pbzero::TrackEvent::LegacyEvent::FLOW_INOUT:
+          value = flow_direction_value_inout_id_;
+          break;
+        default:
+          PERFETTO_FATAL("Unknown flow direction: %d",
+                         legacy_event.flow_direction());
+          break;
+      }
+      args_tracker->AddArg(row_id, legacy_event_flow_direction_key_id_,
+                           legacy_event_flow_direction_key_id_,
+                           Variadic::String(value));
+    }
+  };
+
+  switch (static_cast<char>(phase)) {
+    case 'B': {  // TRACE_EVENT_PHASE_BEGIN.
+      if (!utid) {
+        storage->IncrementStats(stats::track_event_parser_errors);
+        PERFETTO_DLOG("TrackEvent with phase B without thread association");
+        return;
+      }
+
+      auto opt_slice_id =
+          slice_tracker->Begin(ts, track_id, *utid, RefType::kRefUtid,
+                               category_id, name_id, args_callback);
+      if (opt_slice_id.has_value()) {
+        auto* thread_slices = storage->mutable_thread_slices();
+        PERFETTO_DCHECK(!thread_slices->slice_count() ||
+                        thread_slices->slice_ids().back() <
+                            opt_slice_id.value());
+        thread_slices->AddThreadSlice(opt_slice_id.value(), tts,
+                                      kPendingThreadDuration, ticount,
+                                      kPendingThreadInstructionDelta);
+      }
+      break;
+    }
+    case 'E': {  // TRACE_EVENT_PHASE_END.
+      if (!utid) {
+        storage->IncrementStats(stats::track_event_parser_errors);
+        PERFETTO_DLOG("TrackEvent with phase E without thread association");
+        return;
+      }
+
+      auto opt_slice_id =
+          slice_tracker->End(ts, track_id, category_id, name_id, args_callback);
+      if (opt_slice_id.has_value()) {
+        auto* thread_slices = storage->mutable_thread_slices();
+        thread_slices->UpdateThreadDeltasForSliceId(opt_slice_id.value(), tts,
+                                                    ticount);
+      }
+      break;
+    }
+    case 'X': {  // TRACE_EVENT_PHASE_COMPLETE.
+      if (!utid) {
+        storage->IncrementStats(stats::track_event_parser_errors);
+        PERFETTO_DLOG("TrackEvent with phase X without thread association");
+        return;
+      }
+
+      auto duration_ns = legacy_event.duration_us() * 1000;
+      if (duration_ns < 0)
+        return;
+      auto opt_slice_id = slice_tracker->Scoped(
+          ts, track_id, *utid, RefType::kRefUtid, category_id, name_id,
+          duration_ns, args_callback);
+      if (opt_slice_id.has_value()) {
+        auto* thread_slices = storage->mutable_thread_slices();
+        PERFETTO_DCHECK(!thread_slices->slice_count() ||
+                        thread_slices->slice_ids().back() <
+                            opt_slice_id.value());
+        auto thread_duration_ns = legacy_event.thread_duration_us() * 1000;
+        thread_slices->AddThreadSlice(opt_slice_id.value(), tts,
+                                      thread_duration_ns, ticount,
+                                      legacy_event.thread_instruction_delta());
+      }
+      break;
+    }
+    case 'i':
+    case 'I': {  // TRACE_EVENT_PHASE_INSTANT.
+      // Handle instant events as slices with zero duration, so that they end
+      // up nested underneath their parent slices.
+      int64_t duration_ns = 0;
+      int64_t tidelta = 0;
+
+      switch (legacy_event.instant_event_scope()) {
+        case LegacyEvent::SCOPE_UNSPECIFIED:
+        case LegacyEvent::SCOPE_THREAD: {
+          // TODO(lalitm): Associate thread slices with track instead.
+          auto opt_slice_id = slice_tracker->Scoped(
+              ts, track_id, *utid, RefType::kRefUtid, category_id, name_id,
+              duration_ns, args_callback);
+          if (opt_slice_id.has_value()) {
+            auto* thread_slices = storage->mutable_thread_slices();
+            PERFETTO_DCHECK(!thread_slices->slice_count() ||
+                            thread_slices->slice_ids().back() <
+                                opt_slice_id.value());
+            thread_slices->AddThreadSlice(opt_slice_id.value(), tts,
+                                          duration_ns, ticount, tidelta);
+          }
+          break;
+        }
+        case LegacyEvent::SCOPE_GLOBAL: {
+          slice_tracker->Scoped(ts, track_id, /*ref=*/0, RefType::kRefNoRef,
+                                category_id, name_id, duration_ns,
+                                args_callback);
+          break;
+        }
+        case LegacyEvent::SCOPE_PROCESS: {
+          slice_tracker->Scoped(ts, track_id, *upid, RefType::kRefUpid,
+                                category_id, name_id, duration_ns,
+                                args_callback);
+          break;
+        }
+        default: {
+          PERFETTO_FATAL("Unknown instant event scope: %u",
+                         legacy_event.instant_event_scope());
+          break;
+        }
+      }
+      break;
+    }
+    case 'b': {  // TRACE_EVENT_PHASE_NESTABLE_ASYNC_BEGIN
+      auto opt_slice_id =
+          slice_tracker->Begin(ts, track_id, track_id, RefType::kRefTrack,
+                               category_id, name_id, args_callback);
+      // For the time beeing, we only create vtrack slice rows if we need to
+      // store thread timestamps/counters.
+      if (legacy_event.use_async_tts() && opt_slice_id.has_value()) {
+        auto* vtrack_slices = storage->mutable_virtual_track_slices();
+        PERFETTO_DCHECK(!vtrack_slices->slice_count() ||
+                        vtrack_slices->slice_ids().back() <
+                            opt_slice_id.value());
+        vtrack_slices->AddVirtualTrackSlice(opt_slice_id.value(), tts,
+                                            kPendingThreadDuration, ticount,
+                                            kPendingThreadInstructionDelta);
+      }
+      break;
+    }
+    case 'e': {  // TRACE_EVENT_PHASE_NESTABLE_ASYNC_END
+      auto opt_slice_id =
+          slice_tracker->End(ts, track_id, category_id, name_id, args_callback);
+      if (legacy_event.use_async_tts() && opt_slice_id.has_value()) {
+        auto* vtrack_slices = storage->mutable_virtual_track_slices();
+        vtrack_slices->UpdateThreadDeltasForSliceId(opt_slice_id.value(), tts,
+                                                    ticount);
+      }
+      break;
+    }
+    case 'n': {  // TRACE_EVENT_PHASE_NESTABLE_ASYNC_INSTANT
+      // Handle instant events as slices with zero duration, so that they end up
+      // nested underneath their parent slices.
+      int64_t duration_ns = 0;
+      int64_t tidelta = 0;
+      auto opt_slice_id = slice_tracker->Scoped(
+          ts, track_id, track_id, RefType::kRefTrack, category_id, name_id,
+          duration_ns, args_callback);
+      if (legacy_event.use_async_tts() && opt_slice_id.has_value()) {
+        auto* vtrack_slices = storage->mutable_virtual_track_slices();
+        PERFETTO_DCHECK(!vtrack_slices->slice_count() ||
+                        vtrack_slices->slice_ids().back() <
+                            opt_slice_id.value());
+        vtrack_slices->AddVirtualTrackSlice(opt_slice_id.value(), tts,
+                                            duration_ns, ticount, tidelta);
+      }
+      break;
+    }
+    case 'M': {  // TRACE_EVENT_PHASE_METADATA (process and thread names).
+      // Parse process and thread names from correspondingly named events.
+      // TODO(eseckler): Also consider names from process/thread descriptors.
+      NullTermStringView event_name = storage->GetString(name_id);
+      PERFETTO_DCHECK(event_name.data());
+      if (strcmp(event_name.c_str(), "thread_name") == 0) {
+        if (!utid) {
+          storage->IncrementStats(stats::track_event_parser_errors);
+          PERFETTO_DLOG(
+              "thread_name metadata event without thread association");
+          return;
+        }
+
+        auto it = event.debug_annotations();
+        if (!it)
+          break;
+        protos::pbzero::DebugAnnotation::Decoder annotation(*it);
+        auto thread_name = annotation.string_value();
+        if (!thread_name.size)
+          break;
+        auto thread_name_id = storage->InternString(thread_name);
+        procs->UpdateThreadName(storage->GetThread(*utid).tid, thread_name_id);
+        break;
+      }
+      if (strcmp(event_name.c_str(), "process_name") == 0) {
+        if (!upid) {
+          storage->IncrementStats(stats::track_event_parser_errors);
+          PERFETTO_DLOG(
+              "process_name metadata event without process association");
+          return;
+        }
+
+        auto it = event.debug_annotations();
+        if (!it)
+          break;
+        protos::pbzero::DebugAnnotation::Decoder annotation(*it);
+        auto process_name = annotation.string_value();
+        if (!process_name.size)
+          break;
+        procs->SetProcessMetadata(storage->GetProcess(*upid).pid, base::nullopt,
+                                  process_name);
+        break;
+      }
+      // Other metadata events are proxied via the raw table for JSON export.
+      ParseLegacyEventAsRawEvent(ts, tts, ticount, utid, category_id, name_id,
+                                 legacy_event, args_callback);
+      break;
+    }
+    default: {
+      // Other events are proxied via the raw table for JSON export.
+      ParseLegacyEventAsRawEvent(ts, tts, ticount, utid, category_id, name_id,
+                                 legacy_event, args_callback);
+    }
+  }
+}
+
+void TrackEventParser::ParseLegacyEventAsRawEvent(
+    int64_t ts,
+    int64_t tts,
+    int64_t ticount,
+    base::Optional<UniqueTid> utid,
+    StringId category_id,
+    StringId name_id,
+    const protos::pbzero::TrackEvent::LegacyEvent::Decoder& legacy_event,
+    SliceTracker::SetArgsCallback args_callback) {
+  if (!utid) {
+    context_->storage->IncrementStats(stats::track_event_parser_errors);
+    PERFETTO_DLOG("raw legacy event without thread association");
+    return;
+  }
+
+  RowId row_id = context_->storage->mutable_raw_events()->AddRawEvent(
+      ts, raw_legacy_event_id_, 0, *utid);
+  ArgsTracker args(context_);
+  args.AddArg(row_id, legacy_event_category_key_id_,
+              legacy_event_category_key_id_, Variadic::String(category_id));
+  args.AddArg(row_id, legacy_event_name_key_id_, legacy_event_name_key_id_,
+              Variadic::String(name_id));
+
+  std::string phase_string(1, static_cast<char>(legacy_event.phase()));
+  StringId phase_id = context_->storage->InternString(phase_string.c_str());
+  args.AddArg(row_id, legacy_event_phase_key_id_, legacy_event_phase_key_id_,
+              Variadic::String(phase_id));
+
+  if (legacy_event.has_duration_us()) {
+    args.AddArg(row_id, legacy_event_duration_ns_key_id_,
+                legacy_event_duration_ns_key_id_,
+                Variadic::Integer(legacy_event.duration_us() * 1000));
+  }
+
+  if (tts) {
+    args.AddArg(row_id, legacy_event_thread_timestamp_ns_key_id_,
+                legacy_event_thread_timestamp_ns_key_id_,
+                Variadic::Integer(tts));
+    if (legacy_event.has_thread_duration_us()) {
+      args.AddArg(row_id, legacy_event_thread_duration_ns_key_id_,
+                  legacy_event_thread_duration_ns_key_id_,
+                  Variadic::Integer(legacy_event.thread_duration_us() * 1000));
+    }
+  }
+
+  if (ticount) {
+    args.AddArg(row_id, legacy_event_thread_instruction_count_key_id_,
+                legacy_event_thread_instruction_count_key_id_,
+                Variadic::Integer(tts));
+    if (legacy_event.has_thread_instruction_delta()) {
+      args.AddArg(row_id, legacy_event_thread_instruction_delta_key_id_,
+                  legacy_event_thread_instruction_delta_key_id_,
+                  Variadic::Integer(legacy_event.thread_instruction_delta()));
+    }
+  }
+
+  if (legacy_event.use_async_tts()) {
+    args.AddArg(row_id, legacy_event_use_async_tts_key_id_,
+                legacy_event_use_async_tts_key_id_, Variadic::Boolean(true));
+  }
+
+  bool has_id = false;
+  if (legacy_event.has_unscoped_id()) {
+    // Unscoped ids are either global or local depending on the phase. Pass them
+    // through as unscoped IDs to JSON export to preserve this behavior.
+    args.AddArg(row_id, legacy_event_unscoped_id_key_id_,
+                legacy_event_unscoped_id_key_id_,
+                Variadic::UnsignedInteger(legacy_event.unscoped_id()));
+    has_id = true;
+  } else if (legacy_event.has_global_id()) {
+    args.AddArg(row_id, legacy_event_global_id_key_id_,
+                legacy_event_global_id_key_id_,
+                Variadic::UnsignedInteger(legacy_event.global_id()));
+    has_id = true;
+  } else if (legacy_event.has_local_id()) {
+    args.AddArg(row_id, legacy_event_local_id_key_id_,
+                legacy_event_local_id_key_id_,
+                Variadic::UnsignedInteger(legacy_event.local_id()));
+    has_id = true;
+  }
+
+  if (has_id && legacy_event.has_id_scope() && legacy_event.id_scope().size) {
+    args.AddArg(row_id, legacy_event_id_scope_key_id_,
+                legacy_event_id_scope_key_id_,
+                Variadic::String(
+                    context_->storage->InternString(legacy_event.id_scope())));
+  }
+
+  // No need to parse legacy_event.instant_event_scope() because we import
+  // instant events into the slice table.
+
+  args_callback(&args, row_id);
+}
+
+void TrackEventParser::ParseDebugAnnotationArgs(
+    ConstBytes debug_annotation,
+    PacketSequenceState* sequence_state,
+    size_t sequence_state_generation,
+    ArgsTracker* args_tracker,
+    RowId row_id) {
+  TraceStorage* storage = context_->storage.get();
+
+  protos::pbzero::DebugAnnotation::Decoder annotation(debug_annotation.data,
+                                                      debug_annotation.size);
+
+  StringId name_id = 0;
+
+  uint64_t name_iid = annotation.name_iid();
+  if (PERFETTO_LIKELY(name_iid)) {
+    auto* decoder = sequence_state->LookupInternedMessage<
+        protos::pbzero::InternedData::kDebugAnnotationNamesFieldNumber,
+        protos::pbzero::DebugAnnotationName>(sequence_state_generation,
+                                             name_iid);
+    if (!decoder)
+      return;
+
+    std::string name_prefixed = "debug." + decoder->name().ToStdString();
+    name_id = storage->InternString(base::StringView(name_prefixed));
+  } else if (annotation.has_name()) {
+    name_id = storage->InternString(annotation.name());
+  } else {
+    context_->storage->IncrementStats(stats::track_event_parser_errors);
+    PERFETTO_DLOG("Debug annotation without name");
+    return;
+  }
+
+  if (annotation.has_bool_value()) {
+    args_tracker->AddArg(row_id, name_id, name_id,
+                         Variadic::Boolean(annotation.bool_value()));
+  } else if (annotation.has_uint_value()) {
+    args_tracker->AddArg(row_id, name_id, name_id,
+                         Variadic::UnsignedInteger(annotation.uint_value()));
+  } else if (annotation.has_int_value()) {
+    args_tracker->AddArg(row_id, name_id, name_id,
+                         Variadic::Integer(annotation.int_value()));
+  } else if (annotation.has_double_value()) {
+    args_tracker->AddArg(row_id, name_id, name_id,
+                         Variadic::Real(annotation.double_value()));
+  } else if (annotation.has_string_value()) {
+    args_tracker->AddArg(
+        row_id, name_id, name_id,
+        Variadic::String(storage->InternString(annotation.string_value())));
+  } else if (annotation.has_pointer_value()) {
+    args_tracker->AddArg(row_id, name_id, name_id,
+                         Variadic::Pointer(annotation.pointer_value()));
+  } else if (annotation.has_legacy_json_value()) {
+    args_tracker->AddArg(
+        row_id, name_id, name_id,
+        Variadic::Json(storage->InternString(annotation.legacy_json_value())));
+  } else if (annotation.has_nested_value()) {
+    auto name = storage->GetString(name_id);
+    ParseNestedValueArgs(annotation.nested_value(), name, name, args_tracker,
+                         row_id);
+  }
+}
+
+void TrackEventParser::ParseNestedValueArgs(ConstBytes nested_value,
+                                            base::StringView flat_key,
+                                            base::StringView key,
+                                            ArgsTracker* args_tracker,
+                                            RowId row_id) {
+  protos::pbzero::DebugAnnotation::NestedValue::Decoder value(
+      nested_value.data, nested_value.size);
+  switch (value.nested_type()) {
+    case protos::pbzero::DebugAnnotation::NestedValue::UNSPECIFIED: {
+      auto flat_key_id = context_->storage->InternString(flat_key);
+      auto key_id = context_->storage->InternString(key);
+      // Leaf value.
+      if (value.has_bool_value()) {
+        args_tracker->AddArg(row_id, flat_key_id, key_id,
+                             Variadic::Boolean(value.bool_value()));
+      } else if (value.has_int_value()) {
+        args_tracker->AddArg(row_id, flat_key_id, key_id,
+                             Variadic::Integer(value.int_value()));
+      } else if (value.has_double_value()) {
+        args_tracker->AddArg(row_id, flat_key_id, key_id,
+                             Variadic::Real(value.double_value()));
+      } else if (value.has_string_value()) {
+        args_tracker->AddArg(row_id, flat_key_id, key_id,
+                             Variadic::String(context_->storage->InternString(
+                                 value.string_value())));
+      }
+      break;
+    }
+    case protos::pbzero::DebugAnnotation::NestedValue::DICT: {
+      auto key_it = value.dict_keys();
+      auto value_it = value.dict_values();
+      for (; key_it && value_it; ++key_it, ++value_it) {
+        std::string child_name = (*key_it).ToStdString();
+        std::string child_flat_key = flat_key.ToStdString() + "." + child_name;
+        std::string child_key = key.ToStdString() + "." + child_name;
+        ParseNestedValueArgs(*value_it, base::StringView(child_flat_key),
+                             base::StringView(child_key), args_tracker, row_id);
+      }
+      break;
+    }
+    case protos::pbzero::DebugAnnotation::NestedValue::ARRAY: {
+      int child_index = 0;
+      std::string child_flat_key = flat_key.ToStdString();
+      for (auto value_it = value.array_values(); value_it;
+           ++value_it, ++child_index) {
+        std::string child_key =
+            key.ToStdString() + "[" + std::to_string(child_index) + "]";
+        ParseNestedValueArgs(*value_it, base::StringView(child_flat_key),
+                             base::StringView(child_key), args_tracker, row_id);
+      }
+      break;
+    }
+  }
+}
+
+void TrackEventParser::ParseTaskExecutionArgs(
+    ConstBytes task_execution,
+    PacketSequenceState* sequence_state,
+    size_t sequence_state_generation,
+    ArgsTracker* args_tracker,
+    RowId row) {
+  protos::pbzero::TaskExecution::Decoder task(task_execution.data,
+                                              task_execution.size);
+  uint64_t iid = task.posted_from_iid();
+  if (!iid)
+    return;
+
+  auto* decoder = sequence_state->LookupInternedMessage<
+      protos::pbzero::InternedData::kSourceLocationsFieldNumber,
+      protos::pbzero::SourceLocation>(sequence_state_generation, iid);
+  if (!decoder)
+    return;
+
+  StringId file_name_id = 0;
+  StringId function_name_id = 0;
+  uint32_t line_number = 0;
+
+  TraceStorage* storage = context_->storage.get();
+  file_name_id = storage->InternString(decoder->file_name());
+  function_name_id = storage->InternString(decoder->function_name());
+  line_number = decoder->line_number();
+
+  args_tracker->AddArg(row, task_file_name_args_key_id_,
+                       task_file_name_args_key_id_,
+                       Variadic::String(file_name_id));
+  args_tracker->AddArg(row, task_function_name_args_key_id_,
+                       task_function_name_args_key_id_,
+                       Variadic::String(function_name_id));
+
+  args_tracker->AddArg(row, task_line_number_args_key_id_,
+                       task_line_number_args_key_id_,
+                       Variadic::UnsignedInteger(line_number));
+}
+
+void TrackEventParser::ParseLogMessage(ConstBytes blob,
+                                       PacketSequenceState* sequence_state,
+                                       size_t sequence_state_generation,
+                                       int64_t ts,
+                                       base::Optional<UniqueTid> utid,
+                                       ArgsTracker* args_tracker,
+                                       RowId row) {
+  if (!utid) {
+    context_->storage->IncrementStats(stats::track_event_parser_errors);
+    PERFETTO_DLOG("LogMessage without thread association");
+    return;
+  }
+
+  protos::pbzero::LogMessage::Decoder message(blob.data, blob.size);
+
+  TraceStorage* storage = context_->storage.get();
+
+  StringId log_message_id = 0;
+
+  auto* decoder = sequence_state->LookupInternedMessage<
+      protos::pbzero::InternedData::kLogMessageBodyFieldNumber,
+      protos::pbzero::LogMessageBody>(sequence_state_generation,
+                                      message.body_iid());
+  if (!decoder)
+    return;
+
+  log_message_id = storage->InternString(decoder->body());
+
+  // TODO(nicomazz): LogMessage also contains the source of the message (file
+  // and line number). Android logs doesn't support this so far.
+  context_->storage->mutable_android_log()->AddLogEvent(
+      ts, *utid,
+      /*priority*/ 0,
+      /*tag_id*/ 0,  // TODO(nicomazz): Abuse tag_id to display
+                     // "file_name:line_number".
+      log_message_id);
+
+  args_tracker->AddArg(row, log_message_body_key_id_, log_message_body_key_id_,
+                       Variadic::String(log_message_id));
+  // TODO(nicomazz): Add the source location as an argument.
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/proto/track_event_parser.h b/src/trace_processor/importers/proto/track_event_parser.h
new file mode 100644
index 0000000..7e39eb1
--- /dev/null
+++ b/src/trace_processor/importers/proto/track_event_parser.h
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_TRACK_EVENT_PARSER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_TRACK_EVENT_PARSER_H_
+
+#include "src/trace_processor/slice_tracker.h"
+#include "src/trace_processor/trace_storage.h"
+
+#include "protos/perfetto/trace/track_event/track_event.pbzero.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+class PacketSequenceState;
+class TraceProcessorContext;
+
+class TrackEventParser {
+ public:
+  explicit TrackEventParser(TraceProcessorContext* context);
+
+  void ParseTrackEvent(int64_t ts,
+                       int64_t tts,
+                       int64_t ticount,
+                       PacketSequenceState*,
+                       size_t sequence_state_generation,
+                       protozero::ConstBytes);
+  void ParseLegacyEventAsRawEvent(
+      int64_t ts,
+      int64_t tts,
+      int64_t ticount,
+      base::Optional<UniqueTid> utid,
+      StringId category_id,
+      StringId name_id,
+      const protos::pbzero::TrackEvent::LegacyEvent::Decoder& legacy_event,
+      SliceTracker::SetArgsCallback args_callback);
+  void ParseDebugAnnotationArgs(protozero::ConstBytes debug_annotation,
+                                PacketSequenceState*,
+                                size_t sequence_state_generation,
+                                ArgsTracker* args_tracker,
+                                RowId row);
+  void ParseNestedValueArgs(protozero::ConstBytes nested_value,
+                            base::StringView flat_key,
+                            base::StringView key,
+                            ArgsTracker* args_tracker,
+                            RowId row);
+  void ParseTaskExecutionArgs(protozero::ConstBytes task_execution,
+                              PacketSequenceState*,
+                              size_t sequence_state_generation,
+                              ArgsTracker* args_tracker,
+                              RowId row);
+  void ParseLogMessage(protozero::ConstBytes,
+                       PacketSequenceState*,
+                       size_t sequence_state_generation,
+                       int64_t,
+                       base::Optional<UniqueTid>,
+                       ArgsTracker*,
+                       RowId);
+
+ private:
+  TraceProcessorContext* context_;
+
+  const StringId task_file_name_args_key_id_;
+  const StringId task_function_name_args_key_id_;
+  const StringId task_line_number_args_key_id_;
+  const StringId log_message_body_key_id_;
+  const StringId raw_legacy_event_id_;
+  const StringId legacy_event_category_key_id_;
+  const StringId legacy_event_name_key_id_;
+  const StringId legacy_event_phase_key_id_;
+  const StringId legacy_event_duration_ns_key_id_;
+  const StringId legacy_event_thread_timestamp_ns_key_id_;
+  const StringId legacy_event_thread_duration_ns_key_id_;
+  const StringId legacy_event_thread_instruction_count_key_id_;
+  const StringId legacy_event_thread_instruction_delta_key_id_;
+  const StringId legacy_event_use_async_tts_key_id_;
+  const StringId legacy_event_unscoped_id_key_id_;
+  const StringId legacy_event_global_id_key_id_;
+  const StringId legacy_event_local_id_key_id_;
+  const StringId legacy_event_id_scope_key_id_;
+  const StringId legacy_event_bind_id_key_id_;
+  const StringId legacy_event_bind_to_enclosing_key_id_;
+  const StringId legacy_event_flow_direction_key_id_;
+  const StringId flow_direction_value_in_id_;
+  const StringId flow_direction_value_out_id_;
+  const StringId flow_direction_value_inout_id_;
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_TRACK_EVENT_PARSER_H_
diff --git a/src/trace_processor/importers/proto/track_event_tokenizer.cc b/src/trace_processor/importers/proto/track_event_tokenizer.cc
new file mode 100644
index 0000000..15ba499
--- /dev/null
+++ b/src/trace_processor/importers/proto/track_event_tokenizer.cc
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_processor/importers/proto/track_event_tokenizer.h"
+
+#include "perfetto/base/logging.h"
+#include "perfetto/protozero/proto_decoder.h"
+#include "src/trace_processor/clock_tracker.h"
+#include "src/trace_processor/importers/proto/packet_sequence_state.h"
+#include "src/trace_processor/process_tracker.h"
+#include "src/trace_processor/proto_trace_tokenizer.h"
+#include "src/trace_processor/stats.h"
+#include "src/trace_processor/trace_blob_view.h"
+#include "src/trace_processor/trace_sorter.h"
+#include "src/trace_processor/trace_storage.h"
+#include "src/trace_processor/track_tracker.h"
+
+#include "protos/perfetto/trace/clock_snapshot.pbzero.h"
+#include "protos/perfetto/trace/track_event/process_descriptor.pbzero.h"
+#include "protos/perfetto/trace/track_event/thread_descriptor.pbzero.h"
+#include "protos/perfetto/trace/track_event/track_descriptor.pbzero.h"
+#include "protos/perfetto/trace/track_event/track_event.pbzero.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+void TrackEventTokenizer::TokenizeTrackDescriptorPacket(
+    const protos::pbzero::TracePacket::Decoder& packet_decoder) {
+  auto track_descriptor_field = packet_decoder.track_descriptor();
+  protos::pbzero::TrackDescriptor::Decoder track_descriptor_decoder(
+      track_descriptor_field.data, track_descriptor_field.size);
+
+  if (!track_descriptor_decoder.has_uuid()) {
+    PERFETTO_ELOG("TrackDescriptor packet without trusted_packet_sequence_id");
+    context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
+    return;
+  }
+
+  base::Optional<UniquePid> upid;
+  base::Optional<UniqueTid> utid;
+
+  if (track_descriptor_decoder.has_process()) {
+    auto process_descriptor_field = track_descriptor_decoder.process();
+    protos::pbzero::ProcessDescriptor::Decoder process_descriptor_decoder(
+        process_descriptor_field.data, process_descriptor_field.size);
+
+    // TODO(eseckler): Also parse process name / type here.
+
+    upid = context_->process_tracker->GetOrCreateProcess(
+        static_cast<uint32_t>(process_descriptor_decoder.pid()));
+  }
+
+  if (track_descriptor_decoder.has_thread()) {
+    auto thread_descriptor_field = track_descriptor_decoder.thread();
+    protos::pbzero::ThreadDescriptor::Decoder thread_descriptor_decoder(
+        thread_descriptor_field.data, thread_descriptor_field.size);
+
+    TokenizeThreadDescriptor(thread_descriptor_decoder);
+    utid = context_->process_tracker->UpdateThread(
+        static_cast<uint32_t>(thread_descriptor_decoder.tid()),
+        static_cast<uint32_t>(thread_descriptor_decoder.pid()));
+    upid = *context_->storage->GetThread(*utid).upid;
+  }
+
+  StringId name_id =
+      context_->storage->InternString(track_descriptor_decoder.name());
+
+  context_->track_tracker->UpdateDescriptorTrack(
+      track_descriptor_decoder.uuid(), name_id, upid, utid);
+}
+
+void TrackEventTokenizer::TokenizeProcessDescriptorPacket(
+    const protos::pbzero::TracePacket::Decoder& packet_decoder) {
+  protos::pbzero::ProcessDescriptor::Decoder process_descriptor_decoder(
+      packet_decoder.process_descriptor());
+  if (!process_descriptor_decoder.has_chrome_process_type())
+    return;
+  base::StringView name = "Unknown";
+  switch (process_descriptor_decoder.chrome_process_type()) {
+    case protos::pbzero::ProcessDescriptor::PROCESS_BROWSER:
+      name = "Browser";
+      break;
+    case protos::pbzero::ProcessDescriptor::PROCESS_RENDERER:
+      name = "Renderer";
+      break;
+    case protos::pbzero::ProcessDescriptor::PROCESS_UTILITY:
+      name = "Utility";
+      break;
+    case protos::pbzero::ProcessDescriptor::PROCESS_ZYGOTE:
+      name = "Zygote";
+      break;
+    case protos::pbzero::ProcessDescriptor::PROCESS_SANDBOX_HELPER:
+      name = "SandboxHelper";
+      break;
+    case protos::pbzero::ProcessDescriptor::PROCESS_GPU:
+      name = "Gpu";
+      break;
+    case protos::pbzero::ProcessDescriptor::PROCESS_PPAPI_PLUGIN:
+      name = "PpapiPlugin";
+      break;
+    case protos::pbzero::ProcessDescriptor::PROCESS_PPAPI_BROKER:
+      name = "PpapiBroker";
+      break;
+  }
+  context_->process_tracker->SetProcessMetadata(
+      static_cast<uint32_t>(process_descriptor_decoder.pid()), base::nullopt,
+      name);
+}
+
+void TrackEventTokenizer::TokenizeThreadDescriptorPacket(
+    PacketSequenceState* state,
+    const protos::pbzero::TracePacket::Decoder& packet_decoder) {
+  if (PERFETTO_UNLIKELY(!packet_decoder.has_trusted_packet_sequence_id())) {
+    PERFETTO_ELOG("ThreadDescriptor packet without trusted_packet_sequence_id");
+    context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
+    return;
+  }
+
+  // TrackEvents will be ignored while incremental state is invalid. As a
+  // consequence, we should also ignore any ThreadDescriptors received in this
+  // state. Otherwise, any delta-encoded timestamps would be calculated
+  // incorrectly once we move out of the packet loss state. Instead, wait until
+  // the first subsequent descriptor after incremental state is cleared.
+  if (!state->IsIncrementalStateValid()) {
+    context_->storage->IncrementStats(stats::tokenizer_skipped_packets);
+    return;
+  }
+
+  auto thread_descriptor_field = packet_decoder.thread_descriptor();
+  protos::pbzero::ThreadDescriptor::Decoder thread_descriptor_decoder(
+      thread_descriptor_field.data, thread_descriptor_field.size);
+
+  state->SetThreadDescriptor(
+      thread_descriptor_decoder.pid(), thread_descriptor_decoder.tid(),
+      thread_descriptor_decoder.reference_timestamp_us() * 1000,
+      thread_descriptor_decoder.reference_thread_time_us() * 1000,
+      thread_descriptor_decoder.reference_thread_instruction_count());
+
+  TokenizeThreadDescriptor(thread_descriptor_decoder);
+}
+
+void TrackEventTokenizer::TokenizeThreadDescriptor(
+    const protos::pbzero::ThreadDescriptor::Decoder&
+        thread_descriptor_decoder) {
+  base::StringView name;
+  if (thread_descriptor_decoder.has_thread_name()) {
+    name = thread_descriptor_decoder.thread_name();
+  } else if (thread_descriptor_decoder.has_chrome_thread_type()) {
+    using protos::pbzero::ThreadDescriptor;
+    switch (thread_descriptor_decoder.chrome_thread_type()) {
+      case ThreadDescriptor::CHROME_THREAD_MAIN:
+        name = "CrProcessMain";
+        break;
+      case ThreadDescriptor::CHROME_THREAD_IO:
+        name = "ChromeIOThread";
+        break;
+      case ThreadDescriptor::CHROME_THREAD_POOL_FG_WORKER:
+        name = "ThreadPoolForegroundWorker&";
+        break;
+      case ThreadDescriptor::CHROME_THREAD_POOL_BG_WORKER:
+        name = "ThreadPoolBackgroundWorker&";
+        break;
+      case ThreadDescriptor::CHROME_THREAD_POOL_FB_BLOCKING:
+        name = "ThreadPoolSingleThreadForegroundBlocking&";
+        break;
+      case ThreadDescriptor::CHROME_THREAD_POOL_BG_BLOCKING:
+        name = "ThreadPoolSingleThreadBackgroundBlocking&";
+        break;
+      case ThreadDescriptor::CHROME_THREAD_POOL_SERVICE:
+        name = "ThreadPoolService";
+        break;
+      case ThreadDescriptor::CHROME_THREAD_COMPOSITOR_WORKER:
+        name = "CompositorTileWorker&";
+        break;
+      case ThreadDescriptor::CHROME_THREAD_COMPOSITOR:
+        name = "Compositor";
+        break;
+      case ThreadDescriptor::CHROME_THREAD_VIZ_COMPOSITOR:
+        name = "VizCompositorThread";
+        break;
+      case ThreadDescriptor::CHROME_THREAD_SERVICE_WORKER:
+        name = "ServiceWorkerThread&";
+        break;
+      case ThreadDescriptor::CHROME_THREAD_MEMORY_INFRA:
+        name = "MemoryInfra";
+        break;
+      case ThreadDescriptor::CHROME_THREAD_SAMPLING_PROFILER:
+        name = "StackSamplingProfiler";
+        break;
+      case ThreadDescriptor::CHROME_THREAD_UNSPECIFIED:
+        name = "ChromeUnspecified";
+        break;
+    }
+  }
+
+  if (!name.empty()) {
+    auto thread_name_id = context_->storage->InternString(name);
+    ProcessTracker* procs = context_->process_tracker.get();
+    procs->UpdateThreadName(
+        static_cast<uint32_t>(thread_descriptor_decoder.tid()), thread_name_id);
+  }
+}
+
+void TrackEventTokenizer::TokenizeTrackEventPacket(
+    PacketSequenceState* state,
+    const protos::pbzero::TracePacket::Decoder& packet_decoder,
+    TraceBlobView* packet,
+    int64_t packet_timestamp) {
+  constexpr auto kTimestampDeltaUsFieldNumber =
+      protos::pbzero::TrackEvent::kTimestampDeltaUsFieldNumber;
+  constexpr auto kTimestampAbsoluteUsFieldNumber =
+      protos::pbzero::TrackEvent::kTimestampAbsoluteUsFieldNumber;
+  constexpr auto kThreadTimeDeltaUsFieldNumber =
+      protos::pbzero::TrackEvent::kThreadTimeDeltaUsFieldNumber;
+  constexpr auto kThreadTimeAbsoluteUsFieldNumber =
+      protos::pbzero::TrackEvent::kThreadTimeAbsoluteUsFieldNumber;
+  constexpr auto kThreadInstructionCountDeltaFieldNumber =
+      protos::pbzero::TrackEvent::kThreadInstructionCountDeltaFieldNumber;
+  constexpr auto kThreadInstructionCountAbsoluteFieldNumber =
+      protos::pbzero::TrackEvent::kThreadInstructionCountAbsoluteFieldNumber;
+
+  if (PERFETTO_UNLIKELY(!packet_decoder.has_trusted_packet_sequence_id())) {
+    PERFETTO_ELOG("TrackEvent packet without trusted_packet_sequence_id");
+    context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
+    return;
+  }
+
+  // TODO(eseckler): For now, TrackEvents can only be parsed correctly while
+  // incremental state for their sequence is valid, because chromium doesn't set
+  // SEQ_NEEDS_INCREMENTAL_STATE yet. Remove this once it does.
+  if (!state->IsIncrementalStateValid()) {
+    context_->storage->IncrementStats(stats::tokenizer_skipped_packets);
+    return;
+  }
+
+  auto field = packet_decoder.track_event();
+  protozero::ProtoDecoder event_decoder(field.data, field.size);
+
+  int64_t timestamp;
+  int64_t thread_timestamp = 0;
+  int64_t thread_instructions = 0;
+
+  // TODO(eseckler): Remove handling of timestamps relative to ThreadDescriptors
+  // once all producers have switched to clock-domain timestamps (e.g.
+  // TracePacket's timestamp).
+
+  if (auto ts_delta_field =
+          event_decoder.FindField(kTimestampDeltaUsFieldNumber)) {
+    // Delta timestamps require a valid ThreadDescriptor packet since the last
+    // packet loss.
+    if (!state->track_event_timestamps_valid()) {
+      context_->storage->IncrementStats(stats::tokenizer_skipped_packets);
+      return;
+    }
+    timestamp = state->IncrementAndGetTrackEventTimeNs(
+        ts_delta_field.as_int64() * 1000);
+
+    // Legacy TrackEvent timestamp fields are in MONOTONIC domain. Adjust to
+    // trace time if we have a clock snapshot.
+    auto trace_ts = context_->clock_tracker->ToTraceTime(
+        protos::pbzero::ClockSnapshot::Clock::MONOTONIC, timestamp);
+    if (trace_ts.has_value())
+      timestamp = trace_ts.value();
+  } else if (auto ts_absolute_field =
+                 event_decoder.FindField(kTimestampAbsoluteUsFieldNumber)) {
+    // One-off absolute timestamps don't affect delta computation.
+    timestamp = ts_absolute_field.as_int64() * 1000;
+
+    // Legacy TrackEvent timestamp fields are in MONOTONIC domain. Adjust to
+    // trace time if we have a clock snapshot.
+    auto trace_ts = context_->clock_tracker->ToTraceTime(
+        protos::pbzero::ClockSnapshot::Clock::MONOTONIC, timestamp);
+    if (trace_ts.has_value())
+      timestamp = trace_ts.value();
+  } else if (packet_decoder.has_timestamp()) {
+    timestamp = packet_timestamp;
+  } else {
+    PERFETTO_ELOG("TrackEvent without timestamp");
+    context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
+    return;
+  }
+
+  if (auto tt_delta_field =
+          event_decoder.FindField(kThreadTimeDeltaUsFieldNumber)) {
+    // Delta timestamps require a valid ThreadDescriptor packet since the last
+    // packet loss.
+    if (!state->track_event_timestamps_valid()) {
+      context_->storage->IncrementStats(stats::tokenizer_skipped_packets);
+      return;
+    }
+    thread_timestamp = state->IncrementAndGetTrackEventThreadTimeNs(
+        tt_delta_field.as_int64() * 1000);
+  } else if (auto tt_absolute_field =
+                 event_decoder.FindField(kThreadTimeAbsoluteUsFieldNumber)) {
+    // One-off absolute timestamps don't affect delta computation.
+    thread_timestamp = tt_absolute_field.as_int64() * 1000;
+  }
+
+  if (auto ti_delta_field =
+          event_decoder.FindField(kThreadInstructionCountDeltaFieldNumber)) {
+    // Delta timestamps require a valid ThreadDescriptor packet since the last
+    // packet loss.
+    if (!state->track_event_timestamps_valid()) {
+      context_->storage->IncrementStats(stats::tokenizer_skipped_packets);
+      return;
+    }
+    thread_instructions =
+        state->IncrementAndGetTrackEventThreadInstructionCount(
+            ti_delta_field.as_int64());
+  } else if (auto ti_absolute_field = event_decoder.FindField(
+                 kThreadInstructionCountAbsoluteFieldNumber)) {
+    // One-off absolute timestamps don't affect delta computation.
+    thread_instructions = ti_absolute_field.as_int64();
+  }
+
+  context_->sorter->PushTrackEventPacket(timestamp, thread_timestamp,
+                                         thread_instructions, state,
+                                         std::move(*packet));
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/proto/track_event_tokenizer.h b/src/trace_processor/importers/proto/track_event_tokenizer.h
new file mode 100644
index 0000000..32e3446
--- /dev/null
+++ b/src/trace_processor/importers/proto/track_event_tokenizer.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_TRACK_EVENT_TOKENIZER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_TRACK_EVENT_TOKENIZER_H_
+
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+#include "protos/perfetto/trace/track_event/thread_descriptor.pbzero.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+class PacketSequenceState;
+class TraceProcessorContext;
+class TraceBlobView;
+
+class TrackEventTokenizer {
+ public:
+  explicit TrackEventTokenizer(TraceProcessorContext* context)
+      : context_(context) {}
+
+  void TokenizeTrackDescriptorPacket(
+      const protos::pbzero::TracePacket::Decoder&);
+  void TokenizeProcessDescriptorPacket(
+      const protos::pbzero::TracePacket::Decoder&);
+  void TokenizeThreadDescriptorPacket(
+      PacketSequenceState* state,
+      const protos::pbzero::TracePacket::Decoder&);
+  void TokenizeThreadDescriptor(
+      const protos::pbzero::ThreadDescriptor::Decoder&);
+  void TokenizeTrackEventPacket(PacketSequenceState* state,
+                                const protos::pbzero::TracePacket::Decoder&,
+                                TraceBlobView* packet,
+                                int64_t packet_timestamp);
+
+ private:
+  TraceProcessorContext* context_;
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_TRACK_EVENT_TOKENIZER_H_
diff --git a/src/trace_processor/proto_incremental_state.h b/src/trace_processor/proto_incremental_state.h
deleted file mode 100644
index 7dbfdf3..0000000
--- a/src/trace_processor/proto_incremental_state.h
+++ /dev/null
@@ -1,272 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef SRC_TRACE_PROCESSOR_PROTO_INCREMENTAL_STATE_H_
-#define SRC_TRACE_PROCESSOR_PROTO_INCREMENTAL_STATE_H_
-
-#include <stdint.h>
-#include <string.h>
-
-#include <map>
-#include <unordered_map>
-#include <vector>
-
-#include "perfetto/protozero/proto_decoder.h"
-#include "src/trace_processor/stack_profile_tracker.h"
-#include "src/trace_processor/trace_blob_view.h"
-#include "src/trace_processor/trace_processor_context.h"
-#include "src/trace_processor/trace_storage.h"
-
-namespace perfetto {
-namespace trace_processor {
-
-struct DefaultFieldName;
-struct BuildIdFieldName;
-struct MappingPathsFieldName;
-struct FunctionNamesFieldName;
-struct VulkanAnnotationsFieldName;
-
-#if PERFETTO_DCHECK_IS_ON() && defined(__GNUC__)
-// When called from GetOrCreateDecoder(), __PRETTY_FUNCTION__ (supported by GCC
-// + clang) should include the stringified name of the MessageType.
-#define PERFETTO_TYPE_IDENTIFIER __PRETTY_FUNCTION__
-#else  // PERFETTO_DCHECK_IS_ON() && defined(__GNUC__)
-#define PERFETTO_TYPE_IDENTIFIER nullptr
-#endif  // PERFETTO_DCHECK_IS_ON() && defined(__GNUC__)
-
-// Stores per-packet-sequence incremental state during trace parsing, such as
-// reference timestamps for delta timestamp calculation and interned messages.
-class ProtoIncrementalState {
- public:
-  ProtoIncrementalState(TraceProcessorContext* context) : context_(context) {}
-
-  // Entry in an interning index, refers to the interned message.
-  struct InternedMessageView {
-    InternedMessageView(TraceBlobView msg) : message(std::move(msg)) {}
-
-    template <typename MessageType>
-    typename MessageType::Decoder* GetOrCreateDecoder() {
-      if (!decoder) {
-        // Lazy init the decoder and save it away, so that we don't have to
-        // reparse the message every time we access the interning entry.
-        decoder = std::unique_ptr<void, std::function<void(void*)>>(
-            new typename MessageType::Decoder(message.data(), message.length()),
-            [](void* obj) {
-              delete reinterpret_cast<typename MessageType::Decoder*>(obj);
-            });
-        decoder_type = PERFETTO_TYPE_IDENTIFIER;
-      }
-      // Verify that the type of the decoder didn't change.
-      if (PERFETTO_TYPE_IDENTIFIER &&
-          strcmp(decoder_type,
-                 // GCC complains if this arg can be null.
-                 PERFETTO_TYPE_IDENTIFIER ? PERFETTO_TYPE_IDENTIFIER : "") !=
-              0) {
-        PERFETTO_FATAL(
-            "Interning entry accessed under different types! previous type: "
-            "%s. new type: %s.",
-            decoder_type, __PRETTY_FUNCTION__);
-      }
-      return reinterpret_cast<typename MessageType::Decoder*>(decoder.get());
-    }
-
-    TraceBlobView message;
-    std::unique_ptr<void, std::function<void(void*)>> decoder;
-
-   private:
-    const char* decoder_type = nullptr;
-  };
-
-  using InternedMessageMap =
-      std::unordered_map<uint64_t /*iid*/, InternedMessageView>;
-  using InternedFieldMap =
-      std::unordered_map<uint32_t /*field_id*/, InternedMessageMap>;
-  using InternedDataGenerationList = std::vector<InternedFieldMap>;
-
-  class PacketSequenceState {
-   public:
-    PacketSequenceState(TraceProcessorContext* context)
-        : context_(context), stack_profile_tracker_(context) {
-      interned_data_.emplace_back();
-    }
-
-    int64_t IncrementAndGetTrackEventTimeNs(int64_t delta_ns) {
-      PERFETTO_DCHECK(track_event_timestamps_valid());
-      track_event_timestamp_ns_ += delta_ns;
-      return track_event_timestamp_ns_;
-    }
-
-    int64_t IncrementAndGetTrackEventThreadTimeNs(int64_t delta_ns) {
-      PERFETTO_DCHECK(track_event_timestamps_valid());
-      track_event_thread_timestamp_ns_ += delta_ns;
-      return track_event_thread_timestamp_ns_;
-    }
-
-    int64_t IncrementAndGetTrackEventThreadInstructionCount(int64_t delta) {
-      PERFETTO_DCHECK(track_event_timestamps_valid());
-      track_event_thread_instruction_count_ += delta;
-      return track_event_thread_instruction_count_;
-    }
-
-    void OnPacketLoss() {
-      packet_loss_ = true;
-      track_event_timestamps_valid_ = false;
-    }
-
-    void OnIncrementalStateCleared() {
-      packet_loss_ = false;
-      interned_data_.emplace_back();  // Bump generation number
-    }
-
-    void SetThreadDescriptor(int32_t pid,
-                             int32_t tid,
-                             int64_t timestamp_ns,
-                             int64_t thread_timestamp_ns,
-                             int64_t thread_instruction_count) {
-      track_event_timestamps_valid_ = true;
-      pid_and_tid_valid_ = true;
-      pid_ = pid;
-      tid_ = tid;
-      track_event_timestamp_ns_ = timestamp_ns;
-      track_event_thread_timestamp_ns_ = thread_timestamp_ns;
-      track_event_thread_instruction_count_ = thread_instruction_count;
-    }
-
-    bool IsIncrementalStateValid() const { return !packet_loss_; }
-
-    StackProfileTracker& stack_profile_tracker() {
-      return stack_profile_tracker_;
-    }
-
-    // Returns the index of the current generation in the
-    // InternedDataGenerationList.
-    size_t current_generation() const { return interned_data_.size() - 1; }
-
-    bool track_event_timestamps_valid() const {
-      return track_event_timestamps_valid_;
-    }
-
-    bool pid_and_tid_valid() const { return pid_and_tid_valid_; }
-
-    int32_t pid() const { return pid_; }
-    int32_t tid() const { return tid_; }
-
-    void InternMessage(uint32_t field_id, TraceBlobView message) {
-      constexpr auto kIidFieldNumber = 1;
-
-      uint64_t iid = 0;
-      auto message_start = message.data();
-      auto message_size = message.length();
-      protozero::ProtoDecoder decoder(message_start, message_size);
-
-      auto field = decoder.FindField(kIidFieldNumber);
-      if (PERFETTO_UNLIKELY(!field)) {
-        PERFETTO_DLOG("Interned message without interning_id");
-        context_->storage->IncrementStats(
-            stats::interned_data_tokenizer_errors);
-        return;
-      }
-      iid = field.as_uint64();
-
-      auto* map = &interned_data_.back()[field_id];
-      auto res = map->emplace(iid, InternedMessageView(std::move(message)));
-
-      // If a message with this ID is already interned in the same generation,
-      // its data should not have changed (this is forbidden by the InternedData
-      // proto).
-      // TODO(eseckler): This DCHECK assumes that the message is encoded the
-      // same way if it is re-emitted.
-      PERFETTO_DCHECK(res.second ||
-                      (res.first->second.message.length() == message_size &&
-                       memcmp(res.first->second.message.data(), message_start,
-                              message_size) == 0));
-    }
-
-    template <uint32_t FieldId, typename MessageType>
-    typename MessageType::Decoder* LookupInternedMessage(size_t generation,
-                                                         uint64_t iid) {
-      PERFETTO_CHECK(generation <= interned_data_.size());
-      auto* field_map = &interned_data_[generation];
-      auto field_it = field_map->find(FieldId);
-      if (field_it != field_map->end()) {
-        auto* message_map = &field_it->second;
-        auto it = message_map->find(iid);
-        if (it != message_map->end()) {
-          return it->second.GetOrCreateDecoder<MessageType>();
-        }
-      }
-      context_->storage->IncrementStats(stats::interned_data_tokenizer_errors);
-      PERFETTO_DLOG("Could not find interning entry for field ID %" PRIu32
-                    ", generation %zu, and IID %" PRIu64,
-                    FieldId, generation, iid);
-      return nullptr;
-    }
-
-   private:
-    TraceProcessorContext* context_;
-
-    // If true, incremental state on the sequence is considered invalid until we
-    // see the next packet with incremental_state_cleared. We assume that we
-    // missed some packets at the beginning of the trace.
-    bool packet_loss_ = true;
-
-    // We can only consider TrackEvent delta timestamps to be correct after we
-    // have observed a thread descriptor (since the last packet loss).
-    bool track_event_timestamps_valid_ = false;
-
-    // |pid_| and |tid_| are only valid after we parsed at least one
-    // ThreadDescriptor packet on the sequence.
-    bool pid_and_tid_valid_ = false;
-
-    // Process/thread ID of the packet sequence set by a ThreadDescriptor
-    // packet. Used as default values for TrackEvents that don't specify a
-    // pid/tid override. Only valid after |pid_and_tid_valid_| is set to true.
-    int32_t pid_ = 0;
-    int32_t tid_ = 0;
-
-    // Current wall/thread timestamps/counters used as reference for the next
-    // TrackEvent delta timestamp.
-    int64_t track_event_timestamp_ns_ = 0;
-    int64_t track_event_thread_timestamp_ns_ = 0;
-    int64_t track_event_thread_instruction_count_ = 0;
-
-    InternedDataGenerationList interned_data_;
-    StackProfileTracker stack_profile_tracker_;
-  };
-
-  // Returns the PacketSequenceState for the packet sequence with the given id.
-  // If this is a new sequence which we haven't tracked before, initializes and
-  // inserts a new PacketSequenceState into the state map.
-  PacketSequenceState* GetOrCreateStateForPacketSequence(uint32_t sequence_id) {
-    auto& ptr = packet_sequence_states_[sequence_id];
-    if (!ptr)
-      ptr.reset(new PacketSequenceState(context_));
-    return ptr.get();
-  }
-
- private:
-  // Stores unique_ptrs to ensure that pointers to a PacketSequenceState remain
-  // valid even if the map rehashes.
-  std::map<uint32_t, std::unique_ptr<PacketSequenceState>>
-      packet_sequence_states_;
-
-  TraceProcessorContext* context_;
-};
-
-}  // namespace trace_processor
-}  // namespace perfetto
-
-#endif  // SRC_TRACE_PROCESSOR_PROTO_INCREMENTAL_STATE_H_
diff --git a/src/trace_processor/proto_trace_parser.cc b/src/trace_processor/proto_trace_parser.cc
index 42daa08..98ea5f5 100644
--- a/src/trace_processor/proto_trace_parser.cc
+++ b/src/trace_processor/proto_trace_parser.cc
@@ -36,16 +36,16 @@
 #include "src/trace_processor/heap_graph_tracker.h"
 #include "src/trace_processor/heap_profile_tracker.h"
 #include "src/trace_processor/importers/ftrace/ftrace_module.h"
+#include "src/trace_processor/importers/proto/packet_sequence_state.h"
 #include "src/trace_processor/importers/proto/track_event_module.h"
 #include "src/trace_processor/importers/systrace/systrace_parser.h"
 #include "src/trace_processor/metadata.h"
 #include "src/trace_processor/process_tracker.h"
-#include "src/trace_processor/proto_incremental_state.h"
+#include "src/trace_processor/slice_tracker.h"
 #include "src/trace_processor/stack_profile_tracker.h"
 #include "src/trace_processor/syscall_tracker.h"
 #include "src/trace_processor/timestamped_trace_piece.h"
 #include "src/trace_processor/trace_processor_context.h"
-#include "src/trace_processor/track_tracker.h"
 #include "src/trace_processor/variadic.h"
 
 #include "protos/perfetto/common/android_log_constants.pbzero.h"
@@ -69,10 +69,6 @@
 #include "protos/perfetto/trace/system_info.pbzero.h"
 #include "protos/perfetto/trace/trace.pbzero.h"
 #include "protos/perfetto/trace/trace_packet.pbzero.h"
-#include "protos/perfetto/trace/track_event/debug_annotation.pbzero.h"
-#include "protos/perfetto/trace/track_event/log_message.pbzero.h"
-#include "protos/perfetto/trace/track_event/source_location.pbzero.h"
-#include "protos/perfetto/trace/track_event/task_execution.pbzero.h"
 
 namespace perfetto {
 namespace trace_processor {
@@ -120,9 +116,8 @@
 
 class ProfilePacketInternLookup : public StackProfileTracker::InternLookup {
  public:
-  ProfilePacketInternLookup(
-      ProtoIncrementalState::PacketSequenceState* seq_state,
-      size_t seq_state_generation)
+  ProfilePacketInternLookup(PacketSequenceState* seq_state,
+                            size_t seq_state_generation)
       : seq_state_(seq_state), seq_state_generation_(seq_state_generation) {}
 
   base::Optional<base::StringView> GetString(
@@ -183,17 +178,10 @@
   }
 
  private:
-  ProtoIncrementalState::PacketSequenceState* seq_state_;
+  PacketSequenceState* seq_state_;
   size_t seq_state_generation_;
 };
 
-namespace {
-// Slices which have been opened but haven't been closed yet will be marked
-// with these placeholder values.
-constexpr int64_t kPendingThreadDuration = -1;
-constexpr int64_t kPendingThreadInstructionDelta = -1;
-}  // namespace
-
 const char* HeapGraphRootTypeToString(int32_t type) {
   switch (type) {
     case protos::pbzero::HeapGraphRoot::ROOT_UNKNOWN:
@@ -263,60 +251,13 @@
           context->storage->InternString("batt.current.avg_ua")),
       oom_score_adj_id_(context->storage->InternString("oom_score_adj")),
       metatrace_id_(context->storage->InternString("metatrace")),
-      task_file_name_args_key_id_(
-          context->storage->InternString("task.posted_from.file_name")),
-      task_function_name_args_key_id_(
-          context->storage->InternString("task.posted_from.function_name")),
-      task_line_number_args_key_id_(
-          context->storage->InternString("task.posted_from.line_number")),
-      log_message_body_key_id_(
-          context->storage->InternString("track_event.log_message")),
       data_name_id_(context->storage->InternString("data")),
       raw_chrome_metadata_event_id_(
           context->storage->InternString("chrome_event.metadata")),
       raw_chrome_legacy_system_trace_event_id_(
           context->storage->InternString("chrome_event.legacy_system_trace")),
       raw_chrome_legacy_user_trace_event_id_(
-          context->storage->InternString("chrome_event.legacy_user_trace")),
-      raw_legacy_event_id_(
-          context->storage->InternString("track_event.legacy_event")),
-      legacy_event_category_key_id_(
-          context->storage->InternString("legacy_event.category")),
-      legacy_event_name_key_id_(
-          context->storage->InternString("legacy_event.name")),
-      legacy_event_phase_key_id_(
-          context->storage->InternString("legacy_event.phase")),
-      legacy_event_duration_ns_key_id_(
-          context->storage->InternString("legacy_event.duration_ns")),
-      legacy_event_thread_timestamp_ns_key_id_(
-          context->storage->InternString("legacy_event.thread_timestamp_ns")),
-      legacy_event_thread_duration_ns_key_id_(
-          context->storage->InternString("legacy_event.thread_duration_ns")),
-      legacy_event_thread_instruction_count_key_id_(
-          context->storage->InternString(
-              "legacy_event.thread_instruction_count")),
-      legacy_event_thread_instruction_delta_key_id_(
-          context->storage->InternString(
-              "legacy_event.thread_instruction_delta")),
-      legacy_event_use_async_tts_key_id_(
-          context->storage->InternString("legacy_event.use_async_tts")),
-      legacy_event_unscoped_id_key_id_(
-          context->storage->InternString("legacy_event.unscoped_id")),
-      legacy_event_global_id_key_id_(
-          context->storage->InternString("legacy_event.global_id")),
-      legacy_event_local_id_key_id_(
-          context->storage->InternString("legacy_event.local_id")),
-      legacy_event_id_scope_key_id_(
-          context->storage->InternString("legacy_event.id_scope")),
-      legacy_event_bind_id_key_id_(
-          context->storage->InternString("legacy_event.bind_id")),
-      legacy_event_bind_to_enclosing_key_id_(
-          context->storage->InternString("legacy_event.bind_to_enclosing")),
-      legacy_event_flow_direction_key_id_(
-          context->storage->InternString("legacy_event.flow_direction")),
-      flow_direction_value_in_id_(context->storage->InternString("in")),
-      flow_direction_value_out_id_(context->storage->InternString("out")),
-      flow_direction_value_inout_id_(context->storage->InternString("inout")) {
+          context->storage->InternString("chrome_event.legacy_user_trace")) {
   for (const auto& name : BuildMeminfoCounterNames()) {
     meminfo_strs_id_.emplace_back(context->storage->InternString(name));
   }
@@ -413,12 +354,6 @@
   if (packet.has_system_info())
     ParseSystemInfo(packet.system_info());
 
-  if (packet.has_track_event()) {
-    ParseTrackEvent(ts, ttp.thread_timestamp, ttp.thread_instruction_count,
-                    ttp.packet_sequence_state,
-                    ttp.packet_sequence_state_generation, packet.track_event());
-  }
-
   if (packet.has_chrome_benchmark_metadata()) {
     ParseChromeBenchmarkMetadata(packet.chrome_benchmark_metadata());
   }
@@ -865,11 +800,10 @@
   }
 }
 
-void ProtoTraceParser::ParseProfilePacket(
-    int64_t,
-    ProtoIncrementalState::PacketSequenceState* sequence_state,
-    size_t sequence_state_generation,
-    ConstBytes blob) {
+void ProtoTraceParser::ParseProfilePacket(int64_t,
+                                          PacketSequenceState* sequence_state,
+                                          size_t sequence_state_generation,
+                                          ConstBytes blob) {
   protos::pbzero::ProfilePacket::Decoder packet(blob.data, blob.size);
   context_->heap_profile_tracker->SetProfilePacketIndex(packet.index());
 
@@ -942,7 +876,7 @@
 }
 
 void ProtoTraceParser::ParseStreamingProfilePacket(
-    ProtoIncrementalState::PacketSequenceState* sequence_state,
+    PacketSequenceState* sequence_state,
     size_t sequence_state_generation,
     ConstBytes blob) {
   protos::pbzero::StreamingProfilePacket::Decoder packet(blob.data, blob.size);
@@ -1002,805 +936,6 @@
   }
 }
 
-void ProtoTraceParser::ParseTrackEvent(
-    int64_t ts,
-    int64_t tts,
-    int64_t ticount,
-    ProtoIncrementalState::PacketSequenceState* sequence_state,
-    size_t sequence_state_generation,
-    ConstBytes blob) {
-  using LegacyEvent = protos::pbzero::TrackEvent::LegacyEvent;
-
-  protos::pbzero::TrackEvent::Decoder event(blob.data, blob.size);
-
-  const auto legacy_event_blob = event.legacy_event();
-  LegacyEvent::Decoder legacy_event(legacy_event_blob.data,
-                                    legacy_event_blob.size);
-
-  // TODO(eseckler): This legacy event field will eventually be replaced by
-  // fields in TrackEvent itself.
-  if (PERFETTO_UNLIKELY(!event.type() && !legacy_event.has_phase())) {
-    context_->storage->IncrementStats(stats::track_event_parser_errors);
-    PERFETTO_DLOG("TrackEvent without type or phase");
-    return;
-  }
-
-  ProcessTracker* procs = context_->process_tracker.get();
-  TraceStorage* storage = context_->storage.get();
-  TrackTracker* track_tracker = context_->track_tracker.get();
-  SliceTracker* slice_tracker = context_->slice_tracker.get();
-
-  std::vector<uint64_t> category_iids;
-  for (auto it = event.category_iids(); it; ++it) {
-    category_iids.push_back(*it);
-  }
-  std::vector<protozero::ConstChars> category_strings;
-  for (auto it = event.categories(); it; ++it) {
-    category_strings.push_back(*it);
-  }
-
-  StringId category_id = 0;
-
-  // If there's a single category, we can avoid building a concatenated
-  // string.
-  if (PERFETTO_LIKELY(category_iids.size() == 1 && category_strings.empty())) {
-    auto* decoder = sequence_state->LookupInternedMessage<
-        protos::pbzero::InternedData::kEventCategoriesFieldNumber,
-        protos::pbzero::EventCategory>(sequence_state_generation,
-                                       category_iids[0]);
-    if (decoder)
-      category_id = storage->InternString(decoder->name());
-  } else if (category_iids.empty() && category_strings.size() == 1) {
-    category_id = storage->InternString(category_strings[0]);
-  } else if (category_iids.size() + category_strings.size() > 1) {
-    // We concatenate the category strings together since we currently only
-    // support a single "cat" column.
-    // TODO(eseckler): Support multi-category events in the table schema.
-    std::string categories;
-    for (uint64_t iid : category_iids) {
-      auto* decoder = sequence_state->LookupInternedMessage<
-          protos::pbzero::InternedData::kEventCategoriesFieldNumber,
-          protos::pbzero::EventCategory>(sequence_state_generation, iid);
-      if (!decoder)
-        continue;
-      base::StringView name = decoder->name();
-      if (!categories.empty())
-        categories.append(",");
-      categories.append(name.data(), name.size());
-    }
-    for (const protozero::ConstChars& cat : category_strings) {
-      if (!categories.empty())
-        categories.append(",");
-      categories.append(cat.data, cat.size);
-    }
-    if (!categories.empty())
-      category_id = storage->InternString(base::StringView(categories));
-  }
-
-  StringId name_id = 0;
-
-  uint64_t name_iid = event.name_iid();
-  if (!name_iid)
-    name_iid = legacy_event.name_iid();
-
-  if (PERFETTO_LIKELY(name_iid)) {
-    auto* decoder = sequence_state->LookupInternedMessage<
-        protos::pbzero::InternedData::kEventNamesFieldNumber,
-        protos::pbzero::EventName>(sequence_state_generation, name_iid);
-    if (decoder)
-      name_id = storage->InternString(decoder->name());
-  } else if (event.has_name()) {
-    name_id = storage->InternString(event.name());
-  }
-
-  // TODO(eseckler): Also consider track_uuid from TrackEventDefaults.
-  // Fall back to the default descriptor track (uuid 0).
-  uint64_t track_uuid = event.has_track_uuid() ? event.track_uuid() : 0u;
-  TrackId track_id;
-  base::Optional<UniqueTid> utid;
-  base::Optional<UniqueTid> upid;
-
-  // Determine track from track_uuid specified in either TrackEvent or
-  // TrackEventDefaults. If none is set, fall back to the track specified by the
-  // sequence's (or event's) pid + tid or a default track.
-  if (track_uuid) {
-    base::Optional<TrackId> opt_track_id =
-        track_tracker->GetDescriptorTrack(track_uuid);
-    if (!opt_track_id) {
-      storage->IncrementStats(stats::track_event_parser_errors);
-      PERFETTO_DLOG("TrackEvent with unknown track_uuid %" PRIu64, track_uuid);
-      return;
-    }
-    track_id = *opt_track_id;
-
-    auto thread_track_row =
-        context_->storage->thread_track_table().id().IndexOf(
-            SqlValue::Long(track_id));
-    if (thread_track_row) {
-      utid = storage->thread_track_table().utid()[*thread_track_row];
-      upid = storage->GetThread(*utid).upid;
-    } else {
-      auto process_track_row =
-          context_->storage->process_track_table().id().IndexOf(
-              SqlValue::Long(track_id));
-      if (process_track_row)
-        upid = storage->process_track_table().upid()[*process_track_row];
-    }
-  } else if (sequence_state->pid_and_tid_valid() ||
-             (legacy_event.has_pid_override() &&
-              legacy_event.has_tid_override())) {
-    uint32_t pid = static_cast<uint32_t>(sequence_state->pid());
-    uint32_t tid = static_cast<uint32_t>(sequence_state->tid());
-    if (legacy_event.has_pid_override())
-      pid = static_cast<uint32_t>(legacy_event.pid_override());
-    if (legacy_event.has_tid_override())
-      tid = static_cast<uint32_t>(legacy_event.tid_override());
-
-    utid = procs->UpdateThread(tid, pid);
-    upid = storage->GetThread(*utid).upid;
-    track_id = track_tracker->GetOrCreateDescriptorTrackForThread(*utid);
-  } else {
-    track_id = track_tracker->GetOrCreateDefaultDescriptorTrack();
-  }
-
-  // TODO(eseckler): Replace phase with type and remove handling of
-  // legacy_event.phase() once it is no longer used by producers.
-  int32_t phase = 0;
-  if (legacy_event.has_phase()) {
-    phase = legacy_event.phase();
-
-    switch (phase) {
-      case 'b':
-      case 'e':
-      case 'n': {
-        // Intern tracks for legacy async events based on legacy event ids.
-        int64_t source_id = 0;
-        bool source_id_is_process_scoped = false;
-        if (legacy_event.has_unscoped_id()) {
-          source_id = static_cast<int64_t>(legacy_event.unscoped_id());
-        } else if (legacy_event.has_global_id()) {
-          source_id = static_cast<int64_t>(legacy_event.global_id());
-        } else if (legacy_event.has_local_id()) {
-          if (!upid) {
-            storage->IncrementStats(stats::track_event_parser_errors);
-            PERFETTO_DLOG(
-                "TrackEvent with local_id without process association");
-            return;
-          }
-
-          source_id = static_cast<int64_t>(legacy_event.local_id());
-          source_id_is_process_scoped = true;
-        } else {
-          storage->IncrementStats(stats::track_event_parser_errors);
-          PERFETTO_DLOG("Async LegacyEvent without ID");
-          return;
-        }
-
-        // Catapult treats nestable async events of different categories with
-        // the same ID as separate tracks. We replicate the same behavior here.
-        StringId id_scope = category_id;
-        if (legacy_event.has_id_scope()) {
-          std::string concat = storage->GetString(category_id).ToStdString() +
-                               ":" + legacy_event.id_scope().ToStdString();
-          id_scope = storage->InternString(base::StringView(concat));
-        }
-
-        track_id = context_->track_tracker->InternLegacyChromeAsyncTrack(
-            name_id, upid ? *upid : 0, source_id, source_id_is_process_scoped,
-            id_scope);
-        break;
-      }
-      case 'i':
-      case 'I': {
-        // Intern tracks for global or process-scoped legacy instant events.
-        switch (legacy_event.instant_event_scope()) {
-          case LegacyEvent::SCOPE_UNSPECIFIED:
-          case LegacyEvent::SCOPE_THREAD:
-            // Thread-scoped legacy instant events already have the right track
-            // based on the tid/pid of the sequence.
-            if (!utid) {
-              storage->IncrementStats(stats::track_event_parser_errors);
-              PERFETTO_DLOG(
-                  "Thread-scoped instant event without thread association");
-              return;
-            }
-            break;
-          case LegacyEvent::SCOPE_GLOBAL:
-            track_id = context_->track_tracker
-                           ->GetOrCreateLegacyChromeGlobalInstantTrack();
-            break;
-          case LegacyEvent::SCOPE_PROCESS:
-            if (!upid) {
-              storage->IncrementStats(stats::track_event_parser_errors);
-              PERFETTO_DLOG(
-                  "Process-scoped instant event without process association");
-              return;
-            }
-
-            track_id =
-                context_->track_tracker->InternLegacyChromeProcessInstantTrack(
-                    *upid);
-            break;
-        }
-        break;
-      }
-      default:
-        break;
-    }
-  } else {
-    switch (event.type()) {
-      case protos::pbzero::TrackEvent::TYPE_SLICE_BEGIN:
-        phase = utid ? 'B' : 'b';
-        break;
-      case protos::pbzero::TrackEvent::TYPE_SLICE_END:
-        phase = utid ? 'E' : 'e';
-        break;
-      case protos::pbzero::TrackEvent::TYPE_INSTANT:
-        phase = utid ? 'i' : 'n';
-        break;
-      default:
-        PERFETTO_FATAL("unexpected event type %d", event.type());
-        return;
-    }
-  }
-
-  auto args_callback = [this, &event, &legacy_event, &sequence_state,
-                        sequence_state_generation, ts,
-                        utid](ArgsTracker* args_tracker, RowId row_id) {
-    for (auto it = event.debug_annotations(); it; ++it) {
-      ParseDebugAnnotationArgs(*it, sequence_state, sequence_state_generation,
-                               args_tracker, row_id);
-    }
-
-    if (event.has_task_execution()) {
-      ParseTaskExecutionArgs(event.task_execution(), sequence_state,
-                             sequence_state_generation, args_tracker, row_id);
-    }
-
-    if (event.has_log_message()) {
-      ParseLogMessage(event.log_message(), sequence_state,
-                      sequence_state_generation, ts, utid, args_tracker,
-                      row_id);
-    }
-
-    // TODO(eseckler): Parse legacy flow events into flow events table once we
-    // have a design for it.
-    if (legacy_event.has_bind_id()) {
-      args_tracker->AddArg(row_id, legacy_event_bind_id_key_id_,
-                           legacy_event_bind_id_key_id_,
-                           Variadic::UnsignedInteger(legacy_event.bind_id()));
-    }
-
-    if (legacy_event.bind_to_enclosing()) {
-      args_tracker->AddArg(row_id, legacy_event_bind_to_enclosing_key_id_,
-                           legacy_event_bind_to_enclosing_key_id_,
-                           Variadic::Boolean(true));
-    }
-
-    if (legacy_event.flow_direction()) {
-      StringId value;
-      switch (legacy_event.flow_direction()) {
-        case protos::pbzero::TrackEvent::LegacyEvent::FLOW_IN:
-          value = flow_direction_value_in_id_;
-          break;
-        case protos::pbzero::TrackEvent::LegacyEvent::FLOW_OUT:
-          value = flow_direction_value_out_id_;
-          break;
-        case protos::pbzero::TrackEvent::LegacyEvent::FLOW_INOUT:
-          value = flow_direction_value_inout_id_;
-          break;
-        default:
-          PERFETTO_FATAL("Unknown flow direction: %d",
-                         legacy_event.flow_direction());
-          break;
-      }
-      args_tracker->AddArg(row_id, legacy_event_flow_direction_key_id_,
-                           legacy_event_flow_direction_key_id_,
-                           Variadic::String(value));
-    }
-  };
-
-  switch (static_cast<char>(phase)) {
-    case 'B': {  // TRACE_EVENT_PHASE_BEGIN.
-      if (!utid) {
-        storage->IncrementStats(stats::track_event_parser_errors);
-        PERFETTO_DLOG("TrackEvent with phase B without thread association");
-        return;
-      }
-
-      auto opt_slice_id =
-          slice_tracker->Begin(ts, track_id, *utid, RefType::kRefUtid,
-                               category_id, name_id, args_callback);
-      if (opt_slice_id.has_value()) {
-        auto* thread_slices = storage->mutable_thread_slices();
-        PERFETTO_DCHECK(!thread_slices->slice_count() ||
-                        thread_slices->slice_ids().back() <
-                            opt_slice_id.value());
-        thread_slices->AddThreadSlice(opt_slice_id.value(), tts,
-                                      kPendingThreadDuration, ticount,
-                                      kPendingThreadInstructionDelta);
-      }
-      break;
-    }
-    case 'E': {  // TRACE_EVENT_PHASE_END.
-      if (!utid) {
-        storage->IncrementStats(stats::track_event_parser_errors);
-        PERFETTO_DLOG("TrackEvent with phase E without thread association");
-        return;
-      }
-
-      auto opt_slice_id =
-          slice_tracker->End(ts, track_id, category_id, name_id, args_callback);
-      if (opt_slice_id.has_value()) {
-        auto* thread_slices = storage->mutable_thread_slices();
-        thread_slices->UpdateThreadDeltasForSliceId(opt_slice_id.value(), tts,
-                                                    ticount);
-      }
-      break;
-    }
-    case 'X': {  // TRACE_EVENT_PHASE_COMPLETE.
-      if (!utid) {
-        storage->IncrementStats(stats::track_event_parser_errors);
-        PERFETTO_DLOG("TrackEvent with phase X without thread association");
-        return;
-      }
-
-      auto duration_ns = legacy_event.duration_us() * 1000;
-      if (duration_ns < 0)
-        return;
-      auto opt_slice_id = slice_tracker->Scoped(
-          ts, track_id, *utid, RefType::kRefUtid, category_id, name_id,
-          duration_ns, args_callback);
-      if (opt_slice_id.has_value()) {
-        auto* thread_slices = storage->mutable_thread_slices();
-        PERFETTO_DCHECK(!thread_slices->slice_count() ||
-                        thread_slices->slice_ids().back() <
-                            opt_slice_id.value());
-        auto thread_duration_ns = legacy_event.thread_duration_us() * 1000;
-        thread_slices->AddThreadSlice(opt_slice_id.value(), tts,
-                                      thread_duration_ns, ticount,
-                                      legacy_event.thread_instruction_delta());
-      }
-      break;
-    }
-    case 'i':
-    case 'I': {  // TRACE_EVENT_PHASE_INSTANT.
-      // Handle instant events as slices with zero duration, so that they end
-      // up nested underneath their parent slices.
-      int64_t duration_ns = 0;
-      int64_t tidelta = 0;
-
-      switch (legacy_event.instant_event_scope()) {
-        case LegacyEvent::SCOPE_UNSPECIFIED:
-        case LegacyEvent::SCOPE_THREAD: {
-          // TODO(lalitm): Associate thread slices with track instead.
-          auto opt_slice_id = slice_tracker->Scoped(
-              ts, track_id, *utid, RefType::kRefUtid, category_id, name_id,
-              duration_ns, args_callback);
-          if (opt_slice_id.has_value()) {
-            auto* thread_slices = storage->mutable_thread_slices();
-            PERFETTO_DCHECK(!thread_slices->slice_count() ||
-                            thread_slices->slice_ids().back() <
-                                opt_slice_id.value());
-            thread_slices->AddThreadSlice(opt_slice_id.value(), tts,
-                                          duration_ns, ticount, tidelta);
-          }
-          break;
-        }
-        case LegacyEvent::SCOPE_GLOBAL: {
-          slice_tracker->Scoped(ts, track_id, /*ref=*/0, RefType::kRefNoRef,
-                                category_id, name_id, duration_ns,
-                                args_callback);
-          break;
-        }
-        case LegacyEvent::SCOPE_PROCESS: {
-          slice_tracker->Scoped(ts, track_id, *upid, RefType::kRefUpid,
-                                category_id, name_id, duration_ns,
-                                args_callback);
-          break;
-        }
-        default: {
-          PERFETTO_FATAL("Unknown instant event scope: %u",
-                         legacy_event.instant_event_scope());
-          break;
-        }
-      }
-      break;
-    }
-    case 'b': {  // TRACE_EVENT_PHASE_NESTABLE_ASYNC_BEGIN
-      auto opt_slice_id =
-          slice_tracker->Begin(ts, track_id, track_id, RefType::kRefTrack,
-                               category_id, name_id, args_callback);
-      // For the time beeing, we only create vtrack slice rows if we need to
-      // store thread timestamps/counters.
-      if (legacy_event.use_async_tts() && opt_slice_id.has_value()) {
-        auto* vtrack_slices = storage->mutable_virtual_track_slices();
-        PERFETTO_DCHECK(!vtrack_slices->slice_count() ||
-                        vtrack_slices->slice_ids().back() <
-                            opt_slice_id.value());
-        vtrack_slices->AddVirtualTrackSlice(opt_slice_id.value(), tts,
-                                            kPendingThreadDuration, ticount,
-                                            kPendingThreadInstructionDelta);
-      }
-      break;
-    }
-    case 'e': {  // TRACE_EVENT_PHASE_NESTABLE_ASYNC_END
-      auto opt_slice_id =
-          slice_tracker->End(ts, track_id, category_id, name_id, args_callback);
-      if (legacy_event.use_async_tts() && opt_slice_id.has_value()) {
-        auto* vtrack_slices = storage->mutable_virtual_track_slices();
-        vtrack_slices->UpdateThreadDeltasForSliceId(opt_slice_id.value(), tts,
-                                                    ticount);
-      }
-      break;
-    }
-    case 'n': {  // TRACE_EVENT_PHASE_NESTABLE_ASYNC_INSTANT
-      // Handle instant events as slices with zero duration, so that they end up
-      // nested underneath their parent slices.
-      int64_t duration_ns = 0;
-      int64_t tidelta = 0;
-      auto opt_slice_id = slice_tracker->Scoped(
-          ts, track_id, track_id, RefType::kRefTrack, category_id, name_id,
-          duration_ns, args_callback);
-      if (legacy_event.use_async_tts() && opt_slice_id.has_value()) {
-        auto* vtrack_slices = storage->mutable_virtual_track_slices();
-        PERFETTO_DCHECK(!vtrack_slices->slice_count() ||
-                        vtrack_slices->slice_ids().back() <
-                            opt_slice_id.value());
-        vtrack_slices->AddVirtualTrackSlice(opt_slice_id.value(), tts,
-                                            duration_ns, ticount, tidelta);
-      }
-      break;
-    }
-    case 'M': {  // TRACE_EVENT_PHASE_METADATA (process and thread names).
-      // Parse process and thread names from correspondingly named events.
-      // TODO(eseckler): Also consider names from process/thread descriptors.
-      NullTermStringView event_name = storage->GetString(name_id);
-      PERFETTO_DCHECK(event_name.data());
-      if (strcmp(event_name.c_str(), "thread_name") == 0) {
-        if (!utid) {
-          storage->IncrementStats(stats::track_event_parser_errors);
-          PERFETTO_DLOG(
-              "thread_name metadata event without thread association");
-          return;
-        }
-
-        auto it = event.debug_annotations();
-        if (!it)
-          break;
-        protos::pbzero::DebugAnnotation::Decoder annotation(*it);
-        auto thread_name = annotation.string_value();
-        if (!thread_name.size)
-          break;
-        auto thread_name_id = storage->InternString(thread_name);
-        procs->UpdateThreadName(storage->GetThread(*utid).tid, thread_name_id);
-        break;
-      }
-      if (strcmp(event_name.c_str(), "process_name") == 0) {
-        if (!upid) {
-          storage->IncrementStats(stats::track_event_parser_errors);
-          PERFETTO_DLOG(
-              "process_name metadata event without process association");
-          return;
-        }
-
-        auto it = event.debug_annotations();
-        if (!it)
-          break;
-        protos::pbzero::DebugAnnotation::Decoder annotation(*it);
-        auto process_name = annotation.string_value();
-        if (!process_name.size)
-          break;
-        procs->SetProcessMetadata(storage->GetProcess(*upid).pid, base::nullopt,
-                                  process_name);
-        break;
-      }
-      // Other metadata events are proxied via the raw table for JSON export.
-      ParseLegacyEventAsRawEvent(ts, tts, ticount, utid, category_id, name_id,
-                                 legacy_event, args_callback);
-      break;
-    }
-    default: {
-      // Other events are proxied via the raw table for JSON export.
-      ParseLegacyEventAsRawEvent(ts, tts, ticount, utid, category_id, name_id,
-                                 legacy_event, args_callback);
-    }
-  }
-}
-
-void ProtoTraceParser::ParseLegacyEventAsRawEvent(
-    int64_t ts,
-    int64_t tts,
-    int64_t ticount,
-    base::Optional<UniqueTid> utid,
-    StringId category_id,
-    StringId name_id,
-    const protos::pbzero::TrackEvent::LegacyEvent::Decoder& legacy_event,
-    SliceTracker::SetArgsCallback args_callback) {
-  if (!utid) {
-    context_->storage->IncrementStats(stats::track_event_parser_errors);
-    PERFETTO_DLOG("raw legacy event without thread association");
-    return;
-  }
-
-  RowId row_id = context_->storage->mutable_raw_events()->AddRawEvent(
-      ts, raw_legacy_event_id_, 0, *utid);
-  ArgsTracker args(context_);
-  args.AddArg(row_id, legacy_event_category_key_id_,
-              legacy_event_category_key_id_, Variadic::String(category_id));
-  args.AddArg(row_id, legacy_event_name_key_id_, legacy_event_name_key_id_,
-              Variadic::String(name_id));
-
-  std::string phase_string(1, static_cast<char>(legacy_event.phase()));
-  StringId phase_id = context_->storage->InternString(phase_string.c_str());
-  args.AddArg(row_id, legacy_event_phase_key_id_, legacy_event_phase_key_id_,
-              Variadic::String(phase_id));
-
-  if (legacy_event.has_duration_us()) {
-    args.AddArg(row_id, legacy_event_duration_ns_key_id_,
-                legacy_event_duration_ns_key_id_,
-                Variadic::Integer(legacy_event.duration_us() * 1000));
-  }
-
-  if (tts) {
-    args.AddArg(row_id, legacy_event_thread_timestamp_ns_key_id_,
-                legacy_event_thread_timestamp_ns_key_id_,
-                Variadic::Integer(tts));
-    if (legacy_event.has_thread_duration_us()) {
-      args.AddArg(row_id, legacy_event_thread_duration_ns_key_id_,
-                  legacy_event_thread_duration_ns_key_id_,
-                  Variadic::Integer(legacy_event.thread_duration_us() * 1000));
-    }
-  }
-
-  if (ticount) {
-    args.AddArg(row_id, legacy_event_thread_instruction_count_key_id_,
-                legacy_event_thread_instruction_count_key_id_,
-                Variadic::Integer(tts));
-    if (legacy_event.has_thread_instruction_delta()) {
-      args.AddArg(row_id, legacy_event_thread_instruction_delta_key_id_,
-                  legacy_event_thread_instruction_delta_key_id_,
-                  Variadic::Integer(legacy_event.thread_instruction_delta()));
-    }
-  }
-
-  if (legacy_event.use_async_tts()) {
-    args.AddArg(row_id, legacy_event_use_async_tts_key_id_,
-                legacy_event_use_async_tts_key_id_, Variadic::Boolean(true));
-  }
-
-  bool has_id = false;
-  if (legacy_event.has_unscoped_id()) {
-    // Unscoped ids are either global or local depending on the phase. Pass them
-    // through as unscoped IDs to JSON export to preserve this behavior.
-    args.AddArg(row_id, legacy_event_unscoped_id_key_id_,
-                legacy_event_unscoped_id_key_id_,
-                Variadic::UnsignedInteger(legacy_event.unscoped_id()));
-    has_id = true;
-  } else if (legacy_event.has_global_id()) {
-    args.AddArg(row_id, legacy_event_global_id_key_id_,
-                legacy_event_global_id_key_id_,
-                Variadic::UnsignedInteger(legacy_event.global_id()));
-    has_id = true;
-  } else if (legacy_event.has_local_id()) {
-    args.AddArg(row_id, legacy_event_local_id_key_id_,
-                legacy_event_local_id_key_id_,
-                Variadic::UnsignedInteger(legacy_event.local_id()));
-    has_id = true;
-  }
-
-  if (has_id && legacy_event.has_id_scope() && legacy_event.id_scope().size) {
-    args.AddArg(row_id, legacy_event_id_scope_key_id_,
-                legacy_event_id_scope_key_id_,
-                Variadic::String(
-                    context_->storage->InternString(legacy_event.id_scope())));
-  }
-
-  // No need to parse legacy_event.instant_event_scope() because we import
-  // instant events into the slice table.
-
-  args_callback(&args, row_id);
-}
-
-void ProtoTraceParser::ParseDebugAnnotationArgs(
-    ConstBytes debug_annotation,
-    ProtoIncrementalState::PacketSequenceState* sequence_state,
-    size_t sequence_state_generation,
-    ArgsTracker* args_tracker,
-    RowId row_id) {
-  TraceStorage* storage = context_->storage.get();
-
-  protos::pbzero::DebugAnnotation::Decoder annotation(debug_annotation.data,
-                                                      debug_annotation.size);
-
-  StringId name_id = 0;
-
-  uint64_t name_iid = annotation.name_iid();
-  if (PERFETTO_LIKELY(name_iid)) {
-    auto* decoder = sequence_state->LookupInternedMessage<
-        protos::pbzero::InternedData::kDebugAnnotationNamesFieldNumber,
-        protos::pbzero::DebugAnnotationName>(sequence_state_generation,
-                                             name_iid);
-    if (!decoder)
-      return;
-
-    std::string name_prefixed = "debug." + decoder->name().ToStdString();
-    name_id = storage->InternString(base::StringView(name_prefixed));
-  } else if (annotation.has_name()) {
-    name_id = storage->InternString(annotation.name());
-  } else {
-    context_->storage->IncrementStats(stats::track_event_parser_errors);
-    PERFETTO_DLOG("Debug annotation without name");
-    return;
-  }
-
-  if (annotation.has_bool_value()) {
-    args_tracker->AddArg(row_id, name_id, name_id,
-                         Variadic::Boolean(annotation.bool_value()));
-  } else if (annotation.has_uint_value()) {
-    args_tracker->AddArg(row_id, name_id, name_id,
-                         Variadic::UnsignedInteger(annotation.uint_value()));
-  } else if (annotation.has_int_value()) {
-    args_tracker->AddArg(row_id, name_id, name_id,
-                         Variadic::Integer(annotation.int_value()));
-  } else if (annotation.has_double_value()) {
-    args_tracker->AddArg(row_id, name_id, name_id,
-                         Variadic::Real(annotation.double_value()));
-  } else if (annotation.has_string_value()) {
-    args_tracker->AddArg(
-        row_id, name_id, name_id,
-        Variadic::String(storage->InternString(annotation.string_value())));
-  } else if (annotation.has_pointer_value()) {
-    args_tracker->AddArg(row_id, name_id, name_id,
-                         Variadic::Pointer(annotation.pointer_value()));
-  } else if (annotation.has_legacy_json_value()) {
-    args_tracker->AddArg(
-        row_id, name_id, name_id,
-        Variadic::Json(storage->InternString(annotation.legacy_json_value())));
-  } else if (annotation.has_nested_value()) {
-    auto name = storage->GetString(name_id);
-    ParseNestedValueArgs(annotation.nested_value(), name, name, args_tracker,
-                         row_id);
-  }
-}
-
-void ProtoTraceParser::ParseNestedValueArgs(ConstBytes nested_value,
-                                            base::StringView flat_key,
-                                            base::StringView key,
-                                            ArgsTracker* args_tracker,
-                                            RowId row_id) {
-  protos::pbzero::DebugAnnotation::NestedValue::Decoder value(
-      nested_value.data, nested_value.size);
-  switch (value.nested_type()) {
-    case protos::pbzero::DebugAnnotation::NestedValue::UNSPECIFIED: {
-      auto flat_key_id = context_->storage->InternString(flat_key);
-      auto key_id = context_->storage->InternString(key);
-      // Leaf value.
-      if (value.has_bool_value()) {
-        args_tracker->AddArg(row_id, flat_key_id, key_id,
-                             Variadic::Boolean(value.bool_value()));
-      } else if (value.has_int_value()) {
-        args_tracker->AddArg(row_id, flat_key_id, key_id,
-                             Variadic::Integer(value.int_value()));
-      } else if (value.has_double_value()) {
-        args_tracker->AddArg(row_id, flat_key_id, key_id,
-                             Variadic::Real(value.double_value()));
-      } else if (value.has_string_value()) {
-        args_tracker->AddArg(row_id, flat_key_id, key_id,
-                             Variadic::String(context_->storage->InternString(
-                                 value.string_value())));
-      }
-      break;
-    }
-    case protos::pbzero::DebugAnnotation::NestedValue::DICT: {
-      auto key_it = value.dict_keys();
-      auto value_it = value.dict_values();
-      for (; key_it && value_it; ++key_it, ++value_it) {
-        std::string child_name = (*key_it).ToStdString();
-        std::string child_flat_key = flat_key.ToStdString() + "." + child_name;
-        std::string child_key = key.ToStdString() + "." + child_name;
-        ParseNestedValueArgs(*value_it, base::StringView(child_flat_key),
-                             base::StringView(child_key), args_tracker, row_id);
-      }
-      break;
-    }
-    case protos::pbzero::DebugAnnotation::NestedValue::ARRAY: {
-      int child_index = 0;
-      std::string child_flat_key = flat_key.ToStdString();
-      for (auto value_it = value.array_values(); value_it;
-           ++value_it, ++child_index) {
-        std::string child_key =
-            key.ToStdString() + "[" + std::to_string(child_index) + "]";
-        ParseNestedValueArgs(*value_it, base::StringView(child_flat_key),
-                             base::StringView(child_key), args_tracker, row_id);
-      }
-      break;
-    }
-  }
-}
-
-void ProtoTraceParser::ParseTaskExecutionArgs(
-    ConstBytes task_execution,
-    ProtoIncrementalState::PacketSequenceState* sequence_state,
-    size_t sequence_state_generation,
-    ArgsTracker* args_tracker,
-    RowId row) {
-  protos::pbzero::TaskExecution::Decoder task(task_execution.data,
-                                              task_execution.size);
-  uint64_t iid = task.posted_from_iid();
-  if (!iid)
-    return;
-
-  auto* decoder = sequence_state->LookupInternedMessage<
-      protos::pbzero::InternedData::kSourceLocationsFieldNumber,
-      protos::pbzero::SourceLocation>(sequence_state_generation, iid);
-  if (!decoder)
-    return;
-
-  StringId file_name_id = 0;
-  StringId function_name_id = 0;
-  uint32_t line_number = 0;
-
-  TraceStorage* storage = context_->storage.get();
-  file_name_id = storage->InternString(decoder->file_name());
-  function_name_id = storage->InternString(decoder->function_name());
-  line_number = decoder->line_number();
-
-  args_tracker->AddArg(row, task_file_name_args_key_id_,
-                       task_file_name_args_key_id_,
-                       Variadic::String(file_name_id));
-  args_tracker->AddArg(row, task_function_name_args_key_id_,
-                       task_function_name_args_key_id_,
-                       Variadic::String(function_name_id));
-
-  args_tracker->AddArg(row, task_line_number_args_key_id_,
-                       task_line_number_args_key_id_,
-                       Variadic::UnsignedInteger(line_number));
-}
-
-void ProtoTraceParser::ParseLogMessage(
-    ConstBytes blob,
-    ProtoIncrementalState::PacketSequenceState* sequence_state,
-    size_t sequence_state_generation,
-    int64_t ts,
-    base::Optional<UniqueTid> utid,
-    ArgsTracker* args_tracker,
-    RowId row) {
-  if (!utid) {
-    context_->storage->IncrementStats(stats::track_event_parser_errors);
-    PERFETTO_DLOG("LogMessage without thread association");
-    return;
-  }
-
-  protos::pbzero::LogMessage::Decoder message(blob.data, blob.size);
-
-  TraceStorage* storage = context_->storage.get();
-
-  StringId log_message_id = 0;
-
-  auto* decoder = sequence_state->LookupInternedMessage<
-      protos::pbzero::InternedData::kLogMessageBodyFieldNumber,
-      protos::pbzero::LogMessageBody>(sequence_state_generation,
-                                      message.body_iid());
-  if (!decoder)
-    return;
-
-  log_message_id = storage->InternString(decoder->body());
-
-  // TODO(nicomazz): LogMessage also contains the source of the message (file
-  // and line number). Android logs doesn't support this so far.
-  context_->storage->mutable_android_log()->AddLogEvent(
-      ts, *utid,
-      /*priority*/ 0,
-      /*tag_id*/ 0,  // TODO(nicomazz): Abuse tag_id to display
-                     // "file_name:line_number".
-      log_message_id);
-
-  args_tracker->AddArg(row, log_message_body_key_id_, log_message_body_key_id_,
-                       Variadic::String(log_message_id));
-  // TODO(nicomazz): Add the source location as an argument.
-}
-
 void ProtoTraceParser::ParseChromeBenchmarkMetadata(ConstBytes blob) {
   TraceStorage* storage = context_->storage.get();
   protos::pbzero::ChromeBenchmarkMetadata::Decoder packet(blob.data, blob.size);
diff --git a/src/trace_processor/proto_trace_parser.h b/src/trace_processor/proto_trace_parser.h
index 1ed2ac7..8e52c8a 100644
--- a/src/trace_processor/proto_trace_parser.h
+++ b/src/trace_processor/proto_trace_parser.h
@@ -26,20 +26,18 @@
 #include "perfetto/ext/base/string_view.h"
 #include "perfetto/protozero/field.h"
 #include "src/trace_processor/graphics_event_parser.h"
-#include "src/trace_processor/proto_incremental_state.h"
-#include "src/trace_processor/slice_tracker.h"
 #include "src/trace_processor/timestamped_trace_piece.h"
 #include "src/trace_processor/trace_blob_view.h"
 #include "src/trace_processor/trace_parser.h"
 #include "src/trace_processor/trace_storage.h"
 
 #include "protos/perfetto/trace/trace_packet.pbzero.h"
-#include "protos/perfetto/trace/track_event/track_event.pbzero.h"
 
 namespace perfetto {
 namespace trace_processor {
 
 class ArgsTracker;
+class PacketSequenceState;
 class TraceProcessorContext;
 
 class ProtoTraceParser : public TraceParser {
@@ -68,56 +66,19 @@
   void ParseAndroidLogStats(ConstBytes);
   void ParseTraceStats(ConstBytes);
   void ParseProfilePacket(int64_t ts,
-                          ProtoIncrementalState::PacketSequenceState*,
+                          PacketSequenceState*,
                           size_t sequence_state_generation,
                           ConstBytes);
-  void ParseStreamingProfilePacket(ProtoIncrementalState::PacketSequenceState*,
+  void ParseStreamingProfilePacket(PacketSequenceState*,
                                    size_t sequence_state_generation,
                                    ConstBytes);
   void ParseSystemInfo(ConstBytes);
-  void ParseTrackEvent(int64_t ts,
-                       int64_t tts,
-                       int64_t ticount,
-                       ProtoIncrementalState::PacketSequenceState*,
-                       size_t sequence_state_generation,
-                       ConstBytes);
-  void ParseLegacyEventAsRawEvent(
-      int64_t ts,
-      int64_t tts,
-      int64_t ticount,
-      base::Optional<UniqueTid> utid,
-      StringId category_id,
-      StringId name_id,
-      const protos::pbzero::TrackEvent::LegacyEvent::Decoder& legacy_event,
-      SliceTracker::SetArgsCallback args_callback);
-  void ParseDebugAnnotationArgs(ConstBytes debug_annotation,
-                                ProtoIncrementalState::PacketSequenceState*,
-                                size_t sequence_state_generation,
-                                ArgsTracker* args_tracker,
-                                RowId row);
-  void ParseNestedValueArgs(ConstBytes nested_value,
-                            base::StringView flat_key,
-                            base::StringView key,
-                            ArgsTracker* args_tracker,
-                            RowId row);
-  void ParseTaskExecutionArgs(ConstBytes task_execution,
-                              ProtoIncrementalState::PacketSequenceState*,
-                              size_t sequence_state_generation,
-                              ArgsTracker* args_tracker,
-                              RowId row);
   void ParseChromeBenchmarkMetadata(ConstBytes);
   void ParseChromeEvents(int64_t ts, ConstBytes);
   void ParseMetatraceEvent(int64_t ts, ConstBytes);
   void ParseTraceConfig(ConstBytes);
   void ParseStatsdMetadata(ConstBytes);
   void ParseAndroidPackagesList(ConstBytes);
-  void ParseLogMessage(ConstBytes,
-                       ProtoIncrementalState::PacketSequenceState*,
-                       size_t sequence_state_generation,
-                       int64_t,
-                       base::Optional<UniqueTid>,
-                       ArgsTracker*,
-                       RowId);
   void ParseModuleSymbols(ConstBytes);
   void ParseHeapGraph(int64_t ts, ConstBytes);
 
@@ -144,34 +105,10 @@
   const StringId batt_current_avg_id_;
   const StringId oom_score_adj_id_;
   const StringId metatrace_id_;
-  const StringId task_file_name_args_key_id_;
-  const StringId task_function_name_args_key_id_;
-  const StringId task_line_number_args_key_id_;
-  const StringId log_message_body_key_id_;
   const StringId data_name_id_;
   const StringId raw_chrome_metadata_event_id_;
   const StringId raw_chrome_legacy_system_trace_event_id_;
   const StringId raw_chrome_legacy_user_trace_event_id_;
-  const StringId raw_legacy_event_id_;
-  const StringId legacy_event_category_key_id_;
-  const StringId legacy_event_name_key_id_;
-  const StringId legacy_event_phase_key_id_;
-  const StringId legacy_event_duration_ns_key_id_;
-  const StringId legacy_event_thread_timestamp_ns_key_id_;
-  const StringId legacy_event_thread_duration_ns_key_id_;
-  const StringId legacy_event_thread_instruction_count_key_id_;
-  const StringId legacy_event_thread_instruction_delta_key_id_;
-  const StringId legacy_event_use_async_tts_key_id_;
-  const StringId legacy_event_unscoped_id_key_id_;
-  const StringId legacy_event_global_id_key_id_;
-  const StringId legacy_event_local_id_key_id_;
-  const StringId legacy_event_id_scope_key_id_;
-  const StringId legacy_event_bind_id_key_id_;
-  const StringId legacy_event_bind_to_enclosing_key_id_;
-  const StringId legacy_event_flow_direction_key_id_;
-  const StringId flow_direction_value_in_id_;
-  const StringId flow_direction_value_out_id_;
-  const StringId flow_direction_value_inout_id_;
   std::vector<StringId> meminfo_strs_id_;
   std::vector<StringId> vmstat_strs_id_;
   std::vector<StringId> power_rails_strs_id_;
diff --git a/src/trace_processor/proto_trace_tokenizer.cc b/src/trace_processor/proto_trace_tokenizer.cc
index 379573b..17afd4e 100644
--- a/src/trace_processor/proto_trace_tokenizer.cc
+++ b/src/trace_processor/proto_trace_tokenizer.cc
@@ -30,14 +30,12 @@
 #include "src/trace_processor/clock_tracker.h"
 #include "src/trace_processor/event_tracker.h"
 #include "src/trace_processor/importers/ftrace/ftrace_module.h"
+#include "src/trace_processor/importers/proto/packet_sequence_state.h"
+#include "src/trace_processor/importers/proto/proto_incremental_state.h"
 #include "src/trace_processor/importers/proto/track_event_module.h"
-#include "src/trace_processor/process_tracker.h"
-#include "src/trace_processor/proto_incremental_state.h"
 #include "src/trace_processor/stats.h"
-#include "src/trace_processor/trace_blob_view.h"
 #include "src/trace_processor/trace_sorter.h"
 #include "src/trace_processor/trace_storage.h"
-#include "src/trace_processor/track_tracker.h"
 
 #include "protos/perfetto/config/trace_config.pbzero.h"
 #include "protos/perfetto/trace/clock_snapshot.pbzero.h"
@@ -45,17 +43,10 @@
 #include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
 #include "protos/perfetto/trace/profiling/profile_common.pbzero.h"
 #include "protos/perfetto/trace/trace.pbzero.h"
-#include "protos/perfetto/trace/track_event/process_descriptor.pbzero.h"
-#include "protos/perfetto/trace/track_event/source_location.pbzero.h"
-#include "protos/perfetto/trace/track_event/task_execution.pbzero.h"
-#include "protos/perfetto/trace/track_event/thread_descriptor.pbzero.h"
-#include "protos/perfetto/trace/track_event/track_descriptor.pbzero.h"
-#include "protos/perfetto/trace/track_event/track_event.pbzero.h"
 
 namespace perfetto {
 namespace trace_processor {
 
-using protozero::ProtoDecoder;
 using protozero::proto_utils::MakeTagLengthDelimited;
 using protozero::proto_utils::ParseVarInt;
 
@@ -287,36 +278,16 @@
   }
 
   ModuleResult res = ModuleResult::Ignored();
-  res = context_->ftrace_module->TokenizePacket(decoder, &packet, timestamp);
+  res = context_->ftrace_module->TokenizePacket(decoder, &packet, timestamp,
+                                                state);
   if (!res.ignored())
     return res.ToStatus();
 
-  res =
-      context_->track_event_module->TokenizePacket(decoder, &packet, timestamp);
+  res = context_->track_event_module->TokenizePacket(decoder, &packet,
+                                                     timestamp, state);
   if (!res.ignored())
     return res.ToStatus();
 
-  if (decoder.has_track_descriptor()) {
-    ParseTrackDescriptorPacket(decoder);
-    return util::OkStatus();
-  }
-
-  if (decoder.has_track_event()) {
-    ParseTrackEventPacket(decoder, std::move(packet), timestamp);
-    return util::OkStatus();
-  }
-
-  // TODO(eseckler): Remove this once Chrome has switched fully over to
-  // TrackDescriptors.
-  if (decoder.has_thread_descriptor()) {
-    ParseThreadDescriptorPacket(decoder);
-    return util::OkStatus();
-  }
-  if (decoder.has_process_descriptor()) {
-    ParseProcessDescriptorPacket(decoder);
-    return util::OkStatus();
-  }
-
   if (decoder.has_compressed_packets()) {
     protozero::ConstBytes field = decoder.compressed_packets();
     const size_t field_off = packet.offset_of(field.data);
@@ -431,185 +402,6 @@
   }
 }
 
-void ProtoTraceTokenizer::ParseTrackDescriptorPacket(
-    const protos::pbzero::TracePacket::Decoder& packet_decoder) {
-  auto track_descriptor_field = packet_decoder.track_descriptor();
-  protos::pbzero::TrackDescriptor::Decoder track_descriptor_decoder(
-      track_descriptor_field.data, track_descriptor_field.size);
-
-  if (!track_descriptor_decoder.has_uuid()) {
-    PERFETTO_ELOG("TrackDescriptor packet without trusted_packet_sequence_id");
-    context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
-    return;
-  }
-
-  base::Optional<UniquePid> upid;
-  base::Optional<UniqueTid> utid;
-
-  if (track_descriptor_decoder.has_process()) {
-    auto process_descriptor_field = track_descriptor_decoder.process();
-    protos::pbzero::ProcessDescriptor::Decoder process_descriptor_decoder(
-        process_descriptor_field.data, process_descriptor_field.size);
-
-    // TODO(eseckler): Also parse process name / type here.
-
-    upid = context_->process_tracker->GetOrCreateProcess(
-        static_cast<uint32_t>(process_descriptor_decoder.pid()));
-  }
-
-  if (track_descriptor_decoder.has_thread()) {
-    auto thread_descriptor_field = track_descriptor_decoder.thread();
-    protos::pbzero::ThreadDescriptor::Decoder thread_descriptor_decoder(
-        thread_descriptor_field.data, thread_descriptor_field.size);
-
-    ParseThreadDescriptor(thread_descriptor_decoder);
-    utid = context_->process_tracker->UpdateThread(
-        static_cast<uint32_t>(thread_descriptor_decoder.tid()),
-        static_cast<uint32_t>(thread_descriptor_decoder.pid()));
-    upid = *context_->storage->GetThread(*utid).upid;
-  }
-
-  StringId name_id =
-      context_->storage->InternString(track_descriptor_decoder.name());
-
-  context_->track_tracker->UpdateDescriptorTrack(
-      track_descriptor_decoder.uuid(), name_id, upid, utid);
-}
-
-void ProtoTraceTokenizer::ParseProcessDescriptorPacket(
-    const protos::pbzero::TracePacket::Decoder& packet_decoder) {
-  protos::pbzero::ProcessDescriptor::Decoder process_descriptor_decoder(
-      packet_decoder.process_descriptor());
-  if (!process_descriptor_decoder.has_chrome_process_type())
-    return;
-  base::StringView name = "Unknown";
-  switch (process_descriptor_decoder.chrome_process_type()) {
-    case protos::pbzero::ProcessDescriptor::PROCESS_BROWSER:
-      name = "Browser";
-      break;
-    case protos::pbzero::ProcessDescriptor::PROCESS_RENDERER:
-      name = "Renderer";
-      break;
-    case protos::pbzero::ProcessDescriptor::PROCESS_UTILITY:
-      name = "Utility";
-      break;
-    case protos::pbzero::ProcessDescriptor::PROCESS_ZYGOTE:
-      name = "Zygote";
-      break;
-    case protos::pbzero::ProcessDescriptor::PROCESS_SANDBOX_HELPER:
-      name = "SandboxHelper";
-      break;
-    case protos::pbzero::ProcessDescriptor::PROCESS_GPU:
-      name = "Gpu";
-      break;
-    case protos::pbzero::ProcessDescriptor::PROCESS_PPAPI_PLUGIN:
-      name = "PpapiPlugin";
-      break;
-    case protos::pbzero::ProcessDescriptor::PROCESS_PPAPI_BROKER:
-      name = "PpapiBroker";
-      break;
-  }
-  context_->process_tracker->SetProcessMetadata(
-      static_cast<uint32_t>(process_descriptor_decoder.pid()), base::nullopt,
-      name);
-}
-
-void ProtoTraceTokenizer::ParseThreadDescriptorPacket(
-    const protos::pbzero::TracePacket::Decoder& packet_decoder) {
-  if (PERFETTO_UNLIKELY(!packet_decoder.has_trusted_packet_sequence_id())) {
-    PERFETTO_ELOG("ThreadDescriptor packet without trusted_packet_sequence_id");
-    context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
-    return;
-  }
-
-  auto* state = GetIncrementalStateForPacketSequence(
-      packet_decoder.trusted_packet_sequence_id());
-
-  // TrackEvents will be ignored while incremental state is invalid. As a
-  // consequence, we should also ignore any ThreadDescriptors received in this
-  // state. Otherwise, any delta-encoded timestamps would be calculated
-  // incorrectly once we move out of the packet loss state. Instead, wait until
-  // the first subsequent descriptor after incremental state is cleared.
-  if (!state->IsIncrementalStateValid()) {
-    context_->storage->IncrementStats(stats::tokenizer_skipped_packets);
-    return;
-  }
-
-  auto thread_descriptor_field = packet_decoder.thread_descriptor();
-  protos::pbzero::ThreadDescriptor::Decoder thread_descriptor_decoder(
-      thread_descriptor_field.data, thread_descriptor_field.size);
-
-  state->SetThreadDescriptor(
-      thread_descriptor_decoder.pid(), thread_descriptor_decoder.tid(),
-      thread_descriptor_decoder.reference_timestamp_us() * 1000,
-      thread_descriptor_decoder.reference_thread_time_us() * 1000,
-      thread_descriptor_decoder.reference_thread_instruction_count());
-
-  ParseThreadDescriptor(thread_descriptor_decoder);
-}
-
-void ProtoTraceTokenizer::ParseThreadDescriptor(
-    const protos::pbzero::ThreadDescriptor::Decoder&
-        thread_descriptor_decoder) {
-  base::StringView name;
-  if (thread_descriptor_decoder.has_thread_name()) {
-    name = thread_descriptor_decoder.thread_name();
-  } else if (thread_descriptor_decoder.has_chrome_thread_type()) {
-    using protos::pbzero::ThreadDescriptor;
-    switch (thread_descriptor_decoder.chrome_thread_type()) {
-      case ThreadDescriptor::CHROME_THREAD_MAIN:
-        name = "CrProcessMain";
-        break;
-      case ThreadDescriptor::CHROME_THREAD_IO:
-        name = "ChromeIOThread";
-        break;
-      case ThreadDescriptor::CHROME_THREAD_POOL_FG_WORKER:
-        name = "ThreadPoolForegroundWorker&";
-        break;
-      case ThreadDescriptor::CHROME_THREAD_POOL_BG_WORKER:
-        name = "ThreadPoolBackgroundWorker&";
-        break;
-      case ThreadDescriptor::CHROME_THREAD_POOL_FB_BLOCKING:
-        name = "ThreadPoolSingleThreadForegroundBlocking&";
-        break;
-      case ThreadDescriptor::CHROME_THREAD_POOL_BG_BLOCKING:
-        name = "ThreadPoolSingleThreadBackgroundBlocking&";
-        break;
-      case ThreadDescriptor::CHROME_THREAD_POOL_SERVICE:
-        name = "ThreadPoolService";
-        break;
-      case ThreadDescriptor::CHROME_THREAD_COMPOSITOR_WORKER:
-        name = "CompositorTileWorker&";
-        break;
-      case ThreadDescriptor::CHROME_THREAD_COMPOSITOR:
-        name = "Compositor";
-        break;
-      case ThreadDescriptor::CHROME_THREAD_VIZ_COMPOSITOR:
-        name = "VizCompositorThread";
-        break;
-      case ThreadDescriptor::CHROME_THREAD_SERVICE_WORKER:
-        name = "ServiceWorkerThread&";
-        break;
-      case ThreadDescriptor::CHROME_THREAD_MEMORY_INFRA:
-        name = "MemoryInfra";
-        break;
-      case ThreadDescriptor::CHROME_THREAD_SAMPLING_PROFILER:
-        name = "StackSamplingProfiler";
-        break;
-      case ThreadDescriptor::CHROME_THREAD_UNSPECIFIED:
-        name = "ChromeUnspecified";
-        break;
-    }
-  }
-
-  if (!name.empty()) {
-    auto thread_name_id = context_->storage->InternString(name);
-    ProcessTracker* procs = context_->process_tracker.get();
-    procs->UpdateThreadName(
-        static_cast<uint32_t>(thread_descriptor_decoder.tid()), thread_name_id);
-  }
-}
-
 util::Status ProtoTraceTokenizer::ParseClockSnapshot(ConstBytes blob,
                                                      uint32_t seq_id) {
   std::map<ClockTracker::ClockId, int64_t> clock_map;
@@ -632,124 +424,5 @@
   return util::OkStatus();
 }
 
-void ProtoTraceTokenizer::ParseTrackEventPacket(
-    const protos::pbzero::TracePacket::Decoder& packet_decoder,
-    TraceBlobView packet,
-    int64_t packet_timestamp) {
-  constexpr auto kTimestampDeltaUsFieldNumber =
-      protos::pbzero::TrackEvent::kTimestampDeltaUsFieldNumber;
-  constexpr auto kTimestampAbsoluteUsFieldNumber =
-      protos::pbzero::TrackEvent::kTimestampAbsoluteUsFieldNumber;
-  constexpr auto kThreadTimeDeltaUsFieldNumber =
-      protos::pbzero::TrackEvent::kThreadTimeDeltaUsFieldNumber;
-  constexpr auto kThreadTimeAbsoluteUsFieldNumber =
-      protos::pbzero::TrackEvent::kThreadTimeAbsoluteUsFieldNumber;
-  constexpr auto kThreadInstructionCountDeltaFieldNumber =
-      protos::pbzero::TrackEvent::kThreadInstructionCountDeltaFieldNumber;
-  constexpr auto kThreadInstructionCountAbsoluteFieldNumber =
-      protos::pbzero::TrackEvent::kThreadInstructionCountAbsoluteFieldNumber;
-
-  if (PERFETTO_UNLIKELY(!packet_decoder.has_trusted_packet_sequence_id())) {
-    PERFETTO_ELOG("TrackEvent packet without trusted_packet_sequence_id");
-    context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
-    return;
-  }
-
-  auto* state = GetIncrementalStateForPacketSequence(
-      packet_decoder.trusted_packet_sequence_id());
-
-  // TODO(eseckler): For now, TrackEvents can only be parsed correctly while
-  // incremental state for their sequence is valid, because chromium doesn't set
-  // SEQ_NEEDS_INCREMENTAL_STATE yet. Remove this once it does.
-  if (!state->IsIncrementalStateValid()) {
-    context_->storage->IncrementStats(stats::tokenizer_skipped_packets);
-    return;
-  }
-
-  auto field = packet_decoder.track_event();
-  ProtoDecoder event_decoder(field.data, field.size);
-
-  int64_t timestamp;
-  int64_t thread_timestamp = 0;
-  int64_t thread_instructions = 0;
-
-  // TODO(eseckler): Remove handling of timestamps relative to ThreadDescriptors
-  // once all producers have switched to clock-domain timestamps (e.g.
-  // TracePacket's timestamp).
-
-  if (auto ts_delta_field =
-          event_decoder.FindField(kTimestampDeltaUsFieldNumber)) {
-    // Delta timestamps require a valid ThreadDescriptor packet since the last
-    // packet loss.
-    if (!state->track_event_timestamps_valid()) {
-      context_->storage->IncrementStats(stats::tokenizer_skipped_packets);
-      return;
-    }
-    timestamp = state->IncrementAndGetTrackEventTimeNs(
-        ts_delta_field.as_int64() * 1000);
-
-    // Legacy TrackEvent timestamp fields are in MONOTONIC domain. Adjust to
-    // trace time if we have a clock snapshot.
-    auto trace_ts = context_->clock_tracker->ToTraceTime(
-        protos::pbzero::ClockSnapshot::Clock::MONOTONIC, timestamp);
-    if (trace_ts.has_value())
-      timestamp = trace_ts.value();
-  } else if (auto ts_absolute_field =
-                 event_decoder.FindField(kTimestampAbsoluteUsFieldNumber)) {
-    // One-off absolute timestamps don't affect delta computation.
-    timestamp = ts_absolute_field.as_int64() * 1000;
-
-    // Legacy TrackEvent timestamp fields are in MONOTONIC domain. Adjust to
-    // trace time if we have a clock snapshot.
-    auto trace_ts = context_->clock_tracker->ToTraceTime(
-        protos::pbzero::ClockSnapshot::Clock::MONOTONIC, timestamp);
-    if (trace_ts.has_value())
-      timestamp = trace_ts.value();
-  } else if (packet_decoder.has_timestamp()) {
-    timestamp = packet_timestamp;
-  } else {
-    PERFETTO_ELOG("TrackEvent without timestamp");
-    context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
-    return;
-  }
-
-  if (auto tt_delta_field =
-          event_decoder.FindField(kThreadTimeDeltaUsFieldNumber)) {
-    // Delta timestamps require a valid ThreadDescriptor packet since the last
-    // packet loss.
-    if (!state->track_event_timestamps_valid()) {
-      context_->storage->IncrementStats(stats::tokenizer_skipped_packets);
-      return;
-    }
-    thread_timestamp = state->IncrementAndGetTrackEventThreadTimeNs(
-        tt_delta_field.as_int64() * 1000);
-  } else if (auto tt_absolute_field =
-                 event_decoder.FindField(kThreadTimeAbsoluteUsFieldNumber)) {
-    // One-off absolute timestamps don't affect delta computation.
-    thread_timestamp = tt_absolute_field.as_int64() * 1000;
-  }
-
-  if (auto ti_delta_field =
-          event_decoder.FindField(kThreadInstructionCountDeltaFieldNumber)) {
-    // Delta timestamps require a valid ThreadDescriptor packet since the last
-    // packet loss.
-    if (!state->track_event_timestamps_valid()) {
-      context_->storage->IncrementStats(stats::tokenizer_skipped_packets);
-      return;
-    }
-    thread_instructions =
-        state->IncrementAndGetTrackEventThreadInstructionCount(
-            ti_delta_field.as_int64());
-  } else if (auto ti_absolute_field = event_decoder.FindField(
-                 kThreadInstructionCountAbsoluteFieldNumber)) {
-    // One-off absolute timestamps don't affect delta computation.
-    thread_instructions = ti_absolute_field.as_int64();
-  }
-
-  context_->sorter->PushTrackEventPacket(timestamp, thread_timestamp,
-                                         thread_instructions, state,
-                                         std::move(packet));
-}
-
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/proto_trace_tokenizer.h b/src/trace_processor/proto_trace_tokenizer.h
index 40564f3..81d0425 100644
--- a/src/trace_processor/proto_trace_tokenizer.h
+++ b/src/trace_processor/proto_trace_tokenizer.h
@@ -23,12 +23,10 @@
 #include <vector>
 
 #include "src/trace_processor/chunked_trace_reader.h"
-#include "src/trace_processor/proto_incremental_state.h"
+#include "src/trace_processor/importers/proto/proto_incremental_state.h"
+#include "src/trace_processor/trace_blob_view.h"
 #include "src/trace_processor/trace_processor_impl.h"
 
-#include "protos/perfetto/trace/trace_packet.pbzero.h"
-#include "protos/perfetto/trace/track_event/thread_descriptor.pbzero.h"
-
 namespace protozero {
 struct ConstBytes;
 }
@@ -36,8 +34,8 @@
 namespace perfetto {
 namespace trace_processor {
 
+class PacketSequenceState;
 class TraceProcessorContext;
-class TraceBlobView;
 class TraceSorter;
 class TraceStorage;
 
@@ -65,17 +63,8 @@
   void HandlePreviousPacketDropped(const protos::pbzero::TracePacket::Decoder&);
   void ParseInternedData(const protos::pbzero::TracePacket::Decoder&,
                          TraceBlobView interned_data);
-  void ParseTrackDescriptorPacket(const protos::pbzero::TracePacket::Decoder&);
-  void ParseProcessDescriptorPacket(
-      const protos::pbzero::TracePacket::Decoder&);
-  void ParseThreadDescriptorPacket(const protos::pbzero::TracePacket::Decoder&);
-  void ParseThreadDescriptor(const protos::pbzero::ThreadDescriptor::Decoder&);
-  void ParseTrackEventPacket(const protos::pbzero::TracePacket::Decoder&,
-                             TraceBlobView packet,
-                             int64_t packet_timestamp);
-
-  ProtoIncrementalState::PacketSequenceState*
-  GetIncrementalStateForPacketSequence(uint32_t sequence_id) {
+  PacketSequenceState* GetIncrementalStateForPacketSequence(
+      uint32_t sequence_id) {
     if (!incremental_state)
       incremental_state.reset(new ProtoIncrementalState(context_));
     return incremental_state->GetOrCreateStateForPacketSequence(sequence_id);
diff --git a/src/trace_processor/timestamped_trace_piece.h b/src/trace_processor/timestamped_trace_piece.h
index ac3fd26..dded81b 100644
--- a/src/trace_processor/timestamped_trace_piece.h
+++ b/src/trace_processor/timestamped_trace_piece.h
@@ -20,7 +20,7 @@
 #include "perfetto/base/build_config.h"
 #include "perfetto/trace_processor/basic_types.h"
 #include "src/trace_processor/importers/fuchsia/fuchsia_provider_view.h"
-#include "src/trace_processor/proto_incremental_state.h"
+#include "src/trace_processor/importers/proto/packet_sequence_state.h"
 #include "src/trace_processor/trace_blob_view.h"
 #include "src/trace_processor/trace_processor_context.h"
 #include "src/trace_processor/trace_storage.h"
@@ -65,11 +65,10 @@
 // A TimestampedTracePiece is (usually a reference to) a piece of a trace that
 // is sorted by TraceSorter.
 struct TimestampedTracePiece {
-  TimestampedTracePiece(
-      int64_t ts,
-      uint64_t idx,
-      TraceBlobView tbv,
-      ProtoIncrementalState::PacketSequenceState* sequence_state)
+  TimestampedTracePiece(int64_t ts,
+                        uint64_t idx,
+                        TraceBlobView tbv,
+                        PacketSequenceState* sequence_state)
       : TimestampedTracePiece(ts,
                               /*thread_ts=*/0,
                               /*thread_instructions=*/0,
@@ -120,13 +119,12 @@
                               /*sequence_state=*/nullptr,
                               InlineEvent{}) {}
 
-  TimestampedTracePiece(
-      int64_t ts,
-      int64_t thread_ts,
-      int64_t thread_instructions,
-      uint64_t idx,
-      TraceBlobView tbv,
-      ProtoIncrementalState::PacketSequenceState* sequence_state)
+  TimestampedTracePiece(int64_t ts,
+                        int64_t thread_ts,
+                        int64_t thread_instructions,
+                        uint64_t idx,
+                        TraceBlobView tbv,
+                        PacketSequenceState* sequence_state)
       : TimestampedTracePiece(ts,
                               thread_ts,
                               thread_instructions,
@@ -148,16 +146,15 @@
                               /*sequence_state=*/nullptr,
                               inline_evt) {}
 
-  TimestampedTracePiece(
-      int64_t ts,
-      int64_t thread_ts,
-      int64_t thread_instructions,
-      uint64_t idx,
-      TraceBlobView tbv,
-      std::unique_ptr<Json::Value> value,
-      std::unique_ptr<FuchsiaProviderView> fpv,
-      ProtoIncrementalState::PacketSequenceState* sequence_state,
-      InlineEvent inline_evt)
+  TimestampedTracePiece(int64_t ts,
+                        int64_t thread_ts,
+                        int64_t thread_instructions,
+                        uint64_t idx,
+                        TraceBlobView tbv,
+                        std::unique_ptr<Json::Value> value,
+                        std::unique_ptr<FuchsiaProviderView> fpv,
+                        PacketSequenceState* sequence_state,
+                        InlineEvent inline_evt)
       : json_value(std::move(value)),
         fuchsia_provider_view(std::move(fpv)),
         packet_sequence_state(sequence_state),
@@ -186,7 +183,7 @@
 
   std::unique_ptr<Json::Value> json_value;
   std::unique_ptr<FuchsiaProviderView> fuchsia_provider_view;
-  ProtoIncrementalState::PacketSequenceState* packet_sequence_state;
+  PacketSequenceState* packet_sequence_state;
   size_t packet_sequence_state_generation;
 
   int64_t timestamp;
diff --git a/src/trace_processor/trace_sorter.h b/src/trace_processor/trace_sorter.h
index 6320fad..82f5acd 100644
--- a/src/trace_processor/trace_sorter.h
+++ b/src/trace_processor/trace_sorter.h
@@ -21,7 +21,6 @@
 
 #include "perfetto/ext/base/circular_queue.h"
 #include "perfetto/trace_processor/basic_types.h"
-#include "src/trace_processor/proto_incremental_state.h"
 #include "src/trace_processor/timestamped_trace_piece.h"
 #include "src/trace_processor/trace_blob_view.h"
 #include "src/trace_processor/trace_processor_context.h"
@@ -35,6 +34,7 @@
 namespace trace_processor {
 
 class FuchsiaProviderView;
+class PacketSequenceState;
 
 // This class takes care of sorting events parsed from the trace stream in
 // arbitrary order and pushing them to the next pipeline stages (parsing) in
@@ -68,7 +68,7 @@
   TraceSorter(TraceProcessorContext*, int64_t window_size_ns);
 
   inline void PushTracePacket(int64_t timestamp,
-                              ProtoIncrementalState::PacketSequenceState* state,
+                              PacketSequenceState* state,
                               TraceBlobView packet) {
     DCHECK_ftrace_batch_cpu(kNoBatch);
     auto* queue = GetQueue(0);
@@ -124,12 +124,11 @@
         TimestampedTracePiece(timestamp, packet_idx_++, inline_event));
   }
 
-  inline void PushTrackEventPacket(
-      int64_t timestamp,
-      int64_t thread_time,
-      int64_t thread_instruction_count,
-      ProtoIncrementalState::PacketSequenceState* state,
-      TraceBlobView packet) {
+  inline void PushTrackEventPacket(int64_t timestamp,
+                                   int64_t thread_time,
+                                   int64_t thread_instruction_count,
+                                   PacketSequenceState* state,
+                                   TraceBlobView packet) {
     auto* queue = GetQueue(0);
     queue->Append(TimestampedTracePiece(timestamp, thread_time,
                                         thread_instruction_count, packet_idx_++,