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