kernel - Add callout debugging
[dragonfly.git] / sys / kern / kern_collect.c
1 /*
2  * Copyright (c) 2017 The DragonFly Project.  All rights reserved.
3  *
4  * This code is derived from software contributed to The DragonFly Project
5  * by Matthew Dillon <dillon@backplane.com>
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in
15  *    the documentation and/or other materials provided with the
16  *    distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
21  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
22  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
23  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
24  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
28  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31
32 /*
33  * Collects general statistics on a 10-second interval.
34  */
35
36 #include <sys/param.h>
37 #include <sys/systm.h>
38 #include <sys/kernel.h>
39 #include <sys/proc.h>
40 #include <sys/kinfo.h>
41 #include <sys/queue.h>
42 #include <sys/sysctl.h>
43 #include <sys/kthread.h>
44 #include <machine/cpu.h>
45 #include <sys/lock.h>
46 #include <sys/spinlock.h>
47 #include <sys/kcollect.h>
48
49 #include <sys/thread2.h>
50 #include <sys/spinlock2.h>
51
52 #include <vm/vm.h>
53 #include <vm/vm_param.h>
54 #include <vm/vm_kern.h>
55 #include <vm/vm_object.h>
56 #include <vm/vm_page.h>
57 #include <vm/vm_map.h>
58 #include <vm/vm_pager.h>
59 #include <vm/vm_extern.h>
60
61 #include <machine/stdarg.h>
62 #include <machine/smp.h>
63 #include <machine/clock.h>
64
65 static uint32_t kcollect_samples = -1;  /* 0 to disable */
66 TUNABLE_INT("kern.collect_samples", &kcollect_samples);
67 SYSCTL_UINT(_kern, OID_AUTO, collect_samples, CTLFLAG_RD,
68         &kcollect_samples, 0, "number of 10-second samples");
69
70 static uint64_t kcollect_index;
71 static const char *kcollect_slots[KCOLLECT_ENTRIES];
72 static kcallback_t kcollect_callback[KCOLLECT_ENTRIES];
73 static kcollect_t kcollect_scale;
74 static kcollect_t *kcollect_ary;
75 static struct lock kcollect_lock = LOCK_INITIALIZER("kcolk", 0, 0);
76
77 MALLOC_DEFINE(M_KCOLLECT, "kcollect", "kcollect array");
78
79 int
80 kcollect_register(int which, const char *id, kcallback_t func, uint64_t scale)
81 {
82         int n;
83
84         lockmgr(&kcollect_lock, LK_EXCLUSIVE);
85         if (which >= 0) {
86                 n = which;
87         } else {
88                 for (n = KCOLLECT_DYNAMIC_START; n < KCOLLECT_ENTRIES; ++n) {
89                         if (kcollect_slots[n] == NULL)
90                                 break;
91                 }
92         }
93         if (n < 0 || n >= KCOLLECT_ENTRIES) {
94                 n = -1;
95         } else {
96                 kcollect_slots[n] = id;
97                 kcollect_callback[n] = func;
98                 kcollect_scale.data[n] = scale;
99         }
100         lockmgr(&kcollect_lock, LK_RELEASE);
101
102         return n;
103 }
104
105 void
106 kcollect_unregister(int n)
107 {
108         lockmgr(&kcollect_lock, LK_EXCLUSIVE);
109         if (n >= 0 && n < KCOLLECT_ENTRIES) {
110                 kcollect_slots[n] = NULL;
111                 kcollect_callback[n] = NULL;
112         }
113         lockmgr(&kcollect_lock, LK_RELEASE);
114 }
115
116 /*
117  * Typically called by a rollup function in the callback from the
118  * collection thread.  Not usually called ad-hoc.  This allows a
119  * subsystem to register several collection ids but only one callback
120  * which populates all of them.
121  */
122 void
123 kcollect_setvalue(int n, uint64_t value)
124 {
125         uint32_t i;
126
127         if (n >= 0 && n < KCOLLECT_ENTRIES) {
128                 i = kcollect_index % kcollect_samples;
129                 kcollect_ary[i].data[n] = value;
130         }
131 }
132
133 /*
134  * Callback to change scale adjustment, if necessary.  Certain statistics
135  * have scale info available (such as KCOLLECT_SWAPANO and SWAPCAC).
136  */
137 void
138 kcollect_setscale(int n, uint64_t value)
139 {
140         if (n >= 0 && n < KCOLLECT_ENTRIES) {
141                 kcollect_scale.data[n] = value;
142         }
143 }
144
145 static
146 void
147 kcollect_thread(void *dummy)
148 {
149         uint32_t i;
150         int n;
151
152         for (;;) {
153                 lockmgr(&kcollect_lock, LK_EXCLUSIVE);
154                 i = kcollect_index % kcollect_samples;
155                 bzero(&kcollect_ary[i], sizeof(kcollect_ary[i]));
156                 crit_enter();
157                 kcollect_ary[i].ticks = ticks;
158                 getmicrotime(&kcollect_ary[i].realtime);
159                 crit_exit();
160                 for (n = 0; n < KCOLLECT_ENTRIES; ++n) {
161                         if (kcollect_callback[n]) {
162                                 kcollect_ary[i].data[n] =
163                                         kcollect_callback[n](n);
164                         }
165                 }
166                 cpu_sfence();
167                 ++kcollect_index;
168                 lockmgr(&kcollect_lock, LK_RELEASE);
169                 tsleep(&dummy, 0, "sleep", hz * KCOLLECT_INTERVAL);
170         }
171 }
172
173 /*
174  * No requirements.
175  */
176 static int
177 sysctl_kcollect_data(SYSCTL_HANDLER_ARGS)
178 {
179         int error;
180         uint32_t i;
181         uint32_t start;
182         uint64_t count;
183         kcollect_t scale;
184         kcollect_t id;
185
186         if (kcollect_samples == (uint32_t)-1 ||
187             kcollect_samples == 0) {
188                 return EINVAL;
189         }
190
191         error = 0;
192         count = kcollect_index;
193         start = count % kcollect_samples;
194         if (count >= kcollect_samples)
195                 count = kcollect_samples - 1;
196
197         /*
198          * Sizing request
199          */
200         if (req->oldptr == NULL) {
201                 error = SYSCTL_OUT(req, 0, sizeof(kcollect_t) * (count + 2));
202                 return error;
203         }
204
205         /*
206          * Output request.  We output a scale record, a string record, and
207          * N collection records.  The strings in the string record can be
208          * up to 8 characters long, and if a string is 8 characters long it
209          * will not be zero-terminated.
210          *
211          * The low byte of the scale record specifies the format.  To get
212          * the scale value shift right by 8.
213          */
214         if (kcollect_ary == NULL)
215                 return ENOTSUP;
216
217         lockmgr(&kcollect_lock, LK_EXCLUSIVE);
218         scale = kcollect_scale;
219         scale.ticks = ticks;
220         scale.hz = hz;
221
222         bzero(&id, sizeof(id));
223         for (i = 0; i < KCOLLECT_ENTRIES; ++i) {
224                 if (kcollect_slots[i]) {
225                         char *ptr = (char *)&id.data[i];
226                         size_t len = strlen(kcollect_slots[i]);
227                         if (len > sizeof(id.data[0]))
228                                 len = sizeof(id.data[0]);
229                         bcopy(kcollect_slots[i], ptr, len);
230                 }
231         }
232         lockmgr(&kcollect_lock, LK_RELEASE);
233
234         error = SYSCTL_OUT(req, &scale, sizeof(scale));
235         if (error == 0)
236                 error = SYSCTL_OUT(req, &id, sizeof(id));
237
238         /*
239          * Start at the current entry (not yet populated) and work
240          * backwards.  This allows callers of the sysctl to acquire
241          * a lesser amount of data aligned to the most recent side of
242          * the array.
243          */
244         i = start;
245         while (count) {
246                 if (req->oldlen - req->oldidx < sizeof(kcollect_t))
247                         break;
248                 if (i == 0)
249                         i = kcollect_samples - 1;
250                 else
251                         --i;
252                 error = SYSCTL_OUT(req, &kcollect_ary[i], sizeof(kcollect_t));
253                 if (error)
254                         break;
255                 --count;
256         }
257         return error;
258 }
259 SYSCTL_PROC(_kern, OID_AUTO, collect_data,
260         CTLFLAG_RD | CTLTYPE_STRUCT, 0, 0,
261         sysctl_kcollect_data, "S,kcollect", "Dump collected statistics");
262
263 static
264 void
265 kcollect_thread_init(void)
266 {
267         thread_t td = NULL;
268
269         /*
270          * Autosize sample retention (10 second interval)
271          */
272         if ((int)kcollect_samples < 0) {
273                 if (kmem_lim_size() < 1024)
274                         kcollect_samples = 1024;
275                 else
276                         kcollect_samples = 8192;
277         }
278
279         if (kcollect_samples) {
280                 kcollect_ary = kmalloc(kcollect_samples * sizeof(kcollect_t),
281                                        M_KCOLLECT, M_WAITOK | M_ZERO);
282                 lwkt_create(kcollect_thread, NULL, &td, NULL, 0, 0, "kcollect");
283         }
284 }
285 SYSINIT(kcol, SI_SUB_HELPER_THREADS, SI_ORDER_ANY, kcollect_thread_init, 0);