1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | #define pr_fmt(fmt) "drbd debugfs: " fmt |
3 | #include <linux/kernel.h> |
4 | #include <linux/module.h> |
5 | #include <linux/debugfs.h> |
6 | #include <linux/seq_file.h> |
7 | #include <linux/stat.h> |
8 | #include <linux/jiffies.h> |
9 | #include <linux/list.h> |
10 | |
11 | #include "drbd_int.h" |
12 | #include "drbd_req.h" |
13 | #include "drbd_debugfs.h" |
14 | |
15 | |
16 | /********************************************************************** |
17 | * Whenever you change the file format, remember to bump the version. * |
18 | **********************************************************************/ |
19 | |
20 | static struct dentry *drbd_debugfs_root; |
21 | static struct dentry *drbd_debugfs_version; |
22 | static struct dentry *drbd_debugfs_resources; |
23 | static struct dentry *drbd_debugfs_minors; |
24 | |
25 | static void seq_print_age_or_dash(struct seq_file *m, bool valid, unsigned long dt) |
26 | { |
27 | if (valid) |
28 | seq_printf(m, fmt: "\t%d" , jiffies_to_msecs(j: dt)); |
29 | else |
30 | seq_printf(m, fmt: "\t-" ); |
31 | } |
32 | |
33 | static void __seq_print_rq_state_bit(struct seq_file *m, |
34 | bool is_set, char *sep, const char *set_name, const char *unset_name) |
35 | { |
36 | if (is_set && set_name) { |
37 | seq_putc(m, c: *sep); |
38 | seq_puts(m, s: set_name); |
39 | *sep = '|'; |
40 | } else if (!is_set && unset_name) { |
41 | seq_putc(m, c: *sep); |
42 | seq_puts(m, s: unset_name); |
43 | *sep = '|'; |
44 | } |
45 | } |
46 | |
47 | static void seq_print_rq_state_bit(struct seq_file *m, |
48 | bool is_set, char *sep, const char *set_name) |
49 | { |
50 | __seq_print_rq_state_bit(m, is_set, sep, set_name, NULL); |
51 | } |
52 | |
53 | /* pretty print enum drbd_req_state_bits req->rq_state */ |
54 | static void seq_print_request_state(struct seq_file *m, struct drbd_request *req) |
55 | { |
56 | unsigned int s = req->rq_state; |
57 | char sep = ' '; |
58 | seq_printf(m, fmt: "\t0x%08x" , s); |
59 | seq_printf(m, fmt: "\tmaster: %s" , req->master_bio ? "pending" : "completed" ); |
60 | |
61 | /* RQ_WRITE ignored, already reported */ |
62 | seq_puts(m, s: "\tlocal:" ); |
63 | seq_print_rq_state_bit(m, is_set: s & RQ_IN_ACT_LOG, sep: &sep, set_name: "in-AL" ); |
64 | seq_print_rq_state_bit(m, is_set: s & RQ_POSTPONED, sep: &sep, set_name: "postponed" ); |
65 | seq_print_rq_state_bit(m, is_set: s & RQ_COMPLETION_SUSP, sep: &sep, set_name: "suspended" ); |
66 | sep = ' '; |
67 | seq_print_rq_state_bit(m, is_set: s & RQ_LOCAL_PENDING, sep: &sep, set_name: "pending" ); |
68 | seq_print_rq_state_bit(m, is_set: s & RQ_LOCAL_COMPLETED, sep: &sep, set_name: "completed" ); |
69 | seq_print_rq_state_bit(m, is_set: s & RQ_LOCAL_ABORTED, sep: &sep, set_name: "aborted" ); |
70 | seq_print_rq_state_bit(m, is_set: s & RQ_LOCAL_OK, sep: &sep, set_name: "ok" ); |
71 | if (sep == ' ') |
72 | seq_puts(m, s: " -" ); |
73 | |
74 | /* for_each_connection ... */ |
75 | seq_printf(m, fmt: "\tnet:" ); |
76 | sep = ' '; |
77 | seq_print_rq_state_bit(m, is_set: s & RQ_NET_PENDING, sep: &sep, set_name: "pending" ); |
78 | seq_print_rq_state_bit(m, is_set: s & RQ_NET_QUEUED, sep: &sep, set_name: "queued" ); |
79 | seq_print_rq_state_bit(m, is_set: s & RQ_NET_SENT, sep: &sep, set_name: "sent" ); |
80 | seq_print_rq_state_bit(m, is_set: s & RQ_NET_DONE, sep: &sep, set_name: "done" ); |
81 | seq_print_rq_state_bit(m, is_set: s & RQ_NET_SIS, sep: &sep, set_name: "sis" ); |
82 | seq_print_rq_state_bit(m, is_set: s & RQ_NET_OK, sep: &sep, set_name: "ok" ); |
83 | if (sep == ' ') |
84 | seq_puts(m, s: " -" ); |
85 | |
86 | seq_printf(m, fmt: " :" ); |
87 | sep = ' '; |
88 | seq_print_rq_state_bit(m, is_set: s & RQ_EXP_RECEIVE_ACK, sep: &sep, set_name: "B" ); |
89 | seq_print_rq_state_bit(m, is_set: s & RQ_EXP_WRITE_ACK, sep: &sep, set_name: "C" ); |
90 | seq_print_rq_state_bit(m, is_set: s & RQ_EXP_BARR_ACK, sep: &sep, set_name: "barr" ); |
91 | if (sep == ' ') |
92 | seq_puts(m, s: " -" ); |
93 | seq_printf(m, fmt: "\n" ); |
94 | } |
95 | |
96 | static void seq_print_one_request(struct seq_file *m, struct drbd_request *req, unsigned long now) |
97 | { |
98 | /* change anything here, fixup header below! */ |
99 | unsigned int s = req->rq_state; |
100 | |
101 | #define RQ_HDR_1 "epoch\tsector\tsize\trw" |
102 | seq_printf(m, fmt: "0x%x\t%llu\t%u\t%s" , |
103 | req->epoch, |
104 | (unsigned long long)req->i.sector, req->i.size >> 9, |
105 | (s & RQ_WRITE) ? "W" : "R" ); |
106 | |
107 | #define RQ_HDR_2 "\tstart\tin AL\tsubmit" |
108 | seq_printf(m, fmt: "\t%d" , jiffies_to_msecs(j: now - req->start_jif)); |
109 | seq_print_age_or_dash(m, valid: s & RQ_IN_ACT_LOG, dt: now - req->in_actlog_jif); |
110 | seq_print_age_or_dash(m, valid: s & RQ_LOCAL_PENDING, dt: now - req->pre_submit_jif); |
111 | |
112 | #define RQ_HDR_3 "\tsent\tacked\tdone" |
113 | seq_print_age_or_dash(m, valid: s & RQ_NET_SENT, dt: now - req->pre_send_jif); |
114 | seq_print_age_or_dash(m, valid: (s & RQ_NET_SENT) && !(s & RQ_NET_PENDING), dt: now - req->acked_jif); |
115 | seq_print_age_or_dash(m, valid: s & RQ_NET_DONE, dt: now - req->net_done_jif); |
116 | |
117 | #define RQ_HDR_4 "\tstate\n" |
118 | seq_print_request_state(m, req); |
119 | } |
120 | #define RQ_HDR RQ_HDR_1 RQ_HDR_2 RQ_HDR_3 RQ_HDR_4 |
121 | |
122 | static void seq_print_minor_vnr_req(struct seq_file *m, struct drbd_request *req, unsigned long now) |
123 | { |
124 | seq_printf(m, fmt: "%u\t%u\t" , req->device->minor, req->device->vnr); |
125 | seq_print_one_request(m, req, now); |
126 | } |
127 | |
128 | static void seq_print_resource_pending_meta_io(struct seq_file *m, struct drbd_resource *resource, unsigned long now) |
129 | { |
130 | struct drbd_device *device; |
131 | unsigned int i; |
132 | |
133 | seq_puts(m, s: "minor\tvnr\tstart\tsubmit\tintent\n" ); |
134 | rcu_read_lock(); |
135 | idr_for_each_entry(&resource->devices, device, i) { |
136 | struct drbd_md_io tmp; |
137 | /* In theory this is racy, |
138 | * in the sense that there could have been a |
139 | * drbd_md_put_buffer(); drbd_md_get_buffer(); |
140 | * between accessing these members here. */ |
141 | tmp = device->md_io; |
142 | if (atomic_read(v: &tmp.in_use)) { |
143 | seq_printf(m, fmt: "%u\t%u\t%d\t" , |
144 | device->minor, device->vnr, |
145 | jiffies_to_msecs(j: now - tmp.start_jif)); |
146 | if (time_before(tmp.submit_jif, tmp.start_jif)) |
147 | seq_puts(m, s: "-\t" ); |
148 | else |
149 | seq_printf(m, fmt: "%d\t" , jiffies_to_msecs(j: now - tmp.submit_jif)); |
150 | seq_printf(m, fmt: "%s\n" , tmp.current_use); |
151 | } |
152 | } |
153 | rcu_read_unlock(); |
154 | } |
155 | |
156 | static void seq_print_waiting_for_AL(struct seq_file *m, struct drbd_resource *resource, unsigned long now) |
157 | { |
158 | struct drbd_device *device; |
159 | unsigned int i; |
160 | |
161 | seq_puts(m, s: "minor\tvnr\tage\t#waiting\n" ); |
162 | rcu_read_lock(); |
163 | idr_for_each_entry(&resource->devices, device, i) { |
164 | unsigned long jif; |
165 | struct drbd_request *req; |
166 | int n = atomic_read(v: &device->ap_actlog_cnt); |
167 | if (n) { |
168 | spin_lock_irq(lock: &device->resource->req_lock); |
169 | req = list_first_entry_or_null(&device->pending_master_completion[1], |
170 | struct drbd_request, req_pending_master_completion); |
171 | /* if the oldest request does not wait for the activity log |
172 | * it is not interesting for us here */ |
173 | if (req && !(req->rq_state & RQ_IN_ACT_LOG)) |
174 | jif = req->start_jif; |
175 | else |
176 | req = NULL; |
177 | spin_unlock_irq(lock: &device->resource->req_lock); |
178 | } |
179 | if (n) { |
180 | seq_printf(m, fmt: "%u\t%u\t" , device->minor, device->vnr); |
181 | if (req) |
182 | seq_printf(m, fmt: "%u\t" , jiffies_to_msecs(j: now - jif)); |
183 | else |
184 | seq_puts(m, s: "-\t" ); |
185 | seq_printf(m, fmt: "%u\n" , n); |
186 | } |
187 | } |
188 | rcu_read_unlock(); |
189 | } |
190 | |
191 | static void seq_print_device_bitmap_io(struct seq_file *m, struct drbd_device *device, unsigned long now) |
192 | { |
193 | struct drbd_bm_aio_ctx *ctx; |
194 | unsigned long start_jif; |
195 | unsigned int in_flight; |
196 | unsigned int flags; |
197 | spin_lock_irq(lock: &device->resource->req_lock); |
198 | ctx = list_first_entry_or_null(&device->pending_bitmap_io, struct drbd_bm_aio_ctx, list); |
199 | if (ctx && ctx->done) |
200 | ctx = NULL; |
201 | if (ctx) { |
202 | start_jif = ctx->start_jif; |
203 | in_flight = atomic_read(v: &ctx->in_flight); |
204 | flags = ctx->flags; |
205 | } |
206 | spin_unlock_irq(lock: &device->resource->req_lock); |
207 | if (ctx) { |
208 | seq_printf(m, fmt: "%u\t%u\t%c\t%u\t%u\n" , |
209 | device->minor, device->vnr, |
210 | (flags & BM_AIO_READ) ? 'R' : 'W', |
211 | jiffies_to_msecs(j: now - start_jif), |
212 | in_flight); |
213 | } |
214 | } |
215 | |
216 | static void seq_print_resource_pending_bitmap_io(struct seq_file *m, struct drbd_resource *resource, unsigned long now) |
217 | { |
218 | struct drbd_device *device; |
219 | unsigned int i; |
220 | |
221 | seq_puts(m, s: "minor\tvnr\trw\tage\t#in-flight\n" ); |
222 | rcu_read_lock(); |
223 | idr_for_each_entry(&resource->devices, device, i) { |
224 | seq_print_device_bitmap_io(m, device, now); |
225 | } |
226 | rcu_read_unlock(); |
227 | } |
228 | |
229 | /* pretty print enum peer_req->flags */ |
230 | static void seq_print_peer_request_flags(struct seq_file *m, struct drbd_peer_request *peer_req) |
231 | { |
232 | unsigned long f = peer_req->flags; |
233 | char sep = ' '; |
234 | |
235 | __seq_print_rq_state_bit(m, is_set: f & EE_SUBMITTED, sep: &sep, set_name: "submitted" , unset_name: "preparing" ); |
236 | __seq_print_rq_state_bit(m, is_set: f & EE_APPLICATION, sep: &sep, set_name: "application" , unset_name: "internal" ); |
237 | seq_print_rq_state_bit(m, is_set: f & EE_CALL_AL_COMPLETE_IO, sep: &sep, set_name: "in-AL" ); |
238 | seq_print_rq_state_bit(m, is_set: f & EE_SEND_WRITE_ACK, sep: &sep, set_name: "C" ); |
239 | seq_print_rq_state_bit(m, is_set: f & EE_MAY_SET_IN_SYNC, sep: &sep, set_name: "set-in-sync" ); |
240 | seq_print_rq_state_bit(m, is_set: f & EE_TRIM, sep: &sep, set_name: "trim" ); |
241 | seq_print_rq_state_bit(m, is_set: f & EE_ZEROOUT, sep: &sep, set_name: "zero-out" ); |
242 | seq_print_rq_state_bit(m, is_set: f & EE_WRITE_SAME, sep: &sep, set_name: "write-same" ); |
243 | seq_putc(m, c: '\n'); |
244 | } |
245 | |
246 | static void seq_print_peer_request(struct seq_file *m, |
247 | struct drbd_device *device, struct list_head *lh, |
248 | unsigned long now) |
249 | { |
250 | bool reported_preparing = false; |
251 | struct drbd_peer_request *peer_req; |
252 | list_for_each_entry(peer_req, lh, w.list) { |
253 | if (reported_preparing && !(peer_req->flags & EE_SUBMITTED)) |
254 | continue; |
255 | |
256 | if (device) |
257 | seq_printf(m, fmt: "%u\t%u\t" , device->minor, device->vnr); |
258 | |
259 | seq_printf(m, fmt: "%llu\t%u\t%c\t%u\t" , |
260 | (unsigned long long)peer_req->i.sector, peer_req->i.size >> 9, |
261 | (peer_req->flags & EE_WRITE) ? 'W' : 'R', |
262 | jiffies_to_msecs(j: now - peer_req->submit_jif)); |
263 | seq_print_peer_request_flags(m, peer_req); |
264 | if (peer_req->flags & EE_SUBMITTED) |
265 | break; |
266 | else |
267 | reported_preparing = true; |
268 | } |
269 | } |
270 | |
271 | static void seq_print_device_peer_requests(struct seq_file *m, |
272 | struct drbd_device *device, unsigned long now) |
273 | { |
274 | seq_puts(m, s: "minor\tvnr\tsector\tsize\trw\tage\tflags\n" ); |
275 | spin_lock_irq(lock: &device->resource->req_lock); |
276 | seq_print_peer_request(m, device, lh: &device->active_ee, now); |
277 | seq_print_peer_request(m, device, lh: &device->read_ee, now); |
278 | seq_print_peer_request(m, device, lh: &device->sync_ee, now); |
279 | spin_unlock_irq(lock: &device->resource->req_lock); |
280 | if (test_bit(FLUSH_PENDING, &device->flags)) { |
281 | seq_printf(m, fmt: "%u\t%u\t-\t-\tF\t%u\tflush\n" , |
282 | device->minor, device->vnr, |
283 | jiffies_to_msecs(j: now - device->flush_jif)); |
284 | } |
285 | } |
286 | |
287 | static void seq_print_resource_pending_peer_requests(struct seq_file *m, |
288 | struct drbd_resource *resource, unsigned long now) |
289 | { |
290 | struct drbd_device *device; |
291 | unsigned int i; |
292 | |
293 | rcu_read_lock(); |
294 | idr_for_each_entry(&resource->devices, device, i) { |
295 | seq_print_device_peer_requests(m, device, now); |
296 | } |
297 | rcu_read_unlock(); |
298 | } |
299 | |
300 | static void seq_print_resource_transfer_log_summary(struct seq_file *m, |
301 | struct drbd_resource *resource, |
302 | struct drbd_connection *connection, |
303 | unsigned long now) |
304 | { |
305 | struct drbd_request *req; |
306 | unsigned int count = 0; |
307 | unsigned int show_state = 0; |
308 | |
309 | seq_puts(m, s: "n\tdevice\tvnr\t" RQ_HDR); |
310 | spin_lock_irq(lock: &resource->req_lock); |
311 | list_for_each_entry(req, &connection->transfer_log, tl_requests) { |
312 | unsigned int tmp = 0; |
313 | unsigned int s; |
314 | ++count; |
315 | |
316 | /* don't disable irq "forever" */ |
317 | if (!(count & 0x1ff)) { |
318 | struct drbd_request *req_next; |
319 | kref_get(kref: &req->kref); |
320 | spin_unlock_irq(lock: &resource->req_lock); |
321 | cond_resched(); |
322 | spin_lock_irq(lock: &resource->req_lock); |
323 | req_next = list_next_entry(req, tl_requests); |
324 | if (kref_put(kref: &req->kref, release: drbd_req_destroy)) |
325 | req = req_next; |
326 | if (&req->tl_requests == &connection->transfer_log) |
327 | break; |
328 | } |
329 | |
330 | s = req->rq_state; |
331 | |
332 | /* This is meant to summarize timing issues, to be able to tell |
333 | * local disk problems from network problems. |
334 | * Skip requests, if we have shown an even older request with |
335 | * similar aspects already. */ |
336 | if (req->master_bio == NULL) |
337 | tmp |= 1; |
338 | if ((s & RQ_LOCAL_MASK) && (s & RQ_LOCAL_PENDING)) |
339 | tmp |= 2; |
340 | if (s & RQ_NET_MASK) { |
341 | if (!(s & RQ_NET_SENT)) |
342 | tmp |= 4; |
343 | if (s & RQ_NET_PENDING) |
344 | tmp |= 8; |
345 | if (!(s & RQ_NET_DONE)) |
346 | tmp |= 16; |
347 | } |
348 | if ((tmp & show_state) == tmp) |
349 | continue; |
350 | show_state |= tmp; |
351 | seq_printf(m, fmt: "%u\t" , count); |
352 | seq_print_minor_vnr_req(m, req, now); |
353 | if (show_state == 0x1f) |
354 | break; |
355 | } |
356 | spin_unlock_irq(lock: &resource->req_lock); |
357 | } |
358 | |
359 | /* TODO: transfer_log and friends should be moved to resource */ |
360 | static int in_flight_summary_show(struct seq_file *m, void *pos) |
361 | { |
362 | struct drbd_resource *resource = m->private; |
363 | struct drbd_connection *connection; |
364 | unsigned long jif = jiffies; |
365 | |
366 | connection = first_connection(resource); |
367 | /* This does not happen, actually. |
368 | * But be robust and prepare for future code changes. */ |
369 | if (!connection || !kref_get_unless_zero(kref: &connection->kref)) |
370 | return -ESTALE; |
371 | |
372 | /* BUMP me if you change the file format/content/presentation */ |
373 | seq_printf(m, fmt: "v: %u\n\n" , 0); |
374 | |
375 | seq_puts(m, s: "oldest bitmap IO\n" ); |
376 | seq_print_resource_pending_bitmap_io(m, resource, now: jif); |
377 | seq_putc(m, c: '\n'); |
378 | |
379 | seq_puts(m, s: "meta data IO\n" ); |
380 | seq_print_resource_pending_meta_io(m, resource, now: jif); |
381 | seq_putc(m, c: '\n'); |
382 | |
383 | seq_puts(m, s: "socket buffer stats\n" ); |
384 | /* for each connection ... once we have more than one */ |
385 | rcu_read_lock(); |
386 | if (connection->data.socket) { |
387 | /* open coded SIOCINQ, the "relevant" part */ |
388 | struct tcp_sock *tp = tcp_sk(connection->data.socket->sk); |
389 | int answ = tp->rcv_nxt - tp->copied_seq; |
390 | seq_printf(m, fmt: "unread receive buffer: %u Byte\n" , answ); |
391 | /* open coded SIOCOUTQ, the "relevant" part */ |
392 | answ = tp->write_seq - tp->snd_una; |
393 | seq_printf(m, fmt: "unacked send buffer: %u Byte\n" , answ); |
394 | } |
395 | rcu_read_unlock(); |
396 | seq_putc(m, c: '\n'); |
397 | |
398 | seq_puts(m, s: "oldest peer requests\n" ); |
399 | seq_print_resource_pending_peer_requests(m, resource, now: jif); |
400 | seq_putc(m, c: '\n'); |
401 | |
402 | seq_puts(m, s: "application requests waiting for activity log\n" ); |
403 | seq_print_waiting_for_AL(m, resource, now: jif); |
404 | seq_putc(m, c: '\n'); |
405 | |
406 | seq_puts(m, s: "oldest application requests\n" ); |
407 | seq_print_resource_transfer_log_summary(m, resource, connection, now: jif); |
408 | seq_putc(m, c: '\n'); |
409 | |
410 | jif = jiffies - jif; |
411 | if (jif) |
412 | seq_printf(m, fmt: "generated in %d ms\n" , jiffies_to_msecs(j: jif)); |
413 | kref_put(kref: &connection->kref, release: drbd_destroy_connection); |
414 | return 0; |
415 | } |
416 | |
417 | /* make sure at *open* time that the respective object won't go away. */ |
418 | static int drbd_single_open(struct file *file, int (*show)(struct seq_file *, void *), |
419 | void *data, struct kref *kref, |
420 | void (*release)(struct kref *)) |
421 | { |
422 | struct dentry *parent; |
423 | int ret = -ESTALE; |
424 | |
425 | /* Are we still linked, |
426 | * or has debugfs_remove() already been called? */ |
427 | parent = file->f_path.dentry->d_parent; |
428 | /* serialize with d_delete() */ |
429 | inode_lock(inode: d_inode(dentry: parent)); |
430 | /* Make sure the object is still alive */ |
431 | if (simple_positive(dentry: file->f_path.dentry) |
432 | && kref_get_unless_zero(kref)) |
433 | ret = 0; |
434 | inode_unlock(inode: d_inode(dentry: parent)); |
435 | if (!ret) { |
436 | ret = single_open(file, show, data); |
437 | if (ret) |
438 | kref_put(kref, release); |
439 | } |
440 | return ret; |
441 | } |
442 | |
443 | static int in_flight_summary_open(struct inode *inode, struct file *file) |
444 | { |
445 | struct drbd_resource *resource = inode->i_private; |
446 | return drbd_single_open(file, show: in_flight_summary_show, data: resource, |
447 | kref: &resource->kref, release: drbd_destroy_resource); |
448 | } |
449 | |
450 | static int in_flight_summary_release(struct inode *inode, struct file *file) |
451 | { |
452 | struct drbd_resource *resource = inode->i_private; |
453 | kref_put(kref: &resource->kref, release: drbd_destroy_resource); |
454 | return single_release(inode, file); |
455 | } |
456 | |
457 | static const struct file_operations in_flight_summary_fops = { |
458 | .owner = THIS_MODULE, |
459 | .open = in_flight_summary_open, |
460 | .read = seq_read, |
461 | .llseek = seq_lseek, |
462 | .release = in_flight_summary_release, |
463 | }; |
464 | |
465 | void drbd_debugfs_resource_add(struct drbd_resource *resource) |
466 | { |
467 | struct dentry *dentry; |
468 | |
469 | dentry = debugfs_create_dir(name: resource->name, parent: drbd_debugfs_resources); |
470 | resource->debugfs_res = dentry; |
471 | |
472 | dentry = debugfs_create_dir(name: "volumes" , parent: resource->debugfs_res); |
473 | resource->debugfs_res_volumes = dentry; |
474 | |
475 | dentry = debugfs_create_dir(name: "connections" , parent: resource->debugfs_res); |
476 | resource->debugfs_res_connections = dentry; |
477 | |
478 | dentry = debugfs_create_file(name: "in_flight_summary" , mode: 0440, |
479 | parent: resource->debugfs_res, data: resource, |
480 | fops: &in_flight_summary_fops); |
481 | resource->debugfs_res_in_flight_summary = dentry; |
482 | } |
483 | |
484 | static void drbd_debugfs_remove(struct dentry **dp) |
485 | { |
486 | debugfs_remove(dentry: *dp); |
487 | *dp = NULL; |
488 | } |
489 | |
490 | void drbd_debugfs_resource_cleanup(struct drbd_resource *resource) |
491 | { |
492 | /* it is ok to call debugfs_remove(NULL) */ |
493 | drbd_debugfs_remove(dp: &resource->debugfs_res_in_flight_summary); |
494 | drbd_debugfs_remove(dp: &resource->debugfs_res_connections); |
495 | drbd_debugfs_remove(dp: &resource->debugfs_res_volumes); |
496 | drbd_debugfs_remove(dp: &resource->debugfs_res); |
497 | } |
498 | |
499 | static void seq_print_one_timing_detail(struct seq_file *m, |
500 | const struct drbd_thread_timing_details *tdp, |
501 | unsigned long now) |
502 | { |
503 | struct drbd_thread_timing_details td; |
504 | /* No locking... |
505 | * use temporary assignment to get at consistent data. */ |
506 | do { |
507 | td = *tdp; |
508 | } while (td.cb_nr != tdp->cb_nr); |
509 | if (!td.cb_addr) |
510 | return; |
511 | seq_printf(m, fmt: "%u\t%d\t%s:%u\t%ps\n" , |
512 | td.cb_nr, |
513 | jiffies_to_msecs(j: now - td.start_jif), |
514 | td.caller_fn, td.line, |
515 | td.cb_addr); |
516 | } |
517 | |
518 | static void seq_print_timing_details(struct seq_file *m, |
519 | const char *title, |
520 | unsigned int cb_nr, struct drbd_thread_timing_details *tdp, unsigned long now) |
521 | { |
522 | unsigned int start_idx; |
523 | unsigned int i; |
524 | |
525 | seq_printf(m, fmt: "%s\n" , title); |
526 | /* If not much is going on, this will result in natural ordering. |
527 | * If it is very busy, we will possibly skip events, or even see wrap |
528 | * arounds, which could only be avoided with locking. |
529 | */ |
530 | start_idx = cb_nr % DRBD_THREAD_DETAILS_HIST; |
531 | for (i = start_idx; i < DRBD_THREAD_DETAILS_HIST; i++) |
532 | seq_print_one_timing_detail(m, tdp: tdp+i, now); |
533 | for (i = 0; i < start_idx; i++) |
534 | seq_print_one_timing_detail(m, tdp: tdp+i, now); |
535 | } |
536 | |
537 | static int callback_history_show(struct seq_file *m, void *ignored) |
538 | { |
539 | struct drbd_connection *connection = m->private; |
540 | unsigned long jif = jiffies; |
541 | |
542 | /* BUMP me if you change the file format/content/presentation */ |
543 | seq_printf(m, fmt: "v: %u\n\n" , 0); |
544 | |
545 | seq_puts(m, s: "n\tage\tcallsite\tfn\n" ); |
546 | seq_print_timing_details(m, title: "worker" , cb_nr: connection->w_cb_nr, tdp: connection->w_timing_details, now: jif); |
547 | seq_print_timing_details(m, title: "receiver" , cb_nr: connection->r_cb_nr, tdp: connection->r_timing_details, now: jif); |
548 | return 0; |
549 | } |
550 | |
551 | static int callback_history_open(struct inode *inode, struct file *file) |
552 | { |
553 | struct drbd_connection *connection = inode->i_private; |
554 | return drbd_single_open(file, show: callback_history_show, data: connection, |
555 | kref: &connection->kref, release: drbd_destroy_connection); |
556 | } |
557 | |
558 | static int callback_history_release(struct inode *inode, struct file *file) |
559 | { |
560 | struct drbd_connection *connection = inode->i_private; |
561 | kref_put(kref: &connection->kref, release: drbd_destroy_connection); |
562 | return single_release(inode, file); |
563 | } |
564 | |
565 | static const struct file_operations connection_callback_history_fops = { |
566 | .owner = THIS_MODULE, |
567 | .open = callback_history_open, |
568 | .read = seq_read, |
569 | .llseek = seq_lseek, |
570 | .release = callback_history_release, |
571 | }; |
572 | |
573 | static int connection_oldest_requests_show(struct seq_file *m, void *ignored) |
574 | { |
575 | struct drbd_connection *connection = m->private; |
576 | unsigned long now = jiffies; |
577 | struct drbd_request *r1, *r2; |
578 | |
579 | /* BUMP me if you change the file format/content/presentation */ |
580 | seq_printf(m, fmt: "v: %u\n\n" , 0); |
581 | |
582 | spin_lock_irq(lock: &connection->resource->req_lock); |
583 | r1 = connection->req_next; |
584 | if (r1) |
585 | seq_print_minor_vnr_req(m, req: r1, now); |
586 | r2 = connection->req_ack_pending; |
587 | if (r2 && r2 != r1) { |
588 | r1 = r2; |
589 | seq_print_minor_vnr_req(m, req: r1, now); |
590 | } |
591 | r2 = connection->req_not_net_done; |
592 | if (r2 && r2 != r1) |
593 | seq_print_minor_vnr_req(m, req: r2, now); |
594 | spin_unlock_irq(lock: &connection->resource->req_lock); |
595 | return 0; |
596 | } |
597 | |
598 | static int connection_oldest_requests_open(struct inode *inode, struct file *file) |
599 | { |
600 | struct drbd_connection *connection = inode->i_private; |
601 | return drbd_single_open(file, show: connection_oldest_requests_show, data: connection, |
602 | kref: &connection->kref, release: drbd_destroy_connection); |
603 | } |
604 | |
605 | static int connection_oldest_requests_release(struct inode *inode, struct file *file) |
606 | { |
607 | struct drbd_connection *connection = inode->i_private; |
608 | kref_put(kref: &connection->kref, release: drbd_destroy_connection); |
609 | return single_release(inode, file); |
610 | } |
611 | |
612 | static const struct file_operations connection_oldest_requests_fops = { |
613 | .owner = THIS_MODULE, |
614 | .open = connection_oldest_requests_open, |
615 | .read = seq_read, |
616 | .llseek = seq_lseek, |
617 | .release = connection_oldest_requests_release, |
618 | }; |
619 | |
620 | void drbd_debugfs_connection_add(struct drbd_connection *connection) |
621 | { |
622 | struct dentry *conns_dir = connection->resource->debugfs_res_connections; |
623 | struct dentry *dentry; |
624 | |
625 | /* Once we enable mutliple peers, |
626 | * these connections will have descriptive names. |
627 | * For now, it is just the one connection to the (only) "peer". */ |
628 | dentry = debugfs_create_dir(name: "peer" , parent: conns_dir); |
629 | connection->debugfs_conn = dentry; |
630 | |
631 | dentry = debugfs_create_file(name: "callback_history" , mode: 0440, |
632 | parent: connection->debugfs_conn, data: connection, |
633 | fops: &connection_callback_history_fops); |
634 | connection->debugfs_conn_callback_history = dentry; |
635 | |
636 | dentry = debugfs_create_file(name: "oldest_requests" , mode: 0440, |
637 | parent: connection->debugfs_conn, data: connection, |
638 | fops: &connection_oldest_requests_fops); |
639 | connection->debugfs_conn_oldest_requests = dentry; |
640 | } |
641 | |
642 | void drbd_debugfs_connection_cleanup(struct drbd_connection *connection) |
643 | { |
644 | drbd_debugfs_remove(dp: &connection->debugfs_conn_callback_history); |
645 | drbd_debugfs_remove(dp: &connection->debugfs_conn_oldest_requests); |
646 | drbd_debugfs_remove(dp: &connection->debugfs_conn); |
647 | } |
648 | |
649 | static void resync_dump_detail(struct seq_file *m, struct lc_element *e) |
650 | { |
651 | struct bm_extent *bme = lc_entry(e, struct bm_extent, lce); |
652 | |
653 | seq_printf(m, fmt: "%5d %s %s %s" , bme->rs_left, |
654 | test_bit(BME_NO_WRITES, &bme->flags) ? "NO_WRITES" : "---------" , |
655 | test_bit(BME_LOCKED, &bme->flags) ? "LOCKED" : "------" , |
656 | test_bit(BME_PRIORITY, &bme->flags) ? "PRIORITY" : "--------" |
657 | ); |
658 | } |
659 | |
660 | static int device_resync_extents_show(struct seq_file *m, void *ignored) |
661 | { |
662 | struct drbd_device *device = m->private; |
663 | |
664 | /* BUMP me if you change the file format/content/presentation */ |
665 | seq_printf(m, fmt: "v: %u\n\n" , 0); |
666 | |
667 | if (get_ldev_if_state(device, D_FAILED)) { |
668 | lc_seq_printf_stats(seq: m, lc: device->resync); |
669 | lc_seq_dump_details(seq: m, lc: device->resync, utext: "rs_left flags" , detail: resync_dump_detail); |
670 | put_ldev(device); |
671 | } |
672 | return 0; |
673 | } |
674 | |
675 | static int device_act_log_extents_show(struct seq_file *m, void *ignored) |
676 | { |
677 | struct drbd_device *device = m->private; |
678 | |
679 | /* BUMP me if you change the file format/content/presentation */ |
680 | seq_printf(m, fmt: "v: %u\n\n" , 0); |
681 | |
682 | if (get_ldev_if_state(device, D_FAILED)) { |
683 | lc_seq_printf_stats(seq: m, lc: device->act_log); |
684 | lc_seq_dump_details(seq: m, lc: device->act_log, utext: "" , NULL); |
685 | put_ldev(device); |
686 | } |
687 | return 0; |
688 | } |
689 | |
690 | static int device_oldest_requests_show(struct seq_file *m, void *ignored) |
691 | { |
692 | struct drbd_device *device = m->private; |
693 | struct drbd_resource *resource = device->resource; |
694 | unsigned long now = jiffies; |
695 | struct drbd_request *r1, *r2; |
696 | int i; |
697 | |
698 | /* BUMP me if you change the file format/content/presentation */ |
699 | seq_printf(m, fmt: "v: %u\n\n" , 0); |
700 | |
701 | seq_puts(m, RQ_HDR); |
702 | spin_lock_irq(lock: &resource->req_lock); |
703 | /* WRITE, then READ */ |
704 | for (i = 1; i >= 0; --i) { |
705 | r1 = list_first_entry_or_null(&device->pending_master_completion[i], |
706 | struct drbd_request, req_pending_master_completion); |
707 | r2 = list_first_entry_or_null(&device->pending_completion[i], |
708 | struct drbd_request, req_pending_local); |
709 | if (r1) |
710 | seq_print_one_request(m, req: r1, now); |
711 | if (r2 && r2 != r1) |
712 | seq_print_one_request(m, req: r2, now); |
713 | } |
714 | spin_unlock_irq(lock: &resource->req_lock); |
715 | return 0; |
716 | } |
717 | |
718 | static int device_data_gen_id_show(struct seq_file *m, void *ignored) |
719 | { |
720 | struct drbd_device *device = m->private; |
721 | struct drbd_md *md; |
722 | enum drbd_uuid_index idx; |
723 | |
724 | if (!get_ldev_if_state(device, D_FAILED)) |
725 | return -ENODEV; |
726 | |
727 | md = &device->ldev->md; |
728 | spin_lock_irq(lock: &md->uuid_lock); |
729 | for (idx = UI_CURRENT; idx <= UI_HISTORY_END; idx++) { |
730 | seq_printf(m, fmt: "0x%016llX\n" , md->uuid[idx]); |
731 | } |
732 | spin_unlock_irq(lock: &md->uuid_lock); |
733 | put_ldev(device); |
734 | return 0; |
735 | } |
736 | |
737 | static int device_ed_gen_id_show(struct seq_file *m, void *ignored) |
738 | { |
739 | struct drbd_device *device = m->private; |
740 | seq_printf(m, fmt: "0x%016llX\n" , (unsigned long long)device->ed_uuid); |
741 | return 0; |
742 | } |
743 | |
744 | #define drbd_debugfs_device_attr(name) \ |
745 | static int device_ ## name ## _open(struct inode *inode, struct file *file) \ |
746 | { \ |
747 | struct drbd_device *device = inode->i_private; \ |
748 | return drbd_single_open(file, device_ ## name ## _show, device, \ |
749 | &device->kref, drbd_destroy_device); \ |
750 | } \ |
751 | static int device_ ## name ## _release(struct inode *inode, struct file *file) \ |
752 | { \ |
753 | struct drbd_device *device = inode->i_private; \ |
754 | kref_put(&device->kref, drbd_destroy_device); \ |
755 | return single_release(inode, file); \ |
756 | } \ |
757 | static const struct file_operations device_ ## name ## _fops = { \ |
758 | .owner = THIS_MODULE, \ |
759 | .open = device_ ## name ## _open, \ |
760 | .read = seq_read, \ |
761 | .llseek = seq_lseek, \ |
762 | .release = device_ ## name ## _release, \ |
763 | }; |
764 | |
765 | drbd_debugfs_device_attr(oldest_requests) |
766 | drbd_debugfs_device_attr(act_log_extents) |
767 | drbd_debugfs_device_attr(resync_extents) |
768 | drbd_debugfs_device_attr(data_gen_id) |
769 | drbd_debugfs_device_attr(ed_gen_id) |
770 | |
771 | void drbd_debugfs_device_add(struct drbd_device *device) |
772 | { |
773 | struct dentry *vols_dir = device->resource->debugfs_res_volumes; |
774 | char minor_buf[8]; /* MINORMASK, MINORBITS == 20; */ |
775 | char vnr_buf[8]; /* volume number vnr is even 16 bit only; */ |
776 | char *slink_name = NULL; |
777 | |
778 | struct dentry *dentry; |
779 | if (!vols_dir || !drbd_debugfs_minors) |
780 | return; |
781 | |
782 | snprintf(buf: vnr_buf, size: sizeof(vnr_buf), fmt: "%u" , device->vnr); |
783 | dentry = debugfs_create_dir(name: vnr_buf, parent: vols_dir); |
784 | device->debugfs_vol = dentry; |
785 | |
786 | snprintf(buf: minor_buf, size: sizeof(minor_buf), fmt: "%u" , device->minor); |
787 | slink_name = kasprintf(GFP_KERNEL, fmt: "../resources/%s/volumes/%u" , |
788 | device->resource->name, device->vnr); |
789 | if (!slink_name) |
790 | goto fail; |
791 | dentry = debugfs_create_symlink(name: minor_buf, parent: drbd_debugfs_minors, dest: slink_name); |
792 | device->debugfs_minor = dentry; |
793 | kfree(objp: slink_name); |
794 | slink_name = NULL; |
795 | |
796 | #define DCF(name) do { \ |
797 | dentry = debugfs_create_file(#name, 0440, \ |
798 | device->debugfs_vol, device, \ |
799 | &device_ ## name ## _fops); \ |
800 | device->debugfs_vol_ ## name = dentry; \ |
801 | } while (0) |
802 | |
803 | DCF(oldest_requests); |
804 | DCF(act_log_extents); |
805 | DCF(resync_extents); |
806 | DCF(data_gen_id); |
807 | DCF(ed_gen_id); |
808 | #undef DCF |
809 | return; |
810 | |
811 | fail: |
812 | drbd_debugfs_device_cleanup(device); |
813 | drbd_err(device, "failed to create debugfs entries\n" ); |
814 | } |
815 | |
816 | void drbd_debugfs_device_cleanup(struct drbd_device *device) |
817 | { |
818 | drbd_debugfs_remove(dp: &device->debugfs_minor); |
819 | drbd_debugfs_remove(dp: &device->debugfs_vol_oldest_requests); |
820 | drbd_debugfs_remove(dp: &device->debugfs_vol_act_log_extents); |
821 | drbd_debugfs_remove(dp: &device->debugfs_vol_resync_extents); |
822 | drbd_debugfs_remove(dp: &device->debugfs_vol_data_gen_id); |
823 | drbd_debugfs_remove(dp: &device->debugfs_vol_ed_gen_id); |
824 | drbd_debugfs_remove(dp: &device->debugfs_vol); |
825 | } |
826 | |
827 | void drbd_debugfs_peer_device_add(struct drbd_peer_device *peer_device) |
828 | { |
829 | struct dentry *conn_dir = peer_device->connection->debugfs_conn; |
830 | struct dentry *dentry; |
831 | char vnr_buf[8]; |
832 | |
833 | snprintf(buf: vnr_buf, size: sizeof(vnr_buf), fmt: "%u" , peer_device->device->vnr); |
834 | dentry = debugfs_create_dir(name: vnr_buf, parent: conn_dir); |
835 | peer_device->debugfs_peer_dev = dentry; |
836 | } |
837 | |
838 | void drbd_debugfs_peer_device_cleanup(struct drbd_peer_device *peer_device) |
839 | { |
840 | drbd_debugfs_remove(dp: &peer_device->debugfs_peer_dev); |
841 | } |
842 | |
843 | static int drbd_version_show(struct seq_file *m, void *ignored) |
844 | { |
845 | seq_printf(m, fmt: "# %s\n" , drbd_buildtag()); |
846 | seq_printf(m, fmt: "VERSION=%s\n" , REL_VERSION); |
847 | seq_printf(m, fmt: "API_VERSION=%u\n" , GENL_MAGIC_VERSION); |
848 | seq_printf(m, fmt: "PRO_VERSION_MIN=%u\n" , PRO_VERSION_MIN); |
849 | seq_printf(m, fmt: "PRO_VERSION_MAX=%u\n" , PRO_VERSION_MAX); |
850 | return 0; |
851 | } |
852 | |
853 | static int drbd_version_open(struct inode *inode, struct file *file) |
854 | { |
855 | return single_open(file, drbd_version_show, NULL); |
856 | } |
857 | |
858 | static const struct file_operations drbd_version_fops = { |
859 | .owner = THIS_MODULE, |
860 | .open = drbd_version_open, |
861 | .llseek = seq_lseek, |
862 | .read = seq_read, |
863 | .release = single_release, |
864 | }; |
865 | |
866 | /* not __exit, may be indirectly called |
867 | * from the module-load-failure path as well. */ |
868 | void drbd_debugfs_cleanup(void) |
869 | { |
870 | drbd_debugfs_remove(dp: &drbd_debugfs_resources); |
871 | drbd_debugfs_remove(dp: &drbd_debugfs_minors); |
872 | drbd_debugfs_remove(dp: &drbd_debugfs_version); |
873 | drbd_debugfs_remove(dp: &drbd_debugfs_root); |
874 | } |
875 | |
876 | void __init drbd_debugfs_init(void) |
877 | { |
878 | struct dentry *dentry; |
879 | |
880 | dentry = debugfs_create_dir(name: "drbd" , NULL); |
881 | drbd_debugfs_root = dentry; |
882 | |
883 | dentry = debugfs_create_file(name: "version" , mode: 0444, parent: drbd_debugfs_root, NULL, fops: &drbd_version_fops); |
884 | drbd_debugfs_version = dentry; |
885 | |
886 | dentry = debugfs_create_dir(name: "resources" , parent: drbd_debugfs_root); |
887 | drbd_debugfs_resources = dentry; |
888 | |
889 | dentry = debugfs_create_dir(name: "minors" , parent: drbd_debugfs_root); |
890 | drbd_debugfs_minors = dentry; |
891 | } |
892 | |