Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(14)

Side by Side Diff: components/page_load_metrics/browser/metrics_web_contents_observer.cc

Issue 1357403003: Separate page load metrics for backgrounded pages (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Added new histograms to replace the corrupted old ones Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2015 The Chromium Authors. All rights reserved. 1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "components/page_load_metrics/browser/metrics_web_contents_observer.h" 5 #include "components/page_load_metrics/browser/metrics_web_contents_observer.h"
6 6
7 #include "base/logging.h" 7 #include "base/logging.h"
8 #include "base/metrics/histogram.h" 8 #include "base/metrics/histogram.h"
9 #include "components/page_load_metrics/common/page_load_metrics_messages.h" 9 #include "components/page_load_metrics/common/page_load_metrics_messages.h"
10 #include "components/page_load_metrics/common/page_load_timing.h" 10 #include "components/page_load_metrics/common/page_load_timing.h"
(...skipping 15 matching lines...) Expand all
26 namespace { 26 namespace {
27 27
28 bool IsValidPageLoadTiming(const PageLoadTiming& timing) { 28 bool IsValidPageLoadTiming(const PageLoadTiming& timing) {
29 if (timing.IsEmpty()) 29 if (timing.IsEmpty())
30 return false; 30 return false;
31 31
32 // If we have a non-empty timing, it should always have a navigation start. 32 // If we have a non-empty timing, it should always have a navigation start.
33 DCHECK(!timing.navigation_start.is_null()); 33 DCHECK(!timing.navigation_start.is_null());
34 34
35 // If we have a DOM content loaded event, we should have a response start. 35 // If we have a DOM content loaded event, we should have a response start.
36 DCHECK_IMPLIES( 36 DCHECK_IMPLIES(
Deprecated (see juliatuttle) 2015/09/30 15:47:30 Whoa, I didn't know this was a thing. Cool.
37 !timing.dom_content_loaded_event_start.is_zero(), 37 !timing.dom_content_loaded_event_start.is_zero(),
38 timing.response_start <= timing.dom_content_loaded_event_start); 38 timing.response_start <= timing.dom_content_loaded_event_start);
39 39
40 // If we have a load event, we should have both a response start and a DCL. 40 // If we have a load event, we should have both a response start and a DCL.
41 DCHECK_IMPLIES( 41 DCHECK_IMPLIES(
42 !timing.load_event_start.is_zero(), 42 !timing.load_event_start.is_zero(),
43 !timing.dom_content_loaded_event_start.is_zero() && 43 !timing.dom_content_loaded_event_start.is_zero() &&
44 timing.response_start <= timing.load_event_start && 44 timing.response_start <= timing.load_event_start &&
45 timing.dom_content_loaded_event_start <= timing.load_event_start); 45 timing.dom_content_loaded_event_start <= timing.load_event_start);
46 46
47 return true; 47 return true;
48 } 48 }
49 49
50 base::Time WallTimeFromTimeTicks(base::TimeTicks time) {
51 return base::Time::FromDoubleT(
52 (time - base::TimeTicks::UnixEpoch()).InSecondsF());
53 }
54
50 } // namespace 55 } // namespace
51 56
57 #define PAGE_LOAD_HISTOGRAM(name, sample) \
58 UMA_HISTOGRAM_CUSTOM_TIMES(name, sample, \
59 base::TimeDelta::FromMilliseconds(10), \
60 base::TimeDelta::FromMinutes(10), 100)
61
62 PageLoadTracker::PageLoadTracker(bool in_foreground)
63 : has_commit_(false), started_in_foreground_(in_foreground) {}
64
65 PageLoadTracker::~PageLoadTracker() {
66 if (has_commit_)
67 RecordTimingHistograms();
68 }
69
70 void PageLoadTracker::WebContentsHidden() {
71 // Only log the first time we background in a given page load.
72 if (background_time_.is_null()) {
73 background_time_ = base::TimeTicks::Now();
74 }
75 }
76
77 void PageLoadTracker::Commit() {
78 has_commit_ = true;
79 }
80
81 bool PageLoadTracker::UpdateTiming(const PageLoadTiming& timing) {
82 // Throw away IPCs that are not relevant to the current navigation.
83 if (!timing_.navigation_start.is_null() &&
84 timing_.navigation_start != timing.navigation_start) {
85 // TODO(csharrison) uma log a counter here
86 return false;
87 }
88 if (IsValidPageLoadTiming(timing)) {
89 timing_ = timing;
90 return true;
91 }
92 return false;
93 }
94
95 void PageLoadTracker::RecordTimingHistograms() {
96 DCHECK(has_commit_);
97 // This method is similar to how blink converts TimeTicks to epoch time.
98 // There may be slight inaccuracies due to inter-process timestamps, but
99 // this solution is the best we have right now.
100 base::TimeDelta background_delta;
101 if (started_in_foreground_) {
102 background_delta = background_time_.is_null()
103 ? base::TimeDelta::Max()
104 : WallTimeFromTimeTicks(background_time_) - timing_.navigation_start;
105 }
106
107 if (!timing_.dom_content_loaded_event_start.is_zero()) {
108 if (timing_.dom_content_loaded_event_start < background_delta) {
109 PAGE_LOAD_HISTOGRAM(
110 "PageLoad.Timing2.NavigationToDOMContentLoadedEventFired",
111 timing_.dom_content_loaded_event_start);
112 } else {
113 PAGE_LOAD_HISTOGRAM(
114 "PageLoad.Timing2.NavigationToDOMContentLoadedEventFired.BG",
115 timing_.dom_content_loaded_event_start);
116 }
117 }
118 if (!timing_.load_event_start.is_zero()) {
119 if (timing_.load_event_start < background_delta) {
120 PAGE_LOAD_HISTOGRAM("PageLoad.Timing2.NavigationToLoadEventFired",
121 timing_.load_event_start);
122 } else {
123 PAGE_LOAD_HISTOGRAM("PageLoad.Timing2.NavigationToLoadEventFired.BG",
124 timing_.load_event_start);
125 }
126 }
127 if (!timing_.first_layout.is_zero()) {
128 if (timing_.first_layout < background_delta) {
129 PAGE_LOAD_HISTOGRAM("PageLoad.Timing2.NavigationToFirstLayout",
130 timing_.first_layout);
131 } else {
132 PAGE_LOAD_HISTOGRAM("PageLoad.Timing2.NavigationToFirstLayout.BG",
133 timing_.first_layout);
134 }
135 }
136 }
137
52 MetricsWebContentsObserver::MetricsWebContentsObserver( 138 MetricsWebContentsObserver::MetricsWebContentsObserver(
53 content::WebContents* web_contents) 139 content::WebContents* web_contents)
54 : content::WebContentsObserver(web_contents) {} 140 : content::WebContentsObserver(web_contents), in_foreground_(false) {}
55 141
56 // This object is tied to a single WebContents for its entire lifetime. 142 MetricsWebContentsObserver::~MetricsWebContentsObserver() {}
57 MetricsWebContentsObserver::~MetricsWebContentsObserver() {
58 RecordTimingHistograms();
59 }
60 143
61 bool MetricsWebContentsObserver::OnMessageReceived( 144 bool MetricsWebContentsObserver::OnMessageReceived(
62 const IPC::Message& message, 145 const IPC::Message& message,
63 content::RenderFrameHost* render_frame_host) { 146 content::RenderFrameHost* render_frame_host) {
64 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); 147 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
65 bool handled = true; 148 bool handled = true;
66 IPC_BEGIN_MESSAGE_MAP_WITH_PARAM(MetricsWebContentsObserver, message, 149 IPC_BEGIN_MESSAGE_MAP_WITH_PARAM(MetricsWebContentsObserver, message,
67 render_frame_host) 150 render_frame_host)
68 IPC_MESSAGE_HANDLER(PageLoadMetricsMsg_TimingUpdated, OnTimingUpdated) 151 IPC_MESSAGE_HANDLER(PageLoadMetricsMsg_TimingUpdated, OnTimingUpdated)
69 IPC_MESSAGE_UNHANDLED(handled = false) 152 IPC_MESSAGE_UNHANDLED(handled = false)
70 IPC_END_MESSAGE_MAP() 153 IPC_END_MESSAGE_MAP()
71 return handled; 154 return handled;
72 } 155 }
73 156
157 void MetricsWebContentsObserver::DidStartNavigation(
158 content::NavigationHandle* navigation_handle) {
159 if (!navigation_handle->IsInMainFrame())
160 return;
161 // We can have two provisional loads in some cases. E.g. a same-site
162 // navigation can have a concurrent cross-process navigation started
163 // from the omnibox.
164 DCHECK(provisional_loads_.size() < 2);
Deprecated (see juliatuttle) 2015/09/30 15:47:30 DCHECK_GT(2, provisional_loads_.size()); (It'll g
165 provisional_loads_.insert(
166 navigation_handle, make_scoped_ptr(new PageLoadTracker(in_foreground_)));
167 }
168
74 void MetricsWebContentsObserver::DidFinishNavigation( 169 void MetricsWebContentsObserver::DidFinishNavigation(
75 content::NavigationHandle* navigation_handle) { 170 content::NavigationHandle* navigation_handle) {
171 if (!navigation_handle->IsInMainFrame())
172 return;
173
174 scoped_ptr<PageLoadTracker> finished_nav(
175 provisional_loads_.take_and_erase(navigation_handle));
176 DCHECK(finished_nav);
177
178 // TODO(csharrison) handle the two error cases:
179 // 1. Error pages that replace the previous page.
180 // 2. Error pages that leave the user on the previous page.
181 // For now these cases will be ignored.
76 if (!navigation_handle->HasCommitted()) 182 if (!navigation_handle->HasCommitted())
77 return; 183 return;
78 if (navigation_handle->IsInMainFrame() && !navigation_handle->IsSamePage()) 184
79 RecordTimingHistograms(); 185 // Don't treat a same-page nav as a new page load.
80 if (IsRelevantNavigation(navigation_handle)) 186 if (navigation_handle->IsSamePage())
81 current_timing_.reset(new PageLoadTiming()); 187 return;
188
189 // Eagerly log the previous UMA even if we don't care about the current
190 // navigation.
191 committed_load_.reset();
192
193 if (!IsRelevantNavigation(navigation_handle))
194 return;
195
196 committed_load_ = finished_nav.Pass();
197 committed_load_->Commit();
82 } 198 }
83 199
84 // This will occur when the process for the main RenderFrameHost exits. 200 void MetricsWebContentsObserver::WasShown() {
85 // This will happen with a normal exit or a crash. 201 in_foreground_ = true;
202 }
203
204 void MetricsWebContentsObserver::WasHidden() {
205 in_foreground_ = false;
206 if (committed_load_)
207 committed_load_->WebContentsHidden();
208 for (const auto& kv : provisional_loads_) {
209 kv.second->WebContentsHidden();
210 }
211 }
212
213 // This will occur when the process for the main RenderFrameHost exits, either
214 // normally or from a crash. We eagerly log data from the last committed load if
215 // we have one.
86 void MetricsWebContentsObserver::RenderProcessGone( 216 void MetricsWebContentsObserver::RenderProcessGone(
87 base::TerminationStatus status) { 217 base::TerminationStatus status) {
88 RecordTimingHistograms(); 218 committed_load_.reset();
89 } 219 }
90 220
91 #define PAGE_LOAD_HISTOGRAM(name, sample) \
92 UMA_HISTOGRAM_CUSTOM_TIMES(name, sample, \
93 base::TimeDelta::FromMilliseconds(10), \
94 base::TimeDelta::FromMinutes(10), 100);
95
96 void MetricsWebContentsObserver::OnTimingUpdated( 221 void MetricsWebContentsObserver::OnTimingUpdated(
97 content::RenderFrameHost* render_frame_host, 222 content::RenderFrameHost* render_frame_host,
98 const PageLoadTiming& timing) { 223 const PageLoadTiming& timing) {
99 if (!current_timing_) 224 if (!committed_load_)
100 return; 225 return;
101 226
102 // We may receive notifications from frames that have been navigated away 227 // We may receive notifications from frames that have been navigated away
103 // from. We simply ignore them. 228 // from. We simply ignore them.
104 if (render_frame_host != web_contents()->GetMainFrame()) 229 if (render_frame_host != web_contents()->GetMainFrame())
105 return; 230 return;
106 231
107 // For urls like chrome://newtab, the renderer and browser disagree, 232 // For urls like chrome://newtab, the renderer and browser disagree,
108 // so we have to double check that the renderer isn't sending data from a 233 // so we have to double check that the renderer isn't sending data from a
109 // bad url like https://www.google.com/_/chrome/newtab. 234 // bad url like https://www.google.com/_/chrome/newtab.
110 if (!web_contents()->GetLastCommittedURL().SchemeIsHTTPOrHTTPS()) 235 if (!web_contents()->GetLastCommittedURL().SchemeIsHTTPOrHTTPS())
111 return; 236 return;
112 237
113 // Throw away IPCs that are not relevant to the current navigation. 238 committed_load_->UpdateTiming(timing);
114 if (!current_timing_->navigation_start.is_null() &&
115 timing.navigation_start != current_timing_->navigation_start) {
116 // TODO(csharrison) uma log a counter here
117 return;
118 }
119
120 *current_timing_ = timing;
121 }
122
123 void MetricsWebContentsObserver::RecordTimingHistograms() {
124 if (!current_timing_ || !IsValidPageLoadTiming(*current_timing_))
125 return;
126
127 if (!current_timing_->dom_content_loaded_event_start.is_zero()) {
128 PAGE_LOAD_HISTOGRAM(
129 "PageLoad.Timing.NavigationToDOMContentLoadedEventFired",
130 current_timing_->dom_content_loaded_event_start);
131 }
132
133 if (!current_timing_->load_event_start.is_zero()) {
134 PAGE_LOAD_HISTOGRAM("PageLoad.Timing.NavigationToLoadEventFired",
135 current_timing_->load_event_start);
136 }
137
138 if (!current_timing_->first_layout.is_zero()) {
139 PAGE_LOAD_HISTOGRAM("PageLoad.Timing.NavigationToFirstLayout",
140 current_timing_->first_layout);
141 }
142 current_timing_.reset();
143 } 239 }
144 240
145 bool MetricsWebContentsObserver::IsRelevantNavigation( 241 bool MetricsWebContentsObserver::IsRelevantNavigation(
146 content::NavigationHandle* navigation_handle) { 242 content::NavigationHandle* navigation_handle) {
147 // The url we see from the renderer side is not always the same as what 243 // The url we see from the renderer side is not always the same as what
148 // we see from the browser side (e.g. chrome://newtab). We want to be 244 // we see from the browser side (e.g. chrome://newtab). We want to be
149 // sure here that we aren't logging UMA for internal pages. 245 // sure here that we aren't logging UMA for internal pages.
150 const GURL& browser_url = web_contents()->GetLastCommittedURL(); 246 const GURL& browser_url = web_contents()->GetLastCommittedURL();
151 return navigation_handle->IsInMainFrame() && 247 return navigation_handle->IsInMainFrame() &&
152 !navigation_handle->IsSamePage() && 248 !navigation_handle->IsSamePage() &&
153 !navigation_handle->IsErrorPage() && 249 !navigation_handle->IsErrorPage() &&
154 navigation_handle->GetURL().SchemeIsHTTPOrHTTPS() && 250 navigation_handle->GetURL().SchemeIsHTTPOrHTTPS() &&
155 browser_url.SchemeIsHTTPOrHTTPS(); 251 browser_url.SchemeIsHTTPOrHTTPS();
156 } 252 }
157 253
158 } // namespace page_load_metrics 254 } // namespace page_load_metrics
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698