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
Merged-In: I6b51972951026854a079fcda33d6786b7ed035e4
Change-Id: I6b51972951026854a079fcda33d6786b7ed035e4
Signed-off-by: Suren Baghdasaryan <surenb@google.com>
diff --git a/lmkd/README.md b/lmkd/README.md
index ba2e83d..656a6ea 100644
--- a/lmkd/README.md
+++ b/lmkd/README.md
@@ -29,6 +29,11 @@
   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/lmkd.c b/lmkd/lmkd.c
index 5eb3fbe..151e1dc 100644
--- a/lmkd/lmkd.c
+++ b/lmkd/lmkd.c
@@ -109,6 +109,7 @@
 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 {
@@ -972,11 +973,10 @@
  * 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];
 
     for (i = OOM_SCORE_ADJ_MAX; i >= min_score_adj; i--) {
         struct proc *procp;
@@ -1071,9 +1071,14 @@
     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,
@@ -1114,11 +1119,40 @@
         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);
     }
@@ -1167,39 +1201,58 @@
 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;
             }
-            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");
+            }
         }
 
-        /* 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);
+                ALOGI("Unable to free enough memory (pages freed=%d)", pages_freed);
             }
-            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);
-            }
+        } else {
+            gettimeofday(&last_report_tm, NULL);
         }
     }
 }
@@ -1409,6 +1462,8 @@
     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);
 
     if (!init()) {
         if (!use_inkernel_interface) {