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) &&