TraceProcessor: Add EXPORT_JSON SQL function

Adds basic support for exporting traces in the legacy json format.
Only nestable slices and process/thread names are exported so far.

Bug: 130786269
Change-Id: I8be954c08652b5208ab9d1d33d1548fb930fdefe
diff --git a/src/trace_processor/BUILD.gn b/src/trace_processor/BUILD.gn
index 0a76207..44ba597 100644
--- a/src/trace_processor/BUILD.gn
+++ b/src/trace_processor/BUILD.gn
@@ -170,6 +170,8 @@
   ]
   if (perfetto_build_standalone) {
     sources += [
+      "export_json.cc",
+      "export_json.h",
       "json_trace_parser.cc",
       "json_trace_parser.h",
       "json_trace_tokenizer.cc",
@@ -254,6 +256,7 @@
   ]
   if (perfetto_build_standalone) {
     sources += [
+      "export_json_unittest.cc",
       "json_trace_tokenizer_unittest.cc",
       "json_trace_utils_unittest.cc",
     ]
diff --git a/src/trace_processor/export_json.cc b/src/trace_processor/export_json.cc
new file mode 100644
index 0000000..1d3d269
--- /dev/null
+++ b/src/trace_processor/export_json.cc
@@ -0,0 +1,146 @@
+/*
+ * 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/trace_processor/export_json.h"
+
+#include <json/value.h>
+#include <json/writer.h>
+#include <stdio.h>
+
+#include "src/trace_processor/trace_storage.h"
+
+namespace {
+
+class TraceFormatWriter {
+ public:
+  TraceFormatWriter(FILE* output) : output_(output), first_event_(true) {
+    WriteHeader();
+  }
+
+  ~TraceFormatWriter() { WriteFooter(); }
+
+  void WriteSlice(int64_t begin_ts_us,
+                  int64_t duration_us,
+                  const char* cat,
+                  const char* name,
+                  uint32_t tid,
+                  uint32_t pid) {
+    if (!first_event_) {
+      fputs(",", output_);
+    }
+    Json::FastWriter writer;
+    Json::Value value;
+    value["ph"] = "X";
+    value["cat"] = cat;
+    value["name"] = name;
+    value["tid"] = Json::UInt(tid);
+    value["pid"] = Json::UInt(pid);
+    value["ts"] = Json::Int64(begin_ts_us);
+    value["dur"] = Json::Int64(duration_us);
+    fputs(writer.write(value).c_str(), output_);
+    first_event_ = false;
+  }
+
+  void WriteMetadataEvent(const char* metadata_type,
+                          const char* metadata_value,
+                          uint32_t tid,
+                          uint32_t pid) {
+    if (!first_event_) {
+      fputs(",", output_);
+    }
+    Json::FastWriter writer;
+    Json::Value value;
+    value["ph"] = "M";
+    value["cat"] = "__metadata";
+    value["ts"] = 0;
+    value["name"] = metadata_type;
+    value["tid"] = Json::UInt(tid);
+    value["pid"] = Json::UInt(pid);
+
+    Json::Value args;
+    args["name"] = metadata_value;
+    value["args"] = args;
+
+    fputs(writer.write(value).c_str(), output_);
+    first_event_ = false;
+  }
+
+ private:
+  void WriteHeader() { fputs("{\"traceEvents\":[\n", output_); }
+
+  void WriteFooter() {
+    fputs("]}\n", output_);
+    fflush(output_);
+  }
+
+  FILE* output_;
+  bool first_event_;
+};
+
+}  // anonymous namespace
+
+namespace perfetto {
+namespace trace_processor {
+namespace json {
+
+ResultCode ExportJson(const TraceStorage* storage, FILE* output) {
+  const StringPool& string_pool = storage->string_pool();
+
+  TraceFormatWriter writer(output);
+
+  // Write thread names.
+  for (UniqueTid i = 1; i < storage->thread_count(); ++i) {
+    auto thread = storage->GetThread(i);
+    if (thread.name_id > 0) {
+      const char* thread_name = string_pool.Get(thread.name_id).c_str();
+      uint32_t pid = thread.upid ? storage->GetProcess(*thread.upid).pid : 0;
+      writer.WriteMetadataEvent("thread_name", thread_name, thread.tid, pid);
+    }
+  }
+
+  // Write process names.
+  for (UniquePid i = 1; i < storage->process_count(); ++i) {
+    auto process = storage->GetProcess(i);
+    if (process.name_id > 0) {
+      const char* process_name = string_pool.Get(process.name_id).c_str();
+      writer.WriteMetadataEvent("process_name", process_name, 0, process.pid);
+    }
+  }
+
+  // Write slices.
+  const auto& slices = storage->nestable_slices();
+  for (size_t i = 0; i < slices.slice_count(); ++i) {
+    if (slices.types()[i] == RefType::kRefUtid) {
+      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();
+      const char* name = string_pool.Get(slices.names()[i]).c_str();
+
+      UniqueTid utid = static_cast<UniqueTid>(slices.refs()[i]);
+      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);
+    } else {
+      return kResultWrongRefType;
+    }
+  }
+  return kResultOk;
+}
+
+}  // namespace json
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/export_json.h b/src/trace_processor/export_json.h
new file mode 100644
index 0000000..0ceefd8
--- /dev/null
+++ b/src/trace_processor/export_json.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_TRACE_PROCESSOR_EXPORT_JSON_H_
+#define SRC_TRACE_PROCESSOR_EXPORT_JSON_H_
+
+#include "src/trace_processor/trace_storage.h"
+
+#include <stdio.h>
+
+namespace perfetto {
+namespace trace_processor {
+namespace json {
+
+enum ResultCode {
+  kResultOk = 0,
+  kResultWrongRefType = 1,
+};
+
+// Export trace to a stream in json format.
+ResultCode ExportJson(const TraceStorage* storage, FILE* output);
+
+}  // namespace json
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_EXPORT_JSON_H_
diff --git a/src/trace_processor/export_json_unittest.cc b/src/trace_processor/export_json_unittest.cc
new file mode 100644
index 0000000..a4bbbf5
--- /dev/null
+++ b/src/trace_processor/export_json_unittest.cc
@@ -0,0 +1,132 @@
+/*
+ * 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/trace_processor/export_json.h"
+
+#include "perfetto/base/temp_file.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include <json/reader.h>
+#include <json/value.h>
+
+namespace perfetto {
+namespace trace_processor {
+namespace json {
+namespace {
+
+std::string ReadFile(FILE* input) {
+  fseek(input, 0, SEEK_SET);
+  const int kBufSize = 1000;
+  char buffer[kBufSize];
+  fread(buffer, sizeof(char), kBufSize, input);
+  return std::string(buffer);
+}
+
+TEST(ExportJsonTest, EmptyStorage) {
+  TraceStorage storage;
+
+  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(), 0);
+}
+
+TEST(ExportJsonTest, StorageWithOneSlice) {
+  const int64_t kTimestamp = 10000000;
+  const int64_t kDuration = 10000;
+  const int64_t kThreadID = 100;
+  const char* kCategory = "cat";
+  const char* kName = "name";
+
+  TraceStorage storage;
+  UniqueTid utid = storage.AddEmptyThread(kThreadID);
+  StringId cat_id = storage.InternString(base::StringView(kCategory));
+  StringId name_id = storage.InternString(base::StringView(kName));
+  storage.mutable_nestable_slices()->AddSlice(
+      kTimestamp, kDuration, utid, RefType::kRefUtid, cat_id, name_id, 0, 0, 0);
+
+  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(), 1);
+
+  Json::Value event = result["traceEvents"][0];
+  EXPECT_EQ(event["ph"].asString(), "X");
+  EXPECT_EQ(event["ts"].asInt64(), kTimestamp / 1000);
+  EXPECT_EQ(event["dur"].asInt64(), kDuration / 1000);
+  EXPECT_EQ(event["tid"].asUInt(), kThreadID);
+  EXPECT_EQ(event["cat"].asString(), kCategory);
+  EXPECT_EQ(event["name"].asString(), kName);
+}
+
+TEST(ExportJsonTest, StorageWithThreadName) {
+  const int64_t kThreadID = 100;
+  const char* kName = "thread";
+
+  TraceStorage storage;
+  UniqueTid utid = storage.AddEmptyThread(kThreadID);
+  StringId name_id = storage.InternString(base::StringView(kName));
+  storage.GetMutableThread(utid)->name_id = name_id;
+
+  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(), 1);
+
+  Json::Value event = result["traceEvents"][0];
+  EXPECT_EQ(event["ph"].asString(), "M");
+  EXPECT_EQ(event["tid"].asUInt(), kThreadID);
+  EXPECT_EQ(event["name"].asString(), "thread_name");
+  EXPECT_EQ(event["args"]["name"].asString(), kName);
+}
+
+TEST(ExportJsonTest, WrongRefType) {
+  TraceStorage storage;
+  storage.mutable_nestable_slices()->AddSlice(0, 0, 0, RefType::kRefCpuId, 0, 0,
+                                              0, 0, 0);
+
+  base::TempFile temp_file = base::TempFile::Create();
+  FILE* output = fopen(temp_file.path().c_str(), "w+");
+  int code = ExportJson(&storage, output);
+
+  EXPECT_EQ(code, kResultWrongRefType);
+}
+
+}  // namespace
+}  // namespace json
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index 4313b2d..a7e9029 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -18,6 +18,7 @@
 
 #include <inttypes.h>
 #include <algorithm>
+#include <fstream>
 #include <functional>
 
 #include "perfetto/base/logging.h"
@@ -59,8 +60,9 @@
 #include "perfetto/metrics/android/mem_metric.pbzero.h"
 #include "perfetto/metrics/metrics.pbzero.h"
 
-// JSON parsing is only supported in the standalone build.
+// JSON parsing and exporting is only supported in the standalone build.
 #if PERFETTO_BUILDFLAG(PERFETTO_STANDALONE_BUILD)
+#include "src/trace_processor/export_json.h"
 #include "src/trace_processor/json_trace_parser.h"
 #include "src/trace_processor/json_trace_tokenizer.h"
 #endif
@@ -191,6 +193,40 @@
   }
 }
 
+// Exporting traces in legacy JSON format is only supported
+// in the standalone build so far.
+#if PERFETTO_BUILDFLAG(PERFETTO_STANDALONE_BUILD)
+void ExportJson(sqlite3_context* ctx, int /*argc*/, sqlite3_value** argv) {
+  TraceStorage* storage = static_cast<TraceStorage*>(sqlite3_user_data(ctx));
+  const char* filename =
+      reinterpret_cast<const char*>(sqlite3_value_text(argv[0]));
+  FILE* output = fopen(filename, "w");
+  if (!output) {
+    sqlite3_result_error(ctx, "Couldn't open output file", -1);
+    return;
+  }
+
+  json::ResultCode result = json::ExportJson(storage, output);
+  switch (result) {
+    case json::kResultOk:
+      return;
+    case json::kResultWrongRefType:
+      sqlite3_result_error(ctx, "Encountered a slice with unsupported ref type",
+                           -1);
+      return;
+  }
+}
+
+void CreateJsonExportFunction(TraceStorage* ts, sqlite3* db) {
+  auto ret = sqlite3_create_function_v2(db, "EXPORT_JSON", 1, SQLITE_UTF8, ts,
+                                        ExportJson, nullptr, nullptr,
+                                        sqlite_utils::kSqliteStatic);
+  if (ret) {
+    PERFETTO_ELOG("Error initializing EXPORT_JSON");
+  }
+}
+#endif
+
 // Fuchsia traces have a magic number as documented here:
 // https://fuchsia.googlesource.com/fuchsia/+/HEAD/docs/development/tracing/trace-format/README.md#magic-number-record-trace-info-type-0
 constexpr uint64_t kFuchsiaMagicNumber = 0x0016547846040010;
@@ -233,6 +269,10 @@
   context_.clock_tracker.reset(new ClockTracker(&context_));
   context_.heap_profile_tracker.reset(new HeapProfileTracker(&context_));
 
+#if PERFETTO_BUILDFLAG(PERFETTO_STANDALONE_BUILD)
+  CreateJsonExportFunction(this->context_.storage.get(), db);
+#endif
+
   ArgsTable::RegisterTable(*db_, context_.storage.get());
   ProcessTable::RegisterTable(*db_, context_.storage.get());
   SchedSliceTable::RegisterTable(*db_, context_.storage.get());