| /* |
| * 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 <sqlite3.h> |
| #include <cstdint> |
| #include <optional> |
| #include <string> |
| #include <utility> |
| |
| #include "perfetto/base/build_config.h" |
| #include "perfetto/base/logging.h" |
| #include "perfetto/base/status.h" |
| #include "perfetto/public/compiler.h" |
| #include "src/trace_processor/sqlite/scoped_db.h" |
| #include "src/trace_processor/sqlite/sql_source.h" |
| #include "src/trace_processor/tp_metatrace.h" |
| |
| #include "protos/perfetto/trace_processor/metatrace_categories.pbzero.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::trace_processor { |
| namespace { |
| |
| void EnsureSqliteInitialized() { |
| // sqlite3_initialize isn't actually thread-safe in standalone builds because |
| // we build with SQLITE_THREADSAFE=0. Ensure it's only called from a single |
| // thread. |
| static bool init_once = [] { |
| // Enabling memstatus causes a lock to be taken on every malloc/free in |
| // SQLite to update the memory statistics. This can cause massive contention |
| // in trace processor when multiple instances are used in parallel. |
| // Fix this by disabling the memstatus API which we don't make use of in |
| // any case. See b/335019324 for more info on this. |
| int ret = sqlite3_config(SQLITE_CONFIG_MEMSTATUS, 0); |
| |
| // As much as it is painful, we need to catch instances of SQLITE_MISUSE |
| // here against all the advice of the SQLite developers and lalitm@'s |
| // intuition: SQLITE_MISUSE for sqlite3_config really means: that someone |
| // else has already initialized SQLite. As we are an embeddable library, |
| // it's very possible that the process embedding us has initialized SQLite |
| // in a different way to what we want to do and, if so, we should respect |
| // their choice. |
| // |
| // TODO(lalitm): ideally we would have an sqlite3_is_initialized API we |
| // could use to gate the above check but that doesn't exist: report this |
| // issue to SQLite developers and see if such an API could be added. If so |
| // we can remove this check. |
| if (ret == SQLITE_MISUSE) { |
| return true; |
| } |
| |
| PERFETTO_CHECK(ret == SQLITE_OK); |
| 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. |
| #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(); |
| |
| // Ensure that we open the database with mutexes disabled: this is because |
| // trace processor as a whole cannot be used from multiple threads so there is |
| // no point paying the (potentially significant) cost of mutexes at the SQLite |
| // level. |
| static constexpr int kSqliteOpenFlags = |
| SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX; |
| PERFETTO_CHECK(sqlite3_open_v2(":memory:", &db, kSqliteOpenFlags, nullptr) == |
| SQLITE_OK); |
| InitializeSqlite(db); |
| db_.reset(db); |
| } |
| |
| SqliteEngine::~SqliteEngine() { |
| // 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); |
| if (PERFETTO_UNLIKELY(ret != SQLITE_OK)) { |
| PERFETTO_FATAL("Failed to drop function: '%s'", it.key().first.c_str()); |
| } |
| } |
| fn_ctx_.Clear(); |
| } |
| |
| SqliteEngine::PreparedStatement SqliteEngine::PrepareStatement(SqlSource sql) { |
| PERFETTO_TP_TRACE(metatrace::Category::QUERY_DETAILED, "QUERY_PREPARE"); |
| sqlite3_stmt* raw_stmt = nullptr; |
| int err = |
| sqlite3_prepare_v2(db_.get(), sql.sql().c_str(), -1, &raw_stmt, nullptr); |
| PreparedStatement statement{ScopedStmt(raw_stmt), std::move(sql)}; |
| if (err != SQLITE_OK) { |
| const char* errmsg = sqlite3_errmsg(db_.get()); |
| std::string frame = |
| statement.sql_source_.AsTracebackForSqliteOffset(GetErrorOffset()); |
| base::Status status = base::ErrStatus("%s%s", frame.c_str(), errmsg); |
| status.SetPayload("perfetto.dev/has_traceback", "true"); |
| |
| statement.status_ = std::move(status); |
| return statement; |
| } |
| if (!raw_stmt) { |
| statement.status_ = base::ErrStatus("No SQL to execute"); |
| } |
| return statement; |
| } |
| |
| 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::RegisterAggregateFunction( |
| const char* name, |
| int argc, |
| AggregateFnStep* step, |
| AggregateFnFinal* final, |
| 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, nullptr, step, final, destructor); |
| if (ret != SQLITE_OK) { |
| return base::ErrStatus("Unable to register function with name %s", name); |
| } |
| return base::OkStatus(); |
| } |
| |
| base::Status SqliteEngine::RegisterWindowFunction(const char* name, |
| int argc, |
| WindowFnStep* step, |
| WindowFnInverse* inverse, |
| WindowFnValue* value, |
| WindowFnFinal* final, |
| void* ctx, |
| FnCtxDestructor* destructor, |
| bool deterministic) { |
| int flags = SQLITE_UTF8 | (deterministic ? SQLITE_DETERMINISTIC : 0); |
| int ret = sqlite3_create_window_function( |
| db_.get(), name, static_cast<int>(argc), flags, ctx, step, final, value, |
| inverse, destructor); |
| if (ret != SQLITE_OK) { |
| return base::ErrStatus("Unable to register function with name %s", name); |
| } |
| return base::OkStatus(); |
| } |
| |
| base::Status SqliteEngine::UnregisterFunction(const char* name, int argc) { |
| int ret = sqlite3_create_function_v2(db_.get(), name, static_cast<int>(argc), |
| SQLITE_UTF8, nullptr, nullptr, nullptr, |
| nullptr, nullptr); |
| if (ret != SQLITE_OK) { |
| return base::ErrStatus("Unable to unregister function with name %s", name); |
| } |
| fn_ctx_.Erase({name, argc}); |
| 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(); |
| } |
| |
| 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()); |
| } |
| |
| SqliteEngine::PreparedStatement::PreparedStatement(ScopedStmt stmt, |
| SqlSource source) |
| : stmt_(std::move(stmt)), |
| expanded_sql_(sqlite3_expanded_sql(stmt_.get())), |
| sql_source_(std::move(source)) {} |
| |
| bool SqliteEngine::PreparedStatement::Step() { |
| PERFETTO_TP_TRACE(metatrace::Category::QUERY_DETAILED, "STMT_STEP", |
| [this](metatrace::Record* record) { |
| record->AddArg("Original SQL", original_sql()); |
| record->AddArg("Executed SQL", 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_.AsTracebackForSqliteOffset(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::original_sql() const { |
| return sql_source_.original_sql().c_str(); |
| } |
| |
| const char* SqliteEngine::PreparedStatement::sql() const { |
| return expanded_sql_.get(); |
| } |
| |
| } // namespace perfetto::trace_processor |