xref: /openbsd-src/usr.sbin/iostat/iostat.c (revision 3374c67d44f9b75b98444cbf63020f777792342e)
1 /*	$OpenBSD: iostat.c,v 1.46 2022/12/28 20:56:37 cheloha Exp $	*/
2 /*	$NetBSD: iostat.c,v 1.10 1996/10/25 18:21:58 scottr Exp $	*/
3 
4 /*
5  * Copyright (c) 1996 John M. Vinopal
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. All advertising materials mentioning features or use of this software
17  *    must display the following acknowledgement:
18  *      This product includes software developed for the NetBSD Project
19  *      by John M. Vinopal.
20  * 4. The name of the author may not be used to endorse or promote products
21  *    derived from this software without specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
24  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
25  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
26  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
27  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
28  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
29  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
30  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
31  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33  * SUCH DAMAGE.
34  */
35 
36 /*-
37  * Copyright (c) 1986, 1991, 1993
38  *      The Regents of the University of California.  All rights reserved.
39  *
40  * Redistribution and use in source and binary forms, with or without
41  * modification, are permitted provided that the following conditions
42  * are met:
43  * 1. Redistributions of source code must retain the above copyright
44  *    notice, this list of conditions and the following disclaimer.
45  * 2. Redistributions in binary form must reproduce the above copyright
46  *    notice, this list of conditions and the following disclaimer in the
47  *    documentation and/or other materials provided with the distribution.
48  * 3. Neither the name of the University nor the names of its contributors
49  *    may be used to endorse or promote products derived from this software
50  *    without specific prior written permission.
51  *
52  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
53  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
54  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
55  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
56  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
57  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
58  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
59  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
60  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
61  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
62  * SUCH DAMAGE.
63  */
64 
65 #include <sys/limits.h>
66 #include <sys/time.h>
67 #include <sys/sched.h>
68 
69 #include <err.h>
70 #include <ctype.h>
71 #include <limits.h>
72 #include <signal.h>
73 #include <stdio.h>
74 #include <stdlib.h>
75 #include <string.h>
76 #include <time.h>
77 #include <unistd.h>
78 #include <kvm.h>
79 
80 #include "dkstats.h"
81 
82 /* Defined in dkstats.c */
83 extern struct _disk cur, last;
84 extern int	dk_ndrive;
85 
86 /* Namelist and memory files. */
87 kvm_t *kd;
88 char	*nlistf, *memf;
89 
90 int		hz, reps;
91 time_t		interval;
92 static int	todo = 0;
93 
94 volatile sig_atomic_t wantheader;
95 
96 #define ISSET(x, a)	((x) & (a))
97 #define SHOW_CPU	0x0001
98 #define SHOW_TTY	0x0002
99 #define SHOW_STATS_1	0x0004
100 #define SHOW_STATS_2	0x0008
101 #define SHOW_TOTALS	0x0080
102 
103 static void cpustats(void);
104 static void disk_stats(double);
105 static void disk_stats2(double);
106 static void sigalarm(int);
107 static void sigheader(int);
108 static void header(void);
109 static void usage(void);
110 static void display(void);
111 static void selectdrives(char **);
112 
113 void dkswap(void);
114 void dkreadstats(void);
115 int dkinit(int);
116 
117 int
118 main(int argc, char *argv[])
119 {
120 	struct itimerval itv;
121 	const char *errstr;
122 	sigset_t empty;
123 	int ch, hdrcnt;
124 
125 	while ((ch = getopt(argc, argv, "Cc:dDIM:N:Tw:")) != -1)
126 		switch(ch) {
127 		case 'c':
128 			reps = strtonum(optarg, 1, INT_MAX, &errstr);
129 			if (errstr)
130 				errx(1, "repetition count is %s", errstr);
131 			break;
132 		case 'C':
133 			todo |= SHOW_CPU;
134 			break;
135 		case 'd':
136 			todo |= SHOW_STATS_1;
137 			break;
138 		case 'D':
139 			todo |= SHOW_STATS_2;
140 			break;
141 		case 'I':
142 			todo |= SHOW_TOTALS;
143 			break;
144 		case 'M':
145 			memf = optarg;
146 			break;
147 		case 'N':
148 			nlistf = optarg;
149 			break;
150 		case 'T':
151 			todo |= SHOW_TTY;
152 			break;
153 		case 'w':
154 			interval = strtonum(optarg, 1, UINT_MAX, &errstr);
155 			if (errstr)
156 				errx(1, "wait is %s", errstr);
157 			break;
158 		default:
159 			usage();
160 		}
161 	argc -= optind;
162 	argv += optind;
163 
164 	if (!ISSET(todo, SHOW_CPU | SHOW_TTY | SHOW_STATS_1 | SHOW_STATS_2))
165 		todo |= SHOW_CPU | SHOW_TTY | SHOW_STATS_1;
166 
167 	dkinit(0);
168 
169 	if (unveil("/", "") == -1)
170 		err(1, "unveil /");
171 	if (unveil(NULL, NULL) == -1)
172 		err(1, "unveil");
173 
174 	dkreadstats();
175 	selectdrives(argv);
176 
177 	/* print a new header on sigcont */
178 	signal(SIGCONT, sigheader);
179 
180 	if (interval != 0) {
181 		if (signal(SIGALRM, sigalarm) == SIG_ERR)
182 			err(1, "signal");
183 		sigemptyset(&empty);
184 		itv.it_value.tv_sec = interval;
185 		itv.it_value.tv_usec = 0;
186 		itv.it_interval = itv.it_value;
187 		if (setitimer(ITIMER_REAL, &itv, NULL) == -1)
188 			err(1, "setitimer");
189 	}
190 
191 	for (hdrcnt = 1;;) {
192 		if (!--hdrcnt || wantheader) {
193 			header();
194 			hdrcnt = 20;
195 			wantheader = 0;
196 		}
197 
198 		if (!ISSET(todo, SHOW_TOTALS))
199 			dkswap();
200 		display();
201 
202 		if (reps >= 0 && --reps <= 0)
203 			break;
204 		sigsuspend(&empty);
205 		dkreadstats();
206 		if (last.dk_ndrive != cur.dk_ndrive)
207 			wantheader = 1;
208 	}
209 	exit(0);
210 }
211 
212 static void
213 sigalarm(int signo)
214 {
215 }
216 
217 /*ARGSUSED*/
218 static void
219 sigheader(int signo)
220 {
221 	wantheader = 1;
222 }
223 
224 static void
225 header(void)
226 {
227 	int i;
228 	static int printedheader = 0;
229 
230 	if (printedheader && !isatty(STDOUT_FILENO))
231 		return;
232 
233 	/* Main Headers. */
234 	if (ISSET(todo, SHOW_TTY)) {
235 		if (ISSET(todo, SHOW_TOTALS))
236 			printf("            tty");
237 		else
238 			printf("      tty");
239 	}
240 
241 	if (ISSET(todo, SHOW_STATS_1))
242 		for (i = 0; i < dk_ndrive; i++)
243 			if (cur.dk_select[i]) {
244 				printf(" %18.18s ", cur.dk_name[i]);
245 			}
246 	if (ISSET(todo, SHOW_STATS_2))
247 		for (i = 0; i < dk_ndrive; i++)
248 			if (cur.dk_select[i])
249 				printf(" %17.17s ", cur.dk_name[i]);
250 
251 	if (ISSET(todo, SHOW_CPU))
252 		printf("               cpu");
253 	printf("\n");
254 
255 	/* Sub-Headers. */
256 	if (ISSET(todo, SHOW_TTY)) {
257 		if (ISSET(todo, SHOW_TOTALS))
258 			printf("   tin     tout");
259 		else
260 			printf(" tin tout");
261 	}
262 
263 	if (ISSET(todo, SHOW_STATS_1))
264 		for (i = 0; i < dk_ndrive; i++)
265 			if (cur.dk_select[i]) {
266 				if (ISSET(todo, SHOW_TOTALS))
267 					printf("  KB/t   xfr     MB ");
268 				else
269 					printf("  KB/t  t/s    MB/s ");
270 			}
271 	if (ISSET(todo, SHOW_STATS_2))
272 		for (i = 0; i < dk_ndrive; i++)
273 			if (cur.dk_select[i])
274 				printf("      KB  xfr time ");
275 
276 	if (ISSET(todo, SHOW_CPU))
277 		printf(" us ni sy sp in id");
278 	printf("\n");
279 }
280 
281 static void
282 disk_stats(double etime)
283 {
284 	int dn;
285 	double atime, mbps;
286 
287 	for (dn = 0; dn < dk_ndrive; ++dn) {
288 		if (!cur.dk_select[dn])
289 			continue;
290 
291 		/* average Kbytes per transfer. */
292 		if (cur.dk_rxfer[dn] + cur.dk_wxfer[dn])
293 			mbps = ((cur.dk_rbytes[dn] + cur.dk_wbytes[dn]) /
294 			    (1024.0)) / (cur.dk_rxfer[dn] + cur.dk_wxfer[dn]);
295 		else
296 			mbps = 0.0;
297 
298 		printf(" %5.2f", mbps);
299 
300 		/* average transfers per second. */
301 		if (ISSET(todo, SHOW_TOTALS))
302 			printf(" %5.0f", (cur.dk_rxfer[dn] + cur.dk_wxfer[dn]) / etime);
303 		else
304 			printf(" %4.0f", (cur.dk_rxfer[dn] + cur.dk_wxfer[dn]) / etime);
305 
306 		/* time busy in disk activity */
307 		atime = (double)cur.dk_time[dn].tv_sec +
308 			((double)cur.dk_time[dn].tv_usec / (double)1000000);
309 
310 		/* Megabytes per second. */
311 		if (atime != 0.0)
312 			mbps = (cur.dk_rbytes[dn] + cur.dk_wbytes[dn]) /
313 			    (double)(1024 * 1024);
314 		else
315 			mbps = 0;
316 		if (ISSET(todo, SHOW_TOTALS))
317 			printf(" %6.2f ", mbps / etime);
318 		else
319 			printf(" %7.2f ", mbps / etime);
320 	}
321 }
322 
323 static void
324 disk_stats2(double etime)
325 {
326 	int dn;
327 	double atime;
328 
329 	for (dn = 0; dn < dk_ndrive; ++dn) {
330 		if (!cur.dk_select[dn])
331 			continue;
332 
333 		/* average kbytes per second. */
334 		printf(" %7.0f",
335 		    (cur.dk_rbytes[dn] + cur.dk_wbytes[dn]) / (1024.0) / etime);
336 
337 		/* average transfers per second. */
338 		printf(" %4.0f", (cur.dk_rxfer[dn] + cur.dk_wxfer[dn]) / etime);
339 
340 		/* average time busy in disk activity. */
341 		atime = (double)cur.dk_time[dn].tv_sec +
342 		    ((double)cur.dk_time[dn].tv_usec / (double)1000000);
343 		printf(" %4.2f ", atime / etime);
344 	}
345 }
346 
347 static void
348 cpustats(void)
349 {
350 	int state;
351 	double t = 0;
352 
353 	for (state = 0; state < CPUSTATES; ++state)
354 		t += cur.cp_time[state];
355 	if (!t)
356 		t = 1.0;
357 	/* States are generally never 100% and can use %3.0f. */
358 	for (state = 0; state < CPUSTATES; ++state)
359 		printf("%3.0f", 100. * cur.cp_time[state] / t);
360 }
361 
362 static void
363 usage(void)
364 {
365 	fprintf(stderr,
366 "usage: iostat [-CDdIT] [-c count] [-M core] [-N system] [-w wait] [drives]\n");
367 	exit(1);
368 }
369 
370 static void
371 display(void)
372 {
373 	int	i;
374 	double	etime;
375 
376 	/* Sum up the elapsed ticks. */
377 	etime = 0.0;
378 	for (i = 0; i < CPUSTATES; i++)
379 		etime += cur.cp_time[i];
380 	if (etime == 0.0)
381 		etime = 1.0;
382 	/* Convert to seconds. */
383 	etime /= (float)hz;
384 
385 	/* If we're showing totals only, then don't divide by the
386 	 * system time.
387 	 */
388 	if (ISSET(todo, SHOW_TOTALS))
389 		etime = 1.0;
390 
391 	if (ISSET(todo, SHOW_TTY)) {
392 		if (ISSET(todo, SHOW_TOTALS))
393 			printf("%6.0f %8.0f", cur.tk_nin / etime,
394 			    cur.tk_nout / etime);
395 		else
396 			printf("%4.0f %4.0f", cur.tk_nin / etime,
397 			    cur.tk_nout / etime);
398 	}
399 
400 	if (ISSET(todo, SHOW_STATS_1))
401 		disk_stats(etime);
402 
403 	if (ISSET(todo, SHOW_STATS_2))
404 		disk_stats2(etime);
405 
406 	if (ISSET(todo, SHOW_CPU))
407 		cpustats();
408 
409 	printf("\n");
410 	fflush(stdout);
411 }
412 
413 static void
414 selectdrives(char *argv[])
415 {
416 	const char *errstr;
417 	int	i, ndrives;
418 
419 	/*
420 	 * Choose drives to be displayed.  Priority goes to (in order) drives
421 	 * supplied as arguments and default drives.  If everything isn't
422 	 * filled in and there are drives not taken care of, display the first
423 	 * few that fit.
424 	 *
425 	 * The backward compatibility syntax is:
426 	 *	iostat [ drives ] [ interval [ count ] ]
427 	 */
428 	for (ndrives = 0; *argv; ++argv) {
429 		if (isdigit((unsigned char)**argv))
430 			break;
431 		for (i = 0; i < dk_ndrive; i++) {
432 			if (strcmp(cur.dk_name[i], *argv))
433 				continue;
434 			cur.dk_select[i] = 1;
435 			++ndrives;
436 			break;
437 		}
438 		if (i == dk_ndrive)
439 			errx(1, "invalid interval or drive name: %s", *argv);
440 	}
441 	if (*argv) {
442 		interval = strtonum(*argv, 1, UINT_MAX, &errstr);
443 		if (errstr)
444 			errx(1, "interval is %s", errstr);
445 		if (*++argv) {
446 			reps = strtonum(*argv, 1, INT_MAX, &errstr);
447 			if (errstr)
448 				errx(1, "repetition count is %s", errstr);
449 			++argv;
450 		}
451 	}
452 	if (*argv)
453 		errx(1, "too many arguments");
454 
455 	if (interval) {
456 		if (!reps)
457 			reps = -1;
458 	} else
459 		if (reps)
460 			interval = 1;
461 
462 	/* Pick up to 4 drives if none specified. */
463 	if (ndrives == 0)
464 		for (i = 0; i < dk_ndrive && ndrives < 4; i++) {
465 			if (cur.dk_select[i])
466 				continue;
467 			cur.dk_select[i] = 1;
468 			++ndrives;
469 		}
470 }
471