diff options
author | chrisha <chrisha@chromium.org> | 2015-06-19 18:21:58 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-06-20 01:22:22 +0000 |
commit | f3c87f3238e503d995f3690d592d454715ff9cea (patch) | |
tree | 3a8f6c86e3fdb0b12aaf2be95dd60b1f386241ad | |
parent | 55c615dc327046f24354b815e1edc795ed52bccb (diff) | |
download | chromium_src-f3c87f3238e503d995f3690d592d454715ff9cea.zip chromium_src-f3c87f3238e503d995f3690d592d454715ff9cea.tar.gz chromium_src-f3c87f3238e503d995f3690d592d454715ff9cea.tar.bz2 |
This makes SessionRestoreStatsCollector aware of 'deferred' tabs and adds detailed logging of SessionRestore events.
BUG=472772
Review URL: https://codereview.chromium.org/1136523004
Cr-Commit-Position: refs/heads/master@{#335413}
-rw-r--r-- | chrome/browser/chromeos/power/extension_event_observer_unittest.cc | 8 | ||||
-rw-r--r-- | chrome/browser/sessions/session_restore_delegate.cc | 30 | ||||
-rw-r--r-- | chrome/browser/sessions/session_restore_stats_collector.cc | 565 | ||||
-rw-r--r-- | chrome/browser/sessions/session_restore_stats_collector.h | 250 | ||||
-rw-r--r-- | chrome/browser/sessions/session_restore_stats_collector_unittest.cc | 621 | ||||
-rw-r--r-- | chrome/browser/sessions/tab_loader.cc | 22 | ||||
-rw-r--r-- | chrome/browser/sessions/tab_loader.h | 7 | ||||
-rw-r--r-- | chrome/chrome_tests_unit.gypi | 1 | ||||
-rw-r--r-- | content/test/test_render_view_host.cc | 2 | ||||
-rw-r--r-- | tools/metrics/histograms/histograms.xml | 56 |
10 files changed, 1345 insertions, 217 deletions
diff --git a/chrome/browser/chromeos/power/extension_event_observer_unittest.cc b/chrome/browser/chromeos/power/extension_event_observer_unittest.cc index e092a03..455fb6a 100644 --- a/chrome/browser/chromeos/power/extension_event_observer_unittest.cc +++ b/chrome/browser/chromeos/power/extension_event_observer_unittest.cc @@ -30,6 +30,8 @@ #include "extensions/common/manifest_handlers/background_info.h" #include "extensions/common/value_builder.h" #include "testing/gtest/include/gtest/gtest.h" +#include "ui/aura/test/test_screen.h" +#include "ui/gfx/screen.h" namespace chromeos { @@ -37,6 +39,7 @@ class ExtensionEventObserverTest : public ::testing::Test { public: ExtensionEventObserverTest() : power_manager_client_(new FakePowerManagerClient()), + test_screen_(aura::TestScreen::Create(gfx::Size())), fake_user_manager_(new FakeChromeUserManager()), scoped_user_manager_enabler_(fake_user_manager_) { DBusThreadManager::GetSetterForTesting()->SetPowerManagerClient( @@ -59,6 +62,8 @@ class ExtensionEventObserverTest : public ::testing::Test { void SetUp() override { ::testing::Test::SetUp(); + gfx::Screen::SetScreenInstance(gfx::SCREEN_TYPE_NATIVE, test_screen_.get()); + // Must be called from ::testing::Test::SetUp. ASSERT_TRUE(profile_manager_->SetUp()); @@ -72,7 +77,7 @@ class ExtensionEventObserverTest : public ::testing::Test { void TearDown() override { profile_ = NULL; profile_manager_->DeleteAllTestingProfiles(); - + gfx::Screen::SetScreenInstance(gfx::SCREEN_TYPE_NATIVE, nullptr); ::testing::Test::TearDown(); } @@ -122,6 +127,7 @@ class ExtensionEventObserverTest : public ::testing::Test { scoped_ptr<TestingProfileManager> profile_manager_; private: + scoped_ptr<aura::TestScreen> test_screen_; content::TestBrowserThreadBundle browser_thread_bundle_; // Needed to ensure we don't end up creating actual RenderViewHosts diff --git a/chrome/browser/sessions/session_restore_delegate.cc b/chrome/browser/sessions/session_restore_delegate.cc index 52564f9..87b3e06 100644 --- a/chrome/browser/sessions/session_restore_delegate.cc +++ b/chrome/browser/sessions/session_restore_delegate.cc @@ -69,22 +69,38 @@ bool SessionRestoreDelegate::RestoredTab::operator<( void SessionRestoreDelegate::RestoreTabs( const std::vector<RestoredTab>& tabs, const base::TimeTicks& restore_started) { + // Restore the favicon for all tabs. Any tab may end up being deferred due + // to memory pressure so it's best to have some visual indication of its + // contents. + for (const auto& restored_tab : tabs) { + // Restore the favicon for deferred tabs. + favicon::ContentFaviconDriver* favicon_driver = + favicon::ContentFaviconDriver::FromWebContents(restored_tab.contents()); + favicon_driver->FetchFavicon(favicon_driver->GetActiveURL()); + } + // This experiment allows us to have comparative numbers for session restore // metrics. It will be removed once those numbers are obtained. // TODO(georgesak): Remove this experiment when stats are collected. base::FieldTrial* trial = base::FieldTrialList::Find("IntelligentSessionRestore"); if (!trial || trial->group_name() != "DontRestoreBackgroundTabs") { - SessionRestoreStatsCollector::TrackTabs(tabs, restore_started); TabLoader::RestoreTabs(tabs, restore_started); } else { - SessionRestoreStatsCollector::TrackActiveTabs(tabs, restore_started); - for (auto& restored_tab : tabs) { + // A TabLoader will not be used for this session restore, so manually create + // and use a SessionRestoreStatsCollector, normally owned by the TabLoader. + scoped_ptr<SessionRestoreStatsCollector::StatsReportingDelegate> + reporting_delegate( + new SessionRestoreStatsCollector::UmaStatsReportingDelegate()); + scoped_refptr<SessionRestoreStatsCollector> stats_collector = + new SessionRestoreStatsCollector(restore_started, + reporting_delegate.Pass()); + stats_collector->TrackTabs(tabs); + for (const auto& restored_tab : tabs) { if (!restored_tab.is_active()) { - favicon::ContentFaviconDriver* favicon_driver = - favicon::ContentFaviconDriver::FromWebContents( - restored_tab.contents()); - favicon_driver->FetchFavicon(favicon_driver->GetActiveURL()); + // Non-active tabs aren't being loaded, so mark them as deferred. + auto tab_controller = &restored_tab.contents()->GetController(); + stats_collector->DeferTab(tab_controller); } } } diff --git a/chrome/browser/sessions/session_restore_stats_collector.cc b/chrome/browser/sessions/session_restore_stats_collector.cc index f892d1a..262500f 100644 --- a/chrome/browser/sessions/session_restore_stats_collector.cc +++ b/chrome/browser/sessions/session_restore_stats_collector.cc @@ -8,58 +8,137 @@ #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_view.h" #include "content/public/browser/web_contents.h" +namespace { + using content::NavigationController; using content::RenderWidgetHost; +using content::Source; using content::WebContents; -// static -void SessionRestoreStatsCollector::TrackTabs( - const std::vector<SessionRestoreDelegate::RestoredTab>& tabs, - const base::TimeTicks& restore_started) { - if (!shared_collector_) - shared_collector_ = new SessionRestoreStatsCollector(restore_started); +// 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, +}; - shared_collector_->AddTabs(tabs); +// Emits a SessionRestore.Actions UMA event. +void EmitUmaSessionRestoreActionEvent(SessionRestoreActionsUma action) { + UMA_HISTOGRAM_ENUMERATION("SessionRestore.Actions", action, + SESSION_RESTORE_ACTIONS_UMA_MAX); } -// static -void SessionRestoreStatsCollector::TrackActiveTabs( - const std::vector<SessionRestoreDelegate::RestoredTab>& tabs, - const base::TimeTicks& restore_started) { - if (!shared_collector_) - shared_collector_ = new SessionRestoreStatsCollector(restore_started); +// 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, + // The size of this enum. Must be the last entry. + SESSION_RESTORE_TAB_ACTIONS_UMA_MAX, +}; - std::vector<SessionRestoreDelegate::RestoredTab> active_tabs; - for (auto tab : tabs) { - if (tab.is_active()) - active_tabs.push_back(tab); - } - shared_collector_->AddTabs(active_tabs); +// Emits a SessionRestore.TabActions UMA event. +void EmitUmaSessionRestoreTabActionEvent(SessionRestoreTabActionsUma action) { + UMA_HISTOGRAM_ENUMERATION("SessionRestore.TabActions", action, + SESSION_RESTORE_TAB_ACTIONS_UMA_MAX); +} + +} // namespace + +SessionRestoreStatsCollector::TabLoaderStats::TabLoaderStats() + : tab_count(0u), tabs_loaded(0u), parallel_tab_loads(0u) { +} + +SessionRestoreStatsCollector::TabState::TabState( + NavigationController* controller) + : controller(controller), + render_widget_host(nullptr), + is_deferred(false), + loading_state(TAB_IS_NOT_LOADING) { } SessionRestoreStatsCollector::SessionRestoreStatsCollector( - const base::TimeTicks& restore_started) - : got_first_foreground_load_(false), + const base::TimeTicks& restore_started, + scoped_ptr<StatsReportingDelegate> reporting_delegate) + : done_tracking_non_deferred_tabs_(false), + got_first_foreground_load_(false), got_first_paint_(false), restore_started_(restore_started), - tab_count_(0), - max_parallel_tab_loads_(0) { + 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; - registrar_.Add( - this, content::NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE, - content::NotificationService::AllSources()); } SessionRestoreStatsCollector::~SessionRestoreStatsCollector() { - DCHECK((got_first_paint_ || render_widget_hosts_to_paint_.empty()) && - tabs_tracked_.empty() && render_widget_hosts_loading_.empty()); - DCHECK(shared_collector_ == this); - shared_collector_ = nullptr; +} + +void SessionRestoreStatsCollector::TrackTabs( + const std::vector<SessionRestoreDelegate::RestoredTab>& 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_; + + // 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( @@ -68,99 +147,112 @@ void SessionRestoreStatsCollector::Observe( const content::NotificationDetails& details) { switch (type) { case content::NOTIFICATION_LOAD_START: { - // Add this render_widget_host to the set of those we're waiting for - // paints on. We want to only record stats for paints that occur after - // a load has finished. - NavigationController* tab = - content::Source<NavigationController>(source).ptr(); - RenderWidgetHost* render_widget_host = GetRenderWidgetHost(tab); - DCHECK(render_widget_host); - render_widget_hosts_loading_.insert(render_widget_host); + // 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<NavigationController>(source).ptr(); + TabState* tab_state = GetTabState(tab); + MarkTabAsLoading(tab_state); break; } case content::NOTIFICATION_WEB_CONTENTS_DESTROYED: { - WebContents* web_contents = content::Source<WebContents>(source).ptr(); - RemoveTab(&web_contents->GetController()); + // 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<WebContents>(source).ptr(); + NavigationController* tab = &web_contents->GetController(); + RemoveTab(tab); break; } case content::NOTIFICATION_LOAD_STOP: { - NavigationController* tab = - content::Source<NavigationController>(source).ptr(); - RenderWidgetHost* render_widget_host = GetRenderWidgetHost(tab); - render_widget_hosts_to_paint_.insert(render_widget_host); - RemoveTab(tab); - if (!got_first_foreground_load_ && render_widget_host && - render_widget_host->GetView() && - render_widget_host->GetView()->IsShowing()) { + // 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<NavigationController>(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_ && tab_state->render_widget_host && + tab_state->render_widget_host->GetView() && + tab_state->render_widget_host->GetView()->IsShowing()) { got_first_foreground_load_ = true; - base::TimeDelta time_to_load = - base::TimeTicks::Now() - restore_started_; - UMA_HISTOGRAM_CUSTOM_TIMES("SessionRestore.ForegroundTabFirstLoaded", - time_to_load, - 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_%d", 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(time_to_load); + 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 = - content::Source<RenderWidgetHost>(source).ptr(); + Source<RenderWidgetHost>(source).ptr(); if (!got_first_paint_ && render_widget_host->GetView() && render_widget_host->GetView()->IsShowing()) { - if (render_widget_hosts_to_paint_.find(render_widget_host) != - render_widget_hosts_to_paint_.end()) { - // Got a paint for one of our renderers, so record time. - got_first_paint_ = true; - base::TimeDelta time_to_paint = - base::TimeTicks::Now() - restore_started_; - // TODO(danduong): to remove this with 467680, to make sure we - // don't forget to clean this up. - UMA_HISTOGRAM_CUSTOM_TIMES("SessionRestore.ForegroundTabFirstPaint", - time_to_paint, - 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.ForegroundTabFirstPaint_%d", 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(time_to_paint); - UMA_HISTOGRAM_CUSTOM_TIMES("SessionRestore.ForegroundTabFirstPaint2", - time_to_paint, - base::TimeDelta::FromMilliseconds(100), - base::TimeDelta::FromMinutes(16), 50); - // Record a time for the number of tabs, to help track down - // contention. - std::string time_for_count2 = base::StringPrintf( - "SessionRestore.ForegroundTabFirstPaint2_%d", tab_count_); - base::HistogramBase* counter_for_count2 = - base::Histogram::FactoryTimeGet( - time_for_count2, base::TimeDelta::FromMilliseconds(100), - base::TimeDelta::FromMinutes(16), 50, - base::Histogram::kUmaTargetedHistogramFlag); - counter_for_count2->AddTime(time_to_paint); - } else if (render_widget_hosts_loading_.find(render_widget_host) == - render_widget_hosts_loading_.end()) { - // If this is a host for a tab we're not loading some other tab - // has rendered and there's no point tracking the time. This could + 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 painted. - got_first_paint_ = true; + // 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<NavigationController*> 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; } @@ -169,72 +261,69 @@ void SessionRestoreStatsCollector::Observe( break; } - // Check if we are done and if so, reset |this_retainer_| as the collector no - // longer needs to stay alive. - if ((got_first_paint_ || render_widget_hosts_to_paint_.empty()) && - tabs_tracked_.empty() && render_widget_hosts_loading_.empty()) - this_retainer_ = nullptr; -} - -void SessionRestoreStatsCollector::AddTabs( - const std::vector<SessionRestoreDelegate::RestoredTab>& tabs) { - tab_count_ += tabs.size(); - for (auto& tab : tabs) { - RegisterForNotifications(&tab.contents()->GetController()); - if (tab.is_active()) { - RenderWidgetHost* render_widget_host = - GetRenderWidgetHost(&tab.contents()->GetController()); - render_widget_hosts_loading_.insert(render_widget_host); - } - } + ReleaseIfDoneTracking(); } void SessionRestoreStatsCollector::RemoveTab(NavigationController* tab) { + // Stop observing this tab. registrar_.Remove(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED, - content::Source<WebContents>(tab->GetWebContents())); + Source<WebContents>(tab->GetWebContents())); registrar_.Remove(this, content::NOTIFICATION_LOAD_STOP, - content::Source<NavigationController>(tab)); + Source<NavigationController>(tab)); registrar_.Remove(this, content::NOTIFICATION_LOAD_START, - content::Source<NavigationController>(tab)); - if (render_widget_hosts_loading_.size() > max_parallel_tab_loads_) - max_parallel_tab_loads_ = render_widget_hosts_loading_.size(); - RenderWidgetHost* render_widget_host = GetRenderWidgetHost(tab); - render_widget_hosts_loading_.erase(render_widget_host); - tabs_tracked_.erase(tab); - - // If there are no more tabs loading or being tracked, restore is done and - // record the time. Note that we are not yet finished, as we might still be - // waiting for our first paint, which can happen after all tabs are done - // loading. - // TODO(georgesak): review behaviour of ForegroundTabFirstPaint. - if (tabs_tracked_.empty() && render_widget_hosts_loading_.empty()) { - base::TimeDelta time_to_load = base::TimeTicks::Now() - restore_started_; - UMA_HISTOGRAM_CUSTOM_TIMES("SessionRestore.AllTabsLoaded", time_to_load, - 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_%d", 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(time_to_load); + Source<NavigationController>(tab)); - UMA_HISTOGRAM_COUNTS_100("SessionRestore.ParallelTabLoads", - max_parallel_tab_loads_); + 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()); } } -void SessionRestoreStatsCollector::RegisterForNotifications( +SessionRestoreStatsCollector::TabState* +SessionRestoreStatsCollector::RegisterForNotifications( NavigationController* tab) { registrar_.Add(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED, - content::Source<WebContents>(tab->GetWebContents())); + Source<WebContents>(tab->GetWebContents())); registrar_.Add(this, content::NOTIFICATION_LOAD_STOP, - content::Source<NavigationController>(tab)); + Source<NavigationController>(tab)); registrar_.Add(this, content::NOTIFICATION_LOAD_START, - content::Source<NavigationController>(tab)); - tabs_tracked_.insert(tab); + Source<NavigationController>(tab)); + auto result = tabs_tracked_.insert(std::make_pair(tab, TabState(tab))); + DCHECK(result.second); + TabState* tab_state = &result.first->second; + return tab_state; } RenderWidgetHost* SessionRestoreStatsCollector::GetRenderWidgetHost( @@ -249,6 +338,158 @@ RenderWidgetHost* SessionRestoreStatsCollector::GetRenderWidgetHost( return nullptr; } -// static -SessionRestoreStatsCollector* SessionRestoreStatsCollector::shared_collector_ = - nullptr; +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_) { + if (pair.second.render_widget_host == 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_.parallel_tab_loads = + std::max(tab_loader_stats_.parallel_tab_loads, loading_tab_count_); + } + + // Get the RenderWidgetHost for the tab and add it to the secondary index. + RenderWidgetHost* render_widget_host = + GetRenderWidgetHost(tab_state->controller); + DCHECK(render_widget_host); + tab_state->render_widget_host = render_widget_host; +} + +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); + + 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); + } + + 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<unsigned int>(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<unsigned int>(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<unsigned int>(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); +} diff --git a/chrome/browser/sessions/session_restore_stats_collector.h b/chrome/browser/sessions/session_restore_stats_collector.h index 668e2c0..48da96e 100644 --- a/chrome/browser/sessions/session_restore_stats_collector.h +++ b/chrome/browser/sessions/session_restore_stats_collector.h @@ -5,9 +5,10 @@ #ifndef CHROME_BROWSER_SESSIONS_SESSION_RESTORE_STATS_COLLECTOR_H_ #define CHROME_BROWSER_SESSIONS_SESSION_RESTORE_STATS_COLLECTOR_H_ -#include <set> +#include <map> #include "base/callback_list.h" +#include "base/time/tick_clock.h" #include "chrome/browser/sessions/session_restore.h" #include "chrome/browser/sessions/session_restore_delegate.h" #include "content/public/browser/notification_observer.h" @@ -20,83 +21,252 @@ class NavigationController; // SessionRestoreStatsCollector observes SessionRestore events ands records UMA // accordingly. +// +// A SessionRestoreStatsCollector is tied to an instance of a session restore, +// currently being instantianted and owned by the TabLoader. It has two main +// phases to its life: +// +// 1. The session restore is active and ongoing (the TabLoader is still +// scheduling tabs for loading). This phases ends when there are no +// non-deferred tabs left to be loaded. During this phases statistics are +// gathered in a structure before being emitted as UMA metrics at the end of +// this phase. At this point the TabLoader ceases to exist and destroys it's +// reference to the SessionRestoreStatsCollector. +// 2. If any tabs have been deferred the SessionRestoreStatsCollector continues +// tracking deferred tabs. This continues to observe the tabs to see which +// (if any) of the deferred tabs are subsequently forced to be loaded by the +// user. Since such tabs may exist until the end of the browsers life the +// statistics are emitted immediately, or risk being lost entirely. When +// there are no longer deferred tabs to track the +// SessionRestoreStatsCollector will destroy itself. +// +// TODO(chrisha): Many of these metrics don't make sense to collect in the +// presence of an unavailable network, or when tabs are closed during loading. +// Rethink the collection in these cases. class SessionRestoreStatsCollector : public content::NotificationObserver, public base::RefCounted<SessionRestoreStatsCollector> { public: - // Called to start tracking tabs. If a restore is already occuring, the tabs - // are added to the existing list of tracked tabs. - static void TrackTabs( - const std::vector<SessionRestoreDelegate::RestoredTab>& tabs, - const base::TimeTicks& restore_started); - - // Called to start tracking only active tabs. If a restore is already - // occuring, the tabs are added to the existing list of tracked tabs. - static void TrackActiveTabs( - const std::vector<SessionRestoreDelegate::RestoredTab>& tabs, - const base::TimeTicks& restore_started); + // Houses all of the statistics gathered by the SessionRestoreStatsCollector + // while the underlying TabLoader is active. These statistics are all reported + // at once via the reporting delegate. + struct TabLoaderStats { + // Constructor that initializes everything to zero. + TabLoaderStats(); + + // The number of tabs involved in all overlapping session restores being + // tracked by this SessionRestoreStatsCollector. This corresponds to the + // "SessionRestore.TabCount" metric and one bucket of the + // "SessionRestore.TabActions" histogram. + size_t tab_count; + + // The number of tabs loaded automatically because they are active, and + // explicitly caused to be loaded by the TabLoader. This corresponds to one + // bucket of the "SessionRestore.TabActions" histogram. + size_t tabs_loaded; + + // The time elapsed between |restore_started| and reception of the first + // NOTIFICATION_LOAD_STOP event for any of the active tabs involved in the + // session restore. If this is zero it is because it has not been + // recorded (all visible tabs were closed before they finished loading, or + // the user switched to an already loaded tab before a visible session + // restore tab finished loading). Corresponds to + // "SessionRestore.ForegroundTabFirstLoaded" and its _XX variants. + base::TimeDelta foreground_tab_first_loaded; + + // The time elapsed between |restore_started| and reception of the first + // NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE event for any of + // the tabs involved in the session restore. If this is zero it is because + // it has not been recorded (all visible tabs were closed or switched away + // from before they were painted). Corresponds to + // "SessionRestore.ForegroundTabFirstPaint3" and its _XX variants. + base::TimeDelta foreground_tab_first_paint; + + // The time taken for all non-deferred tabs to be loaded. This corresponds + // to the "SessionRestore.AllTabsLoaded" metric and its _XX variants + // (vaguely named for historical reasons, as it predates the concept of + // deferred tabs). + base::TimeDelta non_deferred_tabs_loaded; + + // The maximum number of tabs loading in parallel. This corresponds to the + // "SessionRestore.ParallelTabLoads" metric. + size_t parallel_tab_loads; + }; + + // The StatsReportingDelegate is responsible for delivering statistics + // reported by the SessionRestoreStatsCollector. + class StatsReportingDelegate; + + // An implementation of StatsReportingDelegate for reporting via UMA. + class UmaStatsReportingDelegate; + + // Constructs a SessionRestoreStatsCollector. + SessionRestoreStatsCollector( + const base::TimeTicks& restore_started, + scoped_ptr<StatsReportingDelegate> reporting_delegate); + + // Adds new tabs to the list of tracked tabs. + void TrackTabs(const std::vector<SessionRestoreDelegate::RestoredTab>& tabs); + + // Called to indicate that the loading of a tab has been deferred by session + // restore. + void DeferTab(content::NavigationController* tab); + + // Exposed for unittesting. + const TabLoaderStats& tab_loader_stats() const { return tab_loader_stats_; } private: + friend class TestSessionRestoreStatsCollector; friend class base::RefCounted<SessionRestoreStatsCollector>; - using RenderWidgetHostSet = std::set<content::RenderWidgetHost*>; + enum TabLoadingState { TAB_IS_NOT_LOADING, TAB_IS_LOADING, TAB_IS_LOADED }; + + // State that is tracked for a tab while it is being observed. + struct TabState { + explicit TabState(content::NavigationController* controller); + + // The NavigationController associated with the tab. This is the primary + // index for it and is never null. + content::NavigationController* controller; + + // The RenderWidgetHost associated with the tab. This is the secondary + // index and starts out being null. If it is not null it is because the tab + // is actively loading or waiting to be painted. + content::RenderWidgetHost* render_widget_host; + + // Set to true if the tab has been deferred by the TabLoader. + bool is_deferred; + + // The current loading state of the tab. + TabLoadingState loading_state; + }; + + // Maps a NavigationController to its state. This is the primary map and + // physically houses the state. + using NavigationControllerMap = + std::map<content::NavigationController*, TabState>; - explicit SessionRestoreStatsCollector(const base::TimeTicks& restore_started); ~SessionRestoreStatsCollector() override; - // NotificationObserver method. + // NotificationObserver method. This is the workhorse of the class and drives + // all state transitions. void Observe(int type, const content::NotificationSource& source, const content::NotificationDetails& details) override; - // Adds new tabs to the list of tracked tabs. - void AddTabs(const std::vector<SessionRestoreDelegate::RestoredTab>& tabs); - - // Called when a tab is no longer tracked. + // Called when a tab is no longer tracked. This is called by the 'Observe' + // notification callback. Takes care of unregistering all observers and + // removing the tab from all internal data structures. void RemoveTab(content::NavigationController* tab); - // Registers for relevant notifications for a tab. - void RegisterForNotifications(content::NavigationController* tab); + // Registers for relevant notifications for a tab and inserts the tab into + // to tabs_tracked_ map. Return a pointer to the newly created TabState. + TabState* RegisterForNotifications(content::NavigationController* tab); // Returns the RenderWidgetHost of a tab. content::RenderWidgetHost* GetRenderWidgetHost( content::NavigationController* tab); - // Have we recorded the times for a foreground tab load? + // Returns the tab state, nullptr if not found. + TabState* GetTabState(content::NavigationController* tab); + TabState* GetTabState(content::RenderWidgetHost* tab); + + // Marks a tab as loading. + void MarkTabAsLoading(TabState* tab_state); + + // Checks to see if the SessionRestoreStatsCollector has finished collecting, + // and if so, releases the self reference to the shared pointer. + void ReleaseIfDoneTracking(); + + // Testing seam for configuring the tick clock in use. + void set_tick_clock(scoped_ptr<base::TickClock> tick_clock) { + tick_clock_ = tick_clock.Pass(); + } + + // Has ReleaseIfDoneTracking determined that there are no non-deferred tabs to + // track? + bool done_tracking_non_deferred_tabs_; + + // Has the time for foreground tab load been recorded? bool got_first_foreground_load_; - // Have we recorded the times for a foreground tab paint? + // Has the time for foreground tab paint been recorded? bool got_first_paint_; // The time the restore process started. - base::TimeTicks restore_started_; + const base::TimeTicks restore_started_; - // The renderers we have started loading into. - RenderWidgetHostSet render_widget_hosts_loading_; + // List of tracked tabs, mapped to their TabState. + NavigationControllerMap tabs_tracked_; - // The renderers we have loaded and are waiting on to paint. - RenderWidgetHostSet render_widget_hosts_to_paint_; + // Counts the number of non-deferred tabs that the + // SessionRestoreStatsCollector is waiting to see load. + size_t waiting_for_load_tab_count_; - // List of tracked tabs. - std::set<content::NavigationController*> tabs_tracked_; + // Counts the current number of actively loading tabs. + size_t loading_tab_count_; - // The number of tabs that have been restored. - int tab_count_; - - // Max number of tabs that were loaded in parallel (for metrics). - size_t max_parallel_tab_loads_; + // Counts the number of deferred tabs. + size_t deferred_tab_count_; // Notification registrar. content::NotificationRegistrar registrar_; - // To keep the collector alive as long as needed. - scoped_refptr<SessionRestoreStatsCollector> this_retainer_; + // Statistics gathered regarding the TabLoader. + TabLoaderStats tab_loader_stats_; + + // The source of ticks used for taking timing information. This is + // configurable as a testing seam. Defaults to using base::DefaultTickClock, + // which in turn uses base::TimeTicks. + scoped_ptr<base::TickClock> tick_clock_; - // The shared SessionRestoreNotifier instance for all SessionRestores running - // at this time. - static SessionRestoreStatsCollector* shared_collector_; + // The reporting delegate used to report gathered statistics. + scoped_ptr<StatsReportingDelegate> reporting_delegate_; + + // For keeping SessionRestoreStatsCollector alive while it is still working + // even if no TabLoader references it. The object only lives on if it still + // has deferred tabs remaining from an interrupted session restore. + scoped_refptr<SessionRestoreStatsCollector> this_retainer_; DISALLOW_COPY_AND_ASSIGN(SessionRestoreStatsCollector); }; +// An abstract reporting delegate is used as a testing seam. +class SessionRestoreStatsCollector::StatsReportingDelegate { + public: + StatsReportingDelegate() {} + virtual ~StatsReportingDelegate() {} + + // Called when TabLoader has completed its work. + virtual void ReportTabLoaderStats(const TabLoaderStats& tab_loader_stats) = 0; + + // Called when a tab has been deferred. + virtual void ReportTabDeferred() = 0; + + // Called when a deferred tab has been loaded. + virtual void ReportDeferredTabLoaded() = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(StatsReportingDelegate); +}; + +// The default reporting delegate, which reports statistics via UMA. +class SessionRestoreStatsCollector::UmaStatsReportingDelegate + : public StatsReportingDelegate { + public: + UmaStatsReportingDelegate(); + ~UmaStatsReportingDelegate() override {} + + // StatsReportingDelegate: + void ReportTabLoaderStats(const TabLoaderStats& tab_loader_stats) override; + void ReportTabDeferred() override; + void ReportDeferredTabLoaded() override; + + private: + // Has ReportTabDeferred been called? + bool got_report_tab_deferred_; + + DISALLOW_COPY_AND_ASSIGN(UmaStatsReportingDelegate); +}; + #endif // CHROME_BROWSER_SESSIONS_SESSION_RESTORE_STATS_COLLECTOR_H_ diff --git a/chrome/browser/sessions/session_restore_stats_collector_unittest.cc b/chrome/browser/sessions/session_restore_stats_collector_unittest.cc new file mode 100644 index 0000000..e50100b --- /dev/null +++ b/chrome/browser/sessions/session_restore_stats_collector_unittest.cc @@ -0,0 +1,621 @@ +// 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 "base/message_loop/message_loop.h" +#include "base/test/simple_test_tick_clock.h" +#include "chrome/test/base/testing_profile.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" +#include "content/public/test/test_browser_thread.h" +#include "content/public/test/test_web_contents_factory.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +using TabLoaderStats = SessionRestoreStatsCollector::TabLoaderStats; +using StatsReportingDelegate = + SessionRestoreStatsCollector::StatsReportingDelegate; + +// A mock StatsReportingDelegate. This is used by the unittests to validate the +// reporting and lifetime behaviour of the SessionRestoreStatsCollector under +// test. +class MockStatsReportingDelegate : public StatsReportingDelegate { + public: + MockStatsReportingDelegate() + : report_tab_loader_stats_call_count_(0u), + report_tab_deferred_call_count_(0u), + report_deferred_tab_loaded_call_count_(0u), + report_stats_collector_death_call_count_(0u) {} + + ~MockStatsReportingDelegate() override { EnsureNoUnexpectedCalls(); } + + void ReportTabLoaderStats(const TabLoaderStats& stats) override { + report_tab_loader_stats_call_count_++; + tab_loader_stats_ = stats; + } + + void ReportTabDeferred() override { report_tab_deferred_call_count_++; } + + void ReportDeferredTabLoaded() override { + report_deferred_tab_loaded_call_count_++; + } + + // This is not part of the StatsReportingDelegate, but an added function that + // is invoked by the PassthroughStatsReportingDelegate when it dies. This + // allows the tests to be notified the moment the underlying stats collector + // terminates itself. + void ReportStatsCollectorDeath() { + report_stats_collector_death_call_count_++; + } + + void ExpectReportTabLoaderStatsCalled(size_t tab_count, + size_t tabs_loaded, + int foreground_tab_first_loaded_ms, + int foreground_tab_first_paint_ms, + int non_deferred_tabs_loaded_ms, + size_t parallel_tab_loads) { + EXPECT_LT(0u, report_tab_loader_stats_call_count_); + report_tab_loader_stats_call_count_--; + + EXPECT_EQ(tab_count, tab_loader_stats_.tab_count); + EXPECT_EQ(tabs_loaded, tab_loader_stats_.tabs_loaded); + EXPECT_EQ(base::TimeDelta::FromMilliseconds(foreground_tab_first_loaded_ms), + tab_loader_stats_.foreground_tab_first_loaded); + EXPECT_EQ(base::TimeDelta::FromMilliseconds(foreground_tab_first_paint_ms), + tab_loader_stats_.foreground_tab_first_paint); + EXPECT_EQ(base::TimeDelta::FromMilliseconds(non_deferred_tabs_loaded_ms), + tab_loader_stats_.non_deferred_tabs_loaded); + EXPECT_EQ(parallel_tab_loads, tab_loader_stats_.parallel_tab_loads); + } + + void ExpectReportTabDeferredCalled() { + EXPECT_LT(0u, report_tab_deferred_call_count_); + report_tab_deferred_call_count_--; + } + + void ExpectReportDeferredTabLoadedCalled() { + EXPECT_LT(0u, report_deferred_tab_loaded_call_count_); + report_deferred_tab_loaded_call_count_--; + } + + void ExpectReportStatsCollectorDeathCalled() { + EXPECT_LT(0u, report_stats_collector_death_call_count_); + report_stats_collector_death_call_count_--; + } + + void EnsureNoUnexpectedCalls() { + EXPECT_EQ(0u, report_tab_loader_stats_call_count_); + EXPECT_EQ(0u, report_tab_deferred_call_count_); + EXPECT_EQ(0u, report_deferred_tab_loaded_call_count_); + EXPECT_EQ(0u, report_stats_collector_death_call_count_); + + report_tab_loader_stats_call_count_ = 0u; + report_tab_deferred_call_count_ = 0u; + report_deferred_tab_loaded_call_count_ = 0u; + report_stats_collector_death_call_count_ = 0u; + tab_loader_stats_ = TabLoaderStats(); + } + + private: + size_t report_tab_loader_stats_call_count_; + size_t report_tab_deferred_call_count_; + size_t report_deferred_tab_loaded_call_count_; + size_t report_stats_collector_death_call_count_; + TabLoaderStats tab_loader_stats_; + + DISALLOW_COPY_AND_ASSIGN(MockStatsReportingDelegate); +}; + +// A pass-through stats reporting delegate. This is used to decouple the +// lifetime of the mock reporting delegate from the SessionRestoreStatsCollector +// under test. The SessionRestoreStatsCollector has ownership of this delegate, +// which will notify the mock delegate upon its death. +class PassthroughStatsReportingDelegate : public StatsReportingDelegate { + public: + PassthroughStatsReportingDelegate() : reporting_delegate_(nullptr) {} + ~PassthroughStatsReportingDelegate() override { + reporting_delegate_->ReportStatsCollectorDeath(); + } + + void set_reporting_delegate(MockStatsReportingDelegate* reporting_delegate) { + reporting_delegate_ = reporting_delegate; + } + + void ReportTabLoaderStats(const TabLoaderStats& tab_loader_stats) override { + reporting_delegate_->ReportTabLoaderStats(tab_loader_stats); + } + + void ReportTabDeferred() override { + reporting_delegate_->ReportTabDeferred(); + } + + void ReportDeferredTabLoaded() override { + reporting_delegate_->ReportDeferredTabLoaded(); + } + + private: + MockStatsReportingDelegate* reporting_delegate_; + + DISALLOW_COPY_AND_ASSIGN(PassthroughStatsReportingDelegate); +}; + +} // namespace + +class TestSessionRestoreStatsCollector : public SessionRestoreStatsCollector { + public: + using SessionRestoreStatsCollector::Observe; + + TestSessionRestoreStatsCollector( + scoped_ptr<base::TickClock> tick_clock, + scoped_ptr<StatsReportingDelegate> reporting_delegate) + : SessionRestoreStatsCollector(tick_clock->NowTicks(), + reporting_delegate.Pass()) { + set_tick_clock(tick_clock.Pass()); + } + + private: + friend class base::RefCounted<TestSessionRestoreStatsCollector>; + + ~TestSessionRestoreStatsCollector() override {} + + base::SimpleTestTickClock* test_tick_clock_; + + DISALLOW_COPY_AND_ASSIGN(TestSessionRestoreStatsCollector); +}; + +class SessionRestoreStatsCollectorTest : public testing::Test { + public: + using RestoredTab = SessionRestoreDelegate::RestoredTab; + + SessionRestoreStatsCollectorTest() + : ui_thread_(content::BrowserThread::UI, &message_loop_) {} + + void SetUp() override { + test_web_contents_factory_.reset(new content::TestWebContentsFactory); + + // Ownership of the reporting delegate is passed to the + // SessionRestoreStatsCollector, but a raw pointer is kept to it so it can + // be queried by the test. + passthrough_reporting_delegate_ = new PassthroughStatsReportingDelegate(); + + // Ownership of this clock is passed to the SessionRestoreStatsCollector. + // A raw pointer is kept to it so that it can be modified from the outside. + // The unittest must take care to access the clock only while the + // SessionRestoreStatsCollector under test is still alive. + test_tick_clock_ = new base::SimpleTestTickClock(); + + // Create a stats collector, keep a raw pointer to it, and detach from it. + // The stats collector will stay alive as long as it has not yet completed + // its job, and will clean itself up when done. + scoped_refptr<TestSessionRestoreStatsCollector> stats_collector = + new TestSessionRestoreStatsCollector( + scoped_ptr<base::TickClock>(test_tick_clock_), + scoped_ptr<StatsReportingDelegate>( + passthrough_reporting_delegate_)); + stats_collector_ = stats_collector.get(); + stats_collector = nullptr; + } + + void TearDown() override { + passthrough_reporting_delegate_ = nullptr; + test_tick_clock_ = nullptr; + stats_collector_ = nullptr; + + // Clean up any tabs that were generated by the unittest. + restored_tabs_.clear(); + test_web_contents_factory_.reset(); + } + + // Advances the test clock by 1ms. + void Tick() { + test_tick_clock_->Advance(base::TimeDelta::FromMilliseconds(1)); + } + + void Show(size_t tab_index) { + restored_tabs_[tab_index].contents()->GetRenderWidgetHostView()->Show(); + } + + void Hide(size_t tab_index) { + restored_tabs_[tab_index].contents()->GetRenderWidgetHostView()->Hide(); + } + + // Creates a restored tab backed by dummy WebContents/NavigationController/ + // RenderWidgetHost/RenderWidgetHostView. Returns the index of the restored + // tab for future simulation of events. + void CreateRestoredTab(bool is_active) { + content::WebContents* contents = + test_web_contents_factory_->CreateWebContents(&testing_profile_); + restored_tabs_.push_back(RestoredTab(contents, is_active, false, false)); + if (is_active) + Show(restored_tabs_.size() - 1); + } + + // Helper function for various notification generation. + void GenerateControllerNotification(size_t tab_index, int type) { + content::WebContents* contents = restored_tabs_[tab_index].contents(); + content::NavigationController* controller = &contents->GetController(); + stats_collector_->Observe( + type, content::Source<content::NavigationController>(controller), + content::NotificationService::NoDetails()); + } + + // Generates a load start notification for the given tab. + void GenerateLoadStart(size_t tab_index) { + GenerateControllerNotification(tab_index, content::NOTIFICATION_LOAD_START); + } + + // Generates a load stop notification for the given tab. + void GenerateLoadStop(size_t tab_index) { + GenerateControllerNotification(tab_index, content::NOTIFICATION_LOAD_STOP); + } + + // Generates a web contents destroyed notification for the given tab. + void GenerateWebContentsDestroyed(size_t tab_index) { + content::WebContents* contents = restored_tabs_[tab_index].contents(); + stats_collector_->Observe(content::NOTIFICATION_WEB_CONTENTS_DESTROYED, + content::Source<content::WebContents>(contents), + content::NotificationService::NoDetails()); + } + + // Generates a paint notification for the given tab. + void GenerateRenderWidgetHostDidUpdateBackingStore(size_t tab_index) { + content::WebContents* contents = restored_tabs_[tab_index].contents(); + content::RenderWidgetHost* host = + contents->GetRenderWidgetHostView()->GetRenderWidgetHost(); + stats_collector_->Observe( + content::NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE, + content::Source<content::RenderWidgetHost>(host), + content::NotificationService::NoDetails()); + } + + // Defers a tab. + void DeferTab(size_t tab_index) { + content::WebContents* contents = restored_tabs_[tab_index].contents(); + content::NavigationController* controller = &contents->GetController(); + stats_collector_->DeferTab(controller); + } + + // Inputs to the stats collector. Reset prior to each test. + base::SimpleTestTickClock* test_tick_clock_; + std::vector<RestoredTab> restored_tabs_; + + // Infrastructure needed for using the TestWebContentsFactory. These are + // initialized once by the fixture and reused across unittests. + base::MessageLoop message_loop_; + TestingProfile testing_profile_; + content::TestBrowserThread ui_thread_; + + // A new web contents factory is generated per test. This automatically cleans + // up any tabs created by previous tests. + scoped_ptr<content::TestWebContentsFactory> test_web_contents_factory_; + + // These are recreated for each test. The reporting delegate allows the test + // to observe the behaviour of the SessionRestoreStatsCollector under test. + PassthroughStatsReportingDelegate* passthrough_reporting_delegate_; + TestSessionRestoreStatsCollector* stats_collector_; + + private: + DISALLOW_COPY_AND_ASSIGN(SessionRestoreStatsCollectorTest); +}; + +TEST_F(SessionRestoreStatsCollectorTest, SingleTabPaintBeforeLoad) { + MockStatsReportingDelegate mock_reporting_delegate; + passthrough_reporting_delegate_->set_reporting_delegate( + &mock_reporting_delegate); + + CreateRestoredTab(true); + stats_collector_->TrackTabs(restored_tabs_); + mock_reporting_delegate.EnsureNoUnexpectedCalls(); + + Tick(); // 1ms. + GenerateRenderWidgetHostDidUpdateBackingStore(0); + mock_reporting_delegate.EnsureNoUnexpectedCalls(); + + Tick(); // 2ms. + GenerateLoadStop(0); + mock_reporting_delegate.ExpectReportTabLoaderStatsCalled(1, 1, 2, 1, 2, 1); + mock_reporting_delegate.ExpectReportStatsCollectorDeathCalled(); +} + +TEST_F(SessionRestoreStatsCollectorTest, SingleTabPaintAfterLoad) { + MockStatsReportingDelegate mock_reporting_delegate; + passthrough_reporting_delegate_->set_reporting_delegate( + &mock_reporting_delegate); + + CreateRestoredTab(true); + stats_collector_->TrackTabs(restored_tabs_); + + Tick(); // 1ms. + GenerateLoadStop(0); + mock_reporting_delegate.EnsureNoUnexpectedCalls(); + + Tick(); // 2ms. + GenerateRenderWidgetHostDidUpdateBackingStore(0); + mock_reporting_delegate.ExpectReportTabLoaderStatsCalled(1, 1, 1, 2, 1, 1); + mock_reporting_delegate.ExpectReportStatsCollectorDeathCalled(); +} + +TEST_F(SessionRestoreStatsCollectorTest, MultipleTabsLoadSerially) { + MockStatsReportingDelegate mock_reporting_delegate; + passthrough_reporting_delegate_->set_reporting_delegate( + &mock_reporting_delegate); + + CreateRestoredTab(true); + CreateRestoredTab(false); + CreateRestoredTab(false); + stats_collector_->TrackTabs(restored_tabs_); + mock_reporting_delegate.EnsureNoUnexpectedCalls(); + + // Foreground tab paints then finishes loading. + Tick(); // 1ms. + GenerateRenderWidgetHostDidUpdateBackingStore(0); + mock_reporting_delegate.EnsureNoUnexpectedCalls(); + Tick(); // 2ms. + GenerateLoadStop(0); + mock_reporting_delegate.EnsureNoUnexpectedCalls(); + + // First background tab starts loading, paints, then finishes loading. + Tick(); // 3ms. + GenerateLoadStart(1); + mock_reporting_delegate.EnsureNoUnexpectedCalls(); + Tick(); // 4ms. + GenerateRenderWidgetHostDidUpdateBackingStore(1); + mock_reporting_delegate.EnsureNoUnexpectedCalls(); + Tick(); // 5ms. + GenerateLoadStop(1); + mock_reporting_delegate.EnsureNoUnexpectedCalls(); + + // Second background tab starts loading, finishes loading, but never paints. + Tick(); // 6ms. + GenerateLoadStart(2); + mock_reporting_delegate.EnsureNoUnexpectedCalls(); + + Tick(); // 7ms. + GenerateLoadStop(2); + mock_reporting_delegate.ExpectReportTabLoaderStatsCalled(3, 3, 2, 1, 7, 1); + mock_reporting_delegate.ExpectReportStatsCollectorDeathCalled(); + mock_reporting_delegate.EnsureNoUnexpectedCalls(); +} + +TEST_F(SessionRestoreStatsCollectorTest, MultipleTabsLoadSimultaneously) { + MockStatsReportingDelegate mock_reporting_delegate; + passthrough_reporting_delegate_->set_reporting_delegate( + &mock_reporting_delegate); + + CreateRestoredTab(true); + CreateRestoredTab(false); + CreateRestoredTab(false); + stats_collector_->TrackTabs(restored_tabs_); + mock_reporting_delegate.EnsureNoUnexpectedCalls(); + + // Foreground tab paints then finishes loading. + Tick(); // 1ms. + GenerateRenderWidgetHostDidUpdateBackingStore(0); + mock_reporting_delegate.EnsureNoUnexpectedCalls(); + Tick(); // 2ms. + GenerateLoadStop(0); + mock_reporting_delegate.EnsureNoUnexpectedCalls(); + + // Both background tabs start loading at the same time. The first one paints + // before finishing loading, the second one paints after finishing loading + // (the stats collector never sees the paint event). + Tick(); // 3ms. + GenerateLoadStart(1); + mock_reporting_delegate.EnsureNoUnexpectedCalls(); + GenerateLoadStart(2); + mock_reporting_delegate.EnsureNoUnexpectedCalls(); + Tick(); // 4ms. + GenerateRenderWidgetHostDidUpdateBackingStore(1); + mock_reporting_delegate.EnsureNoUnexpectedCalls(); + Tick(); // 5ms. + GenerateLoadStop(1); + mock_reporting_delegate.EnsureNoUnexpectedCalls(); + Tick(); // 6ms. + GenerateLoadStop(2); + mock_reporting_delegate.ExpectReportTabLoaderStatsCalled(3, 3, 2, 1, 6, 2); + mock_reporting_delegate.ExpectReportStatsCollectorDeathCalled(); + mock_reporting_delegate.EnsureNoUnexpectedCalls(); +} + +TEST_F(SessionRestoreStatsCollectorTest, DeferredTabs) { + MockStatsReportingDelegate mock_reporting_delegate; + passthrough_reporting_delegate_->set_reporting_delegate( + &mock_reporting_delegate); + + CreateRestoredTab(true); + CreateRestoredTab(false); + stats_collector_->TrackTabs(restored_tabs_); + mock_reporting_delegate.EnsureNoUnexpectedCalls(); + + // Foreground tab paints, then the background tab is deferred. + Tick(); // 1ms. + GenerateRenderWidgetHostDidUpdateBackingStore(0); + mock_reporting_delegate.EnsureNoUnexpectedCalls(); + DeferTab(1); + mock_reporting_delegate.ExpectReportTabDeferredCalled(); + mock_reporting_delegate.EnsureNoUnexpectedCalls(); + + // Foreground tab finishes loading and stats get reported. + Tick(); // 2ms. + GenerateLoadStop(0); + mock_reporting_delegate.ExpectReportTabLoaderStatsCalled(2, 1, 2, 1, 2, 1); + mock_reporting_delegate.EnsureNoUnexpectedCalls(); + + // Background tab starts loading, paints and stops loading. This fires off a + // deferred tab loaded notification. + Tick(); // 3ms. + GenerateLoadStart(1); + mock_reporting_delegate.EnsureNoUnexpectedCalls(); + Tick(); // 4ms. + GenerateRenderWidgetHostDidUpdateBackingStore(1); + mock_reporting_delegate.EnsureNoUnexpectedCalls(); + Tick(); // 5ms. + GenerateLoadStop(1); + mock_reporting_delegate.ExpectReportDeferredTabLoadedCalled(); + mock_reporting_delegate.ExpectReportStatsCollectorDeathCalled(); + mock_reporting_delegate.EnsureNoUnexpectedCalls(); +} + +TEST_F(SessionRestoreStatsCollectorTest, FocusSwitchNoForegroundPaintOrLoad) { + MockStatsReportingDelegate mock_reporting_delegate; + passthrough_reporting_delegate_->set_reporting_delegate( + &mock_reporting_delegate); + + CreateRestoredTab(true); + stats_collector_->TrackTabs(restored_tabs_); + mock_reporting_delegate.EnsureNoUnexpectedCalls(); + + // Create another tab and make it the foreground tab. This tab is not actually + // being tracked by the SessionRestoreStatsCollector, but its paint events + // will be observed. + CreateRestoredTab(false); + Hide(0); + Show(1); + + // Load and paint the restored tab (now the background tab). Don't expect + // any calls to the mock as a visible tab paint has not yet been observed. + Tick(); // 1ms. + GenerateRenderWidgetHostDidUpdateBackingStore(0); + mock_reporting_delegate.EnsureNoUnexpectedCalls(); + Tick(); // 2ms. + GenerateLoadStop(0); + mock_reporting_delegate.EnsureNoUnexpectedCalls(); + + // Mark the new foreground tab as having painted. This should cause the + // stats to be emitted, but with empty foreground paint and load values. + Tick(); // 3ms. + GenerateRenderWidgetHostDidUpdateBackingStore(1); + mock_reporting_delegate.ExpectReportTabLoaderStatsCalled(1, 1, 0, 0, 2, 1); + mock_reporting_delegate.ExpectReportStatsCollectorDeathCalled(); + mock_reporting_delegate.EnsureNoUnexpectedCalls(); +} + +TEST_F(SessionRestoreStatsCollectorTest, FocusSwitchNoForegroundPaint) { + MockStatsReportingDelegate mock_reporting_delegate; + passthrough_reporting_delegate_->set_reporting_delegate( + &mock_reporting_delegate); + + CreateRestoredTab(true); + stats_collector_->TrackTabs(restored_tabs_); + mock_reporting_delegate.EnsureNoUnexpectedCalls(); + + // Load the foreground tab. + Tick(); // 1ms. + GenerateLoadStop(0); + mock_reporting_delegate.EnsureNoUnexpectedCalls(); + + // Create another tab and make it the foreground tab. This tab is not actually + // being tracked by the SessionRestoreStatsCollector, but its paint events + // will still be observed. + CreateRestoredTab(false); + Hide(0); + Show(1); + + // Load and paint the restored tab (now the background tab). Don't expect + // any calls to the mock as a visible tab paint has not yet been observed. + Tick(); // 2ms. + GenerateRenderWidgetHostDidUpdateBackingStore(0); + mock_reporting_delegate.EnsureNoUnexpectedCalls(); + + // Mark the new foreground tab as having painted. This should cause the + // stats to be emitted, but with an empty foreground paint value. + Tick(); // 3ms. + GenerateRenderWidgetHostDidUpdateBackingStore(1); + mock_reporting_delegate.ExpectReportTabLoaderStatsCalled(1, 1, 1, 0, 1, 1); + mock_reporting_delegate.ExpectReportStatsCollectorDeathCalled(); + mock_reporting_delegate.EnsureNoUnexpectedCalls(); +} + +TEST_F(SessionRestoreStatsCollectorTest, LoadingTabDestroyedBeforePaint) { + MockStatsReportingDelegate mock_reporting_delegate; + passthrough_reporting_delegate_->set_reporting_delegate( + &mock_reporting_delegate); + + CreateRestoredTab(true); + stats_collector_->TrackTabs(restored_tabs_); + mock_reporting_delegate.EnsureNoUnexpectedCalls(); + + // Destroy the tab. Expect all timings to be zero. + GenerateWebContentsDestroyed(0); + mock_reporting_delegate.ExpectReportTabLoaderStatsCalled(1, 0, 0, 0, 0, 1); + mock_reporting_delegate.ExpectReportStatsCollectorDeathCalled(); + mock_reporting_delegate.EnsureNoUnexpectedCalls(); +} + +TEST_F(SessionRestoreStatsCollectorTest, LoadingTabDestroyedAfterPaint) { + MockStatsReportingDelegate mock_reporting_delegate; + passthrough_reporting_delegate_->set_reporting_delegate( + &mock_reporting_delegate); + + CreateRestoredTab(true); + stats_collector_->TrackTabs(restored_tabs_); + mock_reporting_delegate.EnsureNoUnexpectedCalls(); + + Tick(); // 1 ms. + GenerateRenderWidgetHostDidUpdateBackingStore(0); + mock_reporting_delegate.EnsureNoUnexpectedCalls(); + + // Destroy the tab. Expect both load timings to be zero. + GenerateWebContentsDestroyed(0); + mock_reporting_delegate.ExpectReportTabLoaderStatsCalled(1, 0, 0, 1, 0, 1); + mock_reporting_delegate.ExpectReportStatsCollectorDeathCalled(); + mock_reporting_delegate.EnsureNoUnexpectedCalls(); +} + +TEST_F(SessionRestoreStatsCollectorTest, BrowseAwayBeforePaint) { + MockStatsReportingDelegate mock_reporting_delegate; + passthrough_reporting_delegate_->set_reporting_delegate( + &mock_reporting_delegate); + + CreateRestoredTab(true); + stats_collector_->TrackTabs(restored_tabs_); + mock_reporting_delegate.EnsureNoUnexpectedCalls(); + + // Load the tab. + Tick(); // 1 ms. + GenerateLoadStop(0); + mock_reporting_delegate.EnsureNoUnexpectedCalls(); + + // Reload the tab. Expect the paint timing to be zero. + Tick(); // 2 ms. + GenerateLoadStart(0); + mock_reporting_delegate.ExpectReportTabLoaderStatsCalled(1, 1, 1, 0, 1, 1); + mock_reporting_delegate.ExpectReportStatsCollectorDeathCalled(); + mock_reporting_delegate.EnsureNoUnexpectedCalls(); +} + +TEST_F(SessionRestoreStatsCollectorTest, DiscardDeferredTabs) { + MockStatsReportingDelegate mock_reporting_delegate; + passthrough_reporting_delegate_->set_reporting_delegate( + &mock_reporting_delegate); + + CreateRestoredTab(true); + CreateRestoredTab(false); + stats_collector_->TrackTabs(restored_tabs_); + mock_reporting_delegate.EnsureNoUnexpectedCalls(); + + // Defer the background tab. + Tick(); // 1 ms. + DeferTab(1); + mock_reporting_delegate.ExpectReportTabDeferredCalled(); + mock_reporting_delegate.EnsureNoUnexpectedCalls(); + + // Discard the foreground tab. The stats tab loader stats should be reported + // with all zero timings. + Tick(); // 2 ms. + GenerateWebContentsDestroyed(0); + mock_reporting_delegate.ExpectReportTabLoaderStatsCalled(2, 0, 0, 0, 0, 1); + mock_reporting_delegate.EnsureNoUnexpectedCalls(); + + // Destroy the background tab. The collector should release itself. + Tick(); // 3 ms. + GenerateWebContentsDestroyed(1); + mock_reporting_delegate.ExpectReportStatsCollectorDeathCalled(); + mock_reporting_delegate.EnsureNoUnexpectedCalls(); +} diff --git a/chrome/browser/sessions/tab_loader.cc b/chrome/browser/sessions/tab_loader.cc index 98226b7..0bff726 100644 --- a/chrome/browser/sessions/tab_loader.cc +++ b/chrome/browser/sessions/tab_loader.cc @@ -9,6 +9,7 @@ #include "base/metrics/histogram.h" #include "base/strings/stringprintf.h" +#include "chrome/browser/sessions/session_restore_stats_collector.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_finder.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" @@ -49,13 +50,17 @@ void TabLoader::Observe(int type, } void TabLoader::SetTabLoadingEnabled(bool enable_tab_loading) { + // TODO(chrisha): Make the SessionRestoreStatsCollector aware that tab loading + // was explicitly stopped or restarted. This can make be used to invalidate + // various metrics. if (enable_tab_loading == loading_enabled_) return; loading_enabled_ = enable_tab_loading; - if (loading_enabled_) + if (loading_enabled_) { LoadNextTab(); - else + } else { force_load_timer_.Stop(); + } } // static @@ -64,6 +69,7 @@ void TabLoader::RestoreTabs(const std::vector<RestoredTab>& tabs, if (!shared_tab_loader_) shared_tab_loader_ = new TabLoader(restore_started); + shared_tab_loader_->stats_collector_->TrackTabs(tabs); shared_tab_loader_->StartLoading(tabs); } @@ -73,6 +79,10 @@ TabLoader::TabLoader(base::TimeTicks restore_started) force_load_delay_multiplier_(1), loading_enabled_(true), restore_started_(restore_started) { + stats_collector_ = new SessionRestoreStatsCollector( + restore_started, + make_scoped_ptr( + new SessionRestoreStatsCollector::UmaStatsReportingDelegate())); shared_tab_loader_ = this; this_retainer_ = this; } @@ -221,9 +231,13 @@ void TabLoader::OnMemoryPressure( // Stop the timer and suppress any tab loads while we clean the list. SetTabLoadingEnabled(false); while (!tabs_to_load_.empty()) { - NavigationController* controller = tabs_to_load_.front(); + NavigationController* tab = tabs_to_load_.front(); tabs_to_load_.pop_front(); - RemoveTab(controller); + RemoveTab(tab); + + // Notify the stats collector that a tab's loading has been deferred due to + // memory pressure. + stats_collector_->DeferTab(tab); } // By calling |LoadNextTab| explicitly, we make sure that the // |NOTIFICATION_SESSION_RESTORE_DONE| event gets sent. diff --git a/chrome/browser/sessions/tab_loader.h b/chrome/browser/sessions/tab_loader.h index d40987d..32714e8 100644 --- a/chrome/browser/sessions/tab_loader.h +++ b/chrome/browser/sessions/tab_loader.h @@ -21,6 +21,8 @@ class NavigationController; class RenderWidgetHost; } +class SessionRestoreStatsCollector; + // TabLoader is responsible for loading tabs after session restore has finished // creating all the tabs. Tabs are loaded after a previously tab finishes // loading or a timeout is reached. If the timeout is reached before a tab @@ -132,6 +134,11 @@ class TabLoader : public content::NotificationObserver, // SessionRestoreImpls reference it. scoped_refptr<TabLoader> this_retainer_; + // The SessionRestoreStatsCollector associated with this TabLoader. This is + // explicitly referenced so that it can be notified of deferred tab loads due + // to memory pressure. + scoped_refptr<SessionRestoreStatsCollector> stats_collector_; + static TabLoader* shared_tab_loader_; DISALLOW_COPY_AND_ASSIGN(TabLoader); diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi index 95bc6e8..c8ed6ee 100644 --- a/chrome/chrome_tests_unit.gypi +++ b/chrome/chrome_tests_unit.gypi @@ -1095,6 +1095,7 @@ 'browser/captive_portal/captive_portal_tab_reloader_unittest.cc', ], 'chrome_unit_tests_session_service_sources': [ + 'browser/sessions/session_restore_stats_collector_unittest.cc', 'browser/sessions/session_service_unittest.cc', 'browser/ui/startup/session_crashed_infobar_delegate_unittest.cc', ], diff --git a/content/test/test_render_view_host.cc b/content/test/test_render_view_host.cc index 5e222d1..3a1d6c0 100644 --- a/content/test/test_render_view_host.cc +++ b/content/test/test_render_view_host.cc @@ -61,7 +61,7 @@ TestRenderWidgetHostView::~TestRenderWidgetHostView() { } RenderWidgetHost* TestRenderWidgetHostView::GetRenderWidgetHost() const { - return NULL; + return rwh_; } gfx::Vector2dF TestRenderWidgetHostView::GetLastScrollOffset() const { diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml index 5e2dec2..f5a2958 100644 --- a/tools/metrics/histograms/histograms.xml +++ b/tools/metrics/histograms/histograms.xml @@ -37533,6 +37533,15 @@ http://cs/file:chrome/histograms.xml - but prefer this file for new entries. <summary>How did the user interact with the SessionCrashed Bubble?</summary> </histogram> +<histogram name="SessionRestore.Actions" enum="SessionRestoreActions"> + <owner>chrisha@chromium.org</owner> + <summary> + The actions that have occurred in a session restore timeline. These are to + be interpreted as raw event counts. Tabs are almost certainly deferred due + to the existence memory pressure, but this may not always be the case. + </summary> +</histogram> + <histogram name="SessionRestore.AllTabsLoaded" units="milliseconds"> <owner>jeremy@chromium.org</owner> <owner>sky@chromium.org</owner> @@ -37551,7 +37560,8 @@ http://cs/file:chrome/histograms.xml - but prefer this file for new entries. <histogram name="SessionRestore.FirstTabPainted" units="milliseconds"> <obsolete> - Deprecated 2014-10 in favor of SessionRestore.ForegroundTabFirstPaint. + Deprecated 2014-10 in favor of SessionRestore.ForegroundTabFirstPaint and + ultimately SessionRestore.ForegroundTabFirstPaint3. </obsolete> <owner>jeremy@chromium.org</owner> <owner>sky@chromium.org</owner> @@ -37570,11 +37580,25 @@ http://cs/file:chrome/histograms.xml - but prefer this file for new entries. <owner>jeremy@chromium.org</owner> <owner>sky@chromium.org</owner> <summary> - Deprecated 2015-03-13 in favor of SessionRestore.ForegroundTabFirstPaint2. + Deprecated 2015-03-13 in favor of SessionRestore.ForegroundTabFirstPaint2 + and ultimately SessionRestore.ForegroundTabFirstPaint3. </summary> </histogram> <histogram name="SessionRestore.ForegroundTabFirstPaint2" units="milliseconds"> + <obsolete> + Deprecated 2015-05 in favor of SessionRestore.ForegroundTabFirstPaint3. + </obsolete> + <owner>jeremy@chromium.org</owner> + <owner>sky@chromium.org</owner> + <summary> + The time from SessionRestore start until a visible tab's first paint. This + metric only records paints that have occurred after a tab has loaded. + </summary> +</histogram> + +<histogram name="SessionRestore.ForegroundTabFirstPaint3" units="milliseconds"> + <owner>chrisha@chromium.org</owner> <owner>jeremy@chromium.org</owner> <owner>sky@chromium.org</owner> <summary> @@ -37663,6 +37687,14 @@ http://cs/file:chrome/histograms.xml - but prefer this file for new entries. </details> </histogram> +<histogram name="SessionRestore.TabActions" enum="SessionRestoreTabActions"> + <owner>chrisha@chromium.org</owner> + <summary> + A breakdown of key events that occur to individual tabs as they are + processed by an ongoing session restore. + </summary> +</histogram> + <histogram name="SessionRestore.TabClosedLongPeriod"> <owner>jeremy@chromium.org</owner> <owner>sky@chromium.org</owner> @@ -37680,6 +37712,13 @@ http://cs/file:chrome/histograms.xml - but prefer this file for new entries. </summary> </histogram> +<histogram name="SessionRestore.TabCount" units="tabs"> + <owner>chrisha@chromium.org</owner> + <summary> + The number of tabs involved in a single session restore event. + </summary> +</histogram> + <histogram name="SessionStorageDatabase.Open" enum="SessionStorageDatabaseOpen"> <owner>michaeln@chromium.org</owner> <summary> @@ -65420,6 +65459,18 @@ To add a new entry, add it with any value and run test to compute valid value. <int value="7" label="The bar with UMA opt-in option was shown."/> </enum> +<enum name="SessionRestoreActions" type="int"> + <int value="0" label="A session restore was started"/> + <int value="1" label="A session restore deferred one or more tabs"/> +</enum> + +<enum name="SessionRestoreTabActions" type="int"> + <int value="0" label="A tab was created"/> + <int value="1" label="A tab's content was automatically loaded"/> + <int value="2" label="The loading of a tab's content was deferred"/> + <int value="3" label="A deferred tab's content was loaded via user action"/> +</enum> + <enum name="SessionStartupPref" type="int"> <int value="0" label="Open home page (unused)"/> <int value="1" label="Continue from last opened pages"/> @@ -72834,6 +72885,7 @@ To add a new entry, add it with any value and run test to compute valid value. <affected-histogram name="SessionRestore.ForegroundTabFirstLoaded"/> <affected-histogram name="SessionRestore.ForegroundTabFirstPaint"/> <affected-histogram name="SessionRestore.ForegroundTabFirstPaint2"/> + <affected-histogram name="SessionRestore.ForegroundTabFirstPaint3"/> </histogram_suffixes> <histogram_suffixes name="ShillWiFiRememberedNetworkSecurityMode" separator="."> |