Trace processor: export args in json format

Rebuilds arg set for each event from args table and renames some fields
to conform to legacy format.

Bug: 130786269
Change-Id: Ic3161d6cb8a5f7c140e3f3595e789625ee53c171
diff --git a/src/trace_processor/export_json.cc b/src/trace_processor/export_json.cc
index ef0870e..5ea3f4b 100644
--- a/src/trace_processor/export_json.cc
+++ b/src/trace_processor/export_json.cc
@@ -14,13 +14,15 @@
  * limitations under the License.
  */
 
-#include "src/trace_processor/export_json.h"
-#include "src/trace_processor/metadata.h"
-
+#include <inttypes.h>
+#include <json/reader.h>
 #include <json/value.h>
 #include <json/writer.h>
 #include <stdio.h>
 
+#include "perfetto/ext/base/string_splitter.h"
+#include "src/trace_processor/export_json.h"
+#include "src/trace_processor/metadata.h"
 #include "src/trace_processor/trace_storage.h"
 
 namespace {
@@ -38,7 +40,8 @@
                   const char* cat,
                   const char* name,
                   uint32_t tid,
-                  uint32_t pid) {
+                  uint32_t pid,
+                  const Json::Value& args) {
     if (!first_event_) {
       fputs(",", output_);
     }
@@ -51,6 +54,7 @@
     value["pid"] = Json::UInt(pid);
     value["ts"] = Json::Int64(begin_ts_us);
     value["dur"] = Json::Int64(duration_us);
+    value["args"] = args;
     fputs(writer.write(value).c_str(), output_);
     first_event_ = false;
   }
@@ -111,13 +115,125 @@
   Json::Value metadata_;
 };
 
+std::string PrintUint64(uint64_t x) {
+  char hex_str[19];
+  sprintf(hex_str, "0x%" PRIx64, x);
+  return hex_str;
+}
+
 }  // anonymous namespace
 
 namespace perfetto {
 namespace trace_processor {
 namespace json {
 
+namespace {
+
+class ArgsBuilder {
+ public:
+  explicit ArgsBuilder(const TraceStorage* storage)
+      : string_pool_(&storage->string_pool()) {
+    const TraceStorage::Args& args = storage->args();
+    Json::Value empty_value(Json::objectValue);
+    if (args.args_count() == 0) {
+      args_sets_.resize(1, empty_value);
+      return;
+    }
+    args_sets_.resize(args.set_ids().back() + 1, empty_value);
+    for (size_t i = 0; i < args.args_count(); ++i) {
+      ArgSetId set_id = args.set_ids()[i];
+      const char* key = string_pool_->Get(args.keys()[i]).c_str();
+      Variadic value = args.arg_values()[i];
+      AppendArg(set_id, key, VariadicToJson(value));
+    }
+    PostprocessArgs();
+  }
+
+  const Json::Value& GetArgs(ArgSetId set_id) const {
+    return args_sets_[set_id];
+  }
+
+ private:
+  Json::Value VariadicToJson(Variadic variadic) {
+    switch (variadic.type) {
+      case Variadic::kInt:
+        return Json::Int64(variadic.int_value);
+      case Variadic::kUint:
+        return Json::UInt64(variadic.uint_value);
+      case Variadic::kString:
+        return string_pool_->Get(variadic.string_value).c_str();
+      case Variadic::kReal:
+        return variadic.real_value;
+      case Variadic::kPointer:
+        return PrintUint64(variadic.pointer_value);
+      case Variadic::kBool:
+        return variadic.bool_value;
+      case Variadic::kJson:
+        Json::Reader reader;
+        Json::Value result;
+        reader.parse(string_pool_->Get(variadic.string_value).c_str(), result);
+        return result;
+    }
+  }
+
+  void AppendArg(ArgSetId set_id,
+                 const std::string& key,
+                 const Json::Value& value) {
+    Json::Value* target = &args_sets_[set_id];
+    for (base::StringSplitter parts(key, '.'); parts.Next();) {
+      std::string key_part = parts.cur_token();
+      size_t bracketpos = key_part.find('[');
+      if (bracketpos == key_part.npos) {  // A single item
+        target = &(*target)[key_part];
+      } else {  // A list item
+        target = &(*target)[key_part.substr(0, bracketpos)];
+        while (bracketpos != key_part.npos) {
+          std::string index = key_part.substr(
+              bracketpos + 1, key_part.find(']', bracketpos) - bracketpos - 1);
+          target = &(*target)[stoi(index)];
+          bracketpos = key_part.find('[', bracketpos + 1);
+        }
+      }
+    }
+    *target = value;
+  }
+
+  void PostprocessArgs() {
+    for (Json::Value& args : args_sets_) {
+      // Move all fields from "debug" key to upper level.
+      if (args.isMember("debug")) {
+        Json::Value debug = args.removeMember("debug");
+        for (const auto& member : debug.getMemberNames()) {
+          args[member] = debug[member];
+        }
+      }
+
+      // Rename source fields.
+      if (args.isMember("task")) {
+        if (args["task"].isMember("posted_from")) {
+          Json::Value posted_from = args["task"].removeMember("posted_from");
+          if (posted_from.isMember("function_name")) {
+            args["src_func"] = posted_from["function_name"];
+            args["src_file"] = posted_from["file_name"];
+          } else if (posted_from.isMember("file_name")) {
+            args["src"] = posted_from["file_name"];
+          }
+        }
+        if (args["task"].empty()) {
+          args.removeMember("task");
+        }
+      }
+    }
+  }
+
+  const StringPool* string_pool_;
+  std::vector<Json::Value> args_sets_;
+};
+
+}  // anonymous namespace
+
 ResultCode ExportJson(const TraceStorage* storage, FILE* output) {
+  ArgsBuilder args_builder(storage);
   const StringPool& string_pool = storage->string_pool();
 
   TraceFormatWriter writer(output);
@@ -145,6 +261,7 @@
   const auto& slices = storage->nestable_slices();
   for (size_t i = 0; i < slices.slice_count(); ++i) {
     if (slices.types()[i] == RefType::kRefUtid) {
+      ArgSetId arg_set_id = slices.arg_set_ids()[i];
       int64_t begin_ts_us = slices.start_ns()[i] / 1000;
       int64_t duration_us = slices.durations()[i] / 1000;
       const char* cat = string_pool.Get(slices.cats()[i]).c_str();
@@ -154,7 +271,8 @@
       auto thread = storage->GetThread(utid);
       uint32_t pid = thread.upid ? storage->GetProcess(*thread.upid).pid : 0;
 
-      writer.WriteSlice(begin_ts_us, duration_us, cat, name, thread.tid, pid);
+      writer.WriteSlice(begin_ts_us, duration_us, cat, name, thread.tid, pid,
+                        args_builder.GetArgs(arg_set_id));
     } else {
       return kResultWrongRefType;
     }
diff --git a/src/trace_processor/export_json_unittest.cc b/src/trace_processor/export_json_unittest.cc
index 185542a..56336db 100644
--- a/src/trace_processor/export_json_unittest.cc
+++ b/src/trace_processor/export_json_unittest.cc
@@ -24,6 +24,8 @@
 #include <json/reader.h>
 #include <json/value.h>
 
+#include <limits>
+
 namespace perfetto {
 namespace trace_processor {
 namespace json {
@@ -205,6 +207,265 @@
   EXPECT_EQ(telemetry_metadata["hadFailures"][0].asBool(), kHadFailures);
 }
 
+TEST(ExportJsonTest, StorageWithArgs) {
+  const char* kCategory = "cat";
+  const char* kName = "name";
+  const char* kSrc = "source_file.cc";
+
+  TraceStorage storage;
+  UniqueTid utid = storage.AddEmptyThread(0);
+  StringId cat_id = storage.InternString(base::StringView(kCategory));
+  StringId name_id = storage.InternString(base::StringView(kName));
+  storage.mutable_nestable_slices()->AddSlice(0, 0, utid, RefType::kRefUtid,
+                                              cat_id, name_id, 0, 0, 0);
+
+  StringId arg_key_id =
+      storage.InternString(base::StringView("task.posted_from.file_name"));
+  StringId arg_value_id = storage.InternString(base::StringView(kSrc));
+  TraceStorage::Args::Arg arg;
+  arg.flat_key = arg_key_id;
+  arg.key = arg_key_id;
+  arg.value = Variadic::String(arg_value_id);
+  storage.mutable_args()->AddArgSet({arg}, 0, 1);
+  storage.mutable_nestable_slices()->set_arg_set_id(0, 1);
+
+  base::TempFile temp_file = base::TempFile::Create();
+  FILE* output = fopen(temp_file.path().c_str(), "w+");
+  int code = ExportJson(&storage, output);
+
+  EXPECT_EQ(code, kResultOk);
+
+  Json::Reader reader;
+  Json::Value result;
+  EXPECT_TRUE(reader.parse(ReadFile(output), result));
+  EXPECT_EQ(result["traceEvents"].size(), 1u);
+
+  Json::Value event = result["traceEvents"][0];
+  EXPECT_EQ(event["cat"].asString(), kCategory);
+  EXPECT_EQ(event["name"].asString(), kName);
+  EXPECT_EQ(event["args"]["src"].asString(), kSrc);
+}
+
+TEST(ExportJsonTest, StorageWithListArgs) {
+  const char* kCategory = "cat";
+  const char* kName = "name";
+  double kValues[] = {1.234, 2.345};
+
+  TraceStorage storage;
+  UniqueTid utid = storage.AddEmptyThread(0);
+  StringId cat_id = storage.InternString(base::StringView(kCategory));
+  StringId name_id = storage.InternString(base::StringView(kName));
+  storage.mutable_nestable_slices()->AddSlice(0, 0, utid, RefType::kRefUtid,
+                                              cat_id, name_id, 0, 0, 0);
+
+  StringId arg_flat_key_id =
+      storage.InternString(base::StringView("debug.draw_duration_ms"));
+  StringId arg_key0_id =
+      storage.InternString(base::StringView("debug.draw_duration_ms[0]"));
+  StringId arg_key1_id =
+      storage.InternString(base::StringView("debug.draw_duration_ms[1]"));
+  TraceStorage::Args::Arg arg0;
+  arg0.flat_key = arg_flat_key_id;
+  arg0.key = arg_key0_id;
+  arg0.value = Variadic::Real(kValues[0]);
+  TraceStorage::Args::Arg arg1;
+  arg1.flat_key = arg_flat_key_id;
+  arg1.key = arg_key1_id;
+  arg1.value = Variadic::Real(kValues[1]);
+  storage.mutable_args()->AddArgSet({arg0, arg1}, 0, 2);
+  storage.mutable_nestable_slices()->set_arg_set_id(0, 1);
+
+  base::TempFile temp_file = base::TempFile::Create();
+  FILE* output = fopen(temp_file.path().c_str(), "w+");
+  int code = ExportJson(&storage, output);
+
+  EXPECT_EQ(code, kResultOk);
+
+  Json::Reader reader;
+  Json::Value result;
+  EXPECT_TRUE(reader.parse(ReadFile(output), result));
+  EXPECT_EQ(result["traceEvents"].size(), 1u);
+
+  Json::Value event = result["traceEvents"][0];
+  EXPECT_EQ(event["cat"].asString(), kCategory);
+  EXPECT_EQ(event["name"].asString(), kName);
+  EXPECT_EQ(event["args"]["draw_duration_ms"].size(), 2);
+  EXPECT_EQ(event["args"]["draw_duration_ms"][0].asDouble(), kValues[0]);
+  EXPECT_EQ(event["args"]["draw_duration_ms"][1].asDouble(), kValues[1]);
+}
+
+TEST(ExportJsonTest, StorageWithMultiplePointerArgs) {
+  const char* kCategory = "cat";
+  const char* kName = "name";
+  uint64_t kValue0 = 1;
+  uint64_t kValue1 = std::numeric_limits<uint64_t>::max();
+
+  TraceStorage storage;
+  UniqueTid utid = storage.AddEmptyThread(0);
+  StringId cat_id = storage.InternString(base::StringView(kCategory));
+  StringId name_id = storage.InternString(base::StringView(kName));
+  storage.mutable_nestable_slices()->AddSlice(0, 0, utid, RefType::kRefUtid,
+                                              cat_id, name_id, 0, 0, 0);
+
+  StringId arg_key0_id = storage.InternString(base::StringView("arg0"));
+  StringId arg_key1_id = storage.InternString(base::StringView("arg1"));
+  TraceStorage::Args::Arg arg0;
+  arg0.flat_key = arg_key0_id;
+  arg0.key = arg_key0_id;
+  arg0.value = Variadic::Pointer(kValue0);
+  TraceStorage::Args::Arg arg1;
+  arg1.flat_key = arg_key1_id;
+  arg1.key = arg_key1_id;
+  arg1.value = Variadic::Pointer(kValue1);
+  storage.mutable_args()->AddArgSet({arg0, arg1}, 0, 2);
+  storage.mutable_nestable_slices()->set_arg_set_id(0, 1);
+
+  base::TempFile temp_file = base::TempFile::Create();
+  FILE* output = fopen(temp_file.path().c_str(), "w+");
+  int code = ExportJson(&storage, output);
+
+  EXPECT_EQ(code, kResultOk);
+
+  Json::Reader reader;
+  Json::Value result;
+  EXPECT_TRUE(reader.parse(ReadFile(output), result));
+  EXPECT_EQ(result["traceEvents"].size(), 1u);
+
+  Json::Value event = result["traceEvents"][0];
+  EXPECT_EQ(event["cat"].asString(), kCategory);
+  EXPECT_EQ(event["name"].asString(), kName);
+  EXPECT_EQ(event["args"]["arg0"].asString(), "0x1");
+  EXPECT_EQ(event["args"]["arg1"].asString(), "0xffffffffffffffff");
+}
+
+TEST(ExportJsonTest, StorageWithObjectListArgs) {
+  const char* kCategory = "cat";
+  const char* kName = "name";
+  int kValues[] = {123, 234};
+
+  TraceStorage storage;
+  UniqueTid utid = storage.AddEmptyThread(0);
+  StringId cat_id = storage.InternString(base::StringView(kCategory));
+  StringId name_id = storage.InternString(base::StringView(kName));
+  storage.mutable_nestable_slices()->AddSlice(0, 0, utid, RefType::kRefUtid,
+                                              cat_id, name_id, 0, 0, 0);
+
+  StringId arg_flat_key_id = storage.InternString(base::StringView("a.b"));
+  StringId arg_key0_id = storage.InternString(base::StringView("a[0].b"));
+  StringId arg_key1_id = storage.InternString(base::StringView("a[1].b"));
+  TraceStorage::Args::Arg arg0;
+  arg0.flat_key = arg_flat_key_id;
+  arg0.key = arg_key0_id;
+  arg0.value = Variadic::Integer(kValues[0]);
+  TraceStorage::Args::Arg arg1;
+  arg1.flat_key = arg_flat_key_id;
+  arg1.key = arg_key1_id;
+  arg1.value = Variadic::Integer(kValues[1]);
+  storage.mutable_args()->AddArgSet({arg0, arg1}, 0, 2);
+  storage.mutable_nestable_slices()->set_arg_set_id(0, 1);
+
+  base::TempFile temp_file = base::TempFile::Create();
+  FILE* output = fopen(temp_file.path().c_str(), "w+");
+  int code = ExportJson(&storage, output);
+
+  EXPECT_EQ(code, kResultOk);
+
+  Json::Reader reader;
+  Json::Value result;
+  EXPECT_TRUE(reader.parse(ReadFile(output), result));
+  EXPECT_EQ(result["traceEvents"].size(), 1u);
+
+  Json::Value event = result["traceEvents"][0];
+  EXPECT_EQ(event["cat"].asString(), kCategory);
+  EXPECT_EQ(event["name"].asString(), kName);
+  EXPECT_EQ(event["args"]["a"].size(), 2);
+  EXPECT_EQ(event["args"]["a"][0]["b"].asInt(), kValues[0]);
+  EXPECT_EQ(event["args"]["a"][1]["b"].asInt(), kValues[1]);
+}
+
+TEST(ExportJsonTest, StorageWithNestedListArgs) {
+  const char* kCategory = "cat";
+  const char* kName = "name";
+  int kValues[] = {123, 234};
+
+  TraceStorage storage;
+  UniqueTid utid = storage.AddEmptyThread(0);
+  StringId cat_id = storage.InternString(base::StringView(kCategory));
+  StringId name_id = storage.InternString(base::StringView(kName));
+  storage.mutable_nestable_slices()->AddSlice(0, 0, utid, RefType::kRefUtid,
+                                              cat_id, name_id, 0, 0, 0);
+
+  StringId arg_flat_key_id = storage.InternString(base::StringView("a"));
+  StringId arg_key0_id = storage.InternString(base::StringView("a[0][0]"));
+  StringId arg_key1_id = storage.InternString(base::StringView("a[0][1]"));
+  TraceStorage::Args::Arg arg0;
+  arg0.flat_key = arg_flat_key_id;
+  arg0.key = arg_key0_id;
+  arg0.value = Variadic::Integer(kValues[0]);
+  TraceStorage::Args::Arg arg1;
+  arg1.flat_key = arg_flat_key_id;
+  arg1.key = arg_key1_id;
+  arg1.value = Variadic::Integer(kValues[1]);
+  storage.mutable_args()->AddArgSet({arg0, arg1}, 0, 2);
+  storage.mutable_nestable_slices()->set_arg_set_id(0, 1);
+
+  base::TempFile temp_file = base::TempFile::Create();
+  FILE* output = fopen(temp_file.path().c_str(), "w+");
+  int code = ExportJson(&storage, output);
+
+  EXPECT_EQ(code, kResultOk);
+
+  Json::Reader reader;
+  Json::Value result;
+  EXPECT_TRUE(reader.parse(ReadFile(output), result));
+  EXPECT_EQ(result["traceEvents"].size(), 1u);
+
+  Json::Value event = result["traceEvents"][0];
+  EXPECT_EQ(event["cat"].asString(), kCategory);
+  EXPECT_EQ(event["name"].asString(), kName);
+  EXPECT_EQ(event["args"]["a"].size(), 1);
+  EXPECT_EQ(event["args"]["a"][0].size(), 2);
+  EXPECT_EQ(event["args"]["a"][0][0].asInt(), kValues[0]);
+  EXPECT_EQ(event["args"]["a"][0][1].asInt(), kValues[1]);
+}
+
+TEST(ExportJsonTest, StorageWithLegacyJsonArgs) {
+  const char* kCategory = "cat";
+  const char* kName = "name";
+
+  TraceStorage storage;
+  UniqueTid utid = storage.AddEmptyThread(0);
+  StringId cat_id = storage.InternString(base::StringView(kCategory));
+  StringId name_id = storage.InternString(base::StringView(kName));
+  storage.mutable_nestable_slices()->AddSlice(0, 0, utid, RefType::kRefUtid,
+                                              cat_id, name_id, 0, 0, 0);
+
+  StringId arg_key_id = storage.InternString(base::StringView("a"));
+  StringId arg_value_id = storage.InternString(base::StringView("{\"b\":123}"));
+  TraceStorage::Args::Arg arg;
+  arg.flat_key = arg_key_id;
+  arg.key = arg_key_id;
+  arg.value = Variadic::Json(arg_value_id);
+  storage.mutable_args()->AddArgSet({arg}, 0, 1);
+  storage.mutable_nestable_slices()->set_arg_set_id(0, 1);
+
+  base::TempFile temp_file = base::TempFile::Create();
+  FILE* output = fopen(temp_file.path().c_str(), "w+");
+  int code = ExportJson(&storage, output);
+
+  EXPECT_EQ(code, kResultOk);
+
+  Json::Reader reader;
+  Json::Value result;
+  EXPECT_TRUE(reader.parse(ReadFile(output), result));
+  EXPECT_EQ(result["traceEvents"].size(), 1u);
+
+  Json::Value event = result["traceEvents"][0];
+  EXPECT_EQ(event["cat"].asString(), kCategory);
+  EXPECT_EQ(event["name"].asString(), kName);
+  EXPECT_EQ(event["args"]["a"]["b"].asInt(), 123);
+}
+
 }  // namespace
 }  // namespace json
 }  // namespace trace_processor
diff --git a/src/trace_processor/proto_trace_parser.cc b/src/trace_processor/proto_trace_parser.cc
index 22c26d3..b19086b 100644
--- a/src/trace_processor/proto_trace_parser.cc
+++ b/src/trace_processor/proto_trace_parser.cc
@@ -1725,6 +1725,10 @@
   if (name_view_it->second.storage_refs) {
     name_id = name_view_it->second.storage_refs->name_id;
   } else {
+    // TODO(khokhlov): If there are dots or brackets in argument names,
+    // they will confuse the JSON exporter. Either introduce escape
+    // sequences (both here and in export_json.cc), or find another way
+    // to encode such names.
     auto name = name_view_it->second.CreateDecoder();
     std::string name_prefixed = "debug." + name.name().ToStdString();
     name_id = storage->InternString(base::StringView(name_prefixed));
@@ -1754,9 +1758,9 @@
     args_tracker->AddArg(row, name_id, name_id,
                          Variadic::Pointer(annotation.pointer_value()));
   } else if (annotation.has_legacy_json_value()) {
-    args_tracker->AddArg(row, name_id, name_id,
-                         Variadic::String(storage->InternString(
-                             annotation.legacy_json_value())));
+    args_tracker->AddArg(
+        row, name_id, name_id,
+        Variadic::Json(storage->InternString(annotation.legacy_json_value())));
   } else if (annotation.has_nested_value()) {
     auto name = storage->GetString(name_id);
     ParseNestedValueArgs(annotation.nested_value(), name, name, args_tracker,
diff --git a/src/trace_processor/proto_trace_parser_unittest.cc b/src/trace_processor/proto_trace_parser_unittest.cc
index 5027c38..a1aaaf7 100644
--- a/src/trace_processor/proto_trace_parser_unittest.cc
+++ b/src/trace_processor/proto_trace_parser_unittest.cc
@@ -1381,7 +1381,7 @@
       .WillOnce(Return(17));
   EXPECT_CALL(*storage_, InternString(base::StringView("val8")))
       .WillOnce(Return(18));
-  EXPECT_CALL(args, AddArg(1u, 17, 17, Variadic::String(18)));
+  EXPECT_CALL(args, AddArg(1u, 17, 17, Variadic::Json(18)));
 
   context_.sorter->ExtractEventsForced();
 }