1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/predictors/resource_prefetch_predictor_tables.h"
7 #include "base/logging.h"
8 #include "base/metrics/histogram.h"
9 #include "base/stringprintf.h"
10 #include "content/public/browser/browser_thread.h"
11 #include "sql/statement.h"
13 using content::BrowserThread
;
17 const char kUrlResourceTableName
[] = "resource_prefetch_predictor_url";
18 const char kUrlMetadataTableName
[] = "resource_prefetch_predictor_url_metadata";
20 // The maximum length allowed for strings in the database.
21 const size_t kMaxURLLength
= 2048;
23 std::string
MaybeTrimUrl(const GURL
& url
, bool is_main_frame_url
) {
24 std::string spec
= url
.spec();
25 if (spec
.length() > kMaxURLLength
) {
26 if (is_main_frame_url
) {
27 UMA_HISTOGRAM_BOOLEAN("ResourcePrefetchPredictor.MainFrameUrlTooLong",
30 UMA_HISTOGRAM_BOOLEAN("ResourcePrefetchPredictor.ResourceUrlTooLong",
33 return spec
.substr(0, kMaxURLLength
);
38 void BindUrlResourceRowToStatement(
39 const predictors::ResourcePrefetchPredictorTables::UrlResourceRow
& row
,
40 sql::Statement
* statement
) {
41 statement
->BindString(0, MaybeTrimUrl(row
.main_frame_url
, true));
42 statement
->BindString(1, MaybeTrimUrl(row
.resource_url
, false));
43 statement
->BindInt(2, static_cast<int>(row
.resource_type
));
44 statement
->BindInt(3, row
.number_of_hits
);
45 statement
->BindInt(4, row
.number_of_misses
);
46 statement
->BindInt(5, row
.consecutive_misses
);
47 statement
->BindDouble(6, row
.average_position
);
50 bool StepAndInitializeUrlResourceRow(
51 sql::Statement
* statement
,
52 predictors::ResourcePrefetchPredictorTables::UrlResourceRow
* row
) {
53 if (!statement
->Step())
56 row
->main_frame_url
= GURL(statement
->ColumnString(0));
57 row
->resource_url
= GURL(statement
->ColumnString(1));
58 row
->resource_type
= ResourceType::FromInt(statement
->ColumnInt(2));
59 row
->number_of_hits
= statement
->ColumnInt(3);
60 row
->number_of_misses
= statement
->ColumnInt(4);
61 row
->consecutive_misses
= statement
->ColumnInt(5);
62 row
->average_position
= statement
->ColumnDouble(6);
68 namespace predictors
{
70 ResourcePrefetchPredictorTables::UrlResourceRow::UrlResourceRow()
71 : resource_type(ResourceType::LAST_TYPE
),
74 consecutive_misses(0),
75 average_position(0.0),
79 ResourcePrefetchPredictorTables::UrlResourceRow::UrlResourceRow(
80 const UrlResourceRow
& other
)
81 : main_frame_url(other
.main_frame_url
),
82 resource_url(other
.resource_url
),
83 resource_type(other
.resource_type
),
84 number_of_hits(other
.number_of_hits
),
85 number_of_misses(other
.number_of_misses
),
86 consecutive_misses(other
.consecutive_misses
),
87 average_position(other
.average_position
),
91 ResourcePrefetchPredictorTables::UrlResourceRow::UrlResourceRow(
92 const std::string
& i_main_frame_url
,
93 const std::string
& i_resource_url
,
94 ResourceType::Type i_resource_type
,
96 int i_number_of_misses
,
97 int i_consecutive_misses
,
98 double i_average_position
)
99 : main_frame_url(i_main_frame_url
),
100 resource_url(i_resource_url
),
101 resource_type(i_resource_type
),
102 number_of_hits(i_number_of_hits
),
103 number_of_misses(i_number_of_misses
),
104 consecutive_misses(i_consecutive_misses
),
105 average_position(i_average_position
) {
109 void ResourcePrefetchPredictorTables::UrlResourceRow::UpdateScore() {
110 // The score is calculated so that when the rows are sorted, the stylesheets
111 // and scripts appear first, sorted by position(ascending) and then the rest
112 // of the resources sorted by position(ascending).
113 static const int kMaxResourcesPerType
= 100;
114 switch (resource_type
) {
115 case ResourceType::STYLESHEET
:
116 case ResourceType::SCRIPT
:
117 score
= (2 * kMaxResourcesPerType
) - average_position
;
120 case ResourceType::IMAGE
:
121 score
= kMaxResourcesPerType
- average_position
;
125 score
= kMaxResourcesPerType
- average_position
;
130 bool ResourcePrefetchPredictorTables::UrlResourceRow::operator==(
131 const UrlResourceRow
& rhs
) const {
132 return main_frame_url
== rhs
.main_frame_url
&&
133 resource_url
== rhs
.resource_url
&&
134 resource_type
== rhs
.resource_type
&&
135 number_of_hits
== rhs
.number_of_hits
&&
136 number_of_misses
== rhs
.number_of_misses
&&
137 consecutive_misses
== rhs
.consecutive_misses
&&
138 average_position
== rhs
.average_position
&&
142 bool ResourcePrefetchPredictorTables::UrlResourceRowSorter::operator()(
143 const UrlResourceRow
& x
, const UrlResourceRow
& y
) const {
144 return x
.score
> y
.score
;
147 ResourcePrefetchPredictorTables::UrlData::UrlData(const GURL
& i_main_frame_url
)
148 : main_frame_url(i_main_frame_url
) {
151 ResourcePrefetchPredictorTables::UrlData::UrlData(const UrlData
& other
)
152 : main_frame_url(other
.main_frame_url
),
153 last_visit(other
.last_visit
),
154 resources(other
.resources
) {
157 ResourcePrefetchPredictorTables::UrlData::~UrlData() {
160 bool ResourcePrefetchPredictorTables::UrlData::operator==(
161 const UrlData
& rhs
) const {
162 return main_frame_url
== rhs
.main_frame_url
&&
163 resources
== rhs
.resources
;
166 ResourcePrefetchPredictorTables::ResourcePrefetchPredictorTables()
167 : PredictorTableBase() {
170 ResourcePrefetchPredictorTables::~ResourcePrefetchPredictorTables() {
173 void ResourcePrefetchPredictorTables::GetAllUrlData(
174 std::vector
<UrlData
>* url_data_buffer
) {
175 CHECK(BrowserThread::CurrentlyOn(BrowserThread::DB
));
176 if (CantAccessDatabase())
179 CHECK(url_data_buffer
&& url_data_buffer
->empty());
181 // First read the resources table and organize it per main_frame_url.
182 sql::Statement
resource_reader(DB()->GetCachedStatement(SQL_FROM_HERE
,
183 base::StringPrintf("SELECT * FROM %s", kUrlResourceTableName
).c_str()));
184 std::map
<GURL
, size_t> url_index
;
185 UrlResourceRow url_row
;
186 while (StepAndInitializeUrlResourceRow(&resource_reader
, &url_row
)) {
187 url_row
.UpdateScore();
189 if (url_index
.find(url_row
.main_frame_url
) == url_index
.end()) {
190 url_index
[url_row
.main_frame_url
] = url_data_buffer
->size();
191 url_data_buffer
->push_back(UrlData(url_row
.main_frame_url
));
193 url_data_buffer
->at(url_index
[url_row
.main_frame_url
]).resources
.push_back(
197 // Read the metadata and keep track of Urls that have metadata, but no
198 // resource entries, so they can be deleted.
199 std::vector
<GURL
> urls_to_delete
;
201 sql::Statement
metadata_reader(DB()->GetCachedStatement(SQL_FROM_HERE
,
202 base::StringPrintf("SELECT * FROM %s", kUrlMetadataTableName
).c_str()));
203 while (metadata_reader
.Step()) {
204 GURL main_frame_url
= GURL(metadata_reader
.ColumnString(0));
205 if (url_index
.find(main_frame_url
) != url_index
.end()) {
206 int64 last_visit
= metadata_reader
.ColumnInt64(1);
207 url_data_buffer
->at(url_index
[main_frame_url
]).last_visit
=
208 base::Time::FromInternalValue(last_visit
);
210 urls_to_delete
.push_back(main_frame_url
);
214 // Delete rows in the UrlMetadataTable for which there are no resources.
215 if (!urls_to_delete
.empty())
216 DeleteDataForUrls(urls_to_delete
);
219 void ResourcePrefetchPredictorTables::UpdateDataForUrl(
220 const UrlData
& url_data
) {
221 CHECK(BrowserThread::CurrentlyOn(BrowserThread::DB
));
222 if (CantAccessDatabase())
226 const std::string main_frame_url
= MaybeTrimUrl(url_data
.main_frame_url
,
229 DB()->BeginTransaction();
231 // Delete the older data from both the tables.
232 sql::Statement
resource_deleter(DB()->GetCachedStatement(
234 base::StringPrintf("DELETE FROM %s WHERE main_page_url=?",
235 kUrlResourceTableName
).c_str()));
236 resource_deleter
.BindString(0, main_frame_url
);
237 sql::Statement
url_deleter(DB()->GetCachedStatement(
239 base::StringPrintf("DELETE FROM %s WHERE main_page_url=?",
240 kUrlMetadataTableName
).c_str()));
241 url_deleter
.BindString(0, main_frame_url
);
242 if (!resource_deleter
.Run() || !url_deleter
.Run()) {
243 DB()->RollbackTransaction();
247 // Add the new data to the tables.
248 const UrlResourceRows
& resources
= url_data
.resources
;
249 for (UrlResourceRows::const_iterator it
= resources
.begin();
250 it
!= resources
.end(); ++it
) {
251 sql::Statement
resource_inserter(
252 DB()->GetCachedStatement(
256 "(main_page_url, resource_url, resource_type, number_of_hits, "
257 "number_of_misses, consecutive_misses, average_position) "
258 "VALUES (?,?,?,?,?,?,?)",
259 kUrlResourceTableName
).c_str()));
260 BindUrlResourceRowToStatement(*it
, &resource_inserter
);
261 if (!resource_inserter
.Run()) {
262 DB()->RollbackTransaction();
267 sql::Statement
metadata_inserter(DB()->GetCachedStatement(
269 base::StringPrintf("INSERT INTO %s (main_page_url, last_visit_time) "
270 "VALUES (?,?)", kUrlMetadataTableName
).c_str()));
271 metadata_inserter
.BindString(0,main_frame_url
);
272 metadata_inserter
.BindInt64(1, url_data
.last_visit
.ToInternalValue());
273 if (!metadata_inserter
.Run()) {
274 DB()->RollbackTransaction();
278 DB()->CommitTransaction();
281 void ResourcePrefetchPredictorTables::DeleteDataForUrls(
282 const std::vector
<GURL
>& urls
) {
283 CHECK(BrowserThread::CurrentlyOn(BrowserThread::DB
));
284 if (CantAccessDatabase())
287 for (std::vector
<GURL
>::const_iterator it
= urls
.begin(); it
!= urls
.end();
289 sql::Statement
resource_deleter(DB()->GetCachedStatement(
291 base::StringPrintf("DELETE FROM %s WHERE main_page_url=?",
292 kUrlResourceTableName
).c_str()));
293 resource_deleter
.BindString(0, it
->spec());
294 resource_deleter
.Run();
296 sql::Statement
url_deleter(DB()->GetCachedStatement(
298 base::StringPrintf("DELETE FROM %s WHERE main_page_url=?",
299 kUrlMetadataTableName
).c_str()));
300 url_deleter
.BindString(0, it
->spec());
305 void ResourcePrefetchPredictorTables::DeleteAllUrlData() {
306 if (CantAccessDatabase())
309 sql::Statement
resource_deleter(DB()->GetCachedStatement(
311 base::StringPrintf("DELETE FROM %s", kUrlResourceTableName
).c_str()));
312 resource_deleter
.Run();
314 sql::Statement
url_deleter(DB()->GetCachedStatement(
316 base::StringPrintf("DELETE FROM %s", kUrlMetadataTableName
).c_str()));
320 void ResourcePrefetchPredictorTables::CreateTableIfNonExistent() {
321 CHECK(BrowserThread::CurrentlyOn(BrowserThread::DB
));
322 if (CantAccessDatabase())
325 std::string url_resource_table_creation_statement
= base::StringPrintf(
327 "main_page_url TEXT, "
328 "resource_url TEXT, "
329 "resource_type INTEGER, "
330 "number_of_hits INTEGER, "
331 "number_of_misses INTEGER, "
332 "consecutive_misses INTEGER, "
333 "average_position DOUBLE, "
334 "PRIMARY KEY(main_page_url, resource_url))",
335 kUrlResourceTableName
);
337 std::string url_metadata_table_creation_statement
= base::StringPrintf(
339 "main_page_url TEXT, "
340 "last_visit_time INTEGER, "
341 "PRIMARY KEY(main_page_url))",
342 kUrlMetadataTableName
);
344 if ((!DB()->DoesTableExist(kUrlResourceTableName
) &&
345 !DB()->Execute(url_resource_table_creation_statement
.c_str())) ||
346 (!DB()->DoesTableExist(kUrlMetadataTableName
) &&
347 !DB()->Execute(url_metadata_table_creation_statement
.c_str()))) {
352 void ResourcePrefetchPredictorTables::LogDatabaseStats() {
353 CHECK(BrowserThread::CurrentlyOn(BrowserThread::DB
));
354 if (CantAccessDatabase())
357 sql::Statement
url_statement(DB()->GetUniqueStatement(
358 base::StringPrintf("SELECT count(*) FROM %s",
359 kUrlResourceTableName
).c_str()));
360 if (url_statement
.Step())
361 UMA_HISTOGRAM_COUNTS("ResourcePrefetchPredictor.UrlTableRowCount",
362 url_statement
.ColumnInt(0));
365 } // namespace predictors