iostat.c revision 59789
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 * $FreeBSD: head/usr.sbin/iostat/iostat.c 59789 2000-04-30 17:05:26Z asmodai $
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/errno.h>
105#include <sys/dkstat.h>
106
107#include <err.h>
108#include <ctype.h>
109#include <fcntl.h>
110#include <kvm.h>
111#include <stdio.h>
112#include <stdlib.h>
113#include <string.h>
114#include <unistd.h>
115#include <limits.h>
116#include <devstat.h>
117
118struct nlist namelist[] = {
119#define X_TK_NIN	0
120	{ "_tk_nin" },
121#define X_TK_NOUT	1
122	{ "_tk_nout" },
123#define X_CP_TIME	2
124	{ "_cp_time" },
125#define X_HZ		3
126	{ "_hz" },
127#define X_STATHZ	4
128	{ "_stathz" },
129#define X_END		4
130	{ NULL },
131};
132
133struct statinfo cur, last;
134int num_devices;
135struct device_selection *dev_select;
136int maxshowdevs;
137int dflag = 0, Iflag = 0, Cflag = 0, Tflag = 0, oflag = 0, Kflag = 0;
138
139#define nlread(x, v) \
140	kvm_read(kd, namelist[x].n_value, &(v), sizeof(v))
141
142/* local function declarations */
143static void usage(void);
144static void phdr(int signo);
145static void devstats(int perf_select);
146static void cpustats(void);
147
148static void
149usage(void)
150{
151	/*
152	 * We also support the following 'traditional' syntax:
153	 * iostat [drives] [wait [count]]
154	 * This isn't mentioned in the man page, or the usage statement,
155	 * but it is supported.
156	 */
157	fprintf(stderr, "usage: iostat [-CdhIKoT?] [-c count] [-M core]"
158		" [-n devs] [-N system]\n"
159		"\t      [-t type,if,pass] [-w wait] [drives]\n");
160}
161
162int
163main(int argc, char **argv)
164{
165	int c;
166	register int i;
167	int tflag = 0, hflag = 0, cflag = 0, wflag = 0, nflag = 0;
168	int count = 0, waittime = 0;
169	char *memf = NULL, *nlistf = NULL;
170	struct devstat_match *matches;
171	int num_matches = 0;
172        char errbuf[_POSIX2_LINE_MAX];
173	kvm_t	 *kd;
174	int hz, stathz;
175	int headercount;
176	long generation;
177	int num_devices_specified;
178	int num_selected, num_selections;
179	long select_generation;
180	char **specified_devices;
181	devstat_select_mode select_mode;
182
183	matches = NULL;
184	maxshowdevs = 3;
185
186	while ((c = getopt(argc, argv, "c:CdhIKM:n:N:ot:Tw:?")) != -1) {
187		switch(c) {
188			case 'c':
189				cflag++;
190				count = atoi(optarg);
191				if (count < 1)
192					errx(1, "count %d is < 1", count);
193				break;
194			case 'C':
195				Cflag++;
196				break;
197			case 'd':
198				dflag++;
199				break;
200			case 'h':
201				hflag++;
202				break;
203			case 'I':
204				Iflag++;
205				break;
206			case 'K':
207				Kflag++;
208				break;
209			case 'M':
210				memf = optarg;
211				break;
212			case 'n':
213				nflag++;
214				maxshowdevs = atoi(optarg);
215				if (maxshowdevs < 0)
216					errx(1, "number of devices %d is < 0",
217					     maxshowdevs);
218				break;
219			case 'N':
220				nlistf = optarg;
221				break;
222			case 'o':
223				oflag++;
224				break;
225			case 't':
226				tflag++;
227				if (buildmatch(optarg, &matches,
228					       &num_matches) != 0)
229					errx(1, "%s", devstat_errbuf);
230				break;
231			case 'T':
232				Tflag++;
233				break;
234			case 'w':
235				wflag++;
236				waittime = atoi(optarg);
237				if (waittime < 1)
238					errx(1, "wait time is < 1");
239				break;
240			default:
241				usage();
242				exit(1);
243				break;
244		}
245	}
246
247	argc -= optind;
248	argv += optind;
249
250	/*
251	 * Discard setgid privileges if not the running kernel so that bad
252	 * guys can't print interesting stuff from kernel memory.
253	 */
254	if (nlistf != NULL || memf != NULL)
255		setgid(getgid());
256
257	/*
258	 * Make sure that the userland devstat version matches the kernel
259	 * devstat version.  If not, exit and print a message informing
260	 * the user of his mistake.
261	 */
262	if (checkversion() < 0)
263		errx(1, "%s", devstat_errbuf);
264
265	/*
266	 * Figure out how many devices we should display.
267	 */
268	if (nflag == 0) {
269		if (oflag > 0) {
270			if ((dflag > 0) && (Cflag == 0) && (Tflag == 0))
271				maxshowdevs = 5;
272			else if ((dflag > 0) && (Tflag > 0) && (Cflag == 0))
273				maxshowdevs = 5;
274			else
275				maxshowdevs = 4;
276		} else {
277			if ((dflag > 0) && (Cflag == 0))
278				maxshowdevs = 4;
279			else
280				maxshowdevs = 3;
281		}
282	}
283
284	/* find out how many devices we have */
285	if ((num_devices = getnumdevs()) < 0)
286		err(1, "can't get number of devices");
287
288	cur.dinfo = (struct devinfo *)malloc(sizeof(struct devinfo));
289	last.dinfo = (struct devinfo *)malloc(sizeof(struct devinfo));
290	bzero(cur.dinfo, sizeof(struct devinfo));
291	bzero(last.dinfo, sizeof(struct devinfo));
292
293	/*
294	 * Grab all the devices.  We don't look to see if the list has
295	 * changed here, since it almost certainly has.  We only look for
296	 * errors.
297	 */
298	if (getdevs(&cur) == -1)
299		errx(1, "%s", devstat_errbuf);
300
301	num_devices = cur.dinfo->numdevs;
302	generation = cur.dinfo->generation;
303
304	/*
305	 * If the user specified any devices on the command line, see if
306	 * they are in the list of devices we have now.
307	 */
308	specified_devices = (char **)malloc(sizeof(char *));
309	for (num_devices_specified = 0; *argv; ++argv) {
310		if (isdigit(**argv))
311			break;
312		num_devices_specified++;
313		specified_devices = (char **)realloc(specified_devices,
314						     sizeof(char *) *
315						     num_devices_specified);
316		specified_devices[num_devices_specified - 1] = *argv;
317
318	}
319	if (nflag == 0 && maxshowdevs < num_devices_specified)
320		maxshowdevs = num_devices_specified;
321
322	dev_select = NULL;
323
324	if ((num_devices_specified == 0) && (num_matches == 0))
325		select_mode = DS_SELECT_ADD;
326	else
327		select_mode = DS_SELECT_ONLY;
328
329	/*
330	 * At this point, selectdevs will almost surely indicate that the
331	 * device list has changed, so we don't look for return values of 0
332	 * or 1.  If we get back -1, though, there is an error.
333	 */
334	if (selectdevs(&dev_select, &num_selected,
335		       &num_selections, &select_generation,
336		       generation, cur.dinfo->devices, num_devices,
337		       matches, num_matches,
338		       specified_devices, num_devices_specified,
339		       select_mode, maxshowdevs, hflag) == -1)
340		errx(1, "%s", devstat_errbuf);
341
342	/*
343	 * Look for the traditional wait time and count arguments.
344	 */
345	if (*argv) {
346		waittime = atoi(*argv);
347
348		/* Let the user know he goofed, but keep going anyway */
349		if (wflag != 0)
350			warnx("discarding previous wait interval, using"
351			      " %d instead", waittime);
352		wflag++;
353
354		if (*++argv) {
355			count = atoi(*argv);
356			if (cflag != 0)
357				warnx("discarding previous count, using %d"
358				      " instead", count);
359			cflag++;
360		} else
361			count = -1;
362	}
363
364	/*
365	 * If the user specified a count, but not an interval, we default
366	 * to an interval of 1 second.
367	 */
368	if ((wflag == 0) && (cflag > 0))
369		waittime = 1;
370
371	/*
372	 * If the user specified a wait time, but not a count, we want to
373	 * go on ad infinitum.  This can be redundant if the user uses the
374	 * traditional method of specifying the wait, since in that case we
375	 * already set count = -1 above.  Oh well.
376	 */
377	if ((wflag > 0) && (cflag == 0))
378		count = -1;
379
380	kd = kvm_openfiles(nlistf, memf, NULL, O_RDONLY, errbuf);
381
382	if (kd == 0)
383		errx(1, "kvm_openfiles: %s", errbuf);
384
385	if (kvm_nlist(kd, namelist) == -1)
386		errx(1, "kvm_nlist: %s", kvm_geterr(kd));
387
388	(void)nlread(X_HZ, hz);
389	(void)nlread(X_STATHZ, stathz);
390	if (stathz)
391		hz = stathz;
392
393	/*
394	 * If the user stops the program (control-Z) and then resumes it,
395	 * print out the header again.
396	 */
397	(void)signal(SIGCONT, phdr);
398
399	for (headercount = 1;;) {
400		struct devinfo *tmp_dinfo;
401		long tmp;
402		double etime;
403
404		if (!--headercount) {
405			phdr(0);
406			headercount = 20;
407		}
408		(void)kvm_read(kd, namelist[X_TK_NIN].n_value,
409		    &cur.tk_nin, sizeof(cur.tk_nin));
410		(void)kvm_read(kd, namelist[X_TK_NOUT].n_value,
411		    &cur.tk_nout, sizeof(cur.tk_nout));
412		(void)kvm_read(kd, namelist[X_CP_TIME].n_value,
413		    cur.cp_time, sizeof(cur.cp_time));
414
415		tmp_dinfo = last.dinfo;
416		last.dinfo = cur.dinfo;
417		cur.dinfo = tmp_dinfo;
418
419		last.busy_time = cur.busy_time;
420
421		/*
422		 * Here what we want to do is refresh our device stats.
423		 * getdevs() returns 1 when the device list has changed.
424		 * If the device list has changed, we want to go through
425		 * the selection process again, in case a device that we
426		 * were previously displaying has gone away.
427		 */
428		switch (getdevs(&cur)) {
429		case -1:
430			errx(1, "%s", devstat_errbuf);
431			break;
432		case 1: {
433			int retval;
434
435			num_devices = cur.dinfo->numdevs;
436			generation = cur.dinfo->generation;
437			retval = selectdevs(&dev_select, &num_selected,
438					    &num_selections, &select_generation,
439					    generation, cur.dinfo->devices,
440					    num_devices, matches, num_matches,
441					    specified_devices,
442					    num_devices_specified,
443					    select_mode, maxshowdevs, hflag);
444			switch(retval) {
445			case -1:
446				errx(1, "%s", devstat_errbuf);
447				break;
448			case 1:
449				phdr(0);
450				headercount = 20;
451				break;
452			default:
453				break;
454			}
455			break;
456		}
457		default:
458			break;
459		}
460
461		/*
462		 * We only want to re-select devices if we're in 'top'
463		 * mode.  This is the only mode where the devices selected
464		 * could actually change.
465		 */
466		if (hflag > 0) {
467			int retval;
468			retval = selectdevs(&dev_select, &num_selected,
469					    &num_selections, &select_generation,
470					    generation, cur.dinfo->devices,
471					    num_devices, matches, num_matches,
472					    specified_devices,
473					    num_devices_specified,
474					    select_mode, maxshowdevs, hflag);
475			switch(retval) {
476			case -1:
477				errx(1,"%s", devstat_errbuf);
478				break;
479			case 1:
480				phdr(0);
481				headercount = 20;
482				break;
483			default:
484				break;
485			}
486		}
487
488		tmp = cur.tk_nin;
489		cur.tk_nin -= last.tk_nin;
490		last.tk_nin = tmp;
491		tmp = cur.tk_nout;
492		cur.tk_nout -= last.tk_nout;
493		last.tk_nout = tmp;
494
495		etime = 0.0;
496
497#define X(fld)	tmp = cur.fld[i]; cur.fld[i] -= last.fld[i]; last.fld[i] = tmp
498
499		for (i = 0; i < CPUSTATES; i++) {
500			X(cp_time);
501			etime += cur.cp_time[i];
502		}
503		if (etime == 0.0)
504			etime = 1.0;
505		etime /= (float)hz;
506		if ((dflag == 0) || (Tflag > 0))
507			printf("%4.0f%5.0f", cur.tk_nin / etime,
508				cur.tk_nout/etime);
509		devstats(hflag);
510		if ((dflag == 0) || (Cflag > 0))
511			cpustats();
512		printf("\n");
513		fflush(stdout);
514
515		if (count >= 0 && --count <= 0)
516			break;
517
518		sleep(waittime);
519	}
520
521	exit(0);
522}
523
524static void
525phdr(int signo)
526{
527	register int i;
528	int printed;
529
530	if ((dflag == 0) || (Tflag > 0))
531		(void)printf("      tty");
532	for (i = 0, printed=0;(i < num_devices) && (printed < maxshowdevs);i++){
533		int di;
534		if ((dev_select[i].selected != 0)
535		 && (dev_select[i].selected <= maxshowdevs)) {
536			di = dev_select[i].position;
537			if (oflag > 0)
538				(void)printf("%12.6s%d ",
539					    cur.dinfo->devices[di].device_name,
540					    cur.dinfo->devices[di].unit_number);
541			else
542				printf("%15.6s%d ",
543					    cur.dinfo->devices[di].device_name,
544					    cur.dinfo->devices[di].unit_number);
545			printed++;
546		}
547	}
548	if ((dflag == 0) || (Cflag > 0))
549		(void)printf("            cpu\n");
550	else
551		(void)printf("\n");
552
553	if ((dflag == 0) || (Tflag > 0))
554		(void)printf(" tin tout");
555
556	for (i=0, printed = 0;(i < num_devices) && (printed < maxshowdevs);i++){
557		if ((dev_select[i].selected != 0)
558		 && (dev_select[i].selected <= maxshowdevs)) {
559			if (oflag > 0) {
560				if (Iflag == 0)
561					(void)printf(" sps tps msps ");
562				else
563					(void)printf(" blk xfr msps ");
564			} else {
565				if (Iflag == 0)
566					printf("  KB/t tps  MB/s ");
567				else
568					printf("  KB/t xfrs   MB ");
569			}
570			printed++;
571		}
572	}
573	if ((dflag == 0) || (Cflag > 0))
574		(void)printf(" us ni sy in id\n");
575	else
576		printf("\n");
577
578}
579
580static void
581devstats(int perf_select)
582{
583	register int dn;
584	long double transfers_per_second;
585	long double kb_per_transfer, mb_per_second;
586	u_int64_t total_bytes, total_transfers, total_blocks;
587	long double busy_seconds;
588	long double total_mb;
589	long double blocks_per_second, ms_per_transaction;
590
591	/*
592	 * Calculate elapsed time up front, since it's the same for all
593	 * devices.
594	 */
595	busy_seconds = compute_etime(cur.busy_time, last.busy_time);
596
597	for (dn = 0; dn < num_devices; dn++) {
598		int di;
599
600		if (((perf_select == 0) && (dev_select[dn].selected == 0))
601		 || (dev_select[dn].selected > maxshowdevs))
602			continue;
603
604		di = dev_select[dn].position;
605
606		if (compute_stats(&cur.dinfo->devices[di],
607				  &last.dinfo->devices[di], busy_seconds,
608				  &total_bytes, &total_transfers,
609				  &total_blocks, &kb_per_transfer,
610				  &transfers_per_second, &mb_per_second,
611				  &blocks_per_second, &ms_per_transaction)!= 0)
612			errx(1, "%s", devstat_errbuf);
613
614		if (perf_select != 0) {
615			dev_select[dn].bytes = total_bytes;
616			if ((dev_select[dn].selected == 0)
617			 || (dev_select[dn].selected > maxshowdevs))
618				continue;
619		}
620
621		if (Kflag) {
622			int block_size = cur.dinfo->devices[di].block_size;
623			total_blocks = total_blocks * (block_size ?
624						       block_size : 512) / 1024;
625		}
626
627		if (oflag > 0) {
628			int msdig = (ms_per_transaction < 100.0) ? 1 : 0;
629
630			if (Iflag == 0)
631				printf("%4.0Lf%4.0Lf%5.*Lf ",
632				       blocks_per_second,
633				       transfers_per_second,
634				       msdig,
635				       ms_per_transaction);
636			else
637				printf("%4.1qu%4.1qu%5.*Lf ",
638				       total_blocks,
639				       total_transfers,
640				       msdig,
641				       ms_per_transaction);
642		} else {
643			if (Iflag == 0)
644				printf(" %5.2Lf %3.0Lf %5.2Lf ",
645				       kb_per_transfer,
646				       transfers_per_second,
647				       mb_per_second);
648			else {
649				total_mb = total_bytes;
650				total_mb /= 1024 * 1024;
651
652				printf(" %5.2Lf %3.1qu %5.2Lf ",
653				       kb_per_transfer,
654				       total_transfers,
655				       total_mb);
656			}
657		}
658	}
659}
660
661static void
662cpustats(void)
663{
664	register int state;
665	double time;
666
667	time = 0.0;
668
669	for (state = 0; state < CPUSTATES; ++state)
670		time += cur.cp_time[state];
671	for (state = 0; state < CPUSTATES; ++state)
672		printf("%3.0f",
673		       100. * cur.cp_time[state] / (time ? time : 1));
674}
675