MINOR: cli: add a new "show fd" command
authorWilly Tarreau <w@1wt.eu>
Tue, 25 Jul 2017 17:32:50 +0000 (19:32 +0200)
committerWilly Tarreau <w@1wt.eu>
Fri, 28 Jul 2017 15:03:12 +0000 (17:03 +0200)
This one dumps the fdtab for all active FDs with some quickly interpretable
characters to read the flags (like upper case=set, lower case=unset). It
can probably be improved to report fdupdt[] and/or fdinfo[] but at least it
provides a good start and allows to see how FDs are seen. When the fd owner
is a connection, its flags are also reported as it can help compare with the
polling status, and the target (fe/px/sv) as well. When it's a listener, the
listener's state is reported as well as the frontend it belongs to.

doc/management.txt
src/cli.c

index 5a5e093..dd604ae 100644 (file)
@@ -1809,6 +1809,25 @@ show errors [<iid>|<proxy>] [request|response]
     is the slash ('/') in header name "header/bizarre", which is not a valid
     HTTP character for a header name.
 
+show fd [<fd>]
+  Dump the list of either all open file descriptors or just the one number <fd>
+  if specified. This is only aimed at developers who need to observe internal
+  states in order to debug complex issues such as abnormal CPU usages. One fd
+  is reported per lines, and for each of them, its state in the poller using
+  upper case letters for enabled flags and lower case for disabled flags, using
+  "P" for "polled", "R" for "ready", "A" for "active", the events status using
+  "H" for "hangup", "E" for "error", "O" for "output", "P" for "priority" and
+  "I" for "input", a few other flags like "N" for "new" (just added into the fd
+  cache), "U" for "updated" (received an update in the fd cache), "L" for
+  "linger_risk", "C" for "cloned", then the cached entry position, the pointer
+  to the internal owner, the pointer to the I/O callback and its name when
+  known. When the owner is a connection, the connection flags, and the target
+  are reported (frontend, proxy or server). When the owner is a listener, the
+  listener's state and its frontend are reported. There is no point in using
+  this command without a good knowledge of the internals. It's worth noting
+  that the output format may evolve over time so this output must not be parsed
+  by tools designed to be durable.
+
 show info [typed|json]
   Dump info about haproxy status on current process. If "typed" is passed as an
   optional argument, field numbers, names and types are emitted as well so that
index fef34e9..6498815 100644 (file)
--- a/src/cli.c
+++ b/src/cli.c
@@ -66,6 +66,7 @@
 #include <proto/server.h>
 #include <proto/stream_interface.h>
 #include <proto/task.h>
+#include <proto/proto_udp.h>
 
 static struct applet cli_applet;
 
@@ -734,6 +735,106 @@ static int cli_io_handler_show_env(struct appctx *appctx)
        return 1;
 }
 
+/* This function dumps all file descriptors states (or the requested one) to
+ * the buffer. It returns 0 if the output buffer is full and it needs to be
+ * called again, otherwise non-zero. Dumps only one entry if st2 == STAT_ST_END.
+ * It uses cli.i0 as the fd number to restart from.
+ */
+static int cli_io_handler_show_fd(struct appctx *appctx)
+{
+       struct stream_interface *si = appctx->owner;
+       int fd = appctx->ctx.cli.i0;
+
+       if (unlikely(si_ic(si)->flags & (CF_WRITE_ERROR|CF_SHUTW)))
+               return 1;
+
+       chunk_reset(&trash);
+
+       /* we have two inner loops here, one for the proxy, the other one for
+        * the buffer.
+        */
+       while (fd < maxfd) {
+               struct fdtab fdt;
+               struct listener *li;
+               struct server *sv;
+               struct proxy *px;
+               uint32_t conn_flags;
+
+               fdt = fdtab[fd];
+
+               if (fdt.iocb == conn_fd_handler) {
+                       conn_flags = ((struct connection *)fdt.owner)->flags;
+                       li = objt_listener(((struct connection *)fdt.owner)->target);
+                       sv = objt_server(((struct connection *)fdt.owner)->target);
+                       px = objt_proxy(((struct connection *)fdt.owner)->target);
+               }
+               else if (fdt.iocb == listener_accept)
+                       li = fdt.owner;
+
+               if (!fdt.owner)
+                       goto skip; // closed
+
+               chunk_printf(&trash,
+                            "  %5d : st=0x%02x(R:%c%c%c W:%c%c%c) ev=0x%02x(%c%c%c%c%c) [%c%c%c%c] cache=%u owner=%p iocb=%p(%s)",
+                            fd,
+                            fdt.state,
+                            (fdt.state & FD_EV_POLLED_R) ? 'P' : 'p',
+                            (fdt.state & FD_EV_READY_R)  ? 'R' : 'r',
+                            (fdt.state & FD_EV_ACTIVE_R) ? 'A' : 'a',
+                            (fdt.state & FD_EV_POLLED_W) ? 'P' : 'p',
+                            (fdt.state & FD_EV_READY_W)  ? 'R' : 'r',
+                            (fdt.state & FD_EV_ACTIVE_W) ? 'A' : 'a',
+                            fdt.ev,
+                            (fdt.ev & FD_POLL_HUP) ? 'H' : 'h',
+                            (fdt.ev & FD_POLL_ERR) ? 'E' : 'e',
+                            (fdt.ev & FD_POLL_OUT) ? 'O' : 'o',
+                            (fdt.ev & FD_POLL_PRI) ? 'P' : 'p',
+                            (fdt.ev & FD_POLL_IN)  ? 'I' : 'i',
+                            fdt.new ? 'N' : 'n',
+                            fdt.updated ? 'U' : 'u',
+                            fdt.linger_risk ? 'L' : 'l',
+                            fdt.cloned ? 'C' : 'c',
+                            fdt.cache,
+                            fdt.owner,
+                            fdt.iocb,
+                            (fdt.iocb == conn_fd_handler)  ? "conn_fd_handler" :
+                            (fdt.iocb == dgram_fd_handler) ? "dgram_fd_handler" :
+                            (fdt.iocb == listener_accept)  ? "listener_accept" :
+                            "unknown");
+
+               if (fdt.iocb == conn_fd_handler) {
+                       chunk_appendf(&trash, " cflg=0x%08x", conn_flags);
+                       if (px)
+                               chunk_appendf(&trash, " px=%s", px->id);
+                       else if (sv)
+                               chunk_appendf(&trash, " sv=%s/%s", sv->id, sv->proxy->id);
+                       else if (li)
+                               chunk_appendf(&trash, " fe=%s", li->bind_conf->frontend->id);
+               }
+               else if (fdt.iocb == listener_accept) {
+                       chunk_appendf(&trash, " l.st=%s fe=%s",
+                                     listener_state_str(li),
+                                     li->bind_conf->frontend->id);
+               }
+
+               chunk_appendf(&trash, "\n");
+
+               if (bi_putchk(si_ic(si), &trash) == -1) {
+                       si_applet_cant_put(si);
+                       return 0;
+               }
+       skip:
+               if (appctx->st2 == STAT_ST_END)
+                       break;
+
+               fd++;
+               appctx->ctx.cli.i0 = fd;
+       }
+
+       /* dump complete */
+       return 1;
+}
+
 /*
  * CLI IO handler for `show cli sockets`.
  * Uses ctx.cli.p0 to store the restart pointer.
@@ -863,6 +964,24 @@ static int cli_parse_show_env(char **args, struct appctx *appctx, void *private)
        return 0;
 }
 
+/* parse a "show fd" CLI request. Returns 0 if it needs to continue, 1 if it
+ * wants to stop here. It puts the FD number into cli.i0 if a specific FD is
+ * requested and sets st2 to STAT_ST_END, otherwise leaves 0 in i0.
+ */
+static int cli_parse_show_fd(char **args, struct appctx *appctx, void *private)
+{
+       if (!cli_has_level(appctx, ACCESS_LVL_OPER))
+               return 1;
+
+       appctx->ctx.cli.i0 = 0;
+
+       if (*args[2]) {
+               appctx->ctx.cli.i0 = atoi(args[2]);
+               appctx->st2 = STAT_ST_END;
+       }
+       return 0;
+}
+
 /* parse a "set timeout" CLI request. It always returns 1. */
 static int cli_parse_set_timeout(char **args, struct appctx *appctx, void *private)
 {
@@ -1234,6 +1353,7 @@ static struct cli_kw_list cli_kws = {{ },{
        { { "set", "timeout",  NULL }, "set timeout    : change a timeout setting", cli_parse_set_timeout, NULL, NULL },
        { { "show", "env",  NULL }, "show env [var] : dump environment variables known to the process", cli_parse_show_env, cli_io_handler_show_env, NULL },
        { { "show", "cli", "sockets",  NULL }, "show cli sockets : dump list of cli sockets", cli_parse_default, cli_io_handler_show_cli_sock, NULL },
+       { { "show", "fd", NULL }, "show fd [num] : dump list of file descriptors in use", cli_parse_show_fd, cli_io_handler_show_fd, NULL },
        { { "_getsocks", NULL }, NULL,  _getsocks, NULL },
        {{},}
 }};