blob: b9dfe6c3c02a2ee4286cef0c51a70898e4162907 [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 "base/memory/memory_pressure_monitor_notifying_chromeos.h"
#include <unistd.h>
#include <string>
#include "base/bind.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/macros.h"
#include "base/memory/memory_pressure_listener.h"
#include "base/run_loop.h"
#include "base/synchronization/waitable_event.h"
#include "base/system/sys_info.h"
#include "base/task/post_task.h"
#include "base/test/scoped_task_environment.h"
#include "base/test/test_timeouts.h"
#include "base/threading/platform_thread.h"
#include "base/threading/scoped_blocking_call.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base {
namespace chromeos {
namespace {
bool SetFileContents(const FilePath& path, const std::string& contents) {
return static_cast<std::string::size_type>(base::WriteFile(
path, contents.c_str(), contents.size())) == contents.size();
}
// Since it would be very hard to mock sysfs instead we will send in our own
// implementation of WaitForKernelNotification which instead will block on a
// pipe that we can trigger for the test to cause a mock kernel notification.
bool WaitForMockKernelNotification(int pipe_read_fd, int available_fd) {
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::WILL_BLOCK);
// We just use a pipe to block our kernel notification thread until we have
// a fake kernel notification.
char buf = 0;
int res = HANDLE_EINTR(read(pipe_read_fd, &buf, sizeof(buf)));
// Fail if we encounter any error.
return res > 0;
}
void TriggerKernelNotification(int pipe_write_fd) {
char buf = '1';
HANDLE_EINTR(write(pipe_write_fd, &buf, sizeof(buf)));
}
// Processes OnMemoryPressure calls by just storing the sequence of events so we
// can validate that we received the expected pressure levels as the test runs.
void OnMemoryPressure(
std::vector<MemoryPressureListener::MemoryPressureLevel>* history,
MemoryPressureListener::MemoryPressureLevel level) {
history->push_back(level);
}
} // namespace
class TestMemoryPressureMonitorNotifying
: public MemoryPressureMonitorNotifying {
public:
TestMemoryPressureMonitorNotifying(
const std::string& mock_margin_file,
const std::string& mock_available_file,
base::RepeatingCallback<bool(int)> kernel_waiting_callback,
bool enable_metrics)
: MemoryPressureMonitorNotifying(mock_margin_file,
mock_available_file,
std::move(kernel_waiting_callback),
enable_metrics) {}
static std::vector<int> GetMarginFileParts(const std::string& file) {
return MemoryPressureMonitorNotifying::GetMarginFileParts(file);
}
~TestMemoryPressureMonitorNotifying() override = default;
private:
DISALLOW_COPY_AND_ASSIGN(TestMemoryPressureMonitorNotifying);
};
TEST(ChromeOSMemoryPressureMonitorNotifyingTest, ParseMarginFileGood) {
base::ScopedTempDir tmp_dir;
ASSERT_TRUE(tmp_dir.CreateUniqueTempDir());
FilePath margin_file = tmp_dir.GetPath().Append("margin");
ASSERT_TRUE(SetFileContents(margin_file, "123"));
const std::vector<int> parts1 =
TestMemoryPressureMonitorNotifying::GetMarginFileParts(
margin_file.value());
ASSERT_EQ(1u, parts1.size());
ASSERT_EQ(123, parts1[0]);
ASSERT_TRUE(SetFileContents(margin_file, "123 456"));
const std::vector<int> parts2 =
TestMemoryPressureMonitorNotifying::GetMarginFileParts(
margin_file.value());
ASSERT_EQ(2u, parts2.size());
ASSERT_EQ(123, parts2[0]);
ASSERT_EQ(456, parts2[1]);
}
TEST(ChromeOSMemoryPressureMonitorNotifyingTest, ParseMarginFileBad) {
base::ScopedTempDir tmp_dir;
ASSERT_TRUE(tmp_dir.CreateUniqueTempDir());
FilePath margin_file = tmp_dir.GetPath().Append("margin");
// An empty margin file is bad.
ASSERT_TRUE(SetFileContents(margin_file, ""));
ASSERT_TRUE(TestMemoryPressureMonitorNotifying::GetMarginFileParts(
margin_file.value())
.empty());
// The numbers will be in base10, so 4a6 would be invalid.
ASSERT_TRUE(SetFileContents(margin_file, "123 4a6"));
ASSERT_TRUE(TestMemoryPressureMonitorNotifying::GetMarginFileParts(
margin_file.value())
.empty());
// The numbers must be integers.
ASSERT_TRUE(SetFileContents(margin_file, "123.2 412.3"));
ASSERT_TRUE(TestMemoryPressureMonitorNotifying::GetMarginFileParts(
margin_file.value())
.empty());
}
TEST(ChromeOSMemoryPressureMonitorNotifyingTest, CheckMemoryPressure) {
// Create a temporary directory for our margin and available files.
base::ScopedTempDir tmp_dir;
ASSERT_TRUE(tmp_dir.CreateUniqueTempDir());
FilePath margin_file = tmp_dir.GetPath().Append("margin");
FilePath available_file = tmp_dir.GetPath().Append("available");
// Set the margin values to 500 (critical) and 1000 (moderate).
const std::string kMarginContents = "500 1000";
ASSERT_TRUE(SetFileContents(margin_file, kMarginContents));
// Write the initial available contents.
const std::string kInitialAvailableContents = "1500";
ASSERT_TRUE(SetFileContents(available_file, kInitialAvailableContents));
test::ScopedTaskEnvironment scoped_task_environment(
test::ScopedTaskEnvironment::MainThreadType::UI);
// We will use a mock listener to keep track of our kernel notifications which
// cause event to be fired. We can just examine the sequence of pressure
// events when we're done to validate that the pressure events were as
// expected.
std::vector<MemoryPressureListener::MemoryPressureLevel> pressure_events;
auto listener = std::make_unique<MemoryPressureListener>(
base::BindRepeating(&OnMemoryPressure, &pressure_events));
// We use a pipe to notify our blocked kernel notification thread that there
// is a kernel notification we need to use a simple blocking syscall and
// read(2)/write(2) will work.
int fds[2] = {};
ASSERT_EQ(0, HANDLE_EINTR(pipe(fds)));
// Make sure the pipe FDs get closed.
ScopedFD write_end(fds[1]);
ScopedFD read_end(fds[0]);
auto monitor = std::make_unique<TestMemoryPressureMonitorNotifying>(
margin_file.value(), available_file.value(),
// Bind the read end to WaitForMockKernelNotification.
base::BindRepeating(&WaitForMockKernelNotification, read_end.get()),
/*enable_metrics=*/false);
// Validate that our margin levels are as expected after being parsed from our
// synthetic margin file.
ASSERT_EQ(500, monitor->CriticalPressureThresholdMBForTesting());
ASSERT_EQ(1000, monitor->ModeratePressureThresholdMBForTesting());
// At this point we have no memory pressure.
ASSERT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE,
monitor->GetCurrentPressureLevel());
// Moderate Pressure.
ASSERT_TRUE(SetFileContents(available_file, "900"));
TriggerKernelNotification(write_end.get());
RunLoop().RunWithTimeout(base::TimeDelta::FromSeconds(1));
ASSERT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE,
monitor->GetCurrentPressureLevel());
// Critical Pressure.
ASSERT_TRUE(SetFileContents(available_file, "450"));
TriggerKernelNotification(write_end.get());
RunLoop().RunWithTimeout(base::TimeDelta::FromSeconds(1));
ASSERT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL,
monitor->GetCurrentPressureLevel());
// Moderate Pressure.
ASSERT_TRUE(SetFileContents(available_file, "550"));
TriggerKernelNotification(write_end.get());
RunLoop().RunWithTimeout(base::TimeDelta::FromSeconds(1));
ASSERT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE,
monitor->GetCurrentPressureLevel());
// No pressure, note: this will not cause any event.
ASSERT_TRUE(SetFileContents(available_file, "1150"));
TriggerKernelNotification(write_end.get());
RunLoop().RunWithTimeout(base::TimeDelta::FromSeconds(1));
ASSERT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE,
monitor->GetCurrentPressureLevel());
// Back into moderate.
ASSERT_TRUE(SetFileContents(available_file, "950"));
TriggerKernelNotification(write_end.get());
RunLoop().RunWithTimeout(base::TimeDelta::FromSeconds(1));
ASSERT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE,
monitor->GetCurrentPressureLevel());
// Now our events should be MODERATE, CRITICAL, MODERATE.
ASSERT_EQ(4u, pressure_events.size());
ASSERT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE,
pressure_events[0]);
ASSERT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL,
pressure_events[1]);
ASSERT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE,
pressure_events[2]);
ASSERT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE,
pressure_events[3]);
}
} // namespace chromeos
} // namespace base