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();
}