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