blob: c0b285d461a5ee61438aad86c13d78ce708f9f4c [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// 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/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/stringprintf.h"
#include "base/trace_event/trace_event.h"
#include "sql/statement.h"
using google::protobuf::MessageLite;
namespace {
const char kMetadataTableName[] = "resource_prefetch_predictor_metadata";
const char kUrlResourceTableName[] = "resource_prefetch_predictor_url";
const char kUrlRedirectTableName[] = "resource_prefetch_predictor_url_redirect";
const char kHostResourceTableName[] = "resource_prefetch_predictor_host";
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))";
int GetResourceTypeMultiplier(
predictors::ResourceData::ResourceType resource_type) {
switch (resource_type) {
case predictors::ResourceData::RESOURCE_TYPE_STYLESHEET:
return 4;
case predictors::ResourceData::RESOURCE_TYPE_SCRIPT:
return 3;
case predictors::ResourceData::RESOURCE_TYPE_FONT_RESOURCE:
return 2;
case predictors::ResourceData::RESOURCE_TYPE_IMAGE:
default:
return 1;
}
}
} // namespace
namespace predictors {
// static
void ResourcePrefetchPredictorTables::TrimResources(
PrefetchData* data,
size_t max_consecutive_misses) {
auto new_end = std::remove_if(
data->mutable_resources()->begin(), data->mutable_resources()->end(),
[max_consecutive_misses](const ResourceData& x) {
return x.consecutive_misses() >= max_consecutive_misses;
});
data->mutable_resources()->erase(new_end, data->mutable_resources()->end());
}
// static
void ResourcePrefetchPredictorTables::SortResources(PrefetchData* data) {
std::sort(data->mutable_resources()->begin(),
data->mutable_resources()->end(),
[](const ResourceData& x, const ResourceData& y) {
// Decreasing score ordering.
return ComputeResourceScore(x) > ComputeResourceScore(y);
});
}
// 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) {
std::sort(data->mutable_origins()->begin(), data->mutable_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)
: PredictorTableBase(db_task_runner) {
url_resource_table_ = base::MakeUnique<GlowplugKeyValueTable<PrefetchData>>(
kUrlResourceTableName);
url_redirect_table_ = base::MakeUnique<GlowplugKeyValueTable<RedirectData>>(
kUrlRedirectTableName);
host_resource_table_ = base::MakeUnique<GlowplugKeyValueTable<PrefetchData>>(
kHostResourceTableName);
host_redirect_table_ = base::MakeUnique<GlowplugKeyValueTable<RedirectData>>(
kHostRedirectTableName);
origin_table_ =
base::MakeUnique<GlowplugKeyValueTable<OriginData>>(kOriginTableName);
}
ResourcePrefetchPredictorTables::~ResourcePrefetchPredictorTables() = default;
// static
float ResourcePrefetchPredictorTables::ComputeResourceScore(
const ResourceData& data) {
// The ranking is done by considering, in this order:
// 1. Resource Priority
// 2. Request resource type
// 3. Finally, the average position, giving a higher priotity to earlier
// resources.
int priority_multiplier;
switch (data.priority()) {
case ResourceData::REQUEST_PRIORITY_HIGHEST:
priority_multiplier = 3;
break;
case ResourceData::REQUEST_PRIORITY_MEDIUM:
priority_multiplier = 2;
break;
case ResourceData::REQUEST_PRIORITY_LOW:
case ResourceData::REQUEST_PRIORITY_LOWEST:
case ResourceData::REQUEST_PRIORITY_IDLE:
default:
priority_multiplier = 1;
break;
}
int type_multiplier = GetResourceTypeMultiplier(data.resource_type());
constexpr int kMaxResourcesPerType = 100;
return kMaxResourcesPerType *
(priority_multiplier * 100 + type_multiplier * 10) -
data.average_position();
}
// 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;
score += origin.always_access_network() * 1e4;
score += origin.accessed_network() * 1e2;
score += 1e2 - origin.average_position();
return score;
}
void ResourcePrefetchPredictorTables::ScheduleDBTask(
const tracked_objects::Location& from_here,
DBTask task) {
GetTaskRunner()->PostTask(
from_here,
base::BindOnce(
&ResourcePrefetchPredictorTables::ExecuteDBTaskOnDBSequence, this,
std::move(task)));
}
void ResourcePrefetchPredictorTables::ExecuteDBTaskOnDBSequence(DBTask task) {
DCHECK(GetTaskRunner()->RunsTasksInCurrentSequence());
if (CantAccessDatabase())
return;
std::move(task).Run(DB());
}
GlowplugKeyValueTable<PrefetchData>*
ResourcePrefetchPredictorTables::url_resource_table() {
return url_resource_table_.get();
}
GlowplugKeyValueTable<RedirectData>*
ResourcePrefetchPredictorTables::url_redirect_table() {
return url_redirect_table_.get();
}
GlowplugKeyValueTable<PrefetchData>*
ResourcePrefetchPredictorTables::host_resource_table() {
return host_resource_table_.get();
}
GlowplugKeyValueTable<RedirectData>*
ResourcePrefetchPredictorTables::host_redirect_table() {
return host_redirect_table_.get();
}
GlowplugKeyValueTable<OriginData>*
ResourcePrefetchPredictorTables::origin_table() {
return origin_table_.get();
}
// static
bool ResourcePrefetchPredictorTables::DropTablesIfOutdated(
sql::Connection* 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";
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)
.c_str());
}
}
if (incompatible_version) {
success =
success &&
db->Execute(base::StringPrintf(kCreateGlobalMetadataStatementTemplate,
kMetadataTableName)
.c_str());
success = success && SetDatabaseVersion(db, kDatabaseVersion);
}
return success;
}
// static
int ResourcePrefetchPredictorTables::GetDatabaseVersion(sql::Connection* db) {
int version = 0;
if (db->DoesTableExist(kMetadataTableName)) {
sql::Statement statement(db->GetUniqueStatement(
base::StringPrintf("SELECT value FROM %s WHERE key='version'",
kMetadataTableName)
.c_str()));
if (statement.Step())
version = statement.ColumnInt(0);
}
return version;
}
// static
bool ResourcePrefetchPredictorTables::SetDatabaseVersion(sql::Connection* db,
int version) {
sql::Statement statement(db->GetUniqueStatement(
base::StringPrintf(
"INSERT OR REPLACE INTO %s (key,value) VALUES ('version',%d)",
kMetadataTableName, version)
.c_str()));
return statement.Run();
}
void ResourcePrefetchPredictorTables::CreateTableIfNonExistent() {
DCHECK(GetTaskRunner()->RunsTasksInCurrentSequence());
if (CantAccessDatabase())
return;
// Database initialization is all-or-nothing.
sql::Connection* db = DB();
bool success = db->BeginTransaction();
success = success && DropTablesIfOutdated(db);
for (const char* table_name :
{kUrlResourceTableName, kHostResourceTableName, kUrlRedirectTableName,
kHostRedirectTableName, kOriginTableName}) {
success = success &&
(db->DoesTableExist(table_name) ||
db->Execute(base::StringPrintf(
kCreateProtoTableStatementTemplate, table_name)
.c_str()));
}
if (success)
success = db->CommitTransaction();
else
db->RollbackTransaction();
if (!success)
ResetDB();
}
void ResourcePrefetchPredictorTables::LogDatabaseStats() {
DCHECK(GetTaskRunner()->RunsTasksInCurrentSequence());
if (CantAccessDatabase())
return;
sql::Statement statement(DB()->GetUniqueStatement(
base::StringPrintf("SELECT count(*) FROM %s", kUrlResourceTableName)
.c_str()));
if (statement.Step())
UMA_HISTOGRAM_COUNTS("ResourcePrefetchPredictor.UrlTableRowCount2",
statement.ColumnInt(0));
statement.Assign(DB()->GetUniqueStatement(
base::StringPrintf("SELECT count(*) FROM %s", kHostResourceTableName)
.c_str()));
if (statement.Step())
UMA_HISTOGRAM_COUNTS("ResourcePrefetchPredictor.HostTableRowCount2",
statement.ColumnInt(0));
}
} // namespace predictors