[MEDIUM] fix stats socket limitation to 16 kB
Due to the way the stats socket work, it was not possible to
maintain the information related to the command entered, so
after filling a whole buffer, the request was lost and it was
considered that there was nothing to write anymore.
The major reason was that some flags were passed directly
during the first call to stats_dump_raw() instead of being
stored persistently in the session.
To definitely fix this problem, flags were added to the stats
member of the session structure.
A second problem appeared. When the stats were produced, a first
call to client_retnclose() was performed, then one or multiple
subsequent calls to buffer_write_chunks() were done. But once the
stats buffer was full and a reschedule operated, the buffer was
flushed, the write flag cleared from the buffer and nothing was
done to re-arm it.
For this reason, a check was added in the proto_uxst_stats()
function in order to re-call the client FSM when data were added
by stats_dump_raw(). Finally, the whole unix stats dump FSM was
rewritten to avoid all the magics it depended on. It is now
simpler and looks more like the HTTP one.
diff --git a/include/proto/dumpstats.h b/include/proto/dumpstats.h
index 3626505..6cca84f 100644
--- a/include/proto/dumpstats.h
+++ b/include/proto/dumpstats.h
@@ -27,18 +27,22 @@
#include <types/buffers.h>
#include <types/session.h>
-#define STAT_FMT_HTML 0x1
-#define STAT_SHOW_STAT 0x2
-#define STAT_SHOW_INFO 0x4
+/* Flags for session->data_ctx.stats.flags */
+#define STAT_FMT_CSV 0x00000001 /* dump the stats in CSV format instead of HTML */
+#define STAT_SHOW_STAT 0x00000002 /* dump the stats part */
+#define STAT_SHOW_INFO 0x00000004 /* dump the info part */
+#define STAT_HIDE_DOWN 0x00000008 /* hide 'down' servers in the stats page */
+#define STAT_NO_REFRESH 0x00000010 /* do not automatically refresh the stats page */
+#define STAT_BOUND 0x00800000 /* bound statistics to selected proxies/types/services */
#define STATS_TYPE_FE 0
#define STATS_TYPE_BE 1
#define STATS_TYPE_SV 2
int stats_parse_global(const char **args, char *err, int errlen);
-int stats_dump_raw(struct session *s, struct uri_auth *uri, int flags);
-int stats_dump_http(struct session *s, struct uri_auth *uri, int flags);
-int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri, int flags);
+int stats_dump_raw(struct session *s, struct uri_auth *uri);
+int stats_dump_http(struct session *s, struct uri_auth *uri);
+int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri);
#endif /* _PROTO_DUMPSTATS_H */
diff --git a/include/types/session.h b/include/types/session.h
index 18d5115..45e38fd 100644
--- a/include/types/session.h
+++ b/include/types/session.h
@@ -2,7 +2,7 @@
include/types/session.h
This file defines everything related to sessions.
- Copyright (C) 2000-2007 Willy Tarreau - w@1wt.eu
+ Copyright (C) 2000-2008 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
@@ -78,13 +78,6 @@
#define SN_FINST_SHIFT 16 /* bit shift */
/* unused: 0x00080000 */
-/* Note: those flags must move to another place */
-#define SN_STAT_HIDEDWN 0x00100000 /* hide 'down' servers in the stats page */
-#define SN_STAT_NORFRSH 0x00200000 /* do not automatically refresh the stats page */
-#define SN_STAT_FMTCSV 0x00400000 /* dump the stats in CSV format instead of HTML */
-#define SN_STAT_BOUND 0x00800000 /* bound statistics to selected proxies/types/services */
-
-
/* WARNING: if new fields are added, they must be initialized in event_accept()
* and freed in session_free() !
*/
@@ -127,6 +120,7 @@
struct proxy *px;
struct server *sv;
short px_st, sv_st; /* DATA_ST_INIT or DATA_ST_DATA */
+ unsigned int flags; /* STAT_* */
int iid, type, sid; /* proxy id, type and service id if bounding of stats is enabled */
} stats;
} data_ctx; /* used by produce_content to dump the stats right now */
diff --git a/src/dumpstats.c b/src/dumpstats.c
index 99ac770..a9fb84d 100644
--- a/src/dumpstats.c
+++ b/src/dumpstats.c
@@ -177,12 +177,12 @@
/*
* Produces statistics data for the session <s>. Expects to be called with
- * s->cli_state == CL_STSHUTR. It *may* make use of informations from <uri>
- * and <flags>.
+ * s->cli_state == CL_STSHUTR. It *may* make use of informations from <uri>.
+ * s->data_ctx must have been zeroed first, and the flags properly set.
* It returns 0 if it had to stop writing data and an I/O is needed, 1 if the
* dump is finished and the session must be closed, or -1 in case of any error.
*/
-int stats_dump_raw(struct session *s, struct uri_auth *uri, int flags)
+int stats_dump_raw(struct session *s, struct uri_auth *uri)
{
struct buffer *rep = s->rep;
struct proxy *px;
@@ -202,7 +202,7 @@
/* fall through */
case DATA_ST_HEAD:
- if (flags & STAT_SHOW_STAT) {
+ if (s->data_ctx.stats.flags & STAT_SHOW_STAT) {
print_csv_header(&msg, sizeof(trash));
if (buffer_write_chunk(rep, &msg) != 0)
return 0;
@@ -213,8 +213,7 @@
case DATA_ST_INFO:
up = (now.tv_sec - start_date.tv_sec);
-
- if (flags & STAT_SHOW_INFO) {
+ if (s->data_ctx.stats.flags & STAT_SHOW_INFO) {
chunk_printf(&msg, sizeof(trash),
"Name: " PRODUCT_NAME "\n"
"Version: " HAPROXY_VERSION "\n"
@@ -256,13 +255,13 @@
case DATA_ST_LIST:
/* dump proxies */
- if (flags & STAT_SHOW_STAT) {
+ if (s->data_ctx.stats.flags & STAT_SHOW_STAT) {
while (s->data_ctx.stats.px) {
px = s->data_ctx.stats.px;
/* skip the disabled proxies and non-networked ones */
if (px->state != PR_STSTOPPED &&
(px->cap & (PR_CAP_FE | PR_CAP_BE)))
- if (stats_dump_proxy(s, px, NULL, 0) == 0)
+ if (stats_dump_proxy(s, px, NULL) == 0)
return 0;
s->data_ctx.stats.px = px->next;
@@ -293,10 +292,11 @@
* s->cli_state == CL_STSHUTR. It stops by itself by unsetting the SN_SELF_GEN
* flag from the session, which it uses to keep on being called when there is
* free space in the buffer, of simply by letting an empty buffer upon return.
+ * s->data_ctx must have been zeroed before the first call, and the flags set.
* It returns 0 if it had to stop writing data and an I/O is needed, 1 if the
* dump is finished and the session must be closed, or -1 in case of any error.
*/
-int stats_dump_http(struct session *s, struct uri_auth *uri, int flags)
+int stats_dump_http(struct session *s, struct uri_auth *uri)
{
struct buffer *rep = s->rep;
struct proxy *px;
@@ -316,9 +316,9 @@
"Cache-Control: no-cache\r\n"
"Connection: close\r\n"
"Content-Type: %s\r\n",
- (flags & STAT_FMT_HTML) ? "text/html" : "text/plain");
+ (s->data_ctx.stats.flags & STAT_FMT_CSV) ? "text/plain" : "text/html");
- if (uri->refresh > 0 && !(s->flags & SN_STAT_NORFRSH))
+ if (uri->refresh > 0 && !(s->data_ctx.stats.flags & STAT_NO_REFRESH))
chunk_printf(&msg, sizeof(trash), "Refresh: %d\r\n",
uri->refresh);
@@ -344,7 +344,7 @@
/* fall through */
case DATA_ST_HEAD:
- if (flags & STAT_FMT_HTML) {
+ if (!(s->data_ctx.stats.flags & STAT_FMT_CSV)) {
/* WARNING! This must fit in the first buffer !!! */
chunk_printf(&msg, sizeof(trash),
"<html><head><title>Statistics Report for " PRODUCT_NAME "</title>\n"
@@ -435,7 +435,7 @@
* We are around 3.5 kB, add adding entries will
* become tricky if we want to support 4kB buffers !
*/
- if (flags & STAT_FMT_HTML) {
+ if (!(s->data_ctx.stats.flags & STAT_FMT_CSV)) {
chunk_printf(&msg, sizeof(trash),
"<body><h1><a href=\"" PRODUCT_URL "\" style=\"text-decoration: none;\">"
PRODUCT_NAME "%s</a></h1>\n"
@@ -480,39 +480,39 @@
actconn
);
- if (s->flags & SN_STAT_HIDEDWN)
+ if (s->data_ctx.stats.flags & STAT_HIDE_DOWN)
chunk_printf(&msg, sizeof(trash),
"<li><a href=\"%s%s%s\">Show all servers</a><br>\n",
uri->uri_prefix,
"",
- (s->flags & SN_STAT_NORFRSH) ? ";norefresh" : "");
+ (s->data_ctx.stats.flags & STAT_NO_REFRESH) ? ";norefresh" : "");
else
chunk_printf(&msg, sizeof(trash),
"<li><a href=\"%s%s%s\">Hide 'DOWN' servers</a><br>\n",
uri->uri_prefix,
";up",
- (s->flags & SN_STAT_NORFRSH) ? ";norefresh" : "");
+ (s->data_ctx.stats.flags & STAT_NO_REFRESH) ? ";norefresh" : "");
if (uri->refresh > 0) {
- if (s->flags & SN_STAT_NORFRSH)
+ if (s->data_ctx.stats.flags & STAT_NO_REFRESH)
chunk_printf(&msg, sizeof(trash),
"<li><a href=\"%s%s%s\">Enable refresh</a><br>\n",
uri->uri_prefix,
- (s->flags & SN_STAT_HIDEDWN) ? ";up" : "",
+ (s->data_ctx.stats.flags & STAT_HIDE_DOWN) ? ";up" : "",
"");
else
chunk_printf(&msg, sizeof(trash),
"<li><a href=\"%s%s%s\">Disable refresh</a><br>\n",
uri->uri_prefix,
- (s->flags & SN_STAT_HIDEDWN) ? ";up" : "",
+ (s->data_ctx.stats.flags & STAT_HIDE_DOWN) ? ";up" : "",
";norefresh");
}
chunk_printf(&msg, sizeof(trash),
"<li><a href=\"%s%s%s\">Refresh now</a><br>\n",
uri->uri_prefix,
- (s->flags & SN_STAT_HIDEDWN) ? ";up" : "",
- (s->flags & SN_STAT_NORFRSH) ? ";norefresh" : "");
+ (s->data_ctx.stats.flags & STAT_HIDE_DOWN) ? ";up" : "",
+ (s->data_ctx.stats.flags & STAT_NO_REFRESH) ? ";norefresh" : "");
chunk_printf(&msg, sizeof(trash),
"<li><a href=\"%s;csv%s\">CSV export</a><br>\n",
@@ -536,8 +536,6 @@
return 0;
}
- memset(&s->data_ctx, 0, sizeof(s->data_ctx));
-
s->data_ctx.stats.px = proxy;
s->data_ctx.stats.px_st = DATA_ST_PX_INIT;
s->data_state = DATA_ST_LIST;
@@ -549,7 +547,7 @@
px = s->data_ctx.stats.px;
/* skip the disabled proxies and non-networked ones */
if (px->state != PR_STSTOPPED && (px->cap & (PR_CAP_FE | PR_CAP_BE)))
- if (stats_dump_proxy(s, px, uri, flags) == 0)
+ if (stats_dump_proxy(s, px, uri) == 0)
return 0;
s->data_ctx.stats.px = px->next;
@@ -561,7 +559,7 @@
/* fall through */
case DATA_ST_END:
- if (flags & STAT_FMT_HTML) {
+ if (!(s->data_ctx.stats.flags & STAT_FMT_CSV)) {
chunk_printf(&msg, sizeof(trash), "</body></html>\n");
if (buffer_write_chunk(rep, &msg) != 0)
return 0;
@@ -587,7 +585,7 @@
* Returns 0 if it had to stop dumping data because of lack of buffer space,
* ot non-zero if everything completed.
*/
-int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri, int flags)
+int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri)
{
struct buffer *rep = s->rep;
struct server *sv, *svs; /* server and server-state, server-state=server or server->tracked */
@@ -624,7 +622,7 @@
return 1;
}
- if ((s->flags & SN_STAT_BOUND) && (s->data_ctx.stats.iid != -1) &&
+ if ((s->data_ctx.stats.flags & STAT_BOUND) && (s->data_ctx.stats.iid != -1) &&
(px->uuid != s->data_ctx.stats.iid))
return 1;
@@ -632,7 +630,7 @@
/* fall through */
case DATA_ST_PX_TH:
- if (flags & STAT_FMT_HTML) {
+ if (!(s->data_ctx.stats.flags & STAT_FMT_CSV)) {
/* print a new table */
chunk_printf(&msg, sizeof(trash),
"<table cols=\"26\" class=\"tbl\" width=\"100%%\">\n"
@@ -667,8 +665,9 @@
case DATA_ST_PX_FE:
/* print the frontend */
- if ((px->cap & PR_CAP_FE) && (!(s->flags & SN_STAT_BOUND) || (s->data_ctx.stats.type & (1 << STATS_TYPE_FE)))) {
- if (flags & STAT_FMT_HTML) {
+ if ((px->cap & PR_CAP_FE) &&
+ (!(s->data_ctx.stats.flags & STAT_BOUND) || (s->data_ctx.stats.type & (1 << STATS_TYPE_FE)))) {
+ if (!(s->data_ctx.stats.flags & STAT_FMT_CSV)) {
chunk_printf(&msg, sizeof(trash),
/* name, queue */
"<tr align=center class=\"frontend\"><td>Frontend</td><td colspan=3></td>"
@@ -742,7 +741,7 @@
sv = s->data_ctx.stats.sv;
- if (s->flags & SN_STAT_BOUND) {
+ if (s->data_ctx.stats.flags & STAT_BOUND) {
if (!(s->data_ctx.stats.type & (1 << STATS_TYPE_SV)))
break;
@@ -773,13 +772,13 @@
else
sv_state = 0; /* DOWN */
- if ((sv_state == 0) && (s->flags & SN_STAT_HIDEDWN)) {
+ if ((sv_state == 0) && (s->data_ctx.stats.flags & STAT_HIDE_DOWN)) {
/* do not report servers which are DOWN */
s->data_ctx.stats.sv = sv->next;
continue;
}
- if (flags & STAT_FMT_HTML) {
+ if (!(s->data_ctx.stats.flags & STAT_FMT_CSV)) {
static char *srv_hlt_st[7] = { "DOWN", "DN %d/%d ↑",
"UP %d/%d ↓", "UP",
"NOLB %d/%d ↓", "NOLB",
@@ -952,8 +951,9 @@
case DATA_ST_PX_BE:
/* print the backend */
- if ((px->cap & PR_CAP_BE) && (!(s->flags & SN_STAT_BOUND) || (s->data_ctx.stats.type & (1 << STATS_TYPE_BE)))) {
- if (flags & STAT_FMT_HTML) {
+ if ((px->cap & PR_CAP_BE) &&
+ (!(s->data_ctx.stats.flags & STAT_BOUND) || (s->data_ctx.stats.type & (1 << STATS_TYPE_BE)))) {
+ if (!(s->data_ctx.stats.flags & STAT_FMT_CSV)) {
chunk_printf(&msg, sizeof(trash),
/* name */
"<tr align=center class=\"backend\"><td>Backend</td>"
@@ -1047,7 +1047,7 @@
/* fall through */
case DATA_ST_PX_END:
- if (flags & STAT_FMT_HTML) {
+ if (!(s->data_ctx.stats.flags & STAT_FMT_CSV)) {
chunk_printf(&msg, sizeof(trash), "</table><p>\n");
if (buffer_write_chunk(rep, &msg) != 0)
diff --git a/src/proto_http.c b/src/proto_http.c
index d83e601..ba79c2b 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -3557,8 +3557,7 @@
}
else if (s->data_source == DATA_SRC_STATS) {
/* dump server statistics */
- int ret = stats_dump_http(s, s->be->uri_auth,
- (s->flags & SN_STAT_FMTCSV) ? 0 : STAT_FMT_HTML);
+ int ret = stats_dump_http(s, s->be->uri_auth);
if (ret >= 0)
return ret;
/* -1 indicates an error */
@@ -4828,6 +4827,8 @@
int authenticated, cur_idx;
char *h;
+ memset(&t->data_ctx.stats, 0, sizeof(t->data_ctx.stats));
+
/* check URI size */
if (uri_auth->uri_len > txn->req.sl.rq.u_l)
return 0;
@@ -4841,7 +4842,7 @@
h += uri_auth->uri_len;
while (h <= t->req->data + txn->req.sl.rq.u + txn->req.sl.rq.u_l - 3) {
if (memcmp(h, ";up", 3) == 0) {
- t->flags |= SN_STAT_HIDEDWN;
+ t->data_ctx.stats.flags |= STAT_HIDE_DOWN;
break;
}
h++;
@@ -4851,7 +4852,7 @@
h = t->req->data + txn->req.sl.rq.u + uri_auth->uri_len;
while (h <= t->req->data + txn->req.sl.rq.u + txn->req.sl.rq.u_l - 10) {
if (memcmp(h, ";norefresh", 10) == 0) {
- t->flags |= SN_STAT_NORFRSH;
+ t->data_ctx.stats.flags |= STAT_NO_REFRESH;
break;
}
h++;
@@ -4861,12 +4862,14 @@
h = t->req->data + txn->req.sl.rq.u + uri_auth->uri_len;
while (h <= t->req->data + txn->req.sl.rq.u + txn->req.sl.rq.u_l - 4) {
if (memcmp(h, ";csv", 4) == 0) {
- t->flags |= SN_STAT_FMTCSV;
+ t->data_ctx.stats.flags |= STAT_FMT_CSV;
break;
}
h++;
}
+ t->data_ctx.stats.flags |= STAT_SHOW_STAT | STAT_SHOW_INFO;
+
/* we are in front of a interceptable URI. Let's check
* if there's an authentication and if it's valid.
*/
@@ -4925,7 +4928,7 @@
return 1;
}
- /* The request is valid, the user is authenticate. Let's start sending
+ /* The request is valid, the user is authenticated. Let's start sending
* data.
*/
t->cli_state = CL_STSHUTR;
diff --git a/src/proto_uxst.c b/src/proto_uxst.c
index 76709c1..556649c 100644
--- a/src/proto_uxst.c
+++ b/src/proto_uxst.c
@@ -1356,38 +1356,39 @@
/* Processes data exchanges on the statistics socket. The client processing
* is called and the task is put back in the wait queue or it is cleared.
* In order to ease the transition, we simply simulate the server status
- * for now. It only knows states SV_STIDLE, SV_STDATA and SV_STCLOSE. Returns
- * in <next> the task's expiration date.
+ * for now. It only knows states SV_STIDLE, SV_STCONN, SV_STDATA, and
+ * SV_STCLOSE. Returns in <next> the task's expiration date.
*/
void process_uxst_stats(struct task *t, struct timeval *next)
{
struct session *s = t->context;
struct listener *listener;
int fsm_resync = 0;
+ int last_rep_l;
- /* we need to be in DATA phase on the "server" side */
- if (s->srv_state == SV_STIDLE) {
- s->srv_state = SV_STDATA;
- s->data_source = DATA_SRC_STATS;
- }
-
do {
+ char *args[MAX_UXST_ARGS + 1];
+ char *line, *p;
+ int arg;
+
fsm_resync = process_uxst_cli(s);
- if (s->srv_state != SV_STDATA)
- continue;
if (s->cli_state == CL_STCLOSE || s->cli_state == CL_STSHUTW) {
s->srv_state = SV_STCLOSE;
- fsm_resync |= 1;
- continue;
+ break;
}
- if (s->data_state == DATA_ST_INIT) {
-
- char *args[MAX_UXST_ARGS + 1];
- char *line, *p;
- int arg;
+ switch (s->srv_state) {
+ case SV_STIDLE:
+ /* stats output not initialized yet */
+ memset(&s->data_ctx.stats, 0, sizeof(s->data_ctx.stats));
+ s->data_source = DATA_SRC_STATS;
+ s->srv_state = SV_STCONN;
+ fsm_resync |= 1;
+ break;
+ case SV_STCONN: /* will change to SV_STANALYZE */
+ /* stats initialized, but waiting for the command */
line = s->req->data;
p = memchr(line, '\n', s->req->l);
@@ -1422,28 +1423,24 @@
if (!strcmp(args[0], "show")) {
if (!strcmp(args[1], "stat")) {
if (*args[2] && *args[3] && *args[4]) {
- s->flags |= SN_STAT_BOUND;
+ s->data_ctx.stats.flags |= STAT_BOUND;
s->data_ctx.stats.iid = atoi(args[2]);
s->data_ctx.stats.type = atoi(args[3]);
s->data_ctx.stats.sid = atoi(args[4]);
}
- /* send the stats, and changes the data_state */
- if (stats_dump_raw(s, NULL, STAT_SHOW_STAT) != 0) {
- s->srv_state = SV_STCLOSE;
- fsm_resync |= 1;
- }
-
+ s->data_ctx.stats.flags |= STAT_SHOW_STAT;
+ s->data_ctx.stats.flags |= STAT_FMT_CSV;
+ s->srv_state = SV_STDATA;
+ fsm_resync |= 1;
continue;
}
if (!strcmp(args[1], "info")) {
- /* send the stats, and changes the data_state */
- if (stats_dump_raw(s, NULL, STAT_SHOW_INFO) != 0) {
- s->srv_state = SV_STCLOSE;
- fsm_resync |= 1;
- }
-
+ s->data_ctx.stats.flags |= STAT_SHOW_INFO;
+ s->data_ctx.stats.flags |= STAT_FMT_CSV;
+ s->srv_state = SV_STDATA;
+ fsm_resync |= 1;
continue;
}
}
@@ -1451,16 +1448,21 @@
s->srv_state = SV_STCLOSE;
fsm_resync |= 1;
continue;
- }
- /* OK we have some remaining data to process. Just for the
- * sake of an exercice, we copy the req into the resp,
- * and flush the req. This produces a simple echo function.
- */
- if (stats_dump_raw(s, NULL, 0) != 0) {
- s->srv_state = SV_STCLOSE;
- fsm_resync |= 1;
- continue;
+ case SV_STDATA:
+ /* OK we have to process the request. Since it is possible
+ * that we get there with the client output paused, we
+ * will simply check that we have really sent some data
+ * and wake the client up if needed.
+ */
+ last_rep_l = s->rep->l;
+ if (stats_dump_raw(s, NULL) != 0) {
+ s->srv_state = SV_STCLOSE;
+ fsm_resync |= 1;
+ }
+ if (s->rep->l != last_rep_l)
+ fsm_resync |= 1;
+ break;
}
} while (fsm_resync);