MEDIUM: stats: convert stats_dump_sv_stats() to use stats_dump_fields_csv()

This function now only fills the relevant fields with raw values and
calls stats_dump_fields_csv() for the CSV part. The output remains
exactly the same for now.

Some limits are only emitted if set, so the HTML part will have to
check for these being set.

A number of fields had to be built using printf-format strings, so
instead of allocating strings that risk not being freed, we use
chunk_newstr() and chunk_appendf().

Text strings are now copied verbatim in the stats fields so that only
the CSV dump encodes them as CSV. A single "out" chunk is used and cut
into multiple substrings using chunk_newstr() so that we don't need
distinct chunks for each stats field holding a string. The total amount
of data being emitted at once will never overflow the "out" chunk since
only a small part of it goes into the trash buffer which is the same size
and will thus overflow first.

One point of care is that failed_checks and down_trans were logged as
64-bit quantities on servers but they're 32-bit on proxies. This may
have to be changed later to unify them.
diff --git a/src/dumpstats.c b/src/dumpstats.c
index b87e7fc..500e56f 100644
--- a/src/dumpstats.c
+++ b/src/dumpstats.c
@@ -3547,6 +3547,20 @@
 	SRV_STATS_STATE_COUNT, /* Must be last */
 };
 
+static const char *srv_hlt_st[SRV_STATS_STATE_COUNT] = {
+	[SRV_STATS_STATE_DOWN]			= "DOWN",
+	[SRV_STATS_STATE_DOWN_AGENT]		= "DOWN (agent)",
+	[SRV_STATS_STATE_GOING_UP]		= "DOWN %d/%d",
+	[SRV_STATS_STATE_UP_GOING_DOWN]		= "UP %d/%d",
+	[SRV_STATS_STATE_UP]			= "UP",
+	[SRV_STATS_STATE_NOLB_GOING_DOWN]	= "NOLB %d/%d",
+	[SRV_STATS_STATE_NOLB]			= "NOLB",
+	[SRV_STATS_STATE_DRAIN_GOING_DOWN]	= "DRAIN %d/%d",
+	[SRV_STATS_STATE_DRAIN]			= "DRAIN",
+	[SRV_STATS_STATE_DRAIN_AGENT]		= "DRAIN (agent)",
+	[SRV_STATS_STATE_NO_CHECK]		= "no check"
+};
+
 enum srv_stats_colour {
 	SRV_STATS_COLOUR_DOWN = 0,
 	SRV_STATS_COLOUR_GOING_UP,
@@ -3581,8 +3595,8 @@
 	struct server *via, *ref;
 	char str[INET6_ADDRSTRLEN];
 	struct chunk src;
-	int i;
-
+	struct chunk *out = get_trash_chunk();
+	char *fld_status;
 	/* we have "via" which is the tracked server as described in the configuration,
 	 * and "ref" which is the checked server and the end of the chain.
 	 */
@@ -3591,6 +3605,116 @@
 	while (ref->track)
 		ref = ref->track;
 
+	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, sv->id);
+	stats[ST_F_QCUR]     = mkf_u32(0, sv->nbpend);
+	stats[ST_F_QMAX]     = mkf_u32(FN_MAX, sv->counters.nbpend_max);
+	stats[ST_F_SCUR]     = mkf_u32(0, sv->cur_sess);
+	stats[ST_F_SMAX]     = mkf_u32(FN_MAX, sv->counters.cur_sess_max);
+
+	if (sv->maxconn)
+		stats[ST_F_SLIM] = mkf_u32(FO_CONFIG|FN_LIMIT, sv->maxconn);
+
+	stats[ST_F_STOT]     = mkf_u64(FN_COUNTER, sv->counters.cum_sess);
+	stats[ST_F_BIN]      = mkf_u64(FN_COUNTER, sv->counters.bytes_in);
+	stats[ST_F_BOUT]     = mkf_u64(FN_COUNTER, sv->counters.bytes_out);
+	stats[ST_F_DRESP]    = mkf_u64(FN_COUNTER, sv->counters.failed_secu);
+	stats[ST_F_ECON]     = mkf_u64(FN_COUNTER, sv->counters.failed_conns);
+	stats[ST_F_ERESP]    = mkf_u64(FN_COUNTER, sv->counters.failed_resp);
+	stats[ST_F_WRETR]    = mkf_u64(FN_COUNTER, sv->counters.retries);
+	stats[ST_F_WREDIS]   = mkf_u64(FN_COUNTER, sv->counters.redispatches);
+
+	/* status */
+	fld_status = chunk_newstr(out);
+	if (sv->admin & SRV_ADMF_IMAINT)
+		chunk_appendf(out, "MAINT (via %s/%s)", via->proxy->id, via->id);
+	else if (sv->admin & SRV_ADMF_MAINT)
+		chunk_appendf(out, "MAINT");
+	else
+		chunk_appendf(out,
+			      srv_hlt_st[state],
+			      (ref->state != SRV_ST_STOPPED) ? (ref->check.health - ref->check.rise + 1) : (ref->check.health),
+			      (ref->state != SRV_ST_STOPPED) ? (ref->check.fall) : (ref->check.rise));
+
+	stats[ST_F_STATUS]   = mkf_str(FO_STATUS, fld_status);
+	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_LASTCHG]  = mkf_u32(FN_AGE, now.tv_sec - sv->last_change);
+		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) {
+		const char *fld_chksts;
+
+		fld_chksts = chunk_newstr(out);
+		chunk_strcat(out, get_check_status_info(sv->check.status));
+		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);
+	}
+
+	/* 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]);
+	}
+
+	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));
+
+	if ((sv->check.state & (CHK_ST_ENABLED|CHK_ST_PAUSED)) == CHK_ST_ENABLED)
+		stats[ST_F_LAST_CHK] = mkf_str(FN_OUTPUT, sv->check.desc);
+
+	if ((sv->agent.state & (CHK_ST_ENABLED|CHK_ST_PAUSED)) == CHK_ST_ENABLED)
+		stats[ST_F_LAST_AGT] = mkf_str(FN_OUTPUT, sv->agent.desc);
+
+	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 (appctx->ctx.stats.flags & STAT_FMT_HTML) {
 		static char *srv_hlt_st[SRV_STATS_STATE_COUNT] = {
 			[SRV_STATS_STATE_DOWN]			= "DOWN",
@@ -3686,6 +3810,7 @@
 		/* http response (via hover): 1xx, 2xx, 3xx, 4xx, 5xx, other */
 		if (px->mode == PR_MODE_HTTP) {
 			unsigned long long tot;
+			int i;
 			for (tot = i = 0; i < 6; i++)
 				tot += sv->counters.p.http.rsp[i];
 
@@ -3847,161 +3972,8 @@
 			chunk_appendf(&trash, "<td class=ac>-</td></tr>\n");
 	}
 	else { /* CSV mode */
-		struct chunk *out = get_trash_chunk();
-		static char *srv_hlt_st[SRV_STATS_STATE_COUNT] = {
-			[SRV_STATS_STATE_DOWN]			= "DOWN,",
-			[SRV_STATS_STATE_DOWN_AGENT]		= "DOWN (agent),",
-			[SRV_STATS_STATE_GOING_UP]		= "DOWN %d/%d,",
-			[SRV_STATS_STATE_UP_GOING_DOWN]		= "UP %d/%d,",
-			[SRV_STATS_STATE_UP]			= "UP,",
-			[SRV_STATS_STATE_NOLB_GOING_DOWN]	= "NOLB %d/%d,",
-			[SRV_STATS_STATE_NOLB]			= "NOLB,",
-			[SRV_STATS_STATE_DRAIN_GOING_DOWN]	= "DRAIN %d/%d,",
-			[SRV_STATS_STATE_DRAIN]			= "DRAIN,",
-			[SRV_STATS_STATE_DRAIN_AGENT]		= "DRAIN (agent),",
-			[SRV_STATS_STATE_NO_CHECK]		= "no check,"
-		};
-
-		chunk_appendf(&trash,
-		              /* pxid, name */
-		              "%s,%s,"
-		              /* queue : current, max */
-		              "%d,%d,"
-		              /* sessions : current, max, limit, total */
-		              "%d,%d,%s,%lld,"
-		              /* bytes : in, out */
-		              "%lld,%lld,"
-		              /* denied: req, resp */
-		              ",%lld,"
-		              /* errors : request, connect, response */
-		              ",%lld,%lld,"
-		              /* warnings: retries, redispatches */
-		              "%lld,%lld,"
-		              "",
-		              px->id, sv->id,
-		              sv->nbpend, sv->counters.nbpend_max,
-		              sv->cur_sess, sv->counters.cur_sess_max, LIM2A(sv->maxconn, ""), sv->counters.cum_sess,
-		              sv->counters.bytes_in, sv->counters.bytes_out,
-		              sv->counters.failed_secu,
-		              sv->counters.failed_conns, sv->counters.failed_resp,
-		              sv->counters.retries, sv->counters.redispatches);
-
-		/* status */
-		if (sv->admin & SRV_ADMF_IMAINT)
-			chunk_appendf(&trash, "MAINT (via %s/%s),", via->proxy->id, via->id);
-		else if (sv->admin & SRV_ADMF_MAINT)
-			chunk_appendf(&trash, "MAINT,");
-		else
-			chunk_appendf(&trash,
-			              srv_hlt_st[state],
-			              (ref->state != SRV_ST_STOPPED) ? (ref->check.health - ref->check.rise + 1) : (ref->check.health),
-			              (ref->state != SRV_ST_STOPPED) ? (ref->check.fall) : (ref->check.rise));
-
-		chunk_appendf(&trash,
-		              /* weight, active, backup */
-		              "%d,%d,%d,"
-		              "",
-		              (sv->eweight * px->lbprm.wmult + px->lbprm.wdiv - 1) / px->lbprm.wdiv,
-		              (sv->flags & SRV_F_BACKUP) ? 0 : 1,
-		              (sv->flags & SRV_F_BACKUP) ? 1 : 0);
-
-		/* check failures: unique, fatal; last change, total downtime */
-		if (sv->check.state & CHK_ST_ENABLED)
-			chunk_appendf(&trash,
-			              "%lld,%lld,%d,%d,",
-			              sv->counters.failed_checks, sv->counters.down_trans,
-			              (int)(now.tv_sec - sv->last_change), srv_downtime(sv));
-		else
-			chunk_appendf(&trash, ",,,,");
-
-		/* queue limit, pid, iid, sid, */
-		chunk_appendf(&trash,
-		              "%s,"
-		              "%d,%d,%d,",
-		              LIM2A(sv->maxqueue, ""),
-		              relative_pid, px->uuid, sv->puid);
-
-		/* throttle */
-		if (sv->state == SRV_ST_STARTING && !server_is_draining(sv))
-			chunk_appendf(&trash, "%d", server_throttle_rate(sv));
-
-		/* sessions: lbtot */
-		chunk_appendf(&trash, ",%lld,", sv->counters.cum_lbconn);
-
-		/* tracked */
-		if (sv->track)
-			chunk_appendf(&trash, "%s/%s,",
-			              sv->track->proxy->id, sv->track->id);
-		else
-			chunk_appendf(&trash, ",");
-
-		/* type */
-		chunk_appendf(&trash, "%d,", STATS_TYPE_SV);
-
-		/* rate */
-		chunk_appendf(&trash, "%u,,%u,",
-		              read_freq_ctr(&sv->sess_per_sec),
-		              sv->counters.sps_max);
-
-		if (sv->check.state & CHK_ST_ENABLED) {
-			/* check_status */
-			chunk_appendf(&trash, "%s,", csv_enc(get_check_status_info(sv->check.status), 1, out));
-
-			/* check_code */
-			if (sv->check.status >= HCHK_STATUS_L57DATA)
-				chunk_appendf(&trash, "%u,", sv->check.code);
-			else
-				chunk_appendf(&trash, ",");
-
-			/* check_duration */
-			if (sv->check.status >= HCHK_STATUS_CHECKED)
-				chunk_appendf(&trash, "%lu,", sv->check.duration);
-			else
-				chunk_appendf(&trash, ",");
-
-		}
-		else
-			chunk_appendf(&trash, ",,,");
-
-		/* http response: 1xx, 2xx, 3xx, 4xx, 5xx, other */
-		if (px->mode == PR_MODE_HTTP) {
-			for (i=1; i<6; i++)
-				chunk_appendf(&trash, "%lld,", sv->counters.p.http.rsp[i]);
-
-			chunk_appendf(&trash, "%lld,", sv->counters.p.http.rsp[0]);
-		}
-		else
-			chunk_appendf(&trash, ",,,,,,");
-
-		/* failed health analyses */
-		chunk_appendf(&trash, "%lld,",  sv->counters.failed_hana);
-
-		/* requests : req_rate, req_rate_max, req_tot, */
-		chunk_appendf(&trash, ",,,");
-
-		/* errors: cli_aborts, srv_aborts */
-		chunk_appendf(&trash, "%lld,%lld,",
-		              sv->counters.cli_aborts, sv->counters.srv_aborts);
-
-		/* compression: in, out, bypassed, comp_rsp */
-		chunk_appendf(&trash, ",,,,");
-
-		/* lastsess */
-		chunk_appendf(&trash, "%d,", srv_lastsession(sv));
-
-		/* capture of last check and agent statuses */
-		chunk_appendf(&trash, "%s,", ((sv->check.state & (CHK_ST_ENABLED|CHK_ST_PAUSED)) == CHK_ST_ENABLED) ? csv_enc(cstr(sv->check.desc), 1, out) : "");
-		chunk_appendf(&trash, "%s,", ((sv->agent.state & (CHK_ST_ENABLED|CHK_ST_PAUSED)) == CHK_ST_ENABLED) ? csv_enc(cstr(sv->agent.desc), 1, out) : "");
-
-		/* qtime, ctime, rtime, ttime, */
-		chunk_appendf(&trash, "%u,%u,%u,%u,",
-		              swrate_avg(sv->counters.q_time, TIME_STATS_SAMPLES),
-		              swrate_avg(sv->counters.c_time, TIME_STATS_SAMPLES),
-		              swrate_avg(sv->counters.d_time, TIME_STATS_SAMPLES),
-		              swrate_avg(sv->counters.t_time, TIME_STATS_SAMPLES));
-
-		/* finish with EOL */
-		chunk_appendf(&trash, "\n");
+		/* dump everything */
+		stats_dump_fields_csv(&trash, stats);
 	}
 	return 1;
 }