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