blob: 2e84ac955f3e8e1bc4437c72465734a7c5e7cf17 [file] [log] [blame]
/*
* Copyright (C) 2023 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_engine.h"
#include <memory>
#include <optional>
#include <utility>
#include "perfetto/base/status.h"
#include "perfetto/ext/base/string_utils.h"
#include "src/trace_processor/sqlite/db_sqlite_table.h"
#include "src/trace_processor/sqlite/query_cache.h"
#include "src/trace_processor/sqlite/scoped_db.h"
#include "src/trace_processor/sqlite/sql_source.h"
#include "src/trace_processor/sqlite/sqlite_table.h"
#include "src/trace_processor/sqlite/sqlite_utils.h"
// In Android and Chromium tree builds, we don't have the percentile module.
// Just don't include it.
#if PERFETTO_BUILDFLAG(PERFETTO_TP_PERCENTILE)
// defined in sqlite_src/ext/misc/percentile.c
extern "C" int sqlite3_percentile_init(sqlite3* db,
char** error,
const sqlite3_api_routines* api);
#endif // PERFETTO_BUILDFLAG(PERFETTO_TP_PERCENTILE)
namespace perfetto {
namespace trace_processor {
namespace {
void EnsureSqliteInitialized() {
// sqlite3_initialize isn't actually thread-safe despite being documented
// as such; we need to make sure multiple TraceProcessorImpl instances don't
// call it concurrently and only gets called once per process, instead.
static bool init_once = [] { return sqlite3_initialize() == SQLITE_OK; }();
PERFETTO_CHECK(init_once);
}
void InitializeSqlite(sqlite3* db) {
char* error = nullptr;
sqlite3_exec(db, "PRAGMA temp_store=2", nullptr, nullptr, &error);
if (error) {
PERFETTO_FATAL("Error setting pragma temp_store: %s", error);
}
// In Android tree builds, we don't have the percentile module.
// Just don't include it.
#if PERFETTO_BUILDFLAG(PERFETTO_TP_PERCENTILE)
sqlite3_percentile_init(db, &error, nullptr);
if (error) {
PERFETTO_ELOG("Error initializing: %s", error);
sqlite3_free(error);
}
#endif
}
std::optional<uint32_t> GetErrorOffsetDb(sqlite3* db) {
int offset = sqlite3_error_offset(db);
return offset == -1 ? std::nullopt
: std::make_optional(static_cast<uint32_t>(offset));
}
} // namespace
SqliteEngine::SqliteEngine() {
sqlite3* db = nullptr;
EnsureSqliteInitialized();
PERFETTO_CHECK(sqlite3_open(":memory:", &db) == SQLITE_OK);
InitializeSqlite(db);
db_.reset(std::move(db));
}
SqliteEngine::~SqliteEngine() {
// IMPORTANT: the order of operations in this destructor is very sensitive and
// should not be changed without careful consideration of the consequences.
// Thankfully, because we are very aggressive with PERFETTO_CHECK, mistakes
// will usually manifest as crashes, but this is not guaranteed.
// Drop any explicitly created virtual tables before destroying the database
// so that any prepared statements are correctly finalized. Note that we need
// to do this in two steps (first create all the SQLs before then executing
// them) because |OnSqliteTableDestroyed| will be called as each DROP is
// executed.
std::vector<std::string> drop_stmts;
for (auto it = sqlite_tables_.GetIterator(); it; ++it) {
if (it.value() != SqliteTable::TableType::kExplicitCreate) {
continue;
}
base::StackString<1024> drop("DROP TABLE %s", it.key().c_str());
drop_stmts.emplace_back(drop.ToStdString());
}
for (const auto& drop : drop_stmts) {
int ret = sqlite3_exec(db(), drop.c_str(), nullptr, nullptr, nullptr);
PERFETTO_CHECK(ret == SQLITE_OK);
}
// It is important to unregister any functions that have been registered with
// the database before destroying it. This is because functions can hold onto
// prepared statements, which must be finalized before database destruction.
for (auto it = fn_ctx_.GetIterator(); it; ++it) {
int ret = sqlite3_create_function_v2(db_.get(), it.key().first.c_str(),
it.key().second, SQLITE_UTF8, nullptr,
nullptr, nullptr, nullptr, nullptr);
PERFETTO_CHECK(ret == SQLITE_OK);
}
fn_ctx_.Clear();
// Reset the database itself.
db_.reset();
// SQLite is not guaranteed to pick saved tables back up when destroyed as
// from it's perspective, it has called xDisconnect. Make sure to do that
// ourselves.
saved_tables_.Clear();
// The above operations should have cleared all the tables.
PERFETTO_CHECK(sqlite_tables_.size() == 0);
}
base::StatusOr<SqliteEngine::PreparedStatement> SqliteEngine::PrepareStatement(
SqlSource sql) {
PERFETTO_TP_TRACE(metatrace::Category::QUERY, "QUERY_PREPARE");
sqlite3_stmt* raw_stmt = nullptr;
int err =
sqlite3_prepare_v2(db_.get(), sql.sql().c_str(), -1, &raw_stmt, nullptr);
if (err != SQLITE_OK) {
const char* errmsg = sqlite3_errmsg(db_.get());
std::string frame = sql.AsTracebackFrame(GetErrorOffset());
base::Status status = base::ErrStatus("%s%s", frame.c_str(), errmsg);
status.SetPayload("perfetto.dev/has_traceback", "true");
return status;
}
if (!raw_stmt) {
return base::ErrStatus("No SQL to execute");
}
return PreparedStatement{ScopedStmt(raw_stmt), std::move(sql)};
}
base::Status SqliteEngine::RegisterFunction(const char* name,
int argc,
Fn* fn,
void* ctx,
FnCtxDestructor* destructor,
bool deterministic) {
int flags = SQLITE_UTF8 | (deterministic ? SQLITE_DETERMINISTIC : 0);
int ret =
sqlite3_create_function_v2(db_.get(), name, static_cast<int>(argc), flags,
ctx, fn, nullptr, nullptr, destructor);
if (ret != SQLITE_OK) {
return base::ErrStatus("Unable to register function with name %s", name);
}
*fn_ctx_.Insert(std::make_pair(name, argc), ctx).first = ctx;
return base::OkStatus();
}
base::Status SqliteEngine::DeclareVirtualTable(const std::string& create_stmt) {
int res = sqlite3_declare_vtab(db_.get(), create_stmt.c_str());
if (res != SQLITE_OK) {
return base::ErrStatus("Declare vtab failed: %s",
sqlite3_errmsg(db_.get()));
}
return base::OkStatus();
}
base::Status SqliteEngine::SaveSqliteTable(const std::string& table_name,
std::unique_ptr<SqliteTable> table) {
auto res = saved_tables_.Insert(table_name, {});
if (!res.second) {
return base::ErrStatus("Table with name %s already is saved",
table_name.c_str());
}
*res.first = std::move(table);
return base::OkStatus();
}
base::StatusOr<std::unique_ptr<SqliteTable>> SqliteEngine::RestoreSqliteTable(
const std::string& table_name) {
auto* res = saved_tables_.Find(table_name);
if (!res) {
return base::ErrStatus("Table with name %s does not exist in saved state",
table_name.c_str());
}
std::unique_ptr<SqliteTable> table = std::move(*res);
PERFETTO_CHECK(saved_tables_.Erase(table_name));
return std::move(table);
}
void* SqliteEngine::GetFunctionContext(const std::string& name, int argc) {
auto* res = fn_ctx_.Find(std::make_pair(name, argc));
return res ? *res : nullptr;
}
std::optional<uint32_t> SqliteEngine::GetErrorOffset() const {
return GetErrorOffsetDb(db_.get());
}
void SqliteEngine::OnSqliteTableCreated(const std::string& name,
SqliteTable::TableType type) {
auto it_and_inserted = sqlite_tables_.Insert(name, type);
PERFETTO_CHECK(it_and_inserted.second);
}
void SqliteEngine::OnSqliteTableDestroyed(const std::string& name) {
PERFETTO_CHECK(sqlite_tables_.Erase(name));
}
SqliteEngine::PreparedStatement::PreparedStatement(ScopedStmt stmt,
SqlSource tagged)
: stmt_(std::move(stmt)), sql_source_(std::move(tagged)) {}
bool SqliteEngine::PreparedStatement::Step() {
PERFETTO_TP_TRACE(metatrace::Category::QUERY, "STMT_STEP",
[this](metatrace::Record* record) {
record->AddArg("SQL", expanded_sql());
});
// Now step once into |cur_stmt| so that when we prepare the next statment
// we will have executed any dependent bytecode in this one.
int err = sqlite3_step(stmt_.get());
if (err == SQLITE_ROW) {
return true;
}
if (err == SQLITE_DONE) {
return false;
}
sqlite3* db = sqlite3_db_handle(stmt_.get());
std::string frame = sql_source_.AsTracebackFrame(GetErrorOffsetDb(db));
const char* errmsg = sqlite3_errmsg(db);
status_ = base::ErrStatus("%s%s", frame.c_str(), errmsg);
return false;
}
bool SqliteEngine::PreparedStatement::IsDone() const {
return !sqlite3_stmt_busy(stmt_.get());
}
const char* SqliteEngine::PreparedStatement::sql() const {
return sqlite3_sql(stmt_.get());
}
const char* SqliteEngine::PreparedStatement::expanded_sql() {
if (!expanded_sql_) {
expanded_sql_.reset(sqlite3_expanded_sql(stmt_.get()));
}
return expanded_sql_.get();
}
} // namespace trace_processor
} // namespace perfetto