From d28a97323567a9f49478ccead37a6200cbd89cd9 Mon Sep 17 00:00:00 2001 From: Suren Baghdasaryan Date: Fri, 13 Apr 2018 13:11:51 -0700 Subject: [PATCH 1/3] lmkd: Add zoneinfo and meminfo parsing routines /proc/zoneinfo and /proc/meminfo contain information necessary for lmkd to assess system memory state. Add routines to parse these files. Bug: 77299493 Bug: 75322373 Change-Id: Ie7d80bbb81fd0d2fc0629f6f678e6afc97fed1f6 Merged-In: Ie7d80bbb81fd0d2fc0629f6f678e6afc97fed1f6 Signed-off-by: Suren Baghdasaryan (cherry picked from commit da0bc05b2293095c4e1153268ae7b53773f8f5ed) --- lmkd.c | 238 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 237 insertions(+), 1 deletion(-) diff --git a/lmkd.c b/lmkd.c index 72e3251..830bb87 100644 --- a/lmkd.c +++ b/lmkd.c @@ -66,7 +66,8 @@ #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 ZONEINFO_PATH "/proc/zoneinfo" +#define MEMINFO_PATH "/proc/meminfo" #define LINE_MAX 128 #define INKERNEL_MINFREE_PATH "/sys/module/lowmemorykiller/parameters/minfree" @@ -151,6 +152,86 @@ static int lowmem_adj[MAX_TARGETS]; static int lowmem_minfree[MAX_TARGETS]; static int lowmem_targets_size; +/* Fields to parse in /proc/zoneinfo */ +enum zoneinfo_field { + ZI_NR_FREE_PAGES = 0, + ZI_NR_FILE_PAGES, + ZI_NR_SHMEM, + ZI_NR_UNEVICTABLE, + ZI_WORKINGSET_REFAULT, + ZI_HIGH, + ZI_FIELD_COUNT +}; + +static const char* const zoneinfo_field_names[ZI_FIELD_COUNT] = { + "nr_free_pages", + "nr_file_pages", + "nr_shmem", + "nr_unevictable", + "workingset_refault", + "high", +}; + +union zoneinfo { + struct { + int64_t nr_free_pages; + int64_t nr_file_pages; + int64_t nr_shmem; + int64_t nr_unevictable; + int64_t workingset_refault; + int64_t high; + /* fields below are calculated rather than read from the file */ + int64_t totalreserve_pages; + } field; + int64_t arr[ZI_FIELD_COUNT]; +}; + +/* Fields to parse in /proc/meminfo */ +enum meminfo_field { + MI_NR_FREE_PAGES = 0, + MI_CACHED, + MI_SWAP_CACHED, + MI_BUFFERS, + MI_SHMEM, + MI_UNEVICTABLE, + MI_FREE_SWAP, + MI_DIRTY, + MI_FIELD_COUNT +}; + +static const char* const meminfo_field_names[MI_FIELD_COUNT] = { + "MemFree:", + "Cached:", + "SwapCached:", + "Buffers:", + "Shmem:", + "Unevictable:", + "SwapFree:", + "Dirty:", +}; + +union meminfo { + struct { + int64_t nr_free_pages; + int64_t cached; + int64_t swap_cached; + int64_t buffers; + int64_t shmem; + int64_t unevictable; + int64_t free_swap; + int64_t dirty; + /* fields below are calculated rather than read from the file */ + int64_t nr_file_pages; + } field; + int64_t arr[MI_FIELD_COUNT]; +}; + +enum field_match_result { + NO_MATCH, + PARSE_FAIL, + PARSE_SUCCESS +}; + struct sysmeminfo { int nr_free_pages; int nr_file_pages; @@ -201,6 +282,22 @@ static bool parse_int64(const char* str, int64_t* ret) { return true; } +static enum field_match_result match_field(const char* cp, const char* ap, + const char* const field_names[], + int field_count, int64_t* field, + int *field_idx) { + int64_t val; + int i; + + for (i = 0; i < field_count; i++) { + if (!strcmp(cp, field_names[i])) { + *field_idx = i; + return parse_int64(ap, field) ? PARSE_SUCCESS : PARSE_FAIL; + } + } + return NO_MATCH; +} + /* * Read file content from the beginning up to max_len bytes or EOF * whichever happens first. @@ -653,6 +750,145 @@ static int memory_stat_parse(struct memory_stat *mem_st, int pid, uid_t uid) { } #endif +/* /prop/zoneinfo parsing routines */ +static int64_t zoneinfo_parse_protection(char *cp) { + int64_t max = 0; + long long zoneval; + char *save_ptr; + + for (cp = strtok_r(cp, "(), ", &save_ptr); cp; + cp = strtok_r(NULL, "), ", &save_ptr)) { + zoneval = strtoll(cp, &cp, 0); + if (zoneval > max) { + max = (zoneval > INT64_MAX) ? INT64_MAX : zoneval; + } + } + + return max; +} + +static bool zoneinfo_parse_line(char *line, union zoneinfo *zi) { + char *cp = line; + char *ap; + char *save_ptr; + int64_t val; + int field_idx; + + cp = strtok_r(line, " ", &save_ptr); + if (!cp) { + return true; + } + + if (!strcmp(cp, "protection:")) { + ap = strtok_r(NULL, ")", &save_ptr); + } else { + ap = strtok_r(NULL, " ", &save_ptr); + } + + if (!ap) { + return true; + } + + switch (match_field(cp, ap, zoneinfo_field_names, + ZI_FIELD_COUNT, &val, &field_idx)) { + case (PARSE_SUCCESS): + zi->arr[field_idx] += val; + break; + case (NO_MATCH): + if (!strcmp(cp, "protection:")) { + zi->field.totalreserve_pages += + zoneinfo_parse_protection(ap); + } + break; + case (PARSE_FAIL): + default: + return false; + } + return true; +} + +static int zoneinfo_parse(union zoneinfo *zi) { + static struct reread_data file_data = { + .filename = ZONEINFO_PATH, + .fd = -1, + }; + char buf[PAGE_SIZE]; + char *save_ptr; + char *line; + + memset(zi, 0, sizeof(union zoneinfo)); + + if (reread_file(&file_data, buf, sizeof(buf)) < 0) { + return -1; + } + + for (line = strtok_r(buf, "\n", &save_ptr); line; + line = strtok_r(NULL, "\n", &save_ptr)) { + if (!zoneinfo_parse_line(line, zi)) { + ALOGE("%s parse error", file_data.filename); + return -1; + } + } + zi->field.totalreserve_pages += zi->field.high; + + return 0; +} + +/* /prop/meminfo parsing routines */ +static bool meminfo_parse_line(char *line, union meminfo *mi) { + char *cp = line; + char *ap; + char *save_ptr; + int64_t val; + int field_idx; + enum field_match_result match_res; + + cp = strtok_r(line, " ", &save_ptr); + if (!cp) { + return false; + } + + ap = strtok_r(NULL, " ", &save_ptr); + if (!ap) { + return false; + } + + match_res = match_field(cp, ap, meminfo_field_names, MI_FIELD_COUNT, + &val, &field_idx); + if (match_res == PARSE_SUCCESS) { + mi->arr[field_idx] = val / page_k; + } + return (match_res != PARSE_FAIL); +} + +static int meminfo_parse(union meminfo *mi) { + static struct reread_data file_data = { + .filename = MEMINFO_PATH, + .fd = -1, + }; + char buf[PAGE_SIZE]; + char *save_ptr; + char *line; + + memset(mi, 0, sizeof(union meminfo)); + + if (reread_file(&file_data, buf, sizeof(buf)) < 0) { + return -1; + } + + for (line = strtok_r(buf, "\n", &save_ptr); line; + line = strtok_r(NULL, "\n", &save_ptr)) { + if (!meminfo_parse_line(line, mi)) { + ALOGE("%s parse error", file_data.filename); + return -1; + } + } + mi->field.nr_file_pages = mi->field.cached + mi->field.swap_cached + + mi->field.buffers; + + return 0; +} + static int get_free_memory(struct mem_size *ms) { struct sysinfo si; From e0f3dd132da2ebb8a0a6e673538abfbb2f24a7f9 Mon Sep 17 00:00:00 2001 From: Suren Baghdasaryan Date: Fri, 13 Apr 2018 13:41:12 -0700 Subject: [PATCH 2/3] lmkd: Switch to using /proc/meminfo to have access to file cache size Current mechanism of getting system memory state by using sysinfo() is not enough because it does not provide information about file cache size which is needed to correctly assess memory state. Switch to using data from /proc/meminfo that includes information provided by sysinfo() and file cache size in addition to that. Bug: 77299493 Bug: 75322373 Change-Id: I16106fc4f9254f17f776803e60502b7b6474e1b7 Merged-In: I16106fc4f9254f17f776803e60502b7b6474e1b7 Signed-off-by: Suren Baghdasaryan (cherry picked from commit 9ac54eb2f88a4a8fc84d1da307be93f99573ead4) --- lmkd.c | 79 ++++++++++++++++++++++------------------------------------ 1 file changed, 30 insertions(+), 49 deletions(-) diff --git a/lmkd.c b/lmkd.c index 830bb87..2bf201c 100644 --- a/lmkd.c +++ b/lmkd.c @@ -97,14 +97,9 @@ 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; + int64_t min_nr_free_pages; /* recorded but not used yet */ + int64_t max_nr_free_pages; } low_pressure_mem = { -1, -1 }; static int level_oomadj[VMPRESS_LEVEL_COUNT]; @@ -232,13 +227,6 @@ enum field_match_result { PARSE_SUCCESS }; -struct sysmeminfo { - int nr_free_pages; - int nr_file_pages; - int nr_shmem; - int totalreserve_pages; -}; - struct adjslot_list { struct adjslot_list *next; struct adjslot_list *prev; @@ -889,18 +877,6 @@ static int meminfo_parse(union meminfo *mi) { 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]; @@ -1111,29 +1087,30 @@ static int64_t get_memory_usage(struct reread_data *file_data) { 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) { +void record_low_pressure_levels(union meminfo *mi) { + if (low_pressure_mem.min_nr_free_pages == -1 || + low_pressure_mem.min_nr_free_pages > mi->field.nr_free_pages) { if (debug_process_killing) { - ALOGI("Low pressure min memory update from %d to %d", - low_pressure_mem.min_free, free_mem->free_mem); + ALOGI("Low pressure min memory update from %" PRId64 " to %" PRId64, + low_pressure_mem.min_nr_free_pages, mi->field.nr_free_pages); } - low_pressure_mem.min_free = free_mem->free_mem; + low_pressure_mem.min_nr_free_pages = mi->field.nr_free_pages; } /* * 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. + * Ignore large jumps in max_nr_free_pages 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 (low_pressure_mem.max_nr_free_pages == -1 || + (low_pressure_mem.max_nr_free_pages < mi->field.nr_free_pages && + mi->field.nr_free_pages - low_pressure_mem.max_nr_free_pages < + low_pressure_mem.max_nr_free_pages * 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); + ALOGI("Low pressure max memory update from %" PRId64 " to %" PRId64, + low_pressure_mem.max_nr_free_pages, mi->field.nr_free_pages); } - low_pressure_mem.max_free = free_mem->free_mem; + low_pressure_mem.max_nr_free_pages = mi->field.nr_free_pages; } } @@ -1159,7 +1136,7 @@ static void mp_event_common(int data, uint32_t events __unused) { int64_t mem_usage, memsw_usage; int64_t mem_pressure; enum vmpressure_level lvl; - struct mem_size free_mem; + union meminfo mi; static struct timeval last_report_tm; static unsigned long skip_count = 0; enum vmpressure_level level = (enum vmpressure_level)data; @@ -1203,15 +1180,15 @@ static void mp_event_common(int data, uint32_t events __unused) { skip_count = 0; } - if (get_free_memory(&free_mem) == 0) { - if (level == VMPRESS_LEVEL_LOW) { - record_low_pressure_levels(&free_mem); - } - } else { + if (meminfo_parse(&mi) < 0) { ALOGE("Failed to get free memory!"); return; } + if (level == VMPRESS_LEVEL_LOW) { + record_low_pressure_levels(&mi); + } + if (level_oomadj[level] > OOM_SCORE_ADJ_MAX) { /* Do not monitor this pressure level */ return; @@ -1263,16 +1240,20 @@ do_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 (level < VMPRESS_LEVEL_CRITICAL && + mi.field.free_swap > low_pressure_mem.max_nr_free_pages) { if (debug_process_killing) { - ALOGI("Ignoring pressure since %d swap pages are available ", free_mem.free_swap); + ALOGI("Ignoring pressure since %" PRId64 + " swap pages are available ", + mi.field.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 (mi.field.nr_free_pages < low_pressure_mem.max_nr_free_pages) { + int pages_to_free = low_pressure_mem.max_nr_free_pages - + mi.field.nr_free_pages; if (debug_process_killing) { ALOGI("Trying to free %d pages", pages_to_free); } From 2c4611a5ab7bd2aa5e70fb5dd730c7e7772528f5 Mon Sep 17 00:00:00 2001 From: Suren Baghdasaryan Date: Fri, 13 Apr 2018 13:53:43 -0700 Subject: [PATCH 3/3] lmkd: Introduce support for legacy kill algorithm that uses minfree levels Add ability to switch to the algorithm used by lowmemorykiller driver for determining when to kill. It uses minfree levels to decide at which levels of free memory and file cache to kill a process. oom_adj_score is also determined by comparing current memory resources against minfree levels. ro.lmk.use_minfree_levels property is introduces for switching into this mode. By default it is disabled. Bug: 77299493 Bug: 75322373 Change-Id: I6b51972951026854a079fcda33d6786b7ed035e4 Merged-In: I6b51972951026854a079fcda33d6786b7ed035e4 Signed-off-by: Suren Baghdasaryan (cherry picked from commit d273b6630d47d6bb32996bcc87e01a73240cb228) --- README.md | 5 +++ lmkd.c | 107 +++++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 86 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index ba2e83d..656a6ea 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,11 @@ properties: ro.config.low_ram: choose between low-memory vs high-performance device. Default = false. + ro.lmk.use_minfree_levels: use free memory and file cache thresholds for + making decisions when to kill. This mode works + the same way kernel lowmemorykiller driver used + to work. Default = false + ro.lmk.low: min oom_adj score for processes eligible to be killed at low vmpressure level. Default = 1001 (disabled) diff --git a/lmkd.c b/lmkd.c index 2bf201c..ec55f90 100644 --- a/lmkd.c +++ b/lmkd.c @@ -111,6 +111,7 @@ static int64_t downgrade_pressure; static bool low_ram_device; static bool kill_heaviest_task; static unsigned long kill_timeout_ms; +static bool use_minfree_levels; /* data required to handle events */ struct event_handler_info { @@ -1019,11 +1020,10 @@ static int kill_one_process(struct proc* procp, int min_score_adj, * Returns the size of the killed processes. */ static int find_and_kill_processes(enum vmpressure_level level, - int pages_to_free) { + int min_score_adj, int pages_to_free) { int i; int killed_size; int pages_freed = 0; - int min_score_adj = level_oomadj[level]; #ifdef LMKD_LOG_STATS if (enable_stats_log) { @@ -1137,9 +1137,14 @@ static void mp_event_common(int data, uint32_t events __unused) { int64_t mem_pressure; enum vmpressure_level lvl; union meminfo mi; + union zoneinfo zi; static struct timeval last_report_tm; static unsigned long skip_count = 0; enum vmpressure_level level = (enum vmpressure_level)data; + long other_free = 0, other_file = 0; + int min_score_adj; + int pages_to_free = 0; + int minfree = 0; static struct reread_data mem_usage_file_data = { .filename = MEMCG_MEMORY_USAGE, .fd = -1, @@ -1180,11 +1185,40 @@ static void mp_event_common(int data, uint32_t events __unused) { skip_count = 0; } - if (meminfo_parse(&mi) < 0) { + if (meminfo_parse(&mi) < 0 || zoneinfo_parse(&zi) < 0) { ALOGE("Failed to get free memory!"); return; } + if (use_minfree_levels) { + int i; + + other_free = mi.field.nr_free_pages - zi.field.totalreserve_pages; + if (mi.field.nr_file_pages > (mi.field.shmem + mi.field.unevictable + mi.field.swap_cached)) { + other_file = (mi.field.nr_file_pages - mi.field.shmem - + mi.field.unevictable - mi.field.swap_cached); + } else { + other_file = 0; + } + + min_score_adj = OOM_SCORE_ADJ_MAX + 1; + for (i = 0; i < lowmem_targets_size; i++) { + minfree = lowmem_minfree[i]; + if (other_free < minfree && other_file < minfree) { + min_score_adj = lowmem_adj[i]; + break; + } + } + + if (min_score_adj == OOM_SCORE_ADJ_MAX + 1) + return; + + /* Free up enough pages to push over the highest minfree level */ + pages_to_free = lowmem_minfree[lowmem_targets_size - 1] - + ((other_free < other_file) ? other_free : other_file); + goto do_kill; + } + if (level == VMPRESS_LEVEL_LOW) { record_low_pressure_levels(&mi); } @@ -1233,39 +1267,58 @@ static void mp_event_common(int data, uint32_t events __unused) { do_kill: if (low_ram_device) { /* For Go devices kill only one task */ - if (find_and_kill_processes(level, 0) == 0) { + if (find_and_kill_processes(level, level_oomadj[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 && - mi.field.free_swap > low_pressure_mem.max_nr_free_pages) { - if (debug_process_killing) { - ALOGI("Ignoring pressure since %" PRId64 - " swap pages are available ", - mi.field.free_swap); + int pages_freed; + + if (!use_minfree_levels) { + /* If pressure level is less than critical and enough free swap then ignore */ + if (level < VMPRESS_LEVEL_CRITICAL && + mi.field.free_swap > low_pressure_mem.max_nr_free_pages) { + if (debug_process_killing) { + ALOGI("Ignoring pressure since %" PRId64 + " swap pages are available ", + mi.field.free_swap); + } + return; + } + /* Free up enough memory to downgrate the memory pressure to low level */ + if (mi.field.nr_free_pages < low_pressure_mem.max_nr_free_pages) { + pages_to_free = low_pressure_mem.max_nr_free_pages - + mi.field.nr_free_pages; + } else { + if (debug_process_killing) { + ALOGI("Ignoring pressure since more memory is " + "available (%" PRId64 ") than watermark (%" PRId64 ")", + mi.field.nr_free_pages, low_pressure_mem.max_nr_free_pages); + } + return; + } + min_score_adj = level_oomadj[level]; + } else { + if (debug_process_killing) { + ALOGI("Killing because cache %ldkB is below " + "limit %ldkB for oom_adj %d\n" + " Free memory is %ldkB %s reserved", + other_file * page_k, minfree * page_k, min_score_adj, + other_free * page_k, other_free >= 0 ? "above" : "below"); } - return; } - /* Free up enough memory to downgrate the memory pressure to low level */ - if (mi.field.nr_free_pages < low_pressure_mem.max_nr_free_pages) { - int pages_to_free = low_pressure_mem.max_nr_free_pages - - mi.field.nr_free_pages; + if (debug_process_killing) { + ALOGI("Trying to free %d pages", pages_to_free); + } + pages_freed = find_and_kill_processes(level, min_score_adj, pages_to_free); + if (pages_freed < pages_to_free) { 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); - } - } else { - gettimeofday(&last_report_tm, NULL); + ALOGI("Unable to free enough memory (pages freed=%d)", pages_freed); } + } else { + gettimeofday(&last_report_tm, NULL); } } } @@ -1475,6 +1528,8 @@ int main(int argc __unused, char **argv __unused) { low_ram_device = property_get_bool("ro.config.low_ram", false); kill_timeout_ms = (unsigned long)property_get_int32("ro.lmk.kill_timeout_ms", 0); + use_minfree_levels = + property_get_bool("ro.lmk.use_minfree_levels", false); #ifdef LMKD_LOG_STATS statslog_init(&log_ctx, &enable_stats_log);