Hi Stephen,

I think this patch is reasonably tested now. Eric, who reported the
original issue, is also satisfied with it. Is there any issue with it?

-- 
Stefano

On Thu, 14 Feb 2019 01:58:32 +0100
Stefano Brivio <sbri...@redhat.com> wrote:

> Eric reported that, with 10 million sockets, ss -emoi (about 1000 bytes
> output per socket) can easily lead to OOM (buffer would grow to 10GB of
> memory).
> 
> Limit the maximum size of the buffer to five chunks, 1M each. Render and
> flush buffers whenever we reach that.
> 
> This might make the resulting blocks slightly unaligned between them, with
> occasional loss of readability on lines occurring every 5k to 50k sockets
> approximately. Something like (from ss -tu):
> 
> [...]
> CLOSE-WAIT   32       0           192.168.1.50:35232           10.0.0.1:https
> ESTAB        0        0           192.168.1.50:53820           10.0.0.1:https
> ESTAB       0        0           192.168.1.50:46924            10.0.0.1:https
> CLOSE-WAIT  32       0           192.168.1.50:35228            10.0.0.1:https
> [...]
> 
> However, I don't actually expect any human user to scroll through that
> amount of sockets, so readability should be preserved when it matters.
> 
> The bulk of the diffstat comes from moving field_next() around, as we now
> call render() from it. Functionally, this is implemented by six lines of
> code, most of them in field_next().
> 
> Reported-by: Eric Dumazet <eric.duma...@gmail.com>
> Fixes: 691bd854bf4a ("ss: Buffer raw fields first, then render them as a 
> table")
> Signed-off-by: Stefano Brivio <sbri...@redhat.com>
> ---
> Eric, it would be nice if you could test this with your bazillion sockets,
> I checked this with -emoi and "only" 500,000 sockets.
> 
>  misc/ss.c | 68 ++++++++++++++++++++++++++++++++-----------------------
>  1 file changed, 40 insertions(+), 28 deletions(-)
> 
> diff --git a/misc/ss.c b/misc/ss.c
> index 9e821faf0d31..28bdcba72d73 100644
> --- a/misc/ss.c
> +++ b/misc/ss.c
> @@ -52,7 +52,8 @@
>  #include <linux/tipc_sockets_diag.h>
>  
>  #define MAGIC_SEQ 123456
> -#define BUF_CHUNK (1024 * 1024)
> +#define BUF_CHUNK (1024 * 1024)      /* Buffer chunk allocation size */
> +#define BUF_CHUNKS_MAX 5     /* Maximum number of allocated buffer chunks */
>  #define LEN_ALIGN(x) (((x) + 1) & ~1)
>  
>  #define DIAG_REQUEST(_req, _r)                                               
>     \
> @@ -176,6 +177,7 @@ static struct {
>       struct buf_token *cur;  /* Position of current token in chunk */
>       struct buf_chunk *head; /* First chunk */
>       struct buf_chunk *tail; /* Current chunk */
> +     int chunks;             /* Number of allocated chunks */
>  } buffer;
>  
>  static const char *TCP_PROTO = "tcp";
> @@ -936,6 +938,8 @@ static struct buf_chunk *buf_chunk_new(void)
>  
>       new->end = buffer.cur->data;
>  
> +     buffer.chunks++;
> +
>       return new;
>  }
>  
> @@ -1080,33 +1084,6 @@ 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))
> -             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->disabled)
> -                     out("%s", current_field->header);
> -             field_next();
> -     }
> -}
> -
>  /* Get the next available token in the buffer starting from the current 
> token */
>  static struct buf_token *buf_token_next(struct buf_token *cur)
>  {
> @@ -1132,6 +1109,7 @@ static void buf_free_all(void)
>               free(tmp);
>       }
>       buffer.head = NULL;
> +     buffer.chunks = 0;
>  }
>  
>  /* Get current screen width, default to 80 columns if TIOCGWINSZ fails */
> @@ -1294,6 +1272,40 @@ static void render(void)
>       current_field = columns;
>  }
>  
> +/* Move to next field, and render buffer if we reached the maximum number of
> + * chunks, at the last field in a line.
> + */
> +static void field_next(void)
> +{
> +     if (field_is_last(current_field) && buffer.chunks >= BUF_CHUNKS_MAX) {
> +             render();
> +             return;
> +     }
> +
> +     field_flush(current_field);
> +     if (field_is_last(current_field))
> +             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->disabled)
> +                     out("%s", current_field->header);
> +             field_next();
> +     }
> +}
> +
>  static void sock_state_print(struct sockstat *s)
>  {
>       const char *sock_name;

Reply via email to