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