diff options
-rw-r--r-- | include/linux/shrinker.h | 5 | ||||
-rw-r--r-- | mm/vmscan.c | 78 |
2 files changed, 83 insertions, 0 deletions
diff --git a/include/linux/shrinker.h b/include/linux/shrinker.h index 76fbf92b04d9..b5f411768b70 100644 --- a/include/linux/shrinker.h +++ b/include/linux/shrinker.h @@ -2,6 +2,8 @@ #ifndef _LINUX_SHRINKER_H #define _LINUX_SHRINKER_H +struct printbuf; + /* * This struct is used to pass information from page reclaim to the shrinkers. * We consolidate the values for easier extension later. @@ -58,10 +60,12 @@ struct shrink_control { * @flags determine the shrinker abilities, like numa awareness */ struct shrinker { + char name[32]; unsigned long (*count_objects)(struct shrinker *, struct shrink_control *sc); unsigned long (*scan_objects)(struct shrinker *, struct shrink_control *sc); + void (*to_text)(struct printbuf *, struct shrinker *); long batch; /* reclaim batch size, 0 = default */ int seeks; /* seeks to recreate an obj */ @@ -94,4 +98,5 @@ extern int register_shrinker(struct shrinker *shrinker); extern void unregister_shrinker(struct shrinker *shrinker); extern void free_prealloced_shrinker(struct shrinker *shrinker); extern void synchronize_shrinkers(void); +void shrinkers_to_text(struct printbuf *); #endif diff --git a/mm/vmscan.c b/mm/vmscan.c index 1678802e03e7..565e498d68f8 100644 --- a/mm/vmscan.c +++ b/mm/vmscan.c @@ -50,6 +50,7 @@ #include <linux/printk.h> #include <linux/dax.h> #include <linux/psi.h> +#include <linux/printbuf.h> #include <asm/tlbflush.h> #include <asm/div64.h> @@ -703,6 +704,83 @@ void synchronize_shrinkers(void) } EXPORT_SYMBOL(synchronize_shrinkers); +void shrinker_to_text(struct printbuf *out, struct shrinker *shrinker) +{ + struct shrink_control sc = { .gfp_mask = GFP_KERNEL, }; + + if (shrinker->name[0]) + prt_printf(out, "%s", shrinker->name); + else + prt_printf(out, "%ps:", shrinker->scan_objects); + + prt_printf(out, " objects: %lu", shrinker->count_objects(shrinker, &sc)); + prt_newline(out); + + if (shrinker->to_text) { + printbuf_indent_add(out, 2); + shrinker->to_text(out, shrinker); + printbuf_indent_sub(out, 2); + prt_newline(out); + } +} + +/** + * shrinkers_to_text - Report on shrinkers with highest usage + * + * This reports on the top 10 shrinkers, by object counts, in sorted order: + * intended to be used for OOM reporting. + */ +void shrinkers_to_text(struct printbuf *out) +{ + struct shrinker *shrinker; + struct shrinker_by_mem { + struct shrinker *shrinker; + unsigned long mem; + } shrinkers_by_mem[10]; + int i, nr = 0; + + if (!down_read_trylock(&shrinker_rwsem)) { + prt_printf(out, "(couldn't take shrinker lock)"); + return; + } + + list_for_each_entry(shrinker, &shrinker_list, list) { + struct shrink_control sc = { .gfp_mask = GFP_KERNEL, }; + unsigned long mem = shrinker->count_objects(shrinker, &sc); + + if (!mem || mem == SHRINK_STOP || mem == SHRINK_EMPTY) + continue; + + for (i = 0; i < nr; i++) + if (mem < shrinkers_by_mem[i].mem) + break; + + if (nr < ARRAY_SIZE(shrinkers_by_mem)) { + memmove(&shrinkers_by_mem[i + 1], + &shrinkers_by_mem[i], + sizeof(shrinkers_by_mem[0]) * (nr - i)); + nr++; + } else if (i) { + i--; + memmove(&shrinkers_by_mem[0], + &shrinkers_by_mem[1], + sizeof(shrinkers_by_mem[0]) * i); + } else { + continue; + } + + shrinkers_by_mem[i] = (struct shrinker_by_mem) { + .shrinker = shrinker, + .mem = mem, + }; + } + + for (i = nr - 1; i >= 0; --i) + shrinker_to_text(out, shrinkers_by_mem[i].shrinker); + + up_read(&shrinker_rwsem); +} + #define SHRINK_BATCH 128 static unsigned long do_shrink_slab(struct shrink_control *shrinkctl, |