From f8eb9adf9be4729266863ae43928435994f2a8f9 Mon Sep 17 00:00:00 2001 From: Suren Baghdasaryan Date: Fri, 8 Dec 2017 12:58:52 -0800 Subject: [PATCH 01/10] lmkd: add ability to monitor all vmpressure events (cherry pick from commit 96bf3a600c5f2678665a7c028dacbbf3fcc8f7c7) Ability to monitor all available vmpressure event levels is needed to accommodate systems with different memory resources. Low memory systems can rely on medium and critical level events because working under memory pressure is usual mode of operation. High performance systems with more memory need to react earlier using also low vmpressure level events to free memory early and prevent low memory condition affecting its performance. Bug: 63631020 Test: alloc-stress Change-Id: I0cef1bd4c97d32c005045ae47f0ce3464ed98899 Merged-In: I0cef1bd4c97d32c005045ae47f0ce3464ed98899 Signed-off-by: Suren Baghdasaryan --- lmkd.c | 136 ++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 81 insertions(+), 55 deletions(-) diff --git a/lmkd.c b/lmkd.c index 15471e0..9acb83d 100644 --- a/lmkd.c +++ b/lmkd.c @@ -44,8 +44,6 @@ #define MEMCG_SYSFS_PATH "/dev/memcg/" #define MEMCG_MEMORY_USAGE "/dev/memcg/memory.usage_in_bytes" #define MEMCG_MEMORYSW_USAGE "/dev/memcg/memory.memsw.usage_in_bytes" -#define MEMPRESSURE_WATCH_MEDIUM_LEVEL "medium" -#define MEMPRESSURE_WATCH_CRITICAL_LEVEL "critical" #define ZONEINFO_PATH "/proc/zoneinfo" #define LINE_MAX 128 @@ -72,13 +70,22 @@ enum lmk_cmd { static int use_inkernel_interface = 1; static bool has_inkernel_module; -/* memory pressure level medium event */ -static int mpevfd[2]; -#define CRITICAL_INDEX 1 -#define MEDIUM_INDEX 0 +/* memory pressure levels */ +enum vmpressure_level { + VMPRESS_LEVEL_LOW = 0, + VMPRESS_LEVEL_MEDIUM, + VMPRESS_LEVEL_CRITICAL, + VMPRESS_LEVEL_COUNT +}; -static int medium_oomadj; -static int critical_oomadj; +static const char *level_name[] = { + "low", + "medium", + "critical" +}; + +static int level_oomadj[VMPRESS_LEVEL_COUNT]; +static int mpevfd[VMPRESS_LEVEL_COUNT]; static bool debug_process_killing; static bool enable_pressure_upgrade; static int64_t upgrade_pressure; @@ -90,8 +97,8 @@ static int ctrl_lfd; static int ctrl_dfd = -1; static int ctrl_dfd_reopened; /* did we reopen ctrl conn on this loop? */ -/* 2 memory pressure levels, 1 ctrl listen socket, 1 ctrl data socket */ -#define MAX_EPOLL_EVENTS 4 +/* 3 memory pressure levels, 1 ctrl listen socket, 1 ctrl data socket */ +#define MAX_EPOLL_EVENTS 5 static int epollfd; static int maxevents; @@ -226,7 +233,7 @@ static int pid_remove(int pid) { return 0; } -static void writefilestring(char *path, char *s) { +static void writefilestring(const char *path, char *s) { int fd = open(path, O_WRONLY | O_CLOEXEC); int len = strlen(s); int ret; @@ -587,7 +594,8 @@ static struct proc *proc_adj_lru(int oomadj) { } /* Kill one process specified by procp. Returns the size of the process killed */ -static int kill_one_process(struct proc* procp, int min_score_adj, bool is_critical) { +static int kill_one_process(struct proc* procp, int min_score_adj, + enum vmpressure_level level) { int pid = procp->pid; uid_t uid = procp->uid; char *taskname; @@ -606,12 +614,12 @@ static int kill_one_process(struct proc* procp, int min_score_adj, bool is_criti return -1; } + r = kill(pid, SIGKILL); ALOGI( "Killing '%s' (%d), uid %d, adj %d\n" " to free %ldkB because system is under %s memory pressure oom_adj %d\n", - taskname, pid, uid, procp->oomadj, tasksize * page_k, is_critical ? "critical" : "medium", - min_score_adj); - r = kill(pid, SIGKILL); + taskname, pid, uid, procp->oomadj, tasksize * page_k, + level_name[level], min_score_adj); pid_remove(pid); if (r) { @@ -626,10 +634,10 @@ static int kill_one_process(struct proc* procp, int min_score_adj, bool is_criti * Find a process to kill based on the current (possibly estimated) free memory * and cached memory sizes. Returns the size of the killed processes. */ -static int find_and_kill_process(bool is_critical) { +static int find_and_kill_process(enum vmpressure_level level) { int i; int killed_size = 0; - int min_score_adj = is_critical ? critical_oomadj : medium_oomadj; + int min_score_adj = level_oomadj[level]; for (i = OOM_SCORE_ADJ_MAX; i >= min_score_adj; i--) { struct proc *procp; @@ -638,7 +646,7 @@ retry: procp = proc_adj_lru(i); if (procp) { - killed_size = kill_one_process(procp, min_score_adj, is_critical); + killed_size = kill_one_process(procp, min_score_adj, level); if (killed_size < 0) { goto retry; } else { @@ -674,14 +682,23 @@ static int64_t get_memory_usage(const char* path) { return mem_usage; } -static void mp_event_common(bool is_critical) { +enum vmpressure_level upgrade_level(enum vmpressure_level level) { + return (enum vmpressure_level)((level < VMPRESS_LEVEL_CRITICAL) ? + level + 1 : level); +} + +enum vmpressure_level downgrade_level(enum vmpressure_level level) { + return (enum vmpressure_level)((level > VMPRESS_LEVEL_LOW) ? + level - 1 : level); +} + +static void mp_event_common(enum vmpressure_level level) { int ret; unsigned long long evcount; - int index = is_critical ? CRITICAL_INDEX : MEDIUM_INDEX; int64_t mem_usage, memsw_usage; int64_t mem_pressure; - ret = read(mpevfd[index], &evcount, sizeof(evcount)); + ret = read(mpevfd[level], &evcount, sizeof(evcount)); if (ret < 0) ALOGE("Error reading memory pressure event fd; errno=%d", errno); @@ -689,18 +706,19 @@ static void mp_event_common(bool is_critical) { mem_usage = get_memory_usage(MEMCG_MEMORY_USAGE); memsw_usage = get_memory_usage(MEMCG_MEMORYSW_USAGE); if (memsw_usage < 0 || mem_usage < 0) { - find_and_kill_process(is_critical); - return; + goto do_kill; } // Calculate percent for swappinness. mem_pressure = (mem_usage * 100) / memsw_usage; - if (enable_pressure_upgrade && !is_critical) { + if (enable_pressure_upgrade && level != VMPRESS_LEVEL_CRITICAL) { // We are swapping too much. if (mem_pressure < upgrade_pressure) { - ALOGI("Event upgraded to critical."); - is_critical = true; + level = upgrade_level(level); + if (debug_process_killing) { + ALOGI("Event upgraded to %s", level_name[level]); + } } } @@ -708,41 +726,51 @@ static void mp_event_common(bool is_critical) { // kill any process, since enough memory is available. if (mem_pressure > downgrade_pressure) { if (debug_process_killing) { - ALOGI("Ignore %s memory pressure", is_critical ? "critical" : "medium"); + ALOGI("Ignore %s memory pressure", level_name[level]); } return; - } else if (is_critical && mem_pressure > upgrade_pressure) { + } else if (level == VMPRESS_LEVEL_CRITICAL && + mem_pressure > upgrade_pressure) { if (debug_process_killing) { ALOGI("Downgrade critical memory pressure"); } - // Downgrade event to medium, since enough memory available. - is_critical = false; + // Downgrade event, since enough memory available. + level = downgrade_level(level); } - if (find_and_kill_process(is_critical) == 0) { +do_kill: + if (find_and_kill_process(level) == 0) { if (debug_process_killing) { ALOGI("Nothing to kill"); } } } -static void mp_event(uint32_t events __unused) { - mp_event_common(false); +static void mp_event_low(uint32_t events __unused) { + mp_event_common(VMPRESS_LEVEL_LOW); +} + +static void mp_event_medium(uint32_t events __unused) { + mp_event_common(VMPRESS_LEVEL_MEDIUM); } static void mp_event_critical(uint32_t events __unused) { - mp_event_common(true); + mp_event_common(VMPRESS_LEVEL_CRITICAL); } -static int init_mp_common(char *levelstr, void *event_handler, bool is_critical) -{ +static bool init_mp_common(void *event_handler, enum vmpressure_level level) { int mpfd; int evfd; int evctlfd; char buf[256]; struct epoll_event epev; int ret; - int mpevfd_index = is_critical ? CRITICAL_INDEX : MEDIUM_INDEX; + const char *levelstr = level_name[level]; + + if (level_oomadj[level] > OOM_SCORE_ADJ_MAX) { + ALOGI("%s pressure events are disabled", levelstr); + return true; + } mpfd = open(MEMCG_SYSFS_PATH "memory.pressure_level", O_RDONLY | O_CLOEXEC); if (mpfd < 0) { @@ -783,8 +811,8 @@ static int init_mp_common(char *levelstr, void *event_handler, bool is_critical) goto err; } maxevents++; - mpevfd[mpevfd_index] = evfd; - return 0; + mpevfd[level] = evfd; + return true; err: close(evfd); @@ -793,17 +821,7 @@ err_eventfd: err_open_evctlfd: close(mpfd); err_open_mpfd: - return -1; -} - -static int init_mp_medium() -{ - return init_mp_common(MEMPRESSURE_WATCH_MEDIUM_LEVEL, (void *)&mp_event, false); -} - -static int init_mp_critical() -{ - return init_mp_common(MEMPRESSURE_WATCH_CRITICAL_LEVEL, (void *)&mp_event_critical, true); + return false; } static int init(void) { @@ -848,10 +866,13 @@ static int init(void) { if (use_inkernel_interface) { ALOGI("Using in-kernel low memory killer interface"); } else { - ret = init_mp_medium(); - ret |= init_mp_critical(); - if (ret) + if (!init_mp_common((void *)&mp_event_low, VMPRESS_LEVEL_LOW) || + !init_mp_common((void *)&mp_event_medium, VMPRESS_LEVEL_MEDIUM) || + !init_mp_common((void *)&mp_event_critical, + VMPRESS_LEVEL_CRITICAL)) { ALOGE("Kernel does not support memory pressure events or in-kernel low memory killer"); + return -1; + } } for (i = 0; i <= ADJTOSLOT(OOM_SCORE_ADJ_MAX); i++) { @@ -892,8 +913,13 @@ int main(int argc __unused, char **argv __unused) { .sched_priority = 1, }; - medium_oomadj = property_get_int32("ro.lmk.medium", 800); - critical_oomadj = property_get_int32("ro.lmk.critical", 0); + /* By default disable low level vmpressure events */ + level_oomadj[VMPRESS_LEVEL_LOW] = + property_get_int32("ro.lmk.low", OOM_SCORE_ADJ_MAX + 1); + level_oomadj[VMPRESS_LEVEL_MEDIUM] = + property_get_int32("ro.lmk.medium", 800); + level_oomadj[VMPRESS_LEVEL_CRITICAL] = + property_get_int32("ro.lmk.critical", 0); debug_process_killing = property_get_bool("ro.lmk.debug", false); enable_pressure_upgrade = property_get_bool("ro.lmk.critical_upgrade", false); upgrade_pressure = (int64_t)property_get_int32("ro.lmk.upgrade_pressure", 50); From 2148af42b33de9ae7ad5001afabcac537b9d89c3 Mon Sep 17 00:00:00 2001 From: Suren Baghdasaryan Date: Fri, 8 Dec 2017 13:08:41 -0800 Subject: [PATCH 02/10] lmkd: change defaults to disable event upgrade/downgrade logic (cherry pick from commit ad2fd9150bdbb9abdbc26c6a395f007b4cca7567) vmpressure upgrade/downgrade logic based on swap utilization works well for low memory devices because of a small swap size, however for high performance devices this measure is not a good indication of the memory pressure because of large swap resources. This change sets the default levels to disable upgrade/downgrade logic by default and each device can set these properties appropriately. Bug: 63631020 Test: alloc-stress Change-Id: Ifd4fbd4d6bb3e82f0f87b029df94934f1e7b1c9c Merged-In: Ifd4fbd4d6bb3e82f0f87b029df94934f1e7b1c9c Signed-off-by: Suren Baghdasaryan --- lmkd.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lmkd.c b/lmkd.c index 9acb83d..d438203 100644 --- a/lmkd.c +++ b/lmkd.c @@ -921,9 +921,14 @@ int main(int argc __unused, char **argv __unused) { level_oomadj[VMPRESS_LEVEL_CRITICAL] = property_get_int32("ro.lmk.critical", 0); debug_process_killing = property_get_bool("ro.lmk.debug", false); - enable_pressure_upgrade = property_get_bool("ro.lmk.critical_upgrade", false); - upgrade_pressure = (int64_t)property_get_int32("ro.lmk.upgrade_pressure", 50); - downgrade_pressure = (int64_t)property_get_int32("ro.lmk.downgrade_pressure", 60); + + /* By default disable upgrade/downgrade logic */ + enable_pressure_upgrade = + property_get_bool("ro.lmk.critical_upgrade", false); + upgrade_pressure = + (int64_t)property_get_int32("ro.lmk.upgrade_pressure", 100); + downgrade_pressure = + (int64_t)property_get_int32("ro.lmk.downgrade_pressure", 100); is_go_device = property_get_bool("ro.config.low_ram", false); // MCL_ONFAULT pins pages as they fault instead of loading From 742897f28c5961ffcc1fa4a4491198c13148d4f5 Mon Sep 17 00:00:00 2001 From: Suren Baghdasaryan Date: Fri, 8 Dec 2017 13:17:06 -0800 Subject: [PATCH 03/10] lmkd: add logic to kill the heaviest of the eligible processes (cherry pick from commit 662492ab1d21f138483a8f3943483924e8779d29) Killing the most memory-demanding process from the set of eligible processes yields better results on high-performance devices than killing the first one we could find. This is in line with how in-kernel lowmemorykiller driver chooses its victims. Bug: 63631020 Test: alloc-stress Change-Id: Ie1ef7f33f3e79698a9b4120c14490386d6129f9b Merged-In: Ie1ef7f33f3e79698a9b4120c14490386d6129f9b Signed-off-by: Suren Baghdasaryan --- lmkd.c | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/lmkd.c b/lmkd.c index d438203..17ebb14 100644 --- a/lmkd.c +++ b/lmkd.c @@ -91,6 +91,7 @@ static bool enable_pressure_upgrade; static int64_t upgrade_pressure; static int64_t downgrade_pressure; static bool is_go_device; +static bool kill_heaviest_task; /* control socket listen and data */ static int ctrl_lfd; @@ -593,6 +594,29 @@ static struct proc *proc_adj_lru(int oomadj) { return (struct proc *)adjslot_tail(&procadjslot_list[ADJTOSLOT(oomadj)]); } +static struct proc *proc_get_heaviest(int oomadj) { + struct adjslot_list *head = &procadjslot_list[ADJTOSLOT(oomadj)]; + struct adjslot_list *curr = head->next; + struct proc *maxprocp = NULL; + int maxsize = 0; + while (curr != head) { + int pid = ((struct proc *)curr)->pid; + int tasksize = proc_get_size(pid); + if (tasksize <= 0) { + struct adjslot_list *next = curr->next; + pid_remove(pid); + curr = next; + } else { + if (tasksize > maxsize) { + maxsize = tasksize; + maxprocp = (struct proc *)curr; + } + curr = curr->next; + } + } + return maxprocp; +} + /* Kill one process specified by procp. Returns the size of the process killed */ static int kill_one_process(struct proc* procp, int min_score_adj, enum vmpressure_level level) { @@ -643,7 +667,10 @@ static int find_and_kill_process(enum vmpressure_level level) { struct proc *procp; retry: - procp = proc_adj_lru(i); + if (kill_heaviest_task) + procp = proc_get_heaviest(i); + else + procp = proc_adj_lru(i); if (procp) { killed_size = kill_one_process(procp, min_score_adj, level); @@ -929,6 +956,8 @@ int main(int argc __unused, char **argv __unused) { (int64_t)property_get_int32("ro.lmk.upgrade_pressure", 100); downgrade_pressure = (int64_t)property_get_int32("ro.lmk.downgrade_pressure", 100); + kill_heaviest_task = + property_get_bool("ro.lmk.kill_heaviest_task", true); is_go_device = property_get_bool("ro.config.low_ram", false); // MCL_ONFAULT pins pages as they fault instead of loading From a052dcd6a6e205affbf78b96ddc1dbc213710b26 Mon Sep 17 00:00:00 2001 From: Suren Baghdasaryan Date: Thu, 4 Jan 2018 10:43:58 -0800 Subject: [PATCH 04/10] lmkd: Add ability to trace lmkd kills (cherry pick from commit c71355991d4bafb4694f6252ac10e288a5fb9f75) For tracing lmkd kills inside kernel it is useful to have traces indicating when and which process lmkd is killing. By default the tracing is disabled. Bug: 63631020 Test: alloc-stress Change-Id: I3ceb2bde0c292eec55855cb4535927f3b4c5d08b Merged-In: I3ceb2bde0c292eec55855cb4535927f3b4c5d08b Signed-off-by: Suren Baghdasaryan --- Android.bp | 8 ++++++++ lmkd.c | 23 +++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/Android.bp b/Android.bp index 3f8a503..2731596 100644 --- a/Android.bp +++ b/Android.bp @@ -10,4 +10,12 @@ cc_binary { cflags: ["-Werror"], init_rc: ["lmkd.rc"], + + product_variables: { + debuggable: { + cflags: [ + "-DLMKD_TRACE_KILLS" + ], + }, + }, } diff --git a/lmkd.c b/lmkd.c index 17ebb14..aa25b5c 100644 --- a/lmkd.c +++ b/lmkd.c @@ -37,6 +37,25 @@ #include #include +/* + * Define LMKD_TRACE_KILLS to record lmkd kills in kernel traces + * to profile and correlate with OOM kills + */ +#ifdef LMKD_TRACE_KILLS + +#define ATRACE_TAG ATRACE_TAG_ALWAYS +#include + +#define TRACE_KILL_START(pid) ATRACE_INT(__FUNCTION__, pid); +#define TRACE_KILL_END() ATRACE_INT(__FUNCTION__, 0); + +#else /* LMKD_TRACE_KILLS */ + +#define TRACE_KILL_START(pid) +#define TRACE_KILL_END() + +#endif /* LMKD_TRACE_KILLS */ + #ifndef __unused #define __unused __attribute__((__unused__)) #endif @@ -638,6 +657,8 @@ static int kill_one_process(struct proc* procp, int min_score_adj, return -1; } + TRACE_KILL_START(pid); + r = kill(pid, SIGKILL); ALOGI( "Killing '%s' (%d), uid %d, adj %d\n" @@ -646,6 +667,8 @@ static int kill_one_process(struct proc* procp, int min_score_adj, level_name[level], min_score_adj); pid_remove(pid); + TRACE_KILL_END(); + if (r) { ALOGE("kill(%d): errno=%d", pid, errno); return -1; From 0fa737d068e326327326435d1a9dec7d1439a19b Mon Sep 17 00:00:00 2001 From: Suren Baghdasaryan Date: Thu, 4 Jan 2018 08:50:48 -0800 Subject: [PATCH 05/10] lmkd: Remove stale dependency on libprocessgroup (cherry pick from commit b333f83481d3de7a29a7aa8d27456af89581c12b) Remove stale dependencies and header file inclusions Change-Id: Ic0e7adb5bd2a0832937a831b6918e52ace4ad46a Merged-In: Ic0e7adb5bd2a0832937a831b6918e52ace4ad46a Signed-off-by: Suren Baghdasaryan --- Android.bp | 1 - lmkd.c | 2 -- 2 files changed, 3 deletions(-) diff --git a/Android.bp b/Android.bp index 2731596..76d308a 100644 --- a/Android.bp +++ b/Android.bp @@ -4,7 +4,6 @@ cc_binary { srcs: ["lmkd.c"], shared_libs: [ "liblog", - "libprocessgroup", "libcutils", ], cflags: ["-Werror"], diff --git a/lmkd.c b/lmkd.c index aa25b5c..d58c8f5 100644 --- a/lmkd.c +++ b/lmkd.c @@ -29,13 +29,11 @@ #include #include #include -#include #include #include #include #include -#include /* * Define LMKD_TRACE_KILLS to record lmkd kills in kernel traces From bb1087eb18047bfe6109faff6c48b1a8d7f4c30d Mon Sep 17 00:00:00 2001 From: Suren Baghdasaryan Date: Thu, 4 Jan 2018 08:54:53 -0800 Subject: [PATCH 06/10] lmkd: Close cgroup.event_control file when done writing (cherry pick from commit 1bd2fc4fb6310da4303c3a76a259ab7e67bf39b8) After events are specified by writing into cgroup.event_control file the file should be closed. Change-Id: Id015e6a7bac2b74bbc8d8793c85f529ee00bdf55 Merged-In: Id015e6a7bac2b74bbc8d8793c85f529ee00bdf55 Signed-off-by: Suren Baghdasaryan --- lmkd.c | 1 + 1 file changed, 1 insertion(+) diff --git a/lmkd.c b/lmkd.c index d58c8f5..a9dc39c 100644 --- a/lmkd.c +++ b/lmkd.c @@ -860,6 +860,7 @@ static bool init_mp_common(void *event_handler, enum vmpressure_level level) { } maxevents++; mpevfd[level] = evfd; + close(evctlfd); return true; err: From f70073f52ed674519e33709130479a11194fc26a Mon Sep 17 00:00:00 2001 From: Suren Baghdasaryan Date: Thu, 4 Jan 2018 09:16:21 -0800 Subject: [PATCH 07/10] lmkd: Detect the highest level of vmpressure when event is detected (cherry pick from commit e82e15c242d32272fe3493b0d358329e6e3d9fa7) lmkd checks for vmpressure events using epoll_wait() with eventfds of all registered events. It's possible that multiple events of different priorities happen before epoll_wait() returns. For these cases we use conservative approach by assuming that the system is under the highest registered vmpressure levels. This speeds up lmkd response time to high memory pressure by not responding to possibly stale low pressure levels when vmpressure rises quickly. Bug: 63631020 Test: alloc-stress Change-Id: I79a85c3342e7e1b3a3be82945266b2cc60b437cf Merged-In: I79a85c3342e7e1b3a3be82945266b2cc60b437cf Signed-off-by: Suren Baghdasaryan --- lmkd.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/lmkd.c b/lmkd.c index a9dc39c..6b40d3f 100644 --- a/lmkd.c +++ b/lmkd.c @@ -102,7 +102,7 @@ static const char *level_name[] = { }; static int level_oomadj[VMPRESS_LEVEL_COUNT]; -static int mpevfd[VMPRESS_LEVEL_COUNT]; +static int mpevfd[VMPRESS_LEVEL_COUNT] = { -1, -1, -1 }; static bool debug_process_killing; static bool enable_pressure_upgrade; static int64_t upgrade_pressure; @@ -745,11 +745,20 @@ static void mp_event_common(enum vmpressure_level level) { unsigned long long evcount; int64_t mem_usage, memsw_usage; int64_t mem_pressure; + enum vmpressure_level lvl; - ret = read(mpevfd[level], &evcount, sizeof(evcount)); - if (ret < 0) - ALOGE("Error reading memory pressure event fd; errno=%d", - errno); + /* + * Check all event counters from low to critical + * and upgrade to the highest priority one. By reading + * eventfd we also reset the event counters. + */ + for (lvl = VMPRESS_LEVEL_LOW; lvl < VMPRESS_LEVEL_COUNT; lvl++) { + if (mpevfd[lvl] != -1 && + read(mpevfd[lvl], &evcount, sizeof(evcount)) > 0 && + evcount > 0 && lvl > level) { + level = lvl; + } + } mem_usage = get_memory_usage(MEMCG_MEMORY_USAGE); memsw_usage = get_memory_usage(MEMCG_MEMORYSW_USAGE); From 3bcbb99183e0272a5bbf2da924d279d515e88dc2 Mon Sep 17 00:00:00 2001 From: Suren Baghdasaryan Date: Wed, 17 Jan 2018 17:17:44 -0800 Subject: [PATCH 08/10] lmkd: Allow killing multiple processes to downgrade memory pressure (cherry pick from commit 65f54a2665c5d8ebddcb18108ea54ed36df13609) Record free memory at low vmpressure levels and whenever pressure increases beyond low free up enough memory to downgrade memory pressure to low. This is done by freeing enough memory to get to the max free memory levels seen during low vmpressure. The kill logic for Go devices is not changed as these devices are designed to operate under high memory pressure. Bug: 63631020 Test: alloc-stress Change-Id: Ic8396eee08013b1c709072a13525601d5c8bf1f1 Merged-In: Ic8396eee08013b1c709072a13525601d5c8bf1f1 Signed-off-by: Suren Baghdasaryan --- lmkd.c | 137 +++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 114 insertions(+), 23 deletions(-) diff --git a/lmkd.c b/lmkd.c index 6b40d3f..cedd444 100644 --- a/lmkd.c +++ b/lmkd.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -101,6 +102,16 @@ static const char *level_name[] = { "critical" }; +struct mem_size { + int free_mem; + int free_swap; +}; + +struct { + int min_free; /* recorded but not used yet */ + int max_free; +} low_pressure_mem = { -1, -1 }; + static int level_oomadj[VMPRESS_LEVEL_COUNT]; static int mpevfd[VMPRESS_LEVEL_COUNT] = { -1, -1, -1 }; static bool debug_process_killing; @@ -559,6 +570,18 @@ static int zoneinfo_parse(struct sysmeminfo *mip) { return 0; } +static int get_free_memory(struct mem_size *ms) { + struct sysinfo si; + + if (sysinfo(&si) < 0) + return -1; + + ms->free_mem = (int)(si.freeram * si.mem_unit / PAGE_SIZE); + ms->free_swap = (int)(si.freeswap * si.mem_unit / PAGE_SIZE); + + return 0; +} + static int proc_get_size(int pid) { char path[PATH_MAX]; char line[LINE_MAX]; @@ -676,34 +699,40 @@ static int kill_one_process(struct proc* procp, int min_score_adj, } /* - * Find a process to kill based on the current (possibly estimated) free memory - * and cached memory sizes. Returns the size of the killed processes. + * Find processes to kill to free required number of pages. + * If pages_to_free is set to 0 only one process will be killed. + * Returns the size of the killed processes. */ -static int find_and_kill_process(enum vmpressure_level level) { +static int find_and_kill_processes(enum vmpressure_level level, + int pages_to_free) { int i; - int killed_size = 0; + int killed_size; + int pages_freed = 0; int min_score_adj = level_oomadj[level]; for (i = OOM_SCORE_ADJ_MAX; i >= min_score_adj; i--) { struct proc *procp; -retry: - if (kill_heaviest_task) - procp = proc_get_heaviest(i); - else - procp = proc_adj_lru(i); + while (true) { + if (is_go_device) + procp = proc_adj_lru(i); + else + procp = proc_get_heaviest(i); + + if (!procp) + break; - if (procp) { killed_size = kill_one_process(procp, min_score_adj, level); - if (killed_size < 0) { - goto retry; - } else { - return killed_size; + if (killed_size >= 0) { + pages_freed += killed_size; + if (pages_freed >= pages_to_free) { + return pages_freed; + } } } } - return 0; + return pages_freed; } static int64_t get_memory_usage(const char* path) { @@ -730,6 +759,32 @@ static int64_t get_memory_usage(const char* path) { return mem_usage; } +void record_low_pressure_levels(struct mem_size *free_mem) { + if (low_pressure_mem.min_free == -1 || + low_pressure_mem.min_free > free_mem->free_mem) { + if (debug_process_killing) { + ALOGI("Low pressure min memory update from %d to %d", + low_pressure_mem.min_free, free_mem->free_mem); + } + low_pressure_mem.min_free = free_mem->free_mem; + } + /* + * Free memory at low vmpressure events occasionally gets spikes, + * possibly a stale low vmpressure event with memory already + * freed up (no memory pressure should have been reported). + * Ignore large jumps in max_free that would mess up our stats. + */ + if (low_pressure_mem.max_free == -1 || + (low_pressure_mem.max_free < free_mem->free_mem && + free_mem->free_mem - low_pressure_mem.max_free < low_pressure_mem.max_free * 0.1)) { + if (debug_process_killing) { + ALOGI("Low pressure max memory update from %d to %d", + low_pressure_mem.max_free, free_mem->free_mem); + } + low_pressure_mem.max_free = free_mem->free_mem; + } +} + enum vmpressure_level upgrade_level(enum vmpressure_level level) { return (enum vmpressure_level)((level < VMPRESS_LEVEL_CRITICAL) ? level + 1 : level); @@ -746,6 +801,7 @@ static void mp_event_common(enum vmpressure_level level) { int64_t mem_usage, memsw_usage; int64_t mem_pressure; enum vmpressure_level lvl; + struct mem_size free_mem; /* * Check all event counters from low to critical @@ -760,6 +816,20 @@ static void mp_event_common(enum vmpressure_level level) { } } + if (get_free_memory(&free_mem) == 0) { + if (level == VMPRESS_LEVEL_LOW) { + record_low_pressure_levels(&free_mem); + } + } else { + ALOGE("Failed to get free memory!"); + return; + } + + if (level_oomadj[level] > OOM_SCORE_ADJ_MAX) { + /* Do not monitor this pressure level */ + return; + } + mem_usage = get_memory_usage(MEMCG_MEMORY_USAGE); memsw_usage = get_memory_usage(MEMCG_MEMORYSW_USAGE); if (memsw_usage < 0 || mem_usage < 0) { @@ -796,9 +866,35 @@ static void mp_event_common(enum vmpressure_level level) { } do_kill: - if (find_and_kill_process(level) == 0) { - if (debug_process_killing) { - ALOGI("Nothing to kill"); + if (is_go_device) { + /* For Go devices kill only one task */ + if (find_and_kill_processes(level, 0) == 0) { + if (debug_process_killing) { + ALOGI("Nothing to kill"); + } + } + } else { + /* If pressure level is less than critical and enough free swap then ignore */ + if (level < VMPRESS_LEVEL_CRITICAL && free_mem.free_swap > low_pressure_mem.max_free) { + if (debug_process_killing) { + ALOGI("Ignoring pressure since %d swap pages are available ", free_mem.free_swap); + } + return; + } + + /* Free up enough memory to downgrate the memory pressure to low level */ + if (free_mem.free_mem < low_pressure_mem.max_free) { + int pages_to_free = low_pressure_mem.max_free - free_mem.free_mem; + if (debug_process_killing) { + ALOGI("Trying to free %d pages", pages_to_free); + } + int pages_freed = find_and_kill_processes(level, pages_to_free); + if (pages_freed < pages_to_free) { + if (debug_process_killing) { + ALOGI("Unable to free enough memory (pages freed=%d)", + pages_freed); + } + } } } } @@ -824,11 +920,6 @@ static bool init_mp_common(void *event_handler, enum vmpressure_level level) { int ret; const char *levelstr = level_name[level]; - if (level_oomadj[level] > OOM_SCORE_ADJ_MAX) { - ALOGI("%s pressure events are disabled", levelstr); - return true; - } - mpfd = open(MEMCG_SYSFS_PATH "memory.pressure_level", O_RDONLY | O_CLOEXEC); if (mpfd < 0) { ALOGI("No kernel memory.pressure_level support (errno=%d)", errno); From de9e6931716d3a449e9a860b307ca6e6a6fe90d7 Mon Sep 17 00:00:00 2001 From: Suren Baghdasaryan Date: Wed, 17 Jan 2018 17:28:01 -0800 Subject: [PATCH 09/10] lmkd: Implement kill timeout (cherry pick from commit caa2dc56fd52d8d773aa8b902fc605b453111976) New ro.lmk.kill_timeout_ms property defines timeout in ms after a successful kill cycle for more kills to be considered. This is necessary because memory pressure after a kill does not go down instantly and system needs time to reflect new memory state. This timeout prevents extra kills in the period immediately after a kill cycle. By default it is set to 0 which disables this feature. Bug: 63631020 Test: alloc-stress Change-Id: Ia847118c8c4a659a7fc38cd5cd0042acb514ae28 Merged-In: Ia847118c8c4a659a7fc38cd5cd0042acb514ae28 Signed-off-by: Suren Baghdasaryan --- lmkd.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/lmkd.c b/lmkd.c index cedd444..7e940ec 100644 --- a/lmkd.c +++ b/lmkd.c @@ -120,6 +120,7 @@ static int64_t upgrade_pressure; static int64_t downgrade_pressure; static bool is_go_device; static bool kill_heaviest_task; +static unsigned long kill_timeout_ms; /* control socket listen and data */ static int ctrl_lfd; @@ -795,6 +796,12 @@ enum vmpressure_level downgrade_level(enum vmpressure_level level) { level - 1 : level); } +static inline unsigned long get_time_diff_ms(struct timeval *from, + struct timeval *to) { + return (to->tv_sec - from->tv_sec) * 1000 + + (to->tv_usec - from->tv_usec) / 1000; +} + static void mp_event_common(enum vmpressure_level level) { int ret; unsigned long long evcount; @@ -802,6 +809,8 @@ static void mp_event_common(enum vmpressure_level level) { int64_t mem_pressure; enum vmpressure_level lvl; struct mem_size free_mem; + static struct timeval last_report_tm; + static unsigned long skip_count = 0; /* * Check all event counters from low to critical @@ -816,6 +825,23 @@ static void mp_event_common(enum vmpressure_level level) { } } + if (kill_timeout_ms) { + struct timeval curr_tm; + gettimeofday(&curr_tm, NULL); + if (get_time_diff_ms(&last_report_tm, &curr_tm) < kill_timeout_ms) { + skip_count++; + return; + } + } + + if (skip_count > 0) { + if (debug_process_killing) { + ALOGI("%lu memory pressure events were skipped after a kill!", + skip_count); + } + skip_count = 0; + } + if (get_free_memory(&free_mem) == 0) { if (level == VMPRESS_LEVEL_LOW) { record_low_pressure_levels(&free_mem); @@ -894,6 +920,8 @@ do_kill: ALOGI("Unable to free enough memory (pages freed=%d)", pages_freed); } + } else { + gettimeofday(&last_report_tm, NULL); } } } @@ -1081,6 +1109,8 @@ int main(int argc __unused, char **argv __unused) { kill_heaviest_task = property_get_bool("ro.lmk.kill_heaviest_task", true); is_go_device = property_get_bool("ro.config.low_ram", false); + kill_timeout_ms = + (unsigned long)property_get_int32("ro.lmk.kill_timeout_ms", 0); // MCL_ONFAULT pins pages as they fault instead of loading // everything immediately all at once. (Which would be bad, From 4d27393414a81a1fb769fb06af29899f2d088f23 Mon Sep 17 00:00:00 2001 From: Suren Baghdasaryan Date: Thu, 18 Jan 2018 17:27:30 -0800 Subject: [PATCH 10/10] lmkd: Select in-kernel vs userspace lmk based on kernel driver presence (cherry pick from commit 979591b627601f457955bcf1f6b5f6de6493777b) Currently selection criteria for in-kernel vs userspace lmk is kernel driver presence and device not being a Go device. This change removes Go device check leaving kernel driver presence to be the only selection criteria. Bug: 71502948 Change-Id: I394a7920433a8d090e207ea86296356413a63fe7 Merged-In: I394a7920433a8d090e207ea86296356413a63fe7 Signed-off-by: Suren Baghdasaryan --- lmkd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lmkd.c b/lmkd.c index 7e940ec..338e5fa 100644 --- a/lmkd.c +++ b/lmkd.c @@ -1038,7 +1038,7 @@ static int init(void) { maxevents++; has_inkernel_module = !access(INKERNEL_MINFREE_PATH, W_OK); - use_inkernel_interface = has_inkernel_module && !is_go_device; + use_inkernel_interface = has_inkernel_module; if (use_inkernel_interface) { ALOGI("Using in-kernel low memory killer interface");