blob: 1447d211b38262c0d738add1ec423d194c7b27d6 [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// 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/process_node_impl.h"
#include <optional>
#include "base/containers/contains.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/read_only_shared_memory_region.h"
#include "base/process/process.h"
#include "base/scoped_observation.h"
#include "base/task/task_traits.h"
#include "base/test/bind.h"
#include "base/test/scoped_command_line.h"
#include "base/test/task_environment.h"
#include "base/trace_event/named_trigger.h"
#include "components/performance_manager/embedder/scoped_global_scenario_memory.h"
#include "components/performance_manager/graph/frame_node_impl.h"
#include "components/performance_manager/public/render_process_host_id.h"
#include "components/performance_manager/public/render_process_host_proxy.h"
#include "components/performance_manager/scenarios/browser_performance_scenarios.h"
#include "components/performance_manager/test_support/graph/mock_process_node_observer.h"
#include "components/performance_manager/test_support/graph_test_harness.h"
#include "components/performance_manager/test_support/mock_graphs.h"
#include "content/public/common/content_switches.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace performance_manager {
namespace {
class ProcessNodeImplTest : public GraphTestHarness {};
} // namespace
TEST_F(ProcessNodeImplTest, SafeDowncast) {
auto process = CreateNode<ProcessNodeImpl>();
ProcessNode* node = process.get();
EXPECT_EQ(process.get(), ProcessNodeImpl::FromNode(node));
NodeBase* base = process.get();
EXPECT_EQ(base, NodeBase::FromNode(node));
EXPECT_EQ(static_cast<Node*>(node), base->ToNode());
}
using ProcessNodeImplDeathTest = ProcessNodeImplTest;
TEST_F(ProcessNodeImplDeathTest, SafeDowncast) {
auto process = CreateNode<ProcessNodeImpl>();
ASSERT_DEATH_IF_SUPPORTED(FrameNodeImpl::FromNodeBase(process.get()), "");
}
TEST_F(ProcessNodeImplTest, ProcessLifeCycle) {
auto process_node = CreateNode<ProcessNodeImpl>();
// Test the potential lifecycles of a process node.
// First go to exited without an intervening process attached, as would happen
// in the case the process fails to start.
EXPECT_FALSE(process_node->GetProcess().IsValid());
EXPECT_FALSE(process_node->GetExitStatus());
constexpr int32_t kExitStatus = 0xF00;
process_node->SetProcessExitStatus(kExitStatus);
EXPECT_TRUE(process_node->GetExitStatus());
EXPECT_EQ(kExitStatus, process_node->GetExitStatus().value());
// Next go through PID->exit status.
const base::Process self = base::Process::Current();
const base::TimeTicks launch_time = base::TimeTicks::Now();
process_node->SetProcess(self.Duplicate(), launch_time);
EXPECT_TRUE(process_node->GetProcess().IsValid());
EXPECT_EQ(self.Pid(), process_node->GetProcessId());
EXPECT_EQ(launch_time, process_node->GetLaunchTime());
// Resurrection should clear the exit status.
EXPECT_FALSE(process_node->GetExitStatus());
EXPECT_EQ(0U, process_node->GetPrivateFootprintKb());
EXPECT_EQ(0U, process_node->GetResidentSetKb());
process_node->set_private_footprint_kb(10u);
process_node->set_resident_set_kb(20u);
// Kill it again.
// Verify that the process is cleared, but the properties stick around.
process_node->SetProcessExitStatus(kExitStatus);
EXPECT_FALSE(process_node->GetProcess().IsValid());
EXPECT_EQ(self.Pid(), process_node->GetProcessId());
EXPECT_EQ(launch_time, process_node->GetLaunchTime());
EXPECT_EQ(10u, process_node->GetPrivateFootprintKb());
EXPECT_EQ(20u, process_node->GetResidentSetKb());
// Resurrect again and verify the launch time and measurements
// are cleared.
const base::TimeTicks launch2_time = launch_time + base::Seconds(1);
process_node->SetProcess(self.Duplicate(), launch2_time);
EXPECT_EQ(launch2_time, process_node->GetLaunchTime());
EXPECT_EQ(0U, process_node->GetPrivateFootprintKb());
EXPECT_EQ(0U, process_node->GetResidentSetKb());
}
namespace {
class MockObserver : public MockProcessNodeObserver {
public:
explicit MockObserver(Graph* graph = nullptr) {
// If a `graph` is passed, automatically start observing it.
if (graph) {
scoped_observation_.Observe(graph);
}
}
void SetNotifiedProcessNode(const ProcessNode* process_node) {
notified_process_node_ = process_node;
}
void TestNotifiedProcessNode(const ProcessNode* process_node) {
EXPECT_EQ(notified_process_node_, process_node);
}
void ExpectNoEdges(const ProcessNode* process_node) {
// Node should be created without edges.
EXPECT_TRUE(process_node->GetFrameNodes().empty());
EXPECT_TRUE(process_node->GetWorkerNodes().empty());
}
const ProcessNode* TakeNotifiedProcessNode() {
const ProcessNode* node = notified_process_node_;
notified_process_node_ = nullptr;
return node;
}
private:
base::ScopedObservation<Graph, ProcessNodeObserver> scoped_observation_{this};
raw_ptr<const ProcessNode, DanglingUntriaged> notified_process_node_ =
nullptr;
};
using ::testing::_;
using ::testing::DoAll;
using ::testing::InSequence;
using ::testing::Invoke;
using ::testing::InvokeWithoutArgs;
using ::testing::Return;
} // namespace
TEST_F(ProcessNodeImplTest, ObserverWorks) {
MockObserver head_obs;
MockObserver obs;
MockObserver tail_obs;
graph()->AddProcessNodeObserver(&head_obs);
graph()->AddProcessNodeObserver(&obs);
graph()->AddProcessNodeObserver(&tail_obs);
// Remove observers at the head and tail of the list inside a callback, and
// expect that `obs` is still notified correctly.
EXPECT_CALL(head_obs, OnBeforeProcessNodeAdded(_))
.WillOnce(InvokeWithoutArgs([&] {
graph()->RemoveProcessNodeObserver(&head_obs);
graph()->RemoveProcessNodeObserver(&tail_obs);
}));
// `tail_obs` should not be notified as it was removed.
EXPECT_CALL(tail_obs, OnBeforeProcessNodeAdded(_)).Times(0);
// Create a page node and expect a matching call to both "OnBeforeProcessNodeAdded" and
// "OnProcessNodeAdded".
{
InSequence seq;
EXPECT_CALL(obs, OnBeforeProcessNodeAdded(_))
.WillOnce(DoAll(Invoke(&obs, &MockObserver::SetNotifiedProcessNode),
Invoke(&obs, &MockObserver::ExpectNoEdges)));
EXPECT_CALL(obs, OnProcessNodeAdded(_))
.WillOnce(Invoke(&obs, &MockObserver::TestNotifiedProcessNode));
}
auto process_node = CreateNode<ProcessNodeImpl>();
const ProcessNode* raw_process_node = process_node.get();
EXPECT_EQ(raw_process_node, obs.TakeNotifiedProcessNode());
// Test process creation and exit events.
EXPECT_CALL(obs, OnProcessLifetimeChange(_));
process_node->SetProcess(base::Process::Current(),
/* launch_time=*/base::TimeTicks::Now());
EXPECT_CALL(obs, OnProcessLifetimeChange(_));
process_node->SetProcessExitStatus(10);
EXPECT_CALL(obs, OnMainThreadTaskLoadIsLow(_))
.WillOnce(Invoke(&obs, &MockObserver::SetNotifiedProcessNode));
process_node->SetMainThreadTaskLoadIsLow(true);
EXPECT_EQ(raw_process_node, obs.TakeNotifiedProcessNode());
// This call does nothing as the priority is initialized at HIGHEST.
EXPECT_EQ(base::TaskPriority::HIGHEST, process_node->GetPriority());
process_node->set_priority(base::TaskPriority::HIGHEST);
// This call should fire a notification.
EXPECT_CALL(obs, OnPriorityChanged(_, base::TaskPriority::HIGHEST));
process_node->set_priority(base::TaskPriority::LOWEST);
EXPECT_CALL(obs, OnAllFramesInProcessFrozen(_))
.WillOnce(Invoke(&obs, &MockObserver::SetNotifiedProcessNode));
process_node->OnAllFramesInProcessFrozenForTesting();
EXPECT_EQ(raw_process_node, obs.TakeNotifiedProcessNode());
// Re-entrant iteration should work.
EXPECT_CALL(obs, OnMainThreadTaskLoadIsLow(raw_process_node))
.WillOnce(InvokeWithoutArgs([&] {
process_node->set_priority(base::TaskPriority::USER_BLOCKING);
}));
EXPECT_CALL(obs, OnPriorityChanged(raw_process_node, _));
process_node->SetMainThreadTaskLoadIsLow(false);
// Release the page node and expect a call to both "OnBeforeProcessNodeRemoved" and
// "OnProcessNodeRemoved".
{
InSequence seq;
EXPECT_CALL(obs, OnBeforeProcessNodeRemoved(raw_process_node));
EXPECT_CALL(obs, OnProcessNodeRemoved(raw_process_node));
}
process_node.reset();
graph()->RemoveProcessNodeObserver(&obs);
}
TEST_F(ProcessNodeImplTest, ConstructionArguments_Browser) {
auto process_node = CreateNode<ProcessNodeImpl>(BrowserProcessNodeTag{});
EXPECT_EQ(content::PROCESS_TYPE_BROWSER, process_node->GetProcessType());
}
TEST_F(ProcessNodeImplTest, ConstructionArguments_Renderer) {
constexpr RenderProcessHostId kRenderProcessHostId =
RenderProcessHostId(0xF0B);
auto process_node = CreateNode<ProcessNodeImpl>(
RenderProcessHostProxy::CreateForTesting(kRenderProcessHostId));
EXPECT_EQ(content::PROCESS_TYPE_RENDERER, process_node->GetProcessType());
EXPECT_EQ(kRenderProcessHostId,
process_node->GetRenderProcessHostProxy().render_process_host_id());
}
TEST_F(ProcessNodeImplTest, ConstructionArguments_NonRenderer) {
constexpr BrowserChildProcessHostId kBrowserChildProcessHostId =
BrowserChildProcessHostId(0xF0B);
auto process_node = CreateNode<ProcessNodeImpl>(
content::PROCESS_TYPE_GPU, BrowserChildProcessHostProxy::CreateForTesting(
kBrowserChildProcessHostId));
EXPECT_EQ(content::PROCESS_TYPE_GPU, process_node->GetProcessType());
EXPECT_EQ(kBrowserChildProcessHostId,
process_node->GetBrowserChildProcessHostProxy()
.browser_child_process_host_id());
}
TEST_F(ProcessNodeImplTest, PublicInterface) {
auto process_node = CreateNode<ProcessNodeImpl>();
const ProcessNode* public_process_node = process_node.get();
// Create a small frame-tree so that GetFrameNodes can be well tested.
auto page_node = CreateNode<PageNodeImpl>();
auto main_frame_node =
CreateFrameNodeAutoId(process_node.get(), page_node.get());
auto child_frame_node = CreateFrameNodeAutoId(
process_node.get(), page_node.get(), main_frame_node.get());
const std::string kMetricsName("TestUtilityProcess");
process_node->SetProcessMetricsName(kMetricsName);
EXPECT_EQ(process_node->GetMetricsName(), kMetricsName);
process_node->SetMainThreadTaskLoadIsLow(true);
EXPECT_TRUE(process_node->GetMainThreadTaskLoadIsLow());
// For properties returning nodes, simply test that the public interface impls
// yield the same result as their private counterpart.
auto frame_nodes = process_node->frame_nodes();
auto public_frame_nodes = public_process_node->GetFrameNodes();
EXPECT_EQ(frame_nodes.size(), public_frame_nodes.size());
for (const FrameNodeImpl* frame_node : frame_nodes) {
const FrameNode* public_frame_node = frame_node;
EXPECT_TRUE(base::Contains(public_frame_nodes, public_frame_node));
}
}
TEST_F(ProcessNodeImplTest, InitializeChildProcessCoordination) {
auto process_node = CreateNode<ProcessNodeImpl>();
// No global memory mapped. ProcessNodeImpl automatically creates a process
// memory region on request.
process_node->InitializeChildProcessCoordination(
0u, base::BindLambdaForTesting(
[&](base::ReadOnlySharedMemoryRegion global_region,
base::ReadOnlySharedMemoryRegion process_region) {
EXPECT_FALSE(global_region.IsValid());
EXPECT_TRUE(process_region.IsValid());
})
.Then(task_env().QuitClosure()));
task_env().RunUntilQuit();
// Map global memory.
ScopedGlobalScenarioMemory global_shared_memory;
process_node->InitializeChildProcessCoordination(
0u, base::BindLambdaForTesting(
[&](base::ReadOnlySharedMemoryRegion global_region,
base::ReadOnlySharedMemoryRegion process_region) {
EXPECT_TRUE(global_region.IsValid());
EXPECT_TRUE(process_region.IsValid());
})
.Then(task_env().QuitClosure()));
task_env().RunUntilQuit();
// In single process mode, memory shouldn't be shared even if it's mapped,
// because the request isn't actually sent from a different process.
base::test::ScopedCommandLine scoped_command_line;
scoped_command_line.GetProcessCommandLine()->AppendSwitch(
switches::kSingleProcess);
process_node->InitializeChildProcessCoordination(
0u, base::BindLambdaForTesting(
[&](base::ReadOnlySharedMemoryRegion global_region,
base::ReadOnlySharedMemoryRegion process_region) {
EXPECT_FALSE(global_region.IsValid());
EXPECT_FALSE(process_region.IsValid());
})
.Then(task_env().QuitClosure()));
task_env().RunUntilQuit();
}
} // namespace performance_manager