Commit 20314ad9 authored by Kuo-Hsin Yang's avatar Kuo-Hsin Yang Committed by Chromium LUCI CQ

CrOS: Notify low memory when PSI memory pressure is high

When PSI memory pressure is high, triggers the moderate memory pressure.
Add feature flag CrOSLowMemoryNotificationPSI.

BUG=b:159299004
TEST=check moderate memory pressure is triggered properly when using \
    disk swap.

Change-Id: Iba533d5e4733619e2df7480eb2851b955b7ec99a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2428503
Commit-Queue: Kuo-Hsin Yang <vovoy@chromium.org>
Reviewed-by: default avatarBrian Geffon <bgeffon@chromium.org>
Cr-Commit-Position: refs/heads/master@{#837032}
parent 295d736c
...@@ -18,6 +18,12 @@ namespace pressure { ...@@ -18,6 +18,12 @@ namespace pressure {
namespace { namespace {
const base::Feature kCrOSLowMemoryNotificationPSI{
"CrOSLowMemoryNotificationPSI", base::FEATURE_DISABLED_BY_DEFAULT};
const base::FeatureParam<int> kCrOSLowMemoryPSIThreshold{
&kCrOSLowMemoryNotificationPSI, "CrOSLowMemoryPSIThreshold", 20};
// The reserved file cache. // The reserved file cache.
constexpr char kMinFilelist[] = "/proc/sys/vm/min_filelist_kbytes"; constexpr char kMinFilelist[] = "/proc/sys/vm/min_filelist_kbytes";
...@@ -59,6 +65,41 @@ uint64_t ReadFileToUint64(const base::FilePath& file) { ...@@ -59,6 +65,41 @@ uint64_t ReadFileToUint64(const base::FilePath& file) {
return file_contents_uint64; return file_contents_uint64;
} }
uint64_t GetReservedMemoryKB() {
std::string file_contents;
if (!base::ReadFileToStringNonBlocking(base::FilePath("/proc/zoneinfo"),
&file_contents)) {
PLOG(ERROR) << "Couldn't get /proc/zoneinfo";
return 0;
}
// Reserve free pages is high watermark + lowmem_reserve and extra_free_kbytes
// raises the high watermark. Nullify the effect of extra_free_kbytes by
// excluding it from the reserved pages. The default extra_free_kbytes value
// is 0 if the file couldn't be accessed.
return CalculateReservedFreeKB(file_contents) -
ReadFileToUint64(base::FilePath(kExtraFree));
}
bool SupportsPSI() {
static bool supports_psi =
base::PathExists(base::FilePath("/proc/pressure/"));
return supports_psi;
}
// Returns the percentage of the recent 10 seconds that some process is blocked
// by memory.
double GetPSIMemoryPressure10Seconds() {
base::FilePath psi_memory("/proc/pressure/memory");
std::string contents;
if (!base::ReadFileToStringNonBlocking(base::FilePath(psi_memory),
&contents)) {
PLOG(ERROR) << "Unable to read file: " << psi_memory;
return 0;
}
return ParsePSIMemory(contents);
}
} // namespace } // namespace
// CalculateReservedFreeKB() calculates the reserved free memory in KiB from // CalculateReservedFreeKB() calculates the reserved free memory in KiB from
...@@ -132,23 +173,39 @@ uint64_t CalculateReservedFreeKB(const std::string& zoneinfo) { ...@@ -132,23 +173,39 @@ uint64_t CalculateReservedFreeKB(const std::string& zoneinfo) {
return num_reserved_pages * kPageSizeKB; return num_reserved_pages * kPageSizeKB;
} }
static uint64_t GetReservedMemoryKB() { // Returns the percentage of the recent 10 seconds that some process is blocked
std::string file_contents; // by memory.
if (!base::ReadFileToStringNonBlocking(base::FilePath("/proc/zoneinfo"), // Example input:
&file_contents)) { // some avg10=0.00 avg60=0.00 avg300=0.00 total=0
PLOG(ERROR) << "Couldn't get /proc/zoneinfo"; // full avg10=0.00 avg60=0.00 avg300=0.00 total=0
return 0; double ParsePSIMemory(const std::string& contents) {
for (const base::StringPiece& line : base::SplitStringPiece(
contents, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY)) {
std::vector<base::StringPiece> tokens = base::SplitStringPiece(
line, base::kWhitespaceASCII, base::TRIM_WHITESPACE,
base::SPLIT_WANT_NONEMPTY);
if (tokens[0] == "some") {
base::StringPairs kv_pairs;
if (base::SplitStringIntoKeyValuePairs(line.substr(5), '=', ' ',
&kv_pairs)) {
double some_10seconds;
if (base::StringToDouble(kv_pairs[0].second, &some_10seconds)) {
return some_10seconds;
} else {
LOG(ERROR) << "Couldn't parse the value of the first pair";
} }
} else {
// Reserve free pages is high watermark + lowmem_reserve and extra_free_kbytes LOG(ERROR)
// raises the high watermark. Nullify the effect of extra_free_kbytes by << "Couldn't split the key-value pairs in /proc/pressure/memory";
// excluding it from the reserved pages. The default extra_free_kbytes value }
// is 0 if the file couldn't be accessed. }
return CalculateReservedFreeKB(file_contents) - }
ReadFileToUint64(base::FilePath(kExtraFree)); LOG(ERROR) << "Couldn't parse /proc/pressure/memory: " << contents;
DCHECK(false);
return 0;
} }
// CalculateAvailableMemoryUserSpaceKB implements the same available memory // CalculateAvailableMemoryUserSpaceKB implements similar available memory
// calculation as kernel function get_available_mem_adj(). The available memory // calculation as kernel function get_available_mem_adj(). The available memory
// consists of 3 parts: the free memory, the file cache, and the swappable // consists of 3 parts: the free memory, the file cache, and the swappable
// memory. The available free memory is free memory minus reserved free memory. // memory. The available free memory is free memory minus reserved free memory.
...@@ -167,25 +224,43 @@ uint64_t CalculateAvailableMemoryUserSpaceKB( ...@@ -167,25 +224,43 @@ uint64_t CalculateAvailableMemoryUserSpaceKB(
const uint64_t anon = info.active_anon + info.inactive_anon; const uint64_t anon = info.active_anon + info.inactive_anon;
const uint64_t file = info.active_file + info.inactive_file; const uint64_t file = info.active_file + info.inactive_file;
const uint64_t dirty = info.dirty; const uint64_t dirty = info.dirty;
const uint64_t swap_free = info.swap_free; const uint64_t free_component =
(free > reserved_free) ? free - reserved_free : 0;
uint64_t available = (free > reserved_free) ? free - reserved_free : 0; const uint64_t cache_component =
available += (file > dirty + min_filelist) ? file - dirty - min_filelist : 0; (file > dirty + min_filelist) ? file - dirty - min_filelist : 0;
available += std::min<uint64_t>(anon, swap_free) / ram_swap_weight; const uint64_t swappable = std::min<uint64_t>(anon, info.swap_free);
const uint64_t swap_component = swappable / ram_swap_weight;
return available; return free_component + cache_component + swap_component;
} }
uint64_t GetAvailableMemoryKB() { uint64_t GetAvailableMemoryKB() {
base::SystemMemoryInfoKB info; base::SystemMemoryInfoKB info;
uint64_t available_kb;
if (base::GetSystemMemoryInfo(&info)) { if (base::GetSystemMemoryInfo(&info)) {
return CalculateAvailableMemoryUserSpaceKB(info, reserved_free, available_kb = CalculateAvailableMemoryUserSpaceKB(
min_filelist, ram_swap_weight); info, reserved_free, min_filelist, ram_swap_weight);
} } else {
PLOG(ERROR) << "Assume low memory pressure if opening/parsing meminfo failed"; PLOG(ERROR)
<< "Assume low memory pressure if opening/parsing meminfo failed";
LOG_IF(FATAL, base::SysInfo::IsRunningOnChromeOS()) LOG_IF(FATAL, base::SysInfo::IsRunningOnChromeOS())
<< "procfs isn't mounted or unable to open /proc/meminfo"; << "procfs isn't mounted or unable to open /proc/meminfo";
return 4 * 1024; available_kb = 4 * 1024;
}
static bool using_psi = SupportsPSI() && base::FeatureList::IsEnabled(
kCrOSLowMemoryNotificationPSI);
if (using_psi) {
auto margins = GetMemoryMarginsKB();
const uint64_t critical_margin = margins.first;
const uint64_t moderate_margin = margins.second;
static double psi_threshold = kCrOSLowMemoryPSIThreshold.Get();
// When PSI memory pressure is high, trigger moderate memory pressure.
if (GetPSIMemoryPressure10Seconds() > psi_threshold) {
available_kb =
std::min(available_kb, (moderate_margin + critical_margin) / 2);
}
}
return available_kb;
} }
std::vector<uint64_t> GetMarginFileParts(const std::string& file) { std::vector<uint64_t> GetMarginFileParts(const std::string& file) {
......
...@@ -27,6 +27,9 @@ namespace pressure { ...@@ -27,6 +27,9 @@ namespace pressure {
// Export for unittest. // Export for unittest.
CHROMEOS_EXPORT uint64_t CalculateReservedFreeKB(const std::string& zoneinfo); CHROMEOS_EXPORT uint64_t CalculateReservedFreeKB(const std::string& zoneinfo);
// Export for unittest.
CHROMEOS_EXPORT double ParsePSIMemory(const std::string& contents);
// Export for unittest. // Export for unittest.
CHROMEOS_EXPORT uint64_t CHROMEOS_EXPORT uint64_t
CalculateAvailableMemoryUserSpaceKB(const base::SystemMemoryInfoKB& info, CalculateAvailableMemoryUserSpaceKB(const base::SystemMemoryInfoKB& info,
......
...@@ -45,6 +45,15 @@ Node 0, zone Normal ...@@ -45,6 +45,15 @@ Node 0, zone Normal
ASSERT_EQ(reserved, (high_watermarks + lowmem_reserves) * kPageSizeKB); ASSERT_EQ(reserved, (high_watermarks + lowmem_reserves) * kPageSizeKB);
} }
TEST(MemoryPressureTest, ParsePSIMemory) {
const std::string kMockPSIMemory(R"(
some avg10=57.25 avg60=35.97 avg300=10.18 total=32748793
full avg10=29.29 avg60=19.01 avg300=5.44 total=17589167)");
const double pressure =
chromeos::memory::pressure::ParsePSIMemory(kMockPSIMemory);
ASSERT_EQ(pressure, 57.25);
}
TEST(MemoryPressureTest, CalculateAvailableMemoryUserSpaceKB) { TEST(MemoryPressureTest, CalculateAvailableMemoryUserSpaceKB) {
base::SystemMemoryInfoKB info; base::SystemMemoryInfoKB info;
uint64_t available; uint64_t available;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment