1 // Copyright (c) 2012 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/renderer/page_load_histograms.h" 6 7 #include <string> 8 9 #include "base/command_line.h" 10 #include "base/logging.h" 11 #include "base/metrics/field_trial.h" 12 #include "base/metrics/histogram.h" 13 #include "base/strings/string_number_conversions.h" 14 #include "base/strings/string_split.h" 15 #include "base/strings/string_util.h" 16 #include "base/strings/stringprintf.h" 17 #include "base/strings/utf_string_conversions.h" 18 #include "base/time/time.h" 19 #include "chrome/common/chrome_switches.h" 20 #include "chrome/renderer/chrome_content_renderer_client.h" 21 #include "components/data_reduction_proxy/common/data_reduction_proxy_headers.h" 22 #include "content/public/common/content_constants.h" 23 #include "content/public/renderer/document_state.h" 24 #include "content/public/renderer/render_thread.h" 25 #include "content/public/renderer/render_view.h" 26 #include "extensions/common/url_pattern.h" 27 #include "net/base/url_util.h" 28 #include "net/http/http_response_headers.h" 29 #include "third_party/WebKit/public/platform/WebURLRequest.h" 30 #include "third_party/WebKit/public/platform/WebURLResponse.h" 31 #include "third_party/WebKit/public/web/WebDocument.h" 32 #include "third_party/WebKit/public/web/WebFrame.h" 33 #include "third_party/WebKit/public/web/WebPerformance.h" 34 #include "third_party/WebKit/public/web/WebView.h" 35 #include "url/gurl.h" 36 37 using blink::WebDataSource; 38 using blink::WebFrame; 39 using blink::WebPerformance; 40 using blink::WebString; 41 using base::Time; 42 using base::TimeDelta; 43 using content::DocumentState; 44 45 const size_t kPLTCount = 100; 46 47 namespace { 48 49 // ID indicating that no GWS-Chrome joint experiment is active. 50 const int kNoExperiment = 0; 51 52 // Max ID of GWS-Chrome joint experiment. If you change this value, please 53 // update PLT_HISTOGRAM_WITH_GWS_VARIANT accordingly. 54 const int kMaxExperimentID = 20; 55 56 TimeDelta kPLTMin() { 57 return TimeDelta::FromMilliseconds(10); 58 } 59 TimeDelta kPLTMax() { 60 return TimeDelta::FromMinutes(10); 61 } 62 63 // This function corresponds to PLT_HISTOGRAM macro invocation without caching. 64 // Use this for PLT histograms with dynamically generated names, which 65 // otherwise can't use the caching PLT_HISTOGRAM macro without code duplication. 66 void PltHistogramWithNoMacroCaching(const std::string& name, 67 const TimeDelta& sample) { 68 // The parameters should exacly match the parameters in 69 // UMA_HISTOGRAM_CUSTOM_TIMES macro. 70 base::HistogramBase* histogram_pointer = base::Histogram::FactoryTimeGet( 71 name, kPLTMin(), kPLTMax(), kPLTCount, 72 base::HistogramBase::kUmaTargetedHistogramFlag); 73 histogram_pointer->AddTime(sample); 74 } 75 76 // This records UMA corresponding to the PLT_HISTOGRAM macro without caching. 77 void PltHistogramWithGwsPreview(const char* name, 78 const TimeDelta& sample, 79 bool is_preview, 80 int experiment_id) { 81 std::string preview_suffix = is_preview ? "_Preview" : "_NoPreview"; 82 PltHistogramWithNoMacroCaching(name + preview_suffix, sample); 83 84 if (experiment_id != kNoExperiment) { 85 std::string name_with_experiment_id = base::StringPrintf( 86 "%s%s_Experiment%d", name, preview_suffix.c_str(), experiment_id); 87 PltHistogramWithNoMacroCaching(name_with_experiment_id, sample); 88 } 89 } 90 91 #define PLT_HISTOGRAM(name, sample) \ 92 UMA_HISTOGRAM_CUSTOM_TIMES(name, sample, kPLTMin(), kPLTMax(), kPLTCount); 93 94 #define PLT_HISTOGRAM_WITH_GWS_VARIANT( \ 95 name, sample, came_from_websearch, websearch_chrome_joint_experiment_id, \ 96 is_preview) { \ 97 PLT_HISTOGRAM(name, sample); \ 98 if (came_from_websearch) { \ 99 PLT_HISTOGRAM(base::StringPrintf("%s_FromGWS", name), sample) \ 100 if (websearch_chrome_joint_experiment_id != kNoExperiment) { \ 101 std::string name_with_experiment_id = base::StringPrintf( \ 102 "%s_FromGWS_Experiment%d", \ 103 name, websearch_chrome_joint_experiment_id); \ 104 PltHistogramWithNoMacroCaching(name_with_experiment_id, sample); \ 105 } \ 106 } \ 107 PltHistogramWithGwsPreview(name, sample, is_preview, \ 108 websearch_chrome_joint_experiment_id); \ 109 } 110 111 // In addition to PLT_HISTOGRAM, add the *_DataReductionProxy variant 112 // conditionally. This macro runs only in one thread. 113 #define PLT_HISTOGRAM_DRP(name, sample, data_reduction_proxy_was_used) \ 114 do { \ 115 static base::HistogramBase* counter(NULL); \ 116 static base::HistogramBase* drp_counter(NULL); \ 117 if (!counter) { \ 118 DCHECK(drp_counter == NULL); \ 119 counter = base::Histogram::FactoryTimeGet( \ 120 name, kPLTMin(), kPLTMax(), kPLTCount, \ 121 base::Histogram::kUmaTargetedHistogramFlag); \ 122 } \ 123 counter->AddTime(sample); \ 124 if (!data_reduction_proxy_was_used) break; \ 125 if (!drp_counter) { \ 126 drp_counter = base::Histogram::FactoryTimeGet( \ 127 std::string(name) + "_DataReductionProxy", \ 128 kPLTMin(), kPLTMax(), kPLTCount, \ 129 base::Histogram::kUmaTargetedHistogramFlag); \ 130 } \ 131 drp_counter->AddTime(sample); \ 132 } while (0) 133 134 // Returns the scheme type of the given URL if its type is one for which we 135 // dump page load histograms. Otherwise returns NULL. 136 URLPattern::SchemeMasks GetSupportedSchemeType(const GURL& url) { 137 if (url.SchemeIs("http")) 138 return URLPattern::SCHEME_HTTP; 139 else if (url.SchemeIs("https")) 140 return URLPattern::SCHEME_HTTPS; 141 return static_cast<URLPattern::SchemeMasks>(0); 142 } 143 144 // Helper function to check for string in 'via' header. Returns true if 145 // |via_value| is one of the values listed in the Via header. 146 bool ViaHeaderContains(WebFrame* frame, const std::string& via_value) { 147 const char kViaHeaderName[] = "Via"; 148 std::vector<std::string> values; 149 // Multiple via headers have already been coalesced and hence each value 150 // separated by a comma corresponds to a proxy. The value added by a proxy is 151 // not expected to contain any commas. 152 // Example., Via: 1.0 Compression proxy, 1.1 Google Instant Proxy Preview 153 base::SplitString( 154 frame->dataSource()->response().httpHeaderField(kViaHeaderName).utf8(), 155 ',', &values); 156 return std::find(values.begin(), values.end(), via_value) != values.end(); 157 } 158 159 // Returns true if the data reduction proxy was used. Note, this function will 160 // produce a false positive if a page is fetched using SPDY and using a proxy, 161 // and the data reduction proxy's via value is added to the Via header. 162 // TODO(bengr): Plumb the hostname of the proxy and check if it matches 163 // |SPDY_PROXY_AUTH_ORIGIN|. 164 bool DataReductionProxyWasUsed(WebFrame* frame) { 165 DocumentState* document_state = 166 DocumentState::FromDataSource(frame->dataSource()); 167 if (!document_state->was_fetched_via_proxy()) 168 return false; 169 170 std::string via_header = 171 base::UTF16ToUTF8(frame->dataSource()->response().httpHeaderField("Via")); 172 173 if (via_header.empty()) 174 return false; 175 std::string headers = "HTTP/1.1 200 OK\nVia: " + via_header + "\n\n"; 176 // Produce raw headers, expected by the |HttpResponseHeaders| constructor. 177 std::replace(headers.begin(), headers.end(), '\n', '\0'); 178 scoped_refptr<net::HttpResponseHeaders> response_headers( 179 new net::HttpResponseHeaders(headers)); 180 return data_reduction_proxy::HasDataReductionProxyViaHeader(response_headers); 181 } 182 183 // Returns true if the provided URL is a referrer string that came from 184 // a Google Web Search results page. This is a little non-deterministic 185 // because desktop and mobile websearch differ and sometimes just provide 186 // http://www.google.com/ as the referrer. In the case of /url we can be sure 187 // that it came from websearch but we will be generous and allow for cases 188 // where a non-Google URL was provided a bare Google URL as a referrer. 189 // The domain validation matches the code used by the prerenderer for similar 190 // purposes. 191 // TODO(pmeenan): Remove the fuzzy logic when the referrer is reliable 192 bool IsFromGoogleSearchResult(const GURL& url, const GURL& referrer) { 193 if (!StartsWithASCII(referrer.host(), "www.google.", true)) 194 return false; 195 if (StartsWithASCII(referrer.path(), "/url", true)) 196 return true; 197 bool is_possible_search_referrer = 198 referrer.path().empty() || 199 referrer.path() == "/" || 200 StartsWithASCII(referrer.path(), "/search", true) || 201 StartsWithASCII(referrer.path(), "/webhp", true); 202 if (is_possible_search_referrer && 203 !StartsWithASCII(url.host(), "www.google", true)) 204 return true; 205 return false; 206 } 207 208 // Extracts a Google Web Search and Chrome joint experiment ID from a referrer 209 // that came from a Google Web Search results page. An experiment ID is embedded 210 // in a query string as a "gcjeid=" parameter value. 211 int GetQueryStringBasedExperiment(const GURL& referrer) { 212 std::string value; 213 if (!net::GetValueForKeyInQuery(referrer, "gcjeid", &value)) 214 return kNoExperiment; 215 216 int experiment_id; 217 if (!base::StringToInt(value, &experiment_id)) 218 return kNoExperiment; 219 if (0 < experiment_id && experiment_id <= kMaxExperimentID) 220 return experiment_id; 221 return kNoExperiment; 222 } 223 224 void DumpPerformanceTiming(const WebPerformance& performance, 225 DocumentState* document_state, 226 bool data_reduction_proxy_was_used, 227 bool came_from_websearch, 228 int websearch_chrome_joint_experiment_id, 229 bool is_preview) { 230 Time request = document_state->request_time(); 231 232 Time navigation_start = Time::FromDoubleT(performance.navigationStart()); 233 Time redirect_start = Time::FromDoubleT(performance.redirectStart()); 234 Time redirect_end = Time::FromDoubleT(performance.redirectEnd()); 235 Time fetch_start = Time::FromDoubleT(performance.fetchStart()); 236 Time domain_lookup_start = Time::FromDoubleT(performance.domainLookupStart()); 237 Time domain_lookup_end = Time::FromDoubleT(performance.domainLookupEnd()); 238 Time connect_start = Time::FromDoubleT(performance.connectStart()); 239 Time connect_end = Time::FromDoubleT(performance.connectEnd()); 240 Time request_start = Time::FromDoubleT(performance.requestStart()); 241 Time response_start = Time::FromDoubleT(performance.responseStart()); 242 Time response_end = Time::FromDoubleT(performance.responseEnd()); 243 Time dom_loading = Time::FromDoubleT(performance.domLoading()); 244 Time dom_interactive = Time::FromDoubleT(performance.domInteractive()); 245 Time dom_content_loaded_start = 246 Time::FromDoubleT(performance.domContentLoadedEventStart()); 247 Time dom_content_loaded_end = 248 Time::FromDoubleT(performance.domContentLoadedEventEnd()); 249 Time load_event_start = Time::FromDoubleT(performance.loadEventStart()); 250 Time load_event_end = Time::FromDoubleT(performance.loadEventEnd()); 251 Time begin = (request.is_null() ? navigation_start : request_start); 252 253 DCHECK(!navigation_start.is_null()); 254 255 // It is possible for a document to have navigation_start time, but no 256 // request_start. An example is doing a window.open, which synchronously 257 // loads "about:blank", then using document.write add a meta http-equiv 258 // refresh tag, which causes a navigation. In such case, we will arrive at 259 // this function with no request/response timing data and identical load 260 // start/end values. Avoid logging this case, as it doesn't add any 261 // meaningful information to the histogram. 262 if (request_start.is_null()) 263 return; 264 265 // TODO(dominich): Investigate conditions under which |load_event_start| and 266 // |load_event_end| may be NULL as in the non-PT_ case below. Examples in 267 // http://crbug.com/112006. 268 // DCHECK(!load_event_start.is_null()); 269 // DCHECK(!load_event_end.is_null()); 270 271 if (document_state->web_timing_histograms_recorded()) 272 return; 273 document_state->set_web_timing_histograms_recorded(true); 274 275 if (!redirect_start.is_null() && !redirect_end.is_null()) { 276 PLT_HISTOGRAM_DRP("PLT.NT_Redirect", redirect_end - redirect_start, 277 data_reduction_proxy_was_used); 278 PLT_HISTOGRAM_DRP( 279 "PLT.NT_DelayBeforeFetchRedirect", 280 (fetch_start - navigation_start) - (redirect_end - redirect_start), 281 data_reduction_proxy_was_used); 282 } else { 283 PLT_HISTOGRAM_DRP("PLT.NT_DelayBeforeFetch", 284 fetch_start - navigation_start, 285 data_reduction_proxy_was_used); 286 } 287 PLT_HISTOGRAM_DRP("PLT.NT_DelayBeforeDomainLookup", 288 domain_lookup_start - fetch_start, 289 data_reduction_proxy_was_used); 290 PLT_HISTOGRAM_DRP("PLT.NT_DomainLookup", 291 domain_lookup_end - domain_lookup_start, 292 data_reduction_proxy_was_used); 293 PLT_HISTOGRAM_DRP("PLT.NT_DelayBeforeConnect", 294 connect_start - domain_lookup_end, 295 data_reduction_proxy_was_used); 296 PLT_HISTOGRAM_DRP("PLT.NT_Connect", connect_end - connect_start, 297 data_reduction_proxy_was_used); 298 PLT_HISTOGRAM_DRP("PLT.NT_DelayBeforeRequest", 299 request_start - connect_end, 300 data_reduction_proxy_was_used); 301 PLT_HISTOGRAM_DRP("PLT.NT_Request", response_start - request_start, 302 data_reduction_proxy_was_used); 303 PLT_HISTOGRAM_DRP("PLT.NT_Response", response_end - response_start, 304 data_reduction_proxy_was_used); 305 306 if (!dom_loading.is_null()) { 307 PLT_HISTOGRAM_DRP("PLT.NT_DelayBeforeDomLoading", 308 dom_loading - response_start, 309 data_reduction_proxy_was_used); 310 } 311 if (!dom_interactive.is_null() && !dom_loading.is_null()) { 312 PLT_HISTOGRAM_DRP("PLT.NT_DomLoading", 313 dom_interactive - dom_loading, 314 data_reduction_proxy_was_used); 315 } 316 if (!dom_content_loaded_start.is_null() && !dom_interactive.is_null()) { 317 PLT_HISTOGRAM_DRP("PLT.NT_DomInteractive", 318 dom_content_loaded_start - dom_interactive, 319 data_reduction_proxy_was_used); 320 } 321 if (!dom_content_loaded_start.is_null() && 322 !dom_content_loaded_end.is_null() ) { 323 PLT_HISTOGRAM_DRP("PLT.NT_DomContentLoaded", 324 dom_content_loaded_end - dom_content_loaded_start, 325 data_reduction_proxy_was_used); 326 } 327 if (!dom_content_loaded_end.is_null() && !load_event_start.is_null()) { 328 PLT_HISTOGRAM_DRP("PLT.NT_DelayBeforeLoadEvent", 329 load_event_start - dom_content_loaded_end, 330 data_reduction_proxy_was_used); 331 } 332 333 // TODO(simonjam): There is no way to distinguish between abandonment and 334 // intentional Javascript navigation before the load event fires. 335 // TODO(dominich): Load type breakdown 336 if (!load_event_start.is_null()) { 337 PLT_HISTOGRAM_WITH_GWS_VARIANT("PLT.PT_BeginToFinishDoc", 338 load_event_start - begin, 339 came_from_websearch, 340 websearch_chrome_joint_experiment_id, 341 is_preview); 342 PLT_HISTOGRAM_WITH_GWS_VARIANT("PLT.PT_CommitToFinishDoc", 343 load_event_start - response_start, 344 came_from_websearch, 345 websearch_chrome_joint_experiment_id, 346 is_preview); 347 PLT_HISTOGRAM_WITH_GWS_VARIANT("PLT.PT_RequestToFinishDoc", 348 load_event_start - navigation_start, 349 came_from_websearch, 350 websearch_chrome_joint_experiment_id, 351 is_preview); 352 if (data_reduction_proxy_was_used) { 353 PLT_HISTOGRAM("PLT.PT_BeginToFinishDoc_DataReductionProxy", 354 load_event_start - begin); 355 PLT_HISTOGRAM("PLT.PT_CommitToFinishDoc_DataReductionProxy", 356 load_event_start - response_start); 357 PLT_HISTOGRAM("PLT.PT_RequestToFinishDoc_DataReductionProxy", 358 load_event_start - navigation_start); 359 } 360 } 361 if (!load_event_end.is_null()) { 362 PLT_HISTOGRAM_WITH_GWS_VARIANT("PLT.PT_BeginToFinish", 363 load_event_end - begin, 364 came_from_websearch, 365 websearch_chrome_joint_experiment_id, 366 is_preview); 367 PLT_HISTOGRAM_WITH_GWS_VARIANT("PLT.PT_CommitToFinish", 368 load_event_end - response_start, 369 came_from_websearch, 370 websearch_chrome_joint_experiment_id, 371 is_preview); 372 PLT_HISTOGRAM_WITH_GWS_VARIANT("PLT.PT_RequestToFinish", 373 load_event_end - navigation_start, 374 came_from_websearch, 375 websearch_chrome_joint_experiment_id, 376 is_preview); 377 PLT_HISTOGRAM_WITH_GWS_VARIANT("PLT.PT_StartToFinish", 378 load_event_end - request_start, 379 came_from_websearch, 380 websearch_chrome_joint_experiment_id, 381 is_preview); 382 if (data_reduction_proxy_was_used) { 383 PLT_HISTOGRAM("PLT.PT_BeginToFinish_DataReductionProxy", 384 load_event_end - begin); 385 PLT_HISTOGRAM("PLT.PT_CommitToFinish_DataReductionProxy", 386 load_event_end - response_start); 387 PLT_HISTOGRAM("PLT.PT_RequestToFinish_DataReductionProxy", 388 load_event_end - navigation_start); 389 PLT_HISTOGRAM("PLT.PT_StartToFinish_DataReductionProxy", 390 load_event_end - request_start); 391 } 392 } 393 if (!load_event_start.is_null() && !load_event_end.is_null()) { 394 PLT_HISTOGRAM("PLT.PT_FinishDocToFinish", 395 load_event_end - load_event_start); 396 PLT_HISTOGRAM_DRP("PLT.NT_LoadEvent", 397 load_event_end - load_event_start, 398 data_reduction_proxy_was_used); 399 400 if (data_reduction_proxy_was_used) 401 PLT_HISTOGRAM("PLT.PT_FinishDocToFinish_DataReductionProxy", 402 load_event_end - load_event_start); 403 } 404 if (!dom_content_loaded_start.is_null()) { 405 PLT_HISTOGRAM_WITH_GWS_VARIANT("PLT.PT_RequestToDomContentLoaded", 406 dom_content_loaded_start - navigation_start, 407 came_from_websearch, 408 websearch_chrome_joint_experiment_id, 409 is_preview); 410 if (data_reduction_proxy_was_used) 411 PLT_HISTOGRAM("PLT.PT_RequestToDomContentLoaded_DataReductionProxy", 412 dom_content_loaded_start - navigation_start); 413 } 414 PLT_HISTOGRAM_WITH_GWS_VARIANT("PLT.PT_BeginToCommit", 415 response_start - begin, 416 came_from_websearch, 417 websearch_chrome_joint_experiment_id, 418 is_preview); 419 PLT_HISTOGRAM_WITH_GWS_VARIANT("PLT.PT_RequestToStart", 420 request_start - navigation_start, 421 came_from_websearch, 422 websearch_chrome_joint_experiment_id, 423 is_preview); 424 PLT_HISTOGRAM_WITH_GWS_VARIANT("PLT.PT_StartToCommit", 425 response_start - request_start, 426 came_from_websearch, 427 websearch_chrome_joint_experiment_id, 428 is_preview); 429 PLT_HISTOGRAM_WITH_GWS_VARIANT("PLT.PT_RequestToCommit", 430 response_start - navigation_start, 431 came_from_websearch, 432 websearch_chrome_joint_experiment_id, 433 is_preview); 434 if (data_reduction_proxy_was_used) { 435 PLT_HISTOGRAM("PLT.PT_BeginToCommit_DataReductionProxy", 436 response_start - begin); 437 PLT_HISTOGRAM("PLT.PT_RequestToStart_DataReductionProxy", 438 request_start - navigation_start); 439 PLT_HISTOGRAM("PLT.PT_StartToCommit_DataReductionProxy", 440 response_start - request_start); 441 PLT_HISTOGRAM("PLT.PT_RequestToCommit_DataReductionProxy", 442 response_start - navigation_start); 443 } 444 } 445 446 enum MissingStartType { 447 START_MISSING = 0x1, 448 COMMIT_MISSING = 0x2, 449 NAV_START_MISSING = 0x4, 450 MISSING_START_TYPE_MAX = 0x8 451 }; 452 453 enum AbandonType { 454 FINISH_DOC_MISSING = 0x1, 455 FINISH_ALL_LOADS_MISSING = 0x2, 456 LOAD_EVENT_START_MISSING = 0x4, 457 LOAD_EVENT_END_MISSING = 0x8, 458 ABANDON_TYPE_MAX = 0x10 459 }; 460 461 } // namespace 462 463 PageLoadHistograms::PageLoadHistograms(content::RenderView* render_view) 464 : content::RenderViewObserver(render_view) { 465 } 466 467 void PageLoadHistograms::Dump(WebFrame* frame) { 468 // We only dump histograms for main frames. 469 // In the future, it may be interesting to tag subframes and dump them too. 470 if (!frame || frame->parent()) 471 return; 472 473 // Only dump for supported schemes. 474 URLPattern::SchemeMasks scheme_type = 475 GetSupportedSchemeType(frame->document().url()); 476 if (scheme_type == 0) 477 return; 478 479 // Ignore multipart requests. 480 if (frame->dataSource()->response().isMultipartPayload()) 481 return; 482 483 DocumentState* document_state = 484 DocumentState::FromDataSource(frame->dataSource()); 485 486 bool data_reduction_proxy_was_used = DataReductionProxyWasUsed(frame); 487 bool came_from_websearch = 488 IsFromGoogleSearchResult(frame->document().url(), 489 GURL(frame->document().referrer())); 490 int websearch_chrome_joint_experiment_id = kNoExperiment; 491 bool is_preview = false; 492 if (came_from_websearch) { 493 websearch_chrome_joint_experiment_id = 494 GetQueryStringBasedExperiment(GURL(frame->document().referrer())); 495 is_preview = ViaHeaderContains(frame, "1.1 Google Instant Proxy Preview"); 496 } 497 498 // Times based on the Web Timing metrics. 499 // http://www.w3.org/TR/navigation-timing/ 500 // TODO(tonyg, jar): We are in the process of vetting these metrics against 501 // the existing ones. Once we understand any differences, we will standardize 502 // on a single set of metrics. 503 DumpPerformanceTiming(frame->performance(), document_state, 504 data_reduction_proxy_was_used, 505 came_from_websearch, 506 websearch_chrome_joint_experiment_id, 507 is_preview); 508 509 // If we've already dumped, do nothing. 510 // This simple bool works because we only dump for the main frame. 511 if (document_state->load_histograms_recorded()) 512 return; 513 514 // Collect measurement times. 515 Time start = document_state->start_load_time(); 516 Time commit = document_state->commit_load_time(); 517 518 // TODO(tonyg, jar): Start can be missing after an in-document navigation and 519 // possibly other cases like a very premature abandonment of the page. 520 // The PLT.MissingStart histogram should help us troubleshoot and then we can 521 // remove this. 522 Time navigation_start = 523 Time::FromDoubleT(frame->performance().navigationStart()); 524 int missing_start_type = 0; 525 missing_start_type |= start.is_null() ? START_MISSING : 0; 526 missing_start_type |= commit.is_null() ? COMMIT_MISSING : 0; 527 missing_start_type |= navigation_start.is_null() ? NAV_START_MISSING : 0; 528 UMA_HISTOGRAM_ENUMERATION("PLT.MissingStart", missing_start_type, 529 MISSING_START_TYPE_MAX); 530 if (missing_start_type) 531 return; 532 533 // We properly handle null values for the next 3 variables. 534 Time request = document_state->request_time(); 535 Time first_paint = document_state->first_paint_time(); 536 Time first_paint_after_load = document_state->first_paint_after_load_time(); 537 Time finish_doc = document_state->finish_document_load_time(); 538 Time finish_all_loads = document_state->finish_load_time(); 539 540 // TODO(tonyg, jar): We suspect a bug in abandonment counting, this temporary 541 // histogram should help us to troubleshoot. 542 Time load_event_start = 543 Time::FromDoubleT(frame->performance().loadEventStart()); 544 Time load_event_end = Time::FromDoubleT(frame->performance().loadEventEnd()); 545 int abandon_type = 0; 546 abandon_type |= finish_doc.is_null() ? FINISH_DOC_MISSING : 0; 547 abandon_type |= finish_all_loads.is_null() ? FINISH_ALL_LOADS_MISSING : 0; 548 abandon_type |= load_event_start.is_null() ? LOAD_EVENT_START_MISSING : 0; 549 abandon_type |= load_event_end.is_null() ? LOAD_EVENT_END_MISSING : 0; 550 UMA_HISTOGRAM_ENUMERATION("PLT.AbandonType", abandon_type, ABANDON_TYPE_MAX); 551 552 // Handle case where user hits "stop" or "back" before loading completely. 553 bool abandoned_page = finish_doc.is_null(); 554 if (abandoned_page) { 555 finish_doc = Time::Now(); 556 document_state->set_finish_document_load_time(finish_doc); 557 } 558 559 // TODO(jar): We should really discriminate the definition of "abandon" more 560 // finely. We should have: 561 // abandon_before_document_loaded 562 // abandon_before_onload_fired 563 564 if (finish_all_loads.is_null()) { 565 finish_all_loads = Time::Now(); 566 document_state->set_finish_load_time(finish_all_loads); 567 } else { 568 DCHECK(!abandoned_page); // How can the doc have finished but not the page? 569 if (abandoned_page) 570 return; // Don't try to record a stat which is broken. 571 } 572 573 document_state->set_load_histograms_recorded(true); 574 575 // Note: Client side redirects will have no request time. 576 Time begin = request.is_null() ? start : request; 577 TimeDelta begin_to_finish_doc = finish_doc - begin; 578 TimeDelta begin_to_finish_all_loads = finish_all_loads - begin; 579 TimeDelta start_to_finish_all_loads = finish_all_loads - start; 580 TimeDelta start_to_commit = commit - start; 581 582 DocumentState::LoadType load_type = document_state->load_type(); 583 584 // The above code sanitized all values of times, in preparation for creating 585 // actual histograms. The remainder of this code could be run at destructor 586 // time for the document_state, since all data is intact. 587 588 // Aggregate PLT data across all link types. 589 UMA_HISTOGRAM_ENUMERATION("PLT.Abandoned", abandoned_page ? 1 : 0, 2); 590 UMA_HISTOGRAM_ENUMERATION("PLT.LoadType", load_type, 591 DocumentState::kLoadTypeMax); 592 PLT_HISTOGRAM("PLT.StartToCommit", start_to_commit); 593 PLT_HISTOGRAM("PLT.CommitToFinishDoc", finish_doc - commit); 594 PLT_HISTOGRAM("PLT.FinishDocToFinish", finish_all_loads - finish_doc); 595 PLT_HISTOGRAM("PLT.BeginToCommit", commit - begin); 596 PLT_HISTOGRAM("PLT.StartToFinish", start_to_finish_all_loads); 597 if (!request.is_null()) { 598 PLT_HISTOGRAM("PLT.RequestToStart", start - request); 599 PLT_HISTOGRAM("PLT.RequestToFinish", finish_all_loads - request); 600 } 601 PLT_HISTOGRAM("PLT.CommitToFinish", finish_all_loads - commit); 602 603 scoped_ptr<TimeDelta> begin_to_first_paint; 604 scoped_ptr<TimeDelta> commit_to_first_paint; 605 if (!first_paint.is_null()) { 606 // 'first_paint' can be before 'begin' for an unknown reason. 607 // See bug http://crbug.com/125273 for details. 608 if (begin <= first_paint) { 609 begin_to_first_paint.reset(new TimeDelta(first_paint - begin)); 610 PLT_HISTOGRAM_WITH_GWS_VARIANT("PLT.BeginToFirstPaint", 611 *begin_to_first_paint, 612 came_from_websearch, 613 websearch_chrome_joint_experiment_id, 614 is_preview); 615 } 616 DCHECK(commit <= first_paint); 617 commit_to_first_paint.reset(new TimeDelta(first_paint - commit)); 618 PLT_HISTOGRAM_WITH_GWS_VARIANT("PLT.CommitToFirstPaint", 619 *commit_to_first_paint, 620 came_from_websearch, 621 websearch_chrome_joint_experiment_id, 622 is_preview); 623 } 624 if (!first_paint_after_load.is_null()) { 625 // 'first_paint_after_load' can be before 'begin' for an unknown reason. 626 // See bug http://crbug.com/125273 for details. 627 if (begin <= first_paint_after_load) { 628 PLT_HISTOGRAM("PLT.BeginToFirstPaintAfterLoad", 629 first_paint_after_load - begin); 630 } 631 DCHECK(commit <= first_paint_after_load); 632 PLT_HISTOGRAM("PLT.CommitToFirstPaintAfterLoad", 633 first_paint_after_load - commit); 634 DCHECK(finish_all_loads <= first_paint_after_load); 635 PLT_HISTOGRAM("PLT.FinishToFirstPaintAfterLoad", 636 first_paint_after_load - finish_all_loads); 637 } 638 PLT_HISTOGRAM_WITH_GWS_VARIANT("PLT.BeginToFinishDoc", begin_to_finish_doc, 639 came_from_websearch, 640 websearch_chrome_joint_experiment_id, 641 is_preview); 642 PLT_HISTOGRAM_WITH_GWS_VARIANT("PLT.BeginToFinish", begin_to_finish_all_loads, 643 came_from_websearch, 644 websearch_chrome_joint_experiment_id, 645 is_preview); 646 647 // Load type related histograms. 648 switch (load_type) { 649 case DocumentState::UNDEFINED_LOAD: 650 PLT_HISTOGRAM("PLT.BeginToFinishDoc_UndefLoad", begin_to_finish_doc); 651 PLT_HISTOGRAM("PLT.BeginToFinish_UndefLoad", begin_to_finish_all_loads); 652 break; 653 case DocumentState::RELOAD: 654 PLT_HISTOGRAM("PLT.BeginToFinishDoc_Reload", begin_to_finish_doc); 655 PLT_HISTOGRAM("PLT.BeginToFinish_Reload", begin_to_finish_all_loads); 656 break; 657 case DocumentState::HISTORY_LOAD: 658 PLT_HISTOGRAM("PLT.BeginToFinishDoc_HistoryLoad", begin_to_finish_doc); 659 PLT_HISTOGRAM("PLT.BeginToFinish_HistoryLoad", begin_to_finish_all_loads); 660 break; 661 case DocumentState::NORMAL_LOAD: 662 PLT_HISTOGRAM("PLT.BeginToFinishDoc_NormalLoad", begin_to_finish_doc); 663 PLT_HISTOGRAM("PLT.BeginToFinish_NormalLoad", begin_to_finish_all_loads); 664 break; 665 case DocumentState::LINK_LOAD_NORMAL: 666 PLT_HISTOGRAM("PLT.BeginToFinishDoc_LinkLoadNormal", 667 begin_to_finish_doc); 668 PLT_HISTOGRAM("PLT.BeginToFinish_LinkLoadNormal", 669 begin_to_finish_all_loads); 670 break; 671 case DocumentState::LINK_LOAD_RELOAD: 672 PLT_HISTOGRAM("PLT.BeginToFinishDoc_LinkLoadReload", 673 begin_to_finish_doc); 674 PLT_HISTOGRAM("PLT.BeginToFinish_LinkLoadReload", 675 begin_to_finish_all_loads); 676 break; 677 case DocumentState::LINK_LOAD_CACHE_STALE_OK: 678 PLT_HISTOGRAM("PLT.BeginToFinishDoc_LinkLoadStaleOk", 679 begin_to_finish_doc); 680 PLT_HISTOGRAM("PLT.BeginToFinish_LinkLoadStaleOk", 681 begin_to_finish_all_loads); 682 break; 683 case DocumentState::LINK_LOAD_CACHE_ONLY: 684 PLT_HISTOGRAM("PLT.BeginToFinishDoc_LinkLoadCacheOnly", 685 begin_to_finish_doc); 686 PLT_HISTOGRAM("PLT.BeginToFinish_LinkLoadCacheOnly", 687 begin_to_finish_all_loads); 688 break; 689 default: 690 break; 691 } 692 693 if (data_reduction_proxy_was_used) { 694 UMA_HISTOGRAM_ENUMERATION( 695 "PLT.Abandoned_SpdyProxy", abandoned_page ? 1 : 0, 2); 696 PLT_HISTOGRAM("PLT.BeginToFinishDoc_SpdyProxy", begin_to_finish_doc); 697 PLT_HISTOGRAM("PLT.BeginToFinish_SpdyProxy", begin_to_finish_all_loads); 698 } 699 700 if (document_state->was_prefetcher()) { 701 PLT_HISTOGRAM("PLT.BeginToFinishDoc_ContentPrefetcher", 702 begin_to_finish_doc); 703 PLT_HISTOGRAM("PLT.BeginToFinish_ContentPrefetcher", 704 begin_to_finish_all_loads); 705 } 706 if (document_state->was_referred_by_prefetcher()) { 707 PLT_HISTOGRAM("PLT.BeginToFinishDoc_ContentPrefetcherReferrer", 708 begin_to_finish_doc); 709 PLT_HISTOGRAM("PLT.BeginToFinish_ContentPrefetcherReferrer", 710 begin_to_finish_all_loads); 711 } 712 if (document_state->was_after_preconnect_request()) { 713 PLT_HISTOGRAM("PLT.BeginToFinishDoc_AfterPreconnectRequest", 714 begin_to_finish_doc); 715 PLT_HISTOGRAM("PLT.BeginToFinish_AfterPreconnectRequest", 716 begin_to_finish_all_loads); 717 } 718 719 const bool use_webrequest_histogram = 720 ChromeContentRendererClient::WasWebRequestUsedBySomeExtensions(); 721 if (use_webrequest_histogram) { 722 UMA_HISTOGRAM_ENUMERATION( 723 "PLT.Abandoned_ExtensionWebRequest", 724 abandoned_page ? 1 : 0, 2); 725 switch (load_type) { 726 case DocumentState::NORMAL_LOAD: 727 PLT_HISTOGRAM( 728 "PLT.BeginToFinish_NormalLoad_ExtensionWebRequest", 729 begin_to_finish_all_loads); 730 break; 731 case DocumentState::LINK_LOAD_NORMAL: 732 PLT_HISTOGRAM( 733 "PLT.BeginToFinish_LinkLoadNormal_ExtensionWebRequest", 734 begin_to_finish_all_loads); 735 break; 736 case DocumentState::LINK_LOAD_RELOAD: 737 PLT_HISTOGRAM( 738 "PLT.BeginToFinish_LinkLoadReload_ExtensionWebRequest", 739 begin_to_finish_all_loads); 740 break; 741 case DocumentState::LINK_LOAD_CACHE_STALE_OK: 742 PLT_HISTOGRAM( 743 "PLT.BeginToFinish_LinkLoadStaleOk_ExtensionWebRequest", 744 begin_to_finish_all_loads); 745 break; 746 default: 747 break; 748 } 749 } 750 751 // Record SpdyCwnd results. 752 if (document_state->was_fetched_via_spdy()) { 753 switch (load_type) { 754 case DocumentState::LINK_LOAD_NORMAL: 755 PLT_HISTOGRAM("PLT.BeginToFinish_LinkLoadNormal_cwndDynamic", 756 begin_to_finish_all_loads); 757 PLT_HISTOGRAM("PLT.StartToFinish_LinkLoadNormal_cwndDynamic", 758 start_to_finish_all_loads); 759 PLT_HISTOGRAM("PLT.StartToCommit_LinkLoadNormal_cwndDynamic", 760 start_to_commit); 761 break; 762 case DocumentState::NORMAL_LOAD: 763 PLT_HISTOGRAM("PLT.BeginToFinish_NormalLoad_cwndDynamic", 764 begin_to_finish_all_loads); 765 PLT_HISTOGRAM("PLT.StartToFinish_NormalLoad_cwndDynamic", 766 start_to_finish_all_loads); 767 PLT_HISTOGRAM("PLT.StartToCommit_NormalLoad_cwndDynamic", 768 start_to_commit); 769 break; 770 default: 771 break; 772 } 773 } 774 775 // Record page load time and abandonment rates for proxy cases. 776 if (document_state->was_fetched_via_proxy()) { 777 if (scheme_type == URLPattern::SCHEME_HTTPS) { 778 PLT_HISTOGRAM("PLT.StartToFinish.Proxy.https", start_to_finish_all_loads); 779 UMA_HISTOGRAM_ENUMERATION("PLT.Abandoned.Proxy.https", 780 abandoned_page ? 1 : 0, 2); 781 } else { 782 DCHECK(scheme_type == URLPattern::SCHEME_HTTP); 783 PLT_HISTOGRAM("PLT.StartToFinish.Proxy.http", start_to_finish_all_loads); 784 UMA_HISTOGRAM_ENUMERATION("PLT.Abandoned.Proxy.http", 785 abandoned_page ? 1 : 0, 2); 786 } 787 } else { 788 if (scheme_type == URLPattern::SCHEME_HTTPS) { 789 PLT_HISTOGRAM("PLT.StartToFinish.NoProxy.https", 790 start_to_finish_all_loads); 791 UMA_HISTOGRAM_ENUMERATION("PLT.Abandoned.NoProxy.https", 792 abandoned_page ? 1 : 0, 2); 793 } else { 794 DCHECK(scheme_type == URLPattern::SCHEME_HTTP); 795 PLT_HISTOGRAM("PLT.StartToFinish.NoProxy.http", 796 start_to_finish_all_loads); 797 UMA_HISTOGRAM_ENUMERATION("PLT.Abandoned.NoProxy.http", 798 abandoned_page ? 1 : 0, 2); 799 } 800 } 801 802 // Log the PLT to the info log. 803 LogPageLoadTime(document_state, frame->dataSource()); 804 805 // Since there are currently no guarantees that renderer histograms will be 806 // sent to the browser, we initiate a PostTask here to be sure that we send 807 // the histograms we generated. Without this call, pages that don't have an 808 // on-close-handler might generate data that is lost when the renderer is 809 // shutdown abruptly (perchance because the user closed the tab). 810 // TODO(jar) BUG=33233: This needs to be moved to a PostDelayedTask, and it 811 // should post when the onload is complete, so that it doesn't interfere with 812 // the next load. 813 content::RenderThread::Get()->UpdateHistograms( 814 content::kHistogramSynchronizerReservedSequenceNumber); 815 } 816 817 void PageLoadHistograms::FrameWillClose(WebFrame* frame) { 818 Dump(frame); 819 } 820 821 void PageLoadHistograms::ClosePage() { 822 // TODO(davemoore) This code should be removed once willClose() gets 823 // called when a page is destroyed. page_load_histograms_.Dump() is safe 824 // to call multiple times for the same frame, but it will simplify things. 825 Dump(render_view()->GetWebView()->mainFrame()); 826 } 827 828 void PageLoadHistograms::LogPageLoadTime(const DocumentState* document_state, 829 const WebDataSource* ds) const { 830 // Because this function gets called on every page load, 831 // take extra care to optimize it away if logging is turned off. 832 if (logging::LOG_INFO < logging::GetMinLogLevel()) 833 return; 834 835 DCHECK(document_state); 836 DCHECK(ds); 837 GURL url(ds->request().url()); 838 Time start = document_state->start_load_time(); 839 Time finish = document_state->finish_load_time(); 840 // TODO(mbelshe): should we log more stats? 841 VLOG(1) << "PLT: " << (finish - start).InMilliseconds() << "ms " 842 << url.spec(); 843 } 844