445 lines
11 KiB
C
445 lines
11 KiB
C
/* $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 <sys/cdefs.h>
|
|
#ifndef lint
|
|
__RCSID("$NetBSD: bufcache.c,v 1.30 2020/03/02 09:50:12 mrg Exp $");
|
|
#endif /* not lint */
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/buf.h>
|
|
#define __EXPOSE_MOUNT
|
|
#include <sys/mount.h>
|
|
#include <sys/sysctl.h>
|
|
#include <sys/vnode.h>
|
|
|
|
#include <uvm/uvm_extern.h>
|
|
|
|
#include <err.h>
|
|
#include <errno.h>
|
|
#include <inttypes.h>
|
|
#include <math.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <stdbool.h>
|
|
|
|
#include <miscfs/specfs/specdev.h>
|
|
|
|
#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);
|
|
}
|