diff --git a/doc/configuration.txt b/doc/configuration.txt index 4a7c08034..79fe2fa0b 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -6490,27 +6490,35 @@ page. Both means provide a CSV format whose fields follow. ------------------------- The following commands are supported on the UNIX stats socket ; all of them -must be terminated by a line feed. It is important to understand that when -multiple haproxy processes are started on the same sockets, any process may -pick up the request and will output its own stats. +must be terminated by a line feed. The socket supports pipelining, so that it +is possible to chain multiple commands at once provided they are delimited by +a semi-colon or a line feed, although the former is more reliable as it has no +risk of being truncated over the network. The responses themselves will each be +followed by an empty line, so it will be easy for an external script to match a +given response with a given request. By default one command line is processed +then the connection closes, but there is an interactive allowing multiple lines +to be issued one at a time. -show stat [ ] - Dump statistics in the CSV format. By passing , and , it is - possible to dump only selected items : - - is a proxy ID, -1 to dump everything - - selects the type of dumpable objects : 1 for frontends, 2 for - backends, 4 for servers, -1 for everything. These values can be ORed, - for example: - 1 + 2 = 3 -> frontend + backend. - 1 + 2 + 4 = 7 -> frontend + backend + server. - - is a server ID, -1 to dump everything from the selected proxy. +It is important to understand that when multiple haproxy processes are started +on the same sockets, any process may pick up the request and will output its +own stats. -show info - Dump info about haproxy status on current process. +help + Print the list of known keywords and their basic usage. The same help screen + is also displayed for unknown commands. -show sess - Dump all known sessions. Avoid doing this on slow connections as this can - be huge. +prompt + Toggle the prompt at the beginning of the line and enter or leave interactive + mode. In interactive mode, the connection is not closed after a command + completes. Instead, the prompt will appear again, indicating the user that + the interpreter is waiting for a new command. The prompt consists in a right + angle bracket followed by a space "> ". This mode is particularly convenient + when one wants to periodically check information such as stats or errors. + It is also a good idea to enter interactive mode before issuing a "help" + command. + +quit + Close the connection when in interactive mode. show errors [] Dump last known request and response errors collected by frontends and @@ -6563,6 +6571,47 @@ show errors [] is the slash ('/') in header name "header/bizarre", which is not a valid HTTP character for a header name. +show info + Dump info about haproxy status on current process. + +show sess + Dump all known sessions. Avoid doing this on slow connections as this can + be huge. + +show stat [ ] + Dump statistics in the CSV format. By passing , and , it is + possible to dump only selected items : + - is a proxy ID, -1 to dump everything + - selects the type of dumpable objects : 1 for frontends, 2 for + backends, 4 for servers, -1 for everything. These values can be ORed, + for example: + 1 + 2 = 3 -> frontend + backend. + 1 + 2 + 4 = 7 -> frontend + backend + server. + - is a server ID, -1 to dump everything from the selected proxy. + + Example : + >>> $ echo "show info;show stat" | socat stdio unix-connect:/tmp/sock1 + Name: HAProxy + Version: 1.4-dev2-49 + Release_date: 2009/09/23 + Nbproc: 1 + Process_num: 1 + (...) + + # pxname,svname,qcur,qmax,scur,smax,slim,stot,bin,bout,dreq, (...) + stats,FRONTEND,,,0,0,1000,0,0,0,0,0,0,,,,,OPEN,,,,,,,,,1,1,0, (...) + stats,BACKEND,0,0,0,0,1000,0,0,0,0,0,,0,0,0,0,UP,0,0,0,,0,250,(...) + (...) + www1,BACKEND,0,0,0,0,1000,0,0,0,0,0,,0,0,0,0,UP,1,1,0,,0,250, (...) + + $ + + Here, two commands have been issued at once. That way it's easy to find + which process the stats apply to in multi-process mode. Notice the empty + line after the information output which marks the end of the first block. + A similar empty line appears at the end of the second block (stats) so that + the reader knows the output has not been trucated. + /* * Local variables: * fill-column: 79 diff --git a/include/proto/dumpstats.h b/include/proto/dumpstats.h index a0167a7f1..7a92c987d 100644 --- a/include/proto/dumpstats.h +++ b/include/proto/dumpstats.h @@ -3,7 +3,7 @@ This file contains definitions of some primitives to dedicated to statistics output. - Copyright (C) 2000-2008 Willy Tarreau - w@1wt.eu + Copyright (C) 2000-2009 Willy Tarreau - w@1wt.eu This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -44,8 +44,8 @@ #define STATS_ST_REP 2 #define STATS_ST_CLOSE 3 -int stats_sock_parse_request(struct session *s, char *line); -int stats_sock_req_analyser(struct session *s, struct buffer *req, int an_bit); +int stats_sock_parse_request(struct stream_interface *si, char *line); +void stats_io_handler(struct stream_interface *si); int stats_dump_raw(struct session *s, struct buffer *rep, struct uri_auth *uri); void stats_dump_raw_to_buffer(struct session *s, struct buffer *req); int stats_dump_http(struct session *s, struct buffer *rep, struct uri_auth *uri); diff --git a/include/types/buffers.h b/include/types/buffers.h index 14cb966da..c7ac91b52 100644 --- a/include/types/buffers.h +++ b/include/types/buffers.h @@ -138,7 +138,6 @@ #define AN_REQ_HTTP_INNER 0x00000020 /* inner processing of HTTP request */ #define AN_REQ_HTTP_TARPIT 0x00000040 /* wait for end of HTTP tarpit */ #define AN_REQ_HTTP_BODY 0x00000080 /* inspect HTTP request body */ -#define AN_REQ_STATS_SOCK 0x00000100 /* process stats socket request */ #define AN_RTR_HTTP_HDR 0x00000200 /* inspect HTTP response headers */ #define AN_REQ_PRST_RDP_COOKIE 0x00000400 /* persistence on rdp cookie */ diff --git a/src/dumpstats.c b/src/dumpstats.c index 97de0e9f2..390f853c5 100644 --- a/src/dumpstats.c +++ b/src/dumpstats.c @@ -54,11 +54,14 @@ const char stats_sock_usage_msg[] = "Unknown command. Please enter one of the following commands only :\n" + " help : this message\n" + " prompt : toggle interactive mode with prompt\n" + " quit : disconnect\n" " show info : report information about the running process\n" " show stat : report counters for each proxy and server\n" " show errors : report last request and response errors for each proxy\n" " show sess : report the list of current sessions\n" - "\n"; + ""; const struct chunk stats_sock_usage = { .str = (char *)&stats_sock_usage_msg, @@ -122,7 +125,7 @@ static int stats_parse_global(char **args, int section_type, struct proxy *curpx global.stats_sock.options = LI_O_NONE; global.stats_sock.accept = uxst_event_accept; global.stats_sock.handler = process_session; - global.stats_sock.analysers = AN_REQ_STATS_SOCK; + global.stats_sock.analysers = 0; global.stats_sock.nice = -64; /* we want to boost priority for local stats */ global.stats_sock.private = global.stats_fe; /* must point to the frontend */ @@ -228,12 +231,15 @@ int print_csv_header(struct chunk *msg, int size) "\n"); } -/* Parses the request line in and possibly starts dumping stats on - * s->rep with the hijack bit set. Returns 1 if OK, 0 in case of any error. - * The line is modified after parsing. +/* Processes the stats interpreter on the statistics socket. This function is + * called from an applet running in a stream interface. Right now we still + * support older functions which used to emulate servers and to set + * STATS_ST_CLOSE upon completion, but we also support a new interactive mode. + * The function returns 1 if the request was understood, otherwise zero. */ -int stats_sock_parse_request(struct session *s, char *line) +int stats_sock_parse_request(struct stream_interface *si, char *line) { + struct session *s = si->private; char *args[MAX_STATS_ARGS + 1]; int arg; @@ -260,6 +266,8 @@ int stats_sock_parse_request(struct session *s, char *line) while (++arg <= MAX_STATS_ARGS) args[arg] = line; + si->st0 = 0; + s->data_ctx.stats.flags = 0; if (strcmp(args[0], "show") == 0) { if (strcmp(args[1], "stat") == 0) { if (*args[2] && *args[3] && *args[4]) { @@ -271,21 +279,21 @@ int stats_sock_parse_request(struct session *s, char *line) s->data_ctx.stats.flags |= STAT_SHOW_STAT; s->data_ctx.stats.flags |= STAT_FMT_CSV; + s->data_state = DATA_ST_INIT; s->ana_state = STATS_ST_REP; - stream_int_retnclose(s->rep->cons, NULL); - buffer_install_hijacker(s, s->rep, stats_dump_raw_to_buffer); + si->st0 = 3; // stats_dump_raw_to_buffer } else if (strcmp(args[1], "info") == 0) { s->data_ctx.stats.flags |= STAT_SHOW_INFO; s->data_ctx.stats.flags |= STAT_FMT_CSV; + s->data_state = DATA_ST_INIT; s->ana_state = STATS_ST_REP; - stream_int_retnclose(s->rep->cons, NULL); - buffer_install_hijacker(s, s->rep, stats_dump_raw_to_buffer); + si->st0 = 3; // stats_dump_raw_to_buffer } else if (strcmp(args[1], "sess") == 0) { + s->data_state = DATA_ST_INIT; s->ana_state = STATS_ST_REP; - stream_int_retnclose(s->rep->cons, NULL); - buffer_install_hijacker(s, s->rep, stats_dump_sess_to_buffer); + si->st0 = 4; // stats_dump_sess_to_buffer } else if (strcmp(args[1], "errors") == 0) { if (*args[2]) @@ -293,9 +301,9 @@ int stats_sock_parse_request(struct session *s, char *line) else s->data_ctx.errors.iid = -1; s->data_ctx.errors.px = NULL; + s->data_state = DATA_ST_INIT; s->ana_state = STATS_ST_REP; - stream_int_retnclose(s->rep->cons, NULL); - buffer_install_hijacker(s, s->rep, stats_dump_errors_to_buffer); + si->st0 = 5; // stats_dump_errors_to_buffer } else { /* neither "stat" nor "info" nor "sess" */ return 0; @@ -307,70 +315,207 @@ int stats_sock_parse_request(struct session *s, char *line) return 1; } -/* Processes the stats interpreter on the statistics socket. - * In order to ease the transition, we simply simulate the server status - * for now. It only knows states STATS_ST_INIT, STATS_ST_REQ, STATS_ST_REP, and - * STATS_ST_CLOSE. It removes its analyser bit from req->analysers once done. - * It always returns 0. +/* This I/O handler runs as an applet embedded in a stream interface. It is + * used to processes I/O from/to the stats unix socket. Right now we still + * support older functions which used to emulate servers and to set + * STATS_ST_CLOSE upon completion, but we also support a new interactive mode. + * The system relies on a request/response flip-flop state machine. We read + * a request, then we process it and send the response. Then we can read again. + * This could be enhanced a lot but we're still bound to support older output + * functions which were designed to work as hijackers. + * At the moment, we use si->st0 as the output type, and si->st1 to indicate + * whether we're in prompt mode or not. */ -int stats_sock_req_analyser(struct session *s, struct buffer *req, int an_bit) +void stats_io_handler(struct stream_interface *si) { - char *line, *p; + struct session *s = si->private; + struct buffer *req = si->ob; + struct buffer *res = si->ib; + int reql; + int len; - switch (s->ana_state) { - case STATS_ST_INIT: - /* Stats output not initialized yet */ - memset(&s->data_ctx.stats, 0, sizeof(s->data_ctx.stats)); - s->data_source = DATA_SRC_STATS; - s->ana_state = STATS_ST_REQ; - buffer_dont_connect(s->req); - /* fall through */ + if (unlikely(si->state == SI_ST_DIS || si->state == SI_ST_CLO)) + goto out; - case STATS_ST_REQ: - /* Now, stats are initialized, hijack is not set, and - * we are waiting for a complete request line. - */ - - line = s->req->data; - p = memchr(line, '\n', s->req->l); - - if (p) { - *p = '\0'; - if (!stats_sock_parse_request(s, line)) { - /* invalid request */ - stream_int_retnclose(s->req->prod, &stats_sock_usage); - s->ana_state = 0; - req->analysers = 0; - return 0; + while (1) { + if (s->ana_state == STATS_ST_INIT) { + /* Stats output not initialized yet */ + memset(&s->data_ctx.stats, 0, sizeof(s->data_ctx.stats)); + s->data_source = DATA_SRC_STATS; + s->ana_state = STATS_ST_REQ; + } + else if (s->ana_state == STATS_ST_REQ) { + reql = buffer_si_peekline(si->ob, trash, sizeof(trash)); + if (reql <= 0) { /* closed or EOL not found */ + if (reql == 0) + break; + s->ana_state = STATS_ST_CLOSE; + continue; } - } - /* processing a valid or incomplete request */ - if ((req->flags & BF_FULL) || /* invalid request */ - (req->flags & BF_READ_ERROR) || /* input error */ - (req->flags & BF_READ_TIMEOUT) || /* read timeout */ - tick_is_expired(req->analyse_exp, now_ms) || /* request timeout */ - (req->flags & BF_SHUTR)) { /* input closed */ - buffer_shutw_now(s->rep); + /* seek for a possible semi-colon. If we find one, we + * replace it with an LF and skip only this part. + */ + for (len = 0; len < reql; len++) + if (trash[len] == ';') { + trash[len] = '\n'; + reql = len + 1; + break; + } + + /* ensure we have a full line */ + if (trash[reql-1] != '\n') { + s->ana_state = STATS_ST_CLOSE; + continue; + } + + len = reql - 1; + trash[len] = '\0'; + + si->st0 = 1; // default to prompt + if (len) { + if (strcmp(trash, "quit") == 0) { + s->ana_state = STATS_ST_CLOSE; + continue; + } + else if (strcmp(trash, "prompt") == 0) + si->st1 = !si->st1; + else if (strcmp(trash, "help") == 0 || + !stats_sock_parse_request(si, trash)) + si->st0 = 2; // help + } + else if (!si->st1) { + /* if prompt is disabled, print help on empty lines, + * so that the user at least knows how to enable + * prompt and find help. + */ + si->st0 = 2; + } + + /* re-adjust req buffer */ + buffer_skip(si->ob, reql); + + s->ana_state = STATS_ST_REP; + req->flags |= BF_READ_DONTWAIT; /* we plan to read small requests */ + } + else if (s->ana_state == STATS_ST_REP) { + if (res->flags & (BF_SHUTR_NOW|BF_SHUTR)) { + s->ana_state = STATS_ST_CLOSE; + continue; + } + + switch (si->st0) { + case 2: /* help */ + if (buffer_feed(si->ib, stats_sock_usage.str, stats_sock_usage.len) < 0) + /* message sent or too large for buffer (!) */ + si->st0 = 1; // send prompt + break; + case 3: /* stats/info dump, should be split later ? */ + stats_dump_raw_to_buffer(s, res); + si->ib->flags |= BF_READ_PARTIAL; /* remove this once we use buffer_feed */ + if (s->ana_state == STATS_ST_CLOSE) + si->st0 = 1; // end of command, send prompt + break; + case 4: /* sessions dump */ + stats_dump_sess_to_buffer(s, res); + si->ib->flags |= BF_READ_PARTIAL; /* remove this once we use buffer_feed */ + if (s->ana_state == STATS_ST_CLOSE) + si->st0 = 1; // end of command, send prompt + break; + case 5: /* errors dump */ + stats_dump_errors_to_buffer(s, res); + si->ib->flags |= BF_READ_PARTIAL; /* remove this once we use buffer_feed */ + if (s->ana_state == STATS_ST_CLOSE) + si->st0 = 1; // end of command, send prompt + break; + default: /* abnormal state or lack of space for prompt */ + si->st0 = 1; // return to prompt + break; + } + + if (si->st0 == 1) { /* post-command prompt (LF or LF + '> ') */ + if (!si->st1) { /* non-interactive mode */ + if (buffer_feed(si->ib, "\n", 1) < 0) + si->st0 = 0; // end of output + } + else { /* interactive mode */ + if (buffer_feed(si->ib, "\n> ", 3) < 0) + si->st0 = 0; // end of output + } + } + + /* If the output functions are still there, it means + * they require more room. + */ + if (si->st0 > 0) { + s->ana_state = STATS_ST_REP; /* some old applets still force CLOSE */ + si->flags |= SI_FL_WAIT_ROOM; + break; + } + + /* Now we close the output if one of the writers did so, + * or if we're not in interactive mode and the request + * buffer is empty. This still allows pipelined requests + * to be sent in non-interactive mode. + */ + if ((res->flags & (BF_SHUTW|BF_SHUTW_NOW)) || (!si->st1 && !req->send_max)) { + s->ana_state = STATS_ST_CLOSE; + continue; + } + + /* switch state back to ST_REQ to read next requests */ + s->ana_state = STATS_ST_REQ; + } + else if (s->ana_state == STATS_ST_CLOSE) { + /* let's close for real now. Note that we may as well + * call shutw+shutr, but this is enough since the shut + * conditions below will complete. + */ + buffer_shutw(si->ob); s->ana_state = 0; - req->analysers = 0; - return 0; + break; } - /* don't forward nor abort */ - req->flags |= BF_READ_DONTWAIT; /* we plan to read small requests */ - return 0; - - case STATS_ST_REP: - /* do nothing while response is being processed */ - return 0; - - case STATS_ST_CLOSE: - /* end of dump */ - s->req->analysers &= ~an_bit; - s->ana_state = 0; - break; } - return 0; + + if ((res->flags & BF_SHUTR) && (si->state == SI_ST_EST) && (s->ana_state != STATS_ST_REQ)) { + DPRINTF(stderr, "%s@%d: si to buf closed. req=%08x, res=%08x, st=%d\n", + __FUNCTION__, __LINE__, req->flags, res->flags, si->state); + /* Other size has closed, let's abort if we have no more processing to do + * and nothing more to consume. This is comparable to a broken pipe, so + * we forward the close to the request side so that it flows upstream to + * the client. + */ + si->shutw(si); + } + + if ((req->flags & BF_SHUTW) && (si->state == SI_ST_EST) && (s->ana_state != STATS_ST_REP)) { + DPRINTF(stderr, "%s@%d: buf to si closed. req=%08x, res=%08x, st=%d\n", + __FUNCTION__, __LINE__, req->flags, res->flags, si->state); + /* We have no more processing to do, and nothing more to send, and + * the client side has closed. So we'll forward this state downstream + * on the response buffer. + */ + si->shutr(si); + res->flags |= BF_READ_NULL; + } + + /* update all other flags and resync with the other side */ + si->update(si); + + /* we don't want to expire timeouts while we're processing requests */ + si->ib->rex = TICK_ETERNITY; + si->ob->wex = TICK_ETERNITY; + + out: + DPRINTF(stderr, "%s@%d: st=%d, rqf=%x, rpf=%x, rql=%d, rqs=%d, rl=%d, rs=%d\n", + __FUNCTION__, __LINE__, + si->state, req->flags, res->flags, req->l, req->send_max, res->l, res->send_max); + + if (unlikely(si->state == SI_ST_DIS || si->state == SI_ST_CLO)) { + /* check that we have released everything then unregister */ + stream_int_unregister_handler(si); + s->ana_state = 0; + } } /* diff --git a/src/proto_uxst.c b/src/proto_uxst.c index 686d85429..695f407ea 100644 --- a/src/proto_uxst.c +++ b/src/proto_uxst.c @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -460,16 +461,12 @@ int uxst_event_accept(int fd) { s->si[1].err_type = SI_ET_NONE; s->si[1].err_loc = NULL; s->si[1].owner = t; - s->si[1].update = stream_sock_data_finish; - s->si[1].shutr = stream_sock_shutr; - s->si[1].shutw = stream_sock_shutw; - s->si[1].chk_rcv = stream_sock_chk_rcv; - s->si[1].chk_snd = stream_sock_chk_snd; - s->si[1].connect = NULL; - s->si[1].iohandler = NULL; s->si[1].exp = TICK_ETERNITY; s->si[1].fd = -1; /* just to help with debugging */ s->si[1].flags = SI_FL_NONE; + stream_int_register_handler(&s->si[1], stats_io_handler); + s->si[1].private = s; + s->si[1].st0 = s->si[1].st1 = 0; s->srv = s->prev_srv = s->srv_conn = NULL; s->pend_pos = NULL; diff --git a/src/session.c b/src/session.c index f9b715794..e359eb1aa 100644 --- a/src/session.c +++ b/src/session.c @@ -847,12 +847,6 @@ resync_stream_interface: break; } - if (s->req->analysers & AN_REQ_STATS_SOCK) { - last_ana |= AN_REQ_STATS_SOCK; - if (!stats_sock_req_analyser(s, s->req, AN_REQ_STATS_SOCK)) - break; - } - if (s->req->analysers & AN_REQ_PRST_RDP_COOKIE) { last_ana |= AN_REQ_PRST_RDP_COOKIE; if (!tcp_persist_rdp_cookie(s, s->req, AN_REQ_PRST_RDP_COOKIE))