MINOR: stats: add by HTTP version cumulated number of sessions and requests

Add cum_sess_ver[] new array of counters to count the number of cumulated
HTTP sessions by version (h1, h2 or h3).
Implement proxy_inc_fe_cum_sess_ver_ctr() to increment these counter.
This function is called each a HTTP mux is correctly initialized. The QUIC
must before verify the application operations for the mux is for h3 before
calling proxy_inc_fe_cum_sess_ver_ctr().
ST_F_SESS_OTHER stat field for the cumulated of sessions others than
HTTP sessions is deduced from ->cum_sess_ver counter (for all the session,
not only HTTP sessions) from which the HTTP sessions counters are substracted.

Add cum_req[] new array of counters to count the number of cumulated HTTP
requests by version and others than HTTP requests. This new member replace ->cum_req.
Modify proxy_inc_fe_req_ctr() which increments these counters to pass an HTTP
version, 0 special values meaning "other than an HTTP request". This is the case
for instance for syslog.c from which proxy_inc_fe_req_ctr() is called with 0
as version parameter.
ST_F_REQ_TOT stat field compputing for the cumulated number of requests is modified
to count the sum of all the cum_req[] counters.

As this patch is useful for QUIC, it must be backported to 2.7.
diff --git a/include/haproxy/counters-t.h b/include/haproxy/counters-t.h
index 1ea68dd..849f096 100644
--- a/include/haproxy/counters-t.h
+++ b/include/haproxy/counters-t.h
@@ -28,6 +28,7 @@
 	unsigned int conn_max;                  /* max # of active sessions */
 	long long    cum_conn;                  /* cumulated number of received connections */
 	long long    cum_sess;                  /* cumulated number of accepted connections */
+	long long    cum_sess_ver[3];           /* cumulated number of h1/h2/h3 sessions */
 
 	unsigned int cps_max;                   /* maximum of new connections received per second */
 	unsigned int sps_max;                   /* maximum of new connections accepted per second (sessions) */
@@ -53,7 +54,7 @@
 
 	union {
 		struct {
-			long long cum_req;      /* cumulated number of processed HTTP requests */
+			long long cum_req[4];   /* cumulated number of processed other/h1/h2/h3 requests */
 			long long comp_rsp;     /* number of compressed responses */
 			unsigned int rps_max;   /* maximum of new HTTP requests second observed */
 			long long rsp[6];       /* http response codes */
diff --git a/include/haproxy/proxy.h b/include/haproxy/proxy.h
index 0197153..27a7ae8 100644
--- a/include/haproxy/proxy.h
+++ b/include/haproxy/proxy.h
@@ -143,6 +143,21 @@
 			     update_freq_ctr(&fe->fe_sess_per_sec, 1));
 }
 
+/* increase the number of cumulated HTTP sessions on the designated frontend.
+ * <http_ver> must be the HTTP version for such requests.
+ */
+static inline void proxy_inc_fe_cum_sess_ver_ctr(struct listener *l, struct proxy *fe,
+                                                 unsigned int http_ver)
+{
+	if (http_ver == 0 ||
+	    http_ver > sizeof(fe->fe_counters.cum_sess_ver) / sizeof(*fe->fe_counters.cum_sess_ver))
+	    return;
+
+	_HA_ATOMIC_INC(&fe->fe_counters.cum_sess_ver[http_ver - 1]);
+	if (l && l->counters)
+		_HA_ATOMIC_INC(&l->counters->cum_sess_ver[http_ver - 1]);
+}
+
 /* increase the number of cumulated connections on the designated backend */
 static inline void proxy_inc_be_ctr(struct proxy *be)
 {
@@ -151,12 +166,19 @@
 			     update_freq_ctr(&be->be_sess_per_sec, 1));
 }
 
-/* increase the number of cumulated requests on the designated frontend */
-static inline void proxy_inc_fe_req_ctr(struct listener *l, struct proxy *fe)
+/* increase the number of cumulated requests on the designated frontend.
+ * <http_ver> must be the HTTP version for HTTP request. 0 may be provided
+ * for others requests.
+ */
+static inline void proxy_inc_fe_req_ctr(struct listener *l, struct proxy *fe,
+                                        unsigned int http_ver)
 {
-	_HA_ATOMIC_INC(&fe->fe_counters.p.http.cum_req);
+	if (http_ver >= sizeof(fe->fe_counters.p.http.cum_req) / sizeof(*fe->fe_counters.p.http.cum_req))
+	    return;
+
+	_HA_ATOMIC_INC(&fe->fe_counters.p.http.cum_req[http_ver]);
 	if (l && l->counters)
-		_HA_ATOMIC_INC(&l->counters->p.http.cum_req);
+		_HA_ATOMIC_INC(&l->counters->p.http.cum_req[http_ver]);
 	HA_ATOMIC_UPDATE_MAX(&fe->fe_counters.p.http.rps_max,
 			     update_freq_ctr(&fe->fe_req_per_sec, 1));
 }
diff --git a/include/haproxy/stats-t.h b/include/haproxy/stats-t.h
index 9cc1d33..5a46d30 100644
--- a/include/haproxy/stats-t.h
+++ b/include/haproxy/stats-t.h
@@ -459,6 +459,14 @@
 	ST_F_AGG_SRV_CHECK_STATUS,
 	ST_F_AGG_CHECK_STATUS,
 	ST_F_SRID,
+	ST_F_SESS_OTHER,
+	ST_F_H1SESS,
+	ST_F_H2SESS,
+	ST_F_H3SESS,
+	ST_F_REQ_OTHER,
+	ST_F_H1REQ,
+	ST_F_H2REQ,
+	ST_F_H3REQ,
 
 	/* must always be the last one */
 	ST_F_TOTAL_FIELDS
diff --git a/src/http_ana.c b/src/http_ana.c
index 4f1decd..8cee6f9 100644
--- a/src/http_ana.c
+++ b/src/http_ana.c
@@ -94,6 +94,8 @@
 	struct http_msg *msg = &txn->req;
 	struct htx *htx;
 	struct htx_sl *sl;
+	char http_ver;
+	int len;
 
 	DBG_TRACE_ENTER(STRM_EV_STRM_ANA|STRM_EV_HTTP_ANA, s, txn, msg);
 
@@ -127,11 +129,22 @@
 	}
 
 	htx = htxbuf(&req->buf);
+	sl = http_get_stline(htx);
+	len = HTX_SL_REQ_VLEN(sl);
+	if (len < 6) {
+		http_ver = 0;
+	}
+	else {
+		char *ptr;
+
+		ptr = HTX_SL_REQ_VPTR(sl);
+		http_ver = ptr[5] - '0';
+	}
 
 	/* Parsing errors are caught here */
 	if (htx->flags & (HTX_FL_PARSING_ERROR|HTX_FL_PROCESSING_ERROR)) {
 		stream_inc_http_req_ctr(s);
-		proxy_inc_fe_req_ctr(sess->listener, sess->fe);
+		proxy_inc_fe_req_ctr(sess->listener, sess->fe, http_ver);
 		if (htx->flags & HTX_FL_PARSING_ERROR) {
 			stream_inc_http_err_ctr(s);
 			goto return_bad_req;
@@ -145,13 +158,12 @@
 
 	msg->msg_state = HTTP_MSG_BODY;
 	stream_inc_http_req_ctr(s);
-	proxy_inc_fe_req_ctr(sess->listener, sess->fe); /* one more valid request for this FE */
+	proxy_inc_fe_req_ctr(sess->listener, sess->fe, http_ver); /* one more valid request for this FE */
 
 	/* kill the pending keep-alive timeout */
 	req->analyse_exp = TICK_ETERNITY;
 
 	BUG_ON(htx_get_first_type(htx) != HTX_BLK_REQ_SL);
-	sl = http_get_stline(htx);
 
 	/* 0: we might have to print this header in debug mode */
 	if (unlikely((global.mode & MODE_DEBUG) &&
diff --git a/src/log.c b/src/log.c
index 3fbae30..fc4cdd9 100644
--- a/src/log.c
+++ b/src/log.c
@@ -3546,7 +3546,7 @@
 
 			/* update counters */
 			_HA_ATOMIC_INC(&cum_log_messages);
-			proxy_inc_fe_req_ctr(l, l->bind_conf->frontend);
+			proxy_inc_fe_req_ctr(l, l->bind_conf->frontend, 0);
 
 			parse_log_message(buf->area, buf->data, &level, &facility, metadata, &message, &size);
 
@@ -3653,7 +3653,7 @@
 
 		/* update counters */
 		_HA_ATOMIC_INC(&cum_log_messages);
-		proxy_inc_fe_req_ctr(l, frontend);
+		proxy_inc_fe_req_ctr(l, frontend, 0);
 
 		parse_log_message(buf->area, buf->data, &level, &facility, metadata, &message, &size);
 
diff --git a/src/mux_h1.c b/src/mux_h1.c
index ce51f43..1dea699 100644
--- a/src/mux_h1.c
+++ b/src/mux_h1.c
@@ -978,6 +978,8 @@
 	else if (h1_recv_allowed(h1c))
 		h1c->conn->xprt->subscribe(h1c->conn, h1c->conn->xprt_ctx, SUB_RETRY_RECV, &h1c->wait_event);
 
+	if (!conn_is_back(conn))
+		proxy_inc_fe_cum_sess_ver_ctr(sess->listener, proxy, 1);
 	HA_ATOMIC_INC(&h1c->px_counters->open_conns);
 	HA_ATOMIC_INC(&h1c->px_counters->total_conns);
 
@@ -2655,7 +2657,7 @@
 	int ret = 0;
 
 	session_inc_http_req_ctr(sess);
-	proxy_inc_fe_req_ctr(sess->listener, sess->fe);
+	proxy_inc_fe_req_ctr(sess->listener, sess->fe, 1);
 	_HA_ATOMIC_INC(&sess->fe->fe_counters.p.http.rsp[5]);
 	_HA_ATOMIC_INC(&sess->fe->fe_counters.internal_errors);
 	if (sess->listener && sess->listener->counters)
@@ -2685,7 +2687,7 @@
 
 	session_inc_http_req_ctr(sess);
 	session_inc_http_err_ctr(sess);
-	proxy_inc_fe_req_ctr(sess->listener, sess->fe);
+	proxy_inc_fe_req_ctr(sess->listener, sess->fe, 1);
 	_HA_ATOMIC_INC(&sess->fe->fe_counters.p.http.rsp[4]);
 	_HA_ATOMIC_INC(&sess->fe->fe_counters.failed_req);
 	if (sess->listener && sess->listener->counters)
@@ -2717,7 +2719,7 @@
 	}
 
 	session_inc_http_req_ctr(sess);
-	proxy_inc_fe_req_ctr(sess->listener, sess->fe);
+	proxy_inc_fe_req_ctr(sess->listener, sess->fe, 1);
 	_HA_ATOMIC_INC(&sess->fe->fe_counters.p.http.rsp[4]);
 	_HA_ATOMIC_INC(&sess->fe->fe_counters.failed_req);
 	if (sess->listener && sess->listener->counters)
@@ -2747,7 +2749,7 @@
 	}
 
 	session_inc_http_req_ctr(sess);
-	proxy_inc_fe_req_ctr(sess->listener, sess->fe);
+	proxy_inc_fe_req_ctr(sess->listener, sess->fe, 1);
 	_HA_ATOMIC_INC(&sess->fe->fe_counters.p.http.rsp[4]);
 	_HA_ATOMIC_INC(&sess->fe->fe_counters.failed_req);
 	if (sess->listener && sess->listener->counters)
diff --git a/src/mux_h2.c b/src/mux_h2.c
index 4c5171a..bc82cb3 100644
--- a/src/mux_h2.c
+++ b/src/mux_h2.c
@@ -26,6 +26,7 @@
 #include <haproxy/log.h>
 #include <haproxy/mux_h2-t.h>
 #include <haproxy/net_helper.h>
+#include <haproxy/proxy.h>
 #include <haproxy/session-t.h>
 #include <haproxy/stats.h>
 #include <haproxy/stconn.h>
@@ -1038,6 +1039,7 @@
 			goto fail_stream;
 	}
 
+	proxy_inc_fe_cum_sess_ver_ctr(sess->listener, prx, 2);
 	HA_ATOMIC_INC(&h2c->px_counters->open_conns);
 	HA_ATOMIC_INC(&h2c->px_counters->total_conns);
 
diff --git a/src/mux_quic.c b/src/mux_quic.c
index 1060635..26006fb 100644
--- a/src/mux_quic.c
+++ b/src/mux_quic.c
@@ -5,9 +5,11 @@
 #include <haproxy/api.h>
 #include <haproxy/connection.h>
 #include <haproxy/dynbuf.h>
+#include <haproxy/h3.h>
 #include <haproxy/list.h>
 #include <haproxy/ncbuf.h>
 #include <haproxy/pool.h>
+#include <haproxy/proxy.h>
 #include <haproxy/qmux_http.h>
 #include <haproxy/qmux_trace.h>
 #include <haproxy/quic_conn.h>
@@ -2208,6 +2210,9 @@
 		goto fail_install_app_ops;
 	}
 
+	if (qcc->app_ops == &h3_ops)
+		proxy_inc_fe_cum_sess_ver_ctr(sess->listener, prx, 3);
+
 	/* init read cycle */
 	tasklet_wakeup(qcc->wait_event.tasklet);
 
diff --git a/src/stats.c b/src/stats.c
index 5e3dfbe..4a1dca2 100644
--- a/src/stats.c
+++ b/src/stats.c
@@ -263,6 +263,14 @@
 	[ST_F_AGG_SRV_STATUS ]               = { .name = "agg_server_status",           .desc = "Backend's aggregated gauge of servers' status" },
 	[ST_F_AGG_CHECK_STATUS]              = { .name = "agg_check_status",            .desc = "Backend's aggregated gauge of servers' state check status" },
 	[ST_F_SRID]                          = { .name = "srid",                        .desc = "Server id revision, to prevent server id reuse mixups" },
+	[ST_F_SESS_OTHER]                    = { .name = "sess_other",                  .desc = "Total number of sessions other than HTTP since process started" },
+	[ST_F_H1SESS]                        = { .name = "h1sess",                      .desc = "Total number of HTTP/1 sessions since process started" },
+	[ST_F_H2SESS]                        = { .name = "h2sess",                      .desc = "Total number of HTTP/2 sessions since process started" },
+	[ST_F_H3SESS]                        = { .name = "h3sess",                      .desc = "Total number of HTTP/3 sessions since process started" },
+	[ST_F_REQ_OTHER]                     = { .name = "req_other",                   .desc = "Total number of sessions other than HTTP processed by this object since the worker process started" },
+	[ST_F_H1REQ]                         = { .name = "h1req",                       .desc = "Total number of HTTP/1 sessions processed by this object since the worker process started" },
+	[ST_F_H2REQ]                         = { .name = "h2req",                       .desc = "Total number of hTTP/2 sessions processed by this object since the worker process started" },
+	[ST_F_H3REQ]                         = { .name = "h3req",                       .desc = "Total number of HTTP/3 sessions processed by this object since the worker process started" },
 };
 
 /* one line of info */
@@ -917,7 +925,27 @@
 		/* http response (via hover): 1xx, 2xx, 3xx, 4xx, 5xx, other */
 		if (strcmp(field_str(stats, ST_F_MODE), "http") == 0) {
 			chunk_appendf(out,
+			              "<tr><th>- HTTP/1 sessions:</th><td>%s</td></tr>"
+			              "<tr><th>- HTTP/2 sessions:</th><td>%s</td></tr>"
+			              "<tr><th>- HTTP/3 sessions:</th><td>%s</td></tr>"
+			              "<tr><th>- other sessions:</th><td>%s</td></tr>"
 			              "<tr><th>Cum. HTTP requests:</th><td>%s</td></tr>"
+			              "<tr><th>- HTTP/1 requests:</th><td>%s</td></tr>"
+			              "<tr><th>- HTTP/2 requests:</th><td>%s</td></tr>"
+			              "<tr><th>- HTTP/3 requests:</th><td>%s</td></tr>"
+			              "<tr><th>- other requests:</th><td>%s</td></tr>"
+			              "",
+			              U2H(stats[ST_F_H1SESS].u.u64),
+			              U2H(stats[ST_F_H2SESS].u.u64),
+			              U2H(stats[ST_F_H3SESS].u.u64),
+			              U2H(stats[ST_F_SESS_OTHER].u.u64),
+			              U2H(stats[ST_F_REQ_TOT].u.u64),
+			              U2H(stats[ST_F_H1REQ].u.u64),
+			              U2H(stats[ST_F_H2REQ].u.u64),
+			              U2H(stats[ST_F_H3REQ].u.u64),
+			              U2H(stats[ST_F_REQ_OTHER].u.u64));
+
+			chunk_appendf(out,
 			              "<tr><th>- HTTP 1xx responses:</th><td>%s</td></tr>"
 			              "<tr><th>- HTTP 2xx responses:</th><td>%s</td></tr>"
 			              "<tr><th>&nbsp;&nbsp;Compressed 2xx:</th><td>%s</td><td>(%d%%)</td></tr>"
@@ -925,13 +953,7 @@
 			              "<tr><th>- HTTP 4xx responses:</th><td>%s</td></tr>"
 			              "<tr><th>- HTTP 5xx responses:</th><td>%s</td></tr>"
 			              "<tr><th>- other responses:</th><td>%s</td></tr>"
-			              "<tr><th>Intercepted requests:</th><td>%s</td></tr>"
-			              "<tr><th>Cache lookups:</th><td>%s</td></tr>"
-			              "<tr><th>Cache hits:</th><td>%s</td><td>(%d%%)</td></tr>"
-			              "<tr><th>Failed hdr rewrites:</th><td>%s</td></tr>"
-			              "<tr><th>Internal errors:</th><td>%s</td></tr>"
 			              "",
-			              U2H(stats[ST_F_REQ_TOT].u.u64),
 			              U2H(stats[ST_F_HRSP_1XX].u.u64),
 			              U2H(stats[ST_F_HRSP_2XX].u.u64),
 			              U2H(stats[ST_F_COMP_RSP].u.u64),
@@ -940,7 +962,15 @@
 			              U2H(stats[ST_F_HRSP_3XX].u.u64),
 			              U2H(stats[ST_F_HRSP_4XX].u.u64),
 			              U2H(stats[ST_F_HRSP_5XX].u.u64),
-			              U2H(stats[ST_F_HRSP_OTHER].u.u64),
+			              U2H(stats[ST_F_HRSP_OTHER].u.u64));
+
+			chunk_appendf(out,
+			              "<tr><th>Intercepted requests:</th><td>%s</td></tr>"
+			              "<tr><th>Cache lookups:</th><td>%s</td></tr>"
+			              "<tr><th>Cache hits:</th><td>%s</td><td>(%d%%)</td></tr>"
+			              "<tr><th>Failed hdr rewrites:</th><td>%s</td></tr>"
+			              "<tr><th>Internal errors:</th><td>%s</td></tr>"
+			              "",
 			              U2H(stats[ST_F_INTERCEPTED].u.u64),
 			              U2H(stats[ST_F_CACHE_LOOKUPS].u.u64),
 			              U2H(stats[ST_F_CACHE_HITS].u.u64),
@@ -1805,9 +1835,18 @@
 			case ST_F_REQ_RATE_MAX:
 				metric = mkf_u32(FN_MAX, px->fe_counters.p.http.rps_max);
 				break;
-			case ST_F_REQ_TOT:
-				metric = mkf_u64(FN_COUNTER, px->fe_counters.p.http.cum_req);
+			case ST_F_REQ_TOT: {
+				int i;
+				uint64_t total_req;
+				size_t nb_reqs =
+					sizeof(px->fe_counters.p.http.cum_req) / sizeof(*px->fe_counters.p.http.cum_req);
+
+				total_req = 0;
+				for (i = 0; i < nb_reqs; i++)
+					total_req += px->fe_counters.p.http.cum_req[i];
+				metric = mkf_u64(FN_COUNTER, total_req);
 				break;
+			}
 			case ST_F_COMP_IN:
 				metric = mkf_u64(FN_COUNTER, px->fe_counters.comp_in);
 				break;
@@ -1829,6 +1868,39 @@
 			case ST_F_CONN_TOT:
 				metric = mkf_u64(FN_COUNTER, px->fe_counters.cum_conn);
 				break;
+			case ST_F_SESS_OTHER: {
+				int i;
+				uint64_t total_sess;
+				size_t nb_sess =
+					sizeof(px->fe_counters.cum_sess_ver) / sizeof(*px->fe_counters.cum_sess_ver);
+
+				total_sess = px->fe_counters.cum_sess;
+				for (i = 0; i < nb_sess; i++)
+					total_sess -= px->fe_counters.cum_sess_ver[i];
+				metric = mkf_u64(FN_COUNTER, total_sess);
+				break;
+			}
+			case ST_F_H1SESS:
+				metric = mkf_u64(FN_COUNTER, px->fe_counters.cum_sess_ver[0]);
+				break;
+			case ST_F_H2SESS:
+				metric = mkf_u64(FN_COUNTER, px->fe_counters.cum_sess_ver[1]);
+				break;
+			case ST_F_H3SESS:
+				metric = mkf_u64(FN_COUNTER, px->fe_counters.cum_sess_ver[2]);
+				break;
+			case ST_F_REQ_OTHER:
+				metric = mkf_u64(FN_COUNTER, px->fe_counters.p.http.cum_req[0]);
+				break;
+			case ST_F_H1REQ:
+				metric = mkf_u64(FN_COUNTER, px->fe_counters.p.http.cum_req[1]);
+				break;
+			case ST_F_H2REQ:
+				metric = mkf_u64(FN_COUNTER, px->fe_counters.p.http.cum_req[2]);
+				break;
+			case ST_F_H3REQ:
+				metric = mkf_u64(FN_COUNTER, px->fe_counters.p.http.cum_req[3]);
+				break;
 			default:
 				/* not used for frontends. If a specific metric
 				 * is requested, return an error. Otherwise continue.