[MEDIUM] first working code for an HTML status report.
diff --git a/haproxy.c b/haproxy.c
index adeaefe..33ecaed 100644
--- a/haproxy.c
+++ b/haproxy.c
@@ -171,6 +171,12 @@
#define DEFAULT_MAXCONN SYSTEM_MAXCONN
#endif
+#ifdef CONFIG_PRODUCT_NAME
+#define PRODUCT_NAME CONFIG_PRODUCT_NAME
+#else
+#define PRODUCT_NAME "HAProxy"
+#endif
+
/* how many bits are needed to code the size of an int (eg: 32bits -> 5) */
#define INTBITS 5
@@ -410,7 +416,11 @@
#define SN_MONITOR 0x00400000 /* this session comes from a monitoring system */
#define SN_ASSIGNED 0x00800000 /* no need to assign a server to this session */
#define SN_ADDR_SET 0x01000000 /* this session's server address has been set */
+#define SN_SELF_GEN 0x02000000 /* the proxy generates data for the client (eg: stats) */
+/* various data sources for the responses */
+#define DATA_SRC_NONE 0
+#define DATA_SRC_STATS 1
/* different possible states for the client side */
#define CL_STHEADERS 0
@@ -610,6 +620,13 @@
int status; /* HTTP status from the server, negative if from proxy */
long long bytes; /* number of bytes transferred from the server */
} logs;
+ int data_source; /* where to get the data we generate ourselves */
+ union {
+ struct {
+ struct proxy *px;
+ struct server *sv;
+ } stats;
+ } data_ctx;
unsigned int uniq_id; /* unique ID used for the traces */
};
@@ -765,6 +782,7 @@
static int listeners = 0; /* # of listeners */
static int stopping = 0; /* non zero means stopping in progress */
static struct timeval now = {0,0}; /* the current date at any moment */
+static struct timeval start_date; /* the process's start date */
static struct proxy defproxy; /* fake proxy used to assign default values on all instances */
/* Here we store informations about the pids of the processes we may pause
@@ -2890,7 +2908,70 @@
}
+/* returns 1 if the buffer is empty, 0 otherwise */
+static inline int buffer_isempty(struct buffer *buf) {
+ return buf->l == 0;
+}
+
+
+/* returns 1 if the buffer is full, 0 otherwise */
+static inline int buffer_isfull(struct buffer *buf) {
+ return buf->l == BUFSIZE;
+}
+
+
+/* flushes any content from buffer <buf> */
+void buffer_flush(struct buffer *buf) {
+ buf->r = buf->h = buf->lr = buf->w = buf->data;
+ buf->l = 0;
+}
+
+
+/* returns the maximum number of bytes writable at once in this buffer */
+int buffer_max(struct buffer *buf) {
+ if (buf->l == 0)
+ return BUFSIZE;
+ else if (buf->r > buf->w)
+ return buf->data + BUFSIZE - buf->r;
+ else
+ return buf->w - buf->r;
+}
+
+
/*
+ * Tries to realign the given buffer, and returns how many bytes can be written
+ * there at once without overwriting anything.
+ */
+int buffer_realign(struct buffer *buf) {
+ if (buf->l == 0) {
+ /* let's realign the buffer to optimize I/O */
+ buf->r = buf->w = buf->h = buf->lr = buf->data;
+ }
+ return buffer_max(buf);
+}
+
+
+/* writes <len> bytes from message <msg> to buffer <buf>. Returns 0 in case of
+ * success, or the number of bytes available otherwise.
+ */
+int buffer_write(struct buffer *buf, const char *msg, int len) {
+ int max;
+
+ max = buffer_max(buf);
+
+ if (len > max)
+ return max;
+
+ memcpy(buf->r, msg, len);
+ buf->l += len;
+ buf->r += len;
+ if (buf->r == buf->data + BUFSIZE)
+ buf->r = buf->data + BUFSIZE;
+ return 0;
+}
+
+
+/*
* returns a message to the client ; the connection is shut down for read,
* and the request is cleared so that no server connection can be initiated.
* The client must be in a valid state for this (HEADER, DATA ...).
@@ -2904,10 +2985,8 @@
tv_delayfrom(&s->cwexpire, &now, s->proxy->clitimeout);
shutdown(s->cli_fd, SHUT_RD);
s->cli_state = CL_STSHUTR;
- strcpy(s->rep->data, msg);
- s->rep->l = len;
- s->rep->r = s->rep->h = s->rep->lr = s->rep->w = s->rep->data;
- s->rep->r += len;
+ buffer_flush(s->rep);
+ buffer_write(s->rep, msg, len);
s->req->l = 0;
}
@@ -2917,13 +2996,200 @@
* The reply buffer doesn't need to be empty before this.
*/
void client_return(struct session *s, int len, const char *msg) {
- strcpy(s->rep->data, msg);
- s->rep->l = len;
- s->rep->r = s->rep->h = s->rep->lr = s->rep->w = s->rep->data;
- s->rep->r += len;
+ buffer_flush(s->rep);
+ buffer_write(s->rep, msg, len);
s->req->l = 0;
}
+/*
+ * Produces data for the session <s> depending on its source. Expects to be
+ * called with s->cli_state == CL_STSHUTR. Right now, only statistics can be
+ * produced. It stops by itself by unsetting the SN_SELF_GEN flag from the
+ * session, which it uses to keep on being called when there is free space in
+ * the buffer, of simply by letting an empty buffer upon return. It returns 1
+ * if it changes the session state from CL_STSHUTR, otherwise 0.
+ */
+int produce_content(struct session *s) {
+ struct buffer *rep = s->rep;
+ struct proxy *px;
+ struct server *sv;
+ int max, msglen;
+ unsigned int up;
+
+ if (s->data_source == DATA_SRC_NONE) {
+ s->flags &= ~SN_SELF_GEN;
+ return 1;
+ }
+ else if (s->data_source == DATA_SRC_STATS) {
+ if (s->data_ctx.stats.px == NULL) {
+ /* the proxy was not known, the function had not been called yet */
+
+ s->flags |= SN_SELF_GEN; // more data will follow
+ msglen = sprintf(trash,
+ "HTTP/1.0 200 OK\r\n"
+ "Cache-Control: no-cache\r\n"
+ "Connection: close\r\n"
+ "\r\n\r\n");
+
+ s->logs.status = 200;
+ client_retnclose(s, msglen, trash); // send the start of the response.
+ if (!(s->flags & SN_ERR_MASK)) // this is not really an error but it is
+ s->flags |= SN_ERR_PRXCOND; // to mark that it comes from the proxy
+ if (!(s->flags & SN_FINST_MASK))
+ s->flags |= SN_FINST_R;
+
+ /* WARNING! This must fit in the first buffer !!! */
+ msglen = snprintf(trash, 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"
+ "body {"
+ " font-family: helvetica, arial;"
+ " font-size: 12px;"
+ " font-weight: normal;"
+ " color: black;"
+ " background: white;"
+ "}\n"
+ "td {"
+ " font-size: 12px;"
+ "}\n"
+ "h1 {"
+ " font-size: xx-large;"
+ " margin-bottom: 0.5em;"
+ "}\n"
+ "h2 {"
+ " font-family: helvetica, arial;"
+ " font-size: x-large;"
+ " font-weight: bold;"
+ " font-style: italic;"
+ " color: #6020a0;"
+ " margin-top: 0em;"
+ " margin-bottom: 0em;"
+ "}\n"
+ "h3 {"
+ " font-family: helvetica, arial;"
+ " font-size: 16px;"
+ " font-weight: bold;"
+ " color: #b00040;"
+ " background: #e8e8d0;"
+ " margin-top: 0em;"
+ " margin-bottom: 0em;"
+ "}\n"
+ "li {"
+ " margin-top: 0.25em;"
+ " margin-right: 2em;"
+ "}\n"
+ ".hr {"
+ " margin-top: 0.25em;"
+ " border-color: black;"
+ " border-bottom-style: solid;"
+ "}"
+ "-->"
+ "</style></head>");
+
+ buffer_write(rep, trash, msglen);
+
+ up = (now.tv_sec - start_date.tv_sec);
+
+ /* WARNING! this has to fit the first packet too */
+ msglen = snprintf(trash, sizeof(trash),
+ "<body><h1>" PRODUCT_NAME "</h1>\n"
+ "<h2>Statistics Report for pid %d</h2>\n"
+ "<hr width=\"100%%\" class=\"hr\">\n"
+ "<h3>> General process informations</h3>\n"
+ "<p><b>pid = </b> %d (nbproc = %d)<br>\n"
+ "<b>uptime = </b> %dd %dh%02dm%02ds<br>\n"
+ "<b>system limits :</b> memmax = %d Megs ; ulimit-n = %d<br>\n"
+ "<b>maxsock = </b> %d<br>\n"
+ "<b>maxconn = </b> %d (current conns = %d)<br>\n"
+ "",
+ pid, pid, global.nbproc,
+ up / 86400, (up % 86400) / 3600,
+ (up % 3600) / 60, (up % 60),
+ global.rlimit_memmax,
+ global.rlimit_nofile,
+ global.maxsock,
+ global.maxconn,
+ actconn
+ );
+
+ buffer_write(rep, trash, msglen);
+ px = s->data_ctx.stats.px = proxy;
+ }
+
+ while (s->data_ctx.stats.px) {
+ /* if sv == NULL, this is because we are on a new proxy */
+ while (s->data_ctx.stats.sv == NULL) {
+ px = s->data_ctx.stats.px;
+ msglen = snprintf(trash, sizeof(trash),
+ "<h3>> Proxy instance %s : "
+ "%d conns (maxconn=%d), %d queued (%d unassigned), %d total conns</h3>\n"
+ "",
+ px->id,
+ px->nbconn, px->maxconn, px->totpend, px->nbpend, px->cum_conn);
+
+ msglen += snprintf(trash + msglen, sizeof(trash) - msglen,
+ "<table cols=8 border=1 cellpadding=1 cellspacing=0><tr>"
+ "<th>Server</th><th>Health</th><th>Sessions</th><th>Queue</th>"
+ "<th>Total Sess</th><th>Weight</th><th>Active</th><th>Backup</th></tr>\n");
+
+ if (buffer_write(rep, trash, msglen) != 0)
+ return 0;
+ s->data_ctx.stats.sv = px->srv;
+ }
+
+ while (s->data_ctx.stats.sv != NULL) {
+ px = s->data_ctx.stats.px;
+ sv = s->data_ctx.stats.sv;
+
+ msglen = snprintf(trash, sizeof(trash),
+ "<tr><td>%s</td><td>%s %d/%d</td><td>%d</td><td>%d</th>"
+ "<td>%d</td><td>%d</td><td>%s</td><td>%s</td></tr>\n",
+ sv->id,
+ (sv->state & SRV_RUNNING) ? "UP" : "DOWN",
+ (sv->state & SRV_RUNNING) ? (sv->fall - sv->health + sv->rise) : (sv->fall - sv->health),
+ (sv->state & SRV_RUNNING) ? (sv->fall) : (sv->rise),
+ sv->cur_sess, sv->nbpend, sv->cum_sess, sv->uweight+1,
+ (sv->state & SRV_BACKUP) ? "-" : "Y",
+ (sv->state & SRV_BACKUP) ? "Y" : "-");
+
+ sv = sv->next;
+ if (!sv) {
+ /* write a summary for the proxy */
+ msglen += snprintf(trash + msglen, sizeof(trash) - msglen,
+ "<tr><td>dispatcher</td><td>-</td><td>%d</td><td>%d</th>"
+ "<td>%d</td><td>-</td><td>%d</td><td>%d</td></tr></table><p>\n",
+ px->nbconn, px->nbpend, px->cum_conn,
+ px->srv_act, px->srv_bck);
+
+ px = px->next;
+ /* the server loop will automaticall detect the NULL */
+ }
+
+ if (buffer_write(rep, trash, msglen) != 0)
+ return 0;
+ s->data_ctx.stats.sv = sv;
+ s->data_ctx.stats.px = px;
+ }
+ }
+ /* here, we just have reached the sv == NULL and px == NULL */
+ s->flags &= ~SN_SELF_GEN;
+ return 1;
+ }
+ else {
+ /* unknown data source */
+ s->logs.status = 500;
+ client_retnclose(s, s->proxy->errmsg.len500, s->proxy->errmsg.msg500);
+ if (!(s->flags & SN_ERR_MASK))
+ s->flags |= SN_ERR_PRXCOND;
+ if (!(s->flags & SN_FINST_MASK))
+ s->flags |= SN_FINST_R;
+ s->flags &= SN_SELF_GEN;
+ return 1;
+ }
+}
+
+
/*
* send a log for the session when we have enough info about it
*/
@@ -3184,6 +3450,9 @@
s->logs.prx_queue_size = 0; /* we get the number of pending conns before us */
s->logs.srv_queue_size = 0; /* we will get this number soon */
+ s->data_source = DATA_SRC_NONE;
+ memset(&s->data_ctx, 0, sizeof(s->data_ctx));
+
s->uniq_id = totalconn;
p->cum_conn++;
@@ -3623,8 +3892,6 @@
return err;
}
-
-
/*
* manages the client FSM and its socket. BTW, it also tries to handle the
* cookie. It returns 1 if a state has changed (and a resync may be needed),
@@ -3710,11 +3977,6 @@
* whatever we want.
*/
- /* FIXME debugging code !!! */
- if (t->req_line.len >= 0) {
- write(2, t->req_line.str, t->req_line.len);
- }
-
if (t->proxy->uri_auth != NULL
&& t->req_line.len >= t->proxy->uri_auth->uri_len + 4) { /* +4 for "GET /" */
if (!memcmp(t->req_line.str + 4,
@@ -3765,7 +4027,10 @@
return 1;
}
- /* Hey, we have passed the authentication ! */
+ t->cli_state = CL_STSHUTR;
+ t->data_source = DATA_SRC_STATS;
+ produce_content(t);
+ return 1;
}
}
@@ -4554,7 +4819,8 @@
}
return 1;
}
- else if ((s == SV_STSHUTR || s == SV_STCLOSE) && (rep->l == 0)) {
+ else if ((s == SV_STSHUTR || s == SV_STCLOSE) && (rep->l == 0)
+ && !(t->flags & SN_SELF_GEN)) {
tv_eternity(&t->cwexpire);
fd_delete(t->cli_fd);
t->cli_state = CL_STCLOSE;
@@ -4576,8 +4842,19 @@
}
return 1;
}
- else if ((rep->l == 0) ||
- ((s == SV_STHEADERS) /* FIXME: this may be optimized && (rep->w == rep->h)*/)) {
+
+ if (t->flags & SN_SELF_GEN) {
+ produce_content(t);
+ if (rep->l == 0) {
+ tv_eternity(&t->cwexpire);
+ fd_delete(t->cli_fd);
+ t->cli_state = CL_STCLOSE;
+ return 1;
+ }
+ }
+
+ if ((rep->l == 0)
+ || ((s == SV_STHEADERS) /* FIXME: this may be optimized && (rep->w == rep->h)*/)) {
if (FD_ISSET(t->cli_fd, StaticWriteEvent)) {
FD_CLR(t->cli_fd, StaticWriteEvent); /* stop writing */
tv_eternity(&t->cwexpire);
@@ -8936,6 +9213,7 @@
*/
tv_now(&now);
localtime(&now.tv_sec);
+ start_date = now;
/* initialize the log header encoding map : '{|}"#' should be encoded with
* '#' as prefix, as well as non-printable characters ( <32 or >= 127 ).