MINOR: cli: add a new "show fd" command

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.
This commit is contained in:
Willy Tarreau 2017-07-25 19:32:50 +02:00
parent 6d0d3f6546
commit 7a4a0ac71d
2 changed files with 139 additions and 0 deletions

View 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

120
src/cli.c
View File

@ -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 },
{{},}
}};