git: c263dd413ca0 - main - gstat: Refactor and polish. Added option for {kB/MB/GB} per second.

From: Poul-Henning Kamp <phk_at_FreeBSD.org>
Date: Sat, 27 Jun 2026 11:28:44 UTC
The branch main has been updated by phk:

URL: https://cgit.FreeBSD.org/src/commit/?id=c263dd413ca0b43b4513e6e6dd8ed64c0eb9c3aa

commit c263dd413ca0b43b4513e6e6dd8ed64c0eb9c3aa
Author:     Poul-Henning Kamp <phk@FreeBSD.org>
AuthorDate: 2026-06-27 11:26:18 +0000
Commit:     Poul-Henning Kamp <phk@FreeBSD.org>
CommitDate: 2026-06-27 11:28:33 +0000

    gstat:  Refactor and polish.  Added option for {kB/MB/GB} per second.
---
 usr.sbin/gstat/gstat.8 |  24 +-
 usr.sbin/gstat/gstat.c | 770 ++++++++++++++++++++++++++-----------------------
 2 files changed, 437 insertions(+), 357 deletions(-)

diff --git a/usr.sbin/gstat/gstat.8 b/usr.sbin/gstat/gstat.8
index e882aa75b8d7..602376d5fd0d 100644
--- a/usr.sbin/gstat/gstat.8
+++ b/usr.sbin/gstat/gstat.8
@@ -78,6 +78,12 @@ Only devices with the names matching
 will be displayed.
 The format of the regular expression is described in
 .Xr re_format 7 .
+.It Fl g
+Show Gigabytes
+.It Fl k
+Show kilobytes
+.It Fl m
+Show Megabytes
 .It Fl I Ar interval
 Refresh the
 .Nm
@@ -96,7 +102,7 @@ Enable display of statistics for other operations
 .It Fl p
 Only display physical providers (those with rank of 1).
 .It Fl s
-Enable blocks' size statistics.
+Enable average block size.
 .El
 .Sh INTERACTIVE COMMANDS
 These commands are currently recognized if
@@ -107,17 +113,33 @@ is not running in batch mode:
 Halve the update interval.
 .It Ic >
 Double the update interval.
+.It Ic 1
+Set update interval to one second
 .It Ic c
 Toggle display of
 .Xr geom 4
 consumers.
+.It Ic d
+Toggle display BIO_DELETE
 .It Ic f
 Display only devices with the names matching regular expression filter
 (prompt for filter).
 .It Ic F
 Remove device filter.
+.It Ic g
+Show gigabytes.
+.It Ic k
+Show kilobytes.
+.It Ic m
+Show megabytes.
+.It Ic o
+Toggle display other (BIO_FLUSH) operations.
+.It Ic p
+Toggle display other only primary providers.
 .It Ic q
 Quit
+.It Ic s
+Toggle average block size.
 .Nm .
 .El
 .Sh EXIT STATUS
diff --git a/usr.sbin/gstat/gstat.c b/usr.sbin/gstat/gstat.c
index c650f7120a40..11616d263272 100644
--- a/usr.sbin/gstat/gstat.c
+++ b/usr.sbin/gstat/gstat.c
@@ -44,6 +44,7 @@
 #include <libgeom.h>
 #include <paths.h>
 #include <regex.h>
+#include <stdarg.h>
 #include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -51,20 +52,190 @@
 #include <sysexits.h>
 #include <unistd.h>
 
+#define HIGH_PCT_BUSY_THRESH 80
+#define MEDIUM_PCT_BUSY_THRESH 50
+
 static int flag_a, flag_b, flag_B, flag_c, flag_C, flag_d, flag_o, flag_p,
 	   flag_s;
 static int flag_I = 1000000;
 
-#define HIGH_PCT_BUSY_THRESH 80
-#define MEDIUM_PCT_BUSY_THRESH 50
-#define PRINTMSG(...) do {						\
-		if ((flag_b && !loop) || (flag_B))			\
-			printf(__VA_ARGS__);				\
-		else if (!flag_b)					\
-			printw(__VA_ARGS__);				\
-	} while(0)
+static int quit;
+static int max_flen;
+static History *hist;
+static EditLine *el;
+static HistEvent hist_ev;
+static regex_t f_re;
+static char pf_s[100];
+static char f_s[100];
+static uintmax_t loop;
+static double dt;
+static double byte_scale = 1024;
+static const char *byte_unit = "k";
+
+struct kstat {
+	long double		transfers_per_second;
+	long double		mb_per_second;
+	long double		ms_per_transaction;
+	long double		kb_per_transfer;
+};
+
+static void __dead2
+usage(void)
+{
+	fprintf(stderr,
+		"usage: gstat [-abBcCdgkmops] [-f filter] [-I interval]\n");
+	exit(EX_USAGE);
+	/* NOTREACHED */
+}
+
+static void
+use_kb(void)
+{
+	byte_scale = 1024;
+	byte_unit = "k";
+}
+
+static void
+use_mb(void)
+{
+	byte_scale = 1;
+	byte_unit = "M";
+}
+
+static void
+use_gb(void)
+{
+	byte_scale = 1.0/1024;
+	byte_unit = "G";
+}
+
+static void
+printer(const char *fmt, ...)
+{
+	va_list ap;
+	va_start(ap, fmt);
+	if (flag_B || (flag_b && loop == 2))
+		vprintf(fmt, ap);
+	if (!flag_b)
+		vw_printw(stdscr, fmt, ap);
+	va_end(ap);
+}
 
-static void usage(void) __dead2;
+static void
+text_fld_header(const char *k)
+{
+	if (flag_s)
+		printer(" %s/s     kB   %sBps   ms/%s   ", k, byte_unit, k);
+	else
+		printer(" %s/s   %sBps   ms/%s   ", k, byte_unit, k);
+}
+
+static void
+text_header(void)
+{
+	int curx = 0, cury = 0;
+	int maxx = 0, maxy = 0;
+
+	if (!flag_b)
+		move(0,0);
+	printer("dT: %5.3fs  w: %.3fs", dt,
+			(float)flag_I / 1000000);
+	if (f_s[0] != '\0') {
+		printer("  filter: ");
+		if (!flag_b) {
+			getyx(stdscr, cury, curx);
+			(void)cury;
+			getmaxyx(stdscr, maxy, maxx);
+			(void)maxy;
+		}
+		strlcpy(pf_s, f_s, sizeof(pf_s));
+		max_flen = maxx - curx - 1;
+		if ((int)strlen(f_s) > max_flen && max_flen >= 0) {
+			if (max_flen > 3)
+				pf_s[max_flen - 3] = '.';
+			if (max_flen > 2)
+				pf_s[max_flen - 2] = '.';
+			if (max_flen > 1)
+				pf_s[max_flen - 1] = '.';
+			pf_s[max_flen] = '\0';
+		}
+		printer("%s", pf_s);
+	}
+
+	if (!flag_b)
+		clrtoeol();
+	printer("\n");
+
+	printer(" L(q)  ops/s   ");
+	text_fld_header("r");
+	text_fld_header("w");
+	if (flag_d)
+		text_fld_header("d");
+	if (flag_o)
+		printer(" o/s   ms/o   ");
+	printer("%%busy Name");
+	if (!flag_b)
+		clrtoeol();
+	printer("\n");
+}
+
+static void
+print_kstat_text(const struct kstat *ks, int other)
+{
+	printer(" %6.0f", (double)ks->transfers_per_second);
+	if (flag_s && !other)
+		printer(" %6.0f", (double)ks->kb_per_transfer);
+	if (!other)
+		printer(" %6.0f", (double)ks->mb_per_second * byte_scale);
+	if (ks->ms_per_transaction < 100)
+		printer(" %6.3f", (double)ks->ms_per_transaction);
+	else if (ks->ms_per_transaction < 1000)
+		printer(" %6.2f", (double)ks->ms_per_transaction);
+	else if (ks->ms_per_transaction < 10000)
+		printer(" %6.1f", (double)ks->ms_per_transaction);
+	else
+		printer(" %6.0f", (double)ks->ms_per_transaction);
+}
+
+static void
+print_head_csv(void)
+{
+
+	printf("timestamp,name,q-depth,total_ops/s");
+	if (flag_s) {
+		printf(",read/s,read_sz-KiB");
+		printf(",read-KiB/s,ms/read");
+		printf(",write/s,write_sz-KiB");
+		printf(",write-KiB/s,ms/write");
+	} else {
+		printf(",read/s,read-KiB/s,ms/read");
+		printf(",write/s,write-KiB/s,ms/write");
+	}
+	if (flag_d) {
+		if (flag_s) {
+			printf(",delete/s,delete-sz-KiB");
+			printf(",delete-KiB/s,ms/delete");
+		} else {
+			printf(",delete/s,delete-KiB/s");
+			printf(",ms/delete");
+		}
+	}
+	if (flag_o)
+		printf(",other/s,ms/other");
+	printf(",%%busy\n");
+}
+
+static void
+print_kstat_csv(const struct kstat *ks, int other)
+{
+
+	printf(",%.0f", (double)ks->transfers_per_second);
+	if (flag_s && !other)
+		printf(",%.0f", (double)ks->kb_per_transfer);
+	if (!other)
+		printf(",%.0f", (double)ks->mb_per_second * 1024);
+	printf(",%.3f", (double)ks->ms_per_transaction);
+}
 
 static const char*
 el_prompt(void)
@@ -73,42 +244,167 @@ el_prompt(void)
 	return ("Filter: ");
 }
 
+static void
+setup_interactive(void)
+{
+	short cf, cb;
+
+	/* Setup libedit */
+	hist = history_init();
+	if (hist == NULL)
+		err(EX_SOFTWARE, "history_init()");
+	history(hist, &hist_ev, H_SETSIZE, 100);
+	el = el_init("gstat", stdin, stdout, stderr);
+	if (el == NULL)
+		err(EX_SOFTWARE, "el_init");
+	el_set(el, EL_EDITOR, "emacs");
+	el_set(el, EL_SIGNAL, 1);
+	el_set(el, EL_HIST, history, hist);
+	el_set(el, EL_PROMPT, el_prompt);
+	if (f_s[0] != '\0')
+		history(hist, &hist_ev, H_ENTER, f_s);
+	/* Setup curses */
+	initscr();
+	start_color();
+	use_default_colors();
+	pair_content(0, &cf, &cb);
+	init_pair(1, COLOR_GREEN, cb);
+	init_pair(2, COLOR_MAGENTA, cb);
+	init_pair(3, COLOR_RED, cb);
+	cbreak();
+	noecho();
+	nonl();
+	nodelay(stdscr, 1);
+	intrflush(stdscr, FALSE);
+	keypad(stdscr, TRUE);
+}
+
+static int
+poll_input(void)
+{
+	int i, line_len;
+	char *p;
+	const char *line;
+	char tmp_f_s[100];
+	regex_t tmp_f_re;
+
+	i = getch();
+	if (i == ERR)
+		return (0);
+	switch (i) {
+	case '>':
+		flag_I *= 2;
+		break;
+	case '<':
+		flag_I /= 2;
+		if (flag_I < 1000)
+			flag_I = 1000;
+		break;
+	case '1':
+		flag_I = 1000000;
+		break;
+	case 'c':
+		flag_c = !flag_c;
+		break;
+	case 'd':
+		flag_d = !flag_d;
+		break;
+	case 'o':
+		flag_o = !flag_o;
+		break;
+	case 'f':
+		move(0,0);
+		clrtoeol();
+		refresh();
+		line = el_gets(el, &line_len);
+		if (line == NULL)
+			err(1, "el_gets");
+		if (line_len > 1)
+			history(hist, &hist_ev, H_ENTER, line);
+		strlcpy(tmp_f_s, line, sizeof(f_s));
+		if ((p = strchr(tmp_f_s, '\n')) != NULL)
+			*p = '\0';
+		/*
+		 * Fix the terminal.  We messed up
+		 * curses idea of the screen by using
+		 * libedit.
+		 */
+		clear();
+		refresh();
+		cbreak();
+		noecho();
+		nonl();
+		if (regcomp(&tmp_f_re, tmp_f_s, REG_EXTENDED)
+		    != 0) {
+			move(0, 0);
+			printw("Invalid filter");
+			refresh();
+			sleep(1);
+		} else {
+			strlcpy(f_s, tmp_f_s, sizeof(f_s));
+			f_re = tmp_f_re;
+		}
+		break;
+	case 'F':
+		f_s[0] = '\0';
+		break;
+	case 'g':
+	case 'G':
+		use_gb();
+		break;
+	case 'k':
+	case 'K':
+		use_kb();
+		break;
+	case 'm':
+	case 'M':
+		use_mb();
+		break;
+	case 'q':
+		quit = 1;
+		break;
+	case 'p':
+		flag_p = !flag_p;
+		break;
+	case 's':
+		flag_s = !flag_s;
+		break;
+	default:
+		break;
+	}
+	return (1);
+}
+
 int
 main(int argc, char **argv)
 {
-	int error, i, quit;
-	int curx, cury, maxx, maxy, line_len, loop, max_flen, head_printed;
+	int error, i;
+	int curx, cury, maxx, maxy;
 	struct devstat *gsp, *gsq;
 	void *sp, *sq;
-	double dt;
 	struct timespec tp, tq;
 	struct gmesh gmp;
 	struct gprovider *pp;
 	struct gconsumer *cp;
 	struct gident *gid;
-	regex_t f_re, tmp_f_re;
-	short cf, cb;
 	char *p;
-	char f_s[100], pf_s[100], tmp_f_s[100];
 	char ts[100], g_name[4096];
-	const char *line;
-	long double ld[16];
-	uint64_t u64;
-	EditLine *el;
-	History *hist;
-	HistEvent hist_ev;
+	long double xfer_per_sec, busy_pct;
+	struct kstat krd, kwr, kfree, kother;
+	uint64_t queue_len;
 
+	memset(&kother, 0, sizeof kother);
 	hist = NULL;
 	el = NULL;
 	maxx = -1;
 	curx = -1;
-	loop = 1;
+	loop = 0;
 	/* Turn on batch mode if output is not tty. */
 	if (!isatty(fileno(stdout)))
 		flag_b = 1;
 
 	f_s[0] = '\0';
-	while ((i = getopt(argc, argv, "abBdcCf:I:ops")) != -1) {
+	while ((i = getopt(argc, argv, "abBcdCf:gI:kmops")) != -1) {
 		switch (i) {
 		case 'a':
 			flag_a = 1;
@@ -128,7 +424,6 @@ main(int argc, char **argv)
 			/* csv out implies repeating batch mode */
 			flag_b = 1;
 			flag_B = 1;
-			head_printed = 0;
 			break;
 		case 'd':
 			flag_d = 1;
@@ -141,6 +436,9 @@ main(int argc, char **argv)
 				    "Invalid filter - see re_format(7)");
 			strlcpy(f_s, optarg, sizeof(f_s));
 			break;
+		case 'g':
+			use_gb();
+			break;
 		case 'I':
 			p = NULL;
 			i = strtoul(optarg, &p, 0);
@@ -155,6 +453,12 @@ main(int argc, char **argv)
 				i *= 1;
 			flag_I = i;
 			break;
+		case 'k':
+			use_kb();
+			break;
+		case 'm':
+			use_mb();
+			break;
 		case 'o':
 			flag_o = 1;
 			break;
@@ -180,42 +484,16 @@ main(int argc, char **argv)
 	error = geom_stats_open();
 	if (error)
 		err(1, "geom_stats_open()");
-	sq = NULL;
 	sq = geom_stats_snapshot_get();
 	if (sq == NULL)
 		err(1, "geom_stats_snapshot()");
-	if (!flag_b) {
-		/* Setup libedit */
-		hist = history_init();
-		if (hist == NULL)
-			err(EX_SOFTWARE, "history_init()");
-		history(hist, &hist_ev, H_SETSIZE, 100);
-		el = el_init("gstat", stdin, stdout, stderr);
-		if (el == NULL)
-			err(EX_SOFTWARE, "el_init");
-		el_set(el, EL_EDITOR, "emacs");
-		el_set(el, EL_SIGNAL, 1);
-		el_set(el, EL_HIST, history, hist);
-		el_set(el, EL_PROMPT, el_prompt);
-		if (f_s[0] != '\0')
-			history(hist, &hist_ev, H_ENTER, f_s);
-		/* Setup curses */
-		initscr();
-		start_color();
-		use_default_colors();
-		pair_content(0, &cf, &cb);
-		init_pair(1, COLOR_GREEN, cb);
-		init_pair(2, COLOR_MAGENTA, cb);
-		init_pair(3, COLOR_RED, cb);
-		cbreak();
-		noecho();
-		nonl();
-		nodelay(stdscr, 1);
-		intrflush(stdscr, FALSE);
-		keypad(stdscr, TRUE);
-	}
+	if (!flag_b)
+		setup_interactive();
+	if (flag_C)
+		print_head_csv();
 	geom_stats_snapshot_timestamp(sq, &tq);
 	for (quit = 0; !quit;) {
+		loop += 1;
 		sp = geom_stats_snapshot_get();
 		if (sp == NULL)
 			err(1, "geom_stats_snapshot()");
@@ -229,79 +507,11 @@ main(int argc, char **argv)
 			(void)snprintf(ts,sizeof(ts),
 					"%s.%.9ld",ts,tq.tv_nsec);
 		}
-	
+		if (loop > 1 && !flag_C)
+			text_header();
+
 		geom_stats_snapshot_reset(sp);
 		geom_stats_snapshot_reset(sq);
-		if (!flag_b)
-			move(0,0);
-		if (!flag_C)
-			PRINTMSG("dT: %5.3fs  w: %.3fs", dt,
-					(float)flag_I / 1000000);
-		if (!flag_C && f_s[0] != '\0') {
-			PRINTMSG("  filter: ");
-			if (!flag_b) {
-				getyx(stdscr, cury, curx);
-				getmaxyx(stdscr, maxy, maxx);
-			}
-			strlcpy(pf_s, f_s, sizeof(pf_s));
-			max_flen = maxx - curx - 1;
-			if ((int)strlen(f_s) > max_flen && max_flen >= 0) {
-				if (max_flen > 3)
-					pf_s[max_flen - 3] = '.';
-				if (max_flen > 2)
-					pf_s[max_flen - 2] = '.';
-				if (max_flen > 1)
-					pf_s[max_flen - 1] = '.';
-				pf_s[max_flen] = '\0';
-			}
-			PRINTMSG("%s", pf_s);
-		}
-		if (!flag_C) {
-			PRINTMSG("\n");
-			PRINTMSG(" L(q)  ops/s   ");
-			if (flag_s) {
-				PRINTMSG(" r/s     kB   kBps   ms/r   ");
-				PRINTMSG(" w/s     kB   kBps   ms/w   ");
-			}
-			else {
-				PRINTMSG(" r/s   kBps   ms/r   ");
-				PRINTMSG(" w/s   kBps   ms/w   ");
-			}
-			if (flag_d) {
-				if (flag_s) {
-					PRINTMSG(" d/s     kB   kBps");
-					PRINTMSG("   ms/d   ");
-				} else
-					PRINTMSG(" d/s   kBps   ms/d   ");
-			}
-			if (flag_o)
-				PRINTMSG(" o/s   ms/o   ");
-			PRINTMSG("%%busy Name\n");
-		} else if (flag_C && !head_printed) {
-			PRINTMSG("timestamp,name,q-depth,total_ops/s");
-			if (flag_s) {
-				PRINTMSG(",read/s,read_sz-KiB");
-				PRINTMSG(",read-KiB/s,ms/read");
-				PRINTMSG(",write/s,write_sz-KiB");
-				PRINTMSG(",write-KiB/s,ms/write");
-			} else {
-				PRINTMSG(",read/s,read-KiB/s,ms/read");
-				PRINTMSG(",write/s,write-KiB/s,ms/write");
-			}
-			if (flag_d) {
-				if (flag_s) {
-					PRINTMSG(",delete/s,delete-sz-KiB");
-					PRINTMSG(",delete-KiB/s,ms/delete");
-				} else {
-					PRINTMSG(",delete/s,delete-KiB/s");
-					PRINTMSG(",ms/delete");
-				}
-			}
-			if (flag_o)
-				PRINTMSG(",other/s,ms/other");
-			PRINTMSG(",%%busy\n");
-			head_printed = 1;
-		}
 		for (;;) {
 			gsp = geom_stats_snapshot_next(sp);
 			gsq = geom_stats_snapshot_next(sq);
@@ -339,50 +549,48 @@ main(int argc, char **argv)
 				  continue;
 			}
 			if (gsp->sequence0 != gsp->sequence1) {
-				/* 
+				/*
 				 * it is ok to skip entire line silently
 				 * for CSV output
 				 */
 				if (!flag_C)
-					PRINTMSG("*\n");
+					printer("*\n");
 				continue;
 			}
-			devstat_compute_statistics(gsp, gsq, dt, 
-			    DSM_QUEUE_LENGTH, &u64,
-			    DSM_TRANSFERS_PER_SECOND, &ld[0],
-
-			    DSM_TRANSFERS_PER_SECOND_READ, &ld[1],
-			    DSM_MB_PER_SECOND_READ, &ld[2],
-			    DSM_MS_PER_TRANSACTION_READ, &ld[3],
 
-			    DSM_TRANSFERS_PER_SECOND_WRITE, &ld[4],
-			    DSM_MB_PER_SECOND_WRITE, &ld[5],
-			    DSM_MS_PER_TRANSACTION_WRITE, &ld[6],
+#define CONVENIENCE(oper, kst) \
+	DSM_TRANSFERS_PER_SECOND_##oper, &kst.transfers_per_second, \
+	DSM_MB_PER_SECOND_##oper, &kst.mb_per_second, \
+	DSM_MS_PER_TRANSACTION_##oper, &kst.ms_per_transaction, \
+	DSM_KB_PER_TRANSFER_##oper, &kst.kb_per_transfer,
 
-			    DSM_BUSY_PCT, &ld[7],
+			devstat_compute_statistics(gsp, gsq, dt,
+			    DSM_QUEUE_LENGTH, &queue_len,
+			    DSM_TRANSFERS_PER_SECOND, &xfer_per_sec,
 
-			    DSM_TRANSFERS_PER_SECOND_FREE, &ld[8],
-			    DSM_MB_PER_SECOND_FREE, &ld[9],
-			    DSM_MS_PER_TRANSACTION_FREE, &ld[10],
+			    CONVENIENCE(READ, krd)
+			    CONVENIENCE(WRITE, kwr)
+			    CONVENIENCE(FREE, kfree)
 
-			    DSM_TRANSFERS_PER_SECOND_OTHER, &ld[11],
-			    DSM_MS_PER_TRANSACTION_OTHER, &ld[12],
+			    DSM_BUSY_PCT, &busy_pct,
 
-			    DSM_KB_PER_TRANSFER_READ, &ld[13],
-			    DSM_KB_PER_TRANSFER_WRITE, &ld[14],
-			    DSM_KB_PER_TRANSFER_FREE, &ld[15],
+			    DSM_TRANSFERS_PER_SECOND_OTHER,
+				&kother.transfers_per_second,
+			    DSM_MS_PER_TRANSACTION_OTHER,
+				&kother.ms_per_transaction,
 
 			    DSM_NONE);
 
-			if (flag_a && ld[7] < 0.1) {
-				*gsq = *gsp;
+			*gsq = *gsp;
+			if (loop == 1)
+				continue;
+
+			if (flag_a && busy_pct < 0.1) {
 				continue;
 			}
 
 			/* store name for geom device */
-			if (gid == NULL) {
-				(void)snprintf(g_name, sizeof(g_name), "??");
-			} else if (gid->lg_what == ISPROVIDER) {
+			if (gid->lg_what == ISPROVIDER) {
 				pp = gid->lg_ptr;
 				(void)snprintf(g_name, sizeof(g_name), "%s",
 						pp->lg_name);
@@ -391,231 +599,81 @@ main(int argc, char **argv)
 				(void)snprintf(g_name, sizeof(g_name),
 					"%s/%s/%s",
 					cp->lg_geom->lg_class->lg_name,
-				   	cp->lg_geom->lg_name,
-				    	cp->lg_provider->lg_name);
+					cp->lg_geom->lg_name,
+					cp->lg_provider->lg_name);
 			}
-	
-			if (flag_C) {
-				PRINTMSG("%s", ts); /* timestamp */
-				PRINTMSG(",%s", g_name); /* print name */
-				PRINTMSG(",%ju", (uintmax_t)u64);
-				PRINTMSG(",%.0f", (double)ld[0]);
-				PRINTMSG(",%.0f", (double)ld[1]);
-				if (flag_s)
-					PRINTMSG(",%.0f", (double)ld[13]);
-				PRINTMSG(",%.0f", (double)ld[2] * 1024);
-				if (ld[3] > 1e3) 
-					PRINTMSG(",%.0f", (double)ld[3]);
-				else if (ld[3] > 1e0)
-					PRINTMSG(",%.1f", (double)ld[3]);
-				else
-					PRINTMSG(",%.3f", (double)ld[3]);
-				PRINTMSG(",%.0f", (double)ld[4]);
-				if (flag_s)
-					PRINTMSG(",%.0f", (double)ld[14]);
-				PRINTMSG(",%.0f", (double)ld[5] * 1024);
-				if (ld[6] > 1e3) 
-					PRINTMSG(",%.0f", (double)ld[6]);
-				else if (ld[6] > 1e0)
-					PRINTMSG(",%.1f", (double)ld[6]);
-				else
-					PRINTMSG(",%.3f", (double)ld[6]);
-
-				if (flag_d) {
-					PRINTMSG(",%.0f", (double)ld[8]);
-					if (flag_s)
-						PRINTMSG(",%.0f",
-								(double)ld[15]);
-					PRINTMSG(",%.0f", (double)ld[9] * 1024);
-					if (ld[10] > 1e3) 
-						PRINTMSG(",%.0f",
-								(double)ld[10]);
-					else if (ld[10] > 1e0)
-						PRINTMSG(",%.1f",
-								(double)ld[10]);
-					else
-						PRINTMSG(",%.3f",
-								(double)ld[10]);
-				}
 
-				if (flag_o) {
-					PRINTMSG(",%.0f", (double)ld[11]);
-					if (ld[12] > 1e3) 
-						PRINTMSG(",%.0f",
-								(double)ld[12]);
-					else if (ld[12] > 1e0)
-						PRINTMSG(",%.1f",
-								(double)ld[12]);
-					else
-						PRINTMSG(",%.3f",
-								(double)ld[12]);
-				}
-				PRINTMSG(",%.1lf", (double)ld[7]);
+			if (flag_C) {
+				printf("%s", ts); /* timestamp */
+				printf(",%s", g_name); /* print name */
+				printf(",%ju", (uintmax_t)queue_len);
+				printf(",%.0f", (double)xfer_per_sec);
+				print_kstat_csv(&krd, 0);
+				print_kstat_csv(&kwr, 0);
+				if (flag_d)
+					print_kstat_csv(&kfree, 0);
+				if (flag_o)
+					print_kstat_csv(&kother, 1);
+				printf(",%.1lf\n", (double)busy_pct);
 			} else {
-				PRINTMSG(" %4ju", (uintmax_t)u64);
-				PRINTMSG(" %6.0f", (double)ld[0]);
-				PRINTMSG(" %6.0f", (double)ld[1]);
-				if (flag_s)
-					PRINTMSG(" %6.0f", (double)ld[13]);
-				PRINTMSG(" %6.0f", (double)ld[2] * 1024);
-				if (ld[3] > 1e3) 
-					PRINTMSG(" %6.0f", (double)ld[3]);
-				else if (ld[3] > 1e0)
-					PRINTMSG(" %6.1f", (double)ld[3]);
-				else
-					PRINTMSG(" %6.3f", (double)ld[3]);
-				PRINTMSG(" %6.0f", (double)ld[4]);
-				if (flag_s)
-					PRINTMSG(" %6.0f", (double)ld[14]);
-				PRINTMSG(" %6.0f", (double)ld[5] * 1024);
-				if (ld[6] > 1e3) 
-					PRINTMSG(" %6.0f", (double)ld[6]);
-				else if (ld[6] > 1e0)
-					PRINTMSG(" %6.1f", (double)ld[6]);
-				else
-					PRINTMSG(" %6.3f", (double)ld[6]);
-
-				if (flag_d) {
-					PRINTMSG(" %6.0f", (double)ld[8]);
-					if (flag_s)
-						PRINTMSG(" %6.0f", 
-								(double)ld[15]);
-					PRINTMSG(" %6.0f", 
-							(double)ld[9] * 1024);
-					if (ld[10] > 1e3) 
-						PRINTMSG(" %6.0f",
-								(double)ld[10]);
-					else if (ld[10] > 1e0)
-						PRINTMSG(" %6.1f",
-								(double)ld[10]);
-					else
-						PRINTMSG(" %6.3f",
-								(double)ld[10]);
-				}
+				printer(" %4ju", (uintmax_t)queue_len);
+				printer(" %6.0f", (double)xfer_per_sec);
 
-				if (flag_o) {
-					PRINTMSG(" %6.0f", (double)ld[11]);
-					if (ld[12] > 1e3) 
-						PRINTMSG(" %6.0f",
-								(double)ld[12]);
-					else if (ld[12] > 1e0)
-						PRINTMSG(" %6.1f",
-								(double)ld[12]);
+				print_kstat_text(&krd, 0);
+				print_kstat_text(&kwr, 0);
+				if (flag_d)
+					print_kstat_text(&kfree, 0);
+				if (flag_o)
+					print_kstat_text(&kother, 1);
+				if (!flag_b) {
+					if (busy_pct > HIGH_PCT_BUSY_THRESH)
+						i = 3;
+					else if (busy_pct > MEDIUM_PCT_BUSY_THRESH)
+						i = 2;
 					else
-						PRINTMSG(" %6.3f",
-								(double)ld[12]);
+						i = 1;
+					attron(COLOR_PAIR(i));
 				}
-
-				if (ld[7] > HIGH_PCT_BUSY_THRESH)
-					i = 3;
-				else if (ld[7] > MEDIUM_PCT_BUSY_THRESH)
-					i = 2;
-				else 
-					i = 1;
+				printer("  %6.1lf", (double)busy_pct);
 				if (!flag_b)
-					attron(COLOR_PAIR(i));
-				PRINTMSG(" %6.1lf", (double)ld[7]);
-				if (!flag_b) {
 					attroff(COLOR_PAIR(i));
-					PRINTMSG("|");
-				} else
-					PRINTMSG(" ");
-				PRINTMSG(" %s", g_name);
+
+				printer(" %s", g_name);
 				if (!flag_b)
 					clrtoeol();
+				printer("\n");
 			}
-			PRINTMSG("\n");
-			*gsq = *gsp;
 		}
 		geom_stats_snapshot_free(sp);
-		if (flag_b) {
-			/* We loop extra to make sure we get the information. */
-			if (!loop)
+		if (flag_b || flag_C) {
+			if (!flag_B && !flag_C && loop == 2)
 				break;
-			if (!flag_B)
-				loop = 0;
-			else
-				if (fflush(stdout) == EOF)
-					goto out;
-			usleep(flag_I);
-			continue;
-		}
-		getyx(stdscr, cury, curx);
-		getmaxyx(stdscr, maxy, maxx);
-		clrtobot();
-		if (maxy - 1 <= cury)
-			move(maxy - 1, 0);
-		refresh();
-		usleep(flag_I);
-		while((i = getch()) != ERR) {
-			switch (i) {
-			case '>':
-				flag_I *= 2;
-				break;
-			case '<':
-				flag_I /= 2;
-				if (flag_I < 1000)
-					flag_I = 1000;
-				break;
-			case 'c':
-				flag_c = !flag_c;
-				break;
-			case 'f':
-				move(0,0);
-				clrtoeol();
-				refresh();
-				line = el_gets(el, &line_len);
-				if (line == NULL)
-					err(1, "el_gets");
-				if (line_len > 1)
-					history(hist, &hist_ev, H_ENTER, line);
-				strlcpy(tmp_f_s, line, sizeof(f_s));
-				if ((p = strchr(tmp_f_s, '\n')) != NULL)
-					*p = '\0';
-				/*
-				 * Fix the terminal.  We messed up
-				 * curses idea of the screen by using
-				 * libedit.
-				 */
-				clear();
-				refresh();
-				cbreak();
-				noecho();
-				nonl();
-				if (regcomp(&tmp_f_re, tmp_f_s, REG_EXTENDED)
-				    != 0) {
-					move(0, 0);
-					printw("Invalid filter");
-					refresh();
-					sleep(1);
-				} else {
-					strlcpy(f_s, tmp_f_s, sizeof(f_s));
-					f_re = tmp_f_re;
-				}
-				break;
-			case 'F':
-				f_s[0] = '\0';
-				break;
-			case 'q':
-				quit = 1;
-				break;
-			default:
+			if (fflush(stdout) == EOF)
 				break;
+			usleep(flag_I);
+		} else {
+			getyx(stdscr, cury, curx);
+			getmaxyx(stdscr, maxy, maxx);
+			clrtobot();
+			if (maxy - 1 <= cury)
+				move(maxy - 1, 0);
+			refresh();
+
+			int di = flag_I;
+			while (di > 0) {
+				int dii = di;
+				if (dii > 100000)
+					dii = 100000;
+				usleep(dii);
+				di -= dii;
+				if (poll_input())
+					break;
 			}
 		}
 	}
-out:
 	if (!flag_b) {
 		el_end(el);
 		endwin();
 	}
 	exit(EX_OK);
 }
-
-static void
*** 6 LINES SKIPPED ***