TrackEvent: Add support for dynamically enabled tracing categories

This patch introduces support for annotating track events with named
tracing categories. Each category can be individually enabled or
disabled using a trace config.

The set of categories is defined at compile time using a macro:

  PERFETTO_DEFINE_CATEGORIES(
      PERFETTO_CATEGORY(cat1),
      PERFETTO_CATEGORY(cat2),
      PERFETTO_CATEGORY(cat3));

We also introduce a set of macros for efficiently emitting track events
with category annotation:

  TRACE_EVENT_BEGIN("cat1", "EventName");
  TRACE_EVENT_END();

The trace point implementation replaces the per-data source instance
enable bitmap with a per-category bitmap in order to avoid doing any
extra work for categories that aren't enabled for tracing.

Bug: 132678367
Change-Id: I84cdb13fc2608a23f311f49a3402640c92199909
diff --git a/Android.bp b/Android.bp
index 3b14ab4..bb3f96b 100644
--- a/Android.bp
+++ b/Android.bp
@@ -4974,9 +4974,10 @@
     "src/tracing/internal/in_process_tracing_backend.cc",
     "src/tracing/internal/system_tracing_backend.cc",
     "src/tracing/internal/tracing_muxer_impl.cc",
+    "src/tracing/internal/track_event_internal.cc",
     "src/tracing/platform.cc",
     "src/tracing/tracing.cc",
-    "src/tracing/track_event.cc",
+    "src/tracing/track_event_category_registry.cc",
     "src/tracing/virtual_destructors.cc",
   ],
 }
@@ -4986,6 +4987,8 @@
   name: "perfetto_src_tracing_client_api_integrationtests",
   srcs: [
     "src/tracing/api_integrationtest.cc",
+    "src/tracing/test/tracing_module.cc",
+    "src/tracing/test/tracing_module2.cc",
   ],
 }
 
diff --git a/BUILD b/BUILD
index 587d616..f1f8f15 100644
--- a/BUILD
+++ b/BUILD
@@ -400,12 +400,15 @@
         "include/perfetto/tracing/internal/tracing_muxer.h",
         "include/perfetto/tracing/internal/tracing_tls.h",
         "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/locked_handle.h",
         "include/perfetto/tracing/platform.h",
         "include/perfetto/tracing/trace_writer_base.h",
         "include/perfetto/tracing/tracing.h",
         "include/perfetto/tracing/tracing_backend.h",
         "include/perfetto/tracing/track_event.h",
+        "include/perfetto/tracing/track_event_category_registry.h",
     ],
 )
 
@@ -940,9 +943,10 @@
         "src/tracing/internal/system_tracing_backend.h",
         "src/tracing/internal/tracing_muxer_impl.cc",
         "src/tracing/internal/tracing_muxer_impl.h",
+        "src/tracing/internal/track_event_internal.cc",
         "src/tracing/platform.cc",
         "src/tracing/tracing.cc",
-        "src/tracing/track_event.cc",
+        "src/tracing/track_event_category_registry.cc",
         "src/tracing/virtual_destructors.cc",
     ],
 )
diff --git a/include/perfetto/base/compiler.h b/include/perfetto/base/compiler.h
index a4cb6ec..fa44b6b 100644
--- a/include/perfetto/base/compiler.h
+++ b/include/perfetto/base/compiler.h
@@ -30,10 +30,12 @@
 
 #if defined(__clang__)
 #define PERFETTO_ALWAYS_INLINE __attribute__((__always_inline__))
+#define PERFETTO_NO_INLINE __attribute__((__noinline__))
 #else
 // GCC is too pedantic and often fails with the error:
 // "always_inline function might not be inlinable"
 #define PERFETTO_ALWAYS_INLINE
+#define PERFETTO_NO_INLINE
 #endif
 
 // TODO(lalitm): is_trivially_constructible is currently not available
diff --git a/include/perfetto/tracing/BUILD.gn b/include/perfetto/tracing/BUILD.gn
index 8be1aa8..289abf3 100644
--- a/include/perfetto/tracing/BUILD.gn
+++ b/include/perfetto/tracing/BUILD.gn
@@ -29,11 +29,14 @@
     "internal/tracing_muxer.h",
     "internal/tracing_tls.h",
     "internal/track_event_data_source.h",
+    "internal/track_event_internal.h",
+    "internal/track_event_macros.h",
     "locked_handle.h",
     "platform.h",
     "trace_writer_base.h",
     "tracing.h",
     "tracing_backend.h",
     "track_event.h",
+    "track_event_category_registry.h",
   ]
 }
diff --git a/include/perfetto/tracing/data_source.h b/include/perfetto/tracing/data_source.h
index c6fa774..312ba34 100644
--- a/include/perfetto/tracing/data_source.h
+++ b/include/perfetto/tracing/data_source.h
@@ -44,6 +44,9 @@
 #include "protos/perfetto/trace/trace_packet.pbzero.h"
 
 namespace perfetto {
+namespace internal {
+class TracingMuxerImpl;
+}  // namespace internal
 
 class DataSourceConfig;
 
@@ -65,10 +68,17 @@
     // This is valid only within the scope of the OnSetup() call and must not
     // be retained.
     const DataSourceConfig* config = nullptr;
+
+    // The index of this data source instance (0..kMaxDataSourceInstances - 1).
+    uint32_t internal_instance_index = 0;
   };
   virtual void OnSetup(const SetupArgs&);
 
-  class StartArgs {};
+  class StartArgs {
+   public:
+    // The index of this data source instance (0..kMaxDataSourceInstances - 1).
+    uint32_t internal_instance_index = 0;
+  };
   virtual void OnStart(const StartArgs&);
 
   class StopArgs {
@@ -94,20 +104,49 @@
     // other words, it is fine to accidentally hold onto this closure for too
     // long but, if that happens, some tracing data will be lost.
     virtual std::function<void()> HandleStopAsynchronously() const = 0;
+
+    // The index of this data source instance (0..kMaxDataSourceInstances - 1).
+    uint32_t internal_instance_index = 0;
   };
   virtual void OnStop(const StopArgs&);
 };
 
+struct DefaultDataSourceTraits {
+  // |IncrementalStateType| can optionally be used store custom per-sequence
+  // incremental data (e.g., interning tables). It should have a Clear() method
+  // for when incremental state needs to be cleared. See
+  // TraceContext::GetIncrementalState().
+  using IncrementalStateType = void;
+
+  // Allows overriding what type of thread-local state configuration the data
+  // source uses. By default every data source gets independent thread-local
+  // state, which means every instance uses separate trace writers and
+  // incremental state even on the same thread. Some data sources (most notably
+  // the track event data source) want to share trace writers and incremental
+  // state on the same thread.
+  static internal::DataSourceThreadLocalState* GetDataSourceTLS(
+      internal::DataSourceStaticState* static_state,
+      internal::TracingTLS* root_tls) {
+    auto* ds_tls = &root_tls->data_sources_tls[static_state->index];
+    // The per-type TLS is either zero-initialized or must have been initialized
+    // for this specific data source type.
+    assert(!ds_tls->static_state ||
+           ds_tls->static_state->index == static_state->index);
+    return ds_tls;
+  }
+};
+
 // Templated base class meant to be derived by embedders to create a custom data
 // source. DataSourceType must be the type of the derived class itself, e.g.:
 // class MyDataSource : public DataSourceBase<MyDataSource> {...}.
 //
-// |IncrementalStateType| can optionally be used store custom per-sequence
-// incremental data (e.g., interning tables). It should have a Clear() method
-// for when incremental state needs to be cleared. See
-// TraceContext::GetIncrementalState().
-template <typename DataSourceType, typename IncrementalStateType = void>
+// |DataSourceTraits| allows customizing the behavior of the data source. See
+// |DefaultDataSourceTraits|.
+template <typename DataSourceType,
+          typename DataSourceTraits = DefaultDataSourceTraits>
 class DataSource : public DataSourceBase {
+  struct DefaultTracePointTraits;
+
  public:
   // The BufferExhaustedPolicy to use for TraceWriters of this DataSource.
   // Override this in your DataSource class to change the default, which is to
@@ -162,11 +201,13 @@
           static_cast<DataSourceType*>(internal_state->data_source.get()));
     }
 
-    IncrementalStateType* GetIncrementalState() {
-      return reinterpret_cast<IncrementalStateType*>(
+    typename DataSourceTraits::IncrementalStateType* GetIncrementalState() {
+      return reinterpret_cast<typename DataSourceTraits::IncrementalStateType*>(
           tls_inst_->incremental_state.get());
     }
 
+    uint32_t internal_instance_index() const { return instance_index_; }
+
    private:
     friend class DataSource;
     TraceContext(internal::DataSourceInstanceThreadLocalState* tls_inst,
@@ -189,33 +230,55 @@
   // twice within the same trace config).
   template <typename Lambda>
   static void Trace(Lambda tracing_fn) {
-    constexpr auto kMaxDataSourceInstances = internal::kMaxDataSourceInstances;
+    CallIfEnabled<DefaultTracePointTraits>([&tracing_fn](uint32_t instances) {
+      TraceWithInstances<DefaultTracePointTraits>(instances,
+                                                  std::move(tracing_fn));
+    });
+  }
 
+  // An efficient trace point guard for checking if this data source is active.
+  // |callback| is a function which will only be called if there are active
+  // instances. It is given an instance state parameter, which should be passed
+  // to TraceWithInstances() to actually record trace data.
+  template <typename Traits = DefaultTracePointTraits, typename Callback>
+  static void CallIfEnabled(Callback callback) PERFETTO_ALWAYS_INLINE {
     // |instances| is a per-class bitmap that tells:
     // 1. If the data source is enabled at all.
-    // 2. The index of the slot within |valid_instances| that holds the instance
+    // 2. The index of the slot within |static_state_| that holds the instance
     //    state. In turn this allows to map the data source to the tracing
     //    session and buffers.
     // memory_order_relaxed is okay because:
     // - |instances| is re-read with an acquire barrier below if this succeeds.
     // - The code between this point and the acquire-load is based on static
     //    storage which has indefinite lifetime.
-    auto instances =
-        static_state_.valid_instances.load(std::memory_order_relaxed);
+    uint32_t instances =
+        Traits::GetActiveInstances()->load(std::memory_order_relaxed);
 
     // This is the tracing fast-path. Bail out immediately if tracing is not
     // enabled (or tracing is enabled but not for this data source).
     if (PERFETTO_LIKELY(!instances))
       return;
+    callback(instances);
+  }
 
-    // TODO(primiano): all the stuff below should be outlined. Or at least
-    // we should have some compile-time traits like kOptimizeBinarySize /
-    // kOptimizeTracingLatency.
+  // The "lower half" of a trace point which actually performs tracing after
+  // this data source has been determined to be active.
+  // |instances| must be the instance state value retrieved through
+  // CallIfEnabled().
+  // |tracing_fn| will be called to record trace data as in Trace().
+  //
+  // TODO(primiano): all the stuff below should be outlined from the trace
+  // point. Or at least we should have some compile-time traits like
+  // kOptimizeBinarySize / kOptimizeTracingLatency.
+  template <typename Traits = DefaultTracePointTraits, typename Lambda>
+  static void TraceWithInstances(uint32_t instances, Lambda tracing_fn) {
+    PERFETTO_DCHECK(instances);
+    constexpr auto kMaxDataSourceInstances = internal::kMaxDataSourceInstances;
 
     // See tracing_muxer.h for the structure of the TLS.
     auto* tracing_impl = internal::TracingMuxer::Get();
     if (PERFETTO_UNLIKELY(!tls_state_))
-      tls_state_ = tracing_impl->GetOrCreateDataSourceTLS(&static_state_);
+      tls_state_ = GetOrCreateDataSourceTLS(&static_state_);
 
     // TracingTLS::generation is a global monotonic counter that is incremented
     // every time a tracing session is stopped. We use that as a signal to force
@@ -276,7 +339,7 @@
         // by TracingMuxerImpl::SetupDataSource(), to ensure that the backend_id
         // and buffer_id are consistent.
         instances =
-            static_state_.valid_instances.load(std::memory_order_acquire);
+            Traits::GetActiveInstances()->load(std::memory_order_acquire);
         instance_state = static_state_.TryGetCached(instances, i);
         if (!instance_state || !instance_state->trace_lambda_enabled)
           return;
@@ -284,8 +347,10 @@
         tls_inst.buffer_id = instance_state->buffer_id;
         tls_inst.trace_writer = tracing_impl->CreateTraceWriter(
             instance_state, DataSourceType::kBufferExhaustedPolicy);
-        CreateIncrementalState(&tls_inst,
-                               static_cast<IncrementalStateType*>(nullptr));
+        CreateIncrementalState(
+            &tls_inst,
+            static_cast<typename DataSourceTraits::IncrementalStateType*>(
+                nullptr));
 
         // Even in the case of out-of-IDs, SharedMemoryArbiterImpl returns a
         // NullTraceWriter. The returned pointer should never be null.
@@ -320,6 +385,20 @@
   }
 
  private:
+  // Traits for customizing the behavior of a specific trace point.
+  struct DefaultTracePointTraits {
+    // By default, every call to DataSource::Trace() will record trace events
+    // for every active instance of that data source. A single trace point can,
+    // however, use a custom set of enable flags for more fine grained control
+    // of when that trace point is active.
+    //
+    // DANGER: when doing this, the data source must use the appropriate memory
+    // fences when changing the state of the bitmap.
+    static constexpr std::atomic<uint32_t>* GetActiveInstances() {
+      return &static_state_.valid_instances;
+    }
+  };
+
   // Create the user provided incremental state in the given thread-local
   // storage. Note: The second parameter here is used to specialize the case
   // where there is no incremental state type.
@@ -337,6 +416,22 @@
       internal::DataSourceInstanceThreadLocalState*,
       const void*) {}
 
+  // Note that the returned object is one per-thread per-data-source-type, NOT
+  // per data-source *instance*.
+  static internal::DataSourceThreadLocalState* GetOrCreateDataSourceTLS(
+      internal::DataSourceStaticState* static_state) {
+    auto* tracing_impl = internal::TracingMuxer::Get();
+    internal::TracingTLS* root_tls = tracing_impl->GetOrCreateTracingTLS();
+    internal::DataSourceThreadLocalState* ds_tls =
+        DataSourceTraits::GetDataSourceTLS(static_state, root_tls);
+    // We keep re-initializing as the initialization is idempotent and not worth
+    // the code for extra checks.
+    ds_tls->static_state = static_state;
+    assert(!ds_tls->root_tls || ds_tls->root_tls == root_tls);
+    ds_tls->root_tls = root_tls;
+    return ds_tls;
+  }
+
   // Static state. Accessed by the static Trace() method fastpaths.
   static internal::DataSourceStaticState static_state_;
 
@@ -350,17 +445,16 @@
   static thread_local internal::DataSourceThreadLocalState* tls_state_;
 };
 
+template <typename T, typename D>
+internal::DataSourceStaticState DataSource<T, D>::static_state_;
+template <typename T, typename D>
+thread_local internal::DataSourceThreadLocalState* DataSource<T, D>::tls_state_;
+
 }  // namespace perfetto
 
-// If a data source is used across translation units, this declaration must be
-// placed into the header file defining the data source.
-#define PERFETTO_DECLARE_DATA_SOURCE_STATIC_MEMBERS(...)       \
-  template <>                                                  \
-  perfetto::internal::DataSourceStaticState                    \
-      perfetto::DataSource<__VA_ARGS__>::static_state_;        \
-  template <>                                                  \
-  thread_local perfetto::internal::DataSourceThreadLocalState* \
-      perfetto::DataSource<__VA_ARGS__>::tls_state_
+// Not needed -- only here for backwards compatibility.
+// TODO(skyostil): Remove this macro.
+#define PERFETTO_DECLARE_DATA_SOURCE_STATIC_MEMBERS(...)
 
 // The API client must use this in a translation unit. This is because it needs
 // to instantiate the static storage for the datasource to allow the fastpath
diff --git a/include/perfetto/tracing/internal/data_source_internal.h b/include/perfetto/tracing/internal/data_source_internal.h
index 6a373f7..8e06ee4 100644
--- a/include/perfetto/tracing/internal/data_source_internal.h
+++ b/include/perfetto/tracing/internal/data_source_internal.h
@@ -75,6 +75,11 @@
   // Only the tuple (backend_id, data_source_instance_id) is globally unique.
   uint64_t data_source_instance_id = 0;
 
+  // A hash of the trace config used by this instance. This is used to
+  // de-duplicate instances for data sources with identical names (e.g., track
+  // event).
+  uint64_t config_hash = 0;
+
   // This lock is not held to implement Trace() and it's used only if the trace
   // code wants to access its own data source state.
   // This is to prevent that accessing the data source on an arbitrary embedder
@@ -93,8 +98,8 @@
 
 // Per-DataSource-type global state.
 struct DataSourceStaticState {
-  uint32_t index =
-      kMaxDataSources;  // Unique ID, assigned at registration time.
+  // Unique index of the data source, assigned at registration time.
+  uint32_t index = kMaxDataSources;
 
   // A bitmap that tells about the validity of each |instances| entry. When the
   // i-th bit of the bitmap it's set, instances[i] is valid.
diff --git a/include/perfetto/tracing/internal/tracing_muxer.h b/include/perfetto/tracing/internal/tracing_muxer.h
index 6cc7187..4556ed3 100644
--- a/include/perfetto/tracing/internal/tracing_muxer.h
+++ b/include/perfetto/tracing/internal/tracing_muxer.h
@@ -57,23 +57,6 @@
     return static_cast<TracingTLS*>(platform_->GetOrCreateThreadLocalObject());
   }
 
-  // Note that the returned object is one per-thread per-data-source-type, NOT
-  // per data-soruce *instance*.
-  DataSourceThreadLocalState* GetOrCreateDataSourceTLS(
-      DataSourceStaticState* static_state) {
-    TracingTLS* root_tls = GetOrCreateTracingTLS();
-    auto* ds_tls = &root_tls->data_sources_tls[static_state->index];
-
-    // The per-type TLS is either zero-initialized or must have been initialized
-    // for this specific data source type. We keep re-initializing as the
-    // initialization is idempotent and not worth the code for extra checks.
-    assert(!ds_tls->static_state || ds_tls->static_state == static_state);
-    ds_tls->static_state = static_state;
-    assert(!ds_tls->root_tls || ds_tls->root_tls == root_tls);
-    ds_tls->root_tls = root_tls;
-    return ds_tls;
-  }
-
   // This method can fail and return false if trying to register more than
   // kMaxDataSources types.
   using DataSourceFactory = std::function<std::unique_ptr<DataSourceBase>()>;
diff --git a/include/perfetto/tracing/internal/tracing_tls.h b/include/perfetto/tracing/internal/tracing_tls.h
index 8c39dfa..67f0637 100644
--- a/include/perfetto/tracing/internal/tracing_tls.h
+++ b/include/perfetto/tracing/internal/tracing_tls.h
@@ -74,7 +74,14 @@
   // thread-local TraceWriter(s) is issued.
   uint32_t generation = 0;
 
+  // By default all data source instances have independent thread-local state
+  // (see above).
   std::array<DataSourceThreadLocalState, kMaxDataSources> data_sources_tls{};
+
+  // Track event data sources, however, share the same thread-local state in
+  // order to be able to share trace writers and interning state across all
+  // track event categories.
+  DataSourceThreadLocalState track_event_tls{};
 };
 
 }  // namespace internal
diff --git a/include/perfetto/tracing/internal/track_event_data_source.h b/include/perfetto/tracing/internal/track_event_data_source.h
index a2a8199..742e438 100644
--- a/include/perfetto/tracing/internal/track_event_data_source.h
+++ b/include/perfetto/tracing/internal/track_event_data_source.h
@@ -17,61 +17,105 @@
 #ifndef INCLUDE_PERFETTO_TRACING_INTERNAL_TRACK_EVENT_DATA_SOURCE_H_
 #define INCLUDE_PERFETTO_TRACING_INTERNAL_TRACK_EVENT_DATA_SOURCE_H_
 
+#include "perfetto/base/compiler.h"
+#include "perfetto/protozero/message_handle.h"
 #include "perfetto/tracing/data_source.h"
+#include "perfetto/tracing/internal/track_event_internal.h"
+#include "perfetto/tracing/track_event_category_registry.h"
 #include "protos/perfetto/trace/track_event/track_event.pbzero.h"
 
 #include <unordered_map>
 
 namespace perfetto {
-class TrackEvent;
-
 namespace internal {
 
-struct TrackEventIncrementalState {
-  bool was_cleared = true;
+struct TrackEventDataSourceTraits : public perfetto::DefaultDataSourceTraits {
+  using IncrementalStateType = TrackEventIncrementalState;
 
-  // Interned data.
-  // TODO(skyostil): Replace this with something more clever that supports
-  // dynamic strings too.
-  std::unordered_map<const char*, uint64_t> event_names;
-  std::unordered_map<const char*, uint64_t> categories;
+  // Use a one shared TLS slot so that all track event data sources write into
+  // the same sequence and share interning dictionaries.
+  static DataSourceThreadLocalState* GetDataSourceTLS(DataSourceStaticState*,
+                                                      TracingTLS* root_tls) {
+    return &root_tls->track_event_tls;
+  }
 };
 
+// A generic track event data source which is instantiated once per track event
+// category namespace.
+template <typename DataSourceType, const TrackEventCategoryRegistry* Registry>
 class TrackEventDataSource
-    : public DataSource<TrackEventDataSource, TrackEventIncrementalState> {
+    : public DataSource<DataSourceType, TrackEventDataSourceTraits> {
+  using Base = DataSource<DataSourceType, TrackEventDataSourceTraits>;
+
  public:
-  void OnSetup(const SetupArgs&) override;
-  void OnStart(const StartArgs&) override;
-  void OnStop(const StopArgs&) override;
-
- private:
-  friend class perfetto::TrackEvent;
-
-  static void WriteEvent(const char* category,
-                         const char* name,
-                         perfetto::protos::pbzero::TrackEvent::Type type) {
-    Trace([category, name, type](TraceContext ctx) {
-      WriteEventImpl(std::move(ctx), category, name, type);
-    });
+  // DataSource implementation.
+  void OnSetup(const DataSourceBase::SetupArgs& args) override {
+    TrackEventInternal::EnableTracing(*Registry, *args.config,
+                                      args.internal_instance_index);
   }
 
-  // Outlined to reduce binary size.
-  static void WriteEventImpl(TraceContext ctx,
-                             const char* category,
-                             const char* name,
-                             perfetto::protos::pbzero::TrackEvent::Type type);
+  void OnStart(const DataSourceBase::StartArgs&) override {}
 
-  static void WriteSequenceDescriptors(
-      internal::TrackEventDataSource::TraceContext*,
-      uint64_t timestamp);
+  void OnStop(const DataSourceBase::StopArgs& args) override {
+    TrackEventInternal::DisableTracing(*Registry, args.internal_instance_index);
+  }
+
+  static void Flush() {
+    Base::template Trace([](typename Base::TraceContext ctx) { ctx.Flush(); });
+  }
+
+  // This is the inlined entrypoint for all track event trace points. It tries
+  // to be as lightweight as possible in terms of instructions and aims to
+  // compile down to an unlikely conditional jump to the actual trace writing
+  // function.
+  template <size_t CategoryIndex, typename Callback>
+  static void CallIfCategoryEnabled(Callback callback) PERFETTO_ALWAYS_INLINE {
+    Base::template CallIfEnabled<CategoryTracePointTraits<CategoryIndex>>(
+        [&callback](uint32_t instances) { callback(instances); });
+  }
+
+  // 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.
+  // TODO(skyostil): Investigate whether this should be fully outlined to reduce
+  // binary size.
+  template <size_t CategoryIndex>
+  static void TraceForCategory(uint32_t instances,
+                               const char* event_name,
+                               perfetto::protos::pbzero::TrackEvent::Type type)
+      PERFETTO_NO_INLINE {
+    Base::template TraceWithInstances<CategoryTracePointTraits<CategoryIndex>>(
+        instances, [&](typename Base::TraceContext ctx) {
+          TrackEventTraceContext track_event_ctx(
+              ctx.GetIncrementalState(),
+              [&ctx]() { return ctx.NewTracePacket(); });
+          // TODO(skyostil): Intern categories at compile time.
+          TrackEventInternal::WriteEvent(
+              &track_event_ctx, Registry->GetCategory(CategoryIndex)->name,
+              event_name, type);
+        });
+  }
+
+  static bool Register() {
+    TrackEventInternal::Initialize();
+    perfetto::DataSourceDescriptor dsd;
+    // TODO(skyostil): Advertise the known categories.
+    dsd.set_name("track_event");
+    return Base::Register(dsd);
+  }
+
+ private:
+  // Each category has its own enabled/disabled state, stored in the category
+  // registry.
+  template <size_t CategoryIndex>
+  struct CategoryTracePointTraits {
+    static constexpr std::atomic<uint8_t>* GetActiveInstances() {
+      return Registry->GetCategoryState(CategoryIndex);
+    }
+  };
 };
 
 }  // namespace internal
-
 }  // namespace perfetto
 
-PERFETTO_DECLARE_DATA_SOURCE_STATIC_MEMBERS(
-    perfetto::internal::TrackEventDataSource,
-    perfetto::internal::TrackEventIncrementalState);
-
 #endif  // INCLUDE_PERFETTO_TRACING_INTERNAL_TRACK_EVENT_DATA_SOURCE_H_
diff --git a/include/perfetto/tracing/internal/track_event_internal.h b/include/perfetto/tracing/internal/track_event_internal.h
new file mode 100644
index 0000000..5f594fb
--- /dev/null
+++ b/include/perfetto/tracing/internal/track_event_internal.h
@@ -0,0 +1,84 @@
+/*
+ * 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_INTERNAL_TRACK_EVENT_INTERNAL_H_
+#define INCLUDE_PERFETTO_TRACING_INTERNAL_TRACK_EVENT_INTERNAL_H_
+
+#include "perfetto/protozero/message_handle.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+#include "protos/perfetto/trace/track_event/track_event.pbzero.h"
+
+#include <unordered_map>
+
+namespace perfetto {
+class DataSourceConfig;
+
+namespace internal {
+class TrackEventCategoryRegistry;
+
+struct TrackEventIncrementalState {
+  bool was_cleared = true;
+
+  // Interned data.
+  // TODO(skyostil): Replace this with something more clever that supports
+  // dynamic strings too.
+  std::unordered_map<const char*, uint64_t> event_names;
+  std::unordered_map<const char*, uint64_t> categories;
+};
+
+class TrackEventTraceContext {
+ public:
+  using TracePacketHandle =
+      ::protozero::MessageHandle<::perfetto::protos::pbzero::TracePacket>;
+  using TracePacketCreator = std::function<TracePacketHandle()>;
+
+  TrackEventTraceContext(TrackEventIncrementalState* incremental_state,
+                         TracePacketCreator new_trace_packet);
+
+  TrackEventIncrementalState* incremental_state() const {
+    return incremental_state_;
+  }
+
+  TracePacketHandle NewTracePacket();
+
+ private:
+  TrackEventIncrementalState* incremental_state_;
+  TracePacketCreator new_trace_packet_;
+};
+
+// The backend portion of the track event trace point implemention. Outlined to
+// a separate .cc file so it can be shared by different track event category
+// namespaces.
+class TrackEventInternal {
+ public:
+  static void Initialize();
+
+  static void EnableTracing(const TrackEventCategoryRegistry& registry,
+                            const DataSourceConfig& config,
+                            uint32_t instance_index);
+  static void DisableTracing(const TrackEventCategoryRegistry& registry,
+                             uint32_t instance_index);
+
+  static void WriteEvent(TrackEventTraceContext*,
+                         const char* category,
+                         const char* name,
+                         perfetto::protos::pbzero::TrackEvent::Type);
+};
+
+}  // namespace internal
+}  // namespace perfetto
+
+#endif  // INCLUDE_PERFETTO_TRACING_INTERNAL_TRACK_EVENT_INTERNAL_H_
diff --git a/include/perfetto/tracing/internal/track_event_macros.h b/include/perfetto/tracing/internal/track_event_macros.h
new file mode 100644
index 0000000..915176c
--- /dev/null
+++ b/include/perfetto/tracing/internal/track_event_macros.h
@@ -0,0 +1,98 @@
+/*
+ * 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_INTERNAL_TRACK_EVENT_MACROS_H_
+#define INCLUDE_PERFETTO_TRACING_INTERNAL_TRACK_EVENT_MACROS_H_
+
+// This file contains underlying macros for the trace point track event
+// implementation. Perfetto API users typically don't need to use anything here
+// directly.
+
+#include "perfetto/base/compiler.h"
+#include "perfetto/tracing/internal/track_event_data_source.h"
+#include "perfetto/tracing/track_event_category_registry.h"
+
+// Defines data structures for backing a category registry.
+//
+// Each category has one enabled/disabled bit per possible data source instance.
+// The bits are packed, i.e., each byte holds the state for instances. To
+// improve cache locality, the bits for each instance are stored separately from
+// the names of the categories:
+//
+//   byte 0                      byte 1
+//   (inst0, inst1, ..., inst7), (inst0, inst1, ..., inst7)
+//
+#define PERFETTO_INTERNAL_DECLARE_CATEGORIES(...)                             \
+  namespace internal {                                                        \
+  constexpr ::perfetto::internal::TrackEventCategory kCategories[] = {        \
+      __VA_ARGS__};                                                           \
+  constexpr size_t kCategoryCount =                                           \
+      sizeof(kCategories) / sizeof(kCategories[0]);                           \
+  /* The per-instance enable/disable state per category */                    \
+  extern std::atomic<uint8_t> g_category_state_storage[kCategoryCount];       \
+  /* The category registry which mediates access to the above structures. */  \
+  /* The registry is used for two purposes: */                                \
+  /**/                                                                        \
+  /*    1) For looking up categories at build (constexpr) time. */            \
+  /*    2) For declaring the per-namespace TrackEvent data source. */         \
+  /**/                                                                        \
+  /* Because usage #1 requires a constexpr type and usage #2 requires an */   \
+  /* extern type (to avoid declaring a type based on a translation-unit */    \
+  /* variable), we need two separate copies of the registry with different */ \
+  /* storage specifiers. */                                                   \
+  /**/                                                                        \
+  /* TODO(skyostil): Unify these using a C++17 inline constexpr variable. */  \
+  constexpr ::perfetto::internal::TrackEventCategoryRegistry                  \
+      kConstExprCategoryRegistry(kCategoryCount,                              \
+                                 &kCategories[0],                             \
+                                 &g_category_state_storage[0]);               \
+  extern const ::perfetto::internal::TrackEventCategoryRegistry               \
+      kCategoryRegistry;                                                      \
+  }  // namespace internal
+
+// In a .cc file, declares storage for each category's runtime state.
+#define PERFETTO_INTERNAL_CATEGORY_STORAGE()                     \
+  namespace internal {                                           \
+  std::atomic<uint8_t> g_category_state_storage[kCategoryCount]; \
+  constexpr ::perfetto::internal::TrackEventCategoryRegistry     \
+      kCategoryRegistry(kCategoryCount,                          \
+                        &kCategories[0],                         \
+                        &g_category_state_storage[0]);           \
+  }  // namespace internal
+
+// Defines the TrackEvent data source for the current track event namespace.
+#define PERFETTO_INTERNAL_DECLARE_TRACK_EVENT_DATA_SOURCE()              \
+  struct TrackEvent : public ::perfetto::internal::TrackEventDataSource< \
+                          TrackEvent, &internal::kCategoryRegistry> {}
+
+// At compile time, turns a category name represented by a static string into an
+// index into the current category registry. A build error will be generated if
+// the category hasn't been registered. See PERFETTO_DEFINE_CATEGORIES.
+#define PERFETTO_GET_CATEGORY_INDEX(category)                                \
+  ::perfetto::internal::TrackEventCategoryRegistry::Validate<                \
+      ::PERFETTO_TRACK_EVENT_NAMESPACE::internal::kConstExprCategoryRegistry \
+          .Find(category)>()
+
+// Efficiently determines whether tracing is enabled for the given category, and
+// if so, emits one trace event with the given arguments.
+#define PERFETTO_INTERNAL_TRACK_EVENT(category, ...)                    \
+  ::PERFETTO_TRACK_EVENT_NAMESPACE::TrackEvent::CallIfCategoryEnabled<  \
+      PERFETTO_GET_CATEGORY_INDEX(category)>([&](uint32_t instances) {  \
+    ::PERFETTO_TRACK_EVENT_NAMESPACE::TrackEvent::TraceForCategory<     \
+        PERFETTO_GET_CATEGORY_INDEX(category)>(instances, __VA_ARGS__); \
+  })
+
+#endif  // INCLUDE_PERFETTO_TRACING_INTERNAL_TRACK_EVENT_MACROS_H_
diff --git a/include/perfetto/tracing/track_event.h b/include/perfetto/tracing/track_event.h
index 3c6dee1..e6e9689 100644
--- a/include/perfetto/tracing/track_event.h
+++ b/include/perfetto/tracing/track_event.h
@@ -19,57 +19,98 @@
 
 #include "perfetto/base/time.h"
 #include "perfetto/tracing/internal/track_event_data_source.h"
+#include "perfetto/tracing/internal/track_event_macros.h"
+#include "perfetto/tracing/track_event_category_registry.h"
 #include "protos/perfetto/trace/track_event/track_event.pbzero.h"
 
-namespace perfetto {
+// This file contains a set of macros designed for instrumenting applications
+// with track event trace points. While the underlying TrackEvent API can also
+// be used directly, doing so efficiently requires some care (e.g., to avoid
+// evaluating arguments while tracing is disabled). These types of optimizations
+// are abstracted away by the macros below.
+//
+// ================
+// Quickstart guide
+// ================
+//
+//   To add track events to your application, first define your categories in,
+//   e.g., my_tracing.h:
+//
+//       PERFETTO_DEFINE_CATEGORIES(
+//           PERFETTO_CATEGORY(base),
+//           PERFETTO_CATEGORY(v8),
+//           PERFETTO_CATEGORY(cc));
+//
+//   Then in a single .cc file, e.g., my_tracing.cc:
+//
+//       #include "my_tracing.h"
+//       PERFETTO_TRACK_EVENT_STATIC_STORAGE();
+//
+//   Finally, register track events at startup, after which you can record
+//   events with the TRACE_EVENT macros:
+//
+//       #include "my_tracing.h"
+//
+//       int main() {
+//         perfetto::TrackEvent::Register();
+//         TRACK_EVENT_BEGIN("category", "MyEvent");
+//         TRACK_EVENT_END("category");
+//         ...
+//       }
 
-// Track events are time-based markers that an application can use to construct
-// a timeline of its operation.
-class TrackEvent {
- public:
-  // Initializes the track event data source. Must be called before any other
-  // method on this class.
-  static void Initialize();
+// Each compilation unit can be in exactly one track event namespace,
+// allowing the overall program to use multiple track event data sources and
+// category lists if necessary. Use this macro to select the namespace for the
+// current compilation unit.
+//
+// If the program uses multiple track event namespaces, category & track event
+// registration (see quickstart above) needs to happen for both namespaces
+// separately.
+#ifndef PERFETTO_TRACK_EVENT_NAMESPACE
+#define PERFETTO_TRACK_EVENT_NAMESPACE perfetto
+#endif
 
-  // Returns the current tracing clock in nanoseconds.
-  static uint64_t GetTimeNs() {
-    // TODO(skyostil): Consider using boot time where available.
-    return static_cast<uint64_t>(perfetto::base::GetWallTimeNs().count());
-  }
+// A name for a single category. Wrapped in a macro in case we need to introduce
+// more fields in the future.
+#define PERFETTO_CATEGORY(name) \
+  { #name }
 
-  // Begin a slice on the current thread. |category| and |name| are free-form
-  // strings that describe the event. Both |category| and |name| must be
-  // statically allocated.
-  static void Begin(const char* category, const char* name) {
-    internal::TrackEventDataSource::WriteEvent(
-        category, name, perfetto::protos::pbzero::TrackEvent::TYPE_SLICE_BEGIN);
-  }
+// Register the set of available categories by passing a list of categories to
+// this macro: PERFETTO_CATEGORY(cat1), PERFETTO_CATEGORY(cat2), ...
+#define PERFETTO_DEFINE_CATEGORIES(...)                        \
+  namespace PERFETTO_TRACK_EVENT_NAMESPACE {                   \
+  /* The list of category names */                             \
+  PERFETTO_INTERNAL_DECLARE_CATEGORIES(__VA_ARGS__);           \
+  /* The track event data source for this set of categories */ \
+  PERFETTO_INTERNAL_DECLARE_TRACK_EVENT_DATA_SOURCE();         \
+  }  // namespace PERFETTO_TRACK_EVENT_NAMESPACE
 
-  // End a slice on the current thread.
-  static void End(const char* category) {
-    internal::TrackEventDataSource::WriteEvent(
-        category, nullptr,
-        perfetto::protos::pbzero::TrackEvent::TYPE_SLICE_END);
-  }
+// Allocate storage for each category by using this macro once per track event
+// namespace.
+#define PERFETTO_TRACK_EVENT_STATIC_STORAGE() \
+  namespace PERFETTO_TRACK_EVENT_NAMESPACE {  \
+  PERFETTO_INTERNAL_CATEGORY_STORAGE();       \
+  }  // namespace PERFETTO_TRACK_EVENT_NAMESPACE
 
-  // TODO(skyostil): Add per-category enable/disable.
-  // TODO(skyostil): Add arguments.
-  // TODO(skyostil): Add scoped events.
-  // TODO(skyostil): Add async events.
-  // TODO(skyostil): Add flow events.
-  // TODO(skyostil): Add instant events.
-  // TODO(skyostil): Add counters.
+// 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.
+#define TRACE_EVENT_BEGIN(category, name) \
+  PERFETTO_INTERNAL_TRACK_EVENT(          \
+      category, name,                     \
+      ::perfetto::protos::pbzero::TrackEvent::TYPE_SLICE_BEGIN)
 
-  static void Flush() {
-    internal::TrackEventDataSource::Trace(
-        [&](internal::TrackEventDataSource::TraceContext ctx) { ctx.Flush(); });
-  }
-};
+// End a thread-scoped slice under |category|.
+#define TRACE_EVENT_END(category) \
+  PERFETTO_INTERNAL_TRACK_EVENT(  \
+      category, nullptr,          \
+      ::perfetto::protos::pbzero::TrackEvent::TYPE_SLICE_END)
 
-}  // namespace perfetto
-
-PERFETTO_DECLARE_DATA_SOURCE_STATIC_MEMBERS(
-    perfetto::internal::TrackEventDataSource,
-    perfetto::internal::TrackEventIncrementalState);
+// TODO(skyostil): Add arguments.
+// TODO(skyostil): Add scoped events.
+// TODO(skyostil): Add async events.
+// TODO(skyostil): Add flow events.
+// TODO(skyostil): Add instant events.
+// TODO(skyostil): Add counters.
 
 #endif  // INCLUDE_PERFETTO_TRACING_TRACK_EVENT_H_
diff --git a/include/perfetto/tracing/track_event_category_registry.h b/include/perfetto/tracing/track_event_category_registry.h
new file mode 100644
index 0000000..3e15db6
--- /dev/null
+++ b/include/perfetto/tracing/track_event_category_registry.h
@@ -0,0 +1,107 @@
+/*
+ * 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_EVENT_CATEGORY_REGISTRY_H_
+#define INCLUDE_PERFETTO_TRACING_TRACK_EVENT_CATEGORY_REGISTRY_H_
+
+#include "perfetto/tracing/data_source.h"
+
+#include <atomic>
+
+namespace perfetto {
+namespace internal {
+
+// A compile-time representation of a track event category. See
+// PERFETTO_DEFINE_CATEGORIES for registering your own categories.
+struct TrackEventCategory {
+  const char* const name;
+};
+
+// Holds all the registered categories for one category namespace. See
+// PERFETTO_DEFINE_CATEGORIES for building the registry.
+class TrackEventCategoryRegistry {
+ public:
+  constexpr TrackEventCategoryRegistry(size_t category_count,
+                                       const TrackEventCategory* categories,
+                                       std::atomic<uint8_t>* state_storage)
+      : categories_(categories),
+        category_count_(category_count),
+        state_storage_(state_storage) {
+    static_assert(
+        sizeof(state_storage[0].load()) * 8 >= kMaxDataSourceInstances,
+        "The category state must have enough bits for all possible data source "
+        "instances");
+  }
+
+  size_t category_count() const { return category_count_; }
+
+  // Returns a category based on its index.
+  const TrackEventCategory* GetCategory(size_t index) const;
+
+  // Turn tracing on or off for the given category in a track event data source
+  // instance.
+  void EnableCategoryForInstance(size_t category_index,
+                                 uint32_t instance_index) const;
+  void DisableCategoryForInstance(size_t category_index,
+                                  uint32_t instance_index) const;
+
+  constexpr std::atomic<uint8_t>* GetCategoryState(
+      size_t category_index) const {
+    return &state_storage_[category_index];
+  }
+
+  // --------------------------------------------------------------------------
+  // Trace point support
+  // --------------------------------------------------------------------------
+  //
+  // (The following methods are used by the track event trace point
+  // implementation and typically don't need to be called by other code.)
+
+  // At compile time, turn a category name into an index into the registry.
+  // Returns kInvalidCategoryIndex if the category was not found.
+  static constexpr size_t kInvalidCategoryIndex = static_cast<size_t>(-1);
+  constexpr size_t Find(const char* name, size_t index = 0) const {
+    return (index == category_count_) ? kInvalidCategoryIndex
+                                      : StringEq(categories_[index].name, name)
+                                            ? index
+                                            : Find(name, index + 1);
+  }
+
+  // A helper for validating that a category was registered at compile time.
+  template <size_t CategoryIndex>
+  static constexpr size_t Validate() {
+    static_assert(CategoryIndex != kInvalidCategoryIndex,
+                  "A track event used an unknown category. Please add it to "
+                  "PERFETTO_DEFINE_CATEGORIES().");
+    return CategoryIndex;
+  }
+
+ private:
+  // TODO(skyostil): Make the compile-time routines nicer with C++14.
+  static constexpr bool StringEq(const char* a, const char* b) {
+    return *a != *b ? false
+                    : (!*a || !*b) ? (*a == *b) : StringEq(a + 1, b + 1);
+  }
+
+  const TrackEventCategory* const categories_;
+  const size_t category_count_;
+  std::atomic<uint8_t>* const state_storage_;
+};
+
+}  // namespace internal
+}  // namespace perfetto
+
+#endif  // INCLUDE_PERFETTO_TRACING_TRACK_EVENT_CATEGORY_REGISTRY_H_
diff --git a/src/tracing/BUILD.gn b/src/tracing/BUILD.gn
index e426793..aca11d7 100644
--- a/src/tracing/BUILD.gn
+++ b/src/tracing/BUILD.gn
@@ -231,6 +231,8 @@
 source_set("client_api") {
   deps = [
     ":common",
+    "../../include/perfetto/tracing/core",
+    "../../protos/perfetto/config:lite",
     "../base",
     "../tracing",
   ]
@@ -244,9 +246,10 @@
     "internal/in_process_tracing_backend.h",
     "internal/tracing_muxer_impl.cc",
     "internal/tracing_muxer_impl.h",
+    "internal/track_event_internal.cc",
     "platform.cc",
     "tracing.cc",
-    "track_event.cc",
+    "track_event_category_registry.cc",
     "virtual_destructors.cc",
   ]
 
@@ -276,6 +279,10 @@
     ]
     sources = [
       "api_integrationtest.cc",
+      "test/tracing_module.cc",
+      "test/tracing_module.h",
+      "test/tracing_module2.cc",
+      "test/tracing_module_categories.h",
     ]
   }
 }
diff --git a/src/tracing/api_integrationtest.cc b/src/tracing/api_integrationtest.cc
index e093b6b..d11359c 100644
--- a/src/tracing/api_integrationtest.cc
+++ b/src/tracing/api_integrationtest.cc
@@ -32,18 +32,28 @@
 // Deliberately not pulling any non-public perfetto header to spot accidental
 // header public -> non-public dependency while building this file.
 
-// This is the only header allowed here, see comments in api_test_support.h.
+// These two are the only headers allowed here, see comments in
+// api_test_support.h.
 #include "src/tracing/test/api_test_support.h"
+#include "src/tracing/test/tracing_module.h"
 
 #include "perfetto/tracing/core/data_source_descriptor.h"
 #include "perfetto/tracing/core/trace_config.h"
 
+// Trace categories used in the tests.
+PERFETTO_DEFINE_CATEGORIES(PERFETTO_CATEGORY(test),
+                           PERFETTO_CATEGORY(foo),
+                           PERFETTO_CATEGORY(bar));
+PERFETTO_TRACK_EVENT_STATIC_STORAGE();
+
 namespace {
 
 using ::testing::_;
+using ::testing::HasSubstr;
 using ::testing::Invoke;
 using ::testing::InvokeWithoutArgs;
 using ::testing::NiceMock;
+using ::testing::Not;
 using ::testing::Property;
 using ::testing::StrEq;
 
@@ -109,6 +119,42 @@
   void OnStop(const StopArgs&) override {}
 };
 
+// Used to verify that track event data sources in different namespaces register
+// themselves correctly in the muxer.
+class MockTracingMuxer : public perfetto::internal::TracingMuxer {
+ public:
+  struct DataSource {
+    const perfetto::DataSourceDescriptor dsd;
+    perfetto::internal::DataSourceStaticState* static_state;
+  };
+
+  MockTracingMuxer() : TracingMuxer(nullptr), prev_instance_(instance_) {
+    instance_ = this;
+  }
+  ~MockTracingMuxer() override { instance_ = prev_instance_; }
+
+  bool RegisterDataSource(
+      const perfetto::DataSourceDescriptor& dsd,
+      DataSourceFactory,
+      perfetto::internal::DataSourceStaticState* static_state) override {
+    data_sources.emplace_back(DataSource{dsd, static_state});
+    return true;
+  }
+
+  std::unique_ptr<perfetto::TraceWriterBase> CreateTraceWriter(
+      perfetto::internal::DataSourceState*,
+      perfetto::BufferExhaustedPolicy) override {
+    return nullptr;
+  }
+
+  void DestroyStoppedTraceWritersForCurrentThread() override {}
+
+  std::vector<DataSource> data_sources;
+
+ private:
+  TracingMuxer* prev_instance_;
+};
+
 struct TestIncrementalState {
   TestIncrementalState() { constructed = true; }
   // Note: a virtual destructor is not required for incremental state.
@@ -122,9 +168,14 @@
 bool TestIncrementalState::constructed;
 bool TestIncrementalState::destroyed;
 
+struct TestIncrementalDataSourceTraits
+    : public perfetto::DefaultDataSourceTraits {
+  using IncrementalStateType = TestIncrementalState;
+};
+
 class TestIncrementalDataSource
     : public perfetto::DataSource<TestIncrementalDataSource,
-                                  TestIncrementalState> {
+                                  TestIncrementalDataSourceTraits> {
  public:
   void OnSetup(const SetupArgs&) override {}
   void OnStart(const StartArgs&) override {}
@@ -156,6 +207,7 @@
       perfetto::Tracing::Initialize(args);
       was_initialized = true;
       RegisterDataSource<MockDataSource>("my_data_source");
+      perfetto::TrackEvent::Register();
     }
     // Make sure our data source always has a valid handle.
     data_sources_["my_data_source"];
@@ -229,7 +281,6 @@
   // This test used to cause a use after free as the tracing session got
   // destroyed. It needed to be run approximately 2000 times to catch it so test
   // with --gtest_repeat=3000 (less if running under GDB).
-  perfetto::TrackEvent::Initialize(/* TODO(skyostil): Register categories */);
 
   // Setup the trace config.
   perfetto::TraceConfig cfg;
@@ -253,7 +304,6 @@
   // This test used to cause a deadlock (due to StopBlocking() after the session
   // already stopped). This usually occurred within 1 or 2 runs of the test so
   // use --gtest_repeat=10
-  perfetto::TrackEvent::Initialize(/* TODO(skyostil): Register categories */);
 
   // Setup the trace config.
   perfetto::TraceConfig cfg;
@@ -277,8 +327,6 @@
 }
 
 TEST_F(PerfettoApiTest, TrackEvent) {
-  perfetto::TrackEvent::Initialize(/* TODO(skyostil): Register categories */);
-
   // Setup the trace config.
   perfetto::TraceConfig cfg;
   cfg.set_duration_ms(500);
@@ -292,8 +340,8 @@
   tracing_session->get()->StartBlocking();
 
   // Emit one complete track event.
-  perfetto::TrackEvent::Begin("test", "TestEvent");
-  perfetto::TrackEvent::End("test");
+  TRACE_EVENT_BEGIN("test", "TestEvent");
+  TRACE_EVENT_END("test");
   perfetto::TrackEvent::Flush();
 
   tracing_session->on_stop.Wait();
@@ -311,7 +359,7 @@
   bool end_found = false;
   bool process_descriptor_found = false;
   bool thread_descriptor_found = false;
-  auto now = perfetto::TrackEvent::GetTimeNs();
+  auto now = perfetto::test::GetWallTimeNs();
   uint32_t sequence_id = 0;
   int32_t cur_pid = perfetto::test::GetCurrentProcessId();
   for (const auto& packet : trace.packet()) {
@@ -381,6 +429,194 @@
   EXPECT_TRUE(end_found);
 }
 
+TEST_F(PerfettoApiTest, TrackEventCategories) {
+  // 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 some track events.
+  TRACE_EVENT_BEGIN("foo", "NotEnabled");
+  TRACE_EVENT_END("foo");
+  TRACE_EVENT_BEGIN("bar", "Enabled");
+  TRACE_EVENT_END("bar");
+
+  tracing_session->get()->StopBlocking();
+  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
+  std::string trace(raw_trace.data(), raw_trace.size());
+  // TODO(skyostil): Come up with a nicer way to verify trace contents.
+  EXPECT_THAT(trace, HasSubstr("Enabled"));
+  EXPECT_THAT(trace, Not(HasSubstr("NotEnabled")));
+}
+
+TEST_F(PerfettoApiTest, TrackEventRegistrationWithModule) {
+  MockTracingMuxer muxer;
+
+  // Each track event namespace registers its own data source.
+  perfetto::TrackEvent::Register();
+  EXPECT_EQ(1u, muxer.data_sources.size());
+
+  tracing_module::InitializeCategories();
+  EXPECT_EQ(2u, muxer.data_sources.size());
+
+  // Both data sources have the same name but distinct static data (i.e.,
+  // individual instance states).
+  EXPECT_EQ("track_event", muxer.data_sources[0].dsd.name());
+  EXPECT_EQ("track_event", muxer.data_sources[1].dsd.name());
+  EXPECT_NE(muxer.data_sources[0].static_state,
+            muxer.data_sources[1].static_state);
+}
+
+TEST_F(PerfettoApiTest, TrackEventSharedIncrementalState) {
+  tracing_module::InitializeCategories();
+
+  // 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");
+  auto* tracing_session = NewTrace(cfg);
+  tracing_session->get()->StartBlocking();
+
+  perfetto::internal::TrackEventIncrementalState* main_state = nullptr;
+  perfetto::TrackEvent::Trace(
+      [&main_state](perfetto::TrackEvent::TraceContext ctx) {
+        main_state = ctx.GetIncrementalState();
+      });
+  perfetto::internal::TrackEventIncrementalState* module_state =
+      tracing_module::GetIncrementalState();
+
+  // Both track event data sources should use the same incremental state (thanks
+  // to sharing TLS).
+  EXPECT_NE(nullptr, main_state);
+  EXPECT_EQ(main_state, module_state);
+  tracing_session->get()->StopBlocking();
+}
+
+TEST_F(PerfettoApiTest, TrackEventCategoriesWithModule) {
+  // Check that categories defined in two different category registries are
+  // enabled and disabled correctly.
+  tracing_module::InitializeCategories();
+
+  // 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");
+
+  // Only the "foo" category is enabled. It also exists both locally and in the
+  // existing module.
+  ds_cfg->set_legacy_config("foo");
+
+  // Create a new trace session.
+  auto* tracing_session = NewTrace(cfg);
+  tracing_session->get()->StartBlocking();
+
+  // Emit some track events locally and from the test module.
+  TRACE_EVENT_BEGIN("foo", "FooEventFromMain");
+  TRACE_EVENT_END("foo");
+  tracing_module::EmitTrackEvents();
+  tracing_module::EmitTrackEvents2();
+  TRACE_EVENT_BEGIN("bar", "DisabledEventFromMain");
+  TRACE_EVENT_END("bar");
+
+  tracing_session->get()->StopBlocking();
+  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
+  std::string trace(raw_trace.data(), raw_trace.size());
+  EXPECT_THAT(trace, HasSubstr("FooEventFromMain"));
+  EXPECT_THAT(trace, Not(HasSubstr("DisabledEventFromMain")));
+  EXPECT_THAT(trace, HasSubstr("FooEventFromModule"));
+  EXPECT_THAT(trace, Not(HasSubstr("DisabledEventFromModule")));
+  EXPECT_THAT(trace, HasSubstr("FooEventFromModule2"));
+  EXPECT_THAT(trace, Not(HasSubstr("DisabledEventFromModule2")));
+
+  perfetto::protos::Trace parsed_trace;
+  ASSERT_TRUE(
+      parsed_trace.ParseFromArray(raw_trace.data(), int(raw_trace.size())));
+
+  uint32_t sequence_id = 0;
+  for (const auto& packet : parsed_trace.packet()) {
+    if (!packet.has_track_event())
+      continue;
+
+    // Make sure we only see track events on one sequence. This means all track
+    // event modules are sharing the same trace writer (by using the same TLS
+    // index).
+    if (packet.trusted_packet_sequence_id()) {
+      if (!sequence_id)
+        sequence_id = packet.trusted_packet_sequence_id();
+      EXPECT_EQ(sequence_id, packet.trusted_packet_sequence_id());
+    }
+  }
+}
+
+TEST_F(PerfettoApiTest, TrackEventConcurrentSessions) {
+  // Check that categories that are enabled and disabled in two parallel tracing
+  // sessions don't interfere.
+
+  // 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");
+
+  // Session #1 enables the "foo" category.
+  ds_cfg->set_legacy_config("foo");
+  auto* tracing_session = NewTrace(cfg);
+  tracing_session->get()->StartBlocking();
+
+  // Session #2 enables the "bar" category.
+  ds_cfg->set_legacy_config("bar");
+  auto* tracing_session2 = NewTrace(cfg);
+  tracing_session2->get()->StartBlocking();
+
+  // Emit some track events under both categories.
+  TRACE_EVENT_BEGIN("foo", "Session1_First");
+  TRACE_EVENT_END("foo");
+  TRACE_EVENT_BEGIN("bar", "Session2_First");
+  TRACE_EVENT_END("bar");
+
+  tracing_session->get()->StopBlocking();
+  TRACE_EVENT_BEGIN("foo", "Session1_Second");
+  TRACE_EVENT_END("foo");
+  TRACE_EVENT_BEGIN("bar", "Session2_Second");
+  TRACE_EVENT_END("bar");
+
+  tracing_session2->get()->StopBlocking();
+  TRACE_EVENT_BEGIN("foo", "Session1_Third");
+  TRACE_EVENT_END("foo");
+  TRACE_EVENT_BEGIN("bar", "Session2_Third");
+  TRACE_EVENT_END("bar");
+
+  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
+  std::string trace(raw_trace.data(), raw_trace.size());
+  EXPECT_THAT(trace, HasSubstr("Session1_First"));
+  EXPECT_THAT(trace, Not(HasSubstr("Session1_Second")));
+  EXPECT_THAT(trace, Not(HasSubstr("Session1_Third")));
+  EXPECT_THAT(trace, Not(HasSubstr("Session2_First")));
+  EXPECT_THAT(trace, Not(HasSubstr("Session2_Second")));
+  EXPECT_THAT(trace, Not(HasSubstr("Session2_Third")));
+
+  std::vector<char> raw_trace2 = tracing_session2->get()->ReadTraceBlocking();
+  std::string trace2(raw_trace2.data(), raw_trace2.size());
+  EXPECT_THAT(trace2, Not(HasSubstr("Session1_First")));
+  EXPECT_THAT(trace2, Not(HasSubstr("Session1_Second")));
+  EXPECT_THAT(trace2, Not(HasSubstr("Session1_Third")));
+  EXPECT_THAT(trace2, HasSubstr("Session2_First"));
+  EXPECT_THAT(trace2, HasSubstr("Session2_Second"));
+  EXPECT_THAT(trace2, Not(HasSubstr("Session2_Third")));
+}
+
 TEST_F(PerfettoApiTest, OneDataSourceOneEvent) {
   auto* data_source = &data_sources_["my_data_source"];
 
@@ -398,6 +634,9 @@
   MockDataSource::Trace([](MockDataSource::TraceContext) {
     FAIL() << "Should not be called because the trace was not started";
   });
+  MockDataSource::CallIfEnabled([](uint32_t) {
+    FAIL() << "Should not be called because the trace was not started";
+  });
 
   tracing_session->get()->Start();
   data_source->on_setup.Wait();
@@ -420,6 +659,12 @@
         packet = ctx.NewTracePacket();
       });
 
+  uint32_t active_instances = 0;
+  MockDataSource::CallIfEnabled([&active_instances](uint32_t instances) {
+    active_instances = instances;
+  });
+  EXPECT_EQ(1u, active_instances);
+
   data_source->on_stop.Wait();
   tracing_session->on_stop.Wait();
   EXPECT_EQ(trace_lambda_calls, 1);
@@ -427,6 +672,9 @@
   MockDataSource::Trace([](MockDataSource::TraceContext) {
     FAIL() << "Should not be called because the trace is now stopped";
   });
+  MockDataSource::CallIfEnabled([](uint32_t) {
+    FAIL() << "Should not be called because the trace is now stopped";
+  });
 
   std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
   ASSERT_GE(raw_trace.size(), 0u);
@@ -738,4 +986,4 @@
 PERFETTO_DEFINE_DATA_SOURCE_STATIC_MEMBERS(MockDataSource);
 PERFETTO_DEFINE_DATA_SOURCE_STATIC_MEMBERS(MockDataSource2);
 PERFETTO_DEFINE_DATA_SOURCE_STATIC_MEMBERS(TestIncrementalDataSource,
-                                           TestIncrementalState);
+                                           TestIncrementalDataSourceTraits);
diff --git a/src/tracing/internal/tracing_muxer_impl.cc b/src/tracing/internal/tracing_muxer_impl.cc
index 8b269a8..1ce6f93 100644
--- a/src/tracing/internal/tracing_muxer_impl.cc
+++ b/src/tracing/internal/tracing_muxer_impl.cc
@@ -24,6 +24,7 @@
 #include "perfetto/base/build_config.h"
 #include "perfetto/base/logging.h"
 #include "perfetto/base/task_runner.h"
+#include "perfetto/ext/base/hash.h"
 #include "perfetto/ext/base/thread_checker.h"
 #include "perfetto/ext/base/waitable_event.h"
 #include "perfetto/ext/tracing/core/trace_packet.h"
@@ -36,6 +37,7 @@
 #include "perfetto/tracing/trace_writer_base.h"
 #include "perfetto/tracing/tracing.h"
 #include "perfetto/tracing/tracing_backend.h"
+#include "protos/perfetto/config/trace_config.pb.h"
 #include "src/tracing/internal/in_process_tracing_backend.h"
 #include "src/tracing/internal/system_tracing_backend.h"
 
@@ -55,6 +57,17 @@
   mutable std::function<void()> async_stop_closure;
 };
 
+uint64_t ComputeConfigHash(const DataSourceConfig& config) {
+  base::Hash hasher;
+  perfetto::protos::DataSourceConfig config_proto;
+  config.ToProto(&config_proto);
+  std::string config_bytes;
+  bool serialized = config_proto.SerializeToString(&config_bytes);
+  PERFETTO_DCHECK(serialized);
+  hasher.Update(&config_bytes[0], config_bytes.size());
+  return hasher.digest();
+}
+
 }  // namespace
 
 // ----- Begin of TracingMuxerImpl::ProducerImpl
@@ -461,7 +474,7 @@
 
   static std::atomic<uint32_t> last_id{};
   uint32_t new_index = last_id++;
-  if (new_index >= kMaxDataSources - 1) {
+  if (new_index >= kMaxDataSources) {
     PERFETTO_DLOG(
         "RegisterDataSource failed: too many data sources already registered");
     return false;
@@ -493,12 +506,39 @@
   PERFETTO_DCHECK_THREAD(thread_checker_);
   PERFETTO_DLOG("Setting up data source %" PRIu64 " %s", instance_id,
                 cfg.name().c_str());
+  uint64_t config_hash = ComputeConfigHash(cfg);
 
   for (const auto& rds : data_sources_) {
     if (rds.descriptor.name() != cfg.name())
       continue;
-
     DataSourceStaticState& static_state = *rds.static_state;
+
+    // If this data source is already active for this exact config, don't start
+    // another instance. This happens when we have several data sources with the
+    // same name, in which case the service sends one SetupDataSource event for
+    // each one. Since we can't map which event maps to which data source, we
+    // ensure each event only starts one data source instance.
+    // TODO(skyostil): Register a unique id with each data source to the service
+    // to disambiguate.
+    bool active_for_config = false;
+    for (uint32_t i = 0; i < kMaxDataSourceInstances; i++) {
+      if (!static_state.TryGet(i))
+        continue;
+      auto* internal_state =
+          reinterpret_cast<DataSourceState*>(&static_state.instances[i]);
+      if (internal_state->backend_id == backend_id &&
+          internal_state->config_hash == config_hash) {
+        active_for_config = true;
+        break;
+      }
+    }
+    if (active_for_config) {
+      PERFETTO_DLOG(
+          "Data source %s is already active with this config, skipping",
+          cfg.name().c_str());
+      continue;
+    }
+
     for (uint32_t i = 0; i < kMaxDataSourceInstances; i++) {
       // Find a free slot.
       if (static_state.TryGet(i))
@@ -515,14 +555,16 @@
       internal_state->data_source_instance_id = instance_id;
       internal_state->buffer_id =
           static_cast<internal::BufferId>(cfg.target_buffer());
+      internal_state->config_hash = config_hash;
       internal_state->data_source = rds.factory();
 
       // This must be made at the end. See matching acquire-load in
       // DataSource::Trace().
-      static_state.valid_instances.fetch_or(1 << i, std::memory_order_acq_rel);
+      static_state.valid_instances.fetch_or(1 << i, std::memory_order_release);
 
       DataSourceBase::SetupArgs setup_args;
       setup_args.config = &cfg;
+      setup_args.internal_instance_index = i;
       internal_state->data_source->OnSetup(setup_args);
       return;
     }
@@ -530,6 +572,7 @@
         "Maximum number of data source instances exhausted. "
         "Dropping data source %" PRIu64,
         instance_id);
+    break;
   }
 }
 
@@ -545,9 +588,12 @@
     return;
   }
 
+  DataSourceBase::StartArgs start_args{};
+  start_args.internal_instance_index = ds.instance_idx;
+
   std::lock_guard<std::recursive_mutex> guard(ds.internal_state->lock);
   ds.internal_state->trace_lambda_enabled = true;
-  ds.internal_state->data_source->OnStart(DataSourceBase::StartArgs{});
+  ds.internal_state->data_source->OnStart(start_args);
 }
 
 // Called by the service of one of the backends.
@@ -564,6 +610,7 @@
   }
 
   StopArgsImpl stop_args{};
+  stop_args.internal_instance_index = ds.instance_idx;
   stop_args.async_stop_closure = [this, backend_id, instance_id] {
     // TracingMuxerImpl is long lived, capturing |this| is okay.
     // The notification closure can be moved out of the StopArgs by the
@@ -631,12 +678,12 @@
   // Iterate across all possible data source types.
   auto cur_generation = generation_.load(std::memory_order_acquire);
   auto* root_tls = GetOrCreateTracingTLS();
-  for (size_t ds_idx = 0; ds_idx < kMaxDataSources; ds_idx++) {
+
+  auto destroy_stopped_instances = [](DataSourceThreadLocalState& tls) {
     // |tls| has a vector of per-data-source-instance thread-local state.
-    DataSourceThreadLocalState& tls = root_tls->data_sources_tls[ds_idx];
     DataSourceStaticState* static_state = tls.static_state;
     if (!static_state)
-      continue;  // Slot not used.
+      return;  // Slot not used.
 
     // Iterate across all possible instances for this data source.
     for (uint32_t inst = 0; inst < kMaxDataSourceInstances; inst++) {
@@ -653,7 +700,14 @@
       // The DataSource instance has been destroyed or recycled.
       ds_tls.Reset();  // Will also destroy the |ds_tls.trace_writer|.
     }
+  };
+
+  for (size_t ds_idx = 0; ds_idx < kMaxDataSources; ds_idx++) {
+    // |tls| has a vector of per-data-source-instance thread-local state.
+    DataSourceThreadLocalState& tls = root_tls->data_sources_tls[ds_idx];
+    destroy_stopped_instances(tls);
   }
+  destroy_stopped_instances(root_tls->track_event_tls);
   root_tls->generation = cur_generation;
 }
 
diff --git a/src/tracing/track_event.cc b/src/tracing/internal/track_event_internal.cc
similarity index 63%
rename from src/tracing/track_event.cc
rename to src/tracing/internal/track_event_internal.cc
index 88411b9..b797e08 100644
--- a/src/tracing/track_event.cc
+++ b/src/tracing/internal/track_event_internal.cc
@@ -14,16 +14,16 @@
  * limitations under the License.
  */
 
-#include "perfetto/tracing/track_event.h"
+#include "perfetto/tracing/internal/track_event_internal.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_descriptor.h"
-#include "perfetto/tracing/data_source.h"
+#include "perfetto/tracing/core/data_source_config.h"
+#include "perfetto/tracing/track_event_category_registry.h"
 #include "protos/perfetto/trace/interned_data/interned_data.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_event.pbzero.h"
 
 namespace perfetto {
 namespace internal {
@@ -31,6 +31,11 @@
 
 std::atomic<perfetto::base::PlatformThreadID> g_main_thread;
 
+uint64_t GetTimeNs() {
+  // TODO(skyostil): Consider using boot time where available.
+  return static_cast<uint64_t>(perfetto::base::GetWallTimeNs().count());
+}
+
 uint64_t GetNameIidOrZero(std::unordered_map<const char*, uint64_t>& name_map,
                           const char* name) {
   auto it = name_map.find(name);
@@ -39,27 +44,84 @@
   return it->second;
 }
 
+// static
+void WriteSequenceDescriptors(TrackEventTraceContext* ctx, uint64_t timestamp) {
+  if (perfetto::base::GetThreadId() == g_main_thread) {
+    auto packet = ctx->NewTracePacket();
+    packet->set_timestamp(timestamp);
+    packet->set_incremental_state_cleared(true);
+    auto pd = packet->set_process_descriptor();
+    pd->set_pid(static_cast<int32_t>(base::GetProcessId()));
+    // TODO(skyostil): Record command line.
+  }
+  {
+    auto packet = ctx->NewTracePacket();
+    packet->set_timestamp(timestamp);
+    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
 
-void TrackEventDataSource::OnSetup(const SetupArgs&) {}
-void TrackEventDataSource::OnStart(const StartArgs&) {}
-void TrackEventDataSource::OnStop(const StopArgs&) {}
+TrackEventTraceContext::TrackEventTraceContext(
+    TrackEventIncrementalState* incremental_state,
+    TracePacketCreator new_trace_packet)
+    : incremental_state_(incremental_state),
+      new_trace_packet_(std::move(new_trace_packet)) {}
+
+TrackEventTraceContext::TracePacketHandle
+TrackEventTraceContext::NewTracePacket() {
+  return new_trace_packet_();
+}
 
 // static
-void TrackEventDataSource::WriteEventImpl(
-    internal::TrackEventDataSource::TraceContext ctx,
+void TrackEventInternal::Initialize() {
+  if (!g_main_thread)
+    g_main_thread = perfetto::base::GetThreadId();
+}
+
+// static
+void TrackEventInternal::EnableTracing(
+    const TrackEventCategoryRegistry& registry,
+    const DataSourceConfig& config,
+    uint32_t instance_index) {
+  for (size_t i = 0; i < registry.category_count(); i++) {
+    // TODO(skyostil): Support the full category config syntax instead of
+    // just strict matching.
+    // TODO(skyostil): Support comma-separated categories.
+    if (config.legacy_config().empty() ||
+        config.legacy_config() == registry.GetCategory(i)->name) {
+      registry.EnableCategoryForInstance(i, instance_index);
+    }
+  }
+}
+
+// static
+void TrackEventInternal::DisableTracing(
+    const TrackEventCategoryRegistry& registry,
+    uint32_t instance_index) {
+  for (size_t i = 0; i < registry.category_count(); i++)
+    registry.DisableCategoryForInstance(i, instance_index);
+}
+
+// static
+void TrackEventInternal::WriteEvent(
+    TrackEventTraceContext* ctx,
     const char* category,
     const char* name,
     perfetto::protos::pbzero::TrackEvent::Type type) {
   PERFETTO_DCHECK(category);
-  auto timestamp = TrackEvent::GetTimeNs();
+  PERFETTO_DCHECK(g_main_thread);
+  auto timestamp = GetTimeNs();
 
-  auto* incr_state = ctx.GetIncrementalState();
+  auto* incr_state = ctx->incremental_state();
   if (incr_state->was_cleared) {
     incr_state->was_cleared = false;
-    WriteSequenceDescriptors(&ctx, timestamp);
+    WriteSequenceDescriptors(ctx, timestamp);
   }
-  auto packet = ctx.NewTracePacket();
+  auto packet = ctx->NewTracePacket();
   packet->set_timestamp(timestamp);
 
   // We assume that |category| and |name| point to strings with static lifetime.
@@ -94,39 +156,5 @@
   }
 }
 
-// static
-void TrackEventDataSource::WriteSequenceDescriptors(
-    internal::TrackEventDataSource::TraceContext* ctx,
-    uint64_t timestamp) {
-  if (perfetto::base::GetThreadId() == g_main_thread) {
-    auto packet = ctx->NewTracePacket();
-    packet->set_timestamp(timestamp);
-    packet->set_incremental_state_cleared(true);
-    auto pd = packet->set_process_descriptor();
-    pd->set_pid(static_cast<int32_t>(base::GetProcessId()));
-    // TODO(skyostil): Record command line.
-  }
-  {
-    auto packet = ctx->NewTracePacket();
-    packet->set_timestamp(timestamp);
-    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 internal
-
-// static
-void TrackEvent::Initialize() {
-  internal::g_main_thread = perfetto::base::GetThreadId();
-  DataSourceDescriptor dsd;
-  dsd.set_name("track_event");
-  internal::TrackEventDataSource::Register(dsd);
-}
-
 }  // namespace perfetto
-
-PERFETTO_DEFINE_DATA_SOURCE_STATIC_MEMBERS(
-    perfetto::internal::TrackEventDataSource,
-    perfetto::internal::TrackEventIncrementalState);
diff --git a/src/tracing/test/api_test_support.cc b/src/tracing/test/api_test_support.cc
index 73f1fae..ce49407 100644
--- a/src/tracing/test/api_test_support.cc
+++ b/src/tracing/test/api_test_support.cc
@@ -16,6 +16,7 @@
 
 #include "src/tracing/test/api_test_support.h"
 
+#include "perfetto/base/time.h"
 #include "perfetto/ext/base/proc_utils.h"
 
 namespace perfetto {
@@ -25,5 +26,9 @@
   return static_cast<int32_t>(base::GetProcessId());
 }
 
+uint64_t GetWallTimeNs() {
+  return static_cast<uint64_t>(perfetto::base::GetWallTimeNs().count());
+}
+
 }  // namespace test
 }  // namespace perfetto
diff --git a/src/tracing/test/api_test_support.h b/src/tracing/test/api_test_support.h
index 9409caf..d85b9ec 100644
--- a/src/tracing/test/api_test_support.h
+++ b/src/tracing/test/api_test_support.h
@@ -32,6 +32,7 @@
 namespace test {
 
 int32_t GetCurrentProcessId();
+uint64_t GetWallTimeNs();
 
 }  // namespace test
 }  // namespace perfetto
diff --git a/src/tracing/test/tracing_module.cc b/src/tracing/test/tracing_module.cc
new file mode 100644
index 0000000..68a31cc
--- /dev/null
+++ b/src/tracing/test/tracing_module.cc
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/tracing/test/tracing_module.h"
+
+#include "src/tracing/test/tracing_module_categories.h"
+
+#include <stdio.h>
+
+// This file is for checking that multiple sets of trace event categories can be
+// combined into the same program.
+
+PERFETTO_TRACK_EVENT_STATIC_STORAGE();
+
+namespace tracing_module {
+
+void InitializeCategories() {
+  TrackEvent::Register();
+}
+
+void EmitTrackEvents() {
+  TRACE_EVENT_BEGIN("cat1", "DisabledEventFromModule");
+  TRACE_EVENT_END("cat1");
+  TRACE_EVENT_BEGIN("cat4", "DisabledEventFromModule");
+  TRACE_EVENT_END("cat4");
+  TRACE_EVENT_BEGIN("cat9", "DisabledEventFromModule");
+  TRACE_EVENT_END("cat9");
+  TRACE_EVENT_BEGIN("foo", "FooEventFromModule");
+  TRACE_EVENT_END("foo");
+}
+
+perfetto::internal::TrackEventIncrementalState* GetIncrementalState() {
+  perfetto::internal::TrackEventIncrementalState* state = nullptr;
+  TrackEvent::Trace([&state](TrackEvent::TraceContext ctx) {
+    state = ctx.GetIncrementalState();
+  });
+  return state;
+}
+
+void FunctionWithOneTrackEvent() {
+  TRACE_EVENT_BEGIN("cat1", "DisabledEventFromModule");
+  // 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
new file mode 100644
index 0000000..ce9af43
--- /dev/null
+++ b/src/tracing/test/tracing_module.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACING_TEST_TRACING_MODULE_H_
+#define SRC_TRACING_TEST_TRACING_MODULE_H_
+
+// Note: No non-client API header includes are allowed here.
+
+namespace perfetto {
+namespace internal {
+struct TrackEventIncrementalState;
+}  // namespace internal
+}  // namespace perfetto
+
+namespace tracing_module {
+
+void InitializeCategories();
+void EmitTrackEvents();
+void EmitTrackEvents2();
+perfetto::internal::TrackEventIncrementalState* GetIncrementalState();
+
+// This function is used to check the instruction size overhead of a single
+// track event.
+void FunctionWithOneTrackEvent();
+
+}  // namespace tracing_module
+
+#endif  // SRC_TRACING_TEST_TRACING_MODULE_H_
diff --git a/src/tracing/test/tracing_module2.cc b/src/tracing/test/tracing_module2.cc
new file mode 100644
index 0000000..e907d71
--- /dev/null
+++ b/src/tracing/test/tracing_module2.cc
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/tracing/test/tracing_module.h"
+
+#include "src/tracing/test/tracing_module_categories.h"
+
+#include <stdio.h>
+
+// This file checks that one track event category list can be shared by two
+// compilation units.
+
+namespace tracing_module {
+
+void EmitTrackEvents2() {
+  TRACE_EVENT_BEGIN("cat1", "DisabledEventFromModule2");
+  TRACE_EVENT_END("cat1");
+  TRACE_EVENT_BEGIN("cat4", "DisabledEventFromModule2");
+  TRACE_EVENT_END("cat4");
+  TRACE_EVENT_BEGIN("cat9", "DisabledEventFromModule2");
+  TRACE_EVENT_END("cat9");
+  TRACE_EVENT_BEGIN("foo", "FooEventFromModule2");
+  TRACE_EVENT_END("foo");
+}
+
+}  // namespace tracing_module
diff --git a/src/tracing/test/tracing_module_categories.h b/src/tracing/test/tracing_module_categories.h
new file mode 100644
index 0000000..f776894
--- /dev/null
+++ b/src/tracing/test/tracing_module_categories.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACING_TEST_TRACING_MODULE_CATEGORIES_H_
+#define SRC_TRACING_TEST_TRACING_MODULE_CATEGORIES_H_
+
+// This header defines the tracing categories (and track event data source) used
+// in the external tracing test module. These categories are distinct from the
+// ones defined in api_integrationtest.cc, but events for both sets of
+// categories can be written to the same trace writer.
+
+#define PERFETTO_TRACK_EVENT_NAMESPACE tracing_module
+
+#include "perfetto/tracing.h"
+
+PERFETTO_DEFINE_CATEGORIES(PERFETTO_CATEGORY(cat1),
+                           PERFETTO_CATEGORY(cat2),
+                           PERFETTO_CATEGORY(cat3),
+                           PERFETTO_CATEGORY(cat4),
+                           PERFETTO_CATEGORY(cat5),
+                           PERFETTO_CATEGORY(cat6),
+                           PERFETTO_CATEGORY(cat7),
+                           PERFETTO_CATEGORY(cat8),
+                           PERFETTO_CATEGORY(cat9),
+                           PERFETTO_CATEGORY(foo));
+
+#endif  // SRC_TRACING_TEST_TRACING_MODULE_CATEGORIES_H_
diff --git a/src/tracing/track_event_category_registry.cc b/src/tracing/track_event_category_registry.cc
new file mode 100644
index 0000000..e64fa3b
--- /dev/null
+++ b/src/tracing/track_event_category_registry.cc
@@ -0,0 +1,49 @@
+/*
+ * 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_event_category_registry.h"
+
+namespace perfetto {
+namespace internal {
+
+const TrackEventCategory* TrackEventCategoryRegistry::GetCategory(
+    size_t index) const {
+  PERFETTO_DCHECK(index < category_count_);
+  return &categories_[index];
+}
+
+void TrackEventCategoryRegistry::EnableCategoryForInstance(
+    size_t category_index,
+    uint32_t instance_index) const {
+  PERFETTO_DCHECK(instance_index < kMaxDataSourceInstances);
+  PERFETTO_DCHECK(category_index < category_count_);
+  // Matches the acquire_load in DataSource::Trace().
+  state_storage_[category_index].fetch_or(
+      static_cast<uint8_t>(1u << instance_index), std::memory_order_release);
+}
+
+void TrackEventCategoryRegistry::DisableCategoryForInstance(
+    size_t category_index,
+    uint32_t instance_index) const {
+  PERFETTO_DCHECK(instance_index < kMaxDataSourceInstances);
+  PERFETTO_DCHECK(category_index < category_count_);
+  // Matches the acquire_load in DataSource::Trace().
+  state_storage_[category_index].fetch_and(
+      static_cast<uint8_t>(~(1u << instance_index)), std::memory_order_release);
+}
+
+}  // namespace internal
+}  // namespace perfetto