xref: /netbsd-src/usr.bin/systat/bufcache.c (revision e89934bbf778a6d6d6894877c4da59d0c7835b0f)
1 /*	$NetBSD: bufcache.c,v 1.27 2016/10/24 00:40:17 christos Exp $	*/
2 
3 /*-
4  * Copyright (c) 1999 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Simon Burge.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29  * POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 #include <sys/cdefs.h>
33 #ifndef lint
34 __RCSID("$NetBSD: bufcache.c,v 1.27 2016/10/24 00:40:17 christos Exp $");
35 #endif /* not lint */
36 
37 #include <sys/param.h>
38 #include <sys/buf.h>
39 #include <sys/mount.h>
40 #include <sys/sysctl.h>
41 #include <sys/vnode.h>
42 
43 #include <uvm/uvm_extern.h>
44 
45 #include <err.h>
46 #include <errno.h>
47 #include <inttypes.h>
48 #include <math.h>
49 #include <stdlib.h>
50 #include <string.h>
51 #include <unistd.h>
52 #include <stdbool.h>
53 
54 #include <miscfs/specfs/specdev.h>
55 
56 #include "systat.h"
57 #include "extern.h"
58 
59 #define VCACHE_SIZE	50
60 #define	PAGEINFO_ROWS	 5
61 
62 struct vcache {
63 	int vc_age;
64 	struct vnode *vc_addr;
65 	struct vnode vc_node;
66 };
67 
68 struct ml_entry {
69 	u_int ml_count;
70 	u_long ml_size;
71 	u_long ml_valid;
72 	struct mount *ml_addr;
73 	LIST_ENTRY(ml_entry) ml_entries;
74 	struct mount ml_mount;
75 };
76 
77 static struct vcache vcache[VCACHE_SIZE];
78 static LIST_HEAD(mount_list, ml_entry) mount_list;
79 
80 static uint64_t bufmem;
81 static u_int nbuf, pgwidth, kbwidth;
82 static struct uvmexp_sysctl uvmexp;
83 
84 static void	vc_init(void);
85 static void	ml_init(void);
86 static struct 	vnode *vc_lookup(struct vnode *);
87 static struct 	mount *ml_lookup(struct mount *, int, int);
88 static void	fetchuvmexp(void);
89 
90 
91 WINDOW *
92 openbufcache(void)
93 {
94 
95 	return (subwin(stdscr, -1, 0, 5, 0));
96 }
97 
98 void
99 closebufcache(WINDOW *w)
100 {
101 
102 	if (w == NULL)
103 		return;
104 	wclear(w);
105 	wrefresh(w);
106 	delwin(w);
107 	ml_init();		/* Clear out mount list */
108 }
109 
110 void
111 labelbufcache(void)
112 {
113 	int i;
114 
115 	for (i = 0; i <= PAGEINFO_ROWS; i++) {
116 		wmove(wnd, i, 0);
117 		wclrtoeol(wnd);
118 	}
119 	mvwaddstr(wnd, PAGEINFO_ROWS + 1, 0, "File System          Bufs used"
120 	    "   %   kB in use   %  Bufsize kB   %  Util %");
121 	wclrtoeol(wnd);
122 }
123 
124 void
125 showbufcache(void)
126 {
127 	int tbuf, i, lastrow;
128 	double tvalid, tsize;
129 	struct ml_entry *ml;
130 	size_t len;
131 	static int mib[] = { -1, 0 };
132 
133 	if (mib[0] == -1) {
134 		len = __arraycount(mib);
135 		if (sysctlnametomib("vm.bufmem", mib, &len) == -1)
136 			error("can't get \"vm.bufmem\" mib: %s",
137 			    strerror(errno));
138 	}
139 	len = sizeof(bufmem);
140 	if (sysctl(mib, 2, &bufmem, &len, NULL, 0) == -1)
141 		error("can't get \"vm.bufmem\": %s", strerror(errno));
142 
143 	mvwprintw(wnd, 0, 0,
144 	    "   %*d metadata buffers using             %*"PRIu64" kBytes of "
145 	    "memory (%2.0f%%).",
146 	    pgwidth, nbuf, kbwidth, bufmem / 1024,
147 	    ((bufmem * 100.0) + 0.5) / getpagesize() / uvmexp.npages);
148 	wclrtoeol(wnd);
149 	mvwprintw(wnd, 1, 0,
150 	    "   %*" PRIu64 " pages for cached file data using   %*"
151 	    PRIu64 " kBytes of memory (%2.0f%%).",
152 	    pgwidth, uvmexp.filepages,
153 	    kbwidth, uvmexp.filepages * getpagesize() / 1024,
154 	    (uvmexp.filepages * 100 + 0.5) / uvmexp.npages);
155 	wclrtoeol(wnd);
156 	mvwprintw(wnd, 2, 0,
157 	    "   %*" PRIu64 " pages for executables using        %*"
158 	    PRIu64 " kBytes of memory (%2.0f%%).",
159 	    pgwidth, uvmexp.execpages,
160 	    kbwidth, uvmexp.execpages * getpagesize() / 1024,
161 	    (uvmexp.execpages * 100 + 0.5) / uvmexp.npages);
162 	wclrtoeol(wnd);
163 	mvwprintw(wnd, 3, 0,
164 	    "   %*" PRIu64 " pages for anon (non-file) data     %*"
165 	    PRIu64 " kBytes of memory (%2.0f%%).",
166 	    pgwidth, uvmexp.anonpages,
167 	    kbwidth, uvmexp.anonpages * getpagesize() / 1024,
168 	    (uvmexp.anonpages * 100 + 0.5) / uvmexp.npages);
169 	wclrtoeol(wnd);
170 	mvwprintw(wnd, 4, 0,
171 	    "   %*" PRIu64 " free pages                         %*"
172 	    PRIu64 " kBytes of memory (%2.0f%%).",
173 	    pgwidth, uvmexp.free,
174 	    kbwidth, uvmexp.free * getpagesize() / 1024,
175 	    (uvmexp.free * 100 + 0.5) / uvmexp.npages);
176 	wclrtoeol(wnd);
177 
178 	if (nbuf == 0 || bufmem == 0) {
179 		wclrtobot(wnd);
180 		return;
181 	}
182 
183 	tbuf = 0;
184 	tvalid = tsize = 0;
185 	lastrow = PAGEINFO_ROWS + 2;	/* Leave room for header. */
186 	for (i = lastrow, ml = LIST_FIRST(&mount_list); ml != NULL;
187 	    i++, ml = LIST_NEXT(ml, ml_entries)) {
188 
189 		int cnt = ml->ml_count;
190 		double v = ml->ml_valid;
191 		double s = ml->ml_size;
192 
193 		/* Display in window if enough room. */
194 		if (i < getmaxy(wnd) - 2) {
195 			mvwprintw(wnd, i, 0, "%-20.20s", ml->ml_addr == NULL ?
196 			    "NULL" : ml->ml_mount.mnt_stat.f_mntonname);
197 			wprintw(wnd,
198 			    "    %6d %3d    %8ld %3.0f    %8ld %3.0f     %3.0f",
199 			    cnt, (100 * cnt) / nbuf,
200 			    (long)(v/1024), 100 * v / bufmem,
201 			    (long)(s/1024), 100 * s / bufmem,
202 			    100 * v / s);
203 			wclrtoeol(wnd);
204 			lastrow = i;
205 		}
206 
207 		/* Update statistics. */
208 		tbuf += cnt;
209 		tvalid += v;
210 		tsize += s;
211 	}
212 
213 	wclrtobot(wnd);
214 	mvwprintw(wnd, lastrow + 2, 0,
215 	    "%-20s    %6d %3d    %8ld %3.0f    %8ld %3.0f     %3.0f",
216 	    "Total:", tbuf, (100 * tbuf) / nbuf,
217 	    (long)(tvalid/1024), 100 * tvalid / bufmem,
218 	    (long)(tsize/1024), 100 * tsize / bufmem,
219 	    tsize != 0 ? ((100 * tvalid) / tsize) : 0);
220 }
221 
222 int
223 initbufcache(void)
224 {
225 	fetchuvmexp();
226 	pgwidth = (int)(floor(log10((double)uvmexp.npages)) + 1);
227 	kbwidth = (int)(floor(log10(uvmexp.npages * getpagesize() / 1024.0)) +
228 	    1);
229 
230 	return(1);
231 }
232 
233 static void
234 fetchuvmexp(void)
235 {
236 	int mib[2];
237 	size_t size;
238 
239 	/* Re-read pages used for vnodes & executables */
240 	size = sizeof(uvmexp);
241 	mib[0] = CTL_VM;
242 	mib[1] = VM_UVMEXP2;
243 	if (sysctl(mib, 2, &uvmexp, &size, NULL, 0) < 0) {
244 		error("can't get uvmexp: %s\n", strerror(errno));
245 		memset(&uvmexp, 0, sizeof(uvmexp));
246 	}
247 }
248 
249 void
250 fetchbufcache(void)
251 {
252 	int count;
253 	struct buf_sysctl *bp, *buffers;
254 	struct vnode *vn;
255 	struct ml_entry *ml;
256 	int mib[6];
257 	size_t size;
258 	int extraslop = 0;
259 
260 	/* Re-read pages used for vnodes & executables */
261 	fetchuvmexp();
262 
263 	/* Initialise vnode cache and mount list. */
264 	vc_init();
265 	ml_init();
266 
267 	/* Get metadata buffers */
268 	size = 0;
269 	buffers = NULL;
270 	mib[0] = CTL_KERN;
271 	mib[1] = KERN_BUF;
272 	mib[2] = KERN_BUF_ALL;
273 	mib[3] = KERN_BUF_ALL;
274 	mib[4] = (int)sizeof(struct buf_sysctl);
275 	mib[5] = INT_MAX; /* we want them all */
276 again:
277 	if (sysctl(mib, 6, NULL, &size, NULL, 0) < 0) {
278 		error("can't get buffers size: %s\n", strerror(errno));
279 		return;
280 	}
281 	if (size == 0)
282 		return;
283 
284 	size += extraslop * sizeof(struct buf_sysctl);
285 	buffers = malloc(size);
286 	if (buffers == NULL) {
287 		error("can't allocate buffers: %s\n", strerror(errno));
288 		return;
289 	}
290 	if (sysctl(mib, 6, buffers, &size, NULL, 0) < 0) {
291 		free(buffers);
292 		if (extraslop == 0) {
293 			extraslop = 100;
294 			goto again;
295 		}
296 		error("can't get buffers: %s\n", strerror(errno));
297 		return;
298 	}
299 
300 	nbuf = size / sizeof(struct buf_sysctl);
301 	for (bp = buffers; bp < buffers + nbuf; bp++) {
302 		if (UINT64TOPTR(bp->b_vp) != NULL) {
303 			struct mount *mp;
304 			vn = vc_lookup(UINT64TOPTR(bp->b_vp));
305 			if (vn == NULL)
306 				break;
307 
308 			mp = vn->v_mount;
309 			/*
310 			 * References to mounted-on vnodes should be
311 			 * counted towards the mounted filesystem.
312 			 */
313 			if (vn->v_type == VBLK && vn->v_specnode != NULL) {
314 				specnode_t sn;
315 				specdev_t sd;
316 				if (!KREAD(vn->v_specnode, &sn, sizeof(sn)))
317 					continue;
318 				if (!KREAD(sn.sn_dev, &sd, sizeof(sd)))
319 					continue;
320 				if (sd.sd_mountpoint)
321 					mp = sd.sd_mountpoint;
322 			}
323 			if (mp != NULL)
324 				(void)ml_lookup(mp, bp->b_bufsize,
325 				    bp->b_bcount);
326 		}
327 	}
328 
329 	/* simple sort - there's not that many entries */
330 	do {
331 		if ((ml = LIST_FIRST(&mount_list)) == NULL ||
332 		    LIST_NEXT(ml, ml_entries) == NULL)
333 			break;
334 
335 		count = 0;
336 		for (ml = LIST_FIRST(&mount_list); ml != NULL;
337 		    ml = LIST_NEXT(ml, ml_entries)) {
338 			if (LIST_NEXT(ml, ml_entries) == NULL)
339 				break;
340 			if (ml->ml_count < LIST_NEXT(ml, ml_entries)->ml_count) {
341 				ml = LIST_NEXT(ml, ml_entries);
342 				LIST_REMOVE(ml, ml_entries);
343 				LIST_INSERT_HEAD(&mount_list, ml, ml_entries);
344 				count++;
345 			}
346 		}
347 	} while (count != 0);
348 
349 	free(buffers);
350 }
351 
352 static void
353 vc_init(void)
354 {
355 	int i;
356 
357 	/* vc_addr == NULL for unused cache entry. */
358 	for (i = 0; i < VCACHE_SIZE; i++)
359 		vcache[i].vc_addr = NULL;
360 }
361 
362 static void
363 ml_init(void)
364 {
365 	struct ml_entry *ml;
366 
367 	/* Throw out the current mount list and start again. */
368 	while ((ml = LIST_FIRST(&mount_list)) != NULL) {
369 		LIST_REMOVE(ml, ml_entries);
370 		free(ml);
371 	}
372 }
373 
374 
375 static struct vnode *
376 vc_lookup(struct vnode *vaddr)
377 {
378 	struct vnode *ret;
379 	size_t i, oldest;
380 
381 	ret = NULL;
382 	oldest = 0;
383 	for (i = 0; i < VCACHE_SIZE; i++) {
384 		if (vcache[i].vc_addr == NULL)
385 			break;
386 		vcache[i].vc_age++;
387 		if (vcache[i].vc_age < vcache[oldest].vc_age)
388 			oldest = i;
389 		if (vcache[i].vc_addr == vaddr) {
390 			vcache[i].vc_age = 0;
391 			ret = &vcache[i].vc_node;
392 		}
393 	}
394 
395 	/* Find an entry in the cache? */
396 	if (ret != NULL)
397 		return(ret);
398 
399 	/* Go past the end of the cache? */
400 	if  (i >= VCACHE_SIZE)
401 		i = oldest;
402 
403 	/* Read in new vnode and reset age counter. */
404 	if (KREAD(vaddr, &vcache[i].vc_node, sizeof(struct vnode)) == 0)
405 		return NULL;
406 	vcache[i].vc_addr = vaddr;
407 	vcache[i].vc_age = 0;
408 
409 	return(&vcache[i].vc_node);
410 }
411 
412 static struct mount *
413 ml_lookup(struct mount *maddr, int size, int valid)
414 {
415 	struct ml_entry *ml;
416 
417 	for (ml = LIST_FIRST(&mount_list); ml != NULL;
418 	    ml = LIST_NEXT(ml, ml_entries))
419 		if (ml->ml_addr == maddr) {
420 			ml->ml_count++;
421 			ml->ml_size += size;
422 			ml->ml_valid += valid;
423 			if (ml->ml_addr == NULL)
424 				return(NULL);
425 			else
426 				return(&ml->ml_mount);
427 		}
428 
429 	if ((ml = malloc(sizeof(struct ml_entry))) == NULL) {
430 		error("out of memory");
431 		die(0);
432 	}
433 	LIST_INSERT_HEAD(&mount_list, ml, ml_entries);
434 	ml->ml_count = 1;
435 	ml->ml_size = size;
436 	ml->ml_valid = valid;
437 	ml->ml_addr = maddr;
438 	if (maddr == NULL)
439 		return(NULL);
440 
441 	KREAD(maddr, &ml->ml_mount, sizeof(struct mount));
442 	return(&ml->ml_mount);
443 }
444