Commit 417d037d authored by Chris Hamilton's avatar Chris Hamilton Committed by Commit Bot

[PM] Add ProcessPriorityPolicy.

This creates a policy engine that forwards calculated priority values
out of the graph, applying the priorities to RenderProcessHosts (and
subsequently to the process itself).

BUG=971272

Change-Id: I11c8c040e5382a8fa6881c40f80f6885f26e88df
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1904367
Commit-Queue: Chris Hamilton <chrisha@chromium.org>
Reviewed-by: default avatarSigurður Ásgeirsson <siggi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#713859}
parent 0af5fd5b
......@@ -26,6 +26,8 @@ static_library("performance_manager") {
"graph/page_node.cc",
"graph/page_node_impl.cc",
"graph/page_node_impl.h",
"graph/policies/process_priority_policy.cc",
"graph/policies/process_priority_policy.h",
"graph/process_node.cc",
"graph/process_node_impl.cc",
"graph/process_node_impl.h",
......@@ -95,6 +97,7 @@ source_set("unit_tests") {
"graph/node_attached_data_unittest.cc",
"graph/node_base_unittest.cc",
"graph/page_node_impl_unittest.cc",
"graph/policies/process_priority_policy_unittest.cc",
"graph/process_node_impl_unittest.cc",
"graph/properties_unittest.cc",
"graph/system_node_impl_unittest.cc",
......
......@@ -211,6 +211,10 @@ std::vector<const WorkerNode*> GraphImpl::GetAllWorkerNodes() const {
return GetAllNodesOfType<WorkerNodeImpl, const WorkerNode*>();
}
bool GraphImpl::IsEmpty() const {
return nodes_.empty();
}
ukm::UkmRecorder* GraphImpl::GetUkmRecorder() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return ukm_recorder();
......
......@@ -70,6 +70,7 @@ class GraphImpl : public Graph {
std::vector<const FrameNode*> GetAllFrameNodes() const override;
std::vector<const PageNode*> GetAllPageNodes() const override;
std::vector<const WorkerNode*> GetAllWorkerNodes() const override;
bool IsEmpty() const override;
ukm::UkmRecorder* GetUkmRecorder() const override;
uintptr_t GetImplType() const override;
const void* GetImpl() const override;
......
......@@ -25,7 +25,9 @@ TEST_F(GraphImplTest, SafeCasting) {
}
TEST_F(GraphImplTest, FindOrCreateSystemNode) {
EXPECT_TRUE(graph()->IsEmpty());
SystemNodeImpl* system_node = graph()->FindOrCreateSystemNodeImpl();
EXPECT_FALSE(graph()->IsEmpty());
// A second request should return the same instance.
EXPECT_EQ(system_node, graph()->FindOrCreateSystemNodeImpl());
......
// 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 "components/performance_manager/graph/policies/process_priority_policy.h"
#include "base/bind.h"
#include "base/memory/ptr_util.h"
#include "base/task/post_task.h"
#include "components/performance_manager/public/render_process_host_proxy.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_process_host.h"
namespace performance_manager {
namespace policies {
namespace {
#if DCHECK_IS_ON()
size_t g_instance_count = 0;
#endif
// Used as a testing seam. If this is set then SetProcessPriorityOnUiThread will
// invoke the provided callback immediately after calling SetPriorityOverride on
// the RenderProcessHost.
ProcessPriorityPolicy::SetPriorityOnUiThreadCallback* g_callback = nullptr;
// Maps a TaskPriority to a "is_foreground" bool. Process priorities are
// currently simply "background" or "foreground", despite there actually being
// more expressive power on most platforms.
bool IsForegroundTaskPriority(base::TaskPriority priority) {
switch (priority) {
case base::TaskPriority::BEST_EFFORT:
return false;
case base::TaskPriority::USER_VISIBLE:
case base::TaskPriority::USER_BLOCKING:
break;
}
return true;
}
// Helper function for setting the RenderProcessHost priority.
void SetProcessPriorityOnUIThread(RenderProcessHostProxy rph_proxy,
bool foreground) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// Deliver the policy message if the RPH still exists by the time the
// message arrives. Note that this will involve yet another bounce over to
// the process launcher thread.
auto* rph = rph_proxy.Get();
if (rph)
rph->SetPriorityOverride(foreground);
// Invoke the testing seam callback if one was provided.
if (g_callback && !g_callback->is_null())
g_callback->Run(rph_proxy, foreground);
}
// Dispatches a process priority change to the RenderProcessHost associated with
// a given ProcessNode. The task is posted to the UI thread, where the RPH
// lives.
void DispatchSetProcessPriority(const ProcessNode* process_node,
bool foreground) {
// TODO(chrisha): This will actually result in a further thread-hop over to
// the process launcher thread. If we migrate to process priority logic being
// driven 100% from the PM, we could post directly to the launcher thread
// via the base::Process directly.
const auto& proxy = process_node->GetRenderProcessHostProxy();
base::PostTask(
FROM_HERE, {content::BrowserThread::UI},
base::BindOnce(&SetProcessPriorityOnUIThread, proxy, foreground));
}
} // namespace
ProcessPriorityPolicy::ProcessPriorityPolicy() {
#if DCHECK_IS_ON()
DCHECK_EQ(0u, g_instance_count);
++g_instance_count;
#endif
}
ProcessPriorityPolicy::~ProcessPriorityPolicy() {
#if DCHECK_IS_ON()
DCHECK_EQ(1u, g_instance_count);
--g_instance_count;
#endif
}
// static
void ProcessPriorityPolicy::SetCallbackForTesting(
SetPriorityOnUiThreadCallback callback) {
ClearCallbackForTesting();
g_callback = new SetPriorityOnUiThreadCallback(callback);
}
void ProcessPriorityPolicy::ClearCallbackForTesting() {
if (!g_callback)
return;
delete g_callback;
g_callback = nullptr;
}
void ProcessPriorityPolicy::OnPassedToGraph(Graph* graph) {
DCHECK(graph->IsEmpty());
graph->AddProcessNodeObserver(this);
}
void ProcessPriorityPolicy::OnTakenFromGraph(Graph* graph) {
graph->RemoveProcessNodeObserver(this);
}
void ProcessPriorityPolicy::OnProcessNodeAdded(
const ProcessNode* process_node) {
// Set the initial process priority.
// TODO(chrisha): Get provisional nodes working so we can make an informed
// choice in the graph (processes launching ads-to-be, or extensions, or
// frames for backgrounded tabs, etc, can be launched with background
// priority).
// TODO(chrisha): Make process creation take a detour through the graph in
// order to get the initial priority parameter that is set here. Currently
// this is effectively a nop.
DispatchSetProcessPriority(
process_node, IsForegroundTaskPriority(process_node->GetPriority()));
}
void ProcessPriorityPolicy::OnPriorityChanged(
const ProcessNode* process_node,
base::TaskPriority previous_value) {
bool previous_foreground = IsForegroundTaskPriority(previous_value);
bool current_foreground =
IsForegroundTaskPriority(process_node->GetPriority());
// Only dispatch a message if the resulting process priority has changed.
if (previous_foreground != current_foreground)
DispatchSetProcessPriority(process_node, current_foreground);
}
} // namespace policies
} // 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 COMPONENTS_PERFORMANCE_MANAGER_GRAPH_POLICIES_PROCESS_PRIORITY_POLICY_H_
#define COMPONENTS_PERFORMANCE_MANAGER_GRAPH_POLICIES_PROCESS_PRIORITY_POLICY_H_
#include "base/callback.h"
#include "components/performance_manager/public/graph/graph.h"
#include "components/performance_manager/public/graph/process_node.h"
#include "components/performance_manager/public/render_process_host_proxy.h"
namespace performance_manager {
namespace policies {
// Policy that observes priority changes on ProcessNodes, and applies these
// to the actual processes via RenderProcessHost::SetPriorityOverride. There
// is no need for more than one of these to be instantiated at a time (enforced
// by a DCHECK). This policy expects to be attached to an empty graph (also
// enforced by a DCHECK).
class ProcessPriorityPolicy : public GraphOwned,
public ProcessNode::ObserverDefaultImpl {
public:
using SetPriorityOnUiThreadCallback =
base::RepeatingCallback<void(RenderProcessHostProxy rph_proxy,
bool foreground)>;
ProcessPriorityPolicy();
ProcessPriorityPolicy(const ProcessPriorityPolicy&) = delete;
ProcessPriorityPolicy(ProcessPriorityPolicy&&) = delete;
ProcessPriorityPolicy& operator=(const ProcessPriorityPolicy&) = delete;
ProcessPriorityPolicy& operator=(ProcessPriorityPolicy&&) = delete;
~ProcessPriorityPolicy() override;
// Testing seams. This allows testing this class without requiring a full
// browser test.
static void SetCallbackForTesting(SetPriorityOnUiThreadCallback callback);
static void ClearCallbackForTesting();
private:
// GraphOwned implementation:
void OnPassedToGraph(Graph* graph) override;
void OnTakenFromGraph(Graph* graph) override;
// ProcessNodeObserver implementation:
void OnProcessNodeAdded(const ProcessNode* process_node) override;
void OnPriorityChanged(const ProcessNode* process_node,
base::TaskPriority previous_value) override;
};
} // namespace policies
} // namespace performance_manager
#endif // COMPONENTS_PERFORMANCE_MANAGER_GRAPH_POLICIES_PROCESS_PRIORITY_POLICY_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 "components/performance_manager/graph/policies/process_priority_policy.h"
#include "base/test/bind_test_util.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "components/performance_manager/graph/process_node_impl.h"
#include "components/performance_manager/performance_manager_test_harness.h"
#include "components/performance_manager/public/performance_manager.h"
#include "components/performance_manager/render_process_user_data.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/test/navigation_simulator.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace performance_manager {
namespace policies {
namespace {
// Returns a priority that will lead to an opposite process priority.
base::TaskPriority GetOppositePriority(base::TaskPriority priority) {
switch (priority) {
case base::TaskPriority::BEST_EFFORT:
return base::TaskPriority::USER_BLOCKING;
case base::TaskPriority::USER_VISIBLE:
case base::TaskPriority::USER_BLOCKING:
break;
}
return base::TaskPriority::BEST_EFFORT;
}
void PostToggleProcessNodePriority(content::RenderProcessHost* rph) {
auto* rpud = RenderProcessUserData::GetForRenderProcessHost(rph);
auto* process_node = rpud->process_node();
PerformanceManager::CallOnGraph(
FROM_HERE,
base::BindLambdaForTesting([process_node](Graph* graph_unused) {
process_node->set_priority(
GetOppositePriority(process_node->priority()));
}));
}
class ProcessPriorityPolicyTest : public PerformanceManagerTestHarness {
public:
ProcessPriorityPolicyTest() {}
ProcessPriorityPolicyTest(const ProcessPriorityPolicyTest&) = delete;
ProcessPriorityPolicyTest(ProcessPriorityPolicyTest&&) = delete;
ProcessPriorityPolicyTest& operator=(const ProcessPriorityPolicyTest&) =
delete;
ProcessPriorityPolicyTest& operator=(ProcessPriorityPolicyTest&&) = delete;
~ProcessPriorityPolicyTest() override {}
void SetUp() override {
PerformanceManagerTestHarness::SetUp();
// It's safe to pass unretained as we clear the callback before being
// torn down.
ProcessPriorityPolicy::SetCallbackForTesting(
base::Bind(&ProcessPriorityPolicyTest::OnSetPriorityWrapper,
base::Unretained(this)));
}
void TearDown() override {
ProcessPriorityPolicy::ClearCallbackForTesting();
// Clean up the web contents, which should dispose of the page and frame
// nodes involved.
DeleteContents();
// The RenderProcessHosts seem to get leaked, or at least be still alive
// here, so explicitly detach from them in order to clean up the graph
// nodes.
RenderProcessUserData::DetachAndDestroyAll();
PerformanceManagerTestHarness::TearDown();
}
void RunUntilOnSetPriority() {
base::RunLoop run_loop;
quit_closure_ = run_loop.QuitClosure();
run_loop.Run();
quit_closure_.Reset();
}
// This is eventually invoked by the testing callback when the policy sets a
// process priority.
MOCK_METHOD2(OnSetPriority, void(content::RenderProcessHost*, bool));
private:
void OnSetPriorityWrapper(RenderProcessHostProxy rph_proxy, bool foreground) {
OnSetPriority(rph_proxy.Get(), foreground);
quit_closure_.Run();
}
base::RepeatingClosure quit_closure_;
};
} // namespace
TEST_F(ProcessPriorityPolicyTest, GraphReflectedToRenderProcessHost) {
// Create an instance of the process priority policy.
PerformanceManager::CallOnGraph(
FROM_HERE, base::Bind([](Graph* graph) {
std::unique_ptr<ProcessPriorityPolicy> policy(
new ProcessPriorityPolicy());
graph->PassToGraph(std::move(policy));
}));
// Set the active contents in the RenderViewHostTestHarness.
SetContents(CreateTestWebContents());
auto* rvh = web_contents()->GetRenderViewHost();
DCHECK(rvh);
auto* rph = rvh->GetProcess();
DCHECK(rph);
// Simulate a navigation so that graph nodes spring into existence.
content::NavigationSimulator::NavigateAndCommitFromBrowser(
web_contents(), GURL("https://www.foo.com/"));
// Expect a background priority override to be set for process creation.
// NOTE: This is going to change once we have provisional frames and the like,
// and can calculate meaningful process startup priorities.
EXPECT_CALL(*this, OnSetPriority(rph, false));
RunUntilOnSetPriority();
// Toggle the priority and expect it to change.
PostToggleProcessNodePriority(rph);
EXPECT_CALL(*this, OnSetPriority(rph, true));
RunUntilOnSetPriority();
testing::Mock::VerifyAndClearExpectations(this);
}
} // namespace policies
} // namespace performance_manager
......@@ -80,6 +80,9 @@ class Graph {
virtual std::vector<const PageNode*> GetAllPageNodes() const = 0;
virtual std::vector<const WorkerNode*> GetAllWorkerNodes() const = 0;
// Returns true if the graph is currently empty.
virtual bool IsEmpty() const = 0;
// Returns the associated UKM recorder if it is defined.
virtual ukm::UkmRecorder* GetUkmRecorder() const = 0;
......
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