]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
REORG: stats: dump the server stats via the generic function
authorWilly Tarreau <w@1wt.eu>
Fri, 8 Jan 2016 16:23:28 +0000 (17:23 +0100)
committerWilly Tarreau <w@1wt.eu>
Fri, 11 Mar 2016 16:08:05 +0000 (17:08 +0100)
The code was simply moved as-is to the new function. There's no
functional change.

src/dumpstats.c

index 00c9bfeed7fa744cf4e08833bc12b2732e90433c..83cd86a13ea43f21384a3dec25d9fbd81c31f999 100644 (file)
@@ -3254,6 +3254,8 @@ static int stats_dump_fields_csv(struct chunk *out, const struct field *stats)
  */
 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 @@ static int stats_dump_fields_html(const struct field *stats, int admin, unsigned
                              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 @@ static int stats_dump_sv_stats(struct stream_interface *si, struct proxy *px, in
        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 @@ static int stats_dump_sv_stats(struct stream_interface *si, struct proxy *px, in
                              (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 */