lmkd: Use aggregate zone watermarks as low memory threshold
Parsing /proc/zoneinfo is expensive and zone watermarks normally do no
change often. Instead of checking free memory per each zone we aggregate
zone watermarks and compare them with MemFree from meminfo as an
approximation of memory being under a given watermark.
zoneinfo parsing is rate limited to once per minute to detect a possible
change of the memory margins from userspace.
Bug: 132642304
Test: lmkd_unit_test, ACT memory pressure tests
Change-Id: If4a8154c004e24324e6de44359de416766139df6
Signed-off-by: Suren Baghdasaryan <surenb@google.com>
diff --git a/lmkd/lmkd.c b/lmkd/lmkd.c
index 221fbc7..42e3db3 100644
--- a/lmkd/lmkd.c
+++ b/lmkd/lmkd.c
@@ -1951,41 +1951,49 @@
WMARK_NONE
};
+struct zone_watermarks {
+ long high_wmark;
+ long low_wmark;
+ long min_wmark;
+};
+
/*
* Returns lowest breached watermark or WMARK_NONE.
*/
-static enum zone_watermark get_lowest_watermark(struct zoneinfo *zi)
+static enum zone_watermark get_lowest_watermark(union meminfo *mi,
+ struct zone_watermarks *watermarks)
{
- enum zone_watermark wmark = WMARK_NONE;
+ int64_t nr_free_pages = mi->field.nr_free_pages - mi->field.cma_free;
+
+ if (nr_free_pages < watermarks->min_wmark) {
+ return WMARK_MIN;
+ }
+ if (nr_free_pages < watermarks->low_wmark) {
+ return WMARK_LOW;
+ }
+ if (nr_free_pages < watermarks->high_wmark) {
+ return WMARK_HIGH;
+ }
+ return WMARK_NONE;
+}
+
+void calc_zone_watermarks(struct zoneinfo *zi, struct zone_watermarks *watermarks) {
+ memset(watermarks, 0, sizeof(struct zone_watermarks));
for (int node_idx = 0; node_idx < zi->node_count; node_idx++) {
struct zoneinfo_node *node = &zi->nodes[node_idx];
-
for (int zone_idx = 0; zone_idx < node->zone_count; zone_idx++) {
struct zoneinfo_zone *zone = &node->zones[zone_idx];
- int zone_free_mem;
if (!zone->fields.field.present) {
continue;
}
- zone_free_mem = zone->fields.field.nr_free_pages - zone->fields.field.nr_free_cma;
- if (zone_free_mem > zone->max_protection + zone->fields.field.high) {
- continue;
- }
- if (zone_free_mem > zone->max_protection + zone->fields.field.low) {
- if (wmark > WMARK_HIGH) wmark = WMARK_HIGH;
- continue;
- }
- if (zone_free_mem > zone->max_protection + zone->fields.field.min) {
- if (wmark > WMARK_LOW) wmark = WMARK_LOW;
- continue;
- }
- wmark = WMARK_MIN;
+ watermarks->high_wmark += zone->max_protection + zone->fields.field.high;
+ watermarks->low_wmark += zone->max_protection + zone->fields.field.low;
+ watermarks->min_wmark += zone->max_protection + zone->fields.field.min;
}
}
-
- return wmark;
}
static void mp_event_psi(int data, uint32_t events, struct polling_params *poll_params) {
@@ -2012,10 +2020,11 @@
static bool killing;
static int thrashing_limit;
static bool in_reclaim;
+ static struct zone_watermarks watermarks;
+ static struct timespec wmark_update_tm;
union meminfo mi;
union vmstat vs;
- struct zoneinfo zi;
struct timespec curr_tm;
int64_t thrashing = 0;
bool swap_is_low = false;
@@ -2088,12 +2097,25 @@
}
in_reclaim = true;
+ /*
+ * Refresh watermarks once per min in case user updated one of the margins.
+ * TODO: b/140521024 replace this periodic update with an API for AMS to notify LMKD
+ * that zone watermarks were changed by the system software.
+ */
+ if (watermarks.high_wmark == 0 || get_time_diff_ms(&wmark_update_tm, &curr_tm) > 60000) {
+ struct zoneinfo zi;
+
+ if (zoneinfo_parse(&zi) < 0) {
+ ALOGE("Failed to parse zoneinfo!");
+ return;
+ }
+
+ calc_zone_watermarks(&zi, &watermarks);
+ wmark_update_tm = curr_tm;
+ }
+
/* Find out which watermark is breached if any */
- if (zoneinfo_parse(&zi) < 0) {
- ALOGE("Failed to parse zoneinfo!");
- return;
- }
- wmark = get_lowest_watermark(&zi);
+ wmark = get_lowest_watermark(&mi, &watermarks);
/*
* TODO: move this logic into a separate function