/* * Functions dedicated to statistics output * * Copyright 2000-2009 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 #include #include const char stats_sock_usage_msg[] = "Unknown command. Please enter one of the following commands only :\n" " show info : report information about the running process\n" " show stat : report counters for each proxy and server\n" " show errors : report last request and response errors for each proxy\n" " show sess : report the list of current sessions\n" "\n"; const struct chunk stats_sock_usage = { .str = (char *)&stats_sock_usage_msg, .len = sizeof(stats_sock_usage_msg)-1 }; /* 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". */ static int stats_parse_global(char **args, int section_type, struct proxy *curpx, struct proxy *defpx, char *err, int errlen) { args++; 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 if (!global.stats_fe) { if ((global.stats_fe = (struct proxy *)calloc(1, sizeof(struct proxy))) == NULL) { snprintf(err, errlen, "out of memory"); return -1; } LIST_INIT(&global.stats_fe->pendconns); LIST_INIT(&global.stats_fe->acl); LIST_INIT(&global.stats_fe->block_cond); LIST_INIT(&global.stats_fe->redirect_rules); LIST_INIT(&global.stats_fe->mon_fail_cond); LIST_INIT(&global.stats_fe->switching_rules); LIST_INIT(&global.stats_fe->tcp_req.inspect_rules); /* Timeouts are defined as -1, so we cannot use the zeroed area * as a default value. */ proxy_reset_timeouts(global.stats_fe); global.stats_fe->last_change = now.tv_sec; global.stats_fe->id = strdup("GLOBAL"); global.stats_fe->cap = PR_CAP_FE; } global.stats_sock.state = LI_INIT; global.stats_sock.options = LI_O_NONE; global.stats_sock.accept = uxst_event_accept; global.stats_sock.handler = process_session; global.stats_sock.analysers = AN_REQ_STATS_SOCK; global.stats_sock.nice = -64; /* we want to boost priority for local stats */ global.stats_sock.private = global.stats_fe; /* must point to the frontend */ global.stats_fe->timeout.client = MS_TO_TICKS(10000); /* default timeout of 10 seconds */ global.stats_sock.timeout = &global.stats_fe->timeout.client; global.stats_sock.next = global.stats_fe->listen; global.stats_fe->listen = &global.stats_sock; 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")) { unsigned timeout; const char *res = parse_time_err(args[1], &timeout, TIME_UNIT_MS); if (res) { snprintf(err, errlen, "unexpected character '%c' in 'stats timeout' in 'global' section", *res); return -1; } if (!timeout) { snprintf(err, errlen, "a positive value is expected for 'stats timeout' in 'global section'"); return -1; } global.stats_fe->timeout.client = MS_TO_TICKS(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; } int print_csv_header(struct chunk *msg, int size) { return chunk_printf(msg, size, "# 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,qlimit," "pid,iid,sid,throttle,lbtot,tracked,type," "rate,rate_lim,rate_max," "\n"); } /* Parses the request line in and possibly starts dumping stats on * s->rep with the hijack bit set. Returns 1 if OK, 0 in case of any error. * The line is modified after parsing. */ int stats_sock_parse_request(struct session *s, char *line) { char *args[MAX_STATS_ARGS + 1]; int arg; while (isspace((unsigned char)*line)) line++; arg = 0; args[arg] = line; while (*line && arg < MAX_STATS_ARGS) { if (isspace((unsigned char)*line)) { *line++ = '\0'; while (isspace((unsigned char)*line)) line++; args[++arg] = line; continue; } line++; } while (++arg <= MAX_STATS_ARGS) args[arg] = line; if (strcmp(args[0], "show") == 0) { if (strcmp(args[1], "stat") == 0) { if (*args[2] && *args[3] && *args[4]) { s->data_ctx.stats.flags |= STAT_BOUND; s->data_ctx.stats.iid = atoi(args[2]); s->data_ctx.stats.type = atoi(args[3]); s->data_ctx.stats.sid = atoi(args[4]); } s->data_ctx.stats.flags |= STAT_SHOW_STAT; s->data_ctx.stats.flags |= STAT_FMT_CSV; s->ana_state = STATS_ST_REP; buffer_install_hijacker(s, s->rep, stats_dump_raw_to_buffer); } else if (strcmp(args[1], "info") == 0) { s->data_ctx.stats.flags |= STAT_SHOW_INFO; s->data_ctx.stats.flags |= STAT_FMT_CSV; s->ana_state = STATS_ST_REP; buffer_install_hijacker(s, s->rep, stats_dump_raw_to_buffer); } else if (strcmp(args[1], "sess") == 0) { s->ana_state = STATS_ST_REP; buffer_install_hijacker(s, s->rep, stats_dump_sess_to_buffer); } else if (strcmp(args[1], "errors") == 0) { if (*args[2]) s->data_ctx.errors.iid = atoi(args[2]); else s->data_ctx.errors.iid = -1; s->data_ctx.errors.px = NULL; s->ana_state = STATS_ST_REP; buffer_install_hijacker(s, s->rep, stats_dump_errors_to_buffer); } else { /* neither "stat" nor "info" nor "sess" */ return 0; } } else { /* not "show" */ return 0; } return 1; } /* Processes the stats interpreter on the statistics socket. * In order to ease the transition, we simply simulate the server status * for now. It only knows states STATS_ST_INIT, STATS_ST_REQ, STATS_ST_REP, and * STATS_ST_CLOSE. It removes its analyser bit from req->analysers once done. * It always returns 0. */ int stats_sock_req_analyser(struct session *s, struct buffer *req, int an_bit) { char *line, *p; switch (s->ana_state) { case STATS_ST_INIT: /* Stats output not initialized yet */ memset(&s->data_ctx.stats, 0, sizeof(s->data_ctx.stats)); s->data_source = DATA_SRC_STATS; s->ana_state = STATS_ST_REQ; buffer_write_dis(s->req); /* fall through */ case STATS_ST_REQ: /* Now, stats are initialized, hijack is not set, and * we are waiting for a complete request line. */ line = s->req->data; p = memchr(line, '\n', s->req->l); if (p) { *p = '\0'; if (!stats_sock_parse_request(s, line)) { /* invalid request */ stream_int_retnclose(s->req->prod, &stats_sock_usage); s->ana_state = 0; req->analysers = 0; return 0; } } /* processing a valid or incomplete request */ if ((req->flags & BF_FULL) || /* invalid request */ (req->flags & BF_READ_ERROR) || /* input error */ (req->flags & BF_READ_TIMEOUT) || /* read timeout */ tick_is_expired(req->analyse_exp, now_ms) || /* request timeout */ (req->flags & BF_SHUTR)) { /* input closed */ buffer_shutw_now(s->rep); s->ana_state = 0; req->analysers = 0; return 0; } /* don't forward nor abort */ req->flags |= BF_READ_DONTWAIT; /* we plan to read small requests */ return 0; case STATS_ST_REP: /* do nothing while response is being processed */ return 0; case STATS_ST_CLOSE: /* end of dump */ s->req->analysers &= ~an_bit; s->ana_state = 0; break; } return 0; } /* * Produces statistics data for the session . Expects to be called with * client socket shut down on input. It *may* make use of informations from * . s->data_ctx must have been zeroed first, and the flags properly set. * 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. * It automatically clears the HIJACK bit from the response buffer. */ int stats_dump_raw(struct session *s, struct buffer *rep, struct uri_auth *uri) { 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, let's prepare the * buffer for a response. */ stream_int_retnclose(rep->cons, &msg); s->data_state = DATA_ST_HEAD; /* fall through */ case DATA_ST_HEAD: if (s->data_ctx.stats.flags & STAT_SHOW_STAT) { print_csv_header(&msg, sizeof(trash)); 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); if (s->data_ctx.stats.flags & STAT_SHOW_INFO) { chunk_printf(&msg, sizeof(trash), "Name: " PRODUCT_NAME "\n" "Version: " HAPROXY_VERSION "\n" "Release_date: " HAPROXY_DATE "\n" "Nbproc: %d\n" "Process_num: %d\n" "Pid: %d\n" "Uptime: %dd %dh%02dm%02ds\n" "Uptime_sec: %d\n" "Memmax_MB: %d\n" "Ulimit-n: %d\n" "Maxsock: %d\n" "Maxconn: %d\n" "Maxpipes: %d\n" "CurrConns: %d\n" "PipesUsed: %d\n" "PipesFree: %d\n" "Tasks: %d\n" "Run_queue: %d\n" "", global.nbproc, relative_pid, pid, up / 86400, (up % 86400) / 3600, (up % 3600) / 60, (up % 60), up, global.rlimit_memmax, global.rlimit_nofile, global.maxsock, global.maxconn, global.maxpipes, actconn, pipes_used, pipes_free, nb_tasks_cur, run_queue_cur ); if (buffer_write_chunk(rep, &msg) >= 0) return 0; } s->data_ctx.stats.px = proxy; s->data_ctx.stats.px_st = DATA_ST_PX_INIT; s->data_ctx.stats.sv = NULL; s->data_ctx.stats.sv_st = 0; s->data_state = DATA_ST_LIST; /* fall through */ case DATA_ST_LIST: /* dump proxies */ if (s->data_ctx.stats.flags & STAT_SHOW_STAT) { 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) 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; /* fall through */ case DATA_ST_FIN: buffer_stop_hijack(rep); return 1; default: /* unknown state ! */ buffer_stop_hijack(rep); return -1; } } /* This function is called to send output to the response buffer. It simply * calls stats_dump_raw(), and releases the buffer's hijack bit when the dump * is finished. */ void stats_dump_raw_to_buffer(struct session *s, struct buffer *rep) { if (s->ana_state != STATS_ST_REP) return; if (stats_dump_raw(s, rep, NULL) != 0) s->ana_state = STATS_ST_CLOSE; return; } /* * Produces statistics data for the session . Expects to be called with * client socket shut down on input. It stops by itself by unsetting the * BF_HIJACK flag from the buffer, 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.s->data_ctx must have been zeroed before the first call, and the * flags set. 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 buffer *rep, struct uri_auth *uri) { struct proxy *px; struct chunk msg; unsigned int up; msg.len = 0; msg.str = trash; switch (s->data_state) { case DATA_ST_INIT: 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", (s->data_ctx.stats.flags & STAT_FMT_CSV) ? "text/plain" : "text/html"); if (uri->refresh > 0 && !(s->data_ctx.stats.flags & STAT_NO_REFRESH)) chunk_printf(&msg, sizeof(trash), "Refresh: %d\r\n", uri->refresh); chunk_printf(&msg, sizeof(trash), "\r\n"); s->txn.status = 200; stream_int_retnclose(rep->cons, &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; buffer_stop_hijack(rep); return 1; } s->data_state = DATA_ST_HEAD; /* let's start producing data */ /* fall through */ case DATA_ST_HEAD: if (!(s->data_ctx.stats.flags & STAT_FMT_CSV)) { /* WARNING! This must fit in the first buffer !!! */ chunk_printf(&msg, sizeof(trash), "Statistics Report for " PRODUCT_NAME "%s%s\n" "\n" "\n", uri->node_name ? " on " : "", uri->node_name ? uri->node_name : "" ); } else { print_csv_header(&msg, sizeof(trash)); } 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 (!(s->data_ctx.stats.flags & STAT_FMT_CSV)) { chunk_printf(&msg, sizeof(trash), "

" PRODUCT_NAME "%s

\n" "

Statistics Report for pid %d%s%s

\n" "
\n" "

> General process information

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

pid = %d (process #%d, nbproc = %d)
\n" "uptime = %dd %dh%02dm%02ds
\n" "system limits : memmax = %s%s ; ulimit-n = %d
\n" "maxsock = %d ; maxconn = %d ; maxpipes = %d
\n" "current conns = %d ; current pipes = %d/%d
\n" "Running tasks : %d/%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" "Note: UP with load-balancing disabled is reported as \"NOLB\"." "
" "Display option:
    " "", (uri->flags&ST_HIDEVER)?"":(STATS_VERSION_STRING), pid, uri->node_name ? " on " : "", uri->node_name ? uri->node_name : "", pid, relative_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, global.maxpipes, actconn, pipes_used, pipes_used+pipes_free, run_queue_cur, nb_tasks_cur ); if (s->data_ctx.stats.flags & STAT_HIDE_DOWN) chunk_printf(&msg, sizeof(trash), "
  • Show all servers
    \n", uri->uri_prefix, "", (s->data_ctx.stats.flags & STAT_NO_REFRESH) ? ";norefresh" : ""); else chunk_printf(&msg, sizeof(trash), "
  • Hide 'DOWN' servers
    \n", uri->uri_prefix, ";up", (s->data_ctx.stats.flags & STAT_NO_REFRESH) ? ";norefresh" : ""); if (uri->refresh > 0) { if (s->data_ctx.stats.flags & STAT_NO_REFRESH) chunk_printf(&msg, sizeof(trash), "
  • Enable refresh
    \n", uri->uri_prefix, (s->data_ctx.stats.flags & STAT_HIDE_DOWN) ? ";up" : "", ""); else chunk_printf(&msg, sizeof(trash), "
  • Disable refresh
    \n", uri->uri_prefix, (s->data_ctx.stats.flags & STAT_HIDE_DOWN) ? ";up" : "", ";norefresh"); } chunk_printf(&msg, sizeof(trash), "
  • Refresh now
    \n", uri->uri_prefix, (s->data_ctx.stats.flags & STAT_HIDE_DOWN) ? ";up" : "", (s->data_ctx.stats.flags & STAT_NO_REFRESH) ? ";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; } 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) == 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 (!(s->data_ctx.stats.flags & STAT_FMT_CSV)) { 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: buffer_stop_hijack(rep); return 1; default: /* unknown state ! */ buffer_stop_hijack(rep); 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) { struct buffer *rep = s->rep; struct server *sv, *svs; /* server and server-state, server-state=server or server->tracked */ 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; } if ((s->data_ctx.stats.flags & STAT_BOUND) && (s->data_ctx.stats.iid != -1) && (px->uuid != s->data_ctx.stats.iid)) return 1; s->data_ctx.stats.px_st = DATA_ST_PX_TH; /* fall through */ case DATA_ST_PX_TH: if (!(s->data_ctx.stats.flags & STAT_FMT_CSV)) { /* 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) && (!(s->data_ctx.stats.flags & STAT_BOUND) || (s->data_ctx.stats.type & (1 << STATS_TYPE_FE)))) { if (!(s->data_ctx.stats.flags & STAT_FMT_CSV)) { chunk_printf(&msg, sizeof(trash), /* name, queue */ "" /* sessions rate : current, max, limit */ "" /* sessions : current, max, limit, total, lbtot */ "" "" /* bytes : in, out */ "" "", U2H0(read_freq_ctr(&px->fe_sess_per_sec)), U2H1(px->fe_sps_max), LIM2A2(px->fe_sps_lim, "-"), U2H3(px->feconn), U2H4(px->feconn_max), U2H5(px->maxconn), U2H6(px->cum_feconn), U2H7(px->bytes_in), U2H8(px->bytes_out)); chunk_printf(&msg, sizeof(trash), /* denied: req, resp */ "" /* errors : request, connect, response */ "" /* warnings: retries, redispatches */ "" /* server status : reflect frontend status */ "" /* rest of server: nothing */ "" "", U2H0(px->denied_req), U2H1(px->denied_resp), U2H2(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, total */ "%d,%d,%d,%lld," /* bytes : in, out */ "%lld,%lld," /* denied: req, resp */ "%lld,%lld," /* errors : request, connect, response */ "%lld,,," /* warnings: retries, redispatches */ ",," /* server status : reflect frontend status */ "%s," /* rest of server: nothing */ ",,,,,,,," /* pid, iid, sid, throttle, lbtot, tracked, type */ "%d,%d,0,,,,%d," /* rate, rate_lim, rate_max, */ "%u,%u,%u," "\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", relative_pid, px->uuid, STATS_TYPE_FE, read_freq_ctr(&px->fe_sess_per_sec), px->fe_sps_lim, px->fe_sps_max); } 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 */ for (; s->data_ctx.stats.sv != NULL; s->data_ctx.stats.sv = sv->next) { int sv_state; /* 0=DOWN, 1=going up, 2=going down, 3=UP, 4,5=NOLB, 6=unchecked */ sv = s->data_ctx.stats.sv; if (s->data_ctx.stats.flags & STAT_BOUND) { if (!(s->data_ctx.stats.type & (1 << STATS_TYPE_SV))) break; if (s->data_ctx.stats.sid != -1 && sv->puid != s->data_ctx.stats.sid) continue; } if (sv->tracked) svs = sv->tracked; else svs = sv; /* FIXME: produce some small strings for "UP/DOWN x/y &#xxxx;" */ if (!(svs->state & SRV_CHECKED)) sv_state = 6; else if (svs->state & SRV_RUNNING) { if (svs->health == svs->rise + svs->fall - 1) sv_state = 3; /* UP */ else sv_state = 2; /* going down */ if (svs->state & SRV_GOINGDOWN) sv_state += 2; } else if (svs->health) sv_state = 1; /* going up */ else sv_state = 0; /* DOWN */ if ((sv_state == 0) && (s->data_ctx.stats.flags & STAT_HIDE_DOWN)) { /* do not report servers which are DOWN */ s->data_ctx.stats.sv = sv->next; continue; } if (!(s->data_ctx.stats.flags & STAT_FMT_CSV)) { static char *srv_hlt_st[7] = { "DOWN", "DN %d/%d ↑", "UP %d/%d ↓", "UP", "NOLB %d/%d ↓", "NOLB", "no check" }; chunk_printf(&msg, sizeof(trash), /* name */ "" /* queue : current, max, limit */ "" /* sessions rate : current, max, limit */ "" /* sessions : current, max, limit, total, lbtot */ "" "" "", (sv->state & SRV_BACKUP) ? "backup" : "active", sv_state, sv->id, U2H0(sv->nbpend), U2H1(sv->nbpend_max), LIM2A2(sv->maxqueue, "-"), U2H3(read_freq_ctr(&sv->sess_per_sec)), U2H4(sv->sps_max), U2H5(sv->cur_sess), U2H6(sv->cur_sess_max), LIM2A7(sv->maxconn, "-"), U2H8(sv->cum_sess), U2H9(sv->cum_lbconn)); chunk_printf(&msg, sizeof(trash), /* bytes : in, out */ "" /* denied: req, resp */ "" /* errors : request, connect, response */ "\n" /* warnings: retries, redispatches */ "" "", U2H0(sv->bytes_in), U2H1(sv->bytes_out), U2H2(sv->failed_secu), U2H3(sv->failed_conns), U2H4(sv->failed_resp), sv->retries, sv->redispatches); /* status */ chunk_printf(&msg, sizeof(trash), "" /* act, bck */ "" "", (sv->eweight * px->lbprm.wmult + px->lbprm.wdiv - 1) / px->lbprm.wdiv, (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), "" "" "", svs->failed_checks, svs->down_trans, human_time(srv_downtime(sv), 1)); else if (sv != svs) chunk_printf(&msg, sizeof(trash), "", svs->proxy->id, svs->id); else chunk_printf(&msg, sizeof(trash), ""); /* throttle */ if ((sv->state & SRV_WARMINGUP) && now.tv_sec < sv->last_change + sv->slowstart && now.tv_sec >= sv->last_change) { unsigned int ratio; ratio = MAX(1, 100 * (now.tv_sec - sv->last_change) / sv->slowstart); chunk_printf(&msg, sizeof(trash), "\n", ratio); } else { chunk_printf(&msg, sizeof(trash), "\n"); } } else { static char *srv_hlt_st[7] = { "DOWN,", "DOWN %d/%d,", "UP %d/%d,", "UP,", "NOLB %d/%d,", "NOLB,", "no check," }; chunk_printf(&msg, sizeof(trash), /* pxid, name */ "%s,%s," /* queue : current, max */ "%d,%d," /* sessions : current, max, limit, total */ "%d,%d,%s,%lld," /* bytes : in, out */ "%lld,%lld," /* denied: req, resp */ ",%lld," /* errors : request, connect, response */ ",%lld,%lld," /* warnings: retries, redispatches */ "%lld,%lld," "", px->id, sv->id, sv->nbpend, sv->nbpend_max, sv->cur_sess, sv->cur_sess_max, LIM2A0(sv->maxconn, ""), sv->cum_sess, sv->bytes_in, sv->bytes_out, sv->failed_secu, sv->failed_conns, sv->failed_resp, sv->retries, sv->redispatches); /* 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->eweight * px->lbprm.wmult + px->lbprm.wdiv - 1) / px->lbprm.wdiv, (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), "%lld,%lld,%d,%d,", sv->failed_checks, sv->down_trans, (int)(now.tv_sec - sv->last_change), srv_downtime(sv)); else chunk_printf(&msg, sizeof(trash), ",,,,"); /* queue limit, pid, iid, sid, */ chunk_printf(&msg, sizeof(trash), "%s," "%d,%d,%d,", LIM2A0(sv->maxqueue, ""), relative_pid, px->uuid, sv->puid); /* throttle */ if ((sv->state & SRV_WARMINGUP) && now.tv_sec < sv->last_change + sv->slowstart && now.tv_sec >= sv->last_change) { unsigned int ratio; ratio = MAX(1, 100 * (now.tv_sec - sv->last_change) / sv->slowstart); chunk_printf(&msg, sizeof(trash), "%d", ratio); } /* sessions: lbtot */ chunk_printf(&msg, sizeof(trash), ",%lld,", sv->cum_lbconn); /* tracked */ if (sv->tracked) chunk_printf(&msg, sizeof(trash), "%s/%s,", sv->tracked->proxy->id, sv->tracked->id); else chunk_printf(&msg, sizeof(trash), ","); /* type */ chunk_printf(&msg, sizeof(trash), "%d,", STATS_TYPE_SV); /* rate */ chunk_printf(&msg, sizeof(trash), "%u,,%u,", read_freq_ctr(&sv->sess_per_sec), sv->sps_max); /* finish with EOL */ chunk_printf(&msg, sizeof(trash), "\n"); } if (buffer_write_chunk(rep, &msg) >= 0) return 0; } /* for 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) && (!(s->data_ctx.stats.flags & STAT_BOUND) || (s->data_ctx.stats.type & (1 << STATS_TYPE_BE)))) { if (!(s->data_ctx.stats.flags & STAT_FMT_CSV)) { chunk_printf(&msg, sizeof(trash), /* name */ "" /* queue : current, max */ "" /* sessions rate : current, max, limit */ "" "", U2H0(px->nbpend) /* or px->totpend ? */, U2H1(px->nbpend_max), U2H2(read_freq_ctr(&px->be_sess_per_sec)), U2H3(px->be_sps_max)); chunk_printf(&msg, sizeof(trash), /* sessions : current, max, limit, total, lbtot */ "" "" /* bytes : in, out */ "" "", U2H2(px->beconn), U2H3(px->beconn_max), U2H4(px->fullconn), U2H6(px->cum_beconn), U2H7(px->cum_lbconn), U2H8(px->bytes_in), U2H9(px->bytes_out)); chunk_printf(&msg, sizeof(trash), /* 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. */ "" "" "", U2H0(px->denied_req), U2H1(px->denied_resp), U2H2(px->failed_conns), U2H3(px->failed_resp), px->retries, px->redispatches, human_time(now.tv_sec - px->last_change, 1), (px->lbprm.tot_weight > 0 || !px->srv) ? "UP" : "DOWN", (px->lbprm.tot_weight * px->lbprm.wmult + px->lbprm.wdiv - 1) / px->lbprm.wdiv, px->srv_act, px->srv_bck); chunk_printf(&msg, sizeof(trash), /* rest of backend: nothing, down transitions, total downtime, throttle */ "" "" "" "", 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, total */ "%d,%d,%d,%lld," /* bytes : in, out */ "%lld,%lld," /* denied: req, resp */ "%lld,%lld," /* errors : request, connect, response */ ",%lld,%lld," /* warnings: retries, redispatches */ "%lld,%lld," /* 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 transitions, last change, total downtime */ ",%d,%d,%d,," /* pid, iid, sid, throttle, lbtot, tracked, type */ "%d,%d,0,,%lld,,%d," /* rate, rate_lim, rate_max, */ "%u,,%u," "\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->lbprm.tot_weight > 0 || !px->srv) ? "UP" : "DOWN", (px->lbprm.tot_weight * px->lbprm.wmult + px->lbprm.wdiv - 1) / px->lbprm.wdiv, px->srv_act, px->srv_bck, px->down_trans, (int)(now.tv_sec - px->last_change), px->srv?be_downtime(px):0, relative_pid, px->uuid, px->cum_lbconn, STATS_TYPE_BE, read_freq_ctr(&px->be_sess_per_sec), px->be_sps_max); } 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 (!(s->data_ctx.stats.flags & STAT_FMT_CSV)) { chunk_printf(&msg, sizeof(trash), "
%s
QueueSession rateSessionsBytesDeniedErrorsWarningsServer
CurMaxLimitCurMaxLimitCurMaxLimitTotalLbTotInOutReqRespReqConnRespRetrRedisStatusWghtActBckChkDwnDwntmeThrtle
Frontend%s%s%s%s%s%s%s%s%s%s%s%s%s
%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%lld%lld"); 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], (svs->state & SRV_RUNNING) ? (svs->health - svs->rise + 1) : (svs->health), (svs->state & SRV_RUNNING) ? (svs->fall) : (svs->rise)); chunk_printf(&msg, sizeof(trash), /* weight */ "%d%s%s%lld%lld%svia %s/%s%d %%
-
Backend%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%lld%lld%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; } } /* This function is called to send output to the response buffer. * It dumps the sessions states onto the output buffer . * Expects to be called with client socket shut down on input. * s->data_ctx must have been zeroed first, and the flags properly set. * It automatically clears the HIJACK bit from the response buffer. */ void stats_dump_sess_to_buffer(struct session *s, struct buffer *rep) { struct chunk msg; if (unlikely(rep->flags & (BF_WRITE_ERROR|BF_SHUTW))) { /* If we're forced to shut down, we might have to remove our * reference to the last session being dumped. */ if (s->data_state == DATA_ST_LIST) { if (!LIST_ISEMPTY(&s->data_ctx.sess.bref.users)) { LIST_DEL(&s->data_ctx.sess.bref.users); LIST_INIT(&s->data_ctx.sess.bref.users); } } s->data_state = DATA_ST_FIN; buffer_stop_hijack(rep); s->ana_state = STATS_ST_CLOSE; return; } if (s->ana_state != STATS_ST_REP) return; 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. We initialize the current session * pointer to the first in the global list. When a target * session is being destroyed, it is responsible for updating * this pointer. We know we have reached the end when this * pointer points back to the head of the sessions list. */ stream_int_retnclose(rep->cons, &msg); LIST_INIT(&s->data_ctx.sess.bref.users); s->data_ctx.sess.bref.ref = sessions.n; s->data_state = DATA_ST_LIST; /* fall through */ case DATA_ST_LIST: /* first, let's detach the back-ref from a possible previous session */ if (!LIST_ISEMPTY(&s->data_ctx.sess.bref.users)) { LIST_DEL(&s->data_ctx.sess.bref.users); LIST_INIT(&s->data_ctx.sess.bref.users); } /* and start from where we stopped */ while (s->data_ctx.sess.bref.ref != &sessions) { char pn[INET6_ADDRSTRLEN + strlen(":65535")]; struct session *curr_sess; curr_sess = LIST_ELEM(s->data_ctx.sess.bref.ref, struct session *, list); chunk_printf(&msg, sizeof(trash), "%p: proto=%s", curr_sess, curr_sess->listener->proto->name); switch (curr_sess->listener->proto->sock_family) { case AF_INET: inet_ntop(AF_INET, (const void *)&((struct sockaddr_in *)&curr_sess->cli_addr)->sin_addr, pn, sizeof(pn)); chunk_printf(&msg, sizeof(trash), " src=%s:%d fe=%s be=%s srv=%s", pn, ntohs(((struct sockaddr_in *)&curr_sess->cli_addr)->sin_port), curr_sess->fe->id, curr_sess->be->id, curr_sess->srv ? curr_sess->srv->id : "" ); break; case AF_INET6: inet_ntop(AF_INET6, (const void *)&((struct sockaddr_in6 *)(&curr_sess->cli_addr))->sin6_addr, pn, sizeof(pn)); chunk_printf(&msg, sizeof(trash), " src=%s:%d fe=%s be=%s srv=%s", pn, ntohs(((struct sockaddr_in6 *)&curr_sess->cli_addr)->sin6_port), curr_sess->fe->id, curr_sess->be->id, curr_sess->srv ? curr_sess->srv->id : "" ); break; case AF_UNIX: /* no more information to print right now */ break; } chunk_printf(&msg, sizeof(trash), " as=%d ts=%02x age=%s calls=%d", curr_sess->ana_state, curr_sess->task->state, human_time(now.tv_sec - curr_sess->logs.tv_accept.tv_sec, 1), curr_sess->task->calls); chunk_printf(&msg, sizeof(trash), " rq[f=%06xh,l=%d,an=%02xh,rx=%s", curr_sess->req->flags, curr_sess->req->l, curr_sess->req->analysers, curr_sess->req->rex ? human_time(TICKS_TO_MS(curr_sess->req->rex - now_ms), TICKS_TO_MS(1000)) : ""); chunk_printf(&msg, sizeof(trash), ",wx=%s", curr_sess->req->wex ? human_time(TICKS_TO_MS(curr_sess->req->wex - now_ms), TICKS_TO_MS(1000)) : ""); chunk_printf(&msg, sizeof(trash), ",ax=%s]", curr_sess->req->analyse_exp ? human_time(TICKS_TO_MS(curr_sess->req->analyse_exp - now_ms), TICKS_TO_MS(1000)) : ""); chunk_printf(&msg, sizeof(trash), " rp[f=%06xh,l=%d,an=%02xh,rx=%s", curr_sess->rep->flags, curr_sess->rep->l, curr_sess->rep->analysers, curr_sess->rep->rex ? human_time(TICKS_TO_MS(curr_sess->rep->rex - now_ms), TICKS_TO_MS(1000)) : ""); chunk_printf(&msg, sizeof(trash), ",wx=%s", curr_sess->rep->wex ? human_time(TICKS_TO_MS(curr_sess->rep->wex - now_ms), TICKS_TO_MS(1000)) : ""); chunk_printf(&msg, sizeof(trash), ",ax=%s]", curr_sess->rep->analyse_exp ? human_time(TICKS_TO_MS(curr_sess->rep->analyse_exp - now_ms), TICKS_TO_MS(1000)) : ""); chunk_printf(&msg, sizeof(trash), " s0=[%d,%1xh,fd=%d,ex=%s]", curr_sess->si[0].state, curr_sess->si[0].flags, curr_sess->si[0].fd, curr_sess->si[0].exp ? human_time(TICKS_TO_MS(curr_sess->si[0].exp - now_ms), TICKS_TO_MS(1000)) : ""); chunk_printf(&msg, sizeof(trash), " s1=[%d,%1xh,fd=%d,ex=%s]", curr_sess->si[1].state, curr_sess->si[1].flags, curr_sess->si[1].fd, curr_sess->si[1].exp ? human_time(TICKS_TO_MS(curr_sess->si[1].exp - now_ms), TICKS_TO_MS(1000)) : ""); chunk_printf(&msg, sizeof(trash), " exp=%s", curr_sess->task->expire ? human_time(TICKS_TO_MS(curr_sess->task->expire - now_ms), TICKS_TO_MS(1000)) : ""); if (task_in_rq(curr_sess->task)) chunk_printf(&msg, sizeof(trash), " run(nice=%d)", curr_sess->task->nice); chunk_printf(&msg, sizeof(trash), "\n"); if (buffer_write_chunk(rep, &msg) >= 0) { /* let's try again later from this session. We add ourselves into * this session's users so that it can remove us upon termination. */ LIST_ADDQ(&curr_sess->back_refs, &s->data_ctx.sess.bref.users); return; } s->data_ctx.sess.bref.ref = curr_sess->list.n; } s->data_state = DATA_ST_FIN; /* fall through */ default: s->data_state = DATA_ST_FIN; buffer_stop_hijack(rep); s->ana_state = STATS_ST_CLOSE; return; } } /* print a line of error buffer (limited to 70 bytes) to . The format is : * <2 spaces> <70 chars max> <\n> * which is 60 chars per line. Non-printable chars \t, \n, \r and \e are * encoded in C format. Other non-printable chars are encoded "\xHH". Original * lines are respected within the limit of 70 output chars. Lines that are * continuation of a previous truncated line begin with "+" instead of " " * after the offset. The new pointer is returned. */ static int dump_error_line(struct chunk *out, int size, struct error_snapshot *err, int *line, int ptr) { int end; unsigned char c; end = out->len + 80; if (end > size) return ptr; chunk_printf(out, size, " %05d%c ", ptr, (ptr == *line) ? ' ' : '+'); while (ptr < err->len) { c = err->buf[ptr]; if (isprint(c) && isascii(c) && c != '\\') { if (out->len > end - 2) break; out->str[out->len++] = c; } else if (c == '\t' || c == '\n' || c == '\r' || c == '\e' || c == '\\') { if (out->len > end - 3) break; out->str[out->len++] = '\\'; switch (c) { case '\t': c = 't'; break; case '\n': c = 'n'; break; case '\r': c = 'r'; break; case '\e': c = 'e'; break; case '\\': c = '\\'; break; } out->str[out->len++] = c; } else { if (out->len > end - 5) break; out->str[out->len++] = '\\'; out->str[out->len++] = 'x'; out->str[out->len++] = hextab[(c >> 4) & 0xF]; out->str[out->len++] = hextab[c & 0xF]; } if (err->buf[ptr++] == '\n') { /* we had a line break, let's return now */ out->str[out->len++] = '\n'; *line = ptr; return ptr; } } /* we have an incomplete line, we return it as-is */ out->str[out->len++] = '\n'; return ptr; } /* This function is called to send output to the response buffer. * It dumps the errors logged in proxies onto the output buffer . * Expects to be called with client socket shut down on input. * s->data_ctx must have been zeroed first, and the flags properly set. * It automatically clears the HIJACK bit from the response buffer. */ void stats_dump_errors_to_buffer(struct session *s, struct buffer *rep) { extern const char *monthname[12]; struct chunk msg; if (unlikely(rep->flags & (BF_WRITE_ERROR|BF_SHUTW))) { s->data_state = DATA_ST_FIN; buffer_stop_hijack(rep); s->ana_state = STATS_ST_CLOSE; return; } if (s->ana_state != STATS_ST_REP) return; msg.len = 0; msg.str = trash; if (!s->data_ctx.errors.px) { /* the function had not been called yet, let's prepare the * buffer for a response. */ stream_int_retnclose(rep->cons, &msg); s->data_ctx.errors.px = proxy; s->data_ctx.errors.buf = 0; s->data_ctx.errors.bol = 0; s->data_ctx.errors.ptr = -1; } /* we have two inner loops here, one for the proxy, the other one for * the buffer. */ while (s->data_ctx.errors.px) { struct error_snapshot *es; if (s->data_ctx.errors.buf == 0) es = &s->data_ctx.errors.px->invalid_req; else es = &s->data_ctx.errors.px->invalid_rep; if (!es->when.tv_sec) goto next; if (s->data_ctx.errors.iid >= 0 && s->data_ctx.errors.px->uuid != s->data_ctx.errors.iid && es->oe->uuid != s->data_ctx.errors.iid) goto next; if (s->data_ctx.errors.ptr < 0) { /* just print headers now */ char pn[INET6_ADDRSTRLEN]; struct tm tm; get_localtime(es->when.tv_sec, &tm); chunk_printf(&msg, sizeof(trash), "\n[%02d/%s/%04d:%02d:%02d:%02d.%03d]", tm.tm_mday, monthname[tm.tm_mon], tm.tm_year+1900, tm.tm_hour, tm.tm_min, tm.tm_sec, (int)(es->when.tv_usec/1000)); if (es->src.ss_family == AF_INET) inet_ntop(AF_INET, (const void *)&((struct sockaddr_in *)&es->src)->sin_addr, pn, sizeof(pn)); else inet_ntop(AF_INET6, (const void *)&((struct sockaddr_in6 *)(&es->src))->sin6_addr, pn, sizeof(pn)); switch (s->data_ctx.errors.buf) { case 0: chunk_printf(&msg, sizeof(trash), " frontend %s (#%d): invalid request\n" " src %s, session #%d, backend %s (#%d), server %s (#%d)\n" " request length %d bytes, error at position %d:\n\n", s->data_ctx.errors.px->id, s->data_ctx.errors.px->uuid, pn, es->sid, es->oe->id, es->oe->uuid, es->srv ? es->srv->id : "", es->srv ? es->srv->puid : -1, es->len, es->pos); break; case 1: chunk_printf(&msg, sizeof(trash), " backend %s (#%d) : invalid response\n" " src %s, session #%d, frontend %s (#%d), server %s (#%d)\n" " response length %d bytes, error at position %d:\n\n", s->data_ctx.errors.px->id, s->data_ctx.errors.px->uuid, pn, es->sid, es->oe->id, es->oe->uuid, es->srv ? es->srv->id : "", es->srv ? es->srv->puid : -1, es->len, es->pos); break; } if (buffer_write_chunk(rep, &msg) >= 0) { /* Socket buffer full. Let's try again later from the same point */ return; } s->data_ctx.errors.ptr = 0; s->data_ctx.errors.sid = es->sid; } if (s->data_ctx.errors.sid != es->sid) { /* the snapshot changed while we were dumping it */ chunk_printf(&msg, sizeof(trash), " WARNING! update detected on this snapshot, dump interrupted. Please re-check!\n"); if (buffer_write_chunk(rep, &msg) >= 0) return; goto next; } /* OK, ptr >= 0, so we have to dump the current line */ while (s->data_ctx.errors.ptr < es->len) { int newptr; int newline; newline = s->data_ctx.errors.bol; newptr = dump_error_line(&msg, sizeof(trash), es, &newline, s->data_ctx.errors.ptr); if (newptr == s->data_ctx.errors.ptr) return; if (buffer_write_chunk(rep, &msg) >= 0) { /* Socket buffer full. Let's try again later from the same point */ return; } s->data_ctx.errors.ptr = newptr; s->data_ctx.errors.bol = newline; }; next: s->data_ctx.errors.bol = 0; s->data_ctx.errors.ptr = -1; s->data_ctx.errors.buf++; if (s->data_ctx.errors.buf > 1) { s->data_ctx.errors.buf = 0; s->data_ctx.errors.px = s->data_ctx.errors.px->next; } } /* dump complete */ buffer_stop_hijack(rep); s->ana_state = STATS_ST_CLOSE; } static struct cfg_kw_list cfg_kws = {{ },{ { CFG_GLOBAL, "stats", stats_parse_global }, { 0, NULL, NULL }, }}; __attribute__((constructor)) static void __dumpstats_module_init(void) { cfg_register_keywords(&cfg_kws); } /* * Local variables: * c-indent-level: 8 * c-basic-offset: 8 * End: */