/* $NetBSD: bufcache.c,v 1.30 2020/03/02 09:50:12 mrg Exp $ */ /*- * Copyright (c) 1999 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Simon Burge. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #ifndef lint __RCSID("$NetBSD: bufcache.c,v 1.30 2020/03/02 09:50:12 mrg Exp $"); #endif /* not lint */ #include #include #define __EXPOSE_MOUNT #include #include #include #include #include #include #include #include #include #include #include #include #include #include "systat.h" #include "extern.h" #define VCACHE_SIZE 50 #define PAGEINFO_ROWS 5 struct vcache { int vc_age; struct vnode *vc_addr; struct vnode vc_node; }; struct ml_entry { u_int ml_count; u_long ml_size; u_long ml_valid; struct mount *ml_addr; LIST_ENTRY(ml_entry) ml_entries; struct mount ml_mount; }; static struct vcache vcache[VCACHE_SIZE]; static LIST_HEAD(mount_list, ml_entry) mount_list; static uint64_t bufmem; static u_int nbuf, pgwidth, kbwidth; static struct uvmexp_sysctl uvmexp; static void vc_init(void); static void ml_init(void); static struct vnode *vc_lookup(struct vnode *); static struct mount *ml_lookup(struct mount *, int, int); static void fetchuvmexp(void); WINDOW * openbufcache(void) { return (subwin(stdscr, -1, 0, 5, 0)); } void closebufcache(WINDOW *w) { if (w == NULL) return; wclear(w); wrefresh(w); delwin(w); ml_init(); /* Clear out mount list */ } void labelbufcache(void) { int i; for (i = 0; i <= PAGEINFO_ROWS; i++) { wmove(wnd, i, 0); wclrtoeol(wnd); } mvwaddstr(wnd, PAGEINFO_ROWS + 1, 0, "File System Bufs used % kB in use % Bufsize kB % Util %"); wclrtoeol(wnd); } void showbufcache(void) { int tbuf, i, lastrow; double tvalid, tsize; struct ml_entry *ml; size_t len; static int mib[] = { -1, 0 }; if (mib[0] == -1) { len = __arraycount(mib); if (sysctlnametomib("vm.bufmem", mib, &len) == -1) error("can't get \"vm.bufmem\" mib: %s", strerror(errno)); } len = sizeof(bufmem); if (sysctl(mib, 2, &bufmem, &len, NULL, 0) == -1) error("can't get \"vm.bufmem\": %s", strerror(errno)); mvwprintw(wnd, 0, 0, " %*d metadata buffers using %*"PRIu64" kBytes of " "memory (%2.0f%%).", pgwidth, nbuf, kbwidth, bufmem / 1024, ((bufmem * 100.0) + 0.5) / getpagesize() / uvmexp.npages); wclrtoeol(wnd); mvwprintw(wnd, 1, 0, " %*" PRIu64 " pages for cached file data using %*" PRIu64 " kBytes of memory (%2.0f%%).", pgwidth, uvmexp.filepages, kbwidth, uvmexp.filepages * getpagesize() / 1024, (uvmexp.filepages * 100 + 0.5) / uvmexp.npages); wclrtoeol(wnd); mvwprintw(wnd, 2, 0, " %*" PRIu64 " pages for executables using %*" PRIu64 " kBytes of memory (%2.0f%%).", pgwidth, uvmexp.execpages, kbwidth, uvmexp.execpages * getpagesize() / 1024, (uvmexp.execpages * 100 + 0.5) / uvmexp.npages); wclrtoeol(wnd); mvwprintw(wnd, 3, 0, " %*" PRIu64 " pages for anon (non-file) data %*" PRIu64 " kBytes of memory (%2.0f%%).", pgwidth, uvmexp.anonpages, kbwidth, uvmexp.anonpages * getpagesize() / 1024, (uvmexp.anonpages * 100 + 0.5) / uvmexp.npages); wclrtoeol(wnd); mvwprintw(wnd, 4, 0, " %*" PRIu64 " free pages %*" PRIu64 " kBytes of memory (%2.0f%%).", pgwidth, uvmexp.free, kbwidth, uvmexp.free * getpagesize() / 1024, (uvmexp.free * 100 + 0.5) / uvmexp.npages); wclrtoeol(wnd); if (nbuf == 0 || bufmem == 0) { wclrtobot(wnd); return; } tbuf = 0; tvalid = tsize = 0; lastrow = PAGEINFO_ROWS + 2; /* Leave room for header. */ for (i = lastrow, ml = LIST_FIRST(&mount_list); ml != NULL; i++, ml = LIST_NEXT(ml, ml_entries)) { int cnt = ml->ml_count; double v = ml->ml_valid; double s = ml->ml_size; /* Display in window if enough room. */ if (i < getmaxy(wnd) - 2) { mvwprintw(wnd, i, 0, "%-20.20s", ml->ml_addr == NULL ? "NULL" : ml->ml_mount.mnt_stat.f_mntonname); wprintw(wnd, " %6d %3d %8ld %3.0f %8ld %3.0f %3.0f", cnt, (100 * cnt) / nbuf, (long)(v/1024), 100 * v / bufmem, (long)(s/1024), 100 * s / bufmem, 100 * v / s); wclrtoeol(wnd); lastrow = i; } /* Update statistics. */ tbuf += cnt; tvalid += v; tsize += s; } wclrtobot(wnd); mvwprintw(wnd, lastrow + 2, 0, "%-20s %6d %3d %8ld %3.0f %8ld %3.0f %3.0f", "Total:", tbuf, (100 * tbuf) / nbuf, (long)(tvalid/1024), 100 * tvalid / bufmem, (long)(tsize/1024), 100 * tsize / bufmem, tsize != 0 ? ((100 * tvalid) / tsize) : 0); } int initbufcache(void) { fetchuvmexp(); pgwidth = (int)(floor(log10((double)uvmexp.npages)) + 1); kbwidth = (int)(floor(log10(uvmexp.npages * getpagesize() / 1024.0)) + 1); return(1); } static void fetchuvmexp(void) { int mib[2]; size_t size; /* Re-read pages used for vnodes & executables */ size = sizeof(uvmexp); mib[0] = CTL_VM; mib[1] = VM_UVMEXP2; if (sysctl(mib, 2, &uvmexp, &size, NULL, 0) < 0) { error("can't get uvmexp: %s\n", strerror(errno)); memset(&uvmexp, 0, sizeof(uvmexp)); } } void fetchbufcache(void) { int count; struct buf_sysctl *bp, *buffers; struct vnode *vn; struct ml_entry *ml; int mib[6]; size_t size; int extraslop = 0; /* Re-read pages used for vnodes & executables */ fetchuvmexp(); /* Initialise vnode cache and mount list. */ vc_init(); ml_init(); /* Get metadata buffers */ size = 0; buffers = NULL; mib[0] = CTL_KERN; mib[1] = KERN_BUF; mib[2] = KERN_BUF_ALL; mib[3] = KERN_BUF_ALL; mib[4] = (int)sizeof(struct buf_sysctl); mib[5] = INT_MAX; /* we want them all */ again: if (sysctl(mib, 6, NULL, &size, NULL, 0) < 0) { error("can't get buffers size: %s\n", strerror(errno)); return; } if (size == 0) return; size += extraslop * sizeof(struct buf_sysctl); buffers = malloc(size); if (buffers == NULL) { error("can't allocate buffers: %s\n", strerror(errno)); return; } if (sysctl(mib, 6, buffers, &size, NULL, 0) < 0) { free(buffers); if (extraslop < 1000) { extraslop += 100; goto again; } error("can't get buffers: %s\n", strerror(errno)); return; } nbuf = size / sizeof(struct buf_sysctl); for (bp = buffers; bp < buffers + nbuf; bp++) { if (UINT64TOPTR(bp->b_vp) != NULL) { struct mount *mp; vn = vc_lookup(UINT64TOPTR(bp->b_vp)); if (vn == NULL) break; mp = vn->v_mount; /* * References to mounted-on vnodes should be * counted towards the mounted filesystem. */ if (vn->v_type == VBLK && vn->v_specnode != NULL) { specnode_t sn; specdev_t sd; if (!KREAD(vn->v_specnode, &sn, sizeof(sn))) continue; if (!KREAD(sn.sn_dev, &sd, sizeof(sd))) continue; if (sd.sd_mountpoint) mp = sd.sd_mountpoint; } if (mp != NULL) (void)ml_lookup(mp, bp->b_bufsize, bp->b_bcount); } } /* simple sort - there's not that many entries */ do { if ((ml = LIST_FIRST(&mount_list)) == NULL || LIST_NEXT(ml, ml_entries) == NULL) break; count = 0; for (ml = LIST_FIRST(&mount_list); ml != NULL; ml = LIST_NEXT(ml, ml_entries)) { if (LIST_NEXT(ml, ml_entries) == NULL) break; if (ml->ml_count < LIST_NEXT(ml, ml_entries)->ml_count) { ml = LIST_NEXT(ml, ml_entries); LIST_REMOVE(ml, ml_entries); LIST_INSERT_HEAD(&mount_list, ml, ml_entries); count++; } } } while (count != 0); free(buffers); } static void vc_init(void) { int i; /* vc_addr == NULL for unused cache entry. */ for (i = 0; i < VCACHE_SIZE; i++) vcache[i].vc_addr = NULL; } static void ml_init(void) { struct ml_entry *ml; /* Throw out the current mount list and start again. */ while ((ml = LIST_FIRST(&mount_list)) != NULL) { LIST_REMOVE(ml, ml_entries); free(ml); } } static struct vnode * vc_lookup(struct vnode *vaddr) { struct vnode *ret; size_t i, oldest; ret = NULL; oldest = 0; for (i = 0; i < VCACHE_SIZE; i++) { if (vcache[i].vc_addr == NULL) break; vcache[i].vc_age++; if (vcache[i].vc_age < vcache[oldest].vc_age) oldest = i; if (vcache[i].vc_addr == vaddr) { vcache[i].vc_age = 0; ret = &vcache[i].vc_node; } } /* Find an entry in the cache? */ if (ret != NULL) return(ret); /* Go past the end of the cache? */ if (i >= VCACHE_SIZE) i = oldest; /* Read in new vnode and reset age counter. */ if (KREAD(vaddr, &vcache[i].vc_node, sizeof(struct vnode)) == 0) return NULL; vcache[i].vc_addr = vaddr; vcache[i].vc_age = 0; return(&vcache[i].vc_node); } static struct mount * ml_lookup(struct mount *maddr, int size, int valid) { struct ml_entry *ml; for (ml = LIST_FIRST(&mount_list); ml != NULL; ml = LIST_NEXT(ml, ml_entries)) if (ml->ml_addr == maddr) { ml->ml_count++; ml->ml_size += size; ml->ml_valid += valid; if (ml->ml_addr == NULL) return(NULL); else return(&ml->ml_mount); } if ((ml = malloc(sizeof(struct ml_entry))) == NULL) { error("out of memory"); die(0); } LIST_INSERT_HEAD(&mount_list, ml, ml_entries); ml->ml_count = 1; ml->ml_size = size; ml->ml_valid = valid; ml->ml_addr = maddr; if (maddr == NULL) return(NULL); KREAD(maddr, &ml->ml_mount, sizeof(struct mount)); return(&ml->ml_mount); }