tracing: Add support for track event timestamps using custom clocks

Make it possible to write track events using custom timestamps together
with a custom reference clock. For example:

  TRACE_EVENT_BEGIN("cat", "Name",
      perfetto::TraceTimestamp{
          perfetto::protos::BuiltinClock::BUILTIN_CLOCK_MONOTONIC,
          timestamp_ns});

You can also use a completely custom clock id, but will also need
to manually emit a clock snapshot mapping that clock to the builtin
ones.

Change-Id: I2635eef84dc62368e6e529f43ac5ad9e751c601a
diff --git a/CHANGELOG b/CHANGELOG
index 79050a5..67b6c6b 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -6,7 +6,7 @@
   UI:
     *
   SDK:
-    *
+    * Added support for writing track events using custom clock timestamps.
 
 
 v16.0 - 2021-06-01:
diff --git a/include/perfetto/tracing/internal/track_event_data_source.h b/include/perfetto/tracing/internal/track_event_data_source.h
index 12a5a85..9229153 100644
--- a/include/perfetto/tracing/internal/track_event_data_source.h
+++ b/include/perfetto/tracing/internal/track_event_data_source.h
@@ -35,11 +35,6 @@
 
 namespace perfetto {
 
-struct TraceTimestamp {
-  protos::pbzero::BuiltinClock clock_id;
-  uint64_t nanoseconds;
-};
-
 // This template provides a way to convert an abstract timestamp into the trace
 // clock timebase in nanoseconds. By specialising this template and defining
 // static ConvertTimestampToTraceTimeNs function in it the user can register
@@ -60,6 +55,15 @@
   }
 };
 
+// A pass-through implementation for the trace timestamp structure.
+template <>
+struct TraceTimestampTraits<TraceTimestamp> {
+  static inline TraceTimestamp ConvertTimestampToTraceTimeNs(
+      const TraceTimestamp& timestamp) {
+    return timestamp;
+  }
+};
+
 namespace internal {
 namespace {
 
@@ -440,19 +444,16 @@
             return;
           }
 
-          // TODO(skyostil): Support additional clock ids.
           TraceTimestamp trace_timestamp = ::perfetto::TraceTimestampTraits<
               TimestampType>::ConvertTimestampToTraceTimeNs(timestamp);
-          PERFETTO_DCHECK(trace_timestamp.clock_id ==
-                          TrackEventInternal::GetClockId());
 
           // Make sure incremental state is valid.
           TraceWriterBase* trace_writer = ctx.tls_inst_->trace_writer.get();
           TrackEventIncrementalState* incr_state = ctx.GetIncrementalState();
           if (incr_state->was_cleared) {
             incr_state->was_cleared = false;
-            TrackEventInternal::ResetIncrementalState(
-                trace_writer, trace_timestamp.nanoseconds);
+            TrackEventInternal::ResetIncrementalState(trace_writer,
+                                                      trace_timestamp);
           }
 
           // Write the track descriptor before any event on the track.
@@ -465,7 +466,7 @@
           {
             auto event_ctx = TrackEventInternal::WriteEvent(
                 trace_writer, incr_state, static_category, event_name, type,
-                trace_timestamp.nanoseconds);
+                trace_timestamp);
             // Write dynamic categories (except for events that don't require
             // categories). For counter events, the counter name (and optional
             // category) is stored as part of the track descriptor instead being
diff --git a/include/perfetto/tracing/internal/track_event_internal.h b/include/perfetto/tracing/internal/track_event_internal.h
index d293039..afd874c 100644
--- a/include/perfetto/tracing/internal/track_event_internal.h
+++ b/include/perfetto/tracing/internal/track_event_internal.h
@@ -32,6 +32,13 @@
 #include <unordered_map>
 
 namespace perfetto {
+
+// Represents a point in time for the clock specified by |clock_id|.
+struct TraceTimestamp {
+  protos::pbzero::BuiltinClock clock_id;
+  uint64_t nanoseconds;
+};
+
 class EventContext;
 class TrackEventSessionObserver;
 struct Category;
@@ -139,9 +146,9 @@
       const Category* category,
       const char* name,
       perfetto::protos::pbzero::TrackEvent::Type,
-      uint64_t timestamp = GetTimeNs());
+      TraceTimestamp timestamp = {GetClockId(), GetTimeNs()});
 
-  static void ResetIncrementalState(TraceWriterBase*, uint64_t timestamp);
+  static void ResetIncrementalState(TraceWriterBase*, TraceTimestamp);
 
   template <typename T>
   static void AddDebugAnnotation(perfetto::EventContext* event_ctx,
@@ -171,7 +178,7 @@
   static void WriteTrackDescriptor(const TrackType& track,
                                    TraceWriterBase* trace_writer) {
     TrackRegistry::Get()->SerializeTrack(
-        track, NewTracePacket(trace_writer, GetTimeNs()));
+        track, NewTracePacket(trace_writer, {GetClockId(), GetTimeNs()}));
   }
 
   // Get the current time in nanoseconds in the trace clock timebase.
@@ -195,7 +202,7 @@
  private:
   static protozero::MessageHandle<protos::pbzero::TracePacket> NewTracePacket(
       TraceWriterBase*,
-      uint64_t timestamp,
+      TraceTimestamp,
       uint32_t seq_flags =
           protos::pbzero::TracePacket::SEQ_NEEDS_INCREMENTAL_STATE);
   static protos::pbzero::DebugAnnotation* AddDebugAnnotation(
diff --git a/src/tracing/internal/track_event_internal.cc b/src/tracing/internal/track_event_internal.cc
index 736c312..5e4a154 100644
--- a/src/tracing/internal/track_event_internal.cc
+++ b/src/tracing/internal/track_event_internal.cc
@@ -309,7 +309,7 @@
 
 // static
 void TrackEventInternal::ResetIncrementalState(TraceWriterBase* trace_writer,
-                                               uint64_t timestamp) {
+                                               TraceTimestamp timestamp) {
   auto default_track = ThreadTrack::Current();
   {
     // Mark any incremental state before this point invalid. Also set up
@@ -337,14 +337,18 @@
 // static
 protozero::MessageHandle<protos::pbzero::TracePacket>
 TrackEventInternal::NewTracePacket(TraceWriterBase* trace_writer,
-                                   uint64_t timestamp,
+                                   TraceTimestamp 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 (GetClockId() != protos::pbzero::BUILTIN_CLOCK_BOOTTIME)
+  packet->set_timestamp(timestamp.nanoseconds);
+  if (timestamp.clock_id != GetClockId()) {
+    packet->set_timestamp_clock_id(static_cast<uint32_t>(timestamp.clock_id));
+  } else if (GetClockId() != protos::pbzero::BUILTIN_CLOCK_BOOTTIME) {
+    // TODO(skyostil): Stop emitting the clock id for the default trace clock
+    // for every event once the trace processor understands trace packet
+    // defaults.
     packet->set_timestamp_clock_id(GetClockId());
+  }
   packet->set_sequence_flags(seq_flags);
   return packet;
 }
@@ -356,7 +360,7 @@
     const Category* category,
     const char* name,
     perfetto::protos::pbzero::TrackEvent::Type type,
-    uint64_t timestamp) {
+    TraceTimestamp timestamp) {
   PERFETTO_DCHECK(g_main_thread);
   PERFETTO_DCHECK(!incr_state->was_cleared);
 
diff --git a/src/tracing/test/api_integrationtest.cc b/src/tracing/test/api_integrationtest.cc
index 061e2fe..ed08a17 100644
--- a/src/tracing/test/api_integrationtest.cc
+++ b/src/tracing/test/api_integrationtest.cc
@@ -1473,6 +1473,65 @@
   perfetto::TrackEvent::EraseTrackDescriptor(track);
 }
 
+TEST_P(PerfettoApiTest, TrackEventCustomTimestampClock) {
+  // Create a new trace session.
+  auto* tracing_session = NewTraceWithCategories({"foo"});
+  tracing_session->get()->StartBlocking();
+
+  const perfetto::protos::pbzero::BuiltinClock kMyClockId =
+      static_cast<perfetto::protos::pbzero::BuiltinClock>(700);
+  const uint64_t kTimestamp = 12345678;
+
+  // First emit a clock snapshot that maps our custom clock to regular trace
+  // time. Note that the clock snapshot should come before any events
+  // referencing that clock.
+  perfetto::TrackEvent::Trace([](perfetto::TrackEvent::TraceContext ctx) {
+    auto packet = ctx.NewTracePacket();
+    packet->set_timestamp_clock_id(perfetto::TrackEvent::GetTraceClockId());
+    packet->set_timestamp(perfetto::TrackEvent::GetTraceTimeNs());
+    auto* clock_snapshot = packet->set_clock_snapshot();
+    // First set the reference clock, i.e., the default trace clock in this
+    // case.
+    auto* clock = clock_snapshot->add_clocks();
+    clock->set_clock_id(perfetto::TrackEvent::GetTraceClockId());
+    clock->set_timestamp(perfetto::TrackEvent::GetTraceTimeNs());
+    // Then set the value of our reference clock at the same point in time. We
+    // pretend our clock is one second behind trace time.
+    clock = clock_snapshot->add_clocks();
+    clock->set_clock_id(kMyClockId);
+    clock->set_timestamp(kTimestamp + 1000000000ull);
+  });
+
+  // Next emit a trace event with a custom timestamp and a custom clock.
+  TRACE_EVENT_INSTANT("foo", "EventWithCustomTime",
+                      perfetto::TraceTimestamp{kMyClockId, kTimestamp});
+  TRACE_EVENT_INSTANT("foo", "EventWithNormalTime");
+
+  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 both the clock id and the timestamp got written together with
+  // the packet. Note that we don't check the actual clock sync behavior here
+  // since that happens in the Trace Processor instead.
+  bool found_clock_snapshot = false;
+  bool found_event = false;
+  for (const auto& packet : trace.packet()) {
+    if (packet.has_clock_snapshot())
+      found_clock_snapshot = true;
+    if (!packet.has_track_event() || packet.timestamp() != kTimestamp)
+      continue;
+    found_event = true;
+    EXPECT_EQ(static_cast<uint32_t>(kMyClockId), packet.timestamp_clock_id());
+    EXPECT_EQ(kTimestamp, packet.timestamp());
+  }
+  EXPECT_TRUE(found_clock_snapshot);
+  EXPECT_TRUE(found_event);
+}
+
 TEST_P(PerfettoApiTest, LegacyEventWithThreadOverride) {
   // Create a new trace session.
   auto* tracing_session = NewTraceWithCategories({"cat"});