[VM/Timeline] Store an array of flow IDs in each dart::TimelineEvent
instead of just one ID

Sometimes the Flutter Engine is associating events with multiple flows,
this change is needed to allow events to be associated with multiple
flows in a Perfetto-format trace.

TEST=Checked the flow events reported through dart:developer still
looked correct when retrieved in Perfetto traces.

Change-Id: I2901ffde5e8b984abb1e924e014722bb0568f6d3
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/305801
Reviewed-by: Ben Konyi <bkonyi@google.com>
Commit-Queue: Derek Xu <derekx@google.com>
diff --git a/runtime/lib/timeline.cc b/runtime/lib/timeline.cc
index 64e63d6..4592e50 100644
--- a/runtime/lib/timeline.cc
+++ b/runtime/lib/timeline.cc
@@ -55,8 +55,16 @@
     return Object::null();
   }
 
+  std::unique_ptr<const int64_t[]> flow_ids;
+  if (flow_id.AsInt64Value() != TimelineEvent::kNoFlowId) {
+    int64_t* flow_ids_internal = new int64_t[1];
+    flow_ids_internal[0] = flow_id.AsInt64Value();
+    flow_ids = std::unique_ptr<const int64_t[]>(flow_ids_internal);
+  }
+  intptr_t flow_id_count =
+      flow_id.AsInt64Value() == TimelineEvent::kNoFlowId ? 0 : 1;
   DartTimelineEventHelpers::ReportTaskEvent(
-      event, id.AsInt64Value(), flow_id.AsInt64Value(), type.Value(),
+      event, id.AsInt64Value(), flow_id_count, flow_ids, type.Value(),
       name.ToMallocCString(), args.ToMallocCString());
 #endif  // SUPPORT_TIMELINE
   return Object::null();
diff --git a/runtime/vm/dart_api_impl.cc b/runtime/vm/dart_api_impl.cc
index 55b98e9..7c42f26 100644
--- a/runtime/vm/dart_api_impl.cc
+++ b/runtime/vm/dart_api_impl.cc
@@ -6443,8 +6443,7 @@
         // TODO(derekx): Dart_TimelineEvent() needs to be updated so that arrows
         // corresponding to flow events reported by embedders get included in
         // Perfetto traces.
-        event->Begin(label, timestamp1_or_async_id,
-                     /*flow_id=*/TimelineEvent::kNoFlowId, timestamp0);
+        event->Begin(label, timestamp1_or_async_id, timestamp0);
         break;
       case Dart_Timeline_Event_End:
         event->End(label, timestamp1_or_async_id, timestamp0);
diff --git a/runtime/vm/timeline.cc b/runtime/vm/timeline.cc
index 94da8fc..1f63bef 100644
--- a/runtime/vm/timeline.cc
+++ b/runtime/vm/timeline.cc
@@ -505,7 +505,8 @@
 TimelineEvent::TimelineEvent()
     : timestamp0_(0),
       timestamp1_(0),
-      flow_id_(TimelineEvent::kNoFlowId),
+      flow_id_count_(0),
+      flow_ids_(),
       state_(0),
       label_(nullptr),
       stream_(nullptr),
@@ -585,14 +586,12 @@
 
 void TimelineEvent::Begin(const char* label,
                           int64_t id,
-                          int64_t flow_id,
                           int64_t micros) {
   Init(kBegin, label);
   set_timestamp0(micros);
   // Overload timestamp1_ with the task id. This is required for the MacOS
   // recorder to work.
   set_timestamp1(id);
-  set_flow_id(flow_id);
 }
 
 void TimelineEvent::End(const char* label, int64_t id, int64_t micros) {
@@ -670,7 +669,8 @@
   state_ = 0;
   timestamp0_ = 0;
   timestamp1_ = 0;
-  flow_id_ = TimelineEvent::kNoFlowId;
+  flow_id_count_ = 0;
+  flow_ids_.reset();
   OSThread* os_thread = OSThread::Current();
   ASSERT(os_thread != nullptr);
   thread_ = os_thread->trace_id();
@@ -841,12 +841,12 @@
   AddBeginAndInstantEventCommonFields(track_event, event);
   track_event->set_type(
       perfetto::protos::pbzero::TrackEvent::Type::TYPE_SLICE_BEGIN);
-  if (event.HasFlowId()) {
+  for (intptr_t i = 0; i < event.flow_id_count(); ++i) {
     // TODO(derekx): |TrackEvent|s have a |terminating_flow_ids| field that we
     // aren't able to populate right now because we aren't keeping track of
     // terminating flow IDs in |TimelineEvent|. I'm not even sure if using that
     // field will provide any benefit though.
-    track_event->add_flow_ids(event.flow_id());
+    track_event->add_flow_ids(event.FlowIds()[i]);
   }
 }
 
@@ -985,10 +985,6 @@
   return timestamp1_ - timestamp0_;
 }
 
-bool TimelineEvent::HasFlowId() const {
-  return flow_id_ != TimelineEvent::kNoFlowId;
-}
-
 bool TimelineEvent::HasIsolateId() const {
   return isolate_id_ != ILLEGAL_ISOLATE_ID;
 }
@@ -1240,7 +1236,7 @@
   }
   ASSERT(event != nullptr);
   // Emit a begin event.
-  event->Begin(label(), id(), /*flow_id=*/TimelineEvent::kNoFlowId);
+  event->Begin(label(), id());
   event->Complete();
 }
 
@@ -2416,12 +2412,14 @@
 #endif
 }
 
-void DartTimelineEventHelpers::ReportTaskEvent(TimelineEvent* event,
-                                               int64_t id,
-                                               int64_t flow_id,
-                                               intptr_t type,
-                                               char* name,
-                                               char* args) {
+void DartTimelineEventHelpers::ReportTaskEvent(
+    TimelineEvent* event,
+    int64_t id,
+    intptr_t flow_id_count,
+    std::unique_ptr<const int64_t[]>& flow_ids,
+    intptr_t type,
+    char* name,
+    char* args) {
   const int64_t start = OS::GetCurrentMonotonicMicrosForTimeline();
   switch (static_cast<TimelineEvent::EventType>(type)) {
     case TimelineEvent::kAsyncInstant:
@@ -2434,7 +2432,7 @@
       event->AsyncEnd(name, id, start);
       break;
     case TimelineEvent::kBegin:
-      event->Begin(name, id, flow_id, start);
+      event->Begin(name, id, start);
       break;
     case TimelineEvent::kEnd:
       event->End(name, id, start);
@@ -2454,6 +2452,14 @@
     default:
       UNREACHABLE();
   }
+  if (flow_id_count > 0) {
+    ASSERT(type == Dart_Timeline_Event_Begin ||
+           type == Dart_Timeline_Event_Instant ||
+           type == Dart_Timeline_Event_Async_Begin ||
+           type == Dart_Timeline_Event_Async_Instant);
+
+    event->SetFlowIds(flow_id_count, flow_ids);
+  }
   event->set_owns_label(true);
   event->CompleteWithPreSerializedArgs(args);
 }
diff --git a/runtime/vm/timeline.h b/runtime/vm/timeline.h
index 7bb39f5..6650599 100644
--- a/runtime/vm/timeline.h
+++ b/runtime/vm/timeline.h
@@ -373,7 +373,6 @@
 
   void Begin(const char* label,
              int64_t id,
-             int64_t flow_id,
              int64_t micros = OS::GetCurrentMonotonicMicrosForTimeline());
 
   void End(const char* label,
@@ -441,8 +440,13 @@
   int64_t timestamp0() const { return timestamp0_; }
   int64_t timestamp1() const { return timestamp1_; }
 
-  bool HasFlowId() const;
-  int64_t flow_id() const { return flow_id_; }
+  void SetFlowIds(intptr_t flow_id_count,
+                  std::unique_ptr<const int64_t[]>& flow_ids) {
+    flow_id_count_ = flow_id_count;
+    flow_ids_.swap(flow_ids);
+  }
+  intptr_t flow_id_count() const { return flow_id_count_; }
+  const int64_t* FlowIds() const { return flow_ids_.get(); }
 
   bool HasIsolateId() const;
   bool HasIsolateGroupId() const;
@@ -563,11 +567,6 @@
     timestamp1_ = value;
   }
 
-  void set_flow_id(int64_t flow_id) {
-    ASSERT(flow_id_ == TimelineEvent::kNoFlowId);
-    flow_id_ = flow_id;
-  }
-
   void set_pre_serialized_args(bool pre_serialized_args) {
     state_ = PreSerializedArgsBit::update(pre_serialized_args, state_);
   }
@@ -588,12 +587,13 @@
 
   int64_t timestamp0_;
   int64_t timestamp1_;
+  intptr_t flow_id_count_;
   // This field is only used by the Perfetto recorders, because Perfetto's trace
   // format handles flow events by processing flow IDs attached to
   // |TimelineEvent::kBegin| events. Other recorders handle flow events by
   // processing events of type TimelineEvent::kFlowBegin|,
   // |TimelineEvent::kFlowStep|, and |TimelineEvent::kFlowEnd|.
-  int64_t flow_id_;
+  std::unique_ptr<const int64_t[]> flow_ids_;
   TimelineEventArguments arguments_;
   uword state_;
   const char* label_;
@@ -1293,7 +1293,8 @@
  public:
   static void ReportTaskEvent(TimelineEvent* event,
                               int64_t id,
-                              int64_t flow_id,
+                              intptr_t flow_id_count,
+                              std::unique_ptr<const int64_t[]>& flow_ids,
                               intptr_t type,
                               char* name,
                               char* args);
diff --git a/runtime/vm/timeline_test.cc b/runtime/vm/timeline_test.cc
index 828775c..1050073 100644
--- a/runtime/vm/timeline_test.cc
+++ b/runtime/vm/timeline_test.cc
@@ -85,7 +85,7 @@
     ASSERT(start >= 0);
     TimelineEvent* event = recorder->StartEvent();
     ASSERT(event != nullptr);
-    event->Begin(label, /*id=*/-1, /*flow_id=*/TimelineEvent::kNoFlowId, start);
+    event->Begin(label, /*id=*/-1, start);
     event->Complete();
   }