]> git.proxmox.com Git - mirror_iproute2.git/commitdiff
ss: Render buffer to output every time a number of chunks are allocated
authorStefano Brivio <sbrivio@redhat.com>
Thu, 14 Feb 2019 00:58:32 +0000 (01:58 +0100)
committerStephen Hemminger <stephen@networkplumber.org>
Thu, 21 Feb 2019 22:45:45 +0000 (14:45 -0800)
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.dumazet@gmail.com>
Fixes: 691bd854bf4a ("ss: Buffer raw fields first, then render them as a table")
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
misc/ss.c

index 766fdc5f5bb6b9ce6bc42e8beb88086e2b707645..e9033503ddbb3ec87c2e831908918bc6f3443458 100644 (file)
--- a/misc/ss.c
+++ b/misc/ss.c
@@ -60,7 +60,8 @@
 #endif
 
 #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)                                             \
@@ -184,6 +185,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";
@@ -944,6 +946,8 @@ static struct buf_chunk *buf_chunk_new(void)
 
        new->end = buffer.cur->data;
 
+       buffer.chunks++;
+
        return new;
 }
 
@@ -1088,33 +1092,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)
 {
@@ -1140,6 +1117,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 */
@@ -1302,6 +1280,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;