blob: ffc5e573b9074b1c439b4c9b3da2b5c0eee8290a [file] [log] [blame]
[email protected]48329f92011-03-28 19:38:221// Copyright (c) 2011 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]1933eb202009-02-19 18:23:2512#include "base/compiler_specific.h"
[email protected]835d7c82010-10-14 04:38:3813#include "base/metrics/histogram.h"
[email protected]da2f68f2011-05-11 21:40:5514#include "base/stringprintf.h"
[email protected]1933eb202009-02-19 18:23:2515#include "base/time.h"
[email protected]c02c853d72010-08-07 06:23:2416#include "base/values.h"
[email protected]760d970a2010-05-18 00:39:1817#include "chrome/browser/net/preconnect.h"
[email protected]5f945a0e2011-03-01 17:47:5318#include "content/browser/browser_thread.h"
[email protected]1933eb202009-02-19 18:23:2519#include "net/base/address_list.h"
20#include "net/base/completion_callback.h"
[email protected]760d970a2010-05-18 00:39:1821#include "net/base/host_port_pair.h"
[email protected]1933eb202009-02-19 18:23:2522#include "net/base/host_resolver.h"
23#include "net/base/net_errors.h"
[email protected]9e743cd2010-03-16 07:03:5324#include "net/base/net_log.h"
[email protected]b11279e2011-06-06 18:25:1925#include "net/base/single_request_host_resolver.h"
[email protected]e1acf6f2008-10-27 20:43:3326
[email protected]602faf3c2009-06-27 14:35:4427using base::TimeDelta;
28
initial.commit09911bf2008-07-26 23:55:2929namespace chrome_browser_net {
30
[email protected]f4ef861ba2010-07-28 22:37:2331// static
[email protected]1455ccf12010-08-18 16:32:1432const double Predictor::kPreconnectWorthyExpectedValue = 0.8;
[email protected]f4ef861ba2010-07-28 22:37:2333// static
[email protected]1455ccf12010-08-18 16:32:1434const double Predictor::kDNSPreresolutionWorthyExpectedValue = 0.1;
[email protected]f4ef861ba2010-07-28 22:37:2335// static
[email protected]579f2a12011-04-06 16:27:3136const double Predictor::kDiscardableExpectedValue = 0.05;
37// The goal is of trimming is to to reduce the importance (number of expected
38// subresources needed) by a factor of 2 after about 24 hours of uptime. We will
39// trim roughly once-an-hour of uptime. The ratio to use in each trim operation
40// is then the 24th root of 0.5. If a user only surfs for 4 hours a day, then
41// after about 6 days they will have halved all their estimates of subresource
42// connections. Once this falls below kDiscardableExpectedValue the referrer
43// will be discarded.
44// TODO(jar): Measure size of referrer lists in the field. Consider an adaptive
45// system that uses a higher trim ratio when the list is large.
46// static
47const double Predictor::kReferrerTrimRatio = 0.97153;
[email protected]f4ef861ba2010-07-28 22:37:2348
[email protected]579f2a12011-04-06 16:27:3149// static
50const TimeDelta Predictor::kDurationBetweenTrimmings = TimeDelta::FromHours(1);
51// static
52const TimeDelta Predictor::kDurationBetweenTrimmingIncrements =
53 TimeDelta::FromSeconds(15);
54// static
55const size_t Predictor::kUrlsTrimmedPerIncrement = 5u;
[email protected]f4ef861ba2010-07-28 22:37:2356
[email protected]74be069e82010-06-25 00:12:4957class Predictor::LookupRequest {
[email protected]1933eb202009-02-19 18:23:2558 public:
[email protected]74be069e82010-06-25 00:12:4959 LookupRequest(Predictor* predictor,
[email protected]8a00f00a2009-06-12 00:49:3860 net::HostResolver* host_resolver,
[email protected]c5629c32010-06-23 01:22:4361 const GURL& url)
[email protected]1933eb202009-02-19 18:23:2562 : ALLOW_THIS_IN_INITIALIZER_LIST(
[email protected]579f2a12011-04-06 16:27:3163 net_callback_(this, &LookupRequest::OnLookupFinished)),
[email protected]74be069e82010-06-25 00:12:4964 predictor_(predictor),
[email protected]c5629c32010-06-23 01:22:4365 url_(url),
[email protected]8a00f00a2009-06-12 00:49:3866 resolver_(host_resolver) {
initial.commit09911bf2008-07-26 23:55:2967 }
[email protected]1933eb202009-02-19 18:23:2568
[email protected]85398532009-06-16 21:32:1869 // Return underlying network resolver status.
70 // net::OK ==> Host was found synchronously.
71 // net:ERR_IO_PENDING ==> Network will callback later with result.
72 // anything else ==> Host was not found synchronously.
73 int Start() {
[email protected]930cc742010-09-15 22:54:1074 net::HostResolver::RequestInfo resolve_info(
75 net::HostPortPair::FromURL(url_));
[email protected]2884a462009-06-15 05:08:4276
77 // Make a note that this is a speculative resolve request. This allows us
78 // to separate it from real navigations in the observer's callback, and
79 // lets the HostResolver know it can de-prioritize it.
80 resolve_info.set_is_speculative(true);
[email protected]ec08bb22009-08-12 00:25:1281 return resolver_.Resolve(
[email protected]9e743cd2010-03-16 07:03:5382 resolve_info, &addresses_, &net_callback_, net::BoundNetLog());
[email protected]1933eb202009-02-19 18:23:2583 }
84
85 private:
86 void OnLookupFinished(int result) {
[email protected]74be069e82010-06-25 00:12:4987 predictor_->OnLookupFinished(this, url_, result == net::OK);
[email protected]1933eb202009-02-19 18:23:2588 }
89
90 // HostResolver will call us using this callback when resolution is complete.
91 net::CompletionCallbackImpl<LookupRequest> net_callback_;
92
[email protected]74be069e82010-06-25 00:12:4993 Predictor* predictor_; // The predictor which started us.
[email protected]1933eb202009-02-19 18:23:2594
[email protected]c5629c32010-06-23 01:22:4395 const GURL url_; // Hostname to resolve.
[email protected]8a00f00a2009-06-12 00:49:3896 net::SingleRequestHostResolver resolver_;
[email protected]1933eb202009-02-19 18:23:2597 net::AddressList addresses_;
98
99 DISALLOW_COPY_AND_ASSIGN(LookupRequest);
100};
101
[email protected]9f07c9b72011-08-19 15:44:32102Predictor::Predictor(net::HostResolver* host_resolver,
103 TimeDelta max_dns_queue_delay,
104 size_t max_concurrent,
105 bool preconnect_enabled)
106 : peak_pending_lookups_(0),
[email protected]760d970a2010-05-18 00:39:18107 shutdown_(false),
[email protected]9f07c9b72011-08-19 15:44:32108 max_concurrent_dns_lookups_(max_concurrent),
109 max_dns_queue_delay_(max_dns_queue_delay),
110 host_resolver_(host_resolver),
[email protected]1455ccf12010-08-18 16:32:14111 preconnect_enabled_(preconnect_enabled),
[email protected]579f2a12011-04-06 16:27:31112 consecutive_omnibox_preconnect_count_(0),
[email protected]9f07c9b72011-08-19 15:44:32113 next_trim_time_(base::TimeTicks::Now() + kDurationBetweenTrimmings),
114 ALLOW_THIS_IN_INITIALIZER_LIST(trim_task_factory_(this)) {
[email protected]1933eb202009-02-19 18:23:25115}
116
[email protected]74be069e82010-06-25 00:12:49117Predictor::~Predictor() {
[email protected]1933eb202009-02-19 18:23:25118 DCHECK(shutdown_);
119}
120
[email protected]9f07c9b72011-08-19 15:44:32121void Predictor::Shutdown() {
122 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
123 DCHECK(!shutdown_);
124 shutdown_ = true;
125
126 std::set<LookupRequest*>::iterator it;
127 for (it = pending_lookups_.begin(); it != pending_lookups_.end(); ++it)
128 delete *it;
initial.commit09911bf2008-07-26 23:55:29129}
130
[email protected]9f07c9b72011-08-19 15:44:32131// Overloaded Resolve() to take a vector of names.
132void Predictor::ResolveList(const UrlList& urls,
133 UrlInfo::ResolutionMotivation motivation) {
134 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
[email protected]fd2f8afe2009-06-11 21:53:55135
[email protected]9f07c9b72011-08-19 15:44:32136 for (UrlList::const_iterator it = urls.begin(); it < urls.end(); ++it) {
137 AppendToResolutionQueue(*it, motivation);
[email protected]c5629c32010-06-23 01:22:43138 }
initial.commit09911bf2008-07-26 23:55:29139}
140
[email protected]9f07c9b72011-08-19 15:44:32141// Basic Resolve() takes an invidual name, and adds it
142// to the queue.
143void Predictor::Resolve(const GURL& url,
144 UrlInfo::ResolutionMotivation motivation) {
145 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
146 if (!url.has_host())
147 return;
148 AppendToResolutionQueue(url, motivation);
149}
150
151void Predictor::LearnFromNavigation(const GURL& referring_url,
152 const GURL& target_url) {
153 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
154 DCHECK_EQ(referring_url, Predictor::CanonicalizeUrl(referring_url));
155 DCHECK_NE(referring_url, GURL::EmptyGURL());
156 DCHECK_EQ(target_url, Predictor::CanonicalizeUrl(target_url));
157 DCHECK_NE(target_url, GURL::EmptyGURL());
158
159 referrers_[referring_url].SuggestHost(target_url);
160 // Possibly do some referrer trimming.
161 TrimReferrers();
162}
163
164enum SubresourceValue {
165 PRECONNECTION,
166 PRERESOLUTION,
167 TOO_NEW,
168 SUBRESOURCE_VALUE_MAX
169};
170
[email protected]1455ccf12010-08-18 16:32:14171void Predictor::AnticipateOmniboxUrl(const GURL& url, bool preconnectable) {
172 std::string host = url.HostNoBrackets();
173 bool is_new_host_request = (host != last_omnibox_host_);
174 last_omnibox_host_ = host;
175
176 UrlInfo::ResolutionMotivation motivation(UrlInfo::OMNIBOX_MOTIVATED);
177 base::TimeTicks now = base::TimeTicks::Now();
178
179 if (preconnect_enabled()) {
180 if (preconnectable && !is_new_host_request) {
181 ++consecutive_omnibox_preconnect_count_;
182 // The omnibox suggests a search URL (for which we can preconnect) after
183 // one or two characters are typed, even though such typing often (1 in
184 // 3?) becomes a real URL. This code waits till is has more evidence of a
185 // preconnectable URL (search URL) before forming a preconnection, so as
186 // to reduce the useless preconnect rate.
187 // Perchance this logic should be pushed back into the omnibox, where the
188 // actual characters typed, such as a space, can better forcast whether
189 // we need to search/preconnect or not. By waiting for at least 4
190 // characters in a row that have lead to a search proposal, we avoid
191 // preconnections for a prefix like "www." and we also wait until we have
192 // at least a 4 letter word to search for.
193 // Each character typed appears to induce 2 calls to
194 // AnticipateOmniboxUrl(), so we double 4 characters and limit at 8
195 // requests.
196 // TODO(jar): Use an A/B test to optimize this.
197 const int kMinConsecutiveRequests = 8;
198 if (consecutive_omnibox_preconnect_count_ >= kMinConsecutiveRequests) {
[email protected]ef42e302011-06-02 18:36:18199 // TODO(jar): Perhaps we should do a GET to leave the socket open in the
200 // pool. Currently, we just do a connect, which MAY be reset if we
201 // don't use it in 10 secondes!!! As a result, we may do more
202 // connections, and actually cost the server more than if we did a real
203 // get with a fake request (/gen_204 might be the good path on Google).
204 const int kMaxSearchKeepaliveSeconds(10);
[email protected]1455ccf12010-08-18 16:32:14205 if ((now - last_omnibox_preconnect_).InSeconds() <
206 kMaxSearchKeepaliveSeconds)
207 return; // We've done a preconnect recently.
208 last_omnibox_preconnect_ = now;
[email protected]0707e2a2010-11-30 01:57:04209 const int kConnectionsNeeded = 1;
[email protected]102e27c2011-02-23 01:01:31210 PreconnectOnUIThread(CanonicalizeUrl(url), motivation,
211 kConnectionsNeeded);
[email protected]1455ccf12010-08-18 16:32:14212 return; // Skip pre-resolution, since we'll open a connection.
213 }
214 } else {
215 consecutive_omnibox_preconnect_count_ = 0;
216 }
217 }
218
219 // Fall through and consider pre-resolution.
220
221 // Omnibox tends to call in pairs (just a few milliseconds apart), and we
222 // really don't need to keep resolving a name that often.
223 // TODO(jar): A/B tests could check for perf impact of the early returns.
224 if (!is_new_host_request) {
225 const int kMinPreresolveSeconds(10);
226 if (kMinPreresolveSeconds > (now - last_omnibox_preresolve_).InSeconds())
227 return;
228 }
229 last_omnibox_preresolve_ = now;
230
231 // Perform at least DNS pre-resolution.
[email protected]ba4f1132010-10-09 02:02:35232 BrowserThread::PostTask(
233 BrowserThread::IO,
[email protected]1455ccf12010-08-18 16:32:14234 FROM_HERE,
[email protected]9f07c9b72011-08-19 15:44:32235 NewRunnableMethod(this, &Predictor::Resolve, CanonicalizeUrl(url),
236 motivation));
[email protected]1455ccf12010-08-18 16:32:14237}
238
[email protected]e326922d2010-09-03 09:08:10239void Predictor::PreconnectUrlAndSubresources(const GURL& url) {
240 if (preconnect_enabled()) {
241 std::string host = url.HostNoBrackets();
242 UrlInfo::ResolutionMotivation motivation(UrlInfo::EARLY_LOAD_MOTIVATED);
[email protected]0707e2a2010-11-30 01:57:04243 const int kConnectionsNeeded = 1;
[email protected]102e27c2011-02-23 01:01:31244 PreconnectOnUIThread(CanonicalizeUrl(url), motivation,
245 kConnectionsNeeded);
[email protected]e326922d2010-09-03 09:08:10246 PredictFrameSubresources(url.GetWithEmptyPath());
247 }
248}
249
[email protected]9f07c9b72011-08-19 15:44:32250void Predictor::PredictFrameSubresources(const GURL& url) {
251 DCHECK_EQ(url.GetWithEmptyPath(), url);
252 // Add one pass through the message loop to allow current navigation to
253 // proceed.
[email protected]ba4f1132010-10-09 02:02:35254 BrowserThread::PostTask(
255 BrowserThread::IO,
[email protected]9008c86f2010-08-06 07:10:24256 FROM_HERE,
[email protected]9f07c9b72011-08-19 15:44:32257 NewRunnableMethod(this, &Predictor::PrepareFrameSubresources, url));
[email protected]9008c86f2010-08-06 07:10:24258}
259
[email protected]9f07c9b72011-08-19 15:44:32260void Predictor::PrepareFrameSubresources(const GURL& url) {
[email protected]b1af3432011-08-19 09:36:14261 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
[email protected]9f07c9b72011-08-19 15:44:32262 DCHECK_EQ(url.GetWithEmptyPath(), url);
263 Referrers::iterator it = referrers_.find(url);
264 if (referrers_.end() == it) {
265 // Only when we don't know anything about this url, make 2 connections
266 // available. We could do this completely via learning (by prepopulating
267 // the referrer_ list with this expected value), but it would swell the
268 // size of the list with all the "Leaf" nodes in the tree (nodes that don't
269 // load any subresources). If we learn about this resource, we will instead
270 // provide a more carefully estimated preconnection count.
271 if (preconnect_enabled_)
272 PreconnectOnIOThread(url, UrlInfo::SELF_REFERAL_MOTIVATED, 2);
273 return;
initial.commit09911bf2008-07-26 23:55:29274 }
[email protected]b1af3432011-08-19 09:36:14275
[email protected]9f07c9b72011-08-19 15:44:32276 Referrer* referrer = &(it->second);
277 referrer->IncrementUseCount();
278 const UrlInfo::ResolutionMotivation motivation =
279 UrlInfo::LEARNED_REFERAL_MOTIVATED;
280 for (Referrer::iterator future_url = referrer->begin();
281 future_url != referrer->end(); ++future_url) {
282 SubresourceValue evalution(TOO_NEW);
283 double connection_expectation = future_url->second.subresource_use_rate();
284 UMA_HISTOGRAM_CUSTOM_COUNTS("Net.PreconnectSubresourceExpectation",
285 static_cast<int>(connection_expectation * 100),
286 10, 5000, 50);
287 future_url->second.ReferrerWasObserved();
288 if (preconnect_enabled_ &&
289 connection_expectation > kPreconnectWorthyExpectedValue) {
290 evalution = PRECONNECTION;
291 future_url->second.IncrementPreconnectionCount();
292 int count = static_cast<int>(std::ceil(connection_expectation));
293 if (url.host() == future_url->first.host())
294 ++count;
295 PreconnectOnIOThread(future_url->first, motivation, count);
296 } else if (connection_expectation > kDNSPreresolutionWorthyExpectedValue) {
297 evalution = PRERESOLUTION;
298 future_url->second.preresolution_increment();
299 UrlInfo* queued_info = AppendToResolutionQueue(future_url->first,
300 motivation);
301 if (queued_info)
302 queued_info->SetReferringHostname(url);
[email protected]b1af3432011-08-19 09:36:14303 }
[email protected]9f07c9b72011-08-19 15:44:32304 UMA_HISTOGRAM_ENUMERATION("Net.PreconnectSubresourceEval", evalution,
305 SUBRESOURCE_VALUE_MAX);
[email protected]b1af3432011-08-19 09:36:14306 }
initial.commit09911bf2008-07-26 23:55:29307}
308
initial.commit09911bf2008-07-26 23:55:29309// Provide sort order so all .com's are together, etc.
310struct RightToLeftStringSorter {
[email protected]760d970a2010-05-18 00:39:18311 bool operator()(const GURL& left,
312 const GURL& right) const {
313 return string_compare(left.host(), right.host());
314 }
315
316 static bool string_compare(const std::string& left_host,
[email protected]48329f92011-03-28 19:38:22317 const std::string& right_host) {
[email protected]760d970a2010-05-18 00:39:18318 if (left_host == right_host) return true;
319 size_t left_already_matched = left_host.size();
320 size_t right_already_matched = right_host.size();
[email protected]21dae9b2008-11-06 23:32:53321
322 // Ensure both strings have characters.
323 if (!left_already_matched) return true;
324 if (!right_already_matched) return false;
325
326 // Watch for trailing dot, so we'll always be safe to go one beyond dot.
[email protected]760d970a2010-05-18 00:39:18327 if ('.' == left_host[left_already_matched - 1]) {
328 if ('.' != right_host[right_already_matched - 1])
initial.commit09911bf2008-07-26 23:55:29329 return true;
[email protected]21dae9b2008-11-06 23:32:53330 // Both have dots at end of string.
331 --left_already_matched;
332 --right_already_matched;
333 } else {
[email protected]760d970a2010-05-18 00:39:18334 if ('.' == right_host[right_already_matched - 1])
[email protected]21dae9b2008-11-06 23:32:53335 return false;
336 }
337
338 while (1) {
339 if (!left_already_matched) return true;
340 if (!right_already_matched) return false;
341
[email protected]760d970a2010-05-18 00:39:18342 size_t left_start = left_host.find_last_of('.', left_already_matched - 1);
[email protected]21dae9b2008-11-06 23:32:53343 if (std::string::npos == left_start) {
[email protected]21dae9b2008-11-06 23:32:53344 left_already_matched = left_start = 0;
345 } else {
[email protected]21dae9b2008-11-06 23:32:53346 left_already_matched = left_start;
347 ++left_start; // Don't compare the dot.
348 }
[email protected]760d970a2010-05-18 00:39:18349 size_t right_start = right_host.find_last_of('.',
350 right_already_matched - 1);
[email protected]21dae9b2008-11-06 23:32:53351 if (std::string::npos == right_start) {
[email protected]21dae9b2008-11-06 23:32:53352 right_already_matched = right_start = 0;
353 } else {
[email protected]21dae9b2008-11-06 23:32:53354 right_already_matched = right_start;
355 ++right_start; // Don't compare the dot.
356 }
357
[email protected]760d970a2010-05-18 00:39:18358 int diff = left_host.compare(left_start, left_host.size(),
359 right_host, right_start, right_host.size());
[email protected]21dae9b2008-11-06 23:32:53360 if (diff > 0) return false;
361 if (diff < 0) return true;
initial.commit09911bf2008-07-26 23:55:29362 }
363 }
364};
365
[email protected]74be069e82010-06-25 00:12:49366void Predictor::GetHtmlReferrerLists(std::string* output) {
[email protected]ba4f1132010-10-09 02:02:35367 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
[email protected]21dae9b2008-11-06 23:32:53368 if (referrers_.empty())
369 return;
370
371 // TODO(jar): Remove any plausible JavaScript from names before displaying.
372
[email protected]c5629c32010-06-23 01:22:43373 typedef std::set<GURL, struct RightToLeftStringSorter>
[email protected]760d970a2010-05-18 00:39:18374 SortedNames;
[email protected]21dae9b2008-11-06 23:32:53375 SortedNames sorted_names;
376
377 for (Referrers::iterator it = referrers_.begin();
378 referrers_.end() != it; ++it)
379 sorted_names.insert(it->first);
380
381 output->append("<br><table border>");
[email protected]760d970a2010-05-18 00:39:18382 output->append(
383 "<tr><th>Host for Page</th>"
384 "<th>Page Load<br>Count</th>"
385 "<th>Subresource<br>Navigations</th>"
386 "<th>Subresource<br>PreConnects</th>"
[email protected]f4ef861ba2010-07-28 22:37:23387 "<th>Subresource<br>PreResolves</th>"
[email protected]760d970a2010-05-18 00:39:18388 "<th>Expected<br>Connects</th>"
[email protected]760d970a2010-05-18 00:39:18389 "<th>Subresource Spec</th></tr>");
[email protected]21dae9b2008-11-06 23:32:53390
391 for (SortedNames::iterator it = sorted_names.begin();
392 sorted_names.end() != it; ++it) {
393 Referrer* referrer = &(referrers_[*it]);
[email protected]760d970a2010-05-18 00:39:18394 bool first_set_of_futures = true;
[email protected]c5629c32010-06-23 01:22:43395 for (Referrer::iterator future_url = referrer->begin();
396 future_url != referrer->end(); ++future_url) {
[email protected]760d970a2010-05-18 00:39:18397 output->append("<tr align=right>");
[email protected]a77fa2dc2010-11-15 12:11:11398 if (first_set_of_futures) {
399 base::StringAppendF(output,
400 "<td rowspan=%d>%s</td><td rowspan=%d>%d</td>",
401 static_cast<int>(referrer->size()),
402 it->spec().c_str(),
403 static_cast<int>(referrer->size()),
404 static_cast<int>(referrer->use_count()));
405 }
[email protected]760d970a2010-05-18 00:39:18406 first_set_of_futures = false;
[email protected]a77fa2dc2010-11-15 12:11:11407 base::StringAppendF(output,
[email protected]f4ef861ba2010-07-28 22:37:23408 "<td>%d</td><td>%d</td><td>%d</td><td>%2.3f</td><td>%s</td></tr>",
[email protected]c5629c32010-06-23 01:22:43409 static_cast<int>(future_url->second.navigation_count()),
410 static_cast<int>(future_url->second.preconnection_count()),
[email protected]f4ef861ba2010-07-28 22:37:23411 static_cast<int>(future_url->second.preresolution_count()),
[email protected]c5629c32010-06-23 01:22:43412 static_cast<double>(future_url->second.subresource_use_rate()),
[email protected]c5629c32010-06-23 01:22:43413 future_url->first.spec().c_str());
[email protected]21dae9b2008-11-06 23:32:53414 }
[email protected]21dae9b2008-11-06 23:32:53415 }
416 output->append("</table>");
417}
418
[email protected]74be069e82010-06-25 00:12:49419void Predictor::GetHtmlInfo(std::string* output) {
[email protected]ba4f1132010-10-09 02:02:35420 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
[email protected]74be069e82010-06-25 00:12:49421 // Local lists for calling UrlInfo
[email protected]f4ef861ba2010-07-28 22:37:23422 UrlInfo::UrlInfoTable name_not_found;
423 UrlInfo::UrlInfoTable name_preresolved;
initial.commit09911bf2008-07-26 23:55:29424
[email protected]ec86bea2009-12-08 18:35:14425 // Get copies of all useful data.
[email protected]f4ef861ba2010-07-28 22:37:23426 typedef std::map<GURL, UrlInfo, RightToLeftStringSorter> SortedUrlInfo;
427 SortedUrlInfo snapshot;
428 // UrlInfo supports value semantics, so we can do a shallow copy.
429 for (Results::iterator it(results_.begin()); it != results_.end(); it++)
430 snapshot[it->first] = it->second;
initial.commit09911bf2008-07-26 23:55:29431
[email protected]74be069e82010-06-25 00:12:49432 // Partition the UrlInfo's into categories.
[email protected]f4ef861ba2010-07-28 22:37:23433 for (SortedUrlInfo::iterator it(snapshot.begin());
434 it != snapshot.end(); it++) {
[email protected]e7afe2452010-08-22 16:19:13435 if (it->second.was_nonexistent()) {
initial.commit09911bf2008-07-26 23:55:29436 name_not_found.push_back(it->second);
437 continue;
438 }
439 if (!it->second.was_found())
440 continue; // Still being processed.
[email protected]f4ef861ba2010-07-28 22:37:23441 name_preresolved.push_back(it->second);
initial.commit09911bf2008-07-26 23:55:29442 }
443
444 bool brief = false;
445#ifdef NDEBUG
446 brief = true;
447#endif // NDEBUG
448
449 // Call for display of each table, along with title.
[email protected]f4ef861ba2010-07-28 22:37:23450 UrlInfo::GetHtmlTable(name_preresolved,
451 "Preresolution DNS records performed for ", brief, output);
[email protected]74be069e82010-06-25 00:12:49452 UrlInfo::GetHtmlTable(name_not_found,
[email protected]7798871882010-09-14 17:18:56453 "Preresolving DNS records revealed non-existence for ", brief, output);
initial.commit09911bf2008-07-26 23:55:29454}
455
[email protected]74be069e82010-06-25 00:12:49456UrlInfo* Predictor::AppendToResolutionQueue(
[email protected]c5629c32010-06-23 01:22:43457 const GURL& url,
[email protected]74be069e82010-06-25 00:12:49458 UrlInfo::ResolutionMotivation motivation) {
[email protected]ba4f1132010-10-09 02:02:35459 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
[email protected]c5629c32010-06-23 01:22:43460 DCHECK(url.has_host());
initial.commit09911bf2008-07-26 23:55:29461
[email protected]1933eb202009-02-19 18:23:25462 if (shutdown_)
463 return NULL;
464
[email protected]74be069e82010-06-25 00:12:49465 UrlInfo* info = &results_[url];
[email protected]c5629c32010-06-23 01:22:43466 info->SetUrl(url); // Initialize or DCHECK.
initial.commit09911bf2008-07-26 23:55:29467 // TODO(jar): I need to discard names that have long since expired.
468 // Currently we only add to the domain map :-/
469
[email protected]c5629c32010-06-23 01:22:43470 DCHECK(info->HasUrl(url));
initial.commit09911bf2008-07-26 23:55:29471
[email protected]760d970a2010-05-18 00:39:18472 if (!info->NeedsDnsUpdate()) {
initial.commit09911bf2008-07-26 23:55:29473 info->DLogResultsStats("DNS PrefetchNotUpdated");
[email protected]21dae9b2008-11-06 23:32:53474 return NULL;
initial.commit09911bf2008-07-26 23:55:29475 }
476
[email protected]21dae9b2008-11-06 23:32:53477 info->SetQueuedState(motivation);
[email protected]c5629c32010-06-23 01:22:43478 work_queue_.Push(url, motivation);
[email protected]ec86bea2009-12-08 18:35:14479 StartSomeQueuedResolutions();
[email protected]21dae9b2008-11-06 23:32:53480 return info;
initial.commit09911bf2008-07-26 23:55:29481}
482
[email protected]74be069e82010-06-25 00:12:49483void Predictor::StartSomeQueuedResolutions() {
[email protected]ba4f1132010-10-09 02:02:35484 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
[email protected]fd2f8afe2009-06-11 21:53:55485
[email protected]a20bc092009-06-05 01:34:20486 while (!work_queue_.IsEmpty() &&
[email protected]74be069e82010-06-25 00:12:49487 pending_lookups_.size() < max_concurrent_dns_lookups_) {
[email protected]c5629c32010-06-23 01:22:43488 const GURL url(work_queue_.Pop());
[email protected]74be069e82010-06-25 00:12:49489 UrlInfo* info = &results_[url];
[email protected]c5629c32010-06-23 01:22:43490 DCHECK(info->HasUrl(url));
initial.commit09911bf2008-07-26 23:55:29491 info->SetAssignedState();
492
[email protected]ec86bea2009-12-08 18:35:14493 if (CongestionControlPerformed(info)) {
[email protected]a20bc092009-06-05 01:34:20494 DCHECK(work_queue_.IsEmpty());
495 return;
496 }
497
[email protected]c5629c32010-06-23 01:22:43498 LookupRequest* request = new LookupRequest(this, host_resolver_, url);
[email protected]85398532009-06-16 21:32:18499 int status = request->Start();
500 if (status == net::ERR_IO_PENDING) {
[email protected]fd2f8afe2009-06-11 21:53:55501 // Will complete asynchronously.
[email protected]1933eb202009-02-19 18:23:25502 pending_lookups_.insert(request);
503 peak_pending_lookups_ = std::max(peak_pending_lookups_,
504 pending_lookups_.size());
505 } else {
[email protected]221f33362009-06-29 20:46:48506 // Completed synchronously (was already cached by HostResolver), or else
[email protected]85398532009-06-16 21:32:18507 // there was (equivalently) some network error that prevents us from
508 // finding the name. Status net::OK means it was "found."
[email protected]c5629c32010-06-23 01:22:43509 LookupFinished(request, url, status == net::OK);
[email protected]1933eb202009-02-19 18:23:25510 delete request;
511 }
512 }
initial.commit09911bf2008-07-26 23:55:29513}
514
[email protected]9f07c9b72011-08-19 15:44:32515bool Predictor::CongestionControlPerformed(UrlInfo* info) {
516 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
517 // Note: queue_duration is ONLY valid after we go to assigned state.
518 if (info->queue_duration() < max_dns_queue_delay_)
519 return false;
520 // We need to discard all entries in our queue, as we're keeping them waiting
521 // too long. By doing this, we'll have a chance to quickly service urgent
522 // resolutions, and not have a bogged down system.
523 while (true) {
524 info->RemoveFromQueue();
525 if (work_queue_.IsEmpty())
526 break;
527 info = &results_[work_queue_.Pop()];
528 info->SetAssignedState();
529 }
530 return true;
531}
532
533void Predictor::OnLookupFinished(LookupRequest* request, const GURL& url,
534 bool found) {
535 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
536
537 LookupFinished(request, url, found);
538 pending_lookups_.erase(request);
539 delete request;
540
541 StartSomeQueuedResolutions();
542}
543
544void Predictor::LookupFinished(LookupRequest* request, const GURL& url,
545 bool found) {
546 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
547 UrlInfo* info = &results_[url];
548 DCHECK(info->HasUrl(url));
549 if (info->is_marked_to_delete()) {
550 results_.erase(url);
551 } else {
552 if (found)
553 info->SetFoundState();
554 else
555 info->SetNoSuchNameState();
556 }
557}
558
559void Predictor::DiscardAllResults() {
560 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
561 // Delete anything listed so far in this session that shows in about:dns.
562 referrers_.clear();
563
564
565 // Try to delete anything in our work queue.
566 while (!work_queue_.IsEmpty()) {
567 // Emulate processing cycle as though host was not found.
568 GURL url = work_queue_.Pop();
569 UrlInfo* info = &results_[url];
570 DCHECK(info->HasUrl(url));
571 info->SetAssignedState();
572 info->SetNoSuchNameState();
573 }
574 // Now every result_ is either resolved, or is being resolved
575 // (see LookupRequest).
576
577 // Step through result_, recording names of all hosts that can't be erased.
578 // We can't erase anything being worked on.
579 Results assignees;
580 for (Results::iterator it = results_.begin(); results_.end() != it; ++it) {
581 GURL url(it->first);
582 UrlInfo* info = &it->second;
583 DCHECK(info->HasUrl(url));
584 if (info->is_assigned()) {
585 info->SetPendingDeleteState();
586 assignees[url] = *info;
587 }
588 }
589 DCHECK(assignees.size() <= max_concurrent_dns_lookups_);
590 results_.clear();
591 // Put back in the names being worked on.
592 for (Results::iterator it = assignees.begin(); assignees.end() != it; ++it) {
593 DCHECK(it->second.is_marked_to_delete());
594 results_[it->first] = it->second;
595 }
596}
597
598void Predictor::TrimReferrersNow() {
599 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
600 // Just finish up work if an incremental trim is in progress.
601 if (urls_being_trimmed_.empty())
602 LoadUrlsForTrimming();
603 IncrementalTrimReferrers(true); // Do everything now.
604}
605
606void Predictor::SerializeReferrers(ListValue* referral_list) {
607 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
608 referral_list->Clear();
609 referral_list->Append(new base::FundamentalValue(PREDICTOR_REFERRER_VERSION));
610 for (Referrers::const_iterator it = referrers_.begin();
611 it != referrers_.end(); ++it) {
612 // Serialize the list of subresource names.
613 Value* subresource_list(it->second.Serialize());
614
615 // Create a list for each referer.
616 ListValue* motivator(new ListValue);
617 motivator->Append(new StringValue(it->first.spec()));
618 motivator->Append(subresource_list);
619
620 referral_list->Append(motivator);
621 }
622}
623
624void Predictor::DeserializeReferrers(const ListValue& referral_list) {
625 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
626 int format_version = -1;
627 if (referral_list.GetSize() > 0 &&
628 referral_list.GetInteger(0, &format_version) &&
629 format_version == PREDICTOR_REFERRER_VERSION) {
630 for (size_t i = 1; i < referral_list.GetSize(); ++i) {
631 ListValue* motivator;
632 if (!referral_list.GetList(i, &motivator)) {
633 NOTREACHED();
634 return;
635 }
636 std::string motivating_url_spec;
637 if (!motivator->GetString(0, &motivating_url_spec)) {
638 NOTREACHED();
639 return;
640 }
641
642 Value* subresource_list;
643 if (!motivator->Get(1, &subresource_list)) {
644 NOTREACHED();
645 return;
646 }
647
648 referrers_[GURL(motivating_url_spec)].Deserialize(*subresource_list);
649 }
650 }
651}
652
[email protected]579f2a12011-04-06 16:27:31653void Predictor::TrimReferrers() {
654 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
655 if (!urls_being_trimmed_.empty())
656 return; // There is incremental trimming in progress already.
657
658 // Check to see if it is time to trim yet.
659 base::TimeTicks now = base::TimeTicks::Now();
660 if (now < next_trim_time_)
661 return;
662 next_trim_time_ = now + kDurationBetweenTrimmings;
663
664 LoadUrlsForTrimming();
665 PostIncrementalTrimTask();
666}
667
668void Predictor::LoadUrlsForTrimming() {
669 DCHECK(urls_being_trimmed_.empty());
670 for (Referrers::const_iterator it = referrers_.begin();
671 it != referrers_.end(); ++it)
672 urls_being_trimmed_.push_back(it->first);
673 UMA_HISTOGRAM_COUNTS("Net.PredictionTrimSize", urls_being_trimmed_.size());
674}
675
676void Predictor::PostIncrementalTrimTask() {
677 if (urls_being_trimmed_.empty())
678 return;
679 MessageLoop::current()->PostDelayedTask(
680 FROM_HERE,
[email protected]9f07c9b72011-08-19 15:44:32681 trim_task_factory_.NewRunnableMethod(&Predictor::IncrementalTrimReferrers,
682 false),
[email protected]579f2a12011-04-06 16:27:31683 kDurationBetweenTrimmingIncrements.InMilliseconds());
684}
685
686void Predictor::IncrementalTrimReferrers(bool trim_all_now) {
687 size_t trim_count = urls_being_trimmed_.size();
688 if (!trim_all_now)
689 trim_count = std::min(trim_count, kUrlsTrimmedPerIncrement);
690 while (trim_count-- != 0) {
691 Referrers::iterator it = referrers_.find(urls_being_trimmed_.back());
692 urls_being_trimmed_.pop_back();
693 if (it == referrers_.end())
694 continue; // Defensive code: It got trimmed away already.
695 if (!it->second.Trim(kReferrerTrimRatio, kDiscardableExpectedValue))
696 referrers_.erase(it);
697 }
698 PostIncrementalTrimTask();
699}
[email protected]a20bc092009-06-05 01:34:20700
[email protected]9f07c9b72011-08-19 15:44:32701//------------------------------------------------------------------------------
[email protected]a20bc092009-06-05 01:34:20702
[email protected]74be069e82010-06-25 00:12:49703Predictor::HostNameQueue::HostNameQueue() {
[email protected]a20bc092009-06-05 01:34:20704}
705
[email protected]74be069e82010-06-25 00:12:49706Predictor::HostNameQueue::~HostNameQueue() {
[email protected]a20bc092009-06-05 01:34:20707}
708
[email protected]74be069e82010-06-25 00:12:49709void Predictor::HostNameQueue::Push(const GURL& url,
710 UrlInfo::ResolutionMotivation motivation) {
[email protected]a20bc092009-06-05 01:34:20711 switch (motivation) {
[email protected]74be069e82010-06-25 00:12:49712 case UrlInfo::STATIC_REFERAL_MOTIVATED:
713 case UrlInfo::LEARNED_REFERAL_MOTIVATED:
714 case UrlInfo::MOUSE_OVER_MOTIVATED:
[email protected]c5629c32010-06-23 01:22:43715 rush_queue_.push(url);
[email protected]a20bc092009-06-05 01:34:20716 break;
717
718 default:
[email protected]c5629c32010-06-23 01:22:43719 background_queue_.push(url);
[email protected]a20bc092009-06-05 01:34:20720 break;
721 }
722}
723
[email protected]74be069e82010-06-25 00:12:49724bool Predictor::HostNameQueue::IsEmpty() const {
[email protected]a20bc092009-06-05 01:34:20725 return rush_queue_.empty() && background_queue_.empty();
726}
727
[email protected]74be069e82010-06-25 00:12:49728GURL Predictor::HostNameQueue::Pop() {
[email protected]a20bc092009-06-05 01:34:20729 DCHECK(!IsEmpty());
[email protected]c5629c32010-06-23 01:22:43730 std::queue<GURL> *queue(rush_queue_.empty() ? &background_queue_
731 : &rush_queue_);
732 GURL url(queue->front());
733 queue->pop();
734 return url;
[email protected]a20bc092009-06-05 01:34:20735}
736
[email protected]9f07c9b72011-08-19 15:44:32737void Predictor::DeserializeReferrersThenDelete(ListValue* referral_list) {
738 DeserializeReferrers(*referral_list);
739 delete referral_list;
[email protected]c02c853d72010-08-07 06:23:24740}
741
[email protected]1455ccf12010-08-18 16:32:14742
[email protected]9f07c9b72011-08-19 15:44:32743//------------------------------------------------------------------------------
[email protected]1455ccf12010-08-18 16:32:14744// Helper functions
[email protected]9f07c9b72011-08-19 15:44:32745//------------------------------------------------------------------------------
[email protected]1455ccf12010-08-18 16:32:14746
747// static
748GURL Predictor::CanonicalizeUrl(const GURL& url) {
749 if (!url.has_host())
750 return GURL::EmptyGURL();
751
752 std::string scheme;
753 if (url.has_scheme()) {
754 scheme = url.scheme();
755 if (scheme != "http" && scheme != "https")
756 return GURL::EmptyGURL();
757 if (url.has_port())
758 return url.GetWithEmptyPath();
759 } else {
760 scheme = "http";
761 }
762
763 // If we omit a port, it will default to 80 or 443 as appropriate.
764 std::string colon_plus_port;
765 if (url.has_port())
766 colon_plus_port = ":" + url.port();
767
768 return GURL(scheme + "://" + url.host() + colon_plus_port);
769}
770
[email protected]1455ccf12010-08-18 16:32:14771
initial.commit09911bf2008-07-26 23:55:29772} // namespace chrome_browser_net