Module Name:    src
Committed By:   kre
Date:           Sat Jun 18 11:33:13 UTC 2022

Modified Files:
        src/usr.sbin/iostat: iostat.8 iostat.c

Log Message:
Significant changes to output layout methods - except for -x and -y
which have not (yet) been touched (-xD needs *serious* improvements).

While this still has no run-time configurability, it is now easy to
adjust the column widths in the source and recompile.  Dynamic (auto)
column width sizing is probably out of the question (requires predicting
the future) but options to allow the widths to be set isn't out of the
question.

The columns are now (mostly) considerably wider than they were before,
hence wider windows are needed to view the same info.  In an 80
column window the default display (with tty & cpu included) displays
just 2 drives.   160 columns will fit 7 (but with -I, just 4).

One new option added (-z) suppresses output which is true 0 (but still
prints 0 for values rounded down to 0) for everything except tty stats.
For drive output, the drive must have done nothing during the interval
to get its output data blanked.

Also options -H h -W w to set the output size (page height & width), the
former used to decide when to print headers, and the latter to calculate
the number of drives to print when no drive names were given.  Env vars
LINES and COLUMNS are used if the options are not given, with fallback
to the terminal size (if output is to a terminal, and its sizes are
known), and if all else fails, 20 lines, 80 columns.  Specifying 0
means unlimited (infinite).   So "iostat -W 0" will show all of the
drives, across one (often very) long line.  Wedges count as drives.

When drives are specified, the output will now appear in the order they were
given on the command line, rather than the order the system discovered them
during auto-configuration.  If specified as an fnmatch(3) pattern, drives that
match will appear in auto-conf order, but that's generally what is wanted.
When none are specified, you still get the first N (however many fits based
upon the options selected) in auto-conf order (usually useless, more so now
given that less will fit).

Lastly, for those who looked at the patch I sent to current-users@
and were horrified at how kludgey it was, rest assured, that was just
a quick hack to demonstrate what the output format changes would look
like.   This version (I hope) is not nearly so disgusting.


To generate a diff of this commit:
cvs rdiff -u -r1.27 -r1.28 src/usr.sbin/iostat/iostat.8
cvs rdiff -u -r1.68 -r1.69 src/usr.sbin/iostat/iostat.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/usr.sbin/iostat/iostat.8
diff -u src/usr.sbin/iostat/iostat.8:1.27 src/usr.sbin/iostat/iostat.8:1.28
--- src/usr.sbin/iostat/iostat.8:1.27	Sun Jul 29 21:17:43 2018
+++ src/usr.sbin/iostat/iostat.8	Sat Jun 18 11:33:13 2022
@@ -1,4 +1,4 @@
-.\"	$NetBSD: iostat.8,v 1.27 2018/07/29 21:17:43 wiz Exp $
+.\"	$NetBSD: iostat.8,v 1.28 2022/06/18 11:33:13 kre Exp $
 .\"
 .\" Copyright (c) 1985, 1991, 1993
 .\"	The Regents of the University of California.  All rights reserved.
@@ -29,7 +29,7 @@
 .\"
 .\"	from: @(#)iostat.8	8.1 (Berkeley) 6/6/93
 .\"
-.Dd July 29, 2018
+.Dd June 18, 2022
 .Dt IOSTAT 8
 .Os
 .Sh NAME
@@ -37,8 +37,10 @@
 .Nd report I/O statistics
 .Sh SYNOPSIS
 .Nm
-.Op Fl CdDITxy
+.Op Fl CdDITxyz
 .Op Fl c Ar count
+.Op Fl H Ar height
+.Op Fl W Ar width
 .Op Fl w Ar wait
 .Op Ar drives
 .Sh DESCRIPTION
@@ -49,6 +51,8 @@ By default,
 displays one line of statistics averaged over the machine's run time.
 The use of
 .Fl c
+or
+.Fl w
 presents successive lines averaged over the
 .Ar wait
 period.
@@ -56,13 +60,14 @@ The
 .Fl I
 option causes
 .Nm
-to print raw, unaveraged values.
+to print raw, unaveraged values (totals).
 .Pp
 Only the last disk option specified
 .Fl ( d ,
 .Fl D ,
+.Fl x ,
 or
-.Fl x )
+.Fl y )
 is used.
 .Pp
 The options are as follows:
@@ -80,34 +85,86 @@ If no
 interval is specified, the default is 1 second.
 .It Fl C
 Show CPU statistics.
-This is enabled by default unless the
+This is enabled by default unless any of the
 .Fl d ,
 .Fl D ,
 .Fl T ,
+.Fl x ,
 or
-.Fl x
+.Fl y
 flags are used.
 .It Fl d
 Show disk statistics.
 This is the default.
-Displays kilobytes per
-transfer, number of transfers, and megabytes transferred.
-Use of this flag disables display of CPU and tty statistics.
+Displays
+number of transfers per second,
+kilobytes per transfer,
+and
+megabytes transferred per second.
+Use of this flag disables the default display of CPU and tty statistics.
 .It Fl D
 Show alternative disk statistics.
-Displays kilobytes transferred, number of
-transfers, and time spent in transfers.
+Displays
+number of transfers,
+kilobytes transferred,
+and
+time spent in transfers,
+during the
+.Ar wait
+period (or since boot with
+.Fl I ) .
 Use of this flag disables the default display.
+.It Fl H Ar height
+Set the page size (length, or height) explicitly, as the number of lines,
+.Ar height .
+If not set, the page length is taken from the environment variable
+.Ev LINES
+if set, or from the terminal (window) size, if output is to a terminal
+and its size is set,
+and otherwise defaults to 20.
+If explicitly set to zero, pages are considered to be infinitely long.
+This parameter determines the frequency at which repeated headers are output.
+If the value is greater than zero, but too small for the header, along with
+one output set, then a new header will be produced for each set of output.
 .It Fl I
 Show the running total values, rather than an average.
+.ig ii
+.It Fl i
+Like
+.Fl I
+except the totals shown are those since
+.Nm
+started running, rather than since the system booted.
+In this case the first output would necessarily be zero,
+so is suppressed.
+Consequently this option produces no output if neither
+.Fl w
+nor
+.Fl c
+is given.
+.ii
 .It Fl T
 Show tty statistics.
-This is enabled by default unless the
+This is enabled by default unless one, or more, of the
 .Fl C ,
 .Fl d ,
+.Fl D ,
+.Fl x ,
 or
-.Fl D
+.Fl y
 flags are used.
+.ig uu
+.It Fl u
+When totals are being shown
+.Pq Fl I
+.ig ii
+.Po or
+.Fl i Pc
+.ii
+include a column after each relevant output column
+showing the difference between this output and the previous.
+These added columns will be blank in the first displayed output.
+.uu
 .It Fl w Ar wait
 Pause
 .Ar wait
@@ -115,16 +172,51 @@ seconds between each display.
 If no repeat
 .Ar count
 is specified, the default is infinity.
+.It Fl W Ar width
+Set the page width explicitly, as the number of columns of characters,
+.Ar width .
+If not set, the page width is taken from the environment variable
+.Ev COLUMNS
+if set, or from the terminal (window) size, if output is to a terminal
+and its size is set,
+and otherwise defaults to 80.
+If explicitly set to 0, lines are considered infinitely long.
+This width is used only to determine the number of drives to display
+by default when no drive list is given.
+In other cases output will be as wide as needed to display the
+data requested.
 .It Fl x
 Show extended disk statistics.
 Each disk is displayed on a line of its own with all available
 statistics.
 This option overrides all other display options, and all
-disks are displayed unless specific disks
+disks are displayed unless specific disk names
 are provided as arguments.
 Additionally, separate read and write statistics are displayed.
+The
+.Fl C
+and
+.Fl T
+options are ignored with this output format.
 .It Fl y
-Shows the extended statistics and additional queuing statistics.
+Shows the extended statistics (as with
+.Fl x )
+and additional queuing statistics.
+Output does not fit in an 80 column window.
+The
+.Fl C
+and
+.Fl T
+options are ignored with this output format.
+.It Fl z
+Replaces drive and CPU statistic outputs that are exactly zero with spaces.
+Note that zero values can still appear \(en this indicates a
+value that was not zero, but was rounded down so appears as zero.
+Drive output is replaced by spaces only when the drive did no
+input or output at all in the interval,
+or with
+.Fl I ,
+has never done any I/O.
 .El
 .Pp
 .Nm
@@ -152,27 +244,29 @@ line, either as names or
 patterns.
 .Pp
 .Bl -tag -width indent -compact
-.It KB/t
-Kilobytes transferred per disk transfer
 .It t/s
 transfers per second
+.It KB/t
+Kilobytes transferred per disk transfer
 .It MB/s
 Megabytes transferred per second
 .El
+.Pp
 The alternative display format, (selected with
 .Fl D ) ,
-presents the following values.
+presents the following values:
 .Bl -tag -width indent -compact
-.It KB
-Kilobytes transferred
 .It xfr
 Disk transfers
+.It KB
+Kilobytes transferred
 .It time
 Seconds spent in disk activity
 .El
+.Pp
 With the
 .Fl y
-flag, the following queuing measurements are added
+flag, the following queuing measurements are added:
 .Bl -tag -width indent -compact
 .It wait
 Number of I/O requests queued up
@@ -200,6 +294,9 @@ and is then shown as zeros.
 .It \&id
 % of CPU time in idle mode
 .El
+.Pp
+Note that because of rounding, these percentages may
+appear to total more or less than 100.
 .El
 .Sh SEE ALSO
 .Xr fstat 1 ,
@@ -221,7 +318,21 @@ The
 .Fl x
 option was added in
 .Nx 1.4 .
-Collection of queueing values and the
+Collection of queuing values and the
 .Fl y
 option were added in
 .Nx 8.0 .
+The archaic option format:
+.br
+.ti +3n
+.Nm
+.Op Ar drives ...
+.Oo Ar wait Op Ar count Oc
+.br
+remains supported (the first
+.Ar drive
+whose name starts with a digit is taken to be the
+.Ar wait
+period) but is deprecated,
+and may be removed in a future version,
+so should not be used.

Index: src/usr.sbin/iostat/iostat.c
diff -u src/usr.sbin/iostat/iostat.c:1.68 src/usr.sbin/iostat/iostat.c:1.69
--- src/usr.sbin/iostat/iostat.c:1.68	Fri Jun 17 01:47:45 2022
+++ src/usr.sbin/iostat/iostat.c	Sat Jun 18 11:33:13 2022
@@ -1,4 +1,4 @@
-/*	$NetBSD: iostat.c,v 1.68 2022/06/17 01:47:45 kre Exp $	*/
+/*	$NetBSD: iostat.c,v 1.69 2022/06/18 11:33:13 kre Exp $	*/
 
 /*
  * Copyright (c) 1996 John M. Vinopal
@@ -71,7 +71,7 @@ __COPYRIGHT("@(#) Copyright (c) 1986, 19
 #if 0
 static char sccsid[] = "@(#)iostat.c	8.3 (Berkeley) 4/28/95";
 #else
-__RCSID("$NetBSD: iostat.c,v 1.68 2022/06/17 01:47:45 kre Exp $");
+__RCSID("$NetBSD: iostat.c,v 1.69 2022/06/18 11:33:13 kre Exp $");
 #endif
 #endif /* not lint */
 
@@ -99,7 +99,12 @@ static int	defdrives;
 static int	winlines = 20;
 static int	wincols = 80;
 
+static int *order, ordersize;
+
+static char Line_Marker[] = "________________________________________________";
+
 #define	MAX(a,b)	(((a)>(b))?(a):(b))
+#define	MIN(a,b)	(((a)<(b))?(a):(b))
 
 #define	ISSET(x, a)	((x) & (a))
 #define	SHOW_CPU	(1<<0)
@@ -108,22 +113,85 @@ static int	wincols = 80;
 #define	SHOW_STATS_2	(1<<3)
 #define	SHOW_STATS_X	(1<<4)
 #define	SHOW_STATS_Y	(1<<5)
+#define	SHOW_UPDATES	(1<<6)
 #define	SHOW_TOTALS	(1<<7)
-#define	SHOW_STATS_ALL	(SHOW_STATS_1 | SHOW_STATS_2 | SHOW_STATS_X | SHOW_STATS_Y)
+#define	SHOW_NEW_TOTALS	(1<<8)
+#define	SUPPRESS_ZERO	(1<<9)
+
+#define	SHOW_STATS_ALL	(SHOW_STATS_1 | SHOW_STATS_2 |	\
+			    SHOW_STATS_X | SHOW_STATS_Y)
+
+/*
+ * Decide how many screen columns each output statistic is given
+ * (these are determined empirically ("looks good to me") and likely
+ * will require changes from time to time as technology advances).
+ *
+ * The odd "+ N" at the end of the summary (total width of stat) definition
+ * allows for the gaps between the columns, and is (#data cols - 1).
+ * So, tty stats have "in" and "out", 2 columns, so there is 1 extra space,
+ * whereas the cpu stats have 5 columns, so 4 extra spaces (etc).
+ */
+#define	LAYOUT_TTY_IN	4	/* tty input in last interval */
+#define	LAYOUT_TTY_TIN	7	/* tty input forever */
+#define	LAYOUT_TTY_OUT	5	/* tty output in last interval */
+#define	LAYOUT_TTY_TOUT	10	/* tty output forever */
+#define	LAYOUT_TTY	(((todo & SHOW_TOTALS)				     \
+				? (LAYOUT_TTY_TIN + LAYOUT_TTY_TOUT)	     \
+				: (LAYOUT_TTY_IN + LAYOUT_TTY_OUT)) + 1)
+#define	LAYOUT_TTY_GAP	0		/* always starts at left margin */
+
+#define	LAYOUT_CPU_USER	2
+#define	LAYOUT_CPU_NICE	2
+#define	LAYOUT_CPU_SYS	2
+#define	LAYOUT_CPU_INT	2
+#define	LAYOUT_CPU_IDLE	3
+#define	LAYOUT_CPU	(LAYOUT_CPU_USER + LAYOUT_CPU_NICE + LAYOUT_CPU_SYS + \
+			    LAYOUT_CPU_INT + LAYOUT_CPU_IDLE + 4)
+#define	LAYOUT_CPU_GAP	2
+
+			/* used for:       w/o TOTALS  w TOTALS	*/
+#define	LAYOUT_DRIVE_1_XSIZE	5	/*	KB/t	KB/t	*/
+#define	LAYOUT_DRIVE_1_RATE	6	/*	t/s		*/
+#define	LAYOUT_DRIVE_1_XFER	10	/*		xfr	*/
+#define	LAYOUT_DRIVE_1_SPEED	5	/*	MB/s		*/
+#define	LAYOUT_DRIVE_1_VOLUME	8	/*		MB	*/
+#define	LAYOUT_DRIVE_1_INCR	5	/*		(inc)	*/
+
+#define	LAYOUT_DRIVE_2_XSIZE	7	/*	KB		*/
+#define	LAYOUT_DRIVE_2_VOLUME	11	/*		KB	*/
+#define	LAYOUT_DRIVE_2_XFR	7	/*	xfr		*/
+#define	LAYOUT_DRIVE_2_TXFR	10	/*		xfr	*/
+#define	LAYOUT_DRIVE_2_INCR	5	/*		(inc)	*/
+#define	LAYOUT_DRIVE_2_TBUSY	9	/*		time	*/
+#define	LAYOUT_DRIVE_2_BUSY	5	/*	time		*/
+
+#define	LAYOUT_DRIVE_1	(LAYOUT_DRIVE_1_XSIZE + ((todo & SHOW_TOTALS) ?	       \
+			    (LAYOUT_DRIVE_1_XFER + LAYOUT_DRIVE_1_VOLUME +     \
+			    ((todo&SHOW_UPDATES)? 2*LAYOUT_DRIVE_1_INCR+2 :0)) \
+			  : (LAYOUT_DRIVE_1_RATE + LAYOUT_DRIVE_1_SPEED)) + 3)
+#define	LAYOUT_DRIVE_2	(((todo & SHOW_TOTALS) ? (LAYOUT_DRIVE_2_VOLUME +      \
+			    LAYOUT_DRIVE_2_TXFR + LAYOUT_DRIVE_2_TBUSY +       \
+			    ((todo&SHOW_UPDATES)? 2*LAYOUT_DRIVE_2_INCR+2 : 0))\
+			  : (LAYOUT_DRIVE_2_XSIZE + LAYOUT_DRIVE_2_XFR +       \
+			     LAYOUT_DRIVE_2_BUSY)) + 3)
+
+#define	LAYOUT_DRIVE_GAP 0	/* Gap included in column, always present */
+
+/* TODO: X & Y stats layouts */
 
 static void cpustats(void);
 static double drive_time(double, int);
-static void drive_stats(double);
-static void drive_stats2(double);
-static void drive_statsx(double);
-static void drive_statsy(double);
+static void drive_stats(int, double);
+static void drive_stats2(int, double);
+static void drive_statsx(int, double);
+static void drive_statsy(int, double);
 static void drive_statsy_io(double, double, double);
 static void drive_statsy_q(double, double, double, double, double, double);
 static void sig_header(int);
 static volatile int do_header;
-static void header(void);
+static void header(int);
 __dead static void usage(void);
-static void display(void);
+static void display(int);
 static int selectdrives(int, char *[], int);
 
 int
@@ -132,8 +200,14 @@ main(int argc, char *argv[])
 	int ch, hdrcnt, hdroffset, ndrives, lines;
 	struct timespec	tv;
 	struct ttysize ts;
+	long width = -1, height = -1;
+	char *ep;
 
-	while ((ch = getopt(argc, argv, "Cc:dDITw:xy")) != -1)
+#if 0		/* -i and -u are not currently (sanely) implementable */
+	while ((ch = getopt(argc, argv, "Cc:dDH:iITuw:W:xyz")) != -1)
+#else
+	while ((ch = getopt(argc, argv, "Cc:dDH:ITw:W:xyz")) != -1)
+#endif
 		switch (ch) {
 		case 'c':
 			if ((reps = atoi(optarg)) <= 0)
@@ -150,16 +224,37 @@ main(int argc, char *argv[])
 			todo &= ~SHOW_STATS_ALL;
 			todo |= SHOW_STATS_2;
 			break;
+		case 'H':
+			height = strtol(optarg, &ep, 10);
+			if (height < 0 || *ep != '\0')
+				errx(1, "bad height (-H) value.");
+			height += 2;	/* magic, but needed to be sane */
+			break;
+#if 0
+		case 'i':
+			todo |= SHOW_TOTALS | SHOW_NEW_TOTALS;
+			break;
+#endif
 		case 'I':
 			todo |= SHOW_TOTALS;
 			break;
 		case 'T':
 			todo |= SHOW_TTY;
 			break;
+#if 0
+		case 'u':
+			todo |= SHOW_UPDATES;
+			break;
+#endif
 		case 'w':
 			if ((interval = atoi(optarg)) <= 0)
 				errx(1, "interval <= 0.");
 			break;
+		case 'W':
+			width = strtol(optarg, &ep, 10);
+			if (width < 0 || *ep != '\0')
+				errx(1, "bad width (-W) value.");
+			break;
 		case 'x':
 			todo &= ~SHOW_STATS_ALL;
 			todo |= SHOW_STATS_X;
@@ -168,6 +263,9 @@ main(int argc, char *argv[])
 			todo &= ~SHOW_STATS_ALL;
 			todo |= SHOW_STATS_Y;
 			break;
+		case 'z':
+			todo |= SUPPRESS_ZERO;
+			break;
 		case '?':
 		default:
 			usage();
@@ -193,16 +291,40 @@ main(int argc, char *argv[])
 			wincols = ts.ts_cols;
 	}
 
-	defdrives = wincols;
-	if (ISSET(todo, SHOW_CPU))
-		defdrives -= 16;	/* XXX magic number */
-	if (ISSET(todo, SHOW_TTY))
-		defdrives -= 10;	/* XXX magic number */
-	defdrives /= 18;		/* XXX magic number */
+	if (height == -1) {
+		char *lns = getenv("LINES");
+
+		if (lns == NULL || (height = strtol(lns, &ep, 10)) < 0 ||
+		    *ep != '\0')
+			height = winlines;
+	}
+	winlines = height;
+
+	if (width == -1) {
+		char *cols = getenv("COLUMNS");
+
+		if (cols == NULL || (width = strtol(cols, &ep, 10)) < 0 ||
+		    *ep != '\0')
+			width = wincols;
+	}
+	defdrives = width;
+	if (defdrives == 0) {
+		defdrives = 5000;	/* anything absurdly big */
+	} else {
+		if (ISSET(todo, SHOW_CPU))
+			defdrives -= LAYOUT_CPU + LAYOUT_CPU_GAP;
+		if (ISSET(todo, SHOW_TTY))
+			defdrives -= LAYOUT_TTY + LAYOUT_TTY_GAP;
+		if (ISSET(todo, SHOW_STATS_2))
+			defdrives /= LAYOUT_DRIVE_2 + LAYOUT_DRIVE_GAP;
+		else
+			defdrives /= LAYOUT_DRIVE_1 + LAYOUT_DRIVE_GAP;
+	}
 
 	drvinit(0);
 	cpureadstats();
 	drvreadstats();
+	ordersize = 0;
 	ndrives = selectdrives(argc, argv, 1);
 	if (ndrives == 0) {
 		/* No drives are selected.  No need to show drive stats. */
@@ -215,6 +337,7 @@ main(int argc, char *argv[])
 
 	/* print a new header on sigcont */
 	(void)signal(SIGCONT, sig_header);
+	do_header = 1;
 
 	for (hdrcnt = 1;;) {
 		if (ISSET(todo, SHOW_STATS_X | SHOW_STATS_Y)) {
@@ -225,19 +348,20 @@ main(int argc, char *argv[])
 			hdroffset = 4;
 		}
 
-		if (do_header || (hdrcnt -= lines) <= 0) {
+		if (do_header || (winlines != 0 && (hdrcnt -= lines) <= 0)) {
 			do_header = 0;
-			header();
+			header(ndrives);
 			hdrcnt = winlines - hdroffset;
 		}
 
-		if (!ISSET(todo, SHOW_TOTALS)) {
+		if (!ISSET(todo, SHOW_TOTALS) || ISSET(todo, SHOW_NEW_TOTALS)) {
 			cpuswap();
 			drvswap();
 			tkswap();
+			todo &= ~SHOW_NEW_TOTALS;
 		}
 
-		display();
+		display(ndrives);
 
 		if (reps >= 0 && --reps <= 0)
 			break;
@@ -257,9 +381,9 @@ sig_header(int signo)
 }
 
 static void
-header(void)
+header(int ndrives)
 {
-	size_t i;
+	int i;
 
 					/* Main Headers. */
 	if (ISSET(todo, SHOW_STATS_X)) {
@@ -283,47 +407,101 @@ header(void)
 	}
 
 	if (ISSET(todo, SHOW_TTY))
-		(void)printf("      tty");
+		(void)printf("%*s", LAYOUT_TTY_GAP + LAYOUT_TTY, "tty");
 
 	if (ISSET(todo, SHOW_STATS_1)) {
-		for (i = 0; i < ndrive; i++)
-			if (cur.select[i])
-				(void)printf("        %9.9s ", cur.name[i]);
+		for (i = 0; i < ndrives; i++) {
+			char *dname = cur.name[order[i]];
+			int dnlen = (int)strlen(dname);
+
+			printf(" ");	/* always a 1 column gap */
+			if (dnlen < LAYOUT_DRIVE_1 - 6)
+				printf("|%-*.*s ",
+				    (LAYOUT_DRIVE_1 - 1 - dnlen - 1) / 2 - 1,
+				    (LAYOUT_DRIVE_1 - 1 - dnlen - 1) / 2 - 1,
+				    Line_Marker);
+			printf("%*.*s", ((dnlen >= LAYOUT_DRIVE_1 - 6) ?
+			    MIN(MAX((LAYOUT_DRIVE_1 - dnlen) / 2, 0),
+				LAYOUT_DRIVE_1) : 0),
+			    LAYOUT_DRIVE_1, dname);
+			if (dnlen < LAYOUT_DRIVE_1 - 6)
+				printf(" %*.*s|",
+				    (LAYOUT_DRIVE_1 - 1 - dnlen - 2) / 2 - 1,
+				    (LAYOUT_DRIVE_1 - 1 - dnlen - 2) / 2 - 1,
+				    Line_Marker);
+		}
 	}
 
 	if (ISSET(todo, SHOW_STATS_2)) {
-		for (i = 0; i < ndrive; i++)
-			if (cur.select[i])
-				(void)printf("        %9.9s ", cur.name[i]);
+		for (i = 0; i < ndrives; i++) {
+			char *dname = cur.name[order[i]];
+			int dnlen = (int)strlen(dname);
+
+			printf(" ");	/* always a 1 column gap */
+			if (dnlen < LAYOUT_DRIVE_2 - 6)
+				printf("|%-*.*s ",
+				    (LAYOUT_DRIVE_2 - 1 - dnlen - 1) / 2 - 1,
+				    (LAYOUT_DRIVE_2 - 1 - dnlen - 1) / 2 - 1,
+				    Line_Marker);
+			printf("%*.*s", ((dnlen >= LAYOUT_DRIVE_1 - 6) ?
+			    MIN(MAX((LAYOUT_DRIVE_2 - dnlen) / 2, 0),
+				LAYOUT_DRIVE_2) : 0),
+			    LAYOUT_DRIVE_1, dname);
+			if (dnlen < LAYOUT_DRIVE_2 - 6)
+				printf(" %*.*s|",
+				    (LAYOUT_DRIVE_2 - 1 - dnlen - 2) / 2 - 1,
+				    (LAYOUT_DRIVE_2 - 1 - dnlen - 2) / 2 - 1,
+				    Line_Marker);
+		}
 	}
 
 	if (ISSET(todo, SHOW_CPU))
-		(void)printf("            CPU");
+		(void)printf("%*s", LAYOUT_CPU + LAYOUT_CPU_GAP, "CPU");
 
 	printf("\n");
 
 					/* Sub-Headers. */
-	if (ISSET(todo, SHOW_TTY))
-		printf(" tin  tout");
+	if (ISSET(todo, SHOW_TTY)) {
+		printf("%*s %*s",
+		   ((todo&SHOW_TOTALS)?LAYOUT_TTY_TIN:LAYOUT_TTY_IN), "tin",
+		   ((todo&SHOW_TOTALS)?LAYOUT_TTY_TOUT:LAYOUT_TTY_OUT), "tout");
+	}
 
 	if (ISSET(todo, SHOW_STATS_1)) {
-		for (i = 0; i < ndrive; i++)
-			if (cur.select[i]) {
-				if (ISSET(todo, SHOW_TOTALS))
-					(void)printf("  KB/t  xfr  MB   ");
-				else
-					(void)printf("  KB/t  t/s  MB/s ");
+		for (i = 0; i < ndrives; i++) {
+			if (ISSET(todo, SHOW_TOTALS)) {
+				(void)printf(" %*s %*s %*s",
+				    LAYOUT_DRIVE_1_XFER, "xfr",
+				    LAYOUT_DRIVE_1_XSIZE, "KB/t",
+				    LAYOUT_DRIVE_1_VOLUME, "MB");
+			} else {
+				(void)printf(" %*s %*s %*s",
+				    LAYOUT_DRIVE_1_RATE, "t/s",
+				    LAYOUT_DRIVE_1_XSIZE, "KB/t",
+				    LAYOUT_DRIVE_1_SPEED, "MB/s");
 			}
+		}
 	}
 
 	if (ISSET(todo, SHOW_STATS_2)) {
-		for (i = 0; i < ndrive; i++)
-			if (cur.select[i])
-				(void)printf("    KB   xfr time ");
+		for (i = 0; i < ndrives; i++) {
+			if (ISSET(todo, SHOW_TOTALS)) {
+				(void)printf(" %*s %*s %*s",
+				    LAYOUT_DRIVE_2_TXFR, "xfr",
+				    LAYOUT_DRIVE_2_VOLUME, "KB",
+				    LAYOUT_DRIVE_2_TBUSY, "time");
+			} else {
+				(void)printf(" %*s %*s %*s",
+				    LAYOUT_DRIVE_2_XFR, "xfr",
+				    LAYOUT_DRIVE_2_XSIZE, "KB",
+				    LAYOUT_DRIVE_2_BUSY, "time");
+			}
+		}
 	}
 
+	/* should do this properly, but it is such a simple case... */
 	if (ISSET(todo, SHOW_CPU))
-		(void)printf(" us ni sy in id");
+		(void)printf("  us ni sy in  id");
 	printf("\n");
 }
 
@@ -342,30 +520,53 @@ drive_time(double etime, int dn)
 }
 
 static void
-drive_stats(double etime)
+drive_stats(int ndrives, double etime)
 {
-	size_t dn;
+	int drive;
 	double atime, dtime, mbps;
+	int c1, c2, c3;
+
+	if (ISSET(todo, SHOW_TOTALS)) {
+		c1 = LAYOUT_DRIVE_1_XFER;
+		c2 = LAYOUT_DRIVE_1_XSIZE;
+		c3 = LAYOUT_DRIVE_1_VOLUME;	
+	} else {
+		c1 = LAYOUT_DRIVE_1_RATE;
+		c2 = LAYOUT_DRIVE_1_XSIZE;
+		c3 = LAYOUT_DRIVE_1_SPEED;
+	}
 
-	for (dn = 0; dn < ndrive; ++dn) {
-		if (!cur.select[dn])
+	for (drive = 0; drive < ndrives; ++drive) {
+		int dn = order[drive];
+
+		if (!cur.select[dn])	/* should be impossible */
 			continue;
 
+		if (todo & SUPPRESS_ZERO) {
+			if (cur.rxfer[dn] == 0 &&
+			    cur.wxfer[dn] == 0 &&
+			    cur.rbytes[dn] == 0 &&
+			    cur.wbytes[dn] == 0) {
+				printf("%*s", c1 + 1 + c2 + 1 + c3 + 1, "");
+				continue;
+			}
+		}
+
 		dtime = drive_time(etime, dn);
 
+					/* average transfers per second. */
+		(void)printf(" %*.0f", c1,
+		    (cur.rxfer[dn] + cur.wxfer[dn]) / dtime);
+
 					/* average Kbytes per transfer. */
 		if (cur.rxfer[dn] + cur.wxfer[dn])
 			mbps = ((cur.rbytes[dn] + cur.wbytes[dn]) /
 			    1024.0) / (cur.rxfer[dn] + cur.wxfer[dn]);
 		else
 			mbps = 0.0;
-		(void)printf(" %5.*f",
+		(void)printf(" %*.*f", c2,
 		    MAX(0, 3 - (int)floor(log10(fmax(1.0, mbps)))), mbps);
 
-					/* average transfers per second. */
-		(void)printf(" %4.0f",
-		    (cur.rxfer[dn] + cur.wxfer[dn]) / dtime);
-
 					/* time busy in drive activity */
 		atime = (double)cur.time[dn].tv_sec +
 		    ((double)cur.time[dn].tv_usec / (double)1000000);
@@ -377,52 +578,85 @@ drive_stats(double etime)
 		else
 			mbps = 0;
 		mbps /= dtime;
-		(void)printf(" %5.*f ",
+		(void)printf(" %*.*f", c3,
 		    MAX(0, 3 - (int)floor(log10(fmax(1.0, mbps)))), mbps);
 	}
 }
 
 static void
-drive_stats2(double etime)
+drive_stats2(int ndrives, double etime)
 {
-	size_t dn;
+	int drive;
 	double atime, dtime;
+	int c1, c2, c3;
+
+	if (ISSET(todo, SHOW_TOTALS)) {
+		c1 = LAYOUT_DRIVE_2_TXFR;
+		c2 = LAYOUT_DRIVE_2_VOLUME;
+		c3 = LAYOUT_DRIVE_2_TBUSY;	
+	} else {
+		c1 = LAYOUT_DRIVE_2_XFR;
+		c2 = LAYOUT_DRIVE_2_XSIZE;
+		c3 = LAYOUT_DRIVE_2_BUSY;
+	}
 
-	for (dn = 0; dn < ndrive; ++dn) {
-		if (!cur.select[dn])
+	for (drive = 0; drive < ndrives; ++drive) {
+		int dn = order[drive];
+
+		if (!cur.select[dn])		/* should be impossible */
 			continue;
 
-		dtime = drive_time(etime, dn);
+		if (todo & SUPPRESS_ZERO) {
+			if (cur.rxfer[dn] == 0 &&
+			    cur.wxfer[dn] == 0 &&
+			    cur.rbytes[dn] == 0 &&
+			    cur.wbytes[dn] == 0) {
+				printf("%*s", c1 + 1 + c2 + 1 + c3 + 1, "");
+				continue;
+			}
+		}
 
-					/* average kbytes per second. */
-		(void)printf(" %5.0f",
-		    (cur.rbytes[dn] + cur.wbytes[dn]) / 1024.0 / dtime);
+		dtime = drive_time(etime, dn);
 
 					/* average transfers per second. */
-		(void)printf(" %5.0f",
+		(void)printf(" %*.0f", c1,
 		    (cur.rxfer[dn] + cur.wxfer[dn]) / dtime);
 
-					/* average time busy in drive activity */
+					/* average kbytes per second. */
+		(void)printf(" %*.0f", c2,
+		    (cur.rbytes[dn] + cur.wbytes[dn]) / 1024.0 / dtime);
+
+					/* average time busy in dn activity */
 		atime = (double)cur.time[dn].tv_sec +
 		    ((double)cur.time[dn].tv_usec / (double)1000000);
-		(void)printf(" %4.2f ", atime / dtime);
+		(void)printf(" %*.2f", c3, atime / dtime);
 	}
 }
 
 static void
-drive_statsx(double etime)
+drive_statsx(int ndrives, double etime)
 {
-	size_t dn;
+	int dn, drive;
 	double atime, dtime, kbps;
 
-	for (dn = 0; dn < ndrive; ++dn) {
-		if (!cur.select[dn])
-			continue;
+	for (drive = 0; drive < ndrives; ++drive) {
+		dn = order[drive];
 
-		dtime = drive_time(etime, dn);
+		if (!cur.select[dn])	/* impossible */
+			continue;
 
 		(void)printf("%-8.8s", cur.name[dn]);
 
+		if (todo & SUPPRESS_ZERO) {
+			if (cur.rbytes[dn] == 0 && cur.rxfer[dn] == 0 &&
+			    cur.wbytes[dn] == 0 && cur.wxfer[dn] == 0) {
+				printf("\n");
+				continue;
+			}
+		}
+
+		dtime = drive_time(etime, dn);
+
 					/* average read Kbytes per transfer */
 		if (cur.rxfer[dn])
 			kbps = (cur.rbytes[dn] / 1024.0) / cur.rxfer[dn];
@@ -510,19 +744,28 @@ drive_statsy_q(double elapsed, double bu
 }
 
 static void
-drive_statsy(double etime)
+drive_statsy(int ndrives, double etime)
 {
-	size_t dn;
+	int drive, dn;
 	double atime, await, abusysum, awaitsum, dtime;
 
-	for (dn = 0; dn < ndrive; ++dn) {
-		if (!cur.select[dn])
+	for (drive = 0; drive < ndrives; ++drive) {
+		dn = order[drive];
+		if (!cur.select[dn])	/* impossible */
 			continue;
 
-		dtime = drive_time(etime, dn);
-
 		(void)printf("%-8.8s", cur.name[dn]);
 
+		if (todo & SUPPRESS_ZERO) {
+			if (cur.rbytes[dn] == 0 && cur.rxfer[dn] == 0 &&
+			    cur.wbytes[dn] == 0 && cur.wxfer[dn] == 0) {
+				printf("\n");
+				continue;
+			}
+		}
+
+		dtime = drive_time(etime, dn);
+
 		atime = (double)cur.time[dn].tv_sec +
 		    ((double)cur.time[dn].tv_usec / (double)1000000);
 		await = (double)cur.wait[dn].tv_sec +
@@ -547,27 +790,42 @@ cpustats(void)
 	int state;
 	double ttime;
 
+	static int cwidth[CPUSTATES] = {
+		LAYOUT_CPU_USER,
+		LAYOUT_CPU_NICE,
+		LAYOUT_CPU_SYS,
+		LAYOUT_CPU_INT,
+		LAYOUT_CPU_IDLE
+	};
+
 	ttime = 0;
 	for (state = 0; state < CPUSTATES; ++state)
 		ttime += cur.cp_time[state];
 	if (!ttime)
 		ttime = 1.0;
-			/* States are generally never 100% and can use %3.0f. */
-	for (state = 0; state < CPUSTATES; ++state)
-		printf(" %2.0f", 100. * cur.cp_time[state] / ttime);
+
+	printf("%*s", LAYOUT_CPU_GAP - 1, "");	/* the 1 is the next space */
+	for (state = 0; state < CPUSTATES; ++state) {
+		if ((todo & SUPPRESS_ZERO) && cur.cp_time[state] == 0) {
+			printf(" %*s", cwidth[state], "");
+			continue;
+		}
+		printf(" %*.0f", cwidth[state],
+		    100. * cur.cp_time[state] / ttime);
+	}
 }
 
 static void
 usage(void)
 {
 
-	(void)fprintf(stderr, "usage: iostat [-CdDITxy] [-c count] "
-	    "[-w wait] [drives]\n");
+	(void)fprintf(stderr, "usage: iostat [-CdDITxyz] [-c count] "
+	    "[-H height] [-W width] [-w wait] [drives]\n");
 	exit(1);
 }
 
 static void
-display(void)
+display(int ndrives)
 {
 	double	etime;
 
@@ -582,25 +840,29 @@ display(void)
 		etime = 1.0;
 
 	if (ISSET(todo, SHOW_STATS_X)) {
-		drive_statsx(etime);
+		drive_statsx(ndrives, etime);
 		goto out;
 	}
 
 	if (ISSET(todo, SHOW_STATS_Y)) {
-		drive_statsy(etime);
+		drive_statsy(ndrives, etime);
 		goto out;
 	}
 
 	if (ISSET(todo, SHOW_TTY))
-		printf("%4.0f %5.0f", cur.tk_nin / etime, cur.tk_nout / etime);
+		printf("%*.0f %*.0f",
+		    ((todo & SHOW_TOTALS) ? LAYOUT_TTY_TIN : LAYOUT_TTY_IN),
+		    cur.tk_nin / etime,
+		    ((todo & SHOW_TOTALS) ? LAYOUT_TTY_TOUT : LAYOUT_TTY_OUT),
+		    cur.tk_nout / etime);
 
 	if (ISSET(todo, SHOW_STATS_1)) {
-		drive_stats(etime);
+		drive_stats(ndrives, etime);
 	}
 
 
 	if (ISSET(todo, SHOW_STATS_2)) {
-		drive_stats2(etime);
+		drive_stats2(ndrives, etime);
 	}
 
 
@@ -639,7 +901,15 @@ selectdrives(int argc, char *argv[], int
 			if (fnmatch(*argv, cur.name[i], 0))
 				continue;
 			cur.select[i] = 1;
-			++ndrives;
+			if (ordersize <= ndrives) {
+				int *new = realloc(order,
+				    (ordersize + 8) * sizeof *order);
+				if (new == NULL)
+					break;
+				ordersize += 8;
+				order = new;
+			}
+			order[ndrives++] = i;
 		}
 
 	}
@@ -652,8 +922,14 @@ selectdrives(int argc, char *argv[], int
 		maxdrives = (ISSET(todo, SHOW_STATS_X | SHOW_STATS_Y) ||
 			     (int)ndrive < defdrives)
 			? (int)(ndrive) : defdrives;
+		ordersize = maxdrives;
+		free(order);
+		order = calloc(ordersize, sizeof *order);
+		if (order == NULL)
+			errx(1, "Insufficient memory");
 		for (i = 0; i < maxdrives; i++) {
 			cur.select[i] = 1;
+			order[i] = i;
 
 			++ndrives;
 			if (!ISSET(todo, SHOW_STATS_X | SHOW_STATS_Y) &&

Reply via email to