Merge branch 'core-fixes-for-linus' of git://git.kernel.org/pub/scm/linux/kernel...
[linux-2.6.git] / kernel / slow-work-debugfs.c
1 /* Slow work debugging
2  *
3  * Copyright (C) 2009 Red Hat, Inc. All Rights Reserved.
4  * Written by David Howells (dhowells@redhat.com)
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public Licence
8  * as published by the Free Software Foundation; either version
9  * 2 of the Licence, or (at your option) any later version.
10  */
11
12 #include <linux/module.h>
13 #include <linux/slow-work.h>
14 #include <linux/fs.h>
15 #include <linux/time.h>
16 #include <linux/seq_file.h>
17 #include "slow-work.h"
18
19 #define ITERATOR_SHIFT          (BITS_PER_LONG - 4)
20 #define ITERATOR_SELECTOR       (0xfUL << ITERATOR_SHIFT)
21 #define ITERATOR_COUNTER        (~ITERATOR_SELECTOR)
22
23 void slow_work_new_thread_desc(struct slow_work *work, struct seq_file *m)
24 {
25         seq_puts(m, "Slow-work: New thread");
26 }
27
28 /*
29  * Render the time mark field on a work item into a 5-char time with units plus
30  * a space
31  */
32 static void slow_work_print_mark(struct seq_file *m, struct slow_work *work)
33 {
34         struct timespec now, diff;
35
36         now = CURRENT_TIME;
37         diff = timespec_sub(now, work->mark);
38
39         if (diff.tv_sec < 0)
40                 seq_puts(m, "  -ve ");
41         else if (diff.tv_sec == 0 && diff.tv_nsec < 1000)
42                 seq_printf(m, "%3luns ", diff.tv_nsec);
43         else if (diff.tv_sec == 0 && diff.tv_nsec < 1000000)
44                 seq_printf(m, "%3luus ", diff.tv_nsec / 1000);
45         else if (diff.tv_sec == 0 && diff.tv_nsec < 1000000000)
46                 seq_printf(m, "%3lums ", diff.tv_nsec / 1000000);
47         else if (diff.tv_sec <= 1)
48                 seq_puts(m, "   1s ");
49         else if (diff.tv_sec < 60)
50                 seq_printf(m, "%4lus ", diff.tv_sec);
51         else if (diff.tv_sec < 60 * 60)
52                 seq_printf(m, "%4lum ", diff.tv_sec / 60);
53         else if (diff.tv_sec < 60 * 60 * 24)
54                 seq_printf(m, "%4luh ", diff.tv_sec / 3600);
55         else
56                 seq_puts(m, "exces ");
57 }
58
59 /*
60  * Describe a slow work item for debugfs
61  */
62 static int slow_work_runqueue_show(struct seq_file *m, void *v)
63 {
64         struct slow_work *work;
65         struct list_head *p = v;
66         unsigned long id;
67
68         switch ((unsigned long) v) {
69         case 1:
70                 seq_puts(m, "THR PID   ITEM ADDR        FL MARK  DESC\n");
71                 return 0;
72         case 2:
73                 seq_puts(m, "=== ===== ================ == ===== ==========\n");
74                 return 0;
75
76         case 3 ... 3 + SLOW_WORK_THREAD_LIMIT - 1:
77                 id = (unsigned long) v - 3;
78
79                 read_lock(&slow_work_execs_lock);
80                 work = slow_work_execs[id];
81                 if (work) {
82                         smp_read_barrier_depends();
83
84                         seq_printf(m, "%3lu %5d %16p %2lx ",
85                                    id, slow_work_pids[id], work, work->flags);
86                         slow_work_print_mark(m, work);
87
88                         if (work->ops->desc)
89                                 work->ops->desc(work, m);
90                         seq_putc(m, '\n');
91                 }
92                 read_unlock(&slow_work_execs_lock);
93                 return 0;
94
95         default:
96                 work = list_entry(p, struct slow_work, link);
97                 seq_printf(m, "%3s     - %16p %2lx ",
98                            work->flags & SLOW_WORK_VERY_SLOW ? "vsq" : "sq",
99                            work, work->flags);
100                 slow_work_print_mark(m, work);
101
102                 if (work->ops->desc)
103                         work->ops->desc(work, m);
104                 seq_putc(m, '\n');
105                 return 0;
106         }
107 }
108
109 /*
110  * map the iterator to a work item
111  */
112 static void *slow_work_runqueue_index(struct seq_file *m, loff_t *_pos)
113 {
114         struct list_head *p;
115         unsigned long count, id;
116
117         switch (*_pos >> ITERATOR_SHIFT) {
118         case 0x0:
119                 if (*_pos == 0)
120                         *_pos = 1;
121                 if (*_pos < 3)
122                         return (void *)(unsigned long) *_pos;
123                 if (*_pos < 3 + SLOW_WORK_THREAD_LIMIT)
124                         for (id = *_pos - 3;
125                              id < SLOW_WORK_THREAD_LIMIT;
126                              id++, (*_pos)++)
127                                 if (slow_work_execs[id])
128                                         return (void *)(unsigned long) *_pos;
129                 *_pos = 0x1UL << ITERATOR_SHIFT;
130
131         case 0x1:
132                 count = *_pos & ITERATOR_COUNTER;
133                 list_for_each(p, &slow_work_queue) {
134                         if (count == 0)
135                                 return p;
136                         count--;
137                 }
138                 *_pos = 0x2UL << ITERATOR_SHIFT;
139
140         case 0x2:
141                 count = *_pos & ITERATOR_COUNTER;
142                 list_for_each(p, &vslow_work_queue) {
143                         if (count == 0)
144                                 return p;
145                         count--;
146                 }
147                 *_pos = 0x3UL << ITERATOR_SHIFT;
148
149         default:
150                 return NULL;
151         }
152 }
153
154 /*
155  * set up the iterator to start reading from the first line
156  */
157 static void *slow_work_runqueue_start(struct seq_file *m, loff_t *_pos)
158 {
159         spin_lock_irq(&slow_work_queue_lock);
160         return slow_work_runqueue_index(m, _pos);
161 }
162
163 /*
164  * move to the next line
165  */
166 static void *slow_work_runqueue_next(struct seq_file *m, void *v, loff_t *_pos)
167 {
168         struct list_head *p = v;
169         unsigned long selector = *_pos >> ITERATOR_SHIFT;
170
171         (*_pos)++;
172         switch (selector) {
173         case 0x0:
174                 return slow_work_runqueue_index(m, _pos);
175
176         case 0x1:
177                 if (*_pos >> ITERATOR_SHIFT == 0x1) {
178                         p = p->next;
179                         if (p != &slow_work_queue)
180                                 return p;
181                 }
182                 *_pos = 0x2UL << ITERATOR_SHIFT;
183                 p = &vslow_work_queue;
184
185         case 0x2:
186                 if (*_pos >> ITERATOR_SHIFT == 0x2) {
187                         p = p->next;
188                         if (p != &vslow_work_queue)
189                                 return p;
190                 }
191                 *_pos = 0x3UL << ITERATOR_SHIFT;
192
193         default:
194                 return NULL;
195         }
196 }
197
198 /*
199  * clean up after reading
200  */
201 static void slow_work_runqueue_stop(struct seq_file *m, void *v)
202 {
203         spin_unlock_irq(&slow_work_queue_lock);
204 }
205
206 static const struct seq_operations slow_work_runqueue_ops = {
207         .start          = slow_work_runqueue_start,
208         .stop           = slow_work_runqueue_stop,
209         .next           = slow_work_runqueue_next,
210         .show           = slow_work_runqueue_show,
211 };
212
213 /*
214  * open "/sys/kernel/debug/slow_work/runqueue" to list queue contents
215  */
216 static int slow_work_runqueue_open(struct inode *inode, struct file *file)
217 {
218         return seq_open(file, &slow_work_runqueue_ops);
219 }
220
221 const struct file_operations slow_work_runqueue_fops = {
222         .owner          = THIS_MODULE,
223         .open           = slow_work_runqueue_open,
224         .read           = seq_read,
225         .llseek         = seq_lseek,
226         .release        = seq_release,
227 };