REORG: stats: massive code reorg and cleanup

The dumpstats code looks like a spaghetti plate. Several functions are
supposed to be able to do several things but rely on complex states to
dispatch the work to independant functions. Most of the HTML output is
performed within the switch/case statements of the whole state machine.

Let's clean this up by adding new functions to emit the data and have
a few more iterators to avoid relying on so complex states.

The new stats dump sequence looks like this for CLI and for HTTP :

  cli_io_handler()
      -> stats_dump_sess_to_buffer()      // "show sess"
      -> stats_dump_errors_to_buffer()    // "show errors"
      -> stats_dump_raw_info_to_buffer()  // "show info"
         -> stats_dump_raw_info()
      -> stats_dump_raw_stat_to_buffer()  // "show stat"
         -> stats_dump_csv_header()
         -> stats_dump_proxy()
            -> stats_dump_px_hdr()
            -> stats_dump_fe_stats()
            -> stats_dump_li_stats()
            -> stats_dump_sv_stats()
            -> stats_dump_be_stats()
            -> stats_dump_px_end()

  http_stats_io_handler()
      -> stats_http_redir()
      -> stats_dump_http()              // also emits the HTTP headers
         -> stats_dump_html_head()      // emits the HTML headers
         -> stats_dump_csv_header()     // emits the CSV headers (same as above)
         -> stats_dump_http_info()      // note: ignores non-HTML output
         -> stats_dump_proxy()          // same as above
         -> stats_dump_http_end()       // emits HTML trailer
diff --git a/include/proto/dumpstats.h b/include/proto/dumpstats.h
index b6a689c..e9b2f76 100644
--- a/include/proto/dumpstats.h
+++ b/include/proto/dumpstats.h
@@ -28,8 +28,6 @@
 
 /* Flags for applet.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_ADMIN      0x00000020	/* indicate a stats admin level */
@@ -48,12 +46,13 @@
 #define STAT_CLI_PROMPT 3   /* display the prompt (first output, same code) */
 #define STAT_CLI_PRINT  4   /* display message in cli->msg */
 
-#define STAT_CLI_O_INFO 5   /* dump info/stats */
+#define STAT_CLI_O_INFO 5   /* dump info */
 #define STAT_CLI_O_SESS 6   /* dump sessions */
 #define STAT_CLI_O_ERR  7   /* dump errors */
 #define STAT_CLI_O_TAB  8   /* dump tables */
 #define STAT_CLI_O_CLR  9   /* clear tables */
 #define STAT_CLI_O_SET  10  /* set entries in tables */
+#define STAT_CLI_O_STAT 11  /* dump stats */
 
 extern struct si_applet http_stats_applet;
 
diff --git a/src/dumpstats.c b/src/dumpstats.c
index 31d24b0..661aa3f 100644
--- a/src/dumpstats.c
+++ b/src/dumpstats.c
@@ -1,7 +1,7 @@
 /*
  * Functions dedicated to statistics output and the stats socket
  *
- * Copyright 2000-2010 Willy Tarreau <w@1wt.eu>
+ * Copyright 2000-2012 Willy Tarreau <w@1wt.eu>
  * Copyright 2007-2009 Krzysztof Piotr Oledzki <ole@ans.pl>
  *
  * This program is free software; you can redistribute it and/or
@@ -61,7 +61,8 @@
 #include <proto/ssl_sock.h>
 #endif
 
-static int stats_dump_raw_to_buffer(struct stream_interface *si);
+static int stats_dump_raw_info_to_buffer(struct stream_interface *si);
+static int stats_dump_raw_stat_to_buffer(struct stream_interface *si);
 static int stats_dump_full_sess_to_buffer(struct stream_interface *si, struct session *sess);
 static int stats_dump_sess_to_buffer(struct stream_interface *si);
 static int stats_dump_errors_to_buffer(struct stream_interface *si);
@@ -69,6 +70,32 @@
 static int stats_dump_proxy(struct stream_interface *si, struct proxy *px, struct uri_auth *uri);
 static int stats_dump_http(struct stream_interface *si, struct uri_auth *uri);
 
+/*
+  cli_io_handler()
+      -> stats_dump_sess_to_buffer()      // "show sess"
+      -> stats_dump_errors_to_buffer()    // "show errors"
+      -> stats_dump_raw_info_to_buffer()  // "show info"
+         -> stats_dump_raw_info()
+      -> stats_dump_raw_stat_to_buffer()  // "show stat"
+         -> stats_dump_csv_header()
+         -> stats_dump_proxy()
+            -> stats_dump_px_hdr()
+            -> stats_dump_fe_stats()
+            -> stats_dump_li_stats()
+            -> stats_dump_sv_stats()
+            -> stats_dump_be_stats()
+            -> stats_dump_px_end()
+
+  http_stats_io_handler()
+      -> stats_http_redir()
+      -> stats_dump_http()              // also emits the HTTP headers
+         -> stats_dump_html_head()      // emits the HTML headers
+         -> stats_dump_csv_header()     // emits the CSV headers (same as above)
+         -> stats_dump_http_info()      // note: ignores non-HTML output
+         -> stats_dump_proxy()          // same as above
+         -> stats_dump_http_end()       // emits HTML trailer
+ */
+
 static struct si_applet cli_applet;
 
 static const char stats_sock_usage_msg[] =
@@ -361,26 +388,29 @@
 	return 0;
 }
 
-static int print_csv_header(struct chunk *msg)
+/* Dumps the stats CSV header to the trash buffer which. The caller is responsible
+ * for clearing it if needed.
+ */
+static void stats_dump_csv_header()
 {
-	return chunk_appendf(msg,
-			    "# pxname,svname,"
-			    "qcur,qmax,"
-			    "scur,smax,slim,stot,"
-			    "bin,bout,"
-			    "dreq,dresp,"
-			    "ereq,econ,eresp,"
-			    "wretr,wredis,"
-			    "status,weight,act,bck,"
-			    "chkfail,chkdown,lastchg,downtime,qlimit,"
-			    "pid,iid,sid,throttle,lbtot,tracked,type,"
-			    "rate,rate_lim,rate_max,"
-			    "check_status,check_code,check_duration,"
-			    "hrsp_1xx,hrsp_2xx,hrsp_3xx,hrsp_4xx,hrsp_5xx,hrsp_other,hanafail,"
-			    "req_rate,req_rate_max,req_tot,"
-			    "cli_abrt,srv_abrt,"
-			    "comp_in,comp_out,comp_byp,comp_rsp,"
-			    "\n");
+	chunk_appendf(&trash,
+	              "# pxname,svname,"
+	              "qcur,qmax,"
+	              "scur,smax,slim,stot,"
+	              "bin,bout,"
+	              "dreq,dresp,"
+	              "ereq,econ,eresp,"
+	              "wretr,wredis,"
+	              "status,weight,act,bck,"
+	              "chkfail,chkdown,lastchg,downtime,qlimit,"
+	              "pid,iid,sid,throttle,lbtot,tracked,type,"
+	              "rate,rate_lim,rate_max,"
+	              "check_status,check_code,check_duration,"
+	              "hrsp_1xx,hrsp_2xx,hrsp_3xx,hrsp_4xx,hrsp_5xx,hrsp_other,hanafail,"
+	              "req_rate,req_rate_max,req_tot,"
+	              "cli_abrt,srv_abrt,"
+	              "comp_in,comp_out,comp_byp,comp_rsp,"
+	              "\n");
 }
 
 /* print a string of text buffer to <out>. The format is :
@@ -910,16 +940,14 @@
 				si->applet.ctx.stats.sid = atoi(args[4]);
 			}
 
-			si->applet.ctx.stats.flags |= STAT_SHOW_STAT;
 			si->applet.ctx.stats.flags |= STAT_FMT_CSV;
 			si->conn->xprt_st = STAT_ST_INIT;
-			si->applet.st0 = STAT_CLI_O_INFO; // stats_dump_raw_to_buffer
+			si->applet.st0 = STAT_CLI_O_STAT; // stats_dump_raw_stat_to_buffer
 		}
 		else if (strcmp(args[1], "info") == 0) {
-			si->applet.ctx.stats.flags |= STAT_SHOW_INFO;
 			si->applet.ctx.stats.flags |= STAT_FMT_CSV;
 			si->conn->xprt_st = STAT_ST_INIT;
-			si->applet.st0 = STAT_CLI_O_INFO; // stats_dump_raw_to_buffer
+			si->applet.st0 = STAT_CLI_O_INFO; // stats_dump_raw_info_to_buffer
 		}
 		else if (strcmp(args[1], "sess") == 0) {
 			si->conn->xprt_st = STAT_ST_INIT;
@@ -1615,7 +1643,11 @@
 					si->applet.st0 = STAT_CLI_PROMPT;
 				break;
 			case STAT_CLI_O_INFO:
-				if (stats_dump_raw_to_buffer(si))
+				if (stats_dump_raw_info_to_buffer(si))
+					si->applet.st0 = STAT_CLI_PROMPT;
+				break;
+			case STAT_CLI_O_STAT:
+				if (stats_dump_raw_stat_to_buffer(si))
 					si->applet.st0 = STAT_CLI_PROMPT;
 				break;
 			case STAT_CLI_O_SESS:
@@ -1701,93 +1733,98 @@
 	}
 }
 
+/* Dumps the raw stats information block to the trash for and uses the state from
+ * stream interface <si>. The caller is responsible for clearing the trash if needed.
+ */
+static void stats_dump_raw_info(struct stream_interface *si)
+{
+	unsigned int up = (now.tv_sec - start_date.tv_sec);
+
+	chunk_appendf(&trash,
+	              "Name: " PRODUCT_NAME "\n"
+	              "Version: " HAPROXY_VERSION "\n"
+	              "Release_date: " HAPROXY_DATE "\n"
+	              "Nbproc: %d\n"
+	              "Process_num: %d\n"
+	              "Pid: %d\n"
+	              "Uptime: %dd %dh%02dm%02ds\n"
+	              "Uptime_sec: %d\n"
+	              "Memmax_MB: %d\n"
+	              "Ulimit-n: %d\n"
+	              "Maxsock: %d\n"
+	              "Maxconn: %d\n"
+	              "Hard_maxconn: %d\n"
+	              "Maxpipes: %d\n"
+	              "CurrConns: %d\n"
+	              "PipesUsed: %d\n"
+	              "PipesFree: %d\n"
+	              "ConnRate: %d\n"
+	              "ConnRateLimit: %d\n"
+	              "MaxConnRate: %d\n"
+	              "CompressBpsIn: %u\n"
+	              "CompressBpsOut: %u\n"
+	              "CompressBpsRateLim: %u\n"
+#ifdef USE_ZLIB
+	              "ZlibMemUsage: %ld\n"
+	              "MaxZlibMemUsage: %ld\n"
+#endif
+	              "Tasks: %d\n"
+	              "Run_queue: %d\n"
+	              "Idle_pct: %d\n"
+	              "node: %s\n"
+	              "description: %s\n"
+	              "",
+	              global.nbproc,
+	              relative_pid,
+	              pid,
+	              up / 86400, (up % 86400) / 3600, (up % 3600) / 60, (up % 60),
+	              up,
+	              global.rlimit_memmax,
+	              global.rlimit_nofile,
+	              global.maxsock, global.maxconn, global.hardmaxconn, global.maxpipes,
+	              actconn, pipes_used, pipes_free,
+	              read_freq_ctr(&global.conn_per_sec), global.cps_lim, global.cps_max,
+	              read_freq_ctr(&global.comp_bps_in), read_freq_ctr(&global.comp_bps_out),
+	              global.comp_rate_lim,
+#ifdef USE_ZLIB
+	              zlib_used_memory, global.maxzlibmem,
+#endif
+	              nb_tasks_cur, run_queue_cur, idle_pct,
+	              global.node, global.desc ? global.desc : ""
+	              );
+}
+
+/* This function dumps information onto the stream interface's read buffer.
+ * It returns 0 as long as it does not complete, non-zero upon completion.
+ * No state is used.
+ */
+static int stats_dump_raw_info_to_buffer(struct stream_interface *si)
+{
+	chunk_reset(&trash);
+	stats_dump_raw_info(si);
+
+	if (bi_putchk(si->ib, &trash) == -1)
+		return 0;
+
+	return 1;
+}
+
 /* This function dumps statistics onto the stream interface's read buffer.
  * The xprt_ctx must have been zeroed first, and the flags properly set.
  * It returns 0 as long as it does not complete, non-zero upon completion.
- * Some states are not used but it makes the code more similar to other
- * functions which handle stats too.
+ * Only states STAT_ST_INIT and STAT_ST_LIST are used.
  */
-static int stats_dump_raw_to_buffer(struct stream_interface *si)
+static int stats_dump_raw_stat_to_buffer(struct stream_interface *si)
 {
 	struct proxy *px;
-	unsigned int up;
 
 	chunk_reset(&trash);
 
 	switch (si->conn->xprt_st) {
 	case STAT_ST_INIT:
-		/* the function had not been called yet */
-		si->conn->xprt_st = STAT_ST_HEAD;
-		/* fall through */
-
-	case STAT_ST_HEAD:
-		if (si->applet.ctx.stats.flags & STAT_SHOW_STAT) {
-			print_csv_header(&trash);
-			if (bi_putchk(si->ib, &trash) == -1)
-				return 0;
-		}
-
-		si->conn->xprt_st = STAT_ST_INFO;
-		/* fall through */
-
-	case STAT_ST_INFO:
-		up = (now.tv_sec - start_date.tv_sec);
-		if (si->applet.ctx.stats.flags & STAT_SHOW_INFO) {
-			chunk_appendf(&trash,
-				     "Name: " PRODUCT_NAME "\n"
-				     "Version: " HAPROXY_VERSION "\n"
-				     "Release_date: " HAPROXY_DATE "\n"
-				     "Nbproc: %d\n"
-				     "Process_num: %d\n"
-				     "Pid: %d\n"
-				     "Uptime: %dd %dh%02dm%02ds\n"
-				     "Uptime_sec: %d\n"
-				     "Memmax_MB: %d\n"
-				     "Ulimit-n: %d\n"
-				     "Maxsock: %d\n"
-				     "Maxconn: %d\n"
-				     "Hard_maxconn: %d\n"
-				     "Maxpipes: %d\n"
-				     "CurrConns: %d\n"
-				     "PipesUsed: %d\n"
-				     "PipesFree: %d\n"
-				     "ConnRate: %d\n"
-				     "ConnRateLimit: %d\n"
-				     "MaxConnRate: %d\n"
-				     "CompressBpsIn: %u\n"
-				     "CompressBpsOut: %u\n"
-				     "CompressBpsRateLim: %u\n"
-#ifdef USE_ZLIB
-				     "ZlibMemUsage: %ld\n"
-				     "MaxZlibMemUsage: %ld\n"
-#endif
-				     "Tasks: %d\n"
-				     "Run_queue: %d\n"
-				     "Idle_pct: %d\n"
-				     "node: %s\n"
-				     "description: %s\n"
-				     "",
-				     global.nbproc,
-				     relative_pid,
-				     pid,
-				     up / 86400, (up % 86400) / 3600, (up % 3600) / 60, (up % 60),
-				     up,
-				     global.rlimit_memmax,
-				     global.rlimit_nofile,
-				     global.maxsock, global.maxconn, global.hardmaxconn, global.maxpipes,
-				     actconn, pipes_used, pipes_free,
-				     read_freq_ctr(&global.conn_per_sec), global.cps_lim, global.cps_max,
-				     read_freq_ctr(&global.comp_bps_in), read_freq_ctr(&global.comp_bps_out),
-				     global.comp_rate_lim,
-#ifdef USE_ZLIB
-				     zlib_used_memory, global.maxzlibmem,
-#endif
-				     nb_tasks_cur, run_queue_cur, idle_pct,
-				     global.node, global.desc?global.desc:""
-				     );
-			if (bi_putchk(si->ib, &trash) == -1)
-				return 0;
-		}
+		stats_dump_csv_header();
+		if (bi_putchk(si->ib, &trash) == -1)
+			return 0;
 
 		si->applet.ctx.stats.px = proxy;
 		si->applet.ctx.stats.px_st = STAT_PX_ST_INIT;
@@ -1795,509 +1832,1003 @@
 		si->conn->xprt_st = STAT_ST_LIST;
 		/* fall through */
 
-	case STAT_ST_LIST:
+	default:
 		/* dump proxies */
-		if (si->applet.ctx.stats.flags & STAT_SHOW_STAT) {
-			while (si->applet.ctx.stats.px) {
-				px = si->applet.ctx.stats.px;
-				/* skip the disabled proxies, global frontend and non-networked ones */
-				if (px->state != PR_STSTOPPED && px->uuid > 0 &&
-				    (px->cap & (PR_CAP_FE | PR_CAP_BE))) {
-					if (stats_dump_proxy(si, px, NULL) == 0)
-						return 0;
-				}
-
-				si->applet.ctx.stats.px = px->next;
-				si->applet.ctx.stats.px_st = STAT_PX_ST_INIT;
+		while (si->applet.ctx.stats.px) {
+			px = si->applet.ctx.stats.px;
+			/* skip the disabled proxies, global frontend and non-networked ones */
+			if (px->state != PR_STSTOPPED && px->uuid > 0 &&
+			    (px->cap & (PR_CAP_FE | PR_CAP_BE))) {
+				if (stats_dump_proxy(si, px, NULL) == 0)
+					return 0;
 			}
-			/* here, we just have reached the last proxy */
+			si->applet.ctx.stats.px = px->next;
+			si->applet.ctx.stats.px_st = STAT_PX_ST_INIT;
 		}
-
-		si->conn->xprt_st = STAT_ST_END;
-		/* fall through */
-
-	case STAT_ST_END:
-		si->conn->xprt_st = STAT_ST_FIN;
-		/* fall through */
-
-	case STAT_ST_FIN:
-		return 1;
-
-	default:
-		/* unknown state ! */
-		si->conn->xprt_st = STAT_ST_FIN;
+		/* here, we just have reached the last proxy */
 		return 1;
 	}
 }
 
-
-/* We don't want to land on the posted stats page because a refresh will
- * repost the data.  We don't want this to happen on accident so we redirect
- * the browse to the stats page with a GET.
+/* Dumps a frontend's line to the trash for the current proxy <px> and uses
+ * the state from stream interface <si>. The caller is responsible for clearing
+ * the trash if needed. Returns non-zero if it emits anything, zero otherwise.
  */
-static int stats_http_redir(struct stream_interface *si, struct uri_auth *uri)
+static int stats_dump_fe_stats(struct stream_interface *si, struct proxy *px)
 {
-	struct session *s = si->conn->xprt_ctx;
+	int i;
 
-	chunk_reset(&trash);
+	if (!(px->cap & PR_CAP_FE))
+		return 0;
 
-	switch (si->conn->xprt_st) {
-	case STAT_ST_INIT:
+	if ((si->applet.ctx.stats.flags & STAT_BOUND) && !(si->applet.ctx.stats.type & (1 << STATS_TYPE_FE)))
+		return 0;
+
+	if (!(si->applet.ctx.stats.flags & STAT_FMT_CSV)) {
 		chunk_appendf(&trash,
-			"HTTP/1.0 303 See Other\r\n"
-			"Cache-Control: no-cache\r\n"
-			"Content-Type: text/plain\r\n"
-			"Connection: close\r\n"
-			"Location: %s;st=%s",
-			uri->uri_prefix,
-			((si->applet.ctx.stats.st_code > STAT_STATUS_INIT) &&
-			 (si->applet.ctx.stats.st_code < STAT_STATUS_SIZE) &&
-			 stat_status_codes[si->applet.ctx.stats.st_code]) ?
-				stat_status_codes[si->applet.ctx.stats.st_code] :
-				stat_status_codes[STAT_STATUS_UNKN]);
-		chunk_appendf(&trash, "\r\n\r\n");
+		              /* name, queue */
+		              "<tr class=\"frontend\">");
 
-		if (bi_putchk(si->ib, &trash) == -1)
-			return 0;
+		if (px->cap & PR_CAP_BE && px->srv && (si->applet.ctx.stats.flags & STAT_ADMIN)) {
+			/* Column sub-heading for Enable or Disable server */
+			chunk_appendf(&trash, "<td></td>");
+		}
 
-		s->txn.status = 303;
+		chunk_appendf(&trash,
+		              "<td class=ac>"
+		              "<a name=\"%s/Frontend\"></a>"
+		              "<a class=lfsb href=\"#%s/Frontend\">Frontend</a></td>"
+		              "<td colspan=3></td>"
+		              "",
+		              px->id, px->id);
 
-		if (!(s->flags & SN_ERR_MASK))  // this is not really an error but it is
-			s->flags |= SN_ERR_PRXCOND; // to mark that it comes from the proxy
-		if (!(s->flags & SN_FINST_MASK))
-			s->flags |= SN_FINST_R;
+		if (px->mode == PR_MODE_HTTP)
+			chunk_appendf(&trash,
+			              /* sessions rate : current, max, limit */
+			              "<td title=\"Cur: %u req/s\"><u>%s</u></td><td title=\"Max: %u req/s\"><u>%s</u></td><td>%s</td>"
+			              "",
+			              read_freq_ctr(&px->fe_req_per_sec),
+			              U2H0(read_freq_ctr(&px->fe_sess_per_sec)),
+			              px->fe_counters.p.http.rps_max,
+			              U2H1(px->fe_counters.sps_max),
+			              LIM2A2(px->fe_sps_lim, "-"));
+		else
+			chunk_appendf(&trash,
+			              /* sessions rate : current, max, limit */
+			              "<td>%s</td><td>%s</td><td>%s</td>"
+			              "",
+			              U2H0(read_freq_ctr(&px->fe_sess_per_sec)),
+			              U2H1(px->fe_counters.sps_max), LIM2A2(px->fe_sps_lim, "-"));
 
-		si->conn->xprt_st = STAT_ST_FIN;
-		return 1;
-	}
-	return 1;
-}
+		chunk_appendf(&trash,
+		              /* sessions: current, max, limit */
+		              "<td>%s</td><td>%s</td><td>%s</td>"
+		              "<td"
+		              "",
+		              U2H3(px->feconn), U2H4(px->fe_counters.conn_max), U2H5(px->maxconn));
 
+		/* http response (via td title): 1xx, 2xx, 3xx, 4xx, 5xx, other */
+		if (px->mode == PR_MODE_HTTP) {
+			chunk_appendf(&trash, " title=\"%lld requests:", px->fe_counters.p.http.cum_req);
 
-/* This I/O handler runs as an applet embedded in a stream interface. It is
- * used to send HTTP stats over a TCP socket. The mechanism is very simple.
- * si->applet.st0 becomes non-zero once the transfer is finished. The handler
- * automatically unregisters itself once transfer is complete.
- */
-static void http_stats_io_handler(struct stream_interface *si)
-{
-	struct session *s = si->conn->xprt_ctx;
-	struct channel *req = si->ob;
-	struct channel *res = si->ib;
+			for (i = 1; i < 6; i++)
+				chunk_appendf(&trash, " %dxx=%lld,", i, px->fe_counters.p.http.rsp[i]);
 
-	if (unlikely(si->state == SI_ST_DIS || si->state == SI_ST_CLO))
-		goto out;
+			chunk_appendf(&trash, " other=%lld,", px->fe_counters.p.http.rsp[0]);
+			chunk_appendf(&trash, " compressed=%lld (%d%%)",
+			              px->fe_counters.p.http.comp_rsp,
+			              px->fe_counters.p.http.rsp[2] ?
+			              (int)(100*px->fe_counters.p.http.comp_rsp/px->fe_counters.p.http.rsp[2]) : 0);
+			chunk_appendf(&trash, " intercepted=%lld\"", px->fe_counters.intercepted_req);
+		}
 
-	/* check that the output is not closed */
-	if (res->flags & (CF_SHUTW|CF_SHUTW_NOW))
-		si->applet.st0 = 1;
+		chunk_appendf(&trash,
+		              /* sessions: total, lbtot */
+		              ">%s%s%s</td><td></td>"
+		              /* bytes : in */
+		              "<td>%s</td><td"
+		              "",
+		              (px->mode == PR_MODE_HTTP)?"<u>":"",
+		              U2H6(px->fe_counters.cum_sess),
+		              (px->mode == PR_MODE_HTTP)?"</u>":"",
+		              U2H7(px->fe_counters.bytes_in));
 
-	if (!si->applet.st0) {
-		if (s->txn.meth == HTTP_METH_POST) {
-			if (stats_http_redir(si, s->be->uri_auth)) {
-				si->applet.st0 = 1;
-				si_shutw(si);
-			}
-		} else {
-			if (stats_dump_http(si, s->be->uri_auth)) {
-				si->applet.st0 = 1;
-				si_shutw(si);
-			}
-		}
-	}
+		/* compression stats (via td title): comp_in, comp_out, comp_byp */
+		chunk_appendf(&trash, " title=\"compression: in=%lld out=%lld bypassed=%lld savings=%d%%\"",
+		              px->fe_counters.comp_in, px->fe_counters.comp_out, px->fe_counters.comp_byp,
+		              px->fe_counters.comp_in ?
+		              (int)((px->fe_counters.comp_in - px->fe_counters.comp_out)*100/px->fe_counters.comp_in) : 0);
 
-	if ((res->flags & CF_SHUTR) && (si->state == SI_ST_EST))
-		si_shutw(si);
+		chunk_appendf(&trash,
+		              /* bytes: out */
+		              ">%s%s%s</td>"
+		              "",
+		              (px->fe_counters.comp_in || px->fe_counters.comp_byp) ? "<u>":"",
+		              U2H0(px->fe_counters.bytes_out),
+		              (px->fe_counters.comp_in || px->fe_counters.comp_byp) ? "</u>":"");
 
-	if ((req->flags & CF_SHUTW) && (si->state == SI_ST_EST) && si->applet.st0) {
-		si_shutr(si);
-		res->flags |= CF_READ_NULL;
+		chunk_appendf(&trash,
+		              /* denied: req, resp */
+		              "<td>%s</td><td>%s</td>"
+		              /* errors : request, connect, response */
+		              "<td>%s</td><td></td><td></td>"
+		              /* warnings: retries, redispatches */
+		              "<td></td><td></td>"
+		              /* server status : reflect frontend status */
+		              "<td class=ac>%s</td>"
+		              /* rest of server: nothing */
+		              "<td class=ac colspan=8></td></tr>"
+		              "",
+		              U2H0(px->fe_counters.denied_req), U2H1(px->fe_counters.denied_resp),
+		              U2H2(px->fe_counters.failed_req),
+		              px->state == PR_STREADY ? "OPEN" :
+		              px->state == PR_STFULL ? "FULL" : "STOP");
 	}
+	else { /* CSV mode */
+		chunk_appendf(&trash,
+		              /* pxid, name, queue cur, queue max, */
+		              "%s,FRONTEND,,,"
+		              /* sessions : current, max, limit, total */
+		              "%d,%d,%d,%lld,"
+		              /* bytes : in, out */
+		              "%lld,%lld,"
+		              /* denied: req, resp */
+		              "%lld,%lld,"
+		              /* errors : request, connect, response */
+		              "%lld,,,"
+		              /* warnings: retries, redispatches */
+		              ",,"
+		              /* server status : reflect frontend status */
+		              "%s,"
+		              /* rest of server: nothing */
+		              ",,,,,,,,"
+		              /* pid, iid, sid, throttle, lbtot, tracked, type */
+		              "%d,%d,0,,,,%d,"
+		              /* rate, rate_lim, rate_max */
+		              "%u,%u,%u,"
+		              /* check_status, check_code, check_duration */
+		              ",,,",
+		              px->id,
+		              px->feconn, px->fe_counters.conn_max, px->maxconn, px->fe_counters.cum_sess,
+		              px->fe_counters.bytes_in, px->fe_counters.bytes_out,
+		              px->fe_counters.denied_req, px->fe_counters.denied_resp,
+		              px->fe_counters.failed_req,
+		              px->state == PR_STREADY ? "OPEN" :
+		              px->state == PR_STFULL ? "FULL" : "STOP",
+		              relative_pid, px->uuid, STATS_TYPE_FE,
+		              read_freq_ctr(&px->fe_sess_per_sec),
+		              px->fe_sps_lim, px->fe_counters.sps_max);
 
-	/* update all other flags and resync with the other side */
-	si_update(si);
+		/* http response: 1xx, 2xx, 3xx, 4xx, 5xx, other */
+		if (px->mode == PR_MODE_HTTP) {
+			for (i=1; i<6; i++)
+				chunk_appendf(&trash, "%lld,", px->fe_counters.p.http.rsp[i]);
+			chunk_appendf(&trash, "%lld,", px->fe_counters.p.http.rsp[0]);
+		}
+		else
+			chunk_appendf(&trash, ",,,,,,");
 
-	/* we don't want to expire timeouts while we're processing requests */
-	si->ib->rex = TICK_ETERNITY;
-	si->ob->wex = TICK_ETERNITY;
+		/* failed health analyses */
+		chunk_appendf(&trash, ",");
 
- out:
-	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);
+		/* requests : req_rate, req_rate_max, req_tot, */
+		chunk_appendf(&trash, "%u,%u,%lld,",
+		              read_freq_ctr(&px->fe_req_per_sec),
+		              px->fe_counters.p.http.rps_max, px->fe_counters.p.http.cum_req);
+
+		/* errors: cli_aborts, srv_aborts */
+		chunk_appendf(&trash, ",,");
+
+		/* compression: in, out, bypassed */
+		chunk_appendf(&trash, "%lld,%lld,%lld,",
+		              px->fe_counters.comp_in, px->fe_counters.comp_out, px->fe_counters.comp_byp);
+
+		/* compression: comp_rsp */
+		chunk_appendf(&trash, "%lld,",
+		              px->fe_counters.p.http.comp_rsp);
+
+		/* finish with EOL */
+		chunk_appendf(&trash, "\n");
 	}
+	return 1;
 }
 
-
-/* This function dumps statistics in HTTP format onto the stream interface's
- * read buffer. The xprt_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.
+/* Dumps a line for listener <l> and proxy <px> to the trash and uses the state
+ * from stream interface <si>, and stats flags <flags>. The caller is responsible
+ * for clearing the trash if needed. Returns non-zero if it emits anything, zero
+ * otherwise.
  */
-static int stats_dump_http(struct stream_interface *si, struct uri_auth *uri)
+static int stats_dump_li_stats(struct stream_interface *si, struct proxy *px, struct listener *l, int flags)
 {
-	struct session *s = si->conn->xprt_ctx;
-	struct channel *rep = si->ib;
-	struct proxy *px;
-	unsigned int up;
+	if (!(si->applet.ctx.stats.flags & STAT_FMT_CSV)) {
+		chunk_appendf(&trash, "<tr class=socket>");
+		if (px->cap & PR_CAP_BE && px->srv && (si->applet.ctx.stats.flags & STAT_ADMIN)) {
+			/* Column sub-heading for Enable or Disable server */
+			chunk_appendf(&trash, "<td></td>");
+		}
+		chunk_appendf(&trash, "<td class=ac");
 
-	chunk_reset(&trash);
+		if (flags & ST_SHLGNDS) {
+			char str[INET6_ADDRSTRLEN];
+			int port;
 
-	switch (si->conn->xprt_st) {
-	case STAT_ST_INIT:
-		chunk_appendf(&trash,
-			     "HTTP/1.0 200 OK\r\n"
-			     "Cache-Control: no-cache\r\n"
-			     "Connection: close\r\n"
-			     "Content-Type: %s\r\n",
-			     (si->applet.ctx.stats.flags & STAT_FMT_CSV) ? "text/plain" : "text/html");
+			chunk_appendf(&trash, " title=\"");
 
-		if (uri->refresh > 0 && !(si->applet.ctx.stats.flags & STAT_NO_REFRESH))
-			chunk_appendf(&trash, "Refresh: %d\r\n",
-				     uri->refresh);
+			port = get_host_port(&l->addr);
+			switch (addr_to_str(&l->addr, str, sizeof(str))) {
+			case AF_INET:
+				chunk_appendf(&trash, "IPv4: %s:%d, ", str, port);
+				break;
+			case AF_INET6:
+				chunk_appendf(&trash, "IPv6: [%s]:%d, ", str, port);
+				break;
+			case AF_UNIX:
+				chunk_appendf(&trash, "unix, ");
+				break;
+			case -1:
+				chunk_appendf(&trash, "(%s), ", strerror(errno));
+				break;
+			}
 
-		chunk_appendf(&trash, "\r\n");
+			/* id */
+			chunk_appendf(&trash, "id: %d\"", l->luid);
+		}
 
-		s->txn.status = 200;
-		if (bi_putchk(rep, &trash) == -1)
-			return 0;
+		chunk_appendf(&trash,
+		              /* name, queue */
+		              ">%s<a name=\"%s/+%s\"></a>"
+		              "<a class=lfsb href=\"#%s/+%s\">%s</a></td><td colspan=3>%s</td>"
+		              /* sessions rate: current, max, limit */
+		              "<td colspan=3>&nbsp;</td>"
+		              /* sessions: current, max, limit, total, lbtot */
+		              "<td>%s</td><td>%s</td><td>%s</td>"
+		              "<td>%s</td><td>&nbsp;</td>"
+		              /* bytes: in, out */
+		              "<td>%s</td><td>%s</td>"
+		              "",
+		              (flags & ST_SHLGNDS)?"<u>":"",
+		              px->id, l->name, px->id, l->name, l->name,
+		              (flags & ST_SHLGNDS)?"</u>":"",
+		              U2H3(l->nbconn), U2H4(l->counters->conn_max), U2H5(l->maxconn),
+		              U2H6(l->counters->cum_conn), U2H7(l->counters->bytes_in), U2H8(l->counters->bytes_out));
 
-		if (!(s->flags & SN_ERR_MASK))  // this is not really an error but it is
-			s->flags |= SN_ERR_PRXCOND; // to mark that it comes from the proxy
-		if (!(s->flags & SN_FINST_MASK))
-			s->flags |= SN_FINST_R;
+		chunk_appendf(&trash,
+		              /* denied: req, resp */
+		              "<td>%s</td><td>%s</td>"
+		              /* errors: request, connect, response */
+		              "<td>%s</td><td></td><td></td>"
+		              /* warnings: retries, redispatches */
+		              "<td></td><td></td>"
+		              /* server status: reflect listener status */
+		              "<td class=ac>%s</td>"
+		              /* rest of server: nothing */
+		              "<td class=ac colspan=8></td></tr>"
+		              "",
+		              U2H0(l->counters->denied_req), U2H1(l->counters->denied_resp),
+		              U2H2(l->counters->failed_req),
+		              (l->nbconn < l->maxconn) ? (l->state == LI_LIMITED) ? "WAITING" : "OPEN" : "FULL");
+	}
+	else { /* CSV mode */
+		chunk_appendf(&trash,
+		              /* pxid, name, queue cur, queue max, */
+		              "%s,%s,,,"
+		              /* sessions: current, max, limit, total */
+		              "%d,%d,%d,%lld,"
+		              /* bytes: in, out */
+		              "%lld,%lld,"
+		              /* denied: req, resp */
+		              "%lld,%lld,"
+		              /* errors: request, connect, response */
+		              "%lld,,,"
+		              /* warnings: retries, redispatches */
+		              ",,"
+		              /* server status: reflect listener status */
+		              "%s,"
+		              /* rest of server: nothing */
+		              ",,,,,,,,"
+		              /* pid, iid, sid, throttle, lbtot, tracked, type */
+		              "%d,%d,%d,,,,%d,"
+		              /* rate, rate_lim, rate_max */
+		              ",,,"
+		              /* check_status, check_code, check_duration */
+		              ",,,"
+		              /* http response: 1xx, 2xx, 3xx, 4xx, 5xx, other */
+		              ",,,,,,"
+		              /* failed health analyses */
+		              ","
+		              /* requests : req_rate, req_rate_max, req_tot, */
+		              ",,,"
+		              /* errors: cli_aborts, srv_aborts */
+		              ",,"
+		              /* compression: in, out, bypassed, comp_rsp */
+		              ",,,,"
+		              "\n",
+		              px->id, l->name,
+		              l->nbconn, l->counters->conn_max,
+		              l->maxconn, l->counters->cum_conn,
+		              l->counters->bytes_in, l->counters->bytes_out,
+		              l->counters->denied_req, l->counters->denied_resp,
+		              l->counters->failed_req,
+		              (l->nbconn < l->maxconn) ? "OPEN" : "FULL",
+		              relative_pid, px->uuid, l->luid, STATS_TYPE_SO);
+	}
+	return 1;
+}
 
-		if (s->txn.meth == HTTP_METH_HEAD) {
-			/* that's all we return in case of HEAD request */
-			si->conn->xprt_st = STAT_ST_FIN;
-			return 1;
-		}
+/* Dumps a line for server <sv> and proxy <px> to the trash and uses the state
+ * from stream interface <si>, stats flags <flags>, and server state <state>.
+ * The caller is responsible for clearing the trash if needed. Returns non-zero
+ * if it emits anything, zero otherwise. The <state> parameter can take the
+ * following values : 0=DOWN, 1=going up, 2=going down, 3=UP, 4,5=NOLB, 6=unchecked.
+ */
+static int stats_dump_sv_stats(struct stream_interface *si, struct proxy *px, int flags, struct server *sv, int state)
+{
+	struct server *ref = sv->track ? sv->track : sv;
+	char str[INET6_ADDRSTRLEN];
+	struct chunk src;
+	int i;
 
-		si->conn->xprt_st = STAT_ST_HEAD; /* let's start producing data */
-		/* fall through */
+	if (!(si->applet.ctx.stats.flags & STAT_FMT_CSV)) { /* HTML mode */
+		static char *srv_hlt_st[7] = {
+			"DOWN",
+			"DN %d/%d &uarr;",
+			"UP %d/%d &darr;",
+			"UP",
+			"NOLB %d/%d &darr;",
+			"NOLB",
+			"<i>no check</i>"
+		};
 
-	case STAT_ST_HEAD:
-		if (!(si->applet.ctx.stats.flags & STAT_FMT_CSV)) {
-			/* WARNING! This must fit in the first buffer !!! */
+		if ((sv->state & SRV_MAINTAIN) || (ref->state & SRV_MAINTAIN))
+			chunk_appendf(&trash, "<tr class=\"maintain\">");
+		else
 			chunk_appendf(&trash,
-			     "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\n"
-			     "\"http://www.w3.org/TR/html4/loose.dtd\">\n"
-			     "<html><head><title>Statistics Report for " PRODUCT_NAME "%s%s</title>\n"
-			     "<meta http-equiv=\"content-type\" content=\"text/html; charset=iso-8859-1\">\n"
-			     "<style type=\"text/css\"><!--\n"
-			     "body {"
-			     " font-family: arial, helvetica, sans-serif;"
-			     " font-size: 12px;"
-			     " font-weight: normal;"
-			     " color: black;"
-			     " background: white;"
-			     "}\n"
-			     "th,td {"
-			     " font-size: 10px;"
-			     "}\n"
-			     "h1 {"
-			     " font-size: x-large;"
-			     " margin-bottom: 0.5em;"
-			     "}\n"
-			     "h2 {"
-			     " font-family: helvetica, arial;"
-			     " font-size: x-large;"
-			     " font-weight: bold;"
-			     " font-style: italic;"
-			     " color: #6020a0;"
-			     " margin-top: 0em;"
-			     " margin-bottom: 0em;"
-			     "}\n"
-			     "h3 {"
-			     " font-family: helvetica, arial;"
-			     " font-size: 16px;"
-			     " font-weight: bold;"
-			     " color: #b00040;"
-			     " background: #e8e8d0;"
-			     " margin-top: 0em;"
-			     " margin-bottom: 0em;"
-			     "}\n"
-			     "li {"
-			     " margin-top: 0.25em;"
-			     " margin-right: 2em;"
-			     "}\n"
-			     ".hr {margin-top: 0.25em;"
-			     " border-color: black;"
-			     " border-bottom-style: solid;"
-			     "}\n"
-			     ".titre	{background: #20D0D0;color: #000000; font-weight: bold; text-align: center;}\n"
-			     ".total	{background: #20D0D0;color: #ffff80;}\n"
-			     ".frontend	{background: #e8e8d0;}\n"
-			     ".socket	{background: #d0d0d0;}\n"
-			     ".backend	{background: #e8e8d0;}\n"
-			     ".active0	{background: #ff9090;}\n"
-			     ".active1	{background: #ffd020;}\n"
-			     ".active2	{background: #ffffa0;}\n"
-			     ".active3	{background: #c0ffc0;}\n"
-			     ".active4	{background: #ffffa0;}\n"  /* NOLB state shows same as going down */
-			     ".active5	{background: #a0e0a0;}\n"  /* NOLB state shows darker than up */
-			     ".active6	{background: #e0e0e0;}\n"
-			     ".backup0	{background: #ff9090;}\n"
-			     ".backup1	{background: #ff80ff;}\n"
-			     ".backup2	{background: #c060ff;}\n"
-			     ".backup3	{background: #b0d0ff;}\n"
-			     ".backup4	{background: #c060ff;}\n"  /* NOLB state shows same as going down */
-			     ".backup5	{background: #90b0e0;}\n"  /* NOLB state shows same as going down */
-			     ".backup6	{background: #e0e0e0;}\n"
-			     ".maintain	{background: #c07820;}\n"
-			     ".rls      {letter-spacing: 0.2em; margin-right: 1px;}\n" /* right letter spacing (used for grouping digits) */
-			     "\n"
-			     "a.px:link {color: #ffff40; text-decoration: none;}"
-			     "a.px:visited {color: #ffff40; text-decoration: none;}"
-			     "a.px:hover {color: #ffffff; text-decoration: none;}"
-			     "a.lfsb:link {color: #000000; text-decoration: none;}"
-			     "a.lfsb:visited {color: #000000; text-decoration: none;}"
-			     "a.lfsb:hover {color: #505050; text-decoration: none;}"
-			     "\n"
-			     "table.tbl { border-collapse: collapse; border-style: none;}\n"
-			     "table.tbl td { text-align: right; border-width: 1px 1px 1px 1px; border-style: solid solid solid solid; padding: 2px 3px; border-color: gray; white-space: nowrap;}\n"
-			     "table.tbl td.ac { text-align: center;}\n"
-			     "table.tbl th { border-width: 1px; border-style: solid solid solid solid; border-color: gray;}\n"
-			     "table.tbl th.pxname { background: #b00040; color: #ffff40; font-weight: bold; border-style: solid solid none solid; padding: 2px 3px; white-space: nowrap;}\n"
-			     "table.tbl th.empty { border-style: none; empty-cells: hide; background: white;}\n"
-			     "table.tbl th.desc { background: white; border-style: solid solid none solid; text-align: left; padding: 2px 3px;}\n"
-			     "\n"
-			     "table.lgd { border-collapse: collapse; border-width: 1px; border-style: none none none solid; border-color: black;}\n"
-			     "table.lgd td { border-width: 1px; border-style: solid solid solid solid; border-color: gray; padding: 2px;}\n"
-			     "table.lgd td.noborder { border-style: none; padding: 2px; white-space: nowrap;}\n"
-			     "u {text-decoration:none; border-bottom: 1px dotted black;}\n"
-			     "-->\n"
-			     "</style></head>\n",
-			     (uri->flags&ST_SHNODE) ? " on " : "",
-			     (uri->flags&ST_SHNODE) ? (uri->node ? uri->node : global.node) : ""
-			     );
-		} else {
-			print_csv_header(&trash);
-		}
-		if (bi_putchk(rep, &trash) == -1)
-			return 0;
-
-		si->conn->xprt_st = STAT_ST_INFO;
-		/* fall through */
+			              "<tr class=\"%s%d\">",
+			              (sv->state & SRV_BACKUP) ? "backup" : "active", state);
 
-	case STAT_ST_INFO:
-		up = (now.tv_sec - start_date.tv_sec);
-
-		/* WARNING! this has to fit the first packet too.
-			 * We are around 3.5 kB, add adding entries will
-			 * become tricky if we want to support 4kB buffers !
-			 */
-		if (!(si->applet.ctx.stats.flags & STAT_FMT_CSV)) {
+		if ((px->cap & PR_CAP_BE) && px->srv && (si->applet.ctx.stats.flags & STAT_ADMIN))
 			chunk_appendf(&trash,
-			     "<body><h1><a href=\"" PRODUCT_URL "\" style=\"text-decoration: none;\">"
-			     PRODUCT_NAME "%s</a></h1>\n"
-			     "<h2>Statistics Report for pid %d%s%s%s%s</h2>\n"
-			     "<hr width=\"100%%\" class=\"hr\">\n"
-			     "<h3>&gt; General process information</h3>\n"
-			     "<table border=0><tr><td align=\"left\" nowrap width=\"1%%\">\n"
-			     "<p><b>pid = </b> %d (process #%d, nbproc = %d)<br>\n"
-			     "<b>uptime = </b> %dd %dh%02dm%02ds<br>\n"
-			     "<b>system limits:</b> memmax = %s%s; ulimit-n = %d<br>\n"
-			     "<b>maxsock = </b> %d; <b>maxconn = </b> %d; <b>maxpipes = </b> %d<br>\n"
-			     "current conns = %d; current pipes = %d/%d; conn rate = %d/sec<br>\n"
-			     "Running tasks: %d/%d; idle = %d %%<br>\n"
-			     "</td><td align=\"center\" nowrap>\n"
-			     "<table class=\"lgd\"><tr>\n"
-			     "<td class=\"active3\">&nbsp;</td><td class=\"noborder\">active UP </td>"
-			     "<td class=\"backup3\">&nbsp;</td><td class=\"noborder\">backup UP </td>"
-			     "</tr><tr>\n"
-			     "<td class=\"active2\"></td><td class=\"noborder\">active UP, going down </td>"
-			     "<td class=\"backup2\"></td><td class=\"noborder\">backup UP, going down </td>"
-			     "</tr><tr>\n"
-			     "<td class=\"active1\"></td><td class=\"noborder\">active DOWN, going up </td>"
-			     "<td class=\"backup1\"></td><td class=\"noborder\">backup DOWN, going up </td>"
-			     "</tr><tr>\n"
-			     "<td class=\"active0\"></td><td class=\"noborder\">active or backup DOWN &nbsp;</td>"
-			     "<td class=\"active6\"></td><td class=\"noborder\">not checked </td>"
-			     "</tr><tr>\n"
-			     "<td class=\"maintain\"></td><td class=\"noborder\" colspan=\"3\">active or backup DOWN for maintenance (MAINT) &nbsp;</td>"
-			     "</tr></table>\n"
-			     "Note: UP with load-balancing disabled is reported as \"NOLB\"."
-			     "</td>"
-			     "<td align=\"left\" valign=\"top\" nowrap width=\"1%%\">"
-			     "<b>Display option:</b><ul style=\"margin-top: 0.25em;\">"
-			     "",
-			     (uri->flags&ST_HIDEVER)?"":(STATS_VERSION_STRING),
-			     pid, (uri->flags&ST_SHNODE) ? " on " : "", (uri->flags&ST_SHNODE) ? (uri->node ? uri->node : global.node) : "",
-			     (uri->flags&ST_SHDESC)? ": " : "", (uri->flags&ST_SHDESC) ? (uri->desc ? uri->desc : global.desc) : "",
-			     pid, relative_pid, global.nbproc,
-			     up / 86400, (up % 86400) / 3600,
-			     (up % 3600) / 60, (up % 60),
-			     global.rlimit_memmax ? ultoa(global.rlimit_memmax) : "unlimited",
-			     global.rlimit_memmax ? " MB" : "",
-			     global.rlimit_nofile,
-			     global.maxsock, global.maxconn, global.maxpipes,
-			     actconn, pipes_used, pipes_used+pipes_free, read_freq_ctr(&global.conn_per_sec),
-			     run_queue_cur, nb_tasks_cur, idle_pct
-			     );
+			              "<td><input type=\"checkbox\" name=\"s\" value=\"%s\"></td>",
+			              sv->id);
 
-			if (si->applet.ctx.stats.flags & STAT_HIDE_DOWN)
-				chunk_appendf(&trash,
-				     "<li><a href=\"%s%s%s\">Show all servers</a><br>\n",
-				     uri->uri_prefix,
-				     "",
-				     (si->applet.ctx.stats.flags & STAT_NO_REFRESH) ? ";norefresh" : "");
-			else
-				chunk_appendf(&trash,
-				     "<li><a href=\"%s%s%s\">Hide 'DOWN' servers</a><br>\n",
-				     uri->uri_prefix,
-				     ";up",
-				     (si->applet.ctx.stats.flags & STAT_NO_REFRESH) ? ";norefresh" : "");
+		chunk_appendf(&trash, "<td class=ac");
 
-			if (uri->refresh > 0) {
-				if (si->applet.ctx.stats.flags & STAT_NO_REFRESH)
-					chunk_appendf(&trash,
-					     "<li><a href=\"%s%s%s\">Enable refresh</a><br>\n",
-					     uri->uri_prefix,
-					     (si->applet.ctx.stats.flags & STAT_HIDE_DOWN) ? ";up" : "",
-					     "");
-				else
-					chunk_appendf(&trash,
-					     "<li><a href=\"%s%s%s\">Disable refresh</a><br>\n",
-					     uri->uri_prefix,
-					     (si->applet.ctx.stats.flags & STAT_HIDE_DOWN) ? ";up" : "",
-					     ";norefresh");
+		if (flags & ST_SHLGNDS) {
+			chunk_appendf(&trash, " title=\"");
+
+			switch (addr_to_str(&sv->addr, str, sizeof(str))) {
+			case AF_INET:
+				chunk_appendf(&trash, "IPv4: %s:%d, ", str, get_host_port(&sv->addr));
+				break;
+			case AF_INET6:
+				chunk_appendf(&trash, "IPv6: [%s]:%d, ", str, get_host_port(&sv->addr));
+				break;
+			case AF_UNIX:
+				chunk_appendf(&trash, "unix, ");
+				break;
+			case -1:
+				chunk_appendf(&trash, "(%s), ", strerror(errno));
+				break;
+			default: /* address family not supported */
+				break;
 			}
 
-			chunk_appendf(&trash,
-			     "<li><a href=\"%s%s%s\">Refresh now</a><br>\n",
-			     uri->uri_prefix,
-			     (si->applet.ctx.stats.flags & STAT_HIDE_DOWN) ? ";up" : "",
-			     (si->applet.ctx.stats.flags & STAT_NO_REFRESH) ? ";norefresh" : "");
+			/* id */
+			chunk_appendf(&trash, "id: %d", sv->puid);
 
-			chunk_appendf(&trash,
-			     "<li><a href=\"%s;csv%s\">CSV export</a><br>\n",
-			     uri->uri_prefix,
-			     (uri->refresh > 0) ? ";norefresh" : "");
+			/* cookie */
+			if (sv->cookie) {
+				chunk_appendf(&trash, ", cookie: '");
 
-			chunk_appendf(&trash,
-			     "</ul></td>"
-			     "<td align=\"left\" valign=\"top\" nowrap width=\"1%%\">"
-			     "<b>External resources:</b><ul style=\"margin-top: 0.25em;\">\n"
-			     "<li><a href=\"" PRODUCT_URL "\">Primary site</a><br>\n"
-			     "<li><a href=\"" PRODUCT_URL_UPD "\">Updates (v" PRODUCT_BRANCH ")</a><br>\n"
-			     "<li><a href=\"" PRODUCT_URL_DOC "\">Online manual</a><br>\n"
-			     "</ul>"
-			     "</td>"
-			     "</tr></table>\n"
-			     ""
-			     );
+				chunk_initlen(&src, sv->cookie, 0, strlen(sv->cookie));
+				chunk_htmlencode(&trash, &src);
 
-			if (si->applet.ctx.stats.st_code) {
-				switch (si->applet.ctx.stats.st_code) {
-				case STAT_STATUS_DONE:
-					chunk_appendf(&trash,
-						     "<p><div class=active3>"
-						     "<a class=lfsb href=\"%s\" title=\"Remove this message\">[X]</a> "
-						     "Action processed successfully."
-						     "</div>\n", uri->uri_prefix);
-					break;
-				case STAT_STATUS_NONE:
-					chunk_appendf(&trash,
-						     "<p><div class=active2>"
-						     "<a class=lfsb href=\"%s\" title=\"Remove this message\">[X]</a> "
-						     "Nothing has changed."
-						     "</div>\n", uri->uri_prefix);
-					break;
-				case STAT_STATUS_PART:
-					chunk_appendf(&trash,
-						     "<p><div class=active2>"
-						     "<a class=lfsb href=\"%s\" title=\"Remove this message\">[X]</a> "
-						     "Action partially processed.<br>"
-						     "Some server names are probably unknown or ambiguous (duplicated names in the backend)."
-						     "</div>\n", uri->uri_prefix);
-					break;
-				case STAT_STATUS_ERRP:
-					chunk_appendf(&trash,
-						     "<p><div class=active0>"
-						     "<a class=lfsb href=\"%s\" title=\"Remove this message\">[X]</a> "
-						     "Action not processed because of invalid parameters."
-						     "<ul>"
-						     "<li>The action is maybe unknown.</li>"
-						     "<li>The backend name is probably unknown or ambiguous (duplicated names).</li>"
-						     "<li>Some server names are probably unknown or ambiguous (duplicated names in the backend).</li>"
-						     "</ul>"
-						     "</div>\n", uri->uri_prefix);
-					break;
-				case STAT_STATUS_EXCD:
-					chunk_appendf(&trash,
-						     "<p><div class=active0>"
-						     "<a class=lfsb href=\"%s\" title=\"Remove this message\">[X]</a> "
-						     "<b>Action not processed : the buffer couldn't store all the data.<br>"
-						     "You should retry with less servers at a time.</b>"
-						     "</div>\n", uri->uri_prefix);
-					break;
-				case STAT_STATUS_DENY:
-					chunk_appendf(&trash,
-						     "<p><div class=active0>"
-						     "<a class=lfsb href=\"%s\" title=\"Remove this message\">[X]</a> "
-						     "<b>Action denied.</b>"
-						     "</div>\n", uri->uri_prefix);
-					break;
-				default:
-					chunk_appendf(&trash,
-						     "<p><div class=active6>"
-						     "<a class=lfsb href=\"%s\" title=\"Remove this message\">[X]</a> "
-						     "Unexpected result."
-						     "</div>\n", uri->uri_prefix);
-				}
-				chunk_appendf(&trash,"<p>\n");
+				chunk_appendf(&trash, "'");
 			}
 
-			if (bi_putchk(rep, &trash) == -1)
-				return 0;
+			chunk_appendf(&trash, "\"");
 		}
 
-		si->applet.ctx.stats.px = proxy;
-		si->applet.ctx.stats.px_st = STAT_PX_ST_INIT;
-		si->conn->xprt_st = STAT_ST_LIST;
-		/* fall through */
+		chunk_appendf(&trash,
+		              ">%s<a name=\"%s/%s\"></a>"
+		              "<a class=lfsb href=\"#%s/%s\">%s</a>%s</td>"
+		              /* queue : current, max, limit */
+		              "<td>%s</td><td>%s</td><td>%s</td>"
+		              /* sessions rate : current, max, limit */
+		              "<td>%s</td><td>%s</td><td></td>"
+		              /* sessions: current, max, limit */
+		              "<td>%s</td><td>%s</td><td>%s</td>"
+		              "<td"
+		              "",
+		              (flags & ST_SHLGNDS) ? "<u>" : "",
+		              px->id, sv->id, px->id, sv->id, sv->id,
+		              (flags & ST_SHLGNDS) ? "</u>" : "",
+		              U2H0(sv->nbpend), U2H1(sv->counters.nbpend_max), LIM2A2(sv->maxqueue, "-"),
+		              U2H3(read_freq_ctr(&sv->sess_per_sec)), U2H4(sv->counters.sps_max),
+		              U2H5(sv->cur_sess), U2H6(sv->counters.cur_sess_max), LIM2A7(sv->maxconn, "-"));
 
-	case STAT_ST_LIST:
-		/* dump proxies */
-		while (si->applet.ctx.stats.px) {
-			if (buffer_almost_full(rep->buf))
-				return 0;
-			px = si->applet.ctx.stats.px;
-			/* skip the disabled proxies, global frontend and non-networked ones */
-			if (px->state != PR_STSTOPPED && px->uuid > 0 && (px->cap & (PR_CAP_FE | PR_CAP_BE)))
-				if (stats_dump_proxy(si, px, uri) == 0)
-					return 0;
+		/* http response (via td title): 1xx, 2xx, 3xx, 4xx, 5xx, other */
+		if (px->mode == PR_MODE_HTTP) {
+			chunk_appendf(&trash, " title=\"rsp codes:");
 
-			si->applet.ctx.stats.px = px->next;
-			si->applet.ctx.stats.px_st = STAT_PX_ST_INIT;
+			for (i = 1; i < 6; i++)
+				chunk_appendf(&trash, " %dxx=%lld,", i, sv->counters.p.http.rsp[i]);
+
+			chunk_appendf(&trash, " other=%lld\"", sv->counters.p.http.rsp[0]);
 		}
-		/* here, we just have reached the last proxy */
 
-		si->conn->xprt_st = STAT_ST_END;
-		/* fall through */
+		chunk_appendf(&trash,
+		              /* sessions: total, lbtot */
+		              ">%s%s%s</td><td>%s</td>",
+		              (px->mode == PR_MODE_HTTP)?"<u>":"",
+		              U2H0(sv->counters.cum_sess),
+		              (px->mode == PR_MODE_HTTP)?"</u>":"",
+		              U2H1(sv->counters.cum_lbconn));
 
-	case STAT_ST_END:
-		if (!(si->applet.ctx.stats.flags & STAT_FMT_CSV)) {
-			chunk_appendf(&trash, "</body></html>\n");
-			if (bi_putchk(rep, &trash) == -1)
-				return 0;
+		chunk_appendf(&trash,
+		              /* bytes : in, out */
+		              "<td>%s</td><td>%s</td>"
+		              /* denied: req, resp */
+		              "<td></td><td>%s</td>"
+		              /* errors : request, connect */
+		              "<td></td><td>%s</td>"
+		              /* errors : response */
+		              "<td title=\"Connection resets during transfers: %lld client, %lld server\"><u>%s</u></td>"
+		              /* warnings: retries, redispatches */
+		              "<td>%lld</td><td>%lld</td>"
+		              "",
+		              U2H0(sv->counters.bytes_in), U2H1(sv->counters.bytes_out),
+		              U2H2(sv->counters.failed_secu),
+		              U2H3(sv->counters.failed_conns),
+		              sv->counters.cli_aborts,
+		              sv->counters.srv_aborts,
+		              U2H6(sv->counters.failed_resp),
+		              sv->counters.retries, sv->counters.redispatches);
+
+		/* status, lest check */
+		chunk_appendf(&trash, "<td class=ac>");
+
+		if (sv->state & SRV_MAINTAIN) {
+			chunk_appendf(&trash, "%s ", human_time(now.tv_sec - sv->last_change, 1));
+			chunk_appendf(&trash, "MAINT");
+		}
+		else if (ref != sv && ref->state & SRV_MAINTAIN) {
+			chunk_appendf(&trash, "%s ", human_time(now.tv_sec - ref->last_change, 1));
+			chunk_appendf(&trash, "MAINT(via)");
+		}
+		else if (ref->state & SRV_CHECKED) {
+			chunk_appendf(&trash, "%s ", human_time(now.tv_sec - ref->last_change, 1));
+			chunk_appendf(&trash,
+			              srv_hlt_st[state],
+			              (ref->state & SRV_RUNNING) ? (ref->health - ref->rise + 1) : (ref->health),
+			              (ref->state & SRV_RUNNING) ? (ref->fall) : (ref->rise));
 		}
 
-		si->conn->xprt_st = STAT_ST_FIN;
-		/* fall through */
+		if (sv->state & SRV_CHECKED) {
+			chunk_appendf(&trash, "</td><td class=ac title=\"%s",
+				      get_check_status_description(sv->check.status));
 
-	case STAT_ST_FIN:
-		return 1;
+			if (*sv->check.desc) {
+				chunk_appendf(&trash, ": ");
+				chunk_initlen(&src, sv->check.desc, 0, strlen(sv->check.desc));
+				chunk_htmlencode(&trash, &src);
+			}
 
-	default:
-		/* unknown state ! */
-		si->conn->xprt_st = STAT_ST_FIN;
-		return -1;
+			chunk_appendf(&trash, "\"><u> %s%s",
+			              (sv->state & SRV_CHK_RUNNING) ? "* " : "",
+			              get_check_status_info(sv->check.status));
+
+			if (sv->check.status >= HCHK_STATUS_L57DATA)
+				chunk_appendf(&trash, "/%d", sv->check.code);
+
+			if (sv->check.status >= HCHK_STATUS_CHECKED && sv->check.duration >= 0)
+				chunk_appendf(&trash, " in %lums</u>", sv->check.duration);
+		}
+		else
+			chunk_appendf(&trash, "</td><td>");
+
+		chunk_appendf(&trash,
+		              /* weight */
+		              "</td><td class=ac>%d</td>"
+		              /* act, bck */
+		              "<td class=ac>%s</td><td class=ac>%s</td>"
+		              "",
+		              (sv->eweight * px->lbprm.wmult + px->lbprm.wdiv - 1) / px->lbprm.wdiv,
+		              (sv->state & SRV_BACKUP) ? "-" : "Y",
+		              (sv->state & SRV_BACKUP) ? "Y" : "-");
+
+		/* check failures: unique, fatal, down time */
+		if (sv->state & SRV_CHECKED) {
+			chunk_appendf(&trash, "<td title=\"Failed Health Checks%s\"><u>%lld",
+			              ref->observe?"/Health Analyses":"", ref->counters.failed_checks);
+
+			if (ref->observe)
+				chunk_appendf(&trash, "/%lld", ref->counters.failed_hana);
+
+			chunk_appendf(&trash,
+			              "</u></td>"
+			              "<td>%lld</td><td>%s</td>"
+			              "",
+			              ref->counters.down_trans, human_time(srv_downtime(sv), 1));
+		}
+		else if (sv != ref)
+			chunk_appendf(&trash,
+			              "<td class=ac colspan=3><a class=lfsb href=\"#%s/%s\">via %s/%s<a></td>",
+			              ref->proxy->id, ref->id, ref->proxy->id, ref->id);
+		else
+			chunk_appendf(&trash, "<td colspan=3></td>");
+
+		/* throttle */
+		if ((sv->state & SRV_WARMINGUP) &&
+		    now.tv_sec < sv->last_change + sv->slowstart &&
+		    now.tv_sec >= sv->last_change) {
+			chunk_appendf(&trash, "<td class=ac>%d %%</td></tr>\n",
+			              (int)MAX(1, 100 * (now.tv_sec - sv->last_change) / sv->slowstart));
+		}
+		else
+			chunk_appendf(&trash, "<td class=ac>-</td></tr>\n");
+	}
+	else { /* CSV mode */
+		static char *srv_hlt_st[7] = {
+			"DOWN,",
+			"DOWN %d/%d,",
+			"UP %d/%d,",
+			"UP,",
+			"NOLB %d/%d,",
+			"NOLB,",
+			"no check,"
+		};
+
+		chunk_appendf(&trash,
+		              /* pxid, name */
+		              "%s,%s,"
+		              /* queue : current, max */
+		              "%d,%d,"
+		              /* sessions : current, max, limit, total */
+		              "%d,%d,%s,%lld,"
+		              /* bytes : in, out */
+		              "%lld,%lld,"
+		              /* denied: req, resp */
+		              ",%lld,"
+		              /* errors : request, connect, response */
+		              ",%lld,%lld,"
+		              /* warnings: retries, redispatches */
+		              "%lld,%lld,"
+		              "",
+		              px->id, sv->id,
+		              sv->nbpend, sv->counters.nbpend_max,
+		              sv->cur_sess, sv->counters.cur_sess_max, LIM2A0(sv->maxconn, ""), sv->counters.cum_sess,
+		              sv->counters.bytes_in, sv->counters.bytes_out,
+		              sv->counters.failed_secu,
+		              sv->counters.failed_conns, sv->counters.failed_resp,
+		              sv->counters.retries, sv->counters.redispatches);
+
+		/* status */
+		if (sv->state & SRV_MAINTAIN)
+			chunk_appendf(&trash, "MAINT,");
+		else if (ref != sv && ref->state & SRV_MAINTAIN)
+			chunk_appendf(&trash, "MAINT(via),");
+		else
+			chunk_appendf(&trash,
+			              srv_hlt_st[state],
+			              (ref->state & SRV_RUNNING) ? (ref->health - ref->rise + 1) : (ref->health),
+			              (ref->state & SRV_RUNNING) ? (ref->fall) : (ref->rise));
+
+		chunk_appendf(&trash,
+		              /* weight, active, backup */
+		              "%d,%d,%d,"
+		              "",
+		              (sv->eweight * px->lbprm.wmult + px->lbprm.wdiv - 1) / px->lbprm.wdiv,
+		              (sv->state & SRV_BACKUP) ? 0 : 1,
+		              (sv->state & SRV_BACKUP) ? 1 : 0);
+
+		/* check failures: unique, fatal; last change, total downtime */
+		if (sv->state & SRV_CHECKED)
+			chunk_appendf(&trash,
+			              "%lld,%lld,%d,%d,",
+			              sv->counters.failed_checks, sv->counters.down_trans,
+			              (int)(now.tv_sec - sv->last_change), srv_downtime(sv));
+		else
+			chunk_appendf(&trash, ",,,,");
+
+		/* queue limit, pid, iid, sid, */
+		chunk_appendf(&trash,
+		              "%s,"
+		              "%d,%d,%d,",
+		              LIM2A0(sv->maxqueue, ""),
+		              relative_pid, px->uuid, sv->puid);
+
+		/* throttle */
+		if ((sv->state & SRV_WARMINGUP) &&
+		    now.tv_sec < sv->last_change + sv->slowstart &&
+		    now.tv_sec >= sv->last_change)
+			chunk_appendf(&trash, "%d", (int)MAX(1, 100 * (now.tv_sec - sv->last_change) / sv->slowstart));
+
+		/* sessions: lbtot */
+		chunk_appendf(&trash, ",%lld,", sv->counters.cum_lbconn);
+
+		/* tracked */
+		if (sv->track)
+			chunk_appendf(&trash, "%s/%s,",
+			              sv->track->proxy->id, sv->track->id);
+		else
+			chunk_appendf(&trash, ",");
+
+		/* type */
+		chunk_appendf(&trash, "%d,", STATS_TYPE_SV);
+
+		/* rate */
+		chunk_appendf(&trash, "%u,,%u,",
+		              read_freq_ctr(&sv->sess_per_sec),
+		              sv->counters.sps_max);
+
+		if (sv->state & SRV_CHECKED) {
+			/* check_status */
+			chunk_appendf(&trash, "%s,", get_check_status_info(sv->check.status));
+
+			/* check_code */
+			if (sv->check.status >= HCHK_STATUS_L57DATA)
+				chunk_appendf(&trash, "%u,", sv->check.code);
+			else
+				chunk_appendf(&trash, ",");
+
+			/* check_duration */
+			if (sv->check.status >= HCHK_STATUS_CHECKED)
+				chunk_appendf(&trash, "%lu,", sv->check.duration);
+			else
+				chunk_appendf(&trash, ",");
+
+		}
+		else
+			chunk_appendf(&trash, ",,,");
+
+		/* http response: 1xx, 2xx, 3xx, 4xx, 5xx, other */
+		if (px->mode == PR_MODE_HTTP) {
+			for (i=1; i<6; i++)
+				chunk_appendf(&trash, "%lld,", sv->counters.p.http.rsp[i]);
+
+			chunk_appendf(&trash, "%lld,", sv->counters.p.http.rsp[0]);
+		}
+		else
+			chunk_appendf(&trash, ",,,,,,");
+
+		/* failed health analyses */
+		chunk_appendf(&trash, "%lld,",  sv->counters.failed_hana);
+
+		/* requests : req_rate, req_rate_max, req_tot, */
+		chunk_appendf(&trash, ",,,");
+
+		/* errors: cli_aborts, srv_aborts */
+		chunk_appendf(&trash, "%lld,%lld,",
+		              sv->counters.cli_aborts, sv->counters.srv_aborts);
+
+		/* compression: in, out, bypassed, comp_rsp */
+		chunk_appendf(&trash, ",,,,");
+
+		/* finish with EOL */
+		chunk_appendf(&trash, "\n");
+	}
+	return 1;
+}
+
+/* Dumps a line for backend <px> to the trash for and uses the state from stream
+ * interface <si> and stats flags <flags>. The caller is responsible for clearing
+ * the trash if needed. Returns non-zero if it emits anything, zero otherwise.
+ */
+static int stats_dump_be_stats(struct stream_interface *si, struct proxy *px, int flags)
+{
+	struct chunk src;
+	int i;
+
+	if (!(px->cap & PR_CAP_BE))
+		return 0;
+
+	if ((si->applet.ctx.stats.flags & STAT_BOUND) && !(si->applet.ctx.stats.type & (1 << STATS_TYPE_BE)))
+		return 0;
+
+	if (!(si->applet.ctx.stats.flags & STAT_FMT_CSV)) { /* HTML mode */
+		chunk_appendf(&trash, "<tr class=\"backend\">");
+		if (px->srv && (si->applet.ctx.stats.flags & STAT_ADMIN)) {
+			/* Column sub-heading for Enable or Disable server */
+			chunk_appendf(&trash, "<td></td>");
+		}
+		chunk_appendf(&trash, "<td class=ac");
+
+		if (flags & ST_SHLGNDS) {
+			/* balancing */
+			chunk_appendf(&trash, " title=\"balancing: %s",
+			              backend_lb_algo_str(px->lbprm.algo & BE_LB_ALGO));
+
+			/* cookie */
+			if (px->cookie_name) {
+				chunk_appendf(&trash, ", cookie: '");
+				chunk_initlen(&src, px->cookie_name, 0, strlen(px->cookie_name));
+				chunk_htmlencode(&trash, &src);
+				chunk_appendf(&trash, "'");
+			}
+			chunk_appendf(&trash, "\"");
+		}
+
+		chunk_appendf(&trash,
+		              /* name */
+		              ">%s<a name=\"%s/Backend\"></a>"
+		              "<a class=lfsb href=\"#%s/Backend\">Backend</a>%s</td>"
+		              /* queue : current, max */
+		              "<td>%s</td><td>%s</td><td></td>"
+		              /* sessions rate : current, max, limit */
+		              "<td>%s</td><td>%s</td><td></td>"
+		              "",
+		              (flags & ST_SHLGNDS)?"<u>":"",
+		              px->id, px->id,
+		              (flags & ST_SHLGNDS)?"</u>":"",
+		              U2H0(px->nbpend) /* or px->totpend ? */, U2H1(px->be_counters.nbpend_max),
+		              U2H2(read_freq_ctr(&px->be_sess_per_sec)), U2H3(px->be_counters.sps_max));
+
+		chunk_appendf(&trash,
+		              /* sessions: current, max, limit */
+		              "<td>%s</td><td>%s</td><td>%s</td>"
+		              "<td"
+		              "",
+		              U2H2(px->beconn), U2H3(px->be_counters.conn_max), U2H4(px->fullconn));
+
+		/* http response (via td title): 1xx, 2xx, 3xx, 4xx, 5xx, other */
+		if (px->mode == PR_MODE_HTTP) {
+			chunk_appendf(&trash, " title=\"%lld requests:", px->be_counters.p.http.cum_req);
+
+			for (i = 1; i < 6; i++)
+				chunk_appendf(&trash, " %dxx=%lld", i, px->be_counters.p.http.rsp[i]);
+
+			chunk_appendf(&trash, " other=%lld ", px->be_counters.p.http.rsp[0]);
+			chunk_appendf(&trash, " compressed=%lld (%d%%)\"",
+			              px->be_counters.p.http.comp_rsp,
+			              px->be_counters.p.http.rsp[2] ?
+			              (int)(100*px->be_counters.p.http.comp_rsp/px->be_counters.p.http.rsp[2]) : 0);
+		}
+
+		chunk_appendf(&trash,
+		              /* sessions: total, lbtot */
+		              ">%s%s%s</td><td>%s</td>"
+		              /* bytes: in */
+		              "<td>%s</td><td"
+		              "",
+		              (px->mode == PR_MODE_HTTP)?"<u>":"",
+		              U2H6(px->be_counters.cum_conn),
+		              (px->mode == PR_MODE_HTTP)?"</u>":"",
+		              U2H7(px->be_counters.cum_lbconn),
+		              U2H8(px->be_counters.bytes_in));
+
+		/* compression stats (via td title): comp_in, comp_out, comp_byp */
+		chunk_appendf(&trash, " title=\"compression: in=%lld out=%lld bypassed=%lld savings=%d%%\"",
+		              px->be_counters.comp_in, px->be_counters.comp_out, px->be_counters.comp_byp,
+		              px->be_counters.comp_in ?
+		              (int)((px->be_counters.comp_in - px->be_counters.comp_out)*100/px->be_counters.comp_in) : 0);
+
+		chunk_appendf(&trash,
+		              /* bytes: out */
+		              ">%s%s%s</td>"
+		              "",
+		              (px->be_counters.comp_in || px->be_counters.comp_byp) ? "<u>":"",
+		              U2H0(px->be_counters.bytes_out),
+		              (px->be_counters.comp_in || px->be_counters.comp_byp) ? "</u>":"");
+
+		chunk_appendf(&trash,
+		              /* denied: req, resp */
+		              "<td>%s</td><td>%s</td>"
+		              /* errors : request, connect */
+		              "<td></td><td>%s</td>"
+		              /* errors : response */
+		              "<td title=\"Connection resets during transfers: %lld client, %lld server\"><u>%s</u></td>"
+		              /* warnings: retries, redispatches */
+		              "<td>%lld</td><td>%lld</td>"
+		              /* backend status: reflect backend status (up/down): we display UP
+		               * if the backend has known working servers or if it has no server at
+		               * all (eg: for stats). Then we display the total weight, number of
+		               * active and backups. */
+		              "<td class=ac>%s %s</td><td class=ac>&nbsp;</td><td class=ac>%d</td>"
+		              "<td class=ac>%d</td><td class=ac>%d</td>"
+		              "",
+		              U2H0(px->be_counters.denied_req), U2H1(px->be_counters.denied_resp),
+		              U2H2(px->be_counters.failed_conns),
+		              px->be_counters.cli_aborts,
+		              px->be_counters.srv_aborts,
+		              U2H5(px->be_counters.failed_resp),
+		              px->be_counters.retries, px->be_counters.redispatches,
+		              human_time(now.tv_sec - px->last_change, 1),
+		              (px->lbprm.tot_weight > 0 || !px->srv) ? "UP" :
+		              "<font color=\"red\"><b>DOWN</b></font>",
+		              (px->lbprm.tot_weight * px->lbprm.wmult + px->lbprm.wdiv - 1) / px->lbprm.wdiv,
+		              px->srv_act, px->srv_bck);
+
+		chunk_appendf(&trash,
+		              /* rest of backend: nothing, down transitions, total downtime, throttle */
+		              "<td class=ac>&nbsp;</td><td>%d</td>"
+		              "<td>%s</td>"
+		              "<td></td>"
+		              "</tr>",
+		              px->down_trans,
+		              px->srv?human_time(be_downtime(px), 1):"&nbsp;");
+	}
+	else { /* CSV mode */
+		chunk_appendf(&trash,
+		              /* pxid, name */
+		              "%s,BACKEND,"
+		              /* queue : current, max */
+		              "%d,%d,"
+		              /* sessions : current, max, limit, total */
+		              "%d,%d,%d,%lld,"
+		              /* bytes : in, out */
+		              "%lld,%lld,"
+		              /* denied: req, resp */
+		              "%lld,%lld,"
+		              /* errors : request, connect, response */
+		              ",%lld,%lld,"
+		              /* warnings: retries, redispatches */
+		              "%lld,%lld,"
+		              /* backend status: reflect backend status (up/down): we display UP
+		               * if the backend has known working servers or if it has no server at
+		               * all (eg: for stats). Then we display the total weight, number of
+		               * active and backups. */
+		              "%s,"
+		              "%d,%d,%d,"
+		              /* rest of backend: nothing, down transitions, last change, total downtime */
+		              ",%d,%d,%d,,"
+		              /* pid, iid, sid, throttle, lbtot, tracked, type */
+		              "%d,%d,0,,%lld,,%d,"
+		              /* rate, rate_lim, rate_max, */
+		              "%u,,%u,"
+		              /* check_status, check_code, check_duration */
+		              ",,,",
+		              px->id,
+		              px->nbpend /* or px->totpend ? */, px->be_counters.nbpend_max,
+		              px->beconn, px->be_counters.conn_max, px->fullconn, px->be_counters.cum_conn,
+		              px->be_counters.bytes_in, px->be_counters.bytes_out,
+		              px->be_counters.denied_req, px->be_counters.denied_resp,
+		              px->be_counters.failed_conns, px->be_counters.failed_resp,
+		              px->be_counters.retries, px->be_counters.redispatches,
+		              (px->lbprm.tot_weight > 0 || !px->srv) ? "UP" : "DOWN",
+		              (px->lbprm.tot_weight * px->lbprm.wmult + px->lbprm.wdiv - 1) / px->lbprm.wdiv,
+		              px->srv_act, px->srv_bck,
+		              px->down_trans, (int)(now.tv_sec - px->last_change),
+		              px->srv?be_downtime(px):0,
+		              relative_pid, px->uuid,
+		              px->be_counters.cum_lbconn, STATS_TYPE_BE,
+		              read_freq_ctr(&px->be_sess_per_sec),
+		              px->be_counters.sps_max);
+
+		/* http response: 1xx, 2xx, 3xx, 4xx, 5xx, other */
+		if (px->mode == PR_MODE_HTTP) {
+			for (i=1; i<6; i++)
+				chunk_appendf(&trash, "%lld,", px->be_counters.p.http.rsp[i]);
+			chunk_appendf(&trash, "%lld,", px->be_counters.p.http.rsp[0]);
+		}
+		else
+			chunk_appendf(&trash, ",,,,,,");
+
+		/* failed health analyses */
+		chunk_appendf(&trash, ",");
+
+		/* requests : req_rate, req_rate_max, req_tot, */
+		chunk_appendf(&trash, ",,,");
+
+		/* errors: cli_aborts, srv_aborts */
+		chunk_appendf(&trash, "%lld,%lld,",
+			      px->be_counters.cli_aborts, px->be_counters.srv_aborts);
+
+		/* compression: in, out, bypassed */
+		chunk_appendf(&trash, "%lld,%lld,%lld,",
+			      px->be_counters.comp_in, px->be_counters.comp_out, px->be_counters.comp_byp);
+
+		/* compression: comp_rsp */
+		chunk_appendf(&trash, "%lld,", px->be_counters.p.http.comp_rsp);
+
+		/* finish with EOL */
+		chunk_appendf(&trash, "\n");
+	}
+	return 1;
+}
+
+/* Dumps the table header for proxy <px> to the trash for and uses the state from
+ * stream interface <si> and per-uri parameters <uri>. The caller is responsible
+ * for clearing the trash if needed. Returns non-zero if it emits anything, zero
+ * otherwise.
+ */
+static int stats_dump_px_hdr(struct stream_interface *si, struct proxy *px, struct uri_auth *uri)
+{
+	if (si->applet.ctx.stats.flags & STAT_FMT_CSV)
+		return 0;
+
+	if (px->cap & PR_CAP_BE && px->srv && (si->applet.ctx.stats.flags & STAT_ADMIN)) {
+		/* A form to enable/disable this proxy servers */
+		chunk_appendf(&trash,
+			      "<form action=\"%s\" method=\"post\">",
+			      uri->uri_prefix);
+	}
+
+	/* print a new table */
+	chunk_appendf(&trash,
+		      "<table class=\"tbl\" width=\"100%%\">\n"
+		      "<tr class=\"titre\">"
+		      "<th class=\"pxname\" width=\"10%%\"");
+
+	if (uri->flags & ST_SHLGNDS) {
+		/* cap, mode, id */
+		chunk_appendf(&trash, " title=\"cap: %s, mode: %s, id: %d",
+		              proxy_cap_str(px->cap), proxy_mode_str(px->mode),
+		              px->uuid);
+		chunk_appendf(&trash, "\"");
+	}
+
+	chunk_appendf(&trash,
+	              ">%s<a name=\"%s\"></a>"
+	              "<a class=px href=\"#%s\">%s</a>%s</th>"
+	              "<th class=\"%s\" width=\"90%%\">%s</th>"
+	              "</tr>\n"
+	              "</table>\n"
+	              "<table class=\"tbl\" width=\"100%%\">\n"
+	              "<tr class=\"titre\">",
+	              (uri->flags & ST_SHLGNDS) ? "<u>":"",
+	              px->id, px->id, px->id,
+	              (uri->flags & ST_SHLGNDS) ? "</u>":"",
+	              px->desc ? "desc" : "empty", px->desc ? px->desc : "");
+
+	if ((px->cap & PR_CAP_BE) && px->srv && (si->applet.ctx.stats.flags & STAT_ADMIN)) {
+		/* Column heading for Enable or Disable server */
+		chunk_appendf(&trash, "<th rowspan=2 width=1></th>");
 	}
+
+	chunk_appendf(&trash,
+	              "<th rowspan=2></th>"
+	              "<th colspan=3>Queue</th>"
+	              "<th colspan=3>Session rate</th><th colspan=5>Sessions</th>"
+	              "<th colspan=2>Bytes</th><th colspan=2>Denied</th>"
+	              "<th colspan=3>Errors</th><th colspan=2>Warnings</th>"
+	              "<th colspan=9>Server</th>"
+	              "</tr>\n"
+	              "<tr class=\"titre\">"
+	              "<th>Cur</th><th>Max</th><th>Limit</th>"
+	              "<th>Cur</th><th>Max</th><th>Limit</th><th>Cur</th><th>Max</th>"
+	              "<th>Limit</th><th>Total</th><th>LbTot</th><th>In</th><th>Out</th>"
+	              "<th>Req</th><th>Resp</th><th>Req</th><th>Conn</th>"
+	              "<th>Resp</th><th>Retr</th><th>Redis</th>"
+	              "<th>Status</th><th>LastChk</th><th>Wght</th><th>Act</th>"
+	              "<th>Bck</th><th>Chk</th><th>Dwn</th><th>Dwntme</th>"
+	              "<th>Thrtle</th>\n"
+	              "</tr>");
+	return 1;
 }
 
+/* Dumps the table trailer for proxy <px> to the trash for and uses the state from
+ * stream interface <si>. The caller is responsible for clearing the trash if needed.
+ * Returns non-zero if it emits anything, zero otherwise.
+ */
+static int stats_dump_px_end(struct stream_interface *si, struct proxy *px)
+{
+	if (si->applet.ctx.stats.flags & STAT_FMT_CSV)
+		return 0;
+
+	chunk_appendf(&trash, "</table>");
+
+	if ((px->cap & PR_CAP_BE) && px->srv && (si->applet.ctx.stats.flags & STAT_ADMIN)) {
+		/* close the form used to enable/disable this proxy servers */
+		chunk_appendf(&trash,
+		              "Choose the action to perform on the checked servers : "
+		              "<select name=action>"
+		              "<option value=\"\"></option>"
+		              "<option value=\"disable\">Disable</option>"
+		              "<option value=\"enable\">Enable</option>"
+		              "<option value=\"stop\">Soft Stop</option>"
+		              "<option value=\"start\">Soft Start</option>"
+		              "<option value=\"shutdown\">Kill Sessions</option>"
+		              "</select>"
+		              "<input type=\"hidden\" name=\"b\" value=\"#%d\">"
+		              "&nbsp;<input type=\"submit\" value=\"Apply\">"
+		              "</form>",
+		              px->uuid);
+	}
+
+	chunk_appendf(&trash, "<p>\n");
+	return 1;
+}
 
 /*
  * Dumps statistics for a proxy.
@@ -2341,260 +2872,27 @@
 				return 1;
 		}
 
-		if ((si->applet.ctx.stats.flags & STAT_BOUND) && (si->applet.ctx.stats.iid != -1) &&
-			(px->uuid != si->applet.ctx.stats.iid))
+		if ((si->applet.ctx.stats.flags & STAT_BOUND) &&
+		    (si->applet.ctx.stats.iid != -1) &&
+		    (px->uuid != si->applet.ctx.stats.iid))
 			return 1;
 
 		si->applet.ctx.stats.px_st = STAT_PX_ST_TH;
 		/* fall through */
 
 	case STAT_PX_ST_TH:
-		if (!(si->applet.ctx.stats.flags & STAT_FMT_CSV)) {
-			if (px->cap & PR_CAP_BE && px->srv && (si->applet.ctx.stats.flags & STAT_ADMIN)) {
-				/* A form to enable/disable this proxy servers */
-				chunk_appendf(&trash,
-					"<form action=\"%s\" method=\"post\">",
-					uri->uri_prefix);
-			}
-
-			/* print a new table */
-			chunk_appendf(&trash,
-				     "<table class=\"tbl\" width=\"100%%\">\n"
-				     "<tr class=\"titre\">"
-				     "<th class=\"pxname\" width=\"10%%\"");
-
-			if (uri->flags&ST_SHLGNDS) {
-				/* cap, mode, id */
-				chunk_appendf(&trash, " title=\"cap: %s, mode: %s, id: %d",
-					proxy_cap_str(px->cap), proxy_mode_str(px->mode),
-					px->uuid);
-
-				chunk_appendf(&trash, "\"");
-			}
-
-			chunk_appendf(&trash,
-				     ">%s<a name=\"%s\"></a>"
-				     "<a class=px href=\"#%s\">%s</a>%s</th>"
-				     "<th class=\"%s\" width=\"90%%\">%s</th>"
-				     "</tr>\n"
-				     "</table>\n"
-				     "<table class=\"tbl\" width=\"100%%\">\n"
-				     "<tr class=\"titre\">",
-				     (uri->flags & ST_SHLGNDS)?"<u>":"",
-				     px->id, px->id, px->id,
-				     (uri->flags & ST_SHLGNDS)?"</u>":"",
-				     px->desc ? "desc" : "empty", px->desc ? px->desc : "");
-
-			if (px->cap & PR_CAP_BE && px->srv && (si->applet.ctx.stats.flags & STAT_ADMIN)) {
-				 /* Column heading for Enable or Disable server */
-				chunk_appendf(&trash, "<th rowspan=2 width=1></th>");
-			}
-
-			chunk_appendf(&trash,
-				     "<th rowspan=2></th>"
-				     "<th colspan=3>Queue</th>"
-				     "<th colspan=3>Session rate</th><th colspan=5>Sessions</th>"
-				     "<th colspan=2>Bytes</th><th colspan=2>Denied</th>"
-				     "<th colspan=3>Errors</th><th colspan=2>Warnings</th>"
-				     "<th colspan=9>Server</th>"
-				     "</tr>\n"
-				     "<tr class=\"titre\">"
-				     "<th>Cur</th><th>Max</th><th>Limit</th>"
-				     "<th>Cur</th><th>Max</th><th>Limit</th><th>Cur</th><th>Max</th>"
-				     "<th>Limit</th><th>Total</th><th>LbTot</th><th>In</th><th>Out</th>"
-				     "<th>Req</th><th>Resp</th><th>Req</th><th>Conn</th>"
-				     "<th>Resp</th><th>Retr</th><th>Redis</th>"
-				     "<th>Status</th><th>LastChk</th><th>Wght</th><th>Act</th>"
-				     "<th>Bck</th><th>Chk</th><th>Dwn</th><th>Dwntme</th>"
-				     "<th>Thrtle</th>\n"
-				     "</tr>");
-
+		if (stats_dump_px_hdr(si, px, uri))
 			if (bi_putchk(rep, &trash) == -1)
 				return 0;
-		}
 
 		si->applet.ctx.stats.px_st = STAT_PX_ST_FE;
 		/* fall through */
 
 	case STAT_PX_ST_FE:
 		/* print the frontend */
-		if ((px->cap & PR_CAP_FE) &&
-		    (!(si->applet.ctx.stats.flags & STAT_BOUND) || (si->applet.ctx.stats.type & (1 << STATS_TYPE_FE)))) {
-			if (!(si->applet.ctx.stats.flags & STAT_FMT_CSV)) {
-				chunk_appendf(&trash,
-				     /* name, queue */
-				     "<tr class=\"frontend\">");
-
-				if (px->cap & PR_CAP_BE && px->srv && (si->applet.ctx.stats.flags & STAT_ADMIN)) {
-					/* Column sub-heading for Enable or Disable server */
-					chunk_appendf(&trash, "<td></td>");
-				}
-
-				chunk_appendf(&trash,
-				     "<td class=ac>"
-				     "<a name=\"%s/Frontend\"></a>"
-				     "<a class=lfsb href=\"#%s/Frontend\">Frontend</a></td>"
-				     "<td colspan=3></td>"
-				     "",
-				     px->id, px->id);
-
-				if (px->mode == PR_MODE_HTTP) {
-					chunk_appendf(&trash,
-						     /* sessions rate : current, max, limit */
-						     "<td title=\"Cur: %u req/s\"><u>%s</u></td><td title=\"Max: %u req/s\"><u>%s</u></td><td>%s</td>"
-						     "",
-						     read_freq_ctr(&px->fe_req_per_sec),
-						     U2H0(read_freq_ctr(&px->fe_sess_per_sec)),
-						     px->fe_counters.p.http.rps_max,
-						     U2H1(px->fe_counters.sps_max),
-						     LIM2A2(px->fe_sps_lim, "-"));
-				} else {
-					chunk_appendf(&trash,
-						     /* sessions rate : current, max, limit */
-						     "<td>%s</td><td>%s</td><td>%s</td>"
-						     "",
-						     U2H0(read_freq_ctr(&px->fe_sess_per_sec)),
-						     U2H1(px->fe_counters.sps_max), LIM2A2(px->fe_sps_lim, "-"));
-				}
-
-				chunk_appendf(&trash,
-				     /* sessions: current, max, limit */
-				     "<td>%s</td><td>%s</td><td>%s</td>"
-				     "<td"
-				     "",
-				     U2H3(px->feconn), U2H4(px->fe_counters.conn_max), U2H5(px->maxconn));
-
-				/* http response (via td title): 1xx, 2xx, 3xx, 4xx, 5xx, other */
-				if (px->mode == PR_MODE_HTTP) {
-					int i;
-
-					chunk_appendf(&trash, " title=\"%lld requests:", px->fe_counters.p.http.cum_req);
-
-					for (i = 1; i < 6; i++)
-						chunk_appendf(&trash, " %dxx=%lld,", i, px->fe_counters.p.http.rsp[i]);
-
-					chunk_appendf(&trash, " other=%lld,", px->fe_counters.p.http.rsp[0]);
-					chunk_appendf(&trash, " compressed=%lld (%d%%)",
-					              px->fe_counters.p.http.comp_rsp,
-					              px->fe_counters.p.http.rsp[2] ?
-					              (int)(100*px->fe_counters.p.http.comp_rsp/px->fe_counters.p.http.rsp[2]) : 0);
-					chunk_appendf(&trash, " intercepted=%lld\"", px->fe_counters.intercepted_req);
-				}
-
-				chunk_appendf(&trash,
-				     /* sessions: total, lbtot */
-				     ">%s%s%s</td><td></td>"
-				     /* bytes : in */
-				     "<td>%s</td><td"
-				     "",
-				     (px->mode == PR_MODE_HTTP)?"<u>":"",
-				     U2H6(px->fe_counters.cum_sess),
-				     (px->mode == PR_MODE_HTTP)?"</u>":"",
-				     U2H7(px->fe_counters.bytes_in));
-
-				/* compression stats (via td title): comp_in, comp_out, comp_byp */
-				chunk_appendf(&trash, " title=\"compression: in=%lld out=%lld bypassed=%lld savings=%d%%\"",
-				              px->fe_counters.comp_in, px->fe_counters.comp_out, px->fe_counters.comp_byp,
-				              px->fe_counters.comp_in ?
-				              (int)((px->fe_counters.comp_in - px->fe_counters.comp_out)*100/px->fe_counters.comp_in) : 0);
-
-				chunk_appendf(&trash,
-				     /* bytes: out */
-				     ">%s%s%s</td>"
-				     "",
-				     (px->fe_counters.comp_in || px->fe_counters.comp_byp) ? "<u>":"",
-				     U2H0(px->fe_counters.bytes_out),
-				     (px->fe_counters.comp_in || px->fe_counters.comp_byp) ? "</u>":"");
-
-				chunk_appendf(&trash,
-				     /* denied: req, resp */
-				     "<td>%s</td><td>%s</td>"
-				     /* errors : request, connect, response */
-				     "<td>%s</td><td></td><td></td>"
-				     /* warnings: retries, redispatches */
-				     "<td></td><td></td>"
-				     /* server status : reflect frontend status */
-				     "<td class=ac>%s</td>"
-				     /* rest of server: nothing */
-				     "<td class=ac colspan=8></td></tr>"
-				     "",
-				     U2H0(px->fe_counters.denied_req), U2H1(px->fe_counters.denied_resp),
-				     U2H2(px->fe_counters.failed_req),
-				     px->state == PR_STREADY ? "OPEN" :
-				     px->state == PR_STFULL ? "FULL" : "STOP");
-			} else {
-				chunk_appendf(&trash,
-				     /* pxid, name, queue cur, queue max, */
-				     "%s,FRONTEND,,,"
-				     /* sessions : current, max, limit, total */
-				     "%d,%d,%d,%lld,"
-				     /* bytes : in, out */
-				     "%lld,%lld,"
-				     /* denied: req, resp */
-				     "%lld,%lld,"
-				     /* errors : request, connect, response */
-				     "%lld,,,"
-				     /* warnings: retries, redispatches */
-				     ",,"
-				     /* server status : reflect frontend status */
-				     "%s,"
-				     /* rest of server: nothing */
-				     ",,,,,,,,"
-				     /* pid, iid, sid, throttle, lbtot, tracked, type */
-				     "%d,%d,0,,,,%d,"
-				     /* rate, rate_lim, rate_max */
-				     "%u,%u,%u,"
-				     /* check_status, check_code, check_duration */
-				     ",,,",
-				     px->id,
-				     px->feconn, px->fe_counters.conn_max, px->maxconn, px->fe_counters.cum_sess,
-				     px->fe_counters.bytes_in, px->fe_counters.bytes_out,
-				     px->fe_counters.denied_req, px->fe_counters.denied_resp,
-				     px->fe_counters.failed_req,
-				     px->state == PR_STREADY ? "OPEN" :
-				     px->state == PR_STFULL ? "FULL" : "STOP",
-				     relative_pid, px->uuid, STATS_TYPE_FE,
-				     read_freq_ctr(&px->fe_sess_per_sec),
-				     px->fe_sps_lim, px->fe_counters.sps_max);
-
-				/* http response: 1xx, 2xx, 3xx, 4xx, 5xx, other */
-				if (px->mode == PR_MODE_HTTP) {
-					int i;
-
-					for (i=1; i<6; i++)
-						chunk_appendf(&trash, "%lld,", px->fe_counters.p.http.rsp[i]);
-
-					chunk_appendf(&trash, "%lld,", px->fe_counters.p.http.rsp[0]);
-				} else {
-					chunk_appendf(&trash, ",,,,,,");
-				}
-
-				/* failed health analyses */
-				chunk_appendf(&trash, ",");
-
-				/* requests : req_rate, req_rate_max, req_tot, */
-				chunk_appendf(&trash, "%u,%u,%lld,",
-					     read_freq_ctr(&px->fe_req_per_sec),
-					     px->fe_counters.p.http.rps_max, px->fe_counters.p.http.cum_req);
-
-				/* errors: cli_aborts, srv_aborts */
-				chunk_appendf(&trash, ",,");
-
-				/* compression: in, out, bypassed */
-				chunk_appendf(&trash, "%lld,%lld,%lld,",
-			              px->fe_counters.comp_in, px->fe_counters.comp_out, px->fe_counters.comp_byp);
-
-				/* compression: comp_rsp */
-				chunk_appendf(&trash, "%lld,",
-			              px->fe_counters.p.http.comp_rsp);
-
-				/* finish with EOL */
-				chunk_appendf(&trash, "\n");
-			}
-
+		if (stats_dump_fe_stats(si, px))
 			if (bi_putchk(rep, &trash) == -1)
 				return 0;
-		}
 
 		si->applet.ctx.stats.l = px->conf.listeners.n;
 		si->applet.ctx.stats.px_st = STAT_PX_ST_LI;
@@ -2618,120 +2916,10 @@
 					continue;
 			}
 
-			if (!(si->applet.ctx.stats.flags & STAT_FMT_CSV)) {
-				chunk_appendf(&trash, "<tr class=socket>");
-				if (px->cap & PR_CAP_BE && px->srv && (si->applet.ctx.stats.flags & STAT_ADMIN)) {
-					 /* Column sub-heading for Enable or Disable server */
-					chunk_appendf(&trash, "<td></td>");
-				}
-				chunk_appendf(&trash, "<td class=ac");
-
-					if (uri->flags&ST_SHLGNDS) {
-						char str[INET6_ADDRSTRLEN];
-						int port;
-
-						chunk_appendf(&trash, " title=\"");
-
-						port = get_host_port(&l->addr);
-						switch (addr_to_str(&l->addr, str, sizeof(str))) {
-						case AF_INET:
-							chunk_appendf(&trash, "IPv4: %s:%d, ", str, port);
-							break;
-						case AF_INET6:
-							chunk_appendf(&trash, "IPv6: [%s]:%d, ", str, port);
-							break;
-						case AF_UNIX:
-							chunk_appendf(&trash, "unix, ");
-							break;
-						case -1:
-							chunk_appendf(&trash, "(%s), ", strerror(errno));
-							break;
-						}
-
-						/* id */
-						chunk_appendf(&trash, "id: %d\"", l->luid);
-					}
-
-				chunk_appendf(&trash,
-				     /* name, queue */
-				     ">%s<a name=\"%s/+%s\"></a>"
-				     "<a class=lfsb href=\"#%s/+%s\">%s</a></td><td colspan=3>%s</td>"
-				     /* sessions rate: current, max, limit */
-				     "<td colspan=3>&nbsp;</td>"
-				     /* sessions: current, max, limit, total, lbtot */
-				     "<td>%s</td><td>%s</td><td>%s</td>"
-				     "<td>%s</td><td>&nbsp;</td>"
-				     /* bytes: in, out */
-				     "<td>%s</td><td>%s</td>"
-				     "",
-				     (uri->flags & ST_SHLGNDS)?"<u>":"",
-				     px->id, l->name, px->id, l->name, l->name,
-				     (uri->flags & ST_SHLGNDS)?"</u>":"",
-				     U2H3(l->nbconn), U2H4(l->counters->conn_max), U2H5(l->maxconn),
-				     U2H6(l->counters->cum_conn), U2H7(l->counters->bytes_in), U2H8(l->counters->bytes_out));
-
-				chunk_appendf(&trash,
-				     /* denied: req, resp */
-				     "<td>%s</td><td>%s</td>"
-				     /* errors: request, connect, response */
-				     "<td>%s</td><td></td><td></td>"
-				     /* warnings: retries, redispatches */
-				     "<td></td><td></td>"
-				     /* server status: reflect listener status */
-				     "<td class=ac>%s</td>"
-				     /* rest of server: nothing */
-				     "<td class=ac colspan=8></td></tr>"
-				     "",
-				     U2H0(l->counters->denied_req), U2H1(l->counters->denied_resp),
-				     U2H2(l->counters->failed_req),
-				     (l->nbconn < l->maxconn) ? (l->state == LI_LIMITED) ? "WAITING" : "OPEN" : "FULL");
-			} else {
-				chunk_appendf(&trash,
-				     /* pxid, name, queue cur, queue max, */
-				     "%s,%s,,,"
-				     /* sessions: current, max, limit, total */
-				     "%d,%d,%d,%lld,"
-				     /* bytes: in, out */
-				     "%lld,%lld,"
-				     /* denied: req, resp */
-				     "%lld,%lld,"
-				     /* errors: request, connect, response */
-				     "%lld,,,"
-				     /* warnings: retries, redispatches */
-				     ",,"
-				     /* server status: reflect listener status */
-				     "%s,"
-				     /* rest of server: nothing */
-				     ",,,,,,,,"
-				     /* pid, iid, sid, throttle, lbtot, tracked, type */
-				     "%d,%d,%d,,,,%d,"
-				     /* rate, rate_lim, rate_max */
-				     ",,,"
-				     /* check_status, check_code, check_duration */
-				     ",,,"
-				     /* http response: 1xx, 2xx, 3xx, 4xx, 5xx, other */
-				     ",,,,,,"
-				     /* failed health analyses */
-				     ","
-				     /* requests : req_rate, req_rate_max, req_tot, */
-				     ",,,"
-				     /* errors: cli_aborts, srv_aborts */
-				     ",,"
-				     /* compression: in, out, bypassed, comp_rsp */
-				     ",,,,"
-				     "\n",
-				     px->id, l->name,
-				     l->nbconn, l->counters->conn_max,
-				     l->maxconn, l->counters->cum_conn,
-				     l->counters->bytes_in, l->counters->bytes_out,
-				     l->counters->denied_req, l->counters->denied_resp,
-				     l->counters->failed_req,
-				     (l->nbconn < l->maxconn) ? "OPEN" : "FULL",
-				     relative_pid, px->uuid, l->luid, STATS_TYPE_SO);
-			}
-
-			if (bi_putchk(rep, &trash) == -1)
-				return 0;
+			/* print the frontend */
+			if (stats_dump_li_stats(si, px, l, uri ? uri->flags : 0))
+				if (bi_putchk(rep, &trash) == -1)
+					return 0;
 		}
 
 		si->applet.ctx.stats.sv = px->srv; /* may be NULL */
@@ -2785,632 +2973,542 @@
 				continue;
 			}
 
-			if (!(si->applet.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",
-							       "<i>no check</i>" };
-				if ((sv->state & SRV_MAINTAIN) || (svs->state & SRV_MAINTAIN)) {
-					chunk_appendf(&trash,
-					    /* name */
-					    "<tr class=\"maintain\">"
-					);
-				}
-				else {
-					chunk_appendf(&trash,
-					    /* name */
-					    "<tr class=\"%s%d\">",
-					    (sv->state & SRV_BACKUP) ? "backup" : "active", sv_state);
-				}
-
-				if (px->cap & PR_CAP_BE && px->srv && (si->applet.ctx.stats.flags & STAT_ADMIN)) {
-					chunk_appendf(&trash,
-						"<td><input type=\"checkbox\" name=\"s\" value=\"%s\"></td>",
-						sv->id);
-				}
+			if (stats_dump_sv_stats(si, px, uri ? uri->flags : 0, sv, sv_state))
+				if (bi_putchk(rep, &trash) == -1)
+					return 0;
+		} /* for sv */
 
-				chunk_appendf(&trash, "<td class=ac");
+		si->applet.ctx.stats.px_st = STAT_PX_ST_BE;
+		/* fall through */
 
-				if (uri->flags&ST_SHLGNDS) {
-					char str[INET6_ADDRSTRLEN];
+	case STAT_PX_ST_BE:
+		/* print the backend */
+		if (stats_dump_be_stats(si, px, uri ? uri->flags : 0))
+			if (bi_putchk(rep, &trash) == -1)
+				return 0;
 
-					chunk_appendf(&trash, " title=\"");
+		si->applet.ctx.stats.px_st = STAT_PX_ST_END;
+		/* fall through */
 
-					switch (addr_to_str(&sv->addr, str, sizeof(str))) {
-					case AF_INET:
-						chunk_appendf(&trash, "IPv4: %s:%d, ", str, get_host_port(&sv->addr));
-						break;
-					case AF_INET6:
-						chunk_appendf(&trash, "IPv6: [%s]:%d, ", str, get_host_port(&sv->addr));
-						break;
-					case AF_UNIX:
-						chunk_appendf(&trash, "unix, ");
-						break;
-					case -1:
-						chunk_appendf(&trash, "(%s), ", strerror(errno));
-						break;
-					default: /* address family not supported */
-						break;
-					}
+	case STAT_PX_ST_END:
+		if (stats_dump_px_end(si, px))
+			if (bi_putchk(rep, &trash) == -1)
+				return 0;
 
-					/* id */
-					chunk_appendf(&trash, "id: %d", sv->puid);
+		si->applet.ctx.stats.px_st = STAT_PX_ST_FIN;
+		/* fall through */
 
-					/* cookie */
-					if (sv->cookie) {
-						struct chunk src;
+	case STAT_PX_ST_FIN:
+		return 1;
 
-						chunk_appendf(&trash, ", cookie: '");
+	default:
+		/* unknown state, we should put an abort() here ! */
+		return 1;
+	}
+}
 
-						chunk_initlen(&src, sv->cookie, 0, strlen(sv->cookie));
-						chunk_htmlencode(&trash, &src);
+/* Dumps the HTTP stats head block to the trash for and uses the state from
+ * stream interface <si> and per-uri parameters <uri>. The caller is responsible
+ * for clearing the trash if needed.
+ */
+static void stats_dump_html_head(struct stream_interface *si, struct proxy *px, struct uri_auth *uri)
+{
+	/* WARNING! This must fit in the first buffer !!! */
+	chunk_appendf(&trash,
+	              "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\n"
+	              "\"http://www.w3.org/TR/html4/loose.dtd\">\n"
+	              "<html><head><title>Statistics Report for " PRODUCT_NAME "%s%s</title>\n"
+	              "<meta http-equiv=\"content-type\" content=\"text/html; charset=iso-8859-1\">\n"
+	              "<style type=\"text/css\"><!--\n"
+	              "body {"
+	              " font-family: arial, helvetica, sans-serif;"
+	              " font-size: 12px;"
+	              " font-weight: normal;"
+	              " color: black;"
+	              " background: white;"
+	              "}\n"
+	              "th,td {"
+	              " font-size: 10px;"
+	              "}\n"
+	              "h1 {"
+	              " font-size: x-large;"
+	              " margin-bottom: 0.5em;"
+	              "}\n"
+	              "h2 {"
+	              " font-family: helvetica, arial;"
+	              " font-size: x-large;"
+	              " font-weight: bold;"
+	              " font-style: italic;"
+	              " color: #6020a0;"
+	              " margin-top: 0em;"
+	              " margin-bottom: 0em;"
+	              "}\n"
+	              "h3 {"
+	              " font-family: helvetica, arial;"
+	              " font-size: 16px;"
+	              " font-weight: bold;"
+	              " color: #b00040;"
+	              " background: #e8e8d0;"
+	              " margin-top: 0em;"
+	              " margin-bottom: 0em;"
+	              "}\n"
+	              "li {"
+	              " margin-top: 0.25em;"
+	              " margin-right: 2em;"
+	              "}\n"
+	              ".hr {margin-top: 0.25em;"
+	              " border-color: black;"
+	              " border-bottom-style: solid;"
+	              "}\n"
+	              ".titre	{background: #20D0D0;color: #000000; font-weight: bold; text-align: center;}\n"
+	              ".total	{background: #20D0D0;color: #ffff80;}\n"
+	              ".frontend	{background: #e8e8d0;}\n"
+	              ".socket	{background: #d0d0d0;}\n"
+	              ".backend	{background: #e8e8d0;}\n"
+	              ".active0	{background: #ff9090;}\n"
+	              ".active1	{background: #ffd020;}\n"
+	              ".active2	{background: #ffffa0;}\n"
+	              ".active3	{background: #c0ffc0;}\n"
+	              ".active4	{background: #ffffa0;}\n"  /* NOLB state shows same as going down */
+	              ".active5	{background: #a0e0a0;}\n"  /* NOLB state shows darker than up */
+	              ".active6	{background: #e0e0e0;}\n"
+	              ".backup0	{background: #ff9090;}\n"
+	              ".backup1	{background: #ff80ff;}\n"
+	              ".backup2	{background: #c060ff;}\n"
+	              ".backup3	{background: #b0d0ff;}\n"
+	              ".backup4	{background: #c060ff;}\n"  /* NOLB state shows same as going down */
+	              ".backup5	{background: #90b0e0;}\n"  /* NOLB state shows same as going down */
+	              ".backup6	{background: #e0e0e0;}\n"
+	              ".maintain	{background: #c07820;}\n"
+	              ".rls      {letter-spacing: 0.2em; margin-right: 1px;}\n" /* right letter spacing (used for grouping digits) */
+	              "\n"
+	              "a.px:link {color: #ffff40; text-decoration: none;}"
+	              "a.px:visited {color: #ffff40; text-decoration: none;}"
+	              "a.px:hover {color: #ffffff; text-decoration: none;}"
+	              "a.lfsb:link {color: #000000; text-decoration: none;}"
+	              "a.lfsb:visited {color: #000000; text-decoration: none;}"
+	              "a.lfsb:hover {color: #505050; text-decoration: none;}"
+	              "\n"
+	              "table.tbl { border-collapse: collapse; border-style: none;}\n"
+	              "table.tbl td { text-align: right; border-width: 1px 1px 1px 1px; border-style: solid solid solid solid; padding: 2px 3px; border-color: gray; white-space: nowrap;}\n"
+	              "table.tbl td.ac { text-align: center;}\n"
+	              "table.tbl th { border-width: 1px; border-style: solid solid solid solid; border-color: gray;}\n"
+	              "table.tbl th.pxname { background: #b00040; color: #ffff40; font-weight: bold; border-style: solid solid none solid; padding: 2px 3px; white-space: nowrap;}\n"
+	              "table.tbl th.empty { border-style: none; empty-cells: hide; background: white;}\n"
+	              "table.tbl th.desc { background: white; border-style: solid solid none solid; text-align: left; padding: 2px 3px;}\n"
+	              "\n"
+	              "table.lgd { border-collapse: collapse; border-width: 1px; border-style: none none none solid; border-color: black;}\n"
+	              "table.lgd td { border-width: 1px; border-style: solid solid solid solid; border-color: gray; padding: 2px;}\n"
+	              "table.lgd td.noborder { border-style: none; padding: 2px; white-space: nowrap;}\n"
+	              "u {text-decoration:none; border-bottom: 1px dotted black;}\n"
+	              "-->\n"
+	              "</style></head>\n",
+	              (uri->flags & ST_SHNODE) ? " on " : "",
+	              (uri->flags & ST_SHNODE) ? (uri->node ? uri->node : global.node) : ""
+	              );
+}
 
-						chunk_appendf(&trash, "'");
-					}
+/* Dumps the HTTP stats information block to the trash for and uses the state from
+ * stream interface <si> and per-uri parameters <uri>. The caller is responsible
+ * for clearing the trash if needed. Returns non-zero if it emits anything, zero
+ * otherwise.
+ */
+static int stats_dump_http_info(struct stream_interface *si, struct proxy *px, struct uri_auth *uri)
+{
+	unsigned int up = (now.tv_sec - start_date.tv_sec);
 
-					chunk_appendf(&trash, "\"");
-				}
+	/* WARNING! this has to fit the first packet too.
+	 * We are around 3.5 kB, add adding entries will
+	 * become tricky if we want to support 4kB buffers !
+	 */
+	if ((si->applet.ctx.stats.flags & STAT_FMT_CSV))
+		return 0;
 
-				chunk_appendf(&trash,
-				     ">%s<a name=\"%s/%s\"></a>"
-				     "<a class=lfsb href=\"#%s/%s\">%s</a>%s</td>"
-				     /* queue : current, max, limit */
-				     "<td>%s</td><td>%s</td><td>%s</td>"
-				     /* sessions rate : current, max, limit */
-				     "<td>%s</td><td>%s</td><td></td>"
-				     /* sessions: current, max, limit */
-				     "<td>%s</td><td>%s</td><td>%s</td>"
-				     "<td"
-				     "",
-				     (uri->flags & ST_SHLGNDS)?"<u>":"",
-				     px->id, sv->id, px->id, sv->id, sv->id,
-				     (uri->flags & ST_SHLGNDS)?"</u>":"",
-				     U2H0(sv->nbpend), U2H1(sv->counters.nbpend_max), LIM2A2(sv->maxqueue, "-"),
-				     U2H3(read_freq_ctr(&sv->sess_per_sec)), U2H4(sv->counters.sps_max),
-				     U2H5(sv->cur_sess), U2H6(sv->counters.cur_sess_max), LIM2A7(sv->maxconn, "-"));
+	chunk_appendf(&trash,
+	              "<body><h1><a href=\"" PRODUCT_URL "\" style=\"text-decoration: none;\">"
+	              PRODUCT_NAME "%s</a></h1>\n"
+	              "<h2>Statistics Report for pid %d%s%s%s%s</h2>\n"
+	              "<hr width=\"100%%\" class=\"hr\">\n"
+	              "<h3>&gt; General process information</h3>\n"
+	              "<table border=0><tr><td align=\"left\" nowrap width=\"1%%\">\n"
+	              "<p><b>pid = </b> %d (process #%d, nbproc = %d)<br>\n"
+	              "<b>uptime = </b> %dd %dh%02dm%02ds<br>\n"
+	              "<b>system limits:</b> memmax = %s%s; ulimit-n = %d<br>\n"
+	              "<b>maxsock = </b> %d; <b>maxconn = </b> %d; <b>maxpipes = </b> %d<br>\n"
+	              "current conns = %d; current pipes = %d/%d; conn rate = %d/sec<br>\n"
+	              "Running tasks: %d/%d; idle = %d %%<br>\n"
+	              "</td><td align=\"center\" nowrap>\n"
+	              "<table class=\"lgd\"><tr>\n"
+	              "<td class=\"active3\">&nbsp;</td><td class=\"noborder\">active UP </td>"
+	              "<td class=\"backup3\">&nbsp;</td><td class=\"noborder\">backup UP </td>"
+	              "</tr><tr>\n"
+	              "<td class=\"active2\"></td><td class=\"noborder\">active UP, going down </td>"
+	              "<td class=\"backup2\"></td><td class=\"noborder\">backup UP, going down </td>"
+	              "</tr><tr>\n"
+	              "<td class=\"active1\"></td><td class=\"noborder\">active DOWN, going up </td>"
+	              "<td class=\"backup1\"></td><td class=\"noborder\">backup DOWN, going up </td>"
+	              "</tr><tr>\n"
+	              "<td class=\"active0\"></td><td class=\"noborder\">active or backup DOWN &nbsp;</td>"
+	              "<td class=\"active6\"></td><td class=\"noborder\">not checked </td>"
+	              "</tr><tr>\n"
+	              "<td class=\"maintain\"></td><td class=\"noborder\" colspan=\"3\">active or backup DOWN for maintenance (MAINT) &nbsp;</td>"
+	              "</tr></table>\n"
+	              "Note: UP with load-balancing disabled is reported as \"NOLB\"."
+	              "</td>"
+	              "<td align=\"left\" valign=\"top\" nowrap width=\"1%%\">"
+	              "<b>Display option:</b><ul style=\"margin-top: 0.25em;\">"
+	              "",
+	              (uri->flags & ST_HIDEVER) ? "" : (STATS_VERSION_STRING),
+	              pid, (uri->flags & ST_SHNODE) ? " on " : "",
+		      (uri->flags & ST_SHNODE) ? (uri->node ? uri->node : global.node) : "",
+	              (uri->flags & ST_SHDESC) ? ": " : "",
+		      (uri->flags & ST_SHDESC) ? (uri->desc ? uri->desc : global.desc) : "",
+	              pid, relative_pid, global.nbproc,
+	              up / 86400, (up % 86400) / 3600,
+	              (up % 3600) / 60, (up % 60),
+	              global.rlimit_memmax ? ultoa(global.rlimit_memmax) : "unlimited",
+	              global.rlimit_memmax ? " MB" : "",
+	              global.rlimit_nofile,
+	              global.maxsock, global.maxconn, global.maxpipes,
+	              actconn, pipes_used, pipes_used+pipes_free, read_freq_ctr(&global.conn_per_sec),
+	              run_queue_cur, nb_tasks_cur, idle_pct
+	              );
 
-				/* http response (via td title): 1xx, 2xx, 3xx, 4xx, 5xx, other */
-				if (px->mode == PR_MODE_HTTP) {
-					int i;
-
-					chunk_appendf(&trash, " title=\"rsp codes:");
-
-					for (i = 1; i < 6; i++)
-						chunk_appendf(&trash, " %dxx=%lld,", i, sv->counters.p.http.rsp[i]);
-
-					chunk_appendf(&trash, " other=%lld\"", sv->counters.p.http.rsp[0]);
-				}
-
-				chunk_appendf(&trash,
-				     /* sessions: total, lbtot */
-				     ">%s%s%s</td><td>%s</td>",
-				     (px->mode == PR_MODE_HTTP)?"<u>":"",
-				     U2H0(sv->counters.cum_sess),
-				     (px->mode == PR_MODE_HTTP)?"</u>":"",
-				     U2H1(sv->counters.cum_lbconn));
-
-				chunk_appendf(&trash,
-				     /* bytes : in, out */
-				     "<td>%s</td><td>%s</td>"
-				     /* denied: req, resp */
-				     "<td></td><td>%s</td>"
-				     /* errors : request, connect */
-				     "<td></td><td>%s</td>"
-				     /* errors : response */
-				     "<td title=\"Connection resets during transfers: %lld client, %lld server\"><u>%s</u></td>"
-				     /* warnings: retries, redispatches */
-				     "<td>%lld</td><td>%lld</td>"
-				     "",
-				     U2H0(sv->counters.bytes_in), U2H1(sv->counters.bytes_out),
-				     U2H2(sv->counters.failed_secu),
-				     U2H3(sv->counters.failed_conns),
-				     sv->counters.cli_aborts,
-				     sv->counters.srv_aborts,
-				     U2H6(sv->counters.failed_resp),
-				     sv->counters.retries, sv->counters.redispatches);
-
-				/* status, lest check */
-				chunk_appendf(&trash, "<td class=ac>");
-
-				if (sv->state & SRV_MAINTAIN) {
-					chunk_appendf(&trash, "%s ",
-						human_time(now.tv_sec - sv->last_change, 1));
-					chunk_appendf(&trash, "MAINT");
-				}
-				else if (svs != sv && svs->state & SRV_MAINTAIN) {
-					chunk_appendf(&trash, "%s ",
-						human_time(now.tv_sec - svs->last_change, 1));
-					chunk_appendf(&trash, "MAINT(via)");
-				}
-				else if (svs->state & SRV_CHECKED) {
-					chunk_appendf(&trash, "%s ",
-						human_time(now.tv_sec - svs->last_change, 1));
-
-					chunk_appendf(&trash,
-					     srv_hlt_st[sv_state],
-					     (svs->state & SRV_RUNNING) ? (svs->health - svs->rise + 1) : (svs->health),
-					     (svs->state & SRV_RUNNING) ? (svs->fall) : (svs->rise));
-				}
-
-				if (sv->state & SRV_CHECKED) {
-					chunk_appendf(&trash, "</td><td class=ac title=\"%s",
-						get_check_status_description(sv->check.status));
-
-					if (*sv->check.desc) {
-						struct chunk src;
-
-						chunk_appendf(&trash, ": ");
-
-						chunk_initlen(&src, sv->check.desc, 0, strlen(sv->check.desc));
-						chunk_htmlencode(&trash, &src);
-					}
-
-					chunk_appendf(&trash, "\"><u> %s%s",
-					        (sv->state & SRV_CHK_RUNNING) ? "* " : "",
-						get_check_status_info(sv->check.status));
-
-					if (sv->check.status >= HCHK_STATUS_L57DATA)
-						chunk_appendf(&trash, "/%d", sv->check.code);
-
-					if (sv->check.status >= HCHK_STATUS_CHECKED && sv->check.duration >= 0)
-					chunk_appendf(&trash, " in %lums</u>", sv->check.duration);
-				} else
-					chunk_appendf(&trash, "</td><td>");
-
-				chunk_appendf(&trash,
-				     /* weight */
-				     "</td><td class=ac>%d</td>"
-				     /* act, bck */
-				     "<td class=ac>%s</td><td class=ac>%s</td>"
-				     "",
-				     (sv->eweight * px->lbprm.wmult + px->lbprm.wdiv - 1) / px->lbprm.wdiv,
-				     (sv->state & SRV_BACKUP) ? "-" : "Y",
-				     (sv->state & SRV_BACKUP) ? "Y" : "-");
-
-				/* check failures: unique, fatal, down time */
-				if (sv->state & SRV_CHECKED) {
-					chunk_appendf(&trash, "<td title=\"Failed Health Checks%s\"><u>%lld",
-					     svs->observe?"/Health Analyses":"", svs->counters.failed_checks);
-
-					if (svs->observe)
-						chunk_appendf(&trash, "/%lld", svs->counters.failed_hana);
-
-					chunk_appendf(&trash,
-					     "</u></td>"
-					     "<td>%lld</td><td>%s</td>"
-					     "",
-					     svs->counters.down_trans, human_time(srv_downtime(sv), 1));
-				} else if (sv != svs)
-					chunk_appendf(&trash,
-					     "<td class=ac colspan=3><a class=lfsb href=\"#%s/%s\">via %s/%s<a></td>",
-							svs->proxy->id, svs->id, svs->proxy->id, svs->id);
-				else
-					chunk_appendf(&trash,
-					     "<td colspan=3></td>");
-
-				/* throttle */
-				if ((sv->state & SRV_WARMINGUP) &&
-				    now.tv_sec < sv->last_change + sv->slowstart &&
-				    now.tv_sec >= sv->last_change) {
-					unsigned int ratio;
-					ratio = MAX(1, 100 * (now.tv_sec - sv->last_change) / sv->slowstart);
-					chunk_appendf(&trash,
-						     "<td class=ac>%d %%</td></tr>\n", ratio);
-				} else {
-					chunk_appendf(&trash,
-						     "<td class=ac>-</td></tr>\n");
-				}
-			} else {
-				static char *srv_hlt_st[7] = { "DOWN,", "DOWN %d/%d,",
-							       "UP %d/%d,", "UP,",
-							       "NOLB %d/%d,", "NOLB,",
-							       "no check," };
-				chunk_appendf(&trash,
-				     /* pxid, name */
-				     "%s,%s,"
-				     /* queue : current, max */
-				     "%d,%d,"
-				     /* sessions : current, max, limit, total */
-				     "%d,%d,%s,%lld,"
-				     /* bytes : in, out */
-				     "%lld,%lld,"
-				     /* denied: req, resp */
-				     ",%lld,"
-				     /* errors : request, connect, response */
-				     ",%lld,%lld,"
-				     /* warnings: retries, redispatches */
-				     "%lld,%lld,"
-				     "",
-				     px->id, sv->id,
-				     sv->nbpend, sv->counters.nbpend_max,
-				     sv->cur_sess, sv->counters.cur_sess_max, LIM2A0(sv->maxconn, ""), sv->counters.cum_sess,
-				     sv->counters.bytes_in, sv->counters.bytes_out,
-				     sv->counters.failed_secu,
-				     sv->counters.failed_conns, sv->counters.failed_resp,
-				     sv->counters.retries, sv->counters.redispatches);
-
-				/* status */
-				if (sv->state & SRV_MAINTAIN) {
-					chunk_appendf(&trash, "MAINT,");
-				}
-				else if (svs != sv && svs->state & SRV_MAINTAIN) {
-					chunk_appendf(&trash, "MAINT(via),");
-				}
-				else {
-					chunk_appendf(&trash,
-					    srv_hlt_st[sv_state],
-					    (svs->state & SRV_RUNNING) ? (svs->health - svs->rise + 1) : (svs->health),
-					    (svs->state & SRV_RUNNING) ? (svs->fall) : (svs->rise));
-				}
+	if (si->applet.ctx.stats.flags & STAT_HIDE_DOWN)
+		chunk_appendf(&trash,
+		              "<li><a href=\"%s%s%s\">Show all servers</a><br>\n",
+		              uri->uri_prefix,
+		              "",
+		              (si->applet.ctx.stats.flags & STAT_NO_REFRESH) ? ";norefresh" : "");
+	else
+		chunk_appendf(&trash,
+		              "<li><a href=\"%s%s%s\">Hide 'DOWN' servers</a><br>\n",
+		              uri->uri_prefix,
+		              ";up",
+		              (si->applet.ctx.stats.flags & STAT_NO_REFRESH) ? ";norefresh" : "");
 
-				chunk_appendf(&trash,
-				     /* weight, active, backup */
-				     "%d,%d,%d,"
-				     "",
-				     (sv->eweight * px->lbprm.wmult + px->lbprm.wdiv - 1) / px->lbprm.wdiv,
-				     (sv->state & SRV_BACKUP) ? 0 : 1,
-				     (sv->state & SRV_BACKUP) ? 1 : 0);
+	if (uri->refresh > 0) {
+		if (si->applet.ctx.stats.flags & STAT_NO_REFRESH)
+			chunk_appendf(&trash,
+			              "<li><a href=\"%s%s%s\">Enable refresh</a><br>\n",
+			              uri->uri_prefix,
+			              (si->applet.ctx.stats.flags & STAT_HIDE_DOWN) ? ";up" : "",
+			              "");
+		else
+			chunk_appendf(&trash,
+			              "<li><a href=\"%s%s%s\">Disable refresh</a><br>\n",
+			              uri->uri_prefix,
+			              (si->applet.ctx.stats.flags & STAT_HIDE_DOWN) ? ";up" : "",
+			              ";norefresh");
+	}
 
-				/* check failures: unique, fatal; last change, total downtime */
-				if (sv->state & SRV_CHECKED)
-					chunk_appendf(&trash,
-					     "%lld,%lld,%d,%d,",
-					     sv->counters.failed_checks, sv->counters.down_trans,
-					     (int)(now.tv_sec - sv->last_change), srv_downtime(sv));
-				else
-					chunk_appendf(&trash,
-					     ",,,,");
+	chunk_appendf(&trash,
+	              "<li><a href=\"%s%s%s\">Refresh now</a><br>\n",
+	              uri->uri_prefix,
+	              (si->applet.ctx.stats.flags & STAT_HIDE_DOWN) ? ";up" : "",
+	              (si->applet.ctx.stats.flags & STAT_NO_REFRESH) ? ";norefresh" : "");
 
-				/* queue limit, pid, iid, sid, */
-				chunk_appendf(&trash,
-				     "%s,"
-				     "%d,%d,%d,",
-				     LIM2A0(sv->maxqueue, ""),
-				     relative_pid, px->uuid, sv->puid);
+	chunk_appendf(&trash,
+	              "<li><a href=\"%s;csv%s\">CSV export</a><br>\n",
+	              uri->uri_prefix,
+	              (uri->refresh > 0) ? ";norefresh" : "");
 
-				/* throttle */
-				if ((sv->state & SRV_WARMINGUP) &&
-				    now.tv_sec < sv->last_change + sv->slowstart &&
-				    now.tv_sec >= sv->last_change) {
-					unsigned int ratio;
-					ratio = MAX(1, 100 * (now.tv_sec - sv->last_change) / sv->slowstart);
-					chunk_appendf(&trash, "%d", ratio);
-				}
+	chunk_appendf(&trash,
+	              "</ul></td>"
+	              "<td align=\"left\" valign=\"top\" nowrap width=\"1%%\">"
+	              "<b>External resources:</b><ul style=\"margin-top: 0.25em;\">\n"
+	              "<li><a href=\"" PRODUCT_URL "\">Primary site</a><br>\n"
+	              "<li><a href=\"" PRODUCT_URL_UPD "\">Updates (v" PRODUCT_BRANCH ")</a><br>\n"
+	              "<li><a href=\"" PRODUCT_URL_DOC "\">Online manual</a><br>\n"
+	              "</ul>"
+	              "</td>"
+	              "</tr></table>\n"
+	              ""
+	              );
 
-				/* sessions: lbtot */
-				chunk_appendf(&trash, ",%lld,", sv->counters.cum_lbconn);
+	if (si->applet.ctx.stats.st_code) {
+		switch (si->applet.ctx.stats.st_code) {
+		case STAT_STATUS_DONE:
+			chunk_appendf(&trash,
+			              "<p><div class=active3>"
+			              "<a class=lfsb href=\"%s\" title=\"Remove this message\">[X]</a> "
+			              "Action processed successfully."
+			              "</div>\n", uri->uri_prefix);
+			break;
+		case STAT_STATUS_NONE:
+			chunk_appendf(&trash,
+			              "<p><div class=active2>"
+			              "<a class=lfsb href=\"%s\" title=\"Remove this message\">[X]</a> "
+			              "Nothing has changed."
+			              "</div>\n", uri->uri_prefix);
+			break;
+		case STAT_STATUS_PART:
+			chunk_appendf(&trash,
+			              "<p><div class=active2>"
+			              "<a class=lfsb href=\"%s\" title=\"Remove this message\">[X]</a> "
+			              "Action partially processed.<br>"
+			              "Some server names are probably unknown or ambiguous (duplicated names in the backend)."
+			              "</div>\n", uri->uri_prefix);
+			break;
+		case STAT_STATUS_ERRP:
+			chunk_appendf(&trash,
+			              "<p><div class=active0>"
+			              "<a class=lfsb href=\"%s\" title=\"Remove this message\">[X]</a> "
+			              "Action not processed because of invalid parameters."
+			              "<ul>"
+			              "<li>The action is maybe unknown.</li>"
+			              "<li>The backend name is probably unknown or ambiguous (duplicated names).</li>"
+			              "<li>Some server names are probably unknown or ambiguous (duplicated names in the backend).</li>"
+			              "</ul>"
+			              "</div>\n", uri->uri_prefix);
+			break;
+		case STAT_STATUS_EXCD:
+			chunk_appendf(&trash,
+			              "<p><div class=active0>"
+			              "<a class=lfsb href=\"%s\" title=\"Remove this message\">[X]</a> "
+			              "<b>Action not processed : the buffer couldn't store all the data.<br>"
+			              "You should retry with less servers at a time.</b>"
+			              "</div>\n", uri->uri_prefix);
+			break;
+		case STAT_STATUS_DENY:
+			chunk_appendf(&trash,
+			              "<p><div class=active0>"
+			              "<a class=lfsb href=\"%s\" title=\"Remove this message\">[X]</a> "
+			              "<b>Action denied.</b>"
+			              "</div>\n", uri->uri_prefix);
+			break;
+		default:
+			chunk_appendf(&trash,
+			              "<p><div class=active6>"
+			              "<a class=lfsb href=\"%s\" title=\"Remove this message\">[X]</a> "
+			              "Unexpected result."
+			              "</div>\n", uri->uri_prefix);
+		}
+		chunk_appendf(&trash, "<p>\n");
+	}
+	return 1;
+}
 
-				/* tracked */
-				if (sv->track)
-					chunk_appendf(&trash, "%s/%s,",
-						sv->track->proxy->id, sv->track->id);
-				else
-					chunk_appendf(&trash, ",");
+/* Dumps the HTTP stats trailer block to the trash for and uses the state from
+ * stream interface <si> and per-uri parameters <uri>. The caller is responsible
+ * for clearing the trash if needed. Returns non-zero if it emits anything, zero
+ * otherwise.
+ */
+static int stats_dump_http_end(struct stream_interface *si, struct proxy *px, struct uri_auth *uri)
+{
+	if (si->applet.ctx.stats.flags & STAT_FMT_CSV)
+		return 0;
 
-				/* type */
-				chunk_appendf(&trash, "%d,", STATS_TYPE_SV);
+	chunk_appendf(&trash, "</body></html>\n");
+	return 1;
+}
 
-				/* rate */
-				chunk_appendf(&trash, "%u,,%u,",
-					     read_freq_ctr(&sv->sess_per_sec),
-					     sv->counters.sps_max);
+/* This function dumps statistics in HTTP format onto the stream interface's
+ * read buffer. The xprt_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.
+ */
+static int stats_dump_http(struct stream_interface *si, struct uri_auth *uri)
+{
+	struct session *s = si->conn->xprt_ctx;
+	struct channel *rep = si->ib;
+	struct proxy *px;
 
-				if (sv->state & SRV_CHECKED) {
-					/* check_status */
-					chunk_appendf(&trash, "%s,", get_check_status_info(sv->check.status));
+	chunk_reset(&trash);
 
-					/* check_code */
-					if (sv->check.status >= HCHK_STATUS_L57DATA)
-						chunk_appendf(&trash, "%u,", sv->check.code);
-					else
-						chunk_appendf(&trash, ",");
+	switch (si->conn->xprt_st) {
+	case STAT_ST_INIT:
+		chunk_appendf(&trash,
+			     "HTTP/1.0 200 OK\r\n"
+			     "Cache-Control: no-cache\r\n"
+			     "Connection: close\r\n"
+			     "Content-Type: %s\r\n",
+			     (si->applet.ctx.stats.flags & STAT_FMT_CSV) ? "text/plain" : "text/html");
 
-					/* check_duration */
-					if (sv->check.status >= HCHK_STATUS_CHECKED)
-						chunk_appendf(&trash, "%lu,", sv->check.duration);
-					else
-						chunk_appendf(&trash, ",");
+		if (uri->refresh > 0 && !(si->applet.ctx.stats.flags & STAT_NO_REFRESH))
+			chunk_appendf(&trash, "Refresh: %d\r\n",
+				     uri->refresh);
 
-				} else {
-					chunk_appendf(&trash, ",,,");
-				}
+		chunk_appendf(&trash, "\r\n");
 
-				/* http response: 1xx, 2xx, 3xx, 4xx, 5xx, other */
-				if (px->mode == PR_MODE_HTTP) {
-					int i;
+		s->txn.status = 200;
+		if (bi_putchk(rep, &trash) == -1)
+			return 0;
 
-					for (i=1; i<6; i++)
-						chunk_appendf(&trash, "%lld,", sv->counters.p.http.rsp[i]);
+		if (!(s->flags & SN_ERR_MASK))  // this is not really an error but it is
+			s->flags |= SN_ERR_PRXCOND; // to mark that it comes from the proxy
+		if (!(s->flags & SN_FINST_MASK))
+			s->flags |= SN_FINST_R;
 
-					chunk_appendf(&trash, "%lld,", sv->counters.p.http.rsp[0]);
-				} else {
-					chunk_appendf(&trash, ",,,,,,");
-				}
+		if (s->txn.meth == HTTP_METH_HEAD) {
+			/* that's all we return in case of HEAD request */
+			si->conn->xprt_st = STAT_ST_FIN;
+			return 1;
+		}
 
-				/* failed health analyses */
-				chunk_appendf(&trash, "%lld,",  sv->counters.failed_hana);
+		si->conn->xprt_st = STAT_ST_HEAD; /* let's start producing data */
+		/* fall through */
 
-				/* requests : req_rate, req_rate_max, req_tot, */
-				chunk_appendf(&trash, ",,,");
+	case STAT_ST_HEAD:
+		if (si->applet.ctx.stats.flags & STAT_FMT_CSV)
+			stats_dump_csv_header();
+		else
+			stats_dump_html_head(si, px, uri);
 
-				/* errors: cli_aborts, srv_aborts */
-				chunk_appendf(&trash, "%lld,%lld,",
-					     sv->counters.cli_aborts, sv->counters.srv_aborts);
+		if (bi_putchk(rep, &trash) == -1)
+			return 0;
 
-				/* compression: in, out, bypassed, comp_rsp */
-				chunk_appendf(&trash, ",,,,");
+		si->conn->xprt_st = STAT_ST_INFO;
+		/* fall through */
 
-				/* finish with EOL */
-				chunk_appendf(&trash, "\n");
-			}
+	case STAT_ST_INFO:
+		if (stats_dump_http_info(si, px, uri)) {
 			if (bi_putchk(rep, &trash) == -1)
 				return 0;
-		} /* for sv */
+		}
 
-		si->applet.ctx.stats.px_st = STAT_PX_ST_BE;
+		si->applet.ctx.stats.px = proxy;
+		si->applet.ctx.stats.px_st = STAT_PX_ST_INIT;
+		si->conn->xprt_st = STAT_ST_LIST;
 		/* fall through */
 
-	case STAT_PX_ST_BE:
-		/* print the backend */
-		if ((px->cap & PR_CAP_BE) &&
-		    (!(si->applet.ctx.stats.flags & STAT_BOUND) || (si->applet.ctx.stats.type & (1 << STATS_TYPE_BE)))) {
-			if (!(si->applet.ctx.stats.flags & STAT_FMT_CSV)) {
-				chunk_appendf(&trash, "<tr class=\"backend\">");
-				if (px->cap & PR_CAP_BE && px->srv && (si->applet.ctx.stats.flags & STAT_ADMIN)) {
-					/* Column sub-heading for Enable or Disable server */
-					chunk_appendf(&trash, "<td></td>");
-				}
-				chunk_appendf(&trash, "<td class=ac");
-
-				if (uri->flags&ST_SHLGNDS) {
-					/* balancing */
-					 chunk_appendf(&trash, " title=\"balancing: %s",
-						 backend_lb_algo_str(px->lbprm.algo & BE_LB_ALGO));
-
-					/* cookie */
-					if (px->cookie_name) {
-						struct chunk src;
-
-						chunk_appendf(&trash, ", cookie: '");
-
-						chunk_initlen(&src, px->cookie_name, 0, strlen(px->cookie_name));
-						chunk_htmlencode(&trash, &src);
-
-						chunk_appendf(&trash, "'");
-					}
-
-					chunk_appendf(&trash, "\"");
-
-				}
-
-				chunk_appendf(&trash,
-				     /* name */
-				     ">%s<a name=\"%s/Backend\"></a>"
-				     "<a class=lfsb href=\"#%s/Backend\">Backend</a>%s</td>"
-				     /* queue : current, max */
-				     "<td>%s</td><td>%s</td><td></td>"
-				     /* sessions rate : current, max, limit */
-				     "<td>%s</td><td>%s</td><td></td>"
-				     "",
-				     (uri->flags & ST_SHLGNDS)?"<u>":"",
-				     px->id, px->id,
-				     (uri->flags & ST_SHLGNDS)?"</u>":"",
-				     U2H0(px->nbpend) /* or px->totpend ? */, U2H1(px->be_counters.nbpend_max),
-				     U2H2(read_freq_ctr(&px->be_sess_per_sec)), U2H3(px->be_counters.sps_max));
-
-				chunk_appendf(&trash,
-				     /* sessions: current, max, limit */
-				     "<td>%s</td><td>%s</td><td>%s</td>"
-				     "<td"
-				     "",
-				     U2H2(px->beconn), U2H3(px->be_counters.conn_max), U2H4(px->fullconn));
-
-				/* http response (via td title): 1xx, 2xx, 3xx, 4xx, 5xx, other */
-				if (px->mode == PR_MODE_HTTP) {
-					int i;
+	case STAT_ST_LIST:
+		/* dump proxies */
+		while (si->applet.ctx.stats.px) {
+			if (buffer_almost_full(rep->buf))
+				return 0;
 
-					chunk_appendf(&trash, " title=\"%lld requests:", px->be_counters.p.http.cum_req);
+			px = si->applet.ctx.stats.px;
+			/* skip the disabled proxies, global frontend and non-networked ones */
+			if (px->state != PR_STSTOPPED && px->uuid > 0 && (px->cap & (PR_CAP_FE | PR_CAP_BE)))
+				if (stats_dump_proxy(si, px, uri) == 0)
+					return 0;
 
-					for (i = 1; i < 6; i++)
-						chunk_appendf(&trash, " %dxx=%lld", i, px->be_counters.p.http.rsp[i]);
+			si->applet.ctx.stats.px = px->next;
+			si->applet.ctx.stats.px_st = STAT_PX_ST_INIT;
+		}
+		/* here, we just have reached the last proxy */
 
-					chunk_appendf(&trash, " other=%lld ", px->be_counters.p.http.rsp[0]);
-					chunk_appendf(&trash, " compressed=%lld (%d%%)\"",
-					              px->be_counters.p.http.comp_rsp,
-					              px->be_counters.p.http.rsp[2] ?
-					              (int)(100*px->be_counters.p.http.comp_rsp/px->be_counters.p.http.rsp[2]) : 0);
-				}
+		si->conn->xprt_st = STAT_ST_END;
+		/* fall through */
 
-				chunk_appendf(&trash,
-				     /* sessions: total, lbtot */
-				     ">%s%s%s</td><td>%s</td>"
-				     /* bytes: in */
-				     "<td>%s</td><td"
-				     "",
-				     (px->mode == PR_MODE_HTTP)?"<u>":"",
-				     U2H6(px->be_counters.cum_conn),
-				     (px->mode == PR_MODE_HTTP)?"</u>":"",
-				     U2H7(px->be_counters.cum_lbconn),
-				     U2H8(px->be_counters.bytes_in));
+	case STAT_ST_END:
+		if (stats_dump_http_end(si, px, uri)) {
+			if (bi_putchk(rep, &trash) == -1)
+				return 0;
+		}
 
-				/* compression stats (via td title): comp_in, comp_out, comp_byp */
-				chunk_appendf(&trash, " title=\"compression: in=%lld out=%lld bypassed=%lld savings=%d%%\"",
-				     px->be_counters.comp_in, px->be_counters.comp_out, px->be_counters.comp_byp,
-				     px->be_counters.comp_in ?
-				     (int)((px->be_counters.comp_in - px->be_counters.comp_out)*100/px->be_counters.comp_in) : 0);
+		si->conn->xprt_st = STAT_ST_FIN;
+		/* fall through */
 
-				chunk_appendf(&trash,
-				     /* bytes: out */
-				     ">%s%s%s</td>"
-				     "",
-				     (px->be_counters.comp_in || px->be_counters.comp_byp) ? "<u>":"",
-				     U2H0(px->be_counters.bytes_out),
-				     (px->be_counters.comp_in || px->be_counters.comp_byp) ? "</u>":"");
+	case STAT_ST_FIN:
+		return 1;
 
-				chunk_appendf(&trash,
-				     /* denied: req, resp */
-				     "<td>%s</td><td>%s</td>"
-				     /* errors : request, connect */
-				     "<td></td><td>%s</td>"
-				     /* errors : response */
-				     "<td title=\"Connection resets during transfers: %lld client, %lld server\"><u>%s</u></td>"
-				     /* warnings: retries, redispatches */
-				     "<td>%lld</td><td>%lld</td>"
-				     /* backend status: reflect backend status (up/down): we display UP
-				      * if the backend has known working servers or if it has no server at
-				      * all (eg: for stats). Then we display the total weight, number of
-				      * active and backups. */
-				     "<td class=ac>%s %s</td><td class=ac>&nbsp;</td><td class=ac>%d</td>"
-				     "<td class=ac>%d</td><td class=ac>%d</td>"
-				     "",
-				     U2H0(px->be_counters.denied_req), U2H1(px->be_counters.denied_resp),
-				     U2H2(px->be_counters.failed_conns),
-				     px->be_counters.cli_aborts,
-				     px->be_counters.srv_aborts,
-				     U2H5(px->be_counters.failed_resp),
-				     px->be_counters.retries, px->be_counters.redispatches,
-				     human_time(now.tv_sec - px->last_change, 1),
-				     (px->lbprm.tot_weight > 0 || !px->srv) ? "UP" :
-					     "<font color=\"red\"><b>DOWN</b></font>",
-				     (px->lbprm.tot_weight * px->lbprm.wmult + px->lbprm.wdiv - 1) / px->lbprm.wdiv,
-				     px->srv_act, px->srv_bck);
+	default:
+		/* unknown state ! */
+		si->conn->xprt_st = STAT_ST_FIN;
+		return -1;
+	}
+}
 
-				chunk_appendf(&trash,
-				     /* rest of backend: nothing, down transitions, total downtime, throttle */
-				     "<td class=ac>&nbsp;</td><td>%d</td>"
-				     "<td>%s</td>"
-				     "<td></td>"
-				     "</tr>",
-				     px->down_trans,
-				     px->srv?human_time(be_downtime(px), 1):"&nbsp;");
-			} else {
-				chunk_appendf(&trash,
-				     /* pxid, name */
-				     "%s,BACKEND,"
-				     /* queue : current, max */
-				     "%d,%d,"
-				     /* sessions : current, max, limit, total */
-				     "%d,%d,%d,%lld,"
-				     /* bytes : in, out */
-				     "%lld,%lld,"
-				     /* denied: req, resp */
-				     "%lld,%lld,"
-				     /* errors : request, connect, response */
-				     ",%lld,%lld,"
-				     /* warnings: retries, redispatches */
-				     "%lld,%lld,"
-				     /* backend status: reflect backend status (up/down): we display UP
-				      * if the backend has known working servers or if it has no server at
-				      * all (eg: for stats). Then we display the total weight, number of
-				      * active and backups. */
-				     "%s,"
-				     "%d,%d,%d,"
-				     /* rest of backend: nothing, down transitions, last change, total downtime */
-				     ",%d,%d,%d,,"
-				     /* pid, iid, sid, throttle, lbtot, tracked, type */
-				     "%d,%d,0,,%lld,,%d,"
-				     /* rate, rate_lim, rate_max, */
-				     "%u,,%u,"
-				     /* check_status, check_code, check_duration */
-				     ",,,",
-				     px->id,
-				     px->nbpend /* or px->totpend ? */, px->be_counters.nbpend_max,
-				     px->beconn, px->be_counters.conn_max, px->fullconn, px->be_counters.cum_conn,
-				     px->be_counters.bytes_in, px->be_counters.bytes_out,
-				     px->be_counters.denied_req, px->be_counters.denied_resp,
-				     px->be_counters.failed_conns, px->be_counters.failed_resp,
-				     px->be_counters.retries, px->be_counters.redispatches,
-				     (px->lbprm.tot_weight > 0 || !px->srv) ? "UP" : "DOWN",
-				     (px->lbprm.tot_weight * px->lbprm.wmult + px->lbprm.wdiv - 1) / px->lbprm.wdiv,
-				     px->srv_act, px->srv_bck,
-				     px->down_trans, (int)(now.tv_sec - px->last_change),
-				     px->srv?be_downtime(px):0,
-				     relative_pid, px->uuid,
-				     px->be_counters.cum_lbconn, STATS_TYPE_BE,
-				     read_freq_ctr(&px->be_sess_per_sec),
-				     px->be_counters.sps_max);
+/* We don't want to land on the posted stats page because a refresh will
+ * repost the data.  We don't want this to happen on accident so we redirect
+ * the browse to the stats page with a GET.
+ */
+static int stats_http_redir(struct stream_interface *si, struct uri_auth *uri)
+{
+	struct session *s = si->conn->xprt_ctx;
 
-				/* http response: 1xx, 2xx, 3xx, 4xx, 5xx, other */
-				if (px->mode == PR_MODE_HTTP) {
-					int i;
+	chunk_reset(&trash);
 
-					for (i=1; i<6; i++)
-						chunk_appendf(&trash, "%lld,", px->be_counters.p.http.rsp[i]);
+	switch (si->conn->xprt_st) {
+	case STAT_ST_INIT:
+		chunk_appendf(&trash,
+			"HTTP/1.0 303 See Other\r\n"
+			"Cache-Control: no-cache\r\n"
+			"Content-Type: text/plain\r\n"
+			"Connection: close\r\n"
+			"Location: %s;st=%s",
+			uri->uri_prefix,
+			((si->applet.ctx.stats.st_code > STAT_STATUS_INIT) &&
+			 (si->applet.ctx.stats.st_code < STAT_STATUS_SIZE) &&
+			 stat_status_codes[si->applet.ctx.stats.st_code]) ?
+				stat_status_codes[si->applet.ctx.stats.st_code] :
+				stat_status_codes[STAT_STATUS_UNKN]);
+		chunk_appendf(&trash, "\r\n\r\n");
 
-					chunk_appendf(&trash, "%lld,", px->be_counters.p.http.rsp[0]);
-				} else {
-					chunk_appendf(&trash, ",,,,,,");
-				}
+		if (bi_putchk(si->ib, &trash) == -1)
+			return 0;
 
-				/* failed health analyses */
-				chunk_appendf(&trash, ",");
+		s->txn.status = 303;
 
-				/* requests : req_rate, req_rate_max, req_tot, */
-				chunk_appendf(&trash, ",,,");
+		if (!(s->flags & SN_ERR_MASK))  // this is not really an error but it is
+			s->flags |= SN_ERR_PRXCOND; // to mark that it comes from the proxy
+		if (!(s->flags & SN_FINST_MASK))
+			s->flags |= SN_FINST_R;
 
-				/* errors: cli_aborts, srv_aborts */
-				chunk_appendf(&trash, "%lld,%lld,",
-					     px->be_counters.cli_aborts, px->be_counters.srv_aborts);
+		si->conn->xprt_st = STAT_ST_FIN;
+		return 1;
+	}
+	return 1;
+}
 
-				/* compression: in, out, bypassed */
-				chunk_appendf(&trash, "%lld,%lld,%lld,",
-			              px->be_counters.comp_in, px->be_counters.comp_out, px->be_counters.comp_byp);
+/* This I/O handler runs as an applet embedded in a stream interface. It is
+ * used to send HTTP stats over a TCP socket. The mechanism is very simple.
+ * si->applet.st0 becomes non-zero once the transfer is finished. The handler
+ * automatically unregisters itself once transfer is complete.
+ */
+static void http_stats_io_handler(struct stream_interface *si)
+{
+	struct session *s = si->conn->xprt_ctx;
+	struct channel *req = si->ob;
+	struct channel *res = si->ib;
 
-				/* compression: comp_rsp */
-				chunk_appendf(&trash, "%lld,",
-			              px->be_counters.p.http.comp_rsp);
+	if (unlikely(si->state == SI_ST_DIS || si->state == SI_ST_CLO))
+		goto out;
 
-				/* finish with EOL */
-				chunk_appendf(&trash, "\n");
+	/* check that the output is not closed */
+	if (res->flags & (CF_SHUTW|CF_SHUTW_NOW))
+		si->applet.st0 = 1;
 
+	if (!si->applet.st0) {
+		if (s->txn.meth == HTTP_METH_POST) {
+			if (stats_http_redir(si, s->be->uri_auth)) {
+				si->applet.st0 = 1;
+				si_shutw(si);
 			}
-			if (bi_putchk(rep, &trash) == -1)
-				return 0;
-		}
-
-		si->applet.ctx.stats.px_st = STAT_PX_ST_END;
-		/* fall through */
-
-	case STAT_PX_ST_END:
-		if (!(si->applet.ctx.stats.flags & STAT_FMT_CSV)) {
-			chunk_appendf(&trash, "</table>");
-
-			if (px->cap & PR_CAP_BE && px->srv && (si->applet.ctx.stats.flags & STAT_ADMIN)) {
-				/* close the form used to enable/disable this proxy servers */
-				chunk_appendf(&trash,
-					"Choose the action to perform on the checked servers : "
-					"<select name=action>"
-					"<option value=\"\"></option>"
-					"<option value=\"disable\">Disable</option>"
-					"<option value=\"enable\">Enable</option>"
-					"<option value=\"stop\">Soft Stop</option>"
-					"<option value=\"start\">Soft Start</option>"
-					"<option value=\"shutdown\">Kill Sessions</option>"
-					"</select>"
-					"<input type=\"hidden\" name=\"b\" value=\"#%d\">"
-					"&nbsp;<input type=\"submit\" value=\"Apply\">"
-					"</form>",
-					px->uuid);
+		} else {
+			if (stats_dump_http(si, s->be->uri_auth)) {
+				si->applet.st0 = 1;
+				si_shutw(si);
 			}
+		}
+	}
 
-			chunk_appendf(&trash, "<p>\n");
+	if ((res->flags & CF_SHUTR) && (si->state == SI_ST_EST))
+		si_shutw(si);
 
-			if (bi_putchk(rep, &trash) == -1)
-				return 0;
-		}
+	if ((req->flags & CF_SHUTW) && (si->state == SI_ST_EST) && si->applet.st0) {
+		si_shutr(si);
+		res->flags |= CF_READ_NULL;
+	}
 
-		si->applet.ctx.stats.px_st = STAT_PX_ST_FIN;
-		/* fall through */
+	/* update all other flags and resync with the other side */
+	si_update(si);
 
-	case STAT_PX_ST_FIN:
-		return 1;
+	/* we don't want to expire timeouts while we're processing requests */
+	si->ib->rex = TICK_ETERNITY;
+	si->ob->wex = TICK_ETERNITY;
 
-	default:
-		/* unknown state, we should put an abort() here ! */
-		return 1;
+ out:
+	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);
 	}
 }
 
+
 static inline const char *get_conn_ctrl_name(const struct connection *conn)
 {
 	if (!conn->ctrl)
diff --git a/src/proto_http.c b/src/proto_http.c
index 5354284..f6535f2 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -7631,9 +7631,6 @@
 		}
 		h++;
 	}
-
-	si->applet.ctx.stats.flags |= STAT_SHOW_STAT | STAT_SHOW_INFO;
-
 	return 1;
 }