diff options
Diffstat (limited to 'chrome/browser/net/url_info.cc')
-rw-r--r-- | chrome/browser/net/url_info.cc | 439 |
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 |