diff mbox series

[PACTH,iproute2-next] ip: dynamically size columns when printing stats

Message ID 20210501031059.529906-1-kuba@kernel.org
State New
Headers show
Series [PACTH,iproute2-next] ip: dynamically size columns when printing stats | expand

Commit Message

Jakub Kicinski May 1, 2021, 3:10 a.m. UTC
This change makes ip -s -s output size the columns
automatically. I often find myself using json
output because the normal output is unreadable.
Even on a laptop after 2 days of uptime byte
and packet counters almost overflow their columns,
let alone a busy server.

For max readability switch to right align.

Before:

    RX: bytes  packets  errors  dropped missed  mcast
    8227918473 8617683  0       0       0       0
    RX errors: length   crc     frame   fifo    overrun
               0        0       0       0       0
    TX: bytes  packets  errors  dropped carrier collsns
    691937917  4727223  0       0       0       0
    TX errors: aborted  fifo   window heartbeat transns
               0        0       0       0       10

After:

    RX:  bytes packets errors dropped  missed   mcast
    8228633710 8618408      0       0       0       0
    RX errors:  length    crc   frame    fifo overrun
                     0      0       0       0       0
    TX:  bytes packets errors dropped carrier collsns
     692006303 4727740      0       0       0       0
    TX errors: aborted   fifo  window heartbt transns
                     0      0       0       0      10

More importantly, with large values before:

    RX: bytes  packets  errors  dropped overrun mcast
    126570234447969 15016149200 0       0       0       0
    RX errors: length   crc     frame   fifo    missed
               0        0       0       0       0
    TX: bytes  packets  errors  dropped carrier collsns
    126570234447969 15016149200 0       0       0       0
    TX errors: aborted  fifo   window heartbeat transns
               0        0       0       0       10

Note that in this case we have full shift by a column,
e.g. the value under "dropped" is actually for "errors" etc.

After:

    RX:       bytes     packets errors dropped  missed   mcast
    126570234447969 15016149200      0       0       0       0
    RX errors:           length    crc   frame    fifo overrun
                              0      0       0       0       0
    TX:       bytes     packets errors dropped carrier collsns
    126570234447969 15016149200      0       0       0       0
    TX errors:          aborted   fifo  window heartbt transns
                              0      0       0       0      10

Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
Note - this patch does depend on the trivial nohandler print fix.

 ip/ipaddress.c | 146 +++++++++++++++++++++++++++++++++++--------------
 1 file changed, 106 insertions(+), 40 deletions(-)

Comments

Stephen Hemminger May 3, 2021, 2:57 p.m. UTC | #1
On Fri, 30 Apr 2021 20:10:59 -0700
Jakub Kicinski <kuba@kernel.org> wrote:

> This change makes ip -s -s output size the columns

> automatically. I often find myself using json

> output because the normal output is unreadable.

> Even on a laptop after 2 days of uptime byte

> and packet counters almost overflow their columns,

> let alone a busy server.

> 

> For max readability switch to right align.

> 

> Before:

> 

>     RX: bytes  packets  errors  dropped missed  mcast

>     8227918473 8617683  0       0       0       0

>     RX errors: length   crc     frame   fifo    overrun

>                0        0       0       0       0

>     TX: bytes  packets  errors  dropped carrier collsns

>     691937917  4727223  0       0       0       0

>     TX errors: aborted  fifo   window heartbeat transns

>                0        0       0       0       10

> 

> After:

> 

>     RX:  bytes packets errors dropped  missed   mcast

>     8228633710 8618408      0       0       0       0

>     RX errors:  length    crc   frame    fifo overrun

>                      0      0       0       0       0

>     TX:  bytes packets errors dropped carrier collsns

>      692006303 4727740      0       0       0       0

>     TX errors: aborted   fifo  window heartbt transns

>                      0      0       0       0      10

> 

> More importantly, with large values before:

> 

>     RX: bytes  packets  errors  dropped overrun mcast

>     126570234447969 15016149200 0       0       0       0

>     RX errors: length   crc     frame   fifo    missed

>                0        0       0       0       0

>     TX: bytes  packets  errors  dropped carrier collsns

>     126570234447969 15016149200 0       0       0       0

>     TX errors: aborted  fifo   window heartbeat transns

>                0        0       0       0       10

> 

> Note that in this case we have full shift by a column,

> e.g. the value under "dropped" is actually for "errors" etc.

> 

> After:

> 

>     RX:       bytes     packets errors dropped  missed   mcast

>     126570234447969 15016149200      0       0       0       0

>     RX errors:           length    crc   frame    fifo overrun

>                               0      0       0       0       0

>     TX:       bytes     packets errors dropped carrier collsns

>     126570234447969 15016149200      0       0       0       0

>     TX errors:          aborted   fifo  window heartbt transns

>                               0      0       0       0      10

> 

> Signed-off-by: Jakub Kicinski <kuba@kernel.org>


Looks good to me.

Maybe good time to refactor the code to make it table driven rather
than individual statistic items.
Jakub Kicinski May 3, 2021, 4 p.m. UTC | #2
On Mon, 3 May 2021 07:57:39 -0700 Stephen Hemminger wrote:
> Maybe good time to refactor the code to make it table driven rather

> than individual statistic items.


🤔 should be doable.

Can I do it on top or before making the change to the columns?
David Ahern May 3, 2021, 5:16 p.m. UTC | #3
On 5/3/21 10:00 AM, Jakub Kicinski wrote:
> On Mon, 3 May 2021 07:57:39 -0700 Stephen Hemminger wrote:

>> Maybe good time to refactor the code to make it table driven rather

>> than individual statistic items.

> 

> 🤔 should be doable.

> 

> Can I do it on top or before making the change to the columns?

> 


I think it can be a follow on change. This one is clearly an improvement
for large numbers.
Jakub Kicinski May 3, 2021, 7:22 p.m. UTC | #4
On Mon, 3 May 2021 11:16:41 -0600 David Ahern wrote:
> On 5/3/21 10:00 AM, Jakub Kicinski wrote:

> > On Mon, 3 May 2021 07:57:39 -0700 Stephen Hemminger wrote:  

> >> Maybe good time to refactor the code to make it table driven rather

> >> than individual statistic items.  

> > 

> > 🤔 should be doable.

> > 

> > Can I do it on top or before making the change to the columns?

> >   

> 

> I think it can be a follow on change. This one is clearly an improvement

> for large numbers.


Fun little discrepancy b/w JSON and plain on what's printed with -s 
vs -s -s: JSON output prints rx_over_errors where plain would print
rx_missed_errors. I will change plain to follow JSON, since presumably
we should worry more about breaking machine-readable format.
David Ahern May 4, 2021, 2:15 a.m. UTC | #5
On 5/3/21 1:22 PM, Jakub Kicinski wrote:
> On Mon, 3 May 2021 11:16:41 -0600 David Ahern wrote:

>> On 5/3/21 10:00 AM, Jakub Kicinski wrote:

>>> On Mon, 3 May 2021 07:57:39 -0700 Stephen Hemminger wrote:  

>>>> Maybe good time to refactor the code to make it table driven rather

>>>> than individual statistic items.  

>>>

>>> 🤔 should be doable.

>>>

>>> Can I do it on top or before making the change to the columns?

>>>   

>>

>> I think it can be a follow on change. This one is clearly an improvement

>> for large numbers.

> 

> Fun little discrepancy b/w JSON and plain on what's printed with -s 

> vs -s -s: JSON output prints rx_over_errors where plain would print

> rx_missed_errors. I will change plain to follow JSON, since presumably

> we should worry more about breaking machine-readable format.

> 


you mean from this commit?

commit b8663da04939dd5de223ca0de23e466ce0a372f7
Author: Jakub Kicinski <kuba@kernel.org>
Date:   Wed Sep 16 12:42:49 2020 -0700

    ip: promote missed packets to the -s row
Jakub Kicinski May 4, 2021, 4:46 p.m. UTC | #6
On Mon, 3 May 2021 20:15:27 -0600 David Ahern wrote:
> On 5/3/21 1:22 PM, Jakub Kicinski wrote:

> > On Mon, 3 May 2021 11:16:41 -0600 David Ahern wrote:  

> >> I think it can be a follow on change. This one is clearly an improvement

> >> for large numbers.  

> > 

> > Fun little discrepancy b/w JSON and plain on what's printed with -s 

> > vs -s -s: JSON output prints rx_over_errors where plain would print

> > rx_missed_errors. I will change plain to follow JSON, since presumably

> > we should worry more about breaking machine-readable format.

> 

> you mean from this commit?

> 

> commit b8663da04939dd5de223ca0de23e466ce0a372f7

> Author: Jakub Kicinski <kuba@kernel.org>

> Date:   Wed Sep 16 12:42:49 2020 -0700

> 

>     ip: promote missed packets to the -s row


Yes, with table driven printing we will lose that again.

With the implementation I have we don't save that much LoC:

 ip/ipaddress.c | 339 ++++++++++++++++++++++++++++----------------------------
 1 file changed, 170 insertions(+), 169 deletions(-)

----->8------
diff --git a/ip/ipaddress.c b/ip/ipaddress.c
index 06ca7273c531..6b3b03002a52 100644
--- a/ip/ipaddress.c
+++ b/ip/ipaddress.c
@@ -10,7 +10,6 @@
  *
  */
 
-#include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
@@ -545,27 +544,18 @@ static void print_vfinfo(FILE *fp, struct ifinfomsg *ifi, struct rtattr *vfinfo)
 		print_vf_stats64(fp, vf[IFLA_VF_STATS]);
 }
 
-static void size_columns(unsigned int cols[], unsigned int n, ...)
+static void size_column(unsigned int *col, uint64_t val)
 {
-	unsigned int i, len;
-	uint64_t val, powi;
-	va_list args;
-
-	va_start(args, n);
-
-	for (i = 0; i < n; i++) {
-		val = va_arg(args, unsigned long long);
-
-		if (human_readable)
-			continue;
+	unsigned int len;
+	uint64_t powi;
 
-		for (len = 1, powi = 10; powi < val; len++, powi *= 10)
-			/* nothing */;
-		if (len > cols[i])
-			cols[i] = len;
-	}
+	if (human_readable)
+		return;
 
-	va_end(args);
+	for (len = 1, powi = 10; powi < val; len++, powi *= 10)
+		/* nothing */;
+	if (len > *col)
+		*col = len;
 }
 
 void print_num(FILE *fp, unsigned int width, uint64_t count)
@@ -679,192 +669,203 @@ static void print_vf_stats64(FILE *fp, struct rtattr *vfstats)
 	}
 }
 
+struct stats_desc {
+	const char *json_key;
+	const char *hdr;
+	unsigned int offset;
+	bool hide;
+	bool empty;
+	bool direct;
+};
+
+#define STATS_DESC(_json_key, _hdr, _memb)				\
+	{								\
+		.json_key = (_json_key),				\
+		.hdr = (_hdr),						\
+		.offset = offsetof(struct rtnl_link_stats64, _memb),	\
+	}
+#define STATS_EMPTY		{ .hdr = "", .empty = true, }
+
+#define N_STAT_COLUMNS	7
+
+static const struct stats_desc _stats_rx[N_STAT_COLUMNS] = {
+	STATS_DESC("bytes", "bytes", rx_bytes),
+	STATS_DESC("packets", "packets", rx_packets),
+	STATS_DESC("errors", "errors", rx_errors),
+	STATS_DESC("dropped", "dropped", rx_dropped),
+	STATS_DESC("over_errors", "overrun", rx_over_errors),
+	STATS_DESC("multicast", "mcast", multicast),
+	STATS_DESC("compressed", "compressed", rx_compressed),
+};
+
+static const struct stats_desc _stats_rx_err[N_STAT_COLUMNS] = {
+	STATS_EMPTY,
+	STATS_DESC("length_errors", "length", rx_length_errors),
+	STATS_DESC("crc_errors", "crc", rx_crc_errors),
+	STATS_DESC("frame_errors", "frame", rx_frame_errors),
+	STATS_DESC("fifo_errors", "fifo", rx_fifo_errors),
+	STATS_DESC("missed_errors", "missed", rx_missed_errors),
+	STATS_DESC("nohandler", "nohandler", rx_nohandler),
+};
+
+static const struct stats_desc _stats_tx[N_STAT_COLUMNS] = {
+	STATS_DESC("bytes", "bytes", tx_bytes),
+	STATS_DESC("packets", "packets", tx_packets),
+	STATS_DESC("errors", "errors", tx_errors),
+	STATS_DESC("dropped", "dropped", tx_dropped),
+	STATS_DESC("carrier_errors", "carrier", tx_carrier_errors),
+	STATS_DESC("collisions", "collsns", collisions),
+	STATS_DESC("compressed", "compressed", tx_compressed),
+};
+
+static const struct stats_desc _stats_tx_err[N_STAT_COLUMNS] = {
+	STATS_EMPTY,
+	STATS_DESC("aborted_errors", "aborted", tx_aborted_errors),
+	STATS_DESC("fifo_errors", "fifo", tx_fifo_errors),
+	STATS_DESC("window_errors", "window", tx_window_errors),
+	STATS_DESC("heartbeat_errors", "heartbt", tx_heartbeat_errors),
+	{ .json_key = "carrier_changes", .hdr = "transns", .direct = true, },
+	{ .hide = true, },
+};
+
+static void stat_table_print_json(struct rtnl_link_stats64 *s,
+				  const struct stats_desc desc[])
+{
+	unsigned int i;
+
+	for (i = 0; i < N_STAT_COLUMNS; i++) {
+		if (desc[i].hide || desc[i].empty)
+			continue;
+		print_u64(PRINT_JSON, desc[i].json_key, NULL,
+			  desc[i].direct ? desc[i].offset :
+			  ((__u64 *)s)[desc[i].offset / sizeof(__u64)]);
+	}
+}
+
+static void stat_table_size_columns(const char *pfx,
+				    struct rtnl_link_stats64 *s,
+				    unsigned int cols[],
+				    const struct stats_desc desc[])
+{
+	unsigned int i, len;
+
+	for (i = 0; i < N_STAT_COLUMNS; i++) {
+		if (desc[i].hide)
+			continue;
+		len = desc[i].empty ? 0 : strlen(desc[i].hdr);
+		if (i == 0)
+			len += strlen(pfx) + !desc[i].empty;
+		if (len > cols[i])
+			cols[i] = len;
+		if (desc[i].empty)
+			continue;
+		size_column(&cols[i], desc[i].direct ? desc[i].offset :
+			    ((__u64 *)s)[desc[i].offset / sizeof(__u64)]);
+	}
+}
+
+static void stat_table_print_fp(FILE *fp, const char *pfx,
+				struct rtnl_link_stats64 *s,
+				unsigned int cols[],
+				const struct stats_desc desc[])
+{
+	unsigned int i, col;
+
+	fprintf(fp, "    %s", pfx);
+	for (i = 0; i < N_STAT_COLUMNS; i++) {
+		if (desc[i].hide)
+			continue;
+		col = cols[i];
+		if (i == 0)
+			col -= strlen(pfx) + 1;
+		fprintf(fp, "%*s", col + 1, desc[i].hdr);
+	}
+	fprintf(fp, "%s", _SL_);
+
+	fprintf(fp, "    ");
+	for (i = 0; i < N_STAT_COLUMNS; i++) {
+		if (desc[i].hide)
+			continue;
+		if (desc[i].empty) {
+			printf("%*s", cols[i] + 1, "");
+			continue;
+		}
+
+		print_num(fp, cols[i], desc[i].direct ? desc[i].offset :
+			  ((__u64 *)s)[desc[i].offset / sizeof(__u64)]);
+	}
+}
+
 static void __print_link_stats(FILE *fp, struct rtattr *tb[])
 {
+	struct stats_desc stats_rx[N_STAT_COLUMNS], stats_tx[N_STAT_COLUMNS];
 	const struct rtattr *carrier_changes = tb[IFLA_CARRIER_CHANGES];
+	struct stats_desc stats_rx_err[N_STAT_COLUMNS];
+	struct stats_desc stats_tx_err[N_STAT_COLUMNS];
 	struct rtnl_link_stats64 _s, *s = &_s;
-	unsigned int cols[] = {
-		strlen("*X errors:"),
-		strlen("packets"),
-		strlen("errors"),
-		strlen("dropped"),
-		strlen("heartbt"),
-		strlen("overrun"),
-		strlen("compressed"),
-	};
+	unsigned int cols[N_STAT_COLUMNS] = { };
 	int ret;
 
 	ret = get_rtnl_link_stats_rta(s, tb);
 	if (ret < 0)
 		return;
 
+	memcpy(stats_rx, _stats_rx, sizeof(stats_rx));
+	stats_rx[6].hide = !s->rx_compressed;
+	memcpy(stats_rx_err, _stats_rx_err, sizeof(stats_rx_err));
+	stats_rx_err[6].hide = !s->rx_nohandler;
+	memcpy(stats_tx, _stats_tx, sizeof(stats_tx));
+	stats_tx[6].hide = !s->tx_compressed;
+	memcpy(stats_tx_err, _stats_tx_err, sizeof(stats_tx_err));
+	stats_tx_err[5].hide = !carrier_changes;
+	if (carrier_changes)
+		stats_tx_err[5].offset = rta_getattr_u32(carrier_changes);
+
 	if (is_json_context()) {
 		open_json_object((ret == sizeof(*s)) ? "stats64" : "stats");
 
-		/* RX stats */
 		open_json_object("rx");
-		print_u64(PRINT_JSON, "bytes", NULL, s->rx_bytes);
-		print_u64(PRINT_JSON, "packets", NULL, s->rx_packets);
-		print_u64(PRINT_JSON, "errors", NULL, s->rx_errors);
-		print_u64(PRINT_JSON, "dropped", NULL, s->rx_dropped);
-		print_u64(PRINT_JSON, "over_errors", NULL, s->rx_over_errors);
-		print_u64(PRINT_JSON, "multicast", NULL, s->multicast);
-		if (s->rx_compressed)
-			print_u64(PRINT_JSON,
-				   "compressed", NULL, s->rx_compressed);
-
-		/* RX error stats */
-		if (show_stats > 1) {
-			print_u64(PRINT_JSON,
-				   "length_errors",
-				   NULL, s->rx_length_errors);
-			print_u64(PRINT_JSON,
-				   "crc_errors",
-				   NULL, s->rx_crc_errors);
-			print_u64(PRINT_JSON,
-				   "frame_errors",
-				   NULL, s->rx_frame_errors);
-			print_u64(PRINT_JSON,
-				   "fifo_errors",
-				   NULL, s->rx_fifo_errors);
-			print_u64(PRINT_JSON,
-				   "missed_errors",
-				   NULL, s->rx_missed_errors);
-			if (s->rx_nohandler)
-				print_u64(PRINT_JSON,
-					   "nohandler", NULL, s->rx_nohandler);
-		}
+		stat_table_print_json(s, stats_rx);
+		if (show_stats > 1)
+			stat_table_print_json(s, stats_rx_err);
 		close_json_object();
 
-		/* TX stats */
 		open_json_object("tx");
-		print_u64(PRINT_JSON, "bytes", NULL, s->tx_bytes);
-		print_u64(PRINT_JSON, "packets", NULL, s->tx_packets);
-		print_u64(PRINT_JSON, "errors", NULL, s->tx_errors);
-		print_u64(PRINT_JSON, "dropped", NULL, s->tx_dropped);
-		print_u64(PRINT_JSON,
-			   "carrier_errors",
-			   NULL, s->tx_carrier_errors);
-		print_u64(PRINT_JSON, "collisions", NULL, s->collisions);
-		if (s->tx_compressed)
-			print_u64(PRINT_JSON,
-				   "compressed", NULL, s->tx_compressed);
-
-		/* TX error stats */
-		if (show_stats > 1) {
-			print_u64(PRINT_JSON,
-				   "aborted_errors",
-				   NULL, s->tx_aborted_errors);
-			print_u64(PRINT_JSON,
-				   "fifo_errors",
-				   NULL, s->tx_fifo_errors);
-			print_u64(PRINT_JSON,
-				   "window_errors",
-				   NULL, s->tx_window_errors);
-			print_u64(PRINT_JSON,
-				   "heartbeat_errors",
-				   NULL, s->tx_heartbeat_errors);
-			if (carrier_changes)
-				print_u64(PRINT_JSON, "carrier_changes", NULL,
-					   rta_getattr_u32(carrier_changes));
-		}
-
+		stat_table_print_json(s, stats_tx);
+		if (show_stats > 1)
+			stat_table_print_json(s, stats_tx_err);
 		close_json_object();
+
 		close_json_object();
 	} else {
-		size_columns(cols, ARRAY_SIZE(cols),
-			     s->rx_bytes, s->rx_packets, s->rx_errors,
-			     s->rx_dropped, s->rx_missed_errors,
-			     s->multicast, s->rx_compressed);
+		stat_table_size_columns("RX:", s, cols, stats_rx);
 		if (show_stats > 1)
-			size_columns(cols, ARRAY_SIZE(cols), 0,
-				     s->rx_length_errors, s->rx_crc_errors,
-				     s->rx_frame_errors, s->rx_fifo_errors,
-				     s->rx_over_errors, s->rx_nohandler);
-		size_columns(cols, ARRAY_SIZE(cols),
-			     s->tx_bytes, s->tx_packets, s->tx_errors,
-			     s->tx_dropped, s->tx_carrier_errors,
-			     s->collisions, s->tx_compressed);
+			stat_table_size_columns("RX errors:", s, cols,
+						stats_rx_err);
+		stat_table_size_columns("TX:", s, cols, stats_tx);
 		if (show_stats > 1)
-			size_columns(cols, ARRAY_SIZE(cols), 0, 0,
-				     s->tx_aborted_errors, s->tx_fifo_errors,
-				     s->tx_window_errors,
-				     s->tx_heartbeat_errors,
-				     carrier_changes ?
-				     rta_getattr_u32(carrier_changes) : 0);
+			stat_table_size_columns("TX errors:", s, cols,
+						stats_tx_err);
 
 		/* RX stats */
-		fprintf(fp, "    RX: %*s %*s %*s %*s %*s %*s %*s%s",
-			cols[0] - 4, "bytes", cols[1], "packets",
-			cols[2], "errors", cols[3], "dropped",
-			cols[4], "missed", cols[5], "mcast",
-			cols[6], s->rx_compressed ? "compressed" : "", _SL_);
-
-		fprintf(fp, "    ");
-		print_num(fp, cols[0], s->rx_bytes);
-		print_num(fp, cols[1], s->rx_packets);
-		print_num(fp, cols[2], s->rx_errors);
-		print_num(fp, cols[3], s->rx_dropped);
-		print_num(fp, cols[4], s->rx_missed_errors);
-		print_num(fp, cols[5], s->multicast);
-		if (s->rx_compressed)
-			print_num(fp, cols[6], s->rx_compressed);
+		stat_table_print_fp(fp, "RX:", s, cols, stats_rx);
 
 		/* RX error stats */
 		if (show_stats > 1) {
 			fprintf(fp, "%s", _SL_);
-			fprintf(fp, "    RX errors:%*s %*s %*s %*s %*s %*s %*s%s",
-				cols[0] - 10, "", cols[1], "length",
-				cols[2], "crc", cols[3], "frame",
-				cols[4], "fifo", cols[5], "overrun",
-				cols[6], s->rx_nohandler ? "nohandler" : "",
-				_SL_);
-			fprintf(fp, "%*s", cols[0] + 5, "");
-			print_num(fp, cols[1], s->rx_length_errors);
-			print_num(fp, cols[2], s->rx_crc_errors);
-			print_num(fp, cols[3], s->rx_frame_errors);
-			print_num(fp, cols[4], s->rx_fifo_errors);
-			print_num(fp, cols[5], s->rx_over_errors);
-			if (s->rx_nohandler)
-				print_num(fp, cols[6], s->rx_nohandler);
+			stat_table_print_fp(fp, "RX errors:", s, cols,
+					    stats_rx_err);
 		}
 		fprintf(fp, "%s", _SL_);
 
 		/* TX stats */
-		fprintf(fp, "    TX: %*s %*s %*s %*s %*s %*s %*s%s",
-			cols[0] - 4, "bytes", cols[1], "packets",
-			cols[2], "errors", cols[3], "dropped",
-			cols[4], "carrier", cols[5], "collsns",
-			cols[6], s->tx_compressed ? "compressed" : "", _SL_);
-
-		fprintf(fp, "    ");
-		print_num(fp, cols[0], s->tx_bytes);
-		print_num(fp, cols[1], s->tx_packets);
-		print_num(fp, cols[2], s->tx_errors);
-		print_num(fp, cols[3], s->tx_dropped);
-		print_num(fp, cols[4], s->tx_carrier_errors);
-		print_num(fp, cols[5], s->collisions);
-		if (s->tx_compressed)
-			print_num(fp, cols[6], s->tx_compressed);
+		stat_table_print_fp(fp, "TX:", s, cols, stats_tx);
 
 		/* TX error stats */
 		if (show_stats > 1) {
 			fprintf(fp, "%s", _SL_);
-			fprintf(fp, "    TX errors:%*s %*s %*s %*s %*s %*s%s",
-				cols[0] - 10, "", cols[1], "aborted",
-				cols[2], "fifo", cols[3], "window",
-				cols[4], "heartbt",
-				cols[5], carrier_changes ? "transns" : "",
-				_SL_);
-
-			fprintf(fp, "%*s", cols[0] + 5, "");
-			print_num(fp, cols[1], s->tx_aborted_errors);
-			print_num(fp, cols[2], s->tx_fifo_errors);
-			print_num(fp, cols[3], s->tx_window_errors);
-			print_num(fp, cols[4], s->tx_heartbeat_errors);
-			if (carrier_changes)
-				print_num(fp, cols[5],
-					  rta_getattr_u32(carrier_changes));
+			stat_table_print_fp(fp, "TX errors:", s, cols,
+					    stats_tx_err);
 		}
 	}
 }
-- 
2.31.1
David Ahern May 9, 2021, 10:53 p.m. UTC | #7
On 4/30/21 9:10 PM, Jakub Kicinski wrote:
> This change makes ip -s -s output size the columns

> automatically. I often find myself using json

> output because the normal output is unreadable.

> Even on a laptop after 2 days of uptime byte

> and packet counters almost overflow their columns,

> let alone a busy server.

> 

> For max readability switch to right align.

> 

> Before:

> 

>     RX: bytes  packets  errors  dropped missed  mcast

>     8227918473 8617683  0       0       0       0

>     RX errors: length   crc     frame   fifo    overrun

>                0        0       0       0       0

>     TX: bytes  packets  errors  dropped carrier collsns

>     691937917  4727223  0       0       0       0

>     TX errors: aborted  fifo   window heartbeat transns

>                0        0       0       0       10

> 

> After:

> 

>     RX:  bytes packets errors dropped  missed   mcast

>     8228633710 8618408      0       0       0       0

>     RX errors:  length    crc   frame    fifo overrun

>                      0      0       0       0       0

>     TX:  bytes packets errors dropped carrier collsns

>      692006303 4727740      0       0       0       0

>     TX errors: aborted   fifo  window heartbt transns

>                      0      0       0       0      10

> 

> More importantly, with large values before:

> 

>     RX: bytes  packets  errors  dropped overrun mcast

>     126570234447969 15016149200 0       0       0       0

>     RX errors: length   crc     frame   fifo    missed

>                0        0       0       0       0

>     TX: bytes  packets  errors  dropped carrier collsns

>     126570234447969 15016149200 0       0       0       0

>     TX errors: aborted  fifo   window heartbeat transns

>                0        0       0       0       10

> 

> Note that in this case we have full shift by a column,

> e.g. the value under "dropped" is actually for "errors" etc.

> 

> After:

> 

>     RX:       bytes     packets errors dropped  missed   mcast

>     126570234447969 15016149200      0       0       0       0

>     RX errors:           length    crc   frame    fifo overrun

>                               0      0       0       0       0

>     TX:       bytes     packets errors dropped carrier collsns

>     126570234447969 15016149200      0       0       0       0

>     TX errors:          aborted   fifo  window heartbt transns

>                               0      0       0       0      10

> 

> Signed-off-by: Jakub Kicinski <kuba@kernel.org>

> ---

> Note - this patch does depend on the trivial nohandler print fix.

> 

>  ip/ipaddress.c | 146 +++++++++++++++++++++++++++++++++++--------------

>  1 file changed, 106 insertions(+), 40 deletions(-)

> 


good improvement. applied. Thanks,
diff mbox series

Patch

diff --git a/ip/ipaddress.c b/ip/ipaddress.c
index 8783b70d81e2..06ca7273c531 100644
--- a/ip/ipaddress.c
+++ b/ip/ipaddress.c
@@ -10,6 +10,7 @@ 
  *
  */
 
+#include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
@@ -544,6 +545,29 @@  static void print_vfinfo(FILE *fp, struct ifinfomsg *ifi, struct rtattr *vfinfo)
 		print_vf_stats64(fp, vf[IFLA_VF_STATS]);
 }
 
+static void size_columns(unsigned int cols[], unsigned int n, ...)
+{
+	unsigned int i, len;
+	uint64_t val, powi;
+	va_list args;
+
+	va_start(args, n);
+
+	for (i = 0; i < n; i++) {
+		val = va_arg(args, unsigned long long);
+
+		if (human_readable)
+			continue;
+
+		for (len = 1, powi = 10; powi < val; len++, powi *= 10)
+			/* nothing */;
+		if (len > cols[i])
+			cols[i] = len;
+	}
+
+	va_end(args);
+}
+
 void print_num(FILE *fp, unsigned int width, uint64_t count)
 {
 	const char *prefix = "kMGTPE";
@@ -554,7 +578,7 @@  void print_num(FILE *fp, unsigned int width, uint64_t count)
 	char buf[64];
 
 	if (!human_readable || count < base) {
-		fprintf(fp, "%-*"PRIu64" ", width, count);
+		fprintf(fp, "%*"PRIu64" ", width, count);
 		return;
 	}
 
@@ -581,7 +605,7 @@  void print_num(FILE *fp, unsigned int width, uint64_t count)
 	snprintf(buf, sizeof(buf), "%.*f%c%s", precision,
 		 (double) count / powi, *prefix, use_iec ? "i" : "");
 
-	fprintf(fp, "%-*s ", width, buf);
+	fprintf(fp, "%*s ", width, buf);
 }
 
 static void print_vf_stats64(FILE *fp, struct rtattr *vfstats)
@@ -659,6 +683,15 @@  static void __print_link_stats(FILE *fp, struct rtattr *tb[])
 {
 	const struct rtattr *carrier_changes = tb[IFLA_CARRIER_CHANGES];
 	struct rtnl_link_stats64 _s, *s = &_s;
+	unsigned int cols[] = {
+		strlen("*X errors:"),
+		strlen("packets"),
+		strlen("errors"),
+		strlen("dropped"),
+		strlen("heartbt"),
+		strlen("overrun"),
+		strlen("compressed"),
+	};
 	int ret;
 
 	ret = get_rtnl_link_stats_rta(s, tb);
@@ -739,65 +772,98 @@  static void __print_link_stats(FILE *fp, struct rtattr *tb[])
 		close_json_object();
 		close_json_object();
 	} else {
+		size_columns(cols, ARRAY_SIZE(cols),
+			     s->rx_bytes, s->rx_packets, s->rx_errors,
+			     s->rx_dropped, s->rx_missed_errors,
+			     s->multicast, s->rx_compressed);
+		if (show_stats > 1)
+			size_columns(cols, ARRAY_SIZE(cols), 0,
+				     s->rx_length_errors, s->rx_crc_errors,
+				     s->rx_frame_errors, s->rx_fifo_errors,
+				     s->rx_over_errors, s->rx_nohandler);
+		size_columns(cols, ARRAY_SIZE(cols),
+			     s->tx_bytes, s->tx_packets, s->tx_errors,
+			     s->tx_dropped, s->tx_carrier_errors,
+			     s->collisions, s->tx_compressed);
+		if (show_stats > 1)
+			size_columns(cols, ARRAY_SIZE(cols), 0, 0,
+				     s->tx_aborted_errors, s->tx_fifo_errors,
+				     s->tx_window_errors,
+				     s->tx_heartbeat_errors,
+				     carrier_changes ?
+				     rta_getattr_u32(carrier_changes) : 0);
+
 		/* RX stats */
-		fprintf(fp, "    RX: bytes  packets  errors  dropped missed  mcast   %s%s",
-			s->rx_compressed ? "compressed" : "", _SL_);
+		fprintf(fp, "    RX: %*s %*s %*s %*s %*s %*s %*s%s",
+			cols[0] - 4, "bytes", cols[1], "packets",
+			cols[2], "errors", cols[3], "dropped",
+			cols[4], "missed", cols[5], "mcast",
+			cols[6], s->rx_compressed ? "compressed" : "", _SL_);
 
 		fprintf(fp, "    ");
-		print_num(fp, 10, s->rx_bytes);
-		print_num(fp, 8, s->rx_packets);
-		print_num(fp, 7, s->rx_errors);
-		print_num(fp, 7, s->rx_dropped);
-		print_num(fp, 7, s->rx_missed_errors);
-		print_num(fp, 7, s->multicast);
+		print_num(fp, cols[0], s->rx_bytes);
+		print_num(fp, cols[1], s->rx_packets);
+		print_num(fp, cols[2], s->rx_errors);
+		print_num(fp, cols[3], s->rx_dropped);
+		print_num(fp, cols[4], s->rx_missed_errors);
+		print_num(fp, cols[5], s->multicast);
 		if (s->rx_compressed)
-			print_num(fp, 7, s->rx_compressed);
+			print_num(fp, cols[6], s->rx_compressed);
 
 		/* RX error stats */
 		if (show_stats > 1) {
 			fprintf(fp, "%s", _SL_);
-			fprintf(fp, "    RX errors: length   crc     frame   fifo    overrun%s%s",
-				s->rx_nohandler ? " nohandler" : "", _SL_);
-			fprintf(fp, "               ");
-			print_num(fp, 8, s->rx_length_errors);
-			print_num(fp, 7, s->rx_crc_errors);
-			print_num(fp, 7, s->rx_frame_errors);
-			print_num(fp, 7, s->rx_fifo_errors);
-			print_num(fp, 7, s->rx_over_errors);
+			fprintf(fp, "    RX errors:%*s %*s %*s %*s %*s %*s %*s%s",
+				cols[0] - 10, "", cols[1], "length",
+				cols[2], "crc", cols[3], "frame",
+				cols[4], "fifo", cols[5], "overrun",
+				cols[6], s->rx_nohandler ? "nohandler" : "",
+				_SL_);
+			fprintf(fp, "%*s", cols[0] + 5, "");
+			print_num(fp, cols[1], s->rx_length_errors);
+			print_num(fp, cols[2], s->rx_crc_errors);
+			print_num(fp, cols[3], s->rx_frame_errors);
+			print_num(fp, cols[4], s->rx_fifo_errors);
+			print_num(fp, cols[5], s->rx_over_errors);
 			if (s->rx_nohandler)
-				print_num(fp, 7, s->rx_nohandler);
+				print_num(fp, cols[6], s->rx_nohandler);
 		}
 		fprintf(fp, "%s", _SL_);
 
 		/* TX stats */
-		fprintf(fp, "    TX: bytes  packets  errors  dropped carrier collsns %s%s",
-			s->tx_compressed ? "compressed" : "", _SL_);
+		fprintf(fp, "    TX: %*s %*s %*s %*s %*s %*s %*s%s",
+			cols[0] - 4, "bytes", cols[1], "packets",
+			cols[2], "errors", cols[3], "dropped",
+			cols[4], "carrier", cols[5], "collsns",
+			cols[6], s->tx_compressed ? "compressed" : "", _SL_);
 
 		fprintf(fp, "    ");
-		print_num(fp, 10, s->tx_bytes);
-		print_num(fp, 8, s->tx_packets);
-		print_num(fp, 7, s->tx_errors);
-		print_num(fp, 7, s->tx_dropped);
-		print_num(fp, 7, s->tx_carrier_errors);
-		print_num(fp, 7, s->collisions);
+		print_num(fp, cols[0], s->tx_bytes);
+		print_num(fp, cols[1], s->tx_packets);
+		print_num(fp, cols[2], s->tx_errors);
+		print_num(fp, cols[3], s->tx_dropped);
+		print_num(fp, cols[4], s->tx_carrier_errors);
+		print_num(fp, cols[5], s->collisions);
 		if (s->tx_compressed)
-			print_num(fp, 7, s->tx_compressed);
+			print_num(fp, cols[6], s->tx_compressed);
 
 		/* TX error stats */
 		if (show_stats > 1) {
 			fprintf(fp, "%s", _SL_);
-			fprintf(fp, "    TX errors: aborted  fifo   window heartbeat");
-			if (carrier_changes)
-				fprintf(fp, " transns");
-			fprintf(fp, "%s", _SL_);
-
-			fprintf(fp, "               ");
-			print_num(fp, 8, s->tx_aborted_errors);
-			print_num(fp, 7, s->tx_fifo_errors);
-			print_num(fp, 7, s->tx_window_errors);
-			print_num(fp, 7, s->tx_heartbeat_errors);
+			fprintf(fp, "    TX errors:%*s %*s %*s %*s %*s %*s%s",
+				cols[0] - 10, "", cols[1], "aborted",
+				cols[2], "fifo", cols[3], "window",
+				cols[4], "heartbt",
+				cols[5], carrier_changes ? "transns" : "",
+				_SL_);
+
+			fprintf(fp, "%*s", cols[0] + 5, "");
+			print_num(fp, cols[1], s->tx_aborted_errors);
+			print_num(fp, cols[2], s->tx_fifo_errors);
+			print_num(fp, cols[3], s->tx_window_errors);
+			print_num(fp, cols[4], s->tx_heartbeat_errors);
 			if (carrier_changes)
-				print_num(fp, 7,
+				print_num(fp, cols[5],
 					  rta_getattr_u32(carrier_changes));
 		}
 	}