blob: 04523ae5051782708fab95beb80fdd6a2c7d2ff2 [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 "components/performance_manager/graph/process_node_impl.h"
#include "base/process/process.h"
#include "base/test/bind_test_util.h"
#include "components/performance_manager/graph/frame_node_impl.h"
#include "components/performance_manager/test_support/graph_test_harness.h"
#include "components/performance_manager/test_support/mock_graphs.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, MeasureCPUUsage) {
auto process_node = CreateNode<ProcessNodeImpl>();
process_node->SetCPUUsage(1.0);
EXPECT_EQ(1.0, process_node->cpu_usage());
}
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->process().IsValid());
EXPECT_FALSE(process_node->exit_status());
constexpr int32_t kExitStatus = 0xF00;
process_node->SetProcessExitStatus(kExitStatus);
EXPECT_TRUE(process_node->exit_status());
EXPECT_EQ(kExitStatus, process_node->exit_status().value());
// Next go through PID->exit status.
const base::Process self = base::Process::Current();
const base::Time launch_time = base::Time::Now();
process_node->SetProcess(self.Duplicate(), launch_time);
EXPECT_TRUE(process_node->process().IsValid());
EXPECT_EQ(self.Pid(), process_node->process_id());
EXPECT_EQ(launch_time, process_node->launch_time());
// Resurrection should clear the exit status.
EXPECT_FALSE(process_node->exit_status());
EXPECT_EQ(0U, process_node->private_footprint_kb());
EXPECT_EQ(0U, process_node->resident_set_kb());
EXPECT_EQ(base::TimeDelta(), process_node->cumulative_cpu_usage());
constexpr base::TimeDelta kCpuUsage = base::TimeDelta::FromMicroseconds(1);
process_node->set_private_footprint_kb(10u);
process_node->set_resident_set_kb(20u);
process_node->set_cumulative_cpu_usage(kCpuUsage);
// Kill it again.
// Verify that the process is cleared, but the properties stick around.
process_node->SetProcessExitStatus(kExitStatus);
EXPECT_FALSE(process_node->process().IsValid());
EXPECT_EQ(self.Pid(), process_node->process_id());
EXPECT_EQ(launch_time, process_node->launch_time());
EXPECT_EQ(10u, process_node->private_footprint_kb());
EXPECT_EQ(20u, process_node->resident_set_kb());
EXPECT_EQ(kCpuUsage, process_node->cumulative_cpu_usage());
// Resurrect again and verify the launch time and measurements
// are cleared.
const base::Time launch2_time = launch_time + base::TimeDelta::FromSeconds(1);
process_node->SetProcess(self.Duplicate(), launch2_time);
EXPECT_EQ(launch2_time, process_node->launch_time());
EXPECT_EQ(0U, process_node->private_footprint_kb());
EXPECT_EQ(0U, process_node->resident_set_kb());
EXPECT_EQ(base::TimeDelta(), process_node->cumulative_cpu_usage());
}
TEST_F(ProcessNodeImplTest, GetPageNodeIfExclusive) {
{
MockSinglePageInSingleProcessGraph g(graph());
EXPECT_EQ(g.page.get(), g.process.get()->GetPageNodeIfExclusive());
}
{
MockSinglePageWithMultipleProcessesGraph g(graph());
EXPECT_EQ(g.page.get(), g.process.get()->GetPageNodeIfExclusive());
}
{
MockMultiplePagesInSingleProcessGraph g(graph());
EXPECT_FALSE(g.process.get()->GetPageNodeIfExclusive());
}
{
MockMultiplePagesWithMultipleProcessesGraph g(graph());
EXPECT_FALSE(g.process.get()->GetPageNodeIfExclusive());
EXPECT_EQ(g.other_page.get(),
g.other_process.get()->GetPageNodeIfExclusive());
}
}
namespace {
class LenientMockObserver : public ProcessNodeImpl::Observer {
public:
LenientMockObserver() {}
~LenientMockObserver() override {}
MOCK_METHOD1(OnProcessNodeAdded, void(const ProcessNode*));
MOCK_METHOD1(OnProcessLifetimeChange, void(const ProcessNode*));
MOCK_METHOD1(OnBeforeProcessNodeRemoved, void(const ProcessNode*));
MOCK_METHOD1(OnExpectedTaskQueueingDurationSample, void(const ProcessNode*));
MOCK_METHOD1(OnMainThreadTaskLoadIsLow, void(const ProcessNode*));
MOCK_METHOD1(OnAllFramesInProcessFrozen, void(const ProcessNode*));
void SetNotifiedProcessNode(const ProcessNode* process_node) {
notified_process_node_ = process_node;
}
const ProcessNode* TakeNotifiedProcessNode() {
const ProcessNode* node = notified_process_node_;
notified_process_node_ = nullptr;
return node;
}
private:
const ProcessNode* notified_process_node_ = nullptr;
};
using MockObserver = ::testing::StrictMock<LenientMockObserver>;
using testing::_;
using testing::Invoke;
} // namespace
TEST_F(ProcessNodeImplTest, ObserverWorks) {
MockObserver obs;
graph()->AddProcessNodeObserver(&obs);
// Create a page node and expect a matching call to "OnProcessNodeAdded".
EXPECT_CALL(obs, OnProcessNodeAdded(_))
.WillOnce(Invoke(&obs, &MockObserver::SetNotifiedProcessNode));
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(), base::Time::Now());
EXPECT_CALL(obs, OnProcessLifetimeChange(_));
process_node->SetProcessExitStatus(10);
EXPECT_CALL(obs, OnExpectedTaskQueueingDurationSample(_))
.WillOnce(Invoke(&obs, &MockObserver::SetNotifiedProcessNode));
process_node->SetExpectedTaskQueueingDuration(
base::TimeDelta::FromSeconds(1));
EXPECT_EQ(raw_process_node, obs.TakeNotifiedProcessNode());
EXPECT_CALL(obs, OnMainThreadTaskLoadIsLow(_))
.WillOnce(Invoke(&obs, &MockObserver::SetNotifiedProcessNode));
process_node->SetMainThreadTaskLoadIsLow(true);
EXPECT_EQ(raw_process_node, obs.TakeNotifiedProcessNode());
EXPECT_CALL(obs, OnAllFramesInProcessFrozen(_))
.WillOnce(Invoke(&obs, &MockObserver::SetNotifiedProcessNode));
process_node->OnAllFramesInProcessFrozenForTesting();
EXPECT_EQ(raw_process_node, obs.TakeNotifiedProcessNode());
// Release the page node and expect a call to "OnBeforeProcessNodeRemoved".
EXPECT_CALL(obs, OnBeforeProcessNodeRemoved(_))
.WillOnce(Invoke(&obs, &MockObserver::SetNotifiedProcessNode));
process_node.reset();
EXPECT_EQ(raw_process_node, obs.TakeNotifiedProcessNode());
graph()->RemoveProcessNodeObserver(&obs);
}
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());
// Simply test that the public interface impls yield the same result as their
// private counterpart.
const base::Process self = base::Process::Current();
const base::Time launch_time = base::Time::Now();
process_node->SetProcess(self.Duplicate(), launch_time);
EXPECT_EQ(process_node->process_id(), public_process_node->GetProcessId());
EXPECT_EQ(&process_node->process(), &public_process_node->GetProcess());
EXPECT_EQ(process_node->launch_time(), public_process_node->GetLaunchTime());
constexpr int32_t kExitStatus = 0xF00;
process_node->SetProcessExitStatus(kExitStatus);
EXPECT_EQ(process_node->exit_status(), public_process_node->GetExitStatus());
const 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 auto* frame_node : frame_nodes) {
const FrameNode* public_frame_node = frame_node;
EXPECT_TRUE(base::Contains(public_frame_nodes, public_frame_node));
}
decltype(public_frame_nodes) visited_frame_nodes;
public_process_node->VisitFrameNodes(base::BindLambdaForTesting(
[&visited_frame_nodes](const FrameNode* frame_node) -> bool {
visited_frame_nodes.insert(frame_node);
return true;
}));
EXPECT_EQ(public_frame_nodes, visited_frame_nodes);
process_node->SetExpectedTaskQueueingDuration(
base::TimeDelta::FromSeconds(1));
EXPECT_EQ(process_node->expected_task_queueing_duration(),
public_process_node->GetExpectedTaskQueueingDuration());
process_node->SetMainThreadTaskLoadIsLow(true);
EXPECT_EQ(process_node->main_thread_task_load_is_low(),
public_process_node->GetMainThreadTaskLoadIsLow());
process_node->SetCPUUsage(0.5);
EXPECT_EQ(process_node->cpu_usage(), public_process_node->GetCpuUsage());
process_node->set_cumulative_cpu_usage(base::TimeDelta::FromSeconds(1));
EXPECT_EQ(process_node->cumulative_cpu_usage(),
public_process_node->GetCumulativeCpuUsage());
process_node->set_private_footprint_kb(628);
EXPECT_EQ(process_node->private_footprint_kb(),
public_process_node->GetPrivateFootprintKb());
process_node->set_resident_set_kb(398);
EXPECT_EQ(process_node->resident_set_kb(),
public_process_node->GetResidentSetKb());
}
} // namespace performance_manager