]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
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 5a5e093bb43bc6a2baae417d3e33bef84fc60b20..dd604aeb220f65afb1a4590bcba5bf20d6a3094b 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 fef34e97104044e3aa97f2eacae280dabd56b0b2..649881592a713d6d6b65cac9598acbe984e0e27e 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 },
        {{},}
 }};