1/*	$OpenBSD: iostat.c,v 1.47 2023/03/08 04:43:13 guenther 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 */
83extern struct _disk cur, last;
84extern int	dk_ndrive;
85
86/* Namelist and memory files. */
87kvm_t *kd;
88char	*nlistf, *memf;
89
90int		hz, reps;
91time_t		interval;
92static int	todo = 0;
93
94volatile 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
103static void cpustats(void);
104static void disk_stats(double);
105static void disk_stats2(double);
106static void sigalarm(int);
107static void sigheader(int);
108static void header(void);
109static void usage(void);
110static void display(void);
111static void selectdrives(char **);
112
113void dkswap(void);
114void dkreadstats(void);
115int dkinit(int);
116
117int
118main(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
212static void
213sigalarm(int signo)
214{
215}
216
217static void
218sigheader(int signo)
219{
220	wantheader = 1;
221}
222
223static void
224header(void)
225{
226	int i;
227	static int printedheader = 0;
228
229	if (printedheader && !isatty(STDOUT_FILENO))
230		return;
231
232	/* Main Headers. */
233	if (ISSET(todo, SHOW_TTY)) {
234		if (ISSET(todo, SHOW_TOTALS))
235			printf("            tty");
236		else
237			printf("      tty");
238	}
239
240	if (ISSET(todo, SHOW_STATS_1))
241		for (i = 0; i < dk_ndrive; i++)
242			if (cur.dk_select[i]) {
243				printf(" %18.18s ", cur.dk_name[i]);
244			}
245	if (ISSET(todo, SHOW_STATS_2))
246		for (i = 0; i < dk_ndrive; i++)
247			if (cur.dk_select[i])
248				printf(" %17.17s ", cur.dk_name[i]);
249
250	if (ISSET(todo, SHOW_CPU))
251		printf("               cpu");
252	printf("\n");
253
254	/* Sub-Headers. */
255	if (ISSET(todo, SHOW_TTY)) {
256		if (ISSET(todo, SHOW_TOTALS))
257			printf("   tin     tout");
258		else
259			printf(" tin tout");
260	}
261
262	if (ISSET(todo, SHOW_STATS_1))
263		for (i = 0; i < dk_ndrive; i++)
264			if (cur.dk_select[i]) {
265				if (ISSET(todo, SHOW_TOTALS))
266					printf("  KB/t   xfr     MB ");
267				else
268					printf("  KB/t  t/s    MB/s ");
269			}
270	if (ISSET(todo, SHOW_STATS_2))
271		for (i = 0; i < dk_ndrive; i++)
272			if (cur.dk_select[i])
273				printf("      KB  xfr time ");
274
275	if (ISSET(todo, SHOW_CPU))
276		printf(" us ni sy sp in id");
277	printf("\n");
278}
279
280static void
281disk_stats(double etime)
282{
283	int dn;
284	double atime, mbps;
285
286	for (dn = 0; dn < dk_ndrive; ++dn) {
287		if (!cur.dk_select[dn])
288			continue;
289
290		/* average Kbytes per transfer. */
291		if (cur.dk_rxfer[dn] + cur.dk_wxfer[dn])
292			mbps = ((cur.dk_rbytes[dn] + cur.dk_wbytes[dn]) /
293			    (1024.0)) / (cur.dk_rxfer[dn] + cur.dk_wxfer[dn]);
294		else
295			mbps = 0.0;
296
297		printf(" %5.2f", mbps);
298
299		/* average transfers per second. */
300		if (ISSET(todo, SHOW_TOTALS))
301			printf(" %5.0f", (cur.dk_rxfer[dn] + cur.dk_wxfer[dn]) / etime);
302		else
303			printf(" %4.0f", (cur.dk_rxfer[dn] + cur.dk_wxfer[dn]) / etime);
304
305		/* time busy in disk activity */
306		atime = (double)cur.dk_time[dn].tv_sec +
307			((double)cur.dk_time[dn].tv_usec / (double)1000000);
308
309		/* Megabytes per second. */
310		if (atime != 0.0)
311			mbps = (cur.dk_rbytes[dn] + cur.dk_wbytes[dn]) /
312			    (double)(1024 * 1024);
313		else
314			mbps = 0;
315		if (ISSET(todo, SHOW_TOTALS))
316			printf(" %6.2f ", mbps / etime);
317		else
318			printf(" %7.2f ", mbps / etime);
319	}
320}
321
322static void
323disk_stats2(double etime)
324{
325	int dn;
326	double atime;
327
328	for (dn = 0; dn < dk_ndrive; ++dn) {
329		if (!cur.dk_select[dn])
330			continue;
331
332		/* average kbytes per second. */
333		printf(" %7.0f",
334		    (cur.dk_rbytes[dn] + cur.dk_wbytes[dn]) / (1024.0) / etime);
335
336		/* average transfers per second. */
337		printf(" %4.0f", (cur.dk_rxfer[dn] + cur.dk_wxfer[dn]) / etime);
338
339		/* average time busy in disk activity. */
340		atime = (double)cur.dk_time[dn].tv_sec +
341		    ((double)cur.dk_time[dn].tv_usec / (double)1000000);
342		printf(" %4.2f ", atime / etime);
343	}
344}
345
346static void
347cpustats(void)
348{
349	int state;
350	double t = 0;
351
352	for (state = 0; state < CPUSTATES; ++state)
353		t += cur.cp_time[state];
354	if (!t)
355		t = 1.0;
356	/* States are generally never 100% and can use %3.0f. */
357	for (state = 0; state < CPUSTATES; ++state)
358		printf("%3.0f", 100. * cur.cp_time[state] / t);
359}
360
361static void
362usage(void)
363{
364	fprintf(stderr,
365"usage: iostat [-CDdIT] [-c count] [-M core] [-N system] [-w wait] [drives]\n");
366	exit(1);
367}
368
369static void
370display(void)
371{
372	int	i;
373	double	etime;
374
375	/* Sum up the elapsed ticks. */
376	etime = 0.0;
377	for (i = 0; i < CPUSTATES; i++)
378		etime += cur.cp_time[i];
379	if (etime == 0.0)
380		etime = 1.0;
381	/* Convert to seconds. */
382	etime /= (float)hz;
383
384	/* If we're showing totals only, then don't divide by the
385	 * system time.
386	 */
387	if (ISSET(todo, SHOW_TOTALS))
388		etime = 1.0;
389
390	if (ISSET(todo, SHOW_TTY)) {
391		if (ISSET(todo, SHOW_TOTALS))
392			printf("%6.0f %8.0f", cur.tk_nin / etime,
393			    cur.tk_nout / etime);
394		else
395			printf("%4.0f %4.0f", cur.tk_nin / etime,
396			    cur.tk_nout / etime);
397	}
398
399	if (ISSET(todo, SHOW_STATS_1))
400		disk_stats(etime);
401
402	if (ISSET(todo, SHOW_STATS_2))
403		disk_stats2(etime);
404
405	if (ISSET(todo, SHOW_CPU))
406		cpustats();
407
408	printf("\n");
409	fflush(stdout);
410}
411
412static void
413selectdrives(char *argv[])
414{
415	const char *errstr;
416	int	i, ndrives;
417
418	/*
419	 * Choose drives to be displayed.  Priority goes to (in order) drives
420	 * supplied as arguments and default drives.  If everything isn't
421	 * filled in and there are drives not taken care of, display the first
422	 * few that fit.
423	 *
424	 * The backward compatibility syntax is:
425	 *	iostat [ drives ] [ interval [ count ] ]
426	 */
427	for (ndrives = 0; *argv; ++argv) {
428		if (isdigit((unsigned char)**argv))
429			break;
430		for (i = 0; i < dk_ndrive; i++) {
431			if (strcmp(cur.dk_name[i], *argv))
432				continue;
433			cur.dk_select[i] = 1;
434			++ndrives;
435			break;
436		}
437		if (i == dk_ndrive)
438			errx(1, "invalid interval or drive name: %s", *argv);
439	}
440	if (*argv) {
441		interval = strtonum(*argv, 1, UINT_MAX, &errstr);
442		if (errstr)
443			errx(1, "interval is %s", errstr);
444		if (*++argv) {
445			reps = strtonum(*argv, 1, INT_MAX, &errstr);
446			if (errstr)
447				errx(1, "repetition count is %s", errstr);
448			++argv;
449		}
450	}
451	if (*argv)
452		errx(1, "too many arguments");
453
454	if (interval) {
455		if (!reps)
456			reps = -1;
457	} else
458		if (reps)
459			interval = 1;
460
461	/* Pick up to 4 drives if none specified. */
462	if (ndrives == 0)
463		for (i = 0; i < dk_ndrive && ndrives < 4; i++) {
464			if (cur.dk_select[i])
465				continue;
466			cur.dk_select[i] = 1;
467			++ndrives;
468		}
469}
470