xref: /dragonfly/sys/kern/kern_collect.c (revision 65cc0652)
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);
286