]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
[MEDIUM] fix stats socket limitation to 16 kB
authorWilly Tarreau <w@1wt.eu>
Mon, 17 Mar 2008 20:38:24 +0000 (21:38 +0100)
committerWilly Tarreau <w@1wt.eu>
Mon, 17 Mar 2008 21:08:01 +0000 (22:08 +0100)
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.

include/proto/dumpstats.h
include/types/session.h
src/dumpstats.c
src/proto_http.c
src/proto_uxst.c

index 362650567162151c8a492a962819c4980708fb79..6cca84fd657504a8c7c5d3dde8acb7aeca11e979 100644 (file)
 #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 */
index 18d5115f0a285754fd72562d00d492cf975b290d..45e38fd833e81a7dc96415ea90aabafd46f8147f 100644 (file)
@@ -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
 #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 session {
                        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 */
index 99ac770f61abdd852d9fc2c785de7713ba97ab81..a9fb84dc768f6e9908d9804321ad30d6f0f65583 100644 (file)
@@ -177,12 +177,12 @@ int print_csv_header(struct chunk *msg, int size)
 
 /*
  * 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 @@ int stats_dump_raw(struct session *s, struct uri_auth *uri, int flags)
                /* 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 @@ int stats_dump_raw(struct session *s, struct uri_auth *uri, int flags)
 
        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 @@ int stats_dump_raw(struct session *s, struct uri_auth *uri, int flags)
 
        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 @@ int stats_dump_raw(struct session *s, struct uri_auth *uri, int flags)
  * 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 @@ int stats_dump_http(struct session *s, struct uri_auth *uri, int flags)
                             "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 @@ int stats_dump_http(struct session *s, struct uri_auth *uri, int flags)
                /* 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 @@ int stats_dump_http(struct session *s, struct uri_auth *uri, int flags)
                         * 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 @@ int stats_dump_http(struct session *s, struct uri_auth *uri, int flags)
                             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 @@ int stats_dump_http(struct session *s, struct uri_auth *uri, int flags)
                                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 @@ int stats_dump_http(struct session *s, struct uri_auth *uri, int flags)
                        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 @@ int stats_dump_http(struct session *s, struct uri_auth *uri, int flags)
                /* 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 @@ int stats_dump_http(struct session *s, struct uri_auth *uri, int flags)
  * 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 @@ int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri,
                                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 @@ int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri,
                /* 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 @@ int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri,
 
        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 @@ int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri,
 
                        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 @@ int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri,
                                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 &uarr;",
                                                               "UP %d/%d &darr;", "UP",
                                                               "NOLB %d/%d &darr;", "NOLB",
@@ -952,8 +951,9 @@ int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri,
 
        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 @@ int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri,
                /* 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)
index d83e601c8cddc7e2d919953b9d047ae6669e4ab8..ba79c2b6c3d8268b5e13f471c0c0ec4e66be052f 100644 (file)
@@ -3557,8 +3557,7 @@ int produce_content(struct session *s)
        }
        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 stats_check_uri_auth(struct session *t, struct proxy *backend)
        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 @@ int stats_check_uri_auth(struct session *t, struct proxy *backend)
        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 @@ int stats_check_uri_auth(struct session *t, struct proxy *backend)
                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 @@ int stats_check_uri_auth(struct session *t, struct proxy *backend)
        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 @@ int stats_check_uri_auth(struct session *t, struct proxy *backend)
                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;
index 76709c1372caf2e6370d438ce44b81381271fd55..556649c358147c032ab9c13cbe1b737f9ea2a37b 100644 (file)
@@ -1356,38 +1356,39 @@ void process_uxst_session(struct task *t, struct timeval *next)
 /* 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 @@ void process_uxst_stats(struct task *t, struct timeval *next)
                        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 @@ void process_uxst_stats(struct task *t, struct timeval *next)
                        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);