Merge "Implement trace sqlite exporting"
diff --git a/src/trace_processor/span_join_operator_table.cc b/src/trace_processor/span_join_operator_table.cc
index db8bb78..de06fbf 100644
--- a/src/trace_processor/span_join_operator_table.cc
+++ b/src/trace_processor/span_join_operator_table.cc
@@ -41,7 +41,9 @@
void SpanJoinOperatorTable::RegisterTable(sqlite3* db,
const TraceStorage* storage) {
- Table::Register<SpanJoinOperatorTable>(db, storage, "span_join");
+ Table::Register<SpanJoinOperatorTable>(db, storage, "span_join",
+ /* read_write */ false,
+ /* requires_args */ true);
}
Table::Schema SpanJoinOperatorTable::CreateSchema(int argc,
diff --git a/src/trace_processor/table.cc b/src/trace_processor/table.cc
index ceca4b6..8ded39a 100644
--- a/src/trace_processor/table.cc
+++ b/src/trace_processor/table.cc
@@ -71,6 +71,7 @@
const TraceStorage* storage,
const std::string& table_name,
bool read_write,
+ bool requires_args,
Factory factory) {
std::unique_ptr<TableDescriptor> desc(new TableDescriptor());
desc->storage = storage;
@@ -86,7 +87,7 @@
auto schema = table->CreateSchema(argc, argv);
auto create_stmt = schema.ToCreateTableStmt();
- PERFETTO_DLOG("Create table statment: %s", create_stmt.c_str());
+ PERFETTO_DLOG("Create table statement: %s", create_stmt.c_str());
int res = sqlite3_declare_vtab(xdb, create_stmt.c_str());
if (res != SQLITE_OK)
@@ -156,6 +157,22 @@
db, table_name.c_str(), module, desc.release(),
[](void* arg) { delete static_cast<TableDescriptor*>(arg); });
PERFETTO_CHECK(res == SQLITE_OK);
+
+ // Register virtual tables into an internal 'perfetto_tables' table. This is
+ // used for iterating through all the tables during a database export. Note
+ // that virtual tables requiring arguments aren't registered because they
+ // can't be automatically instantiated for exporting.
+ if (!requires_args) {
+ char* insert_sql = sqlite3_mprintf(
+ "INSERT INTO perfetto_tables(name) VALUES('%q')", table_name.c_str());
+ char* error = nullptr;
+ sqlite3_exec(db, insert_sql, 0, 0, &error);
+ sqlite3_free(insert_sql);
+ if (error) {
+ PERFETTO_ELOG("Error registering table: %s", error);
+ sqlite3_free(error);
+ }
+ }
}
int Table::OpenInternal(sqlite3_vtab_cursor** ppCursor) {
diff --git a/src/trace_processor/table.h b/src/trace_processor/table.h
index bccf710..f0b4cac 100644
--- a/src/trace_processor/table.h
+++ b/src/trace_processor/table.h
@@ -138,12 +138,17 @@
Table();
// Called by derived classes to register themselves with the SQLite db.
+ // |read_write| specifies whether the table can also be written to.
+ // |requires_args| should be true if the table requires arguments in order to
+ // be instantiated.
template <typename T>
static void Register(sqlite3* db,
const TraceStorage* storage,
const std::string& name,
- bool read_write = false) {
- RegisterInternal(db, storage, name, read_write, GetFactory<T>());
+ bool read_write = false,
+ bool requires_args = false) {
+ RegisterInternal(db, storage, name, read_write, requires_args,
+ GetFactory<T>());
}
// Methods to be implemented by derived table classes.
@@ -178,6 +183,7 @@
const TraceStorage*,
const std::string& name,
bool read_write,
+ bool requires_args,
Factory);
// Overriden functions from sqlite3_vtab.
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index 614cb21..297944b 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -52,8 +52,18 @@
void InitializeSqliteModules(sqlite3* db) {
char* error = nullptr;
sqlite3_percentile_init(db, &error, nullptr);
- if (error != nullptr) {
+ if (error) {
PERFETTO_ELOG("Error initializing: %s", error);
+ sqlite3_free(error);
+ }
+}
+
+void CreateBuiltinTables(sqlite3* db) {
+ char* error = nullptr;
+ sqlite3_exec(db, "CREATE TABLE perfetto_tables(name STRING)", 0, 0, &error);
+ if (error) {
+ PERFETTO_ELOG("Error initializing: %s", error);
+ sqlite3_free(error);
}
}
} // namespace
@@ -91,6 +101,7 @@
sqlite3* db = nullptr;
PERFETTO_CHECK(sqlite3_open(":memory:", &db) == SQLITE_OK);
InitializeSqliteModules(db);
+ CreateBuiltinTables(db);
db_.reset(std::move(db));
context_.storage.reset(new TraceStorage());
diff --git a/src/trace_processor/trace_processor_shell.cc b/src/trace_processor/trace_processor_shell.cc
index 1cc5d0d..51b4579 100644
--- a/src/trace_processor/trace_processor_shell.cc
+++ b/src/trace_processor/trace_processor_shell.cc
@@ -135,6 +135,68 @@
#endif
+int ExportTraceToDatabase(const std::string& output_name) {
+ PERFETTO_CHECK(output_name.find("'") == std::string::npos);
+ {
+ base::ScopedFile fd(base::OpenFile(output_name, O_CREAT | O_RDWR, 0600));
+ if (!fd) {
+ PERFETTO_PLOG("Failed to create file: %s", output_name.c_str());
+ return 1;
+ }
+ int res = ftruncate(fd.get(), 0);
+ PERFETTO_CHECK(res == 0);
+ }
+
+ // TODO(skyostil): Use synchronous queries.
+ std::string attach_sql =
+ "ATTACH DATABASE '" + output_name + "' AS perfetto_export";
+ protos::RawQueryArgs attach_query;
+ attach_query.set_sql_query(attach_sql);
+ g_tp->ExecuteQuery(
+ attach_query, [](const protos::RawQueryResult& attach_res) {
+ if (attach_res.has_error()) {
+ PERFETTO_ELOG("SQLite error: %s", attach_res.error().c_str());
+ return;
+ }
+ protos::RawQueryArgs list_query;
+ // Find all the virtual tables we have created internally as well as
+ // actual tables registered through SQL.
+ list_query.set_sql_query(
+ "SELECT name FROM perfetto_tables UNION "
+ "SELECT name FROM sqlite_master WHERE type='table'");
+ g_tp->ExecuteQuery(list_query, [](const protos::RawQueryResult& res) {
+ if (res.has_error()) {
+ PERFETTO_ELOG("SQLite error: %s", res.error().c_str());
+ return;
+ }
+ PERFETTO_CHECK(res.columns_size() == 1);
+ for (int r = 0; r < static_cast<int>(res.num_records()); r++) {
+ std::string table_name = res.columns(0).string_values(r);
+ PERFETTO_CHECK(table_name.find("'") == std::string::npos);
+ std::string export_sql = "CREATE TABLE perfetto_export." +
+ table_name + " AS SELECT * FROM " +
+ table_name;
+ protos::RawQueryArgs export_query;
+ export_query.set_sql_query(export_sql);
+ g_tp->ExecuteQuery(export_query,
+ [](const protos::RawQueryResult& export_res) {
+ if (export_res.has_error())
+ PERFETTO_ELOG("SQLite error: %s",
+ export_res.error().c_str());
+ });
+ }
+ });
+ });
+
+ protos::RawQueryArgs detach_query;
+ detach_query.set_sql_query("DETACH DATABASE perfetto_export");
+ g_tp->ExecuteQuery(detach_query, [](const protos::RawQueryResult& res) {
+ if (res.has_error())
+ PERFETTO_ELOG("SQLite error: %s", res.error().c_str());
+ });
+ return 0;
+}
+
void PrintQueryResultInteractively(base::TimeNanos t_start,
const protos::RawQueryResult& res) {
if (res.has_error()) {
@@ -185,15 +247,40 @@
}
printf("\nQuery executed in %.3f ms\n\n", (t_end - t_start).count() / 1E6);
}
+
+void PrintShellUsage() {
+ PERFETTO_ELOG(
+ "Available commands:\n"
+ ".quit, .q Exit the shell.\n"
+ ".help This text.\n"
+ ".dump FILE Export the trace as a sqlite database.\n");
+}
+
int StartInteractiveShell() {
SetupLineEditor();
for (;;) {
char* line = GetLine("> ");
- if (!line || strcmp(line, "q\n") == 0)
+ if (!line)
break;
if (strcmp(line, "") == 0)
continue;
+ if (line[0] == '.') {
+ char command[32] = {};
+ char arg[1024] = {};
+ sscanf(line + 1, "%31s %1023s", command, arg);
+ if (strcmp(command, "quit") == 0 || strcmp(command, "q") == 0) {
+ break;
+ } else if (strcmp(command, "help") == 0) {
+ PrintShellUsage();
+ } else if (strcmp(command, "dump") == 0 && strlen(arg)) {
+ if (ExportTraceToDatabase(arg) != 0)
+ PERFETTO_ELOG("Database export failed");
+ } else {
+ PrintShellUsage();
+ }
+ continue;
+ }
protos::RawQueryArgs query;
query.set_sql_query(line);
base::TimeNanos t_start = base::GetWallTimeNs();
@@ -287,7 +374,14 @@
}
void PrintUsage(char** argv) {
- PERFETTO_ELOG("Usage: %s [-d] [-q query.sql] trace_file.pb", argv[0]);
+ PERFETTO_ELOG(
+ "Interactive trace processor shell.\n"
+ "Usage: %s [OPTIONS] trace_file.pb\n\n"
+ "Options:\n"
+ " -d Enable virtual table debugging.\n"
+ " -q FILE Read and execute an SQL query from a file.\n"
+ " -e FILE Export the trace into a SQLite database.\n",
+ argv[0]);
}
int TraceProcessorMain(int argc, char** argv) {
@@ -297,6 +391,7 @@
}
const char* trace_file_path = nullptr;
const char* query_file_path = nullptr;
+ const char* sqlite_file_path = nullptr;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "-d") == 0) {
EnableSQLiteVtableDebugging();
@@ -309,6 +404,19 @@
}
query_file_path = argv[i];
continue;
+ } else if (strcmp(argv[i], "-e") == 0) {
+ if (++i == argc) {
+ PrintUsage(argv);
+ return 1;
+ }
+ sqlite_file_path = argv[i];
+ continue;
+ } else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
+ PrintUsage(argv);
+ return 0;
+ } else if (argv[i][0] == '-') {
+ PERFETTO_ELOG("Unknown option: %s", argv[i]);
+ return 1;
}
trace_file_path = argv[i];
}
@@ -375,14 +483,26 @@
signal(SIGINT, [](int) { g_tp->InterruptQuery(); });
#endif
- // If there is no query file, start a shell.
- if (query_file_path == nullptr) {
- return StartInteractiveShell();
+ int ret = 0;
+
+ // If we were given a query file, first load and execute it.
+ if (query_file_path) {
+ base::ScopedFstream file(fopen(query_file_path, "r"));
+ ret = RunQueryAndPrintResult(file.get(), stdout);
}
- // Otherwise run the queries and print the results.
- base::ScopedFstream file(fopen(query_file_path, "r"));
- return RunQueryAndPrintResult(file.get(), stdout);
+ // After this we can dump the database and exit if needed.
+ if (ret == 0 && sqlite_file_path) {
+ return ExportTraceToDatabase(sqlite_file_path);
+ }
+
+ // If we ran an automated query, exit.
+ if (query_file_path) {
+ return ret;
+ }
+
+ // Otherwise start an interactive shell.
+ return StartInteractiveShell();
}
} // namespace