| /* |
| * Functions dedicated to statistics output and the stats socket |
| * |
| * Copyright 2000-2012 Willy Tarreau <w@1wt.eu> |
| * Copyright 2007-2009 Krzysztof Piotr Oledzki <ole@ans.pl> |
| * |
| * 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 <ctype.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <pwd.h> |
| #include <grp.h> |
| |
| #include <sys/socket.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| |
| #include <common/cfgparse.h> |
| #include <common/compat.h> |
| #include <common/config.h> |
| #include <common/debug.h> |
| #include <common/memory.h> |
| #include <common/mini-clist.h> |
| #include <common/standard.h> |
| #include <common/ticks.h> |
| #include <common/time.h> |
| #include <common/uri_auth.h> |
| #include <common/version.h> |
| |
| #include <types/global.h> |
| |
| #include <proto/backend.h> |
| #include <proto/channel.h> |
| #include <proto/checks.h> |
| #include <proto/compression.h> |
| #include <proto/dumpstats.h> |
| #include <proto/fd.h> |
| #include <proto/freq_ctr.h> |
| #include <proto/log.h> |
| #include <proto/pattern.h> |
| #include <proto/pipe.h> |
| #include <proto/listener.h> |
| #include <proto/map.h> |
| #include <proto/proto_http.h> |
| #include <proto/proto_uxst.h> |
| #include <proto/proxy.h> |
| #include <proto/sample.h> |
| #include <proto/session.h> |
| #include <proto/server.h> |
| #include <proto/raw_sock.h> |
| #include <proto/stream_interface.h> |
| #include <proto/task.h> |
| |
| #ifdef USE_OPENSSL |
| #include <proto/ssl_sock.h> |
| #endif |
| |
| /* stats socket states */ |
| enum { |
| STAT_CLI_INIT = 0, /* initial state, must leave to zero ! */ |
| STAT_CLI_END, /* final state, let's close */ |
| STAT_CLI_GETREQ, /* wait for a request */ |
| STAT_CLI_OUTPUT, /* all states after this one are responses */ |
| STAT_CLI_PROMPT, /* display the prompt (first output, same code) */ |
| STAT_CLI_PRINT, /* display message in cli->msg */ |
| STAT_CLI_PRINT_FREE, /* display message in cli->msg. After the display, free the pointer */ |
| STAT_CLI_O_INFO, /* dump info */ |
| STAT_CLI_O_SESS, /* dump sessions */ |
| STAT_CLI_O_ERR, /* dump errors */ |
| STAT_CLI_O_TAB, /* dump tables */ |
| STAT_CLI_O_CLR, /* clear tables */ |
| STAT_CLI_O_SET, /* set entries in tables */ |
| STAT_CLI_O_STAT, /* dump stats */ |
| STAT_CLI_O_PATS, /* list all pattern reference avalaible */ |
| STAT_CLI_O_PAT, /* list all entries of a pattern */ |
| STAT_CLI_O_MLOOK, /* lookup a map entry */ |
| STAT_CLI_O_POOLS, /* dump memory pools */ |
| }; |
| |
| /* Actions available for the stats admin forms */ |
| enum { |
| ST_ADM_ACTION_NONE = 0, |
| ST_ADM_ACTION_READY, |
| ST_ADM_ACTION_DRAIN, |
| ST_ADM_ACTION_MAINT, |
| ST_ADM_ACTION_SHUTDOWN, |
| /* these are the ancient actions, still available for compatibility */ |
| ST_ADM_ACTION_DISABLE, |
| ST_ADM_ACTION_ENABLE, |
| ST_ADM_ACTION_STOP, |
| ST_ADM_ACTION_START, |
| }; |
| |
| static int stats_dump_info_to_buffer(struct stream_interface *si); |
| static int stats_dump_pools_to_buffer(struct stream_interface *si); |
| static int stats_dump_full_sess_to_buffer(struct stream_interface *si, struct session *sess); |
| static int stats_dump_sess_to_buffer(struct stream_interface *si); |
| static int stats_dump_errors_to_buffer(struct stream_interface *si); |
| static int stats_table_request(struct stream_interface *si, int show); |
| static int stats_dump_proxy_to_buffer(struct stream_interface *si, struct proxy *px, struct uri_auth *uri); |
| static int stats_dump_stat_to_buffer(struct stream_interface *si, struct uri_auth *uri); |
| static int stats_pats_list(struct stream_interface *si); |
| static int stats_pat_list(struct stream_interface *si); |
| static int stats_map_lookup(struct stream_interface *si); |
| |
| /* |
| * cli_io_handler() |
| * -> stats_dump_sess_to_buffer() // "show sess" |
| * -> stats_dump_errors_to_buffer() // "show errors" |
| * -> stats_dump_info_to_buffer() // "show info" |
| * -> stats_dump_stat_to_buffer() // "show stat" |
| * -> stats_dump_csv_header() |
| * -> stats_dump_proxy_to_buffer() |
| * -> stats_dump_fe_stats() |
| * -> stats_dump_li_stats() |
| * -> stats_dump_sv_stats() |
| * -> stats_dump_be_stats() |
| * |
| * http_stats_io_handler() |
| * -> stats_dump_stat_to_buffer() // same as above, but used for CSV or HTML |
| * -> stats_dump_csv_header() // emits the CSV headers (same as above) |
| * -> stats_dump_html_head() // emits the HTML headers |
| * -> stats_dump_html_info() // emits the equivalent of "show info" at the top |
| * -> stats_dump_proxy_to_buffer() // same as above, valid for CSV and HTML |
| * -> stats_dump_html_px_hdr() |
| * -> stats_dump_fe_stats() |
| * -> stats_dump_li_stats() |
| * -> stats_dump_sv_stats() |
| * -> stats_dump_be_stats() |
| * -> stats_dump_html_px_end() |
| * -> stats_dump_html_end() // emits HTML trailer |
| */ |
| |
| static struct si_applet cli_applet; |
| |
| static const char stats_sock_usage_msg[] = |
| "Unknown command. Please enter one of the following commands only :\n" |
| " clear counters : clear max statistics counters (add 'all' for all counters)\n" |
| " clear table : remove an entry from a table\n" |
| " help : this message\n" |
| " prompt : toggle interactive mode with prompt\n" |
| " quit : disconnect\n" |
| " show info : report information about the running process\n" |
| " show pools : report information about the memory pools usage\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 [id] : report the list of current sessions or dump this session\n" |
| " show table [id]: report table usage stats or dump this table's contents\n" |
| " get weight : report a server's current weight\n" |
| " set weight : change a server's weight\n" |
| " set server : change a server's state or weight\n" |
| " set table [id] : update or create a table entry's data\n" |
| " set timeout : change a timeout setting\n" |
| " set maxconn : change a maxconn setting\n" |
| " set rate-limit : change a rate limiting value\n" |
| " disable : put a server or frontend in maintenance mode\n" |
| " enable : re-enable a server or frontend which is in maintenance mode\n" |
| " shutdown : kill a session or a frontend (eg:to release listening ports)\n" |
| " show acl [id] : report avalaible acls or dump an acl's contents\n" |
| " get acl : reports the patterns matching a sample for an ACL\n" |
| " add acl : add acl entry\n" |
| " del acl : delete acl entry\n" |
| " clear acl <id> : clear the content of this acl\n" |
| " show map [id] : report avalaible maps or dump a map's contents\n" |
| " get map : reports the keys and values matching a sample for a map\n" |
| " set map : modify map entry\n" |
| " add map : add map entry\n" |
| " del map : delete map entry\n" |
| " clear map <id> : clear the content of this map\n" |
| ""; |
| |
| static const char stats_permission_denied_msg[] = |
| "Permission denied\n" |
| ""; |
| |
| /* data transmission states for the stats responses */ |
| enum { |
| STAT_ST_INIT = 0, |
| STAT_ST_HEAD, |
| STAT_ST_INFO, |
| STAT_ST_LIST, |
| STAT_ST_END, |
| STAT_ST_FIN, |
| }; |
| |
| /* data transmission states for the stats responses inside a proxy */ |
| enum { |
| STAT_PX_ST_INIT = 0, |
| STAT_PX_ST_TH, |
| STAT_PX_ST_FE, |
| STAT_PX_ST_LI, |
| STAT_PX_ST_SV, |
| STAT_PX_ST_BE, |
| STAT_PX_ST_END, |
| STAT_PX_ST_FIN, |
| }; |
| |
| extern const char *stat_status_codes[]; |
| |
| /* This function is called from the session-level accept() in order to instanciate |
| * a new stats socket. It returns a positive value upon success, 0 if the session |
| * needs to be closed and ignored, or a negative value upon critical failure. |
| */ |
| static int stats_accept(struct session *s) |
| { |
| s->target = &cli_applet.obj_type; |
| /* no need to initialize the applet, it will start with st0=st1 = 0 */ |
| |
| tv_zero(&s->logs.tv_request); |
| s->logs.t_queue = 0; |
| s->logs.t_connect = 0; |
| s->logs.t_data = 0; |
| s->logs.t_close = 0; |
| s->logs.bytes_in = s->logs.bytes_out = 0; |
| 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->req->flags |= CF_READ_DONTWAIT; /* we plan to read small requests */ |
| |
| if (s->listener->timeout) { |
| s->req->rto = *s->listener->timeout; |
| s->rep->wto = *s->listener->timeout; |
| } |
| return 1; |
| } |
| |
| /* allocate a new stats frontend named <name>, and return it |
| * (or NULL in case of lack of memory). |
| */ |
| static struct proxy *alloc_stats_fe(const char *name, const char *file, int line) |
| { |
| struct proxy *fe; |
| |
| fe = (struct proxy *)calloc(1, sizeof(struct proxy)); |
| if (!fe) |
| return NULL; |
| |
| init_new_proxy(fe); |
| fe->next = proxy; |
| proxy = fe; |
| fe->last_change = now.tv_sec; |
| fe->id = strdup("GLOBAL"); |
| fe->cap = PR_CAP_FE; |
| fe->maxconn = 10; /* default to 10 concurrent connections */ |
| fe->timeout.client = MS_TO_TICKS(10000); /* default timeout of 10 seconds */ |
| fe->conf.file = strdup(file); |
| fe->conf.line = line; |
| fe->accept = stats_accept; |
| |
| /* the stats frontend is the only one able to assign ID #0 */ |
| fe->conf.id.key = fe->uuid = 0; |
| eb32_insert(&used_proxy_id, &fe->conf.id); |
| return fe; |
| } |
| |
| /* 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 will write an |
| * error message into the <err> buffer which will be preallocated. The trailing |
| * '\n' must not be written. The function must be called with <args> pointing to |
| * the first word after "stats". |
| */ |
| static int stats_parse_global(char **args, int section_type, struct proxy *curpx, |
| struct proxy *defpx, const char *file, int line, |
| char **err) |
| { |
| struct bind_conf *bind_conf; |
| struct listener *l; |
| |
| if (!strcmp(args[1], "socket")) { |
| int cur_arg; |
| |
| if (*args[2] == 0) { |
| memprintf(err, "'%s %s' in global section expects an address or a path to a UNIX socket", args[0], args[1]); |
| return -1; |
| } |
| |
| if (!global.stats_fe) { |
| if ((global.stats_fe = alloc_stats_fe("GLOBAL", file, line)) == NULL) { |
| memprintf(err, "'%s %s' : out of memory trying to allocate a frontend", args[0], args[1]); |
| return -1; |
| } |
| } |
| |
| bind_conf = bind_conf_alloc(&global.stats_fe->conf.bind, file, line, args[2]); |
| bind_conf->level = ACCESS_LVL_OPER; /* default access level */ |
| |
| if (!str2listener(args[2], global.stats_fe, bind_conf, file, line, err)) { |
| memprintf(err, "parsing [%s:%d] : '%s %s' : %s\n", |
| file, line, args[0], args[1], err && *err ? *err : "error"); |
| return -1; |
| } |
| |
| cur_arg = 3; |
| while (*args[cur_arg]) { |
| static int bind_dumped; |
| struct bind_kw *kw; |
| |
| kw = bind_find_kw(args[cur_arg]); |
| if (kw) { |
| if (!kw->parse) { |
| memprintf(err, "'%s %s' : '%s' option is not implemented in this version (check build options).", |
| args[0], args[1], args[cur_arg]); |
| return -1; |
| } |
| |
| if (kw->parse(args, cur_arg, curpx, bind_conf, err) != 0) { |
| if (err && *err) |
| memprintf(err, "'%s %s' : '%s'", args[0], args[1], *err); |
| else |
| memprintf(err, "'%s %s' : error encountered while processing '%s'", |
| args[0], args[1], args[cur_arg]); |
| return -1; |
| } |
| |
| cur_arg += 1 + kw->skip; |
| continue; |
| } |
| |
| if (!bind_dumped) { |
| bind_dump_kws(err); |
| indent_msg(err, 4); |
| bind_dumped = 1; |
| } |
| |
| memprintf(err, "'%s %s' : unknown keyword '%s'.%s%s", |
| args[0], args[1], args[cur_arg], |
| err && *err ? " Registered keywords :" : "", err && *err ? *err : ""); |
| return -1; |
| } |
| |
| list_for_each_entry(l, &bind_conf->listeners, by_bind) { |
| l->maxconn = global.stats_fe->maxconn; |
| l->backlog = global.stats_fe->backlog; |
| l->timeout = &global.stats_fe->timeout.client; |
| l->accept = session_accept; |
| l->handler = process_session; |
| l->options |= LI_O_UNLIMITED; /* don't make the peers subject to global limits */ |
| l->nice = -64; /* we want to boost priority for local stats */ |
| global.maxsock += l->maxconn; |
| } |
| } |
| else if (!strcmp(args[1], "timeout")) { |
| unsigned timeout; |
| const char *res = parse_time_err(args[2], &timeout, TIME_UNIT_MS); |
| |
| if (res) { |
| memprintf(err, "'%s %s' : unexpected character '%c'", args[0], args[1], *res); |
| return -1; |
| } |
| |
| if (!timeout) { |
| memprintf(err, "'%s %s' expects a positive value", args[0], args[1]); |
| return -1; |
| } |
| if (!global.stats_fe) { |
| if ((global.stats_fe = alloc_stats_fe("GLOBAL", file, line)) == NULL) { |
| memprintf(err, "'%s %s' : out of memory trying to allocate a frontend", args[0], args[1]); |
| return -1; |
| } |
| } |
| global.stats_fe->timeout.client = MS_TO_TICKS(timeout); |
| } |
| else if (!strcmp(args[1], "maxconn")) { |
| int maxconn = atol(args[2]); |
| |
| if (maxconn <= 0) { |
| memprintf(err, "'%s %s' expects a positive value", args[0], args[1]); |
| return -1; |
| } |
| |
| if (!global.stats_fe) { |
| if ((global.stats_fe = alloc_stats_fe("GLOBAL", file, line)) == NULL) { |
| memprintf(err, "'%s %s' : out of memory trying to allocate a frontend", args[0], args[1]); |
| return -1; |
| } |
| } |
| global.stats_fe->maxconn = maxconn; |
| } |
| else if (!strcmp(args[1], "bind-process")) { /* enable the socket only on some processes */ |
| int cur_arg = 2; |
| unsigned long set = 0; |
| |
| if (!global.stats_fe) { |
| if ((global.stats_fe = alloc_stats_fe("GLOBAL", file, line)) == NULL) { |
| memprintf(err, "'%s %s' : out of memory trying to allocate a frontend", args[0], args[1]); |
| return -1; |
| } |
| } |
| |
| while (*args[cur_arg]) { |
| unsigned int low, high; |
| |
| if (strcmp(args[cur_arg], "all") == 0) { |
| set = 0; |
| break; |
| } |
| else if (strcmp(args[cur_arg], "odd") == 0) { |
| set |= ~0UL/3UL; /* 0x555....555 */ |
| } |
| else if (strcmp(args[cur_arg], "even") == 0) { |
| set |= (~0UL/3UL) << 1; /* 0xAAA...AAA */ |
| } |
| else if (isdigit((int)*args[cur_arg])) { |
| char *dash = strchr(args[cur_arg], '-'); |
| |
| low = high = str2uic(args[cur_arg]); |
| if (dash) |
| high = str2uic(dash + 1); |
| |
| if (high < low) { |
| unsigned int swap = low; |
| low = high; |
| high = swap; |
| } |
| |
| if (low < 1 || high > LONGBITS) { |
| memprintf(err, "'%s %s' supports process numbers from 1 to %d.\n", |
| args[0], args[1], LONGBITS); |
| return -1; |
| } |
| while (low <= high) |
| set |= 1UL << (low++ - 1); |
| } |
| else { |
| memprintf(err, |
| "'%s %s' expects 'all', 'odd', 'even', or a list of process ranges with numbers from 1 to %d.\n", |
| args[0], args[1], LONGBITS); |
| return -1; |
| } |
| cur_arg++; |
| } |
| global.stats_fe->bind_proc = set; |
| } |
| else { |
| memprintf(err, "'%s' only supports 'socket', 'maxconn', 'bind-process' and 'timeout' (got '%s')", args[0], args[1]); |
| return -1; |
| } |
| return 0; |
| } |
| |
| /* Dumps the stats CSV header to the trash buffer which. The caller is responsible |
| * for clearing it if needed. |
| * NOTE: Some tools happen to rely on the field position instead of its name, |
| * so please only append new fields at the end, never in the middle. |
| */ |
| static void stats_dump_csv_header() |
| { |
| chunk_appendf(&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,qlimit," |
| "pid,iid,sid,throttle,lbtot,tracked,type," |
| "rate,rate_lim,rate_max," |
| "check_status,check_code,check_duration," |
| "hrsp_1xx,hrsp_2xx,hrsp_3xx,hrsp_4xx,hrsp_5xx,hrsp_other,hanafail," |
| "req_rate,req_rate_max,req_tot," |
| "cli_abrt,srv_abrt," |
| "comp_in,comp_out,comp_byp,comp_rsp,lastsess," |
| "\n"); |
| } |
| |
| /* print a string of text buffer to <out>. The format is : |
| * Non-printable chars \t, \n, \r and \e are * encoded in C format. |
| * Other non-printable chars are encoded "\xHH". Space and '\' are also escaped. |
| * Print stopped if null char or <bsize> is reached, or if no more place in the chunk. |
| */ |
| static int dump_text(struct chunk *out, const char *buf, int bsize) |
| { |
| unsigned char c; |
| int ptr = 0; |
| |
| while (buf[ptr] && ptr < bsize) { |
| c = buf[ptr]; |
| if (isprint(c) && isascii(c) && c != '\\' && c != ' ') { |
| if (out->len > out->size - 1) |
| break; |
| out->str[out->len++] = c; |
| } |
| else if (c == '\t' || c == '\n' || c == '\r' || c == '\e' || c == '\\' || c == ' ') { |
| if (out->len > out->size - 2) |
| break; |
| out->str[out->len++] = '\\'; |
| switch (c) { |
| case ' ': c = ' '; break; |
| 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 > out->size - 4) |
| 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]; |
| } |
| ptr++; |
| } |
| |
| return ptr; |
| } |
| |
| /* print a buffer in hexa. |
| * Print stopped if <bsize> is reached, or if no more place in the chunk. |
| */ |
| static int dump_binary(struct chunk *out, const char *buf, int bsize) |
| { |
| unsigned char c; |
| int ptr = 0; |
| |
| while (ptr < bsize) { |
| c = buf[ptr]; |
| |
| if (out->len > out->size - 2) |
| break; |
| out->str[out->len++] = hextab[(c >> 4) & 0xF]; |
| out->str[out->len++] = hextab[c & 0xF]; |
| |
| ptr++; |
| } |
| return ptr; |
| } |
| |
| /* Dump the status of a table to a stream interface's |
| * read buffer. It returns 0 if the output buffer is full |
| * and needs to be called again, otherwise non-zero. |
| */ |
| static int stats_dump_table_head_to_buffer(struct chunk *msg, struct stream_interface *si, |
| struct proxy *proxy, struct proxy *target) |
| { |
| struct session *s = session_from_task(si->owner); |
| |
| chunk_appendf(msg, "# table: %s, type: %s, size:%d, used:%d\n", |
| proxy->id, stktable_types[proxy->table.type].kw, proxy->table.size, proxy->table.current); |
| |
| /* any other information should be dumped here */ |
| |
| if (target && s->listener->bind_conf->level < ACCESS_LVL_OPER) |
| chunk_appendf(msg, "# contents not dumped due to insufficient privileges\n"); |
| |
| if (bi_putchk(si->ib, msg) == -1) |
| return 0; |
| |
| return 1; |
| } |
| |
| /* Dump the a table entry to a stream interface's |
| * read buffer. It returns 0 if the output buffer is full |
| * and needs to be called again, otherwise non-zero. |
| */ |
| static int stats_dump_table_entry_to_buffer(struct chunk *msg, struct stream_interface *si, |
| struct proxy *proxy, struct stksess *entry) |
| { |
| int dt; |
| |
| chunk_appendf(msg, "%p:", entry); |
| |
| if (proxy->table.type == STKTABLE_TYPE_IP) { |
| char addr[INET_ADDRSTRLEN]; |
| inet_ntop(AF_INET, (const void *)&entry->key.key, addr, sizeof(addr)); |
| chunk_appendf(msg, " key=%s", addr); |
| } |
| else if (proxy->table.type == STKTABLE_TYPE_IPV6) { |
| char addr[INET6_ADDRSTRLEN]; |
| inet_ntop(AF_INET6, (const void *)&entry->key.key, addr, sizeof(addr)); |
| chunk_appendf(msg, " key=%s", addr); |
| } |
| else if (proxy->table.type == STKTABLE_TYPE_INTEGER) { |
| chunk_appendf(msg, " key=%u", *(unsigned int *)entry->key.key); |
| } |
| else if (proxy->table.type == STKTABLE_TYPE_STRING) { |
| chunk_appendf(msg, " key="); |
| dump_text(msg, (const char *)entry->key.key, proxy->table.key_size); |
| } |
| else { |
| chunk_appendf(msg, " key="); |
| dump_binary(msg, (const char *)entry->key.key, proxy->table.key_size); |
| } |
| |
| chunk_appendf(msg, " use=%d exp=%d", entry->ref_cnt - 1, tick_remain(now_ms, entry->expire)); |
| |
| for (dt = 0; dt < STKTABLE_DATA_TYPES; dt++) { |
| void *ptr; |
| |
| if (proxy->table.data_ofs[dt] == 0) |
| continue; |
| if (stktable_data_types[dt].arg_type == ARG_T_DELAY) |
| chunk_appendf(msg, " %s(%d)=", stktable_data_types[dt].name, proxy->table.data_arg[dt].u); |
| else |
| chunk_appendf(msg, " %s=", stktable_data_types[dt].name); |
| |
| ptr = stktable_data_ptr(&proxy->table, entry, dt); |
| switch (stktable_data_types[dt].std_type) { |
| case STD_T_SINT: |
| chunk_appendf(msg, "%d", stktable_data_cast(ptr, std_t_sint)); |
| break; |
| case STD_T_UINT: |
| chunk_appendf(msg, "%u", stktable_data_cast(ptr, std_t_uint)); |
| break; |
| case STD_T_ULL: |
| chunk_appendf(msg, "%lld", stktable_data_cast(ptr, std_t_ull)); |
| break; |
| case STD_T_FRQP: |
| chunk_appendf(msg, "%d", |
| read_freq_ctr_period(&stktable_data_cast(ptr, std_t_frqp), |
| proxy->table.data_arg[dt].u)); |
| break; |
| } |
| } |
| chunk_appendf(msg, "\n"); |
| |
| if (bi_putchk(si->ib, msg) == -1) |
| return 0; |
| |
| return 1; |
| } |
| |
| static void stats_sock_table_key_request(struct stream_interface *si, char **args, int action) |
| { |
| struct session *s = session_from_task(si->owner); |
| struct appctx *appctx = __objt_appctx(si->end); |
| struct proxy *px = appctx->ctx.table.target; |
| struct stksess *ts; |
| uint32_t uint32_key; |
| unsigned char ip6_key[sizeof(struct in6_addr)]; |
| long long value; |
| int data_type; |
| int cur_arg; |
| void *ptr; |
| struct freq_ctr_period *frqp; |
| |
| appctx->st0 = STAT_CLI_OUTPUT; |
| |
| if (!*args[4]) { |
| appctx->ctx.cli.msg = "Key value expected\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return; |
| } |
| |
| switch (px->table.type) { |
| case STKTABLE_TYPE_IP: |
| uint32_key = htonl(inetaddr_host(args[4])); |
| static_table_key->key = &uint32_key; |
| break; |
| case STKTABLE_TYPE_IPV6: |
| inet_pton(AF_INET6, args[4], ip6_key); |
| static_table_key->key = &ip6_key; |
| break; |
| case STKTABLE_TYPE_INTEGER: |
| { |
| char *endptr; |
| unsigned long val; |
| errno = 0; |
| val = strtoul(args[4], &endptr, 10); |
| if ((errno == ERANGE && val == ULONG_MAX) || |
| (errno != 0 && val == 0) || endptr == args[4] || |
| val > 0xffffffff) { |
| appctx->ctx.cli.msg = "Invalid key\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return; |
| } |
| uint32_key = (uint32_t) val; |
| static_table_key->key = &uint32_key; |
| break; |
| } |
| break; |
| case STKTABLE_TYPE_STRING: |
| static_table_key->key = args[4]; |
| static_table_key->key_len = strlen(args[4]); |
| break; |
| default: |
| switch (action) { |
| case STAT_CLI_O_TAB: |
| appctx->ctx.cli.msg = "Showing keys from tables of type other than ip, ipv6, string and integer is not supported\n"; |
| break; |
| case STAT_CLI_O_CLR: |
| appctx->ctx.cli.msg = "Removing keys from ip tables of type other than ip, ipv6, string and integer is not supported\n"; |
| break; |
| default: |
| appctx->ctx.cli.msg = "Unknown action\n"; |
| break; |
| } |
| appctx->st0 = STAT_CLI_PRINT; |
| return; |
| } |
| |
| /* check permissions */ |
| if (s->listener->bind_conf->level < ACCESS_LVL_OPER) { |
| appctx->ctx.cli.msg = stats_permission_denied_msg; |
| appctx->st0 = STAT_CLI_PRINT; |
| return; |
| } |
| |
| ts = stktable_lookup_key(&px->table, static_table_key); |
| |
| switch (action) { |
| case STAT_CLI_O_TAB: |
| if (!ts) |
| return; |
| chunk_reset(&trash); |
| if (!stats_dump_table_head_to_buffer(&trash, si, px, px)) |
| return; |
| stats_dump_table_entry_to_buffer(&trash, si, px, ts); |
| return; |
| |
| case STAT_CLI_O_CLR: |
| if (!ts) |
| return; |
| if (ts->ref_cnt) { |
| /* don't delete an entry which is currently referenced */ |
| appctx->ctx.cli.msg = "Entry currently in use, cannot remove\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return; |
| } |
| stksess_kill(&px->table, ts); |
| break; |
| |
| case STAT_CLI_O_SET: |
| if (ts) |
| stktable_touch(&px->table, ts, 1); |
| else { |
| ts = stksess_new(&px->table, static_table_key); |
| if (!ts) { |
| /* don't delete an entry which is currently referenced */ |
| appctx->ctx.cli.msg = "Unable to allocate a new entry\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return; |
| } |
| stktable_store(&px->table, ts, 1); |
| } |
| |
| for (cur_arg = 5; *args[cur_arg]; cur_arg += 2) { |
| if (strncmp(args[cur_arg], "data.", 5) != 0) { |
| appctx->ctx.cli.msg = "\"data.<type>\" followed by a value expected\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return; |
| } |
| |
| data_type = stktable_get_data_type(args[cur_arg] + 5); |
| if (data_type < 0) { |
| appctx->ctx.cli.msg = "Unknown data type\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return; |
| } |
| |
| if (!px->table.data_ofs[data_type]) { |
| appctx->ctx.cli.msg = "Data type not stored in this table\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return; |
| } |
| |
| if (!*args[cur_arg+1] || strl2llrc(args[cur_arg+1], strlen(args[cur_arg+1]), &value) != 0) { |
| appctx->ctx.cli.msg = "Require a valid integer value to store\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return; |
| } |
| |
| ptr = stktable_data_ptr(&px->table, ts, data_type); |
| |
| switch (stktable_data_types[data_type].std_type) { |
| case STD_T_SINT: |
| stktable_data_cast(ptr, std_t_sint) = value; |
| break; |
| case STD_T_UINT: |
| stktable_data_cast(ptr, std_t_uint) = value; |
| break; |
| case STD_T_ULL: |
| stktable_data_cast(ptr, std_t_ull) = value; |
| break; |
| case STD_T_FRQP: |
| /* We set both the current and previous values. That way |
| * the reported frequency is stable during all the period |
| * then slowly fades out. This allows external tools to |
| * push measures without having to update them too often. |
| */ |
| frqp = &stktable_data_cast(ptr, std_t_frqp); |
| frqp->curr_tick = now_ms; |
| frqp->prev_ctr = 0; |
| frqp->curr_ctr = value; |
| break; |
| } |
| } |
| break; |
| |
| default: |
| appctx->ctx.cli.msg = "Unknown action\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| break; |
| } |
| } |
| |
| static void stats_sock_table_data_request(struct stream_interface *si, char **args, int action) |
| { |
| struct appctx *appctx = __objt_appctx(si->end); |
| |
| if (action != STAT_CLI_O_TAB && action != STAT_CLI_O_CLR) { |
| appctx->ctx.cli.msg = "content-based lookup is only supported with the \"show\" and \"clear\" actions"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return; |
| } |
| |
| /* condition on stored data value */ |
| appctx->ctx.table.data_type = stktable_get_data_type(args[3] + 5); |
| if (appctx->ctx.table.data_type < 0) { |
| appctx->ctx.cli.msg = "Unknown data type\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return; |
| } |
| |
| if (!((struct proxy *)appctx->ctx.table.target)->table.data_ofs[appctx->ctx.table.data_type]) { |
| appctx->ctx.cli.msg = "Data type not stored in this table\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return; |
| } |
| |
| appctx->ctx.table.data_op = get_std_op(args[4]); |
| if (appctx->ctx.table.data_op < 0) { |
| appctx->ctx.cli.msg = "Require and operator among \"eq\", \"ne\", \"le\", \"ge\", \"lt\", \"gt\"\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return; |
| } |
| |
| if (!*args[5] || strl2llrc(args[5], strlen(args[5]), &appctx->ctx.table.value) != 0) { |
| appctx->ctx.cli.msg = "Require a valid integer value to compare against\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return; |
| } |
| } |
| |
| static void stats_sock_table_request(struct stream_interface *si, char **args, int action) |
| { |
| struct appctx *appctx = __objt_appctx(si->end); |
| |
| appctx->ctx.table.data_type = -1; |
| appctx->st2 = STAT_ST_INIT; |
| appctx->ctx.table.target = NULL; |
| appctx->ctx.table.proxy = NULL; |
| appctx->ctx.table.entry = NULL; |
| appctx->st0 = action; |
| |
| if (*args[2]) { |
| appctx->ctx.table.target = find_stktable(args[2]); |
| if (!appctx->ctx.table.target) { |
| appctx->ctx.cli.msg = "No such table\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return; |
| } |
| } |
| else { |
| if (action != STAT_CLI_O_TAB) |
| goto err_args; |
| return; |
| } |
| |
| if (strcmp(args[3], "key") == 0) |
| stats_sock_table_key_request(si, args, action); |
| else if (strncmp(args[3], "data.", 5) == 0) |
| stats_sock_table_data_request(si, args, action); |
| else if (*args[3]) |
| goto err_args; |
| |
| return; |
| |
| err_args: |
| switch (action) { |
| case STAT_CLI_O_TAB: |
| appctx->ctx.cli.msg = "Optional argument only supports \"data.<store_data_type>\" <operator> <value> and key <key>\n"; |
| break; |
| case STAT_CLI_O_CLR: |
| appctx->ctx.cli.msg = "Required arguments: <table> \"data.<store_data_type>\" <operator> <value> or <table> key <key>\n"; |
| break; |
| default: |
| appctx->ctx.cli.msg = "Unknown action\n"; |
| break; |
| } |
| appctx->st0 = STAT_CLI_PRINT; |
| } |
| |
| /* Expects to find a frontend named <arg> and returns it, otherwise displays various |
| * adequate error messages and returns NULL. This function also expects the session |
| * level to be admin. |
| */ |
| static struct proxy *expect_frontend_admin(struct session *s, struct stream_interface *si, const char *arg) |
| { |
| struct appctx *appctx = __objt_appctx(si->end); |
| struct proxy *px; |
| |
| if (s->listener->bind_conf->level < ACCESS_LVL_ADMIN) { |
| appctx->ctx.cli.msg = stats_permission_denied_msg; |
| appctx->st0 = STAT_CLI_PRINT; |
| return NULL; |
| } |
| |
| if (!*arg) { |
| appctx->ctx.cli.msg = "A frontend name is expected.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return NULL; |
| } |
| |
| px = findproxy(arg, PR_CAP_FE); |
| if (!px) { |
| appctx->ctx.cli.msg = "No such frontend.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return NULL; |
| } |
| return px; |
| } |
| |
| /* Expects to find a backend and a server in <arg> under the form <backend>/<server>, |
| * and returns the pointer to the server. Otherwise, display adequate error messages |
| * and returns NULL. This function also expects the session level to be admin. Note: |
| * the <arg> is modified to remove the '/'. |
| */ |
| static struct server *expect_server_admin(struct session *s, struct stream_interface *si, char *arg) |
| { |
| struct appctx *appctx = __objt_appctx(si->end); |
| struct proxy *px; |
| struct server *sv; |
| char *line; |
| |
| if (s->listener->bind_conf->level < ACCESS_LVL_ADMIN) { |
| appctx->ctx.cli.msg = stats_permission_denied_msg; |
| appctx->st0 = STAT_CLI_PRINT; |
| return NULL; |
| } |
| |
| /* split "backend/server" and make <line> point to server */ |
| for (line = arg; *line; line++) |
| if (*line == '/') { |
| *line++ = '\0'; |
| break; |
| } |
| |
| if (!*line || !*arg) { |
| appctx->ctx.cli.msg = "Require 'backend/server'.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return NULL; |
| } |
| |
| if (!get_backend_server(arg, line, &px, &sv)) { |
| appctx->ctx.cli.msg = px ? "No such server.\n" : "No such backend.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return NULL; |
| } |
| |
| if (px->state == PR_STSTOPPED) { |
| appctx->ctx.cli.msg = "Proxy is disabled.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return NULL; |
| } |
| |
| return sv; |
| } |
| |
| /* This function is used with map and acl management. It permits to browse |
| * each reference. The variable <getnext> must contain the current node, |
| * <end> point to the root node and the <flags> permit to filter required |
| * nodes. |
| */ |
| static inline |
| struct pat_ref *pat_list_get_next(struct pat_ref *getnext, struct list *end, |
| unsigned int flags) |
| { |
| struct pat_ref *ref = getnext; |
| |
| while (1) { |
| |
| /* Get next list entry. */ |
| ref = LIST_NEXT(&ref->list, struct pat_ref *, list); |
| |
| /* If the entry is the last of the list, return NULL. */ |
| if (&ref->list == end) |
| return NULL; |
| |
| /* If the entry match the flag, return it. */ |
| if (ref->flags & flags) |
| return ref; |
| } |
| } |
| |
| static inline |
| struct pat_ref *pat_ref_lookup_ref(const char *reference) |
| { |
| int id; |
| char *error; |
| |
| /* If the reference starts by a '#', this is numeric id. */ |
| if (reference[0] == '#') { |
| /* Try to convert the numeric id. If the conversion fails, the lookup fails. */ |
| id = strtol(reference + 1, &error, 10); |
| if (*error != '\0') |
| return NULL; |
| |
| /* Perform the unique id lookup. */ |
| return pat_ref_lookupid(id); |
| } |
| |
| /* Perform the string lookup. */ |
| return pat_ref_lookup(reference); |
| } |
| |
| /* This function is used with map and acl management. It permits to browse |
| * each reference. |
| */ |
| static inline |
| struct pattern_expr *pat_expr_get_next(struct pattern_expr *getnext, struct list *end) |
| { |
| struct pattern_expr *expr; |
| expr = LIST_NEXT(&getnext->list, struct pattern_expr *, list); |
| if (&expr->list == end) |
| return NULL; |
| return expr; |
| } |
| |
| /* Processes the stats interpreter on the statistics socket. This function is |
| * called from an applet running in a stream interface. The function returns 1 |
| * if the request was understood, otherwise zero. It sets appctx->st0 to a value |
| * designating the function which will have to process the request, which can |
| * also be the print function to display the return message set into cli.msg. |
| */ |
| static int stats_sock_parse_request(struct stream_interface *si, char *line) |
| { |
| struct session *s = session_from_task(si->owner); |
| struct appctx *appctx = __objt_appctx(si->end); |
| char *args[MAX_STATS_ARGS + 1]; |
| int arg; |
| int i, j; |
| |
| while (isspace((unsigned char)*line)) |
| line++; |
| |
| arg = 0; |
| args[arg] = line; |
| |
| while (*line && arg < MAX_STATS_ARGS) { |
| if (*line == '\\') { |
| line++; |
| if (*line == '\0') |
| break; |
| } |
| else 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; |
| |
| /* remove \ */ |
| arg = 0; |
| while (*args[arg] != '\0') { |
| j = 0; |
| for (i=0; args[arg][i] != '\0'; i++) { |
| if (args[arg][i] == '\\') |
| continue; |
| args[arg][j] = args[arg][i]; |
| j++; |
| } |
| args[arg][j] = '\0'; |
| arg++; |
| } |
| |
| appctx->ctx.stats.flags = 0; |
| if (strcmp(args[0], "show") == 0) { |
| if (strcmp(args[1], "stat") == 0) { |
| if (*args[2] && *args[3] && *args[4]) { |
| appctx->ctx.stats.flags |= STAT_BOUND; |
| appctx->ctx.stats.iid = atoi(args[2]); |
| appctx->ctx.stats.type = atoi(args[3]); |
| appctx->ctx.stats.sid = atoi(args[4]); |
| } |
| |
| appctx->st2 = STAT_ST_INIT; |
| appctx->st0 = STAT_CLI_O_STAT; // stats_dump_stat_to_buffer |
| } |
| else if (strcmp(args[1], "info") == 0) { |
| appctx->st2 = STAT_ST_INIT; |
| appctx->st0 = STAT_CLI_O_INFO; // stats_dump_info_to_buffer |
| } |
| else if (strcmp(args[1], "pools") == 0) { |
| appctx->st2 = STAT_ST_INIT; |
| appctx->st0 = STAT_CLI_O_POOLS; // stats_dump_pools_to_buffer |
| } |
| else if (strcmp(args[1], "sess") == 0) { |
| appctx->st2 = STAT_ST_INIT; |
| if (s->listener->bind_conf->level < ACCESS_LVL_OPER) { |
| appctx->ctx.cli.msg = stats_permission_denied_msg; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| if (*args[2] && strcmp(args[2], "all") == 0) |
| appctx->ctx.sess.target = (void *)-1; |
| else if (*args[2]) |
| appctx->ctx.sess.target = (void *)strtoul(args[2], NULL, 0); |
| else |
| appctx->ctx.sess.target = NULL; |
| appctx->ctx.sess.section = 0; /* start with session status */ |
| appctx->ctx.sess.pos = 0; |
| appctx->st0 = STAT_CLI_O_SESS; // stats_dump_sess_to_buffer |
| } |
| else if (strcmp(args[1], "errors") == 0) { |
| if (s->listener->bind_conf->level < ACCESS_LVL_OPER) { |
| appctx->ctx.cli.msg = stats_permission_denied_msg; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| if (*args[2]) |
| appctx->ctx.errors.iid = atoi(args[2]); |
| else |
| appctx->ctx.errors.iid = -1; |
| appctx->ctx.errors.px = NULL; |
| appctx->st2 = STAT_ST_INIT; |
| appctx->st0 = STAT_CLI_O_ERR; // stats_dump_errors_to_buffer |
| } |
| else if (strcmp(args[1], "table") == 0) { |
| stats_sock_table_request(si, args, STAT_CLI_O_TAB); |
| } |
| else if (strcmp(args[1], "map") == 0 || |
| strcmp(args[1], "acl") == 0) { |
| |
| /* Set ACL or MAP flags. */ |
| if (args[1][0] == 'm') |
| appctx->ctx.map.display_flags = PAT_REF_MAP; |
| else |
| appctx->ctx.map.display_flags = PAT_REF_ACL; |
| |
| /* no parameter: display all map avalaible */ |
| if (!*args[2]) { |
| appctx->st2 = STAT_ST_INIT; |
| appctx->st0 = STAT_CLI_O_PATS; |
| return 1; |
| } |
| |
| /* lookup into the refs and check the map flag */ |
| appctx->ctx.map.ref = pat_ref_lookup_ref(args[2]); |
| if (!appctx->ctx.map.ref || |
| !(appctx->ctx.map.ref->flags & appctx->ctx.map.display_flags)) { |
| if (appctx->ctx.map.display_flags == PAT_REF_MAP) |
| appctx->ctx.cli.msg = "Unknown map identifier. Please use #<id> or <file>.\n"; |
| else |
| appctx->ctx.cli.msg = "Unknown ACL identifier. Please use #<id> or <file>.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| appctx->st2 = STAT_ST_INIT; |
| appctx->st0 = STAT_CLI_O_PAT; |
| } |
| else { /* neither "stat" nor "info" nor "sess" nor "errors" nor "table" */ |
| return 0; |
| } |
| } |
| else if (strcmp(args[0], "clear") == 0) { |
| if (strcmp(args[1], "counters") == 0) { |
| struct proxy *px; |
| struct server *sv; |
| struct listener *li; |
| int clrall = 0; |
| |
| if (strcmp(args[2], "all") == 0) |
| clrall = 1; |
| |
| /* check permissions */ |
| if (s->listener->bind_conf->level < ACCESS_LVL_OPER || |
| (clrall && s->listener->bind_conf->level < ACCESS_LVL_ADMIN)) { |
| appctx->ctx.cli.msg = stats_permission_denied_msg; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| |
| for (px = proxy; px; px = px->next) { |
| if (clrall) { |
| memset(&px->be_counters, 0, sizeof(px->be_counters)); |
| memset(&px->fe_counters, 0, sizeof(px->fe_counters)); |
| } |
| else { |
| px->be_counters.conn_max = 0; |
| px->be_counters.p.http.rps_max = 0; |
| px->be_counters.sps_max = 0; |
| px->be_counters.cps_max = 0; |
| px->be_counters.nbpend_max = 0; |
| |
| px->fe_counters.conn_max = 0; |
| px->fe_counters.p.http.rps_max = 0; |
| px->fe_counters.sps_max = 0; |
| px->fe_counters.cps_max = 0; |
| px->fe_counters.nbpend_max = 0; |
| } |
| |
| for (sv = px->srv; sv; sv = sv->next) |
| if (clrall) |
| memset(&sv->counters, 0, sizeof(sv->counters)); |
| else { |
| sv->counters.cur_sess_max = 0; |
| sv->counters.nbpend_max = 0; |
| sv->counters.sps_max = 0; |
| } |
| |
| list_for_each_entry(li, &px->conf.listeners, by_fe) |
| if (li->counters) { |
| if (clrall) |
| memset(li->counters, 0, sizeof(*li->counters)); |
| else |
| li->counters->conn_max = 0; |
| } |
| } |
| |
| global.cps_max = 0; |
| global.sps_max = 0; |
| return 1; |
| } |
| else if (strcmp(args[1], "table") == 0) { |
| stats_sock_table_request(si, args, STAT_CLI_O_CLR); |
| /* end of processing */ |
| return 1; |
| } |
| else if (strcmp(args[1], "map") == 0 || strcmp(args[1], "acl") == 0) { |
| /* Set ACL or MAP flags. */ |
| if (args[1][0] == 'm') |
| appctx->ctx.map.display_flags = PAT_REF_MAP; |
| else |
| appctx->ctx.map.display_flags = PAT_REF_ACL; |
| |
| /* no parameter */ |
| if (!*args[2]) { |
| if (appctx->ctx.map.display_flags == PAT_REF_MAP) |
| appctx->ctx.cli.msg = "Missing map identifier.\n"; |
| else |
| appctx->ctx.cli.msg = "Missing ACL identifier.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| |
| /* lookup into the refs and check the map flag */ |
| appctx->ctx.map.ref = pat_ref_lookup_ref(args[2]); |
| if (!appctx->ctx.map.ref || |
| !(appctx->ctx.map.ref->flags & appctx->ctx.map.display_flags)) { |
| if (appctx->ctx.map.display_flags == PAT_REF_MAP) |
| appctx->ctx.cli.msg = "Unknown map identifier. Please use #<id> or <file>.\n"; |
| else |
| appctx->ctx.cli.msg = "Unknown ACL identifier. Please use #<id> or <file>.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| |
| /* Clear all. */ |
| pat_ref_prune(appctx->ctx.map.ref); |
| |
| /* return response */ |
| appctx->ctx.cli.msg = "Done.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| else { |
| /* unknown "clear" argument */ |
| return 0; |
| } |
| } |
| else if (strcmp(args[0], "get") == 0) { |
| if (strcmp(args[1], "weight") == 0) { |
| struct proxy *px; |
| struct server *sv; |
| |
| /* split "backend/server" and make <line> point to server */ |
| for (line = args[2]; *line; line++) |
| if (*line == '/') { |
| *line++ = '\0'; |
| break; |
| } |
| |
| if (!*line) { |
| appctx->ctx.cli.msg = "Require 'backend/server'.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| |
| if (!get_backend_server(args[2], line, &px, &sv)) { |
| appctx->ctx.cli.msg = px ? "No such server.\n" : "No such backend.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| |
| /* return server's effective weight at the moment */ |
| snprintf(trash.str, trash.size, "%d (initial %d)\n", sv->uweight, sv->iweight); |
| bi_putstr(si->ib, trash.str); |
| return 1; |
| } |
| else if (strcmp(args[1], "map") == 0 || strcmp(args[1], "acl") == 0) { |
| /* Set flags. */ |
| if (args[1][0] == 'm') |
| appctx->ctx.map.display_flags = PAT_REF_MAP; |
| else |
| appctx->ctx.map.display_flags = PAT_REF_ACL; |
| |
| /* No parameter. */ |
| if (!*args[2] || !*args[3]) { |
| if (appctx->ctx.map.display_flags == PAT_REF_MAP) |
| appctx->ctx.cli.msg = "Missing map identifier and/or key.\n"; |
| else |
| appctx->ctx.cli.msg = "Missing ACL identifier and/or key.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| |
| /* lookup into the maps */ |
| appctx->ctx.map.ref = pat_ref_lookup_ref(args[2]); |
| if (!appctx->ctx.map.ref) { |
| if (appctx->ctx.map.display_flags == PAT_REF_MAP) |
| appctx->ctx.cli.msg = "Unknown map identifier. Please use #<id> or <file>.\n"; |
| else |
| appctx->ctx.cli.msg = "Unknown ACL identifier. Please use #<id> or <file>.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| |
| /* copy input string. The string must be allocated because |
| * it may be used over multiple iterations. It's released |
| * at the end and upon abort anyway. |
| */ |
| appctx->ctx.map.chunk.len = strlen(args[3]); |
| appctx->ctx.map.chunk.size = appctx->ctx.map.chunk.len + 1; |
| appctx->ctx.map.chunk.str = strdup(args[3]); |
| if (!appctx->ctx.map.chunk.str) { |
| appctx->ctx.cli.msg = "Out of memory error.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| |
| /* prepare response */ |
| appctx->st2 = STAT_ST_INIT; |
| appctx->st0 = STAT_CLI_O_MLOOK; |
| } |
| else { /* not "get weight" */ |
| return 0; |
| } |
| } |
| else if (strcmp(args[0], "set") == 0) { |
| if (strcmp(args[1], "weight") == 0) { |
| struct server *sv; |
| const char *warning; |
| |
| sv = expect_server_admin(s, si, args[2]); |
| if (!sv) |
| return 1; |
| |
| warning = server_parse_weight_change_request(sv, args[3]); |
| if (warning) { |
| appctx->ctx.cli.msg = warning; |
| appctx->st0 = STAT_CLI_PRINT; |
| } |
| return 1; |
| } |
| else if (strcmp(args[1], "server") == 0) { |
| struct server *sv; |
| const char *warning; |
| |
| sv = expect_server_admin(s, si, args[2]); |
| if (!sv) |
| return 1; |
| |
| if (strcmp(args[3], "weight") == 0) { |
| warning = server_parse_weight_change_request(sv, args[4]); |
| if (warning) { |
| appctx->ctx.cli.msg = warning; |
| appctx->st0 = STAT_CLI_PRINT; |
| } |
| } |
| else if (strcmp(args[3], "state") == 0) { |
| if (strcmp(args[4], "ready") == 0) |
| srv_adm_set_ready(sv); |
| else if (strcmp(args[4], "drain") == 0) |
| srv_adm_set_drain(sv); |
| else if (strcmp(args[4], "maint") == 0) |
| srv_adm_set_maint(sv); |
| else { |
| appctx->ctx.cli.msg = "'set server <srv> state' expects 'ready', 'drain' and 'maint'.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| } |
| } |
| else if (strcmp(args[3], "health") == 0) { |
| if (sv->track) { |
| appctx->ctx.cli.msg = "cannot change health on a tracking server.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| } |
| else if (strcmp(args[4], "up") == 0) { |
| sv->check.health = sv->check.rise + sv->check.fall - 1; |
| srv_set_running(sv, "changed from CLI"); |
| } |
| else if (strcmp(args[4], "stopping") == 0) { |
| sv->check.health = sv->check.rise + sv->check.fall - 1; |
| srv_set_stopping(sv, "changed from CLI"); |
| } |
| else if (strcmp(args[4], "down") == 0) { |
| sv->check.health = 0; |
| srv_set_stopped(sv, "changed from CLI"); |
| } |
| else { |
| appctx->ctx.cli.msg = "'set server <srv> health' expects 'up', 'stopping', or 'down'.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| } |
| } |
| else if (strcmp(args[3], "agent") == 0) { |
| if (!(sv->agent.state & CHK_ST_ENABLED)) { |
| appctx->ctx.cli.msg = "agent checks are not enabled on this server.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| } |
| else if (strcmp(args[4], "up") == 0) { |
| sv->agent.health = sv->agent.rise + sv->agent.fall - 1; |
| srv_set_running(sv, "changed from CLI"); |
| } |
| else if (strcmp(args[4], "down") == 0) { |
| sv->agent.health = 0; |
| srv_set_stopped(sv, "changed from CLI"); |
| } |
| else { |
| appctx->ctx.cli.msg = "'set server <srv> agent' expects 'up' or 'down'.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| } |
| } |
| else { |
| appctx->ctx.cli.msg = "'set server <srv>' only supports 'agent', 'health', 'state' and 'weight'.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| } |
| return 1; |
| } |
| else if (strcmp(args[1], "timeout") == 0) { |
| if (strcmp(args[2], "cli") == 0) { |
| unsigned timeout; |
| const char *res; |
| |
| if (!*args[3]) { |
| appctx->ctx.cli.msg = "Expects an integer value.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| |
| res = parse_time_err(args[3], &timeout, TIME_UNIT_S); |
| if (res || timeout < 1) { |
| appctx->ctx.cli.msg = "Invalid timeout value.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| |
| s->req->rto = s->rep->wto = 1 + MS_TO_TICKS(timeout*1000); |
| return 1; |
| } |
| else { |
| appctx->ctx.cli.msg = "'set timeout' only supports 'cli'.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| } |
| else if (strcmp(args[1], "maxconn") == 0) { |
| if (strcmp(args[2], "frontend") == 0) { |
| struct proxy *px; |
| struct listener *l; |
| int v; |
| |
| px = expect_frontend_admin(s, si, args[3]); |
| if (!px) |
| return 1; |
| |
| if (!*args[4]) { |
| appctx->ctx.cli.msg = "Integer value expected.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| |
| v = atoi(args[4]); |
| if (v < 0) { |
| appctx->ctx.cli.msg = "Value out of range.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| |
| /* OK, the value is fine, so we assign it to the proxy and to all of |
| * its listeners. The blocked ones will be dequeued. |
| */ |
| px->maxconn = v; |
| list_for_each_entry(l, &px->conf.listeners, by_fe) { |
| l->maxconn = v; |
| if (l->state == LI_FULL) |
| resume_listener(l); |
| } |
| |
| if (px->maxconn > px->feconn && !LIST_ISEMPTY(&s->fe->listener_queue)) |
| dequeue_all_listeners(&s->fe->listener_queue); |
| |
| return 1; |
| } |
| else if (strcmp(args[2], "global") == 0) { |
| int v; |
| |
| if (s->listener->bind_conf->level < ACCESS_LVL_ADMIN) { |
| appctx->ctx.cli.msg = stats_permission_denied_msg; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| |
| if (!*args[3]) { |
| appctx->ctx.cli.msg = "Expects an integer value.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| |
| v = atoi(args[3]); |
| if (v > global.hardmaxconn) { |
| appctx->ctx.cli.msg = "Value out of range.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| |
| /* check for unlimited values */ |
| if (v <= 0) |
| v = global.hardmaxconn; |
| |
| global.maxconn = v; |
| |
| /* Dequeues all of the listeners waiting for a resource */ |
| if (!LIST_ISEMPTY(&global_listener_queue)) |
| dequeue_all_listeners(&global_listener_queue); |
| |
| return 1; |
| } |
| else { |
| appctx->ctx.cli.msg = "'set maxconn' only supports 'frontend' and 'global'.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| } |
| else if (strcmp(args[1], "rate-limit") == 0) { |
| if (strcmp(args[2], "connections") == 0) { |
| if (strcmp(args[3], "global") == 0) { |
| int v; |
| |
| if (s->listener->bind_conf->level < ACCESS_LVL_ADMIN) { |
| appctx->ctx.cli.msg = stats_permission_denied_msg; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| |
| if (!*args[4]) { |
| appctx->ctx.cli.msg = "Expects an integer value.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| |
| v = atoi(args[4]); |
| if (v < 0) { |
| appctx->ctx.cli.msg = "Value out of range.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| |
| global.cps_lim = v; |
| |
| /* Dequeues all of the listeners waiting for a resource */ |
| if (!LIST_ISEMPTY(&global_listener_queue)) |
| dequeue_all_listeners(&global_listener_queue); |
| |
| return 1; |
| } |
| else { |
| appctx->ctx.cli.msg = "'set rate-limit connections' only supports 'global'.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| } |
| else if (strcmp(args[2], "sessions") == 0) { |
| if (strcmp(args[3], "global") == 0) { |
| int v; |
| |
| if (s->listener->bind_conf->level < ACCESS_LVL_ADMIN) { |
| appctx->ctx.cli.msg = stats_permission_denied_msg; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| |
| if (!*args[4]) { |
| appctx->ctx.cli.msg = "Expects an integer value.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| |
| v = atoi(args[4]); |
| if (v < 0) { |
| appctx->ctx.cli.msg = "Value out of range.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| |
| global.sps_lim = v; |
| |
| /* Dequeues all of the listeners waiting for a resource */ |
| if (!LIST_ISEMPTY(&global_listener_queue)) |
| dequeue_all_listeners(&global_listener_queue); |
| |
| return 1; |
| } |
| else { |
| appctx->ctx.cli.msg = "'set rate-limit sessions' only supports 'global'.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| } |
| #ifdef USE_OPENSSL |
| else if (strcmp(args[2], "ssl-sessions") == 0) { |
| if (strcmp(args[3], "global") == 0) { |
| int v; |
| |
| if (s->listener->bind_conf->level < ACCESS_LVL_ADMIN) { |
| appctx->ctx.cli.msg = stats_permission_denied_msg; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| |
| if (!*args[4]) { |
| appctx->ctx.cli.msg = "Expects an integer value.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| |
| v = atoi(args[4]); |
| if (v < 0) { |
| appctx->ctx.cli.msg = "Value out of range.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| |
| global.ssl_lim = v; |
| |
| /* Dequeues all of the listeners waiting for a resource */ |
| if (!LIST_ISEMPTY(&global_listener_queue)) |
| dequeue_all_listeners(&global_listener_queue); |
| |
| return 1; |
| } |
| else { |
| appctx->ctx.cli.msg = "'set rate-limit ssl-sessions' only supports 'global'.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| } |
| #endif |
| else if (strcmp(args[2], "http-compression") == 0) { |
| if (strcmp(args[3], "global") == 0) { |
| int v; |
| |
| if (!*args[4]) { |
| appctx->ctx.cli.msg = "Expects a maximum input byte rate in kB/s.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| |
| v = atoi(args[4]); |
| global.comp_rate_lim = v * 1024; /* Kilo to bytes. */ |
| } |
| else { |
| appctx->ctx.cli.msg = "'set rate-limit http-compression' only supports 'global'.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| } |
| else { |
| appctx->ctx.cli.msg = "'set rate-limit' supports 'connections', 'sessions', 'ssl-sessions', and 'http-compression'.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| } |
| else if (strcmp(args[1], "table") == 0) { |
| stats_sock_table_request(si, args, STAT_CLI_O_SET); |
| } |
| else if (strcmp(args[1], "map") == 0) { |
| char *err; |
| |
| /* Set flags. */ |
| appctx->ctx.map.display_flags = PAT_REF_MAP; |
| |
| /* Expect three parameters: map name, key and new value. */ |
| if (!*args[2] || !*args[3] || !*args[4]) { |
| appctx->ctx.cli.msg = "'set map' expects three parameters: map identifier, key and value.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| |
| /* Lookup the reference in the maps. */ |
| appctx->ctx.map.ref = pat_ref_lookup_ref(args[2]); |
| if (!appctx->ctx.map.ref) { |
| appctx->ctx.cli.msg = "Unknown map identifier. Please use #<id> or <file>.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| |
| /* If the entry identifier start with a '#', it is considered as |
| * pointer id |
| */ |
| if (args[3][0] == '#' && args[3][1] == '0' && args[3][2] == 'x') { |
| struct pat_ref_elt *ref; |
| long long int conv; |
| char *error; |
| |
| /* Convert argument to integer value. */ |
| conv = strtoll(&args[3][1], &error, 16); |
| if (*error != '\0') { |
| appctx->ctx.cli.msg = "Malformed identifier. Please use #<id> or <file>.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| |
| /* Convert and check integer to pointer. */ |
| ref = (struct pat_ref_elt *)(long)conv; |
| if ((long long int)(long)ref != conv) { |
| appctx->ctx.cli.msg = "Malformed identifier. Please use #<id> or <file>.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| |
| /* Try to delete the entry. */ |
| err = NULL; |
| if (!pat_ref_set_by_id(appctx->ctx.map.ref, ref, args[4], &err)) { |
| if (err) |
| memprintf(&err, "%s.\n", err); |
| appctx->ctx.cli.err = err; |
| appctx->st0 = STAT_CLI_PRINT_FREE; |
| return 1; |
| } |
| } |
| else { |
| /* Else, use the entry identifier as pattern |
| * string, and update the value. |
| */ |
| err = NULL; |
| if (!pat_ref_set(appctx->ctx.map.ref, args[3], args[4], &err)) { |
| if (err) |
| memprintf(&err, "%s.\n", err); |
| appctx->ctx.cli.err = err; |
| appctx->st0 = STAT_CLI_PRINT_FREE; |
| return 1; |
| } |
| } |
| |
| /* The set is done, send message. */ |
| appctx->ctx.cli.msg = "Done.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| else { /* unknown "set" parameter */ |
| return 0; |
| } |
| } |
| else if (strcmp(args[0], "enable") == 0) { |
| if (strcmp(args[1], "agent") == 0) { |
| struct server *sv; |
| |
| sv = expect_server_admin(s, si, args[2]); |
| if (!sv) |
| return 1; |
| |
| if (!(sv->agent.state & CHK_ST_CONFIGURED)) { |
| appctx->ctx.cli.msg = "Agent was not configured on this server, cannot enable.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| |
| sv->agent.state |= CHK_ST_ENABLED; |
| return 1; |
| } |
| if (strcmp(args[1], "server") == 0) { |
| struct server *sv; |
| |
| sv = expect_server_admin(s, si, args[2]); |
| if (!sv) |
| return 1; |
| |
| srv_adm_set_ready(sv); |
| return 1; |
| } |
| else if (strcmp(args[1], "frontend") == 0) { |
| struct proxy *px; |
| |
| px = expect_frontend_admin(s, si, args[2]); |
| if (!px) |
| return 1; |
| |
| if (px->state == PR_STSTOPPED) { |
| appctx->ctx.cli.msg = "Frontend was previously shut down, cannot enable.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| |
| if (px->state != PR_STPAUSED) { |
| appctx->ctx.cli.msg = "Frontend is already enabled.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| |
| if (!resume_proxy(px)) { |
| appctx->ctx.cli.msg = "Failed to resume frontend, check logs for precise cause (port conflict?).\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| return 1; |
| } |
| else { /* unknown "enable" parameter */ |
| appctx->ctx.cli.msg = "'enable' only supports 'agent', 'frontend' and 'server'.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| } |
| else if (strcmp(args[0], "disable") == 0) { |
| if (strcmp(args[1], "agent") == 0) { |
| struct server *sv; |
| |
| sv = expect_server_admin(s, si, args[2]); |
| if (!sv) |
| return 1; |
| |
| sv->agent.state &= ~CHK_ST_ENABLED; |
| return 1; |
| } |
| else if (strcmp(args[1], "server") == 0) { |
| struct server *sv; |
| |
| sv = expect_server_admin(s, si, args[2]); |
| if (!sv) |
| return 1; |
| |
| srv_adm_set_maint(sv); |
| return 1; |
| } |
| else if (strcmp(args[1], "frontend") == 0) { |
| struct proxy *px; |
| |
| px = expect_frontend_admin(s, si, args[2]); |
| if (!px) |
| return 1; |
| |
| if (px->state == PR_STSTOPPED) { |
| appctx->ctx.cli.msg = "Frontend was previously shut down, cannot disable.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| |
| if (px->state == PR_STPAUSED) { |
| appctx->ctx.cli.msg = "Frontend is already disabled.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| |
| if (!pause_proxy(px)) { |
| appctx->ctx.cli.msg = "Failed to pause frontend, check logs for precise cause.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| return 1; |
| } |
| else { /* unknown "disable" parameter */ |
| appctx->ctx.cli.msg = "'disable' only supports 'agent', 'frontend' and 'server'.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| } |
| else if (strcmp(args[0], "shutdown") == 0) { |
| if (strcmp(args[1], "frontend") == 0) { |
| struct proxy *px; |
| |
| px = expect_frontend_admin(s, si, args[2]); |
| if (!px) |
| return 1; |
| |
| if (px->state == PR_STSTOPPED) { |
| appctx->ctx.cli.msg = "Frontend was already shut down.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| |
| Warning("Proxy %s stopped (FE: %lld conns, BE: %lld conns).\n", |
| px->id, px->fe_counters.cum_conn, px->be_counters.cum_conn); |
| send_log(px, LOG_WARNING, "Proxy %s stopped (FE: %lld conns, BE: %lld conns).\n", |
| px->id, px->fe_counters.cum_conn, px->be_counters.cum_conn); |
| stop_proxy(px); |
| return 1; |
| } |
| else if (strcmp(args[1], "session") == 0) { |
| struct session *sess, *ptr; |
| |
| if (s->listener->bind_conf->level < ACCESS_LVL_ADMIN) { |
| appctx->ctx.cli.msg = stats_permission_denied_msg; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| |
| if (!*args[2]) { |
| appctx->ctx.cli.msg = "Session pointer expected (use 'show sess').\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| |
| ptr = (void *)strtoul(args[2], NULL, 0); |
| |
| /* first, look for the requested session in the session table */ |
| list_for_each_entry(sess, &sessions, list) { |
| if (sess == ptr) |
| break; |
| } |
| |
| /* do we have the session ? */ |
| if (sess != ptr) { |
| appctx->ctx.cli.msg = "No such session (use 'show sess').\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| |
| session_shutdown(sess, SN_ERR_KILLED); |
| return 1; |
| } |
| else if (strcmp(args[1], "sessions") == 0) { |
| if (strcmp(args[2], "server") == 0) { |
| struct server *sv; |
| struct session *sess, *sess_bck; |
| |
| sv = expect_server_admin(s, si, args[3]); |
| if (!sv) |
| return 1; |
| |
| /* kill all the session that are on this server */ |
| list_for_each_entry_safe(sess, sess_bck, &sv->actconns, by_srv) |
| if (sess->srv_conn == sv) |
| session_shutdown(sess, SN_ERR_KILLED); |
| |
| return 1; |
| } |
| else { |
| appctx->ctx.cli.msg = "'shutdown sessions' only supports 'server'.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| } |
| else { /* unknown "disable" parameter */ |
| appctx->ctx.cli.msg = "'shutdown' only supports 'frontend', 'session' and 'sessions'.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| } |
| else if (strcmp(args[0], "del") == 0) { |
| if (strcmp(args[1], "map") == 0 || strcmp(args[1], "acl") == 0) { |
| if (args[1][0] == 'm') |
| appctx->ctx.map.display_flags = PAT_REF_MAP; |
| else |
| appctx->ctx.map.display_flags = PAT_REF_ACL; |
| |
| /* Expect two parameters: map name and key. */ |
| if (appctx->ctx.map.display_flags == PAT_REF_MAP) { |
| if (!*args[2] || !*args[3]) { |
| appctx->ctx.cli.msg = "This command expects two parameters: map identifier and key.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| } |
| |
| else { |
| if (!*args[2] || !*args[3]) { |
| appctx->ctx.cli.msg = "This command expects two parameters: ACL identifier and key.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| } |
| |
| /* Lookup the reference in the maps. */ |
| appctx->ctx.map.ref = pat_ref_lookup_ref(args[2]); |
| if (!appctx->ctx.map.ref || |
| !(appctx->ctx.map.ref->flags & appctx->ctx.map.display_flags)) { |
| appctx->ctx.cli.msg = "Unknown map identifier. Please use #<id> or <file>.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| |
| /* If the entry identifier start with a '#', it is considered as |
| * pointer id |
| */ |
| if (args[3][0] == '#' && args[3][1] == '0' && args[3][2] == 'x') { |
| struct pat_ref_elt *ref; |
| long long int conv; |
| char *error; |
| |
| /* Convert argument to integer value. */ |
| conv = strtoll(&args[3][1], &error, 16); |
| if (*error != '\0') { |
| appctx->ctx.cli.msg = "Malformed identifier. Please use #<id> or <file>.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| |
| /* Convert and check integer to pointer. */ |
| ref = (struct pat_ref_elt *)(long)conv; |
| if ((long long int)(long)ref != conv) { |
| appctx->ctx.cli.msg = "Malformed identifier. Please use #<id> or <file>.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| |
| /* Try to delete the entry. */ |
| if (!pat_ref_delete_by_id(appctx->ctx.map.ref, ref)) { |
| /* The entry is not found, send message. */ |
| appctx->ctx.cli.msg = "Key not found.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| } |
| else { |
| /* Else, use the entry identifier as pattern |
| * string and try to delete the entry. |
| */ |
| if (!pat_ref_delete(appctx->ctx.map.ref, args[3])) { |
| /* The entry is not found, send message. */ |
| appctx->ctx.cli.msg = "Key not found.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| } |
| |
| /* The deletion is done, send message. */ |
| appctx->ctx.cli.msg = "Done.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| else { /* unknown "del" parameter */ |
| appctx->ctx.cli.msg = "'del' only supports 'map' or 'acl'.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| } |
| else if (strcmp(args[0], "add") == 0) { |
| if (strcmp(args[1], "map") == 0 || |
| strcmp(args[1], "acl") == 0) { |
| int ret; |
| char *err; |
| |
| /* Set flags. */ |
| if (args[1][0] == 'm') |
| appctx->ctx.map.display_flags = PAT_REF_MAP; |
| else |
| appctx->ctx.map.display_flags = PAT_REF_ACL; |
| |
| /* If the keywork is "map", we expect three parameters, if it |
| * is "acl", we expect only two parameters |
| */ |
| if (appctx->ctx.map.display_flags == PAT_REF_MAP) { |
| if (!*args[2] || !*args[3] || !*args[4]) { |
| appctx->ctx.cli.msg = "'add map' expects three parameters: map identifier, key and value.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| } |
| else { |
| if (!*args[2] || !*args[3]) { |
| appctx->ctx.cli.msg = "'add acl' expects two parameters: ACL identifier and pattern.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| } |
| |
| /* Lookup for the reference. */ |
| appctx->ctx.map.ref = pat_ref_lookup_ref(args[2]); |
| if (!appctx->ctx.map.ref) { |
| if (appctx->ctx.map.display_flags == PAT_REF_MAP) |
| appctx->ctx.cli.msg = "Unknown map identifier. Please use #<id> or <file>.\n"; |
| else |
| appctx->ctx.cli.msg = "Unknown ACL identifier. Please use #<id> or <file>.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| |
| /* The command "add acl" is prohibited if the reference |
| * use samples. |
| */ |
| if ((appctx->ctx.map.display_flags & PAT_REF_ACL) && |
| (appctx->ctx.map.ref->flags & PAT_REF_SMP)) { |
| appctx->ctx.cli.msg = "This ACL is shared with a map containing samples. " |
| "You must use the command 'add map' to add values.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| |
| /* Add value. */ |
| err = NULL; |
| if (appctx->ctx.map.display_flags == PAT_REF_MAP) |
| ret = pat_ref_add(appctx->ctx.map.ref, args[3], args[4], &err); |
| else |
| ret = pat_ref_add(appctx->ctx.map.ref, args[3], NULL, &err); |
| if (!ret) { |
| if (err) |
| memprintf(&err, "%s.\n", err); |
| appctx->ctx.cli.err = err; |
| appctx->st0 = STAT_CLI_PRINT_FREE; |
| return 1; |
| } |
| |
| /* The add is done, send message. */ |
| appctx->ctx.cli.msg = "Done.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| else { /* unknown "del" parameter */ |
| appctx->ctx.cli.msg = "'add' only supports 'map'.\n"; |
| appctx->st0 = STAT_CLI_PRINT; |
| return 1; |
| } |
| } |
| else { /* not "show" nor "clear" nor "get" nor "set" nor "enable" nor "disable" */ |
| return 0; |
| } |
| return 1; |
| } |
| |
| /* This I/O handler runs as an applet embedded in a stream interface. It is |
| * used to processes I/O from/to the stats unix socket. The system relies on a |
| * state machine handling requests and various responses. We read a request, |
| * then we process it and send the response, and we possibly display a prompt. |
| * Then we can read again. The state is stored in appctx->st0 and is one of the |
| * STAT_CLI_* constants. appctx->st1 is used to indicate whether prompt is enabled |
| * or not. |
| */ |
| static void cli_io_handler(struct stream_interface *si) |
| { |
| struct appctx *appctx = __objt_appctx(si->end); |
| struct channel *req = si->ob; |
| struct channel *res = si->ib; |
| int reql; |
| int len; |
| |
| if (unlikely(si->state == SI_ST_DIS || si->state == SI_ST_CLO)) |
| goto out; |
| |
| while (1) { |
| if (appctx->st0 == STAT_CLI_INIT) { |
| /* Stats output not initialized yet */ |
| memset(&appctx->ctx.stats, 0, sizeof(appctx->ctx.stats)); |
| appctx->st0 = STAT_CLI_GETREQ; |
| } |
| else if (appctx->st0 == STAT_CLI_END) { |
| /* Let's close for real now. We just close the request |
| * side, the conditions below will complete if needed. |
| */ |
| si_shutw(si); |
| break; |
| } |
| else if (appctx->st0 == STAT_CLI_GETREQ) { |
| /* ensure we have some output room left in the event we |
| * would want to return some info right after parsing. |
| */ |
| if (buffer_almost_full(si->ib->buf)) { |
| si->ib->flags |= CF_WAKE_WRITE; |
| break; |
| } |
| |
| reql = bo_getline(si->ob, trash.str, trash.size); |
| if (reql <= 0) { /* closed or EOL not found */ |
| if (reql == 0) |
| break; |
| appctx->st0 = STAT_CLI_END; |
| continue; |
| } |
| |
| /* seek for a possible semi-colon. If we find one, we |
| * replace it with an LF and skip only this part. |
| */ |
| for (len = 0; len < reql; len++) |
| if (trash.str[len] == ';') { |
| trash.str[len] = '\n'; |
| reql = len + 1; |
| break; |
| } |
| |
| /* now it is time to check that we have a full line, |
| * remove the trailing \n and possibly \r, then cut the |
| * line. |
| */ |
| len = reql - 1; |
| if (trash.str[len] != '\n') { |
| appctx->st0 = STAT_CLI_END; |
| continue; |
| } |
| |
| if (len && trash.str[len-1] == '\r') |
| len--; |
| |
| trash.str[len] = '\0'; |
| |
| appctx->st0 = STAT_CLI_PROMPT; |
| if (len) { |
| if (strcmp(trash.str, "quit") == 0) { |
| appctx->st0 = STAT_CLI_END; |
| continue; |
| } |
| else if (strcmp(trash.str, "prompt") == 0) |
| appctx->st1 = !appctx->st1; |
| else if (strcmp(trash.str, "help") == 0 || |
| !stats_sock_parse_request(si, trash.str)) { |
| appctx->ctx.cli.msg = stats_sock_usage_msg; |
| appctx->st0 = STAT_CLI_PRINT; |
| } |
| /* NB: stats_sock_parse_request() may have put |
| * another STAT_CLI_O_* into appctx->st0. |
| */ |
| } |
| else if (!appctx->st1) { |
| /* if prompt is disabled, print help on empty lines, |
| * so that the user at least knows how to enable |
| * prompt and find help. |
| */ |
| appctx->ctx.cli.msg = stats_sock_usage_msg; |
| appctx->st0 = STAT_CLI_PRINT; |
| } |
| |
| /* re-adjust req buffer */ |
| bo_skip(si->ob, reql); |
| req->flags |= CF_READ_DONTWAIT; /* we plan to read small requests */ |
| } |
| else { /* output functions: first check if the output buffer is closed then abort */ |
| if (res->flags & (CF_SHUTR_NOW|CF_SHUTR)) { |
| appctx->st0 = STAT_CLI_END; |
| continue; |
| } |
| |
| switch (appctx->st0) { |
| case STAT_CLI_PRINT: |
| if (bi_putstr(si->ib, appctx->ctx.cli.msg) != -1) |
| appctx->st0 = STAT_CLI_PROMPT; |
| break; |
| case STAT_CLI_PRINT_FREE: |
| if (bi_putstr(si->ib, appctx->ctx.cli.err) != -1) { |
| free(appctx->ctx.cli.err); |
| appctx->st0 = STAT_CLI_PROMPT; |
| } |
| break; |
| case STAT_CLI_O_INFO: |
| if (stats_dump_info_to_buffer(si)) |
| appctx->st0 = STAT_CLI_PROMPT; |
| break; |
| case STAT_CLI_O_STAT: |
| if (stats_dump_stat_to_buffer(si, NULL)) |
| appctx->st0 = STAT_CLI_PROMPT; |
| break; |
| case STAT_CLI_O_SESS: |
| if (stats_dump_sess_to_buffer(si)) |
| appctx->st0 = STAT_CLI_PROMPT; |
| break; |
| case STAT_CLI_O_ERR: /* errors dump */ |
| if (stats_dump_errors_to_buffer(si)) |
| appctx->st0 = STAT_CLI_PROMPT; |
| break; |
| case STAT_CLI_O_TAB: |
| case STAT_CLI_O_CLR: |
| if (stats_table_request(si, appctx->st0)) |
| appctx->st0 = STAT_CLI_PROMPT; |
| break; |
| case STAT_CLI_O_PATS: |
| if (stats_pats_list(si)) |
| appctx->st0 = STAT_CLI_PROMPT; |
| break; |
| case STAT_CLI_O_PAT: |
| if (stats_pat_list(si)) |
| appctx->st0 = STAT_CLI_PROMPT; |
| break; |
| case STAT_CLI_O_MLOOK: |
| if (stats_map_lookup(si)) |
| appctx->st0 = STAT_CLI_PROMPT; |
| break; |
| case STAT_CLI_O_POOLS: |
| if (stats_dump_pools_to_buffer(si)) |
| appctx->st0 = STAT_CLI_PROMPT; |
| break; |
| default: /* abnormal state */ |
| appctx->st0 = STAT_CLI_PROMPT; |
| break; |
| } |
| |
| /* The post-command prompt is either LF alone or LF + '> ' in interactive mode */ |
| if (appctx->st0 == STAT_CLI_PROMPT) { |
| if (bi_putstr(si->ib, appctx->st1 ? "\n> " : "\n") != -1) |
| appctx->st0 = STAT_CLI_GETREQ; |
| } |
| |
| /* If the output functions are still there, it means they require more room. */ |
| if (appctx->st0 >= STAT_CLI_OUTPUT) |
| break; |
| |
| /* Now we close the output if one of the writers did so, |
| * or if we're not in interactive mode and the request |
| * buffer is empty. This still allows pipelined requests |
| * to be sent in non-interactive mode. |
| */ |
| if ((res->flags & (CF_SHUTW|CF_SHUTW_NOW)) || (!appctx->st1 && !req->buf->o)) { |
| appctx->st0 = STAT_CLI_END; |
| continue; |
| } |
| |
| /* switch state back to GETREQ to read next requests */ |
| appctx->st0 = STAT_CLI_GETREQ; |
| } |
| } |
| |
| if ((res->flags & CF_SHUTR) && (si->state == SI_ST_EST) && (appctx->st0 != STAT_CLI_GETREQ)) { |
| DPRINTF(stderr, "%s@%d: si to buf closed. req=%08x, res=%08x, st=%d\n", |
| __FUNCTION__, __LINE__, req->flags, res->flags, si->state); |
| /* Other side has closed, let's abort if we have no more processing to do |
| * and nothing more to consume. This is comparable to a broken pipe, so |
| * we forward the close to the request side so that it flows upstream to |
| * the client. |
| */ |
| si_shutw(si); |
| } |
| |
| if ((req->flags & CF_SHUTW) && (si->state == SI_ST_EST) && (appctx->st0 < STAT_CLI_OUTPUT)) { |
| DPRINTF(stderr, "%s@%d: buf to si closed. req=%08x, res=%08x, st=%d\n", |
| __FUNCTION__, __LINE__, req->flags, res->flags, si->state); |
| /* We have no more processing to do, and nothing more to send, and |
| * the client side has closed. So we'll forward this state downstream |
| * on the response buffer. |
| */ |
| si_shutr(si); |
| res->flags |= CF_READ_NULL; |
| } |
| |
| /* update all other flags and resync with the other side */ |
| si_update(si); |
| |
| /* we don't want to expire timeouts while we're processing requests */ |
| si->ib->rex = TICK_ETERNITY; |
| si->ob->wex = TICK_ETERNITY; |
| |
| out: |
| DPRINTF(stderr, "%s@%d: st=%d, rqf=%x, rpf=%x, rqh=%d, rqs=%d, rh=%d, rs=%d\n", |
| __FUNCTION__, __LINE__, |
| si->state, req->flags, res->flags, req->buf->i, req->buf->o, res->buf->i, res->buf->o); |
| |
| if (unlikely(si->state == SI_ST_DIS || si->state == SI_ST_CLO)) { |
| /* check that we have released everything then unregister */ |
| stream_int_unregister_handler(si); |
| } |
| } |
| |
| /* This function dumps information onto the stream interface's read buffer. |
| * It returns 0 as long as it does not complete, non-zero upon completion. |
| * No state is used. |
| */ |
| static int stats_dump_info_to_buffer(struct stream_interface *si) |
| { |
| unsigned int up = (now.tv_sec - start_date.tv_sec); |
| |
| chunk_printf(&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" |
| "Hard_maxconn: %d\n" |
| "CurrConns: %d\n" |
| "CumConns: %d\n" |
| "CumReq: %d\n" |
| #ifdef USE_OPENSSL |
| "MaxSslConns: %d\n" |
| "CurrSslConns: %d\n" |
| "CumSslConns: %d\n" |
| #endif |
| "Maxpipes: %d\n" |
| "PipesUsed: %d\n" |
| "PipesFree: %d\n" |
| "ConnRate: %d\n" |
| "ConnRateLimit: %d\n" |
| "MaxConnRate: %d\n" |
| "SessRate: %d\n" |
| "SessRateLimit: %d\n" |
| "MaxSessRate: %d\n" |
| #ifdef USE_OPENSSL |
| "SslRate: %d\n" |
| "SslRateLimit: %d\n" |
| "MaxSslRate: %d\n" |
| #endif |
| "CompressBpsIn: %u\n" |
| "CompressBpsOut: %u\n" |
| "CompressBpsRateLim: %u\n" |
| #ifdef USE_ZLIB |
| "ZlibMemUsage: %ld\n" |
| "MaxZlibMemUsage: %ld\n" |
| #endif |
| "Tasks: %d\n" |
| "Run_queue: %d\n" |
| "Idle_pct: %d\n" |
| "node: %s\n" |
| "description: %s\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.hardmaxconn, |
| actconn, totalconn, global.req_count, |
| #ifdef USE_OPENSSL |
| global.maxsslconn, sslconns, totalsslconns, |
| #endif |
| global.maxpipes, pipes_used, pipes_free, |
| read_freq_ctr(&global.conn_per_sec), global.cps_lim, global.cps_max, |
| read_freq_ctr(&global.sess_per_sec), global.sps_lim, global.sps_max, |
| #ifdef USE_OPENSSL |
| read_freq_ctr(&global.ssl_per_sec), global.ssl_lim, global.ssl_max, |
| #endif |
| read_freq_ctr(&global.comp_bps_in), read_freq_ctr(&global.comp_bps_out), |
| global.comp_rate_lim, |
| #ifdef USE_ZLIB |
| zlib_used_memory, global.maxzlibmem, |
| #endif |
| nb_tasks_cur, run_queue_cur, idle_pct, |
| global.node, global.desc ? global.desc : "" |
| ); |
| |
| if (bi_putchk(si->ib, &trash) == -1) |
| return 0; |
| |
| return 1; |
| } |
| |
| /* This function dumps memory usage information onto the stream interface's |
| * read buffer. It returns 0 as long as it does not complete, non-zero upon |
| * completion. No state is used. |
| */ |
| static int stats_dump_pools_to_buffer(struct stream_interface *si) |
| { |
| dump_pools_to_trash(); |
| if (bi_putchk(si->ib, &trash) == -1) |
| return 0; |
| return 1; |
| } |
| |
| /* Dumps a frontend's line to the trash for the current proxy <px> and uses |
| * the state from stream interface <si>. The caller is responsible for clearing |
| * the trash if needed. Returns non-zero if it emits anything, zero otherwise. |
| */ |
| static int stats_dump_fe_stats(struct stream_interface *si, struct proxy *px) |
| { |
| struct appctx *appctx = __objt_appctx(si->end); |
| int i; |
| |
| if (!(px->cap & PR_CAP_FE)) |
| return 0; |
| |
| if ((appctx->ctx.stats.flags & STAT_BOUND) && !(appctx->ctx.stats.type & (1 << STATS_TYPE_FE))) |
| return 0; |
| |
| if (appctx->ctx.stats.flags & STAT_FMT_HTML) { |
| chunk_appendf(&trash, |
| /* name, queue */ |
| "<tr class=\"frontend\">"); |
| |
| if (px->cap & PR_CAP_BE && px->srv && (appctx->ctx.stats.flags & STAT_ADMIN)) { |
| /* Column sub-heading for Enable or Disable server */ |
| chunk_appendf(&trash, "<td></td>"); |
| } |
| |
| chunk_appendf(&trash, |
| "<td class=ac>" |
| "<a name=\"%s/Frontend\"></a>" |
| "<a class=lfsb href=\"#%s/Frontend\">Frontend</a></td>" |
| "<td colspan=3></td>" |
| "", |
| px->id, px->id); |
| |
| chunk_appendf(&trash, |
| /* sessions rate : current */ |
| "<td><u>%s<div class=tips><table class=det>" |
| "<tr><th>Current connection rate:</th><td>%s/s</td></tr>" |
| "<tr><th>Current session rate:</th><td>%s/s</td></tr>" |
| "", |
| U2H(read_freq_ctr(&px->fe_sess_per_sec)), |
| U2H(read_freq_ctr(&px->fe_conn_per_sec)), |
| U2H(read_freq_ctr(&px->fe_sess_per_sec))); |
| |
| if (px->mode == PR_MODE_HTTP) |
| chunk_appendf(&trash, |
| "<tr><th>Current request rate:</th><td>%s/s</td></tr>", |
| U2H(read_freq_ctr(&px->fe_req_per_sec))); |
| |
| chunk_appendf(&trash, |
| "</table></div></u></td>" |
| /* sessions rate : max */ |
| "<td><u>%s<div class=tips><table class=det>" |
| "<tr><th>Max connection rate:</th><td>%s/s</td></tr>" |
| "<tr><th>Max session rate:</th><td>%s/s</td></tr>" |
| "", |
| U2H(px->fe_counters.sps_max), |
| U2H(px->fe_counters.cps_max), |
| U2H(px->fe_counters.sps_max)); |
| |
| if (px->mode == PR_MODE_HTTP) |
| chunk_appendf(&trash, |
| "<tr><th>Max request rate:</th><td>%s/s</td></tr>", |
| U2H(px->fe_counters.p.http.rps_max)); |
| |
| chunk_appendf(&trash, |
| "</table></div></u></td>" |
| /* sessions rate : limit */ |
| "<td>%s</td>", |
| LIM2A(px->fe_sps_lim, "-")); |
| |
| chunk_appendf(&trash, |
| /* sessions: current, max, limit, total */ |
| "<td>%s</td><td>%s</td><td>%s</td>" |
| "<td><u>%s<div class=tips><table class=det>" |
| "<tr><th>Cum. connections:</th><td>%s</td></tr>" |
| "<tr><th>Cum. sessions:</th><td>%s</td></tr>" |
| "", |
| U2H(px->feconn), U2H(px->fe_counters.conn_max), U2H(px->maxconn), |
| U2H(px->fe_counters.cum_sess), |
| U2H(px->fe_counters.cum_conn), |
| U2H(px->fe_counters.cum_sess)); |
| |
| /* http response (via hover): 1xx, 2xx, 3xx, 4xx, 5xx, other */ |
| if (px->mode == PR_MODE_HTTP) { |
| chunk_appendf(&trash, |
| "<tr><th>Cum. HTTP requests:</th><td>%s</td></tr>" |
| "<tr><th>- HTTP 1xx responses:</th><td>%s</td></tr>" |
| "<tr><th>- HTTP 2xx responses:</th><td>%s</td></tr>" |
| "<tr><th> Compressed 2xx:</th><td>%s</td><td>(%d%%)</td></tr>" |
| "<tr><th>- HTTP 3xx responses:</th><td>%s</td></tr>" |
| "<tr><th>- HTTP 4xx responses:</th><td>%s</td></tr>" |
| "<tr><th>- HTTP 5xx responses:</th><td>%s</td></tr>" |
| "<tr><th>- other responses:</th><td>%s</td></tr>" |
| "<tr><th>Intercepted requests:</th><td>%s</td></tr>" |
| "", |
| U2H(px->fe_counters.p.http.cum_req), |
| U2H(px->fe_counters.p.http.rsp[1]), |
| U2H(px->fe_counters.p.http.rsp[2]), |
| U2H(px->fe_counters.p.http.comp_rsp), |
| px->fe_counters.p.http.rsp[2] ? |
| (int)(100*px->fe_counters.p.http.comp_rsp/px->fe_counters.p.http.rsp[2]) : 0, |
| U2H(px->fe_counters.p.http.rsp[3]), |
| U2H(px->fe_counters.p.http.rsp[4]), |
| U2H(px->fe_counters.p.http.rsp[5]), |
| U2H(px->fe_counters.p.http.rsp[0]), |
| U2H(px->fe_counters.intercepted_req)); |
| } |
| |
| chunk_appendf(&trash, |
| "</table></div></u></td>" |
| /* sessions: lbtot, lastsess */ |
| "<td></td><td></td>" |
| /* bytes : in */ |
| "<td>%s</td>" |
| "", |
| U2H(px->fe_counters.bytes_in)); |
| |
| chunk_appendf(&trash, |
| /* bytes:out + compression stats (via hover): comp_in, comp_out, comp_byp */ |
| "<td>%s%s<div class=tips>compression: in=%lld out=%lld bypassed=%lld savings=%d%%</div>%s</td>", |
| (px->fe_counters.comp_in || px->fe_counters.comp_byp) ? "<u>":"", |
| U2H(px->fe_counters.bytes_out), |
| px->fe_counters.comp_in, px->fe_counters.comp_out, px->fe_counters.comp_byp, |
| px->fe_counters.comp_in ? |
| (int)((px->fe_counters.comp_in - px->fe_counters.comp_out)*100/px->fe_counters.comp_in) : 0, |
| (px->fe_counters.comp_in || px->fe_counters.comp_byp) ? "</u>":""); |
| |
| chunk_appendf(&trash, |
| /* denied: req, resp */ |
| "<td>%s</td><td>%s</td>" |
| /* errors : request, connect, response */ |
| "<td>%s</td><td></td><td></td>" |
| /* warnings: retries, redispatches */ |
| "<td></td><td></td>" |
| /* server status : reflect frontend status */ |
| "<td class=ac>%s</td>" |
| /* rest of server: nothing */ |
| "<td class=ac colspan=8></td></tr>" |
| "", |
| U2H(px->fe_counters.denied_req), U2H(px->fe_counters.denied_resp), |
| U2H(px->fe_counters.failed_req), |
| px->state == PR_STREADY ? "OPEN" : |
| px->state == PR_STFULL ? "FULL" : "STOP"); |
| } |
| else { /* CSV mode */ |
| chunk_appendf(&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," |
| /* check_status, check_code, check_duration */ |
| ",,,", |
| px->id, |
| px->feconn, px->fe_counters.conn_max, px->maxconn, px->fe_counters.cum_sess, |
| px->fe_counters.bytes_in, px->fe_counters.bytes_out, |
| px->fe_counters.denied_req, px->fe_counters.denied_resp, |
| px->fe_counters.failed_req, |
| px->state == PR_STREADY ? "OPEN" : |
| px->state == PR_STFULL ? "FULL" : "STOP", |
| relative_pid, px->uuid, STATS_TYPE_FE, |
| read_freq_ctr(&px->fe_sess_per_sec), |
| px->fe_sps_lim, px->fe_counters.sps_max); |
| |
| /* http response: 1xx, 2xx, 3xx, 4xx, 5xx, other */ |
| if (px->mode == PR_MODE_HTTP) { |
| for (i=1; i<6; i++) |
| chunk_appendf(&trash, "%lld,", px->fe_counters.p.http.rsp[i]); |
| chunk_appendf(&trash, "%lld,", px->fe_counters.p.http.rsp[0]); |
| } |
| else |
| chunk_appendf(&trash, ",,,,,,"); |
| |
| /* failed health analyses */ |
| chunk_appendf(&trash, ","); |
| |
| /* requests : req_rate, req_rate_max, req_tot, */ |
| chunk_appendf(&trash, "%u,%u,%lld,", |
| read_freq_ctr(&px->fe_req_per_sec), |
| px->fe_counters.p.http.rps_max, px->fe_counters.p.http.cum_req); |
| |
| /* errors: cli_aborts, srv_aborts */ |
| chunk_appendf(&trash, ",,"); |
| |
| /* compression: in, out, bypassed */ |
| chunk_appendf(&trash, "%lld,%lld,%lld,", |
| px->fe_counters.comp_in, px->fe_counters.comp_out, px->fe_counters.comp_byp); |
| |
| /* compression: comp_rsp */ |
| chunk_appendf(&trash, "%lld,", |
| px->fe_counters.p.http.comp_rsp); |
| |
| /* lastsess */ |
| chunk_appendf(&trash, ","); |
| |
| /* finish with EOL */ |
| chunk_appendf(&trash, "\n"); |
| } |
| return 1; |
| } |
| |
| /* Dumps a line for listener <l> and proxy <px> to the trash and uses the state |
| * from stream interface <si>, and stats flags <flags>. The caller is responsible |
| * for clearing the trash if needed. Returns non-zero if it emits anything, zero |
| * otherwise. |
| */ |
| static int stats_dump_li_stats(struct stream_interface *si, struct proxy *px, struct listener *l, int flags) |
| { |
| struct appctx *appctx = __objt_appctx(si->end); |
| |
| if (appctx->ctx.stats.flags & STAT_FMT_HTML) { |
| chunk_appendf(&trash, "<tr class=socket>"); |
| if (px->cap & PR_CAP_BE && px->srv && (appctx->ctx.stats.flags & STAT_ADMIN)) { |
| /* Column sub-heading for Enable or Disable server */ |
| chunk_appendf(&trash, "<td></td>"); |
| } |
| chunk_appendf(&trash, |
| /* frontend name, listener name */ |
| "<td class=ac><a name=\"%s/+%s\"></a>%s" |
| "<a class=lfsb href=\"#%s/+%s\">%s</a>" |
| "", |
| px->id, l->name, |
| (flags & ST_SHLGNDS)?"<u>":"", |
| px->id, l->name, l->name); |
| |
| if (flags & ST_SHLGNDS) { |
| char str[INET6_ADDRSTRLEN]; |
| int port; |
| |
| chunk_appendf(&trash, "<div class=tips>"); |
| |
| port = get_host_port(&l->addr); |
| switch (addr_to_str(&l->addr, str, sizeof(str))) { |
| case AF_INET: |
| chunk_appendf(&trash, "IPv4: %s:%d, ", str, port); |
| break; |
| case AF_INET6: |
| chunk_appendf(&trash, "IPv6: [%s]:%d, ", str, port); |
| break; |
| case AF_UNIX: |
| chunk_appendf(&trash, "unix, "); |
| break; |
| case -1: |
| chunk_appendf(&trash, "(%s), ", strerror(errno)); |
| break; |
| } |
| |
| /* id */ |
| chunk_appendf(&trash, "id: %d</div>", l->luid); |
| } |
| |
| chunk_appendf(&trash, |
| /* queue */ |
| "%s</td><td colspan=3></td>" |
| /* sessions rate: current, max, limit */ |
| "<td colspan=3> </td>" |
| /* sessions: current, max, limit, total, lbtot, lastsess */ |
| "<td>%s</td><td>%s</td><td>%s</td>" |
| "<td>%s</td><td> </td><td> </td>" |
| /* bytes: in, out */ |
| "<td>%s</td><td>%s</td>" |
| "", |
| (flags & ST_SHLGNDS)?"</u>":"", |
| U2H(l->nbconn), U2H(l->counters->conn_max), U2H(l->maxconn), |
| U2H(l->counters->cum_conn), U2H(l->counters->bytes_in), U2H(l->counters->bytes_out)); |
| |
| chunk_appendf(&trash, |
| /* denied: req, resp */ |
| "<td>%s</td><td>%s</td>" |
| /* errors: request, connect, response */ |
| "<td>%s</td><td></td><td></td>" |
| /* warnings: retries, redispatches */ |
| "<td></td><td></td>" |
| /* server status: reflect listener status */ |
| "<td class=ac>%s</td>" |
| /* rest of server: nothing */ |
| "<td class=ac colspan=8></td></tr>" |
| "", |
| U2H(l->counters->denied_req), U2H(l->counters->denied_resp), |
| U2H(l->counters->failed_req), |
| (l->nbconn < l->maxconn) ? (l->state == LI_LIMITED) ? "WAITING" : "OPEN" : "FULL"); |
| } |
| else { /* CSV mode */ |
| chunk_appendf(&trash, |
| /* pxid, name, queue cur, queue max, */ |
| "%s,%s,,," |
| /* 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 listener status */ |
| "%s," |
| /* rest of server: nothing */ |
| ",,,,,,,," |
| /* pid, iid, sid, throttle, lbtot, tracked, type */ |
| "%d,%d,%d,,,,%d," |
| /* rate, rate_lim, rate_max */ |
| ",,," |
| /* check_status, check_code, check_duration */ |
| ",,," |
| /* http response: 1xx, 2xx, 3xx, 4xx, 5xx, other */ |
| ",,,,,," |
| /* failed health analyses */ |
| "," |
| /* requests : req_rate, req_rate_max, req_tot, */ |
|
|