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. 4 5 #include "chrome/browser/predictors/resource_prefetch_predictor.h" 6 7 #include <map> 8 #include <set> 9 #include <utility> 10 11 #include "base/command_line.h" 12 #include "base/metrics/histogram.h" 13 #include "base/stl_util.h" 14 #include "base/strings/string_number_conversions.h" 15 #include "base/strings/stringprintf.h" 16 #include "base/time/time.h" 17 #include "chrome/browser/chrome_notification_types.h" 18 #include "chrome/browser/history/history_database.h" 19 #include "chrome/browser/history/history_db_task.h" 20 #include "chrome/browser/history/history_notifications.h" 21 #include "chrome/browser/history/history_service.h" 22 #include "chrome/browser/history/history_service_factory.h" 23 #include "chrome/browser/predictors/predictor_database.h" 24 #include "chrome/browser/predictors/predictor_database_factory.h" 25 #include "chrome/browser/predictors/resource_prefetcher_manager.h" 26 #include "chrome/browser/profiles/profile.h" 27 #include "chrome/common/chrome_switches.h" 28 #include "chrome/common/url_constants.h" 29 #include "content/public/browser/browser_thread.h" 30 #include "content/public/browser/load_from_memory_cache_details.h" 31 #include "content/public/browser/navigation_controller.h" 32 #include "content/public/browser/notification_service.h" 33 #include "content/public/browser/notification_source.h" 34 #include "content/public/browser/notification_types.h" 35 #include "content/public/browser/resource_request_info.h" 36 #include "content/public/browser/web_contents.h" 37 #include "net/base/mime_util.h" 38 #include "net/http/http_response_headers.h" 39 #include "net/url_request/url_request.h" 40 #include "net/url_request/url_request_context_getter.h" 41 42 using content::BrowserThread; 43 44 namespace { 45 46 // For reporting whether a subresource is handled or not, and for what reasons. 47 enum ResourceStatus { 48 RESOURCE_STATUS_HANDLED = 0, 49 RESOURCE_STATUS_NOT_HTTP_PAGE = 1, 50 RESOURCE_STATUS_NOT_HTTP_RESOURCE = 2, 51 RESOURCE_STATUS_UNSUPPORTED_MIME_TYPE = 4, 52 RESOURCE_STATUS_NOT_GET = 8, 53 RESOURCE_STATUS_URL_TOO_LONG = 16, 54 RESOURCE_STATUS_NOT_CACHEABLE = 32, 55 RESOURCE_STATUS_HEADERS_MISSING = 64, 56 RESOURCE_STATUS_MAX = 128, 57 }; 58 59 // For reporting various interesting events that occur during the loading of a 60 // single main frame. 61 enum NavigationEvent { 62 NAVIGATION_EVENT_REQUEST_STARTED = 0, 63 NAVIGATION_EVENT_REQUEST_REDIRECTED = 1, 64 NAVIGATION_EVENT_REQUEST_REDIRECTED_EMPTY_URL = 2, 65 NAVIGATION_EVENT_REQUEST_EXPIRED = 3, 66 NAVIGATION_EVENT_RESPONSE_STARTED = 4, 67 NAVIGATION_EVENT_ONLOAD = 5, 68 NAVIGATION_EVENT_ONLOAD_EMPTY_URL = 6, 69 NAVIGATION_EVENT_ONLOAD_UNTRACKED_URL = 7, 70 NAVIGATION_EVENT_ONLOAD_TRACKED_URL = 8, 71 NAVIGATION_EVENT_SHOULD_TRACK_URL = 9, 72 NAVIGATION_EVENT_SHOULD_NOT_TRACK_URL = 10, 73 NAVIGATION_EVENT_URL_TABLE_FULL = 11, 74 NAVIGATION_EVENT_HAVE_PREDICTIONS_FOR_URL = 12, 75 NAVIGATION_EVENT_NO_PREDICTIONS_FOR_URL = 13, 76 NAVIGATION_EVENT_MAIN_FRAME_URL_TOO_LONG = 14, 77 NAVIGATION_EVENT_HOST_TOO_LONG = 15, 78 NAVIGATION_EVENT_COUNT = 16, 79 }; 80 81 // For reporting events of interest that are not tied to any navigation. 82 enum ReportingEvent { 83 REPORTING_EVENT_ALL_HISTORY_CLEARED = 0, 84 REPORTING_EVENT_PARTIAL_HISTORY_CLEARED = 1, 85 REPORTING_EVENT_COUNT = 2 86 }; 87 88 void RecordNavigationEvent(NavigationEvent event) { 89 UMA_HISTOGRAM_ENUMERATION("ResourcePrefetchPredictor.NavigationEvent", 90 event, 91 NAVIGATION_EVENT_COUNT); 92 } 93 94 } // namespace 95 96 namespace predictors { 97 98 //////////////////////////////////////////////////////////////////////////////// 99 // History lookup task. 100 101 // Used to fetch the visit count for a URL from the History database. 102 class GetUrlVisitCountTask : public history::HistoryDBTask { 103 public: 104 typedef ResourcePrefetchPredictor::URLRequestSummary URLRequestSummary; 105 typedef base::Callback<void( 106 int, // Visit count. 107 const NavigationID&, 108 const std::vector<URLRequestSummary>&)> VisitInfoCallback; 109 110 GetUrlVisitCountTask( 111 const NavigationID& navigation_id, 112 std::vector<URLRequestSummary>* requests, 113 VisitInfoCallback callback) 114 : visit_count_(0), 115 navigation_id_(navigation_id), 116 requests_(requests), 117 callback_(callback) { 118 DCHECK(requests_.get()); 119 } 120 121 virtual bool RunOnDBThread(history::HistoryBackend* backend, 122 history::HistoryDatabase* db) OVERRIDE { 123 history::URLRow url_row; 124 if (db->GetRowForURL(navigation_id_.main_frame_url, &url_row)) 125 visit_count_ = url_row.visit_count(); 126 return true; 127 } 128 129 virtual void DoneRunOnMainThread() OVERRIDE { 130 callback_.Run(visit_count_, navigation_id_, *requests_); 131 } 132 133 private: 134 virtual ~GetUrlVisitCountTask() { } 135 136 int visit_count_; 137 NavigationID navigation_id_; 138 scoped_ptr<std::vector<URLRequestSummary> > requests_; 139 VisitInfoCallback callback_; 140 141 DISALLOW_COPY_AND_ASSIGN(GetUrlVisitCountTask); 142 }; 143 144 //////////////////////////////////////////////////////////////////////////////// 145 // ResourcePrefetchPredictor static functions. 146 147 // static 148 bool ResourcePrefetchPredictor::ShouldRecordRequest( 149 net::URLRequest* request, 150 ResourceType::Type resource_type) { 151 return resource_type == ResourceType::MAIN_FRAME && 152 IsHandledMainPage(request); 153 } 154 155 // static 156 bool ResourcePrefetchPredictor::ShouldRecordResponse( 157 net::URLRequest* response) { 158 const content::ResourceRequestInfo* request_info = 159 content::ResourceRequestInfo::ForRequest(response); 160 if (!request_info) 161 return false; 162 163 return request_info->GetResourceType() == ResourceType::MAIN_FRAME ? 164 IsHandledMainPage(response) : IsHandledSubresource(response); 165 } 166 167 // static 168 bool ResourcePrefetchPredictor::ShouldRecordRedirect( 169 net::URLRequest* response) { 170 const content::ResourceRequestInfo* request_info = 171 content::ResourceRequestInfo::ForRequest(response); 172 if (!request_info) 173 return false; 174 175 return request_info->GetResourceType() == ResourceType::MAIN_FRAME && 176 IsHandledMainPage(response); 177 } 178 179 // static 180 bool ResourcePrefetchPredictor::IsHandledMainPage(net::URLRequest* request) { 181 return request->original_url().scheme() == chrome::kHttpScheme; 182 } 183 184 // static 185 bool ResourcePrefetchPredictor::IsHandledSubresource( 186 net::URLRequest* response) { 187 int resource_status = 0; 188 if (response->first_party_for_cookies().scheme() != chrome::kHttpScheme) 189 resource_status |= RESOURCE_STATUS_NOT_HTTP_PAGE; 190 191 if (response->original_url().scheme() != chrome::kHttpScheme) 192 resource_status |= RESOURCE_STATUS_NOT_HTTP_RESOURCE; 193 194 std::string mime_type; 195 response->GetMimeType(&mime_type); 196 if (!mime_type.empty() && 197 !net::IsSupportedImageMimeType(mime_type.c_str()) && 198 !net::IsSupportedJavascriptMimeType(mime_type.c_str()) && 199 !net::MatchesMimeType("text/css", mime_type)) { 200 resource_status |= RESOURCE_STATUS_UNSUPPORTED_MIME_TYPE; 201 } 202 203 if (response->method() != "GET") 204 resource_status |= RESOURCE_STATUS_NOT_GET; 205 206 if (response->original_url().spec().length() > 207 ResourcePrefetchPredictorTables::kMaxStringLength) { 208 resource_status |= RESOURCE_STATUS_URL_TOO_LONG; 209 } 210 211 if (!response->response_info().headers.get()) 212 resource_status |= RESOURCE_STATUS_HEADERS_MISSING; 213 214 if (!IsCacheable(response)) 215 resource_status |= RESOURCE_STATUS_NOT_CACHEABLE; 216 217 UMA_HISTOGRAM_ENUMERATION("ResourcePrefetchPredictor.ResourceStatus", 218 resource_status, 219 RESOURCE_STATUS_MAX); 220 221 return resource_status == 0; 222 } 223 224 // static 225 bool ResourcePrefetchPredictor::IsCacheable(const net::URLRequest* response) { 226 if (response->was_cached()) 227 return true; 228 229 // For non cached responses, we will ensure that the freshness lifetime is 230 // some sane value. 231 const net::HttpResponseInfo& response_info = response->response_info(); 232 if (!response_info.headers.get()) 233 return false; 234 base::Time response_time(response_info.response_time); 235 response_time += base::TimeDelta::FromSeconds(1); 236 base::TimeDelta freshness = response_info.headers->GetFreshnessLifetime( 237 response_time); 238 return freshness > base::TimeDelta(); 239 } 240 241 // static 242 ResourceType::Type ResourcePrefetchPredictor::GetResourceTypeFromMimeType( 243 const std::string& mime_type, 244 ResourceType::Type fallback) { 245 if (net::IsSupportedImageMimeType(mime_type.c_str())) 246 return ResourceType::IMAGE; 247 else if (net::IsSupportedJavascriptMimeType(mime_type.c_str())) 248 return ResourceType::SCRIPT; 249 else if (net::MatchesMimeType("text/css", mime_type)) 250 return ResourceType::STYLESHEET; 251 else 252 return fallback; 253 } 254 255 //////////////////////////////////////////////////////////////////////////////// 256 // ResourcePrefetchPredictor structs. 257 258 ResourcePrefetchPredictor::URLRequestSummary::URLRequestSummary() 259 : resource_type(ResourceType::LAST_TYPE), 260 was_cached(false) { 261 } 262 263 ResourcePrefetchPredictor::URLRequestSummary::URLRequestSummary( 264 const URLRequestSummary& other) 265 : navigation_id(other.navigation_id), 266 resource_url(other.resource_url), 267 resource_type(other.resource_type), 268 mime_type(other.mime_type), 269 was_cached(other.was_cached), 270 redirect_url(other.redirect_url) { 271 } 272 273 ResourcePrefetchPredictor::URLRequestSummary::~URLRequestSummary() { 274 } 275 276 ResourcePrefetchPredictor::Result::Result( 277 PrefetchKeyType i_key_type, 278 ResourcePrefetcher::RequestVector* i_requests) 279 : key_type(i_key_type), 280 requests(i_requests) { 281 } 282 283 ResourcePrefetchPredictor::Result::~Result() { 284 } 285 286 //////////////////////////////////////////////////////////////////////////////// 287 // ResourcePrefetchPredictor. 288 289 ResourcePrefetchPredictor::ResourcePrefetchPredictor( 290 const ResourcePrefetchPredictorConfig& config, 291 Profile* profile) 292 : profile_(profile), 293 config_(config), 294 initialization_state_(NOT_INITIALIZED), 295 tables_(PredictorDatabaseFactory::GetForProfile( 296 profile)->resource_prefetch_tables()), 297 results_map_deleter_(&results_map_) { 298 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 299 300 // Some form of learning has to be enabled. 301 DCHECK(config_.IsLearningEnabled()); 302 if (config_.IsURLPrefetchingEnabled()) 303 DCHECK(config_.IsURLLearningEnabled()); 304 if (config_.IsHostPrefetchingEnabled()) 305 DCHECK(config_.IsHostLearningEnabled()); 306 307 notification_registrar_.Add(this, 308 content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME, 309 content::NotificationService::AllSources()); 310 } 311 312 ResourcePrefetchPredictor::~ResourcePrefetchPredictor() { 313 } 314 315 void ResourcePrefetchPredictor::RecordURLRequest( 316 const URLRequestSummary& request) { 317 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 318 if (initialization_state_ != INITIALIZED) 319 return; 320 321 CHECK_EQ(request.resource_type, ResourceType::MAIN_FRAME); 322 OnMainFrameRequest(request); 323 } 324 325 void ResourcePrefetchPredictor::RecordUrlResponse( 326 const URLRequestSummary& response) { 327 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 328 if (initialization_state_ != INITIALIZED) 329 return; 330 331 if (response.resource_type == ResourceType::MAIN_FRAME) 332 OnMainFrameResponse(response); 333 else 334 OnSubresourceResponse(response); 335 } 336 337 void ResourcePrefetchPredictor::RecordUrlRedirect( 338 const URLRequestSummary& response) { 339 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 340 if (initialization_state_ != INITIALIZED) 341 return; 342 343 CHECK_EQ(response.resource_type, ResourceType::MAIN_FRAME); 344 OnMainFrameRedirect(response); 345 } 346 347 void ResourcePrefetchPredictor::FinishedPrefetchForNavigation( 348 const NavigationID& navigation_id, 349 PrefetchKeyType key_type, 350 ResourcePrefetcher::RequestVector* requests) { 351 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 352 353 Result* result = new Result(key_type, requests); 354 // Add the results to the results map. 355 if (!results_map_.insert(std::make_pair(navigation_id, result)).second) { 356 DLOG(FATAL) << "Returning results for existing navigation."; 357 delete result; 358 } 359 } 360 361 void ResourcePrefetchPredictor::Observe( 362 int type, 363 const content::NotificationSource& source, 364 const content::NotificationDetails& details) { 365 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 366 367 switch (type) { 368 case content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME: { 369 switch (initialization_state_) { 370 case NOT_INITIALIZED: 371 StartInitialization(); 372 break; 373 case INITIALIZING: 374 break; 375 case INITIALIZED: { 376 RecordNavigationEvent(NAVIGATION_EVENT_ONLOAD); 377 const content::WebContents* web_contents = 378 content::Source<content::WebContents>(source).ptr(); 379 NavigationID navigation_id(*web_contents); 380 // WebContents can return an empty URL if the navigation entry 381 // corresponding to the navigation has not been created yet. 382 if (navigation_id.main_frame_url.is_empty()) 383 RecordNavigationEvent(NAVIGATION_EVENT_ONLOAD_EMPTY_URL); 384 else 385 OnNavigationComplete(navigation_id); 386 break; 387 } 388 default: 389 NOTREACHED() << "Unexpected initialization_state_: " 390 << initialization_state_; 391 } 392 break; 393 } 394 395 case content::NOTIFICATION_LOAD_FROM_MEMORY_CACHE: { 396 const content::LoadFromMemoryCacheDetails* load_details = 397 content::Details<content::LoadFromMemoryCacheDetails>(details).ptr(); 398 const content::WebContents* web_contents = 399 content::Source<content::NavigationController>( 400 source).ptr()->GetWebContents(); 401 402 NavigationID navigation_id(*web_contents); 403 OnSubresourceLoadedFromMemory(navigation_id, 404 load_details->url, 405 load_details->mime_type, 406 load_details->resource_type); 407 break; 408 } 409 410 case chrome::NOTIFICATION_HISTORY_LOADED: { 411 DCHECK_EQ(initialization_state_, INITIALIZING); 412 notification_registrar_.Remove(this, 413 chrome::NOTIFICATION_HISTORY_LOADED, 414 content::Source<Profile>(profile_)); 415 OnHistoryAndCacheLoaded(); 416 break; 417 } 418 419 case chrome::NOTIFICATION_HISTORY_URLS_DELETED: { 420 DCHECK_EQ(initialization_state_, INITIALIZED); 421 const content::Details<const history::URLsDeletedDetails> 422 urls_deleted_details = 423 content::Details<const history::URLsDeletedDetails>(details); 424 if (urls_deleted_details->all_history) { 425 DeleteAllUrls(); 426 UMA_HISTOGRAM_ENUMERATION("ResourcePrefetchPredictor.ReportingEvent", 427 REPORTING_EVENT_ALL_HISTORY_CLEARED, 428 REPORTING_EVENT_COUNT); 429 } else { 430 DeleteUrls(urls_deleted_details->rows); 431 UMA_HISTOGRAM_ENUMERATION("ResourcePrefetchPredictor.ReportingEvent", 432 REPORTING_EVENT_PARTIAL_HISTORY_CLEARED, 433 REPORTING_EVENT_COUNT); 434 } 435 break; 436 } 437 438 default: 439 NOTREACHED() << "Unexpected notification observed."; 440 break; 441 } 442 } 443 444 void ResourcePrefetchPredictor::Shutdown() { 445 if (prefetch_manager_.get()) { 446 prefetch_manager_->ShutdownOnUIThread(); 447 prefetch_manager_ = NULL; 448 } 449 } 450 451 void ResourcePrefetchPredictor::OnMainFrameRequest( 452 const URLRequestSummary& request) { 453 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 454 DCHECK_EQ(INITIALIZED, initialization_state_); 455 456 RecordNavigationEvent(NAVIGATION_EVENT_REQUEST_STARTED); 457 458 StartPrefetching(request.navigation_id); 459 460 // Cleanup older navigations. 461 CleanupAbandonedNavigations(request.navigation_id); 462 463 // New empty navigation entry. 464 inflight_navigations_.insert(std::make_pair( 465 request.navigation_id, 466 make_linked_ptr(new std::vector<URLRequestSummary>()))); 467 } 468 469 void ResourcePrefetchPredictor::OnMainFrameResponse( 470 const URLRequestSummary& response) { 471 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 472 if (initialization_state_ != INITIALIZED) 473 return; 474 475 RecordNavigationEvent(NAVIGATION_EVENT_RESPONSE_STARTED); 476 477 StopPrefetching(response.navigation_id); 478 } 479 480 void ResourcePrefetchPredictor::OnMainFrameRedirect( 481 const URLRequestSummary& response) { 482 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 483 484 RecordNavigationEvent(NAVIGATION_EVENT_REQUEST_REDIRECTED); 485 486 // TODO(shishir): There are significant gains to be had here if we can use the 487 // start URL in a redirect chain as the key to start prefetching. We can save 488 // of redirect times considerably assuming that the redirect chains do not 489 // change. 490 491 // Stop any inflight prefetching. Remove the older navigation. 492 StopPrefetching(response.navigation_id); 493 inflight_navigations_.erase(response.navigation_id); 494 495 // A redirect will not lead to another OnMainFrameRequest call, so record the 496 // redirect url as a new navigation. 497 498 // The redirect url may be empty if the url was invalid. 499 if (response.redirect_url.is_empty()) { 500 RecordNavigationEvent(NAVIGATION_EVENT_REQUEST_REDIRECTED_EMPTY_URL); 501 return; 502 } 503 504 NavigationID navigation_id(response.navigation_id); 505 navigation_id.main_frame_url = response.redirect_url; 506 inflight_navigations_.insert(std::make_pair( 507 navigation_id, 508 make_linked_ptr(new std::vector<URLRequestSummary>()))); 509 } 510 511 void ResourcePrefetchPredictor::OnSubresourceResponse( 512 const URLRequestSummary& response) { 513 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 514 515 if (inflight_navigations_.find(response.navigation_id) == 516 inflight_navigations_.end()) { 517 return; 518 } 519 520 inflight_navigations_[response.navigation_id]->push_back(response); 521 } 522 523 void ResourcePrefetchPredictor::OnSubresourceLoadedFromMemory( 524 const NavigationID& navigation_id, 525 const GURL& resource_url, 526 const std::string& mime_type, 527 ResourceType::Type resource_type) { 528 if (inflight_navigations_.find(navigation_id) == inflight_navigations_.end()) 529 return; 530 531 URLRequestSummary summary; 532 summary.navigation_id = navigation_id; 533 summary.resource_url = resource_url; 534 summary.mime_type = mime_type; 535 summary.resource_type = GetResourceTypeFromMimeType(mime_type, resource_type); 536 summary.was_cached = true; 537 inflight_navigations_[navigation_id]->push_back(summary); 538 } 539 540 void ResourcePrefetchPredictor::OnNavigationComplete( 541 const NavigationID& navigation_id) { 542 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 543 544 NavigationMap::const_iterator nav_it = 545 inflight_navigations_.find(navigation_id); 546 if (nav_it == inflight_navigations_.end()) { 547 RecordNavigationEvent(NAVIGATION_EVENT_ONLOAD_UNTRACKED_URL); 548 return; 549 } 550 RecordNavigationEvent(NAVIGATION_EVENT_ONLOAD_TRACKED_URL); 551 552 // Report any stats. 553 if (prefetch_manager_.get()) { 554 ResultsMap::iterator results_it = results_map_.find(navigation_id); 555 bool have_prefetch_results = results_it != results_map_.end(); 556 UMA_HISTOGRAM_BOOLEAN("ResourcePrefetchPredictor.HavePrefetchResults", 557 have_prefetch_results); 558 if (have_prefetch_results) { 559 ReportAccuracyStats(results_it->second->key_type, 560 *(nav_it->second), 561 results_it->second->requests.get()); 562 } 563 } else { 564 scoped_ptr<ResourcePrefetcher::RequestVector> requests( 565 new ResourcePrefetcher::RequestVector); 566 PrefetchKeyType key_type; 567 if (GetPrefetchData(navigation_id, requests.get(), &key_type)){ 568 RecordNavigationEvent(NAVIGATION_EVENT_HAVE_PREDICTIONS_FOR_URL); 569 ReportPredictedAccuracyStats(key_type, 570 *(nav_it->second), 571 *requests); 572 } else { 573 RecordNavigationEvent(NAVIGATION_EVENT_NO_PREDICTIONS_FOR_URL); 574 } 575 } 576 577 // Remove the navigation from the inflight navigations. 578 std::vector<URLRequestSummary>* requests = 579 inflight_navigations_[navigation_id].release(); 580 inflight_navigations_.erase(navigation_id); 581 582 // Kick off history lookup to determine if we should record the URL. 583 HistoryService* history_service = HistoryServiceFactory::GetForProfile( 584 profile_, Profile::EXPLICIT_ACCESS); 585 DCHECK(history_service); 586 history_service->ScheduleDBTask( 587 new GetUrlVisitCountTask( 588 navigation_id, 589 requests, 590 base::Bind(&ResourcePrefetchPredictor::OnVisitCountLookup, 591 AsWeakPtr())), 592 &history_lookup_consumer_); 593 } 594 595 bool ResourcePrefetchPredictor::GetPrefetchData( 596 const NavigationID& navigation_id, 597 ResourcePrefetcher::RequestVector* prefetch_requests, 598 PrefetchKeyType* key_type) { 599 DCHECK(prefetch_requests); 600 DCHECK(key_type); 601 602 *key_type = PREFETCH_KEY_TYPE_URL; 603 const GURL& main_frame_url = navigation_id.main_frame_url; 604 605 bool use_url_data = config_.IsPrefetchingEnabled() ? 606 config_.IsURLPrefetchingEnabled() : config_.IsURLLearningEnabled(); 607 if (use_url_data) { 608 PrefetchDataMap::const_iterator iterator = 609 url_table_cache_->find(main_frame_url.spec()); 610 if (iterator != url_table_cache_->end()) 611 PopulatePrefetcherRequest(iterator->second, prefetch_requests); 612 } 613 if (!prefetch_requests->empty()) 614 return true; 615 616 bool use_host_data = config_.IsPrefetchingEnabled() ? 617 config_.IsHostPrefetchingEnabled() : config_.IsHostLearningEnabled(); 618 if (use_host_data) { 619 PrefetchDataMap::const_iterator iterator = 620 host_table_cache_->find(main_frame_url.host()); 621 if (iterator != host_table_cache_->end()) { 622 *key_type = PREFETCH_KEY_TYPE_HOST; 623 PopulatePrefetcherRequest(iterator->second, prefetch_requests); 624 } 625 } 626 627 return !prefetch_requests->empty(); 628 } 629 630 void ResourcePrefetchPredictor::PopulatePrefetcherRequest( 631 const PrefetchData& data, 632 ResourcePrefetcher::RequestVector* requests) { 633 for (ResourceRows::const_iterator it = data.resources.begin(); 634 it != data.resources.end(); ++it) { 635 float confidence = static_cast<float>(it->number_of_hits) / 636 (it->number_of_hits + it->number_of_misses); 637 if (confidence < config_.min_resource_confidence_to_trigger_prefetch || 638 it->number_of_hits < config_.min_resource_hits_to_trigger_prefetch) { 639 continue; 640 } 641 642 ResourcePrefetcher::Request* req = new ResourcePrefetcher::Request( 643 it->resource_url); 644 requests->push_back(req); 645 } 646 } 647 648 void ResourcePrefetchPredictor::StartPrefetching( 649 const NavigationID& navigation_id) { 650 if (!prefetch_manager_.get()) // Prefetching not enabled. 651 return; 652 653 // Prefer URL based data first. 654 scoped_ptr<ResourcePrefetcher::RequestVector> requests( 655 new ResourcePrefetcher::RequestVector); 656 PrefetchKeyType key_type; 657 if (!GetPrefetchData(navigation_id, requests.get(), &key_type)) { 658 // No prefetching data at host or URL level. 659 return; 660 } 661 662 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, 663 base::Bind(&ResourcePrefetcherManager::MaybeAddPrefetch, 664 prefetch_manager_, 665 navigation_id, 666 key_type, 667 base::Passed(&requests))); 668 } 669 670 void ResourcePrefetchPredictor::StopPrefetching( 671 const NavigationID& navigation_id) { 672 if (!prefetch_manager_.get()) // Not enabled. 673 return; 674 675 BrowserThread::PostTask( 676 BrowserThread::IO, FROM_HERE, 677 base::Bind(&ResourcePrefetcherManager::MaybeRemovePrefetch, 678 prefetch_manager_, 679 navigation_id)); 680 } 681 682 void ResourcePrefetchPredictor::StartInitialization() { 683 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 684 685 DCHECK_EQ(initialization_state_, NOT_INITIALIZED); 686 initialization_state_ = INITIALIZING; 687 688 // Create local caches using the database as loaded. 689 scoped_ptr<PrefetchDataMap> url_data_map(new PrefetchDataMap()); 690 scoped_ptr<PrefetchDataMap> host_data_map(new PrefetchDataMap()); 691 PrefetchDataMap* url_data_ptr = url_data_map.get(); 692 PrefetchDataMap* host_data_ptr = host_data_map.get(); 693 694 BrowserThread::PostTaskAndReply( 695 BrowserThread::DB, FROM_HERE, 696 base::Bind(&ResourcePrefetchPredictorTables::GetAllData, 697 tables_, url_data_ptr, host_data_ptr), 698 base::Bind(&ResourcePrefetchPredictor::CreateCaches, AsWeakPtr(), 699 base::Passed(&url_data_map), base::Passed(&host_data_map))); 700 } 701 702 void ResourcePrefetchPredictor::CreateCaches( 703 scoped_ptr<PrefetchDataMap> url_data_map, 704 scoped_ptr<PrefetchDataMap> host_data_map) { 705 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 706 707 DCHECK_EQ(initialization_state_, INITIALIZING); 708 DCHECK(!url_table_cache_); 709 DCHECK(!host_table_cache_); 710 DCHECK(inflight_navigations_.empty()); 711 712 url_table_cache_.reset(url_data_map.release()); 713 host_table_cache_.reset(host_data_map.release()); 714 715 UMA_HISTOGRAM_COUNTS("ResourcePrefetchPredictor.UrlTableMainFrameUrlCount", 716 url_table_cache_->size()); 717 UMA_HISTOGRAM_COUNTS("ResourcePrefetchPredictor.HostTableHostCount", 718 host_table_cache_->size()); 719 720 // Add notifications for history loading if it is not ready. 721 HistoryService* history_service = HistoryServiceFactory::GetForProfile( 722 profile_, Profile::EXPLICIT_ACCESS); 723 if (!history_service) { 724 notification_registrar_.Add(this, chrome::NOTIFICATION_HISTORY_LOADED, 725 content::Source<Profile>(profile_)); 726 } else { 727 OnHistoryAndCacheLoaded(); 728 } 729 } 730 731 void ResourcePrefetchPredictor::OnHistoryAndCacheLoaded() { 732 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 733 DCHECK_EQ(initialization_state_, INITIALIZING); 734 735 notification_registrar_.Add(this, 736 content::NOTIFICATION_LOAD_FROM_MEMORY_CACHE, 737 content::NotificationService::AllSources()); 738 notification_registrar_.Add(this, 739 chrome::NOTIFICATION_HISTORY_URLS_DELETED, 740 content::Source<Profile>(profile_)); 741 742 // Initialize the prefetch manager only if prefetching is enabled. 743 if (config_.IsPrefetchingEnabled()) { 744 prefetch_manager_ = new ResourcePrefetcherManager( 745 this, config_, profile_->GetRequestContext()); 746 } 747 748 initialization_state_ = INITIALIZED; 749 } 750 751 void ResourcePrefetchPredictor::CleanupAbandonedNavigations( 752 const NavigationID& navigation_id) { 753 static const base::TimeDelta max_navigation_age = 754 base::TimeDelta::FromSeconds(config_.max_navigation_lifetime_seconds); 755 756 base::TimeTicks time_now = base::TimeTicks::Now(); 757 for (NavigationMap::iterator it = inflight_navigations_.begin(); 758 it != inflight_navigations_.end();) { 759 if (it->first.IsSameRenderer(navigation_id) || 760 (time_now - it->first.creation_time > max_navigation_age)) { 761 inflight_navigations_.erase(it++); 762 RecordNavigationEvent(NAVIGATION_EVENT_REQUEST_EXPIRED); 763 } else { 764 ++it; 765 } 766 } 767 for (ResultsMap::iterator it = results_map_.begin(); 768 it != results_map_.end();) { 769 if (it->first.IsSameRenderer(navigation_id) || 770 (time_now - it->first.creation_time > max_navigation_age)) { 771 delete it->second; 772 results_map_.erase(it++); 773 } else { 774 ++it; 775 } 776 } 777 } 778 779 void ResourcePrefetchPredictor::DeleteAllUrls() { 780 inflight_navigations_.clear(); 781 url_table_cache_->clear(); 782 host_table_cache_->clear(); 783 784 BrowserThread::PostTask(BrowserThread::DB, FROM_HERE, 785 base::Bind(&ResourcePrefetchPredictorTables::DeleteAllData, tables_)); 786 } 787 788 void ResourcePrefetchPredictor::DeleteUrls(const history::URLRows& urls) { 789 // Check all the urls in the database and pick out the ones that are present 790 // in the cache. 791 std::vector<std::string> urls_to_delete, hosts_to_delete; 792 793 for (history::URLRows::const_iterator it = urls.begin(); it != urls.end(); 794 ++it) { 795 const std::string url_spec = it->url().spec(); 796 if (url_table_cache_->find(url_spec) != url_table_cache_->end()) { 797 urls_to_delete.push_back(url_spec); 798 url_table_cache_->erase(url_spec); 799 } 800 801 const std::string host = it->url().host(); 802 if (host_table_cache_->find(host) != host_table_cache_->end()) { 803 hosts_to_delete.push_back(host); 804 host_table_cache_->erase(host); 805 } 806 } 807 808 if (!urls_to_delete.empty() || !hosts_to_delete.empty()) { 809 BrowserThread::PostTask(BrowserThread::DB, FROM_HERE, 810 base::Bind(&ResourcePrefetchPredictorTables::DeleteData, 811 tables_, 812 urls_to_delete, 813 hosts_to_delete)); 814 } 815 } 816 817 void ResourcePrefetchPredictor::RemoveOldestEntryInPrefetchDataMap( 818 PrefetchKeyType key_type, 819 PrefetchDataMap* data_map) { 820 if (data_map->empty()) 821 return; 822 823 base::Time oldest_time; 824 std::string key_to_delete; 825 for (PrefetchDataMap::iterator it = data_map->begin(); 826 it != data_map->end(); ++it) { 827 if (key_to_delete.empty() || it->second.last_visit < oldest_time) { 828 key_to_delete = it->first; 829 oldest_time = it->second.last_visit; 830 } 831 } 832 833 data_map->erase(key_to_delete); 834 BrowserThread::PostTask(BrowserThread::DB, FROM_HERE, 835 base::Bind(&ResourcePrefetchPredictorTables::DeleteSingleDataPoint, 836 tables_, 837 key_to_delete, 838 key_type)); 839 } 840 841 void ResourcePrefetchPredictor::OnVisitCountLookup( 842 int visit_count, 843 const NavigationID& navigation_id, 844 const std::vector<URLRequestSummary>& requests) { 845 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 846 847 UMA_HISTOGRAM_COUNTS("ResourcePrefetchPredictor.HistoryVisitCountForUrl", 848 visit_count); 849 850 // URL level data - merge only if we are already saving the data, or we it 851 // meets the cutoff requirement. 852 const std::string url_spec = navigation_id.main_frame_url.spec(); 853 bool already_tracking = url_table_cache_->find(url_spec) != 854 url_table_cache_->end(); 855 bool should_track_url = already_tracking || 856 (visit_count >= config_.min_url_visit_count); 857 858 if (should_track_url) { 859 RecordNavigationEvent(NAVIGATION_EVENT_SHOULD_TRACK_URL); 860 861 if (config_.IsURLLearningEnabled()) { 862 LearnNavigation(url_spec, PREFETCH_KEY_TYPE_URL, requests, 863 config_.max_urls_to_track, url_table_cache_.get()); 864 } 865 } else { 866 RecordNavigationEvent(NAVIGATION_EVENT_SHOULD_NOT_TRACK_URL); 867 } 868 869 // Host level data - no cutoff, always learn the navigation if enabled. 870 if (config_.IsHostLearningEnabled()) { 871 LearnNavigation(navigation_id.main_frame_url.host(), 872 PREFETCH_KEY_TYPE_HOST, 873 requests, 874 config_.max_hosts_to_track, 875 host_table_cache_.get()); 876 } 877 878 // Remove the navigation from the results map. 879 delete results_map_[navigation_id]; 880 results_map_.erase(navigation_id); 881 } 882 883 void ResourcePrefetchPredictor::LearnNavigation( 884 const std::string& key, 885 PrefetchKeyType key_type, 886 const std::vector<URLRequestSummary>& new_resources, 887 int max_data_map_size, 888 PrefetchDataMap* data_map) { 889 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 890 891 // If the primary key is too long reject it. 892 if (key.length() > ResourcePrefetchPredictorTables::kMaxStringLength) { 893 if (key_type == PREFETCH_KEY_TYPE_HOST) 894 RecordNavigationEvent(NAVIGATION_EVENT_HOST_TOO_LONG); 895 else 896 RecordNavigationEvent(NAVIGATION_EVENT_MAIN_FRAME_URL_TOO_LONG); 897 return; 898 } 899 900 PrefetchDataMap::iterator cache_entry = data_map->find(key); 901 if (cache_entry == data_map->end()) { 902 if (static_cast<int>(data_map->size()) >= max_data_map_size) { 903 // The table is full, delete an entry. 904 RemoveOldestEntryInPrefetchDataMap(key_type, data_map); 905 } 906 907 cache_entry = data_map->insert(std::make_pair( 908 key, PrefetchData(key_type, key))).first; 909 cache_entry->second.last_visit = base::Time::Now(); 910 int new_resources_size = static_cast<int>(new_resources.size()); 911 std::set<GURL> resources_seen; 912 for (int i = 0; i < new_resources_size; ++i) { 913 if (resources_seen.find(new_resources[i].resource_url) != 914 resources_seen.end()) { 915 continue; 916 } 917 ResourceRow row_to_add; 918 row_to_add.resource_url = new_resources[i].resource_url; 919 row_to_add.resource_type = new_resources[i].resource_type; 920 row_to_add.number_of_hits = 1; 921 row_to_add.average_position = i + 1; 922 cache_entry->second.resources.push_back(row_to_add); 923 resources_seen.insert(new_resources[i].resource_url); 924 } 925 } else { 926 ResourceRows& old_resources = cache_entry->second.resources; 927 cache_entry->second.last_visit = base::Time::Now(); 928 929 // Build indices over the data. 930 std::map<GURL, int> new_index, old_index; 931 int new_resources_size = static_cast<int>(new_resources.size()); 932 for (int i = 0; i < new_resources_size; ++i) { 933 const URLRequestSummary& summary = new_resources[i]; 934 // Take the first occurence of every url. 935 if (new_index.find(summary.resource_url) == new_index.end()) 936 new_index[summary.resource_url] = i; 937 } 938 int old_resources_size = static_cast<int>(old_resources.size()); 939 for (int i = 0; i < old_resources_size; ++i) { 940 const ResourceRow& row = old_resources[i]; 941 DCHECK(old_index.find(row.resource_url) == old_index.end()); 942 old_index[row.resource_url] = i; 943 } 944 945 // Go through the old urls and update their hit/miss counts. 946 for (int i = 0; i < old_resources_size; ++i) { 947 ResourceRow& old_row = old_resources[i]; 948 if (new_index.find(old_row.resource_url) == new_index.end()) { 949 ++old_row.number_of_misses; 950 ++old_row.consecutive_misses; 951 } else { 952 const URLRequestSummary& new_row = 953 new_resources[new_index[old_row.resource_url]]; 954 955 // Update the resource type since it could have changed. 956 if (new_row.resource_type != ResourceType::LAST_TYPE) 957 old_row.resource_type = new_row.resource_type; 958 959 int position = new_index[old_row.resource_url] + 1; 960 int total = old_row.number_of_hits + old_row.number_of_misses; 961 old_row.average_position = 962 ((old_row.average_position * total) + position) / (total + 1); 963 ++old_row.number_of_hits; 964 old_row.consecutive_misses = 0; 965 } 966 } 967 968 // Add the new ones that we have not seen before. 969 for (int i = 0; i < new_resources_size; ++i) { 970 const URLRequestSummary& summary = new_resources[i]; 971 if (old_index.find(summary.resource_url) != old_index.end()) 972 continue; 973 974 // Only need to add new stuff. 975 ResourceRow row_to_add; 976 row_to_add.resource_url = summary.resource_url; 977 row_to_add.resource_type = summary.resource_type; 978 row_to_add.number_of_hits = 1; 979 row_to_add.average_position = i + 1; 980 old_resources.push_back(row_to_add); 981 982 // To ensure we dont add the same url twice. 983 old_index[summary.resource_url] = 0; 984 } 985 } 986 987 // Trim and sort the resources after the update. 988 ResourceRows& resources = cache_entry->second.resources; 989 for (ResourceRows::iterator it = resources.begin(); 990 it != resources.end();) { 991 it->UpdateScore(); 992 if (it->consecutive_misses >= config_.max_consecutive_misses) 993 it = resources.erase(it); 994 else 995 ++it; 996 } 997 std::sort(resources.begin(), resources.end(), 998 ResourcePrefetchPredictorTables::ResourceRowSorter()); 999 if (static_cast<int>(resources.size()) > config_.max_resources_per_entry) 1000 resources.resize(config_.max_resources_per_entry); 1001 1002 // If the row has no resources, remove it from the cache and delete the 1003 // entry in the database. Else update the database. 1004 if (resources.size() == 0) { 1005 data_map->erase(key); 1006 BrowserThread::PostTask( 1007 BrowserThread::DB, FROM_HERE, 1008 base::Bind(&ResourcePrefetchPredictorTables::DeleteSingleDataPoint, 1009 tables_, 1010 key, 1011 key_type)); 1012 } else { 1013 bool is_host = key_type == PREFETCH_KEY_TYPE_HOST; 1014 PrefetchData empty_data( 1015 !is_host ? PREFETCH_KEY_TYPE_HOST : PREFETCH_KEY_TYPE_URL, 1016 std::string()); 1017 const PrefetchData& host_data = is_host ? cache_entry->second : empty_data; 1018 const PrefetchData& url_data = is_host ? empty_data : cache_entry->second; 1019 BrowserThread::PostTask( 1020 BrowserThread::DB, FROM_HERE, 1021 base::Bind(&ResourcePrefetchPredictorTables::UpdateData, 1022 tables_, 1023 url_data, 1024 host_data)); 1025 } 1026 } 1027 1028 //////////////////////////////////////////////////////////////////////////////// 1029 // Accuracy measurement. 1030 1031 void ResourcePrefetchPredictor::ReportAccuracyStats( 1032 PrefetchKeyType key_type, 1033 const std::vector<URLRequestSummary>& actual, 1034 ResourcePrefetcher::RequestVector* prefetched) const { 1035 // Annotate the results. 1036 std::map<GURL, bool> actual_resources; 1037 for (std::vector<URLRequestSummary>::const_iterator it = actual.begin(); 1038 it != actual.end(); ++it) { 1039 actual_resources[it->resource_url] = it->was_cached; 1040 } 1041 1042 int prefetch_cancelled = 0, prefetch_failed = 0, prefetch_not_started = 0; 1043 // 'a_' -> actual, 'p_' -> predicted. 1044 int p_cache_a_cache = 0, p_cache_a_network = 0, p_cache_a_notused = 0, 1045 p_network_a_cache = 0, p_network_a_network = 0, p_network_a_notused = 0; 1046 1047 for (ResourcePrefetcher::RequestVector::iterator it = prefetched->begin(); 1048 it != prefetched->end(); ++it) { 1049 ResourcePrefetcher::Request* req = *it; 1050 1051 // Set the usage states if the resource was actually used. 1052 std::map<GURL, bool>::iterator actual_it = actual_resources.find( 1053 req->resource_url); 1054 if (actual_it != actual_resources.end()) { 1055 if (actual_it->second) { 1056 req->usage_status = 1057 ResourcePrefetcher::Request::USAGE_STATUS_FROM_CACHE; 1058 } else { 1059 req->usage_status = 1060 ResourcePrefetcher::Request::USAGE_STATUS_FROM_NETWORK; 1061 } 1062 } 1063 1064 switch (req->prefetch_status) { 1065 1066 // TODO(shishir): Add histogram for each cancellation reason. 1067 case ResourcePrefetcher::Request::PREFETCH_STATUS_REDIRECTED: 1068 case ResourcePrefetcher::Request::PREFETCH_STATUS_AUTH_REQUIRED: 1069 case ResourcePrefetcher::Request::PREFETCH_STATUS_CERT_REQUIRED: 1070 case ResourcePrefetcher::Request::PREFETCH_STATUS_CERT_ERROR: 1071 case ResourcePrefetcher::Request::PREFETCH_STATUS_CANCELLED: 1072 ++prefetch_cancelled; 1073 break; 1074 1075 case ResourcePrefetcher::Request::PREFETCH_STATUS_FAILED: 1076 ++prefetch_failed; 1077 break; 1078 1079 case ResourcePrefetcher::Request::PREFETCH_STATUS_FROM_CACHE: 1080 if (req->usage_status == 1081 ResourcePrefetcher::Request::USAGE_STATUS_FROM_CACHE) 1082 ++p_cache_a_cache; 1083 else if (req->usage_status == 1084 ResourcePrefetcher::Request::USAGE_STATUS_FROM_NETWORK) 1085 ++p_cache_a_network; 1086 else 1087 ++p_cache_a_notused; 1088 break; 1089 1090 case ResourcePrefetcher::Request::PREFETCH_STATUS_FROM_NETWORK: 1091 if (req->usage_status == 1092 ResourcePrefetcher::Request::USAGE_STATUS_FROM_CACHE) 1093 ++p_network_a_cache; 1094 else if (req->usage_status == 1095 ResourcePrefetcher::Request::USAGE_STATUS_FROM_NETWORK) 1096 ++p_network_a_network; 1097 else 1098 ++p_network_a_notused; 1099 break; 1100 1101 case ResourcePrefetcher::Request::PREFETCH_STATUS_NOT_STARTED: 1102 ++prefetch_not_started; 1103 break; 1104 1105 case ResourcePrefetcher::Request::PREFETCH_STATUS_STARTED: 1106 DLOG(FATAL) << "Invalid prefetch status"; 1107 break; 1108 } 1109 } 1110 1111 int total_prefetched = p_cache_a_cache + p_cache_a_network + p_cache_a_notused 1112 + p_network_a_cache + p_network_a_network + p_network_a_notused; 1113 1114 std::string histogram_type = key_type == PREFETCH_KEY_TYPE_HOST ? "Host." : 1115 "Url."; 1116 1117 // Macros to avoid using the STATIC_HISTOGRAM_POINTER_BLOCK in UMA_HISTOGRAM 1118 // definitions. 1119 #define RPP_HISTOGRAM_PERCENTAGE(suffix, value) \ 1120 { \ 1121 std::string name = "ResourcePrefetchPredictor." + histogram_type + suffix; \ 1122 std::string g_name = "ResourcePrefetchPredictor." + std::string(suffix); \ 1123 base::HistogramBase* histogram = base::LinearHistogram::FactoryGet( \ 1124 name, 1, 101, 102, base::Histogram::kUmaTargetedHistogramFlag); \ 1125 histogram->Add(value); \ 1126 UMA_HISTOGRAM_PERCENTAGE(g_name, value); \ 1127 } 1128 1129 RPP_HISTOGRAM_PERCENTAGE("PrefetchCancelled", 1130 prefetch_cancelled * 100.0 / total_prefetched); 1131 RPP_HISTOGRAM_PERCENTAGE("PrefetchFailed", 1132 prefetch_failed * 100.0 / total_prefetched); 1133 RPP_HISTOGRAM_PERCENTAGE("PrefetchFromCacheUsedFromCache", 1134 p_cache_a_cache * 100.0 / total_prefetched); 1135 RPP_HISTOGRAM_PERCENTAGE("PrefetchFromCacheUsedFromNetwork", 1136 p_cache_a_network * 100.0 / total_prefetched); 1137 RPP_HISTOGRAM_PERCENTAGE("PrefetchFromCacheNotUsed", 1138 p_cache_a_notused * 100.0 / total_prefetched); 1139 RPP_HISTOGRAM_PERCENTAGE("PrefetchFromNetworkUsedFromCache", 1140 p_network_a_cache * 100.0 / total_prefetched); 1141 RPP_HISTOGRAM_PERCENTAGE("PrefetchFromNetworkUsedFromNetwork", 1142 p_network_a_network * 100.0 / total_prefetched); 1143 RPP_HISTOGRAM_PERCENTAGE("PrefetchFromNetworkNotUsed", 1144 p_network_a_notused * 100.0 / total_prefetched); 1145 1146 RPP_HISTOGRAM_PERCENTAGE( 1147 "PrefetchNotStarted", 1148 prefetch_not_started * 100.0 / (prefetch_not_started + total_prefetched)); 1149 1150 #undef RPP_HISTOGRAM_PERCENTAGE 1151 } 1152 1153 void ResourcePrefetchPredictor::ReportPredictedAccuracyStats( 1154 PrefetchKeyType key_type, 1155 const std::vector<URLRequestSummary>& actual, 1156 const ResourcePrefetcher::RequestVector& predicted) const { 1157 std::map<GURL, bool> actual_resources; 1158 int from_network = 0; 1159 for (std::vector<URLRequestSummary>::const_iterator it = actual.begin(); 1160 it != actual.end(); ++it) { 1161 actual_resources[it->resource_url] = it->was_cached; 1162 if (!it->was_cached) 1163 ++from_network; 1164 } 1165 1166 // Measure the accuracy at 25, 50 predicted resources. 1167 ReportPredictedAccuracyStatsHelper(key_type, predicted, actual_resources, 1168 from_network, 25); 1169 ReportPredictedAccuracyStatsHelper(key_type, predicted, actual_resources, 1170 from_network, 50); 1171 } 1172 1173 void ResourcePrefetchPredictor::ReportPredictedAccuracyStatsHelper( 1174 PrefetchKeyType key_type, 1175 const ResourcePrefetcher::RequestVector& predicted, 1176 const std::map<GURL, bool>& actual, 1177 int total_resources_fetched_from_network, 1178 int max_assumed_prefetched) const { 1179 int prefetch_cached = 0, prefetch_network = 0, prefetch_missed = 0; 1180 int num_assumed_prefetched = std::min(static_cast<int>(predicted.size()), 1181 max_assumed_prefetched); 1182 if (num_assumed_prefetched == 0) 1183 return; 1184 1185 for (int i = 0; i < num_assumed_prefetched; ++i) { 1186 const ResourcePrefetcher::Request& row = *(predicted[i]); 1187 std::map<GURL, bool>::const_iterator it = actual.find(row.resource_url); 1188 if (it == actual.end()) { 1189 ++prefetch_missed; 1190 } else if (it->second) { 1191 ++prefetch_cached; 1192 } else { 1193 ++prefetch_network; 1194 } 1195 } 1196 1197 std::string prefix = key_type == PREFETCH_KEY_TYPE_HOST ? 1198 "ResourcePrefetchPredictor.Host.Predicted" : 1199 "ResourcePrefetchPredictor.Url.Predicted"; 1200 std::string suffix = "_" + base::IntToString(max_assumed_prefetched); 1201 1202 // Macros to avoid using the STATIC_HISTOGRAM_POINTER_BLOCK in UMA_HISTOGRAM 1203 // definitions. 1204 #define RPP_PREDICTED_HISTOGRAM_COUNTS(name, value) \ 1205 { \ 1206 std::string full_name = prefix + name + suffix; \ 1207 base::HistogramBase* histogram = base::Histogram::FactoryGet( \ 1208 full_name, 1, 1000000, 50, \ 1209 base::Histogram::kUmaTargetedHistogramFlag); \ 1210 histogram->Add(value); \ 1211 } 1212 1213 #define RPP_PREDICTED_HISTOGRAM_PERCENTAGE(name, value) \ 1214 { \ 1215 std::string full_name = prefix + name + suffix; \ 1216 base::HistogramBase* histogram = base::LinearHistogram::FactoryGet( \ 1217 full_name, 1, 101, 102, base::Histogram::kUmaTargetedHistogramFlag); \ 1218 histogram->Add(value); \ 1219 } 1220 1221 RPP_PREDICTED_HISTOGRAM_COUNTS("PrefetchCount", num_assumed_prefetched); 1222 RPP_PREDICTED_HISTOGRAM_COUNTS("PrefetchMisses_Count", prefetch_missed); 1223 RPP_PREDICTED_HISTOGRAM_COUNTS("PrefetchFromCache_Count", prefetch_cached); 1224 RPP_PREDICTED_HISTOGRAM_COUNTS("PrefetchFromNetwork_Count", prefetch_network); 1225 1226 RPP_PREDICTED_HISTOGRAM_PERCENTAGE( 1227 "PrefetchMisses_PercentOfTotalPrefetched", 1228 prefetch_missed * 100.0 / num_assumed_prefetched); 1229 RPP_PREDICTED_HISTOGRAM_PERCENTAGE( 1230 "PrefetchFromCache_PercentOfTotalPrefetched", 1231 prefetch_cached * 100.0 / num_assumed_prefetched); 1232 RPP_PREDICTED_HISTOGRAM_PERCENTAGE( 1233 "PrefetchFromNetwork_PercentOfTotalPrefetched", 1234 prefetch_network * 100.0 / num_assumed_prefetched); 1235 1236 // Measure the ratio of total number of resources prefetched from network vs 1237 // the total number of resources fetched by the page from the network. 1238 if (total_resources_fetched_from_network > 0) { 1239 RPP_PREDICTED_HISTOGRAM_PERCENTAGE( 1240 "PrefetchFromNetworkPercentOfTotalFromNetwork", 1241 prefetch_network * 100.0 / total_resources_fetched_from_network); 1242 } 1243 1244 #undef RPP_PREDICTED_HISTOGRAM_PERCENTAGE 1245 #undef RPP_PREDICTED_HISTOGRAM_COUNTS 1246 } 1247 1248 } // namespace predictors 1249