diff options
-rw-r--r-- | include/linux/shrinker.h | 6 | ||||
-rw-r--r-- | mm/vmscan.c | 88 |
2 files changed, 77 insertions, 17 deletions
diff --git a/include/linux/shrinker.h b/include/linux/shrinker.h index 7bde8e1c228a..c9d24d39462e 100644 --- a/include/linux/shrinker.h +++ b/include/linux/shrinker.h @@ -5,6 +5,8 @@ #include <linux/atomic.h> #include <linux/types.h> +struct seq_buf; + /* * This struct is used to pass information from page reclaim to the shrinkers. * We consolidate the values for easier extension later. @@ -61,10 +63,12 @@ struct shrink_control { * @flags determine the shrinker abilities, like numa awareness */ struct shrinker { + const char *name; unsigned long (*count_objects)(struct shrinker *, struct shrink_control *sc); unsigned long (*scan_objects)(struct shrinker *, struct shrink_control *sc); + void (*to_text)(struct seq_buf *, struct shrinker *); long batch; /* reclaim batch size, 0 = default */ int seeks; /* seeks to recreate an obj */ @@ -78,7 +82,6 @@ struct shrinker { #endif #ifdef CONFIG_SHRINKER_DEBUG int debugfs_id; - const char *name; struct dentry *debugfs_entry; #endif /* objs pending delete, per node */ @@ -104,6 +107,7 @@ extern int __printf(2, 3) 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 seq_buf *); #ifdef CONFIG_SHRINKER_DEBUG extern int shrinker_debugfs_add(struct shrinker *shrinker); diff --git a/mm/vmscan.c b/mm/vmscan.c index 9c1c5e8b24b8..ce93194e73dd 100644 --- a/mm/vmscan.c +++ b/mm/vmscan.c @@ -57,6 +57,7 @@ #include <linux/khugepaged.h> #include <linux/rculist_nulls.h> #include <linux/random.h> +#include <linux/seq_buf.h> #include <asm/tlbflush.h> #include <asm/div64.h> @@ -652,7 +653,6 @@ static int __prealloc_shrinker(struct shrinker *shrinker) return 0; } -#ifdef CONFIG_SHRINKER_DEBUG int prealloc_shrinker(struct shrinker *shrinker, const char *fmt, ...) { va_list ap; @@ -672,19 +672,12 @@ int prealloc_shrinker(struct shrinker *shrinker, const char *fmt, ...) return err; } -#else -int prealloc_shrinker(struct shrinker *shrinker, const char *fmt, ...) -{ - return __prealloc_shrinker(shrinker); -} -#endif void free_prealloced_shrinker(struct shrinker *shrinker) { -#ifdef CONFIG_SHRINKER_DEBUG kfree_const(shrinker->name); shrinker->name = NULL; -#endif + if (shrinker->flags & SHRINKER_MEMCG_AWARE) { down_write(&shrinker_rwsem); unregister_memcg_shrinker(shrinker); @@ -715,7 +708,6 @@ static int __register_shrinker(struct shrinker *shrinker) return 0; } -#ifdef CONFIG_SHRINKER_DEBUG int register_shrinker(struct shrinker *shrinker, const char *fmt, ...) { va_list ap; @@ -734,12 +726,6 @@ int register_shrinker(struct shrinker *shrinker, const char *fmt, ...) } return err; } -#else -int register_shrinker(struct shrinker *shrinker, const char *fmt, ...) -{ - return __register_shrinker(shrinker); -} -#endif EXPORT_SYMBOL(register_shrinker); /* @@ -782,6 +768,76 @@ void synchronize_shrinkers(void) } EXPORT_SYMBOL(synchronize_shrinkers); +void shrinker_to_text(struct seq_buf *out, struct shrinker *shrinker) +{ + struct shrink_control sc = { .gfp_mask = GFP_KERNEL, }; + + seq_buf_puts(out, shrinker->name); + seq_buf_printf(out, " objects: %lu\n", shrinker->count_objects(shrinker, &sc)); + + if (shrinker->to_text) { + shrinker->to_text(out, shrinker); + seq_buf_puts(out, "\n"); + } +} + +/** + * 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 seq_buf *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)) { + seq_buf_puts(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, |