blob: 7da1a30f19db805a094b167350fcb442efcb3708 [file] [log] [blame]
// Copyright 2017 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/graph/page_node_impl.h"
#include <memory>
#include "base/logging.h"
#include "base/stl_util.h"
#include "base/time/default_tick_clock.h"
#include "chrome/browser/performance_manager/graph/frame_node_impl.h"
#include "chrome/browser/performance_manager/graph/graph_impl.h"
#include "chrome/browser/performance_manager/graph/process_node_impl.h"
#include "chrome/browser/performance_manager/performance_manager_clock.h"
namespace performance_manager {
namespace {
constexpr size_t kMaxInterventionIndex = static_cast<size_t>(
resource_coordinator::mojom::PolicyControlledIntervention::kMaxValue);
size_t ToIndex(
resource_coordinator::mojom::PolicyControlledIntervention intervention) {
const size_t kIndex = static_cast<size_t>(intervention);
DCHECK(kIndex <= kMaxInterventionIndex);
return kIndex;
}
// Calls |map_function| for |frame_node| and all its offspring, or until
// |map_function| returns false.
template <typename MapFunction>
void ForFrameAndDescendents(FrameNodeImpl* frame_node,
MapFunction map_function) {
if (!map_function(frame_node))
return;
for (FrameNodeImpl* child : frame_node->child_frame_nodes())
ForFrameAndDescendents(child, map_function);
}
} // namespace
PageNodeImplObserver::PageNodeImplObserver() = default;
PageNodeImplObserver::~PageNodeImplObserver() = default;
PageNodeImpl::PageNodeImpl(GraphImpl* graph,
const WebContentsProxy& contents_proxy,
bool is_visible)
: TypedNodeBase(graph),
contents_proxy_(contents_proxy),
visibility_change_time_(PerformanceManagerClock::NowTicks()),
is_visible_(is_visible) {
DETACH_FROM_SEQUENCE(sequence_checker_);
}
PageNodeImpl::~PageNodeImpl() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
const WebContentsProxy& PageNodeImpl::contents_proxy() const {
return contents_proxy_;
}
void PageNodeImpl::AddFrame(FrameNodeImpl* frame_node) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(frame_node);
DCHECK_EQ(this, frame_node->page_node());
DCHECK(graph()->NodeInGraph(frame_node));
++frame_node_count_;
if (frame_node->parent_frame_node() == nullptr)
main_frame_nodes_.insert(frame_node);
MaybeInvalidateInterventionPolicies(frame_node, true /* adding_frame */);
}
void PageNodeImpl::RemoveFrame(FrameNodeImpl* frame_node) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(frame_node);
DCHECK_EQ(this, frame_node->page_node());
DCHECK(graph()->NodeInGraph(frame_node));
--frame_node_count_;
if (frame_node->parent_frame_node() == nullptr) {
size_t removed = main_frame_nodes_.erase(frame_node);
DCHECK_EQ(1u, removed);
}
MaybeInvalidateInterventionPolicies(frame_node, false /* adding_frame */);
}
void PageNodeImpl::SetIsLoading(bool is_loading) {
is_loading_.SetAndMaybeNotify(this, is_loading);
}
void PageNodeImpl::SetIsVisible(bool is_visible) {
if (is_visible_.SetAndMaybeNotify(this, is_visible)) {
// The change time needs to be updated after observers are notified, as they
// use this to determine time passed since the *previous* visibility state
// change. They can infer the current state change time themselves via
// NowTicks.
visibility_change_time_ = PerformanceManagerClock::NowTicks();
}
}
void PageNodeImpl::SetUkmSourceId(ukm::SourceId ukm_source_id) {
ukm_source_id_.SetAndMaybeNotify(this, ukm_source_id);
}
void PageNodeImpl::OnFaviconUpdated() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (auto& observer : observers())
observer.OnFaviconUpdated(this);
}
void PageNodeImpl::OnTitleUpdated() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (auto& observer : observers())
observer.OnTitleUpdated(this);
}
void PageNodeImpl::OnMainFrameNavigationCommitted(
base::TimeTicks navigation_committed_time,
int64_t navigation_id,
const GURL& url) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
navigation_committed_time_ = navigation_committed_time;
main_frame_url_ = url;
navigation_id_ = navigation_id;
for (auto& observer : observers())
observer.OnMainFrameNavigationCommitted(this);
}
base::flat_set<ProcessNodeImpl*> PageNodeImpl::GetAssociatedProcessNodes()
const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
base::flat_set<ProcessNodeImpl*> process_nodes;
ForAllFrameNodes([&process_nodes](FrameNodeImpl* frame_node) -> bool {
if (auto* process_node = frame_node->process_node())
process_nodes.insert(process_node);
return true;
});
return process_nodes;
}
double PageNodeImpl::GetCPUUsage() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
double cpu_usage = 0;
for (auto* process_node : GetAssociatedProcessNodes()) {
size_t pages_in_process = process_node->GetAssociatedPageNodes().size();
DCHECK_LE(1u, pages_in_process);
cpu_usage += process_node->cpu_usage() / pages_in_process;
}
return cpu_usage;
}
base::TimeDelta PageNodeImpl::TimeSinceLastNavigation() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (navigation_committed_time_.is_null())
return base::TimeDelta();
return PerformanceManagerClock::NowTicks() - navigation_committed_time_;
}
base::TimeDelta PageNodeImpl::TimeSinceLastVisibilityChange() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return PerformanceManagerClock::NowTicks() - visibility_change_time_;
}
std::vector<FrameNodeImpl*> PageNodeImpl::GetFrameNodes() const {
std::vector<FrameNodeImpl*> all_frames;
ForAllFrameNodes([&all_frames](FrameNodeImpl* frame_node) -> bool {
all_frames.push_back(frame_node);
return true;
});
return all_frames;
}
FrameNodeImpl* PageNodeImpl::GetMainFrameNode() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (main_frame_nodes_.empty())
return nullptr;
// Return the current frame node if there is one. Iterating over this set is
// fine because it is almost always of length 1 or 2.
for (auto* frame : main_frame_nodes_) {
if (frame->is_current())
return frame;
}
// Otherwise, return any old main frame node.
return *main_frame_nodes_.begin();
}
bool PageNodeImpl::is_visible() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return is_visible_.value();
}
bool PageNodeImpl::is_loading() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return is_loading_.value();
}
ukm::SourceId PageNodeImpl::ukm_source_id() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return ukm_source_id_.value();
}
PageNodeImpl::LifecycleState PageNodeImpl::lifecycle_state() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return lifecycle_state_.value();
}
const base::flat_set<FrameNodeImpl*>& PageNodeImpl::main_frame_nodes() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return main_frame_nodes_;
}
base::TimeTicks PageNodeImpl::usage_estimate_time() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return usage_estimate_time_;
}
base::TimeDelta PageNodeImpl::cumulative_cpu_usage_estimate() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return cumulative_cpu_usage_estimate_;
}
uint64_t PageNodeImpl::private_footprint_kb_estimate() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return private_footprint_kb_estimate_;
}
bool PageNodeImpl::page_almost_idle() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return page_almost_idle_.value();
}
const GURL& PageNodeImpl::main_frame_url() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return main_frame_url_;
}
int64_t PageNodeImpl::navigation_id() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return navigation_id_;
}
void PageNodeImpl::set_usage_estimate_time(
base::TimeTicks usage_estimate_time) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
usage_estimate_time_ = usage_estimate_time;
}
void PageNodeImpl::set_cumulative_cpu_usage_estimate(
base::TimeDelta cumulative_cpu_usage_estimate) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
cumulative_cpu_usage_estimate_ = cumulative_cpu_usage_estimate;
}
void PageNodeImpl::set_private_footprint_kb_estimate(
uint64_t private_footprint_kb_estimate) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
private_footprint_kb_estimate_ = private_footprint_kb_estimate;
}
void PageNodeImpl::set_has_nonempty_beforeunload(
bool has_nonempty_beforeunload) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
has_nonempty_beforeunload_ = has_nonempty_beforeunload;
}
void PageNodeImpl::OnFrameInterventionPolicyChanged(
FrameNodeImpl* frame,
resource_coordinator::mojom::PolicyControlledIntervention intervention,
resource_coordinator::mojom::InterventionPolicy old_policy,
resource_coordinator::mojom::InterventionPolicy new_policy) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const size_t kIndex = ToIndex(intervention);
// Invalidate the local policy aggregation for this intervention. It will be
// recomputed on the next query to GetInterventionPolicy.
intervention_policy_[kIndex] =
resource_coordinator::mojom::InterventionPolicy::kUnknown;
// The first time a frame transitions away from kUnknown for the last policy,
// then that frame is considered to have checked in. Frames always provide
// initial policy values in order, ensuring this works.
if (old_policy == resource_coordinator::mojom::InterventionPolicy::kUnknown &&
new_policy != resource_coordinator::mojom::InterventionPolicy::kUnknown &&
intervention == resource_coordinator::mojom::
PolicyControlledIntervention::kMaxValue) {
++intervention_policy_frames_reported_;
DCHECK_LE(intervention_policy_frames_reported_, frame_node_count_);
}
}
resource_coordinator::mojom::InterventionPolicy
PageNodeImpl::GetInterventionPolicy(
resource_coordinator::mojom::PolicyControlledIntervention intervention) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// If there are no frames, or they've not all reported, then return kUnknown.
if (frame_node_count_ == 0u ||
intervention_policy_frames_reported_ != frame_node_count_) {
return resource_coordinator::mojom::InterventionPolicy::kUnknown;
}
// Recompute the policy if it is currently invalid.
const size_t kIndex = ToIndex(intervention);
DCHECK_LE(kIndex, kMaxInterventionIndex);
if (intervention_policy_[kIndex] ==
resource_coordinator::mojom::InterventionPolicy::kUnknown) {
RecomputeInterventionPolicy(intervention);
DCHECK_NE(resource_coordinator::mojom::InterventionPolicy::kUnknown,
intervention_policy_[kIndex]);
}
return intervention_policy_[kIndex];
}
void PageNodeImpl::JoinGraph() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
InvalidateAllInterventionPolicies();
NodeBase::JoinGraph();
}
void PageNodeImpl::LeaveGraph() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_EQ(0u, frame_node_count_);
NodeBase::LeaveGraph();
}
bool PageNodeImpl::HasFrame(FrameNodeImpl* frame_node) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
bool has_node = false;
ForAllFrameNodes([&has_node, &frame_node](FrameNodeImpl* node) -> bool {
if (node != frame_node)
return true;
has_node = true;
return false;
});
return has_node;
}
void PageNodeImpl::SetPageAlmostIdle(bool page_almost_idle) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
page_almost_idle_.SetAndMaybeNotify(this, page_almost_idle);
}
void PageNodeImpl::SetLifecycleState(LifecycleState lifecycle_state) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
lifecycle_state_.SetAndMaybeNotify(this, lifecycle_state);
}
void PageNodeImpl::InvalidateAllInterventionPolicies() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (size_t i = 0; i <= kMaxInterventionIndex; ++i)
intervention_policy_[i] =
resource_coordinator::mojom::InterventionPolicy::kUnknown;
}
void PageNodeImpl::MaybeInvalidateInterventionPolicies(
FrameNodeImpl* frame_node,
bool adding_frame) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Ensure that the frame was already added or removed as expected.
DCHECK(!adding_frame || HasFrame(frame_node));
// Determine whether or not the frames had all reported prior to this change.
const size_t prior_frame_count = frame_node_count_ + (adding_frame ? -1 : 1);
const bool frames_all_reported_prior =
prior_frame_count > 0 &&
intervention_policy_frames_reported_ == prior_frame_count;
// If the previous state was considered fully reported, then aggregation may
// have occurred. Adding or removing a frame (even one that is fully reported)
// needs to invalidate that aggregation. Invalidation could happen on every
// single frame addition and removal, but only doing this when the previous
// state was fully reported reduces unnecessary invalidations.
if (frames_all_reported_prior)
InvalidateAllInterventionPolicies();
// Update the reporting frame count.
const bool frame_reported = frame_node->AreAllInterventionPoliciesSet();
if (frame_reported)
intervention_policy_frames_reported_ += adding_frame ? 1 : -1;
DCHECK_LE(intervention_policy_frames_reported_, frame_node_count_);
}
void PageNodeImpl::RecomputeInterventionPolicy(
resource_coordinator::mojom::PolicyControlledIntervention intervention) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const size_t kIndex = ToIndex(intervention);
// This should never be called with an empty frame tree.
DCHECK_NE(0u, frame_node_count_);
resource_coordinator::mojom::InterventionPolicy policy =
resource_coordinator::mojom::InterventionPolicy::kDefault;
ForAllFrameNodes([&policy, &kIndex](FrameNodeImpl* frame) -> bool {
// No frame should have an unknown policy, as aggregation should only be
// invoked after all frames have checked in.
DCHECK_NE(resource_coordinator::mojom::InterventionPolicy::kUnknown,
frame->intervention_policy_[kIndex]);
// If any frame opts out then the whole frame tree opts out, even if other
// frames have opted in.
if (frame->intervention_policy_[kIndex] ==
resource_coordinator::mojom::InterventionPolicy::kOptOut) {
policy = resource_coordinator::mojom::InterventionPolicy::kOptOut;
return false;
}
// If any frame opts in and none opt out, then the whole tree opts in.
if (frame->intervention_policy_[kIndex] ==
resource_coordinator::mojom::InterventionPolicy::kOptIn) {
policy = resource_coordinator::mojom::InterventionPolicy::kOptIn;
}
return true;
});
intervention_policy_[kIndex] = policy;
}
template <typename MapFunction>
void PageNodeImpl::ForAllFrameNodes(MapFunction map_function) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (auto* main_frame_node : main_frame_nodes_)
ForFrameAndDescendents(main_frame_node, map_function);
}
PageNodeImpl::ObserverDefaultImpl::ObserverDefaultImpl() = default;
PageNodeImpl::ObserverDefaultImpl::~ObserverDefaultImpl() = default;
} // namespace performance_manager