| 1 | |
| 2 | #include <linux/idr.h>
|
| 3 | #include <linux/slab.h>
|
| 4 | #include <linux/debugfs.h>
|
| 5 | #include <linux/seq_file.h>
|
| 6 | #include <linux/shrinker.h>
|
| 7 | #include <linux/memcontrol.h>
|
| 8 |
|
| 9 | #include "internal.h"
|
| 10 |
|
| 11 |
|
| 12 | extern struct mutex shrinker_mutex;
|
| 13 | extern struct list_head shrinker_list;
|
| 14 |
|
| 15 | static DEFINE_IDA(shrinker_debugfs_ida);
|
| 16 | static struct dentry *shrinker_debugfs_root;
|
| 17 |
|
| 18 | static unsigned long shrinker_count_objects(struct shrinker *shrinker,
|
| 19 | struct mem_cgroup *memcg,
|
| 20 | unsigned long *count_per_node)
|
| 21 | {
|
| 22 | unsigned long nr, total = 0;
|
| 23 | int nid;
|
| 24 |
|
| 25 | for_each_node(nid) {
|
| 26 | if (nid == 0 || (shrinker->flags & SHRINKER_NUMA_AWARE)) {
|
| 27 | struct shrink_control sc = {
|
| 28 | .gfp_mask = GFP_KERNEL,
|
| 29 | .nid = nid,
|
| 30 | .memcg = memcg,
|
| 31 | };
|
| 32 |
|
| 33 | nr = shrinker->count_objects(shrinker, &sc);
|
| 34 | if (nr == SHRINK_EMPTY)
|
| 35 | nr = 0;
|
| 36 | } else {
|
| 37 | nr = 0;
|
| 38 | }
|
| 39 |
|
| 40 | count_per_node[nid] = nr;
|
| 41 | total += nr;
|
| 42 | }
|
| 43 |
|
| 44 | return total;
|
| 45 | }
|
| 46 |
|
| 47 | static int shrinker_debugfs_count_show(struct seq_file *m, void *v)
|
| 48 | {
|
| 49 | struct shrinker *shrinker = m->private;
|
| 50 | unsigned long *count_per_node;
|
| 51 | struct mem_cgroup *memcg;
|
| 52 | unsigned long total;
|
| 53 | bool memcg_aware;
|
| 54 | int ret = 0, nid;
|
| 55 |
|
| 56 | count_per_node = kcalloc(nr_node_ids, sizeof(unsigned long), GFP_KERNEL);
|
| 57 | if (!count_per_node)
|
| 58 | return -ENOMEM;
|
| 59 |
|
| 60 | rcu_read_lock();
|
| 61 |
|
| 62 | memcg_aware = shrinker->flags & SHRINKER_MEMCG_AWARE;
|
| 63 |
|
| 64 | memcg = mem_cgroup_iter(NULL, NULL, NULL);
|
| 65 | do {
|
| 66 | if (memcg && !mem_cgroup_online(memcg))
|
| 67 | continue;
|
| 68 |
|
| 69 | total = shrinker_count_objects(shrinker,
|
| 70 | memcg_aware ? memcg : NULL,
|
| 71 | count_per_node);
|
| 72 | if (total) {
|
| 73 | seq_printf(m, "%llu", mem_cgroup_id(memcg));
|
| 74 | for_each_node(nid)
|
| 75 | seq_printf(m, " %lu", count_per_node[nid]);
|
| 76 | seq_putc(m, '\n');
|
| 77 | }
|
| 78 |
|
| 79 | if (!memcg_aware) {
|
| 80 | mem_cgroup_iter_break(NULL, memcg);
|
| 81 | break;
|
| 82 | }
|
| 83 |
|
| 84 | if (signal_pending(current)) {
|
| 85 | mem_cgroup_iter_break(NULL, memcg);
|
| 86 | ret = -EINTR;
|
| 87 | break;
|
| 88 | }
|
| 89 | } while ((memcg = mem_cgroup_iter(NULL, memcg, NULL)) != NULL);
|
| 90 |
|
| 91 | rcu_read_unlock();
|
| 92 |
|
| 93 | kfree(count_per_node);
|
| 94 | return ret;
|
| 95 | }
|
| 96 | DEFINE_SHOW_ATTRIBUTE(shrinker_debugfs_count);
|
| 97 |
|
| 98 | static int shrinker_debugfs_scan_open(struct inode *inode, struct file *file)
|
| 99 | {
|
| 100 | file->private_data = inode->i_private;
|
| 101 | return nonseekable_open(inode, file);
|
| 102 | }
|
| 103 |
|
| 104 | static ssize_t shrinker_debugfs_scan_write(struct file *file,
|
| 105 | const char __user *buf,
|
| 106 | size_t size, loff_t *pos)
|
| 107 | {
|
| 108 | struct shrinker *shrinker = file->private_data;
|
| 109 | unsigned long nr_to_scan = 0, read_len;
|
| 110 | u64 id;
|
| 111 | struct shrink_control sc = {
|
| 112 | .gfp_mask = GFP_KERNEL,
|
| 113 | };
|
| 114 | struct mem_cgroup *memcg = NULL;
|
| 115 | int nid;
|
| 116 | char kbuf[72];
|
| 117 |
|
| 118 | read_len = min(size, sizeof(kbuf) - 1);
|
| 119 | if (copy_from_user(kbuf, buf, read_len))
|
| 120 | return -EFAULT;
|
| 121 | kbuf[read_len] = '\0';
|
| 122 |
|
| 123 | if (sscanf(kbuf, "%llu %d %lu", &id, &nid, &nr_to_scan) != 3)
|
| 124 | return -EINVAL;
|
| 125 |
|
| 126 | if (nid < 0 || nid >= nr_node_ids)
|
| 127 | return -EINVAL;
|
| 128 |
|
| 129 | if (nr_to_scan == 0)
|
| 130 | return size;
|
| 131 |
|
| 132 | if (shrinker->flags & SHRINKER_MEMCG_AWARE) {
|
| 133 | memcg = mem_cgroup_get_from_id(id);
|
| 134 | if (!memcg)
|
| 135 | return -ENOENT;
|
| 136 |
|
| 137 | if (!mem_cgroup_online(memcg)) {
|
| 138 | mem_cgroup_put(memcg);
|
| 139 | return -ENOENT;
|
| 140 | }
|
| 141 | } else if (id != 0) {
|
| 142 | return -EINVAL;
|
| 143 | }
|
| 144 |
|
| 145 | sc.nid = nid;
|
| 146 | sc.memcg = memcg;
|
| 147 | sc.nr_to_scan = nr_to_scan;
|
| 148 | sc.nr_scanned = nr_to_scan;
|
| 149 |
|
| 150 | shrinker->scan_objects(shrinker, &sc);
|
| 151 |
|
| 152 | mem_cgroup_put(memcg);
|
| 153 |
|
| 154 | return size;
|
| 155 | }
|
| 156 |
|
| 157 | static const struct file_operations shrinker_debugfs_scan_fops = {
|
| 158 | .owner = THIS_MODULE,
|
| 159 | .open = shrinker_debugfs_scan_open,
|
| 160 | .write = shrinker_debugfs_scan_write,
|
| 161 | };
|
| 162 |
|
| 163 | int shrinker_debugfs_add(struct shrinker *shrinker)
|
| 164 | {
|
| 165 | struct dentry *entry;
|
| 166 | char buf[128];
|
| 167 | int id;
|
| 168 |
|
| 169 | lockdep_assert_held(&shrinker_mutex);
|
| 170 |
|
| 171 |
|
| 172 | if (!shrinker_debugfs_root)
|
| 173 | return 0;
|
| 174 |
|
| 175 | id = ida_alloc(&shrinker_debugfs_ida, GFP_KERNEL);
|
| 176 | if (id < 0)
|
| 177 | return id;
|
| 178 | shrinker->debugfs_id = id;
|
| 179 |
|
| 180 | snprintf(buf, sizeof(buf), "%s-%d", shrinker->name, id);
|
| 181 |
|
| 182 |
|
| 183 | entry = debugfs_create_dir(buf, shrinker_debugfs_root);
|
| 184 | if (IS_ERR(entry)) {
|
| 185 | ida_free(&shrinker_debugfs_ida, id);
|
| 186 | return PTR_ERR(entry);
|
| 187 | }
|
| 188 | shrinker->debugfs_entry = entry;
|
| 189 |
|
| 190 | debugfs_create_file("count", 0440, entry, shrinker,
|
| 191 | &shrinker_debugfs_count_fops);
|
| 192 | debugfs_create_file("scan", 0220, entry, shrinker,
|
| 193 | &shrinker_debugfs_scan_fops);
|
| 194 | return 0;
|
| 195 | }
|
| 196 |
|
| 197 | int shrinker_debugfs_rename(struct shrinker *shrinker, const char *fmt, ...)
|
| 198 | {
|
| 199 | const char *new, *old;
|
| 200 | va_list ap;
|
| 201 | int ret = 0;
|
| 202 |
|
| 203 | va_start(ap, fmt);
|
| 204 | new = kvasprintf_const(GFP_KERNEL, fmt, ap);
|
| 205 | va_end(ap);
|
| 206 |
|
| 207 | if (!new)
|
| 208 | return -ENOMEM;
|
| 209 |
|
| 210 | mutex_lock(&shrinker_mutex);
|
| 211 |
|
| 212 | old = shrinker->name;
|
| 213 | shrinker->name = new;
|
| 214 |
|
| 215 | ret = debugfs_change_name(shrinker->debugfs_entry, "%s-%d",
|
| 216 | shrinker->name, shrinker->debugfs_id);
|
| 217 |
|
| 218 | if (ret) {
|
| 219 | shrinker->name = old;
|
| 220 | kfree_const(new);
|
| 221 | } else {
|
| 222 | kfree_const(old);
|
| 223 | }
|
| 224 | mutex_unlock(&shrinker_mutex);
|
| 225 |
|
| 226 | return ret;
|
| 227 | }
|
| 228 | EXPORT_SYMBOL(shrinker_debugfs_rename);
|
| 229 |
|
| 230 | struct dentry *shrinker_debugfs_detach(struct shrinker *shrinker,
|
| 231 | int *debugfs_id)
|
| 232 | {
|
| 233 | struct dentry *entry = shrinker->debugfs_entry;
|
| 234 |
|
| 235 | lockdep_assert_held(&shrinker_mutex);
|
| 236 |
|
| 237 | *debugfs_id = entry ? shrinker->debugfs_id : -1;
|
| 238 | shrinker->debugfs_entry = NULL;
|
| 239 |
|
| 240 | return entry;
|
| 241 | }
|
| 242 |
|
| 243 | void shrinker_debugfs_remove(struct dentry *debugfs_entry, int debugfs_id)
|
| 244 | {
|
| 245 | debugfs_remove_recursive(debugfs_entry);
|
| 246 | ida_free(&shrinker_debugfs_ida, debugfs_id);
|
| 247 | }
|
| 248 |
|
| 249 | static int __init shrinker_debugfs_init(void)
|
| 250 | {
|
| 251 | struct shrinker *shrinker;
|
| 252 | struct dentry *dentry;
|
| 253 | int ret = 0;
|
| 254 |
|
| 255 | dentry = debugfs_create_dir("shrinker", NULL);
|
| 256 | if (IS_ERR(dentry))
|
| 257 | return PTR_ERR(dentry);
|
| 258 | shrinker_debugfs_root = dentry;
|
| 259 |
|
| 260 |
|
| 261 | mutex_lock(&shrinker_mutex);
|
| 262 | list_for_each_entry(shrinker, &shrinker_list, list)
|
| 263 | if (!shrinker->debugfs_entry) {
|
| 264 | ret = shrinker_debugfs_add(shrinker);
|
| 265 | if (ret)
|
| 266 | break;
|
| 267 | }
|
| 268 | mutex_unlock(&shrinker_mutex);
|
| 269 |
|
| 270 | return ret;
|
| 271 | }
|
| 272 | late_initcall(shrinker_debugfs_init);
|