blob: cb6f4cdb60a90a507c4d2ec7fa27ed21a6e1a55c [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 "content/public/browser/browser_thread.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 kManifestTableName[] = "resource_prefetch_predictor_manifest";
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))";
predictors::ResourceData::ResourceType PrecacheResourceTypeToResourceType(
precache::PrecacheResource::Type resource_type) {
using precache::PrecacheResource;
using predictors::ResourceData;
switch (resource_type) {
case PrecacheResource::RESOURCE_TYPE_IMAGE:
return ResourceData::RESOURCE_TYPE_IMAGE;
case PrecacheResource::RESOURCE_TYPE_FONT:
return ResourceData::RESOURCE_TYPE_FONT_RESOURCE;
case PrecacheResource::RESOURCE_TYPE_STYLESHEET:
return ResourceData::RESOURCE_TYPE_STYLESHEET;
case PrecacheResource::RESOURCE_TYPE_SCRIPT:
return ResourceData::RESOURCE_TYPE_SCRIPT;
case PrecacheResource::RESOURCE_TYPE_OTHER:
case PrecacheResource::RESOURCE_TYPE_UNKNOWN:
default:
return ResourceData::RESOURCE_TYPE_SUB_RESOURCE;
}
}
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 {
using content::BrowserThread;
// 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);
});
}
void ResourcePrefetchPredictorTables::GetAllData(
PrefetchDataMap* url_data_map,
PrefetchDataMap* host_data_map,
RedirectDataMap* url_redirect_data_map,
RedirectDataMap* host_redirect_data_map,
ManifestDataMap* manifest_map,
OriginDataMap* origin_data_map) {
TRACE_EVENT0("browser", "ResourcePrefetchPredictor::GetAllData");
DCHECK_CURRENTLY_ON(BrowserThread::DB);
if (CantAccessDatabase())
return;
DCHECK(url_data_map);
DCHECK(host_data_map);
DCHECK(url_redirect_data_map);
DCHECK(host_redirect_data_map);
DCHECK(manifest_map);
DCHECK(origin_data_map);
url_data_map->clear();
host_data_map->clear();
url_redirect_data_map->clear();
host_redirect_data_map->clear();
manifest_map->clear();
origin_data_map->clear();
url_resource_table_->GetAllData(url_data_map);
host_resource_table_->GetAllData(host_data_map);
url_redirect_table_->GetAllData(url_redirect_data_map);
host_redirect_table_->GetAllData(host_redirect_data_map);
manifest_table_->GetAllData(manifest_map);
origin_table_->GetAllData(origin_data_map);
}
void ResourcePrefetchPredictorTables::UpdateResourceData(
const PrefetchData& data,
PrefetchKeyType key_type) {
TRACE_EVENT0("browser", "ResourcePrefetchPredictor::UpdateResourceData");
DCHECK_CURRENTLY_ON(BrowserThread::DB);
if (CantAccessDatabase())
return;
if (key_type == PREFETCH_KEY_TYPE_URL)
url_resource_table_->UpdateData(data.primary_key(), data);
else
host_resource_table_->UpdateData(data.primary_key(), data);
}
void ResourcePrefetchPredictorTables::UpdateRedirectData(
const RedirectData& data,
PrefetchKeyType key_type) {
TRACE_EVENT0("browser", "ResourcePrefetchPredictor::UpdateRedirectData");
DCHECK_CURRENTLY_ON(BrowserThread::DB);
if (CantAccessDatabase())
return;
if (key_type == PREFETCH_KEY_TYPE_URL)
url_redirect_table_->UpdateData(data.primary_key(), data);
else
host_redirect_table_->UpdateData(data.primary_key(), data);
}
void ResourcePrefetchPredictorTables::UpdateManifestData(
const std::string& host,
const precache::PrecacheManifest& manifest_data) {
TRACE_EVENT0("browser", "ResourcePrefetchPredictor::UpdateManifestData");
DCHECK_CURRENTLY_ON(BrowserThread::DB);
if (CantAccessDatabase())
return;
manifest_table_->UpdateData(host, manifest_data);
}
void ResourcePrefetchPredictorTables::UpdateOriginData(
const OriginData& origin_data) {
TRACE_EVENT0("browser", "ResourcePrefetchPredictor::UpdateOriginData");
DCHECK_CURRENTLY_ON(BrowserThread::DB);
if (CantAccessDatabase())
return;
origin_table_->UpdateData(origin_data.host(), origin_data);
}
void ResourcePrefetchPredictorTables::DeleteResourceData(
const std::vector<std::string>& urls,
const std::vector<std::string>& hosts) {
DCHECK_CURRENTLY_ON(BrowserThread::DB);
if (CantAccessDatabase())
return;
DCHECK(!urls.empty() || !hosts.empty());
if (!urls.empty())
url_resource_table_->DeleteData(urls);
if (!hosts.empty())
host_resource_table_->DeleteData(hosts);
}
void ResourcePrefetchPredictorTables::DeleteSingleResourceDataPoint(
const std::string& key,
PrefetchKeyType key_type) {
DCHECK_CURRENTLY_ON(BrowserThread::DB);
if (CantAccessDatabase())
return;
if (key_type == PREFETCH_KEY_TYPE_URL)
url_resource_table_->DeleteData({key});
else
host_resource_table_->DeleteData({key});
}
void ResourcePrefetchPredictorTables::DeleteRedirectData(
const std::vector<std::string>& urls,
const std::vector<std::string>& hosts) {
DCHECK_CURRENTLY_ON(BrowserThread::DB);
if (CantAccessDatabase())
return;
DCHECK(!urls.empty() || !hosts.empty());
if (!urls.empty())
url_redirect_table_->DeleteData(urls);
if (!hosts.empty())
host_redirect_table_->DeleteData(hosts);
}
void ResourcePrefetchPredictorTables::DeleteSingleRedirectDataPoint(
const std::string& key,
PrefetchKeyType key_type) {
DCHECK_CURRENTLY_ON(BrowserThread::DB);
if (CantAccessDatabase())
return;
if (key_type == PREFETCH_KEY_TYPE_URL)
url_redirect_table_->DeleteData({key});
else
host_redirect_table_->DeleteData({key});
}
void ResourcePrefetchPredictorTables::DeleteManifestData(
const std::vector<std::string>& hosts) {
DCHECK_CURRENTLY_ON(BrowserThread::DB);
if (CantAccessDatabase())
return;
manifest_table_->DeleteData(hosts);
}
void ResourcePrefetchPredictorTables::DeleteOriginData(
const std::vector<std::string>& hosts) {
DCHECK_CURRENTLY_ON(BrowserThread::DB);
if (CantAccessDatabase())
return;
origin_table_->DeleteData(hosts);
}
void ResourcePrefetchPredictorTables::DeleteAllData() {
DCHECK_CURRENTLY_ON(BrowserThread::DB);
if (CantAccessDatabase())
return;
url_resource_table_->DeleteAllData();
url_redirect_table_->DeleteAllData();
host_resource_table_->DeleteAllData();
host_redirect_table_->DeleteAllData();
manifest_table_->DeleteAllData();
origin_table_->DeleteAllData();
}
ResourcePrefetchPredictorTables::ResourcePrefetchPredictorTables() = default;
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::ComputePrecacheResourceScore(
const precache::PrecacheResource& resource) {
int type_multiplier = GetResourceTypeMultiplier(
PrecacheResourceTypeToResourceType(resource.type()));
// This means a strict ordering, since the weight_ratio is in [0,1).
return type_multiplier * 10 + resource.weight_ratio();
}
// 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;
}
// 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";
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_CURRENTLY_ON(BrowserThread::DB);
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, kManifestTableName, 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();
if (success) {
url_resource_table_ = base::MakeUnique<GlowplugKeyValueTable<PrefetchData>>(
kUrlResourceTableName, db);
url_redirect_table_ = base::MakeUnique<GlowplugKeyValueTable<RedirectData>>(
kUrlRedirectTableName, db);
host_resource_table_ =
base::MakeUnique<GlowplugKeyValueTable<PrefetchData>>(
kHostResourceTableName, db);
host_redirect_table_ =
base::MakeUnique<GlowplugKeyValueTable<RedirectData>>(
kHostRedirectTableName, db);
manifest_table_ =
base::MakeUnique<GlowplugKeyValueTable<precache::PrecacheManifest>>(
kManifestTableName, db);
origin_table_ = base::MakeUnique<GlowplugKeyValueTable<OriginData>>(
kOriginTableName, db);
}
}
void ResourcePrefetchPredictorTables::LogDatabaseStats() {
DCHECK_CURRENTLY_ON(BrowserThread::DB);
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