blob: f620029857df32bf9218ca2da4e94e5797ea8a4f [file] [log] [blame]
// 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/graph/policies/working_set_trimmer_policy_chromeos.h"
#include "base/memory/memory_pressure_listener.h"
#include "components/performance_manager/graph/graph_impl_operations.h"
#include "components/performance_manager/graph/page_node_impl.h"
#include "components/performance_manager/graph/process_node_impl.h"
#include "components/performance_manager/performance_manager_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 policies {
class MockWorkingSetTrimmerPolicyChromeOS
: public WorkingSetTrimmerPolicyChromeOS {
public:
MockWorkingSetTrimmerPolicyChromeOS() {
trim_on_memory_pressure_enabled_ = true;
trim_on_freeze_enabled_ = true;
// Setup parameters for trimming on memory pressure.
trim_on_memory_pressure_params_.graph_walk_backoff_time =
base::TimeDelta::FromSeconds(30);
trim_on_memory_pressure_params_.node_invisible_time =
base::TimeDelta::FromSeconds(30);
trim_on_memory_pressure_params_.node_trim_backoff_time =
base::TimeDelta::FromSeconds(30);
}
~MockWorkingSetTrimmerPolicyChromeOS() override {}
base::MemoryPressureListener& listener() {
return memory_pressure_listener_.value();
}
base::TimeTicks get_last_graph_walk() { return last_graph_walk_; }
MOCK_METHOD1(TrimWorkingSet, bool(const ProcessNode*));
private:
DISALLOW_COPY_AND_ASSIGN(MockWorkingSetTrimmerPolicyChromeOS);
};
class WorkingSetTrimmerPolicyChromeOSTest : public GraphTestHarness {
public:
WorkingSetTrimmerPolicyChromeOSTest()
: GraphTestHarness(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
~WorkingSetTrimmerPolicyChromeOSTest() override {}
void SetUp() override {
GraphTestHarness::SetUp();
// Add our mock policy to the graph.
std::unique_ptr<MockWorkingSetTrimmerPolicyChromeOS> mock_policy(
new MockWorkingSetTrimmerPolicyChromeOS);
policy_ = mock_policy.get();
graph()->PassToGraph(std::move(mock_policy));
}
void TearDown() override {
policy_ = nullptr;
GraphTestHarness::TearDown();
}
MockWorkingSetTrimmerPolicyChromeOS* policy() { return policy_; }
base::TimeTicks NowTicks() { return task_env().NowTicks(); }
base::TimeTicks FastForwardBy(base::TimeDelta delta) {
task_env().FastForwardBy(delta);
return NowTicks();
}
private:
MockWorkingSetTrimmerPolicyChromeOS* policy_ = nullptr; // Not owned.
DISALLOW_COPY_AND_ASSIGN(WorkingSetTrimmerPolicyChromeOSTest);
};
// Validate that we don't walk again before the backoff period has expired.
TEST_F(WorkingSetTrimmerPolicyChromeOSTest, GraphWalkBackoffPeriod) {
// Since we've never walked the graph we should do so now.
const base::TimeTicks initial_walk_time = policy()->get_last_graph_walk();
ASSERT_EQ(initial_walk_time, base::TimeTicks());
policy()->listener().SimulatePressureNotification(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE);
FastForwardBy(base::TimeDelta::FromSeconds(1));
// Since we have never walked we expect that we walked it now, we confirm by
// checking the last walk time against the known clock.
const base::TimeTicks last_walk_time = policy()->get_last_graph_walk();
EXPECT_LT(initial_walk_time, last_walk_time);
policy()->listener().SimulatePressureNotification(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE);
FastForwardBy(base::TimeDelta::FromSeconds(1));
// We will not have caused a walk as the clock has not advanced beyond the
// backoff period.
EXPECT_EQ(last_walk_time, policy()->get_last_graph_walk());
}
// Validate that we will walk the graph again after the backoff period is
// expired.
TEST_F(WorkingSetTrimmerPolicyChromeOSTest, GraphWalkAfterBackoffPeriod) {
// Since we've never walked the graph we should do so now.
const base::TimeTicks initial_walk_time = policy()->get_last_graph_walk();
ASSERT_EQ(initial_walk_time, base::TimeTicks());
policy()->listener().SimulatePressureNotification(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE);
FastForwardBy(base::TimeDelta::FromSeconds(1));
// Since we have never walked we expect that we walked it now, we confirm by
// checking the last walk time against the known clock.
const base::TimeTicks last_walk_time = policy()->get_last_graph_walk();
EXPECT_LT(initial_walk_time, last_walk_time);
FastForwardBy(base::TimeDelta::FromDays(1));
policy()->listener().SimulatePressureNotification(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE);
// Finally advance the clock beyond the backoff period and it should allow it
// to walk again.
FastForwardBy(base::TimeDelta::FromSeconds(1));
const base::TimeTicks final_walk_time = policy()->get_last_graph_walk();
EXPECT_GT(final_walk_time, last_walk_time);
}
// This test will validate that we will NOT try to trim a node if it has not
// been invisible for long enough.
TEST_F(WorkingSetTrimmerPolicyChromeOSTest, DontTrimIfNotInvisibleLongEnough) {
// Create a simple graph
auto process_node = CreateNode<ProcessNodeImpl>();
auto page_node = CreateNode<PageNodeImpl>();
auto parent_frame =
CreateFrameNodeAutoId(process_node.get(), page_node.get());
// Since we've never walked the graph we should do so now.
const base::TimeTicks clock_time = NowTicks();
const base::TimeTicks initial_walk_time = policy()->get_last_graph_walk();
// Set the PageNode to invisible but the state change time to now, since it
// will not have been invisible long enough it will NOT trigger a call to
// TrimWorkingSet.
page_node->SetIsVisible(true); // Reset visibility and set invisible Now.
page_node->SetIsVisible(false); // Uses the testing clock.
EXPECT_CALL(*policy(), TrimWorkingSet(testing::_)).Times(0);
// Triger memory pressure and we should observe the walk.
policy()->listener().SimulatePressureNotification(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE);
FastForwardBy(base::TimeDelta::FromSeconds(1));
const base::TimeTicks current_walk_time = policy()->get_last_graph_walk();
EXPECT_EQ(clock_time, current_walk_time);
EXPECT_NE(current_walk_time, initial_walk_time);
}
// This test will validate that we skip a page node that doesn't have a main
// frame node.
TEST_F(WorkingSetTrimmerPolicyChromeOSTest, DontTrimIfNoMainFrame) {
// Create a lone page node.
auto page_node = CreateNode<PageNodeImpl>();
// Make sure the node is not visible for 1 day.
page_node->SetIsVisible(true); // Reset visibility and set invisible Now.
page_node->SetIsVisible(false); // Uses the testing clock.
FastForwardBy(base::TimeDelta::FromDays(1));
// We should not be called because we don't have a frame node or process node.
EXPECT_CALL(*policy(), TrimWorkingSet(testing::_)).Times(0);
// Triger memory pressure and we should observe the walk.
policy()->listener().SimulatePressureNotification(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE);
FastForwardBy(base::TimeDelta::FromDays(1));
}
// This test will validate that we WILL trim the working set if it has been
// invisible long enough.
TEST_F(WorkingSetTrimmerPolicyChromeOSTest, TrimIfInvisibleLongEnough) {
// Create a simple graph
auto process_node = CreateNode<ProcessNodeImpl>();
auto page_node = CreateNode<PageNodeImpl>();
auto parent_frame =
CreateFrameNodeAutoId(process_node.get(), page_node.get());
ASSERT_EQ(1u, graph()->GetAllPageNodes().size());
// Create a Process so this process node doesn't bail on Process.IsValid();
const base::Process self = base::Process::Current();
auto duplicate = self.Duplicate();
ASSERT_TRUE(duplicate.IsValid());
process_node->SetProcess(std::move(duplicate), base::Time::Now());
// Set it invisible using the current clock, then we will advance the clock
// and it should result in a TrimWorkingSet since it's been invisible long
// enough.
page_node->SetIsVisible(true); // Reset visibility and then set invisible.
page_node->SetIsVisible(false); // Uses the testing clock.
const base::TimeTicks cur_time =
FastForwardBy(base::TimeDelta::FromDays(365));
// We will attempt to trim to corresponding ProcessNode since we've been
// invisible long enough.
EXPECT_CALL(*policy(), TrimWorkingSet(process_node.get())).Times(1);
// Triger memory pressure and we should observe the walk since we've never
// walked before.
policy()->listener().SimulatePressureNotification(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE);
FastForwardBy(base::TimeDelta::FromSeconds(1));
// We should have triggered the walk and it should have trimmed.
EXPECT_EQ(cur_time, policy()->get_last_graph_walk());
}
} // namespace policies
} // namespace performance_manager