summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorchrisha <chrisha@chromium.org>2015-06-19 18:21:58 -0700
committerCommit bot <commit-bot@chromium.org>2015-06-20 01:22:22 +0000
commitf3c87f3238e503d995f3690d592d454715ff9cea (patch)
tree3a8f6c86e3fdb0b12aaf2be95dd60b1f386241ad
parent55c615dc327046f24354b815e1edc795ed52bccb (diff)
downloadchromium_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.cc8
-rw-r--r--chrome/browser/sessions/session_restore_delegate.cc30
-rw-r--r--chrome/browser/sessions/session_restore_stats_collector.cc565
-rw-r--r--chrome/browser/sessions/session_restore_stats_collector.h250
-rw-r--r--chrome/browser/sessions/session_restore_stats_collector_unittest.cc621
-rw-r--r--chrome/browser/sessions/tab_loader.cc22
-rw-r--r--chrome/browser/sessions/tab_loader.h7
-rw-r--r--chrome/chrome_tests_unit.gypi1
-rw-r--r--content/test/test_render_view_host.cc2
-rw-r--r--tools/metrics/histograms/histograms.xml56
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=".">