REORG: stats: dump the server stats via the generic function

The code was simply moved as-is to the new function. There's no
functional change.
diff --git a/src/dumpstats.c b/src/dumpstats.c
index 00c9bfe..83cd86a 100644
--- a/src/dumpstats.c
+++ b/src/dumpstats.c
@@ -3254,6 +3254,8 @@
  */
 static int stats_dump_fields_html(const struct field *stats, int admin, unsigned int flags, struct proxy *px)
 {
+	struct chunk src;
+
 	if (stats[ST_F_TYPE].u.u32 == STATS_TYPE_FE) {
 		chunk_appendf(&trash,
 		              /* name, queue */
@@ -3453,155 +3455,431 @@
 		              U2H(stats[ST_F_EREQ].u.u64),
 		              field_str(stats, ST_F_STATUS));
 	}
-	return 1;
-}
+	else if (stats[ST_F_TYPE].u.u32 == STATS_TYPE_SV) {
+		const char *style;
 
-/* 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_dump_fe_stats(struct stream_interface *si, struct proxy *px)
-{
-	struct appctx *appctx = __objt_appctx(si->end);
+		/* determine the style to use depending on the server's state,
+		 * its health and weight. There isn't a 1-to-1 mapping between
+		 * state and styles for the cases where the server is (still)
+		 * up. The reason is that we don't want to report nolb and
+		 * drain with the same color.
+		 */
 
-	if (!(px->cap & PR_CAP_FE))
-		return 0;
+		if (strcmp(field_str(stats, ST_F_STATUS), "DOWN") == 0 ||
+		    strcmp(field_str(stats, ST_F_STATUS), "DOWN (agent)") == 0) {
+			style = "down";
+		}
+		else if (strcmp(field_str(stats, ST_F_STATUS), "DOWN ") == 0) {
+			style = "going_up";
+		}
+		else if (strcmp(field_str(stats, ST_F_STATUS), "NOLB ") == 0) {
+			style = "going_down";
+		}
+		else if (strcmp(field_str(stats, ST_F_STATUS), "NOLB") == 0) {
+			style = "nolb";
+		}
+		else if (strcmp(field_str(stats, ST_F_STATUS), "no check") == 0) {
+			style = "no_check";
+		}
+		else if (!stats[ST_F_CHKFAIL].type ||
+			 stats[ST_F_CHECK_HEALTH].u.u32 == stats[ST_F_CHECK_RISE].u.u32 + stats[ST_F_CHECK_FALL].u.u32 - 1) {
+			/* no check or max health = UP */
+			if (stats[ST_F_WEIGHT].u.u32)
+				style = "up";
+			else
+				style = "draining";
+		}
+		else {
+			style = "going_down";
+		}
 
-	if ((appctx->ctx.stats.flags & STAT_BOUND) && !(appctx->ctx.stats.type & (1 << STATS_TYPE_FE)))
-		return 0;
+		if (memcmp(field_str(stats, ST_F_STATUS), "MAINT", 5) == 0)
+			chunk_appendf(&trash, "<tr class=\"maintain\">");
+		else
+			chunk_appendf(&trash,
+			              "<tr class=\"%s_%s\">",
+			              (stats[ST_F_BCK].u.u32) ? "backup" : "active", style);
 
-	memset(&stats, 0, sizeof(stats));
 
-	stats[ST_F_PXNAME]   = mkf_str(FO_KEY|FN_NAME|FS_SERVICE, px->id);
-	stats[ST_F_SVNAME]   = mkf_str(FO_KEY|FN_NAME|FS_SERVICE, "FRONTEND");
-	stats[ST_F_SCUR]     = mkf_u32(0, px->feconn);
-	stats[ST_F_SMAX]     = mkf_u32(FN_MAX, px->fe_counters.conn_max);
-	stats[ST_F_SLIM]     = mkf_u32(FO_CONFIG|FN_LIMIT, px->maxconn);
-	stats[ST_F_STOT]     = mkf_u64(FN_COUNTER, px->fe_counters.cum_sess);
-	stats[ST_F_BIN]      = mkf_u64(FN_COUNTER, px->fe_counters.bytes_in);
-	stats[ST_F_BOUT]     = mkf_u64(FN_COUNTER, px->fe_counters.bytes_out);
-	stats[ST_F_DREQ]     = mkf_u64(FN_COUNTER, px->fe_counters.denied_req);
-	stats[ST_F_DRESP]    = mkf_u64(FN_COUNTER, px->fe_counters.denied_resp);
-	stats[ST_F_EREQ]     = mkf_u64(FN_COUNTER, px->fe_counters.failed_req);
-	stats[ST_F_STATUS]   = mkf_str(FO_STATUS, px->state == PR_STREADY ? "OPEN" : px->state == PR_STFULL ? "FULL" : "STOP");
-	stats[ST_F_PID]      = mkf_u32(FO_KEY, relative_pid);
-	stats[ST_F_IID]      = mkf_u32(FO_KEY|FS_SERVICE, px->uuid);
-	stats[ST_F_SID]      = mkf_u32(FO_KEY|FS_SERVICE, 0);
-	stats[ST_F_TYPE]     = mkf_u32(FO_CONFIG|FS_SERVICE, STATS_TYPE_FE);
-	stats[ST_F_RATE]     = mkf_u32(FN_RATE, read_freq_ctr(&px->fe_sess_per_sec));
-	stats[ST_F_RATE_LIM] = mkf_u32(FO_CONFIG|FN_LIMIT, px->fe_sps_lim);
-	stats[ST_F_RATE_MAX] = mkf_u32(FN_MAX, px->fe_counters.sps_max);
+		if (admin)
+			chunk_appendf(&trash,
+			              "<td><input type=\"checkbox\" name=\"s\" value=\"%s\"></td>",
+			              field_str(stats, ST_F_SVNAME));
 
-	/* http response: 1xx, 2xx, 3xx, 4xx, 5xx, other */
-	if (px->mode == PR_MODE_HTTP) {
-		stats[ST_F_HRSP_1XX]    = mkf_u64(FN_COUNTER, px->fe_counters.p.http.rsp[1]);
-		stats[ST_F_HRSP_2XX]    = mkf_u64(FN_COUNTER, px->fe_counters.p.http.rsp[2]);
-		stats[ST_F_HRSP_3XX]    = mkf_u64(FN_COUNTER, px->fe_counters.p.http.rsp[3]);
-		stats[ST_F_HRSP_4XX]    = mkf_u64(FN_COUNTER, px->fe_counters.p.http.rsp[4]);
-		stats[ST_F_HRSP_5XX]    = mkf_u64(FN_COUNTER, px->fe_counters.p.http.rsp[5]);
-		stats[ST_F_HRSP_OTHER] = mkf_u64(FN_COUNTER, px->fe_counters.p.http.rsp[0]);
-	}
+		chunk_appendf(&trash,
+		              "<td class=ac><a name=\"%s/%s\"></a>%s"
+		              "<a class=lfsb href=\"#%s/%s\">%s</a>"
+		              "",
+		              field_str(stats, ST_F_PXNAME), field_str(stats, ST_F_SVNAME),
+		              (flags & ST_SHLGNDS) ? "<u>" : "",
+		              field_str(stats, ST_F_PXNAME), field_str(stats, ST_F_SVNAME), field_str(stats, ST_F_SVNAME));
 
-	/* requests : req_rate, req_rate_max, req_tot, */
-	stats[ST_F_REQ_RATE]     = mkf_u32(FN_RATE, read_freq_ctr(&px->fe_req_per_sec));
-	stats[ST_F_REQ_RATE_MAX] = mkf_u32(FN_MAX, px->fe_counters.p.http.rps_max);
-	stats[ST_F_REQ_TOT]      = mkf_u64(FN_COUNTER, px->fe_counters.p.http.cum_req);
+		if (flags & ST_SHLGNDS) {
+			chunk_appendf(&trash, "<div class=tips>");
 
-	/* compression: in, out, bypassed, responses */
-	stats[ST_F_COMP_IN]      = mkf_u64(FN_COUNTER, px->fe_counters.comp_in);
-	stats[ST_F_COMP_OUT]     = mkf_u64(FN_COUNTER, px->fe_counters.comp_out);
-	stats[ST_F_COMP_BYP]     = mkf_u64(FN_COUNTER, px->fe_counters.comp_byp);
-	stats[ST_F_COMP_RSP]     = mkf_u64(FN_COUNTER, px->fe_counters.p.http.comp_rsp);
+			if (isdigit(*field_str(stats, ST_F_ADDR)))
+				chunk_appendf(&trash, "IPv4: %s, ", field_str(stats, ST_F_ADDR));
+			else if (*field_str(stats, ST_F_ADDR) == '[')
+				chunk_appendf(&trash, "IPv6: %s, ", field_str(stats, ST_F_ADDR));
+			else if (*field_str(stats, ST_F_ADDR))
+				chunk_appendf(&trash, "%s, ", field_str(stats, ST_F_ADDR));
 
-	if (appctx->ctx.stats.flags & STAT_FMT_HTML) {
-		int admin;
+			/* id */
+			chunk_appendf(&trash, "id: %d", stats[ST_F_SID].u.u32);
 
-		admin = (px->cap & PR_CAP_BE) && px->srv && (appctx->ctx.stats.flags & STAT_ADMIN);
-		stats_dump_fields_html(stats, admin, 0, px);
-	}
-	else { /* CSV mode */
-		/* dump everything */
-		stats_dump_fields_csv(&trash, stats);
-	}
-	return 1;
-}
+			/* cookie */
+			if (stats[ST_F_COOKIE].type) {
+				chunk_appendf(&trash, ", cookie: '");
+				chunk_initstr(&src, field_str(stats, ST_F_COOKIE));
+				chunk_htmlencode(&trash, &src);
+				chunk_appendf(&trash, "'");
+			}
 
-/* 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_li_stats(struct stream_interface *si, struct proxy *px, struct listener *l, int flags)
-{
-	struct appctx *appctx = __objt_appctx(si->end);
-	struct chunk *out = get_trash_chunk();
+			chunk_appendf(&trash, "</div>");
+		}
 
-	chunk_reset(out);
-	memset(&stats, 0, sizeof(stats));
+		chunk_appendf(&trash,
+		              /* queue : current, max, limit */
+		              "%s</td><td>%s</td><td>%s</td><td>%s</td>"
+		              /* sessions rate : current, max, limit */
+		              "<td>%s</td><td>%s</td><td></td>"
+		              "",
+		              (flags & ST_SHLGNDS) ? "</u>" : "",
+		              U2H(stats[ST_F_QCUR].u.u32), U2H(stats[ST_F_QMAX].u.u32), LIM2A(stats[ST_F_QLIMIT].u.u32, "-"),
+		              U2H(stats[ST_F_RATE].u.u32), U2H(stats[ST_F_RATE_MAX].u.u32));
 
-	stats[ST_F_PXNAME]   = mkf_str(FO_KEY|FN_NAME|FS_SERVICE, px->id);
-	stats[ST_F_SVNAME]   = mkf_str(FO_KEY|FN_NAME|FS_SERVICE, l->name);
-	stats[ST_F_SCUR]     = mkf_u32(0, l->nbconn);
-	stats[ST_F_SMAX]     = mkf_u32(FN_MAX, l->counters->conn_max);
-	stats[ST_F_SLIM]     = mkf_u32(FO_CONFIG|FN_LIMIT, l->maxconn);
-	stats[ST_F_STOT]     = mkf_u64(FN_COUNTER, l->counters->cum_conn);
-	stats[ST_F_BIN]      = mkf_u64(FN_COUNTER, l->counters->bytes_in);
-	stats[ST_F_BOUT]     = mkf_u64(FN_COUNTER, l->counters->bytes_out);
-	stats[ST_F_DREQ]     = mkf_u64(FN_COUNTER, l->counters->denied_req);
-	stats[ST_F_DRESP]    = mkf_u64(FN_COUNTER, l->counters->denied_resp);
-	stats[ST_F_EREQ]     = mkf_u64(FN_COUNTER, l->counters->failed_req);
-	stats[ST_F_STATUS]   = mkf_str(FO_STATUS, (l->nbconn < l->maxconn) ? (l->state == LI_LIMITED) ? "WAITING" : "OPEN" : "FULL");
-	stats[ST_F_PID]      = mkf_u32(FO_KEY, relative_pid);
-	stats[ST_F_IID]      = mkf_u32(FO_KEY|FS_SERVICE, px->uuid);
-	stats[ST_F_SID]      = mkf_u32(FO_KEY|FS_SERVICE, l->luid);
-	stats[ST_F_TYPE]     = mkf_u32(FO_CONFIG|FS_SERVICE, STATS_TYPE_SO);
+		chunk_appendf(&trash,
+		              /* sessions: current, max, limit, total */
+		              "<td>%s</td><td>%s</td><td>%s</td>"
+		              "<td><u>%s<div class=tips><table class=det>"
+		              "<tr><th>Cum. sessions:</th><td>%s</td></tr>"
+		              "",
+		              U2H(stats[ST_F_SCUR].u.u32), U2H(stats[ST_F_SMAX].u.u32), LIM2A(stats[ST_F_SLIM].u.u32, "-"),
+		              U2H(stats[ST_F_STOT].u.u64),
+		              U2H(stats[ST_F_STOT].u.u64));
 
-	if (flags & ST_SHLGNDS) {
-		char str[INET6_ADDRSTRLEN];
-		int port;
+		/* http response (via hover): 1xx, 2xx, 3xx, 4xx, 5xx, other */
+		if (px->mode == PR_MODE_HTTP) {
+			unsigned long long tot;
 
-		port = get_host_port(&l->addr);
-		switch (addr_to_str(&l->addr, str, sizeof(str))) {
-		case AF_INET:
-			stats[ST_F_ADDR] = mkf_str(FO_CONFIG|FS_SERVICE, chunk_newstr(out));
-			chunk_appendf(out, "%s:%d", str, port);
-			break;
-		case AF_INET6:
-			stats[ST_F_ADDR] = mkf_str(FO_CONFIG|FS_SERVICE, chunk_newstr(out));
-			chunk_appendf(out, "[%s]:%d", str, port);
-			break;
-		case AF_UNIX:
-			stats[ST_F_ADDR] = mkf_str(FO_CONFIG|FS_SERVICE, "unix");
-			break;
-		case -1:
-			stats[ST_F_ADDR] = mkf_str(FO_CONFIG|FS_SERVICE, chunk_newstr(out));
-			chunk_strcat(out, strerror(errno));
-			break;
-		default: /* address family not supported */
-			break;
+			tot  = stats[ST_F_HRSP_OTHER].u.u64;
+			tot += stats[ST_F_HRSP_1XX].u.u64;
+			tot += stats[ST_F_HRSP_2XX].u.u64;
+			tot += stats[ST_F_HRSP_3XX].u.u64;
+			tot += stats[ST_F_HRSP_4XX].u.u64;
+			tot += stats[ST_F_HRSP_5XX].u.u64;
+
+			chunk_appendf(&trash,
+			              "<tr><th>Cum. HTTP responses:</th><td>%s</td></tr>"
+			              "<tr><th>- HTTP 1xx responses:</th><td>%s</td><td>(%d%%)</td></tr>"
+			              "<tr><th>- HTTP 2xx responses:</th><td>%s</td><td>(%d%%)</td></tr>"
+			              "<tr><th>- HTTP 3xx responses:</th><td>%s</td><td>(%d%%)</td></tr>"
+			              "<tr><th>- HTTP 4xx responses:</th><td>%s</td><td>(%d%%)</td></tr>"
+			              "<tr><th>- HTTP 5xx responses:</th><td>%s</td><td>(%d%%)</td></tr>"
+			              "<tr><th>- other responses:</th><td>%s</td><td>(%d%%)</td></tr>"
+			              "",
+			              U2H(tot),
+			              U2H(stats[ST_F_HRSP_1XX].u.u64), tot ? (int)(100 * stats[ST_F_HRSP_1XX].u.u64 / tot) : 0,
+			              U2H(stats[ST_F_HRSP_2XX].u.u64), tot ? (int)(100 * stats[ST_F_HRSP_2XX].u.u64 / tot) : 0,
+			              U2H(stats[ST_F_HRSP_3XX].u.u64), tot ? (int)(100 * stats[ST_F_HRSP_3XX].u.u64 / tot) : 0,
+			              U2H(stats[ST_F_HRSP_4XX].u.u64), tot ? (int)(100 * stats[ST_F_HRSP_4XX].u.u64 / tot) : 0,
+			              U2H(stats[ST_F_HRSP_5XX].u.u64), tot ? (int)(100 * stats[ST_F_HRSP_5XX].u.u64 / tot) : 0,
+			              U2H(stats[ST_F_HRSP_OTHER].u.u64), tot ? (int)(100 * stats[ST_F_HRSP_OTHER].u.u64 / tot) : 0);
 		}
-	}
 
-	if (appctx->ctx.stats.flags & STAT_FMT_HTML) {
-		int admin;
+		chunk_appendf(&trash, "<tr><th colspan=3>Avg over last 1024 success. conn.</th></tr>");
+		chunk_appendf(&trash, "<tr><th>- Queue time:</th><td>%s</td><td>ms</td></tr>",   U2H(stats[ST_F_QTIME].u.u32));
+		chunk_appendf(&trash, "<tr><th>- Connect time:</th><td>%s</td><td>ms</td></tr>", U2H(stats[ST_F_CTIME].u.u32));
+		if (px->mode == PR_MODE_HTTP)
+			chunk_appendf(&trash, "<tr><th>- Response time:</th><td>%s</td><td>ms</td></tr>", U2H(stats[ST_F_RTIME].u.u32));
+		chunk_appendf(&trash, "<tr><th>- Total time:</th><td>%s</td><td>ms</td></tr>",   U2H(stats[ST_F_TTIME].u.u32));
 
-		admin = (px->cap & PR_CAP_BE) && px->srv && (appctx->ctx.stats.flags & STAT_ADMIN);
-		stats_dump_fields_html(stats, admin, flags, px);
-	}
-	else { /* CSV mode */
-		/* dump everything */
-		stats_dump_fields_csv(&trash, stats);
-	}
-	return 1;
-}
+		chunk_appendf(&trash,
+		              "</table></div></u></td>"
+		              /* sessions: lbtot, last */
+		              "<td>%s</td><td>%s</td>",
+		              U2H(stats[ST_F_LBTOT].u.u64),
+		              human_time(stats[ST_F_LASTSESS].u.s32, 1));
 
-enum srv_stats_state {
-	SRV_STATS_STATE_DOWN = 0,
-	SRV_STATS_STATE_DOWN_AGENT,
-	SRV_STATS_STATE_GOING_UP,
-	SRV_STATS_STATE_UP_GOING_DOWN,
-	SRV_STATS_STATE_UP,
-	SRV_STATS_STATE_NOLB_GOING_DOWN,
+		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><u>%s<div class=tips>Connection resets during transfers: %lld client, %lld server</div></u></td>"
+		              /* warnings: retries, redispatches */
+		              "<td>%lld</td><td>%lld</td>"
+		              "",
+		              U2H(stats[ST_F_BIN].u.u64), U2H(stats[ST_F_BOUT].u.u64),
+		              U2H(stats[ST_F_DRESP].u.u64),
+		              U2H(stats[ST_F_ECON].u.u64),
+		              U2H(stats[ST_F_ERESP].u.u64),
+		              (long long)stats[ST_F_CLI_ABRT].u.u64,
+		              (long long)stats[ST_F_SRV_ABRT].u.u64,
+		              (long long)stats[ST_F_WRETR].u.u64,
+			      (long long)stats[ST_F_WREDIS].u.u64);
+
+		/* status, last change */
+		chunk_appendf(&trash, "<td class=ac>");
+
+		/* FIXME!!!!
+		 *   LASTCHG should contain the last change for *this* server and must be computed
+		 * properly above, as was done below, ie: this server if maint, otherwise ref server
+		 * if tracking. Note that ref is either local or remote depending on tracking.
+		 */
+
+
+		if (memcmp(field_str(stats, ST_F_STATUS), "MAINT", 5) == 0) {
+			chunk_appendf(&trash, "%s MAINT", human_time(stats[ST_F_LASTCHG].u.u32, 1));
+		}
+		else if (memcmp(field_str(stats, ST_F_STATUS), "no check", 5) == 0) {
+			chunk_strcat(&trash, "<i>no check</i>");
+		}
+		else {
+			chunk_appendf(&trash, "%s %s", human_time(stats[ST_F_LASTCHG].u.u32, 1), field_str(stats, ST_F_STATUS));
+			if (memcmp(field_str(stats, ST_F_STATUS), "DOWN", 4) == 0) {
+				if (stats[ST_F_CHECK_HEALTH].u.u32)
+					chunk_strcat(&trash, " &uarr;");
+			}
+			else if (stats[ST_F_CHECK_HEALTH].u.u32 < stats[ST_F_CHECK_RISE].u.u32 + stats[ST_F_CHECK_FALL].u.u32 - 1)
+				chunk_strcat(&trash, " &darr;");
+		}
+
+		if (memcmp(field_str(stats, ST_F_STATUS), "DOWN", 4) == 0 &&
+		    stats[ST_F_AGENT_STATUS].type && !stats[ST_F_AGENT_HEALTH].u.u32) {
+			chunk_appendf(&trash,
+			              "</td><td class=ac><u> %s",
+			              field_str(stats, ST_F_AGENT_STATUS));
+
+			if (stats[ST_F_AGENT_CODE].type)
+				chunk_appendf(&trash, "/%d", stats[ST_F_AGENT_CODE].u.u32);
+
+			if (stats[ST_F_AGENT_DURATION].type && stats[ST_F_AGENT_DURATION].u.u64 >= 0)
+				chunk_appendf(&trash, " in %lums", (long)stats[ST_F_AGENT_DURATION].u.u64);
+
+			chunk_appendf(&trash, "<div class=tips>%s", field_str(stats, ST_F_AGENT_DESC));
+
+			if (*field_str(stats, ST_F_LAST_AGT)) {
+				chunk_appendf(&trash, ": ");
+				chunk_initstr(&src, field_str(stats, ST_F_LAST_AGT));
+				chunk_htmlencode(&trash, &src);
+			}
+			chunk_appendf(&trash, "</div></u>");
+		}
+		else if (stats[ST_F_CHECK_STATUS].type) {
+			chunk_appendf(&trash,
+			              "</td><td class=ac><u> %s",
+			              field_str(stats, ST_F_CHECK_STATUS));
+
+			if (stats[ST_F_CHECK_CODE].type)
+				chunk_appendf(&trash, "/%d", stats[ST_F_CHECK_CODE].u.u32);
+
+			if (stats[ST_F_CHECK_DURATION].type && stats[ST_F_CHECK_DURATION].u.u64 >= 0)
+				chunk_appendf(&trash, " in %lums", (long)stats[ST_F_CHECK_DURATION].u.u64);
+
+			chunk_appendf(&trash, "<div class=tips>%s", field_str(stats, ST_F_CHECK_DESC));
+
+			if (*field_str(stats, ST_F_LAST_CHK)) {
+				chunk_appendf(&trash, ": ");
+				chunk_initstr(&src, field_str(stats, ST_F_LAST_CHK));
+				chunk_htmlencode(&trash, &src);
+			}
+			chunk_appendf(&trash, "</div></u>");
+		}
+		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>"
+		              "",
+		              stats[ST_F_WEIGHT].u.u32,
+		              stats[ST_F_BCK].u.u32 ? "-" : "Y",
+		              stats[ST_F_BCK].u.u32 ? "Y" : "-");
+
+		/* check failures: unique, fatal, down time */
+		if (stats[ST_F_CHKFAIL].type) {
+			chunk_appendf(&trash, "<td><u>%lld", (long long)stats[ST_F_CHKFAIL].u.u64);
+
+			if (stats[ST_F_HANAFAIL].type)
+				chunk_appendf(&trash, "/%lld", (long long)stats[ST_F_HANAFAIL].u.u64);
+
+			chunk_appendf(&trash,
+			              "<div class=tips>Failed Health Checks%s</div></u></td>"
+			              "<td>%lld</td><td>%s</td>"
+			              "",
+			              stats[ST_F_HANAFAIL].type ? "/Health Analyses" : "",
+			              (long long)stats[ST_F_CHKDOWN].u.u64, human_time(stats[ST_F_DOWNTIME].u.u32, 1));
+		}
+		else if (strcmp(field_str(stats, ST_F_STATUS), "MAINT") != 0 && field_format(stats, ST_F_TRACKED) == FF_STR) {
+			/* tracking a server (hence inherited maint would appear as "MAINT (via...)" */
+			chunk_appendf(&trash,
+			              "<td class=ac colspan=3><a class=lfsb href=\"#%s\">via %s</a></td>",
+			              field_str(stats, ST_F_TRACKED), field_str(stats, ST_F_TRACKED));
+		}
+		else
+			chunk_appendf(&trash, "<td colspan=3></td>");
+
+		/* throttle */
+		if (stats[ST_F_THROTTLE].type)
+			chunk_appendf(&trash, "<td class=ac>%d %%</td></tr>\n", stats[ST_F_THROTTLE].u.u32);
+		else
+			chunk_appendf(&trash, "<td class=ac>-</td></tr>\n");
+	}
+	return 1;
+}
+
+/* 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_dump_fe_stats(struct stream_interface *si, struct proxy *px)
+{
+	struct appctx *appctx = __objt_appctx(si->end);
+
+	if (!(px->cap & PR_CAP_FE))
+		return 0;
+
+	if ((appctx->ctx.stats.flags & STAT_BOUND) && !(appctx->ctx.stats.type & (1 << STATS_TYPE_FE)))
+		return 0;
+
+	memset(&stats, 0, sizeof(stats));
+
+	stats[ST_F_PXNAME]   = mkf_str(FO_KEY|FN_NAME|FS_SERVICE, px->id);
+	stats[ST_F_SVNAME]   = mkf_str(FO_KEY|FN_NAME|FS_SERVICE, "FRONTEND");
+	stats[ST_F_SCUR]     = mkf_u32(0, px->feconn);
+	stats[ST_F_SMAX]     = mkf_u32(FN_MAX, px->fe_counters.conn_max);
+	stats[ST_F_SLIM]     = mkf_u32(FO_CONFIG|FN_LIMIT, px->maxconn);
+	stats[ST_F_STOT]     = mkf_u64(FN_COUNTER, px->fe_counters.cum_sess);
+	stats[ST_F_BIN]      = mkf_u64(FN_COUNTER, px->fe_counters.bytes_in);
+	stats[ST_F_BOUT]     = mkf_u64(FN_COUNTER, px->fe_counters.bytes_out);
+	stats[ST_F_DREQ]     = mkf_u64(FN_COUNTER, px->fe_counters.denied_req);
+	stats[ST_F_DRESP]    = mkf_u64(FN_COUNTER, px->fe_counters.denied_resp);
+	stats[ST_F_EREQ]     = mkf_u64(FN_COUNTER, px->fe_counters.failed_req);
+	stats[ST_F_STATUS]   = mkf_str(FO_STATUS, px->state == PR_STREADY ? "OPEN" : px->state == PR_STFULL ? "FULL" : "STOP");
+	stats[ST_F_PID]      = mkf_u32(FO_KEY, relative_pid);
+	stats[ST_F_IID]      = mkf_u32(FO_KEY|FS_SERVICE, px->uuid);
+	stats[ST_F_SID]      = mkf_u32(FO_KEY|FS_SERVICE, 0);
+	stats[ST_F_TYPE]     = mkf_u32(FO_CONFIG|FS_SERVICE, STATS_TYPE_FE);
+	stats[ST_F_RATE]     = mkf_u32(FN_RATE, read_freq_ctr(&px->fe_sess_per_sec));
+	stats[ST_F_RATE_LIM] = mkf_u32(FO_CONFIG|FN_LIMIT, px->fe_sps_lim);
+	stats[ST_F_RATE_MAX] = mkf_u32(FN_MAX, px->fe_counters.sps_max);
+
+	/* http response: 1xx, 2xx, 3xx, 4xx, 5xx, other */
+	if (px->mode == PR_MODE_HTTP) {
+		stats[ST_F_HRSP_1XX]    = mkf_u64(FN_COUNTER, px->fe_counters.p.http.rsp[1]);
+		stats[ST_F_HRSP_2XX]    = mkf_u64(FN_COUNTER, px->fe_counters.p.http.rsp[2]);
+		stats[ST_F_HRSP_3XX]    = mkf_u64(FN_COUNTER, px->fe_counters.p.http.rsp[3]);
+		stats[ST_F_HRSP_4XX]    = mkf_u64(FN_COUNTER, px->fe_counters.p.http.rsp[4]);
+		stats[ST_F_HRSP_5XX]    = mkf_u64(FN_COUNTER, px->fe_counters.p.http.rsp[5]);
+		stats[ST_F_HRSP_OTHER]  = mkf_u64(FN_COUNTER, px->fe_counters.p.http.rsp[0]);
+	}
+
+	/* requests : req_rate, req_rate_max, req_tot, */
+	stats[ST_F_REQ_RATE]     = mkf_u32(FN_RATE, read_freq_ctr(&px->fe_req_per_sec));
+	stats[ST_F_REQ_RATE_MAX] = mkf_u32(FN_MAX, px->fe_counters.p.http.rps_max);
+	stats[ST_F_REQ_TOT]      = mkf_u64(FN_COUNTER, px->fe_counters.p.http.cum_req);
+
+	/* compression: in, out, bypassed, responses */
+	stats[ST_F_COMP_IN]      = mkf_u64(FN_COUNTER, px->fe_counters.comp_in);
+	stats[ST_F_COMP_OUT]     = mkf_u64(FN_COUNTER, px->fe_counters.comp_out);
+	stats[ST_F_COMP_BYP]     = mkf_u64(FN_COUNTER, px->fe_counters.comp_byp);
+	stats[ST_F_COMP_RSP]     = mkf_u64(FN_COUNTER, px->fe_counters.p.http.comp_rsp);
+
+	if (appctx->ctx.stats.flags & STAT_FMT_HTML) {
+		int admin;
+
+		admin = (px->cap & PR_CAP_BE) && px->srv && (appctx->ctx.stats.flags & STAT_ADMIN);
+		stats_dump_fields_html(stats, admin, 0, px);
+	}
+	else { /* CSV mode */
+		/* dump everything */
+		stats_dump_fields_csv(&trash, stats);
+	}
+	return 1;
+}
+
+/* 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_li_stats(struct stream_interface *si, struct proxy *px, struct listener *l, int flags)
+{
+	struct appctx *appctx = __objt_appctx(si->end);
+	struct chunk *out = get_trash_chunk();
+
+	chunk_reset(out);
+	memset(&stats, 0, sizeof(stats));
+
+	stats[ST_F_PXNAME]   = mkf_str(FO_KEY|FN_NAME|FS_SERVICE, px->id);
+	stats[ST_F_SVNAME]   = mkf_str(FO_KEY|FN_NAME|FS_SERVICE, l->name);
+	stats[ST_F_SCUR]     = mkf_u32(0, l->nbconn);
+	stats[ST_F_SMAX]     = mkf_u32(FN_MAX, l->counters->conn_max);
+	stats[ST_F_SLIM]     = mkf_u32(FO_CONFIG|FN_LIMIT, l->maxconn);
+	stats[ST_F_STOT]     = mkf_u64(FN_COUNTER, l->counters->cum_conn);
+	stats[ST_F_BIN]      = mkf_u64(FN_COUNTER, l->counters->bytes_in);
+	stats[ST_F_BOUT]     = mkf_u64(FN_COUNTER, l->counters->bytes_out);
+	stats[ST_F_DREQ]     = mkf_u64(FN_COUNTER, l->counters->denied_req);
+	stats[ST_F_DRESP]    = mkf_u64(FN_COUNTER, l->counters->denied_resp);
+	stats[ST_F_EREQ]     = mkf_u64(FN_COUNTER, l->counters->failed_req);
+	stats[ST_F_STATUS]   = mkf_str(FO_STATUS, (l->nbconn < l->maxconn) ? (l->state == LI_LIMITED) ? "WAITING" : "OPEN" : "FULL");
+	stats[ST_F_PID]      = mkf_u32(FO_KEY, relative_pid);
+	stats[ST_F_IID]      = mkf_u32(FO_KEY|FS_SERVICE, px->uuid);
+	stats[ST_F_SID]      = mkf_u32(FO_KEY|FS_SERVICE, l->luid);
+	stats[ST_F_TYPE]     = mkf_u32(FO_CONFIG|FS_SERVICE, STATS_TYPE_SO);
+
+	if (flags & ST_SHLGNDS) {
+		char str[INET6_ADDRSTRLEN];
+		int port;
+
+		port = get_host_port(&l->addr);
+		switch (addr_to_str(&l->addr, str, sizeof(str))) {
+		case AF_INET:
+			stats[ST_F_ADDR] = mkf_str(FO_CONFIG|FS_SERVICE, chunk_newstr(out));
+			chunk_appendf(out, "%s:%d", str, port);
+			break;
+		case AF_INET6:
+			stats[ST_F_ADDR] = mkf_str(FO_CONFIG|FS_SERVICE, chunk_newstr(out));
+			chunk_appendf(out, "[%s]:%d", str, port);
+			break;
+		case AF_UNIX:
+			stats[ST_F_ADDR] = mkf_str(FO_CONFIG|FS_SERVICE, "unix");
+			break;
+		case -1:
+			stats[ST_F_ADDR] = mkf_str(FO_CONFIG|FS_SERVICE, chunk_newstr(out));
+			chunk_strcat(out, strerror(errno));
+			break;
+		default: /* address family not supported */
+			break;
+		}
+	}
+
+	if (appctx->ctx.stats.flags & STAT_FMT_HTML) {
+		int admin;
+
+		admin = (px->cap & PR_CAP_BE) && px->srv && (appctx->ctx.stats.flags & STAT_ADMIN);
+		stats_dump_fields_html(stats, admin, flags, px);
+	}
+	else { /* CSV mode */
+		/* dump everything */
+		stats_dump_fields_csv(&trash, stats);
+	}
+	return 1;
+}
+
+enum srv_stats_state {
+	SRV_STATS_STATE_DOWN = 0,
+	SRV_STATS_STATE_DOWN_AGENT,
+	SRV_STATS_STATE_GOING_UP,
+	SRV_STATS_STATE_UP_GOING_DOWN,
+	SRV_STATS_STATE_UP,
+	SRV_STATS_STATE_NOLB_GOING_DOWN,
 	SRV_STATS_STATE_NOLB,
 	SRV_STATS_STATE_DRAIN_GOING_DOWN,
 	SRV_STATS_STATE_DRAIN,
@@ -3635,7 +3913,6 @@
 	struct appctx *appctx = __objt_appctx(si->end);
 	struct server *via, *ref;
 	char str[INET6_ADDRSTRLEN];
-	struct chunk src;
 	struct chunk *out = get_trash_chunk();
 	enum srv_stats_state state;
 	char *fld_status;
@@ -3723,409 +4000,139 @@
 			      (ref->state != SRV_ST_STOPPED) ? (ref->check.fall) : (ref->check.rise));
 
 	stats[ST_F_STATUS]   = mkf_str(FO_STATUS, fld_status);
-	stats[ST_F_LASTCHG]  = mkf_u32(FN_AGE, now.tv_sec - sv->last_change);
-	stats[ST_F_WEIGHT]   = mkf_u32(FN_AVG, (sv->eweight * px->lbprm.wmult + px->lbprm.wdiv - 1) / px->lbprm.wdiv);
-	stats[ST_F_ACT]      = mkf_u32(FO_STATUS, (sv->flags & SRV_F_BACKUP) ? 0 : 1);
-	stats[ST_F_BCK]      = mkf_u32(FO_STATUS, (sv->flags & SRV_F_BACKUP) ? 1 : 0);
-
-	/* check failures: unique, fatal; last change, total downtime */
-	if (sv->check.state & CHK_ST_ENABLED) {
-		stats[ST_F_CHKFAIL]  = mkf_u64(FN_COUNTER, sv->counters.failed_checks);
-		stats[ST_F_CHKDOWN]  = mkf_u64(FN_COUNTER, sv->counters.down_trans);
-		stats[ST_F_DOWNTIME] = mkf_u32(FN_COUNTER, srv_downtime(sv));
-	}
-
-	if (sv->maxqueue)
-		stats[ST_F_QLIMIT]   = mkf_u32(FO_CONFIG|FS_SERVICE, sv->maxqueue);
-
-	stats[ST_F_PID]      = mkf_u32(FO_KEY, relative_pid);
-	stats[ST_F_IID]      = mkf_u32(FO_KEY|FS_SERVICE, px->uuid);
-	stats[ST_F_SID]      = mkf_u32(FO_KEY|FS_SERVICE, sv->puid);
-
-	if (sv->state == SRV_ST_STARTING && !server_is_draining(sv))
-		stats[ST_F_THROTTLE] = mkf_u32(FN_AVG, server_throttle_rate(sv));
-
-	stats[ST_F_LBTOT]    = mkf_u64(FN_COUNTER, sv->counters.cum_lbconn);
-
-	if (sv->track) {
-		char *fld_track = chunk_newstr(out);
-
-		chunk_appendf(out, "%s/%s", sv->track->proxy->id, sv->track->id);
-		stats[ST_F_TRACKED] = mkf_str(FO_CONFIG|FN_NAME|FS_SERVICE, fld_track);
-	}
-
-	stats[ST_F_TYPE]     = mkf_u32(FO_CONFIG|FS_SERVICE, STATS_TYPE_SV);
-	stats[ST_F_RATE]     = mkf_u32(FN_RATE, read_freq_ctr(&sv->sess_per_sec));
-	stats[ST_F_RATE_MAX] = mkf_u32(FN_MAX, sv->counters.sps_max);
-
-	if ((sv->check.state & (CHK_ST_ENABLED|CHK_ST_PAUSED)) == CHK_ST_ENABLED) {
-		const char *fld_chksts;
-
-		fld_chksts = chunk_newstr(out);
-		chunk_strcat(out, "* "); // for check in progress
-		chunk_strcat(out, get_check_status_info(sv->check.status));
-		if (!(sv->check.state & CHK_ST_INPROGRESS))
-			fld_chksts += 2; // skip "* "
-		stats[ST_F_CHECK_STATUS] = mkf_str(FN_OUTPUT, fld_chksts);
-
-		if (sv->check.status >= HCHK_STATUS_L57DATA)
-			stats[ST_F_CHECK_CODE] = mkf_u32(FN_OUTPUT, sv->check.code);
-
-		if (sv->check.status >= HCHK_STATUS_CHECKED)
-			stats[ST_F_CHECK_DURATION] = mkf_u64(FN_DURATION, sv->check.duration);
-
-		stats[ST_F_CHECK_DESC] = mkf_str(FN_OUTPUT, get_check_status_description(sv->check.status));
-		stats[ST_F_LAST_CHK] = mkf_str(FN_OUTPUT, sv->check.desc);
-		stats[ST_F_CHECK_RISE]   = mkf_u32(FO_CONFIG|FS_SERVICE, ref->check.rise);
-		stats[ST_F_CHECK_FALL]   = mkf_u32(FO_CONFIG|FS_SERVICE, ref->check.fall);
-		stats[ST_F_CHECK_HEALTH] = mkf_u32(FO_CONFIG|FS_SERVICE, ref->check.health);
-	}
-
-	if ((sv->agent.state & (CHK_ST_ENABLED|CHK_ST_PAUSED)) == CHK_ST_ENABLED) {
-		const char *fld_chksts;
-
-		fld_chksts = chunk_newstr(out);
-		chunk_strcat(out, "* "); // for check in progress
-		chunk_strcat(out, get_check_status_info(sv->agent.status));
-		if (!(sv->agent.state & CHK_ST_INPROGRESS))
-			fld_chksts += 2; // skip "* "
-		stats[ST_F_AGENT_STATUS] = mkf_str(FN_OUTPUT, fld_chksts);
-
-		if (sv->agent.status >= HCHK_STATUS_L57DATA)
-			stats[ST_F_AGENT_CODE] = mkf_u32(FN_OUTPUT, sv->agent.code);
-
-		if (sv->agent.status >= HCHK_STATUS_CHECKED)
-			stats[ST_F_AGENT_DURATION] = mkf_u64(FN_DURATION, sv->agent.duration);
-
-		stats[ST_F_AGENT_DESC] = mkf_str(FN_OUTPUT, get_check_status_description(sv->agent.status));
-		stats[ST_F_LAST_AGT] = mkf_str(FN_OUTPUT, sv->agent.desc);
-		stats[ST_F_AGENT_RISE]   = mkf_u32(FO_CONFIG|FS_SERVICE, sv->agent.rise);
-		stats[ST_F_AGENT_FALL]   = mkf_u32(FO_CONFIG|FS_SERVICE, sv->agent.fall);
-		stats[ST_F_AGENT_HEALTH] = mkf_u32(FO_CONFIG|FS_SERVICE, sv->agent.health);
-	}
-
-	/* http response: 1xx, 2xx, 3xx, 4xx, 5xx, other */
-	if (px->mode == PR_MODE_HTTP) {
-		stats[ST_F_HRSP_1XX]   = mkf_u64(FN_COUNTER, sv->counters.p.http.rsp[1]);
-		stats[ST_F_HRSP_2XX]   = mkf_u64(FN_COUNTER, sv->counters.p.http.rsp[2]);
-		stats[ST_F_HRSP_3XX]   = mkf_u64(FN_COUNTER, sv->counters.p.http.rsp[3]);
-		stats[ST_F_HRSP_4XX]   = mkf_u64(FN_COUNTER, sv->counters.p.http.rsp[4]);
-		stats[ST_F_HRSP_5XX]   = mkf_u64(FN_COUNTER, sv->counters.p.http.rsp[5]);
-		stats[ST_F_HRSP_OTHER] = mkf_u64(FN_COUNTER, sv->counters.p.http.rsp[0]);
-	}
-
-	if (ref->observe)
-		stats[ST_F_HANAFAIL] = mkf_u64(FN_COUNTER, sv->counters.failed_hana);
-
-	stats[ST_F_CLI_ABRT] = mkf_u64(FN_COUNTER, sv->counters.cli_aborts);
-	stats[ST_F_SRV_ABRT] = mkf_u64(FN_COUNTER, sv->counters.srv_aborts);
-	stats[ST_F_LASTSESS] = mkf_s32(FN_AGE, srv_lastsession(sv));
-
-	stats[ST_F_QTIME] = mkf_u32(FN_AVG, swrate_avg(sv->counters.q_time, TIME_STATS_SAMPLES));
-	stats[ST_F_CTIME] = mkf_u32(FN_AVG, swrate_avg(sv->counters.c_time, TIME_STATS_SAMPLES));
-	stats[ST_F_RTIME] = mkf_u32(FN_AVG, swrate_avg(sv->counters.d_time, TIME_STATS_SAMPLES));
-	stats[ST_F_TTIME] = mkf_u32(FN_AVG, swrate_avg(sv->counters.t_time, TIME_STATS_SAMPLES));
-
-	if (flags & ST_SHLGNDS) {
-		switch (addr_to_str(&sv->addr, str, sizeof(str))) {
-		case AF_INET:
-			stats[ST_F_ADDR] = mkf_str(FO_CONFIG|FS_SERVICE, chunk_newstr(out));
-			chunk_appendf(out, "%s:%d", str, get_host_port(&sv->addr));
-			break;
-		case AF_INET6:
-			stats[ST_F_ADDR] = mkf_str(FO_CONFIG|FS_SERVICE, chunk_newstr(out));
-			chunk_appendf(out, "[%s]:%d", str, get_host_port(&sv->addr));
-			break;
-		case AF_UNIX:
-			stats[ST_F_ADDR] = mkf_str(FO_CONFIG|FS_SERVICE, "unix");
-			break;
-		case -1:
-			stats[ST_F_ADDR] = mkf_str(FO_CONFIG|FS_SERVICE, chunk_newstr(out));
-			chunk_strcat(out, strerror(errno));
-			break;
-		default: /* address family not supported */
-			break;
-		}
-
-		if (sv->cookie)
-			stats[ST_F_COOKIE] = mkf_str(FO_CONFIG|FN_NAME|FS_SERVICE, sv->cookie);
-	}
-
-	if (appctx->ctx.stats.flags & STAT_FMT_HTML) {
-		const char *style;
-
-		/* determine the style to use depending on the server's state,
-		 * its health and weight. There isn't a 1-to-1 mapping between
-		 * state and styles for the cases where the server is (still)
-		 * up. The reason is that we don't want to report nolb and
-		 * drain with the same color.
-		 */
-
-		if (strcmp(field_str(stats, ST_F_STATUS), "DOWN") == 0 ||
-		    strcmp(field_str(stats, ST_F_STATUS), "DOWN (agent)") == 0) {
-			style = "down";
-		}
-		else if (strcmp(field_str(stats, ST_F_STATUS), "DOWN ") == 0) {
-			style = "going_up";
-		}
-		else if (strcmp(field_str(stats, ST_F_STATUS), "NOLB ") == 0) {
-			style = "going_down";
-		}
-		else if (strcmp(field_str(stats, ST_F_STATUS), "NOLB") == 0) {
-			style = "nolb";
-		}
-		else if (strcmp(field_str(stats, ST_F_STATUS), "no check") == 0) {
-			style = "no_check";
-		}
-		else if (!stats[ST_F_CHKFAIL].type ||
-			 stats[ST_F_CHECK_HEALTH].u.u32 == stats[ST_F_CHECK_RISE].u.u32 + stats[ST_F_CHECK_FALL].u.u32 - 1) {
-			/* no check or max health = UP */
-			if (stats[ST_F_WEIGHT].u.u32)
-				style = "up";
-			else
-				style = "draining";
-		}
-		else {
-			style = "going_down";
-		}
-
-		if (memcmp(field_str(stats, ST_F_STATUS), "MAINT", 5) == 0)
-			chunk_appendf(&trash, "<tr class=\"maintain\">");
-		else
-			chunk_appendf(&trash,
-			              "<tr class=\"%s_%s\">",
-			              (stats[ST_F_BCK].u.u32) ? "backup" : "active", style);
-
-
-		if ((px->cap & PR_CAP_BE) && px->srv && (appctx->ctx.stats.flags & STAT_ADMIN))
-			chunk_appendf(&trash,
-			              "<td><input type=\"checkbox\" name=\"s\" value=\"%s\"></td>",
-			              field_str(stats, ST_F_SVNAME));
-
-		chunk_appendf(&trash,
-		              "<td class=ac><a name=\"%s/%s\"></a>%s"
-		              "<a class=lfsb href=\"#%s/%s\">%s</a>"
-		              "",
-		              field_str(stats, ST_F_PXNAME), field_str(stats, ST_F_SVNAME),
-		              (flags & ST_SHLGNDS) ? "<u>" : "",
-		              field_str(stats, ST_F_PXNAME), field_str(stats, ST_F_SVNAME), field_str(stats, ST_F_SVNAME));
-
-		if (flags & ST_SHLGNDS) {
-			chunk_appendf(&trash, "<div class=tips>");
-
-			if (isdigit(*field_str(stats, ST_F_ADDR)))
-				chunk_appendf(&trash, "IPv4: %s, ", field_str(stats, ST_F_ADDR));
-			else if (*field_str(stats, ST_F_ADDR) == '[')
-				chunk_appendf(&trash, "IPv6: %s, ", field_str(stats, ST_F_ADDR));
-			else if (*field_str(stats, ST_F_ADDR))
-				chunk_appendf(&trash, "%s, ", field_str(stats, ST_F_ADDR));
-
-			/* id */
-			chunk_appendf(&trash, "id: %d", stats[ST_F_SID].u.u32);
-
-			/* cookie */
-			if (stats[ST_F_COOKIE].type) {
-				chunk_appendf(&trash, ", cookie: '");
-				chunk_initstr(&src, field_str(stats, ST_F_COOKIE));
-				chunk_htmlencode(&trash, &src);
-				chunk_appendf(&trash, "'");
-			}
-
-			chunk_appendf(&trash, "</div>");
-		}
-
-		chunk_appendf(&trash,
-		              /* queue : current, max, limit */
-		              "%s</td><td>%s</td><td>%s</td><td>%s</td>"
-		              /* sessions rate : current, max, limit */
-		              "<td>%s</td><td>%s</td><td></td>"
-		              "",
-		              (flags & ST_SHLGNDS) ? "</u>" : "",
-		              U2H(stats[ST_F_QCUR].u.u32), U2H(stats[ST_F_QMAX].u.u32), LIM2A(stats[ST_F_QLIMIT].u.u32, "-"),
-		              U2H(stats[ST_F_RATE].u.u32), U2H(stats[ST_F_RATE_MAX].u.u32));
-
-		chunk_appendf(&trash,
-		              /* sessions: current, max, limit, total */
-		              "<td>%s</td><td>%s</td><td>%s</td>"
-		              "<td><u>%s<div class=tips><table class=det>"
-		              "<tr><th>Cum. sessions:</th><td>%s</td></tr>"
-		              "",
-		              U2H(stats[ST_F_SCUR].u.u32), U2H(stats[ST_F_SMAX].u.u32), LIM2A(stats[ST_F_SLIM].u.u32, "-"),
-		              U2H(stats[ST_F_STOT].u.u64),
-		              U2H(stats[ST_F_STOT].u.u64));
+	stats[ST_F_LASTCHG]  = mkf_u32(FN_AGE, now.tv_sec - sv->last_change);
+	stats[ST_F_WEIGHT]   = mkf_u32(FN_AVG, (sv->eweight * px->lbprm.wmult + px->lbprm.wdiv - 1) / px->lbprm.wdiv);
+	stats[ST_F_ACT]      = mkf_u32(FO_STATUS, (sv->flags & SRV_F_BACKUP) ? 0 : 1);
+	stats[ST_F_BCK]      = mkf_u32(FO_STATUS, (sv->flags & SRV_F_BACKUP) ? 1 : 0);
 
-		/* http response (via hover): 1xx, 2xx, 3xx, 4xx, 5xx, other */
-		if (px->mode == PR_MODE_HTTP) {
-			unsigned long long tot;
+	/* check failures: unique, fatal; last change, total downtime */
+	if (sv->check.state & CHK_ST_ENABLED) {
+		stats[ST_F_CHKFAIL]  = mkf_u64(FN_COUNTER, sv->counters.failed_checks);
+		stats[ST_F_CHKDOWN]  = mkf_u64(FN_COUNTER, sv->counters.down_trans);
+		stats[ST_F_DOWNTIME] = mkf_u32(FN_COUNTER, srv_downtime(sv));
+	}
 
-			tot  = stats[ST_F_HRSP_OTHER].u.u64;
-			tot += stats[ST_F_HRSP_1XX].u.u64;
-			tot += stats[ST_F_HRSP_2XX].u.u64;
-			tot += stats[ST_F_HRSP_3XX].u.u64;
-			tot += stats[ST_F_HRSP_4XX].u.u64;
-			tot += stats[ST_F_HRSP_5XX].u.u64;
+	if (sv->maxqueue)
+		stats[ST_F_QLIMIT]   = mkf_u32(FO_CONFIG|FS_SERVICE, sv->maxqueue);
 
-			chunk_appendf(&trash,
-			              "<tr><th>Cum. HTTP responses:</th><td>%s</td></tr>"
-			              "<tr><th>- HTTP 1xx responses:</th><td>%s</td><td>(%d%%)</td></tr>"
-			              "<tr><th>- HTTP 2xx responses:</th><td>%s</td><td>(%d%%)</td></tr>"
-			              "<tr><th>- HTTP 3xx responses:</th><td>%s</td><td>(%d%%)</td></tr>"
-			              "<tr><th>- HTTP 4xx responses:</th><td>%s</td><td>(%d%%)</td></tr>"
-			              "<tr><th>- HTTP 5xx responses:</th><td>%s</td><td>(%d%%)</td></tr>"
-			              "<tr><th>- other responses:</th><td>%s</td><td>(%d%%)</td></tr>"
-			              "",
-			              U2H(tot),
-			              U2H(stats[ST_F_HRSP_1XX].u.u64), tot ? (int)(100 * stats[ST_F_HRSP_1XX].u.u64 / tot) : 0,
-			              U2H(stats[ST_F_HRSP_2XX].u.u64), tot ? (int)(100 * stats[ST_F_HRSP_2XX].u.u64 / tot) : 0,
-			              U2H(stats[ST_F_HRSP_3XX].u.u64), tot ? (int)(100 * stats[ST_F_HRSP_3XX].u.u64 / tot) : 0,
-			              U2H(stats[ST_F_HRSP_4XX].u.u64), tot ? (int)(100 * stats[ST_F_HRSP_4XX].u.u64 / tot) : 0,
-			              U2H(stats[ST_F_HRSP_5XX].u.u64), tot ? (int)(100 * stats[ST_F_HRSP_5XX].u.u64 / tot) : 0,
-			              U2H(stats[ST_F_HRSP_OTHER].u.u64), tot ? (int)(100 * stats[ST_F_HRSP_OTHER].u.u64 / tot) : 0);
-		}
+	stats[ST_F_PID]      = mkf_u32(FO_KEY, relative_pid);
+	stats[ST_F_IID]      = mkf_u32(FO_KEY|FS_SERVICE, px->uuid);
+	stats[ST_F_SID]      = mkf_u32(FO_KEY|FS_SERVICE, sv->puid);
 
-		chunk_appendf(&trash, "<tr><th colspan=3>Avg over last 1024 success. conn.</th></tr>");
-		chunk_appendf(&trash, "<tr><th>- Queue time:</th><td>%s</td><td>ms</td></tr>",   U2H(stats[ST_F_QTIME].u.u32));
-		chunk_appendf(&trash, "<tr><th>- Connect time:</th><td>%s</td><td>ms</td></tr>", U2H(stats[ST_F_CTIME].u.u32));
-		if (px->mode == PR_MODE_HTTP)
-			chunk_appendf(&trash, "<tr><th>- Response time:</th><td>%s</td><td>ms</td></tr>", U2H(stats[ST_F_RTIME].u.u32));
-		chunk_appendf(&trash, "<tr><th>- Total time:</th><td>%s</td><td>ms</td></tr>",   U2H(stats[ST_F_TTIME].u.u32));
+	if (sv->state == SRV_ST_STARTING && !server_is_draining(sv))
+		stats[ST_F_THROTTLE] = mkf_u32(FN_AVG, server_throttle_rate(sv));
 
-		chunk_appendf(&trash,
-		              "</table></div></u></td>"
-		              /* sessions: lbtot, last */
-		              "<td>%s</td><td>%s</td>",
-		              U2H(stats[ST_F_LBTOT].u.u64),
-		              human_time(stats[ST_F_LASTSESS].u.s32, 1));
+	stats[ST_F_LBTOT]    = mkf_u64(FN_COUNTER, 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><u>%s<div class=tips>Connection resets during transfers: %lld client, %lld server</div></u></td>"
-		              /* warnings: retries, redispatches */
-		              "<td>%lld</td><td>%lld</td>"
-		              "",
-		              U2H(stats[ST_F_BIN].u.u64), U2H(stats[ST_F_BOUT].u.u64),
-		              U2H(stats[ST_F_DRESP].u.u64),
-		              U2H(stats[ST_F_ECON].u.u64),
-		              U2H(stats[ST_F_ERESP].u.u64),
-		              (long long)stats[ST_F_CLI_ABRT].u.u64,
-		              (long long)stats[ST_F_SRV_ABRT].u.u64,
-		              (long long)stats[ST_F_WRETR].u.u64,
-			      (long long)stats[ST_F_WREDIS].u.u64);
+	if (sv->track) {
+		char *fld_track = chunk_newstr(out);
 
-		/* status, last change */
-		chunk_appendf(&trash, "<td class=ac>");
+		chunk_appendf(out, "%s/%s", sv->track->proxy->id, sv->track->id);
+		stats[ST_F_TRACKED] = mkf_str(FO_CONFIG|FN_NAME|FS_SERVICE, fld_track);
+	}
 
-		/* FIXME!!!!
-		 *   LASTCHG should contain the last change for *this* server and must be computed
-		 * properly above, as was done below, ie: this server if maint, otherwise ref server
-		 * if tracking. Note that ref is either local or remote depending on tracking.
-		 */
+	stats[ST_F_TYPE]     = mkf_u32(FO_CONFIG|FS_SERVICE, STATS_TYPE_SV);
+	stats[ST_F_RATE]     = mkf_u32(FN_RATE, read_freq_ctr(&sv->sess_per_sec));
+	stats[ST_F_RATE_MAX] = mkf_u32(FN_MAX, sv->counters.sps_max);
 
+	if ((sv->check.state & (CHK_ST_ENABLED|CHK_ST_PAUSED)) == CHK_ST_ENABLED) {
+		const char *fld_chksts;
 
-		if (memcmp(field_str(stats, ST_F_STATUS), "MAINT", 5) == 0) {
-			chunk_appendf(&trash, "%s MAINT", human_time(stats[ST_F_LASTCHG].u.u32, 1));
-		}
-		else if (memcmp(field_str(stats, ST_F_STATUS), "no check", 5) == 0) {
-			chunk_strcat(&trash, "<i>no check</i>");
-		}
-		else {
-			chunk_appendf(&trash, "%s %s", human_time(stats[ST_F_LASTCHG].u.u32, 1), field_str(stats, ST_F_STATUS));
-			if (memcmp(field_str(stats, ST_F_STATUS), "DOWN", 4) == 0) {
-				if (stats[ST_F_CHECK_HEALTH].u.u32)
-					chunk_strcat(&trash, " &uarr;");
-			}
-			else if (stats[ST_F_CHECK_HEALTH].u.u32 < stats[ST_F_CHECK_RISE].u.u32 + stats[ST_F_CHECK_FALL].u.u32 - 1)
-				chunk_strcat(&trash, " &darr;");
-		}
+		fld_chksts = chunk_newstr(out);
+		chunk_strcat(out, "* "); // for check in progress
+		chunk_strcat(out, get_check_status_info(sv->check.status));
+		if (!(sv->check.state & CHK_ST_INPROGRESS))
+			fld_chksts += 2; // skip "* "
+		stats[ST_F_CHECK_STATUS] = mkf_str(FN_OUTPUT, fld_chksts);
 
-		if (memcmp(field_str(stats, ST_F_STATUS), "DOWN", 4) == 0 &&
-		    stats[ST_F_AGENT_STATUS].type && !stats[ST_F_AGENT_HEALTH].u.u32) {
-			chunk_appendf(&trash,
-			              "</td><td class=ac><u> %s",
-			              field_str(stats, ST_F_AGENT_STATUS));
+		if (sv->check.status >= HCHK_STATUS_L57DATA)
+			stats[ST_F_CHECK_CODE] = mkf_u32(FN_OUTPUT, sv->check.code);
 
-			if (stats[ST_F_AGENT_CODE].type)
-				chunk_appendf(&trash, "/%d", stats[ST_F_AGENT_CODE].u.u32);
+		if (sv->check.status >= HCHK_STATUS_CHECKED)
+			stats[ST_F_CHECK_DURATION] = mkf_u64(FN_DURATION, sv->check.duration);
 
-			if (stats[ST_F_AGENT_DURATION].type && stats[ST_F_AGENT_DURATION].u.u64 >= 0)
-				chunk_appendf(&trash, " in %lums", (long)stats[ST_F_AGENT_DURATION].u.u64);
+		stats[ST_F_CHECK_DESC] = mkf_str(FN_OUTPUT, get_check_status_description(sv->check.status));
+		stats[ST_F_LAST_CHK] = mkf_str(FN_OUTPUT, sv->check.desc);
+		stats[ST_F_CHECK_RISE]   = mkf_u32(FO_CONFIG|FS_SERVICE, ref->check.rise);
+		stats[ST_F_CHECK_FALL]   = mkf_u32(FO_CONFIG|FS_SERVICE, ref->check.fall);
+		stats[ST_F_CHECK_HEALTH] = mkf_u32(FO_CONFIG|FS_SERVICE, ref->check.health);
+	}
 
-			chunk_appendf(&trash, "<div class=tips>%s", field_str(stats, ST_F_AGENT_DESC));
+	if ((sv->agent.state & (CHK_ST_ENABLED|CHK_ST_PAUSED)) == CHK_ST_ENABLED) {
+		const char *fld_chksts;
 
-			if (*field_str(stats, ST_F_LAST_AGT)) {
-				chunk_appendf(&trash, ": ");
-				chunk_initstr(&src, field_str(stats, ST_F_LAST_AGT));
-				chunk_htmlencode(&trash, &src);
-			}
-			chunk_appendf(&trash, "</div></u>");
-		}
-		else if (stats[ST_F_CHECK_STATUS].type) {
-			chunk_appendf(&trash,
-			              "</td><td class=ac><u> %s",
-			              field_str(stats, ST_F_CHECK_STATUS));
+		fld_chksts = chunk_newstr(out);
+		chunk_strcat(out, "* "); // for check in progress
+		chunk_strcat(out, get_check_status_info(sv->agent.status));
+		if (!(sv->agent.state & CHK_ST_INPROGRESS))
+			fld_chksts += 2; // skip "* "
+		stats[ST_F_AGENT_STATUS] = mkf_str(FN_OUTPUT, fld_chksts);
 
-			if (stats[ST_F_CHECK_CODE].type)
-				chunk_appendf(&trash, "/%d", stats[ST_F_CHECK_CODE].u.u32);
+		if (sv->agent.status >= HCHK_STATUS_L57DATA)
+			stats[ST_F_AGENT_CODE] = mkf_u32(FN_OUTPUT, sv->agent.code);
 
-			if (stats[ST_F_CHECK_DURATION].type && stats[ST_F_CHECK_DURATION].u.u64 >= 0)
-				chunk_appendf(&trash, " in %lums", (long)stats[ST_F_CHECK_DURATION].u.u64);
+		if (sv->agent.status >= HCHK_STATUS_CHECKED)
+			stats[ST_F_AGENT_DURATION] = mkf_u64(FN_DURATION, sv->agent.duration);
 
-			chunk_appendf(&trash, "<div class=tips>%s", field_str(stats, ST_F_CHECK_DESC));
+		stats[ST_F_AGENT_DESC] = mkf_str(FN_OUTPUT, get_check_status_description(sv->agent.status));
+		stats[ST_F_LAST_AGT] = mkf_str(FN_OUTPUT, sv->agent.desc);
+		stats[ST_F_AGENT_RISE]   = mkf_u32(FO_CONFIG|FS_SERVICE, sv->agent.rise);
+		stats[ST_F_AGENT_FALL]   = mkf_u32(FO_CONFIG|FS_SERVICE, sv->agent.fall);
+		stats[ST_F_AGENT_HEALTH] = mkf_u32(FO_CONFIG|FS_SERVICE, sv->agent.health);
+	}
 
-			if (*field_str(stats, ST_F_LAST_CHK)) {
-				chunk_appendf(&trash, ": ");
-				chunk_initstr(&src, field_str(stats, ST_F_LAST_CHK));
-				chunk_htmlencode(&trash, &src);
-			}
-			chunk_appendf(&trash, "</div></u>");
-		}
-		else
-			chunk_appendf(&trash, "</td><td>");
+	/* http response: 1xx, 2xx, 3xx, 4xx, 5xx, other */
+	if (px->mode == PR_MODE_HTTP) {
+		stats[ST_F_HRSP_1XX]   = mkf_u64(FN_COUNTER, sv->counters.p.http.rsp[1]);
+		stats[ST_F_HRSP_2XX]   = mkf_u64(FN_COUNTER, sv->counters.p.http.rsp[2]);
+		stats[ST_F_HRSP_3XX]   = mkf_u64(FN_COUNTER, sv->counters.p.http.rsp[3]);
+		stats[ST_F_HRSP_4XX]   = mkf_u64(FN_COUNTER, sv->counters.p.http.rsp[4]);
+		stats[ST_F_HRSP_5XX]   = mkf_u64(FN_COUNTER, sv->counters.p.http.rsp[5]);
+		stats[ST_F_HRSP_OTHER] = mkf_u64(FN_COUNTER, sv->counters.p.http.rsp[0]);
+	}
 
-		chunk_appendf(&trash,
-		              /* weight */
-		              "</td><td class=ac>%d</td>"
-		              /* act, bck */
-		              "<td class=ac>%s</td><td class=ac>%s</td>"
-		              "",
-		              stats[ST_F_WEIGHT].u.u32,
-		              stats[ST_F_BCK].u.u32 ? "-" : "Y",
-		              stats[ST_F_BCK].u.u32 ? "Y" : "-");
+	if (ref->observe)
+		stats[ST_F_HANAFAIL] = mkf_u64(FN_COUNTER, sv->counters.failed_hana);
 
-		/* check failures: unique, fatal, down time */
-		if (stats[ST_F_CHKFAIL].type) {
-			chunk_appendf(&trash, "<td><u>%lld", (long long)stats[ST_F_CHKFAIL].u.u64);
+	stats[ST_F_CLI_ABRT] = mkf_u64(FN_COUNTER, sv->counters.cli_aborts);
+	stats[ST_F_SRV_ABRT] = mkf_u64(FN_COUNTER, sv->counters.srv_aborts);
+	stats[ST_F_LASTSESS] = mkf_s32(FN_AGE, srv_lastsession(sv));
 
-			if (stats[ST_F_HANAFAIL].type)
-				chunk_appendf(&trash, "/%lld", (long long)stats[ST_F_HANAFAIL].u.u64);
+	stats[ST_F_QTIME] = mkf_u32(FN_AVG, swrate_avg(sv->counters.q_time, TIME_STATS_SAMPLES));
+	stats[ST_F_CTIME] = mkf_u32(FN_AVG, swrate_avg(sv->counters.c_time, TIME_STATS_SAMPLES));
+	stats[ST_F_RTIME] = mkf_u32(FN_AVG, swrate_avg(sv->counters.d_time, TIME_STATS_SAMPLES));
+	stats[ST_F_TTIME] = mkf_u32(FN_AVG, swrate_avg(sv->counters.t_time, TIME_STATS_SAMPLES));
 
-			chunk_appendf(&trash,
-			              "<div class=tips>Failed Health Checks%s</div></u></td>"
-			              "<td>%lld</td><td>%s</td>"
-			              "",
-			              stats[ST_F_HANAFAIL].type ? "/Health Analyses" : "",
-			              (long long)stats[ST_F_CHKDOWN].u.u64, human_time(stats[ST_F_DOWNTIME].u.u32, 1));
-		}
-		else if (strcmp(field_str(stats, ST_F_STATUS), "MAINT") != 0 && field_format(stats, ST_F_TRACKED) == FF_STR) {
-			/* tracking a server (hence inherited maint would appear as "MAINT (via...)" */
-			chunk_appendf(&trash,
-			              "<td class=ac colspan=3><a class=lfsb href=\"#%s\">via %s</a></td>",
-			              field_str(stats, ST_F_TRACKED), field_str(stats, ST_F_TRACKED));
+	if (flags & ST_SHLGNDS) {
+		switch (addr_to_str(&sv->addr, str, sizeof(str))) {
+		case AF_INET:
+			stats[ST_F_ADDR] = mkf_str(FO_CONFIG|FS_SERVICE, chunk_newstr(out));
+			chunk_appendf(out, "%s:%d", str, get_host_port(&sv->addr));
+			break;
+		case AF_INET6:
+			stats[ST_F_ADDR] = mkf_str(FO_CONFIG|FS_SERVICE, chunk_newstr(out));
+			chunk_appendf(out, "[%s]:%d", str, get_host_port(&sv->addr));
+			break;
+		case AF_UNIX:
+			stats[ST_F_ADDR] = mkf_str(FO_CONFIG|FS_SERVICE, "unix");
+			break;
+		case -1:
+			stats[ST_F_ADDR] = mkf_str(FO_CONFIG|FS_SERVICE, chunk_newstr(out));
+			chunk_strcat(out, strerror(errno));
+			break;
+		default: /* address family not supported */
+			break;
 		}
-		else
-			chunk_appendf(&trash, "<td colspan=3></td>");
 
-		/* throttle */
-		if (stats[ST_F_THROTTLE].type)
-			chunk_appendf(&trash, "<td class=ac>%d %%</td></tr>\n", stats[ST_F_THROTTLE].u.u32);
-		else
-			chunk_appendf(&trash, "<td class=ac>-</td></tr>\n");
+		if (sv->cookie)
+			stats[ST_F_COOKIE] = mkf_str(FO_CONFIG|FN_NAME|FS_SERVICE, sv->cookie);
+	}
+
+	if (appctx->ctx.stats.flags & STAT_FMT_HTML) {
+		int admin;
+
+		admin = (px->cap & PR_CAP_BE) && px->srv && (appctx->ctx.stats.flags & STAT_ADMIN);
+		stats_dump_fields_html(stats, admin, flags, px);
 	}
 	else { /* CSV mode */
 		/* dump everything */