| // SPDX-License-Identifier: GPL-2.0+ |
| |
| #include <linux/memcg_memfs_info.h> |
| #include <linux/fs.h> |
| #include <linux/sysfs.h> |
| #include <linux/kobject.h> |
| #include <linux/slab.h> |
| #include "../fs/mount.h" |
| |
| #define SEQ_printf(m, x...) \ |
| do { \ |
| if (m) \ |
| seq_printf(m, x); \ |
| else \ |
| pr_info(x); \ |
| } while (0) |
| |
| struct print_files_control { |
| struct mem_cgroup *memcg; |
| struct seq_file *m; |
| unsigned long size_threshold; |
| unsigned long max_print_files; |
| |
| char *pathbuf; |
| unsigned long pathbuf_size; |
| |
| const char *fs_type_name; |
| struct vfsmount *vfsmnt; |
| unsigned long total_print_files; |
| unsigned long total_files_size; |
| }; |
| |
| static bool memfs_enable; |
| static unsigned long memfs_size_threshold; |
| static unsigned long memfs_max_print_files = 500; |
| |
| static const char *const fs_type_names[] = { |
| "rootfs", |
| "tmpfs", |
| }; |
| |
| static struct vfsmount *memfs_get_vfsmount(struct super_block *sb) |
| { |
| struct mount *mnt; |
| struct vfsmount *vfsmnt; |
| |
| lock_mount_hash(); |
| list_for_each_entry(mnt, &sb->s_mounts, mnt_instance) { |
| /* |
| * There may be multiple mount points for a super_block, |
| * just need to print one of these mount points to determine |
| * the file path. |
| */ |
| vfsmnt = mntget(&mnt->mnt); |
| unlock_mount_hash(); |
| return vfsmnt; |
| } |
| unlock_mount_hash(); |
| |
| return NULL; |
| } |
| |
| static unsigned long memfs_count_in_mem_cgroup(struct mem_cgroup *memcg, |
| struct address_space *mapping) |
| { |
| XA_STATE(xas, &mapping->i_pages, 0); |
| unsigned long size = 0; |
| struct page *page, *head; |
| |
| rcu_read_lock(); |
| xas_for_each(&xas, page, ULONG_MAX) { |
| if (xas_retry(&xas, page)) |
| continue; |
| |
| if (xa_is_value(page)) |
| continue; |
| |
| head = compound_head(page); |
| if ((unsigned long)memcg == head->memcg_data) |
| size += PAGE_SIZE; |
| } |
| rcu_read_unlock(); |
| return size; |
| } |
| |
| static void memfs_show_file_in_mem_cgroup(void *data, struct inode *inode) |
| { |
| struct print_files_control *pfc = data; |
| struct dentry *dentry; |
| unsigned long size; |
| struct path path; |
| char *filepath; |
| |
| size = memfs_count_in_mem_cgroup(pfc->memcg, inode->i_mapping); |
| if (!size || size < pfc->size_threshold) |
| return; |
| |
| dentry = d_find_alias(inode); |
| if (!dentry) |
| return; |
| path.mnt = pfc->vfsmnt; |
| path.dentry = dentry; |
| filepath = d_absolute_path(&path, pfc->pathbuf, pfc->pathbuf_size); |
| if (!filepath || IS_ERR(filepath)) |
| filepath = "(too long)"; |
| pfc->total_print_files++; |
| pfc->total_files_size += size; |
| dput(dentry); |
| |
| /* |
| * To prevent excessive logs, limit the amount of data |
| * that can be output to logs. |
| */ |
| if (!pfc->m && pfc->total_print_files > pfc->max_print_files) |
| return; |
| |
| SEQ_printf(pfc->m, "%lukB %llukB %s\n", |
| size >> 10, inode->i_size >> 10, filepath); |
| } |
| |
| static void memfs_show_files_in_mem_cgroup(struct super_block *sb, void *data) |
| { |
| struct print_files_control *pfc = data; |
| struct inode *inode, *toput_inode = NULL; |
| |
| if (strncmp(sb->s_type->name, |
| pfc->fs_type_name, strlen(pfc->fs_type_name))) |
| return; |
| |
| pfc->vfsmnt = memfs_get_vfsmount(sb); |
| if (!pfc->vfsmnt) |
| return; |
| |
| spin_lock(&sb->s_inode_list_lock); |
| list_for_each_entry(inode, &sb->s_inodes, i_sb_list) { |
| spin_lock(&inode->i_lock); |
| |
| if ((inode->i_state & (I_FREEING|I_WILL_FREE|I_NEW)) || |
| (inode->i_mapping->nrpages == 0 && !need_resched())) { |
| spin_unlock(&inode->i_lock); |
| continue; |
| } |
| __iget(inode); |
| spin_unlock(&inode->i_lock); |
| spin_unlock(&sb->s_inode_list_lock); |
| |
| memfs_show_file_in_mem_cgroup(pfc, inode); |
| |
| iput(toput_inode); |
| toput_inode = inode; |
| |
| cond_resched(); |
| spin_lock(&sb->s_inode_list_lock); |
| } |
| spin_unlock(&sb->s_inode_list_lock); |
| iput(toput_inode); |
| mntput(pfc->vfsmnt); |
| } |
| |
| void mem_cgroup_print_memfs_info(struct mem_cgroup *memcg, char *pathbuf, |
| struct seq_file *m) |
| { |
| struct print_files_control pfc = { |
| .memcg = memcg, |
| .m = m, |
| .max_print_files = memfs_max_print_files, |
| .size_threshold = memfs_size_threshold, |
| }; |
| int i; |
| |
| if (!memfs_enable || !memcg) |
| return; |
| |
| pfc.pathbuf = pathbuf; |
| pfc.pathbuf_size = PATH_MAX; |
| |
| for (i = 0; i < ARRAY_SIZE(fs_type_names); i++) { |
| pfc.fs_type_name = fs_type_names[i]; |
| pfc.total_print_files = 0; |
| pfc.total_files_size = 0; |
| |
| SEQ_printf(m, "Show %s files (memory-size > %lukB):\n", |
| pfc.fs_type_name, pfc.size_threshold >> 10); |
| SEQ_printf(m, "<memory-size> <file-size> <path>\n"); |
| iterate_supers(memfs_show_files_in_mem_cgroup, &pfc); |
| |
| SEQ_printf(m, "total files: %lu, total memory-size: %lukB\n", |
| pfc.total_print_files, pfc.total_files_size >> 10); |
| } |
| } |
| |
| int mem_cgroup_memfs_files_show(struct seq_file *m, void *v) |
| { |
| struct mem_cgroup *memcg = mem_cgroup_from_css(seq_css(m)); |
| char *pathbuf; |
| |
| pathbuf = kmalloc(PATH_MAX, GFP_KERNEL); |
| if (!pathbuf) { |
| SEQ_printf(m, "Show memfs abort: failed to allocate memory\n"); |
| return 0; |
| } |
| mem_cgroup_print_memfs_info(memcg, pathbuf, m); |
| kfree(pathbuf); |
| return 0; |
| } |
| |
| static ssize_t memfs_size_threshold_show(struct kobject *kobj, |
| struct kobj_attribute *attr, |
| char *buf) |
| { |
| return sprintf(buf, "%lu\n", memfs_size_threshold); |
| } |
| |
| static ssize_t memfs_size_threshold_store(struct kobject *kobj, |
| struct kobj_attribute *attr, |
| const char *buf, size_t len) |
| { |
| unsigned long count; |
| int err; |
| |
| err = kstrtoul(buf, 10, &count); |
| if (err) |
| return err; |
| memfs_size_threshold = count; |
| return len; |
| } |
| |
| static struct kobj_attribute memfs_size_threshold_attr = { |
| .attr = {"size_threshold", 0644}, |
| .show = &memfs_size_threshold_show, |
| .store = &memfs_size_threshold_store, |
| }; |
| |
| static ssize_t memfs_max_print_files_show(struct kobject *kobj, |
| struct kobj_attribute *attr, |
| char *buf) |
| { |
| return sprintf(buf, "%lu\n", memfs_max_print_files); |
| } |
| |
| static ssize_t memfs_max_print_files_store(struct kobject *kobj, |
| struct kobj_attribute *attr, |
| const char *buf, size_t len) |
| { |
| unsigned long count; |
| int err; |
| |
| err = kstrtoul(buf, 10, &count); |
| if (err) |
| return err; |
| memfs_max_print_files = count; |
| return len; |
| } |
| |
| static struct kobj_attribute memfs_max_print_files_attr = { |
| .attr = {"max_print_files_in_oom", 0644}, |
| .show = &memfs_max_print_files_show, |
| .store = &memfs_max_print_files_store, |
| }; |
| |
| static ssize_t memfs_enable_show(struct kobject *kobj, |
| struct kobj_attribute *attr, char *buf) |
| { |
| return sprintf(buf, "%u\n", memfs_enable); |
| } |
| |
| static ssize_t memfs_enable_store(struct kobject *kobj, |
| struct kobj_attribute *attr, |
| const char *buf, size_t len) |
| { |
| bool enable; |
| int err; |
| |
| err = kstrtobool(buf, &enable); |
| if (err) |
| return err; |
| |
| memfs_enable = enable; |
| return len; |
| } |
| |
| static struct kobj_attribute memfs_enable_attr = { |
| .attr = {"enable", 0644}, |
| .show = &memfs_enable_show, |
| .store = &memfs_enable_store, |
| }; |
| |
| static struct attribute *memfs_attr[] = { |
| &memfs_size_threshold_attr.attr, |
| &memfs_max_print_files_attr.attr, |
| &memfs_enable_attr.attr, |
| NULL, |
| }; |
| |
| static struct attribute_group memfs_attr_group = { |
| .attrs = memfs_attr, |
| }; |
| |
| void mem_cgroup_memfs_info_init(void) |
| { |
| struct kobject *memcg_memfs_kobj; |
| |
| if (mem_cgroup_disabled()) |
| return; |
| |
| memcg_memfs_kobj = kobject_create_and_add("memcg_memfs_info", mm_kobj); |
| if (unlikely(!memcg_memfs_kobj)) { |
| pr_err("failed to create memcg_memfs kobject\n"); |
| return; |
| } |
| |
| if (sysfs_create_group(memcg_memfs_kobj, &memfs_attr_group)) { |
| pr_err("failed to register memcg_memfs group\n"); |
| kobject_put(memcg_memfs_kobj); |
| } |
| } |