From e03312613013001f081e40827356e0b408683c30 Mon Sep 17 00:00:00 2001 From: willy tarreau Date: Mon, 15 May 2006 03:02:46 +0200 Subject: [PATCH] [MEDIUM] first working code for an HTML status report. --- haproxy.c | 316 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 297 insertions(+), 19 deletions(-) diff --git a/haproxy.c b/haproxy.c index adeaefee32..33ecaedef9 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 @@ int strlcpy2(char *dst, const char *src, int size) { #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 @@ struct session { 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 maxfd = 0; /* # of the highest fd + 1 */ 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,6 +2908,69 @@ int event_srv_write(int fd) { } +/* 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 */ +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 bytes from message to buffer . 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. @@ -2904,10 +2985,8 @@ void client_retnclose(struct session *s, int len, const char *msg) { 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 @@ void client_retnclose(struct session *s, int len, const char *msg) { * 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 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), + "Statistics Report for " PRODUCT_NAME "\n" + "\n" + ""); + + 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), + "

" PRODUCT_NAME "

\n" + "

Statistics Report for pid %d

\n" + "
\n" + "

> General process informations

\n" + "

pid = %d (nbproc = %d)
\n" + "uptime = %dd %dh%02dm%02ds
\n" + "system limits : memmax = %d Megs ; ulimit-n = %d
\n" + "maxsock = %d
\n" + "maxconn = %d (current conns = %d)
\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), + "

> Proxy instance %s : " + "%d conns (maxconn=%d), %d queued (%d unassigned), %d total conns

\n" + "", + px->id, + px->nbconn, px->maxconn, px->totpend, px->nbpend, px->cum_conn); + + msglen += snprintf(trash + msglen, sizeof(trash) - msglen, + "" + "" + "\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), + "\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, + "
ServerHealthSessionsQueueTotal SessWeightActiveBackup
%s%s %d/%d%d%d" + "%d%d%s%s
dispatcher-%d%d" + "%d-%d%d

\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 @@ int event_accept(int fd) { 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 @@ char *check_replace_string(char *str) 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 @@ int process_cli(struct session *t) { * 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 @@ int process_cli(struct session *t) { 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 @@ int process_cli(struct session *t) { } 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 @@ int process_cli(struct session *t) { } 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 @@ void init(int argc, char **argv) { */ 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 ). -- 2.39.5