| /* | |
| * Copyright (C) 2026 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/sqlite/sqlite_export.h" | |
| #include <cstdint> | |
| #include <memory> | |
| #include <string> | |
| #include <utility> | |
| #include "perfetto/base/logging.h" | |
| #include "perfetto/base/status.h" | |
| #include "perfetto/ext/base/status_macros.h" | |
| #include "perfetto/ext/base/string_utils.h" | |
| #include "perfetto/trace_processor/io.h" | |
| #include "perfetto/trace_processor/trace_processor.h" | |
| #include "src/trace_processor/sqlite/file_system_vfs.h" | |
| namespace perfetto::trace_processor { | |
| namespace { | |
| constexpr char kExportDbName[] = "perfetto_export"; | |
| std::string MakeFileUri(const std::string& path, const char* vfs_name) { | |
| std::string uri = "file:"; | |
| for (char c : path) { | |
| bool unreserved = (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || | |
| (c >= '0' && c <= '9') || c == '-' || c == '.' || | |
| c == '_' || c == '~' || c == '/' || c == ':'; | |
| if (unreserved) { | |
| uri.push_back(c); | |
| } else { | |
| base::StackString<4> escaped("%%%02X", static_cast<uint8_t>(c)); | |
| uri.append(escaped.c_str()); | |
| } | |
| } | |
| uri.append("?vfs="); | |
| uri.append(vfs_name); | |
| return uri; | |
| } | |
| base::Status ExecuteToCompletion(TraceProcessor* trace_processor, | |
| const std::string& sql) { | |
| auto iterator = trace_processor->ExecuteQuery(sql); | |
| while (iterator.Next()) { | |
| } | |
| return iterator.Status(); | |
| } | |
| base::Status ExportAttachedDatabase(TraceProcessor* trace_processor) { | |
| // The export database is disposable and regenerated from the trace on | |
| // failure, so trade durability for speed and lower I/O. | |
| RETURN_IF_ERROR(ExecuteToCompletion( | |
| trace_processor, | |
| "PRAGMA " + std::string(kExportDbName) + ".journal_mode=OFF")); | |
| RETURN_IF_ERROR(ExecuteToCompletion( | |
| trace_processor, | |
| "PRAGMA " + std::string(kExportDbName) + ".synchronous=OFF")); | |
| // Preserve the existing SQLite export contract: materialize all tables | |
| // visible through perfetto_tables, including runtime SQL tables and virtual | |
| // tables. | |
| auto tables = | |
| trace_processor->ExecuteQuery("SELECT name FROM perfetto_tables"); | |
| while (tables.Next()) { | |
| std::string table_name = tables.Get(0).string_value; | |
| PERFETTO_CHECK(!base::Contains(table_name, '\'')); | |
| RETURN_IF_ERROR(ExecuteToCompletion( | |
| trace_processor, "CREATE TABLE " + std::string(kExportDbName) + "." + | |
| table_name + " AS SELECT * FROM " + table_name)); | |
| } | |
| RETURN_IF_ERROR(tables.Status()); | |
| // Preserve SQL views as views over the exported tables. | |
| auto views = trace_processor->ExecuteQuery( | |
| "SELECT sql FROM sqlite_master WHERE type='view'"); | |
| while (views.Next()) { | |
| std::string sql = views.Get(0).string_value; | |
| const std::string kPrefix = "CREATE VIEW "; | |
| PERFETTO_CHECK(base::StartsWith(sql, kPrefix)); | |
| sql = sql.substr(0, kPrefix.size()) + std::string(kExportDbName) + "." + | |
| sql.substr(kPrefix.size()); | |
| RETURN_IF_ERROR(ExecuteToCompletion(trace_processor, sql)); | |
| } | |
| return views.Status(); | |
| } | |
| } // namespace | |
| base::Status ExportSqliteDatabase(TraceProcessor* trace_processor, | |
| io::FileSystem* file_system, | |
| const std::string& path) { | |
| if (!trace_processor) { | |
| return base::ErrStatus("Trace Processor is null"); | |
| } | |
| if (!file_system) { | |
| return base::ErrStatus("SQLite export filesystem is null"); | |
| } | |
| if (path.empty()) { | |
| return base::ErrStatus("SQLite export path is empty"); | |
| } | |
| io::FileOpenOptions options; | |
| options.access = io::FileAccess::kReadWrite; | |
| options.create = true; | |
| options.truncate = true; | |
| std::unique_ptr<io::File> output; | |
| RETURN_IF_ERROR(file_system->OpenFile(path, options, &output)); | |
| output.reset(); | |
| ASSIGN_OR_RETURN(auto vfs, SqliteFileSystemVfs::Create(file_system)); | |
| std::string uri = MakeFileUri(path, vfs->name()); | |
| RETURN_IF_ERROR(ExecuteToCompletion( | |
| trace_processor, "ATTACH DATABASE '" + uri + "' AS " + kExportDbName)); | |
| base::Status export_status = ExportAttachedDatabase(trace_processor); | |
| base::Status detach_status = ExecuteToCompletion( | |
| trace_processor, "DETACH DATABASE " + std::string(kExportDbName)); | |
| if (!detach_status.ok()) { | |
| // The connection can still hold sqlite3_file objects which reference this | |
| // VFS. Keep the VFS registered rather than leaving dangling callbacks. | |
| base::ignore_result(vfs.release()); | |
| } | |
| if (!export_status.ok()) { | |
| return export_status; | |
| } | |
| return detach_status; | |
| } | |
| } // namespace perfetto::trace_processor |