netdev.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Stefano Brivio <sbrivio@redhat.com>
To: Stephen Hemminger <stephen@networkplumber.org>
Cc: netdev@vger.kernel.org, Sabrina Dubroca <sd@queasysnail.net>
Subject: [PATCH iproute2 net-next v2 2/4] ss: Introduce columns lightweight abstraction
Date: Tue, 12 Dec 2017 01:46:31 +0100	[thread overview]
Message-ID: <0efa628603c0ca739c9b44a85819d93636f90c3b.1513039237.git.sbrivio@redhat.com> (raw)
In-Reply-To: <cover.1513039237.git.sbrivio@redhat.com>
In-Reply-To: <cover.1513039237.git.sbrivio@redhat.com>

Instead of embedding spacing directly while printing contents,
logically declare columns and functions to buffer their content,
to print left and right spacing around fields, to flush them to
screen, and to print headers.

This makes it a bit easier to handle layout changes and prepares
for full output buffering, needed for optimal spacing in field
output layout.

Columns are currently set up to retain exactly the same output
as before. This needs some slight adjustments of the values
previously calculated in main(), as the width value introduced
here already includes the width of left delimiters and spacing
is not explicitly printed anymore whenever a field is printed.
These calculations will go away altogether once automatic width
calculation is implemented.

We can also remove explicit printing of newlines after the final
content for a given line is printed, flushing the last field on
a line will cause field_flush() to print newlines where
appropriate.

No changes in output expected here.

Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
Reviewed-by: Sabrina Dubroca <sd@queasysnail.net>
---
v2: rebase after conflict with 00ac78d39c29 ("ss: print tcpi_rcv_ssthresh")

 misc/ss.c | 291 ++++++++++++++++++++++++++++++++++++++++++--------------------
 1 file changed, 198 insertions(+), 93 deletions(-)

diff --git a/misc/ss.c b/misc/ss.c
index a7d3b89e1478..42310ba4120d 100644
--- a/misc/ss.c
+++ b/misc/ss.c
@@ -103,11 +103,48 @@ int show_header = 1;
 int follow_events;
 int sctp_ino;
 
-int netid_width;
-int state_width;
-int addr_width;
-int serv_width;
-char *odd_width_pad = "";
+enum col_id {
+	COL_NETID,
+	COL_STATE,
+	COL_RECVQ,
+	COL_SENDQ,
+	COL_ADDR,
+	COL_SERV,
+	COL_RADDR,
+	COL_RSERV,
+	COL_EXT,
+	COL_MAX
+};
+
+enum col_align {
+	ALIGN_LEFT,
+	ALIGN_CENTER,
+	ALIGN_RIGHT
+};
+
+struct column {
+	const enum col_align align;
+	const char *header;
+	const char *ldelim;
+	int width;	/* Including delimiter. -1: fit to content, 0: hide */
+	int stored;	/* Characters buffered */
+	int printed;	/* Characters printed so far */
+};
+
+static struct column columns[] = {
+	{ ALIGN_LEFT,	"Netid",		"",	0,	0,	0 },
+	{ ALIGN_LEFT,	"State",		" ",	0,	0,	0 },
+	{ ALIGN_LEFT,	"Recv-Q",		" ",	7,	0,	0 },
+	{ ALIGN_LEFT,	"Send-Q",		" ",	7,	0,	0 },
+	{ ALIGN_RIGHT,	"Local Address:",	" ",	0,	0,	0 },
+	{ ALIGN_LEFT,	"Port",			"",	0,	0,	0 },
+	{ ALIGN_RIGHT,	"Peer Address:",	" ",	0,	0,	0 },
+	{ ALIGN_LEFT,	"Port",			"",	0,	0,	0 },
+	{ ALIGN_LEFT,	"",			"",	-1,	0,	0 },
+};
+
+static struct column *current_field = columns;
+static char field_buf[BUFSIZ];
 
 static const char *TCP_PROTO = "tcp";
 static const char *SCTP_PROTO = "sctp";
@@ -826,13 +863,113 @@ static const char *vsock_netid_name(int type)
 
 static void out(const char *fmt, ...)
 {
+	struct column *f = current_field;
 	va_list args;
 
 	va_start(args, fmt);
-	vfprintf(stdout, fmt, args);
+	f->stored += vsnprintf(field_buf + f->stored, BUFSIZ - f->stored,
+			       fmt, args);
 	va_end(args);
 }
 
+static int print_left_spacing(struct column *f)
+{
+	int s;
+
+	if (f->width < 0 || f->align == ALIGN_LEFT)
+		return 0;
+
+	s = f->width - f->stored - f->printed;
+	if (f->align == ALIGN_CENTER)
+		/* If count of total spacing is odd, shift right by one */
+		s = (s + 1) / 2;
+
+	if (s > 0)
+		return printf("%*c", s, ' ');
+
+	return 0;
+}
+
+static void print_right_spacing(struct column *f)
+{
+	int s;
+
+	if (f->width < 0 || f->align == ALIGN_RIGHT)
+		return;
+
+	s = f->width - f->printed;
+	if (f->align == ALIGN_CENTER)
+		s /= 2;
+
+	if (s > 0)
+		printf("%*c", s, ' ');
+}
+
+static int field_needs_delimiter(struct column *f)
+{
+	if (!f->stored)
+		return 0;
+
+	/* Was another field already printed on this line? */
+	for (f--; f >= columns; f--)
+		if (f->width)
+			return 1;
+
+	return 0;
+}
+
+/* Flush given field to screen together with delimiter and spacing */
+static void field_flush(struct column *f)
+{
+	if (!f->width)
+		return;
+
+	if (field_needs_delimiter(f))
+		f->printed = printf("%s", f->ldelim);
+
+	f->printed += print_left_spacing(f);
+	f->printed += printf("%s", field_buf);
+	print_right_spacing(f);
+
+	*field_buf = 0;
+	f->printed = 0;
+	f->stored = 0;
+}
+
+static int field_is_last(struct column *f)
+{
+	return f - columns == COL_MAX - 1;
+}
+
+static void field_next(void)
+{
+	field_flush(current_field);
+
+	if (field_is_last(current_field)) {
+		printf("\n");
+		current_field = columns;
+	} else {
+		current_field++;
+	}
+}
+
+/* Walk through fields and flush them until we reach the desired one */
+static void field_set(enum col_id id)
+{
+	while (id != current_field - columns)
+		field_next();
+}
+
+/* Print header for all non-empty columns */
+static void print_header(void)
+{
+	while (!field_is_last(current_field)) {
+		if (current_field->width)
+			out(current_field->header);
+		field_next();
+	}
+}
+
 static void sock_state_print(struct sockstat *s)
 {
 	const char *sock_name;
@@ -872,18 +1009,21 @@ static void sock_state_print(struct sockstat *s)
 		sock_name = "unknown";
 	}
 
-	if (netid_width)
-		out("%-*s ", netid_width,
-		    is_sctp_assoc(s, sock_name) ? "" : sock_name);
-	if (state_width) {
-		if (is_sctp_assoc(s, sock_name))
-			out("`- %-*s ", state_width - 3,
-			    sctp_sstate_name[s->state]);
-		else
-			out("%-*s ", state_width, sstate_name[s->state]);
+	if (is_sctp_assoc(s, sock_name)) {
+		field_set(COL_STATE);		/* Empty Netid field */
+		out("`- %s", sctp_sstate_name[s->state]);
+	} else {
+		field_set(COL_NETID);
+		out("%s", sock_name);
+		field_set(COL_STATE);
+		out("%s", sstate_name[s->state]);
 	}
 
-	out("%-6d %-6d %s", s->rq, s->wq, odd_width_pad);
+	field_set(COL_RECVQ);
+	out("%-6d", s->rq);
+	field_set(COL_SENDQ);
+	out("%-6d", s->wq);
+	field_set(COL_ADDR);
 }
 
 static void sock_details_print(struct sockstat *s)
@@ -898,21 +1038,17 @@ static void sock_details_print(struct sockstat *s)
 		out(" fwmark:0x%x", s->mark);
 }
 
-static void sock_addr_print_width(int addr_len, const char *addr, char *delim,
-		int port_len, const char *port, const char *ifname)
-{
-	if (ifname) {
-		out("%*s%%%s%s%-*s ", addr_len, addr, ifname, delim,
-		    port_len, port);
-	} else {
-		out("%*s%s%-*s ", addr_len, addr, delim, port_len, port);
-	}
-}
-
 static void sock_addr_print(const char *addr, char *delim, const char *port,
 		const char *ifname)
 {
-	sock_addr_print_width(addr_width, addr, delim, serv_width, port, ifname);
+	if (ifname)
+		out("%s" "%%" "%s%s", addr, ifname, delim);
+	else
+		out("%s%s", addr, delim);
+
+	field_next();
+	out("%s", port);
+	field_next();
 }
 
 static const char *print_ms_timer(unsigned int timeout)
@@ -1093,7 +1229,6 @@ static void inet_addr_print(const inet_prefix *a, int port,
 {
 	char buf[1024];
 	const char *ap = buf;
-	int est_len = addr_width;
 	const char *ifname = NULL;
 
 	if (a->family == AF_INET) {
@@ -1112,24 +1247,13 @@ static void inet_addr_print(const inet_prefix *a, int port,
 					 "[%s]", ap);
 				ap = buf;
 			}
-
-			est_len = strlen(ap);
-			if (est_len <= addr_width)
-				est_len = addr_width;
-			else
-				est_len = addr_width + ((est_len-addr_width+3)/4)*4;
 		}
 	}
 
-	if (ifindex) {
-		ifname   = ll_index_to_name(ifindex);
-		est_len -= strlen(ifname) + 1;  /* +1 for percent char */
-		if (est_len < 0)
-			est_len = 0;
-	}
+	if (ifindex)
+		ifname = ll_index_to_name(ifindex);
 
-	sock_addr_print_width(est_len, ap, ":", serv_width, resolve_service(port),
-			ifname);
+	sock_addr_print(ap, ":", resolve_service(port), ifname);
 }
 
 struct aafilter {
@@ -2166,7 +2290,6 @@ static int tcp_show_line(char *line, const struct filter *f, int family)
 	if (show_tcpinfo)
 		tcp_stats_print(&s);
 
-	out("\n");
 	return 0;
 }
 
@@ -2547,7 +2670,6 @@ static int inet_show_sock(struct nlmsghdr *nlh,
 	}
 	sctp_ino = s->ino;
 
-	out("\n");
 	return 0;
 }
 
@@ -3005,7 +3127,6 @@ static int dgram_show_line(char *line, const struct filter *f, int family)
 	if (show_details && opt[0])
 		out(" opt:\"%s\"", opt);
 
-	out("\n");
 	return 0;
 }
 
@@ -3184,7 +3305,6 @@ static int unix_show_sock(const struct sockaddr_nl *addr, struct nlmsghdr *nlh,
 			    mask & 1 ? '-' : '<', mask & 2 ? '-' : '>');
 		}
 	}
-	out("\n");
 
 	return 0;
 }
@@ -3341,8 +3461,6 @@ static int unix_show(struct filter *f)
 		if (++cnt > MAX_UNIX_REMEMBER) {
 			while (list) {
 				unix_stats_print(list, f);
-				out("\n");
-
 				unix_list_drop_first(&list);
 			}
 			cnt = 0;
@@ -3351,8 +3469,6 @@ static int unix_show(struct filter *f)
 	fclose(fp);
 	while (list) {
 		unix_stats_print(list, f);
-		out("\n");
-
 		unix_list_drop_first(&list);
 	}
 
@@ -3527,7 +3643,6 @@ static int packet_show_sock(const struct sockaddr_nl *addr,
 			fil++;
 		}
 	}
-	out("\n");
 	return 0;
 }
 
@@ -3570,7 +3685,6 @@ static int packet_show_line(char *buf, const struct filter *f, int fam)
 	if (packet_stats_print(&stat, f))
 		return 0;
 
-	out("\n");
 	return 0;
 }
 
@@ -3690,7 +3804,6 @@ static int netlink_show_one(struct filter *f,
 	if (show_details) {
 		out(" sk=%llx cb=%llx groups=0x%08x", sk, cb, groups);
 	}
-	out("\n");
 
 	return 0;
 }
@@ -3728,7 +3841,6 @@ static int netlink_show_sock(const struct sockaddr_nl *addr,
 	if (show_mem) {
 		out("\t");
 		print_skmeminfo(tb, NETLINK_DIAG_MEMINFO);
-		out("\n");
 	}
 
 	return 0;
@@ -3818,8 +3930,6 @@ static void vsock_stats_print(struct sockstat *s, struct filter *f)
 	vsock_addr_print(&s->remote, s->rport);
 
 	proc_ctx_print(s);
-
-	out("\n");
 }
 
 static int vsock_show_sock(const struct sockaddr_nl *addr,
@@ -4539,13 +4649,17 @@ int main(int argc, char *argv[])
 	if (ssfilter_parse(&current_filter.f, argc, argv, filter_fp))
 		usage();
 
-	netid_width = 0;
 	if (current_filter.dbs&(current_filter.dbs-1))
-		netid_width = 5;
+		columns[COL_NETID].width = 6;
 
-	state_width = 0;
 	if (current_filter.states&(current_filter.states-1))
-		state_width = 10;
+		columns[COL_STATE].width = 10;
+
+	/* If Netid or State are hidden, no delimiter before next column */
+	if (!columns[COL_NETID].width)
+		columns[COL_STATE].width--;
+	else if (!columns[COL_STATE].width)
+		columns[COL_RECVQ].width--;
 
 	if (isatty(STDOUT_FILENO)) {
 		struct winsize w;
@@ -4556,49 +4670,38 @@ int main(int argc, char *argv[])
 		}
 	}
 
-	addrp_width = screen_width;
-	if (netid_width)
-		addrp_width -= netid_width + 1;
-	if (state_width)
-		addrp_width -= state_width + 1;
-	addrp_width -= 14;
+	addrp_width = screen_width -
+		      columns[COL_NETID].width -
+		      columns[COL_STATE].width -
+		      columns[COL_RECVQ].width -
+		      columns[COL_SENDQ].width;
 
 	if (addrp_width&1) {
-		if (netid_width)
-			netid_width++;
-		else if (state_width)
-			state_width++;
+		if (columns[COL_NETID].width)
+			columns[COL_NETID].width++;
+		else if (columns[COL_STATE].width)
+			columns[COL_STATE].width++;
 		else
-			odd_width_pad = " ";
+			columns[COL_SENDQ].width++;
 	}
 
 	addrp_width /= 2;
-	addrp_width--;
 
-	serv_width = resolve_services ? 7 : 5;
+	columns[COL_SERV].width = resolve_services ? 8 : 6;
+	if (addrp_width < 15 + columns[COL_SERV].width)
+		addrp_width = 15 + columns[COL_SERV].width;
 
-	if (addrp_width < 15+serv_width+1)
-		addrp_width = 15+serv_width+1;
-
-	addr_width = addrp_width - serv_width - 1;
-
-	if (show_header) {
-		if (netid_width)
-			out("%-*s ", netid_width, "Netid");
-		if (state_width)
-			out("%-*s ", state_width, "State");
-		out("%-6s %-6s %s", "Recv-Q", "Send-Q", odd_width_pad);
-	}
+	columns[COL_ADDR].width = addrp_width - columns[COL_SERV].width;
 
 	/* Make enough space for the local/remote port field */
-	addr_width -= 13;
-	serv_width += 13;
+	columns[COL_ADDR].width -= 13;
+	columns[COL_SERV].width += 13;
 
-	if (show_header) {
-		out("%*s:%-*s %*s:%-*s\n",
-		    addr_width, "Local Address", serv_width, "Port",
-		    addr_width, "Peer Address", serv_width, "Port");
-	}
+	columns[COL_RADDR].width = columns[COL_ADDR].width;
+	columns[COL_RSERV].width = columns[COL_SERV].width;
+
+	if (show_header)
+		print_header();
 
 	fflush(stdout);
 
@@ -4627,5 +4730,7 @@ int main(int argc, char *argv[])
 	if (show_users || show_proc_ctx || show_sock_ctx)
 		user_ent_destroy();
 
+	field_next();
+
 	return 0;
 }
-- 
2.9.4

  parent reply	other threads:[~2017-12-12  0:46 UTC|newest]

Thread overview: 19+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2017-12-12  0:46 [PATCH iproute2 net-next v2 0/4] Abstract columns, properly space and wrap fields Stefano Brivio
2017-12-12  0:46 ` [PATCH iproute2 net-next v2 1/4] ss: Replace printf() calls for "main" output by calls to helper Stefano Brivio
2017-12-12  0:46 ` Stefano Brivio [this message]
2017-12-12  0:46 ` [PATCH iproute2 net-next v2 3/4] ss: Buffer raw fields first, then render them as a table Stefano Brivio
2019-02-13  0:42   ` Eric Dumazet
2019-02-13  8:37     ` Stefano Brivio
2019-02-13 16:51       ` Stephen Hemminger
2019-02-13 17:22         ` Stefano Brivio
2019-02-13 17:32           ` Eric Dumazet
2019-02-13 17:31       ` Eric Dumazet
2019-02-13 17:38         ` Stefano Brivio
2019-02-13 18:01           ` Eric Dumazet
2019-02-13 21:17         ` Stefano Brivio
2019-02-13 21:55           ` Stephen Hemminger
2019-02-13 22:20             ` Stefano Brivio
2019-02-13 23:39             ` Phil Sutter
2019-02-13 23:47               ` David Ahern
2017-12-12  0:46 ` [PATCH iproute2 net-next v2 4/4] ss: Implement automatic column width calculation Stefano Brivio
2017-12-12 20:13 ` [PATCH iproute2 net-next v2 0/4] Abstract columns, properly space and wrap fields Stephen Hemminger

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=0efa628603c0ca739c9b44a85819d93636f90c3b.1513039237.git.sbrivio@redhat.com \
    --to=sbrivio@redhat.com \
    --cc=netdev@vger.kernel.org \
    --cc=sd@queasysnail.net \
    --cc=stephen@networkplumber.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).