iostat.c revision 48171
1/*
2 * Copyright (c) 1997, 1998  Kenneth D. Merry.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 * 3. The name of the author may not be used to endorse or promote products
14 *    derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 *
28 *	$Id: iostat.c,v 1.12 1998/09/20 00:11:14 ken Exp $
29 */
30/*
31 * Parts of this program are derived from the original FreeBSD iostat
32 * program:
33 */
34/*-
35 * Copyright (c) 1986, 1991, 1993
36 *	The Regents of the University of California.  All rights reserved.
37 *
38 * Redistribution and use in source and binary forms, with or without
39 * modification, are permitted provided that the following conditions
40 * are met:
41 * 1. Redistributions of source code must retain the above copyright
42 *    notice, this list of conditions and the following disclaimer.
43 * 2. Redistributions in binary form must reproduce the above copyright
44 *    notice, this list of conditions and the following disclaimer in the
45 *    documentation and/or other materials provided with the distribution.
46 * 3. All advertising materials mentioning features or use of this software
47 *    must display the following acknowledgement:
48 *	This product includes software developed by the University of
49 *	California, Berkeley and its contributors.
50 * 4. Neither the name of the University nor the names of its contributors
51 *    may be used to endorse or promote products derived from this software
52 *    without specific prior written permission.
53 *
54 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
55 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
56 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
57 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
58 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
59 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
60 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
61 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
62 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
63 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
64 * SUCH DAMAGE.
65 */
66/*
67 * Ideas for the new iostat statistics output modes taken from the NetBSD
68 * version of iostat:
69 */
70/*
71 * Copyright (c) 1996 John M. Vinopal
72 * All rights reserved.
73 *
74 * Redistribution and use in source and binary forms, with or without
75 * modification, are permitted provided that the following conditions
76 * are met:
77 * 1. Redistributions of source code must retain the above copyright
78 *    notice, this list of conditions and the following disclaimer.
79 * 2. Redistributions in binary form must reproduce the above copyright
80 *    notice, this list of conditions and the following disclaimer in the
81 *    documentation and/or other materials provided with the distribution.
82 * 3. All advertising materials mentioning features or use of this software
83 *    must display the following acknowledgement:
84 *      This product includes software developed for the NetBSD Project
85 *      by John M. Vinopal.
86 * 4. The name of the author may not be used to endorse or promote products
87 *    derived from this software without specific prior written permission.
88 *
89 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
90 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
91 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
92 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
93 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
94 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
95 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
96 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
97 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
98 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
99 * SUCH DAMAGE.
100 */
101
102
103#include <sys/param.h>
104#include <sys/types.h>
105#include <sys/errno.h>
106#include <sys/dkstat.h>
107
108#include <err.h>
109#include <ctype.h>
110#include <fcntl.h>
111#include <kvm.h>
112#include <stdio.h>
113#include <stdlib.h>
114#include <string.h>
115#include <unistd.h>
116#include <limits.h>
117#include <devstat.h>
118
119struct nlist namelist[] = {
120#define X_TK_NIN	0
121	{ "_tk_nin" },
122#define X_TK_NOUT	1
123	{ "_tk_nout" },
124#define X_CP_TIME	2
125	{ "_cp_time" },
126#define X_HZ		3
127	{ "_hz" },
128#define X_STATHZ	4
129	{ "_stathz" },
130#define X_END		4
131	{ NULL },
132};
133
134struct statinfo cur, last;
135int num_devices;
136struct device_selection *dev_select;
137int maxshowdevs;
138int dflag = 0, Iflag = 0, Cflag = 0, Tflag = 0, oflag = 0, Kflag = 0;
139
140#define nlread(x, v) \
141	kvm_read(kd, namelist[x].n_value, &(v), sizeof(v))
142
143/* local function declarations */
144static void usage(void);
145static void phdr(int signo);
146static void devstats(int perf_select);
147static void cpustats(void);
148
149static void
150usage(void)
151{
152	/*
153	 * We also support the following 'traditional' syntax:
154	 * iostat [drives] [wait [count]]
155	 * This isn't mentioned in the man page, or the usage statement,
156	 * but it is supported.
157	 */
158	fprintf(stderr, "usage: iostat [-CdhIKoT?] [-c count] [-M core]"
159		" [-n devs] [-N system]\n"
160		"\t      [-t type,if,pass] [-w wait] [drives]\n");
161}
162
163int
164main(int argc, char **argv)
165{
166	int c;
167	register int i;
168	int tflag = 0, hflag = 0, cflag = 0, wflag = 0, nflag = 0;
169	int count = 0, waittime = 0;
170	char *memf = NULL, *nlistf = NULL;
171	struct devstat_match *matches;
172	int num_matches = 0;
173        char errbuf[_POSIX2_LINE_MAX];
174	char *err_str;
175	kvm_t	 *kd;
176	int hz, stathz;
177	int headercount;
178	long generation;
179	int num_devices_specified;
180	int num_selected, num_selections;
181	long select_generation;
182	char **specified_devices;
183	devstat_select_mode select_mode;
184
185	matches = NULL;
186	maxshowdevs = 3;
187
188	while ((c = getopt(argc, argv, "c:CdhIKM:n:N:ot:Tw:")) != -1) {
189		switch(c) {
190			case 'c':
191				cflag++;
192				count = atoi(optarg);
193				if (count < 1)
194					errx(1, "count %d is < 1", count);
195				break;
196			case 'C':
197				Cflag++;
198				break;
199			case 'd':
200				dflag++;
201				break;
202			case 'h':
203				hflag++;
204				break;
205			case 'I':
206				Iflag++;
207				break;
208			case 'K':
209				Kflag++;
210				break;
211			case 'M':
212				memf = optarg;
213				break;
214			case 'n':
215				nflag++;
216				maxshowdevs = atoi(optarg);
217				if (maxshowdevs < 0)
218					errx(1, "number of devcies %d is < 0",
219					     maxshowdevs);
220				break;
221			case 'N':
222				nlistf = optarg;
223				break;
224			case 'o':
225				oflag++;
226				break;
227			case 't':
228				tflag++;
229				if (buildmatch(optarg, &matches,
230					       &num_matches) != 0)
231					errx(1, "%s", devstat_errbuf);
232				break;
233			case 'T':
234				Tflag++;
235				break;
236			case 'w':
237				wflag++;
238				waittime = atoi(optarg);
239				if (waittime < 1)
240					errx(1, "wait time is < 1");
241				break;
242			default:
243				usage();
244				exit(1);
245				break;
246		}
247	}
248
249	argc -= optind;
250	argv += optind;
251
252	/*
253	 * Discard setgid privileges if not the running kernel so that bad
254	 * guys can't print interesting stuff from kernel memory.
255	 */
256	if (nlistf != NULL || memf != NULL)
257		setgid(getgid());
258
259	/*
260	 * Make sure that the userland devstat version matches the kernel
261	 * devstat version.  If not, exit and print a message informing
262	 * the user of his mistake.
263	 */
264	if (checkversion() < 0)
265		errx(1, "%s", devstat_errbuf);
266
267	/*
268	 * Figure out how many devices we should display.
269	 */
270	if (nflag == 0) {
271		if (oflag > 0) {
272			if ((dflag > 0) && (Cflag == 0) && (Tflag == 0))
273				maxshowdevs = 5;
274			else if ((dflag > 0) && (Tflag > 0) && (Cflag == 0))
275				maxshowdevs = 5;
276			else
277				maxshowdevs = 4;
278		} else {
279			if ((dflag > 0) && (Cflag == 0))
280				maxshowdevs = 4;
281			else
282				maxshowdevs = 3;
283		}
284	}
285
286	/* find out how many devices we have */
287	if ((num_devices = getnumdevs()) < 0)
288		err(1, "can't get number of devices");
289
290	cur.dinfo = (struct devinfo *)malloc(sizeof(struct devinfo));
291	last.dinfo = (struct devinfo *)malloc(sizeof(struct devinfo));
292	bzero(cur.dinfo, sizeof(struct devinfo));
293	bzero(last.dinfo, sizeof(struct devinfo));
294
295	/*
296	 * Grab all the devices.  We don't look to see if the list has
297	 * changed here, since it almost certainly has.  We only look for
298	 * errors.
299	 */
300	if (getdevs(&cur) == -1)
301		errx(1, "%s", devstat_errbuf);
302
303	num_devices = cur.dinfo->numdevs;
304	generation = cur.dinfo->generation;
305
306	/*
307	 * If the user specified any devices on the command line, see if
308	 * they are in the list of devices we have now.
309	 */
310	specified_devices = (char **)malloc(sizeof(char *));
311	for (num_devices_specified = 0; *argv; ++argv) {
312		if (isdigit(**argv))
313			break;
314		num_devices_specified++;
315		specified_devices = (char **)realloc(specified_devices,
316						     sizeof(char *) *
317						     num_devices_specified);
318		specified_devices[num_devices_specified - 1] = *argv;
319
320	}
321	if (nflag == 0 && maxshowdevs < num_devices_specified)
322		maxshowdevs = num_devices_specified;
323
324	dev_select = NULL;
325
326	if ((num_devices_specified == 0) && (num_matches == 0))
327		select_mode = DS_SELECT_ADD;
328	else
329		select_mode = DS_SELECT_ONLY;
330
331	/*
332	 * At this point, selectdevs will almost surely indicate that the
333	 * device list has changed, so we don't look for return values of 0
334	 * or 1.  If we get back -1, though, there is an error.
335	 */
336	if (selectdevs(&dev_select, &num_selected,
337		       &num_selections, &select_generation,
338		       generation, cur.dinfo->devices, num_devices,
339		       matches, num_matches,
340		       specified_devices, num_devices_specified,
341		       select_mode, maxshowdevs, hflag) == -1)
342		errx(1, "%s", devstat_errbuf);
343
344	/*
345	 * Look for the traditional wait time and count arguments.
346	 */
347	if (*argv) {
348		waittime = atoi(*argv);
349
350		/* Let the user know he goofed, but keep going anyway */
351		if (wflag != 0)
352			warnx("discarding previous wait interval, using"
353			      " %d instead", waittime);
354		wflag++;
355
356		if (*++argv) {
357			count = atoi(*argv);
358			if (cflag != 0)
359				warnx("discarding previous count, using %d"
360				      " instead", count);
361			cflag++;
362		} else
363			count = -1;
364	}
365
366	/*
367	 * If the user specified a count, but not an interval, we default
368	 * to an interval of 1 second.
369	 */
370	if ((wflag == 0) && (cflag > 0))
371		waittime = 1;
372
373	/*
374	 * If the user specified a wait time, but not a count, we want to
375	 * go on ad infinitum.  This can be redundant if the user uses the
376	 * traditional method of specifying the wait, since in that case we
377	 * already set count = -1 above.  Oh well.
378	 */
379	if ((wflag > 0) && (cflag == 0))
380		count = -1;
381
382	kd = kvm_openfiles(nlistf, memf, NULL, O_RDONLY, errbuf);
383
384	if (kd == 0)
385		errx(1, "kvm_openfiles: %s", errbuf);
386
387	if (kvm_nlist(kd, namelist) == -1)
388		errx(1, "kvm_nlist: %s", kvm_geterr(kd));
389
390	(void)nlread(X_HZ, hz);
391	(void)nlread(X_STATHZ, stathz);
392	if (stathz)
393		hz = stathz;
394
395	/*
396	 * If the user stops the program (control-Z) and then resumes it,
397	 * print out the header again.
398	 */
399	(void)signal(SIGCONT, phdr);
400
401	for (headercount = 1;;) {
402		struct devinfo *tmp_dinfo;
403		long tmp;
404		double etime;
405
406		if (!--headercount) {
407			phdr(0);
408			headercount = 20;
409		}
410		(void)kvm_read(kd, namelist[X_TK_NIN].n_value,
411		    &cur.tk_nin, sizeof(cur.tk_nin));
412		(void)kvm_read(kd, namelist[X_TK_NOUT].n_value,
413		    &cur.tk_nout, sizeof(cur.tk_nout));
414		(void)kvm_read(kd, namelist[X_CP_TIME].n_value,
415		    cur.cp_time, sizeof(cur.cp_time));
416
417		tmp_dinfo = last.dinfo;
418		last.dinfo = cur.dinfo;
419		cur.dinfo = tmp_dinfo;
420
421		last.busy_time = cur.busy_time;
422
423		/*
424		 * Here what we want to do is refresh our device stats.
425		 * getdevs() returns 1 when the device list has changed.
426		 * If the device list has changed, we want to go through
427		 * the selection process again, in case a device that we
428		 * were previously displaying has gone away.
429		 */
430		switch (getdevs(&cur)) {
431		case -1:
432			errx(1, "%s", devstat_errbuf);
433			break;
434		case 1: {
435			int retval;
436
437			num_devices = cur.dinfo->numdevs;
438			generation = cur.dinfo->generation;
439			retval = selectdevs(&dev_select, &num_selected,
440					    &num_selections, &select_generation,
441					    generation, cur.dinfo->devices,
442					    num_devices, matches, num_matches,
443					    specified_devices,
444					    num_devices_specified,
445					    select_mode, maxshowdevs, hflag);
446			switch(retval) {
447			case -1:
448				errx(1, "%s", devstat_errbuf);
449				break;
450			case 1:
451				phdr(0);
452				headercount = 20;
453				break;
454			default:
455				break;
456			}
457			break;
458		}
459		default:
460			break;
461		}
462
463		/*
464		 * We only want to re-select devices if we're in 'top'
465		 * mode.  This is the only mode where the devices selected
466		 * could actually change.
467		 */
468		if (hflag > 0) {
469			int retval;
470			retval = selectdevs(&dev_select, &num_selected,
471					    &num_selections, &select_generation,
472					    generation, cur.dinfo->devices,
473					    num_devices, matches, num_matches,
474					    specified_devices,
475					    num_devices_specified,
476					    select_mode, maxshowdevs, hflag);
477			switch(retval) {
478			case -1:
479				errx(1,"%s", devstat_errbuf);
480				break;
481			case 1:
482				phdr(0);
483				headercount = 20;
484				break;
485			default:
486				break;
487			}
488		}
489
490		tmp = cur.tk_nin;
491		cur.tk_nin -= last.tk_nin;
492		last.tk_nin = tmp;
493		tmp = cur.tk_nout;
494		cur.tk_nout -= last.tk_nout;
495		last.tk_nout = tmp;
496
497		etime = 0.0;
498
499#define X(fld)	tmp = cur.fld[i]; cur.fld[i] -= last.fld[i]; last.fld[i] = tmp
500
501		for (i = 0; i < CPUSTATES; i++) {
502			X(cp_time);
503			etime += cur.cp_time[i];
504		}
505		if (etime == 0.0)
506			etime = 1.0;
507		etime /= (float)hz;
508		if ((dflag == 0) || (Tflag > 0))
509			printf("%4.0f%5.0f", cur.tk_nin / etime,
510				cur.tk_nout/etime);
511		devstats(hflag);
512		if ((dflag == 0) || (Cflag > 0))
513			cpustats();
514		printf("\n");
515		fflush(stdout);
516
517		if (count >= 0 && --count <= 0)
518			break;
519
520		sleep(waittime);
521	}
522
523	exit(0);
524}
525
526static void
527phdr(int signo)
528{
529	register int i;
530	int printed;
531
532	if ((dflag == 0) || (Tflag > 0))
533		(void)printf("      tty");
534	for (i = 0, printed=0;(i < num_devices) && (printed < maxshowdevs);i++){
535		int di;
536		if ((dev_select[i].selected != 0)
537		 && (dev_select[i].selected <= maxshowdevs)) {
538			di = dev_select[i].position;
539			if (oflag > 0)
540				(void)printf("%12.6s%d ",
541					    cur.dinfo->devices[di].device_name,
542					    cur.dinfo->devices[di].unit_number);
543			else
544				printf("%15.6s%d ",
545					    cur.dinfo->devices[di].device_name,
546					    cur.dinfo->devices[di].unit_number);
547			printed++;
548		}
549	}
550	if ((dflag == 0) || (Cflag > 0))
551		(void)printf("            cpu\n");
552	else
553		(void)printf("\n");
554
555	if ((dflag == 0) || (Tflag > 0))
556		(void)printf(" tin tout");
557
558	for (i=0, printed = 0;(i < num_devices) && (printed < maxshowdevs);i++){
559		if ((dev_select[i].selected != 0)
560		 && (dev_select[i].selected <= maxshowdevs)) {
561			if (oflag > 0) {
562				if (Iflag == 0)
563					(void)printf(" sps tps msps ");
564				else
565					(void)printf(" blk xfr msps ");
566			} else {
567				if (Iflag == 0)
568					printf("  KB/t tps  MB/s ");
569				else
570					printf("  KB/t xfrs   MB ");
571			}
572			printed++;
573		}
574	}
575	if ((dflag == 0) || (Cflag > 0))
576		(void)printf(" us ni sy in id\n");
577	else
578		printf("\n");
579
580}
581
582static void
583devstats(int perf_select)
584{
585	register int dn;
586	long double transfers_per_second;
587	long double kb_per_transfer, mb_per_second;
588	u_int64_t total_bytes, total_transfers, total_blocks;
589	long double busy_seconds;
590	long double total_mb;
591	long double blocks_per_second, ms_per_transaction;
592
593	/*
594	 * Calculate elapsed time up front, since it's the same for all
595	 * devices.
596	 */
597	busy_seconds = compute_etime(cur.busy_time, last.busy_time);
598
599	for (dn = 0; dn < num_devices; dn++) {
600		int di;
601
602		if (((perf_select == 0) && (dev_select[dn].selected == 0))
603		 || (dev_select[dn].selected > maxshowdevs))
604			continue;
605
606		di = dev_select[dn].position;
607
608		if (compute_stats(&cur.dinfo->devices[di],
609				  &last.dinfo->devices[di], busy_seconds,
610				  &total_bytes, &total_transfers,
611				  &total_blocks, &kb_per_transfer,
612				  &transfers_per_second, &mb_per_second,
613				  &blocks_per_second, &ms_per_transaction)!= 0)
614			errx(1, "%s", devstat_errbuf);
615
616		if (perf_select != 0) {
617			dev_select[dn].bytes = total_bytes;
618			if ((dev_select[dn].selected == 0)
619			 || (dev_select[dn].selected > maxshowdevs))
620				continue;
621		}
622
623		if (Kflag) {
624			int block_size = cur.dinfo->devices[di].block_size;
625			total_blocks = total_blocks * (block_size ?
626						       block_size : 512) / 1024;
627		}
628
629		if (oflag > 0) {
630			int msdig = (ms_per_transaction < 100.0) ? 1 : 0;
631
632			if (Iflag == 0)
633				printf("%4.0Lf%4.0Lf%5.*Lf ",
634				       blocks_per_second,
635				       transfers_per_second,
636				       msdig,
637				       ms_per_transaction);
638			else
639				printf("%4.1qu%4.1qu%5.*Lf ",
640				       total_blocks,
641				       total_transfers,
642				       msdig,
643				       ms_per_transaction);
644		} else {
645			if (Iflag == 0)
646				printf(" %5.2Lf %3.0Lf %5.2Lf ",
647				       kb_per_transfer,
648				       transfers_per_second,
649				       mb_per_second);
650			else {
651				total_mb = total_bytes;
652				total_mb /= 1024 * 1024;
653
654				printf(" %5.2Lf %3.1qu %5.2Lf ",
655				       kb_per_transfer,
656				       total_transfers,
657				       total_mb);
658			}
659		}
660	}
661}
662
663static void
664cpustats(void)
665{
666	register int state;
667	double time;
668
669	time = 0.0;
670
671	for (state = 0; state < CPUSTATES; ++state)
672		time += cur.cp_time[state];
673	for (state = 0; state < CPUSTATES; ++state)
674		printf("%3.0f",
675		       100. * cur.cp_time[state] / (time ? time : 1));
676}
677