xref: /dragonfly/sys/kern/kern_collect.c (revision 37de577a)
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 #include <sys/malloc.h>
49 
50 #include <sys/thread2.h>
51 #include <sys/spinlock2.h>
52 
53 #include <vm/vm.h>
54 #include <vm/vm_param.h>
55 #include <vm/vm_kern.h>
56 #include <vm/vm_object.h>
57 #include <vm/vm_page.h>
58 #include <vm/vm_map.h>
59 #include <vm/vm_pager.h>
60 #include <vm/vm_extern.h>
61 
62 #include <machine/stdarg.h>
63 #include <machine/smp.h>
64 #include <machine/clock.h>
65 
66 static uint32_t kcollect_samples = -1;	/* 0 to disable */
67 TUNABLE_INT("kern.collect_samples", &kcollect_samples);
68 SYSCTL_UINT(_kern, OID_AUTO, collect_samples, CTLFLAG_RD,
69 	&kcollect_samples, 0, "number of 10-second samples");
70 
71 static uint64_t kcollect_index;
72 static const char *kcollect_slots[KCOLLECT_ENTRIES];
73 static kcallback_t kcollect_callback[KCOLLECT_ENTRIES];
74 static kcollect_t kcollect_scale;
75 static kcollect_t *kcollect_ary;
76 static struct lock kcollect_lock = LOCK_INITIALIZER("kcolk", 0, 0);
77 
78 MALLOC_DEFINE(M_KCOLLECT, "kcollect", "kcollect array");
79 
80 int
81 kcollect_register(int which, const char *id, kcallback_t func, uint64_t scale)
82 {
83 	int n;
84 
85 	lockmgr(&kcollect_lock, LK_EXCLUSIVE);
86 	if (which >= 0) {
87 		n = which;
88 	} else {
89 		for (n = KCOLLECT_DYNAMIC_START; n < KCOLLECT_ENTRIES; ++n) {
90 			if (kcollect_slots[n] == NULL)
91 				break;
92 		}
93 	}
94 	if (n < 0 || n >= KCOLLECT_ENTRIES) {
95 		n = -1;
96 	} else {
97 		kcollect_slots[n] = id;
98 		kcollect_callback[n] = func;
99 		kcollect_scale.data[n] = scale;
100 	}
101 	lockmgr(&kcollect_lock, LK_RELEASE);
102 
103 	return n;
104 }
105 
106 void
107 kcollect_unregister(int n)
108 {
109 	lockmgr(&kcollect_lock, LK_EXCLUSIVE);
110 	if (n >= 0 && n < KCOLLECT_ENTRIES) {
111 		kcollect_slots[n] = NULL;
112 		kcollect_callback[n] = NULL;
113 	}
114 	lockmgr(&kcollect_lock, LK_RELEASE);
115 }
116 
117 /*
118  * Typically called by a rollup function in the callback from the
119  * collection thread.  Not usually called ad-hoc.  This allows a
120  * subsystem to register several collection ids but only one callback
121  * which populates all of them.
122  */
123 void
124 kcollect_setvalue(int n, uint64_t value)
125 {
126 	uint32_t i;
127 
128 	if (n >= 0 && n < KCOLLECT_ENTRIES) {
129 		i = kcollect_index % kcollect_samples;
130 		kcollect_ary[i].data[n] = value;
131 	}
132 }
133 
134 /*
135  * Callback to change scale adjustment, if necessary.  Certain statistics
136  * have scale info available (such as KCOLLECT_SWAPANO and SWAPCAC).
137  */
138 void
139 kcollect_setscale(int n, uint64_t value)
140 {
141 	if (n >= 0 && n < KCOLLECT_ENTRIES) {
142 		kcollect_scale.data[n] = value;
143 	}
144 }
145 
146 static
147 void
148 kcollect_thread(void *dummy)
149 {
150 	uint32_t i;
151 	int n;
152 
153 	for (;;) {
154 		lockmgr(&kcollect_lock, LK_EXCLUSIVE);
155 		i = kcollect_index % kcollect_samples;
156 		bzero(&kcollect_ary[i], sizeof(kcollect_ary[i]));
157 		crit_enter();
158 		kcollect_ary[i].ticks = ticks;
159 		getmicrotime(&kcollect_ary[i].realtime);
160 		crit_exit();
161 		for (n = 0; n < KCOLLECT_ENTRIES; ++n) {
162 			if (kcollect_callback[n]) {
163 				kcollect_ary[i].data[n] =
164 					kcollect_callback[n](n);
165 			}
166 		}
167 		cpu_sfence();
168 		++kcollect_index;
169 		lockmgr(&kcollect_lock, LK_RELEASE);
170 		tsleep(&dummy, 0, "sleep", hz * KCOLLECT_INTERVAL);
171 	}
172 }
173 
174 /*
175  * No requirements.
176  */
177 static int
178 sysctl_kcollect_data(SYSCTL_HANDLER_ARGS)
179 {
180 	int error;
181 	uint32_t i;
182 	uint32_t start;
183 	uint64_t count;
184 	kcollect_t scale;
185 	kcollect_t id;
186 
187 	if (kcollect_samples == (uint32_t)-1 ||
188 	    kcollect_samples == 0) {
189 		return EINVAL;
190 	}
191 
192 	error = 0;
193 	count = kcollect_index;
194 	start = count % kcollect_samples;
195 	if (count >= kcollect_samples)
196 		count = kcollect_samples - 1;
197 
198 	/*
199 	 * Sizing request
200 	 */
201 	if (req->oldptr == NULL) {
202 		error = SYSCTL_OUT(req, 0, sizeof(kcollect_t) * (count + 2));
203 		return error;
204 	}
205 
206 	/*
207 	 * Output request.  We output a scale record, a string record, and
208 	 * N collection records.  The strings in the string record can be
209 	 * up to 8 characters long, and if a string is 8 characters long it
210 	 * will not be zero-terminated.
211 	 *
212 	 * The low byte of the scale record specifies the format.  To get
213 	 * the scale value shift right by 8.
214 	 */
215 	if (kcollect_ary == NULL)
216 		return ENOTSUP;
217 
218 	lockmgr(&kcollect_lock, LK_EXCLUSIVE);
219 	scale = kcollect_scale;
220 	scale.ticks = ticks;
221 	scale.hz = hz;
222 
223 	bzero(&id, sizeof(id));
224 	for (i = 0; i < KCOLLECT_ENTRIES; ++i) {
225 		if (kcollect_slots[i]) {
226 			char *ptr = (char *)&id.data[i];
227 			size_t len = strlen(kcollect_slots[i]);
228 			if (len > sizeof(id.data[0]))
229 				len = sizeof(id.data[0]);
230 			bcopy(kcollect_slots[i], ptr, len);
231 		}
232 	}
233 	lockmgr(&kcollect_lock, LK_RELEASE);
234 
235 	error = SYSCTL_OUT(req, &scale, sizeof(scale));
236 	if (error == 0)
237 		error = SYSCTL_OUT(req, &id, sizeof(id));
238 
239 	/*
240 	 * Start at the current entry (not yet populated) and work
241 	 * backwards.  This allows callers of the sysctl to acquire
242 	 * a lesser amount of data aligned to the most recent side of
243 	 * the array.
244 	 */
245 	i = start;
246 	while (count) {
247 		if (req->oldlen - req->oldidx < sizeof(kcollect_t))
248 			break;
249 		if (i == 0)
250 			i = kcollect_samples - 1;
251 		else
252 			--i;
253 		error = SYSCTL_OUT(req, &kcollect_ary[i], sizeof(kcollect_t));
254 		if (error)
255 			break;
256 		--count;
257 	}
258 	return error;
259 }
260 SYSCTL_PROC(_kern, OID_AUTO, collect_data,
261 	CTLFLAG_RD | CTLTYPE_STRUCT, 0, 0,
262 	sysctl_kcollect_data, "S,kcollect", "Dump collected statistics");
263 
264 static
265 void
266 kcollect_thread_init(void)
267 {
268 	thread_t td = NULL;
269 
270 	/*
271 	 * Autosize sample retention (10 second interval)
272 	 */
273 	if ((int)kcollect_samples < 0) {
274 		if (kmem_lim_size() < 1024)
275 			kcollect_samples = 1024;
276 		else
277 			kcollect_samples = 8192;
278 	}
279 
280 	if (kcollect_samples) {
281 		kcollect_ary = kmalloc(kcollect_samples * sizeof(kcollect_t),
282 				       M_KCOLLECT, M_WAITOK | M_ZERO);
283 		lwkt_create(kcollect_thread, NULL, &td, NULL, 0, 0, "kcollect");
284 	}
285 }
286 SYSINIT(kcol, SI_SUB_HELPER_THREADS, SI_ORDER_ANY, kcollect_thread_init, 0);
287