OLD | NEW |
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/net/predictor.h" | 5 #include "chrome/browser/net/predictor.h" |
6 | 6 |
7 #include <algorithm> | 7 #include <algorithm> |
8 #include <cmath> | 8 #include <cmath> |
9 #include <set> | 9 #include <set> |
10 #include <sstream> | 10 #include <sstream> |
11 | 11 |
| 12 #include "base/bind.h" |
| 13 #include "base/command_line.h" |
12 #include "base/compiler_specific.h" | 14 #include "base/compiler_specific.h" |
13 #include "base/metrics/histogram.h" | 15 #include "base/metrics/histogram.h" |
| 16 #include "base/stl_util.h" |
14 #include "base/stringprintf.h" | 17 #include "base/stringprintf.h" |
| 18 #include "base/synchronization/waitable_event.h" |
15 #include "base/time.h" | 19 #include "base/time.h" |
16 #include "base/values.h" | 20 #include "base/values.h" |
| 21 #include "chrome/browser/io_thread.h" |
17 #include "chrome/browser/net/preconnect.h" | 22 #include "chrome/browser/net/preconnect.h" |
| 23 #include "chrome/browser/prefs/browser_prefs.h" |
| 24 #include "chrome/browser/prefs/pref_service.h" |
| 25 #include "chrome/browser/prefs/scoped_user_pref_update.h" |
| 26 #include "chrome/browser/prefs/session_startup_pref.h" |
| 27 #include "chrome/common/chrome_switches.h" |
| 28 #include "chrome/common/pref_names.h" |
18 #include "content/browser/browser_thread.h" | 29 #include "content/browser/browser_thread.h" |
19 #include "net/base/address_list.h" | 30 #include "net/base/address_list.h" |
20 #include "net/base/completion_callback.h" | 31 #include "net/base/completion_callback.h" |
21 #include "net/base/host_port_pair.h" | 32 #include "net/base/host_port_pair.h" |
22 #include "net/base/host_resolver.h" | 33 #include "net/base/host_resolver.h" |
23 #include "net/base/net_errors.h" | 34 #include "net/base/net_errors.h" |
24 #include "net/base/net_log.h" | 35 #include "net/base/net_log.h" |
25 #include "net/base/single_request_host_resolver.h" | 36 #include "net/base/single_request_host_resolver.h" |
26 | 37 |
27 using base::TimeDelta; | 38 using base::TimeDelta; |
(...skipping 13 matching lines...) Expand all Loading... |
41 // connections. Once this falls below kDiscardableExpectedValue the referrer | 52 // connections. Once this falls below kDiscardableExpectedValue the referrer |
42 // will be discarded. | 53 // will be discarded. |
43 // TODO(jar): Measure size of referrer lists in the field. Consider an adaptive | 54 // TODO(jar): Measure size of referrer lists in the field. Consider an adaptive |
44 // system that uses a higher trim ratio when the list is large. | 55 // system that uses a higher trim ratio when the list is large. |
45 // static | 56 // static |
46 const double Predictor::kReferrerTrimRatio = 0.97153; | 57 const double Predictor::kReferrerTrimRatio = 0.97153; |
47 const TimeDelta Predictor::kDurationBetweenTrimmings = TimeDelta::FromHours(1); | 58 const TimeDelta Predictor::kDurationBetweenTrimmings = TimeDelta::FromHours(1); |
48 const TimeDelta Predictor::kDurationBetweenTrimmingIncrements = | 59 const TimeDelta Predictor::kDurationBetweenTrimmingIncrements = |
49 TimeDelta::FromSeconds(15); | 60 TimeDelta::FromSeconds(15); |
50 const size_t Predictor::kUrlsTrimmedPerIncrement = 5u; | 61 const size_t Predictor::kUrlsTrimmedPerIncrement = 5u; |
| 62 const size_t Predictor::kMaxSpeculativeParallelResolves = 3; |
| 63 // To control our congestion avoidance system, which discards a queue when |
| 64 // resolutions are "taking too long," we need an expected resolution time. |
| 65 // Common average is in the range of 300-500ms. |
| 66 const int kExpectedResolutionTimeMs = 500; |
| 67 const int Predictor::kTypicalSpeculativeGroupSize = 8; |
| 68 const int Predictor::kMaxSpeculativeResolveQueueDelayMs = |
| 69 (kExpectedResolutionTimeMs * Predictor::kTypicalSpeculativeGroupSize) / |
| 70 Predictor::kMaxSpeculativeParallelResolves; |
| 71 |
| 72 static int g_max_queueing_delay_ms = 0; |
| 73 static size_t g_max_parallel_resolves = 0u; |
| 74 |
| 75 // A version number for prefs that are saved. This should be incremented when |
| 76 // we change the format so that we discard old data. |
| 77 static const int kPredictorStartupFormatVersion = 1; |
51 | 78 |
52 class Predictor::LookupRequest { | 79 class Predictor::LookupRequest { |
53 public: | 80 public: |
54 LookupRequest(Predictor* predictor, | 81 LookupRequest(Predictor* predictor, |
55 net::HostResolver* host_resolver, | 82 net::HostResolver* host_resolver, |
56 const GURL& url) | 83 const GURL& url) |
57 : ALLOW_THIS_IN_INITIALIZER_LIST( | 84 : ALLOW_THIS_IN_INITIALIZER_LIST( |
58 net_callback_(this, &LookupRequest::OnLookupFinished)), | 85 net_callback_(this, &LookupRequest::OnLookupFinished)), |
59 predictor_(predictor), | 86 predictor_(predictor), |
60 url_(url), | 87 url_(url), |
(...skipping 26 matching lines...) Expand all Loading... |
87 | 114 |
88 Predictor* predictor_; // The predictor which started us. | 115 Predictor* predictor_; // The predictor which started us. |
89 | 116 |
90 const GURL url_; // Hostname to resolve. | 117 const GURL url_; // Hostname to resolve. |
91 net::SingleRequestHostResolver resolver_; | 118 net::SingleRequestHostResolver resolver_; |
92 net::AddressList addresses_; | 119 net::AddressList addresses_; |
93 | 120 |
94 DISALLOW_COPY_AND_ASSIGN(LookupRequest); | 121 DISALLOW_COPY_AND_ASSIGN(LookupRequest); |
95 }; | 122 }; |
96 | 123 |
97 Predictor::Predictor(net::HostResolver* host_resolver, | 124 Predictor::Predictor(bool preconnect_enabled) |
98 TimeDelta max_dns_queue_delay, | 125 : initial_observer_(NULL), |
99 size_t max_concurrent, | 126 predictor_enabled_(true), |
100 bool preconnect_enabled) | 127 peak_pending_lookups_(0), |
101 : peak_pending_lookups_(0), | |
102 shutdown_(false), | 128 shutdown_(false), |
103 max_concurrent_dns_lookups_(max_concurrent), | 129 max_concurrent_dns_lookups_(g_max_parallel_resolves), |
104 max_dns_queue_delay_(max_dns_queue_delay), | 130 max_dns_queue_delay_( |
105 host_resolver_(host_resolver), | 131 TimeDelta::FromMilliseconds(g_max_queueing_delay_ms)), |
| 132 host_resolver_(NULL), |
106 preconnect_enabled_(preconnect_enabled), | 133 preconnect_enabled_(preconnect_enabled), |
107 consecutive_omnibox_preconnect_count_(0), | 134 consecutive_omnibox_preconnect_count_(0), |
108 next_trim_time_(base::TimeTicks::Now() + kDurationBetweenTrimmings), | 135 next_trim_time_(base::TimeTicks::Now() + kDurationBetweenTrimmings) { |
109 ALLOW_THIS_IN_INITIALIZER_LIST(trim_task_factory_(this)) { | 136 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
110 } | 137 } |
111 | 138 |
112 Predictor::~Predictor() { | 139 Predictor::~Predictor() { |
| 140 // TODO(rlp): Add DCHECK for CurrentlyOn(BrowserThread::IO) when the |
| 141 // ProfileManagerTest has been updated with a mock profile. |
113 DCHECK(shutdown_); | 142 DCHECK(shutdown_); |
114 } | 143 } |
115 | 144 |
116 void Predictor::Shutdown() { | 145 // static |
117 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 146 Predictor* Predictor::CreatePredictor( |
118 DCHECK(!shutdown_); | 147 bool preconnect_enabled, bool simple_shutdown) { |
119 shutdown_ = true; | 148 if (simple_shutdown) |
120 | 149 return new SimplePredictor(preconnect_enabled); |
121 std::set<LookupRequest*>::iterator it; | 150 return new Predictor(preconnect_enabled); |
122 for (it = pending_lookups_.begin(); it != pending_lookups_.end(); ++it) | |
123 delete *it; | |
124 } | 151 } |
125 | 152 |
126 // Overloaded Resolve() to take a vector of names. | 153 void Predictor::RegisterUserPrefs(PrefService* user_prefs) { |
127 void Predictor::ResolveList(const UrlList& urls, | 154 user_prefs->RegisterListPref(prefs::kDnsPrefetchingStartupList, |
128 UrlInfo::ResolutionMotivation motivation) { | 155 PrefService::UNSYNCABLE_PREF); |
129 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 156 user_prefs->RegisterListPref(prefs::kDnsPrefetchingHostReferralList, |
130 | 157 PrefService::UNSYNCABLE_PREF); |
131 for (UrlList::const_iterator it = urls.begin(); it < urls.end(); ++it) { | |
132 AppendToResolutionQueue(*it, motivation); | |
133 } | |
134 } | 158 } |
135 | 159 |
136 // Basic Resolve() takes an invidual name, and adds it | 160 // --------------------- Start UI methods. ------------------------------------ |
137 // to the queue. | 161 |
138 void Predictor::Resolve(const GURL& url, | 162 void Predictor::InitNetworkPredictor(PrefService* user_prefs, |
139 UrlInfo::ResolutionMotivation motivation) { | 163 PrefService* local_state, |
140 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 164 IOThread* io_thread) { |
141 if (!url.has_host()) | 165 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
142 return; | 166 |
143 AppendToResolutionQueue(url, motivation); | 167 bool predictor_enabled = |
| 168 user_prefs->GetBoolean(prefs::kNetworkPredictionEnabled); |
| 169 |
| 170 // Gather the list of hostnames to prefetch on startup. |
| 171 UrlList urls = GetPredictedUrlListAtStartup(user_prefs, local_state); |
| 172 |
| 173 base::ListValue* referral_list = |
| 174 static_cast<base::ListValue*>(user_prefs->GetList( |
| 175 prefs::kDnsPrefetchingHostReferralList)->DeepCopy()); |
| 176 |
| 177 // Remove obsolete preferences from local state if necessary. |
| 178 int current_version = |
| 179 local_state->GetInteger(prefs::kMultipleProfilePrefMigration); |
| 180 if ((current_version & browser::DNS_PREFS) == 0) { |
| 181 local_state->RegisterListPref(prefs::kDnsStartupPrefetchList, |
| 182 PrefService::UNSYNCABLE_PREF); |
| 183 local_state->RegisterListPref(prefs::kDnsHostReferralList, |
| 184 PrefService::UNSYNCABLE_PREF); |
| 185 local_state->ClearPref(prefs::kDnsStartupPrefetchList); |
| 186 local_state->ClearPref(prefs::kDnsHostReferralList); |
| 187 local_state->SetInteger(prefs::kMultipleProfilePrefMigration, |
| 188 current_version | browser::DNS_PREFS); |
| 189 } |
| 190 |
| 191 BrowserThread::PostTask( |
| 192 BrowserThread::IO, |
| 193 FROM_HERE, |
| 194 base::Bind( |
| 195 &Predictor::FinalizeInitializationOnIOThread, |
| 196 base::Unretained(this), |
| 197 urls, referral_list, |
| 198 io_thread, predictor_enabled)); |
144 } | 199 } |
145 | 200 |
146 void Predictor::LearnFromNavigation(const GURL& referring_url, | |
147 const GURL& target_url) { | |
148 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
149 DCHECK_EQ(referring_url, Predictor::CanonicalizeUrl(referring_url)); | |
150 DCHECK_NE(referring_url, GURL::EmptyGURL()); | |
151 DCHECK_EQ(target_url, Predictor::CanonicalizeUrl(target_url)); | |
152 DCHECK_NE(target_url, GURL::EmptyGURL()); | |
153 | |
154 referrers_[referring_url].SuggestHost(target_url); | |
155 // Possibly do some referrer trimming. | |
156 TrimReferrers(); | |
157 } | |
158 | |
159 enum SubresourceValue { | |
160 PRECONNECTION, | |
161 PRERESOLUTION, | |
162 TOO_NEW, | |
163 SUBRESOURCE_VALUE_MAX | |
164 }; | |
165 | |
166 void Predictor::AnticipateOmniboxUrl(const GURL& url, bool preconnectable) { | 201 void Predictor::AnticipateOmniboxUrl(const GURL& url, bool preconnectable) { |
| 202 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 203 if (!predictor_enabled_) |
| 204 return; |
| 205 if (!url.is_valid() || !url.has_host()) |
| 206 return; |
167 std::string host = url.HostNoBrackets(); | 207 std::string host = url.HostNoBrackets(); |
168 bool is_new_host_request = (host != last_omnibox_host_); | 208 bool is_new_host_request = (host != last_omnibox_host_); |
169 last_omnibox_host_ = host; | 209 last_omnibox_host_ = host; |
170 | 210 |
171 UrlInfo::ResolutionMotivation motivation(UrlInfo::OMNIBOX_MOTIVATED); | 211 UrlInfo::ResolutionMotivation motivation(UrlInfo::OMNIBOX_MOTIVATED); |
172 base::TimeTicks now = base::TimeTicks::Now(); | 212 base::TimeTicks now = base::TimeTicks::Now(); |
173 | 213 |
174 if (preconnect_enabled()) { | 214 if (preconnect_enabled()) { |
175 if (preconnectable && !is_new_host_request) { | 215 if (preconnectable && !is_new_host_request) { |
176 ++consecutive_omnibox_preconnect_count_; | 216 ++consecutive_omnibox_preconnect_count_; |
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
220 const int kMinPreresolveSeconds(10); | 260 const int kMinPreresolveSeconds(10); |
221 if (kMinPreresolveSeconds > (now - last_omnibox_preresolve_).InSeconds()) | 261 if (kMinPreresolveSeconds > (now - last_omnibox_preresolve_).InSeconds()) |
222 return; | 262 return; |
223 } | 263 } |
224 last_omnibox_preresolve_ = now; | 264 last_omnibox_preresolve_ = now; |
225 | 265 |
226 // Perform at least DNS pre-resolution. | 266 // Perform at least DNS pre-resolution. |
227 BrowserThread::PostTask( | 267 BrowserThread::PostTask( |
228 BrowserThread::IO, | 268 BrowserThread::IO, |
229 FROM_HERE, | 269 FROM_HERE, |
230 NewRunnableMethod(this, &Predictor::Resolve, CanonicalizeUrl(url), | 270 base::Bind(&Predictor::Resolve, base::Unretained(this), |
231 motivation)); | 271 CanonicalizeUrl(url), motivation)); |
232 } | 272 } |
233 | 273 |
234 void Predictor::PreconnectUrlAndSubresources(const GURL& url) { | 274 void Predictor::PreconnectUrlAndSubresources(const GURL& url) { |
| 275 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 276 if (!predictor_enabled_) |
| 277 return; |
| 278 if (!url.is_valid() || !url.has_host()) |
| 279 return; |
235 if (preconnect_enabled()) { | 280 if (preconnect_enabled()) { |
236 std::string host = url.HostNoBrackets(); | 281 std::string host = url.HostNoBrackets(); |
237 UrlInfo::ResolutionMotivation motivation(UrlInfo::EARLY_LOAD_MOTIVATED); | 282 UrlInfo::ResolutionMotivation motivation(UrlInfo::EARLY_LOAD_MOTIVATED); |
238 const int kConnectionsNeeded = 1; | 283 const int kConnectionsNeeded = 1; |
239 PreconnectOnUIThread(CanonicalizeUrl(url), motivation, | 284 PreconnectOnUIThread(CanonicalizeUrl(url), motivation, |
240 kConnectionsNeeded); | 285 kConnectionsNeeded); |
241 PredictFrameSubresources(url.GetWithEmptyPath()); | 286 PredictFrameSubresources(url.GetWithEmptyPath()); |
242 } | 287 } |
243 } | 288 } |
244 | 289 |
245 void Predictor::PredictFrameSubresources(const GURL& url) { | 290 UrlList Predictor::GetPredictedUrlListAtStartup( |
246 DCHECK_EQ(url.GetWithEmptyPath(), url); | 291 PrefService* user_prefs, |
247 // Add one pass through the message loop to allow current navigation to | 292 PrefService* local_state) { |
248 // proceed. | 293 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 294 UrlList urls; |
| 295 // Recall list of URLs we learned about during last session. |
| 296 // This may catch secondary hostnames, pulled in by the homepages. It will |
| 297 // also catch more of the "primary" home pages, since that was (presumably) |
| 298 // rendered first (and will be rendered first this time too). |
| 299 const ListValue* startup_list = |
| 300 user_prefs->GetList(prefs::kDnsPrefetchingStartupList); |
| 301 |
| 302 if (startup_list) { |
| 303 base::ListValue::const_iterator it = startup_list->begin(); |
| 304 int format_version = -1; |
| 305 if (it != startup_list->end() && |
| 306 (*it)->GetAsInteger(&format_version) && |
| 307 format_version == kPredictorStartupFormatVersion) { |
| 308 ++it; |
| 309 for (; it != startup_list->end(); ++it) { |
| 310 std::string url_spec; |
| 311 if (!(*it)->GetAsString(&url_spec)) { |
| 312 LOG(DFATAL); |
| 313 break; // Format incompatibility. |
| 314 } |
| 315 GURL url(url_spec); |
| 316 if (!url.has_host() || !url.has_scheme()) { |
| 317 LOG(DFATAL); |
| 318 break; // Format incompatibility. |
| 319 } |
| 320 |
| 321 urls.push_back(url); |
| 322 } |
| 323 } |
| 324 } |
| 325 |
| 326 // Prepare for any static home page(s) the user has in prefs. The user may |
| 327 // have a LOT of tab's specified, so we may as well try to warm them all. |
| 328 SessionStartupPref tab_start_pref = |
| 329 SessionStartupPref::GetStartupPref(user_prefs); |
| 330 if (SessionStartupPref::URLS == tab_start_pref.type) { |
| 331 for (size_t i = 0; i < tab_start_pref.urls.size(); i++) { |
| 332 GURL gurl = tab_start_pref.urls[i]; |
| 333 if (!gurl.is_valid() || gurl.SchemeIsFile() || gurl.host().empty()) |
| 334 continue; |
| 335 if (gurl.SchemeIs("http") || gurl.SchemeIs("https")) |
| 336 urls.push_back(gurl.GetWithEmptyPath()); |
| 337 } |
| 338 } |
| 339 |
| 340 if (urls.empty()) |
| 341 urls.push_back(GURL("http://www.google.com:80")); |
| 342 |
| 343 return urls; |
| 344 } |
| 345 |
| 346 void Predictor::set_max_queueing_delay(int max_queueing_delay_ms) { |
| 347 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 348 g_max_queueing_delay_ms = max_queueing_delay_ms; |
| 349 } |
| 350 |
| 351 void Predictor::set_max_parallel_resolves(size_t max_parallel_resolves) { |
| 352 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 353 g_max_parallel_resolves = max_parallel_resolves; |
| 354 } |
| 355 |
| 356 void Predictor::ShutdownOnUIThread(PrefService* user_prefs) { |
| 357 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 358 |
| 359 SaveStateForNextStartupAndTrim(user_prefs); |
| 360 |
249 BrowserThread::PostTask( | 361 BrowserThread::PostTask( |
250 BrowserThread::IO, | 362 BrowserThread::IO, |
251 FROM_HERE, | 363 FROM_HERE, |
252 NewRunnableMethod(this, &Predictor::PrepareFrameSubresources, url)); | 364 base::Bind(&Predictor::Shutdown, base::Unretained(this))); |
253 } | 365 } |
254 | 366 |
255 void Predictor::PrepareFrameSubresources(const GURL& url) { | 367 // ---------------------- End UI methods. ------------------------------------- |
| 368 |
| 369 // --------------------- Start IO methods. ------------------------------------ |
| 370 |
| 371 void Predictor::Shutdown() { |
256 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 372 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
257 DCHECK_EQ(url.GetWithEmptyPath(), url); | 373 DCHECK(!shutdown_); |
258 Referrers::iterator it = referrers_.find(url); | 374 shutdown_ = true; |
259 if (referrers_.end() == it) { | 375 |
260 // Only when we don't know anything about this url, make 2 connections | 376 STLDeleteElements(&pending_lookups_); |
261 // available. We could do this completely via learning (by prepopulating | 377 } |
262 // the referrer_ list with this expected value), but it would swell the | 378 |
263 // size of the list with all the "Leaf" nodes in the tree (nodes that don't | 379 void Predictor::DiscardAllResults() { |
264 // load any subresources). If we learn about this resource, we will instead | 380 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
265 // provide a more carefully estimated preconnection count. | 381 // Delete anything listed so far in this session that shows in about:dns. |
266 if (preconnect_enabled_) | 382 referrers_.clear(); |
267 PreconnectOnIOThread(url, UrlInfo::SELF_REFERAL_MOTIVATED, 2); | 383 |
| 384 |
| 385 // Try to delete anything in our work queue. |
| 386 while (!work_queue_.IsEmpty()) { |
| 387 // Emulate processing cycle as though host was not found. |
| 388 GURL url = work_queue_.Pop(); |
| 389 UrlInfo* info = &results_[url]; |
| 390 DCHECK(info->HasUrl(url)); |
| 391 info->SetAssignedState(); |
| 392 info->SetNoSuchNameState(); |
| 393 } |
| 394 // Now every result_ is either resolved, or is being resolved |
| 395 // (see LookupRequest). |
| 396 |
| 397 // Step through result_, recording names of all hosts that can't be erased. |
| 398 // We can't erase anything being worked on. |
| 399 Results assignees; |
| 400 for (Results::iterator it = results_.begin(); results_.end() != it; ++it) { |
| 401 GURL url(it->first); |
| 402 UrlInfo* info = &it->second; |
| 403 DCHECK(info->HasUrl(url)); |
| 404 if (info->is_assigned()) { |
| 405 info->SetPendingDeleteState(); |
| 406 assignees[url] = *info; |
| 407 } |
| 408 } |
| 409 DCHECK_LE(assignees.size(), max_concurrent_dns_lookups_); |
| 410 results_.clear(); |
| 411 // Put back in the names being worked on. |
| 412 for (Results::iterator it = assignees.begin(); assignees.end() != it; ++it) { |
| 413 DCHECK(it->second.is_marked_to_delete()); |
| 414 results_[it->first] = it->second; |
| 415 } |
| 416 } |
| 417 |
| 418 // Overloaded Resolve() to take a vector of names. |
| 419 void Predictor::ResolveList(const UrlList& urls, |
| 420 UrlInfo::ResolutionMotivation motivation) { |
| 421 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 422 |
| 423 for (UrlList::const_iterator it = urls.begin(); it < urls.end(); ++it) { |
| 424 AppendToResolutionQueue(*it, motivation); |
| 425 } |
| 426 } |
| 427 |
| 428 // Basic Resolve() takes an invidual name, and adds it |
| 429 // to the queue. |
| 430 void Predictor::Resolve(const GURL& url, |
| 431 UrlInfo::ResolutionMotivation motivation) { |
| 432 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 433 if (!url.has_host()) |
268 return; | 434 return; |
| 435 AppendToResolutionQueue(url, motivation); |
| 436 } |
| 437 |
| 438 void Predictor::LearnFromNavigation(const GURL& referring_url, |
| 439 const GURL& target_url) { |
| 440 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 441 if (!predictor_enabled_) |
| 442 return; |
| 443 DCHECK_EQ(referring_url, Predictor::CanonicalizeUrl(referring_url)); |
| 444 DCHECK_NE(referring_url, GURL::EmptyGURL()); |
| 445 DCHECK_EQ(target_url, Predictor::CanonicalizeUrl(target_url)); |
| 446 DCHECK_NE(target_url, GURL::EmptyGURL()); |
| 447 |
| 448 referrers_[referring_url].SuggestHost(target_url); |
| 449 // Possibly do some referrer trimming. |
| 450 TrimReferrers(); |
| 451 } |
| 452 |
| 453 //----------------------------------------------------------------------------- |
| 454 // This section supports the about:dns page. |
| 455 |
| 456 void Predictor::PredictorGetHtmlInfo(Predictor* predictor, |
| 457 std::string* output) { |
| 458 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 459 |
| 460 output->append("<html><head><title>About DNS</title>" |
| 461 // We'd like the following no-cache... but it doesn't work. |
| 462 // "<META HTTP-EQUIV=\"Pragma\" CONTENT=\"no-cache\">" |
| 463 "</head><body>"); |
| 464 if (predictor && predictor->predictor_enabled()) { |
| 465 predictor->GetHtmlInfo(output); |
| 466 } else { |
| 467 output->append("DNS pre-resolution and TCP pre-connection is disabled."); |
269 } | 468 } |
270 | 469 output->append("</body></html>"); |
271 Referrer* referrer = &(it->second); | |
272 referrer->IncrementUseCount(); | |
273 const UrlInfo::ResolutionMotivation motivation = | |
274 UrlInfo::LEARNED_REFERAL_MOTIVATED; | |
275 for (Referrer::iterator future_url = referrer->begin(); | |
276 future_url != referrer->end(); ++future_url) { | |
277 SubresourceValue evalution(TOO_NEW); | |
278 double connection_expectation = future_url->second.subresource_use_rate(); | |
279 UMA_HISTOGRAM_CUSTOM_COUNTS("Net.PreconnectSubresourceExpectation", | |
280 static_cast<int>(connection_expectation * 100), | |
281 10, 5000, 50); | |
282 future_url->second.ReferrerWasObserved(); | |
283 if (preconnect_enabled_ && | |
284 connection_expectation > kPreconnectWorthyExpectedValue) { | |
285 evalution = PRECONNECTION; | |
286 future_url->second.IncrementPreconnectionCount(); | |
287 int count = static_cast<int>(std::ceil(connection_expectation)); | |
288 if (url.host() == future_url->first.host()) | |
289 ++count; | |
290 PreconnectOnIOThread(future_url->first, motivation, count); | |
291 } else if (connection_expectation > kDNSPreresolutionWorthyExpectedValue) { | |
292 evalution = PRERESOLUTION; | |
293 future_url->second.preresolution_increment(); | |
294 UrlInfo* queued_info = AppendToResolutionQueue(future_url->first, | |
295 motivation); | |
296 if (queued_info) | |
297 queued_info->SetReferringHostname(url); | |
298 } | |
299 UMA_HISTOGRAM_ENUMERATION("Net.PreconnectSubresourceEval", evalution, | |
300 SUBRESOURCE_VALUE_MAX); | |
301 } | |
302 } | 470 } |
303 | 471 |
304 // Provide sort order so all .com's are together, etc. | 472 // Provide sort order so all .com's are together, etc. |
305 struct RightToLeftStringSorter { | 473 struct RightToLeftStringSorter { |
306 bool operator()(const GURL& left, | 474 bool operator()(const GURL& left, |
307 const GURL& right) const { | 475 const GURL& right) const { |
308 return string_compare(left.host(), right.host()); | 476 return string_compare(left.host(), right.host()); |
309 } | 477 } |
310 | 478 |
311 static bool string_compare(const std::string& left_host, | 479 static bool string_compare(const std::string& left_host, |
(...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
406 static_cast<int>(future_url->second.preresolution_count()), | 574 static_cast<int>(future_url->second.preresolution_count()), |
407 static_cast<double>(future_url->second.subresource_use_rate()), | 575 static_cast<double>(future_url->second.subresource_use_rate()), |
408 future_url->first.spec().c_str()); | 576 future_url->first.spec().c_str()); |
409 } | 577 } |
410 } | 578 } |
411 output->append("</table>"); | 579 output->append("</table>"); |
412 } | 580 } |
413 | 581 |
414 void Predictor::GetHtmlInfo(std::string* output) { | 582 void Predictor::GetHtmlInfo(std::string* output) { |
415 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 583 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 584 if (initial_observer_.get()) |
| 585 initial_observer_->GetFirstResolutionsHtml(output); |
| 586 // Show list of subresource predictions and stats. |
| 587 GetHtmlReferrerLists(output); |
| 588 |
416 // Local lists for calling UrlInfo | 589 // Local lists for calling UrlInfo |
417 UrlInfo::UrlInfoTable name_not_found; | 590 UrlInfo::UrlInfoTable name_not_found; |
418 UrlInfo::UrlInfoTable name_preresolved; | 591 UrlInfo::UrlInfoTable name_preresolved; |
419 | 592 |
420 // Get copies of all useful data. | 593 // Get copies of all useful data. |
421 typedef std::map<GURL, UrlInfo, RightToLeftStringSorter> SortedUrlInfo; | 594 typedef std::map<GURL, UrlInfo, RightToLeftStringSorter> SortedUrlInfo; |
422 SortedUrlInfo snapshot; | 595 SortedUrlInfo snapshot; |
423 // UrlInfo supports value semantics, so we can do a shallow copy. | 596 // UrlInfo supports value semantics, so we can do a shallow copy. |
424 for (Results::iterator it(results_.begin()); it != results_.end(); it++) | 597 for (Results::iterator it(results_.begin()); it != results_.end(); it++) |
425 snapshot[it->first] = it->second; | 598 snapshot[it->first] = it->second; |
(...skipping 15 matching lines...) Expand all Loading... |
441 brief = true; | 614 brief = true; |
442 #endif // NDEBUG | 615 #endif // NDEBUG |
443 | 616 |
444 // Call for display of each table, along with title. | 617 // Call for display of each table, along with title. |
445 UrlInfo::GetHtmlTable(name_preresolved, | 618 UrlInfo::GetHtmlTable(name_preresolved, |
446 "Preresolution DNS records performed for ", brief, output); | 619 "Preresolution DNS records performed for ", brief, output); |
447 UrlInfo::GetHtmlTable(name_not_found, | 620 UrlInfo::GetHtmlTable(name_not_found, |
448 "Preresolving DNS records revealed non-existence for ", brief, output); | 621 "Preresolving DNS records revealed non-existence for ", brief, output); |
449 } | 622 } |
450 | 623 |
451 UrlInfo* Predictor::AppendToResolutionQueue( | 624 void Predictor::TrimReferrersNow() { |
452 const GURL& url, | 625 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 626 // Just finish up work if an incremental trim is in progress. |
| 627 if (urls_being_trimmed_.empty()) |
| 628 LoadUrlsForTrimming(); |
| 629 IncrementalTrimReferrers(true); // Do everything now. |
| 630 } |
| 631 |
| 632 void Predictor::SerializeReferrers(base::ListValue* referral_list) { |
| 633 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 634 referral_list->Clear(); |
| 635 referral_list->Append(new base::FundamentalValue(kPredictorReferrerVersion)); |
| 636 for (Referrers::const_iterator it = referrers_.begin(); |
| 637 it != referrers_.end(); ++it) { |
| 638 // Serialize the list of subresource names. |
| 639 Value* subresource_list(it->second.Serialize()); |
| 640 |
| 641 // Create a list for each referer. |
| 642 ListValue* motivator(new ListValue); |
| 643 motivator->Append(new StringValue(it->first.spec())); |
| 644 motivator->Append(subresource_list); |
| 645 |
| 646 referral_list->Append(motivator); |
| 647 } |
| 648 } |
| 649 |
| 650 void Predictor::DeserializeReferrers(const base::ListValue& referral_list) { |
| 651 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 652 int format_version = -1; |
| 653 if (referral_list.GetSize() > 0 && |
| 654 referral_list.GetInteger(0, &format_version) && |
| 655 format_version == kPredictorReferrerVersion) { |
| 656 for (size_t i = 1; i < referral_list.GetSize(); ++i) { |
| 657 base::ListValue* motivator; |
| 658 if (!referral_list.GetList(i, &motivator)) { |
| 659 NOTREACHED(); |
| 660 return; |
| 661 } |
| 662 std::string motivating_url_spec; |
| 663 if (!motivator->GetString(0, &motivating_url_spec)) { |
| 664 NOTREACHED(); |
| 665 return; |
| 666 } |
| 667 |
| 668 Value* subresource_list; |
| 669 if (!motivator->Get(1, &subresource_list)) { |
| 670 NOTREACHED(); |
| 671 return; |
| 672 } |
| 673 |
| 674 referrers_[GURL(motivating_url_spec)].Deserialize(*subresource_list); |
| 675 } |
| 676 } |
| 677 } |
| 678 |
| 679 void Predictor::DeserializeReferrersThenDelete( |
| 680 base::ListValue* referral_list) { |
| 681 DeserializeReferrers(*referral_list); |
| 682 delete referral_list; |
| 683 } |
| 684 |
| 685 void Predictor::DiscardInitialNavigationHistory() { |
| 686 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 687 if (initial_observer_.get()) |
| 688 initial_observer_->DiscardInitialNavigationHistory(); |
| 689 } |
| 690 |
| 691 void Predictor::FinalizeInitializationOnIOThread( |
| 692 const UrlList& startup_urls, |
| 693 base::ListValue* referral_list, |
| 694 IOThread* io_thread, |
| 695 bool predictor_enabled) { |
| 696 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 697 |
| 698 predictor_enabled_ = predictor_enabled; |
| 699 initial_observer_.reset(new InitialObserver()); |
| 700 host_resolver_ = io_thread->globals()->host_resolver.get(); |
| 701 |
| 702 // ScopedRunnableMethodFactory instances need to be created and destroyed |
| 703 // on the same thread. The predictor lives on the IO thread and will die |
| 704 // from there so now that we're on the IO thread we need to properly |
| 705 // initialize the ScopedrunnableMethodFactory. |
| 706 trim_task_factory_.reset(new ScopedRunnableMethodFactory<Predictor>(this)); |
| 707 |
| 708 // Prefetch these hostnames on startup. |
| 709 DnsPrefetchMotivatedList(startup_urls, UrlInfo::STARTUP_LIST_MOTIVATED); |
| 710 DeserializeReferrersThenDelete(referral_list); |
| 711 } |
| 712 |
| 713 //----------------------------------------------------------------------------- |
| 714 // This section intermingles prefetch results with actual browser HTTP |
| 715 // network activity. It supports calculating of the benefit of a prefetch, as |
| 716 // well as recording what prefetched hostname resolutions might be potentially |
| 717 // helpful during the next chrome-startup. |
| 718 //----------------------------------------------------------------------------- |
| 719 |
| 720 void Predictor::LearnAboutInitialNavigation(const GURL& url) { |
| 721 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 722 if (!predictor_enabled_ || NULL == initial_observer_.get() ) |
| 723 return; |
| 724 initial_observer_->Append(url, this); |
| 725 } |
| 726 |
| 727 // This API is only used in the browser process. |
| 728 // It is called from an IPC message originating in the renderer. It currently |
| 729 // includes both Page-Scan, and Link-Hover prefetching. |
| 730 // TODO(jar): Separate out link-hover prefetching, and page-scan results. |
| 731 void Predictor::DnsPrefetchList(const NameList& hostnames) { |
| 732 // TODO(jar): Push GURL transport further back into renderer, but this will |
| 733 // require a Webkit change in the observer :-/. |
| 734 UrlList urls; |
| 735 for (NameList::const_iterator it = hostnames.begin(); |
| 736 it < hostnames.end(); |
| 737 ++it) { |
| 738 urls.push_back(GURL("http://" + *it + ":80")); |
| 739 } |
| 740 |
| 741 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 742 DnsPrefetchMotivatedList(urls, UrlInfo::PAGE_SCAN_MOTIVATED); |
| 743 } |
| 744 |
| 745 void Predictor::DnsPrefetchMotivatedList( |
| 746 const UrlList& urls, |
453 UrlInfo::ResolutionMotivation motivation) { | 747 UrlInfo::ResolutionMotivation motivation) { |
454 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 748 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) || |
455 DCHECK(url.has_host()); | 749 BrowserThread::CurrentlyOn(BrowserThread::IO)); |
456 | 750 if (!predictor_enabled_) |
457 if (shutdown_) | 751 return; |
458 return NULL; | 752 |
459 | 753 if (BrowserThread::CurrentlyOn(BrowserThread::IO)) { |
460 UrlInfo* info = &results_[url]; | 754 ResolveList(urls, motivation); |
461 info->SetUrl(url); // Initialize or DCHECK. | 755 } else { |
462 // TODO(jar): I need to discard names that have long since expired. | 756 BrowserThread::PostTask( |
463 // Currently we only add to the domain map :-/ | 757 BrowserThread::IO, |
464 | 758 FROM_HERE, |
465 DCHECK(info->HasUrl(url)); | 759 base::Bind(&Predictor::ResolveList, base::Unretained(this), |
466 | 760 urls, motivation)); |
467 if (!info->NeedsDnsUpdate()) { | 761 } |
468 info->DLogResultsStats("DNS PrefetchNotUpdated"); | 762 } |
469 return NULL; | 763 |
470 } | 764 //----------------------------------------------------------------------------- |
471 | 765 // Functions to handle saving of hostnames from one session to the next, to |
472 info->SetQueuedState(motivation); | 766 // expedite startup times. |
473 work_queue_.Push(url, motivation); | 767 |
474 StartSomeQueuedResolutions(); | 768 static void SaveDnsPrefetchStateForNextStartupAndTrimOnIOThread( |
475 return info; | 769 base::ListValue* startup_list, |
476 } | 770 base::ListValue* referral_list, |
477 | 771 base::WaitableEvent* completion, |
478 void Predictor::StartSomeQueuedResolutions() { | 772 Predictor* predictor) { |
479 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 773 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
480 | 774 |
481 while (!work_queue_.IsEmpty() && | 775 if (NULL == predictor) { |
482 pending_lookups_.size() < max_concurrent_dns_lookups_) { | 776 completion->Signal(); |
483 const GURL url(work_queue_.Pop()); | 777 return; |
484 UrlInfo* info = &results_[url]; | 778 } |
485 DCHECK(info->HasUrl(url)); | 779 predictor->SaveDnsPrefetchStateForNextStartupAndTrim( |
486 info->SetAssignedState(); | 780 startup_list, referral_list, completion); |
487 | 781 } |
488 if (CongestionControlPerformed(info)) { | 782 |
489 DCHECK(work_queue_.IsEmpty()); | 783 void Predictor::SaveStateForNextStartupAndTrim(PrefService* prefs) { |
490 return; | 784 if (!predictor_enabled_) |
| 785 return; |
| 786 |
| 787 base::WaitableEvent completion(true, false); |
| 788 |
| 789 ListPrefUpdate update_startup_list(prefs, prefs::kDnsPrefetchingStartupList); |
| 790 ListPrefUpdate update_referral_list(prefs, |
| 791 prefs::kDnsPrefetchingHostReferralList); |
| 792 if (BrowserThread::CurrentlyOn(BrowserThread::IO)) { |
| 793 SaveDnsPrefetchStateForNextStartupAndTrimOnIOThread( |
| 794 update_startup_list.Get(), |
| 795 update_referral_list.Get(), |
| 796 &completion, |
| 797 this); |
| 798 } else { |
| 799 bool posted = BrowserThread::PostTask( |
| 800 BrowserThread::IO, |
| 801 FROM_HERE, |
| 802 base::Bind( |
| 803 SaveDnsPrefetchStateForNextStartupAndTrimOnIOThread, |
| 804 update_startup_list.Get(), |
| 805 update_referral_list.Get(), |
| 806 &completion, |
| 807 this)); |
| 808 |
| 809 // TODO(jar): Synchronous waiting for the IO thread is a potential source |
| 810 // to deadlocks and should be investigated. See http://crbug.com/78451. |
| 811 DCHECK(posted); |
| 812 if (posted) |
| 813 completion.Wait(); |
| 814 } |
| 815 } |
| 816 |
| 817 void Predictor::SaveDnsPrefetchStateForNextStartupAndTrim( |
| 818 base::ListValue* startup_list, |
| 819 base::ListValue* referral_list, |
| 820 base::WaitableEvent* completion) { |
| 821 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 822 if (initial_observer_.get()) |
| 823 initial_observer_->GetInitialDnsResolutionList(startup_list); |
| 824 |
| 825 // Do at least one trim at shutdown, in case the user wasn't running long |
| 826 // enough to do any regular trimming of referrers. |
| 827 TrimReferrersNow(); |
| 828 SerializeReferrers(referral_list); |
| 829 |
| 830 completion->Signal(); |
| 831 } |
| 832 |
| 833 void Predictor::EnablePredictor(bool enable) { |
| 834 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) || |
| 835 BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 836 |
| 837 if (BrowserThread::CurrentlyOn(BrowserThread::IO)) { |
| 838 EnablePredictorOnIOThread(enable); |
| 839 } else { |
| 840 BrowserThread::PostTask( |
| 841 BrowserThread::IO, |
| 842 FROM_HERE, |
| 843 base::Bind(&Predictor::EnablePredictorOnIOThread, |
| 844 base::Unretained(this), enable)); |
| 845 } |
| 846 } |
| 847 |
| 848 void Predictor::EnablePredictorOnIOThread(bool enable) { |
| 849 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 850 predictor_enabled_ = enable; |
| 851 } |
| 852 |
| 853 void Predictor::PredictFrameSubresources(const GURL& url) { |
| 854 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) || |
| 855 BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 856 if (!predictor_enabled_) |
| 857 return; |
| 858 DCHECK_EQ(url.GetWithEmptyPath(), url); |
| 859 // Add one pass through the message loop to allow current navigation to |
| 860 // proceed. |
| 861 if (BrowserThread::CurrentlyOn(BrowserThread::IO)) { |
| 862 PrepareFrameSubresources(url); |
| 863 } else { |
| 864 BrowserThread::PostTask( |
| 865 BrowserThread::IO, |
| 866 FROM_HERE, |
| 867 base::Bind(&Predictor::PrepareFrameSubresources, |
| 868 base::Unretained(this), url)); |
| 869 } |
| 870 } |
| 871 |
| 872 enum SubresourceValue { |
| 873 PRECONNECTION, |
| 874 PRERESOLUTION, |
| 875 TOO_NEW, |
| 876 SUBRESOURCE_VALUE_MAX |
| 877 }; |
| 878 |
| 879 void Predictor::PrepareFrameSubresources(const GURL& url) { |
| 880 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 881 DCHECK_EQ(url.GetWithEmptyPath(), url); |
| 882 Referrers::iterator it = referrers_.find(url); |
| 883 if (referrers_.end() == it) { |
| 884 // Only when we don't know anything about this url, make 2 connections |
| 885 // available. We could do this completely via learning (by prepopulating |
| 886 // the referrer_ list with this expected value), but it would swell the |
| 887 // size of the list with all the "Leaf" nodes in the tree (nodes that don't |
| 888 // load any subresources). If we learn about this resource, we will instead |
| 889 // provide a more carefully estimated preconnection count. |
| 890 if (preconnect_enabled_) |
| 891 PreconnectOnIOThread(url, UrlInfo::SELF_REFERAL_MOTIVATED, 2); |
| 892 return; |
| 893 } |
| 894 |
| 895 Referrer* referrer = &(it->second); |
| 896 referrer->IncrementUseCount(); |
| 897 const UrlInfo::ResolutionMotivation motivation = |
| 898 UrlInfo::LEARNED_REFERAL_MOTIVATED; |
| 899 for (Referrer::iterator future_url = referrer->begin(); |
| 900 future_url != referrer->end(); ++future_url) { |
| 901 SubresourceValue evalution(TOO_NEW); |
| 902 double connection_expectation = future_url->second.subresource_use_rate(); |
| 903 UMA_HISTOGRAM_CUSTOM_COUNTS("Net.PreconnectSubresourceExpectation", |
| 904 static_cast<int>(connection_expectation * 100), |
| 905 10, 5000, 50); |
| 906 future_url->second.ReferrerWasObserved(); |
| 907 if (preconnect_enabled_ && |
| 908 connection_expectation > kPreconnectWorthyExpectedValue) { |
| 909 evalution = PRECONNECTION; |
| 910 future_url->second.IncrementPreconnectionCount(); |
| 911 int count = static_cast<int>(std::ceil(connection_expectation)); |
| 912 if (url.host() == future_url->first.host()) |
| 913 ++count; |
| 914 PreconnectOnIOThread(future_url->first, motivation, count); |
| 915 } else if (connection_expectation > kDNSPreresolutionWorthyExpectedValue) { |
| 916 evalution = PRERESOLUTION; |
| 917 future_url->second.preresolution_increment(); |
| 918 UrlInfo* queued_info = AppendToResolutionQueue(future_url->first, |
| 919 motivation); |
| 920 if (queued_info) |
| 921 queued_info->SetReferringHostname(url); |
491 } | 922 } |
492 | 923 UMA_HISTOGRAM_ENUMERATION("Net.PreconnectSubresourceEval", evalution, |
493 LookupRequest* request = new LookupRequest(this, host_resolver_, url); | 924 SUBRESOURCE_VALUE_MAX); |
494 int status = request->Start(); | 925 } |
495 if (status == net::ERR_IO_PENDING) { | |
496 // Will complete asynchronously. | |
497 pending_lookups_.insert(request); | |
498 peak_pending_lookups_ = std::max(peak_pending_lookups_, | |
499 pending_lookups_.size()); | |
500 } else { | |
501 // Completed synchronously (was already cached by HostResolver), or else | |
502 // there was (equivalently) some network error that prevents us from | |
503 // finding the name. Status net::OK means it was "found." | |
504 LookupFinished(request, url, status == net::OK); | |
505 delete request; | |
506 } | |
507 } | |
508 } | |
509 | |
510 bool Predictor::CongestionControlPerformed(UrlInfo* info) { | |
511 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
512 // Note: queue_duration is ONLY valid after we go to assigned state. | |
513 if (info->queue_duration() < max_dns_queue_delay_) | |
514 return false; | |
515 // We need to discard all entries in our queue, as we're keeping them waiting | |
516 // too long. By doing this, we'll have a chance to quickly service urgent | |
517 // resolutions, and not have a bogged down system. | |
518 while (true) { | |
519 info->RemoveFromQueue(); | |
520 if (work_queue_.IsEmpty()) | |
521 break; | |
522 info = &results_[work_queue_.Pop()]; | |
523 info->SetAssignedState(); | |
524 } | |
525 return true; | |
526 } | 926 } |
527 | 927 |
528 void Predictor::OnLookupFinished(LookupRequest* request, const GURL& url, | 928 void Predictor::OnLookupFinished(LookupRequest* request, const GURL& url, |
529 bool found) { | 929 bool found) { |
530 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 930 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
531 | 931 |
532 LookupFinished(request, url, found); | 932 LookupFinished(request, url, found); |
533 pending_lookups_.erase(request); | 933 pending_lookups_.erase(request); |
534 delete request; | 934 delete request; |
535 | 935 |
536 StartSomeQueuedResolutions(); | 936 StartSomeQueuedResolutions(); |
537 } | 937 } |
538 | 938 |
539 void Predictor::LookupFinished(LookupRequest* request, const GURL& url, | 939 void Predictor::LookupFinished(LookupRequest* request, const GURL& url, |
540 bool found) { | 940 bool found) { |
541 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 941 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
542 UrlInfo* info = &results_[url]; | 942 UrlInfo* info = &results_[url]; |
543 DCHECK(info->HasUrl(url)); | 943 DCHECK(info->HasUrl(url)); |
544 if (info->is_marked_to_delete()) { | 944 if (info->is_marked_to_delete()) { |
545 results_.erase(url); | 945 results_.erase(url); |
546 } else { | 946 } else { |
547 if (found) | 947 if (found) |
548 info->SetFoundState(); | 948 info->SetFoundState(); |
549 else | 949 else |
550 info->SetNoSuchNameState(); | 950 info->SetNoSuchNameState(); |
551 } | 951 } |
552 } | 952 } |
553 | 953 |
554 void Predictor::DiscardAllResults() { | 954 UrlInfo* Predictor::AppendToResolutionQueue( |
| 955 const GURL& url, |
| 956 UrlInfo::ResolutionMotivation motivation) { |
555 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 957 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
556 // Delete anything listed so far in this session that shows in about:dns. | 958 DCHECK(url.has_host()); |
557 referrers_.clear(); | |
558 | 959 |
| 960 if (shutdown_) |
| 961 return NULL; |
559 | 962 |
560 // Try to delete anything in our work queue. | 963 UrlInfo* info = &results_[url]; |
561 while (!work_queue_.IsEmpty()) { | 964 info->SetUrl(url); // Initialize or DCHECK. |
562 // Emulate processing cycle as though host was not found. | 965 // TODO(jar): I need to discard names that have long since expired. |
563 GURL url = work_queue_.Pop(); | 966 // Currently we only add to the domain map :-/ |
| 967 |
| 968 DCHECK(info->HasUrl(url)); |
| 969 |
| 970 if (!info->NeedsDnsUpdate()) { |
| 971 info->DLogResultsStats("DNS PrefetchNotUpdated"); |
| 972 return NULL; |
| 973 } |
| 974 |
| 975 info->SetQueuedState(motivation); |
| 976 work_queue_.Push(url, motivation); |
| 977 StartSomeQueuedResolutions(); |
| 978 return info; |
| 979 } |
| 980 |
| 981 bool Predictor::CongestionControlPerformed(UrlInfo* info) { |
| 982 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 983 // Note: queue_duration is ONLY valid after we go to assigned state. |
| 984 if (info->queue_duration() < max_dns_queue_delay_) |
| 985 return false; |
| 986 // We need to discard all entries in our queue, as we're keeping them waiting |
| 987 // too long. By doing this, we'll have a chance to quickly service urgent |
| 988 // resolutions, and not have a bogged down system. |
| 989 while (true) { |
| 990 info->RemoveFromQueue(); |
| 991 if (work_queue_.IsEmpty()) |
| 992 break; |
| 993 info = &results_[work_queue_.Pop()]; |
| 994 info->SetAssignedState(); |
| 995 } |
| 996 return true; |
| 997 } |
| 998 |
| 999 void Predictor::StartSomeQueuedResolutions() { |
| 1000 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 1001 |
| 1002 while (!work_queue_.IsEmpty() && |
| 1003 pending_lookups_.size() < max_concurrent_dns_lookups_) { |
| 1004 const GURL url(work_queue_.Pop()); |
564 UrlInfo* info = &results_[url]; | 1005 UrlInfo* info = &results_[url]; |
565 DCHECK(info->HasUrl(url)); | 1006 DCHECK(info->HasUrl(url)); |
566 info->SetAssignedState(); | 1007 info->SetAssignedState(); |
567 info->SetNoSuchNameState(); | |
568 } | |
569 // Now every result_ is either resolved, or is being resolved | |
570 // (see LookupRequest). | |
571 | 1008 |
572 // Step through result_, recording names of all hosts that can't be erased. | 1009 if (CongestionControlPerformed(info)) { |
573 // We can't erase anything being worked on. | 1010 DCHECK(work_queue_.IsEmpty()); |
574 Results assignees; | 1011 return; |
575 for (Results::iterator it = results_.begin(); results_.end() != it; ++it) { | |
576 GURL url(it->first); | |
577 UrlInfo* info = &it->second; | |
578 DCHECK(info->HasUrl(url)); | |
579 if (info->is_assigned()) { | |
580 info->SetPendingDeleteState(); | |
581 assignees[url] = *info; | |
582 } | 1012 } |
583 } | |
584 DCHECK(assignees.size() <= max_concurrent_dns_lookups_); | |
585 results_.clear(); | |
586 // Put back in the names being worked on. | |
587 for (Results::iterator it = assignees.begin(); assignees.end() != it; ++it) { | |
588 DCHECK(it->second.is_marked_to_delete()); | |
589 results_[it->first] = it->second; | |
590 } | |
591 } | |
592 | 1013 |
593 void Predictor::TrimReferrersNow() { | 1014 LookupRequest* request = new LookupRequest(this, host_resolver_, url); |
594 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 1015 int status = request->Start(); |
595 // Just finish up work if an incremental trim is in progress. | 1016 if (status == net::ERR_IO_PENDING) { |
596 if (urls_being_trimmed_.empty()) | 1017 // Will complete asynchronously. |
597 LoadUrlsForTrimming(); | 1018 pending_lookups_.insert(request); |
598 IncrementalTrimReferrers(true); // Do everything now. | 1019 peak_pending_lookups_ = std::max(peak_pending_lookups_, |
599 } | 1020 pending_lookups_.size()); |
600 | 1021 } else { |
601 void Predictor::SerializeReferrers(ListValue* referral_list) { | 1022 // Completed synchronously (was already cached by HostResolver), or else |
602 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 1023 // there was (equivalently) some network error that prevents us from |
603 referral_list->Clear(); | 1024 // finding the name. Status net::OK means it was "found." |
604 referral_list->Append(new base::FundamentalValue(kPredictorReferrerVersion)); | 1025 LookupFinished(request, url, status == net::OK); |
605 for (Referrers::const_iterator it = referrers_.begin(); | 1026 delete request; |
606 it != referrers_.end(); ++it) { | |
607 // Serialize the list of subresource names. | |
608 Value* subresource_list(it->second.Serialize()); | |
609 | |
610 // Create a list for each referer. | |
611 ListValue* motivator(new ListValue); | |
612 motivator->Append(new StringValue(it->first.spec())); | |
613 motivator->Append(subresource_list); | |
614 | |
615 referral_list->Append(motivator); | |
616 } | |
617 } | |
618 | |
619 void Predictor::DeserializeReferrers(const ListValue& referral_list) { | |
620 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
621 int format_version = -1; | |
622 if (referral_list.GetSize() > 0 && | |
623 referral_list.GetInteger(0, &format_version) && | |
624 format_version == kPredictorReferrerVersion) { | |
625 for (size_t i = 1; i < referral_list.GetSize(); ++i) { | |
626 ListValue* motivator; | |
627 if (!referral_list.GetList(i, &motivator)) { | |
628 NOTREACHED(); | |
629 return; | |
630 } | |
631 std::string motivating_url_spec; | |
632 if (!motivator->GetString(0, &motivating_url_spec)) { | |
633 NOTREACHED(); | |
634 return; | |
635 } | |
636 | |
637 Value* subresource_list; | |
638 if (!motivator->Get(1, &subresource_list)) { | |
639 NOTREACHED(); | |
640 return; | |
641 } | |
642 | |
643 referrers_[GURL(motivating_url_spec)].Deserialize(*subresource_list); | |
644 } | 1027 } |
645 } | 1028 } |
646 } | 1029 } |
647 | 1030 |
648 void Predictor::TrimReferrers() { | 1031 void Predictor::TrimReferrers() { |
649 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 1032 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
650 if (!urls_being_trimmed_.empty()) | 1033 if (!urls_being_trimmed_.empty()) |
651 return; // There is incremental trimming in progress already. | 1034 return; // There is incremental trimming in progress already. |
652 | 1035 |
653 // Check to see if it is time to trim yet. | 1036 // Check to see if it is time to trim yet. |
(...skipping 12 matching lines...) Expand all Loading... |
666 it != referrers_.end(); ++it) | 1049 it != referrers_.end(); ++it) |
667 urls_being_trimmed_.push_back(it->first); | 1050 urls_being_trimmed_.push_back(it->first); |
668 UMA_HISTOGRAM_COUNTS("Net.PredictionTrimSize", urls_being_trimmed_.size()); | 1051 UMA_HISTOGRAM_COUNTS("Net.PredictionTrimSize", urls_being_trimmed_.size()); |
669 } | 1052 } |
670 | 1053 |
671 void Predictor::PostIncrementalTrimTask() { | 1054 void Predictor::PostIncrementalTrimTask() { |
672 if (urls_being_trimmed_.empty()) | 1055 if (urls_being_trimmed_.empty()) |
673 return; | 1056 return; |
674 MessageLoop::current()->PostDelayedTask( | 1057 MessageLoop::current()->PostDelayedTask( |
675 FROM_HERE, | 1058 FROM_HERE, |
676 trim_task_factory_.NewRunnableMethod(&Predictor::IncrementalTrimReferrers, | 1059 trim_task_factory_->NewRunnableMethod( |
677 false), | 1060 &Predictor::IncrementalTrimReferrers, false), |
678 kDurationBetweenTrimmingIncrements.InMilliseconds()); | 1061 kDurationBetweenTrimmingIncrements.InMilliseconds()); |
679 } | 1062 } |
680 | 1063 |
681 void Predictor::IncrementalTrimReferrers(bool trim_all_now) { | 1064 void Predictor::IncrementalTrimReferrers(bool trim_all_now) { |
682 size_t trim_count = urls_being_trimmed_.size(); | 1065 size_t trim_count = urls_being_trimmed_.size(); |
683 if (!trim_all_now) | 1066 if (!trim_all_now) |
684 trim_count = std::min(trim_count, kUrlsTrimmedPerIncrement); | 1067 trim_count = std::min(trim_count, kUrlsTrimmedPerIncrement); |
685 while (trim_count-- != 0) { | 1068 while (trim_count-- != 0) { |
686 Referrers::iterator it = referrers_.find(urls_being_trimmed_.back()); | 1069 Referrers::iterator it = referrers_.find(urls_being_trimmed_.back()); |
687 urls_being_trimmed_.pop_back(); | 1070 urls_being_trimmed_.pop_back(); |
688 if (it == referrers_.end()) | 1071 if (it == referrers_.end()) |
689 continue; // Defensive code: It got trimmed away already. | 1072 continue; // Defensive code: It got trimmed away already. |
690 if (!it->second.Trim(kReferrerTrimRatio, kDiscardableExpectedValue)) | 1073 if (!it->second.Trim(kReferrerTrimRatio, kDiscardableExpectedValue)) |
691 referrers_.erase(it); | 1074 referrers_.erase(it); |
692 } | 1075 } |
693 PostIncrementalTrimTask(); | 1076 PostIncrementalTrimTask(); |
694 } | 1077 } |
695 | 1078 |
696 //------------------------------------------------------------------------------ | 1079 // ---------------------- End IO methods. ------------------------------------- |
| 1080 |
| 1081 //----------------------------------------------------------------------------- |
697 | 1082 |
698 Predictor::HostNameQueue::HostNameQueue() { | 1083 Predictor::HostNameQueue::HostNameQueue() { |
699 } | 1084 } |
700 | 1085 |
701 Predictor::HostNameQueue::~HostNameQueue() { | 1086 Predictor::HostNameQueue::~HostNameQueue() { |
702 } | 1087 } |
703 | 1088 |
704 void Predictor::HostNameQueue::Push(const GURL& url, | 1089 void Predictor::HostNameQueue::Push(const GURL& url, |
705 UrlInfo::ResolutionMotivation motivation) { | 1090 UrlInfo::ResolutionMotivation motivation) { |
706 switch (motivation) { | 1091 switch (motivation) { |
(...skipping 15 matching lines...) Expand all Loading... |
722 | 1107 |
723 GURL Predictor::HostNameQueue::Pop() { | 1108 GURL Predictor::HostNameQueue::Pop() { |
724 DCHECK(!IsEmpty()); | 1109 DCHECK(!IsEmpty()); |
725 std::queue<GURL> *queue(rush_queue_.empty() ? &background_queue_ | 1110 std::queue<GURL> *queue(rush_queue_.empty() ? &background_queue_ |
726 : &rush_queue_); | 1111 : &rush_queue_); |
727 GURL url(queue->front()); | 1112 GURL url(queue->front()); |
728 queue->pop(); | 1113 queue->pop(); |
729 return url; | 1114 return url; |
730 } | 1115 } |
731 | 1116 |
732 void Predictor::DeserializeReferrersThenDelete(ListValue* referral_list) { | 1117 //----------------------------------------------------------------------------- |
733 DeserializeReferrers(*referral_list); | 1118 // Member definitions for InitialObserver class. |
734 delete referral_list; | 1119 |
| 1120 Predictor::InitialObserver::InitialObserver() { |
735 } | 1121 } |
736 | 1122 |
| 1123 Predictor::InitialObserver::~InitialObserver() { |
| 1124 } |
737 | 1125 |
738 //------------------------------------------------------------------------------ | 1126 void Predictor::InitialObserver::Append(const GURL& url, |
| 1127 Predictor* predictor) { |
| 1128 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 1129 |
| 1130 // TODO(rlp): Do we really need the predictor check here? |
| 1131 if (NULL == predictor) |
| 1132 return; |
| 1133 if (kStartupResolutionCount <= first_navigations_.size()) |
| 1134 return; |
| 1135 |
| 1136 DCHECK(url.SchemeIs("http") || url.SchemeIs("https")); |
| 1137 DCHECK_EQ(url, Predictor::CanonicalizeUrl(url)); |
| 1138 if (first_navigations_.find(url) == first_navigations_.end()) |
| 1139 first_navigations_[url] = base::TimeTicks::Now(); |
| 1140 } |
| 1141 |
| 1142 void Predictor::InitialObserver::GetInitialDnsResolutionList( |
| 1143 base::ListValue* startup_list) { |
| 1144 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 1145 DCHECK(startup_list); |
| 1146 startup_list->Clear(); |
| 1147 DCHECK_EQ(0u, startup_list->GetSize()); |
| 1148 startup_list->Append( |
| 1149 new base::FundamentalValue(kPredictorStartupFormatVersion)); |
| 1150 for (FirstNavigations::iterator it = first_navigations_.begin(); |
| 1151 it != first_navigations_.end(); |
| 1152 ++it) { |
| 1153 DCHECK(it->first == Predictor::CanonicalizeUrl(it->first)); |
| 1154 startup_list->Append(new StringValue(it->first.spec())); |
| 1155 } |
| 1156 } |
| 1157 |
| 1158 void Predictor::InitialObserver::GetFirstResolutionsHtml( |
| 1159 std::string* output) { |
| 1160 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 1161 |
| 1162 UrlInfo::UrlInfoTable resolution_list; |
| 1163 { |
| 1164 for (FirstNavigations::iterator it(first_navigations_.begin()); |
| 1165 it != first_navigations_.end(); |
| 1166 it++) { |
| 1167 UrlInfo info; |
| 1168 info.SetUrl(it->first); |
| 1169 info.set_time(it->second); |
| 1170 resolution_list.push_back(info); |
| 1171 } |
| 1172 } |
| 1173 UrlInfo::GetHtmlTable(resolution_list, |
| 1174 "Future startups will prefetch DNS records for ", false, output); |
| 1175 } |
| 1176 |
| 1177 //----------------------------------------------------------------------------- |
739 // Helper functions | 1178 // Helper functions |
740 //------------------------------------------------------------------------------ | 1179 //----------------------------------------------------------------------------- |
741 | 1180 |
742 // static | 1181 // static |
743 GURL Predictor::CanonicalizeUrl(const GURL& url) { | 1182 GURL Predictor::CanonicalizeUrl(const GURL& url) { |
744 if (!url.has_host()) | 1183 if (!url.has_host()) |
745 return GURL::EmptyGURL(); | 1184 return GURL::EmptyGURL(); |
746 | 1185 |
747 std::string scheme; | 1186 std::string scheme; |
748 if (url.has_scheme()) { | 1187 if (url.has_scheme()) { |
749 scheme = url.scheme(); | 1188 scheme = url.scheme(); |
750 if (scheme != "http" && scheme != "https") | 1189 if (scheme != "http" && scheme != "https") |
751 return GURL::EmptyGURL(); | 1190 return GURL::EmptyGURL(); |
752 if (url.has_port()) | 1191 if (url.has_port()) |
753 return url.GetWithEmptyPath(); | 1192 return url.GetWithEmptyPath(); |
754 } else { | 1193 } else { |
755 scheme = "http"; | 1194 scheme = "http"; |
756 } | 1195 } |
757 | 1196 |
758 // If we omit a port, it will default to 80 or 443 as appropriate. | 1197 // If we omit a port, it will default to 80 or 443 as appropriate. |
759 std::string colon_plus_port; | 1198 std::string colon_plus_port; |
760 if (url.has_port()) | 1199 if (url.has_port()) |
761 colon_plus_port = ":" + url.port(); | 1200 colon_plus_port = ":" + url.port(); |
762 | 1201 |
763 return GURL(scheme + "://" + url.host() + colon_plus_port); | 1202 return GURL(scheme + "://" + url.host() + colon_plus_port); |
764 } | 1203 } |
765 | 1204 |
| 1205 void SimplePredictor::InitNetworkPredictor(PrefService* user_prefs, |
| 1206 PrefService* local_state, |
| 1207 IOThread* io_thread) { |
| 1208 // Empty function for unittests. |
| 1209 } |
| 1210 |
| 1211 void SimplePredictor::ShutdownOnUIThread(PrefService* user_prefs) { |
| 1212 SetShutdown(true); |
| 1213 } |
766 | 1214 |
767 } // namespace chrome_browser_net | 1215 } // namespace chrome_browser_net |
OLD | NEW |