blob: 6294cce944cae99d8ea1d0fb13471e9595d7b432 [file] [log] [blame]
// Copyright 2022 The Fuchsia 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 "src/virtualization/bin/guest_manager/memory_pressure_handler.h"
#include <fuchsia/memorypressure/cpp/fidl.h>
#include <fuchsia/virtualization/cpp/fidl_test_base.h>
#include <lib/fidl/cpp/binding.h>
#include <lib/fidl/cpp/binding_set.h>
#include <lib/sys/cpp/testing/component_context_provider.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/zx/time.h>
#include <src/lib/testing/loop_fixture/test_loop_fixture.h>
#include <virtio/balloon.h>
#include <virtio/virtio_ids.h>
namespace {
const uint64_t kTestAvailMemSize = static_cast<uint64_t>(PAGE_SIZE) * 1182938;
const uint32_t kExpectedInflateNumPages =
kTestAvailMemSize / PAGE_SIZE / 100 *
MemoryPressureHandler::kBalloonAvailableMemoryInflatePercentage;
static_assert(
kTestAvailMemSize > std::numeric_limits<uint32_t>::max(),
"Available memory should not fit into uint32_t to validate math in the memory pressure handler");
class FakeBalloonController : public fuchsia::virtualization::BalloonController {
public:
void GetBalloonSize(GetBalloonSizeCallback callback) override {
callback(current_num_pages_, target_num_pages);
}
void RequestNumPages(uint32_t requested_num_pages) override {
target_num_pages = requested_num_pages;
}
void GetMemStats(GetMemStatsCallback callback) override {
std::vector<::fuchsia::virtualization::MemStat> mem_stats;
mem_stats.push_back({VIRTIO_BALLOON_S_MEMFREE, free_memory_});
mem_stats.push_back({VIRTIO_BALLOON_S_AVAIL, available_memory_});
return callback(ZX_OK, mem_stats);
}
uint32_t current_num_pages_ = 0;
uint32_t target_num_pages = 0;
uint64_t available_memory_ = kTestAvailMemSize;
uint64_t free_memory_ = kTestAvailMemSize / 2;
};
class FakeGuest : public fuchsia::virtualization::testing::Guest_TestBase {
public:
FakeGuest(sys::testing::ComponentContextProvider* provider,
FakeBalloonController* balloon_controller)
: balloon_controller_binding_(balloon_controller) {
FX_CHECK(ZX_OK ==
provider->service_directory_provider()->AddService(bindings_.GetHandler(this)));
}
// |fuchsia::virtualization::Guest|
void GetBalloonController(
::fidl::InterfaceRequest<::fuchsia::virtualization::BalloonController> controller,
GetBalloonControllerCallback callback) override {
balloon_controller_binding_.Bind(std::move(controller));
}
private:
void NotImplemented_(const std::string& name) override {
ADD_FAILURE() << "unexpected message received: " << name;
}
fidl::BindingSet<fuchsia::virtualization::Guest> bindings_;
fidl::Binding<fuchsia::virtualization::BalloonController> balloon_controller_binding_;
};
class FakeMemoryPressureProvider : public fuchsia::memorypressure::Provider {
public:
explicit FakeMemoryPressureProvider(sys::testing::ComponentContextProvider* provider) {
FX_CHECK(ZX_OK ==
provider->service_directory_provider()->AddService(bindings_.GetHandler(this)));
}
// |fuchsia::memorypressure::Provider|
void RegisterWatcher(
::fidl::InterfaceHandle<::fuchsia::memorypressure::Watcher> watcher) override {
watcher_ = watcher.Bind();
}
void SetMemoryPressureLevel(fuchsia::memorypressure::Level level) {
watcher_->OnLevelChanged(level, []() {});
}
private:
fuchsia::memorypressure::WatcherPtr watcher_;
fidl::BindingSet<fuchsia::memorypressure::Provider> bindings_;
};
class MemoryPressureHandlerTest : public gtest::TestLoopFixture {
public:
MemoryPressureHandlerTest()
: context_provider_(dispatcher()), memory_pressure_handler_(dispatcher()) {}
void SetUp() override {
TestLoopFixture::SetUp();
fake_memory_pressure_provider_ =
std::make_unique<FakeMemoryPressureProvider>(&context_provider_);
fake_balloon_controller_ = std::make_unique<FakeBalloonController>();
fake_guest_ = std::make_unique<FakeGuest>(&context_provider_, fake_balloon_controller_.get());
memory_pressure_handler_.Start(context_provider_.context());
RunLoopUntilIdle();
}
sys::testing::ComponentContextProvider context_provider_;
MemoryPressureHandler memory_pressure_handler_;
std::unique_ptr<FakeMemoryPressureProvider> fake_memory_pressure_provider_;
std::unique_ptr<FakeBalloonController> fake_balloon_controller_;
std::unique_ptr<FakeGuest> fake_guest_;
};
TEST_F(MemoryPressureHandlerTest, InflateOnWarningDeflateScheduled) {
fake_memory_pressure_provider_->SetMemoryPressureLevel(fuchsia::memorypressure::Level::WARNING);
RunLoopUntilIdle();
EXPECT_EQ(fake_balloon_controller_->target_num_pages, kExpectedInflateNumPages);
fake_memory_pressure_provider_->SetMemoryPressureLevel(fuchsia::memorypressure::Level::NORMAL);
RunLoopUntilIdle();
// Balloon deflate got scheduled, it would not happen at this point
EXPECT_EQ(fake_balloon_controller_->target_num_pages, kExpectedInflateNumPages);
// Wait for inflate operation to complete, deflate would happen after wait
RunLoopFor(MemoryPressureHandler::kBalloonInflateCompletionWaitTime);
EXPECT_EQ(fake_balloon_controller_->target_num_pages, 0u);
}
TEST_F(MemoryPressureHandlerTest, InflateOnWarningDeflateAfterWait) {
fake_memory_pressure_provider_->SetMemoryPressureLevel(fuchsia::memorypressure::Level::WARNING);
RunLoopUntilIdle();
EXPECT_EQ(fake_balloon_controller_->target_num_pages, kExpectedInflateNumPages);
RunLoopFor(MemoryPressureHandler::kBalloonInflateCompletionWaitTime);
fake_memory_pressure_provider_->SetMemoryPressureLevel(fuchsia::memorypressure::Level::NORMAL);
RunLoopUntilIdle();
EXPECT_EQ(fake_balloon_controller_->target_num_pages, 0u);
}
TEST_F(MemoryPressureHandlerTest, InflateOnWarningThenCriticalWhichIsIgnored) {
fake_memory_pressure_provider_->SetMemoryPressureLevel(fuchsia::memorypressure::Level::WARNING);
RunLoopUntilIdle();
EXPECT_EQ(fake_balloon_controller_->target_num_pages, kExpectedInflateNumPages);
fake_memory_pressure_provider_->SetMemoryPressureLevel(fuchsia::memorypressure::Level::CRITICAL);
RunLoopUntilIdle();
EXPECT_EQ(fake_balloon_controller_->target_num_pages, kExpectedInflateNumPages);
}
TEST_F(MemoryPressureHandlerTest, InflateOnWarningScheduleDeflateAndBackToWarning) {
fake_memory_pressure_provider_->SetMemoryPressureLevel(fuchsia::memorypressure::Level::WARNING);
RunLoopFor(zx::msec(100));
EXPECT_EQ(fake_balloon_controller_->target_num_pages, kExpectedInflateNumPages);
// Deflate is scheduled
fake_memory_pressure_provider_->SetMemoryPressureLevel(fuchsia::memorypressure::Level::NORMAL);
RunLoopUntilIdle();
EXPECT_EQ(fake_balloon_controller_->target_num_pages, kExpectedInflateNumPages);
// Scheduled deflate will be ignored because we switched back to warning
fake_memory_pressure_provider_->SetMemoryPressureLevel(fuchsia::memorypressure::Level::WARNING);
RunLoopFor(zx::hour(1));
EXPECT_EQ(fake_balloon_controller_->target_num_pages, kExpectedInflateNumPages);
}
} // namespace