blob: b19a6aa5e1131ab37dc51c4fdf0987a3ec805b77 [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/predictors/resource_prefetch_predictor_tables.h"
#include <algorithm>
#include <utility>
#include "base/functional/bind.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/stringprintf.h"
#include "base/task/sequenced_task_runner.h"
#include "base/trace_event/trace_event.h"
#include "chrome/browser/predictors/lcp_critical_path_predictor/lcp_critical_path_predictor_util.h"
#include "chrome/browser/predictors/predictors_features.h"
#include "sql/statement.h"
#include "sql/transaction.h"
namespace {
const char kMetadataTableName[] = "resource_prefetch_predictor_metadata";
const char kHostRedirectTableName[] =
"resource_prefetch_predictor_host_redirect";
const char kOriginTableName[] = "resource_prefetch_predictor_origin";
const char kCreateGlobalMetadataStatementTemplate[] =
"CREATE TABLE %s ( "
"key TEXT, value INTEGER, "
"PRIMARY KEY (key))";
const char kCreateProtoTableStatementTemplate[] =
"CREATE TABLE %s ( "
"key TEXT, "
"proto BLOB, "
"PRIMARY KEY(key))";
} // namespace
namespace predictors {
// static
void ResourcePrefetchPredictorTables::TrimRedirects(
RedirectData* data,
size_t max_consecutive_misses) {
auto new_end =
std::remove_if(data->mutable_redirect_endpoints()->begin(),
data->mutable_redirect_endpoints()->end(),
[max_consecutive_misses](const RedirectStat& x) {
return x.consecutive_misses() >= max_consecutive_misses;
});
data->mutable_redirect_endpoints()->erase(
new_end, data->mutable_redirect_endpoints()->end());
}
// static
void ResourcePrefetchPredictorTables::TrimOrigins(
OriginData* data,
size_t max_consecutive_misses) {
auto* origins = data->mutable_origins();
auto new_end = std::remove_if(
origins->begin(), origins->end(), [=](const OriginStat& x) {
return x.consecutive_misses() >= max_consecutive_misses;
});
origins->erase(new_end, origins->end());
}
// static
void ResourcePrefetchPredictorTables::SortOrigins(
OriginData* data,
const std::string& main_frame_origin) {
auto* origins = data->mutable_origins();
auto it = std::ranges::find(*origins, main_frame_origin, &OriginStat::origin);
int iterator_offset = 0;
if (it != origins->end()) {
origins->SwapElements(0, it - origins->begin());
iterator_offset = 1;
}
std::sort(origins->begin() + iterator_offset, origins->end(),
[](const OriginStat& x, const OriginStat& y) {
// Decreasing score ordering.
return ComputeOriginScore(x) > ComputeOriginScore(y);
});
}
ResourcePrefetchPredictorTables::ResourcePrefetchPredictorTables(
scoped_refptr<base::SequencedTaskRunner> db_task_runner)
: sqlite_proto::TableManager(db_task_runner) {
host_redirect_table_ =
std::make_unique<sqlite_proto::KeyValueTable<RedirectData>>(
kHostRedirectTableName);
origin_table_ = std::make_unique<sqlite_proto::KeyValueTable<OriginData>>(
kOriginTableName);
}
ResourcePrefetchPredictorTables::~ResourcePrefetchPredictorTables() = default;
// static
float ResourcePrefetchPredictorTables::ComputeOriginScore(
const OriginStat& origin) {
// The ranking is done by considering, in this order:
// 1. High confidence resources (>75% and more than 10 hits)
// 2. Mandatory network access
// 3. Network accessed
// 4. Average position (decreasing)
float score = 0;
float confidence = static_cast<float>(origin.number_of_hits()) /
(origin.number_of_hits() + origin.number_of_misses());
bool is_high_confidence = confidence > .75 && origin.number_of_hits() > 10;
score += is_high_confidence * 1e6;
if (!base::FeatureList::IsEnabled(
features::kLoadingPredictorDisregardAlwaysAccessesNetwork)) {
score += origin.always_access_network() * 1e4;
}
score += origin.accessed_network() * 1e2;
score += 1e2 - origin.average_position();
return score;
}
sqlite_proto::KeyValueTable<RedirectData>*
ResourcePrefetchPredictorTables::host_redirect_table() {
return host_redirect_table_.get();
}
sqlite_proto::KeyValueTable<OriginData>*
ResourcePrefetchPredictorTables::origin_table() {
return origin_table_.get();
}
// static
bool ResourcePrefetchPredictorTables::DropTablesIfOutdated(sql::Database* db) {
int version = GetDatabaseVersion(db);
bool success = true;
// Too new is also a problem.
bool incompatible_version = version != kDatabaseVersion;
// These are deprecated tables but they still have to be removed if present.
static const char kUrlMetadataTableName[] =
"resource_prefetch_predictor_url_metadata";
static const char kHostMetadataTableName[] =
"resource_prefetch_predictor_host_metadata";
static const char kManifestTableName[] =
"resource_prefetch_predictor_manifest";
static const char kUrlResourceTableName[] = "resource_prefetch_predictor_url";
static const char kUrlRedirectTableName[] =
"resource_prefetch_predictor_url_redirect";
static const char kHostResourceTableName[] =
"resource_prefetch_predictor_host";
if (incompatible_version) {
for (const char* table_name :
{kMetadataTableName, kUrlResourceTableName, kHostResourceTableName,
kUrlRedirectTableName, kHostRedirectTableName, kManifestTableName,
kUrlMetadataTableName, kHostMetadataTableName, kOriginTableName}) {
success = success && db->Execute(base::StringPrintf(
"DROP TABLE IF EXISTS %s", table_name));
}
}
if (incompatible_version) {
success = success &&
db->Execute(base::StringPrintf(
kCreateGlobalMetadataStatementTemplate, kMetadataTableName));
success = success && SetDatabaseVersion(db, kDatabaseVersion);
}
return success;
}
// static
int ResourcePrefetchPredictorTables::GetDatabaseVersion(sql::Database* db) {
int version = 0;
if (db->DoesTableExist(kMetadataTableName)) {
sql::Statement statement(db->GetUniqueStatement(base::StringPrintf(
"SELECT value FROM %s WHERE key='version'", kMetadataTableName)));
if (statement.Step())
version = statement.ColumnInt(0);
}
return version;
}
// static
bool ResourcePrefetchPredictorTables::SetDatabaseVersion(sql::Database* db,
int version) {
sql::Statement statement(db->GetUniqueStatement(base::StringPrintf(
"INSERT OR REPLACE INTO %s (key,value) VALUES ('version',%d)",
kMetadataTableName, version)));
return statement.Run();
}
void ResourcePrefetchPredictorTables::CreateOrClearTablesIfNecessary() {
// TODO(crbug.com/40778330): This method's logic is almost identical to
// sqlite_proto::ProtoTableManager::CreateOrClearTablesIfNecessary, so the two
// classes could probably share a common implementation wrapping
// sql::MetaTable.
DCHECK(GetTaskRunner()->RunsTasksInCurrentSequence());
if (CantAccessDatabase())
return;
// Database initialization is all-or-nothing.
sql::Database* db = DB();
sql::Transaction transaction(db);
bool success = transaction.Begin();
success = success && DropTablesIfOutdated(db);
for (const char* table_name : {kHostRedirectTableName, kOriginTableName}) {
success = success && (db->DoesTableExist(table_name) ||
db->Execute(base::StringPrintf(
kCreateProtoTableStatementTemplate, table_name)));
}
success &= LcppDataMap::CreateOrClearTablesIfNecessary(db);
if (success) {
success = transaction.Commit();
} else {
transaction.Rollback();
}
if (!success)
ResetDB();
}
void ResourcePrefetchPredictorTables::LogDatabaseStats() {
DCHECK(GetTaskRunner()->RunsTasksInCurrentSequence());
if (CantAccessDatabase())
return;
// TODO(alexilin): Add existing tables stats.
}
} // namespace predictors