diff options
Diffstat (limited to 'net/sunrpc/debugfs.c')
-rw-r--r-- | net/sunrpc/debugfs.c | 292 |
1 files changed, 292 insertions, 0 deletions
diff --git a/net/sunrpc/debugfs.c b/net/sunrpc/debugfs.c new file mode 100644 index 000000000000..e811f390f9f6 --- /dev/null +++ b/net/sunrpc/debugfs.c | |||
@@ -0,0 +1,292 @@ | |||
1 | /** | ||
2 | * debugfs interface for sunrpc | ||
3 | * | ||
4 | * (c) 2014 Jeff Layton <jlayton@primarydata.com> | ||
5 | */ | ||
6 | |||
7 | #include <linux/debugfs.h> | ||
8 | #include <linux/sunrpc/sched.h> | ||
9 | #include <linux/sunrpc/clnt.h> | ||
10 | #include "netns.h" | ||
11 | |||
12 | static struct dentry *topdir; | ||
13 | static struct dentry *rpc_clnt_dir; | ||
14 | static struct dentry *rpc_xprt_dir; | ||
15 | |||
16 | struct rpc_clnt_iter { | ||
17 | struct rpc_clnt *clnt; | ||
18 | loff_t pos; | ||
19 | }; | ||
20 | |||
21 | static int | ||
22 | tasks_show(struct seq_file *f, void *v) | ||
23 | { | ||
24 | u32 xid = 0; | ||
25 | struct rpc_task *task = v; | ||
26 | struct rpc_clnt *clnt = task->tk_client; | ||
27 | const char *rpc_waitq = "none"; | ||
28 | |||
29 | if (RPC_IS_QUEUED(task)) | ||
30 | rpc_waitq = rpc_qname(task->tk_waitqueue); | ||
31 | |||
32 | if (task->tk_rqstp) | ||
33 | xid = be32_to_cpu(task->tk_rqstp->rq_xid); | ||
34 | |||
35 | seq_printf(f, "%5u %04x %6d 0x%x 0x%x %8ld %ps %sv%u %s a:%ps q:%s\n", | ||
36 | task->tk_pid, task->tk_flags, task->tk_status, | ||
37 | clnt->cl_clid, xid, task->tk_timeout, task->tk_ops, | ||
38 | clnt->cl_program->name, clnt->cl_vers, rpc_proc_name(task), | ||
39 | task->tk_action, rpc_waitq); | ||
40 | return 0; | ||
41 | } | ||
42 | |||
43 | static void * | ||
44 | tasks_start(struct seq_file *f, loff_t *ppos) | ||
45 | __acquires(&clnt->cl_lock) | ||
46 | { | ||
47 | struct rpc_clnt_iter *iter = f->private; | ||
48 | loff_t pos = *ppos; | ||
49 | struct rpc_clnt *clnt = iter->clnt; | ||
50 | struct rpc_task *task; | ||
51 | |||
52 | iter->pos = pos + 1; | ||
53 | spin_lock(&clnt->cl_lock); | ||
54 | list_for_each_entry(task, &clnt->cl_tasks, tk_task) | ||
55 | if (pos-- == 0) | ||
56 | return task; | ||
57 | return NULL; | ||
58 | } | ||
59 | |||
60 | static void * | ||
61 | tasks_next(struct seq_file *f, void *v, loff_t *pos) | ||
62 | { | ||
63 | struct rpc_clnt_iter *iter = f->private; | ||
64 | struct rpc_clnt *clnt = iter->clnt; | ||
65 | struct rpc_task *task = v; | ||
66 | struct list_head *next = task->tk_task.next; | ||
67 | |||
68 | ++iter->pos; | ||
69 | ++*pos; | ||
70 | |||
71 | /* If there's another task on list, return it */ | ||
72 | if (next == &clnt->cl_tasks) | ||
73 | return NULL; | ||
74 | return list_entry(next, struct rpc_task, tk_task); | ||
75 | } | ||
76 | |||
77 | static void | ||
78 | tasks_stop(struct seq_file *f, void *v) | ||
79 | __releases(&clnt->cl_lock) | ||
80 | { | ||
81 | struct rpc_clnt_iter *iter = f->private; | ||
82 | struct rpc_clnt *clnt = iter->clnt; | ||
83 | |||
84 | spin_unlock(&clnt->cl_lock); | ||
85 | } | ||
86 | |||
87 | static const struct seq_operations tasks_seq_operations = { | ||
88 | .start = tasks_start, | ||
89 | .next = tasks_next, | ||
90 | .stop = tasks_stop, | ||
91 | .show = tasks_show, | ||
92 | }; | ||
93 | |||
94 | static int tasks_open(struct inode *inode, struct file *filp) | ||
95 | { | ||
96 | int ret = seq_open_private(filp, &tasks_seq_operations, | ||
97 | sizeof(struct rpc_clnt_iter)); | ||
98 | |||
99 | if (!ret) { | ||
100 | struct seq_file *seq = filp->private_data; | ||
101 | struct rpc_clnt_iter *iter = seq->private; | ||
102 | |||
103 | iter->clnt = inode->i_private; | ||
104 | |||
105 | if (!atomic_inc_not_zero(&iter->clnt->cl_count)) { | ||
106 | seq_release_private(inode, filp); | ||
107 | ret = -EINVAL; | ||
108 | } | ||
109 | } | ||
110 | |||
111 | return ret; | ||
112 | } | ||
113 | |||
114 | static int | ||
115 | tasks_release(struct inode *inode, struct file *filp) | ||
116 | { | ||
117 | struct seq_file *seq = filp->private_data; | ||
118 | struct rpc_clnt_iter *iter = seq->private; | ||
119 | |||
120 | rpc_release_client(iter->clnt); | ||
121 | return seq_release_private(inode, filp); | ||
122 | } | ||
123 | |||
124 | static const struct file_operations tasks_fops = { | ||
125 | .owner = THIS_MODULE, | ||
126 | .open = tasks_open, | ||
127 | .read = seq_read, | ||
128 | .llseek = seq_lseek, | ||
129 | .release = tasks_release, | ||
130 | }; | ||
131 | |||
132 | int | ||
133 | rpc_clnt_debugfs_register(struct rpc_clnt *clnt) | ||
134 | { | ||
135 | int len, err; | ||
136 | char name[24]; /* enough for "../../rpc_xprt/ + 8 hex digits + NULL */ | ||
137 | |||
138 | /* Already registered? */ | ||
139 | if (clnt->cl_debugfs) | ||
140 | return 0; | ||
141 | |||
142 | len = snprintf(name, sizeof(name), "%x", clnt->cl_clid); | ||
143 | if (len >= sizeof(name)) | ||
144 | return -EINVAL; | ||
145 | |||
146 | /* make the per-client dir */ | ||
147 | clnt->cl_debugfs = debugfs_create_dir(name, rpc_clnt_dir); | ||
148 | if (!clnt->cl_debugfs) | ||
149 | return -ENOMEM; | ||
150 | |||
151 | /* make tasks file */ | ||
152 | err = -ENOMEM; | ||
153 | if (!debugfs_create_file("tasks", S_IFREG | S_IRUSR, clnt->cl_debugfs, | ||
154 | clnt, &tasks_fops)) | ||
155 | goto out_err; | ||
156 | |||
157 | err = -EINVAL; | ||
158 | rcu_read_lock(); | ||
159 | len = snprintf(name, sizeof(name), "../../rpc_xprt/%s", | ||
160 | rcu_dereference(clnt->cl_xprt)->debugfs->d_name.name); | ||
161 | rcu_read_unlock(); | ||
162 | if (len >= sizeof(name)) | ||
163 | goto out_err; | ||
164 | |||
165 | err = -ENOMEM; | ||
166 | if (!debugfs_create_symlink("xprt", clnt->cl_debugfs, name)) | ||
167 | goto out_err; | ||
168 | |||
169 | return 0; | ||
170 | out_err: | ||
171 | debugfs_remove_recursive(clnt->cl_debugfs); | ||
172 | clnt->cl_debugfs = NULL; | ||
173 | return err; | ||
174 | } | ||
175 | |||
176 | void | ||
177 | rpc_clnt_debugfs_unregister(struct rpc_clnt *clnt) | ||
178 | { | ||
179 | debugfs_remove_recursive(clnt->cl_debugfs); | ||
180 | clnt->cl_debugfs = NULL; | ||
181 | } | ||
182 | |||
183 | static int | ||
184 | xprt_info_show(struct seq_file *f, void *v) | ||
185 | { | ||
186 | struct rpc_xprt *xprt = f->private; | ||
187 | |||
188 | seq_printf(f, "netid: %s\n", xprt->address_strings[RPC_DISPLAY_NETID]); | ||
189 | seq_printf(f, "addr: %s\n", xprt->address_strings[RPC_DISPLAY_ADDR]); | ||
190 | seq_printf(f, "port: %s\n", xprt->address_strings[RPC_DISPLAY_PORT]); | ||
191 | seq_printf(f, "state: 0x%lx\n", xprt->state); | ||
192 | return 0; | ||
193 | } | ||
194 | |||
195 | static int | ||
196 | xprt_info_open(struct inode *inode, struct file *filp) | ||
197 | { | ||
198 | int ret; | ||
199 | struct rpc_xprt *xprt = inode->i_private; | ||
200 | |||
201 | ret = single_open(filp, xprt_info_show, xprt); | ||
202 | |||
203 | if (!ret) { | ||
204 | if (!xprt_get(xprt)) { | ||
205 | single_release(inode, filp); | ||
206 | ret = -EINVAL; | ||
207 | } | ||
208 | } | ||
209 | return ret; | ||
210 | } | ||
211 | |||
212 | static int | ||
213 | xprt_info_release(struct inode *inode, struct file *filp) | ||
214 | { | ||
215 | struct rpc_xprt *xprt = inode->i_private; | ||
216 | |||
217 | xprt_put(xprt); | ||
218 | return single_release(inode, filp); | ||
219 | } | ||
220 | |||
221 | static const struct file_operations xprt_info_fops = { | ||
222 | .owner = THIS_MODULE, | ||
223 | .open = xprt_info_open, | ||
224 | .read = seq_read, | ||
225 | .llseek = seq_lseek, | ||
226 | .release = xprt_info_release, | ||
227 | }; | ||
228 | |||
229 | int | ||
230 | rpc_xprt_debugfs_register(struct rpc_xprt *xprt) | ||
231 | { | ||
232 | int len, id; | ||
233 | static atomic_t cur_id; | ||
234 | char name[9]; /* 8 hex digits + NULL term */ | ||
235 | |||
236 | id = (unsigned int)atomic_inc_return(&cur_id); | ||
237 | |||
238 | len = snprintf(name, sizeof(name), "%x", id); | ||
239 | if (len >= sizeof(name)) | ||
240 | return -EINVAL; | ||
241 | |||
242 | /* make the per-client dir */ | ||
243 | xprt->debugfs = debugfs_create_dir(name, rpc_xprt_dir); | ||
244 | if (!xprt->debugfs) | ||
245 | return -ENOMEM; | ||
246 | |||
247 | /* make tasks file */ | ||
248 | if (!debugfs_create_file("info", S_IFREG | S_IRUSR, xprt->debugfs, | ||
249 | xprt, &xprt_info_fops)) { | ||
250 | debugfs_remove_recursive(xprt->debugfs); | ||
251 | xprt->debugfs = NULL; | ||
252 | return -ENOMEM; | ||
253 | } | ||
254 | |||
255 | return 0; | ||
256 | } | ||
257 | |||
258 | void | ||
259 | rpc_xprt_debugfs_unregister(struct rpc_xprt *xprt) | ||
260 | { | ||
261 | debugfs_remove_recursive(xprt->debugfs); | ||
262 | xprt->debugfs = NULL; | ||
263 | } | ||
264 | |||
265 | void __exit | ||
266 | sunrpc_debugfs_exit(void) | ||
267 | { | ||
268 | debugfs_remove_recursive(topdir); | ||
269 | } | ||
270 | |||
271 | int __init | ||
272 | sunrpc_debugfs_init(void) | ||
273 | { | ||
274 | topdir = debugfs_create_dir("sunrpc", NULL); | ||
275 | if (!topdir) | ||
276 | goto out; | ||
277 | |||
278 | rpc_clnt_dir = debugfs_create_dir("rpc_clnt", topdir); | ||
279 | if (!rpc_clnt_dir) | ||
280 | goto out_remove; | ||
281 | |||
282 | rpc_xprt_dir = debugfs_create_dir("rpc_xprt", topdir); | ||
283 | if (!rpc_xprt_dir) | ||
284 | goto out_remove; | ||
285 | |||
286 | return 0; | ||
287 | out_remove: | ||
288 | debugfs_remove_recursive(topdir); | ||
289 | topdir = NULL; | ||
290 | out: | ||
291 | return -ENOMEM; | ||
292 | } | ||