Commit df3e764d authored by Luis Chamberlain's avatar Luis Chamberlain

module: add debug stats to help identify memory pressure

Loading modules with finit_module() can end up using vmalloc(), vmap()
and vmalloc() again, for a total of up to 3 separate allocations in the
worst case for a single module. We always kernel_read*() the module,
that's a vmalloc(). Then vmap() is used for the module decompression,
and if so the last read buffer is freed as we use the now decompressed
module buffer to stuff data into our copy module. The last allocation is
specific to each architectures but pretty much that's generally a series
of vmalloc() calls or a variation of vmalloc to handle ELF sections with
special permissions.

Evaluation with new stress-ng module support [1] with just 100 ops
is proving that you can end up using GiBs of data easily even with all
care we have in the kernel and userspace today in trying to not load modules
which are already loaded. 100 ops seems to resemble the sort of pressure a
system with about 400 CPUs can create on module loading. Although issues
relating to duplicate module requests due to each CPU inucurring a new
module reuest is silly and some of these are being fixed, we currently lack
proper tooling to help diagnose easily what happened, when it happened
and who likely is to blame -- userspace or kernel module autoloading.

Provide an initial set of stats which use debugfs to let us easily scrape
post-boot information about failed loads. This sort of information can
be used on production worklaods to try to optimize *avoiding* redundant
memory pressure using finit_module().

There's a few examples that can be provided:

A 255 vCPU system without the next patch in this series applied:

Startup finished in 19.143s (kernel) + 7.078s (userspace) = 26.221s
graphical.target reached after 6.988s in userspace

And 13.58 GiB of virtual memory space lost due to failed module loading:

root@big ~ # cat /sys/kernel/debug/modules/stats
         Mods ever loaded       67
     Mods failed on kread       0
Mods failed on decompress       0
  Mods failed on becoming       0
      Mods failed on load       1411
        Total module size       11464704
      Total mod text size       4194304
       Failed kread bytes       0
  Failed decompress bytes       0
    Failed becoming bytes       0
        Failed kmod bytes       14588526272
 Virtual mem wasted bytes       14588526272
         Average mod size       171115
    Average mod text size       62602
  Average fail load bytes       10339140
Duplicate failed modules:
              module-name        How-many-times                    Reason
                kvm_intel                   249                      Load
                      kvm                   249                      Load
                irqbypass                     8                      Load
         crct10dif_pclmul                   128                      Load
      ghash_clmulni_intel                    27                      Load
             sha512_ssse3                    50                      Load
           sha512_generic                   200                      Load
              aesni_intel                   249                      Load
              crypto_simd                    41                      Load
                   cryptd                   131                      Load
                    evdev                     2                      Load
                serio_raw                     1                      Load
               virtio_pci                     3                      Load
                     nvme                     3                      Load
                nvme_core                     3                      Load
    virtio_pci_legacy_dev                     3                      Load
    virtio_pci_modern_dev                     3                      Load
                   t10_pi                     3                      Load
                   virtio                     3                      Load
             crc32_pclmul                     6                      Load
           crc64_rocksoft                     3                      Load
             crc32c_intel                    40                      Load
              virtio_ring                     3                      Load
                    crc64                     3                      Load

The following screen shot, of a simple 8vcpu 8 GiB KVM guest with the
next patch in this series applied, shows 226.53 MiB are wasted in virtual
memory allocations which due to duplicate module requests during boot.
It also shows an average module memory size of 167.10 KiB and an an
average module .text + .init.text size of 61.13 KiB. The end shows all
modules which were detected as duplicate requests and whether or not
they failed early after just the first kernel_read*() call or late after
we've already allocated the private space for the module in
layout_and_allocate(). A system with module decompression would reveal
more wasted virtual memory space.

We should put effort now into identifying the source of these duplicate
module requests and trimming these down as much possible. Larger systems
will obviously show much more wasted virtual memory allocations.

root@kmod ~ # cat /sys/kernel/debug/modules/stats
         Mods ever loaded       67
     Mods failed on kread       0
Mods failed on decompress       0
  Mods failed on becoming       83
      Mods failed on load       16
        Total module size       11464704
      Total mod text size       4194304
       Failed kread bytes       0
  Failed decompress bytes       0
    Failed becoming bytes       228959096
        Failed kmod bytes       8578080
 Virtual mem wasted bytes       237537176
         Average mod size       171115
    Average mod text size       62602
  Avg fail becoming bytes       2758544
  Average fail load bytes       536130
Duplicate failed modules:
              module-name        How-many-times                    Reason
                kvm_intel                     7                  Becoming
                      kvm                     7                  Becoming
                irqbypass                     6           Becoming & Load
         crct10dif_pclmul                     7           Becoming & Load
      ghash_clmulni_intel                     7           Becoming & Load
             sha512_ssse3                     6           Becoming & Load
           sha512_generic                     7           Becoming & Load
              aesni_intel                     7                  Becoming
              crypto_simd                     7           Becoming & Load
                   cryptd                     3           Becoming & Load
                    evdev                     1                  Becoming
                serio_raw                     1                  Becoming
                     nvme                     3                  Becoming
                nvme_core                     3                  Becoming
                   t10_pi                     3                  Becoming
               virtio_pci                     3                  Becoming
             crc32_pclmul                     6           Becoming & Load
           crc64_rocksoft                     3                  Becoming
             crc32c_intel                     3                  Becoming
    virtio_pci_modern_dev                     2                  Becoming
    virtio_pci_legacy_dev                     1                  Becoming
                    crc64                     2                  Becoming
                   virtio                     2                  Becoming
              virtio_ring                     2                  Becoming

[0] https://github.com/ColinIanKing/stress-ng.git
[1] echo 0 > /proc/sys/vm/oom_dump_tasks
    ./stress-ng --module 100 --module-name xfs
Signed-off-by: default avatarLuis Chamberlain <mcgrof@kernel.org>
parent f71afa6a
...@@ -220,12 +220,30 @@ relay interface ...@@ -220,12 +220,30 @@ relay interface
Module Support Module Support
============== ==============
Module Loading Kernel module auto-loading
-------------- --------------------------
.. kernel-doc:: kernel/module/kmod.c .. kernel-doc:: kernel/module/kmod.c
:export: :export:
Module debugging
----------------
.. kernel-doc:: kernel/module/stats.c
:doc: module debugging statistics overview
dup_failed_modules - tracks duplicate failed modules
****************************************************
.. kernel-doc:: kernel/module/stats.c
:doc: dup_failed_modules - tracks duplicate failed modules
module statistics debugfs counters
**********************************
.. kernel-doc:: kernel/module/stats.c
:doc: module statistics debugfs counters
Inter Module support Inter Module support
-------------------- --------------------
......
...@@ -22,6 +22,45 @@ menuconfig MODULES ...@@ -22,6 +22,45 @@ menuconfig MODULES
if MODULES if MODULES
config MODULE_DEBUGFS
bool
config MODULE_DEBUG
bool "Module debugging"
depends on DEBUG_FS
help
Allows you to enable / disable features which can help you debug
modules. You don't need these options on production systems.
if MODULE_DEBUG
config MODULE_STATS
bool "Module statistics"
depends on DEBUG_FS
select MODULE_DEBUGFS
help
This option allows you to maintain a record of module statistics.
For example, size of all modules, average size, text size, a list
of failed modules and the size for each of those. For failed
modules we keep track of modules which failed due to either the
existing module taking too long to load or that module was already
loaded.
You should enable this if you are debugging production loads
and want to see if userspace or the kernel is doing stupid things
with loading modules when it shouldn't or if you want to help
optimize userspace / kernel space module autoloading schemes.
You might want to do this because failed modules tend to use
up significant amount of memory, and so you'd be doing everyone a
favor in avoiding these failures proactively.
This functionality is also useful for those experimenting with
module .text ELF section optimization.
If unsure, say N.
endif # MODULE_DEBUG
config MODULE_FORCE_LOAD config MODULE_FORCE_LOAD
bool "Forced module loading" bool "Forced module loading"
default n default n
...@@ -51,7 +90,7 @@ config MODULE_FORCE_UNLOAD ...@@ -51,7 +90,7 @@ config MODULE_FORCE_UNLOAD
config MODULE_UNLOAD_TAINT_TRACKING config MODULE_UNLOAD_TAINT_TRACKING
bool "Tainted module unload tracking" bool "Tainted module unload tracking"
depends on MODULE_UNLOAD depends on MODULE_UNLOAD
default n select MODULE_DEBUGFS
help help
This option allows you to maintain a record of each unloaded This option allows you to maintain a record of each unloaded
module that tainted the kernel. In addition to displaying a module that tainted the kernel. In addition to displaying a
......
...@@ -21,3 +21,4 @@ obj-$(CONFIG_SYSFS) += sysfs.o ...@@ -21,3 +21,4 @@ obj-$(CONFIG_SYSFS) += sysfs.o
obj-$(CONFIG_KGDB_KDB) += kdb.o obj-$(CONFIG_KGDB_KDB) += kdb.o
obj-$(CONFIG_MODVERSIONS) += version.o obj-$(CONFIG_MODVERSIONS) += version.o
obj-$(CONFIG_MODULE_UNLOAD_TAINT_TRACKING) += tracking.o obj-$(CONFIG_MODULE_UNLOAD_TAINT_TRACKING) += tracking.o
obj-$(CONFIG_MODULE_STATS) += stats.o
...@@ -297,6 +297,10 @@ int module_decompress(struct load_info *info, const void *buf, size_t size) ...@@ -297,6 +297,10 @@ int module_decompress(struct load_info *info, const void *buf, size_t size)
ssize_t data_size; ssize_t data_size;
int error; int error;
#if defined(CONFIG_MODULE_STATS)
info->compressed_len = size;
#endif
/* /*
* Start with number of pages twice as big as needed for * Start with number of pages twice as big as needed for
* compressed data. * compressed data.
......
...@@ -59,6 +59,9 @@ struct load_info { ...@@ -59,6 +59,9 @@ struct load_info {
unsigned long mod_kallsyms_init_off; unsigned long mod_kallsyms_init_off;
#endif #endif
#ifdef CONFIG_MODULE_DECOMPRESS #ifdef CONFIG_MODULE_DECOMPRESS
#ifdef CONFIG_MODULE_STATS
unsigned long compressed_len;
#endif
struct page **pages; struct page **pages;
unsigned int max_pages; unsigned int max_pages;
unsigned int used_pages; unsigned int used_pages;
...@@ -143,6 +146,81 @@ static inline bool set_livepatch_module(struct module *mod) ...@@ -143,6 +146,81 @@ static inline bool set_livepatch_module(struct module *mod)
#endif #endif
} }
/**
* enum fail_dup_mod_reason - state at which a duplicate module was detected
*
* @FAIL_DUP_MOD_BECOMING: the module is read properly, passes all checks but
* we've determined that another module with the same name is already loaded
* or being processed on our &modules list. This happens on early_mod_check()
* right before layout_and_allocate(). The kernel would have already
* vmalloc()'d space for the entire module through finit_module(). If
* decompression was used two vmap() spaces were used. These failures can
* happen when userspace has not seen the module present on the kernel and
* tries to load the module multiple times at same time.
* @FAIL_DUP_MOD_LOAD: the module has been read properly, passes all validation
* checks and the kernel determines that the module was unique and because
* of this allocated yet another private kernel copy of the module space in
* layout_and_allocate() but after this determined in add_unformed_module()
* that another module with the same name is already loaded or being processed.
* These failures should be mitigated as much as possible and are indicative
* of really fast races in loading modules. Without module decompression
* they waste twice as much vmap space. With module decompression three
* times the module's size vmap space is wasted.
*/
enum fail_dup_mod_reason {
FAIL_DUP_MOD_BECOMING = 0,
FAIL_DUP_MOD_LOAD,
};
#ifdef CONFIG_MODULE_DEBUGFS
extern struct dentry *mod_debugfs_root;
#endif
#ifdef CONFIG_MODULE_STATS
#define mod_stat_add_long(count, var) atomic_long_add(count, var)
#define mod_stat_inc(name) atomic_inc(name)
extern atomic_long_t total_mod_size;
extern atomic_long_t total_text_size;
extern atomic_long_t invalid_kread_bytes;
extern atomic_long_t invalid_decompress_bytes;
extern atomic_t modcount;
extern atomic_t failed_kreads;
extern atomic_t failed_decompress;
struct mod_fail_load {
struct list_head list;
char name[MODULE_NAME_LEN];
atomic_long_t count;
unsigned long dup_fail_mask;
};
int try_add_failed_module(const char *name, enum fail_dup_mod_reason reason);
void mod_stat_bump_invalid(struct load_info *info, int flags);
void mod_stat_bump_becoming(struct load_info *info, int flags);
#else
#define mod_stat_add_long(name, var)
#define mod_stat_inc(name)
static inline int try_add_failed_module(const char *name,
enum fail_dup_mod_reason reason)
{
return 0;
}
static inline void mod_stat_bump_invalid(struct load_info *info, int flags)
{
}
static inline void mod_stat_bump_becoming(struct load_info *info, int flags)
{
}
#endif /* CONFIG_MODULE_STATS */
#ifdef CONFIG_MODULE_UNLOAD_TAINT_TRACKING #ifdef CONFIG_MODULE_UNLOAD_TAINT_TRACKING
struct mod_unload_taint { struct mod_unload_taint {
struct list_head list; struct list_head list;
......
...@@ -56,6 +56,7 @@ ...@@ -56,6 +56,7 @@
#include <linux/dynamic_debug.h> #include <linux/dynamic_debug.h>
#include <linux/audit.h> #include <linux/audit.h>
#include <linux/cfi.h> #include <linux/cfi.h>
#include <linux/debugfs.h>
#include <uapi/linux/module.h> #include <uapi/linux/module.h>
#include "internal.h" #include "internal.h"
...@@ -2500,6 +2501,18 @@ static noinline int do_init_module(struct module *mod) ...@@ -2500,6 +2501,18 @@ static noinline int do_init_module(struct module *mod)
{ {
int ret = 0; int ret = 0;
struct mod_initfree *freeinit; struct mod_initfree *freeinit;
#if defined(CONFIG_MODULE_STATS)
unsigned int text_size = 0, total_size = 0;
for_each_mod_mem_type(type) {
const struct module_memory *mod_mem = &mod->mem[type];
if (mod_mem->size) {
total_size += mod_mem->size;
if (type == MOD_TEXT || type == MOD_INIT_TEXT)
text_size += mod_mem->size;
}
}
#endif
freeinit = kmalloc(sizeof(*freeinit), GFP_KERNEL); freeinit = kmalloc(sizeof(*freeinit), GFP_KERNEL);
if (!freeinit) { if (!freeinit) {
...@@ -2561,6 +2574,7 @@ static noinline int do_init_module(struct module *mod) ...@@ -2561,6 +2574,7 @@ static noinline int do_init_module(struct module *mod)
mod->mem[type].base = NULL; mod->mem[type].base = NULL;
mod->mem[type].size = 0; mod->mem[type].size = 0;
} }
#ifdef CONFIG_DEBUG_INFO_BTF_MODULES #ifdef CONFIG_DEBUG_INFO_BTF_MODULES
/* .BTF is not SHF_ALLOC and will get removed, so sanitize pointer */ /* .BTF is not SHF_ALLOC and will get removed, so sanitize pointer */
mod->btf_data = NULL; mod->btf_data = NULL;
...@@ -2584,6 +2598,11 @@ static noinline int do_init_module(struct module *mod) ...@@ -2584,6 +2598,11 @@ static noinline int do_init_module(struct module *mod)
mutex_unlock(&module_mutex); mutex_unlock(&module_mutex);
wake_up_all(&module_wq); wake_up_all(&module_wq);
mod_stat_add_long(text_size, &total_text_size);
mod_stat_add_long(total_size, &total_mod_size);
mod_stat_inc(&modcount);
return 0; return 0;
fail_free_freeinit: fail_free_freeinit:
...@@ -2599,6 +2618,7 @@ static noinline int do_init_module(struct module *mod) ...@@ -2599,6 +2618,7 @@ static noinline int do_init_module(struct module *mod)
ftrace_release_mod(mod); ftrace_release_mod(mod);
free_module(mod); free_module(mod);
wake_up_all(&module_wq); wake_up_all(&module_wq);
return ret; return ret;
} }
...@@ -2632,7 +2652,8 @@ static bool finished_loading(const char *name) ...@@ -2632,7 +2652,8 @@ static bool finished_loading(const char *name)
} }
/* Must be called with module_mutex held */ /* Must be called with module_mutex held */
static int module_patient_check_exists(const char *name) static int module_patient_check_exists(const char *name,
enum fail_dup_mod_reason reason)
{ {
struct module *old; struct module *old;
int err = 0; int err = 0;
...@@ -2655,6 +2676,9 @@ static int module_patient_check_exists(const char *name) ...@@ -2655,6 +2676,9 @@ static int module_patient_check_exists(const char *name)
old = find_module_all(name, strlen(name), true); old = find_module_all(name, strlen(name), true);
} }
if (try_add_failed_module(name, reason))
pr_warn("Could not add fail-tracking for module: %s\n", name);
/* /*
* We are here only when the same module was being loaded. Do * We are here only when the same module was being loaded. Do
* not try to load it again right now. It prevents long delays * not try to load it again right now. It prevents long delays
...@@ -2679,7 +2703,7 @@ static int add_unformed_module(struct module *mod) ...@@ -2679,7 +2703,7 @@ static int add_unformed_module(struct module *mod)
mod->state = MODULE_STATE_UNFORMED; mod->state = MODULE_STATE_UNFORMED;
mutex_lock(&module_mutex); mutex_lock(&module_mutex);
err = module_patient_check_exists(mod->name); err = module_patient_check_exists(mod->name, FAIL_DUP_MOD_LOAD);
if (err) if (err)
goto out; goto out;
...@@ -2800,6 +2824,7 @@ static int load_module(struct load_info *info, const char __user *uargs, ...@@ -2800,6 +2824,7 @@ static int load_module(struct load_info *info, const char __user *uargs,
int flags) int flags)
{ {
struct module *mod; struct module *mod;
bool module_allocated = false;
long err = 0; long err = 0;
char *after_dashes; char *after_dashes;
...@@ -2839,6 +2864,8 @@ static int load_module(struct load_info *info, const char __user *uargs, ...@@ -2839,6 +2864,8 @@ static int load_module(struct load_info *info, const char __user *uargs,
goto free_copy; goto free_copy;
} }
module_allocated = true;
audit_log_kern_module(mod->name); audit_log_kern_module(mod->name);
/* Reserve our place in the list. */ /* Reserve our place in the list. */
...@@ -2983,6 +3010,7 @@ static int load_module(struct load_info *info, const char __user *uargs, ...@@ -2983,6 +3010,7 @@ static int load_module(struct load_info *info, const char __user *uargs,
synchronize_rcu(); synchronize_rcu();
mutex_unlock(&module_mutex); mutex_unlock(&module_mutex);
free_module: free_module:
mod_stat_bump_invalid(info, flags);
/* Free lock-classes; relies on the preceding sync_rcu() */ /* Free lock-classes; relies on the preceding sync_rcu() */
for_class_mod_mem_type(type, core_data) { for_class_mod_mem_type(type, core_data) {
lockdep_free_key_range(mod->mem[type].base, lockdep_free_key_range(mod->mem[type].base,
...@@ -2991,6 +3019,13 @@ static int load_module(struct load_info *info, const char __user *uargs, ...@@ -2991,6 +3019,13 @@ static int load_module(struct load_info *info, const char __user *uargs,
module_deallocate(mod, info); module_deallocate(mod, info);
free_copy: free_copy:
/*
* The info->len is always set. We distinguish between
* failures once the proper module was allocated and
* before that.
*/
if (!module_allocated)
mod_stat_bump_becoming(info, flags);
free_copy(info, flags); free_copy(info, flags);
return err; return err;
} }
...@@ -3009,8 +3044,11 @@ SYSCALL_DEFINE3(init_module, void __user *, umod, ...@@ -3009,8 +3044,11 @@ SYSCALL_DEFINE3(init_module, void __user *, umod,
umod, len, uargs); umod, len, uargs);
err = copy_module_from_user(umod, len, &info); err = copy_module_from_user(umod, len, &info);
if (err) if (err) {
mod_stat_inc(&failed_kreads);
mod_stat_add_long(len, &invalid_kread_bytes);
return err; return err;
}
return load_module(&info, uargs, 0); return load_module(&info, uargs, 0);
} }
...@@ -3035,14 +3073,20 @@ SYSCALL_DEFINE3(finit_module, int, fd, const char __user *, uargs, int, flags) ...@@ -3035,14 +3073,20 @@ SYSCALL_DEFINE3(finit_module, int, fd, const char __user *, uargs, int, flags)
len = kernel_read_file_from_fd(fd, 0, &buf, INT_MAX, NULL, len = kernel_read_file_from_fd(fd, 0, &buf, INT_MAX, NULL,
READING_MODULE); READING_MODULE);
if (len < 0) if (len < 0) {
mod_stat_inc(&failed_kreads);
mod_stat_add_long(len, &invalid_kread_bytes);
return len; return len;
}
if (flags & MODULE_INIT_COMPRESSED_FILE) { if (flags & MODULE_INIT_COMPRESSED_FILE) {
err = module_decompress(&info, buf, len); err = module_decompress(&info, buf, len);
vfree(buf); /* compressed data is no longer needed */ vfree(buf); /* compressed data is no longer needed */
if (err) if (err) {
mod_stat_inc(&failed_decompress);
mod_stat_add_long(len, &invalid_decompress_bytes);
return err; return err;
}
} else { } else {
info.hdr = buf; info.hdr = buf;
info.len = len; info.len = len;
...@@ -3216,3 +3260,14 @@ void print_modules(void) ...@@ -3216,3 +3260,14 @@ void print_modules(void)
last_unloaded_module.taints); last_unloaded_module.taints);
pr_cont("\n"); pr_cont("\n");
} }
#ifdef CONFIG_MODULE_DEBUGFS
struct dentry *mod_debugfs_root;
static int module_debugfs_init(void)
{
mod_debugfs_root = debugfs_create_dir("modules", NULL);
return 0;
}
module_init(module_debugfs_init);
#endif
This diff is collapsed.
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include "internal.h" #include "internal.h"
static LIST_HEAD(unloaded_tainted_modules); static LIST_HEAD(unloaded_tainted_modules);
extern struct dentry *mod_debugfs_root;
int try_add_tainted_module(struct module *mod) int try_add_tainted_module(struct module *mod)
{ {
...@@ -120,12 +121,8 @@ static const struct file_operations unloaded_tainted_modules_fops = { ...@@ -120,12 +121,8 @@ static const struct file_operations unloaded_tainted_modules_fops = {
static int __init unloaded_tainted_modules_init(void) static int __init unloaded_tainted_modules_init(void)
{ {
struct dentry *dir; debugfs_create_file("unloaded_tainted", 0444, mod_debugfs_root, NULL,
dir = debugfs_create_dir("modules", NULL);
debugfs_create_file("unloaded_tainted", 0444, dir, NULL,
&unloaded_tainted_modules_fops); &unloaded_tainted_modules_fops);
return 0; return 0;
} }
module_init(unloaded_tainted_modules_init); module_init(unloaded_tainted_modules_init);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment