[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <93d2f58e80494fcec32444d240b1e0541fdf3068.1512750298.git.sbrivio@redhat.com>
Date: Fri, 8 Dec 2017 18:07:23 +0100
From: Stefano Brivio <sbrivio@...hat.com>
To: Stephen Hemminger <stephen@...workplumber.org>
Cc: netdev@...r.kernel.org, Sabrina Dubroca <sd@...asysnail.net>
Subject: [PATCH iproute2 net-next 4/4] ss: Implement automatic column width calculation
Group fitting fields into lines and space them equally using the
remaining screen width for each line. If columns don't fit on
one line, break them into the least possible amount of lines and
keep them aligned across lines.
This is done by:
- recording the length of the longest item in each column during
formatting and buffering (which was added in the previous patch)
- fitting as many fields as possible on each line of output
- distributing the remaining padding space equally between the
columns
Signed-off-by: Stefano Brivio <sbrivio@...hat.com>
Reviewed-by: Sabrina Dubroca <sd@...asysnail.net>
---
misc/ss.c | 188 +++++++++++++++++++++++++++++++++++++++-----------------------
1 file changed, 120 insertions(+), 68 deletions(-)
diff --git a/misc/ss.c b/misc/ss.c
index abc0da7fa8fe..44ad9587b62c 100644
--- a/misc/ss.c
+++ b/misc/ss.c
@@ -128,19 +128,21 @@ struct column {
const enum col_align align;
const char *header;
const char *ldelim;
- int width; /* Including delimiter. -1: fit to content, 0: hide */
+ int disabled;
+ int width; /* Calculated, including additional layout spacing */
+ int max_len; /* Measured maximum field length in this column */
};
static struct column columns[] = {
- { ALIGN_LEFT, "Netid", "", 0 },
- { ALIGN_LEFT, "State", " ", 0 },
- { ALIGN_LEFT, "Recv-Q", " ", 7 },
- { ALIGN_LEFT, "Send-Q", " ", 7 },
- { ALIGN_RIGHT, "Local Address:", " ", 0 },
- { ALIGN_LEFT, "Port", "", 0 },
- { ALIGN_RIGHT, "Peer Address:", " ", 0 },
- { ALIGN_LEFT, "Port", "", 0 },
- { ALIGN_LEFT, "", "", -1 },
+ { ALIGN_LEFT, "Netid", "", 0, 0, 0 },
+ { ALIGN_LEFT, "State", " ", 0, 0, 0 },
+ { ALIGN_LEFT, "Recv-Q", " ", 0, 0, 0 },
+ { ALIGN_LEFT, "Send-Q", " ", 0, 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, "", "", 0, 0, 0 },
};
static struct column *current_field = columns;
@@ -959,7 +961,7 @@ static void out(const char *fmt, ...)
char *pos;
int len;
- if (!f->width)
+ if (f->disabled)
return;
if (!buffer.head)
@@ -982,7 +984,7 @@ static int print_left_spacing(struct column *f, int stored, int printed)
{
int s;
- if (f->width < 0 || f->align == ALIGN_LEFT)
+ if (!f->width || f->align == ALIGN_LEFT)
return 0;
s = f->width - stored - printed;
@@ -1000,7 +1002,7 @@ static void print_right_spacing(struct column *f, int printed)
{
int s;
- if (f->width < 0 || f->align == ALIGN_RIGHT)
+ if (!f->width || f->align == ALIGN_RIGHT)
return;
s = f->width - printed;
@@ -1017,9 +1019,12 @@ static void field_flush(struct column *f)
struct buf_chunk *chunk = buffer.tail;
unsigned int pad = buffer.cur->len % 2;
- if (!f->width)
+ if (f->disabled)
return;
+ if (buffer.cur->len > f->max_len)
+ f->max_len = buffer.cur->len;
+
/* We need a new chunk if we can't store the next length descriptor.
* Mind the gap between end of previous token and next aligned position
* for length descriptor.
@@ -1062,7 +1067,7 @@ static void field_set(enum col_id id)
static void print_header(void)
{
while (!field_is_last(current_field)) {
- if (current_field->width)
+ if (!current_field->disabled)
out(current_field->header);
field_next();
}
@@ -1095,16 +1100,106 @@ static void buf_free_all(void)
buffer.head = NULL;
}
+/* Calculate column width from contents length. If columns don't fit on one
+ * line, break them into the least possible amount of lines and keep them
+ * aligned across lines. Available screen space is equally spread between fields
+ * as additional spacing.
+ */
+static void render_calc_width(int screen_width)
+{
+ int first, len = 0, linecols = 0;
+ struct column *c, *eol = columns - 1;
+
+ /* First pass: set width for each column to measured content length */
+ for (first = 1, c = columns; c - columns < COL_MAX; c++) {
+ if (c->disabled)
+ continue;
+
+ if (!first && c->max_len)
+ c->width = c->max_len + strlen(c->ldelim);
+ else
+ c->width = c->max_len;
+
+ /* But don't exceed screen size. If we exceed the screen size
+ * for even a single field, it will just start on a line of its
+ * own and then naturally wrap.
+ */
+ c->width = min(c->width, screen_width);
+
+ if (c->width)
+ first = 0;
+ }
+
+ /* Second pass: find out newlines and distribute available spacing */
+ for (c = columns; c - columns < COL_MAX; c++) {
+ int pad, spacing, rem, last;
+ struct column *tmp;
+
+ if (!c->width)
+ continue;
+
+ linecols++;
+ len += c->width;
+
+ for (last = 1, tmp = c + 1; tmp - columns < COL_MAX; tmp++) {
+ if (tmp->width) {
+ last = 0;
+ break;
+ }
+ }
+
+ if (!last && len < screen_width) {
+ /* Columns fit on screen so far, nothing to do yet */
+ continue;
+ }
+
+ if (len == screen_width) {
+ /* Exact fit, just start with new line */
+ goto newline;
+ }
+
+ if (len > screen_width) {
+ /* Screen width exceeded: go back one column */
+ len -= c->width;
+ c--;
+ linecols--;
+ }
+
+ /* Distribute remaining space to columns on this line */
+ pad = screen_width - len;
+ spacing = pad / linecols;
+ rem = pad % linecols;
+ for (tmp = c; tmp > eol; tmp--) {
+ if (!tmp->width)
+ continue;
+
+ tmp->width += spacing;
+ if (rem) {
+ tmp->width++;
+ rem--;
+ }
+ }
+
+newline:
+ /* Line break: reset line counters, mark end-of-line */
+ eol = c;
+ len = 0;
+ linecols = 0;
+ }
+}
+
/* Render buffered output with spacing and delimiters, then free up buffers */
-static void render(void)
+static void render(int screen_width)
{
struct buf_token *token = (struct buf_token *)buffer.head->data;
- int printed, line_started = 0, need_newline = 0;
+ int printed, line_started = 0;
struct column *f;
/* Ensure end alignment of last token, it wasn't necessarily flushed */
buffer.tail->end += buffer.cur->len % 2;
+ render_calc_width(screen_width);
+
/* Rewind and replay */
buffer.tail = buffer.head;
@@ -1124,23 +1219,16 @@ static void render(void)
printed += fwrite(token->data, 1, token->len, stdout);
print_right_spacing(f, printed);
- /* Variable field size or overflow, won't align to screen */
- if (printed > f->width)
- need_newline = 1;
-
/* Go to next non-empty field, deal with end-of-line */
do {
if (field_is_last(f)) {
- if (need_newline) {
- printf("\n");
- need_newline = 0;
- }
+ printf("\n");
f = columns;
line_started = 0;
} else {
f++;
}
- } while (!f->width);
+ } while (f->disabled);
token = buf_token_next(token);
}
@@ -4531,7 +4619,7 @@ int main(int argc, char *argv[])
FILE *filter_fp = NULL;
int ch;
int state_filter = 0;
- int addrp_width, screen_width = 80;
+ int screen_width = 80;
while ((ch = getopt_long(argc, argv,
"dhaletuwxnro460spbEf:miA:D:F:vVzZN:KHS",
@@ -4827,17 +4915,11 @@ int main(int argc, char *argv[])
if (ssfilter_parse(¤t_filter.f, argc, argv, filter_fp))
usage();
- if (current_filter.dbs&(current_filter.dbs-1))
- columns[COL_NETID].width = 6;
-
- if (current_filter.states&(current_filter.states-1))
- columns[COL_STATE].width = 10;
+ if (!(current_filter.dbs & (current_filter.dbs - 1)))
+ columns[COL_NETID].disabled = 1;
- /* 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 (!(current_filter.states & (current_filter.states - 1)))
+ columns[COL_STATE].disabled = 1;
if (isatty(STDOUT_FILENO)) {
struct winsize w;
@@ -4848,36 +4930,6 @@ int main(int argc, char *argv[])
}
}
- 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 (columns[COL_NETID].width)
- columns[COL_NETID].width++;
- else if (columns[COL_STATE].width)
- columns[COL_STATE].width++;
- else
- columns[COL_SENDQ].width++;
- }
-
- addrp_width /= 2;
-
- columns[COL_SERV].width = resolve_services ? 8 : 6;
- if (addrp_width < 15 + columns[COL_SERV].width)
- addrp_width = 15 + columns[COL_SERV].width;
-
- columns[COL_ADDR].width = addrp_width - columns[COL_SERV].width;
-
- /* Make enough space for the local/remote port field */
- columns[COL_ADDR].width -= 13;
- columns[COL_SERV].width += 13;
-
- columns[COL_RADDR].width = columns[COL_ADDR].width;
- columns[COL_RSERV].width = columns[COL_SERV].width;
-
if (show_header)
print_header();
@@ -4908,7 +4960,7 @@ int main(int argc, char *argv[])
if (show_users || show_proc_ctx || show_sock_ctx)
user_ent_destroy();
- render();
+ render(screen_width);
return 0;
}
--
2.9.4
Powered by blists - more mailing lists