// Copyright (c) 2012 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 #include #include "base/logging.h" #include "base/metrics/histogram.h" #include "base/stringprintf.h" #include "content/public/browser/browser_thread.h" #include "sql/statement.h" using content::BrowserThread; using sql::Statement; namespace { const char kUrlResourceTableName[] = "resource_prefetch_predictor_url"; const char kUrlMetadataTableName[] = "resource_prefetch_predictor_url_metadata"; const char kHostResourceTableName[] = "resource_prefetch_predictor_host"; const char kHostMetadataTableName[] = "resource_prefetch_predictor_host_metadata"; void BindResourceRowToStatement( const predictors::ResourcePrefetchPredictorTables::ResourceRow& row, const std::string& primary_key, Statement* statement) { statement->BindString(0, primary_key); statement->BindString(1, row.resource_url.spec()); statement->BindInt(2, static_cast(row.resource_type)); statement->BindInt(3, row.number_of_hits); statement->BindInt(4, row.number_of_misses); statement->BindInt(5, row.consecutive_misses); statement->BindDouble(6, row.average_position); } bool StepAndInitializeResourceRow( Statement* statement, predictors::ResourcePrefetchPredictorTables::ResourceRow* row) { if (!statement->Step()) return false; row->primary_key = statement->ColumnString(0); row->resource_url = GURL(statement->ColumnString(1)); row->resource_type = ResourceType::FromInt(statement->ColumnInt(2)); row->number_of_hits = statement->ColumnInt(3); row->number_of_misses = statement->ColumnInt(4); row->consecutive_misses = statement->ColumnInt(5); row->average_position = statement->ColumnDouble(6); return true; } } // namespace namespace predictors { // static const size_t ResourcePrefetchPredictorTables::kMaxStringLength = 1024; ResourcePrefetchPredictorTables::ResourceRow::ResourceRow() : resource_type(ResourceType::LAST_TYPE), number_of_hits(0), number_of_misses(0), consecutive_misses(0), average_position(0.0), score(0.0) { } ResourcePrefetchPredictorTables::ResourceRow::ResourceRow( const ResourceRow& other) : primary_key(other.primary_key), resource_url(other.resource_url), resource_type(other.resource_type), number_of_hits(other.number_of_hits), number_of_misses(other.number_of_misses), consecutive_misses(other.consecutive_misses), average_position(other.average_position), score(other.score) { } ResourcePrefetchPredictorTables::ResourceRow::ResourceRow( const std::string& i_primary_key, const std::string& i_resource_url, ResourceType::Type i_resource_type, int i_number_of_hits, int i_number_of_misses, int i_consecutive_misses, double i_average_position) : primary_key(i_primary_key), resource_url(i_resource_url), resource_type(i_resource_type), number_of_hits(i_number_of_hits), number_of_misses(i_number_of_misses), consecutive_misses(i_consecutive_misses), average_position(i_average_position) { UpdateScore(); } void ResourcePrefetchPredictorTables::ResourceRow::UpdateScore() { // The score is calculated so that when the rows are sorted, the stylesheets // and scripts appear first, sorted by position(ascending) and then the rest // of the resources sorted by position(ascending). static const int kMaxResourcesPerType = 100; switch (resource_type) { case ResourceType::STYLESHEET: case ResourceType::SCRIPT: score = (2 * kMaxResourcesPerType) - average_position; break; case ResourceType::IMAGE: score = kMaxResourcesPerType - average_position; break; default: score = kMaxResourcesPerType - average_position; break; } } bool ResourcePrefetchPredictorTables::ResourceRow::operator==( const ResourceRow& rhs) const { return primary_key == rhs.primary_key && resource_url == rhs.resource_url && resource_type == rhs.resource_type && number_of_hits == rhs.number_of_hits && number_of_misses == rhs.number_of_misses && consecutive_misses == rhs.consecutive_misses && average_position == rhs.average_position && score == rhs.score; } bool ResourcePrefetchPredictorTables::ResourceRowSorter::operator()( const ResourceRow& x, const ResourceRow& y) const { return x.score > y.score; } ResourcePrefetchPredictorTables::PrefetchData::PrefetchData( PrefetchKeyType i_key_type, const std::string& i_primary_key) : key_type(i_key_type), primary_key(i_primary_key) { } ResourcePrefetchPredictorTables::PrefetchData::PrefetchData( const PrefetchData& other) : key_type(other.key_type), primary_key(other.primary_key), last_visit(other.last_visit), resources(other.resources) { } ResourcePrefetchPredictorTables::PrefetchData::~PrefetchData() { } bool ResourcePrefetchPredictorTables::PrefetchData::operator==( const PrefetchData& rhs) const { return key_type == rhs.key_type && primary_key == rhs.primary_key && resources == rhs.resources; } void ResourcePrefetchPredictorTables::GetAllData( PrefetchDataMap* url_data_map, PrefetchDataMap* host_data_map) { CHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); if (CantAccessDatabase()) return; DCHECK(url_data_map); DCHECK(host_data_map); url_data_map->clear(); host_data_map->clear(); std::vector urls_to_delete, hosts_to_delete; GetAllDataHelper(PREFETCH_KEY_TYPE_URL, url_data_map, &urls_to_delete); GetAllDataHelper(PREFETCH_KEY_TYPE_HOST, host_data_map, &hosts_to_delete); if (!urls_to_delete.empty() || !hosts_to_delete.empty()) DeleteData(urls_to_delete, hosts_to_delete); } void ResourcePrefetchPredictorTables::UpdateData( const PrefetchData& url_data, const PrefetchData& host_data) { CHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); if (CantAccessDatabase()) return; DCHECK(!url_data.is_host() && host_data.is_host()); DCHECK(!url_data.primary_key.empty() || !host_data.primary_key.empty()); DB()->BeginTransaction(); bool success = (url_data.primary_key.empty() || UpdateDataHelper(url_data)) && (host_data.primary_key.empty() || UpdateDataHelper(host_data)); if (!success) DB()->RollbackTransaction(); DB()->CommitTransaction(); } void ResourcePrefetchPredictorTables::DeleteData( const std::vector& urls, const std::vector& hosts) { CHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); if (CantAccessDatabase()) return; DCHECK(!urls.empty() || !hosts.empty()); if (!urls.empty()) DeleteDataHelper(PREFETCH_KEY_TYPE_URL, urls); if (!hosts.empty()) DeleteDataHelper(PREFETCH_KEY_TYPE_HOST, hosts); } void ResourcePrefetchPredictorTables::DeleteSingleDataPoint( const std::string& key, PrefetchKeyType key_type) { CHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); if (CantAccessDatabase()) return; DeleteDataHelper(key_type, std::vector(1, key)); } void ResourcePrefetchPredictorTables::DeleteAllData() { if (CantAccessDatabase()) return; Statement deleter(DB()->GetUniqueStatement( base::StringPrintf("DELETE FROM %s", kUrlResourceTableName).c_str())); deleter.Run(); deleter.Assign(DB()->GetUniqueStatement( base::StringPrintf("DELETE FROM %s", kUrlMetadataTableName).c_str())); deleter.Run(); deleter.Assign(DB()->GetUniqueStatement( base::StringPrintf("DELETE FROM %s", kHostResourceTableName).c_str())); deleter.Run(); deleter.Assign(DB()->GetUniqueStatement( base::StringPrintf("DELETE FROM %s", kHostMetadataTableName).c_str())); deleter.Run(); } ResourcePrefetchPredictorTables::ResourcePrefetchPredictorTables() : PredictorTableBase() { } ResourcePrefetchPredictorTables::~ResourcePrefetchPredictorTables() { } void ResourcePrefetchPredictorTables::GetAllDataHelper( PrefetchKeyType key_type, PrefetchDataMap* data_map, std::vector* to_delete) { bool is_host = key_type == PREFETCH_KEY_TYPE_HOST; // Read the resources table and organize it per primary key. const char* resource_table_name = is_host ? kHostResourceTableName : kUrlResourceTableName; Statement resource_reader(DB()->GetUniqueStatement( base::StringPrintf("SELECT * FROM %s", resource_table_name).c_str())); ResourceRow row; while (StepAndInitializeResourceRow(&resource_reader, &row)) { row.UpdateScore(); std::string primary_key = row.primary_key; // Don't need to store primary key since the data is grouped by primary key. row.primary_key.clear(); PrefetchDataMap::iterator it = data_map->find(primary_key); if (it == data_map->end()) { it = data_map->insert(std::make_pair( primary_key, PrefetchData(key_type, primary_key))).first; } it->second.resources.push_back(row); } // Sort each of the resource row vectors by score. for (PrefetchDataMap::iterator it = data_map->begin(); it != data_map->end(); ++it) { std::sort(it->second.resources.begin(), it->second.resources.end(), ResourceRowSorter()); } // Read the metadata and keep track of entries that have metadata, but no // resource entries, so they can be deleted. const char* metadata_table_name = is_host ? kHostMetadataTableName : kUrlMetadataTableName; Statement metadata_reader(DB()->GetUniqueStatement( base::StringPrintf("SELECT * FROM %s", metadata_table_name).c_str())); while (metadata_reader.Step()) { std::string primary_key = metadata_reader.ColumnString(0); PrefetchDataMap::iterator it = data_map->find(primary_key); if (it != data_map->end()) { int64 last_visit = metadata_reader.ColumnInt64(1); it->second.last_visit = base::Time::FromInternalValue(last_visit); } else { to_delete->push_back(primary_key); } } } bool ResourcePrefetchPredictorTables::UpdateDataHelper( const PrefetchData& data) { DCHECK(!data.primary_key.empty()); if (!StringsAreSmallerThanDBLimit(data)) { UMA_HISTOGRAM_BOOLEAN("ResourcePrefetchPredictor.DbStringTooLong", true); return false; } // Delete the older data from both the tables. scoped_ptr deleter(data.is_host() ? GetHostResourceDeleteStatement() : GetUrlResourceDeleteStatement()); deleter->BindString(0, data.primary_key); if (!deleter->Run()) return false; deleter.reset(data.is_host() ? GetHostMetadataDeleteStatement() : GetUrlMetadataDeleteStatement()); deleter->BindString(0, data.primary_key); if (!deleter->Run()) return false; // Add the new data to the tables. const ResourceRows& resources = data.resources; for (ResourceRows::const_iterator it = resources.begin(); it != resources.end(); ++it) { scoped_ptr resource_inserter(data.is_host() ? GetHostResourceUpdateStatement() : GetUrlResourceUpdateStatement()); BindResourceRowToStatement(*it, data.primary_key, resource_inserter.get()); if (!resource_inserter->Run()) return false; } scoped_ptr metadata_inserter(data.is_host() ? GetHostMetadataUpdateStatement() : GetUrlMetadataUpdateStatement()); metadata_inserter->BindString(0, data.primary_key); metadata_inserter->BindInt64(1, data.last_visit.ToInternalValue()); if (!metadata_inserter->Run()) return false; return true; } void ResourcePrefetchPredictorTables::DeleteDataHelper( PrefetchKeyType key_type, const std::vector& keys) { bool is_host = key_type == PREFETCH_KEY_TYPE_HOST; for (std::vector::const_iterator it = keys.begin(); it != keys.end(); ++it) { scoped_ptr deleter(is_host ? GetHostResourceDeleteStatement() : GetUrlResourceDeleteStatement()); deleter->BindString(0, *it); deleter->Run(); deleter.reset(is_host ? GetHostMetadataDeleteStatement() : GetUrlMetadataDeleteStatement()); deleter->BindString(0, *it); deleter->Run(); } } bool ResourcePrefetchPredictorTables::StringsAreSmallerThanDBLimit( const PrefetchData& data) const { if (data.primary_key.length() > kMaxStringLength) return false; for (ResourceRows::const_iterator it = data.resources.begin(); it != data.resources.end(); ++it) { if (it->resource_url.spec().length() > kMaxStringLength) return false; } return true; } void ResourcePrefetchPredictorTables::CreateTableIfNonExistent() { CHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); if (CantAccessDatabase()) return; const char* resource_table_creator = "CREATE TABLE %s ( " "main_page_url TEXT, " "resource_url TEXT, " "resource_type INTEGER, " "number_of_hits INTEGER, " "number_of_misses INTEGER, " "consecutive_misses INTEGER, " "average_position DOUBLE, " "PRIMARY KEY(main_page_url, resource_url))"; const char* metadata_table_creator = "CREATE TABLE %s ( " "main_page_url TEXT, " "last_visit_time INTEGER, " "PRIMARY KEY(main_page_url))"; sql::Connection* db = DB(); bool success = (db->DoesTableExist(kUrlResourceTableName) || db->Execute(base::StringPrintf(resource_table_creator, kUrlResourceTableName).c_str())) && (db->DoesTableExist(kUrlMetadataTableName) || db->Execute(base::StringPrintf(metadata_table_creator, kUrlMetadataTableName).c_str())) && (db->DoesTableExist(kHostResourceTableName) || db->Execute(base::StringPrintf(resource_table_creator, kHostResourceTableName).c_str())) && (db->DoesTableExist(kHostMetadataTableName) || db->Execute(base::StringPrintf(metadata_table_creator, kHostMetadataTableName).c_str())); if (!success) ResetDB(); } void ResourcePrefetchPredictorTables::LogDatabaseStats() { CHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); if (CantAccessDatabase()) return; Statement statement(DB()->GetUniqueStatement( base::StringPrintf("SELECT count(*) FROM %s", kUrlResourceTableName).c_str())); if (statement.Step()) UMA_HISTOGRAM_COUNTS("ResourcePrefetchPredictor.UrlTableRowCount", statement.ColumnInt(0)); statement.Assign(DB()->GetUniqueStatement( base::StringPrintf("SELECT count(*) FROM %s", kHostResourceTableName).c_str())); if (statement.Step()) UMA_HISTOGRAM_COUNTS("ResourcePrefetchPredictor.HostTableRowCount", statement.ColumnInt(0)); } Statement* ResourcePrefetchPredictorTables::GetUrlResourceDeleteStatement() { return new Statement(DB()->GetCachedStatement( SQL_FROM_HERE, base::StringPrintf("DELETE FROM %s WHERE main_page_url=?", kUrlResourceTableName).c_str())); } Statement* ResourcePrefetchPredictorTables::GetUrlResourceUpdateStatement() { return new Statement(DB()->GetCachedStatement( SQL_FROM_HERE, base::StringPrintf( "INSERT INTO %s " "(main_page_url, resource_url, resource_type, number_of_hits, " "number_of_misses, consecutive_misses, average_position) " "VALUES (?,?,?,?,?,?,?)", kUrlResourceTableName).c_str())); } Statement* ResourcePrefetchPredictorTables::GetUrlMetadataDeleteStatement() { return new Statement(DB()->GetCachedStatement( SQL_FROM_HERE, base::StringPrintf("DELETE FROM %s WHERE main_page_url=?", kUrlMetadataTableName).c_str())); } Statement* ResourcePrefetchPredictorTables::GetUrlMetadataUpdateStatement() { return new Statement(DB()->GetCachedStatement( SQL_FROM_HERE, base::StringPrintf( "INSERT INTO %s (main_page_url, last_visit_time) VALUES (?,?)", kUrlMetadataTableName).c_str())); } Statement* ResourcePrefetchPredictorTables::GetHostResourceDeleteStatement() { return new Statement(DB()->GetCachedStatement( SQL_FROM_HERE, base::StringPrintf("DELETE FROM %s WHERE main_page_url=?", kHostResourceTableName).c_str())); } Statement* ResourcePrefetchPredictorTables::GetHostResourceUpdateStatement() { return new Statement(DB()->GetCachedStatement( SQL_FROM_HERE, base::StringPrintf( "INSERT INTO %s " "(main_page_url, resource_url, resource_type, number_of_hits, " "number_of_misses, consecutive_misses, average_position) " "VALUES (?,?,?,?,?,?,?)", kHostResourceTableName).c_str())); } Statement* ResourcePrefetchPredictorTables::GetHostMetadataDeleteStatement() { return new Statement(DB()->GetCachedStatement( SQL_FROM_HERE, base::StringPrintf("DELETE FROM %s WHERE main_page_url=?", kHostMetadataTableName).c_str())); } Statement* ResourcePrefetchPredictorTables::GetHostMetadataUpdateStatement() { return new Statement(DB()->GetCachedStatement( SQL_FROM_HERE, base::StringPrintf( "INSERT INTO %s (main_page_url, last_visit_time) VALUES (?,?)", kHostMetadataTableName).c_str())); } } // namespace predictors