Home | History | Annotate | Line # | Download | only in vmstat
drvstats.c revision 1.5
      1 /*	$NetBSD: drvstats.c,v 1.5 2009/01/18 07:20:00 lukem Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 1996 John M. Vinopal
      5  * All rights reserved.
      6  *
      7  * Redistribution and use in source and binary forms, with or without
      8  * modification, are permitted provided that the following conditions
      9  * are met:
     10  * 1. Redistributions of source code must retain the above copyright
     11  *    notice, this list of conditions and the following disclaimer.
     12  * 2. Redistributions in binary form must reproduce the above copyright
     13  *    notice, this list of conditions and the following disclaimer in the
     14  *    documentation and/or other materials provided with the distribution.
     15  * 3. All advertising materials mentioning features or use of this software
     16  *    must display the following acknowledgement:
     17  *      This product includes software developed for the NetBSD Project
     18  *      by John M. Vinopal.
     19  * 4. The name of the author may not be used to endorse or promote products
     20  *    derived from this software without specific prior written permission.
     21  *
     22  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
     23  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     24  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
     25  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
     26  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
     27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
     29  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
     30  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     31  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     32  * SUCH DAMAGE.
     33  */
     34 
     35 #include <sys/param.h>
     36 #include <sys/sched.h>
     37 #include <sys/sysctl.h>
     38 #include <sys/time.h>
     39 #include <sys/iostat.h>
     40 
     41 #include <err.h>
     42 #include <fcntl.h>
     43 #include <kvm.h>
     44 #include <limits.h>
     45 #include <nlist.h>
     46 #include <stdio.h>
     47 #include <stdlib.h>
     48 #include <string.h>
     49 #include <unistd.h>
     50 #include "drvstats.h"
     51 
     52 static struct nlist namelist[] = {
     53 #define	X_TK_NIN	0
     54 	{ .n_name = "_tk_nin" },		/* tty characters in */
     55 #define	X_TK_NOUT	1
     56 	{ .n_name = "_tk_nout" },		/* tty characters out */
     57 #define	X_HZ		2
     58 	{ .n_name = "_hz" },		/* ticks per second */
     59 #define	X_STATHZ	3
     60 	{ .n_name = "_stathz" },
     61 #define	X_DRIVE_COUNT	4
     62 	{ .n_name = "_iostat_count" },	/* number of drives */
     63 #define	X_DRIVELIST	5
     64 	{ .n_name = "_iostatlist" },	/* TAILQ of drives */
     65 	{ .n_name = NULL },
     66 };
     67 
     68 /* Structures to hold the statistics. */
     69 struct _drive	cur, last;
     70 
     71 /* Kernel pointers: nlistf and memf defined in calling program. */
     72 static kvm_t	*kd = NULL;
     73 extern char	*nlistf;
     74 extern char	*memf;
     75 extern int	hz;
     76 
     77 /* Pointer to list of drives. */
     78 static struct io_stats	*iostathead = NULL;
     79 /* sysctl hw.drivestats buffer. */
     80 static struct io_sysctl	*drives = NULL;
     81 
     82 /* Backward compatibility references. */
     83 size_t		ndrive = 0;
     84 int		*drv_select;
     85 char		**dr_name;
     86 
     87 #define	KVM_ERROR(_string) do {						\
     88 	warnx("%s", (_string));						\
     89 	errx(1, "%s", kvm_geterr(kd));					\
     90 } while (/* CONSTCOND */0)
     91 
     92 /*
     93  * Dereference the namelist pointer `v' and fill in the local copy
     94  * 'p' which is of size 's'.
     95  */
     96 #define	deref_nl(v, p, s) do {						\
     97 	deref_kptr((void *)namelist[(v)].n_value, (p), (s));		\
     98 } while (/* CONSTCOND */0)
     99 
    100 /* Missing from <sys/time.h> */
    101 #define	timerset(tvp, uvp) do {						\
    102 	((uvp)->tv_sec = (tvp)->tv_sec);				\
    103 	((uvp)->tv_usec = (tvp)->tv_usec);				\
    104 } while (/* CONSTCOND */0)
    105 
    106 static void deref_kptr(void *, void *, size_t);
    107 
    108 /*
    109  * Take the delta between the present values and the last recorded
    110  * values, storing the present values in the 'last' structure, and
    111  * the delta values in the 'cur' structure.
    112  */
    113 void
    114 drvswap(void)
    115 {
    116 	u_int64_t tmp;
    117 	size_t	i;
    118 
    119 #define	SWAP(fld) do {							\
    120 	tmp = cur.fld;							\
    121 	cur.fld -= last.fld;						\
    122 	last.fld = tmp;							\
    123 } while (/* CONSTCOND */0)
    124 
    125 	for (i = 0; i < ndrive; i++) {
    126 		struct timeval	tmp_timer;
    127 
    128 		if (!cur.select[i])
    129 			continue;
    130 
    131 		/* Delta Values. */
    132 		SWAP(rxfer[i]);
    133 		SWAP(wxfer[i]);
    134 		SWAP(seek[i]);
    135 		SWAP(rbytes[i]);
    136 		SWAP(wbytes[i]);
    137 
    138 		/* Delta Time. */
    139 		timerclear(&tmp_timer);
    140 		timerset(&(cur.time[i]), &tmp_timer);
    141 		timersub(&tmp_timer, &(last.time[i]), &(cur.time[i]));
    142 		timerclear(&(last.time[i]));
    143 		timerset(&tmp_timer, &(last.time[i]));
    144 	}
    145 }
    146 
    147 void
    148 tkswap(void)
    149 {
    150 	u_int64_t tmp;
    151 
    152 	SWAP(tk_nin);
    153 	SWAP(tk_nout);
    154 }
    155 
    156 void
    157 cpuswap(void)
    158 {
    159 	double etime;
    160 	u_int64_t tmp;
    161 	int	i, state;
    162 
    163 	for (i = 0; i < CPUSTATES; i++)
    164 		SWAP(cp_time[i]);
    165 
    166 	etime = 0;
    167 	for (state = 0; state < CPUSTATES; ++state) {
    168 		etime += cur.cp_time[state];
    169 	}
    170 	if (etime == 0)
    171 		etime = 1;
    172 	etime /= hz;
    173 	etime /= cur.cp_ncpu;
    174 
    175 	cur.cp_etime = etime;
    176 }
    177 #undef SWAP
    178 
    179 /*
    180  * Read the drive statistics for each drive in the drive list.
    181  * Also collect statistics for tty i/o and CPU ticks.
    182  */
    183 void
    184 drvreadstats(void)
    185 {
    186 	struct io_stats	cur_drive, *p;
    187 	size_t		size, i;
    188 	int		mib[3];
    189 
    190 	p = iostathead;
    191 
    192 	if (memf == NULL) {
    193 		mib[0] = CTL_HW;
    194 		mib[1] = HW_IOSTATS;
    195 		mib[2] = sizeof(struct io_sysctl);
    196 
    197 		size = ndrive * sizeof(struct io_sysctl);
    198 		if (sysctl(mib, 3, drives, &size, NULL, 0) < 0)
    199 			err(1, "sysctl hw.iostats failed");
    200 		for (i = 0; i < ndrive; i++) {
    201 			cur.rxfer[i] = drives[i].rxfer;
    202 			cur.wxfer[i] = drives[i].wxfer;
    203 			cur.seek[i] = drives[i].seek;
    204 			cur.rbytes[i] = drives[i].rbytes;
    205 			cur.wbytes[i] = drives[i].wbytes;
    206 			cur.time[i].tv_sec = drives[i].time_sec;
    207 			cur.time[i].tv_usec = drives[i].time_usec;
    208 		}
    209 
    210 		mib[0] = CTL_KERN;
    211 		mib[1] = KERN_TKSTAT;
    212 		mib[2] = KERN_TKSTAT_NIN;
    213 		size = sizeof(cur.tk_nin);
    214 		if (sysctl(mib, 3, &cur.tk_nin, &size, NULL, 0) < 0)
    215 			cur.tk_nin = 0;
    216 
    217 		mib[2] = KERN_TKSTAT_NOUT;
    218 		size = sizeof(cur.tk_nout);
    219 		if (sysctl(mib, 3, &cur.tk_nout, &size, NULL, 0) < 0)
    220 			cur.tk_nout = 0;
    221 	} else {
    222 		for (i = 0; i < ndrive; i++) {
    223 			deref_kptr(p, &cur_drive, sizeof(cur_drive));
    224 			cur.rxfer[i] = cur_drive.io_rxfer;
    225 			cur.wxfer[i] = cur_drive.io_wxfer;
    226 			cur.seek[i] = cur_drive.io_seek;
    227 			cur.rbytes[i] = cur_drive.io_rbytes;
    228 			cur.wbytes[i] = cur_drive.io_wbytes;
    229 			timerset(&(cur_drive.io_time), &(cur.time[i]));
    230 			p = cur_drive.io_link.tqe_next;
    231 		}
    232 
    233 		deref_nl(X_TK_NIN, &cur.tk_nin, sizeof(cur.tk_nin));
    234 		deref_nl(X_TK_NOUT, &cur.tk_nout, sizeof(cur.tk_nout));
    235 	}
    236 
    237 	/*
    238 	 * XXX Need to locate the `correct' CPU when looking for this
    239 	 * XXX in crash dumps.  Just don't report it for now, in that
    240 	 * XXX case.
    241 	 */
    242 	size = sizeof(cur.cp_time);
    243 	(void)memset(cur.cp_time, 0, size);
    244 	if (memf == NULL) {
    245 		mib[0] = CTL_KERN;
    246 		mib[1] = KERN_CP_TIME;
    247 		if (sysctl(mib, 2, cur.cp_time, &size, NULL, 0) < 0)
    248 			(void)memset(cur.cp_time, 0, sizeof(cur.cp_time));
    249 	}
    250 }
    251 
    252 /*
    253  * Read collect statistics for tty i/o.
    254  */
    255 
    256 void
    257 tkreadstats(void)
    258 {
    259 	size_t		size;
    260 	int		mib[3];
    261 
    262 	if (memf == NULL) {
    263 		mib[0] = CTL_KERN;
    264 		mib[1] = KERN_TKSTAT;
    265 		mib[2] = KERN_TKSTAT_NIN;
    266 		size = sizeof(cur.tk_nin);
    267 		if (sysctl(mib, 3, &cur.tk_nin, &size, NULL, 0) < 0)
    268 			cur.tk_nin = 0;
    269 
    270 		mib[2] = KERN_TKSTAT_NOUT;
    271 		size = sizeof(cur.tk_nout);
    272 		if (sysctl(mib, 3, &cur.tk_nout, &size, NULL, 0) < 0)
    273 			cur.tk_nout = 0;
    274 	} else {
    275 		deref_nl(X_TK_NIN, &cur.tk_nin, sizeof(cur.tk_nin));
    276 		deref_nl(X_TK_NOUT, &cur.tk_nout, sizeof(cur.tk_nout));
    277 	}
    278 }
    279 
    280 /*
    281  * Read collect statistics for CPU ticks.
    282  */
    283 
    284 void
    285 cpureadstats(void)
    286 {
    287 	size_t		size;
    288 	int		mib[2];
    289 
    290 	/*
    291 	 * XXX Need to locate the `correct' CPU when looking for this
    292 	 * XXX in crash dumps.  Just don't report it for now, in that
    293 	 * XXX case.
    294 	 */
    295 	size = sizeof(cur.cp_time);
    296 	(void)memset(cur.cp_time, 0, size);
    297 	if (memf == NULL) {
    298 		mib[0] = CTL_KERN;
    299 		mib[1] = KERN_CP_TIME;
    300 		if (sysctl(mib, 2, cur.cp_time, &size, NULL, 0) < 0)
    301 			(void)memset(cur.cp_time, 0, sizeof(cur.cp_time));
    302 	}
    303 }
    304 
    305 /*
    306  * Perform all of the initialization and memory allocation needed to
    307  * track drive statistics.
    308  */
    309 int
    310 drvinit(int selected)
    311 {
    312 	struct iostatlist_head iostat_head;
    313 	struct io_stats	cur_drive, *p;
    314 	struct clockinfo clockinfo;
    315 	char		errbuf[_POSIX2_LINE_MAX];
    316 	size_t		size, i;
    317 	static int	once = 0;
    318 	int		mib[3];
    319 
    320 	if (once)
    321 		return (1);
    322 
    323 	if (memf == NULL) {
    324 		mib[0] = CTL_HW;
    325 		mib[1] = HW_NCPU;
    326 		size = sizeof(cur.cp_ncpu);
    327 		if (sysctl(mib, 2, &cur.cp_ncpu, &size, NULL, 0) == -1)
    328 			err(1, "sysctl hw.ncpu failed");
    329 
    330 		mib[0] = CTL_KERN;
    331 		mib[1] = KERN_CLOCKRATE;
    332 		size = sizeof(clockinfo);
    333 		if (sysctl(mib, 2, &clockinfo, &size, NULL, 0) == -1)
    334 			err(1, "sysctl kern.clockrate failed");
    335 		hz = clockinfo.stathz;
    336 		if (!hz)
    337 			hz = clockinfo.hz;
    338 
    339 		mib[0] = CTL_HW;
    340 		mib[1] = HW_IOSTATS;
    341 		mib[2] = sizeof(struct io_sysctl);
    342 		if (sysctl(mib, 3, NULL, &size, NULL, 0) == -1)
    343 			err(1, "sysctl hw.drivestats failed");
    344 		ndrive = size / sizeof(struct io_sysctl);
    345 
    346 		if (size == 0) {
    347 			warnx("No drives attached.");
    348 		} else {
    349 			drives = (struct io_sysctl *)malloc(size);
    350 			if (drives == NULL)
    351 				errx(1, "Memory allocation failure.");
    352 		}
    353 	} else {
    354 		int drive_count;
    355 		/* Open the kernel. */
    356 		if ((kd = kvm_openfiles(nlistf, memf, NULL, O_RDONLY,
    357 		    errbuf)) == NULL)
    358 			errx(1, "kvm_openfiles: %s", errbuf);
    359 
    360 		/* Obtain the namelist symbols from the kernel. */
    361 		if (kvm_nlist(kd, namelist))
    362 			KVM_ERROR("kvm_nlist failed to read symbols.");
    363 
    364 		/* Get the number of attached drives. */
    365 		deref_nl(X_DRIVE_COUNT, &drive_count, sizeof(drive_count));
    366 
    367 		if (drive_count < 0)
    368 			errx(1, "invalid _drive_count %d.", drive_count);
    369 		else if (drive_count == 0) {
    370 			warnx("No drives attached.");
    371 		} else {
    372 			/* Get a pointer to the first drive. */
    373 			deref_nl(X_DRIVELIST, &iostat_head,
    374 				 sizeof(iostat_head));
    375 			iostathead = iostat_head.tqh_first;
    376 		}
    377 		ndrive = drive_count;
    378 
    379 		/* Get ticks per second. */
    380 		deref_nl(X_STATHZ, &hz, sizeof(hz));
    381 		if (!hz)
    382 			deref_nl(X_HZ, &hz, sizeof(hz));
    383 	}
    384 
    385 	/* Allocate space for the statistics. */
    386 	cur.time = calloc(ndrive, sizeof(struct timeval));
    387 	cur.rxfer = calloc(ndrive, sizeof(u_int64_t));
    388 	cur.wxfer = calloc(ndrive, sizeof(u_int64_t));
    389 	cur.seek = calloc(ndrive, sizeof(u_int64_t));
    390 	cur.rbytes = calloc(ndrive, sizeof(u_int64_t));
    391 	cur.wbytes = calloc(ndrive, sizeof(u_int64_t));
    392 	last.time = calloc(ndrive, sizeof(struct timeval));
    393 	last.rxfer = calloc(ndrive, sizeof(u_int64_t));
    394 	last.wxfer = calloc(ndrive, sizeof(u_int64_t));
    395 	last.seek = calloc(ndrive, sizeof(u_int64_t));
    396 	last.rbytes = calloc(ndrive, sizeof(u_int64_t));
    397 	last.wbytes = calloc(ndrive, sizeof(u_int64_t));
    398 	cur.select = calloc(ndrive, sizeof(int));
    399 	cur.name = calloc(ndrive, sizeof(char *));
    400 
    401 	if (cur.time == NULL || cur.rxfer == NULL ||
    402 	    cur.wxfer == NULL || cur.seek == NULL ||
    403 	    cur.rbytes == NULL || cur.wbytes == NULL ||
    404 	    last.time == NULL || last.rxfer == NULL ||
    405 	    last.wxfer == NULL || last.seek == NULL ||
    406 	    last.rbytes == NULL || last.wbytes == NULL ||
    407 	    cur.select == NULL || cur.name == NULL)
    408 		errx(1, "Memory allocation failure.");
    409 
    410 	/* Set up the compatibility interfaces. */
    411 	drv_select = cur.select;
    412 	dr_name = cur.name;
    413 
    414 	/* Read the drive names and set intial selection. */
    415 	if (memf == NULL) {
    416 		mib[0] = CTL_HW;		/* Should be still set from */
    417 		mib[1] = HW_IOSTATS;		/* ... above, but be safe... */
    418 		mib[2] = sizeof(struct io_sysctl);
    419 		if (sysctl(mib, 3, drives, &size, NULL, 0) == -1)
    420 			err(1, "sysctl hw.iostats failed");
    421 		for (i = 0; i < ndrive; i++) {
    422 			cur.name[i] = drives[i].name;
    423 			cur.select[i] = selected;
    424 		}
    425 	} else {
    426 		p = iostathead;
    427 		for (i = 0; i < ndrive; i++) {
    428 			char	buf[10];
    429 			deref_kptr(p, &cur_drive, sizeof(cur_drive));
    430 			deref_kptr(cur_drive.io_name, buf, sizeof(buf));
    431 			cur.name[i] = strdup(buf);
    432 			if (!cur.name[i])
    433 				err(1, "strdup");
    434 			cur.select[i] = selected;
    435 
    436 			p = cur_drive.io_link.tqe_next;
    437 		}
    438 	}
    439 
    440 	/* Never do this initialization again. */
    441 	once = 1;
    442 	return (1);
    443 }
    444 
    445 /*
    446  * Dereference the kernel pointer `kptr' and fill in the local copy
    447  * pointed to by `ptr'.  The storage space must be pre-allocated,
    448  * and the size of the copy passed in `len'.
    449  */
    450 static void
    451 deref_kptr(void *kptr, void *ptr, size_t len)
    452 {
    453 	char buf[128];
    454 
    455 	if ((size_t)kvm_read(kd, (u_long)kptr, (char *)ptr, len) != len) {
    456 		(void)memset(buf, 0, sizeof(buf));
    457 		(void)snprintf(buf, sizeof buf, "can't dereference kptr 0x%lx",
    458 		    (u_long)kptr);
    459 		KVM_ERROR(buf);
    460 	}
    461 }
    462