Commit 0199750b authored by Chris Hamilton's avatar Chris Hamilton Committed by Commit Bot

Create PageAlmostIdleDecorator.

This moves the implementation of PageAlmostIdle from
PageSignal(Generator|Receiver|Observer), to a new "decorator" that
runs directly in the graph.

BUG=931407

Change-Id: I1e8c86e34f4cdd224cb3b77c7545debcffc820a4
Reviewed-on: https://chromium-review.googlesource.com/c/1471750
Commit-Queue: Chris Hamilton <chrisha@chromium.org>
Reviewed-by: default avatarSigurður Ásgeirsson <siggi@chromium.org>
Reviewed-by: default avatarFrançois Doray <fdoray@chromium.org>
Cr-Commit-Position: refs/heads/master@{#635589}
parent 9daf386d
......@@ -1066,6 +1066,10 @@ jumbo_split_static_library("browser") {
"performance_manager/browser_child_process_watcher.h",
"performance_manager/chrome_content_browser_client_performance_manager_part.cc",
"performance_manager/chrome_content_browser_client_performance_manager_part.h",
"performance_manager/common/page_almost_idle_data.cc",
"performance_manager/common/page_almost_idle_data.h",
"performance_manager/decorators/page_almost_idle_decorator.cc",
"performance_manager/decorators/page_almost_idle_decorator.h",
"performance_manager/frame_resource_coordinator.cc",
"performance_manager/frame_resource_coordinator.h",
"performance_manager/graph/frame_node_impl.cc",
......
// Copyright 2019 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/performance_manager/common/page_almost_idle_data.h"
namespace performance_manager {
PageAlmostIdleData::PageAlmostIdleData() = default;
PageAlmostIdleData::~PageAlmostIdleData() = default;
} // namespace performance_manager
// Copyright 2019 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.
#ifndef CHROME_BROWSER_PERFORMANCE_MANAGER_COMMON_PAGE_ALMOST_IDLE_DATA_H_
#define CHROME_BROWSER_PERFORMANCE_MANAGER_COMMON_PAGE_ALMOST_IDLE_DATA_H_
#include "base/macros.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
namespace performance_manager {
namespace testing {
class PageAlmostIdleDecoratorTestUtils;
} // namespace testing
// Data storage for the PageAlmostIdleDecorator. An instance of this class is
// stored internally in each PageNodeImpl, but is only accessible to a
// PageAlmostIdleDecorator. An object of this type will come into existence as
// a page starts a main frame navigation, and it will be torn down once it has
// worked through the state machine and transitioned to "loaded and idle".
// TODO(chrisha): Fold this back into private impl details of
// PageAlmostIdleDecorator once the NodeAttachedData solution is in place.
class PageAlmostIdleData {
public:
PageAlmostIdleData();
~PageAlmostIdleData();
protected:
friend class PageAlmostIdleDecorator;
friend class testing::PageAlmostIdleDecoratorTestUtils;
// The state transitions for the PageAlmostIdle signal. In general a page
// transitions through these states from top to bottom.
enum class LoadIdleState {
// The initial state. Can only transition to kLoading from here.
kLoadingNotStarted,
// Loading has started. Almost idle signals are ignored in this state.
// Can transition to kLoadedNotIdling and kLoadedAndIdling from here.
kLoading,
// Loading has completed, but the page has not started idling. Can only
// transition to kLoadedAndIdling from here.
kLoadedNotIdling,
// Loading has completed, and the page is idling. Can transition to
// kLoadedNotIdling or kLoadedAndIdle from here.
kLoadedAndIdling,
// Loading has completed and the page has been idling for sufficiently long.
// This is the final state. Once this state has been reached a signal will
// be emitted and no further state transitions will be tracked. Committing a
// new non-same document navigation can start the cycle over again.
kLoadedAndIdle
};
// Marks the point in time when the DidStopLoading signal was received,
// transitioning to kLoadedAndNotIdling or kLoadedAndIdling. This is used as
// the basis for the kWaitingForIdleTimeout.
base::TimeTicks loading_stopped_;
// Marks the point in time when the last transition to kLoadedAndIdling
// occurred. Used for gating the transition to kLoadedAndIdle.
base::TimeTicks idling_started_;
// A one-shot timer used for transitioning between kLoadedAndIdling and
// kLoadedAndIdle.
base::OneShotTimer idling_timer_;
// Initially at kLoadingNotStarted. Transitions through the states via calls
// to UpdateLoadIdleState. Is reset to kLoadingNotStarted when a non-same
// document navigation is committed.
LoadIdleState load_idle_state_ = LoadIdleState::kLoadingNotStarted;
private:
DISALLOW_COPY_AND_ASSIGN(PageAlmostIdleData);
};
} // namespace performance_manager
#endif // CHROME_BROWSER_PERFORMANCE_MANAGER_COMMON_PAGE_ALMOST_IDLE_DATA_H_
// Copyright 2019 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/performance_manager/decorators/page_almost_idle_decorator.h"
#include <algorithm>
#include "chrome/browser/performance_manager/resource_coordinator_clock.h"
namespace performance_manager {
// static
constexpr base::TimeDelta PageAlmostIdleDecorator::kLoadedAndIdlingTimeout;
// static
constexpr base::TimeDelta PageAlmostIdleDecorator::kWaitingForIdleTimeout;
PageAlmostIdleDecorator::PageAlmostIdleDecorator() {
// Ensure the timeouts make sense relative to each other.
static_assert(kWaitingForIdleTimeout > kLoadedAndIdlingTimeout,
"timeouts must be well ordered");
}
PageAlmostIdleDecorator::~PageAlmostIdleDecorator() = default;
bool PageAlmostIdleDecorator::ShouldObserve(const NodeBase* node) {
switch (node->id().type) {
case resource_coordinator::CoordinationUnitType::kFrame:
case resource_coordinator::CoordinationUnitType::kPage:
case resource_coordinator::CoordinationUnitType::kProcess:
return true;
default:
return false;
}
NOTREACHED();
}
void PageAlmostIdleDecorator::OnFramePropertyChanged(
FrameNodeImpl* frame_node,
resource_coordinator::mojom::PropertyType property_type,
int64_t value) {
// Only the network idle state of a frame is of interest.
if (property_type ==
resource_coordinator::mojom::PropertyType::kNetworkAlmostIdle) {
UpdateLoadIdleStateFrame(frame_node);
}
}
void PageAlmostIdleDecorator::OnPagePropertyChanged(
PageNodeImpl* page_node,
resource_coordinator::mojom::PropertyType property_type,
int64_t value) {
if (property_type == resource_coordinator::mojom::PropertyType::kIsLoading)
UpdateLoadIdleStatePage(page_node);
}
void PageAlmostIdleDecorator::OnProcessPropertyChanged(
ProcessNodeImpl* process_node,
resource_coordinator::mojom::PropertyType property_type,
int64_t value) {
if (property_type ==
resource_coordinator::mojom::PropertyType::kMainThreadTaskLoadIsLow) {
UpdateLoadIdleStateProcess(process_node);
}
}
void PageAlmostIdleDecorator::OnPageEventReceived(
PageNodeImpl* page_node,
resource_coordinator::mojom::Event event) {
// Only the navigation committed event is of interest.
if (event != resource_coordinator::mojom::Event::kNavigationCommitted)
return;
// Reset the load-idle state associated with this page as a new navigation has
// started.
auto* data = GetOrCreateData(page_node);
data->load_idle_state_ = LoadIdleState::kLoadingNotStarted;
PageNodeImpl::PageAlmostIdleHelper::set_page_almost_idle(page_node, false);
UpdateLoadIdleStatePage(page_node);
}
void PageAlmostIdleDecorator::UpdateLoadIdleStateFrame(
FrameNodeImpl* frame_node) {
// Only main frames are relevant in the load idle state.
if (!frame_node->IsMainFrame())
return;
// Update the load idle state of the page associated with this frame.
auto* page_node = frame_node->GetPageNode();
if (!page_node)
return;
UpdateLoadIdleStatePage(page_node);
}
void PageAlmostIdleDecorator::UpdateLoadIdleStatePage(PageNodeImpl* page_node) {
// Once the cycle is complete state transitions are no longer tracked for this
// page. When this occurs the backing data store is deleted.
auto* data = GetData(page_node);
if (data == nullptr)
return;
// This is the terminal state, so should never occur.
DCHECK_NE(LoadIdleState::kLoadedAndIdle, data->load_idle_state_);
// Cancel any ongoing timers. A new timer will be set if necessary.
data->idling_timer_.Stop();
const base::TimeTicks now = ResourceCoordinatorClock::NowTicks();
// Determine if the overall timeout has fired.
if ((data->load_idle_state_ == LoadIdleState::kLoadedNotIdling ||
data->load_idle_state_ == LoadIdleState::kLoadedAndIdling) &&
(now - data->loading_stopped_) >= kWaitingForIdleTimeout) {
TransitionToLoadedAndIdle(page_node);
return;
}
// Otherwise do normal state transitions.
switch (data->load_idle_state_) {
case LoadIdleState::kLoadingNotStarted: {
if (!IsLoading(page_node))
return;
data->load_idle_state_ = LoadIdleState::kLoading;
return;
}
case LoadIdleState::kLoading: {
if (IsLoading(page_node))
return;
data->load_idle_state_ = LoadIdleState::kLoadedNotIdling;
data->loading_stopped_ = now;
// Let the kLoadedNotIdling state transition evaluate, allowing an
// effective transition directly from kLoading to kLoadedAndIdling.
FALLTHROUGH;
}
case LoadIdleState::kLoadedNotIdling: {
if (IsIdling(page_node)) {
data->load_idle_state_ = LoadIdleState::kLoadedAndIdling;
data->idling_started_ = now;
}
// Break out of the switch statement and set a timer to check for the
// next state transition.
break;
}
case LoadIdleState::kLoadedAndIdling: {
// If the page is not still idling then transition back a state.
if (!IsIdling(page_node)) {
data->load_idle_state_ = LoadIdleState::kLoadedNotIdling;
} else {
// Idling has been happening long enough so make the last state
// transition.
if (now - data->idling_started_ >= kLoadedAndIdlingTimeout) {
TransitionToLoadedAndIdle(page_node);
return;
}
}
// Break out of the switch statement and set a timer to check for the
// next state transition.
break;
}
// This should never occur.
case LoadIdleState::kLoadedAndIdle:
NOTREACHED();
}
// Getting here means a new timer needs to be set. Use the nearer of the two
// applicable timeouts.
base::TimeDelta timeout =
(data->loading_stopped_ + kWaitingForIdleTimeout) - now;
if (data->load_idle_state_ == LoadIdleState::kLoadedAndIdling) {
timeout = std::min(timeout,
(data->idling_started_ + kLoadedAndIdlingTimeout) - now);
}
// It's safe to use base::Unretained here because the graph owns the timer via
// PageNodeImpl, and all nodes are destroyed *before* this observer during
// tear down. By the time the observer is destroyed, the timer will have
// already been destroyed and the associated posted task canceled.
data->idling_timer_.Start(
FROM_HERE, timeout,
base::BindRepeating(&PageAlmostIdleDecorator::UpdateLoadIdleStatePage,
base::Unretained(this), page_node));
}
void PageAlmostIdleDecorator::UpdateLoadIdleStateProcess(
ProcessNodeImpl* process_node) {
for (auto* frame_node : process_node->GetFrameNodes())
UpdateLoadIdleStateFrame(frame_node);
}
void PageAlmostIdleDecorator::TransitionToLoadedAndIdle(
PageNodeImpl* page_node) {
auto* data = GetData(page_node);
data->load_idle_state_ = LoadIdleState::kLoadedAndIdle;
PageNodeImpl::PageAlmostIdleHelper::set_page_almost_idle(page_node, true);
// Destroy the metadata as there are no more transitions possible. The
// machinery will start up again if a navigation occurs.
PageNodeImpl::PageAlmostIdleHelper::DestroyData(page_node);
}
// static
PageAlmostIdleData* PageAlmostIdleDecorator::GetOrCreateData(
PageNodeImpl* page_node) {
return PageNodeImpl::PageAlmostIdleHelper::GetOrCreateData(page_node);
}
// static
PageAlmostIdleData* PageAlmostIdleDecorator::GetData(PageNodeImpl* page_node) {
return PageNodeImpl::PageAlmostIdleHelper::GetData(page_node);
}
// static
bool PageAlmostIdleDecorator::IsLoading(const PageNodeImpl* page_node) {
return page_node->GetPropertyOrDefault(
resource_coordinator::mojom::PropertyType::kIsLoading, 0u);
}
// static
bool PageAlmostIdleDecorator::IsIdling(const PageNodeImpl* page_node) {
// Get the Frame CU for the main frame associated with this page.
const FrameNodeImpl* main_frame_node = page_node->GetMainFrameNode();
if (!main_frame_node)
return false;
// Get the process CU associated with this main frame.
const auto* process_node = main_frame_node->GetProcessNode();
if (!process_node)
return false;
// Note that it's possible for one misbehaving frame hosted in the same
// process as this page's main frame to keep the main thread task low high.
// In this case the IsIdling signal will be delayed, despite the task load
// associated with this page's main frame actually being low. In the case
// of session restore this is mitigated by having a timeout while waiting for
// this signal.
return main_frame_node->GetPropertyOrDefault(
resource_coordinator::mojom::PropertyType::kNetworkAlmostIdle,
0u) &&
process_node->GetPropertyOrDefault(
resource_coordinator::mojom::PropertyType::
kMainThreadTaskLoadIsLow,
0u);
}
} // namespace performance_manager
// Copyright 2019 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.
#ifndef CHROME_BROWSER_PERFORMANCE_MANAGER_DECORATORS_PAGE_ALMOST_IDLE_DECORATOR_H_
#define CHROME_BROWSER_PERFORMANCE_MANAGER_DECORATORS_PAGE_ALMOST_IDLE_DECORATOR_H_
#include "chrome/browser/performance_manager/common/page_almost_idle_data.h"
#include "base/time/time.h"
#include "chrome/browser/performance_manager/graph/frame_node_impl.h"
#include "chrome/browser/performance_manager/graph/node_base.h"
#include "chrome/browser/performance_manager/graph/page_node_impl.h"
#include "chrome/browser/performance_manager/graph/process_node_impl.h"
#include "chrome/browser/performance_manager/observers/coordination_unit_graph_observer.h"
namespace performance_manager {
namespace testing {
class PageAlmostIdleDecoratorTestUtils;
} // namespace testing
// The PageAlmostIdle decorator is responsible for determining when a page has
// reached an "almost idle" state after initial load, based on CPU and network
// quiescence, as well as an absolute timeout. This state is then updated on
// PageNodes in a graph.
class PageAlmostIdleDecorator : public GraphObserver {
public:
PageAlmostIdleDecorator();
~PageAlmostIdleDecorator() override;
// GraphObserver implementation:
bool ShouldObserve(const NodeBase* coordination_unit) override;
void OnFramePropertyChanged(
FrameNodeImpl* frame_node,
resource_coordinator::mojom::PropertyType property_type,
int64_t value) override;
void OnPagePropertyChanged(
PageNodeImpl* page_node,
resource_coordinator::mojom::PropertyType property_type,
int64_t value) override;
void OnProcessPropertyChanged(
ProcessNodeImpl* process_node,
resource_coordinator::mojom::PropertyType property_type,
int64_t value) override;
void OnPageEventReceived(PageNodeImpl* page_node,
resource_coordinator::mojom::Event event) override;
protected:
using LoadIdleState = PageAlmostIdleData::LoadIdleState;
friend class PageAlmostIdleDecoratorTestHelper;
friend class testing::PageAlmostIdleDecoratorTestUtils;
// The amount of time a page has to be idle post-loading in order for it to be
// considered loaded and idle. This is used in UpdateLoadIdleState
// transitions.
static constexpr base::TimeDelta kLoadedAndIdlingTimeout =
base::TimeDelta::FromSeconds(1);
// The maximum amount of time post-DidStopLoading a page can be waiting for
// an idle state to occur before the page is simply considered loaded anyways.
// Since PageAlmostIdle is intended as an "initial loading complete" signal,
// it needs to eventually terminate. This is strictly greater than the
// kLoadedAndIdlingTimeout.
//
// This is taken as the 95th percentile of tab loading times on desktop
// (see SessionRestore.ForegroundTabFirstLoaded). This ensures that all tabs
// eventually transition to loaded, even if they keep the main task queue
// busy, or continue loading content.
static constexpr base::TimeDelta kWaitingForIdleTimeout =
base::TimeDelta::FromMinutes(1);
// These are called when properties/events affecting the load-idle state are
// observed. Frame and Process variants will eventually all redirect to the
// appropriate Page variant, where the real work is done.
void UpdateLoadIdleStateFrame(FrameNodeImpl* frame_node);
void UpdateLoadIdleStatePage(PageNodeImpl* page_node);
void UpdateLoadIdleStateProcess(ProcessNodeImpl* process_node);
// Helper function for transitioning to the final state.
void TransitionToLoadedAndIdle(PageNodeImpl* page_node);
// Convenience accessors for state associated with a |page_node|.
static PageAlmostIdleData* GetOrCreateData(PageNodeImpl* page_node);
static PageAlmostIdleData* GetData(PageNodeImpl* page_node);
static bool IsLoading(const PageNodeImpl* page_node);
static bool IsIdling(const PageNodeImpl* page_node);
private:
DISALLOW_COPY_AND_ASSIGN(PageAlmostIdleDecorator);
};
} // namespace performance_manager
#endif // CHROME_BROWSER_PERFORMANCE_MANAGER_DECORATORS_PAGE_ALMOST_IDLE_DECORATOR_H_
// Copyright 2019 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/performance_manager/decorators/page_almost_idle_decorator_test_utils.h"
#include "base/run_loop.h"
#include "chrome/browser/performance_manager/decorators/page_almost_idle_decorator.h"
#include "chrome/browser/performance_manager/graph/frame_node_impl.h"
#include "chrome/browser/performance_manager/graph/node_base.h"
#include "chrome/browser/performance_manager/graph/page_node_impl.h"
#include "chrome/browser/performance_manager/graph/process_node_impl.h"
#include "chrome/browser/performance_manager/resource_coordinator_clock.h"
namespace performance_manager {
namespace testing {
namespace {
// Helper observer for detecting a change to page almost idle.
class PageAlmostIdleObserverForTesting : public GraphObserver {
public:
PageAlmostIdleObserverForTesting(base::RunLoop* run_loop,
PageNodeImpl* page_node)
: run_loop_(run_loop), page_node_(page_node) {
page_node_->AddObserver(this);
}
~PageAlmostIdleObserverForTesting() override {
page_node_->RemoveObserver(this);
}
// GraphObserver implementation:
bool ShouldObserve(const NodeBase* coordination_unit) override {
return false;
}
void OnPageAlmostIdleChanged(PageNodeImpl* page_node) override {
if (page_node == page_node_ && page_node->page_almost_idle())
run_loop_->Quit();
}
private:
base::RunLoop* run_loop_ = nullptr;
PageNodeImpl* page_node_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(PageAlmostIdleObserverForTesting);
};
} // namespace
// static
void PageAlmostIdleDecoratorTestUtils::DrivePageToLoadedAndIdle(
PageNodeImpl* page_node) {
using PropertyType = resource_coordinator::mojom::PropertyType;
auto* frame_node = page_node->GetMainFrameNode();
auto* process_node = frame_node->GetProcessNode();
if (page_node->page_almost_idle())
return;
// If no navigation has yet started, then initiate one. This will ensure that
// the PageAlmostIdleData comes into existence.
auto* data = PageAlmostIdleDecorator::GetData(page_node);
if (!data) {
page_node->OnMainFrameNavigationCommitted(
ResourceCoordinatorClock::NowTicks(), 0, "foo.com");
data = PageAlmostIdleDecorator::GetData(page_node);
DCHECK(data);
}
// Walk through the state machine, tickling only those functions needed in
// order to bring the state machine to its final state.
switch (data->load_idle_state_) {
case PageAlmostIdleData::LoadIdleState::kLoadedAndIdle:
NOTREACHED();
break;
case PageAlmostIdleData::LoadIdleState::kLoadingNotStarted:
DCHECK(!page_node->GetPropertyOrDefault(PropertyType::kIsLoading, 0));
page_node->SetIsLoading(true);
FALLTHROUGH;
case PageAlmostIdleData::LoadIdleState::kLoading:
DCHECK(page_node->GetPropertyOrDefault(PropertyType::kIsLoading, 0));
page_node->SetIsLoading(false);
FALLTHROUGH;
case PageAlmostIdleData::LoadIdleState::kLoadedNotIdling:
if (!frame_node->GetPropertyOrDefault(PropertyType::kNetworkAlmostIdle,
0)) {
frame_node->SetNetworkAlmostIdle(true);
}
if (!process_node->GetPropertyOrDefault(
PropertyType::kMainThreadTaskLoadIsLow, 0)) {
process_node->SetMainThreadTaskLoadIsLow(true);
}
FALLTHROUGH;
case PageAlmostIdleData::LoadIdleState::kLoadedAndIdling:
default:
break;
}
// Run the loop until the state change occurs, as all of the above calls
// simply caused messages to be posted.
base::RunLoop run_loop;
PageAlmostIdleObserverForTesting observer(&run_loop, page_node);
run_loop.Run();
DCHECK(page_node->page_almost_idle());
}
} // namespace testing
} // namespace performance_manager
// Copyright 2019 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.
#ifndef CHROME_BROWSER_PERFORMANCE_MANAGER_DECORATORS_PAGE_ALMOST_IDLE_DECORATOR_TEST_UTILS_H_
#define CHROME_BROWSER_PERFORMANCE_MANAGER_DECORATORS_PAGE_ALMOST_IDLE_DECORATOR_TEST_UTILS_H_
namespace performance_manager {
class PageNodeImpl;
namespace testing {
class PageAlmostIdleDecoratorTestUtils {
public:
// Drives the state transition machinery through the entire state machine to
// the end point of being "loaded and idle".
static void DrivePageToLoadedAndIdle(PageNodeImpl* page_node);
};
} // namespace testing
} // namespace performance_manager
#endif // CHROME_BROWSER_PERFORMANCE_MANAGER_DECORATORS_PAGE_ALMOST_IDLE_DECORATOR_TEST_UTILS_H_
......@@ -54,7 +54,7 @@ class TestNodeWrapper {
DISALLOW_COPY_AND_ASSIGN(TestNodeWrapper);
};
class GraphTestHarness : public testing::Test {
class GraphTestHarness : public ::testing::Test {
public:
GraphTestHarness();
~GraphTestHarness() override;
......
......@@ -4,8 +4,11 @@
#include "chrome/browser/performance_manager/graph/page_node_impl.h"
#include <memory>
#include "base/logging.h"
#include "base/time/default_tick_clock.h"
#include "chrome/browser/performance_manager/common/page_almost_idle_data.h"
#include "chrome/browser/performance_manager/graph/frame_node_impl.h"
#include "chrome/browser/performance_manager/graph/process_node_impl.h"
#include "chrome/browser/performance_manager/observers/coordination_unit_graph_observer.h"
......@@ -238,6 +241,14 @@ PageNodeImpl::GetInterventionPolicy(
return intervention_policy_[kIndex];
}
void PageNodeImpl::set_page_almost_idle(bool page_almost_idle) {
if (page_almost_idle_ == page_almost_idle)
return;
page_almost_idle_ = page_almost_idle;
for (auto& observer : observers())
observer.OnPageAlmostIdleChanged(this);
}
void PageNodeImpl::OnEventReceived(resource_coordinator::mojom::Event event) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (auto& observer : observers())
......@@ -396,4 +407,31 @@ void PageNodeImpl::RecomputeInterventionPolicy(
intervention_policy_[kIndex] = policy;
}
// static
PageAlmostIdleData* PageNodeImpl::PageAlmostIdleHelper::GetOrCreateData(
PageNodeImpl* page_node) {
if (!page_node->page_almost_idle_data_.get())
page_node->page_almost_idle_data_ = std::make_unique<PageAlmostIdleData>();
return page_node->page_almost_idle_data_.get();
}
// static
PageAlmostIdleData* PageNodeImpl::PageAlmostIdleHelper::GetData(
PageNodeImpl* page_node) {
return page_node->page_almost_idle_data_.get();
}
// static
void PageNodeImpl::PageAlmostIdleHelper::DestroyData(PageNodeImpl* page_node) {
DCHECK(page_node->page_almost_idle_data_.get());
page_node->page_almost_idle_data_.reset();
}
// static
void PageNodeImpl::PageAlmostIdleHelper::set_page_almost_idle(
PageNodeImpl* page_node,
bool page_almost_idle) {
page_node->set_page_almost_idle(page_almost_idle);
}
} // namespace performance_manager
......@@ -5,6 +5,8 @@
#ifndef CHROME_BROWSER_PERFORMANCE_MANAGER_GRAPH_PAGE_NODE_IMPL_H_
#define CHROME_BROWSER_PERFORMANCE_MANAGER_GRAPH_PAGE_NODE_IMPL_H_
#include <memory>
#include "base/macros.h"
#include "base/time/time.h"
#include "chrome/browser/performance_manager/graph/node_base.h"
......@@ -12,6 +14,7 @@
namespace performance_manager {
class FrameNodeImpl;
class PageAlmostIdleData;
class ProcessNodeImpl;
class PageNodeImpl
......@@ -20,6 +23,8 @@ class PageNodeImpl
resource_coordinator::mojom::PageCoordinationUnit,
resource_coordinator::mojom::PageCoordinationUnitRequest> {
public:
struct PageAlmostIdleHelper;
static resource_coordinator::CoordinationUnitType Type() {
return resource_coordinator::CoordinationUnitType::kPage;
}
......@@ -89,6 +94,7 @@ class PageNodeImpl
void set_has_nonempty_beforeunload(bool has_nonempty_beforeunload) {
has_nonempty_beforeunload_ = has_nonempty_beforeunload;
}
bool page_almost_idle() const { return page_almost_idle_; }
const std::string& main_frame_url() const { return main_frame_url_; }
int64_t navigation_id() const { return navigation_id_; }
......@@ -126,6 +132,8 @@ class PageNodeImpl
private:
friend class FrameNodeImpl;
void set_page_almost_idle(bool page_almost_idle);
// CoordinationUnitInterface implementation.
void OnEventReceived(resource_coordinator::mojom::Event event) override;
void OnPropertyChanged(
......@@ -208,9 +216,32 @@ class PageNodeImpl
// is used as a signal that the frame has reported.
size_t intervention_policy_frames_reported_ = 0;
// Page almost idle state. This is the output that is driven by the
// PageAlmostIdleDecorator.
bool page_almost_idle_ = false;
// TODO(chrisha): Hide away this type using a base class, and expose a
// strongly typed "WebContentsUserData" like mechanism.
// "User data" storage for the PageAlmostIdleDecorator.
std::unique_ptr<PageAlmostIdleData> page_almost_idle_data_;
DISALLOW_COPY_AND_ASSIGN(PageNodeImpl);
};
// Helper that allows the PageAlmostIdleDecorator scoped access to the
// PageNodeImpl.
struct PageNodeImpl::PageAlmostIdleHelper {
protected:
friend class PageAlmostIdleDecorator;
static PageAlmostIdleData* GetOrCreateData(PageNodeImpl* page_node);
static PageAlmostIdleData* GetData(PageNodeImpl* page_node);
static void DestroyData(PageNodeImpl* page_node);
static void set_page_almost_idle(PageNodeImpl* page_node,
bool page_almost_idle);
};
} // namespace performance_manager
#endif // CHROME_BROWSER_PERFORMANCE_MANAGER_GRAPH_PAGE_NODE_IMPL_H_
......@@ -31,6 +31,9 @@ class SystemNodeImpl;
// (1) Derive from this class.
// (2) Register by calling on |coordination_unit_graph().RegisterObserver|
// inside of the ResourceCoordinatorService::Create.
//
// TODO: Clean up the observer API, and create a wrapper version that sees
// const Node* rather then mutable NodeImpl* types for external consumers.
class GraphObserver {
public:
GraphObserver();
......@@ -92,6 +95,10 @@ class GraphObserver {
resource_coordinator::mojom::Event event) {
}
// Called when page almost idle state changes. This is a computed property and
// will only be maintained if a PageAlmostIdleDecorator exists on the graph.
virtual void OnPageAlmostIdleChanged(PageNodeImpl* page_node) {}
// Called when all the frames in a process become frozen.
virtual void OnAllFramesInProcessFrozen(ProcessNodeImpl* process_cu) {}
......
......@@ -40,133 +40,55 @@ class PageSignalGeneratorImpl
bool ShouldObserve(const NodeBase* coordination_unit) override;
void OnNodeCreated(NodeBase* cu) override;
void OnBeforeNodeDestroyed(NodeBase* cu) override;
void OnFramePropertyChanged(
FrameNodeImpl* frame_cu,
resource_coordinator::mojom::PropertyType property_type,
int64_t value) override;
void OnPagePropertyChanged(
PageNodeImpl* page_cu,
PageNodeImpl* page_node,
resource_coordinator::mojom::PropertyType property_type,
int64_t value) override;
void OnProcessPropertyChanged(
ProcessNodeImpl* process_cu,
ProcessNodeImpl* process_node,
resource_coordinator::mojom::PropertyType property_type,
int64_t value) override;
void OnFrameEventReceived(FrameNodeImpl* frame_cu,
void OnFrameEventReceived(FrameNodeImpl* frame_node,
resource_coordinator::mojom::Event event) override;
void OnPageEventReceived(PageNodeImpl* page_cu,
resource_coordinator::mojom::Event event) override;
void OnProcessEventReceived(
ProcessNodeImpl* page_cu,
ProcessNodeImpl* page_node,
resource_coordinator::mojom::Event event) override;
void OnSystemEventReceived(SystemNodeImpl* system_cu,
void OnSystemEventReceived(SystemNodeImpl* system_node,
resource_coordinator::mojom::Event event) override;
void OnPageAlmostIdleChanged(PageNodeImpl* page_node) override;
void BindToInterface(
resource_coordinator::mojom::PageSignalGeneratorRequest request,
const service_manager::BindSourceInfo& source_info);
private:
// The amount of time a page has to be idle post-loading in order for it to be
// considered loaded and idle. This is used in UpdateLoadIdleState
// transitions.
static const base::TimeDelta kLoadedAndIdlingTimeout;
// The maximum amount of time post-DidStopLoading a page can be waiting for
// an idle state to occur before the page is simply considered loaded anyways.
// Since PageAlmostIdle is intended as an "initial loading complete" signal,
// it needs to eventually terminate. This is strictly greater than the
// kLoadedAndIdlingTimeout.
static const base::TimeDelta kWaitingForIdleTimeout;
friend class PageSignalGeneratorImplTest;
FRIEND_TEST_ALL_PREFIXES(PageSignalGeneratorImplTest, IsLoading);
FRIEND_TEST_ALL_PREFIXES(PageSignalGeneratorImplTest, IsIdling);
FRIEND_TEST_ALL_PREFIXES(PageSignalGeneratorImplTest,
NonPersistentNotificationCreatedEvent);
FRIEND_TEST_ALL_PREFIXES(PageSignalGeneratorImplTest,
PageDataCorrectlyManaged);
FRIEND_TEST_ALL_PREFIXES(PageSignalGeneratorImplTest,
PageAlmostIdleTransitionsNoTimeout);
FRIEND_TEST_ALL_PREFIXES(PageSignalGeneratorImplTest,
PageAlmostIdleTransitionsWithTimeout);
FRIEND_TEST_ALL_PREFIXES(PageSignalGeneratorImplTest,
OnLoadTimePerformanceEstimate);
// The state transitions for the PageAlmostIdle signal. In general a page
// transitions through these states from top to bottom.
enum LoadIdleState {
// The initial state. Can only transition to kLoading from here.
kLoadingNotStarted,
// Loading has started. Almost idle signals are ignored in this state.
// Can transition to kLoadedNotIdling and kLoadedAndIdling from here.
kLoading,
// Loading has completed, but the page has not started idling. Can only
// transition to kLoadedAndIdling from here.
kLoadedNotIdling,
// Loading has completed, and the page is idling. Can transition to
// kLoadedNotIdling or kLoadedAndIdle from here.
kLoadedAndIdling,
// Loading has completed and the page has been idling for sufficiently long.
// This is the final state. Once this state has been reached a signal will
// be emitted and no further state transitions will be tracked. Committing a
// new non-same document navigation can start the cycle over again.
kLoadedAndIdle
};
// Holds state per page CU. These are created via OnNodeCreated
// and destroyed via OnBeforeNodeDestroyed.
// TODO(chrisha): Move this to a PerformanceEstimateDecorator directly on the
// graph.
struct PageData {
// Set the load idle state and the time of change. Also clears the
// |performance_estimate_issued| flag.
void SetLoadIdleState(LoadIdleState new_state, base::TimeTicks now);
LoadIdleState GetLoadIdleState() const { return load_idle_state; }
// Marks the point in time when the DidStopLoading signal was received,
// transitioning to kLoadedAndNotIdling or kLoadedAndIdling. This is used as
// the basis for the kWaitingForIdleTimeout.
base::TimeTicks loading_stopped;
// Marks the point in time when the last transition to kLoadedAndIdling
// occurred. Used for gating the transition to kLoadedAndIdle.
base::TimeTicks idling_started;
// Notes the time of the last state change.
// Sets |performance_estimate_issued| to false, and updates
// |last_state_change| to the current time.
void Reset();
base::TimeTicks last_state_change;
// True iff a performance estimate has been issued for this page.
bool performance_estimate_issued = false;
// A one-shot timer used for transitioning between kLoadedAndIdling and
// kLoadedAndIdle.
base::OneShotTimer idling_timer;
private:
// Initially at kLoadingNotStarted. Transitions through the states via calls
// to UpdateLoadIdleState. Is reset to kLoadingNotStarted when a non-same
// document navigation is committed.
LoadIdleState load_idle_state;
};
// These are called when properties/events affecting the load-idle state are
// observed. Frame and Process variants will eventually all redirect to the
// appropriate Page variant, where the real work is done.
void UpdateLoadIdleStateFrame(const FrameNodeImpl* frame_cu);
void UpdateLoadIdleStatePage(const PageNodeImpl* page_cu);
void UpdateLoadIdleStateProcess(const ProcessNodeImpl* process_cu);
// This method is called when a property affecting the lifecycle state is
// observed.
void UpdateLifecycleState(const PageNodeImpl* page_cu,
resource_coordinator::mojom::LifecycleState state);
// Helper function for transitioning to the final state.
void TransitionToLoadedAndIdle(const PageNodeImpl* page_cu,
base::TimeTicks now);
// Convenience accessors for state associated with a |page_cu|.
PageData* GetPageData(const PageNodeImpl* page_cu);
bool IsLoading(const PageNodeImpl* page_cu);
bool IsIdling(const PageNodeImpl* page_cu);
// Convenience accessors for state associated with a |page_node|.
PageData* GetPageData(const PageNodeImpl* page_node);
template <typename Method, typename... Params>
void DispatchPageSignal(const PageNodeImpl* page_cu,
void DispatchPageSignal(const PageNodeImpl* page_node,
Method m,
Params... params);
......
......@@ -13,6 +13,7 @@
#include "base/task/post_task.h"
#include "base/task/task_traits.h"
#include "build/build_config.h"
#include "chrome/browser/performance_manager/decorators/page_almost_idle_decorator.h"
#include "chrome/browser/performance_manager/graph/system_node_impl.h"
#include "chrome/browser/performance_manager/observers/metrics_collector.h"
#include "chrome/browser/performance_manager/observers/page_signal_generator_impl.h"
......@@ -110,6 +111,7 @@ void PerformanceManager::OnStartImpl(
graph_.RegisterObserver(std::move(page_signal_generator_impl));
graph_.RegisterObserver(std::make_unique<MetricsCollector>());
graph_.RegisterObserver(std::make_unique<PageAlmostIdleDecorator>());
#if defined(OS_WIN)
if (base::FeatureList::IsEnabled(features::kEmptyWorkingSet))
......
......@@ -2743,6 +2743,9 @@ test("unit_tests") {
"../browser/password_manager/password_store_mac_unittest.cc",
"../browser/password_manager/password_store_x_unittest.cc",
"../browser/payments/payment_handler_permission_context_unittest.cc",
"../browser/performance_manager/decorators/page_almost_idle_decorator_test_utils.cc",
"../browser/performance_manager/decorators/page_almost_idle_decorator_test_utils.h",
"../browser/performance_manager/decorators/page_almost_idle_decorator_unittest.cc",
"../browser/performance_manager/graph/frame_node_impl_unittest.cc",
"../browser/performance_manager/graph/graph_test_harness.cc",
"../browser/performance_manager/graph/graph_test_harness.h",
......
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