From 87b1a7aec762503c7befd2fd041f7c84ca4d3fe0 Mon Sep 17 00:00:00 2001 From: Stefano Brivio Date: Tue, 12 Dec 2017 01:46:33 +0100 Subject: [PATCH] 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 Reviewed-by: Sabrina Dubroca --- misc/ss.c | 188 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 120 insertions(+), 68 deletions(-) diff --git a/misc/ss.c b/misc/ss.c index 16626797..9d21ed7a 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; @@ -960,7 +962,7 @@ static void out(const char *fmt, ...) char *pos; int len; - if (!f->width) + if (f->disabled) return; if (!buffer.head) @@ -983,7 +985,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; @@ -1001,7 +1003,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; @@ -1018,9 +1020,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. @@ -1063,7 +1068,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(); } @@ -1096,16 +1101,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; @@ -1125,23 +1220,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); } @@ -4532,7 +4620,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", @@ -4828,17 +4916,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; @@ -4849,36 +4931,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(); @@ -4909,7 +4961,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.39.2