blob: 68e3c6d8445bf1b1bc6844b6312c26e1c8536bd1 [file]
/*
* 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