[STATS] count transfer aborts caused by client and by server

Often we need to understand why some transfers were aborted or what
constitutes server response errors. With those two counters, it is
now possible to detect an unexpected transfer abort during a data
phase (eg: too short HTTP response), and to know what part of the
server response errors may in fact be assigned to aborted transfers.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 394a46f..761edfb 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -7584,7 +7584,7 @@
  11. dresp: denied responses
  12. ereq: request errors
  13. econ: connection errors
- 14. eresp: response errors
+ 14. eresp: response errors (among which srv_abrt)
  15. wretr: retries (warning)
  16. wredis: redispatches (warning)
  17. status: status (UP/DOWN/NOLB/MAINT/MAINT(via)...)
@@ -7635,6 +7635,8 @@
  46. req_rate: HTTP requests per second over last elapsed second
  47. req_rate_max: max number of HTTP requests per second observed
  48. req_tot: total number of HTTP requests received
+ 49. cli_abrt: number of data transfers aborted by the client
+ 50. srv_abrt: number of data transfers aborted by the server (inc. in eresp)
 
 
 9.2. Unix Socket commands
diff --git a/include/types/counters.h b/include/types/counters.h
index 33fbb5c..6e620e1 100644
--- a/include/types/counters.h
+++ b/include/types/counters.h
@@ -47,6 +47,7 @@
 	} fe, be;				/* FE and BE stats */
 
 	long long failed_conns, failed_resp;	/* failed connect() and responses */
+	long long cli_aborts, srv_aborts;	/* aborted responses during DATA phase due to client or server */
 	long long retries, redispatches;	/* retried and redispatched connections */
 };
 
@@ -74,6 +75,7 @@
 	long long bytes_out;			/* number of bytes transferred from the server to the client */
 
 	long long failed_conns, failed_resp;	/* failed connect() and responses */
+	long long cli_aborts, srv_aborts;	/* aborted responses during DATA phase due to client or server */
 	long long retries, redispatches;	/* retried and redispatched connections */
 	long long failed_secu;			/* blocked responses because of security concerns */
 
diff --git a/src/dumpstats.c b/src/dumpstats.c
index d3d036b..b2967fc 100644
--- a/src/dumpstats.c
+++ b/src/dumpstats.c
@@ -248,7 +248,8 @@
 			    "rate,rate_lim,rate_max,"
 			    "check_status,check_code,check_duration,"
 			    "hrsp_1xx,hrsp_2xx,hrsp_3xx,hrsp_4xx,hrsp_5xx,hrsp_other,hanafail,"
-			    "req_rate, req_rate_max, req_tot,"
+			    "req_rate,req_rate_max,req_tot,"
+			    "cli_abrt,srv_abrt,"
 			    "\n");
 }
 
@@ -1543,6 +1544,9 @@
 					     read_freq_ctr(&px->fe_req_per_sec),
 					     px->counters.fe_rps_max, px->counters.cum_fe_req);
 
+				/* errors: cli_aborts, srv_aborts */
+				chunk_printf(&msg, ",,");
+
 				/* finish with EOL */
 				chunk_printf(&msg, "\n");
 			}
@@ -1672,6 +1676,8 @@
 				     ","
 				     /* requests : req_rate, req_rate_max, req_tot, */
 				     ",,,"
+				     /* errors: cli_aborts, srv_aborts */
+				     ",,"
 				     "\n",
 				     px->id, l->name,
 				     l->nbconn, l->counters->conn_max,
@@ -1828,14 +1834,19 @@
 				     "<td>%s</td><td>%s</td>"
 				     /* denied: req, resp */
 				     "<td></td><td>%s</td>"
-				     /* errors : request, connect, response */
-				     "<td></td><td>%s</td><td>%s</td>\n"
+				     /* errors : request, connect */
+				     "<td></td><td>%s</td>"
+				     /* errors : response */
+				     "<td title=\"Connection resets during transfers: %s client, %s server\"><u>%s</u></td>"
 				     /* warnings: retries, redispatches */
 				     "<td>%lld</td><td>%lld</td>"
 				     "",
 				     U2H0(sv->counters.bytes_in), U2H1(sv->counters.bytes_out),
 				     U2H2(sv->counters.failed_secu),
-				     U2H3(sv->counters.failed_conns), U2H4(sv->counters.failed_resp),
+				     U2H3(sv->counters.failed_conns),
+				     U2H4(sv->counters.cli_aborts),
+				     U2H5(sv->counters.srv_aborts),
+				     U2H6(sv->counters.failed_resp),
 				     sv->counters.retries, sv->counters.redispatches);
 
 				/* status, lest check */
@@ -2062,6 +2073,10 @@
 				/* requests : req_rate, req_rate_max, req_tot, */
 				chunk_printf(&msg, ",,,");
 
+				/* errors: cli_aborts, srv_aborts */
+				chunk_printf(&msg, "%lld,%lld,",
+					     sv->counters.cli_aborts, sv->counters.srv_aborts);
+
 				/* finish with EOL */
 				chunk_printf(&msg, "\n");
 			}
@@ -2150,8 +2165,10 @@
 				chunk_printf(&msg,
 				     /* denied: req, resp */
 				     "<td>%s</td><td>%s</td>"
-				     /* errors : request, connect, response */
-				     "<td></td><td>%s</td><td>%s</td>\n"
+				     /* errors : request, connect */
+				     "<td></td><td>%s</td>"
+				     /* errors : response */
+				     "<td title=\"Connection resets during transfers: %s client, %s server\"><u>%s</u></td>"
 				     /* warnings: retries, redispatches */
 				     "<td>%lld</td><td>%lld</td>"
 				     /* backend status: reflect backend status (up/down): we display UP
@@ -2162,7 +2179,10 @@
 				     "<td class=ac>%d</td><td class=ac>%d</td>"
 				     "",
 				     U2H0(px->counters.denied_req), U2H1(px->counters.denied_resp),
-				     U2H2(px->counters.failed_conns), U2H3(px->counters.failed_resp),
+				     U2H2(px->counters.failed_conns),
+				     U2H3(px->counters.cli_aborts),
+				     U2H4(px->counters.srv_aborts),
+				     U2H5(px->counters.failed_resp),
 				     px->counters.retries, px->counters.redispatches,
 				     human_time(now.tv_sec - px->last_change, 1),
 				     (px->lbprm.tot_weight > 0 || !px->srv) ? "UP" :
@@ -2243,6 +2263,10 @@
 				/* requests : req_rate, req_rate_max, req_tot, */
 				chunk_printf(&msg, ",,,");
 
+				/* errors: cli_aborts, srv_aborts */
+				chunk_printf(&msg, "%lld,%lld,",
+					     px->counters.cli_aborts, px->counters.srv_aborts);
+
 				/* finish with EOL */
 				chunk_printf(&msg, "\n");
 
diff --git a/src/proto_http.c b/src/proto_http.c
index 72d2da9..3db8ac4 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -3971,6 +3971,9 @@
 		}
 		else if (buf->flags & BF_SHUTW) {
 			txn->rsp.msg_state = HTTP_MSG_ERROR;
+			s->be->counters.cli_aborts++;
+			if (s->srv)
+				s->srv->counters.cli_aborts++;
 			goto wait_other_side;
 		}
 	}
@@ -5071,6 +5074,9 @@
 	if (res->flags & BF_SHUTR) {
 		if (!(s->flags & SN_ERR_MASK))
 			s->flags |= SN_ERR_SRVCL;
+		s->be->counters.srv_aborts++;
+		if (s->srv)
+			s->srv->counters.srv_aborts++;
 		goto return_bad_res;
 	}
 
diff --git a/src/session.c b/src/session.c
index b602147..60d8d2a 100644
--- a/src/session.c
+++ b/src/session.c
@@ -893,6 +893,9 @@
 			s->si[0].shutw(&s->si[0]);
 			stream_int_report_error(&s->si[0]);
 			if (!(s->req->analysers) && !(s->rep->analysers)) {
+				s->be->counters.cli_aborts++;
+				if (s->srv)
+					s->srv->counters.cli_aborts++;
 				if (!(s->flags & SN_ERR_MASK))
 					s->flags |= SN_ERR_CLICL;
 				if (!(s->flags & SN_FINST_MASK))
@@ -910,6 +913,9 @@
 			if (s->srv)
 				s->srv->counters.failed_resp++;
 			if (!(s->req->analysers) && !(s->rep->analysers)) {
+				s->be->counters.srv_aborts++;
+				if (s->srv)
+					s->srv->counters.srv_aborts++;
 				if (!(s->flags & SN_ERR_MASK))
 					s->flags |= SN_ERR_SRVCL;
 				if (!(s->flags & SN_FINST_MASK))
@@ -1212,33 +1218,67 @@
 
 
 	/*
-	 * Now we propagate unhandled errors to the session
+	 * Now we propagate unhandled errors to the session. Normally
+	 * we're just in a data phase here since it means we have not
+	 * seen any analyser who could set an error status.
 	 */
 	if (!(s->flags & SN_ERR_MASK)) {
 		if (s->req->flags & (BF_READ_ERROR|BF_READ_TIMEOUT|BF_WRITE_ERROR|BF_WRITE_TIMEOUT)) {
 			/* Report it if the client got an error or a read timeout expired */
 			s->req->analysers = 0;
-			if (s->req->flags & BF_READ_ERROR)
+			if (s->req->flags & BF_READ_ERROR) {
+				s->be->counters.cli_aborts++;
+				if (s->srv)
+					s->srv->counters.cli_aborts++;
 				s->flags |= SN_ERR_CLICL;
-			else if (s->req->flags & BF_READ_TIMEOUT)
+			}
+			else if (s->req->flags & BF_READ_TIMEOUT) {
+				s->be->counters.cli_aborts++;
+				if (s->srv)
+					s->srv->counters.cli_aborts++;
 				s->flags |= SN_ERR_CLITO;
-			else if (s->req->flags & BF_WRITE_ERROR)
+			}
+			else if (s->req->flags & BF_WRITE_ERROR) {
+				s->be->counters.srv_aborts++;
+				if (s->srv)
+					s->srv->counters.srv_aborts++;
 				s->flags |= SN_ERR_SRVCL;
-			else
+			}
+			else {
+				s->be->counters.srv_aborts++;
+				if (s->srv)
+					s->srv->counters.srv_aborts++;
 				s->flags |= SN_ERR_SRVTO;
+			}
 			sess_set_term_flags(s);
 		}
 		else if (s->rep->flags & (BF_READ_ERROR|BF_READ_TIMEOUT|BF_WRITE_ERROR|BF_WRITE_TIMEOUT)) {
 			/* Report it if the server got an error or a read timeout expired */
 			s->rep->analysers = 0;
-			if (s->rep->flags & BF_READ_ERROR)
+			if (s->rep->flags & BF_READ_ERROR) {
+				s->be->counters.srv_aborts++;
+				if (s->srv)
+					s->srv->counters.srv_aborts++;
 				s->flags |= SN_ERR_SRVCL;
-			else if (s->rep->flags & BF_READ_TIMEOUT)
+			}
+			else if (s->rep->flags & BF_READ_TIMEOUT) {
+				s->be->counters.srv_aborts++;
+				if (s->srv)
+					s->srv->counters.srv_aborts++;
 				s->flags |= SN_ERR_SRVTO;
-			else if (s->rep->flags & BF_WRITE_ERROR)
+			}
+			else if (s->rep->flags & BF_WRITE_ERROR) {
+				s->be->counters.cli_aborts++;
+				if (s->srv)
+					s->srv->counters.cli_aborts++;
 				s->flags |= SN_ERR_CLICL;
-			else
-			s->flags |= SN_ERR_CLITO;
+			}
+			else {
+				s->be->counters.cli_aborts++;
+				if (s->srv)
+					s->srv->counters.cli_aborts++;
+				s->flags |= SN_ERR_CLITO;
+			}
 			sess_set_term_flags(s);
 		}
 	}