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