[MEDIUM] stats: add "show table [<name>]" to dump a stick-table

It is now possible to dump a table's contents with keys, expire,
use count, and various data using the command above on the stats
socket.

"show table" only shows main table stats, while "show table <name>"
dumps table contents, only if the socket level is admin.
This commit is contained in:
Willy Tarreau 2010-07-12 17:55:33 +02:00
parent da7ff64aa9
commit 69f58c8058
3 changed files with 190 additions and 0 deletions

View File

@ -51,6 +51,7 @@
#define STAT_CLI_O_INFO 5 /* dump info/stats */
#define STAT_CLI_O_SESS 6 /* dump sessions */
#define STAT_CLI_O_ERR 7 /* dump errors */
#define STAT_CLI_O_TAB 8 /* dump tables */
int stats_accept(struct session *s);
@ -60,6 +61,7 @@ int stats_dump_raw_to_buffer(struct session *s, struct buffer *rep);
int stats_dump_http(struct session *s, struct buffer *rep, struct uri_auth *uri);
int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri);
int stats_dump_sess_to_buffer(struct session *s, struct buffer *rep);
int stats_dump_table_to_buffer(struct session *s, struct buffer *rep);
int stats_dump_errors_to_buffer(struct session *s, struct buffer *rep);
void http_stats_io_handler(struct stream_interface *si);

View File

@ -230,6 +230,11 @@ struct session {
int ptr; /* <0: headers, >=0 : text pointer to restart from */
int bol; /* pointer to beginning of current line */
} errors;
struct {
void *target; /* table we want to dump, or NULL for all */
struct proxy *proxy; /* table being currently dumped (first if NULL) */
struct stksess *entry; /* last entry we were trying to dump (or first if NULL) */
} table;
struct {
const char *msg; /* pointer to a persistent message to be returned in PRINT state */
} cli;

View File

@ -64,6 +64,7 @@ const char stats_sock_usage_msg[] =
" 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 timeout : change a timeout setting\n"
@ -377,6 +378,16 @@ int stats_sock_parse_request(struct stream_interface *si, char *line)
s->data_state = DATA_ST_INIT;
si->st0 = STAT_CLI_O_ERR; // stats_dump_errors_to_buffer
}
else if (strcmp(args[1], "table") == 0) {
s->data_state = DATA_ST_INIT;
if (*args[2])
s->data_ctx.table.target = findproxy(args[2], 0);
else
s->data_ctx.table.target = NULL;
s->data_ctx.table.proxy = NULL;
s->data_ctx.table.entry = NULL;
si->st0 = STAT_CLI_O_TAB; // stats_dump_table_to_buffer
}
else { /* neither "stat" nor "info" nor "sess" nor "errors"*/
return 0;
}
@ -807,6 +818,10 @@ void stats_io_handler(struct stream_interface *si)
if (stats_dump_errors_to_buffer(s, res))
si->st0 = STAT_CLI_PROMPT;
break;
case STAT_CLI_O_TAB:
if (stats_dump_table_to_buffer(s, res))
si->st0 = STAT_CLI_PROMPT;
break;
default: /* abnormal state */
si->st0 = STAT_CLI_PROMPT;
break;
@ -2769,6 +2784,174 @@ int stats_dump_sess_to_buffer(struct session *s, struct buffer *rep)
}
}
/* This function is called to send output to the response buffer.
* It dumps the tables states onto the output buffer <rep>.
* 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 returns 0 as long as it does not complete, non-zero upon completion.
*/
int stats_dump_table_to_buffer(struct session *s, struct buffer *rep)
{
struct chunk msg;
struct ebmb_node *eb;
int dt;
/*
* We have 3 possible states in s->data_state :
* - DATA_ST_INIT : the first call
* - DATA_ST_INFO : the proxy pointer points to the next table to
* dump, the entry pointer is NULL ;
* - DATA_ST_LIST : the proxy pointer points to the current table
* and the entry pointer points to the next entry to be dumped,
* and the refcount on the next entry is held ;
* - DATA_ST_END : nothing left to dump, the buffer may contain some
* data though.
*/
if (unlikely(rep->flags & (BF_WRITE_ERROR|BF_SHUTW))) {
/* in case of abort, remove any refcount we might have set on an entry */
if (s->data_state == DATA_ST_LIST)
s->data_ctx.table.entry->ref_cnt--;
return 1;
}
chunk_init(&msg, trash, sizeof(trash));
while (s->data_state != DATA_ST_FIN) {
switch (s->data_state) {
case DATA_ST_INIT:
s->data_ctx.table.proxy = s->data_ctx.table.target;
if (!s->data_ctx.table.proxy)
s->data_ctx.table.proxy = proxy;
s->data_ctx.table.entry = NULL;
s->data_state = DATA_ST_INFO;
break;
case DATA_ST_INFO:
if (!s->data_ctx.table.proxy ||
(s->data_ctx.table.target &&
s->data_ctx.table.proxy != s->data_ctx.table.target)) {
s->data_state = DATA_ST_END;
break;
}
if (s->data_ctx.table.proxy->table.size) {
chunk_printf(&msg, "# table: %s, type: %ld, size:%d, used:%d\n",
s->data_ctx.table.proxy->id,
s->data_ctx.table.proxy->table.type,
s->data_ctx.table.proxy->table.size,
s->data_ctx.table.proxy->table.current);
/* any other information should be dumped here */
if (s->data_ctx.table.target &&
s->listener->perm.ux.level < ACCESS_LVL_OPER)
chunk_printf(&msg, "# contents not dumped due to insufficient privileges\n");
if (buffer_feed_chunk(rep, &msg) >= 0)
return 0;
if (s->data_ctx.table.target &&
s->listener->perm.ux.level >= ACCESS_LVL_OPER) {
/* dump entries only if table explicitly requested */
eb = ebmb_first(&s->data_ctx.table.proxy->table.keys);
if (eb) {
s->data_ctx.table.entry = ebmb_entry(eb, struct stksess, key);
s->data_ctx.table.entry->ref_cnt++;
s->data_state = DATA_ST_LIST;
break;
}
}
}
s->data_ctx.table.proxy = s->data_ctx.table.proxy->next;
break;
case DATA_ST_LIST:
chunk_printf(&msg, "%p:", s->data_ctx.table.entry);
if (s->data_ctx.table.proxy->table.type == STKTABLE_TYPE_IP) {
char addr[16];
inet_ntop(AF_INET,
(const void *)&s->data_ctx.table.entry->key.key,
addr, sizeof(addr));
chunk_printf(&msg, " key=%s", addr);
}
else
chunk_printf(&msg, " key=?");
chunk_printf(&msg, " use=%d exp=%d",
s->data_ctx.table.entry->ref_cnt - 1,
tick_remain(now_ms, s->data_ctx.table.entry->expire));
for (dt = 0; dt < STKTABLE_DATA_TYPES; dt++) {
void *ptr;
if (s->data_ctx.table.proxy->table.data_ofs[dt] == 0)
continue;
if (stktable_data_types[dt].arg_type == ARG_T_DELAY)
chunk_printf(&msg, " %s(%d)=",
stktable_data_types[dt].name,
s->data_ctx.table.proxy->table.data_arg[dt].u);
else
chunk_printf(&msg, " %s=", stktable_data_types[dt].name);
ptr = stktable_data_ptr(&s->data_ctx.table.proxy->table,
s->data_ctx.table.entry,
dt);
switch (dt) {
/* all entries using the same type can be folded */
case STKTABLE_DT_SERVER_ID:
case STKTABLE_DT_GPC0:
case STKTABLE_DT_CONN_CNT:
case STKTABLE_DT_CONN_CUR:
case STKTABLE_DT_SESS_CNT:
case STKTABLE_DT_HTTP_REQ_CNT:
case STKTABLE_DT_HTTP_ERR_CNT:
chunk_printf(&msg, "%u", stktable_data_cast(ptr, server_id));
break;
case STKTABLE_DT_CONN_RATE:
case STKTABLE_DT_SESS_RATE:
case STKTABLE_DT_HTTP_REQ_RATE:
case STKTABLE_DT_HTTP_ERR_RATE:
case STKTABLE_DT_BYTES_IN_RATE:
case STKTABLE_DT_BYTES_OUT_RATE:
chunk_printf(&msg, "%d",
read_freq_ctr_period(&stktable_data_cast(ptr, conn_rate),
s->data_ctx.table.proxy->table.data_arg[dt].u));
break;
case STKTABLE_DT_BYTES_IN_CNT:
case STKTABLE_DT_BYTES_OUT_CNT:
chunk_printf(&msg, "%lld", stktable_data_cast(ptr, bytes_in_cnt));
break;
}
}
chunk_printf(&msg, "\n");
if (buffer_feed_chunk(rep, &msg) >= 0)
return 0;
s->data_ctx.table.entry->ref_cnt--;
eb = ebmb_next(&s->data_ctx.table.entry->key);
if (eb) {
s->data_ctx.table.entry = ebmb_entry(eb, struct stksess, key);
s->data_ctx.table.entry->ref_cnt++;
break;
}
s->data_ctx.table.proxy = s->data_ctx.table.proxy->next;
s->data_state = DATA_ST_INFO;
break;
case DATA_ST_END:
s->data_state = DATA_ST_FIN;
break;
}
}
return 1;
}
/* print a line of text buffer (limited to 70 bytes) to <out>. The format is :
* <2 spaces> <offset=5 digits> <space or plus> <space> <70 chars max> <\n>
* which is 60 chars per line. Non-printable chars \t, \n, \r and \e are