TrackEvent: Add support for tracks

Track events are recorded on a timeline track, which can be attached to a
process (ProcessTrack) or a thread (ThreadTrack). Custom timelines can be
created using asynchronous tracks (see AsyncTrack), which can also optionally
be scoped to a thread or a process.

A track is represented by a uuid, which must be unique across the entire
recorded trace.

Async event example:

  TRACE_EVENT_BEGIN("category", "AsyncEvent", perfetto::AsyncTrack(8086));
  ...
  TRACE_EVENT_END("category", perfetto::AsyncTrack(8086));

Tracks can also be annotated with metadata:

  perfetto::TrackEvent::SetAsyncTrackDescriptor(
      track, [](perfetto::protos::gen::TrackDescriptor* desc) {
        desc->set_name("MyAsyncTrack");
      });

Bug: 132678367
Change-Id: I83c5300fe48ae2e9612942b2f73237fd67f948d3
diff --git a/Android.bp b/Android.bp
index 35cec9e..76fb7b1 100644
--- a/Android.bp
+++ b/Android.bp
@@ -6229,6 +6229,7 @@
     "src/tracing/internal/track_event_internal.cc",
     "src/tracing/platform.cc",
     "src/tracing/tracing.cc",
+    "src/tracing/track.cc",
     "src/tracing/track_event_category_registry.cc",
     "src/tracing/virtual_destructors.cc",
   ],
diff --git a/BUILD b/BUILD
index 4ee218d..ec5393f 100644
--- a/BUILD
+++ b/BUILD
@@ -245,8 +245,11 @@
         "include/perfetto/base/build_config.h",
         "include/perfetto/base/compiler.h",
         "include/perfetto/base/export.h",
+        "include/perfetto/base/flat_set.h",
         "include/perfetto/base/logging.h",
+        "include/perfetto/base/proc_utils.h",
         "include/perfetto/base/task_runner.h",
+        "include/perfetto/base/thread_utils.h",
         "include/perfetto/base/time.h",
     ],
 )
@@ -259,7 +262,6 @@
         "include/perfetto/ext/base/container_annotations.h",
         "include/perfetto/ext/base/event_fd.h",
         "include/perfetto/ext/base/file_utils.h",
-        "include/perfetto/ext/base/flat_set.h",
         "include/perfetto/ext/base/hash.h",
         "include/perfetto/ext/base/lookup_set.h",
         "include/perfetto/ext/base/metatrace.h",
@@ -268,7 +270,6 @@
         "include/perfetto/ext/base/optional.h",
         "include/perfetto/ext/base/paged_memory.h",
         "include/perfetto/ext/base/pipe.h",
-        "include/perfetto/ext/base/proc_utils.h",
         "include/perfetto/ext/base/scoped_file.h",
         "include/perfetto/ext/base/small_set.h",
         "include/perfetto/ext/base/string_splitter.h",
@@ -279,7 +280,6 @@
         "include/perfetto/ext/base/thread_annotations.h",
         "include/perfetto/ext/base/thread_checker.h",
         "include/perfetto/ext/base/thread_task_runner.h",
-        "include/perfetto/ext/base/thread_utils.h",
         "include/perfetto/ext/base/unix_socket.h",
         "include/perfetto/ext/base/unix_task_runner.h",
         "include/perfetto/ext/base/utils.h",
@@ -477,6 +477,7 @@
         "include/perfetto/tracing/trace_writer_base.h",
         "include/perfetto/tracing/tracing.h",
         "include/perfetto/tracing/tracing_backend.h",
+        "include/perfetto/tracing/track.h",
         "include/perfetto/tracing/track_event.h",
         "include/perfetto/tracing/track_event_category_registry.h",
         "include/perfetto/tracing/track_event_interned_data_index.h",
@@ -1126,6 +1127,7 @@
         "src/tracing/internal/track_event_internal.cc",
         "src/tracing/platform.cc",
         "src/tracing/tracing.cc",
+        "src/tracing/track.cc",
         "src/tracing/track_event_category_registry.cc",
         "src/tracing/virtual_destructors.cc",
     ],
diff --git a/include/perfetto/base/BUILD.gn b/include/perfetto/base/BUILD.gn
index e490551..85819b7 100644
--- a/include/perfetto/base/BUILD.gn
+++ b/include/perfetto/base/BUILD.gn
@@ -19,8 +19,11 @@
     "build_config.h",
     "compiler.h",
     "export.h",
+    "flat_set.h",
     "logging.h",
+    "proc_utils.h",
     "task_runner.h",
+    "thread_utils.h",
     "time.h",
   ]
 }
diff --git a/include/perfetto/ext/base/flat_set.h b/include/perfetto/base/flat_set.h
similarity index 91%
rename from include/perfetto/ext/base/flat_set.h
rename to include/perfetto/base/flat_set.h
index e33fcf7..068ad3c 100644
--- a/include/perfetto/ext/base/flat_set.h
+++ b/include/perfetto/base/flat_set.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef INCLUDE_PERFETTO_EXT_BASE_FLAT_SET_H_
-#define INCLUDE_PERFETTO_EXT_BASE_FLAT_SET_H_
+#ifndef INCLUDE_PERFETTO_BASE_FLAT_SET_H_
+#define INCLUDE_PERFETTO_BASE_FLAT_SET_H_
 
 #include <algorithm>
 #include <vector>
@@ -63,14 +63,15 @@
 
   size_t count(T value) const { return find(value) == entries_.end() ? 0 : 1; }
 
-  void insert(T value) {
+  std::pair<iterator, bool> insert(T value) {
     auto entries_end = entries_.end();
     auto it = std::lower_bound(entries_.begin(), entries_end, value);
     if (it != entries_end && *it == value)
-      return;
+      return std::make_pair(it, false);
     // If the value is not found |it| is either end() or the next item strictly
     // greater than |value|. In both cases we want to insert just before that.
-    entries_.insert(it, std::move(value));
+    it = entries_.insert(it, std::move(value));
+    return std::make_pair(it, true);
   }
 
   size_t erase(T value) {
@@ -96,4 +97,4 @@
 }  // namespace base
 }  // namespace perfetto
 
-#endif  // INCLUDE_PERFETTO_EXT_BASE_FLAT_SET_H_
+#endif  // INCLUDE_PERFETTO_BASE_FLAT_SET_H_
diff --git a/include/perfetto/ext/base/proc_utils.h b/include/perfetto/base/proc_utils.h
similarity index 90%
rename from include/perfetto/ext/base/proc_utils.h
rename to include/perfetto/base/proc_utils.h
index d5bacb9..8818ec0 100644
--- a/include/perfetto/ext/base/proc_utils.h
+++ b/include/perfetto/base/proc_utils.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef INCLUDE_PERFETTO_EXT_BASE_PROC_UTILS_H_
-#define INCLUDE_PERFETTO_EXT_BASE_PROC_UTILS_H_
+#ifndef INCLUDE_PERFETTO_BASE_PROC_UTILS_H_
+#define INCLUDE_PERFETTO_BASE_PROC_UTILS_H_
 
 #include <stdint.h>
 
@@ -54,4 +54,4 @@
 }  // namespace base
 }  // namespace perfetto
 
-#endif  // INCLUDE_PERFETTO_EXT_BASE_PROC_UTILS_H_
+#endif  // INCLUDE_PERFETTO_BASE_PROC_UTILS_H_
diff --git a/include/perfetto/ext/base/thread_utils.h b/include/perfetto/base/thread_utils.h
similarity index 93%
rename from include/perfetto/ext/base/thread_utils.h
rename to include/perfetto/base/thread_utils.h
index 2e6495c..fa93c27 100644
--- a/include/perfetto/ext/base/thread_utils.h
+++ b/include/perfetto/base/thread_utils.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef INCLUDE_PERFETTO_EXT_BASE_THREAD_UTILS_H_
-#define INCLUDE_PERFETTO_EXT_BASE_THREAD_UTILS_H_
+#ifndef INCLUDE_PERFETTO_BASE_THREAD_UTILS_H_
+#define INCLUDE_PERFETTO_BASE_THREAD_UTILS_H_
 
 #include <stdint.h>
 
@@ -79,4 +79,4 @@
 }  // namespace base
 }  // namespace perfetto
 
-#endif  // INCLUDE_PERFETTO_EXT_BASE_THREAD_UTILS_H_
+#endif  // INCLUDE_PERFETTO_BASE_THREAD_UTILS_H_
diff --git a/include/perfetto/ext/base/BUILD.gn b/include/perfetto/ext/base/BUILD.gn
index 7d76c84..fd369cf 100644
--- a/include/perfetto/ext/base/BUILD.gn
+++ b/include/perfetto/ext/base/BUILD.gn
@@ -20,7 +20,6 @@
     "container_annotations.h",
     "event_fd.h",
     "file_utils.h",
-    "flat_set.h",
     "hash.h",
     "lookup_set.h",
     "metatrace.h",
@@ -29,7 +28,6 @@
     "optional.h",
     "paged_memory.h",
     "pipe.h",
-    "proc_utils.h",
     "scoped_file.h",
     "small_set.h",
     "string_splitter.h",
@@ -40,7 +38,6 @@
     "thread_annotations.h",
     "thread_checker.h",
     "thread_task_runner.h",
-    "thread_utils.h",
     "unix_task_runner.h",
     "utils.h",
     "uuid.h",
diff --git a/include/perfetto/ext/base/metatrace.h b/include/perfetto/ext/base/metatrace.h
index 2c587c3..f5eb057 100644
--- a/include/perfetto/ext/base/metatrace.h
+++ b/include/perfetto/ext/base/metatrace.h
@@ -23,10 +23,10 @@
 #include <string>
 
 #include "perfetto/base/logging.h"
+#include "perfetto/base/thread_utils.h"
 #include "perfetto/base/time.h"
 #include "perfetto/ext/base/metatrace_events.h"
 #include "perfetto/ext/base/thread_annotations.h"
-#include "perfetto/ext/base/thread_utils.h"
 #include "perfetto/ext/base/utils.h"
 
 // A facility to trace execution of the perfetto codebase itself.
diff --git a/include/perfetto/ext/base/unix_task_runner.h b/include/perfetto/ext/base/unix_task_runner.h
index 9ced3b9..9a743e3 100644
--- a/include/perfetto/ext/base/unix_task_runner.h
+++ b/include/perfetto/ext/base/unix_task_runner.h
@@ -19,11 +19,11 @@
 
 #include "perfetto/base/build_config.h"
 #include "perfetto/base/task_runner.h"
+#include "perfetto/base/thread_utils.h"
 #include "perfetto/base/time.h"
 #include "perfetto/ext/base/event_fd.h"
 #include "perfetto/ext/base/scoped_file.h"
 #include "perfetto/ext/base/thread_checker.h"
-#include "perfetto/ext/base/thread_utils.h"
 
 #include <poll.h>
 #include <chrono>
diff --git a/include/perfetto/tracing/BUILD.gn b/include/perfetto/tracing/BUILD.gn
index 01294bd..60f4d4e 100644
--- a/include/perfetto/tracing/BUILD.gn
+++ b/include/perfetto/tracing/BUILD.gn
@@ -41,6 +41,7 @@
     "trace_writer_base.h",
     "tracing.h",
     "tracing_backend.h",
+    "track.h",
     "track_event.h",
     "track_event_category_registry.h",
     "track_event_interned_data_index.h",
diff --git a/include/perfetto/tracing/internal/track_event_data_source.h b/include/perfetto/tracing/internal/track_event_data_source.h
index 1debee3..e6c8af0 100644
--- a/include/perfetto/tracing/internal/track_event_data_source.h
+++ b/include/perfetto/tracing/internal/track_event_data_source.h
@@ -22,6 +22,7 @@
 #include "perfetto/tracing/data_source.h"
 #include "perfetto/tracing/event_context.h"
 #include "perfetto/tracing/internal/track_event_internal.h"
+#include "perfetto/tracing/track.h"
 #include "perfetto/tracing/track_event_category_registry.h"
 #include "protos/perfetto/trace/track_event/track_event.pbzero.h"
 
@@ -30,6 +31,34 @@
 
 namespace perfetto {
 namespace internal {
+namespace {
+
+// A template helper for determining whether a type can be used as a track event
+// lambda, i.e., it has the signature "void(EventContext)". This is achieved by
+// checking that we can pass an EventContext value (the inner declval) into a T
+// instance (the outer declval). If this is a valid expression, the result
+// evaluates to sizeof(0), i.e., true.
+// TODO(skyostil): Replace this with std::is_convertible<std::function<...>>
+// once we have C++14.
+template <typename T>
+static constexpr bool IsValidTraceLambdaImpl(
+    typename std::enable_if<static_cast<bool>(
+        sizeof(std::declval<T>()(std::declval<EventContext>()), 0))>::type* =
+        nullptr) {
+  return true;
+}
+
+template <typename T>
+static constexpr bool IsValidTraceLambdaImpl(...) {
+  return false;
+}
+
+template <typename T>
+static constexpr bool IsValidTraceLambda() {
+  return IsValidTraceLambdaImpl<T>(nullptr);
+}
+
+}  // namespace
 
 struct TrackEventDataSourceTraits : public perfetto::DefaultDataSourceTraits {
   using IncrementalStateType = TrackEventIncrementalState;
@@ -87,8 +116,8 @@
   }
 
   // Once we've determined tracing to be enabled for this category, actually
-  // write a trace event. Outlined to avoid bloating code at the actual trace
-  // point.
+  // write a trace event onto this thread's default track. Outlined to avoid
+  // bloating code at the actual trace point.
   // TODO(skyostil): Investigate whether this should be fully outlined to reduce
   // binary size.
   template <size_t CategoryIndex,
@@ -97,13 +126,51 @@
       uint32_t instances,
       const char* event_name,
       perfetto::protos::pbzero::TrackEvent::Type type,
-      ArgumentFunction arg_function = [](EventContext) {}) PERFETTO_NO_INLINE {
+      ArgumentFunction arg_function = [](EventContext) {},
+      typename std::enable_if<IsValidTraceLambda<ArgumentFunction>()>::type* =
+          nullptr) PERFETTO_NO_INLINE {
+    // We don't simply call TraceForCategory(..., Track(), ...) here, since that
+    // would add extra binary bloat to all trace points that target the default
+    // track.
     Base::template TraceWithInstances<CategoryTracePointTraits<CategoryIndex>>(
         instances, [&](typename Base::TraceContext ctx) {
           // TODO(skyostil): Intern categories at compile time.
           arg_function(TrackEventInternal::WriteEvent(
               ctx.tls_inst_->trace_writer.get(), ctx.GetIncrementalState(),
               Registry->GetCategory(CategoryIndex)->name, event_name, type));
+          // There's no need to emit a track descriptor for the default track
+          // here since that's done in ResetIncrementalState().
+        });
+  }
+
+  // This variant of the inner trace point takes a Track argument which can be
+  // used to emit events on a non-default track.
+  template <size_t CategoryIndex,
+            typename TrackType,
+            typename ArgumentFunction = void (*)(EventContext)>
+  static void TraceForCategory(
+      uint32_t instances,
+      const char* event_name,
+      perfetto::protos::pbzero::TrackEvent::Type type,
+      const TrackType& track,
+      ArgumentFunction arg_function = [](EventContext) {},
+      typename std::enable_if<IsValidTraceLambda<ArgumentFunction>()>::type* =
+          nullptr,
+      typename std::enable_if<
+          std::is_convertible<TrackType, Track>::value>::type* = nullptr)
+      PERFETTO_NO_INLINE {
+    PERFETTO_DCHECK(track);
+    Base::template TraceWithInstances<CategoryTracePointTraits<CategoryIndex>>(
+        instances, [&](typename Base::TraceContext ctx) {
+          // TODO(skyostil): Intern categories at compile time.
+          auto event_ctx = TrackEventInternal::WriteEvent(
+              ctx.tls_inst_->trace_writer.get(), ctx.GetIncrementalState(),
+              Registry->GetCategory(CategoryIndex)->name, event_name, type);
+          event_ctx.event()->set_track_uuid(track.uuid);
+          arg_function(std::move(event_ctx));
+          TrackEventInternal::WriteTrackDescriptorIfNeeded(
+              track, ctx.tls_inst_->trace_writer.get(),
+              ctx.GetIncrementalState());
         });
   }
 
@@ -124,27 +191,51 @@
                                perfetto::protos::pbzero::TrackEvent::Type type,
                                const char* arg_name,
                                ArgType&& arg_value) PERFETTO_ALWAYS_INLINE {
-    TraceForCategoryWithDebugAnnotations<CategoryIndex, ArgType>(
-        instances, event_name, type, arg_name,
+    TraceForCategoryWithDebugAnnotations<CategoryIndex, Track, ArgType>(
+        instances, event_name, type, Track(), arg_name,
         std::forward<ArgType>(arg_value));
   }
 
-  template <size_t CategoryIndex, typename ArgType>
+  // A one argument trace point which takes an explicit track.
+  template <size_t CategoryIndex, typename TrackType, typename ArgType>
+  static void TraceForCategory(uint32_t instances,
+                               const char* event_name,
+                               perfetto::protos::pbzero::TrackEvent::Type type,
+                               const TrackType& track,
+                               const char* arg_name,
+                               ArgType&& arg_value) PERFETTO_ALWAYS_INLINE {
+    PERFETTO_DCHECK(track);
+    TraceForCategoryWithDebugAnnotations<CategoryIndex, TrackType, ArgType>(
+        instances, event_name, type, track, arg_name,
+        std::forward<ArgType>(arg_value));
+  }
+
+  template <size_t CategoryIndex, typename TrackType, typename ArgType>
   static void TraceForCategoryWithDebugAnnotations(
       uint32_t instances,
       const char* event_name,
       perfetto::protos::pbzero::TrackEvent::Type type,
+      const TrackType& track,
       const char* arg_name,
       typename internal::DebugAnnotationArg<ArgType>::type arg_value)
       PERFETTO_NO_INLINE {
     Base::template TraceWithInstances<CategoryTracePointTraits<CategoryIndex>>(
         instances, [&](typename Base::TraceContext ctx) {
-          // TODO(skyostil): Intern categories at compile time.
-          auto event_ctx = TrackEventInternal::WriteEvent(
-              ctx.tls_inst_->trace_writer.get(), ctx.GetIncrementalState(),
-              Registry->GetCategory(CategoryIndex)->name, event_name, type);
-          TrackEventInternal::AddDebugAnnotation(&event_ctx, arg_name,
-                                                 arg_value);
+          {
+            // TODO(skyostil): Intern categories at compile time.
+            auto event_ctx = TrackEventInternal::WriteEvent(
+                ctx.tls_inst_->trace_writer.get(), ctx.GetIncrementalState(),
+                Registry->GetCategory(CategoryIndex)->name, event_name, type);
+            if (track)
+              event_ctx.event()->set_track_uuid(track.uuid);
+            TrackEventInternal::AddDebugAnnotation(&event_ctx, arg_name,
+                                                   arg_value);
+          }
+          if (track) {
+            TrackEventInternal::WriteTrackDescriptorIfNeeded(
+                track, ctx.tls_inst_->trace_writer.get(),
+                ctx.GetIncrementalState());
+          }
         });
   }
 
@@ -160,16 +251,43 @@
                                ArgType&& arg_value,
                                const char* arg_name2,
                                ArgType2&& arg_value2) PERFETTO_ALWAYS_INLINE {
-    TraceForCategoryWithDebugAnnotations<CategoryIndex, ArgType, ArgType2>(
-        instances, event_name, type, arg_name, std::forward<ArgType>(arg_value),
-        arg_name2, std::forward<ArgType2>(arg_value2));
+    TraceForCategoryWithDebugAnnotations<CategoryIndex, Track, ArgType,
+                                         ArgType2>(
+        instances, event_name, type, Track(), arg_name,
+        std::forward<ArgType>(arg_value), arg_name2,
+        std::forward<ArgType2>(arg_value2));
   }
 
-  template <size_t CategoryIndex, typename ArgType, typename ArgType2>
+  // A two argument trace point which takes an explicit track.
+  template <size_t CategoryIndex,
+            typename TrackType,
+            typename ArgType,
+            typename ArgType2>
+  static void TraceForCategory(uint32_t instances,
+                               const char* event_name,
+                               perfetto::protos::pbzero::TrackEvent::Type type,
+                               const TrackType& track,
+                               const char* arg_name,
+                               ArgType&& arg_value,
+                               const char* arg_name2,
+                               ArgType2&& arg_value2) PERFETTO_ALWAYS_INLINE {
+    PERFETTO_DCHECK(track);
+    TraceForCategoryWithDebugAnnotations<CategoryIndex, TrackType, ArgType,
+                                         ArgType2>(
+        instances, event_name, type, track, arg_name,
+        std::forward<ArgType>(arg_value), arg_name2,
+        std::forward<ArgType2>(arg_value2));
+  }
+
+  template <size_t CategoryIndex,
+            typename TrackType,
+            typename ArgType,
+            typename ArgType2>
   static void TraceForCategoryWithDebugAnnotations(
       uint32_t instances,
       const char* event_name,
       perfetto::protos::pbzero::TrackEvent::Type type,
+      TrackType track,
       const char* arg_name,
       typename internal::DebugAnnotationArg<ArgType>::type arg_value,
       const char* arg_name2,
@@ -178,16 +296,27 @@
     Base::template TraceWithInstances<CategoryTracePointTraits<CategoryIndex>>(
         instances, [&](typename Base::TraceContext ctx) {
           // TODO(skyostil): Intern categories at compile time.
-          auto event_ctx = TrackEventInternal::WriteEvent(
-              ctx.tls_inst_->trace_writer.get(), ctx.GetIncrementalState(),
-              Registry->GetCategory(CategoryIndex)->name, event_name, type);
-          TrackEventInternal::AddDebugAnnotation(&event_ctx, arg_name,
-                                                 arg_value);
-          TrackEventInternal::AddDebugAnnotation(&event_ctx, arg_name2,
-                                                 arg_value2);
+          {
+            auto event_ctx = TrackEventInternal::WriteEvent(
+                ctx.tls_inst_->trace_writer.get(), ctx.GetIncrementalState(),
+                Registry->GetCategory(CategoryIndex)->name, event_name, type);
+            if (track)
+              event_ctx.event()->set_track_uuid(track.uuid);
+            TrackEventInternal::AddDebugAnnotation(&event_ctx, arg_name,
+                                                   arg_value);
+            TrackEventInternal::AddDebugAnnotation(&event_ctx, arg_name2,
+                                                   arg_value2);
+          }
+          if (track) {
+            TrackEventInternal::WriteTrackDescriptorIfNeeded(
+                track, ctx.tls_inst_->trace_writer.get(),
+                ctx.GetIncrementalState());
+          }
         });
   }
 
+  // Initialize the track event library. Should be called before tracing is
+  // enabled.
   static bool Register() {
     // Registration is performed out-of-line so users don't need to depend on
     // DataSourceDescriptor C++ bindings.
@@ -195,6 +324,29 @@
         [](const DataSourceDescriptor& dsd) { return Base::Register(dsd); });
   }
 
+  // Record metadata about different types of timeline tracks. See Track.
+  static void SetTrackDescriptor(
+      const Track& track,
+      std::function<void(protos::pbzero::TrackDescriptor*)> callback) {
+    SetTrackDescriptorImpl(track, std::move(callback));
+  }
+
+  static void SetProcessDescriptor(
+      std::function<void(protos::pbzero::TrackDescriptor*)> callback,
+      const ProcessTrack& track = ProcessTrack::Current()) {
+    SetTrackDescriptorImpl(std::move(track), std::move(callback));
+  }
+
+  static void SetThreadDescriptor(
+      std::function<void(protos::pbzero::TrackDescriptor*)> callback,
+      const ThreadTrack& track = ThreadTrack::Current()) {
+    SetTrackDescriptorImpl(std::move(track), std::move(callback));
+  }
+
+  static void EraseTrackDescriptor(const Track& track) {
+    TrackRegistry::Get()->EraseTrack(track);
+  }
+
  private:
   // Each category has its own enabled/disabled state, stored in the category
   // registry.
@@ -204,6 +356,20 @@
       return Registry->GetCategoryState(CategoryIndex);
     }
   };
+
+  // Records a track descriptor into the track descriptor registry and, if we
+  // are tracing, also mirrors the descriptor into the trace.
+  template <typename TrackType>
+  static void SetTrackDescriptorImpl(
+      const TrackType& track,
+      std::function<void(protos::pbzero::TrackDescriptor*)> callback) {
+    TrackRegistry::Get()->UpdateTrack(
+        track, [&](protos::pbzero::TrackDescriptor* desc) { callback(desc); });
+    Base::template Trace([&](typename Base::TraceContext ctx) {
+      TrackEventInternal::WriteTrackDescriptor(
+          track, ctx.tls_inst_->trace_writer.get());
+    });
+  }
 };
 
 }  // namespace internal
diff --git a/include/perfetto/tracing/internal/track_event_internal.h b/include/perfetto/tracing/internal/track_event_internal.h
index e37a30b..a4ee050 100644
--- a/include/perfetto/tracing/internal/track_event_internal.h
+++ b/include/perfetto/tracing/internal/track_event_internal.h
@@ -17,15 +17,17 @@
 #ifndef INCLUDE_PERFETTO_TRACING_INTERNAL_TRACK_EVENT_INTERNAL_H_
 #define INCLUDE_PERFETTO_TRACING_INTERNAL_TRACK_EVENT_INTERNAL_H_
 
-#include <unordered_map>
-
+#include "perfetto/base/flat_set.h"
 #include "perfetto/protozero/scattered_heap_buffer.h"
 #include "perfetto/tracing/core/forward_decls.h"
 #include "perfetto/tracing/debug_annotation.h"
 #include "perfetto/tracing/trace_writer_base.h"
+#include "perfetto/tracing/track.h"
 #include "protos/perfetto/trace/interned_data/interned_data.pbzero.h"
 #include "protos/perfetto/trace/track_event/track_event.pbzero.h"
 
+#include <set>
+
 namespace perfetto {
 class EventContext;
 namespace protos {
@@ -70,6 +72,11 @@
                 std::unique_ptr<BaseTrackEventInternedDataIndex>>;
   std::array<InternedDataIndex, kMaxInternedDataFields> interned_data_indices =
       {};
+
+  // Track uuids for which we have written descriptors into the trace. If a
+  // trace event uses a track which is not in this set, we'll write out a
+  // descriptor for it.
+  base::FlatSet<uint64_t> seen_tracks;
 };
 
 // The backend portion of the track event trace point implemention. Outlined to
@@ -101,7 +108,36 @@
     WriteDebugAnnotation(annotation, value);
   }
 
+  // If the given track hasn't been seen by the trace writer yet, write a
+  // descriptor for it into the trace. Doesn't take a lock unless the track
+  // descriptor is new.
+  template <typename TrackType>
+  static void WriteTrackDescriptorIfNeeded(
+      const TrackType& track,
+      TraceWriterBase* trace_writer,
+      TrackEventIncrementalState* incr_state) {
+    auto it_and_inserted = incr_state->seen_tracks.insert(track.uuid);
+    if (PERFETTO_LIKELY(!it_and_inserted.second))
+      return;
+    WriteTrackDescriptor(track, trace_writer);
+  }
+
+  // Unconditionally write a track descriptor into the trace.
+  template <typename TrackType>
+  static void WriteTrackDescriptor(const TrackType& track,
+                                   TraceWriterBase* trace_writer) {
+    TrackRegistry::Get()->SerializeTrack(
+        track, NewTracePacket(trace_writer, GetTimeNs()));
+  }
+
  private:
+  static uint64_t GetTimeNs();
+  static void ResetIncrementalState(TraceWriterBase*, uint64_t timestamp);
+  static protozero::MessageHandle<protos::pbzero::TracePacket> NewTracePacket(
+      TraceWriterBase*,
+      uint64_t timestamp,
+      uint32_t seq_flags =
+          protos::pbzero::TracePacket::SEQ_NEEDS_INCREMENTAL_STATE);
   static protos::pbzero::DebugAnnotation* AddDebugAnnotation(
       perfetto::EventContext*,
       const char* name);
diff --git a/include/perfetto/tracing/track.h b/include/perfetto/tracing/track.h
new file mode 100644
index 0000000..a93b0f9
--- /dev/null
+++ b/include/perfetto/tracing/track.h
@@ -0,0 +1,214 @@
+/*
+ * 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 INCLUDE_PERFETTO_TRACING_TRACK_H_
+#define INCLUDE_PERFETTO_TRACING_TRACK_H_
+
+#include "perfetto/base/proc_utils.h"
+#include "perfetto/base/thread_utils.h"
+#include "perfetto/protozero/message_handle.h"
+#include "perfetto/protozero/scattered_heap_buffer.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+#include "protos/perfetto/trace/track_event/track_descriptor.pbzero.h"
+
+#include <stdint.h>
+#include <map>
+#include <mutex>
+
+namespace perfetto {
+namespace internal {
+class TrackRegistry;
+}
+
+// Track events are recorded on a timeline track, which maintains the relative
+// time ordering of all events on that track. Each thread has its own default
+// track (ThreadTrack), which is by default where all track events are written.
+// Thread tracks are grouped under their hosting process (ProcessTrack).
+
+// Events which aren't strictly scoped to a thread or a process, or don't
+// correspond to synchronous code execution on a thread can use a custom
+// track (Track, ThreadTrack or ProcessTrack). A Track object can also
+// optionally be parented to a thread or a process.
+//
+// A track is represented by a uuid, which must be unique across the entire
+// recorded trace.
+//
+// For example, to record an event that begins and ends on different threads,
+// use a matching id to tie the begin and end events together:
+//
+//   TRACE_EVENT_BEGIN("category", "AsyncEvent", perfetto::Track(8086));
+//   ...
+//   TRACE_EVENT_END("category", perfetto::Track(8086));
+//
+// Tracks can also be annotated with metadata:
+//
+//   perfetto::TrackEvent::SetTrackDescriptor(
+//       track, [](perfetto::protos::pbzero::TrackDescriptor* desc) {
+//         desc->set_name("MyTrack");
+//       });
+//
+// The metadata remains valid between tracing sessions. To free up data for a
+// track, call EraseTrackDescriptor:
+//
+//   perfetto::TrackEvent::EraseTrackDescriptor(track);
+//
+struct Track {
+  const uint64_t uuid;
+  const uint64_t parent_uuid;
+  constexpr Track() : uuid(0), parent_uuid(0) {}
+
+  // Construct a track with identifier |id|, optionally parented under |parent|.
+  // If no parent is specified, the track's parent is the current process's
+  // track.
+  //
+  // To minimize the chances for accidental id collisions across processes, the
+  // track's effective uuid is generated by xorring |id| with a random,
+  // per-process cookie.
+  explicit Track(uint64_t id, Track parent = MakeProcessTrack())
+      : uuid(id ^ parent.uuid), parent_uuid(parent.uuid) {}
+
+  explicit operator bool() const { return uuid; }
+  void Serialize(protos::pbzero::TrackDescriptor*) const;
+
+ protected:
+  static Track MakeThreadTrack(base::PlatformThreadID tid_) {
+    return Track(static_cast<uint64_t>(tid_), MakeProcessTrack());
+  }
+
+  static Track MakeProcessTrack() { return Track(process_uuid, Track()); }
+
+ private:
+  friend class internal::TrackRegistry;
+  static uint64_t process_uuid;
+};
+
+// A process track represents events that describe the state of the entire
+// application (e.g., counter events). Currently a ProcessTrack can only
+// represent the current process.
+struct ProcessTrack : public Track {
+  const base::PlatformProcessId pid;
+
+  static ProcessTrack Current() { return ProcessTrack(); }
+
+  void Serialize(protos::pbzero::TrackDescriptor*) const;
+
+ private:
+  ProcessTrack() : Track(MakeProcessTrack()), pid(base::GetProcessId()) {}
+};
+
+// A thread track is associated with a specific thread of execution. Currently
+// only threads in the current process can be referenced.
+struct ThreadTrack : public Track {
+  const base::PlatformProcessId pid;
+  const base::PlatformThreadID tid;
+
+  static ThreadTrack Current() { return ThreadTrack(base::GetThreadId()); }
+
+  // Represents a thread in the current process.
+  static ThreadTrack ForThread(base::PlatformThreadID tid_) {
+    return ThreadTrack(tid_);
+  }
+
+  void Serialize(protos::pbzero::TrackDescriptor*) const;
+
+ private:
+  explicit ThreadTrack(base::PlatformThreadID tid_)
+      : Track(MakeThreadTrack(tid_)),
+        pid(ProcessTrack::Current().pid),
+        tid(tid_) {}
+};
+
+namespace internal {
+
+// Keeps a map of uuids to serialized track descriptors and provides a
+// thread-safe way to read and write them. Each trace writer keeps a TLS set of
+// the tracks it has seen (see TrackEventIncrementalState). In the common case,
+// this registry is not consulted (and no locks are taken). However when a new
+// track is seen, this registry is used to write either 1) the default
+// descriptor for that track (see *Track::Serialize) or 2) a serialized
+// descriptor stored in the registry which may have additional metadata (e.g.,
+// track name).
+class TrackRegistry {
+ public:
+  using SerializedTrackDescriptor = std::string;
+
+  TrackRegistry();
+  ~TrackRegistry();
+
+  static void InitializeInstance();
+  static TrackRegistry* Get() { return instance_; }
+
+  void EraseTrack(Track);
+
+  // Store metadata for |track| in the registry. |fill_function| is called
+  // synchronously to record additional properties for the track.
+  template <typename TrackType>
+  void UpdateTrack(
+      const TrackType& track,
+      std::function<void(protos::pbzero::TrackDescriptor*)> fill_function) {
+    UpdateTrackImpl(track, [&](protos::pbzero::TrackDescriptor* desc) {
+      track.Serialize(desc);
+      fill_function(desc);
+    });
+  }
+
+  // If |track| exists in the registry, write out the serialized track
+  // descriptor for it into |packet|. Otherwise just the ephemeral track object
+  // is serialized without any additional metadata.
+  template <typename TrackType>
+  void SerializeTrack(
+      const TrackType& track,
+      protozero::MessageHandle<protos::pbzero::TracePacket> packet) {
+    // If the track has extra metadata (recorded with UpdateTrack), it will be
+    // found in the registry. To minimize the time the lock is held, make a copy
+    // of the data held in the registry and write it outside the lock.
+    std::string desc_copy;
+    {
+      std::lock_guard<std::mutex> lock(mutex_);
+      const auto& it = tracks_.find(track.uuid);
+      if (it != tracks_.end()) {
+        desc_copy = it->second;
+        PERFETTO_DCHECK(!desc_copy.empty());
+      }
+    }
+    if (!desc_copy.empty()) {
+      WriteTrackDescriptor(std::move(desc_copy), std::move(packet));
+    } else {
+      // Otherwise we just write the basic descriptor for this type of track
+      // (e.g., just uuid, no name).
+      track.Serialize(packet->set_track_descriptor());
+    }
+  }
+
+  static void WriteTrackDescriptor(
+      const SerializedTrackDescriptor& desc,
+      protozero::MessageHandle<protos::pbzero::TracePacket> packet);
+
+ private:
+  void UpdateTrackImpl(
+      Track,
+      std::function<void(protos::pbzero::TrackDescriptor*)> fill_function);
+
+  std::mutex mutex_;
+  std::map<uint64_t /* uuid */, SerializedTrackDescriptor> tracks_;
+
+  static TrackRegistry* instance_;
+};
+
+}  // namespace internal
+}  // namespace perfetto
+
+#endif  // INCLUDE_PERFETTO_TRACING_TRACK_H_
diff --git a/include/perfetto/tracing/track_event.h b/include/perfetto/tracing/track_event.h
index fa4776b..95c2923 100644
--- a/include/perfetto/tracing/track_event.h
+++ b/include/perfetto/tracing/track_event.h
@@ -21,6 +21,7 @@
 #include "perfetto/tracing/internal/track_event_data_source.h"
 #include "perfetto/tracing/internal/track_event_internal.h"
 #include "perfetto/tracing/internal/track_event_macros.h"
+#include "perfetto/tracing/track.h"
 #include "perfetto/tracing/track_event_category_registry.h"
 #include "protos/perfetto/trace/track_event/track_event.pbzero.h"
 
@@ -68,6 +69,14 @@
 //         });
 //       }
 //
+//  Note that track events must be nested consistently, i.e., the following is
+//  not allowed:
+//
+//    TRACE_EVENT_BEGIN("a", "bar", ...);
+//    TRACE_EVENT_BEGIN("b", "foo", ...);
+//    TRACE_EVENT_END("a");  // "foo" must be closed before "bar".
+//    TRACE_EVENT_END("b");
+//
 // ====================
 // Implementation notes
 // ====================
@@ -138,14 +147,17 @@
   PERFETTO_INTERNAL_CATEGORY_STORAGE()        \
   }  // namespace PERFETTO_TRACK_EVENT_NAMESPACE
 
-// Begin a thread-scoped slice under |category| with the title |name|. Both
-// strings must be static constants. The track event is only recorded if
-// |category| is enabled for a tracing session.
+// Begin a slice under |category| with the title |name|. Both strings must be
+// static constants. The track event is only recorded if |category| is enabled
+// for a tracing session.
 //
 // |name| must be a string with static lifetime (i.e., the same
 // address must not be used for a different event name in the future). If you
 // want to use a dynamically allocated name, do this:
 //
+// The slice is thread-scoped (i.e., written to the default track of the current
+// thread) unless overridden with a custom track object (see Track).
+//
 //  TRACE_EVENT("category", nullptr, [&](perfetto::EventContext ctx) {
 //    ctx.event()->set_name(dynamic_name);
 //  });
@@ -155,18 +167,17 @@
       category, name,                          \
       ::perfetto::protos::pbzero::TrackEvent::TYPE_SLICE_BEGIN, ##__VA_ARGS__)
 
-// End a thread-scoped slice under |category|.
+// End a slice under |category|.
 #define TRACE_EVENT_END(category, ...) \
   PERFETTO_INTERNAL_TRACK_EVENT(       \
       category, nullptr,               \
       ::perfetto::protos::pbzero::TrackEvent::TYPE_SLICE_END, ##__VA_ARGS__)
 
-// Begin a thread-scoped slice which gets automatically closed when going out of
-// scope.
+// Begin a slice which gets automatically closed when going out of scope.
 #define TRACE_EVENT(category, name, ...) \
   PERFETTO_INTERNAL_SCOPED_TRACK_EVENT(category, name, ##__VA_ARGS__)
 
-// Emit a thread-scoped slice which has zero duration.
+// Emit a slice which has zero duration.
 // TODO(skyostil): Add support for process-wide and global instant events.
 #define TRACE_EVENT_INSTANT(category, name, ...)                            \
   PERFETTO_INTERNAL_TRACK_EVENT(                                            \
diff --git a/src/base/flat_set_benchmark.cc b/src/base/flat_set_benchmark.cc
index 12e5565..0773fbe 100644
--- a/src/base/flat_set_benchmark.cc
+++ b/src/base/flat_set_benchmark.cc
@@ -18,7 +18,7 @@
 
 #include <benchmark/benchmark.h>
 
-#include "perfetto/ext/base/flat_set.h"
+#include "perfetto/base/flat_set.h"
 
 namespace {
 
diff --git a/src/base/flat_set_unittest.cc b/src/base/flat_set_unittest.cc
index c853e13..406e05c 100644
--- a/src/base/flat_set_unittest.cc
+++ b/src/base/flat_set_unittest.cc
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "perfetto/ext/base/flat_set.h"
+#include "perfetto/base/flat_set.h"
 
 #include <random>
 #include <set>
@@ -38,7 +38,9 @@
     EXPECT_EQ(flat_set.find(42), flat_set.end());
     EXPECT_EQ(flat_set.find(42), flat_set.begin());
 
-    flat_set.insert(1);
+    auto it_and_inserted = flat_set.insert(1);
+    EXPECT_EQ(it_and_inserted.first, flat_set.find(1));
+    EXPECT_TRUE(it_and_inserted.second);
     EXPECT_FALSE(flat_set.empty());
     EXPECT_EQ(flat_set.size(), 1u);
     {
@@ -52,7 +54,9 @@
     EXPECT_NE(flat_set.begin(), flat_set.end());
     EXPECT_EQ(std::distance(flat_set.begin(), flat_set.end()), 1);
 
-    flat_set.insert(1);
+    it_and_inserted = flat_set.insert(1);
+    EXPECT_EQ(it_and_inserted.first, flat_set.find(1));
+    EXPECT_FALSE(it_and_inserted.second);
     EXPECT_EQ(flat_set.size(), 1u);
     EXPECT_TRUE(flat_set.count(1));
     EXPECT_FALSE(flat_set.count(0));
@@ -62,10 +66,10 @@
     EXPECT_FALSE(flat_set.count(1));
     EXPECT_EQ(flat_set.size(), 0u);
 
-    flat_set.insert(7);
-    flat_set.insert(-4);
-    flat_set.insert(11);
-    flat_set.insert(-13);
+    EXPECT_TRUE(flat_set.insert(7).second);
+    EXPECT_TRUE(flat_set.insert(-4).second);
+    EXPECT_TRUE(flat_set.insert(11).second);
+    EXPECT_TRUE(flat_set.insert(-13).second);
     EXPECT_TRUE(flat_set.count(7));
     EXPECT_TRUE(flat_set.count(-4));
     EXPECT_TRUE(flat_set.count(11));
@@ -94,8 +98,11 @@
   for (int i = 0; i < 10000; i++) {
     const int val = int_dist(rng);
     if (i % 3) {
-      flat_set.insert(val);
-      gold_set.insert(val);
+      auto flat_result = flat_set.insert(val);
+      auto gold_result = gold_set.insert(val);
+      EXPECT_EQ(flat_result.first, flat_set.find(val));
+      EXPECT_EQ(gold_result.first, gold_set.find(val));
+      EXPECT_EQ(flat_result.second, gold_result.second);
     } else {
       flat_set.erase(val);
       gold_set.erase(val);
diff --git a/src/base/watchdog_posix.cc b/src/base/watchdog_posix.cc
index 401ecdb..3517afd 100644
--- a/src/base/watchdog_posix.cc
+++ b/src/base/watchdog_posix.cc
@@ -28,8 +28,8 @@
 
 #include "perfetto/base/build_config.h"
 #include "perfetto/base/logging.h"
+#include "perfetto/base/thread_utils.h"
 #include "perfetto/ext/base/scoped_file.h"
-#include "perfetto/ext/base/thread_utils.h"
 
 namespace perfetto {
 namespace base {
diff --git a/src/base/watchdog_unittest.cc b/src/base/watchdog_unittest.cc
index f3bc850..2841a79 100644
--- a/src/base/watchdog_unittest.cc
+++ b/src/base/watchdog_unittest.cc
@@ -17,9 +17,9 @@
 #include "perfetto/ext/base/watchdog.h"
 
 #include "perfetto/base/logging.h"
+#include "perfetto/base/thread_utils.h"
 #include "perfetto/ext/base/paged_memory.h"
 #include "perfetto/ext/base/scoped_file.h"
-#include "perfetto/ext/base/thread_utils.h"
 #include "test/gtest_and_gmock.h"
 
 #include <signal.h>
diff --git a/src/profiling/memory/client.cc b/src/profiling/memory/client.cc
index 8bcb2a0..ed65372 100644
--- a/src/profiling/memory/client.cc
+++ b/src/profiling/memory/client.cc
@@ -35,9 +35,9 @@
 #include <new>
 
 #include "perfetto/base/logging.h"
+#include "perfetto/base/thread_utils.h"
 #include "perfetto/base/time.h"
 #include "perfetto/ext/base/scoped_file.h"
-#include "perfetto/ext/base/thread_utils.h"
 #include "perfetto/ext/base/unix_socket.h"
 #include "perfetto/ext/base/utils.h"
 #include "src/profiling/memory/sampler.h"
diff --git a/src/profiling/memory/client_unittest.cc b/src/profiling/memory/client_unittest.cc
index e2f0500..68b2ee7 100644
--- a/src/profiling/memory/client_unittest.cc
+++ b/src/profiling/memory/client_unittest.cc
@@ -18,7 +18,7 @@
 
 #include <thread>
 
-#include "perfetto/ext/base/thread_utils.h"
+#include "perfetto/base/thread_utils.h"
 #include "perfetto/ext/base/unix_socket.h"
 #include "test/gtest_and_gmock.h"
 
diff --git a/src/traced/probes/filesystem/inode_file_data_source.h b/src/traced/probes/filesystem/inode_file_data_source.h
index ac5275c..8ccab1a 100644
--- a/src/traced/probes/filesystem/inode_file_data_source.h
+++ b/src/traced/probes/filesystem/inode_file_data_source.h
@@ -25,8 +25,8 @@
 #include <string>
 #include <unordered_map>
 
+#include "perfetto/base/flat_set.h"
 #include "perfetto/base/task_runner.h"
-#include "perfetto/ext/base/flat_set.h"
 #include "perfetto/ext/base/weak_ptr.h"
 #include "perfetto/ext/traced/data_source_types.h"
 #include "perfetto/ext/tracing/core/basic_types.h"
diff --git a/src/traced/probes/ftrace/ftrace_metadata.h b/src/traced/probes/ftrace/ftrace_metadata.h
index de9d89a..8294f25 100644
--- a/src/traced/probes/ftrace/ftrace_metadata.h
+++ b/src/traced/probes/ftrace/ftrace_metadata.h
@@ -23,8 +23,8 @@
 
 #include <bitset>
 
+#include "perfetto/base/flat_set.h"
 #include "perfetto/base/logging.h"
-#include "perfetto/ext/base/flat_set.h"
 #include "perfetto/ext/traced/data_source_types.h"
 
 namespace perfetto {
diff --git a/src/traced/probes/ps/process_stats_data_source.h b/src/traced/probes/ps/process_stats_data_source.h
index 04c683e..100ef62 100644
--- a/src/traced/probes/ps/process_stats_data_source.h
+++ b/src/traced/probes/ps/process_stats_data_source.h
@@ -23,7 +23,7 @@
 #include <unordered_map>
 #include <vector>
 
-#include "perfetto/ext/base/flat_set.h"
+#include "perfetto/base/flat_set.h"
 #include "perfetto/ext/base/scoped_file.h"
 #include "perfetto/ext/base/weak_ptr.h"
 #include "perfetto/ext/tracing/core/basic_types.h"
diff --git a/src/tracing/BUILD.gn b/src/tracing/BUILD.gn
index aa7c6f6..6fd71dc 100644
--- a/src/tracing/BUILD.gn
+++ b/src/tracing/BUILD.gn
@@ -250,6 +250,7 @@
     "internal/track_event_internal.cc",
     "platform.cc",
     "tracing.cc",
+    "track.cc",
     "track_event_category_registry.cc",
     "virtual_destructors.cc",
   ]
diff --git a/src/tracing/api_integrationtest.cc b/src/tracing/api_integrationtest.cc
index a9f3fbb..2fea0d0 100644
--- a/src/tracing/api_integrationtest.cc
+++ b/src/tracing/api_integrationtest.cc
@@ -21,6 +21,7 @@
 #include <functional>
 #include <list>
 #include <mutex>
+#include <thread>
 #include <vector>
 
 #include "perfetto/tracing.h"
@@ -50,14 +51,19 @@
 #include "protos/perfetto/trace/trace.gen.h"
 #include "protos/perfetto/trace/trace_packet.gen.h"
 #include "protos/perfetto/trace/trace_packet.pbzero.h"
+#include "protos/perfetto/trace/track_event/chrome_process_descriptor.gen.h"
+#include "protos/perfetto/trace/track_event/chrome_process_descriptor.pbzero.h"
 #include "protos/perfetto/trace/track_event/debug_annotation.gen.h"
 #include "protos/perfetto/trace/track_event/debug_annotation.pbzero.h"
 #include "protos/perfetto/trace/track_event/log_message.gen.h"
 #include "protos/perfetto/trace/track_event/log_message.pbzero.h"
 #include "protos/perfetto/trace/track_event/process_descriptor.gen.h"
+#include "protos/perfetto/trace/track_event/process_descriptor.pbzero.h"
 #include "protos/perfetto/trace/track_event/source_location.gen.h"
 #include "protos/perfetto/trace/track_event/source_location.pbzero.h"
 #include "protos/perfetto/trace/track_event/thread_descriptor.gen.h"
+#include "protos/perfetto/trace/track_event/thread_descriptor.pbzero.h"
+#include "protos/perfetto/trace/track_event/track_descriptor.gen.h"
 #include "protos/perfetto/trace/track_event/track_event.gen.h"
 
 // Trace categories used in the tests.
@@ -345,7 +351,8 @@
     bool incremental_state_was_cleared = false;
     uint32_t sequence_id = 0;
     for (const auto& packet : parsed_trace.packet()) {
-      if (packet.incremental_state_cleared()) {
+      if (packet.sequence_flags() & perfetto::protos::pbzero::TracePacket::
+                                        SEQ_INCREMENTAL_STATE_CLEARED) {
         incremental_state_was_cleared = true;
         categories.clear();
         event_names.clear();
@@ -558,25 +565,21 @@
   bool begin_found = false;
   bool end_found = false;
   bool process_descriptor_found = false;
-  bool thread_descriptor_found = false;
   auto now = perfetto::test::GetTraceTimeNs();
   uint32_t sequence_id = 0;
   int32_t cur_pid = perfetto::test::GetCurrentProcessId();
   for (const auto& packet : trace.packet()) {
-    if (packet.has_process_descriptor()) {
-      EXPECT_FALSE(process_descriptor_found);
-      const auto& pd = packet.process_descriptor();
-      EXPECT_EQ(cur_pid, pd.pid());
-      process_descriptor_found = true;
+    if (packet.has_track_descriptor()) {
+      const auto& desc = packet.track_descriptor();
+      if (desc.has_process()) {
+        EXPECT_FALSE(process_descriptor_found);
+        const auto& pd = desc.process();
+        EXPECT_EQ(cur_pid, pd.pid());
+        process_descriptor_found = true;
+      }
     }
-    if (packet.has_thread_descriptor()) {
-      EXPECT_FALSE(thread_descriptor_found);
-      const auto& td = packet.thread_descriptor();
-      EXPECT_EQ(cur_pid, td.pid());
-      EXPECT_NE(0, td.tid());
-      thread_descriptor_found = true;
-    }
-    if (packet.incremental_state_cleared()) {
+    if (packet.sequence_flags() &
+        perfetto::protos::pbzero::TracePacket::SEQ_INCREMENTAL_STATE_CLEARED) {
       EXPECT_TRUE(packet.has_trace_packet_defaults());
       incremental_state_was_cleared = true;
       categories.clear();
@@ -585,6 +588,10 @@
 
     if (!packet.has_track_event())
       continue;
+    EXPECT_TRUE(
+        packet.sequence_flags() &
+        (perfetto::protos::pbzero::TracePacket::SEQ_INCREMENTAL_STATE_CLEARED |
+         perfetto::protos::pbzero::TracePacket::SEQ_NEEDS_INCREMENTAL_STATE));
     const auto& track_event = packet.track_event();
 
     // Make sure we only see track events on one sequence.
@@ -636,7 +643,6 @@
   }
   EXPECT_TRUE(incremental_state_was_cleared);
   EXPECT_TRUE(process_descriptor_found);
-  EXPECT_TRUE(thread_descriptor_found);
   EXPECT_TRUE(begin_found);
   EXPECT_TRUE(end_found);
 }
@@ -828,6 +834,230 @@
   EXPECT_THAT(trace2, Not(HasSubstr("Session2_Third")));
 }
 
+TEST_F(PerfettoApiTest, TrackEventProcessAndThreadDescriptors) {
+  // Thread and process descriptors can be set before tracing is enabled.
+  perfetto::TrackEvent::SetProcessDescriptor(
+      [](perfetto::protos::pbzero::TrackDescriptor* desc) {
+        desc->set_name("hello.exe");
+        desc->set_chrome_process()->set_process_priority(1);
+      });
+
+  // Erased tracks shouldn't show up anywhere.
+  perfetto::Track erased(1234u);
+  perfetto::TrackEvent::SetTrackDescriptor(
+      erased, [](perfetto::protos::pbzero::TrackDescriptor* desc) {
+        desc->set_name("ErasedTrack");
+      });
+  perfetto::TrackEvent::EraseTrackDescriptor(erased);
+
+  // Setup the trace config.
+  perfetto::TraceConfig cfg;
+  cfg.set_duration_ms(500);
+  cfg.add_buffers()->set_size_kb(1024);
+  auto* ds_cfg = cfg.add_data_sources()->mutable_config();
+  ds_cfg->set_name("track_event");
+
+  // Create a new trace session.
+  auto* tracing_session = NewTrace(cfg);
+  tracing_session->get()->StartBlocking();
+  TRACE_EVENT_INSTANT("test", "MainThreadEvent");
+
+  std::thread thread([&] {
+    perfetto::TrackEvent::SetThreadDescriptor(
+        [](perfetto::protos::pbzero::TrackDescriptor* desc) {
+          desc->set_name("TestThread");
+        });
+    TRACE_EVENT_INSTANT("test", "ThreadEvent");
+  });
+  thread.join();
+
+  // Update the process descriptor while tracing is enabled. It should be
+  // immediately reflected in the trace.
+  perfetto::TrackEvent::SetProcessDescriptor(
+      [](perfetto::protos::pbzero::TrackDescriptor* desc) {
+        desc->set_name("goodbye.exe");
+      });
+  perfetto::TrackEvent::Flush();
+
+  tracing_session->get()->StopBlocking();
+
+  // After tracing ends, setting the descriptor has no immediate effect.
+  perfetto::TrackEvent::SetProcessDescriptor(
+      [](perfetto::protos::pbzero::TrackDescriptor* desc) {
+        desc->set_name("noop.exe");
+      });
+
+  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
+  perfetto::protos::gen::Trace trace;
+  ASSERT_TRUE(trace.ParseFromArray(raw_trace.data(), raw_trace.size()));
+
+  std::vector<perfetto::protos::gen::TrackDescriptor> descs;
+  std::vector<perfetto::protos::gen::TrackDescriptor> thread_descs;
+  constexpr uint32_t kMainThreadSequence = 2;
+  for (const auto& packet : trace.packet()) {
+    if (packet.has_track_descriptor()) {
+      if (packet.trusted_packet_sequence_id() == kMainThreadSequence) {
+        descs.push_back(packet.track_descriptor());
+      } else {
+        thread_descs.push_back(packet.track_descriptor());
+      }
+    }
+  }
+
+  // The main thread records the initial process name as well as the one that's
+  // set during tracing. Additionally it records a thread descriptor for the
+  // main thread.
+
+  EXPECT_EQ(3u, descs.size());
+
+  // Default track for the main thread.
+  EXPECT_EQ(0, descs[0].process().pid());
+  EXPECT_NE(0, descs[0].thread().pid());
+
+  // First process descriptor.
+  EXPECT_NE(0, descs[1].process().pid());
+  EXPECT_EQ("hello.exe", descs[1].name());
+
+  // Second process descriptor.
+  EXPECT_NE(0, descs[2].process().pid());
+  EXPECT_EQ("goodbye.exe", descs[2].name());
+
+  // The child thread records only its own thread descriptor (twice, since it
+  // was mutated).
+  EXPECT_EQ(2u, thread_descs.size());
+  EXPECT_EQ("TestThread", thread_descs[0].name());
+  EXPECT_NE(0, thread_descs[0].thread().pid());
+  EXPECT_NE(0, thread_descs[0].thread().tid());
+  EXPECT_EQ("TestThread", thread_descs[1].name());
+  EXPECT_NE(0, thread_descs[1].thread().pid());
+  EXPECT_NE(0, thread_descs[1].thread().tid());
+}
+
+TEST_F(PerfettoApiTest, TrackEventCustomTrack) {
+  // Setup the trace config.
+  perfetto::TraceConfig cfg;
+  cfg.set_duration_ms(500);
+  cfg.add_buffers()->set_size_kb(1024);
+  auto* ds_cfg = cfg.add_data_sources()->mutable_config();
+  ds_cfg->set_name("track_event");
+  ds_cfg->set_legacy_config("bar");
+
+  // Create a new trace session.
+  auto* tracing_session = NewTrace(cfg);
+  tracing_session->get()->StartBlocking();
+
+  // Declare a custom track and give it a name.
+  uint64_t async_id = 123;
+  perfetto::TrackEvent::SetTrackDescriptor(
+      perfetto::Track(async_id),
+      [](perfetto::protos::pbzero::TrackDescriptor* desc) {
+        desc->set_name("MyCustomTrack");
+      });
+
+  // Start events on one thread and end them on another.
+  TRACE_EVENT_BEGIN("bar", "AsyncEvent", perfetto::Track(async_id), "debug_arg",
+                    123);
+
+  TRACE_EVENT_BEGIN("bar", "SubEvent", perfetto::Track(async_id),
+                    [](perfetto::EventContext) {});
+  const auto main_thread_track =
+      perfetto::Track(async_id, perfetto::ThreadTrack::Current());
+  std::thread thread([&] {
+    TRACE_EVENT_END("bar", perfetto::Track(async_id));
+    TRACE_EVENT_END("bar", perfetto::Track(async_id), "arg1", false, "arg2",
+                    true);
+    const auto thread_track =
+        perfetto::Track(async_id, perfetto::ThreadTrack::Current());
+    // Thread-scoped tracks will have different uuids on different thread even
+    // if the id matches.
+    ASSERT_NE(main_thread_track.uuid, thread_track.uuid);
+  });
+  thread.join();
+
+  perfetto::TrackEvent::Flush();
+  tracing_session->get()->StopBlocking();
+
+  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
+  perfetto::protos::gen::Trace trace;
+  ASSERT_TRUE(trace.ParseFromArray(raw_trace.data(), raw_trace.size()));
+
+  // Check that the track uuids match on the begin and end events.
+  const auto track = perfetto::Track(async_id);
+  constexpr uint32_t kMainThreadSequence = 2;
+  int event_count = 0;
+  bool found_descriptor = false;
+  for (const auto& packet : trace.packet()) {
+    if (packet.has_track_descriptor() &&
+        !packet.track_descriptor().has_process() &&
+        !packet.track_descriptor().has_thread()) {
+      auto td = packet.track_descriptor();
+      EXPECT_EQ("MyCustomTrack", td.name());
+      EXPECT_EQ(track.uuid, td.uuid());
+      EXPECT_EQ(perfetto::ProcessTrack::Current().uuid, td.parent_uuid());
+      found_descriptor = true;
+      continue;
+    }
+
+    if (!packet.has_track_event())
+      continue;
+    auto track_event = packet.track_event();
+    if (track_event.type() ==
+        perfetto::protos::gen::TrackEvent::TYPE_SLICE_BEGIN) {
+      EXPECT_EQ(kMainThreadSequence, packet.trusted_packet_sequence_id());
+      EXPECT_EQ(track.uuid, track_event.track_uuid());
+    } else {
+      EXPECT_NE(kMainThreadSequence, packet.trusted_packet_sequence_id());
+      EXPECT_EQ(track.uuid, track_event.track_uuid());
+    }
+    event_count++;
+  }
+  EXPECT_TRUE(found_descriptor);
+  EXPECT_EQ(4, event_count);
+  perfetto::TrackEvent::EraseTrackDescriptor(track);
+}
+
+TEST_F(PerfettoApiTest, TrackEventAnonymousCustomTrack) {
+  // Setup the trace config.
+  perfetto::TraceConfig cfg;
+  cfg.set_duration_ms(500);
+  cfg.add_buffers()->set_size_kb(1024);
+  auto* ds_cfg = cfg.add_data_sources()->mutable_config();
+  ds_cfg->set_name("track_event");
+  ds_cfg->set_legacy_config("bar");
+
+  // Create a new trace session.
+  auto* tracing_session = NewTrace(cfg);
+  tracing_session->get()->StartBlocking();
+
+  // Emit an async event without giving it an explicit descriptor.
+  uint64_t async_id = 4004;
+  auto track = perfetto::Track(async_id, perfetto::ThreadTrack::Current());
+  TRACE_EVENT_BEGIN("bar", "AsyncEvent", track);
+  std::thread thread([&] { TRACE_EVENT_END("bar", track); });
+  thread.join();
+
+  perfetto::TrackEvent::Flush();
+  tracing_session->get()->StopBlocking();
+
+  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
+  perfetto::protos::gen::Trace trace;
+  ASSERT_TRUE(trace.ParseFromArray(raw_trace.data(), raw_trace.size()));
+
+  // Check that a descriptor for the track was emitted.
+  bool found_descriptor = false;
+  for (const auto& packet : trace.packet()) {
+    if (packet.has_track_descriptor() &&
+        !packet.track_descriptor().has_process() &&
+        !packet.track_descriptor().has_thread()) {
+      auto td = packet.track_descriptor();
+      EXPECT_EQ(track.uuid, td.uuid());
+      EXPECT_EQ(perfetto::ThreadTrack::Current().uuid, td.parent_uuid());
+      found_descriptor = true;
+    }
+  }
+  EXPECT_TRUE(found_descriptor);
+}
+
 TEST_F(PerfettoApiTest, TrackEventTypedArgs) {
   // Setup the trace config.
   perfetto::TraceConfig cfg;
diff --git a/src/tracing/internal/track_event_internal.cc b/src/tracing/internal/track_event_internal.cc
index 71afc7b..7bd766c 100644
--- a/src/tracing/internal/track_event_internal.cc
+++ b/src/tracing/internal/track_event_internal.cc
@@ -16,9 +16,9 @@
 
 #include "perfetto/tracing/internal/track_event_internal.h"
 
+#include "perfetto/base/proc_utils.h"
+#include "perfetto/base/thread_utils.h"
 #include "perfetto/base/time.h"
-#include "perfetto/ext/base/proc_utils.h"
-#include "perfetto/ext/base/thread_utils.h"
 #include "perfetto/tracing/core/data_source_config.h"
 #include "perfetto/tracing/track_event.h"
 #include "perfetto/tracing/track_event_category_registry.h"
@@ -28,8 +28,7 @@
 #include "protos/perfetto/trace/interned_data/interned_data.pbzero.h"
 #include "protos/perfetto/trace/trace_packet_defaults.pbzero.h"
 #include "protos/perfetto/trace/track_event/debug_annotation.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"
 
 namespace perfetto {
 namespace internal {
@@ -95,49 +94,6 @@
 #endif
 }
 
-uint64_t GetTimeNs() {
-  if (GetClockType() == protos::pbzero::ClockSnapshot::Clock::BOOTTIME)
-    return static_cast<uint64_t>(perfetto::base::GetBootTimeNs().count());
-  PERFETTO_DCHECK(GetClockType() ==
-                  protos::pbzero::ClockSnapshot::Clock::MONOTONIC);
-  return static_cast<uint64_t>(perfetto::base::GetWallTimeNs().count());
-}
-
-protozero::MessageHandle<protos::pbzero::TracePacket> NewTracePacket(
-    TraceWriterBase* trace_writer,
-    uint64_t timestamp) {
-  auto packet = trace_writer->NewTracePacket();
-  packet->set_timestamp(timestamp);
-  // TODO(skyostil): Stop emitting this for every event once the trace processor
-  // understands trace packet defaults.
-  if (GetClockType() != protos::pbzero::ClockSnapshot::Clock::BOOTTIME)
-    packet->set_timestamp_clock_id(GetClockType());
-  return packet;
-}
-
-// static
-void WriteSequenceDescriptors(TraceWriterBase* trace_writer,
-                              uint64_t timestamp) {
-  if (perfetto::base::GetThreadId() == g_main_thread) {
-    auto packet = NewTracePacket(trace_writer, timestamp);
-    packet->set_incremental_state_cleared(true);
-    auto defaults = packet->set_trace_packet_defaults();
-    defaults->set_timestamp_clock_id(GetClockType());
-    auto pd = packet->set_process_descriptor();
-    pd->set_pid(static_cast<int32_t>(base::GetProcessId()));
-    // TODO(skyostil): Record command line.
-  }
-  {
-    auto packet = NewTracePacket(trace_writer, timestamp);
-    packet->set_incremental_state_cleared(true);
-    auto defaults = packet->set_trace_packet_defaults();
-    defaults->set_timestamp_clock_id(GetClockType());
-    auto td = packet->set_thread_descriptor();
-    td->set_pid(static_cast<int32_t>(base::GetProcessId()));
-    td->set_tid(static_cast<int32_t>(perfetto::base::GetThreadId()));
-  }
-}
-
 }  // namespace
 
 // static
@@ -177,6 +133,57 @@
 }
 
 // static
+uint64_t TrackEventInternal::GetTimeNs() {
+  if (GetClockType() == protos::pbzero::ClockSnapshot::Clock::BOOTTIME)
+    return static_cast<uint64_t>(perfetto::base::GetBootTimeNs().count());
+  PERFETTO_DCHECK(GetClockType() ==
+                  protos::pbzero::ClockSnapshot::Clock::MONOTONIC);
+  return static_cast<uint64_t>(perfetto::base::GetWallTimeNs().count());
+}
+
+// static
+void TrackEventInternal::ResetIncrementalState(TraceWriterBase* trace_writer,
+                                               uint64_t timestamp) {
+  auto default_track = ThreadTrack::Current();
+  {
+    // Mark any incremental state before this point invalid. Also set up
+    // defaults so that we don't need to repeat constant data for each packet.
+    auto packet = NewTracePacket(
+        trace_writer, timestamp,
+        protos::pbzero::TracePacket::SEQ_INCREMENTAL_STATE_CLEARED);
+    auto defaults = packet->set_trace_packet_defaults();
+    defaults->set_timestamp_clock_id(GetClockType());
+
+    // Establish the default track for this event sequence.
+    auto track_defaults = defaults->set_track_event_defaults();
+    track_defaults->set_track_uuid(default_track.uuid);
+  }
+
+  // Every thread should write a descriptor for its default track, because most
+  // trace points won't explicitly reference it.
+  WriteTrackDescriptor(default_track, trace_writer);
+
+  // Additionally the main thread should dump the process descriptor.
+  if (perfetto::base::GetThreadId() == g_main_thread)
+    WriteTrackDescriptor(ProcessTrack::Current(), trace_writer);
+}
+
+// static
+protozero::MessageHandle<protos::pbzero::TracePacket>
+TrackEventInternal::NewTracePacket(TraceWriterBase* trace_writer,
+                                   uint64_t timestamp,
+                                   uint32_t seq_flags) {
+  auto packet = trace_writer->NewTracePacket();
+  packet->set_timestamp(timestamp);
+  // TODO(skyostil): Stop emitting this for every event once the trace processor
+  // understands trace packet defaults.
+  if (GetClockType() != protos::pbzero::ClockSnapshot::Clock::BOOTTIME)
+    packet->set_timestamp_clock_id(GetClockType());
+  packet->set_sequence_flags(seq_flags);
+  return packet;
+}
+
+// static
 EventContext TrackEventInternal::WriteEvent(
     TraceWriterBase* trace_writer,
     TrackEventIncrementalState* incr_state,
@@ -189,7 +196,7 @@
 
   if (incr_state->was_cleared) {
     incr_state->was_cleared = false;
-    WriteSequenceDescriptors(trace_writer, timestamp);
+    ResetIncrementalState(trace_writer, timestamp);
   }
   auto packet = NewTracePacket(trace_writer, timestamp);
 
diff --git a/src/tracing/test/api_test_support.cc b/src/tracing/test/api_test_support.cc
index 24d8d67..1667ffc 100644
--- a/src/tracing/test/api_test_support.cc
+++ b/src/tracing/test/api_test_support.cc
@@ -16,8 +16,8 @@
 
 #include "src/tracing/test/api_test_support.h"
 
+#include "perfetto/base/proc_utils.h"
 #include "perfetto/base/time.h"
-#include "perfetto/ext/base/proc_utils.h"
 
 namespace perfetto {
 namespace test {
diff --git a/src/tracing/test/tracing_module.cc b/src/tracing/test/tracing_module.cc
index 09b6e5f..7c10d4e 100644
--- a/src/tracing/test/tracing_module.cc
+++ b/src/tracing/test/tracing_module.cc
@@ -82,4 +82,11 @@
   puts("Hello");
 }
 
+void FunctionWithOneTrackEventWithCustomTrack() {
+  TRACE_EVENT_BEGIN("cat1", "EventWithTrack", perfetto::Track(8086));
+  // Simulates the non-tracing work of this function, which should take priority
+  // over the above trace event in terms of instruction scheduling.
+  puts("Hello");
+}
+
 }  // namespace tracing_module
diff --git a/src/tracing/test/tracing_module.h b/src/tracing/test/tracing_module.h
index e0fd1b3..c005a6f 100644
--- a/src/tracing/test/tracing_module.h
+++ b/src/tracing/test/tracing_module.h
@@ -37,6 +37,7 @@
 void FunctionWithOneTrackEventWithTypedArgument();
 void FunctionWithOneScopedTrackEvent();
 void FunctionWithOneTrackEventWithDebugAnnotations();
+void FunctionWithOneTrackEventWithCustomTrack();
 
 }  // namespace tracing_module
 
diff --git a/src/tracing/tracing.cc b/src/tracing/tracing.cc
index fc6a696..54d5722 100644
--- a/src/tracing/tracing.cc
+++ b/src/tracing/tracing.cc
@@ -15,6 +15,7 @@
  */
 
 #include "perfetto/tracing/tracing.h"
+#include "perfetto/tracing/internal/track_event_internal.h"
 #include "src/tracing/internal/tracing_muxer_impl.h"
 
 #include <condition_variable>
@@ -27,6 +28,7 @@
   // Make sure the headers and implementation files agree on the build config.
   PERFETTO_CHECK(args.dcheck_is_on_ == PERFETTO_DCHECK_IS_ON());
   internal::TracingMuxerImpl::InitializeInstance(args);
+  internal::TrackRegistry::InitializeInstance();
 }
 
 //  static
diff --git a/src/tracing/track.cc b/src/tracing/track.cc
new file mode 100644
index 0000000..91045c4
--- /dev/null
+++ b/src/tracing/track.cc
@@ -0,0 +1,94 @@
+/*
+ * 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 "perfetto/tracing/track.h"
+
+#include "perfetto/ext/base/uuid.h"
+#include "perfetto/tracing/internal/track_event_data_source.h"
+#include "protos/perfetto/trace/track_event/process_descriptor.pbzero.h"
+#include "protos/perfetto/trace/track_event/thread_descriptor.pbzero.h"
+
+namespace perfetto {
+
+// static
+uint64_t Track::process_uuid;
+
+void Track::Serialize(protos::pbzero::TrackDescriptor* desc) const {
+  desc->set_uuid(uuid);
+  if (parent_uuid)
+    desc->set_parent_uuid(parent_uuid);
+}
+
+void ProcessTrack::Serialize(protos::pbzero::TrackDescriptor* desc) const {
+  Track::Serialize(desc);
+  auto pd = desc->set_process();
+  pd->set_pid(pid);
+  // TODO(skyostil): Record command line.
+}
+
+void ThreadTrack::Serialize(protos::pbzero::TrackDescriptor* desc) const {
+  Track::Serialize(desc);
+  auto td = desc->set_thread();
+  td->set_pid(pid);
+  td->set_tid(tid);
+  // TODO(skyostil): Record thread name.
+}
+
+namespace internal {
+
+// static
+TrackRegistry* TrackRegistry::instance_;
+
+TrackRegistry::TrackRegistry() = default;
+TrackRegistry::~TrackRegistry() = default;
+
+// static
+void TrackRegistry::InitializeInstance() {
+  PERFETTO_DCHECK(!instance_);
+  instance_ = new TrackRegistry();
+  Track::process_uuid = static_cast<uint64_t>(base::Uuidv4().lsb());
+}
+
+void TrackRegistry::UpdateTrackImpl(
+    Track track,
+    std::function<void(protos::pbzero::TrackDescriptor*)> fill_function) {
+  constexpr size_t kInitialSliceSize = 32;
+  constexpr size_t kMaximumSliceSize = 4096;
+  protozero::HeapBuffered<protos::pbzero::TrackDescriptor> new_descriptor(
+      kInitialSliceSize, kMaximumSliceSize);
+  fill_function(new_descriptor.get());
+  auto serialized_desc = new_descriptor.SerializeAsString();
+  {
+    std::lock_guard<std::mutex> lock(mutex_);
+    tracks_[track.uuid] = std::move(serialized_desc);
+  }
+}
+
+void TrackRegistry::EraseTrack(Track track) {
+  std::lock_guard<std::mutex> lock(mutex_);
+  tracks_.erase(track.uuid);
+}
+
+// static
+void TrackRegistry::WriteTrackDescriptor(
+    const SerializedTrackDescriptor& desc,
+    protozero::MessageHandle<protos::pbzero::TracePacket> packet) {
+  packet->AppendString(
+      perfetto::protos::pbzero::TracePacket::kTrackDescriptorFieldNumber, desc);
+}
+
+}  // namespace internal
+}  // namespace perfetto