changeset 14007:f59d28091999

3668 add "%H" to mdb_printf() for human-readable sizes Reviewed by: Robert Mustacchi <rm@joyent.com> Reviewed by: Carlos Cardenas <carlos.cardenas@joyent.com> Reviewed by: Dan McDonald <danmcd@nexenta.com> Approved by: Gordon Ross <gwr@nexenta.com>
author Bryan Cantrill <bryan@joyent.com>
date Fri, 11 Jan 2013 08:53:08 -0800
parents 09f0146de94b
children 0a1a841641da
files usr/src/cmd/mdb/common/mdb/mdb_io.c usr/src/cmd/mdb/common/mdb/mdb_print.c
diffstat 2 files changed, 145 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- a/usr/src/cmd/mdb/common/mdb/mdb_io.c	Mon Apr 08 13:11:12 2013 -0800
+++ b/usr/src/cmd/mdb/common/mdb/mdb_io.c	Fri Jan 11 08:53:08 2013 -0800
@@ -23,7 +23,9 @@
  * Use is subject to license terms.
  */
 
-#pragma ident	"%Z%%M%	%I%	%E% SMI"
+/*
+ * Copyright (c) 2012, Joyent, Inc. All rights reserved.
+ */
 
 /*
  * MDB uses its own enhanced standard i/o mechanism for all input and output.
@@ -686,6 +688,9 @@
 		size = SZ_SHORT;
 		goto fmt_switch;
 
+	case 'H':
+		return ("human-readable size");
+
 	case 'I':
 		return ("IPv4 address");
 
@@ -868,6 +873,137 @@
 	return (name);
 }
 
+/*
+ * Produce human-readable size, similar in spirit (and identical in output)
+ * to libzfs's zfs_nicenum() -- but made significantly more complicated by
+ * the constraint that we cannot use snprintf() as an implementation detail.
+ */
+static const char *
+iob_bytes2str(varglist_t *ap, intsize_t size)
+{
+	const int sigfig = 3;
+	uint64_t n, orig;
+	static char buf[68], *c;
+	int index = 0;
+	char u;
+
+	switch (size) {
+	case SZ_LONGLONG:
+		n = (u_longlong_t)VA_ARG(ap, u_longlong_t);
+		break;
+
+	case SZ_LONG:
+		n = (ulong_t)VA_ARG(ap, ulong_t);
+		break;
+
+	case SZ_SHORT:
+		n = (ushort_t)VA_ARG(ap, uint_t);
+
+	default:
+		n = (uint_t)VA_ARG(ap, uint_t);
+	}
+
+	orig = n;
+
+	while (n >= 1024) {
+		n /= 1024;
+		index++;
+	}
+
+	u = " KMGTPE"[index];
+	buf[0] = '\0';
+
+	if (index == 0) {
+		return (numtostr(n, 10, 0));
+	} else if ((orig & ((1ULL << 10 * index) - 1)) == 0) {
+		/*
+		 * If this is an even multiple of the base, always display
+		 * without any decimal precision.
+		 */
+		(void) strcat(buf, numtostr(n, 10, 0));
+	} else {
+		/*
+		 * We want to choose a precision that results in the specified
+		 * number of significant figures (by default, 3).  This is
+		 * similar to the output that one would get specifying the %.*g
+		 * format specifier (where the asterisk denotes the number of
+		 * significant digits), but (1) we include trailing zeros if
+		 * the there are non-zero digits beyond the number of
+		 * significant digits (that is, 10241 is '10.0K', not the
+		 * '10K' that it would be with %.3g) and (2) we never resort
+		 * to %e notation when the number of digits exceeds the
+		 * number of significant figures (that is, 1043968 is '1020K',
+		 * not '1.02e+03K').  This is also made somewhat complicated
+		 * by the fact that we need to deal with rounding (10239 is
+		 * '10.0K', not '9.99K'), for which we perform nearest-even
+		 * rounding.
+		 */
+		double val = (double)orig / (1ULL << 10 * index);
+		int i, mag = 1, thresh;
+
+		for (i = 0; i < sigfig - 1; i++)
+			mag *= 10;
+
+		for (thresh = mag * 10; mag >= 1; mag /= 10, i--) {
+			double mult = val * (double)mag;
+			uint32_t v;
+
+			/*
+			 * Note that we cast mult to a 32-bit value.  We know
+			 * that val is less than 1024 due to the logic above,
+			 * and that mag is at most 10^(sigfig - 1).  This means
+			 * that as long as sigfig is 9 or lower, this will not
+			 * overflow.  (We perform this cast because it assures
+			 * that we are never converting a double to a uint64_t,
+			 * which for some compilers requires a call to a
+			 * function not guaranteed to be in libstand.)
+			 */
+			if (mult - (double)(uint32_t)mult != 0.5) {
+				v = (uint32_t)(mult + 0.5);
+			} else {
+				/*
+				 * We are exactly between integer multiples
+				 * of units; perform nearest-even rounding
+				 * to be consistent with the behavior of
+				 * printf().
+				 */
+				if ((v = (uint32_t)mult) & 1)
+					v++;
+			}
+
+			if (mag == 1) {
+				(void) strcat(buf, numtostr(v, 10, 0));
+				break;
+			}
+
+			if (v < thresh) {
+				(void) strcat(buf, numtostr(v / mag, 10, 0));
+				(void) strcat(buf, ".");
+
+				c = (char *)numtostr(v % mag, 10, 0);
+				i -= strlen(c);
+
+				/*
+				 * We need to zero-fill from the right of the
+				 * decimal point to the first significant digit
+				 * of the fractional component.
+				 */
+				while (i--)
+					(void) strcat(buf, "0");
+
+				(void) strcat(buf, c);
+				break;
+			}
+		}
+	}
+
+	c = &buf[strlen(buf)];
+	*c++ = u;
+	*c++ = '\0';
+
+	return (buf);
+}
+
 static int
 iob_setattr(mdb_iob_t *iob, const char *s, size_t nbytes)
 {
@@ -1173,6 +1309,10 @@
 			size = SZ_SHORT;
 			goto fmt_switch;
 
+		case 'H':
+			u.str = iob_bytes2str(ap, size);
+			break;
+
 		case 'I':
 			u.ui32 = VA_ARG(ap, uint32_t);
 			u.str = iob_inaddr2str(u.ui32);
--- a/usr/src/cmd/mdb/common/mdb/mdb_print.c	Mon Apr 08 13:11:12 2013 -0800
+++ b/usr/src/cmd/mdb/common/mdb/mdb_print.c	Fri Jan 11 08:53:08 2013 -0800
@@ -2911,6 +2911,7 @@
 			funcs[nfmts] = printf_ipv6;
 			break;
 
+		case 'H':
 		case 'o':
 		case 'r':
 		case 'u':
@@ -3028,8 +3029,9 @@
 "  %a    Prints the member in symbolic form.\n"
 "  %d    Prints the member as a decimal integer.  If the member is a signed\n"
 "        integer type, the output will be signed.\n"
-"  %I    Prints the member a IPv4 address (must be a 32-bit integer type).\n"
-"  %N    Prints the member an IPv6 address (must be of type in6_addr_t).\n"
+"  %H    Prints the member as a human-readable size.\n"
+"  %I    Prints the member as an IPv4 address (must be 32-bit integer type).\n"
+"  %N    Prints the member as an IPv6 address (must be of type in6_addr_t).\n"
 "  %o    Prints the member as an unsigned octal integer.\n"
 "  %p    Prints the member as a pointer, in hexadecimal.\n"
 "  %q    Prints the member in signed octal.  Honk if you ever use this!\n"