/* $OpenBSD: dkstats.c,v 1.42 2024/05/06 16:54:22 cheloha Exp $ */
/* $NetBSD: dkstats.c,v 1.1 1996/05/10 23:19:27 thorpej Exp $ */
/*
* Copyright (c) 1996 John M. Vinopal
* 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. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed for the NetBSD Project
* by John M. Vinopal.
* 4. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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/time.h>
#include <sys/disk.h>
#include <sys/sched.h>
#include <sys/sysctl.h>
#include <sys/tty.h>
#include <err.h>
#include <fcntl.h>
#include <kvm.h>
#include <limits.h>
#include <nlist.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "dkstats.h"
#if !defined(NOKVM)
static struct nlist namelist[] = {
#define X_TK_NIN 0 /* sysctl */
{ "_tk_nin" },
#define X_TK_NOUT 1 /* sysctl */
{ "_tk_nout" },
#define X_CP_TIME 2 /* sysctl */
{ "_cp_time" },
#define X_HZ 3 /* sysctl */
{ "_hz" },
#define X_STATHZ 4 /* sysctl */
{ "_stathz" },
#define X_DISK_COUNT 5 /* sysctl */
{ "_disk_count" },
#define X_DISKLIST 6 /* sysctl */
{ "_disklist" },
{ NULL },
};
#define KVM_ERROR(_string) { \
warnx("%s", (_string)); \
errx(1, "%s", kvm_geterr(kd)); \
}
/*
* Dereference the namelist pointer `v' and fill in the local copy
* 'p' which is of size 's'.
*/
#define deref_nl(v, p, s) deref_kptr((void *)namelist[(v)].n_value, (p), (s));
static void deref_kptr(void *, void *, size_t);
#endif /* !defined(NOKVM) */
/* Structures to hold the statistics. */
struct _disk cur, last;
/* Kernel pointers: nlistf and memf defined in calling program. */
#if !defined(NOKVM)
extern kvm_t *kd;
#endif
extern char *nlistf;
extern char *memf;
#if !defined(NOKVM)
/* Pointer to list of disks. */
static struct disk *dk_drivehead = NULL;
#endif
/* Backward compatibility references. */
int dk_ndrive = 0;
int *dk_select;
char **dr_name;
#define SWAP(fld) tmp = cur.fld; \
cur.fld -= last.fld; \
last.fld = tmp
/*
* Take the delta between the present values and the last recorded
* values, storing the present values in the 'last' structure, and
* the delta values in the 'cur' structure.
*/
void
dkswap(void)
{
u_int64_t tmp;
int i;
for (i = 0; i < cur.dk_ndrive; i++) {
struct timeval tmp_timer;
if (!cur.dk_select[i])
continue;
/* Delta Values. */
SWAP(dk_rxfer[i]);
SWAP(dk_wxfer[i]);
SWAP(dk_seek[i]);
SWAP(dk_rbytes[i]);
SWAP(dk_wbytes[i]);
/* Delta Time. */
tmp_timer = cur.dk_time[i];
timersub(&tmp_timer, &last.dk_time[i], &cur.dk_time[i]);
last.dk_time[i] = tmp_timer;
}
for (i = 0; i < CPUSTATES; i++) {
long ltmp;
ltmp = cur.cp_time[i];
cur.cp_time[i] -= last.cp_time[i];
last.cp_time[i] = ltmp;
}
SWAP(tk_nin);
SWAP(tk_nout);
#undef SWAP
}
/*
* Read the disk statistics for each disk in the disk list.
* Also collect statistics for tty i/o and cpu ticks.
*/
void
dkreadstats(void)
{
#if !defined(NOKVM)
struct disk cur_disk, *p;
#endif
int i, j, mib[3];
size_t size;
char *disknames, *name, *bufpp, **dk_name;
struct diskstats *q;
last.dk_ndrive = cur.dk_ndrive;
if (nlistf == NULL && memf == NULL) {
/* Get the number of attached drives. */
mib[0] = CTL_HW;
mib[1] = HW_DISKCOUNT;
size = sizeof(dk_ndrive);
if (sysctl(mib, 2, &dk_ndrive, &size, NULL, 0) == -1 ) {
warn("could not read hw.diskcount");
dk_ndrive = 0;
}
if (cur.dk_ndrive != dk_ndrive) {
/* Re-read the disk names. */
dk_name = calloc((size_t)dk_ndrive, sizeof(char *));
if (dk_name == NULL)
err(1, NULL);
mib[0] = CTL_HW;
mib[1] = HW_DISKNAMES;
size = 0;
if (sysctl(mib, 2, NULL, &size, NULL, 0) == -1)
err(1, "can't get hw.disknames");
disknames = malloc(size);
if (disknames == NULL)
err(1, NULL);
if (sysctl(mib, 2, disknames, &size, NULL, 0) == -1)
err(1, "can't get hw.disknames");
bufpp = disknames;
for (i = 0; i < dk_ndrive &&
(name = strsep(&bufpp, ",")) != NULL; i++)
dk_name[i] = name;
for (i = 0; i < dk_ndrive; i++) {
char *ep = strchr(dk_name[i], ':');
if (ep)
*ep = '\0';
}
disknames = cur.dk_name[0]; /* To free old names. */
if (dk_ndrive < cur.dk_ndrive) {
for (i = 0, j = 0; i < dk_ndrive; i++, j++) {
while (j < cur.dk_ndrive &&
strcmp(cur.dk_name[j], dk_name[i]))
j++;
if (i == j) continue;
if (j >= cur.dk_ndrive) {
cur.dk_select[i] = 1;
last.dk_rxfer[i] = 0;
last.dk_wxfer[i] = 0;
last.dk_seek[i] = 0;
last.dk_rbytes[i] = 0;
last.dk_wbytes[i] = 0;
memset(&last.dk_time[i], 0,
sizeof(struct timeval));
continue;
}
cur.dk_select[i] = cur.dk_select[j];
last.dk_rxfer[i] = last.dk_rxfer[j];
last.dk_wxfer[i] = last.dk_wxfer[j];
last.dk_seek[i] = last.dk_seek[j];
last.dk_rbytes[i] = last.dk_rbytes[j];
last.dk_wbytes[i] = last.dk_wbytes[j];
last.dk_time[i] = last.dk_time[j];
}
cur.dk_select = reallocarray(cur.dk_select,
dk_ndrive, sizeof(*cur.dk_select));
cur.dk_rxfer = reallocarray(cur.dk_rxfer,
dk_ndrive, sizeof(*cur.dk_rxfer));
cur.dk_wxfer = reallocarray(cur.dk_wxfer,
dk_ndrive, sizeof(*cur.dk_wxfer));
cur.dk_seek = reallocarray(cur.dk_seek,
dk_ndrive, sizeof(*cur.dk_seek));
cur.dk_rbytes = reallocarray(cur.dk_rbytes,
dk_ndrive, sizeof(*cur.dk_rbytes));
cur.dk_wbytes = reallocarray(cur.dk_wbytes,
dk_ndrive, sizeof(*cur.dk_wbytes));
cur.dk_time = reallocarray(cur.dk_time,
dk_ndrive, sizeof(*cur.dk_time));
last.dk_rxfer = reallocarray(last.dk_rxfer,
dk_ndrive, sizeof(*last.dk_rxfer));
last.dk_wxfer = reallocarray(last.dk_wxfer,
dk_ndrive, sizeof(*last.dk_wxfer));
last.dk_seek = reallocarray(last.dk_seek,
dk_ndrive, sizeof(*last.dk_seek));
last.dk_rbytes = reallocarray(last.dk_rbytes,
dk_ndrive, sizeof(*last.dk_rbytes));
last.dk_wbytes = reallocarray(last.dk_wbytes,
dk_ndrive, sizeof(*last.dk_wbytes));
last.dk_time = reallocarray(last.dk_time,
dk_ndrive, sizeof(*last.dk_time));
if (!cur.dk_select || !cur.dk_rxfer ||
!cur.dk_wxfer || !cur.dk_seek ||
!cur.dk_rbytes || !cur.dk_wbytes ||
!cur.dk_time || !last.dk_rxfer ||
!last.dk_wxfer || !last.dk_seek ||
!last.dk_rbytes || !last.dk_wbytes ||
!last.dk_time)
errx(1, "Memory allocation failure.");
} else {
cur.dk_select = reallocarray(cur.dk_select,
dk_ndrive, sizeof(*cur.dk_select));
cur.dk_rxfer = reallocarray(cur.dk_rxfer,
dk_ndrive, sizeof(*cur.dk_rxfer));
cur.dk_wxfer = reallocarray(cur.dk_wxfer,
dk_ndrive, sizeof(*cur.dk_wxfer));
cur.dk_seek = reallocarray(cur.dk_seek,
dk_ndrive, sizeof(*cur.dk_seek));
cur.dk_rbytes = reallocarray(cur.dk_rbytes,
dk_ndrive, sizeof(*cur.dk_rbytes));
cur.dk_wbytes = reallocarray(cur.dk_wbytes,
dk_ndrive, sizeof(*cur.dk_wbytes));
cur.dk_time = reallocarray(cur.dk_time,
dk_ndrive, sizeof(*cur.dk_time));
last.dk_rxfer = reallocarray(last.dk_rxfer,
dk_ndrive, sizeof(*last.dk_rxfer));
last.dk_wxfer = reallocarray(last.dk_wxfer,
dk_ndrive, sizeof(*last.dk_wxfer));
last.dk_seek = reallocarray(last.dk_seek,
dk_ndrive, sizeof(*last.dk_seek));
last.dk_rbytes = reallocarray(last.dk_rbytes,
dk_ndrive, sizeof(*last.dk_rbytes));
last.dk_wbytes = reallocarray(last.dk_wbytes,
dk_ndrive, sizeof(*last.dk_wbytes));
last.dk_time = reallocarray(last.dk_time,
dk_ndrive, sizeof(*last.dk_time));
if (!cur.dk_select || !cur.dk_rxfer ||
!cur.dk_wxfer || !cur.dk_seek ||
!cur.dk_rbytes || !cur.dk_wbytes ||
!cur.dk_time || !last.dk_rxfer ||
!last.dk_wxfer || !last.dk_seek ||
!last.dk_rbytes || !last.dk_wbytes ||
!last.dk_time)
errx(1, "Memory allocation failure.");
for (i = dk_ndrive - 1, j = cur.dk_ndrive - 1;
i >= 0; i--) {
if (j < 0 ||
strcmp(cur.dk_name[j], dk_name[i]))
{
cur.dk_select[i] = 1;
last.dk_rxfer[i] = 0;
last.dk_wxfer[i] = 0;
last.dk_seek[i] = 0;
last.dk_rbytes[i] = 0;
last.dk_wbytes[i] = 0;
memset(&last.dk_time[i], 0,
sizeof(struct timeval));
continue;
}
if (i > j) {
cur.dk_select[i] =
cur.dk_select[j];
last.dk_rxfer[i] =
last.dk_rxfer[j];
last.dk_wxfer[i] =
last.dk_wxfer[j];
last.dk_seek[i] =
last.dk_seek[j];
last.dk_rbytes[i] =
last.dk_rbytes[j];
last.dk_wbytes[i] =
last.dk_wbytes[j];
last.dk_time[i] =
last.dk_time[j];
}
j--;
}
}
cur.dk_ndrive = dk_ndrive;
free(disknames);
cur.dk_name = dk_name;
dr_name = cur.dk_name;
dk_select = cur.dk_select;
}
size = cur.dk_ndrive * sizeof(struct diskstats);
mib[0] = CTL_HW;
mib[1] = HW_DISKSTATS;
q = malloc(size);
if (q == NULL)
err(1, NULL);
if (sysctl(mib, 2, q, &size, NULL, 0) == -1) {
#ifdef DEBUG
warn("could not read hw.diskstats");
#endif /* DEBUG */
memset(q, 0, cur.dk_ndrive * sizeof(struct diskstats));
}
for (i = 0; i < cur.dk_ndrive; i++) {
cur.dk_rxfer[i] = q[i].ds_rxfer;
cur.dk_wxfer[i] = q[i].ds_wxfer;
cur.dk_seek[i] = q[i].ds_seek;
cur.dk_rbytes[i] = q[i].ds_rbytes;
cur.dk_wbytes[i] = q[i].ds_wbytes;
cur.dk_time[i] = q[i].ds_time;
}
free(q);
size = sizeof(cur.cp_time);
mib[0] = CTL_KERN;
mib[1] = KERN_CPTIME;
if (sysctl(mib, 2, cur.cp_time, &size, NULL, 0) == -1) {
warn("could not read kern.cp_time");
memset(cur.cp_time, 0, sizeof(cur.cp_time));
}
size = sizeof(cur.tk_nin);
mib[0] = CTL_KERN;
mib[1] = KERN_TTY;
mib[2] = KERN_TTY_TKNIN;
if (sysctl(mib, 3, &cur.tk_nin, &size, NULL, 0) == -1) {
warn("could not read kern.tty.tk_nin");
cur.tk_nin = 0;
}
size = sizeof(cur.tk_nin);
mib[0] = CTL_KERN;
mib[1] = KERN_TTY;
mib[2] = KERN_TTY_TKNOUT;
if (sysctl(mib, 3, &cur.tk_nout, &size, NULL, 0) == -1) {
warn("could not read kern.tty.tk_nout");
cur.tk_nout = 0;
}
} else {
#if !defined(NOKVM)
p = dk_drivehead;
for (i = 0; i < cur.dk_ndrive; i++) {
deref_kptr(p, &cur_disk, sizeof(cur_disk));
cur.dk_rxfer[i] = cur_disk.dk_rxfer;
cur.dk_wxfer[i] = cur_disk.dk_wxfer;
cur.dk_seek[i] = cur_disk.dk_seek;
cur.dk_rbytes[i] = cur_disk.dk_rbytes;
cur.dk_wbytes[i] = cur_disk.dk_wbytes;
cur.dk_time[i] = cur_disk.dk_time;
p = TAILQ_NEXT(&cur_disk, dk_link);
}
deref_nl(X_CP_TIME, cur.cp_time, sizeof(cur.cp_time));
deref_nl(X_TK_NIN, &cur.tk_nin, sizeof(cur.tk_nin));
deref_nl(X_TK_NOUT, &cur.tk_nout, sizeof(cur.tk_nout));
#endif /* !defined(NOKVM) */
}
}
/*
* Perform all of the initialization and memory allocation needed to
* track disk statistics.
*/
int
dkinit(int sel)
{
#if !defined(NOKVM)
struct disklist_head disk_head;
struct disk cur_disk, *p;
char errbuf[_POSIX2_LINE_MAX];
#endif
static int once = 0;
extern int hz;
int i, mib[2];
size_t size;
struct clockinfo clkinfo;
char *disknames, *name, *bufpp;
if (once)
return(1);
if (nlistf != NULL || memf != NULL) {
#if !defined(NOKVM)
/* Open the kernel. */
if (kd == NULL &&
(kd = kvm_openfiles(nlistf, memf, NULL, O_RDONLY,
errbuf)) == NULL)
errx(1, "kvm_openfiles: %s", errbuf);
/* Obtain the namelist symbols from the kernel. */
if (kvm_nlist(kd, namelist))
KVM_ERROR("kvm_nlist failed to read symbols.");
/* Get the number of attached drives. */
deref_nl(X_DISK_COUNT, &cur.dk_ndrive, sizeof(cur.dk_ndrive));
if (cur.dk_ndrive < 0)
errx(1, "invalid _disk_count %d.", cur.dk_ndrive);
/* Get a pointer to the first disk. */
deref_nl(X_DISKLIST, &disk_head, sizeof(disk_head));
dk_drivehead = TAILQ_FIRST(&disk_head);
/* Get ticks per second. */
deref_nl(X_STATHZ, &hz, sizeof(hz));
if (!hz)
deref_nl(X_HZ, &hz, sizeof(hz));
#endif /* !defined(NOKVM) */
} else {
/* Get the number of attached drives. */
mib[0] = CTL_HW;
mib[1] = HW_DISKCOUNT;
size = sizeof(cur.dk_ndrive);
if (sysctl(mib, 2, &cur.dk_ndrive, &size, NULL, 0) == -1 ) {
warn("could not read hw.diskcount");
cur.dk_ndrive = 0;
}
/* Get ticks per second. */
mib[0] = CTL_KERN;
mib[1] = KERN_CLOCKRATE;
size = sizeof(clkinfo);
if (sysctl(mib, 2, &clkinfo, &size, NULL, 0) == -1) {
warn("could not read kern.clockrate");
hz = 0;
} else
hz = clkinfo.stathz;
}
/* allocate space for the statistics */
cur.dk_time = calloc((size_t)cur.dk_ndrive, sizeof(struct timeval));
cur.dk_rxfer = calloc((size_t)cur.dk_ndrive, sizeof(u_int64_t));
cur.dk_wxfer = calloc((size_t)cur.dk_ndrive, sizeof(u_int64_t));
cur.dk_seek = calloc((size_t)cur.dk_ndrive, sizeof(u_int64_t));
cur.dk_rbytes = calloc((size_t)cur.dk_ndrive, sizeof(u_int64_t));
cur.dk_wbytes = calloc((size_t)cur.dk_ndrive, sizeof(u_int64_t));
cur.dk_select = calloc((size_t)cur.dk_ndrive, sizeof(int));
cur.dk_name = calloc((size_t)cur.dk_ndrive, sizeof(char *));
last.dk_time = calloc((size_t)cur.dk_ndrive, sizeof(struct timeval));
last.dk_rxfer = calloc((size_t)cur.dk_ndrive, sizeof(u_int64_t));
last.dk_wxfer = calloc((size_t)cur.dk_ndrive, sizeof(u_int64_t));
last.dk_seek = calloc((size_t)cur.dk_ndrive, sizeof(u_int64_t));
last.dk_rbytes = calloc((size_t)cur.dk_ndrive, sizeof(u_int64_t));
last.dk_wbytes = calloc((size_t)cur.dk_ndrive, sizeof(u_int64_t));
if (!cur.dk_time || !cur.dk_rxfer || !cur.dk_wxfer || !cur.dk_seek ||
!cur.dk_rbytes || !cur.dk_wbytes || !cur.dk_select ||
!cur.dk_name || !last.dk_time || !last.dk_rxfer ||
!last.dk_wxfer || !last.dk_seek || !last.dk_rbytes ||
!last.dk_wbytes)
errx(1, "Memory allocation failure.");
/* Set up the compatibility interfaces. */
dk_ndrive = cur.dk_ndrive;
dk_select = cur.dk_select;
dr_name = cur.dk_name;
/* Read the disk names and set initial selection. */
if (nlistf == NULL && memf == NULL) {
mib[0] = CTL_HW;
mib[1] = HW_DISKNAMES;
size = 0;
if (sysctl(mib, 2, NULL, &size, NULL, 0) == -1)
err(1, "can't get hw.disknames");
disknames = malloc(size);
if (disknames == NULL)
err(1, NULL);
if (sysctl(mib, 2, disknames, &size, NULL, 0) == -1)
err(1, "can't get hw.disknames");
bufpp = disknames;
for (i = 0; i < dk_ndrive && (name = strsep(&bufpp, ",")) != NULL; i++) {
cur.dk_name[i] = name;
cur.dk_select[i] = sel;
}
for (i = 0; i < dk_ndrive; i++) {
char *ep = strchr(cur.dk_name[i], ':');
if (ep)
*ep = '\0';
}
} else {
#if !defined(NOKVM)
p = dk_drivehead;
for (i = 0; i < cur.dk_ndrive; i++) {
char buf[10];
deref_kptr(p, &cur_disk, sizeof(cur_disk));
deref_kptr(cur_disk.dk_name, buf, sizeof(buf));
cur.dk_name[i] = strdup(buf);
if (!cur.dk_name[i])
errx(1, "Memory allocation failure.");
cur.dk_select[i] = sel;
p = TAILQ_NEXT(&cur_disk, dk_link);
}
#endif /* !defined(NOKVM) */
}
/* Never do this initialization again. */
once = 1;
return(1);
}
#if !defined(NOKVM)
/*
* 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'.
*/
static void
deref_kptr(void *kptr, void *ptr, size_t len)
{
char buf[128];
if (kvm_read(kd, (u_long)kptr, ptr, len) != len) {
memset(buf, 0, sizeof(buf));
snprintf(buf, (sizeof(buf) - 1),
"can't dereference kptr 0x%lx", (u_long)kptr);
KVM_ERROR(buf);
}
}
#endif /* !defined(NOKVM) */