diff options
author | jar@chromium.org <jar@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-05-18 00:39:18 +0000 |
---|---|---|
committer | jar@chromium.org <jar@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-05-18 00:39:18 +0000 |
commit | 760d970aa408a7ea9a00e4e2ab792ef05f9355e5 (patch) | |
tree | e6699d0e0b2fb930685ce1e346ca41db566c8c21 /chrome | |
parent | d71cc6cc3834d8824c063b9fccf75b3559f545f3 (diff) | |
download | chromium_src-760d970aa408a7ea9a00e4e2ab792ef05f9355e5.zip chromium_src-760d970aa408a7ea9a00e4e2ab792ef05f9355e5.tar.gz chromium_src-760d970aa408a7ea9a00e4e2ab792ef05f9355e5.tar.bz2 |
Support speculative pre-connection to search URLs
Implement several flavors of TCP/IP speculative preconnection
under a command line flag (not yet on by default).
The first area of preconnection takes place when a user types
a query into the omnibox, as we preconnect to the search service
when the omnibox suggests it is going to do a search.
The second area involves subresources, such as images.
When a navigation takes place, and we've seen navigations
to that domain/port before, and the history-based
probabability that we'll need to make a connection to
a second site (host/port) is sufficiently large, then we
preconnect to that second site while we are still connecting
to the primary site (and before we've gotten content from
the primary site.
We also fall-back to mere DNS pre-resolution of subresource
hostnames when the probability of a connection to the
subresource is not high enough.
BUG=42694
r=pkasting,willchan,mbelshe
Review URL: http://codereview.chromium.org/1585029
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@47479 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
-rw-r--r-- | chrome/browser/autocomplete/autocomplete_edit.cc | 22 | ||||
-rw-r--r-- | chrome/browser/browser_main.cc | 5 | ||||
-rw-r--r-- | chrome/browser/browser_process_impl.cc | 4 | ||||
-rw-r--r-- | chrome/browser/io_thread.cc | 13 | ||||
-rw-r--r-- | chrome/browser/io_thread.h | 6 | ||||
-rw-r--r-- | chrome/browser/net/dns_global.cc | 137 | ||||
-rw-r--r-- | chrome/browser/net/dns_global.h | 12 | ||||
-rw-r--r-- | chrome/browser/net/dns_host_info.cc | 29 | ||||
-rw-r--r-- | chrome/browser/net/dns_host_info.h | 23 | ||||
-rw-r--r-- | chrome/browser/net/dns_host_info_unittest.cc | 26 | ||||
-rw-r--r-- | chrome/browser/net/dns_master.cc | 287 | ||||
-rw-r--r-- | chrome/browser/net/dns_master.h | 70 | ||||
-rw-r--r-- | chrome/browser/net/dns_master_unittest.cc | 416 | ||||
-rw-r--r-- | chrome/browser/net/preconnect.cc | 67 | ||||
-rw-r--r-- | chrome/browser/net/preconnect.h | 40 | ||||
-rw-r--r-- | chrome/browser/net/referrer.cc | 156 | ||||
-rw-r--r-- | chrome/browser/net/referrer.h | 69 | ||||
-rw-r--r-- | chrome/chrome_browser.gypi | 2 | ||||
-rw-r--r-- | chrome/common/chrome_switches.cc | 3 | ||||
-rw-r--r-- | chrome/common/chrome_switches.h | 1 | ||||
-rw-r--r-- | chrome/common/pref_names.cc | 4 |
21 files changed, 948 insertions, 444 deletions
diff --git a/chrome/browser/autocomplete/autocomplete_edit.cc b/chrome/browser/autocomplete/autocomplete_edit.cc index 9060153..a650e1d 100644 --- a/chrome/browser/autocomplete/autocomplete_edit.cc +++ b/chrome/browser/autocomplete/autocomplete_edit.cc @@ -569,6 +569,25 @@ bool AutocompleteEditModel::OnAfterPossibleChange(const std::wstring& new_text, return true; } +// Return true if the suggestion type warrants a TCP/IP preconnection. +// i.e., it is now highly likely that the user will select the related domain. +static bool IsPreconnectable(AutocompleteMatch::Type type) { + UMA_HISTOGRAM_ENUMERATION("Autocomplete.MatchType", type, + AutocompleteMatch::NUM_TYPES); + switch (type) { + // Matches using the user's default search engine. + case AutocompleteMatch::SEARCH_WHAT_YOU_TYPED: + case AutocompleteMatch::SEARCH_HISTORY: + case AutocompleteMatch::SEARCH_SUGGEST: + // A match that uses a non-default search engine (e.g. for tab-to-search). + case AutocompleteMatch::SEARCH_OTHER_ENGINE: + return true; + + default: + return false; + } +} + void AutocompleteEditModel::Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details) { @@ -588,7 +607,8 @@ void AutocompleteEditModel::Observe(NotificationType type, match->fill_into_edit.substr(match->inline_autocomplete_offset); } // Warm up DNS Prefetch Cache. - chrome_browser_net::DnsPrefetchUrl(match->destination_url); + chrome_browser_net::DnsPrefetchUrl(match->destination_url, + IsPreconnectable(match->type)); // We could prefetch the alternate nav URL, if any, but because there // can be many of these as a user types an initial series of characters, // the OS DNS cache could suffer eviction problems for minimal gain. diff --git a/chrome/browser/browser_main.cc b/chrome/browser/browser_main.cc index 5d05685..75d74c4 100644 --- a/chrome/browser/browser_main.cc +++ b/chrome/browser/browser_main.cc @@ -1077,7 +1077,10 @@ int BrowserMain(const MainFunctionParams& parameters) { // Initialize and maintain DNS prefetcher module. Also registers an observer // to clear the host cache when closing incognito mode. - chrome_browser_net::DnsGlobalInit dns_prefetch(user_prefs, local_state); + chrome_browser_net::DnsGlobalInit dns_prefetch( + user_prefs, + local_state, + parsed_command_line.HasSwitch(switches::kEnablePreconnect)); #if defined(OS_WIN) win_util::ScopedCOMInitializer com_initializer; diff --git a/chrome/browser/browser_process_impl.cc b/chrome/browser/browser_process_impl.cc index 75a47c2..9c118bb 100644 --- a/chrome/browser/browser_process_impl.cc +++ b/chrome/browser/browser_process_impl.cc @@ -211,7 +211,7 @@ unsigned int BrowserProcessImpl::AddRefModule() { unsigned int BrowserProcessImpl::ReleaseModule() { DCHECK(CalledOnValidThread()); - DCHECK(0 != module_ref_count_); + DCHECK_NE(0u, module_ref_count_); module_ref_count_--; if (0 == module_ref_count_) { MessageLoop::current()->PostTask( @@ -459,7 +459,7 @@ void BrowserProcessImpl::CheckForInspectorFiles() { #if (defined(OS_WIN) || defined(OS_LINUX)) && !defined(OS_CHROMEOS) void BrowserProcessImpl::StartAutoupdateTimer() { autoupdate_timer_.Start( - TimeDelta::FromHours(kUpdateCheckIntervalHours), + base::TimeDelta::FromHours(kUpdateCheckIntervalHours), this, &BrowserProcessImpl::OnAutoupdateTimer); } diff --git a/chrome/browser/io_thread.cc b/chrome/browser/io_thread.cc index 7436750..ca5151b 100644 --- a/chrome/browser/io_thread.cc +++ b/chrome/browser/io_thread.cc @@ -110,7 +110,8 @@ void IOThread::InitDnsMaster( base::TimeDelta max_queue_delay, size_t max_concurrent, const chrome_common_net::NameList& hostnames_to_prefetch, - ListValue* referral_list) { + ListValue* referral_list, + bool preconnect_enabled) { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); message_loop()->PostTask( FROM_HERE, @@ -118,7 +119,7 @@ void IOThread::InitDnsMaster( this, &IOThread::InitDnsMasterOnIOThread, prefetching_enabled, max_queue_delay, max_concurrent, - hostnames_to_prefetch, referral_list)); + hostnames_to_prefetch, referral_list, preconnect_enabled)); } void IOThread::ChangedToOnTheRecord() { @@ -250,14 +251,18 @@ void IOThread::InitDnsMasterOnIOThread( base::TimeDelta max_queue_delay, size_t max_concurrent, chrome_common_net::NameList hostnames_to_prefetch, - ListValue* referral_list) { + ListValue* referral_list, + bool preconnect_enabled) { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); CHECK(!dns_master_); chrome_browser_net::EnableDnsPrefetch(prefetching_enabled); dns_master_ = new chrome_browser_net::DnsMaster( - globals_->host_resolver, max_queue_delay, max_concurrent); + globals_->host_resolver, + max_queue_delay, + max_concurrent, + preconnect_enabled); dns_master_->AddRef(); DCHECK(!prefetch_observer_); diff --git a/chrome/browser/io_thread.h b/chrome/browser/io_thread.h index a2860ac..c624a99 100644 --- a/chrome/browser/io_thread.h +++ b/chrome/browser/io_thread.h @@ -53,7 +53,8 @@ class IOThread : public BrowserProcessSubThread { base::TimeDelta max_queue_delay, size_t max_concurrent, const chrome_common_net::NameList& hostnames_to_prefetch, - ListValue* referral_list); + ListValue* referral_list, + bool preconnect_enabled); // Handles changing to On The Record mode. Posts a task for this onto the // IOThread's message loop. @@ -72,7 +73,8 @@ class IOThread : public BrowserProcessSubThread { base::TimeDelta max_queue_delay, size_t max_concurrent, chrome_common_net::NameList hostnames_to_prefetch, - ListValue* referral_list); + ListValue* referral_list, + bool preconnect_enabled); void ChangedToOnTheRecordOnIOThread(); diff --git a/chrome/browser/net/dns_global.cc b/chrome/browser/net/dns_global.cc index 88da39e..f57e029 100644 --- a/chrome/browser/net/dns_global.cc +++ b/chrome/browser/net/dns_global.cc @@ -9,6 +9,7 @@ #include "base/singleton.h" #include "base/stats_counters.h" +#include "base/stl_util-inl.h" #include "base/string_util.h" #include "base/thread.h" #include "base/waitable_event.h" @@ -18,6 +19,7 @@ #include "chrome/browser/chrome_thread.h" #include "chrome/browser/io_thread.h" #include "chrome/browser/net/dns_host_info.h" +#include "chrome/browser/net/preconnect.h" #include "chrome/browser/net/referrer.h" #include "chrome/browser/pref_service.h" #include "chrome/browser/profile.h" @@ -33,10 +35,10 @@ using base::TimeDelta; namespace chrome_browser_net { -static void DnsMotivatedPrefetch(const std::string& hostname, - DnsHostInfo::ResolutionMotivation motivation); -static void DnsPrefetchMotivatedList( - const NameList& hostnames, +static void DnsMotivatedPrefetch(const net::HostPortPair& hostport, + DnsHostInfo::ResolutionMotivation motivation); + +static void DnsPrefetchMotivatedList(const NameList& hostnames, DnsHostInfo::ResolutionMotivation motivation); static NameList GetDnsPrefetchHostNamesAtStartup( @@ -48,6 +50,10 @@ const size_t DnsGlobalInit::kMaxPrefetchConcurrentLookups = 8; // static const int DnsGlobalInit::kMaxPrefetchQueueingDelayMs = 500; +// A version number for prefs that are saved. This should be incremented when +// we change the format so that we discard old data. +static const int kDnsStartupFormatVersion = 0; + //------------------------------------------------------------------------------ // This section contains all the globally accessable API entry points for the // DNS Prefetching feature. @@ -117,25 +123,62 @@ static void DnsPrefetchMotivatedList( } // This API is used by the autocomplete popup box (where URLs are typed). -void DnsPrefetchUrl(const GURL& url) { +void DnsPrefetchUrl(const GURL& url, bool preconnectable) { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); if (!dns_prefetch_enabled || NULL == dns_master) return; - if (url.is_valid()) - DnsMotivatedPrefetch(url.host(), DnsHostInfo::OMNIBOX_MOTIVATED); + if (!url.is_valid()) + return; + + static std::string last_host; + std::string host = url.HostNoBrackets(); + bool is_new_host_request = (host != last_host); + last_host = host; + + // Omnibox tends to call in pairs (just a few milliseconds apart), and we + // really don't need to keep resolving a name that often. + // TODO(jar): A/B tests could check for perf impact of the early returns. + static base::TimeTicks last_prefetch_for_host; + base::TimeTicks now = base::TimeTicks::Now(); + if (!is_new_host_request) { + const int kMinPreresolveSeconds(10); + if (kMinPreresolveSeconds > (now - last_prefetch_for_host).InSeconds()) + return; + } + last_prefetch_for_host = now; + + net::HostPortPair hostport(url.HostNoBrackets(), url.EffectiveIntPort()); + + if (dns_master->preconnect_enabled() && preconnectable) { + static base::TimeTicks last_keepalive; + // TODO(jar): The wild guess of 30 seconds could be tuned/tested, but it + // currently is just a guess that most sockets will remain open for at least + // 30 seconds. + const int kMaxSearchKeepaliveSeconds(30); + if ((now - last_keepalive).InSeconds() < kMaxSearchKeepaliveSeconds) + return; + last_keepalive = now; + + if (Preconnect::PreconnectOnUIThread(hostport)) + return; // Skip pre-resolution, since we'll open a connection. + } + + // Perform at least DNS pre-resolution. + // TODO(jar): We could propogate a hostport here instead of a host. + DnsMotivatedPrefetch(hostport, DnsHostInfo::OMNIBOX_MOTIVATED); } -static void DnsMotivatedPrefetch(const std::string& hostname, +static void DnsMotivatedPrefetch(const net::HostPortPair& hostport, DnsHostInfo::ResolutionMotivation motivation) { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); - if (!dns_prefetch_enabled || NULL == dns_master || !hostname.size()) + if (!dns_prefetch_enabled || NULL == dns_master || hostport.host.empty()) return; ChromeThread::PostTask( ChromeThread::IO, FROM_HERE, NewRunnableMethod(dns_master, - &DnsMaster::Resolve, hostname, motivation)); + &DnsMaster::Resolve, hostport, motivation)); } //------------------------------------------------------------------------------ @@ -152,16 +195,18 @@ static bool AccruePrefetchBenefits(const GURL& referrer, DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); if (!dns_prefetch_enabled || NULL == dns_master) return false; - return dns_master->AccruePrefetchBenefits(referrer, navigation_info); + return dns_master->AccruePrefetchBenefits( + net::HostPortPair(referrer.host(), referrer.EffectiveIntPort()), + navigation_info); } // When we navigate, we may know in advance some other domains that will need to // be resolved. This function initiates those side effects. -static void NavigatingTo(const std::string& host_name) { +static void NavigatingTo(const net::HostPortPair& hostport) { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); if (!dns_prefetch_enabled || NULL == dns_master) return; - dns_master->NavigatingTo(host_name); + dns_master->NavigatingTo(hostport); } // The observer class needs to connect starts and finishes of HTTP network @@ -173,6 +218,8 @@ typedef std::map<int, DnsHostInfo> ObservedResolutionMap; // resolutions made by the network stack. class PrefetchObserver : public net::HostResolver::Observer { public: + typedef std::map<net::HostPortPair, DnsHostInfo> FirstResolutionMap; + // net::HostResolver::Observer implementation: virtual void OnStartResolution( int request_id, @@ -194,7 +241,7 @@ class PrefetchObserver : public net::HostResolver::Observer { // Map of pending resolutions seen by observer. ObservedResolutionMap resolutions_; // List of the first N hostname resolutions observed in this run. - Results first_resolutions_; + FirstResolutionMap first_resolutions_; // The number of hostnames we'll save for prefetching at next startup. static const size_t kStartupResolutionCount = 10; }; @@ -212,8 +259,10 @@ void PrefetchObserver::OnStartResolution( if (request_info.is_speculative()) return; // One of our own requests. DCHECK_NE(0U, request_info.hostname().length()); + DnsHostInfo navigation_info; - navigation_info.SetHostname(request_info.hostname()); + navigation_info.SetHostname(net::HostPortPair(request_info.hostname(), + request_info.port())); navigation_info.SetStartedState(); // This entry will be deleted either by OnFinishResolutionWithStatus(), or @@ -233,7 +282,7 @@ void PrefetchObserver::OnFinishResolutionWithStatus( { ObservedResolutionMap::iterator it = resolutions_.find(request_id); if (resolutions_.end() == it) { - DCHECK(false); + NOTREACHED(); return; } navigation_info = it->second; @@ -247,7 +296,7 @@ void PrefetchObserver::OnFinishResolutionWithStatus( // Handle sub-resource resolutions now that the critical navigational // resolution has completed. This prevents us from in any way delaying that // navigational resolution. - NavigatingTo(request_info.hostname()); + NavigatingTo(net::HostPortPair(request_info.hostname(), request_info.port())); if (kStartupResolutionCount <= startup_count || !was_resolved) return; @@ -267,7 +316,7 @@ void PrefetchObserver::OnCancelResolution( // Remove the entry from |resolutions| that was added by OnStartResolution(). ObservedResolutionMap::iterator it = resolutions_.find(request_id); if (resolutions_.end() == it) { - DCHECK(false); + NOTREACHED(); return; } resolutions_.erase(it); @@ -280,10 +329,9 @@ void PrefetchObserver::StartupListAppend(const DnsHostInfo& navigation_info) { return; if (kStartupResolutionCount <= first_resolutions_.size()) return; // Someone just added the last item. - std::string host_name = navigation_info.hostname(); - if (first_resolutions_.find(host_name) != first_resolutions_.end()) + if (ContainsKey(first_resolutions_, navigation_info.hostport())) return; // We already have this hostname listed. - first_resolutions_[host_name] = navigation_info; + first_resolutions_[navigation_info.hostport()] = navigation_info; } void PrefetchObserver::GetInitialDnsResolutionList(ListValue* startup_list) { @@ -291,11 +339,12 @@ void PrefetchObserver::GetInitialDnsResolutionList(ListValue* startup_list) { DCHECK(startup_list); startup_list->Clear(); DCHECK_EQ(0u, startup_list->GetSize()); - for (Results::iterator it = first_resolutions_.begin(); + startup_list->Append(new FundamentalValue(kDnsStartupFormatVersion)); + for (FirstResolutionMap::iterator it = first_resolutions_.begin(); it != first_resolutions_.end(); - it++) { - const std::string hostname = it->first; - startup_list->Append(Value::CreateStringValue(hostname)); + ++it) { + startup_list->Append(new StringValue(it->first.host)); + startup_list->Append(new FundamentalValue(it->first.port)); } } @@ -304,7 +353,7 @@ void PrefetchObserver::DnsGetFirstResolutionsHtml(std::string* output) { DnsHostInfo::DnsInfoTable resolution_list; { - for (Results::iterator it(first_resolutions_.begin()); + for (FirstResolutionMap::iterator it(first_resolutions_.begin()); it != first_resolutions_.end(); it++) { resolution_list.push_back(it->second); @@ -398,7 +447,8 @@ void DnsPrefetchGetHtmlInfo(std::string* output) { //------------------------------------------------------------------------------ static void InitDnsPrefetch(TimeDelta max_queue_delay, size_t max_concurrent, - PrefService* user_prefs, PrefService* local_state) { + PrefService* user_prefs, PrefService* local_state, + bool preconnect_enabled) { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); bool prefetching_enabled = @@ -414,7 +464,7 @@ static void InitDnsPrefetch(TimeDelta max_queue_delay, size_t max_concurrent, g_browser_process->io_thread()->InitDnsMaster( prefetching_enabled, max_queue_delay, max_concurrent, hostnames, - referral_list); + referral_list, preconnect_enabled); } void FinalizeDnsPrefetchInitialization( @@ -505,12 +555,23 @@ static NameList GetDnsPrefetchHostNamesAtStartup(PrefService* user_prefs, ListValue* startup_list = local_state->GetMutableList(prefs::kDnsStartupPrefetchList); if (startup_list) { - for (ListValue::iterator it = startup_list->begin(); - it != startup_list->end(); - it++) { - std::string hostname; - (*it)->GetAsString(&hostname); - hostnames.push_back(hostname); + ListValue::iterator it = startup_list->begin(); + int format_version = -1; + if (it != startup_list->end() && + (*it)->GetAsInteger(&format_version) && + format_version == kDnsStartupFormatVersion) { + ++it; + for (; it != startup_list->end(); ++it) { + std::string hostname; + if (!(*it)->GetAsString(&hostname)) + break; // Format incompatibility. + int port; + if (!(*++it)->GetAsInteger(&port)) + break; // Format incompatibility. + + // TODO(jar): We sohould accept hostport pairs. + hostnames.push_back(hostname); + } } } @@ -522,7 +583,7 @@ static NameList GetDnsPrefetchHostNamesAtStartup(PrefService* user_prefs, for (size_t i = 0; i < tab_start_pref.urls.size(); i++) { GURL gurl = tab_start_pref.urls[i]; if (gurl.is_valid() && !gurl.host().empty()) - hostnames.push_back(gurl.host()); + hostnames.push_back(gurl.HostNoBrackets()); } } @@ -537,7 +598,8 @@ static NameList GetDnsPrefetchHostNamesAtStartup(PrefService* user_prefs, // DNS prefetch system. DnsGlobalInit::DnsGlobalInit(PrefService* user_prefs, - PrefService* local_state) { + PrefService* local_state, + bool preconnect_enabled) { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); // Set up a field trial to see what disabling DNS pre-resolution does to // latency of page loads. @@ -616,8 +678,9 @@ DnsGlobalInit::DnsGlobalInit(PrefService* user_prefs, DCHECK(!dns_master); InitDnsPrefetch(max_queueing_delay, max_concurrent, user_prefs, - local_state); + local_state, preconnect_enabled); } } + } // namespace chrome_browser_net diff --git a/chrome/browser/net/dns_global.h b/chrome/browser/net/dns_global.h index bc44b79..639f614 100644 --- a/chrome/browser/net/dns_global.h +++ b/chrome/browser/net/dns_global.h @@ -16,6 +16,7 @@ #include "base/field_trial.h" #include "base/scoped_ptr.h" +#include "chrome/browser/autocomplete/autocomplete.h" #include "chrome/browser/net/dns_master.h" #include "net/base/host_resolver.h" @@ -42,10 +43,16 @@ net::HostResolver::Observer* CreatePrefetchObserver(); void EnableDnsPrefetch(bool enable); void RegisterPrefs(PrefService* local_state); void RegisterUserPrefs(PrefService* user_prefs); + // Renderer bundles up list and sends to this browser API via IPC. void DnsPrefetchList(const NameList& hostnames); + // This API is used by the autocomplete popup box (as user types). -void DnsPrefetchUrl(const GURL& url); +// This will either preresolve the domain name, or possibly preconnect creating +// an open TCP/IP connection to the host. +void DnsPrefetchUrl(const GURL& url, bool preconnectable); + +// When displaying info in about:dns, the following API is called. void DnsPrefetchGetHtmlInfo(std::string* output); //------------------------------------------------------------------------------ @@ -63,7 +70,8 @@ class DnsGlobalInit { // of that state. The following is the suggested default time limit. static const int kMaxPrefetchQueueingDelayMs; - DnsGlobalInit(PrefService* user_prefs, PrefService* local_state); + DnsGlobalInit(PrefService* user_prefs, PrefService* local_state, + bool preconnect_enabled); private: // Maintain a field trial instance when we do A/B testing. diff --git a/chrome/browser/net/dns_host_info.cc b/chrome/browser/net/dns_host_info.cc index 45e088b..2fb626d 100644 --- a/chrome/browser/net/dns_host_info.cc +++ b/chrome/browser/net/dns_host_info.cc @@ -32,8 +32,7 @@ void EnableDnsDetailedLog(bool enable) { int DnsHostInfo::sequence_counter = 1; -bool DnsHostInfo::NeedsDnsUpdate(const std::string& hostname) { - DCHECK(hostname == hostname_); +bool DnsHostInfo::NeedsDnsUpdate() { switch (state_) { case PENDING: // Just now created info. return true; @@ -48,7 +47,7 @@ bool DnsHostInfo::NeedsDnsUpdate(const std::string& hostname) { return !IsStillCached(); // See if DNS cache expired. default: - DCHECK(false); + NOTREACHED(); return false; } } @@ -171,11 +170,11 @@ void DnsHostInfo::SetFinishedState(bool was_resolved) { DLogResultsStats("DNS HTTP Finished"); } -void DnsHostInfo::SetHostname(const std::string& hostname) { - if (hostname != hostname_) { - DCHECK_EQ(hostname_.size(), 0u); // Not yet initialized. - hostname_ = hostname; - } +void DnsHostInfo::SetHostname(const net::HostPortPair& hostport) { + if (hostport_.host.empty()) // Not yet initialized. + hostport_ = hostport; + else + DCHECK(hostport_.Equals(hostport)); } // IsStillCached() guesses if the DNS cache still has IP data, @@ -201,7 +200,7 @@ bool DnsHostInfo::IsStillCached() const { DnsBenefit DnsHostInfo::AccruePrefetchBenefits(DnsHostInfo* navigation_info) { DCHECK(FINISHED == navigation_info->state_ || FINISHED_UNRESOLVED == navigation_info->state_); - DCHECK_EQ(navigation_info->hostname_, hostname_.data()); + DCHECK(navigation_info->hostport().Equals(hostport_)); if ((0 == benefits_remaining_.InMilliseconds()) || (FOUND != state_ && NO_SUCH_NAME != state_)) { @@ -221,7 +220,7 @@ DnsBenefit DnsHostInfo::AccruePrefetchBenefits(DnsHostInfo* navigation_info) { navigation_info->motivation_ = motivation_; if (LEARNED_REFERAL_MOTIVATED == motivation_ || STATIC_REFERAL_MOTIVATED == motivation_) - navigation_info->referring_hostname_ = referring_hostname_; + navigation_info->referring_hostport_ = referring_hostport_; if (navigation_info->resolve_duration_ > kMaxNonNetworkDnsLookupDuration) { // Our precache effort didn't help since HTTP stack hit the network. @@ -256,7 +255,7 @@ void DnsHostInfo::DLogResultsStats(const char* message) const { << resolve_duration().InMilliseconds() << "ms\tp=" << benefits_remaining_.InMilliseconds() << "ms\tseq=" << sequence_number_ - << "\t" << hostname_; + << "\t" << hostport_.ToString(); } //------------------------------------------------------------------------------ @@ -270,7 +269,7 @@ static std::string RemoveJs(const std::string& text) { size_t length = output.length(); for (size_t i = 0; i < length; i++) { char next = output[i]; - if (isalnum(next) || isspace(next) || strchr(".-:", next) != NULL) + if (isalnum(next) || isspace(next) || strchr(".-:/", next) != NULL) continue; output[i] = '?'; } @@ -362,7 +361,7 @@ void DnsHostInfo::GetHtmlTable(const DnsInfoTable host_infos, it != host_infos.end(); it++) { queue.sample((it->queue_duration_.InMilliseconds())); StringAppendF(output, row_format, - RemoveJs(it->hostname_).c_str(), + RemoveJs(it->hostport_.ToString()).c_str(), preresolve.sample((it->benefits_remaining_.InMilliseconds())), resolve.sample((it->resolve_duration_.InMilliseconds())), HoursMinutesSeconds(when.sample( @@ -427,10 +426,10 @@ std::string DnsHostInfo::GetAsciiMotivation() const { return "n/a"; case STATIC_REFERAL_MOTIVATED: - return RemoveJs(referring_hostname_) + "*"; + return RemoveJs(referring_hostport_.ToString()) + "*"; case LEARNED_REFERAL_MOTIVATED: - return RemoveJs(referring_hostname_); + return RemoveJs(referring_hostport_.ToString()); default: return ""; diff --git a/chrome/browser/net/dns_host_info.h b/chrome/browser/net/dns_host_info.h index 34160d5..73a0b2e 100644 --- a/chrome/browser/net/dns_host_info.h +++ b/chrome/browser/net/dns_host_info.h @@ -15,6 +15,7 @@ #include "base/time.h" #include "googleurl/src/gurl.h" +#include "net/base/host_port_pair.h" namespace chrome_browser_net { @@ -43,7 +44,7 @@ class DnsHostInfo { NO_PREFETCH_MOTIVATION, // Browser navigation info (not prefetch related). // The following involve predictive prefetching, triggered by a navigation. - // The referring_hostname_ is also set when these are used. + // The referring_hostport_ is also set when these are used. // TODO(jar): Support STATIC_REFERAL_MOTIVATED API and integration. STATIC_REFERAL_MOTIVATED, // External database suggested this resolution. LEARNED_REFERAL_MOTIVATED, // Prior navigation taught us this resolution. @@ -89,7 +90,7 @@ class DnsHostInfo { // if it would be valuable to attempt to update (prefectch) // DNS data for hostname. This decision is based // on how recently we've done DNS prefetching for hostname. - bool NeedsDnsUpdate(const std::string& hostname); + bool NeedsDnsUpdate(); static void set_cache_expiration(base::TimeDelta time); @@ -105,13 +106,13 @@ class DnsHostInfo { void SetFinishedState(bool was_resolved); // Finish initialization. Must only be called once. - void SetHostname(const std::string& hostname); + void SetHostname(const net::HostPortPair& hostport); bool was_linked() const { return was_linked_; } - std::string referring_hostname() const { return referring_hostname_; } - void SetReferringHostname(const std::string& hostname) { - referring_hostname_ = hostname; + net::HostPortPair referring_hostname() const { return referring_hostport_; } + void SetReferringHostname(const net::HostPortPair& hostport) { + referring_hostport_ = hostport; } bool was_found() const { return FOUND == state_; } @@ -120,10 +121,10 @@ class DnsHostInfo { return ASSIGNED == state_ || ASSIGNED_BUT_MARKED == state_; } bool is_marked_to_delete() const { return ASSIGNED_BUT_MARKED == state_; } - const std::string hostname() const { return hostname_; } + const net::HostPortPair hostport() const { return hostport_; } - bool HasHostname(const std::string& hostname) const { - return (hostname == hostname_); + bool HasHostname(const net::HostPortPair& hostport) const { + return (hostport.Equals(hostport_)); } base::TimeDelta resolve_duration() const { return resolve_duration_;} @@ -166,7 +167,7 @@ class DnsHostInfo { // out of the queue. DnsProcessingState old_prequeue_state_; - std::string hostname_; // Hostname for this info. + net::HostPortPair hostport_; // Hostname for this info. // When was last state changed (usually lookup completed). base::TimeTicks time_; @@ -189,7 +190,7 @@ class DnsHostInfo { // If this instance holds data about a navigation, we store the referrer. // If this instance hold data about a prefetch, and the prefetch was // instigated by a referrer, we store it here (for use in about:dns). - std::string referring_hostname_; + net::HostPortPair referring_hostport_; // We put these objects into a std::map, and hence we // need some "evil" constructors. diff --git a/chrome/browser/net/dns_host_info_unittest.cc b/chrome/browser/net/dns_host_info_unittest.cc index 87f64bc..7c145a4 100644 --- a/chrome/browser/net/dns_host_info_unittest.cc +++ b/chrome/browser/net/dns_host_info_unittest.cc @@ -22,7 +22,7 @@ typedef chrome_browser_net::DnsHostInfo DnsHostInfo; TEST(DnsHostInfoTest, StateChangeTest) { DnsHostInfo info_practice, info; - std::string hostname1("domain1.com"), hostname2("domain2.com"); + net::HostPortPair hostname1("domain1.com", 80), hostname2("domain2.com", 443); // First load DLL, so that their load time won't interfere with tests. // Some tests involve timing function performance, and DLL time can overwhelm @@ -36,14 +36,14 @@ TEST(DnsHostInfoTest, StateChangeTest) { // Complete the construction of real test object. info.SetHostname(hostname1); - EXPECT_TRUE(info.NeedsDnsUpdate(hostname1)) << "error in construction state"; + EXPECT_TRUE(info.NeedsDnsUpdate()) << "error in construction state"; info.SetQueuedState(DnsHostInfo::UNIT_TEST_MOTIVATED); - EXPECT_FALSE(info.NeedsDnsUpdate(hostname1)) + EXPECT_FALSE(info.NeedsDnsUpdate()) << "update needed after being queued"; info.SetAssignedState(); - EXPECT_FALSE(info.NeedsDnsUpdate(hostname1)); + EXPECT_FALSE(info.NeedsDnsUpdate()); info.SetFoundState(); - EXPECT_FALSE(info.NeedsDnsUpdate(hostname1)) + EXPECT_FALSE(info.NeedsDnsUpdate()) << "default expiration time is TOOOOO short"; // Note that time from ASSIGNED to FOUND was VERY short (probably 0ms), so the @@ -55,31 +55,31 @@ TEST(DnsHostInfoTest, StateChangeTest) { << "Non-net time is set too low"; info.set_cache_expiration(TimeDelta::FromMilliseconds(300)); - EXPECT_FALSE(info.NeedsDnsUpdate(hostname1)) << "expiration time not honored"; + EXPECT_FALSE(info.NeedsDnsUpdate()) << "expiration time not honored"; PlatformThread::Sleep(80); // Not enough time to pass our 300ms mark. - EXPECT_FALSE(info.NeedsDnsUpdate(hostname1)) << "expiration time not honored"; + EXPECT_FALSE(info.NeedsDnsUpdate()) << "expiration time not honored"; // That was a nice life when the object was found.... but next time it won't // be found. We'll sleep for a while, and then come back with not-found. info.SetQueuedState(DnsHostInfo::UNIT_TEST_MOTIVATED); info.SetAssignedState(); - EXPECT_FALSE(info.NeedsDnsUpdate(hostname1)); + EXPECT_FALSE(info.NeedsDnsUpdate()); // Greater than minimal expected network latency on DNS lookup. PlatformThread::Sleep(25); info.SetNoSuchNameState(); - EXPECT_FALSE(info.NeedsDnsUpdate(hostname1)) + EXPECT_FALSE(info.NeedsDnsUpdate()) << "default expiration time is TOOOOO short"; // Note that now we'll actually utilize an expiration of 300ms, // since there was detected network activity time during lookup. // We're assuming the caching just started with our lookup. PlatformThread::Sleep(80); // Not enough time to pass our 300ms mark. - EXPECT_FALSE(info.NeedsDnsUpdate(hostname1)) << "expiration time not honored"; + EXPECT_FALSE(info.NeedsDnsUpdate()) << "expiration time not honored"; // Still not past our 300ms mark (only about 4+2ms) PlatformThread::Sleep(80); - EXPECT_FALSE(info.NeedsDnsUpdate(hostname1)) << "expiration time not honored"; + EXPECT_FALSE(info.NeedsDnsUpdate()) << "expiration time not honored"; PlatformThread::Sleep(150); - EXPECT_TRUE(info.NeedsDnsUpdate(hostname1)) << "expiration time not honored"; + EXPECT_TRUE(info.NeedsDnsUpdate()) << "expiration time not honored"; } // When a system gets "congested" relative to DNS, it means it is doing too many @@ -93,7 +93,7 @@ TEST(DnsHostInfoTest, StateChangeTest) { // the state transitions used in such congestion handling. TEST(DnsHostInfoTest, CongestionResetStateTest) { DnsHostInfo info; - std::string hostname1("domain1.com"); + net::HostPortPair hostname1("domain1.com", 80); info.SetHostname(hostname1); info.SetQueuedState(DnsHostInfo::UNIT_TEST_MOTIVATED); diff --git a/chrome/browser/net/dns_master.cc b/chrome/browser/net/dns_master.cc index 431842d..11f65d7 100644 --- a/chrome/browser/net/dns_master.cc +++ b/chrome/browser/net/dns_master.cc @@ -14,8 +14,10 @@ #include "base/string_util.h" #include "base/time.h" #include "chrome/browser/chrome_thread.h" +#include "chrome/browser/net/preconnect.h" #include "net/base/address_list.h" #include "net/base/completion_callback.h" +#include "net/base/host_port_pair.h" #include "net/base/host_resolver.h" #include "net/base/net_errors.h" #include "net/base/net_log.h" @@ -28,11 +30,11 @@ class DnsMaster::LookupRequest { public: LookupRequest(DnsMaster* master, net::HostResolver* host_resolver, - const std::string& hostname) + const net::HostPortPair& hostport) : ALLOW_THIS_IN_INITIALIZER_LIST( net_callback_(this, &LookupRequest::OnLookupFinished)), master_(master), - hostname_(hostname), + hostport_(hostport), resolver_(host_resolver) { } @@ -41,8 +43,7 @@ class DnsMaster::LookupRequest { // net:ERR_IO_PENDING ==> Network will callback later with result. // anything else ==> Host was not found synchronously. int Start() { - // Port doesn't really matter. - net::HostResolver::RequestInfo resolve_info(hostname_, 80); + net::HostResolver::RequestInfo resolve_info(hostport_.host, hostport_.port); // Make a note that this is a speculative resolve request. This allows us // to separate it from real navigations in the observer's callback, and @@ -54,7 +55,7 @@ class DnsMaster::LookupRequest { private: void OnLookupFinished(int result) { - master_->OnLookupFinished(this, hostname_, result == net::OK); + master_->OnLookupFinished(this, hostport_, result == net::OK); } // HostResolver will call us using this callback when resolution is complete. @@ -62,7 +63,7 @@ class DnsMaster::LookupRequest { DnsMaster* master_; // Master which started us. - const std::string hostname_; // Hostname to resolve. + const net::HostPortPair hostport_; // Hostname to resolve. net::SingleRequestHostResolver resolver_; net::AddressList addresses_; @@ -70,13 +71,16 @@ class DnsMaster::LookupRequest { }; DnsMaster::DnsMaster(net::HostResolver* host_resolver, - TimeDelta max_queue_delay, - size_t max_concurrent) - : peak_pending_lookups_(0), - shutdown_(false), - max_concurrent_lookups_(max_concurrent), - max_queue_delay_(max_queue_delay), - host_resolver_(host_resolver) { + base::TimeDelta max_queue_delay, + size_t max_concurrent, + bool preconnect_enabled) + : peak_pending_lookups_(0), + shutdown_(false), + max_concurrent_lookups_(max_concurrent), + max_queue_delay_(max_queue_delay), + host_resolver_(host_resolver), + preconnect_enabled_(preconnect_enabled) { + Referrer::SetUsePreconnectValuations(preconnect_enabled); } DnsMaster::~DnsMaster() { @@ -100,25 +104,25 @@ void DnsMaster::ResolveList(const NameList& hostnames, NameList::const_iterator it; for (it = hostnames.begin(); it < hostnames.end(); ++it) - AppendToResolutionQueue(*it, motivation); + // TODO(jar): I should pass port all the way in from renderer. + AppendToResolutionQueue(net::HostPortPair(*it, 80), motivation); } // Basic Resolve() takes an invidual name, and adds it // to the queue. -void DnsMaster::Resolve(const std::string& hostname, +void DnsMaster::Resolve(const net::HostPortPair& hostport, DnsHostInfo::ResolutionMotivation motivation) { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); - if (0 == hostname.length()) + if (hostport.host.empty()) return; - AppendToResolutionQueue(hostname, motivation); + AppendToResolutionQueue(hostport, motivation); } -bool DnsMaster::AccruePrefetchBenefits(const GURL& referrer, +bool DnsMaster::AccruePrefetchBenefits(const net::HostPortPair& referrer, DnsHostInfo* navigation_info) { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); - std::string hostname = navigation_info->hostname(); - - Results::iterator it = results_.find(hostname); + net::HostPortPair hostport = navigation_info->hostport(); + Results::iterator it = results_.find(hostport); if (it == results_.end()) { // Use UMA histogram to quantify potential future gains here. UMA_HISTOGRAM_LONG_TIMES("DNS.UnexpectedResolutionL", @@ -147,17 +151,15 @@ bool DnsMaster::AccruePrefetchBenefits(const GURL& referrer, case PREFETCH_NAME_NONEXISTANT: cache_hits_.push_back(*navigation_info); if (referrer_based_prefetch) { - std::string motivating_referrer( - prefetched_host_info.referring_hostname()); - if (!motivating_referrer.empty()) { - referrers_[motivating_referrer].AccrueValue( - navigation_info->benefits_remaining(), hostname); + if (!referrer.host.empty()) { + referrers_[referrer].AccrueValue( + navigation_info->benefits_remaining(), hostport); } } return true; case PREFETCH_CACHE_EVICTION: - cache_eviction_map_[hostname] = *navigation_info; + cache_eviction_map_[hostport] = *navigation_info; return false; case PREFETCH_NO_BENEFIT: @@ -165,58 +167,74 @@ bool DnsMaster::AccruePrefetchBenefits(const GURL& referrer, return false; default: - DCHECK(false); + NOTREACHED(); return false; } } -void DnsMaster::NonlinkNavigation(const GURL& referrer, +void DnsMaster::NonlinkNavigation(const net::HostPortPair& referring_hostport, const DnsHostInfo* navigation_info) { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); - std::string referring_host = referrer.host(); - if (referring_host.empty() || referring_host == navigation_info->hostname()) + if (referring_hostport.host.empty() || + referring_hostport.Equals(navigation_info->hostport())) return; - - referrers_[referring_host].SuggestHost(navigation_info->hostname()); + referrers_[referring_hostport].SuggestHost(navigation_info->hostport()); } -void DnsMaster::NavigatingTo(const std::string& host_name) { +void DnsMaster::NavigatingTo(const net::HostPortPair& hostport) { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); - - Referrers::iterator it = referrers_.find(host_name); + Referrers::iterator it = referrers_.find(hostport); if (referrers_.end() == it) return; Referrer* referrer = &(it->second); - for (Referrer::iterator future_host = referrer->begin(); - future_host != referrer->end(); ++future_host) { + referrer->IncrementUseCount(); + for (Referrer::iterator future_hostport = referrer->begin(); + future_hostport != referrer->end(); ++future_hostport) { + if (preconnect_enabled_) { + if (future_hostport->second.IsPreconnectWorthDoing()) { + Preconnect::PreconnectOnIOThread(future_hostport->first); + continue; // No need he pre-resolve DNS. + } + // Fall through and do DNS pre-resolution. + } DnsHostInfo* queued_info = AppendToResolutionQueue( - future_host->first, + future_hostport->first, DnsHostInfo::LEARNED_REFERAL_MOTIVATED); if (queued_info) - queued_info->SetReferringHostname(host_name); + queued_info->SetReferringHostname(hostport); } } // Provide sort order so all .com's are together, etc. struct RightToLeftStringSorter { - bool operator()(const std::string& left, const std::string& right) const { - if (left == right) return true; - size_t left_already_matched = left.size(); - size_t right_already_matched = right.size(); + bool operator()(const net::HostPortPair& left, + const net::HostPortPair& right) const { + return string_compare(left.host, right.host); + } + bool operator()(const GURL& left, + const GURL& right) const { + return string_compare(left.host(), right.host()); + } + + static bool string_compare(const std::string& left_host, + const std::string right_host) { + if (left_host == right_host) return true; + size_t left_already_matched = left_host.size(); + size_t right_already_matched = right_host.size(); // Ensure both strings have characters. if (!left_already_matched) return true; if (!right_already_matched) return false; // Watch for trailing dot, so we'll always be safe to go one beyond dot. - if ('.' == left[left.size() - 1]) { - if ('.' != right[right.size() - 1]) + if ('.' == left_host[left_already_matched - 1]) { + if ('.' != right_host[right_already_matched - 1]) return true; // Both have dots at end of string. --left_already_matched; --right_already_matched; } else { - if ('.' == right[right.size() - 1]) + if ('.' == right_host[right_already_matched - 1]) return false; } @@ -225,7 +243,7 @@ struct RightToLeftStringSorter { if (!right_already_matched) return false; size_t left_length, right_length; - size_t left_start = left.find_last_of('.', left_already_matched - 1); + size_t left_start = left_host.find_last_of('.', left_already_matched - 1); if (std::string::npos == left_start) { left_length = left_already_matched; left_already_matched = left_start = 0; @@ -234,7 +252,8 @@ struct RightToLeftStringSorter { left_already_matched = left_start; ++left_start; // Don't compare the dot. } - size_t right_start = right.find_last_of('.', right_already_matched - 1); + size_t right_start = right_host.find_last_of('.', + right_already_matched - 1); if (std::string::npos == right_start) { right_length = right_already_matched; right_already_matched = right_start = 0; @@ -244,8 +263,8 @@ struct RightToLeftStringSorter { ++right_start; // Don't compare the dot. } - int diff = left.compare(left_start, left.size(), - right, right_start, right.size()); + int diff = left_host.compare(left_start, left_host.size(), + right_host, right_start, right_host.size()); if (diff > 0) return false; if (diff < 0) return true; } @@ -259,7 +278,8 @@ void DnsMaster::GetHtmlReferrerLists(std::string* output) { // TODO(jar): Remove any plausible JavaScript from names before displaying. - typedef std::set<std::string, struct RightToLeftStringSorter> SortedNames; + typedef std::set<net::HostPortPair, struct RightToLeftStringSorter> + SortedNames; SortedNames sorted_names; for (Referrers::iterator it = referrers_.begin(); @@ -267,22 +287,37 @@ void DnsMaster::GetHtmlReferrerLists(std::string* output) { sorted_names.insert(it->first); output->append("<br><table border>"); - StringAppendF(output, - "<tr><th>%s</th><th>%s</th></tr>", - "Host for Page", "Host(s) in Page<br>(benefits in ms)"); + output->append( + "<tr><th>Host for Page</th>" + "<th>Page Load<br>Count</th>" + "<th>Subresource<br>Navigations</th>" + "<th>Subresource<br>PreConnects</th>" + "<th>Expected<br>Connects</th>" + "<th>DNS<br>Savings</th>" + "<th>Subresource Spec</th></tr>"); for (SortedNames::iterator it = sorted_names.begin(); sorted_names.end() != it; ++it) { Referrer* referrer = &(referrers_[*it]); - StringAppendF(output, "<tr align=right><td>%s</td><td>", it->c_str()); - output->append("<table>"); - for (Referrer::iterator future_host = referrer->begin(); - future_host != referrer->end(); ++future_host) { - StringAppendF(output, "<tr align=right><td>(%dms)</td><td>%s</td></tr>", - static_cast<int>(future_host->second.latency().InMilliseconds()), - future_host->first.c_str()); + bool first_set_of_futures = true; + for (Referrer::iterator future_hostport = referrer->begin(); + future_hostport != referrer->end(); ++future_hostport) { + output->append("<tr align=right>"); + if (first_set_of_futures) + StringAppendF(output, "<td rowspan=%d>%s</td><td rowspan=%d>%d</td>", + static_cast<int>(referrer->size()), + it->ToString().c_str(), + static_cast<int>(referrer->size()), + static_cast<int>(referrer->use_count())); + first_set_of_futures = false; + StringAppendF(output, + "<td>%d</td><td>%d</td><td>%2.3f</td><td>%dms</td><td>%s</td></tr>", + static_cast<int>(future_hostport->second.navigation_count()), + static_cast<int>(future_hostport->second.preconnection_count()), + static_cast<double>(future_hostport->second.subresource_use_rate()), + static_cast<int>(future_hostport->second.latency().InMilliseconds()), + future_hostport->first.ToString().c_str()); } - output->append("</table></td></tr>"); } output->append("</table>"); } @@ -297,7 +332,8 @@ void DnsMaster::GetHtmlInfo(std::string* output) { DnsHostInfo::DnsInfoTable already_cached; // Get copies of all useful data. - typedef std::map<std::string, DnsHostInfo, RightToLeftStringSorter> Snapshot; + typedef std::map<net::HostPortPair, DnsHostInfo, RightToLeftStringSorter> + Snapshot; Snapshot snapshot; { // DnsHostInfo supports value semantics, so we can do a shallow copy. @@ -357,28 +393,28 @@ void DnsMaster::GetHtmlInfo(std::string* output) { } DnsHostInfo* DnsMaster::AppendToResolutionQueue( - const std::string& hostname, + const net::HostPortPair& hostport, DnsHostInfo::ResolutionMotivation motivation) { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); - DCHECK_NE(0u, hostname.length()); + DCHECK(!hostport.host.empty()); if (shutdown_) return NULL; - DnsHostInfo* info = &results_[hostname]; - info->SetHostname(hostname); // Initialize or DCHECK. + DnsHostInfo* info = &results_[hostport]; + info->SetHostname(hostport); // Initialize or DCHECK. // TODO(jar): I need to discard names that have long since expired. // Currently we only add to the domain map :-/ - DCHECK(info->HasHostname(hostname)); + DCHECK(info->HasHostname(hostport)); - if (!info->NeedsDnsUpdate(hostname)) { + if (!info->NeedsDnsUpdate()) { info->DLogResultsStats("DNS PrefetchNotUpdated"); return NULL; } info->SetQueuedState(motivation); - work_queue_.Push(hostname, motivation); + work_queue_.Push(hostport, motivation); StartSomeQueuedResolutions(); return info; } @@ -388,9 +424,9 @@ void DnsMaster::StartSomeQueuedResolutions() { while (!work_queue_.IsEmpty() && pending_lookups_.size() < max_concurrent_lookups_) { - const std::string hostname(work_queue_.Pop()); - DnsHostInfo* info = &results_[hostname]; - DCHECK(info->HasHostname(hostname)); + const net::HostPortPair hostport(work_queue_.Pop()); + DnsHostInfo* info = &results_[hostport]; + DCHECK(info->HasHostname(hostport)); info->SetAssignedState(); if (CongestionControlPerformed(info)) { @@ -398,7 +434,7 @@ void DnsMaster::StartSomeQueuedResolutions() { return; } - LookupRequest* request = new LookupRequest(this, host_resolver_, hostname); + LookupRequest* request = new LookupRequest(this, host_resolver_, hostport); int status = request->Start(); if (status == net::ERR_IO_PENDING) { // Will complete asynchronously. @@ -409,7 +445,7 @@ void DnsMaster::StartSomeQueuedResolutions() { // Completed synchronously (was already cached by HostResolver), or else // there was (equivalently) some network error that prevents us from // finding the name. Status net::OK means it was "found." - LookupFinished(request, hostname, status == net::OK); + LookupFinished(request, hostport, status == net::OK); delete request; } } @@ -434,10 +470,11 @@ bool DnsMaster::CongestionControlPerformed(DnsHostInfo* info) { } void DnsMaster::OnLookupFinished(LookupRequest* request, - const std::string& hostname, bool found) { + const net::HostPortPair& hostport, + bool found) { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); - LookupFinished(request, hostname, found); + LookupFinished(request, hostport, found); pending_lookups_.erase(request); delete request; @@ -445,13 +482,13 @@ void DnsMaster::OnLookupFinished(LookupRequest* request, } void DnsMaster::LookupFinished(LookupRequest* request, - const std::string& hostname, + const net::HostPortPair& hostport, bool found) { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); - DnsHostInfo* info = &results_[hostname]; - DCHECK(info->HasHostname(hostname)); + DnsHostInfo* info = &results_[hostport]; + DCHECK(info->HasHostname(hostport)); if (info->is_marked_to_delete()) { - results_.erase(hostname); + results_.erase(hostport); } else { if (found) info->SetFoundState(); @@ -471,9 +508,9 @@ void DnsMaster::DiscardAllResults() { // Try to delete anything in our work queue. while (!work_queue_.IsEmpty()) { // Emulate processing cycle as though host was not found. - std::string hostname = work_queue_.Pop(); - DnsHostInfo* info = &results_[hostname]; - DCHECK(info->HasHostname(hostname)); + net::HostPortPair hostport = work_queue_.Pop(); + DnsHostInfo* info = &results_[hostport]; + DCHECK(info->HasHostname(hostport)); info->SetAssignedState(); info->SetNoSuchNameState(); } @@ -484,12 +521,12 @@ void DnsMaster::DiscardAllResults() { // We can't erase anything being worked on. Results assignees; for (Results::iterator it = results_.begin(); results_.end() != it; ++it) { - std::string hostname = it->first; + net::HostPortPair hostport(it->first); DnsHostInfo* info = &it->second; - DCHECK(info->HasHostname(hostname)); + DCHECK(info->HasHostname(hostport)); if (info->is_assigned()) { info->SetPendingDeleteState(); - assignees[hostname] = *info; + assignees[hostport] = *info; } } DCHECK(assignees.size() <= max_concurrent_lookups_); @@ -503,7 +540,7 @@ void DnsMaster::DiscardAllResults() { void DnsMaster::TrimReferrers() { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); - std::vector<std::string> hosts; + std::vector<net::HostPortPair> hosts; for (Referrers::const_iterator it = referrers_.begin(); it != referrers_.end(); ++it) hosts.push_back(it->first); @@ -515,35 +552,57 @@ void DnsMaster::TrimReferrers() { void DnsMaster::SerializeReferrers(ListValue* referral_list) { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); referral_list->Clear(); + referral_list->Append(new FundamentalValue(DNS_REFERRER_VERSION)); for (Referrers::const_iterator it = referrers_.begin(); it != referrers_.end(); ++it) { // Serialize the list of subresource names. Value* subresource_list(it->second.Serialize()); // Create a list for each referer. - ListValue* motivating_host(new ListValue); - motivating_host->Append(new StringValue(it->first)); - motivating_host->Append(subresource_list); + ListValue* motivator(new ListValue); + motivator->Append(new FundamentalValue(it->first.port)); + motivator->Append(new StringValue(it->first.host)); + motivator->Append(subresource_list); - referral_list->Append(motivating_host); + referral_list->Append(motivator); } } void DnsMaster::DeserializeReferrers(const ListValue& referral_list) { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); - for (size_t i = 0; i < referral_list.GetSize(); ++i) { - ListValue* motivating_host; - if (!referral_list.GetList(i, &motivating_host)) - continue; - std::string motivating_referrer; - if (!motivating_host->GetString(0, &motivating_referrer)) - continue; - Value* subresource_list; - if (!motivating_host->Get(1, &subresource_list)) - continue; - if (motivating_referrer.empty()) - continue; - referrers_[motivating_referrer].Deserialize(*subresource_list); + int format_version = -1; + if (referral_list.GetSize() > 0 && + referral_list.GetInteger(0, &format_version) && + format_version == DNS_REFERRER_VERSION) { + for (size_t i = 1; i < referral_list.GetSize(); ++i) { + ListValue* motivator; + if (!referral_list.GetList(i, &motivator)) { + NOTREACHED(); + continue; + } + int motivating_port; + if (!motivator->GetInteger(0, &motivating_port)) { + NOTREACHED(); + continue; + } + std::string motivating_host; + if (!motivator->GetString(1, &motivating_host)) { + NOTREACHED(); + continue; + } + if (motivating_host.empty()) { + NOTREACHED(); + continue; + } + + Value* subresource_list; + if (!motivator->Get(2, &subresource_list)) { + NOTREACHED(); + continue; + } + net::HostPortPair motivating_hostport(motivating_host, motivating_port); + referrers_[motivating_hostport].Deserialize(*subresource_list); + } } } @@ -556,17 +615,17 @@ DnsMaster::HostNameQueue::HostNameQueue() { DnsMaster::HostNameQueue::~HostNameQueue() { } -void DnsMaster::HostNameQueue::Push(const std::string& hostname, +void DnsMaster::HostNameQueue::Push(const net::HostPortPair& hostport, DnsHostInfo::ResolutionMotivation motivation) { switch (motivation) { case DnsHostInfo::STATIC_REFERAL_MOTIVATED: case DnsHostInfo::LEARNED_REFERAL_MOTIVATED: case DnsHostInfo::MOUSE_OVER_MOTIVATED: - rush_queue_.push(hostname); + rush_queue_.push(hostport); break; default: - background_queue_.push(hostname); + background_queue_.push(hostport); break; } } @@ -575,16 +634,16 @@ bool DnsMaster::HostNameQueue::IsEmpty() const { return rush_queue_.empty() && background_queue_.empty(); } -std::string DnsMaster::HostNameQueue::Pop() { +net::HostPortPair DnsMaster::HostNameQueue::Pop() { DCHECK(!IsEmpty()); if (!rush_queue_.empty()) { - std::string hostname(rush_queue_.front()); + net::HostPortPair hostport(rush_queue_.front()); rush_queue_.pop(); - return hostname; + return hostport; } - std::string hostname(background_queue_.front()); + net::HostPortPair hostport(background_queue_.front()); background_queue_.pop(); - return hostname; + return hostport; } } // namespace chrome_browser_net diff --git a/chrome/browser/net/dns_master.h b/chrome/browser/net/dns_master.h index b665358..8449731 100644 --- a/chrome/browser/net/dns_master.h +++ b/chrome/browser/net/dns_master.h @@ -23,10 +23,9 @@ #include "chrome/browser/net/dns_host_info.h" #include "chrome/browser/net/referrer.h" #include "chrome/common/net/dns.h" +#include "net/base/host_port_pair.h" #include "testing/gtest/include/gtest/gtest_prod.h" -using base::TimeDelta; - namespace net { class HostResolver; } // namespace net @@ -34,16 +33,21 @@ class HostResolver; namespace chrome_browser_net { typedef chrome_common_net::NameList NameList; -typedef std::map<std::string, DnsHostInfo> Results; +typedef std::map<net::HostPortPair, DnsHostInfo> Results; // Note that DNS master is not thread safe, and must only be called from // the IO thread. Failure to do so will result in a DCHECK at runtime. class DnsMaster : public base::RefCountedThreadSafe<DnsMaster> { public: - // |max_concurrent| specifies how many concurrent (parallel) prefetches will + // A version number for prefs that are saved. This should be incremented when + // we change the format so that we discard old data. + enum {DNS_REFERRER_VERSION = 0 }; + +// |max_concurrent| specifies how many concurrent (parallel) prefetches will // be performed. Host lookups will be issued through |host_resolver|. DnsMaster(net::HostResolver* host_resolver, - TimeDelta max_queue_delay_ms, size_t max_concurrent); + base::TimeDelta max_queue_delay_ms, size_t max_concurrent, + bool preconnect_enabled); // Cancel pending requests and prevent new ones from being made. void Shutdown(); @@ -58,21 +62,21 @@ class DnsMaster : public base::RefCountedThreadSafe<DnsMaster> { // Add hostname(s) to the queue for processing. void ResolveList(const NameList& hostnames, DnsHostInfo::ResolutionMotivation motivation); - void Resolve(const std::string& hostname, + void Resolve(const net::HostPortPair& hostport, DnsHostInfo::ResolutionMotivation motivation); // Get latency benefit of the prefetch that we are navigating to. - bool AccruePrefetchBenefits(const GURL& referrer, + bool AccruePrefetchBenefits(const net::HostPortPair& referrer, DnsHostInfo* navigation_info); // Instigate prefetch of any domains we predict will be needed after this // navigation. - void NavigatingTo(const std::string& host_name); + void NavigatingTo(const net::HostPortPair& hostport); // Record details of a navigation so that we can preresolve the host name // ahead of time the next time the users navigates to the indicated host. // TODO(eroman): can this be a const& instead? - void NonlinkNavigation(const GURL& referrer, + void NonlinkNavigation(const net::HostPortPair& referrer, const DnsHostInfo* navigation_info); // Dump HTML table containing list of referrers for about:dns. @@ -106,6 +110,9 @@ class DnsMaster : public base::RefCountedThreadSafe<DnsMaster> { // For unit test code only. size_t max_concurrent_lookups() const { return max_concurrent_lookups_; } + // Flag setting to use preconnection instead of just DNS pre-fetching. + bool preconnect_enabled() const { return preconnect_enabled_; } + private: friend class base::RefCountedThreadSafe<DnsMaster>; FRIEND_TEST(DnsMasterTest, BenefitLookupTest); @@ -133,39 +140,41 @@ class DnsMaster : public base::RefCountedThreadSafe<DnsMaster> { public: HostNameQueue(); ~HostNameQueue(); - void Push(const std::string& hostname, + void Push(const net::HostPortPair& hostport, DnsHostInfo::ResolutionMotivation motivation); bool IsEmpty() const; - std::string Pop(); + net::HostPortPair Pop(); private: // The names in the queue that should be serviced (popped) ASAP. - std::queue<std::string> rush_queue_; + std::queue<net::HostPortPair> rush_queue_; // The names in the queue that should only be serviced when rush_queue is // empty. - std::queue<std::string> background_queue_; + std::queue<net::HostPortPair> background_queue_; DISALLOW_COPY_AND_ASSIGN(HostNameQueue); }; - // A map that is keyed with the hostnames that we've learned were the cause - // of loading additional hostnames. The list of additional hostnames in held - // in a Referrer instance, which is found in this type. - typedef std::map<std::string, Referrer> Referrers; + // A map that is keyed with the host/port that we've learned were the cause + // of loading additional URLs. The list of additional targets is held + // in a Referrer instance, which is a value in this map. + typedef std::map<net::HostPortPair, Referrer> Referrers; // Only for testing. Returns true if hostname has been successfully resolved // (name found). - bool WasFound(const std::string& hostname) { - return (results_.find(hostname) != results_.end()) && - results_[hostname].was_found(); + bool WasFound(const net::HostPortPair& hostport) const { + Results::const_iterator it(results_.find(hostport)); + return (it != results_.end()) && + it->second.was_found(); } // Only for testing. Return how long was the resolution // or DnsHostInfo::kNullDuration if it hasn't been resolved yet. - base::TimeDelta GetResolutionDuration(const std::string& hostname) { - if (results_.find(hostname) == results_.end()) + base::TimeDelta GetResolutionDuration(const net::HostPortPair& hostport) { + + if (results_.find(hostport) == results_.end()) return DnsHostInfo::kNullDuration; - return results_[hostname].resolve_duration(); + return results_[hostport].resolve_duration(); } // Only for testing; @@ -173,16 +182,15 @@ class DnsMaster : public base::RefCountedThreadSafe<DnsMaster> { // Access method for use by async lookup request to pass resolution result. void OnLookupFinished(LookupRequest* request, - const std::string& hostname, bool found); + const net::HostPortPair& hostport, bool found); // Underlying method for both async and synchronous lookup to update state. void LookupFinished(LookupRequest* request, - const std::string& hostname, - bool found); + const net::HostPortPair& hostport, bool found); // Queue hostname for resolution. If queueing was done, return the pointer // to the queued instance, otherwise return NULL. - DnsHostInfo* AppendToResolutionQueue(const std::string& hostname, + DnsHostInfo* AppendToResolutionQueue(const net::HostPortPair& hostport, DnsHostInfo::ResolutionMotivation motivation); // Check to see if too much queuing delay has been noted for the given info, @@ -209,7 +217,7 @@ class DnsMaster : public base::RefCountedThreadSafe<DnsMaster> { // results_ contains information for existing/prior prefetches. Results results_; - // For each hostname that we might navigate to (that we've "learned about") + // For each URL that we might navigate to (that we've "learned about") // we have a Referrer list. Each Referrer list has all hostnames we need to // pre-resolve when there is a navigation to the orginial hostname. Referrers referrers_; @@ -233,11 +241,15 @@ class DnsMaster : public base::RefCountedThreadSafe<DnsMaster> { // The maximum queueing delay that is acceptable before we enter congestion // reduction mode, and discard all queued (but not yet assigned) resolutions. - const TimeDelta max_queue_delay_; + const base::TimeDelta max_queue_delay_; // The host resovler we warm DNS entries for. scoped_refptr<net::HostResolver> host_resolver_; + // Are we currently using preconnection, rather than just DNS resolution, for + // subresources and omni-box search URLs. + bool preconnect_enabled_; + DISALLOW_COPY_AND_ASSIGN(DnsMaster); }; diff --git a/chrome/browser/net/dns_master_unittest.cc b/chrome/browser/net/dns_master_unittest.cc index 2f83d30..2699b1f 100644 --- a/chrome/browser/net/dns_master_unittest.cc +++ b/chrome/browser/net/dns_master_unittest.cc @@ -41,7 +41,8 @@ class WaitForResolutionHelper { void Run() { for (NameList::const_iterator i = hosts_.begin(); i != hosts_.end(); ++i) - if (master_->GetResolutionDuration(*i) == DnsHostInfo::kNullDuration) + if (master_->GetResolutionDuration(net::HostPortPair(*i, 80)) == + DnsHostInfo::kNullDuration) return; // We don't have resolution for that host. // When all hostnames have been resolved, exit the loop. @@ -109,19 +110,22 @@ class DnsMasterTest : public testing::Test { TEST_F(DnsMasterTest, StartupShutdownTest) { scoped_refptr<DnsMaster> testing_master = new DnsMaster(host_resolver_, default_max_queueing_delay_, - DnsGlobalInit::kMaxPrefetchConcurrentLookups); + DnsGlobalInit::kMaxPrefetchConcurrentLookups, + false); testing_master->Shutdown(); } TEST_F(DnsMasterTest, BenefitLookupTest) { - scoped_refptr<DnsMaster> testing_master = new DnsMaster(host_resolver_, + scoped_refptr<DnsMaster> testing_master = new DnsMaster( + host_resolver_, default_max_queueing_delay_, - DnsGlobalInit::kMaxPrefetchConcurrentLookups); + DnsGlobalInit::kMaxPrefetchConcurrentLookups, + false); - std::string goog("www.google.com"), - goog2("gmail.google.com.com"), - goog3("mail.google.com"), - goog4("gmail.com"); + net::HostPortPair goog("www.google.com", 80), + goog2("gmail.google.com.com", 80), + goog3("mail.google.com", 80), + goog4("gmail.com", 80); DnsHostInfo goog_info, goog2_info, goog3_info, goog4_info; // Simulate getting similar names from a network observer @@ -141,10 +145,10 @@ TEST_F(DnsMasterTest, BenefitLookupTest) { goog4_info.SetFinishedState(true); NameList names; - names.insert(names.end(), goog); - names.insert(names.end(), goog2); - names.insert(names.end(), goog3); - names.insert(names.end(), goog4); + names.push_back(goog.host); + names.push_back(goog2.host); + names.push_back(goog3.host); + names.push_back(goog4.host); testing_master->ResolveList(names, DnsHostInfo::PAGE_SCAN_MOTIVATED); @@ -158,18 +162,20 @@ TEST_F(DnsMasterTest, BenefitLookupTest) { // With the mock DNS, each of these should have taken some time, and hence // shown a benefit (i.e., prefetch cost more than network access time). + net::HostPortPair referer; // Null host. + // Simulate actual navigation, and acrue the benefit for "helping" the DNS // part of the navigation. - EXPECT_TRUE(testing_master->AccruePrefetchBenefits(GURL(), &goog_info)); - EXPECT_TRUE(testing_master->AccruePrefetchBenefits(GURL(), &goog2_info)); - EXPECT_TRUE(testing_master->AccruePrefetchBenefits(GURL(), &goog3_info)); - EXPECT_TRUE(testing_master->AccruePrefetchBenefits(GURL(), &goog4_info)); + EXPECT_TRUE(testing_master->AccruePrefetchBenefits(referer, &goog_info)); + EXPECT_TRUE(testing_master->AccruePrefetchBenefits(referer, &goog2_info)); + EXPECT_TRUE(testing_master->AccruePrefetchBenefits(referer, &goog3_info)); + EXPECT_TRUE(testing_master->AccruePrefetchBenefits(referer, &goog4_info)); // Benefits can ONLY be reported once (for the first navigation). - EXPECT_FALSE(testing_master->AccruePrefetchBenefits(GURL(), &goog_info)); - EXPECT_FALSE(testing_master->AccruePrefetchBenefits(GURL(), &goog2_info)); - EXPECT_FALSE(testing_master->AccruePrefetchBenefits(GURL(), &goog3_info)); - EXPECT_FALSE(testing_master->AccruePrefetchBenefits(GURL(), &goog4_info)); + EXPECT_FALSE(testing_master->AccruePrefetchBenefits(referer, &goog_info)); + EXPECT_FALSE(testing_master->AccruePrefetchBenefits(referer, &goog2_info)); + EXPECT_FALSE(testing_master->AccruePrefetchBenefits(referer, &goog3_info)); + EXPECT_FALSE(testing_master->AccruePrefetchBenefits(referer, &goog4_info)); testing_master->Shutdown(); } @@ -181,11 +187,12 @@ TEST_F(DnsMasterTest, ShutdownWhenResolutionIsPendingTest) { scoped_refptr<DnsMaster> testing_master = new DnsMaster(host_resolver_, default_max_queueing_delay_, - DnsGlobalInit::kMaxPrefetchConcurrentLookups); + DnsGlobalInit::kMaxPrefetchConcurrentLookups, + false); - std::string localhost("127.0.0.1"); + net::HostPortPair localhost("127.0.0.1", 80); NameList names; - names.insert(names.end(), localhost); + names.push_back(localhost.host); testing_master->ResolveList(names, DnsHostInfo::PAGE_SCAN_MOTIVATED); @@ -205,12 +212,13 @@ TEST_F(DnsMasterTest, ShutdownWhenResolutionIsPendingTest) { TEST_F(DnsMasterTest, SingleLookupTest) { scoped_refptr<DnsMaster> testing_master = new DnsMaster(host_resolver_, default_max_queueing_delay_, - DnsGlobalInit::kMaxPrefetchConcurrentLookups); + DnsGlobalInit::kMaxPrefetchConcurrentLookups, + false); - std::string goog("www.google.com"); + net::HostPortPair goog("www.google.com", 80); NameList names; - names.insert(names.end(), goog); + names.push_back(goog.host); // Try to flood the master with many concurrent requests. for (int i = 0; i < 10; i++) @@ -235,23 +243,24 @@ TEST_F(DnsMasterTest, ConcurrentLookupTest) { scoped_refptr<DnsMaster> testing_master = new DnsMaster(host_resolver_, default_max_queueing_delay_, - DnsGlobalInit::kMaxPrefetchConcurrentLookups); + DnsGlobalInit::kMaxPrefetchConcurrentLookups, + false); - std::string goog("www.google.com"), - goog2("gmail.google.com.com"), - goog3("mail.google.com"), - goog4("gmail.com"); - std::string bad1("bad1.notfound"), - bad2("bad2.notfound"); + net::HostPortPair goog("www.google.com", 80), + goog2("gmail.google.com.com", 80), + goog3("mail.google.com", 80), + goog4("gmail.com", 80); + net::HostPortPair bad1("bad1.notfound", 80), + bad2("bad2.notfound", 80); NameList names; - names.insert(names.end(), goog); - names.insert(names.end(), goog3); - names.insert(names.end(), bad1); - names.insert(names.end(), goog2); - names.insert(names.end(), bad2); - names.insert(names.end(), goog4); - names.insert(names.end(), goog); + names.push_back(goog.host); + names.push_back(goog3.host); + names.push_back(bad1.host); + names.push_back(goog2.host); + names.push_back(bad2.host); + names.push_back(goog4.host); + names.push_back(goog.host); // Try to flood the master with many concurrent requests. for (int i = 0; i < 10; i++) @@ -282,9 +291,11 @@ TEST_F(DnsMasterTest, ConcurrentLookupTest) { TEST_F(DnsMasterTest, MassiveConcurrentLookupTest) { host_resolver_->rules()->AddSimulatedFailure("*.notfound"); - scoped_refptr<DnsMaster> testing_master = new DnsMaster(host_resolver_, + scoped_refptr<DnsMaster> testing_master = new DnsMaster( + host_resolver_, default_max_queueing_delay_, - DnsGlobalInit::kMaxPrefetchConcurrentLookups); + DnsGlobalInit::kMaxPrefetchConcurrentLookups, + false); NameList names; for (int i = 0; i < 100; i++) @@ -311,25 +322,39 @@ TEST_F(DnsMasterTest, MassiveConcurrentLookupTest) { // Return a motivation_list if we can find one for the given motivating_host (or // NULL if a match is not found). -static ListValue* FindSerializationMotivation(const std::string& motivation, - const ListValue& referral_list) { +static ListValue* FindSerializationMotivation( + const net::HostPortPair& motivation, const ListValue& referral_list) { + CHECK_LT(0u, referral_list.GetSize()); // Room for version. + int format_version = -1; + CHECK(referral_list.GetInteger(0, &format_version)); + CHECK_EQ(DnsMaster::DNS_REFERRER_VERSION, format_version); ListValue* motivation_list(NULL); - for (size_t i = 0; i < referral_list.GetSize(); ++i) { + for (size_t i = 1; i < referral_list.GetSize(); ++i) { referral_list.GetList(i, &motivation_list); - std::string existing_motivation; - EXPECT_TRUE(motivation_list->GetString(i, &existing_motivation)); - if (existing_motivation == motivation) - break; - motivation_list = NULL; + std::string existing_host; + int existing_port; + EXPECT_TRUE(motivation_list->GetInteger(0, &existing_port)); + EXPECT_TRUE(motivation_list->GetString(1, &existing_host)); + if (motivation.host == existing_host && motivation.port == existing_port) + return motivation_list; } - return motivation_list; + return NULL; +} + +// Create a new empty serialization list. +static ListValue* NewEmptySerializationList() { + ListValue* list = new ListValue; + list->Append(new FundamentalValue(DnsMaster::DNS_REFERRER_VERSION)); + return list; } // Add a motivating_host and a subresource_host to a serialized list, using // this given latency. This is a helper function for quickly building these // lists. -static void AddToSerializedList(const std::string& motivation, - const std::string& subresource, int latency, +static void AddToSerializedList(const net::HostPortPair& motivation, + const net::HostPortPair& subresource, + int latency, + double rate, ListValue* referral_list ) { // Find the motivation if it is already used. ListValue* motivation_list = FindSerializationMotivation(motivation, @@ -337,7 +362,8 @@ static void AddToSerializedList(const std::string& motivation, if (!motivation_list) { // This is the first mention of this motivation, so build a list. motivation_list = new ListValue; - motivation_list->Append(new StringValue(motivation)); + motivation_list->Append(new FundamentalValue(motivation.port)); + motivation_list->Append(new StringValue(motivation.host)); // Provide empty subresource list. motivation_list->Append(new ListValue()); @@ -346,41 +372,48 @@ static void AddToSerializedList(const std::string& motivation, } ListValue* subresource_list(NULL); - EXPECT_TRUE(motivation_list->GetList(1, &subresource_list)); + // 0 == port; 1 == host; 2 == subresource_list. + EXPECT_TRUE(motivation_list->GetList(2, &subresource_list)); // We won't bother to check for the subresource being there already. Worst // case, during deserialization, the latency value we supply plus the // existing value(s) will be added to the referrer. - subresource_list->Append(new StringValue(subresource)); + + subresource_list->Append(new FundamentalValue(subresource.port)); + subresource_list->Append(new StringValue(subresource.host)); subresource_list->Append(new FundamentalValue(latency)); + subresource_list->Append(new FundamentalValue(rate)); } static const int kLatencyNotFound = -1; -// For a given motivation_hostname, and subresource_hostname, find what latency -// is currently listed. This assume a well formed serialization, which has -// at most one such entry for any pair of names. If no such pair is found, then -// return kLatencyNotFound. -int GetLatencyFromSerialization(const std::string& motivation, - const std::string& subresource, - const ListValue& referral_list) { +// For a given motivation, and subresource, find what latency is currently +// listed. This assume a well formed serialization, which has at most one such +// entry for any pair of names. If no such pair is found, then return false. +// Data is written into rate and latency arguments. +static bool GetDataFromSerialization(const net::HostPortPair& motivation, + const net::HostPortPair& subresource, + const ListValue& referral_list, + double* rate, + int* latency) { ListValue* motivation_list = FindSerializationMotivation(motivation, referral_list); if (!motivation_list) - return kLatencyNotFound; + return false; ListValue* subresource_list; - EXPECT_TRUE(motivation_list->GetList(1, &subresource_list)); - for (size_t i = 0; i < subresource_list->GetSize(); ++i) { - std::string subresource_name; - EXPECT_TRUE(subresource_list->GetString(i, &subresource_name)); - if (subresource_name == subresource) { - int latency; - EXPECT_TRUE(subresource_list->GetInteger(i + 1, &latency)); - return latency; + EXPECT_TRUE(motivation_list->GetList(2, &subresource_list)); + for (size_t i = 0; i < subresource_list->GetSize();) { + std::string host; + int port; + EXPECT_TRUE(subresource_list->GetInteger(i++, &port)); + EXPECT_TRUE(subresource_list->GetString(i++, &host)); + EXPECT_TRUE(subresource_list->GetInteger(i++, latency)); + EXPECT_TRUE(subresource_list->GetReal(i++, rate)); + if (subresource.host == host && subresource.port == port) { + return true; } - ++i; // Skip latency value. } - return kLatencyNotFound; + return false; } //------------------------------------------------------------------------------ @@ -389,12 +422,14 @@ int GetLatencyFromSerialization(const std::string& motivation, TEST_F(DnsMasterTest, ReferrerSerializationNilTest) { scoped_refptr<DnsMaster> master = new DnsMaster(host_resolver_, default_max_queueing_delay_, - DnsGlobalInit::kMaxPrefetchConcurrentLookups); - ListValue referral_list; - master->SerializeReferrers(&referral_list); - EXPECT_EQ(0U, referral_list.GetSize()); - EXPECT_EQ(kLatencyNotFound, GetLatencyFromSerialization("a.com", "b.com", - referral_list)); + DnsGlobalInit::kMaxPrefetchConcurrentLookups, + false); + scoped_ptr<ListValue> referral_list(NewEmptySerializationList()); + master->SerializeReferrers(referral_list.get()); + EXPECT_EQ(1U, referral_list->GetSize()); + EXPECT_FALSE(GetDataFromSerialization( + net::HostPortPair("a.com", 79), net::HostPortPair("b.com", 78), + *referral_list.get(), NULL, NULL)); master->Shutdown(); } @@ -405,23 +440,29 @@ TEST_F(DnsMasterTest, ReferrerSerializationNilTest) { TEST_F(DnsMasterTest, ReferrerSerializationSingleReferrerTest) { scoped_refptr<DnsMaster> master = new DnsMaster(host_resolver_, default_max_queueing_delay_, - DnsGlobalInit::kMaxPrefetchConcurrentLookups); - std::string motivation_hostname = "www.google.com"; - std::string subresource_hostname = "icons.google.com"; + DnsGlobalInit::kMaxPrefetchConcurrentLookups, + false); + const net::HostPortPair motivation_hostport("www.google.com", 91); + const net::HostPortPair subresource_hostport("icons.google.com", 90); const int kLatency = 3; - ListValue referral_list; + const double kRate = 23.4; + scoped_ptr<ListValue> referral_list(NewEmptySerializationList()); - AddToSerializedList(motivation_hostname, subresource_hostname, kLatency, - &referral_list); + AddToSerializedList(motivation_hostport, subresource_hostport, + kLatency, kRate, referral_list.get()); - master->DeserializeReferrers(referral_list); + master->DeserializeReferrers(*referral_list.get()); ListValue recovered_referral_list; master->SerializeReferrers(&recovered_referral_list); - EXPECT_EQ(1U, recovered_referral_list.GetSize()); - EXPECT_EQ(kLatency, GetLatencyFromSerialization(motivation_hostname, - subresource_hostname, - recovered_referral_list)); + EXPECT_EQ(2U, recovered_referral_list.GetSize()); + int latency; + double rate; + EXPECT_TRUE(GetDataFromSerialization( + motivation_hostport, subresource_hostport, recovered_referral_list, &rate, + &latency)); + EXPECT_EQ(rate, kRate); + EXPECT_EQ(latency, kLatency); master->Shutdown(); } @@ -430,72 +471,104 @@ TEST_F(DnsMasterTest, ReferrerSerializationSingleReferrerTest) { TEST_F(DnsMasterTest, ReferrerSerializationTrimTest) { scoped_refptr<DnsMaster> master = new DnsMaster(host_resolver_, default_max_queueing_delay_, - DnsGlobalInit::kMaxPrefetchConcurrentLookups); - std::string motivation_hostname = "www.google.com"; - std::string icon_subresource_hostname = "icons.google.com"; - std::string img_subresource_hostname = "img.google.com"; - ListValue referral_list; - - AddToSerializedList(motivation_hostname, icon_subresource_hostname, 10, - &referral_list); - AddToSerializedList(motivation_hostname, img_subresource_hostname, 3, - &referral_list); - - master->DeserializeReferrers(referral_list); + DnsGlobalInit::kMaxPrefetchConcurrentLookups, + false); + net::HostPortPair motivation_hostport("www.google.com", 110); + + net::HostPortPair icon_subresource_hostport("icons.google.com", 111); + const int kLatencyIcon = 10; + const double kRateIcon = 0.; // User low rate, so latency will dominate. + net::HostPortPair img_subresource_hostport("img.google.com", 118); + const int kLatencyImg = 3; + const double kRateImg = 0.; + + scoped_ptr<ListValue> referral_list(NewEmptySerializationList()); + AddToSerializedList( + motivation_hostport, icon_subresource_hostport, + kLatencyIcon, kRateIcon, referral_list.get()); + AddToSerializedList( + motivation_hostport, img_subresource_hostport, + kLatencyImg, kRateImg, referral_list.get()); + + master->DeserializeReferrers(*referral_list.get()); ListValue recovered_referral_list; master->SerializeReferrers(&recovered_referral_list); - EXPECT_EQ(1U, recovered_referral_list.GetSize()); - EXPECT_EQ(10, GetLatencyFromSerialization(motivation_hostname, - icon_subresource_hostname, - recovered_referral_list)); - EXPECT_EQ(3, GetLatencyFromSerialization(motivation_hostname, - img_subresource_hostname, - recovered_referral_list)); + EXPECT_EQ(2U, recovered_referral_list.GetSize()); + int latency; + double rate; + EXPECT_TRUE(GetDataFromSerialization( + motivation_hostport, icon_subresource_hostport, recovered_referral_list, + &rate, &latency)); + EXPECT_EQ(latency, kLatencyIcon); + EXPECT_EQ(rate, kRateIcon); + + EXPECT_TRUE(GetDataFromSerialization( + motivation_hostport, img_subresource_hostport, recovered_referral_list, + &rate, &latency)); + EXPECT_EQ(latency, kLatencyImg); + EXPECT_EQ(rate, kRateImg); // Each time we Trim, the latency figures should reduce by a factor of two, // until they both are 0, an then a trim will delete the whole entry. master->TrimReferrers(); master->SerializeReferrers(&recovered_referral_list); - EXPECT_EQ(1U, recovered_referral_list.GetSize()); - EXPECT_EQ(5, GetLatencyFromSerialization(motivation_hostname, - icon_subresource_hostname, - recovered_referral_list)); - EXPECT_EQ(1, GetLatencyFromSerialization(motivation_hostname, - img_subresource_hostname, - recovered_referral_list)); + EXPECT_EQ(2U, recovered_referral_list.GetSize()); + EXPECT_TRUE(GetDataFromSerialization( + motivation_hostport, icon_subresource_hostport, recovered_referral_list, + &rate, &latency)); + EXPECT_EQ(latency, kLatencyIcon / 2); + EXPECT_EQ(rate, kRateIcon); + + EXPECT_TRUE(GetDataFromSerialization( + motivation_hostport, img_subresource_hostport, recovered_referral_list, + &rate, &latency)); + EXPECT_EQ(latency, kLatencyImg / 2); + EXPECT_EQ(rate, kRateImg); master->TrimReferrers(); master->SerializeReferrers(&recovered_referral_list); - EXPECT_EQ(1U, recovered_referral_list.GetSize()); - EXPECT_EQ(2, GetLatencyFromSerialization(motivation_hostname, - icon_subresource_hostname, - recovered_referral_list)); - EXPECT_EQ(0, GetLatencyFromSerialization(motivation_hostname, - img_subresource_hostname, - recovered_referral_list)); + EXPECT_EQ(2U, recovered_referral_list.GetSize()); + EXPECT_TRUE(GetDataFromSerialization( + motivation_hostport, icon_subresource_hostport, recovered_referral_list, + &rate, &latency)); + EXPECT_EQ(latency, kLatencyIcon / 4); + EXPECT_EQ(rate, kRateIcon); + // Img is down to zero, but we don't delete it yet. + EXPECT_TRUE(GetDataFromSerialization( + motivation_hostport, img_subresource_hostport, recovered_referral_list, + &rate, &latency)); + EXPECT_EQ(kLatencyImg / 4, 0); + EXPECT_EQ(latency, kLatencyImg / 4); + EXPECT_EQ(rate, kRateImg); master->TrimReferrers(); master->SerializeReferrers(&recovered_referral_list); - EXPECT_EQ(1U, recovered_referral_list.GetSize()); - EXPECT_EQ(1, GetLatencyFromSerialization(motivation_hostname, - icon_subresource_hostname, - recovered_referral_list)); - EXPECT_EQ(0, GetLatencyFromSerialization(motivation_hostname, - img_subresource_hostname, - recovered_referral_list)); + EXPECT_EQ(2U, recovered_referral_list.GetSize()); + EXPECT_TRUE(GetDataFromSerialization( + motivation_hostport, icon_subresource_hostport, recovered_referral_list, + &rate, &latency)); + EXPECT_EQ(latency, kLatencyIcon / 8); + EXPECT_EQ(rate, kRateIcon); + + // Img is down to zero, but we don't delete it yet. + EXPECT_TRUE(GetDataFromSerialization( + motivation_hostport, img_subresource_hostport, recovered_referral_list, + &rate, &latency)); + EXPECT_EQ(kLatencyImg / 8, 0); + EXPECT_EQ(latency, kLatencyImg / 8); + EXPECT_EQ(rate, kRateImg); master->TrimReferrers(); master->SerializeReferrers(&recovered_referral_list); - EXPECT_EQ(0U, recovered_referral_list.GetSize()); - EXPECT_EQ(kLatencyNotFound, - GetLatencyFromSerialization(motivation_hostname, - icon_subresource_hostname, - recovered_referral_list)); - EXPECT_EQ(kLatencyNotFound, - GetLatencyFromSerialization(motivation_hostname, - img_subresource_hostname, - recovered_referral_list)); + // Icon is also trimmed away, so entire set gets discarded. + EXPECT_EQ(1U, recovered_referral_list.GetSize()); + EXPECT_FALSE(GetDataFromSerialization( + motivation_hostport, icon_subresource_hostport, recovered_referral_list, + &rate, &latency)); + EXPECT_FALSE(GetDataFromSerialization( + motivation_hostport, img_subresource_hostport, recovered_referral_list, + &rate, &latency)); master->Shutdown(); } @@ -504,25 +577,27 @@ TEST_F(DnsMasterTest, ReferrerSerializationTrimTest) { TEST_F(DnsMasterTest, PriorityQueuePushPopTest) { DnsMaster::HostNameQueue queue; + net::HostPortPair first("first", 80), second("second", 90); + // First check high priority queue FIFO functionality. EXPECT_TRUE(queue.IsEmpty()); - queue.Push("a", DnsHostInfo::LEARNED_REFERAL_MOTIVATED); + queue.Push(first, DnsHostInfo::LEARNED_REFERAL_MOTIVATED); EXPECT_FALSE(queue.IsEmpty()); - queue.Push("b", DnsHostInfo::MOUSE_OVER_MOTIVATED); + queue.Push(second, DnsHostInfo::MOUSE_OVER_MOTIVATED); EXPECT_FALSE(queue.IsEmpty()); - EXPECT_EQ(queue.Pop(), "a"); + EXPECT_EQ(queue.Pop().ToString(), first.ToString()); EXPECT_FALSE(queue.IsEmpty()); - EXPECT_EQ(queue.Pop(), "b"); + EXPECT_EQ(queue.Pop().ToString(), second.ToString()); EXPECT_TRUE(queue.IsEmpty()); // Then check low priority queue FIFO functionality. - queue.Push("a", DnsHostInfo::PAGE_SCAN_MOTIVATED); + queue.Push(first, DnsHostInfo::PAGE_SCAN_MOTIVATED); EXPECT_FALSE(queue.IsEmpty()); - queue.Push("b", DnsHostInfo::OMNIBOX_MOTIVATED); + queue.Push(second, DnsHostInfo::OMNIBOX_MOTIVATED); EXPECT_FALSE(queue.IsEmpty()); - EXPECT_EQ(queue.Pop(), "a"); + EXPECT_EQ(queue.Pop().ToString(), first.ToString()); EXPECT_FALSE(queue.IsEmpty()); - EXPECT_EQ(queue.Pop(), "b"); + EXPECT_EQ(queue.Pop().ToString(), second.ToString()); EXPECT_TRUE(queue.IsEmpty()); } @@ -530,31 +605,40 @@ TEST_F(DnsMasterTest, PriorityQueueReorderTest) { DnsMaster::HostNameQueue queue; // Push all the low priority items. + net::HostPortPair low1("low1", 80), + low2("low2", 80), + low3("low3", 443), + low4("low4", 80), + low5("low5", 80), + hi1("hi1", 80), + hi2("hi2", 80), + hi3("hi3", 80); + EXPECT_TRUE(queue.IsEmpty()); - queue.Push("scan", DnsHostInfo::PAGE_SCAN_MOTIVATED); - queue.Push("unit", DnsHostInfo::UNIT_TEST_MOTIVATED); - queue.Push("lmax", DnsHostInfo::LINKED_MAX_MOTIVATED); - queue.Push("omni", DnsHostInfo::OMNIBOX_MOTIVATED); - queue.Push("startup", DnsHostInfo::STARTUP_LIST_MOTIVATED); - queue.Push("omni", DnsHostInfo::OMNIBOX_MOTIVATED); + queue.Push(low1, DnsHostInfo::PAGE_SCAN_MOTIVATED); + queue.Push(low2, DnsHostInfo::UNIT_TEST_MOTIVATED); + queue.Push(low3, DnsHostInfo::LINKED_MAX_MOTIVATED); + queue.Push(low4, DnsHostInfo::OMNIBOX_MOTIVATED); + queue.Push(low5, DnsHostInfo::STARTUP_LIST_MOTIVATED); + queue.Push(low4, DnsHostInfo::OMNIBOX_MOTIVATED); // Push all the high prority items - queue.Push("learned", DnsHostInfo::LEARNED_REFERAL_MOTIVATED); - queue.Push("refer", DnsHostInfo::STATIC_REFERAL_MOTIVATED); - queue.Push("mouse", DnsHostInfo::MOUSE_OVER_MOTIVATED); + queue.Push(hi1, DnsHostInfo::LEARNED_REFERAL_MOTIVATED); + queue.Push(hi2, DnsHostInfo::STATIC_REFERAL_MOTIVATED); + queue.Push(hi3, DnsHostInfo::MOUSE_OVER_MOTIVATED); // Check that high priority stuff comes out first, and in FIFO order. - EXPECT_EQ(queue.Pop(), "learned"); - EXPECT_EQ(queue.Pop(), "refer"); - EXPECT_EQ(queue.Pop(), "mouse"); + EXPECT_EQ(queue.Pop().ToString(), hi1.ToString()); + EXPECT_EQ(queue.Pop().ToString(), hi2.ToString()); + EXPECT_EQ(queue.Pop().ToString(), hi3.ToString()); // ...and then low priority strings. - EXPECT_EQ(queue.Pop(), "scan"); - EXPECT_EQ(queue.Pop(), "unit"); - EXPECT_EQ(queue.Pop(), "lmax"); - EXPECT_EQ(queue.Pop(), "omni"); - EXPECT_EQ(queue.Pop(), "startup"); - EXPECT_EQ(queue.Pop(), "omni"); + EXPECT_EQ(queue.Pop().ToString(), low1.ToString()); + EXPECT_EQ(queue.Pop().ToString(), low2.ToString()); + EXPECT_EQ(queue.Pop().ToString(), low3.ToString()); + EXPECT_EQ(queue.Pop().ToString(), low4.ToString()); + EXPECT_EQ(queue.Pop().ToString(), low5.ToString()); + EXPECT_EQ(queue.Pop().ToString(), low4.ToString()); EXPECT_TRUE(queue.IsEmpty()); } diff --git a/chrome/browser/net/preconnect.cc b/chrome/browser/net/preconnect.cc new file mode 100644 index 0000000..ff200de --- /dev/null +++ b/chrome/browser/net/preconnect.cc @@ -0,0 +1,67 @@ +// Copyright (c) 2006-2008 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/net/preconnect.h" + +#include "base/logging.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/chrome_thread.h" +#include "chrome/common/net/url_request_context_getter.h" +#include "net/http/http_network_session.h" +#include "net/http/http_transaction_factory.h" +#include "net/url_request/url_request_context.h" + +namespace chrome_browser_net { + +// We will deliberately leak this singular instance, which is used only for +// callbacks. +// static +Preconnect* Preconnect::callback_instance_; + +// static +bool Preconnect::PreconnectOnUIThread(const net::HostPortPair& hostport) { + // Try to do connection warming for this search provider. + URLRequestContextGetter* getter = Profile::GetDefaultRequestContext(); + if (!getter) + return false; + // Prewarm connection to Search URL. + ChromeThread::PostTask( + ChromeThread::IO, + FROM_HERE, + NewRunnableFunction(Preconnect::PreconnectOnIOThread, hostport)); + return true; +} + +// static +void Preconnect::PreconnectOnIOThread(const net::HostPortPair& hostport) { + URLRequestContextGetter* getter = Profile::GetDefaultRequestContext(); + if (!getter) + return; + if (!ChromeThread::CurrentlyOn(ChromeThread::IO)) { + LOG(DFATAL) << "This must be run only on the IO thread."; + return; + } + URLRequestContext* context = getter->GetURLRequestContext(); + net::HttpTransactionFactory* factory = context->http_transaction_factory(); + net::HttpNetworkSession* session = factory->GetSession(); + scoped_refptr<net::TCPClientSocketPool> pool = session->tcp_socket_pool(); + + net::TCPSocketParams params(hostport.host, hostport.port, net::LOW, + GURL(), false); + + net::ClientSocketHandle handle; + if (!callback_instance_) + callback_instance_ = new Preconnect; + + // TODO(jar): This does not handle proxies currently. + handle.Init(hostport.ToString() , params, net::LOWEST, + callback_instance_, pool, net::BoundNetLog()); + handle.Reset(); +} + +void Preconnect::RunWithParams(const Tuple1<int>& params) { + // This will rarely be called, as we reset the connection just after creating. + NOTREACHED(); +} +} // chrome_browser_net diff --git a/chrome/browser/net/preconnect.h b/chrome/browser/net/preconnect.h new file mode 100644 index 0000000..d4fb0dd --- /dev/null +++ b/chrome/browser/net/preconnect.h @@ -0,0 +1,40 @@ +// Copyright (c) 2006-2010 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. + +// A Preconnect instance maintains state while a TCP/IP connection is made, and +// and then released into the pool of available connections for future use. + +#ifndef CHROME_BROWSER_NET_PRECONNECT_H_ +#define CHROME_BROWSER_NET_PRECONNECT_H_ + +#include "base/ref_counted.h" +#include "net/base/completion_callback.h" +#include "net/base/host_port_pair.h" +#include "net/socket/client_socket_handle.h" +#include "net/socket/tcp_client_socket_pool.h" +#include "net/url_request/url_request_context.h" + +namespace chrome_browser_net { + +class Preconnect : public net::CompletionCallback { + public: + static bool PreconnectOnUIThread(const net::HostPortPair& hostport); + + static void PreconnectOnIOThread(const net::HostPortPair& hostport); + + private: + Preconnect() {} + + // Supply an instance that could have been used in an IO callback, but will + // never actually be used (because we reset the connection so quickly). + static Preconnect* callback_instance_; + + // IO Callback which whould be performed when the connection is established. + virtual void RunWithParams(const Tuple1<int>& params); + + DISALLOW_COPY_AND_ASSIGN(Preconnect); +}; +} // chrome_browser_net + +#endif // CHROME_BROWSER_NET_PRECONNECT_H_ diff --git a/chrome/browser/net/referrer.cc b/chrome/browser/net/referrer.cc index bf171e5..736790a 100644 --- a/chrome/browser/net/referrer.cc +++ b/chrome/browser/net/referrer.cc @@ -4,69 +4,119 @@ #include "chrome/browser/net/referrer.h" +#include <limits.h> + #include "base/logging.h" namespace chrome_browser_net { -void Referrer::SuggestHost(const std::string& host) { +//------------------------------------------------------------------------------ +// Smoothing parameter for updating subresource_use_rate_. + +// We always combine our old expected value, weighted by some factor, with the +// new expected value Enew. The new "expected value" is the number of actual +// connections made due to the curernt navigations. +// This means the formula (in a concise form) is: +// Eupdated = Eold * W + Enew * (1 - W) +// That means that IF we end up needing to connect, we should apply the formula: +// Pupdated = Pold * W + Enew * (1 - W) +// If we visit the containing url, but don't end up needing a connection: +// Pupdated = Pold * W +// To achive the above upating algorithm, we end up doing the multiplication +// by W every time we contemplate doing a preconneciton (i.e., when we navigate +// to the containing URL, and consider doing a preconnection), and then IFF we +// learn that we really needed a connection to the subresource, we complete the +// above algorithm by adding the (1 - W) for each connection we make. + +// We weight the new expected value by a factor which is in the range of 0.0 to +// 1.0. +static const double kWeightingForOldExpectedValue = 0.66; + +// The expected value needed before we actually do a preconnection. +static const double kPreconnectWorthyExpectedValue = 0.7; + +// The expected value that we'll need a preconnection when we first see the +// subresource getting fetched. Very conservative is 0.0, which will mean that +// we have to wait for a while before using preconnection... but we do persist +// results, so we'll have the learned answer in the long run. +static const double kInitialExpectedValue = 0.0; + +// static +bool Referrer::use_preconnect_valuations_ = false; + +void Referrer::SuggestHost(const net::HostPortPair& hostport) { // Limit how large our list can get, in case we make mistakes about what // hostnames are in sub-resources (example: Some advertisments have a link to // the ad agency, and then provide a "surprising" redirect to the advertised // entity, which then (mistakenly) appears to be a subresource on the page // hosting the ad). // TODO(jar): Do experiments to optimize the max count of suggestions. - static const size_t kMaxSuggestions = 8; + static const size_t kMaxSuggestions = 10; - if (host.empty()) + if (hostport.host.empty()) // Is this really needed???? + return; + SubresourceMap::iterator it = find(hostport); + if (it != end()) { + it->second.SubresourceIsNeeded(); return; + } + if (kMaxSuggestions <= size()) { DeleteLeastUseful(); DCHECK(kMaxSuggestions > size()); } - // Add in the new suggestion. - (*this)[host]; + (*this)[hostport].SubresourceIsNeeded(); } void Referrer::DeleteLeastUseful() { - std::string least_useful_name; + // Find the item with the lowest value. Most important is preconnection_rate, + // next is latency savings, and last is lifetime (age). + net::HostPortPair least_useful_hostport; + double lowest_rate_seen = 0.0; // We use longs for durations because we will use multiplication on them. - int64 least_useful_latency = 0; // Duration in milliseconds. + int64 lowest_latency_seen = 0; // Duration in milliseconds. int64 least_useful_lifetime = 0; // Duration in milliseconds. const base::Time kNow(base::Time::Now()); // Avoid multiple calls. - for (HostNameMap::iterator it = begin(); it != end(); ++it) { + for (SubresourceMap::iterator it = begin(); it != end(); ++it) { int64 lifetime = (kNow - it->second.birth_time()).InMilliseconds(); int64 latency = it->second.latency().InMilliseconds(); - if (!least_useful_name.empty()) { - if (!latency && !least_useful_latency) { + double rate = it->second.subresource_use_rate(); + if (!least_useful_hostport.host.empty()) { + if (rate > lowest_rate_seen) + continue; + if (!latency && !lowest_latency_seen) { // Older name is less useful. if (lifetime <= least_useful_lifetime) continue; } else { - // Compare the ratios latency/lifetime vs. - // least_useful_latency/least_useful_lifetime by cross multiplying (to - // avoid integer division hassles). Overflow's won't happen until - // both latency and lifetime pass about 49 days. - if (latency * least_useful_lifetime >= - least_useful_latency * lifetime) { + // Compare the ratios: + // latency/lifetime + // vs. + // lowest_latency_seen/least_useful_lifetime + // by cross multiplying (to avoid integer division hassles). Overflow's + // won't happen until both latency and lifetime pass about 49 days. + if (latency * least_useful_lifetime > + lowest_latency_seen * lifetime) { continue; } } } - least_useful_name = it->first; - least_useful_latency = latency; + least_useful_hostport = it->first; + lowest_rate_seen = rate; + lowest_latency_seen = latency; least_useful_lifetime = lifetime; } - erase(least_useful_name); - // Note: there is a small chance that we will discard a least_useful_name + erase(least_useful_hostport); + // Note: there is a small chance that we will discard a least_useful_hostport // that is currently being prefetched because it *was* in this referer list. // In that case, when a benefit appears in AccrueValue() below, we are careful // to check before accessing the member. } void Referrer::AccrueValue(const base::TimeDelta& delta, - const std::string& host) { - HostNameMap::iterator it = find(host); + const net::HostPortPair& hostport) { + SubresourceMap::iterator it = find(hostport); // Be careful that we weren't evicted from this referrer in DeleteLeastUseful. if (it != end()) it->second.AccrueValue(delta); @@ -74,7 +124,7 @@ void Referrer::AccrueValue(const base::TimeDelta& delta, bool Referrer::Trim() { bool has_some_latency_left = false; - for (HostNameMap::iterator it = begin(); it != end(); ++it) + for (SubresourceMap::iterator it = begin(); it != end(); ++it) if (it->second.Trim()) has_some_latency_left = true; return has_some_latency_left; @@ -83,7 +133,8 @@ bool Referrer::Trim() { bool ReferrerValue::Trim() { int64 latency_ms = latency_.InMilliseconds() / 2; latency_ = base::TimeDelta::FromMilliseconds(latency_ms); - return latency_ms > 0; + return latency_ms > 0 || + subresource_use_rate_ > kPreconnectWorthyExpectedValue / 2; } @@ -91,42 +142,85 @@ void Referrer::Deserialize(const Value& value) { if (value.GetType() != Value::TYPE_LIST) return; const ListValue* subresource_list(static_cast<const ListValue*>(&value)); - for (size_t index = 0; index + 1 < subresource_list->GetSize(); index += 2) { + size_t index = 0; // Bounds checking is done by subresource_list->Get*(). + while (true) { + int port; + if (!subresource_list->GetInteger(index++, &port)) + return; std::string host; - if (!subresource_list->GetString(index, &host)) + if (!subresource_list->GetString(index++, &host)) return; int latency_ms; - if (!subresource_list->GetInteger(index + 1, &latency_ms)) + if (!subresource_list->GetInteger(index++, &latency_ms)) return; + double rate; + if (!subresource_list->GetReal(index++, &rate)) + return; + + net::HostPortPair hostport(host, port); base::TimeDelta latency = base::TimeDelta::FromMilliseconds(latency_ms); // TODO(jar): We could be more direct, and change birth date or similar to // show that this is a resurrected value we're adding in. I'm not yet sure // of how best to optimize the learning and pruning (Trim) algorithm at this // level, so for now, we just suggest subresources, which leaves them all // with the same birth date (typically start of process). - SuggestHost(host); - AccrueValue(latency, host); + SuggestHost(hostport); + AccrueValue(latency, hostport); + (*this)[hostport].SetSubresourceUseRate(rate); } } Value* Referrer::Serialize() const { ListValue* subresource_list(new ListValue); for (const_iterator it = begin(); it != end(); ++it) { - StringValue* host(new StringValue(it->first)); + FundamentalValue* port(new FundamentalValue(it->first.port)); + StringValue* host(new StringValue(it->first.host)); int latency_integer = static_cast<int>(it->second.latency(). InMilliseconds()); // Watch out for overflow in the above static_cast! Check to see if we went // negative, and just use a "big" value. The value seems unimportant once // we get to such high latencies. Probable cause of high latency is a bug // in other code, so also do a DCHECK. - DCHECK(latency_integer >= 0); + DCHECK_GE(latency_integer, 0); if (latency_integer < 0) latency_integer = INT_MAX; FundamentalValue* latency(new FundamentalValue(latency_integer)); + FundamentalValue* rate(new FundamentalValue( + it->second.subresource_use_rate())); + + subresource_list->Append(port); subresource_list->Append(host); subresource_list->Append(latency); + subresource_list->Append(rate); } return subresource_list; } +//------------------------------------------------------------------------------ + +ReferrerValue::ReferrerValue() + : birth_time_(base::Time::Now()), + navigation_count_(0), + preconnection_count_(0), + subresource_use_rate_(kInitialExpectedValue) { +} + +void ReferrerValue::SubresourceIsNeeded() { + DCHECK_GE(kWeightingForOldExpectedValue, 0); + DCHECK_LE(kWeightingForOldExpectedValue, 1.0); + ++navigation_count_; + subresource_use_rate_ += 1 - kWeightingForOldExpectedValue; +} + +bool ReferrerValue::IsPreconnectWorthDoing() { + bool preconnecting = kPreconnectWorthyExpectedValue < subresource_use_rate_; + if (preconnecting) + ++preconnection_count_; + subresource_use_rate_ *= kWeightingForOldExpectedValue; + // Note: the use rate is temporarilly possibly incorect, as we need to find + // out if we really end up connecting. This will happen in a few hundred + // milliseconds (when content arrives, etc.). + return preconnecting; +} + } // namespace chrome_browser_net diff --git a/chrome/browser/net/referrer.h b/chrome/browser/net/referrer.h index 8c252b6..bef13a1 100644 --- a/chrome/browser/net/referrer.h +++ b/chrome/browser/net/referrer.h @@ -20,36 +20,61 @@ #include "base/basictypes.h" #include "base/time.h" #include "base/values.h" -#include "googleurl/src/gurl.h" +#include "net/base/host_port_pair.h" namespace chrome_browser_net { //------------------------------------------------------------------------------ // For each hostname in a Referrer, we have a ReferrerValue. It indicates -// exactly how much value (re: latency reduction) has resulted from having this -// entry. +// exactly how much value (re: latency reduction, or connection use) has +// resulted from having this entry. class ReferrerValue { public: - ReferrerValue() : birth_time_(base::Time::Now()) {} + ReferrerValue(); + + // Used during deserialization. + void SetSubresourceUseRate(double rate) { subresource_use_rate_ = rate; } base::TimeDelta latency() const { return latency_; } base::Time birth_time() const { return birth_time_; } void AccrueValue(const base::TimeDelta& delta) { latency_ += delta; } + // Record the fact that we navigated to the associated subresource URL. + void SubresourceIsNeeded(); + + // Evaluate if it is worth making this preconnection, and return true if it + // seems worthwhile. As a side effect, we also tally the proconnection for + // statistical purposes only. + bool IsPreconnectWorthDoing(); + + int64 navigation_count() const { return navigation_count_; } + int64 preconnection_count() const { return preconnection_count_; } + double subresource_use_rate() const { return subresource_use_rate_; } + // Reduce the latency figure by a factor of 2, and return true if any latency // remains. bool Trim(); private: - base::TimeDelta latency_; // Accumulated latency savings. + base::TimeDelta latency_; // Accumulated DNS resolution latency savings. const base::Time birth_time_; + + // The number of times this item was navigated to with the fixed referrer. + int64 navigation_count_; + + // The number of times this item was preconnected as a consequence of its + // referrer. + int64 preconnection_count_; + + // A smoothed estimate of the probability that a connection will be needed. + double subresource_use_rate_; }; //------------------------------------------------------------------------------ // A list of domain names to pre-resolve. The names are the keys to this map, // and the values indicate the amount of benefit derived from having each name // around. -typedef std::map<std::string, ReferrerValue> HostNameMap; +typedef std::map<net::HostPortPair, ReferrerValue> SubresourceMap; //------------------------------------------------------------------------------ // There is one Referrer instance for each hostname that has acted as an HTTP @@ -58,18 +83,23 @@ typedef std::map<std::string, ReferrerValue> HostNameMap; // was probably needed as a subresource of a page, and was not otherwise // predictable until the content with the reference arrived). Most typically, // an outer page was a page fetched by the user, and this instance lists names -// in HostNameMap which are subresources and that were needed to complete the +// in SubresourceMap which are subresources and that were needed to complete the // rendering of the outer page. -class Referrer : public HostNameMap { +class Referrer : public SubresourceMap { public: - // Add the indicated host to the list of hosts that are resolved via DNS when - // the user navigates to this referrer. Note that if the list is long, an - // entry may be discarded to make room for this insertion. - void SuggestHost(const std::string& host); + Referrer() : use_count_(1) {} + void IncrementUseCount() { ++use_count_; } + int64 use_count() const { return use_count_; } + + // Add the indicated host/port to the list of hosts that are resolved via DNS + // when the user navigates to this referrer. Note that if the list is long, + // an entry may be discarded to make room for this insertion. + void SuggestHost(const net::HostPortPair& hostport); - // Record additional usefulness of having this host name in the list. + // Record additional usefulness of having this host/port name in the list. // Value is expressed as positive latency of amount delta. - void AccrueValue(const base::TimeDelta& delta, const std::string& host); + void AccrueValue(const base::TimeDelta& delta, + const net::HostPortPair& hostport); // Trim the Referrer, by first diminishing (scaling down) the latency for each // ReferredValue. @@ -80,6 +110,10 @@ class Referrer : public HostNameMap { Value* Serialize() const; void Deserialize(const Value& referrers); + static void SetUsePreconnectValuations(bool dns) { + use_preconnect_valuations_ = dns; + } + private: // Helper function for pruning list. Metric for usefulness is "large accrued // value," in the form of latency_ savings associated with a host name. We @@ -89,6 +123,13 @@ class Referrer : public HostNameMap { // accrue savings as quickly. void DeleteLeastUseful(); + // The number of times this referer had its subresources scaned for possible + // preconnection or DNS preresolution. + int64 use_count_; + + // Select between DNS prefetch latency savings, or preconnection valuations + // for a metric to decide which referers to save. + static bool use_preconnect_valuations_; // We put these into a std::map<>, so we need copy constructors. // DISALLOW_COPY_AND_ASSIGN(Referrer); diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 55a75ce..def903d 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -1644,6 +1644,8 @@ 'browser/net/metadata_url_request.h', 'browser/net/passive_log_collector.cc', 'browser/net/passive_log_collector.h', + 'browser/net/preconnect.cc', + 'browser/net/preconnect.h', 'browser/net/referrer.cc', 'browser/net/referrer.h', 'browser/net/resolve_proxy_msg_helper.cc', diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc index c9028cf..2b14989 100644 --- a/chrome/common/chrome_switches.cc +++ b/chrome/common/chrome_switches.cc @@ -321,6 +321,9 @@ const char kEnableNaCl[] = "enable-nacl"; // Enable Native Web Worker support. const char kEnableNativeWebWorkers[] = "enable-native-web-workers"; +// Enable speculative TCP/IP preconnection. +const char kEnablePreconnect[] = "enable-preconnect"; + // Enable Privacy Blacklists. const char kEnablePrivacyBlacklists[] = "enable-privacy-blacklists"; diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h index bc82fd5..d9ac1e9 100644 --- a/chrome/common/chrome_switches.h +++ b/chrome/common/chrome_switches.h @@ -105,6 +105,7 @@ extern const char kEnableLogging[]; extern const char kEnableMonitorProfile[]; extern const char kEnableNaCl[]; extern const char kEnableNativeWebWorkers[]; +extern const char kEnablePreconnect[]; extern const char kEnablePrivacyBlacklists[]; extern const char kEnableRendererAccessibility[]; extern const char kEnableStatsTable[]; diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc index 4d73e6a..b7dc105 100644 --- a/chrome/common/pref_names.cc +++ b/chrome/common/pref_names.cc @@ -164,8 +164,8 @@ const wchar_t kDnsPrefetchingEnabled[] = L"dns_prefetching.enabled"; const wchar_t kDnsStartupPrefetchList[] = L"StartupDNSPrefetchList"; // A list of host names used to fetch web pages, and their commonly used -// sub-resource hostnames (and expected latency benefits from pre-resolving such -// sub-resource hostnames). +// sub-resource hostnames (and expected latency benefits from pre-resolving, or +// preconnecting to, such sub-resource hostnames). // This list is adaptively grown and pruned. const wchar_t kDnsHostReferralList[] = L"HostReferralList"; |