Commit 93cd24be authored by Chris Hamilton's avatar Chris Hamilton Committed by Commit Bot

Update SessionRestoreStatsCollector to use TabLoadTracker.

This is necessary in order for it to work when the PageAlmostIdle
feature is enabled.

BUG=829933

Change-Id: Ic3158a984713ceef849788eaa758ecaa255eba5a
Reviewed-on: https://chromium-review.googlesource.com/1097421Reviewed-by: default avatarScott Violet <sky@chromium.org>
Reviewed-by: default avatarFrançois Doray <fdoray@chromium.org>
Commit-Queue: Chris Hamilton <chrisha@chromium.org>
Cr-Commit-Position: refs/heads/master@{#567484}
parent 7a8d2a31
......@@ -113,6 +113,10 @@ class SessionRestoreObserverTest : public ChromeRenderViewHostTestHarness {
void LoadWebContents(content::WebContents* contents) {
WebContentsTester::For(contents)->NavigateAndCommit(GURL(kDefaultUrl));
WebContentsTester::For(contents)->TestSetIsLoading(false);
// Transition through LOADING to LOADED in order to keep the
// SessionRestoreStatsCollector state machine happy.
TabLoadTracker::Get()->TransitionStateForTesting(contents,
TabLoadTracker::LOADING);
TabLoadTracker::Get()->TransitionStateForTesting(contents,
TabLoadTracker::LOADED);
mock_observer_.OnDidRestoreTab(contents);
......
......@@ -27,6 +27,7 @@ using content::RenderWidgetHost;
using content::RenderWidgetHostView;
using content::Source;
using content::WebContents;
using resource_coordinator::TabLoadTracker;
// The enumeration values stored in the "SessionRestore.Actions" histogram.
enum SessionRestoreActionsUma {
......@@ -124,9 +125,11 @@ SessionRestoreStatsCollector::SessionRestoreStatsCollector(
tick_clock_(new base::DefaultTickClock()),
reporting_delegate_(std::move(reporting_delegate)) {
this_retainer_ = this;
TabLoadTracker::Get()->AddObserver(this);
}
SessionRestoreStatsCollector::~SessionRestoreStatsCollector() {
TabLoadTracker::Get()->RemoveObserver(this);
}
void SessionRestoreStatsCollector::TrackTabs(
......@@ -167,9 +170,11 @@ void SessionRestoreStatsCollector::TrackTabs(
TabState* tab_state = RegisterForNotifications(controller);
// The tab might already be loading if it is active in a visible window.
if (!controller->NeedsReload())
if (TabLoadTracker::Get()->GetLoadingState(tab.contents()) ==
TabLoadTracker::LOADING) {
MarkTabAsLoading(tab_state);
}
}
}
void SessionRestoreStatsCollector::DeferTab(NavigationController* tab) {
......@@ -208,80 +213,22 @@ void SessionRestoreStatsCollector::Observe(
int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
switch (type) {
case content::NOTIFICATION_LOAD_START: {
// This occurs when a tab has started to load. This can be because of
// the tab loader (only for non-deferred tabs) or because the user clicked
// on the tab.
NavigationController* tab = Source<NavigationController>(source).ptr();
TabState* tab_state = GetTabState(tab);
MarkTabAsLoading(tab_state);
break;
}
case content::NOTIFICATION_WEB_CONTENTS_DESTROYED: {
// This happens when a tab has been closed. A tab can be in any state
// when this occurs. Simply stop tracking the tab.
WebContents* web_contents = Source<WebContents>(source).ptr();
NavigationController* tab = &web_contents->GetController();
RemoveTab(tab);
break;
}
case content::NOTIFICATION_LOAD_STOP: {
// This occurs to loading tabs when they have finished loading. The tab
// may or may not already have painted at this point.
// Update the tab state and any global state as necessary.
NavigationController* tab = Source<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_ && IsShowing(tab_state->controller)) {
got_first_foreground_load_ = true;
DCHECK(!done_tracking_non_deferred_tabs_);
tab_loader_stats_.foreground_tab_first_loaded = time_to_load;
if (type !=
content::NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_VISUAL_PROPERTIES) {
return;
}
// 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_VISUAL_PROPERTIES: {
// This notification is across all tabs in the browser so notifications
// will arrive for tabs that the collector is not explicitly tracking.
// Only process this event if first paint hasn't been seen and this is a
// paint of a visible tab.
RenderWidgetHost* render_widget_host =
Source<RenderWidgetHost>(source).ptr();
if (!got_first_paint_ && render_widget_host->GetView() &&
render_widget_host->GetView()->IsShowing()) {
RenderWidgetHost* render_widget_host = Source<RenderWidgetHost>(source).ptr();
if (got_first_paint_ || !render_widget_host->GetView() ||
!render_widget_host->GetView()->IsShowing()) {
return;
}
got_first_paint_ = true;
TabState* tab_state = GetTabState(render_widget_host);
if (tab_state) {
......@@ -292,8 +239,7 @@ void SessionRestoreStatsCollector::Observe(
// happen because the user opened a different tab or restored tabs
// to an already existing browser and an existing tab was in the
// foreground.
base::TimeDelta time_to_paint =
tick_clock_->NowTicks() - restore_started_;
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;
}
......@@ -302,8 +248,7 @@ void SessionRestoreStatsCollector::Observe(
// mechanism is no longer needed.
registrar_.Remove(
this,
content::
NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_VISUAL_PROPERTIES,
content::NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_VISUAL_PROPERTIES,
content::NotificationService::AllSources());
// Remove any tabs that have loaded. These were only being kept around
......@@ -316,32 +261,67 @@ void SessionRestoreStatsCollector::Observe(
}
for (auto* tab : loaded_tabs)
RemoveTab(tab);
}
break;
}
default:
NOTREACHED() << "Unknown notification received:" << type;
break;
ReleaseIfDoneTracking();
}
void SessionRestoreStatsCollector::OnLoadingStateChange(
WebContents* contents,
LoadingState old_loading_state,
LoadingState new_loading_state) {
// This notification is across all tabs in the browser so notifications
// will arrive for tabs that the collector is not explicitly tracking.
NavigationController* tab = &contents->GetController();
TabState* tab_state = GetTabState(tab);
if (!tab_state)
return;
switch (new_loading_state) {
case TabLoadTracker::LOADING: {
// 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.
MarkTabAsLoading(tab_state);
} break;
// A tab that transitions here means that loading was aborted or errored
// out. Either way, we consider it "loaded" from our point of view.
case TabLoadTracker::UNLOADED:
// A tab that completes loading successfully will transition to this state.
case TabLoadTracker::LOADED: {
MarkTabAsLoaded(tab_state);
} break;
case TabLoadTracker::LOADING_STATE_MAX: {
NOTREACHED();
} break;
}
ReleaseIfDoneTracking();
}
void SessionRestoreStatsCollector::RemoveTab(NavigationController* tab) {
// Stop observing this tab.
registrar_.Remove(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
Source<WebContents>(tab->GetWebContents()));
registrar_.Remove(this, content::NOTIFICATION_LOAD_STOP,
Source<NavigationController>(tab));
registrar_.Remove(this, content::NOTIFICATION_LOAD_START,
Source<NavigationController>(tab));
void SessionRestoreStatsCollector::OnStopTracking(WebContents* contents,
LoadingState loading_state) {
// This notification is across all tabs in the browser so notifications
// will arrive for tabs that the collector is not explicitly tracking.
// This happens when a tab has been closed. A tab can be in any state
// when this occurs. Simply stop tracking the tab.
NavigationController* tab = &contents->GetController();
RemoveTab(tab);
ReleaseIfDoneTracking();
}
void SessionRestoreStatsCollector::RemoveTab(NavigationController* tab) {
// This can be called for any tab in the browser, not just those that are
// being tracked.
auto tab_it = tabs_tracked_.find(tab);
DCHECK(tab_it != tabs_tracked_.end());
if (tab_it == tabs_tracked_.end())
return;
TabState& tab_state = tab_it->second;
// If this tab was waiting for a NOTIFICATION_LOAD_STOP event then update
// the loading counts.
// If this tab was in the process of loading then update the loading counts.
if (tab_state.loading_state == TAB_IS_LOADING) {
DCHECK_LT(0u, loading_tab_count_);
--loading_tab_count_;
......@@ -350,7 +330,7 @@ void SessionRestoreStatsCollector::RemoveTab(NavigationController* tab) {
// 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
// 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_;
......@@ -364,7 +344,7 @@ void SessionRestoreStatsCollector::RemoveTab(NavigationController* tab) {
// 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
// |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;
......@@ -378,12 +358,6 @@ void SessionRestoreStatsCollector::RemoveTab(NavigationController* tab) {
SessionRestoreStatsCollector::TabState*
SessionRestoreStatsCollector::RegisterForNotifications(
NavigationController* tab) {
registrar_.Add(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
Source<WebContents>(tab->GetWebContents()));
registrar_.Add(this, content::NOTIFICATION_LOAD_STOP,
Source<NavigationController>(tab));
registrar_.Add(this, content::NOTIFICATION_LOAD_START,
Source<NavigationController>(tab));
auto result = tabs_tracked_.insert(std::make_pair(tab, TabState(tab)));
DCHECK(result.second);
TabState* tab_state = &result.first->second;
......@@ -431,6 +405,44 @@ void SessionRestoreStatsCollector::MarkTabAsLoading(TabState* tab_state) {
++tab_loader_stats_.tabs_load_started;
}
void SessionRestoreStatsCollector::MarkTabAsLoaded(TabState* tab_state) {
// Note that the tab may or may not already have painted at this point.
tab_state->loading_state = TAB_IS_LOADED;
DCHECK_LT(0u, loading_tab_count_);
--loading_tab_count_;
if (!tab_state->is_deferred) {
DCHECK_LT(0u, waiting_for_load_tab_count_);
--waiting_for_load_tab_count_;
}
if (tab_state->is_deferred) {
reporting_delegate_->ReportDeferredTabLoaded();
} else {
DCHECK(!done_tracking_non_deferred_tabs_);
++tab_loader_stats_.tabs_loaded;
}
// Update statistics for foreground tabs.
base::TimeDelta time_to_load = tick_clock_->NowTicks() - restore_started_;
if (!got_first_foreground_load_ && IsShowing(tab_state->controller)) {
got_first_foreground_load_ = true;
DCHECK(!done_tracking_non_deferred_tabs_);
tab_loader_stats_.foreground_tab_first_loaded = time_to_load;
}
// Update statistics for all tabs, if this wasn't a deferred tab. This is
// done here and not in ReleaseIfDoneTracking because it is possible to
// wait for a paint long after all loads have completed.
if (!done_tracking_non_deferred_tabs_ && !tab_state->is_deferred)
tab_loader_stats_.non_deferred_tabs_loaded = time_to_load;
// By default tabs transition to being tracked for paint events after the
// load event has been seen. However, if the first paint event has already
// been seen then this is not necessary and the tab can be removed.
if (got_first_paint_)
RemoveTab(tab_state->controller);
}
void SessionRestoreStatsCollector::ReleaseIfDoneTracking() {
// If non-deferred tabs are no longer being tracked then report tab loader
// statistics.
......
......@@ -12,6 +12,7 @@
#include "base/callback_list.h"
#include "base/macros.h"
#include "base/time/tick_clock.h"
#include "chrome/browser/resource_coordinator/tab_load_tracker.h"
#include "chrome/browser/sessions/session_restore.h"
#include "chrome/browser/sessions/session_restore_delegate.h"
#include "content/public/browser/notification_observer.h"
......@@ -47,8 +48,9 @@ class RenderWidgetHost;
// 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 base::RefCounted<SessionRestoreStatsCollector>,
public content::NotificationObserver,
public resource_coordinator::TabLoadTracker::Observer {
public:
// Houses all of the statistics gathered by the SessionRestoreStatsCollector
// while the underlying TabLoader is active. These statistics are all reported
......@@ -160,19 +162,24 @@ class SessionRestoreStatsCollector
~SessionRestoreStatsCollector() override;
// NotificationObserver method. This is the workhorse of the class and drives
// all state transitions.
// NotificationObserver method. Used for detecting first paint.
void Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) override;
// 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.
// resource_coordinator::TabLoadTracker::Observer implementation:
void OnLoadingStateChange(content::WebContents* contents,
LoadingState old_loading_state,
LoadingState new_loading_state) override;
void OnStopTracking(content::WebContents* contents,
LoadingState loading_state) override;
// Called when a tab is no longer tracked. 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 and inserts the tab into
// to tabs_tracked_ map. Return a pointer to the newly created TabState.
// the |tabs_tracked_| map. Return a pointer to the newly created TabState.
TabState* RegisterForNotifications(content::NavigationController* tab);
// Returns the tab state, nullptr if not found.
......@@ -182,6 +189,9 @@ class SessionRestoreStatsCollector
// Marks a tab as loading.
void MarkTabAsLoading(TabState* tab_state);
// Marks a tab as loaded.
void MarkTabAsLoaded(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();
......
......@@ -10,6 +10,7 @@
#include "base/macros.h"
#include "base/test/simple_test_tick_clock.h"
#include "chrome/browser/resource_coordinator/tab_helper.h"
#include "chrome/test/base/testing_profile.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_entry.h"
......@@ -24,6 +25,9 @@
namespace {
using resource_coordinator::TabLoadTracker;
using resource_coordinator::ResourceCoordinatorTabHelper;
using TabLoaderStats = SessionRestoreStatsCollector::TabLoaderStats;
using StatsReportingDelegate =
SessionRestoreStatsCollector::StatsReportingDelegate;
......@@ -277,36 +281,32 @@ class SessionRestoreStatsCollectorTest : public testing::Test {
// Create a last active time in the past.
contents->SetLastActiveTime(base::TimeTicks::Now() -
base::TimeDelta::FromMinutes(1));
// TabLoadTracker needs the resource_coordinator WebContentsData to be
// initialized.
ResourceCoordinatorTabHelper::CreateForWebContents(contents);
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);
content::WebContents* contents = restored_tabs_[tab_index].contents();
TabLoadTracker::Get()->TransitionStateForTesting(contents,
TabLoadTracker::LOADING);
}
// Generates a load stop notification for the given tab.
void GenerateLoadStop(size_t tab_index) {
GenerateControllerNotification(tab_index, content::NOTIFICATION_LOAD_STOP);
content::WebContents* contents = restored_tabs_[tab_index].contents();
TabLoadTracker::Get()->TransitionStateForTesting(contents,
TabLoadTracker::LOADED);
}
// 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());
test_web_contents_factory_->DestroyWebContents(contents);
}
// Generates a paint notification for the given tab.
......
......@@ -69,8 +69,13 @@ class TabLoaderTest : public testing::Test {
}
void SimulateLoaded(size_t tab_index) {
TabLoadTracker::Get()->TransitionStateForTesting(
restored_tabs_[tab_index].contents(), TabLoadTracker::LOADED);
// Transition to a LOADED state. This has to pass through the LOADING state
// in order to satisfy the internal logic of SessionRestoreStatsCollector.
auto* contents = restored_tabs_[tab_index].contents();
auto* tracker = TabLoadTracker::Get();
if (tracker->GetLoadingState(contents) != TabLoadTracker::LOADING)
tracker->TransitionStateForTesting(contents, TabLoadTracker::LOADING);
tracker->TransitionStateForTesting(contents, TabLoadTracker::LOADED);
}
void SimulateLoadedAll() {
......
......@@ -36,6 +36,9 @@ class TestWebContentsFactory {
// Ownership remains with the TestWebContentsFactory.
WebContents* CreateWebContents(BrowserContext* context);
// Destroys the provided WebContents.
void DestroyWebContents(WebContents* contents);
private:
// The test factory (and friends) for creating test web contents.
std::unique_ptr<RenderViewHostTestEnabler> rvh_enabler_;
......
......@@ -36,4 +36,15 @@ WebContents* TestWebContentsFactory::CreateWebContents(
return web_contents_.back().get();
}
void TestWebContentsFactory::DestroyWebContents(WebContents* contents) {
auto it = web_contents_.begin();
for (; it != web_contents_.end(); ++it) {
if (it->get() == contents)
break;
}
if (it == web_contents_.end())
return;
web_contents_.erase(it);
}
} // namespace content
......@@ -79,6 +79,7 @@ MainThreadMetricsHelper::MainThreadMetricsHelper(
bool renderer_backgrounded)
: MetricsHelper(WebThreadType::kMainThread),
main_thread_scheduler_(main_thread_scheduler),
renderer_shutting_down_(false),
is_page_almost_idle_signal_enabled_(
::resource_coordinator::IsPageAlmostIdleSignalEnabled()),
main_thread_load_tracker_(
......@@ -134,6 +135,7 @@ void MainThreadMetricsHelper::OnRendererBackgrounded(base::TimeTicks now) {
}
void MainThreadMetricsHelper::OnRendererShutdown(base::TimeTicks now) {
renderer_shutting_down_ = true;
foreground_main_thread_load_tracker_.RecordIdle(now);
background_main_thread_load_tracker_.RecordIdle(now);
main_thread_load_tracker_.RecordIdle(now);
......@@ -491,6 +493,13 @@ void MainThreadMetricsHelper::ReportLowThreadLoadForPageAlmostIdleSignal(
if (!is_page_almost_idle_signal_enabled_)
return;
// Avoid sending IPCs when the renderer is shutting down as this wreaks havoc
// in test harnesses. These messages aren't needed in production code either
// as the endpoint receiving them dies shortly after and does nothing with
// them.
if (renderer_shutting_down_)
return;
static const int main_thread_task_load_low_threshold =
::resource_coordinator::GetMainThreadTaskLoadLowThreshold();
......
......@@ -55,6 +55,11 @@ class PLATFORM_EXPORT MainThreadMetricsHelper : public MetricsHelper {
MainThreadSchedulerImpl* main_thread_scheduler_; // NOT OWNED
// Set to true when OnRendererShutdown is called. Used to ensure that metrics
// that need to cross IPC boundaries aren't sent, as they cause additional
// useless tasks to be posted.
bool renderer_shutting_down_;
const bool is_page_almost_idle_signal_enabled_;
base::Optional<base::TimeTicks> last_reported_task_;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment