/* * Functions dedicated to statistics output * * Copyright 2000-2007 Willy Tarreau * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* This function parses a "stats" statement in the "global" section. It returns * -1 if there is any error, otherwise zero. If it returns -1, it may write an * error message into ther buffer, for at most bytes, trailing * zero included. The trailing '\n' must not be written. The function must be * called with pointing to the first word after "stats". */ int stats_parse_global(const char **args, char *err, int errlen) { if (!strcmp(args[0], "socket")) { struct sockaddr_un su; int cur_arg; if (*args[1] == 0) { snprintf(err, errlen, "'stats socket' in global section expects a path to a UNIX socket"); return -1; } if (global.stats_sock.state != LI_NEW) { snprintf(err, errlen, "'stats socket' already specified in global section"); return -1; } su.sun_family = AF_UNIX; strncpy(su.sun_path, args[1], sizeof(su.sun_path)); su.sun_path[sizeof(su.sun_path) - 1] = 0; memcpy(&global.stats_sock.addr, &su, sizeof(su)); // guaranteed to fit global.stats_sock.state = LI_INIT; global.stats_sock.accept = uxst_event_accept; global.stats_sock.handler = process_uxst_stats; global.stats_sock.private = NULL; cur_arg = 2; while (*args[cur_arg]) { if (!strcmp(args[cur_arg], "uid")) { global.stats_sock.perm.ux.uid = atol(args[cur_arg + 1]); cur_arg += 2; } else if (!strcmp(args[cur_arg], "gid")) { global.stats_sock.perm.ux.gid = atol(args[cur_arg + 1]); cur_arg += 2; } else if (!strcmp(args[cur_arg], "mode")) { global.stats_sock.perm.ux.mode = strtol(args[cur_arg + 1], NULL, 8); cur_arg += 2; } else if (!strcmp(args[cur_arg], "user")) { struct passwd *user; user = getpwnam(args[cur_arg + 1]); if (!user) { snprintf(err, errlen, "unknown user '%s' in 'global' section ('stats user')", args[cur_arg + 1]); return -1; } global.stats_sock.perm.ux.uid = user->pw_uid; cur_arg += 2; } else if (!strcmp(args[cur_arg], "group")) { struct group *group; group = getgrnam(args[cur_arg + 1]); if (!group) { snprintf(err, errlen, "unknown group '%s' in 'global' section ('stats group')", args[cur_arg + 1]); return -1; } global.stats_sock.perm.ux.gid = group->gr_gid; cur_arg += 2; } else { snprintf(err, errlen, "'stats socket' only supports 'user', 'uid', 'group', 'gid', and 'mode'"); return -1; } } uxst_add_listener(&global.stats_sock); global.maxsock++; } else if (!strcmp(args[0], "timeout")) { int timeout = atol(args[1]); if (timeout <= 0) { snprintf(err, errlen, "a positive value is expected for 'stats timeout' in 'global section'"); return -1; } __tv_from_ms(&global.stats_timeout, timeout); } else if (!strcmp(args[0], "maxconn")) { int maxconn = atol(args[1]); if (maxconn <= 0) { snprintf(err, errlen, "a positive value is expected for 'stats maxconn' in 'global section'"); return -1; } global.maxsock -= global.stats_sock.maxconn; global.stats_sock.maxconn = maxconn; global.maxsock += global.stats_sock.maxconn; } else { snprintf(err, errlen, "'stats' only supports 'socket', 'maxconn' and 'timeout' in 'global' section"); return -1; } return 0; } /* * Produces statistics data for the session . Expects to be called with * s->cli_state == CL_STSHUTR. It *may* make use of informations from * and . * It returns 0 if it had to stop writing data and an I/O is needed, 1 if the * dump is finished and the session must be closed, or -1 in case of any error. */ int stats_dump_raw(struct session *s, struct uri_auth *uri, int flags) { struct buffer *rep = s->rep; struct proxy *px; struct chunk msg; msg.len = 0; msg.str = trash; switch (s->data_state) { case DATA_ST_INIT: /* the function had not been called yet, let's prepare the * buffer for a response. */ client_retnclose(s, &msg); s->data_state = DATA_ST_HEAD; /* fall through */ case DATA_ST_HEAD: chunk_printf(&msg, sizeof(trash), "# pxname,svname," "qcur,qmax," "scur,smax,slim,stot," "bin,bout," "dreq,dresp," "ereq,econ,eresp," "wretr,wredis," "status,weight,act,bck," "chkfail,chkdown,lastchg,downtime," "\n"); if (buffer_write_chunk(rep, &msg) != 0) return 0; s->data_state = DATA_ST_INFO; /* fall through */ case DATA_ST_INFO: memset(&s->data_ctx, 0, sizeof(s->data_ctx)); s->data_ctx.stats.px = proxy; s->data_ctx.stats.px_st = DATA_ST_PX_INIT; s->data_state = DATA_ST_LIST; /* fall through */ case DATA_ST_LIST: /* dump proxies */ while (s->data_ctx.stats.px) { px = s->data_ctx.stats.px; /* skip the disabled proxies and non-networked ones */ if (px->state != PR_STSTOPPED && (px->cap & (PR_CAP_FE | PR_CAP_BE))) if (stats_dump_proxy(s, px, NULL, 0) == 0) return 0; s->data_ctx.stats.px = px->next; s->data_ctx.stats.px_st = DATA_ST_PX_INIT; } /* here, we just have reached the last proxy */ s->data_state = DATA_ST_END; /* fall through */ case DATA_ST_END: s->data_state = DATA_ST_FIN; return 1; case DATA_ST_FIN: return 1; default: /* unknown state ! */ return -1; } } /* * Produces statistics data for the session . Expects to be called with * s->cli_state == CL_STSHUTR. 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 0 if it had to stop writing data and an I/O is needed, 1 if the * dump is finished and the session must be closed, or -1 in case of any error. */ int stats_dump_http(struct session *s, struct uri_auth *uri, int flags) { struct buffer *rep = s->rep; struct proxy *px; struct chunk msg; unsigned int up; msg.len = 0; msg.str = trash; switch (s->data_state) { case DATA_ST_INIT: /* the function had not been called yet */ s->flags |= SN_SELF_GEN; // more data will follow chunk_printf(&msg, sizeof(trash), "HTTP/1.0 200 OK\r\n" "Cache-Control: no-cache\r\n" "Connection: close\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", uri->refresh); chunk_printf(&msg, sizeof(trash), "\r\n"); s->txn.status = 200; client_retnclose(s, &msg); // send the start of the response. msg.len = 0; 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; if (s->txn.meth == HTTP_METH_HEAD) { /* that's all we return in case of HEAD request */ s->data_state = DATA_ST_FIN; s->flags &= ~SN_SELF_GEN; return 1; } s->data_state = DATA_ST_HEAD; /* let's start producing data */ /* fall through */ case DATA_ST_HEAD: if (flags & STAT_FMT_HTML) { /* WARNING! This must fit in the first buffer !!! */ chunk_printf(&msg, sizeof(trash), "Statistics Report for " PRODUCT_NAME "\n" "\n" "\n"); } else { chunk_printf(&msg, sizeof(trash), "# pxname,svname," "qcur,qmax," "scur,smax,slim,stot," "bin,bout," "dreq,dresp," "ereq,econ,eresp," "wretr,wredis," "status,weight,act,bck," "chkfail,chkdown,lastchg,downtime," "\n"); } if (buffer_write_chunk(rep, &msg) != 0) return 0; s->data_state = DATA_ST_INFO; /* fall through */ case DATA_ST_INFO: up = (now.tv_sec - start_date.tv_sec); /* WARNING! this has to fit the first packet too. * We are around 3.5 kB, add adding entries will * become tricky if we want to support 4kB buffers ! */ if (flags & STAT_FMT_HTML) { chunk_printf(&msg, sizeof(trash), "

" PRODUCT_NAME "%s

\n" "

Statistics Report for pid %d

\n" "
\n" "

> General process information

\n" "" "" "" "
\n" "

pid = %d (nbproc = %d)
\n" "uptime = %dd %dh%02dm%02ds
\n" "system limits : memmax = %s%s ; ulimit-n = %d
\n" "maxsock = %d
\n" "maxconn = %d (current conns = %d)
\n" "

\n" "\n" "" "" "\n" "" "" "\n" "" "" "\n" "" "" "
 active UP  backup UP
active UP, going down backup UP, going down
active DOWN, going up backup DOWN, going up
active or backup DOWN  not checked
\n" "
" "Display option:
    " "", (uri->flags&ST_HIDEVER)?"":(STATS_VERSION_STRING), pid, pid, global.nbproc, up / 86400, (up % 86400) / 3600, (up % 3600) / 60, (up % 60), global.rlimit_memmax ? ultoa(global.rlimit_memmax) : "unlimited", global.rlimit_memmax ? " MB" : "", global.rlimit_nofile, global.maxsock, global.maxconn, actconn ); if (s->flags & SN_STAT_HIDEDWN) chunk_printf(&msg, sizeof(trash), "
  • Show all servers
    \n", uri->uri_prefix, "", (s->flags & SN_STAT_NORFRSH) ? ";norefresh" : ""); else chunk_printf(&msg, sizeof(trash), "
  • Hide 'DOWN' servers
    \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), "
  • Enable refresh
    \n", uri->uri_prefix, (s->flags & SN_STAT_HIDEDWN) ? ";up" : "", ""); else chunk_printf(&msg, sizeof(trash), "
  • Disable refresh
    \n", uri->uri_prefix, (s->flags & SN_STAT_HIDEDWN) ? ";up" : "", ";norefresh"); } chunk_printf(&msg, sizeof(trash), "
  • Refresh now
    \n", uri->uri_prefix, (s->flags & SN_STAT_HIDEDWN) ? ";up" : "", (s->flags & SN_STAT_NORFRSH) ? ";norefresh" : ""); chunk_printf(&msg, sizeof(trash), "
  • CSV export
    \n", uri->uri_prefix, (uri->refresh > 0) ? ";norefresh" : ""); chunk_printf(&msg, sizeof(trash), "
" "External ressources:" "
\n" "" ); if (buffer_write_chunk(rep, &msg) != 0) return 0; } memset(&s->data_ctx, 0, sizeof(s->data_ctx)); s->data_ctx.stats.px = proxy; s->data_ctx.stats.px_st = DATA_ST_PX_INIT; s->data_state = DATA_ST_LIST; /* fall through */ case DATA_ST_LIST: /* dump proxies */ while (s->data_ctx.stats.px) { px = s->data_ctx.stats.px; /* skip the disabled proxies and non-networked ones */ if (px->state != PR_STSTOPPED && (px->cap & (PR_CAP_FE | PR_CAP_BE))) if (stats_dump_proxy(s, px, uri, flags) == 0) return 0; s->data_ctx.stats.px = px->next; s->data_ctx.stats.px_st = DATA_ST_PX_INIT; } /* here, we just have reached the last proxy */ s->data_state = DATA_ST_END; /* fall through */ case DATA_ST_END: if (flags & STAT_FMT_HTML) { chunk_printf(&msg, sizeof(trash), "\n"); if (buffer_write_chunk(rep, &msg) != 0) return 0; } s->data_state = DATA_ST_FIN; /* fall through */ case DATA_ST_FIN: s->flags &= ~SN_SELF_GEN; return 1; default: /* unknown state ! */ s->flags &= ~SN_SELF_GEN; return -1; } } /* * Dumps statistics for a proxy. * Returns 0 if it had to stop dumping data because of lack of buffer space, * ot non-zero if everything completed. */ int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri, int flags) { struct buffer *rep = s->rep; struct server *sv; struct chunk msg; msg.len = 0; msg.str = trash; switch (s->data_ctx.stats.px_st) { case DATA_ST_PX_INIT: /* we are on a new proxy */ if (uri && uri->scope) { /* we have a limited scope, we have to check the proxy name */ struct stat_scope *scope; int len; len = strlen(px->id); scope = uri->scope; while (scope) { /* match exact proxy name */ if (scope->px_len == len && !memcmp(px->id, scope->px_id, len)) break; /* match '.' which means 'self' proxy */ if (!strcmp(scope->px_id, ".") && px == s->be) break; scope = scope->next; } /* proxy name not found : don't dump anything */ if (scope == NULL) return 1; } s->data_ctx.stats.px_st = DATA_ST_PX_TH; /* fall through */ case DATA_ST_PX_TH: if (flags & STAT_FMT_HTML) { /* print a new table */ chunk_printf(&msg, sizeof(trash), "\n" "" "" "" "\n" "" "" "" "" "" "" "\n" "" "" "" "" "" "" "\n" "", px->id); if (buffer_write_chunk(rep, &msg) != 0) return 0; } s->data_ctx.stats.px_st = DATA_ST_PX_FE; /* fall through */ case DATA_ST_PX_FE: /* print the frontend */ if (px->cap & PR_CAP_FE) { if (flags & STAT_FMT_HTML) { chunk_printf(&msg, sizeof(trash), /* name, queue */ "" /* sessions : current, max, limit, cumul */ "" "" /* bytes : in, out */ "" /* denied: req, resp */ "" /* errors : request, connect, response */ "" /* warnings: retries, redispatches */ "" /* server status : reflect frontend status */ "" /* rest of server: nothing */ "" "", 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"); } 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,,," /* warnings: retries, redispatches */ ",," /* 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; } s->data_ctx.stats.sv = px->srv; /* may be NULL */ s->data_ctx.stats.px_st = DATA_ST_PX_SV; /* fall through */ case DATA_ST_PX_SV: /* stats.sv has been initialized above */ while (s->data_ctx.stats.sv != NULL) { int sv_state; /* 0=DOWN, 1=going up, 2=going down, 3=UP, 4=unchecked */ sv = s->data_ctx.stats.sv; /* FIXME: produce some small strings for "UP/DOWN x/y &#xxxx;" */ if (!(sv->state & SRV_CHECKED)) sv_state = 4; else if (sv->state & SRV_RUNNING) if (sv->health == sv->rise + sv->fall - 1) sv_state = 3; /* UP */ else sv_state = 2; /* going down */ else if (sv->health) sv_state = 1; /* going up */ else sv_state = 0; /* DOWN */ if ((sv_state == 0) && (s->flags & SN_STAT_HIDEDWN)) { /* do not report servers which are DOWN */ s->data_ctx.stats.sv = sv->next; continue; } if (flags & STAT_FMT_HTML) { static char *srv_hlt_st[5] = { "DOWN", "DN %d/%d ↑", "UP %d/%d ↓", "UP", "no check" }; chunk_printf(&msg, sizeof(trash), /* name */ "" /* queue : current, max */ "" /* sessions : current, max, limit, cumul */ "" "" /* bytes : in, out */ "" /* denied: req, resp */ "" /* errors : request, connect, response */ "\n" /* warnings: retries, redispatches */ "" "", (sv->state & SRV_BACKUP) ? "backup" : "active", sv_state, 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, sv->retries); /* status */ chunk_printf(&msg, sizeof(trash), "" /* act, bck */ "" "", sv->uweight, (sv->state & SRV_BACKUP) ? "-" : "Y", (sv->state & SRV_BACKUP) ? "Y" : "-"); /* check failures: unique, fatal, down time */ if (sv->state & SRV_CHECKED) chunk_printf(&msg, sizeof(trash), "" "" "\n", sv->failed_checks, sv->down_trans, human_time(srv_downtime(sv), 1)); else chunk_printf(&msg, sizeof(trash), "\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," /* warnings: retries, redispatches */ "%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, sv->retries); /* 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; last change, total downtime */ if (sv->state & SRV_CHECKED) chunk_printf(&msg, sizeof(trash), "%d,%d,%d,%d,\n", sv->failed_checks, sv->down_trans, now.tv_sec - sv->last_change, srv_downtime(sv)); else chunk_printf(&msg, sizeof(trash), ",,,,\n"); } if (buffer_write_chunk(rep, &msg) != 0) return 0; s->data_ctx.stats.sv = sv->next; } /* while sv */ s->data_ctx.stats.px_st = DATA_ST_PX_BE; /* fall through */ case DATA_ST_PX_BE: /* print the backend */ if (px->cap & PR_CAP_BE) { int gcd = 1; if (px->map_state & PR_MAP_RECALC) recalc_server_map(px); /* The GCD which was computed causes the total effective * weight to appear lower than all weights. Let's * recompute it. */ if (px->srv && px->srv->eweight) gcd = px->srv->uweight / px->srv->eweight; if (flags & STAT_FMT_HTML) { chunk_printf(&msg, sizeof(trash), /* name */ "" /* queue : current, max */ "" /* sessions : current, max, limit, cumul. */ "" /* bytes : in, out */ "" /* denied: req, resp */ "" /* errors : request, connect, response */ "\n" /* warnings: retries, redispatches */ "" /* backend 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). Then we display the total weight, number of * active and backups. */ "" "", 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->retries, px->redispatches, human_time(now.tv_sec - px->last_change, 1), (px->srv_map_sz > 0 || !px->srv) ? "UP" : "DOWN", px->srv_map_sz * gcd, px->srv_act, px->srv_bck); chunk_printf(&msg, sizeof(trash), /* rest of backend: nothing, down transformations, total downtime */ "" "" "", px->down_trans, px->srv?human_time(be_downtime(px), 1):" "); } 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," /* warnings: retries, redispatches */ "%d,%d," /* backend 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). Then we display the total weight, number of * active and backups. */ "%s," "%d,%d,%d," /* rest of backend: nothing, down transformations, * last change, total downtime. */ ",%d,%d,%d," "\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->retries, px->redispatches, (px->srv_map_sz > 0 || !px->srv) ? "UP" : "DOWN", px->srv_map_sz * gcd, px->srv_act, px->srv_bck, px->down_trans, now.tv_sec - px->last_change, px->srv?be_downtime(px):0); } if (buffer_write_chunk(rep, &msg) != 0) return 0; } s->data_ctx.stats.px_st = DATA_ST_PX_END; /* fall through */ case DATA_ST_PX_END: if (flags & STAT_FMT_HTML) { chunk_printf(&msg, sizeof(trash), "
%s
QueueSessionsBytesDeniedErrorsWarningsServer
CurMaxCurMaxLimitCumulInOutReqRespReqConnRespRetrRedisStatusWghtActBckChkDwnDwntme
Frontend%d%d%d%d%lld%lld%d%d%d%s
%s%d%d%d%d%s%d%lld%lld%d%d%d%d"); if (sv->state & SRV_CHECKED) chunk_printf(&msg, sizeof(trash), "%s ", human_time(now.tv_sec - sv->last_change, 1)); 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 */ "%d%s%s%d%d%s
Backend%d%d%d%d%d%d%lld%lld%d%d%d%d%d%d%s %s%d%d%d %d%s

\n"); if (buffer_write_chunk(rep, &msg) != 0) return 0; } s->data_ctx.stats.px_st = DATA_ST_PX_FIN; /* fall through */ case DATA_ST_PX_FIN: return 1; default: /* unknown state, we should put an abort() here ! */ return 1; } } /* * Local variables: * c-indent-level: 8 * c-basic-offset: 8 * End: */