EXPORT_JSON support for CPU stack samples

R=khokhlov@google.com,fmayer@google.com
CC=eseckler@google.com,ssid@google.com

Change-Id: Ie2c13191fc4d97c691f810dc6a61525a02f2f1d8
diff --git a/src/trace_processor/export_json.cc b/src/trace_processor/export_json.cc
index 662a32f..1b9e4b7 100644
--- a/src/trace_processor/export_json.cc
+++ b/src/trace_processor/export_json.cc
@@ -20,6 +20,7 @@
 #include <json/writer.h>
 #include <stdio.h>
 #include <cstring>
+#include <vector>
 
 #include "perfetto/ext/base/string_splitter.h"
 #include "src/trace_processor/export_json.h"
@@ -599,6 +600,76 @@
   return kResultOk;
 }
 
+ResultCode ExportCpuProfileSamples(const TraceStorage* storage,
+                                   TraceFormatWriter* writer) {
+  const TraceStorage::CpuProfileStackSamples& samples =
+      storage->cpu_profile_stack_samples();
+  for (uint32_t i = 0; i < samples.size(); ++i) {
+    Json::Value event;
+    event["ts"] = Json::Int64(samples.timestamps()[i] / 1000);
+
+    UniqueTid utid = static_cast<UniqueTid>(samples.utids()[i]);
+    auto thread = storage->GetThread(utid);
+    event["tid"] = thread.tid;
+    if (thread.upid)
+      event["pid"] = storage->GetProcess(*thread.upid).pid;
+
+    event["ph"] = "I";
+    event["cat"] = "disabled_by_default-cpu_profiler";
+    event["name"] = "StackCpuSampling";
+    event["scope"] = "t";
+
+    std::vector<std::string> callstack;
+    const TraceStorage::StackProfileCallsites& callsites =
+        storage->stack_profile_callsites();
+    int64_t maybe_callsite_id = samples.callsite_ids()[i];
+    PERFETTO_DCHECK(maybe_callsite_id >= 0 &&
+                    maybe_callsite_id < callsites.size());
+    while (maybe_callsite_id >= 0) {
+      size_t callsite_id = static_cast<size_t>(maybe_callsite_id);
+
+      const TraceStorage::StackProfileFrames& frames =
+          storage->stack_profile_frames();
+      PERFETTO_DCHECK(callsites.frame_ids()[callsite_id] >= 0 &&
+                      callsites.frame_ids()[callsite_id] < frames.size());
+      size_t frame_id = static_cast<size_t>(callsites.frame_ids()[callsite_id]);
+
+      const TraceStorage::StackProfileMappings& mappings =
+          storage->stack_profile_mappings();
+      PERFETTO_DCHECK(frames.mappings()[frame_id] >= 0 &&
+                      frames.mappings()[frame_id] < mappings.size());
+      size_t mapping_id = static_cast<size_t>(frames.mappings()[frame_id]);
+
+      NullTermStringView symbol_name =
+          storage->GetString(frames.names()[frame_id]);
+
+      char frame_entry[1024];
+      snprintf(
+          frame_entry, sizeof(frame_entry), "%s - %s [%s]\n",
+          (symbol_name.empty()
+               ? PrintUint64(static_cast<uint64_t>(frames.rel_pcs()[frame_id]))
+                     .c_str()
+               : symbol_name.c_str()),
+          storage->GetString(mappings.names()[mapping_id]).c_str(),
+          storage->GetString(mappings.build_ids()[mapping_id]).c_str());
+
+      callstack.emplace_back(frame_entry);
+
+      maybe_callsite_id = callsites.parent_callsite_ids()[callsite_id];
+    }
+
+    std::string merged_callstack;
+    for (auto entry = callstack.rbegin(); entry != callstack.rend(); ++entry) {
+      merged_callstack += *entry;
+    }
+
+    event["args"]["frames"] = merged_callstack;
+    writer->WriteCommonEvent(event);
+  }
+
+  return kResultOk;
+}
+
 ResultCode ExportMetadata(const TraceStorage* storage,
                           TraceFormatWriter* writer) {
   const auto& trace_metadata = storage->metadata();
@@ -758,6 +829,10 @@
   if (code != kResultOk)
     return code;
 
+  code = ExportCpuProfileSamples(storage, &writer);
+  if (code != kResultOk)
+    return code;
+
   code = ExportMetadata(storage, &writer);
   if (code != kResultOk)
     return code;
diff --git a/src/trace_processor/export_json_unittest.cc b/src/trace_processor/export_json_unittest.cc
index ea04091..536fa25 100644
--- a/src/trace_processor/export_json_unittest.cc
+++ b/src/trace_processor/export_json_unittest.cc
@@ -967,6 +967,67 @@
   EXPECT_EQ(result["systemTraceEvents"].asString(), kLegacyFtraceData);
 }
 
+TEST(ExportJsonTest, CpuProfileEvent) {
+  const int64_t kProcessID = 100;
+  const int64_t kThreadID = 200;
+  const int64_t kTimestamp = 10000000;
+
+  TraceProcessorContext context;
+  context.storage.reset(new TraceStorage());
+  TraceStorage* storage = context.storage.get();
+
+  UniquePid upid = storage->AddEmptyProcess(kProcessID);
+  UniqueTid utid = storage->AddEmptyThread(kThreadID);
+  storage->GetMutableThread(utid)->upid = upid;
+
+  RowId module_row_id_1 = storage->mutable_stack_profile_mappings()->Insert(
+      {storage->InternString("foo_module_id"), 0, 0, 0, 0, 0,
+       storage->InternString("foo_module_name")});
+
+  RowId module_row_id_2 = storage->mutable_stack_profile_mappings()->Insert(
+      {storage->InternString("bar_module_id"), 0, 0, 0, 0, 0,
+       storage->InternString("bar_module_name")});
+
+  RowId frame_row_id_1 = storage->mutable_stack_profile_frames()->Insert(
+      {storage->InternString("foo_func"), module_row_id_1, 0x42});
+
+  RowId frame_row_id_2 = storage->mutable_stack_profile_frames()->Insert(
+      {storage->InternString("bar_func"), module_row_id_2, 0x4242});
+
+  RowId frame_callsite_id_1 =
+      storage->mutable_stack_profile_callsites()->Insert(
+          {0, -1, frame_row_id_1});
+
+  RowId frame_callsite_id_2 =
+      storage->mutable_stack_profile_callsites()->Insert(
+          {1, frame_callsite_id_1, frame_row_id_2});
+
+  storage->mutable_cpu_profile_stack_samples()->Insert(
+      {kTimestamp, frame_callsite_id_2, utid});
+
+  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["ph"].asString(), "I");
+  EXPECT_EQ(event["ts"].asInt64(), kTimestamp / 1000);
+  EXPECT_EQ(event["tid"].asUInt(), kThreadID);
+  EXPECT_EQ(event["cat"].asString(), "disabled_by_default-cpu_profiler");
+  EXPECT_EQ(event["name"].asString(), "StackCpuSampling");
+  EXPECT_EQ(event["scope"].asString(), "t");
+  EXPECT_EQ(event["args"]["frames"].asString(),
+            "foo_func - foo_module_name [foo_module_id]\nbar_func - "
+            "bar_module_name [bar_module_id]\n");
+}
+
 }  // namespace
 }  // namespace json
 }  // namespace trace_processor