blob: 35ad1501d5506ccd1076164e1d12162296180815 [file] [log] [blame] [edit]
/*
* 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.
*/
#define STATSD_DEBUG false // STOPSHIP if true
#include "Log.h"
#include "utils/DbUtils.h"
#include <android/api-level.h>
#include "FieldValue.h"
#include "android-base/properties.h"
#include "android-base/stringprintf.h"
#include "stats_log_util.h"
#include "storage/StorageManager.h"
namespace android {
namespace os {
namespace statsd {
namespace dbutils {
using ::android::os::statsd::FLOAT;
using ::android::os::statsd::INT;
using ::android::os::statsd::LONG;
using ::android::os::statsd::StorageManager;
using ::android::os::statsd::STRING;
using base::GetProperty;
using base::StringPrintf;
const string TABLE_NAME_PREFIX = "metric_";
const string COLUMN_NAME_ATOM_TAG = "atomId";
const string COLUMN_NAME_EVENT_ELAPSED_CLOCK_NS = "elapsedTimestampNs";
const string COLUMN_NAME_EVENT_WALL_CLOCK_NS = "wallTimestampNs";
const string COLUMN_NAME_SDK_VERSION = "sdkVersion";
const string COLUMN_NAME_MODEL = "model";
const string COLUMN_NAME_PRODUCT = "product";
const string COLUMN_NAME_HARDWARE = "hardware";
const string COLUMN_NAME_DEVICE = "device";
const string COLUMN_NAME_BUILD = "osBuild";
const string COLUMN_NAME_FINGERPRINT = "fingerprint";
const string COLUMN_NAME_BRAND = "brand";
const string COLUMN_NAME_MANUFACTURER = "manufacturer";
const string COLUMN_NAME_BOARD = "board";
static std::vector<std::string> getExpectedTableSchema(const LogEvent& logEvent) {
vector<std::string> result;
for (const FieldValue& fieldValue : logEvent.getValues()) {
if (fieldValue.mField.getDepth() > 0) {
// Repeated fields are not supported.
continue;
}
switch (fieldValue.mValue.getType()) {
case INT:
case LONG:
result.push_back("INTEGER");
break;
case STRING:
result.push_back("TEXT");
break;
case FLOAT:
result.push_back("REAL");
break;
default:
// Byte array fields are not supported.
break;
}
}
return result;
}
static int integrityCheckCallback(void*, int colCount, char** queryResults, char**) {
if (colCount == 0 || strcmp(queryResults[0], "ok") != 0) {
// Returning 1 is an error code that causes exec to stop and error.
return 1;
}
return 0;
}
string getDbName(const ConfigKey& key) {
return StringPrintf("%s/%d_%lld.db", STATS_RESTRICTED_DATA_DIR, key.GetUid(),
(long long)key.GetId());
}
static string getCreateSqlString(const int64_t metricId, const LogEvent& event) {
string result = StringPrintf("CREATE TABLE IF NOT EXISTS %s%s", TABLE_NAME_PREFIX.c_str(),
reformatMetricId(metricId).c_str());
result += StringPrintf("(%s INTEGER,%s INTEGER,%s INTEGER,", COLUMN_NAME_ATOM_TAG.c_str(),
COLUMN_NAME_EVENT_ELAPSED_CLOCK_NS.c_str(),
COLUMN_NAME_EVENT_WALL_CLOCK_NS.c_str());
for (size_t fieldId = 1; fieldId <= event.getValues().size(); ++fieldId) {
const FieldValue& fieldValue = event.getValues()[fieldId - 1];
if (fieldValue.mField.getDepth() > 0) {
// Repeated fields are not supported.
continue;
}
switch (fieldValue.mValue.getType()) {
case INT:
case LONG:
result += StringPrintf("field_%d INTEGER,", fieldValue.mField.getPosAtDepth(0));
break;
case STRING:
result += StringPrintf("field_%d TEXT,", fieldValue.mField.getPosAtDepth(0));
break;
case FLOAT:
result += StringPrintf("field_%d REAL,", fieldValue.mField.getPosAtDepth(0));
break;
default:
// Byte array fields are not supported.
break;
}
}
result.pop_back();
result += ") STRICT;";
return result;
}
string reformatMetricId(const int64_t metricId) {
return metricId < 0 ? StringPrintf("n%lld", (long long)metricId * -1)
: StringPrintf("%lld", (long long)metricId);
}
bool createTableIfNeeded(const ConfigKey& key, const int64_t metricId, const LogEvent& event) {
const string dbName = getDbName(key);
sqlite3* db;
if (sqlite3_open(dbName.c_str(), &db) != SQLITE_OK) {
sqlite3_close(db);
return false;
}
char* error = nullptr;
string zSql = getCreateSqlString(metricId, event);
sqlite3_exec(db, zSql.c_str(), nullptr, nullptr, &error);
sqlite3_close(db);
if (error) {
ALOGW("Failed to create table to db: %s", error);
return false;
}
return true;
}
bool isEventCompatible(const ConfigKey& key, const int64_t metricId, const LogEvent& event) {
const string dbName = getDbName(key);
sqlite3* db;
if (sqlite3_open(dbName.c_str(), &db) != SQLITE_OK) {
sqlite3_close(db);
return false;
}
string zSql = StringPrintf("PRAGMA table_info(metric_%s);", reformatMetricId(metricId).c_str());
string err;
std::vector<int32_t> columnTypes;
std::vector<string> columnNames;
std::vector<std::vector<std::string>> rows;
if (!query(key, zSql, rows, columnTypes, columnNames, err)) {
ALOGE("Failed to check table schema for metric %lld: %s", (long long)metricId, err.c_str());
sqlite3_close(db);
return false;
}
// Sample query result
// cid name type notnull dflt_value pk
// --- ----------------- ------- ------- ---------- --
// 0 atomId INTEGER 0 (null) 0
// 1 elapsedTimestampNs INTEGER 0 (null) 0
// 2 wallTimestampNs INTEGER 0 (null) 0
// 3 field_1 INTEGER 0 (null) 0
// 4 field_2 TEXT 0 (null) 0
std::vector<string> tableSchema;
for (size_t i = 3; i < rows.size(); ++i) { // Atom fields start at the third row
tableSchema.push_back(rows[i][2]); // The third column stores the data type for the column
}
sqlite3_close(db);
// An empty rows vector implies the table has not yet been created.
return rows.size() == 0 || getExpectedTableSchema(event) == tableSchema;
}
bool deleteTable(const ConfigKey& key, const int64_t metricId) {
const string dbName = getDbName(key);
sqlite3* db;
if (sqlite3_open(dbName.c_str(), &db) != SQLITE_OK) {
sqlite3_close(db);
return false;
}
string zSql = StringPrintf("DROP TABLE metric_%s", reformatMetricId(metricId).c_str());
char* error = nullptr;
sqlite3_exec(db, zSql.c_str(), nullptr, nullptr, &error);
sqlite3_close(db);
if (error) {
ALOGW("Failed to drop table from db: %s", error);
return false;
}
return true;
}
void deleteDb(const ConfigKey& key) {
const string dbName = getDbName(key);
StorageManager::deleteFile(dbName.c_str());
}
sqlite3* getDb(const ConfigKey& key) {
const string dbName = getDbName(key);
sqlite3* db;
if (sqlite3_open(dbName.c_str(), &db) == SQLITE_OK) {
return db;
}
return nullptr;
}
void closeDb(sqlite3* db) {
sqlite3_close(db);
}
static bool getInsertSqlStmt(sqlite3* db, sqlite3_stmt** stmt, const int64_t metricId,
const vector<LogEvent>& events, string& err) {
string result =
StringPrintf("INSERT INTO metric_%s VALUES", reformatMetricId(metricId).c_str());
for (auto& logEvent : events) {
result += StringPrintf("(%d, %lld, %lld,", logEvent.GetTagId(),
(long long)logEvent.GetElapsedTimestampNs(),
(long long)logEvent.GetLogdTimestampNs());
for (auto& fieldValue : logEvent.getValues()) {
if (fieldValue.mField.getDepth() > 0 || fieldValue.mValue.getType() == STORAGE) {
// Repeated fields and byte fields are not supported.
continue;
}
result += "?,";
}
result.pop_back();
result += "),";
}
result.pop_back();
result += ";";
if (sqlite3_prepare_v2(db, result.c_str(), -1, stmt, nullptr) != SQLITE_OK) {
err = sqlite3_errmsg(db);
return false;
}
// ? parameters start with an index of 1 from start of query string to the
// end.
int32_t index = 1;
for (auto& logEvent : events) {
for (auto& fieldValue : logEvent.getValues()) {
if (fieldValue.mField.getDepth() > 0 || fieldValue.mValue.getType() == STORAGE) {
// Repeated fields and byte fields are not supported.
continue;
}
switch (fieldValue.mValue.getType()) {
case INT:
sqlite3_bind_int(*stmt, index, fieldValue.mValue.int_value);
break;
case LONG:
sqlite3_bind_int64(*stmt, index, fieldValue.mValue.long_value);
break;
case STRING:
sqlite3_bind_text(*stmt, index, fieldValue.mValue.str_value.c_str(), -1,
SQLITE_STATIC);
break;
case FLOAT:
sqlite3_bind_double(*stmt, index, fieldValue.mValue.float_value);
break;
default:
// Byte array fields are not supported.
break;
}
++index;
}
}
return true;
}
bool insert(const ConfigKey& key, const int64_t metricId, const vector<LogEvent>& events,
string& error) {
const string dbName = getDbName(key);
sqlite3* db;
if (sqlite3_open(dbName.c_str(), &db) != SQLITE_OK) {
error = sqlite3_errmsg(db);
sqlite3_close(db);
return false;
}
bool success = insert(db, metricId, events, error);
sqlite3_close(db);
return success;
}
bool insert(sqlite3* db, const int64_t metricId, const vector<LogEvent>& events, string& error) {
sqlite3_stmt* stmt = nullptr;
if (!getInsertSqlStmt(db, &stmt, metricId, events, error)) {
ALOGW("Failed to generate prepared sql insert query %s", error.c_str());
sqlite3_finalize(stmt);
return false;
}
if (sqlite3_step(stmt) != SQLITE_DONE) {
error = sqlite3_errmsg(db);
ALOGW("Failed to insert data to db: %s", error.c_str());
sqlite3_finalize(stmt);
return false;
}
sqlite3_finalize(stmt);
return true;
}
bool query(const ConfigKey& key, const string& zSql, vector<vector<string>>& rows,
vector<int32_t>& columnTypes, vector<string>& columnNames, string& err) {
const string dbName = getDbName(key);
sqlite3* db;
if (sqlite3_open_v2(dbName.c_str(), &db, SQLITE_OPEN_READONLY, nullptr) != SQLITE_OK) {
err = sqlite3_errmsg(db);
sqlite3_close(db);
return false;
}
sqlite3_stmt* stmt;
if (sqlite3_prepare_v2(db, zSql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) {
err = sqlite3_errmsg(db);
sqlite3_finalize(stmt);
sqlite3_close(db);
return false;
}
int result = sqlite3_step(stmt);
bool firstIter = true;
while (result == SQLITE_ROW) {
int colCount = sqlite3_column_count(stmt);
vector<string> rowData(colCount);
for (int i = 0; i < colCount; ++i) {
if (firstIter) {
int32_t columnType = sqlite3_column_type(stmt, i);
// Needed to convert to java compatible cursor types. See AbstractCursor#getType()
if (columnType == 5) {
columnType = 0; // Remap 5 (null type) to 0 for java cursor
}
columnTypes.push_back(columnType);
columnNames.push_back(reinterpret_cast<const char*>(sqlite3_column_name(stmt, i)));
}
const unsigned char* textResult = sqlite3_column_text(stmt, i);
string colData =
textResult != nullptr ? string(reinterpret_cast<const char*>(textResult)) : "";
rowData[i] = std::move(colData);
}
rows.push_back(std::move(rowData));
firstIter = false;
result = sqlite3_step(stmt);
}
sqlite3_finalize(stmt);
if (result != SQLITE_DONE) {
err = sqlite3_errmsg(db);
sqlite3_close(db);
return false;
}
sqlite3_close(db);
return true;
}
bool flushTtl(sqlite3* db, const int64_t metricId, const int64_t ttlWallClockNs) {
string zSql = StringPrintf("DELETE FROM %s%s WHERE %s <= %lld", TABLE_NAME_PREFIX.c_str(),
reformatMetricId(metricId).c_str(),
COLUMN_NAME_EVENT_WALL_CLOCK_NS.c_str(), (long long)ttlWallClockNs);
char* error = nullptr;
sqlite3_exec(db, zSql.c_str(), nullptr, nullptr, &error);
if (error) {
ALOGW("Failed to enforce ttl: %s", error);
return false;
}
return true;
}
void verifyIntegrityAndDeleteIfNecessary(const ConfigKey& configKey) {
const string dbName = getDbName(configKey);
sqlite3* db;
if (sqlite3_open(dbName.c_str(), &db) != SQLITE_OK) {
sqlite3_close(db);
return;
}
string zSql = "PRAGMA integrity_check";
char* error = nullptr;
sqlite3_exec(db, zSql.c_str(), integrityCheckCallback, nullptr, &error);
if (error) {
StatsdStats::getInstance().noteDbCorrupted(configKey);
ALOGW("Integrity Check failed %s", error);
sqlite3_close(db);
deleteDb(configKey);
return;
}
sqlite3_close(db);
}
static bool getDeviceInfoInsertStmt(sqlite3* db, sqlite3_stmt** stmt, string error) {
string insertSql = StringPrintf("INSERT INTO device_info VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
if (sqlite3_prepare_v2(db, insertSql.c_str(), -1, stmt, nullptr) != SQLITE_OK) {
error = sqlite3_errmsg(db);
return false;
}
// ? parameters start with an index of 1 from start of query string to the end.
int32_t index = 1;
int32_t sdkVersion = android_get_device_api_level();
sqlite3_bind_int(*stmt, index, sdkVersion);
++index;
string model = GetProperty("ro.product.model", "(unknown)");
sqlite3_bind_text(*stmt, index, model.c_str(), -1, SQLITE_TRANSIENT);
++index;
string product = GetProperty("ro.product.name", "(unknown)");
sqlite3_bind_text(*stmt, index, product.c_str(), -1, SQLITE_TRANSIENT);
++index;
string hardware = GetProperty("ro.hardware", "(unknown)");
sqlite3_bind_text(*stmt, index, hardware.c_str(), -1, SQLITE_TRANSIENT);
++index;
string device = GetProperty("ro.product.device", "(unknown)");
sqlite3_bind_text(*stmt, index, device.c_str(), -1, SQLITE_TRANSIENT);
++index;
string osBuild = GetProperty("ro.build.id", "(unknown)");
sqlite3_bind_text(*stmt, index, osBuild.c_str(), -1, SQLITE_TRANSIENT);
++index;
string fingerprint = GetProperty("ro.build.fingerprint", "(unknown)");
sqlite3_bind_text(*stmt, index, fingerprint.c_str(), -1, SQLITE_TRANSIENT);
++index;
string brand = GetProperty("ro.product.brand", "(unknown)");
sqlite3_bind_text(*stmt, index, brand.c_str(), -1, SQLITE_TRANSIENT);
++index;
string manufacturer = GetProperty("ro.product.manufacturer", "(unknown)");
sqlite3_bind_text(*stmt, index, manufacturer.c_str(), -1, SQLITE_TRANSIENT);
++index;
string board = GetProperty("ro.product.board", "(unknown)");
sqlite3_bind_text(*stmt, index, board.c_str(), -1, SQLITE_TRANSIENT);
++index;
return true;
}
bool updateDeviceInfoTable(const ConfigKey& key, string& error) {
const string dbName = getDbName(key);
sqlite3* db;
if (sqlite3_open(dbName.c_str(), &db) != SQLITE_OK) {
error = sqlite3_errmsg(db);
sqlite3_close(db);
return false;
}
string dropTableSql = "DROP TABLE device_info";
// Ignore possible error result code if table has not yet been created.
sqlite3_exec(db, dropTableSql.c_str(), nullptr, nullptr, nullptr);
string createTableSql = StringPrintf(
"CREATE TABLE device_info(%s INTEGER, %s TEXT, %s TEXT, %s TEXT, %s TEXT, %s TEXT, %s "
"TEXT, %s TEXT, %s TEXT, %s TEXT) "
"STRICT",
COLUMN_NAME_SDK_VERSION.c_str(), COLUMN_NAME_MODEL.c_str(), COLUMN_NAME_PRODUCT.c_str(),
COLUMN_NAME_HARDWARE.c_str(), COLUMN_NAME_DEVICE.c_str(), COLUMN_NAME_BUILD.c_str(),
COLUMN_NAME_FINGERPRINT.c_str(), COLUMN_NAME_BRAND.c_str(),
COLUMN_NAME_MANUFACTURER.c_str(), COLUMN_NAME_BOARD.c_str());
if (sqlite3_exec(db, createTableSql.c_str(), nullptr, nullptr, nullptr) != SQLITE_OK) {
error = sqlite3_errmsg(db);
ALOGW("Failed to create device info table %s", error.c_str());
sqlite3_close(db);
return false;
}
sqlite3_stmt* stmt = nullptr;
if (!getDeviceInfoInsertStmt(db, &stmt, error)) {
ALOGW("Failed to generate device info prepared sql insert query %s", error.c_str());
sqlite3_finalize(stmt);
sqlite3_close(db);
return false;
}
if (sqlite3_step(stmt) != SQLITE_DONE) {
error = sqlite3_errmsg(db);
ALOGW("Failed to insert data to device info table: %s", error.c_str());
sqlite3_finalize(stmt);
sqlite3_close(db);
return false;
}
sqlite3_finalize(stmt);
sqlite3_close(db);
return true;
}
} // namespace dbutils
} // namespace statsd
} // namespace os
} // namespace android