processor: Support TrackDescriptor's parent_uuid + pid/tid reuse

Refactors tokenization and parsing of TrackDescriptors so that:
 - Only a single ProcessDescriptor track is supported per process.
 - Only a single ThreadDescriptor track is supported per thread.
 - Such a process/thread track is merged with slice-type system events
   into a single track.
 - Other tracks (e.g. async tracks) can be associated with a thread
   or process using the "parent_uuid" field.
 - pid/tid reuse (i.e. new process/thread spawns with same pid/tid
   after old process/thread terminates) is detected based on changing
   uuids of Process/ThreadDescriptor tracks.

To accomplish this, tokenization now only makes reservations for tracks
without actually inserting anything into the track table - since
TrackDescriptors may appear in any order in the file, and we cannot
know the track's type/context until we see its parent's descriptor.

During parsing, the reservations will be resolved and inserted into the
track table. If the uuid of a process's or thread's track changes, we
assume that the pid/tid of the process/thread was reused.

Change-Id: Iec1d019db211773eb15ee517ae0dac1ce87553c9
diff --git a/src/trace_processor/export_json.cc b/src/trace_processor/export_json.cc
index 04e3a5d..acc6e93 100644
--- a/src/trace_processor/export_json.cc
+++ b/src/trace_processor/export_json.cc
@@ -517,6 +517,12 @@
                           TraceFormatWriter* writer) {
   const auto& slices = storage->slice_table();
   for (uint32_t i = 0; i < slices.row_count(); ++i) {
+    // Skip slices with empty category - these are ftrace/system slices that
+    // were also imported into the raw table and will be exported from there by
+    // trace_to_text.
+    if (slices.category()[i] == kNullStringId)
+      continue;
+
     Json::Value event;
     event["ts"] = Json::Int64(slices.ts()[i] / 1000);
     event["cat"] = GetNonNullString(storage, slices.category()[i]);
@@ -551,14 +557,11 @@
 
     uint32_t track_row = *track_table.id().IndexOf(TrackId{track_id});
     auto track_args_id = track_table.source_arg_set_id()[track_row];
-    if (!track_args_id)
-      continue;
-    const auto& track_args = args_builder.GetArgs(*track_args_id);
-    bool legacy_chrome_track = track_args["source"].asString() == "chrome";
-    if (!track_args.isMember("source") ||
-        (!legacy_chrome_track &&
-         track_args["source"].asString() != "descriptor")) {
-      continue;
+    const Json::Value* track_args = nullptr;
+    bool legacy_chrome_track = false;
+    if (track_args_id) {
+      track_args = &args_builder.GetArgs(*track_args_id);
+      legacy_chrome_track = (*track_args)["source"].asString() == "chrome";
     }
 
     const auto& thread_table = storage->thread_table();
@@ -646,12 +649,13 @@
       }
       writer->WriteCommonEvent(event);
     } else if (!legacy_chrome_track ||
-               (legacy_chrome_track && track_args.isMember("source_id"))) {
+               (legacy_chrome_track && track_args->isMember("source_id"))) {
       // Async event slice.
       auto opt_process_row = process_track.id().IndexOf(TrackId{track_id});
       if (legacy_chrome_track) {
-        // Legacy async tracks are always process-associated.
+        // Legacy async tracks are always process-associated and have args.
         PERFETTO_DCHECK(opt_process_row);
+        PERFETTO_DCHECK(track_args);
         uint32_t upid = process_track.upid()[*opt_process_row];
         event["pid"] = static_cast<int32_t>(process_table.pid()[upid]);
         event["tid"] = legacy_tid
@@ -660,16 +664,16 @@
 
         // Preserve original event IDs for legacy tracks. This is so that e.g.
         // memory dump IDs show up correctly in the JSON trace.
-        PERFETTO_DCHECK(track_args.isMember("source_id"));
-        PERFETTO_DCHECK(track_args.isMember("source_id_is_process_scoped"));
-        PERFETTO_DCHECK(track_args.isMember("source_scope"));
+        PERFETTO_DCHECK(track_args->isMember("source_id"));
+        PERFETTO_DCHECK(track_args->isMember("source_id_is_process_scoped"));
+        PERFETTO_DCHECK(track_args->isMember("source_scope"));
         uint64_t source_id =
-            static_cast<uint64_t>(track_args["source_id"].asInt64());
-        std::string source_scope = track_args["source_scope"].asString();
+            static_cast<uint64_t>((*track_args)["source_id"].asInt64());
+        std::string source_scope = (*track_args)["source_scope"].asString();
         if (!source_scope.empty())
           event["scope"] = source_scope;
         bool source_id_is_process_scoped =
-            track_args["source_id_is_process_scoped"].asBool();
+            (*track_args)["source_id_is_process_scoped"].asBool();
         if (source_id_is_process_scoped) {
           event["id2"]["local"] = PrintUint64(source_id);
         } else {
@@ -730,6 +734,7 @@
       }
     } else {
       // Global or process-scoped instant event.
+      PERFETTO_DCHECK(legacy_chrome_track);
       PERFETTO_DCHECK(duration_ns == 0);
       // Use "I" instead of "i" phase for backwards-compat with old consumers.
       event["ph"] = "I";
diff --git a/src/trace_processor/export_json_unittest.cc b/src/trace_processor/export_json_unittest.cc
index e572d67..a0050df 100644
--- a/src/trace_processor/export_json_unittest.cc
+++ b/src/trace_processor/export_json_unittest.cc
@@ -119,8 +119,7 @@
   const char* kName = "name";
 
   UniqueTid utid = context_.process_tracker->GetOrCreateThread(kThreadID);
-  TrackId track =
-      context_.track_tracker->GetOrCreateDescriptorTrackForThread(utid);
+  TrackId track = context_.track_tracker->InternThreadTrack(utid);
   context_.args_tracker->Flush();  // Flush track args.
   StringId cat_id = context_.storage->InternString(base::StringView(kCategory));
   StringId name_id = context_.storage->InternString(base::StringView(kName));
@@ -166,8 +165,7 @@
   const char* kName = "name";
 
   UniqueTid utid = context_.process_tracker->GetOrCreateThread(kThreadID);
-  TrackId track =
-      context_.track_tracker->GetOrCreateDescriptorTrackForThread(utid);
+  TrackId track = context_.track_tracker->InternThreadTrack(utid);
   context_.args_tracker->Flush();  // Flush track args.
   StringId cat_id = context_.storage->InternString(base::StringView(kCategory));
   StringId name_id = context_.storage->InternString(base::StringView(kName));
@@ -225,13 +223,14 @@
   EXPECT_EQ(event["args"]["name"].asString(), kName);
 }
 
-TEST_F(ExportJsonTest, WrongTrackTypeIgnored) {
+TEST_F(ExportJsonTest, SystemEventsIgnored) {
   constexpr int64_t kCookie = 22;
   TrackId track = context_.track_tracker->InternAndroidAsyncTrack(
       /*name=*/0, /*upid=*/0, kCookie);
   context_.args_tracker->Flush();  // Flush track args.
 
-  StringId cat_id = context_.storage->InternString("cat");
+  // System events have no category.
+  StringId cat_id = kNullStringId;
   StringId name_id = context_.storage->InternString("name");
   context_.storage->mutable_slice_table()->Insert(
       {0, 0, track.value, cat_id, name_id, 0, 0, 0});
@@ -406,8 +405,7 @@
   const char* kSrc = "source_file.cc";
 
   UniqueTid utid = context_.process_tracker->GetOrCreateThread(0);
-  TrackId track =
-      context_.track_tracker->GetOrCreateDescriptorTrackForThread(utid);
+  TrackId track = context_.track_tracker->InternThreadTrack(utid);
   context_.args_tracker->Flush();  // Flush track args.
   StringId cat_id = context_.storage->InternString(base::StringView(kCategory));
   StringId name_id = context_.storage->InternString(base::StringView(kName));
@@ -451,8 +449,7 @@
   TraceStorage* storage = context_.storage.get();
 
   UniqueTid utid = context_.process_tracker->GetOrCreateThread(0);
-  TrackId track =
-      context_.track_tracker->GetOrCreateDescriptorTrackForThread(utid);
+  TrackId track = context_.track_tracker->InternThreadTrack(utid);
   context_.args_tracker->Flush();  // Flush track args.
   StringId cat_id = storage->InternString(base::StringView(kCategory));
   StringId name_id = storage->InternString(base::StringView(kName));
@@ -501,8 +498,7 @@
   double kValues[] = {1.234, 2.345};
 
   UniqueTid utid = context_.process_tracker->GetOrCreateThread(0);
-  TrackId track =
-      context_.track_tracker->GetOrCreateDescriptorTrackForThread(utid);
+  TrackId track = context_.track_tracker->InternThreadTrack(utid);
   context_.args_tracker->Flush();  // Flush track args.
   StringId cat_id = context_.storage->InternString(base::StringView(kCategory));
   StringId name_id = context_.storage->InternString(base::StringView(kName));
@@ -550,8 +546,7 @@
   uint64_t kValue1 = std::numeric_limits<uint64_t>::max();
 
   UniqueTid utid = context_.process_tracker->GetOrCreateThread(0);
-  TrackId track =
-      context_.track_tracker->GetOrCreateDescriptorTrackForThread(utid);
+  TrackId track = context_.track_tracker->InternThreadTrack(utid);
   context_.args_tracker->Flush();  // Flush track args.
   StringId cat_id = context_.storage->InternString(base::StringView(kCategory));
   StringId name_id = context_.storage->InternString(base::StringView(kName));
@@ -595,8 +590,7 @@
   int kValues[] = {123, 234};
 
   UniqueTid utid = context_.process_tracker->GetOrCreateThread(0);
-  TrackId track =
-      context_.track_tracker->GetOrCreateDescriptorTrackForThread(utid);
+  TrackId track = context_.track_tracker->InternThreadTrack(utid);
   context_.args_tracker->Flush();  // Flush track args.
   StringId cat_id = context_.storage->InternString(base::StringView(kCategory));
   StringId name_id = context_.storage->InternString(base::StringView(kName));
@@ -643,8 +637,7 @@
   int kValues[] = {123, 234};
 
   UniqueTid utid = context_.process_tracker->GetOrCreateThread(0);
-  TrackId track =
-      context_.track_tracker->GetOrCreateDescriptorTrackForThread(utid);
+  TrackId track = context_.track_tracker->InternThreadTrack(utid);
   context_.args_tracker->Flush();  // Flush track args.
   StringId cat_id = context_.storage->InternString(base::StringView(kCategory));
   StringId name_id = context_.storage->InternString(base::StringView(kName));
@@ -691,8 +684,7 @@
   const char* kName = "name";
 
   UniqueTid utid = context_.process_tracker->GetOrCreateThread(0);
-  TrackId track =
-      context_.track_tracker->GetOrCreateDescriptorTrackForThread(utid);
+  TrackId track = context_.track_tracker->InternThreadTrack(utid);
   context_.args_tracker->Flush();  // Flush track args.
   StringId cat_id = context_.storage->InternString(base::StringView(kCategory));
   StringId name_id = context_.storage->InternString(base::StringView(kName));
@@ -761,8 +753,7 @@
   const char* kName = "name";
 
   UniqueTid utid = context_.process_tracker->GetOrCreateThread(kThreadID);
-  TrackId track =
-      context_.track_tracker->GetOrCreateDescriptorTrackForThread(utid);
+  TrackId track = context_.track_tracker->InternThreadTrack(utid);
   context_.args_tracker->Flush();  // Flush track args.
   StringId cat_id = context_.storage->InternString(base::StringView(kCategory));
   StringId name_id = context_.storage->InternString(base::StringView(kName));
@@ -1241,8 +1232,7 @@
 
 TEST_F(ExportJsonTest, ArgumentFilter) {
   UniqueTid utid = context_.process_tracker->GetOrCreateThread(0);
-  TrackId track =
-      context_.track_tracker->GetOrCreateDescriptorTrackForThread(utid);
+  TrackId track = context_.track_tracker->InternThreadTrack(utid);
   context_.args_tracker->Flush();  // Flush track args.
 
   StringId cat_id = context_.storage->InternString(base::StringView("cat"));
@@ -1354,8 +1344,7 @@
   const char* kName = "name";
 
   UniqueTid utid = context_.process_tracker->GetOrCreateThread(kThreadID);
-  TrackId track =
-      context_.track_tracker->GetOrCreateDescriptorTrackForThread(utid);
+  TrackId track = context_.track_tracker->InternThreadTrack(utid);
   context_.args_tracker->Flush();  // Flush track args.
   StringId cat_id = context_.storage->InternString(base::StringView(kCategory));
   StringId name_id = context_.storage->InternString(base::StringView(kName));
diff --git a/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc b/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
index ac04c4f..424c7d2 100644
--- a/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
+++ b/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
@@ -786,7 +786,6 @@
   Tokenize();
 
   EXPECT_CALL(*process_, UpdateThread(16, 15))
-      .Times(3)
       .WillRepeatedly(Return(1));
 
   tables::ThreadTable::Row row(16);
@@ -867,7 +866,6 @@
   Tokenize();
 
   EXPECT_CALL(*process_, UpdateThread(16, 15))
-      .Times(3)
       .WillRepeatedly(Return(1));
 
   tables::ThreadTable::Row row(16);
@@ -1003,7 +1001,6 @@
   Tokenize();
 
   EXPECT_CALL(*process_, UpdateThread(16, 15))
-      .Times(5)
       .WillRepeatedly(Return(1));
 
   tables::ThreadTable::Row row(16);
@@ -1234,6 +1231,7 @@
     auto* packet = trace_.add_packet();
     packet->set_trusted_packet_sequence_id(1);
     packet->set_incremental_state_cleared(true);
+    packet->set_timestamp(1000000);
     auto* track_desc = packet->set_track_descriptor();
     track_desc->set_uuid(1234);
     track_desc->set_name("Thread track 1");
@@ -1244,6 +1242,7 @@
   {
     auto* packet = trace_.add_packet();
     packet->set_trusted_packet_sequence_id(1);
+    packet->set_timestamp(1000000);
     auto* track_desc = packet->set_track_descriptor();
     track_desc->set_uuid(5678);
     track_desc->set_name("Async track 1");
@@ -1297,6 +1296,7 @@
     auto* packet = trace_.add_packet();
     packet->set_trusted_packet_sequence_id(2);
     packet->set_incremental_state_cleared(true);
+    packet->set_timestamp(1000000);
     auto* track_desc = packet->set_track_descriptor();
     track_desc->set_uuid(4321);
     track_desc->set_name("Thread track 2");
@@ -1305,12 +1305,6 @@
     thread_desc->set_tid(17);
   }
   {
-    auto* packet = trace_.add_packet();
-    packet->set_trusted_packet_sequence_id(2);
-    auto* track_desc = packet->set_track_descriptor();
-    track_desc->set_uuid(5678);  // "Async track 1" defined on sequence 1.
-  }
-  {
     // Async event completed on "Async track 1".
     auto* packet = trace_.add_packet();
     packet->set_trusted_packet_sequence_id(2);
@@ -1361,21 +1355,9 @@
       .WillOnce(Return(11));
   EXPECT_CALL(*storage_, InternString(base::StringView("Thread track 2")))
       .WillOnce(Return(12));
-  EXPECT_CALL(*storage_, InternString(base::StringView("")))
-      .WillOnce(Return(0));
 
   Tokenize();
 
-  // First track is "Thread track 1"; second is "Async track 1", third is
-  // "Thread track 2".
-  EXPECT_EQ(storage_->track_table().row_count(), 3u);
-  EXPECT_EQ(storage_->track_table().name()[0], 10u);  // "Thread track 1"
-  EXPECT_EQ(storage_->track_table().name()[1], 11u);  // "Async track 1"
-  EXPECT_EQ(storage_->track_table().name()[2], 12u);  // "Thread track 2"
-  EXPECT_EQ(storage_->thread_track_table().row_count(), 2u);
-  EXPECT_EQ(storage_->thread_track_table().utid()[0], 1u);
-  EXPECT_EQ(storage_->thread_track_table().utid()[1], 2u);
-
   InSequence in_sequence;  // Below slices should be sorted by timestamp.
 
   EXPECT_CALL(*storage_, InternString(base::StringView("cat1")))
@@ -1406,9 +1388,15 @@
 
   context_.sorter->ExtractEventsForced();
 
-  // Track tables shouldn't have changed.
+  // First track is "Thread track 1"; second is "Async track 1", third is
+  // "Thread track 2".
   EXPECT_EQ(storage_->track_table().row_count(), 3u);
+  EXPECT_EQ(storage_->track_table().name()[0], 10u);  // "Thread track 1"
+  EXPECT_EQ(storage_->track_table().name()[1], 11u);  // "Async track 1"
+  EXPECT_EQ(storage_->track_table().name()[2], 12u);  // "Thread track 2"
   EXPECT_EQ(storage_->thread_track_table().row_count(), 2u);
+  EXPECT_EQ(storage_->thread_track_table().utid()[0], 1u);
+  EXPECT_EQ(storage_->thread_track_table().utid()[1], 2u);
 
   EXPECT_EQ(storage_->virtual_track_slices().slice_count(), 1u);
   EXPECT_EQ(storage_->virtual_track_slices().slice_ids()[0], 0u);
@@ -1575,7 +1563,6 @@
   Tokenize();
 
   EXPECT_CALL(*process_, UpdateThread(16, 15))
-      .Times(2)
       .WillRepeatedly(Return(1));
 
   tables::ThreadTable::Row row(16);
@@ -1678,10 +1665,8 @@
   Tokenize();
 
   EXPECT_CALL(*process_, UpdateThread(16, 15))
-      .Times(2)
       .WillRepeatedly(Return(1));
   EXPECT_CALL(*process_, UpdateThread(17, 15))
-      .Times(2)
       .WillRepeatedly(Return(2));
 
   tables::ThreadTable::Row t1(16);
@@ -1842,7 +1827,6 @@
   Tokenize();
 
   EXPECT_CALL(*process_, UpdateThread(16, 15))
-      .Times(2)
       .WillRepeatedly(Return(1));
 
   tables::ThreadTable::Row row(16);
@@ -1972,7 +1956,7 @@
 
   Tokenize();
 
-  EXPECT_CALL(*process_, UpdateThread(16, 15)).WillOnce(Return(1));
+  EXPECT_CALL(*process_, UpdateThread(16, 15)).WillRepeatedly(Return(1));
 
   tables::ThreadTable::Row row(16);
   row.upid = 1u;
@@ -2051,7 +2035,7 @@
 
   Tokenize();
 
-  EXPECT_CALL(*process_, UpdateThread(16, 15)).WillOnce(Return(1));
+  EXPECT_CALL(*process_, UpdateThread(16, 15)).WillRepeatedly(Return(1));
 
   tables::ThreadTable::Row row(16);
   row.upid = 1u;
@@ -2136,7 +2120,7 @@
 
   Tokenize();
 
-  EXPECT_CALL(*process_, UpdateThread(16, 15)).WillOnce(Return(1));
+  EXPECT_CALL(*process_, UpdateThread(16, 15)).WillRepeatedly(Return(1));
 
   tables::ThreadTable::Row row(16);
   row.upid = 1u;
@@ -2230,7 +2214,7 @@
 
   Tokenize();
 
-  EXPECT_CALL(*process_, UpdateThread(16, 15)).WillOnce(Return(1));
+  EXPECT_CALL(*process_, UpdateThread(16, 15)).WillRepeatedly(Return(1));
 
   tables::ThreadTable::Row row(16);
   row.upid = 1u;
diff --git a/src/trace_processor/importers/proto/track_event_module.cc b/src/trace_processor/importers/proto/track_event_module.cc
index 2fd7cd1..1bbbebd 100644
--- a/src/trace_processor/importers/proto/track_event_module.cc
+++ b/src/trace_processor/importers/proto/track_event_module.cc
@@ -14,9 +14,14 @@
  * limitations under the License.
  */
 #include "src/trace_processor/importers/proto/track_event_module.h"
-#include "perfetto/base/build_config.h"
-#include "src/trace_processor/timestamped_trace_piece.h"
 
+#include "perfetto/base/build_config.h"
+#include "perfetto/ext/base/string_utils.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 "protos/perfetto/config/data_source_config.pbzero.h"
 #include "protos/perfetto/config/trace_config.pbzero.h"
 #include "protos/perfetto/trace/trace_packet.pbzero.h"
 
@@ -43,20 +48,15 @@
     uint32_t field_id) {
   switch (field_id) {
     case TracePacket::kTrackDescriptorFieldNumber:
-      tokenizer_.TokenizeTrackDescriptorPacket(state, decoder);
-      return ModuleResult::Handled();
+      return tokenizer_.TokenizeTrackDescriptorPacket(state, decoder,
+                                                      packet_timestamp);
     case TracePacket::kTrackEventFieldNumber:
       tokenizer_.TokenizeTrackEventPacket(state, decoder, packet,
                                           packet_timestamp);
       return ModuleResult::Handled();
     case TracePacket::kThreadDescriptorFieldNumber:
-      // TODO(eseckler): Remove these once Chrome has switched fully over to
-      // TrackDescriptors.
-      tokenizer_.TokenizeThreadDescriptorPacket(state, decoder);
-      return ModuleResult::Handled();
-    case TracePacket::kProcessDescriptorFieldNumber:
-      tokenizer_.TokenizeProcessDescriptorPacket(decoder);
-      return ModuleResult::Handled();
+      // TODO(eseckler): Remove once Chrome has switched to TrackDescriptors.
+      return tokenizer_.TokenizeThreadDescriptorPacket(state, decoder);
   }
   return ModuleResult::Ignored();
 }
@@ -64,12 +64,28 @@
 void TrackEventModule::ParsePacket(const TracePacket::Decoder& decoder,
                                    const TimestampedTracePiece& ttp,
                                    uint32_t field_id) {
-  if (field_id == TracePacket::kTrackEventFieldNumber) {
-    PERFETTO_DCHECK(ttp.type == TimestampedTracePiece::Type::kTrackEvent);
-    parser_.ParseTrackEvent(
-        ttp.timestamp, ttp.track_event_data->thread_timestamp,
-        ttp.track_event_data->thread_instruction_count,
-        ttp.track_event_data->sequence_state, decoder.track_event());
+  switch (field_id) {
+    case TracePacket::kTrackDescriptorFieldNumber:
+      PERFETTO_DCHECK(ttp.type == TimestampedTracePiece::Type::kTracePacket);
+      parser_.ParseTrackDescriptor(decoder.track_descriptor());
+      break;
+    case TracePacket::kTrackEventFieldNumber:
+      PERFETTO_DCHECK(ttp.type == TimestampedTracePiece::Type::kTrackEvent);
+      parser_.ParseTrackEvent(
+          ttp.timestamp, ttp.track_event_data->thread_timestamp,
+          ttp.track_event_data->thread_instruction_count,
+          ttp.track_event_data->sequence_state, decoder.track_event());
+      break;
+    case TracePacket::kProcessDescriptorFieldNumber:
+      // TODO(eseckler): Remove once Chrome has switched to TrackDescriptors.
+      PERFETTO_DCHECK(ttp.type == TimestampedTracePiece::Type::kTracePacket);
+      parser_.ParseProcessDescriptor(decoder.process_descriptor());
+      break;
+    case TracePacket::kThreadDescriptorFieldNumber:
+      // TODO(eseckler): Remove once Chrome has switched to TrackDescriptors.
+      PERFETTO_DCHECK(ttp.type == TimestampedTracePiece::Type::kTracePacket);
+      parser_.ParseThreadDescriptor(decoder.thread_descriptor());
+      break;
   }
 }
 
diff --git a/src/trace_processor/importers/proto/track_event_parser.cc b/src/trace_processor/importers/proto/track_event_parser.cc
index 720db46..5d86efe 100644
--- a/src/trace_processor/importers/proto/track_event_parser.cc
+++ b/src/trace_processor/importers/proto/track_event_parser.cc
@@ -31,11 +31,16 @@
 #include "protos/perfetto/trace/track_event/chrome_histogram_sample.pbzero.h"
 #include "protos/perfetto/trace/track_event/chrome_keyed_service.pbzero.h"
 #include "protos/perfetto/trace/track_event/chrome_legacy_ipc.pbzero.h"
+#include "protos/perfetto/trace/track_event/chrome_process_descriptor.pbzero.h"
+#include "protos/perfetto/trace/track_event/chrome_thread_descriptor.pbzero.h"
 #include "protos/perfetto/trace/track_event/chrome_user_event.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/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 {
@@ -200,7 +205,143 @@
            context->storage->InternString("MEDIA_PLAYER_DELEGATE"),
            context->storage->InternString("EXTENSION_WORKER"),
            context->storage->InternString("SUBRESOURCE_FILTER"),
-           context->storage->InternString("UNFREEZABLE_FRAME")}} {}
+           context->storage->InternString("UNFREEZABLE_FRAME")}},
+      chrome_process_name_ids_{
+          {context_->storage->InternString("Unknown"),
+           context_->storage->InternString("Browser"),
+           context_->storage->InternString("Renderer"),
+           context_->storage->InternString("Utility"),
+           context_->storage->InternString("Zygote"),
+           context_->storage->InternString("SandboxHelper"),
+           context_->storage->InternString("Gpu"),
+           context_->storage->InternString("PpapiPlugin"),
+           context_->storage->InternString("PpapiBroker")}},
+      chrome_thread_name_ids_{
+          {context_->storage->InternString("ChromeUnspecified"),
+           context_->storage->InternString("CrProcessMain"),
+           context_->storage->InternString("ChromeIOThread"),
+           context_->storage->InternString("ThreadPoolBackgroundWorker&"),
+           context_->storage->InternString("ThreadPoolForegroundWorker&"),
+           context_->storage->InternString(
+               "ThreadPoolSingleThreadForegroundBlocking&"),
+           context_->storage->InternString(
+               "ThreadPoolSingleThreadBackgroundBlocking&"),
+           context_->storage->InternString("ThreadPoolService"),
+           context_->storage->InternString("Compositor"),
+           context_->storage->InternString("VizCompositorThread"),
+           context_->storage->InternString("CompositorTileWorker&"),
+           context_->storage->InternString("ServiceWorkerThread&"),
+           context_->storage->InternString("MemoryInfra"),
+           context_->storage->InternString("StackSamplingProfiler")}} {}
+
+void TrackEventParser::ParseTrackDescriptor(
+    protozero::ConstBytes track_descriptor) {
+  protos::pbzero::TrackDescriptor::Decoder decoder(track_descriptor);
+
+  // Ensure that the track and its parents are resolved. This may start a new
+  // process and/or thread (i.e. new upid/utid).
+  TrackId track_id =
+      *context_->track_tracker->GetDescriptorTrack(decoder.uuid());
+
+  if (decoder.has_process()) {
+    UniquePid upid = ParseProcessDescriptor(decoder.process());
+    if (decoder.has_chrome_process())
+      ParseChromeProcessDescriptor(upid, decoder.chrome_process());
+  }
+
+  if (decoder.has_thread()) {
+    UniqueTid utid = ParseThreadDescriptor(decoder.thread());
+    if (decoder.has_chrome_thread())
+      ParseChromeThreadDescriptor(utid, decoder.chrome_thread());
+  }
+
+  if (decoder.has_name()) {
+    auto* tracks = context_->storage->mutable_track_table();
+    StringId name_id = context_->storage->InternString(decoder.name());
+    tracks->mutable_name()->Set(*tracks->id().IndexOf(track_id), name_id);
+  }
+}
+
+UniquePid TrackEventParser::ParseProcessDescriptor(
+    protozero::ConstBytes process_descriptor) {
+  protos::pbzero::ProcessDescriptor::Decoder decoder(process_descriptor);
+  UniquePid upid = context_->process_tracker->GetOrCreateProcess(
+      static_cast<uint32_t>(decoder.pid()));
+  if (decoder.has_process_name()) {
+    // Don't override system-provided names.
+    context_->process_tracker->SetProcessNameIfUnset(
+        upid, context_->storage->InternString(decoder.process_name()));
+  }
+  // TODO(skyostil): Remove parsing for legacy chrome_process_type field.
+  if (decoder.has_chrome_process_type()) {
+    auto process_type = decoder.chrome_process_type();
+    size_t name_index =
+        static_cast<size_t>(process_type) < chrome_process_name_ids_.size()
+            ? static_cast<size_t>(process_type)
+            : 0u;
+    StringId name_id = chrome_process_name_ids_[name_index];
+    // Don't override system-provided names.
+    context_->process_tracker->SetProcessNameIfUnset(upid, name_id);
+  }
+  return upid;
+}
+
+void TrackEventParser::ParseChromeProcessDescriptor(
+    UniquePid upid,
+    protozero::ConstBytes chrome_process_descriptor) {
+  protos::pbzero::ChromeProcessDescriptor::Decoder decoder(
+      chrome_process_descriptor);
+  auto process_type = decoder.process_type();
+  size_t name_index =
+      static_cast<size_t>(process_type) < chrome_process_name_ids_.size()
+          ? static_cast<size_t>(process_type)
+          : 0u;
+  StringId name_id = chrome_process_name_ids_[name_index];
+  // Don't override system-provided names.
+  context_->process_tracker->SetProcessNameIfUnset(upid, name_id);
+}
+
+UniqueTid TrackEventParser::ParseThreadDescriptor(
+    protozero::ConstBytes thread_descriptor) {
+  protos::pbzero::ThreadDescriptor::Decoder decoder(thread_descriptor);
+  UniqueTid utid = context_->process_tracker->UpdateThread(
+      static_cast<uint32_t>(decoder.tid()),
+      static_cast<uint32_t>(decoder.pid()));
+  StringId name_id = kNullStringId;
+  if (decoder.has_thread_name()) {
+    name_id = context_->storage->InternString(decoder.thread_name());
+  } else if (decoder.has_chrome_thread_type()) {
+    // TODO(skyostil): Remove parsing for legacy chrome_thread_type field.
+    auto thread_type = decoder.chrome_thread_type();
+    size_t name_index =
+        static_cast<size_t>(thread_type) < chrome_thread_name_ids_.size()
+            ? static_cast<size_t>(thread_type)
+            : 0u;
+    name_id = chrome_thread_name_ids_[name_index];
+  }
+  if (name_id != kNullStringId) {
+    // Don't override system-provided names.
+    context_->process_tracker->SetThreadNameIfUnset(utid, name_id);
+  }
+  return utid;
+}
+
+void TrackEventParser::ParseChromeThreadDescriptor(
+    UniqueTid utid,
+    protozero::ConstBytes chrome_thread_descriptor) {
+  protos::pbzero::ChromeThreadDescriptor::Decoder decoder(
+      chrome_thread_descriptor);
+  if (!decoder.has_thread_type())
+    return;
+
+  auto thread_type = decoder.thread_type();
+  size_t name_index =
+      static_cast<size_t>(thread_type) < chrome_thread_name_ids_.size()
+          ? static_cast<size_t>(thread_type)
+          : 0u;
+  StringId name_id = chrome_thread_name_ids_[name_index];
+  context_->process_tracker->SetThreadNameIfUnset(utid, name_id);
+}
 
 void TrackEventParser::ParseTrackEvent(
     int64_t ts,
@@ -353,7 +494,7 @@
 
     utid = procs->UpdateThread(tid, pid);
     upid = storage->thread_table().upid()[*utid];
-    track_id = track_tracker->GetOrCreateDescriptorTrackForThread(*utid);
+    track_id = track_tracker->InternThreadTrack(*utid);
   } else {
     track_id = track_tracker->GetOrCreateDefaultDescriptorTrack();
   }
diff --git a/src/trace_processor/importers/proto/track_event_parser.h b/src/trace_processor/importers/proto/track_event_parser.h
index a0a42c7..1787f0f 100644
--- a/src/trace_processor/importers/proto/track_event_parser.h
+++ b/src/trace_processor/importers/proto/track_event_parser.h
@@ -39,11 +39,19 @@
  public:
   explicit TrackEventParser(TraceProcessorContext* context);
 
+  void ParseTrackDescriptor(protozero::ConstBytes);
+  UniquePid ParseProcessDescriptor(protozero::ConstBytes);
+  UniqueTid ParseThreadDescriptor(protozero::ConstBytes);
+
   void ParseTrackEvent(int64_t ts,
                        int64_t tts,
                        int64_t ticount,
                        PacketSequenceStateGeneration*,
                        protozero::ConstBytes);
+
+ private:
+  void ParseChromeProcessDescriptor(UniquePid upid, protozero::ConstBytes);
+  void ParseChromeThreadDescriptor(UniqueTid utid, protozero::ConstBytes);
   void ParseLegacyEventAsRawEvent(
       int64_t ts,
       int64_t tts,
@@ -80,7 +88,6 @@
   void ParseChromeHistogramSample(protozero::ConstBytes chrome_keyed_service,
                                   ArgsTracker::BoundInserter* inserter);
 
- private:
   TraceProcessorContext* context_;
 
   const StringId task_file_name_args_key_id_;
@@ -117,6 +124,8 @@
   const StringId chrome_histogram_sample_sample_args_key_id_;
 
   std::array<StringId, 38> chrome_legacy_ipc_class_ids_;
+  std::array<StringId, 9> chrome_process_name_ids_;
+  std::array<StringId, 14> chrome_thread_name_ids_;
 };
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/importers/proto/track_event_tokenizer.cc b/src/trace_processor/importers/proto/track_event_tokenizer.cc
index 4b03008..0c3ea93 100644
--- a/src/trace_processor/importers/proto/track_event_tokenizer.cc
+++ b/src/trace_processor/importers/proto/track_event_tokenizer.cc
@@ -41,20 +41,12 @@
 namespace trace_processor {
 
 TrackEventTokenizer::TrackEventTokenizer(TraceProcessorContext* context)
-    : context_(context),
-      process_name_ids_{{context_->storage->InternString("Unknown"),
-                         context_->storage->InternString("Browser"),
-                         context_->storage->InternString("Renderer"),
-                         context_->storage->InternString("Utility"),
-                         context_->storage->InternString("Zygote"),
-                         context_->storage->InternString("SandboxHelper"),
-                         context_->storage->InternString("Gpu"),
-                         context_->storage->InternString("PpapiPlugin"),
-                         context_->storage->InternString("PpapiBroker")}} {}
+    : context_(context) {}
 
-void TrackEventTokenizer::TokenizeTrackDescriptorPacket(
+ModuleResult TrackEventTokenizer::TokenizeTrackDescriptorPacket(
     PacketSequenceState* state,
-    const protos::pbzero::TracePacket::Decoder& packet_decoder) {
+    const protos::pbzero::TracePacket::Decoder& packet_decoder,
+    int64_t packet_timestamp) {
   auto track_descriptor_field = packet_decoder.track_descriptor();
   protos::pbzero::TrackDescriptor::Decoder track_descriptor_decoder(
       track_descriptor_field.data, track_descriptor_field.size);
@@ -62,37 +54,7 @@
   if (!track_descriptor_decoder.has_uuid()) {
     PERFETTO_ELOG("TrackDescriptor packet without uuid");
     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);
-
-    upid = TokenizeProcessDescriptor(process_descriptor_decoder);
-
-    if (track_descriptor_decoder.has_chrome_process()) {
-      auto chrome_process_descriptor_field =
-          track_descriptor_decoder.chrome_process();
-      protos::pbzero::ChromeProcessDescriptor::Decoder
-          chrome_process_descriptor_decoder(
-              chrome_process_descriptor_field.data,
-              chrome_process_descriptor_field.size);
-
-      auto process_type = chrome_process_descriptor_decoder.process_type();
-      size_t name_index =
-          static_cast<size_t>(process_type) < process_name_ids_.size()
-              ? static_cast<size_t>(process_type)
-              : 0u;
-      StringId name = process_name_ids_[name_index];
-
-      // Don't override system-provided names.
-      context_->process_tracker->SetProcessNameIfUnset(*upid, name);
-    }
+    return ModuleResult::Handled();
   }
 
   if (track_descriptor_decoder.has_thread()) {
@@ -100,72 +62,57 @@
     protos::pbzero::ThreadDescriptor::Decoder thread_descriptor_decoder(
         thread_descriptor_field.data, thread_descriptor_field.size);
 
-    utid = TokenizeThreadDescriptor(state, thread_descriptor_decoder);
-    upid = *context_->storage->thread_table().upid()[*utid];
-
-    if (track_descriptor_decoder.has_chrome_thread()) {
-      auto chrome_thread_descriptor_field =
-          track_descriptor_decoder.chrome_thread();
-      protos::pbzero::ChromeThreadDescriptor::Decoder
-          chrome_thread_descriptor_decoder(chrome_thread_descriptor_field.data,
-                                           chrome_thread_descriptor_field.size);
-
-      TokenizeChromeThreadDescriptor(thread_descriptor_decoder.pid(),
-                                     thread_descriptor_decoder.tid(),
-                                     chrome_thread_descriptor_decoder);
+    if (!thread_descriptor_decoder.has_pid() ||
+        !thread_descriptor_decoder.has_tid()) {
+      PERFETTO_ELOG(
+          "No pid or tid in ThreadDescriptor for track with uuid %" PRIu64,
+          track_descriptor_decoder.uuid());
+      context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
+      return ModuleResult::Handled();
     }
+
+    if (state->IsIncrementalStateValid()) {
+      TokenizeThreadDescriptor(state, thread_descriptor_decoder);
+    }
+
+    context_->track_tracker->ReserveDescriptorThreadTrack(
+        track_descriptor_decoder.uuid(), track_descriptor_decoder.parent_uuid(),
+        static_cast<uint32_t>(thread_descriptor_decoder.pid()),
+        static_cast<uint32_t>(thread_descriptor_decoder.tid()),
+        packet_timestamp);
+  } else 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);
+
+    if (!process_descriptor_decoder.has_pid()) {
+      PERFETTO_ELOG("No pid in ProcessDescriptor for track with uuid %" PRIu64,
+                    track_descriptor_decoder.uuid());
+      context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
+      return ModuleResult::Handled();
+    }
+
+    context_->track_tracker->ReserveDescriptorProcessTrack(
+        track_descriptor_decoder.uuid(),
+        static_cast<uint32_t>(process_descriptor_decoder.pid()),
+        packet_timestamp);
+  } else {
+    context_->track_tracker->ReserveDescriptorChildTrack(
+        track_descriptor_decoder.uuid(),
+        track_descriptor_decoder.parent_uuid());
   }
 
-  StringId name_id =
-      context_->storage->InternString(track_descriptor_decoder.name());
-
-  context_->track_tracker->UpdateDescriptorTrack(
-      track_descriptor_decoder.uuid(), name_id, upid, utid);
+  // Let ProtoTraceTokenizer forward the packet to the parser.
+  return ModuleResult::Ignored();
 }
 
-void TrackEventTokenizer::TokenizeProcessDescriptorPacket(
-    const protos::pbzero::TracePacket::Decoder& packet_decoder) {
-  protos::pbzero::ProcessDescriptor::Decoder process_descriptor_decoder(
-      packet_decoder.process_descriptor());
-  TokenizeProcessDescriptor(process_descriptor_decoder);
-}
-
-UniquePid TrackEventTokenizer::TokenizeProcessDescriptor(
-    const protos::pbzero::ProcessDescriptor::Decoder&
-        process_descriptor_decoder) {
-  UniquePid upid = context_->process_tracker->GetOrCreateProcess(
-      static_cast<uint32_t>(process_descriptor_decoder.pid()));
-
-  if (process_descriptor_decoder.has_process_name()) {
-    // Don't override system-provided names.
-    context_->process_tracker->SetProcessNameIfUnset(
-        upid, context_->storage->InternString(
-                  process_descriptor_decoder.process_name()));
-  }
-
-  // TODO(skyostil): Remove parsing for legacy chrome_process_type field.
-  if (process_descriptor_decoder.has_chrome_process_type()) {
-    auto process_type = process_descriptor_decoder.chrome_process_type();
-    size_t name_index =
-        static_cast<size_t>(process_type) < process_name_ids_.size()
-            ? static_cast<size_t>(process_type)
-            : 0u;
-    StringId name = process_name_ids_[name_index];
-
-    // Don't override system-provided names.
-    context_->process_tracker->SetProcessNameIfUnset(upid, name);
-  }
-
-  return upid;
-}
-
-void TrackEventTokenizer::TokenizeThreadDescriptorPacket(
+ModuleResult 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;
+    return ModuleResult::Handled();
   }
 
   // TrackEvents will be ignored while incremental state is invalid. As a
@@ -175,161 +122,29 @@
   // the first subsequent descriptor after incremental state is cleared.
   if (!state->IsIncrementalStateValid()) {
     context_->storage->IncrementStats(stats::tokenizer_skipped_packets);
-    return;
+    return ModuleResult::Handled();
   }
 
   auto thread_descriptor_field = packet_decoder.thread_descriptor();
   protos::pbzero::ThreadDescriptor::Decoder thread_descriptor_decoder(
       thread_descriptor_field.data, thread_descriptor_field.size);
   TokenizeThreadDescriptor(state, thread_descriptor_decoder);
+
+  // Let ProtoTraceTokenizer forward the packet to the parser.
+  return ModuleResult::Ignored();
 }
 
-UniqueTid TrackEventTokenizer::TokenizeThreadDescriptor(
+void TrackEventTokenizer::TokenizeThreadDescriptor(
     PacketSequenceState* state,
     const protos::pbzero::ThreadDescriptor::Decoder&
         thread_descriptor_decoder) {
-  UniqueTid utid = context_->process_tracker->UpdateThread(
-      static_cast<uint32_t>(thread_descriptor_decoder.tid()),
-      static_cast<uint32_t>(thread_descriptor_decoder.pid()));
-
   // TODO(eseckler): Remove support for legacy thread descriptor-based default
   // tracks and delta timestamps.
-  if (state->IsIncrementalStateValid()) {
-    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());
-  }
-
-  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()) {
-    // TODO(skyostil): Remove parsing for legacy chrome_thread_type field.
-    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();
-    // Don't override system-provided names.
-    procs->SetThreadNameIfUnset(utid, thread_name_id);
-  }
-
-  return utid;
-}
-
-void TrackEventTokenizer::TokenizeChromeThreadDescriptor(
-    int32_t pid,
-    int32_t tid,
-    const protos::pbzero::ChromeThreadDescriptor::Decoder&
-        thread_descriptor_decoder) {
-  if (!thread_descriptor_decoder.has_thread_type())
-    return;
-
-  base::StringView name;
-  using protos::pbzero::ChromeThreadDescriptor;
-  // Thread names that end in an ampersand indicate multiple thread instances
-  // (e.g., CompositorTileWorker{1,2,...}) that have been collapsed into a
-  // single thread name.
-  switch (thread_descriptor_decoder.thread_type()) {
-    case ChromeThreadDescriptor::THREAD_MAIN:
-      name = "CrProcessMain";
-      break;
-    case ChromeThreadDescriptor::THREAD_IO:
-      name = "ChromeIOThread";
-      break;
-    case ChromeThreadDescriptor::THREAD_POOL_FG_WORKER:
-      name = "ThreadPoolForegroundWorker&";
-      break;
-    case ChromeThreadDescriptor::THREAD_POOL_BG_WORKER:
-      name = "ThreadPoolBackgroundWorker&";
-      break;
-    case ChromeThreadDescriptor::THREAD_POOL_FB_BLOCKING:
-      name = "ThreadPoolSingleThreadForegroundBlocking&";
-      break;
-    case ChromeThreadDescriptor::THREAD_POOL_BG_BLOCKING:
-      name = "ThreadPoolSingleThreadBackgroundBlocking&";
-      break;
-    case ChromeThreadDescriptor::THREAD_POOL_SERVICE:
-      name = "ThreadPoolService";
-      break;
-    case ChromeThreadDescriptor::THREAD_COMPOSITOR_WORKER:
-      name = "CompositorTileWorker&";
-      break;
-    case ChromeThreadDescriptor::THREAD_COMPOSITOR:
-      name = "Compositor";
-      break;
-    case ChromeThreadDescriptor::THREAD_VIZ_COMPOSITOR:
-      name = "VizCompositorThread";
-      break;
-    case ChromeThreadDescriptor::THREAD_SERVICE_WORKER:
-      name = "ServiceWorkerThread&";
-      break;
-    case ChromeThreadDescriptor::THREAD_MEMORY_INFRA:
-      name = "MemoryInfra";
-      break;
-    case ChromeThreadDescriptor::THREAD_SAMPLING_PROFILER:
-      name = "StackSamplingProfiler";
-      break;
-    case ChromeThreadDescriptor::THREAD_UNSPECIFIED:
-      name = "ChromeUnspecified";
-      break;
-  }
-
-  if (!name.empty()) {
-    auto thread_name_id = context_->storage->InternString(name);
-    ProcessTracker* procs = context_->process_tracker.get();
-    // Don't override system-provided names.
-    procs->SetThreadNameIfUnset(procs->UpdateThread(static_cast<uint32_t>(tid),
-                                                    static_cast<uint32_t>(pid)),
-                                thread_name_id);
-  }
+  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());
 }
 
 void TrackEventTokenizer::TokenizeTrackEventPacket(
diff --git a/src/trace_processor/importers/proto/track_event_tokenizer.h b/src/trace_processor/importers/proto/track_event_tokenizer.h
index b50b564..48fdf47 100644
--- a/src/trace_processor/importers/proto/track_event_tokenizer.h
+++ b/src/trace_processor/importers/proto/track_event_tokenizer.h
@@ -19,6 +19,7 @@
 
 #include <stdint.h>
 
+#include "src/trace_processor/importers/proto/proto_importer_module.h"
 #include "src/trace_processor/trace_storage.h"
 
 namespace perfetto {
@@ -42,33 +43,24 @@
  public:
   explicit TrackEventTokenizer(TraceProcessorContext* context);
 
-  void TokenizeTrackDescriptorPacket(
+  ModuleResult TokenizeTrackDescriptorPacket(
+      PacketSequenceState* state,
+      const protos::pbzero::TracePacket_Decoder&,
+      int64_t packet_timestamp);
+  ModuleResult TokenizeThreadDescriptorPacket(
       PacketSequenceState* state,
       const protos::pbzero::TracePacket_Decoder&);
-  void TokenizeProcessDescriptorPacket(
-      const protos::pbzero::TracePacket_Decoder&);
-  void TokenizeThreadDescriptorPacket(
-      PacketSequenceState* state,
-      const protos::pbzero::TracePacket_Decoder&);
-  void TokenizeChromeThreadDescriptor(
-      int32_t pid,
-      int32_t tid,
-      const protos::pbzero::ChromeThreadDescriptor_Decoder&);
   void TokenizeTrackEventPacket(PacketSequenceState* state,
                                 const protos::pbzero::TracePacket_Decoder&,
                                 TraceBlobView* packet,
                                 int64_t packet_timestamp);
 
  private:
-  UniqueTid TokenizeThreadDescriptor(
+  void TokenizeThreadDescriptor(
       PacketSequenceState* state,
       const protos::pbzero::ThreadDescriptor_Decoder&);
-  UniquePid TokenizeProcessDescriptor(
-      const protos::pbzero::ProcessDescriptor_Decoder&);
 
   TraceProcessorContext* context_;
-
-  std::array<StringId, 9> process_name_ids_;
 };
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/process_tracker.cc b/src/trace_processor/process_tracker.cc
index 20ed994..5880e12 100644
--- a/src/trace_processor/process_tracker.cc
+++ b/src/trace_processor/process_tracker.cc
@@ -161,7 +161,7 @@
 }
 
 UniquePid ProcessTracker::StartNewProcess(int64_t timestamp,
-                                          uint32_t parent_tid,
+                                          base::Optional<uint32_t> parent_tid,
                                           uint32_t pid,
                                           StringId main_thread_name) {
   pids_.erase(pid);
@@ -181,12 +181,14 @@
   process_table->mutable_start_ts()->Set(upid, timestamp);
   process_table->mutable_name()->Set(upid, main_thread_name);
 
-  UniqueTid parent_utid = GetOrCreateThread(parent_tid);
-  auto opt_parent_upid = thread_table->upid()[parent_utid];
-  if (opt_parent_upid.has_value()) {
-    process_table->mutable_parent_upid()->Set(upid, *opt_parent_upid);
-  } else {
-    pending_parent_assocs_.emplace_back(parent_utid, upid);
+  if (parent_tid) {
+    UniqueTid parent_utid = GetOrCreateThread(*parent_tid);
+    auto opt_parent_upid = thread_table->upid()[parent_utid];
+    if (opt_parent_upid.has_value()) {
+      process_table->mutable_parent_upid()->Set(upid, *opt_parent_upid);
+    } else {
+      pending_parent_assocs_.emplace_back(parent_utid, upid);
+    }
   }
   return upid;
 }
diff --git a/src/trace_processor/process_tracker.h b/src/trace_processor/process_tracker.h
index d7ac808..6c9f819 100644
--- a/src/trace_processor/process_tracker.h
+++ b/src/trace_processor/process_tracker.h
@@ -79,7 +79,7 @@
   // Called when a task_newtask without the CLONE_THREAD flag is observed.
   // This force the tracker to start both a new UTID and a new UPID.
   UniquePid StartNewProcess(int64_t timestamp,
-                            uint32_t parent_tid,
+                            base::Optional<uint32_t> parent_tid,
                             uint32_t pid,
                             StringId main_thread_name);
 
diff --git a/src/trace_processor/process_tracker_unittest.cc b/src/trace_processor/process_tracker_unittest.cc
index 7a251c5..11ae675 100644
--- a/src/trace_processor/process_tracker_unittest.cc
+++ b/src/trace_processor/process_tracker_unittest.cc
@@ -59,7 +59,7 @@
 
 TEST_F(ProcessTrackerTest, StartNewProcess) {
   TraceStorage storage;
-  auto upid = context.process_tracker->StartNewProcess(1000, 0, 123, 0);
+  auto upid = context.process_tracker->StartNewProcess(1000, 0u, 123, 0);
   ASSERT_EQ(context.process_tracker->GetOrCreateProcess(123), upid);
   ASSERT_EQ(context.storage->process_table().start_ts()[upid], 1000);
 }
diff --git a/src/trace_processor/track_tracker.cc b/src/trace_processor/track_tracker.cc
index 944e674..0a35102 100644
--- a/src/trace_processor/track_tracker.cc
+++ b/src/trace_processor/track_tracker.cc
@@ -17,6 +17,7 @@
 #include "src/trace_processor/track_tracker.h"
 
 #include "src/trace_processor/args_tracker.h"
+#include "src/trace_processor/process_tracker.h"
 
 namespace perfetto {
 namespace trace_processor {
@@ -50,6 +51,18 @@
   return id;
 }
 
+TrackId TrackTracker::InternProcessTrack(UniquePid upid) {
+  auto it = process_tracks_.find(upid);
+  if (it != process_tracks_.end())
+    return it->second;
+
+  tables::ProcessTrackTable::Row row;
+  row.upid = upid;
+  auto id = context_->storage->mutable_process_track_table()->Insert(row);
+  process_tracks_[upid] = id;
+  return id;
+}
+
 TrackId TrackTracker::InternFuchsiaAsyncTrack(StringId name,
                                               int64_t correlation_id) {
   auto it = fuchsia_async_tracks_.find(correlation_id);
@@ -160,150 +173,221 @@
   return *chrome_global_instant_track_id_;
 }
 
-TrackId TrackTracker::UpdateDescriptorTrack(uint64_t uuid,
-                                            StringId name,
-                                            base::Optional<UniquePid> upid,
-                                            base::Optional<UniqueTid> utid) {
-  auto it = descriptor_tracks_.find(uuid);
-  if (it != descriptor_tracks_.end()) {
-    // Update existing track for |uuid|.
-    TrackId track_id = it->second;
-    if (name != kNullStringId) {
-      auto* track = context_->storage->mutable_track_table();
-      auto row = *track->id().IndexOf(track_id);
-      track->mutable_name()->Set(row, name);
-    }
+void TrackTracker::ReserveDescriptorProcessTrack(uint64_t uuid,
+                                                 uint32_t pid,
+                                                 int64_t timestamp) {
+  DescriptorTrackReservation reservation;
+  reservation.min_timestamp = timestamp;
+  reservation.pid = pid;
 
-#if PERFETTO_DLOG_IS_ON()
-    if (upid) {
-      // Verify that upid didn't change.
-      auto process_track_row =
-          context_->storage->process_track_table().id().IndexOf(track_id);
-      if (!process_track_row) {
-        PERFETTO_DLOG("Can't update non-scoped track with uuid %" PRIu64
-                      " to a scoped track.",
-                      uuid);
-      } else {
-        auto old_upid =
-            context_->storage->process_track_table().upid()[*process_track_row];
-        if (old_upid != upid) {
-          PERFETTO_DLOG("Ignoring upid change for track with uuid %" PRIu64
-                        " from %" PRIu32 " to %" PRIu32 ".",
-                        uuid, old_upid, *upid);
-        }
-      }
-    }
+  std::map<uint64_t, DescriptorTrackReservation>::iterator it;
+  bool inserted;
+  std::tie(it, inserted) =
+      reserved_descriptor_tracks_.insert(std::make_pair<>(uuid, reservation));
 
-    if (utid) {
-      // Verify that utid didn't change.
-      auto thread_track_row =
-          context_->storage->thread_track_table().id().IndexOf(track_id);
-      if (!thread_track_row) {
-        PERFETTO_DLOG("Can't update non-thread track with uuid %" PRIu64
-                      " to a thread track.",
-                      uuid);
-      } else {
-        auto old_utid =
-            context_->storage->thread_track_table().utid()[*thread_track_row];
-        if (old_utid != utid) {
-          PERFETTO_DLOG("Ignoring utid change for track with uuid %" PRIu64
-                        " from %" PRIu32 " to %" PRIu32 ".",
-                        uuid, old_utid, *utid);
-        }
-      }
-    }
-#endif  // PERFETTO_DLOG_IS_ON()
+  if (inserted)
+    return;
 
-    return track_id;
+  if (!it->second.IsForSameTrack(reservation)) {
+    // Process tracks should not be reassigned to a different pid later (neither
+    // should the type of the track change).
+    PERFETTO_DLOG("New track reservation for process track with uuid %" PRIu64
+                  " doesn't match earlier one",
+                  uuid);
+    context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
+    return;
   }
 
-  TrackId track_id;
-
-  if (utid) {
-    // Update existing track for the thread if we have previously created one
-    // in GetOrCreateDescriptorTrackForThread().
-    auto utid_it = descriptor_tracks_by_utid_.find(*utid);
-    if (utid_it != descriptor_tracks_by_utid_.end()) {
-      TrackId candidate_track_id = utid_it->second;
-      // Only update this track if it hasn't been associated with a different
-      // UUID already.
-      auto descriptor_it = std::find_if(
-          descriptor_tracks_.begin(), descriptor_tracks_.end(),
-          [candidate_track_id](const std::pair<uint64_t, TrackId>& entry) {
-            return entry.second == candidate_track_id;
-          });
-      if (descriptor_it == descriptor_tracks_.end()) {
-        descriptor_tracks_[uuid] = candidate_track_id;
-        context_->args_tracker->AddArg(
-            TableId::kTrack, candidate_track_id.value, source_id_key_,
-            source_id_key_, Variadic::Integer(static_cast<int64_t>(uuid)));
-
-        return candidate_track_id;
-      }
-    }
-
-    // New thread track.
-    tables::ThreadTrackTable::Row row(name);
-    row.utid = *utid;
-    track_id = context_->storage->mutable_thread_track_table()->Insert(row);
-    if (descriptor_tracks_by_utid_.find(*utid) ==
-        descriptor_tracks_by_utid_.end()) {
-      descriptor_tracks_by_utid_[*utid] = track_id;
-    }
-  } else if (upid) {
-    // New process-scoped async track.
-    tables::ProcessTrackTable::Row track(name);
-    track.upid = *upid;
-    track_id = context_->storage->mutable_process_track_table()->Insert(track);
-  } else {
-    // New global async track.
-    tables::TrackTable::Row track(name);
-    track_id = context_->storage->mutable_track_table()->Insert(track);
-  }
-
-  descriptor_tracks_[uuid] = track_id;
-
-  ArgsTracker::BoundInserter inserter(context_->args_tracker.get(),
-                                      TableId::kTrack, track_id.value);
-  inserter.AddArg(source_key_, Variadic::String(descriptor_source_));
-  inserter.AddArg(source_id_key_,
-                  Variadic::Integer(static_cast<int64_t>(uuid)));
-  return track_id;
+  it->second.min_timestamp = std::min(it->second.min_timestamp, timestamp);
 }
 
-base::Optional<TrackId> TrackTracker::GetDescriptorTrack(uint64_t uuid) const {
-  auto it = descriptor_tracks_.find(uuid);
-  if (it == descriptor_tracks_.end())
-    return base::nullopt;
+void TrackTracker::ReserveDescriptorThreadTrack(uint64_t uuid,
+                                                uint64_t parent_uuid,
+                                                uint32_t pid,
+                                                uint32_t tid,
+                                                int64_t timestamp) {
+  DescriptorTrackReservation reservation;
+  reservation.min_timestamp = timestamp;
+  reservation.parent_uuid = parent_uuid;
+  reservation.pid = pid;
+  reservation.tid = tid;
+
+  std::map<uint64_t, DescriptorTrackReservation>::iterator it;
+  bool inserted;
+  std::tie(it, inserted) =
+      reserved_descriptor_tracks_.insert(std::make_pair<>(uuid, reservation));
+
+  if (inserted)
+    return;
+
+  if (!it->second.IsForSameTrack(reservation)) {
+    // Thread tracks should not be reassigned to a different pid/tid later
+    // (neither should the type of the track change).
+    PERFETTO_DLOG("New track reservation for thread track with uuid %" PRIu64
+                  " doesn't match earlier one",
+                  uuid);
+    context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
+    return;
+  }
+
+  it->second.min_timestamp = std::min(it->second.min_timestamp, timestamp);
+}
+
+void TrackTracker::ReserveDescriptorChildTrack(uint64_t uuid,
+                                               uint64_t parent_uuid) {
+  DescriptorTrackReservation reservation;
+  reservation.parent_uuid = parent_uuid;
+
+  std::map<uint64_t, DescriptorTrackReservation>::iterator it;
+  bool inserted;
+  std::tie(it, inserted) =
+      reserved_descriptor_tracks_.insert(std::make_pair<>(uuid, reservation));
+
+  if (inserted || it->second.IsForSameTrack(reservation))
+    return;
+
+  // Child tracks should not be reassigned to a different parent track later
+  // (neither should the type of the track change).
+  PERFETTO_DLOG("New track reservation for child track with uuid %" PRIu64
+                " doesn't match earlier one",
+                uuid);
+  context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
+}
+
+base::Optional<TrackId> TrackTracker::GetDescriptorTrack(uint64_t uuid) {
+  auto it = resolved_descriptor_tracks_.find(uuid);
+  if (it == resolved_descriptor_tracks_.end()) {
+    auto reservation_it = reserved_descriptor_tracks_.find(uuid);
+    if (reservation_it == reserved_descriptor_tracks_.end())
+      return base::nullopt;
+    TrackId track_id = ResolveDescriptorTrack(uuid, reservation_it->second);
+    resolved_descriptor_tracks_[uuid] = track_id;
+    return track_id;
+  }
   return it->second;
 }
 
-TrackId TrackTracker::GetOrCreateDescriptorTrackForThread(UniqueTid utid) {
-  auto it = descriptor_tracks_by_utid_.find(utid);
-  if (it != descriptor_tracks_by_utid_.end()) {
-    return it->second;
+TrackId TrackTracker::ResolveDescriptorTrack(
+    uint64_t uuid,
+    const DescriptorTrackReservation& reservation) {
+  base::Optional<TrackId> parent_track_id;
+  if (reservation.parent_uuid) {
+    // Ensure that parent track is resolved.
+    parent_track_id = GetDescriptorTrack(reservation.parent_uuid);
+    if (!parent_track_id) {
+      PERFETTO_ELOG("Unknown parent track %" PRIu64 " for track %" PRIu64,
+                    reservation.parent_uuid, uuid);
+    }
   }
-  // TODO(eseckler): How should this track receive its name?
-  tables::ThreadTrackTable::Row row(/*name=*/kNullStringId);
-  row.utid = utid;
-  TrackId track_id =
-      context_->storage->mutable_thread_track_table()->Insert(row);
-  descriptor_tracks_by_utid_[utid] = track_id;
 
-  context_->args_tracker->AddArg(TableId::kTrack, track_id.value, source_key_,
-                                 source_key_,
-                                 Variadic::String(descriptor_source_));
-  return track_id;
+  if (reservation.tid) {
+    UniqueTid utid = context_->process_tracker->UpdateThread(*reservation.tid,
+                                                             *reservation.pid);
+    auto it_and_inserted =
+        descriptor_uuids_by_utid_.insert(std::make_pair<>(utid, uuid));
+    if (!it_and_inserted.second) {
+      // We already saw a another track with a different uuid for this thread.
+      // Since there should only be one descriptor track for each thread, we
+      // assume that its tid was reused. So, start a new thread.
+      uint64_t old_uuid = it_and_inserted.first->second;
+      PERFETTO_DCHECK(old_uuid != uuid);  // Every track is only resolved once.
+
+      PERFETTO_DLOG("Detected tid reuse (pid: %" PRIu32 " tid: %" PRIu32
+                    ") from track descriptors (old uuid: %" PRIu64
+                    " new uuid: %" PRIu64 " timestamp: %" PRId64 ")",
+                    *reservation.pid, *reservation.tid, old_uuid, uuid,
+                    reservation.min_timestamp);
+
+      utid = context_->process_tracker->StartNewThread(
+          reservation.min_timestamp, *reservation.tid, kNullStringId);
+
+      descriptor_uuids_by_utid_[utid] = uuid;
+    }
+    return InternThreadTrack(utid);
+  }
+
+  if (reservation.pid) {
+    UniquePid upid =
+        context_->process_tracker->GetOrCreateProcess(*reservation.pid);
+    auto it_and_inserted =
+        descriptor_uuids_by_upid_.insert(std::make_pair<>(upid, uuid));
+    if (!it_and_inserted.second) {
+      // We already saw a another track with a different uuid for this process.
+      // Since there should only be one descriptor track for each process, we
+      // assume that its pid was reused. So, start a new process.
+      uint64_t old_uuid = it_and_inserted.first->second;
+      PERFETTO_DCHECK(old_uuid != uuid);  // Every track is only resolved once.
+
+      PERFETTO_DLOG("Detected pid reuse (pid: %" PRIu32
+                    ") from track descriptors (old uuid: %" PRIu64
+                    " new uuid: %" PRIu64 " timestamp: %" PRId64 ")",
+                    *reservation.pid, old_uuid, uuid,
+                    reservation.min_timestamp);
+
+      upid = context_->process_tracker->StartNewProcess(
+          reservation.min_timestamp, base::nullopt, *reservation.pid,
+          kNullStringId);
+
+      descriptor_uuids_by_upid_[upid] = uuid;
+    }
+    return InternProcessTrack(upid);
+  }
+
+  base::Optional<TrackId> track_id;
+  if (parent_track_id) {
+    // If parent is a thread track, create another thread-associated track.
+    base::Optional<uint32_t> thread_track_index =
+        context_->storage->thread_track_table().id().IndexOf(*parent_track_id);
+    if (thread_track_index) {
+      auto* thread_tracks = context_->storage->mutable_thread_track_table();
+      tables::ThreadTrackTable::Row row;
+      row.utid = thread_tracks->utid()[*thread_track_index];
+      track_id = thread_tracks->Insert(row);
+    } else {
+      // If parent is a process track, create another process-associated track.
+      base::Optional<uint32_t> process_track_index =
+          context_->storage->process_track_table().id().IndexOf(
+              *parent_track_id);
+      if (process_track_index) {
+        auto* process_tracks = context_->storage->mutable_process_track_table();
+        tables::ProcessTrackTable::Row track;
+        track.upid = process_tracks->upid()[*process_track_index];
+        track_id = process_tracks->Insert(track);
+      }
+    }
+  }
+
+  // Otherwise create a global track.
+  if (!track_id) {
+    tables::TrackTable::Row track;
+    track_id = context_->storage->mutable_track_table()->Insert(track);
+  }
+
+  ArgsTracker::BoundInserter inserter(context_->args_tracker.get(),
+                                      TableId::kTrack, track_id->value);
+  inserter.AddArg(source_key_, Variadic::String(descriptor_source_));
+  inserter.AddArg(source_id_key_,
+                  Variadic::Integer(static_cast<int64_t>(uuid)));
+
+  return *track_id;
 }
 
 TrackId TrackTracker::GetOrCreateDefaultDescriptorTrack() {
-  base::Optional<TrackId> opt_track_id =
+  // If the default track was already reserved (e.g. because a producer emitted
+  // a descriptor for it) or created, resolve and return it.
+  base::Optional<TrackId> track_id =
       GetDescriptorTrack(kDefaultDescriptorTrackUuid);
-  if (opt_track_id)
-    return *opt_track_id;
+  if (track_id)
+    return *track_id;
 
-  return UpdateDescriptorTrack(kDefaultDescriptorTrackUuid,
-                               default_descriptor_track_name_);
+  // Otherwise reserve a new track and resolve it.
+  ReserveDescriptorChildTrack(kDefaultDescriptorTrackUuid, /*parent_uuid=*/0);
+  track_id = GetDescriptorTrack(kDefaultDescriptorTrackUuid);
+
+  auto* tracks = context_->storage->mutable_track_table();
+  tracks->mutable_name()->Set(*tracks->id().IndexOf(*track_id),
+                              default_descriptor_track_name_);
+  return *track_id;
 }
 
 TrackId TrackTracker::InternGlobalCounterTrack(StringId name) {
diff --git a/src/trace_processor/track_tracker.h b/src/trace_processor/track_tracker.h
index 3a2b0ba..803b908 100644
--- a/src/trace_processor/track_tracker.h
+++ b/src/trace_processor/track_tracker.h
@@ -31,6 +31,9 @@
   // Interns a thread track into the storage.
   TrackId InternThreadTrack(UniqueTid utid);
 
+  // Interns a process track into the storage.
+  TrackId InternProcessTrack(UniquePid upid);
+
   // Interns a Fuchsia async track into the storage.
   TrackId InternFuchsiaAsyncTrack(StringId name, int64_t correlation_id);
 
@@ -56,21 +59,50 @@
   // Lazily creates the track for legacy Chrome global instant events.
   TrackId GetOrCreateLegacyChromeGlobalInstantTrack();
 
-  // Create or update the track for the TrackDescriptor with the given |uuid|.
-  // Optionally, associate the track with a process or thread.
-  TrackId UpdateDescriptorTrack(uint64_t uuid,
-                                StringId name,
-                                base::Optional<UniquePid> upid = base::nullopt,
-                                base::Optional<UniqueTid> utid = base::nullopt);
+  // Associate a TrackDescriptor track identified by the given |uuid| with a
+  // process's |pid|. This is called during tokenization. If a reservation for
+  // the same |uuid| already exists, verifies that the present reservation
+  // matches the new one.
+  //
+  // The track will be resolved to the process track (see InternProcessTrack())
+  // upon the first call to GetDescriptorTrack() with the same |uuid|. At this
+  // time, |pid| will also be resolved to a |upid|.
+  void ReserveDescriptorProcessTrack(uint64_t uuid,
+                                     uint32_t pid,
+                                     int64_t timestamp);
+
+  // Associate a TrackDescriptor track identified by the given |uuid| with a
+  // thread's |pid| and |tid|. This is called during tokenization. If a
+  // reservation for the same |uuid| already exists, verifies that the present
+  // reservation matches the new one.
+  //
+  // The track will be resolved to the thread track (see InternThreadTrack())
+  // upon the first call to GetDescriptorTrack() with the same |uuid|. At this
+  // time, |pid| will also be resolved to a |upid|.
+  void ReserveDescriptorThreadTrack(uint64_t uuid,
+                                    uint64_t parent_uuid,
+                                    uint32_t pid,
+                                    uint32_t tid,
+                                    int64_t timestamp);
+
+  // Associate a TrackDescriptor track identified by the given |uuid| with a
+  // parent track (usually a process- or thread-associated track). This is
+  // called during tokenization. If a reservation for the same |uuid| already
+  // exists, will attempt to update it.
+  //
+  // The track will be created upon the first call to GetDescriptorTrack() with
+  // the same |uuid|. If |parent_uuid| is 0, the track will become a global
+  // track. Otherwise, it will become a new track of the same type as its parent
+  // track.
+  void ReserveDescriptorChildTrack(uint64_t uuid, uint64_t parent_uuid);
 
   // Returns the ID of the track for the TrackDescriptor with the given |uuid|.
-  // Returns nullopt if no TrackDescriptor with this |uuid| has been parsed yet.
-  base::Optional<TrackId> GetDescriptorTrack(uint64_t uuid) const;
-
-  // Returns the ID of the TrackDescriptor track associated with the given utid.
-  // If the trace contained multiple tracks associated with the utid, the first
-  // created track is returned. Creates a new track if no such track exists.
-  TrackId GetOrCreateDescriptorTrackForThread(UniqueTid utid);
+  // This is called during parsing. The first call to GetDescriptorTrack() for
+  // each |uuid| resolves and inserts the track (and its parent tracks,
+  // following the parent_uuid chain recursively) based on reservations made for
+  // the |uuid|. Returns nullopt if no track for a descriptor with this |uuid|
+  // has been reserved.
+  base::Optional<TrackId> GetDescriptorTrack(uint64_t uuid);
 
   // Returns the ID of the implicit trace-global default TrackDescriptor track.
   TrackId GetOrCreateDefaultDescriptorTrack();
@@ -135,18 +167,37 @@
              std::tie(r.upid, r.cookie, r.name);
     }
   };
+  struct DescriptorTrackReservation {
+    uint64_t parent_uuid = 0;
+    base::Optional<uint32_t> pid;
+    base::Optional<uint32_t> tid;
+    int64_t min_timestamp = 0;  // only set if |pid| and/or |tid| is set.
+
+    // Whether |other| is a valid descriptor for this track reservation. A track
+    // should always remain nested underneath its original parent.
+    bool IsForSameTrack(const DescriptorTrackReservation& other) {
+      // Note that |timestamp| is ignored for this comparison.
+      return std::tie(parent_uuid, pid, tid) ==
+             std::tie(other.parent_uuid, pid, tid);
+    }
+  };
+
+  TrackId ResolveDescriptorTrack(uint64_t uuid,
+                                 const DescriptorTrackReservation&);
 
   static constexpr uint64_t kDefaultDescriptorTrackUuid = 0u;
 
-  std::map<UniqueTid /* utid */, TrackId> thread_tracks_;
+  std::map<UniqueTid, TrackId> thread_tracks_;
+  std::map<UniquePid, TrackId> process_tracks_;
   std::map<int64_t /* correlation_id */, TrackId> fuchsia_async_tracks_;
   std::map<GpuTrackTuple, TrackId> gpu_tracks_;
   std::map<ChromeTrackTuple, TrackId> chrome_tracks_;
   std::map<AndroidAsyncTrackTuple, TrackId> android_async_tracks_;
   std::map<UniquePid, TrackId> chrome_process_instant_tracks_;
   base::Optional<TrackId> chrome_global_instant_track_id_;
-  std::map<uint64_t /* uuid */, TrackId> descriptor_tracks_;
-  std::map<UniqueTid, TrackId> descriptor_tracks_by_utid_;
+  std::map<uint64_t /* uuid */, DescriptorTrackReservation>
+      reserved_descriptor_tracks_;
+  std::map<uint64_t /* uuid */, TrackId> resolved_descriptor_tracks_;
 
   std::map<StringId, TrackId> global_counter_tracks_by_name_;
   std::map<std::pair<StringId, uint32_t>, TrackId> cpu_counter_tracks_;
@@ -156,6 +207,11 @@
   std::map<std::pair<StringId, int32_t>, TrackId> softirq_counter_tracks_;
   std::map<std::pair<StringId, uint32_t>, TrackId> gpu_counter_tracks_;
 
+  // Stores the descriptor uuid used for the primary process/thread track
+  // for the given upid / utid. Used for pid/tid reuse detection.
+  std::map<UniquePid, uint64_t /*uuid*/> descriptor_uuids_by_upid_;
+  std::map<UniqueTid, uint64_t /*uuid*/> descriptor_uuids_by_utid_;
+
   const StringId source_key_ = 0;
   const StringId source_id_key_ = 0;
   const StringId source_id_is_process_scoped_key_ = 0;