Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / chrome / renderer / page_load_histograms.cc
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(
181       response_headers.get(), NULL);
182 }
183
184 // Returns true if the provided URL is a referrer string that came from
185 // a Google Web Search results page. This is a little non-deterministic
186 // because desktop and mobile websearch differ and sometimes just provide
187 // http://www.google.com/ as the referrer. In the case of /url we can be sure
188 // that it came from websearch but we will be generous and allow for cases
189 // where a non-Google URL was provided a bare Google URL as a referrer.
190 // The domain validation matches the code used by the prerenderer for similar
191 // purposes.
192 // TODO(pmeenan): Remove the fuzzy logic when the referrer is reliable
193 bool IsFromGoogleSearchResult(const GURL& url, const GURL& referrer) {
194   if (!StartsWithASCII(referrer.host(), "www.google.", true))
195     return false;
196   if (StartsWithASCII(referrer.path(), "/url", true))
197     return true;
198   bool is_possible_search_referrer =
199       referrer.path().empty() ||
200       referrer.path() == "/" ||
201       StartsWithASCII(referrer.path(), "/search", true) ||
202       StartsWithASCII(referrer.path(), "/webhp", true);
203   if (is_possible_search_referrer &&
204       !StartsWithASCII(url.host(), "www.google", true))
205     return true;
206   return false;
207 }
208
209 // Extracts a Google Web Search and Chrome joint experiment ID from a referrer
210 // that came from a Google Web Search results page. An experiment ID is embedded
211 // in a query string as a "gcjeid=" parameter value.
212 int GetQueryStringBasedExperiment(const GURL& referrer) {
213   std::string value;
214   if (!net::GetValueForKeyInQuery(referrer, "gcjeid", &value))
215     return kNoExperiment;
216
217   int experiment_id;
218   if (!base::StringToInt(value, &experiment_id))
219     return kNoExperiment;
220   if (0 < experiment_id && experiment_id <= kMaxExperimentID)
221     return experiment_id;
222   return kNoExperiment;
223 }
224
225 void DumpHistograms(const WebPerformance& performance,
226                     DocumentState* document_state,
227                     bool data_reduction_proxy_was_used,
228                     bool came_from_websearch,
229                     int websearch_chrome_joint_experiment_id,
230                     bool is_preview) {
231   // This function records new histograms based on the Navigation Timing
232   // records. As such, the histograms should not depend on the deprecated timing
233   // information collected in DocumentState. However, here for some reason we
234   // check if document_state->request_time() is null. TODO(ppi): find out why
235   // and remove DocumentState from the parameter list.
236   Time request = document_state->request_time();
237
238   Time navigation_start = Time::FromDoubleT(performance.navigationStart());
239   Time redirect_start = Time::FromDoubleT(performance.redirectStart());
240   Time redirect_end = Time::FromDoubleT(performance.redirectEnd());
241   Time fetch_start = Time::FromDoubleT(performance.fetchStart());
242   Time domain_lookup_start = Time::FromDoubleT(performance.domainLookupStart());
243   Time domain_lookup_end = Time::FromDoubleT(performance.domainLookupEnd());
244   Time connect_start = Time::FromDoubleT(performance.connectStart());
245   Time connect_end = Time::FromDoubleT(performance.connectEnd());
246   Time request_start = Time::FromDoubleT(performance.requestStart());
247   Time response_start = Time::FromDoubleT(performance.responseStart());
248   Time response_end = Time::FromDoubleT(performance.responseEnd());
249   Time dom_loading = Time::FromDoubleT(performance.domLoading());
250   Time dom_interactive = Time::FromDoubleT(performance.domInteractive());
251   Time dom_content_loaded_start =
252       Time::FromDoubleT(performance.domContentLoadedEventStart());
253   Time dom_content_loaded_end =
254       Time::FromDoubleT(performance.domContentLoadedEventEnd());
255   Time load_event_start = Time::FromDoubleT(performance.loadEventStart());
256   Time load_event_end = Time::FromDoubleT(performance.loadEventEnd());
257   Time begin = (request.is_null() ? navigation_start : request_start);
258
259   DCHECK(!navigation_start.is_null());
260
261   // It is possible for a document to have navigation_start time, but no
262   // request_start. An example is doing a window.open, which synchronously
263   // loads "about:blank", then using document.write add a meta http-equiv
264   // refresh tag, which causes a navigation. In such case, we will arrive at
265   // this function with no request/response timing data and identical load
266   // start/end values. Avoid logging this case, as it doesn't add any
267   // meaningful information to the histogram.
268   if (request_start.is_null())
269     return;
270
271   // TODO(dominich): Investigate conditions under which |load_event_start| and
272   // |load_event_end| may be NULL as in the non-PT_ case below. Examples in
273   // http://crbug.com/112006.
274   // DCHECK(!load_event_start.is_null());
275   // DCHECK(!load_event_end.is_null());
276
277   if (document_state->web_timing_histograms_recorded())
278     return;
279   document_state->set_web_timing_histograms_recorded(true);
280
281   if (!redirect_start.is_null() && !redirect_end.is_null()) {
282     PLT_HISTOGRAM_DRP("PLT.NT_Redirect", redirect_end - redirect_start,
283                       data_reduction_proxy_was_used);
284     PLT_HISTOGRAM_DRP(
285         "PLT.NT_DelayBeforeFetchRedirect",
286         (fetch_start - navigation_start) - (redirect_end - redirect_start),
287         data_reduction_proxy_was_used);
288   } else {
289     PLT_HISTOGRAM_DRP("PLT.NT_DelayBeforeFetch",
290                       fetch_start - navigation_start,
291                       data_reduction_proxy_was_used);
292   }
293   PLT_HISTOGRAM_DRP("PLT.NT_DelayBeforeDomainLookup",
294                     domain_lookup_start - fetch_start,
295                     data_reduction_proxy_was_used);
296   PLT_HISTOGRAM_DRP("PLT.NT_DomainLookup",
297                     domain_lookup_end - domain_lookup_start,
298                     data_reduction_proxy_was_used);
299   PLT_HISTOGRAM_DRP("PLT.NT_DelayBeforeConnect",
300                     connect_start - domain_lookup_end,
301                     data_reduction_proxy_was_used);
302   PLT_HISTOGRAM_DRP("PLT.NT_Connect", connect_end - connect_start,
303                     data_reduction_proxy_was_used);
304   PLT_HISTOGRAM_DRP("PLT.NT_DelayBeforeRequest",
305                     request_start - connect_end,
306                     data_reduction_proxy_was_used);
307   PLT_HISTOGRAM_DRP("PLT.NT_Request", response_start - request_start,
308                     data_reduction_proxy_was_used);
309   PLT_HISTOGRAM_DRP("PLT.NT_Response", response_end - response_start,
310                     data_reduction_proxy_was_used);
311
312   if (!dom_loading.is_null()) {
313     PLT_HISTOGRAM_DRP("PLT.NT_DelayBeforeDomLoading",
314                       dom_loading - response_start,
315                       data_reduction_proxy_was_used);
316   }
317   if (!dom_interactive.is_null() && !dom_loading.is_null()) {
318     PLT_HISTOGRAM_DRP("PLT.NT_DomLoading",
319                       dom_interactive - dom_loading,
320                       data_reduction_proxy_was_used);
321   }
322   if (!dom_content_loaded_start.is_null() && !dom_interactive.is_null()) {
323     PLT_HISTOGRAM_DRP("PLT.NT_DomInteractive",
324                       dom_content_loaded_start - dom_interactive,
325                       data_reduction_proxy_was_used);
326   }
327   if (!dom_content_loaded_start.is_null() &&
328       !dom_content_loaded_end.is_null() ) {
329     PLT_HISTOGRAM_DRP("PLT.NT_DomContentLoaded",
330                       dom_content_loaded_end - dom_content_loaded_start,
331                       data_reduction_proxy_was_used);
332   }
333   if (!dom_content_loaded_end.is_null() && !load_event_start.is_null()) {
334     PLT_HISTOGRAM_DRP("PLT.NT_DelayBeforeLoadEvent",
335                       load_event_start - dom_content_loaded_end,
336                       data_reduction_proxy_was_used);
337   }
338
339   // TODO(simonjam): There is no way to distinguish between abandonment and
340   // intentional Javascript navigation before the load event fires.
341   // TODO(dominich): Load type breakdown
342   if (!load_event_start.is_null()) {
343     PLT_HISTOGRAM_WITH_GWS_VARIANT("PLT.PT_BeginToFinishDoc",
344                                    load_event_start - begin,
345                                    came_from_websearch,
346                                    websearch_chrome_joint_experiment_id,
347                                    is_preview);
348     PLT_HISTOGRAM_WITH_GWS_VARIANT("PLT.PT_CommitToFinishDoc",
349                                    load_event_start - response_start,
350                                    came_from_websearch,
351                                    websearch_chrome_joint_experiment_id,
352                                    is_preview);
353     PLT_HISTOGRAM_WITH_GWS_VARIANT("PLT.PT_RequestToFinishDoc",
354                                    load_event_start - navigation_start,
355                                    came_from_websearch,
356                                    websearch_chrome_joint_experiment_id,
357                                    is_preview);
358     if (data_reduction_proxy_was_used) {
359       PLT_HISTOGRAM("PLT.PT_BeginToFinishDoc_DataReductionProxy",
360                     load_event_start - begin);
361       PLT_HISTOGRAM("PLT.PT_CommitToFinishDoc_DataReductionProxy",
362                     load_event_start - response_start);
363       PLT_HISTOGRAM("PLT.PT_RequestToFinishDoc_DataReductionProxy",
364                     load_event_start - navigation_start);
365     }
366   }
367   if (!load_event_end.is_null()) {
368     PLT_HISTOGRAM_WITH_GWS_VARIANT("PLT.PT_BeginToFinish",
369                                    load_event_end - begin,
370                                    came_from_websearch,
371                                    websearch_chrome_joint_experiment_id,
372                                    is_preview);
373     PLT_HISTOGRAM_WITH_GWS_VARIANT("PLT.PT_CommitToFinish",
374                                    load_event_end - response_start,
375                                    came_from_websearch,
376                                    websearch_chrome_joint_experiment_id,
377                                    is_preview);
378     PLT_HISTOGRAM_WITH_GWS_VARIANT("PLT.PT_RequestToFinish",
379                                    load_event_end - navigation_start,
380                                    came_from_websearch,
381                                    websearch_chrome_joint_experiment_id,
382                                    is_preview);
383     PLT_HISTOGRAM_WITH_GWS_VARIANT("PLT.PT_StartToFinish",
384                                    load_event_end - request_start,
385                                    came_from_websearch,
386                                    websearch_chrome_joint_experiment_id,
387                                    is_preview);
388     if (data_reduction_proxy_was_used) {
389       PLT_HISTOGRAM("PLT.PT_BeginToFinish_DataReductionProxy",
390                     load_event_end - begin);
391       PLT_HISTOGRAM("PLT.PT_CommitToFinish_DataReductionProxy",
392                     load_event_end - response_start);
393       PLT_HISTOGRAM("PLT.PT_RequestToFinish_DataReductionProxy",
394                     load_event_end - navigation_start);
395       PLT_HISTOGRAM("PLT.PT_StartToFinish_DataReductionProxy",
396                     load_event_end - request_start);
397     }
398   }
399   if (!load_event_start.is_null() && !load_event_end.is_null()) {
400     PLT_HISTOGRAM("PLT.PT_FinishDocToFinish",
401                   load_event_end - load_event_start);
402     PLT_HISTOGRAM_DRP("PLT.NT_LoadEvent",
403                       load_event_end - load_event_start,
404                       data_reduction_proxy_was_used);
405
406     if (data_reduction_proxy_was_used)
407       PLT_HISTOGRAM("PLT.PT_FinishDocToFinish_DataReductionProxy",
408                     load_event_end - load_event_start);
409   }
410   if (!dom_content_loaded_start.is_null()) {
411     PLT_HISTOGRAM_WITH_GWS_VARIANT("PLT.PT_RequestToDomContentLoaded",
412                                    dom_content_loaded_start - navigation_start,
413                                    came_from_websearch,
414                                    websearch_chrome_joint_experiment_id,
415                                    is_preview);
416     if (data_reduction_proxy_was_used)
417       PLT_HISTOGRAM("PLT.PT_RequestToDomContentLoaded_DataReductionProxy",
418                     dom_content_loaded_start - navigation_start);
419   }
420   PLT_HISTOGRAM_WITH_GWS_VARIANT("PLT.PT_BeginToCommit",
421                                  response_start - begin,
422                                  came_from_websearch,
423                                  websearch_chrome_joint_experiment_id,
424                                  is_preview);
425   PLT_HISTOGRAM_WITH_GWS_VARIANT("PLT.PT_RequestToStart",
426                                  request_start - navigation_start,
427                                  came_from_websearch,
428                                  websearch_chrome_joint_experiment_id,
429                                  is_preview);
430   PLT_HISTOGRAM_WITH_GWS_VARIANT("PLT.PT_StartToCommit",
431                                  response_start - request_start,
432                                  came_from_websearch,
433                                  websearch_chrome_joint_experiment_id,
434                                  is_preview);
435   PLT_HISTOGRAM_WITH_GWS_VARIANT("PLT.PT_RequestToCommit",
436                                  response_start - navigation_start,
437                                  came_from_websearch,
438                                  websearch_chrome_joint_experiment_id,
439                                  is_preview);
440   if (data_reduction_proxy_was_used) {
441     PLT_HISTOGRAM("PLT.PT_BeginToCommit_DataReductionProxy",
442                   response_start - begin);
443     PLT_HISTOGRAM("PLT.PT_RequestToStart_DataReductionProxy",
444                   request_start - navigation_start);
445     PLT_HISTOGRAM("PLT.PT_StartToCommit_DataReductionProxy",
446                   response_start - request_start);
447     PLT_HISTOGRAM("PLT.PT_RequestToCommit_DataReductionProxy",
448                   response_start - navigation_start);
449   }
450 }
451
452 // These histograms are based on the timing information collected in
453 // DocumentState. They should be transitioned to equivalents based on the
454 // Navigation Timing records (see DumpPerformanceTiming()) or dropped if not
455 // needed. Please do not add new metrics based on DocumentState.
456 void DumpDeprecatedHistograms(const WebPerformance& performance,
457                               DocumentState* document_state,
458                               bool data_reduction_proxy_was_used,
459                               bool came_from_websearch,
460                               int websearch_chrome_joint_experiment_id,
461                               bool is_preview,
462                               URLPattern::SchemeMasks scheme_type) {
463   // If we've already dumped, do nothing.
464   // This simple bool works because we only dump for the main frame.
465   if (document_state->load_histograms_recorded())
466     return;
467
468   // Abort if any of these is missing.
469   Time start = document_state->start_load_time();
470   Time commit = document_state->commit_load_time();
471   Time navigation_start =
472       Time::FromDoubleT(performance.navigationStart());
473   if (start.is_null() || commit.is_null() || navigation_start.is_null())
474     return;
475
476   // We properly handle null values for the next 3 variables.
477   Time request = document_state->request_time();
478   Time first_paint = document_state->first_paint_time();
479   Time first_paint_after_load = document_state->first_paint_after_load_time();
480   Time finish_doc = document_state->finish_document_load_time();
481   Time finish_all_loads = document_state->finish_load_time();
482
483   // Handle case where user hits "stop" or "back" before loading completely.
484   // Note that this makes abandoned page loads be recorded as if they were
485   // completed, polluting the metrics with artifically short completion times.
486   // We are not fixing this as these metrics are being dropped as deprecated.
487   if (finish_doc.is_null()) {
488     finish_doc = Time::Now();
489     document_state->set_finish_document_load_time(finish_doc);
490   }
491   if (finish_all_loads.is_null()) {
492     finish_all_loads = Time::Now();
493     document_state->set_finish_load_time(finish_all_loads);
494   }
495
496   document_state->set_load_histograms_recorded(true);
497
498   // Note: Client side redirects will have no request time.
499   Time begin = request.is_null() ? start : request;
500   TimeDelta begin_to_finish_doc = finish_doc - begin;
501   TimeDelta begin_to_finish_all_loads = finish_all_loads - begin;
502   TimeDelta start_to_finish_all_loads = finish_all_loads - start;
503   TimeDelta start_to_commit = commit - start;
504
505   DocumentState::LoadType load_type = document_state->load_type();
506
507   // The above code sanitized all values of times, in preparation for creating
508   // actual histograms.  The remainder of this code could be run at destructor
509   // time for the document_state, since all data is intact.
510
511   // Aggregate PLT data across all link types.
512   UMA_HISTOGRAM_ENUMERATION("PLT.LoadType", load_type,
513       DocumentState::kLoadTypeMax);
514   PLT_HISTOGRAM("PLT.StartToCommit", start_to_commit);
515   PLT_HISTOGRAM("PLT.CommitToFinishDoc", finish_doc - commit);
516   PLT_HISTOGRAM("PLT.FinishDocToFinish", finish_all_loads - finish_doc);
517   PLT_HISTOGRAM("PLT.BeginToCommit", commit - begin);
518   PLT_HISTOGRAM("PLT.StartToFinish", start_to_finish_all_loads);
519   if (!request.is_null()) {
520     PLT_HISTOGRAM("PLT.RequestToStart", start - request);
521     PLT_HISTOGRAM("PLT.RequestToFinish", finish_all_loads - request);
522   }
523   PLT_HISTOGRAM("PLT.CommitToFinish", finish_all_loads - commit);
524
525   scoped_ptr<TimeDelta> begin_to_first_paint;
526   scoped_ptr<TimeDelta> commit_to_first_paint;
527   if (!first_paint.is_null()) {
528     // 'first_paint' can be before 'begin' for an unknown reason.
529     // See bug http://crbug.com/125273 for details.
530     if (begin <= first_paint) {
531       begin_to_first_paint.reset(new TimeDelta(first_paint - begin));
532       PLT_HISTOGRAM_WITH_GWS_VARIANT("PLT.BeginToFirstPaint",
533                                      *begin_to_first_paint,
534                                      came_from_websearch,
535                                      websearch_chrome_joint_experiment_id,
536                                      is_preview);
537     }
538
539     // Conditional was previously a DCHECK. Changed due to multiple bot
540     // failures, listed in crbug.com/383963
541     if (commit <= first_paint) {
542       commit_to_first_paint.reset(new TimeDelta(first_paint - commit));
543       PLT_HISTOGRAM_WITH_GWS_VARIANT("PLT.CommitToFirstPaint",
544                                      *commit_to_first_paint,
545                                      came_from_websearch,
546                                      websearch_chrome_joint_experiment_id,
547                                      is_preview);
548     }
549   }
550   if (!first_paint_after_load.is_null()) {
551     // 'first_paint_after_load' can be before 'begin' for an unknown reason.
552     // See bug http://crbug.com/125273 for details.
553     if (begin <= first_paint_after_load) {
554       PLT_HISTOGRAM("PLT.BeginToFirstPaintAfterLoad",
555           first_paint_after_load - begin);
556     }
557     // Both following conditionals were previously DCHECKs. Changed due to
558     // multiple bot failures, listed in crbug.com/383963
559     if (commit <= first_paint_after_load) {
560       PLT_HISTOGRAM("PLT.CommitToFirstPaintAfterLoad",
561           first_paint_after_load - commit);
562     }
563     if (finish_all_loads <= first_paint_after_load) {
564       PLT_HISTOGRAM("PLT.FinishToFirstPaintAfterLoad",
565           first_paint_after_load - finish_all_loads);
566     }
567   }
568   PLT_HISTOGRAM_WITH_GWS_VARIANT("PLT.BeginToFinishDoc", begin_to_finish_doc,
569                                  came_from_websearch,
570                                  websearch_chrome_joint_experiment_id,
571                                  is_preview);
572   PLT_HISTOGRAM_WITH_GWS_VARIANT("PLT.BeginToFinish", begin_to_finish_all_loads,
573                                  came_from_websearch,
574                                  websearch_chrome_joint_experiment_id,
575                                  is_preview);
576
577   // Load type related histograms.
578   switch (load_type) {
579     case DocumentState::UNDEFINED_LOAD:
580       PLT_HISTOGRAM("PLT.BeginToFinishDoc_UndefLoad", begin_to_finish_doc);
581       PLT_HISTOGRAM("PLT.BeginToFinish_UndefLoad", begin_to_finish_all_loads);
582       break;
583     case DocumentState::RELOAD:
584       PLT_HISTOGRAM("PLT.BeginToFinishDoc_Reload", begin_to_finish_doc);
585       PLT_HISTOGRAM("PLT.BeginToFinish_Reload", begin_to_finish_all_loads);
586       break;
587     case DocumentState::HISTORY_LOAD:
588       PLT_HISTOGRAM("PLT.BeginToFinishDoc_HistoryLoad", begin_to_finish_doc);
589       PLT_HISTOGRAM("PLT.BeginToFinish_HistoryLoad", begin_to_finish_all_loads);
590       break;
591     case DocumentState::NORMAL_LOAD:
592       PLT_HISTOGRAM("PLT.BeginToFinishDoc_NormalLoad", begin_to_finish_doc);
593       PLT_HISTOGRAM("PLT.BeginToFinish_NormalLoad", begin_to_finish_all_loads);
594       break;
595     case DocumentState::LINK_LOAD_NORMAL:
596       PLT_HISTOGRAM("PLT.BeginToFinishDoc_LinkLoadNormal",
597           begin_to_finish_doc);
598       PLT_HISTOGRAM("PLT.BeginToFinish_LinkLoadNormal",
599                     begin_to_finish_all_loads);
600       break;
601     case DocumentState::LINK_LOAD_RELOAD:
602       PLT_HISTOGRAM("PLT.BeginToFinishDoc_LinkLoadReload",
603            begin_to_finish_doc);
604       PLT_HISTOGRAM("PLT.BeginToFinish_LinkLoadReload",
605                     begin_to_finish_all_loads);
606       break;
607     case DocumentState::LINK_LOAD_CACHE_STALE_OK:
608       PLT_HISTOGRAM("PLT.BeginToFinishDoc_LinkLoadStaleOk",
609            begin_to_finish_doc);
610       PLT_HISTOGRAM("PLT.BeginToFinish_LinkLoadStaleOk",
611                     begin_to_finish_all_loads);
612       break;
613     case DocumentState::LINK_LOAD_CACHE_ONLY:
614       PLT_HISTOGRAM("PLT.BeginToFinishDoc_LinkLoadCacheOnly",
615            begin_to_finish_doc);
616       PLT_HISTOGRAM("PLT.BeginToFinish_LinkLoadCacheOnly",
617                     begin_to_finish_all_loads);
618       break;
619     default:
620       break;
621   }
622
623   if (data_reduction_proxy_was_used) {
624     PLT_HISTOGRAM("PLT.BeginToFinishDoc_SpdyProxy", begin_to_finish_doc);
625     PLT_HISTOGRAM("PLT.BeginToFinish_SpdyProxy", begin_to_finish_all_loads);
626   }
627
628   if (document_state->was_prefetcher()) {
629     PLT_HISTOGRAM("PLT.BeginToFinishDoc_ContentPrefetcher",
630                   begin_to_finish_doc);
631     PLT_HISTOGRAM("PLT.BeginToFinish_ContentPrefetcher",
632                   begin_to_finish_all_loads);
633   }
634   if (document_state->was_referred_by_prefetcher()) {
635     PLT_HISTOGRAM("PLT.BeginToFinishDoc_ContentPrefetcherReferrer",
636                   begin_to_finish_doc);
637     PLT_HISTOGRAM("PLT.BeginToFinish_ContentPrefetcherReferrer",
638                   begin_to_finish_all_loads);
639   }
640
641   const bool use_webrequest_histogram =
642       ChromeContentRendererClient::WasWebRequestUsedBySomeExtensions();
643   if (use_webrequest_histogram) {
644     switch (load_type) {
645       case DocumentState::NORMAL_LOAD:
646         PLT_HISTOGRAM(
647             "PLT.BeginToFinish_NormalLoad_ExtensionWebRequest",
648             begin_to_finish_all_loads);
649         break;
650       case DocumentState::LINK_LOAD_NORMAL:
651         PLT_HISTOGRAM(
652             "PLT.BeginToFinish_LinkLoadNormal_ExtensionWebRequest",
653             begin_to_finish_all_loads);
654         break;
655       case DocumentState::LINK_LOAD_RELOAD:
656         PLT_HISTOGRAM(
657             "PLT.BeginToFinish_LinkLoadReload_ExtensionWebRequest",
658             begin_to_finish_all_loads);
659         break;
660       case DocumentState::LINK_LOAD_CACHE_STALE_OK:
661         PLT_HISTOGRAM(
662             "PLT.BeginToFinish_LinkLoadStaleOk_ExtensionWebRequest",
663             begin_to_finish_all_loads);
664         break;
665       default:
666         break;
667     }
668   }
669 }
670
671 }  // namespace
672
673 PageLoadHistograms::PageLoadHistograms(content::RenderView* render_view)
674     : content::RenderViewObserver(render_view) {
675 }
676
677 void PageLoadHistograms::Dump(WebFrame* frame) {
678   // We only dump histograms for main frames.
679   // In the future, it may be interesting to tag subframes and dump them too.
680   if (!frame || frame->parent())
681     return;
682
683   // Only dump for supported schemes.
684   URLPattern::SchemeMasks scheme_type =
685       GetSupportedSchemeType(frame->document().url());
686   if (scheme_type == 0)
687     return;
688
689   // Ignore multipart requests.
690   if (frame->dataSource()->response().isMultipartPayload())
691     return;
692
693   DocumentState* document_state =
694       DocumentState::FromDataSource(frame->dataSource());
695
696   bool data_reduction_proxy_was_used = DataReductionProxyWasUsed(frame);
697   bool came_from_websearch =
698       IsFromGoogleSearchResult(frame->document().url(),
699                                GURL(frame->document().referrer()));
700   int websearch_chrome_joint_experiment_id = kNoExperiment;
701   bool is_preview = false;
702   if (came_from_websearch) {
703     websearch_chrome_joint_experiment_id =
704         GetQueryStringBasedExperiment(GURL(frame->document().referrer()));
705     is_preview = ViaHeaderContains(frame, "1.1 Google Instant Proxy Preview");
706   }
707
708   // Metrics based on the timing information recorded for the Navigation Timing
709   // API - http://www.w3.org/TR/navigation-timing/.
710   DumpHistograms(frame->performance(), document_state,
711                  data_reduction_proxy_was_used,
712                  came_from_websearch,
713                  websearch_chrome_joint_experiment_id,
714                  is_preview);
715
716   // Old metrics based on the timing information stored in DocumentState. These
717   // are deprecated and should go away.
718   DumpDeprecatedHistograms(frame->performance(), document_state,
719                            data_reduction_proxy_was_used,
720                            came_from_websearch,
721                            websearch_chrome_joint_experiment_id,
722                            is_preview,
723                            scheme_type);
724
725   // Log the PLT to the info log.
726   LogPageLoadTime(document_state, frame->dataSource());
727
728   // Since there are currently no guarantees that renderer histograms will be
729   // sent to the browser, we initiate a PostTask here to be sure that we send
730   // the histograms we generated.  Without this call, pages that don't have an
731   // on-close-handler might generate data that is lost when the renderer is
732   // shutdown abruptly (perchance because the user closed the tab).
733   // TODO(jar) BUG=33233: This needs to be moved to a PostDelayedTask, and it
734   // should post when the onload is complete, so that it doesn't interfere with
735   // the next load.
736   content::RenderThread::Get()->UpdateHistograms(
737       content::kHistogramSynchronizerReservedSequenceNumber);
738 }
739
740 void PageLoadHistograms::FrameWillClose(WebFrame* frame) {
741   Dump(frame);
742 }
743
744 void PageLoadHistograms::ClosePage() {
745   // TODO(davemoore) This code should be removed once willClose() gets
746   // called when a page is destroyed. page_load_histograms_.Dump() is safe
747   // to call multiple times for the same frame, but it will simplify things.
748   Dump(render_view()->GetWebView()->mainFrame());
749 }
750
751 void PageLoadHistograms::LogPageLoadTime(const DocumentState* document_state,
752                                          const WebDataSource* ds) const {
753   // Because this function gets called on every page load,
754   // take extra care to optimize it away if logging is turned off.
755   if (logging::LOG_INFO < logging::GetMinLogLevel())
756     return;
757
758   DCHECK(document_state);
759   DCHECK(ds);
760   GURL url(ds->request().url());
761   Time start = document_state->start_load_time();
762   Time finish = document_state->finish_load_time();
763   // TODO(mbelshe): should we log more stats?
764   VLOG(1) << "PLT: " << (finish - start).InMilliseconds() << "ms "
765           << url.spec();
766 }