lowmemorykiller: adapt to vmpressure

There were issues reported, where page cache thrashing was
observed because of LMK not killing tasks when required,
resulting in sluggishness and higher app launch latency.
LMK does not kill a task for the following reasons.
1. The free and file pages are above the LMK thresholds
2. LMK tries to pick task with an adj level corresponding
to current thresholds, but fails to do so because of the
absence of tasks in that level.
But sometimes it is better to kill a lower adj task, than thrashing.
And there are cases where the number of file pages are huge, though
we dont thrash, the reclaim process becomes time consuming, since
LMK triggers will be delayed because of higher number of file
pages. Even in such cases, when reclaim path finds it difficult
to reclaim pages, it is better to trigger lmk to free up some memory
faster.

The basic idea here is to make LMK more aggressive dynamically
when such a thrashing scenario is detected.

To detect thrashing, this patch uses vmpressure events.
The values of vmpressure upon which an action has to be taken,
was derived empirically.

This patch also adds tracepoints to validate this feature,
almk_shrink and almk_vmpressure.

Two knobs are available for the user to tune adaptive lmk
behaviour.

/sys/module/lowmemorykiller/parameters/adaptive_lmk - Write
1 to enable the feature, 0 to disable. By default disabled.

/sys/module/lowmemorykiller/parameters/vmpressure_file_min -
This parameter controls the behaviour of LMK when vmpressure
is in the range of 90-94. Adaptive lmk triggers based on number file
pages wrt vmpressure_file_min, when vmpressure is in the range of
90-94. Usually this is a pseudo minfree value, higher than the
highest configured value in minfree array.

Change-Id: I1a08160c35d3e33bdfd1d2c789c288fc07d0f0d3
Signed-off-by: Vinayak Menon <vinmenon@codeaurora.org>
This commit is contained in:
Vinayak Menon 2015-03-04 20:41:21 +05:30
parent 314a207926
commit a7668cd5e2
2 changed files with 193 additions and 2 deletions

View File

@ -45,6 +45,10 @@
#include <linux/fs.h>
#include <linux/cpuset.h>
#include <linux/show_mem_notifier.h>
#include <linux/vmpressure.h>
#define CREATE_TRACE_POINTS
#include <trace/events/almk.h>
#ifdef CONFIG_HIGHMEM
#define _ZONE ZONE_HIGHMEM
@ -77,6 +81,96 @@ static unsigned long lowmem_deathpending_timeout;
pr_info(x); \
} while (0)
static atomic_t shift_adj = ATOMIC_INIT(0);
static short adj_max_shift = 353;
/* User knob to enable/disable adaptive lmk feature */
static int enable_adaptive_lmk;
module_param_named(enable_adaptive_lmk, enable_adaptive_lmk, int,
S_IRUGO | S_IWUSR);
/*
* This parameter controls the behaviour of LMK when vmpressure is in
* the range of 90-94. Adaptive lmk triggers based on number of file
* pages wrt vmpressure_file_min, when vmpressure is in the range of
* 90-94. Usually this is a pseudo minfree value, higher than the
* highest configured value in minfree array.
*/
static int vmpressure_file_min;
module_param_named(vmpressure_file_min, vmpressure_file_min, int,
S_IRUGO | S_IWUSR);
enum {
VMPRESSURE_NO_ADJUST = 0,
VMPRESSURE_ADJUST_ENCROACH,
VMPRESSURE_ADJUST_NORMAL,
};
int adjust_minadj(short *min_score_adj)
{
int ret = VMPRESSURE_NO_ADJUST;
if (!enable_adaptive_lmk)
return 0;
if (atomic_read(&shift_adj) &&
(*min_score_adj > adj_max_shift)) {
if (*min_score_adj == OOM_SCORE_ADJ_MAX + 1)
ret = VMPRESSURE_ADJUST_ENCROACH;
else
ret = VMPRESSURE_ADJUST_NORMAL;
*min_score_adj = adj_max_shift;
}
atomic_set(&shift_adj, 0);
return ret;
}
static int lmk_vmpressure_notifier(struct notifier_block *nb,
unsigned long action, void *data)
{
int other_free, other_file;
unsigned long pressure = action;
int array_size = ARRAY_SIZE(lowmem_adj);
if (!enable_adaptive_lmk)
return 0;
if (pressure >= 95) {
other_file = global_page_state(NR_FILE_PAGES) -
global_page_state(NR_SHMEM) -
total_swapcache_pages();
other_free = global_page_state(NR_FREE_PAGES);
atomic_set(&shift_adj, 1);
trace_almk_vmpressure(pressure, other_free, other_file);
} else if (pressure >= 90) {
if (lowmem_adj_size < array_size)
array_size = lowmem_adj_size;
if (lowmem_minfree_size < array_size)
array_size = lowmem_minfree_size;
other_file = global_page_state(NR_FILE_PAGES) -
global_page_state(NR_SHMEM) -
total_swapcache_pages();
other_free = global_page_state(NR_FREE_PAGES);
if ((other_free < lowmem_minfree[array_size - 1]) &&
(other_file < vmpressure_file_min)) {
atomic_set(&shift_adj, 1);
trace_almk_vmpressure(pressure, other_free,
other_file);
}
}
return 0;
}
static struct notifier_block lmk_vmpr_nb = {
.notifier_call = lmk_vmpressure_notifier,
};
static int test_task_flag(struct task_struct *p, int flag)
{
struct task_struct *t = p;
@ -270,6 +364,7 @@ static int lowmem_shrink(struct shrinker *s, struct shrink_control *sc)
int rem = 0;
int tasksize;
int i;
int ret = 0;
short min_score_adj = OOM_SCORE_ADJ_MAX + 1;
int minfree = 0;
int selected_tasksize = 0;
@ -307,10 +402,13 @@ static int lowmem_shrink(struct shrinker *s, struct shrink_control *sc)
break;
}
}
if (nr_to_scan > 0)
if (nr_to_scan > 0) {
ret = adjust_minadj(&min_score_adj);
lowmem_print(3, "lowmem_shrink %lu, %x, ofree %d %d, ma %hd\n",
nr_to_scan, sc->gfp_mask, other_free,
other_file, min_score_adj);
}
rem = global_page_state(NR_ACTIVE_ANON) +
global_page_state(NR_ACTIVE_FILE) +
global_page_state(NR_INACTIVE_ANON) +
@ -322,6 +420,10 @@ static int lowmem_shrink(struct shrinker *s, struct shrink_control *sc)
if (nr_to_scan > 0)
mutex_unlock(&scan_mutex);
if ((min_score_adj == OOM_SCORE_ADJ_MAX + 1) &&
(nr_to_scan > 0))
trace_almk_shrink(0, ret, other_free, other_file, 0);
return rem;
}
selected_oom_score_adj = min_score_adj;
@ -425,8 +527,12 @@ static int lowmem_shrink(struct shrinker *s, struct shrink_control *sc)
rcu_read_unlock();
/* give the system time to free up the memory */
msleep_interruptible(20);
} else
trace_almk_shrink(selected_tasksize, ret,
other_free, other_file, selected_oom_score_adj);
} else {
trace_almk_shrink(1, ret, other_free, other_file, 0);
rcu_read_unlock();
}
lowmem_print(4, "lowmem_shrink %lu, %x, return %d\n",
nr_to_scan, sc->gfp_mask, rem);
@ -442,6 +548,7 @@ static struct shrinker lowmem_shrinker = {
static int __init lowmem_init(void)
{
register_shrinker(&lowmem_shrinker);
vmpressure_notifier_register(&lmk_vmpr_nb);
return 0;
}

View File

@ -0,0 +1,84 @@
/* Copyright (c) 2015, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#undef TRACE_SYSTEM
#define TRACE_SYSTEM almk
#if !defined(_TRACE_EVENT_ALMK_H) || defined(TRACE_HEADER_MULTI_READ)
#define _TRACE_EVENT_ALMK_H
#include <linux/tracepoint.h>
#include <linux/types.h>
TRACE_EVENT(almk_vmpressure,
TP_PROTO(unsigned long pressure,
int other_free,
int other_file),
TP_ARGS(pressure, other_free, other_file),
TP_STRUCT__entry(
__field(unsigned long, pressure)
__field(int, other_free)
__field(int, other_file)
),
TP_fast_assign(
__entry->pressure = pressure;
__entry->other_free = other_free;
__entry->other_file = other_file;
),
TP_printk("%lu, %d, %d",
__entry->pressure, __entry->other_free,
__entry->other_file)
);
TRACE_EVENT(almk_shrink,
TP_PROTO(int tsize,
int vmp,
int other_free,
int other_file,
short adj),
TP_ARGS(tsize, vmp, other_free, other_file, adj),
TP_STRUCT__entry(
__field(int, tsize)
__field(int, vmp)
__field(int, other_free)
__field(int, other_file)
__field(short, adj)
),
TP_fast_assign(
__entry->tsize = tsize;
__entry->vmp = vmp;
__entry->other_free = other_free;
__entry->other_file = other_file;
__entry->adj = adj;
),
TP_printk("%d, %d, %d, %d, %d",
__entry->tsize,
__entry->vmp,
__entry->other_free,
__entry->other_file,
__entry->adj)
);
#endif
#include <trace/define_trace.h>