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
3 // found in the LICENSE file.
5 #include "chrome/browser/sessions/session_restore_stats_collector.h"
9 #include "base/metrics/histogram.h"
10 #include "base/strings/stringprintf.h"
11 #include "base/time/default_tick_clock.h"
12 #include "content/public/browser/notification_service.h"
13 #include "content/public/browser/notification_types.h"
14 #include "content/public/browser/render_widget_host.h"
15 #include "content/public/browser/render_widget_host_view.h"
16 #include "content/public/browser/web_contents.h"
20 using content::NavigationController
;
21 using content::RenderWidgetHost
;
22 using content::RenderWidgetHostView
;
23 using content::Source
;
24 using content::WebContents
;
26 // The enumeration values stored in the "SessionRestore.Actions" histogram.
27 enum SessionRestoreActionsUma
{
28 // Counts the total number of session restores that have occurred.
29 SESSION_RESTORE_ACTIONS_UMA_INITIATED
= 0,
30 // Counts the number of session restores that have seen deferred tab loadings
31 // for whatever reason (almost certainly due to memory pressure).
32 SESSION_RESTORE_ACTIONS_UMA_DEFERRED_TABS
= 1,
33 // The size of this enum. Must be the last entry.
34 SESSION_RESTORE_ACTIONS_UMA_MAX
,
37 // Emits a SessionRestore.Actions UMA event.
38 void EmitUmaSessionRestoreActionEvent(SessionRestoreActionsUma action
) {
39 UMA_HISTOGRAM_ENUMERATION("SessionRestore.Actions", action
,
40 SESSION_RESTORE_ACTIONS_UMA_MAX
);
43 // The enumeration of values stored in the "SessionRestore.TabActions"
45 enum SessionRestoreTabActionsUma
{
46 // Incremented for each tab created in a session restore.
47 SESSION_RESTORE_TAB_ACTIONS_UMA_TAB_CREATED
= 0,
48 // Incremented for each tab that session restore decides not to load.
49 SESSION_RESTORE_TAB_ACTIONS_UMA_TAB_LOADING_DEFERRED
= 1,
50 // Incremented for each tab that is successfully loaded.
51 SESSION_RESTORE_TAB_ACTIONS_UMA_TAB_LOADED
= 2,
52 // Incremented for each session-restore-deferred tab that is subsequently
54 SESSION_RESTORE_TAB_ACTIONS_UMA_DEFERRED_TAB_LOADED
= 3,
55 // Incremented for each tab that starts loading due to the session restore.
56 SESSION_RESTORE_TAB_ACTIONS_UMA_TAB_LOAD_STARTED
= 4,
57 // The size of this enum. Must be the last entry.
58 SESSION_RESTORE_TAB_ACTIONS_UMA_MAX
,
61 // Emits a SessionRestore.TabActions UMA event.
62 void EmitUmaSessionRestoreTabActionEvent(SessionRestoreTabActionsUma action
) {
63 UMA_HISTOGRAM_ENUMERATION("SessionRestore.TabActions", action
,
64 SESSION_RESTORE_TAB_ACTIONS_UMA_MAX
);
67 // Returns the RenderWidgetHostView associated with a NavigationController.
68 RenderWidgetHostView
* GetRenderWidgetHostView(
69 NavigationController
* tab
) {
70 WebContents
* web_contents
= tab
->GetWebContents();
72 return web_contents
->GetRenderWidgetHostView();
76 // Returns the RenderWidgetHost associated with a NavigationController.
77 RenderWidgetHost
* GetRenderWidgetHost(
78 NavigationController
* tab
) {
79 content::RenderWidgetHostView
* render_widget_host_view
=
80 GetRenderWidgetHostView(tab
);
81 if (render_widget_host_view
)
82 return render_widget_host_view
->GetRenderWidgetHost();
86 // Determines if the RenderWidgetHostView associated with a given
87 // NavigationController is visible.
88 bool IsShowing(NavigationController
* tab
) {
89 content::RenderWidgetHostView
* render_widget_host_view
=
90 GetRenderWidgetHostView(tab
);
91 return render_widget_host_view
&& render_widget_host_view
->IsShowing();
96 SessionRestoreStatsCollector::TabLoaderStats::TabLoaderStats()
99 tabs_load_started(0u),
101 parallel_tab_loads(0u) {
104 SessionRestoreStatsCollector::TabState::TabState(
105 NavigationController
* controller
)
106 : controller(controller
),
108 loading_state(TAB_IS_NOT_LOADING
) {
111 SessionRestoreStatsCollector::SessionRestoreStatsCollector(
112 const base::TimeTicks
& restore_started
,
113 scoped_ptr
<StatsReportingDelegate
> reporting_delegate
)
114 : done_tracking_non_deferred_tabs_(false),
115 got_first_foreground_load_(false),
116 got_first_paint_(false),
117 restore_started_(restore_started
),
118 waiting_for_load_tab_count_(0u),
119 loading_tab_count_(0u),
120 deferred_tab_count_(0u),
121 tick_clock_(new base::DefaultTickClock()),
122 reporting_delegate_(reporting_delegate
.Pass()) {
123 this_retainer_
= this;
126 SessionRestoreStatsCollector::~SessionRestoreStatsCollector() {
129 void SessionRestoreStatsCollector::TrackTabs(
130 const std::vector
<SessionRestoreDelegate::RestoredTab
>& tabs
) {
131 DCHECK(!done_tracking_non_deferred_tabs_
);
133 // If this is the first call to TrackTabs then start observing events.
134 if (tab_loader_stats_
.tab_count
== 0) {
136 this, content::NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE
,
137 content::NotificationService::AllSources());
140 tab_loader_stats_
.tab_count
+= tabs
.size();
141 waiting_for_load_tab_count_
+= tabs
.size();
142 for (const auto& tab
: tabs
) {
143 TabState
* tab_state
=
144 RegisterForNotifications(&tab
.contents()->GetController());
145 // Active tabs have already started loading.
147 MarkTabAsLoading(tab_state
);
151 void SessionRestoreStatsCollector::DeferTab(NavigationController
* tab
) {
152 TabState
* tab_state
= GetTabState(tab
);
154 // If the tab is no longer being tracked it has already finished loading.
155 // This can occur if the user forces the tab to load before the entire session
156 // restore is over, and the TabLoader then decides it would defer loading of
161 // Mark this tab as deferred, if its still being tracked. A tab should not be
162 // marked as deferred twice.
163 DCHECK(!tab_state
->is_deferred
);
164 tab_state
->is_deferred
= true;
165 ++deferred_tab_count_
;
166 ++tab_loader_stats_
.tabs_deferred
;
168 // A tab that didn't start loading before it was deferred is not to be
169 // actively monitored for loading.
170 if (tab_state
->loading_state
== TAB_IS_NOT_LOADING
) {
171 DCHECK_LT(0u, waiting_for_load_tab_count_
);
172 if (--waiting_for_load_tab_count_
== 0)
173 ReleaseIfDoneTracking();
176 reporting_delegate_
->ReportTabDeferred();
179 void SessionRestoreStatsCollector::Observe(
181 const content::NotificationSource
& source
,
182 const content::NotificationDetails
& details
) {
184 case content::NOTIFICATION_LOAD_START
: {
185 // This occurs when a tab has started to load. This can be because of
186 // the tab loader (only for non-deferred tabs) or because the user clicked
188 NavigationController
* tab
= Source
<NavigationController
>(source
).ptr();
189 TabState
* tab_state
= GetTabState(tab
);
190 MarkTabAsLoading(tab_state
);
193 case content::NOTIFICATION_WEB_CONTENTS_DESTROYED
: {
194 // This happens when a tab has been closed. A tab can be in any state
195 // when this occurs. Simply stop tracking the tab.
196 WebContents
* web_contents
= Source
<WebContents
>(source
).ptr();
197 NavigationController
* tab
= &web_contents
->GetController();
201 case content::NOTIFICATION_LOAD_STOP
: {
202 // This occurs to loading tabs when they have finished loading. The tab
203 // may or may not already have painted at this point.
205 // Update the tab state and any global state as necessary.
206 NavigationController
* tab
= Source
<NavigationController
>(source
).ptr();
207 TabState
* tab_state
= GetTabState(tab
);
209 tab_state
->loading_state
= TAB_IS_LOADED
;
210 DCHECK_LT(0u, loading_tab_count_
);
211 --loading_tab_count_
;
212 if (!tab_state
->is_deferred
) {
213 DCHECK_LT(0u, waiting_for_load_tab_count_
);
214 --waiting_for_load_tab_count_
;
217 if (tab_state
->is_deferred
) {
218 reporting_delegate_
->ReportDeferredTabLoaded();
220 DCHECK(!done_tracking_non_deferred_tabs_
);
221 ++tab_loader_stats_
.tabs_loaded
;
224 // Update statistics for foreground tabs.
225 base::TimeDelta time_to_load
= tick_clock_
->NowTicks() - restore_started_
;
226 if (!got_first_foreground_load_
&& IsShowing(tab_state
->controller
)) {
227 got_first_foreground_load_
= true;
228 DCHECK(!done_tracking_non_deferred_tabs_
);
229 tab_loader_stats_
.foreground_tab_first_loaded
= time_to_load
;
232 // Update statistics for all tabs, if this wasn't a deferred tab. This is
233 // done here and not in ReleaseIfDoneTracking because it is possible to
234 // wait for a paint long after all loads have completed.
235 if (!done_tracking_non_deferred_tabs_
&& !tab_state
->is_deferred
)
236 tab_loader_stats_
.non_deferred_tabs_loaded
= time_to_load
;
238 // By default tabs transition to being tracked for paint events after the
239 // load event has been seen. However, if the first paint event has already
240 // been seen then this is not necessary and the tab can be removed.
241 if (got_first_paint_
)
246 case content::NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE
: {
247 // This notification is across all tabs in the browser so notifications
248 // will arrive for tabs that the collector is not explicitly tracking.
250 // Only process this event if first paint hasn't been seen and this is a
251 // paint of a visible tab.
252 RenderWidgetHost
* render_widget_host
=
253 Source
<RenderWidgetHost
>(source
).ptr();
254 if (!got_first_paint_
&& render_widget_host
->GetView() &&
255 render_widget_host
->GetView()->IsShowing()) {
256 got_first_paint_
= true;
257 TabState
* tab_state
= GetTabState(render_widget_host
);
259 // This is a paint for a tab that is explicitly being tracked so
260 // update the statistics. Otherwise the host is for a tab that's not
261 // being tracked thus some other tab has visibility and has rendered
262 // and there's no point in tracking the time to first paint. This can
263 // happen because the user opened a different tab or restored tabs
264 // to an already existing browser and an existing tab was in the
266 base::TimeDelta time_to_paint
=
267 tick_clock_
->NowTicks() - restore_started_
;
268 DCHECK(!done_tracking_non_deferred_tabs_
);
269 tab_loader_stats_
.foreground_tab_first_paint
= time_to_paint
;
272 // Once first paint has been observed the entire to-paint tracking
273 // mechanism is no longer needed.
276 content::NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE
,
277 content::NotificationService::AllSources());
279 // Remove any tabs that have loaded. These were only being kept around
280 // while waiting for a paint event.
281 std::vector
<NavigationController
*> loaded_tabs
;
282 for (auto& map_entry
: tabs_tracked_
) {
283 TabState
& tab_state
= map_entry
.second
;
284 if (tab_state
.loading_state
== TAB_IS_LOADED
)
285 loaded_tabs
.push_back(tab_state
.controller
);
287 for (auto& tab
: loaded_tabs
)
293 NOTREACHED() << "Unknown notification received:" << type
;
297 ReleaseIfDoneTracking();
300 void SessionRestoreStatsCollector::RemoveTab(NavigationController
* tab
) {
301 // Stop observing this tab.
302 registrar_
.Remove(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED
,
303 Source
<WebContents
>(tab
->GetWebContents()));
304 registrar_
.Remove(this, content::NOTIFICATION_LOAD_STOP
,
305 Source
<NavigationController
>(tab
));
306 registrar_
.Remove(this, content::NOTIFICATION_LOAD_START
,
307 Source
<NavigationController
>(tab
));
309 auto tab_it
= tabs_tracked_
.find(tab
);
310 DCHECK(tab_it
!= tabs_tracked_
.end());
311 TabState
& tab_state
= tab_it
->second
;
313 // If this tab was waiting for a NOTIFICATION_LOAD_STOP event then update
314 // the loading counts.
315 if (tab_state
.loading_state
== TAB_IS_LOADING
) {
316 DCHECK_LT(0u, loading_tab_count_
);
317 --loading_tab_count_
;
320 // Only non-deferred not-loading/not-loaded tabs are waiting to be loaded.
321 if (tab_state
.loading_state
!= TAB_IS_LOADED
&& !tab_state
.is_deferred
) {
322 DCHECK_LT(0u, waiting_for_load_tab_count_
);
323 // It's possible for waiting_for_load_tab_count_ to reach zero here. This
324 // function is only called from 'Observe', so the transition will be
326 --waiting_for_load_tab_count_
;
329 if (tab_state
.is_deferred
)
330 --deferred_tab_count_
;
332 // Remove the tab from the |tracked_tabs_| map.
333 tabs_tracked_
.erase(tab_it
);
335 // It is possible for all restored contents to be destroyed or forcibly
336 // renavigated before a first paint has arrived. This can be detected by
337 // tabs_tracked_ containing only deferred tabs. At this point the paint
338 // mechanism can be disabled and stats collection will stop.
339 if (tabs_tracked_
.size() == deferred_tab_count_
&& !got_first_paint_
) {
340 got_first_paint_
= true;
342 this, content::NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE
,
343 content::NotificationService::AllSources());
347 SessionRestoreStatsCollector::TabState
*
348 SessionRestoreStatsCollector::RegisterForNotifications(
349 NavigationController
* tab
) {
350 registrar_
.Add(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED
,
351 Source
<WebContents
>(tab
->GetWebContents()));
352 registrar_
.Add(this, content::NOTIFICATION_LOAD_STOP
,
353 Source
<NavigationController
>(tab
));
354 registrar_
.Add(this, content::NOTIFICATION_LOAD_START
,
355 Source
<NavigationController
>(tab
));
356 auto result
= tabs_tracked_
.insert(std::make_pair(tab
, TabState(tab
)));
357 DCHECK(result
.second
);
358 TabState
* tab_state
= &result
.first
->second
;
362 SessionRestoreStatsCollector::TabState
*
363 SessionRestoreStatsCollector::GetTabState(NavigationController
* tab
) {
364 // This lookup can fail because DeferTab calls can arrive for tabs that have
365 // already loaded (user forced) and are no longer tracked.
366 auto it
= tabs_tracked_
.find(tab
);
367 if (it
== tabs_tracked_
.end())
372 SessionRestoreStatsCollector::TabState
*
373 SessionRestoreStatsCollector::GetTabState(RenderWidgetHost
* tab
) {
374 for (auto& pair
: tabs_tracked_
) {
375 auto rwh
= GetRenderWidgetHost(pair
.first
);
379 // It's possible for this lookup to fail as paint events can be received for
380 // tabs that aren't being tracked.
384 void SessionRestoreStatsCollector::MarkTabAsLoading(TabState
* tab_state
) {
385 // If the tab has already started or finished loading then a user navigation
386 // has caused the tab to be forcibly reloaded. This tab can be removed from
388 if (tab_state
->loading_state
== TAB_IS_LOADED
) {
389 RemoveTab(tab_state
->controller
);
393 DCHECK_EQ(TAB_IS_NOT_LOADING
, tab_state
->loading_state
);
394 if (tab_state
->loading_state
!= TAB_IS_NOT_LOADING
)
396 tab_state
->loading_state
= TAB_IS_LOADING
;
397 ++loading_tab_count_
;
399 if (!done_tracking_non_deferred_tabs_
) {
400 ++tab_loader_stats_
.tabs_load_started
;
401 tab_loader_stats_
.parallel_tab_loads
=
402 std::max(tab_loader_stats_
.parallel_tab_loads
, loading_tab_count_
);
406 void SessionRestoreStatsCollector::ReleaseIfDoneTracking() {
407 // If non-deferred tabs are no longer being tracked then report tab loader
409 if (!done_tracking_non_deferred_tabs_
&& got_first_paint_
&&
410 waiting_for_load_tab_count_
== 0) {
411 done_tracking_non_deferred_tabs_
= true;
412 reporting_delegate_
->ReportTabLoaderStats(tab_loader_stats_
);
415 // If tracking is completely finished then emit collected metrics and destroy
416 // this stats collector.
417 if (done_tracking_non_deferred_tabs_
&& tabs_tracked_
.empty())
418 this_retainer_
= nullptr;
421 SessionRestoreStatsCollector::UmaStatsReportingDelegate::
422 UmaStatsReportingDelegate()
423 : got_report_tab_deferred_(false) {
426 void SessionRestoreStatsCollector::UmaStatsReportingDelegate::
427 ReportTabLoaderStats(const TabLoaderStats
& tab_loader_stats
) {
428 UMA_HISTOGRAM_COUNTS_100("SessionRestore.TabCount",
429 tab_loader_stats
.tab_count
);
431 // Emit suffix specializations of the SessionRestore.TabCount metric.
432 if (tab_loader_stats
.tabs_deferred
) {
433 UMA_HISTOGRAM_COUNTS_100("SessionRestore.TabCount_MemoryPressure",
434 tab_loader_stats
.tab_count
);
435 UMA_HISTOGRAM_COUNTS_100("SessionRestore.TabCount_MemoryPressure_Loaded",
436 tab_loader_stats
.tabs_loaded
);
437 UMA_HISTOGRAM_COUNTS_100(
438 "SessionRestore.TabCount_MemoryPressure_LoadStarted",
439 tab_loader_stats
.tabs_load_started
);
440 UMA_HISTOGRAM_COUNTS_100("SessionRestore.TabCount_MemoryPressure_Deferred",
441 tab_loader_stats
.tabs_deferred
);
443 UMA_HISTOGRAM_COUNTS_100("SessionRestore.TabCount_NoMemoryPressure",
444 tab_loader_stats
.tab_count
);
445 UMA_HISTOGRAM_COUNTS_100("SessionRestore.TabCount_NoMemoryPressure_Loaded",
446 tab_loader_stats
.tabs_loaded
);
447 UMA_HISTOGRAM_COUNTS_100(
448 "SessionRestore.TabCount_NoMemoryPressure_LoadStarted",
449 tab_loader_stats
.tabs_load_started
);
452 EmitUmaSessionRestoreActionEvent(SESSION_RESTORE_ACTIONS_UMA_INITIATED
);
454 for (size_t i
= 0; i
< tab_loader_stats
.tab_count
; ++i
) {
455 EmitUmaSessionRestoreTabActionEvent(
456 SESSION_RESTORE_TAB_ACTIONS_UMA_TAB_CREATED
);
459 for (size_t i
= 0; i
< tab_loader_stats
.tabs_loaded
; ++i
) {
460 EmitUmaSessionRestoreTabActionEvent(
461 SESSION_RESTORE_TAB_ACTIONS_UMA_TAB_LOADED
);
464 for (size_t i
= 0; i
< tab_loader_stats
.tabs_load_started
; ++i
) {
465 EmitUmaSessionRestoreTabActionEvent(
466 SESSION_RESTORE_TAB_ACTIONS_UMA_TAB_LOAD_STARTED
);
469 if (!tab_loader_stats
.foreground_tab_first_loaded
.is_zero()) {
470 UMA_HISTOGRAM_CUSTOM_TIMES("SessionRestore.ForegroundTabFirstLoaded",
471 tab_loader_stats
.foreground_tab_first_loaded
,
472 base::TimeDelta::FromMilliseconds(10),
473 base::TimeDelta::FromSeconds(100), 100);
475 // Record a time for the number of tabs, to help track down contention.
476 std::string time_for_count
= base::StringPrintf(
477 "SessionRestore.ForegroundTabFirstLoaded_%u",
478 static_cast<unsigned int>(tab_loader_stats
.tab_count
));
479 base::HistogramBase
* counter_for_count
= base::Histogram::FactoryTimeGet(
480 time_for_count
, base::TimeDelta::FromMilliseconds(10),
481 base::TimeDelta::FromSeconds(100), 100,
482 base::Histogram::kUmaTargetedHistogramFlag
);
483 counter_for_count
->AddTime(tab_loader_stats
.foreground_tab_first_loaded
);
486 if (!tab_loader_stats
.foreground_tab_first_paint
.is_zero()) {
487 UMA_HISTOGRAM_CUSTOM_TIMES("SessionRestore.ForegroundTabFirstPaint3",
488 tab_loader_stats
.foreground_tab_first_paint
,
489 base::TimeDelta::FromMilliseconds(100),
490 base::TimeDelta::FromMinutes(16), 50);
492 std::string time_for_count
= base::StringPrintf(
493 "SessionRestore.ForegroundTabFirstPaint3_%u",
494 static_cast<unsigned int>(tab_loader_stats
.tab_count
));
495 base::HistogramBase
* counter_for_count
= base::Histogram::FactoryTimeGet(
496 time_for_count
, base::TimeDelta::FromMilliseconds(100),
497 base::TimeDelta::FromMinutes(16), 50,
498 base::Histogram::kUmaTargetedHistogramFlag
);
499 counter_for_count
->AddTime(tab_loader_stats
.foreground_tab_first_paint
);
502 if (!tab_loader_stats
.non_deferred_tabs_loaded
.is_zero()) {
503 UMA_HISTOGRAM_CUSTOM_TIMES("SessionRestore.AllTabsLoaded",
504 tab_loader_stats
.non_deferred_tabs_loaded
,
505 base::TimeDelta::FromMilliseconds(10),
506 base::TimeDelta::FromSeconds(100), 100);
508 // Record a time for the number of tabs, to help track down contention.
509 std::string time_for_count
= base::StringPrintf(
510 "SessionRestore.AllTabsLoaded_%u",
511 static_cast<unsigned int>(tab_loader_stats
.tab_count
));
512 base::HistogramBase
* counter_for_count
= base::Histogram::FactoryTimeGet(
513 time_for_count
, base::TimeDelta::FromMilliseconds(10),
514 base::TimeDelta::FromSeconds(100), 100,
515 base::Histogram::kUmaTargetedHistogramFlag
);
516 counter_for_count
->AddTime(tab_loader_stats
.non_deferred_tabs_loaded
);
519 UMA_HISTOGRAM_COUNTS_100("SessionRestore.ParallelTabLoads",
520 tab_loader_stats
.parallel_tab_loads
);
523 void SessionRestoreStatsCollector::UmaStatsReportingDelegate::
524 ReportTabDeferred() {
525 if (!got_report_tab_deferred_
) {
526 got_report_tab_deferred_
= true;
527 EmitUmaSessionRestoreActionEvent(SESSION_RESTORE_ACTIONS_UMA_DEFERRED_TABS
);
530 EmitUmaSessionRestoreTabActionEvent(
531 SESSION_RESTORE_TAB_ACTIONS_UMA_TAB_LOADING_DEFERRED
);
534 void SessionRestoreStatsCollector::UmaStatsReportingDelegate::
535 ReportDeferredTabLoaded() {
536 EmitUmaSessionRestoreTabActionEvent(
537 SESSION_RESTORE_TAB_ACTIONS_UMA_DEFERRED_TAB_LOADED
);