blob: 2a6e2832bf08da3f3ee391f69d65af50a1b68329 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include <vm/evictor.h>
#include "test_helper.h"
namespace vm_unittest {
// Custom pmm node to link with the evictor under test. Facilitates verifying the free count which
// is not possible with the global pmm node.
class TestPmmNode {
public:
explicit TestPmmNode(bool discardable)
: evictor_(
[this](VmCompression* compression, Evictor::EvictionLevel eviction_level) {
return this->TestReclaim(compression, eviction_level);
},
[this]() { return this->FreePages(); }),
discardable_(discardable) {
evictor_.EnableEviction(true);
}
~TestPmmNode() = default;
Evictor::EvictionTarget GetEvictionTarget() const { return evictor_.DebugGetEvictionTarget(); }
void CombineEvictionTarget(Evictor::EvictionTarget target) {
evictor_.CombineEvictionTarget(target);
}
Evictor::EvictedPageCounts EvictFromPreloadedTarget() {
return evictor_.EvictFromPreloadedTarget();
}
uint64_t FreePages() const { return free_pages_; }
Evictor* evictor() { return &evictor_; }
void CapEvictions(uint64_t max) { max_evictions_ = max; }
void UncapEvictions() { max_evictions_ = UINT64_MAX; }
private:
ktl::optional<VmCowReclaimResult> TestReclaim(VmCompression* compression,
Evictor::EvictionLevel eviction_level) {
if (total_evictions_ >= max_evictions_) {
return ktl::nullopt;
}
if (discardable_) {
// Discardable VMOs get freed in their entirety, which could be any amount of pages. Claiming
// 10 here is a bit arbitrary, and could be made configurable if/when there are some tests
// that need it.
free_pages_ += 10;
total_evictions_ += 10;
return fit::ok(VmCowReclaimSuccess{
.type = VmCowReclaimSuccess::Type::Discard,
.num_pages = 10,
});
}
free_pages_++;
total_evictions_++;
return fit::ok(VmCowReclaimSuccess{
.type = VmCowReclaimSuccess::Type::EvictNonLoaned,
.num_pages = 1,
});
}
uint64_t free_pages_ = 0;
uint64_t total_evictions_ = 0;
uint64_t max_evictions_ = UINT64_MAX;
Evictor evictor_;
bool discardable_;
};
// Test that a one shot eviction target can be set as expected.
static bool evictor_set_target_test() {
BEGIN_TEST;
AutoVmScannerDisable scanner_disable;
TestPmmNode node(false);
auto expected = Evictor::EvictionTarget{
.pending = static_cast<bool>(rand() % 2),
.free_pages_target = static_cast<uint64_t>(rand()),
.min_pages_to_free = static_cast<uint64_t>(rand()),
.level =
(rand() % 2) ? Evictor::EvictionLevel::IncludeNewest : Evictor::EvictionLevel::OnlyOldest,
};
node.CombineEvictionTarget(expected);
auto actual = node.GetEvictionTarget();
ASSERT_EQ(actual.pending, expected.pending);
ASSERT_EQ(actual.free_pages_target, expected.free_pages_target);
ASSERT_EQ(actual.min_pages_to_free, expected.min_pages_to_free);
ASSERT_EQ(actual.level, expected.level);
END_TEST;
}
// Test that multiple one shot eviction targets can be combined as expected.
static bool evictor_combine_targets_test() {
BEGIN_TEST;
AutoVmScannerDisable scanner_disable;
TestPmmNode node(false);
static constexpr int kNumTargets = 5;
Evictor::EvictionTarget targets[kNumTargets];
for (auto& target : targets) {
target = Evictor::EvictionTarget{
.pending = true,
.free_pages_target = static_cast<uint64_t>(rand() % 1000),
.min_pages_to_free = static_cast<uint64_t>(rand() % 1000),
.level = Evictor::EvictionLevel::IncludeNewest,
};
node.CombineEvictionTarget(target);
}
Evictor::EvictionTarget expected = {};
for (auto& target : targets) {
expected.pending = expected.pending || target.pending;
expected.level = ktl::max(expected.level, target.level);
expected.min_pages_to_free += target.min_pages_to_free;
expected.free_pages_target = ktl::max(expected.free_pages_target, target.free_pages_target);
}
auto actual = node.GetEvictionTarget();
ASSERT_EQ(actual.pending, expected.pending);
ASSERT_EQ(actual.free_pages_target, expected.free_pages_target);
ASSERT_EQ(actual.min_pages_to_free, expected.min_pages_to_free);
ASSERT_EQ(actual.level, expected.level);
END_TEST;
}
// Test that the evictor can evict from pager backed vmos as expected.
static bool evictor_pager_backed_test() {
BEGIN_TEST;
AutoVmScannerDisable scanner_disable;
TestPmmNode node(false);
auto target = Evictor::EvictionTarget{
.pending = true,
.free_pages_target = 20,
.min_pages_to_free = 10,
.level = Evictor::EvictionLevel::IncludeNewest,
};
// The node starts off with zero pages.
uint64_t free_count = node.FreePages();
EXPECT_EQ(free_count, 0u);
node.CombineEvictionTarget(target);
auto counts = node.EvictFromPreloadedTarget();
// No discardable pages were evicted.
EXPECT_EQ(counts.discardable, 0u);
// Free pages target was greater than min pages target. So precisely free pages target must have
// been evicted.
EXPECT_EQ(counts.pager_backed, target.free_pages_target);
EXPECT_GE(counts.pager_backed, target.min_pages_to_free);
// The node has the desired number of free pages now, and a minimum of min pages have been freed.
free_count = node.FreePages();
EXPECT_EQ(free_count, target.free_pages_target);
EXPECT_GE(free_count, target.min_pages_to_free);
target = Evictor::EvictionTarget{
.pending = true,
.free_pages_target = 10,
.min_pages_to_free = 20,
.level = Evictor::EvictionLevel::IncludeNewest,
};
node.CombineEvictionTarget(target);
counts = node.EvictFromPreloadedTarget();
// No discardable pages were evicted.
EXPECT_EQ(counts.discardable, 0u);
// Min pages target was greater than free pages target. So precisely min pages target must have
// been evicted.
EXPECT_EQ(counts.pager_backed, target.min_pages_to_free);
// The node has the desired number of free pages now, and a minimum of min pages have been freed.
EXPECT_GE(node.FreePages(), target.free_pages_target);
EXPECT_EQ(node.FreePages(), free_count + target.min_pages_to_free);
END_TEST;
}
// Test that the evictor can discard from discardable vmos as expected.
static bool evictor_discardable_test() {
BEGIN_TEST;
AutoVmScannerDisable scanner_disable;
TestPmmNode node(true);
auto target = Evictor::EvictionTarget{
.pending = true,
.free_pages_target = 20,
.min_pages_to_free = 10,
.level = Evictor::EvictionLevel::IncludeNewest,
};
// The node starts off with zero pages.
uint64_t free_count = node.FreePages();
EXPECT_EQ(free_count, 0u);
node.CombineEvictionTarget(target);
auto counts = node.EvictFromPreloadedTarget();
// No pager backed pages were evicted.
EXPECT_EQ(counts.pager_backed, 0u);
// Free pages target was greater than min pages target. So precisely free pages target must have
// been evicted. However, a discardable vmo can only be discarded in its entirety, so we can't
// check for equality with free pages target.
EXPECT_GE(counts.discardable, target.free_pages_target);
EXPECT_GE(counts.discardable, target.min_pages_to_free);
// The node has the desired number of free pages now, and a minimum of min pages have been freed.
free_count = node.FreePages();
EXPECT_GE(free_count, target.free_pages_target);
EXPECT_GE(free_count, target.min_pages_to_free);
target = Evictor::EvictionTarget{
.pending = true,
.free_pages_target = 10,
.min_pages_to_free = 20,
.level = Evictor::EvictionLevel::IncludeNewest,
};
node.CombineEvictionTarget(target);
counts = node.EvictFromPreloadedTarget();
// No pager backed pages were evicted.
EXPECT_EQ(counts.pager_backed, 0u);
// Min pages target was greater than free pages target. So precisely min pages target must have
// been evicted. However, a discardable vmo can only be discarded in its entirety, so we can't
// check for equality with free pages target.
EXPECT_GE(counts.discardable, target.min_pages_to_free);
// The node has the desired number of free pages now, and a minimum of min pages have been freed.
EXPECT_GE(node.FreePages(), target.free_pages_target);
EXPECT_GE(node.FreePages(), free_count + target.min_pages_to_free);
END_TEST;
}
// Test that eviction meets the required free and min target as expected.
static bool evictor_free_target_test() {
BEGIN_TEST;
AutoVmScannerDisable scanner_disable;
// Only evict from pager backed vmos.
TestPmmNode node(false);
auto target = Evictor::EvictionTarget{
.pending = true,
.free_pages_target = 20,
.min_pages_to_free = 0,
.level = Evictor::EvictionLevel::IncludeNewest,
};
// The node starts off with zero pages.
uint64_t free_count = node.FreePages();
EXPECT_EQ(free_count, 0u);
node.CombineEvictionTarget(target);
auto counts = node.EvictFromPreloadedTarget();
// No discardable pages were evicted.
EXPECT_EQ(counts.discardable, 0u);
// Free pages target was greater than min pages target. So precisely free pages target must have
// been evicted.
EXPECT_EQ(counts.pager_backed, target.free_pages_target);
// The node has the desired number of free pages now, and a minimum of min pages have been freed.
free_count = node.FreePages();
EXPECT_EQ(free_count, target.free_pages_target);
EXPECT_GE(free_count, target.min_pages_to_free);
// Evict again with the same target.
node.CombineEvictionTarget(target);
counts = node.EvictFromPreloadedTarget();
// No new pages should have been evicted, as the free target was already met with the previous
// round of eviction, and no minimum pages were requested to be evicted.
EXPECT_EQ(counts.discardable, 0u);
EXPECT_EQ(counts.pager_backed, 0u);
EXPECT_EQ(node.FreePages(), free_count);
// Evict again with a higher free memory target. No min pages target.
uint64_t delta_pages = 10;
target.free_pages_target += delta_pages;
target.min_pages_to_free = 0;
node.CombineEvictionTarget(target);
counts = node.EvictFromPreloadedTarget();
// No discardable pages evicted.
EXPECT_EQ(counts.discardable, 0u);
// Exactly delta_pages evicted.
EXPECT_EQ(counts.pager_backed, delta_pages);
EXPECT_GE(counts.pager_backed, target.min_pages_to_free);
// Free count increased by delta_pages.
free_count = node.FreePages();
EXPECT_EQ(free_count, target.free_pages_target);
// Evict again with a higher free memory target and also a min pages target.
target.free_pages_target += delta_pages;
target.min_pages_to_free = delta_pages;
node.CombineEvictionTarget(target);
counts = node.EvictFromPreloadedTarget();
// No discardable pages evicted.
EXPECT_EQ(counts.discardable, 0u);
// Exactly delta_pages evicted.
EXPECT_EQ(counts.pager_backed, delta_pages);
EXPECT_GE(counts.pager_backed, target.min_pages_to_free);
// Free count increased by delta_pages.
free_count = node.FreePages();
EXPECT_EQ(free_count, target.free_pages_target);
// Evict again with the same free target, but request a min number of pages to be freed.
target.min_pages_to_free = 2;
node.CombineEvictionTarget(target);
counts = node.EvictFromPreloadedTarget();
// No discardable pages evicted.
EXPECT_EQ(counts.discardable, 0u);
// Exactly min pages evicted.
EXPECT_EQ(counts.pager_backed, target.min_pages_to_free);
// Free count increased by min pages.
EXPECT_EQ(node.FreePages(), free_count + target.min_pages_to_free);
END_TEST;
}
// Test that eviction using an external target does not alter a previously set eviction target.
static bool evictor_external_target_test() {
BEGIN_TEST;
AutoVmScannerDisable scanner_disable;
TestPmmNode node(false);
auto expected = Evictor::EvictionTarget{
.pending = static_cast<bool>(rand() % 2),
.free_pages_target = 111,
.min_pages_to_free = 33,
.level =
(rand() % 2) ? Evictor::EvictionLevel::IncludeNewest : Evictor::EvictionLevel::OnlyOldest,
};
node.CombineEvictionTarget(expected);
auto external = Evictor::EvictionTarget{
.pending = !expected.pending,
.free_pages_target = 99,
.min_pages_to_free = 22,
.level = expected.level == Evictor::EvictionLevel::OnlyOldest
? Evictor::EvictionLevel::IncludeNewest
: Evictor::EvictionLevel::OnlyOldest,
};
node.evictor()->EvictFromExternalTarget(external);
auto actual = node.GetEvictionTarget();
ASSERT_EQ(actual.pending, expected.pending);
ASSERT_EQ(actual.free_pages_target, expected.free_pages_target);
ASSERT_EQ(actual.min_pages_to_free, expected.min_pages_to_free);
ASSERT_EQ(actual.level, expected.level);
END_TEST;
}
// Test that a one shot eviction target can be set as expected.
static bool evictor_min_target_carried_over_test() {
BEGIN_TEST;
AutoVmScannerDisable scanner_disable;
TestPmmNode node(false);
auto target = Evictor::EvictionTarget{
.pending = true,
.free_pages_target = 10,
.min_pages_to_free = 15,
.level = Evictor::EvictionLevel::IncludeNewest,
};
// The node starts off with zero pages.
uint64_t free_count = node.FreePages();
EXPECT_EQ(free_count, 0u);
// Cap the number of evictions to 5.
node.CapEvictions(5);
node.CombineEvictionTarget(target);
auto counts = node.EvictFromPreloadedTarget();
// No discardable pages evicted.
EXPECT_EQ(counts.discardable, 0u);
// Exactly 5 pages evicted.
EXPECT_EQ(counts.pager_backed, 5u);
// Uncap evictions.
node.UncapEvictions();
// Combine target with zero min pages requested.
node.CombineEvictionTarget(Evictor::EvictionTarget{
.pending = true,
.free_pages_target = 0,
.min_pages_to_free = 0,
.level = Evictor::EvictionLevel::IncludeNewest,
});
counts = node.EvictFromPreloadedTarget();
// No discardable pages evicted.
EXPECT_EQ(counts.discardable, 0u);
// Remaining pages should have been evicted.
EXPECT_EQ(counts.pager_backed, target.min_pages_to_free - 5u);
free_count = node.FreePages();
EXPECT_EQ(free_count, target.min_pages_to_free);
END_TEST;
}
UNITTEST_START_TESTCASE(evictor_tests)
VM_UNITTEST(evictor_set_target_test)
VM_UNITTEST(evictor_combine_targets_test)
VM_UNITTEST(evictor_pager_backed_test)
VM_UNITTEST(evictor_discardable_test)
VM_UNITTEST(evictor_free_target_test)
VM_UNITTEST(evictor_external_target_test)
VM_UNITTEST(evictor_min_target_carried_over_test)
UNITTEST_END_TESTCASE(evictor_tests, "evictor", "Evictor tests")
} // namespace vm_unittest