/*-
* Copyright (c) 1998, 2000, 2001, 2007, 2019, 2020
* The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation by:
* - Jason R. Thorpe of the Numerical Aerospace Simulation Facility,
* NASA Ames Research Center.
* - Simon Burge and Luke Mewburn of Wasabi Systems, Inc.
*
* 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.
*/
/*
* Copyright (c) 1980, 1986, 1991, 1993
* The Regents of the University of California. All rights reserved.
*
* 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.
* 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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
__COPYRIGHT("@(#) Copyright (c) 1980, 1986, 1991, 1993\
The Regents of the University of California. All rights reserved.");
#endif /* not lint */
/*
* Print single word. `ovflow' is number of characters didn't fit
* on the last word. `fmt' is a format string to print this word.
* It must contain asterisk for field width. `width' is a width
* occupied by this word. `fixed' is a number of constant chars in
* `fmt'. `val' is a value to be printed using format string `fmt'.
*/
#define PRWORD(ovflw, fmt, width, fixed, val) do { \
(ovflw) += printf((fmt), \
(width) - (fixed) - (ovflw) > 0 ? \
(width) - (fixed) - (ovflw) : 0, \
(val)) - (width); \
if ((ovflw) < 0) \
(ovflw) = 0; \
} while (0)
/* Namelist and memory file names. */
char *nlistf, *memf;
/* allow old usage [vmstat 1] */
#define BACKWARD_COMPATIBILITY
static const int clockrate_mib[] = { CTL_KERN, KERN_CLOCKRATE };
static const int vmmeter_mib[] = { CTL_VM, VM_METER };
static const int uvmexp2_mib[] = { CTL_VM, VM_UVMEXP2 };
static const int boottime_mib[] = { CTL_KERN, KERN_BOOTTIME };
static int numdisks = 2;
int
main(int argc, char *argv[])
{
int c, todo, verbose, wide;
struct timespec interval;
int reps;
const char *histname, *hashname;
char errbuf[_POSIX2_LINE_MAX];
histname = hashname = NULL;
memf = nlistf = NULL;
reps = todo = verbose = wide = 0;
interval.tv_sec = 0;
interval.tv_nsec = 0;
while ((c = getopt(argc, argv, "Cc:efh:HilLM:mN:n:stu:UvWw:")) != -1) {
switch (c) {
case 'c':
reps = atoi(optarg);
break;
case 'C':
todo |= POOLCACHESTAT;
break;
case 'e':
todo |= EVCNTSTAT;
break;
case 'f':
todo |= FORKSTAT;
break;
case 'h':
hashname = optarg;
/* FALLTHROUGH */
case 'H':
todo |= HASHSTAT;
break;
case 'i':
todo |= INTRSTAT;
break;
case 'l':
todo |= HISTLIST;
break;
case 'L':
todo |= HASHLIST;
break;
case 'M':
memf = optarg;
break;
case 'm':
todo |= MEMSTAT;
break;
case 'N':
nlistf = optarg;
break;
case 'n':
numdisks = atoi(optarg);
break;
case 's':
todo |= SUMSTAT;
break;
case 't':
todo |= VMTOTAL;
break;
case 'u':
histname = optarg;
/* FALLTHROUGH */
case 'U':
todo |= HISTDUMP;
break;
case 'v':
verbose++;
break;
case 'W':
wide++;
break;
case 'w':
interval.tv_sec = atol(optarg);
break;
case '?':
default:
usage();
}
}
argc -= optind;
argv += optind;
#ifdef BACKWARD_COMPATIBILITY
if (*argv) {
interval.tv_sec = atol(*argv);
if (*++argv)
reps = atoi(*argv);
}
#endif
if (interval.tv_sec) {
if (!reps)
reps = -1;
} else if (reps)
interval.tv_sec = 1;
/*
* Statistics dumping is incompatible with the default
* VMSTAT/dovmstat() output. So perform the interval/reps handling
* for it here.
*/
if ((todo & (VMSTAT|VMTOTAL)) == 0) {
for (;;) {
if (todo & (HISTLIST|HISTDUMP)) {
if ((todo & (HISTLIST|HISTDUMP)) ==
(HISTLIST|HISTDUMP))
errx(1, "you may list or dump,"
" but not both!");
if (memf != NULL)
hist_traverse(todo, histname);
else
hist_traverse_sysctl(todo, histname);
(void)putchar('\n');
}
if (todo & FORKSTAT) {
doforkst();
(void)putchar('\n');
}
if (todo & MEMSTAT) {
dopool(verbose, wide);
(void)putchar('\n');
}
if (todo & POOLCACHESTAT) {
dopoolcache(verbose);
(void)putchar('\n');
}
if (todo & SUMSTAT) {
dosum();
(void)putchar('\n');
}
if (todo & INTRSTAT) {
dointr(verbose);
(void)putchar('\n');
}
if (todo & EVCNTSTAT) {
doevcnt(verbose, EVCNT_TYPE_ANY);
(void)putchar('\n');
}
if (todo & (HASHLIST|HASHSTAT)) {
if ((todo & (HASHLIST|HASHSTAT)) ==
(HASHLIST|HASHSTAT))
errx(1, "you may list or display,"
" but not both!");
dohashstat(verbose, todo, hashname);
(void)putchar('\n');
}
fflush(stdout);
if (reps >= 0 && --reps <=0)
break;
(void)nanosleep(&interval, NULL);
}
} else {
if ((todo & (VMSTAT|VMTOTAL)) == (VMSTAT|VMTOTAL)) {
errx(1, "you may not both do vmstat and vmtotal");
}
if (todo & VMSTAT)
dovmstat(&interval, reps);
if (todo & VMTOTAL)
dovmtotal(&interval, reps);
}
return 0;
}
void
getnlist(int todo)
{
static int done = 0;
int c;
size_t i;
if ((c = kvm_nlist(kd, namelist)) != 0) {
int doexit = 0;
if (c == -1)
errx(1, "kvm_nlist: %s %s",
"namelist", kvm_geterr(kd));
for (i = 0; i < __arraycount(namelist)-1; i++)
if (namelist[i].n_type == 0) {
if (doexit++ == 0)
(void)fprintf(stderr,
"%s: undefined symbols:",
getprogname());
(void)fprintf(stderr, " %s",
namelist[i].n_name);
}
if (doexit) {
(void)fputc('\n', stderr);
exit(1);
}
}
char **
choosedrives(char **argv)
{
size_t i, j, k;
/*
* Choose drives to be displayed. Priority goes to (in order) drives
* supplied as arguments, default drives. If everything isn't filled
* in and there are drives not taken care of, display the first few
* that fit.
*/
#define BACKWARD_COMPATIBILITY
for (ndrives = 0; *argv; ++argv) {
#ifdef BACKWARD_COMPATIBILITY
if (isdigit((unsigned char)**argv))
break;
#endif
for (i = 0; i < ndrive; i++) {
if (strcmp(dr_name[i], *argv))
continue;
drv_select[i] = 1;
++ndrives;
break;
}
}
/*
* Pick the most active drives. Must read the stats once before
* sorting so that there is current IO data, before selecting
* just the first 'numdisks' (default 2) drives.
*/
drvreadstats();
for (i = 0; i < ndrive && ndrives < numdisks; i++) {
uint64_t high_bytes = 0, bytes;
k = ndrive;
for (j = 0; j < ndrive; j++) {
if (drv_select[j])
continue;
bytes = cur.rbytes[j] + cur.wbytes[j];
if (bytes > high_bytes) {
high_bytes = bytes;
k = j;
}
}
if (k != ndrive) {
drv_select[k] = 1;
++ndrives;
}
}
(void)printf(" r b avm fre flt re pi po fr sr ");
for (i = 0; i < ndrive; i++)
if (drv_select[i])
(void)printf("%c%c ", dr_name[i][0],
dr_name[i][strlen(dr_name[i]) - 1]);
(void)printf(" in sy cs us sy id\n");
hdrcnt = winlines - 2;
}
/*
* Force a header to be prepended to the next output.
*/
void
/*ARGSUSED*/
needhdr(int dummy)
{
hdrcnt = 1;
}
long
pct(u_long top, u_long bot)
{
long ans;
if (bot == 0)
return (0);
ans = (long)((quad_t)top * 100 / bot);
return (ans);
}
/*
* The "active" and "inactive" variables
* are now estimated by the kernel and sadly
* can not easily be dug out of a crash dump.
*/
ssize = sizeof(uvmexp);
memset(&uvmexp, 0, ssize);
active_kernel = (memf == NULL);
if (active_kernel) {
/* only on active kernel */
if (sysctl(uvmexp2_mib, __arraycount(uvmexp2_mib), &uvmexp,
&ssize, NULL, 0) == -1)
warn("sysctl vm.uvmexp2 failed");
} else {
struct uvmexp uvmexp_kernel;
struct pool pool, *pp = &pool;
struct pool_allocator pa;
TAILQ_HEAD(,pool) pool_head;
void *addr;
uint64_t bytes;
for (curhash = khashes; curhash->description; curhash++) {
if (hashnl[curhash->hashsize].n_value == 0 ||
hashnl[curhash->hashtbl].n_value == 0)
continue;
if (hashname != NULL &&
strcmp(hashnl[curhash->hashsize].n_name + 1, hashname))
continue;
switch (curhash->type) {
case HASH_LIST:
elemsize = sizeof(*hashtbl_list);
break;
case HASH_SLIST:
elemsize = sizeof(*hashtbl_slist);
break;
case HASH_TAILQ:
elemsize = sizeof(*hashtbl_tailq);
break;
default:
/* shouldn't get here */
continue;
}
deref_kptr((void *)hashnl[curhash->hashsize].n_value,
&hashsize, sizeof(hashsize),
hashnl[curhash->hashsize].n_name);
hashsize++;
deref_kptr((void *)hashnl[curhash->hashtbl].n_value,
&hashaddr, sizeof(hashaddr),
hashnl[curhash->hashtbl].n_name);
if (verbose)
(void)printf(
"%s %lu, %s %p, offset %ld, elemsize %llu\n",
hashnl[curhash->hashsize].n_name + 1, hashsize,
hashnl[curhash->hashtbl].n_name + 1, hashaddr,
(long)curhash->offset,
(unsigned long long)elemsize);
thissize = hashsize * elemsize;
if (hashbuf == NULL || thissize > hashbufsize) {
if ((nhashbuf = realloc(hashbuf, thissize)) == NULL)
errx(1, "malloc hashbuf %llu",
(unsigned long long)hashbufsize);
hashbuf = nhashbuf;
hashbufsize = thissize;
}
deref_kptr(hashaddr, hashbuf, thissize,
hashnl[curhash->hashtbl].n_name);
used = 0;
items = maxchain = 0;
if (curhash->type == HASH_LIST) {
hashtbl_list = hashbuf;
hashtbl_slist = NULL;
hashtbl_tailq = NULL;
} else if (curhash->type == HASH_SLIST) {
hashtbl_list = NULL;
hashtbl_slist = hashbuf;
hashtbl_tailq = NULL;
} else {
hashtbl_list = NULL;
hashtbl_slist = NULL;
hashtbl_tailq = hashbuf;
}
for (i = 0; i < hashsize; i++) {
if (curhash->type == HASH_LIST)
nextaddr = LIST_FIRST(&hashtbl_list[i]);
else if (curhash->type == HASH_SLIST)
nextaddr = SLIST_FIRST(&hashtbl_slist[i]);
else
nextaddr = TAILQ_FIRST(&hashtbl_tailq[i]);
if (nextaddr == NULL)
continue;
if (verbose)
(void)printf("%5lu: %p\n", i, nextaddr);
used++;
chain = 0;
do {
chain++;
deref_kptr((char *)nextaddr + curhash->offset,
&nextaddr, sizeof(void *),
"hash chain corrupted");
if (verbose > 1)
(void)printf("got nextaddr as %p\n",
nextaddr);
} while (nextaddr != NULL);
items += chain;
if (verbose && chain > 1)
(void)printf("\tchain = %d\n", chain);
if (chain > maxchain)
maxchain = chain;
}
(void)printf("%-16s %8ld %8d %8.2f %8d %8.2f %8d\n",
hashnl[curhash->hashsize].n_name + 1,
hashsize, used, used * 100.0 / hashsize,
items, used ? (double)items / used : 0.0, maxchain);
}
}
void
dohashstat_sysctl(int verbose, int todo, const char *hashname)
{
struct hashstat_sysctl hash, *data, *hs;
int mib[3];
int error;
size_t i, len, miblen;
/*
* kread reads something from the kernel, given its nlist index in namelist[].
*/
void
kread(struct nlist *nl, int nlx, void *addr, size_t size)
{
const char *sym;
sym = nl[nlx].n_name;
if (*sym == '_')
++sym;
if (nl[nlx].n_type == 0 || nl[nlx].n_value == 0)
errx(1, "symbol %s not defined", sym);
deref_kptr((void *)nl[nlx].n_value, addr, size, sym);
}
/*
* Dereference the kernel pointer `kptr' and fill in the local copy
* pointed to by `ptr'. The storage space must be pre-allocated,
* and the size of the copy passed in `len'.
*/
void
deref_kptr(const void *kptr, void *ptr, size_t len, const char *msg)
{
if (histhead.lh_first == NULL) {
warnx("No active kernel history logs.");
return;
}
if (todo & HISTLIST)
(void)printf("Active kernel histories:");
for (histkva = LIST_FIRST(&histhead); histkva != NULL;
histkva = LIST_NEXT(&hist, list)) {
deref_kptr(histkva, &hist, sizeof(hist), "histkva");
if (name == NULL || hist.namelen > namelen) {
if (name != NULL)
free(name);
namelen = hist.namelen;
if ((name = malloc(namelen + 1)) == NULL)
err(1, "malloc history name");
}
deref_kptr(hist.name, name, namelen, "history name");
name[namelen] = '\0';
if (todo & HISTLIST)
(void)printf(" %s", name);
else {
/*
* If we're dumping all histories, do it, else
* check to see if this is the one we want.
*/
if (histname == NULL || strcmp(histname, name) == 0) {
if (histname == NULL)
(void)printf(
"\nkernel history `%s':\n", name);
hist_dodump(&hist);
}
}
}
bintime2timeval(&e->bt, &tv);
(void)printf("%06ld.%06ld ", (long int)tv.tv_sec,
(long int)tv.tv_usec);
(void)printf("%s#%" PRId32 "@%" PRId32 ": ",
fn, e->call, e->cpunum);
(void)printf(fmt, e->v[0], e->v[1], e->v[2], e->v[3]);
(void)putchar('\n');
}
i = (i + 1) % histp->n;
} while (i != histp->f);
free(histents);
free(fmt);
free(fn);
}
void
hist_traverse_sysctl(int todo, const char *histname)
{
int error;
int mib[4];
unsigned int i;
size_t len, miblen;
struct sysctlnode query, histnode[32];
/* retrieve names of available histories */
miblen = __arraycount(mib);
error = sysctlnametomib("kern.hist", mib, &miblen);
if (error != 0) {
if (errno == ENOENT) {
warnx("kernel history is not compiled into the kernel.");
return;
} else
err(EXIT_FAILURE, "nametomib kern.hist failed");
}
/* get the list of nodenames below kern.hist */
mib[2] = CTL_QUERY;
memset(&query, 0, sizeof(query));
query.sysctl_flags = SYSCTL_VERSION;
len = sizeof(histnode);
error = sysctl(mib, 3, &histnode[0], &len, &query, sizeof(query));
if (error != 0) {
err(1, "query failed");
return;
}
if (len == 0) {
warnx("No active kernel history logs.");
return;
}
len = len / sizeof(histnode[0]); /* get # of entries returned */
if (todo & HISTLIST)
(void)printf("Active kernel histories:");
for (i = 0; i < len; i++) {
if (todo & HISTLIST)
(void)printf(" %s", histnode[i].sysctl_name);
else {
/*
* If we're dumping all histories, do it, else
* check to see if this is the one we want.
*/
if (histname == NULL ||
strcmp(histname, histnode[i].sysctl_name) == 0) {
if (histname == NULL)
(void)printf(
"\nkernel history `%s':\n",
histnode[i].sysctl_name);
mib[2] = histnode[i].sysctl_num;
mib[3] = CTL_EOL;
hist_dodump_sysctl(mib, 4);
}
}
}
if (todo & HISTLIST)
(void)putchar('\n');
else if (mib[2] == CTL_QUERY)
warnx("history %s not found", histname);
}
/*
* Actually dump the history buffer at the specified KVA.
*/
void
hist_dodump_sysctl(int mib[], unsigned int miblen)
{
struct sysctl_history *hist;
struct timeval tv;
struct sysctl_history_event *e;
size_t histsize;
char *strp;
unsigned i;
char *fmt = NULL, *fn = NULL;
hist = NULL;
histsize = 0;
do {
errno = 0;
if (sysctl(mib, miblen, hist, &histsize, NULL, 0) == 0)
break;
if (errno != ENOMEM)
break;
if ((hist = realloc(hist, histsize)) == NULL)
errx(1, "realloc history buffer");
} while (errno == ENOMEM);
if (errno != 0)
err(1, "sysctl failed");