blob: f30e4ae38642117164894126a44a0b3a11475485 [file] [log] [blame]
[email protected]ac4c6682012-01-04 00:57:391// Copyright (c) 2012 The Chromium Authors. All rights reserved.
license.botbf09a502008-08-24 00:55:552// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
initial.commit09911bf2008-07-26 23:55:294
[email protected]3530cd92010-06-27 06:22:015#include "chrome/browser/net/predictor.h"
initial.commit09911bf2008-07-26 23:55:296
[email protected]1933eb202009-02-19 18:23:257#include <algorithm>
[email protected]0707e2a2010-11-30 01:57:048#include <cmath>
[email protected]319d9e6f2009-02-18 19:47:219#include <set>
[email protected]1933eb202009-02-19 18:23:2510#include <sstream>
initial.commit09911bf2008-07-26 23:55:2911
[email protected]67372ecf2011-09-10 01:30:4612#include "base/bind.h"
13#include "base/command_line.h"
[email protected]1933eb202009-02-19 18:23:2514#include "base/compiler_specific.h"
[email protected]835d7c82010-10-14 04:38:3815#include "base/metrics/histogram.h"
[email protected]67372ecf2011-09-10 01:30:4616#include "base/stl_util.h"
[email protected]da2f68f2011-05-11 21:40:5517#include "base/stringprintf.h"
[email protected]67372ecf2011-09-10 01:30:4618#include "base/synchronization/waitable_event.h"
[email protected]1933eb202009-02-19 18:23:2519#include "base/time.h"
[email protected]c02c853d72010-08-07 06:23:2420#include "base/values.h"
[email protected]67372ecf2011-09-10 01:30:4621#include "chrome/browser/io_thread.h"
[email protected]760d970a2010-05-18 00:39:1822#include "chrome/browser/net/preconnect.h"
[email protected]67372ecf2011-09-10 01:30:4623#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"
[email protected]c38831a12011-10-28 12:44:4929#include "content/public/browser/browser_thread.h"
[email protected]1933eb202009-02-19 18:23:2530#include "net/base/address_list.h"
31#include "net/base/completion_callback.h"
[email protected]760d970a2010-05-18 00:39:1832#include "net/base/host_port_pair.h"
[email protected]1933eb202009-02-19 18:23:2533#include "net/base/host_resolver.h"
34#include "net/base/net_errors.h"
[email protected]9e743cd2010-03-16 07:03:5335#include "net/base/net_log.h"
[email protected]b11279e2011-06-06 18:25:1936#include "net/base/single_request_host_resolver.h"
[email protected]7c46a7082012-01-14 01:24:3637#include "net/url_request/url_request_context_getter.h"
[email protected]e1acf6f2008-10-27 20:43:3338
[email protected]602faf3c2009-06-27 14:35:4439using base::TimeDelta;
[email protected]631bb742011-11-02 11:29:3940using content::BrowserThread;
[email protected]602faf3c2009-06-27 14:35:4441
initial.commit09911bf2008-07-26 23:55:2942namespace chrome_browser_net {
43
[email protected]f4ef861ba2010-07-28 22:37:2344// static
[email protected]d6431722011-09-01 00:46:3345const int Predictor::kPredictorReferrerVersion = 2;
[email protected]1455ccf12010-08-18 16:32:1446const double Predictor::kPreconnectWorthyExpectedValue = 0.8;
[email protected]1455ccf12010-08-18 16:32:1447const double Predictor::kDNSPreresolutionWorthyExpectedValue = 0.1;
[email protected]579f2a12011-04-06 16:27:3148const double Predictor::kDiscardableExpectedValue = 0.05;
49// The goal is of trimming is to to reduce the importance (number of expected
50// subresources needed) by a factor of 2 after about 24 hours of uptime. We will
51// trim roughly once-an-hour of uptime. The ratio to use in each trim operation
52// is then the 24th root of 0.5. If a user only surfs for 4 hours a day, then
53// after about 6 days they will have halved all their estimates of subresource
54// connections. Once this falls below kDiscardableExpectedValue the referrer
55// will be discarded.
56// TODO(jar): Measure size of referrer lists in the field. Consider an adaptive
57// system that uses a higher trim ratio when the list is large.
58// static
59const double Predictor::kReferrerTrimRatio = 0.97153;
[email protected]4890fe592012-01-27 09:19:0360const int64 Predictor::kDurationBetweenTrimmingsHours = 1;
61const int64 Predictor::kDurationBetweenTrimmingIncrementsSeconds = 15;
[email protected]579f2a12011-04-06 16:27:3162const size_t Predictor::kUrlsTrimmedPerIncrement = 5u;
[email protected]67372ecf2011-09-10 01:30:4663const size_t Predictor::kMaxSpeculativeParallelResolves = 3;
64// To control our congestion avoidance system, which discards a queue when
65// resolutions are "taking too long," we need an expected resolution time.
66// Common average is in the range of 300-500ms.
67const int kExpectedResolutionTimeMs = 500;
68const int Predictor::kTypicalSpeculativeGroupSize = 8;
69const int Predictor::kMaxSpeculativeResolveQueueDelayMs =
70 (kExpectedResolutionTimeMs * Predictor::kTypicalSpeculativeGroupSize) /
71 Predictor::kMaxSpeculativeParallelResolves;
72
73static int g_max_queueing_delay_ms = 0;
74static size_t g_max_parallel_resolves = 0u;
75
76// A version number for prefs that are saved. This should be incremented when
77// we change the format so that we discard old data.
78static const int kPredictorStartupFormatVersion = 1;
[email protected]f4ef861ba2010-07-28 22:37:2379
[email protected]74be069e82010-06-25 00:12:4980class Predictor::LookupRequest {
[email protected]1933eb202009-02-19 18:23:2581 public:
[email protected]74be069e82010-06-25 00:12:4982 LookupRequest(Predictor* predictor,
[email protected]8a00f00a2009-06-12 00:49:3883 net::HostResolver* host_resolver,
[email protected]c5629c32010-06-23 01:22:4384 const GURL& url)
[email protected]aa22b242011-11-16 18:58:2985 : predictor_(predictor),
[email protected]c5629c32010-06-23 01:22:4386 url_(url),
[email protected]8a00f00a2009-06-12 00:49:3887 resolver_(host_resolver) {
initial.commit09911bf2008-07-26 23:55:2988 }
[email protected]1933eb202009-02-19 18:23:2589
[email protected]85398532009-06-16 21:32:1890 // Return underlying network resolver status.
91 // net::OK ==> Host was found synchronously.
92 // net:ERR_IO_PENDING ==> Network will callback later with result.
93 // anything else ==> Host was not found synchronously.
94 int Start() {
[email protected]930cc742010-09-15 22:54:1095 net::HostResolver::RequestInfo resolve_info(
96 net::HostPortPair::FromURL(url_));
[email protected]2884a462009-06-15 05:08:4297
98 // Make a note that this is a speculative resolve request. This allows us
99 // to separate it from real navigations in the observer's callback, and
100 // lets the HostResolver know it can de-prioritize it.
101 resolve_info.set_is_speculative(true);
[email protected]ec08bb22009-08-12 00:25:12102 return resolver_.Resolve(
[email protected]aa22b242011-11-16 18:58:29103 resolve_info, &addresses_,
104 base::Bind(&LookupRequest::OnLookupFinished, base::Unretained(this)),
105 net::BoundNetLog());
[email protected]1933eb202009-02-19 18:23:25106 }
107
108 private:
109 void OnLookupFinished(int result) {
[email protected]74be069e82010-06-25 00:12:49110 predictor_->OnLookupFinished(this, url_, result == net::OK);
[email protected]1933eb202009-02-19 18:23:25111 }
112
[email protected]74be069e82010-06-25 00:12:49113 Predictor* predictor_; // The predictor which started us.
[email protected]1933eb202009-02-19 18:23:25114
[email protected]c5629c32010-06-23 01:22:43115 const GURL url_; // Hostname to resolve.
[email protected]8a00f00a2009-06-12 00:49:38116 net::SingleRequestHostResolver resolver_;
[email protected]1933eb202009-02-19 18:23:25117 net::AddressList addresses_;
118
119 DISALLOW_COPY_AND_ASSIGN(LookupRequest);
120};
121
[email protected]67372ecf2011-09-10 01:30:46122Predictor::Predictor(bool preconnect_enabled)
123 : initial_observer_(NULL),
[email protected]7c46a7082012-01-14 01:24:36124 url_request_context_getter_(NULL),
[email protected]67372ecf2011-09-10 01:30:46125 predictor_enabled_(true),
126 peak_pending_lookups_(0),
[email protected]760d970a2010-05-18 00:39:18127 shutdown_(false),
[email protected]67372ecf2011-09-10 01:30:46128 max_concurrent_dns_lookups_(g_max_parallel_resolves),
129 max_dns_queue_delay_(
130 TimeDelta::FromMilliseconds(g_max_queueing_delay_ms)),
131 host_resolver_(NULL),
[email protected]1455ccf12010-08-18 16:32:14132 preconnect_enabled_(preconnect_enabled),
[email protected]579f2a12011-04-06 16:27:31133 consecutive_omnibox_preconnect_count_(0),
[email protected]4890fe592012-01-27 09:19:03134 next_trim_time_(base::TimeTicks::Now() +
135 TimeDelta::FromHours(kDurationBetweenTrimmingsHours)) {
[email protected]67372ecf2011-09-10 01:30:46136 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
[email protected]1933eb202009-02-19 18:23:25137}
138
[email protected]74be069e82010-06-25 00:12:49139Predictor::~Predictor() {
[email protected]67372ecf2011-09-10 01:30:46140 // TODO(rlp): Add DCHECK for CurrentlyOn(BrowserThread::IO) when the
141 // ProfileManagerTest has been updated with a mock profile.
[email protected]1933eb202009-02-19 18:23:25142 DCHECK(shutdown_);
143}
144
[email protected]67372ecf2011-09-10 01:30:46145// static
[email protected]7c46a7082012-01-14 01:24:36146Predictor* Predictor::CreatePredictor(bool preconnect_enabled,
147 bool simple_shutdown) {
[email protected]67372ecf2011-09-10 01:30:46148 if (simple_shutdown)
149 return new SimplePredictor(preconnect_enabled);
150 return new Predictor(preconnect_enabled);
initial.commit09911bf2008-07-26 23:55:29151}
152
[email protected]67372ecf2011-09-10 01:30:46153void Predictor::RegisterUserPrefs(PrefService* user_prefs) {
154 user_prefs->RegisterListPref(prefs::kDnsPrefetchingStartupList,
155 PrefService::UNSYNCABLE_PREF);
156 user_prefs->RegisterListPref(prefs::kDnsPrefetchingHostReferralList,
157 PrefService::UNSYNCABLE_PREF);
158}
[email protected]fd2f8afe2009-06-11 21:53:55159
[email protected]67372ecf2011-09-10 01:30:46160// --------------------- Start UI methods. ------------------------------------
161
162void Predictor::InitNetworkPredictor(PrefService* user_prefs,
163 PrefService* local_state,
[email protected]7c46a7082012-01-14 01:24:36164 IOThread* io_thread,
165 net::URLRequestContextGetter* getter) {
[email protected]67372ecf2011-09-10 01:30:46166 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
167
168 bool predictor_enabled =
169 user_prefs->GetBoolean(prefs::kNetworkPredictionEnabled);
170
[email protected]7c46a7082012-01-14 01:24:36171 url_request_context_getter_ = getter;
172
[email protected]67372ecf2011-09-10 01:30:46173 // Gather the list of hostnames to prefetch on startup.
174 UrlList urls = GetPredictedUrlListAtStartup(user_prefs, local_state);
175
176 base::ListValue* referral_list =
177 static_cast<base::ListValue*>(user_prefs->GetList(
178 prefs::kDnsPrefetchingHostReferralList)->DeepCopy());
179
180 // Remove obsolete preferences from local state if necessary.
181 int current_version =
182 local_state->GetInteger(prefs::kMultipleProfilePrefMigration);
183 if ((current_version & browser::DNS_PREFS) == 0) {
184 local_state->RegisterListPref(prefs::kDnsStartupPrefetchList,
185 PrefService::UNSYNCABLE_PREF);
186 local_state->RegisterListPref(prefs::kDnsHostReferralList,
187 PrefService::UNSYNCABLE_PREF);
188 local_state->ClearPref(prefs::kDnsStartupPrefetchList);
189 local_state->ClearPref(prefs::kDnsHostReferralList);
190 local_state->SetInteger(prefs::kMultipleProfilePrefMigration,
191 current_version | browser::DNS_PREFS);
[email protected]c5629c32010-06-23 01:22:43192 }
[email protected]67372ecf2011-09-10 01:30:46193
194 BrowserThread::PostTask(
195 BrowserThread::IO,
196 FROM_HERE,
197 base::Bind(
198 &Predictor::FinalizeInitializationOnIOThread,
199 base::Unretained(this),
200 urls, referral_list,
201 io_thread, predictor_enabled));
initial.commit09911bf2008-07-26 23:55:29202}
203
[email protected]1455ccf12010-08-18 16:32:14204void Predictor::AnticipateOmniboxUrl(const GURL& url, bool preconnectable) {
[email protected]67372ecf2011-09-10 01:30:46205 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
206 if (!predictor_enabled_)
207 return;
208 if (!url.is_valid() || !url.has_host())
209 return;
[email protected]1455ccf12010-08-18 16:32:14210 std::string host = url.HostNoBrackets();
211 bool is_new_host_request = (host != last_omnibox_host_);
212 last_omnibox_host_ = host;
213
214 UrlInfo::ResolutionMotivation motivation(UrlInfo::OMNIBOX_MOTIVATED);
215 base::TimeTicks now = base::TimeTicks::Now();
216
217 if (preconnect_enabled()) {
218 if (preconnectable && !is_new_host_request) {
219 ++consecutive_omnibox_preconnect_count_;
220 // The omnibox suggests a search URL (for which we can preconnect) after
221 // one or two characters are typed, even though such typing often (1 in
222 // 3?) becomes a real URL. This code waits till is has more evidence of a
223 // preconnectable URL (search URL) before forming a preconnection, so as
224 // to reduce the useless preconnect rate.
225 // Perchance this logic should be pushed back into the omnibox, where the
226 // actual characters typed, such as a space, can better forcast whether
227 // we need to search/preconnect or not. By waiting for at least 4
228 // characters in a row that have lead to a search proposal, we avoid
229 // preconnections for a prefix like "www." and we also wait until we have
230 // at least a 4 letter word to search for.
231 // Each character typed appears to induce 2 calls to
232 // AnticipateOmniboxUrl(), so we double 4 characters and limit at 8
233 // requests.
234 // TODO(jar): Use an A/B test to optimize this.
235 const int kMinConsecutiveRequests = 8;
236 if (consecutive_omnibox_preconnect_count_ >= kMinConsecutiveRequests) {
[email protected]ef42e302011-06-02 18:36:18237 // TODO(jar): Perhaps we should do a GET to leave the socket open in the
238 // pool. Currently, we just do a connect, which MAY be reset if we
239 // don't use it in 10 secondes!!! As a result, we may do more
240 // connections, and actually cost the server more than if we did a real
241 // get with a fake request (/gen_204 might be the good path on Google).
242 const int kMaxSearchKeepaliveSeconds(10);
[email protected]1455ccf12010-08-18 16:32:14243 if ((now - last_omnibox_preconnect_).InSeconds() <
244 kMaxSearchKeepaliveSeconds)
245 return; // We've done a preconnect recently.
246 last_omnibox_preconnect_ = now;
[email protected]0707e2a2010-11-30 01:57:04247 const int kConnectionsNeeded = 1;
[email protected]102e27c2011-02-23 01:01:31248 PreconnectOnUIThread(CanonicalizeUrl(url), motivation,
[email protected]7c46a7082012-01-14 01:24:36249 kConnectionsNeeded,
250 url_request_context_getter_);
[email protected]1455ccf12010-08-18 16:32:14251 return; // Skip pre-resolution, since we'll open a connection.
252 }
253 } else {
254 consecutive_omnibox_preconnect_count_ = 0;
255 }
256 }
257
258 // Fall through and consider pre-resolution.
259
260 // Omnibox tends to call in pairs (just a few milliseconds apart), and we
261 // really don't need to keep resolving a name that often.
262 // TODO(jar): A/B tests could check for perf impact of the early returns.
263 if (!is_new_host_request) {
264 const int kMinPreresolveSeconds(10);
265 if (kMinPreresolveSeconds > (now - last_omnibox_preresolve_).InSeconds())
266 return;
267 }
268 last_omnibox_preresolve_ = now;
269
270 // Perform at least DNS pre-resolution.
[email protected]ba4f1132010-10-09 02:02:35271 BrowserThread::PostTask(
272 BrowserThread::IO,
[email protected]1455ccf12010-08-18 16:32:14273 FROM_HERE,
[email protected]67372ecf2011-09-10 01:30:46274 base::Bind(&Predictor::Resolve, base::Unretained(this),
275 CanonicalizeUrl(url), motivation));
[email protected]1455ccf12010-08-18 16:32:14276}
277
[email protected]e326922d2010-09-03 09:08:10278void Predictor::PreconnectUrlAndSubresources(const GURL& url) {
[email protected]67372ecf2011-09-10 01:30:46279 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
280 if (!predictor_enabled_)
281 return;
282 if (!url.is_valid() || !url.has_host())
283 return;
[email protected]e326922d2010-09-03 09:08:10284 if (preconnect_enabled()) {
285 std::string host = url.HostNoBrackets();
286 UrlInfo::ResolutionMotivation motivation(UrlInfo::EARLY_LOAD_MOTIVATED);
[email protected]0707e2a2010-11-30 01:57:04287 const int kConnectionsNeeded = 1;
[email protected]102e27c2011-02-23 01:01:31288 PreconnectOnUIThread(CanonicalizeUrl(url), motivation,
[email protected]7c46a7082012-01-14 01:24:36289 kConnectionsNeeded,
290 url_request_context_getter_);
[email protected]e326922d2010-09-03 09:08:10291 PredictFrameSubresources(url.GetWithEmptyPath());
292 }
293}
294
[email protected]67372ecf2011-09-10 01:30:46295UrlList Predictor::GetPredictedUrlListAtStartup(
296 PrefService* user_prefs,
297 PrefService* local_state) {
298 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
299 UrlList urls;
300 // Recall list of URLs we learned about during last session.
301 // This may catch secondary hostnames, pulled in by the homepages. It will
302 // also catch more of the "primary" home pages, since that was (presumably)
303 // rendered first (and will be rendered first this time too).
304 const ListValue* startup_list =
305 user_prefs->GetList(prefs::kDnsPrefetchingStartupList);
306
307 if (startup_list) {
308 base::ListValue::const_iterator it = startup_list->begin();
309 int format_version = -1;
310 if (it != startup_list->end() &&
311 (*it)->GetAsInteger(&format_version) &&
312 format_version == kPredictorStartupFormatVersion) {
313 ++it;
314 for (; it != startup_list->end(); ++it) {
315 std::string url_spec;
316 if (!(*it)->GetAsString(&url_spec)) {
317 LOG(DFATAL);
318 break; // Format incompatibility.
319 }
320 GURL url(url_spec);
321 if (!url.has_host() || !url.has_scheme()) {
322 LOG(DFATAL);
323 break; // Format incompatibility.
324 }
325
326 urls.push_back(url);
327 }
328 }
329 }
330
331 // Prepare for any static home page(s) the user has in prefs. The user may
332 // have a LOT of tab's specified, so we may as well try to warm them all.
333 SessionStartupPref tab_start_pref =
334 SessionStartupPref::GetStartupPref(user_prefs);
335 if (SessionStartupPref::URLS == tab_start_pref.type) {
336 for (size_t i = 0; i < tab_start_pref.urls.size(); i++) {
337 GURL gurl = tab_start_pref.urls[i];
338 if (!gurl.is_valid() || gurl.SchemeIsFile() || gurl.host().empty())
339 continue;
340 if (gurl.SchemeIs("http") || gurl.SchemeIs("https"))
341 urls.push_back(gurl.GetWithEmptyPath());
342 }
343 }
344
345 if (urls.empty())
346 urls.push_back(GURL("http://www.google.com:80"));
347
348 return urls;
349}
350
351void Predictor::set_max_queueing_delay(int max_queueing_delay_ms) {
352 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
353 g_max_queueing_delay_ms = max_queueing_delay_ms;
354}
355
356void Predictor::set_max_parallel_resolves(size_t max_parallel_resolves) {
357 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
358 g_max_parallel_resolves = max_parallel_resolves;
359}
360
361void Predictor::ShutdownOnUIThread(PrefService* user_prefs) {
362 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
363
364 SaveStateForNextStartupAndTrim(user_prefs);
365
[email protected]ba4f1132010-10-09 02:02:35366 BrowserThread::PostTask(
367 BrowserThread::IO,
[email protected]9008c86f2010-08-06 07:10:24368 FROM_HERE,
[email protected]67372ecf2011-09-10 01:30:46369 base::Bind(&Predictor::Shutdown, base::Unretained(this)));
[email protected]9008c86f2010-08-06 07:10:24370}
371
[email protected]67372ecf2011-09-10 01:30:46372// ---------------------- End UI methods. -------------------------------------
[email protected]d237cec2011-08-19 15:50:52373
[email protected]67372ecf2011-09-10 01:30:46374// --------------------- Start IO methods. ------------------------------------
375
376void Predictor::Shutdown() {
377 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
378 DCHECK(!shutdown_);
379 shutdown_ = true;
380
381 STLDeleteElements(&pending_lookups_);
382}
383
384void Predictor::DiscardAllResults() {
385 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
386 // Delete anything listed so far in this session that shows in about:dns.
387 referrers_.clear();
388
389
390 // Try to delete anything in our work queue.
391 while (!work_queue_.IsEmpty()) {
392 // Emulate processing cycle as though host was not found.
393 GURL url = work_queue_.Pop();
394 UrlInfo* info = &results_[url];
395 DCHECK(info->HasUrl(url));
396 info->SetAssignedState();
397 info->SetNoSuchNameState();
[email protected]d237cec2011-08-19 15:50:52398 }
[email protected]67372ecf2011-09-10 01:30:46399 // Now every result_ is either resolved, or is being resolved
400 // (see LookupRequest).
401
402 // Step through result_, recording names of all hosts that can't be erased.
403 // We can't erase anything being worked on.
404 Results assignees;
405 for (Results::iterator it = results_.begin(); results_.end() != it; ++it) {
406 GURL url(it->first);
407 UrlInfo* info = &it->second;
408 DCHECK(info->HasUrl(url));
409 if (info->is_assigned()) {
410 info->SetPendingDeleteState();
411 assignees[url] = *info;
412 }
413 }
414 DCHECK_LE(assignees.size(), max_concurrent_dns_lookups_);
415 results_.clear();
416 // Put back in the names being worked on.
417 for (Results::iterator it = assignees.begin(); assignees.end() != it; ++it) {
418 DCHECK(it->second.is_marked_to_delete());
419 results_[it->first] = it->second;
420 }
421}
422
423// Overloaded Resolve() to take a vector of names.
424void Predictor::ResolveList(const UrlList& urls,
425 UrlInfo::ResolutionMotivation motivation) {
426 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
427
428 for (UrlList::const_iterator it = urls.begin(); it < urls.end(); ++it) {
429 AppendToResolutionQueue(*it, motivation);
430 }
431}
432
433// Basic Resolve() takes an invidual name, and adds it
434// to the queue.
435void Predictor::Resolve(const GURL& url,
436 UrlInfo::ResolutionMotivation motivation) {
437 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
438 if (!url.has_host())
439 return;
440 AppendToResolutionQueue(url, motivation);
441}
442
443void Predictor::LearnFromNavigation(const GURL& referring_url,
444 const GURL& target_url) {
445 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
446 if (!predictor_enabled_)
447 return;
448 DCHECK_EQ(referring_url, Predictor::CanonicalizeUrl(referring_url));
449 DCHECK_NE(referring_url, GURL::EmptyGURL());
450 DCHECK_EQ(target_url, Predictor::CanonicalizeUrl(target_url));
451 DCHECK_NE(target_url, GURL::EmptyGURL());
452
453 referrers_[referring_url].SuggestHost(target_url);
454 // Possibly do some referrer trimming.
455 TrimReferrers();
456}
457
458//-----------------------------------------------------------------------------
459// This section supports the about:dns page.
460
461void Predictor::PredictorGetHtmlInfo(Predictor* predictor,
462 std::string* output) {
463 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
464
465 output->append("<html><head><title>About DNS</title>"
466 // We'd like the following no-cache... but it doesn't work.
467 // "<META HTTP-EQUIV=\"Pragma\" CONTENT=\"no-cache\">"
468 "</head><body>");
469 if (predictor && predictor->predictor_enabled()) {
470 predictor->GetHtmlInfo(output);
471 } else {
472 output->append("DNS pre-resolution and TCP pre-connection is disabled.");
473 }
474 output->append("</body></html>");
initial.commit09911bf2008-07-26 23:55:29475}
476
initial.commit09911bf2008-07-26 23:55:29477// Provide sort order so all .com's are together, etc.
478struct RightToLeftStringSorter {
[email protected]760d970a2010-05-18 00:39:18479 bool operator()(const GURL& left,
480 const GURL& right) const {
481 return string_compare(left.host(), right.host());
482 }
483
484 static bool string_compare(const std::string& left_host,
[email protected]48329f92011-03-28 19:38:22485 const std::string& right_host) {
[email protected]760d970a2010-05-18 00:39:18486 if (left_host == right_host) return true;
487 size_t left_already_matched = left_host.size();
488 size_t right_already_matched = right_host.size();
[email protected]21dae9b2008-11-06 23:32:53489
490 // Ensure both strings have characters.
491 if (!left_already_matched) return true;
492 if (!right_already_matched) return false;
493
494 // Watch for trailing dot, so we'll always be safe to go one beyond dot.
[email protected]760d970a2010-05-18 00:39:18495 if ('.' == left_host[left_already_matched - 1]) {
496 if ('.' != right_host[right_already_matched - 1])
initial.commit09911bf2008-07-26 23:55:29497 return true;
[email protected]21dae9b2008-11-06 23:32:53498 // Both have dots at end of string.
499 --left_already_matched;
500 --right_already_matched;
501 } else {
[email protected]760d970a2010-05-18 00:39:18502 if ('.' == right_host[right_already_matched - 1])
[email protected]21dae9b2008-11-06 23:32:53503 return false;
504 }
505
506 while (1) {
507 if (!left_already_matched) return true;
508 if (!right_already_matched) return false;
509
[email protected]760d970a2010-05-18 00:39:18510 size_t left_start = left_host.find_last_of('.', left_already_matched - 1);
[email protected]21dae9b2008-11-06 23:32:53511 if (std::string::npos == left_start) {
[email protected]21dae9b2008-11-06 23:32:53512 left_already_matched = left_start = 0;
513 } else {
[email protected]21dae9b2008-11-06 23:32:53514 left_already_matched = left_start;
515 ++left_start; // Don't compare the dot.
516 }
[email protected]760d970a2010-05-18 00:39:18517 size_t right_start = right_host.find_last_of('.',
518 right_already_matched - 1);
[email protected]21dae9b2008-11-06 23:32:53519 if (std::string::npos == right_start) {
[email protected]21dae9b2008-11-06 23:32:53520 right_already_matched = right_start = 0;
521 } else {
[email protected]21dae9b2008-11-06 23:32:53522 right_already_matched = right_start;
523 ++right_start; // Don't compare the dot.
524 }
525
[email protected]760d970a2010-05-18 00:39:18526 int diff = left_host.compare(left_start, left_host.size(),
527 right_host, right_start, right_host.size());
[email protected]21dae9b2008-11-06 23:32:53528 if (diff > 0) return false;
529 if (diff < 0) return true;
initial.commit09911bf2008-07-26 23:55:29530 }
531 }
532};
533
[email protected]74be069e82010-06-25 00:12:49534void Predictor::GetHtmlReferrerLists(std::string* output) {
[email protected]ba4f1132010-10-09 02:02:35535 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
[email protected]21dae9b2008-11-06 23:32:53536 if (referrers_.empty())
537 return;
538
539 // TODO(jar): Remove any plausible JavaScript from names before displaying.
540
[email protected]c5629c32010-06-23 01:22:43541 typedef std::set<GURL, struct RightToLeftStringSorter>
[email protected]760d970a2010-05-18 00:39:18542 SortedNames;
[email protected]21dae9b2008-11-06 23:32:53543 SortedNames sorted_names;
544
545 for (Referrers::iterator it = referrers_.begin();
546 referrers_.end() != it; ++it)
547 sorted_names.insert(it->first);
548
549 output->append("<br><table border>");
[email protected]760d970a2010-05-18 00:39:18550 output->append(
551 "<tr><th>Host for Page</th>"
552 "<th>Page Load<br>Count</th>"
553 "<th>Subresource<br>Navigations</th>"
554 "<th>Subresource<br>PreConnects</th>"
[email protected]f4ef861ba2010-07-28 22:37:23555 "<th>Subresource<br>PreResolves</th>"
[email protected]760d970a2010-05-18 00:39:18556 "<th>Expected<br>Connects</th>"
[email protected]760d970a2010-05-18 00:39:18557 "<th>Subresource Spec</th></tr>");
[email protected]21dae9b2008-11-06 23:32:53558
559 for (SortedNames::iterator it = sorted_names.begin();
560 sorted_names.end() != it; ++it) {
561 Referrer* referrer = &(referrers_[*it]);
[email protected]760d970a2010-05-18 00:39:18562 bool first_set_of_futures = true;
[email protected]c5629c32010-06-23 01:22:43563 for (Referrer::iterator future_url = referrer->begin();
564 future_url != referrer->end(); ++future_url) {
[email protected]760d970a2010-05-18 00:39:18565 output->append("<tr align=right>");
[email protected]a77fa2dc2010-11-15 12:11:11566 if (first_set_of_futures) {
567 base::StringAppendF(output,
568 "<td rowspan=%d>%s</td><td rowspan=%d>%d</td>",
569 static_cast<int>(referrer->size()),
570 it->spec().c_str(),
571 static_cast<int>(referrer->size()),
572 static_cast<int>(referrer->use_count()));
573 }
[email protected]760d970a2010-05-18 00:39:18574 first_set_of_futures = false;
[email protected]a77fa2dc2010-11-15 12:11:11575 base::StringAppendF(output,
[email protected]f4ef861ba2010-07-28 22:37:23576 "<td>%d</td><td>%d</td><td>%d</td><td>%2.3f</td><td>%s</td></tr>",
[email protected]c5629c32010-06-23 01:22:43577 static_cast<int>(future_url->second.navigation_count()),
578 static_cast<int>(future_url->second.preconnection_count()),
[email protected]f4ef861ba2010-07-28 22:37:23579 static_cast<int>(future_url->second.preresolution_count()),
[email protected]c5629c32010-06-23 01:22:43580 static_cast<double>(future_url->second.subresource_use_rate()),
[email protected]c5629c32010-06-23 01:22:43581 future_url->first.spec().c_str());
[email protected]21dae9b2008-11-06 23:32:53582 }
[email protected]21dae9b2008-11-06 23:32:53583 }
584 output->append("</table>");
585}
586
[email protected]74be069e82010-06-25 00:12:49587void Predictor::GetHtmlInfo(std::string* output) {
[email protected]ba4f1132010-10-09 02:02:35588 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
[email protected]67372ecf2011-09-10 01:30:46589 if (initial_observer_.get())
590 initial_observer_->GetFirstResolutionsHtml(output);
591 // Show list of subresource predictions and stats.
592 GetHtmlReferrerLists(output);
593
[email protected]74be069e82010-06-25 00:12:49594 // Local lists for calling UrlInfo
[email protected]f4ef861ba2010-07-28 22:37:23595 UrlInfo::UrlInfoTable name_not_found;
596 UrlInfo::UrlInfoTable name_preresolved;
initial.commit09911bf2008-07-26 23:55:29597
[email protected]ec86bea2009-12-08 18:35:14598 // Get copies of all useful data.
[email protected]f4ef861ba2010-07-28 22:37:23599 typedef std::map<GURL, UrlInfo, RightToLeftStringSorter> SortedUrlInfo;
600 SortedUrlInfo snapshot;
601 // UrlInfo supports value semantics, so we can do a shallow copy.
602 for (Results::iterator it(results_.begin()); it != results_.end(); it++)
603 snapshot[it->first] = it->second;
initial.commit09911bf2008-07-26 23:55:29604
[email protected]74be069e82010-06-25 00:12:49605 // Partition the UrlInfo's into categories.
[email protected]f4ef861ba2010-07-28 22:37:23606 for (SortedUrlInfo::iterator it(snapshot.begin());
607 it != snapshot.end(); it++) {
[email protected]e7afe2452010-08-22 16:19:13608 if (it->second.was_nonexistent()) {
initial.commit09911bf2008-07-26 23:55:29609 name_not_found.push_back(it->second);
610 continue;
611 }
612 if (!it->second.was_found())
613 continue; // Still being processed.
[email protected]f4ef861ba2010-07-28 22:37:23614 name_preresolved.push_back(it->second);
initial.commit09911bf2008-07-26 23:55:29615 }
616
617 bool brief = false;
618#ifdef NDEBUG
619 brief = true;
620#endif // NDEBUG
621
622 // Call for display of each table, along with title.
[email protected]f4ef861ba2010-07-28 22:37:23623 UrlInfo::GetHtmlTable(name_preresolved,
624 "Preresolution DNS records performed for ", brief, output);
[email protected]74be069e82010-06-25 00:12:49625 UrlInfo::GetHtmlTable(name_not_found,
[email protected]7798871882010-09-14 17:18:56626 "Preresolving DNS records revealed non-existence for ", brief, output);
initial.commit09911bf2008-07-26 23:55:29627}
628
[email protected]67372ecf2011-09-10 01:30:46629void Predictor::TrimReferrersNow() {
630 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
631 // Just finish up work if an incremental trim is in progress.
632 if (urls_being_trimmed_.empty())
633 LoadUrlsForTrimming();
634 IncrementalTrimReferrers(true); // Do everything now.
635}
636
637void Predictor::SerializeReferrers(base::ListValue* referral_list) {
638 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
639 referral_list->Clear();
640 referral_list->Append(new base::FundamentalValue(kPredictorReferrerVersion));
641 for (Referrers::const_iterator it = referrers_.begin();
642 it != referrers_.end(); ++it) {
643 // Serialize the list of subresource names.
644 Value* subresource_list(it->second.Serialize());
645
646 // Create a list for each referer.
647 ListValue* motivator(new ListValue);
648 motivator->Append(new StringValue(it->first.spec()));
649 motivator->Append(subresource_list);
650
651 referral_list->Append(motivator);
652 }
653}
654
655void Predictor::DeserializeReferrers(const base::ListValue& referral_list) {
656 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
657 int format_version = -1;
658 if (referral_list.GetSize() > 0 &&
659 referral_list.GetInteger(0, &format_version) &&
660 format_version == kPredictorReferrerVersion) {
661 for (size_t i = 1; i < referral_list.GetSize(); ++i) {
662 base::ListValue* motivator;
663 if (!referral_list.GetList(i, &motivator)) {
664 NOTREACHED();
665 return;
666 }
667 std::string motivating_url_spec;
668 if (!motivator->GetString(0, &motivating_url_spec)) {
669 NOTREACHED();
670 return;
671 }
672
673 Value* subresource_list;
674 if (!motivator->Get(1, &subresource_list)) {
675 NOTREACHED();
676 return;
677 }
678
679 referrers_[GURL(motivating_url_spec)].Deserialize(*subresource_list);
680 }
681 }
682}
683
684void Predictor::DeserializeReferrersThenDelete(
685 base::ListValue* referral_list) {
686 DeserializeReferrers(*referral_list);
687 delete referral_list;
688}
689
690void Predictor::DiscardInitialNavigationHistory() {
691 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
692 if (initial_observer_.get())
693 initial_observer_->DiscardInitialNavigationHistory();
694}
695
696void Predictor::FinalizeInitializationOnIOThread(
697 const UrlList& startup_urls,
698 base::ListValue* referral_list,
699 IOThread* io_thread,
700 bool predictor_enabled) {
701 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
702
703 predictor_enabled_ = predictor_enabled;
704 initial_observer_.reset(new InitialObserver());
705 host_resolver_ = io_thread->globals()->host_resolver.get();
706
[email protected]ac4c6682012-01-04 00:57:39707 // base::WeakPtrFactory instances need to be created and destroyed
[email protected]67372ecf2011-09-10 01:30:46708 // on the same thread. The predictor lives on the IO thread and will die
709 // from there so now that we're on the IO thread we need to properly
[email protected]ac4c6682012-01-04 00:57:39710 // initialize the base::WeakPtrFactory.
[email protected]12465ec2011-11-17 21:18:23711 // TODO(groby): Check if WeakPtrFactory has the same constraint.
712 weak_factory_.reset(new base::WeakPtrFactory<Predictor>(this));
[email protected]67372ecf2011-09-10 01:30:46713
714 // Prefetch these hostnames on startup.
715 DnsPrefetchMotivatedList(startup_urls, UrlInfo::STARTUP_LIST_MOTIVATED);
716 DeserializeReferrersThenDelete(referral_list);
717}
718
719//-----------------------------------------------------------------------------
720// This section intermingles prefetch results with actual browser HTTP
721// network activity. It supports calculating of the benefit of a prefetch, as
722// well as recording what prefetched hostname resolutions might be potentially
723// helpful during the next chrome-startup.
724//-----------------------------------------------------------------------------
725
726void Predictor::LearnAboutInitialNavigation(const GURL& url) {
727 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
728 if (!predictor_enabled_ || NULL == initial_observer_.get() )
729 return;
730 initial_observer_->Append(url, this);
731}
732
733// This API is only used in the browser process.
734// It is called from an IPC message originating in the renderer. It currently
735// includes both Page-Scan, and Link-Hover prefetching.
736// TODO(jar): Separate out link-hover prefetching, and page-scan results.
737void Predictor::DnsPrefetchList(const NameList& hostnames) {
738 // TODO(jar): Push GURL transport further back into renderer, but this will
739 // require a Webkit change in the observer :-/.
740 UrlList urls;
741 for (NameList::const_iterator it = hostnames.begin();
742 it < hostnames.end();
743 ++it) {
744 urls.push_back(GURL("http://" + *it + ":80"));
745 }
746
747 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
748 DnsPrefetchMotivatedList(urls, UrlInfo::PAGE_SCAN_MOTIVATED);
749}
750
751void Predictor::DnsPrefetchMotivatedList(
752 const UrlList& urls,
753 UrlInfo::ResolutionMotivation motivation) {
754 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) ||
755 BrowserThread::CurrentlyOn(BrowserThread::IO));
756 if (!predictor_enabled_)
757 return;
758
759 if (BrowserThread::CurrentlyOn(BrowserThread::IO)) {
760 ResolveList(urls, motivation);
761 } else {
762 BrowserThread::PostTask(
763 BrowserThread::IO,
764 FROM_HERE,
765 base::Bind(&Predictor::ResolveList, base::Unretained(this),
766 urls, motivation));
767 }
768}
769
770//-----------------------------------------------------------------------------
771// Functions to handle saving of hostnames from one session to the next, to
772// expedite startup times.
773
774static void SaveDnsPrefetchStateForNextStartupAndTrimOnIOThread(
775 base::ListValue* startup_list,
776 base::ListValue* referral_list,
777 base::WaitableEvent* completion,
778 Predictor* predictor) {
779 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
780
781 if (NULL == predictor) {
782 completion->Signal();
783 return;
784 }
785 predictor->SaveDnsPrefetchStateForNextStartupAndTrim(
786 startup_list, referral_list, completion);
787}
788
789void Predictor::SaveStateForNextStartupAndTrim(PrefService* prefs) {
790 if (!predictor_enabled_)
791 return;
792
793 base::WaitableEvent completion(true, false);
794
795 ListPrefUpdate update_startup_list(prefs, prefs::kDnsPrefetchingStartupList);
796 ListPrefUpdate update_referral_list(prefs,
797 prefs::kDnsPrefetchingHostReferralList);
798 if (BrowserThread::CurrentlyOn(BrowserThread::IO)) {
799 SaveDnsPrefetchStateForNextStartupAndTrimOnIOThread(
800 update_startup_list.Get(),
801 update_referral_list.Get(),
802 &completion,
803 this);
804 } else {
805 bool posted = BrowserThread::PostTask(
806 BrowserThread::IO,
807 FROM_HERE,
808 base::Bind(
[email protected]7296f2762011-11-21 19:23:44809 &SaveDnsPrefetchStateForNextStartupAndTrimOnIOThread,
[email protected]67372ecf2011-09-10 01:30:46810 update_startup_list.Get(),
811 update_referral_list.Get(),
812 &completion,
813 this));
814
815 // TODO(jar): Synchronous waiting for the IO thread is a potential source
816 // to deadlocks and should be investigated. See http://crbug.com/78451.
817 DCHECK(posted);
818 if (posted)
819 completion.Wait();
820 }
821}
822
823void Predictor::SaveDnsPrefetchStateForNextStartupAndTrim(
824 base::ListValue* startup_list,
825 base::ListValue* referral_list,
826 base::WaitableEvent* completion) {
827 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
828 if (initial_observer_.get())
829 initial_observer_->GetInitialDnsResolutionList(startup_list);
830
831 // Do at least one trim at shutdown, in case the user wasn't running long
832 // enough to do any regular trimming of referrers.
833 TrimReferrersNow();
834 SerializeReferrers(referral_list);
835
836 completion->Signal();
837}
838
839void Predictor::EnablePredictor(bool enable) {
840 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) ||
841 BrowserThread::CurrentlyOn(BrowserThread::IO));
842
843 if (BrowserThread::CurrentlyOn(BrowserThread::IO)) {
844 EnablePredictorOnIOThread(enable);
845 } else {
846 BrowserThread::PostTask(
847 BrowserThread::IO,
848 FROM_HERE,
849 base::Bind(&Predictor::EnablePredictorOnIOThread,
850 base::Unretained(this), enable));
851 }
852}
853
854void Predictor::EnablePredictorOnIOThread(bool enable) {
855 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
856 predictor_enabled_ = enable;
857}
858
859void Predictor::PredictFrameSubresources(const GURL& url) {
860 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) ||
861 BrowserThread::CurrentlyOn(BrowserThread::IO));
862 if (!predictor_enabled_)
863 return;
864 DCHECK_EQ(url.GetWithEmptyPath(), url);
865 // Add one pass through the message loop to allow current navigation to
866 // proceed.
867 if (BrowserThread::CurrentlyOn(BrowserThread::IO)) {
868 PrepareFrameSubresources(url);
869 } else {
870 BrowserThread::PostTask(
871 BrowserThread::IO,
872 FROM_HERE,
873 base::Bind(&Predictor::PrepareFrameSubresources,
874 base::Unretained(this), url));
875 }
876}
877
878enum SubresourceValue {
879 PRECONNECTION,
880 PRERESOLUTION,
881 TOO_NEW,
882 SUBRESOURCE_VALUE_MAX
883};
884
885void Predictor::PrepareFrameSubresources(const GURL& url) {
886 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
887 DCHECK_EQ(url.GetWithEmptyPath(), url);
888 Referrers::iterator it = referrers_.find(url);
889 if (referrers_.end() == it) {
890 // Only when we don't know anything about this url, make 2 connections
891 // available. We could do this completely via learning (by prepopulating
892 // the referrer_ list with this expected value), but it would swell the
893 // size of the list with all the "Leaf" nodes in the tree (nodes that don't
894 // load any subresources). If we learn about this resource, we will instead
895 // provide a more carefully estimated preconnection count.
[email protected]7c46a7082012-01-14 01:24:36896 if (preconnect_enabled_) {
897 PreconnectOnIOThread(url, UrlInfo::SELF_REFERAL_MOTIVATED, 2,
898 url_request_context_getter_);
899 }
[email protected]67372ecf2011-09-10 01:30:46900 return;
901 }
902
903 Referrer* referrer = &(it->second);
904 referrer->IncrementUseCount();
905 const UrlInfo::ResolutionMotivation motivation =
906 UrlInfo::LEARNED_REFERAL_MOTIVATED;
907 for (Referrer::iterator future_url = referrer->begin();
908 future_url != referrer->end(); ++future_url) {
909 SubresourceValue evalution(TOO_NEW);
910 double connection_expectation = future_url->second.subresource_use_rate();
911 UMA_HISTOGRAM_CUSTOM_COUNTS("Net.PreconnectSubresourceExpectation",
912 static_cast<int>(connection_expectation * 100),
913 10, 5000, 50);
914 future_url->second.ReferrerWasObserved();
915 if (preconnect_enabled_ &&
916 connection_expectation > kPreconnectWorthyExpectedValue) {
917 evalution = PRECONNECTION;
918 future_url->second.IncrementPreconnectionCount();
919 int count = static_cast<int>(std::ceil(connection_expectation));
920 if (url.host() == future_url->first.host())
921 ++count;
[email protected]7c46a7082012-01-14 01:24:36922 PreconnectOnIOThread(future_url->first, motivation, count,
923 url_request_context_getter_);
[email protected]67372ecf2011-09-10 01:30:46924 } else if (connection_expectation > kDNSPreresolutionWorthyExpectedValue) {
925 evalution = PRERESOLUTION;
926 future_url->second.preresolution_increment();
927 UrlInfo* queued_info = AppendToResolutionQueue(future_url->first,
928 motivation);
929 if (queued_info)
930 queued_info->SetReferringHostname(url);
931 }
932 UMA_HISTOGRAM_ENUMERATION("Net.PreconnectSubresourceEval", evalution,
933 SUBRESOURCE_VALUE_MAX);
934 }
935}
936
937void Predictor::OnLookupFinished(LookupRequest* request, const GURL& url,
938 bool found) {
939 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
940
941 LookupFinished(request, url, found);
942 pending_lookups_.erase(request);
943 delete request;
944
945 StartSomeQueuedResolutions();
946}
947
948void Predictor::LookupFinished(LookupRequest* request, const GURL& url,
949 bool found) {
950 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
951 UrlInfo* info = &results_[url];
952 DCHECK(info->HasUrl(url));
953 if (info->is_marked_to_delete()) {
954 results_.erase(url);
955 } else {
956 if (found)
957 info->SetFoundState();
958 else
959 info->SetNoSuchNameState();
960 }
961}
962
[email protected]74be069e82010-06-25 00:12:49963UrlInfo* Predictor::AppendToResolutionQueue(
[email protected]c5629c32010-06-23 01:22:43964 const GURL& url,
[email protected]74be069e82010-06-25 00:12:49965 UrlInfo::ResolutionMotivation motivation) {
[email protected]ba4f1132010-10-09 02:02:35966 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
[email protected]c5629c32010-06-23 01:22:43967 DCHECK(url.has_host());
initial.commit09911bf2008-07-26 23:55:29968
[email protected]1933eb202009-02-19 18:23:25969 if (shutdown_)
970 return NULL;
971
[email protected]74be069e82010-06-25 00:12:49972 UrlInfo* info = &results_[url];
[email protected]c5629c32010-06-23 01:22:43973 info->SetUrl(url); // Initialize or DCHECK.
initial.commit09911bf2008-07-26 23:55:29974 // TODO(jar): I need to discard names that have long since expired.
975 // Currently we only add to the domain map :-/
976
[email protected]c5629c32010-06-23 01:22:43977 DCHECK(info->HasUrl(url));
initial.commit09911bf2008-07-26 23:55:29978
[email protected]760d970a2010-05-18 00:39:18979 if (!info->NeedsDnsUpdate()) {
initial.commit09911bf2008-07-26 23:55:29980 info->DLogResultsStats("DNS PrefetchNotUpdated");
[email protected]21dae9b2008-11-06 23:32:53981 return NULL;
initial.commit09911bf2008-07-26 23:55:29982 }
983
[email protected]21dae9b2008-11-06 23:32:53984 info->SetQueuedState(motivation);
[email protected]c5629c32010-06-23 01:22:43985 work_queue_.Push(url, motivation);
[email protected]ec86bea2009-12-08 18:35:14986 StartSomeQueuedResolutions();
[email protected]21dae9b2008-11-06 23:32:53987 return info;
initial.commit09911bf2008-07-26 23:55:29988}
989
[email protected]67372ecf2011-09-10 01:30:46990bool Predictor::CongestionControlPerformed(UrlInfo* info) {
991 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
992 // Note: queue_duration is ONLY valid after we go to assigned state.
993 if (info->queue_duration() < max_dns_queue_delay_)
994 return false;
995 // We need to discard all entries in our queue, as we're keeping them waiting
996 // too long. By doing this, we'll have a chance to quickly service urgent
997 // resolutions, and not have a bogged down system.
998 while (true) {
999 info->RemoveFromQueue();
1000 if (work_queue_.IsEmpty())
1001 break;
1002 info = &results_[work_queue_.Pop()];
1003 info->SetAssignedState();
1004 }
1005 return true;
1006}
1007
[email protected]74be069e82010-06-25 00:12:491008void Predictor::StartSomeQueuedResolutions() {
[email protected]ba4f1132010-10-09 02:02:351009 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
[email protected]fd2f8afe2009-06-11 21:53:551010
[email protected]a20bc092009-06-05 01:34:201011 while (!work_queue_.IsEmpty() &&
[email protected]74be069e82010-06-25 00:12:491012 pending_lookups_.size() < max_concurrent_dns_lookups_) {
[email protected]c5629c32010-06-23 01:22:431013 const GURL url(work_queue_.Pop());
[email protected]74be069e82010-06-25 00:12:491014 UrlInfo* info = &results_[url];
[email protected]c5629c32010-06-23 01:22:431015 DCHECK(info->HasUrl(url));
initial.commit09911bf2008-07-26 23:55:291016 info->SetAssignedState();
1017
[email protected]ec86bea2009-12-08 18:35:141018 if (CongestionControlPerformed(info)) {
[email protected]a20bc092009-06-05 01:34:201019 DCHECK(work_queue_.IsEmpty());
1020 return;
1021 }
1022
[email protected]c5629c32010-06-23 01:22:431023 LookupRequest* request = new LookupRequest(this, host_resolver_, url);
[email protected]85398532009-06-16 21:32:181024 int status = request->Start();
1025 if (status == net::ERR_IO_PENDING) {
[email protected]fd2f8afe2009-06-11 21:53:551026 // Will complete asynchronously.
[email protected]1933eb202009-02-19 18:23:251027 pending_lookups_.insert(request);
1028 peak_pending_lookups_ = std::max(peak_pending_lookups_,
1029 pending_lookups_.size());
1030 } else {
[email protected]221f33362009-06-29 20:46:481031 // Completed synchronously (was already cached by HostResolver), or else
[email protected]85398532009-06-16 21:32:181032 // there was (equivalently) some network error that prevents us from
1033 // finding the name. Status net::OK means it was "found."
[email protected]c5629c32010-06-23 01:22:431034 LookupFinished(request, url, status == net::OK);
[email protected]1933eb202009-02-19 18:23:251035 delete request;
1036 }
1037 }
initial.commit09911bf2008-07-26 23:55:291038}
1039
[email protected]579f2a12011-04-06 16:27:311040void Predictor::TrimReferrers() {
1041 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
1042 if (!urls_being_trimmed_.empty())
1043 return; // There is incremental trimming in progress already.
1044
1045 // Check to see if it is time to trim yet.
1046 base::TimeTicks now = base::TimeTicks::Now();
1047 if (now < next_trim_time_)
1048 return;
[email protected]4890fe592012-01-27 09:19:031049 next_trim_time_ = now + TimeDelta::FromHours(kDurationBetweenTrimmingsHours);
[email protected]579f2a12011-04-06 16:27:311050
1051 LoadUrlsForTrimming();
1052 PostIncrementalTrimTask();
1053}
1054
1055void Predictor::LoadUrlsForTrimming() {
1056 DCHECK(urls_being_trimmed_.empty());
1057 for (Referrers::const_iterator it = referrers_.begin();
1058 it != referrers_.end(); ++it)
1059 urls_being_trimmed_.push_back(it->first);
1060 UMA_HISTOGRAM_COUNTS("Net.PredictionTrimSize", urls_being_trimmed_.size());
1061}
1062
1063void Predictor::PostIncrementalTrimTask() {
1064 if (urls_being_trimmed_.empty())
1065 return;
[email protected]26b9973962012-01-28 00:57:001066 const TimeDelta kDurationBetweenTrimmingIncrements =
1067 TimeDelta::FromSeconds(kDurationBetweenTrimmingIncrementsSeconds);
[email protected]579f2a12011-04-06 16:27:311068 MessageLoop::current()->PostDelayedTask(
1069 FROM_HERE,
[email protected]12465ec2011-11-17 21:18:231070 base::Bind(&Predictor::IncrementalTrimReferrers,
1071 weak_factory_->GetWeakPtr(), false),
[email protected]26b9973962012-01-28 00:57:001072 kDurationBetweenTrimmingIncrements);
[email protected]579f2a12011-04-06 16:27:311073}
1074
1075void Predictor::IncrementalTrimReferrers(bool trim_all_now) {
1076 size_t trim_count = urls_being_trimmed_.size();
1077 if (!trim_all_now)
1078 trim_count = std::min(trim_count, kUrlsTrimmedPerIncrement);
1079 while (trim_count-- != 0) {
1080 Referrers::iterator it = referrers_.find(urls_being_trimmed_.back());
1081 urls_being_trimmed_.pop_back();
1082 if (it == referrers_.end())
1083 continue; // Defensive code: It got trimmed away already.
1084 if (!it->second.Trim(kReferrerTrimRatio, kDiscardableExpectedValue))
1085 referrers_.erase(it);
1086 }
1087 PostIncrementalTrimTask();
1088}
[email protected]a20bc092009-06-05 01:34:201089
[email protected]67372ecf2011-09-10 01:30:461090// ---------------------- End IO methods. -------------------------------------
1091
1092//-----------------------------------------------------------------------------
[email protected]a20bc092009-06-05 01:34:201093
[email protected]74be069e82010-06-25 00:12:491094Predictor::HostNameQueue::HostNameQueue() {
[email protected]a20bc092009-06-05 01:34:201095}
1096
[email protected]74be069e82010-06-25 00:12:491097Predictor::HostNameQueue::~HostNameQueue() {
[email protected]a20bc092009-06-05 01:34:201098}
1099
[email protected]74be069e82010-06-25 00:12:491100void Predictor::HostNameQueue::Push(const GURL& url,
1101 UrlInfo::ResolutionMotivation motivation) {
[email protected]a20bc092009-06-05 01:34:201102 switch (motivation) {
[email protected]74be069e82010-06-25 00:12:491103 case UrlInfo::STATIC_REFERAL_MOTIVATED:
1104 case UrlInfo::LEARNED_REFERAL_MOTIVATED:
1105 case UrlInfo::MOUSE_OVER_MOTIVATED:
[email protected]c5629c32010-06-23 01:22:431106 rush_queue_.push(url);
[email protected]a20bc092009-06-05 01:34:201107 break;
1108
1109 default:
[email protected]c5629c32010-06-23 01:22:431110 background_queue_.push(url);
[email protected]a20bc092009-06-05 01:34:201111 break;
1112 }
1113}
1114
[email protected]74be069e82010-06-25 00:12:491115bool Predictor::HostNameQueue::IsEmpty() const {
[email protected]a20bc092009-06-05 01:34:201116 return rush_queue_.empty() && background_queue_.empty();
1117}
1118
[email protected]74be069e82010-06-25 00:12:491119GURL Predictor::HostNameQueue::Pop() {
[email protected]a20bc092009-06-05 01:34:201120 DCHECK(!IsEmpty());
[email protected]c5629c32010-06-23 01:22:431121 std::queue<GURL> *queue(rush_queue_.empty() ? &background_queue_
1122 : &rush_queue_);
1123 GURL url(queue->front());
1124 queue->pop();
1125 return url;
[email protected]a20bc092009-06-05 01:34:201126}
1127
[email protected]67372ecf2011-09-10 01:30:461128//-----------------------------------------------------------------------------
1129// Member definitions for InitialObserver class.
1130
1131Predictor::InitialObserver::InitialObserver() {
[email protected]c02c853d72010-08-07 06:23:241132}
1133
[email protected]67372ecf2011-09-10 01:30:461134Predictor::InitialObserver::~InitialObserver() {
1135}
[email protected]1455ccf12010-08-18 16:32:141136
[email protected]67372ecf2011-09-10 01:30:461137void Predictor::InitialObserver::Append(const GURL& url,
1138 Predictor* predictor) {
1139 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
1140
1141 // TODO(rlp): Do we really need the predictor check here?
1142 if (NULL == predictor)
1143 return;
1144 if (kStartupResolutionCount <= first_navigations_.size())
1145 return;
1146
1147 DCHECK(url.SchemeIs("http") || url.SchemeIs("https"));
1148 DCHECK_EQ(url, Predictor::CanonicalizeUrl(url));
1149 if (first_navigations_.find(url) == first_navigations_.end())
1150 first_navigations_[url] = base::TimeTicks::Now();
1151}
1152
1153void Predictor::InitialObserver::GetInitialDnsResolutionList(
1154 base::ListValue* startup_list) {
1155 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
1156 DCHECK(startup_list);
1157 startup_list->Clear();
1158 DCHECK_EQ(0u, startup_list->GetSize());
1159 startup_list->Append(
1160 new base::FundamentalValue(kPredictorStartupFormatVersion));
1161 for (FirstNavigations::iterator it = first_navigations_.begin();
1162 it != first_navigations_.end();
1163 ++it) {
1164 DCHECK(it->first == Predictor::CanonicalizeUrl(it->first));
1165 startup_list->Append(new StringValue(it->first.spec()));
1166 }
1167}
1168
1169void Predictor::InitialObserver::GetFirstResolutionsHtml(
1170 std::string* output) {
1171 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
1172
1173 UrlInfo::UrlInfoTable resolution_list;
1174 {
1175 for (FirstNavigations::iterator it(first_navigations_.begin());
1176 it != first_navigations_.end();
1177 it++) {
1178 UrlInfo info;
1179 info.SetUrl(it->first);
1180 info.set_time(it->second);
1181 resolution_list.push_back(info);
1182 }
1183 }
1184 UrlInfo::GetHtmlTable(resolution_list,
1185 "Future startups will prefetch DNS records for ", false, output);
1186}
1187
1188//-----------------------------------------------------------------------------
[email protected]1455ccf12010-08-18 16:32:141189// Helper functions
[email protected]67372ecf2011-09-10 01:30:461190//-----------------------------------------------------------------------------
[email protected]1455ccf12010-08-18 16:32:141191
1192// static
1193GURL Predictor::CanonicalizeUrl(const GURL& url) {
1194 if (!url.has_host())
1195 return GURL::EmptyGURL();
1196
1197 std::string scheme;
1198 if (url.has_scheme()) {
1199 scheme = url.scheme();
1200 if (scheme != "http" && scheme != "https")
1201 return GURL::EmptyGURL();
1202 if (url.has_port())
1203 return url.GetWithEmptyPath();
1204 } else {
1205 scheme = "http";
1206 }
1207
1208 // If we omit a port, it will default to 80 or 443 as appropriate.
1209 std::string colon_plus_port;
1210 if (url.has_port())
1211 colon_plus_port = ":" + url.port();
1212
1213 return GURL(scheme + "://" + url.host() + colon_plus_port);
1214}
1215
[email protected]7c46a7082012-01-14 01:24:361216void SimplePredictor::InitNetworkPredictor(
1217 PrefService* user_prefs,
1218 PrefService* local_state,
1219 IOThread* io_thread,
1220 net::URLRequestContextGetter* getter) {
[email protected]67372ecf2011-09-10 01:30:461221 // Empty function for unittests.
1222}
1223
1224void SimplePredictor::ShutdownOnUIThread(PrefService* user_prefs) {
1225 SetShutdown(true);
1226}
[email protected]1455ccf12010-08-18 16:32:141227
initial.commit09911bf2008-07-26 23:55:291228} // namespace chrome_browser_net