Introduce ProfilerMetricsProvider
[chromium-blink-merge.git] / chrome / browser / predictors / autocomplete_action_predictor.cc
blob923b25b4c4d7c1c9041db231b860df245884071c
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/autocomplete_action_predictor.h"
7 #include <math.h>
9 #include <vector>
11 #include "base/bind.h"
12 #include "base/guid.h"
13 #include "base/i18n/case_conversion.h"
14 #include "base/metrics/histogram.h"
15 #include "base/strings/string_util.h"
16 #include "base/strings/stringprintf.h"
17 #include "base/strings/utf_string_conversions.h"
18 #include "chrome/browser/autocomplete/autocomplete_match.h"
19 #include "chrome/browser/autocomplete/autocomplete_result.h"
20 #include "chrome/browser/chrome_notification_types.h"
21 #include "chrome/browser/history/history_notifications.h"
22 #include "chrome/browser/history/history_service.h"
23 #include "chrome/browser/history/history_service_factory.h"
24 #include "chrome/browser/history/in_memory_database.h"
25 #include "chrome/browser/omnibox/omnibox_log.h"
26 #include "chrome/browser/predictors/autocomplete_action_predictor_factory.h"
27 #include "chrome/browser/predictors/predictor_database.h"
28 #include "chrome/browser/predictors/predictor_database_factory.h"
29 #include "chrome/browser/prerender/prerender_field_trial.h"
30 #include "chrome/browser/prerender/prerender_handle.h"
31 #include "chrome/browser/prerender/prerender_manager.h"
32 #include "chrome/browser/prerender/prerender_manager_factory.h"
33 #include "chrome/browser/profiles/profile.h"
34 #include "chrome/browser/ui/omnibox/omnibox_popup_model.h"
35 #include "content/public/browser/browser_thread.h"
36 #include "content/public/browser/notification_details.h"
37 #include "content/public/browser/notification_service.h"
38 #include "content/public/browser/notification_source.h"
40 namespace {
42 const float kConfidenceCutoff[] = {
43 0.8f,
44 0.5f
47 COMPILE_ASSERT(arraysize(kConfidenceCutoff) ==
48 predictors::AutocompleteActionPredictor::LAST_PREDICT_ACTION,
49 ConfidenceCutoff_count_mismatch);
51 const size_t kMinimumUserTextLength = 1;
52 const int kMinimumNumberOfHits = 3;
54 enum DatabaseAction {
55 DATABASE_ACTION_ADD,
56 DATABASE_ACTION_UPDATE,
57 DATABASE_ACTION_DELETE_SOME,
58 DATABASE_ACTION_DELETE_ALL,
59 DATABASE_ACTION_COUNT
62 } // namespace
64 namespace predictors {
66 const int AutocompleteActionPredictor::kMaximumDaysToKeepEntry = 14;
68 AutocompleteActionPredictor::AutocompleteActionPredictor(Profile* profile)
69 : profile_(profile),
70 main_profile_predictor_(NULL),
71 incognito_predictor_(NULL),
72 initialized_(false) {
73 if (profile_->IsOffTheRecord()) {
74 main_profile_predictor_ = AutocompleteActionPredictorFactory::GetForProfile(
75 profile_->GetOriginalProfile());
76 DCHECK(main_profile_predictor_);
77 main_profile_predictor_->incognito_predictor_ = this;
78 if (main_profile_predictor_->initialized_)
79 CopyFromMainProfile();
80 } else {
81 // Request the in-memory database from the history to force it to load so
82 // it's available as soon as possible.
83 HistoryService* history_service = HistoryServiceFactory::GetForProfile(
84 profile_, Profile::EXPLICIT_ACCESS);
85 if (history_service)
86 history_service->InMemoryDatabase();
88 table_ =
89 PredictorDatabaseFactory::GetForProfile(profile_)->autocomplete_table();
91 // Observe all main frame loads so we can wait for the first to complete
92 // before accessing DB and IO threads to build the local cache.
93 notification_registrar_.Add(this,
94 content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
95 content::NotificationService::AllSources());
99 AutocompleteActionPredictor::~AutocompleteActionPredictor() {
100 if (main_profile_predictor_)
101 main_profile_predictor_->incognito_predictor_ = NULL;
102 else if (incognito_predictor_)
103 incognito_predictor_->main_profile_predictor_ = NULL;
104 if (prerender_handle_.get())
105 prerender_handle_->OnCancel();
108 void AutocompleteActionPredictor::RegisterTransitionalMatches(
109 const base::string16& user_text,
110 const AutocompleteResult& result) {
111 if (user_text.length() < kMinimumUserTextLength)
112 return;
113 const base::string16 lower_user_text(base::i18n::ToLower(user_text));
115 // Merge this in to an existing match if we already saw |user_text|
116 std::vector<TransitionalMatch>::iterator match_it =
117 std::find(transitional_matches_.begin(), transitional_matches_.end(),
118 lower_user_text);
120 if (match_it == transitional_matches_.end()) {
121 TransitionalMatch transitional_match;
122 transitional_match.user_text = lower_user_text;
123 match_it = transitional_matches_.insert(transitional_matches_.end(),
124 transitional_match);
127 for (AutocompleteResult::const_iterator i(result.begin()); i != result.end();
128 ++i) {
129 if (std::find(match_it->urls.begin(), match_it->urls.end(),
130 i->destination_url) == match_it->urls.end()) {
131 match_it->urls.push_back(i->destination_url);
136 void AutocompleteActionPredictor::ClearTransitionalMatches() {
137 transitional_matches_.clear();
140 void AutocompleteActionPredictor::CancelPrerender() {
141 // If the prerender has already been abandoned, leave it to its own timeout;
142 // this normally gets called immediately after OnOmniboxOpenedUrl.
143 if (prerender_handle_ && !prerender_handle_->IsAbandoned()) {
144 prerender_handle_->OnCancel();
145 prerender_handle_.reset();
149 void AutocompleteActionPredictor::StartPrerendering(
150 const GURL& url,
151 const content::SessionStorageNamespaceMap& session_storage_namespace_map,
152 const gfx::Size& size) {
153 // Only cancel the old prerender after starting the new one, so if the URLs
154 // are the same, the underlying prerender will be reused.
155 scoped_ptr<prerender::PrerenderHandle> old_prerender_handle(
156 prerender_handle_.release());
157 if (prerender::PrerenderManager* prerender_manager =
158 prerender::PrerenderManagerFactory::GetForProfile(profile_)) {
159 content::SessionStorageNamespace* session_storage_namespace = NULL;
160 content::SessionStorageNamespaceMap::const_iterator it =
161 session_storage_namespace_map.find(std::string());
162 if (it != session_storage_namespace_map.end())
163 session_storage_namespace = it->second.get();
164 prerender_handle_.reset(prerender_manager->AddPrerenderFromOmnibox(
165 url, session_storage_namespace, size));
167 if (old_prerender_handle)
168 old_prerender_handle->OnCancel();
171 // Given a match, return a recommended action.
172 AutocompleteActionPredictor::Action
173 AutocompleteActionPredictor::RecommendAction(
174 const base::string16& user_text,
175 const AutocompleteMatch& match) const {
176 bool is_in_db = false;
177 const double confidence = CalculateConfidence(user_text, match, &is_in_db);
178 DCHECK(confidence >= 0.0 && confidence <= 1.0);
180 UMA_HISTOGRAM_BOOLEAN("AutocompleteActionPredictor.MatchIsInDb", is_in_db);
182 if (is_in_db) {
183 // Multiple enties with the same URL are fine as the confidence may be
184 // different.
185 tracked_urls_.push_back(std::make_pair(match.destination_url, confidence));
186 UMA_HISTOGRAM_COUNTS_100("AutocompleteActionPredictor.Confidence",
187 confidence * 100);
190 // Map the confidence to an action.
191 Action action = ACTION_NONE;
192 for (int i = 0; i < LAST_PREDICT_ACTION; ++i) {
193 if (confidence >= kConfidenceCutoff[i]) {
194 action = static_cast<Action>(i);
195 break;
199 // Downgrade prerender to preconnect if this is a search match or if omnibox
200 // prerendering is disabled. There are cases when Instant will not handle a
201 // search suggestion and in those cases it would be good to prerender the
202 // search results, however search engines have not been set up to correctly
203 // handle being prerendered and until they are we should avoid it.
204 // http://crbug.com/117495
205 if (action == ACTION_PRERENDER &&
206 (AutocompleteMatch::IsSearchType(match.type) ||
207 !prerender::IsOmniboxEnabled(profile_))) {
208 action = ACTION_PRECONNECT;
211 return action;
214 // Return true if the suggestion type warrants a TCP/IP preconnection.
215 // i.e., it is now quite likely that the user will select the related domain.
216 // static
217 bool AutocompleteActionPredictor::IsPreconnectable(
218 const AutocompleteMatch& match) {
219 return AutocompleteMatch::IsSearchType(match.type);
222 bool AutocompleteActionPredictor::IsPrerenderAbandonedForTesting() {
223 return prerender_handle_ && prerender_handle_->IsAbandoned();
226 void AutocompleteActionPredictor::Observe(
227 int type,
228 const content::NotificationSource& source,
229 const content::NotificationDetails& details) {
230 switch (type) {
231 case content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME:
232 CreateLocalCachesFromDatabase();
233 notification_registrar_.Remove(
234 this,
235 content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
236 content::NotificationService::AllSources());
237 break;
239 case chrome::NOTIFICATION_HISTORY_URLS_DELETED: {
240 DCHECK(initialized_);
241 const content::Details<const history::URLsDeletedDetails>
242 urls_deleted_details =
243 content::Details<const history::URLsDeletedDetails>(details);
244 if (urls_deleted_details->all_history)
245 DeleteAllRows();
246 else
247 DeleteRowsWithURLs(urls_deleted_details->rows);
248 break;
251 case chrome::NOTIFICATION_OMNIBOX_OPENED_URL: {
252 DCHECK(initialized_);
254 // TODO(dominich): This doesn't need to be synchronous. Investigate
255 // posting it as a task to be run later.
256 OnOmniboxOpenedUrl(*content::Details<OmniboxLog>(details).ptr());
257 break;
260 case chrome::NOTIFICATION_HISTORY_LOADED: {
261 TryDeleteOldEntries(content::Details<HistoryService>(details).ptr());
263 notification_registrar_.Remove(this,
264 chrome::NOTIFICATION_HISTORY_LOADED,
265 content::Source<Profile>(profile_));
266 break;
269 default:
270 NOTREACHED() << "Unexpected notification observed.";
271 break;
275 void AutocompleteActionPredictor::CreateLocalCachesFromDatabase() {
276 // Create local caches using the database as loaded. We will garbage collect
277 // rows from the caches and the database once the history service is
278 // available.
279 std::vector<AutocompleteActionPredictorTable::Row>* rows =
280 new std::vector<AutocompleteActionPredictorTable::Row>();
281 content::BrowserThread::PostTaskAndReply(content::BrowserThread::DB,
282 FROM_HERE,
283 base::Bind(&AutocompleteActionPredictorTable::GetAllRows, table_, rows),
284 base::Bind(&AutocompleteActionPredictor::CreateCaches, AsWeakPtr(),
285 base::Owned(rows)));
288 void AutocompleteActionPredictor::DeleteAllRows() {
289 if (!initialized_)
290 return;
292 db_cache_.clear();
293 db_id_cache_.clear();
295 if (table_.get()) {
296 content::BrowserThread::PostTask(content::BrowserThread::DB, FROM_HERE,
297 base::Bind(&AutocompleteActionPredictorTable::DeleteAllRows,
298 table_));
301 UMA_HISTOGRAM_ENUMERATION("AutocompleteActionPredictor.DatabaseAction",
302 DATABASE_ACTION_DELETE_ALL, DATABASE_ACTION_COUNT);
305 void AutocompleteActionPredictor::DeleteRowsWithURLs(
306 const history::URLRows& rows) {
307 if (!initialized_)
308 return;
310 std::vector<AutocompleteActionPredictorTable::Row::Id> id_list;
312 for (DBCacheMap::iterator it = db_cache_.begin(); it != db_cache_.end();) {
313 if (std::find_if(rows.begin(), rows.end(),
314 history::URLRow::URLRowHasURL(it->first.url)) != rows.end()) {
315 const DBIdCacheMap::iterator id_it = db_id_cache_.find(it->first);
316 DCHECK(id_it != db_id_cache_.end());
317 id_list.push_back(id_it->second);
318 db_id_cache_.erase(id_it);
319 db_cache_.erase(it++);
320 } else {
321 ++it;
325 if (table_.get()) {
326 content::BrowserThread::PostTask(content::BrowserThread::DB, FROM_HERE,
327 base::Bind(&AutocompleteActionPredictorTable::DeleteRows, table_,
328 id_list));
331 UMA_HISTOGRAM_ENUMERATION("AutocompleteActionPredictor.DatabaseAction",
332 DATABASE_ACTION_DELETE_SOME, DATABASE_ACTION_COUNT);
335 void AutocompleteActionPredictor::OnOmniboxOpenedUrl(const OmniboxLog& log) {
336 if (log.text.length() < kMinimumUserTextLength)
337 return;
339 // Do not attempt to learn from omnibox interactions where the omnibox
340 // dropdown is closed. In these cases the user text (|log.text|) that we
341 // learn from is either empty or effectively identical to the destination
342 // string. In either case, it can't teach us much. Also do not attempt
343 // to learn from paste-and-go actions even if the popup is open because
344 // the paste-and-go destination has no relation to whatever text the user
345 // may have typed.
346 if (!log.is_popup_open || log.is_paste_and_go)
347 return;
349 // Abandon the current prerender. If it is to be used, it will be used very
350 // soon, so use the lower timeout.
351 if (prerender_handle_) {
352 prerender_handle_->OnNavigateAway();
353 // Don't release |prerender_handle_| so it is canceled if it survives to the
354 // next StartPrerendering call.
357 UMA_HISTOGRAM_BOOLEAN(
358 base::StringPrintf("Prerender.OmniboxNavigationsCouldPrerender%s",
359 prerender::PrerenderManager::GetModeString()).c_str(),
360 prerender::IsOmniboxEnabled(profile_));
362 const AutocompleteMatch& match = log.result.match_at(log.selected_index);
363 const GURL& opened_url = match.destination_url;
364 const base::string16 lower_user_text(base::i18n::ToLower(log.text));
366 // Traverse transitional matches for those that have a user_text that is a
367 // prefix of |lower_user_text|.
368 std::vector<AutocompleteActionPredictorTable::Row> rows_to_add;
369 std::vector<AutocompleteActionPredictorTable::Row> rows_to_update;
371 for (std::vector<TransitionalMatch>::const_iterator it =
372 transitional_matches_.begin(); it != transitional_matches_.end();
373 ++it) {
374 if (!StartsWith(lower_user_text, it->user_text, true))
375 continue;
377 // Add entries to the database for those matches.
378 for (std::vector<GURL>::const_iterator url_it = it->urls.begin();
379 url_it != it->urls.end(); ++url_it) {
380 DCHECK(it->user_text.length() >= kMinimumUserTextLength);
381 const DBCacheKey key = { it->user_text, *url_it };
382 const bool is_hit = (*url_it == opened_url);
384 AutocompleteActionPredictorTable::Row row;
385 row.user_text = key.user_text;
386 row.url = key.url;
388 DBCacheMap::iterator it = db_cache_.find(key);
389 if (it == db_cache_.end()) {
390 row.id = base::GenerateGUID();
391 row.number_of_hits = is_hit ? 1 : 0;
392 row.number_of_misses = is_hit ? 0 : 1;
394 rows_to_add.push_back(row);
395 } else {
396 DCHECK(db_id_cache_.find(key) != db_id_cache_.end());
397 row.id = db_id_cache_.find(key)->second;
398 row.number_of_hits = it->second.number_of_hits + (is_hit ? 1 : 0);
399 row.number_of_misses = it->second.number_of_misses + (is_hit ? 0 : 1);
401 rows_to_update.push_back(row);
405 if (rows_to_add.size() > 0 || rows_to_update.size() > 0)
406 AddAndUpdateRows(rows_to_add, rows_to_update);
408 ClearTransitionalMatches();
410 // Check against tracked urls and log accuracy for the confidence we
411 // predicted.
412 for (std::vector<std::pair<GURL, double> >::const_iterator it =
413 tracked_urls_.begin(); it != tracked_urls_.end();
414 ++it) {
415 if (opened_url == it->first) {
416 UMA_HISTOGRAM_COUNTS_100("AutocompleteActionPredictor.AccurateCount",
417 it->second * 100);
420 tracked_urls_.clear();
423 void AutocompleteActionPredictor::AddAndUpdateRows(
424 const AutocompleteActionPredictorTable::Rows& rows_to_add,
425 const AutocompleteActionPredictorTable::Rows& rows_to_update) {
426 if (!initialized_)
427 return;
429 for (AutocompleteActionPredictorTable::Rows::const_iterator it =
430 rows_to_add.begin(); it != rows_to_add.end(); ++it) {
431 const DBCacheKey key = { it->user_text, it->url };
432 DBCacheValue value = { it->number_of_hits, it->number_of_misses };
434 DCHECK(db_cache_.find(key) == db_cache_.end());
436 db_cache_[key] = value;
437 db_id_cache_[key] = it->id;
438 UMA_HISTOGRAM_ENUMERATION("AutocompleteActionPredictor.DatabaseAction",
439 DATABASE_ACTION_ADD, DATABASE_ACTION_COUNT);
441 for (AutocompleteActionPredictorTable::Rows::const_iterator it =
442 rows_to_update.begin(); it != rows_to_update.end(); ++it) {
443 const DBCacheKey key = { it->user_text, it->url };
445 DBCacheMap::iterator db_it = db_cache_.find(key);
446 DCHECK(db_it != db_cache_.end());
447 DCHECK(db_id_cache_.find(key) != db_id_cache_.end());
449 db_it->second.number_of_hits = it->number_of_hits;
450 db_it->second.number_of_misses = it->number_of_misses;
451 UMA_HISTOGRAM_ENUMERATION("AutocompleteActionPredictor.DatabaseAction",
452 DATABASE_ACTION_UPDATE, DATABASE_ACTION_COUNT);
455 if (table_.get()) {
456 content::BrowserThread::PostTask(content::BrowserThread::DB, FROM_HERE,
457 base::Bind(&AutocompleteActionPredictorTable::AddAndUpdateRows,
458 table_, rows_to_add, rows_to_update));
462 void AutocompleteActionPredictor::CreateCaches(
463 std::vector<AutocompleteActionPredictorTable::Row>* rows) {
464 CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
465 DCHECK(!profile_->IsOffTheRecord());
466 DCHECK(!initialized_);
467 DCHECK(db_cache_.empty());
468 DCHECK(db_id_cache_.empty());
470 for (std::vector<AutocompleteActionPredictorTable::Row>::const_iterator it =
471 rows->begin(); it != rows->end(); ++it) {
472 const DBCacheKey key = { it->user_text, it->url };
473 const DBCacheValue value = { it->number_of_hits, it->number_of_misses };
474 db_cache_[key] = value;
475 db_id_cache_[key] = it->id;
478 // If the history service is ready, delete any old or invalid entries.
479 HistoryService* history_service =
480 HistoryServiceFactory::GetForProfile(profile_, Profile::EXPLICIT_ACCESS);
481 if (!TryDeleteOldEntries(history_service)) {
482 // Wait for the notification that the history service is ready and the URL
483 // DB is loaded.
484 notification_registrar_.Add(this, chrome::NOTIFICATION_HISTORY_LOADED,
485 content::Source<Profile>(profile_));
489 bool AutocompleteActionPredictor::TryDeleteOldEntries(HistoryService* service) {
490 CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
491 DCHECK(!profile_->IsOffTheRecord());
492 DCHECK(!initialized_);
494 if (!service)
495 return false;
497 history::URLDatabase* url_db = service->InMemoryDatabase();
498 if (!url_db)
499 return false;
501 DeleteOldEntries(url_db);
502 return true;
505 void AutocompleteActionPredictor::DeleteOldEntries(
506 history::URLDatabase* url_db) {
507 CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
508 DCHECK(!profile_->IsOffTheRecord());
509 DCHECK(!initialized_);
510 DCHECK(table_.get());
512 std::vector<AutocompleteActionPredictorTable::Row::Id> ids_to_delete;
513 DeleteOldIdsFromCaches(url_db, &ids_to_delete);
515 content::BrowserThread::PostTask(content::BrowserThread::DB, FROM_HERE,
516 base::Bind(&AutocompleteActionPredictorTable::DeleteRows, table_,
517 ids_to_delete));
519 FinishInitialization();
520 if (incognito_predictor_)
521 incognito_predictor_->CopyFromMainProfile();
524 void AutocompleteActionPredictor::DeleteOldIdsFromCaches(
525 history::URLDatabase* url_db,
526 std::vector<AutocompleteActionPredictorTable::Row::Id>* id_list) {
527 CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
528 DCHECK(!profile_->IsOffTheRecord());
529 DCHECK(!initialized_);
530 DCHECK(url_db);
531 DCHECK(id_list);
533 id_list->clear();
534 for (DBCacheMap::iterator it = db_cache_.begin(); it != db_cache_.end();) {
535 history::URLRow url_row;
537 if ((url_db->GetRowForURL(it->first.url, &url_row) == 0) ||
538 ((base::Time::Now() - url_row.last_visit()).InDays() >
539 kMaximumDaysToKeepEntry)) {
540 const DBIdCacheMap::iterator id_it = db_id_cache_.find(it->first);
541 DCHECK(id_it != db_id_cache_.end());
542 id_list->push_back(id_it->second);
543 db_id_cache_.erase(id_it);
544 db_cache_.erase(it++);
545 } else {
546 ++it;
551 void AutocompleteActionPredictor::CopyFromMainProfile() {
552 CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
553 DCHECK(profile_->IsOffTheRecord());
554 DCHECK(!initialized_);
555 DCHECK(main_profile_predictor_);
556 DCHECK(main_profile_predictor_->initialized_);
558 db_cache_ = main_profile_predictor_->db_cache_;
559 db_id_cache_ = main_profile_predictor_->db_id_cache_;
560 FinishInitialization();
563 void AutocompleteActionPredictor::FinishInitialization() {
564 CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
565 DCHECK(!initialized_);
567 // Incognito and normal profiles should listen only to omnibox notifications
568 // from their own profile, but both should listen to history deletions from
569 // the main profile, since opening the history page in either case actually
570 // opens the non-incognito history (and lets users delete from there).
571 notification_registrar_.Add(this, chrome::NOTIFICATION_OMNIBOX_OPENED_URL,
572 content::Source<Profile>(profile_));
573 notification_registrar_.Add(this, chrome::NOTIFICATION_HISTORY_URLS_DELETED,
574 content::Source<Profile>(profile_->GetOriginalProfile()));
575 initialized_ = true;
578 double AutocompleteActionPredictor::CalculateConfidence(
579 const base::string16& user_text,
580 const AutocompleteMatch& match,
581 bool* is_in_db) const {
582 const DBCacheKey key = { user_text, match.destination_url };
584 *is_in_db = false;
585 if (user_text.length() < kMinimumUserTextLength)
586 return 0.0;
588 const DBCacheMap::const_iterator iter = db_cache_.find(key);
589 if (iter == db_cache_.end())
590 return 0.0;
592 *is_in_db = true;
593 return CalculateConfidenceForDbEntry(iter);
596 double AutocompleteActionPredictor::CalculateConfidenceForDbEntry(
597 DBCacheMap::const_iterator iter) const {
598 const DBCacheValue& value = iter->second;
599 if (value.number_of_hits < kMinimumNumberOfHits)
600 return 0.0;
602 const double number_of_hits = static_cast<double>(value.number_of_hits);
603 return number_of_hits / (number_of_hits + value.number_of_misses);
606 AutocompleteActionPredictor::TransitionalMatch::TransitionalMatch() {
609 AutocompleteActionPredictor::TransitionalMatch::~TransitionalMatch() {
612 } // namespace predictors