[MEDIUM] implement the CSV output for the statistics
It is now possible to get CSV ouput from the statistics by
simply appending ";csv" to the HTTP request sent to get the
stats. The fields keep the same ordering as in the HTML page,
and a field "pxname" has been prepended at the beginning of
the line.
diff --git a/src/dumpstats.c b/src/dumpstats.c
index 4e2929c..c77528d 100644
--- a/src/dumpstats.c
+++ b/src/dumpstats.c
@@ -72,7 +72,8 @@
"HTTP/1.0 200 OK\r\n"
"Cache-Control: no-cache\r\n"
"Connection: close\r\n"
- "Content-Type: text/html\r\n");
+ "Content-Type: %s\r\n",
+ (flags & STAT_FMT_HTML) ? "text/html" : "text/plain");
if (uri->refresh > 0 && !(s->flags & SN_STAT_NORFRSH))
chunk_printf(&msg, sizeof(trash), "Refresh: %d\r\n",
@@ -100,8 +101,9 @@
/* fall through */
case DATA_ST_HEAD:
- /* WARNING! This must fit in the first buffer !!! */
- chunk_printf(&msg, sizeof(trash),
+ if (flags & STAT_FMT_HTML) {
+ /* WARNING! This must fit in the first buffer !!! */
+ chunk_printf(&msg, sizeof(trash),
"<html><head><title>Statistics Report for " PRODUCT_NAME "</title>\n"
"<meta http-equiv=\"content-type\" content=\"text/html; charset=iso-8859-1\">\n"
"<style type=\"text/css\"><!--\n"
@@ -170,7 +172,18 @@
"table.lgd td.noborder { border-style: none; padding: 2px; white-space: nowrap;}\n"
"-->\n"
"</style></head>\n");
-
+ } else {
+ chunk_printf(&msg, sizeof(trash),
+ "# pxname,svname,"
+ "qcur,qmax,"
+ "scur,smax,slim,stot,"
+ "bin,bout,"
+ "dreq,dresp,"
+ "ereq,econ,eresp,"
+ "weight,act,bck,"
+ "chkfail,chkdown"
+ "\n");
+ }
if (buffer_write_chunk(rep, &msg) != 0)
return 0;
@@ -184,7 +197,8 @@
* We are around 3.5 kB, add adding entries will
* become tricky if we want to support 4kB buffers !
*/
- chunk_printf(&msg, sizeof(trash),
+ if (flags & STAT_FMT_HTML) {
+ chunk_printf(&msg, sizeof(trash),
"<body><h1><a href=\"" PRODUCT_URL "\" style=\"text-decoration: none;\">"
PRODUCT_NAME "%s</a></h1>\n"
"<h2>Statistics Report for pid %d</h2>\n"
@@ -226,41 +240,41 @@
actconn
);
- if (s->flags & SN_STAT_HIDEDWN)
- chunk_printf(&msg, sizeof(trash),
+ if (s->flags & SN_STAT_HIDEDWN)
+ chunk_printf(&msg, sizeof(trash),
"<li><a href=\"%s%s%s\">Show all servers</a><br>\n",
uri->uri_prefix,
"",
(s->flags & SN_STAT_NORFRSH) ? ";norefresh" : "");
- else
- chunk_printf(&msg, sizeof(trash),
+ else
+ chunk_printf(&msg, sizeof(trash),
"<li><a href=\"%s%s%s\">Hide 'DOWN' servers</a><br>\n",
uri->uri_prefix,
";up",
(s->flags & SN_STAT_NORFRSH) ? ";norefresh" : "");
- if (uri->refresh > 0) {
- if (s->flags & SN_STAT_NORFRSH)
- chunk_printf(&msg, sizeof(trash),
+ if (uri->refresh > 0) {
+ if (s->flags & SN_STAT_NORFRSH)
+ chunk_printf(&msg, sizeof(trash),
"<li><a href=\"%s%s%s\">Enable refresh</a><br>\n",
uri->uri_prefix,
(s->flags & SN_STAT_HIDEDWN) ? ";up" : "",
"");
- else
- chunk_printf(&msg, sizeof(trash),
+ else
+ chunk_printf(&msg, sizeof(trash),
"<li><a href=\"%s%s%s\">Disable refresh</a><br>\n",
uri->uri_prefix,
(s->flags & SN_STAT_HIDEDWN) ? ";up" : "",
";norefresh");
- }
+ }
- chunk_printf(&msg, sizeof(trash),
+ chunk_printf(&msg, sizeof(trash),
"<li><a href=\"%s%s%s\">Refresh now</a><br>\n",
uri->uri_prefix,
(s->flags & SN_STAT_HIDEDWN) ? ";up" : "",
(s->flags & SN_STAT_NORFRSH) ? ";norefresh" : "");
- chunk_printf(&msg, sizeof(trash),
+ chunk_printf(&msg, sizeof(trash),
"</td>"
"<td align=\"left\" valign=\"top\" nowrap width=\"1%%\">"
"<b>External ressources:</b><ul style=\"margin-top: 0.25em;\">\n"
@@ -273,8 +287,9 @@
""
);
- if (buffer_write_chunk(rep, &msg) != 0)
- return 0;
+ if (buffer_write_chunk(rep, &msg) != 0)
+ return 0;
+ }
memset(&s->data_ctx, 0, sizeof(s->data_ctx));
@@ -301,9 +316,11 @@
/* fall through */
case DATA_ST_END:
- chunk_printf(&msg, sizeof(trash), "</body></html>\n");
- if (buffer_write_chunk(rep, &msg) != 0)
- return 0;
+ if (flags & STAT_FMT_HTML) {
+ chunk_printf(&msg, sizeof(trash), "</body></html>\n");
+ if (buffer_write_chunk(rep, &msg) != 0)
+ return 0;
+ }
s->data_state = DATA_ST_FIN;
/* fall through */
@@ -366,30 +383,32 @@
/* fall through */
case DATA_ST_PX_TH:
- /* print a new table */
- chunk_printf(&msg, sizeof(trash),
- "<table cols=\"20\" class=\"tbl\" width=\"100%%\">\n"
- "<tr align=\"center\" class=\"titre\">"
- "<th colspan=2 class=\"pxname\">%s</th>"
- "<th colspan=18 class=\"empty\"></th>"
- "</tr>\n"
- "<tr align=\"center\" class=\"titre\">"
- "<th rowspan=2></th>"
- "<th colspan=2>Queue</th><th colspan=4>Sessions</th>"
- "<th colspan=2>Bytes</th><th colspan=2>Denied</th>"
- "<th colspan=3>Errors</th><th colspan=6>Server</th>"
- "</tr>\n"
- "<tr align=\"center\" class=\"titre\">"
- "<th>Cur</th><th>Max</th><th>Cur</th><th>Max</th>"
- "<th>Limit</th><th>Cumul</th><th>In</th><th>Out</th>"
- "<th>Req</th><th>Resp</th><th>Req</th><th>Conn</th>"
- "<th>Resp</th><th>Status</th><th>Weight</th><th>Act</th>"
- "<th>Bck</th><th>Check</th><th>Down</th></tr>\n"
- "",
- px->id);
-
- if (buffer_write_chunk(rep, &msg) != 0)
- return 0;
+ if (flags & STAT_FMT_HTML) {
+ /* print a new table */
+ chunk_printf(&msg, sizeof(trash),
+ "<table cols=\"20\" class=\"tbl\" width=\"100%%\">\n"
+ "<tr align=\"center\" class=\"titre\">"
+ "<th colspan=2 class=\"pxname\">%s</th>"
+ "<th colspan=18 class=\"empty\"></th>"
+ "</tr>\n"
+ "<tr align=\"center\" class=\"titre\">"
+ "<th rowspan=2></th>"
+ "<th colspan=2>Queue</th><th colspan=4>Sessions</th>"
+ "<th colspan=2>Bytes</th><th colspan=2>Denied</th>"
+ "<th colspan=3>Errors</th><th colspan=6>Server</th>"
+ "</tr>\n"
+ "<tr align=\"center\" class=\"titre\">"
+ "<th>Cur</th><th>Max</th><th>Cur</th><th>Max</th>"
+ "<th>Limit</th><th>Cumul</th><th>In</th><th>Out</th>"
+ "<th>Req</th><th>Resp</th><th>Req</th><th>Conn</th>"
+ "<th>Resp</th><th>Status</th><th>Weight</th><th>Act</th>"
+ "<th>Bck</th><th>Check</th><th>Down</th></tr>\n"
+ "",
+ px->id);
+
+ if (buffer_write_chunk(rep, &msg) != 0)
+ return 0;
+ }
s->data_ctx.stats.px_st = DATA_ST_PX_FE;
/* fall through */
@@ -397,18 +416,20 @@
case DATA_ST_PX_FE:
/* print the frontend */
if (px->cap & PR_CAP_FE) {
- chunk_printf(&msg, sizeof(trash),
+ if (flags & STAT_FMT_HTML) {
+ chunk_printf(&msg, sizeof(trash),
/* name, queue */
"<tr align=center class=\"frontend\"><td>Frontend</td><td colspan=2></td>"
- /* sessions : current, max, limit, cumul. */
- "<td align=right>%d</td><td align=right>%d</td><td align=right>%d</td><td align=right>%d</td>"
+ /* sessions : current, max, limit, cumul */
+ "<td align=right>%d</td><td align=right>%d</td>"
+ "<td align=right>%d</td><td align=right>%d</td>"
/* bytes : in, out */
"<td align=right>%lld</td><td align=right>%lld</td>"
/* denied: req, resp */
"<td align=right>%d</td><td align=right>%d</td>"
/* errors : request, connect, response */
"<td align=right>%d</td><td align=right></td><td align=right></td>"
- /* server status : reflect backend status */
+ /* server status : reflect frontend status */
"<td align=center>%s</td>"
/* rest of server: nothing */
"<td align=center colspan=5></td></tr>"
@@ -419,6 +440,31 @@
px->failed_req,
px->state == PR_STRUN ? "OPEN" :
px->state == PR_STIDLE ? "FULL" : "STOP");
+ } else {
+ chunk_printf(&msg, sizeof(trash),
+ /* pxid, name, queue cur, queue max, */
+ "%s,FRONTEND,,,"
+ /* sessions : current, max, limit, cumul */
+ "%d,%d,%d,%d,"
+ /* bytes : in, out */
+ "%lld,%lld,"
+ /* denied: req, resp */
+ "%d,%d,"
+ /* errors : request, connect, response */
+ "%d,,,"
+ /* server status : reflect frontend status */
+ "%s,"
+ /* rest of server: nothing */
+ ",,,,,"
+ "\n",
+ px->id,
+ px->feconn, px->feconn_max, px->maxconn, px->cum_feconn,
+ px->bytes_in, px->bytes_out,
+ px->denied_req, px->denied_resp,
+ px->failed_req,
+ px->state == PR_STRUN ? "OPEN" :
+ px->state == PR_STIDLE ? "FULL" : "STOP");
+ }
if (buffer_write_chunk(rep, &msg) != 0)
return 0;
@@ -431,7 +477,6 @@
case DATA_ST_PX_SV:
/* stats.sv has been initialized above */
while (s->data_ctx.stats.sv != NULL) {
- static char *srv_hlt_st[5] = { "DOWN", "DN %d/%d ↑", "UP %d/%d ↓", "UP", "<i>no check</i>" };
int sv_state; /* 0=DOWN, 1=going up, 2=going down, 3=UP, 4=unchecked */
sv = s->data_ctx.stats.sv;
@@ -456,13 +501,17 @@
continue;
}
- chunk_printf(&msg, sizeof(trash),
+ if (flags & STAT_FMT_HTML) {
+ static char *srv_hlt_st[5] = { "DOWN", "DN %d/%d ↑", "UP %d/%d ↓",
+ "UP", "<i>no check</i>" };
+ chunk_printf(&msg, sizeof(trash),
/* name */
"<tr align=\"center\" class=\"%s%d\"><td>%s</td>"
/* queue : current, max */
"<td align=right>%d</td><td align=right>%d</td>"
/* sessions : current, max, limit, cumul */
- "<td align=right>%d</td><td align=right>%d</td><td align=right>%s</td><td align=right>%d</td>"
+ "<td align=right>%d</td><td align=right>%d</td>"
+ "<td align=right>%s</td><td align=right>%d</td>"
/* bytes : in, out */
"<td align=right>%lld</td><td align=right>%lld</td>"
/* denied: req, resp */
@@ -478,14 +527,14 @@
sv->failed_secu,
sv->failed_conns, sv->failed_resp);
- /* status */
- chunk_printf(&msg, sizeof(trash), "<td nowrap>");
- chunk_printf(&msg, sizeof(trash),
+ /* status */
+ chunk_printf(&msg, sizeof(trash), "<td nowrap>");
+ chunk_printf(&msg, sizeof(trash),
srv_hlt_st[sv_state],
(sv->state & SRV_RUNNING) ? (sv->health - sv->rise + 1) : (sv->health),
(sv->state & SRV_RUNNING) ? (sv->fall) : (sv->rise));
- chunk_printf(&msg, sizeof(trash),
+ chunk_printf(&msg, sizeof(trash),
/* weight */
"</td><td>%d</td>"
/* act, bck */
@@ -495,15 +544,61 @@
(sv->state & SRV_BACKUP) ? "-" : "Y",
(sv->state & SRV_BACKUP) ? "Y" : "-");
- /* check failures : unique, fatal */
- if (sv->state & SRV_CHECKED)
- chunk_printf(&msg, sizeof(trash),
+ /* check failures : unique, fatal */
+ if (sv->state & SRV_CHECKED)
+ chunk_printf(&msg, sizeof(trash),
"<td align=right>%d</td><td align=right>%d</td></tr>\n",
sv->failed_checks, sv->down_trans);
- else
- chunk_printf(&msg, sizeof(trash),
+ else
+ chunk_printf(&msg, sizeof(trash),
"<td colspan=2></td></tr>\n");
+ } else {
+ static char *srv_hlt_st[5] = { "DOWN,", "DOWN %d/%d,", "UP %d/%d,",
+ "UP,", "no check," };
+ chunk_printf(&msg, sizeof(trash),
+ /* pxid, name */
+ "%s,%s,"
+ /* queue : current, max */
+ "%d,%d,"
+ /* sessions : current, max, limit, cumul */
+ "%d,%d,%s,%d,"
+ /* bytes : in, out */
+ "%lld,%lld,"
+ /* denied: req, resp */
+ ",%d,"
+ /* errors : request, connect, response */
+ ",%d,%d,"
+ "",
+ px->id, sv->id,
+ sv->nbpend, sv->nbpend_max,
+ sv->cur_sess, sv->cur_sess_max, sv->maxconn ? ultoa(sv->maxconn) : "-", sv->cum_sess,
+ sv->bytes_in, sv->bytes_out,
+ sv->failed_secu,
+ sv->failed_conns, sv->failed_resp);
+
+ /* status */
+ chunk_printf(&msg, sizeof(trash),
+ srv_hlt_st[sv_state],
+ (sv->state & SRV_RUNNING) ? (sv->health - sv->rise + 1) : (sv->health),
+ (sv->state & SRV_RUNNING) ? (sv->fall) : (sv->rise));
+ chunk_printf(&msg, sizeof(trash),
+ /* weight, active, backup */
+ "%d,%d,%d,"
+ "",
+ sv->uweight,
+ (sv->state & SRV_BACKUP) ? 0 : 1,
+ (sv->state & SRV_BACKUP) ? 1 : 0);
+
+ /* check failures : unique, fatal */
+ if (sv->state & SRV_CHECKED)
+ chunk_printf(&msg, sizeof(trash),
+ "%d,%d,\n",
+ sv->failed_checks, sv->down_trans);
+ else
+ chunk_printf(&msg, sizeof(trash),
+ ",,\n");
+ }
if (buffer_write_chunk(rep, &msg) != 0)
return 0;
@@ -528,7 +623,8 @@
if (px->srv && px->srv->eweight)
gcd = px->srv->uweight / px->srv->eweight;
- chunk_printf(&msg, sizeof(trash),
+ if (flags & STAT_FMT_HTML) {
+ chunk_printf(&msg, sizeof(trash),
/* name */
"<tr align=center class=\"backend\"><td>Backend</td>"
/* queue : current, max */
@@ -557,7 +653,38 @@
px->failed_conns, px->failed_resp,
(px->srv_map_sz > 0 || !px->srv) ? "UP" : "DOWN",
px->srv_map_sz * gcd, px->srv_act, px->srv_bck);
-
+ } else {
+ chunk_printf(&msg, sizeof(trash),
+ /* pxid, name */
+ "%s,BACKEND,"
+ /* queue : current, max */
+ "%d,%d,"
+ /* sessions : current, max, limit, cumul */
+ "%d,%d,%d,%d,"
+ /* bytes : in, out */
+ "%lld,%lld,"
+ /* denied: req, resp */
+ "%d,%d,"
+ /* errors : request, connect, response */
+ ",%d,%d,"
+ /* server status : reflect backend status (up/down) : we display UP
+ * if the backend has known working servers or if it has no server at
+ * all (eg: for stats). Tthen we display the total weight, number of
+ * active and backups. */
+ "%s,"
+ "%d,%d,%d,"
+ /* rest of server: nothing */
+ ",,"
+ "\n",
+ px->id,
+ px->nbpend /* or px->totpend ? */, px->nbpend_max,
+ px->beconn, px->beconn_max, px->fullconn, px->cum_beconn,
+ px->bytes_in, px->bytes_out,
+ px->denied_req, px->denied_resp,
+ px->failed_conns, px->failed_resp,
+ (px->srv_map_sz > 0 || !px->srv) ? "UP" : "DOWN",
+ px->srv_map_sz * gcd, px->srv_act, px->srv_bck);
+ }
if (buffer_write_chunk(rep, &msg) != 0)
return 0;
}
@@ -566,10 +693,12 @@
/* fall through */
case DATA_ST_PX_END:
- chunk_printf(&msg, sizeof(trash), "</table><p>\n");
+ if (flags & STAT_FMT_HTML) {
+ chunk_printf(&msg, sizeof(trash), "</table><p>\n");
- if (buffer_write_chunk(rep, &msg) != 0)
- return 0;
+ if (buffer_write_chunk(rep, &msg) != 0)
+ return 0;
+ }
s->data_ctx.stats.px_st = DATA_ST_PX_FIN;
/* fall through */
diff --git a/src/proto_http.c b/src/proto_http.c
index 82b8cea..7077df5 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -3387,7 +3387,8 @@
}
else if (s->data_source == DATA_SRC_STATS) {
/* dump server statistics */
- int ret = stats_dump_http(s, s->be->uri_auth, 0);
+ int ret = stats_dump_http(s, s->be->uri_auth,
+ (s->flags & SN_STAT_FMTCSV) ? 0 : STAT_FMT_HTML);
if (ret >= 0)
return ret;
/* -1 indicates an error */
@@ -4694,6 +4695,15 @@
}
}
+ h = t->req->data + txn->req.sl.rq.u + uri_auth->uri_len;
+ while (h <= t->req->data + txn->req.sl.rq.u + txn->req.sl.rq.u_l - 4) {
+ if (memcmp(h, ";csv", 4) == 0) {
+ t->flags |= SN_STAT_FMTCSV;
+ break;
+ }
+ h++;
+ }
+
/* we are in front of a interceptable URI. Let's check
* if there's an authentication and if it's valid.
*/