// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/sessions/session_restore_stats_collector.h" #include #include "base/metrics/histogram.h" #include "base/strings/stringprintf.h" #include "base/time/default_tick_clock.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/notification_types.h" #include "content/public/browser/render_widget_host.h" #include "content/public/browser/render_widget_host_view.h" #include "content/public/browser/web_contents.h" namespace { using content::NavigationController; using content::RenderWidgetHost; using content::RenderWidgetHostView; using content::Source; using content::WebContents; // The enumeration values stored in the "SessionRestore.Actions" histogram. enum SessionRestoreActionsUma { // Counts the total number of session restores that have occurred. SESSION_RESTORE_ACTIONS_UMA_INITIATED = 0, // Counts the number of session restores that have seen deferred tab loadings // for whatever reason (almost certainly due to memory pressure). SESSION_RESTORE_ACTIONS_UMA_DEFERRED_TABS = 1, // The size of this enum. Must be the last entry. SESSION_RESTORE_ACTIONS_UMA_MAX, }; // Emits a SessionRestore.Actions UMA event. void EmitUmaSessionRestoreActionEvent(SessionRestoreActionsUma action) { UMA_HISTOGRAM_ENUMERATION("SessionRestore.Actions", action, SESSION_RESTORE_ACTIONS_UMA_MAX); } // The enumeration of values stored in the "SessionRestore.TabActions" // histogram. enum SessionRestoreTabActionsUma { // Incremented for each tab created in a session restore. SESSION_RESTORE_TAB_ACTIONS_UMA_TAB_CREATED = 0, // Incremented for each tab that session restore decides not to load. SESSION_RESTORE_TAB_ACTIONS_UMA_TAB_LOADING_DEFERRED = 1, // Incremented for each tab that is successfully loaded. SESSION_RESTORE_TAB_ACTIONS_UMA_TAB_LOADED = 2, // Incremented for each session-restore-deferred tab that is subsequently // loaded. SESSION_RESTORE_TAB_ACTIONS_UMA_DEFERRED_TAB_LOADED = 3, // Incremented for each tab that starts loading due to the session restore. SESSION_RESTORE_TAB_ACTIONS_UMA_TAB_LOAD_STARTED = 4, // The size of this enum. Must be the last entry. SESSION_RESTORE_TAB_ACTIONS_UMA_MAX, }; // Emits a SessionRestore.TabActions UMA event. void EmitUmaSessionRestoreTabActionEvent(SessionRestoreTabActionsUma action) { UMA_HISTOGRAM_ENUMERATION("SessionRestore.TabActions", action, SESSION_RESTORE_TAB_ACTIONS_UMA_MAX); } // Returns the RenderWidgetHostView associated with a NavigationController. RenderWidgetHostView* GetRenderWidgetHostView( NavigationController* tab) { WebContents* web_contents = tab->GetWebContents(); if (web_contents) return web_contents->GetRenderWidgetHostView(); return nullptr; } // Returns the RenderWidgetHost associated with a NavigationController. RenderWidgetHost* GetRenderWidgetHost( NavigationController* tab) { content::RenderWidgetHostView* render_widget_host_view = GetRenderWidgetHostView(tab); if (render_widget_host_view) return render_widget_host_view->GetRenderWidgetHost(); return nullptr; } // Determines if the RenderWidgetHostView associated with a given // NavigationController is visible. bool IsShowing(NavigationController* tab) { content::RenderWidgetHostView* render_widget_host_view = GetRenderWidgetHostView(tab); return render_widget_host_view && render_widget_host_view->IsShowing(); } } // namespace SessionRestoreStatsCollector::TabLoaderStats::TabLoaderStats() : tab_count(0u), tabs_deferred(0u), tabs_load_started(0u), tabs_loaded(0u), parallel_tab_loads(0u) { } SessionRestoreStatsCollector::TabState::TabState( NavigationController* controller) : controller(controller), is_deferred(false), loading_state(TAB_IS_NOT_LOADING) { } SessionRestoreStatsCollector::SessionRestoreStatsCollector( const base::TimeTicks& restore_started, scoped_ptr reporting_delegate) : done_tracking_non_deferred_tabs_(false), got_first_foreground_load_(false), got_first_paint_(false), restore_started_(restore_started), waiting_for_load_tab_count_(0u), loading_tab_count_(0u), deferred_tab_count_(0u), tick_clock_(new base::DefaultTickClock()), reporting_delegate_(reporting_delegate.Pass()) { this_retainer_ = this; } SessionRestoreStatsCollector::~SessionRestoreStatsCollector() { } void SessionRestoreStatsCollector::TrackTabs( const std::vector& tabs) { DCHECK(!done_tracking_non_deferred_tabs_); // If this is the first call to TrackTabs then start observing events. if (tab_loader_stats_.tab_count == 0) { registrar_.Add( this, content::NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE, content::NotificationService::AllSources()); } tab_loader_stats_.tab_count += tabs.size(); waiting_for_load_tab_count_ += tabs.size(); for (const auto& tab : tabs) { TabState* tab_state = RegisterForNotifications(&tab.contents()->GetController()); // Active tabs have already started loading. if (tab.is_active()) MarkTabAsLoading(tab_state); } } void SessionRestoreStatsCollector::DeferTab(NavigationController* tab) { TabState* tab_state = GetTabState(tab); // If the tab is no longer being tracked it has already finished loading. // This can occur if the user forces the tab to load before the entire session // restore is over, and the TabLoader then decides it would defer loading of // that tab. if (!tab_state) return; // Mark this tab as deferred, if its still being tracked. A tab should not be // marked as deferred twice. DCHECK(!tab_state->is_deferred); tab_state->is_deferred = true; ++deferred_tab_count_; ++tab_loader_stats_.tabs_deferred; // A tab that didn't start loading before it was deferred is not to be // actively monitored for loading. if (tab_state->loading_state == TAB_IS_NOT_LOADING) { DCHECK_LT(0u, waiting_for_load_tab_count_); if (--waiting_for_load_tab_count_ == 0) ReleaseIfDoneTracking(); } reporting_delegate_->ReportTabDeferred(); } void SessionRestoreStatsCollector::Observe( int type, const content::NotificationSource& source, const content::NotificationDetails& details) { switch (type) { case content::NOTIFICATION_LOAD_START: { // This occurs when a tab has started to load. This can be because of // the tab loader (only for non-deferred tabs) or because the user clicked // on the tab. NavigationController* tab = Source(source).ptr(); TabState* tab_state = GetTabState(tab); MarkTabAsLoading(tab_state); break; } case content::NOTIFICATION_WEB_CONTENTS_DESTROYED: { // This happens when a tab has been closed. A tab can be in any state // when this occurs. Simply stop tracking the tab. WebContents* web_contents = Source(source).ptr(); NavigationController* tab = &web_contents->GetController(); RemoveTab(tab); break; } case content::NOTIFICATION_LOAD_STOP: { // This occurs to loading tabs when they have finished loading. The tab // may or may not already have painted at this point. // Update the tab state and any global state as necessary. NavigationController* tab = Source(source).ptr(); TabState* tab_state = GetTabState(tab); DCHECK(tab_state); tab_state->loading_state = TAB_IS_LOADED; DCHECK_LT(0u, loading_tab_count_); --loading_tab_count_; if (!tab_state->is_deferred) { DCHECK_LT(0u, waiting_for_load_tab_count_); --waiting_for_load_tab_count_; } if (tab_state->is_deferred) { reporting_delegate_->ReportDeferredTabLoaded(); } else { DCHECK(!done_tracking_non_deferred_tabs_); ++tab_loader_stats_.tabs_loaded; } // Update statistics for foreground tabs. base::TimeDelta time_to_load = tick_clock_->NowTicks() - restore_started_; if (!got_first_foreground_load_ && IsShowing(tab_state->controller)) { got_first_foreground_load_ = true; DCHECK(!done_tracking_non_deferred_tabs_); tab_loader_stats_.foreground_tab_first_loaded = time_to_load; } // Update statistics for all tabs, if this wasn't a deferred tab. This is // done here and not in ReleaseIfDoneTracking because it is possible to // wait for a paint long after all loads have completed. if (!done_tracking_non_deferred_tabs_ && !tab_state->is_deferred) tab_loader_stats_.non_deferred_tabs_loaded = time_to_load; // By default tabs transition to being tracked for paint events after the // load event has been seen. However, if the first paint event has already // been seen then this is not necessary and the tab can be removed. if (got_first_paint_) RemoveTab(tab); break; } case content::NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE: { // This notification is across all tabs in the browser so notifications // will arrive for tabs that the collector is not explicitly tracking. // Only process this event if first paint hasn't been seen and this is a // paint of a visible tab. RenderWidgetHost* render_widget_host = Source(source).ptr(); if (!got_first_paint_ && render_widget_host->GetView() && render_widget_host->GetView()->IsShowing()) { got_first_paint_ = true; TabState* tab_state = GetTabState(render_widget_host); if (tab_state) { // This is a paint for a tab that is explicitly being tracked so // update the statistics. Otherwise the host is for a tab that's not // being tracked thus some other tab has visibility and has rendered // and there's no point in tracking the time to first paint. This can // happen because the user opened a different tab or restored tabs // to an already existing browser and an existing tab was in the // foreground. base::TimeDelta time_to_paint = tick_clock_->NowTicks() - restore_started_; DCHECK(!done_tracking_non_deferred_tabs_); tab_loader_stats_.foreground_tab_first_paint = time_to_paint; } // Once first paint has been observed the entire to-paint tracking // mechanism is no longer needed. registrar_.Remove( this, content::NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE, content::NotificationService::AllSources()); // Remove any tabs that have loaded. These were only being kept around // while waiting for a paint event. std::vector loaded_tabs; for (auto& map_entry : tabs_tracked_) { TabState& tab_state = map_entry.second; if (tab_state.loading_state == TAB_IS_LOADED) loaded_tabs.push_back(tab_state.controller); } for (auto& tab : loaded_tabs) RemoveTab(tab); } break; } default: NOTREACHED() << "Unknown notification received:" << type; break; } ReleaseIfDoneTracking(); } void SessionRestoreStatsCollector::RemoveTab(NavigationController* tab) { // Stop observing this tab. registrar_.Remove(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED, Source(tab->GetWebContents())); registrar_.Remove(this, content::NOTIFICATION_LOAD_STOP, Source(tab)); registrar_.Remove(this, content::NOTIFICATION_LOAD_START, Source(tab)); auto tab_it = tabs_tracked_.find(tab); DCHECK(tab_it != tabs_tracked_.end()); TabState& tab_state = tab_it->second; // If this tab was waiting for a NOTIFICATION_LOAD_STOP event then update // the loading counts. if (tab_state.loading_state == TAB_IS_LOADING) { DCHECK_LT(0u, loading_tab_count_); --loading_tab_count_; } // Only non-deferred not-loading/not-loaded tabs are waiting to be loaded. if (tab_state.loading_state != TAB_IS_LOADED && !tab_state.is_deferred) { DCHECK_LT(0u, waiting_for_load_tab_count_); // It's possible for waiting_for_load_tab_count_ to reach zero here. This // function is only called from 'Observe', so the transition will be // noticed there. --waiting_for_load_tab_count_; } if (tab_state.is_deferred) --deferred_tab_count_; // Remove the tab from the |tracked_tabs_| map. tabs_tracked_.erase(tab_it); // It is possible for all restored contents to be destroyed or forcibly // renavigated before a first paint has arrived. This can be detected by // tabs_tracked_ containing only deferred tabs. At this point the paint // mechanism can be disabled and stats collection will stop. if (tabs_tracked_.size() == deferred_tab_count_ && !got_first_paint_) { got_first_paint_ = true; registrar_.Remove( this, content::NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE, content::NotificationService::AllSources()); } } SessionRestoreStatsCollector::TabState* SessionRestoreStatsCollector::RegisterForNotifications( NavigationController* tab) { registrar_.Add(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED, Source(tab->GetWebContents())); registrar_.Add(this, content::NOTIFICATION_LOAD_STOP, Source(tab)); registrar_.Add(this, content::NOTIFICATION_LOAD_START, Source(tab)); auto result = tabs_tracked_.insert(std::make_pair(tab, TabState(tab))); DCHECK(result.second); TabState* tab_state = &result.first->second; return tab_state; } SessionRestoreStatsCollector::TabState* SessionRestoreStatsCollector::GetTabState(NavigationController* tab) { // This lookup can fail because DeferTab calls can arrive for tabs that have // already loaded (user forced) and are no longer tracked. auto it = tabs_tracked_.find(tab); if (it == tabs_tracked_.end()) return nullptr; return &it->second; } SessionRestoreStatsCollector::TabState* SessionRestoreStatsCollector::GetTabState(RenderWidgetHost* tab) { for (auto& pair : tabs_tracked_) { auto rwh = GetRenderWidgetHost(pair.first); if (rwh == tab) return &pair.second; } // It's possible for this lookup to fail as paint events can be received for // tabs that aren't being tracked. return nullptr; } void SessionRestoreStatsCollector::MarkTabAsLoading(TabState* tab_state) { // If the tab has already started or finished loading then a user navigation // has caused the tab to be forcibly reloaded. This tab can be removed from // observation. if (tab_state->loading_state == TAB_IS_LOADED) { RemoveTab(tab_state->controller); return; } DCHECK_EQ(TAB_IS_NOT_LOADING, tab_state->loading_state); if (tab_state->loading_state != TAB_IS_NOT_LOADING) return; tab_state->loading_state = TAB_IS_LOADING; ++loading_tab_count_; if (!done_tracking_non_deferred_tabs_) { ++tab_loader_stats_.tabs_load_started; tab_loader_stats_.parallel_tab_loads = std::max(tab_loader_stats_.parallel_tab_loads, loading_tab_count_); } } void SessionRestoreStatsCollector::ReleaseIfDoneTracking() { // If non-deferred tabs are no longer being tracked then report tab loader // statistics. if (!done_tracking_non_deferred_tabs_ && got_first_paint_ && waiting_for_load_tab_count_ == 0) { done_tracking_non_deferred_tabs_ = true; reporting_delegate_->ReportTabLoaderStats(tab_loader_stats_); } // If tracking is completely finished then emit collected metrics and destroy // this stats collector. if (done_tracking_non_deferred_tabs_ && tabs_tracked_.empty()) this_retainer_ = nullptr; } SessionRestoreStatsCollector::UmaStatsReportingDelegate:: UmaStatsReportingDelegate() : got_report_tab_deferred_(false) { } void SessionRestoreStatsCollector::UmaStatsReportingDelegate:: ReportTabLoaderStats(const TabLoaderStats& tab_loader_stats) { UMA_HISTOGRAM_COUNTS_100("SessionRestore.TabCount", tab_loader_stats.tab_count); // Emit suffix specializations of the SessionRestore.TabCount metric. if (tab_loader_stats.tabs_deferred) { UMA_HISTOGRAM_COUNTS_100("SessionRestore.TabCount_MemoryPressure", tab_loader_stats.tab_count); UMA_HISTOGRAM_COUNTS_100("SessionRestore.TabCount_MemoryPressure_Loaded", tab_loader_stats.tabs_loaded); UMA_HISTOGRAM_COUNTS_100( "SessionRestore.TabCount_MemoryPressure_LoadStarted", tab_loader_stats.tabs_load_started); UMA_HISTOGRAM_COUNTS_100("SessionRestore.TabCount_MemoryPressure_Deferred", tab_loader_stats.tabs_deferred); } else { UMA_HISTOGRAM_COUNTS_100("SessionRestore.TabCount_NoMemoryPressure", tab_loader_stats.tab_count); UMA_HISTOGRAM_COUNTS_100("SessionRestore.TabCount_NoMemoryPressure_Loaded", tab_loader_stats.tabs_loaded); UMA_HISTOGRAM_COUNTS_100( "SessionRestore.TabCount_NoMemoryPressure_LoadStarted", tab_loader_stats.tabs_load_started); } EmitUmaSessionRestoreActionEvent(SESSION_RESTORE_ACTIONS_UMA_INITIATED); for (size_t i = 0; i < tab_loader_stats.tab_count; ++i) { EmitUmaSessionRestoreTabActionEvent( SESSION_RESTORE_TAB_ACTIONS_UMA_TAB_CREATED); } for (size_t i = 0; i < tab_loader_stats.tabs_loaded; ++i) { EmitUmaSessionRestoreTabActionEvent( SESSION_RESTORE_TAB_ACTIONS_UMA_TAB_LOADED); } for (size_t i = 0; i < tab_loader_stats.tabs_load_started; ++i) { EmitUmaSessionRestoreTabActionEvent( SESSION_RESTORE_TAB_ACTIONS_UMA_TAB_LOAD_STARTED); } if (!tab_loader_stats.foreground_tab_first_loaded.is_zero()) { UMA_HISTOGRAM_CUSTOM_TIMES("SessionRestore.ForegroundTabFirstLoaded", tab_loader_stats.foreground_tab_first_loaded, base::TimeDelta::FromMilliseconds(10), base::TimeDelta::FromSeconds(100), 100); // Record a time for the number of tabs, to help track down contention. std::string time_for_count = base::StringPrintf( "SessionRestore.ForegroundTabFirstLoaded_%u", static_cast(tab_loader_stats.tab_count)); base::HistogramBase* counter_for_count = base::Histogram::FactoryTimeGet( time_for_count, base::TimeDelta::FromMilliseconds(10), base::TimeDelta::FromSeconds(100), 100, base::Histogram::kUmaTargetedHistogramFlag); counter_for_count->AddTime(tab_loader_stats.foreground_tab_first_loaded); } if (!tab_loader_stats.foreground_tab_first_paint.is_zero()) { UMA_HISTOGRAM_CUSTOM_TIMES("SessionRestore.ForegroundTabFirstPaint3", tab_loader_stats.foreground_tab_first_paint, base::TimeDelta::FromMilliseconds(100), base::TimeDelta::FromMinutes(16), 50); std::string time_for_count = base::StringPrintf( "SessionRestore.ForegroundTabFirstPaint3_%u", static_cast(tab_loader_stats.tab_count)); base::HistogramBase* counter_for_count = base::Histogram::FactoryTimeGet( time_for_count, base::TimeDelta::FromMilliseconds(100), base::TimeDelta::FromMinutes(16), 50, base::Histogram::kUmaTargetedHistogramFlag); counter_for_count->AddTime(tab_loader_stats.foreground_tab_first_paint); } if (!tab_loader_stats.non_deferred_tabs_loaded.is_zero()) { UMA_HISTOGRAM_CUSTOM_TIMES("SessionRestore.AllTabsLoaded", tab_loader_stats.non_deferred_tabs_loaded, base::TimeDelta::FromMilliseconds(10), base::TimeDelta::FromSeconds(100), 100); // Record a time for the number of tabs, to help track down contention. std::string time_for_count = base::StringPrintf( "SessionRestore.AllTabsLoaded_%u", static_cast(tab_loader_stats.tab_count)); base::HistogramBase* counter_for_count = base::Histogram::FactoryTimeGet( time_for_count, base::TimeDelta::FromMilliseconds(10), base::TimeDelta::FromSeconds(100), 100, base::Histogram::kUmaTargetedHistogramFlag); counter_for_count->AddTime(tab_loader_stats.non_deferred_tabs_loaded); } UMA_HISTOGRAM_COUNTS_100("SessionRestore.ParallelTabLoads", tab_loader_stats.parallel_tab_loads); } void SessionRestoreStatsCollector::UmaStatsReportingDelegate:: ReportTabDeferred() { if (!got_report_tab_deferred_) { got_report_tab_deferred_ = true; EmitUmaSessionRestoreActionEvent(SESSION_RESTORE_ACTIONS_UMA_DEFERRED_TABS); } EmitUmaSessionRestoreTabActionEvent( SESSION_RESTORE_TAB_ACTIONS_UMA_TAB_LOADING_DEFERRED); } void SessionRestoreStatsCollector::UmaStatsReportingDelegate:: ReportDeferredTabLoaded() { EmitUmaSessionRestoreTabActionEvent( SESSION_RESTORE_TAB_ACTIONS_UMA_DEFERRED_TAB_LOADED); }