summaryrefslogtreecommitdiffstats
path: root/chrome/browser/net/url_info.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/net/url_info.cc')
-rw-r--r--chrome/browser/net/url_info.cc439
1 files changed, 439 insertions, 0 deletions
diff --git a/chrome/browser/net/url_info.cc b/chrome/browser/net/url_info.cc
new file mode 100644
index 0000000..002c4b7
--- /dev/null
+++ b/chrome/browser/net/url_info.cc
@@ -0,0 +1,439 @@
+// Copyright (c) 2006-2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/net/url_info.h"
+
+#include <math.h>
+
+#include <algorithm>
+#include <string>
+
+#include "base/field_trial.h"
+#include "base/format_macros.h"
+#include "base/histogram.h"
+#include "base/logging.h"
+#include "base/string_util.h"
+
+using base::Time;
+using base::TimeDelta;
+using base::TimeTicks;
+
+namespace chrome_browser_net {
+
+static bool detailed_logging_enabled = false;
+
+// Use command line switch to enable detailed logging.
+void EnablePredictorDetailedLog(bool enable) {
+ detailed_logging_enabled = enable;
+}
+
+// static
+int UrlInfo::sequence_counter = 1;
+
+
+bool UrlInfo::NeedsDnsUpdate() {
+ switch (state_) {
+ case PENDING: // Just now created info.
+ return true;
+
+ case QUEUED: // In queue.
+ case ASSIGNED: // It's being resolved.
+ case ASSIGNED_BUT_MARKED: // It's being resolved.
+ return false; // We're already working on it
+
+ case NO_SUCH_NAME: // Lookup failed.
+ case FOUND: // Lookup succeeded.
+ return !IsStillCached(); // See if DNS cache expired.
+
+ default:
+ NOTREACHED();
+ return false;
+ }
+}
+
+const TimeDelta UrlInfo::kNullDuration(TimeDelta::FromMilliseconds(-1));
+
+// Common low end TTL for sites is 5 minutes. However, DNS servers give us
+// the remaining time, not the original 5 minutes. Hence it doesn't much matter
+// whether we found something in the local cache, or an ISP cache, it will
+// on average be 2.5 minutes before it expires. We could try to model this with
+// 180 seconds, but simpler is just to do the lookups all the time (wasting
+// OS calls(?)), and let that OS cache decide what to do (with TTL in hand).
+// We use a small time to help get some duplicate suppression, in case a page
+// has a TON of copies of the same domain name, so that we don't thrash the OS
+// to death. Hopefully it is small enough that we're not hurting our cache hit
+// rate (i.e., we could always ask the OS).
+TimeDelta UrlInfo::kCacheExpirationDuration(TimeDelta::FromSeconds(5));
+
+const TimeDelta UrlInfo::kMaxNonNetworkDnsLookupDuration(
+ TimeDelta::FromMilliseconds(15));
+
+// Used by test ONLY. The value is otherwise constant.
+void UrlInfo::set_cache_expiration(TimeDelta time) {
+ kCacheExpirationDuration = time;
+}
+
+void UrlInfo::SetQueuedState(ResolutionMotivation motivation) {
+ DCHECK(PENDING == state_ || FOUND == state_ || NO_SUCH_NAME == state_);
+ old_prequeue_state_ = state_;
+ state_ = QUEUED;
+ queue_duration_ = resolve_duration_ = kNullDuration;
+ SetMotivation(motivation);
+ GetDuration(); // Set time_
+ DLogResultsStats("DNS Prefetch in queue");
+}
+
+void UrlInfo::SetAssignedState() {
+ DCHECK(QUEUED == state_);
+ state_ = ASSIGNED;
+ queue_duration_ = GetDuration();
+ DLogResultsStats("DNS Prefetch assigned");
+ UMA_HISTOGRAM_TIMES("DNS.PrefetchQueue", queue_duration_);
+}
+
+void UrlInfo::RemoveFromQueue() {
+ DCHECK(ASSIGNED == state_);
+ state_ = old_prequeue_state_;
+ DLogResultsStats("DNS Prefetch reset to prequeue");
+ static const TimeDelta kBoundary = TimeDelta::FromSeconds(2);
+ if (queue_duration_ > kBoundary) {
+ UMA_HISTOGRAM_MEDIUM_TIMES("DNS.QueueRecycledDeltaOver2",
+ queue_duration_ - kBoundary);
+ return;
+ }
+ // Make a custom linear histogram for the region from 0 to boundary.
+ const size_t kBucketCount = 52;
+ static scoped_refptr<Histogram> histogram = LinearHistogram::FactoryGet(
+ "DNS.QueueRecycledUnder2", TimeDelta(), kBoundary, kBucketCount,
+ Histogram::kUmaTargetedHistogramFlag);
+ histogram->AddTime(queue_duration_);
+}
+
+void UrlInfo::SetPendingDeleteState() {
+ DCHECK(ASSIGNED == state_ || ASSIGNED_BUT_MARKED == state_);
+ state_ = ASSIGNED_BUT_MARKED;
+}
+
+void UrlInfo::SetFoundState() {
+ DCHECK(ASSIGNED == state_);
+ state_ = FOUND;
+ resolve_duration_ = GetDuration();
+ if (kMaxNonNetworkDnsLookupDuration <= resolve_duration_) {
+ UMA_HISTOGRAM_CUSTOM_TIMES("DNS.PrefetchResolution", resolve_duration_,
+ kMaxNonNetworkDnsLookupDuration, TimeDelta::FromMinutes(15), 100);
+
+ static bool use_ipv6_histogram(FieldTrialList::Find("IPv6_Probe") &&
+ !FieldTrialList::Find("IPv6_Probe")->group_name().empty());
+ if (use_ipv6_histogram) {
+ UMA_HISTOGRAM_CUSTOM_TIMES(
+ FieldTrial::MakeName("DNS.PrefetchResolution", "IPv6_Probe"),
+ resolve_duration_, kMaxNonNetworkDnsLookupDuration,
+ TimeDelta::FromMinutes(15), 100);
+ }
+
+ // Record potential beneficial time, and maybe we'll get a cache hit.
+ // We keep the maximum, as the warming we did earlier may still be
+ // helping with a cache upstream in DNS resolution.
+ benefits_remaining_ = std::max(resolve_duration_, benefits_remaining_);
+ }
+ sequence_number_ = sequence_counter++;
+ DLogResultsStats("DNS PrefetchFound");
+}
+
+void UrlInfo::SetNoSuchNameState() {
+ DCHECK(ASSIGNED == state_);
+ state_ = NO_SUCH_NAME;
+ resolve_duration_ = GetDuration();
+ if (kMaxNonNetworkDnsLookupDuration <= resolve_duration_) {
+ DHISTOGRAM_TIMES("DNS.PrefetchNotFoundName", resolve_duration_);
+ // Record potential beneficial time, and maybe we'll get a cache hit.
+ benefits_remaining_ = std::max(resolve_duration_, benefits_remaining_);
+ }
+ sequence_number_ = sequence_counter++;
+ DLogResultsStats("DNS PrefetchNotFound");
+}
+
+void UrlInfo::SetStartedState() {
+ DCHECK(PENDING == state_);
+ state_ = STARTED;
+ queue_duration_ = resolve_duration_ = TimeDelta(); // 0ms.
+ SetMotivation(NO_PREFETCH_MOTIVATION);
+ GetDuration(); // Set time.
+}
+
+void UrlInfo::SetFinishedState(bool was_resolved) {
+ DCHECK(STARTED == state_);
+ state_ = was_resolved ? FINISHED : FINISHED_UNRESOLVED;
+ resolve_duration_ = GetDuration();
+ // TODO(jar): Sequence number should be incremented in prefetched HostInfo.
+ DLogResultsStats("DNS HTTP Finished");
+}
+
+void UrlInfo::SetUrl(const GURL& url) {
+ if (url_.is_empty()) // Not yet initialized.
+ url_ = url;
+ else
+ DCHECK_EQ(url_, url);
+}
+
+// IsStillCached() guesses if the DNS cache still has IP data,
+// or at least remembers results about "not finding host."
+bool UrlInfo::IsStillCached() const {
+ DCHECK(FOUND == state_ || NO_SUCH_NAME == state_);
+
+ // Default MS OS does not cache failures. Hence we could return false almost
+ // all the time for that case. However, we'd never try again to prefetch
+ // the value if we returned false that way. Hence we'll just let the lookup
+ // time out the same way as FOUND case.
+
+ if (sequence_counter - sequence_number_ > kMaxGuaranteedDnsCacheSize)
+ return false;
+
+ TimeDelta time_since_resolution = TimeTicks::Now() - time_;
+
+ return time_since_resolution < kCacheExpirationDuration;
+}
+
+// Compare the actual navigation DNS latency found in navigation_info, to the
+// previously prefetched info.
+DnsBenefit UrlInfo::AccruePrefetchBenefits(UrlInfo* navigation_info) {
+ DCHECK(FINISHED == navigation_info->state_ ||
+ FINISHED_UNRESOLVED == navigation_info->state_);
+ DCHECK(navigation_info->url() == url_);
+
+ if ((0 == benefits_remaining_.InMilliseconds()) ||
+ (FOUND != state_ && NO_SUCH_NAME != state_)) {
+ if (FINISHED == navigation_info->state_)
+ UMA_HISTOGRAM_LONG_TIMES("DNS.IndependentNavigation",
+ navigation_info->resolve_duration_);
+ else
+ UMA_HISTOGRAM_LONG_TIMES("DNS.IndependentFailedNavigation",
+ navigation_info->resolve_duration_);
+ return PREFETCH_NO_BENEFIT;
+ }
+
+ TimeDelta benefit = benefits_remaining_ - navigation_info->resolve_duration_;
+ navigation_info->benefits_remaining_ = benefits_remaining_;
+ benefits_remaining_ = TimeDelta(); // We used up all our benefits here.
+
+ navigation_info->motivation_ = motivation_;
+ if (LEARNED_REFERAL_MOTIVATED == motivation_ ||
+ STATIC_REFERAL_MOTIVATED == motivation_)
+ navigation_info->referring_url_ = referring_url_;
+
+ if (navigation_info->resolve_duration_ > kMaxNonNetworkDnsLookupDuration) {
+ // Our precache effort didn't help since HTTP stack hit the network.
+ UMA_HISTOGRAM_LONG_TIMES("DNS.PrefetchCacheEvictionL", resolve_duration_);
+ DLogResultsStats("DNS PrefetchCacheEviction");
+ return PREFETCH_CACHE_EVICTION;
+ }
+
+ if (NO_SUCH_NAME == state_) {
+ UMA_HISTOGRAM_LONG_TIMES("DNS.PrefetchNegativeHitL", benefit);
+ DLogResultsStats("DNS PrefetchNegativeHit");
+ return PREFETCH_NAME_NONEXISTANT;
+ }
+
+ DCHECK_EQ(FOUND, state_);
+ if (LEARNED_REFERAL_MOTIVATED == motivation_ ||
+ STATIC_REFERAL_MOTIVATED == motivation_) {
+ UMA_HISTOGRAM_TIMES("DNS.PrefetchReferredPositiveHit", benefit);
+ DLogResultsStats("DNS PrefetchReferredPositiveHit");
+ } else {
+ UMA_HISTOGRAM_LONG_TIMES("DNS.PrefetchPositiveHitL", benefit);
+ DLogResultsStats("DNS PrefetchPositiveHit");
+ }
+ return PREFETCH_NAME_FOUND;
+}
+
+void UrlInfo::DLogResultsStats(const char* message) const {
+ if (!detailed_logging_enabled)
+ return;
+ DLOG(INFO) << "\t" << message << "\tq="
+ << queue_duration().InMilliseconds() << "ms,\tr="
+ << resolve_duration().InMilliseconds() << "ms\tp="
+ << benefits_remaining_.InMilliseconds() << "ms\tseq="
+ << sequence_number_
+ << "\t" << url_.spec();
+}
+
+//------------------------------------------------------------------------------
+// This last section supports HTML output, such as seen in about:dns.
+//------------------------------------------------------------------------------
+
+// Preclude any possibility of Java Script or markup in the text, by only
+// allowing alphanumerics, '.', '-', ':', and whitespace.
+static std::string RemoveJs(const std::string& text) {
+ std::string output(text);
+ size_t length = output.length();
+ for (size_t i = 0; i < length; i++) {
+ char next = output[i];
+ if (isalnum(next) || isspace(next) || strchr(".-:/", next) != NULL)
+ continue;
+ output[i] = '?';
+ }
+ return output;
+}
+
+class MinMaxAverage {
+ public:
+ MinMaxAverage()
+ : sum_(0), square_sum_(0), count_(0),
+ minimum_(kint64max), maximum_(kint64min) {
+ }
+
+ // Return values for use in printf formatted as "%d"
+ int sample(int64 value) {
+ sum_ += value;
+ square_sum_ += value * value;
+ count_++;
+ minimum_ = std::min(minimum_, value);
+ maximum_ = std::max(maximum_, value);
+ return static_cast<int>(value);
+ }
+ int minimum() const { return static_cast<int>(minimum_); }
+ int maximum() const { return static_cast<int>(maximum_); }
+ int average() const { return static_cast<int>(sum_/count_); }
+ int sum() const { return static_cast<int>(sum_); }
+
+ int standard_deviation() const {
+ double average = static_cast<float>(sum_) / count_;
+ double variance = static_cast<float>(square_sum_)/count_
+ - average * average;
+ return static_cast<int>(floor(sqrt(variance) + .5));
+ }
+
+ private:
+ int64 sum_;
+ int64 square_sum_;
+ int count_;
+ int64 minimum_;
+ int64 maximum_;
+
+ // DISALLOW_COPY_AND_ASSIGN(MinMaxAverage);
+};
+
+static std::string HoursMinutesSeconds(int seconds) {
+ std::string result;
+ int print_seconds = seconds % 60;
+ int minutes = seconds / 60;
+ int print_minutes = minutes % 60;
+ int print_hours = minutes/60;
+ if (print_hours)
+ StringAppendF(&result, "%.2d:", print_hours);
+ if (print_hours || print_minutes)
+ StringAppendF(&result, "%2.2d:", print_minutes);
+ StringAppendF(&result, "%2.2d", print_seconds);
+ return result;
+}
+
+// static
+void UrlInfo::GetHtmlTable(const DnsInfoTable host_infos,
+ const char* description,
+ const bool brief,
+ std::string* output) {
+ if (0 == host_infos.size())
+ return;
+ output->append(description);
+ StringAppendF(output, "%" PRIuS " %s", host_infos.size(),
+ (1 == host_infos.size()) ? "hostname" : "hostnames");
+
+ if (brief) {
+ output->append("<br><br>");
+ return;
+ }
+
+ const char* row_format = "<tr align=right><td>%s</td>"
+ "<td>%d</td><td>%d</td><td>%s</td><td>%s</td></tr>";
+
+ output->append("<br><table border=1>");
+ StringAppendF(output,
+ "<tr><th>%s</th><th>%s</th><th>%s</th><th>%s</th><th>%s</th></tr>",
+ "Host name", "Applicable Prefetch<br>Time (ms)",
+ "Recent Resolution<br>Time(ms)", "How long ago<br>(HH:MM:SS)",
+ "Motivation");
+
+ // Print bulk of table, and gather stats at same time.
+ MinMaxAverage queue, resolve, preresolve, when;
+ TimeTicks current_time = TimeTicks::Now();
+ for (DnsInfoTable::const_iterator it(host_infos.begin());
+ it != host_infos.end(); it++) {
+ queue.sample((it->queue_duration_.InMilliseconds()));
+ StringAppendF(output, row_format,
+ RemoveJs(it->url_.spec()).c_str(),
+ preresolve.sample((it->benefits_remaining_.InMilliseconds())),
+ resolve.sample((it->resolve_duration_.InMilliseconds())),
+ HoursMinutesSeconds(when.sample(
+ (current_time - it->time_).InSeconds())).c_str(),
+ it->GetAsciiMotivation().c_str());
+ }
+ // Write min, max, and average summary lines.
+ if (host_infos.size() > 2) {
+ output->append("<B>");
+ StringAppendF(output, row_format,
+ "<b>---minimum---</b>",
+ preresolve.minimum(), resolve.minimum(),
+ HoursMinutesSeconds(when.minimum()).c_str(), "");
+ StringAppendF(output, row_format,
+ "<b>---average---</b>",
+ preresolve.average(), resolve.average(),
+ HoursMinutesSeconds(when.average()).c_str(), "");
+ StringAppendF(output, row_format,
+ "<b>standard deviation</b>",
+ preresolve.standard_deviation(),
+ resolve.standard_deviation(), "n/a", "");
+ StringAppendF(output, row_format,
+ "<b>---maximum---</b>",
+ preresolve.maximum(), resolve.maximum(),
+ HoursMinutesSeconds(when.maximum()).c_str(), "");
+ StringAppendF(output, row_format,
+ "<b>-----SUM-----</b>",
+ preresolve.sum(), resolve.sum(), "n/a", "");
+ }
+ output->append("</table>");
+
+#ifdef DEBUG
+ StringAppendF(output,
+ "Prefetch Queue Durations: min=%d, avg=%d, max=%d<br><br>",
+ queue.minimum(), queue.average(), queue.maximum());
+#endif
+
+ output->append("<br>");
+}
+
+void UrlInfo::SetMotivation(ResolutionMotivation motivation) {
+ motivation_ = motivation;
+ if (motivation < LINKED_MAX_MOTIVATED)
+ was_linked_ = true;
+}
+
+std::string UrlInfo::GetAsciiMotivation() const {
+ switch (motivation_) {
+ case MOUSE_OVER_MOTIVATED:
+ return "[mouse-over]";
+
+ case PAGE_SCAN_MOTIVATED:
+ return "[page scan]";
+
+ case OMNIBOX_MOTIVATED:
+ return "[omnibox]";
+
+ case STARTUP_LIST_MOTIVATED:
+ return "[startup list]";
+
+ case NO_PREFETCH_MOTIVATION:
+ return "n/a";
+
+ case STATIC_REFERAL_MOTIVATED:
+ return RemoveJs(referring_url_.spec()) + "*";
+
+ case LEARNED_REFERAL_MOTIVATED:
+ return RemoveJs(referring_url_.spec());
+
+ default:
+ return "";
+ }
+}
+
+} // namespace chrome_browser_net