1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Debug controller |
4 | * |
5 | * WARNING: This controller is for cgroup core debugging only. |
6 | * Its interfaces are unstable and subject to changes at any time. |
7 | */ |
8 | #include <linux/ctype.h> |
9 | #include <linux/mm.h> |
10 | #include <linux/slab.h> |
11 | |
12 | #include "cgroup-internal.h" |
13 | |
14 | static struct cgroup_subsys_state * |
15 | debug_css_alloc(struct cgroup_subsys_state *parent_css) |
16 | { |
17 | struct cgroup_subsys_state *css = kzalloc(size: sizeof(*css), GFP_KERNEL); |
18 | |
19 | if (!css) |
20 | return ERR_PTR(error: -ENOMEM); |
21 | |
22 | return css; |
23 | } |
24 | |
25 | static void debug_css_free(struct cgroup_subsys_state *css) |
26 | { |
27 | kfree(objp: css); |
28 | } |
29 | |
30 | /* |
31 | * debug_taskcount_read - return the number of tasks in a cgroup. |
32 | * @cgrp: the cgroup in question |
33 | */ |
34 | static u64 debug_taskcount_read(struct cgroup_subsys_state *css, |
35 | struct cftype *cft) |
36 | { |
37 | return cgroup_task_count(cgrp: css->cgroup); |
38 | } |
39 | |
40 | static int current_css_set_read(struct seq_file *seq, void *v) |
41 | { |
42 | struct kernfs_open_file *of = seq->private; |
43 | struct css_set *cset; |
44 | struct cgroup_subsys *ss; |
45 | struct cgroup_subsys_state *css; |
46 | int i, refcnt; |
47 | |
48 | if (!cgroup_kn_lock_live(kn: of->kn, drain_offline: false)) |
49 | return -ENODEV; |
50 | |
51 | spin_lock_irq(lock: &css_set_lock); |
52 | rcu_read_lock(); |
53 | cset = task_css_set(current); |
54 | refcnt = refcount_read(r: &cset->refcount); |
55 | seq_printf(m: seq, fmt: "css_set %pK %d" , cset, refcnt); |
56 | if (refcnt > cset->nr_tasks) |
57 | seq_printf(m: seq, fmt: " +%d" , refcnt - cset->nr_tasks); |
58 | seq_puts(m: seq, s: "\n" ); |
59 | |
60 | /* |
61 | * Print the css'es stored in the current css_set. |
62 | */ |
63 | for_each_subsys(ss, i) { |
64 | css = cset->subsys[ss->id]; |
65 | if (!css) |
66 | continue; |
67 | seq_printf(m: seq, fmt: "%2d: %-4s\t- %p[%d]\n" , ss->id, ss->name, |
68 | css, css->id); |
69 | } |
70 | rcu_read_unlock(); |
71 | spin_unlock_irq(lock: &css_set_lock); |
72 | cgroup_kn_unlock(kn: of->kn); |
73 | return 0; |
74 | } |
75 | |
76 | static u64 current_css_set_refcount_read(struct cgroup_subsys_state *css, |
77 | struct cftype *cft) |
78 | { |
79 | u64 count; |
80 | |
81 | rcu_read_lock(); |
82 | count = refcount_read(r: &task_css_set(current)->refcount); |
83 | rcu_read_unlock(); |
84 | return count; |
85 | } |
86 | |
87 | static int current_css_set_cg_links_read(struct seq_file *seq, void *v) |
88 | { |
89 | struct cgrp_cset_link *link; |
90 | struct css_set *cset; |
91 | char *name_buf; |
92 | |
93 | name_buf = kmalloc(NAME_MAX + 1, GFP_KERNEL); |
94 | if (!name_buf) |
95 | return -ENOMEM; |
96 | |
97 | spin_lock_irq(lock: &css_set_lock); |
98 | rcu_read_lock(); |
99 | cset = task_css_set(current); |
100 | list_for_each_entry(link, &cset->cgrp_links, cgrp_link) { |
101 | struct cgroup *c = link->cgrp; |
102 | |
103 | cgroup_name(cgrp: c, buf: name_buf, NAME_MAX + 1); |
104 | seq_printf(m: seq, fmt: "Root %d group %s\n" , |
105 | c->root->hierarchy_id, name_buf); |
106 | } |
107 | rcu_read_unlock(); |
108 | spin_unlock_irq(lock: &css_set_lock); |
109 | kfree(objp: name_buf); |
110 | return 0; |
111 | } |
112 | |
113 | #define MAX_TASKS_SHOWN_PER_CSS 25 |
114 | static int cgroup_css_links_read(struct seq_file *seq, void *v) |
115 | { |
116 | struct cgroup_subsys_state *css = seq_css(seq); |
117 | struct cgrp_cset_link *link; |
118 | int dead_cnt = 0, = 0, threaded_csets = 0; |
119 | |
120 | spin_lock_irq(lock: &css_set_lock); |
121 | |
122 | list_for_each_entry(link, &css->cgroup->cset_links, cset_link) { |
123 | struct css_set *cset = link->cset; |
124 | struct task_struct *task; |
125 | int count = 0; |
126 | int refcnt = refcount_read(r: &cset->refcount); |
127 | |
128 | /* |
129 | * Print out the proc_cset and threaded_cset relationship |
130 | * and highlight difference between refcount and task_count. |
131 | */ |
132 | seq_printf(m: seq, fmt: "css_set %pK" , cset); |
133 | if (rcu_dereference_protected(cset->dom_cset, 1) != cset) { |
134 | threaded_csets++; |
135 | seq_printf(m: seq, fmt: "=>%pK" , cset->dom_cset); |
136 | } |
137 | if (!list_empty(head: &cset->threaded_csets)) { |
138 | struct css_set *tcset; |
139 | int idx = 0; |
140 | |
141 | list_for_each_entry(tcset, &cset->threaded_csets, |
142 | threaded_csets_node) { |
143 | seq_puts(m: seq, s: idx ? "," : "<=" ); |
144 | seq_printf(m: seq, fmt: "%pK" , tcset); |
145 | idx++; |
146 | } |
147 | } else { |
148 | seq_printf(m: seq, fmt: " %d" , refcnt); |
149 | if (refcnt - cset->nr_tasks > 0) { |
150 | int = refcnt - cset->nr_tasks; |
151 | |
152 | seq_printf(m: seq, fmt: " +%d" , extra); |
153 | /* |
154 | * Take out the one additional reference in |
155 | * init_css_set. |
156 | */ |
157 | if (cset == &init_css_set) |
158 | extra--; |
159 | extra_refs += extra; |
160 | } |
161 | } |
162 | seq_puts(m: seq, s: "\n" ); |
163 | |
164 | list_for_each_entry(task, &cset->tasks, cg_list) { |
165 | if (count++ <= MAX_TASKS_SHOWN_PER_CSS) |
166 | seq_printf(m: seq, fmt: " task %d\n" , |
167 | task_pid_vnr(tsk: task)); |
168 | } |
169 | |
170 | list_for_each_entry(task, &cset->mg_tasks, cg_list) { |
171 | if (count++ <= MAX_TASKS_SHOWN_PER_CSS) |
172 | seq_printf(m: seq, fmt: " task %d\n" , |
173 | task_pid_vnr(tsk: task)); |
174 | } |
175 | /* show # of overflowed tasks */ |
176 | if (count > MAX_TASKS_SHOWN_PER_CSS) |
177 | seq_printf(m: seq, fmt: " ... (%d)\n" , |
178 | count - MAX_TASKS_SHOWN_PER_CSS); |
179 | |
180 | if (cset->dead) { |
181 | seq_puts(m: seq, s: " [dead]\n" ); |
182 | dead_cnt++; |
183 | } |
184 | |
185 | WARN_ON(count != cset->nr_tasks); |
186 | } |
187 | spin_unlock_irq(lock: &css_set_lock); |
188 | |
189 | if (!dead_cnt && !extra_refs && !threaded_csets) |
190 | return 0; |
191 | |
192 | seq_puts(m: seq, s: "\n" ); |
193 | if (threaded_csets) |
194 | seq_printf(m: seq, fmt: "threaded css_sets = %d\n" , threaded_csets); |
195 | if (extra_refs) |
196 | seq_printf(m: seq, fmt: "extra references = %d\n" , extra_refs); |
197 | if (dead_cnt) |
198 | seq_printf(m: seq, fmt: "dead css_sets = %d\n" , dead_cnt); |
199 | |
200 | return 0; |
201 | } |
202 | |
203 | static int cgroup_subsys_states_read(struct seq_file *seq, void *v) |
204 | { |
205 | struct kernfs_open_file *of = seq->private; |
206 | struct cgroup *cgrp; |
207 | struct cgroup_subsys *ss; |
208 | struct cgroup_subsys_state *css; |
209 | char pbuf[16]; |
210 | int i; |
211 | |
212 | cgrp = cgroup_kn_lock_live(kn: of->kn, drain_offline: false); |
213 | if (!cgrp) |
214 | return -ENODEV; |
215 | |
216 | for_each_subsys(ss, i) { |
217 | css = rcu_dereference_check(cgrp->subsys[ss->id], true); |
218 | if (!css) |
219 | continue; |
220 | |
221 | pbuf[0] = '\0'; |
222 | |
223 | /* Show the parent CSS if applicable*/ |
224 | if (css->parent) |
225 | snprintf(buf: pbuf, size: sizeof(pbuf) - 1, fmt: " P=%d" , |
226 | css->parent->id); |
227 | seq_printf(m: seq, fmt: "%2d: %-4s\t- %p[%d] %d%s\n" , ss->id, ss->name, |
228 | css, css->id, |
229 | atomic_read(v: &css->online_cnt), pbuf); |
230 | } |
231 | |
232 | cgroup_kn_unlock(kn: of->kn); |
233 | return 0; |
234 | } |
235 | |
236 | static void cgroup_masks_read_one(struct seq_file *seq, const char *name, |
237 | u16 mask) |
238 | { |
239 | struct cgroup_subsys *ss; |
240 | int ssid; |
241 | bool first = true; |
242 | |
243 | seq_printf(m: seq, fmt: "%-17s: " , name); |
244 | for_each_subsys(ss, ssid) { |
245 | if (!(mask & (1 << ssid))) |
246 | continue; |
247 | if (!first) |
248 | seq_puts(m: seq, s: ", " ); |
249 | seq_puts(m: seq, s: ss->name); |
250 | first = false; |
251 | } |
252 | seq_putc(m: seq, c: '\n'); |
253 | } |
254 | |
255 | static int cgroup_masks_read(struct seq_file *seq, void *v) |
256 | { |
257 | struct kernfs_open_file *of = seq->private; |
258 | struct cgroup *cgrp; |
259 | |
260 | cgrp = cgroup_kn_lock_live(kn: of->kn, drain_offline: false); |
261 | if (!cgrp) |
262 | return -ENODEV; |
263 | |
264 | cgroup_masks_read_one(seq, name: "subtree_control" , mask: cgrp->subtree_control); |
265 | cgroup_masks_read_one(seq, name: "subtree_ss_mask" , mask: cgrp->subtree_ss_mask); |
266 | |
267 | cgroup_kn_unlock(kn: of->kn); |
268 | return 0; |
269 | } |
270 | |
271 | static u64 releasable_read(struct cgroup_subsys_state *css, struct cftype *cft) |
272 | { |
273 | return (!cgroup_is_populated(cgrp: css->cgroup) && |
274 | !css_has_online_children(css: &css->cgroup->self)); |
275 | } |
276 | |
277 | static struct cftype debug_legacy_files[] = { |
278 | { |
279 | .name = "taskcount" , |
280 | .read_u64 = debug_taskcount_read, |
281 | }, |
282 | |
283 | { |
284 | .name = "current_css_set" , |
285 | .seq_show = current_css_set_read, |
286 | .flags = CFTYPE_ONLY_ON_ROOT, |
287 | }, |
288 | |
289 | { |
290 | .name = "current_css_set_refcount" , |
291 | .read_u64 = current_css_set_refcount_read, |
292 | .flags = CFTYPE_ONLY_ON_ROOT, |
293 | }, |
294 | |
295 | { |
296 | .name = "current_css_set_cg_links" , |
297 | .seq_show = current_css_set_cg_links_read, |
298 | .flags = CFTYPE_ONLY_ON_ROOT, |
299 | }, |
300 | |
301 | { |
302 | .name = "cgroup_css_links" , |
303 | .seq_show = cgroup_css_links_read, |
304 | }, |
305 | |
306 | { |
307 | .name = "cgroup_subsys_states" , |
308 | .seq_show = cgroup_subsys_states_read, |
309 | }, |
310 | |
311 | { |
312 | .name = "cgroup_masks" , |
313 | .seq_show = cgroup_masks_read, |
314 | }, |
315 | |
316 | { |
317 | .name = "releasable" , |
318 | .read_u64 = releasable_read, |
319 | }, |
320 | |
321 | { } /* terminate */ |
322 | }; |
323 | |
324 | static struct cftype debug_files[] = { |
325 | { |
326 | .name = "taskcount" , |
327 | .read_u64 = debug_taskcount_read, |
328 | }, |
329 | |
330 | { |
331 | .name = "current_css_set" , |
332 | .seq_show = current_css_set_read, |
333 | .flags = CFTYPE_ONLY_ON_ROOT, |
334 | }, |
335 | |
336 | { |
337 | .name = "current_css_set_refcount" , |
338 | .read_u64 = current_css_set_refcount_read, |
339 | .flags = CFTYPE_ONLY_ON_ROOT, |
340 | }, |
341 | |
342 | { |
343 | .name = "current_css_set_cg_links" , |
344 | .seq_show = current_css_set_cg_links_read, |
345 | .flags = CFTYPE_ONLY_ON_ROOT, |
346 | }, |
347 | |
348 | { |
349 | .name = "css_links" , |
350 | .seq_show = cgroup_css_links_read, |
351 | }, |
352 | |
353 | { |
354 | .name = "csses" , |
355 | .seq_show = cgroup_subsys_states_read, |
356 | }, |
357 | |
358 | { |
359 | .name = "masks" , |
360 | .seq_show = cgroup_masks_read, |
361 | }, |
362 | |
363 | { } /* terminate */ |
364 | }; |
365 | |
366 | struct cgroup_subsys debug_cgrp_subsys = { |
367 | .css_alloc = debug_css_alloc, |
368 | .css_free = debug_css_free, |
369 | .legacy_cftypes = debug_legacy_files, |
370 | }; |
371 | |
372 | /* |
373 | * On v2, debug is an implicit controller enabled by "cgroup_debug" boot |
374 | * parameter. |
375 | */ |
376 | void __init enable_debug_cgroup(void) |
377 | { |
378 | debug_cgrp_subsys.dfl_cftypes = debug_files; |
379 | debug_cgrp_subsys.implicit_on_dfl = true; |
380 | debug_cgrp_subsys.threaded = true; |
381 | } |
382 | |