// Copyright 2019 The Chromium Authors
// 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/policies/working_set_trimmer_policy_chromeos.h"
#include <memory>
#include <vector>
#include "ash/components/arc/mojom/process.mojom.h"
#include "base/memory/memory_pressure_listener.h"
#include "base/memory/raw_ptr.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/ash/arc/process/arc_process.h"
#include "chrome/browser/ash/arc/process/arc_process_service.h"
#include "chrome/browser/ash/arc/vmm/arcvm_working_set_trim_executor.h"
#include "chrome/browser/performance_manager/policies/policy_features.h"
#include "chrome/browser/performance_manager/policies/working_set_trimmer_policy_arcvm.h"
#include "chromeos/dbus/power/fake_power_manager_client.h"
#include "chromeos/dbus/power_manager/suspend.pb.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 mechanism {
class MockWorkingSetTrimmerChromeOS : public WorkingSetTrimmerChromeOS {
public:
MockWorkingSetTrimmerChromeOS() {
ON_CALL(*this, TrimArcVmWorkingSet)
.WillByDefault(testing::Invoke(
this, &MockWorkingSetTrimmerChromeOS::DefaultTrimArcVmWorkingSet));
}
MOCK_METHOD3(TrimArcVmWorkingSet,
void(WorkingSetTrimmerChromeOS::TrimArcVmWorkingSetCallback,
ArcVmReclaimType,
int));
private:
void DefaultTrimArcVmWorkingSet(TrimArcVmWorkingSetCallback callback,
ArcVmReclaimType reclaim_type,
int page_limit) {
std::move(callback).Run(true, "");
}
};
} // namespace mechanism
namespace policies {
namespace {
constexpr auto kNotFirstReclaimPostBoot = performance_manager::policies::
WorkingSetTrimmerPolicyArcVm::kNotFirstReclaimPostBoot;
constexpr auto kYesFirstReclaimPostBoot = performance_manager::policies::
WorkingSetTrimmerPolicyArcVm::kYesFirstReclaimPostBoot;
using testing::_;
using testing::Exactly;
using testing::Expectation;
using testing::InSequence;
using testing::Invoke;
using testing::Return;
// This method as it describes will get the milliseconds since system boot for
// some past time this is necessary because of the JVM using int64 ms since
// system update.
int64_t GetSystemTimeInPastAsMsSinceUptime(base::TimeDelta delta) {
const base::Time cur_time = base::Time::NowFromSystemTime();
return (cur_time - delta).InMillisecondsSinceUnixEpoch();
}
class ScopedTestArcVmDelegate
: public WorkingSetTrimmerPolicyChromeOS::ArcVmDelegate {
public:
ScopedTestArcVmDelegate(WorkingSetTrimmerPolicyChromeOS* policy,
mechanism::ArcVmReclaimType eligibility,
bool is_first_trim_post_boot)
: policy_(policy),
eligibility_(eligibility),
is_first_trim_post_boot_(is_first_trim_post_boot) {
policy_->set_arcvm_delegate_for_testing(this);
}
~ScopedTestArcVmDelegate() override {
policy_->set_arcvm_delegate_for_testing(nullptr);
}
ScopedTestArcVmDelegate(const ScopedTestArcVmDelegate&) = delete;
ScopedTestArcVmDelegate& operator=(const ScopedTestArcVmDelegate&) = delete;
// WorkingSetTrimmerPolicyChromeOS::ArcVmDelegate overrides:
mechanism::ArcVmReclaimType IsEligibleForReclaim(
const base::TimeDelta& arcvm_inactivity_time,
mechanism::ArcVmReclaimType trim_once_type_after_arcvm_boot,
bool* is_first_trim_post_boot) override {
if (is_first_trim_post_boot)
*is_first_trim_post_boot = is_first_trim_post_boot_;
return eligibility_;
}
void set_eligibility(mechanism::ArcVmReclaimType eligibility) {
eligibility_ = eligibility;
}
void set_is_first_trim_post_boot(bool is_first_trim_post_boot) {
is_first_trim_post_boot_ = is_first_trim_post_boot;
}
private:
const raw_ptr<WorkingSetTrimmerPolicyChromeOS> policy_;
mechanism::ArcVmReclaimType eligibility_;
bool is_first_trim_post_boot_;
};
} // namespace
class MockWorkingSetTrimmerPolicyChromeOS
: public WorkingSetTrimmerPolicyChromeOS {
public:
MockWorkingSetTrimmerPolicyChromeOS() : WorkingSetTrimmerPolicyChromeOS() {
// Setup our default configuration
set_trim_on_freeze(true);
set_trim_arc_on_memory_pressure(false);
set_trim_arcvm_on_memory_pressure(false);
params().graph_walk_backoff_time = base::Seconds(30);
params().node_invisible_time = base::Seconds(30);
params().node_trim_backoff_time = base::Seconds(30);
params().arc_process_trim_backoff_time = base::TimeDelta::Min();
params().arc_process_inactivity_time = base::TimeDelta::Min();
params().trim_arc_aggressive = false;
params().arcvm_inactivity_time = base::TimeDelta::Min();
params().arcvm_trim_backoff_time = base::TimeDelta::Min();
params().trim_arcvm_on_critical_pressure = false;
params().trim_arcvm_on_first_memory_pressure_after_arcvm_boot = false;
// Setup some default invocations.
ON_CALL(*this, OnMemoryPressure(_))
.WillByDefault(Invoke(
this,
&MockWorkingSetTrimmerPolicyChromeOS::DefaultOnMemoryPressure));
ON_CALL(*this, TrimArcProcesses)
.WillByDefault(Invoke(
this,
&MockWorkingSetTrimmerPolicyChromeOS::DefaultTrimArcProcesses));
ON_CALL(*this, TrimReceivedArcProcesses)
.WillByDefault(Invoke(this, &MockWorkingSetTrimmerPolicyChromeOS::
DefaultTrimReceivedArcProcesses));
ON_CALL(*this, TrimArcVmProcesses)
.WillByDefault(Invoke(
this,
&MockWorkingSetTrimmerPolicyChromeOS::DefaultTrimArcVmProcesses));
ON_CALL(*this, OnTrimArcVmProcesses)
.WillByDefault(Invoke(
this,
&MockWorkingSetTrimmerPolicyChromeOS::DefaultOnTrimArcVmProcesses));
ON_CALL(*this, OnArcVmTrimEnded)
.WillByDefault(Invoke(
this,
&MockWorkingSetTrimmerPolicyChromeOS::DefaultOnArcVmTrimEnded));
ON_CALL(*this, GetTrimmer)
.WillByDefault(Invoke(
this, &MockWorkingSetTrimmerPolicyChromeOS::DefaultGetTrimmer));
}
MockWorkingSetTrimmerPolicyChromeOS(
const MockWorkingSetTrimmerPolicyChromeOS&) = delete;
MockWorkingSetTrimmerPolicyChromeOS& operator=(
const MockWorkingSetTrimmerPolicyChromeOS&) = delete;
~MockWorkingSetTrimmerPolicyChromeOS() override {}
base::MemoryPressureListener& listener() {
return memory_pressure_listener_.value();
}
base::TimeTicks get_last_graph_walk() {
return last_graph_walk_ ? *last_graph_walk_ : base::TimeTicks();
}
// Allows us to tweak the tests parameters per test.
features::TrimOnMemoryPressureParams& params() { return params_; }
// Mock methods related to tab (renderer) per process reclaim.
MOCK_METHOD1(TrimWorkingSet, void(const ProcessNode*));
MOCK_METHOD1(OnMemoryPressure,
void(base::MemoryPressureListener::MemoryPressureLevel level));
// Mock methods related to ARC process trimming.
MOCK_METHOD0(TrimArcProcesses, void(void));
MOCK_METHOD2(TrimReceivedArcProcesses,
void(int, arc::ArcProcessService::OptionalArcProcessList));
MOCK_METHOD1(IsArcProcessEligibleForReclaim, bool(const arc::ArcProcess&));
MOCK_METHOD1(TrimArcProcess, void(const base::ProcessId));
// Mock methods related to ARCVM process trimming.
MOCK_METHOD1(TrimArcVmProcesses,
void(base::MemoryPressureListener::MemoryPressureLevel));
MOCK_METHOD4(OnTrimArcVmProcesses,
void(mechanism::ArcVmReclaimType, bool, int, int));
MOCK_METHOD2(OnArcVmTrimEnded, void(mechanism::ArcVmReclaimType, bool));
MOCK_METHOD0(GetTrimmer, mechanism::WorkingSetTrimmerChromeOS*(void));
// Exposes the default implementations so they can be used in tests.
void DefaultOnMemoryPressure(
base::MemoryPressureListener::MemoryPressureLevel level) {
WorkingSetTrimmerPolicyChromeOS::OnMemoryPressure(level);
}
void DefaultTrimArcProcesses() {
WorkingSetTrimmerPolicyChromeOS::TrimArcProcesses();
}
void DefaultTrimReceivedArcProcesses(
int available_to_trim,
arc::ArcProcessService::OptionalArcProcessList procs) {
WorkingSetTrimmerPolicyChromeOS::TrimReceivedArcProcesses(available_to_trim,
std::move(procs));
}
bool DefaultIsArcProcessEligibleForReclaim(const arc::ArcProcess& proc) {
return WorkingSetTrimmerPolicyChromeOS::IsArcProcessEligibleForReclaim(
proc);
}
void DefaultTrimArcVmProcesses(
base::MemoryPressureListener::MemoryPressureLevel level) {
WorkingSetTrimmerPolicyChromeOS::TrimArcVmProcesses(level);
}
void DefaultOnTrimArcVmProcesses(mechanism::ArcVmReclaimType reclaim_type,
bool is_first_trim_post_boot,
int pages_per_minute,
int max_pages_per_iteration) {
WorkingSetTrimmerPolicyChromeOS::OnTrimArcVmProcesses(
reclaim_type, is_first_trim_post_boot, pages_per_minute,
max_pages_per_iteration);
}
void DefaultOnArcVmTrimEnded(mechanism::ArcVmReclaimType reclaim_type,
bool success) {
WorkingSetTrimmerPolicyChromeOS::OnArcVmTrimEnded(reclaim_type, success);
}
mechanism::WorkingSetTrimmerChromeOS* DefaultGetTrimmer() {
return WorkingSetTrimmerPolicyChromeOS::GetTrimmer();
}
void trim_arc_on_memory_pressure(bool enabled) {
set_trim_arc_on_memory_pressure(enabled);
}
void trim_arcvm_on_memory_pressure(bool enabled) {
set_trim_arcvm_on_memory_pressure(enabled);
}
};
class PageNodeContext {
public:
TestNodeWrapper<ProcessNodeImpl> process_node_;
TestNodeWrapper<PageNodeImpl> page_node_;
TestNodeWrapper<FrameNodeImpl> parent_frame_;
PageNodeContext() = default;
~PageNodeContext() = default;
PageNodeContext(const PageNodeContext&) = delete;
PageNodeContext& operator=(const PageNodeContext&) = delete;
};
class WorkingSetTrimmerPolicyChromeOSTest : public GraphTestHarness {
public:
WorkingSetTrimmerPolicyChromeOSTest()
: GraphTestHarness(base::test::TaskEnvironment::TimeSource::MOCK_TIME),
run_loop_(std::make_unique<base::RunLoop>()) {}
WorkingSetTrimmerPolicyChromeOSTest(
const WorkingSetTrimmerPolicyChromeOSTest&) = delete;
WorkingSetTrimmerPolicyChromeOSTest& operator=(
const WorkingSetTrimmerPolicyChromeOSTest&) = delete;
~WorkingSetTrimmerPolicyChromeOSTest() override {}
void SetUp() override {
chromeos::PowerManagerClient::InitializeFake();
CreateTrimmer();
GraphTestHarness::SetUp();
RecreatePolicy(base::BindLambdaForTesting(
[](MockWorkingSetTrimmerPolicyChromeOS*) {}));
}
void TearDown() override {
// Fix flakiness due to WorkingSetTrimmerPolicyChromeOS's weak ptr factory
// getting destroyed and causing a weak ptr to get invalidated on a
// different sequenced thread from where it was bound.
task_env().RunUntilIdle();
TakePolicyFromGraph();
GraphTestHarness::TearDown();
chromeos::PowerManagerClient::Shutdown();
}
void DefaultOnTrimArcVmProcessesAndQuit(
mechanism::ArcVmReclaimType reclaim_type,
bool is_first_trim_post_boot,
int pages_per_minute,
int max_pages_per_iteration) {
policy()->DefaultOnTrimArcVmProcesses(reclaim_type, is_first_trim_post_boot,
pages_per_minute,
max_pages_per_iteration);
run_loop()->Quit();
}
void DefaultOnArcVmTrimEndedAndQuit(mechanism::ArcVmReclaimType reclaim_type,
bool success) {
policy()->DefaultOnArcVmTrimEnded(reclaim_type, success);
run_loop()->Quit();
}
// Creates a new policy and runs the |callback| with the policy before passing
// it to the graph().
void RecreatePolicy(
base::OnceCallback<void(MockWorkingSetTrimmerPolicyChromeOS* policy)>
callback) {
if (policy_)
graph()->TakeFromGraph(policy_);
// Add our mock policy to the graph.
auto mock_policy = std::make_unique<
testing::NiceMock<MockWorkingSetTrimmerPolicyChromeOS>>();
policy_ = mock_policy.get();
std::move(callback).Run(policy_.get());
graph()->PassToGraph(std::move(mock_policy));
}
void TakePolicyFromGraph() {
graph()->TakeFromGraph(policy_);
policy_ = nullptr;
}
void CreateTrimmer() {
trimmer_ = std::make_unique<mechanism::MockWorkingSetTrimmerChromeOS>();
}
mechanism::MockWorkingSetTrimmerChromeOS* trimmer() const {
return trimmer_.get();
}
void RecreateRunLoop() { run_loop_ = std::make_unique<base::RunLoop>(); }
base::RunLoop* run_loop() { return run_loop_.get(); }
MockWorkingSetTrimmerPolicyChromeOS* policy() { return policy_; }
features::TrimOnMemoryPressureParams params() { return policy()->params(); }
base::TimeTicks NowTicks() { return task_env().NowTicks(); }
base::TimeTicks FastForwardBy(base::TimeDelta delta) {
task_env().FastForwardBy(delta);
return NowTicks();
}
void ExpectNoReclaim();
void ExpectFullReclaim(bool is_first_reclaim, int computed_page_limit);
void ExpectDropPageCaches();
std::unique_ptr<PageNodeContext> CreateInvisiblePage() {
// Create a simple graph
auto context = std::make_unique<PageNodeContext>();
context->process_node_ = CreateNode<ProcessNodeImpl>();
context->page_node_ = CreateNode<PageNodeImpl>();
context->parent_frame_ = CreateFrameNodeAutoId(context->process_node_.get(),
context->page_node_.get());
// Create a Process so this process node doesn't bail on Process.IsValid();
context->process_node_->SetProcess(base::Process::Current().Duplicate(),
/* launch_time=*/base::TimeTicks::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.
context->page_node_->SetIsVisible(
true); // Reset visibility and then set invisible.
context->page_node_->SetIsVisible(false); // Uses the testing clock.
return context;
}
private:
std::unique_ptr<base::RunLoop> run_loop_;
raw_ptr<MockWorkingSetTrimmerPolicyChromeOS,
DanglingUntriaged>
policy_ = nullptr; // Not owned.
std::unique_ptr<mechanism::MockWorkingSetTrimmerChromeOS> trimmer_;
};
// Validate that we don't walk again before the backoff period has expired.
// TODO(crbug.com/40673488): Test is flaky
TEST_F(WorkingSetTrimmerPolicyChromeOSTest, DISABLED_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::Seconds(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::Seconds(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.
// TODO(crbug.com/40673488): Test is flaky
TEST_F(WorkingSetTrimmerPolicyChromeOSTest,
DISABLED_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::Seconds(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::Days(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::Seconds(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.
// TODO(crbug.com/40673488): Test is flaky
TEST_F(WorkingSetTrimmerPolicyChromeOSTest,
DISABLED_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::Seconds(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::Days(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::Days(1));
}
// This test will validate that we WILL trim the working set if it has been
// invisible long enough.
TEST_F(WorkingSetTrimmerPolicyChromeOSTest, TrimIfInvisibleLongEnough) {
auto page = CreateInvisiblePage();
ASSERT_EQ(1u, graph()->GetAllPageNodes().size());
const base::TimeTicks cur_time = FastForwardBy(base::Days(365));
// We will attempt to trim to corresponding ProcessNode since we've been
// invisible long enough.
EXPECT_CALL(*policy(), TrimWorkingSet(page->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::Seconds(1));
// We should have triggered the walk and it should have trimmed.
EXPECT_EQ(cur_time, policy()->get_last_graph_walk());
}
TEST_F(WorkingSetTrimmerPolicyChromeOSTest, DoNotTrimWhileSuspended) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(features::kDisableTrimmingWhileSuspended);
RecreatePolicy(
base::BindLambdaForTesting([](MockWorkingSetTrimmerPolicyChromeOS*) {}));
auto page = CreateInvisiblePage();
FastForwardBy(base::Minutes(15) + base::Seconds(1));
chromeos::FakePowerManagerClient::Get()->SendSuspendImminent(
power_manager::SuspendImminent_Reason_OTHER);
EXPECT_CALL(*policy(), TrimWorkingSet(testing::_)).Times(0);
policy()->listener().SimulatePressureNotification(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE);
FastForwardBy(base::Seconds(1));
}
TEST_F(WorkingSetTrimmerPolicyChromeOSTest, DoNotTrimJustAfterResumed) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(features::kDisableTrimmingWhileSuspended);
RecreatePolicy(
base::BindLambdaForTesting([](MockWorkingSetTrimmerPolicyChromeOS*) {}));
auto page = CreateInvisiblePage();
FastForwardBy(base::Minutes(15) + base::Seconds(1));
chromeos::FakePowerManagerClient::Get()->SendSuspendImminent(
power_manager::SuspendImminent_Reason_OTHER);
chromeos::FakePowerManagerClient::Get()->SendSuspendDone();
EXPECT_CALL(*policy(), TrimWorkingSet(testing::_)).Times(0);
policy()->listener().SimulatePressureNotification(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE);
FastForwardBy(base::Seconds(1));
}
TEST_F(WorkingSetTrimmerPolicyChromeOSTest, Trim15MinutesAfterResumed) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(features::kDisableTrimmingWhileSuspended);
RecreatePolicy(
base::BindLambdaForTesting([](MockWorkingSetTrimmerPolicyChromeOS*) {}));
auto page = CreateInvisiblePage();
FastForwardBy(base::Minutes(15) + base::Seconds(1));
chromeos::FakePowerManagerClient::Get()->SendSuspendImminent(
power_manager::SuspendImminent_Reason_OTHER);
chromeos::FakePowerManagerClient::Get()->SendSuspendDone();
FastForwardBy(base::Minutes(15) + base::Seconds(1));
EXPECT_CALL(*policy(), TrimWorkingSet(page->process_node_.get())).Times(1);
policy()->listener().SimulatePressureNotification(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE);
FastForwardBy(base::Seconds(1));
}
// This test is a simple smoke test to make sure that ARC process trimming
// doesn't run if it's not enabled.
TEST_F(WorkingSetTrimmerPolicyChromeOSTest, ArcDontTrimOnlyIfDisabled) {
// The Mock defaults to ARC trimming disabled, so just validate that we're not
// actually trimming.
policy()->trim_arc_on_memory_pressure(false);
EXPECT_CALL(*policy(), TrimArcProcesses).Times(0);
FastForwardBy(base::Seconds(1));
policy()->listener().SimulatePressureNotification(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE);
}
// TODO(crbug.com/40748300) Re-enable test
TEST_F(WorkingSetTrimmerPolicyChromeOSTest, DISABLED_ArcTrimOnlyIfEnabled) {
policy()->trim_arc_on_memory_pressure(true);
FastForwardBy(base::Seconds(1));
EXPECT_CALL(*policy(), TrimArcProcesses).Times(1);
policy()->listener().SimulatePressureNotification(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE);
}
// This test will validate that we don't fetch the ARC process list at an
// interval that is greater than the configured value, regardless of memory
// pressure levels.
TEST_F(WorkingSetTrimmerPolicyChromeOSTest,
ArcFetchProcessesAtConfiguredInterval) {
// Our test setup will validate that we don't attempt to try to fetch and look
// for ARC processes more than the configured frequency (in this case 60s).
policy()->trim_arc_on_memory_pressure(true);
policy()->params().arc_process_list_fetch_backoff_time = base::Seconds(60);
// We're going to cause a moderate pressure notification twice, but we only
// expect to attempt to fetch the ARC processes once because of our configured
// backoff time.
EXPECT_CALL(*policy(), TrimArcProcesses).Times(Exactly(1));
FastForwardBy(base::Seconds(12));
policy()->listener().SimulatePressureNotification(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE);
FastForwardBy(base::Seconds(12));
policy()->listener().SimulatePressureNotification(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE);
// Now as we pass through the backoff time we expect that we can be called
// again.
EXPECT_CALL(*policy(), TrimArcProcesses).Times(Exactly(1));
FastForwardBy(policy()->params().arc_process_list_fetch_backoff_time);
policy()->listener().SimulatePressureNotification(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE);
}
// This test validates that a process which is focused is not eligible.
TEST_F(WorkingSetTrimmerPolicyChromeOSTest, ArcProcessFocusedIsNotEligible) {
policy()->trim_arc_on_memory_pressure(true);
// This ARC process is focused and should not be eligible for reclaim.
arc::ArcProcess mock_arc_proc(
/*nspid=*/0, /*pid=*/1234, "mock process",
/*state=*/arc::mojom::ProcessState::SERVICE, /*is_focused=*/true,
/*last activity=*/
GetSystemTimeInPastAsMsSinceUptime(base::Minutes(2)));
EXPECT_FALSE(policy()->DefaultIsArcProcessEligibleForReclaim(mock_arc_proc));
}
// This test validates that a process which is focused is not eligible.
TEST_F(WorkingSetTrimmerPolicyChromeOSTest, ArcBackgroundProtectIsNotEligible) {
policy()->trim_arc_on_memory_pressure(true);
// This validates that a background protected ARC app is not eligible for
// reclaim, the IMPORTANT_BACKGROUND state is considered background protected.
arc::ArcProcess mock_arc_proc(
/*nspid=*/0, /*pid=*/1234, "mock process",
/*state=*/arc::mojom::ProcessState::IMPORTANT_BACKGROUND,
/*is_focused=*/false,
/*last activity=*/
GetSystemTimeInPastAsMsSinceUptime(base::Minutes(2)));
EXPECT_FALSE(policy()->DefaultIsArcProcessEligibleForReclaim(mock_arc_proc));
}
// This test allows for reclaim of background protected when aggresive mode is
// enabled.
TEST_F(WorkingSetTrimmerPolicyChromeOSTest,
ArcAggressiveAllowsBackgroundProtected) {
policy()->trim_arc_on_memory_pressure(true);
policy()->params().trim_arc_aggressive = true;
// This validates that a background protected ARC app is eligible for
// reclaim when trim ARC aggressive is enabled. the IMPORTANT_BACKGROUND state
// is considered background protected.
arc::ArcProcess mock_arc_proc(
/*nspid=*/0, /*pid=*/1234, "mock process",
/*state=*/arc::mojom::ProcessState::IMPORTANT_BACKGROUND,
/*is_focused=*/false,
/*last activity=*/
GetSystemTimeInPastAsMsSinceUptime(base::Minutes(60)));
EXPECT_TRUE(policy()->DefaultIsArcProcessEligibleForReclaim(mock_arc_proc));
}
// This test validates that we can trim important apps when aggresive mode is
// enabled.
TEST_F(WorkingSetTrimmerPolicyChromeOSTest, ArcAggressiveAllowsImportant) {
policy()->trim_arc_on_memory_pressure(true);
policy()->params().trim_arc_aggressive = true;
// This validates that an imporant ARC app is eligible for
// reclaim, the IMPORANT_FOREGROUND state is considered imporant.
arc::ArcProcess mock_arc_proc(
/*nspid=*/0, /*pid=*/1234, "mock process",
/*state=*/arc::mojom::ProcessState::IMPORTANT_FOREGROUND,
/*is_focused=*/false,
/*last activity=*/
GetSystemTimeInPastAsMsSinceUptime(base::Minutes(60)));
EXPECT_TRUE(policy()->DefaultIsArcProcessEligibleForReclaim(mock_arc_proc));
}
// This test validates that we never trim an individual ARC process more
// frequently than configured.
TEST_F(WorkingSetTrimmerPolicyChromeOSTest, ArcProcessNotTrimmedTooFrequently) {
policy()->trim_arc_on_memory_pressure(true);
// For this test we don't care about last activity time.
policy()->params().arc_process_inactivity_time = base::TimeDelta::Min();
// We will only allow trimming of an individual process once every 10 seconds.
policy()->params().arc_process_trim_backoff_time = base::Seconds(10);
// Use a mock ARC process, this process is eligible to be reclaimed so the
// only thing which would prevent it would be that it was reclaimed too
// recently.
arc::ArcProcess mock_arc_proc(
/*nspid=*/0, /*pid=*/1234, "mock process",
/*state=*/arc::mojom::ProcessState::SERVICE, /*is_focused=*/false,
/*last activity=*/
GetSystemTimeInPastAsMsSinceUptime(base::Minutes(2)));
EXPECT_TRUE(policy()->DefaultIsArcProcessEligibleForReclaim(mock_arc_proc));
// Next, we set the last trim time as Now and confirm that it no
// longer is eligible for reclaim.
policy()->SetArcProcessLastTrimTime(mock_arc_proc.pid(),
base::TimeTicks::Now());
FastForwardBy(base::Seconds(5));
EXPECT_FALSE(policy()->DefaultIsArcProcessEligibleForReclaim(mock_arc_proc));
// And finally, as we move through the backoff period we should be able to
// reclaim that process again.
FastForwardBy(policy()->params().arc_process_trim_backoff_time);
EXPECT_TRUE(policy()->DefaultIsArcProcessEligibleForReclaim(mock_arc_proc));
}
// This test validates that we never trim an ARC process if its last activity
// time is less than the configured value.
TEST_F(WorkingSetTrimmerPolicyChromeOSTest,
ArcProcessDontTrimOnRecentActivity) {
policy()->trim_arc_on_memory_pressure(true);
// We won't trim when the last activity is less than 30s ago.
policy()->params().arc_process_inactivity_time = base::Seconds(30);
// We don't care about the ARC process trim backoff time for this test.
policy()->params().arc_process_trim_backoff_time = base::TimeDelta::Min();
// This mock ARC process will not be eligible because its last activity was
// only 10seconds ago and our configured last activity cutoff is 30s.
arc::ArcProcess mock_arc_proc(
/*nspid=*/0, /*pid=*/1234, "mock process",
/*state=*/arc::mojom::ProcessState::SERVICE, /*is_focused=*/false,
/*last activity=*/
GetSystemTimeInPastAsMsSinceUptime(base::Seconds(10)));
// It was active too recently.
EXPECT_FALSE(policy()->DefaultIsArcProcessEligibleForReclaim(mock_arc_proc));
// Now if we advance the clock, the last activity time will be beyond that
// threshold and it'll now be eligible for reclaim.
FastForwardBy(policy()->params().arc_process_inactivity_time);
EXPECT_TRUE(policy()->DefaultIsArcProcessEligibleForReclaim(mock_arc_proc));
}
// This test validates that we don't trim more than the configured number of arc
// processes per fetch interval.
TEST_F(WorkingSetTrimmerPolicyChromeOSTest,
ArcDontTrimMoreProcessesPerRoundThanAllowed) {
policy()->trim_arc_on_memory_pressure(true);
// In this configuration we will not trim more than 2 ARC processes per round
// regardless of how many are returned.
policy()->params().arc_max_number_processes_per_trim = 2;
// We don't care about the ARC process trim backoff time or last activity time
// for this test.
policy()->params().arc_process_inactivity_time = base::TimeDelta::Min();
policy()->params().arc_process_trim_backoff_time = base::TimeDelta::Min();
// We add 20 eligible ARC processes to validate that only 2 of them will
// be reclaimed.
std::vector<arc::ArcProcess> arc_process_list;
for (int i = 0; i < 20; ++i) {
arc_process_list.emplace_back(
/*nspid=*/0, /*pid=*/1234 + i, "mock process",
/*state=*/arc::mojom::ProcessState::SERVICE, /*is_focused=*/false,
/*last activity=*/
GetSystemTimeInPastAsMsSinceUptime(base::Minutes(10)));
}
// We expect that only the first two processes will be trimmed because
// otherwise we would exceed the limit.
EXPECT_CALL(*policy(), IsArcProcessEligibleForReclaim(_))
.WillRepeatedly(
Invoke(policy(), &MockWorkingSetTrimmerPolicyChromeOS::
DefaultIsArcProcessEligibleForReclaim));
EXPECT_CALL(*policy(), TrimArcProcess(_)).Times(Exactly(2));
policy()->DefaultTrimArcProcesses();
policy()->DefaultTrimReceivedArcProcesses(
policy()->params().arc_max_number_processes_per_trim,
arc::ArcProcessService::OptionalArcProcessList(
std::move(arc_process_list)));
}
// This test is a simple smoke test to make sure that ARCVM process trimming
// doesn't run if it's not enabled.
TEST_F(WorkingSetTrimmerPolicyChromeOSTest, ArcVmDontTrimOnlyIfDisabled) {
policy()->trim_arcvm_on_memory_pressure(false);
EXPECT_CALL(*policy(), TrimArcVmProcesses).Times(0);
FastForwardBy(base::Seconds(1));
policy()->listener().SimulatePressureNotification(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE);
}
// This test will validate that we do try to trim the ARCVM process on memory
// pressure when the feature is enabled.
TEST_F(WorkingSetTrimmerPolicyChromeOSTest, ArcVmTrimOnlyIfEnabled) {
ScopedTestArcVmDelegate delegate(policy(),
mechanism::ArcVmReclaimType::kReclaimNone,
kNotFirstReclaimPostBoot);
policy()->trim_arcvm_on_memory_pressure(true);
FastForwardBy(base::Seconds(1));
EXPECT_CALL(*policy(), TrimArcVmProcesses).Times(1);
EXPECT_CALL(*policy(),
OnTrimArcVmProcesses(mechanism::ArcVmReclaimType::kReclaimNone,
kNotFirstReclaimPostBoot,
params().trim_arcvm_pages_per_minute,
params().trim_arcvm_max_pages_per_iteration))
.Times(Exactly(1))
.WillOnce(Invoke(this, &WorkingSetTrimmerPolicyChromeOSTest::
DefaultOnTrimArcVmProcessesAndQuit));
policy()->listener().SimulatePressureNotification(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE);
run_loop()->Run();
}
// Builds a list of expectations for a memory pressure event that results in
// no reclaim.
void WorkingSetTrimmerPolicyChromeOSTest::ExpectNoReclaim() {
auto config_pages_per_minute = policy()->params().trim_arcvm_pages_per_minute;
auto config_max_pages = policy()->params().trim_arcvm_max_pages_per_iteration;
// Enforces all EXPECT_CALLS to occur in the order they are stated below.
InSequence serialize_expected_calls;
EXPECT_CALL(*policy(), TrimArcVmProcesses).Times(Exactly(1));
EXPECT_CALL(*policy(),
OnTrimArcVmProcesses(mechanism::ArcVmReclaimType::kReclaimNone,
kNotFirstReclaimPostBoot,
config_pages_per_minute, config_max_pages))
.Times(Exactly(1))
.WillOnce(Invoke(this, &WorkingSetTrimmerPolicyChromeOSTest::
DefaultOnTrimArcVmProcessesAndQuit));
}
// Builds a list of expectations for a memory pressure event that results in a
// full reclaim, specifying the reclaim parameters:
// |is_first_reclaim| whether or not this will be flagged as a first post-boot
// reclaim.
// |computed_page_limit| Expected limit for the number of pages to reclaim.
void WorkingSetTrimmerPolicyChromeOSTest::ExpectFullReclaim(
bool is_first_reclaim,
int computed_page_limit) {
auto config_pages_per_minute = policy()->params().trim_arcvm_pages_per_minute;
auto config_max_pages = policy()->params().trim_arcvm_max_pages_per_iteration;
mechanism::ArcVmReclaimType full_reclaim =
mechanism::ArcVmReclaimType::kReclaimAll;
// Enforces all EXPECT_CALLS to occur in the order they are stated below.
InSequence serialize_expected_calls;
// 1. The request to trim.
EXPECT_CALL(*policy(), TrimArcVmProcesses).Times(Exactly(1));
// 2. The intermediate forwarding operation. Checking that parameters are
// carried forward.
EXPECT_CALL(*policy(),
OnTrimArcVmProcesses(full_reclaim, is_first_reclaim,
config_pages_per_minute, config_max_pages))
.Times(Exactly(1));
// 3. The call to the underlying trimmer. Validate the computed page limit.
EXPECT_CALL(*trimmer(),
TrimArcVmWorkingSet(_, full_reclaim, computed_page_limit))
.Times(Exactly(1));
// 4. Expect success and quit the run loop.
EXPECT_CALL(*policy(), OnArcVmTrimEnded(full_reclaim, true))
.Times(Exactly(1))
.WillOnce(Invoke(this, &WorkingSetTrimmerPolicyChromeOSTest::
DefaultOnArcVmTrimEndedAndQuit));
}
// Similarly, builds a list of expectations for dropping guest page caches.
void WorkingSetTrimmerPolicyChromeOSTest::ExpectDropPageCaches() {
// Enforces all EXPECT_CALLS to occur in the order they are stated below.
InSequence serialize_expected_calls;
// 1. The request to trim.
EXPECT_CALL(*policy(), TrimArcVmProcesses).Times(Exactly(1));
// 2. The intermediate forwarding operation. Checking that parameters are
// carried forward.
EXPECT_CALL(*policy(),
OnTrimArcVmProcesses(
mechanism::ArcVmReclaimType::kReclaimGuestPageCaches,
/*is_first_reclaim=*/true,
/*config_pages_per_minute=*/_, /*config_max_pages=*/_))
.Times(Exactly(1));
// 3. The call to the underlying trimmer. Validate the computed page limit.
EXPECT_CALL(
*trimmer(),
TrimArcVmWorkingSet(/*callback=*/_,
mechanism::ArcVmReclaimType::kReclaimGuestPageCaches,
/*computed_page_limit=*/_))
.Times(Exactly(1));
// 4. Expect success and quit the run loop.
EXPECT_CALL(*policy(),
OnArcVmTrimEnded(
mechanism::ArcVmReclaimType::kReclaimGuestPageCaches, true))
.Times(Exactly(1))
.WillOnce(Invoke(this, &WorkingSetTrimmerPolicyChromeOSTest::
DefaultOnArcVmTrimEndedAndQuit));
}
// This test validates the calculation of trim limits.
TEST_F(WorkingSetTrimmerPolicyChromeOSTest, ArcVmTrimPageLimits) {
ScopedTestArcVmDelegate delegate(policy(),
mechanism::ArcVmReclaimType::kReclaimNone,
kNotFirstReclaimPostBoot);
// Set up parameters.
int config_pages_per_minute = 3500;
int config_max_pages = 20000;
policy()->trim_arcvm_on_memory_pressure(true);
policy()->params().trim_arcvm_on_first_memory_pressure_after_arcvm_boot =
true;
policy()->params().trim_arcvm_on_critical_pressure = false;
policy()->params().arcvm_trim_backoff_time = base::Seconds(60);
policy()->params().trim_arcvm_pages_per_minute = config_pages_per_minute;
policy()->params().trim_arcvm_max_pages_per_iteration = config_max_pages;
policy()->params().arcvm_inactivity_time = base::Seconds(30);
// Replace the default trimmer with a mock one, so we can verify parameters
// to it and control success/failure return values.
EXPECT_CALL(*policy(), GetTrimmer).WillRepeatedly(Return(trimmer()));
// -------------------------
// Step 1 of 5: No reclaim.
// Tell the fake Arc Delegate to respond as if it is not booted yet.
delegate.set_eligibility(mechanism::ArcVmReclaimType::kReclaimNone);
delegate.set_is_first_trim_post_boot(kNotFirstReclaimPostBoot);
ExpectNoReclaim();
FastForwardBy(base::Seconds(2)); // Still early in boot.
// Trigger pressure event and wait until last expectation is met.
policy()->listener().SimulatePressureNotification(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE);
run_loop()->Run();
RecreateRunLoop();
// -------------------------------------------------------------
// Step 2 of 5: First reclaim post-boot, drop guest page caches only.
// Tell fake Arc delegate to respond with first boot information.
delegate.set_eligibility(
mechanism::ArcVmReclaimType::kReclaimGuestPageCaches);
delegate.set_is_first_trim_post_boot(kYesFirstReclaimPostBoot);
ExpectDropPageCaches();
// Advance time just past the back-off setting.
FastForwardBy(base::Seconds(1));
FastForwardBy(policy()->params().arcvm_trim_backoff_time);
// Trigger pressure event and wait until last expectation is met.
policy()->listener().SimulatePressureNotification(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE);
run_loop()->Run();
RecreateRunLoop();
// ------------------------------------------------------------------------
// Step 3 of 5: Full reclaim after a few minutes
// Tell fake Arc delegate to respond saying it is NOT first boot.
delegate.set_eligibility(mechanism::ArcVmReclaimType::kReclaimAll);
delegate.set_is_first_trim_post_boot(kNotFirstReclaimPostBoot);
// The very first full reclaim is always done with |config_max_pages|.
ExpectFullReclaim(kNotFirstReclaimPostBoot, config_max_pages);
// Advance two times the back-off
FastForwardBy(base::Seconds(1));
FastForwardBy(policy()->params().arcvm_trim_backoff_time * 2);
// Trigger pressure event and wait until last expectation is met.
policy()->listener().SimulatePressureNotification(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL);
run_loop()->Run();
RecreateRunLoop();
// ------------------------------------------------------------------------
// Step 4 of 5: Full reclaim after a few minutes, confirm per-minute rate.
// Tell fake Arc delegate to respond saying it is NOT first boot.
delegate.set_eligibility(mechanism::ArcVmReclaimType::kReclaimAll);
delegate.set_is_first_trim_post_boot(kNotFirstReclaimPostBoot);
// Subsequent full reclaims are controlled by |config_pages_per_minute|.
constexpr int kMinutes = 2;
ExpectFullReclaim(kNotFirstReclaimPostBoot,
config_pages_per_minute * kMinutes);
// Advance two times the back-off
FastForwardBy(base::Seconds(1));
FastForwardBy(policy()->params().arcvm_trim_backoff_time * kMinutes);
// Trigger pressure event and wait until last expectation is met.
policy()->listener().SimulatePressureNotification(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL);
run_loop()->Run();
RecreateRunLoop();
// -----------------------------------------------------------------------
// Step 5 of 5: Full reclaim after a long time, confirm cap at max pages.
// Tell fake Arc delegate to respond saying it is NOT first boot.
delegate.set_eligibility(mechanism::ArcVmReclaimType::kReclaimAll);
delegate.set_is_first_trim_post_boot(kNotFirstReclaimPostBoot);
ExpectFullReclaim(kNotFirstReclaimPostBoot, config_max_pages);
// Advance quite far into the future, to exceed the maximum reclaim.
FastForwardBy(base::Seconds(1));
FastForwardBy(policy()->params().arcvm_trim_backoff_time * 10);
// Trigger pressure event and wait until last expectation is met.
policy()->listener().SimulatePressureNotification(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL);
run_loop()->Run();
}
// This test will validate that we don't trim the ARCVM process at an interval
// that is greater than the configured value, regardless of memory pressure
// levels.
TEST_F(WorkingSetTrimmerPolicyChromeOSTest,
ArcVmTrimProcessesAtConfiguredInterval) {
ScopedTestArcVmDelegate delegate(policy(),
mechanism::ArcVmReclaimType::kReclaimAll,
kNotFirstReclaimPostBoot);
// Our test setup will validate that we don't attempt to try to trim the ARCVM
// processes more than the configured frequency (in this case 60s).
policy()->trim_arcvm_on_memory_pressure(true);
policy()->params().arcvm_trim_backoff_time = base::Seconds(60);
// We're going to cause a moderate pressure notification twice, but we only
// expect to attempt to trim ARCVM once because of our configured backoff
// time.
EXPECT_CALL(*policy(), TrimArcVmProcesses).Times(Exactly(1));
FastForwardBy(base::Seconds(12));
policy()->listener().SimulatePressureNotification(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE);
FastForwardBy(base::Seconds(12));
policy()->listener().SimulatePressureNotification(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE);
// Now as we pass through the backoff time we expect that we can be called
// again.
EXPECT_CALL(*policy(), TrimArcVmProcesses).Times(Exactly(1));
EXPECT_CALL(*policy(),
OnTrimArcVmProcesses(mechanism::ArcVmReclaimType::kReclaimAll,
kNotFirstReclaimPostBoot,
params().trim_arcvm_pages_per_minute,
params().trim_arcvm_max_pages_per_iteration))
.Times(Exactly(1))
.WillOnce(Invoke(this, &WorkingSetTrimmerPolicyChromeOSTest::
DefaultOnTrimArcVmProcessesAndQuit));
FastForwardBy(policy()->params().arcvm_trim_backoff_time);
policy()->listener().SimulatePressureNotification(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE);
run_loop()->Run();
}
// Tests the same but with MEMORY_PRESSURE_LEVEL_CRITICAL. The behavior should
// be the same regardless of the pressure level.
TEST_F(WorkingSetTrimmerPolicyChromeOSTest,
ArcVmTrimProcessesAtConfiguredInterval_Critical) {
ScopedTestArcVmDelegate delegate(policy(),
mechanism::ArcVmReclaimType::kReclaimAll,
kNotFirstReclaimPostBoot);
policy()->trim_arcvm_on_memory_pressure(true);
policy()->params().arcvm_trim_backoff_time = base::Seconds(60);
EXPECT_CALL(*policy(), TrimArcVmProcesses).Times(Exactly(1));
FastForwardBy(base::Seconds(12));
policy()->listener().SimulatePressureNotification(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL);
FastForwardBy(base::Seconds(12));
policy()->listener().SimulatePressureNotification(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL);
EXPECT_CALL(*policy(), TrimArcVmProcesses).Times(Exactly(1));
EXPECT_CALL(*policy(),
OnTrimArcVmProcesses(mechanism::ArcVmReclaimType::kReclaimAll,
kNotFirstReclaimPostBoot,
params().trim_arcvm_pages_per_minute,
params().trim_arcvm_max_pages_per_iteration))
.Times(Exactly(1))
.WillOnce(Invoke(this, &WorkingSetTrimmerPolicyChromeOSTest::
DefaultOnTrimArcVmProcessesAndQuit));
FastForwardBy(policy()->params().arcvm_trim_backoff_time);
policy()->listener().SimulatePressureNotification(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL);
run_loop()->Run();
}
// Tests that the actual reclaim is NOT performed when the delegate returns
// kReclaimNone.
TEST_F(WorkingSetTrimmerPolicyChromeOSTest, ArcVmTrimProcessesIneligible) {
ScopedTestArcVmDelegate delegate(policy(),
mechanism::ArcVmReclaimType::kReclaimNone,
kNotFirstReclaimPostBoot);
policy()->trim_arcvm_on_memory_pressure(true);
policy()->params().arcvm_trim_backoff_time = base::Seconds(60);
EXPECT_CALL(*policy(), TrimArcVmProcesses).Times(Exactly(1));
EXPECT_CALL(*policy(),
OnTrimArcVmProcesses(mechanism::ArcVmReclaimType::kReclaimNone,
kNotFirstReclaimPostBoot,
params().trim_arcvm_pages_per_minute,
params().trim_arcvm_max_pages_per_iteration))
.Times(Exactly(1))
.WillOnce(Invoke(this, &WorkingSetTrimmerPolicyChromeOSTest::
DefaultOnTrimArcVmProcessesAndQuit));
FastForwardBy(base::Seconds(12));
policy()->listener().SimulatePressureNotification(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE);
run_loop()->Run();
RecreateRunLoop();
// Repeat the same with CRITICAL.
EXPECT_CALL(*policy(), TrimArcVmProcesses).Times(Exactly(1));
EXPECT_CALL(*policy(),
OnTrimArcVmProcesses(mechanism::ArcVmReclaimType::kReclaimNone,
kNotFirstReclaimPostBoot,
params().trim_arcvm_pages_per_minute,
params().trim_arcvm_max_pages_per_iteration))
.Times(Exactly(1))
.WillOnce(Invoke(this, &WorkingSetTrimmerPolicyChromeOSTest::
DefaultOnTrimArcVmProcessesAndQuit));
FastForwardBy(base::Seconds(1));
FastForwardBy(policy()->params().arcvm_trim_backoff_time);
policy()->listener().SimulatePressureNotification(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL);
run_loop()->Run();
}
// Tests that the actual reclaim is performed with the reclaim type the delegate
// returns.
TEST_F(WorkingSetTrimmerPolicyChromeOSTest,
ArcVmTrimProcessesDropCachesEligible) {
ScopedTestArcVmDelegate delegate(
policy(), mechanism::ArcVmReclaimType::kReclaimGuestPageCaches,
kNotFirstReclaimPostBoot);
policy()->trim_arcvm_on_memory_pressure(true);
policy()->params().arcvm_trim_backoff_time = base::Seconds(60);
// Verify that OnTrimArcVmProcesses is called with kReclaimGuestPageCaches.
EXPECT_CALL(*policy(), TrimArcVmProcesses).Times(Exactly(1));
EXPECT_CALL(
*policy(),
OnTrimArcVmProcesses(mechanism::ArcVmReclaimType::kReclaimGuestPageCaches,
kNotFirstReclaimPostBoot,
params().trim_arcvm_pages_per_minute,
params().trim_arcvm_max_pages_per_iteration))
.Times(Exactly(1))
.WillOnce(Invoke(this, &WorkingSetTrimmerPolicyChromeOSTest::
DefaultOnTrimArcVmProcessesAndQuit));
FastForwardBy(base::Seconds(12));
policy()->listener().SimulatePressureNotification(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE);
run_loop()->Run();
RecreateRunLoop();
// Change the delegate's return value to kReclaimAll. This happens in
// production too.
delegate.set_eligibility(mechanism::ArcVmReclaimType::kReclaimAll);
// Since dropping page caches is not an actual VM trim, a trimming can happen
// without waiting for the |arcvm_trim_backoff_time|.
EXPECT_CALL(*policy(), TrimArcVmProcesses).Times(Exactly(1));
EXPECT_CALL(*policy(),
OnTrimArcVmProcesses(mechanism::ArcVmReclaimType::kReclaimAll,
kNotFirstReclaimPostBoot,
params().trim_arcvm_pages_per_minute,
params().trim_arcvm_max_pages_per_iteration))
.Times(Exactly(1))
.WillOnce(Invoke(this, &WorkingSetTrimmerPolicyChromeOSTest::
DefaultOnTrimArcVmProcessesAndQuit));
FastForwardBy(base::Seconds(12));
policy()->listener().SimulatePressureNotification(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE);
run_loop()->Run();
}
// Tests that the actual reclaim is performed on LEVEL_CRITICAL when the
// delegate returns kReclaimNone but |trim_arcvm_on_critical_pressure| is
// set to true.
TEST_F(WorkingSetTrimmerPolicyChromeOSTest, ArcVmTrimProcessesForceTrim) {
ScopedTestArcVmDelegate delegate(policy(),
mechanism::ArcVmReclaimType::kReclaimNone,
kNotFirstReclaimPostBoot);
policy()->trim_arcvm_on_memory_pressure(true);
policy()->params().trim_arcvm_on_critical_pressure = true;
policy()->params().arcvm_trim_backoff_time = base::Seconds(60);
EXPECT_CALL(*policy(), TrimArcVmProcesses).Times(Exactly(1));
EXPECT_CALL(*policy(),
OnTrimArcVmProcesses(mechanism::ArcVmReclaimType::kReclaimNone,
kNotFirstReclaimPostBoot,
params().trim_arcvm_pages_per_minute,
params().trim_arcvm_max_pages_per_iteration))
.Times(Exactly(1))
.WillOnce(Invoke(this, &WorkingSetTrimmerPolicyChromeOSTest::
DefaultOnTrimArcVmProcessesAndQuit));
FastForwardBy(base::Seconds(12));
policy()->listener().SimulatePressureNotification(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE);
run_loop()->Run();
RecreateRunLoop();
// Repeat the same with CRITICAL.
EXPECT_CALL(*policy(), TrimArcVmProcesses).Times(Exactly(1));
EXPECT_CALL(*policy(),
OnTrimArcVmProcesses(mechanism::ArcVmReclaimType::kReclaimAll,
kNotFirstReclaimPostBoot,
params().trim_arcvm_pages_per_minute,
params().trim_arcvm_max_pages_per_iteration))
.Times(Exactly(1))
.WillOnce(Invoke(this, &WorkingSetTrimmerPolicyChromeOSTest::
DefaultOnTrimArcVmProcessesAndQuit));
FastForwardBy(base::Seconds(1));
FastForwardBy(policy()->params().arcvm_trim_backoff_time);
policy()->listener().SimulatePressureNotification(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL);
run_loop()->Run();
}
} // namespace policies
} // namespace performance_manager