url_info.cc revision 3345a6884c488ff3a535c2c9acdd33d74b37e311
1// Copyright (c) 2006-2010 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/net/url_info.h"
6
7#include <math.h>
8
9#include <algorithm>
10#include <string>
11
12#include "base/field_trial.h"
13#include "base/format_macros.h"
14#include "base/histogram.h"
15#include "base/logging.h"
16#include "base/string_util.h"
17
18using base::Time;
19using base::TimeDelta;
20using base::TimeTicks;
21
22namespace chrome_browser_net {
23
24static bool detailed_logging_enabled = false;
25
26// Use command line switch to enable detailed logging.
27void EnablePredictorDetailedLog(bool enable) {
28  detailed_logging_enabled = enable;
29}
30
31// static
32int UrlInfo::sequence_counter = 1;
33
34
35bool UrlInfo::NeedsDnsUpdate() {
36  switch (state_) {
37    case PENDING:  // Just now created info.
38      return true;
39
40    case QUEUED:  // In queue.
41    case ASSIGNED:  // It's being resolved.
42    case ASSIGNED_BUT_MARKED:  // It's being resolved.
43      return false;  // We're already working on it
44
45    case NO_SUCH_NAME:  // Lookup failed.
46    case FOUND:  // Lookup succeeded.
47      return !IsStillCached();  // See if DNS cache expired.
48
49    default:
50      NOTREACHED();
51      return false;
52  }
53}
54
55const TimeDelta UrlInfo::kNullDuration(TimeDelta::FromMilliseconds(-1));
56
57// Common low end TTL for sites is 5 minutes.  However, DNS servers give us
58// the remaining time, not the original 5 minutes.  Hence it doesn't much matter
59// whether we found something in the local cache, or an ISP cache, it will
60// on average be 2.5 minutes before it expires.  We could try to model this with
61// 180 seconds, but simpler is just to do the lookups all the time (wasting
62// OS calls(?)), and let that OS cache decide what to do (with TTL in hand).
63// We use a small time to help get some duplicate suppression, in case a page
64// has a TON of copies of the same domain name, so that we don't thrash the OS
65// to death.  Hopefully it is small enough that we're not hurting our cache hit
66// rate (i.e., we could always ask the OS).
67TimeDelta UrlInfo::cache_expiration_duration_(TimeDelta::FromSeconds(5));
68
69const TimeDelta UrlInfo::kMaxNonNetworkDnsLookupDuration(
70    TimeDelta::FromMilliseconds(15));
71
72// Used by test ONLY.  The value is otherwise constant.
73// static
74void UrlInfo::set_cache_expiration(TimeDelta time) {
75  cache_expiration_duration_ = time;
76}
77
78// static
79TimeDelta UrlInfo::get_cache_expiration() {
80  return cache_expiration_duration_;
81}
82
83void UrlInfo::SetQueuedState(ResolutionMotivation motivation) {
84  DCHECK(PENDING == state_ || FOUND == state_ || NO_SUCH_NAME == state_);
85  old_prequeue_state_ = state_;
86  state_ = QUEUED;
87  queue_duration_ = resolve_duration_ = kNullDuration;
88  SetMotivation(motivation);
89  GetDuration();  // Set time_
90  DLogResultsStats("DNS Prefetch in queue");
91}
92
93void UrlInfo::SetAssignedState() {
94  DCHECK(QUEUED == state_);
95  state_ = ASSIGNED;
96  queue_duration_ = GetDuration();
97  DLogResultsStats("DNS Prefetch assigned");
98  UMA_HISTOGRAM_TIMES("DNS.PrefetchQueue", queue_duration_);
99}
100
101void UrlInfo::RemoveFromQueue() {
102  DCHECK(ASSIGNED == state_);
103  state_ = old_prequeue_state_;
104  DLogResultsStats("DNS Prefetch reset to prequeue");
105  static const TimeDelta kBoundary = TimeDelta::FromSeconds(2);
106  if (queue_duration_ > kBoundary) {
107    UMA_HISTOGRAM_MEDIUM_TIMES("DNS.QueueRecycledDeltaOver2",
108                               queue_duration_ - kBoundary);
109    return;
110  }
111  // Make a custom linear histogram for the region from 0 to boundary.
112  const size_t kBucketCount = 52;
113  static scoped_refptr<Histogram> histogram = LinearHistogram::FactoryTimeGet(
114      "DNS.QueueRecycledUnder2", TimeDelta(), kBoundary, kBucketCount,
115      Histogram::kUmaTargetedHistogramFlag);
116  histogram->AddTime(queue_duration_);
117}
118
119void UrlInfo::SetPendingDeleteState() {
120  DCHECK(ASSIGNED == state_  || ASSIGNED_BUT_MARKED == state_);
121  state_ = ASSIGNED_BUT_MARKED;
122}
123
124void UrlInfo::SetFoundState() {
125  DCHECK(ASSIGNED == state_);
126  state_ = FOUND;
127  resolve_duration_ = GetDuration();
128  if (kMaxNonNetworkDnsLookupDuration <= resolve_duration_) {
129    UMA_HISTOGRAM_CUSTOM_TIMES("DNS.PrefetchResolution", resolve_duration_,
130        kMaxNonNetworkDnsLookupDuration, TimeDelta::FromMinutes(15), 100);
131  }
132  sequence_number_ = sequence_counter++;
133  DLogResultsStats("DNS PrefetchFound");
134}
135
136void UrlInfo::SetNoSuchNameState() {
137  DCHECK(ASSIGNED == state_);
138  state_ = NO_SUCH_NAME;
139  resolve_duration_ = GetDuration();
140  if (kMaxNonNetworkDnsLookupDuration <= resolve_duration_) {
141    DHISTOGRAM_TIMES("DNS.PrefetchNotFoundName", resolve_duration_);
142  }
143  sequence_number_ = sequence_counter++;
144  DLogResultsStats("DNS PrefetchNotFound");
145}
146
147void UrlInfo::SetUrl(const GURL& url) {
148  if (url_.is_empty())  // Not yet initialized.
149    url_ = url;
150  else
151    DCHECK_EQ(url_, url);
152}
153
154// IsStillCached() guesses if the DNS cache still has IP data,
155// or at least remembers results about "not finding host."
156bool UrlInfo::IsStillCached() const {
157  DCHECK(FOUND == state_ || NO_SUCH_NAME == state_);
158
159  // Default MS OS does not cache failures. Hence we could return false almost
160  // all the time for that case.  However, we'd never try again to prefetch
161  // the value if we returned false that way.  Hence we'll just let the lookup
162  // time out the same way as FOUND case.
163
164  if (sequence_counter - sequence_number_ > kMaxGuaranteedDnsCacheSize)
165    return false;
166
167  TimeDelta time_since_resolution = TimeTicks::Now() - time_;
168
169  return time_since_resolution < cache_expiration_duration_;
170}
171
172void UrlInfo::DLogResultsStats(const char* message) const {
173  if (!detailed_logging_enabled)
174    return;
175  DLOG(INFO) << "\t" << message <<  "\tq="
176      << queue_duration().InMilliseconds() << "ms,\tr="
177      << resolve_duration().InMilliseconds() << "ms\tp="
178      << sequence_number_
179      << "\t" << url_.spec();
180}
181
182//------------------------------------------------------------------------------
183// This last section supports HTML output, such as seen in about:dns.
184//------------------------------------------------------------------------------
185
186// Preclude any possibility of Java Script or markup in the text, by only
187// allowing alphanumerics, '.', '-', ':', and whitespace.
188static std::string RemoveJs(const std::string& text) {
189  std::string output(text);
190  size_t length = output.length();
191  for (size_t i = 0; i < length; i++) {
192    char next = output[i];
193    if (isalnum(next) || isspace(next) || strchr(".-:/", next) != NULL)
194      continue;
195    output[i] = '?';
196  }
197  return output;
198}
199
200class MinMaxAverage {
201 public:
202  MinMaxAverage()
203    : sum_(0), square_sum_(0), count_(0),
204      minimum_(kint64max), maximum_(kint64min) {
205  }
206
207  // Return values for use in printf formatted as "%d"
208  int sample(int64 value) {
209    sum_ += value;
210    square_sum_ += value * value;
211    count_++;
212    minimum_ = std::min(minimum_, value);
213    maximum_ = std::max(maximum_, value);
214    return static_cast<int>(value);
215  }
216  int minimum() const { return static_cast<int>(minimum_);    }
217  int maximum() const { return static_cast<int>(maximum_);    }
218  int average() const { return static_cast<int>(sum_/count_); }
219  int     sum() const { return static_cast<int>(sum_);        }
220
221  int standard_deviation() const {
222    double average = static_cast<float>(sum_) / count_;
223    double variance = static_cast<float>(square_sum_)/count_
224                      - average * average;
225    return static_cast<int>(floor(sqrt(variance) + .5));
226  }
227
228 private:
229  int64 sum_;
230  int64 square_sum_;
231  int count_;
232  int64 minimum_;
233  int64 maximum_;
234
235  // DISALLOW_COPY_AND_ASSIGN(MinMaxAverage);
236};
237
238static std::string HoursMinutesSeconds(int seconds) {
239  std::string result;
240  int print_seconds = seconds % 60;
241  int minutes = seconds / 60;
242  int print_minutes = minutes % 60;
243  int print_hours = minutes/60;
244  if (print_hours)
245    StringAppendF(&result, "%.2d:",  print_hours);
246  if (print_hours || print_minutes)
247    StringAppendF(&result, "%2.2d:",  print_minutes);
248  StringAppendF(&result, "%2.2d",  print_seconds);
249  return result;
250}
251
252// static
253void UrlInfo::GetHtmlTable(const UrlInfoTable host_infos,
254                               const char* description,
255                               const bool brief,
256                               std::string* output) {
257  if (0 == host_infos.size())
258    return;
259  output->append(description);
260  StringAppendF(output, "%" PRIuS " %s", host_infos.size(),
261                (1 == host_infos.size()) ? "hostname" : "hostnames");
262
263  if (brief) {
264    output->append("<br><br>");
265    return;
266  }
267
268  output->append("<br><table border=1>"
269      "<tr><th>Host name</th>"
270      "<th>How long ago<br>(HH:MM:SS)</th>"
271      "<th>Motivation</th>"
272      "</tr>");
273
274  const char* row_format = "<tr align=right><td>%s</td>"  // Host name.
275                           "<td>%s</td>"                  // How long ago.
276                           "<td>%s</td>"                  // Motivation.
277                           "</tr>";
278
279  // Print bulk of table, and gather stats at same time.
280  MinMaxAverage queue, when;
281  TimeTicks current_time = TimeTicks::Now();
282  for (UrlInfoTable::const_iterator it(host_infos.begin());
283       it != host_infos.end(); it++) {
284    queue.sample((it->queue_duration_.InMilliseconds()));
285    StringAppendF(output, row_format,
286                  RemoveJs(it->url_.spec()).c_str(),
287                  HoursMinutesSeconds(when.sample(
288                      (current_time - it->time_).InSeconds())).c_str(),
289                  it->GetAsciiMotivation().c_str());
290  }
291  output->append("</table>");
292
293#ifndef NDEBUG
294  StringAppendF(output,
295                "Prefetch Queue Durations: min=%d, avg=%d, max=%d<br><br>",
296                queue.minimum(), queue.average(), queue.maximum());
297#endif
298
299  output->append("<br>");
300}
301
302void UrlInfo::SetMotivation(ResolutionMotivation motivation) {
303  motivation_ = motivation;
304  if (motivation < LINKED_MAX_MOTIVATED)
305    was_linked_ = true;
306}
307
308std::string UrlInfo::GetAsciiMotivation() const {
309  switch (motivation_) {
310    case MOUSE_OVER_MOTIVATED:
311      return "[mouse-over]";
312
313    case PAGE_SCAN_MOTIVATED:
314      return "[page scan]";
315
316    case OMNIBOX_MOTIVATED:
317      return "[omnibox]";
318
319    case STARTUP_LIST_MOTIVATED:
320      return "[startup list]";
321
322    case NO_PREFETCH_MOTIVATION:
323      return "n/a";
324
325    case STATIC_REFERAL_MOTIVATED:
326      return RemoveJs(referring_url_.spec()) + "*";
327
328    case LEARNED_REFERAL_MOTIVATED:
329      return RemoveJs(referring_url_.spec());
330
331    default:
332      return "";
333  }
334}
335
336}  // namespace chrome_browser_net
337