processor: Fix ordering of async begin/end events in JSON export

Catapult expects begin/end events to be ordered correctly, i.e.
children's begin/end events before the parent's end.

This adds buffering logic to ExportJson to accomplish this.

Bug: 130786269
Change-Id: If149512004c1e6ec514bd3493770abfc428dd434
diff --git a/src/trace_processor/export_json.cc b/src/trace_processor/export_json.cc
index 4e08f1e..de9d02c 100644
--- a/src/trace_processor/export_json.cc
+++ b/src/trace_processor/export_json.cc
@@ -26,8 +26,10 @@
 #include <json/value.h>
 #include <json/writer.h>
 #include <stdio.h>
+
 #include <cstring>
-#include <vector>
+#include <deque>
+#include <limits>
 
 #include "perfetto/ext/base/string_splitter.h"
 #include "src/trace_processor/metadata.h"
@@ -107,33 +109,26 @@
     if (label_filter_ && !label_filter_("traceEvents"))
       return;
 
-    if (!first_event_)
-      output_->AppendString(",\n");
+    // Pop end events with smaller or equal timestamps.
+    PopEndEvents(event["ts"].asInt64());
 
-    Json::FastWriter writer;
-    writer.omitEndingLineFeed();
+    DoWriteEvent(event);
+  }
 
-    ArgumentNameFilterPredicate argument_name_filter;
-    bool strip_args =
-        argument_filter_ &&
-        !argument_filter_(event["cat"].asCString(), event["name"].asCString(),
-                          &argument_name_filter);
-    if ((strip_args || argument_name_filter) && event.isMember("args")) {
-      Json::Value event_copy = event;
-      if (strip_args) {
-        event_copy["args"] = kStrippedArgument;
-      } else {
-        auto& args = event_copy["args"];
-        for (const auto& member : event["args"].getMemberNames()) {
-          if (!argument_name_filter(member.c_str()))
-            args[member] = kStrippedArgument;
-        }
-      }
-      output_->AppendString(writer.write(event_copy));
-    } else {
-      output_->AppendString(writer.write(event));
-    }
-    first_event_ = false;
+  void PushEndEvent(const Json::Value& event) {
+    if (label_filter_ && !label_filter_("traceEvents"))
+      return;
+
+    // Pop any end events that end before the new one.
+    PopEndEvents(event["ts"].asInt64() - 1);
+
+    // Catapult doesn't handle out-of-order begin/end events well, especially
+    // when their timestamps are the same, but their order is incorrect. Since
+    // our events are sorted by begin timestamp, we only have to reorder end
+    // events. We do this by buffering them into a stack, so that both begin &
+    // end events of potential child events have been emitted before we emit the
+    // end of a parent event.
+    end_events_.push_back(event);
   }
 
   void WriteMetadataEvent(const char* metadata_type,
@@ -226,6 +221,8 @@
   }
 
   void WriteFooter() {
+    PopEndEvents(std::numeric_limits<int64_t>::max());
+
     // Filter metadata entries.
     if (metadata_filter_) {
       for (const auto& member : metadata_.getMemberNames()) {
@@ -266,6 +263,46 @@
       output_->AppendString("}");
   }
 
+  void DoWriteEvent(const Json::Value& event) {
+    if (!first_event_)
+      output_->AppendString(",\n");
+
+    Json::FastWriter writer;
+    writer.omitEndingLineFeed();
+
+    ArgumentNameFilterPredicate argument_name_filter;
+    bool strip_args =
+        argument_filter_ &&
+        !argument_filter_(event["cat"].asCString(), event["name"].asCString(),
+                          &argument_name_filter);
+    if ((strip_args || argument_name_filter) && event.isMember("args")) {
+      Json::Value event_copy = event;
+      if (strip_args) {
+        event_copy["args"] = kStrippedArgument;
+      } else {
+        auto& args = event_copy["args"];
+        for (const auto& member : event["args"].getMemberNames()) {
+          if (!argument_name_filter(member.c_str()))
+            args[member] = kStrippedArgument;
+        }
+      }
+      output_->AppendString(writer.write(event_copy));
+    } else {
+      output_->AppendString(writer.write(event));
+    }
+    first_event_ = false;
+  }
+
+  void PopEndEvents(int64_t max_ts) {
+    while (!end_events_.empty()) {
+      int64_t ts = end_events_.back()["ts"].asInt64();
+      if (ts > max_ts)
+        break;
+      DoWriteEvent(end_events_.back());
+      end_events_.pop_back();
+    }
+  }
+
   OutputWriter* output_;
   ArgumentFilterPredicate argument_filter_;
   MetadataFilterPredicate metadata_filter_;
@@ -275,6 +312,7 @@
   Json::Value metadata_;
   std::string system_trace_data_;
   std::string user_trace_data_;
+  std::deque<Json::Value> end_events_;
 };
 
 std::string PrintUint64(uint64_t x) {
@@ -664,7 +702,7 @@
                 (thread_instruction_count + thread_instruction_delta));
           }
           event["args"].clear();
-          writer->WriteCommonEvent(event);
+          writer->PushEndEvent(event);
         }
       }
     } else {
diff --git a/src/trace_processor/export_json_unittest.cc b/src/trace_processor/export_json_unittest.cc
index c3bfabc..a14f789 100644
--- a/src/trace_processor/export_json_unittest.cc
+++ b/src/trace_processor/export_json_unittest.cc
@@ -780,18 +780,20 @@
   EXPECT_EQ(event["name"].asString(), kName);
 }
 
-TEST_F(ExportJsonTest, AsyncEvent) {
+TEST_F(ExportJsonTest, AsyncEvents) {
   const int64_t kTimestamp = 10000000;
   const int64_t kDuration = 100000;
   const int64_t kProcessID = 100;
   const char* kCategory = "cat";
   const char* kName = "name";
+  const char* kName2 = "name2";
   const char* kArgName = "arg_name";
   const int kArgValue = 123;
 
   UniquePid upid = context_.storage->AddEmptyProcess(kProcessID);
   StringId cat_id = context_.storage->InternString(base::StringView(kCategory));
   StringId name_id = context_.storage->InternString(base::StringView(kName));
+  StringId name2_id = context_.storage->InternString(base::StringView(kName2));
 
   constexpr int64_t kSourceId = 235;
   TrackId track = context_.track_tracker->InternLegacyChromeAsyncTrack(
@@ -810,6 +812,10 @@
   ArgSetId args = context_.storage->mutable_args()->AddArgSet({arg}, 0, 1);
   context_.storage->mutable_slice_table()->mutable_arg_set_id()->Set(0, args);
 
+  // Child event with same timestamps as first one.
+  context_.storage->mutable_slice_table()->Insert(
+      {kTimestamp, kDuration, track.value, cat_id, name2_id, 0, 0, 0});
+
   base::TempFile temp_file = base::TempFile::Create();
   FILE* output = fopen(temp_file.path().c_str(), "w+");
   util::Status status = ExportJson(context_.storage.get(), output);
@@ -817,30 +823,54 @@
   EXPECT_TRUE(status.ok());
 
   Json::Value result = ToJsonValue(ReadFile(output));
-  EXPECT_EQ(result["traceEvents"].size(), 2u);
+  EXPECT_EQ(result["traceEvents"].size(), 4u);
 
-  Json::Value begin_event = result["traceEvents"][0];
-  EXPECT_EQ(begin_event["ph"].asString(), "b");
-  EXPECT_EQ(begin_event["ts"].asInt64(), kTimestamp / 1000);
-  EXPECT_EQ(begin_event["pid"].asInt64(), kProcessID);
-  EXPECT_EQ(begin_event["id2"]["local"].asString(), "0xeb");
-  EXPECT_EQ(begin_event["cat"].asString(), kCategory);
-  EXPECT_EQ(begin_event["name"].asString(), kName);
-  EXPECT_EQ(begin_event["args"][kArgName].asInt(), kArgValue);
-  EXPECT_FALSE(begin_event.isMember("tts"));
-  EXPECT_FALSE(begin_event.isMember("use_async_tts"));
+  Json::Value begin_event1 = result["traceEvents"][0];
+  EXPECT_EQ(begin_event1["ph"].asString(), "b");
+  EXPECT_EQ(begin_event1["ts"].asInt64(), kTimestamp / 1000);
+  EXPECT_EQ(begin_event1["pid"].asInt64(), kProcessID);
+  EXPECT_EQ(begin_event1["id2"]["local"].asString(), "0xeb");
+  EXPECT_EQ(begin_event1["cat"].asString(), kCategory);
+  EXPECT_EQ(begin_event1["name"].asString(), kName);
+  EXPECT_EQ(begin_event1["args"][kArgName].asInt(), kArgValue);
+  EXPECT_FALSE(begin_event1.isMember("tts"));
+  EXPECT_FALSE(begin_event1.isMember("use_async_tts"));
 
-  Json::Value end_event = result["traceEvents"][1];
-  EXPECT_EQ(end_event["ph"].asString(), "e");
-  EXPECT_EQ(end_event["ts"].asInt64(), (kTimestamp + kDuration) / 1000);
-  EXPECT_EQ(end_event["pid"].asInt64(), kProcessID);
-  EXPECT_EQ(end_event["id2"]["local"].asString(), "0xeb");
-  EXPECT_EQ(end_event["cat"].asString(), kCategory);
-  EXPECT_EQ(end_event["name"].asString(), kName);
-  EXPECT_TRUE(end_event["args"].isObject());
-  EXPECT_EQ(end_event["args"].size(), 0u);
-  EXPECT_FALSE(end_event.isMember("tts"));
-  EXPECT_FALSE(end_event.isMember("use_async_tts"));
+  Json::Value begin_event2 = result["traceEvents"][1];
+  EXPECT_EQ(begin_event2["ph"].asString(), "b");
+  EXPECT_EQ(begin_event2["ts"].asInt64(), kTimestamp / 1000);
+  EXPECT_EQ(begin_event2["pid"].asInt64(), kProcessID);
+  EXPECT_EQ(begin_event2["id2"]["local"].asString(), "0xeb");
+  EXPECT_EQ(begin_event2["cat"].asString(), kCategory);
+  EXPECT_EQ(begin_event2["name"].asString(), kName2);
+  EXPECT_TRUE(begin_event2["args"].isObject());
+  EXPECT_EQ(begin_event2["args"].size(), 0u);
+  EXPECT_FALSE(begin_event2.isMember("tts"));
+  EXPECT_FALSE(begin_event2.isMember("use_async_tts"));
+
+  Json::Value end_event2 = result["traceEvents"][3];
+  EXPECT_EQ(end_event2["ph"].asString(), "e");
+  EXPECT_EQ(end_event2["ts"].asInt64(), (kTimestamp + kDuration) / 1000);
+  EXPECT_EQ(end_event2["pid"].asInt64(), kProcessID);
+  EXPECT_EQ(end_event2["id2"]["local"].asString(), "0xeb");
+  EXPECT_EQ(end_event2["cat"].asString(), kCategory);
+  EXPECT_EQ(end_event2["name"].asString(), kName);
+  EXPECT_TRUE(end_event2["args"].isObject());
+  EXPECT_EQ(end_event2["args"].size(), 0u);
+  EXPECT_FALSE(end_event2.isMember("tts"));
+  EXPECT_FALSE(end_event2.isMember("use_async_tts"));
+
+  Json::Value end_event1 = result["traceEvents"][3];
+  EXPECT_EQ(end_event1["ph"].asString(), "e");
+  EXPECT_EQ(end_event1["ts"].asInt64(), (kTimestamp + kDuration) / 1000);
+  EXPECT_EQ(end_event1["pid"].asInt64(), kProcessID);
+  EXPECT_EQ(end_event1["id2"]["local"].asString(), "0xeb");
+  EXPECT_EQ(end_event1["cat"].asString(), kCategory);
+  EXPECT_EQ(end_event1["name"].asString(), kName);
+  EXPECT_TRUE(end_event1["args"].isObject());
+  EXPECT_EQ(end_event1["args"].size(), 0u);
+  EXPECT_FALSE(end_event1.isMember("tts"));
+  EXPECT_FALSE(end_event1.isMember("use_async_tts"));
 }
 
 TEST_F(ExportJsonTest, AsyncEventWithThreadTimestamp) {