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